предисловие
Поскольку в настоящее время компания использует ProtoBuf для взаимодействия с внешними и внутренними данными, она использует базовую библиотеку, написанную великим богом компании с момента присоединения к компании.Обезьяна, вы должны узнать о базовой реализации и записать процесс обучения. здесь.
Краткое введение в Protobuf
Google Protocol Buffer (сокращенно Protobuf) — это легкий и эффективный формат хранения структурированных данных, который не зависит от платформы, языка и расширяем и может использоваться в таких областях, как протоколы связи и хранение данных.
Есть несколько преимуществ:
- 1. Независимая от платформы, независимая от языка, масштабируемая;
- 2. Предоставляет удобную динамическую библиотеку, простую в использовании;
- 3. Скорость парсинга высокая, примерно в 20-100 раз выше, чем у соответствующего XML;
- 4. Сериализованные данные очень лаконичны и компактны.По сравнению с XML объем сериализованных данных составляет от 1/3 до 1/10.
Личный опыт: использование json или protobuf для передачи данных на интерфейсе и сервере на самом деле не имеет значения для разработки, и protobuf должен быть преобразован в json, прежде чем его можно будет использовать. Несколько вещей, которые я думаю, лучше:
- 1. И передняя, и задняя стороны могут использовать protobuf прямо в проекте, без необходимости определять дополнительные модели;
- 2. protobuf можно напрямую использовать в качестве документа для внешних и внутренних данных и интерфейсов, что значительно снижает затраты на связь;
Прежде чем использовать protobuf, интерфейс и поля, определенные внутренним языком, не могут использоваться непосредственно внешним интерфейсом. Связь между внешним и внутренним интерфейсом часто требует ведения документа интерфейса. Если внутренние поля изменены , вам необходимо изменить документ и уведомить внешний интерфейс.Иногда документ Обновление не является своевременным или его легко пропустить, а стоимость связи относительно велика. После использования protobuf файл protobuf единообразно определяется серверной частью,protobuf можно использовать непосредственно как документацию, внешнему интерфейсу нужно только скопировать файл protobuf во внешний проект. Если внутренние поля изменены, просто уведомите интерфейсную часть об обновлении файла protobuf, поскольку серверная часть использует файл protobuf напрямую, поэтому файл protobuf, как правило, не отсутствует или неверен. Со временем эффективность командной работы значительно повысилась.
Много ерунды, давайте перейдем к сути ниже. То, о чем я здесь говорю, в основном используется в vue, который является проектной практикой моей нынешней компании, и вы можете использовать его в качестве справочного материала.
идеи
Необходимо использовать в передней частиprotobuf.jsЭта библиотека для обработки прото файлов.
protobuf.js
protobuf.load("awesome.proto", function(err, root) {...})
protobuf.load("awesome.json", function(err, root) {...})
.proto
protobuf.js
*.proto
*.js
или*.json
, а затем используйте методы, предоставляемые библиотекой, для анализа данных и, наконец, получения объекта данных.
PS: На практике оказалось, что лучше конвертировать в js файл, в конвертированном js файле напрямую определяются некоторые методы на цепочке прототипов, что очень удобно. Поэтому этот метод будет использоваться для парсинга proto позже.
ожидаемый гол
Пакет один в проектеrequest.js
Модуль, надеюсь его можно использовать следующим образом.При вызове апи нужно только указать модель запроса и ответа, а затем передать параметры запроса.Не нужно заботиться о том, как нижележащий слой парсит proto API возвращает объект Promise:
// /api/student.js 定义接口的文件
import request from '@/lib/request'
// params是object类型的请求参数
// school.PBStudentListReq 是定义好的请求体model
// school.PBStudentListRsp 是定义好的响应model
// getStudentList 是接口名称
export function getStudentList (params) {
const req = request.create('school.PBStudentListReq', params)
return request('getStudentList', req, 'school.PBStudentListRsp')
}
// 在HelloWorld.vue中使用
import { getStudentList } from '@/api/student'
export default {
name: 'HelloWorld',
created () {
},
methods: {
_getStudentList () {
const req = {
limit = 20,
offset = 0
}
getStudentList(req).then((res) => {
console.log(res)
}).catch((res) => {
console.error(res)
})
}
}
}
Готов к работе
1. Получите определенный прото-файл.
Хотя синтаксис прост, на самом деле клиентской части не нужно особо заботиться о том, как писать прото-файлы, и обычно она определяется и поддерживается серверной частью. Здесь вы можете напрямую использовать тот, который я определилdemo.
// User.proto
package framework;
syntax = "proto3";
message PBUser {
uint64 user_id = 0;
string name = 1;
string mobile = 2;
}
// Class.proto
package school;
syntax = "proto3";
message PBClass {
uint64 classId = 0;
string name = 1;
}
// Student.proto
package school;
syntax = "proto3";
import "User.proto";
import "Class.proto";
message PBStudent {
uint64 studentId = 0;
PBUser user = 1;
PBClass class = 2;
PBStudentDegree degree = 3;
}
enum PBStudentDegree {
PRIMARY = 0; // 小学生
MIDDLE = 1; // 中学生
SENIOR = 2; // 高中生
COLLEGE = 3; // 大学生
}
message PBStudentListReq {
uint32 offset = 1;
uint32 limit = 2;
}
message PBStudentListRsp {
repeated PBStudent list = 1;
}
// MessageType.proto
package framework;
syntax = "proto3";
// 公共请求体
message PBMessageRequest {
uint32 type = 1; // 消息类型
bytes messageData = 2; // 请求数据
uint64 timestamp = 3; // 客户端时间戳
string version = 4; // api版本号
string token = 14; // 用户登录后服务器返回的 token,用于登录校验
}
// 消息响应包
message PBMessageResponse {
uint32 type = 3; // 消息类型
bytes messageData = 4; // 返回数据
uint32 resultCode = 6; // 返回的结果码
string resultInfo = 7; // 返回的结果消息提示文本(用于错误提示)
}
// 所有的接口
enum PBMessageType {
// 学生相关
getStudentList = 0; // 获取所有学生的列表, PBStudentListReq => PBStudentListRsp
}
На самом деле вам не нужно изучать синтаксис proto, чтобы понять его с первого взгляда. Есть два пространства именframework
а такжеschool
,PBStudent
цитируетсяPBUser
, можно считатьPBStudent
наследоватьPBUser
.
Вообще говоря, внешний и внутренний интерфейсы должны единообразно ограничивать модель запроса и модель ответа, например, какие поля являются обязательными в запросе и какие поля находятся в теле возврата.Здесь мы используемMessageType.proto
изPBMessageRequest
определить поля, необходимые для тела запроса,PBMessageResponse
Определяется как поле возврата тела.
PBMessageType
Это перечисление интерфейсов. Все интерфейсы бэкэнда написаны здесь, а конкретные параметры запроса и типы возврата параметров представлены комментариями. Например, только один интерфейс определен здесьgetStudentList
.
Получите это от бэкэнда*.proto
После напильника можно ли в принципе понять: естьgetStudentList
интерфейс, параметры запросаPBStudentListReq
, возвращаемый параметрPBStudentListRsp
.
Таким образом, прото-файл можно напрямую использовать в качестве документа для внешнего и внутреннего взаимодействия.
шаг
1. Создайте новый проект vue
Также добавить установкуaxios
а такжеprotobufjs
.
# vue create vue-protobuf
# npm install axios protobufjs --save-dev
2. Вsrc
Создайте новый в каталогеproto
каталог для хранения*.proto
файл и скопируйте в него записанный прото-файл.
Каталог проекта в это время иpackage.json
:
3. будет*.proto
генерация файлаsrc/proto/proto.js
(акцент)
protobufjs
Это дает звонокpbjsИнструмент, являющийся артефактом, может быть упакован в файлы xx.json или xx.js по разным параметрам. Например, мы хотим упаковать его в файл json и запустить в корневом каталоге:
npx pbjs -t json src/proto/*.proto > src/proto/proto.json
допустимыйsrc/proto
В каталоге создается файл proto.json, нажмите здесь, чтобы просмотреть его.
Как я уже говорил: практика показала, что упаковка в js-модули — лучшее применение. Я даю окончательную команду прямо здесь
npx pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto
-w
Параметр может указывать обертку для упаковки js.Здесь используется Commonjs.Подробности читайте в документации самостоятельно. Генерируется в каталоге src/proto после выполнения командыproto.js. в хромеconsole.log(proto.js)
один раз:
load
, lookup
и другие очень полезные API, которые мы будем использовать позже.
Для удобства в дальнейшем добавим в скрипт package.json команду:
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"proto": "pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto"
},
После обновления файла proto позже просто нужноnpm run proto
Последний файл proto.js можно восстановить.
4. Инкапсулируйте request.js
После того, как файл proto.js будет сгенерирован во фронтальной части, вы можете приступить к инкапсуляции основных модулей, которые взаимодействуют с серверной частью. Прежде всего, нам нужно знать, что мы используем axios для инициирования http-запросов.
Весь процесс: запуск вызова интерфейса -> request.js превращает данные в бинарные -> передняя часть фактически инициирует запрос -> задняя часть возвращает бинарные данные -> request.js обрабатывает бинарные данные -> получает данные объект.
Можно сказать, что request.js эквивалентен транзитной станции для шифрования и дешифрования. существуетsrc/lib
добавить каталогrequest.js
файл, начните разработку:
Поскольку все наши интерфейсы представляют собой двоичные данные, нам нужно установить заголовок запроса axios и использовать arraybuffer следующим образом:
import axios from 'axios'
const httpService = axios.create({
timeout: 45000,
method: 'post',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/octet-stream'
},
responseType: 'arraybuffer'
})
MessageType.proto
Он определяет перечисление интерфейса, тело запроса и тело ответа, согласованные с серверной частью. Прежде чем инициировать запрос, все запросы должны быть преобразованы в двоичные файлы.Следующее является основной функцией request.js
import protoRoot from '@/proto/proto'
import protobuf from 'protobufjs'
// 请求体message
const PBMessageRequest = protoRoot.lookup('framework.PBMessageRequest')
// 响应体的message
const PBMessageResponse = protoRoot.lookup('framework.PBMessageResponse')
const apiVersion = '1.0.0'
const token = 'my_token'
function getMessageTypeValue(msgType) {
const PBMessageType = protoRoot.lookup('framework.PBMessageType')
const ret = PBMessageType.values[msgType]
return ret
}
/**
*
* @param {*} msgType 接口名称
* @param {*} requestBody 请求体参数
* @param {*} responseType 返回值
*/
function request(msgType, requestBody, responseType) {
// 得到api的枚举值
const _msgType = getMessageTypeValue(msgType)
// 请求需要的数据
const reqData = {
timeStamp: new Date().getTime(),
type: _msgType,
version: apiVersion,
messageData: requestBody,
token: token
}
}
// 将对象序列化成请求体实例
const req = PBMessageRequest.create(reqData)
// 调用axios发起请求
// 这里用到axios的配置项:transformRequest和transformResponse
// transformRequest 发起请求时,调用transformRequest方法,目的是将req转换成二进制
// transformResponse 对返回的数据进行处理,目的是将二进制转换成真正的json数据
return httpService.post('/api', req, {
transformRequest,
transformResponse: transformResponseFactory(responseType)
}).then(({data, status}) => {
// 对请求做处理
if (status !== 200) {
const err = new Error('服务器异常')
throw err
}
console.log(data)
},(err) => {
throw err
})
}
// 将请求数据encode成二进制,encode是proto.js提供的方法
function transformRequest(data) {
return PBMessageRequest.encode(data).finish()
}
function isArrayBuffer (obj) {
return Object.prototype.toString.call(obj) === '[object ArrayBuffer]'
}
function transformResponseFactory(responseType) {
return function transformResponse(rawResponse) {
// 判断response是否是arrayBuffer
if (rawResponse == null || !isArrayBuffer(rawResponse)) {
return rawResponse
}
try {
const buf = protobuf.util.newBuffer(rawResponse)
// decode响应体
const decodedResponse = PBMessageResponse.decode(buf)
if (decodedResponse.messageData && responseType) {
const model = protoRoot.lookup(responseType)
decodedResponse.messageData = model.decode(decodedResponse.messageData)
}
return decodedResponse
} catch (err) {
return err
}
}
}
// 在request下添加一个方法,方便用于处理请求参数
request.create = function (protoName, obj) {
const pbConstruct = protoRoot.lookup(protoName)
return pbConstruct.encode(obj).finish()
}
// 将模块暴露出去
export default request
Окончательный написанный код см.:request.js.
Который используетlookup()
,encode()
, finish()
, decode()
Подождите несколько методов, предоставляемых proto.js.
5. Вызов request.js
Прежде чем файл .vue напрямую вызывает API, мы обычно не используем request.js напрямую для прямой инициации запроса, а инкапсулируем все интерфейсы, потому что при прямом использовании request.js нам нужно указать тело запроса, тело ответа и т. д. фиксированное значение, многократное использование приведет к избыточности кода.
Мы привыкли ставить все back-end интерфейсы в проектsrc/api
каталог, например, интерфейс для студента находится вsrc/api/student.js
файл для удобного управления.
БудуgetStudentList
Интерфейс написан наsrc/api/student.js
середина
import request from '@/lib/request'
// params是object类型的请求参数
// school.PBStudentListReq 是定义好的请求体model
// school.PBStudentListRsp 是定义好的响应model
// getStudentList 是接口名称
export function getStudentList (params) {
const req = request.create('PBStudentListReq', params)
return request('getStudentList', req, 'school.PBStudentListRsp')
}
// 后面如果再添加接口直接以此类推
export function getStudentById (id) {
// const req = ...
// return request(...)
}
6. Использование интерфейсов в .vue
Какой интерфейс нужен, какой интерфейс импортировать, и вернуть объект Promise, что очень удобно.
<template>
<div class="hello">
<button @click="_getStudentList">获取学生列表</button>
</div>
</template>
<script>
import { getStudentList } from '@/api/student'
export default {
name: 'HelloWorld',
methods: {
_getStudentList () {
const req = {
limit: 20,
offset: 0
}
getStudentList(req).then((res) => {
console.log(res)
}).catch((res) => {
console.error(res)
})
}
},
created () {
}
}
</script>
<style lang="scss">
</style>
Суммировать
Код всего демо:demo.
Весь процесс, используемый интерфейсом:
- 1. Скопируйте все прото-файлы, предоставленные серверной частью, в
src/proto
папка - 2. Беги
npm run proto
Сгенерировать proto.js - 3. По перечислению интерфейсов в
src/api
написать интерфейс - 4.
.vue
Интерфейс используется в файле.
(1 и 2, которые можно объединить вместе для написания автоматизированного сценария, каждое обновление может просто запускать этот сценарий).
Написание довольно длинное, и письмо не очень хорошее, пожалуйста, простите меня.
Этот процесс является практикой прототипа во внешнем интерфейсе, который я считаю лучшим, и он может быть не самым лучшим.Если у вас есть другие лучшие практики в вашей компании, вы можете общаться и делиться ими.
следовать за
Для использования в vue лучше упаковать его в модуль js (это связано с тем, что vue упакован только в html, css, js и другие файлы в рабочей среде). Но в некоторых сценариях, например, в среде Node, экспресс-проекту разрешено появляться в производственной среде..proto
файл, на этот раз можно взятьprotobuf.js
Предоставляет другие методы для динамического анализа proto, для которых больше не требуется npm run proto.
Позже, когда у меня будет время, я напишу еще одну запись о динамическом разборе proto на стороне узла.