package service import ( "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "nl-pms-api/internal/commonservice" "nl-pms-api/internal/config" "nl-pms-api/internal/model" ) // AuthService 注册 / 登录 / 刷新 / 改密。 type AuthService struct { DB *gorm.DB Cfg *config.Config } // AuthTokens 登录/刷新返回的令牌与用户信息。 type AuthTokens struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` UserID int64 `json:"userId"` Username string `json:"username"` } func validAccount(username, password string) error { username = strings.TrimSpace(username) if len(username) < 3 || len(username) > 64 { return commonservice.BadRequest("SYNC_USERNAME_INVALID") } if len(password) < 6 { return commonservice.BadRequest("SYNC_PASSWORD_TOO_SHORT") } return nil } // Register 注册新账号。 func (s *AuthService) Register(username, password string) error { if err := validAccount(username, password); err != nil { return err } username = strings.TrimSpace(username) var n int64 s.DB.Model(&model.User{}).Where("username = ?", username).Count(&n) if n > 0 { return commonservice.Conflict("SYNC_USER_EXISTS") } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return commonservice.Internal("INTERNAL") } u := model.User{ Username: username, PasswordHash: string(hash), CreatedAt: commonservice.NowRFC(), } if err := s.DB.Create(&u).Error; err != nil { // 并发注册由唯一索引兜底。 return commonservice.Conflict("SYNC_USER_EXISTS") } return nil } // Login 校验账号并签发双令牌。 func (s *AuthService) Login(username, password, clientIP string) (*AuthTokens, error) { username = strings.TrimSpace(username) var u model.User if err := s.DB.Where("username = ?", username).First(&u).Error; err != nil { return nil, commonservice.Unauthorized("SYNC_BAD_CREDENTIALS") } if u.Disabled != 0 { return nil, commonservice.Forbidden("ACCOUNT_DISABLED") } if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)) != nil { return nil, commonservice.Unauthorized("SYNC_BAD_CREDENTIALS") } tok, err := s.issueTokens(u.ID, u.Username) if err != nil { return nil, err } _ = (&ActivityService{DB: s.DB}).Ping(u.ID, clientIP) return tok, nil } // Refresh 用 refresh token 换新的双令牌。 func (s *AuthService) Refresh(refreshToken, clientIP string) (*AuthTokens, error) { claims, err := commonservice.Parse(s.Cfg.JWTSecret, strings.TrimSpace(refreshToken), "refresh") if err != nil { return nil, err } var u model.User if err := s.DB.First(&u, claims.UserID).Error; err != nil { return nil, commonservice.Unauthorized("UNAUTHORIZED") } if u.Disabled != 0 { return nil, commonservice.Forbidden("ACCOUNT_DISABLED") } tok, err := s.issueTokens(u.ID, u.Username) if err != nil { return nil, err } _ = (&ActivityService{DB: s.DB}).Ping(u.ID, clientIP) return tok, nil } // ChangePassword 在线修改密码。 func (s *AuthService) ChangePassword(userID int64, oldPassword, newPassword string) error { if len(newPassword) < 6 { return commonservice.BadRequest("SYNC_PASSWORD_TOO_SHORT") } var u model.User if err := s.DB.First(&u, userID).Error; err != nil { return commonservice.Unauthorized("UNAUTHORIZED") } if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(oldPassword)) != nil { return commonservice.BadRequest("SYNC_OLD_PASSWORD_WRONG") } hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return commonservice.Internal("INTERNAL") } return s.DB.Model(&u).Update("password_hash", string(hash)).Error } func (s *AuthService) issueTokens(userID int64, username string) (*AuthTokens, error) { access, err := commonservice.IssueAccess(s.Cfg.JWTSecret, userID, username, s.Cfg.AccessTTLHours) if err != nil { return nil, commonservice.Internal("INTERNAL") } refresh, err := commonservice.IssueRefresh(s.Cfg.JWTSecret, userID, username, s.Cfg.RefreshTTLDays) if err != nil { return nil, commonservice.Internal("INTERNAL") } return &AuthTokens{ AccessToken: access, RefreshToken: refresh, UserID: userID, Username: username, }, nil }