- 八字页:未绑定生辰时展示虚构示例排盘(1990-08-08 10:30男)与15条字段说明, 附传统文化声明文案;已绑定时支持多档案切换,排盘增强(十神/藏干/纳音/五行配色/命盘概览) - 个人中心:新增生辰管理卡片,每用户免费绑定3个出生年月;登录改为真实微信登录 - 服务端:新增 BirthProfile 模型与 /api/user/birth-profiles 增删查接口,AutoMigrate 建表 - 前端:新增 birth-profile.js 本地存储优先+登录后云端双向同步 - 修复:个人中心 tabBar 页面入口误用 navigateTo 改为 switchTab
171 lines
4.7 KiB
Go
171 lines
4.7 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
"github.com/gouki/lunar-server/internal/config"
|
||
"github.com/gouki/lunar-server/internal/model"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// UserService 用户服务
|
||
type UserService struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
// NewUserService 创建用户服务
|
||
func NewUserService() *UserService {
|
||
return &UserService{
|
||
db: config.GetDB(),
|
||
}
|
||
}
|
||
|
||
// GetUserByOpenID 根据OpenID获取用户
|
||
func (s *UserService) GetUserByOpenID(openID string) (*model.User, error) {
|
||
var user model.User
|
||
if err := s.db.Where("open_id = ?", openID).First(&user).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
return &user, nil
|
||
}
|
||
|
||
// GetUserByID 根据ID获取用户
|
||
func (s *UserService) GetUserByID(id uint) (*model.User, error) {
|
||
var user model.User
|
||
if err := s.db.First(&user, id).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &user, nil
|
||
}
|
||
|
||
// CreateUser 创建用户
|
||
func (s *UserService) CreateUser(user *model.User) error {
|
||
return s.db.Create(user).Error
|
||
}
|
||
|
||
// UpdateUser 更新用户
|
||
func (s *UserService) UpdateUser(user *model.User) error {
|
||
return s.db.Save(user).Error
|
||
}
|
||
|
||
// UpdateUserProfile 更新用户资料
|
||
func (s *UserService) UpdateUserProfile(profile *model.UserProfile) error {
|
||
return s.db.Save(profile).Error
|
||
}
|
||
|
||
// GetUserProfile 获取用户资料
|
||
func (s *UserService) GetUserProfile(userID uint) (*model.UserProfile, error) {
|
||
var profile model.UserProfile
|
||
if err := s.db.Where("user_id = ?", userID).First(&profile).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
return &profile, nil
|
||
}
|
||
|
||
// ErrBirthProfileLimit 生辰档案数量达到免费上限
|
||
var ErrBirthProfileLimit = fmt.Errorf("最多可免费绑定 %d 个生辰", model.MaxFreeBirthProfiles)
|
||
|
||
// ListBirthProfiles 获取用户的全部生辰档案
|
||
func (s *UserService) ListBirthProfiles(userID uint) ([]model.BirthProfile, error) {
|
||
var profiles []model.BirthProfile
|
||
if err := s.db.Where("user_id = ?", userID).Order("id ASC").Find(&profiles).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return profiles, nil
|
||
}
|
||
|
||
// CreateBirthProfile 新增生辰档案,超过免费上限时返回 ErrBirthProfileLimit
|
||
func (s *UserService) CreateBirthProfile(profile *model.BirthProfile) error {
|
||
var count int64
|
||
if err := s.db.Model(&model.BirthProfile{}).Where("user_id = ?", profile.UserID).Count(&count).Error; err != nil {
|
||
return err
|
||
}
|
||
if count >= model.MaxFreeBirthProfiles {
|
||
return ErrBirthProfileLimit
|
||
}
|
||
return s.db.Create(profile).Error
|
||
}
|
||
|
||
// DeleteBirthProfile 删除生辰档案(仅限本人)
|
||
func (s *UserService) DeleteBirthProfile(id, userID uint) error {
|
||
return s.db.Where("id = ? AND user_id = ?", id, userID).Delete(&model.BirthProfile{}).Error
|
||
}
|
||
|
||
// GenerateToken 生成JWT Token
|
||
func (s *UserService) GenerateToken(userID uint, secret string) (string, error) {
|
||
claims := jwt.MapClaims{
|
||
"user_id": userID,
|
||
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(), // 7天过期
|
||
"iat": time.Now().Unix(),
|
||
}
|
||
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
return token.SignedString([]byte(secret))
|
||
}
|
||
|
||
// ParseToken 解析JWT Token
|
||
func (s *UserService) ParseToken(tokenString, secret string) (uint, error) {
|
||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||
return []byte(secret), nil
|
||
})
|
||
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||
// 管理员 token 不绑定具体用户,不能当普通用户 token 使用
|
||
if role, _ := claims["role"].(string); role == "admin" {
|
||
return 0, errors.New("admin token")
|
||
}
|
||
userIDFloat, ok := claims["user_id"].(float64)
|
||
if !ok {
|
||
return 0, errors.New("invalid token claims")
|
||
}
|
||
return uint(userIDFloat), nil
|
||
}
|
||
|
||
return 0, errors.New("invalid token")
|
||
}
|
||
|
||
// AdminTokenCookie 管理员会话 Cookie 名
|
||
const AdminTokenCookie = "lunar_admin_token"
|
||
|
||
// GenerateAdminToken 签发管理员 JWT(12 小时过期)
|
||
func (s *UserService) GenerateAdminToken(secret string) (string, error) {
|
||
claims := jwt.MapClaims{
|
||
"role": "admin",
|
||
"exp": time.Now().Add(time.Hour * 12).Unix(),
|
||
"iat": time.Now().Unix(),
|
||
}
|
||
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
return token.SignedString([]byte(secret))
|
||
}
|
||
|
||
// IsAdminToken 校验是否为有效的管理员 JWT
|
||
func (s *UserService) IsAdminToken(tokenString, secret string) bool {
|
||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||
return []byte(secret), nil
|
||
})
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
claims, ok := token.Claims.(jwt.MapClaims)
|
||
if !ok || !token.Valid {
|
||
return false
|
||
}
|
||
role, _ := claims["role"].(string)
|
||
return role == "admin"
|
||
}
|