用户端小程序 登录 / 注册 接口 开发设计文档

  • 模块:ruoyi-auth(认证编排)+ ruoyi-user(会员业务)+ ruoyi-gateway(路由/白名单)
  • 日期:2026-10-12
  • 需求来源:“实现用户小程序的登录和注册接口功能,需要多考虑边界情况,并且严格按照 java 开发规范执行”
  • 关联前端:hugray-user-miniapp、hugray-partner-miniapp(两个小程序共用 /miniapp/** 前缀)
  • 已确认决策:
  1. 小程序接口统一走独立前缀 /miniapp/**,**用户端与服务商端共用同一套前缀**;
  2. C 端注册接口落在该前缀下:POST /miniapp/auth/account/register(规避后台注册与图形验证码冲突);
  3. 微信 secret 由部署方填入 nacos(仓库不落明文);
  4. 短信验证码采用**占位实现**(仅打印日志);
  5. 一并补齐网关 /account/** → lb://ruoyi-user 路由;
  6. 登录页「手机验证登录」按钮 → 在登录页内嵌手机号 + 验证码面板(不新增页面),并同步实现后端 POST /miniapp/auth/phone/code-login。

一、功能概述

为 C 端用户小程序提供四个接口(均位于 /miniapp/auth/** 命名空间):

序号 功能 说明
1 短信验证码下发 注册 / 登录 / 重置密码场景下发 6 位验证码,含 30s 频控、有效期、错误次数限制
2 手机号一键登录 前端通过微信 <button open-type="getPhoneNumber"> 拿到 code,后端调微信接口换取手机号,命中会员则签发登录令牌
3 C 端注册 手机号 + 短信验证码 + 推荐码(必填)注册会员,成功后返回注册成功页
4 手机号验证码登录 手机号 + 短信验证码换取登录令牌(登录页内嵌面板,不新增页面)

C 端会员数据落在 ruoyi-user 服务的 account_user 表;认证令牌沿用 RuoYi 既有 TokenService(网关统一校验 Redis 中的 login_tokens:{token})。


二、现状与关键约束(必须先解决的冲突)

2.1 小程序接口前缀(本次核心改动)

  • 现状:用户端 / 服务商端小程序的认证接口都直接复用后台前缀 /auth/**,与 hugray-admin 混在一起;且服务商端“忘记密码” POST /auth/sms/code 与用户端注册验证码**同 path**,无法区分。
  • 决策:**两个小程序共用一套独立前缀 /miniapp/****,与后台(/auth、/system、/code、/schedule、/file)彻底隔离。
  • 网关按二级段分发到不同微服务(StripPrefix=2,即剥掉 /miniapp 与二级段):
外部路径(miniapp 前缀) 目标服务 服务内真实路径
/miniapp/auth/** lb://ruoyi-auth /sms/code、/phone/login、/account/register、/login、/logout …
/miniapp/account/** lb://ruoyi-user /user/info、/partner/** …
/miniapp/product/** lb://ruoyi-product /home/content、/product/**、/order/** …
/miniapp/system/** lb://ruoyi-system /user/getInfo …

StripPrefix=2 说明:/miniapp/auth/sms/code → 剥掉 miniapp、auth → 服务收到 /sms/code。

2.2 路径冲突:/auth/register 已被后台占用

  • ruoyi-auth 现有 POST /auth/register 面向**后台 sys_user 注册**(hugray-admin 在用)。
  • 网关 ValidateCodeFilter 的 VALIDATE_URL = { "/auth/login", "/auth/register" },会对该路径**强制校验图形验证码 uuid**。
  • C 端注册请求体是 { phone, code, referralCode }(无 uuid),复用该路径必然失败。

结论:C 端注册走 POST /miniapp/auth/account/register(服务内 /account/register)。该路由**不挂 ValidateCodeFilter**,不受图形验证码影响;后台注册 POST /auth/register 完全不受影响。

2.3 网关白名单缺口

ruoyi-gateway-dev.yml 的 security.ignore.whites 当前仅含 /auth/logout、/auth/login、/auth/register、/*/v2/api-docs、/*/v3/api-docs、/csrf。新增的 4 个免登录接口(/miniapp/auth/sms/code、/miniapp/auth/phone/login、/miniapp/auth/phone/code-login、/miniapp/auth/account/register)必须补入,否则 AuthFilter 直接返回 401「令牌不能为空」。

2.4 跨服务调用缺口

  • ServiceNameConstants 无 USER_SERVICE。
  • 无指向 ruoyi-user 的 Feign 客户端(现有 RemoteUserService 指向 ruoyi-system)。

2.5 令牌载体不匹配

TokenService.createToken(LoginUser) 强依赖 loginUser.getSysUser().getUserId()/getUserName(),而 LoginUser 只承载 SysUser,C 端会员是 account_user,两者数据源/类型不同。

方案(最小改动,不改动 common-security):用 SysUser 作为**载体**装载会员身份(userId=account_user.user_id、userName=会员用户名、nickName/avatar/phonenumber/status/delFlag 一并带上),再走 createToken。C 端会员详情由 ruoyi-user 读取网关注入的 user_id 头(SecurityUtils.getUserId())返回,**不经过** /system/user/getInfo。

安全提示:C 端令牌与后台令牌共用同一套网关校验,user_id 存在与 sys_user 数值撞号的可能。后台接口均有 @RequiresPermissions(@RequiresRoles/@RequiresPermissions 对 permissions/roles 为空的 C 端令牌一律拒绝),风险可控。本次不做令牌分级,作为已知项记录。

2.6 无短信 SDK / 无 HTTP 工具类

  • 全仓无短信 SDK → 按已定方案做**占位实现**:抽象 SmsSender 接口 + 默认实现(验证码写 Redis + 日志输出),后续替换为真实短信服务商实现即可。
  • 全仓无 HttpUtils → 调微信接口使用 JDK 自带 java.net.http.HttpClient + fastjson2(ruoyi-common-core 已含,无新增依赖)。

2.7 表结构缺口

account_user 无推荐码字段 → 新增 my_referral_code;推荐关系复用既有 referrer_id(权威库已存在),不新增重复列。

2.8 既有代码缺陷

AccountUserServiceImpl.insertAccountUser 无条件执行 SecurityUtils.encryptPassword(getPassword()),C 端注册无密码会 NPE → 本次修复(密码为空跳过加密)。


三、接口设计

统一响应体:R<T>(code=200 成功 / code=500 失败,msg 为提示文案),与工程既有约定一致;前端 utils/request.js 已按 successCode=200 / unauthorizedCode=401 解包。

规范说明:用户规则中的“Result<T>、code=0 成功、错误码分段”属通用模板,本工程实际框架为 RuoYi R/AjaxResult(200 成功)+ @RestControllerAdvice 全局异常。按“与现有工程保持一致”优先,采用框架既有约定。

3.1 下发短信验证码

  • POST /miniapp/auth/sms/code(服务内路径 /sms/code)
  • 入参 SmsCodeBody:
字段 类型 必填 说明
phone String 是 手机号,^1[3-9]\d{9}$
scene String 否 register=注册(校验未注册);login=验证码登录(校验已注册);reset/为空=通用下发(不校验注册状态)
  • 出参:R<?>(data 为空,成功即已发送)
  • 错误提示:请输入手机号 / 请输入正确手机号 / 手机号已注册,请前往登录 / 该手机号未注册,请先注册 / 验证码发送过于频繁,请稍后再试 / 短信发送失败
  • 说明:scene 为空或非 register/login 时按“通用下发”处理,兼容服务商端忘记密码场景(/miniapp/auth/sms/code 同一接口,传 scene=reset)。

3.2 手机号一键登录

  • POST /miniapp/auth/phone/login(服务内路径 /phone/login)
  • 入参 PhoneLoginBody:
字段 类型 必填 说明
code String 是 微信 getPhoneNumber 回调返回的动态令牌 e.detail.code
  • 出参:R<Map>,data = { access_token, expires_in }(对齐现有 /auth/login)
  • 错误提示:微信授权凭证不能为空 / 微信手机号获取失败,请重试 / 该手机号未注册,请先注册 / 账号已停用,请联系管理员 / 账号不存在

3.3 C 端注册

  • POST /miniapp/auth/account/register(服务内路径 /account/register)
  • 入参 AccountRegisterBody:
字段 类型 必填 说明
phone String 是 手机号
code String 是 短信验证码
referralCode String 是 推荐码(推荐人本人的 my_referral_code)
  • 出参:R<?>(注册成功,前台跳转注册成功页)
  • 错误提示:请输入手机号 / 请输入正确手机号 / 请输入验证码 / 请输入推荐人码 / 验证码错误 / 验证码已失效,请重新获取 / 验证码错误次数过多,请重新获取 / 手机号已注册,请前往登录 / 推荐人码无效 / 推荐人账号异常 / 注册失败,请稍后重试
  • 前端:注册页**不含协议勾选行**(注册无需阅读协议),提交仅校验手机号 / 验证码 / 推荐码三项

3.4 手机号验证码登录

  • POST /miniapp/auth/phone/code-login(服务内路径 /phone/code-login)
  • 入参 PhoneCodeLoginBody:
字段 类型 必填 说明
phone String 是 手机号,^1[3-9]\d{9}$
code String 是 短信验证码(下发时 scene=login)
  • 出参:R<Map>,data = { access_token, expires_in }(对齐现有 /auth/login 与一键登录)
  • 错误提示:请输入手机号 / 请输入正确手机号 / 请输入验证码 / 该手机号未注册,请先注册 / 账号已停用,请联系管理员 / 账号不存在 / 验证码已失效,请重新获取 / 验证码错误 / 验证码错误次数过多,请重新获取
  • 说明:登录页「手机验证登录」按钮为**内嵌面板**(不跳转新页面),面板内提供手机号 + 验证码输入框与「获取验证码」倒计时按钮;请求链路为「先查会员与状态 → 再校验消费验证码」,避免对未注册手机号无谓消耗验证码。

3.5 登录页视觉还原规格(1:1)

依据:doc/琥歌瑞0928.pen 登录页稿 + hugray-user-miniapp/static/ 图片素材。
换算基准:设计稿 375pt 宽 → 750rpx(pt × 2 = rpx)。

元素 设计稿规格 实现值
页面底色 渐变 165°、opacity 0.8:#D3E4F8 0% → #BCD6F4 50% → #ABCCEC 100% 同左(rgba .8 叠白底)
微透十字 ×3 右上 80×80(条宽 22 R11 #FFFFFF35)/ 中左 68×68(条宽 18 R9 #FFFFFF30)/ 左下 100×100(条宽 28 R14 #FFFFFF35) 160 / 136 / 200rpx,条宽 44 / 36 / 56rpx
白色点阵 ×3 左上 x10/y200、右上 x310/y130、右下 x240/y752;微点 3×3 #FFFFFF55,gap 10 radial-gradient 平铺,步距 26rpx(设计稿为不规则形状,此处网格近似)
欢迎来到 26pt 500 #0F172A,居中 52rpx / 500 / 居中
官方 Logo 220×76pt(metabolism-300-logo.png) 440×152rpx
英译标语 11pt 600 #003E7E 字距 1.5,居中 22rpx / 600 / 3rpx
医学双背书 加号 15×15(medical-plus-icon.png)+ 勾选 18.2×15(check-icon.png),项内 gap 6、两项 gap 24,文案 12pt 600 #1E293B 30rpx / 36.4×30rpx;gap 12rpx / 48rpx,文案 24rpx
核心标语卡 industry-4-0-slogan-card.png 整图,卡片视觉宽 319pt 图 759×304rpx,上下负边距 -48rpx / -72rpx 抵消透明留白
微信一键登录 H48 R24,渐变 90° #07C160→#05A352,shadow #07C16040 y6 blur16,图标 18×18 + 文案 15pt 600,gap 8 96rpx / 48rpx;图标 36rpx,文案 30rpx,gap 16rpx;通栏(width:100%; margin:0 覆盖小程序 button 默认 margin:0 auto 导致的收缩)
手机验证登录 H48 R24,渐变 90° #1E5EBC→#144A94,shadow #1E5EBC30 同上(phone-icon.png);展开验证码面板后**隐藏本按钮**,面板内提供「收起」入口
协议勾选行 复选框 16×16 #003E7E R4 + 文案 11.5pt(正文 #64748B / 链接 #1E5EBC),与按钮组间距 19pt 32rpx;间距 38rpx;字号按用户反馈由 11.5pt(23rpx) 上调至 26rpx(与原稿存在偏差,属可读性优化)
底部 Home 条 135×5 R2.5 #0F172A25 270×10rpx / R5rpx

约定:设计稿无「还没有账号?立即注册」入口,按业务需要保留在协议行下方(样式弱化)。

3.6 注册页视觉规范(对齐登录页设计体系)

依据:设计稿 doc/琥歌瑞0928.pen 不含注册页,故注册页以登录页已落地的设计体系为实现规范(同一渐变底板、装饰层、控件规格与字号体系)。

元素 规格(实现值)
页面底板 与登录页同款:渐变 165°、rgba .8 叠白底(#D3E4F8 → #BCD6F4 → #ABCCEC)
背景装饰 复用登录页 3 处微透十字(tr/ml/bl)+ 3 处白色点阵(lt/rt/rb)
顶部导航 透明底融合渐变,居中标题「注册」34rpx 600 #003E7E;左侧返回键 88rpx 热区 + CSS 双边框箭头
主标题 「创建账号」44rpx 600 #0F172A;副标题「完善以下信息,开启健康之旅」26rpx #64748B
表单项 标签 26rpx 500 #334155,项间距 40rpx;输入框 R16 / H88,浅底 #F8FAFC + 描边 #CBD5E1,内嵌 44rpx 品牌图标
获取验证码 208×88rpx / R16 白底 + #1E5EBC 描边;倒计时期间置灰不可点
提交按钮 H96 / R48,渐变 90° #1E5EBC → #003E7E,30rpx 600,shadow 0 12rpx 28rpx rgba(0,62,126,.2)
返回登录入口 按钮下方居中弱化「已有账号?去登录」26rpx
底部 Home 条 270×10rpx / R5 rgba(15,23,42,.145)
边距体系 表单区与底部区统一左右 56rpx,底部叠加安全区 32rpx

说明:注册页**无协议勾选行**(注册无需阅读协议),底部区为「注册按钮 + 已有账号?去登录 + Home 条」,与登录页视觉基调一致但裁剪了协议模块。

3.7 注册成功页视觉规范(对齐登录/注册页设计体系)

依据:设计稿 doc/琥歌瑞0928.pen 不含该页,故与注册页同源,沿用登录页已落地的设计体系(渐变底板 + 装饰层 + 控件规格)。

元素 规格(实现值)
页面底板 与登录/注册页同款:渐变 165°、rgba .8 叠白底(#D3E4F8 → #BCD6F4 → #ABCCEC)
背景装饰 复用登录页 3 处微透十字(tr/ml/bl)+ 3 处白色点阵(lt/rt/rb)
结果区 flex:1 占满剩余空间并整体居中;左右 56rpx
成功图标 160rpx 圆形,渐变 135° #34D399 → #10B981,shadow 0 12rpx 32rpx rgba(16,185,129,.25);白色对勾由两条 8rpx 边框旋转 45° 拼成
结果主标题 「注册成功」40rpx 600 #0F172A,距图标 44rpx
结果说明 「您的账号已成功注册,返回登录开启健康之旅吧」26rpx / 行高 40rpx / 居中 #64748B
底部按钮 「返回登录」H96 / R48,渐变 90° #1E5EBC → #003E7E,30rpx 600,shadow 同注册页;点击 uni.reLaunch 回登录页
底部 Home 条 270×10rpx / R5 rgba(15,23,42,.145)
边距体系 底部区左右 56rpx,叠加安全区 32rpx

四、数据库设计

库:hugray-account,表:account_user(**ALTER,非重建**)。

ALTER TABLE `account_user`
  ADD COLUMN `my_referral_code` varchar(20) DEFAULT NULL COMMENT '本人推荐码(注册时生成,全局唯一)' AFTER `remark`;

-- 推荐码全局唯一:随机生成且不重复使用,逻辑删除行占用不影响业务
CREATE UNIQUE INDEX `uk_account_user_referral_code` ON `account_user` (`my_referral_code`);
-- 推荐关系复用既有 referrer_id(权威库已存在),不新增 referrer_user_id

回滚:

DROP INDEX `uk_account_user_referral_code` ON `account_user`;
ALTER TABLE `account_user` DROP COLUMN `my_referral_code`;

说明:
- 不新增 phone 唯一索引——逻辑删除(del_flag='2')残留行会占用手机号,与“仅校验 del_flag='0'”冲突(与表注释既定结论一致);并发同号注册由 Redis 分布式锁 兜底(见 5.3)。
- 归档脚本:sql/hugray_account_20261012.sql(与代码同一次提交,含变更 + 回滚)。


五、流程设计

5.1 下发验证码(SmsCodeService)

STEP 1: 非空 + 正则校验手机号
STEP 2: scene == register 时,Feign 查会员,命中即抛「手机号已注册,请前往登录」;
        scene == login 时,Feign 查会员,未命中即抛「该手机号未注册,请先注册」
STEP 3: 频控:Redis key sms_code_limits:{scene}:{phone} 存在即抛「发送过于频繁」(TTL 30s,setIfAbsent)
STEP 4: 生成 6 位随机码;写 sms_codes:{scene}:{phone}(TTL 5 分钟),同时清零错误次数计数
STEP 5: 调用 SmsSender.send(phone, code) 下发

5.2 手机号一键登录(AccountLoginService.phoneLogin)

STEP 1: code 非空校验
STEP 2: WeChatMiniappService 获取 access_token(Redis 缓存,TTL 7000s,过期自动刷新)
STEP 3: 调微信 phonenumber.getPhoneNumber 换手机号,失败/异常抛「微信手机号获取失败,请重试」
STEP 4: Feign 按手机号查会员;不存在抛「该手机号未注册,请先注册」
STEP 5: 校验会员状态:status=1 停用 → 抛错;delFlag=2 → 抛「账号不存在」
STEP 6: 组装 SysUser 载体 + LoginUser,tokenService.createToken 返回 { access_token, expires_in }

5.3 C 端注册(AccountLoginService.accountRegister → ruoyi-user.registerAccountUser)

STEP 1: 手机号非空 + 正则校验;推荐码非空校验
STEP 2: 校验并消费短信验证码(见 5.4),失败按错误提示抛出
STEP 3: 获取 Redis 分布式锁 account_register_locks:{phone}(setIfAbsent,TTL 10s),未获取到抛「操作过于频繁,请稍后重试」
STEP 4: 锁内二次校验手机号是否已注册(防并发 / 防重复提交),命中抛「手机号已注册,请前往登录」
STEP 5: Feign 调 ruoyi-user 注册:
       5.1 按推荐码查推荐人,不存在抛「推荐人码无效」;推荐人 status=1 抛「推荐人账号异常」
       5.2 生成唯一 my_referral_code(随机 8 位大写字母数字,库内查重,冲突重试 ≤5 次)
       5.3 组装 AccountUser:userName=phone、phone、nickname=phone、myReferralCode、referrerId、status=0、delFlag=0
       5.4 插入(无密码,password 为空)
STEP 6: 删除验证码 key;finally 释放锁

5.4 验证码校验与消费

STEP 1: 从 sms_codes:{scene}:{phone} 读取;不存在抛「验证码已失效,请重新获取」
STEP 2: 比对失败 → 错误次数 +1(TTL 与验证码一致);达到上限(5 次)删除验证码并抛「验证码错误次数过多,请重新获取」
STEP 3: 比对成功 → 删除验证码 key(一次性使用)

5.5 微信换手机号(WeChatMiniappService)

STEP 1: Redis 取 wechat_access_token:{appid};无则请求 /cgi-bin/token 获取并缓存
STEP 2: POST /wxa/business/getuserphonenumber?access_token=xxx  body={"code": code}
STEP 3: 解析:errcode!=0 或 phone_info.phoneNumber 为空 → 抛业务异常

5.6 手机号验证码登录(AccountLoginService.phoneCodeLogin)

STEP 1: 手机号非空 + 正则校验(兜底),非法抛「请输入正确手机号」
STEP 2: Feign 按手机号查会员;不存在抛「该手机号未注册,请先注册」;delFlag=2 抛「账号不存在」
STEP 3: 校验会员状态:status=1 停用 → 抛「账号已停用,请联系管理员」
STEP 4: 校验并消费短信验证码(scene=login,一次性),失败按错误提示抛出
STEP 5: 组装 SysUser 载体 + LoginUser,tokenService.createToken 返回 { access_token, expires_in }

顺序说明:**先校验会员存在与状态,再消费验证码**(STEP 2/3 在 STEP 4 之前),避免未注册/停用手机号白白消耗验证码;该顺序已由单测锁定(未注册/停用场景断言 verifyAndConsume 从未被调用)。

5.7 测试便利:通用验证码 / 通用推荐码

仅用于联调测试,**通过 Nacos 配置开启,配置为空即完全关闭**(生产不配置即为关闭态)。

通用验证码(ruoyi-auth)
  SmsCodeService.verifyAndConsume 入口先判定:
    命中「配置的通用验证码」且场景为 login / register → 直接放行(不读缓存、不计数、不消费)
    否则走原有「读缓存 → 比对 → 一次性消费」逻辑

通用推荐码(ruoyi-user)
  AccountUserServiceImpl.registerMember 定位推荐人前先判定:
    命中「配置的通用推荐码」→ 跳过推荐人查询与校验,referrerId 留空(不绑定推荐人);
                              同时直接发放 L4 会员等级(system_level = L4)
    否则走原有「按码查推荐人 → 不存在/停用则拒绝」逻辑,且不设等级(走库默认值 C)

命中通用码时打印 WARN 日志留痕(手机号脱敏)。

服务 配置项 示例值 说明
ruoyi-auth hugray.auth.universal-sms-code 999999 空 = 关闭;仅 login / register 场景生效
ruoyi-user hugray.user.universal-referral-code HUGRAY88 空 = 关闭;命中则不绑定推荐人,且注册用户直接为 L4 等级

六、枚举与状态

名称 取值 说明
验证码场景 register / login / reset SmsSceneEnum:注册校验未注册、登录校验已注册、reset/为空为通用下发;含 getByCode
会员状态 status 0 正常 / 1 停用 复用现有语义
删除标志 delFlag 0 存在 / 2 删除 复用现有语义
推荐码生成 8 位 [A-Z0-9] 随机 常量:长度、重试次数

新增常量(收口):
- ServiceNameConstants.USER_SERVICE = "ruoyi-user"
- CacheConstants:SMS_CODE_KEY、SMS_CODE_LIMIT_KEY、SMS_CODE_ERR_KEY、ACCOUNT_REGISTER_LOCK_KEY、WECHAT_ACCESS_TOKEN_KEY
- SmsConstants(ruoyi-auth):CODE_LENGTH=6、CODE_EXPIRE_MINUTES=5、SEND_INTERVAL_SECONDS=30、MAX_VERIFY_ATTEMPTS=5
- ReferralConstants(ruoyi-user):CODE_LENGTH=8、GEN_MAX_RETRY=5


七、影响范围与文件清单

新增

模块 文件 说明
ruoyi-common-core (改)ServiceNameConstants、CacheConstants 新增常量
ruoyi-api-system domain/AccountMember.java C 端会员跨服务契约 DTO
ruoyi-api-system model/AccountRegisterRequest.java 注册入参 DTO(phone、referrerReferralCode)
ruoyi-api-system RemoteAccountUserService.java 指向 ruoyi-user 的 Feign
ruoyi-api-system factory/RemoteAccountUserFallbackFactory.java 降级工厂
ruoyi-user controller/AccountMemberInnerController.java 内部接口(@InnerAuth):按手机号查会员、注册会员
ruoyi-user service/impl/(改)AccountUserServiceImpl 新增会员查询/注册业务
ruoyi-auth form/SmsCodeBody.java、form/PhoneLoginBody.java、form/PhoneCodeLoginBody.java、form/AccountRegisterBody.java 入参对象(含 @NotBlank/@Pattern)
ruoyi-auth controller/AccountAuthController.java /sms/code、/phone/login、/phone/code-login、/account/register
ruoyi-auth service/AccountLoginService.java 登录/注册编排
ruoyi-auth service/SmsCodeService.java + service/sms/{SmsSender, PlaceholderSmsSender} 验证码 + 短信抽象
ruoyi-auth service/WeChatMiniappService.java + config/WeChatMiniappProperties.java 微信手机号
ruoyi-auth enums/SmsSceneEnum.java、constant/SmsConstants.java 枚举/常量

修改

文件 改动
ruoyi-user domain/AccountUser.java 新增 myReferralCode 字段与注释、toString;推荐关系复用既有 referrerId(源 referrer_id)
ruoyi-user mapper/AccountUserMapper.java + resources/mapper/user/AccountUserMapper.xml resultMap/列/insert/update 增列;新增 selectAccountUserByReferralCode
ruoyi-user service/impl/AccountUserServiceImpl.insertAccountUser 密码为空时跳过加密(当前无条件 encryptPassword,C 端无密码会 NPE)
sql/hugray_account_20261012.sql 新增 DDL 脚本
nacos ruoyi-gateway-dev.yml 新增 /miniapp/** 路由 + /account/** 路由;白名单增 4 条
nacos ruoyi-auth-dev.yml 新增 wechat.miniapp.*
前端 hugray-user-miniapp/api/index.js 全部业务路径加 /miniapp 前缀;register 改 /miniapp/auth/account/register
前端 hugray-partner-miniapp/api/index.js 同上(共用一套前缀)

八、配置变更(nacos)

8.1 ruoyi-gateway-dev.yml

路由新增(spring.cloud.gateway.server.webflux.routes):

            # 小程序 - 认证(用户端 + 服务商端共用)
            - id: ruoyi-miniapp-auth
              uri: lb://ruoyi-auth
              predicates:
                - Path=/miniapp/auth/**
              filters:
                - StripPrefix=2
            # 小程序 - 会员
            - id: ruoyi-miniapp-user
              uri: lb://ruoyi-user
              predicates:
                - Path=/miniapp/account/**
              filters:
                - StripPrefix=2
            # 小程序 - 商品/订单
            - id: ruoyi-miniapp-product
              uri: lb://ruoyi-product
              predicates:
                - Path=/miniapp/product/**
              filters:
                - StripPrefix=2
            # 小程序 - 系统信息
            - id: ruoyi-miniapp-system
              uri: lb://ruoyi-system
              predicates:
                - Path=/miniapp/system/**
              filters:
                - StripPrefix=2
            # 会员服务(补齐,兼容前端 /account 前缀注释)
            - id: ruoyi-user
              uri: lb://ruoyi-user
              predicates:
                - Path=/account/**
              filters:
                - StripPrefix=1

白名单新增:

security:
  ignore:
    whites:
      - /auth/logout
      - /auth/login
      - /auth/register
      - /miniapp/auth/sms/code          # 新增
      - /miniapp/auth/phone/login       # 新增
      - /miniapp/auth/phone/code-login  # 新增
      - /miniapp/auth/account/register  # 新增
      - /*/v2/api-docs
      - /*/v3/api-docs
      - /csrf

/miniapp/auth/** 路由**不挂 ValidateCodeFilter**,因此 phone/login、account/register 不要求图形验证码 uuid。

8.2 ruoyi-auth-dev.yml

# 微信小程序(appid 已知 wx895deca4258a4ecf,secret 需填入真实值)
wechat:
  miniapp:
    appid: wx895deca4258a4ecf
    secret: <待填入,脱敏归档>
  # 服务商端(hugray-partner-miniapp,appid wx9fff7ff7a44e155a):预留配置,代码暂不消费
  partner-miniapp:
    appid: wx9fff7ff7a44e155a
    secret: <待填入,脱敏归档>

归档:按规范同步归档 doc/nacos/dev/(脱敏),真实值仅存于 nacos。
说明:服务商端目前不走微信能力(无手机号一键登录 / wx.login),wechat.partner-miniapp.* 仅作为**预留配置**写入,待后续微信能力接入时启用(暂无对应 @ConfigurationProperties 与消费方)。
说明:当前仓库 seed 脚本 sql/ry_config_20260918.sql 的 nacos 租户为 public,而服务 bootstrap.yml 使用命名空间 c75c831e-0570-4a9f-b8de-aba9087a5f37;实际生效配置请在对应的 nacos 命名空间中修改。

8.3 ruoyi-user-dev.yml

# 测试便利:通用推荐码(空 = 关闭;命中则不绑定推荐人)
hugray:
  user:
    universal-referral-code: HUGRAY88

归档:doc/nacos/dev/ruoyi-user-dev.yml(脱敏);与 8.2 的 hugray.auth.universal-sms-code 配套,见 5.7 节。


九、边界与异常清单

# 场景 处理
1 手机号为空 / 格式错误 参数校验 + 服务兜底,提示「请输入手机号」/「请输入正确手机号」
2 scene=register 且手机号已注册 「手机号已注册,请前往登录」
3 30s 内重复请求验证码 「验证码发送过于频繁,请稍后再试」
4 验证码错误 / 过期 / 超次数 分别提示,超次数后删除验证码
5 验证码一次性消费 校验成功后立即删除
6 推荐码为空 / 不存在 / 推荐人停用 「请输入推荐人码」/「推荐人码无效」/「推荐人账号异常」
7 推荐码为本人推荐码 注册前用户不存在,天然不可自荐
8 并发 / 重复提交同手机号注册 Redis 分布式锁 + 锁内二次唯一性校验
9 逻辑删除用户以同手机号重新注册 del_flag='0' 判定为未注册,允许注册
10 一键登录:手机号未注册 「该手机号未注册,请先注册」
11 一键登录:账号停用 / 删除 「账号已停用,请联系管理员」/「账号不存在」
12 微信接口报错 / 网络异常 「微信手机号获取失败,请重试」
13 微信 access_token 过期 Redis 缓存 + 失效自动重新获取
14 无密码注册(C 端无密码) insertAccountUser 密码为空跳过加密
15 推荐码生成冲突 查重重试 ≤5 次,仍失败抛「注册失败,请稍后重试」
16 参数校验失败 @Validated → 全局 MethodArgumentNotValidException 处理,返回首条 message
17 内部接口越权访问 @InnerAuth 校验 from-source=inner
18 敏感信息 日志不打印完整手机号(脱敏)、不打印微信 secret
19 前缀迁移影响后台 /auth/**、/system/**、/account/** 旧路由全部保留,后台 hugray-admin 不受影响
20 验证码登录:手机号未注册 下发(scene=login)即拦「该手机号未注册,请先注册」;登录接口再兜底,均**不消费验证码**
21 验证码登录:账号停用 / 删除 「账号已停用,请联系管理员」/「账号不存在」,校验在消费验证码之前
22 通用验证码命中(login / register 且 hugray.auth.universal-sms-code 已配置) 直接放行:不读缓存、不计数、不消费;reset / common 场景不生效;配置为空即关闭
23 通用推荐码命中(hugray.user.universal-referral-code 已配置且有值) 跳过推荐人查询与校验,referrerId 留空(不绑定推荐人),且注册用户等级直接为 L4;命中通用码时不再校验「推荐人码无效 / 推荐人账号异常」

十、规范符合性说明

  • 分层:Controller 只做校验/转发,业务在 Service,SQL 在 Mapper;禁止 Controller 调 Mapper。
  • 命名:queryAccountMemberByPhone / registerAccountMember / sendSmsCode 等见名知意;实体 / DTO / 入参对象分离。
  • 注释:类注释、字段注释(含取值说明)、Javadoc、核心流程 // STEP x: 注释。
  • 魔法值收口枚举/常量(场景、状态、验证码与推荐码参数、Redis Key 前缀)。
  • 异常:统一 ServiceException + 全局处理器,不散落 try-catch 拼返回值。
  • 日志:@Slf4j 占位符 + [业务标记] 前缀,手机号脱敏,禁止 System.out.println。
  • 幂等:注册 Redis setnx 锁;创建类接口具备幂等守卫。
  • 数据库:显式列长度 + COMMENT;推荐码唯一索引;无 ${} 拼接;无 SELECT *。
  • 单测:SmsCodeService、AccountLoginService、推荐码生成等核心逻辑补充单测(Mockito Mock Feign/Redis/HTTP)。
  • 文档:本设计文档与代码同分支、同节奏提交。

十一、确认结果

  1. 前缀:/miniapp/**,**用户端与服务商端小程序共用**。✔
  2. C 端注册路径:POST /miniapp/auth/account/register。✔
  3. 微信 secret:部署方填入 nacos wechat.miniapp.secret,仓库不落明文。✔
  4. 占位短信:PlaceholderSmsSender 仅打印日志(形如 [SMS] phone=138****0000 code=123456),不真实下发。✔
  5. 网关补齐 /account/** → lb://ruoyi-user。✔
  6. 登录页双按钮(微信一键登录 / 手机验证登录),「手机验证登录」为页内嵌面板;后端 POST /miniapp/auth/phone/code-login 与 SmsSceneEnum.LOGIN 同步落地。✔
  7. 登录页按 .pen 设计稿 + static/ 素材做视觉 1:1 还原(背景装饰、Logo、背书图标、标语卡、按钮尺寸与渐变、协议行、Home 条),规格见 3.5 节。✔
  8. 登录页 UI 缺陷修复:微信一键登录恢复通栏;协议行字号 23rpx → 26rpx;展开验证码面板后隐藏「手机验证登录」按钮并在面板内提供「收起」入口。✔
  9. 测试便利:通用验证码 999999(hugray.auth.universal-sms-code,仅 login / register 生效)+ 通用推荐码 HUGRAY88(hugray.user.universal-referral-code,命中不绑定推荐人且注册用户直接发放 L4 等级);均通过 nacos 配置开启,配置为空即关闭,命中打印 WARN 留痕。✔
  10. 注册流程:注册页**无需阅读/勾选协议**(提交仅校验手机号 / 验证码 / 推荐码);注册成功页与登录/注册页视觉统一(渐变底 + 装饰层 + 居中结果区 + 渐变胶囊「返回登录」按钮 + Home 条),规格见 3.7 节。✔

编码顺序:常量 → DDL → Feign/内部接口 → auth 服务与控制器 → 网关配置 → 前端前缀对齐 → GetDiagnostics 校验 → 单测。