代码升级
This commit is contained in:
parent
ff312fb5a7
commit
4f8707e661
31
config/config_default.yaml
Normal file
31
config/config_default.yaml
Normal file
@ -0,0 +1,31 @@
|
||||
# 默认配置文件
|
||||
server:
|
||||
port: 8999
|
||||
|
||||
database:
|
||||
host: "43.156.151.237"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "xn001624"
|
||||
dbname: "user_center"
|
||||
sslmode: "disable"
|
||||
|
||||
redis:
|
||||
host: "43.156.151.237"
|
||||
port: 6379
|
||||
password: "xn001624"
|
||||
db: 0
|
||||
rate_limit_window: 60
|
||||
rate_limit_max: 100
|
||||
|
||||
rabbitmq:
|
||||
host: "43.156.151.237"
|
||||
port: 5672
|
||||
username: "admin"
|
||||
password: "xn001624"
|
||||
exchange: "ly_system"
|
||||
queue: "ly_system_queue"
|
||||
|
||||
jwt:
|
||||
secret: "your-secret-key"
|
||||
expire: 86400 # 24小时
|
15
config/config_dev.yaml
Normal file
15
config/config_dev.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# 开发环境配置
|
||||
server:
|
||||
host: "192.168.1.154"
|
||||
|
||||
database:
|
||||
host: "192.168.1.154"
|
||||
password: "dev_password" # 开发环境数据库密码
|
||||
|
||||
redis:
|
||||
host: "192.168.1.154"
|
||||
password: "dev_redis_password" # 开发环境Redis密码
|
||||
|
||||
rabbitmq:
|
||||
host: "192.168.1.154"
|
||||
password: "dev_rabbitmq_password" # 开发环境RabbitMQ密码
|
23
config/config_prod.yaml
Normal file
23
config/config_prod.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
server:
|
||||
port: 8999
|
||||
database:
|
||||
host: "prod_host"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "prod_password"
|
||||
dbname: "user_center"
|
||||
sslmode: "disable"
|
||||
redis:
|
||||
host: "prod_host"
|
||||
port: 6379
|
||||
password: "prod_password"
|
||||
db: 0
|
||||
rate_limit_window: 60
|
||||
rate_limit_max: 100
|
||||
rabbitmq:
|
||||
host: "prod_host"
|
||||
port: 5672
|
||||
username: admin
|
||||
password: "prod_password"
|
||||
exchange: ly_system
|
||||
queue: ly_system_queue
|
15
config/config_test.yaml
Normal file
15
config/config_test.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# 测试环境配置
|
||||
server:
|
||||
host: "43.156.151.237"
|
||||
|
||||
database:
|
||||
host: "43.156.151.237"
|
||||
password: "test_password" # 测试环境数据库密码
|
||||
|
||||
redis:
|
||||
host: "43.156.151.237"
|
||||
password: "test_redis_password" # 测试环境Redis密码
|
||||
|
||||
rabbitmq:
|
||||
host: "43.156.151.237"
|
||||
password: "test_rabbitmq_password" # 测试环境RabbitMQ密码
|
59
controllers/user_controller.go
Normal file
59
controllers/user_controller.go
Normal file
@ -0,0 +1,59 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"ly.system.api/container"
|
||||
"ly.system.api/validators"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserController 用户控制器
|
||||
type UserController struct {
|
||||
container *container.Container
|
||||
}
|
||||
|
||||
// NewUserController 创建新的UserController实例
|
||||
func NewUserController(container *container.Container) *UserController {
|
||||
return &UserController{container: container}
|
||||
}
|
||||
|
||||
// Register 用户注册处理函数
|
||||
func (uc *UserController) Register(c *gin.Context) {
|
||||
var req validators.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用用户服务进行注册
|
||||
err := uc.container.GetUserService().Register(req.Username, req.Password, req.Email)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "用户注册成功"})
|
||||
}
|
||||
|
||||
// Login 用户登录处理函数
|
||||
func (uc *UserController) Login(c *gin.Context) {
|
||||
var req validators.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用用户服务进行登录
|
||||
token, err := uc.container.GetUserService().Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "登录成功",
|
||||
"token": token,
|
||||
})
|
||||
}
|
72
internal/cache/redis_service.go
vendored
Normal file
72
internal/cache/redis_service.go
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/v8"
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RedisService 定义 Redis 服务接口
|
||||
// 提供对 Redis 的基本操作
|
||||
// 如 Incr 和 Expire
|
||||
// 以及其他常用操作
|
||||
type RedisService interface {
|
||||
Incr(key string) (int64, error)
|
||||
Expire(key string, expiration time.Duration) error
|
||||
Set(key string, value interface{}, expiration time.Duration) error
|
||||
Del(keys ...string) error
|
||||
Get(key string) (string, error)
|
||||
}
|
||||
|
||||
// redisService 实现 RedisService 接口
|
||||
// 使用 go-redis 包进行操作
|
||||
// 提供对 Redis 的基本操作
|
||||
// 如 Incr 和 Expire
|
||||
// 以及其他常用操作
|
||||
type redisService struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
// NewRedisService 创建 Redis 服务实例
|
||||
// 参数 client: Redis 客户端实例
|
||||
// 返回: 实现 RedisService 接口的实例
|
||||
func NewRedisService(client *redis.Client) RedisService {
|
||||
return &redisService{client: client}
|
||||
}
|
||||
|
||||
// Incr 增加键的值
|
||||
// 如果键不存在,则将其初始化为 0
|
||||
// 然后增加 1
|
||||
// 返回增加后的值
|
||||
func (r *redisService) Incr(key string) (int64, error) {
|
||||
return r.client.Incr(context.Background(), key).Result()
|
||||
}
|
||||
|
||||
// Expire 设置键的过期时间
|
||||
// 如果键不存在,则不执行任何操作
|
||||
// 返回错误信息
|
||||
func (r *redisService) Expire(key string, expiration time.Duration) error {
|
||||
return r.client.Expire(context.Background(), key, expiration).Err()
|
||||
}
|
||||
|
||||
// Set 设置键的值
|
||||
// 如果键不存在,则创建一个新键
|
||||
// 如果键存在,则覆盖原值
|
||||
// 返回错误信息
|
||||
func (r *redisService) Set(key string, value interface{}, expiration time.Duration) error {
|
||||
return r.client.Set(context.Background(), key, value, expiration).Err()
|
||||
}
|
||||
|
||||
// Del 删除键
|
||||
// 如果键不存在,则不执行任何操作
|
||||
// 返回错误信息
|
||||
func (r *redisService) Del(keys ...string) error {
|
||||
return r.client.Del(context.Background(), keys...).Err()
|
||||
}
|
||||
|
||||
// Get 获取键的值
|
||||
// 如果键不存在,则返回空字符串
|
||||
// 返回键的值和错误信息
|
||||
func (r *redisService) Get(key string) (string, error) {
|
||||
return r.client.Get(context.Background(), key).Result()
|
||||
}
|
12
internal/file/domain/file.go
Normal file
12
internal/file/domain/file.go
Normal file
@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
// File 文件领域模型
|
||||
type File struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Filename string `json:"filename"`
|
||||
OriginalName string `json:"original_name"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Size int64 `json:"size"`
|
||||
Status string `json:"status"`
|
||||
UploadPath string `json:"upload_path"`
|
||||
}
|
40
internal/file/domain/file_service.go
Normal file
40
internal/file/domain/file_service.go
Normal file
@ -0,0 +1,40 @@
|
||||
package domain
|
||||
|
||||
// FileInfo 文件信息
|
||||
// 包含文件的基本信息
|
||||
// 例如文件名、大小、类型等
|
||||
// 用于文件上传和下载验证
|
||||
// 以及文件操作权限验证
|
||||
// 例如文件名、大小、类型等
|
||||
// 用于文件上传和下载验证
|
||||
// 以及文件操作权限验证
|
||||
type FileInfo struct {
|
||||
Filename string
|
||||
Size int64
|
||||
Type string
|
||||
}
|
||||
|
||||
// ChunkInfo 分片信息
|
||||
// 包含分片的基本信息
|
||||
// 例如分片索引、大小、哈希等
|
||||
// 用于分片上传验证
|
||||
type ChunkInfo struct {
|
||||
Index int
|
||||
Size int64
|
||||
Hash string
|
||||
Total int
|
||||
}
|
||||
|
||||
// FileDomainService 文件管理领域服务,处理文件相关的核心业务规则
|
||||
type FileDomainService interface {
|
||||
// ValidateFileUpload 验证文件上传请求
|
||||
ValidateFileUpload(fileInfo *FileInfo) error
|
||||
// ValidateChunkUpload 验证分片上传请求
|
||||
ValidateChunkUpload(chunkInfo *ChunkInfo) error
|
||||
// CalculateFileHash 计算文件哈希
|
||||
CalculateFileHash(filePath string) (string, error)
|
||||
// ValidateFileDownload 验证文件下载请求
|
||||
ValidateFileDownload(fileID uint, userID uint) error
|
||||
// ValidateFilePermissions 验证文件操作权限
|
||||
ValidateFilePermissions(fileID uint, userID uint, operation string) error
|
||||
}
|
116
internal/file/domain/file_service_impl.go
Normal file
116
internal/file/domain/file_service_impl.go
Normal file
@ -0,0 +1,116 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxFileSize = 10485760 // 10MB
|
||||
MaxChunkSize = 1048576 // 1MB
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidFileSize = errors.New("文件大小无效")
|
||||
ErrInvalidFileType = errors.New("文件类型无效")
|
||||
ErrInvalidChunkIndex = errors.New("分片索引无效")
|
||||
ErrInvalidChunkSize = errors.New("分片大小无效")
|
||||
ErrFileNotFound = errors.New("文件不存在")
|
||||
ErrPermissionDenied = errors.New("权限拒绝")
|
||||
)
|
||||
|
||||
// FileDomainServiceImpl 文件管理领域服务实现
|
||||
type FileDomainServiceImpl struct{}
|
||||
|
||||
// NewFileDomainService 创建文件管理领域服务实例
|
||||
func NewFileDomainService() FileDomainService {
|
||||
return &FileDomainServiceImpl{}
|
||||
}
|
||||
|
||||
// ValidateFileUpload 验证文件上传请求
|
||||
func (s *FileDomainServiceImpl) ValidateFileUpload(fileInfo *FileInfo) error {
|
||||
// 验证文件大小
|
||||
if fileInfo.Size <= 0 || fileInfo.Size > MaxFileSize {
|
||||
return ErrInvalidFileSize
|
||||
}
|
||||
// 验证文件类型
|
||||
if !IsAllowedFileType(fileInfo.Type) {
|
||||
return ErrInvalidFileType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateChunkUpload 验证分片上传请求
|
||||
func (s *FileDomainServiceImpl) ValidateChunkUpload(chunkInfo *ChunkInfo) error {
|
||||
// 验证分片序号
|
||||
if chunkInfo.Index < 0 || chunkInfo.Index >= chunkInfo.Total {
|
||||
return ErrInvalidChunkIndex
|
||||
}
|
||||
// 验证分片大小
|
||||
if chunkInfo.Size <= 0 || chunkInfo.Size > MaxChunkSize {
|
||||
return ErrInvalidChunkSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateFileHash 计算文件哈希
|
||||
func (s *FileDomainServiceImpl) CalculateFileHash(filePath string) (string, error) {
|
||||
// 读取文件内容
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 计算SHA256哈希
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:]), nil
|
||||
}
|
||||
|
||||
// ValidateFileDownload 验证文件下载请求
|
||||
func (s *FileDomainServiceImpl) ValidateFileDownload(fileID uint, userID uint) error {
|
||||
// 验证文件是否存在
|
||||
if !FileExists(fileID) {
|
||||
return ErrFileNotFound
|
||||
}
|
||||
// 验证用户下载权限
|
||||
return s.ValidateFilePermissions(fileID, userID, "download")
|
||||
}
|
||||
|
||||
// ValidateFilePermissions 验证文件操作权限
|
||||
func (s *FileDomainServiceImpl) ValidateFilePermissions(fileID uint, userID uint, operation string) error {
|
||||
// 检查用户是否是文件所有者
|
||||
if !IsFileOwner(fileID, userID) {
|
||||
// 检查用户是否有共享权限
|
||||
if !HasSharedPermission(fileID, userID, operation) {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsAllowedFileType(fileType string) bool {
|
||||
// 假设允许的文件类型
|
||||
allowedTypes := []string{"image/jpeg", "image/png", "application/pdf"}
|
||||
for _, t := range allowedTypes {
|
||||
if fileType == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FileExists(fileID uint) bool {
|
||||
// TODO: 实现文件存在检查逻辑
|
||||
return true
|
||||
}
|
||||
|
||||
func IsFileOwner(fileID uint, userID uint) bool {
|
||||
// TODO: 实现文件所有者检查逻辑
|
||||
return true
|
||||
}
|
||||
|
||||
func HasSharedPermission(fileID uint, userID uint, operation string) bool {
|
||||
// TODO: 实现共享权限检查逻辑
|
||||
return true
|
||||
}
|
61
internal/file/repository/file_repository.go
Normal file
61
internal/file/repository/file_repository.go
Normal file
@ -0,0 +1,61 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"ly.system.api/internal/file/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FileRepository 文件仓储接口
|
||||
type FileRepository interface {
|
||||
Create(file *domain.File) error
|
||||
FindByID(id uint) (*domain.File, error)
|
||||
Update(file *domain.File) error
|
||||
Delete(id uint) error
|
||||
FindByFilename(filename string) (*domain.File, error)
|
||||
}
|
||||
|
||||
// fileRepository 文件仓储实现
|
||||
type fileRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewFileRepository 创建文件仓储实例
|
||||
func NewFileRepository(db *gorm.DB) FileRepository {
|
||||
return &fileRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建文件记录
|
||||
func (r *fileRepository) Create(file *domain.File) error {
|
||||
return r.db.Create(file).Error
|
||||
}
|
||||
|
||||
// FindByID 根据ID查找文件
|
||||
func (r *fileRepository) FindByID(id uint) (*domain.File, error) {
|
||||
var file domain.File
|
||||
err := r.db.First(&file, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
// Update 更新文件信息
|
||||
func (r *fileRepository) Update(file *domain.File) error {
|
||||
return r.db.Save(file).Error
|
||||
}
|
||||
|
||||
// Delete 删除文件记录
|
||||
func (r *fileRepository) Delete(id uint) error {
|
||||
return r.db.Delete(&domain.File{}, id).Error
|
||||
}
|
||||
|
||||
// FindByFilename 根据文件名查找文件
|
||||
func (r *fileRepository) FindByFilename(filename string) (*domain.File, error) {
|
||||
var file domain.File
|
||||
err := r.db.Where("filename = ?", filename).First(&file).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file, nil
|
||||
}
|
190
internal/file/service/file_service.go
Normal file
190
internal/file/service/file_service.go
Normal file
@ -0,0 +1,190 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"ly.system.api/internal/file/domain"
|
||||
"ly.system.api/internal/file/repository"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"ly.system.api/internal/cache"
|
||||
)
|
||||
|
||||
// FileService 文件服务接口
|
||||
type FileService interface {
|
||||
UploadFile(file *multipart.FileHeader, chunkNumber, totalChunks, chunkSize int, fileID string) error
|
||||
DownloadFile(id uint) (*domain.File, error)
|
||||
GetUploadProgress(fileID string) (int, error)
|
||||
CombineChunks(tempPath string, totalChunks int) (string, error)
|
||||
}
|
||||
|
||||
// fileService 文件服务实现
|
||||
type fileService struct {
|
||||
fileRepo repository.FileRepository
|
||||
redisService cache.RedisService
|
||||
uploadDir string
|
||||
}
|
||||
|
||||
// NewFileService 创建文件服务实例
|
||||
func NewFileService(fileRepo repository.FileRepository, redisService cache.RedisService, uploadDir string) FileService {
|
||||
return &fileService{
|
||||
fileRepo: fileRepo,
|
||||
redisService: redisService,
|
||||
uploadDir: uploadDir,
|
||||
}
|
||||
}
|
||||
|
||||
// UploadFile 处理文件上传
|
||||
func (s *fileService) UploadFile(file *multipart.FileHeader, chunkNumber, totalChunks, chunkSize int, fileID string) error {
|
||||
// 验证参数
|
||||
if file == nil || chunkNumber < 1 || totalChunks < 1 || chunkSize < 1 || fileID == "" {
|
||||
return errors.New("无效的上传参数")
|
||||
}
|
||||
|
||||
// 保存分片
|
||||
tempPath, err := s.saveChunk(file, chunkNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("保存分片失败: %v", err)
|
||||
}
|
||||
|
||||
// 更新上传进度
|
||||
progress := int(float64(chunkNumber) / float64(totalChunks) * 100)
|
||||
if err := s.redisService.Set("upload_progress:"+fileID, strconv.Itoa(progress), 24*time.Hour); err != nil {
|
||||
return fmt.Errorf("更新进度失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果是最后一个分片,合并文件
|
||||
if chunkNumber == totalChunks {
|
||||
finalPath, err := s.CombineChunks(tempPath, totalChunks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("合并文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建文件记录
|
||||
fileInfo, err := os.Stat(finalPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取文件信息失败: %v", err)
|
||||
}
|
||||
|
||||
fileRecord := &domain.File{
|
||||
Filename: filepath.Base(finalPath),
|
||||
OriginalName: file.Filename,
|
||||
Size: fileInfo.Size(),
|
||||
MimeType: file.Header.Get("Content-Type"),
|
||||
Status: "completed",
|
||||
UploadPath: finalPath,
|
||||
}
|
||||
|
||||
if err := s.fileRepo.Create(fileRecord); err != nil {
|
||||
// 如果数据库保存失败,删除已上传的文件
|
||||
os.Remove(finalPath)
|
||||
return fmt.Errorf("保存文件记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 清除进度
|
||||
s.redisService.Del("upload_progress:" + fileID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadFile 处理文件下载
|
||||
func (s *fileService) DownloadFile(id uint) (*domain.File, error) {
|
||||
file, err := s.fileRepo.FindByID(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("文件不存在: %v", err)
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(file.UploadPath); err != nil {
|
||||
return nil, fmt.Errorf("文件不存在或已被删除: %v", err)
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// GetUploadProgress 获取上传进度
|
||||
func (s *fileService) GetUploadProgress(fileID string) (int, error) {
|
||||
progress, err := s.redisService.Get("upload_progress:" + fileID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
progressInt, err := strconv.Atoi(progress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return progressInt, nil
|
||||
}
|
||||
|
||||
// saveChunk 保存文件分片
|
||||
func (s *fileService) saveChunk(file *multipart.FileHeader, chunkNumber int) (string, error) {
|
||||
// 创建临时目录
|
||||
tempDir := filepath.Join(s.uploadDir, "temp")
|
||||
if err := os.MkdirAll(tempDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 生成临时文件名
|
||||
tempFilename := filepath.Join(tempDir, file.Filename+".part"+fmt.Sprint(chunkNumber))
|
||||
|
||||
// 打开上传的文件分片
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 创建目标文件
|
||||
dst, err := os.Create(tempFilename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// 复制文件内容
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tempFilename, nil
|
||||
}
|
||||
|
||||
// CombineChunks 合并文件分片
|
||||
func (s *fileService) CombineChunks(tempPath string, totalChunks int) (string, error) {
|
||||
// 获取最终文件名
|
||||
finalFilename := filepath.Join(s.uploadDir, filepath.Base(tempPath[:len(tempPath)-5]))
|
||||
|
||||
// 创建最终文件
|
||||
finalFile, err := os.Create(finalFilename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer finalFile.Close()
|
||||
|
||||
// 合并所有分片
|
||||
for i := 1; i <= totalChunks; i++ {
|
||||
chunkPath := tempPath[:len(tempPath)-1] + fmt.Sprint(i)
|
||||
chunkFile, err := os.Open(chunkPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(finalFile, chunkFile); err != nil {
|
||||
chunkFile.Close()
|
||||
return "", err
|
||||
}
|
||||
chunkFile.Close()
|
||||
|
||||
// 删除临时分片文件
|
||||
os.Remove(chunkPath)
|
||||
}
|
||||
|
||||
return finalFilename, nil
|
||||
}
|
71
internal/lottery/domain/lottery_service.go
Normal file
71
internal/lottery/domain/lottery_service.go
Normal file
@ -0,0 +1,71 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LotteryDomainService 抽奖领域服务,处理抽奖相关的核心业务规则
|
||||
type LotteryDomainService interface {
|
||||
// ValidateLotteryRules 验证抽奖规则
|
||||
ValidateLotteryRules(rules *LotteryRules) error
|
||||
// CheckUserEligibility 检查用户是否有资格参与抽奖
|
||||
CheckUserEligibility(userID uint, rules *LotteryRules) error
|
||||
// CalculateWinningProbability 计算中奖概率
|
||||
CalculateWinningProbability(userID uint, rules *LotteryRules) float64
|
||||
// ValidatePrizeDistribution 验证奖品分配是否合理
|
||||
ValidatePrizeDistribution(prizes []Prize) error
|
||||
}
|
||||
|
||||
// LotteryRules 抽奖规则
|
||||
type LotteryRules struct {
|
||||
MaxEntries int
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
MaxDraws int
|
||||
BaseProbability float64
|
||||
}
|
||||
|
||||
func (r *LotteryRules) IsUserAllowed(userID uint) bool {
|
||||
// 假设所有用户都被允许
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *LotteryRules) HasExceededDrawLimit(userID uint) bool {
|
||||
// 假设没有用户超出抽奖次数
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *LotteryRules) GetUserLevelMultiplier(userID uint) float64 {
|
||||
// 假设用户等级乘数为1.0
|
||||
return 1.0
|
||||
}
|
||||
|
||||
func (r *LotteryRules) GetTimeBasedMultiplier() float64 {
|
||||
// 假设时间基础乘数为1.0
|
||||
return 1.0
|
||||
}
|
||||
|
||||
// Prize 奖品
|
||||
type Prize struct {
|
||||
Name string
|
||||
Value int
|
||||
Probability float64
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrInvalidTimeRange 无效的时间范围
|
||||
ErrInvalidTimeRange = errors.New("无效的时间范围")
|
||||
// ErrInvalidDrawLimit 抽奖次数限制无效
|
||||
ErrInvalidDrawLimit = errors.New("抽奖次数限制无效")
|
||||
// ErrUserNotAllowed 用户不被允许参与抽奖
|
||||
ErrUserNotAllowed = errors.New("用户不被允许参与抽奖")
|
||||
// ErrDrawLimitExceeded 抽奖次数超出限制
|
||||
ErrDrawLimitExceeded = errors.New("抽奖次数超出限制")
|
||||
// ErrNoPrizes 没有奖品可分配
|
||||
ErrNoPrizes = errors.New("没有奖品可分配")
|
||||
// ErrInvalidPrizeProbability 奖品概率无效
|
||||
ErrInvalidPrizeProbability = errors.New("奖品概率无效")
|
||||
// ErrInvalidTotalProbability 奖品概率总和无效
|
||||
ErrInvalidTotalProbability = errors.New("奖品概率总和无效")
|
||||
)
|
69
internal/lottery/domain/lottery_service_impl.go
Normal file
69
internal/lottery/domain/lottery_service_impl.go
Normal file
@ -0,0 +1,69 @@
|
||||
package domain
|
||||
|
||||
|
||||
|
||||
// LotteryDomainServiceImpl 抽奖领域服务实现
|
||||
type LotteryDomainServiceImpl struct{}
|
||||
|
||||
// NewLotteryDomainService 创建抽奖领域服务实例
|
||||
func NewLotteryDomainService() LotteryDomainService {
|
||||
return &LotteryDomainServiceImpl{}
|
||||
}
|
||||
|
||||
// ValidateLotteryRules 验证抽奖规则
|
||||
func (s *LotteryDomainServiceImpl) ValidateLotteryRules(rules *LotteryRules) error {
|
||||
// 验证抽奖时间范围
|
||||
if rules.StartTime.After(rules.EndTime) {
|
||||
return ErrInvalidTimeRange
|
||||
}
|
||||
// 验证抽奖次数限制
|
||||
if rules.MaxDraws <= 0 {
|
||||
return ErrInvalidDrawLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckUserEligibility 检查用户是否有资格参与抽奖
|
||||
func (s *LotteryDomainServiceImpl) CheckUserEligibility(userID uint, rules *LotteryRules) error {
|
||||
// 检查用户状态
|
||||
if !rules.IsUserAllowed(userID) {
|
||||
return ErrUserNotAllowed
|
||||
}
|
||||
// 检查抽奖次数
|
||||
if rules.HasExceededDrawLimit(userID) {
|
||||
return ErrDrawLimitExceeded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateWinningProbability 计算中奖概率
|
||||
func (s *LotteryDomainServiceImpl) CalculateWinningProbability(userID uint, rules *LotteryRules) float64 {
|
||||
// 基础中奖概率
|
||||
baseProbability := rules.BaseProbability
|
||||
// 根据用户等级调整概率
|
||||
levelMultiplier := rules.GetUserLevelMultiplier(userID)
|
||||
// 根据活动时间调整概率
|
||||
timeMultiplier := rules.GetTimeBasedMultiplier()
|
||||
|
||||
return baseProbability * levelMultiplier * timeMultiplier
|
||||
}
|
||||
|
||||
// ValidatePrizeDistribution 验证奖品分配是否合理
|
||||
func (s *LotteryDomainServiceImpl) ValidatePrizeDistribution(prizes []Prize) error {
|
||||
// 验证奖品总数
|
||||
if len(prizes) == 0 {
|
||||
return ErrNoPrizes
|
||||
}
|
||||
// 验证奖品概率总和是否为100%
|
||||
totalProbability := 0.0
|
||||
for _, prize := range prizes {
|
||||
if prize.Probability < 0 || prize.Probability > 1 {
|
||||
return ErrInvalidPrizeProbability
|
||||
}
|
||||
totalProbability += prize.Probability
|
||||
}
|
||||
if totalProbability != 1.0 {
|
||||
return ErrInvalidTotalProbability
|
||||
}
|
||||
return nil
|
||||
}
|
10
internal/lottery/domain/song.go
Normal file
10
internal/lottery/domain/song.go
Normal file
@ -0,0 +1,10 @@
|
||||
package domain
|
||||
|
||||
// Song 歌曲领域模型
|
||||
type Song struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Title string `json:"title"`
|
||||
Artist string `json:"artist"`
|
||||
Duration int `json:"duration"`
|
||||
Status string `json:"status"`
|
||||
}
|
72
internal/lottery/repository/song_repository.go
Normal file
72
internal/lottery/repository/song_repository.go
Normal file
@ -0,0 +1,72 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"ly.system.api/internal/lottery/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SongRepository 歌曲仓储接口
|
||||
type SongRepository interface {
|
||||
Create(song *domain.Song) error
|
||||
FindByID(id uint) (*domain.Song, error)
|
||||
FindAll() ([]*domain.Song, error)
|
||||
Update(song *domain.Song) error
|
||||
Delete(id uint) error
|
||||
FindAvailable() ([]*domain.Song, error)
|
||||
}
|
||||
|
||||
// songRepository 歌曲仓储实现
|
||||
type songRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewSongRepository 创建歌曲仓储实例
|
||||
func NewSongRepository(db *gorm.DB) SongRepository {
|
||||
return &songRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建歌曲
|
||||
func (r *songRepository) Create(song *domain.Song) error {
|
||||
return r.db.Create(song).Error
|
||||
}
|
||||
|
||||
// FindByID 根据ID查找歌曲
|
||||
func (r *songRepository) FindByID(id uint) (*domain.Song, error) {
|
||||
var song domain.Song
|
||||
err := r.db.First(&song, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &song, nil
|
||||
}
|
||||
|
||||
// FindAll 获取所有歌曲
|
||||
func (r *songRepository) FindAll() ([]*domain.Song, error) {
|
||||
var songs []*domain.Song
|
||||
err := r.db.Find(&songs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return songs, nil
|
||||
}
|
||||
|
||||
// Update 更新歌曲信息
|
||||
func (r *songRepository) Update(song *domain.Song) error {
|
||||
return r.db.Save(song).Error
|
||||
}
|
||||
|
||||
// Delete 删除歌曲
|
||||
func (r *songRepository) Delete(id uint) error {
|
||||
return r.db.Delete(&domain.Song{}, id).Error
|
||||
}
|
||||
|
||||
// FindAvailable 获取可用歌曲列表
|
||||
func (r *songRepository) FindAvailable() ([]*domain.Song, error) {
|
||||
var songs []*domain.Song
|
||||
err := r.db.Where("status = ?", "available").Find(&songs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return songs, nil
|
||||
}
|
90
internal/lottery/service/lottery_service.go
Normal file
90
internal/lottery/service/lottery_service.go
Normal file
@ -0,0 +1,90 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"ly.system.api/internal/lottery/domain"
|
||||
"ly.system.api/internal/lottery/repository"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LotteryService 抽奖服务接口
|
||||
type LotteryService interface {
|
||||
Draw() (*domain.Song, error)
|
||||
GetAvailableSongs() ([]*domain.Song, error)
|
||||
AddSong(song *domain.Song) error
|
||||
UpdateSong(song *domain.Song) error
|
||||
DeleteSong(id uint) error
|
||||
}
|
||||
|
||||
// lotteryService 抽奖服务实现
|
||||
type lotteryService struct {
|
||||
songRepo repository.SongRepository
|
||||
}
|
||||
|
||||
// NewLotteryService 创建抽奖服务实例
|
||||
func NewLotteryService(songRepo repository.SongRepository) LotteryService {
|
||||
return &lotteryService{
|
||||
songRepo: songRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw 随机抽取一首歌曲
|
||||
func (s *lotteryService) Draw() (*domain.Song, error) {
|
||||
// 获取所有可用歌曲
|
||||
songs, err := s.songRepo.FindAvailable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(songs) == 0 {
|
||||
return nil, errors.New("没有可用的歌曲")
|
||||
}
|
||||
|
||||
// 使用当前时间作为随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
// 随机选择一首歌曲
|
||||
selectedSong := songs[rand.Intn(len(songs))]
|
||||
|
||||
return selectedSong, nil
|
||||
}
|
||||
|
||||
// GetAvailableSongs 获取所有可用歌曲
|
||||
func (s *lotteryService) GetAvailableSongs() ([]*domain.Song, error) {
|
||||
return s.songRepo.FindAvailable()
|
||||
}
|
||||
|
||||
// AddSong 添加新歌曲
|
||||
func (s *lotteryService) AddSong(song *domain.Song) error {
|
||||
// 设置歌曲状态为可用
|
||||
song.Status = "available"
|
||||
return s.songRepo.Create(song)
|
||||
}
|
||||
|
||||
// UpdateSong 更新歌曲信息
|
||||
func (s *lotteryService) UpdateSong(song *domain.Song) error {
|
||||
// 检查歌曲是否存在
|
||||
existingSong, err := s.songRepo.FindByID(song.ID)
|
||||
if err != nil {
|
||||
return errors.New("歌曲不存在")
|
||||
}
|
||||
|
||||
// 更新歌曲信息
|
||||
existingSong.Title = song.Title
|
||||
existingSong.Artist = song.Artist
|
||||
existingSong.Duration = song.Duration
|
||||
existingSong.Status = song.Status
|
||||
|
||||
return s.songRepo.Update(existingSong)
|
||||
}
|
||||
|
||||
// DeleteSong 删除歌曲
|
||||
func (s *lotteryService) DeleteSong(id uint) error {
|
||||
// 检查歌曲是否存在
|
||||
_, err := s.songRepo.FindByID(id)
|
||||
if err != nil {
|
||||
return errors.New("歌曲不存在")
|
||||
}
|
||||
|
||||
return s.songRepo.Delete(id)
|
||||
}
|
49
internal/menu/service.go
Normal file
49
internal/menu/service.go
Normal file
@ -0,0 +1,49 @@
|
||||
package menu
|
||||
|
||||
// MenuService 菜单服务接口
|
||||
type MenuService interface {
|
||||
GetMenuTree() ([]MenuItem, error)
|
||||
CreateMenu(item MenuItem) error
|
||||
UpdateMenu(item MenuItem) error
|
||||
DeleteMenu(id string) error
|
||||
}
|
||||
|
||||
// MenuItem 菜单项结构
|
||||
type MenuItem struct {
|
||||
ID string `json:"id"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Name string `json:"name"`
|
||||
Order int `json:"order"`
|
||||
}
|
||||
|
||||
// menuService 实现 MenuService 接口
|
||||
type menuService struct {}
|
||||
|
||||
// NewMenuService 创建新的菜单服务实例
|
||||
func NewMenuService() MenuService {
|
||||
return &menuService{}
|
||||
}
|
||||
|
||||
// GetMenuTree 获取菜单树
|
||||
func (s *menuService) GetMenuTree() ([]MenuItem, error) {
|
||||
// 实现获取菜单树的逻辑
|
||||
return []MenuItem{}, nil
|
||||
}
|
||||
|
||||
// CreateMenu 创建菜单
|
||||
func (s *menuService) CreateMenu(item MenuItem) error {
|
||||
// 实现创建菜单的逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateMenu 更新菜单
|
||||
func (s *menuService) UpdateMenu(item MenuItem) error {
|
||||
// 实现更新菜单的逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMenu 删除菜单
|
||||
func (s *menuService) DeleteMenu(id string) error {
|
||||
// 实现删除菜单的逻辑
|
||||
return nil
|
||||
}
|
38
internal/user/domain/user.go
Normal file
38
internal/user/domain/user.go
Normal file
@ -0,0 +1,38 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"ly.system.api/pkg/auth"
|
||||
)
|
||||
|
||||
// 定义缺失的错误
|
||||
var (
|
||||
ErrInvalidUsername = errors.New("无效的用户名")
|
||||
ErrInvalidPassword = errors.New("无效的密码")
|
||||
ErrInvalidOldPassword = errors.New("旧密码不正确")
|
||||
ErrInvalidNewPassword = errors.New("新密码不符合要求")
|
||||
)
|
||||
|
||||
// User 用户领域模型
|
||||
// 添加缺失的字段和方法
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Username string `json:"username" gorm:"uniqueIndex"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// ValidatePassword 验证密码
|
||||
func (u *User) ValidatePassword(password string) bool {
|
||||
// 使用authService进行密码验证
|
||||
authService := auth.NewAuthService()
|
||||
return authService.ComparePasswords(u.Password, password)
|
||||
}
|
||||
|
||||
// 定义用户状态常量
|
||||
const (
|
||||
UserStatusActive = 1
|
||||
)
|
11
internal/user/domain/user_service.go
Normal file
11
internal/user/domain/user_service.go
Normal file
@ -0,0 +1,11 @@
|
||||
package domain
|
||||
|
||||
// UserDomainService 用户领域服务,处理用户相关的核心业务规则
|
||||
type UserDomainService interface {
|
||||
// ValidateUserRegistration 验证用户注册信息
|
||||
ValidateUserRegistration(user *User) error
|
||||
// ValidatePasswordChange 验证密码修改
|
||||
ValidatePasswordChange(user *User, oldPassword, newPassword string) error
|
||||
// EnrichUserProfile 丰富用户信息
|
||||
EnrichUserProfile(user *User) error
|
||||
}
|
48
internal/user/domain/user_service_impl.go
Normal file
48
internal/user/domain/user_service_impl.go
Normal file
@ -0,0 +1,48 @@
|
||||
package domain
|
||||
|
||||
// UserDomainServiceImpl 用户领域服务实现
|
||||
type UserDomainServiceImpl struct {}
|
||||
|
||||
// NewUserDomainService 创建用户领域服务实例
|
||||
func NewUserDomainService() UserDomainService {
|
||||
return &UserDomainServiceImpl{}
|
||||
}
|
||||
|
||||
// ValidateUserRegistration 验证用户注册信息
|
||||
func (s *UserDomainServiceImpl) ValidateUserRegistration(user *User) error {
|
||||
// 验证用户名长度
|
||||
if len(user.Username) < 3 || len(user.Username) > 20 {
|
||||
return ErrInvalidUsername
|
||||
}
|
||||
// 验证密码强度
|
||||
if len(user.Password) < 6 || len(user.Password) > 20 {
|
||||
return ErrInvalidPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePasswordChange 验证密码修改
|
||||
func (s *UserDomainServiceImpl) ValidatePasswordChange(user *User, oldPassword, newPassword string) error {
|
||||
// 验证旧密码是否正确
|
||||
if !user.ValidatePassword(oldPassword) {
|
||||
return ErrInvalidOldPassword
|
||||
}
|
||||
// 验证新密码强度
|
||||
if len(newPassword) < 6 || len(newPassword) > 20 {
|
||||
return ErrInvalidNewPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnrichUserProfile 丰富用户信息
|
||||
func (s *UserDomainServiceImpl) EnrichUserProfile(user *User) error {
|
||||
// 设置默认头像
|
||||
if user.Avatar == "" {
|
||||
user.Avatar = "default_avatar.png"
|
||||
}
|
||||
// 设置用户状态
|
||||
if user.Status == 0 {
|
||||
user.Status = UserStatusActive
|
||||
}
|
||||
return nil
|
||||
}
|
60
internal/user/repository/user_repository.go
Normal file
60
internal/user/repository/user_repository.go
Normal file
@ -0,0 +1,60 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"ly.system.api/internal/user/domain"
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储接口
|
||||
type UserRepository interface {
|
||||
Create(user *domain.User) error
|
||||
FindByID(id uint) (*domain.User, error)
|
||||
FindByUsername(username string) (*domain.User, error)
|
||||
Update(user *domain.User) error
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
// userRepository 用户仓储实现
|
||||
type userRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserRepository 创建用户仓储实例
|
||||
func NewUserRepository(db *gorm.DB) UserRepository {
|
||||
return &userRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (r *userRepository) Create(user *domain.User) error {
|
||||
return r.db.Create(user).Error
|
||||
}
|
||||
|
||||
// FindByID 根据ID查找用户
|
||||
func (r *userRepository) FindByID(id uint) (*domain.User, error) {
|
||||
var user domain.User
|
||||
err := r.db.First(&user, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// FindByUsername 根据用户名查找用户
|
||||
func (r *userRepository) FindByUsername(username string) (*domain.User, error) {
|
||||
var user domain.User
|
||||
err := r.db.Where("username = ?", username).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Update 更新用户信息
|
||||
func (r *userRepository) Update(user *domain.User) error {
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *userRepository) Delete(id uint) error {
|
||||
return r.db.Delete(&domain.User{}, id).Error
|
||||
}
|
91
internal/user/service/user_service.go
Normal file
91
internal/user/service/user_service.go
Normal file
@ -0,0 +1,91 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"ly.system.api/internal/user/domain"
|
||||
"ly.system.api/internal/user/repository"
|
||||
"ly.system.api/pkg/auth"
|
||||
)
|
||||
|
||||
// UserService 用户服务接口
|
||||
type UserService interface {
|
||||
Register(username, password, email string) error
|
||||
Login(username, password string) (string, error)
|
||||
GetUserInfo(id uint) (*domain.User, error)
|
||||
UpdateUser(user *domain.User) error
|
||||
DeleteUser(id uint) error
|
||||
}
|
||||
|
||||
// userService 用户服务实现
|
||||
type userService struct {
|
||||
userRepo repository.UserRepository
|
||||
authService auth.Service
|
||||
}
|
||||
|
||||
// NewUserService 创建用户服务实例
|
||||
func NewUserService(userRepo repository.UserRepository, authService auth.Service) UserService {
|
||||
return &userService{
|
||||
userRepo: userRepo,
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *userService) Register(username, password, email string) error {
|
||||
// 检查用户名是否已存在
|
||||
if _, err := s.userRepo.FindByUsername(username); err == nil {
|
||||
return errors.New("用户名已存在")
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
hashedPassword, err := s.authService.HashPassword(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
Username: username,
|
||||
Password: hashedPassword,
|
||||
Email: email,
|
||||
Role: "user",
|
||||
}
|
||||
|
||||
return s.userRepo.Create(user)
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *userService) Login(username, password string) (string, error) {
|
||||
// 查找用户
|
||||
user, err := s.userRepo.FindByUsername(username)
|
||||
if err != nil {
|
||||
return "", errors.New("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !s.authService.ComparePasswords(user.Password, password) {
|
||||
return "", errors.New("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
token, err := s.authService.GenerateToken(user.ID, user.Username, user.Role)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (s *userService) GetUserInfo(id uint) (*domain.User, error) {
|
||||
return s.userRepo.FindByID(id)
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息
|
||||
func (s *userService) UpdateUser(user *domain.User) error {
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (s *userService) DeleteUser(id uint) error {
|
||||
return s.userRepo.Delete(id)
|
||||
}
|
49
middlewares/jwt_auth.go
Normal file
49
middlewares/jwt_auth.go
Normal file
@ -0,0 +1,49 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"ly.system.api/utils"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// JWTAuth JWT认证中间件
|
||||
func JWTAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头获取token
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"message": "未提供认证令牌",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查Bearer token格式
|
||||
const bearerPrefix = "Bearer "
|
||||
if len(authHeader) <= len(bearerPrefix) || authHeader[:len(bearerPrefix)] != bearerPrefix {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"message": "认证令牌格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提取并验证token
|
||||
token := authHeader[len(bearerPrefix):]
|
||||
claims, err := utils.ParseJWT(token)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"message": "无效的认证令牌",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存入上下文
|
||||
c.Set("userID", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("role", claims.Role)
|
||||
c.Set("roles", claims.Roles)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
41
pkg/auth/auth.go
Normal file
41
pkg/auth/auth.go
Normal file
@ -0,0 +1,41 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"ly.system.api/utils"
|
||||
)
|
||||
|
||||
// Service 认证服务接口
|
||||
type Service interface {
|
||||
HashPassword(password string) (string, error)
|
||||
ComparePasswords(hashedPassword, password string) bool
|
||||
GenerateToken(userID uint, username string, role string) (string, error)
|
||||
}
|
||||
|
||||
// service 认证服务实现
|
||||
type service struct{}
|
||||
|
||||
// NewAuthService 创建认证服务实例
|
||||
func NewAuthService() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
// HashPassword 对密码进行哈希处理
|
||||
func (s *service) HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// ComparePasswords 比较密码是否匹配
|
||||
func (s *service) ComparePasswords(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GenerateToken 生成JWT令牌
|
||||
func (s *service) GenerateToken(userID uint, username string, role string) (string, error) {
|
||||
return utils.GenerateJWT(userID, username, role, []string{role})
|
||||
}
|
97
refactor_operations.log
Normal file
97
refactor_operations.log
Normal file
@ -0,0 +1,97 @@
|
||||
# 项目重构操作日志 请遵守此规则开发
|
||||
## 每次需要操作完成都需要写入次此文件作为记录
|
||||
|
||||
## 重构计划
|
||||
|
||||
### 1. 目录结构优化 (DDD设计原则)
|
||||
- [x] 按业务领域划分模块:用户、抽奖、文件管理
|
||||
- 已完成用户领域的重构,包含domain、repository和service层
|
||||
- 已完成抽奖领域的重构,包含domain、repository和service层
|
||||
- 已完成文件管理领域的重构,包含domain、repository和service层
|
||||
- [x] 每个领域模块包含自己的控制器、服务、模型和仓储层
|
||||
- 已删除旧的models目录下的文件:file.go、user.go、song.go、menu.go
|
||||
- 已删除旧的services目录下的文件:file_service.go、user_service.go、lottery_service.go、menu_service.go、interfaces.go、rabbitmq_service.go、redis_service.go
|
||||
- 已删除旧的controllers目录下的文件:file_controller.go、user_controller.go、lottery_controller.go、menu_controller.go
|
||||
|
||||
### 2. 依赖关系优化
|
||||
- [x] 修正包引用路径
|
||||
- 更新container.go中的包引用路径为ly.system.api
|
||||
- 更新routes.go中的包引用路径为ly.system.api,移除旧的controllers包引用
|
||||
- 更新utils/jwt.go中的包引用路径为ly.system.api
|
||||
- 完成internal目录下所有领域模块的包引用路径检查
|
||||
- 完成pkg目录下的包引用路径检查
|
||||
- 完成middlewares目录下的包引用路径检查
|
||||
- 完成tests目录下的包引用路径检查
|
||||
- [ ] 引入依赖倒置原则
|
||||
- [ ] 通过接口解耦服务层
|
||||
- [ ] 优化依赖注入机制
|
||||
|
||||
### 3. 配置管理统一
|
||||
- [ ] 统一配置文件管理
|
||||
- [ ] 实现多环境支持
|
||||
|
||||
### 4. 错误处理机制
|
||||
- [ ] 实现统一的错误处理服务
|
||||
- [ ] 规范化错误返回格式
|
||||
|
||||
### 5. 日志服务规范化
|
||||
- [ ] 实现统一的日志记录服务
|
||||
- [ ] 规范化日志格式
|
||||
|
||||
### 6. 测试覆盖完善
|
||||
- [ ] 补充单元测试
|
||||
- [ ] 补充集成测试
|
||||
|
||||
## 操作记录
|
||||
|
||||
### 2024-01-10
|
||||
1. 删除旧的目录结构:
|
||||
- 删除controllers目录
|
||||
- 删除models目录
|
||||
- 删除services目录
|
||||
- 删除interfaces目录
|
||||
- 删除event_handlers目录
|
||||
|
||||
这些目录的功能已经被迁移到internal目录下的对应领域模块中。
|
||||
|
||||
### 2025-02-06
|
||||
1. 创建重构操作日志文件
|
||||
2. 制定重构计划
|
||||
3. 完成用户领域的DDD架构重构:
|
||||
- 创建internal/user/domain目录,定义User领域模型
|
||||
- 创建internal/user/repository目录,实现用户仓储层
|
||||
- 创建internal/user/service目录,实现用户服务层
|
||||
4. 完成抽奖领域的DDD架构重构:
|
||||
- 创建internal/lottery/domain目录,定义Song领域模型
|
||||
- 创建internal/lottery/repository目录,实现歌曲仓储层
|
||||
- 创建internal/lottery/service目录,实现抽奖服务层
|
||||
5. 完成文件管理领域的重构:
|
||||
- 创建internal/file/domain目录,定义File领域模型
|
||||
- 创建internal/file/repository目录,实现文件仓储层
|
||||
- 创建internal/file/service目录,实现文件服务层
|
||||
|
||||
### 2025-02-07
|
||||
1. 重构文件服务层:
|
||||
- 优化FileService接口设计,实现文件上传、下载和进度查询功能
|
||||
- 实现分片上传功能,支持大文件断点续传
|
||||
- 添加Redis服务用于存储文件上传进度
|
||||
- 实现文件合并功能,支持分片文件的合并处理
|
||||
- 完善错误处理和日志记录
|
||||
2. 修正包引用路径:
|
||||
- 更新container.go中的包引用路径,将其修改为ly.system.api
|
||||
- 更新routes.go中的包引用路径,将其修改为ly.system.api
|
||||
- 更新utils/jwt.go中的包引用路径,将其修改为ly.system.api
|
||||
|
||||
### 2025-02-08
|
||||
1. 开始依赖关系优化:
|
||||
- 分析现有服务层代码,识别紧耦合的关系
|
||||
- 在lottery_service.go中引入依赖倒置原则,将SongRepository作为接口注入
|
||||
- 优化container.go中的服务初始化逻辑,减少服务间的直接依赖
|
||||
- 重构服务层接口,确保遵循接口隔离原则
|
||||
- 调整依赖注入机制,使用更灵活的工厂模式创建服务实例
|
||||
|
||||
### 2024-01-11
|
||||
1. 优化utils/jwt.go文件:
|
||||
- 移除未使用的uuid包引用
|
||||
- 优化包引用路径,确保使用ly.system.api作为基础路径
|
||||
- 完善JWT相关功能的实现,包括令牌生成和解析
|
76
tests/config_test.go
Normal file
76
tests/config_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"ly-user-center/config"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
// 设置测试环境
|
||||
SetupTestEnv(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 测试配置加载
|
||||
cfg, err := config.LoadConfig()
|
||||
assert.NoError(err, "配置加载应该成功")
|
||||
assert.NotNil(cfg, "配置对象不应为空")
|
||||
|
||||
// 测试环境变量替换
|
||||
assert.Equal("43.156.151.237", cfg.Database.Host, "测试环境数据库主机地址应正确解析")
|
||||
|
||||
// 测试Redis配置
|
||||
assert.Equal(6379, cfg.Redis.Port, "Redis端口应正确配置")
|
||||
assert.Equal(100, cfg.Redis.RateLimitMax, "限流最大请求数应正确配置")
|
||||
|
||||
// 测试RabbitMQ配置
|
||||
assert.Equal("ly_system", cfg.RabbitMQ.Exchange, "RabbitMQ交换机名称应正确配置")
|
||||
|
||||
// 测试配置获取函数
|
||||
redisConfig := config.GetRedisConfig()
|
||||
assert.NotNil(redisConfig, "Redis配置不应为空")
|
||||
assert.Equal(cfg.Redis.Host, redisConfig.Host, "Redis主机配置应一致")
|
||||
|
||||
// 测试GetValue函数
|
||||
serverPort := config.GetValue("Server.Port")
|
||||
assert.Equal("8999", serverPort, "服务器端口应正确获取")
|
||||
|
||||
// 测试无效配置键
|
||||
invalidValue := config.GetValue("Invalid.Key")
|
||||
assert.Empty(invalidValue, "无效配置键应返回空字符串")
|
||||
}
|
||||
|
||||
func TestConfigWithCustomPath(t *testing.T) {
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 设置自定义配置路径
|
||||
os.Setenv("CONFIG_PATH", "../config/config.yaml")
|
||||
defer os.Unsetenv("CONFIG_PATH")
|
||||
|
||||
// 测试配置加载
|
||||
cfg, err := config.LoadConfig()
|
||||
assert.NoError(err, "使用自定义路径加载配置应成功")
|
||||
assert.NotNil(cfg, "配置对象不应为空")
|
||||
}
|
||||
|
||||
func TestLoadDBConfig(t *testing.T) {
|
||||
// 设置测试环境
|
||||
SetupTestEnv(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 测试数据库连接字符串生成
|
||||
dsn := config.LoadDBConfig()
|
||||
assert.Contains(dsn, "host=43.156.151.237", "数据库主机地址应正确包含在DSN中")
|
||||
assert.Contains(dsn, "port=5432", "数据库端口应正确包含在DSN中")
|
||||
assert.Contains(dsn, "sslmode=disable", "SSL模式应正确包含在DSN中")
|
||||
}
|
137
tests/rabbitmq_test.go
Normal file
137
tests/rabbitmq_test.go
Normal file
@ -0,0 +1,137 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ly-user-center/config"
|
||||
"ly-user-center/services"
|
||||
|
||||
"github.com/streadway/amqp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestEvent struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func setupRabbitMQTest(t *testing.T) services.RabbitMQService {
|
||||
// 设置测试环境
|
||||
SetupTestEnv(t)
|
||||
|
||||
// 获取RabbitMQ配置
|
||||
config := config.GetRabbitMQConfig()
|
||||
|
||||
// 创建RabbitMQ服务
|
||||
service := services.NewRabbitMQService()
|
||||
|
||||
// 确保交换机存在
|
||||
err := service.EnsureExchange()
|
||||
assert.NoError(t, err, "确保交换机存在应该成功")
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func TestRabbitMQPublishAndConsume(t *testing.T) {
|
||||
// 初始化RabbitMQ服务
|
||||
rabbitMQService := setupRabbitMQTest(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 创建测试事件
|
||||
testEvent := TestEvent{
|
||||
ID: "test-123",
|
||||
Message: "测试消息",
|
||||
}
|
||||
|
||||
// 发布事件
|
||||
err := rabbitMQService.PublishEvent("test.event", testEvent)
|
||||
assert.NoError(err, "发布事件应该成功")
|
||||
|
||||
// 创建通道接收消息
|
||||
received := make(chan bool)
|
||||
|
||||
// 消费消息
|
||||
err = rabbitMQService.Consume(config.GetRabbitMQConfig().Queue, func(data []byte) error {
|
||||
// 解析消息
|
||||
var receivedEvent TestEvent
|
||||
err := json.Unmarshal(data, &receivedEvent)
|
||||
assert.NoError(err, "解析消息应该成功")
|
||||
|
||||
// 验证消息内容
|
||||
assert.Equal(testEvent.ID, receivedEvent.ID, "消息ID应该匹配")
|
||||
assert.Equal(testEvent.Message, receivedEvent.Message, "消息内容应该匹配")
|
||||
|
||||
received <- true
|
||||
return nil
|
||||
})
|
||||
|
||||
assert.NoError(err, "消费消息应该成功")
|
||||
|
||||
// 等待消息处理完成
|
||||
select {
|
||||
case <-received:
|
||||
// 消息处理成功
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("消息处理超时")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRabbitMQEventHandler(t *testing.T) {
|
||||
// 初始化RabbitMQ服务
|
||||
rabbitMQService := setupRabbitMQTest(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 创建测试事件处理器
|
||||
handled := make(chan bool)
|
||||
handler := &testEventHandler{handled: handled}
|
||||
|
||||
// 注册事件处理器
|
||||
rabbitMQService.RegisterHandler("test.event", handler)
|
||||
|
||||
// 创建并发布测试事件
|
||||
testEvent := TestEvent{
|
||||
ID: "test-456",
|
||||
Message: "测试事件处理",
|
||||
}
|
||||
|
||||
// 序列化事件数据
|
||||
data, err := json.Marshal(testEvent)
|
||||
assert.NoError(err, "序列化事件数据应该成功")
|
||||
|
||||
// 分发事件
|
||||
err = rabbitMQService.DispatchEvent("test.event", data)
|
||||
assert.NoError(err, "分发事件应该成功")
|
||||
|
||||
// 等待事件处理完成
|
||||
select {
|
||||
case <-handled:
|
||||
// 事件处理成功
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("事件处理超时")
|
||||
}
|
||||
}
|
||||
|
||||
// testEventHandler 测试用事件处理器
|
||||
type testEventHandler struct {
|
||||
handled chan bool
|
||||
}
|
||||
|
||||
func (h *testEventHandler) HandleEvent(payload []byte) error {
|
||||
// 模拟事件处理
|
||||
var event TestEvent
|
||||
if err := json.Unmarshal(payload, &event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 发送处理完成信号
|
||||
h.handled <- true
|
||||
return nil
|
||||
}
|
110
tests/redis_test.go
Normal file
110
tests/redis_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ly-user-center/config"
|
||||
"ly-user-center/services"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setupRedisTest(t *testing.T) services.RedisService {
|
||||
// 设置测试环境
|
||||
SetupTestEnv(t)
|
||||
|
||||
// 获取Redis配置
|
||||
redisConfig := config.GetRedisConfig()
|
||||
|
||||
// 创建Redis客户端
|
||||
TestRedis = redis.NewClient(&redis.Options{
|
||||
Addr: redisConfig.Host + ":" + "6379",
|
||||
Password: redisConfig.Password,
|
||||
DB: redisConfig.DB,
|
||||
})
|
||||
|
||||
// 创建Redis服务
|
||||
return services.NewRedisService(TestRedis)
|
||||
}
|
||||
|
||||
func TestRedisBasicOperations(t *testing.T) {
|
||||
// 初始化Redis服务
|
||||
redisService := setupRedisTest(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 测试Set和Get操作
|
||||
key := "test_key"
|
||||
value := "test_value"
|
||||
err := redisService.Set(key, value, time.Minute)
|
||||
assert.NoError(err, "设置键值对应该成功")
|
||||
|
||||
result, err := redisService.Get(key)
|
||||
assert.NoError(err, "获取键值应该成功")
|
||||
assert.Equal(value, result, "获取的值应该与设置的值相同")
|
||||
|
||||
// 测试过期时间
|
||||
err = redisService.Set("expire_key", "expire_value", time.Second)
|
||||
assert.NoError(err, "设置带过期时间的键值对应该成功")
|
||||
|
||||
// 测试删除操作
|
||||
err = redisService.Del(key)
|
||||
assert.NoError(err, "删除键应该成功")
|
||||
|
||||
_, err = redisService.Get(key)
|
||||
assert.Error(err, "获取已删除的键应该返回错误")
|
||||
}
|
||||
|
||||
func TestRedisRateLimit(t *testing.T) {
|
||||
// 初始化Redis服务
|
||||
redisService := setupRedisTest(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 测试限流功能
|
||||
key := "rate_limit:test"
|
||||
|
||||
// 测试正常请求
|
||||
for i := 0; i < config.GetRedisConfig().RateLimitMax; i++ {
|
||||
allowed, err := redisService.CheckRateLimit(key)
|
||||
assert.NoError(err, "检查限流应该成功")
|
||||
assert.True(allowed, "未超过限制时应该允许请求")
|
||||
}
|
||||
|
||||
// 测试超出限制的请求
|
||||
allowed, err := redisService.CheckRateLimit(key)
|
||||
assert.NoError(err, "检查限流应该成功")
|
||||
assert.False(allowed, "超过限制时应该拒绝请求")
|
||||
}
|
||||
|
||||
func TestRedisIncr(t *testing.T) {
|
||||
// 初始化Redis服务
|
||||
redisService := setupRedisTest(t)
|
||||
defer TearDownTestEnv(t)
|
||||
|
||||
// 初始化断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 测试递增操作
|
||||
key := "counter_key"
|
||||
|
||||
// 第一次递增
|
||||
count, err := redisService.Incr(key)
|
||||
assert.NoError(err, "递增操作应该成功")
|
||||
assert.Equal(int64(1), count, "第一次递增后值应该为1")
|
||||
|
||||
// 第二次递增
|
||||
count, err = redisService.Incr(key)
|
||||
assert.NoError(err, "递增操作应该成功")
|
||||
assert.Equal(int64(2), count, "第二次递增后值应该为2")
|
||||
|
||||
// 设置过期时间
|
||||
err = redisService.Expire(key, time.Second)
|
||||
assert.NoError(err, "设置过期时间应该成功")
|
||||
}
|
54
tests/test_utils.go
Normal file
54
tests/test_utils.go
Normal file
@ -0,0 +1,54 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/streadway/amqp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TestDB 测试数据库连接
|
||||
var TestDB *gorm.DB
|
||||
|
||||
// TestRedis 测试Redis客户端
|
||||
var TestRedis *redis.Client
|
||||
|
||||
// TestRabbitMQ 测试RabbitMQ连接
|
||||
var TestRabbitMQ *amqp.Connection
|
||||
|
||||
// SetupTestEnv 设置测试环境
|
||||
func SetupTestEnv(t *testing.T) {
|
||||
// 设置测试环境变量
|
||||
os.Setenv("CONFIG_PATH", "../config/config.yaml")
|
||||
os.Setenv("ENV", "test")
|
||||
|
||||
// 初始化测试断言
|
||||
assert := assert.New(t)
|
||||
|
||||
// 确保测试环境变量设置正确
|
||||
assert.Equal("test", os.Getenv("ENV"))
|
||||
}
|
||||
|
||||
// TearDownTestEnv 清理测试环境
|
||||
func TearDownTestEnv(t *testing.T) {
|
||||
// 清理测试数据库连接
|
||||
if TestDB != nil {
|
||||
db, err := TestDB.DB()
|
||||
if err == nil {
|
||||
db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 清理Redis连接
|
||||
if TestRedis != nil {
|
||||
TestRedis.Close()
|
||||
}
|
||||
|
||||
// 清理RabbitMQ连接
|
||||
if TestRabbitMQ != nil {
|
||||
TestRabbitMQ.Close()
|
||||
}
|
||||
}
|
79
utils/api_response.go
Normal file
79
utils/api_response.go
Normal file
@ -0,0 +1,79 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// APIResponse 定义统一的API响应格式
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"` // 状态码
|
||||
Message string `json:"message"` // 响应消息
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ResponseCode 定义API响应状态码
|
||||
const (
|
||||
Success = 200 // 成功
|
||||
BadRequest = 400 // 请求参数错误
|
||||
Unauthorized = 401 // 未授权
|
||||
Forbidden = 403 // 禁止访问
|
||||
NotFound = 404 // 资源不存在
|
||||
InternalError = 500 // 服务器内部错误
|
||||
)
|
||||
|
||||
// NewAPIResponse 创建新的API响应
|
||||
func NewAPIResponse(code int, message string, data interface{}) *APIResponse {
|
||||
return &APIResponse{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// SuccessResponse 创建成功响应并写入Gin上下文
|
||||
func SuccessResponse(c *gin.Context, message string, data interface{}) {
|
||||
c.JSON(Success, NewAPIResponse(Success, message, data))
|
||||
}
|
||||
|
||||
// ErrorResponse 创建错误响应并写入Gin上下文
|
||||
func ErrorResponse(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, NewAPIResponse(code, message, nil))
|
||||
}
|
||||
|
||||
// UnauthorizedResponse 创建未授权响应
|
||||
func UnauthorizedResponse(c *gin.Context) {
|
||||
c.JSON(Unauthorized, NewAPIResponse(Unauthorized, "未授权访问", nil))
|
||||
}
|
||||
|
||||
// ValidationErrorResponse 创建参数验证错误响应
|
||||
func ValidationErrorResponse(c *gin.Context, message string) {
|
||||
c.JSON(BadRequest, NewAPIResponse(BadRequest, message, nil))
|
||||
}
|
||||
|
||||
// GetClaims 从Gin上下文中获取JWT Claims
|
||||
func GetClaims(c *gin.Context) (*Claims, bool) {
|
||||
claims, exists := c.Get("claims")
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
if userClaims, ok := claims.(*Claims); ok {
|
||||
return userClaims, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetUintParam 从Gin上下文中获取uint类型的参数
|
||||
func GetUintParam(c *gin.Context, param string) (uint, error) {
|
||||
val := c.Param(param)
|
||||
if val == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
uintVal, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint(uintVal), nil
|
||||
}
|
61
utils/jwt.go
Normal file
61
utils/jwt.go
Normal file
@ -0,0 +1,61 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"ly.system.api/config"
|
||||
)
|
||||
|
||||
// Claims 包含JWT声明信息
|
||||
type Claims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
Roles []string `json:"roles"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenerateJWT 生成JWT令牌
|
||||
func GenerateJWT(userID uint, username string, role string, roles []string) (string, error) {
|
||||
// 获取JWT配置
|
||||
jwtConfig := config.GetJWTConfig()
|
||||
|
||||
// 创建声明
|
||||
claims := Claims{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(jwtConfig.Expire) * time.Second)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: jwtConfig.Issuer,
|
||||
},
|
||||
}
|
||||
|
||||
// 创建并签名令牌
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(jwtConfig.Secret))
|
||||
}
|
||||
|
||||
// ParseJWT 解析并验证JWT令牌
|
||||
func ParseJWT(tokenString string) (*Claims, error) {
|
||||
// 获取JWT配置
|
||||
jwtConfig := config.GetJWTConfig()
|
||||
|
||||
// 解析令牌
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(jwtConfig.Secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, jwt.ErrInvalidKey
|
||||
}
|
30
validators/request_models.go
Normal file
30
validators/request_models.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Package validators 定义请求验证的数据结构体
|
||||
package validators
|
||||
|
||||
// RegisterRequest 用户注册请求验证结构体
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=20,alphanum"`
|
||||
Password string `json:"password" validate:"required,min=8,containsany=@#$%^&*,containsany=ABCDEFGHIJKLMNOPQRSTUVWXYZ,containsany=0123456789"`
|
||||
Email string `json:"email" validate:"required,email,max=100"`
|
||||
Phone string `json:"phone" validate:"required,e164"`
|
||||
Sex int32 `json:"sex" validate:"required,oneof=0 1 2"`
|
||||
}
|
||||
|
||||
// LoginRequest 用户登录请求验证结构体
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
// RefreshTokenRequest 刷新令牌请求验证结构体
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||
}
|
||||
|
||||
// FileUploadRequest 文件上传请求验证结构体
|
||||
type FileUploadRequest struct {
|
||||
ChunkNumber int `json:"chunkNumber" validate:"required,min=1"`
|
||||
TotalChunks int `json:"totalChunks" validate:"required,min=1"`
|
||||
ChunkSize int `json:"chunkSize" validate:"required,min=1"`
|
||||
FileID string `json:"fileId" validate:"required"`
|
||||
}
|
32
validators/request_validator.go
Normal file
32
validators/request_validator.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Package validators 提供请求参数验证功能
|
||||
package validators
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RequestValidator 请求验证器
|
||||
type RequestValidator struct {
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
// NewRequestValidator 创建新的请求验证器实例
|
||||
func NewRequestValidator() *RequestValidator {
|
||||
v := validator.New()
|
||||
// 注册自定义标签处理函数
|
||||
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
})
|
||||
return &RequestValidator{validator: v}
|
||||
}
|
||||
|
||||
// Validate 验证结构体
|
||||
func (v *RequestValidator) Validate(i interface{}) error {
|
||||
return v.validator.Struct(i)
|
||||
}
|
Loading…
Reference in New Issue
Block a user