Scert/backend/internal/httpapi/project_members_audit.go

41 lines
1.5 KiB
Go

package httpapi
import (
"context"
"time"
"github.com/gin-gonic/gin"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
"git.pcux.cn/xuans/Scert/backend/internal/repository"
)
// 失败审计覆盖会话、来源、参数及仓储拒绝;成功审计由仓储在成员变更事务内保证。
func (s *Server) projectMemberFailureAudit(action string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if c.Writer.Status() < 400 || s.projectMembers == nil {
return
}
projectID := c.Param("projectID")
if !domain.ValidProjectID(projectID) {
projectID = ""
}
target := c.GetString(projectMemberTargetKey)
if target == "" && validMemberIdentifier(c.Param("userID")) {
target = c.Param("userID")
}
// 客户端取消请求不能取消已发生失败的审计;独立等待有上限,不无限阻塞关闭。
ctx, cancel := context.WithTimeout(context.WithoutCancel(c.Request.Context()), 3*time.Second)
defer cancel()
err := s.projectMembers.RecordProjectMemberFailure(ctx, repository.ProjectMemberFailure{
ActorID: c.GetString(authenticatedActorKey), ProjectID: projectID, UserID: target,
Action: action, StatusCode: c.Writer.Status(), RequestID: requestIDFromContext(c),
})
if err != nil && s.logger != nil {
// 原失败响应已经确定,不能补发成功;存储故障只记录稳定诊断,不打印请求体和数据库原始错误。
s.logger.Error("项目成员失败审计未保存", "request_id", requestIDFromContext(c), "status", c.Writer.Status())
}
}
}