Files
break 5e552d99bc 收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
2026-08-05 14:30:54 +08:00

244 lines
7.3 KiB
Go

// Package accessaudit 定义账号权限与组织简单写用例的统一审计接缝。
package accessaudit
import (
"context"
stderrors "errors"
"strconv"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)
// ChangeAudit 是账号权限与组织变更交给统一审计 Port 的事实。
type ChangeAudit struct {
ActionCode string
Summary string
Result string
ErrorCode string
ErrorSummary string
OperatorID uint
ActorKind string
ActorName string
Source string
ScopeType string
Account *model.Account
Accounts []AccountChange
Shop *model.Shop
ParentShop *model.Shop
Enterprise *model.Enterprise
Cards []IotCardChange
CardAuthorizations []EnterpriseCardAuthorizationChange
Devices []DeviceChange
DeviceBindings []DeviceSimBindingChange
DeviceAuthorizations []EnterpriseDeviceAuthorizationChange
PersonalCustomer *model.PersonalCustomer
PersonalPhones []PersonalCustomerPhoneChange
PersonalOpenIDs []PersonalCustomerOpenIDChange
PersonalDevices []PersonalCustomerDeviceChange
PersonalICCIDs []PersonalCustomerICCIDChange
Role *model.Role
Roles []RoleChange
Permissions []PermissionChange
BeforeData map[string]any
AfterData map[string]any
SubjectVisibility string
SubjectSummary string
SubjectData map[string]any
}
// PersonalCustomerPhoneChange 保存个人客户手机号资源变化。
type PersonalCustomerPhoneChange struct {
Phone *model.PersonalCustomerPhone
BeforeData map[string]any
AfterData map[string]any
}
// PersonalCustomerOpenIDChange 保存个人客户微信主体资源变化。
type PersonalCustomerOpenIDChange struct {
OpenID *model.PersonalCustomerOpenID
BeforeData map[string]any
AfterData map[string]any
}
// PersonalCustomerDeviceChange 保存个人客户设备号绑定资源变化。
type PersonalCustomerDeviceChange struct {
Binding *model.PersonalCustomerDevice
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
}
// PersonalCustomerICCIDChange 保存个人客户 ICCID 绑定资源变化。
type PersonalCustomerICCIDChange struct {
Binding *model.PersonalCustomerICCID
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
}
// DeviceChange 保存组织操作关联设备的资源变化与主体安全投影。
type DeviceChange struct {
Device *model.Device
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
SubjectVisibility string
SubjectSummary string
SubjectData map[string]any
}
// DeviceSimBindingChange 保存企业设备授权涉及的卡槽绑定快照。
type DeviceSimBindingChange struct {
Binding *model.DeviceSimBinding
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
}
// EnterpriseDeviceAuthorizationChange 保存企业设备授权记录的直接变化。
type EnterpriseDeviceAuthorizationChange struct {
Authorization *model.EnterpriseDeviceAuthorization
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
}
// IotCardChange 保存组织操作关联卡的资源变化与主体安全投影。
type IotCardChange struct {
Card *model.IotCard
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
SubjectVisibility string
SubjectSummary string
SubjectData map[string]any
}
// EnterpriseCardAuthorizationChange 保存企业卡授权记录的直接变化。
type EnterpriseCardAuthorizationChange struct {
Authorization *model.EnterpriseCardAuthorization
BeforeData map[string]any
AfterData map[string]any
}
// AccountChange 保存店铺操作关联账号的资源角色与直接变化。
type AccountChange struct {
Account *model.Account
Relation string
Role string
BeforeData map[string]any
AfterData map[string]any
}
// RoleChange 保存主体授权中单个角色资源的前后变化。
type RoleChange struct {
Role *model.Role
BeforeData map[string]any
AfterData map[string]any
}
// PermissionChange 保存单个权限资源的前后变化。
type PermissionChange struct {
Permission *model.Permission
BeforeData map[string]any
AfterData map[string]any
}
// Writer 接收账号权限与组织事务内审计事实。
type Writer interface {
WriteAccessChange(context.Context, *gorm.DB, ChangeAudit) error
}
// RecordFailure 在业务回滚后使用独立短事务记录失败或拒绝事实。
func RecordFailure(ctx context.Context, db *gorm.DB, writer Writer, change ChangeAudit, originalErr error) {
fillFailure(changeError(originalErr), &change)
if db == nil || writer == nil {
recordSecondaryFailure(ctx, change, apperrors.New(apperrors.CodeInvalidStatus, "统一组织审计接缝未配置"))
return
}
if err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return writer.WriteAccessChange(ctx, tx, change)
}); err != nil {
recordSecondaryFailure(ctx, change, err)
}
}
func changeError(err error) *apperrors.AppError {
var appErr *apperrors.AppError
if stderrors.As(err, &appErr) {
return appErr
}
return apperrors.New(apperrors.CodeInternalError, "账号权限或组织操作失败")
}
func fillFailure(appErr *apperrors.AppError, change *ChangeAudit) {
if change.Result == "" {
change.Result = constants.AuditResultFailed
}
if change.ErrorCode == "" {
change.ErrorCode = strconv.Itoa(appErr.Code)
}
if change.ErrorSummary == "" {
change.ErrorSummary = appErr.Message
}
}
func recordSecondaryFailure(ctx context.Context, change ChangeAudit, err error) {
value := auditcontext.From(ctx)
auditfailure.RecordSecondaryWriteFailure(
change.ActionCode, resourceKey(change), value.RequestID, value.CorrelationID, change.ErrorCode, err,
)
}
func resourceKey(change ChangeAudit) string {
if change.Account != nil {
if change.Account.ID != 0 {
return strconv.FormatUint(uint64(change.Account.ID), 10)
}
return change.Account.Username
}
if change.Enterprise != nil {
if change.Enterprise.ID != 0 {
return strconv.FormatUint(uint64(change.Enterprise.ID), 10)
}
return change.Enterprise.EnterpriseCode
}
if change.PersonalCustomer != nil {
return strconv.FormatUint(uint64(change.PersonalCustomer.ID), 10)
}
if change.Shop != nil {
if change.Shop.ID != 0 {
return strconv.FormatUint(uint64(change.Shop.ID), 10)
}
return change.Shop.ShopCode
}
if change.Role != nil {
if change.Role.ID != 0 {
return strconv.FormatUint(uint64(change.Role.ID), 10)
}
return change.Role.RoleName
}
for _, permission := range change.Permissions {
if permission.Permission == nil {
continue
}
if permission.Permission.ID != 0 {
return strconv.FormatUint(uint64(permission.Permission.ID), 10)
}
return permission.Permission.PermCode
}
return "unknown"
}