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 通过
158 lines
5.4 KiB
Go
158 lines
5.4 KiB
Go
package admin
|
||
|
||
import (
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/gofiber/fiber/v2"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||
associationSvc "github.com/break/junhong_cmp_fiber/internal/service/phone_asset_association"
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||
)
|
||
|
||
// PhoneAssetAssociationHandler 手机号—资产关联后台处理器。
|
||
// 仅提供查看投影与解除入口;关联只能由 H5 短信验证建立,后台不提供创建或补录入口。
|
||
type PhoneAssetAssociationHandler struct {
|
||
service *associationSvc.Service
|
||
validator *validator.Validate
|
||
}
|
||
|
||
// NewPhoneAssetAssociationHandler 创建手机号—资产关联处理器。
|
||
func NewPhoneAssetAssociationHandler(service *associationSvc.Service, validator *validator.Validate) *PhoneAssetAssociationHandler {
|
||
return &PhoneAssetAssociationHandler{service: service, validator: validator}
|
||
}
|
||
|
||
// List 查询手机号—资产关联列表。
|
||
// GET /api/admin/phone-asset-associations
|
||
func (h *PhoneAssetAssociationHandler) List(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
var request dto.ListPhoneAssetAssociationRequest
|
||
if err := c.QueryParser(&request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||
}
|
||
if err := h.validate("手机号资产关联查询参数不合法", &request); err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.List(c.UserContext(), &request)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
|
||
}
|
||
|
||
// Unbind 解除指定的一条手机号—资产关联。
|
||
// DELETE /api/admin/phone-asset-associations/:id
|
||
func (h *PhoneAssetAssociationHandler) Unbind(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
id, err := pathID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
var request dto.UnbindPhoneAssetAssociationRequest
|
||
if err := c.BodyParser(&request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||
}
|
||
// 路径字段由 Handler 解析后回填,再执行结构校验(ENG-ERR-002)。
|
||
request.ID = id
|
||
if err := h.validate("手机号资产解绑参数不合法", &request); err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.Unbind(c.UserContext(), &request)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.Success(c, result)
|
||
}
|
||
|
||
// BatchUnbind 按资产集合解除全部当前有效关联。
|
||
// POST /api/admin/phone-asset-associations/batch-unbind
|
||
func (h *PhoneAssetAssociationHandler) BatchUnbind(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
var request dto.BatchUnbindPhoneAssetAssociationRequest
|
||
if err := c.BodyParser(&request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||
}
|
||
if err := h.validate("手机号资产批量解绑参数不合法", &request); err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.BatchUnbind(c.UserContext(), &request)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.Success(c, result)
|
||
}
|
||
|
||
// CreateImportTask 创建手机号资产解绑 CSV 导入任务。
|
||
// POST /api/admin/phone-asset-associations/unbind-imports
|
||
func (h *PhoneAssetAssociationHandler) CreateImportTask(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
var request dto.CreatePhoneAssetUnbindImportRequest
|
||
if err := c.BodyParser(&request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||
}
|
||
if err := h.validate("手机号资产解绑导入参数不合法", &request); err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.CreateImportTask(c.UserContext(), &request)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.Success(c, result)
|
||
}
|
||
|
||
// ListImportTasks 查询手机号资产解绑导入任务列表。
|
||
// GET /api/admin/phone-asset-associations/unbind-imports
|
||
func (h *PhoneAssetAssociationHandler) ListImportTasks(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
var request dto.ListPhoneAssetUnbindImportRequest
|
||
if err := c.QueryParser(&request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||
}
|
||
if err := h.validate("手机号资产解绑导入查询参数不合法", &request); err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.ListImportTasks(c.UserContext(), &request)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
|
||
}
|
||
|
||
// ImportTaskDetail 查询手机号资产解绑导入任务详情与逐行结果。
|
||
// GET /api/admin/phone-asset-associations/unbind-imports/:id
|
||
func (h *PhoneAssetAssociationHandler) ImportTaskDetail(c *fiber.Ctx) error {
|
||
if err := requirePlatformManagement(c); err != nil {
|
||
return err
|
||
}
|
||
id, err := pathID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
result, err := h.service.GetImportTask(c.UserContext(), id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return response.Success(c, result)
|
||
}
|
||
|
||
// validate 执行结构校验,失败时返回字段级提示(ENG-ERR-002)。
|
||
func (h *PhoneAssetAssociationHandler) validate(prefix string, request any) error {
|
||
if h.validator == nil {
|
||
return errors.New(errors.CodeInternalError)
|
||
}
|
||
if err := h.validator.Struct(request); err != nil {
|
||
return errors.New(errors.CodeInvalidParam, validationMessage(prefix, request, err))
|
||
}
|
||
return nil
|
||
}
|