Files
junhong_cmp_fiber/internal/model/dto/phone_asset_association_dto.go
break 70e680eb0a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m2s
feat(手机号资产关联): AUG26-009 手机号—资产关联、十项上限与后台解绑
- 新增成对迁移 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 通过
2026-09-15 11:54:56 +08:00

153 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dto
import "time"
// ListPhoneAssetAssociationRequest 后台查询手机号—资产关联列表请求。
// 仅超级管理员与平台用户可调用,范围外资产不进入结果集,越权与不存在不形成可枚举差异。
type ListPhoneAssetAssociationRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
AssetIdentifier string `json:"asset_identifier" query:"asset_identifier" validate:"omitempty,max=100" maxLength:"100" description:"资产标识ICCID、虚拟号、IMEI、SN 或接入号,精确匹配)"`
Phone string `json:"phone" query:"phone" validate:"omitempty,max=20" maxLength:"20" description:"手机号(完整值精确匹配)"`
Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"关联状态 (0:已失效, 1:有效)"`
CreatedAtStart *time.Time `json:"created_at_start" query:"created_at_start" description:"关联创建时间起(含)"`
CreatedAtEnd *time.Time `json:"created_at_end" query:"created_at_end" description:"关联创建时间止(含)"`
}
// PhoneAssetAssociationResponse 手机号—资产关联列表项。
// 手机号为读取范围内的完整值(读侧不脱敏);审计与日志仍只写脱敏值。
type PhoneAssetAssociationResponse struct {
ID uint `json:"id" description:"关联关系ID"`
Phone string `json:"phone" description:"关联手机号(完整值)"`
AssetType string `json:"asset_type" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetID uint `json:"asset_id" description:"资产ID"`
AssetIdentifier string `json:"asset_identifier" description:"资产当前标识(卡为 ICCID设备为虚拟号"`
Status int `json:"status" description:"关联状态 (0:已失效, 1:有效)"`
StatusName string `json:"status_name" description:"关联状态中文名称"`
Source string `json:"source" description:"建立来源,固定为 h5_sms_verification"`
EstablishedAt string `json:"established_at" description:"建立时间(短信验证通过时间)"`
InvalidatedAt string `json:"invalidated_at" description:"失效时间;有效关系为空"`
InvalidationMethod string `json:"invalidation_method" description:"失效方式 (backend_single:后台单项, backend_batch:后台批量, csv_import:CSV导入);有效关系为空"`
InvalidationMethodName string `json:"invalidation_method_name" description:"失效方式中文名称;有效关系为空"`
InvalidationReason string `json:"invalidation_reason" description:"失效原因;有效关系为空"`
}
// PhoneAssetAssociationPageResult 手机号—资产关联列表分页响应。
type PhoneAssetAssociationPageResult struct {
Items []*PhoneAssetAssociationResponse `json:"items" description:"关联列表"`
Total int64 `json:"total" description:"总记录数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// UnbindPhoneAssetAssociationRequest 单项解除手机号—资产关联请求。
// 路径主键即指定一条关系;解除必须二次确认并填写原因,缺一即拒绝。
type UnbindPhoneAssetAssociationRequest struct {
ID uint `json:"-" path:"id" description:"关联关系ID" required:"true" minimum:"1"`
Reason string `json:"reason" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"解除原因1500 字符)"`
Confirmed bool `json:"confirmed" validate:"required,eq=true" required:"true" description:"二次确认,必须为 true"`
}
// UnbindPhoneAssetAssociationResponse 单项解除结果。
type UnbindPhoneAssetAssociationResponse struct {
ID uint `json:"id" description:"关联关系ID"`
AssetType string `json:"asset_type" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetID uint `json:"asset_id" description:"资产ID"`
UnboundCount int `json:"unbound_count" description:"本次解除的有效关系数,成功时为 1"`
InvalidatedAt string `json:"invalidated_at" description:"失效时间"`
}
// BatchUnbindAssetItem 批量解除中的单项资产。
type BatchUnbindAssetItem struct {
AssetType string `json:"asset_type" validate:"required,oneof=iot_card device" required:"true" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetID uint `json:"asset_id" validate:"required,min=1" required:"true" minimum:"1" description:"资产ID"`
}
// BatchUnbindPhoneAssetAssociationRequest 按资产批量解除手机号关联请求。
// 每项资产解除其全部当前有效关系,逐资产独立执行,部分成功不回滚成功项。
type BatchUnbindPhoneAssetAssociationRequest struct {
Assets []BatchUnbindAssetItem `json:"assets" validate:"required,min=1,max=200,dive" required:"true" description:"待解除的资产集合,按 (资产类型, 资产ID) 去重后执行"`
Reason string `json:"reason" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"解除原因1500 字符)"`
Confirmed bool `json:"confirmed" validate:"required,eq=true" required:"true" description:"二次确认,必须为 true"`
}
// BatchUnbindAssetResult 批量解除的逐项结果。
type BatchUnbindAssetResult struct {
AssetType string `json:"asset_type" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetID uint `json:"asset_id" description:"资产ID"`
Success bool `json:"success" description:"该项是否解除成功"`
UnboundCount int `json:"unbound_count" description:"该项解除的有效关系数"`
Reason string `json:"reason,omitempty" description:"该项失败原因,成功时为空"`
}
// CreatePhoneAssetUnbindImportRequest 创建手机号—资产关联 CSV 解绑导入任务请求。
type CreatePhoneAssetUnbindImportRequest struct {
FileKey string `json:"file_key" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"CSV 对象存储 Key必须以 phone-unbind-imports/ 开头且扩展名为 .csv通过 POST /api/admin/storage/upload-url 获取)"`
Reason string `json:"reason" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"任务级解绑原因1500 字符),写入每次解除的失效原因"`
Confirmed bool `json:"confirmed" validate:"required,eq=true" required:"true" description:"二次确认,必须为 true"`
}
// ListPhoneAssetUnbindImportRequest 查询解绑导入任务列表请求。
type ListPhoneAssetUnbindImportRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:失败)"`
}
// GetPhoneAssetUnbindImportRequest 解绑导入任务详情路径参数。
type GetPhoneAssetUnbindImportRequest struct {
ID uint `path:"id" required:"true" description:"导入任务ID"`
}
// PhoneAssetUnbindImportTaskResponse 解绑导入任务响应。
type PhoneAssetUnbindImportTaskResponse struct {
ID uint `json:"id" description:"导入任务ID"`
TaskNo string `json:"task_no" description:"导入任务编号"`
FileName string `json:"file_name" description:"上传的源 CSV 文件名"`
UnbindReason string `json:"unbind_reason" description:"任务级解绑原因"`
Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:失败)"`
StatusName string `json:"status_name" description:"任务状态中文名称"`
TotalCount int `json:"total_count" description:"数据行总数;任务级失败时为 0"`
SuccessCount int `json:"success_count" description:"成功行数"`
FailCount int `json:"fail_count" description:"失败行数"`
ErrorMessage string `json:"error_message" description:"任务级失败原因;与行级失败原因分开记录"`
CreatorName string `json:"creator_name" description:"任务创建人名称快照"`
CreatedAt string `json:"created_at" description:"创建时间"`
StartedAt string `json:"started_at" description:"开始处理时间"`
CompletedAt string `json:"completed_at" description:"完成时间"`
}
// PhoneAssetUnbindImportTaskPageResult 解绑导入任务分页响应。
type PhoneAssetUnbindImportTaskPageResult struct {
Items []*PhoneAssetUnbindImportTaskResponse `json:"items" description:"导入任务列表"`
Total int64 `json:"total" description:"总记录数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// PhoneAssetUnbindImportItemResponse 解绑导入逐行结果。
// AssociatedPhones 是解绑当时的完整手机号快照:关系已失效,只有快照能事后展示被解绑的手机号。
type PhoneAssetUnbindImportItemResponse struct {
Line int `json:"line" description:"行号,自数据首行起计(表头不计入)"`
AssetType string `json:"asset_type" description:"资产类型 (iot_card:物联网卡, device:设备);未定位到资产时为空"`
AssetIdentifier string `json:"asset_identifier" description:"导入文件中的资产标识原文"`
AssetID uint `json:"asset_id" description:"资产ID未定位到资产时为 0"`
UnboundCount int `json:"unbound_count" description:"该行解除的有效关系数"`
AssociatedPhones []string `json:"associated_phones" description:"解绑当时的完整关联手机号快照;失败行为空数组"`
Status int `json:"status" description:"行状态 (3:成功, 4:失败)"`
StatusName string `json:"status_name" description:"行状态中文名称"`
Reason string `json:"reason" description:"失败原因;成功行为空"`
}
// PhoneAssetUnbindImportTaskDetailResponse 解绑导入任务详情响应。
type PhoneAssetUnbindImportTaskDetailResponse struct {
PhoneAssetUnbindImportTaskResponse
Items []PhoneAssetUnbindImportItemResponse `json:"items" description:"逐行结果明细;任务级失败时为空数组"`
}
type BatchUnbindPhoneAssetAssociationResponse struct {
SuccessCount int `json:"success_count" description:"解除成功的资产项数"`
FailCount int `json:"fail_count" description:"解除失败的资产项数"`
Items []BatchUnbindAssetResult `json:"items" description:"逐项结果,顺序与请求去重后的资产集合一致"`
}