Files
2025-06-27 12:17:45 +08:00

176 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/spf13/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(*error) 保存文件信息的回调函数 (必须要defer中调用才会入库保存该文件信息),若*error不为nil则表示业务逻辑处理失败不需要保存文件信息并将创建的文件删除
NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func(*error) 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) Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error) {
var err error
fileKey, writer, saveFileFunc, err := f.NewWriter(ctx, fileKey, filename)
if err != nil {
return fileKey, err
}
defer saveFileFunc(&err)
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) 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(e *error) error {
if e != nil {
err := *e
if err != nil {
logx.Errorf("the write file business logic failed: %s", err.Error())
// 删除已经创建的文件
f.remove(ctx, file)
return err
}
}
// 获取已写入的字节数
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("file not found")
}
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("file not found")
}
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, "failed to delete old file [%s]: %s", file.Path, err.Error())
return err
}
return nil
}