完善日志和审计链路
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
package auditlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/datatypes"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
IP string
|
||||||
|
UserAgent string
|
||||||
|
RequestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
ActorType string
|
||||||
|
ActorID uint64
|
||||||
|
Action string
|
||||||
|
BizType string
|
||||||
|
BizID *uint64
|
||||||
|
Meta Meta
|
||||||
|
Detail map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append 在业务事务内写入审计日志,确保审计和业务状态一起提交或回滚。
|
||||||
|
func Append(tx *gorm.DB, entry Entry) error {
|
||||||
|
raw, err := marshalDetail(entry.Detail, entry.Meta.RequestID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
row := model.AuditLog{
|
||||||
|
ActorType: entry.ActorType,
|
||||||
|
ActorID: entry.ActorID,
|
||||||
|
Action: entry.Action,
|
||||||
|
BizType: entry.BizType,
|
||||||
|
BizID: entry.BizID,
|
||||||
|
IP: entry.Meta.IP,
|
||||||
|
UserAgent: entry.Meta.UserAgent,
|
||||||
|
Detail: datatypes.JSON(raw),
|
||||||
|
}
|
||||||
|
return tx.Create(&row).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalDetail(detail map[string]any, requestID string) ([]byte, error) {
|
||||||
|
copied := make(map[string]any, len(detail)+1)
|
||||||
|
for key, value := range detail {
|
||||||
|
copied[key] = value
|
||||||
|
}
|
||||||
|
if requestID != "" {
|
||||||
|
copied["request_id"] = requestID
|
||||||
|
}
|
||||||
|
return json.Marshal(copied)
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ func New(cfg config.LogConfig) (*zap.Logger, error) {
|
|||||||
if cfg.EnableFile {
|
if cfg.EnableFile {
|
||||||
writer := newDailyWriter(cfg.Dir, "app", location)
|
writer := newDailyWriter(cfg.Dir, "app", location)
|
||||||
cores = append(cores, zapcore.NewCore(
|
cores = append(cores, zapcore.NewCore(
|
||||||
zapcore.NewConsoleEncoder(encoderConfig),
|
zapcore.NewJSONEncoder(encoderConfig),
|
||||||
writer,
|
writer,
|
||||||
level,
|
level,
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"hfb_sys/backend/pkg/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Recovery(logger *zap.Logger) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
defer func() {
|
||||||
|
if recovered := recover(); recovered != nil {
|
||||||
|
err := fmt.Errorf("panic: %v", recovered)
|
||||||
|
_ = c.Error(err)
|
||||||
|
logger.Error("http panic recovered",
|
||||||
|
zap.String("request_id", GetRequestID(c)),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("route", c.FullPath()),
|
||||||
|
zap.String("client_ip", c.ClientIP()),
|
||||||
|
zap.String("user_agent", c.GetHeader("User-Agent")),
|
||||||
|
zap.String("panic", fmt.Sprint(recovered)),
|
||||||
|
zap.ByteString("stack", debug.Stack()),
|
||||||
|
)
|
||||||
|
if !c.Writer.Written() {
|
||||||
|
response.Error(c, http.StatusInternalServerError, "internal_error", "服务暂时不可用")
|
||||||
|
}
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestIDHeader = "X-Request-ID"
|
||||||
|
ContextRequestID = "request_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequestID() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
requestID := c.GetHeader(RequestIDHeader)
|
||||||
|
if requestID == "" {
|
||||||
|
requestID = newRequestID()
|
||||||
|
}
|
||||||
|
c.Set(ContextRequestID, requestID)
|
||||||
|
c.Writer.Header().Set(RequestIDHeader, requestID)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRequestID(c *gin.Context) string {
|
||||||
|
value, ok := c.Get(ContextRequestID)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
requestID, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return requestID
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequestID() string {
|
||||||
|
buf := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(buf); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(buf)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -12,12 +13,36 @@ func RequestLogger(logger *zap.Logger) gin.HandlerFunc {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
logger.Info("http request",
|
latency := time.Since(start)
|
||||||
|
fields := []zap.Field{
|
||||||
|
zap.String("request_id", GetRequestID(c)),
|
||||||
zap.String("method", c.Request.Method),
|
zap.String("method", c.Request.Method),
|
||||||
zap.String("path", c.Request.URL.Path),
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("route", c.FullPath()),
|
||||||
zap.Int("status", c.Writer.Status()),
|
zap.Int("status", c.Writer.Status()),
|
||||||
zap.Duration("latency", time.Since(start)),
|
zap.Float64("latency_ms", float64(latency.Microseconds())/1000),
|
||||||
zap.String("client_ip", c.ClientIP()),
|
zap.String("client_ip", c.ClientIP()),
|
||||||
)
|
zap.String("user_agent", c.GetHeader("User-Agent")),
|
||||||
|
zap.String("referer", c.GetHeader("Referer")),
|
||||||
|
zap.Int("response_size", c.Writer.Size()),
|
||||||
|
}
|
||||||
|
if userID, ok := c.Get(ContextUserID); ok {
|
||||||
|
fields = append(fields, zap.Any("user_id", userID))
|
||||||
|
}
|
||||||
|
if adminID, ok := c.Get(ContextAdminID); ok {
|
||||||
|
fields = append(fields, zap.Any("admin_id", adminID))
|
||||||
|
}
|
||||||
|
if len(c.Errors) > 0 {
|
||||||
|
fields = append(fields, zap.String("errors", strings.TrimSpace(c.Errors.String())))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c.Writer.Status() >= 500:
|
||||||
|
logger.Error("http request", fields...)
|
||||||
|
case c.Writer.Status() >= 400:
|
||||||
|
logger.Warn("http request", fields...)
|
||||||
|
default:
|
||||||
|
logger.Info("http request", fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ func auditMeta(c *gin.Context) AuditMeta {
|
|||||||
return AuditMeta{
|
return AuditMeta{
|
||||||
IP: c.ClientIP(),
|
IP: c.ClientIP(),
|
||||||
UserAgent: c.GetHeader("User-Agent"),
|
UserAgent: c.GetHeader("User-Agent"),
|
||||||
|
RequestID: middleware.GetRequestID(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package adminuser
|
package adminuser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
@@ -15,10 +14,7 @@ type Repository struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuditMeta struct {
|
type AuditMeta = auditlog.Meta
|
||||||
IP string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRepository(db *gorm.DB) *Repository {
|
func NewRepository(db *gorm.DB) *Repository {
|
||||||
return &Repository{db: db}
|
return &Repository{db: db}
|
||||||
@@ -133,21 +129,15 @@ func (row userRow) toDTO() UserDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
return auditlog.Append(tx, auditlog.Entry{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
row := model.AuditLog{
|
|
||||||
ActorType: "admin",
|
ActorType: "admin",
|
||||||
ActorID: actorID,
|
ActorID: actorID,
|
||||||
Action: action,
|
Action: action,
|
||||||
BizType: "user",
|
BizType: "user",
|
||||||
BizID: &bizID,
|
BizID: &bizID,
|
||||||
IP: meta.IP,
|
Meta: meta,
|
||||||
UserAgent: meta.UserAgent,
|
Detail: detail,
|
||||||
Detail: datatypes.JSON(raw),
|
})
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package dispute
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,10 +38,7 @@ type ArbitrateRequest struct {
|
|||||||
Remark string `json:"remark" binding:"required"`
|
Remark string `json:"remark" binding:"required"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
}
|
}
|
||||||
type AuditMeta struct {
|
type AuditMeta = auditlog.Meta
|
||||||
IP string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaginatedResult struct {
|
type PaginatedResult struct {
|
||||||
Items []DisputeDTO `json:"items"`
|
Items []DisputeDTO `json:"items"`
|
||||||
|
|||||||
@@ -100,10 +100,7 @@ func (h *Handler) AdminArbitrate(c *gin.Context) {
|
|||||||
response.BadRequest(c, "仲裁结果和备注不能为空")
|
response.BadRequest(c, "仲裁结果和备注不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err := h.service.Arbitrate(adminID, id, req, AuditMeta{
|
item, err := h.service.Arbitrate(adminID, id, req, auditMeta(c))
|
||||||
IP: c.ClientIP(),
|
|
||||||
UserAgent: c.GetHeader("User-Agent"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDisputeError(c, err)
|
writeDisputeError(c, err)
|
||||||
return
|
return
|
||||||
@@ -111,6 +108,14 @@ func (h *Handler) AdminArbitrate(c *gin.Context) {
|
|||||||
response.OK(c, item)
|
response.OK(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func auditMeta(c *gin.Context) AuditMeta {
|
||||||
|
return AuditMeta{
|
||||||
|
IP: c.ClientIP(),
|
||||||
|
UserAgent: c.GetHeader("User-Agent"),
|
||||||
|
RequestID: middleware.GetRequestID(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func currentAdminID(c *gin.Context) (uint64, bool) {
|
func currentAdminID(c *gin.Context) (uint64, bool) {
|
||||||
value, ok := c.Get(middleware.ContextAdminID)
|
value, ok := c.Get(middleware.ContextAdminID)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
"hfb_sys/backend/internal/modules/notification"
|
"hfb_sys/backend/internal/modules/notification"
|
||||||
"hfb_sys/backend/internal/modules/wallet"
|
"hfb_sys/backend/internal/modules/wallet"
|
||||||
@@ -470,21 +471,15 @@ func disputeType(input string, isCheckoutDispute bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
return auditlog.Append(tx, auditlog.Entry{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
row := model.AuditLog{
|
|
||||||
ActorType: "admin",
|
ActorType: "admin",
|
||||||
ActorID: actorID,
|
ActorID: actorID,
|
||||||
Action: action,
|
Action: action,
|
||||||
BizType: bizType,
|
BizType: bizType,
|
||||||
BizID: &bizID,
|
BizID: &bizID,
|
||||||
IP: meta.IP,
|
Meta: meta,
|
||||||
UserAgent: meta.UserAgent,
|
Detail: detail,
|
||||||
Detail: datatypes.JSON(raw),
|
})
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListingDTO struct {
|
type ListingDTO struct {
|
||||||
@@ -116,10 +118,7 @@ type AdminActionRequest struct {
|
|||||||
Reason string `json:"reason" binding:"required"`
|
Reason string `json:"reason" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuditMeta struct {
|
type AuditMeta = auditlog.Meta
|
||||||
IP string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExternalUploadRequest struct {
|
type ExternalUploadRequest struct {
|
||||||
UploadTime int64 `json:"uploadTime"`
|
UploadTime int64 `json:"uploadTime"`
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActio
|
|||||||
response.BadRequest(c, "操作原因不能为空")
|
response.BadRequest(c, "操作原因不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err := fn(adminID, id, req, AuditMeta{IP: c.ClientIP(), UserAgent: c.GetHeader("User-Agent")})
|
item, err := fn(adminID, id, req, auditMeta(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeListingError(c, err)
|
writeListingError(c, err)
|
||||||
return
|
return
|
||||||
@@ -182,6 +182,14 @@ func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActio
|
|||||||
response.OK(c, item)
|
response.OK(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func auditMeta(c *gin.Context) AuditMeta {
|
||||||
|
return AuditMeta{
|
||||||
|
IP: c.ClientIP(),
|
||||||
|
UserAgent: c.GetHeader("User-Agent"),
|
||||||
|
RequestID: middleware.GetRequestID(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) Approve(c *gin.Context) {
|
func (h *Handler) Approve(c *gin.Context) {
|
||||||
id, ok := parseID(c)
|
id, ok := parseID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
"hfb_sys/backend/internal/modules/notification"
|
"hfb_sys/backend/internal/modules/notification"
|
||||||
|
|
||||||
@@ -1342,21 +1343,15 @@ func extractListingObjectKey(fileURL string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
return auditlog.Append(tx, auditlog.Entry{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
row := model.AuditLog{
|
|
||||||
ActorType: "admin",
|
ActorType: "admin",
|
||||||
ActorID: actorID,
|
ActorID: actorID,
|
||||||
Action: action,
|
Action: action,
|
||||||
BizType: bizType,
|
BizType: bizType,
|
||||||
BizID: &bizID,
|
BizID: &bizID,
|
||||||
IP: meta.IP,
|
Meta: meta,
|
||||||
UserAgent: meta.UserAgent,
|
Detail: detail,
|
||||||
Detail: datatypes.JSON(raw),
|
})
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package order
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,10 +70,7 @@ type AdminActionRequest struct {
|
|||||||
Reason string `json:"reason" binding:"required"`
|
Reason string `json:"reason" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuditMeta struct {
|
type AuditMeta = auditlog.Meta
|
||||||
IP string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandoffRecordDTO struct {
|
type HandoffRecordDTO struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
|
|||||||
@@ -110,13 +110,21 @@ func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActio
|
|||||||
response.BadRequest(c, "操作原因不能为空")
|
response.BadRequest(c, "操作原因不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := fn(adminID, id, req, AuditMeta{IP: c.ClientIP(), UserAgent: c.GetHeader("User-Agent")}); err != nil {
|
if err := fn(adminID, id, req, auditMeta(c)); err != nil {
|
||||||
writeOrderError(c, err)
|
writeOrderError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response.OK(c, okData)
|
response.OK(c, okData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func auditMeta(c *gin.Context) AuditMeta {
|
||||||
|
return AuditMeta{
|
||||||
|
IP: c.ClientIP(),
|
||||||
|
UserAgent: c.GetHeader("User-Agent"),
|
||||||
|
RequestID: middleware.GetRequestID(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) Detail(c *gin.Context) {
|
func (h *Handler) Detail(c *gin.Context) {
|
||||||
userID, ok := currentUserID(c)
|
userID, ok := currentUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -375,7 +383,6 @@ func parseID(c *gin.Context) (uint64, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeOrderError(c *gin.Context, err error) {
|
func writeOrderError(c *gin.Context, err error) {
|
||||||
println("DEBUG ORDER ERROR:", err.Error())
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, ErrDependencyUnavailable):
|
case errors.Is(err, ErrDependencyUnavailable):
|
||||||
response.ServiceUnavailable(c, "数据库未连接")
|
response.ServiceUnavailable(c, "数据库未连接")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
"hfb_sys/backend/internal/modules/chat"
|
"hfb_sys/backend/internal/modules/chat"
|
||||||
"hfb_sys/backend/internal/modules/notification"
|
"hfb_sys/backend/internal/modules/notification"
|
||||||
@@ -1536,21 +1537,15 @@ func newOrderNo() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
return auditlog.Append(tx, auditlog.Entry{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
row := model.AuditLog{
|
|
||||||
ActorType: "admin",
|
ActorType: "admin",
|
||||||
ActorID: actorID,
|
ActorID: actorID,
|
||||||
Action: action,
|
Action: action,
|
||||||
BizType: bizType,
|
BizType: bizType,
|
||||||
BizID: &bizID,
|
BizID: &bizID,
|
||||||
IP: meta.IP,
|
Meta: meta,
|
||||||
UserAgent: meta.UserAgent,
|
Detail: detail,
|
||||||
Detail: datatypes.JSON(raw),
|
})
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
|
|||||||
@@ -75,10 +75,7 @@ func (h *Handler) Update(c *gin.Context) {
|
|||||||
response.BadRequest(c, "配置值不能为空")
|
response.BadRequest(c, "配置值不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err := h.service.Update(adminID, key, req, AuditMeta{
|
item, err := h.service.Update(adminID, key, req, auditMeta(c))
|
||||||
IP: c.ClientIP(),
|
|
||||||
UserAgent: c.GetHeader("User-Agent"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeConfigError(c, err)
|
writeConfigError(c, err)
|
||||||
return
|
return
|
||||||
@@ -86,6 +83,14 @@ func (h *Handler) Update(c *gin.Context) {
|
|||||||
response.OK(c, item)
|
response.OK(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func auditMeta(c *gin.Context) AuditMeta {
|
||||||
|
return AuditMeta{
|
||||||
|
IP: c.ClientIP(),
|
||||||
|
UserAgent: c.GetHeader("User-Agent"),
|
||||||
|
RequestID: middleware.GetRequestID(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func currentAdminID(c *gin.Context) (uint64, bool) {
|
func currentAdminID(c *gin.Context) (uint64, bool) {
|
||||||
value, ok := c.Get(middleware.ContextAdminID)
|
value, ok := c.Get(middleware.ContextAdminID)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/auditlog"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
@@ -16,10 +16,7 @@ type Repository struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuditMeta struct {
|
type AuditMeta = auditlog.Meta
|
||||||
IP string
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultConfig struct {
|
type defaultConfig struct {
|
||||||
Key string
|
Key string
|
||||||
@@ -249,21 +246,15 @@ func isLegacyPublishOptionsValue(value string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
return auditlog.Append(tx, auditlog.Entry{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
row := model.AuditLog{
|
|
||||||
ActorType: "admin",
|
ActorType: "admin",
|
||||||
ActorID: actorID,
|
ActorID: actorID,
|
||||||
Action: action,
|
Action: action,
|
||||||
BizType: "system_config",
|
BizType: "system_config",
|
||||||
BizID: &bizID,
|
BizID: &bizID,
|
||||||
IP: meta.IP,
|
Meta: meta,
|
||||||
UserAgent: meta.UserAgent,
|
Detail: detail,
|
||||||
Detail: datatypes.JSON(raw),
|
})
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO(row model.SystemConfig) ConfigDTO {
|
func toDTO(row model.SystemConfig) ConfigDTO {
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ func ensureAccount(tx *gorm.DB, userID uint64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyEntry(account *model.WalletAccount, entry Entry) (float64, error) {
|
func applyEntry(account *model.WalletAccount, entry Entry) (float64, error) {
|
||||||
println("DEBUG APPLY:", entry.UserID, "type:", entry.BalanceType, "dir:", entry.Direction, "amt:", fmt.Sprintf("%.2f", entry.Amount), "avail:", fmt.Sprintf("%.2f", account.AvailableBalance), "frozen:", fmt.Sprintf("%.2f", account.FrozenBalance))
|
|
||||||
entry.Amount = roundWalletMoney(entry.Amount)
|
entry.Amount = roundWalletMoney(entry.Amount)
|
||||||
account.AvailableBalance = roundWalletMoney(account.AvailableBalance)
|
account.AvailableBalance = roundWalletMoney(account.AvailableBalance)
|
||||||
account.FrozenBalance = roundWalletMoney(account.FrozenBalance)
|
account.FrozenBalance = roundWalletMoney(account.FrozenBalance)
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
engine.Use(gin.Recovery())
|
engine.Use(middleware.RequestID())
|
||||||
engine.Use(middleware.RequestLogger(logger))
|
engine.Use(middleware.RequestLogger(logger))
|
||||||
|
engine.Use(middleware.Recovery(logger))
|
||||||
|
|
||||||
health := handler.NewHealthHandler()
|
health := handler.NewHealthHandler()
|
||||||
engine.GET("/health", health.Check)
|
engine.GET("/health", health.Check)
|
||||||
|
|||||||
Reference in New Issue
Block a user