mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 16:30:25 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						||
}
 |