3. 后端开发者指南
3.1 开发流程

3.1.1 二开的几种开发模式
全新工程定制开发(推荐)
源码开发与贡献
⚠️ 注意点:
该方式只适用于开发通用特性的需求,只有这样才允许把代码合入主干分支。
在本模式下,产品设计工作将由渊联与开发团队共同协作完成。
3.1.2 git开源社区
地址: https://gitlab.yelinksaas.com/
参与贡献的几种方式如下:
- 程序定制开发:可基于现有小程序进行二次开发,通过开源社区获取对应项目源码并开展定制工作。
- 需求提交:若项目中存在需要Geneios应用社区团队开发的需求,可在社区提交需求单,清晰描述功能目标与应用场景。
- 问题与缺陷反馈:如遇项目问题或系统 BUG,可在社区提交问题单,详细说明现象、复现步骤及影响范围。
- 参与社区应用开发:若您有通用性需求的开发想法,可提交设计文档申请评审。通过设计评审后,参与代码开发工作,最终申请将代码合并至主干分支。

3.1.3 开发准备
搭建技术服务商自己的开发/测试环境
需准备至少一台64g内存,8核CPU以上的服务器,然后提供远程访问方式,让渊联的运维人员上去部署环境。
后台应用开发注意项
- 应用名称避免重复,推荐命名规范建议如公司缩写 + 服务名
- 后台服务监听端口,从30000-40000之间选择
- api接口前缀,推荐按照应用名称作为前缀
- mysql使用前向运维申请账号,redis推荐使用database=10
- 申请ak/sk(可以从gitlab下载demo工程,直接使用里面配置的值)
3.1.4 关键组件对接方式
flyway对接
工作流程
- 项目启动,应用程序完成数据库连接池的建立后,Flyway自动运行。
- 初次使用时,Flyway会创建一个flyway_schema_history表,用于记录sql执行记录,并且可以通过success字段判断sql执行是否成功。
- Flyway会扫描项目指定路径下(默认是classpath:db/migration)的所有sql脚本,与flyway_schema_history表脚本记录进行比对。如果数据库记录执行过的脚本记录,与项目中的sql脚本不一致,Flyway会报错并停止项目执行。
- 如果校验通过,则根据表中的sql记录最大版本号,忽略所有版本号不大于该版本的脚本。再按照版本号从小到大,逐个执行其余脚本。
flyway spring boot客户端常见配置介绍
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: true3.1.5 工业魔方API接口对接说明
本节结合 springboot3-demo 说明工业魔方开放 API 的接入方式。先记住一个结论:业务代码只调用 Feign Client,Token 透传和 AK/SK 签名都由 Feign 拦截器自动处理。
3.1.5.1 demo 中的调用链路
demo 提供了一个最小验证入口:
curl -X POST "http://localhost:38104/call/service/api"这条请求在工程中的调用链路如下:
| 步骤 | 代码位置 | 作用 |
|---|---|---|
| 1 | DemoController#callServiceApi | 对外暴露 /call/service/api |
| 2 | DemoServiceImpl#callServiceApi | 调用 dfsClient.querySensorsRecords(1, 10) |
| 3 | DfsClient#querySensorsRecords | 定义工业魔方开放 API 路径和参数 |
| 4 | EdgeFeignConfig | 为 Feign Client 注册拦截器 |
| 5 | AccessKeyRequestInterceptor | 判断透传 Token 还是生成 AK/SK 请求头 |
| 6 | RequestInterceptorUtil / SignatureUtils | 生成公共参数和签名 |
当前工程中 DfsClient 和 AmsClient 都复用了 EdgeFeignConfig。新增 DFS、AMS 或其他工业魔方开放接口时,通常只需要在对应 Feign Client 中新增方法,不需要在 Controller 或 Service 中手写签名。
3.1.5.2 远程地址如何拼接
以 DfsClient 为例:
@FeignClient(
name = "dfsClient",
url = "${openapi.edge-gateway-url}",
path = "/openApi/dfs/api",
configuration = EdgeFeignConfig.class)历史数据查询接口定义如下:
@GetMapping("/v1/open/sensors/records")
Result querySensorsRecords(@RequestParam("current") Integer page,
@RequestParam("size") Integer size);最终请求地址由三段组成:
${openapi.edge-gateway-url}/openApi/dfs/api/v1/open/sensors/recordsopenapi.edge-gateway-url 配置在 application.yml 中:
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 会按下面顺序处理认证:
当前请求已被 Spring Security 解析成
JwtAuthenticationToken时,透传当前 Token。httpAuthorization: Bearer <token>Feign 请求模板里已经有
Authorization请求头时,保留原请求头。前两者都没有时,自动生成 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:
app:
security:
access-key-id: ${ACCESS_KEY:DJPE3QJ07OXPAFFJGORO}
access-key-secret: ${ACCESS_KEY_SECRET:sFjISWRruGQjJLCZYiEwomqEpBCNiE3HBaYixZFl}生产或现场联调时,应通过环境变量注入现场分配的值:
ACCESS_KEY=<现场分配的 AccessKeyId>
ACCESS_KEY_SECRET=<现场分配的 AccessKeySecret>
EDGE_GATEWAY_URL=<工业魔方网关地址>3.1.5.5 AK/SK 请求头
进入 AK/SK 分支后,拦截器会自动补齐以下请求头:
| 名称 | 类型 | 是否必须 | 说明 |
|---|---|---|---|
AccessKeyId | String | 是 | 调用方身份标识,来自 app.security.access-key-id |
Timestamp | String | 是 | UTC 时间,格式为 yyyy-MM-dd'T'HH:mm:ss'Z' |
SignatureNonce | String | 是 | 随机数,用于防重放 |
Signature | String | 是 | 签名结果 |
这些请求头由 RequestInterceptorUtil#generateAccessKeyHeader 写入 Feign 请求,业务代码不需要感知。
3.1.5.6 签名计算规则
demo 的签名逻辑在 SignatureUtils 中,核心步骤如下:
收集公共参数:
AccessKeyId、Timestamp、SignatureNonce。合并 URL query 参数,例如
current=1、size=10。按参数名排序,并生成规范化 query string。
拼出待签名字符串:
textHTTP_METHOD + "&" + percentEncode("/") + "&" + percentEncode(canonicalizedQueryString)使用
HmacSHA1计算签名,密钥为:textaccessKeySecret + "&"对签名结果做 Base64。
GET请求会再对签名做 URL encode;POST请求直接使用 Base64 结果。
注意:当前 demo 的签名只纳入公共参数和 URL query 参数,未把 JSON body 放入签名参数。
3.1.5.7 新增工业魔方 API 的步骤
在对应 Feign Client 中新增方法。DFS 接口放到
DfsClient,AMS 接口放到AmsClient,新业务域可新增独立 Client,但要复用EdgeFeignConfig。java@PostMapping("/v2/open/work_orders/list") Result<Page<WorkOrderDetailVO>> queryWorkOrders(@RequestBody WorkOrderListQueryDTO req);在 Service 中注入并调用 Feign Client。
javareturn dfsClient.queryWorkOrders(req);如果需要给前端或测试人员暴露 demo 入口,再新增 Controller 方法。
不要在业务方法里手动添加
AccessKeyId、Timestamp、SignatureNonce、Signature。不要为每个接口重复写签名逻辑;统一复用
EdgeFeignConfig和AccessKeyRequestInterceptor。
3.1.5.8 常见排查
- 返回 401 或 403:先确认实际走 Token 还是 AK/SK;再检查
ACCESS_KEY、ACCESS_KEY_SECRET、EDGE_GATEWAY_URL是否为现场有效值。 - 签名不匹配:重点检查服务器时间、query 参数和参数编码;不要绕过拦截器自己拼签名。
- 本地能进入
/call/service/api,但远程调用失败:该接口只是触发远程调用,还依赖工业魔方网关、网络、AK/SK 和证书配置。 - 当前
EdgeFeignConfig#feignClient信任所有 HTTPS 证书,便于 demo 连接自签名环境;生产环境建议替换为可信证书链。
3.1.6 前端对接kafka消息的方式
某些场景下,定制项目没有后端工程,但是业务又需要订阅kafka的消息,可以利用ocs的websocket功能来实现前端订阅kafka消息的操作。
连接地址
wss://${websocketUrl}/ocs/websocket/${sessionId}?token=${token}说明:
websocketUrl表示websocket的地址
sessionId表示会话ID,每个会话ID都是唯一值,后面的消息交互都依赖于此ID识别。
token表示建立会话的认证凭证,取用户登录成功后返回的token即可
样例:
wss://192.168.101.207:8080/ocs/websocket/112?token=eyJhbGciOiJSUzI**心跳消息
客户端定时发送字符串 "1"
服务端响应心跳消息 "2"
推荐按照5s的频率定时发送
请求消息格式
| 属性名称 | 类型 | 必填 | 描述 | 样例 |
|---|---|---|---|---|
| id | String | 是 | 消息ID号,且每个消息ID在当前设备中具有唯一性。 | 12345 |
| version | String | 是 | 协议版本号,目前协议版本号唯一取值为1.0。 | 1.0 |
| method | String | 是 | 请求方法。 | ocs.subscribe.kafka |
| params | Object | 是 | 请求参数。 | |
| filterKey | Array | 否 | 该值不为空的时候,筛选kafka消息的key包含在该列表内的数据 | ["device_mod"] |
| filterValue | Object | 否 | 该值不为空的时候,会筛选kafka消息的value等于该值的数据 支持通过.的方式多层级定位key |
- 请求订阅kafka消息格式样例
{
"id":123,
"version":"1.0",
"method":"ocs.subscribe.kafka",
"params":{
"topic":[
"middle_priority_post"
]
},
"filterKey":["device001"],
"filterValue":{
"key":"params.D3004",
"value":"*0*"
}
}- 取消订阅kafka消息格式样例
{
"id": 123,
"version": "1.0",
"method": "ocs.unsubscribe.kafka"
}- 发布kafka消息格式样例
{
"id": 123,
"version": "1.0",
"method": "ocs.publish.kafka",
"params": {
"topic": "ocs-message",
"key": "device_mod",
"value": {
}
}
}- 请求订阅实时kafka消息格式样例
不再消费过去的
{
"id":123,
"version":"1.0",
"method":"ocs.subscribe.kafka.realtime",
"params":{
"topic":[
"middle_priority_post"
]
}
}响应消息格式
| 属性名称 | 类型 | 必填 | 描述 | 样例 |
|---|---|---|---|---|
| id | String | 是 | 消息ID号,且每个消息ID在当前设备中具有唯一性。 | 12345 |
| version | String | 是 | 协议版本号,目前协议版本号唯一取值为1.0。 | 1.0 |
| method | String | 是 | 请求方法。 | ocs.subscribe.kafka |
| params | Object | 是 | 请求参数。 | |
| key | String | 否 | kafka消息的key | device1 |
| topic | String | 否 | kafka消息的topic | property_post_exchange |
- 请求、取消kafka订阅响应消息格式样例
{
"code":200,
"dataType":"response",
"id":"123"
}- kafka消息格式样例
{
"code":200,
"data":"1111227777",
"dataType":"kafkaMessage",
"id":"30",
"topic":"property_post_exchange"
}3.2 部署流程
3.2.1 本地或者测试环境准备
编写dockerfile
dockerfileFROM 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构建docker镜像
1、 在构建docker容器的环境安装docker服务
2、 编译服务,在target目录生成jar包等.
3、 目录层级如下
aspectj├── Dockerfile └── target ├── lib ├── resources └── yelink-xxxx.jar4、 执行下面指令构建镜像
shelldocker build -t ${SERVICE_NAME}:${SERVICE_VERSION} .编写docker-compose文件
yamlversion: '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的步骤,开发只要把编译好的目录拷贝到对应目录
yamlversion: '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推送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 目标服务器部署
登录到目标服务器,如果是客户的魔方,使用堡垒机的方式登录
创建工作目录
shellsudo mkdir /opt/partner/${应用名称}把上面编写好的docker-compose.yaml文件放到工作目录
执行下面命令,创建和启动容器
shelldocker-compose up -d
3.3 升级流程
转测通过的镜像推送到镜像仓库,可参考3.4.1的操作
登录到目标服务器,进入工作目录
修改docker-compose里的image部分的镜像版本号,推荐把版本号用环境变量的方式注入,这样只需要修改.env文件内的版本号信息
执行下面的命令,重新构建高版本的容器
shell# 备注,如果当前环境已存在相同版本的镜像,需要先执行docker-compose pull docker-compose up -d
