This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/service/auth_service.go
Victor_Jay ff9886f08b refactor: 文件行数超标拆解(Service + Controller 9/16 完成)
- Service 层:energy_service → energize/operations/admin/query 四文件
- Service 层:post_service → helpers/audit + 核心 CRUD 保留
- Service 层:favorite_service → items + 核心文件夹操作保留
- Service 层:auth_service → password + 核心注册/登录保留
- Service 层:notification_service → notify + 核心列表/已读保留
- Service 层:audit_service → review + 核心列表/详情保留
- Controller 层:studio_controller → page/write/api/post_api/action 五文件
- Controller 层:post_controller → page/api/upload 三文件
- Controller 层:comment_controller → list/write/action/upload 四文件
- 清理未使用导入(notification_service.go 移除 fmt)
2026-06-02 15:45:54 +08:00

222 lines
5.8 KiB
Go
Raw Permalink 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 (
"encoding/json"
"regexp"
"time"
"unicode/utf8"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/session"
"golang.org/x/crypto/bcrypt"
)
// AuthService 认证业务逻辑
type AuthService struct {
userRepo userAuthStore
sessionManager *session.Manager
cfg *config.Config
siteSettings *config.SiteSettings
}
// NewAuthService 构造函数
func NewAuthService(userRepo userAuthStore, sm *session.Manager, cfg *config.Config, siteSettings *config.SiteSettings) *AuthService {
return &AuthService{userRepo: userRepo, sessionManager: sm, cfg: cfg, siteSettings: siteSettings}
}
// usernamePattern 用户名合法字符:中文、英文大小写、数字、下划线、连字符
var usernamePattern = regexp.MustCompile(`^[\p{Han}a-zA-Z0-9_-]+$`)
// IsRegistrationEnabled 检查是否允许新用户注册
func (s *AuthService) IsRegistrationEnabled() bool {
return s.siteSettings.IsRegistrationEnabled()
}
// Register 注册
// 流程:密码强度 → 邮箱查重 → 哈希 → 生成唯一用户名 → 创建 → 返回 usersession 由 controller 创建)
func (s *AuthService) Register(req model.RegisterRequest, regIP string) (*model.User, error) {
// 0. 维护期间禁止注册
if s.siteSettings.IsMaintenanceEnabled() {
return nil, common.ErrMaintenanceMode
}
// 1. 检查注册开关
if !s.siteSettings.IsRegistrationEnabled() {
return nil, common.ErrRegistrationDisabled
}
// 2. 密码强度
if err := validatePassword(req.Password); err != nil {
return nil, err
}
// 3. 检查邮箱
exists, err := s.userRepo.ExistsByEmail(req.Email)
if err != nil {
return nil, err
}
if exists {
return nil, common.ErrEmailExists
}
// 4. 哈希密码
hash, err := common.HashPassword(req.Password, s.cfg.Bcrypt.Cost)
if err != nil {
return nil, err
}
// 5. 生成唯一用户名
username, err := common.GenerateUsername(s.userRepo, 20)
if err != nil {
return nil, err
}
// 6. 创建用户
user := &model.User{
Email: req.Email,
PasswordHash: hash,
Username: username,
Role: model.RoleUser,
Status: model.StatusActive,
RegIP: regIP,
}
if err := s.userRepo.Create(user); err != nil {
return nil, err
}
return user, nil
}
// Login 登录
// 流程:按邮箱查找 → 维护期间非站长统一拦截 → 状态检查 → 验证密码 → 记录登录信息 → 返回 user
func (s *AuthService) Login(req model.LoginRequest, loginIP string) (*model.User, error) {
user, err := s.userRepo.FindByEmail(req.Email)
// 维护期间:非站长一律返回统一提示(模拟 bcrypt 防时序攻击)
if s.siteSettings.IsMaintenanceEnabled() {
if err != nil {
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(req.Password))
return nil, common.ErrMaintenanceMode
}
if !model.HasMinRole(user.Role, model.RoleOwner) {
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(req.Password))
return nil, common.ErrMaintenanceMode
}
}
if err != nil {
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(req.Password))
return nil, common.ErrInvalidCred
}
// 已注销deleted→ 验证密码后要求二次确认
if user.Status == model.StatusDeleted {
if bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)) != nil {
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(req.Password))
return nil, common.ErrInvalidCred
}
return nil, common.ErrNeedsConfirmRestore
}
// 永久锁定
if user.Status == model.StatusLocked {
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(req.Password))
return nil, common.ErrUserLocked
}
if bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)) != nil {
return nil, common.ErrInvalidCred
}
// 记录登录 IP 和时间
now := time.Now()
user.LastLoginIP = loginIP
user.LastLoginAt = &now
if err := s.userRepo.Update(user); err != nil {
return nil, err
}
return user, nil
}
// CheckEmail 检查邮箱是否已被注册
func (s *AuthService) CheckEmail(email string) (bool, error) {
return s.userRepo.ExistsByEmail(email)
}
// GetProfile 获取当前用户资料
func (s *AuthService) GetProfile(userID uint) (*model.User, error) {
return s.userRepo.FindByID(userID)
}
// UpdateProfile 修改个人资料
func (s *AuthService) UpdateProfile(userID uint, username, bio string) error {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return common.ErrUserNotFound
}
needsUpdate := false
if n := utf8.RuneCountInString(username); n == 0 {
return common.ErrUsernameInvalid
} else if n > 16 {
return common.ErrUsernameInvalid
}
if !usernamePattern.MatchString(username) {
return common.ErrUsernameInvalid
}
if username != user.Username {
exists, err := s.userRepo.ExistsByUsername(username)
if err != nil {
return err
}
if exists {
return common.ErrUsernameTaken
}
user.Username = username
needsUpdate = true
}
if utf8.RuneCountInString(bio) > 128 {
return common.ErrBioTooLong
}
if bio != user.Bio {
user.Bio = bio
needsUpdate = true
}
if !needsUpdate {
return nil
}
return s.userRepo.Update(user)
}
// GetNotifyPrefs 获取用户通知偏好
func (s *AuthService) GetNotifyPrefs(userID uint) (map[string]bool, error) {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return nil, common.ErrUserNotFound
}
return ParseNotifyPrefs(user.NotifyPrefs), nil
}
// UpdateNotifyPref 更新单个通知偏好项
func (s *AuthService) UpdateNotifyPref(userID uint, key string, enabled bool) error {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return common.ErrUserNotFound
}
prefs := ParseNotifyPrefs(user.NotifyPrefs)
prefs[key] = enabled
data, err := json.Marshal(prefs)
if err != nil {
return err
}
user.NotifyPrefs = string(data)
return s.userRepo.Update(user)
}