// package system // @Link https://github.com/xiujiecn/xiu-admin // @Copyright Copyright (c) 2025 LiXiujie // @Author Lxj // @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 }