59eb34976a
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
982 lines
29 KiB
Go
982 lines
29 KiB
Go
// package system
|
||
// @Link https://github.com/xiujiecn/xiu-admin
|
||
// @Copyright Copyright (c) 2025 LiXiujie
|
||
// @Author Lxj <li@xiujie.cn>
|
||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||
package system
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"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"
|
||
"io"
|
||
"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...)
|
||
}
|
||
|
||
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) (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
|
||
}
|
||
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
|
||
}
|