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