增加七牛云存储
This commit is contained in:
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user