小程序可在 Go 上完成微信手机号登录、测评、预约和下单,不再反代 Java。 Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
2.2 KiB
Go
96 lines
2.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/avatar"
|
|
"github.com/yuxingu/digital-psychology/apps/api/pkg/response"
|
|
)
|
|
|
|
// MediaHandler serves uploaded media files.
|
|
type MediaHandler struct {
|
|
AvatarDir string
|
|
}
|
|
|
|
// Register mounts public media routes.
|
|
func (h *MediaHandler) Register(api *gin.RouterGroup) {
|
|
api.GET("/media/avatars/:file", h.ServeAvatar)
|
|
api.GET("/media/uploads/:file", h.ServeUpload)
|
|
api.POST("/upload", h.Upload)
|
|
}
|
|
|
|
// ServeAvatar streams a stored avatar image.
|
|
func (h *MediaHandler) ServeAvatar(c *gin.Context) {
|
|
dir := h.AvatarDir
|
|
if dir == "" {
|
|
dir = "data/avatars"
|
|
}
|
|
full, err := avatar.ResolveAbs(dir, c.Param("file"))
|
|
if err != nil {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "public, max-age=86400")
|
|
c.File(full)
|
|
_ = filepath.Ext(full)
|
|
}
|
|
|
|
func (h *MediaHandler) uploadDir() string {
|
|
return "data/uploads"
|
|
}
|
|
|
|
func (h *MediaHandler) ServeUpload(c *gin.Context) {
|
|
name := filepath.Base(c.Param("file"))
|
|
full := filepath.Join(h.uploadDir(), name)
|
|
if _, err := os.Stat(full); err != nil {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
c.File(full)
|
|
}
|
|
|
|
func (h *MediaHandler) Upload(c *gin.Context) {
|
|
fh, err := c.FormFile("file")
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 10000, "请选择文件")
|
|
return
|
|
}
|
|
src, err := fh.Open()
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 10000, "读取失败")
|
|
return
|
|
}
|
|
defer src.Close()
|
|
ext := strings.ToLower(filepath.Ext(fh.Filename))
|
|
if ext == "" {
|
|
ext = ".jpg"
|
|
}
|
|
var b [8]byte
|
|
_, _ = rand.Read(b[:])
|
|
name := hex.EncodeToString(b[:]) + ext
|
|
if err := os.MkdirAll(h.uploadDir(), 0o755); err != nil {
|
|
response.Fail(c, http.StatusInternalServerError, 50001, "保存失败")
|
|
return
|
|
}
|
|
dstPath := filepath.Join(h.uploadDir(), name)
|
|
dst, err := os.Create(dstPath)
|
|
if err != nil {
|
|
response.Fail(c, http.StatusInternalServerError, 50001, "保存失败")
|
|
return
|
|
}
|
|
defer dst.Close()
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
response.Fail(c, http.StatusInternalServerError, 50001, "保存失败")
|
|
return
|
|
}
|
|
response.OK(c, "/api/v1/media/uploads/"+name)
|
|
}
|