增加七牛云存储

This commit is contained in:
2026-04-09 16:26:38 +08:00
parent 01426eda44
commit 36d2a8945e
21 changed files with 2544 additions and 61 deletions
+22 -46
View File
@@ -7,13 +7,13 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
@@ -489,39 +489,29 @@ func (c *PlatformFileController) UploadFile() {
return
}
tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("up_%d_%s", time.Now().UnixNano(), header.Filename))
tmp, err := os.Create(tmpPath)
// 获取存储服务
storageService, err := services.GetStorageService()
if err != nil {
c.jsonErr(500, 500, "创建临时文件失败")
return
}
n, copyErr := io.Copy(tmp, fh)
_ = tmp.Close()
if copyErr != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "读取文件失败")
return
}
if n > fileUploadMaxBytes {
_ = os.Remove(tmpPath)
c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB))
return
}
sum, err := md5HashFile(tmpPath)
if err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "计算文件摘要失败")
c.jsonErr(500, 500, "获取存储服务失败: "+err.Error())
return
}
// 上传文件
result, err := storageService.Upload(fh, header)
if err != nil {
c.jsonErr(500, 500, "上传文件失败: "+err.Error())
return
}
// 检查文件是否已存在(通过MD5
var exist models.SystemFile
err = models.Orm.QueryTable(new(models.SystemFile)).
Filter("md5", sum).
Filter("md5", result.MD5).
Filter("tid", tid).
Filter("delete_time__isnull", true).
One(&exist)
if err == nil {
_ = os.Remove(tmpPath)
// 文件已存在,返回已有记录
c.Data["json"] = map[string]interface{}{
"code": 201,
"msg": "文件已存在",
@@ -535,23 +525,7 @@ func (c *PlatformFileController) UploadFile() {
return
}
datePath := time.Now().Format("2006/01/02")
saveName := fmt.Sprintf("%s/%d.%s", datePath, time.Now().UnixNano(), ext)
destDir := filepath.Join("uploads", filepath.FromSlash(datePath))
if err := os.MkdirAll(destDir, 0755); err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "创建目录失败: "+err.Error())
return
}
destPath := filepath.Join("uploads", filepath.FromSlash(saveName))
if err := os.Rename(tmpPath, destPath); err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "保存文件失败: "+err.Error())
return
}
webURL := "/" + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/")
// 获取分类
cateStr := c.GetString("cate")
var cate uint64
if cateStr != "" {
@@ -566,6 +540,7 @@ func (c *PlatformFileController) UploadFile() {
}
}
// 保存文件记录到数据库
row := &models.SystemFile{
Tid: tid,
Uid: &adminID,
@@ -573,14 +548,15 @@ func (c *PlatformFileController) UploadFile() {
Name: header.Filename,
Type: detectFileType(ext),
Cate: cate,
Size: uint64(n),
Src: webURL,
Size: uint64(result.Size),
Src: result.URL,
Uploader: adminID,
Md5: sum,
Md5: result.MD5,
}
id, err := models.Orm.Insert(row)
if err != nil {
removePhysicalBySrc(webURL)
// 数据库插入失败,尝试删除已上传的文件
_ = storageService.Delete(result.Key)
c.jsonErr(500, 500, "上传失败: "+err.Error())
return
}
@@ -589,7 +565,7 @@ func (c *PlatformFileController) UploadFile() {
"code": 200,
"msg": "上传成功",
"data": map[string]interface{}{
"url": webURL,
"url": result.URL,
"id": uint64(id),
"name": header.Filename,
},
+140
View File
@@ -0,0 +1,140 @@
package controllers
import (
"encoding/json"
"io"
"strings"
"server/models"
beego "github.com/beego/beego/v2/server/web"
)
type StorageConfigController struct {
beego.Controller
}
type storageConfigPayload struct {
StorageType string `json:"storage_type"`
QiniuAccessKey *string `json:"qiniu_access_key"`
QiniuSecretKey *string `json:"qiniu_secret_key"`
QiniuBucket *string `json:"qiniu_bucket"`
QiniuDomain *string `json:"qiniu_domain"`
QiniuRegion *string `json:"qiniu_region"`
}
func normalizeStorageType(v string) string {
switch strings.TrimSpace(v) {
case "local", "qiniu":
return strings.TrimSpace(v)
default:
return "local"
}
}
// GetStorageConfig 获取存储配置
// GET /platform/storageConfig
func (c *StorageConfigController) GetStorageConfig() {
cfg, err := models.GetStorageConfig()
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"storage_type": cfg.StorageType,
"qiniu_access_key": cfg.QiniuAccessKey,
"qiniu_secret_key": cfg.QiniuSecretKey,
"qiniu_bucket": cfg.QiniuBucket,
"qiniu_domain": cfg.QiniuDomain,
"qiniu_region": cfg.QiniuRegion,
},
}
_ = c.ServeJSON()
}
// SaveStorageConfig 保存存储配置
// POST /platform/saveStorageConfig
func (c *StorageConfigController) SaveStorageConfig() {
var p storageConfigPayload
raw, _ := io.ReadAll(c.Ctx.Request.Body)
if err := json.Unmarshal(raw, &p); err != nil {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
_ = c.ServeJSON()
return
}
storageType := normalizeStorageType(p.StorageType)
// 如果选择七牛云,验证必填字段
if storageType == "qiniu" {
if p.QiniuAccessKey == nil || strings.TrimSpace(*p.QiniuAccessKey) == "" {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 AccessKey 不能为空"}
_ = c.ServeJSON()
return
}
if p.QiniuSecretKey == nil || strings.TrimSpace(*p.QiniuSecretKey) == "" {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 SecretKey 不能为空"}
_ = c.ServeJSON()
return
}
if p.QiniuBucket == nil || strings.TrimSpace(*p.QiniuBucket) == "" {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 Bucket 不能为空"}
_ = c.ServeJSON()
return
}
if p.QiniuDomain == nil || strings.TrimSpace(*p.QiniuDomain) == "" {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云域名不能为空"}
_ = c.ServeJSON()
return
}
}
var existed models.StorageConfig
err := models.Orm.QueryTable(new(models.StorageConfig)).OrderBy("-id").One(&existed)
if err == nil {
// 更新现有配置
update := map[string]interface{}{
"storage_type": storageType,
"qiniu_access_key": p.QiniuAccessKey,
"qiniu_secret_key": p.QiniuSecretKey,
"qiniu_bucket": p.QiniuBucket,
"qiniu_domain": p.QiniuDomain,
"qiniu_region": p.QiniuRegion,
}
_, err = models.Orm.QueryTable(new(models.StorageConfig)).Filter("id", existed.ID).Update(update)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"}
_ = c.ServeJSON()
return
}
} else {
// 创建新配置
row := &models.StorageConfig{
StorageType: storageType,
QiniuAccessKey: getStringValue(p.QiniuAccessKey),
QiniuSecretKey: getStringValue(p.QiniuSecretKey),
QiniuBucket: getStringValue(p.QiniuBucket),
QiniuDomain: getStringValue(p.QiniuDomain),
QiniuRegion: getStringValue(p.QiniuRegion),
}
if _, err := models.Orm.Insert(row); err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"}
_ = c.ServeJSON()
return
}
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"}
_ = c.ServeJSON()
}
func getStringValue(s *string) string {
if s == nil {
return ""
}
return *s
}
+62
View File
@@ -0,0 +1,62 @@
package controllers
import (
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type StorageMigrationController struct {
beego.Controller
}
// MigrateToQiniu 迁移文件到七牛云
// POST /platform/storage/migrateToQiniu
func (c *StorageMigrationController) MigrateToQiniu() {
// 这里简化处理,实际应该使用异步任务
// 可以使用 goroutine + 进度查询接口实现
// 获取租户ID(从token或参数)
tid := uint64(1) // 示例,实际应从认证信息获取
progress, err := services.MigrateLocalToQiniu(tid)
if err != nil {
c.Data["json"] = map[string]interface{}{
"code": 500,
"msg": "迁移失败: " + err.Error(),
"data": progress,
}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "迁移完成",
"data": map[string]interface{}{
"total": progress.Total,
"success": progress.Success,
"failed": progress.Failed,
"errors": progress.Errors,
},
}
_ = c.ServeJSON()
}
// GetMigrationProgress 获取迁移进度
// GET /platform/storage/migrationProgress
func (c *StorageMigrationController) GetMigrationProgress() {
// 这里需要实现进度查询逻辑
// 可以使用全局变量或Redis存储进度信息
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"total": 0,
"success": 0,
"failed": 0,
"current": "",
},
}
_ = c.ServeJSON()
}