算了,后端我自己写吧
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"ops/internal/database"
|
||||
"ops/internal/repository"
|
||||
)
|
||||
|
||||
// AuthService 用户认证服务结构
|
||||
type AuthService struct {
|
||||
userRepo repository.UserRepository
|
||||
userInfoRepo repository.UserInfoRepository
|
||||
cookieRepo repository.CookieRepository
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// UserWithInfo 用户信息结构
|
||||
type UserWithInfo struct {
|
||||
UserID uint `json:"userID"`
|
||||
Name string `json:"name"`
|
||||
AvatarURL string `json:"avatarURL"`
|
||||
CookieValue string `json:"cookieValue"`
|
||||
}
|
||||
|
||||
// CookieInfo Cookie信息结构
|
||||
type CookieInfo struct {
|
||||
Value string `json:"value"`
|
||||
ExpireDate time.Time `json:"expireDate"`
|
||||
}
|
||||
|
||||
// NewAuthService 创建认证服务实例
|
||||
func NewAuthService(db *gorm.DB) *AuthService {
|
||||
return &AuthService{
|
||||
userRepo: repository.NewUserRepository(db),
|
||||
userInfoRepo: repository.NewUserInfoRepository(db),
|
||||
cookieRepo: repository.NewCookieRepository(db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *AuthService) Login(name, password, deviceID, ip, remember string) (*UserWithInfo, *CookieInfo, error) {
|
||||
if name == "" || password == "" {
|
||||
return nil, nil, errors.New("username and password are required")
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := s.userRepo.FindByName(name)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("find user error: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return nil, nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
// TODO: 密码验证逻辑(需要查看现有系统的密码加密方式)
|
||||
// 假设这里使用MD5加密,需要根据实际情况调整
|
||||
hashedPassword := hashPassword(password)
|
||||
|
||||
// 临时跳过密码验证,因为现有系统的用户没有密码字段
|
||||
fmt.Printf("DEBUG: Trying to login user %s (password: %s, hashed: %s)\n", name, password, hashedPassword)
|
||||
|
||||
// 生成Cookie
|
||||
cookieValue := generateCookieValue(user.ID, name, deviceID)
|
||||
|
||||
// 设置过期时间
|
||||
expiresAt := time.Now()
|
||||
if remember == "1" || remember == "true" {
|
||||
expiresAt = expiresAt.Add(30 * 24 * time.Hour) // 30天
|
||||
} else {
|
||||
expiresAt = expiresAt.Add(24 * time.Hour) // 24小时
|
||||
}
|
||||
|
||||
cookie := &database.TabCookie{
|
||||
Value: cookieValue,
|
||||
UserID: user.ID,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
CreateAt: time.Now().Unix(),
|
||||
Remember: (remember == "1" || remember == "true"),
|
||||
}
|
||||
|
||||
// 保存Cookie到数据库
|
||||
if err := s.cookieRepo.Create(cookie); err != nil {
|
||||
return nil, nil, fmt.Errorf("create cookie error: %w", err)
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
userInfo, err := s.userInfoRepo.FindByUserID(user.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: user info not found for user %s: %v\n", name, err)
|
||||
}
|
||||
|
||||
// 构建头像URL
|
||||
avatarURL := "/static/default_avatar.png"
|
||||
if userInfo != nil && userInfo.AvatarPath != "" {
|
||||
avatarURL = "/static/uploads/" + userInfo.AvatarPath
|
||||
}
|
||||
|
||||
// 返回用户信息和Cookie
|
||||
userWithInfo := &UserWithInfo{
|
||||
UserID: user.ID,
|
||||
Name: user.Name,
|
||||
AvatarURL: avatarURL,
|
||||
CookieValue: cookieValue,
|
||||
}
|
||||
|
||||
cookieInfo := &CookieInfo{
|
||||
Value: cookieValue,
|
||||
ExpireDate: expiresAt,
|
||||
}
|
||||
|
||||
return userWithInfo, cookieInfo, nil
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *AuthService) Register(name, password, email, phone string) (*UserWithInfo, *CookieInfo, error) {
|
||||
if name == "" || password == "" {
|
||||
return nil, nil, errors.New("username and password are required")
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
exists, err := s.userRepo.ExistsByName(name)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("check username exists error: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, nil, errors.New("username already exists")
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := &database.TabUser{
|
||||
Name: name,
|
||||
// 注意:现有TabUser表只有ID和Name字段,没有密码字段
|
||||
}
|
||||
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
return nil, nil, fmt.Errorf("create user error: %w", err)
|
||||
}
|
||||
|
||||
// 创建用户信息
|
||||
userInfo := &database.TabUserInfo{
|
||||
UserID: user.ID,
|
||||
AvatarPath: "", // 默认空
|
||||
Birthdate: "",
|
||||
Gender: 0,
|
||||
Introduction: "",
|
||||
}
|
||||
|
||||
if err := s.userInfoRepo.Create(userInfo); err != nil {
|
||||
// 如果创建用户信息失败,删除用户(可选)
|
||||
s.userRepo.Delete(user.ID)
|
||||
return nil, nil, fmt.Errorf("create user info error: %w", err)
|
||||
}
|
||||
|
||||
// 生成Cookie
|
||||
cookieValue := generateCookieValue(user.ID, name, "register")
|
||||
expiresAt := time.Now().Add(7 * 24 * time.Hour) // 7天
|
||||
|
||||
cookie := &database.TabCookie{
|
||||
Value: cookieValue,
|
||||
UserID: user.ID,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
CreateAt: time.Now().Unix(),
|
||||
Remember: true,
|
||||
}
|
||||
|
||||
if err := s.cookieRepo.Create(cookie); err != nil {
|
||||
return nil, nil, fmt.Errorf("create cookie error: %w", err)
|
||||
}
|
||||
|
||||
// 返回用户信息和Cookie
|
||||
userWithInfo := &UserWithInfo{
|
||||
UserID: user.ID,
|
||||
Name: user.Name,
|
||||
AvatarURL: "/static/default_avatar.png",
|
||||
CookieValue: cookieValue,
|
||||
}
|
||||
|
||||
cookieInfo := &CookieInfo{
|
||||
Value: cookieValue,
|
||||
ExpireDate: expiresAt,
|
||||
}
|
||||
|
||||
return userWithInfo, cookieInfo, nil
|
||||
}
|
||||
|
||||
// ForgotPassword 忘记密码
|
||||
func (s *AuthService) ForgotPassword(name, email, phone string) (string, error) {
|
||||
if name == "" {
|
||||
return "", errors.New("username is required")
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := s.userRepo.FindByName(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find user error: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return "", errors.New("user not found")
|
||||
}
|
||||
|
||||
// 生成重置令牌
|
||||
resetToken := generateResetToken(user.ID, name)
|
||||
|
||||
// TODO: 发送重置密码邮件或短信
|
||||
// 这里应该实现邮件发送或短信发送逻辑
|
||||
|
||||
fmt.Printf("DEBUG: Password reset token for user %s: %s\n", name, resetToken)
|
||||
|
||||
return resetToken, nil
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (s *AuthService) ResetPassword(token, newPassword string) error {
|
||||
if token == "" || newPassword == "" {
|
||||
return errors.New("token and new password are required")
|
||||
}
|
||||
|
||||
// TODO: 验证重置令牌并获取用户ID
|
||||
// 这里应该解析token获取用户ID
|
||||
userID := parseResetToken(token)
|
||||
if userID == 0 {
|
||||
return errors.New("invalid reset token")
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find user error: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// TODO: 更新密码
|
||||
// 注意:现有TabUser表没有密码字段,这里可能需要扩展表结构或使用其他方式存储密码
|
||||
|
||||
fmt.Printf("DEBUG: Password reset for user %s (ID: %d)\n", user.Name, user.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout 用户退出登录
|
||||
func (s *AuthService) Logout(cookieValue, deviceID string) error {
|
||||
if cookieValue == "" {
|
||||
return errors.New("cookie value is required")
|
||||
}
|
||||
|
||||
return s.cookieRepo.DeleteByValue(cookieValue)
|
||||
}
|
||||
|
||||
// GetProfile 获取用户信息
|
||||
func (s *AuthService) GetProfile(userID uint) (*UserWithInfo, error) {
|
||||
if userID == 0 {
|
||||
return nil, errors.New("user ID is required")
|
||||
}
|
||||
|
||||
// 获取增强的用户信息
|
||||
enhancedUser, err := repository.GetEnhancedUserInfo(s.db, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get enhanced user info error: %w", err)
|
||||
}
|
||||
if enhancedUser == nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
return &UserWithInfo{
|
||||
UserID: enhancedUser.TabUser.ID,
|
||||
Name: enhancedUser.TabUser.Name,
|
||||
AvatarURL: enhancedUser.AvatarURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateProfile 更新用户信息
|
||||
func (s *AuthService) UpdateProfile(userID uint, updateData map[string]interface{}) (*UserWithInfo, error) {
|
||||
if userID == 0 {
|
||||
return nil, errors.New("user ID is required")
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
enhancedUser, err := repository.GetEnhancedUserInfo(s.db, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get enhanced user info error: %w", err)
|
||||
}
|
||||
if enhancedUser == nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
// 检查是否有avatar字段
|
||||
if avatarPath, ok := updateData["avatar"]; ok {
|
||||
avatarStr, isString := avatarPath.(string)
|
||||
if isString && avatarStr != "" {
|
||||
enhancedUser.UserInfo.AvatarPath = avatarStr
|
||||
if err := s.userInfoRepo.Update(&enhancedUser.UserInfo); err != nil {
|
||||
return nil, fmt.Errorf("update user info error: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查其他可更新字段
|
||||
if gender, ok := updateData["gender"]; ok {
|
||||
if genderNum, isNum := gender.(float64); isNum {
|
||||
enhancedUser.UserInfo.Gender = int(genderNum)
|
||||
}
|
||||
}
|
||||
|
||||
if birthdate, ok := updateData["birthdate"]; ok {
|
||||
if birthdateStr, isString := birthdate.(string); isString {
|
||||
enhancedUser.UserInfo.Birthdate = birthdateStr
|
||||
}
|
||||
}
|
||||
|
||||
if intro, ok := updateData["introduction"]; ok {
|
||||
if introStr, isString := intro.(string); isString {
|
||||
enhancedUser.UserInfo.Introduction = introStr
|
||||
}
|
||||
}
|
||||
|
||||
// 保存更新后的用户信息
|
||||
if err := s.userInfoRepo.Update(&enhancedUser.UserInfo); err != nil {
|
||||
return nil, fmt.Errorf("update user info error: %w", err)
|
||||
}
|
||||
|
||||
// 构建头像URL
|
||||
avatarURL := "/static/default_avatar.png"
|
||||
if enhancedUser.UserInfo.AvatarPath != "" {
|
||||
avatarURL = "/static/uploads/" + enhancedUser.UserInfo.AvatarPath
|
||||
}
|
||||
|
||||
return &UserWithInfo{
|
||||
UserID: userID,
|
||||
Name: enhancedUser.TabUser.Name,
|
||||
AvatarURL: avatarURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateCookie 验证Cookie有效性
|
||||
func (s *AuthService) ValidateCookie(cookieValue string) (uint, error) {
|
||||
if cookieValue == "" {
|
||||
return 0, errors.New("cookie value is required")
|
||||
}
|
||||
|
||||
cookie, err := s.cookieRepo.FindByValue(cookieValue)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("find cookie error: %w", err)
|
||||
}
|
||||
if cookie == nil {
|
||||
return 0, errors.New("cookie not found")
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if cookie.ExpiresAt < time.Now().Unix() {
|
||||
// 删除过期的Cookie
|
||||
s.cookieRepo.DeleteByValue(cookieValue)
|
||||
return 0, errors.New("cookie expired")
|
||||
}
|
||||
|
||||
return cookie.UserID, nil
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func hashPassword(password string) string {
|
||||
// 使用MD5哈希(根据现有系统可能使用其他方式)
|
||||
hash := md5.Sum([]byte(password))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func generateCookieValue(userID uint, username, deviceID string) string {
|
||||
timestamp := time.Now().UnixNano()
|
||||
data := fmt.Sprintf("%d%s%s%d", userID, username, deviceID, timestamp)
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func generateResetToken(userID uint, username string) string {
|
||||
timestamp := time.Now().UnixNano()
|
||||
random := fmt.Sprintf("%d", timestamp)
|
||||
data := fmt.Sprintf("%d%s%s%d", userID, username, random, timestamp)
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
token := hex.EncodeToString(hash[:])
|
||||
|
||||
// 存储到数据库或Redis(这里简化处理)
|
||||
// 在实际应用中应该存储token并设置过期时间
|
||||
return token
|
||||
}
|
||||
|
||||
func parseResetToken(token string) uint {
|
||||
// 简化的token解析,实际应该从数据库或Redis验证
|
||||
// 这里返回0表示无效
|
||||
if len(token) < 32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO: 实现token解析逻辑
|
||||
// 暂时返回0,需要根据具体token格式实现
|
||||
return 0
|
||||
}
|
||||
|
||||
// CleanupExpiredCookies 清理过期Cookie
|
||||
func (s *AuthService) CleanupExpiredCookies() error {
|
||||
return s.cookieRepo.DeleteExpired()
|
||||
}
|
||||
|
||||
// GetUserByCookie 通过Cookie获取用户信息
|
||||
func (s *AuthService) GetUserByCookie(cookieValue string) (*UserWithInfo, error) {
|
||||
userID, err := s.ValidateCookie(cookieValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetProfile(userID)
|
||||
}
|
||||
|
||||
// UpdateUserPassword 更新用户密码
|
||||
func (s *AuthService) UpdateUserPassword(userID uint, oldPassword, newPassword string) error {
|
||||
if userID == 0 {
|
||||
return errors.New("user ID is required")
|
||||
}
|
||||
|
||||
if oldPassword == "" || newPassword == "" {
|
||||
return errors.New("old password and new password are required")
|
||||
}
|
||||
|
||||
user, err := s.userRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find user error: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// TODO: 验证旧密码
|
||||
// 现有系统没有密码字段,需要扩展
|
||||
|
||||
// TODO: 更新密码
|
||||
// 现有系统没有密码字段,需要扩展
|
||||
|
||||
return errors.New("password update not supported in current schema")
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"ops/internal/repository"
|
||||
"ops/models"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FileService interface {
|
||||
UploadFile(c *gin.Context, userID uint, fileHeader *multipart.FileHeader, fileType, description string) (UploadResponse, bool)
|
||||
GetFileList(userID uint, fileType string, page, entries int) (FileListResponse, bool)
|
||||
GetFileByID(fileID uint, userID uint) (*models.TabFileInfo_, bool)
|
||||
GetFileByHash(hash string) (*models.TabFileInfo_, bool)
|
||||
DeleteFile(fileID uint, userID uint) bool
|
||||
DownloadFile(c *gin.Context, hash string, download bool) bool
|
||||
}
|
||||
|
||||
type fileService struct {
|
||||
repo repository.FileRepository
|
||||
}
|
||||
|
||||
func NewFileService(db *gorm.DB) FileService {
|
||||
return &fileService{
|
||||
repo: repository.NewFileRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
// 响应结构体
|
||||
type UploadResponse struct {
|
||||
FileID uint `json:"file_id"`
|
||||
Name string `json:"name"`
|
||||
SHA256 string `json:"sha256"`
|
||||
Mime string `json:"mime"`
|
||||
Size int64 `json:"size"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
PreviewURL string `json:"preview_url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type FileListResponse struct {
|
||||
Files []FileInfo `json:"files"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pages"`
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
FileID uint `json:"file_id"`
|
||||
Name string `json:"name"`
|
||||
SHA256 string `json:"sha256"`
|
||||
Mime string `json:"mime"`
|
||||
Size int64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func (s *fileService) UploadFile(c *gin.Context, userID uint, fileHeader *multipart.FileHeader, fileType, description string) (UploadResponse, bool) {
|
||||
// 验证文件大小
|
||||
if fileHeader.Size > int64(models.ConfigsFile.MaxSize) {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
// 验证文件最小大小
|
||||
if fileHeader.Size < 512 {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
// 验证文件名
|
||||
if fileHeader.Filename == "" {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
// 安全处理文件名
|
||||
filename := filepath.Base(fileHeader.Filename)
|
||||
|
||||
// 计算文件哈希
|
||||
hashStr, err := models.SHA256HashFile(fileHeader)
|
||||
if err != nil {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
// 获取文件MIME类型
|
||||
mimeType, err := models.GetFileMime(fileHeader)
|
||||
if err != nil {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
// 验证MIME类型(如果是图片)
|
||||
if fileType == "image" {
|
||||
if models.ConfigsFile.AllowImageMime[mimeType] == "" {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// 构建文件保存路径
|
||||
var savePath string
|
||||
switch fileType {
|
||||
case "image":
|
||||
savePath = filepath.Join(models.ConfigsFile.Pahts["image"], hashStr)
|
||||
default:
|
||||
savePath = filepath.Join(models.ConfigsFile.Pahts["default"], hashStr)
|
||||
}
|
||||
|
||||
// 检查文件是否已存在
|
||||
if models.FileExists(savePath) {
|
||||
// 如果文件已存在,增加使用计数
|
||||
existingFile, err := s.repo.GetFileByHash(hashStr)
|
||||
if err == nil && existingFile != nil {
|
||||
s.repo.IncrementFileUsage(existingFile.ID)
|
||||
}
|
||||
} else {
|
||||
// 保存文件到磁盘
|
||||
if err := c.SaveUploadedFile(fileHeader, savePath); err != nil {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// 检查数据库中是否已存在该文件
|
||||
existingFile, _ := s.repo.GetFileByHash(hashStr)
|
||||
if existingFile != nil {
|
||||
// 更新使用计数
|
||||
s.repo.IncrementFileUsage(existingFile.ID)
|
||||
|
||||
return UploadResponse{
|
||||
FileID: existingFile.ID,
|
||||
Name: filename,
|
||||
SHA256: hashStr,
|
||||
Mime: mimeType,
|
||||
Size: fileHeader.Size,
|
||||
DownloadURL: "/api/v1/files/download/" + hashStr,
|
||||
PreviewURL: "/api/v1/files/get/" + hashStr,
|
||||
CreatedAt: existingFile.Date.Format("2006-01-02T15:04:05Z"),
|
||||
}, true
|
||||
}
|
||||
|
||||
// 创建新的文件记录
|
||||
newFile := &models.TabFileInfo_{
|
||||
Name: filename,
|
||||
Path: savePath,
|
||||
Sha256: hashStr,
|
||||
Mime: mimeType,
|
||||
Type: fileType,
|
||||
UserID: userID,
|
||||
Date: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repo.CreateFile(newFile); err != nil {
|
||||
return UploadResponse{}, false
|
||||
}
|
||||
|
||||
return UploadResponse{
|
||||
FileID: newFile.ID,
|
||||
Name: filename,
|
||||
SHA256: hashStr,
|
||||
Mime: mimeType,
|
||||
Size: fileHeader.Size,
|
||||
DownloadURL: "/api/v1/files/download/" + hashStr,
|
||||
PreviewURL: "/api/v1/files/get/" + hashStr,
|
||||
CreatedAt: newFile.Date.Format("2006-01-02T15:04:05Z"),
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *fileService) GetFileList(userID uint, fileType string, page, entries int) (FileListResponse, bool) {
|
||||
// 验证分页参数
|
||||
if entries <= 0 || entries > 100 {
|
||||
return FileListResponse{}, false
|
||||
}
|
||||
if page <= 0 {
|
||||
return FileListResponse{}, false
|
||||
}
|
||||
|
||||
files, total, err := s.repo.GetFilesByUser(userID, fileType, page, entries)
|
||||
if err != nil {
|
||||
return FileListResponse{}, false
|
||||
}
|
||||
|
||||
// 计算总页数
|
||||
pages := int(total) / entries
|
||||
if int(total)%entries > 0 {
|
||||
pages++
|
||||
}
|
||||
|
||||
// 转换文件信息
|
||||
fileInfos := make([]FileInfo, 0, len(files))
|
||||
for _, file := range files {
|
||||
fileInfos = append(fileInfos, FileInfo{
|
||||
FileID: file.ID,
|
||||
Name: file.Name,
|
||||
SHA256: file.Sha256,
|
||||
Mime: file.Mime,
|
||||
Type: file.Type,
|
||||
CreatedAt: file.Date.Format("2006-01-02T15:04:05Z"),
|
||||
})
|
||||
}
|
||||
|
||||
return FileListResponse{
|
||||
Files: fileInfos,
|
||||
Total: total,
|
||||
Page: page,
|
||||
Pages: pages,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *fileService) GetFileByID(fileID uint, userID uint) (*models.TabFileInfo_, bool) {
|
||||
file, err := s.repo.GetFileByID(fileID)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 检查文件所有权
|
||||
if file.UserID != userID {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return file, true
|
||||
}
|
||||
|
||||
func (s *fileService) GetFileByHash(hash string) (*models.TabFileInfo_, bool) {
|
||||
file, err := s.repo.GetFileByHash(hash)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return file, true
|
||||
}
|
||||
|
||||
func (s *fileService) DeleteFile(fileID uint, userID uint) bool {
|
||||
// 首先检查文件所有权
|
||||
file, err := s.repo.GetFileByID(fileID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if file.UserID != userID {
|
||||
return false
|
||||
}
|
||||
|
||||
// 删除文件记录
|
||||
if err := s.repo.DeleteFile(fileID); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 注意:这里不删除物理文件,因为可能还有其他引用
|
||||
// 如果需要删除物理文件,需要检查引用计数
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *fileService) DownloadFile(c *gin.Context, hash string, download bool) bool {
|
||||
file, err := s.repo.GetFileByHash(hash)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if !models.FileExists(file.Path) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
if download {
|
||||
// 下载模式
|
||||
c.Header("Content-Disposition", "attachment; filename=\""+file.Name+"\"")
|
||||
} else {
|
||||
// 预览模式
|
||||
ext := filepath.Ext(file.Name)
|
||||
if ext != "" {
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType != "" {
|
||||
c.Header("Content-Type", mimeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.File(file.Path)
|
||||
|
||||
// 增加使用计数
|
||||
s.repo.IncrementFileUsage(file.ID)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"ops/internal/repository"
|
||||
"ops/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PurchaseService interface {
|
||||
GetOrders(c *gin.Context, userID uint, search string, page, entries int) (gin.H, bool)
|
||||
CreateOrder(c *gin.Context, userID uint, request CreateOrderRequest) bool
|
||||
GetOrderDetails(orderID uint) (*models.TabPurchaseOrder, []models.TabPurchaseCosts, error)
|
||||
}
|
||||
|
||||
type purchaseService struct {
|
||||
repo repository.PurchaseRepository
|
||||
}
|
||||
|
||||
func NewPurchaseService(db *gorm.DB) PurchaseService {
|
||||
return &purchaseService{
|
||||
repo: repository.NewPurchaseRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
// 请求结构体
|
||||
type CostItem struct {
|
||||
Cost int `json:"cost" binding:"required,min=1"`
|
||||
CostT int `json:"costt" binding:"required,min=0"`
|
||||
CurrencyType string `json:"currencytype" binding:"required"`
|
||||
Int int `json:"int" binding:"required,min=1"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
}
|
||||
|
||||
type CreateOrderRequest struct {
|
||||
Costs []CostItem `json:"costs" binding:"required,min=1,dive"`
|
||||
Link string `json:"link"`
|
||||
OrderStatus string `json:"order_status" binding:"required"`
|
||||
PartName string `json:"partname"`
|
||||
Photos []string `json:"photos"`
|
||||
Remark string `json:"remark"`
|
||||
Styles string `json:"styles"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
TrackingNumber string `json:"tracking_number"`
|
||||
UpdateTime string `json:"update_time"`
|
||||
}
|
||||
|
||||
func (s *purchaseService) GetOrders(c *gin.Context, userID uint, search string, page, entries int) (gin.H, bool) {
|
||||
// 验证分页参数
|
||||
if entries <= 0 || entries > 300 {
|
||||
return nil, false
|
||||
}
|
||||
if page <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
orders, total, err := s.repo.GetOrders(userID, search, page, entries)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
result := gin.H{
|
||||
"all_count": total,
|
||||
"all_orders": orders,
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (s *purchaseService) CreateOrder(c *gin.Context, userID uint, request CreateOrderRequest) bool {
|
||||
// 验证数据
|
||||
if request.Title == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证价格和数量
|
||||
for _, cost := range request.Costs {
|
||||
if cost.Cost <= 0 {
|
||||
return false
|
||||
}
|
||||
if cost.Int <= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 验证图片哈希(简单检查是否包含特殊字符)
|
||||
for _, photo := range request.Photos {
|
||||
if models.IsContainsSpecialChar(photo) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 解析更新时间
|
||||
var updateTime *time.Time
|
||||
if request.UpdateTime != "" {
|
||||
parsedTime, err := models.StringToTimePtr(request.UpdateTime)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
updateTime = parsedTime
|
||||
}
|
||||
|
||||
// 转换照片数组为JSON
|
||||
var photosJSON datatypes.JSON
|
||||
if len(request.Photos) > 0 {
|
||||
photosBytes, err := json.Marshal(request.Photos)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
photosJSON = datatypes.JSON(photosBytes)
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
order := &models.TabPurchaseOrder{
|
||||
UserID: userID,
|
||||
Title: request.Title,
|
||||
Remark: request.Remark,
|
||||
Photos: photosJSON,
|
||||
Link: request.Link,
|
||||
PartName: request.PartName,
|
||||
Styles: request.Styles,
|
||||
UpdateTime: updateTime,
|
||||
TrackingNumber: request.TrackingNumber,
|
||||
OrderStatus: request.OrderStatus,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateOrder(order); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 创建费用明细
|
||||
for _, costItem := range request.Costs {
|
||||
cost := &models.TabPurchaseCosts{
|
||||
UserID: userID,
|
||||
OrderID: order.ID,
|
||||
Price: costItem.Cost,
|
||||
Quantity: costItem.Int,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateCost(cost); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *purchaseService) GetOrderDetails(orderID uint) (*models.TabPurchaseOrder, []models.TabPurchaseCosts, error) {
|
||||
order, err := s.repo.GetOrderByID(orderID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
costs, err := s.repo.GetOrderCosts(orderID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return order, costs, nil
|
||||
}
|
||||
Reference in New Issue
Block a user