feat(upload): 支持阿里云 OSS 直传凭证 + 营销图管理后台静态路由

- 新增 internal/common/oss: OSS PostPolicy/UploadHost,CDN 为 aliyuncs 时返回 OSS 凭证
- upload_handler: QINIU_CDN_DOMAIN 为 OSS 域名时返回 oss_access_key_id/policy/signature,upload_url 为 bucket 域名
- routes: 增加 /admin/marketing 静态页面路由

Made-with: Cursor
This commit is contained in:
nepiedg
2026-03-06 11:25:23 +00:00
parent 76fd425ca7
commit e14255cf64
3 changed files with 129 additions and 3 deletions
@@ -3,15 +3,21 @@ package handler
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"path"
"regexp"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"wx_service/config"
oss "wx_service/internal/common/oss"
qiniuservice "wx_service/internal/common/qiniu/service"
"wx_service/internal/middleware"
"wx_service/internal/model"
@@ -37,13 +43,61 @@ type qiniuCallbackPayload struct {
MimeType string `json:"mimeType"`
}
// QiniuToken 返回七牛直传所需的 token/key/upload_url 等信息。
type uploadTokenResponse struct {
Token string `json:"token,omitempty"`
Key string `json:"key"`
UploadURL string `json:"upload_url"`
ExpireAt int64 `json:"expire,omitempty"`
CDNDomain string `json:"cdn_domain,omitempty"`
OSSAccessKey string `json:"oss_access_key_id,omitempty"`
OSSPolicy string `json:"oss_policy,omitempty"`
OSSSignature string `json:"oss_signature,omitempty"`
}
var extPattern = regexp.MustCompile(`^\.[a-z0-9]{1,10}$`)
// QiniuToken 返回直传所需的 token/key/upload_urlCDN 为阿里云 OSS 时返回 OSS PostObject 凭证。
// 建议放在鉴权后:用当前登录用户生成 key,避免前端写入任意路径。
func (h *UploadHandler) QiniuToken(c *gin.Context) {
user := middleware.MustCurrentUser(c)
var req qiniuTokenRequest
_ = c.ShouldBindJSON(&req) // filename 可选,解析失败也不影响生成 token
_ = c.ShouldBindJSON(&req)
cfg := config.AppConfig.Qiniu
cdnDomain := strings.TrimSpace(cfg.CDNDomain)
if oss.IsOSSDomain(cdnDomain) && cfg.AccessKey != "" && cfg.SecretKey != "" && cfg.Bucket != "" {
ext := path.Ext(req.Filename)
if ext == "" || !extPattern.MatchString(strings.ToLower(ext)) {
ext = ".jpg"
}
keyPrefix := strings.Trim(cfg.KeyPrefix, "/")
key := fmt.Sprintf("%s/mp_%d/user_%d/%s/%x%s",
keyPrefix, user.MiniProgramID, user.ID,
time.Now().Format("20060102"), time.Now().UnixNano()&0xffffffff, ext)
endpoint := oss.ParseOSSEndpoint(cdnDomain)
expireSeconds := cfg.TokenExpireSeconds
if expireSeconds <= 0 {
expireSeconds = 300
}
policy, signature, err := oss.PostPolicy(cfg.Bucket, endpoint, key, cfg.SecretKey, expireSeconds)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "生成 OSS 凭证失败"))
return
}
uploadURL := oss.UploadHost(cfg.Bucket, endpoint)
cdnHost := "https://" + cfg.Bucket + "." + endpoint + ".aliyuncs.com"
c.JSON(http.StatusOK, model.Success(uploadTokenResponse{
Key: key,
UploadURL: uploadURL,
CDNDomain: cdnHost,
OSSAccessKey: cfg.AccessKey,
OSSPolicy: policy,
OSSSignature: signature,
}))
return
}
token, err := h.qiniuService.CreateUploadToken(user.MiniProgramID, user.ID, req.Filename)
if err != nil {
@@ -54,7 +108,6 @@ func (h *UploadHandler) QiniuToken(c *gin.Context) {
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取上传凭证失败,请稍后重试"))
return
}
c.JSON(http.StatusOK, model.Success(token))
}