136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
package services
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/inchstep/server/internal/models"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewUserService(db *gorm.DB) *UserService {
|
|
return &UserService{db: db}
|
|
}
|
|
|
|
func generateID() string {
|
|
b := make([]byte, 16)
|
|
rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
func (s *UserService) RegisterByPhone(phone string) (*models.User, error) {
|
|
var existing models.User
|
|
if err := s.db.Where("phone = ?", phone).First(&existing).Error; err == nil {
|
|
return nil, errors.New("该手机号已注册")
|
|
}
|
|
user := &models.User{
|
|
ID: generateID(),
|
|
Phone: &phone,
|
|
Privacy: models.PrivacyFriendsOnly,
|
|
Points: 0,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
if err := s.db.Create(user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (s *UserService) GetByPhone(phone string) (*models.User, error) {
|
|
var user models.User
|
|
if err := s.db.Where("phone = ?", phone).First(&user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *UserService) GetByID(id string) (*models.User, error) {
|
|
var user models.User
|
|
if err := s.db.First(&user, "id = ?", id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *UserService) UpdateProfile(id string, updates map[string]interface{}) (*models.User, error) {
|
|
allowed := map[string]bool{"nickname": true, "avatar": true, "bio": true, "motto": true, "privacy": true}
|
|
filtered := make(map[string]interface{})
|
|
for k, v := range updates {
|
|
if allowed[k] {
|
|
filtered[k] = v
|
|
}
|
|
}
|
|
if err := s.db.Model(&models.User{}).Where("id = ?", id).Updates(filtered).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetByID(id)
|
|
}
|
|
|
|
func (s *UserService) DeleteAccount(id string) error {
|
|
return s.db.Model(&models.User{}).Where("id = ?", id).Updates(map[string]interface{}{
|
|
"phone": nil,
|
|
"nickname": nil,
|
|
"avatar": nil,
|
|
"bio": nil,
|
|
"motto": nil,
|
|
"privacy": models.PrivacyPrivate,
|
|
}).Error
|
|
}
|
|
|
|
// LeaderboardEntry 排行榜条目
|
|
type LeaderboardEntry struct {
|
|
UserID string `json:"userId"`
|
|
Nickname string `json:"nickname"`
|
|
Avatar string `json:"avatar,omitempty"`
|
|
Value int `json:"value"`
|
|
}
|
|
|
|
// GetLeaderboardByPoints 按积分排行
|
|
func (s *UserService) GetLeaderboardByPoints(limit int) ([]LeaderboardEntry, error) {
|
|
var users []models.User
|
|
if err := s.db.Where("points > 0").Order("points desc").Limit(limit).Find(&users).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
entries := make([]LeaderboardEntry, 0, len(users))
|
|
for _, u := range users {
|
|
nick := ""
|
|
if u.Nickname != nil {
|
|
nick = *u.Nickname
|
|
}
|
|
entries = append(entries, LeaderboardEntry{
|
|
UserID: u.ID,
|
|
Nickname: nick,
|
|
Value: u.Points,
|
|
})
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// SearchUsers 搜索用户(按昵称或手机号)
|
|
func (s *UserService) SearchUsers(query string, limit int) ([]models.User, error) {
|
|
var users []models.User
|
|
q := "%" + query + "%"
|
|
if err := s.db.Where("nickname LIKE ? OR phone LIKE ?", q, q).
|
|
Limit(limit).Find(&users).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func HashPassword(password string) (string, error) {
|
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
|
return string(bytes), err
|
|
}
|
|
|
|
func CheckPassword(hash, password string) bool {
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
|
|
}
|