Files
sc-lktx-mp/sc-lktx-backend/internal/modules/upload/handler.go
huangjin fe3ad20fe2 'init'
2026-06-29 17:34:59 +08:00

267 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)
}