mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-10-31 00:27:31 +08:00 
			
		
		
		
	Feat: {path} marker in name rule representing the virtual path of the file
				
					
				
			This commit is contained in:
		| @ -4,6 +4,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/HFO4/cloudreve/pkg/util" | ||||
| 	"github.com/jinzhu/gorm" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| ) | ||||
| @ -69,7 +70,7 @@ func (policy *Policy) SerializeOptions() (err error) { | ||||
| } | ||||
|  | ||||
| // GeneratePath 生成存储文件的路径 | ||||
| func (policy *Policy) GeneratePath(uid uint) string { | ||||
| func (policy *Policy) GeneratePath(uid uint, path string) string { | ||||
| 	dirRule := policy.DirNameRule | ||||
| 	replaceTable := map[string]string{ | ||||
| 		"{randomkey16}": util.RandStringRunes(16), | ||||
| @ -78,9 +79,10 @@ func (policy *Policy) GeneratePath(uid uint) string { | ||||
| 		"{uid}":         strconv.Itoa(int(uid)), | ||||
| 		"{datetime}":    time.Now().Format("20060102150405"), | ||||
| 		"{date}":        time.Now().Format("20060102"), | ||||
| 		"{path}":        path + "/", | ||||
| 	} | ||||
| 	dirRule = util.Replace(replaceTable, dirRule) | ||||
| 	return dirRule | ||||
| 	return filepath.Clean(dirRule) | ||||
| } | ||||
|  | ||||
| // GenerateFileName 生成存储文件名 | ||||
|  | ||||
| @ -46,25 +46,32 @@ func TestPolicy_GeneratePath(t *testing.T) { | ||||
| 	testPolicy := Policy{} | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{randomkey16}" | ||||
| 	asserts.Len(testPolicy.GeneratePath(1), 16) | ||||
| 	asserts.Len(testPolicy.GeneratePath(1, "/"), 16) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{randomkey8}" | ||||
| 	asserts.Len(testPolicy.GeneratePath(1), 8) | ||||
| 	asserts.Len(testPolicy.GeneratePath(1, "/"), 8) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{timestamp}" | ||||
| 	asserts.Equal(testPolicy.GeneratePath(1), strconv.FormatInt(time.Now().Unix(), 10)) | ||||
| 	asserts.Equal(testPolicy.GeneratePath(1, "/"), strconv.FormatInt(time.Now().Unix(), 10)) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{uid}" | ||||
| 	asserts.Equal(testPolicy.GeneratePath(1), strconv.Itoa(int(1))) | ||||
| 	asserts.Equal(testPolicy.GeneratePath(1, "/"), strconv.Itoa(int(1))) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{datetime}" | ||||
| 	asserts.Len(testPolicy.GeneratePath(1), 14) | ||||
| 	asserts.Len(testPolicy.GeneratePath(1, "/"), 14) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "{date}" | ||||
| 	asserts.Len(testPolicy.GeneratePath(1), 8) | ||||
| 	asserts.Len(testPolicy.GeneratePath(1, "/"), 8) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "123{date}ss{datetime}" | ||||
| 	asserts.Len(testPolicy.GeneratePath(1), 27) | ||||
| 	asserts.Len(testPolicy.GeneratePath(1, "/"), 27) | ||||
|  | ||||
| 	testPolicy.DirNameRule = "/1/{path}/456" | ||||
| 	asserts.Condition(func() (success bool) { | ||||
| 		res := testPolicy.GeneratePath(1, "/23") | ||||
| 		return res == "/1/23/456" || res == "\\1\\23\\456" | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestPolicy_GenerateFileName(t *testing.T) { | ||||
|  | ||||
| @ -10,13 +10,14 @@ import ( | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| // FileData 上传来的文件数据处理器 | ||||
| type FileData interface { | ||||
| // FileHeader 上传来的文件数据处理器 | ||||
| type FileHeader interface { | ||||
| 	io.Reader | ||||
| 	io.Closer | ||||
| 	GetSize() uint64 | ||||
| 	GetMIMEType() string | ||||
| 	GetFileName() string | ||||
| 	GetVirtualPath() string | ||||
| } | ||||
|  | ||||
| // Handler 存储策略适配器 | ||||
| @ -77,7 +78,7 @@ func NewFileSystem(user *model.User) (*FileSystem, error) { | ||||
| */ | ||||
|  | ||||
| // Upload 上传文件 | ||||
| func (fs *FileSystem) Upload(ctx context.Context, file FileData) (err error) { | ||||
| func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) { | ||||
| 	ctx = context.WithValue(ctx, FileCtx, file) | ||||
|  | ||||
| 	// 上传前的钩子 | ||||
| @ -89,7 +90,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileData) (err error) { | ||||
| 	} | ||||
|  | ||||
| 	// 生成文件名和路径 | ||||
| 	savePath := fs.GenerateSavePath(file) | ||||
| 	savePath := fs.GenerateSavePath(ctx, file) | ||||
|  | ||||
| 	// 处理客户端未完成上传时,关闭连接 | ||||
| 	go fs.CancelUpload(ctx, savePath, file) | ||||
| @ -122,15 +123,21 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileData) (err error) { | ||||
| } | ||||
|  | ||||
| // GenerateSavePath 生成要存放文件的路径 | ||||
| func (fs *FileSystem) GenerateSavePath(file FileData) string { | ||||
| func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) string { | ||||
| 	return filepath.Join( | ||||
| 		fs.User.Policy.GeneratePath(fs.User.Model.ID), | ||||
| 		fs.User.Policy.GenerateFileName(fs.User.Model.ID, file.GetFileName()), | ||||
| 		fs.User.Policy.GeneratePath( | ||||
| 			fs.User.Model.ID, | ||||
| 			file.GetVirtualPath(), | ||||
| 		), | ||||
| 		fs.User.Policy.GenerateFileName( | ||||
| 			fs.User.Model.ID, | ||||
| 			file.GetFileName(), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // CancelUpload 监测客户端取消上传 | ||||
| func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileData) { | ||||
| func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) { | ||||
| 	ginCtx := ctx.Value(GinCtx).(*gin.Context) | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
|  | ||||
| @ -4,12 +4,11 @@ import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"github.com/HFO4/cloudreve/pkg/util" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| // GenericBeforeUpload 通用上传前处理钩子,包含数据库操作 | ||||
| func GenericBeforeUpload(ctx context.Context, fs *FileSystem) error { | ||||
| 	file := ctx.Value(FileCtx).(FileData) | ||||
| 	file := ctx.Value(FileCtx).(FileHeader) | ||||
|  | ||||
| 	// 验证单文件尺寸 | ||||
| 	if !fs.ValidateFileSize(ctx, file.GetSize()) { | ||||
| @ -35,7 +34,7 @@ func GenericBeforeUpload(ctx context.Context, fs *FileSystem) error { | ||||
|  | ||||
| // GenericAfterUploadCanceled 通用上传取消处理钩子,包含数据库操作 | ||||
| func GenericAfterUploadCanceled(ctx context.Context, fs *FileSystem) error { | ||||
| 	file := ctx.Value(FileCtx).(FileData) | ||||
| 	file := ctx.Value(FileCtx).(FileHeader) | ||||
|  | ||||
| 	filePath := ctx.Value(SavePathCtx).(string) | ||||
| 	// 删除临时文件 | ||||
| @ -55,10 +54,8 @@ func GenericAfterUploadCanceled(ctx context.Context, fs *FileSystem) error { | ||||
|  | ||||
| // GenericAfterUpload 文件上传完成后,包含数据库操作 | ||||
| func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { | ||||
| 	// 获取Gin的上下文 | ||||
| 	ginCtx := ctx.Value(GinCtx).(*gin.Context) | ||||
| 	// 文件存放的虚拟路径 | ||||
| 	virtualPath := util.DotPathToStandardPath(ginCtx.GetHeader("X-Path")) | ||||
| 	virtualPath := ctx.Value(FileCtx).(FileHeader).GetVirtualPath() | ||||
|  | ||||
| 	// 检查路径是否存在 | ||||
| 	if !fs.IsPathExist(virtualPath) { | ||||
|  | ||||
| @ -33,11 +33,16 @@ func (file FileData) GetFileName() string { | ||||
| 	return file.Name | ||||
| } | ||||
|  | ||||
| func (file FileData) GetVirtualPath() string { | ||||
| 	return file.Name | ||||
| } | ||||
|  | ||||
| type FileStream struct { | ||||
| 	File     io.ReadCloser | ||||
| 	Size     uint64 | ||||
| 	Name     string | ||||
| 	MIMEType string | ||||
| 	File        io.ReadCloser | ||||
| 	Size        uint64 | ||||
| 	VirtualPath string | ||||
| 	Name        string | ||||
| 	MIMEType    string | ||||
| } | ||||
|  | ||||
| func (file FileStream) Read(p []byte) (n int, err error) { | ||||
| @ -59,3 +64,7 @@ func (file FileStream) Close() error { | ||||
| func (file FileStream) GetFileName() string { | ||||
| 	return file.Name | ||||
| } | ||||
|  | ||||
| func (file FileStream) GetVirtualPath() string { | ||||
| 	return file.VirtualPath | ||||
| } | ||||
|  | ||||
| @ -8,8 +8,8 @@ import ( | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| type Handler struct { | ||||
| } | ||||
| // Handler 本地策略适配器 | ||||
| type Handler struct{} | ||||
|  | ||||
| // Put 将文件流保存到指定目录 | ||||
| func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string) error { | ||||
|  | ||||
							
								
								
									
										14
									
								
								pkg/util/path_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								pkg/util/path_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| package util | ||||
|  | ||||
| import ( | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestDotPathToStandardPath(t *testing.T) { | ||||
| 	asserts := assert.New(t) | ||||
|  | ||||
| 	asserts.Equal("/", DotPathToStandardPath("")) | ||||
| 	asserts.Equal("/目录", DotPathToStandardPath("目录")) | ||||
| 	asserts.Equal("/目录/目录2", DotPathToStandardPath("目录,目录2")) | ||||
| } | ||||
| @ -6,6 +6,7 @@ import ( | ||||
| 	"github.com/HFO4/cloudreve/pkg/filesystem" | ||||
| 	"github.com/HFO4/cloudreve/pkg/filesystem/local" | ||||
| 	"github.com/HFO4/cloudreve/pkg/serializer" | ||||
| 	"github.com/HFO4/cloudreve/pkg/util" | ||||
| 	"github.com/HFO4/cloudreve/service/file" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"strconv" | ||||
| @ -54,10 +55,11 @@ func FileUploadStream(c *gin.Context) { | ||||
| 	} | ||||
|  | ||||
| 	fileData := local.FileStream{ | ||||
| 		MIMEType: c.Request.Header.Get("Content-Type"), | ||||
| 		File:     c.Request.Body, | ||||
| 		Size:     fileSize, | ||||
| 		Name:     c.Request.Header.Get("X-FileName"), | ||||
| 		MIMEType:    c.Request.Header.Get("Content-Type"), | ||||
| 		File:        c.Request.Body, | ||||
| 		Size:        fileSize, | ||||
| 		Name:        c.Request.Header.Get("X-FileName"), | ||||
| 		VirtualPath: util.DotPathToStandardPath(c.Request.Header.Get("X-Path")), | ||||
| 	} | ||||
| 	user, _ := c.Get("user") | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 HFO4
					HFO4