1058 lines
32 KiB
Go
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.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": "扣费成功",
|
|
})
|
|
}
|