file
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// Package filehdl 是文件上传服务的 HTTP 接入层,分别提供后台 admin 与小程序 ma 两套入口。
|
||||
package filehdl
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"portal-mini-go/internal/middleware"
|
||||
"portal-mini-go/internal/pkg/errors"
|
||||
"portal-mini-go/internal/pkg/response"
|
||||
filesvc "portal-mini-go/internal/service/file"
|
||||
)
|
||||
|
||||
// Handler 文件上传请求处理。
|
||||
type Handler struct {
|
||||
svc *filesvc.Service
|
||||
}
|
||||
|
||||
func NewHandler(svc *filesvc.Service) *Handler { return &Handler{svc: svc} }
|
||||
|
||||
// 表单字段名。
|
||||
const formFieldFile = "file"
|
||||
|
||||
// Upload 接收 multipart 单文件上传。上传者身份由路由分组决定(admin 或 ma)。
|
||||
func (h *Handler) Upload(c *gin.Context) {
|
||||
fh, err := c.FormFile(formFieldFile)
|
||||
if err != nil {
|
||||
c.Error(errors.ErrBadRequest)
|
||||
return
|
||||
}
|
||||
f, err := fh.Open()
|
||||
if err != nil {
|
||||
c.Error(errors.Wrap(50000, "open uploaded file", err))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
typ, id := resolveUploader(c)
|
||||
if id == 0 {
|
||||
c.Error(errors.ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.svc.Upload(c.Request.Context(), filesvc.UploadInput{
|
||||
Reader: f,
|
||||
Size: fh.Size,
|
||||
Filename: fh.Filename,
|
||||
UploaderType: typ,
|
||||
UploaderID: id,
|
||||
})
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
response.OK(c, res)
|
||||
}
|
||||
|
||||
// Info 返回文件元数据。
|
||||
func (h *Handler) Info(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
f, err := h.svc.Get(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
response.OK(c, f)
|
||||
}
|
||||
|
||||
// Raw 以流形式返回文件内容,按 MIME 设置 Content-Type,供预览/下载。
|
||||
func (h *Handler) Raw(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
rc, f, err := h.svc.Download(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
extra := map[string]string{
|
||||
"Content-Disposition": `inline; filename="` + f.Filename + `"`,
|
||||
}
|
||||
c.DataFromReader(http.StatusOK, f.Size, f.MimeType, rc, extra)
|
||||
}
|
||||
|
||||
// Delete 删除文件(存储内容 + 元数据)。
|
||||
func (h *Handler) Delete(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.svc.Delete(c.Request.Context(), id); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"id": id})
|
||||
}
|
||||
|
||||
// resolveUploader 从 context 解析上传者身份:后台走 admin_id,小程序走 user_id。
|
||||
// 两者均不存在(未认证)返回 ("", 0),由调用方拒绝。
|
||||
func resolveUploader(c *gin.Context) (string, uint64) {
|
||||
if aid, ok := middleware.AdminIDUint(c); ok && aid != 0 {
|
||||
return "admin", aid
|
||||
}
|
||||
if uid := middleware.UserID(c); uid != 0 {
|
||||
return "ma", uid
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
func parseID(c *gin.Context) (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.Error(errors.ErrBadRequest)
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
// RegisterAdminUpload 在已鉴权(AdminAuth + PermissionLoader)的后台分组下挂载上传接口。
|
||||
func RegisterAdminUpload(g *gin.RouterGroup, h *Handler) {
|
||||
rg := g.Group("/files")
|
||||
{
|
||||
rg.POST("/upload", h.Upload)
|
||||
rg.GET("/:id", h.Info)
|
||||
rg.GET("/:id/raw", h.Raw)
|
||||
rg.DELETE("/:id", h.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterMiniAppUpload 在已鉴权(MiniAppAuth + MiniUserAuth)的小程序分组下挂载上传接口。
|
||||
func RegisterMiniAppUpload(g *gin.RouterGroup, h *Handler) {
|
||||
rg := g.Group("/files")
|
||||
{
|
||||
rg.POST("/upload", h.Upload)
|
||||
rg.GET("/:id", h.Info)
|
||||
rg.GET("/:id/raw", h.Raw)
|
||||
rg.DELETE("/:id", h.Delete)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user