docs(change-password): 合并 7 个 second-pass issues
- 3.1 序列图:补 GetByID 调用,与 §4.3 一致 - 4.2: 加 call-site 审计 grep 命令 + 已确认 9 文件列表 - 4.6: 补 mocking 模式代码示例 + Test #9 错误类型规约 - 5.3.3: 解释手动 removeStorageSync 的原因(store 不清 temp_register_*) - 5.3.4: 补 updatePasswordApi 调用点审计(2 文件已确认) - 9: 补 AuthMiddleware 已挂载,前端凭 token 即可访问
This commit is contained in:
parent
c875151daa
commit
0a3d8e0afc
@ -87,6 +87,7 @@
|
|||||||
│ │ {old, new, verify_token} │ │ │
|
│ │ {old, new, verify_token} │ │ │
|
||||||
│ ├─────────────────────────→│ ─Dubbo→ │ │
|
│ ├─────────────────────────→│ ─Dubbo→ │ │
|
||||||
│ │ │ UpdatePassword │ │
|
│ │ │ UpdatePassword │ │
|
||||||
|
│ │ │ - GetByID(uid) │ │
|
||||||
│ │ │ - VerifyToken() │ Get+DeleteVerifyTok│
|
│ │ │ - VerifyToken() │ Get+DeleteVerifyTok│
|
||||||
│ │ │ ├───────────────────→│
|
│ │ │ ├───────────────────→│
|
||||||
│ │ │ - bcrypt 比对 │ │
|
│ │ │ - bcrypt 比对 │ │
|
||||||
@ -166,6 +167,15 @@ func verifyTokenKey(scene, mobile string) string {
|
|||||||
- Register 仍用 `scene="register"`,改密用 `scene="password"`,两个 scene 的 Redis key 互不污染
|
- Register 仍用 `scene="register"`,改密用 `scene="password"`,两个 scene 的 Redis key 互不污染
|
||||||
- 老数据兼容:老的 `sms:register:*` / `verify:register:*` 在过渡期可保留(只要还有未过期的 token),过渡期后自然过期(60s/300s TTL)
|
- 老数据兼容:老的 `sms:register:*` / `verify:register:*` 在过渡期可保留(只要还有未过期的 token),过渡期后自然过期(60s/300s TTL)
|
||||||
|
|
||||||
|
**Call-site 审计(实施前必跑)**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "SaveVerifyToken\|GetVerifyToken\|DeleteVerifyToken\|VerifyToken(\|SaveSMSCode\|GetSMSCode\|VerifyCode(\|SendCode(" \
|
||||||
|
--include="*.go" backend/
|
||||||
|
```
|
||||||
|
|
||||||
|
涉及文件(已通过 grep 确认):9 个 —— `auth_service.go / sms_service.go / user.proto / user.pb.go / auth_provider.go / unified_provider.go / sms_redis.go / auth_controller.go / user.triple.go`。其中 `*.proto` / `*.pb.go` / `*.triple.go` 是 regen 自动产物,人工改动集中在 `auth_service.go / sms_service.go / auth_provider.go / auth_controller.go / unified_provider.go`。
|
||||||
|
|
||||||
### 4.3 Service 层 UpdatePassword
|
### 4.3 Service 层 UpdatePassword
|
||||||
|
|
||||||
**修改** [backend/services/userService/service/user_service.go](backend/services/userService/service/user_service.go#L484-L587)
|
**修改** [backend/services/userService/service/user_service.go](backend/services/userService/service/user_service.go#L484-L587)
|
||||||
@ -262,7 +272,37 @@ var (
|
|||||||
| 9 | Redis 故障 | mock `VerifyToken` error | wrap 后抛出 |
|
| 9 | Redis 故障 | mock `VerifyToken` error | wrap 后抛出 |
|
||||||
| 10 | 事务回滚 | mock `tx.Updates` 失败 | 整事务回滚 |
|
| 10 | 事务回滚 | mock `tx.Updates` 失败 | 整事务回滚 |
|
||||||
|
|
||||||
Mock 方案:`testify/mock` 包装 `UserRepository`;`VerifyToken` 是包级函数,用 package-level option 注入避免改签名。
|
Mock 方案:
|
||||||
|
- `UserRepository`:用 `testify/mock` 包装,实现 `UserRepository` 接口,注入到 `userService`
|
||||||
|
- `VerifyToken`(包级函数):用 package-level `var` 注入(标准 Go test 模式)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// sms_service.go(运行时)
|
||||||
|
var VerifyTokenFn func(ctx context.Context, scene, mobile, token string) error = realVerifyToken
|
||||||
|
|
||||||
|
func VerifyToken(ctx context.Context, scene, mobile, token string) error {
|
||||||
|
return VerifyTokenFn(ctx, scene, mobile, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func realVerifyToken(ctx context.Context, scene, mobile, token string) error {
|
||||||
|
// 实际 Get+DeleteVerifyToken 逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// user_service_password_test.go(测试时)
|
||||||
|
func TestUpdatePassword_VerifyTokenExpired(t *testing.T) {
|
||||||
|
orig := VerifyTokenFn
|
||||||
|
defer func() { VerifyTokenFn = orig }()
|
||||||
|
VerifyTokenFn = func(ctx context.Context, scene, mobile, token string) error {
|
||||||
|
return errors.New("verify token expired")
|
||||||
|
}
|
||||||
|
// ... 断言
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test #9 错误类型规约**:当 `VerifyToken` 自身出错(Redis 故障、key 拼错等)时:
|
||||||
|
- `VerifyToken` 返回的原始 error → wrap 为 `fmt.Errorf("failed to verify token: %w", err)` → 在 Provider 层映射为 500
|
||||||
|
- 业务上的"token 不存在 / 已过期" → 仍返回 `ErrInvalidVerifyToken` → 401
|
||||||
|
- §7 错误码表中"其他 5xx"行即对应此场景,前端 toast 通用提示
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -487,6 +527,9 @@ const confirmChangePassword = async () => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
uni.showToast({ title: '修改成功,请重新登录', icon: 'success' })
|
uni.showToast({ title: '修改成功,请重新登录', icon: 'success' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// 改密后清空所有登录态 + 注册临时数据
|
||||||
|
// 备注:store 的 'user/logout' 实际只 commit CLEAR_AUTH(清 access_token/user/star_id/login_mobile/gallery_owner_id),
|
||||||
|
// 不会清 temp_register_*,所以这里需要手动清;后续若 store 升级,本处可同步收敛
|
||||||
store.dispatch('user/logout')
|
store.dispatch('user/logout')
|
||||||
uni.removeStorageSync('access_token')
|
uni.removeStorageSync('access_token')
|
||||||
uni.removeStorageSync('user')
|
uni.removeStorageSync('user')
|
||||||
@ -520,7 +563,15 @@ import { validatePassword } from '@/utils/validator.js' // 已有
|
|||||||
**API 函数兼容性确认**:
|
**API 函数兼容性确认**:
|
||||||
- `sendCodeApi(mobile, scene)`:见 [api.js:187-196](frontend/utils/api.js#L187-L196),已支持任意 `scene` 字符串(后端在 SendCode 中按 scene 路由),无需改动
|
- `sendCodeApi(mobile, scene)`:见 [api.js:187-196](frontend/utils/api.js#L187-L196),已支持任意 `scene` 字符串(后端在 SendCode 中按 scene 路由),无需改动
|
||||||
- `verifyCodeApi(mobile, code, scene)`:见 [api.js:199-209](frontend/utils/api.js#L199-L209),已支持任意 `scene`,无需改动
|
- `verifyCodeApi(mobile, code, scene)`:见 [api.js:199-209](frontend/utils/api.js#L199-L209),已支持任意 `scene`,无需改动
|
||||||
- `updatePasswordApi(oldPassword, newPassword, verifyToken)`:**新签名**,§5.3.5 中给出实现;如有其他调用点需同步更新
|
- `updatePasswordApi(oldPassword, newPassword, verifyToken)`:**新签名**,§5.3.5 中给出实现
|
||||||
|
|
||||||
|
**`updatePasswordApi` 调用点审计**(实施前必跑):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "updatePasswordApi" --include="*.vue" --include="*.js" frontend/
|
||||||
|
```
|
||||||
|
|
||||||
|
经 grep 确认:仅 2 个调用点 —— `frontend/utils/api.js`(定义处)+ `frontend/pages/profile/profile.vue`(使用处)。本 PR 内一并更新即可,无连锁影响。
|
||||||
|
|
||||||
#### 5.3.5 API 函数扩展
|
#### 5.3.5 API 函数扩展
|
||||||
|
|
||||||
@ -629,13 +680,14 @@ export function updatePasswordApi(oldPassword, newPassword, verifyToken) {
|
|||||||
|
|
||||||
| 项 | 措施 |
|
| 项 | 措施 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| 鉴权强制 | 路由 `/api/v1/account/password` 已挂 `AuthMiddleware`(见 [router.go:185-189](backend/gateway/router/router.go#L185-L189)),token 无效直接 401,无需在 Service 重复校验 userID 来源 |
|
||||||
| 短信 token 一次性 | 复用 `DeleteVerifyToken` 消费后即删 |
|
| 短信 token 一次性 | 复用 `DeleteVerifyToken` 消费后即删 |
|
||||||
| 短信 token TTL | 5 分钟(与 Register 一致) |
|
| 短信 token TTL | 5 分钟(与 Register 一致) |
|
||||||
| 限流 | 复用 `sms:limit:mobile:` 计数器,scene=password |
|
| 限流 | 复用 `sms:limit:mobile:` 计数器,scene=password |
|
||||||
| 旧密码明文只传不存 | bcrypt(成本因子 10) |
|
| 旧密码明文只传不存 | bcrypt(成本因子 10) |
|
||||||
| 改密后清 token | 事务内 `access_token = nil` |
|
| 改密后清 token | 事务内 `access_token = nil` |
|
||||||
| updated_at 同步更新 | 与 token 校验机制一致,旧 token 失效 |
|
| updated_at 同步更新 | 与 token 校验机制一致,旧 token 失效 |
|
||||||
| 不暴露 mobile 是否存在 | 短信发送成功/失败统一 200 |
|
| 不暴露 mobile 是否存在 | 短信发送成功/失败统一 200(继承 Register 的 anti-enumeration 行为) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user