|
|
6 päivää sitten | |
|---|---|---|
| .. | ||
| .mvn | 2 viikkoa sitten | |
| src | 6 päivää sitten | |
| .gitignore | 2 viikkoa sitten | |
| ERP-YIDA-PUSH-API.md | 6 päivää sitten | |
| README.md | 6 päivää sitten | |
| pom.xml | 6 päivää sitten | |
明磊业务集成服务。工程基于 Spring Boot 构建,通过统一的 malk:mjava 客户端对接钉钉和宜搭,同时提供明磊内部外部接口的转发能力。
| 技术 | 版本或用途 |
|---|---|
| Java | JDK 8 |
| Spring Boot | 2.7.18 |
| Spring MVC | REST Controller、参数绑定和异常处理 |
| Spring Validation | @Valid、@NotBlank、@NotEmpty 等请求校验 |
| Maven | 项目构建和依赖管理 |
com.malk:mjava |
统一的钉钉、宜搭客户端和数据模型,当前版本 0.0.3 |
| Jackson / Fastjson | JSON 请求绑定、序列化和宜搭字段数据组装 |
| RestTemplate | 调用明磊外部 HTTP 接口 |
| Logback + SLF4J | 控制台、应用文件和错误文件日志 |
| JUnit 5 + Mockito | 单元测试和客户端调用测试 |
工程采用典型的 Controller - Service - Impl 分层结构:
MjavaMingleiApplication
|
v
Controller 层 接收 HTTP 请求、校验参数、返回 HTTP 响应
|
v
Service 接口层 定义业务能力和模块边界
|
v
ServiceImpl 层 编排宜搭、钉钉或外部 T100 调用
|
+--> malk YDClient / DDClient
+--> DingTalk Open Platform
+--> 明磊外部 T100 HTTP 接口
src/main/java/com/malk/minglei
├── MjavaMingleiApplication.java Spring Boot 启动类
├── config Bean、配置属性、HTTP 日志过滤器
├── controller REST 接口和局部异常处理
├── dto 请求模型和统一响应模型
└── service
├── employee 员工花名册、钉钉人员相关接口
├── port 明磊外部 T100 接口转发能力
├── yida 宜搭流程、单据查询和 ERP 推送接口
└── impl
├── employee 员工花名册同步、查询、存储和调度实现
├── port 外部 T100 调用实现
└── yida 宜搭流程分发、处理器和单据查询实现
src/main/resources
├── application.yml 服务、钉钉、宜搭、外部接口配置
└── logback-spring.xml 日志输出和滚动策略
src/test/java 单元测试和集成上下文测试
service 层按业务类型归类,不再把所有接口平铺在根目录。当前约定如下:
service.employee:员工花名册、钉钉人员数据同步和查询,统一通过 EmployeeRosterService 暴露同步与查询能力。service.yida:宜搭流程发起、流程处理器、主数据表单查询和 ERP 推送到宜搭的业务接口。service.port:明磊外部 T100 接口转发。service.impl.*:与接口包保持同名分类,放置对应实现、处理器和该业务内部辅助类。新增能力时先判断是否属于现有分类;同一业务域内优先扩展已有 Service 接口或增加领域处理器,例如新增宜搭流程类型时增加 YidaProcessHandler,不要为每个小动作新建一组 XxxService / XxxServiceImpl。只有出现清晰的新业务边界、独立生命周期或独立外部依赖时,才新增新的 Service 分类,避免服务接口和实现类数量膨胀导致结构混乱。
MjavaYidaConfiguration 注册两套独立的宜搭客户端:
coordinationOfficeYidaClient:明磊锂能协调办公应用。masterDataYidaClient:明磊锂能应用主数据。两套客户端使用各自的 appType 和 systemToken,业务实现通过 @Qualifier 选择,避免多个 YDClient Bean 注入时产生歧义。钉钉客户端由 malk 包提供,宜搭客户端内部通过 YDClientImpl 调用表单和流程接口。
相关类:YidaProcessController、YidaProcessService、YidaProcessServiceImpl、YidaProcessHandler、BomYidaProcessHandler、Cpmp402YidaProcessHandler。
统一入口根据请求体的 type 分发处理器。type 的业务含义是业务路由键,不等于表单 ID;当前 cpmp402 代表包材核价单流程。
分发前会去除首尾空格并转为大写匹配。新增单据时,应新增一个 YidaProcessHandler 实现类,并让 getType() 返回对应的业务 type 编码;表单 ID 和流程编码由处理器代码维护,应用凭证仍由配置管理,不由调用方传入。
处理流程如下:
@Valid 校验请求体。YidaProcessServiceImpl 根据规范化后的 type 查找处理器。YidaOperationParam。YDClient.operateData(..., FORM_OPERATION.start) 发起宜搭流程。相关类:MingleiDocumentService、MingleiDocumentServiceImpl、YidaProcessController。
主数据查询固定使用主数据应用客户端和目标表单 ID。支持两种查询方式:
records、totalCount、pageNumber。dept_id > user_id > dept_name。传入 user_id 时,先调用钉钉通讯录接口取得员工所属部门,再按部门 ID 查询。查询成功后仅提取一级至四级部门主管和部门分管领导字段对应的钉钉 userId。MingleiDocumentServiceImpl.listDepartmentUserIds 先取得钉钉访问令牌,再按 type 决定查询范围:0 获取指定部门及所有子部门,1 仅获取指定部门;随后逐个读取部门成员并按首次出现顺序去重,返回用户 ID 列表。
相关类:MingleiPortController、MingleiPortService、MingleiPortServiceImpl。
该模块是本服务提供的本地触发入口,收到精简的 head 和 detail 后,由服务端补齐固定的外层协议结构,再通过 RestTemplate 调用明磊外部接口。连接超时为 10 秒,读取超时为 30 秒,目标 URL 和接入密钥通过配置注入。
POST /api/yida/process-instances
Content-Type: application/json
请求示例:
{
"type": "cpmp402",
"userId": "41603502201288023",
"dataTitle": "包材核价单",
"fields": {
"mainData": {
"rq": "2026-09-08",
"bm": "ML07000000",
"tjr": "3075",
"gs": "ML",
"t100id": "ML",
"ent": 87,
"t100bm": "ML07000000"
},
"detailData": {
"detailData": [
{
"hjdh": "MLCH326090800001",
"gysbh": "113009",
"gysmc": "南京鼎泰五金工具有限公司",
"t100id": "ML",
"ent": "87",
"yy": "zh_CN"
}
]
}
}
}
userId 也可使用请求别名 originatorUserId。type 是业务路由键,不是表单 ID;fields 会原样交给对应处理器。成功返回 HTTP 201 Created:
{
"code": 200,
"isSuccess": true,
"result": {
"processInstanceId": "宜搭流程实例 ID"
}
}
该接口使用统一的 ApiResponse 包装格式,流程实例 ID 位于 result.processInstanceId。
GET /api/yida/minglei/forms/instances?page=1&size=20
page 从 1 开始,size 范围为 1 至 100。成功响应为宜搭查询结果的服务端整理结构:
{
"records": [],
"totalCount": 0,
"pageNumber": 1
}
POST /api/yida/minglei/forms/instances/query
Content-Type: application/json
请求示例:
{
"dept_id": "123456",
"user_id": "dingTalkUserId",
"dept_name": "明磊锂能技术部"
}
返回示例:
{
"employeeField_mszdqrl8": "一级部门主管 userId",
"employeeField_mszdqrl9": "二级部门主管 userId",
"employeeField_mszdqrla": "三级部门主管 userId",
"employeeField_mszdqrlb": "四级部门主管 userId",
"employeeField_mszdqrl3": "部门分管领导 userId"
}
POST /api/yida/minglei/departments/users
Content-Type: application/json
请求体:
{
"dept_Id": "123456",
"type": 0
}
type 为必填字段:0 表示穿透查询子部门,1 表示仅查询指定部门。
成功响应统一为 code、isSuccess、result 三层结构:
{
"code": 200,
"isSuccess": true,
"result": [
"449856319",
"715357119"
]
}
POST /api/minglei/ports/supplier-price-approvals
Content-Type: application/json
请求体只需要提供协议中的 head 和 detail:
{
"head": {
"pmdidocno": "CH3",
"pmdi001": "N",
"pmdi034": "605188",
"pmdidocdt": "2026-08-06",
"pmdi004": "517022",
"pmdi015": "2026-08-06",
"pmdi016": "2027-06-01",
"pmdi003": "A043",
"pmdi002": "210721017",
"pmdiud001": "N",
"pmdistus": "I"
},
"detail": [
{
"pmdj011": "0.5000",
"pmdj002": "524000059517022000000",
"pmdj030": ""
}
]
}
服务端会自动补齐 type、host、service、datakey、payload 等固定协议字段,并返回外部接口的原始 JSON 响应。
POST /api/erp/yida/push
Content-Type: application/json
当前阶段按 type 路由到对应宜搭处理器。cpmp402 发起宜搭流程;ml_work_order_craft_price 发起宜搭流程 ML-工单工艺工价审定,目标表单 FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K,流程编码 TPROC--WG966BA1VWF8Q1K4OGF8H9KW47RR3ZREH0YSM2。发起人由 mainData.tjr 工号通过员工花名册解析。
请求体:
{
"type": "cpmp402",
"mainData": {
"rq": "2026-09-08",
"bm": "ML07000000",
"tjr": "3075",
"gs": "ML",
"t100id": "ML",
"ent": 87,
"t100bm": "ML07000000"
},
"detailData": {
"detailData": [
{
"hjdh": "MLCH326090800001",
"gysbh": "113009",
"gysmc": "南京鼎泰五金工具有限公司",
"t100id": "ML",
"ent": "87",
"yy": "zh_CN"
}
]
}
}
mainData 为固定主数据结构,其中 tjr 必填且表示提交人工号;服务端会按员工花名册查询钉钉 userId,统一作为宜搭发起人,并把 rq、bm、gs、t100id、ent、t100bm 作为后续所有通用 type 的默认元数据补入内部明细对象。detailData 请求体保持 { "detailData": [] } 外壳不变:外层必须是对象,且必须包含 detailData 数组,数组内放置当前单据类型的明细对象。接口成功返回基座 McR.success(),Service 会按类型记录宜搭流程实例号和响应 JSON 日志。
宜搭或钉钉返回业务错误时,接口返回 HTTP 502,响应体会带出上游错误码和错误信息,便于定位字段格式、权限或配置问题。如果响应仍是 YIDA_CALL_FAILED,说明是网络、序列化或其他未分类异常,需要查看应用错误日志中的异常堆栈。
ml_work_order_craft_price 示例:
{
"type": "ml_work_order_craft_price",
"mainData": {
"rq": "2026-09-08",
"tjr": "3075"
},
"detailData": {
"detailData": [
{
"标题": "工单工艺工价审定-GD-001",
"工单号": "GD-001",
"生产令": "MO-001",
"生产料号": "ITEM-001",
"品名": "电池包",
"规格": "24V",
"组装|包装": "组装",
"固定工价": "10.5",
"总单位工价": "17.5",
"明细表1": [
{
"工序编号": "GX-001",
"名称": "装配",
"数量": "2",
"工价": "3.5",
"合计": "7.0"
}
],
"明细表2": [
{
"项次": "10",
"料号": "MAT-001",
"品名": "电池盒",
"规格": "A1",
"用量": "1.2",
"t100id": "ML",
"车间线长": "leader-001",
"企业编号": "87"
}
]
}
]
}
}
流程推送字段 ID 已通过 OpenYida schema 确认;调用方也可以在 detailData.detailData[] 的明细对象中直接传入宜搭 fieldId,用于补充默认字段名映射。申请人字段为 employeeField_msh186rr,固定由 mainData.tjr 工号查询员工花名册后取 dingtalk_id 写入;申请公司字段为 textField_mu1yk6l5,优先写入 mainData.gs;申请部门字段为 departmentSelectField_msh186ry,优先将 mainData.bm 部门 ID 以数组写入宜搭。明细对象中的 申请公司、申请部门 等字段仅在对应 mainData 为空时作为兜底值。
新增接口优先使用 ApiResponse<T>,成功响应约定为:
{
"code": 200,
"isSuccess": true,
"result": {}
}
部门成员接口使用专用的 DepartmentUserIdsResponse。历史单据查询接口为兼容已有调用方,保留当前响应结构;流程发起接口使用 ApiResponse。
异常处理分为两层:
GlobalExceptionHandler 处理配置缺失,返回 HTTP 412;请求参数或 JSON 格式错误返回 HTTP 400;宜搭平台业务错误返回 HTTP 502 并带出上游错误码和错误信息;未分类的外部调用异常记录错误日志并返回 HTTP 502。配置文件为 src/main/resources/application.yml。生产环境建议通过环境变量注入,不要把真实密钥提交到代码仓库。
| 配置项 | 环境变量 | 用途 |
|---|---|---|
server.port |
SERVER_PORT |
HTTP 服务端口,默认 80 |
spring.profiles.active |
SPRING_PROFILES_ACTIVE |
Spring Profile,默认 dev |
dingtalk.app-key |
DINGTALK_APP_KEY |
钉钉应用 Key |
dingtalk.app-secret |
DINGTALK_APP_SECRET |
钉钉应用 Secret |
minglei.yida.coordination-office.app-type |
YIDA_COORDINATION_OFFICE_APP_TYPE |
协调办公应用编码 |
minglei.yida.coordination-office.system-token |
YIDA_COORDINATION_OFFICE_SYSTEM_TOKEN |
协调办公应用 System Token |
minglei.yida.master-data.app-type |
YIDA_MASTER_DATA_APP_TYPE |
主数据应用编码 |
minglei.yida.master-data.system-token |
YIDA_MASTER_DATA_SYSTEM_TOKEN |
主数据应用 System Token |
minglei.port.supplier-price-approval-url |
MINGLEI_SUPPLIER_PRICE_APPROVAL_URL |
外部 T100 接口地址 |
minglei.port.supplier-price-approval-key |
MINGLEI_SUPPLIER_PRICE_APPROVAL_KEY |
外部接口接入密钥 |
logging.file.path |
LOGGING_FILE_PATH |
日志目录,默认 logs |
Logback 同时输出:
${LOGGING_FILE_PATH}/application.log 应用日志。${LOGGING_FILE_PATH}/error.log 错误日志。archive 子目录中的按日期、大小滚动归档日志。HttpRequestResponseLoggingFilter 会记录请求方法、URI、状态码、耗时、请求体和响应体,单个载荷最多记录 10KB,并对 password、secret、token、authorization、accessToken、systemToken、key 等字段脱敏。日志目录必须对运行账号可写,否则可能导致 Logback 初始化失败。
环境要求:JDK 8、Maven 3.6+,并确保能够访问项目依赖仓库及内部 malk 依赖。
# 根据实际环境填写,不要把真实凭证提交到 README 或代码仓库
$env:SERVER_PORT = "8080"
$env:DINGTALK_APP_KEY = "<dingTalk-app-key>"
$env:DINGTALK_APP_SECRET = "<dingTalk-app-secret>"
$env:YIDA_COORDINATION_OFFICE_APP_TYPE = "<coordination-office-app-type>"
$env:YIDA_COORDINATION_OFFICE_SYSTEM_TOKEN = "<coordination-office-system-token>"
$env:YIDA_MASTER_DATA_APP_TYPE = "<master-data-app-type>"
$env:YIDA_MASTER_DATA_SYSTEM_TOKEN = "<master-data-system-token>"
mvn clean package
mvn spring-boot:run
打包后也可以运行:
java -jar target\mjava-minglei-1.0.0-SNAPSHOT.jar
测试代码位于 src/test/java,覆盖:
ApiResponse 和部门用户响应的 JSON 结构。type 分发。执行测试:
mvn test
新增可发起的宜搭流程时,按以下步骤实现:
XxxYidaProcessHandler,实现 YidaProcessHandler。type 编码,让 getType() 返回该编码。startProcess 中选择正确的 YDClient 组装表单字段。YidaProcessServiceImplTest 增加 type 分发测试。调用方只传 type、发起人、标题和 fields,不传应用凭证、表单 ID 或流程编码。不同宜搭应用必须使用对应的具名客户端,避免协调办公和主数据凭证混用。
LOGGING_FILE_PATH 目录是否存在以及运行账号是否具有写权限。