|
|
@@ -0,0 +1,85 @@
|
|
|
+## ADDED Requirements
|
|
|
+
|
|
|
+### Requirement: 集成平台 OAuth2 鉴权与 token 缓存
|
|
|
+
|
|
|
+`INTPImplClient_User#getAccessToken()` SHALL 通过 `client_credentials` 流程获取 access_token,并使用 `UtilToken` 缓存。CRUD 方法 MUST 接收 `access_token` 作为第一个参数,调用方先调 `getAccessToken()` 再传入。
|
|
|
+
|
|
|
+#### Scenario: 首次拉取 access_token
|
|
|
+
|
|
|
+- **WHEN** `UtilToken` 缓存中无 `integration:accessToken`
|
|
|
+- **THEN** 向 `{baseUrl}/iam/token` 发 POST,body 为 `application/x-www-form-urlencoded`,字段:`grant_type=client_credentials` / `client_id={IntpConf.clientId}` / `client_secret={IntpConf.clientSecret}`
|
|
|
+- **AND** 响应 `access_token` 写入缓存,TTL = `expires_in - 60` 秒
|
|
|
+
|
|
|
+#### Scenario: 缓存命中
|
|
|
+
|
|
|
+- **WHEN** `UtilToken` 缓存中已有未过期的 `integration:accessToken`
|
|
|
+- **THEN** 直接复用,不发 HTTP 请求
|
|
|
+
|
|
|
+#### Scenario: token 失效
|
|
|
+
|
|
|
+- **WHEN** 服务端 revoke 或过期
|
|
|
+- **THEN** 调用方收到 HTTP 401 + `error/error_description`
|
|
|
+- **AND** 本期不做"401 自动重拿",由调用方决定重试策略
|
|
|
+
|
|
|
+### Requirement: 用户创建
|
|
|
+
|
|
|
+`INTPClient_User.createUser` SHALL 对齐 apifox 接口 `POST /iam/api/users`。必填字段必须显式入参;可选字段通过 `body_ext` Map 注入。
|
|
|
+
|
|
|
+#### Scenario: 创建用户(最小集)
|
|
|
+
|
|
|
+- **WHEN** 调用 `createUser(accessToken, "alice", "Pwd@123", null)`
|
|
|
+- **THEN** 向 `{baseUrl}/iam/api/users` 发 POST `application/json`
|
|
|
+- **AND** Header `Authorization: Bearer {accessToken}`
|
|
|
+- **AND** body 为 `{"username":"alice","password":"Pwd@123"}`
|
|
|
+
|
|
|
+#### Scenario: 创建用户(完整字段)
|
|
|
+
|
|
|
+- **WHEN** `body_ext` 含 `name` / `email` / `phone_number` / `user_job_number` / `nick_name` / `picture` / `org_ids` / `address` / `title` / `hired_date` / `tag` / `group_positions`
|
|
|
+- **THEN** body 合并 `username` / `password` 与 `body_ext` 全部字段
|
|
|
+- **AND** javadoc MUST 枚举上述字段名 + 类型 + 含义
|
|
|
+
|
|
|
+#### Scenario: 创建失败
|
|
|
+
|
|
|
+- **WHEN** 服务端返回 `{"result":false,"error":"USER_EXIST","error_description":"用户已存在"}`
|
|
|
+- **THEN** 返回 `McR.error("USER_EXIST", "用户已存在")`
|
|
|
+
|
|
|
+### Requirement: 用户修改
|
|
|
+
|
|
|
+`INTPClient_User.updateUser` SHALL 对齐 apifox 接口 `PATCH /iam/api/user`。`username` 通过 Query Param 传递(**非 Path**),其它字段全部走 body。
|
|
|
+
|
|
|
+#### Scenario: 修改单字段
|
|
|
+
|
|
|
+- **WHEN** 调用 `updateUser(accessToken, "alice", Map.of("email", "alice@new.com"))`
|
|
|
+- **THEN** 向 `{baseUrl}/iam/api/user?username=alice` 发 PATCH
|
|
|
+- **AND** body 为 `{"email":"alice@new.com"}`
|
|
|
+- **AND** 不传的字段保持服务端原值不变
|
|
|
+
|
|
|
+### Requirement: 用户删除(批量)
|
|
|
+
|
|
|
+`INTPClient_User.deleteUsers` SHALL 对齐 apifox 接口 `POST /iam/api/users/delete`。**HTTP 方法是 POST**,因 body 需带 username 数组。
|
|
|
+
|
|
|
+#### Scenario: 批量按登录名删除
|
|
|
+
|
|
|
+- **WHEN** 调用 `deleteUsers(accessToken, List.of("alice","bob"))`
|
|
|
+- **THEN** 向 `{baseUrl}/iam/api/users/delete` 发 POST `application/json`
|
|
|
+- **AND** body 为 `{"usernames":["alice","bob"]}`
|
|
|
+
|
|
|
+### Requirement: 用户查询
|
|
|
+
|
|
|
+`INTPClient_User.queryUsers` SHALL 对齐 apifox 接口 `GET /iam/api/users`,支持分页与多维过滤。
|
|
|
+
|
|
|
+#### Scenario: 关键字搜索
|
|
|
+
|
|
|
+- **WHEN** 调用 `queryUsers(accessToken, Map.of("q","alice","page",1,"size",20))`
|
|
|
+- **THEN** 向 `{baseUrl}/iam/api/users?q=alice&page=1&size=20` 发 GET
|
|
|
+- **AND** 解析响应 `data.items[]` 返回 `McR.success(data)`
|
|
|
+
|
|
|
+### Requirement: 配置段独立命名
|
|
|
+
|
|
|
+`INTPConf` SHALL 通过 Spring `@ConfigurationProperties("integration")` 加载配置;`baseUrl` 不含 `/iam` 前缀。
|
|
|
+
|
|
|
+#### Scenario: 三个客户子模块的 yml 模板
|
|
|
+
|
|
|
+- **WHEN** 浏览 `mjava-mcli` / `mjava-shunfeng` / `mjava-guangming` 的 `application-{dev,prod}.yml.example`
|
|
|
+- **THEN** 每个模板 MUST 含 `integration:` 段示例,字段:`baseUrl` / `clientId` / `clientSecret`
|
|
|
+- **AND** 真实凭据从 `${INTP_CLIENT_ID}` / `${INTP_CLIENT_SECRET}` 环境变量注入
|