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.LogOperation(c, "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.LogOperation(c, "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": "扣费成功", }) }