- 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)
222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
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 注册
|
||
// 流程:密码强度 → 邮箱查重 → 哈希 → 生成唯一用户名 → 创建 → 返回 user(session 由 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)
|
||
}
|