From cb0200ff298b7966568776cc6334b57a41a9aba0 Mon Sep 17 00:00:00 2001 From: Sean Du Date: Sat, 13 Jun 2026 07:34:17 +0800 Subject: [PATCH] Migrate accessories to user data - Add user_accessory as the per-user accessory inventory table. - Migrate old accessory wear records to user-owned accessory ids, including legacy common_accessory_m lookups for old installs. - Allocate default accessories for newly created users and update accessory reads to use user_accessory at runtime. - Validate accessory ownership when wearing accessories and keep existing accessory-based responses working. Signed-off-by: Sean Du --- internal/handler/api/profile/info.go | 18 +- internal/handler/api/unit/accessoryall.go | 2 +- internal/handler/friend/list.go | 29 ++- internal/handler/ghome/account/login.go | 8 + internal/handler/live/live.go | 40 ++++- internal/handler/notice/common.go | 18 +- internal/handler/ranking/live.go | 18 +- internal/handler/unit/wearaccessory.go | 14 ++ internal/model/user/useraccessory.go | 123 +++++++++++++ internal/session/accessory.go | 33 +++- internal/startup/database.go | 205 ++++++++++++++++++++++ 11 files changed, 436 insertions(+), 72 deletions(-) create mode 100644 internal/model/user/useraccessory.go diff --git a/internal/handler/api/profile/info.go b/internal/handler/api/profile/info.go index f5a4455..37da0a5 100644 --- a/internal/handler/api/profile/info.go +++ b/internal/handler/api/profile/info.go @@ -325,17 +325,7 @@ func getAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID int) ( return nil, nil } - accessoryData := struct { - AccessoryID int `xorm:"accessory_id"` - Exp int `xorm:"exp"` - }{} - has, err = ss.MainEng.Table("common_accessory_m"). - Where("accessory_owning_user_id = ?", accessoryOwningID). - Cols("accessory_id,exp"). - Get(&accessoryData) - if err != nil { - return nil, err - } + has, accessoryData := ss.GetAccessoryByAccessoryOwningUserID(accessoryOwningID) if !has { return nil, nil } @@ -345,9 +335,9 @@ func getAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID int) ( AccessoryID: accessoryData.AccessoryID, Exp: accessoryData.Exp, NextExp: 0, - Level: 8, - MaxLevel: 8, - RankUpCount: 4, + Level: accessoryData.MaxLevel, + MaxLevel: accessoryData.MaxLevel, + RankUpCount: accessoryData.MaxLevel - accessoryData.DefaultMaxLevel, FavoriteFlag: true, }, nil } diff --git a/internal/handler/api/unit/accessoryall.go b/internal/handler/api/unit/accessoryall.go index 3cc8411..2f3ec89 100644 --- a/internal/handler/api/unit/accessoryall.go +++ b/internal/handler/api/unit/accessoryall.go @@ -12,7 +12,7 @@ func unitAccessoryAll(ctx *gin.Context) (res any, err error) { ss := session.Get(ctx) accessoryList := []unitapischema.AccessoryList{} - err = ss.MainEng.Table("common_accessory_m").Find(&accessoryList) + err = ss.UserEng.Table("user_accessory").Where("user_id = ?", ss.UserID).Find(&accessoryList) if err != nil { return nil, err } diff --git a/internal/handler/friend/list.go b/internal/handler/friend/list.go index 8519f32..3b4c6b5 100644 --- a/internal/handler/friend/list.go +++ b/internal/handler/friend/list.go @@ -178,23 +178,18 @@ func buildCenterUnitInfo(ss *session.Session, userID, unitOwningUserID, awardID return info, err } if has && accessoryWear.AccessoryOwningUserID > 0 { - var accessoryID, exp int - _, err = ss.MainEng.Table("common_accessory_m"). - Where("accessory_owning_user_id = ?", accessoryWear.AccessoryOwningUserID). - Cols("accessory_id,exp"). - Get(&accessoryID, &exp) - if err != nil { - return info, err - } - accessoryInfo = friendschema.AccessoryInfo{ - AccessoryOwningUserID: accessoryWear.AccessoryOwningUserID, - AccessoryID: accessoryID, - Exp: exp, - NextExp: 0, - Level: 8, - MaxLevel: 8, - RankUpCount: 4, - FavoriteFlag: true, + hasAccessory, accessoryData := ss.GetAccessoryByAccessoryOwningUserID(accessoryWear.AccessoryOwningUserID) + if hasAccessory { + accessoryInfo = friendschema.AccessoryInfo{ + AccessoryOwningUserID: accessoryWear.AccessoryOwningUserID, + AccessoryID: accessoryData.AccessoryID, + Exp: accessoryData.Exp, + NextExp: 0, + Level: accessoryData.MaxLevel, + MaxLevel: accessoryData.MaxLevel, + RankUpCount: accessoryData.MaxLevel - accessoryData.DefaultMaxLevel, + FavoriteFlag: true, + } } } diff --git a/internal/handler/ghome/account/login.go b/internal/handler/ghome/account/login.go index 331ba27..6d70375 100644 --- a/internal/handler/ghome/account/login.go +++ b/internal/handler/ghome/account/login.go @@ -290,6 +290,14 @@ func addUser(dbSession *xorm.Session, phone, password string, isDefault bool) (g } return loginData, loginCode, loginMsg, created, err } + + err = usermodel.EnsureUserAccessories(dbSession, db.MainEng, userID, nil) + if err != nil { + if localSession { + dbSession.Rollback() + } + return loginData, loginCode, loginMsg, created, err + } } // 检查用户卡组配置 diff --git a/internal/handler/live/live.go b/internal/handler/live/live.go index ee6abf6..fc4b55f 100644 --- a/internal/handler/live/live.go +++ b/internal/handler/live/live.go @@ -47,6 +47,7 @@ type memberTagMatchKey struct { type accessoryBonusRow struct { AccessoryOwningUserID int `xorm:"accessory_owning_user_id"` + AccessoryID int `xorm:"accessory_id"` Smile float64 `xorm:"smile_max"` Pure float64 `xorm:"pure_max"` Cool float64 `xorm:"cool_max"` @@ -219,24 +220,51 @@ func loadAccessoryBonusMap(ss *session.Session, userID int, unitOwningUserIDs [] } rows := []accessoryBonusRow{} - err = ss.MainEng.Table("common_accessory_m"). - Join("LEFT", "accessory_m", "common_accessory_m.accessory_id = accessory_m.accessory_id"). - In("common_accessory_m.accessory_owning_user_id", accessoryIDs). - Cols("common_accessory_m.accessory_owning_user_id,smile_max,pure_max,cool_max"). + err = ss.UserEng.Table("user_accessory"). + In("accessory_owning_user_id", accessoryIDs). + Cols("accessory_owning_user_id,accessory_id"). Find(&rows) if err != nil { return nil, err } - accessoryMap := make(map[int]accessoryBonus, len(rows)) + accessoryIDSet := make(map[int]struct{}, len(rows)) for _, row := range rows { - accessoryMap[row.AccessoryOwningUserID] = accessoryBonus{ + accessoryIDSet[row.AccessoryID] = struct{}{} + } + + accessoryIDList := make([]int, 0, len(accessoryIDSet)) + for accessoryID := range accessoryIDSet { + accessoryIDList = append(accessoryIDList, accessoryID) + } + + staticRows := []accessoryBonusRow{} + err = ss.MainEng.Table("accessory_m"). + In("accessory_id", accessoryIDList). + Cols("accessory_id,smile_max,pure_max,cool_max"). + Find(&staticRows) + if err != nil { + return nil, err + } + + staticBonusMap := make(map[int]accessoryBonus, len(staticRows)) + for _, row := range staticRows { + staticBonusMap[row.AccessoryID] = accessoryBonus{ Smile: row.Smile, Pure: row.Pure, Cool: row.Cool, } } + accessoryMap := make(map[int]accessoryBonus, len(rows)) + for _, row := range rows { + bonus, ok := staticBonusMap[row.AccessoryID] + if !ok { + continue + } + accessoryMap[row.AccessoryOwningUserID] = bonus + } + result := make(map[int]accessoryBonus, len(unitToAccessoryID)) for unitOwningUserID, accessoryOwningUserID := range unitToAccessoryID { if bonus, ok := accessoryMap[accessoryOwningUserID]; ok { diff --git a/internal/handler/notice/common.go b/internal/handler/notice/common.go index 3f50b8e..6a48513 100644 --- a/internal/handler/notice/common.go +++ b/internal/handler/notice/common.go @@ -134,17 +134,7 @@ func getGreetingAccessoryInfo(ss *session.Session, userID, unitOwningUserID int) return nil, nil } - accessoryData := struct { - AccessoryID int `xorm:"accessory_id"` - Exp int `xorm:"exp"` - }{} - has, err = ss.MainEng.Table("common_accessory_m"). - Where("accessory_owning_user_id = ?", accessoryWear.AccessoryOwningUserID). - Cols("accessory_id,exp"). - Get(&accessoryData) - if err != nil { - return nil, err - } + has, accessoryData := ss.GetAccessoryByAccessoryOwningUserID(accessoryWear.AccessoryOwningUserID) if !has { return nil, nil } @@ -154,9 +144,9 @@ func getGreetingAccessoryInfo(ss *session.Session, userID, unitOwningUserID int) AccessoryID: accessoryData.AccessoryID, Exp: accessoryData.Exp, NextExp: 0, - Level: 8, - MaxLevel: 8, - RankUpCount: 4, + Level: accessoryData.MaxLevel, + MaxLevel: accessoryData.MaxLevel, + RankUpCount: accessoryData.MaxLevel - accessoryData.DefaultMaxLevel, FavoriteFlag: true, }, nil } diff --git a/internal/handler/ranking/live.go b/internal/handler/ranking/live.go index 66e8ca3..c602b1a 100644 --- a/internal/handler/ranking/live.go +++ b/internal/handler/ranking/live.go @@ -199,17 +199,7 @@ func getRankingAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID return nil, nil } - accessoryData := struct { - AccessoryID int `xorm:"accessory_id"` - Exp int `xorm:"exp"` - }{} - has, err = ss.MainEng.Table("common_accessory_m"). - Where("accessory_owning_user_id = ?", accessoryOwningID). - Cols("accessory_id,exp"). - Get(&accessoryData) - if err != nil { - return nil, err - } + has, accessoryData := ss.GetAccessoryByAccessoryOwningUserID(accessoryOwningID) if !has { return nil, nil } @@ -219,9 +209,9 @@ func getRankingAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID AccessoryID: accessoryData.AccessoryID, Exp: accessoryData.Exp, NextExp: 0, - Level: 8, - MaxLevel: 8, - RankUpCount: 4, + Level: accessoryData.MaxLevel, + MaxLevel: accessoryData.MaxLevel, + RankUpCount: accessoryData.MaxLevel - accessoryData.DefaultMaxLevel, FavoriteFlag: true, }, nil } diff --git a/internal/handler/unit/wearaccessory.go b/internal/handler/unit/wearaccessory.go index b0cd5c3..b38d585 100644 --- a/internal/handler/unit/wearaccessory.go +++ b/internal/handler/unit/wearaccessory.go @@ -1,7 +1,9 @@ package unit import ( + "errors" "honoka-chan/internal/middleware" + usermodel "honoka-chan/internal/model/user" "honoka-chan/internal/router" unitschema "honoka-chan/internal/schema/unit" "honoka-chan/internal/session" @@ -32,8 +34,20 @@ func wearAccessory(ctx *gin.Context) { } // 佩戴饰品 + var exists bool for _, v := range req.Wear { // fmt.Println("Wear:", v.AccessoryOwningUserID, v.UnitOwningUserID) + exists, err = ss.UserEng.Table(new(usermodel.UserAccessory)). + Where("user_id = ? AND accessory_owning_user_id = ?", ss.UserID, v.AccessoryOwningUserID). + Exist() + if ss.CheckErr(err) { + return + } + if !exists { + ss.CheckErr(errors.New("accessory not found for user")) + return + } + data := unitschema.WearAccessoryData{ AccessoryOwningUserID: v.AccessoryOwningUserID, UnitOwningUserID: v.UnitOwningUserID, diff --git a/internal/model/user/useraccessory.go b/internal/model/user/useraccessory.go new file mode 100644 index 0000000..119d73e --- /dev/null +++ b/internal/model/user/useraccessory.go @@ -0,0 +1,123 @@ +package usermodel + +import ( + "fmt" + "sort" + + "xorm.io/xorm" +) + +type UserAccessory struct { + AccessoryOwningUserID int `xorm:"accessory_owning_user_id pk autoincr"` + UserID int `xorm:"user_id index"` + AccessoryID int `xorm:"accessory_id index"` + Exp int `xorm:"exp"` +} + +func (UserAccessory) TableName() string { + return "user_accessory" +} + +type accessoryTemplateRow struct { + AccessoryID int `xorm:"accessory_id"` +} + +type userAccessoryCountRow struct { + AccessoryID int `xorm:"accessory_id"` + Count int `xorm:"cnt"` +} + +func LoadAccessoryTemplateIDs(mainSession xorm.Interface) ([]int, error) { + rows := []accessoryTemplateRow{} + err := mainSession.Table("accessory_m"). + Where("is_material = ? AND rarity = ?", 0, 4). + Cols("accessory_id"). + OrderBy("accessory_id ASC"). + Find(&rows) + if err != nil { + return nil, err + } + + ids := make([]int, 0, len(rows)) + for _, row := range rows { + ids = append(ids, row.AccessoryID) + } + return ids, nil +} + +func EnsureUserAccessories(userSession *xorm.Session, mainSession xorm.Interface, userID int, minCount map[int]int) error { + accessoryIDs, err := LoadAccessoryTemplateIDs(mainSession) + if err != nil { + return err + } + + existingRows := []userAccessoryCountRow{} + err = userSession.Table(new(UserAccessory)). + Select("accessory_id, COUNT(*) AS cnt"). + Where("user_id = ?", userID). + GroupBy("accessory_id"). + Find(&existingRows) + if err != nil { + return err + } + + existingCount := make(map[int]int, len(existingRows)) + for _, row := range existingRows { + existingCount[row.AccessoryID] = row.Count + } + + for _, accessoryID := range accessoryIDs { + targetCount := 1 + if count, ok := minCount[accessoryID]; ok && count > targetCount { + targetCount = count + } + + for existingCount[accessoryID] < targetCount { + _, err = userSession.Insert(&UserAccessory{ + UserID: userID, + AccessoryID: accessoryID, + Exp: 0, + }) + if err != nil { + return err + } + existingCount[accessoryID]++ + } + } + + return nil +} + +func BuildAccessoryOwningUserIDMap(userSession *xorm.Session, userID int) (map[int][]int, error) { + rows := []UserAccessory{} + err := userSession.Table(new(UserAccessory)). + Where("user_id = ?", userID). + OrderBy("accessory_id ASC, accessory_owning_user_id ASC"). + Find(&rows) + if err != nil { + return nil, err + } + + result := make(map[int][]int) + for _, row := range rows { + result[row.AccessoryID] = append(result[row.AccessoryID], row.AccessoryOwningUserID) + } + return result, nil +} + +func ResolveAccessoryOwningUserIDsByAccessoryID(owningIDs map[int][]int, requiredCounts map[int]int) (map[int][]int, error) { + result := make(map[int][]int, len(requiredCounts)) + for accessoryID, count := range requiredCounts { + ids := owningIDs[accessoryID] + if len(ids) < count { + return nil, fmt.Errorf("insufficient user accessories for accessory_id=%d need=%d have=%d", accessoryID, count, len(ids)) + } + result[accessoryID] = append([]int(nil), ids[:count]...) + } + + for accessoryID := range result { + sort.Ints(result[accessoryID]) + } + + return result, nil +} diff --git a/internal/session/accessory.go b/internal/session/accessory.go index 712aa0e..552cdbc 100644 --- a/internal/session/accessory.go +++ b/internal/session/accessory.go @@ -17,12 +17,11 @@ func (ss *Session) GetUserAccessoryWearByUnitOwningUserID(unitOwningUserID int) return has, &wearData } -func (ss *Session) GetAccessoryByAccessoryOwningUserID(accessoryOwningUserID int) (bool, *accessorymodel.Accessory) { - accessoryData := accessorymodel.Accessory{} - has, err := ss.MainEng.Table("common_accessory_m").Alias("a"). - Join("LEFT", "accessory_m", "a.accessory_id = accessory_m.accessory_id"). - Where("a.accessory_owning_user_id = ?", accessoryOwningUserID). - Cols("accessory_m.*,a.exp").Get(&accessoryData) +func (ss *Session) GetUserAccessoryByAccessoryOwningUserID(accessoryOwningUserID int) (bool, *usermodel.UserAccessory) { + accessoryData := usermodel.UserAccessory{} + has, err := ss.UserEng.Table(new(usermodel.UserAccessory)). + Where("accessory_owning_user_id = ?", accessoryOwningUserID). + Get(&accessoryData) if ss.CheckErr(err) { return false, nil } @@ -30,6 +29,28 @@ func (ss *Session) GetAccessoryByAccessoryOwningUserID(accessoryOwningUserID int return has, &accessoryData } +func (ss *Session) GetAccessoryByAccessoryOwningUserID(accessoryOwningUserID int) (bool, *accessorymodel.Accessory) { + has, userAccessoryData := ss.GetUserAccessoryByAccessoryOwningUserID(accessoryOwningUserID) + if !has { + return false, nil + } + + accessoryData := accessorymodel.Accessory{} + has, err := ss.MainEng.Table("accessory_m"). + Where("accessory_id = ?", userAccessoryData.AccessoryID). + Get(&accessoryData) + if ss.CheckErr(err) { + return false, nil + } + if !has { + return false, nil + } + + accessoryData.Exp = userAccessoryData.Exp + + return has, &accessoryData +} + func (ss *Session) GetUserAccessoryInfoByUnitOwningUserID(unitOwningUserID int) (bool, *liverecordschema.AccessoryInfo) { has, wearData := ss.GetUserAccessoryWearByUnitOwningUserID(unitOwningUserID) if !has { diff --git a/internal/startup/database.go b/internal/startup/database.go index 06f84d5..03e8fe8 100644 --- a/internal/startup/database.go +++ b/internal/startup/database.go @@ -22,6 +22,7 @@ func CreateTables() { // db.UserEng.ShowSQL(true) db.UserEng.Sync2(new(ghomemodel.DeviceKey)) db.UserEng.Sync2(new(loginmodel.AuthKey)) + db.UserEng.Sync2(new(usermodel.UserAccessory)) db.UserEng.Sync2(new(usermodel.UserAccessoryWear)) db.UserEng.Sync2(new(usermodel.UserDeck)) db.UserEng.Sync2(new(usermodel.UserDeckUnit)) @@ -39,6 +40,7 @@ func CreateTables() { db.UserEng.Sync2(new(usermodel.UserUnitSkillEquip)) MigrateUserPref() + MigrateUserAccessories() MigrateUserLiveData() } @@ -67,6 +69,209 @@ func MigrateUserPref() { } } +type accessoryOwningMapRow struct { + AccessoryOwningUserID int `xorm:"accessory_owning_user_id"` + AccessoryID int `xorm:"accessory_id"` +} + +func MigrateUserAccessories() { + session := db.UserEng.NewSession() + defer session.Close() + + if err := session.Begin(); err != nil { + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + userIDs := []int{} + if err := session.Table(new(usermodel.Users)).Cols("user_id").Find(&userIDs); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + templateAccessoryIDs, err := usermodel.LoadAccessoryTemplateIDs(db.MainEng) + if err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + legacyAccessoryMap := map[int]int{} + hasLegacyTable, err := db.MainEng.IsTableExist("common_accessory_m") + if err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + if hasLegacyTable { + legacyRows := []accessoryOwningMapRow{} + if err := db.MainEng.Table("common_accessory_m"). + Cols("accessory_owning_user_id,accessory_id"). + Find(&legacyRows); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + for _, row := range legacyRows { + legacyAccessoryMap[row.AccessoryOwningUserID] = row.AccessoryID + } + } + + wearRows := []usermodel.UserAccessoryWear{} + if err := session.Table(new(usermodel.UserAccessoryWear)).Find(&wearRows); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + existingAccessories := []usermodel.UserAccessory{} + if err := session.Table(new(usermodel.UserAccessory)).Find(&existingAccessories); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + currentAccessoryMap := make(map[int]map[int]int) + for _, row := range existingAccessories { + if currentAccessoryMap[row.UserID] == nil { + currentAccessoryMap[row.UserID] = map[int]int{} + } + currentAccessoryMap[row.UserID][row.AccessoryOwningUserID] = row.AccessoryID + } + + requiredCountsByUser := make(map[int]map[int]int, len(userIDs)) + for _, row := range wearRows { + accessoryID, ok := resolveAccessoryIDForWear(currentAccessoryMap, legacyAccessoryMap, templateAccessoryIDs, row.UserID, row.AccessoryOwningUserID) + if !ok { + continue + } + if requiredCountsByUser[row.UserID] == nil { + requiredCountsByUser[row.UserID] = map[int]int{} + } + requiredCountsByUser[row.UserID][accessoryID]++ + } + + for _, userID := range userIDs { + if err := usermodel.EnsureUserAccessories(session, db.MainEng, userID, requiredCountsByUser[userID]); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + } + + updatedAccessories := []usermodel.UserAccessory{} + if err := session.Table(new(usermodel.UserAccessory)). + OrderBy("user_id ASC, accessory_id ASC, accessory_owning_user_id ASC"). + Find(&updatedAccessories); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + + currentAccessoryMap = make(map[int]map[int]int) + availableOwningIDs := make(map[int]map[int][]int) + for _, row := range updatedAccessories { + if currentAccessoryMap[row.UserID] == nil { + currentAccessoryMap[row.UserID] = map[int]int{} + } + if availableOwningIDs[row.UserID] == nil { + availableOwningIDs[row.UserID] = map[int][]int{} + } + currentAccessoryMap[row.UserID][row.AccessoryOwningUserID] = row.AccessoryID + availableOwningIDs[row.UserID][row.AccessoryID] = append(availableOwningIDs[row.UserID][row.AccessoryID], row.AccessoryOwningUserID) + } + + usedOwningIDs := make(map[int]map[int]struct{}) + for _, row := range wearRows { + if currentAccessoryMap[row.UserID] == nil { + continue + } + if _, ok := currentAccessoryMap[row.UserID][row.AccessoryOwningUserID]; !ok { + continue + } + if usedOwningIDs[row.UserID] == nil { + usedOwningIDs[row.UserID] = map[int]struct{}{} + } + usedOwningIDs[row.UserID][row.AccessoryOwningUserID] = struct{}{} + } + + for i := range wearRows { + row := wearRows[i] + if row.AccessoryOwningUserID <= 0 { + continue + } + if currentAccessoryMap[row.UserID] != nil { + if _, ok := currentAccessoryMap[row.UserID][row.AccessoryOwningUserID]; ok { + continue + } + } + + accessoryID, ok := resolveAccessoryIDForWear(currentAccessoryMap, legacyAccessoryMap, templateAccessoryIDs, row.UserID, row.AccessoryOwningUserID) + if !ok { + continue + } + + candidates := availableOwningIDs[row.UserID][accessoryID] + if len(candidates) == 0 { + continue + } + + if usedOwningIDs[row.UserID] == nil { + usedOwningIDs[row.UserID] = map[int]struct{}{} + } + + newOwningID := 0 + for _, candidate := range candidates { + if _, used := usedOwningIDs[row.UserID][candidate]; used { + continue + } + newOwningID = candidate + break + } + if newOwningID == 0 { + newOwningID = candidates[0] + } + + if _, err := session.Table(new(usermodel.UserAccessoryWear)). + ID(row.ID). + Cols("accessory_owning_user_id"). + Update(&usermodel.UserAccessoryWear{AccessoryOwningUserID: newOwningID}); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } + wearRows[i].AccessoryOwningUserID = newOwningID + usedOwningIDs[row.UserID][newOwningID] = struct{}{} + } + + if err := session.Commit(); err != nil { + session.Rollback() + log.Fatalln("迁移 user_accessory 失败:", err.Error()) + } +} + +func resolveAccessoryIDForWear(currentAccessoryMap map[int]map[int]int, legacyAccessoryMap map[int]int, accessoryIDs []int, userID int, accessoryOwningUserID int) (int, bool) { + if accessoryOwningUserID <= 0 { + return 0, false + } + + if currentAccessoryMap[userID] != nil { + if accessoryID, ok := currentAccessoryMap[userID][accessoryOwningUserID]; ok { + return accessoryID, true + } + } + + if accessoryID, ok := legacyAccessoryMap[accessoryOwningUserID]; ok { + return accessoryID, true + } + + return legacyAccessoryOwningUserIDToAccessoryID(accessoryIDs, accessoryOwningUserID) +} + +func legacyAccessoryOwningUserIDToAccessoryID(accessoryIDs []int, accessoryOwningUserID int) (int, bool) { + if accessoryOwningUserID <= 0 { + return 0, false + } + + index := (accessoryOwningUserID - 1) / 9 + if index < 0 || index >= len(accessoryIDs) { + return 0, false + } + + return accessoryIDs[index], true +} + type liveGoalRewardMigrationRow struct { LiveGoalRewardID int `xorm:"live_goal_reward_id"` LiveDifficultyID int `xorm:"live_difficulty_id"`