Files
lunar-mini/server/internal/service/user.go
T
gouki deb32f8fab
Build and Publish Server / build (push) Successful in 4m30s
feat(server): 商业化支付与订单系统 + Harness 改进
- 新增商业化模块:Stripe/Binance Pay 支付、邮箱登录、订单管理
- 新增数据模型:ReportProduct, ReportEntitlement, ReportSnapshot, PaymentEvent, EmailLoginCode
- 新增 API:/api/commerce/checkout, /api/commerce/webhooks/*, /api/commerce/orders/*
- 新增管理后台:订单列表、统计图表、统一 AdminHeader 组件
- 新增中间件测试:RateLimit, Auth, CORS 共 7 个测试用例
- 新增 AGENTS.md 项目代理指令文件
- 新增 REMINDER.md 商业化功能开发规范
- 修复 JWT 解析:添加 WithValidMethods 限制 HS256
- 修复 GetPayStatus:增加用户权限校验
- 更新 .gitignore 和 README.md
2026-09-19 18:57:23 +00:00

171 lines
4.8 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 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
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
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
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
if err != nil {
return false
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return false
}
role, _ := claims["role"].(string)
return role == "admin"
}