bindbox-game/.claude/plan/admin-update-user-mobile.md
2026-02-08 17:19:27 +08:00

269 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📋 实施计划:后台管理端 - 添加修改用户手机号功能
## 任务类型
- [x] 后端 (→ Codex)
- [ ] 前端
- [ ] 全栈
---
## 📊 需求描述
为后台管理端添加修改用户手机号的功能,允许管理员直接修改用户的手机号。
---
## 🎯 技术方案
### API 设计
```http
PUT /api/admin/users/{user_id}/mobile
Content-Type: application/json
Authorization: Bearer {admin_token}
Request Body:
{
"mobile": "13800138000"
}
Response:
{
"success": true,
"message": "手机号更新成功"
}
```
### 安全校验
1. ✅ 管理员权限验证RBAC: `user:edit`
2. ✅ 手机号格式验证11位数字1开头
3. ✅ 手机号唯一性检查(不能与其他用户重复)
4. ✅ 用户存在性验证
---
## 🔧 实施步骤
### 步骤 1添加 API 处理函数
**文件**`internal/api/admin/users_admin.go`
**位置**Line 1893 后(在 `UpdateUserRemark()` 函数之后)
**代码**
```go
// updateUserMobileRequest 更新用户手机号请求
type updateUserMobileRequest struct {
Mobile string `json:"mobile" binding:"required"`
}
// UpdateUserMobile 更新用户手机号
// @Summary 更新用户手机号
// @Description 管理员修改用户手机号
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param body body updateUserMobileRequest true "手机号信息"
// @Success 200 {object} map[string]any
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/mobile [put]
// @Security LoginVerifyToken
func (h *handler) UpdateUserMobile() core.HandlerFunc {
return func(ctx core.Context) {
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "用户ID无效"))
return
}
req := new(updateUserMobileRequest)
if err := ctx.ShouldBindJSON(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
// 验证手机号格式11位数字1开头
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, req.Mobile)
if !matched {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "手机号格式不正确"))
return
}
// 检查手机号是否被其他用户占用
existedUser, _ := h.readDB.Users.WithContext(ctx.RequestContext()).
Where(h.readDB.Users.Mobile.Eq(req.Mobile)).
Where(h.readDB.Users.ID.Neq(userID)).
First()
if existedUser != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "该手机号已被其他用户使用"))
return
}
// 更新用户手机号
_, err = h.writeDB.Users.WithContext(ctx.RequestContext()).
Where(h.writeDB.Users.ID.Eq(userID)).
Update(h.writeDB.Users.Mobile, req.Mobile)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 20303, "更新失败: "+err.Error()))
return
}
ctx.Payload(map[string]any{
"success": true,
"message": "手机号更新成功",
})
}
}
```
---
### 步骤 2添加导入依赖
**文件**`internal/api/admin/users_admin.go`
**位置**:文件顶部 import 区域
**修改**:在现有 import 中添加 `regexp`
```go
import (
"fmt"
"math"
"net/http"
"regexp" // ← 添加这一行
"strconv"
"strings"
"time"
"bindbox-game/internal/code"
// ... 其他导入保持不变
)
```
---
### 步骤 3注册路由
**文件**`internal/router/router.go`
**位置**:在 adminAuthApiRouter 用户管理路由组中
**查找位置参考**
```go
// 找到类似这样的路由组(用户管理相关)
adminAuthApiRouter.PUT("/users/:user_id/douyin_user_id", ...)
adminAuthApiRouter.PUT("/users/:user_id/remark", ...)
adminAuthApiRouter.PUT("/users/:user_id/status", ...)
```
**添加路由**
```go
// 在上述路由附近添加
adminAuthApiRouter.PUT("/users/:user_id/mobile",
intc.RequireAdminAction("user:edit"),
adminHandler.UpdateUserMobile())
```
---
### 步骤 4更新 Swagger 文档
**文件**:运行命令生成文档
```bash
make gen-swagger
```
---
## 📋 关键文件清单
| 文件路径 | 操作 | 说明 |
|---------|------|------|
| `internal/api/admin/users_admin.go` | 新增函数 | 添加 `UpdateUserMobile()` 及请求结构体 |
| `internal/api/admin/users_admin.go` | 修改导入 | 添加 `regexp` 包 |
| `internal/router/router.go` | 新增路由 | 注册 `PUT /users/:user_id/mobile` |
---
## ✅ 验证清单
完成后请验证:
- [ ] API 接口可正常访问
- [ ] 手机号格式验证生效(非法格式返回错误)
- [ ] 手机号唯一性检查生效(重复手机号返回错误)
- [ ] 更新成功后数据库中手机号已变更
- [ ] Swagger 文档已更新
- [ ] 权限验证生效(非管理员或无 `user:edit` 权限无法访问)
---
## ⚠️ 风险与缓解
| 风险 | 缓解措施 |
|------|----------|
| 手机号格式错误 | 正则验证 `^1[3-9]\d{9}$` |
| 手机号被占用 | 查询检查 `WHERE mobile = ? AND id != ?` |
| 权限滥用 | RBAC 中间件 `RequireAdminAction("user:edit")` |
| 更新失败 | 捕获异常并返回错误码 20303 |
---
## 📝 测试用例
### 正常流程
```bash
curl -X PUT http://localhost:9991/api/admin/users/123/mobile \
-H "Authorization: Bearer {admin_token}" \
-H "Content-Type: application/json" \
-d '{"mobile":"13800138000"}'
# 预期响应
{
"success": true,
"message": "手机号更新成功"
}
```
### 异常流程
**1. 手机号格式错误**
```json
{"mobile": "123"}
{"code": 10001, "message": "手机号格式不正确"}
```
**2. 手机号已被占用**
```json
{"mobile": "13800138000"} // 已被用户456使用
{"code": 10001, "message": "该手机号已被其他用户使用"}
```
**3. 用户ID不存在**
```bash
PUT /api/admin/users/999999/mobile
→ 更新成功但影响行数为0GORM特性不会报错
```
---
## 🚀 后续优化建议(可选)
1. **操作日志记录**:记录管理员修改手机号的操作到审计日志
2. **短信验证**:要求新手机号验证码确认(防止恶意修改)
3. **旧手机号通知**:向旧手机号发送变更通知短信
4. **限制修改频率**同一用户手机号修改间隔限制如7天
---
## 📌 备注
- 本方案仅实现管理端修改功能,不包含用户端自助修改
- 遵循现有代码风格(参考 `UpdateUserRemark()``UpdateUserDouyinID()`
- 使用 GORM Gen 生成的 DAO 进行数据库操作
- 错误码 20303 用于手机号更新失败(延续现有错误码序列 20301, 20302