448 lines
12 KiB
Go
448 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"ops/internal/database"
|
|
"ops/internal/repository"
|
|
)
|
|
|
|
// AuthService 用户认证服务结构
|
|
type AuthService struct {
|
|
userRepo repository.UserRepository
|
|
userInfoRepo repository.UserInfoRepository
|
|
cookieRepo repository.CookieRepository
|
|
db *gorm.DB
|
|
}
|
|
|
|
// UserWithInfo 用户信息结构
|
|
type UserWithInfo struct {
|
|
UserID uint `json:"userID"`
|
|
Name string `json:"name"`
|
|
AvatarURL string `json:"avatarURL"`
|
|
CookieValue string `json:"cookieValue"`
|
|
}
|
|
|
|
// CookieInfo Cookie信息结构
|
|
type CookieInfo struct {
|
|
Value string `json:"value"`
|
|
ExpireDate time.Time `json:"expireDate"`
|
|
}
|
|
|
|
// NewAuthService 创建认证服务实例
|
|
func NewAuthService(db *gorm.DB) *AuthService {
|
|
return &AuthService{
|
|
userRepo: repository.NewUserRepository(db),
|
|
userInfoRepo: repository.NewUserInfoRepository(db),
|
|
cookieRepo: repository.NewCookieRepository(db),
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// Login 用户登录
|
|
func (s *AuthService) Login(name, password, deviceID, ip, remember string) (*UserWithInfo, *CookieInfo, error) {
|
|
if name == "" || password == "" {
|
|
return nil, nil, errors.New("username and password are required")
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := s.userRepo.FindByName(name)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("find user error: %w", err)
|
|
}
|
|
if user == nil {
|
|
return nil, nil, errors.New("user not found")
|
|
}
|
|
|
|
// TODO: 密码验证逻辑(需要查看现有系统的密码加密方式)
|
|
// 假设这里使用MD5加密,需要根据实际情况调整
|
|
hashedPassword := hashPassword(password)
|
|
|
|
// 临时跳过密码验证,因为现有系统的用户没有密码字段
|
|
fmt.Printf("DEBUG: Trying to login user %s (password: %s, hashed: %s)\n", name, password, hashedPassword)
|
|
|
|
// 生成Cookie
|
|
cookieValue := generateCookieValue(user.ID, name, deviceID)
|
|
|
|
// 设置过期时间
|
|
expiresAt := time.Now()
|
|
if remember == "1" || remember == "true" {
|
|
expiresAt = expiresAt.Add(30 * 24 * time.Hour) // 30天
|
|
} else {
|
|
expiresAt = expiresAt.Add(24 * time.Hour) // 24小时
|
|
}
|
|
|
|
cookie := &database.TabCookie{
|
|
Value: cookieValue,
|
|
UserID: user.ID,
|
|
ExpiresAt: expiresAt.Unix(),
|
|
CreateAt: time.Now().Unix(),
|
|
Remember: (remember == "1" || remember == "true"),
|
|
}
|
|
|
|
// 保存Cookie到数据库
|
|
if err := s.cookieRepo.Create(cookie); err != nil {
|
|
return nil, nil, fmt.Errorf("create cookie error: %w", err)
|
|
}
|
|
|
|
// 获取用户信息
|
|
userInfo, err := s.userInfoRepo.FindByUserID(user.ID)
|
|
if err != nil {
|
|
fmt.Printf("WARN: user info not found for user %s: %v\n", name, err)
|
|
}
|
|
|
|
// 构建头像URL
|
|
avatarURL := "/static/default_avatar.png"
|
|
if userInfo != nil && userInfo.AvatarPath != "" {
|
|
avatarURL = "/static/uploads/" + userInfo.AvatarPath
|
|
}
|
|
|
|
// 返回用户信息和Cookie
|
|
userWithInfo := &UserWithInfo{
|
|
UserID: user.ID,
|
|
Name: user.Name,
|
|
AvatarURL: avatarURL,
|
|
CookieValue: cookieValue,
|
|
}
|
|
|
|
cookieInfo := &CookieInfo{
|
|
Value: cookieValue,
|
|
ExpireDate: expiresAt,
|
|
}
|
|
|
|
return userWithInfo, cookieInfo, nil
|
|
}
|
|
|
|
// Register 用户注册
|
|
func (s *AuthService) Register(name, password, email, phone string) (*UserWithInfo, *CookieInfo, error) {
|
|
if name == "" || password == "" {
|
|
return nil, nil, errors.New("username and password are required")
|
|
}
|
|
|
|
// 检查用户名是否已存在
|
|
exists, err := s.userRepo.ExistsByName(name)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("check username exists error: %w", err)
|
|
}
|
|
if exists {
|
|
return nil, nil, errors.New("username already exists")
|
|
}
|
|
|
|
// 创建用户
|
|
user := &database.TabUser{
|
|
Name: name,
|
|
// 注意:现有TabUser表只有ID和Name字段,没有密码字段
|
|
}
|
|
|
|
if err := s.userRepo.Create(user); err != nil {
|
|
return nil, nil, fmt.Errorf("create user error: %w", err)
|
|
}
|
|
|
|
// 创建用户信息
|
|
userInfo := &database.TabUserInfo{
|
|
UserID: user.ID,
|
|
AvatarPath: "", // 默认空
|
|
Birthdate: "",
|
|
Gender: 0,
|
|
Introduction: "",
|
|
}
|
|
|
|
if err := s.userInfoRepo.Create(userInfo); err != nil {
|
|
// 如果创建用户信息失败,删除用户(可选)
|
|
s.userRepo.Delete(user.ID)
|
|
return nil, nil, fmt.Errorf("create user info error: %w", err)
|
|
}
|
|
|
|
// 生成Cookie
|
|
cookieValue := generateCookieValue(user.ID, name, "register")
|
|
expiresAt := time.Now().Add(7 * 24 * time.Hour) // 7天
|
|
|
|
cookie := &database.TabCookie{
|
|
Value: cookieValue,
|
|
UserID: user.ID,
|
|
ExpiresAt: expiresAt.Unix(),
|
|
CreateAt: time.Now().Unix(),
|
|
Remember: true,
|
|
}
|
|
|
|
if err := s.cookieRepo.Create(cookie); err != nil {
|
|
return nil, nil, fmt.Errorf("create cookie error: %w", err)
|
|
}
|
|
|
|
// 返回用户信息和Cookie
|
|
userWithInfo := &UserWithInfo{
|
|
UserID: user.ID,
|
|
Name: user.Name,
|
|
AvatarURL: "/static/default_avatar.png",
|
|
CookieValue: cookieValue,
|
|
}
|
|
|
|
cookieInfo := &CookieInfo{
|
|
Value: cookieValue,
|
|
ExpireDate: expiresAt,
|
|
}
|
|
|
|
return userWithInfo, cookieInfo, nil
|
|
}
|
|
|
|
// ForgotPassword 忘记密码
|
|
func (s *AuthService) ForgotPassword(name, email, phone string) (string, error) {
|
|
if name == "" {
|
|
return "", errors.New("username is required")
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := s.userRepo.FindByName(name)
|
|
if err != nil {
|
|
return "", fmt.Errorf("find user error: %w", err)
|
|
}
|
|
if user == nil {
|
|
return "", errors.New("user not found")
|
|
}
|
|
|
|
// 生成重置令牌
|
|
resetToken := generateResetToken(user.ID, name)
|
|
|
|
// TODO: 发送重置密码邮件或短信
|
|
// 这里应该实现邮件发送或短信发送逻辑
|
|
|
|
fmt.Printf("DEBUG: Password reset token for user %s: %s\n", name, resetToken)
|
|
|
|
return resetToken, nil
|
|
}
|
|
|
|
// ResetPassword 重置密码
|
|
func (s *AuthService) ResetPassword(token, newPassword string) error {
|
|
if token == "" || newPassword == "" {
|
|
return errors.New("token and new password are required")
|
|
}
|
|
|
|
// TODO: 验证重置令牌并获取用户ID
|
|
// 这里应该解析token获取用户ID
|
|
userID := parseResetToken(token)
|
|
if userID == 0 {
|
|
return errors.New("invalid reset token")
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := s.userRepo.FindByID(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("find user error: %w", err)
|
|
}
|
|
if user == nil {
|
|
return errors.New("user not found")
|
|
}
|
|
|
|
// TODO: 更新密码
|
|
// 注意:现有TabUser表没有密码字段,这里可能需要扩展表结构或使用其他方式存储密码
|
|
|
|
fmt.Printf("DEBUG: Password reset for user %s (ID: %d)\n", user.Name, user.ID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Logout 用户退出登录
|
|
func (s *AuthService) Logout(cookieValue, deviceID string) error {
|
|
if cookieValue == "" {
|
|
return errors.New("cookie value is required")
|
|
}
|
|
|
|
return s.cookieRepo.DeleteByValue(cookieValue)
|
|
}
|
|
|
|
// GetProfile 获取用户信息
|
|
func (s *AuthService) GetProfile(userID uint) (*UserWithInfo, error) {
|
|
if userID == 0 {
|
|
return nil, errors.New("user ID is required")
|
|
}
|
|
|
|
// 获取增强的用户信息
|
|
enhancedUser, err := repository.GetEnhancedUserInfo(s.db, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get enhanced user info error: %w", err)
|
|
}
|
|
if enhancedUser == nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
return &UserWithInfo{
|
|
UserID: enhancedUser.TabUser.ID,
|
|
Name: enhancedUser.TabUser.Name,
|
|
AvatarURL: enhancedUser.AvatarURL,
|
|
}, nil
|
|
}
|
|
|
|
// UpdateProfile 更新用户信息
|
|
func (s *AuthService) UpdateProfile(userID uint, updateData map[string]interface{}) (*UserWithInfo, error) {
|
|
if userID == 0 {
|
|
return nil, errors.New("user ID is required")
|
|
}
|
|
|
|
// 获取用户信息
|
|
enhancedUser, err := repository.GetEnhancedUserInfo(s.db, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get enhanced user info error: %w", err)
|
|
}
|
|
if enhancedUser == nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
// 更新用户信息
|
|
// 检查是否有avatar字段
|
|
if avatarPath, ok := updateData["avatar"]; ok {
|
|
avatarStr, isString := avatarPath.(string)
|
|
if isString && avatarStr != "" {
|
|
enhancedUser.UserInfo.AvatarPath = avatarStr
|
|
if err := s.userInfoRepo.Update(&enhancedUser.UserInfo); err != nil {
|
|
return nil, fmt.Errorf("update user info error: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查其他可更新字段
|
|
if gender, ok := updateData["gender"]; ok {
|
|
if genderNum, isNum := gender.(float64); isNum {
|
|
enhancedUser.UserInfo.Gender = int(genderNum)
|
|
}
|
|
}
|
|
|
|
if birthdate, ok := updateData["birthdate"]; ok {
|
|
if birthdateStr, isString := birthdate.(string); isString {
|
|
enhancedUser.UserInfo.Birthdate = birthdateStr
|
|
}
|
|
}
|
|
|
|
if intro, ok := updateData["introduction"]; ok {
|
|
if introStr, isString := intro.(string); isString {
|
|
enhancedUser.UserInfo.Introduction = introStr
|
|
}
|
|
}
|
|
|
|
// 保存更新后的用户信息
|
|
if err := s.userInfoRepo.Update(&enhancedUser.UserInfo); err != nil {
|
|
return nil, fmt.Errorf("update user info error: %w", err)
|
|
}
|
|
|
|
// 构建头像URL
|
|
avatarURL := "/static/default_avatar.png"
|
|
if enhancedUser.UserInfo.AvatarPath != "" {
|
|
avatarURL = "/static/uploads/" + enhancedUser.UserInfo.AvatarPath
|
|
}
|
|
|
|
return &UserWithInfo{
|
|
UserID: userID,
|
|
Name: enhancedUser.TabUser.Name,
|
|
AvatarURL: avatarURL,
|
|
}, nil
|
|
}
|
|
|
|
// ValidateCookie 验证Cookie有效性
|
|
func (s *AuthService) ValidateCookie(cookieValue string) (uint, error) {
|
|
if cookieValue == "" {
|
|
return 0, errors.New("cookie value is required")
|
|
}
|
|
|
|
cookie, err := s.cookieRepo.FindByValue(cookieValue)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("find cookie error: %w", err)
|
|
}
|
|
if cookie == nil {
|
|
return 0, errors.New("cookie not found")
|
|
}
|
|
|
|
// 检查是否过期
|
|
if cookie.ExpiresAt < time.Now().Unix() {
|
|
// 删除过期的Cookie
|
|
s.cookieRepo.DeleteByValue(cookieValue)
|
|
return 0, errors.New("cookie expired")
|
|
}
|
|
|
|
return cookie.UserID, nil
|
|
}
|
|
|
|
// 辅助函数
|
|
func hashPassword(password string) string {
|
|
// 使用MD5哈希(根据现有系统可能使用其他方式)
|
|
hash := md5.Sum([]byte(password))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
func generateCookieValue(userID uint, username, deviceID string) string {
|
|
timestamp := time.Now().UnixNano()
|
|
data := fmt.Sprintf("%d%s%s%d", userID, username, deviceID, timestamp)
|
|
hash := sha256.Sum256([]byte(data))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
func generateResetToken(userID uint, username string) string {
|
|
timestamp := time.Now().UnixNano()
|
|
random := fmt.Sprintf("%d", timestamp)
|
|
data := fmt.Sprintf("%d%s%s%d", userID, username, random, timestamp)
|
|
hash := sha256.Sum256([]byte(data))
|
|
token := hex.EncodeToString(hash[:])
|
|
|
|
// 存储到数据库或Redis(这里简化处理)
|
|
// 在实际应用中应该存储token并设置过期时间
|
|
return token
|
|
}
|
|
|
|
func parseResetToken(token string) uint {
|
|
// 简化的token解析,实际应该从数据库或Redis验证
|
|
// 这里返回0表示无效
|
|
if len(token) < 32 {
|
|
return 0
|
|
}
|
|
|
|
// TODO: 实现token解析逻辑
|
|
// 暂时返回0,需要根据具体token格式实现
|
|
return 0
|
|
}
|
|
|
|
// CleanupExpiredCookies 清理过期Cookie
|
|
func (s *AuthService) CleanupExpiredCookies() error {
|
|
return s.cookieRepo.DeleteExpired()
|
|
}
|
|
|
|
// GetUserByCookie 通过Cookie获取用户信息
|
|
func (s *AuthService) GetUserByCookie(cookieValue string) (*UserWithInfo, error) {
|
|
userID, err := s.ValidateCookie(cookieValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetProfile(userID)
|
|
}
|
|
|
|
// UpdateUserPassword 更新用户密码
|
|
func (s *AuthService) UpdateUserPassword(userID uint, oldPassword, newPassword string) error {
|
|
if userID == 0 {
|
|
return errors.New("user ID is required")
|
|
}
|
|
|
|
if oldPassword == "" || newPassword == "" {
|
|
return errors.New("old password and new password are required")
|
|
}
|
|
|
|
user, err := s.userRepo.FindByID(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("find user error: %w", err)
|
|
}
|
|
if user == nil {
|
|
return errors.New("user not found")
|
|
}
|
|
|
|
// TODO: 验证旧密码
|
|
// 现有系统没有密码字段,需要扩展
|
|
|
|
// TODO: 更新密码
|
|
// 现有系统没有密码字段,需要扩展
|
|
|
|
return errors.New("password update not supported in current schema")
|
|
} |