Files
iotlcm/server/internal/logic/common/batch_execution_log.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
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 已说明。
2026-10-23 14:48:30 +08:00

638 lines
22 KiB
Go

package common
import (
"context"
"fmt"
"path/filepath"
"strconv"
"strings"
"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/allocationunits"
"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/library/xgorm/hook"
"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/service"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
type sCommonBatchExecutionLog struct{}
type batchExecutionLogSummaryBatch struct {
CreatedDept int64 `orm:"created_dept"`
TargetDeptId int64 `orm:"target_dept_id"`
SourceChannel string `orm:"source_channel"`
ExecutionMode int `orm:"execution_mode"`
Id int64 `orm:"id"`
BatchNo string `orm:"batch_no"`
BatchType int `orm:"batch_type"`
SourceBatchNo string `orm:"source_batch_no"`
SourceType *int `orm:"source_type"`
BatchAsyncStatus int `orm:"batch_async_status"`
RunResult int `orm:"run_result"`
FailReason string `orm:"fail_reason"`
TotalCount int `orm:"total_count"`
SourceOssId int64 `orm:"source_oss_id"`
ResultOssId int64 `orm:"result_oss_id"`
BatchInfo *gjson.Json `orm:"batch_info"`
StartTime *gtime.Time `orm:"start_time"`
EndTime *gtime.Time `orm:"end_time"`
}
func NewCommonBatchExecutionLog() *sCommonBatchExecutionLog {
return &sCommonBatchExecutionLog{}
}
func init() {
service.RegisterCommonBatchExecutionLog(NewCommonBatchExecutionLog())
}
func (s *sCommonBatchExecutionLog) batchModel(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.BatchMain.Ctx(ctx), option...)
}
func (s *sCommonBatchExecutionLog) detailModel(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.BatchDetail.Ctx(ctx), option...)
}
func (s *sCommonBatchExecutionLog) downloadModel(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: false,
})
}
return handler.Model(dao.TResourceBatchDownload.Ctx(ctx), option...)
}
func (s *sCommonBatchExecutionLog) ossModel(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: false,
})
}
return handler.Model(dao.SysOss.Ctx(ctx), option...)
}
func (s *sCommonBatchExecutionLog) List(ctx context.Context, in *model.BatchExecutionLogListParam) (list []*model.BatchExecutionLogListModel, totalCount int, err error) {
mod := s.batchModel(ctx).
Fields(model.BatchExecutionLogListModel{})
if in.BatchNo != "" {
mod = mod.Where(dao.BatchMain.Columns().BatchNo+" LIKE ?", "%"+in.BatchNo+"%")
}
if keyword := strings.TrimSpace(in.Keyword); keyword != "" {
like := "%" + keyword + "%"
mod = mod.Where(
"("+dao.BatchMain.Columns().BatchNo+" LIKE ? OR "+dao.BatchMain.Columns().OrderSn+" LIKE ? OR "+dao.BatchMain.Columns().FailReason+" LIKE ?)",
like,
like,
like,
)
}
if in.BatchAsyncStatus != nil {
mod = mod.Where(dao.BatchMain.Columns().BatchAsyncStatus, *in.BatchAsyncStatus)
}
if in.SourceType != nil {
mod = mod.Where(dao.BatchMain.Columns().SourceType, *in.SourceType)
}
if in.SourceChannel != "" {
mod = mod.Where(dao.BatchMain.Columns().SourceChannel, in.SourceChannel)
}
if in.ExecutionMode != nil {
mod = mod.Where(dao.BatchMain.Columns().ExecutionMode, *in.ExecutionMode)
}
mod = mod.Where(
fmt.Sprintf("NOT (%s = ? AND COALESCE(%s, '') <> '' AND %s <> %s)", dao.BatchMain.Columns().BatchType, dao.BatchMain.Columns().SourceBatchNo, dao.BatchMain.Columns().SourceBatchNo, dao.BatchMain.Columns().BatchNo),
consts.BatchTypeAllocate,
)
mod = executionLogBatchTypeModel(mod, dao.BatchMain.Columns().BatchType, dao.BatchMain.Columns().BatchInfo, in.BatchType)
if len(in.CreatedAt) == 2 {
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
mod = mod.WhereBetween(dao.BatchMain.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
}
mod = mod.Page(in.Page, in.PageSize).
OrderDesc(dao.BatchMain.Columns().Id).
Hook(hook.MemberSummary)
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取执行日志列表失败,请稍后重试!")
return
}
fields := []*allocationunits.Fields{}
for _, item := range list {
if item.BatchType == consts.BatchTypeAllocate {
item.Fields = allocationunits.Resolve(item.UnitInfo, item.CreatedDept, item.StoredTargetDeptId)
fields = append(fields, &item.Fields)
}
}
if err = allocationunits.Fill(ctx, contexts.GetTenantId(ctx), fields); err != nil {
return nil, 0, err
}
return
}
func (s *sCommonBatchExecutionLog) Summary(ctx context.Context, in *model.BatchExecutionLogDetailParam) (res *model.BatchExecutionLogDetailModel, err error) {
if in == nil || in.BatchNo == "" {
return nil, gerror.New("批次号不能为空")
}
batch := &batchExecutionLogSummaryBatch{}
mod := executionLogBatchTypeModel(
s.batchModel(ctx).Where(dao.BatchMain.Columns().BatchNo, in.BatchNo),
dao.BatchMain.Columns().BatchType,
dao.BatchMain.Columns().BatchInfo,
nil,
)
if err = mod.Scan(batch); err != nil {
return nil, gerror.Wrap(err, "获取执行日志详情失败,请稍后重试!")
}
if batch.Id == 0 {
return nil, gerror.New("执行日志不存在")
}
if isAllocateChildBatch(batch.BatchType, batch.BatchNo, batch.SourceBatchNo) {
return nil, gerror.New("执行日志不存在")
}
res = &model.BatchExecutionLogDetailModel{
SourceChannel: batch.SourceChannel,
ExecutionMode: batch.ExecutionMode,
Id: batch.Id,
BatchNo: batch.BatchNo,
BatchType: batch.BatchType,
SourceType: batch.SourceType,
BatchAsyncStatus: batch.BatchAsyncStatus,
RunResult: batch.RunResult,
FailReason: batch.FailReason,
TotalCount: batch.TotalCount,
StartTime: batch.StartTime,
EndTime: batch.EndTime,
}
if batch.BatchType == consts.BatchTypeAllocate {
res.Fields = allocationunits.Resolve(batch.BatchInfo, batch.CreatedDept, batch.TargetDeptId)
if err := allocationunits.Fill(ctx, contexts.GetTenantId(ctx), []*allocationunits.Fields{&res.Fields}); err != nil {
return nil, err
}
}
if sourceFile, err := s.getSourceFile(ctx, batch.SourceOssId, batch.BatchInfo); err != nil {
return nil, err
} else {
res.SourceFile = sourceFile
}
if resultFile, err := s.getResultFile(ctx, batch.BatchNo, batch.ResultOssId, batch.BatchInfo); err != nil {
return nil, err
} else {
res.ResultFile = resultFile
}
return res, nil
}
func (s *sCommonBatchExecutionLog) DetailList(ctx context.Context, in *model.BatchDetailListParam) (list []*model.BatchDetailListModel, totalCount int, err error) {
if in == nil || in.BatchNo == "" {
return nil, 0, gerror.New("批次号不能为空")
}
batch, err := s.getDetailBatch(ctx, in.BatchNo, in.BatchType)
if err != nil {
return nil, 0, err
}
columns := dao.BatchDetail.Columns()
detailBatchType := batch.BatchType
mod := s.detailModel(ctx).
Fields(
columns.Id,
columns.BatchNo,
columns.BatchType,
columns.DetailStatus,
columns.RowNo,
columns.ItemId,
columns.ErrorReason,
columns.Snapshot,
columns.TenantId,
columns.CreatedDept,
columns.CreatedBy,
).
Where(columns.BatchType, detailBatchType)
if isAllocateParentBatch(batch) {
childBatches := s.batchModel(ctx).
Fields(dao.BatchMain.Columns().BatchNo).
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypeAllocate).
Where(dao.BatchMain.Columns().SourceBatchNo, batch.BatchNo).
Where(dao.BatchMain.Columns().SourceBatchNo + " <> " + dao.BatchMain.Columns().BatchNo)
mod = mod.Where("("+columns.BatchNo+" = ? OR "+columns.BatchNo+" IN (?))", batch.BatchNo, childBatches)
} else {
mod = mod.Where(columns.BatchNo, batch.BatchNo)
}
if in.DetailStatus != nil {
mod = mod.Where(columns.DetailStatus, *in.DetailStatus)
}
if in.Iccid != "" {
mod = mod.Where(columns.ItemId+" LIKE ?", "%"+in.Iccid+"%")
}
if keyword := strings.TrimSpace(in.Keyword); keyword != "" {
like := "%" + keyword + "%"
mod = mod.Where("("+columns.ItemId+" LIKE ? OR "+columns.ErrorReason+" LIKE ?)", like, like)
}
var details []*entity.BatchDetail
mod = mod.Page(in.Page, in.PageSize).OrderAsc(columns.RowNo)
if err = mod.ScanAndCount(&details, &totalCount, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取批次执行明细列表失败,请稍后重试")
}
list = make([]*model.BatchDetailListModel, 0, len(details))
displayRowStart := 0
if in.Page > 0 && in.PageSize > 0 {
displayRowStart = (in.Page - 1) * in.PageSize
}
for index, detail := range details {
iccid, msisdn, imsi := batchExecutionLogDetailSnapshotCards(detail)
item := &model.BatchDetailListModel{
RowNo: displayRowStart + index + 1,
Iccid: iccid,
Msisdn: msisdn,
Imsi: imsi,
Imei: batchExecutionLogSnapshotValue(detail, "IMEI", "imei", "rawFields.IMEI"),
ChanKey: batchExecutionLogSnapshotValue(detail, "chanKey", "chan_key", "rawFields.chanKey", "rawFields.chan_key", "rawFields.通道编号", "rawFields.通道编码", "rawFields.通道编号*"),
CardType: batchExecutionLogSnapshotValue(detail, "cardType", "rawFields.cardType", "rawFields.卡类型"),
UpPkgCode: batchExecutionLogSnapshotValue(detail, "upPkgCode", "rawFields.upPkgCode", "rawFields.上游套餐编码", "rawFields.上游套餐"),
SuppliersCode: batchExecutionLogSnapshotValue(detail, "suppliersCode", "rawFields.suppliersCode", "rawFields.供应商编码", "rawFields.供应商"),
UpstreamActivationTime: batchExecutionLogSnapshotValue(detail, "upstreamActivationTime", "rawFields.upstreamActivationTime", "rawFields.上游激活时间", "rawFields.上游给出激活时间"),
InsertToPlugEnabled: batchExecutionLogSnapshotValue(detail, "insertToPlugEnabled", "rawFields.insertToPlugEnabled", "rawFields.是否贴片转插拔"),
CarrierTrafficCutoffEnabled: batchExecutionLogSnapshotValue(detail, "carrierTrafficCutoffEnabled", "rawFields.carrierTrafficCutoffEnabled", "rawFields.是否运营商达量断网"),
AgreementExpiryDate: batchExecutionLogSnapshotValue(detail, "agreementExpiryDate", "rawFields.agreementExpiryDate", "rawFields.协议到期时间", "rawFields.协议期到期时间"),
HasTestPeriod: batchExecutionLogSnapshotValue(detail, "hasTestPeriod", "rawFields.hasTestPeriod", "rawFields.是否有测试期"),
TestPeriodDays: batchExecutionLogSnapshotValue(detail, "testPeriodDays", "rawFields.testPeriodDays", "rawFields.测试期天数"),
TestPeriodLatestExpiryDate: batchExecutionLogSnapshotValue(detail, "testPeriodLatestExpiryDate", "rawFields.testPeriodLatestExpiryDate", "rawFields.测试期最晚过期日期"),
HasSilentPeriod: batchExecutionLogSnapshotValue(detail, "hasSilentPeriod", "rawFields.hasSilentPeriod", "rawFields.是否有沉默期"),
SilentPeriodDays: batchExecutionLogSnapshotValue(detail, "silentPeriodDays", "rawFields.silentPeriodDays", "rawFields.沉默期天数"),
SilentPeriodLatestExpiryDate: batchExecutionLogSnapshotValue(detail, "silentPeriodLatestExpiryDate", "rawFields.silentPeriodLatestExpiryDate", "rawFields.沉默期最晚过期日期"),
DetailStatus: detail.DetailStatus,
}
if batch.BatchType == consts.BatchTypeAllocate {
parentUnits := allocationunits.Resolve(batch.BatchInfo, batch.CreatedDept, batch.TargetDeptId)
item.Fields = allocationunits.Resolve(detail.Snapshot, parentUnits.SourceDeptId, parentUnits.TargetDeptId)
if item.SourceDeptName == "" && item.SourceDeptId == parentUnits.SourceDeptId {
item.SourceDeptName = parentUnits.SourceDeptName
}
if item.TargetDeptName == "" && item.TargetDeptId == parentUnits.TargetDeptId {
item.TargetDeptName = parentUnits.TargetDeptName
}
}
if detail.ErrorReason != "" {
item.Error = &model.BatchDetailErrorModel{
Reason: detail.ErrorReason,
}
}
list = append(list, item)
}
fields := []*allocationunits.Fields{}
for _, item := range list {
if batch.BatchType == consts.BatchTypeAllocate {
fields = append(fields, &item.Fields)
}
}
if err = allocationunits.Fill(ctx, contexts.GetTenantId(ctx), fields); err != nil {
return nil, 0, err
}
return
}
func (s *sCommonBatchExecutionLog) getDetailBatch(ctx context.Context, batchNo string, batchType *int) (*entity.BatchMain, error) {
if batchNo == "" {
return nil, gerror.New("批次号不能为空")
}
columns := dao.BatchMain.Columns()
batch := &entity.BatchMain{}
mod := s.batchModel(ctx).
Fields(
columns.Id,
columns.BatchNo,
columns.BatchType,
columns.SourceType,
columns.SourceBatchNo,
columns.BatchInfo,
columns.CreatedDept, columns.TargetDeptId,
).
Where(columns.BatchNo, batchNo)
mod = executionLogBatchTypeModel(mod, columns.BatchType, columns.BatchInfo, batchType)
if err := mod.Scan(batch); err != nil {
return nil, gerror.Wrap(err, "获取批次信息失败,请稍后重试")
}
if batch.Id == 0 {
return nil, gerror.New("批次不存在")
}
if isAllocateChildBatch(batch.BatchType, batch.BatchNo, batch.SourceBatchNo) {
return nil, gerror.New("批次不存在")
}
if !executionLogBatchTypeMatches(batch.BatchType, batch.BatchInfo, batchType) {
return nil, gerror.New("批次类型不匹配")
}
return batch, nil
}
func executionLogBatchTypeModel(mod *gdb.Model, batchTypeColumn, batchInfoColumn string, requested *int) *gdb.Model {
if requested == nil {
return mod
}
if *requested == consts.BatchTypePrepare {
return mod.Where(batchTypeColumn, consts.BatchTypePrepare)
}
if *requested == consts.BatchTypeAllocate {
return mod.Where(
fmt.Sprintf("(%s = ? OR (%s = ? AND JSON_UNQUOTE(JSON_EXTRACT(%s, '$.targetBatchType')) = ?))", batchTypeColumn, batchTypeColumn, batchInfoColumn),
consts.BatchTypeAllocate,
consts.BatchTypePrepare,
strconv.Itoa(*requested),
)
}
return mod.Where(
fmt.Sprintf("(%s = ? OR (%s = ? AND JSON_UNQUOTE(JSON_EXTRACT(%s, '$.targetBatchType')) = ?))", batchTypeColumn, batchTypeColumn, batchInfoColumn),
*requested,
consts.BatchTypePrepare,
strconv.Itoa(*requested),
)
}
func executionLogBatchTypeMatches(actual int, info *gjson.Json, requested *int) bool {
if requested == nil {
return true
}
if actual == *requested {
return true
}
return actual == consts.BatchTypePrepare && info != nil && info.Get("targetBatchType").Int() == *requested
}
func isAllocateParentBatch(batch *entity.BatchMain) bool {
if batch == nil || batch.BatchType != consts.BatchTypeAllocate || isAllocateChildBatch(batch.BatchType, batch.BatchNo, batch.SourceBatchNo) {
return false
}
return true
}
func isAllocateChildBatch(batchType int, batchNo, sourceBatchNo string) bool {
return batchType == consts.BatchTypeAllocate &&
strings.TrimSpace(sourceBatchNo) != "" &&
strings.TrimSpace(sourceBatchNo) != strings.TrimSpace(batchNo)
}
func batchExecutionLogDetailSnapshotCards(detail *entity.BatchDetail) (iccid, msisdn, imsi string) {
if detail == nil {
return
}
if detail.Snapshot != nil {
iccid = strings.TrimSpace(detail.Snapshot.Get("ICCID").String())
msisdn = strings.TrimSpace(detail.Snapshot.Get("MSISDN").String())
imsi = strings.TrimSpace(detail.Snapshot.Get("IMSI").String())
if iccid == "" {
iccid = strings.TrimSpace(detail.Snapshot.Get("iccid").String())
}
if msisdn == "" {
msisdn = strings.TrimSpace(detail.Snapshot.Get("msisdn").String())
}
if imsi == "" {
imsi = strings.TrimSpace(detail.Snapshot.Get("imsi").String())
}
}
if iccid == "" {
iccid = detail.ItemId
}
return
}
func batchExecutionLogSnapshotValue(detail *entity.BatchDetail, paths ...string) string {
if detail == nil || detail.Snapshot == nil {
return ""
}
for _, path := range paths {
value := strings.TrimSpace(detail.Snapshot.Get(path).String())
if value != "" {
return value
}
}
return ""
}
func (s *sCommonBatchExecutionLog) getSourceFile(ctx context.Context, sourceOssId int64, batchInfo *gjson.Json) (*model.BatchExecutionLogFileModel, error) {
ossId := sourceOssId
if ossId == 0 {
ossId = legacySourceOssId(batchInfo)
}
if ossId == 0 {
return nil, nil
}
oss := &entity.SysOss{}
if err := s.ossModel(ctx).
Where(dao.SysOss.Columns().OssId, ossId).
Scan(oss); err != nil {
return nil, gerror.Wrap(err, "获取原始文件信息失败,请稍后重试!")
}
if oss.OssId == 0 {
return nil, nil
}
return &model.BatchExecutionLogFileModel{
OssId: oss.OssId,
Url: batchExecutionLogFileURL(ctx, oss.Url, oss.Path),
}, nil
}
func (s *sCommonBatchExecutionLog) getResultFile(ctx context.Context, batchNo string, resultOssId int64, batchInfo *gjson.Json) (*model.BatchExecutionLogResultFileModel, error) {
download := &entity.TResourceBatchDownload{}
if err := s.downloadModel(ctx).
Where(dao.TResourceBatchDownload.Columns().BatchNo, batchNo).
OrderDesc(dao.TResourceBatchDownload.Columns().Id).
Limit(1).
Scan(download); err != nil {
return nil, gerror.Wrap(err, "获取结果文件信息失败,请稍后重试!")
}
ossId := resultOssId
if ossId == 0 && download.OssId > 0 {
ossId = download.OssId
}
if ossId == 0 {
ossId = legacyResultOssId(batchInfo)
}
if download.Id == 0 && ossId == 0 {
return nil, nil
}
url := batchExecutionLogFileURL(ctx, download.FileUrl, download.FilePath)
if ossId > 0 {
oss := &entity.SysOss{}
if err := s.ossModel(ctx).
Where(dao.SysOss.Columns().OssId, ossId).
Scan(oss); err != nil {
return nil, gerror.Wrap(err, "获取结果文件OSS信息失败,请稍后重试!")
}
if oss.OssId > 0 {
if ossUrl := batchExecutionLogFileURL(ctx, oss.Url, oss.Path); ossUrl != "" {
url = ossUrl
}
}
}
return &model.BatchExecutionLogResultFileModel{
DownloadId: download.Id,
OssId: ossId,
Url: url,
DownloadStatus: resultDownloadStatus(download),
}, nil
}
func legacySourceOssId(batchInfo *gjson.Json) int64 {
if batchInfo == nil {
return 0
}
for _, path := range []string{"importFileOssId", "source.importFileOssId", "source.inputFileOssId"} {
if ossId := batchInfo.Get(path).Int64(); ossId > 0 {
return ossId
}
}
return 0
}
func legacyResultOssId(batchInfo *gjson.Json) int64 {
if batchInfo == nil {
return 0
}
return batchInfo.Get("resultFileOssId").Int64()
}
func resultDownloadStatus(download *entity.TResourceBatchDownload) int {
if download != nil && download.Id > 0 {
return download.DownloadStatus
}
return consts.DownloadStatusGenerated
}
func batchExecutionLogFileURL(ctx context.Context, storedURL string, storedPath string) string {
if url := normalizeBatchLocalUploadURL(ctx, storedURL); url != "" {
return url
}
if url := normalizeBatchLocalUploadURL(ctx, storedPath); url != "" {
return url
}
return strings.TrimSpace(storedURL)
}
func normalizeBatchLocalUploadURL(ctx context.Context, value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
lower := strings.ToLower(value)
if strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") {
return value
}
normalized := filepath.ToSlash(filepath.Clean(value))
if normalized == "/upload" || strings.HasPrefix(normalized, "/upload/") {
return normalized
}
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 := batchLocalUploadMappedSuffix(normalized, from); ok {
return batchJoinUploadURL(to, suffix)
}
} else if suffix := batchLocalUploadRelativePath(normalized); suffix != "" {
return batchJoinUploadURL(configValue, suffix)
}
}
}
if suffix := batchLocalUploadRelativePath(normalized); suffix != "" {
return batchJoinUploadURL("/upload", suffix)
}
if strings.HasPrefix(normalized, "/") {
return normalized
}
return ""
}
func batchLocalUploadMappedSuffix(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 batchLocalUploadRelativePath(filePath string) string {
normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(filePath)))
for _, marker := range []string{
"resource/upload/",
"/resource/upload/",
"/app/resource/upload/",
"/app/upload/",
"upload/",
"/upload/",
} {
if idx := strings.Index(normalized, marker); idx >= 0 {
return strings.TrimPrefix(normalized[idx+len(marker):], "/")
}
}
return ""
}
func batchJoinUploadURL(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
}