Files
platforms/backend/api/internal/repair/gas_account.go
2026-08-17 22:34:02 +08:00

149 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}