This commit is contained in:
2026-10-27 22:55:36 +08:00
parent 1231251ea4
commit 1f6cd63815
5 changed files with 203 additions and 23 deletions
+27
View File
@@ -71,6 +71,32 @@ func (h *Handler) Info(c *gin.Context) {
response.OK(c, f)
}
// List 分页列表查询文件(后台文件管理栏目使用)。支持 query:keyword(原始/落盘文件名)、
// uploader_type、uploader_id、page、size。
func (h *Handler) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
keyword := c.Query("keyword")
typ := c.Query("uploader_type")
var uid uint64
if v := c.Query("uploader_id"); v != "" {
uid, _ = strconv.ParseUint(v, 10, 64)
}
list, total, err := h.svc.List(c.Request.Context(), filesvc.ListReq{
UploaderType: typ,
UploaderID: uid,
Keyword: keyword,
Page: page,
Size: size,
})
if err != nil {
c.Error(err)
return
}
response.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
}
// Raw 以流形式返回文件内容,按 MIME 设置 Content-Type,供预览/下载。
func (h *Handler) Raw(c *gin.Context) {
id, ok := parseID(c)
@@ -127,6 +153,7 @@ func parseID(c *gin.Context) (uint64, bool) {
func RegisterAdminUpload(g *gin.RouterGroup, h *Handler) {
rg := g.Group("/files")
{
rg.GET("", h.List)
rg.POST("/upload", h.Upload)
rg.GET("/:id", h.Info)
rg.GET("/:id/raw", h.Raw)
+34
View File
@@ -49,3 +49,37 @@ func (r *Repository) Delete(ctx context.Context, id uint64) error {
}
return nil
}
// ListFilter 文件列表过滤条件。
type ListFilter struct {
UploaderType string // 上传者类型(admin/ma),为空表示不限
UploaderID uint64 // 上传者主键,0 表示不限
Keyword string // 模糊匹配 origin_name 或 filename,为空表示不限
Page int // 页码,从 1 开始
Size int // 每页条数
}
// List 分页查询文件,所有条件均为可选。返回列表、总数与错误。
func (r *Repository) List(ctx context.Context, f ListFilter) ([]model.File, int64, error) {
q := r.db.WithContext(ctx).Model(&model.File{})
if f.UploaderType != "" {
q = q.Where("uploader_type = ?", f.UploaderType)
}
if f.UploaderID != 0 {
q = q.Where("uploader_id = ?", f.UploaderID)
}
if f.Keyword != "" {
kw := "%" + f.Keyword + "%"
q = q.Where("origin_name LIKE ? OR filename LIKE ?", kw, kw)
}
var total int64
if err := q.Count(&total).Error; err != nil {
return nil, 0, errors.Wrap(50000, "count files", err)
}
var list []model.File
if err := q.Order("id DESC").Offset((f.Page - 1) * f.Size).Limit(f.Size).Find(&list).Error; err != nil {
return nil, 0, errors.Wrap(50000, "list files", err)
}
return list, total, nil
}
+29
View File
@@ -18,6 +18,7 @@ import (
"portal-mini-go/internal/pkg/errors"
"portal-mini-go/internal/pkg/storage"
"portal-mini-go/internal/pkg/types"
"portal-mini-go/internal/repository/file"
)
// 文件相关业务错误(业务码沿用现有 40xxx/41xxx/50xxx 段)。
@@ -31,6 +32,7 @@ type FileRepository interface {
Create(ctx context.Context, f *model.File) error
GetByID(ctx context.Context, id uint64) (*model.File, error)
Delete(ctx context.Context, id uint64) error
List(ctx context.Context, filter file.ListFilter) ([]model.File, int64, error)
}
// Service 文件上传业务服务。
@@ -114,6 +116,33 @@ func (s *Service) Get(ctx context.Context, id uint64) (*model.File, error) {
return s.repo.GetByID(ctx, id)
}
// ListReq 文件列表查询条件。
type ListReq struct {
UploaderType string // 上传者类型(admin/ma),空表示不限
UploaderID uint64 // 上传者主键,0 表示不限
Keyword string // 模糊匹配 origin_name 或 filename
Page int // 页码,从 1 开始
Size int // 每页条数
}
// List 分页查询文件,缺省第 1 页、每页 20 条。
func (s *Service) List(ctx context.Context, req ListReq) ([]model.File, int64, error) {
page, size := req.Page, req.Size
if page <= 0 {
page = 1
}
if size <= 0 {
size = 20
}
return s.repo.List(ctx, file.ListFilter{
UploaderType: req.UploaderType,
UploaderID: req.UploaderID,
Keyword: req.Keyword,
Page: page,
Size: size,
})
}
// Download 返回文件读取流与元数据;调用方负责关闭流。
func (s *Service) Download(ctx context.Context, id uint64) (io.ReadCloser, *model.File, error) {
f, err := s.repo.GetByID(ctx, id)
+46 -10
View File
@@ -12,16 +12,17 @@ import (
"portal-mini-go/internal/model"
"portal-mini-go/internal/pkg/config"
"portal-mini-go/internal/pkg/storage"
"portal-mini-go/internal/repository/file"
filesvc "portal-mini-go/internal/service/file"
)
// --- 测试替身 ---
type fakeStorage struct {
putErr error
gotKey string
putErr error
gotKey string
gotSize int64
data map[string][]byte
data map[string][]byte
}
func newFakeStorage() *fakeStorage { return &fakeStorage{data: map[string][]byte{}} }
@@ -50,12 +51,16 @@ func (s *fakeStorage) Delete(_ context.Context, key string) error {
func (s *fakeStorage) URL(key string) string { return "/files/static/" + key }
type fakeRepo struct {
created *model.File
byID *model.File
getErr error
createErr error
delErr error
deletedID uint64
created *model.File
byID *model.File
getErr error
createErr error
delErr error
deletedID uint64
listed []model.File
listTotal int64
listErr error
listFilter file.ListFilter
}
func (r *fakeRepo) Create(_ context.Context, f *model.File) error { r.created = f; return r.createErr }
@@ -66,11 +71,18 @@ func (r *fakeRepo) GetByID(_ context.Context, id uint64) (*model.File, error) {
return r.byID, nil
}
func (r *fakeRepo) Delete(_ context.Context, id uint64) error { r.deletedID = id; return r.delErr }
func (r *fakeRepo) List(_ context.Context, f file.ListFilter) ([]model.File, int64, error) {
r.listFilter = f
if r.listErr != nil {
return nil, 0, r.listErr
}
return r.listed, r.listTotal, nil
}
// 编译期接口断言。
var (
_ filesvc.FileRepository = (*fakeRepo)(nil)
_ storage.Storage = (*fakeStorage)(nil)
_ storage.Storage = (*fakeStorage)(nil)
)
func newSvc(cfg config.UploadConfig, store *fakeStorage, repo *fakeRepo) *filesvc.Service {
@@ -192,3 +204,27 @@ func TestDelete(t *testing.T) {
require.Equal(t, uint64(9), repo.deletedID)
require.Empty(t, store.data)
}
func TestList(t *testing.T) {
want := []model.File{{ID: 1, OriginName: "a.png"}, {ID: 2, OriginName: "b.jpg"}}
repo := &fakeRepo{listed: want, listTotal: 2}
svc := newSvc(config.UploadConfig{}, newFakeStorage(), repo)
got, total, err := svc.List(context.Background(), filesvc.ListReq{Keyword: "png", Page: 2, Size: 15})
require.NoError(t, err)
require.Equal(t, int64(2), total)
require.Equal(t, want, got)
require.Equal(t, "png", repo.listFilter.Keyword)
require.Equal(t, 2, repo.listFilter.Page)
require.Equal(t, 15, repo.listFilter.Size)
}
func TestList_Defaults(t *testing.T) {
repo := &fakeRepo{listed: nil, listTotal: 0}
svc := newSvc(config.UploadConfig{}, newFakeStorage(), repo)
_, _, err := svc.List(context.Background(), filesvc.ListReq{})
require.NoError(t, err)
require.Equal(t, 1, repo.listFilter.Page, "缺省页码应为 1")
require.Equal(t, 20, repo.listFilter.Size, "缺省每页应为 20")
}