# mjava-minglei 明磊业务集成服务。项目基于 Spring Boot 2.7 构建,用于对接钉钉、宜搭、员工花名册以及明磊外部 T100 接口。 本文档说明当前工程的模块边界、配置项、REST 接口、ERP 推送规则和本地构建方式。文档中的凭证均使用占位符,真实密钥必须通过环境变量或部署配置注入。 ## 1. 技术栈 | 技术 | 说明 | | --- | --- | | Java | JDK 8 | | Spring Boot | 2.7.18 | | Spring MVC | REST Controller、参数绑定、异常处理 | | Spring Validation | 请求参数校验 | | Maven | 构建和依赖管理 | | `com.malk:mjava` | 钉钉、宜搭客户端和基础模型,当前版本 `0.0.3` | | Jackson / Fastjson | JSON 绑定、序列化和表单字段组装 | | RestTemplate | 调用外部 T100 HTTP 接口 | | Logback + SLF4J | 应用日志、错误日志和请求响应日志 | | JUnit 5 + Mockito | 单元测试 | ## 2. 工程结构 ```text src/main/java/com/malk/minglei |-- MjavaMingleiApplication.java |-- config | |-- DingTalkRosterConfiguration.java | |-- EmployeeRosterConfiguration.java | |-- EmployeeRosterProperties.java | |-- HttpRequestResponseLoggingFilter.java | |-- MingleiPortConfiguration.java | |-- MingleiPortProperties.java | |-- MjavaYidaConfiguration.java | `-- YidaApplicationsProperties.java |-- controller | |-- EmployeeRosterController.java | |-- ErpYidaPushController.java | |-- GlobalExceptionHandler.java | |-- MingleiPortController.java | `-- YidaProcessController.java |-- dto `-- service |-- employee |-- port |-- yida `-- impl |-- employee |-- port `-- yida ``` ### 2.1 分层职责 | 层级 | 职责 | | --- | --- | | `controller` | 暴露 HTTP 接口,处理请求校验和响应包装 | | `dto` | 请求体、响应体和业务数据模型 | | `service.employee` | 员工花名册同步、查询和钉钉人员数据处理 | | `service.port` | 明磊外部 T100 接口转发 | | `service.yida` | 宜搭流程、宜搭表单查询、ERP 推送分发 | | `service.impl.*` | 各业务实现、处理器和内部辅助类 | 新增能力时优先放入现有业务域。只有出现清晰的新业务边界、独立生命周期或独立外部依赖时,才新增新的 `Service` 分类。 ## 3. 配置说明 配置文件位于 `src/main/resources/application.yml`。生产环境不要使用文件中的默认示例值,应通过环境变量覆盖。 | 配置项 | 环境变量 | 用途 | | --- | --- | --- | | `server.port` | `SERVER_PORT` | HTTP 服务端口 | | `spring.profiles.active` | `SPRING_PROFILES_ACTIVE` | Spring Profile | | `dingtalk.agent-id` | `DINGTALK_AGENT_ID` | 钉钉应用 AgentId | | `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` | 外部 T100 接口接入密钥 | | `minglei.employee-roster.datasource.url` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_URL` | 员工花名册 MySQL JDBC 地址 | | `minglei.employee-roster.datasource.username` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_USERNAME` | 员工花名册数据库用户名 | | `minglei.employee-roster.datasource.password` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_PASSWORD` | 员工花名册数据库密码 | | `minglei.employee-roster.datasource.driver-class-name` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_DRIVER_CLASS_NAME` | JDBC 驱动类 | | `logging.file.path` | `LOGGING_FILE_PATH` | 日志输出目录 | `MjavaYidaConfiguration` 注册两套宜搭客户端: | Bean | 用途 | | --- | --- | | `coordinationOfficeYidaClient` | 协同办公应用,主要用于发起流程 | | `masterDataYidaClient` | 主数据应用,主要用于查询主数据表单 | ## 4. REST 接口 ### 4.1 宜搭流程发起 ```http POST /api/yida/process-instances Content-Type: application/json ``` 请求示例: ```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" } ] } } } ``` 成功响应为 HTTP `201 Created`: ```json { "code": 200, "isSuccess": true, "result": { "processInstanceId": "process-instance-id" } } ``` ### 4.2 宜搭主数据表单查询 分页查询: ```http GET /api/yida/minglei/forms/instances?page=1&size=20 ``` 精确查询: ```http POST /api/yida/minglei/forms/instances/query Content-Type: application/json ``` 请求示例: ```json { "dept_id": "123456", "user_id": "dingTalkUserId", "dept_name": "明磊锂能技术部" } ``` 精确查询优先级为 `dept_id`、`user_id`、`dept_name`。传入 `user_id` 时,服务端会先查询人员所属部门,再按部门 ID 查询主数据。 ### 4.3 部门成员查询 ```http POST /api/yida/minglei/departments/users Content-Type: application/json ``` 请求示例: ```json { "dept_Id": "123456,234567", "type": 0, "userIds": ["user-1", "user-2"] } ``` `userIds` 为可选的用户 ID 列表。未传或传入空数组时不影响部门成员查询结果;传入非空列表时,列表中的用户 ID 会按传入顺序追加在部门成员列表之后,并与部门成员及列表内其他 ID 一并去重。 `dept_Id` 支持单个部门 ID,或多个以英文逗号分隔的部门 ID;返回结果为各部门查询范围内人员的去重合集。 `type` 取值: | 值 | 说明 | | --- | --- | | `0` | 查询指定部门和所有子部门 | | `1` | 仅查询指定部门 | ### 4.4 员工花名册 同步指定部门: ```http POST /api/yida/minglei/employee-roster/sync?departmentId=1&type=0 ``` 全量同步: ```http POST /api/yida/minglei/employee-roster/sync-full ``` 按钉钉用户 ID 查询花名册: ```http POST /api/yida/minglei/employee-roster/getUserRoster Content-Type: application/json ``` ### 4.5 外部 T100 接口转发 ```http POST /api/minglei/ports/supplier-price-approvals Content-Type: application/json ``` 请求体只需要提供 `head` 和 `detail`,服务端会补齐外部接口协议固定字段。 ```json { "head": { "pmdidocno": "CH3", "pmdi001": "N", "pmdi034": "605188", "pmdidocdt": "2026-08-06" }, "detail": [ { "pmdj011": "0.5000", "pmdj002": "524000059517022000000" } ] } ``` ### 4.6 ERP 推送宜搭 ```http POST /api/erp/yida/push Content-Type: application/json ``` 请求示例: ```json { "type": "axmt140", "mainData": { "rq": "2026-09-08", "bm": "123456", "tjr": "3075", "gs": "ML" }, "detailData": { "detailData1": [ { "xmfyseq": "10", "xmfy002": "DN-001", "xmfy003": "2026-09-08", "xmfy004": "CUS-001", "pmaal004": "客户名称" } ] } } ``` 成功响应使用 `McR.success()`: ```json { "success": true, "code": "200", "message": "SUCCESS" } ``` ## 5. ERP 推送规则 ### 5.1 支持的类型 `type` 会去除首尾空格并转为大写后匹配,调用方传入大小写不敏感。 | `type` 或别名 | 处理器 | 单据或流程 | 表单 UUID | 流程编码 | | --- | --- | --- | --- | --- | | `cpmp402` | `Cpmp402YidaProcessHandler` | 包材核价单 | `FORM-A3D4889187E54697A59590BE54F350E7AOO5` | `TPROC--98G66QB1NTF8E1R1LI0OK4D6JI9437HC92YSM1` | | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `MlWorkOrderCraftPriceFormPushHandler` | ML-工单工艺工价审定 | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `TPROC--WG966BA1VWF8Q1K4OGF8H9KW47RR3ZREH0YSM2` | | `axmt500` | `ApproveOrderFormPushHandler` | 客户订单审批 | `FORM-8454FA17732746058A2D90B1C559CC19G5SR` | `TPROC--9VD66QA18AJ8317KK3NXM54W1HIT30C7N28TM7` | | `axmt140` | `CustomerReleaseFormPushHandler` | 客户放行审批 | `FORM-3BA9FE18494F4FDD85FE6ABF7DE741BAE30C` | `TPROC--6TG66381N8G8Y7L9GBEEY7QVXTUY2LBBGZ7TMU` | | `cint301` | `MiscReceiptIssueFormPushHandler` | 杂收杂发审批 | `FORM-4B08ADABCDAA45788C043729A855E03ED76G` | `TPROC--MQD66371C1N84FPSJZO4W79IN4BW2AE7I08TM0` | | `asft800` | `Asft800FormPushHandler` | 包装工单变更审批流程 | `FORM-40D6FBBBF4A94864A2AF9535AAC7924CRBH2` | `TPROC--U2I66F719HF854X6PIGK5A2AP9VC2LODB7YSM1` | | `apmt100` | `Apmt100FormPushHandler` | 供应商导入流程 | `FORM-9EE14CA14D8D4AEE8102944E669E6B0CFUKS` | `TPROC--75G66PD15YG82766OFSWD8BS4ZJ33YPRFOZSM2` | ### 5.2 `mainData` 规则 | 字段 | 说明 | | --- | --- | | `rq` | 申请日期,通常为 `yyyy-MM-dd` 或毫秒时间戳 | | `bm` | 钉钉部门 ID | | `tjr` | 提交人工号,用于查询员工花名册并解析钉钉 `userId` | | `gs` | 公司或组织编码 | | `t100id` | T100 业务标识 | | `ent` | 地区或企业编号 | | `t100bm` | T100 部门 | 顶层 `bm`、`gs`、`tjr` 非空时,会覆盖写入 `mainData`。服务端会将 `mainData` 中的通用字段补入各明细行,便于表单处理器复用。 ### 5.3 `detailData` 规则 `detailData` 外层必须是对象,内部至少包含一个数组字段。数组字段名支持: - `detailData` - `detailData1` - `detailData2` - 其他满足 `detailData\d*` 格式的字段 不同单据的约定: | 类型 | 明细字段 | | --- | --- | | `cpmp402` | `detailData` | | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `detailData` | | `axmt500` | `detailData1`、`detailData2`,兼容历史 `detailData` | | `axmt140` | 优先 `detailData1`,缺失时回退到 `detailData` | | `cint301` | `detailData1` | | `asft800` | `detailData1` | | `apmt100` | 无明细,传入空对象 `{}` | 明细对象中可以直接传入宜搭 `fieldId`。服务端会保留包含 `Field_` 的字段值,用于补充默认字段映射。 ### 5.4 发起人解析 1. `ErpYidaPushServiceImpl` 优先使用顶层 `tjr`,其次使用 `mainData.tjr`。 2. 服务端通过员工花名册查询工号对应的 `dingtalkId`。 3. `dingtalkId` 会作为 `originatorUserId`、`userId` 和 `applicant` 写入请求上下文或明细行。 4. 匹配到 `YidaFormPushHandler` 时,提交人工号允许为空,具体处理器可以使用默认发起人兜底。 5. 未匹配到表单处理器并走通用 `YidaProcessService` 时,提交人工号不能为空。 ### 5.5 `asft800` 包装工单变更审批流程 调用 `POST /api/erp/yida/push` 时传入 `type: "asft800"`。表头使用 ERP 原始字段,明细必须放在 `detailData.detailData1`。 ```json { "type": "asft800", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "mainData": { "rq": "2026-09-16", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "t100id": "EMP0001", "ent": "87", "t100bm": "PACK", "sfkaent": "87", "sfkasite": "ML", "sfkadocno": "WO-20260916-001", "sfka900": "2", "sfka902": "2026/09/16", "sfka057": "包装工单", "sfka010": "ITEM-001", "imaal003": "锂电池包装组件", "imaal004": "标准规格", "sfka012": 100.5, "sfka006": "SOURCE-001", "sfkaud009": "MO-001", "sfka906": "变更物料及数量", "ooagud004": "dingtalk-user-id" }, "detailData": { "detailData1": [ { "sfkgent": "87", "sfkgdocno": "WO-20260916-001", "sfkg900": "2", "sfkgseq": "10", "sfkg901": "变更", "sfkg006": "MAT-002", "imaal003": "新物料名称", "imaal004": "新物料规格", "sfkg013": 101.5, "sfkg014": "PCS", "sfba006": "MAT-001", "o_imaal003": "原物料名称", "o_imaal004": "原物料规格", "sfba013": 100.0 } ] } } ``` `tjr` 是员工工号,服务端会从员工花名册解析对应钉钉用户作为发起人;未传时,处理器使用 `mainData.ooagud004` 作为钉钉用户 ID。`bm` 为钉钉部门 ID,`sfka902` 支持 `yyyy-MM-dd` 或 `yyyy/MM/dd` 格式。 ### 5.6 `apmt100` 供应商导入流程 调用 `POST /api/erp/yida/push` 时传入 `type: "apmt100"`。该流程无明细,`detailData` 必须为 `{}`。 ```json { "type": "apmt100", "bm": "123456789", "gs": "ML", "tjr": "EMP0001", "mainData": { "rq": "2026-09-16", "sqlx": "NEW", "gysmc": "供应商名称", "gysbm": "SUP-001", "sh": "91330100TEST", "gczczj": "1000000.50", "clsj": "2026/09/16", "zysccp": "锂电池包装组件", "gcgm": "120", "lcbh": "PM-001", "smzq": "ACTIVE", "pmbb033all": "CNY", "pmbb034all": "VAT", "pmbb053all": "NET30", "pmbb033": "CNY", "pmbb034": "VAT", "pmbb053": "NET30", "pmbb037": "NET30", "gst100": "ML", "lxr": "张三", "gsdz": "浙江省杭州市", "lxfs": "13800000000" }, "detailData": {} } ``` `lcbh` 用于生成流程标题;若其为空,可使用 `docno`。`clsj` 支持 `yyyy-MM-dd` 或 `yyyy/MM/dd`。所有 `pmbb***all` 字段映射到集团惯用数据,未带 `all` 后缀的 `pmbb***` 映射到据点惯用数据。 ## 6. 日志和异常 ### 6.1 日志 Logback 输出: - 控制台日志。 - `${LOGGING_FILE_PATH}/application.log`。 - `${LOGGING_FILE_PATH}/error.log`。 - `archive` 目录下的滚动归档日志。 `HttpRequestResponseLoggingFilter` 会记录 HTTP 方法、URI、状态码、耗时、请求体和响应体。单个载荷最大记录 10KB,并对 `password`、`secret`、`token`、`authorization`、`accessToken`、`systemToken`、`key` 等字段脱敏。 ### 6.2 异常响应 | 场景 | HTTP 状态码 | 说明 | | --- | --- | --- | | 请求体格式错误或参数校验失败 | `400` | 请求不合法 | | 配置缺失 | `412` | 必要配置未提供 | | 宜搭或钉钉上游业务错误 | `502` | 返回上游错误码和错误信息 | | 外部 T100 调用失败 | `502` | 返回 `MINGLEI_PORT_CALL_FAILED` | ## 7. 构建和运行 环境要求: - JDK 8 - Maven 3.6+ - 可访问项目依赖仓库和内部 `malk` 依赖 本地构建: ```powershell mvn clean package ``` 本地运行: ```powershell $env:SERVER_PORT = "8080" $env:DINGTALK_APP_KEY = "" $env:DINGTALK_APP_SECRET = "" $env:YIDA_COORDINATION_OFFICE_APP_TYPE = "" $env:YIDA_COORDINATION_OFFICE_SYSTEM_TOKEN = "" $env:YIDA_MASTER_DATA_APP_TYPE = "" $env:YIDA_MASTER_DATA_SYSTEM_TOKEN = "" mvn spring-boot:run ``` 运行打包产物: ```powershell java -jar target\mjava-minglei-1.0.0-SNAPSHOT.jar ``` ## 8. 测试 执行完整测试: ```powershell mvn clean test ``` 当前测试覆盖: - Spring Boot 上下文加载。 - Controller 请求和响应结构。 - 员工花名册同步、查询和字段提取。 - 宜搭流程处理器分发。 - ERP 推送到不同宜搭表单处理器的字段映射。 - 外部 T100 请求组装和响应转发。 当前验证结果: ```text Tests run: 36, Failures: 0, Errors: 0, Skipped: 1 ``` ## 9. 扩展规范 ### 9.1 新增宜搭流程处理器 新增可由 `/api/yida/process-instances` 触发的通用流程时: 1. 新建 `XxxYidaProcessHandler`。 2. 实现 `YidaProcessHandler`。 3. 在 `getType()` 返回业务路由编码。 4. 在处理器中维护表单 UUID、流程编码和字段映射。 5. 补充对应单元测试。 6. 更新 README 的支持类型说明。 ### 9.2 新增 ERP 表单推送处理器 新增可由 `/api/erp/yida/push` 路由的表单流程时: 1. 新建 `XxxFormPushHandler`。 2. 实现 `YidaFormPushHandler`。 3. 在 `getTypes()` 返回主 `type` 和必要别名。 4. 在处理器中维护表单 UUID、流程编码和字段映射。 5. 在 `src/test/java` 中补充字段映射和异常测试。 6. 更新 README 的 ERP 推送类型表。 ### 9.3 字段映射原则 - 业务 `type` 是路由键,不等于表单 UUID。 - 应用凭证、表单 UUID、流程编码由服务端维护,调用方不传。 - 表单处理器优先使用 ERP 原始字段名映射。 - 调用方确需补充时,可以传入宜搭 `fieldId` 字段。 - 不要在日志、文档或测试数据中写入真实密钥、Token、Cookie 或数据库密码。 ## 10. 运维注意事项 - 部署账号必须对 `LOGGING_FILE_PATH` 有写权限。 - 宜搭和钉钉接口所需的网络连通性、权限范围、组织数据权限由部署环境保证。 - 当前 Controller 未内置业务鉴权。如需访问控制,应在网关或统一认证层配置。 - 发生宜搭配置不匹配时,确认应用编码、System Token 和处理器选择的客户端属于同一应用。 - 员工花名册依赖数据库连接和数据完整性。若 `mainData.tjr` 无法解析,优先检查花名册同步状态和员工 `dingtalkId`。