package handlers import ( "fmt" "log" "path/filepath" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/niangaodev/art-code/models" "github.com/niangaodev/art-code/repositories" "github.com/niangaodev/art-code/utils" ) // AdminUploadAttachment 上传附件 func AdminUploadAttachment(c *gin.Context) { // 获取文件 file, err := c.FormFile("file") if err != nil { utils.Error(c, 400, "No file uploaded") return } // 获取分类ID(可选) categoryIDStr := c.PostForm("categoryId") var categoryID *uint if categoryIDStr != "" { if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil { idUint := uint(id) categoryID = &idUint } } // 存储类型解析:客户端显式传入 > 数据库启用配置 > Aliyun Env > local storageType := strings.TrimSpace(c.PostForm("storageType")) var ossConfig *models.OSSConfig usedEnvConfig := false if storageType == "" { if cfg, cfgErr := repositories.GetActiveOSSConfig(""); cfgErr == nil && cfg != nil { storageType = cfg.StorageType ossConfig = cfg // 复用,避免下方再查 } else if utils.HasAliyunEnvConfig() { storageType = "aliyun" } else { storageType = "local" } } // 打开文件 src, err := file.Open() if err != nil { utils.Error(c, 500, "Failed to open file") return } defer src.Close() // 创建OSS配置对象 config := &utils.OSSConfig{ StorageType: storageType, } // 仅在无库配置时才用 Env 阿里云(库配置优先) if storageType == "aliyun" && ossConfig == nil { if envCfg, ok := utils.LoadAliyunConfigFromEnv(); ok { config = envCfg usedEnvConfig = true } } if !usedEnvConfig && storageType != "local" { if ossConfig == nil { ossConfig, err = repositories.GetActiveOSSConfig(storageType) if err != nil || ossConfig == nil { utils.Error(c, 400, "OSS config not found or not active") return } } // 根据存储类型从专用字段或通用字段读取配置 var accessKey, secretKey, bucket, region, domain string switch ossConfig.StorageType { case "aliyun": if ossConfig.OSSAccessKeyID != "" { decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeyID) if err == nil { accessKey = decrypted } } if ossConfig.OSSAccessKeySecret != "" { decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeySecret) if err == nil { secretKey = decrypted } } bucket = ossConfig.OSSBucket domain = ossConfig.OSSDomain if accessKey == "" && ossConfig.AccessKey != "" { decrypted, err := utils.DecryptAES(ossConfig.AccessKey) if err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.SecretKey) if err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if domain == "" { domain = ossConfig.Domain } case "qcloud": if ossConfig.QCloudSecretID != "" { decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretID) if err == nil { accessKey = decrypted } } if ossConfig.QCloudSecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretKey) if err == nil { secretKey = decrypted } } bucket = ossConfig.QCloudBucket region = ossConfig.QCloudRegion domain = ossConfig.QCloudDomain if accessKey == "" && ossConfig.AccessKey != "" { decrypted, err := utils.DecryptAES(ossConfig.AccessKey) if err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.SecretKey) if err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if region == "" { region = ossConfig.Region } if domain == "" { domain = ossConfig.Domain } case "qiniu": if ossConfig.QiniuAccessKey != "" { decrypted, err := utils.DecryptAES(ossConfig.QiniuAccessKey) if err == nil { accessKey = decrypted } } if ossConfig.QiniuSecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.QiniuSecretKey) if err == nil { secretKey = decrypted } } bucket = ossConfig.QiniuBucket region = ossConfig.QiniuRegion domain = ossConfig.QiniuDomain if accessKey == "" && ossConfig.AccessKey != "" { decrypted, err := utils.DecryptAES(ossConfig.AccessKey) if err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.SecretKey) if err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if region == "" { region = ossConfig.Region } if domain == "" { domain = ossConfig.Domain } default: if ossConfig.AccessKey != "" { decrypted, err := utils.DecryptAES(ossConfig.AccessKey) if err == nil { accessKey = decrypted } } if ossConfig.SecretKey != "" { decrypted, err := utils.DecryptAES(ossConfig.SecretKey) if err == nil { secretKey = decrypted } } bucket = ossConfig.Bucket region = ossConfig.Region domain = ossConfig.Domain } config.AccessKey = accessKey config.SecretKey = secretKey config.Bucket = bucket config.Region = region config.Domain = domain switch ossConfig.StorageType { case "aliyun": config.OSSAccessKeyID = accessKey config.OSSAccessKeySecret = secretKey config.OSSEndpoint = ossConfig.OSSEndpoint config.OSSBucket = bucket config.OSSDomain = domain case "qcloud": config.QCloudSecretID = accessKey config.QCloudSecretKey = secretKey config.QCloudRegion = region config.QCloudBucket = bucket config.QCloudDomain = domain case "qiniu": config.QiniuAccessKey = accessKey config.QiniuSecretKey = secretKey config.QiniuBucket = bucket config.QiniuRegion = region config.QiniuDomain = domain } } // 获取上传器 uploader, err := utils.GetOSSUploader(config) if err != nil { utils.Error(c, 500, err.Error()) return } // 上传文件 filePath, fileURL, err := uploader.Upload(src, file.Filename, file.Size) if err != nil { utils.Error(c, 500, "Failed to upload file: "+err.Error()) return } storedName := filepath.Base(filePath) if storedName == "." || storedName == "/" || storedName == "" { storedName = file.Filename } // 获取文件类型 fileType := utils.GetFileType(file.Header.Get("Content-Type")) // 创建附件记录 attachment := &models.Attachment{ CategoryID: categoryID, OriginalName: file.Filename, StoredName: storedName, FilePath: filePath, FileURL: fileURL, FileSize: file.Size, FileType: fileType, MimeType: file.Header.Get("Content-Type"), StorageType: storageType, } if ossConfig != nil { ossConfigID := ossConfig.ID attachment.OSSConfigID = &ossConfigID } if err := repositories.CreateAttachment(attachment); err != nil { uploader.Delete(filePath) utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "File uploaded successfully", attachment) } // AdminGetAttachments 获取附件列表 func AdminGetAttachments(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) categoryIDStr := c.Query("categoryId") fileType := c.Query("fileType") keyword := c.Query("keyword") var categoryID *uint if categoryIDStr != "" { if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil { idUint := uint(id) categoryID = &idUint } } attachments, total, err := repositories.GetAttachments(page, pageSize, categoryID, fileType, keyword) if err != nil { utils.ServerError(c, err) return } res := gin.H{ "list": attachments, "total": total, "page": page, "size": pageSize, } utils.Success(c, res) } // AdminDeleteAttachment 删除附件 func AdminDeleteAttachment(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid attachment ID") return } id := uint(parsedID) // 获取附件信息 attachment, err := repositories.GetAttachmentByID(id) if err != nil || attachment == nil { utils.Error(c, 404, "Attachment not found") return } // 获取OSS配置并删除文件 if attachment.StorageType != "" { var ossConfig *models.OSSConfig if attachment.OSSConfigID != nil { ossConfig, err = repositories.GetOSSConfigByID(*attachment.OSSConfigID) } else { ossConfig, err = repositories.GetActiveOSSConfig(attachment.StorageType) } if err == nil && ossConfig != nil { config := &utils.OSSConfig{ StorageType: attachment.StorageType, } // 根据存储类型从专用字段或通用字段读取配置 var accessKey, secretKey, bucket, region, domain string switch ossConfig.StorageType { case "aliyun": if ossConfig.OSSAccessKeyID != "" { if decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeyID); err == nil { accessKey = decrypted } } if ossConfig.OSSAccessKeySecret != "" { if decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeySecret); err == nil { secretKey = decrypted } } bucket = ossConfig.OSSBucket domain = ossConfig.OSSDomain // 向后兼容 if accessKey == "" && ossConfig.AccessKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if domain == "" { domain = ossConfig.Domain } case "qcloud": if ossConfig.QCloudSecretID != "" { if decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretID); err == nil { accessKey = decrypted } } if ossConfig.QCloudSecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretKey); err == nil { secretKey = decrypted } } bucket = ossConfig.QCloudBucket region = ossConfig.QCloudRegion domain = ossConfig.QCloudDomain // 向后兼容 if accessKey == "" && ossConfig.AccessKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if region == "" { region = ossConfig.Region } if domain == "" { domain = ossConfig.Domain } case "qiniu": if ossConfig.QiniuAccessKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.QiniuAccessKey); err == nil { accessKey = decrypted } } if ossConfig.QiniuSecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.QiniuSecretKey); err == nil { secretKey = decrypted } } bucket = ossConfig.QiniuBucket region = ossConfig.QiniuRegion domain = ossConfig.QiniuDomain // 向后兼容 if accessKey == "" && ossConfig.AccessKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil { accessKey = decrypted } } if secretKey == "" && ossConfig.SecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil { secretKey = decrypted } } if bucket == "" { bucket = ossConfig.Bucket } if region == "" { region = ossConfig.Region } if domain == "" { domain = ossConfig.Domain } default: if ossConfig.AccessKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil { accessKey = decrypted } } if ossConfig.SecretKey != "" { if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil { secretKey = decrypted } } bucket = ossConfig.Bucket region = ossConfig.Region domain = ossConfig.Domain } config.AccessKey = accessKey config.SecretKey = secretKey config.Bucket = bucket config.Region = region config.Domain = domain uploader, err := utils.GetOSSUploader(config) if err == nil { uploader.Delete(attachment.FilePath) } } } // 删除数据库记录 if err := repositories.DeleteAttachment(id); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Attachment deleted successfully", nil) } // AdminUpdateAttachment 更新附件 func AdminUpdateAttachment(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid attachment ID") return } id := uint(parsedID) // 获取现有附件 attachment, err := repositories.GetAttachmentByID(id) if err != nil || attachment == nil { utils.Error(c, 404, "Attachment not found") return } // 解析请求体 var req struct { CategoryID *uint `json:"categoryId"` } if err := c.ShouldBindJSON(&req); err != nil { utils.Error(c, 400, "Invalid request") return } // 更新分类ID if req.CategoryID != nil { if *req.CategoryID == 0 { // 如果categoryId为0,设置为nil(无分类) attachment.CategoryID = nil } else { attachment.CategoryID = req.CategoryID } } // 更新附件 if err := repositories.UpdateAttachment(attachment); err != nil { utils.ServerError(c, err) return } // 重新获取更新后的附件(包含关联的分类信息) updatedAttachment, err := repositories.GetAttachmentByID(id) if err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Attachment updated successfully", updatedAttachment) } // AdminGetAttachmentCategories 获取附件分类列表 func AdminGetAttachmentCategories(c *gin.Context) { categories, err := repositories.GetAttachmentCategories() if err != nil { utils.ServerError(c, err) return } utils.Success(c, categories) } // AdminCreateAttachmentCategory 创建附件分类 func AdminCreateAttachmentCategory(c *gin.Context) { var category models.AttachmentCategory if err := c.ShouldBindJSON(&category); err != nil { utils.Error(c, 400, "Invalid request") return } if err := repositories.CreateAttachmentCategory(&category); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Category created successfully", category) } // AdminUpdateAttachmentCategory 更新附件分类 func AdminUpdateAttachmentCategory(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid category ID") return } id := uint(parsedID) var category models.AttachmentCategory if err := c.ShouldBindJSON(&category); err != nil { utils.Error(c, 400, "Invalid request") return } category.ID = id if err := repositories.UpdateAttachmentCategory(&category); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Category updated successfully", nil) } // AdminDeleteAttachmentCategory 删除附件分类 func AdminDeleteAttachmentCategory(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid category ID") return } id := uint(parsedID) if err := repositories.DeleteAttachmentCategory(id); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Category deleted successfully", nil) } // AdminGetOSSConfigs 获取OSS配置列表 func AdminGetOSSConfigs(c *gin.Context) { // 检查是否存在默认本地存储配置,如果不存在则创建 defaultConfig, err := repositories.GetDefaultLocalOSSConfig() if err != nil { log.Printf("Error checking default local OSS config: %v", err) } else if defaultConfig == nil { // 创建默认本地存储配置 // 本地存储不需要AccessKey和SecretKey,直接使用空字符串(不加密) defaultOSSConfig := &models.OSSConfig{ Name: "本地存储", StorageType: "local", AccessKey: "", // 空字符串,不加密 SecretKey: "", // 空字符串,不加密 Bucket: "", Region: "", Domain: "", IsActive: 1, } if err := repositories.CreateOSSConfig(defaultOSSConfig); err != nil { log.Printf("Error creating default local OSS config: %v", err) } } configs, err := repositories.GetOSSConfigs() if err != nil { utils.ServerError(c, err) return } // 不返回加密的密钥 for i := range configs { configs[i].AccessKey = "***" configs[i].SecretKey = "***" configs[i].OSSAccessKeyID = "***" configs[i].OSSAccessKeySecret = "***" configs[i].QCloudSecretID = "***" configs[i].QCloudSecretKey = "***" configs[i].QiniuAccessKey = "***" configs[i].QiniuSecretKey = "***" } utils.Success(c, configs) } // AdminCreateOSSConfig 创建OSS配置 func AdminCreateOSSConfig(c *gin.Context) { var req struct { Name string `json:"name" binding:"required"` StorageType string `json:"storageType" binding:"required"` // 通用字段(向后兼容) AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` Bucket string `json:"bucket"` Region string `json:"region"` Domain string `json:"domain"` // 阿里云OSS专用字段 OSSAccessKeyID string `json:"ossAccessKeyId"` OSSAccessKeySecret string `json:"ossAccessKeySecret"` OSSEndpoint string `json:"ossEndpoint"` OSSBucket string `json:"ossBucket"` OSSDomain string `json:"ossDomain"` // 腾讯云COS专用字段 QCloudSecretID string `json:"qcloudSecretId"` QCloudSecretKey string `json:"qcloudSecretKey"` QCloudRegion string `json:"qcloudRegion"` QCloudBucket string `json:"qcloudBucket"` QCloudDomain string `json:"qcloudDomain"` // 七牛云专用字段 QiniuAccessKey string `json:"qiniuAccessKey"` QiniuSecretKey string `json:"qiniuSecretKey"` QiniuBucket string `json:"qiniuBucket"` QiniuRegion string `json:"qiniuRegion"` QiniuDomain string `json:"qiniuDomain"` IsActive int `json:"isActive"` } if err := c.ShouldBindJSON(&req); err != nil { utils.Error(c, 400, "Invalid request") return } // 加密函数 encryptKey := func(key string) (string, error) { if key == "" { return "", nil } return utils.EncryptAES(key) } // 加密通用密钥(向后兼容) encryptedAccessKey, err := encryptKey(req.AccessKey) if err != nil { utils.Error(c, 500, "Failed to encrypt access key: "+err.Error()) return } encryptedSecretKey, err := encryptKey(req.SecretKey) if err != nil { utils.Error(c, 500, "Failed to encrypt secret key: "+err.Error()) return } // 加密阿里云密钥 encryptedOSSAccessKeyID, err := encryptKey(req.OSSAccessKeyID) if err != nil { utils.Error(c, 500, "Failed to encrypt OSS access key ID: "+err.Error()) return } encryptedOSSAccessKeySecret, err := encryptKey(req.OSSAccessKeySecret) if err != nil { utils.Error(c, 500, "Failed to encrypt OSS access key secret: "+err.Error()) return } // 加密腾讯云密钥 encryptedQCloudSecretID, err := encryptKey(req.QCloudSecretID) if err != nil { utils.Error(c, 500, "Failed to encrypt QCloud secret ID: "+err.Error()) return } encryptedQCloudSecretKey, err := encryptKey(req.QCloudSecretKey) if err != nil { utils.Error(c, 500, "Failed to encrypt QCloud secret key: "+err.Error()) return } // 加密七牛云密钥 encryptedQiniuAccessKey, err := encryptKey(req.QiniuAccessKey) if err != nil { utils.Error(c, 500, "Failed to encrypt Qiniu access key: "+err.Error()) return } encryptedQiniuSecretKey, err := encryptKey(req.QiniuSecretKey) if err != nil { utils.Error(c, 500, "Failed to encrypt Qiniu secret key: "+err.Error()) return } ossConfig := &models.OSSConfig{ Name: req.Name, StorageType: req.StorageType, // 通用字段(向后兼容) AccessKey: encryptedAccessKey, SecretKey: encryptedSecretKey, Bucket: req.Bucket, Region: req.Region, Domain: req.Domain, // 阿里云OSS专用字段 OSSAccessKeyID: encryptedOSSAccessKeyID, OSSAccessKeySecret: encryptedOSSAccessKeySecret, OSSEndpoint: req.OSSEndpoint, OSSBucket: req.OSSBucket, OSSDomain: req.OSSDomain, // 腾讯云COS专用字段 QCloudSecretID: encryptedQCloudSecretID, QCloudSecretKey: encryptedQCloudSecretKey, QCloudRegion: req.QCloudRegion, QCloudBucket: req.QCloudBucket, QCloudDomain: req.QCloudDomain, // 七牛云专用字段 QiniuAccessKey: encryptedQiniuAccessKey, QiniuSecretKey: encryptedQiniuSecretKey, QiniuBucket: req.QiniuBucket, QiniuRegion: req.QiniuRegion, QiniuDomain: req.QiniuDomain, IsActive: req.IsActive, } if err := repositories.CreateOSSConfig(ossConfig); err != nil { utils.ServerError(c, err) return } // 不返回加密的密钥 ossConfig.AccessKey = "***" ossConfig.SecretKey = "***" ossConfig.OSSAccessKeyID = "***" ossConfig.OSSAccessKeySecret = "***" ossConfig.QCloudSecretID = "***" ossConfig.QCloudSecretKey = "***" ossConfig.QiniuAccessKey = "***" ossConfig.QiniuSecretKey = "***" utils.SuccessWithMsg(c, "OSS config created successfully", ossConfig) } // AdminUpdateOSSConfig 更新OSS配置 func AdminUpdateOSSConfig(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid OSS config ID") return } id := uint(parsedID) var req struct { Name string `json:"name"` StorageType string `json:"storageType"` // 通用字段(向后兼容) AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` Bucket string `json:"bucket"` Region string `json:"region"` Domain string `json:"domain"` // 阿里云OSS专用字段 OSSAccessKeyID string `json:"ossAccessKeyId"` OSSAccessKeySecret string `json:"ossAccessKeySecret"` OSSEndpoint string `json:"ossEndpoint"` OSSBucket string `json:"ossBucket"` OSSDomain string `json:"ossDomain"` // 腾讯云COS专用字段 QCloudSecretID string `json:"qcloudSecretId"` QCloudSecretKey string `json:"qcloudSecretKey"` QCloudRegion string `json:"qcloudRegion"` QCloudBucket string `json:"qcloudBucket"` QCloudDomain string `json:"qcloudDomain"` // 七牛云专用字段 QiniuAccessKey string `json:"qiniuAccessKey"` QiniuSecretKey string `json:"qiniuSecretKey"` QiniuBucket string `json:"qiniuBucket"` QiniuRegion string `json:"qiniuRegion"` QiniuDomain string `json:"qiniuDomain"` IsActive int `json:"isActive"` } if err := c.ShouldBindJSON(&req); err != nil { utils.Error(c, 400, "Invalid request") return } // 获取现有配置 existingConfig, err := repositories.GetOSSConfigByID(id) if err != nil || existingConfig == nil { utils.Error(c, 404, "OSS config not found") return } storageType := existingConfig.StorageType if req.StorageType != "" { storageType = req.StorageType } // 启用非 local 配置前,按存储类型校验密钥(优先专用字段,兼容 legacy) if req.IsActive == 1 && existingConfig.IsActive == 0 && storageType != "local" { if errMsg := validateOSSCredentialsForEnable(storageType, existingConfig, req.AccessKey, req.SecretKey, req.OSSAccessKeyID, req.OSSAccessKeySecret, req.OSSEndpoint, req.OSSBucket, req.QCloudSecretID, req.QCloudSecretKey, req.QCloudRegion, req.QCloudBucket, req.QiniuAccessKey, req.QiniuSecretKey, req.QiniuBucket); errMsg != "" { utils.Error(c, 400, errMsg) return } } // 加密函数 encryptKey := func(key string) (string, error) { if key == "" { return "", nil } return utils.EncryptAES(key) } // 密钥字段:*** 或空表示不修改已有值 updateSecret := func(reqVal string, dest *string) error { if reqVal == "***" || reqVal == "" { return nil } encrypted, err := encryptKey(reqVal) if err != nil { return err } *dest = encrypted return nil } // 更新字段 if req.Name != "" { existingConfig.Name = req.Name } if req.StorageType != "" { existingConfig.StorageType = req.StorageType } if err := updateSecret(req.AccessKey, &existingConfig.AccessKey); err != nil { utils.Error(c, 500, "Failed to encrypt access key: "+err.Error()) return } if err := updateSecret(req.SecretKey, &existingConfig.SecretKey); err != nil { utils.Error(c, 500, "Failed to encrypt secret key: "+err.Error()) return } existingConfig.Bucket = req.Bucket existingConfig.Region = req.Region existingConfig.Domain = req.Domain if err := updateSecret(req.OSSAccessKeyID, &existingConfig.OSSAccessKeyID); err != nil { utils.Error(c, 500, "Failed to encrypt OSS access key ID: "+err.Error()) return } if err := updateSecret(req.OSSAccessKeySecret, &existingConfig.OSSAccessKeySecret); err != nil { utils.Error(c, 500, "Failed to encrypt OSS access key secret: "+err.Error()) return } existingConfig.OSSEndpoint = req.OSSEndpoint existingConfig.OSSBucket = req.OSSBucket existingConfig.OSSDomain = req.OSSDomain if err := updateSecret(req.QCloudSecretID, &existingConfig.QCloudSecretID); err != nil { utils.Error(c, 500, "Failed to encrypt QCloud secret ID: "+err.Error()) return } if err := updateSecret(req.QCloudSecretKey, &existingConfig.QCloudSecretKey); err != nil { utils.Error(c, 500, "Failed to encrypt QCloud secret key: "+err.Error()) return } existingConfig.QCloudRegion = req.QCloudRegion existingConfig.QCloudBucket = req.QCloudBucket existingConfig.QCloudDomain = req.QCloudDomain if err := updateSecret(req.QiniuAccessKey, &existingConfig.QiniuAccessKey); err != nil { utils.Error(c, 500, "Failed to encrypt Qiniu access key: "+err.Error()) return } if err := updateSecret(req.QiniuSecretKey, &existingConfig.QiniuSecretKey); err != nil { utils.Error(c, 500, "Failed to encrypt Qiniu secret key: "+err.Error()) return } existingConfig.QiniuBucket = req.QiniuBucket existingConfig.QiniuRegion = req.QiniuRegion existingConfig.QiniuDomain = req.QiniuDomain existingConfig.IsActive = req.IsActive if err := repositories.UpdateOSSConfig(existingConfig); err != nil { utils.ServerError(c, err) return } maskOSSSecrets(existingConfig) utils.SuccessWithMsg(c, "OSS config updated successfully", existingConfig) } // AdminDeleteOSSConfig 删除OSS配置 func AdminDeleteOSSConfig(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid OSS config ID") return } id := uint(parsedID) // 获取配置信息,检查是否为默认本地存储配置 ossConfig, err := repositories.GetOSSConfigByID(id) if err != nil { utils.ServerError(c, err) return } if ossConfig == nil { utils.Error(c, 404, "OSS config not found") return } // 检查是否为默认本地存储配置,不允许删除 if ossConfig.Name == "本地存储" && ossConfig.StorageType == "local" { utils.Error(c, 400, "默认本地存储配置不能删除") return } if err := repositories.DeleteOSSConfig(id); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "OSS config deleted successfully", nil) } // AdminTestOSSConfig 测试OSS配置连通性 func AdminTestOSSConfig(c *gin.Context) { idStr := c.Param("id") parsedID, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.Error(c, 400, "Invalid OSS config ID") return } id := uint(parsedID) ossConfig, err := repositories.GetOSSConfigByID(id) if err != nil || ossConfig == nil { utils.Error(c, 404, "OSS config not found") return } var runtimeCfg *utils.OSSConfig if ossConfig.StorageType == "aliyun" { if envCfg, ok := utils.LoadAliyunConfigFromEnv(); ok { runtimeCfg = envCfg } } if runtimeCfg == nil { runtimeCfg, err = buildRuntimeOSSConfig(ossConfig) if err != nil { utils.Error(c, 400, err.Error()) return } } if err := utils.TestOSSConnection(runtimeCfg); err != nil { utils.Error(c, 400, "连接测试失败: "+err.Error()) return } source := "db" if ossConfig.StorageType == "aliyun" && utils.HasAliyunEnvConfig() { source = "env" } utils.SuccessWithMsg(c, "连接测试成功", gin.H{ "storageType": ossConfig.StorageType, "name": ossConfig.Name, "source": source, }) } func decryptOptional(encrypted string) string { if encrypted == "" { return "" } decrypted, err := utils.DecryptAES(encrypted) if err != nil { return "" } return decrypted } func pickCredential(reqVal, existingEncrypted string) string { if reqVal != "" && reqVal != "***" { return reqVal } return decryptOptional(existingEncrypted) } func validateOSSCredentialsForEnable( storageType string, existing *models.OSSConfig, legacyAccessKey, legacySecretKey string, ossAccessKeyID, ossAccessKeySecret, ossEndpoint, ossBucket string, qcloudSecretID, qcloudSecretKey, qcloudRegion, qcloudBucket string, qiniuAccessKey, qiniuSecretKey, qiniuBucket string, ) string { switch storageType { case "aliyun": ak := pickCredential(ossAccessKeyID, existing.OSSAccessKeyID) sk := pickCredential(ossAccessKeySecret, existing.OSSAccessKeySecret) if ak == "" { ak = pickCredential(legacyAccessKey, existing.AccessKey) } if sk == "" { sk = pickCredential(legacySecretKey, existing.SecretKey) } endpoint := ossEndpoint if endpoint == "" { endpoint = existing.OSSEndpoint } if endpoint == "" { endpoint = existing.Region } bucket := ossBucket if bucket == "" { bucket = existing.OSSBucket } if bucket == "" { bucket = existing.Bucket } if ak == "" || sk == "" || endpoint == "" || bucket == "" { return "启用阿里云OSS前必须填写 AccessKeyId、AccessKeySecret、Endpoint 和 Bucket" } case "qcloud": id := pickCredential(qcloudSecretID, existing.QCloudSecretID) key := pickCredential(qcloudSecretKey, existing.QCloudSecretKey) if id == "" { id = pickCredential(legacyAccessKey, existing.AccessKey) } if key == "" { key = pickCredential(legacySecretKey, existing.SecretKey) } region := qcloudRegion if region == "" { region = existing.QCloudRegion } if region == "" { region = existing.Region } bucket := qcloudBucket if bucket == "" { bucket = existing.QCloudBucket } if bucket == "" { bucket = existing.Bucket } if id == "" || key == "" || region == "" || bucket == "" { return "启用腾讯云COS前必须填写 SecretId、SecretKey、Region 和 Bucket" } case "qiniu": ak := pickCredential(qiniuAccessKey, existing.QiniuAccessKey) sk := pickCredential(qiniuSecretKey, existing.QiniuSecretKey) if ak == "" { ak = pickCredential(legacyAccessKey, existing.AccessKey) } if sk == "" { sk = pickCredential(legacySecretKey, existing.SecretKey) } bucket := qiniuBucket if bucket == "" { bucket = existing.QiniuBucket } if bucket == "" { bucket = existing.Bucket } if ak == "" || sk == "" || bucket == "" { return "启用七牛云前必须填写 AccessKey、SecretKey 和 Bucket" } default: ak := pickCredential(legacyAccessKey, existing.AccessKey) sk := pickCredential(legacySecretKey, existing.SecretKey) if ak == "" || sk == "" { return "启用非本地存储配置前必须填写密钥" } } return "" } func maskOSSSecrets(ossConfig *models.OSSConfig) { ossConfig.AccessKey = "***" ossConfig.SecretKey = "***" ossConfig.OSSAccessKeyID = "***" ossConfig.OSSAccessKeySecret = "***" ossConfig.QCloudSecretID = "***" ossConfig.QCloudSecretKey = "***" ossConfig.QiniuAccessKey = "***" ossConfig.QiniuSecretKey = "***" } func buildRuntimeOSSConfig(ossConfig *models.OSSConfig) (*utils.OSSConfig, error) { cfg := &utils.OSSConfig{ StorageType: ossConfig.StorageType, AccessKey: decryptOptional(ossConfig.AccessKey), SecretKey: decryptOptional(ossConfig.SecretKey), Bucket: ossConfig.Bucket, Region: ossConfig.Region, Domain: ossConfig.Domain, } switch ossConfig.StorageType { case "aliyun": cfg.OSSAccessKeyID = decryptOptional(ossConfig.OSSAccessKeyID) cfg.OSSAccessKeySecret = decryptOptional(ossConfig.OSSAccessKeySecret) cfg.OSSEndpoint = ossConfig.OSSEndpoint cfg.OSSBucket = ossConfig.OSSBucket cfg.OSSDomain = ossConfig.OSSDomain if cfg.OSSAccessKeyID == "" { cfg.OSSAccessKeyID = cfg.AccessKey } if cfg.OSSAccessKeySecret == "" { cfg.OSSAccessKeySecret = cfg.SecretKey } if cfg.OSSBucket == "" { cfg.OSSBucket = cfg.Bucket } if cfg.OSSDomain == "" { cfg.OSSDomain = cfg.Domain } if cfg.OSSAccessKeyID == "" || cfg.OSSAccessKeySecret == "" || cfg.OSSBucket == "" { return nil, fmt.Errorf("阿里云OSS配置不完整:需要 AccessKeyId、AccessKeySecret 和 Bucket") } if cfg.OSSEndpoint == "" && cfg.Region == "" { return nil, fmt.Errorf("阿里云OSS配置不完整:需要 Endpoint 或 Region") } case "qcloud": cfg.QCloudSecretID = decryptOptional(ossConfig.QCloudSecretID) cfg.QCloudSecretKey = decryptOptional(ossConfig.QCloudSecretKey) cfg.QCloudRegion = ossConfig.QCloudRegion cfg.QCloudBucket = ossConfig.QCloudBucket cfg.QCloudDomain = ossConfig.QCloudDomain if cfg.QCloudSecretID == "" { cfg.QCloudSecretID = cfg.AccessKey } if cfg.QCloudSecretKey == "" { cfg.QCloudSecretKey = cfg.SecretKey } if cfg.QCloudRegion == "" { cfg.QCloudRegion = cfg.Region } if cfg.QCloudBucket == "" { cfg.QCloudBucket = cfg.Bucket } if cfg.QCloudDomain == "" { cfg.QCloudDomain = cfg.Domain } if cfg.QCloudSecretID == "" || cfg.QCloudSecretKey == "" || cfg.QCloudBucket == "" || cfg.QCloudRegion == "" { return nil, fmt.Errorf("腾讯云COS配置不完整:需要 SecretId、SecretKey、Bucket 和 Region") } case "qiniu": cfg.QiniuAccessKey = decryptOptional(ossConfig.QiniuAccessKey) cfg.QiniuSecretKey = decryptOptional(ossConfig.QiniuSecretKey) cfg.QiniuBucket = ossConfig.QiniuBucket cfg.QiniuRegion = ossConfig.QiniuRegion cfg.QiniuDomain = ossConfig.QiniuDomain if cfg.QiniuAccessKey == "" { cfg.QiniuAccessKey = cfg.AccessKey } if cfg.QiniuSecretKey == "" { cfg.QiniuSecretKey = cfg.SecretKey } if cfg.QiniuBucket == "" { cfg.QiniuBucket = cfg.Bucket } if cfg.QiniuDomain == "" { cfg.QiniuDomain = cfg.Domain } if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || cfg.QiniuBucket == "" { return nil, fmt.Errorf("七牛云配置不完整:需要 AccessKey、SecretKey 和 Bucket") } case "local": // no credentials required default: return nil, fmt.Errorf("不支持的存储类型: %s", ossConfig.StorageType) } return cfg, nil }