Files
nl-blogs/server/handlers/attachment.go
李琦 4f4153ee0e 1. 小程序端
2. 视频优化
2026-07-30 14:11:26 +08:00

1248 lines
34 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}