改七牛直传

This commit is contained in:
李志强 2026-04-09 18:07:59 +08:00
parent 6df84b0584
commit f84df652d9
3 changed files with 285 additions and 0 deletions

274
controllers/qiniu_upload.go Normal file
View File

@ -0,0 +1,274 @@
package controllers
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
"server/models"
"server/services"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
)
// QiniuUploadController 七牛云上传控制器
type QiniuUploadController struct {
BaseController
}
// GetUploadToken 获取上传凭证
// GET /platform/qiniu/token
func (c *QiniuUploadController) GetUploadToken() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
// 获取存储配置
cfg, err := models.GetStorageConfig()
if err != nil || cfg.StorageType != "qiniu" {
c.jsonErr(400, 400, "当前未配置七牛云存储")
return
}
// 检查配置完整性
if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || cfg.QiniuBucket == "" {
c.jsonErr(500, 500, "七牛云配置不完整")
return
}
// 生成文件 key前端可以覆盖
datePath := time.Now().Format("2006/01/02")
timestamp := time.Now().UnixNano()
keyPrefix := fmt.Sprintf("%s/%d", datePath, timestamp)
// 创建上传策略
mac := qbox.NewMac(cfg.QiniuAccessKey, cfg.QiniuSecretKey)
putPolicy := storage.PutPolicy{
Scope: cfg.QiniuBucket,
ReturnBody: `{"key":"$(key)","hash":"$(etag)","size":$(fsize),"mimeType":"$(mimeType)"}`,
Expires: 3600, // 1小时有效期
}
upToken := putPolicy.UploadToken(mac)
// 返回上传凭证和配置
c.jsonOK(map[string]interface{}{
"token": upToken,
"domain": cfg.QiniuDomain,
"bucket": cfg.QiniuBucket,
"region": cfg.QiniuRegion,
"keyPrefix": keyPrefix,
"expires": time.Now().Add(time.Hour).Unix(),
"uploadUrl": getQiniuUploadURL(cfg.QiniuRegion),
})
}
// SaveFileRecord 保存文件记录
// POST /platform/qiniu/save
func (c *QiniuUploadController) SaveFileRecord() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
tid := c.effectiveTid(claims)
// 解析请求参数
type SaveRequest struct {
Key string `json:"key"` // 七牛云文件 key
Hash string `json:"hash"` // 文件 hash (etag)
Size int64 `json:"size"` // 文件大小
Name string `json:"name"` // 原始文件名
MimeType string `json:"mimeType"` // 文件类型
Cate uint64 `json:"cate"` // 分类 ID
}
var req SaveRequest
if err := c.ParseJSON(&req); err != nil {
c.jsonErr(400, 400, "参数解析失败: "+err.Error())
return
}
// 验证必填字段
if req.Key == "" || req.Name == "" {
c.jsonErr(400, 400, "缺少必填参数")
return
}
// 获取存储配置
cfg, err := models.GetStorageConfig()
if err != nil || cfg.StorageType != "qiniu" {
c.jsonErr(400, 400, "当前未配置七牛云存储")
return
}
// 构建完整 URL
domain := strings.TrimRight(cfg.QiniuDomain, "/")
fileURL := fmt.Sprintf("%s/%s", domain, req.Key)
// 计算 MD5使用 hash 作为 MD5或者重新计算
md5Sum := req.Hash
if md5Sum == "" {
// 如果没有 hash使用 key 生成一个唯一标识
h := md5.New()
h.Write([]byte(req.Key))
md5Sum = hex.EncodeToString(h.Sum(nil))
}
// 检查文件是否已存在(通过 MD5
var exist models.SystemFile
err = models.Orm.QueryTable(new(models.SystemFile)).
Filter("md5", md5Sum).
Filter("tid", tid).
Filter("delete_time__isnull", true).
One(&exist)
if err == nil {
// 文件已存在,返回已有记录
c.Data["json"] = map[string]interface{}{
"code": 201,
"msg": "文件已存在",
"data": map[string]interface{}{
"url": exist.Src,
"id": exist.ID,
"name": exist.Name,
},
}
_ = c.ServeJSON()
return
}
// 检测文件类型
ext := getFileExt(req.Name)
fileType := detectFileType(ext)
// 保存文件记录
adminID := uint64(claims.UserID)
row := &models.SystemFile{
Tid: tid,
Uid: &adminID,
Name: req.Name,
Type: fileType,
Cate: req.Cate,
Size: uint64(req.Size),
Src: fileURL,
Uploader: adminID,
Md5: md5Sum,
}
id, err := models.Orm.Insert(row)
if err != nil {
c.jsonErr(500, 500, "保存文件记录失败: "+err.Error())
return
}
c.jsonOK(map[string]interface{}{
"url": fileURL,
"id": uint64(id),
"name": req.Name,
"key": req.Key,
})
}
// GetStorageConfig 获取存储配置(前端用于判断上传方式)
// GET /platform/storage/config
func (c *QiniuUploadController) GetStorageConfig() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
cfg, err := models.GetStorageConfig()
if err != nil {
c.jsonOK(map[string]interface{}{
"storageType": "local",
})
return
}
// 只返回必要的配置信息,不返回密钥
c.jsonOK(map[string]interface{}{
"storageType": cfg.StorageType,
"qiniuDomain": cfg.QiniuDomain,
"qiniuRegion": cfg.QiniuRegion,
})
}
// getQiniuUploadURL 根据区域获取上传地址
func getQiniuUploadURL(region string) string {
switch region {
case "z0":
return "https://up-z0.qiniup.com"
case "z1":
return "https://up-z1.qiniup.com"
case "z2":
return "https://up-z2.qiniup.com"
case "na0":
return "https://up-na0.qiniup.com"
case "as0":
return "https://up-as0.qiniup.com"
case "cn-east-2":
return "https://up-cn-east-2.qiniup.com"
default:
return "https://up-z0.qiniup.com" // 默认华东
}
}
// getFileExt 获取文件扩展名
func getFileExt(filename string) string {
parts := strings.Split(filename, ".")
if len(parts) > 1 {
return strings.ToLower(parts[len(parts)-1])
}
return ""
}
// detectFileType 检测文件类型
func detectFileType(ext string) uint8 {
imageExts := map[string]bool{
"jpg": true, "jpeg": true, "png": true, "gif": true, "bmp": true,
"webp": true, "svg": true, "ico": true,
}
videoExts := map[string]bool{
"mp4": true, "avi": true, "mov": true, "wmv": true, "flv": true,
"mkv": true, "webm": true, "m4v": true,
}
audioExts := map[string]bool{
"mp3": true, "wav": true, "flac": true, "aac": true, "ogg": true,
"m4a": true, "wma": true,
}
docExts := map[string]bool{
"doc": true, "docx": true, "xls": true, "xlsx": true, "ppt": true,
"pptx": true, "pdf": true, "txt": true, "md": true,
}
archiveExts := map[string]bool{
"zip": true, "rar": true, "7z": true, "tar": true, "gz": true,
"bz2": true, "xz": true,
}
executableExts := map[string]bool{
"exe": true, "msi": true, "dmg": true, "pkg": true, "deb": true,
"rpm": true, "apk": true, "msix": true,
}
if imageExts[ext] {
return 1 // 图片
}
if videoExts[ext] {
return 2 // 视频
}
if audioExts[ext] {
return 3 // 音频
}
if docExts[ext] {
return 4 // 文档
}
if archiveExts[ext] || executableExts[ext] {
return 5 // 压缩包/安装包
}
return 0 // 其他
}

View File

@ -13,6 +13,12 @@ chmod +x install-systemd-service.sh
# 运行安装脚本
sudo bash install-systemd-service.sh
或者
sudo env PATH=$PATH:/usr/local/btgo/bin bash install-systemd-service.sh
```
脚本会自动:

View File

@ -152,4 +152,9 @@ func Register() {
beego.Router("/platform/batchdeletefiles", &controllers.PlatformFileController{}, "post:BatchDeleteFiles")
beego.Router("/platform/batchDeleteFilesPermanently", &controllers.PlatformFileController{}, "post:BatchDeleteFilesPermanently")
beego.Router("/platform/batchMoveFiles", &controllers.PlatformFileController{}, "post:BatchMoveFiles")
// 七牛云直传相关
beego.Router("/platform/storage/config", &controllers.QiniuUploadController{}, "get:GetStorageConfig")
beego.Router("/platform/qiniu/token", &controllers.QiniuUploadController{}, "get:GetUploadToken")
beego.Router("/platform/qiniu/save", &controllers.QiniuUploadController{}, "post:SaveFileRecord")
}