mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: 新增统一文件模块,统一文件操作
This commit is contained in:
169
server/internal/file/application/file.go
Normal file
169
server/internal/file/application/file.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"mayfly-go/internal/file/config"
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type File interface {
|
||||
base.App[*entity.File]
|
||||
|
||||
// Upload 上传文件
|
||||
//
|
||||
// @param fileKey 文件key,若存在则使用存在的文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error)
|
||||
|
||||
// NewWriter 创建文件writer
|
||||
//
|
||||
// @param canEmptyFileKey 文件key,若不为空则使用该文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
//
|
||||
// @return writer 文件writer
|
||||
//
|
||||
// @return saveFunc 保存文件信息的回调函数 (必须要defer中调用才会入库保存该文件信息)
|
||||
NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error)
|
||||
|
||||
// GetReader 获取文件reader
|
||||
//
|
||||
// @return filename 文件名
|
||||
//
|
||||
// @return reader 文件reader
|
||||
//
|
||||
// @return err 错误
|
||||
GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error)
|
||||
|
||||
// Remove 删除文件
|
||||
Remove(ctx context.Context, fileKey string) error
|
||||
}
|
||||
|
||||
type fileAppImpl struct {
|
||||
base.AppImpl[*entity.File, repository.File]
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) InjectFileRepo(repo repository.File) {
|
||||
f.Repo = repo
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error) {
|
||||
fileKey, writer, saveFileFunc, err := f.NewWriter(ctx, fileKey, filename)
|
||||
if err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
defer saveFileFunc()
|
||||
|
||||
if _, err := io.Copy(writer, r); err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
return fileKey, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error) {
|
||||
isNewFile := true
|
||||
file := &entity.File{}
|
||||
|
||||
if canEmptyFileKey == "" {
|
||||
canEmptyFileKey = stringx.RandUUID()
|
||||
file.FileKey = canEmptyFileKey
|
||||
} else {
|
||||
file.FileKey = canEmptyFileKey
|
||||
if err := f.GetByCond(file); err == nil {
|
||||
isNewFile = false
|
||||
}
|
||||
}
|
||||
file.Filename = filename
|
||||
|
||||
if !isNewFile {
|
||||
// 先删除旧文件
|
||||
f.remove(ctx, file)
|
||||
}
|
||||
|
||||
// 生产新的文件名
|
||||
newFilename := canEmptyFileKey + filepath.Ext(filename)
|
||||
filepath, w, err := f.newWriter(newFilename)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
file.Path = filepath
|
||||
|
||||
fileKey = canEmptyFileKey
|
||||
writer = writerx.NewCountingWriteCloser(w)
|
||||
// 创建回调函数
|
||||
saveFunc = func() error {
|
||||
// 获取已写入的字节数
|
||||
file.Size = writer.BytesWritten()
|
||||
writer.Close()
|
||||
// 保存文件信息
|
||||
return f.Save(ctx, file)
|
||||
}
|
||||
|
||||
return fileKey, writer, saveFunc, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error) {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return "", nil, errorx.NewBiz("文件不存在")
|
||||
}
|
||||
r, err := os.Open(filepath.Join(config.GetFileConfig().BasePath, file.Path))
|
||||
return file.Filename, r, err
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Remove(ctx context.Context, fileKey string) error {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return errorx.NewBiz("文件不存在")
|
||||
}
|
||||
f.DeleteById(ctx, file.Id)
|
||||
return f.remove(ctx, file)
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) newWriter(filename string) (string, io.WriteCloser, error) {
|
||||
now := time.Now()
|
||||
filePath := filepath.Join(cast.ToString(now.Year()), cast.ToString(int(now.Month())), cast.ToString(now.Day()), cast.ToString(now.Hour()), filename)
|
||||
fileAbsPath := filepath.Join(config.GetFileConfig().BasePath, filePath)
|
||||
|
||||
// 目录不存在则创建
|
||||
fileDir := filepath.Dir(fileAbsPath)
|
||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(fileDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
out, err := os.OpenFile(fileAbsPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return filePath, out, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) remove(ctx context.Context, file *entity.File) error {
|
||||
if err := os.Remove(filepath.Join(config.GetFileConfig().BasePath, file.Path)); err != nil {
|
||||
logx.ErrorfContext(ctx, "删除旧文件[%s] 失败: %s", file.Path, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user