JianghuJS-协议数据结构

学习目标

  • 学习jianghujs应用协议概念和规范
  • 前端页面进行数据交互练习

1. 应用协议特点

  • 不同端之间所传递的数据,江湖JS所做的规范与设计
  • 应用协议的设计上,进行了统一和简化
  • 能够做到在不同通讯通道间无缝切换

2. Resource请求

在江湖JS里,一个HTTP请求/Socket请求对应数据库_resource表中的一行数据。

我们把请求resource 分成两类:

  • sql resource: 后端根据请求组装出sql,执行并将结果返回给前端
  • service resource: 后端根据请求找出/app/service下对应的service,执行对应的方法并将结果返回给前端

3. Resource请求 & Resource响应

  • 请求: /${appId}/resource
  • 请求方式: POST
  • 请求头:
    • 'Content-Type': 'application/json'
    • 'Accept': 'application/json'
  • 请求body

    where offset limit参数只有在 sql resource场景有用

    协议字段 类型 描述
    packageId String 必填✅ 协议包的唯一id; 可以使用时间戳 + 随机数生成; 比如: 1622720431076_3905352
    packageType String 必填✅ 协议包类型; 比如:'httpRequest', 'socketForward', 'socketRequest'
    appData Object 必填✅ 协议包数据
    --appId String 必填✅ 应用ID; 比如: demo_xiaoapp
    --pageId String 必填✅ 页面ID; 比如: demoPage
    --actionId String 必填✅ 操作ID; 比如: selectStudentList
    --authToken String 必填✅ 用户令牌
    --userAgent String 选填 客户端浏览器信息; 通过window.navigator.userAgent 获取
    --where Object 选填 where条件; 比如: { name: '张三丰', classId: '2021-01级-02班' }
    --whereIn Object 选填 where in查询条件; 比如: { name: ['张三丰', '张无忌'] }
    --whereLike Object 选填 where 模糊查询条件; 比如: { name: '张' }
    --whereOptions Array 选填 where条件 ; 比如: [['name', '=', 'zhangshan'],['level', '>', 3],['name', 'like', '%zhang%']]
    --whereOrOptions Array 选填 where or 条件; 比如: [['name', '=', 'zhangshan'],['level', '>', 3],['a', 100]]
    --offset Number 选填 查询起始位置; 比如: 0
    --limit Number 选填 查询条数,默认查所有; 比如: 10
    --orderBy Array 选填 排序; 比如: [{ column: 'age', order: 'desc' }]
    --actionData Object 选填 操作数据,比如:要保存或更新的数据... { name: '张三丰', level: '03' }
  • 响应body
    字段 类型 描述
    packageId String 必返回✅,请求唯一标识。
    packageType String 必返回✅,标识数据包类型; httpResponse, socketResponse
    status String 必返回✅,标识业务状态; success: 成功, fail: 失败
    timestamp String 必返回✅,响应时间, E.g: 2021-05-24 17:56:59,408"
    appData Object 必返回✅, sql resource or service resource 执行后的结果
    --appId String 必返回✅ 应用ID; 比如: demo_xiaoapp
    --pageId String 必返回✅ 页面ID; 比如: demoPage
    --actionId String 必返回✅ 操作ID; 比如: selectStudentList
    --errorCode String 错误码, 当status==='fail'时返回, E.g: token_expired, request_body_invalid
    --errorReason String 错误码说明, 当status==='fail'时返回, E.g: token失效, params字段字段却失
    --errorReasonSupplement String 错误码说明 补充, E.g: "id must not be null"
    --resultData Object 必返回✅ resource请求的响应数据

参考用例

  1. const package = {
  2. packageId: '123456',
  3. packageType: 'httpRequest',
  4. appData: {
  5. appId: `${window.appInfo.appId}`,
  6. pageId: 'protocolDemo',
  7. actionId: 'insertItem',
  8. userAgent: 'demo_userAgent',
  9. authToken: localStorage.getItem(`${window.appInfo.appId}_authToken`),
  10. actionData: {
  11. studentId: 'G00003',
  12. classId: '2021-01级-02班',
  13. gender: 'male',
  14. level: '02',
  15. name: '小虾米',
  16. },
  17. where: { 字段A: 'A', 字段B: 'B' },
  18. whereIn: { name: ['张三丰', '张无忌'] },
  19. whereLike: { 字段A: 'A', 字段B: 'B' },
  20. whereOptions: [['name', '=', 'zhangshan'],['level', '>', 3]],
  21. whereOrOptions: [['name', '=', 'zhangshan'],['level', '>', 3]],
  22. offset: 10,
  23. limit: 20,
  24. orderBy: [{ column: 'email' }, { column: 'age', order: 'desc' }],
  25. }
  26. };
  27. const result = await axios(package);
  28. const rows = result.data.appData.resultData.rows;
  29. console.log('rows', rows);

jianghuAxios用例

  1. {% include 'common/jianghuAxios.html' %}
  2. <script>
  3. const result = await window.jianghuAxios({
  4. data: {
  5. appData: {
  6. pageId: 'protocolDemo',
  7. actionId: 'insertItem',
  8. actionData: {
  9. studentId: 'G00003',
  10. classId: '2021-01级-02班',
  11. gender: 'male',
  12. level: '02',
  13. name: '小虾米',
  14. }
  15. }
  16. }
  17. });
  18. const result = await axios(package);
  19. const rows = result.data.appData.resultData.rows;
  20. console.log('rows', rows);
  21. </script>

4. 配置数据示例

1. resource sql场景

  • 方式1:resource sql请求中添加where过滤数据
  1. // 只查询登录用户相关的数据
  2. await window.jianghuAxios({
  3. data: {
  4. appData: {
  5. pageId: 'backendSearchDemo',
  6. actionId: 'selectItemList',
  7. where: { userId: "G00001" }
  8. }
  9. }
  10. })
  • 方式2:通过配置 _resource表中 resourceData.where过滤数据,支持 wherewhereInwhereLikewhereOptionswhereOrOptions
  1. {
  2. "table": "student_basic",
  3. "operation": "select",
  4. "whereOrOptions": [["classId", "like","%2021-01级-02班%"],["bodyHeight", ">", 172]]
  5. }
  • 方式3:通过配置_resource表中的resourceHook 实现 sql where 条件追加
  1. class StudentService extends Service {
  2. /**
  3. * 查询学生列表BeforeHook:只查询当前用户添加的学生
  4. */
  5. async selectStudentItemBeforeHook() {
  6. const { userId } = this.ctx.userInfo;
  7. this.ctx.request.body.appData.where.operationByUserId = userId;
  8. }
  9. }
  10. module.exports = StudentService;
resourceHook pageId actionId desc resourceType resourceData
{"after": [], "before": [{"service": "student", "serviceFunction": "selectStudentItemBeforeHook"}]} studentManagement selectItemList ✅查询列表 sql { "table": "student", "operation": "select" }
  • 方式4:通过配置_resource表中的 accessControlTable 实现用户维度的resourceData覆盖

    _resource表

    id accessControlTable resourceHook pageId actionId desc resourceType appDataSchema resourceData requestDemo responseDemo
    1 access_control_student_basic backendSearchDemo selectItemList 查询列表 sql { "table": "student_basic", "operation": "select" }

    access_control_student_basic表

    id userId username resourceData
    1 userId 洪七公 { "table": "student_basic", "operation": "select", "whereOrOptions": [["classId", "like", "%2021-01级-02班%"],["bodyHeight", ">", 172]]}

2. resource servcie场景

  • 方式1:自定义代码逻辑,实现数据过滤
  1. const { actionData } = this.ctx.request.body.appData;
  2. const { classId } = actionData;
  3. await this.app.jianghuKnex('student_basic').where({classId}).select()
  • 方式2:通过配置 _resource表中的resourceHook实现数据过滤, 然后service中直接使用resourceHook查询出来的数据
    1. 'use strict';
    2. const Service = require('egg').Service;
    3. class StudentService extends Service {
    4. async beforHook() {
    5. const studentInfo = await this.app.jianghuKnex('student_basic').where({studentId: this.ctx.userInfo.userId}).first();
    6. const studentList = await this.app.jianghuKnex('student_basic').where({classId: studentInfo.classId}).select();
    7. this.ctx.beforHookData = { studentList }
    8. }
    9. }
    10. module.exports = StudentService;
    11. // _resource表中的resourceHook配置如下
    12. { "before": [{ "service": "student", "serviceFunction": "beforHook" }], "after": [] }

小结

本文介绍了Jianghu框架中的数据权限配置,数据权限指的是不同用户查询Resource获取的数据不同。文章介绍了两种使用数据权限的方式,分别是在resource sql场景中添加where过滤或通过配置_resource表中的resourceData.where过滤;在resource service场景中自定义代码逻辑或通过配置_resource表中的resourceHook实现数据过滤。

课后练习:

尝试给W00001的student页面