353 lines
9.4 KiB
Go
353 lines
9.4 KiB
Go
package file
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"mime/multipart"
|
||
"os"
|
||
"path"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"ems.agt/src/framework/config"
|
||
"ems.agt/src/framework/constants/uploadsubpath"
|
||
"ems.agt/src/framework/logger"
|
||
"ems.agt/src/framework/utils/date"
|
||
"ems.agt/src/framework/utils/generate"
|
||
"ems.agt/src/framework/utils/parse"
|
||
"ems.agt/src/framework/utils/regular"
|
||
)
|
||
|
||
/**最大文件名长度 */
|
||
const DEFAULT_FILE_NAME_LENGTH = 100
|
||
|
||
// 文件上传路径 prefix, dir
|
||
func resourceUpload() (string, string) {
|
||
upload := config.Get("staticFile.upload").(map[string]any)
|
||
dir, err := filepath.Abs(upload["dir"].(string))
|
||
if err != nil {
|
||
logger.Errorf("file resourceUpload err %v", err)
|
||
}
|
||
return upload["prefix"].(string), dir
|
||
}
|
||
|
||
// 最大上传文件大小
|
||
func uploadFileSize() int64 {
|
||
fileSize := 1 * 1024 * 1024
|
||
size := config.Get("upload.fileSize").(int)
|
||
if size > 1 {
|
||
fileSize = size * 1024 * 1024
|
||
}
|
||
return int64(fileSize)
|
||
}
|
||
|
||
// 文件上传扩展名白名单
|
||
func uploadWhiteList() []string {
|
||
arr := config.Get("upload.whitelist").([]any)
|
||
strings := make([]string, len(arr))
|
||
for i, val := range arr {
|
||
if str, ok := val.(string); ok {
|
||
strings[i] = str
|
||
}
|
||
}
|
||
return strings
|
||
}
|
||
|
||
// 生成文件名称 fileName_随机值.extName
|
||
//
|
||
// fileName 原始文件名称含后缀,如:logo.png
|
||
func generateFileName(fileName string) string {
|
||
fileExt := filepath.Ext(fileName)
|
||
// 替换掉后缀和特殊字符保留文件名
|
||
newFileName := regular.Replace(fileName, fileExt, "")
|
||
newFileName = regular.Replace(newFileName, `[<>:"\\|?*]+`, "")
|
||
newFileName = strings.TrimSpace(newFileName)
|
||
return fmt.Sprintf("%s_%s%s", newFileName, generate.Code(6), fileExt)
|
||
}
|
||
|
||
// 检查文件允许写入本地
|
||
//
|
||
// fileName 原始文件名称含后缀,如:midway1_logo_iipc68.png
|
||
//
|
||
// allowExts 允许上传拓展类型,['.png']
|
||
func isAllowWrite(fileName string, allowExts []string, fileSize int64) error {
|
||
// 判断上传文件名称长度
|
||
if len(fileName) > DEFAULT_FILE_NAME_LENGTH {
|
||
return fmt.Errorf("the maximum length limit for uploading file names is %d", DEFAULT_FILE_NAME_LENGTH)
|
||
}
|
||
|
||
// 最大上传文件大小
|
||
maxFileSize := uploadFileSize()
|
||
if fileSize > maxFileSize {
|
||
return fmt.Errorf("maximum upload file size %s", parse.Bit(float64(maxFileSize)))
|
||
}
|
||
|
||
// 判断文件拓展是否为允许的拓展类型
|
||
fileExt := filepath.Ext(fileName)
|
||
hasExt := false
|
||
if len(allowExts) == 0 {
|
||
allowExts = uploadWhiteList()
|
||
}
|
||
for _, ext := range allowExts {
|
||
if ext == fileExt {
|
||
hasExt = true
|
||
break
|
||
}
|
||
}
|
||
if !hasExt {
|
||
return fmt.Errorf("the upload file type is not supported, only the following types are supported: %s", strings.Join(allowExts, ","))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 检查文件允许本地读取
|
||
//
|
||
// filePath 文件存放资源路径,URL相对地址
|
||
func isAllowRead(filePath string) error {
|
||
// 禁止目录上跳级别
|
||
if strings.Contains(filePath, "..") {
|
||
return fmt.Errorf("prohibit jumping levels on the directory")
|
||
}
|
||
|
||
// 检查允许下载的文件规则
|
||
fileExt := filepath.Ext(filePath)
|
||
hasExt := false
|
||
for _, ext := range uploadWhiteList() {
|
||
if ext == fileExt {
|
||
hasExt = true
|
||
break
|
||
}
|
||
}
|
||
if !hasExt {
|
||
return fmt.Errorf("rules for illegally downloaded files: %s", fileExt)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// TransferUploadFile 上传资源文件转存
|
||
//
|
||
// subPath 子路径,默认 UploadSubPath.DEFAULT
|
||
//
|
||
// allowExts 允许上传拓展类型(含“.”),如 ['.png','.jpg']
|
||
func TransferUploadFile(file *multipart.FileHeader, subPath string, allowExts []string) (string, error) {
|
||
// 上传文件检查
|
||
err := isAllowWrite(file.Filename, allowExts, file.Size)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// 上传资源路径
|
||
prefix, dir := resourceUpload()
|
||
// 新文件名称并组装文件地址
|
||
fileName := generateFileName(file.Filename)
|
||
filePath := filepath.Join(subPath, date.ParseDatePath(time.Now()))
|
||
writePathFile := filepath.Join(dir, filePath, fileName)
|
||
// 存入新文件路径
|
||
err = transferToNewFile(file, writePathFile)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
urlPath := filepath.Join(prefix, filePath, fileName)
|
||
return filepath.ToSlash(urlPath), nil
|
||
}
|
||
|
||
// ReadUploadFileStream 上传资源文件读取
|
||
//
|
||
// filePath 文件存放资源路径,URL相对地址 如:/upload/common/2023/06/xxx.png
|
||
//
|
||
// headerRange 断点续传范围区间,bytes=0-12131
|
||
func ReadUploadFileStream(filePath, headerRange string) (map[string]any, error) {
|
||
// 读取文件检查
|
||
err := isAllowRead(filePath)
|
||
if err != nil {
|
||
return map[string]any{}, err
|
||
}
|
||
// 上传资源路径
|
||
prefix, dir := resourceUpload()
|
||
fileAsbPath := strings.Replace(filePath, prefix, dir, 1)
|
||
|
||
// 响应结果
|
||
result := map[string]any{
|
||
"range": "",
|
||
"chunkSize": 0,
|
||
"fileSize": 0,
|
||
"data": nil,
|
||
}
|
||
|
||
// 文件大小
|
||
fileSize := getFileSize(fileAsbPath)
|
||
if fileSize <= 0 {
|
||
return result, nil
|
||
}
|
||
result["fileSize"] = fileSize
|
||
|
||
if headerRange != "" {
|
||
partsStr := strings.Replace(headerRange, "bytes=", "", 1)
|
||
parts := strings.Split(partsStr, "-")
|
||
start, err := strconv.ParseInt(parts[0], 10, 64)
|
||
if err != nil || start > fileSize {
|
||
start = 0
|
||
}
|
||
end, err := strconv.ParseInt(parts[1], 10, 64)
|
||
if err != nil || end > fileSize {
|
||
end = fileSize - 1
|
||
}
|
||
if start > end {
|
||
start = end
|
||
}
|
||
|
||
// 分片结果
|
||
result["range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize)
|
||
result["chunkSize"] = end - start + 1
|
||
byteArr, err := getFileStream(fileAsbPath, start, end)
|
||
if err != nil {
|
||
return map[string]any{}, fmt.Errorf("fail to read file")
|
||
}
|
||
result["data"] = byteArr
|
||
return result, nil
|
||
}
|
||
|
||
byteArr, err := getFileStream(fileAsbPath, 0, fileSize)
|
||
if err != nil {
|
||
return map[string]any{}, fmt.Errorf("fail to read file")
|
||
}
|
||
result["data"] = byteArr
|
||
return result, nil
|
||
}
|
||
|
||
// TransferChunkUploadFile 上传资源切片文件转存
|
||
//
|
||
// file 上传文件对象
|
||
//
|
||
// index 切片文件序号
|
||
//
|
||
// identifier 切片文件目录标识符
|
||
func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier string) (string, error) {
|
||
// 上传文件检查
|
||
err := isAllowWrite(file.Filename, []string{}, file.Size)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// 上传资源路径
|
||
prefix, dir := resourceUpload()
|
||
// 新文件名称并组装文件地址
|
||
filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||
writePathFile := filepath.Join(dir, filePath, index)
|
||
// 存入新文件路径
|
||
err = transferToNewFile(file, writePathFile)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
urlPath := filepath.Join(prefix, filePath, index)
|
||
return filepath.ToSlash(urlPath), nil
|
||
}
|
||
|
||
// 上传资源切片文件检查
|
||
//
|
||
// identifier 切片文件目录标识符
|
||
//
|
||
// originalFileName 原始文件名称,如logo.png
|
||
func ChunkCheckFile(identifier, originalFileName string) ([]string, error) {
|
||
// 读取文件检查
|
||
err := isAllowWrite(originalFileName, []string{}, 0)
|
||
if err != nil {
|
||
return []string{}, err
|
||
}
|
||
// 上传资源路径
|
||
_, dir := resourceUpload()
|
||
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||
readPath := path.Join(dir, dirPath)
|
||
fileList, err := getDirFileNameList(readPath)
|
||
if err != nil {
|
||
return []string{}, fmt.Errorf("fail to read file")
|
||
}
|
||
return fileList, nil
|
||
}
|
||
|
||
// 上传资源切片文件检查
|
||
//
|
||
// identifier 切片文件目录标识符
|
||
//
|
||
// originalFileName 原始文件名称,如logo.png
|
||
//
|
||
// subPath 子路径,默认 DEFAULT
|
||
func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error) {
|
||
// 读取文件检查
|
||
err := isAllowWrite(originalFileName, []string{}, 0)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// 上传资源路径
|
||
prefix, dir := resourceUpload()
|
||
// 切片存放目录
|
||
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||
readPath := path.Join(dir, dirPath)
|
||
// 组合存放文件路径
|
||
fileName := generateFileName(originalFileName)
|
||
filePath := path.Join(subPath, date.ParseDatePath(time.Now()))
|
||
writePath := path.Join(dir, filePath)
|
||
err = mergeToNewFile(readPath, writePath, fileName)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
urlPath := filepath.Join(prefix, filePath, fileName)
|
||
return filepath.ToSlash(urlPath), nil
|
||
}
|
||
|
||
// CopyUploadFile 将上传文件资源转移新目录
|
||
//
|
||
// filePath 上传得到的文件路径 /upload....
|
||
// dst 新文件路径 /a/xx.pdf
|
||
func CopyUploadFile(filePath, dst string) error {
|
||
srcPath := ParseUploadFilePath(filePath)
|
||
src, err := os.Open(srcPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer src.Close()
|
||
|
||
if err := os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 如果目标文件已经存在,先将目标文件重命名
|
||
if _, err := os.Stat(dst); err == nil {
|
||
ext := filepath.Ext(dst)
|
||
name := dst[0 : len(dst)-len(ext)]
|
||
newName := fmt.Sprintf("%s-%s%s", name, time.Now().Format("20060102_150405"), ext)
|
||
err := os.Rename(dst, newName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
out, err := os.Create(dst)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer out.Close()
|
||
|
||
_, err = io.Copy(out, src)
|
||
return err
|
||
}
|
||
|
||
// ParseUploadFileDir 得到上传资源目录
|
||
//
|
||
// subPath 子路径,默认 UploadSubPath.DEFAULT
|
||
func ParseUploadFileDir(subPath string) string {
|
||
_, dir := resourceUpload()
|
||
filePath := filepath.Join(subPath, date.ParseDatePath(time.Now()))
|
||
return filepath.Join(dir, filePath)
|
||
}
|
||
|
||
// ParseUploadFilePath 上传资源本地绝对资源路径
|
||
//
|
||
// filePath 上传文件路径
|
||
func ParseUploadFilePath(filePath string) string {
|
||
prefix, dir := resourceUpload()
|
||
return strings.Replace(filePath, prefix, dir, 1)
|
||
}
|