Files
backend/internal/model/friends.go
lan f2b02682c2
Some checks failed
Build / build-docker (push) Has been cancelled
Build / build (push) Has been cancelled
feat(yggdrasil): 实现好友系统与 profiles 查询接口
按 MinecraftServices 文档实现好友系四组接口与 profiles 服务:

好友系统(/api/yggdrasil/minecraftservices/*)
- Friends(1.3):列表查询、ADD/REMOVE 操作(发请求/接受/拒绝/撤回/删除)
  - 状态枚举+单向记录模型,接受请求时合并两方向避免好友列表重复
- Player Attributes(1.2):friendsPreferences / 脏词过滤 / 聊天偏好读写
  - Upsert 用 map 显式写字段规避 gorm 对带 default 零值布尔字段的忽略
- Presence(1.1):Redis key+TTL 上报与好友在线状态批量查询
- Blocklist(1.6):屏蔽列表查询(含 120s Redis 缓存),Block/Unblock 内部方法

Profiles 服务
- getManyByName(2.1):POST /api/profiles/minecraft,返回 [{id,name}]
  - toLowerCase 规范化、去重、空名过滤、maxBatch=10 超限拒绝
- getByName(2.2):GET /api/users/profiles/minecraft/:name,返回 {id,name},未找到返回 404

路由与基础设施
- 路由按官方 host 前缀一一转发
  - api.mojang.com/* -> /api/yggdrasil/api/*
  - api.minecraftservices.com/* -> /api/yggdrasil/minecraftservices/*
- 新增 Friend/PlayerAttributes 模型与 AutoMigrate 注册
- 新增 FriendsService 与 Container 装配
- 抽 extractBearerToken helper,统一 MinecraftServices 错误响应

修复
- texture_service: ToggleFavorite 在 db 为 nil 时降级非事务执行
  修复 TestTextureServiceImpl_ToggleFavorite 空指针 panic
- texture_service_test: UploadTexture 用例改用真实 SHA-256 命中 mock
  修复 4 个子用例因文件大小/Hash 不匹配的失败
- profile_repository: GetByNames/FindByName 改 LOWER() 大小写不敏感
  与客户端 toLowerCase 规范化对齐

测试
- 好友模型、ADD/REMOVE/接受、属性、屏蔽、Bearer 解析单测全绿
- profiles 查询规范化、去重、超限、大小写不敏感单测全绿
- go build ./... && go test ./... 全部通过
2026-07-09 20:44:41 +08:00

65 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import "time"
// FriendsStatus 好友关系状态
// @Description 好友关系状态枚举
type FriendsStatus string
const (
// FriendsStatusPending 待处理(已发出请求,对方尚未接受)
FriendsStatusPending FriendsStatus = "pending"
// FriendsStatusAccepted 已互相成为好友
FriendsStatusAccepted FriendsStatus = "accepted"
// FriendsStatusBlocked 屏蔽requester 屏蔽 target单向
FriendsStatusBlocked FriendsStatus = "blocked"
)
// Friend 好友/屏蔽关系记录
// @Description 玩家好友与屏蔽关系数据模型
//
// 设计说明:
// - 采用状态枚举+单条记录模型,区分 pending/accepted/blocked。
// - 屏蔽blocked复用本表requester 为屏蔽发起者target 为被屏蔽者。
// - 好友关系在 accepted 状态下,仍以 requester->target 单条记录表示;
// 当 B 接受 A 的请求时,若不存在 B->A 记录则补建一条 accepted 记录,
// 从而双方在 ListFriends 中都能看到对方。
type Friend struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
RequesterUUID string `gorm:"column:requester_uuid;type:varchar(32);not null;uniqueIndex:uk_friend_pair,priority:1;index:idx_friend_requester_status,priority:1" json:"requester_uuid"`
TargetUUID string `gorm:"column:target_uuid;type:varchar(32);not null;uniqueIndex:uk_friend_pair,priority:2;index:idx_friend_target_status,priority:1" json:"target_uuid"`
Status FriendsStatus `gorm:"column:status;type:varchar(16);not null;default:'pending';index:idx_friend_requester_status,priority:2;index:idx_friend_target_status,priority:2" json:"status"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
}
// TableName 指定表名
func (Friend) TableName() string { return "friends" }
// PlayerAttributes 玩家好友/聊天偏好属性
// @Description 玩家偏好属性数据模型(对应文档 friendsPreferences / profanityFilterPreferences / chatPreferences
type PlayerAttributes struct {
ProfileUUID string `gorm:"column:profile_uuid;type:varchar(32);primaryKey" json:"profile_uuid"`
FriendsEnabled bool `gorm:"column:friends_enabled;not null;default:true" json:"friends_enabled"`
AcceptInvites bool `gorm:"column:accept_invites;not null;default:true" json:"accept_invites"`
ProfanityFilterOn bool `gorm:"column:profanity_filter_on;not null;default:true" json:"profanity_filter_on"`
// TextCommunication 聊天文本通信偏好ENABLED / FRIENDS_ONLY / DISABLED
TextCommunication string `gorm:"column:text_communication;type:varchar(16);not null;default:'ENABLED'" json:"text_communication"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
}
// TableName 指定表名
func (PlayerAttributes) TableName() string { return "player_attributes" }
// DefaultPlayerAttributes 返回某 profile 的默认属性(数据库无记录时使用)
func DefaultPlayerAttributes(profileUUID string) PlayerAttributes {
return PlayerAttributes{
ProfileUUID: profileUUID,
FriendsEnabled: true,
AcceptInvites: true,
ProfanityFilterOn: true,
TextCommunication: "ENABLED",
}
}