All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m9s
docs(scripts): 添加测试数据清理脚本的详细使用文档 fix(auth): 修复设备登录时处理孤立认证方法的问题 refactor(public): 改进邮箱绑定逻辑中的推荐码处理
339 lines
8.2 KiB
Go
339 lines
8.2 KiB
Go
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
|
||
} |