From 62cf68b49bcd7f1e8d58ea4e14ed91c2c4b3d69a Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Sat, 4 Apr 2026 11:46:06 -0700 Subject: [PATCH] x --- .codex-tmp/ua999_cleanup.go | 579 ++++++++++++++++++ initialize/register.go | 3 +- .../admin/system/getRegisterConfigLogic.go | 14 +- .../admin/system/updateRegisterConfigLogic.go | 50 +- internal/types/types.go | 20 +- 5 files changed, 652 insertions(+), 14 deletions(-) create mode 100644 .codex-tmp/ua999_cleanup.go diff --git a/.codex-tmp/ua999_cleanup.go b/.codex-tmp/ua999_cleanup.go new file mode 100644 index 0000000..53ac065 --- /dev/null +++ b/.codex-tmp/ua999_cleanup.go @@ -0,0 +1,579 @@ +package main + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "log" + "os" + "sort" + "strings" + "time" + + "github.com/redis/go-redis/v9" + _ "github.com/go-sql-driver/mysql" + "gopkg.in/yaml.v3" +) + +type appConfig struct { + MySQL struct { + Addr string `yaml:"Addr"` + Username string `yaml:"Username"` + Password string `yaml:"Password"` + Dbname string `yaml:"Dbname"` + Config string `yaml:"Config"` + } `yaml:"MySQL"` + Redis struct { + Host string `yaml:"Host"` + Pass string `yaml:"Pass"` + DB int `yaml:"DB"` + } `yaml:"Redis"` +} + +type userRow struct { + ID int64 `json:"id"` + ReferCode string `json:"refer_code"` + Balance int64 `json:"balance"` + Commission int64 `json:"commission"` + GiftAmount int64 `json:"gift_amount"` + Enable bool `json:"enable"` + IsAdmin bool `json:"is_admin"` + ValidEmail bool `json:"valid_email"` + MemberStatus string `json:"member_status"` + CreatedAt time.Time `json:"created_at"` + DeletedAt sql.NullTime `json:"-"` +} + +type authMethod struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + AuthType string `json:"auth_type"` + Identifier string `json:"identifier"` + Verified bool `json:"verified"` + CreatedAt time.Time `json:"created_at"` +} + +type deviceInfo struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + IP string `json:"ip"` + UserAgent string `json:"user_agent"` + Identifier string `json:"identifier"` + ShortCode string `json:"short_code"` + Online bool `json:"online"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` +} + +type subscribeInfo struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + OrderID int64 `json:"order_id"` + SubscribeID int64 `json:"subscribe_id"` + Token string `json:"token"` + UUID string `json:"uuid"` + Status uint8 `json:"status"` + StartTime time.Time `json:"start_time"` + ExpireTime time.Time `json:"expire_time"` +} + +type familyInfo struct { + FamilyID int64 `json:"family_id"` + OwnerUserID int64 `json:"owner_user_id"` + IsOwner bool `json:"is_owner"` + MemberCount int64 `json:"member_count"` +} + +type userSummary struct { + User userRow `json:"user"` + AuthMethods []authMethod `json:"auth_methods"` + Devices []deviceInfo `json:"devices"` + Subscriptions []subscribeInfo `json:"subscriptions"` + Family *familyInfo `json:"family,omitempty"` + OrderCount int64 `json:"order_count"` + TicketCount int64 `json:"ticket_count"` + TrafficLogCount int64 `json:"traffic_log_count"` + SystemLogCount int64 `json:"system_log_count"` + WithdrawalCount int64 `json:"withdrawal_count"` + IAPTransactionCount int64 `json:"iap_transaction_count"` + LogMessageCount int64 `json:"log_message_count"` + OnlineRecordCount int64 `json:"online_record_count"` +} + +type deleteResult struct { + UserID int64 `json:"user_id"` + DeletedDBRows []string `json:"deleted_db_rows"` + DeletedRedisKeys int `json:"deleted_redis_keys"` +} + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} + +func main() { + ctx := context.Background() + cfg := loadConfig("/Users/Apple/code_vpn/vpn/ppanel-server/etc/ppanel.yaml") + + 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 := sql.Open("mysql", dsn) + must(err) + defer db.Close() + must(db.PingContext(ctx)) + + rdb := redis.NewClient(&redis.Options{ + Addr: cfg.Redis.Host, + Password: cfg.Redis.Pass, + DB: cfg.Redis.DB, + }) + defer rdb.Close() + must(rdb.Ping(ctx).Err()) + + targetUserIDs, err := findTargetUsers(ctx, db) + must(err) + if len(targetUserIDs) == 0 { + fmt.Println(`{"matched_users":[],"deleted":[]}`) + return + } + + summaries := make([]userSummary, 0, len(targetUserIDs)) + for _, userID := range targetUserIDs { + summary, sumErr := collectSummary(ctx, db, userID) + must(sumErr) + summaries = append(summaries, summary) + } + + before, err := json.MarshalIndent(map[string]interface{}{ + "matched_users": summaries, + }, "", " ") + must(err) + fmt.Println(string(before)) + + results := make([]deleteResult, 0, len(targetUserIDs)) + for _, summary := range summaries { + result, delErr := deleteUser(ctx, db, rdb, summary) + must(delErr) + results = append(results, result) + } + + after, err := json.MarshalIndent(map[string]interface{}{ + "deleted": results, + }, "", " ") + must(err) + fmt.Println(string(after)) +} + +func loadConfig(path string) appConfig { + content, err := os.ReadFile(path) + must(err) + var cfg appConfig + must(yaml.Unmarshal(content, &cfg)) + return cfg +} + +func findTargetUsers(ctx context.Context, db *sql.DB) ([]int64, error) { + rows, err := db.QueryContext(ctx, ` + SELECT DISTINCT user_id + FROM user_device + WHERE user_agent LIKE ? + ORDER BY user_id ASC + `, "%999%") + if err != nil { + return nil, err + } + defer rows.Close() + + var ids []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + ids = append(ids, id) + } + return ids, rows.Err() +} + +func collectSummary(ctx context.Context, db *sql.DB, userID int64) (userSummary, error) { + var summary userSummary + summary.User.ID = userID + + err := db.QueryRowContext(ctx, ` + SELECT id, refer_code, balance, commission, gift_amount, enable, is_admin, valid_email, member_status, created_at, deleted_at + FROM user + WHERE id = ? + `, userID).Scan( + &summary.User.ID, + &summary.User.ReferCode, + &summary.User.Balance, + &summary.User.Commission, + &summary.User.GiftAmount, + &summary.User.Enable, + &summary.User.IsAdmin, + &summary.User.ValidEmail, + &summary.User.MemberStatus, + &summary.User.CreatedAt, + &summary.User.DeletedAt, + ) + if err != nil { + return summary, err + } + + summary.AuthMethods, err = queryAuthMethods(ctx, db, userID) + if err != nil { + return summary, err + } + summary.Devices, err = queryDevices(ctx, db, userID) + if err != nil { + return summary, err + } + summary.Subscriptions, err = querySubscriptions(ctx, db, userID) + if err != nil { + return summary, err + } + summary.Family, err = queryFamily(ctx, db, userID) + if err != nil { + return summary, err + } + if summary.OrderCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM `order` WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.TicketCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM ticket WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.TrafficLogCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM traffic_log WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.SystemLogCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM system_logs WHERE object_id = ?", userID); err != nil { + return summary, err + } + if summary.WithdrawalCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM user_withdrawal WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.IAPTransactionCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM apple_iap_transactions WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.LogMessageCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM log_message WHERE user_id = ?", userID); err != nil { + return summary, err + } + if summary.OnlineRecordCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM user_device_online_record WHERE user_id = ?", userID); err != nil { + return summary, err + } + + return summary, nil +} + +func queryAuthMethods(ctx context.Context, db *sql.DB, userID int64) ([]authMethod, error) { + rows, err := db.QueryContext(ctx, ` + SELECT id, user_id, auth_type, auth_identifier, verified, created_at + FROM user_auth_methods + WHERE user_id = ? + ORDER BY id ASC + `, userID) + if err != nil { + return nil, err + } + defer rows.Close() + + var items []authMethod + for rows.Next() { + var item authMethod + if err := rows.Scan(&item.ID, &item.UserID, &item.AuthType, &item.Identifier, &item.Verified, &item.CreatedAt); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func queryDevices(ctx context.Context, db *sql.DB, userID int64) ([]deviceInfo, error) { + rows, err := db.QueryContext(ctx, ` + SELECT id, user_id, ip, user_agent, identifier, short_code, online, enabled, created_at + FROM user_device + WHERE user_id = ? + ORDER BY id ASC + `, userID) + if err != nil { + return nil, err + } + defer rows.Close() + + var items []deviceInfo + for rows.Next() { + var item deviceInfo + if err := rows.Scan(&item.ID, &item.UserID, &item.IP, &item.UserAgent, &item.Identifier, &item.ShortCode, &item.Online, &item.Enabled, &item.CreatedAt); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func querySubscriptions(ctx context.Context, db *sql.DB, userID int64) ([]subscribeInfo, error) { + rows, err := db.QueryContext(ctx, ` + SELECT id, user_id, order_id, subscribe_id, token, uuid, status, start_time, expire_time + FROM user_subscribe + WHERE user_id = ? + ORDER BY id ASC + `, userID) + if err != nil { + return nil, err + } + defer rows.Close() + + var items []subscribeInfo + for rows.Next() { + var item subscribeInfo + if err := rows.Scan(&item.ID, &item.UserID, &item.OrderID, &item.SubscribeID, &item.Token, &item.UUID, &item.Status, &item.StartTime, &item.ExpireTime); err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func queryFamily(ctx context.Context, db *sql.DB, userID int64) (*familyInfo, error) { + var info familyInfo + err := db.QueryRowContext(ctx, ` + SELECT ufm.family_id, uf.owner_user_id + FROM user_family_member ufm + JOIN user_family uf ON uf.id = ufm.family_id AND uf.deleted_at IS NULL + WHERE ufm.user_id = ? AND ufm.deleted_at IS NULL + LIMIT 1 + `, userID).Scan(&info.FamilyID, &info.OwnerUserID) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + info.IsOwner = info.OwnerUserID == userID + memberCount, err := queryCount(ctx, db, ` + SELECT COUNT(*) + FROM user_family_member + WHERE family_id = ? AND deleted_at IS NULL + `, info.FamilyID) + if err != nil { + return nil, err + } + info.MemberCount = memberCount + return &info, nil +} + +func queryCount(ctx context.Context, db *sql.DB, q string, arg interface{}) (int64, error) { + var count int64 + err := db.QueryRowContext(ctx, q, arg).Scan(&count) + return count, err +} + +func deleteUser(ctx context.Context, db *sql.DB, rdb *redis.Client, summary userSummary) (deleteResult, error) { + result := deleteResult{UserID: summary.User.ID} + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return result, err + } + defer tx.Rollback() + + if summary.Family != nil { + if summary.Family.IsOwner { + if res, err := tx.ExecContext(ctx, `DELETE FROM user_family_member WHERE family_id = ?`, summary.Family.FamilyID); err != nil { + return result, err + } else { + result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family_member=%d", rowsAffected(res))) + } + if res, err := tx.ExecContext(ctx, `DELETE FROM user_family WHERE id = ?`, summary.Family.FamilyID); err != nil { + return result, err + } else { + result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family=%d", rowsAffected(res))) + } + } else { + if res, err := tx.ExecContext(ctx, `DELETE FROM user_family_member WHERE user_id = ? AND family_id = ?`, summary.User.ID, summary.Family.FamilyID); err != nil { + return result, err + } else { + result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family_member=%d", rowsAffected(res))) + } + } + } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user_auth_methods WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_auth_methods=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user_subscribe WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_subscribe=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user_device WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_device=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user_device_online_record WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_device_online_record=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user_withdrawal WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_withdrawal=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, "DELETE FROM `order` WHERE user_id = ?", summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("order=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM traffic_log WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("traffic_log=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM system_logs WHERE object_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("system_logs=%d", rowsAffected(res))) } + + var ticketIDs []int64 + ticketRows, err := tx.QueryContext(ctx, `SELECT id FROM ticket WHERE user_id = ?`, summary.User.ID) + if err != nil { + return result, err + } + for ticketRows.Next() { + var id int64 + if err := ticketRows.Scan(&id); err != nil { + ticketRows.Close() + return result, err + } + ticketIDs = append(ticketIDs, id) + } + ticketRows.Close() + + if len(ticketIDs) > 0 { + holders := strings.TrimSuffix(strings.Repeat("?,", len(ticketIDs)), ",") + args := make([]interface{}, 0, len(ticketIDs)) + for _, id := range ticketIDs { + args = append(args, id) + } + if res, err := tx.ExecContext(ctx, "DELETE FROM ticket_follow WHERE ticket_id IN ("+holders+")", args...); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("ticket_follow=%d", rowsAffected(res))) } + } + + if res, err := tx.ExecContext(ctx, `DELETE FROM ticket WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("ticket=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM apple_iap_transactions WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("apple_iap_transactions=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM log_message WHERE user_id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("log_message=%d", rowsAffected(res))) } + + if res, err := tx.ExecContext(ctx, `DELETE FROM user WHERE id = ?`, summary.User.ID); err != nil { + return result, err + } else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user=%d", rowsAffected(res))) } + + if err := tx.Commit(); err != nil { + return result, err + } + + redisKeys, err := cleanupRedis(ctx, rdb, summary) + if err != nil { + return result, err + } + result.DeletedRedisKeys = len(redisKeys) + sort.Strings(result.DeletedDBRows) + return result, nil +} + +func cleanupRedis(ctx context.Context, rdb *redis.Client, summary userSummary) ([]string, error) { + keySet := map[string]struct{}{ + fmt.Sprintf("cache:user:id:%d", summary.User.ID): {}, + fmt.Sprintf("cache:user:subscribe:user:%d", summary.User.ID): {}, + fmt.Sprintf("cache:user:subscribe:user:%d:all", summary.User.ID): {}, + fmt.Sprintf("auth:user_sessions:%d", summary.User.ID): {}, + } + + for _, am := range summary.AuthMethods { + if am.AuthType == "email" && am.Identifier != "" { + keySet[fmt.Sprintf("cache:user:email:%s", am.Identifier)] = struct{}{} + } + } + for _, sub := range summary.Subscriptions { + keySet[fmt.Sprintf("cache:user:subscribe:id:%d", sub.ID)] = struct{}{} + if sub.Token != "" { + keySet[fmt.Sprintf("cache:user:subscribe:token:%s", sub.Token)] = struct{}{} + } + } + for _, device := range summary.Devices { + keySet[fmt.Sprintf("cache:user:device:id:%d", device.ID)] = struct{}{} + if device.Identifier != "" { + keySet[fmt.Sprintf("cache:user:device:number:%s", device.Identifier)] = struct{}{} + keySet[fmt.Sprintf("auth:device_identifier:%s", device.Identifier)] = struct{}{} + } + } + + sessionsKey := fmt.Sprintf("auth:user_sessions:%d", summary.User.ID) + sessionIDs, err := rdb.ZRange(ctx, sessionsKey, 0, -1).Result() + if err != nil && err != redis.Nil { + return nil, err + } + for _, sessionID := range sessionIDs { + if sessionID == "" { + continue + } + keySet[fmt.Sprintf("auth:session_id:%s", sessionID)] = struct{}{} + keySet[fmt.Sprintf("auth:session_id:detail:%s", sessionID)] = struct{}{} + } + + var cursor uint64 + for { + keys, nextCursor, scanErr := rdb.Scan(ctx, cursor, "auth:session_id:*", 200).Result() + if scanErr != nil { + return nil, scanErr + } + for _, key := range keys { + if strings.Contains(key, ":detail:") { + continue + } + value, getErr := rdb.Get(ctx, key).Result() + if getErr != nil { + continue + } + if value == fmt.Sprintf("%d", summary.User.ID) { + keySet[key] = struct{}{} + sessionID := strings.TrimPrefix(key, "auth:session_id:") + if sessionID != "" { + keySet[fmt.Sprintf("auth:session_id:detail:%s", sessionID)] = struct{}{} + } + } + } + cursor = nextCursor + if cursor == 0 { + break + } + } + + keys := make([]string, 0, len(keySet)) + for key := range keySet { + keys = append(keys, key) + } + sort.Strings(keys) + if len(keys) == 0 { + return keys, nil + } + if err := rdb.Del(ctx, keys...).Err(); err != nil { + return nil, err + } + return keys, nil +} + +func rowsAffected(res sql.Result) int64 { + if res == nil { + return 0 + } + n, err := res.RowsAffected() + if err != nil { + return 0 + } + return n +} diff --git a/initialize/register.go b/initialize/register.go index bbe44f9..b593abd 100644 --- a/initialize/register.go +++ b/initialize/register.go @@ -5,7 +5,6 @@ import ( "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/tool" ) @@ -17,7 +16,7 @@ func Register(ctx *svc.ServiceContext) { logger.Errorf("[Init Register Config] Get Register Config Error: %s", err.Error()) return } - var registerConfig config.RegisterConfig + registerConfig := ctx.Config.Register tool.SystemConfigSliceReflectToStruct(configs, ®isterConfig) ctx.Config.Register = registerConfig } diff --git a/internal/logic/admin/system/getRegisterConfigLogic.go b/internal/logic/admin/system/getRegisterConfigLogic.go index 7ba002f..74eef6e 100644 --- a/internal/logic/admin/system/getRegisterConfigLogic.go +++ b/internal/logic/admin/system/getRegisterConfigLogic.go @@ -26,7 +26,19 @@ func NewGetRegisterConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) } func (l *GetRegisterConfigLogic) GetRegisterConfig() (*types.RegisterConfig, error) { - resp := &types.RegisterConfig{} + resp := &types.RegisterConfig{ + StopRegister: l.svcCtx.Config.Register.StopRegister, + EnableTrial: l.svcCtx.Config.Register.EnableTrial, + EnableTrialEmailWhitelist: l.svcCtx.Config.Register.EnableTrialEmailWhitelist, + TrialSubscribe: l.svcCtx.Config.Register.TrialSubscribe, + TrialTime: l.svcCtx.Config.Register.TrialTime, + TrialTimeUnit: l.svcCtx.Config.Register.TrialTimeUnit, + TrialEmailDomainWhitelist: l.svcCtx.Config.Register.TrialEmailDomainWhitelist, + EnableIpRegisterLimit: l.svcCtx.Config.Register.EnableIpRegisterLimit, + IpRegisterLimit: l.svcCtx.Config.Register.IpRegisterLimit, + IpRegisterLimitDuration: l.svcCtx.Config.Register.IpRegisterLimitDuration, + DeviceLimit: l.svcCtx.Config.Register.DeviceLimit, + } // get register config from database configs, err := l.svcCtx.SystemModel.GetRegisterConfig(l.ctx) diff --git a/internal/logic/admin/system/updateRegisterConfigLogic.go b/internal/logic/admin/system/updateRegisterConfigLogic.go index d5bf8c3..f6d020b 100644 --- a/internal/logic/admin/system/updateRegisterConfigLogic.go +++ b/internal/logic/admin/system/updateRegisterConfigLogic.go @@ -4,6 +4,7 @@ import ( "context" "reflect" + "strings" "github.com/perfect-panel/server/initialize" "github.com/perfect-panel/server/internal/config" @@ -44,8 +45,28 @@ func (l *UpdateRegisterConfigLogic) UpdateRegisterConfig(req *types.RegisterConf fieldName := t.Field(i).Name // Get the field value to string fieldValue := tool.ConvertValueToString(v.Field(i)) - // Update the site config - err = db.Model(&system.System{}).Where("`category` = 'register' and `key` = ?", fieldName).Update("value", fieldValue).Error + + var existing system.System + queryErr := db.Where("`category` = ? and `key` = ?", "register", fieldName).First(&existing).Error + if queryErr != nil && !errors.Is(queryErr, gorm.ErrRecordNotFound) { + return queryErr + } + if errors.Is(queryErr, gorm.ErrRecordNotFound) { + fieldValue = l.defaultRegisterFieldValue(fieldName, fieldValue) + } + + record := &system.System{ + Category: "register", + Key: fieldName, + } + assignments := map[string]interface{}{ + "value": fieldValue, + "type": inferRegisterSystemValueType(t.Field(i).Type.Kind()), + "desc": fieldName, + } + err = db.Where("`category` = ? and `key` = ?", "register", fieldName). + Assign(assignments). + FirstOrCreate(record).Error if err != nil { break } @@ -63,3 +84,28 @@ func (l *UpdateRegisterConfigLogic) UpdateRegisterConfig(req *types.RegisterConf initialize.Register(l.svcCtx) return nil } + +func inferRegisterSystemValueType(kind reflect.Kind) string { + switch kind { + case reflect.Bool: + return "bool" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return "int" + default: + return "string" + } +} + +func (l *UpdateRegisterConfigLogic) defaultRegisterFieldValue(fieldName, fieldValue string) string { + switch fieldName { + case "EnableTrialEmailWhitelist": + return "true" + case "TrialEmailDomainWhitelist": + if strings.TrimSpace(fieldValue) != "" { + return fieldValue + } + return l.svcCtx.Config.Register.TrialEmailDomainWhitelist + default: + return fieldValue + } +} diff --git a/internal/types/types.go b/internal/types/types.go index 149eef5..81a3f7b 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -2267,15 +2267,17 @@ type RedemptionRecord struct { } type RegisterConfig struct { - StopRegister bool `json:"stop_register"` - EnableTrial bool `json:"enable_trial"` - TrialSubscribe int64 `json:"trial_subscribe"` - TrialTime int64 `json:"trial_time"` - TrialTimeUnit string `json:"trial_time_unit"` - EnableIpRegisterLimit bool `json:"enable_ip_register_limit"` - IpRegisterLimit int64 `json:"ip_register_limit"` - IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"` - DeviceLimit int64 `json:"device_limit"` + StopRegister bool `json:"stop_register"` + EnableTrial bool `json:"enable_trial"` + EnableTrialEmailWhitelist bool `json:"enable_trial_email_whitelist"` + TrialSubscribe int64 `json:"trial_subscribe"` + TrialTime int64 `json:"trial_time"` + TrialTimeUnit string `json:"trial_time_unit"` + TrialEmailDomainWhitelist string `json:"trial_email_domain_whitelist"` + EnableIpRegisterLimit bool `json:"enable_ip_register_limit"` + IpRegisterLimit int64 `json:"ip_register_limit"` + IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"` + DeviceLimit int64 `json:"device_limit"` } type RegisterLog struct {