design.md 4.9 KB

集成平台用户接口设计

文档来源: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 风格)
  • 必填字段显式入参(usernamepasswordusernames
  • 可选字段统一打入 Map<String, Object> body_ext,javadoc 完整枚举字段名 + 类型 + 含义

具体签名(INTPClient_User):

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<String> 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 返回 MapaddGroupUser 返回 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: 平级:

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. updateUserusername 是 Query 而非 Path:与 RESTful 惯例不同,不要写成 /iam/api/user/{username}
  4. deleteUsers 用 POST 而非 DELETE:因为 body 要带数组;按文档原样保留。
  5. token 失效处理:本期仅做"过期前刷新"。如果服务端单边失效(管理员 revoke),调用会拿到 401,本期不做"401 自动重拿"重试链,由调用方处理。