固化七月迭代审计治理进展以隔离线上热修

Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。

Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。

Confidence: medium

Scope-risk: broad

Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。

Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。

Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
This commit is contained in:
2026-08-03 09:47:22 +08:00
parent cf2ff0ac1c
commit b3499adfca
114 changed files with 16961 additions and 2782 deletions

View File

@@ -8,6 +8,7 @@ import (
"regexp"
"time"
accessauditapp "github.com/break/junhong_cmp_fiber/internal/application/accessaudit"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store"
@@ -28,6 +29,8 @@ type AccountServiceInterface interface {
// Service 权限业务服务
type Service struct {
db *gorm.DB
accessAudit accessauditapp.Writer
permissionStore *postgres.PermissionStore
accountRoleStore *postgres.AccountRoleStore
rolePermStore *postgres.RolePermissionStore
@@ -35,6 +38,12 @@ type Service struct {
redisClient *redis.Client
}
// SetAccessAudit 注入权限定义变更的事务审计接缝。
func (s *Service) SetAccessAudit(db *gorm.DB, writer accessauditapp.Writer) {
s.db = db
s.accessAudit = writer
}
// New 创建权限服务
func New(
permissionStore *postgres.PermissionStore,
@@ -60,26 +69,6 @@ func (s *Service) Create(ctx context.Context, req *dto.CreatePermissionRequest)
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
// 验证权限编码格式
if !permCodeRegex.MatchString(req.PermCode) {
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
}
// 检查权限编码唯一性
existing, err := s.permissionStore.GetByCode(ctx, req.PermCode)
if err == nil && existing != nil {
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
}
// 验证 parent_id 存在(如果提供)
if req.ParentID != nil {
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
if err != nil || parent == nil {
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
}
}
// 创建权限
permission := &model.Permission{
PermName: req.PermName,
PermCode: req.PermCode,
@@ -89,14 +78,59 @@ func (s *Service) Create(ctx context.Context, req *dto.CreatePermissionRequest)
ParentID: req.ParentID,
Sort: req.Sort,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: currentUserID,
Updater: currentUserID,
},
}
// 如果未指定 platform默认为 all
if permission.Platform == "" {
permission.Platform = constants.PlatformAll
}
if err := s.permissionStore.Create(ctx, permission); err != nil {
// 验证权限编码格式
if !permCodeRegex.MatchString(req.PermCode) {
appErr := errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "拒绝创建非法权限编码", constants.AuditResultDenied, permission, nil, appErr)
return nil, appErr
}
// 检查权限编码唯一性
existing, err := s.permissionStore.GetByCode(ctx, req.PermCode)
if err == nil && existing != nil {
appErr := errors.New(errors.CodePermCodeExists, "权限编码已存在")
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "拒绝创建重复权限编码", constants.AuditResultDenied, permission, nil, appErr)
return nil, appErr
}
if err != nil && err != gorm.ErrRecordNotFound {
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "创建权限失败", constants.AuditResultFailed, permission, nil, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "检查权限编码失败")
}
// 验证 parent_id 存在(如果提供)
if req.ParentID != nil {
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
if err != nil && err != gorm.ErrRecordNotFound {
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "创建权限失败", constants.AuditResultFailed, permission, nil, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "检查上级权限失败")
}
if err == gorm.ErrRecordNotFound || parent == nil {
appErr := errors.New(errors.CodeNotFound, "上级权限不存在")
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "拒绝创建上级不存在的权限", constants.AuditResultDenied, permission, nil, appErr)
return nil, appErr
}
}
if err := s.runAccessTransaction(ctx, func(tx *gorm.DB) error {
if err := postgres.NewPermissionStore(tx).Create(ctx, permission); err != nil {
return err
}
return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{
ActionCode: constants.AuditActionPermissionCreated, Summary: "创建权限", Result: constants.AuditResultSuccess,
OperatorID: currentUserID, Permissions: permissionChanges(permission, nil, permissionAuditData(permission)),
})
}); err != nil {
permission.ID = 0
s.recordFailure(ctx, constants.AuditActionPermissionCreated, "创建权限失败", constants.AuditResultFailed, permission, nil, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "创建权限失败")
}
@@ -131,6 +165,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePermission
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取权限失败")
}
beforeData := permissionAuditData(permission)
// 更新字段
if req.PermName != nil {
@@ -139,12 +174,20 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePermission
if req.PermCode != nil {
// 验证权限编码格式
if !permCodeRegex.MatchString(*req.PermCode) {
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
appErr := errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "拒绝更新非法权限编码", constants.AuditResultDenied, permission, beforeData, appErr)
return nil, appErr
}
// 检查新权限编码唯一性
existing, err := s.permissionStore.GetByCode(ctx, *req.PermCode)
if err == nil && existing != nil && existing.ID != id {
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
appErr := errors.New(errors.CodePermCodeExists, "权限编码已存在")
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "拒绝更新重复权限编码", constants.AuditResultDenied, permission, beforeData, appErr)
return nil, appErr
}
if err != nil && err != gorm.ErrRecordNotFound {
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "更新权限失败", constants.AuditResultFailed, permission, beforeData, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "检查权限编码失败")
}
permission.PermCode = *req.PermCode
}
@@ -157,8 +200,14 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePermission
if req.ParentID != nil {
// 验证 parent_id 存在
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
if err != nil || parent == nil {
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
if err != nil && err != gorm.ErrRecordNotFound {
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "更新权限失败", constants.AuditResultFailed, permission, beforeData, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "检查上级权限失败")
}
if err == gorm.ErrRecordNotFound || parent == nil {
appErr := errors.New(errors.CodeNotFound, "上级权限不存在")
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "拒绝更新不存在的上级权限", constants.AuditResultDenied, permission, beforeData, appErr)
return nil, appErr
}
permission.ParentID = req.ParentID
}
@@ -171,9 +220,25 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePermission
permission.Updater = currentUserID
if err := s.permissionStore.Update(ctx, permission); err != nil {
var accountIDs []uint
if err := s.runAccessTransaction(ctx, func(tx *gorm.DB) error {
if err := postgres.NewPermissionStore(tx).Update(ctx, permission); err != nil {
return err
}
var err error
accountIDs, err = permissionCacheAccountIDs(ctx, tx, permission.ID)
if err != nil {
return err
}
return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{
ActionCode: constants.AuditActionPermissionUpdated, Summary: "更新权限", Result: constants.AuditResultSuccess,
OperatorID: currentUserID, Permissions: permissionChanges(permission, beforeData, permissionAuditData(permission)),
})
}); err != nil {
s.recordFailure(ctx, constants.AuditActionPermissionUpdated, "更新权限失败", constants.AuditResultFailed, permission, beforeData, err)
return nil, errors.Wrap(errors.CodeInternalError, err, "更新权限失败")
}
s.clearPermissionCaches(ctx, accountIDs)
return permission, nil
}
@@ -181,7 +246,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePermission
// Delete 软删除权限
func (s *Service) Delete(ctx context.Context, id uint) error {
// 检查权限存在
_, err := s.permissionStore.GetByID(ctx, id)
permission, err := s.permissionStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodePermissionNotFound, "权限不存在")
@@ -189,9 +254,27 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
return errors.Wrap(errors.CodeInternalError, err, "获取权限失败")
}
if err := s.permissionStore.Delete(ctx, id); err != nil {
operatorID := middleware.GetUserIDFromContext(ctx)
beforeData := permissionAuditData(permission)
var accountIDs []uint
if err := s.runAccessTransaction(ctx, func(tx *gorm.DB) error {
if err := postgres.NewPermissionStore(tx).Delete(ctx, id); err != nil {
return err
}
var err error
accountIDs, err = permissionCacheAccountIDs(ctx, tx, permission.ID)
if err != nil {
return err
}
return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{
ActionCode: constants.AuditActionPermissionDeleted, Summary: "删除权限", Result: constants.AuditResultSuccess,
OperatorID: operatorID, Permissions: permissionChanges(permission, beforeData, map[string]any{"deleted": true}),
})
}); err != nil {
s.recordFailure(ctx, constants.AuditActionPermissionDeleted, "删除权限失败", constants.AuditResultFailed, permission, beforeData, err)
return errors.Wrap(errors.CodeInternalError, err, "删除权限失败")
}
s.clearPermissionCaches(ctx, accountIDs)
return nil
}
@@ -351,3 +434,70 @@ func (s *Service) matchPermission(permissions []permissionCacheItem, permCode st
}
return false
}
func (s *Service) runAccessTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error {
if s.db == nil || s.accessAudit == nil {
return errors.New(errors.CodeInvalidStatus, "权限审计接缝未配置")
}
return s.db.WithContext(ctx).Transaction(fn)
}
func (s *Service) recordFailure(
ctx context.Context,
actionCode, summary, result string,
permission *model.Permission,
beforeData map[string]any,
originalErr error,
) {
accessauditapp.RecordFailure(ctx, s.db, s.accessAudit, accessauditapp.ChangeAudit{
ActionCode: actionCode, Summary: summary, Result: result,
OperatorID: middleware.GetUserIDFromContext(ctx),
Permissions: permissionChanges(permission, beforeData, nil),
}, originalErr)
}
func permissionChanges(permission *model.Permission, beforeData, afterData map[string]any) []accessauditapp.PermissionChange {
if permission == nil {
return nil
}
return []accessauditapp.PermissionChange{{Permission: permission, BeforeData: beforeData, AfterData: afterData}}
}
func permissionAuditData(permission *model.Permission) map[string]any {
if permission == nil {
return nil
}
return map[string]any{
"perm_name": permission.PermName, "perm_code": permission.PermCode, "perm_type": permission.PermType,
"platform": permission.Platform, "available_for_role_types": permission.AvailableForRoleTypes,
"url": permission.URL, "parent_id": permission.ParentID, "sort": permission.Sort, "status": permission.Status,
}
}
func permissionCacheAccountIDs(ctx context.Context, tx *gorm.DB, permissionID uint) ([]uint, error) {
var roleIDs []uint
if err := tx.WithContext(ctx).Model(&model.RolePermission{}).
Where("perm_id = ?", permissionID).Pluck("role_id", &roleIDs).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询权限关联角色失败")
}
if len(roleIDs) == 0 {
return nil, nil
}
var accountIDs []uint
if err := tx.WithContext(ctx).Model(&model.AccountRole{}).
Where("role_id IN ?", roleIDs).Distinct().Pluck("account_id", &accountIDs).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询权限关联账号失败")
}
return accountIDs, nil
}
func (s *Service) clearPermissionCaches(ctx context.Context, accountIDs []uint) {
if len(accountIDs) == 0 || s.redisClient == nil {
return
}
pipe := s.redisClient.Pipeline()
for _, accountID := range accountIDs {
pipe.Del(ctx, constants.RedisUserPermissionsKey(accountID))
}
_, _ = pipe.Exec(ctx)
}