This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/pkg/conf"
|
||||
"github.com/perfect-panel/server/pkg/orm"
|
||||
)
|
||||
|
||||
var configFile string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&configFile, "config", "configs/ppanel.yaml", "config file path")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(configFile, &c)
|
||||
|
||||
// Construct DSN
|
||||
m := orm.Mysql{Config: c.MySQL}
|
||||
dsn := m.Dsn()
|
||||
|
||||
log.Println("Connecting to database...")
|
||||
db, err := sql.Open("mysql", dsn+"&multiStatements=true")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
// 1. Check Version
|
||||
var version string
|
||||
if err := db.QueryRow("SELECT version()").Scan(&version); err != nil {
|
||||
log.Fatalf("Failed to select version: %v", err)
|
||||
}
|
||||
log.Printf("MySQL Version: %s", version)
|
||||
|
||||
// 2. Read SQL file directly to ensure we are testing what's on disk
|
||||
sqlBytes, err := os.ReadFile("initialize/migrate/database/02118_traffic_log_idx.up.sql")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read SQL file: %v", err)
|
||||
}
|
||||
sqlStmt := string(sqlBytes)
|
||||
|
||||
// 3. Test SQL
|
||||
log.Printf("Testing SQL from file:\n%s", sqlStmt)
|
||||
if _, err := db.Exec(sqlStmt); err != nil {
|
||||
log.Printf("SQL Execution Failed: %v", err)
|
||||
} else {
|
||||
log.Println("SQL Execution Success")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 配置结构
|
||||
type AppConfig struct {
|
||||
JwtAuth struct {
|
||||
AccessSecret string `yaml:"AccessSecret"`
|
||||
} `yaml:"JwtAuth"`
|
||||
MySQL struct {
|
||||
Addr string `yaml:"Addr"`
|
||||
Dbname string `yaml:"Dbname"`
|
||||
Username string `yaml:"Username"`
|
||||
Password string `yaml:"Password"`
|
||||
Config string `yaml:"Config"`
|
||||
} `yaml:"MySQL"`
|
||||
Redis struct {
|
||||
Host string `yaml:"Host"`
|
||||
Pass string `yaml:"Pass"`
|
||||
DB int `yaml:"DB"`
|
||||
} `yaml:"Redis"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("====== 本地测试用户创建 ======")
|
||||
|
||||
// 1. 读取配置
|
||||
cfgData, err := os.ReadFile("configs/ppanel.yaml")
|
||||
if err != nil {
|
||||
fmt.Printf("读取配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg AppConfig
|
||||
if err := yaml.Unmarshal(cfgData, &cfg); err != nil {
|
||||
fmt.Printf("解析配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 连接 Redis
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Redis.Host,
|
||||
Password: cfg.Redis.Pass,
|
||||
DB: cfg.Redis.DB,
|
||||
})
|
||||
ctx := context.Background()
|
||||
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
fmt.Printf("Redis 连接失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ Redis 连接成功")
|
||||
|
||||
// 3. 连接数据库
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s",
|
||||
cfg.MySQL.Username, cfg.MySQL.Password, cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.MySQL.Config)
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
fmt.Printf("数据库连接失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ 数据库连接成功")
|
||||
|
||||
// 4. 查找一个有 refer_code 的用户
|
||||
var user struct {
|
||||
Id int64 `gorm:"column:id"`
|
||||
ReferCode string `gorm:"column:refer_code"`
|
||||
}
|
||||
result := db.Table("user").
|
||||
Where("refer_code IS NOT NULL AND refer_code != ''").
|
||||
First(&user)
|
||||
|
||||
if result.Error != nil {
|
||||
// 没有找到有 refer_code 的用户,查找第一个用户并添加 refer_code
|
||||
fmt.Println("没有找到有 refer_code 的用户,正在更新第一个用户...")
|
||||
result = db.Table("user").First(&user)
|
||||
if result.Error != nil {
|
||||
fmt.Printf("没有找到用户: %v\n", result.Error)
|
||||
return
|
||||
}
|
||||
// 更新 refer_code
|
||||
newReferCode := fmt.Sprintf("TEST%d", time.Now().Unix()%10000)
|
||||
db.Table("user").Where("id = ?", user.Id).Update("refer_code", newReferCode)
|
||||
user.ReferCode = newReferCode
|
||||
fmt.Printf("已为用户 ID=%d 添加 refer_code: %s\n", user.Id, newReferCode)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ 找到用户: ID=%d, ReferCode=%s\n", user.Id, user.ReferCode)
|
||||
|
||||
// 5. 生成 JWT Token
|
||||
sessionId := uuid.New().String()
|
||||
now := time.Now()
|
||||
expireAt := now.Add(time.Hour * 24 * 7) // 7 天
|
||||
|
||||
claims := jwt.MapClaims{
|
||||
"UserId": user.Id,
|
||||
"SessionId": sessionId,
|
||||
"DeviceId": 0,
|
||||
"LoginType": "",
|
||||
"iat": now.Unix(),
|
||||
"exp": expireAt.Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(cfg.JwtAuth.AccessSecret))
|
||||
if err != nil {
|
||||
fmt.Printf("生成 token 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 6. 在 Redis 中创建 session
|
||||
// 正确格式:auth:session_id:sessionId = userId
|
||||
sessionKey := fmt.Sprintf("auth:session_id:%s", sessionId)
|
||||
|
||||
err = rdb.Set(ctx, sessionKey, fmt.Sprintf("%d", user.Id), time.Hour*24*7).Err()
|
||||
if err != nil {
|
||||
fmt.Printf("创建 session 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("✅ Session 创建成功: %s = %d\n", sessionKey, user.Id)
|
||||
|
||||
// 7. 清除旧的短链接缓存,确保重新生成
|
||||
cacheKey := "cache:invite:short_link:" + user.ReferCode
|
||||
rdb.Del(ctx, cacheKey)
|
||||
fmt.Printf("✅ 已清除旧缓存: %s\n", cacheKey)
|
||||
|
||||
// 7. 输出测试信息
|
||||
fmt.Println("\n====================================")
|
||||
fmt.Println("测试 Token 生成成功!")
|
||||
fmt.Println("====================================")
|
||||
fmt.Printf("\n用户 ID: %d\n", user.Id)
|
||||
fmt.Printf("邀请码: %s\n", user.ReferCode)
|
||||
fmt.Printf("Session ID: %s\n", sessionId)
|
||||
fmt.Printf("过期时间: %s\n", expireAt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println("\n====== Token ======")
|
||||
fmt.Println(tokenString)
|
||||
fmt.Println("\n====== 测试命令 ======")
|
||||
fmt.Printf("curl -s 'http://127.0.0.1:8080/v1/public/user/info' \\\n")
|
||||
fmt.Printf(" -H 'authorization: %s' | jq '.'\n", tokenString)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/perfect-panel/server/initialize/migrate"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/pkg/conf"
|
||||
"github.com/perfect-panel/server/pkg/orm"
|
||||
)
|
||||
|
||||
var configFile string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&configFile, "config", "configs/ppanel.yaml", "config file path")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(configFile, &c)
|
||||
|
||||
// Construct DSN
|
||||
m := orm.Mysql{Config: c.MySQL}
|
||||
dsn := m.Dsn()
|
||||
|
||||
log.Println("Connecting to database...")
|
||||
client := migrate.Migrate(dsn)
|
||||
|
||||
log.Println("Forcing version 2117...")
|
||||
if err := client.Force(2117); err != nil {
|
||||
log.Fatalf("Failed to force version: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Force version 2117 success")
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/kutt"
|
||||
)
|
||||
|
||||
// 测试 Kutt 短链接 API
|
||||
// 运行方式: go run cmd/test_kutt/main.go
|
||||
func main() {
|
||||
// Kutt 配置 - 请根据实际情况修改
|
||||
apiURL := "https://getsapp.net/api/v2"
|
||||
apiKey := "6JSjGOzLF1NCYQXuUGZjvrkqU0Jy3upDkYX87DPO"
|
||||
targetURL := "https://gethifast.net"
|
||||
|
||||
// 测试邀请码
|
||||
testInviteCode := "TEST123"
|
||||
|
||||
fmt.Println("====== Kutt 短链接 API 测试 ======")
|
||||
fmt.Printf("API URL: %s\n", apiURL)
|
||||
fmt.Printf("Target URL: %s\n", targetURL)
|
||||
fmt.Printf("测试邀请码: %s\n", testInviteCode)
|
||||
fmt.Println("----------------------------------")
|
||||
|
||||
// 创建客户端
|
||||
client := kutt.NewClient(apiURL, apiKey)
|
||||
ctx := context.Background()
|
||||
|
||||
// 测试 1: 使用便捷方法创建邀请短链接
|
||||
fmt.Println("\n[测试 1] 创建邀请短链接...")
|
||||
shortLink, err := client.CreateInviteShortLink(ctx, targetURL, testInviteCode, "getsapp.net")
|
||||
if err != nil {
|
||||
log.Printf("❌ 创建短链接失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✅ 短链接创建成功: %s\n", shortLink)
|
||||
}
|
||||
|
||||
// 测试 2: 使用完整参数创建短链接
|
||||
fmt.Println("\n[测试 2] 使用完整参数创建短链接...")
|
||||
req := &kutt.CreateLinkRequest{
|
||||
Target: fmt.Sprintf("%s/register?invite=%s", targetURL, "CUSTOM456"),
|
||||
Description: "Test custom short link",
|
||||
Reuse: true,
|
||||
}
|
||||
link, err := client.CreateShortLink(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("❌ 创建短链接失败: %v\n", err)
|
||||
} else {
|
||||
// 打印详细返回信息
|
||||
linkJSON, _ := json.MarshalIndent(link, "", " ")
|
||||
fmt.Printf("✅ 短链接创建成功:\n%s\n", string(linkJSON))
|
||||
}
|
||||
|
||||
fmt.Println("\n====== 测试完成 ======")
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
/*
|
||||
* 设备复用 Session 测试工具
|
||||
*
|
||||
* 这个测试工具用于验证设备复用 session 的逻辑是否正确
|
||||
* 模拟场景:
|
||||
* 1. 设备A第一次登录 - 创建新 session
|
||||
* 2. 设备A再次登录 - 应该复用旧 session
|
||||
* 3. 设备A的session过期 - 应该创建新 session
|
||||
*/
|
||||
|
||||
const (
|
||||
SessionIdKey = "auth:session_id"
|
||||
DeviceCacheKeyKey = "auth:device"
|
||||
UserSessionsKeyPrefix = "auth:user_sessions:"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 连接 Redis
|
||||
rds := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379", // 修改为你的 Redis 地址
|
||||
Password: "", // 修改为你的 Redis 密码
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 检查 Redis 连接
|
||||
if err := rds.Ping(ctx).Err(); err != nil {
|
||||
log.Fatalf("❌ 连接 Redis 失败: %v", err)
|
||||
}
|
||||
fmt.Println("✅ Redis 连接成功")
|
||||
|
||||
// 测试参数
|
||||
testDeviceID := "test-device-12345"
|
||||
testUserID := int64(9999)
|
||||
sessionExpire := 10 * time.Second // 测试用,设置较短的过期时间
|
||||
|
||||
fmt.Println("\n========== 开始测试 ==========")
|
||||
|
||||
// 清理测试数据
|
||||
cleanup(ctx, rds, testDeviceID, testUserID)
|
||||
|
||||
// 测试1: 第一次登录 - 应该创建新 session
|
||||
fmt.Println("\n📋 测试1: 第一次登录")
|
||||
sessionId1, isReuse1 := simulateLogin(ctx, rds, testDeviceID, testUserID, sessionExpire)
|
||||
if isReuse1 {
|
||||
fmt.Println("❌ 测试1失败: 第一次登录不应该复用 session")
|
||||
} else {
|
||||
fmt.Printf("✅ 测试1通过: 创建了新 session: %s\n", sessionId1)
|
||||
}
|
||||
|
||||
// 检查 session 数量
|
||||
count1 := getSessionCount(ctx, rds, testUserID)
|
||||
fmt.Printf(" 当前 session 数量: %d\n", count1)
|
||||
|
||||
// 测试2: 再次登录(session 有效)- 应该复用 session
|
||||
fmt.Println("\n📋 测试2: 再次登录(session 有效)")
|
||||
sessionId2, isReuse2 := simulateLogin(ctx, rds, testDeviceID, testUserID, sessionExpire)
|
||||
if !isReuse2 {
|
||||
fmt.Println("❌ 测试2失败: 应该复用旧 session")
|
||||
} else if sessionId1 != sessionId2 {
|
||||
fmt.Printf("❌ 测试2失败: sessionId 不一致 (%s vs %s)\n", sessionId1, sessionId2)
|
||||
} else {
|
||||
fmt.Printf("✅ 测试2通过: 复用了旧 session: %s\n", sessionId2)
|
||||
}
|
||||
|
||||
// 检查 session 数量 - 应该仍然是1
|
||||
count2 := getSessionCount(ctx, rds, testUserID)
|
||||
fmt.Printf(" 当前 session 数量: %d (预期: 1)\n", count2)
|
||||
if count2 != 1 {
|
||||
fmt.Println("❌ session 数量不正确!")
|
||||
}
|
||||
|
||||
// 测试3: 模拟多设备登录
|
||||
fmt.Println("\n📋 测试3: 多设备登录")
|
||||
testDeviceID2 := "test-device-67890"
|
||||
sessionId3, isReuse3 := simulateLogin(ctx, rds, testDeviceID2, testUserID, sessionExpire)
|
||||
if isReuse3 {
|
||||
fmt.Println("❌ 测试3失败: 新设备不应该复用 session")
|
||||
} else {
|
||||
fmt.Printf("✅ 测试3通过: 设备B创建了新 session: %s\n", sessionId3)
|
||||
}
|
||||
|
||||
// 检查 session 数量 - 应该是2
|
||||
count3 := getSessionCount(ctx, rds, testUserID)
|
||||
fmt.Printf(" 当前 session 数量: %d (预期: 2)\n", count3)
|
||||
|
||||
// 测试4: 设备A再次登录 - 仍然应该复用
|
||||
fmt.Println("\n📋 测试4: 设备A再次登录")
|
||||
sessionId4, isReuse4 := simulateLogin(ctx, rds, testDeviceID, testUserID, sessionExpire)
|
||||
if !isReuse4 {
|
||||
fmt.Println("❌ 测试4失败: 应该复用设备A的旧 session")
|
||||
} else if sessionId1 != sessionId4 {
|
||||
fmt.Printf("❌ 测试4失败: sessionId 不一致 (%s vs %s)\n", sessionId1, sessionId4)
|
||||
} else {
|
||||
fmt.Printf("✅ 测试4通过: 设备A复用了旧 session: %s\n", sessionId4)
|
||||
}
|
||||
|
||||
// 检查 session 数量 - 仍然应该是2
|
||||
count4 := getSessionCount(ctx, rds, testUserID)
|
||||
fmt.Printf(" 当前 session 数量: %d (预期: 2)\n", count4)
|
||||
|
||||
// 测试5: 等待 session 过期后再登录
|
||||
fmt.Println("\n📋 测试5: 等待 session 过期后再登录")
|
||||
fmt.Printf(" 等待 %v ...\n", sessionExpire+time.Second)
|
||||
time.Sleep(sessionExpire + time.Second)
|
||||
|
||||
sessionId5, isReuse5 := simulateLogin(ctx, rds, testDeviceID, testUserID, sessionExpire)
|
||||
if isReuse5 {
|
||||
fmt.Println("❌ 测试5失败: session 过期后不应该复用")
|
||||
} else {
|
||||
fmt.Printf("✅ 测试5通过: 创建了新 session: %s\n", sessionId5)
|
||||
}
|
||||
|
||||
// 测试6: 设备转移场景(关键安全测试)
|
||||
fmt.Println("\n📋 测试6: 设备转移场景(用户A的设备被用户B使用)")
|
||||
testDeviceID3 := "test-device-transfer"
|
||||
testUserA := int64(1001)
|
||||
testUserB := int64(1002)
|
||||
|
||||
// 用户A用设备登录
|
||||
cleanup(ctx, rds, testDeviceID3, testUserA)
|
||||
cleanup(ctx, rds, testDeviceID3, testUserB)
|
||||
sessionA, _ := simulateLogin(ctx, rds, testDeviceID3, testUserA, sessionExpire)
|
||||
fmt.Printf(" 用户A登录,session: %s\n", sessionA)
|
||||
|
||||
// 用户B用同一设备登录(设备转移场景)
|
||||
sessionB, isReuseB := simulateLogin(ctx, rds, testDeviceID3, testUserB, sessionExpire)
|
||||
if isReuseB {
|
||||
fmt.Println("❌ 测试6失败: 用户B不应该复用用户A的session!(安全漏洞)")
|
||||
} else {
|
||||
fmt.Printf("✅ 测试6通过: 用户B创建了新 session: %s\n", sessionB)
|
||||
}
|
||||
|
||||
// 验证用户A和B的session不同
|
||||
if sessionA == sessionB {
|
||||
fmt.Println("❌ 安全问题: 两个用户使用了相同的session!")
|
||||
} else {
|
||||
fmt.Println("✅ 安全验证通过: 两个用户使用不同的session")
|
||||
}
|
||||
cleanup(ctx, rds, testDeviceID, testUserID)
|
||||
cleanup(ctx, rds, testDeviceID2, testUserID)
|
||||
|
||||
fmt.Println("\n========== 测试完成 ==========")
|
||||
}
|
||||
|
||||
// simulateLogin 模拟登录逻辑
|
||||
// 返回: sessionId, isReuse (是否复用了旧 session)
|
||||
func simulateLogin(ctx context.Context, rds *redis.Client, deviceID string, userID int64, expire time.Duration) (string, bool) {
|
||||
var sessionId string
|
||||
var reuseSession bool
|
||||
|
||||
deviceCacheKey := fmt.Sprintf("%v:%v", DeviceCacheKeyKey, deviceID)
|
||||
|
||||
// 检查设备是否有旧的有效 session
|
||||
if oldSid, getErr := rds.Get(ctx, deviceCacheKey).Result(); getErr == nil && oldSid != "" {
|
||||
// 检查旧 session 是否仍然有效 AND 属于当前用户
|
||||
oldSessionKey := fmt.Sprintf("%v:%v", SessionIdKey, oldSid)
|
||||
if uidStr, existErr := rds.Get(ctx, oldSessionKey).Result(); existErr == nil && uidStr != "" {
|
||||
// 验证 session 属于当前用户 (防止设备转移后复用其他用户的session)
|
||||
if uidStr == fmt.Sprintf("%d", userID) {
|
||||
sessionId = oldSid
|
||||
reuseSession = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !reuseSession {
|
||||
// 生成新的 sessionId
|
||||
sessionId = fmt.Sprintf("session-%d-%d", userID, time.Now().UnixNano())
|
||||
|
||||
// 添加到用户的 session 集合
|
||||
sessionsKey := fmt.Sprintf("%s%v", UserSessionsKeyPrefix, userID)
|
||||
rds.ZAdd(ctx, sessionsKey, redis.Z{Score: float64(time.Now().Unix()), Member: sessionId})
|
||||
rds.Expire(ctx, sessionsKey, expire)
|
||||
}
|
||||
|
||||
// 存储/刷新 session
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", SessionIdKey, sessionId)
|
||||
rds.Set(ctx, sessionIdCacheKey, userID, expire)
|
||||
|
||||
// 存储/刷新设备到session的映射
|
||||
rds.Set(ctx, deviceCacheKey, sessionId, expire)
|
||||
|
||||
return sessionId, reuseSession
|
||||
}
|
||||
|
||||
// getSessionCount 获取用户的 session 数量
|
||||
func getSessionCount(ctx context.Context, rds *redis.Client, userID int64) int64 {
|
||||
sessionsKey := fmt.Sprintf("%s%v", UserSessionsKeyPrefix, userID)
|
||||
count, _ := rds.ZCard(ctx, sessionsKey).Result()
|
||||
return count
|
||||
}
|
||||
|
||||
// cleanup 清理测试数据
|
||||
func cleanup(ctx context.Context, rds *redis.Client, deviceID string, userID int64) {
|
||||
deviceCacheKey := fmt.Sprintf("%v:%v", DeviceCacheKeyKey, deviceID)
|
||||
sessionsKey := fmt.Sprintf("%s%v", UserSessionsKeyPrefix, userID)
|
||||
|
||||
// 获取设备的 sessionId
|
||||
if sid, err := rds.Get(ctx, deviceCacheKey).Result(); err == nil {
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", SessionIdKey, sid)
|
||||
rds.Del(ctx, sessionIdCacheKey)
|
||||
}
|
||||
|
||||
rds.Del(ctx, deviceCacheKey)
|
||||
rds.Del(ctx, sessionsKey)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 配置结构
|
||||
type AppConfig struct {
|
||||
MySQL struct {
|
||||
Addr string `yaml:"Addr"`
|
||||
Dbname string `yaml:"Dbname"`
|
||||
Username string `yaml:"Username"`
|
||||
Password string `yaml:"Password"`
|
||||
Config string `yaml:"Config"`
|
||||
} `yaml:"MySQL"`
|
||||
}
|
||||
|
||||
type System struct {
|
||||
Key string `gorm:"column:key;primaryKey"`
|
||||
Value string `gorm:"column:value"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("====== 更新 CustomData ======")
|
||||
|
||||
// 1. 读取配置
|
||||
cfgData, err := os.ReadFile("configs/ppanel.yaml")
|
||||
if err != nil {
|
||||
fmt.Printf("读取配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg AppConfig
|
||||
if err := yaml.Unmarshal(cfgData, &cfg); err != nil {
|
||||
fmt.Printf("解析配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 连接数据库
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s",
|
||||
cfg.MySQL.Username, cfg.MySQL.Password, cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.MySQL.Config)
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
fmt.Printf("数据库连接失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ 数据库连接成功")
|
||||
|
||||
// 3. 查找 SiteConfig (在 system 表中,key 通常是 'SiteConfig')
|
||||
// 注意:system 表结构可能由 key, value 组成
|
||||
// 我们需要查找包含 CustomData 的那个配置
|
||||
|
||||
// 先尝试直接查找 SiteConfig
|
||||
var sysConfig System
|
||||
// 根据之前的查看,SiteConfig 可能不是直接存 JSON,而是字段。
|
||||
// 但用户之前 curl 看到的是 custom_data 字段。
|
||||
// 让我们查找包含 "shareUrl" 的记录来定位
|
||||
err = db.Table("system").Where("value LIKE ?", "%shareUrl%").First(&sysConfig).Error
|
||||
if err != nil {
|
||||
fmt.Printf("未找到包含 shareUrl 的配置: %v\n", err)
|
||||
// 尝试列出所有 key
|
||||
var keys []string
|
||||
db.Table("system").Pluck("key", &keys)
|
||||
fmt.Printf("现有 Keys: %v\n", keys)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("找到配置 Key: %s\n", sysConfig.Key)
|
||||
fmt.Printf("原始内容: %s\n", sysConfig.Value)
|
||||
|
||||
// 4. 解析并修改
|
||||
// System Value 可能是 SiteConfig 的 JSON,或者 CustomData 只是其中一个字段
|
||||
// 假设 Value 是 SiteConfig 结构体的 JSON
|
||||
var siteConfigMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(sysConfig.Value), &siteConfigMap); err != nil {
|
||||
fmt.Printf("解析 Config Value 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有 CustomData
|
||||
if customDataStr, ok := siteConfigMap["CustomData"].(string); ok {
|
||||
fmt.Println("找到 CustomData 字段,正在更新...")
|
||||
|
||||
var customDataMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(customDataStr), &customDataMap); err != nil {
|
||||
fmt.Printf("解析 CustomData 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 添加 domain
|
||||
customDataMap["domain"] = "getsapp.net"
|
||||
|
||||
// 重新序列化 CustomData
|
||||
newCustomDataBytes, _ := json.Marshal(customDataMap)
|
||||
siteConfigMap["CustomData"] = string(newCustomDataBytes)
|
||||
|
||||
fmt.Printf("新的 CustomData: %s\n", string(newCustomDataBytes))
|
||||
|
||||
} else {
|
||||
// 也许 Value 本身就是 CustomData? (不太可能,根据之前的 grep 结果)
|
||||
// 或者 Key 是 'custom_data'?
|
||||
fmt.Println("未在配置中找到 CustomData 字段,尝试直接解析为 CustomData...")
|
||||
// 尝试直接添加 domain 看是否合理
|
||||
siteConfigMap["domain"] = "getsapp.net"
|
||||
}
|
||||
|
||||
// 5. 保存回数据库
|
||||
newConfigBytes, _ := json.Marshal(siteConfigMap)
|
||||
// fmt.Printf("更新后的配置 Value: %s\n", string(newConfigBytes))
|
||||
|
||||
err = db.Table("system").Where("`key` = ?", sysConfig.Key).Update("value", string(newConfigBytes)).Error
|
||||
if err != nil {
|
||||
fmt.Printf("更新数据库失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("✅ 数据库更新成功!")
|
||||
}
|
||||
Reference in New Issue
Block a user