/
storer.go
237 lines (204 loc) · 5.01 KB
/
storer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package driver
import (
"context"
"io"
"io/ioutil"
"mime/multipart"
"net/url"
"os"
"path"
"sort"
uploadClient "github.com/webx-top/client/upload"
"github.com/webx-top/client/upload/watermark"
"github.com/webx-top/echo"
"github.com/admpub/checksum"
"github.com/admpub/color"
"github.com/admpub/log"
"github.com/admpub/nging/application/model/file/storer"
"github.com/admpub/nging/application/registry/upload/table"
"github.com/webx-top/image"
)
var (
// ErrExistsFile 文件不存在
ErrExistsFile = table.ErrExistsFile
)
// BatchUpload 批量上传
func BatchUpload(
ctx echo.Context,
fieldName string,
dstNamer func(*uploadClient.Result) (dst string, err error),
storer Storer,
callback func(*uploadClient.Result, multipart.File) error,
watermarkOptions *image.WatermarkOptions,
) (results uploadClient.Results, err error) {
req := ctx.Request()
if req == nil {
err = ctx.E(`Invalid upload content`)
return
}
m := req.MultipartForm()
if m == nil || m.File == nil {
err = ctx.E(`Invalid upload content`)
return
}
files, ok := m.File[fieldName]
if !ok {
err = echo.ErrNotFoundFileInput
return
}
var dstFile string
for _, fileHdr := range files {
//for each fileheader, get a handle to the actual file
var file multipart.File
file, err = fileHdr.Open()
if err != nil {
if file != nil {
file.Close()
}
return
}
result := &uploadClient.Result{
FileName: fileHdr.Filename,
FileSize: fileHdr.Size,
}
result.Md5, err = checksum.MD5sumReader(file)
if err != nil {
file.Close()
return
}
dstFile, err = dstNamer(result)
if err != nil {
file.Close()
if err == ErrExistsFile {
results.Add(result)
err = nil
continue
}
return
}
if len(dstFile) == 0 {
file.Close()
continue
}
if len(result.SavePath) > 0 {
file.Close()
results.Add(result)
continue
}
file.Seek(0, 0)
if result.FileType.String() == `image` && watermarkOptions != nil && watermarkOptions.IsEnabled() {
var b []byte
b, err = ioutil.ReadAll(file)
if err != nil {
file.Close()
return
}
b, err = watermark.Bytes(b, path.Ext(result.FileName), watermarkOptions)
if err != nil {
file.Close()
return
}
file = watermark.Bytes2file(b)
result.SavePath, result.FileURL, err = storer.Put(dstFile, file, int64(len(b)))
} else {
result.SavePath, result.FileURL, err = storer.Put(dstFile, file, fileHdr.Size)
}
if err != nil {
file.Close()
return
}
file.Seek(0, 0)
if err = callback(result, file); err != nil {
file.Close()
return
}
file.Close()
results.Add(result)
}
return
}
// Sizer 尺寸接口
type Sizer interface {
Size() int64
}
// Storer 文件存储引擎接口
type Storer interface {
// 引擎名
Name() string
ErrIsNotExist(err error) bool
// FileDir 文件夹物理路径
FileDir(subpath string) string
// URLDir 文件夹网址路径
URLDir(subpath string) string
// Put 保存文件
Put(dst string, src io.Reader, size int64) (savePath string, viewURL string, err error)
// Get 获取文件
Get(file string) (io.ReadCloser, error)
// Exists 文件是否存在
Exists(file string) (bool, error)
// FileInfo 文件信息
FileInfo(file string) (os.FileInfo, error)
// SendFile 输出文件到浏览器
SendFile(ctx echo.Context, file string) error
// Delete 删除文件
Delete(file string) error
// DeleteDir 删除目录
DeleteDir(dir string) error
// Move 移动文件
Move(src, dst string) error
// PublicURL 文件物理路径转网址
PublicURL(dst string) string
// URLToFile 网址转文件存储路径(非完整路径)
URLToFile(viewURL string) string
// URLToPath 网址转文件路径(完整路径)
URLToPath(viewURL string) string
// 根网址(末尾不含"/")
SetBaseURL(baseURL string)
BaseURL() string
// FixURL 修正网址
FixURL(content string, embedded ...bool) string
// FixURLWithParams 修正网址并增加网址参数
FixURLWithParams(content string, values url.Values, embedded ...bool) string
// Close 关闭连接
Close() error
}
// Constructor 存储引擎构造函数
type Constructor func(ctx context.Context, typ string) (Storer, error)
var storers = map[string]Constructor{}
// DefaultConstructor 默认构造器
var DefaultConstructor Constructor
// Register 存储引擎注册
func Register(engine string, constructor Constructor) {
log.Info(color.CyanString(`storer.register:`), engine)
storers[engine] = constructor
}
// Get 获取存储引擎构造器
func Get(engine string) Constructor {
constructor, ok := storers[engine]
if !ok {
return DefaultConstructor
}
return constructor
}
// GetBySettings 获取存储引擎构造器
func GetBySettings() Constructor {
engine := `local`
storerConfig, ok := storer.GetOk()
if ok {
engine = storerConfig.Name
}
return Get(engine)
}
// All 存储引擎集合
func All() map[string]Constructor {
return storers
}
// AllNames 存储引擎集合
func AllNames() []string {
var names []string
for name := range storers {
names = append(names, name)
}
sort.Strings(names)
return names
}