docs(scripts): 添加测试数据清理脚本的详细使用文档 fix(auth): 修复设备登录时处理孤立认证方法的问题 refactor(public): 改进邮箱绑定逻辑中的推荐码处理
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# 测试数据清理脚本
|
||||
|
||||
## 问题背景
|
||||
|
||||
在运行测试案例 `test/device.go` 时,可能会遇到以下错误:
|
||||
|
||||
```
|
||||
[ERROR] 2025/01/28 11:40:59 [DeviceLoginLogic] FindOne Error: record not found
|
||||
```
|
||||
|
||||
这个错误通常是由于测试数据清理不完整导致的,具体表现为:
|
||||
- 设备记录存在,但对应的用户记录已被删除
|
||||
- 数据库中存在孤立的记录
|
||||
- 测试用户数据没有完全清理
|
||||
|
||||
## 解决方案
|
||||
|
||||
使用 `cleanup_test_data.go` 脚本来清理测试数据,确保数据一致性。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 配置脚本
|
||||
|
||||
编辑 `cleanup_test_data.go` 文件,修改以下配置:
|
||||
|
||||
```go
|
||||
const (
|
||||
// 服务器地址
|
||||
baseURL = "http://localhost:8080"
|
||||
|
||||
// 管理员认证信息
|
||||
adminEmail = "admin@example.com"
|
||||
adminPassword = "admin123"
|
||||
|
||||
// 测试用户邮箱前缀
|
||||
testEmailPrefix = "test_"
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 运行清理脚本
|
||||
|
||||
```bash
|
||||
# 进入scripts目录
|
||||
cd scripts
|
||||
|
||||
# 查看帮助信息
|
||||
go run cleanup_test_data.go --help
|
||||
|
||||
# 运行清理脚本
|
||||
go run cleanup_test_data.go
|
||||
```
|
||||
|
||||
### 3. 脚本功能
|
||||
|
||||
脚本会自动执行以下操作:
|
||||
|
||||
1. **管理员登录**: 使用配置的管理员账户登录系统
|
||||
2. **识别测试用户**: 根据邮箱规则识别测试用户:
|
||||
- 邮箱以 `test_` 开头
|
||||
- 邮箱包含 `test` 关键字
|
||||
- 邮箱包含 `example.com` 域名
|
||||
3. **批量删除**: 使用管理员API批量删除测试用户
|
||||
4. **数据一致性检查**: 检查剩余用户数据的完整性
|
||||
|
||||
## API接口说明
|
||||
|
||||
脚本使用以下管理员API接口:
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/v1/auth/login` | POST | 管理员登录 |
|
||||
| `/v1/admin/user` | GET | 获取用户列表 |
|
||||
| `/v1/admin/user/batch` | DELETE | 批量删除用户 |
|
||||
| `/v1/admin/user/{id}` | GET | 获取用户详情 |
|
||||
|
||||
## 清理规则
|
||||
|
||||
### 用户删除规则
|
||||
|
||||
脚本会删除符合以下条件的用户:
|
||||
|
||||
```go
|
||||
isTestUser := strings.HasPrefix(user.Email, testEmailPrefix) ||
|
||||
strings.Contains(user.Email, "test") ||
|
||||
strings.Contains(user.Email, "example.com")
|
||||
```
|
||||
|
||||
### 数据清理范围
|
||||
|
||||
根据 `internal/model/user/default.go` 中的 `Delete` 方法,删除用户时会自动清理:
|
||||
|
||||
- 用户基本信息 (`User` 表)
|
||||
- 用户认证方式 (`AuthMethods` 表)
|
||||
- 用户订阅信息 (`Subscribe` 表)
|
||||
- 用户设备信息 (`Device` 表)
|
||||
- 相关缓存数据
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
⚠️ **重要提醒**:
|
||||
|
||||
1. **仅在测试环境使用**: 此脚本会删除用户数据,请勿在生产环境运行
|
||||
2. **备份数据**: 运行前建议备份数据库
|
||||
3. **确认配置**: 确保管理员账户信息正确
|
||||
4. **检查规则**: 确认测试用户识别规则符合预期
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见错误
|
||||
|
||||
1. **登录失败**
|
||||
```
|
||||
❌ 管理员登录失败: 登录失败 (Code: 401): Invalid credentials
|
||||
```
|
||||
- 检查管理员邮箱和密码是否正确
|
||||
- 确认服务器是否正在运行
|
||||
|
||||
2. **连接失败**
|
||||
```
|
||||
❌ 管理员登录失败: 登录请求失败: dial tcp [::1]:8080: connect: connection refused
|
||||
```
|
||||
- 检查服务器地址是否正确
|
||||
- 确认服务器是否启动
|
||||
|
||||
3. **权限不足**
|
||||
```
|
||||
❌ 获取用户列表失败: 获取用户列表失败 (Code: 403): Forbidden
|
||||
```
|
||||
- 确认使用的是管理员账户
|
||||
- 检查管理员权限配置
|
||||
|
||||
### 验证清理效果
|
||||
|
||||
清理完成后,可以重新运行测试脚本验证:
|
||||
|
||||
```bash
|
||||
cd test
|
||||
go run device.go
|
||||
```
|
||||
|
||||
如果清理成功,应该不再出现 "record not found" 错误。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `test/device.go` - 测试脚本
|
||||
- `internal/logic/admin/user/deleteUserLogic.go` - 单个用户删除逻辑
|
||||
- `internal/logic/admin/user/batchDeleteUserLogic.go` - 批量用户删除逻辑
|
||||
- `internal/model/user/default.go` - 用户模型删除方法
|
||||
- `apis/admin/user.api` - 管理员用户API定义
|
||||
@@ -0,0 +1,339 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 服务器地址 - 根据实际情况修改
|
||||
baseURL = "http://localhost:8080"
|
||||
|
||||
// 管理员认证信息 - 需要根据实际情况修改
|
||||
adminEmail = "admin@example.com"
|
||||
adminPassword = "admin123"
|
||||
|
||||
// 测试用户邮箱前缀,用于识别测试数据
|
||||
testEmailPrefix = "test_"
|
||||
)
|
||||
|
||||
// 管理员登录响应
|
||||
type AdminLoginResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// 用户列表响应
|
||||
type UserListResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
} `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
} `json:"data"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// 通用响应结构
|
||||
type CommonResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// HTTP客户端
|
||||
var client = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
// 管理员token
|
||||
var adminToken string
|
||||
|
||||
func main() {
|
||||
// 检查是否显示帮助信息
|
||||
if len(os.Args) > 1 && os.Args[1] == "--help" {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("=== 测试数据清理脚本 ===")
|
||||
fmt.Printf("服务器地址: %s\n", baseURL)
|
||||
fmt.Printf("管理员邮箱: %s\n", adminEmail)
|
||||
fmt.Println()
|
||||
|
||||
// 1. 管理员登录获取token
|
||||
fmt.Println("步骤 1: 管理员登录...")
|
||||
if err := adminLogin(); err != nil {
|
||||
fmt.Printf("❌ 管理员登录失败: %v\n", err)
|
||||
fmt.Println("\n请检查:")
|
||||
fmt.Println("- 服务器是否正在运行")
|
||||
fmt.Println("- 管理员邮箱和密码是否正确")
|
||||
fmt.Println("- 服务器地址是否正确")
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ 管理员登录成功")
|
||||
|
||||
// 2. 清理测试用户数据
|
||||
fmt.Println("\n步骤 2: 清理测试用户...")
|
||||
if err := cleanupTestUsers(); err != nil {
|
||||
fmt.Printf("❌ 清理测试用户失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 3. 清理孤立的设备数据(如果有相应的API)
|
||||
fmt.Println("\n步骤 3: 检查数据一致性...")
|
||||
checkDataConsistency()
|
||||
|
||||
fmt.Println("\n=== 清理完成 ===")
|
||||
}
|
||||
|
||||
// 显示帮助信息
|
||||
func showHelp() {
|
||||
fmt.Println(`测试数据清理脚本使用说明:
|
||||
|
||||
📋 功能说明:
|
||||
本脚本用于清理测试过程中产生的用户数据,解决数据不一致问题
|
||||
|
||||
🔧 配置修改:
|
||||
请在脚本中修改以下配置:
|
||||
- baseURL: 服务器地址 (当前: http://localhost:8080)
|
||||
- adminEmail: 管理员邮箱
|
||||
- adminPassword: 管理员密码
|
||||
- testEmailPrefix: 测试用户邮箱前缀 (当前: test_)
|
||||
|
||||
🚀 运行方式:
|
||||
go run cleanup_test_data.go
|
||||
|
||||
📊 清理规则:
|
||||
- 删除邮箱包含 "test" 的用户
|
||||
- 删除邮箱包含 "example.com" 的用户
|
||||
- 删除邮箱以 "test_" 开头的用户
|
||||
- 检查数据一致性问题
|
||||
|
||||
🔗 使用的API接口:
|
||||
- POST /v1/auth/login - 管理员登录
|
||||
- GET /v1/admin/user - 获取用户列表
|
||||
- DELETE /v1/admin/user/batch - 批量删除用户
|
||||
- GET /v1/admin/user/{id} - 检查用户详情
|
||||
|
||||
⚠️ 安全提示:
|
||||
- 仅在测试环境中使用
|
||||
- 生产环境请谨慎操作
|
||||
- 建议先备份数据库
|
||||
- 确认管理员账户信息正确
|
||||
|
||||
🐛 问题排查:
|
||||
如果遇到 "record not found" 错误:
|
||||
1. 运行此脚本清理测试数据
|
||||
2. 检查数据库外键约束
|
||||
3. 确保测试流程的数据清理完整性`)
|
||||
}
|
||||
|
||||
// 管理员登录
|
||||
func adminLogin() error {
|
||||
loginData := map[string]string{
|
||||
"email": adminEmail,
|
||||
"password": adminPassword,
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(loginData)
|
||||
|
||||
resp, err := client.Post(baseURL+"/v1/auth/login", "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("登录请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
var loginResp AdminLoginResponse
|
||||
if err := json.Unmarshal(body, &loginResp); err != nil {
|
||||
return fmt.Errorf("解析登录响应失败: %v", err)
|
||||
}
|
||||
|
||||
if loginResp.Code != 200 {
|
||||
return fmt.Errorf("登录失败 (Code: %d): %s", loginResp.Code, loginResp.Message)
|
||||
}
|
||||
|
||||
adminToken = loginResp.Data.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清理测试用户
|
||||
func cleanupTestUsers() error {
|
||||
// 获取用户列表
|
||||
users, err := getUserList()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取用户列表失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("📊 总用户数: %d\n", len(users))
|
||||
|
||||
var testUserIDs []int64
|
||||
var testUsers []string
|
||||
|
||||
for _, user := range users {
|
||||
// 识别测试用户的规则
|
||||
isTestUser := strings.HasPrefix(user.Email, testEmailPrefix) ||
|
||||
strings.Contains(user.Email, "test") ||
|
||||
strings.Contains(user.Email, "example.com")
|
||||
|
||||
if isTestUser {
|
||||
testUserIDs = append(testUserIDs, user.ID)
|
||||
testUsers = append(testUsers, fmt.Sprintf("ID=%d, Email=%s", user.ID, user.Email))
|
||||
}
|
||||
}
|
||||
|
||||
if len(testUserIDs) == 0 {
|
||||
fmt.Println("✅ 未发现测试用户,数据已清理")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("🔍 发现 %d 个测试用户:\n", len(testUserIDs))
|
||||
for _, userInfo := range testUsers {
|
||||
fmt.Printf(" - %s\n", userInfo)
|
||||
}
|
||||
|
||||
// 批量删除测试用户
|
||||
fmt.Printf("\n🗑️ 正在删除 %d 个测试用户...\n", len(testUserIDs))
|
||||
if err := batchDeleteUsers(testUserIDs); err != nil {
|
||||
return fmt.Errorf("批量删除用户失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ 成功删除 %d 个测试用户\n", len(testUserIDs))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查数据一致性
|
||||
func checkDataConsistency() {
|
||||
fmt.Println("🔍 检查数据一致性...")
|
||||
|
||||
// 获取用户列表,检查是否还有问题用户
|
||||
users, err := getUserList()
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 无法获取用户列表: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
problemUsers := 0
|
||||
for _, user := range users {
|
||||
// 检查用户详情是否可以正常获取
|
||||
if !checkUserDetail(user.ID) {
|
||||
problemUsers++
|
||||
fmt.Printf("⚠️ 用户 ID=%d Email=%s 可能存在数据问题\n", user.ID, user.Email)
|
||||
}
|
||||
}
|
||||
|
||||
if problemUsers == 0 {
|
||||
fmt.Println("✅ 数据一致性检查通过")
|
||||
} else {
|
||||
fmt.Printf("⚠️ 发现 %d 个可能有问题的用户记录\n", problemUsers)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
func getUserList() ([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
}, error) {
|
||||
req, err := http.NewRequest("GET", baseURL+"/v1/admin/user?page=1&size=1000", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userResp UserListResponse
|
||||
if err := json.Unmarshal(body, &userResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userResp.Code != 200 {
|
||||
return nil, fmt.Errorf("获取用户列表失败 (Code: %d): %s", userResp.Code, userResp.Message)
|
||||
}
|
||||
|
||||
return userResp.Data.List, nil
|
||||
}
|
||||
|
||||
// 批量删除用户
|
||||
func batchDeleteUsers(userIDs []int64) error {
|
||||
deleteData := map[string][]int64{
|
||||
"ids": userIDs,
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(deleteData)
|
||||
|
||||
req, err := http.NewRequest("DELETE", baseURL+"/v1/admin/user/batch", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("删除用户失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var commonResp CommonResponse
|
||||
if err := json.Unmarshal(body, &commonResp); err == nil && commonResp.Code != 200 {
|
||||
return fmt.Errorf("删除用户失败 (Code: %d): %s", commonResp.Code, commonResp.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查用户详情
|
||||
func checkUserDetail(userID int64) bool {
|
||||
req, err := http.NewRequest("GET", baseURL+"/v1/admin/user/"+strconv.FormatInt(userID, 10), nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
Reference in New Issue
Block a user