This commit is contained in:
huangjin
2026-06-29 17:34:59 +08:00
commit fe3ad20fe2
271 changed files with 51767 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
// Upload 模块:图片上传(支持上传到微信服务器和 MinIO公开接口无需权限
package upload
import (
"context"
"io"
"net/http"
"path/filepath"
"strings"
"time"
"com.sclktx/m/v2/internal/common/response"
"com.sclktx/m/v2/internal/pkg/minio"
"com.sclktx/m/v2/internal/pkg/wechat"
"github.com/gin-gonic/gin"
)
// UploadHandler 上传处理器
type UploadHandler struct {
wxClient *wechat.MiniProgramClient
minioClient *minio.Client
}
// NewUploadHandler 创建上传处理器
func NewUploadHandler(wxClient *wechat.MiniProgramClient, minioClient *minio.Client) *UploadHandler {
return &UploadHandler{
wxClient: wxClient,
minioClient: minioClient,
}
}
// UploadImgResponse 上传图片返回
type UploadImgResponse struct {
URL string `json:"url"` // 微信返回的图片 URL
}
// @Summary 上传图片到微信服务器
// @Description 上传图片文件到微信服务器(公开接口,无需权限)
// @Tags 文件上传
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "图片文件"
// @Success 200 {object} response.Response{data=upload.UploadImgResponse} "success"
// @Router /api/v1/upload/image [post]
// UploadImage 上传图片文件到微信服务器(公开接口,无需权限)
// POST /api/v1/upload/image
func (h *UploadHandler) UploadImage(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.BadRequest(c, "请选择要上传的图片文件")
return
}
defer file.Close()
// 读取文件内容
imageData, err := io.ReadAll(file)
if err != nil {
response.InternalError(c, "读取文件失败")
return
}
// 获取文件名,如果没有则生成一个
filename := header.Filename
if filename == "" {
filename = "image_" + time.Now().Format("20060102150405") + ".jpg"
}
// 确保文件名有扩展名
ext := strings.ToLower(filepath.Ext(filename))
if ext == "" {
// 根据 Content-Type 猜测扩展名
contentType := header.Header.Get("Content-Type")
switch contentType {
case "image/png":
filename += ".png"
case "image/gif":
filename += ".gif"
case "image/webp":
filename += ".webp"
default:
filename += ".jpg"
}
}
// 上传到微信服务器
result, err := h.wxClient.UploadImg(imageData, filename)
if err != nil {
response.InternalError(c, "上传到微信失败: "+err.Error())
return
}
response.Success(c, &UploadImgResponse{URL: result.URL})
}
// UploadImageByURLRequest 通过链接上传请求
type UploadImageByURLRequest struct {
URL string `json:"url" binding:"required"` // 图片链接
}
// @Summary 通过链接上传图片到微信服务器
// @Description 通过图片链接下载后上传到微信服务器(公开接口,无需权限)
// @Tags 文件上传
// @Accept json
// @Produce json
// @Param body body upload.UploadImageByURLRequest true "图片链接信息"
// @Success 200 {object} response.Response{data=upload.UploadImgResponse} "success"
// @Router /api/v1/upload/image-by-url [post]
// UploadImageByURL 通过图片链接下载后上传到微信服务器(公开接口,无需权限)
// POST /api/v1/upload/image-by-url
func (h *UploadHandler) UploadImageByURL(c *gin.Context) {
var req UploadImageByURLRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请提供图片链接(url)")
return
}
// 下载图片(使用自定义请求,添加 User-Agent 避免被反爬拦截)
httpReq, err := http.NewRequest("GET", req.URL, nil)
if err != nil {
response.InternalError(c, "创建请求失败: "+err.Error())
return
}
httpReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(httpReq)
if err != nil {
response.InternalError(c, "下载图片失败: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
response.InternalError(c, "下载图片失败HTTP状态码: "+http.StatusText(resp.StatusCode))
return
}
imageData, err := io.ReadAll(resp.Body)
if err != nil {
response.InternalError(c, "读取图片数据失败")
return
}
// 从 URL 中提取文件名
filename := extractFilenameFromURL(req.URL)
contentType := resp.Header.Get("Content-Type")
ext := getExtByContentType(contentType)
if !strings.HasSuffix(strings.ToLower(filename), ext) && ext != "" {
filename += ext
}
// 上传到微信服务器
result, err := h.wxClient.UploadImg(imageData, filename)
if err != nil {
response.InternalError(c, "上传到微信失败: "+err.Error())
return
}
response.Success(c, &UploadImgResponse{URL: result.URL})
}
// extractFilenameFromURL 从 URL 中提取文件名
func extractFilenameFromURL(rawURL string) string {
// 去掉查询参数
if idx := strings.Index(rawURL, "?"); idx != -1 {
rawURL = rawURL[:idx]
}
// 取最后一段作为文件名
base := filepath.Base(rawURL)
if base == "." || base == "/" || base == "" {
return "image_" + time.Now().Format("20060102150405") + ".jpg"
}
return base
}
// getExtByContentType 根据 Content-Type 获取文件扩展名
func getExtByContentType(contentType string) string {
switch {
case strings.Contains(contentType, "image/png"):
return ".png"
case strings.Contains(contentType, "image/gif"):
return ".gif"
case strings.Contains(contentType, "image/webp"):
return ".webp"
case strings.Contains(contentType, "image/svg"):
return ".svg"
case strings.Contains(contentType, "image/jpeg"):
return ".jpg"
default:
return ".jpg"
}
}
// UploadToMinioResponse 上传到 MinIO 返回
type UploadToMinioResponse struct {
URL string `json:"url"` // MinIO 中的图片 URL
}
// @Summary 上传图片到MinIO对象存储
// @Description 上传图片到 MinIO 对象存储(公开接口,无需权限)
// @Tags 文件上传
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "图片文件"
// @Success 200 {object} response.Response{data=upload.UploadToMinioResponse} "success"
// @Router /api/v1/upload/minio [post]
// UploadImageToMinio 上传图片到 MinIO 对象存储(公开接口,无需权限)
// POST /api/v1/upload/minio
func (h *UploadHandler) UploadImageToMinio(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.BadRequest(c, "请选择要上传的图片文件")
return
}
defer file.Close()
// 读取文件内容
imageData, err := io.ReadAll(file)
if err != nil {
response.InternalError(c, "读取文件失败")
return
}
// 获取文件名,如果没有则生成一个
filename := header.Filename
if filename == "" {
filename = "image_" + time.Now().Format("20060102150405") + ".jpg"
}
// 确保文件名有扩展名
ext := strings.ToLower(filepath.Ext(filename))
if ext == "" {
// 根据 Content-Type 猜测扩展名
contentType := header.Header.Get("Content-Type")
switch contentType {
case "image/png":
filename += ".png"
case "image/gif":
filename += ".gif"
case "image/webp":
filename += ".webp"
default:
filename += ".jpg"
}
}
// 上传到 MinIO
ctx := context.Background()
url, err := h.minioClient.UploadImage(ctx, imageData, filename)
if err != nil {
response.InternalError(c, "上传到 MinIO 失败:"+err.Error())
return
}
response.Success(c, &UploadToMinioResponse{URL: url})
}
// RegisterRoutes 注册上传路由(公开接口,无需认证和权限)
func (h *UploadHandler) RegisterRoutes(r *gin.RouterGroup) {
r.POST("/upload/image", h.UploadImage)
r.POST("/upload/image-by-url", h.UploadImageByURL)
r.POST("/upload/minio", h.UploadImageToMinio)
}