- 微信登录改为真实 jscode2session,session_key 不再下发客户端 - 支付回调增加 HMAC 验签(X-Pay-Sign)与幂等处理,未配置密钥时拒绝回调 - 订单金额一律以服务端商品表定价,禁止客户端传入金额 - 付费许愿改为支付成功后创建,不再先许愿后付款 - 管理后台增加登录认证(ADMIN_PASSWORD + role=admin JWT + HttpOnly Cookie) - 订单详情/取消增加本人归属校验,修复越权访问 - 版本信息改为 ldflags 注入单一链路,GoVersion 用 runtime.Version() - 恢复 gin 默认访问日志(原 Logger 中间件输出为空) - 加载 HTML 模板修复后台页面 500;godotenv 加载 .env.local - CORS 支持 CORS_ORIGINS 白名单配置
141 lines
3.6 KiB
Go
141 lines
3.6 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"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
|
||
}
|
||
|
||
// 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"
|
||
}
|