Skip to content

Commit 394b32f

Browse files
committed
refactor!: 将 format 功能并入到 filename 参数中
1 parent 001e9b0 commit 394b32f

5 files changed

Lines changed: 42 additions & 84 deletions

File tree

.github/workflows/go.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ jobs:
1313

1414
steps:
1515
- name: Check out code into the Go module directory
16-
uses: actions/checkout@v4
16+
uses: actions/checkout@v6
1717

1818
- name: Set up Go ${{ matrix.go }}
19-
uses: actions/setup-go@v5
19+
uses: actions/setup-go@v6
2020
with:
2121
go-version: ${{ matrix.go }}
2222
id: go

example/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func post(_ http.ResponseWriter, r *http.Request) {
4848
log.Panic(err)
4949
}
5050

51-
s, err := upload.NewLocalSaver(root, "", upload.Day, upload.Filename)
51+
s, err := upload.NewLocalSaver(root, "", upload.FilenameAI)
5252
if err != nil {
5353
log.Panic(err)
5454
}

save.go

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"strconv"
1515
"strings"
1616
"sync"
17-
"time"
1817
)
1918

2019
const presetMode = fs.ModePerm
@@ -41,20 +40,11 @@ type Deleter interface {
4140
Delete(filename string) error
4241
}
4342

44-
// 为 [New] 的参数 format 所允许的几种取值
45-
const (
46-
None = ""
47-
Year = "2006/"
48-
Month = "2006/01/"
49-
Day = "2006/01/02/"
50-
)
51-
5243
type localSaver struct {
53-
root *os.Root
54-
baseURL string
55-
format string
56-
filenames func(fs fs.FS, filename, ext string) string
57-
moveMux sync.Mutex
44+
root *os.Root
45+
baseURL string
46+
filenames func(fs fs.FS, filename, ext string) string
47+
filenameMux sync.Mutex
5848
}
5949

6050
// NewLocalSaver 实现了一个基于本地文件系统的 [Saver] 接口
@@ -63,26 +53,20 @@ type localSaver struct {
6353
//
6454
// baseURL 为上传的文件生成访问地址的前缀;
6555
//
66-
// format 子目录的格式,只能是时间格式,取值只能是 [None]、[Year]、[Month] 和 [Day];
67-
//
6856
// f 设置文件名的生成方式,要求文件在同一目录下具有唯一性,其类型如下:
6957
//
7058
// func(dir fs.FS, filename, ext string) string
7159
//
72-
// dir 为所属的文件系统,filename 为文件名部分,可能包含部分目录名称,ext 为 filename 中的扩展名部分,
60+
// dir 为所属的文件系统,filename 用户上传的文件名,ext 为 filename 中的扩展名部分,
7361
// 返回值是修正后的 filename,实现者需要保证 filename 在 dir 下是唯一的,filename 的路径分隔符必须是 /,不随系统而改变。
74-
// 如果为空,则会采用 [Filename] 作为默认值;
75-
func NewLocalSaver(root *os.Root, baseURL, format string, f func(dir fs.FS, filename, ext string) string) (Deleter, error) {
62+
// 如果为空,则会采用 [FilenameAI] 作为默认值;
63+
func NewLocalSaver(root *os.Root, baseURL string, f func(dir fs.FS, filename, ext string) string) (Deleter, error) {
7664
if root == nil {
7765
panic("无效的参数 root")
7866
}
7967

80-
if format != Year && format != Month && format != Day && format != None {
81-
panic("无效的参数 format")
82-
}
83-
8468
if f == nil {
85-
f = Filename
69+
f = FilenameAI
8670
}
8771

8872
if baseURL != "" && baseURL[len(baseURL)-1] != '/' {
@@ -92,25 +76,21 @@ func NewLocalSaver(root *os.Root, baseURL, format string, f func(dir fs.FS, file
9276
return &localSaver{
9377
root: root,
9478
baseURL: baseURL,
95-
format: format,
9679
filenames: f,
9780
}, nil
9881
}
9982

10083
func (s *localSaver) Open(name string) (fs.File, error) { return s.root.Open(name) }
10184

10285
func (s *localSaver) Save(f multipart.File, filename, ext string) (string, error) {
103-
var relDir string
104-
if s.format != None {
105-
relDir = time.Now().Format(s.format)
106-
}
107-
if relDir != "" {
108-
if err := s.root.MkdirAll(relDir, presetMode); err != nil { // 若路径不存在,则创建
109-
return "", err
110-
}
86+
p := s.createFilename(filename, ext)
87+
88+
dir := path.Dir(p)
89+
if err := s.root.MkdirAll(dir, presetMode); err != nil {
90+
return "", err
11191
}
11292

113-
p, destFile, err := s.createFile(relDir, filename, ext)
93+
destFile, err := s.root.Create(p)
11494
if err != nil {
11595
return "", err
11696
}
@@ -128,23 +108,19 @@ func (s *localSaver) Delete(filename string) error {
128108
return s.root.Remove(filename)
129109
}
130110

131-
// 主要是为了缩小 moveMux 的范围,只要保证在创建文件时是有效的就行。
132-
func (s *localSaver) createFile(relDir string, filename, ext string) (string, *os.File, error) {
133-
s.moveMux.Lock()
134-
defer s.moveMux.Unlock()
135-
136-
p := s.filenames(s.root.FS(), path.Join(relDir, filename), ext)
137-
destFile, err := s.root.Create(p)
138-
if err != nil {
139-
return "", nil, err
140-
}
141-
return p, destFile, nil
111+
// 主要是为了缩小 filenameMux 的范围。
112+
func (s *localSaver) createFilename(filename, ext string) string {
113+
s.filenameMux.Lock()
114+
defer s.filenameMux.Unlock()
115+
return s.filenames(s.root.FS(), filename, ext)
142116
}
143117

144-
// Filename 在 dir 下为 s 生成唯一文件名
118+
// FilenameAI 在 dir 下为 s 生成唯一文件名
119+
//
120+
// 如果已经存在同名的文件,会在文件后以数字形式自增。
145121
//
146122
// s 包含了扩展名的文件名;
147-
func Filename(dir fs.FS, s, ext string) string {
123+
func FilenameAI(dir fs.FS, s, ext string) string {
148124
base := strings.TrimSuffix(s, ext)
149125

150126
count := 1

save_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ var (
1616
_ Deleter = &localSaver{}
1717
)
1818

19-
func TestFilename(t *testing.T) {
19+
func TestFilenameAI(t *testing.T) {
2020
a := assert.New(t, false)
2121

22-
f := Filename(os.DirFS("./testdir/"), "abc", "")
22+
f := FilenameAI(os.DirFS("./testdir/"), "abc", "")
2323
a.Equal(f, "abc")
2424

25-
f = Filename(os.DirFS("./"), "testdir/file.xml", ".xml")
25+
f = FilenameAI(os.DirFS("./"), "testdir/file.xml", ".xml")
2626
a.Equal(f, "testdir/file_1.xml")
2727
}

upload_test.go

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import (
1111
"mime/multipart"
1212
"net/http"
1313
"os"
14-
"path"
1514
"testing"
16-
"time"
1715

1816
"github.com/issue9/assert/v4"
1917
)
@@ -25,7 +23,7 @@ func TestNew(t *testing.T) {
2523
root, err := os.OpenRoot("./testdir")
2624
a.NotError(err).NotNil(root)
2725

28-
s, err := NewLocalSaver(root, "", Day, Filename)
26+
s, err := NewLocalSaver(root, "", FilenameAI)
2927
a.NotError(err).NotNil(s)
3028

3129
u := New(s, 10*1024, "gif", ".png", ".GIF")
@@ -40,7 +38,7 @@ func TestUpload_isAllowExt(t *testing.T) {
4038
root, err := os.OpenRoot("./testdir")
4139
a.NotError(err).NotNil(root)
4240

43-
s, err := NewLocalSaver(root, "", Day, Filename)
41+
s, err := NewLocalSaver(root, "", FilenameAI)
4442
a.NotError(err).NotNil(s)
4543

4644
u := New(s, 10*1024, "gif", ".png", ".GIF")
@@ -60,7 +58,7 @@ func TestUpload_Do(t *testing.T) {
6058
root, err := os.OpenRoot("./testdir")
6159
a.NotError(err).NotNil(root)
6260

63-
s, err := NewLocalSaver(root, "https://example.com", Day, Filename)
61+
s, err := NewLocalSaver(root, "https://example.com", FilenameAI)
6462
a.NotError(err).NotNil(s)
6563

6664
u := New(s, 10*1024, "xml")
@@ -72,47 +70,31 @@ func TestUpload_Do(t *testing.T) {
7270
a.NotError(err).NotNil(f)
7371
defer f.Close()
7472

75-
body, ct := formData(a, filename)
73+
// 上传一次
7674

75+
body, ct := formData(a, filename)
7776
r, err := http.NewRequest(http.MethodPost, "/upload", body)
7877
a.NotError(err).NotNil(r)
7978
r.Header.Add("content-type", ct)
8079

8180
paths, err := u.Do("file", r)
8281
a.NotError(err).
8382
Length(paths, 1).
84-
Equal(paths[0], "https://example.com/"+path.Join(time.Now().Format(Day), "file.xml"))
83+
Equal(paths[0], "https://example.com/file.xml")
8584

86-
a.NotError(s.Delete(paths[0]))
87-
}
88-
89-
func TestUpload_Do_None(t *testing.T) {
90-
a := assert.New(t, false)
91-
root, err := os.OpenRoot("./testdir")
92-
a.NotError(err).NotNil(root)
93-
94-
s, err := NewLocalSaver(root, "https://example.com", None, Filename)
95-
a.NotError(err).NotNil(s)
96-
97-
u := New(s, 10*1024, "xml")
98-
a.NotError(err)
85+
// 上传两次
9986

100-
filename := "./testdir/file.xml"
101-
102-
f, err := os.Open(filename)
103-
a.NotError(err).NotNil(f)
104-
defer f.Close()
105-
106-
body, ct := formData(a, filename)
107-
108-
r, err := http.NewRequest(http.MethodPost, "/upload", body)
87+
body, ct = formData(a, filename)
88+
r, err = http.NewRequest(http.MethodPost, "/upload", body)
10989
a.NotError(err).NotNil(r)
11090
r.Header.Add("content-type", ct)
11191

112-
paths, err := u.Do("file", r)
92+
paths, err = u.Do("file", r)
11393
a.NotError(err).
11494
Length(paths, 1).
115-
Equal(paths[0], "https://example.com/"+"file_1.xml") // 已经有 file.xml
95+
Equal(paths[0], "https://example.com/file_1.xml")
96+
97+
a.NotError(s.Delete(paths[0]))
11698
}
11799

118100
func formData(a *assert.Assertion, filename string) (*bytes.Buffer, string) {

0 commit comments

Comments
 (0)