feat(手机号资产关联): AUG26-009 手机号—资产关联、十项上限与后台解绑
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m2s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m2s
- 新增成对迁移 000223(tb_phone_asset_association,含有效关系部分唯一索引与 down 守卫)与 000224(解绑导入任务表),不回填历史 - H5:need_bind_phone 三支判定(开关关闭完全短路);已有主号幂等建联;十项上限按手机号 advisory 串行化(含换绑到全新号的并发场景);换绑原子迁移与冲突整单回滚;不写遗留列 - 后台:关联列表、单项/批量解绑、CSV 导入解绑(B1–B16),超管/平台 gate + 资产数据范围复核,三态统一文案 - 读侧:卡/设备列表与详情按页一次 IN 聚合;两类导出补「关联手机号」列并保留历史表头反解兼容 - 脱敏:关联审计走独立动作/资源只写脱敏手机号;访问日志手机号类字段脱敏 - 同步主 Spec openspec/specs/phone-asset-association 并归档 AUG26-009,补齐 requirement-evidence 与入口矩阵,context-health 通过
This commit is contained in:
@@ -524,6 +524,18 @@ const (
|
||||
AuditActionExportTaskCreated = "export_task.create"
|
||||
// AuditActionExportTaskCancelled 表示取消业务导出任务或提交取消请求。
|
||||
AuditActionExportTaskCancelled = "export_task.cancel"
|
||||
// AuditActionPhoneAssetAssociationCreated 表示 H5 短信验证通过后建立手机号—资产关联。
|
||||
AuditActionPhoneAssetAssociationCreated = "phone_asset_association.create"
|
||||
// AuditActionPhoneAssetAssociationMigrated 表示换绑手机号时把旧号全部有效关联迁移到新号。
|
||||
AuditActionPhoneAssetAssociationMigrated = "phone_asset_association.migrate"
|
||||
// AuditActionPhoneAssetAssociationUnbound 表示后台单项或按资产批量解除手机号—资产关联。
|
||||
AuditActionPhoneAssetAssociationUnbound = "phone_asset_association.unbind"
|
||||
// AuditActionPhoneAssetAssociationImported 表示 CSV 导入单行解除手机号—资产关联。
|
||||
AuditActionPhoneAssetAssociationImported = "phone_asset_association.import_unbind"
|
||||
// AuditActionPhoneAssetUnbindImportTaskCreated 表示创建 CSV 解绑导入任务。
|
||||
AuditActionPhoneAssetUnbindImportTaskCreated = "phone_asset_unbind_import_task.create"
|
||||
// AuditActionPhoneAssetUnbindImportTaskCompleted 表示 CSV 解绑导入任务完成。
|
||||
AuditActionPhoneAssetUnbindImportTaskCompleted = "phone_asset_unbind_import_task.complete"
|
||||
// AuditActionNotificationDelivered 表示 Outbox 消费后实际生成站内通知。
|
||||
AuditActionNotificationDelivered = "notification.deliver"
|
||||
// AuditActionNotificationRead 表示单条通知首次标记已读。
|
||||
@@ -667,6 +679,10 @@ const (
|
||||
AuditResourceOrderPackageInvalidateTask = "order_package_invalidate_task"
|
||||
// AuditResourceExportTask 表示业务导出任务资源。
|
||||
AuditResourceExportTask = "export_task"
|
||||
// AuditResourcePhoneAssetAssociation 表示手机号—资产关联资源。
|
||||
AuditResourcePhoneAssetAssociation = "phone_asset_association"
|
||||
// AuditResourcePhoneAssetUnbindImportTask 表示手机号—资产 CSV 解绑导入任务资源。
|
||||
AuditResourcePhoneAssetUnbindImportTask = "phone_asset_unbind_import_task"
|
||||
// AuditResourceNotification 表示站内通知资源。
|
||||
AuditResourceNotification = "notification"
|
||||
// AuditResourceNotificationReadBatch 表示通知批量已读资源。
|
||||
@@ -863,6 +879,10 @@ const (
|
||||
AuditResourceRoleEnterpriseDeviceBinding = "enterprise_device_binding"
|
||||
// AuditResourceRoleEnterpriseDeviceAuthorization 表示企业设备授权记录。
|
||||
AuditResourceRoleEnterpriseDeviceAuthorization = "enterprise_device_authorization"
|
||||
// AuditResourceRolePhoneAssetAssociationTarget 表示手机号—资产关联的主要目标。
|
||||
AuditResourceRolePhoneAssetAssociationTarget = "phone_asset_association_target"
|
||||
// AuditResourceRolePhoneAssetAssociationAsset 表示关联指向的资产。
|
||||
AuditResourceRolePhoneAssetAssociationAsset = "phone_asset_association_asset"
|
||||
// AuditResourceRolePersonalCustomerTarget 表示个人客户操作的主体目标。
|
||||
AuditResourceRolePersonalCustomerTarget = "personal_customer_target"
|
||||
// AuditResourceRolePersonalCustomerPhone 表示个人客户手机号关系。
|
||||
|
||||
@@ -77,6 +77,7 @@ const (
|
||||
TaskTypeAssetPackageBatchOrder = "asset:package:batch_order" // 资产套餐批量订购
|
||||
// 店铺负责人 CSV 导入任务类型,独立成队列以隔离逐行执行的长时任务。
|
||||
TaskTypeShopBusinessOwnerImport = "shop_business_owner:import"
|
||||
TaskTypePhoneAssetUnbindImport = "phone_asset_unbind:import" // 手机号—资产关联 CSV 解绑导入
|
||||
|
||||
// 订单超时任务类型
|
||||
TaskTypeOrderExpire = "order:expire" // 订单超时自动取消
|
||||
@@ -232,6 +233,7 @@ const (
|
||||
QueueOrderPackageInvalidate = TaskTypeOrderPackageInvalidate // 批量失效订单套餐任务队列
|
||||
QueueAssetPackageBatchOrder = TaskTypeAssetPackageBatchOrder // 资产套餐批量订购任务队列
|
||||
QueueShopBusinessOwnerImport = TaskTypeShopBusinessOwnerImport // 店铺负责人 CSV 导入任务队列
|
||||
QueuePhoneAssetUnbindImport = TaskTypePhoneAssetUnbindImport // 手机号—资产关联 CSV 解绑导入任务队列
|
||||
QueueOrderExpire = TaskTypeOrderExpire // 订单超时取消任务队列
|
||||
QueueAutoPurchase = TaskTypeAutoPurchaseAfterRecharge // 充值后自动购包任务队列
|
||||
QueueAlertCheck = TaskTypeAlertCheck // 告警检查任务队列
|
||||
@@ -291,6 +293,8 @@ func QueueForTaskType(taskType string) string {
|
||||
return QueueAssetPackageBatchOrder
|
||||
case TaskTypeShopBusinessOwnerImport:
|
||||
return QueueShopBusinessOwnerImport
|
||||
case TaskTypePhoneAssetUnbindImport:
|
||||
return QueuePhoneAssetUnbindImport
|
||||
case TaskTypeOrderExpire:
|
||||
return QueueOrderExpire
|
||||
case TaskTypeAutoPurchaseAfterRecharge:
|
||||
@@ -332,6 +336,7 @@ func DefaultTaskQueueWeights() map[string]int {
|
||||
QueueOrderPackageInvalidate: 5,
|
||||
QueueAssetPackageBatchOrder: 5,
|
||||
QueueShopBusinessOwnerImport: 4,
|
||||
QueuePhoneAssetUnbindImport: 4,
|
||||
QueueOrderExpire: 4,
|
||||
QueueEmailSend: 4,
|
||||
QueueExportDispatch: 4,
|
||||
|
||||
142
pkg/constants/phone_asset_association.go
Normal file
142
pkg/constants/phone_asset_association.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
// 手机号—资产关联的状态取值。
|
||||
const (
|
||||
// PhoneAssetAssociationStatusInvalid 表示关联已失效,不再占用十项额度。
|
||||
PhoneAssetAssociationStatusInvalid = 0
|
||||
// PhoneAssetAssociationStatusValid 表示关联当前有效。
|
||||
PhoneAssetAssociationStatusValid = 1
|
||||
)
|
||||
|
||||
// 手机号—资产关联的建立来源;关联只能由 H5 短信验证建立,后台无创建或补录入口。
|
||||
const (
|
||||
// PhoneAssetAssociationSourceH5SMSVerification 表示关联由 H5 短信验证码校验通过后建立。
|
||||
PhoneAssetAssociationSourceH5SMSVerification = "h5_sms_verification"
|
||||
)
|
||||
|
||||
// 手机号—资产关联的失效方式,用于审计与事后核对解除入口。
|
||||
const (
|
||||
// PhoneAssetAssociationInvalidateMethodBackendSingle 表示后台单项解除。
|
||||
PhoneAssetAssociationInvalidateMethodBackendSingle = "backend_single"
|
||||
// PhoneAssetAssociationInvalidateMethodBackendBatch 表示后台勾选批量解除。
|
||||
PhoneAssetAssociationInvalidateMethodBackendBatch = "backend_batch"
|
||||
// PhoneAssetAssociationInvalidateMethodCSVImport 表示 CSV 导入解除。
|
||||
PhoneAssetAssociationInvalidateMethodCSVImport = "csv_import"
|
||||
)
|
||||
|
||||
// 手机号—资产关联的业务口径常量。
|
||||
const (
|
||||
// PhoneAssetAssociationMaxValidPerPhone 表示单手机号可同时有效的关联资产项数上限。
|
||||
PhoneAssetAssociationMaxValidPerPhone = 10
|
||||
// PhoneAssetAssociationLimitMessage 表示超过十项上限时的统一失败文案。
|
||||
PhoneAssetAssociationLimitMessage = "该手机号最多关联10项有效资产"
|
||||
)
|
||||
|
||||
// 手机号—资产关联解绑入口的角色说明。
|
||||
const (
|
||||
// PhoneAssetAssociationAccessDescription 表示关联查看与解除入口的角色说明。
|
||||
PhoneAssetAssociationAccessDescription = "仅超级管理员和平台账号可访问,代理与企业账号返回 403。"
|
||||
)
|
||||
|
||||
// 手机号—资产关联解除的入参口径。
|
||||
const (
|
||||
// PhoneAssetAssociationUnbindReasonMaxLength 表示解除原因的最大长度。
|
||||
PhoneAssetAssociationUnbindReasonMaxLength = 500
|
||||
)
|
||||
|
||||
// 手机号—资产关联 CSV 解绑导入的场景常量。
|
||||
const (
|
||||
// StoragePurposePhoneAssetUnbindImport 表示手机号资产解绑 CSV 导入上传用途。
|
||||
StoragePurposePhoneAssetUnbindImport = "phone_unbind_import"
|
||||
// PhoneAssetUnbindImportStoragePrefix 表示解绑导入文件的对象存储目录。
|
||||
PhoneAssetUnbindImportStoragePrefix = "phone-unbind-imports"
|
||||
// PhoneAssetUnbindImportTaskTimeout 表示单个解绑导入任务的最长执行时间。
|
||||
PhoneAssetUnbindImportTaskTimeout = 2 * time.Hour
|
||||
// PhoneAssetUnbindImportProgressBatchSize 表示导入按批更新进度计数的批大小。
|
||||
PhoneAssetUnbindImportProgressBatchSize = 100
|
||||
// PhoneAssetUnbindImportItemStatusSuccess 表示导入单行已成功解除关联。
|
||||
PhoneAssetUnbindImportItemStatusSuccess = 3
|
||||
// PhoneAssetUnbindImportItemStatusFailed 表示导入单行校验或执行失败并保留原状。
|
||||
PhoneAssetUnbindImportItemStatusFailed = 4
|
||||
)
|
||||
|
||||
// PhoneAssetUnbindImportColumns 表示导入文件首行必须完全一致的固定列序;解绑原因由任务级必填字段提供。
|
||||
var PhoneAssetUnbindImportColumns = []string{"资产标识", "备注"}
|
||||
|
||||
// 手机号—资产关联解绑导入的任务级失败原因,与行级失败原因分开记录。
|
||||
const (
|
||||
// PhoneAssetUnbindImportErrorFileFormat 表示文件格式或表头与固定列序不符。
|
||||
PhoneAssetUnbindImportErrorFileFormat = "文件格式或表头与固定列序不符"
|
||||
// PhoneAssetUnbindImportErrorEncoding 表示文件既非合法 UTF-8 也无法按 GBK 解码。
|
||||
PhoneAssetUnbindImportErrorEncoding = "文件编码无法按 UTF-8 或 GBK 解码"
|
||||
// PhoneAssetUnbindImportErrorNoDataRow 表示文件没有数据行。
|
||||
PhoneAssetUnbindImportErrorNoDataRow = "文件没有数据行"
|
||||
)
|
||||
|
||||
// 手机号—资产关联解绑导入的行级失败原因,取值集合固定。
|
||||
const (
|
||||
// PhoneAssetUnbindImportRowErrorIdentifier 表示资产标识为空。
|
||||
PhoneAssetUnbindImportRowErrorIdentifier = "资产标识不能为空"
|
||||
// PhoneAssetUnbindImportRowErrorAssetMissing 表示资产标识无法定位到资产。
|
||||
PhoneAssetUnbindImportRowErrorAssetMissing = "资产标识不存在或已删除"
|
||||
// PhoneAssetUnbindImportRowErrorNoAssociation 表示该资产已无有效关联。
|
||||
PhoneAssetUnbindImportRowErrorNoAssociation = "该资产已无有效关联手机号"
|
||||
// PhoneAssetUnbindImportRowErrorFormat 表示该行列数与固定列序不一致。
|
||||
PhoneAssetUnbindImportRowErrorFormat = "行格式错误"
|
||||
// PhoneAssetUnbindImportRowErrorRemarkTooLong 表示备注超过长度上限。
|
||||
PhoneAssetUnbindImportRowErrorRemarkTooLong = "备注超过500字符"
|
||||
)
|
||||
|
||||
// 手机号—资产关联解绑导入的备注长度上限。
|
||||
const (
|
||||
// PhoneAssetUnbindImportRemarkMaxLength 表示行内备注的最大长度。
|
||||
PhoneAssetUnbindImportRemarkMaxLength = 500
|
||||
)
|
||||
|
||||
// 手机号—资产关联的三态统一失败文案。
|
||||
// 越权、资产不存在与已无有效关系三种情况必须返回同一文案,避免形成可枚举差异(ENG-AUTHZ-001)。
|
||||
// 本常量只供新增用例使用;既有内联字面量按 As-Is 保留,不在本 Change 统一。
|
||||
const (
|
||||
// PhoneAssetAssociationDeniedMessage 表示越权、资产不存在或已无有效关系时的统一失败文案。
|
||||
PhoneAssetAssociationDeniedMessage = "无权限操作该资源或资源不存在"
|
||||
)
|
||||
|
||||
// GetPhoneAssetAssociationImportItemStatusName 返回解绑导入行状态中文名称。
|
||||
func GetPhoneAssetAssociationImportItemStatusName(status int) string {
|
||||
switch status {
|
||||
case PhoneAssetUnbindImportItemStatusSuccess:
|
||||
return "成功"
|
||||
case PhoneAssetUnbindImportItemStatusFailed:
|
||||
return "失败"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
// GetPhoneAssetAssociationStatusName 返回关联状态中文名称。
|
||||
func GetPhoneAssetAssociationStatusName(status int) string {
|
||||
switch status {
|
||||
case PhoneAssetAssociationStatusValid:
|
||||
return "有效"
|
||||
case PhoneAssetAssociationStatusInvalid:
|
||||
return "已失效"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
// PhoneAssetAssociationInvalidateMethodName 返回失效方式中文名称。
|
||||
func PhoneAssetAssociationInvalidateMethodName(method string) string {
|
||||
switch method {
|
||||
case PhoneAssetAssociationInvalidateMethodBackendSingle:
|
||||
return "后台单项解除"
|
||||
case PhoneAssetAssociationInvalidateMethodBackendBatch:
|
||||
return "后台批量解除"
|
||||
case PhoneAssetAssociationInvalidateMethodCSVImport:
|
||||
return "CSV导入解除"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -52,6 +54,38 @@ func truncateBody(body []byte, maxSize int) (string, bool) {
|
||||
return string(body[:maxSize]), true
|
||||
}
|
||||
|
||||
// phoneFieldNames 是访问日志中属于手机号类的字段名集合。
|
||||
// 手机号不在 sanitizer 的 forbidden 字段清单内,仅靠字段名通用清理无法脱敏;
|
||||
// 这里单独识别并输出前 3 位 + **** + 后 4 位,禁止完整号码进入 query、请求正文或响应正文。
|
||||
// 只覆盖手机号类字段,forbidden 字段仍按原语义标记为已脱敏,两者处理互不影响。
|
||||
var phoneFieldNames = map[string]struct{}{
|
||||
"phone": {},
|
||||
"mobile": {},
|
||||
"associated_phones": {},
|
||||
}
|
||||
|
||||
// normalizeLogFieldName 归一化字段名用于敏感字段判断。
|
||||
func normalizeLogFieldName(key string) string {
|
||||
var builder strings.Builder
|
||||
for index, char := range key {
|
||||
if unicode.IsUpper(char) && index > 0 {
|
||||
builder.WriteByte('_')
|
||||
}
|
||||
if char == '-' || char == '.' {
|
||||
builder.WriteByte('_')
|
||||
continue
|
||||
}
|
||||
builder.WriteRune(unicode.ToLower(char))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// isPhoneField 判断字段名是否属于手机号类字段(与 forbidden 字段分开处理)。
|
||||
func isPhoneField(key string) bool {
|
||||
_, ok := phoneFieldNames[normalizeLogFieldName(key)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// maskSensitiveValue 按字段名判断并脱敏访问日志中的敏感值
|
||||
func maskSensitiveValue(key, value string) string {
|
||||
if value == "" {
|
||||
@@ -60,6 +94,9 @@ func maskSensitiveValue(key, value string) string {
|
||||
if shouldMaskField(key) {
|
||||
return redactedValue
|
||||
}
|
||||
if isPhoneField(key) {
|
||||
return sanitizer.MaskPhone(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -125,6 +162,25 @@ func sanitizeJSONValue(value any) {
|
||||
typed[key] = redactedValue
|
||||
continue
|
||||
}
|
||||
if isPhoneField(key) {
|
||||
// 手机号类数组逐元素脱敏;非字符串元素不保留原值。
|
||||
list, ok := item.([]any)
|
||||
if !ok {
|
||||
if item != nil {
|
||||
typed[key] = redactedValue
|
||||
}
|
||||
continue
|
||||
}
|
||||
for index, element := range list {
|
||||
if text, ok := element.(string); ok {
|
||||
list[index] = sanitizer.MaskPhone(text)
|
||||
continue
|
||||
}
|
||||
list[index] = redactedValue
|
||||
}
|
||||
typed[key] = list
|
||||
continue
|
||||
}
|
||||
sanitizeJSONValue(item)
|
||||
}
|
||||
case []any:
|
||||
|
||||
@@ -78,6 +78,7 @@ func BuildDocHandlers() *bootstrap.Handlers {
|
||||
AssetPackageBatchOrder: admin.NewAssetPackageBatchOrderHandler(nil, nil),
|
||||
BusinessUserGroup: admin.NewBusinessUserGroupHandler(nil, nil),
|
||||
ShopBusinessOwnerImport: admin.NewShopBusinessOwnerImportHandler(nil),
|
||||
PhoneAssetAssociation: admin.NewPhoneAssetAssociationHandler(nil, nil),
|
||||
ClientWechat: app.NewClientWechatHandler(nil, nil, nil),
|
||||
SuperAdmin: admin.NewSuperAdminHandler(nil),
|
||||
SystemConfig: admin.NewSystemConfigHandler(nil, nil),
|
||||
|
||||
@@ -75,6 +75,7 @@ func (h *Handler) RegisterHandlers() *asynq.ServeMux {
|
||||
h.registerOrderPackageInvalidateHandler()
|
||||
h.registerAssetPackageBatchOrderHandler()
|
||||
h.registerShopBusinessOwnerImportHandler()
|
||||
h.registerPhoneAssetUnbindImportHandler()
|
||||
h.registerExportHandlers()
|
||||
h.registerCommissionStatsHandlers()
|
||||
h.registerCommissionCalculationHandler()
|
||||
@@ -154,6 +155,22 @@ func (h *Handler) registerShopBusinessOwnerImportHandler() {
|
||||
h.logger.Info("注册店铺负责人导入任务处理器", zap.String("task_type", constants.TaskTypeShopBusinessOwnerImport))
|
||||
}
|
||||
|
||||
func (h *Handler) registerPhoneAssetUnbindImportHandler() {
|
||||
handler := task.NewPhoneAssetUnbindImportHandler(
|
||||
h.db,
|
||||
h.workerResult.Stores.PhoneAssetUnbindImportTask,
|
||||
h.workerResult.Stores.PhoneAssetAssociation,
|
||||
h.workerResult.Stores.AssetIdentifier,
|
||||
h.workerResult.Stores.IotCard,
|
||||
h.workerResult.Stores.Device,
|
||||
h.storage,
|
||||
h.logger,
|
||||
audit.NewWriter(audit.NewRegistry(), nil),
|
||||
)
|
||||
h.mux.HandleFunc(constants.TaskTypePhoneAssetUnbindImport, handler.Handle)
|
||||
h.logger.Info("注册手机号资产解绑导入任务处理器", zap.String("task_type", constants.TaskTypePhoneAssetUnbindImport))
|
||||
}
|
||||
|
||||
func (h *Handler) registerDeviceImportHandler() {
|
||||
deviceImportHandler := task.NewDeviceImportHandler(
|
||||
h.db,
|
||||
|
||||
@@ -51,9 +51,11 @@ type WorkerStores struct {
|
||||
AssetIdentifier *postgres.AssetIdentifierStore
|
||||
PersonalCustomer *postgres.PersonalCustomerStore
|
||||
PersonalCustomerPhone *postgres.PersonalCustomerPhoneStore
|
||||
PhoneAssetAssociation *postgres.PhoneAssetAssociationStore
|
||||
OrderPackageInvalidateTask *postgres.OrderPackageInvalidateTaskStore
|
||||
AssetPackageBatchOrderTask *postgres.AssetPackageBatchOrderTaskStore
|
||||
ShopBusinessOwnerImportTask *postgres.ShopBusinessOwnerImportTaskStore
|
||||
PhoneAssetUnbindImportTask *postgres.PhoneAssetUnbindImportTaskStore
|
||||
}
|
||||
|
||||
// WorkerServices Worker 侧所有 Service 的集合
|
||||
|
||||
@@ -105,6 +105,23 @@ func SanitizeText(value string) string {
|
||||
return TextSummary(value)
|
||||
}
|
||||
|
||||
// 手机号脱敏保留位数:前 3 位与后 4 位。
|
||||
const (
|
||||
phoneMaskedKeepPrefix = 3
|
||||
phoneMaskedKeepSuffix = 4
|
||||
)
|
||||
|
||||
// MaskPhone 生成脱敏手机号,仅保留前 3 位与后 4 位。
|
||||
// 审计、日志与错误文案一律使用本函数的结果;字段名脱敏清单不含 phone,
|
||||
// 因此不能依赖 RemoveForbiddenFields 自动脱敏,必须显式调用。
|
||||
func MaskPhone(phone string) string {
|
||||
phone = strings.TrimSpace(phone)
|
||||
if len(phone) < phoneMaskedKeepPrefix+phoneMaskedKeepSuffix {
|
||||
return ""
|
||||
}
|
||||
return phone[:phoneMaskedKeepPrefix] + "****" + phone[len(phone)-phoneMaskedKeepSuffix:]
|
||||
}
|
||||
|
||||
// TextSummary 将不可信外部文本转换为不可逆大小和哈希摘要。
|
||||
func TextSummary(value string) string {
|
||||
if value == "" {
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *Service) GenerateFileKey(purpose, fileName string) (string, error) {
|
||||
if ext == "" {
|
||||
ext = ".bin"
|
||||
}
|
||||
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation || purpose == constants.StoragePurposeShopBusinessOwnerImport) && !strings.EqualFold(ext, ".csv") {
|
||||
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation || purpose == constants.StoragePurposeShopBusinessOwnerImport || purpose == constants.StoragePurposePhoneAssetUnbindImport) && !strings.EqualFold(ext, ".csv") {
|
||||
return "", errors.New(errors.CodeInvalidParam, "批量业务文件必须为CSV格式")
|
||||
}
|
||||
|
||||
|
||||
@@ -22,4 +22,7 @@ var PurposeMappings = map[string]PurposeMapping{
|
||||
constants.StoragePurposeShopBusinessOwnerImport: {
|
||||
Prefix: constants.ShopBusinessOwnerImportStoragePrefix, ContentType: "text/csv",
|
||||
},
|
||||
constants.StoragePurposePhoneAssetUnbindImport: {
|
||||
Prefix: constants.PhoneAssetUnbindImportStoragePrefix, ContentType: "text/csv",
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user