重构日志与可观测性体系

新增单行文本编码器与结构化 GORM 日志,统一错误记录与请求日志策略,收紧日志文件权限并修复按天切分与压缩,支付回调参数脱敏,生产强制阿里云短信,RequestID 校验防注入,日志文案中文化。
This commit is contained in:
yml2213
2026-07-29 16:19:35 +08:00
parent e75f1a8519
commit 88d74aca7d
64 changed files with 896 additions and 277 deletions
+7 -9
View File
@@ -186,14 +186,13 @@ func (h *Handler) LeshuaNotify(c *gin.Context) {
}
rawPayload := string(body)
contentType := c.GetHeader("Content-Type")
h.log().Info("payment notify received", notifyLogFields(c.Request.Context(), "leshua", params, contentType, len(body))...)
result, err := h.service.HandleLeshuaNotify(c.Request.Context(), params, rawPayload, contentType)
if err != nil || result == nil || !result.OK {
h.log().Warn("payment notify failed", notifyLogFields(c.Request.Context(), "leshua", params, contentType, len(body), zap.Error(err))...)
h.log().Warn("支付回调处理失败", notifyLogFields(c.Request.Context(), "leshua", params, contentType, len(body), zap.Error(err))...)
c.String(http.StatusOK, "FAIL")
return
}
h.log().Info("payment notify processed", notifyLogFields(c.Request.Context(), "leshua", params, contentType, len(body))...)
h.log().Info("支付回调处理完成", notifyLogFields(c.Request.Context(), "leshua", params, contentType, len(body))...)
c.String(http.StatusOK, result.Message)
}
@@ -211,14 +210,13 @@ func (h *Handler) LakalaNotify(c *gin.Context) {
rawPayload := string(body)
contentType := c.GetHeader("Content-Type")
authorization := c.GetHeader("Authorization")
h.log().Info("payment notify received", notifyLogFields(c.Request.Context(), "lakala", params, contentType, len(body))...)
result, err := h.service.HandleNotify(c.Request.Context(), "lakala", params, rawPayload, contentType, authorization)
if err != nil || result == nil || !result.OK {
h.log().Warn("payment notify failed", notifyLogFields(c.Request.Context(), "lakala", params, contentType, len(body), zap.Error(err))...)
h.log().Warn("支付回调处理失败", notifyLogFields(c.Request.Context(), "lakala", params, contentType, len(body), zap.Error(err))...)
c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "失败"})
return
}
h.log().Info("payment notify processed", notifyLogFields(c.Request.Context(), "lakala", params, contentType, len(body))...)
h.log().Info("支付回调处理完成", notifyLogFields(c.Request.Context(), "lakala", params, contentType, len(body))...)
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "message": "执行成功"})
}
@@ -236,14 +234,13 @@ func (h *Handler) ShunchengNotify(c *gin.Context) {
rawPayload := string(body)
contentType := c.GetHeader("Content-Type")
authorization := c.GetHeader("Authorization")
h.log().Info("payment notify received", notifyLogFields(c.Request.Context(), "shuncheng", params, contentType, len(body))...)
result, err := h.service.HandleNotify(c.Request.Context(), "shuncheng", params, rawPayload, contentType, authorization)
if err != nil || result == nil || !result.OK {
h.log().Warn("payment notify failed", notifyLogFields(c.Request.Context(), "shuncheng", params, contentType, len(body), zap.Error(err))...)
h.log().Warn("支付回调处理失败", notifyLogFields(c.Request.Context(), "shuncheng", params, contentType, len(body), zap.Error(err))...)
c.String(http.StatusOK, "FAIL")
return
}
h.log().Info("payment notify processed", notifyLogFields(c.Request.Context(), "shuncheng", params, contentType, len(body))...)
h.log().Info("支付回调处理完成", notifyLogFields(c.Request.Context(), "shuncheng", params, contentType, len(body))...)
c.String(http.StatusOK, result.Message)
}
@@ -295,6 +292,7 @@ func parseAdminPaymentQuery(c *gin.Context) (AdminPaymentQuery, bool) {
}
func writePaymentError(c *gin.Context, err error) {
response.RecordError(c, err)
switch {
case errors.Is(err, ErrDependencyUnavailable):
response.ServiceUnavailable(c, "数据库未连接")
@@ -60,11 +60,6 @@ func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uin
return nil, ErrPaymentUnavailable
}
r.log().Info("mohong payment start", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
)...,
)...)
resp, err := runtimeConfig.Channel.CreatePayment(ctx, channelCreatePaymentRequest{
ThirdOrderID: payment.ThirdOrderID,
AmountCent: payment.AmountCent,
@@ -78,7 +73,7 @@ func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uin
})
if err != nil {
_ = r.markPaymentFailed(ctx, payment.ID, nil, err.Error())
r.log().Warn("mohong payment request failed", paymentLogFields(ctx, appendFields(
r.log().Warn("魔哄支付下单失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{zap.Error(err)},
@@ -107,6 +102,11 @@ func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uin
return nil, err
}
r.recordConfigUsage(ctx, runtimeConfig, latest)
r.log().Info("魔哄支付下单完成", paymentLogFields(ctx, appendFields(
paymentOrderFields(latest),
runtimeConfigFields(runtimeConfig),
)...,
)...)
dto := toDTO(*latest)
return &dto, nil
}
+41 -17
View File
@@ -2,6 +2,10 @@ package payment
import (
"context"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"hfb_sys/backend/internal/model"
@@ -33,7 +37,7 @@ func (r *Repository) HandleNotify(ctx context.Context, provider string, params m
if amount := parseCent(params["amount"]); amount > 0 && amount != payment.AmountCent {
if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "amount_mismatch"); err != nil {
r.log().Warn("payment notify diagnostic save failed", paymentLogFields(ctx, appendFields(
r.log().Warn("支付回调诊断保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
@@ -116,20 +120,18 @@ func (r *Repository) verifyNotify(ctx context.Context, payment *model.PaymentOrd
}
verify, err := runtimeConfig.Channel.VerifyNotify(params, rawPayload, contentType, authorization)
if err != nil || !verify.OK {
r.log().Warn("payment notify verify failed", paymentLogFields(ctx, appendFields(
r.log().Warn("支付回调验签失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
zap.String("sign_got", verify.Got),
zap.String("sign_expected", firstNonEmpty(verify.Expected["notify_key"], verify.Expected["notify_cert"], verify.Expected["error"])),
zap.Bool("signature_present", verify.Got != ""),
zap.Strings("param_keys", verify.ParamKeys),
zap.String("sign_base_string", firstNonEmpty(verify.BaseString["notify_key"], verify.BaseString["notify_cert"])),
zap.Error(err),
},
)...,
)...)
if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "verify_failed"); err != nil {
r.log().Warn("payment notify diagnostic save failed", paymentLogFields(ctx, appendFields(
r.log().Warn("支付回调诊断保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
@@ -141,12 +143,6 @@ func (r *Repository) verifyNotify(ctx context.Context, payment *model.PaymentOrd
}
return verify, ErrPaymentVerifyFailed
}
r.log().Info("payment notify verified", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{zap.String("matched_key", verify.MatchedKey)},
)...,
)...)
return verify, nil
}
func (r *Repository) recordNotifyDiagnostic(ctx context.Context, paymentID uint64, params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) error {
@@ -159,13 +155,41 @@ func (r *Repository) recordNotifyDiagnostic(ctx context.Context, paymentID uint6
Update("raw_response", jsonMap(raw)).Error
}
func withNotifyDiagnostic(params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) map[string]string {
raw := withRawSource(params, channelSourceNotify)
raw := withRawSource(redactNotifyParams(params), channelSourceNotify)
raw["_notify_diagnostic_status"] = status
raw["_raw_payload"] = rawPayload
raw["_raw_content_type"] = contentType
raw["_sign_got"] = verify.Got
raw["_raw_payload_size"] = strconv.Itoa(len(rawPayload))
raw["_raw_payload_sha256"] = shortDigest(rawPayload)
raw["_signature_present"] = strconv.FormatBool(verify.Got != "")
raw["_sign_matched_key"] = verify.MatchedKey
raw["_sign_expected"] = jsonString(verify.Expected)
raw["_sign_base_strings"] = jsonString(verify.BaseString)
raw["_sign_param_keys"] = strings.Join(verify.ParamKeys, ",")
return raw
}
func redactNotifyParams(params map[string]string) map[string]string {
allowed := map[string]bool{
"service": true, "merchant_id": true, "third_order_id": true,
"provider_order_id": true, "leshua_order_id": true, "schc_order_id": true,
"merchant_refund_id": true, "provider_refund_id": true,
"leshua_refund_id": true, "schc_refund_id": true,
"status": true, "amount": true, "refund_amount": true,
"pay_time": true, "refund_time": true,
"code": true, "resp_code": true, "result_code": true, "error_code": true,
}
redacted := make(map[string]string, len(allowed))
for key, value := range params {
lowerKey := strings.ToLower(key)
if allowed[lowerKey] {
redacted[key] = value
}
}
return redacted
}
func shortDigest(value string) string {
if value == "" {
return ""
}
sum := sha256.Sum256([]byte(value))
return hex.EncodeToString(sum[:8])
}
@@ -60,11 +60,6 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r
return nil, ErrPaymentUnavailable
}
r.log().Info("payment start", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
)...,
)...)
resp, err := runtimeConfig.Channel.CreatePayment(ctx, channelCreatePaymentRequest{
ThirdOrderID: payment.ThirdOrderID,
AmountCent: payment.AmountCent,
@@ -78,7 +73,7 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r
})
if err != nil {
_ = r.markPaymentFailed(ctx, payment.ID, nil, err.Error())
r.log().Warn("payment request failed", paymentLogFields(ctx, appendFields(
r.log().Warn("支付下单请求失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{zap.Error(err)},
@@ -88,7 +83,7 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r
}
if !resp.OK {
_ = r.markPaymentFailed(ctx, payment.ID, resp.Raw, resp.ErrorMessage)
r.log().Warn("payment rejected", paymentLogFields(ctx, appendFields(
r.log().Warn("支付下单被渠道拒绝", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
@@ -117,7 +112,7 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r
return nil, err
}
r.recordConfigUsage(ctx, runtimeConfig, latest)
r.log().Info("payment result", paymentLogFields(ctx, appendFields(
r.log().Info("支付下单完成", paymentLogFields(ctx, appendFields(
paymentOrderFields(latest),
runtimeConfigFields(runtimeConfig),
)...,
+9 -19
View File
@@ -31,7 +31,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
if existing {
latest, syncErr := r.syncRefundPayment(ctx, refundOrder, refundOrder.Status != "refunded")
if syncErr != nil {
r.log().Warn("payment existing refund sync failed", paymentLogFields(ctx, appendFields(
r.log().Warn("已有退款单同步失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
[]zap.Field{zap.String("biz_type", bizType), zap.Error(syncErr)},
)...,
@@ -65,19 +65,9 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
return &dto, nil
}
r.log().Info("payment refund start", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
zap.String("merchant_refund_id", refundOrder.ThirdOrderID),
zap.String("origin_third_order_id", originalPayment.ThirdOrderID),
zap.String("origin_provider_order_id", refundOriginProviderOrderID(originalPayment)),
},
)...,
)...)
r.recordConfigUsage(ctx, runtimeConfig, refundOrder)
if err := r.markOrderRefunding(ctx, orderID, refundAmountCent); err != nil {
r.log().Warn("payment mark order refunding failed", paymentLogFields(ctx,
r.log().Warn("订单退款状态更新失败", paymentLogFields(ctx,
zap.Uint64("order_id", orderID),
zap.Int64("refund_amount_cent", refundAmountCent),
zap.Error(err),
@@ -86,7 +76,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
if runtimeConfig.Channel == nil {
if err := r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": "payment channel unavailable"}, nil); err != nil {
r.log().Warn("payment mark refund failed status failed", paymentLogFields(ctx, appendFields(
r.log().Warn("退款失败状态保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
[]zap.Field{zap.Error(err)},
)...,
@@ -109,13 +99,13 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
rawRequest = resp.RawRequest
}
if markErr := r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": err.Error()}, rawRequest); markErr != nil {
r.log().Warn("payment mark refund failed status failed", paymentLogFields(ctx, appendFields(
r.log().Warn("退款失败状态保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
[]zap.Field{zap.Error(markErr)},
)...,
)...)
}
r.log().Warn("payment refund request failed", paymentLogFields(ctx, appendFields(
r.log().Warn("退款渠道请求失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
runtimeConfigFields(runtimeConfig),
[]zap.Field{zap.Error(err)},
@@ -125,13 +115,13 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
}
if !resp.OK {
if markErr := r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, resp.Raw, resp.RawRequest); markErr != nil {
r.log().Warn("payment mark refund rejected status failed", paymentLogFields(ctx, appendFields(
r.log().Warn("退款拒绝状态保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
[]zap.Field{zap.Error(markErr)},
)...,
)...)
}
r.log().Warn("payment refund rejected", paymentLogFields(ctx, appendFields(
r.log().Warn("退款被渠道拒绝", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
@@ -153,7 +143,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou
}); err != nil {
return nil, err
}
r.log().Info("payment refund result", paymentLogFields(ctx, appendFields(
r.log().Info("退款提交完成", paymentLogFields(ctx, appendFields(
paymentOrderFields(refundOrder),
runtimeConfigFields(runtimeConfig),
[]zap.Field{
@@ -308,7 +298,7 @@ func (r *Repository) syncRefundPayment(ctx context.Context, payment *model.Payme
if err != nil {
if resp != nil && resp.RawRequest != nil {
if updateErr := r.updateRefundRawRequest(ctx, payment.ID, resp.RawRequest); updateErr != nil {
r.log().Warn("payment refund query raw request update failed", paymentLogFields(ctx, appendFields(
r.log().Warn("退款查询诊断保存失败", paymentLogFields(ctx, appendFields(
paymentOrderFields(payment),
[]zap.Field{zap.Error(updateErr)},
)...,
@@ -255,7 +255,7 @@ func (r *Repository) recordConfigUsage(ctx context.Context, runtimeConfig *runti
return
}
if err := r.configRepo.RecordUsage(ctx, runtimeConfig.ID, payment.ID, runtimeConfig.Provider, runtimeConfig.MerchantID, payment.AmountCent, payment.BizType); err != nil {
r.log().Warn("payment config usage record failed", paymentLogFields(ctx,
r.log().Warn("支付配置使用记录保存失败", paymentLogFields(ctx,
zap.Uint64("payment_config_id", runtimeConfig.ID),
zap.Uint64("payment_id", payment.ID),
zap.Error(err),