优化结构
This commit is contained in:
@@ -61,7 +61,11 @@ func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.Access
|
||||
if ws != nil && ws.Enabled() {
|
||||
engine.GET(ws.Path(), handleWebSocket)
|
||||
}
|
||||
if snapshot == nil || snapshot.Admin == nil || snapshot.Admin.App == nil || strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env)) != "production" {
|
||||
env := ""
|
||||
if snapshot != nil && snapshot.Admin != nil && snapshot.Admin.App != nil {
|
||||
env = strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env))
|
||||
}
|
||||
if env != "production" && env != "prod" && env != "live" && env != "staging" {
|
||||
registerSwagger(engine, prefix, version, logger)
|
||||
}
|
||||
staticfiles.Register(engine, runtime)
|
||||
|
||||
@@ -30,6 +30,9 @@ type PageResult struct {
|
||||
}
|
||||
|
||||
func Write(c *gin.Context, code int, data any, message string) {
|
||||
if code == CodeError {
|
||||
message = sanitizeFailureMessage(message)
|
||||
}
|
||||
c.JSON(http.StatusOK, Response{Code: code, Data: data, Msg: message})
|
||||
}
|
||||
|
||||
@@ -39,6 +42,20 @@ func OKWithData(c *gin.Context, data any) { Write(c, CodeSuccess, data, "成功"
|
||||
|
||||
func Fail(c *gin.Context, message string) { Write(c, CodeError, gin.H{}, message) }
|
||||
|
||||
func sanitizeFailureMessage(message string) string {
|
||||
message = strings.TrimSpace(message)
|
||||
lower := strings.ToLower(message)
|
||||
for _, marker := range []string{"gorm", "sql:", "redis", "mysql", "mongo", "dial ", "connection", "provider", "sdk", "json:", "serialize", "timeout", "http 4", "http 5", "tls:"} {
|
||||
if strings.Contains(lower, marker) {
|
||||
return "操作失败"
|
||||
}
|
||||
}
|
||||
if strings.Contains(message, "失败:") {
|
||||
return strings.TrimSpace(strings.SplitN(message, "失败:", 2)[0]) + "失败"
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func NoAuth(c *gin.Context, message string) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, Response{Code: CodeError, Data: nil, Msg: message})
|
||||
}
|
||||
|
||||
@@ -40,10 +40,11 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||
}
|
||||
bodyLimit := defaultRequestBodyLimit
|
||||
if mediaUpload {
|
||||
bodyLimit = system.DefaultMaxMediaFileSize + (1 << 20)
|
||||
if admin != nil && admin.Media != nil && admin.Media.MaxFileSize > 0 {
|
||||
bodyLimit = admin.Media.MaxFileSize + (1 << 20)
|
||||
mediaSettings := system.MediaSettings{}
|
||||
if admin != nil && admin.Media != nil {
|
||||
mediaSettings.MaxFileSize = admin.Media.MaxFileSize
|
||||
}
|
||||
bodyLimit = mediaSettings.EffectiveMaxFileSize() + (1 << 20)
|
||||
}
|
||||
bytesIn := c.Request.ContentLength
|
||||
logLimit := 1024
|
||||
@@ -96,6 +97,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||
requestText = redactJSON(requestBody, c.GetHeader("Content-Type"), logLimit)
|
||||
}
|
||||
c.Set(ctxReqBodyKey, requestText)
|
||||
c.Set(ctxRespTruncatedKey, writer.Truncated())
|
||||
if paymentCallback {
|
||||
// Callback acknowledgements and provider payloads must not flow into
|
||||
// the generic response/error audit pipeline.
|
||||
@@ -110,6 +112,9 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||
return
|
||||
}
|
||||
responseText := redactJSON(writer.body.Bytes(), c.Writer.Header().Get("Content-Type"), logLimit)
|
||||
if writer.Truncated() {
|
||||
responseText = "[超出记录长度]"
|
||||
}
|
||||
if paymentCallback {
|
||||
responseText = "[支付回调响应已省略]"
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||
}
|
||||
if c.Request.Method == http.MethodGet {
|
||||
requestBody = operationQueryBody(c.Request.URL.RawQuery)
|
||||
} else if value, ok := c.Get(ctxReqBodyKey); ok {
|
||||
requestBody = []byte(stringValue(value))
|
||||
} else if _, ok := c.Get(ctxReqBodyKey); ok {
|
||||
} else if c.Request.Body != nil {
|
||||
// Fallback for tests or custom middleware chains that omit AccessLog.
|
||||
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
|
||||
@@ -79,13 +78,13 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||
responseBody = redactJSON(body.Bytes(), c.Writer.Header().Get("Content-Type"), maxBytes)
|
||||
}
|
||||
}
|
||||
if isDownloadResponse(c) && len(responseBody) > maxBytes {
|
||||
if truncated, _ := c.Get(ctxRespTruncatedKey); truncated == true {
|
||||
responseBody = "[超出记录长度]"
|
||||
}
|
||||
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||
operationBody := ""
|
||||
capturedBody := false
|
||||
if c.Request.Method != http.MethodGet {
|
||||
if c.Request.Method != "GET" {
|
||||
if value, ok := c.Get(ctxReqBodyKey); ok {
|
||||
// AccessLog has already captured, redacted, summarized, and bounded the
|
||||
// request body. Treat that value as final so audit does not process it a
|
||||
@@ -162,27 +161,3 @@ func maskOperationBody(value any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// operationDownloadHeaders lists the response headers whose presence marks a
|
||||
// download: the value is the substring that identifies it.
|
||||
var operationDownloadHeaders = [...]struct{ header, marker string }{
|
||||
{"Pragma", "public"},
|
||||
{"Expires", "0"},
|
||||
{"Cache-Control", "must-revalidate, post-check=0, pre-check=0"},
|
||||
{"Content-Type", "application/force-download"},
|
||||
{"Content-Type", "application/octet-stream"},
|
||||
{"Content-Type", "application/vnd.ms-excel"},
|
||||
{"Content-Type", "application/download"},
|
||||
{"Content-Disposition", "attachment"},
|
||||
{"Content-Transfer-Encoding", "binary"},
|
||||
}
|
||||
|
||||
func isDownloadResponse(c *gin.Context) bool {
|
||||
header := c.Writer.Header()
|
||||
for _, candidate := range operationDownloadHeaders {
|
||||
if strings.Contains(header.Get(candidate.header), candidate.marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,14 +12,16 @@ import (
|
||||
// OperationAudit consumes these values after the handler returns, avoiding a
|
||||
// second body read/writer wrapper.
|
||||
const (
|
||||
ctxReqBodyKey = "kra_req_body"
|
||||
ctxRespBufferKey = "kra_resp_buffer"
|
||||
ctxReqBodyKey = "kra_req_body"
|
||||
ctxRespBufferKey = "kra_resp_buffer"
|
||||
ctxRespTruncatedKey = "kra_resp_truncated"
|
||||
)
|
||||
|
||||
type captureWriter struct {
|
||||
gin.ResponseWriter
|
||||
body bytes.Buffer
|
||||
maxBytes int
|
||||
body bytes.Buffer
|
||||
maxBytes int
|
||||
truncated bool
|
||||
}
|
||||
|
||||
func (w *captureWriter) Write(data []byte) (int, error) {
|
||||
@@ -31,6 +33,7 @@ func (w *captureWriter) Write(data []byte) (int, error) {
|
||||
remaining := limit - w.body.Len()
|
||||
if len(data) > remaining {
|
||||
_, _ = w.body.Write(data[:remaining])
|
||||
w.truncated = true
|
||||
} else {
|
||||
_, _ = w.body.Write(data)
|
||||
}
|
||||
@@ -38,6 +41,8 @@ func (w *captureWriter) Write(data []byte) (int, error) {
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func (w *captureWriter) Truncated() bool { return w != nil && w.truncated }
|
||||
|
||||
func redactJSON(raw []byte, contentType string, limit int) string {
|
||||
if len(raw) == 0 {
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user