# 集成平台用户接口设计 > 文档来源:apifox 公开站 `https://uj0cyahyvs.apifox.cn/`(共 29 个 API;本期取 5 个) > 抓取时间:2026-04-26 ## 覆盖度矩阵 | 接口 | apifox ID | Method | Path | 本期 | |------|-----------|--------|------|------| | 获取 access_token(请求体方式) | 288915790 | POST | `/iam/token` | ✅ | | 获取 access_token(Basic 方式) | 288915791 | POST | `/iam/token/basic` | ⏸ 暂不实现,请求体方式更通用 | | 创建用户 | 288915800 | POST | `/iam/api/users` | ✅ | | 修改用户 | 288915803 | PATCH | `/iam/api/user` | ✅ | | 删除用户 | 288915805 | POST | `/iam/api/users/delete` | ✅ | | 查询用户 | 288915799 | GET | `/iam/api/users` | ✅(顺手对齐,便于联调验证) | | 其它 23 个接口(org / 角色 / Webhook / 连接器 / token basic / 启用禁用 / 改密码 / ...) | 288915790-288915818 | - | - | ⏸ 后续按需另立 change | ## 关键决策 ### D1. 鉴权方式选「请求体」而非「Basic」 两种方式服务端等价。选请求体方式(`/iam/token`)原因: - `Basic base64(client_id:client_secret)` 需要在客户端做编码 + Header 注入,封装复杂度更高 - 请求体方式直接 form body 三个字段,与 mjava 现有 vendor token 拉取风格一致 ### D2. token 缓存走 `UtilToken`,namespace=`integration` - key:`integration:accessToken`(基座单租户场景,无需拼 clientId) - TTL:服务端返回 `expires_in`,本地缓存设为 `expires_in - 60`(提前 60s 失效,避免边界问题) - 缓存 miss 或过期 → `IntpClientImpl.getAccessToken()` 同步拉新 ### D3. Client 方法签名遵循 baseline §3.4.2 - 第一个参数始终是 `String accessToken`(按现有 dingtalk/aliwork Client 风格) - 必填字段显式入参(`username`、`password`、`usernames`) - 可选字段统一打入 `Map body_ext`,javadoc 完整枚举字段名 + 类型 + 含义 具体签名(`INTPClient_User`): ```java String getAccessToken(); // 自带缓存,调用方第一步先取 Map createUser(String access_token, String username, String password, Map body_ext); // body_ext: name/email/phone_number/user_job_number/nick_name/picture/org_ids/address/title/hired_date/tag/group_positions Boolean updateUser(String access_token, String username, Map body_ext); // username 走 Query Param,body 全是可选字段 Boolean deleteUsers(String access_token, List usernames); // body: { "usernames": [...] } Map queryUsers(String access_token, Map query); // query: q/org_id/page/size/attrs/return_users_in_sub_org ``` 返回类型沿用 mjava 现有 Client 风格(如 `DDClient_Group.createGroup` 返回 `Map`、`addGroupUser` 返回 `Boolean`)。`getAccessToken()` 进接口,调用方先取再调 4 个 CRUD 方法,与 dingtalk `DDService.getAccessToken()` 模式对齐。 ### D4. 响应 unwrap 策略 集成平台统一响应:`{ result, error, error_description, data? }`。沿用 `DDR` / `YDR` 模式新建 `INTPR extends VenR`: - `INTPR.assertSuccess()` 在 `result=false` 时抛 `McException(error, error_description, "integration")` - 由全局 `CatchException` 统一拦截转 HTTP 4xx/5xx - Client 方法直接返回 `(Map) intpR.getData()` 或 `intpR.isResult()`(参考 `DDR.doPost(...).isSuccess()` 用法) ### D5. 配置段命名 `application.yml` 增加 `integration:` 段,与 `dingtalk:` `aliwork:` 平级: ```yaml integration: baseUrl: https://iam.example.com # 集成平台域名(不含路径前缀) clientId: ${INTP_CLIENT_ID:} clientSecret: ${INTP_CLIENT_SECRET:} ``` `baseUrl` 不含 `/iam/` 前缀,由 Client 内部拼接,便于将来扩到 `/acm/` 域。 ### D6. 不做的事(明确 yagni 边界) - ❌ 高阶 Service 封装(用户明确"不需要",调用方自己组合) - ❌ 多租户(每租户一套凭据) —— 留待 mjava-pro `DynamicIntpService` 后续专项 - ❌ Webhook 接收 `/iam/api/open/event/...` —— 不在用户域内 - ❌ 单元测试 / 集成冒烟 —— 阻塞在 Maven,后续阶段做 ## 风险 1. **apifox 文档与生产 API 漂移**:apifox 是文档站,与实际生产环境是否一致需联调验证。建议接入第一个客户时立即抓真实 access_token 跑一次 createUser。 2. **`/iam/api/user` 单数 vs `/iam/api/users` 复数**:修改用户 endpoint 是 `/iam/api/user`(单数),与其它 CRUD 不一致。已按文档原样实现,**实施冒烟时需复核**。 3. **`updateUser` 的 `username` 是 Query 而非 Path**:与 RESTful 惯例不同,不要写成 `/iam/api/user/{username}`。 4. **`deleteUsers` 用 POST 而非 DELETE**:因为 body 要带数组;按文档原样保留。 5. **token 失效处理**:本期仅做"过期前刷新"。如果服务端单边失效(管理员 revoke),调用会拿到 401,本期不做"401 自动重拿"重试链,由调用方处理。