153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
package response
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/net/ghttp"
|
|
"github.com/gogf/gf/v2/util/gvalid"
|
|
|
|
"github.com/qianfree/team-api/internal/consts"
|
|
)
|
|
|
|
// jsonResp is the unified response structure for all API endpoints.
|
|
type jsonResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data interface{} `json:"data"`
|
|
RequestID string `json:"request_id"`
|
|
}
|
|
|
|
// Success writes a successful response: HTTP 200 + {"code":0, "message":"ok", "data":..., "request_id":...}.
|
|
func Success(r *ghttp.Request, data interface{}) {
|
|
requestID := getRequestID(r)
|
|
r.Response.WriteJson(jsonResp{
|
|
Code: 0,
|
|
Message: consts.MsgSuccess,
|
|
Data: data,
|
|
RequestID: requestID,
|
|
})
|
|
r.SetError(nil) // 清除请求上的错误,防止 ErrorHandler 中间件重复处理
|
|
}
|
|
|
|
// Error writes an error response with automatic sanitization.
|
|
//
|
|
// Security: Only business errors (code >= 10000), validation errors, and standard HTTP 4xx errors
|
|
// expose their messages to the client. All other errors (database, network,
|
|
// internal) are replaced with "服务器内部错误" to prevent information leakage.
|
|
// The original error is logged with request_id for debugging.
|
|
func Error(r *ghttp.Request, err error) {
|
|
code := 500
|
|
msg := "服务器内部错误"
|
|
|
|
// Check for GoFrame validation error first (gvalid.Error is NOT a gerror.Error,
|
|
// so the gerror branch below won't match it — must check separately)
|
|
var validErr gvalid.Error
|
|
if errors.As(err, &validErr) {
|
|
code = 422
|
|
msg = validErr.Current().Error()
|
|
} else {
|
|
// Extract code from GoFrame gerror (supports wrapped errors via errors.As)
|
|
var gerr *gerror.Error
|
|
if errors.As(err, &gerr) {
|
|
code = gerr.Code().Code()
|
|
|
|
// Business errors (>= 10000) and HTTP 4xx have safe user-facing messages
|
|
if code >= 10000 || (code >= 400 && code < 500) {
|
|
msg = gerr.Error()
|
|
} else {
|
|
// Internal error — sanitize message, log real cause
|
|
g.Log().Warning(r.Context(), "[Response] Internal error sanitized",
|
|
"request_id", getRequestID(r),
|
|
"code", code,
|
|
"error", err.Error(),
|
|
)
|
|
// Persist system error to sys_error_logs
|
|
writeErrorLog(&ErrorLogRecord{
|
|
RequestId: getRequestID(r),
|
|
ErrorCode: code,
|
|
ErrorMessage: err.Error(),
|
|
StackTrace: captureStackTrace(3),
|
|
HttpMethod: r.Method,
|
|
RequestPath: r.URL.Path,
|
|
RequestBody: safeRequestBodyForLog(r),
|
|
Source: "api",
|
|
})
|
|
}
|
|
} else {
|
|
// Non-gerror (raw Go error, e.g. database driver error) — always sanitize
|
|
g.Log().Warning(r.Context(), "[Response] Raw error sanitized",
|
|
"request_id", getRequestID(r),
|
|
"error", err.Error(),
|
|
)
|
|
// Persist raw error to sys_error_logs
|
|
writeErrorLog(&ErrorLogRecord{
|
|
RequestId: getRequestID(r),
|
|
ErrorCode: 500,
|
|
ErrorMessage: err.Error(),
|
|
StackTrace: captureStackTrace(3),
|
|
HttpMethod: r.Method,
|
|
RequestPath: r.URL.Path,
|
|
RequestBody: safeRequestBodyForLog(r),
|
|
Source: "api",
|
|
})
|
|
}
|
|
}
|
|
|
|
if code == 0 {
|
|
code = 500
|
|
}
|
|
|
|
// Determine HTTP status
|
|
httpStatus := code
|
|
if code >= 10000 {
|
|
httpStatus = 422
|
|
} else if httpStatus < 100 || httpStatus > 599 {
|
|
// GoFrame gcode 可能返回非标准错误码(如 68=CodeNotModified),
|
|
// 不能直接用作 HTTP 状态码,否则 WriteHeader 会 panic
|
|
httpStatus = 500
|
|
}
|
|
|
|
requestID := getRequestID(r)
|
|
r.Response.WriteHeader(httpStatus)
|
|
r.Response.WriteJson(jsonResp{
|
|
Code: code,
|
|
Message: msg,
|
|
Data: nil,
|
|
RequestID: requestID,
|
|
})
|
|
r.SetError(nil) // 清除请求上的错误,防止 ErrorHandler 中间件重复处理
|
|
}
|
|
|
|
// Use this for 400/401/403/404/409/429/500 errors.
|
|
func ErrorMsg(r *ghttp.Request, code int, message string) {
|
|
ErrorWithCode(r, code, code, message)
|
|
}
|
|
|
|
// ErrorWithCode writes an error with separate HTTP status and business code.
|
|
// Use this when HTTP status differs from the business code (e.g., HTTP 401 with code 10020).
|
|
func ErrorWithCode(r *ghttp.Request, httpStatus int, code int, message string) {
|
|
if httpStatus < 100 || httpStatus > 599 {
|
|
httpStatus = http.StatusInternalServerError
|
|
}
|
|
requestID := getRequestID(r)
|
|
r.Response.WriteHeader(httpStatus)
|
|
r.Response.WriteJson(jsonResp{
|
|
Code: code,
|
|
Message: message,
|
|
Data: nil,
|
|
RequestID: requestID,
|
|
})
|
|
r.SetError(nil) // 清除请求上的错误,防止 ErrorHandler 中间件重复处理
|
|
}
|
|
|
|
// getRequestID extracts the request ID from context.
|
|
func getRequestID(r *ghttp.Request) string {
|
|
if v := r.GetCtxVar("RequestId"); v != nil {
|
|
return v.String()
|
|
}
|
|
return ""
|
|
}
|