Files
verify/backend/internal/router/developer/applications.go
T
admin 26349c15d3 fix: 修复日志记录应用列为空的问题
- 添加LogOperationWithApp方法支持传入ApplicationID
- 创建/更新/删除卡密类型时记录ApplicationID
- 创建/更新应用时记录ApplicationID
2026-04-30 20:58:52 +08:00

1058 lines
32 KiB
Go

package developer
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/middleware"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
)
func SetupApplicationRoutes(r *gin.RouterGroup) {
applications := r.Group("/applications")
{
applications.GET("", handleGetApplications)
applications.POST("", handleCreateApplication)
applications.PUT("/:id", handleUpdateApplication)
applications.DELETE("/:id", handleDeleteApplication)
applications.GET("/:id", handleGetApplication)
applications.POST("/:id/icon", handleUploadIcon)
applications.POST("/:id/upload", handleUploadVersionFile)
applications.GET("/:id/versions", handleGetVersions)
applications.POST("/:id/versions", handleCreateVersion)
applications.GET("/:id/versions/:versionId", handleGetVersion)
applications.PUT("/:id/versions/:versionId", handleUpdateVersion)
applications.DELETE("/:id/versions/:versionId", handleDeleteVersion)
applications.POST("/:id/versions/:versionId/publish", handlePublishVersion)
applications.GET("/:id/announcements", handleGetApplicationAnnouncements)
applications.POST("/:id/announcements", handleCreateAnnouncement)
applications.PUT("/:id/announcements/:announcementId", handleUpdateAnnouncement)
applications.DELETE("/:id/announcements/:announcementId", handleDeleteAnnouncement)
applications.PUT("/:id/announcements/:announcementId/top", handleToggleAnnouncementTop)
applications.POST("/:id/deduct", handleManualDeduct)
}
}
func handleGetApplications(c *gin.Context) {
userID := c.GetUint("user_id")
applications, err := service.GetApplicationsWithDisabledStatus(userID)
if err != nil {
response.Error(c, 500, "获取应用列表失败")
return
}
fmt.Printf("找到 %d 个应用\n", len(applications))
response.Success(c, gin.H{
"applications": applications,
})
}
func parseIntWithDefault(s string, defaultVal int) int {
if s == "" {
return defaultVal
}
val, err := strconv.Atoi(s)
if err != nil {
return defaultVal
}
return val
}
func parseFloatWithDefault(s string, defaultVal float64) float64 {
if s == "" {
return defaultVal
}
val, err := strconv.ParseFloat(s, 64)
if err != nil {
return defaultVal
}
return val
}
func handleCreateApplication(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
SecretKey string `form:"secret_key" json:"secret_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices string `form:"max_devices" json:"max_devices"`
ChangeLimit string `form:"change_limit" json:"change_limit"`
ChangeInterval string `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount string `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances string `form:"max_instances" json:"max_instances"`
MultiOpen string `form:"multi_open" json:"multi_open"`
EnableTrial string `form:"enable_trial" json:"enable_trial"`
TrialBalance string `form:"trial_balance" json:"trial_balance"`
TrialDays string `form:"trial_days" json:"trial_days"`
EnableFreePeriod string `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval string `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout string `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts string `form:"max_attempts" json:"max_attempts"`
LockDuration string `form:"lock_duration" json:"lock_duration"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval string `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount string `form:"deduction_amount" json:"deduction_amount"`
DeductionCycle string `form:"deduction_cycle" json:"deduction_cycle"`
}
if err := c.ShouldBind(&req); err != nil {
response.Error(c, 400, "参数错误: "+err.Error())
return
}
multiOpen := req.MultiOpen == "true"
enableTrial := req.EnableTrial == "true"
enableFreePeriod := req.EnableFreePeriod == "true"
maxDevices := parseIntWithDefault(req.MaxDevices, 1)
changeLimit := parseIntWithDefault(req.ChangeLimit, 3)
changeInterval := parseIntWithDefault(req.ChangeInterval, 7)
changeDeductAmount := parseFloatWithDefault(req.ChangeDeductAmount, 1)
maxInstances := parseIntWithDefault(req.MaxInstances, 1)
trialBalance := parseFloatWithDefault(req.TrialBalance, 0)
trialDays := parseIntWithDefault(req.TrialDays, 0)
heartbeatInterval := parseIntWithDefault(req.HeartbeatInterval, 60)
heartbeatTimeout := parseIntWithDefault(req.HeartbeatTimeout, 300)
maxAttempts := parseIntWithDefault(req.MaxAttempts, 5)
lockDuration := parseIntWithDefault(req.LockDuration, 30)
deductionInterval := parseIntWithDefault(req.DeductionInterval, 1)
deductionAmount := parseFloatWithDefault(req.DeductionAmount, 1)
appKey := generateAppKey()
secretKey := req.SecretKey
if secretKey == "" {
secretKey = generateSecretKey()
}
encryptType := req.EncryptType
if encryptType == "" {
encryptType = "none"
}
loginPolicy := req.LoginPolicy
if loginPolicy == "" {
loginPolicy = "loose"
}
changeExceedAction := req.ChangeExceedAction
if changeExceedAction == "" {
changeExceedAction = "deny"
}
multiOpenMode := req.MultiOpenMode
if multiOpenMode == "" {
multiOpenMode = "forbidden"
}
deductionUnit := req.DeductionUnit
if deductionUnit == "" && req.DeductionCycle != "" {
deductionUnit = req.DeductionCycle
}
app := model.Application{
UserID: userID,
Name: req.Name,
Description: req.Description,
AppKey: appKey,
SecretKey: secretKey,
BillingType: req.BillingType,
LoginPolicy: loginPolicy,
EncryptType: encryptType,
BindType: req.BindType,
MaxDevices: maxDevices,
ChangeLimit: changeLimit,
ChangeInterval: changeInterval,
ChangeExceedAction: changeExceedAction,
ChangeDeductAmount: changeDeductAmount,
MultiOpenMode: multiOpenMode,
MaxInstances: maxInstances,
MultiOpen: multiOpen,
EnableTrial: enableTrial,
TrialBalance: trialBalance,
TrialDays: trialDays,
EnableFreePeriod: enableFreePeriod,
FreePeriodType: req.FreePeriodType,
FreePeriodStart: req.FreePeriodStart,
FreePeriodEnd: req.FreePeriodEnd,
FreePeriodWeekdays: req.FreePeriodWeekdays,
FreePeriodStartTime: req.FreePeriodStartTime,
FreePeriodEndTime: req.FreePeriodEndTime,
HeartbeatInterval: heartbeatInterval,
HeartbeatTimeout: heartbeatTimeout,
MaxAttempts: maxAttempts,
LockDuration: lockDuration,
DeductionMode: req.DeductionMode,
DeductionType: req.DeductionType,
DeductionInterval: deductionInterval,
DeductionUnit: deductionUnit,
DeductionAmount: deductionAmount,
Status: "active",
}
if err := database.DB.Create(&app).Error; err != nil {
fmt.Printf("创建应用失败: %v\n", err)
response.Error(c, 500, "创建应用失败: "+err.Error())
return
}
file, header, err := c.Request.FormFile("icon")
if err == nil {
defer file.Close()
uploadDir := "./uploads/icons"
if err := os.MkdirAll(uploadDir, 0755); err == nil {
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%d_%d%s", app.ID, time.Now().Unix(), ext)
filePath := filepath.Join(uploadDir, filename)
dst, err := os.Create(filePath)
if err == nil {
defer dst.Close()
if _, err := io.Copy(dst, file); err == nil {
app.IconURL = "/uploads/icons/" + filename
database.DB.Model(&app).Update("icon_url", app.IconURL)
}
}
}
}
service.LogOperationWithApp(c, &app.ID, "create", "application", &app.ID, fmt.Sprintf("创建应用: %s", app.Name), nil)
response.Success(c, gin.H{
"application": app,
})
}
func generateAppKey() string {
bytes := make([]byte, 16)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
func generateSecretKey() string {
bytes := make([]byte, 32)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
func handleUpdateApplication(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
AllowRegister bool `form:"allow_register" json:"allow_register"`
RegisterMethods string `form:"register_methods" json:"register_methods"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
SecretKey string `form:"secret_key" json:"secret_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices int `form:"max_devices" json:"max_devices"`
ChangeLimit int `form:"change_limit" json:"change_limit"`
ChangeInterval int `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount float64 `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances int `form:"max_instances" json:"max_instances"`
MultiOpen bool `form:"multi_open" json:"multi_open"`
EnableTrial bool `form:"enable_trial" json:"enable_trial"`
TrialBalance float64 `form:"trial_balance" json:"trial_balance"`
TrialDays int `form:"trial_days" json:"trial_days"`
EnableFreePeriod bool `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval int `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout int `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts int `form:"max_attempts" json:"max_attempts"`
LockDuration int `form:"lock_duration" json:"lock_duration"`
Status string `form:"status" json:"status"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval int `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount float64 `form:"deduction_amount" json:"deduction_amount"`
}
contentType := c.GetHeader("Content-Type")
var err error
if contentType != "" && len(contentType) >= 19 && contentType[:19] == "multipart/form-data" {
err = c.ShouldBind(&req)
} else {
err = c.ShouldBindJSON(&req)
}
if err != nil {
response.Error(c, 400, "参数错误: "+err.Error())
return
}
fmt.Printf("更新应用请求: EncryptType=%s, SecretKey=%s\n", req.EncryptType, req.SecretKey)
id := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
fmt.Printf("更新应用前: ID=%d, EncryptType=%s, SecretKey=%s\n", app.ID, app.EncryptType, app.SecretKey)
app.Name = req.Name
app.Description = req.Description
app.BillingType = req.BillingType
app.LoginPolicy = req.LoginPolicy
app.AllowRegister = req.AllowRegister
app.RegisterMethods = req.RegisterMethods
app.EncryptType = req.EncryptType
if req.SecretKey != "" {
app.SecretKey = req.SecretKey
}
app.BindType = req.BindType
app.MaxDevices = req.MaxDevices
app.ChangeLimit = req.ChangeLimit
app.ChangeInterval = req.ChangeInterval
app.ChangeExceedAction = req.ChangeExceedAction
app.ChangeDeductAmount = req.ChangeDeductAmount
app.MultiOpenMode = req.MultiOpenMode
app.MaxInstances = req.MaxInstances
app.MultiOpen = req.MultiOpen
app.EnableTrial = req.EnableTrial
app.TrialBalance = req.TrialBalance
app.TrialDays = req.TrialDays
app.EnableFreePeriod = req.EnableFreePeriod
app.FreePeriodType = req.FreePeriodType
app.FreePeriodStart = req.FreePeriodStart
app.FreePeriodEnd = req.FreePeriodEnd
app.FreePeriodWeekdays = req.FreePeriodWeekdays
app.FreePeriodStartTime = req.FreePeriodStartTime
app.FreePeriodEndTime = req.FreePeriodEndTime
if req.HeartbeatInterval > 0 {
app.HeartbeatInterval = req.HeartbeatInterval
}
if req.HeartbeatTimeout > 0 {
app.HeartbeatTimeout = req.HeartbeatTimeout
}
if req.MaxAttempts > 0 {
app.MaxAttempts = req.MaxAttempts
}
if req.LockDuration > 0 {
app.LockDuration = req.LockDuration
}
app.Status = req.Status
app.DeductionMode = req.DeductionMode
app.DeductionType = req.DeductionType
app.DeductionInterval = req.DeductionInterval
app.DeductionUnit = req.DeductionUnit
app.DeductionAmount = req.DeductionAmount
file, header, err := c.Request.FormFile("icon")
if err == nil {
defer file.Close()
uploadDir := "./uploads/icons"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
response.Error(c, 500, "创建上传目录失败")
return
}
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%d_%d%s", app.ID, time.Now().Unix(), ext)
filePath := filepath.Join(uploadDir, filename)
dst, err := os.Create(filePath)
if err != nil {
response.Error(c, 500, "保存文件失败")
return
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
response.Error(c, 500, "保存文件失败")
return
}
app.IconURL = "/uploads/icons/" + filename
}
fmt.Printf("更新应用: ID=%d, EncryptType=%s, SecretKey=%s\n", app.ID, app.EncryptType, app.SecretKey)
if err := database.DB.Save(&app).Error; err != nil {
response.Error(c, 500, "更新应用失败")
return
}
service.LogOperationWithApp(c, &app.ID, "update", "application", &app.ID, fmt.Sprintf("更新应用: %s", app.Name), nil)
response.Success(c, app)
}
func handleDeleteApplication(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
appName := app.Name
appID := app.ID
tx := database.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Where("id = ? AND user_id = ?", id, userID).Delete(&model.Application{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除应用失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.Version{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除版本失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.CardType{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除卡密类型失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.Card{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除卡密失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.Announcement{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除公告失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.AppUser{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除应用用户失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.AgentApplication{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除代理授权失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.Ticket{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除工单失败")
return
}
if err := tx.Where("app_id = ?", id).Delete(&model.UserVariable{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除用户变量失败")
return
}
if err := tx.Where("application_id = ?", id).Delete(&model.UserDevice{}).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "删除用户设备失败")
return
}
if err := tx.Commit().Error; err != nil {
response.Error(c, 500, "删除应用失败")
return
}
service.LogOperation(c, "delete", "application", &appID, fmt.Sprintf("删除应用: %s", appName), nil)
response.Success(c, nil)
}
func handleGetApplication(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
response.Success(c, gin.H{
"application": app,
})
}
func handleGetApplicationAnnouncements(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "10")
var total int64
database.DB.Model(&model.Announcement{}).Where("application_id = ?", appID).Count(&total)
var announcements []model.Announcement
offset := 0
if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 {
offset = (pageInt - 1) * 10
}
limit := 10
if pageSizeInt, err := strconv.Atoi(pageSize); err == nil && pageSizeInt > 0 {
limit = pageSizeInt
}
if err := database.DB.Where("application_id = ?", appID).Order("is_top DESC, created_at DESC").Limit(limit).Offset(offset).Find(&announcements).Error; err != nil {
response.Error(c, 500, "获取公告列表失败")
return
}
response.Success(c, gin.H{
"announcements": announcements,
"total": total,
})
}
func handleCreateAnnouncement(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var req struct {
Title string `json:"title"`
Content string `json:"content"`
Type string `json:"type"`
Status string `json:"status"`
IsTop bool `json:"is_top"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
announcement := model.Announcement{
ApplicationID: app.ID,
Title: req.Title,
Content: req.Content,
Type: req.Type,
Status: req.Status,
IsTop: req.IsTop,
}
if err := database.DB.Create(&announcement).Error; err != nil {
response.Error(c, 500, "创建公告失败")
return
}
response.Success(c, announcement)
}
func handleUpdateAnnouncement(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
announcementID := c.Param("announcementId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var req struct {
Title string `json:"title"`
Content string `json:"content"`
Type string `json:"type"`
Status string `json:"status"`
IsTop bool `json:"is_top"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var announcement model.Announcement
if err := database.DB.Where("id = ? AND application_id = ?", announcementID, appID).First(&announcement).Error; err != nil {
response.Error(c, 404, "公告不存在")
return
}
announcement.Title = req.Title
announcement.Content = req.Content
announcement.Type = req.Type
announcement.Status = req.Status
announcement.IsTop = req.IsTop
if err := database.DB.Save(&announcement).Error; err != nil {
response.Error(c, 500, "更新公告失败")
return
}
response.Success(c, announcement)
}
func handleDeleteAnnouncement(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
announcementID := c.Param("announcementId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if err := database.DB.Where("id = ? AND application_id = ?", announcementID, appID).Delete(&model.Announcement{}).Error; err != nil {
response.Error(c, 500, "删除公告失败")
return
}
response.Success(c, nil)
}
func handleToggleAnnouncementTop(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
announcementID := c.Param("announcementId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var announcement model.Announcement
if err := database.DB.Where("id = ? AND application_id = ?", announcementID, appID).First(&announcement).Error; err != nil {
response.Error(c, 404, "公告不存在")
return
}
announcement.IsTop = !announcement.IsTop
if err := database.DB.Save(&announcement).Error; err != nil {
response.Error(c, 500, "更新公告失败")
return
}
response.Success(c, announcement)
}
func handleUploadIcon(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
file, err := c.FormFile("icon")
if err != nil {
response.Error(c, 400, "请上传文件")
return
}
filename := fmt.Sprintf("app_%d_%s", app.ID, file.Filename)
if err := c.SaveUploadedFile(file, "uploads/"+filename); err != nil {
response.Error(c, 500, "文件保存失败")
return
}
app.IconURL = "/uploads/" + filename
database.DB.Save(&app)
response.Success(c, gin.H{
"icon": app.IconURL,
})
}
func handleUploadVersionFile(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
file, err := c.FormFile("file")
if err != nil {
response.Error(c, 400, "请上传文件")
return
}
fileSize := file.Size
var user model.User
if err := database.DB.First(&user, userID).Error; err != nil {
response.Error(c, 500, "获取用户信息失败")
return
}
if user.CurrentPackageID != nil {
var permission model.PackagePermission
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err == nil {
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
if user.StorageUsed+fileSize > maxStorageBytes {
usedMB := float64(user.StorageUsed) / 1024 / 1024
maxMB := float64(permission.MaxStorage)
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
return
}
}
}
ext := filepath.Ext(file.Filename)
timestamp := time.Now().Unix()
filename := fmt.Sprintf("version_%d_%d%s", app.ID, timestamp, ext)
dst := filepath.Join("uploads", filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
response.Error(c, 500, "文件保存失败")
return
}
url := fmt.Sprintf("/uploads/%s", filename)
absolutePath := filepath.Join(".", "uploads", filename)
if info, err := os.Stat(absolutePath); err == nil {
if err := middleware.UpdateStorageUsed(userID, info.Size(), "upload"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
usage := model.StorageUsage{
UserID: userID,
ApplicationID: &app.ID,
ResourceType: "version",
ResourceID: 0,
FileName: file.Filename,
FileSize: info.Size(),
Action: "upload",
CreatedAt: time.Now(),
}
database.DB.Create(&usage)
response.Success(c, gin.H{
"url": url,
"size": info.Size(),
})
} else {
response.Error(c, 500, "获取文件大小失败")
return
}
}
func handleGetVersions(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var versions []model.Version
if err := database.DB.Where("application_id = ?", appID).Find(&versions).Error; err != nil {
response.Success(c, gin.H{
"versions": []interface{}{},
})
return
}
response.Success(c, gin.H{
"versions": versions,
})
}
func handleCreateVersion(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var req struct {
Version string `json:"version"`
Description string `json:"description"`
FilePath string `json:"file_path"`
ForceUpdate bool `json:"force_update"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
fmt.Printf("接收到的完整请求: Version=%s, Description=%s, FilePath=%s, ForceUpdate=%v\n",
req.Version, req.Description, req.FilePath, req.ForceUpdate)
finalFilePath := req.FilePath
fileSize := int64(0)
absoluteFilePath := filepath.Join(".", req.FilePath)
fmt.Printf("请求的文件路径: %s, 绝对路径: %s\n", req.FilePath, absoluteFilePath)
if info, err := os.Stat(absoluteFilePath); err == nil {
fileSize = info.Size()
fmt.Printf("文件大小: %d\n", fileSize)
} else {
fmt.Printf("获取文件大小失败: %v\n", err)
}
filePath := finalFilePath
if !strings.HasPrefix(filePath, "/") {
filePath = "/" + filepath.ToSlash(filePath)
}
version := model.Version{
ApplicationID: app.ID,
Version: req.Version,
Description: req.Description,
FilePath: filePath,
ForceUpdate: req.ForceUpdate,
Status: "active",
FileSize: fileSize,
}
fmt.Printf("创建版本,文件大小: %d, 文件路径: %s\n", fileSize, filePath)
if err := database.DB.Create(&version).Error; err != nil {
response.Error(c, 500, "创建版本失败")
return
}
response.Success(c, version)
}
func handleGetVersion(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
versionID := c.Param("versionId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var version model.Version
if err := database.DB.Where("id = ? AND application_id = ?", versionID, appID).First(&version).Error; err != nil {
response.Error(c, 404, "版本不存在")
return
}
response.Success(c, version)
}
func handleUpdateVersion(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
versionID := c.Param("versionId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var req struct {
Version string `json:"version"`
Description string `json:"description"`
FilePath string `json:"file_path"`
ForceUpdate bool `json:"force_update"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var version model.Version
if err := database.DB.Where("id = ? AND application_id = ?", versionID, appID).First(&version).Error; err != nil {
response.Error(c, 404, "版本不存在")
return
}
version.Version = req.Version
version.Description = req.Description
version.FilePath = req.FilePath
version.ForceUpdate = req.ForceUpdate
version.Status = req.Status
if err := database.DB.Save(&version).Error; err != nil {
response.Error(c, 500, "更新版本失败")
return
}
response.Success(c, version)
}
func handleDeleteVersion(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
versionID := c.Param("versionId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if err := database.DB.Where("id = ? AND application_id = ?", versionID, appID).Delete(&model.Version{}).Error; err != nil {
response.Error(c, 500, "删除版本失败")
return
}
response.Success(c, nil)
}
func handlePublishVersion(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
versionID := c.Param("versionId")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var version model.Version
if err := database.DB.Where("id = ? AND application_id = ?", versionID, appID).First(&version).Error; err != nil {
response.Error(c, 404, "版本不存在")
return
}
version.Status = "published"
if err := database.DB.Save(&version).Error; err != nil {
response.Error(c, 500, "发布版本失败")
return
}
response.Success(c, version)
}
func handleManualDeduct(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Param("id")
var app model.Application
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.BillingType != "balance" {
response.Error(c, 400, "只有余额模式的应用才支持手动扣费")
return
}
if app.DeductionMode != "manual" {
response.Error(c, 400, "该应用未开启手动扣费模式,请先在设置中修改扣费方式为手动扣费")
return
}
var req struct {
UserID uint `json:"user_id" binding:"required"`
Amount float64 `json:"amount" binding:"required,gt=0"`
Description string `json:"description"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误: "+err.Error())
return
}
var user model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", req.UserID, appID).First(&user).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
if user.Balance == -1 {
response.Error(c, 400, "该用户为永久会员,无法扣费")
return
}
if user.Balance < req.Amount {
response.Error(c, 400, fmt.Sprintf("用户余额不足,当前余额: %.2f,需扣除: %.2f", user.Balance, req.Amount))
return
}
user.Balance -= req.Amount
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "扣费失败")
return
}
record := model.ConsumptionRecord{
UserID: user.ID,
ApplicationID: app.ID,
Amount: req.Amount,
Type: "manual_deduct",
Description: req.Description,
BalanceAfter: user.Balance,
}
if err := database.DB.Create(&record).Error; err != nil {
fmt.Printf("创建消费记录失败: %v\n", err)
}
response.Success(c, gin.H{
"user_id": user.ID,
"username": user.Username,
"amount": req.Amount,
"balance_before": user.Balance + req.Amount,
"balance_after": user.Balance,
"description": req.Description,
"message": "扣费成功",
})
}