Files
pig-farm-controller/openspec/changes/refactor-migrate-gin-to-echo/tasks.md
2025-10-30 17:34:25 +08:00

32 KiB
Raw Blame History

任务清单Gin 到 Echo 迁移

  • 1. 配置文件 (无代码依赖)

    • 修改 config.ymlmode 配置项的注释,将 "Gin 运行模式" 改为 "服务运行模式"。
    • 修改 config.example.ymlmode 配置项的注释,保持与 config.yml 一致。
  • 2. 控制器辅助函数 (最基础的依赖)

    • internal/infra/models/execution.go
      • 添加 ContextAuditStatusContextAuditResultDetails 常量。
    • internal/app/controller/response.go
      • *gin.Context 参数全部替换为 echo.Context
      • 修改响应函数,使其返回 error
      • 新增 SendErrorWithStatus 函数用于在中间件等场景下发送带有特定HTTP状态码的错误响应。
      • 重构 setAuditDetails 函数,使其成为统一设置所有审计信息(包括操作状态和失败详情)的唯一入口。
      • 更新 SendSuccessWithAuditSendErrorWithAudit 以调用重构后的 setAuditDetails
    • internal/app/controller/auth_utils.go
      • *gin.Context 参数全部替换为 echo.Context
      • 适配 Get...FromContext 系列函数,使用 c.Get("key") 提取数据。
  • 3. 中间件 (internal/app/middleware)

    • auth.go
      • 迁移到 Echo 中间件格式。
      • 使用 controller.SendErrorWithStatus 在认证失败时返回 401500 HTTP状态码。
    • audit.go
      • 极大简化并迁移到 Echo 中间件格式
      • 移除所有响应体捕获和解析的逻辑 (bodyLogWriter, auditResponse 等)。
      • next(c) 调用后,直接从 echo.Context 中获取response.go 设置好的最终审计状态和结果详情。
  • 4. 控制器 (internal/app/controller/...)

    • 通用修改:对所有控制器文件执行以下操作:
      • import "github.com/gin-gonic/gin" 替换为 import "github.com/labstack/echo/v4"
      • 将所有处理函数签名从 func(c *gin.Context) 修改为 func(c echo.Context) error
      • c.ShouldBindJSON(&req)c.ShouldBindQuery(&req) 替换为 if err := c.Bind(&req); err != nil { ... }
      • c.Param("id") 替换为 c.Param("id") (用法相同,检查返回值即可)。
      • controller.SendResponse(c, ...)controller.SendErrorResponse(c, ...) 调用修改为 return controller.SendResponse(c, ...)return controller.SendErrorResponse(c, ...)
    • 文件清单 (按依赖顺序建议):
      • internal/app/controller/management/controller_helpers.go (注意:其中的泛型辅助函数也需要修改为返回 error)
      • internal/app/controller/device/device_controller.go
      • internal/app/controller/management/pig_farm_controller.go
      • internal/app/controller/management/pig_batch_controller.go
      • internal/app/controller/management/pig_batch_health_controller.go
      • internal/app/controller/management/pig_batch_trade_controller.go
      • internal/app/controller/management/pig_batch_transfer_controller.go
      • internal/app/controller/monitor/monitor_controller.go
      • internal/app/controller/plan/plan_controller.go
      • internal/app/controller/user/user_controller.go
  • 5. DTO 结构体注解

    • 通用修改规则

      • json:"..." 标签保持不变。
      • example:"..." 标签保持不变。
      • binding:"required" 替换为 validate:"required"
      • form:"field,default=value" 替换为 query:"field"default 行为需在代码中手动实现(如在 DTO 构造函数中设置默认值),标签中不再需要。
      • form:"field" 替换为 query:"field"
      • 对于 json:"...,omitempty" 的字段,在 validate 标签中也添加 omitempty
      • 对于结构体切片或数组字段,在 validate 标签中添加 dive 以递归验证切片元素。
      • 根据字段的业务含义,添加更具体的 validate 规则(例如 min=0, cron 等)。
    • 文件清单 (按 internal/app/dto 目录下的文件顺序)

      • internal/app/dto/plan_dto.go
        • ListPlansQuery.PlanType: form:"planType,default=自定义任务" -> query:"planType"
        • ListPlansQuery.Page: form:"page,default=1" -> query:"page"
        • ListPlansQuery.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • CreatePlanRequest.Name: binding:"required" -> validate:"required"
        • CreatePlanRequest.ExecutionType: binding:"required" -> validate:"required"
        • CreatePlanRequest.ExecuteNum: 添加 validate:"omitempty,min=0"
        • CreatePlanRequest.CronExpression: 添加 validate:"omitempty,cron"
        • CreatePlanRequest.SubPlanIDs: 添加 validate:"omitempty,dive"
        • CreatePlanRequest.Tasks: 添加 validate:"omitempty,dive"
        • UpdatePlanRequest.ExecutionType: binding:"required" -> validate:"required"
        • UpdatePlanRequest.ExecuteNum: 添加 validate:"omitempty,min=0"
        • UpdatePlanRequest.CronExpression: 添加 validate:"omitempty,cron"
        • UpdatePlanRequest.SubPlanIDs: 添加 validate:"omitempty,dive"
        • UpdatePlanRequest.Tasks: 添加 validate:"omitempty,dive"
      • internal/app/dto/user_dto.go
        • CreateUserRequest.Username: binding:"required" -> validate:"required"
        • CreateUserRequest.Password: binding:"required" -> validate:"required"
        • LoginRequest.Identifier: binding:"required" -> validate:"required"
        • LoginRequest.Password: binding:"required" -> validate:"required"
      • internal/app/dto/device_dto.go
        • CreateDeviceRequest.Name: binding:"required" -> validate:"required"
        • CreateDeviceRequest.DeviceTemplateID: binding:"required" -> validate:"required"
        • CreateDeviceRequest.AreaControllerID: binding:"required" -> validate:"required"
        • CreateDeviceRequest.Location: json:"location,omitempty" -> validate:"omitempty"
        • CreateDeviceRequest.Properties: json:"properties,omitempty" -> validate:"omitempty"
        • UpdateDeviceRequest.Name: binding:"required" -> validate:"required"
        • UpdateDeviceRequest.DeviceTemplateID: binding:"required" -> validate:"required"
        • UpdateDeviceRequest.AreaControllerID: binding:"required" -> validate:"required"
        • UpdateDeviceRequest.Location: json:"location,omitempty" -> validate:"omitempty"
        • UpdateDeviceRequest.Properties: json:"properties,omitempty" -> validate:"omitempty"
        • CreateAreaControllerRequest.Name: binding:"required" -> validate:"required"
        • CreateAreaControllerRequest.NetworkID: binding:"required" -> validate:"required"
        • CreateAreaControllerRequest.Location: json:"location,omitempty" -> validate:"omitempty"
        • CreateAreaControllerRequest.Properties: json:"properties,omitempty" -> validate:"omitempty"
        • UpdateAreaControllerRequest.Name: binding:"required" -> validate:"required"
        • UpdateAreaControllerRequest.NetworkID: binding:"required" -> validate:"required"
        • UpdateAreaControllerRequest.Location: json:"location,omitempty" -> validate:"omitempty"
        • UpdateAreaControllerRequest.Properties: json:"properties,omitempty" -> validate:"omitempty"
        • CreateDeviceTemplateRequest.Name: binding:"required" -> validate:"required"
        • CreateDeviceTemplateRequest.Manufacturer: json:"manufacturer,omitempty" -> validate:"omitempty"
        • CreateDeviceTemplateRequest.Description: json:"description,omitempty" -> validate:"omitempty"
        • CreateDeviceTemplateRequest.Category: binding:"required" -> validate:"required"
        • CreateDeviceTemplateRequest.Commands: binding:"required" -> validate:"required"
        • CreateDeviceTemplateRequest.Values: json:"values,omitempty" -> validate:"omitempty,dive"
        • UpdateDeviceTemplateRequest.Name: binding:"required" -> validate:"required"
        • UpdateDeviceTemplateRequest.Manufacturer: json:"manufacturer,omitempty" -> validate:"omitempty"
        • UpdateDeviceTemplateRequest.Description: json:"description,omitempty" -> validate:"omitempty"
        • UpdateDeviceTemplateRequest.Category: binding:"required" -> validate:"required"
        • UpdateDeviceTemplateRequest.Commands: binding:"required" -> validate:"required"
        • UpdateDeviceTemplateRequest.Values: json:"values,omitempty" -> validate:"omitempty,dive"
      • internal/app/dto/monitor_dto.go
        • ListSensorDataRequest.Page: form:"page,default=1" -> query:"page"
        • ListSensorDataRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListSensorDataRequest.DeviceID: form:"device_id" -> query:"device_id"
        • ListSensorDataRequest.SensorType: form:"sensor_type" -> query:"sensor_type"
        • ListSensorDataRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListSensorDataRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListSensorDataRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListDeviceCommandLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListDeviceCommandLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListDeviceCommandLogRequest.DeviceID: form:"device_id" -> query:"device_id"
        • ListDeviceCommandLogRequest.ReceivedSuccess: form:"received_success" -> query:"received_success"
        • ListDeviceCommandLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListDeviceCommandLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListDeviceCommandLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPlanExecutionLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListPlanExecutionLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPlanExecutionLogRequest.PlanID: form:"plan_id" -> query:"plan_id"
        • ListPlanExecutionLogRequest.Status: form:"status" -> query:"status"
        • ListPlanExecutionLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPlanExecutionLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPlanExecutionLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListTaskExecutionLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListTaskExecutionLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListTaskExecutionLogRequest.PlanExecutionLogID: form:"plan_execution_log_id" -> query:"plan_execution_log_id"
        • ListTaskExecutionLogRequest.TaskID: form:"task_id" -> query:"task_id"
        • ListTaskExecutionLogRequest.Status: form:"status" -> query:"status"
        • ListTaskExecutionLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListTaskExecutionLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListTaskExecutionLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPendingCollectionRequest.Page: form:"page,default=1" -> query:"page"
        • ListPendingCollectionRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPendingCollectionRequest.DeviceID: form:"device_id" -> query:"device_id"
        • ListPendingCollectionRequest.Status: form:"status" -> query:"status"
        • ListPendingCollectionRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPendingCollectionRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPendingCollectionRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListUserActionLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListUserActionLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListUserActionLogRequest.UserID: form:"user_id" -> query:"user_id"
        • ListUserActionLogRequest.Username: form:"username" -> query:"username"
        • ListUserActionLogRequest.ActionType: form:"action_type" -> query:"action_type"
        • ListUserActionLogRequest.Status: form:"status" -> query:"status"
        • ListUserActionLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListUserActionLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListUserActionLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListRawMaterialPurchaseRequest.Page: form:"page,default=1" -> query:"page"
        • ListRawMaterialPurchaseRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListRawMaterialPurchaseRequest.RawMaterialID: form:"raw_material_id" -> query:"raw_material_id"
        • ListRawMaterialPurchaseRequest.Supplier: form:"supplier" -> query:"supplier"
        • ListRawMaterialPurchaseRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListRawMaterialPurchaseRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListRawMaterialPurchaseRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListRawMaterialStockLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListRawMaterialStockLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListRawMaterialStockLogRequest.RawMaterialID: form:"raw_material_id" -> query:"raw_material_id"
        • ListRawMaterialStockLogRequest.SourceType: form:"source_type" -> query:"source_type"
        • ListRawMaterialStockLogRequest.SourceID: form:"source_id" -> query:"source_id"
        • ListRawMaterialStockLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListRawMaterialStockLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListRawMaterialStockLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListFeedUsageRecordRequest.Page: form:"page,default=1" -> query:"page"
        • ListFeedUsageRecordRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListFeedUsageRecordRequest.PenID: form:"pen_id" -> query:"pen_id"
        • ListFeedUsageRecordRequest.FeedFormulaID: form:"feed_formula_id" -> query:"feed_formula_id"
        • ListFeedUsageRecordRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListFeedUsageRecordRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListFeedUsageRecordRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListFeedUsageRecordRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListMedicationLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListMedicationLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListMedicationLogRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListMedicationLogRequest.MedicationID: form:"medication_id" -> query:"medication_id"
        • ListMedicationLogRequest.Reason: form:"reason" -> query:"reason"
        • ListMedicationLogRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListMedicationLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListMedicationLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListMedicationLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPigBatchLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListPigBatchLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPigBatchLogRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListPigBatchLogRequest.ChangeType: form:"change_type" -> query:"change_type"
        • ListPigBatchLogRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListPigBatchLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPigBatchLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPigBatchLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListWeighingBatchRequest.Page: form:"page,default=1" -> query:"page"
        • ListWeighingBatchRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListWeighingBatchRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListWeighingBatchRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListWeighingBatchRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListWeighingBatchRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListWeighingRecordRequest.Page: form:"page,default=1" -> query:"page"
        • ListWeighingRecordRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListWeighingRecordRequest.WeighingBatchID: form:"weighing_batch_id" -> query:"weighing_batch_id"
        • ListWeighingRecordRequest.PenID: form:"pen_id" -> query:"pen_id"
        • ListWeighingRecordRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListWeighingRecordRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListWeighingRecordRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListWeighingRecordRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPigTransferLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListPigTransferLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPigTransferLogRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListPigTransferLogRequest.PenID: form:"pen_id" -> query:"pen_id"
        • ListPigTransferLogRequest.TransferType: form:"transfer_type" -> query:"transfer_type"
        • ListPigTransferLogRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListPigTransferLogRequest.CorrelationID: form:"correlation_id" -> query:"correlation_id"
        • ListPigTransferLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPigTransferLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPigTransferLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPigSickLogRequest.Page: form:"page,default=1" -> query:"page"
        • ListPigSickLogRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPigSickLogRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListPigSickLogRequest.PenID: form:"pen_id" -> query:"pen_id"
        • ListPigSickLogRequest.Reason: form:"reason" -> query:"reason"
        • ListPigSickLogRequest.TreatmentLocation: form:"treatment_location" -> query:"treatment_location"
        • ListPigSickLogRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListPigSickLogRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPigSickLogRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPigSickLogRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPigPurchaseRequest.Page: form:"page,default=1" -> query:"page"
        • ListPigPurchaseRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPigPurchaseRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListPigPurchaseRequest.Supplier: form:"supplier" -> query:"supplier"
        • ListPigPurchaseRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListPigPurchaseRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPigPurchaseRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPigPurchaseRequest.OrderBy: form:"order_by" -> query:"order_by"
        • ListPigSaleRequest.Page: form:"page,default=1" -> query:"page"
        • ListPigSaleRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListPigSaleRequest.PigBatchID: form:"pig_batch_id" -> query:"pig_batch_id"
        • ListPigSaleRequest.Buyer: form:"buyer" -> query:"buyer"
        • ListPigSaleRequest.OperatorID: form:"operator_id" -> query:"operator_id"
        • ListPigSaleRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListPigSaleRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListPigSaleRequest.OrderBy: form:"order_by" -> query:"order_by"
      • internal/app/dto/pig_farm_dto.go
        • CreatePigHouseRequest.Name: binding:"required" -> validate:"required"
        • UpdatePigHouseRequest.Name: binding:"required" -> validate:"required"
        • CreatePenRequest.PenNumber: binding:"required" -> validate:"required"
        • CreatePenRequest.HouseID: binding:"required" -> validate:"required"
        • CreatePenRequest.Capacity: binding:"required" -> validate:"required"
        • UpdatePenRequest.PenNumber: binding:"required" -> validate:"required"
        • UpdatePenRequest.HouseID: binding:"required" -> validate:"required"
        • UpdatePenRequest.Capacity: binding:"required" -> validate:"required"
        • UpdatePenRequest.Status: binding:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中" -> validate:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中"
        • UpdatePenStatusRequest.Status: binding:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中" -> validate:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中"
      • internal/app/dto/pig_batch_dto.go
        • PigBatchCreateDTO.BatchNumber: binding:"required" -> validate:"required"
        • PigBatchCreateDTO.OriginType: binding:"required" -> validate:"required"
        • PigBatchCreateDTO.StartDate: binding:"required" -> validate:"required"
        • PigBatchCreateDTO.InitialCount: binding:"required,min=1" -> validate:"required,min=1"
        • PigBatchCreateDTO.Status: binding:"required" -> validate:"required"
        • PigBatchQueryDTO.IsActive: form:"is_active" -> query:"is_active"
        • AssignEmptyPensToBatchRequest.PenIDs: binding:"required,min=1" -> validate:"required,min=1,dive"
        • ReclassifyPenToNewBatchRequest.ToBatchID: binding:"required" -> validate:"required"
        • ReclassifyPenToNewBatchRequest.PenID: binding:"required" -> validate:"required"
        • RemoveEmptyPenFromBatchRequest.PenID: binding:"required" -> validate:"required"
        • MovePigsIntoPenRequest.ToPenID: binding:"required" -> validate:"required"
        • MovePigsIntoPenRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • SellPigsRequest.PenID: binding:"required" -> validate:"required"
        • SellPigsRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • SellPigsRequest.UnitPrice: binding:"required,min=0" -> validate:"required,min=0"
        • SellPigsRequest.TotalPrice: binding:"required,min=0" -> validate:"required,min=0"
        • SellPigsRequest.TraderName: binding:"required" -> validate:"required"
        • SellPigsRequest.TradeDate: binding:"required" -> validate:"required"
        • BuyPigsRequest.PenID: binding:"required" -> validate:"required"
        • BuyPigsRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • BuyPigsRequest.UnitPrice: binding:"required,min=0" -> validate:"required,min=0"
        • BuyPigsRequest.TotalPrice: binding:"required,min=0" -> validate:"required,min=0"
        • BuyPigsRequest.TraderName: binding:"required" -> validate:"required"
        • BuyPigsRequest.TradeDate: binding:"required" -> validate:"required"
        • TransferPigsAcrossBatchesRequest.DestBatchID: binding:"required" -> validate:"required"
        • TransferPigsAcrossBatchesRequest.FromPenID: binding:"required" -> validate:"required"
        • TransferPigsAcrossBatchesRequest.ToPenID: binding:"required" -> validate:"required"
        • TransferPigsAcrossBatchesRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • TransferPigsWithinBatchRequest.FromPenID: binding:"required" -> validate:"required"
        • TransferPigsWithinBatchRequest.ToPenID: binding:"required" -> validate:"required"
        • TransferPigsWithinBatchRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordSickPigsRequest.PenID: binding:"required" -> validate:"required"
        • RecordSickPigsRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordSickPigsRequest.TreatmentLocation: binding:"required" -> validate:"required"
        • RecordSickPigsRequest.HappenedAt: binding:"required" -> validate:"required"
        • RecordSickPigRecoveryRequest.PenID: binding:"required" -> validate:"required"
        • RecordSickPigRecoveryRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordSickPigRecoveryRequest.TreatmentLocation: binding:"required" -> validate:"required"
        • RecordSickPigRecoveryRequest.HappenedAt: binding:"required" -> validate:"required"
        • RecordSickPigDeathRequest.PenID: binding:"required" -> validate:"required"
        • RecordSickPigDeathRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordSickPigDeathRequest.TreatmentLocation: binding:"required" -> validate:"required"
        • RecordSickPigDeathRequest.HappenedAt: binding:"required" -> validate:"required"
        • RecordSickPigCullRequest.PenID: binding:"required" -> validate:"required"
        • RecordSickPigCullRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordSickPigCullRequest.TreatmentLocation: binding:"required" -> validate:"required"
        • RecordSickPigCullRequest.HappenedAt: binding:"required" -> validate:"required"
        • RecordDeathRequest.PenID: binding:"required" -> validate:"required"
        • RecordDeathRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordDeathRequest.HappenedAt: binding:"required" -> validate:"required"
        • RecordCullRequest.PenID: binding:"required" -> validate:"required"
        • RecordCullRequest.Quantity: binding:"required,min=1" -> validate:"required,min=1"
        • RecordCullRequest.HappenedAt: binding:"required" -> validate:"required"
      • internal/app/dto/notification_dto.go
        • SendTestNotificationRequest.Type: binding:"required" -> validate:"required"
        • ListNotificationRequest.Page: form:"page,default=1" -> query:"page"
        • ListNotificationRequest.PageSize: form:"pageSize,default=10" -> query:"pageSize"
        • ListNotificationRequest.UserID: form:"user_id" -> query:"user_id"
        • ListNotificationRequest.NotifierType: form:"notifier_type" -> query:"notifier_type"
        • ListNotificationRequest.Status: form:"status" -> query:"status"
        • ListNotificationRequest.Level: form:"level" -> query:"level"
        • ListNotificationRequest.StartTime: form:"start_time" -> query:"start_time"
        • ListNotificationRequest.EndTime: form:"end_time" -> query:"end_time"
        • ListNotificationRequest.OrderBy: form:"order_by" -> query:"order_by"
      • internal/app/dto/plan_converter.go (跳过,非 DTO 结构体)
      • internal/app/dto/device_converter.go (跳过,非 DTO 结构体)
      • internal/app/dto/monitor_converter.go (跳过,非 DTO 结构体)
      • internal/app/dto/notification_converter.go (跳过,非 DTO 结构体)
  • 6. 核心 API 层 (internal/app/api)

    • router.go
      • 将所有 router.GET, router.POST 等 Gin 路由注册方法替换为 Echo 的 e.GET, e.POST 等方法。
      • 将 Swagger 路由 router.GET("/swagger/*", ginSwagger.WrapHandler(swaggerFiles.Handler)) 替换为 e.GET("/swagger/*", echoSwagger.WrapHandler)
      • 将 pprof 路由的 gin.WrapHgin.WrapF 调用替换为 echo.WrapHandlerecho.WrapFunc
    • api.go
      • engine *gin.Engine 替换为 engine *echo.Echo
      • 更新 NewAPI 函数:
        • gin.SetMode(cfg.Mode) 替换为 e.Debug = (cfg.Mode == "debug")
        • gin.New() 替换为 echo.New()
        • engine.Use(gin.Recovery()) 替换为 e.Use(middleware.Recover())
  • 7. 依赖管理

    • go.mod 中移除 github.com/gin-gonic/gin
    • go.mod 中移除 github.com/swaggo/gin-swagger
    • go.mod 中添加 github.com/labstack/echo/v4
    • go.mod 中添加 github.com/swaggo/echo-swagger
    • 执行 go mod tidy 清理依赖项。
  • 8. 验证

    • 运行 go build ./... 确保项目能够成功编译。
    • 启动服务,手动测试所有 API 端点,验证功能是否与迁移前一致。
    • 访问 /swagger/index.html,确认 Swagger UI 是否正常工作。
    • (可选) 访问 /debug/pprof/,确认 pprof 路由是否正常。