Files
iotlcm/server/internal/logic/system/sys_oss.go
T
2026-10-29 14:36:49 +08:00

1002 lines
30 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// package system
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package system
import (
"context"
"errors"
"fmt"
"io"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/hash"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/uuid32"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
type sSysOss struct {
}
func NewSysOss() *sSysOss {
return &sSysOss{}
}
func init() {
service.RegisterSysOss(NewSysOss())
}
func (s *sSysOss) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: true,
})
}
return handler.Model(dao.SysOss.Ctx(ctx), option...).
WhereNotIn(dao.SysOss.Columns().OssId, dao.TExportTask.Ctx(ctx).Fields(dao.TExportTask.Columns().OssId).WhereGT(dao.TExportTask.Columns().OssId, 0)).
Where("LOWER(REPLACE(COALESCE(path, ''), CHAR(92), '/')) NOT REGEXP ?", `(^|/)private-export(/|$)|(^|/)user/file/([^/]+/)?export(/|$)`).
Where("LOWER(REPLACE(COALESCE(url, ''), CHAR(92), '/')) NOT REGEXP ?", `(^|/)private-export(/|$)|(^|/)user/file/([^/]+/)?export(/|$)`)
}
func (s *sSysOss) List(ctx context.Context, param *model.SysOssListParam, pageInfo *request.PageInfo) (items []*model.SysOssListModel, total int, err error) {
db := s.Model(ctx)
if param.FileName != "" {
db = db.WhereLike(dao.SysOss.Columns().FileName, "%"+param.FileName+"%")
}
if param.OriginalName != "" {
db = db.WhereLike(dao.SysOss.Columns().OriginalName, "%"+param.OriginalName+"%")
}
if param.FileSuffix != "" {
db = db.Where(dao.SysOss.Columns().FileSuffix, param.FileSuffix)
}
if param.Service != "" {
db = db.Where(dao.SysOss.Columns().Service, param.Service)
}
if len(param.CreatedAt) == 2 {
startTime, err := gtime.StrToTime(param.CreatedAt[0])
if err != nil {
return nil, 0, err
}
endTime, err := gtime.StrToTime(param.CreatedAt[1])
if err != nil {
return nil, 0, err
}
db = db.WhereBetween(dao.SysOss.Columns().CreatedAt, startTime, endTime.EndOfDay())
}
total, err = db.Count()
if err != nil {
return nil, 0, err
}
err = db.Page(pageInfo.Page, pageInfo.PageSize).Order(dao.SysOss.Columns().OssId, "DESC").Scan(&items)
if err != nil {
return nil, 0, err
}
return
}
func (s *sSysOss) View(ctx context.Context, param *model.SysOssViewParam) (output *model.SysOssViewModel, err error) {
if param.OssId == 0 {
return nil, errors.New("未选择文件")
}
db := s.Model(ctx).Where(dao.SysOss.Columns().OssId, param.OssId)
err = db.Scan(&output)
if err != nil {
return nil, err
}
return
}
func (s *sSysOss) Download(ctx context.Context, param *model.SysOssDownloadParam) (output *model.SysOssDownloadModel, err error) {
if param.OssId == 0 {
return nil, errors.New("未选择文件")
}
data := &entity.SysOss{}
db := s.Model(ctx).Where(dao.SysOss.Columns().OssId, param.OssId)
err = db.Scan(&data)
if err != nil {
return nil, err
}
if data.OssId == 0 {
return nil, errors.New("文件不存在")
}
if data.Service != "local" {
err = gerror.New("非本地文件")
return
}
output = &model.SysOssDownloadModel{
SysOss: data,
}
err = s.DownloadLocal(ctx, data)
if err != nil {
return nil, err
}
return
}
func (s *sSysOss) Delete(ctx context.Context, param *model.SysOssDeleteParam) (output *model.SysOssDeleteModel, err error) {
if len(param.OssIds) == 0 {
return nil, errors.New("未选择文件")
}
items := make([]*entity.SysOss, 0)
err = s.Model(ctx).WhereIn(dao.SysOss.Columns().OssId, param.OssIds).Scan(&items)
if err != nil {
return nil, err
}
if len(items) == 0 {
return nil, errors.New("文件不存在")
}
_, err = s.Model(ctx).WhereIn(dao.SysOss.Columns().OssId, param.OssIds).Delete()
if err != nil {
return nil, err
}
for _, item := range items {
if item.Service == "local" {
s.DeleteLocal(ctx, item)
}
}
return
}
func (s *sSysOss) Upload(ctx context.Context, param *model.SysOssUploadParam) (output *model.SysOssUploadModel, err error) {
r := g.RequestFromCtx(ctx)
file := r.GetUploadFile("file")
if file == nil {
err = gerror.New("上传文件必须")
return
}
// 检查文件类型
err = s.CheckType(ctx, param.FileType, file)
if err != nil {
return
}
// 检查文件大小
err = s.CheckSize(ctx, param.FileType, file)
if err != nil {
return
}
// TODO:待实现其他云存储
if param.NewFileType == 0 {
if param.IsDevice {
param.NewFileType = consts.SysUploadNewFileTypeDeviceFile
} else if param.FileType == consts.SysUploadFileTypeImg {
param.NewFileType = consts.SysUploadNewFileTypeUserImg
} else {
param.NewFileType = consts.SysUploadNewFileTypeUserFile
}
}
result, err := s.UploadLocal(ctx, file, param.NewFileType, param.SaveOriginalName, param.SubDirName, param.NotAddDate)
if err != nil {
return nil, err
}
id, err := s.Model(ctx).Data(g.Map{
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
dao.SysOss.Columns().FileName: result.Name,
dao.SysOss.Columns().OriginalName: result.OriginalName,
dao.SysOss.Columns().FileSuffix: result.Ext,
dao.SysOss.Columns().Path: result.Path,
dao.SysOss.Columns().Url: result.FullPath,
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
dao.SysOss.Columns().CreatedAt: gtime.Now(),
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().Service: "local",
dao.SysOss.Columns().Md5: result.Md5,
dao.SysOss.Columns().FileSize: result.Size,
dao.SysOss.Columns().FileCrc16: result.Crc16,
dao.SysOss.Columns().FileSum: result.Sum16,
}).InsertAndGetId()
if err != nil {
return nil, err
}
output = &model.SysOssUploadModel{}
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&output)
if err != nil {
return nil, err
}
return
}
func (s *sSysOss) UploadWithBizName(ctx context.Context, param *model.SysOssUploadParam) (output *model.SysOssUploadModel, err error) {
r := g.RequestFromCtx(ctx)
file := r.GetUploadFile("file")
if file == nil {
err = gerror.New("上传文件必须")
return
}
if err = s.CheckType(ctx, param.FileType, file); err != nil {
return
}
if err = s.CheckSize(ctx, param.FileType, file); err != nil {
return
}
if param.NewFileType == 0 {
if param.IsDevice {
param.NewFileType = consts.SysUploadNewFileTypeDeviceFile
} else if param.FileType == consts.SysUploadFileTypeImg {
param.NewFileType = consts.SysUploadNewFileTypeUserImg
} else {
param.NewFileType = consts.SysUploadNewFileTypeUserFile
}
}
var result model.UploadResponse
for i := 0; i < 3; i++ {
fileName := makeBizUploadFileName(file.Filename)
result, err = s.UploadLocalWithFileName(ctx, file, param.NewFileType, fileName, param.SubDirName, param.NotAddDate)
if err == nil {
break
}
if !errors.Is(err, os.ErrExist) {
return nil, err
}
}
if err != nil {
return nil, err
}
id, err := s.Model(ctx).Data(g.Map{
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
dao.SysOss.Columns().FileName: result.Name,
dao.SysOss.Columns().OriginalName: result.OriginalName,
dao.SysOss.Columns().FileSuffix: result.Ext,
dao.SysOss.Columns().Path: result.Path,
dao.SysOss.Columns().Url: result.FullPath,
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
dao.SysOss.Columns().CreatedAt: gtime.Now(),
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().Service: "local",
dao.SysOss.Columns().Md5: result.Md5,
dao.SysOss.Columns().FileSize: result.Size,
dao.SysOss.Columns().FileCrc16: result.Crc16,
dao.SysOss.Columns().FileSum: result.Sum16,
}).InsertAndGetId()
if err != nil {
return nil, err
}
output = &model.SysOssUploadModel{}
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&output)
if err != nil {
return nil, err
}
return
}
func (s *sSysOss) MoveFile(ctx context.Context, param *model.SysOssMoveFileParam) (output *model.SysOssMoveFileModel, err error) {
result, err := s.MoveFileLocal(ctx, param.FilePath, param.NewFileType, param.SaveOriginalName, param.SubDirName, param.NotAddDate)
if err != nil {
return nil, err
}
id, err := s.Model(ctx).Data(g.Map{
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
dao.SysOss.Columns().FileName: result.Name,
dao.SysOss.Columns().OriginalName: result.OriginalName,
dao.SysOss.Columns().FileSuffix: result.Ext,
dao.SysOss.Columns().Path: result.Path,
dao.SysOss.Columns().Url: result.FullPath,
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
dao.SysOss.Columns().CreatedAt: gtime.Now(),
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().Service: "local",
dao.SysOss.Columns().Md5: result.Md5,
dao.SysOss.Columns().FileSize: result.Size,
dao.SysOss.Columns().FileCrc16: result.Crc16,
dao.SysOss.Columns().FileSum: result.Sum16,
}).InsertAndGetId()
if err != nil {
return nil, err
}
oss := &entity.SysOss{}
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&oss)
if err != nil {
return nil, err
}
output = &model.SysOssMoveFileModel{
SysOss: oss,
}
return output, nil
}
func (s *sSysOss) SaveContent(ctx context.Context, param *model.SysOssSaveContentParam) (output *model.SysOssSaveContentModel, err error) {
result, err := s.SaveContentLocal(ctx, param.Content, param.NewFileType, param.FileName, param.SubDirName, param.NotAddDate)
if err != nil {
return nil, err
}
id, err := s.Model(ctx).Data(g.Map{
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
dao.SysOss.Columns().FileName: result.Name,
dao.SysOss.Columns().OriginalName: result.OriginalName,
dao.SysOss.Columns().FileSuffix: result.Ext,
dao.SysOss.Columns().Path: result.Path,
dao.SysOss.Columns().Url: result.FullPath,
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
dao.SysOss.Columns().CreatedAt: gtime.Now(),
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
dao.SysOss.Columns().Service: "local",
dao.SysOss.Columns().Md5: result.Md5,
dao.SysOss.Columns().FileSize: result.Size,
dao.SysOss.Columns().FileCrc16: result.Crc16,
dao.SysOss.Columns().FileSum: result.Sum16,
}).InsertAndGetId()
if err != nil {
return nil, err
}
oss := &entity.SysOss{}
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&oss)
if err != nil {
return nil, err
}
output = &model.SysOssSaveContentModel{
SysOss: oss,
}
return output, nil
}
func (s *sSysOss) getNewFileTypePath(ctx context.Context, newFileType int) string {
switch newFileType {
case consts.SysUploadNewFileTypeUserFile:
return consts.SysUploadNewFileTypePathUserFile
case consts.SysUploadNewFileTypeUserImg:
return consts.SysUploadNewFileTypePathUserImg
case consts.SysUploadNewFileTypeDeviceFile:
return consts.SysUploadNewFileTypePathDeviceFile
default:
key := fmt.Sprintf(consts.ConfigOssNewFileTypePath, newFileType)
config, err := service.SysConfig().GetConfigByKey(ctx, key)
if err == nil && config != nil {
return config.ConfigValue
}
}
return "other"
}
func (s *sSysOss) GetSaveFilePathConfig(ctx context.Context, newFileType int, notAddDate int) (string, error) {
uploadPath := g.Cfg().MustGet(ctx, "system.upload.local.path").String()
if uploadPath == "" {
return "", errors.New("上传路径未配置")
}
tenantId := contexts.GetTenantId(ctx)
if tenantId == "" {
tenantId = "default"
}
fileTypePath := s.getNewFileTypePath(ctx, newFileType)
fullDirPath := uploadPath + "/" + tenantId + "/" + fileTypePath
if notAddDate == 0 {
nowData := time.Now().Format("20060102")
fullDirPath = fullDirPath + "/" + nowData
}
return fullDirPath, nil
}
func (s *sSysOss) MoveFileLocal(ctx context.Context, filePath string, newFileType int, useOriginalName int, subDirName string, isAddDate int) (result model.UploadResponse, err error) {
if filePath == "" {
err = errors.New("文件路径必须")
return
}
fileName := gfile.Basename(filePath)
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, isAddDate)
if err != nil {
return
}
if subDirName != "" {
fullDirPath = fullDirPath + "/" + subDirName
}
if !gfile.Exists(fullDirPath) {
err = gfile.Mkdir(fullDirPath)
if err != nil {
return
}
}
err = gfile.Move(filePath, fullDirPath)
if err != nil {
g.Log().Errorf(ctx, "sSysOss MoveFileLocal move file error: %+v, filePath: %s, fullDirPath: %s, newFileType: %d, useOriginalName: %d, subDirName: %s", err, filePath, fullDirPath, newFileType, useOriginalName, subDirName)
return
}
fullFilePath := fullDirPath + "/" + fileName
url := localUploadURL(ctx, fullFilePath)
md5, _ := gmd5.EncryptFile(fullFilePath)
crc16 := hash.Crc16ChecksumFile(fullFilePath)
sum16 := hash.Sum16ChecksumFile(fullFilePath)
result = model.UploadResponse{
Size: gfile.Size(fullFilePath),
Path: fullFilePath,
FullPath: url,
OriginalName: fileName,
Name: fileName,
Type: gfile.ExtName(fileName),
Ext: gfile.ExtName(fileName),
Md5: md5,
Crc16: crc16,
Sum16: sum16,
}
return result, nil
}
// 保存内容
func (s *sSysOss) SaveContentLocal(ctx context.Context, content []byte, newFileType int, fileName string, subDirName string, isAddDate int) (result model.UploadResponse, err error) {
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, isAddDate)
if err != nil {
return
}
if subDirName != "" {
fullDirPath = fullDirPath + "/" + subDirName
}
if !gfile.Exists(fullDirPath) {
err = gfile.Mkdir(fullDirPath)
if err != nil {
return
}
}
fullFilePath := fullDirPath + "/" + fileName
err = gfile.PutBytes(fullFilePath, content)
if err != nil {
return
}
url := localUploadURL(ctx, fullFilePath)
md5, _ := gmd5.EncryptFile(fullFilePath)
crc16 := hash.Crc16ChecksumFile(fullFilePath)
sum16 := hash.Sum16ChecksumFile(fullFilePath)
result = model.UploadResponse{
Size: gfile.Size(fullFilePath),
Path: fullFilePath,
FullPath: url,
OriginalName: fileName,
Name: fileName,
Type: "application/json",
Ext: gfile.ExtName(fileName),
Md5: md5,
Crc16: crc16,
Sum16: sum16,
}
return result, nil
}
func (s *sSysOss) UploadLocal(ctx context.Context, file *ghttp.UploadFile, newFileType int, useOriginalName int, subDirName string, notAddDate int) (result model.UploadResponse, err error) {
if file == nil {
err = errors.New("文件必须")
return
}
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, notAddDate)
if err != nil {
return
}
if subDirName != "" {
fullDirPath = fullDirPath + "/" + subDirName
}
if !gfile.Exists(fullDirPath) {
err = gfile.Mkdir(fullDirPath)
if err != nil {
return
}
}
fileName := gfile.Basename(file.Filename)
ext := gfile.Ext(file.Filename)
fileNewName, err := file.Save(fullDirPath, useOriginalName == 0)
if err != nil {
return
}
fullFilePath := fullDirPath + "/" + fileNewName
url := localUploadURL(ctx, fullFilePath)
md5, _ := gmd5.EncryptFile(fullFilePath)
crc16 := hash.Crc16ChecksumFile(fullFilePath)
sum16 := hash.Sum16ChecksumFile(fullFilePath)
result = model.UploadResponse{
Size: file.Size,
Path: fullFilePath,
FullPath: url,
OriginalName: fileName,
Name: fileNewName,
Type: file.Header.Get("Content-type"),
Ext: ext,
Md5: md5,
Crc16: crc16,
Sum16: sum16,
}
return
}
func (s *sSysOss) UploadLocalWithFileName(ctx context.Context, file *ghttp.UploadFile, newFileType int, fileName string, subDirName string, notAddDate int) (result model.UploadResponse, err error) {
if file == nil {
err = errors.New("文件必须")
return
}
fileName = safeUploadFileName(fileName)
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, notAddDate)
if err != nil {
return
}
if subDirName != "" {
fullDirPath = fullDirPath + "/" + subDirName
}
if !gfile.Exists(fullDirPath) {
err = gfile.Mkdir(fullDirPath)
if err != nil {
return
}
}
err = saveUploadFileToPath(file, fullDirPath, fileName)
if err != nil {
return
}
fullFilePath := fullDirPath + "/" + fileName
url := localUploadURL(ctx, fullFilePath)
md5, _ := gmd5.EncryptFile(fullFilePath)
crc16 := hash.Crc16ChecksumFile(fullFilePath)
sum16 := hash.Sum16ChecksumFile(fullFilePath)
result = model.UploadResponse{
Size: file.Size,
Path: fullFilePath,
FullPath: url,
OriginalName: gfile.Basename(file.Filename),
Name: fileName,
Type: file.Header.Get("Content-type"),
Ext: gfile.Ext(fileName),
Md5: md5,
Crc16: crc16,
Sum16: sum16,
}
return
}
func saveUploadFileToPath(file *ghttp.UploadFile, dirPath string, fileName string) error {
source, err := file.Open()
if err != nil {
return gerror.Wrap(err, "open upload file failed")
}
defer source.Close()
filePath := gfile.Join(dirPath, fileName)
target, err := gfile.OpenWithFlagPerm(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return err
}
defer target.Close()
if _, err = io.Copy(target, source); err != nil {
return gerror.Wrapf(err, `copy upload file failed from "%s" to "%s"`, file.Filename, filePath)
}
return nil
}
func safeUploadFileName(fileName string) string {
fileName = strings.TrimSpace(gfile.Basename(fileName))
if fileName == "" {
fileName = "file"
}
for _, old := range []string{"\\", "/", ":", "*", "?", "\"", "<", ">", "|"} {
fileName = strings.ReplaceAll(fileName, old, "_")
}
return fileName
}
func makeBizUploadFileName(fileName string) string {
fileName = safeUploadFileName(fileName)
ext := gfile.Ext(fileName)
baseName := strings.TrimSuffix(fileName, ext)
if baseName == "" {
baseName = "file"
}
uid := uuid32.S()
if len(uid) > 8 {
uid = uid[:8]
}
return fmt.Sprintf("%s_%s_%s.xlsx", time.Now().Format("20060102150405"), baseName, uid)
}
func localUploadURL(ctx context.Context, filePath string) string {
filePath = strings.TrimSpace(filepath.ToSlash(filepath.Clean(filePath)))
if filePath == "" {
return ""
}
if configType, err := service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssUrlPath); err == nil && configType != nil {
configValue := strings.TrimSpace(configType.ConfigValue)
if configValue != "" {
if strings.Contains(configValue, "|") {
split := strings.SplitN(configValue, "|", 2)
from := strings.TrimSpace(filepath.ToSlash(split[0]))
to := strings.TrimSpace(filepath.ToSlash(split[1]))
if suffix, ok := localUploadMappedSuffix(filePath, from); ok {
return joinUploadURL(to, suffix)
}
} else {
if suffix := localUploadRelativePath(filePath); suffix != "" {
return joinUploadURL(configValue, suffix)
}
return joinUploadURL(configValue, filePath)
}
}
}
if suffix := localUploadRelativePath(filePath); suffix != "" {
return joinUploadURL("/upload", suffix)
}
return filePath
}
func localUploadMappedSuffix(filePath string, from string) (string, bool) {
from = strings.Trim(strings.TrimSpace(filepath.ToSlash(from)), "/")
if from == "" {
return "", false
}
normalized := strings.TrimSpace(filepath.ToSlash(filePath))
marker := "/" + from + "/"
if idx := strings.Index(normalized, marker); idx >= 0 {
return strings.TrimPrefix(normalized[idx+len(marker):], "/"), true
}
prefix := from + "/"
if strings.HasPrefix(normalized, prefix) {
return strings.TrimPrefix(normalized[len(prefix):], "/"), true
}
if strings.Trim(normalized, "/") == from {
return "", true
}
return "", false
}
func joinUploadURL(base string, suffix string) string {
base = strings.TrimRight(strings.TrimSpace(filepath.ToSlash(base)), "/")
suffix = strings.TrimLeft(strings.TrimSpace(filepath.ToSlash(suffix)), "/")
if base == "" {
base = "/upload"
}
if suffix == "" {
return base
}
return base + "/" + suffix
}
func (s *sSysOss) DownloadLocal(ctx context.Context, file *entity.SysOss, beforeSend ...func() error) (err error) {
if file == nil {
err = errors.New("文件必须")
return
}
finalPath, err := resolveLocalOssPath(ctx, file.Path)
if err != nil {
return err
}
r := ghttp.RequestFromCtx(ctx)
if r == nil {
err = gerror.New("ctx not http request")
return
}
localFile, err := os.Open(finalPath)
if err != nil {
return gerror.Wrap(err, "本地文件不可读")
}
defer localFile.Close()
info, err := localFile.Stat()
if err != nil {
return gerror.Wrap(err, "获取本地文件信息失败")
}
if !info.Mode().IsRegular() {
return gerror.New("本地文件不是普通文件")
}
for _, callback := range beforeSend {
if err = callback(); err != nil {
return err
}
}
r.Response.ServeFileDownload(finalPath)
r.Response.Status = http.StatusOK
r.Response.Flush()
return
}
func resolveLocalOssPath(ctx context.Context, storedPath string) (string, error) {
storedPath = strings.TrimSpace(storedPath)
if storedPath == "" {
return "", errors.New("文件路径为空")
}
candidates := localOssPathCandidates(ctx, storedPath)
seen := make(map[string]struct{}, len(candidates))
tried := make([]string, 0, len(candidates))
for _, candidate := range candidates {
candidate = strings.TrimSpace(candidate)
if candidate == "" {
continue
}
candidate = filepath.Clean(candidate)
if _, ok := seen[candidate]; ok {
continue
}
seen[candidate] = struct{}{}
tried = append(tried, candidate)
if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
return candidate, nil
}
}
return "", gerror.Newf("本地文件不存在,请检查Web服务和Worker是否共用上传目录,原始路径: %s,尝试路径: %s", storedPath, strings.Join(tried, "; "))
}
func localOssPathCandidates(ctx context.Context, storedPath string) []string {
candidates := []string{storedPath}
if !filepath.IsAbs(storedPath) {
if absPath, err := filepath.Abs(storedPath); err == nil {
candidates = append(candidates, absPath)
}
}
uploadPath := strings.TrimSpace(g.Cfg().MustGet(ctx, "system.upload.local.path").String())
if uploadPath == "" {
return candidates
}
uploadRoots := localOssUploadRootCandidates(uploadPath)
suffixes := localOssRelativeSuffixes(storedPath)
for _, root := range uploadRoots {
if !filepath.IsAbs(storedPath) {
candidates = append(candidates, filepath.Join(root, storedPath))
}
for _, suffix := range suffixes {
candidates = append(candidates, filepath.Join(root, suffix))
}
}
return candidates
}
func localOssUploadRootCandidates(uploadPath string) []string {
roots := make([]string, 0, 4)
for _, root := range []string{
os.Getenv("IOTLCM_SERVER_UPLOAD_ROOT"),
os.Getenv("IOTLCM_UPLOAD_ROOT"),
uploadPath,
} {
root = strings.TrimSpace(root)
if root == "" {
continue
}
roots = appendLocalOssUploadRootCandidate(roots, root)
}
return roots
}
func appendLocalOssUploadRootCandidate(roots []string, root string) []string {
for _, candidate := range localOssEquivalentUploadRoots(root) {
if filepath.IsAbs(candidate) {
roots = appendUniqueLocalOssPath(roots, candidate)
continue
}
if absPath, err := filepath.Abs(candidate); err == nil {
roots = appendUniqueLocalOssPath(roots, absPath)
}
roots = appendUniqueLocalOssPath(roots, candidate)
}
return roots
}
func localOssEquivalentUploadRoots(root string) []string {
root = strings.TrimSpace(root)
if root == "" {
return nil
}
roots := []string{root}
normalized := filepath.ToSlash(filepath.Clean(root))
if normalized == "resource/upload" || strings.HasSuffix(normalized, "/resource/upload") {
prefix := strings.TrimSuffix(normalized, "resource/upload")
roots = appendUniqueLocalOssPath(roots, filepath.FromSlash(prefix+"upload"))
}
return roots
}
func appendUniqueLocalOssPath(values []string, value string) []string {
value = strings.TrimSpace(value)
if value == "" {
return values
}
cleaned := filepath.Clean(value)
for _, existing := range values {
if filepath.Clean(strings.TrimSpace(existing)) == cleaned {
return values
}
}
return append(values, value)
}
func localOssRelativeSuffixes(storedPath string) []string {
normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(storedPath)))
markers := localUploadPathMarkers()
suffixes := make([]string, 0, len(markers))
for _, marker := range markers {
if idx := strings.Index(normalized, marker); idx >= 0 {
suffix := strings.TrimPrefix(normalized[idx+len(marker):], "/")
if suffix != "" {
suffixes = append(suffixes, filepath.FromSlash(suffix))
}
}
}
return suffixes
}
func localUploadRelativePath(filePath string) string {
normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(filePath)))
for _, marker := range localUploadPathMarkers() {
if idx := strings.Index(normalized, marker); idx >= 0 {
return strings.TrimPrefix(normalized[idx+len(marker):], "/")
}
}
return ""
}
func localUploadPathMarkers() []string {
return []string{
"resource/upload/",
"/resource/upload/",
"/app/resource/upload/",
"/app/upload/",
"upload/",
"/upload/",
}
}
func (s *sSysOss) DeleteLocal(ctx context.Context, file *entity.SysOss) (err error) {
if file == nil {
err = errors.New("文件不存在")
return
}
filePath := file.Path
if !gfile.Exists(filePath) {
err = errors.New("文件不存在")
return
}
err = gfile.Remove(filePath)
if err != nil {
return
}
return
}
func (s *sSysOss) CheckType(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
var (
configType *entity.SysConfig
)
if checkFileType == consts.SysUploadFileTypeFile {
//获取上传类型配置
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssFileTypeKey)
if err != nil {
return
}
} else if checkFileType == consts.SysUploadFileTypeImg {
//获取上传类型配置
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssImgTypeKey)
if err != nil {
return
}
} else {
return fmt.Errorf("文件检查类型错误:%s|%s, 文件类型:%s", consts.SysUploadFileTypeFile, consts.SysUploadFileTypeImg, checkFileType)
}
ext := gfile.ExtName(file.Filename)
ext = strings.ToLower(ext)
imageType := gstr.Split(configType.ConfigValue, ",")
rightType := false
for _, v := range imageType {
if v == ext {
rightType = true
break
}
}
if !rightType {
err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。")
return
}
return
}
func (s *sSysOss) CheckSize(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
var (
configType *entity.SysConfig
)
if checkFileType == consts.SysUploadFileTypeFile {
//获取上传类型配置
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssFileSizeKey)
if err != nil {
return
}
} else if checkFileType == consts.SysUploadFileTypeImg {
//获取上传类型配置
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssImgSizeKey)
if err != nil {
return
}
} else {
return fmt.Errorf("文件检查类型错误:%s|%s", consts.SysUploadFileTypeFile, consts.SysUploadFileTypeImg)
}
match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configType.ConfigValue)
if err != nil {
return
}
if len(match) == 0 {
err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)")
return
}
var cfSize int64
switch gstr.ToUpper(match[2]) {
case "MB", "M":
cfSize = gconv.Int64(match[1]) * 1024 * 1024
case "KB", "K":
cfSize = gconv.Int64(match[1]) * 1024
case "":
cfSize = gconv.Int64(match[1])
}
if cfSize == 0 {
err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB")
return
}
if file.Size > cfSize {
err = gerror.New("上传文件大小超过限制,最大为" + configType.ConfigValue)
return
}
return
}
func (s *sSysOss) GetAllUrl(ctx context.Context, url string) (allUrl string, ossId int64, fileSize int64, OriginalName string, md5 string, hmac string, err error) {
entity := &entity.SysOss{}
s.Model(ctx).Where(dao.SysOss.Columns().Path, url).Scan(&entity)
if entity.OssId <= 0 {
return
}
ossId = entity.OssId
ossConfig, err := service.SysOssConfig().View(ctx, &model.SysOssConfigViewParam{
ConfigKey: entity.Service,
})
if err != nil {
g.Log().Errorf(ctx, "sSysOss GetAllUrl error: %+v, url: %s", err, url)
return "", 0, 0, "", "", "", err
}
if ossConfig.IsHttps == "Y" {
allUrl = "https://" + ossConfig.Domain + entity.Path
} else {
allUrl = "http://" + ossConfig.Domain + entity.Path
}
fileSize = gfile.Size(entity.Path)
OriginalName = entity.OriginalName
md5, _ = gmd5.Encrypt(entity.Path)
hmac = ""
return
}