Vue使用protobuf与后台交互

1,118 阅读3分钟
  • vue打包后只会保留js,css,html文件
  • 所以需要把 *.proto 文件转化成 *.js 文件
  • 整体思想封装一个request.js来统一管理发起的请求(pb格式的请求)
  • 把接口统一封装到api文件夹中
  • 使用api里的方法来请求后台接口,后续只需要考虑API里的request,response,请求参数
  1. 在src目录下新建一个proto目录,来存放后台提供的*.proto文件

  2. 将*.proto文件生成src/proto/proto.js

    该命令可以直接生成proto.js

    npx pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto

    -w 参数可以指定打包js的包装器,这里用的是commonjs

    当后期服务端经常性更新proto文件时,每次执行命令是一件很痛苦的事情,所以我们可以把命令提取到package.json的scripts中:

    package.json

    "scripts"``: {

    "proto"``: "pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto"``,

    },

    然后直接使用 npm run proto 或者 yarn proto

  3. 封装request.js

    //封装request.js文件 /utils/request.js

    import axios from 'axios'

    import protoRoot from '@/proto/proto'

    import protobuf from 'protobufjs'

    import {Toast} from "vant"``; //toast提示——可以更换自己的toast

    import {serverHost} from "../../config/service"``;

    import store from '../store'

    const httpService = axios.create({

    baseURL: serverHost,``//后台服务地址

    timeout: 60000,

    method: 'post'``,

    headers: {

    'X-Requested-With'``: 'XMLHttpRequest'``,

    'Content-Type'``: 'application/octet-stream'``,

    },

    responseType: 'arraybuffer'

    })

    httpService.interceptors.request.use(config => {

    //传递token

    config.headers[``'access-token'``] = store.state.token

    return config

    })

    // 请求体message

    // const PBMessageRequest = protoRoot.lookup('GetShopListRequest')

    // 响应体的message

    const PBMessageResponse = protoRoot.lookup(``'GetShopListByTagResponse'``)

    //处理返回的结果集通用的解析方法 ——对照proto.js

    function getMessageTypeValue(msgType) {

    const PBMessageType = protoRoot.lookup(``'Result'``)

    const ret = PBMessageType.fields[msgType]

    return ret

    }

    // 将请求数据encode成二进制,encode是proto.js提供的方法

    function transformRequest(data,reqType) {

    const PBMessageRequest = protoRoot.lookup(reqType)

    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)

    const PBMessageType = protoRoot.lookup(``'Result'``)

    // decode响应体

    const decodedResponse = PBMessageType.decode(buf)``//PBMessageResponse.decode(buf)

    if (decodedResponse.data && responseType) {

    const model = protoRoot.lookup(responseType)

    decodedResponse.data.value = model.decode(decodedResponse.data.value)

    }

    return decodedResponse

    } catch (err) {

    return err

    }

    }

    }

    /**

    *

    * @param {*} msgType 接口名称

    * @param {*} requestBody 请求体参数

    * @param {*} responseType 返回值

    */

    function request(msgType, requestBody, responseType, requestType) {

    // 构造公共请求体:PBMessageRequest

    const reqData = requestBody

    // 将对象序列化成请求体实例

    const PBMessageRequest = protoRoot.lookup(requestType)

    const req = PBMessageRequest.create(reqData)

    // PBMessageRequest.encode(data).finish()

    // 这里用到axios的配置项:transformRequest和transformResponse

    // transformRequest 发起请求时,调用transformRequest方法,目的是将req转换成二进制

    // transformResponse 对返回的数据进行处理,目的是将二进制转换成真正的json数据

    const toast = Toast.loading({

    duration: 0, // 持续展示 toast

    forbidClick: true``,

    message: '加载中...'``,

    });

    return httpService.post(msgType, req, {

    transformRequest: (data)=> transformRequest(data,requestType),

    transformResponse: transformResponseFactory(responseType)

    }).then(({data, status}) => {

    // 对请求做处理

    if (status !== 200) {

    const err = new Error(``'服务器异常'``)

    throw err

    }

    console.log(data)

    data.data = data.data ? data.data : {value:{code: 0}}

    Toast.clear();

    return data.data.value

    },(err) => {

    Toast.fail(err.message)

    // console.log(err)

    // Toast.clear();

    throw err

    })

    }

    // 在request下添加一个方法,方便用于处理请求参数

    request.create = function (protoName, obj) {

    console.log(obj)

    return obj

    }

    export default request

  4. 调用request.js

    // /api/shop.js文件

    import request from '../utils/request'

    // params是object类型的请求参数

    // GetShopListRequest 是定义好的请求体model

    // GetShopListByTagResponse 是定义好的响应model

    // /talk/shopListByTag 是接口名称

    // 后面如果再添加接口直接以此类推

    export function getShopList (params) {

    const req = request.create(``'GetShopListRequest'``, params)

    return request(``'/talk/shopListByTag'``, req, 'GetShopListByTagResponse'``, 'GetShopListRequest'``)

    }

    为了统一管理api接口,所以我在这里封装了一下api的各个接口,为了后续的使用

  5. 在.vue中使用接口

    //Vue文件中的使用

    <script>

    import { getShopList } from "../api/shop"``;

    export default {

    methods: {

    //使用api文件下的shop.js请求服务端

    //param为请求参数,参照proto文件内的参数形式传递

    getList() {

    let param = {

    'countryCode'``: 'CN'``,

    'type'``: '2'``,

    'language'``: 'zh-cn'

    }

    getShopList(param).then(res => {

    console.log(res)

    }).``catch``((err) => {

    console.log(err)

    })

    }

    }

    }

    </script>

    总结:

    • 将后端提供的所有的proto文件拷进src/proto文件夹
    • 运行npm run proto 或者 yarn proto 生成proto.js
    • 根据接口枚举在src/api下写接口
    • .vue文件中使用接口。