Skip to content

3. 后端开发者指南

3.1 开发流程

image-20250516173540408

3.1.1 二开的几种开发模式

  • 全新工程定制开发(推荐)
  • 源码开发与贡献

    ⚠️ 注意点:

该方式只适用于开发通用特性的需求,只有这样才允许把代码合入主干分支。

在本模式下,产品设计工作将由渊联与开发团队共同协作完成。

3.1.2 git开源社区

地址: https://gitlab.yelinksaas.com/

参与贡献的几种方式如下:

  1. 程序定制开发:可基于现有小程序进行二次开发,通过开源社区获取对应项目源码并开展定制工作。
  2. 需求提交:若项目中存在需要Geneios应用社区团队开发的需求,可在社区提交需求单,清晰描述功能目标与应用场景。
  3. 问题与缺陷反馈:如遇项目问题或系统 BUG,可在社区提交问题单,详细说明现象、复现步骤及影响范围。
  4. 参与社区应用开发:若您有通用性需求的开发想法,可提交设计文档申请评审。通过设计评审后,参与代码开发工作,最终申请将代码合并至主干分支。

image-20250517100454705

3.1.3 开发准备

  1. 搭建技术服务商自己的开发/测试环境

    需准备至少一台64g内存,8核CPU以上的服务器,然后提供远程访问方式,让渊联的运维人员上去部署环境。

  2. 后台应用开发注意项

    • 应用名称避免重复,推荐命名规范建议如公司缩写 + 服务名
    • 后台服务监听端口,从30000-40000之间选择
    • api接口前缀,推荐按照应用名称作为前缀
    • mysql使用前向运维申请账号,redis推荐使用database=10
    • 申请ak/sk(可以从gitlab下载demo工程,直接使用里面配置的值)

3.1.4 关键组件对接方式

flyway对接

工作流程

  1. 项目启动,应用程序完成数据库连接池的建立后,Flyway自动运行。
  2. 初次使用时,Flyway会创建一个flyway_schema_history表,用于记录sql执行记录,并且可以通过success字段判断sql执行是否成功。
  3. Flyway会扫描项目指定路径下(默认是classpath:db/migration)的所有sql脚本,与flyway_schema_history表脚本记录进行比对。如果数据库记录执行过的脚本记录,与项目中的sql脚本不一致,Flyway会报错并停止项目执行。
  4. 如果校验通过,则根据表中的sql记录最大版本号,忽略所有版本号不大于该版本的脚本。再按照版本号从小到大,逐个执行其余脚本。

flyway spring boot客户端常见配置介绍

yaml
spring:
  flyway:
    # 1. 启用/禁用 Flyway(默认 true)
    enabled: true
    # 2. 迁移脚本路径(可多个路径)
    locations: classpath:db/migration,filesystem:/opt/sql
    # 3. 基线版本号(初始化已有数据库时使用)
    baseline-version: "1.0"
    # 4. 允许对已有数据库执行基线迁移(无元数据表时自动创建)
    baseline-on-migrate: true
    # 5. 迁移时校验脚本一致性(防止已执行脚本被修改)
    validate-on-migrate: true
    # 6. 禁止 clean 命令(生产环境必配!)
    clean-disabled: true
    # 7. 元数据表名(默认 flyway_schema_history)
    table: schema_version
    # 8. SQL 脚本前缀(默认 V)
    sql-migration-prefix: V
    # 9. SQL 脚本分隔符(默认双下划线 __)
    sql-migration-separator: __
    # 10. 迁移目标版本(仅升级到指定版本)
    target: 3.1
    # 其他高频配置:
    # 11. 占位符替换(用于动态 SQL)
    placeholders:
      user_table: "app_users"
      env: "prod"
    # 12. 脚本编码(默认 UTF-8)
    encoding: UTF-8
    # 13. 允许混合版本化和可重复迁移(默认 false)
    mixed: true
    # 14. 是否在事务中执行迁移(默认 true)
    execute-in-transaction: false
    # 15.允许非顺序迁移
    out-of-order: true

3.1.5 工业魔方API接口对接说明

本节结合 springboot3-demo 说明工业魔方开放 API 的接入方式。先记住一个结论:业务代码只调用 Feign Client,Token 透传和 AK/SK 签名都由 Feign 拦截器自动处理。

3.1.5.1 demo 中的调用链路

demo 提供了一个最小验证入口:

bash
curl -X POST "http://localhost:38104/call/service/api"

这条请求在工程中的调用链路如下:

步骤代码位置作用
1DemoController#callServiceApi对外暴露 /call/service/api
2DemoServiceImpl#callServiceApi调用 dfsClient.querySensorsRecords(1, 10)
3DfsClient#querySensorsRecords定义工业魔方开放 API 路径和参数
4EdgeFeignConfig为 Feign Client 注册拦截器
5AccessKeyRequestInterceptor判断透传 Token 还是生成 AK/SK 请求头
6RequestInterceptorUtil / SignatureUtils生成公共参数和签名

当前工程中 DfsClientAmsClient 都复用了 EdgeFeignConfig。新增 DFS、AMS 或其他工业魔方开放接口时,通常只需要在对应 Feign Client 中新增方法,不需要在 Controller 或 Service 中手写签名。

3.1.5.2 远程地址如何拼接

DfsClient 为例:

java
@FeignClient(
        name = "dfsClient",
        url = "${openapi.edge-gateway-url}",
        path = "/openApi/dfs/api",
        configuration = EdgeFeignConfig.class)

历史数据查询接口定义如下:

java
@GetMapping("/v1/open/sensors/records")
Result querySensorsRecords(@RequestParam("current") Integer page,
                           @RequestParam("size") Integer size);

最终请求地址由三段组成:

text
${openapi.edge-gateway-url}/openApi/dfs/api/v1/open/sensors/records

openapi.edge-gateway-url 配置在 application.yml 中:

yaml
openapi:
  edge-gateway-url: ${EDGE_GATEWAY_URL:https://172.16.0.3:8301}

部署时,deploy.sh 会从公共配置 GATEWAY_URL 生成应用环境变量 EDGE_GATEWAY_URL

3.1.5.3 Token 与 AK/SK 的选择顺序

AccessKeyRequestInterceptor 会按下面顺序处理认证:

  1. 当前请求已被 Spring Security 解析成 JwtAuthenticationToken 时,透传当前 Token。

    http
    Authorization: Bearer <token>
  2. Feign 请求模板里已经有 Authorization 请求头时,保留原请求头。

  3. 前两者都没有时,自动生成 AK/SK 请求头。

本地开发默认 app.security.enable: false,请求头中的 Authorization 通常不会被解析进 Spring Security 上下文,所以更容易走 AK/SK 分支。生产或联调环境启用认证后,携带有效 JWT 的请求会优先走 Token 分支。

3.1.5.4 AK/SK 配置项

AK/SK 配置在 web-server/src/main/resources/config/security.yml

yaml
app:
  security:
    access-key-id: ${ACCESS_KEY:DJPE3QJ07OXPAFFJGORO}
    access-key-secret: ${ACCESS_KEY_SECRET:sFjISWRruGQjJLCZYiEwomqEpBCNiE3HBaYixZFl}

生产或现场联调时,应通过环境变量注入现场分配的值:

bash
ACCESS_KEY=<现场分配的 AccessKeyId>
ACCESS_KEY_SECRET=<现场分配的 AccessKeySecret>
EDGE_GATEWAY_URL=<工业魔方网关地址>
3.1.5.5 AK/SK 请求头

进入 AK/SK 分支后,拦截器会自动补齐以下请求头:

名称类型是否必须说明
AccessKeyIdString调用方身份标识,来自 app.security.access-key-id
TimestampStringUTC 时间,格式为 yyyy-MM-dd'T'HH:mm:ss'Z'
SignatureNonceString随机数,用于防重放
SignatureString签名结果

这些请求头由 RequestInterceptorUtil#generateAccessKeyHeader 写入 Feign 请求,业务代码不需要感知。

3.1.5.6 签名计算规则

demo 的签名逻辑在 SignatureUtils 中,核心步骤如下:

  1. 收集公共参数:AccessKeyIdTimestampSignatureNonce

  2. 合并 URL query 参数,例如 current=1size=10

  3. 按参数名排序,并生成规范化 query string。

  4. 拼出待签名字符串:

    text
    HTTP_METHOD + "&" + percentEncode("/") + "&" + percentEncode(canonicalizedQueryString)
  5. 使用 HmacSHA1 计算签名,密钥为:

    text
    accessKeySecret + "&"
  6. 对签名结果做 Base64。

  7. GET 请求会再对签名做 URL encode;POST 请求直接使用 Base64 结果。

注意:当前 demo 的签名只纳入公共参数和 URL query 参数,未把 JSON body 放入签名参数。

3.1.5.7 新增工业魔方 API 的步骤
  1. 在对应 Feign Client 中新增方法。DFS 接口放到 DfsClient,AMS 接口放到 AmsClient,新业务域可新增独立 Client,但要复用 EdgeFeignConfig

    java
    @PostMapping("/v2/open/work_orders/list")
    Result<Page<WorkOrderDetailVO>> queryWorkOrders(@RequestBody WorkOrderListQueryDTO req);
  2. 在 Service 中注入并调用 Feign Client。

    java
    return dfsClient.queryWorkOrders(req);
  3. 如果需要给前端或测试人员暴露 demo 入口,再新增 Controller 方法。

  4. 不要在业务方法里手动添加 AccessKeyIdTimestampSignatureNonceSignature

  5. 不要为每个接口重复写签名逻辑;统一复用 EdgeFeignConfigAccessKeyRequestInterceptor

3.1.5.8 常见排查
  • 返回 401 或 403:先确认实际走 Token 还是 AK/SK;再检查 ACCESS_KEYACCESS_KEY_SECRETEDGE_GATEWAY_URL 是否为现场有效值。
  • 签名不匹配:重点检查服务器时间、query 参数和参数编码;不要绕过拦截器自己拼签名。
  • 本地能进入 /call/service/api,但远程调用失败:该接口只是触发远程调用,还依赖工业魔方网关、网络、AK/SK 和证书配置。
  • 当前 EdgeFeignConfig#feignClient 信任所有 HTTPS 证书,便于 demo 连接自签名环境;生产环境建议替换为可信证书链。

3.1.6 前端对接kafka消息的方式

某些场景下,定制项目没有后端工程,但是业务又需要订阅kafka的消息,可以利用ocs的websocket功能来实现前端订阅kafka消息的操作。

连接地址
shell
wss://${websocketUrl}/ocs/websocket/${sessionId}?token=${token}

说明:

websocketUrl表示websocket的地址

sessionId表示会话ID,每个会话ID都是唯一值,后面的消息交互都依赖于此ID识别。

token表示建立会话的认证凭证,取用户登录成功后返回的token即可

样例:

shell
wss://192.168.101.207:8080/ocs/websocket/112?token=eyJhbGciOiJSUzI**
心跳消息

客户端定时发送字符串 "1"

服务端响应心跳消息 "2"

推荐按照5s的频率定时发送

请求消息格式
属性名称类型必填描述样例
idString消息ID号,且每个消息ID在当前设备中具有唯一性。12345
versionString协议版本号,目前协议版本号唯一取值为1.0。1.0
methodString请求方法。ocs.subscribe.kafka
paramsObject请求参数。
filterKeyArray该值不为空的时候,筛选kafka消息的key包含在该列表内的数据["device_mod"]
filterValueObject该值不为空的时候,会筛选kafka消息的value等于该值的数据
支持通过.的方式多层级定位key
  1. 请求订阅kafka消息格式样例
json
{
    "id":123,
    "version":"1.0",
    "method":"ocs.subscribe.kafka",
    "params":{
        "topic":[
            "middle_priority_post"
        ]
    },
    "filterKey":["device001"],
    "filterValue":{
        "key":"params.D3004",
        "value":"*0*"
    }
}
  1. 取消订阅kafka消息格式样例
json
{
	"id": 123,
	"version": "1.0",
	"method": "ocs.unsubscribe.kafka"
}
  1. 发布kafka消息格式样例
json
{
	"id": 123,
	"version": "1.0",
	"method": "ocs.publish.kafka",
	"params":  {
		"topic": "ocs-message",
		"key": "device_mod",
		"value": {
            
        }
	}
}
  1. 请求订阅实时kafka消息格式样例

不再消费过去的

json
{
    "id":123,
    "version":"1.0",
    "method":"ocs.subscribe.kafka.realtime",
    "params":{
        "topic":[
            "middle_priority_post"
        ]
    }
}
响应消息格式
属性名称类型必填描述样例
idString消息ID号,且每个消息ID在当前设备中具有唯一性。12345
versionString协议版本号,目前协议版本号唯一取值为1.0。1.0
methodString请求方法。ocs.subscribe.kafka
paramsObject请求参数。
keyStringkafka消息的keydevice1
topicStringkafka消息的topicproperty_post_exchange
  1. 请求、取消kafka订阅响应消息格式样例
json
{
    "code":200,
    "dataType":"response",
    "id":"123"
}
  1. kafka消息格式样例
json
{
    "code":200,
    "data":"1111227777",
    "dataType":"kafkaMessage",
    "id":"30",
    "topic":"property_post_exchange"
}

3.2 部署流程

3.2.1 本地或者测试环境准备

  1. 编写dockerfile

    dockerfile
    FROM swr.cn-south-1.myhuaweicloud.com/extend/openjdk:3u342-jdk
    
    ENV TZ='Asia/Shanghai'
    ENV APP_FILE yelink-demo.jar
    ENV APP_HOME /usr/app
    # Create the home directory for the new app user.
    RUN mkdir -p /usr/app
    RUN mkdir -p /usr/app/lib
    RUN mkdir -p /usr/app/resources
    
    WORKDIR $APP_HOME
    COPY web-server/target/lib/* /usr/app/lib/
    COPY web-server/target/resources/* /usr/app/resources/
    COPY web-server/target/$APP_FILE /usr/app/
    
    ENV JVM_OPTS=${JVM_OPTS}
    ENTRYPOINT java ${JVM_OPTS}  -jar ./yelink-demo.jar
  2. 构建docker镜像

    1、 在构建docker容器的环境安装docker服务

    2、 编译服务,在target目录生成jar包等.

    3、 目录层级如下

    aspectj
    ├── Dockerfile
    └── target
        ├── lib
        ├── resources
        └── yelink-xxxx.jar

    4、 执行下面指令构建镜像

    shell
    docker build -t ${SERVICE_NAME}:${SERVICE_VERSION} .
  3. 编写docker-compose文件

    yaml
    version: '3'
    services:
      yelink-demo:
        image: ${SERVICE_NAME}:${SERVICE_VERSION}
        container_name: yelink-demo
        ports:
          - "38104:38104"
        restart: always
        volumes:
          - "/opt/partner/data/yelink-demo/logs/:/usr/app/logs/"
        env_file:
          - /opt/partner/config/yelink-demo/variables.env

    调试阶段也可以参考下面的docker-compose文件,表示从当前目录下寻找dockerfile文件构建镜像,并创建容器运行,使用此方法,可以省略docker build的步骤,开发只要把编译好的目录拷贝到对应目录

    yaml
    version: '3'
    services:
      yelink-demo:
        build:
          context: .
        container_name: yelink-demo
        ports:
          - "38104:38104"
        restart: always
        volumes:
          - "/opt/partner/data/yelink-demo/logs/:/usr/app/logs/"
        env_file:
          - /opt/partner/config/yelink-demo/variables.env
  4. 推送docker镜像

    shell
    # 先打标签,补充镜像仓库地址
    docker tag {镜像名称}:{版本名称} swr.cn-south-2.myhuaweicloud.com/yelink_appstore_test/{镜像名称}:{版本名称}
    # 推送镜像到指定仓库
    docker push swr.cn-south-2.myhuaweicloud.com/yelink_appstore_test/{镜像名称}:{版本名称}

3.2.2 目标服务器部署

  1. 登录到目标服务器,如果是客户的魔方,使用堡垒机的方式登录

  2. 创建工作目录

    shell
    sudo mkdir /opt/partner/${应用名称}
  3. 把上面编写好的docker-compose.yaml文件放到工作目录

  4. 执行下面命令,创建和启动容器

    shell
    docker-compose up -d

3.3 升级流程

  1. 转测通过的镜像推送到镜像仓库,可参考3.4.1的操作

  2. 登录到目标服务器,进入工作目录

  3. 修改docker-compose里的image部分的镜像版本号,推荐把版本号用环境变量的方式注入,这样只需要修改.env文件内的版本号信息

  4. 执行下面的命令,重新构建高版本的容器

    shell
    # 备注,如果当前环境已存在相同版本的镜像,需要先执行docker-compose pull
    docker-compose up -d

基于 MIT 许可发布