feat: 更新SproutGate前后端代码
This commit is contained in:
55
sproutgate-backend/internal/models/qq_avatar.go
Normal file
55
sproutgate-backend/internal/models/qq_avatar.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package models
|
||||
|
||||
import "strings"
|
||||
|
||||
func qqNumericLocalPart(email string) string {
|
||||
e := strings.TrimSpace(strings.ToLower(email))
|
||||
if e == "" {
|
||||
return ""
|
||||
}
|
||||
at := strings.LastIndex(e, "@")
|
||||
if at <= 0 || at >= len(e)-1 {
|
||||
return ""
|
||||
}
|
||||
local, domain := e[:at], e[at+1:]
|
||||
if domain != "qq.com" {
|
||||
return ""
|
||||
}
|
||||
if local == "" {
|
||||
return ""
|
||||
}
|
||||
for _, r := range local {
|
||||
if r < '0' || r > '9' {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return local
|
||||
}
|
||||
|
||||
func firstQQUINFromRecord(u UserRecord) string {
|
||||
if q := qqNumericLocalPart(u.Email); q != "" {
|
||||
return q
|
||||
}
|
||||
for _, em := range u.SecondaryEmails {
|
||||
if q := qqNumericLocalPart(em); q != "" {
|
||||
return q
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func qqAvatarHeadimgURL(uin string) string {
|
||||
return "https://q.qlogo.cn/headimg_dl?dst_uin=" + uin + "&spec=640&img_type=jpg"
|
||||
}
|
||||
|
||||
// ResolvedAvatarURL 返回用户自选头像;若未设置但主邮箱或辅助邮箱为「纯数字 @qq.com」,
|
||||
// 则返回 QQ 头像 CDN 地址(与手动填写该链接等价,不入库)。
|
||||
func (u UserRecord) ResolvedAvatarURL() string {
|
||||
if s := strings.TrimSpace(u.AvatarURL); s != "" {
|
||||
return s
|
||||
}
|
||||
if uin := firstQQUINFromRecord(u); uin != "" {
|
||||
return qqAvatarHeadimgURL(uin)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -30,6 +30,7 @@ type UserRecord struct {
|
||||
Banned bool `json:"banned"`
|
||||
BanReason string `json:"banReason,omitempty"`
|
||||
BannedAt string `json:"bannedAt,omitempty"`
|
||||
TokenEpoch int64 `json:"-"` // 递增使用户此前签发的 JWT 全局失效;不对外 JSON 序列化
|
||||
AuthClients []AuthClientEntry `json:"authClients,omitempty"`
|
||||
}
|
||||
|
||||
@@ -52,6 +53,7 @@ type UserPublic struct {
|
||||
SecondaryEmails []string `json:"secondaryEmails,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
AvatarURL string `json:"avatarUrl,omitempty"`
|
||||
CustomAvatarURL string `json:"customAvatarUrl,omitempty"` // 用户自选头像链接;为空时 avatarUrl 可能为 QQ 邮箱推断头像
|
||||
WebsiteURL string `json:"websiteUrl,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
@@ -85,13 +87,14 @@ func (u UserRecord) Public() UserPublic {
|
||||
LastVisitAt: lastVisitAt,
|
||||
VisitDays: visitDays,
|
||||
VisitStreak: visitStreak,
|
||||
SecondaryEmails: u.SecondaryEmails,
|
||||
Phone: u.Phone,
|
||||
AvatarURL: u.AvatarURL,
|
||||
WebsiteURL: u.WebsiteURL,
|
||||
Bio: u.Bio,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
SecondaryEmails: u.SecondaryEmails,
|
||||
Phone: u.Phone,
|
||||
AvatarURL: u.ResolvedAvatarURL(),
|
||||
CustomAvatarURL: strings.TrimSpace(u.AvatarURL),
|
||||
WebsiteURL: u.WebsiteURL,
|
||||
Bio: u.Bio,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +136,25 @@ func (u UserRecord) Showcase() UserShowcase {
|
||||
Username: u.Username,
|
||||
Level: u.Level,
|
||||
SproutCoins: u.SproutCoins,
|
||||
AvatarURL: u.AvatarURL,
|
||||
AvatarURL: u.ResolvedAvatarURL(),
|
||||
WebsiteURL: u.WebsiteURL,
|
||||
Bio: u.Bio,
|
||||
}
|
||||
}
|
||||
|
||||
// PublicUserListEntry 公开用户目录条目(含注册时间供排序与展示)。
|
||||
type PublicUserListEntry struct {
|
||||
UserShowcase
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
func (u UserRecord) PublicListEntry() PublicUserListEntry {
|
||||
return PublicUserListEntry{
|
||||
UserShowcase: u.Showcase(),
|
||||
CreatedAt: u.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NowISO() string {
|
||||
return time.Now().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user