plan.md 13 KB

017 实施计划

2026-09-08 执行增量

  1. 在 InfoUserControllerTest 增加苹果/谷歌业务端绑定、角色拒绝、token 分端与用户原协议回归;为凭证校验增加本地 HTTP / 签名 JWT 测试,先验证缺失行为。
  2. 新增 OAuthAccountType 集中描述 provider 对应业务角色;InfoUserController 复用现有绑定流程,保持原 apple/google 用户分支。OAuthVerifyService 新增独立业务端 client ID,缺失时仅拒绝对应新渠道。
  3. LineOAuthProperties、LineCallbackController 兼容旧用户 provider 与无参数回调;绑定查询兼容 line / line_user。不得改变原用户授权地址和强制旧 App 增加参数。
  4. 更新 application.yml 环境变量、DTO 注释、四语言错误与前端接入文档;SQL 仅写人工执行说明,不连接数据库修改数据。
    • 业务 LINE 由 LineOAuthStateService 签发/单次消费 state;新增 /auth/line/authorize 与 DTO,回调及直接 code 登录均校验。用户旧协议不受强制校验影响。
  5. JDK 21 运行 OAuth 定向测试、关联账号回归和 ruoyi-admin -am 构建;记录真实 App / 渠道配置等外部验收限制。继续在当前 test-202609v2 工作区实施。

架构:最大化复用现有登录链路

provider 凭证 ──▶ OAuthVerifyService.verify(provider, credential) ──▶ providerUid
                                                                        │
                ┌───────────────────────────────────────────────────────┘
                ▼
        查 info_user_oauth (provider, provider_uid)
        ├─ 命中 → 取 InfoUser → 校 status/del_flag → issueToken(provider) → 返回
        └─ 未命中 → Redis 缓存 {provider,providerUid}(tempKey,TTL5m) → 返回 needPhone+tempKey
                                                                        │
                ┌───────────────────────────────────────────────────────┘
                ▼  (前端引导输手机号 → getcode 发短信)
        oauthBindPhone(tempKey, phone, code)
          原子消费 tempKey + 取 providerUid + 验短信(复用 lodeing: redis code==input || "8888")
          getuser(phone):
            ├─ 已注册 → 关联(写 oauth)
            └─ 未注册 → createUser(phone) 新建 → 写 oauth
          → issueToken(provider) → 返回
  • token 签发:复用 JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, loginDto),仅给 LoginUserDto 加 provider 字段并在 setToken 写入 claim。
  • 新建用户:复用 createUser 的「建 InfoUser + 建钱包 + 删旧 token」逻辑;昵称=手机号不变。
  • 短信:复用 getcode 发送 + lodeing 的校验(key=phone 去+,万能码 8888)。
  • 一次性绑定凭证:tempKey 在绑定请求开始时以删除成功作为唯一消费权;任一后续校验失败都必须重新发起 OAuth 登录,避免重放和并发重复绑定。

数据模型

CREATE TABLE info_user_oauth (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id      BIGINT       NOT NULL COMMENT '关联 info_user.user_id',
  provider     VARCHAR(16)  NOT NULL COMMENT 'apple/google/line_user/line_rider/line_merchant',
  provider_uid VARCHAR(64)  NOT NULL COMMENT '三方稳定用户ID(Apple sub / Google sub / LINE userId)',
  create_time  DATETIME     DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_provider_uid (provider, provider_uid),
  KEY idx_user_id (user_id)
) COMMENT='三方账号绑定';

API 契约

1) POST /infouser/user/oauthLogin (@Anonymous)

请求 OAuthLoginDto:

provider     apple|google|line|line_user|apple_rider|google_rider|line_rider|apple_merchant|google_merchant|line_merchant
credential   identityToken(Apple) / idToken(Google) / authorizationCode(LINE)
cid, cidType, deviceToken, voIPToken   // 推送字段,与现有登录一致
state        // 业务 LINE 自行提交 code 时必填,由 /auth/line/authorize 签发

响应(命中):

{ code:200, msg, data: InfoUser, token }   // token claim provider=本次渠道

响应(未命中,首次):

{ code:200, msg, data:{ status:"needPhone", tempKey } }

2) POST /infouser/user/oauthBindPhone (@Anonymous)

请求 OAuthBindDto:

tempKey, phone, code   // code=短信验证码
cid, cidType, deviceToken, voIPToken

响应:{ code:200, msg, data: InfoUser, token }

三家凭证校验(OAuthVerifyService)

provider 凭证 校验 取 providerUid 依赖
apple identityToken(ES256 JWT) 拉 appleid.apple.com/auth/keys(JWKS) 验签 + 校 iss/aud/exp sub nimbus-jose-jwt(新增)
google ID-Token GET oauth2.googleapis.com/tokeninfo?id_token= 取 claims + 校 aud=clientId sub httpclient4(已有)
line_user / line_rider / line_merchant authorization code 按 provider 选择 Channel,POST 换 access_token,再 GET api.line.me/v2/profile(Bearer) userId httpclient4(已有)

Google 走 tokeninfo HTTP 而非 firebase-admin,因后者需初始化 FirebaseApp+服务账号(项目未配置),tokeninfo 零配置即可。

文件清单

类型 文件
新增实体 ruoyi-system/.../domain/InfoUserOauth.java
新增 mapper ruoyi-system/.../mapper/InfoUserOauthMapper.java(BaseMapper,无 XML)
新增校验服务 ruoyi-admin/.../utils/oauth/OAuthVerifyService.java
新增 DTO ruoyi-admin/.../user/dto/OAuthLoginDto.java、OAuthBindDto.java
改 controller ruoyi-admin/.../user/InfoUserController.java(+2 方法)
改 token ruoyi-common/.../model/LoginUserDto.java、ruoyi-system/.../utils/JwtUtil.java
改配置 ruoyi-admin/src/main/resources/application.yml(oauth 段)
改依赖 ruoyi-admin/pom.xml(+nimbus-jose-jwt)
SQL updatesql/sql.md

风险 / 待验证

  • Apple / Google / LINE 的凭证校验需用各家真实 token + 正确 clientId/bundleId 联调才能端到端验证;配置值(clientId/jwks url)由前端/运营提供后填入 application.yml。
  • Apple JWKS 建议加缓存(key 偶尔轮换);初版每次拉取或加内存缓存。

2026-09-04 增量:用户/骑手/商家 LINE 登录

最小改造方案

  • 保留前端自行构造 LINE 授权 URL 的现有方式,不新增后端授权入口。
  • 共用 GET /auth/line/callback,回调 URL 增加 provider=line_user|line_rider|line_merchant。
  • OAuthVerifyService 按 provider 选择对应 Channel ID、Channel Secret 和 redirect URI;token/profile URL 继续共用。
  • info_user_oauth.provider 直接保存三个 provider 值;现有 line 数据通过 SQL 迁移为 line_user。
  • line_user 使用 phone 并保留首次自动创建;line_rider、line_merchant 使用 tel_phone 且只绑定已有账号。
  • 登录成功后分别使用 USER_TOKEN_KEY、QS_TOKEN_KEY、SH_APP_TOKEN_KEY。
  • 商家子账号除启用状态外,还必须能解析到有效归属商家/门店权限,失效归属不得通过 LINE 登录。
  • 骑手、商家及商家子账号统一使用 tel_phone。新增/修改入口在业务层检查有效业务账号手机号唯一,软删除账号可复用;不增加数据库唯一索引。

受影响文件

  • ruoyi-admin/src/main/resources/application.yml:三组 LINE Channel 与 App 回跳配置。
  • ruoyi-admin/.../utils/oauth/OAuthVerifyService.java:按 provider 选择 LINE Channel。
  • ruoyi-admin/.../user/LineCallbackController.java:共用回调按 provider 分流。
  • ruoyi-admin/.../user/InfoUserController.java:分角色绑定、签发 token、手机号唯一校验。
  • ruoyi-admin/.../stall/StallController.java:摊主新增统一写入 tel_phone 并检查业务手机号唯一。
  • ruoyi-admin/.../user/BusinessPhoneService.java:有效业务账号手机号唯一检查。
  • ruoyi-admin/.../user/MerchantSubaccountApplicationService.java:子账号改用 tel_phone。
  • updatesql/sql.md:冲突预检后迁移 line provider 和历史业务账号手机号字段。
  • specs/017-oauth-login/*:同步接口及前端参数说明。

三组 Channel Secret 不写入仓库,部署环境分别提供 LINE_USER_CLIENT_SECRET、LINE_RIDER_CLIENT_SECRET、LINE_MERCHANT_CLIENT_SECRET;缺失时 LINE 登录配置校验直接拒绝请求。

验证

  • 单元测试覆盖 provider 配置选择、角色与 token key 映射、手机号重复判断。
  • JDK 21 定向测试与 ruoyi-admin 模块编译。
  • 真实 LINE Channel code、回调地址和三个 App scheme 仍需客户端联调验证。
  • state 的完整 CSRF 防护需服务端签发/消费并由三个 App 保存、回跳比对,作为三端协议联调项统一实施,不能只改单侧回调。

2026-09-24 后续增量计划(待实施)

  1. 扩展 info_user_oauth,新增可空字段 provider_display_name,为每个绑定身份保存第三方显示名称;迁移 SQL 按项目规则写入 updatesql/sql.md,不直接执行。
  2. LINE 登录校验取得 Profile 时保留 displayName,按 (provider, provider_uid) 更新该绑定名称;资料暂不可用时保留旧值。Apple 首次授权时由 App 获取共享的 fullName 并随凭证提交,后端在验证 Apple 身份后保存;历史空值不强制补录。
  3. 用户资料响应从当前登录会话确定渠道及对应绑定身份,提供该身份名称;仅凭 user_id 或 provider 不应在同一用户绑定多个同渠道身份时任意取一条。App 将其与 info_user.nick_name 分开显示;原有昵称修改接口不变。名称为空时只显示渠道,不以用户昵称冒充第三方名称。本增量不在 info_user 写“最近登录渠道”。
  4. 验证 LINE 名称变化、Apple 首次有名/无名及历史空值、同一用户多渠道切换、不同 user_id 不串名,并联调 App 展示。

2026-09-28 实施完成(test-202609v2,验证记录见 tasks.md)。对 App 的契约:/oauthLogin 新增可选 fullName(Apple 首次授权提交);JWT 新增 providerUid claim(gettoken 续签保真,App 无需处理);/getuserinfo 返回新增 oauthProvider(本次登录渠道,手机号会话为空)与 providerDisplayName(该渠道第三方名称,可空——空时 App 只展示渠道,不用昵称冒充)。LINE 名称由服务端在每次成功登录自动刷新,App 无额外操作。

2026-10-08 增量:业务端改用账号密码绑定

设计

  • 动机:业务账号(尤其子账号)不强制登记 tel_phone,按手机号+短信的绑定流程对无手机号账号不可达;user_name+密码是业务端唯一且全覆盖的登录因子。
  • oauthBindPhone 内按 accountType 分流:
    • 业务渠道(allowCreate=false):要求 userName + password(RSA 密文);按 user_name + user_type IN (渠道角色) + del_flag=0 用 list() 查账号(按原串精确匹配、不 trim;多行命中返回 no.oauth.username.duplicate 友好提示而非 getOne 抛 500——平台 edit 改名无唯一复查,存在重名可能);RsaMima 解密比对,异常或比对失败按 no.user.password.error 拒绝;再走 canOauthLogin(停用/子账号状态/归属)。不查短信、不要求 phone。
    • 用户渠道(apple/google/line/line_user):原手机号+短信逻辑逐行保留。
  • 绑定写入、tempKey 原子消费、竞态容错、issueOauthToken 分端签发全部复用现状。
  • 业务端绑定不写 029 设备信任:绑定只验密码未验手机号,共享设备若写入信任,后续用户端 deviceConfirm 会冒用登记手机号一键绑定/建号;recordLogin 仅用户渠道调用。
  • LINE 回调 needPhone=1 契约不动,业务 App 侧渲染账号密码表单(外部待办)。

受影响文件

  • ruoyi-admin/.../user/dto/OAuthBindDto.java:新增 userName、password 字段。
  • ruoyi-admin/.../user/InfoUserController.java:oauthBindPhone 业务分支改为密码绑定;手机号/短信校验移入用户分支。
  • ruoyi-admin/src/main/resources/i18n/messages*.properties(5 份):新增 no.oauth.username.blank。
  • specs/017-oauth-login/*:本增量。

验证

  • InfoUserControllerTest:业务密码绑定成功(免手机号)、密码错误拒绝、缺账号/缺密码拒绝、账号不存在不自动创建、用户端短信绑定回归。
  • JDK 21 定向测试 + ruoyi-admin 模块回归与编译;真实 App 端表单联调为外部待办。