Compare commits

...

2 Commits

Author SHA1 Message Date
fdfadca57f 赠送逻辑
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m57s
2026-03-18 02:04:34 -07:00
50f6995d60 redis
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m33s
2026-03-18 00:53:35 -07:00
9 changed files with 78 additions and 59 deletions

View File

@ -15,10 +15,10 @@ Logger: # 日志配置
Level: debug # 日志级别: debug, info, warn, error, panic, fatal
MySQL:
Addr: 154.12.35.103:3306 # host 网络模式; bridge 模式改为 mysql:3306
Addr: 103.150.215.44:3306 # host 网络模式; bridge 模式改为 mysql:3306
Username: root # MySQL用户名
Password: jpcV41ppanel # MySQL密码与 .env MYSQL_ROOT_PASSWORD 一致
Dbname: ppanel # MySQL数据库名
Dbname: hifast # MySQL数据库名
Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
MaxIdleConns: 10
MaxOpenConns: 100

View File

@ -323,7 +323,7 @@ func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) (*user.Subscri
startTime := time.Now()
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId))
subscribeToken := uuidx.NewUUID().String()
subscribeUUID := uuidx.NewUUID().String()
userSub := &user.Subscribe{

View File

@ -232,7 +232,7 @@ func (l *EmailLoginLogic) activeTrial(uid int64) error {
Traffic: sub.Traffic,
Download: 0,
Upload: 0,
Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)),
Token: uuidx.NewUUID().String(),
UUID: uuidx.NewUUID().String(),
Status: 1,
}

View File

@ -835,7 +835,7 @@ func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) (*use
startTime := time.Now()
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid))
subscribeToken := uuidx.NewUUID().String()
subscribeUUID := uuidx.NewUUID().String()
l.Debugw("creating trial subscription",

View File

@ -269,7 +269,7 @@ func (l *TelephoneUserRegisterLogic) activeTrial(uid int64) (*user.Subscribe, er
Traffic: sub.Traffic,
Download: 0,
Upload: 0,
Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)),
Token: uuidx.NewUUID().String(),
UUID: uuidx.NewUUID().String(),
Status: 1,
}

View File

@ -266,7 +266,7 @@ func (l *UserRegisterLogic) activeTrial(uid int64) (*user.Subscribe, error) {
Traffic: sub.Traffic,
Download: 0,
Upload: 0,
Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)),
Token: uuidx.NewUUID().String(),
UUID: uuidx.NewUUID().String(),
Status: 1,
}

View File

@ -107,8 +107,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
err = rds.Ping(context.Background()).Err()
if err != nil {
panic(err.Error())
} else {
_ = rds.FlushAll(context.Background()).Err()
}
authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align())
nonceStore := signature.NewRedisNonceStore(rds)

View File

@ -276,8 +276,13 @@ func main() {
SELECT user_id AS uid FROM ` + "`order`" + ` WHERE status = ? AND user_id > 0
UNION
SELECT user_id AS uid FROM apple_iap_transactions WHERE user_id > 0
UNION
SELECT user_id AS uid FROM user_subscribe WHERE user_id > 0
) t
INNER JOIN user u ON u.id = t.uid
WHERE u.id NOT IN (
SELECT user_id FROM user_auth_methods WHERE auth_type = 'email' AND auth_identifier = 'devneeds52@gmail.com'
)
ORDER BY t.uid
`, orderStatusCompleted).Scan(&paidIDs).Error
if err != nil {
@ -674,10 +679,22 @@ func main() {
now := time.Now()
familyCount := 0
// ── 为了配合基于新 UID 的条件检查,预先构建映射 ──
deviceCountByNewUID := make(map[int64]int)
for i := range devices {
deviceCountByNewUID[devices[i].UserId]++
}
hasEmailByNewUID := make(map[int64]bool)
for i := range auths {
if auths[i].AuthType == "email" {
hasEmailByNewUID[auths[i].UserId] = true
}
}
err = dstDB.Transaction(func(tx *gorm.DB) error {
for _, uid := range paidIDs {
// 只为多设备用户创建家庭组
if len(deviceByUser[uid]) <= 1 {
// 只为多设备且有邮箱的用户创建家庭组
if deviceCountByNewUID[uid] <= 1 || !hasEmailByNewUID[uid] {
continue
}
@ -753,55 +770,58 @@ func main() {
}
}
// 4. 查找原用户的家庭组(如果不存在则创建,虽然理论上 Step 8 已经为多设备用户创建了)
var family UserFamily
if err := tx.Where("owner_user_id = ?", s.OwnerUID).First(&family).Error; err != nil {
if err == gorm.ErrRecordNotFound {
// 补救措施:为该用户创建一个家庭组
family = UserFamily{
OwnerUserId: s.OwnerUID,
MaxMembers: defaultFamilyMaxSize,
Status: 1,
CreatedAt: now,
UpdatedAt: now,
// 仅仅当原用户有邮箱时,才尝试将其加入家庭组(无邮箱的仅拆分为独立用户)
if hasEmailByNewUID[s.OwnerUID] {
// 4. 查找原用户的家庭组(如果不存在则创建,虽然理论上 Step 8 已经为多设备用户创建了)
var family UserFamily
if err := tx.Where("owner_user_id = ?", s.OwnerUID).First(&family).Error; err != nil {
if err == gorm.ErrRecordNotFound {
// 补救措施:为该用户创建一个家庭组
family = UserFamily{
OwnerUserId: s.OwnerUID,
MaxMembers: defaultFamilyMaxSize,
Status: 1,
CreatedAt: now,
UpdatedAt: now,
}
if err := tx.Create(&family).Error; err != nil {
return fmt.Errorf("创建家庭组补救失败(owner=%d): %w", s.OwnerUID, err)
}
// 创建家主成员
ownerMember := UserFamilyMember{
FamilyId: family.Id,
UserId: s.OwnerUID,
Role: familyRoleOwner,
Status: 1,
JoinSource: "migration_split_recovery",
JoinedAt: now,
CreatedAt: now,
UpdatedAt: now,
}
if err := tx.Create(&ownerMember).Error; err != nil {
return fmt.Errorf("创建家主成员补救失败(owner=%d): %w", s.OwnerUID, err)
}
familyCount++ // 更新计数器
} else {
return fmt.Errorf("查找家庭组失败(owner=%d): %w", s.OwnerUID, err)
}
if err := tx.Create(&family).Error; err != nil {
return fmt.Errorf("创建家庭组补救失败(owner=%d): %w", s.OwnerUID, err)
}
// 创建家主成员
ownerMember := UserFamilyMember{
FamilyId: family.Id,
UserId: s.OwnerUID,
Role: familyRoleOwner,
Status: 1,
JoinSource: "migration_split_recovery",
JoinedAt: now,
CreatedAt: now,
UpdatedAt: now,
}
if err := tx.Create(&ownerMember).Error; err != nil {
return fmt.Errorf("创建家主成员补救失败(owner=%d): %w", s.OwnerUID, err)
}
familyCount++ // 更新计数器
} else {
return fmt.Errorf("查找家庭组失败(owner=%d): %w", s.OwnerUID, err)
}
}
// 5. 加入家庭组
member := UserFamilyMember{
FamilyId: family.Id,
UserId: newUser.Id,
Role: familyRoleMember,
Status: 1,
JoinSource: "migration_split",
JoinedAt: s.Device.CreatedAt,
CreatedAt: now,
UpdatedAt: now,
}
if err := tx.Create(&member).Error; err != nil {
return fmt.Errorf("添加家庭成员失败: %w", err)
// 5. 加入家庭组
member := UserFamilyMember{
FamilyId: family.Id,
UserId: newUser.Id,
Role: familyRoleMember,
Status: 1,
JoinSource: "migration_split",
JoinedAt: s.Device.CreatedAt,
CreatedAt: now,
UpdatedAt: now,
}
if err := tx.Create(&member).Error; err != nil {
return fmt.Errorf("添加家庭成员失败: %w", err)
}
}
splitCount++

View File

@ -15,16 +15,17 @@
| 2026-03-11 | 提供今天所有 ERROR 报错指令 | [x] 已完成 | 已提供根据日期过滤 ERROR 的命令 |
| 2026-03-12 | 分析并确认 Unknown column 错误 | [x] 已完成 | 确认为 `user_device` 缺少 `short_code` 字段,已提供 SQL |
| 2026-03-12 | 提供 SSL 证书替换指令 | [x] 已完成 | 已提供备份与替换证书的组合指令 |
| 2026-03-17 | 合并 internal 到 internal/main | [x] 已完成 | 已查验均为fast-forward受限网络/权限需手动push完成合并 |
certbot certonly --manual --preferred-challenges dns -d airoport.win -d "*.airoport.win" -d hifastapp.com
gunzip -c mysql_dump_20260317_145137.sql.gz \
gunzip -c mysql_dump_20260318_052811.sql.gz \
| docker exec -i ppanel-mysql mysql -uroot -pjpcV41ppanel
go run scripts/migrate_paid_users.go -src 'root:rootpassword@tcp(127.0.0.1:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' -dst 'root:jpcV41ppanel@tcp(154.12.35.103:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' -clean
go run scripts/migrate_paid_users.go -src 'root:rootpassword@tcp(127.0.0.1:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' -dst 'root:jpcV41ppanel@tcp(103.150.215.44:3306)/hifast?charset=utf8mb4&parseTime=True&loc=Local' -clean