Implement friend APIs and target profile support

- Add friend search/list/request/requestCancel/response/expel handlers and schemas
- Store friend relationships in user_friend and backfill default friend links at startup
- Auto-add the default user as a friend for newly created accounts
- Support target user_id in /api profile requests and return target profile data
- Make profile accessory_info optional to avoid zero-value accessory payloads
- Update login topInfo friend counters from friend relationship state

Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
2026-06-05 01:54:59 +08:00
parent ec2c5a6dac
commit d48293be44
28 changed files with 1653 additions and 159 deletions
+2 -2
View File
@@ -76,7 +76,7 @@ func api(ctx *gin.Context) {
case "livese":
result, err = livese.LiveSeApi(v.Action)
case "login":
result, err = login.LoginApi(v.Action)
result, err = login.LoginApi(ctx, v.Action)
case "marathon":
result, err = marathon.MarathonApi(v.Action)
case "multiunit":
@@ -90,7 +90,7 @@ func api(ctx *gin.Context) {
case "payment":
result, err = payment.PaymentApi(v.Action)
case "profile":
result, err = profile.ProfileApi(ctx, v.Action)
result, err = profile.ProfileApi(ctx, v.Action, v.UserID)
case "scenario":
result, err = scenario.ScenarioApi(ctx, v.Action)
case "stamp":
+4 -2
View File
@@ -2,12 +2,14 @@ package login
import (
"fmt"
"github.com/gin-gonic/gin"
)
func LoginApi(action string) (res any, err error) {
func LoginApi(ctx *gin.Context, action string) (res any, err error) {
switch action {
case "topInfo":
res, err = loginTopInfo()
res, err = loginTopInfo(ctx)
case "topInfoOnce":
res, err = loginTopInfoOnce()
default:
+35 -8
View File
@@ -1,25 +1,52 @@
package login
import (
usermodel "honoka-chan/internal/model/user"
loginapischema "honoka-chan/internal/schema/api/login"
"honoka-chan/internal/session"
"time"
"github.com/gin-gonic/gin"
)
func loginTopInfo() (res any, err error) {
func loginTopInfo(ctx *gin.Context) (res any, err error) {
ss := session.Get(ctx)
now := time.Now()
friendsRequestCnt64, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("status = ?", usermodel.FriendStatusAwaitingApproval).
Where("is_new = ?", true).
Count()
if err != nil {
return nil, err
}
friendsApprovalWaitCnt64, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("status = ?", usermodel.FriendStatusPending).
Count()
if err != nil {
return nil, err
}
friendsRequestCnt := int(friendsRequestCnt64)
friendsApprovalWaitCnt := int(friendsApprovalWaitCnt64)
res = loginapischema.TopInfoResp{
Result: loginapischema.TopInfoData{
FriendActionCnt: 0,
FriendGreetCnt: 0,
FriendVarietyCnt: 0,
FriendNewCnt: 0,
FriendNewCnt: friendsRequestCnt + friendsApprovalWaitCnt,
PresentCnt: 0,
SecretBoxBadgeFlag: false,
ServerDatetime: time.Now().Format("2006-01-02 15:04:05"),
ServerTimestamp: time.Now().Unix(),
NoticeFriendDatetime: time.Now().Format("2006-01-02 15:04:05"),
ServerDatetime: now.Format("2006-01-02 15:04:05"),
ServerTimestamp: now.Unix(),
NoticeFriendDatetime: now.Format("2006-01-02 15:04:05"),
NoticeMailDatetime: "2000-01-01 12:00:00",
FriendsApprovalWaitCnt: 0,
FriendsRequestCnt: 0,
FriendsApprovalWaitCnt: friendsApprovalWaitCnt,
FriendsRequestCnt: friendsRequestCnt,
IsTodayBirthday: false,
LicenseInfo: loginapischema.LicenseInfo{
LicenseList: []any{},
@@ -37,7 +64,7 @@ func loginTopInfo() (res any, err error) {
},
Status: 200,
CommandNum: false,
TimeStamp: time.Now().Unix(),
TimeStamp: now.Unix(),
}
return res, err
+283 -62
View File
@@ -1,6 +1,7 @@
package profile
import (
"errors"
unitmodel "honoka-chan/internal/model/unit"
usermodel "honoka-chan/internal/model/user"
profileapischema "honoka-chan/internal/schema/api/profile"
@@ -11,73 +12,182 @@ import (
"github.com/gin-gonic/gin"
)
func profileInfo(ctx *gin.Context) (res any, err error) {
func profileInfo(ctx *gin.Context, targetUserID int) (res any, err error) {
ss := session.Get(ctx)
pref := ss.UserPref
displayUserID := pref.UserID
if inviteUserID, convErr := strconv.Atoi(pref.InviteCode); convErr == nil && inviteUserID > 0 {
displayUserID = inviteUserID
targetPref, err := getTargetUserPref(ss, targetUserID)
if err != nil {
return nil, err
}
targetUserID = targetPref.UserID
unitCount, err := ss.UserEng.Table(new(usermodel.UserUnitData)).
Where("user_id = ?", ss.UserID).Count()
Where("user_id = ?", targetUserID).
Count()
if err != nil {
return nil, err
}
unitData := unitmodel.UnitDataMap{}
_, err = ss.GetBasicUnitInfo().
Where("a.unit_owning_user_id = ?", pref.UnitOwningUserID).Get(&unitData)
lastLoginTime, err := getTargetLastLoginTime(ss, targetUserID)
if err != nil {
return nil, err
}
var accessoryOwningId, accessoryId, exp int
_, err = ss.UserEng.Table("user_accessory_wear").Where("unit_owning_user_id = ? AND user_id = ?", pref.UnitOwningUserID, ss.UserID).
Cols("accessory_owning_user_id").Get(&accessoryOwningId)
centerUnitOwningUserID, err := getMainDeckCenterUnitOwningUserID(ss, targetUserID, targetPref.UnitOwningUserID)
if err != nil {
return nil, err
}
_, err = ss.MainEng.Table("common_accessory_m").Where("accessory_owning_user_id = ?", accessoryOwningId).
Cols("accessory_id,exp").Get(&accessoryId, &exp)
if err != nil {
return nil, err
}
accessoryInfo := profileapischema.AccessoryInfo{
AccessoryOwningUserID: accessoryOwningId,
AccessoryID: accessoryId,
Exp: exp,
NextExp: 0,
Level: 8,
MaxLevel: 8,
RankUpCount: 4,
FavoriteFlag: true,
}
removeSkillIds := []int{}
err = ss.UserEng.Table("user_unit_skill_equip").Where("unit_owning_user_id = ? AND user_id = ?", pref.UnitOwningUserID, ss.UserID).
Cols("unit_removable_skill_id").Find(&removeSkillIds)
centerUnitInfo, centerRaw, err := getProfileCenterUnitInfo(ss, targetUserID, centerUnitOwningUserID, targetPref.AwardID)
if err != nil {
return nil, err
}
naviUnitInfo, naviRaw, err := getProfileNaviUnitInfo(ss, targetUserID, targetPref.UnitOwningUserID)
if err != nil {
return nil, err
}
if naviRaw != nil {
centerUnitInfo.Costume = profileapischema.Costume{
UnitID: naviRaw.UnitID,
IsRankMax: naviRaw.IsRankMax,
IsSigned: naviRaw.IsSigned,
}
} else if centerRaw != nil {
centerUnitInfo.Costume = profileapischema.Costume{
UnitID: centerRaw.UnitID,
IsRankMax: centerRaw.IsRankMax,
IsSigned: centerRaw.IsSigned,
}
}
res = profileapischema.InfoResp{
Result: profileapischema.InfoData{
UserInfo: profileapischema.UserInfo{
UserID: displayUserID,
Name: pref.UserName,
Level: pref.UserLevel,
UserID: targetPref.UserID,
Name: targetPref.UserName,
Level: targetPref.UserLevel,
CostMax: 100,
UnitMax: 5000,
EnergyMax: pref.EffectiveEnergyMax(),
EnergyMax: targetPref.EffectiveEnergyMax(),
FriendMax: 99,
UnitCnt: int(unitCount),
InviteCode: pref.InviteCode,
ElapsedTimeFromLogin: "14\u5c0f\u65f6\u524d",
Introduction: pref.UserDesc,
InviteCode: targetPref.InviteCode,
ElapsedTimeFromLogin: formatProfileElapsedTime(lastLoginTime),
Introduction: targetPref.UserDesc,
},
// TODO: 区分队伍中心卡片和看板卡片
CenterUnitInfo: profileapischema.CenterUnitInfo{
CenterUnitInfo: centerUnitInfo,
NaviUnitInfo: naviUnitInfo,
IsAlliance: false,
FriendStatus: 0,
SettingAwardID: targetPref.AwardID,
SettingBackgroundID: targetPref.BackgroundID,
},
Status: 200,
CommandNum: false,
TimeStamp: time.Now().Unix(),
}
return res, nil
}
func getTargetUserPref(ss *session.Session, targetUserID int) (usermodel.UserPref, error) {
if targetUserID <= 0 || targetUserID == ss.UserID {
return ss.UserPref, nil
}
pref := usermodel.UserPref{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).
Where("user_id = ?", targetUserID).
Get(&pref)
if err != nil {
return usermodel.UserPref{}, err
}
if !has {
return usermodel.UserPref{}, errors.New("user not found")
}
if pref.NeedsProfileMigration() {
pref.ApplyProfileDefaults()
_, err = ss.UserEng.Table(new(usermodel.UserPref)).
Where("user_id = ?", targetUserID).
Cols(usermodel.UserPrefProfileColumns()...).
Update(&pref)
if err != nil {
return usermodel.UserPref{}, err
}
}
return pref, nil
}
func getTargetLastLoginTime(ss *session.Session, targetUserID int) (int64, error) {
if targetUserID == ss.UserID {
user := usermodel.Users{}
has, err := ss.UserEng.Table(new(usermodel.Users)).
Where("user_id = ?", targetUserID).
Get(&user)
if err != nil {
return 0, err
}
if !has {
return 0, errors.New("user not found")
}
return user.LastLoginTime, nil
}
user := usermodel.Users{}
has, err := ss.UserEng.Table(new(usermodel.Users)).
Where("user_id = ?", targetUserID).
Get(&user)
if err != nil {
return 0, err
}
if !has {
return 0, errors.New("user not found")
}
return user.LastLoginTime, nil
}
func getMainDeckCenterUnitOwningUserID(ss *session.Session, targetUserID, fallback int) (int, error) {
var unitOwningUserID int
has, err := ss.UserEng.Table(new(usermodel.UserDeckUnit)).Alias("udu").
Join("LEFT", "user_deck ud", "ud.id = udu.user_deck_id").
Where("udu.user_id = ?", targetUserID).
Where("ud.main_flag = ?", 1).
Where("udu.position = ?", 5).
Cols("udu.unit_owning_user_id").
Get(&unitOwningUserID)
if err != nil {
return 0, err
}
if has && unitOwningUserID > 0 {
return unitOwningUserID, nil
}
return fallback, nil
}
func getProfileCenterUnitInfo(ss *session.Session, targetUserID, unitOwningUserID, awardID int) (profileapischema.CenterUnitInfo, *unitmodel.UnitDataMap, error) {
unitData, err := getTargetUnitData(ss, targetUserID, unitOwningUserID)
if err != nil {
return profileapischema.CenterUnitInfo{}, nil, err
}
if unitData == nil {
return profileapischema.CenterUnitInfo{}, nil, nil
}
accessoryInfo, err := getAccessoryInfo(ss, targetUserID, unitOwningUserID)
if err != nil {
return profileapischema.CenterUnitInfo{}, nil, err
}
removeSkillIDs, err := getRemovableSkillIDs(ss, targetUserID, unitOwningUserID)
if err != nil {
return profileapischema.CenterUnitInfo{}, nil, err
}
info := profileapischema.CenterUnitInfo{
UnitOwningUserID: unitData.UnitOwningUserID,
UnitID: unitData.UnitID,
Exp: unitData.Exp,
@@ -104,16 +214,33 @@ func profileInfo(ctx *gin.Context) (res any, err error) {
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
IsSkillLevelMax: unitData.IsSkillLevelMax,
SettingAwardID: pref.AwardID,
RemovableSkillIds: removeSkillIds,
SettingAwardID: awardID,
RemovableSkillIds: removeSkillIDs,
AccessoryInfo: accessoryInfo,
Costume: profileapischema.Costume{},
TotalSmile: unitData.Smile, // TODO: 加成计算
TotalCute: unitData.Cute, // 同上
TotalCool: unitData.Cool, // 同上
TotalSmile: unitData.Smile,
TotalCute: unitData.Cute,
TotalCool: unitData.Cool,
TotalHp: unitData.MaxHp,
},
NaviUnitInfo: profileapischema.NaviUnitInfo{
}
return info, unitData, nil
}
func getProfileNaviUnitInfo(ss *session.Session, targetUserID, unitOwningUserID int) (profileapischema.NaviUnitInfo, *unitmodel.UnitDataMap, error) {
unitData, err := getTargetUnitData(ss, targetUserID, unitOwningUserID)
if err != nil {
return profileapischema.NaviUnitInfo{}, nil, err
}
if unitData == nil {
return profileapischema.NaviUnitInfo{}, nil, nil
}
removeSkillIDs, err := getRemovableSkillIDs(ss, targetUserID, unitOwningUserID)
if err != nil {
return profileapischema.NaviUnitInfo{}, nil, err
}
info := profileapischema.NaviUnitInfo{
UnitOwningUserID: unitData.UnitOwningUserID,
UnitID: unitData.UnitID,
Exp: unitData.Exp,
@@ -131,28 +258,122 @@ func profileInfo(ctx *gin.Context) (res any, err error) {
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
FavoriteFlag: unitData.FavoriteFlag,
DisplayRank: unitData.DisplayRank,
IsRankMax: unitData.IsRankMax,
IsLoveMax: unitData.IsLoveMax,
IsLevelMax: unitData.IsLevelMax,
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
IsSkillLevelMax: unitData.IsSkillLevelMax,
IsRemovableSkillCapacityMax: unitData.IsRemovableSkillCapacityMax,
InsertDate: "2016-10-11 10:33:03",
TotalSmile: unitData.Smile, // TODO: 加成计算
TotalCute: unitData.Cute, // 同上
TotalCool: unitData.Cool, // 同上
TotalSmile: unitData.Smile,
TotalCute: unitData.Cute,
TotalCool: unitData.Cool,
TotalHp: unitData.MaxHp,
RemovableSkillIds: removeSkillIds,
},
IsAlliance: false,
FriendStatus: 0,
SettingAwardID: pref.AwardID,
SettingBackgroundID: pref.BackgroundID,
},
Status: 200,
CommandNum: false,
TimeStamp: time.Now().Unix(),
RemovableSkillIds: removeSkillIDs,
}
return res, err
return info, unitData, nil
}
func getTargetUnitData(ss *session.Session, targetUserID, unitOwningUserID int) (*unitmodel.UnitDataMap, error) {
if unitOwningUserID <= 0 {
return nil, nil
}
unitData := unitmodel.UnitDataMap{}
has, err := ss.GetBasicUnitInfo().
Where("a.user_id = ?", targetUserID).
Where("a.unit_owning_user_id = ?", unitOwningUserID).
Get(&unitData)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &unitData, nil
}
func getAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID int) (*profileapischema.AccessoryInfo, error) {
if unitOwningUserID <= 0 {
return nil, nil
}
var accessoryOwningID int
has, err := ss.UserEng.Table("user_accessory_wear").
Where("unit_owning_user_id = ? AND user_id = ?", unitOwningUserID, targetUserID).
Cols("accessory_owning_user_id").
Get(&accessoryOwningID)
if err != nil {
return nil, err
}
if !has || accessoryOwningID <= 0 {
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
}
if !has {
return nil, nil
}
return &profileapischema.AccessoryInfo{
AccessoryOwningUserID: accessoryOwningID,
AccessoryID: accessoryData.AccessoryID,
Exp: accessoryData.Exp,
NextExp: 0,
Level: 8,
MaxLevel: 8,
RankUpCount: 4,
FavoriteFlag: true,
}, nil
}
func getRemovableSkillIDs(ss *session.Session, targetUserID, unitOwningUserID int) ([]int, error) {
removeSkillIDs := []int{}
if unitOwningUserID <= 0 {
return removeSkillIDs, nil
}
err := ss.UserEng.Table("user_unit_skill_equip").
Where("unit_owning_user_id = ? AND user_id = ?", unitOwningUserID, targetUserID).
Cols("unit_removable_skill_id").
Find(&removeSkillIDs)
if err != nil {
return nil, err
}
return removeSkillIDs, nil
}
func formatProfileElapsedTime(ts int64) string {
if ts <= 0 {
return "刚刚"
}
delta := time.Since(time.Unix(ts, 0))
if delta < time.Minute {
return "刚刚"
}
if delta < time.Hour {
return timeDurationInt(delta.Minutes()) + "分钟前"
}
if delta < 24*time.Hour {
return timeDurationInt(delta.Hours()) + "小时前"
}
return timeDurationInt(delta.Hours()/24) + "天前"
}
func timeDurationInt(value float64) string {
return strconv.Itoa(int(value))
}
+2 -2
View File
@@ -6,14 +6,14 @@ import (
"github.com/gin-gonic/gin"
)
func ProfileApi(ctx *gin.Context, action string) (res any, err error) {
func ProfileApi(ctx *gin.Context, action string, targetUserID int) (res any, err error) {
switch action {
case "cardRanking":
res, err = cardRanking()
case "liveCnt":
res, err = liveCnt()
case "profileInfo":
res, err = profileInfo(ctx)
res, err = profileInfo(ctx, targetUserID)
default:
err = fmt.Errorf("unimplemented action: profile: %s", action)
}
+87
View File
@@ -0,0 +1,87 @@
package friend
import (
"errors"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/session"
"strconv"
"strings"
)
func resolveActualFriendUserID(ss *session.Session, requestedUserID int) (int, error) {
if requestedUserID <= 0 {
return 0, errors.New("invalid user_id")
}
pref := usermodel.UserPref{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).
Where("invite_code = ?", strconv.Itoa(requestedUserID)).
Cols("user_id").
Get(&pref)
if err != nil {
return 0, err
}
if has && pref.UserID > 0 {
return pref.UserID, nil
}
user := usermodel.Users{}
has, err = ss.UserEng.Table(new(usermodel.Users)).
Where("user_id = ?", requestedUserID).
Cols("user_id").
Get(&user)
if err != nil {
return 0, err
}
if !has || user.UserID <= 0 {
return 0, errors.New("user not found")
}
return user.UserID, nil
}
func areUsersFriends(ss *session.Session, userID, friendUserID int) (bool, error) {
forward, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", userID).
Where("friend_user_id = ?", friendUserID).
Where("status = ?", usermodel.FriendStatusApproved).
Exist()
if err != nil {
return false, err
}
if !forward {
return false, nil
}
return ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", friendUserID).
Where("friend_user_id = ?", userID).
Where("status = ?", usermodel.FriendStatusApproved).
Exist()
}
func resolveUserIDByInviteCode(ss *session.Session, inviteCode string) (int, error) {
digits := strings.Map(func(r rune) rune {
if r >= '0' && r <= '9' {
return r
}
return -1
}, inviteCode)
if digits == "" {
return 0, errors.New("invalid invite_code")
}
pref := usermodel.UserPref{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).
Where("invite_code = ?", digits).
Cols("user_id").
Get(&pref)
if err != nil {
return 0, err
}
if !has || pref.UserID <= 0 {
return 0, errors.New("user not found")
}
return pref.UserID, nil
}
+54
View File
@@ -0,0 +1,54 @@
package friend
import (
"honoka-chan/internal/middleware"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"github.com/gin-gonic/gin"
)
func expel(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.UserIDReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveActualFriendUserID(ss, req.UserID)
if ss.CheckErr(err) {
return
}
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("friend_user_id = ?", targetUserID).
Delete()
if ss.CheckErr(err) {
return
}
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", targetUserID).
Where("friend_user_id = ?", ss.UserID).
Delete()
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.ExpelResp{
ResponseData: []any{},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/expel", middleware.Common, expel)
}
+316
View File
@@ -0,0 +1,316 @@
package friend
import (
"errors"
"honoka-chan/internal/middleware"
unitmodel "honoka-chan/internal/model/unit"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type friendListRow struct {
FriendUserID int `xorm:"friend_user_id"`
Status int `xorm:"status"`
IsNew bool `xorm:"is_new"`
InsertDate int64 `xorm:"insert_date"`
UpdateDate int64 `xorm:"update_date"`
InviteCode string `xorm:"invite_code"`
UserName string `xorm:"user_name"`
UserLevel int `xorm:"user_level"`
UserDesc string `xorm:"user_desc"`
AwardID int `xorm:"award_id"`
LastLoginTime int64 `xorm:"last_login_time"`
CenterUnitOwningUserID int `xorm:"center_unit_owning_user_id"`
}
func list(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
listReq := friendschema.ListReq{}
err := honokautils.ParseRequestData(ctx, &listReq)
if ss.CheckErr(err) {
return
}
friendStatus, err := mapFriendListTypeToStatus(listReq.Type)
if ss.CheckErr(err) {
return
}
totalCount, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("status = ?", friendStatus).
Count()
if ss.CheckErr(err) {
return
}
page := max(listReq.Page, 0)
offset := page * usermodel.FriendListPageSize
rows := []friendListRow{}
err = ss.UserEng.Table(new(usermodel.UserFriend)).Alias("uf").
Join("LEFT", "user_pref up", "up.user_id = uf.friend_user_id").
Join("LEFT", "users u", "u.user_id = uf.friend_user_id").
Join("LEFT", "user_deck ud", "ud.user_id = uf.friend_user_id AND ud.main_flag = 1").
Join("LEFT", "user_deck_unit udu", "udu.user_deck_id = ud.id AND udu.position = 5").
Join("LEFT", "common_unit_data cud", "cud.unit_id = udu.unit_id").
Where("uf.user_id = ?", ss.UserID).
Where("uf.status = ?", friendStatus).
Select(`
uf.friend_user_id,
uf.status,
uf.is_new,
uf.insert_date,
uf.update_date,
up.invite_code,
up.user_name,
up.user_level,
up.user_desc,
up.award_id,
u.last_login_time,
COALESCE(udu.unit_owning_user_id, up.unit_owning_user_id) AS center_unit_owning_user_id
`).
OrderBy(friendListOrderBy(listReq.Sort)).
Limit(usermodel.FriendListPageSize, offset).
Find(&rows)
if ss.CheckErr(err) {
return
}
friendList := make([]friendschema.FriendList, 0, len(rows))
for _, row := range rows {
item, err := buildFriendListItem(ss, row)
if ss.CheckErr(err) {
return
}
friendList = append(friendList, item)
}
if len(rows) > 0 {
switch friendStatus {
case usermodel.FriendStatusApproved, usermodel.FriendStatusAwaitingApproval:
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("status = ?", friendStatus).
Where("is_new = ?", true).
Cols("is_new", "update_date").
Update(&usermodel.UserFriend{
IsNew: false,
UpdateDate: time.Now().Unix(),
})
if ss.CheckErr(err) {
return
}
}
}
ss.Respond(friendschema.ListResp{
ResponseData: friendschema.ListData{
ItemCount: int(totalCount),
FriendList: friendList,
NewFriendList: []any{},
ServerTimestamp: time.Now().Unix(),
},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func buildFriendListItem(ss *session.Session, row friendListRow) (friendschema.FriendList, error) {
appliedAt := max(row.UpdateDate, row.InsertDate)
centerUnitInfo, err := buildCenterUnitInfo(ss, row.FriendUserID, row.CenterUnitOwningUserID, row.AwardID)
if err != nil {
return friendschema.FriendList{}, err
}
return friendschema.FriendList{
UserData: friendschema.UserData{
UserID: displayFriendUserID(row.InviteCode, row.FriendUserID),
Name: row.UserName,
Level: row.UserLevel,
ElapsedTimeFromLogin: formatElapsedTime(row.LastLoginTime),
ElapsedTimeFromApplied: formatElapsedTime(appliedAt),
Comment: row.UserDesc,
},
CenterUnitInfo: centerUnitInfo,
SettingAwardID: row.AwardID,
IsNew: row.IsNew,
}, nil
}
func buildCenterUnitInfo(ss *session.Session, userID, unitOwningUserID, awardID int) (friendschema.CenterUnitInfo, error) {
info := friendschema.CenterUnitInfo{
SettingAwardID: awardID,
RemovableSkillIds: []int{},
}
if unitOwningUserID <= 0 {
return info, nil
}
unitData := unitmodel.UnitDataMap{}
has, err := ss.GetBasicUnitInfo().
Where("a.user_id = ?", userID).
Where("a.unit_owning_user_id = ?", unitOwningUserID).
Get(&unitData)
if err != nil {
return info, err
}
if !has {
return info, nil
}
var accessoryInfo friendschema.AccessoryInfo
accessoryWear := usermodel.UserAccessoryWear{}
has, err = ss.UserEng.Table(new(usermodel.UserAccessoryWear)).
Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
Get(&accessoryWear)
if err != nil {
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,
}
}
removableSkillIDs := []int{}
err = ss.UserEng.Table(new(usermodel.UserUnitSkillEquip)).
Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
Cols("unit_removable_skill_id").
Find(&removableSkillIDs)
if err != nil {
return info, err
}
info = friendschema.CenterUnitInfo{
UnitOwningUserID: int64(unitData.UnitOwningUserID),
UnitID: unitData.UnitID,
Exp: unitData.Exp,
NextExp: 0,
Level: unitData.Level,
LevelLimitID: unitData.LevelLimitID,
MaxLevel: unitData.MaxLevel,
Rank: unitData.Rank,
MaxRank: unitData.MaxRank,
Love: unitData.Love,
MaxLove: unitData.MaxLove,
UnitSkillLevel: unitData.UnitSkillLevel,
MaxHp: unitData.MaxHp,
FavoriteFlag: unitData.FavoriteFlag,
DisplayRank: unitData.DisplayRank,
UnitSkillExp: unitData.UnitSkillExp,
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
Attribute: unitData.Attribute,
Smile: unitData.Smile,
Cute: unitData.Cute,
Cool: unitData.Cool,
IsLoveMax: unitData.IsLoveMax,
IsLevelMax: unitData.IsLevelMax,
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
IsSkillLevelMax: unitData.IsSkillLevelMax,
SettingAwardID: awardID,
RemovableSkillIds: removableSkillIDs,
AccessoryInfo: accessoryInfo,
}
return info, nil
}
func mapFriendListTypeToStatus(listType int) (int, error) {
switch listType {
case 0:
return usermodel.FriendStatusApproved, nil
case 1:
return usermodel.FriendStatusPending, nil
case 2:
return usermodel.FriendStatusAwaitingApproval, nil
default:
return 0, errors.New("invalid friend list type")
}
}
func friendListOrderBy(sort int) string {
switch sort {
case 1:
return "up.user_level ASC"
case 2:
return "up.user_level DESC"
case 3:
return "cud.smile ASC"
case 4:
return "cud.smile DESC"
case 5:
return "cud.cool ASC"
case 6:
return "cud.cool DESC"
case 7:
return "cud.cute ASC"
case 8:
return "cud.cute DESC"
case 9:
return "u.last_login_time ASC"
case 10:
return "u.last_login_time DESC"
case 11:
return "uf.insert_date ASC"
case 12:
return "uf.insert_date DESC"
default:
return "uf.insert_date DESC"
}
}
func displayFriendUserID(inviteCode string, fallback int) int {
if id, err := strconv.Atoi(inviteCode); err == nil && id > 0 {
return id
}
return fallback
}
func formatElapsedTime(ts int64) string {
if ts <= 0 {
return "刚刚"
}
delta := time.Since(time.Unix(ts, 0))
if delta < time.Minute {
return "刚刚"
}
if delta < time.Hour {
return strconv.Itoa(int(delta.Minutes())) + "分钟前"
}
if delta < 24*time.Hour {
return strconv.Itoa(int(delta.Hours())) + "小时前"
}
return strconv.Itoa(int(delta.Hours())/24) + "天前"
}
func init() {
router.AddHandler("main.php", "POST", "/friend/list", middleware.Common, list)
}
+103
View File
@@ -0,0 +1,103 @@
package friend
import (
"errors"
"honoka-chan/internal/middleware"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"github.com/gin-gonic/gin"
)
func request(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.UserIDReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveActualFriendUserID(ss, req.UserID)
if ss.CheckErr(err) {
return
}
if targetUserID == ss.UserID {
ss.CheckErr(errors.New("cannot add self as friend"))
return
}
isFriend, err := areUsersFriends(ss, ss.UserID, targetUserID)
if ss.CheckErr(err) {
return
}
if isFriend {
ss.Respond(friendschema.RequestResp{
ResponseData: friendschema.RequestData{
IsFriend: true,
},
ReleaseInfo: []any{},
StatusCode: 200,
})
return
}
reversePending, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", targetUserID).
Where("friend_user_id = ?", ss.UserID).
Where("status = ?", usermodel.FriendStatusPending).
Exist()
if ss.CheckErr(err) {
return
}
selfAwaitingApproval, err := ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("friend_user_id = ?", targetUserID).
Where("status = ?", usermodel.FriendStatusAwaitingApproval).
Exist()
if ss.CheckErr(err) {
return
}
if reversePending && selfAwaitingApproval {
err = usermodel.EnsureMutualFriendWithIsNew(ss.UserEng, ss.UserID, targetUserID, usermodel.FriendStatusApproved, true, true)
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.RequestResp{
ResponseData: friendschema.RequestData{
IsFriend: true,
},
ReleaseInfo: []any{},
StatusCode: 200,
})
return
}
err = usermodel.EnsureFriendLink(ss.UserEng, ss.UserID, targetUserID, usermodel.FriendStatusPending, false)
if ss.CheckErr(err) {
return
}
err = usermodel.EnsureFriendLink(ss.UserEng, targetUserID, ss.UserID, usermodel.FriendStatusAwaitingApproval, true)
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.RequestResp{
ResponseData: friendschema.RequestData{
IsFriend: false,
},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/request", middleware.Common, request)
}
+63
View File
@@ -0,0 +1,63 @@
package friend
import (
"honoka-chan/internal/middleware"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"github.com/gin-gonic/gin"
)
func requestCancel(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.UserIDReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveActualFriendUserID(ss, req.UserID)
if ss.CheckErr(err) {
return
}
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("friend_user_id = ?", targetUserID).
Where("status = ?", usermodel.FriendStatusPending).
Delete()
if ss.CheckErr(err) {
return
}
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", targetUserID).
Where("friend_user_id = ?", ss.UserID).
Where("status = ?", usermodel.FriendStatusAwaitingApproval).
Delete()
if ss.CheckErr(err) {
return
}
isFriend, err := areUsersFriends(ss, ss.UserID, targetUserID)
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.RequestCancelResp{
ResponseData: friendschema.RequestCancelData{
IsFriend: isFriend,
},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/requestCancel", middleware.Common, requestCancel)
}
+68
View File
@@ -0,0 +1,68 @@
package friend
import (
"errors"
"honoka-chan/internal/middleware"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"github.com/gin-gonic/gin"
)
func response(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.ResponseReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveActualFriendUserID(ss, req.UserID)
if ss.CheckErr(err) {
return
}
switch req.Status {
case 0:
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", ss.UserID).
Where("friend_user_id = ?", targetUserID).
Where("status = ?", usermodel.FriendStatusAwaitingApproval).
Delete()
if ss.CheckErr(err) {
return
}
_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
Where("user_id = ?", targetUserID).
Where("friend_user_id = ?", ss.UserID).
Where("status = ?", usermodel.FriendStatusPending).
Delete()
if ss.CheckErr(err) {
return
}
case 2:
err = usermodel.EnsureMutualFriendWithIsNew(ss.UserEng, ss.UserID, targetUserID, usermodel.FriendStatusApproved, true, true)
if ss.CheckErr(err) {
return
}
default:
ss.CheckErr(errors.New("invalid friend response status"))
return
}
ss.Respond(friendschema.ResponseResp{
ResponseData: []any{},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/response", middleware.Common, response)
}
+187
View File
@@ -0,0 +1,187 @@
package friend
import (
"errors"
"honoka-chan/internal/middleware"
unitmodel "honoka-chan/internal/model/unit"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"time"
"github.com/gin-gonic/gin"
)
type friendSearchRow struct {
UserID int `xorm:"user_id"`
InviteCode string `xorm:"invite_code"`
UserName string `xorm:"user_name"`
UserLevel int `xorm:"user_level"`
UserDesc string `xorm:"user_desc"`
EnergyMax int `xorm:"energy_max"`
AwardID int `xorm:"award_id"`
LastLoginTime int64 `xorm:"last_login_time"`
CenterUnitOwningUserID int `xorm:"center_unit_owning_user_id"`
NaviUnitOwningUserID int `xorm:"navi_unit_owning_user_id"`
}
func search(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.SearchReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveUserIDByInviteCode(ss, req.InviteCode)
if ss.CheckErr(err) {
return
}
row := friendSearchRow{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).Alias("up").
Join("LEFT", "users u", "u.user_id = up.user_id").
Join("LEFT", "user_deck ud", "ud.user_id = up.user_id AND ud.main_flag = 1").
Join("LEFT", "user_deck_unit udu", "udu.user_deck_id = ud.id AND udu.position = 5").
Select(`
up.user_id,
up.invite_code,
up.user_name,
up.user_level,
up.user_desc,
up.energy_max,
up.award_id,
u.last_login_time,
COALESCE(udu.unit_owning_user_id, up.unit_owning_user_id) AS center_unit_owning_user_id,
up.unit_owning_user_id AS navi_unit_owning_user_id
`).
Where("up.user_id = ?", targetUserID).
Get(&row)
if ss.CheckErr(err) {
return
}
if !has {
ss.CheckErr(errors.New("user not found"))
return
}
unitCount, err := ss.UserEng.Table(new(usermodel.UserUnitData)).
Where("user_id = ?", targetUserID).
Count()
if ss.CheckErr(err) {
return
}
centerUnitInfo, err := buildCenterUnitInfo(ss, targetUserID, row.CenterUnitOwningUserID, row.AwardID)
if ss.CheckErr(err) {
return
}
costume, err := getSearchCostume(ss, targetUserID, row.NaviUnitOwningUserID, centerUnitInfo)
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.SearchResp{
ResponseData: friendschema.SearchData{
UserInfo: friendschema.SearchUserInfo{
UserID: row.UserID,
Name: row.UserName,
Level: row.UserLevel,
CostMax: 100,
UnitMax: 5000,
EnergyMax: positiveIntOrDefault(row.EnergyMax, usermodel.DefaultUserEnergyMax),
FriendMax: 99,
UnitCnt: int(unitCount),
ElapsedTimeFromLogin: formatElapsedTime(row.LastLoginTime),
Comment: row.UserDesc,
},
CenterUnitInfo: toSearchCenterUnitInfo(centerUnitInfo, costume),
SettingAwardID: row.AwardID,
IsAlliance: false,
FriendStatus: 0,
ServerTimestamp: time.Now().Unix(),
},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/search", middleware.Common, search)
}
func positiveIntOrDefault(value, fallback int) int {
if value > 0 {
return value
}
return fallback
}
func toSearchCenterUnitInfo(info friendschema.CenterUnitInfo, costume friendschema.SearchCostume) friendschema.SearchCenterUnitInfo {
return friendschema.SearchCenterUnitInfo{
UnitOwningUserID: info.UnitOwningUserID,
UnitID: info.UnitID,
Exp: info.Exp,
NextExp: info.NextExp,
Level: info.Level,
LevelLimitID: info.LevelLimitID,
MaxLevel: info.MaxLevel,
Rank: info.Rank,
MaxRank: info.MaxRank,
Love: info.Love,
MaxLove: info.MaxLove,
UnitSkillLevel: info.UnitSkillLevel,
MaxHp: info.MaxHp,
FavoriteFlag: info.FavoriteFlag,
DisplayRank: info.DisplayRank,
UnitSkillExp: info.UnitSkillExp,
UnitRemovableSkillCapacity: info.UnitRemovableSkillCapacity,
Attribute: info.Attribute,
Smile: info.Smile,
Cute: info.Cute,
Cool: info.Cool,
IsLoveMax: info.IsLoveMax,
IsLevelMax: info.IsLevelMax,
IsRankMax: info.IsRankMax,
IsSigned: info.IsSigned,
IsSkillLevelMax: info.IsSkillLevelMax,
SettingAwardID: info.SettingAwardID,
RemovableSkillIds: info.RemovableSkillIds,
AccessoryInfo: info.AccessoryInfo,
Costume: costume,
}
}
func getSearchCostume(ss *session.Session, targetUserID, unitOwningUserID int, fallback friendschema.CenterUnitInfo) (friendschema.SearchCostume, error) {
costume := friendschema.SearchCostume{
UnitID: fallback.UnitID,
IsRankMax: fallback.IsRankMax,
IsSigned: fallback.IsSigned,
}
if unitOwningUserID <= 0 {
return costume, nil
}
unitData := unitmodel.UnitDataMap{}
has, err := ss.GetBasicUnitInfo().
Where("a.user_id = ?", targetUserID).
Where("a.unit_owning_user_id = ?", unitOwningUserID).
Get(&unitData)
if err != nil {
return costume, err
}
if !has {
return costume, nil
}
return friendschema.SearchCostume{
UnitID: unitData.UnitID,
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
}, nil
}
+10
View File
@@ -362,6 +362,16 @@ func addUser(dbSession *xorm.Session, phone, password string, isDefault bool) (g
}
}
if !isDefault {
err = usermodel.EnsureDefaultFriendship(dbSession, userID)
if err != nil {
if localSession {
dbSession.Rollback()
}
return loginData, loginCode, loginMsg, created, err
}
}
loginData.Autokey = autoKey
loginData.HasRealInfo = 1
loginData.Message = "ok"
+1
View File
@@ -8,6 +8,7 @@ import (
_ "honoka-chan/internal/handler/background"
_ "honoka-chan/internal/handler/download"
_ "honoka-chan/internal/handler/event"
_ "honoka-chan/internal/handler/friend"
_ "honoka-chan/internal/handler/gdpr"
_ "honoka-chan/internal/handler/ghome"
_ "honoka-chan/internal/handler/lbonus"
+107
View File
@@ -0,0 +1,107 @@
package usermodel
import (
"time"
"xorm.io/xorm"
)
const (
DefaultSystemPhone = "1"
FriendStatusApproved = iota
FriendStatusPending
FriendStatusAwaitingApproval
FriendListPageSize = 40
)
type UserFriend struct {
ID int `xorm:"id pk autoincr"`
UserID int `xorm:"user_id unique(friend_pair) index"`
FriendUserID int `xorm:"friend_user_id unique(friend_pair) index"`
Status int `xorm:"status index"`
IsNew bool `xorm:"is_new"`
InsertDate int64 `xorm:"insert_date index"`
UpdateDate int64 `xorm:"update_date"`
}
func (UserFriend) TableName() string {
return "user_friend"
}
func EnsureFriendLink(dbSession *xorm.Session, userID, friendUserID, status int, isNew bool) error {
if dbSession == nil || userID <= 0 || friendUserID <= 0 || userID == friendUserID {
return nil
}
now := time.Now().Unix()
link := UserFriend{}
has, err := dbSession.Table(new(UserFriend)).
Where("user_id = ? AND friend_user_id = ?", userID, friendUserID).
Get(&link)
if err != nil {
return err
}
if has {
update := UserFriend{
Status: status,
IsNew: isNew,
UpdateDate: now,
}
if link.InsertDate <= 0 {
update.InsertDate = now
}
_, err = dbSession.Table(new(UserFriend)).
Where("user_id = ? AND friend_user_id = ?", userID, friendUserID).
Cols("status", "is_new", "update_date", "insert_date").
Update(&update)
return err
}
_, err = dbSession.Table(new(UserFriend)).Insert(&UserFriend{
UserID: userID,
FriendUserID: friendUserID,
Status: status,
IsNew: isNew,
InsertDate: now,
UpdateDate: now,
})
return err
}
func EnsureMutualFriend(dbSession *xorm.Session, userID, friendUserID, status int) error {
if err := EnsureFriendLink(dbSession, userID, friendUserID, status, false); err != nil {
return err
}
return EnsureFriendLink(dbSession, friendUserID, userID, status, false)
}
func EnsureMutualFriendWithIsNew(dbSession *xorm.Session, userID, friendUserID, status int, userIsNew, friendIsNew bool) error {
if err := EnsureFriendLink(dbSession, userID, friendUserID, status, userIsNew); err != nil {
return err
}
return EnsureFriendLink(dbSession, friendUserID, userID, status, friendIsNew)
}
func EnsureDefaultFriendship(dbSession *xorm.Session, userID int) error {
if dbSession == nil || userID <= 0 {
return nil
}
defaultUser := Users{}
has, err := dbSession.Table(new(Users)).
Where("phone = ?", DefaultSystemPhone).
Cols("user_id").
Get(&defaultUser)
if err != nil {
return err
}
if !has || defaultUser.UserID <= 0 || defaultUser.UserID == userID {
return nil
}
return EnsureMutualFriend(dbSession, userID, defaultUser.UserID, FriendStatusApproved)
}
+1
View File
@@ -2,6 +2,7 @@ package apischema
type ApiReq struct {
Module string `json:"module"`
UserID int `json:"user_id"`
Action string `json:"action"`
Timestamp int64 `json:"timeStamp"`
}
+1 -1
View File
@@ -88,7 +88,7 @@ type CenterUnitInfo struct {
IsSkillLevelMax bool `json:"is_skill_level_max"`
SettingAwardID int `json:"setting_award_id"`
RemovableSkillIds []int `json:"removable_skill_ids"`
AccessoryInfo AccessoryInfo `json:"accessory_info"`
AccessoryInfo *AccessoryInfo `json:"accessory_info,omitempty"`
Costume Costume `json:"costume"`
TotalSmile int `json:"total_smile"`
TotalCute int `json:"total_cute"`
+10
View File
@@ -0,0 +1,10 @@
package friendschema
type UserIDReq struct {
UserID int `json:"user_id"`
}
type ResponseReq struct {
UserID int `json:"user_id"`
Status int `json:"status"`
}
+7
View File
@@ -0,0 +1,7 @@
package friendschema
type ExpelResp struct {
ResponseData []any `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+84
View File
@@ -0,0 +1,84 @@
package friendschema
type ListReq struct {
Module string `json:"module"`
Type int `json:"type"`
Action string `json:"action"`
TimeStamp int64 `json:"timeStamp"`
Mgd int `json:"mgd"`
Sort int `json:"sort"`
Page int `json:"page"`
CommandNum string `json:"commandNum"`
}
type UserData struct {
UserID int `json:"user_id"`
Name string `json:"name"`
Level int `json:"level"`
ElapsedTimeFromLogin string `json:"elapsed_time_from_login"`
ElapsedTimeFromApplied string `json:"elapsed_time_from_applied"`
Comment string `json:"comment"`
}
type AccessoryInfo struct {
AccessoryOwningUserID int `json:"accessory_owning_user_id"`
AccessoryID int `json:"accessory_id"`
Exp int `json:"exp"`
NextExp int `json:"next_exp"`
Level int `json:"level"`
MaxLevel int `json:"max_level"`
RankUpCount int `json:"rank_up_count"`
FavoriteFlag bool `json:"favorite_flag"`
}
type CenterUnitInfo struct {
UnitOwningUserID int64 `json:"unit_owning_user_id"`
UnitID int `json:"unit_id"`
Exp int `json:"exp"`
NextExp int `json:"next_exp"`
Level int `json:"level"`
LevelLimitID int `json:"level_limit_id"`
MaxLevel int `json:"max_level"`
Rank int `json:"rank"`
MaxRank int `json:"max_rank"`
Love int `json:"love"`
MaxLove int `json:"max_love"`
UnitSkillLevel int `json:"unit_skill_level"`
MaxHp int `json:"max_hp"`
FavoriteFlag bool `json:"favorite_flag"`
DisplayRank int `json:"display_rank"`
UnitSkillExp int `json:"unit_skill_exp"`
UnitRemovableSkillCapacity int `json:"unit_removable_skill_capacity"`
Attribute int `json:"attribute"`
Smile int `json:"smile"`
Cute int `json:"cute"`
Cool int `json:"cool"`
IsLoveMax bool `json:"is_love_max"`
IsLevelMax bool `json:"is_level_max"`
IsRankMax bool `json:"is_rank_max"`
IsSigned bool `json:"is_signed"`
IsSkillLevelMax bool `json:"is_skill_level_max"`
SettingAwardID int `json:"setting_award_id"`
RemovableSkillIds []int `json:"removable_skill_ids"`
AccessoryInfo AccessoryInfo `json:"accessory_info"`
}
type FriendList struct {
UserData UserData `json:"user_data"`
CenterUnitInfo CenterUnitInfo `json:"center_unit_info"`
SettingAwardID int `json:"setting_award_id"`
IsNew bool `json:"is_new"`
}
type ListData struct {
ItemCount int `json:"item_count"`
FriendList []FriendList `json:"friend_list"`
NewFriendList []any `json:"new_friend_list"`
ServerTimestamp int64 `json:"server_timestamp"`
}
type ListResp struct {
ResponseData ListData `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+11
View File
@@ -0,0 +1,11 @@
package friendschema
type RequestData struct {
IsFriend bool `json:"is_friend"`
}
type RequestResp struct {
ResponseData RequestData `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+11
View File
@@ -0,0 +1,11 @@
package friendschema
type RequestCancelData struct {
IsFriend bool `json:"is_friend"`
}
type RequestCancelResp struct {
ResponseData RequestCancelData `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+7
View File
@@ -0,0 +1,7 @@
package friendschema
type ResponseResp struct {
ResponseData []any `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+77
View File
@@ -0,0 +1,77 @@
package friendschema
type SearchReq struct {
Module string `json:"module"`
Action string `json:"action"`
TimeStamp int64 `json:"timeStamp"`
Mgd int `json:"mgd"`
InviteCode string `json:"invite_code"`
CommandNum string `json:"commandNum"`
}
type SearchUserInfo struct {
UserID int `json:"user_id"`
Name string `json:"name"`
Level int `json:"level"`
CostMax int `json:"cost_max"`
UnitMax int `json:"unit_max"`
EnergyMax int `json:"energy_max"`
FriendMax int `json:"friend_max"`
UnitCnt int `json:"unit_cnt"`
ElapsedTimeFromLogin string `json:"elapsed_time_from_login"`
Comment string `json:"comment"`
}
type SearchCostume struct {
UnitID int `json:"unit_id"`
IsRankMax bool `json:"is_rank_max"`
IsSigned bool `json:"is_signed"`
}
type SearchCenterUnitInfo struct {
UnitOwningUserID int64 `json:"unit_owning_user_id"`
UnitID int `json:"unit_id"`
Exp int `json:"exp"`
NextExp int `json:"next_exp"`
Level int `json:"level"`
LevelLimitID int `json:"level_limit_id"`
MaxLevel int `json:"max_level"`
Rank int `json:"rank"`
MaxRank int `json:"max_rank"`
Love int `json:"love"`
MaxLove int `json:"max_love"`
UnitSkillLevel int `json:"unit_skill_level"`
MaxHp int `json:"max_hp"`
FavoriteFlag bool `json:"favorite_flag"`
DisplayRank int `json:"display_rank"`
UnitSkillExp int `json:"unit_skill_exp"`
UnitRemovableSkillCapacity int `json:"unit_removable_skill_capacity"`
Attribute int `json:"attribute"`
Smile int `json:"smile"`
Cute int `json:"cute"`
Cool int `json:"cool"`
IsLoveMax bool `json:"is_love_max"`
IsLevelMax bool `json:"is_level_max"`
IsRankMax bool `json:"is_rank_max"`
IsSigned bool `json:"is_signed"`
IsSkillLevelMax bool `json:"is_skill_level_max"`
SettingAwardID int `json:"setting_award_id"`
RemovableSkillIds []int `json:"removable_skill_ids"`
AccessoryInfo AccessoryInfo `json:"accessory_info"`
Costume SearchCostume `json:"costume"`
}
type SearchData struct {
UserInfo SearchUserInfo `json:"user_info"`
CenterUnitInfo SearchCenterUnitInfo `json:"center_unit_info"`
SettingAwardID int `json:"setting_award_id"`
IsAlliance bool `json:"is_alliance"`
FriendStatus int `json:"friend_status"`
ServerTimestamp int64 `json:"server_timestamp"`
}
type SearchResp struct {
ResponseData SearchData `json:"response_data"`
ReleaseInfo []any `json:"release_info"`
StatusCode int `json:"status_code"`
}
+1
View File
@@ -27,6 +27,7 @@ func CreateTables() {
db.UserEng.Sync2(new(usermodel.UserLiveGoal))
db.UserEng.Sync2(new(usermodel.UserLiveInProgress))
db.UserEng.Sync2(new(usermodel.UserLiveRecord))
db.UserEng.Sync2(new(usermodel.UserFriend))
db.UserEng.Sync2(new(usermodel.UserPref))
db.UserEng.Sync2(new(usermodel.Users))
db.UserEng.Sync2(new(usermodel.UserUnit))
+3 -3
View File
@@ -2,16 +2,16 @@ package startup
import (
"honoka-chan/internal/handler/ghome/account"
usermodel "honoka-chan/internal/model/user"
"log"
)
const (
defaultPhone = "1"
defaultPassword = "klsbgames"
)
func CreateDefaultUser() {
_, code, msg, created, err := account.AddUser(defaultPhone, defaultPassword, true)
_, code, msg, created, err := account.AddUser(usermodel.DefaultSystemPhone, defaultPassword, true)
if err != nil {
log.Fatalln("默认用户创建失败:", err.Error())
}
@@ -19,7 +19,7 @@ func CreateDefaultUser() {
log.Fatalf("默认用户创建失败: code=%d msg=%s", code, msg)
}
if created {
log.Printf("默认用户创建成功, 账号: %s, 密码: %s\n", defaultPhone, defaultPassword)
log.Printf("默认用户创建成功, 账号: %s, 密码: %s\n", usermodel.DefaultSystemPhone, defaultPassword)
return
}
}
+38
View File
@@ -0,0 +1,38 @@
package startup
import (
usermodel "honoka-chan/internal/model/user"
"honoka-chan/pkg/db"
"log"
)
func EnsureDefaultFriends() {
session := db.UserEng.NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
log.Fatalln("初始化默认好友失败:", err.Error())
}
userIDs := []int{}
err := session.Table(new(usermodel.Users)).
Cols("user_id").
Find(&userIDs)
if err != nil {
session.Rollback()
log.Fatalln("初始化默认好友失败:", err.Error())
}
for _, userID := range userIDs {
err = usermodel.EnsureDefaultFriendship(session, userID)
if err != nil {
session.Rollback()
log.Fatalln("初始化默认好友失败:", err.Error())
}
}
if err := session.Commit(); err != nil {
session.Rollback()
log.Fatalln("初始化默认好友失败:", err.Error())
}
}
+1
View File
@@ -4,4 +4,5 @@ func StartUp() {
CreateTables()
LoadUnitData()
CreateDefaultUser()
EnsureDefaultFriends()
}