# 017 任务清单 ## 2026-09-08 增量(实现中) - [x] T23 骑手/商家 Apple、Google 独立凭证配置、角色绑定、会话签发及用户端兼容回归。 - [x] T24 兼容用户旧 LINE provider、无 provider 回调与旧/新绑定数据。 - [x] T25 更新三端接入文档、环境配置与 SQL 人工执行边界。 - [ ] T26 JDK 21 定向测试、关联回归、模块构建与最终差异检查。 - [x] T27 业务 LINE 服务端 state 签发/单次消费、两条 code 入口校验及旧用户协议兼容。 - [ ] T28 骑手/商家 App 页面、SDK、四语言和回跳 state 比对接入;源码路径待提供。 ### 本轮验证记录 - JDK 21 定向测试及 package:79 tests,0 failures/errors,所有 reactor 模块 SUCCESS。覆盖账号角色、短信绑定、用户原登录/注册、旧 LINE 协议、三端 audience、签名与 state。日志:`ruoyi-admin/target/oauth-final-verification.log`。 - 另运行完整 `mvn -pl ruoyi-admin -am package`:`ruoyi-system` 83 tests 全通过,`ruoyi-admin` 419 tests 中 418 通过、1 error。仍是既有 `PosOrderQsOprateControllerTest` 未注入 `UserService` 导致 NPE,未修改该无关测试,T26 保留未勾选。日志:`ruoyi-admin/target/oauth-regression.log`。 - 新业务账号绑定必须验证实际短信码,用户原校验行为保持兼容;固定短信码拒绝用例已先观察失败再验证修复。 - 用户 App 源码未修改。业务 App 源码、真实渠道配置和真机端到端验证未完成;未执行任何数据库 SQL、部署或密钥轮换。 - 既有实现已包含在 `af8cc41`;本轮增量保留工作区,尚未提交。 > 顺序执行;每步完成后编译验证。 - [x] T1 数据模型:`updatesql/sql.md` 加 `info_user_oauth` 建表(含 uk_provider_uid 唯一索引、idx_user_id)。 - [x] T2 实体:`InfoUserOauth.java`(@TableName=info_user_oauth,字段 id/userId/provider/providerUid/createTime)。 - [x] T3 mapper:`InfoUserOauthMapper extends BaseMapper`(无 XML)。 - [x] T4 LoginUserDto 加 `provider` 字段。 - [x] T5 JwtUtil.setToken(tokenKey, LoginUserDto) 加 `provider` claim(非空才写)。 - [x] T6 配置:application.yml 加 `oauth.apple.clientId/jwks-url`、`oauth.google.clientId/tokeninfo-url`、`oauth.line.profile-url`。 - [x] T7 依赖:ruoyi-admin/pom.xml 加 `nimbus-jose-jwt`。 - [x] T8 OAuthVerifyService:`verify(provider, credential)` → providerUid;Apple(nimbus 验 ES256)、Google(tokeninfo)、LINE(v2/profile),校 audience/iss/exp。 - [x] T9 DTO:OAuthLoginDto、OAuthBindDto。 - [x] T10 InfoUserController.oauthLogin:校验→查 oauth→命中签 token / 未命中缓存 tempKey 返 needPhone。 - [x] T11 InfoUserController.oauthBindPhone:取 providerUid + 验短信→关联/新建→写 oauth→签 token。 - [x] T12 编译验证(JDK21):ruoyi-admin + ruoyi-system + ruoyi-common。 ## 联调待办(开发提供配置后) - [ ] 部署环境提供真实 Apple bundleId/clientId、Google clientId,以及 `LINE_USER_CLIENT_SECRET`、`LINE_RIDER_CLIENT_SECRET`、`LINE_MERCHANT_CLIENT_SECRET`;Secret 不写入仓库。 - [ ] 部署前轮换 Git 历史中曾出现的旧 LINE Channel Secret。 - [ ] 使用真实 Apple、Google、三套 LINE Channel token/code 完成端到端验证(Apple 需真机/测试机 Sign in with Apple)。 - [ ] 三个前端 App 完成登录入口、首次绑手机、provider/回跳 scheme 分流及 i18n 联调。 - [ ] 三端统一升级 `state` CSRF 协议:服务端签发并单次消费,App 保存发起值并在回跳时比对。 ## 代码评审修复 (2026-07-30) 评审发现并修复(均已 JDK21 编译通过): - [x] **HTTP 超时**:`OAuthVerifyService.httpGet` 原用 `HttpClients.createDefault()` 无超时,provider 慢/不可达会拖垮请求线程池 → 加 `RequestConfig`(连接 5s / 读取 10s / 获取连接 5s)。 - [x] **停用账号拦截**:`oauthBindPhone` 用 `getuser(phone)` 只滤 del_flag、不滤 status,停用账号(status≠0)能通过三方绑定绕过停用 → 加 `status!=0` 拦截(与 `lodeing` / `oauthLogin` 已绑定路径一致),文案复用 `no.user.stop`。 - [x] **Apple audience**:原 `aud.get(0)` 在多 audience 时可能漏判 → 改 `aud.contains(appleClientId)`。 - [x] **i18n 化(裸中文 → message key)**:新增 `no.oauth.*` 共 10 个 key,写入 5 份 properties(`messages` / `zh_CN` / `zh_TW` / `en_US` / `vi`);`OAuthVerifyService` 异常文案与 `InfoUserController` 提示全部走 `MessageUtils.message`,与 `lodeing` 风格一致。新增 key: - 控制器:`no.oauth.provider.blank` / `needphone` / `tempkey.missing` / `tempkey.expired` - 校验服务:`no.oauth.credential.blank` / `provider.unsupported` / `token.invalid` / `token.expired` / `audience.mismatch` / `verify.fail`(带 `{0}` 渠道名;异常详情仅记录在服务端日志,不返回客户端) ## 用户/骑手/商家 LINE 登录增量(2026-09-04) - [x] T13 配置三组 LINE Channel:`line_user`、`line_rider`、`line_merchant`,共用 token/profile URL,分别配置 redirect URI 和 App scheme。 - [x] T14 `OAuthVerifyService` 按 provider 白名单选择对应 Channel ID、Secret、redirect URI,并使用完全相同的 redirect URI 换 token。 - [x] T15 共用 `GET /auth/line/callback`,接收 provider/code/state,按 provider 分流验证、绑定查询、token key 和 App 回跳。 - [x] T16 `oauthLogin` / `oauthBindPhone` 支持三个 LINE provider;骑手只绑定 `userType=2`,商家只绑定 `userType=1/3/4/5`,均不得自动创建。 - [x] T17 商家/骑手/子账号统一使用 `tel_phone`;业务层增加有效账号手机号唯一检查,修改时排除自身,软删除后允许复用。 - [x] T18 覆盖 `addqishou`、`addshanghu`、`Bindingphone`、`setuser`、平台新增/修改、`stall/addOwner` 及商家子账号新增入口。 - [x] T19 SQL:先预检 provider 和有效业务账号手机号冲突,再将现有 `info_user_oauth.provider='line'` 迁移为 `line_user`,并把业务账号 `phone` 补入 `tel_phone`,不清除原字段。 - [x] T20 更新 LINE 前端接入说明:原授权方式不变,三个端分别增加 provider 参数并使用对应 Channel ID/回跳 scheme。 - [x] T21 编写并运行定向测试,覆盖 provider 分流、角色限制、手机号唯一规则(2026-09-04:31 tests,0 failures/errors)。 - [x] T22 使用 JDK 21 编译 `ruoyi-common`、`ruoyi-system`、`ruoyi-admin`(2026-09-04 reactor package SUCCESS);真实 LINE 端到端联调保留为外部待办。 > 2026-09-04 另执行完整 reactor test:`ruoyi-system` 53 tests 全通过,`ruoyi-admin` 371 tests 中 370 通过、1 error。失败项为既有 `PosOrderQsOprateControllerTest.newTasksContainOnlyPaidOrdersWaitingForRiderBeforeMerchantAcceptance` 未注入 `UserService` 导致 NPE,与 017 修改文件及定向测试无关,本次不跨范围修改。 > 交付记录(2026-09-04):最终代码复核未发现剩余 Critical/Important;`git diff --check` 通过;数据库迁移未执行,须先运行 `updatesql/sql.md` 中两项冲突预检再由开发者手工执行;017 修改当前保留在工作区,尚未提交。 ## 2026-09-24 后续增量待办:登录渠道与第三方名称展示 - [x] T29 为 `info_user_oauth` 增加可空字段 `provider_display_name`,并将迁移 SQL 记录到 `updatesql/sql.md`(不直接执行)。 - [x] T30 LINE 登录每次成功读取并刷新本渠道 `displayName`;读取失败保留旧值、不阻断登录。 - [x] T31 App 首次 Apple 授权取得 `fullName` 时传给后端;后端验证身份后保存到对应 Apple 绑定,历史无名称允许为空。 - [x] T32 用户资料接口与 App 分别展示原有昵称、本次登录渠道及对应第三方名称;昵称修改流程不变,空名称只显示渠道。 - [x] T33 编写并运行渠道切换、跨账号隔离、LINE 名称更新、Apple 首次/历史空值及 App 展示的定向验证。 > 实施记录(2026-09-28,test-202609v2):后端全部完成——`OAuthIdentity` 随凭证校验带回 displayName,`OAuthLoginDto.fullName` 承载 Apple 首次授权姓名,tempKey 缓存值三段式 `provider@providerUid@displayName`(空名称保持两段式,兼容旧值);JWT 新增 `providerUid` claim(gettoken 续签保真);getuserinfo 按会话渠道 + userId+providerUid 精确绑定回填 `oauthProvider`/`providerDisplayName`,旧会话无 providerUid 只展示渠道不任意挑绑定;LINE 直连与回调两条入口均在成功登录时刷新 displayName(空/未变保留旧值,失败不阻断)。定向测试 100 通过 + 全模块回归 759 tests 0 failures/0 errors。SQL 未执行(updatesql/sql.md 待手动执行);App 端展示联调待 App 源码接入(与 T28 同为外部待办)。 ## 2026-10-08 增量:业务端改用账号密码绑定 - [x] T34 `OAuthBindDto` 新增 `userName`、`password` 字段(无校验注解)。 - [x] T35 `oauthBindPhone` 业务分支(`*_rider`/`*_merchant`)改为账号密码绑定:`user_name + user_type + del_flag` 查账号、RSA 解密比对、`canOauthLogin` 校验后写绑定;移除业务端短信路径;用户端手机号+短信逻辑逐行保留。 - [x] T36 新增 i18n key `no.oauth.username.blank` 写入 5 份 properties;其余提示复用现有 key。 - [x] T37 定向测试(先红后绿):业务密码绑定成功(免手机号)、密码错误/缺账号/缺密码拒绝、账号不存在不自动创建、已删除账号拒绝、用户端短信绑定回归;移除业务短信绑定与 tel_phone 多账号旧用例。 - [x] T38 JDK 21 定向测试与 `ruoyi-admin` 模块回归、编译验证;App 端账号密码表单与 LINE 回跳页联调为外部待办。 > 实施记录(2026-10-08,test-202609v2):TDD 先红后绿——RED 时 66 tests 中 10 个新/改用例如期失败(旧短信路径返回"账号不存在"等),实现后 66/66 通过;全模块回归 `mvn -pl ruoyi-admin -am test` 849 tests 0 failures/0 errors(含 `BackendI18nBundleContractTest` key 一致性契约)。已删除账号由查询 `del_flag='0'` 过滤按"账号不存在"拒绝,`canOauthLogin` 二次兜底(delFlag 异常数据返回"账号已停用")。本增量无数据库结构变更,无 SQL 待执行。App 端(骑手/商家)首次绑定页改为账号密码表单、LINE 回跳 `needPhone=1` 页面同改,为外部待办。 ### 代码评审修复 (2026-10-08) 评审(6 维度核实)发现并修复,第二轮 TDD(RED 9 失败 → GREEN),修复后 `InfoUserControllerTest` 67/67、全模块回归 850 tests 0 failures/0 errors: - [x] **业务端绑定不写设备信任**:原实现 `recordLogin` 会把"设备 → 登记手机号"写入信任表,但业务端只验了密码(共享凭证)未验手机号;共享平板后续可经用户端 deviceConfirm 免短信冒用该手机号绑定/自动建会员账号(含钱包)。现仅用户渠道调用 `recordLogin`,业务绑定不写(成功用例断言 `verifyNoInteractions(deviceMapper)`)。 - [x] **userName 查询改 `list()` + 多行友好提示**:原 `getOne` 在重名(平台 `edit()` 改名无唯一复查,库无唯一索引)时抛 TooManyResults 500 且已消耗 tempKey;现多行返回新 key `no.oauth.username.duplicate`(已加 5 份 properties)。 - [x] **去掉 userName 的 `trim()`**:与 `shanglodeing`/`syslodeing` 及建号路径的原串精确匹配一致,避免"带空格账号名日常可登录但绑定查不到"。 - [x] **测试有效性**:成功用例提交密文与存储密文改为不同字符串(RSA 每次加密结果不同),防止实现退化为"密文直接相等比对"时测试仍然通过。 评审确认但不改(既有问题/产品决策,待定夺): - 平台 `add()`/`edit()` 存密码为客户端密文原样入库(`add()` 内 `System.out.println("加密:"...)` 处疑似遗留未做服务端加密);存量密码若非 RSA 密文则密码绑定永远失败(日常密码登录同样失败,属存量数据问题)。 - 业务端绑定成为无速率限制的密码尝试入口(`@Anonymous`、tempKey 可随意发起换取);绑定永久且无解绑路径。建议后续加失败计数锁定或解绑能力(017 后续增量)。 - 骑手日常登录 `syslodeing` 不校验 `status`,停用骑手可密码登录但三方绑定被拒(两端口径不一,既有行为)。 - OAuth 绑定/登录不刷新 `last_login_at`(既有行为,影响"最近登录"展示)。 - RSA 比对逻辑已有 4 份拷贝(登录×2/改密/绑定),建议后续抽公共服务。