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 通过
153 lines
11 KiB
Go
153 lines
11 KiB
Go
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:"解除原因(1~500 字符)"`
|
||
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:"解除原因(1~500 字符)"`
|
||
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:"任务级解绑原因(1~500 字符),写入每次解除的失效原因"`
|
||
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:"逐项结果,顺序与请求去重后的资产集合一致"`
|
||
}
|