# 集成平台用户接口设计 > 文档来源: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 完整枚举字段名 + 类型 + 含义 具体签名: ```java McR getAccessToken(); // 内部用,不进 Client 接口 McR createUser(String accessToken, 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 McR updateUser(String accessToken, String username, Map body_ext); // username 走 Query Param,body 全是可选字段 McR deleteUsers(String accessToken, List usernames); // body: { "usernames": [...] } McR queryUsers(String accessToken, Map query); // query: q/org_id/page/size/attrs/return_users_in_sub_org ``` ### D4. 响应 unwrap 策略 集成平台统一响应:`{ result, error, error_description, data? }`。 - `result=true` → `McR.success(data)` - `result=false` → `McR.error(error, error_description)` - HTTP 非 2xx → `McException.throwBy(...)` 由 `CatchException` 统一拦截 不在 Client 层抛业务异常(保留 `McR` 让调用方决定如何处理"用户不存在/已删除"等边界)。 ### 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 自动重拿"重试链,由调用方处理。