195 lines
5.2 KiB
Go
195 lines
5.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"filestoragesystem/internal/middleware"
|
|
"filestoragesystem/internal/repository"
|
|
"filestoragesystem/internal/utils"
|
|
)
|
|
|
|
// FileController 文件控制器
|
|
type FileController struct {
|
|
*Base
|
|
}
|
|
|
|
// NewFileController 创建文件控制器
|
|
func NewFileController(b *Base) *FileController { return &FileController{Base: b} }
|
|
|
|
// Upload 上传文件
|
|
func (ctl *FileController) Upload(c *gin.Context) {
|
|
fh, err := c.FormFile("file")
|
|
if err != nil {
|
|
utils.FailMsg(c, "缺少上传文件字段file")
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
f, err := ctl.svc.File.Upload(c, userID, fh)
|
|
if err != nil {
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "upload", "file", 0, fh.Filename, false, err.Error())
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "upload", "file", f.ID, f.Filename, true, "")
|
|
utils.OK(c, f)
|
|
}
|
|
|
|
// List 文件列表(支持project_id/filename/prefix/trash过滤)
|
|
func (ctl *FileController) List(c *gin.Context) {
|
|
userID := middleware.CurrentUserID(c)
|
|
page, pageSize := utils.ParsePage(c)
|
|
filter := repository.FileListFilter{
|
|
ProjectID: parseUintQuery(c, "project_id"),
|
|
Filename: c.Query("filename"),
|
|
Prefix: utils.SanitizePath(c.Query("prefix")),
|
|
Trashed: c.Query("trash") == "true",
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}
|
|
if v := parseUintQuery(c, "visibility"); v > 0 {
|
|
filter.Visibility = int8(v)
|
|
}
|
|
files, total, err := ctl.svc.File.List(userID, middleware.IsAdmin(c), filter)
|
|
if err != nil {
|
|
utils.ServerError(c, err.Error())
|
|
return
|
|
}
|
|
utils.OKPage(c, files, total, page, pageSize)
|
|
}
|
|
|
|
// GetInfo 获取文件信息
|
|
func (ctl *FileController) GetInfo(c *gin.Context) {
|
|
id, ok := paramID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
f, err := ctl.svc.File.GetInfo(userID, id, middleware.IsAdmin(c))
|
|
if err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
utils.OK(c, f)
|
|
}
|
|
|
|
// Download 下载文件
|
|
func (ctl *FileController) Download(c *gin.Context) {
|
|
id, ok := paramID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
f, fp, err := ctl.svc.File.Open(c, userID, id, middleware.IsAdmin(c))
|
|
if err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
defer fp.Close()
|
|
|
|
ctl.svc.File.RecordDownload(c, f)
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "download", "file", f.ID, f.Filename, true, "")
|
|
|
|
c.Header("Content-Disposition",
|
|
fmt.Sprintf(`attachment; filename*=UTF-8''%s`, url.PathEscape(filepath.Base(f.Filename))))
|
|
c.Header("Content-Type", f.MimeType)
|
|
http.ServeContent(c.Writer, c.Request, filepath.Base(f.Filename), f.UpdatedAt, fp)
|
|
}
|
|
|
|
// Delete 删除文件(permanent=true硬删除,否则进入回收站)
|
|
func (ctl *FileController) Delete(c *gin.Context) {
|
|
id, ok := paramID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
permanent := c.Query("permanent") == "true"
|
|
f, _ := ctl.svc.File.GetInfo(userID, id, middleware.IsAdmin(c))
|
|
if err := ctl.svc.File.Delete(userID, id, middleware.IsAdmin(c), permanent); err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
name := ""
|
|
if f != nil {
|
|
name = f.Filename
|
|
}
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "delete", "file", id, name, true, "")
|
|
if permanent {
|
|
utils.OKMsg(c, "文件已彻底删除")
|
|
} else {
|
|
utils.OKMsg(c, "文件已移入回收站")
|
|
}
|
|
}
|
|
|
|
// Restore 恢复回收站文件
|
|
func (ctl *FileController) Restore(c *gin.Context) {
|
|
id, ok := paramID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
if err := ctl.svc.File.Restore(userID, id, middleware.IsAdmin(c)); err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "update", "file", id, "restore", true, "")
|
|
utils.OKMsg(c, "文件已恢复")
|
|
}
|
|
|
|
type tempLinkReq struct {
|
|
Password string `json:"password"`
|
|
MaxCount int `json:"max_count"`
|
|
ExpiresIn int64 `json:"expires_in"` // 秒,0使用默认
|
|
}
|
|
|
|
// CreateTempLink 生成临时访问链接
|
|
func (ctl *FileController) CreateTempLink(c *gin.Context) {
|
|
id, ok := paramID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req tempLinkReq
|
|
if !bindJSON(c, &req) {
|
|
return
|
|
}
|
|
userID := middleware.CurrentUserID(c)
|
|
link, err := ctl.svc.File.CreateTempLink(userID, id, middleware.IsAdmin(c), req.Password, req.MaxCount, req.ExpiresIn)
|
|
if err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
ctl.svc.RecordOpLog(ctl.opCtx(c), "create", "file", id, "temp_link", true, "")
|
|
utils.OK(c, gin.H{
|
|
"token": link.Token,
|
|
"url": "/api/v1/files/temp/" + link.Token,
|
|
"expires_at": link.ExpiresAt,
|
|
"has_password": link.HasPwd,
|
|
"max_count": link.MaxCount,
|
|
})
|
|
}
|
|
|
|
// TempAccess 通过临时链接访问文件(公开接口,支持password参数)
|
|
func (ctl *FileController) TempAccess(c *gin.Context) {
|
|
token := c.Param("token")
|
|
password := c.Query("password")
|
|
if password == "" {
|
|
password = c.GetHeader("X-Link-Password")
|
|
}
|
|
f, fp, err := ctl.svc.File.ResolveTempLink(token, password)
|
|
if err != nil {
|
|
utils.FailMsg(c, err.Error())
|
|
return
|
|
}
|
|
defer fp.Close()
|
|
|
|
ctl.svc.File.RecordDownload(c, f)
|
|
c.Header("Content-Disposition",
|
|
fmt.Sprintf(`inline; filename*=UTF-8''%s`, url.PathEscape(filepath.Base(f.Filename))))
|
|
c.Header("Content-Type", f.MimeType)
|
|
http.ServeContent(c.Writer, c.Request, filepath.Base(f.Filename), f.UpdatedAt, fp)
|
|
}
|