首次提交:初始化项目代码

This commit is contained in:
sunct
2026-07-31 15:24:14 +08:00
commit 8d3c97bd01
73 changed files with 11720 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
package auth
// RegisterRequest represents the request body for user registration.
// RegisterRequest 表示用户注册的请求体。
//
// Fields:
// - Username: The desired username (3-50 characters).
// - Password: The desired password (minimum 6 characters).
//
// Validation:
// - Username is required and must be 3-50 characters.
// - Password is required and must be at least 6 characters.
//
// 字段:
// - Username: 期望的用户名(3-50 个字符)。
// - Password: 期望的密码(最少 6 个字符)。
//
// 验证规则:
// - Username 是必填项,必须是 3-50 个字符。
// - Password 是必填项,必须至少 6 个字符。
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=50"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email"`
Nickname string `json:"nickname"`
CaptchaID string `json:"captcha_id" binding:"required"`
CaptchaAnswer string `json:"captcha_answer" binding:"required"`
EmailCode string `json:"email_code"` // 邮箱注册时必填
}
// LoginRequest represents the request body for user login.
// LoginRequest 表示用户登录的请求体。
//
// Fields:
// - Username: The user's username.
// - Password: The user's password.
//
// Validation:
// - Both fields are required.
//
// 字段:
// - Username: 用户的用户名。
// - Password: 用户的密码。
//
// 验证规则:
// - 两个字段都是必填项。
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
CaptchaID string `json:"captcha_id" binding:"required"`
CaptchaAnswer string `json:"captcha_answer" binding:"required"`
}
// TokenResponse represents the response body for successful login.
// TokenResponse 表示成功登录的响应体。
//
// Fields:
// - Token: The JWT access token.
//
// 字段:
// - Token: JWT 访问令牌。
type TokenResponse struct {
Token string `json:"token"`
}
// UpdateProfileRequest represents the request body for updating user profile.
// UpdateProfileRequest 表示更新用户个人信息的请求体。
//
// Fields:
// - Email: The user's email address.
// - Phone: The user's phone number.
//
// 字段:
// - Email: 用户的邮箱地址。
// - Phone: 用户的手机号码。
type UpdateProfileRequest struct {
Email string `json:"email"`
Phone string `json:"phone"`
Nickname string `json:"nickname"`
}
// ChangePasswordRequest represents the request body for changing password.
// ChangePasswordRequest 表示修改密码的请求体。
//
// Fields:
// - CurrentPassword: The user's current password.
// - NewPassword: The new password.
//
// 字段:
// - CurrentPassword: 用户当前的密码。
// - NewPassword: 新密码。
type ChangePasswordRequest struct {
CurrentPassword string `json:"currentPassword" binding:"required"`
NewPassword string `json:"newPassword" binding:"required,min=6"`
}
+159
View File
@@ -0,0 +1,159 @@
package auth
import (
"fmt"
"math/rand"
"strings"
"time"
"cloudnest/internal/domain/auth/entity"
"cloudnest/internal/domain/auth/repository"
"cloudnest/internal/pkg/crypto"
"cloudnest/internal/pkg/errors"
"cloudnest/internal/pkg/jwt"
)
type EmailVerifier interface {
VerifyCode(email, code string) bool
}
type AuthService struct {
userRepo repository.UserRepository
emailService EmailVerifier
jwtSecret string
jwtExpires int
}
func NewAuthService(userRepo repository.UserRepository, emailService EmailVerifier, jwtSecret string, jwtExpires int) *AuthService {
return &AuthService{
userRepo: userRepo,
emailService: emailService,
jwtSecret: jwtSecret,
jwtExpires: jwtExpires,
}
}
func generateUserCode() string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
r := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, 8)
for i := range b {
b[i] = charset[r.Intn(len(charset))]
}
return string(b)
}
func (s *AuthService) Register(username, password, email, nickname, emailCode string) error {
existing, err := s.userRepo.FindByUsername(username)
if err == nil && existing != nil {
return errors.Conflict("用户名已存在")
}
if email != "" {
existing, err := s.userRepo.FindByEmail(email)
if err == nil && existing != nil {
return errors.Conflict("邮箱已被注册")
}
if emailCode == "" {
return errors.BadRequest("邮箱验证码不能为空")
}
if !s.emailService.VerifyCode(email, emailCode) {
return errors.BadRequest("邮箱验证码错误")
}
}
hashed, err := crypto.HashPassword(password)
if err != nil {
return errors.InternalError("密码加密失败", err)
}
userCode := generateUserCode()
for {
existing, err := s.userRepo.FindByUserCode(userCode)
if err != nil || existing == nil {
break
}
userCode = generateUserCode()
}
user := entity.NewUser(username, hashed, userCode, nickname)
if email != "" {
user.Email = email
}
return s.userRepo.Create(user)
}
func (s *AuthService) Login(loginName, password string) (string, *entity.User, error) {
var user *entity.User
var err error
if strings.Contains(loginName, "@") {
user, err = s.userRepo.FindByEmail(loginName)
} else {
user, err = s.userRepo.FindByUsername(loginName)
}
if err != nil {
return "", nil, err
}
if !crypto.CheckPasswordHash(password, user.Password) {
return "", nil, errors.Unauthorized("用户名或密码错误")
}
expiresIn := time.Duration(s.jwtExpires) * time.Hour
token, err := jwt.GenerateToken(user.ID, user.Username, user.UserCode, s.jwtSecret, expiresIn)
return token, user, err
}
func (s *AuthService) CheckStorageLimit(userCode string, fileSize int64) error {
user, err := s.userRepo.FindByUserCode(userCode)
if err != nil {
return err
}
if user.StorageUsed+fileSize > user.StorageLimit {
return errors.BadRequest("存储空间不足,当前已使用 " + formatSize(user.StorageUsed) + ",限制 " + formatSize(user.StorageLimit))
}
return nil
}
func (s *AuthService) AddStorageUsed(userCode string, fileSize int64) error {
return s.userRepo.UpdateStorageUsed(userCode, fileSize)
}
func (s *AuthService) SubtractStorageUsed(userCode string, fileSize int64) error {
return s.userRepo.UpdateStorageUsed(userCode, -fileSize)
}
func (s *AuthService) UpdateProfile(userCode, email, phone, nickname string) error {
return s.userRepo.UpdateProfile(userCode, email, phone, nickname)
}
func (s *AuthService) ChangePassword(userCode, currentPassword, newPassword string) error {
user, err := s.userRepo.FindByUserCode(userCode)
if err != nil {
return err
}
if !crypto.CheckPasswordHash(currentPassword, user.Password) {
return errors.Unauthorized("当前密码不正确")
}
hashed, err := crypto.HashPassword(newPassword)
if err != nil {
return errors.InternalError("密码加密失败", err)
}
return s.userRepo.UpdatePassword(userCode, hashed)
}
func (s *AuthService) GetUserInfo(userCode string) (*entity.User, error) {
return s.userRepo.FindByUserCode(userCode)
}
func formatSize(bytes int64) string {
if bytes < 1024 {
return fmt.Sprintf("%d B", bytes)
} else if bytes < 1024*1024 {
return fmt.Sprintf("%.2f KB", float64(bytes)/1024)
} else if bytes < 1024*1024*1024 {
return fmt.Sprintf("%.2f MB", float64(bytes)/(1024*1024))
}
return fmt.Sprintf("%.2f GB", float64(bytes)/(1024*1024*1024))
}