// Package processlog 提供交易过程的追加式留痕能力。 package processlog import ( "encoding/json" "strconv" "strings" "hfb_sys/backend/internal/model" "gorm.io/datatypes" "gorm.io/gorm" ) const ( ActorUser = "user" ActorAdmin = "admin" ActorSystem = "system" ) type Entry struct { BusinessType string BusinessID uint64 Stage string Action string ActorType string ActorID uint64 TargetType string TargetID *uint64 Content string Reason string Payload map[string]any Attachments []string StateBefore map[string]any StateAfter map[string]any } func Append(tx *gorm.DB, entry Entry) error { actorName, err := displayName(tx, entry.ActorType, entry.ActorID) if err != nil { return err } targetName := "" if entry.TargetID != nil && *entry.TargetID > 0 { targetName, err = displayName(tx, entry.TargetType, *entry.TargetID) if err != nil { return err } } return tx.Create(&model.ProcessEvent{ BusinessType: entry.BusinessType, BusinessID: entry.BusinessID, Stage: entry.Stage, Action: entry.Action, ActorType: entry.ActorType, ActorID: entry.ActorID, ActorName: actorName, TargetType: entry.TargetType, TargetID: entry.TargetID, TargetName: targetName, Content: strings.TrimSpace(entry.Content), Reason: strings.TrimSpace(entry.Reason), Payload: jsonValue(entry.Payload), AttachmentURLs: jsonValue(entry.Attachments), StateBefore: jsonValue(entry.StateBefore), StateAfter: jsonValue(entry.StateAfter), }).Error } func List(tx *gorm.DB, businessType string, businessID uint64) ([]model.ProcessEvent, error) { var rows []model.ProcessEvent err := tx.Where("business_type = ? AND business_id = ?", businessType, businessID). Order("id ASC").Find(&rows).Error return rows, err } func displayName(tx *gorm.DB, actorType string, id uint64) (string, error) { if id == 0 || actorType == ActorSystem { return "系统", nil } switch actorType { case ActorAdmin: var row model.AdminUser if err := tx.Select("id", "username", "nickname").First(&row, id).Error; err != nil { if err == gorm.ErrRecordNotFound { return "客服 ID " + formatID(id), nil } return "", err } if strings.TrimSpace(row.Nickname) != "" { return row.Nickname + "(客服)", nil } if strings.TrimSpace(row.Username) != "" { return row.Username + "(客服)", nil } return "客服 ID " + formatID(id), nil default: var row model.User if err := tx.Select("id", "nickname", "phone").First(&row, id).Error; err != nil { if err == gorm.ErrRecordNotFound { return "用户 ID " + formatID(id), nil } return "", err } if strings.TrimSpace(row.Nickname) != "" { return row.Nickname + "(用户)", nil } if strings.TrimSpace(row.Phone) != "" { return row.Phone + "(用户)", nil } return "用户 ID " + formatID(id), nil } } func jsonValue(value any) datatypes.JSON { if value == nil { return datatypes.JSON([]byte("{}")) } raw, err := json.Marshal(value) if err != nil || len(raw) == 0 { return datatypes.JSON([]byte("{}")) } return datatypes.JSON(raw) } func formatID(id uint64) string { return strconv.FormatUint(id, 10) }