Files
nl-pms-api/internal/service/profile.go
2026-08-15 17:04:47 +08:00

114 lines
3.3 KiB
Go
Raw Permalink 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 service
import (
"encoding/json"
"strings"
"gorm.io/gorm"
"nl-pms-api/internal/commonservice"
"nl-pms-api/internal/model"
)
// ProfileService 用户公开资料。
type ProfileService struct {
DB *gorm.DB
}
// ProfileDTO 对外资料techTags 为数组)。
type ProfileDTO struct {
Nickname string `json:"nickname"`
Title string `json:"title"`
Email string `json:"email"`
Bio string `json:"bio"`
TechTags []string `json:"techTags"`
AvatarThumb string `json:"avatarThumb"`
UpdatedAt string `json:"updatedAt"`
}
func sanitizeProfile(p *ProfileDTO) error {
p.Nickname = commonservice.ClipRunes(p.Nickname, 32)
p.Title = commonservice.ClipRunes(p.Title, 48)
p.Email = commonservice.ClipRunes(p.Email, 128)
p.Bio = commonservice.ClipRunes(p.Bio, 300)
if p.Email != "" && (!strings.Contains(p.Email, "@") || strings.ContainsAny(p.Email, " \t")) {
return commonservice.BadRequest("PROFILE_EMAIL_INVALID")
}
tags, seen := []string{}, map[string]bool{}
for _, t := range p.TechTags {
t = commonservice.ClipRunes(t, 24)
if t == "" || seen[strings.ToLower(t)] {
continue
}
seen[strings.ToLower(t)] = true
tags = append(tags, t)
if len(tags) >= 20 {
break
}
}
p.TechTags = tags
return nil
}
// Get 读取本人资料;无记录返回空结构。
func (s *ProfileService) Get(userID int64) (*ProfileDTO, error) {
var row model.UserProfile
err := s.DB.Where("user_id = ?", userID).First(&row).Error
if err == gorm.ErrRecordNotFound {
return &ProfileDTO{TechTags: []string{}}, nil
}
if err != nil {
return nil, commonservice.Internal("QUERY_FAILED")
}
return &ProfileDTO{
Nickname: row.Nickname,
Title: row.Title,
Email: row.Email,
Bio: row.Bio,
TechTags: parseTechTags(row.TechTags),
AvatarThumb: row.AvatarThumb,
UpdatedAt: row.UpdatedAt,
}, nil
}
// Put LWW 写入资料:仅当传入 updatedAt 更新时覆盖。
func (s *ProfileService) Put(userID int64, p ProfileDTO) (*ProfileDTO, error) {
if err := sanitizeProfile(&p); err != nil {
return nil, err
}
if p.UpdatedAt == "" {
p.UpdatedAt = commonservice.NowRFC()
}
tags, _ := json.Marshal(p.TechTags)
var remote model.UserProfile
err := s.DB.Where("user_id = ?", userID).First(&remote).Error
if err == nil && remote.UpdatedAt >= p.UpdatedAt {
// 远端相同或更新:返回远端,不覆盖。
return &ProfileDTO{
Nickname: remote.Nickname,
Title: remote.Title,
Email: remote.Email,
Bio: remote.Bio,
TechTags: parseTechTags(remote.TechTags),
AvatarThumb: remote.AvatarThumb,
UpdatedAt: remote.UpdatedAt,
}, nil
}
q := `INSERT INTO user_profiles(user_id,nickname,title,email,bio,tech_tags,avatar_thumb,updated_at)
VALUES(?,?,?,?,?,?,?,?)
ON DUPLICATE KEY UPDATE nickname=VALUES(nickname),title=VALUES(title),email=VALUES(email),bio=VALUES(bio),
tech_tags=VALUES(tech_tags),avatar_thumb=VALUES(avatar_thumb),updated_at=VALUES(updated_at)`
if err := s.DB.Exec(q, userID, p.Nickname, p.Title, p.Email, p.Bio, string(tags), p.AvatarThumb, p.UpdatedAt).Error; err != nil {
return nil, commonservice.Internal("SAVE_FAILED")
}
return &p, nil
}
func parseTechTags(s string) []string {
out := []string{}
if json.Unmarshal([]byte(s), &out) != nil {
return []string{}
}
return out
}