增加气站历史账户显式修复与审计

This commit is contained in:
czl231
2026-08-17 22:34:02 +08:00
parent d6bb3bd43e
commit eef791427a
9 changed files with 354 additions and 4 deletions

View File

@@ -0,0 +1,148 @@
// Package repair 提供必须显式执行且保留审计快照的一次性数据修复。
package repair
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"gorm.io/gorm"
)
const (
legacyGasAccountRole = "010"
gasAdminRole = "admin"
gasAccountRepairAction = "repair_legacy_gas_account_role"
)
// GasAccountRepairResult 返回本次修复定位到的账号及审计信息。
type GasAccountRepairResult struct {
AccountIdentity string `json:"account_identity"`
Username string `json:"username"`
OldRoleCode string `json:"old_role_code"`
NewRoleCode string `json:"new_role_code"`
OldDisplayName string `json:"old_display_name"`
NewDisplayName string `json:"new_display_name"`
Operator string `json:"operator"`
RepairedAt *time.Time `json:"repaired_at,omitempty"`
}
// InspectLegacyGasAccount 只读校验修复目标和操作者,不修改任何业务或审计数据。
func InspectLegacyGasAccount(database *gorm.DB, operatorUsername string, displayName string) (*GasAccountRepairResult, error) {
operatorUsername = strings.TrimSpace(operatorUsername)
displayName = strings.TrimSpace(displayName)
if database == nil || operatorUsername == "" || displayName == "" {
return nil, errors.New("database, operator and display name are required")
}
account, operator, err := legacyGasAccountRepairTarget(database, operatorUsername)
if err != nil {
return nil, err
}
return &GasAccountRepairResult{
AccountIdentity: account.Identity,
Username: account.Username,
OldRoleCode: account.RoleCode,
NewRoleCode: gasAdminRole,
OldDisplayName: account.DisplayName,
NewDisplayName: displayName,
Operator: operator.Username,
}, nil
}
func legacyGasAccountRepairTarget(database *gorm.DB, operatorUsername string) (models.GasAccount, models.PlatformAccount, error) {
var accounts []models.GasAccount
if err := database.Where("role_code = ?", legacyGasAccountRole).Limit(2).Find(&accounts).Error; err != nil {
return models.GasAccount{}, models.PlatformAccount{}, fmt.Errorf("find legacy gas account: %w", err)
}
if len(accounts) == 0 {
return models.GasAccount{}, models.PlatformAccount{}, errors.New("legacy gas account role 010 not found")
}
if len(accounts) > 1 {
return models.GasAccount{}, models.PlatformAccount{}, fmt.Errorf("refuse ambiguous repair: found %d gas accounts with role 010", len(accounts))
}
var operator models.PlatformAccount
if err := database.Where("username = ? AND status = ?", operatorUsername, common.StatusEnable).First(&operator).Error; err != nil {
return models.GasAccount{}, models.PlatformAccount{}, fmt.Errorf("find enabled platform operator %q: %w", operatorUsername, err)
}
return accounts[0], operator, nil
}
type gasAccountSnapshot struct {
RoleCode string `json:"role_code"`
DisplayName string `json:"display_name"`
}
// RepairLegacyGasAccount 将唯一一条历史 010 气站账号显式修复为管理员并写入审计日志。
func RepairLegacyGasAccount(database *gorm.DB, operatorUsername string, displayName string) (*GasAccountRepairResult, error) {
operatorUsername = strings.TrimSpace(operatorUsername)
displayName = strings.TrimSpace(displayName)
if database == nil || operatorUsername == "" || displayName == "" {
return nil, errors.New("database, operator and display name are required")
}
var result GasAccountRepairResult
err := database.Transaction(func(tx *gorm.DB) error {
account, operator, err := legacyGasAccountRepairTarget(tx, operatorUsername)
if err != nil {
return err
}
before := gasAccountSnapshot{RoleCode: account.RoleCode, DisplayName: account.DisplayName}
after := gasAccountSnapshot{RoleCode: gasAdminRole, DisplayName: displayName}
beforeJSON, err := json.Marshal(before)
if err != nil {
return fmt.Errorf("marshal repair before snapshot: %w", err)
}
afterJSON, err := json.Marshal(after)
if err != nil {
return fmt.Errorf("marshal repair after snapshot: %w", err)
}
repairedAt := time.Now()
update := tx.Model(&models.GasAccount{}).
Where("identity = ? AND role_code = ?", account.Identity, legacyGasAccountRole).
Updates(map[string]any{"role_code": gasAdminRole, "display_name": displayName, "updated_at": repairedAt})
if update.Error != nil {
return fmt.Errorf("repair legacy gas account: %w", update.Error)
}
if update.RowsAffected != 1 {
return errors.New("legacy gas account changed concurrently; repair aborted")
}
audit := models.AuditOperationLog{
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
OperatorIdentity: operator.Identity,
OperatorName: operator.Username,
ResourceType: "gas_account",
ResourceIdentity: account.Identity,
Action: gasAccountRepairAction,
BeforeValue: string(beforeJSON),
AfterValue: string(afterJSON),
OccurredAt: repairedAt,
Remark: "显式修复历史非法气站角色编码 010并补齐显示名称",
}
if err := tx.Create(&audit).Error; err != nil {
return fmt.Errorf("write gas account repair audit: %w", err)
}
result = GasAccountRepairResult{
AccountIdentity: account.Identity,
Username: account.Username,
OldRoleCode: before.RoleCode,
NewRoleCode: after.RoleCode,
OldDisplayName: before.DisplayName,
NewDisplayName: after.DisplayName,
Operator: operator.Username,
RepairedAt: &repairedAt,
}
return nil
})
if err != nil {
return nil, err
}
return &result, nil
}