Files
verify/backend/internal/router/developer/cloud.go
T

828 lines
22 KiB
Go

package developer
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"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 SetupCloudRoutes(r *gin.RouterGroup) {
cloudConstants := r.Group("/cloud-constants")
{
cloudConstants.GET("", handleGetCloudConstants)
cloudConstants.GET("/:id", handleGetCloudConstant)
cloudConstants.GET("/:id/download", handleDownloadCloudConstant)
cloudConstants.POST("", handleCreateCloudConstant)
cloudConstants.POST("/upload", handleUploadCloudConstant)
cloudConstants.PUT("/:id", handleUpdateCloudConstant)
cloudConstants.DELETE("/:id", handleDeleteCloudConstant)
}
cloudVariables := r.Group("/cloud-variables")
{
cloudVariables.GET("", handleGetCloudVariables)
cloudVariables.GET("/:id", handleGetCloudVariable)
cloudVariables.GET("/:id/download", handleDownloadCloudVariable)
cloudVariables.POST("", handleCreateCloudVariable)
cloudVariables.POST("/upload", handleUploadCloudVariable)
cloudVariables.PUT("/:id", handleUpdateCloudVariable)
cloudVariables.DELETE("/:id", handleDeleteCloudVariable)
cloudVariables.GET("/:id/records", handleGetCloudVariableRecords)
cloudVariables.DELETE("/:id/records", handleDeleteCloudVariableRecords)
}
}
func handleGetCloudConstants(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Query("app_id")
var constants []model.CloudConstant
query := database.DB.Where("user_id = ?", userID)
if appID != "" {
query = query.Where("app_id = ?", appID)
}
if err := query.Find(&constants).Error; err != nil {
response.Error(c, 500, "获取云端常量失败")
return
}
response.Success(c, gin.H{
"constants": constants,
"total": len(constants),
})
}
func handleGetCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var constant model.CloudConstant
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
response.Success(c, constant)
}
func handleCreateCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
AppID uint `json:"app_id"`
Key string `json:"key"`
Value string `json:"value"`
VarType string `json:"var_type"`
Description string `json:"description"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if req.Key == "" {
response.Error(c, 400, "变量名不能为空")
return
}
if req.Status != "active" && req.Status != "inactive" {
req.Status = "active"
}
if req.VarType == "" {
req.VarType = "string"
}
constant := model.CloudConstant{
UserID: userID,
AppID: &req.AppID,
Key: req.Key,
Value: req.Value,
VarType: req.VarType,
Description: req.Description,
Status: req.Status,
}
if err := database.DB.Create(&constant).Error; err != nil {
response.Error(c, 500, "创建云端常量失败")
return
}
service.LogOperation(c, "create", "cloud_constant", &constant.ID, fmt.Sprintf("创建云端常量: %s", constant.Key), nil)
response.Success(c, constant)
}
func handleUpdateCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Key string `json:"key"`
Value string `json:"value"`
VarType string `json:"var_type"`
Description string `json:"description"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
id := c.Param("id")
var constant model.CloudConstant
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
constant.Key = req.Key
constant.Value = req.Value
constant.VarType = req.VarType
constant.Description = req.Description
if req.Status == "active" || req.Status == "inactive" {
constant.Status = req.Status
}
if err := database.DB.Save(&constant).Error; err != nil {
response.Error(c, 500, "更新云端常量失败")
return
}
service.LogOperation(c, "update", "cloud_constant", &constant.ID, fmt.Sprintf("更新云端常量: %s", constant.Key), nil)
response.Success(c, constant)
}
func handleDeleteCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var constant model.CloudConstant
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
if constant.VarType == "binary" && constant.FilePath != "" {
if err := middleware.UpdateStorageUsed(userID, constant.FileSize, "delete"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
filePath := strings.TrimPrefix(constant.FilePath, "/")
if _, err := os.Stat(filePath); err == nil {
os.Remove(filePath)
}
}
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&model.CloudConstant{}).Error; err != nil {
response.Error(c, 500, "删除云端常量失败")
return
}
service.LogOperation(c, "delete", "cloud_constant", &constant.ID, fmt.Sprintf("删除云端常量: %s", constant.Key), nil)
response.Success(c, nil)
}
func handleUploadCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
file, header, err := c.Request.FormFile("file")
if err != nil {
response.Error(c, 400, "请选择要上传的文件")
return
}
defer file.Close()
appIDStr := c.PostForm("app_id")
key := c.PostForm("key")
description := c.PostForm("description")
status := c.PostForm("status")
if appIDStr == "" {
response.Error(c, 400, "请选择应用")
return
}
if key == "" {
response.Error(c, 400, "请输入变量名")
return
}
var appID uint
fmt.Sscanf(appIDStr, "%d", &appID)
if status != "active" && status != "inactive" {
status = "active"
}
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+header.Size > maxStorageBytes {
usedMB := float64(user.StorageUsed) / 1024 / 1024
maxMB := float64(permission.MaxStorage)
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
return
}
}
}
uploadDir := "uploads/cloud-files"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
response.Error(c, 500, "创建上传目录失败")
return
}
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext)
filePath := filepath.Join(uploadDir, filename)
dst, err := os.Create(filePath)
if err != nil {
response.Error(c, 500, "创建文件失败")
return
}
defer dst.Close()
hash := md5.New()
multiWriter := io.MultiWriter(dst, hash)
if _, err := io.Copy(multiWriter, file); err != nil {
response.Error(c, 500, "保存文件失败")
return
}
fileMD5 := hex.EncodeToString(hash.Sum(nil))
fileURL := "/uploads/cloud-files/" + filename
mimeType := header.Header.Get("Content-Type")
if mimeType == "" {
mimeType = "application/octet-stream"
}
constant := model.CloudConstant{
UserID: userID,
AppID: &appID,
Key: key,
Value: fileURL,
VarType: "binary",
FilePath: fileURL,
FileSize: header.Size,
MimeType: mimeType,
OriginalName: header.Filename,
FileMD5: fileMD5,
Description: description,
Status: status,
}
if err := database.DB.Create(&constant).Error; err != nil {
os.Remove(filePath)
response.Error(c, 500, "创建云端常量失败")
return
}
if err := middleware.UpdateStorageUsed(userID, header.Size, "upload"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
usage := model.StorageUsage{
UserID: userID,
ApplicationID: &appID,
ResourceType: "cloud_constant",
ResourceID: constant.ID,
FileName: header.Filename,
FileSize: header.Size,
Action: "upload",
CreatedAt: time.Now(),
}
database.DB.Create(&usage)
service.LogOperation(c, "create", "cloud_constant", &constant.ID, fmt.Sprintf("上传文件常量: %s", constant.Key), nil)
response.Success(c, constant)
}
func handleDownloadCloudConstant(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var constant model.CloudConstant
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
if constant.VarType != "binary" || constant.FilePath == "" {
response.Error(c, 400, "该常量不是文件类型")
return
}
filePath := constant.FilePath
if strings.HasPrefix(filePath, "/") {
filePath = filePath[1:]
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
response.Error(c, 404, "文件不存在")
return
}
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", constant.OriginalName))
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Expires", "0")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
c.FileAttachment(filePath, constant.OriginalName)
}
func handleGetCloudVariables(c *gin.Context) {
userID := c.GetUint("user_id")
appID := c.Query("app_id")
var variables []model.CloudVariable
query := database.DB.Where("user_id = ?", userID)
if appID != "" {
query = query.Where("app_id = ?", appID)
}
if err := query.Find(&variables).Error; err != nil {
response.Error(c, 500, "获取云端变量失败")
return
}
response.Success(c, gin.H{
"variables": variables,
"total": len(variables),
})
}
func handleGetCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
response.Success(c, variable)
}
func handleCreateCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
AppID uint `json:"app_id"`
Key string `json:"key"`
DefaultValue string `json:"default_value"`
VarType string `json:"var_type"`
DataType string `json:"data_type"`
MaxRecords int `json:"max_records"`
Scope string `json:"scope"`
WritePermission string `json:"write_permission"`
Description string `json:"description"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if req.Key == "" {
response.Error(c, 400, "变量名不能为空")
return
}
if req.Scope != "app" && req.Scope != "user" {
req.Scope = "app"
}
if req.WritePermission != "admin" && req.WritePermission != "user" && req.WritePermission != "app_user" {
req.WritePermission = "admin"
}
if req.Status != "active" && req.Status != "inactive" {
req.Status = "active"
}
if req.VarType == "" {
req.VarType = "string"
}
if req.DataType != "single" && req.DataType != "stream" {
req.DataType = "single"
}
variable := model.CloudVariable{
UserID: userID,
AppID: &req.AppID,
Key: req.Key,
DefaultValue: req.DefaultValue,
VarType: req.VarType,
DataType: req.DataType,
MaxRecords: req.MaxRecords,
Scope: req.Scope,
WritePermission: req.WritePermission,
Description: req.Description,
Status: req.Status,
}
if err := database.DB.Create(&variable).Error; err != nil {
response.Error(c, 500, "创建云端变量失败")
return
}
response.Success(c, variable)
}
func handleUpdateCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Key string `json:"key"`
DefaultValue string `json:"default_value"`
VarType string `json:"var_type"`
DataType string `json:"data_type"`
MaxRecords int `json:"max_records"`
WritePermission string `json:"write_permission"`
Description string `json:"description"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
variable.Key = req.Key
variable.DefaultValue = req.DefaultValue
variable.VarType = req.VarType
if req.DataType == "single" || req.DataType == "stream" {
variable.DataType = req.DataType
}
variable.MaxRecords = req.MaxRecords
if req.WritePermission == "admin" || req.WritePermission == "user" || req.WritePermission == "app_user" {
variable.WritePermission = req.WritePermission
}
variable.Description = req.Description
if req.Status == "active" || req.Status == "inactive" {
variable.Status = req.Status
}
if err := database.DB.Save(&variable).Error; err != nil {
response.Error(c, 500, "更新云端变量失败")
return
}
response.Success(c, variable)
}
func handleDeleteCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.VarType == "binary" && variable.FilePath != "" {
if err := middleware.UpdateStorageUsed(userID, variable.FileSize, "delete"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
filePath := strings.TrimPrefix(variable.FilePath, "/")
if _, err := os.Stat(filePath); err == nil {
os.Remove(filePath)
}
}
if err := database.DB.Delete(&variable).Error; err != nil {
response.Error(c, 500, "删除云端变量失败")
return
}
response.Success(c, nil)
}
func handleUploadCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
file, header, err := c.Request.FormFile("file")
if err != nil {
response.Error(c, 400, "请选择要上传的文件")
return
}
defer file.Close()
appIDStr := c.PostForm("app_id")
key := c.PostForm("key")
description := c.PostForm("description")
status := c.PostForm("status")
scope := c.PostForm("scope")
writePermission := c.PostForm("write_permission")
if appIDStr == "" {
response.Error(c, 400, "请选择应用")
return
}
if key == "" {
response.Error(c, 400, "请输入变量名")
return
}
var appID uint
fmt.Sscanf(appIDStr, "%d", &appID)
if status != "active" && status != "inactive" {
status = "active"
}
if scope != "app" && scope != "user" {
scope = "app"
}
if writePermission != "admin" && writePermission != "user" {
writePermission = "admin"
}
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+header.Size > maxStorageBytes {
usedMB := float64(user.StorageUsed) / 1024 / 1024
maxMB := float64(permission.MaxStorage)
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
return
}
}
}
uploadDir := "uploads/cloud-files"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
response.Error(c, 500, "创建上传目录失败")
return
}
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext)
filePath := filepath.Join(uploadDir, filename)
dst, err := os.Create(filePath)
if err != nil {
response.Error(c, 500, "创建文件失败")
return
}
defer dst.Close()
hash := md5.New()
multiWriter := io.MultiWriter(dst, hash)
if _, err := io.Copy(multiWriter, file); err != nil {
response.Error(c, 500, "保存文件失败")
return
}
fileMD5 := hex.EncodeToString(hash.Sum(nil))
fileURL := "/uploads/cloud-files/" + filename
mimeType := header.Header.Get("Content-Type")
if mimeType == "" {
mimeType = "application/octet-stream"
}
variable := model.CloudVariable{
UserID: userID,
AppID: &appID,
Key: key,
DefaultValue: fileURL,
VarType: "binary",
FilePath: fileURL,
FileSize: header.Size,
MimeType: mimeType,
OriginalName: header.Filename,
FileMD5: fileMD5,
Scope: scope,
WritePermission: writePermission,
Description: description,
Status: status,
}
if err := database.DB.Create(&variable).Error; err != nil {
os.Remove(filePath)
response.Error(c, 500, "创建云端变量失败")
return
}
if err := middleware.UpdateStorageUsed(userID, header.Size, "upload"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
usage := model.StorageUsage{
UserID: userID,
ApplicationID: &appID,
ResourceType: "cloud_variable",
ResourceID: variable.ID,
FileName: header.Filename,
FileSize: header.Size,
Action: "upload",
CreatedAt: time.Now(),
}
database.DB.Create(&usage)
service.LogOperation(c, "create", "cloud_variable", &variable.ID, fmt.Sprintf("上传文件变量: %s", variable.Key), nil)
response.Success(c, variable)
}
func handleDownloadCloudVariable(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.VarType != "binary" || variable.FilePath == "" {
response.Error(c, 400, "该变量不是文件类型")
return
}
filePath := variable.FilePath
if strings.HasPrefix(filePath, "/") {
filePath = filePath[1:]
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
response.Error(c, 404, "文件不存在")
return
}
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", variable.OriginalName))
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Expires", "0")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
c.FileAttachment(filePath, variable.OriginalName)
}
func handleGetCloudVariableRecords(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.DataType != "stream" {
response.Error(c, 400, "该变量不是流水类型")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
userIDFilter := c.Query("user_id")
startDate := c.Query("start_date")
endDate := c.Query("end_date")
var total int64
query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID)
if userIDFilter != "" {
query = query.Where("app_user_id = ?", userIDFilter)
}
if startDate != "" {
query = query.Where("created_at >= ?", startDate+" 00:00:00")
}
if endDate != "" {
query = query.Where("created_at <= ?", endDate+" 23:59:59")
}
query.Count(&total)
var records []model.CloudVariableRecord
offset := (page - 1) * pageSize
if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&records).Error; err != nil {
response.Error(c, 500, "获取记录失败")
return
}
result := make([]gin.H, len(records))
for i, r := range records {
var data map[string]interface{}
json.Unmarshal([]byte(r.Data), &data)
record := gin.H{
"id": r.ID,
"data": data,
"created_at": r.CreatedAt,
}
if r.AppUserID != nil {
record["user_id"] = r.AppUserID
var appUser model.AppUser
if err := database.DB.Select("id, username").First(&appUser, *r.AppUserID).Error; err == nil {
record["user"] = gin.H{
"id": appUser.ID,
"username": appUser.Username,
}
}
}
result[i] = record
}
response.Success(c, gin.H{
"records": result,
"total": total,
"page": page,
"page_size": pageSize,
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
})
}
func handleDeleteCloudVariableRecords(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var variable model.CloudVariable
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.DataType != "stream" {
response.Error(c, 400, "该变量不是流水类型")
return
}
var req struct {
IDs []uint `json:"ids"`
Before string `json:"before"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if len(req.IDs) > 0 {
if err := database.DB.Where("cloud_variable_id = ? AND id IN ?", variable.ID, req.IDs).Delete(&model.CloudVariableRecord{}).Error; err != nil {
response.Error(c, 500, "删除记录失败")
return
}
} else if req.Before != "" {
if err := database.DB.Where("cloud_variable_id = ? AND created_at < ?", variable.ID, req.Before).Delete(&model.CloudVariableRecord{}).Error; err != nil {
response.Error(c, 500, "删除记录失败")
return
}
} else {
response.Error(c, 400, "请指定要删除的记录")
return
}
response.Success(c, nil)
}