// 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 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 } // 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" }