Files
pig-farm-controller/internal/app/controller/management/controller_helpers.go
2025-10-23 11:52:08 +08:00

240 lines
8.7 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 management
import (
"errors"
"fmt"
"strconv"
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
"github.com/gin-gonic/gin"
)
// mapAndSendError 统一映射服务层错误并发送响应。
// 这个函数将服务层返回的错误转换为控制器层应返回的HTTP状态码和审计信息。
func mapAndSendError(c *PigBatchController, ctx *gin.Context, action string, err error, id uint) {
if errors.Is(err, service.ErrPigBatchNotFound) ||
errors.Is(err, service.ErrPenNotFound) ||
errors.Is(err, service.ErrPenNotAssociatedWithBatch) {
controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), id)
} else if errors.Is(err, service.ErrInvalidOperation) ||
errors.Is(err, service.ErrPigBatchActive) ||
errors.Is(err, service.ErrPigBatchNotActive) ||
errors.Is(err, service.ErrPenOccupiedByOtherBatch) ||
errors.Is(err, service.ErrPenStatusInvalidForAllocation) ||
errors.Is(err, service.ErrPenNotEmpty) {
controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), id)
} else {
c.logger.Errorf("操作[%s]业务逻辑失败: %v", action, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, fmt.Sprintf("操作失败: %v", err), action, err.Error(), id)
}
}
// idExtractorFunc 定义了一个函数类型用于从gin.Context中提取主ID。
type idExtractorFunc func(ctx *gin.Context) (uint, error)
// extractOperatorAndPrimaryID 封装了从gin.Context中提取操作员ID和主ID的通用逻辑。
// 它负责处理ID提取过程中的错误并发送相应的HTTP响应。
//
// 参数:
//
// c: *PigBatchController - 控制器实例,用于访问其日志。
// ctx: *gin.Context - Gin上下文。
// action: string - 当前操作的描述,用于日志和审计。
// idExtractor: idExtractorFunc - 可选函数用于从ctx中提取主ID。如果为nil则尝试从":id"路径参数中提取。
//
// 返回值:
//
// operatorID: uint - 提取到的操作员ID。
// primaryID: uint - 提取到的主ID。
// ok: bool - 如果ID提取成功且没有发送错误响应则为true。
func extractOperatorAndPrimaryID(
c *PigBatchController,
ctx *gin.Context,
action string,
idExtractor idExtractorFunc,
) (operatorID uint, primaryID uint, ok bool) {
// 1. 获取操作员ID
operatorID, err := controller.GetOperatorIDFromContext(ctx)
if err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeUnauthorized, "未授权", action, "无法获取操作员ID", nil)
return 0, 0, false
}
// 2. 提取主ID
if idExtractor != nil {
primaryID, err = idExtractor(ctx)
if err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", err.Error())
return 0, 0, false
}
} else { // 默认从 ":id" 路径参数提取
idParam := ctx.Param("id")
if idParam == "" { // 有些端点可能没有 "id" 参数,例如列表或创建操作
// 如果没有ID参数且没有自定义提取器primaryID保持为0这对于某些操作是可接受的
} else {
parsedID, err := strconv.ParseUint(idParam, 10, 32)
if err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", idParam)
return 0, 0, false
}
primaryID = uint(parsedID)
}
}
return operatorID, primaryID, true
}
// handleAPIRequest 封装了控制器中处理带有请求体和路径参数的API请求的通用逻辑。
// 它负责请求体绑定、操作员ID获取、服务层调用、错误映射和响应发送。
func handleAPIRequest[Req any](
c *PigBatchController,
ctx *gin.Context,
action string,
reqDTO Req,
serviceExecutor func(ctx *gin.Context, operatorID uint, primaryID uint, req Req) error,
successMsg string,
idExtractor idExtractorFunc,
) {
// 1. 绑定请求体
if err := ctx.ShouldBindJSON(&reqDTO); err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", reqDTO)
return
}
// 2. 提取操作员ID和主ID
operatorID, primaryID, ok := extractOperatorAndPrimaryID(c, ctx, action, idExtractor)
if !ok {
return // 错误已在 extractOperatorAndPrimaryID 中处理
}
// 3. 执行服务层逻辑
err := serviceExecutor(ctx, operatorID, primaryID, reqDTO)
if err != nil {
mapAndSendError(c, ctx, action, err, primaryID)
return
}
// 4. 发送成功响应
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, successMsg, nil, action, successMsg, primaryID)
}
// handleNoBodyAPIRequest 封装了处理不带请求体但有路径参数和操作员ID的API请求的通用逻辑。
func handleNoBodyAPIRequest(
c *PigBatchController,
ctx *gin.Context,
action string,
serviceExecutor func(ctx *gin.Context, operatorID uint, primaryID uint) error,
successMsg string,
idExtractor idExtractorFunc,
) {
// 1. 提取操作员ID和主ID
operatorID, primaryID, ok := extractOperatorAndPrimaryID(c, ctx, action, idExtractor)
if !ok {
return // 错误已在 extractOperatorAndPrimaryID 中处理
}
// 2. 执行服务层逻辑
err := serviceExecutor(ctx, operatorID, primaryID)
if err != nil {
mapAndSendError(c, ctx, action, err, primaryID)
return
}
// 3. 发送成功响应
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, successMsg, nil, action, successMsg, primaryID)
}
// handleAPIRequestWithResponse 封装了控制器中处理带有请求体、路径参数并返回响应DTO的API请求的通用逻辑。
func handleAPIRequestWithResponse[Req any, Resp any](
c *PigBatchController,
ctx *gin.Context,
action string,
reqDTO Req,
serviceExecutor func(ctx *gin.Context, operatorID uint, primaryID uint, req Req) (Resp, error), // serviceExecutor现在返回Resp
successMsg string,
idExtractor idExtractorFunc,
) {
// 1. 绑定请求体
if err := ctx.ShouldBindJSON(&reqDTO); err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, fmt.Sprintf("无效的请求体: %v", err), action, fmt.Sprintf("请求体绑定失败: %v", err), reqDTO)
return
}
// 2. 提取操作员ID和主ID
operatorID, primaryID, ok := extractOperatorAndPrimaryID(c, ctx, action, idExtractor)
if !ok {
return // 错误已在 extractOperatorAndPrimaryID 中处理
}
// 3. 执行服务层逻辑
respDTO, err := serviceExecutor(ctx, operatorID, primaryID, reqDTO)
if err != nil {
mapAndSendError(c, ctx, action, err, primaryID)
return
}
// 4. 发送成功响应
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, successMsg, respDTO, action, successMsg, primaryID)
}
// handleNoBodyAPIRequestWithResponse 封装了处理不带请求体但有路径参数和操作员ID并返回响应DTO的API请求的通用逻辑。
func handleNoBodyAPIRequestWithResponse[Resp any](
c *PigBatchController,
ctx *gin.Context,
action string,
serviceExecutor func(ctx *gin.Context, operatorID uint, primaryID uint) (Resp, error), // serviceExecutor现在返回Resp
successMsg string,
idExtractor idExtractorFunc,
) {
// 1. 提取操作员ID和主ID
operatorID, primaryID, ok := extractOperatorAndPrimaryID(c, ctx, action, idExtractor)
if !ok {
return // 错误已在 extractOperatorAndPrimaryID 中处理
}
// 2. 执行服务层逻辑
respDTO, err := serviceExecutor(ctx, operatorID, primaryID)
if err != nil {
mapAndSendError(c, ctx, action, err, primaryID)
return
}
// 3. 发送成功响应
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, successMsg, respDTO, action, successMsg, primaryID)
}
// handleQueryAPIRequestWithResponse 封装了处理带有查询参数并返回响应DTO的API请求的通用逻辑。
func handleQueryAPIRequestWithResponse[Query any, Resp any](
c *PigBatchController,
ctx *gin.Context,
action string,
queryDTO Query,
serviceExecutor func(ctx *gin.Context, operatorID uint, query Query) (Resp, error), // serviceExecutor现在接收queryDTO
successMsg string,
) {
// 1. 绑定查询参数
if err := ctx.ShouldBindQuery(&queryDTO); err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数", action, "查询参数绑定失败", queryDTO)
return
}
// 2. 获取操作员ID
operatorID, err := controller.GetOperatorIDFromContext(ctx)
if err != nil {
controller.SendErrorWithAudit(ctx, controller.CodeUnauthorized, "未授权", action, "无法获取操作员ID", nil)
return
}
// 3. 执行服务层逻辑
respDTO, err := serviceExecutor(ctx, operatorID, queryDTO)
if err != nil {
// 对于列表查询通常没有primaryID所以传递0
mapAndSendError(c, ctx, action, err, 0)
return
}
// 4. 发送成功响应
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, successMsg, respDTO, action, successMsg, nil)
}