|
|
@@ -0,0 +1,86 @@
|
|
|
+# 集成平台用户接口设计
|
|
|
+
|
|
|
+> 文档来源: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<String, Object> body_ext`,javadoc 完整枚举字段名 + 类型 + 含义
|
|
|
+
|
|
|
+具体签名:
|
|
|
+
|
|
|
+```java
|
|
|
+McR getAccessToken(); // 内部用,不进 Client 接口
|
|
|
+McR createUser(String accessToken, String username, String password,
|
|
|
+ Map<String, Object> 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<String, Object> body_ext); // username 走 Query Param,body 全是可选字段
|
|
|
+McR deleteUsers(String accessToken, List<String> usernames); // body: { "usernames": [...] }
|
|
|
+McR queryUsers(String accessToken, Map<String, Object> 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 自动重拿"重试链,由调用方处理。
|