После значительной работы над моими ошибками, описанными во второй главе этой серии, я решил перейти к последней главе. В этой статье мы остановимся на моих последних открытиях в области разработки серверной части. Я собираюсь показать, как приложение React-native может собирать данные MiBand и передавать их на реальный сервер.
Мой сервер будет основан на микросервисном решении, которое можно легко развернуть благодаря docker-compose. За последние 5-8 лет микросервисы стали популярным решением для решения многих проблем в разработке на стороне сервера. Его значительные возможности в области масштабирования инфраструктуры и эффективное и минимальное время, затрачиваемое на обработку запросов, побудили меня реализовать небольшой серверный API на основе Spring Cloud.
Прежде всего, давайте проверим задачи, которые будут рассмотрены:
- Приложение React-native отправляет запрос на авторизацию на сервере и возвращает токен для дальнейшего доступа к серверному API.
- Приложение React-native отправляет собранные данные с MiBand 3 на сервер.
- Сервер предоставляет собранную информацию о конкретном пользователе по запросу по защищенному каналу.
Наш сервер будет работать по следующей схеме:
Как видите, схема представляет собой распространенный архетип микросервисов на основе Spring Cloud. Были назначены две основные группы:
- Инфраструктурные услуги
- Услуги, связанные с бизнес-логикой
Подробнее об их природе вы найдете ниже, но традиционно я собираюсь начать со стороны клиента.
Обновление на стороне клиента
Говоря о разработке на стороне сервера, я не могу забыть о стороне клиента, поскольку она должна подготовить некоторые определенные данные и отправить их на наш сервер через HTTP. Что касается PoC, я решил сделать простой запрос, который содержит следующую информацию:
- Последнее значение HR, полученное из MiBand 3
- Значение последних пройденных шагов, полученное из MiBand 3
- Мета-информация об устройстве (имя устройства, Mac-адрес, версия прошивки)
- Отметка времени, когда был создан запрос
Такие функции требуют некоторых изменений в нашем React-native UI. Как правило, мы можем разделить нашу задачу на несколько частей:
- Соберите асинхронное хранилище записей, чтобы хранить записи, включая токены, собранные данные с MiBand и сервера
- Напишите простой отдыхающий клиент для отправки запросов на сервер
- Измените экран интерфейса и сделайте возможными сценарии отправки / получения данных
Работая над первым заданием, я внес следующие изменения в код:
JavaScript
1
// constants for common usage among different views
2
// location: ./src/components/commons/global.jsx
3
export default {
4
// Server Related Info
5
SERVER_AUTH_URL_ADDRESS: '192.168.8.118:5000',
6
SERVER_ACCOUNT_URL_ADDRESS: '192.168.8.118:6000',
7
SERVER_DEVICE_URL_ADDRESS: '192.168.8.118:7000',
8
// Async Storage Keys
10
ACCESS_TOKEN_KEY: '@AccessToken',
11
USERNAME_TOKEN_KEY: '@UserName',
12
DEVICE_ID_KEY: '@DeviceId',
13
// AUTH PROCESS DATA
15
AUTHORIZED_STATE: 'authorized',
16
UNAUTHORIZED_STATE: 'unauthorized'
17
};
Чтобы отправить некоторые данные на сервер, мы должны знать адрес сервера в сети. Вы можете заметить один забавный момент, когда globals.jsx содержит статические IP-адреса и порты. В реальном мире такая ситуация встречается довольно редко. Наиболее распространенным решением здесь является использование доменного имени, которое будет преобразовано в публичный IP-адрес. Скорее всего, переведенный IP-адрес будет указывать на службу шлюза, как описано ниже.
Сохранение IP-адресов частными для целей разработки более чем достаточно для проверки основной работы. Просто не забудьте обновить их, как только IP-адрес сервера будет изменен снова.
В этот раз вкладки стали новыми компонентами. " реакция-родной-материал-нижняя навигация " делает свое дело. Идея добавить вкладки возникла почти сразу. Мы должны как-то инициализировать процедуру регистрации и токен. Наконец, записи, собранные с устройства, должны быть отправлены. Без сомнения, текущий интерфейс выглядит немного грубым, но этого достаточно, чтобы показать, как React-Native взаимодействует с сервером.
На вкладке «Band» показан наш классический экран, связанный со сбором данных. Вкладка «Band» выглядит как наш первый экран, на котором мы можем найти устройство MiBand 3, выполнить сопряжение с ним и получить некоторые данные в режиме реального времени. С тех пор ничего существенно не изменилось.
Из официального источника AsyncStorage
- это незашифрованная, асинхронная, постоянная система хранения ключей и значений, которая является глобальной для приложения. Типичный вариант использования этого выглядит следующим образом:
- Объявите
import {AsyncStorage} from 'react-native'
в исходном файле, где он будет использоваться. - Используйте «ключи», чтобы получить доступ на чтение / запись для определенного элемента внутри хранилища.
- Создайте глобальные константы, которые объявляют «ключи» этой карты, чтобы любая часть клиентского приложения имела доступ к нашим данным
Этот подход используется в нашем приложении на следующих страницах: группа, учетная запись, обмен данными. Общие инструкции для начала использования хранилища перечислены ниже:
JavaScript
xxxxxxxxxx
1
import globals from "../../common/globals.jsx";
2
import {AsyncStorage} from 'react-native';
3
//...
4
try {
5
const accessToken = await AsyncStorage.getItem(globals.ACCESS_TOKEN_KEY);
6
console.log('AceesToken: ' + accessToken)
7
if (accessToken !== null) {
8
this.setState({status: globals.AUTHORIZED_STATE})
9
} else {
10
this.setState({status: globals.UNAUTHORIZED_STATE})
11
}
12
} catch (error) { console.log(error) }
13
//...
Однако структура кода была изменена. Теперь мы используем, AsyncStorage
чтобы сохранить в паре deviceId
:
JavaScript
xxxxxxxxxx
1
// Path: ./src/components/tab/band_connector/bandConnector.jsx
2
searchBluetoothDevices = () => {
3
this.setState({ isConnectedWithMiBand: true})
4
NativeModules.DeviceConnector.enableBTAndDiscover( (error, deviceBondLevel) => {
5
this.setState({ deviceBondLevel: deviceBondLevel})
6
AsyncStorage.setItem(globals.DEVICE_ID_KEY, (((1+Math.random())*0x10000)|0).toString(16).substring(1));
7
})
8
this.setState({ bluetoothSearchInterval: setInterval(this.getDeviceInfo, 5000) })
9
}
deviceId
Будут переданы на сервер позже. Экран данных был перемещен в выделенный компонент с тем же именем:
JavaScript
xxxxxxxxxx
1
// Path: ./src/components/common/dataScreen/dataScreen.jsx
2
// ...
3
export default class DataScreen extends React.Component {
4
render() {
6
return (
7
<View>
8
<View>
9
<Text>Heart Beat:</Text>
10
<Text>{this.props.heartBeatRate + ' Bpm'}</Text>
11
</View>
12
<View>
14
<Text>Steps:</Text>
15
<Text>{this.props.steps}</Text>
16
</View>
17
<View>
19
<Text>Battery:</Text>
20
<Text>{this.props.battery + ' %'}</Text>
21
</View>
22
<View>
24
<Text>Device Bond Level:</Text>
25
<Text>{this.props.deviceBondLevel}</Text>
26
</View>
27
</View>
28
);
29
}
30
}
Здесь ничего особенного не осталось. Сам компонент более или менее готов к повторному использованию на вкладке «Обмен данными».
Вкладка «Учетная запись» служит одной цели: передать процессы аутентификации на наш сервер и получить от пользователя соответствующий токен. Как только это будет сделано, «неавторизованный» станет «авторизованным», и мы AsyncStorage
будем содержать этот токен плюс зарегистрированное имя пользователя. Этот токен будет использоваться каждый раз на вкладке «Обмен данными».
Ниже вы можете найти мою текущую реализацию API выборки. Имя пользователя и пароль должны быть предоставлены. Две .then
инструкции отображают наш ответ в формат JSON и вызывают нашу getAccessToken
функцию.
JavaScript
xxxxxxxxxx
1
// Path: ./src/components/common/rest/AccountRequests.jsx
2
// ...
3
signUp = (username, password) => {
4
console.log('Account signUp: ' + username + ' ' + password)
5
return fetch('http://' + globals.SERVER_ACCOUNT_URL_ADDRESS + '/accounts/', {
6
method: 'POST',
7
headers: {
8
Accept: 'application/json',
9
'Content-Type': 'application/json',
10
},
11
body: JSON.stringify({
12
username: username,
13
password: password
14
})
15
})
16
.then((response) => response.json())
17
.then((responseJson) => {
18
console.log('account signUp: ' + responseJson)
19
this.getAccessToken(username, password)
20
})
21
.catch((error) => { console.error(error) });
22
}
23
// ...
Для выполнения signUp
операции я создал специальный метод, который вы можете увидеть выше. Находит пакет REST. Его алгоритм довольно прост: дотроньтесь до конечной точки REST учетной записи, и, если будет получен правильный ответ, getAccessToken
будет вызван, что реализовано ниже:
JavaScript
xxxxxxxxxx
36
1
// Path: ./src/components/common/rest/accountRequests.jsx
2
// ...
3
getAccessToken = (username, password) => {
4
console.log('Account getAccessToken: ' + username + ' ' + password)
5
var details = {
6
"scope": "ui",
7
"username": username,
8
"password": password,
9
"grant_type": "client_credentials"
10
};
11
12
var formBody = [];
13
for (var property in details) {
14
var encodedKey = encodeURIComponent(property);
15
var encodedValue = encodeURIComponent(details[property]);
16
formBody.push(encodedKey + "=" + encodedValue);
17
}
18
formBody = formBody.join("&");
19
return fetch('http://' + globals.SERVER_AUTH_URL_ADDRESS + '/mservicet/oauth/token', {
21
method: 'POST',
22
headers: {
23
Authorization: "Basic YnJvd3Nlcjo=",
24
Accept: "*/*",
25
"Content-Type": "application/x-www-form-urlencoded",
26
},
27
body: formBody
28
})
29
.then((response) => response.json())
30
.then((responseJson) => {
31
console.log('account getAccessToken: ' + responseJson)
32
this.storeData(responseJson.access_token, username)
33
})
34
.catch((error) => { console.error(error) });
35
}
36
// ...
На этот раз мы собираемся связаться с нашим сервисом аутентификации , который может принести нам новый токен пользователя, предоставив имя пользователя и пароль. Вместо JSON для защиты уязвимых данных используется тело x-www-form-urlencoded . Наконец, responseJson.access_token будет содержать желательный токен, который будет использоваться позже.
Была добавлена вкладка «Обмен данными», чтобы сделать возможной связь со службой устройства на стороне сервера. Экран данных был немного изменен, чтобы дополнительно отображать метку данных сервера Плюс к вышесказанному были добавлены новые кнопки действий для обеспечения:
1) Зарегистрировать устройство
2) Отправить данные
3) Получить сервер
Базовый сценарий выглядит так:
1) Сопряжение с вашим устройством
2) Получить некоторые данные из него
3) авторизация с сервером
4) Поделиться собранными данными с сервером
5) Получить подтверждение того, что данные были сохранены там
В качестве доказательства я сделал небольшое демо-видео, которое можно проверить
Сторона сервера
Ссылка GitHub относится к текущему состоянию серверной части для коннектора miBand. Поскольку Spring Cloud выбран для решения всех моих задач на стороне сервера. Эта архитектура была собрана для обслуживания:
Несколько слов о диаграмме. На стороне сервера интенсивно используется docker-compose в режиме dev . Он указывает на определенный блок конфигурации, который включает порты, пароли БД и так далее. Услуги можно разделить на три основные группы:
1) Технические службы (Config, Gateway, Eureka, Auth, RabbitMQ) отвечают за создание среды, в которой другие службы могут работать и правильно взаимодействовать друг с другом.
2) Услуги, связанные с бизнесом (учетная запись, устройство), отвечают за соответствующие запросы, которые приходят от конечных пользователей через шлюз. Они активно общаются со своими экземплярами базы данных и сервисом аутентификации. Это происходит из-за соображений безопасности: каждая служба должна проверять уровень аутентификации конечного пользователя, который только что сделал запрос. После проверки служба может продолжить обработку запросов в соответствии с уже написанной логикой.
3) Экземпляры базы данных являются самостоятельными объектами в схеме, которая по умолчанию имеет только одну ссылку с определенной службой. Это не означает, что доступ из внешнего мира запрещен. Например, мы хотели бы проверить сохраненные данные там в режиме разработки. Тем не менее это остается возможным благодаря нашему режиму разработки, который включает в себя набор портов для каждого из них. В режиме prod такие возможности будут более ограничены.
Поскольку у нас есть более-менее четкая картина того, что происходит на диаграмме, нашим следующим шагом будет настройка проекта, включая создание среды создания докеров.
Настройка среды
Прежде всего, некоторые дополнительные инструменты должны быть установлены. Ниже вы можете найти то, что я использовал во время моей работы над проектом:
- ОС: Ubuntu 19
- IDE: Intellij Idea 19
- IDE плагин: Docker плагин
- Java: openJDK 11
- Maven: v3
- Докер: v19.03.5
Также настоятельно рекомендуется иметь около 5-10 ГБ свободного места для хранения изображений и контейнеров, созданных докером. Также имейте в виду, что память является еще одним ценным ресурсом здесь. Он будет использоваться крайне часто и в больших размерах микро-сервисами проекта.
Настройка проекта
Давайте предположим, что наша машина разработки была подготовлена; Установлены Docker, Java, Maven и IDE, и мы можем попытаться создать наш проект. В основном все сервисы в нашем «зоопарке» будут Java-приложениями. Другая часть относится к настройке БД. Его сценарии будут храниться в отдельной папке. Наша цель здесь состоит в том, чтобы подготовить проекты Spring Boot и настроить их для использования соответствующей конфигурации, которая определяет их назначение. Затронутые услуги:
1) служба конфигурации
2) служба регистрации
3) шлюз-пункт обслуживания
4) аутентификация
5) аккаунт-сервис
Окончательный вид проекта будет выглядеть ниже:
В настоящее время проект имеет только одно место для хранения и настройки переменных среды. Я говорю о файле .env . Docker-compose всегда читает переменные окружения оттуда. Эти переменные включают в себя важные данные, такие как: порты, пароли, дополнительные параметры для запуска служб с включенным режимом отладки и так далее.
Travis CI помогает проверить последние изменения, внесенные в git repo, путем запуска модульных тестов. На данный момент он компилирует источники, запускает тесты и отправляет отчеты о состоянии в службу кодеков. В конце концов travis и codecov сообщают о статусе сборки последнего проекта и текущем тестовом освещении всех модулей с исходниками java внутри.
Микро-сервисы поддерживают Java 11. На этот раз была выбрана версия O penJdk, чтобы избежать любых возможных лицензионных ограничений. Поскольку в Java 11 появилась новая версия сборщика мусора, мне было интересно, как на практике работает новое видение очистки объектов. Приносит ли это дополнительную производительность или нет на самом деле. Честно говоря, эта тема заслуживает отдельной статьи. Надеюсь, что когда-нибудь я достигну этой точки и напишу свои исследования.
Обратите внимание, что каждый модуль, кроме mongodb, имеет свой собственный файл pom.xml . Так как сервисы могут иметь свои собственные дополнительные библиотеки, я решил разделить их. Также вы можете заметить, что файл Docker существует в каждой папке модуля. Они будут использоваться docker compose напрямую, когда появится вопрос о запуске сервера. Вы можете найти там основной файл pom.xml. Сейчас мы можем сосредоточиться на теге его модуля:
XML
XXXXXXXXXX
1
10
1
<! - ... --->
2
< модули >
3
< module > config </ module >
4
< модуль > реестр </ модуль >
5
< модуль > точка доступа </ module >
6
< module > auth-service </ module >
7
< module > account-service </ module >
8
< модуль > устройство-сервис </ module >
9
</ modules >
10
<! - ... --->
Предполагая, что еще один сервис должен быть добавлен по некоторому требованию, вам нужно добавить еще одну запись в этот список.
Настройка службы настройки
Служба конфигурации - это горизонтально масштабируемая служба централизованной конфигурации для распределенных систем.
Основная цель: сохранять и обмениваться конфигурациями между всеми службами, которые есть у сервера при запуске. Конфиги разделяются родным профилем (который можно изменить в любой момент). Совместное использование конфигурации происходит, когда все контейнеры настроены и начали процедуру инициализации.
Каждый сервис в системе, который будет запускаться по существу, должен хранить в своих источниках файл Bootstrap.yml . Он состоит из сетевого адреса, включая порт. Например, мы можем взять файл начальной загрузки из сервиса аккаунта.
YAML
Икс
16
1
весна
2
главная
3
allow-bean-definition-overriding true
4
применение
5
имя аккаунт-сервис
6
облако
7
конфиг
8
uri http // config $ CONFIG_SERVICE_DEV_PORT
9
отказоустойчивый правда
10
пароль $ CONFIG_SERVICE_PASSWORD
11
имя пользователя пользователь
12
профиль родной
13
повторить попытку
14
макс-попыток 10
15
профили
16
активный родной
Профиль был использован «по умолчанию» вместо «по умолчанию». Это было установлено также в основном конфигурационном файле сервиса конфигурации:
YAML
XXXXXXXXXX
1
14
1
весна
2
облако
3
конфиг
4
сервер
5
родной
6
search-location classpath / shared
7
профили
8
активный родной
9
безопасность
10
пользователь
11
пароль $ CONFIG_SERVICE_PASSWORD
12
13
сервер
14
порт $ CONFIG_SERVICE_DEV_PORT
Найдите точки расположения в «общей» папке, где служба конфигурации сохраняет основные настройки для всех более или менее ценных служб. Например, экземпляры RabbitMq и MongoDB вообще не требуют связи с сервисом конфигурации.
Класс приложения Spring Boot имеет только одну ценную аннотацию:
Джава
XXXXXXXXXX
1
14
1
пакет ком . спайкер . конфиг ;
2
3
импорт орг . пружинная рама . загрузки . SpringApplication ;
4
импорт орг . пружинная рама . загрузки . автоконфигурация . SpringBootApplication ;
5
импорт орг . пружинная рама . облако . конфиг . сервер . EnableConfigServer ;
6
7
8
9
открытый класс ConfigApplication {
10
11
public static void main ( String [] args ) {
12
SpringApplication . run ( ConfigApplication . class , args );
13
}
14
}
@EnableConfigServer дает роль конфигурации для нашего текущего модуля. Еще один класс также должен быть объявлен здесь:
Джава
XXXXXXXXXX
1
21
1
пакет ком . спайкер . конфиг ;
2
3
импорт орг . пружинная рама . контекст . аннотация . Конфигурация ;
4
импорт орг . пружинная рама . безопасность . конфиг . аннотация . Интернет . строители . HttpSecurity ;
5
импорт орг . пружинная рама . безопасность . конфиг . аннотация . Интернет . конфигурации . WebSecurityConfigurerAdapter ;
6
7
8
открытый класс SecurityConfig расширяет WebSecurityConfigurerAdapter {
9
10
11
защищенный недействительным Configure ( HttpSecurity HTTP ) бросает исключение {
12
http . csrf (). отключить ();
13
HTTP
14
, authorizeRequests ()
15
, antMatchers ( "/ привод / **" ). allowAll ()
16
, anyRequest (). аутентифицированный ()
17
, и ()
18
, httpBasic ();
19
}
20
21
}
Конфигурация безопасности позволяет отправлять запросы на точку «/ activator /» без какой-либо авторизации, но другие точки ограничены, и только внутренние службы могут иметь к ним доступ, поскольку все они содержат данные аутентификации в файлах начальной загрузки.
Настройка службы реестра
Сервис Netflix Eureka реализует шаблон архитектуры «Обнаружение сервиса». Это позволяет автоматически обнаруживать экземпляры службы, которые могут иметь динамически назначенные адреса из-за автоматического масштабирования, сбоев и обновлений.
После запуска сервера Eureka зарегистрирует службы и предоставит метаданные, включая хост, порт, URL-адрес индикатора работоспособности, домашнюю страницу и т. Д. Eureka получает сообщения пульса от каждого экземпляра, принадлежащего службе. Если сердцебиение не выполняется в течение настраиваемого расписания, экземпляр будет удален из реестра.
Также Eureka предоставляет простой интерфейс для отслеживания запущенных сервисов и количества доступных экземпляров: http://localhost:8761
Чтобы пометить какой-либо сервис как экземпляр Eureka, основной класс SpringBoot должен содержать аннотацию @EnableEurekaServer . В его файле Bootstrap.yml должно быть несколько дополнительных записей:
YAML
XXXXXXXXXX
1
1
эврика
2
клиент
3
registerWithEureka false
4
fetchRegistry false
Эврика не будет регистрироваться сама по себе и не собирается.
Настройка услуги Gateway-point
Служба шлюза будет обрабатывать все входящие запросы в системе и направлять их по заранее заданным маршрутам. Netflix Zuul был выбран, чтобы играть роль шлюза. В основном будут установлены три основных маршрутизатора:
1) маршрут до службы авторизации
2) маршрут к учетной записи сервиса
3) маршрут до устройства обслуживания
После описания файла конфигурации маршрут выглядит следующим образом:
YAML
Икс
24
1
зуул
2
ignoredServices '*'
3
хост
4
Тайм-аут подключения-миллис 320000
5
сокет-таймаут-миллис 320000
6
7
маршруты
8
Аут-сервис
9
путь / mservicet / **
10
URL http // auth-service $ AUTH_SERVICE_DEV_PORT
11
stripPrefix false
12
чувствительные заголовки
13
14
аккаунт-сервис
15
путь / account / **
16
serviceId аккаунт-сервис
17
stripPrefix false
18
чувствительные заголовки
19
20
устройство-сервис
21
путь / устройства / **
22
serviceId устройство-сервис
23
stripPrefix false
24
чувствительные заголовки
Таким образом, API-шлюз является единой точкой входа для всех клиентов, которых мы можем иметь. @EnableZuulProxy используется, чтобы сказать SpringBoot, что наш модуль шлюза будет реализовывать zuul- реализацию точки шлюза.
Настройка службы аутентификации
Служба авторизации берет на себя ответственность за определение прав, которые конечный пользователь может получить, и использовать их для получения разрешенного серверного API. Обеспечиваемый уровень безопасности основан на OAuth2, который фокусируется на простоте разработки для клиента, обеспечивая при этом определенные потоки авторизации для веб-приложений, настольных приложений, мобильных телефонов и устройств в гостиной.
Его основная конфигурация находится в классе OAuth2AuthorizationConfig :
Джава
xxxxxxxxxx
1
50
1
2
3
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
4
private TokenStore tokenStore = new InMemoryTokenStore();
6
8
"authenticationManagerBean") (
9
private AuthenticationManager authenticationManager;
10
12
private MongoUserDetailsService userDetailsService;
13
15
private Environment env;
16
18
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
19
clients.inMemory()
20
.withClient("browser")
21
.authorizedGrantTypes("refresh_token", "password","client_credentials")
22
.scopes("ui")
23
.and()
24
.withClient("account-service")
25
.secret(env.getProperty("ACCOUNT_SERVICE_PASSWORD"))
26
.authorizedGrantTypes("client_credentials", "refresh_token")
27
.scopes("server")
28
.and()
29
.withClient("device-service")
30
.secret(env.getProperty("DEVICE_SERVICE_PASSWORD"))
31
.authorizedGrantTypes("client_credentials", "refresh_token")
32
.scopes("server");
33
}
34
36
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
37
endpoints.tokenStore(tokenStore)
38
.authenticationManager(authenticationManager)
39
.userDetailsService(userDetailsService);
40
}
41
43
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
44
oauthServer
45
.tokenKeyAccess("permitAll()")
46
.checkTokenAccess("isAuthenticated()")
47
.passwordEncoder(NoOpPasswordEncoder.getInstance());
48
}
49
}
Метод configure (ClientDetailsServiceConfigurer clients) содержит основные области безопасности, типы предоставления и список клиентов, которые могут взаимодействовать с нашей инфраструктурой в целом. Тип « браузер » используется для получения токена. Он используется нашим API отдыха на стороне клиента впервые, когда он хочет пройти процедуру SignUp и получить токен по имени пользователя и паролю. Службы учетных записей и устройств используют область « сервер » для межсервисной связи и внутри микросервисной среды. Для этого используется FeignClient, но более подробная информация по этому вопросу будет оставлена ниже.
Настройка службы учетной записи
Содержит бизнес-логику сервера, ориентированного на управление учетными записями. Помимо CRUD операций над объектом Account, он активно общается с сервисом Auth, когда для новых пользователей начинается сценарий SignUp. С другой стороны, служба Device использует API остальных учетных записей для проверки пользователя, который был помещен в тело json. Это происходит для регистрации нового устройства и дальнейшей передачи данных.
Контроллер Rest предоставляется Spring и содержит пару конечных точек для создания новых учетных записей и получения их информации по имени.
Джава
xxxxxxxxxx
1
32
1
package com.spayker.account.controller;
2
import com.spayker.account.domain.Account;
4
import com.spayker.account.domain.User;
5
import com.spayker.account.service.AccountService;
6
import org.springframework.beans.factory.annotation.Autowired;
7
import org.springframework.security.access.prepost.PreAuthorize;
8
import org.springframework.web.bind.annotation.RestController;
9
import org.springframework.web.bind.annotation.RequestMapping;
10
import org.springframework.web.bind.annotation.RequestMethod;
11
import org.springframework.web.bind.annotation.RequestBody;
12
import org.springframework.web.bind.annotation.PathVariable;
13
import javax.validation.Valid;
15
17
public class AccountController {
18
20
private AccountService accountService;
21
("#oauth2.hasScope('server')")
23
(path = "/{name}", method = RequestMethod.GET)
24
public Account getAccountByName( String name) {
25
return accountService.findByName(name);
26
}
27
(path = "/", method = RequestMethod.POST)
29
public Account createNewAccount( User user) {
30
return accountService.create(user);
31
}
32
}
Метод «getAccountByName (...)» помечается аннотацией @PreAuthorize, которая имеет область действия «сервер». Это означает, что какой-то другой сервис будет использовать этот API от FeignClient. В нашем случае сервис Device будет потребителем этого API.
Настройка службы устройства
Он служит для хранения всего, что связано с данными умных часов / полос. React-native клиент может отправлять запросы:
- зарегистрировать новое устройство по имени пользователя
- сохранять / обновлять информацию по определенной смарт-группе (HR, шаги и т. д.)
- получить текущую информацию с точки зрения идентификатора устройства и имени пользователя
Его контроллер покоя выглядит очень похоже на то, что мы уже видели в сервисе Account.
Джава
xxxxxxxxxx
1
34
1
package com.spayker.device.controller;
2
import com.spayker.device.domain.Device;
4
import com.spayker.device.service.DeviceService;
5
import org.springframework.beans.factory.annotation.Autowired;
6
import org.springframework.web.bind.annotation.RestController;
7
import org.springframework.web.bind.annotation.RequestMapping;
8
import org.springframework.web.bind.annotation.RequestMethod;
9
import org.springframework.web.bind.annotation.PathVariable;
10
import org.springframework.web.bind.annotation.RequestBody;
11
import javax.validation.Valid;
13
15
public class DeviceController {
16
18
private DeviceService deviceService;
19
(path = "/{deviceId}", method = RequestMethod.GET)
21
public Device getDeviceById( String deviceId) {
22
return deviceService.findByDeviceId(deviceId);
23
}
24
(path = "/", method = RequestMethod.PUT)
26
public void updateDeviceData( Device device) {
27
deviceService.saveChanges(device);
28
}
29
(path = "/", method = RequestMethod.POST)
31
public Device createNewDevice( Device device) {
32
return deviceService.create(device);
33
}
34
}
Чтобы начать использовать API сервиса Account, был создан соответствующий интерфейс (на основе аннотации @FeignClient )
Джава
xxxxxxxxxx
1
15
1
package com.spayker.device.client;
2
import org.springframework.cloud.openfeign.FeignClient;
4
import org.springframework.http.MediaType;
5
import org.springframework.web.bind.annotation.PathVariable;
6
import org.springframework.web.bind.annotation.RequestMapping;
7
import org.springframework.web.bind.annotation.RequestMethod;
8
name = "account-service") (
10
public interface AccountServiceClient {
11
(method = RequestMethod.GET, value = "/accounts/{name}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
13
String getAccountByName( ("name") String name);
14
}
Обратите внимание на равенство имен методов. Контроллер остатка учетной записи и интерфейс feign должны иметь идентичные имена методов.
Несколько комментариев о базах данных
Говоря о способах хранения данных на сервере, я бы начал с требований. Что касается PoC, я хотел бы увидеть простую в использовании базу данных, которая не требует слишком много времени для настройки, моделирования данных и обслуживания. Реляционный подход не очень удобен для использования прямо сейчас. Поток данных будет меняться довольно часто и время от времени значительно.
Вот почему я уделяю внимание документированным СУБД. Такие хранилища не требуют моделирования данных для быстрого тестирования концепции; представляет данные в формате json, которые могут быть полезны при тестировании REST. MongoDB выглядел достаточно перспективным для моих нужд. Имеет горизонтальное масштабирование, поддержку транзакций, официальные изображения на Docker Hub.
Чтобы сделать возможным использование MongoDB, мне пришлось создать файл Docker, который создаст образ для будущих контейнеров.
YAML
xxxxxxxxxx
1
13
1
FROM mongo:3
2
ADD init.sh /init.sh
4
ADD ./dump /
5
RUN \
7
chmod +x /init.sh && \
8
apt-get update && apt-get dist-upgrade -y && \
9
apt-get install psmisc -y -q && \
10
apt-get autoremove -y && apt-get clean && \
11
rm -rf /var/cache/* && rm -rf /var/lib/apt/lists/*
12
ENTRYPOINT "/init.sh"
Кроме того, файл сценария init.sh был оставлен для создания технических пользователей в уже созданном контейнере и выполнения там сценариев дампа.
Оболочка
xxxxxxxxxx
1
34
1
2
if test -z "$MONGODB_PASSWORD"; then
3
echo "MONGODB_PASSWORD not defined"
4
exit 1
5
fi
6
auth="-u user -p $MONGODB_PASSWORD"
8
# MONGODB USER CREATION
10
(
11
echo "setup mongodb auth"
12
create_user="if (!db.getUser('user')) { db.createUser({ user: 'user', pwd: '$MONGODB_PASSWORD', roles: [ {role:'readWrite', db:'mservicet'} ]}) }"
13
until mongo mservicet --eval "$create_user" || mongo mservicet $auth --eval "$create_user"; do sleep 5; done
14
killall mongod
15
sleep 1
16
killall -9 mongod
17
) &
18
# INIT DUMP EXECUTION
20
(
21
if test -n "$INIT_DUMP"; then
22
echo "execute dump file"
23
until mongo mservicet $auth $INIT_DUMP; do sleep 5; done
24
fi
25
) &
26
echo "start mongodb without auth"
28
chown -R mongodb /data/db
29
gosu mongodb mongod "$@"
30
echo "restarting with auth on"
32
sleep 5
33
exec gosu mongodb /usr/local/bin/docker-entrypoint.sh --auth "$@"
34
Учетные записи и службы устройств имеют свои собственные сценарии дампа. На данный момент они просто создают коллекцию и заполняют ее некоторыми демонстрационными сущностями.
xxxxxxxxxx
1
18
1
/**
2
* Creates pre-filled demo account
3
*/
4
print('dump start');
6
db.accounts.update(
8
{ "_id": "demo" },
9
{
10
"_id": "demo",
11
"lastSeen": new Date(),
12
"note": "demo note",
13
"data": []
14
},
15
{ upsert: true }
16
);
17
print('dump complete');
Сервис Auth имеет свою собственную базу данных mongo для хранения информации, связанной с пользователем, которая может понадобиться при генерации токенов.
Запуск сервера
Как запустить реагирующий нативный клиент, мы уже знали из предыдущих глав. Серверная часть может быть построена по-другому:
1) Скомпилируйте исходники, находящиеся в корневой папке проекта, набрав в терминале:
Оболочка
xxxxxxxxxx
1
1
mvn clean install
2) Запустить юнит-тесты (запускается автоматически перед построением сервисных артефактов)
3) Создать JAR-файлы служб в целевых папках (происходит автоматически после завершения фазы тестирования). Если все пойдет хорошо, вы сообщите следующее:
4) Запустите docker-compose скрипт, набрав:
Оболочка
1
sudo docker-compose -f ./docker-compose.yml -f ./docker-compose.dev.yml up
Возвращаясь к IDE, который был выбран для использования в проекте, я подготовил пару твиков. Они просто облегчают работу с микро-сервисами.
Во-первых, вы можете включить плагин Docker ( настройки -> плагины -> Docker ) и получить практически полный контроль над текущими образами, контейнерами, развернутыми на докер-машине.
С помощью этого плагина ваша жизнь в разработке, отладке и обслуживании микро-сервисов становится значительно проще. Это происходит благодаря хорошо расширенному пользовательскому интерфейсу, который может отображать текущие журналы каждого работающего сервиса, его открытые порты, объявленные системные переменные и так далее. Кроме того, я подготовил и сохранил конфигурацию запуска IDE в папке resources / idea / run / docker . Вы можете импортировать их в свою идею и начать запускать контейнеры немедленно.
Услуги отладки
Отладка сервисов становится реальной проблемой. На активной стадии разработки запускать снова и снова весь мир микро-услуг становится трудоемким. Особенно, когда ваши изменения в какой-то определенной услуге незначительны. Для таких случаев я добавил отладочные порты для режима разработки. Они позволяют установить отладочное соединение с целевым сервисом и на лету проверить, что там происходит. Кроме того, он поддерживает функцию «горячего развертывания» по умолчанию. Все, что тебе нужно:
1) импортировать настройки запуска из папки « resources / idea / run / remote »
2) запустить соответствующую конфигурацию, которая относится к вашему целевому ресурсу
3) начать модифицировать свои источники
4) перекомпилировать измененный исходный файл ( Ctrl + Shift + F9 )
5) дождаться, пока класс (ы) не будут скомпилированы и загружены на стороне сервиса
6) проверить загруженные изменения по активированным контрольным точкам
Заключение
По умолчанию решение на стороне сервера было разработано как PoC. Умный глаз найдет мелкие вещи, которые нужно решить или улучшить. Однако он удовлетворяет основным требованиям, которые были установлены в начале следующим образом:
1) получить запрос авторизации и обработать его
2) отправить обратно токен пользователя, чтобы получить доступ к основным конечным точкам отдыха на сервере
3) окончательно обработать и сохранить данные из мобильного приложения
4) отправляет обратно собранные данные по требованию
Принимая это во внимание, я продолжу полировать и расширять его возможности. Следующая часть, наконец, будет посвящена вопросам поддержки IoS и MiBand 4. Не стесняйтесь спрашивать меня о структуре проекта, его источниках. Чаты были созданы на платформе Gitter. Вы можете найти ссылки на страницах моих проектов на GitHub. Береги себя! 🙂
связи
Разъем Miband 3 на реактив -родной
Серверная часть для коннектора