issue_36 #47
@@ -63,13 +63,15 @@ func SendErrorWithStatus(c echo.Context, httpStatus int, code ResponseCode, mess
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// --- 带审计功能的响应函数 ---
 | 
					// --- 带审计功能的响应函数 ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setAuditDetails 是一个内部辅助函数,用于在 echo.Context 中设置业务相关的审计信息。
 | 
					// setAuditDetails 是一个内部辅助函数,用于在 echo.Context 中统一设置所有业务相关的审计信息。
 | 
				
			||||||
func setAuditDetails(c echo.Context, actionType, description string, targetResource interface{}) {
 | 
					func setAuditDetails(c echo.Context, actionType, description string, targetResource interface{}, status models.AuditStatus, resultDetails string) {
 | 
				
			||||||
	// 只有当 actionType 不为空时,才设置审计信息,这作为触发审计的标志
 | 
						// 只有当 actionType 不为空时,才设置审计信息,这作为触发审计的标志
 | 
				
			||||||
	if actionType != "" {
 | 
						if actionType != "" {
 | 
				
			||||||
		c.Set(models.ContextAuditActionType.String(), actionType)
 | 
							c.Set(models.ContextAuditActionType.String(), actionType)
 | 
				
			||||||
		c.Set(models.ContextAuditDescription.String(), description)
 | 
							c.Set(models.ContextAuditDescription.String(), description)
 | 
				
			||||||
		c.Set(models.ContextAuditTargetResource.String(), targetResource)
 | 
							c.Set(models.ContextAuditTargetResource.String(), targetResource)
 | 
				
			||||||
 | 
							c.Set(models.ContextAuditStatus.String(), status)
 | 
				
			||||||
 | 
							c.Set(models.ContextAuditResultDetails.String(), resultDetails)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,7 +87,7 @@ func SendSuccessWithAudit(
 | 
				
			|||||||
	targetResource interface{}, // 审计目标资源,被操作的资源对象或其标识
 | 
						targetResource interface{}, // 审计目标资源,被操作的资源对象或其标识
 | 
				
			||||||
) error {
 | 
					) error {
 | 
				
			||||||
	// 1. 设置审计信息
 | 
						// 1. 设置审计信息
 | 
				
			||||||
	setAuditDetails(c, actionType, description, targetResource)
 | 
						setAuditDetails(c, actionType, description, targetResource, models.AuditStatusSuccess, "")
 | 
				
			||||||
	// 2. 发送响应
 | 
						// 2. 发送响应
 | 
				
			||||||
	return SendResponse(c, code, message, data)
 | 
						return SendResponse(c, code, message, data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -101,7 +103,7 @@ func SendErrorWithAudit(
 | 
				
			|||||||
	targetResource interface{}, // 审计目标资源,被操作的资源对象或其标识
 | 
						targetResource interface{}, // 审计目标资源,被操作的资源对象或其标识
 | 
				
			||||||
) error {
 | 
					) error {
 | 
				
			||||||
	// 1. 设置审计信息
 | 
						// 1. 设置审计信息
 | 
				
			||||||
	setAuditDetails(c, actionType, description, targetResource)
 | 
						setAuditDetails(c, actionType, description, targetResource, models.AuditStatusFailed, message)
 | 
				
			||||||
	// 2. 发送响应
 | 
						// 2. 发送响应
 | 
				
			||||||
	return SendErrorResponse(c, code, message)
 | 
						return SendErrorResponse(c, code, message)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,117 +1,59 @@
 | 
				
			|||||||
package middleware
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/labstack/echo/v4"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type auditResponse struct {
 | 
					// AuditLogMiddleware 创建一个Echo中间件,用于在请求结束后记录用户操作审计日志。
 | 
				
			||||||
	Code    int    `json:"code"`
 | 
					// 它依赖于控制器通过调用 SendSuccessWithAudit 或 SendErrorWithAudit 在上下文中设置的审计信息。
 | 
				
			||||||
	Message string `json:"message"`
 | 
					func AuditLogMiddleware(auditService audit.Service) echo.MiddlewareFunc {
 | 
				
			||||||
}
 | 
						return func(next echo.HandlerFunc) echo.HandlerFunc {
 | 
				
			||||||
 | 
							return func(c echo.Context) error {
 | 
				
			||||||
 | 
								// 首先执行请求链中的后续处理程序(即业务控制器)
 | 
				
			||||||
 | 
								err := next(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AuditLogMiddleware 创建一个Gin中间件,用于在请求结束后记录用户操作审计日志
 | 
								// --- 在这里,请求已经处理完毕 ---
 | 
				
			||||||
func AuditLogMiddleware(auditService audit.Service) gin.HandlerFunc {
 | 
					 | 
				
			||||||
	return func(c *gin.Context) {
 | 
					 | 
				
			||||||
		// 使用自定义的 response body writer 来捕获响应体
 | 
					 | 
				
			||||||
		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
 | 
					 | 
				
			||||||
		c.Writer = blw
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 首先执行请求链中的后续处理程序(即业务控制器)
 | 
								// 从上下文中尝试获取由控制器设置的业务审计信息
 | 
				
			||||||
		c.Next()
 | 
								actionType, exists := c.Get(models.ContextAuditActionType.String()).(string)
 | 
				
			||||||
 | 
								if !exists || actionType == "" {
 | 
				
			||||||
		// --- 在这里,请求已经处理完毕 ---
 | 
									// 如果上下文中没有 actionType,说明此接口无需记录审计日志,直接返回
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
		// 从上下文中尝试获取由控制器设置的业务审计信息
 | 
					 | 
				
			||||||
		actionType, exists := c.Get(models.ContextAuditActionType.String())
 | 
					 | 
				
			||||||
		if !exists {
 | 
					 | 
				
			||||||
			// 如果上下文中没有 actionType,说明此接口无需记录审计日志,直接返回
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 从 Gin Context 中获取用户对象
 | 
					 | 
				
			||||||
		userCtx, userExists := c.Get(models.ContextUserKey.String())
 | 
					 | 
				
			||||||
		var user *models.User
 | 
					 | 
				
			||||||
		if userExists {
 | 
					 | 
				
			||||||
			user, _ = userCtx.(*models.User)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 构建 RequestContext
 | 
					 | 
				
			||||||
		reqCtx := audit.RequestContext{
 | 
					 | 
				
			||||||
			ClientIP:   c.ClientIP(),
 | 
					 | 
				
			||||||
			HTTPPath:   c.Request.URL.Path,
 | 
					 | 
				
			||||||
			HTTPMethod: c.Request.Method,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 获取其他审计信息
 | 
					 | 
				
			||||||
		description, _ := c.Get(models.ContextAuditDescription.String())
 | 
					 | 
				
			||||||
		targetResource, _ := c.Get(models.ContextAuditTargetResource.String())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 默认操作状态为成功
 | 
					 | 
				
			||||||
		status := models.AuditStatusSuccess
 | 
					 | 
				
			||||||
		resultDetails := ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 尝试从捕获的响应体中解析平台响应
 | 
					 | 
				
			||||||
		var platformResponse auditResponse
 | 
					 | 
				
			||||||
		if err := json.Unmarshal(blw.body.Bytes(), &platformResponse); err == nil {
 | 
					 | 
				
			||||||
			// 如果解析成功,根据平台状态码判断操作是否失败
 | 
					 | 
				
			||||||
			// 成功状态码范围是 2000-2999
 | 
					 | 
				
			||||||
			if platformResponse.Code < 2000 || platformResponse.Code >= 3000 {
 | 
					 | 
				
			||||||
				status = models.AuditStatusFailed
 | 
					 | 
				
			||||||
				resultDetails = platformResponse.Message
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			// 如果响应体不是预期的平台响应格式,或者解析失败,则记录原始HTTP状态码作为详情
 | 
					 | 
				
			||||||
			// 并且如果HTTP状态码不是2xx,则标记为失败
 | 
					 | 
				
			||||||
			if c.Writer.Status() < 200 || c.Writer.Status() >= 300 {
 | 
					 | 
				
			||||||
				status = models.AuditStatusFailed
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			resultDetails = "HTTP Status: " + strconv.Itoa(c.Writer.Status()) + ", Body Parse Error: " + err.Error()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 调用审计服务记录日志(异步)
 | 
								// 从 Context 中获取用户对象
 | 
				
			||||||
		auditService.LogAction(
 | 
								var user *models.User
 | 
				
			||||||
			user,
 | 
								if userCtx := c.Get(models.ContextUserKey.String()); userCtx != nil {
 | 
				
			||||||
			reqCtx,
 | 
									user, _ = userCtx.(*models.User)
 | 
				
			||||||
			actionType.(string),
 | 
								}
 | 
				
			||||||
			description.(string),
 | 
					
 | 
				
			||||||
			targetResource,
 | 
								// 构建 RequestContext
 | 
				
			||||||
			status,
 | 
								reqCtx := audit.RequestContext{
 | 
				
			||||||
			resultDetails,
 | 
									ClientIP:   c.RealIP(),
 | 
				
			||||||
		)
 | 
									HTTPPath:   c.Request().URL.Path,
 | 
				
			||||||
 | 
									HTTPMethod: c.Request().Method,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// 直接从上下文中获取所有其他审计信息
 | 
				
			||||||
 | 
								description, _ := c.Get(models.ContextAuditDescription.String()).(string)
 | 
				
			||||||
 | 
								targetResource := c.Get(models.ContextAuditTargetResource.String())
 | 
				
			||||||
 | 
								status, _ := c.Get(models.ContextAuditStatus.String()).(models.AuditStatus)
 | 
				
			||||||
 | 
								resultDetails, _ := c.Get(models.ContextAuditResultDetails.String()).(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// 调用审计服务记录日志(异步)
 | 
				
			||||||
 | 
								auditService.LogAction(
 | 
				
			||||||
 | 
									user,
 | 
				
			||||||
 | 
									reqCtx,
 | 
				
			||||||
 | 
									actionType,
 | 
				
			||||||
 | 
									description,
 | 
				
			||||||
 | 
									targetResource,
 | 
				
			||||||
 | 
									status,
 | 
				
			||||||
 | 
									resultDetails,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// bodyLogWriter 是一个自定义的 gin.ResponseWriter,用于捕获响应体
 | 
					 | 
				
			||||||
// 这对于在操作失败时记录详细的错误信息非常有用
 | 
					 | 
				
			||||||
type bodyLogWriter struct {
 | 
					 | 
				
			||||||
	gin.ResponseWriter
 | 
					 | 
				
			||||||
	body *bytes.Buffer
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (w bodyLogWriter) Write(b []byte) (int, error) {
 | 
					 | 
				
			||||||
	w.body.Write(b)
 | 
					 | 
				
			||||||
	return w.ResponseWriter.Write(b)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (w bodyLogWriter) WriteString(s string) (int, error) {
 | 
					 | 
				
			||||||
	w.body.WriteString(s)
 | 
					 | 
				
			||||||
	return w.ResponseWriter.WriteString(s)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ReadBody 用于安全地读取请求体,并防止其被重复读取
 | 
					 | 
				
			||||||
func ReadBody(c *gin.Context) ([]byte, error) {
 | 
					 | 
				
			||||||
	bodyBytes, err := io.ReadAll(c.Request.Body)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// 将读取的内容放回 Body 中,以便后续的处理函数可以再次读取
 | 
					 | 
				
			||||||
	c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
 | 
					 | 
				
			||||||
	return bodyBytes, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -171,7 +171,10 @@ const (
 | 
				
			|||||||
	ContextAuditActionType     AuditContextKey = "auditActionType"
 | 
						ContextAuditActionType     AuditContextKey = "auditActionType"
 | 
				
			||||||
	ContextAuditTargetResource AuditContextKey = "auditTargetResource"
 | 
						ContextAuditTargetResource AuditContextKey = "auditTargetResource"
 | 
				
			||||||
	ContextAuditDescription    AuditContextKey = "auditDescription"
 | 
						ContextAuditDescription    AuditContextKey = "auditDescription"
 | 
				
			||||||
	ContextUserKey             AuditContextKey = "user"
 | 
						ContextAuditStatus         AuditContextKey = "auditStatus"
 | 
				
			||||||
 | 
						ContextAuditResultDetails  AuditContextKey = "auditResultDetails"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ContextUserKey AuditContextKey = "user"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a AuditContextKey) String() string {
 | 
					func (a AuditContextKey) String() string {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,56 +23,45 @@
 | 
				
			|||||||
    - **From**: `gin.SetMode(cfg.Mode)`, `engine := gin.New()`, `engine.Use(gin.Recovery())`
 | 
					    - **From**: `gin.SetMode(cfg.Mode)`, `engine := gin.New()`, `engine.Use(gin.Recovery())`
 | 
				
			||||||
    - **To**: `e := echo.New()`, `e.Debug = (cfg.Mode == "debug")`, `e.Use(middleware.Recover())`
 | 
					    - **To**: `e := echo.New()`, `e.Debug = (cfg.Mode == "debug")`, `e.Use(middleware.Recover())`
 | 
				
			||||||
    - **Rationale**: `echo.New()` 提供了干净的实例。Echo 的 `Debug` 属性控制调试模式,可以根据配置设置。Echo 提供了内置的 `middleware.Recover()` 来替代 Gin 的 Recovery 中间件。
 | 
					    - **Rationale**: `echo.New()` 提供了干净的实例。Echo 的 `Debug` 属性控制调试模式,可以根据配置设置。Echo 提供了内置的 `middleware.Recover()` 来替代 Gin 的 Recovery 中间件。
 | 
				
			||||||
    - **Implementation**: 
 | 
					 | 
				
			||||||
        - 在 `internal/app/api/api.go` 中,将 `engine *gin.Engine` 替换为 `engine *echo.Echo`,并更新 `NewAPI` 方法中的初始化逻辑。
 | 
					 | 
				
			||||||
        - 在 `config.yml` 和 `config.example.yml` 中, 更新关于 `mode` 配置项的注释, 将 "Gin 运行模式" 修改为 "服务运行模式", 因为该配置项现在控制 Echo 的调试模式。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
2.  **上下文对象 (Context) 与处理器签名**:
 | 
					2.  **上下文对象 (Context) 与处理器签名**:
 | 
				
			||||||
    - **From**: `func(c *gin.Context)`
 | 
					    - **From**: `func(c *gin.Context)`
 | 
				
			||||||
    - **To**: `func(c echo.Context) error`
 | 
					    - **To**: `func(c echo.Context) error`
 | 
				
			||||||
    - **Rationale**: 这是两个框架的核心区别。所有控制器处理函数签名都需要更新。常见方法映射如下:
 | 
					    - **Rationale**: 这是两个框架的核心区别。所有控制器处理函数签名都需要更新。常见方法映射如下:
 | 
				
			||||||
        - `ctx.ShouldBindJSON(&req)` -> `c.Bind(&req)` (Echo 的 `Bind` 更通用,能根据 `Content-Type` 自动选择解析器)
 | 
					        - `ctx.ShouldBindJSON(&req)` -> `c.Bind(&req)` (Echo 的 `Bind` 更通用)
 | 
				
			||||||
        - `ctx.ShouldBindQuery(&req)` -> `c.Bind(&req)`
 | 
					        - `ctx.Param("id")` -> `c.Param("id")`
 | 
				
			||||||
        - `ctx.Param("id")` -> `c.Param("id")` (签名相同)
 | 
					 | 
				
			||||||
        - `ctx.GetHeader("Authorization")` -> `c.Request().Header.Get("Authorization")`
 | 
					        - `ctx.GetHeader("Authorization")` -> `c.Request().Header.Get("Authorization")`
 | 
				
			||||||
        - `ctx.Set("key", value)` -> `c.Set("key", value)` (签名相同)
 | 
					        - `ctx.Set/Get("key", value)` -> `c.Set/Get("key")`
 | 
				
			||||||
        - `ctx.Get("key")` -> `c.Get("key")` (签名相同)
 | 
					 | 
				
			||||||
        - `ctx.ClientIP()` -> `c.RealIP()`
 | 
					        - `ctx.ClientIP()` -> `c.RealIP()`
 | 
				
			||||||
        - `controller.SendResponse(ctx, ...)` -> `return controller.SendResponse(c, ...)` (控制器方法需要返回 `error`,辅助函数也需要修改以返回 `error`)
 | 
					        - `controller.SendResponse(ctx, ...)` -> `return controller.SendResponse(c, ...)`
 | 
				
			||||||
        - `controller.SendErrorResponse(ctx, ...)` -> `return controller.SendErrorResponse(c, ...)` (同上)
 | 
					        - `ctx.AbortWithStatusJSON(...)` -> 对于需要返回特定HTTP状态码的场景(如认证中间件),将使用一个专门的辅助函数 `return controller.SendErrorWithStatus(c, http.StatusUnauthorized, ...)`。
 | 
				
			||||||
        - `ctx.AbortWithStatusJSON(...)` -> `return c.JSON(...)` (在中间件中,通过 `return c.JSON(...)` 来中断链并响应)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
3.  **中间件 (Middleware)**:
 | 
					3.  **中间件 (Middleware)**:
 | 
				
			||||||
    - **From**: `func AuthMiddleware(...) gin.HandlerFunc { return func(c *gin.Context) { ... } }`
 | 
					    - **From**: `func AuthMiddleware(...) gin.HandlerFunc { return func(c *gin.Context) { ... } }`
 | 
				
			||||||
    - **To**: `func AuthMiddleware(...) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ...; return next(c) } } }`
 | 
					    - **To**: `func AuthMiddleware(...) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ...; return next(c) } } }`
 | 
				
			||||||
    - **Rationale**: Echo 的中间件是一个包装器模式。我们需要将现有的 `AuthMiddleware` 和 `AuditLogMiddleware` 逻辑迁移到这个新的结构中。中断请求链的方式从 `c.AbortWithStatusJSON()` 变为从处理函数中 `return c.JSON(...)`。
 | 
					    - **Rationale**: Echo 的中间件是一个包装器模式。我们需要将现有的 `AuthMiddleware` 和 `AuditLogMiddleware` 逻辑迁移到这个新的结构中。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4.  **Swagger 集成**:
 | 
					4.  **Swagger 集成**:
 | 
				
			||||||
    - **From**: `github.com/swaggo/gin-swagger`
 | 
					    - **From**: `github.com/swaggo/gin-swagger`
 | 
				
			||||||
    - **To**: `github.com/swaggo/echo-swagger`
 | 
					    - **To**: `github.com/swaggo/echo-swagger`
 | 
				
			||||||
    - **Rationale**: 这是 `swaggo` 官方为 Echo 提供的适配库,可以无缝替换。
 | 
					    - **Rationale**: 这是 `swaggo` 官方为 Echo 提供的适配库,可以无缝替换。
 | 
				
			||||||
    - **Implementation**: 在 `router.go` 中使用 `e.GET("/swagger/*", echoSwagger.WrapHandler)`。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
5.  **Pprof 与其他 `net/http` 处理器集成**:
 | 
					5.  **Pprof 与其他 `net/http` 处理器集成**:
 | 
				
			||||||
    - **From**: `gin.WrapH` 和 `gin.WrapF`
 | 
					    - **From**: `gin.WrapH` 和 `gin.WrapF`
 | 
				
			||||||
    - **To**: `echo.WrapHandler` 和 `echo.WrapFunc`
 | 
					    - **To**: `echo.WrapHandler` 和 `echo.WrapFunc`
 | 
				
			||||||
    - **Rationale**: Echo 提供了类似的 `net/http` 处理器包装函数,可以轻松集成 pprof 和项目中的 `listenHandler`。
 | 
					    - **Rationale**: Echo 提供了类似的 `net/http` 处理器包装函数。
 | 
				
			||||||
    - **Implementation**: 在 `router.go` 中替换所有 `gin.WrapH` 和 `gin.WrapF` 的调用。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
6.  **控制器辅助函数**:
 | 
					6.  **控制器辅助函数与审计逻辑重构**:
 | 
				
			||||||
    - **Affected Files**: 
 | 
					    - **Affected Files**: `response.go`, `auth_utils.go`, `controller_helpers.go`
 | 
				
			||||||
        - `internal/app/controller/response.go`
 | 
					 | 
				
			||||||
        - `internal/app/controller/auth_utils.go`
 | 
					 | 
				
			||||||
        - `internal/app/controller/management/controller_helpers.go`
 | 
					 | 
				
			||||||
    - **Change**:
 | 
					    - **Change**:
 | 
				
			||||||
        - 在 `response.go` 和 `auth_utils.go` 中, 所有接收 `*gin.Context` 的辅助函数 (如 `SendResponse`, `GetOperatorIDFromContext` 等) 签名都需要修改为接收 `echo.Context`。
 | 
					        - 所有辅助函数中的 `*gin.Context` 都将替换为 `echo.Context`。
 | 
				
			||||||
        - 在 `controller_helpers.go` 中, `handle...` 系列的泛型辅助函数 (如 `handleAPIRequest`, `handleNoBodyAPIRequest` 等) 及其依赖的 `extractOperatorAndPrimaryID` 和 `mapAndSendError` 函数, 都需要将其中的 `*gin.Context` 参数和相关调用 (如 `ShouldBindJSON`) 替换为 `echo.Context` 的等效实现。
 | 
					        - **`response.go` 将被重构**:`setAuditDetails` 函数将成为设置所有审计信息(包括操作状态和失败详情)的唯一入口。`SendSuccessWithAudit` 和 `SendErrorWithAudit` 会调用它来将最终结果存入 `echo.Context`。
 | 
				
			||||||
        - 所有这些辅助函数, 如果它们原本不返回 `error`, 现在需要修改为返回 `error`, 以便与 Echo 的处理器错误链兼容。例如, `SendResponse` 这类函数在调用 `c.JSON(...)` 后, 最终应 `return nil`。
 | 
					        - `controller_helpers.go` 中的泛型辅助函数将修改为返回 `error`,以适配 Echo 的错误处理链。
 | 
				
			||||||
    - **Rationale**: 这些辅助函数封装了请求处理、响应发送和错误处理的核心逻辑, 必须进行适配以兼容 Echo 的 `echo.Context` 上下文对象和 `return error` 的错误处理模式。
 | 
					    - **Rationale**: 这种重构使得审计逻辑更加清晰和内聚,避免了在中间件中进行复杂的响应体捕获。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Risks / Trade-offs
 | 
					## Risks / Trade-offs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Risk**: 迁移工作量大,可能遗漏某些 Gin 特有的功能或上下文用法,导致运行时错误。
 | 
					- **Risk**: 迁移工作量大,可能遗漏某些 Gin 特有的功能或上下文用法,导致运行时错误。
 | 
				
			||||||
- **Mitigation**: 采用逐个文件、逐个控制器修改的方式,每修改完一部分就进行编译检查。在完成所有编码后,进行全面的手动 API 测试。
 | 
					- **Mitigation**: 采用逐个文件、逐个控制器修改的方式,每修改完一部分就进行编译检查。在完成所有编码后,进行全面的手动 API 测试。
 | 
				
			||||||
- **Risk**: `AuditLogMiddleware` 中间件依赖 `bodyLogWriter` 捕获响应体,需要验证其与 Echo 的 `ResponseWriter` 是否兼容或需要寻找替代方案。
 | 
					- **Risk (Resolved)**: `AuditLogMiddleware` 中间件最初的设计依赖于捕获响应体,这在 Echo 中难以实现。
 | 
				
			||||||
- **Mitigation**: 在迁移中间件时,优先研究 Echo 官方推荐的 Body Dump 或类似中间件,如果不适用,再尝试适配 `bodyLogWriter`。
 | 
					- **Resolution**: 我们通过重构 `response.go` 解决了这个问题。现在,控制器在调用响应函数时,会将最终的操作状态(成功/失败)和结果详情直接存入 `echo.Context`。`AuditLogMiddleware` 只需从上下文中读取这些信息即可,**完全消除了捕获和解析响应体的需要**,使得设计更加清晰和高效。
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,31 +5,26 @@
 | 
				
			|||||||
    - [x] 修改 `config.example.yml` 中 `mode` 配置项的注释,保持与 `config.yml` 一致。
 | 
					    - [x] 修改 `config.example.yml` 中 `mode` 配置项的注释,保持与 `config.yml` 一致。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [x] **2. 控制器辅助函数 (最基础的依赖)**
 | 
					- [x] **2. 控制器辅助函数 (最基础的依赖)**
 | 
				
			||||||
 | 
					    - [x] **`internal/infra/models/execution.go`**
 | 
				
			||||||
 | 
					        - [x] 添加 `ContextAuditStatus` 和 `ContextAuditResultDetails` 常量。
 | 
				
			||||||
    - [x] **`internal/app/controller/response.go`**
 | 
					    - [x] **`internal/app/controller/response.go`**
 | 
				
			||||||
        - [x] 将 `*gin.Context` 参数全部替换为 `echo.Context`。
 | 
					        - [x] 将 `*gin.Context` 参数全部替换为 `echo.Context`。
 | 
				
			||||||
        - [x] 修改 `SendResponse` 和 `SendErrorResponse` 等函数,使其不再直接写入响应,而是返回 `error`,并在内部调用
 | 
					        - [x] 修改响应函数,使其返回 `error`。
 | 
				
			||||||
          `c.JSON(...)`。
 | 
					        - [x] **新增 `SendErrorWithStatus` 函数**,用于在中间件等场景下发送带有特定HTTP状态码的错误响应。
 | 
				
			||||||
 | 
					        - [x] **重构 `setAuditDetails` 函数**,使其成为统一设置所有审计信息(包括操作状态和结果详情)的唯一入口。
 | 
				
			||||||
 | 
					        - [x] 更新 `SendSuccessWithAudit` 和 `SendErrorWithAudit` 以调用重构后的 `setAuditDetails`。
 | 
				
			||||||
    - [x] **`internal/app/controller/auth_utils.go`**
 | 
					    - [x] **`internal/app/controller/auth_utils.go`**
 | 
				
			||||||
        - [x] 将 `*gin.Context` 参数全部替换为 `echo.Context`。
 | 
					        - [x] 将 `*gin.Context` 参数全部替换为 `echo.Context`。
 | 
				
			||||||
        - [x] 适配 `Get...FromContext` 系列函数,使用 `c.Get("key")` 提取数据。
 | 
					        - [x] 适配 `Get...FromContext` 系列函数,使用 `c.Get("key")` 提取数据。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] **3. 中间件 (`internal/app/middleware`)**
 | 
					- [x] **3. 中间件 (`internal/app/middleware`)**
 | 
				
			||||||
    - [x] **`auth.go`**
 | 
					    - [x] **`auth.go`**
 | 
				
			||||||
        - [x] 将 `import "github.com/gin-gonic/gin"` 替换为 `import "github.com/labstack/echo/v4"`。
 | 
					        - [x] 迁移到 Echo 中间件格式。
 | 
				
			||||||
        - [x] 将中间件函数签名从 `func AuthMiddleware(...) gin.HandlerFunc` 更新为
 | 
					        - [x] **使用 `controller.SendErrorWithStatus`** 在认证失败时返回 `401` 或 `500` HTTP状态码。
 | 
				
			||||||
          `func AuthMiddleware(...) echo.MiddlewareFunc`。
 | 
					    - [x] **`audit.go`**
 | 
				
			||||||
        - [x] 适配中间件内部逻辑,将 `func(c *gin.Context)` 改造为
 | 
					        - [x] **极大简化并迁移到 Echo 中间件格式**。
 | 
				
			||||||
          `func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ... } }` 的结构。
 | 
					        - [x] **移除所有响应体捕获和解析的逻辑** (`bodyLogWriter`, `auditResponse` 等)。
 | 
				
			||||||
        - [x] 将 `c.AbortWithStatusJSON(...)` 调用替换为 `return controller.SendErrorResponse(...)`。
 | 
					        - [x] 在 `next(c)` 调用后,**直接从 `echo.Context` 中获取**由 `response.go` 设置好的最终审计状态和结果详情。
 | 
				
			||||||
        - [x] 在逻辑正常通过的末尾,调用 `return next(c)`。
 | 
					 | 
				
			||||||
    - [ ] **`audit.go`**
 | 
					 | 
				
			||||||
        - [ ] 将 `import "github.com/gin-gonic/gin"` 替换为 `import "github.com/labstack/echo/v4"`。
 | 
					 | 
				
			||||||
        - [ ] 将中间件函数签名从 `func AuditMiddleware(...) gin.HandlerFunc` 更新为
 | 
					 | 
				
			||||||
          `func AuditMiddleware(...) echo.MiddlewareFunc`。
 | 
					 | 
				
			||||||
        - [ ] 适配中间件内部逻辑,将 `func(c *gin.Context)` 改造为
 | 
					 | 
				
			||||||
          `func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ... } }` 的结构。
 | 
					 | 
				
			||||||
        - [ ] 在调用 `next(c)` 之前和之后,使用 `c.Get(...)` 来获取和处理审计信息。
 | 
					 | 
				
			||||||
        - [ ] 在逻辑正常通过的末尾,调用 `return next(c)`。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] **4. 控制器 (`internal/app/controller/...`)**
 | 
					- [ ] **4. 控制器 (`internal/app/controller/...`)**
 | 
				
			||||||
    - [ ] **通用修改**:对所有控制器文件执行以下操作:
 | 
					    - [ ] **通用修改**:对所有控制器文件执行以下操作:
 | 
				
			||||||
@@ -41,7 +36,8 @@
 | 
				
			|||||||
        - [ ] 将 `controller.SendResponse(c, ...)` 和 `controller.SendErrorResponse(c, ...)` 调用修改为
 | 
					        - [ ] 将 `controller.SendResponse(c, ...)` 和 `controller.SendErrorResponse(c, ...)` 调用修改为
 | 
				
			||||||
          `return controller.SendResponse(c, ...)` 和 `return controller.SendErrorResponse(c, ...)`。
 | 
					          `return controller.SendResponse(c, ...)` 和 `return controller.SendErrorResponse(c, ...)`。
 | 
				
			||||||
    - [ ] **文件清单** (按依赖顺序建议):
 | 
					    - [ ] **文件清单** (按依赖顺序建议):
 | 
				
			||||||
        - [ ] `internal/app/controller/management/controller_helpers.go`
 | 
					        - [ ] `internal/app/controller/management/controller_helpers.go` (注意:其中的泛型辅助函数也需要修改为返回
 | 
				
			||||||
 | 
					          `error`)
 | 
				
			||||||
        - [ ] `internal/app/controller/device/device_controller.go`
 | 
					        - [ ] `internal/app/controller/device/device_controller.go`
 | 
				
			||||||
        - [ ] `internal/app/controller/management/pig_farm_controller.go`
 | 
					        - [ ] `internal/app/controller/management/pig_farm_controller.go`
 | 
				
			||||||
        - [ ] `internal/app/controller/management/pig_batch_controller.go`
 | 
					        - [ ] `internal/app/controller/management/pig_batch_controller.go`
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user