'init'
This commit is contained in:
266
sc-lktx-backend/internal/modules/upload/handler.go
Normal file
266
sc-lktx-backend/internal/modules/upload/handler.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user