开发规范

删除钩子、分层日志与异常追踪规范

区分逻辑删除、物理删除、恢复、引用保护与日志责任。

浏览全部手册
本页目录
文档来源与 Markdown 原文

权威正文:ineed-core/docs/project-development/delete-hook-and-logging-guide.md。网站按工作区快照同步,原文中的历史日期和验证范围保留。

开始编码前核对同版本源码;跨仓文件引用可在源码定位目录查找。

下载 Markdown 原文 ↓

删除生命周期钩子映射

Ineed-Core 当前删除与恢复扩展必须对齐 LogicDeleteManager / PhysicsDeleteManager 中已经存在的真实方法名:

业务语义 真实钩子 适用时机
逻辑删除前校验 / 逻辑删除前拦截 beforeLogicDelete 逻辑删除标记真正执行之前
逻辑删除成功后的扩展 afterLogicDelete 逻辑删除标记已经成功落库之后
物理删除前校验 / 物理删除前拦截 beforePhysicsDelete 物理删除真正执行之前
物理删除成功后的扩展 afterPhysicsDelete 物理删除已经成功落库之后
恢复前准备 beforeRecovery 恢复逻辑删除数据之前
恢复后的补偿处理 afterRecovery 恢复已经成功之后

业务口语里常说的 afterDelete 必须先确认删除语义:

  • 如果是逻辑删除,真实实现点是 LogicDeleteManager.afterLogicDelete(...)
  • 如果是物理删除,真实实现点是 PhysicsDeleteManager.afterPhysicsDelete(...)

级联删除落位规则

级联删除属于物理删除语义,不属于逻辑删除语义。

逻辑删除只负责把当前对象标记为已删除,并保留后续恢复的可能性。逻辑删除的 afterLogicDelete 可以记录审计、刷新索引、失效缓存或发布逻辑删除事件,但不应该物理清理从表、关系表、附件对象或授权关系。

物理删除级联按依赖方向分两类落位:

  • 会影响当前实体物理删除能否成功的强依赖清理,放在 beforePhysicsDelete。例如关系表、从表、授权关系、明细表等存在外键或业务强依赖时,应先清理依赖数据,再删除当前实体。
  • 依赖当前实体已经物理删除成功的外部副作用,放在 afterPhysicsDelete。例如回收不可恢复资源、失效外部索引、发布明确的删除完成事件、记录完成日志。

推荐放在 beforePhysicsDelete 的动作包括:

  • 删除当前实体作为主对象时必须同步清理的多对多关系表
  • 删除当前实体前必须删除的从表、明细表或子节点
  • 删除树形节点前按 idPath / parent 关系定位并物理删除后代
  • 删除当前实体前必须解除的授权关系、绑定关系或业务强依赖

核心原则:物理删除时,必须在 beforePhysicsDelete 中级联删除所有关联数据的从表/引用行(多对多关系表同样适用),确保不因 FK 约束阻断物理删除主流程。每个引用方的级联删除顺序应遵循「先删子、后删父;先删引用方、后删被引用方」。

推荐放在 afterPhysicsDelete 的动作包括:

  • 物理删除成功后再回收不可恢复资源
  • 物理删除成功后再刷新外部索引或缓存
  • 物理删除成功后再发布删除完成事件
  • 物理删除成功后再记录明确的业务完成日志

判断标准是:如果不先执行会导致当前实体删不掉,放 beforePhysicsDelete;如果必须确认当前实体已经删掉才有意义,放 afterPhysicsDelete

forceDeletephysicsDelete 的差异只应体现在是否跳过状态判断并直接进入物理删除流程;不要为 forceDelete 额外发明 beforeForceDelete / afterForceDelete 钩子。需要级联清理时仍复用物理删除生命周期。

删除前置保护

删除前校验与拦截逻辑统一落在 beforeLogicDelete

适合放在 beforeLogicDelete 的动作包括:

  • 检查当前实体是否允许删除
  • 检查数据状态是否固定、锁定,或被其他业务状态规则禁止删除
  • 检查是否存在必须先解除的前置依赖
  • 对不满足删除条件的场景直接抛出业务异常

checkCustomCanDelete:直接外键引用保护

如果表有直接的一对多/一对一外键关联(多对多关系表除外),必须覆写 LogicDeleteManager.checkCustomCanDelete(...) 查询所有引用点。只要存在未删除的引用数据,就抛 DataInUseException 阻止逻辑删除。

抛异常时必须使用 FK 约束的 i18n key,而非硬编码中文文案。直接引用保护属于“引用目标正在被使用”语义,i18n key 格式为 {referencingTableName}.fk.in-use.{constraintName},其中 referencingTableName 是引用方表,也就是 FK 所在表;不要写成被引用目标表。constraintName 是数据库真实 FK 约束名。

@Override
public void checkCustomCanDelete(final Demo entity) {
    final Long refCount = getQueryFactory()
        .select(QRefTable.refTable.id.count())
        .from(QRefTable.refTable)
        .where(
            QRefTable.refTable.deleted.eq(CoreConstant.YESNO.NO.getByte()),
            QRefTable.refTable.demoId.eq(entity.getId())
    )
    .fetchOne();
    if (refCount != null && refCount > 0) {
        throw new DataInUseException("demo_ref_table.fk.in-use.fk_constraint_name");
    }
}

对应的 i18n 资源需要在模块的 messages.properties / messages_en.properties / messages_zh_TW.properties 中同步补齐:

# 以 config.category 为例:
icfg_category_simple_value.fk.in-use.fk_icfg_cat_simple_cat_id=分类类型定义数据被分类项(简单)使用,不能删除或更新
icfg_category_tree_value.fk.in-use.fk_icfg_cat_tree_cat_id=分类类型定义数据被分类项(树形)使用,不能删除或更新

DataInUseExceptionuserObjectName 最终通过 I18nExceptionHandlerI18nUtil.getMessage() 查 i18n 资源,因此同一 FK key 可以覆盖简体中文、英文、繁体中文三语,并且与数据库 FK 违规时的自动解析消息共享同一套 key。数据库异常解析会优先使用 SQL 错误中解析出的表名精确查找 {tableName}.fk.*.{constraintName},解析不到表名时才按约束名后缀兼容查找。

同一个数据库 FK 约束必须准备两类文案:

  • {referencingTableName}.fk.not-found.{constraintName}:子表 INSERT / UPDATE 时引用的目标数据不存在
  • {referencingTableName}.fk.in-use.{constraintName}:父表 DELETE / UPDATE 时已有引用数据正在使用目标数据

历史 {basePackage}.{ReferencingEntity}.fk.{constraintName} 仅作为兼容回退,新模块和新文案不要再使用单一 FK key。

直接 FK 引用保护与通用 beforeLogicDelete 的区别:checkCustomCanDeleteLogicDeleteManager 定义的独立扩展点,在 checkCanDelete(null、租户、固定/锁定状态)之后、beforeLogicDelete 之前执行。它只负责「FK 引用计数查询」,不混合其他业务拦截。

如果某个动作的目标是”阻止删除发生”,就应该落在 beforeLogicDelete(通用拦截)或 checkCustomCanDelete(FK 引用拦截),而不是等删除完成后再补救。

恢复后补偿

恢复后的关联处理统一落在 afterRecovery

适合放在 afterRecovery 的动作包括:

  • 恢复后重建关联关系
  • 恢复后补齐状态同步
  • 恢复后执行重新开放或重新挂接动作

如果某个动作依赖“数据已经恢复成功”,就不应放在 beforeRecovery,而应放在 afterRecovery

日志总体原则

0. SLF4J 唯一允许的日志门面

项目中所有日志输出必须通过 SLF4J。禁止使用以下方式:

  • System.out.println / System.err.println
  • java.util.logging(JUL)
  • Log4j / Log4j2 原生 API
  • commons-logging(JCL)

简单判断:只要能拿到 org.slf4j.Logger 实例,就必须用它;不要绕开 SLF4J 直接调用底层实现。

唯一的例外:interface 的默认方法无法使用 @Slf4j(Lombok 不支持接口字段),此时允许通过 LoggerFactory.getLogger(...) 声明静态 Logger,这也是 ineed-core 框架接口当前的做法。

1. 新写具体类统一使用 @Slf4j

开发者、代码生成器或自动化工具在新增具体业务类时,统一直接使用 Lombok 的 @Slf4j

  • Controller
  • Service
  • Manager
  • Repository / EntityManager / Operation 的自定义实现类
  • 删除钩子实现类
  • Filter
  • Handler
  • Listener
  • Task / Initializer / Engine

不要在新生成的具体类里继续手写 LoggerFactory.getLogger(...)

例外只有一类:Ineed-Core 现有的接口默认实现,例如 ICreateControllerICreateServiceILogicDeleteControllerIAccountService 等,由于它们本身是接口默认方法,已经使用接口内静态 Logger,这是框架底座实现,不是新增业务代码应该模仿的写法。

2. 标准支持接口已有默认日志

当前框架已经在标准支持接口里提供了一部分默认日志:

层次 已有默认日志入口
Controller ICreateControllerIUpdateControllerILogicDeleteControllerIAccountControllerIEnableControllerISearchController
Service ICreateServiceIUpdateServiceILogicDeleteServiceIAccountServiceISearchServiceIOrderService
数据回调 / 基础设施 JpaEntityCallbackJdbcEntityCallbackMybatisFlexEntityCallbackR2dbcEntityCallbackWebMvcOperateInfoFilterWebFluxOperateInfoFilterGlobalExceptionHandler

这意味着:

  • 如果某个 Controller / Service 只是纯粹继承标准接口、没有任何手写业务方法,默认日志已经覆盖了标准 CRUD / Query / DataStatus / Account 主链。
  • 即便如此,具体类仍然建议保留 @Slf4j,便于后续补充自定义接口时直接沿统一写法扩展。
  • 不要为了“每层都打日志”而在只调用接口默认方法的空壳类里再人为包一层重复日志。

3. 自定义方法必须显式补日志

凡是自己手写的方法,特别是以下几类,必须显式补日志:

  • 自定义 Controller 接口
  • 自定义 Service 编排方法
  • Manager 中的业务规则方法
  • 删除 / 恢复钩子方法
  • 自定义 Repository / EntityManager / Operation
  • 定时任务、初始化器、监听器、过滤器、鉴权处理器

4. 日志级别统一建议

级别 使用场景
info 写操作入口、登录、安全敏感动作、批量任务开始/完成、初始化开始/完成、跨表业务主编排开始/完成
debug 读操作入口、关键分支判断、关联数量、命中条件、动态查询参数摘要、批处理中间节点
warn 可预期但值得关注的异常分支,例如重复跳过、可恢复失败、缺少可选依赖、补偿动作未执行
error 已捕获异常且准备继续抛出,或者进入明确失败分支

5. 敏感信息禁止直接打印

日志里禁止直接输出以下内容:

  • 明文密码
  • 新密码 / 旧密码
  • Token 全串
  • 密钥 / 私钥 / 密文原文
  • 完整凭证对象
  • 大体量二进制内容

可以输出的替代信息包括:

  • principalId
  • identityType
  • tenantKey
  • resourceKey
  • id
  • idSet.size()
  • 经过裁剪的查询条件摘要

LogHelper 统一用户注入 API

Ineed-Core 提供了 LogHelper(阻塞式)和 ReactiveLogHelper(响应式)两个统一的日志辅助工具。它们与 @Slf4j 不冲突——@Slf4j 负责生成 log 实例,LogHelper/ReactiveLogHelper 负责在每次日志输出时自动注入当前操作用户信息。

API

两个类提供完全一致的静态方法:

// 导入
import com.ineed.base.context.LogHelper;           // 阻塞式(WebMvc)
import com.ineed.base.context.ReactiveLogHelper;    // 响应式(WebFlux)

// 用法:与 log.xxx(format, args) 相同,只是第一个参数传入 log 实例
LogHelper.debug(log, "message, id = {}", id);
LogHelper.info(log, "message, id = {}", id);
LogHelper.warn(log, "message, id = {}", id);
LogHelper.error(log, "message, id = {}", id);

选择规则

场景 使用
WebMvc Controller / Web 适配层 @Slf4j + LogHelper
WebFlux Controller / Web 适配层 @Slf4j + ReactiveLogHelper
Engine Service / Manager / Repository(阻塞式业务链路) LogHelper
Engine Service / Manager / Repository(响应式业务链路) ReactiveLogHelper
ineed-core 框架接口默认方法(engine + webmvc) LogHelper(接口内使用静态 Logger)
ineed-core 框架接口默认方法(engine-reactive + webflux) ReactiveLogHelper(接口内使用静态 Logger)

边界约定:

  • LogHelper 位于 ineed-core-contextReactiveLogHelper 位于 ineed-core-context-reactive。只有模块已经直接依赖对应 context 模块,或当前模块本来就是 Web / Engine / Manager / Repository 等业务链路模块时,才迁移到 helper。
  • 不要为了单条日志给纯 core、纯 runtime、独立工具、agent、测试辅助模块新增 ineed-core-context / ineed-core-context-reactive 依赖。此类模块继续保留 @Slf4j + log.xxx(...),但仍必须遵守 SLF4J 门面、敏感信息保护和异常对象传递规则。
  • Web 侧具体类(Controller、Filter、Handler、Listener、Web 适配 Service)继续使用 Lombok @Slf4j 生成 log 实例;需要输出业务日志时,把已有 log 传给 LogHelper / ReactiveLogHelper。不要自己构建 Logger、日志对象、请求描述对象或另一套日志上下文。
  • Reactive 链路必须使用 ReactiveLogHelper;不要在 WebFlux / R2DBC 代码中混用阻塞式 LogHelper

用户信息来源

  • LogHelperContextHandler.getOperationUserJson() 获取,底层基于 ThreadLocal
  • ReactiveLogHelperReactiveContextHandler.getThreadLocalUser() 获取,通过 Reactor Context → ThreadLocal 桥接(与 Spring Security 的模式一致)
  • 未登录时自动回退为匿名用户 JSON

日志输出效果

// 使用 LogHelper 前
// CreateService.save(), ao = CreateAO(name=test)

// 使用 LogHelper 后
// user={"principalId":"u123","principalAccount":"zhangsan","principalName":"张三","tenantKey":"t1"}, CreateService.save(), ao = CreateAO(name=test)

框架接口默认方法已统一使用

以下框架标准接口的默认方法已全部迁移到 LogHelper / ReactiveLogHelper

层次 已使用 LogHelper/ReactiveLogHelper 的接口
Engine Service ICreateServiceIUpdateServiceILogicDeleteServiceIPhysicsDeleteServiceIDefaultServiceIEnableServiceILockServiceIPrimaryServiceIPublishServiceIAccountServiceIOrderServiceIDuplicateServiceIFindByIdServiceISearchService
Engine Service (Reactive) 同上,对应 reactive 版本
WebMvc Controller ICreateControllerIUpdateControllerILogicDeleteControllerIPhysicsDeleteControllerIDefaultControllerIEnableControllerILockControllerIPrimaryControllerIPublishControllerIAccountControllerIOrderControllerIImportControllerIExportControllerIDuplicateController
WebFlux Controller 同上,对应 webflux 版本

业务代码建议

  • 业务自定义 Controller 方法在 WebMvc 中使用 LogHelper,在 WebFlux 中使用 ReactiveLogHelper;入口日志仍然放在手动校验之前,保证校验失败也能留下调用痕迹
  • WebFlux 自定义 Controller 方法的入口日志必须放在 Mono.defer(() -> { ... }) 内,与 ResultBuild.success(serviceMono) 一起延迟到订阅期执行,避免链外提前读取响应式上下文或提前产生副作用
  • 业务自定义 ServiceManagerRepository 方法在业务链路模块中使用 LogHelper(或 ReactiveLogHelper)替代 log.xxx(),确保排查问题时能追溯到操作人
  • 具体类仍然使用 @Slf4j 生成 log 实例;调用 helper 时只把这个已有 log 作为第一个参数传入
  • LogHelper 内部已经做了级别判断(如 log.isInfoEnabled()),业务代码不需要再在外面包一层 if

分层日志方案

Controller 层

Controller 的职责是 API 入口,因此日志重点是“谁调用了什么动作,入口参数是什么”。

规则如下:

  • 标准支持接口方法继续复用框架默认日志。
  • 自定义 Controller 方法必须在真正执行业务前记录入口日志。
  • 手动触发 Jakarta Validation 或其它参数校验的自定义方法,入口日志放在校验之前;这样即使校验失败,也能在日志中看到调用痕迹。
  • 纯查询、辅助查询、字典类接口优先 debug
  • 创建、编辑、删除、导入、导出、登录、切换租户、权限刷新等动作优先 info
  • 一般不要在 Controller 中捕获业务异常只为打印一遍日志;异常统一交给下层抛出和 GlobalExceptionHandler 兜底。

推荐记录内容:

  • 类名 + 方法名
  • id / idSet
  • 关键查询对象
  • 关键业务参数摘要

示例:

@Slf4j
public class DemoController {

  public ResponseResults<Void> bindRole(final String id, final BindRoleAO ao) {
    LogHelper.info(log, "DemoController.bindRole(), id = {}, roleIdSet = {}", id, ao.getRoleIdSet());
    demoService.bindRole(id, ao);
    return ResultBuild.success();
  }

  public ResponseResults<QueryResults<DemoVO>> searchAudit(final DemoAuditQuery query) {
    LogHelper.debug(log, "DemoController.searchAudit(), query = {}", query);
    return ResultBuild.success(demoService.searchAudit(query));
  }
}

Service 层

Service 是事务边界和业务编排边界,因此日志重点是“一次业务用例开始了、关键编排节点走到了哪、失败原因是什么”。

规则如下:

  • 标准 CRUD / Query / DataStatus / Account 默认方法继续复用接口日志。
  • 自定义 Service 方法必须至少记录一条入口日志。
  • Manager 编排、批量处理、初始化、补偿等方法,建议再补关键节点 debug 日志。
  • 明显的长链路操作建议补一条完成日志。
  • 如果方法里 catch 了异常,就必须 log.error(..., e) 后继续抛出,不吞异常。

推荐记录内容:

  • 类名 + 方法名
  • tenantKey
  • 主对象 id
  • 关键步骤名
  • 批量数量

示例:

@Slf4j
@Service
public class DemoService {

  @Transactional(rollbackFor = Exception.class)
  public void bindRole(final String id, final BindRoleAO ao) {
    LogHelper.info(log, "DemoService.bindRole(), id = {}, roleIdSet = {}", id, ao.getRoleIdSet());
    try {
      LogHelper.debug(log, "DemoService.bindRole(), step = validate-demo");
      demoManager.checkCanBindRole(id);

      LogHelper.debug(log, "DemoService.bindRole(), step = replace-relation");
      demoRoleRelationManager.replaceRelation(id, ao.getRoleIdSet());

      LogHelper.info(log, "DemoService.bindRole() completed, id = {}, relationCount = {}", id, ao.getRoleIdSet().size());
    } catch (final Exception e) {
      LogHelper.error(log, "DemoService.bindRole() is error : {}.", e.getMessage(), e);
      throw e;
    }
  }
}

Manager 层

Manager 不负责整条请求链路的入口审计,它负责“单类业务规则”和“局部实体规则”,因此日志重点是“规则判断、状态变化、钩子清理、补偿结果”。

规则如下:

  • 如果 Manager 只是纯继承标准 CRUD / Query 能力且没有手写方法,不需要为了形式强行打印空日志。
  • 自定义 Manager 方法、规则判断方法、删除/恢复钩子必须使用 @Slf4j 并记录关键业务动作。
  • beforeLogicDelete 重点记录逻辑删除拦截条件和对象标识。
  • afterLogicDelete 重点记录逻辑删除后的审计、缓存、索引、事件等可恢复扩展,不做级联物理清理。
  • beforePhysicsDelete 重点记录物理删除拦截条件和对象标识。
  • afterPhysicsDelete 重点记录级联物理清理动作和关联数量。
  • afterRecovery 重点记录恢复后的补偿动作和关联数量。
  • 规则拦截前如果需要补充定位信息,可以使用 warn;如果只是普通判断过程,使用 debug 即可。

推荐记录内容:

  • tenantKey
  • 主对象 id
  • 关联对象数量
  • 当前规则判断结果

示例:

@Slf4j
@Component
public class DemoManager {

  public void beforeLogicDelete(final Demo beDelete) {
    LogHelper.debug(log, "DemoManager.beforeLogicDelete(), tenantKey = {}, id = {}", beDelete.getTenantKey(), beDelete.getId());
    if (beDelete.isFixed()) {
      LogHelper.warn(log, "DemoManager.beforeLogicDelete() blocked, tenantKey = {}, id = {}, reason = fixed-data",
          beDelete.getTenantKey(), beDelete.getId());
      throw StatefulException.of(ErrorCode.DATA_FIXED_CANNOT_UPDATE);
    }
  }

  public void afterPhysicsDelete(final Demo beDelete) {
    final int relationCount = demoRelationRepository.deleteByDemoId(beDelete.getId());
    LogHelper.info(log, "DemoManager.afterPhysicsDelete(), tenantKey = {}, id = {}, relationCount = {}",
        beDelete.getTenantKey(), beDelete.getId(), relationCount);
  }
}

Repository / EntityManager / Operation 层

这一层不应该把普通单表 CRUD 全部重复打一遍日志,否则会制造噪音。

规则如下:

  • 对于框架已有的标准持久化方法,通常不额外加日志。
  • 只有在存在自定义 SQL、动态 SQL 组装、批量更新、元数据扫描、跨库连接、复杂查询降级等情况时,才补必要日志。
  • 推荐记录“参数摘要、命中数量、耗时、失败原因”,不要打印整包大对象。
  • 如果只是单纯 save/update/findById,不要再打一遍低价值日志。

推荐记录内容:

  • id
  • idSet.size()
  • 关键查询条件摘要
  • 结果数量
  • 耗时

示例:

@Slf4j
@Repository
public class DemoJdbcRepository {

  public List<DemoVO> searchCustom(final DemoQuery query) {
    LogHelper.debug(log, "DemoJdbcRepository.searchCustom(), tenantKey = {}, keyword = {}, pageSize = {}",
        query.getTenantKey(), query.getSearchKey(), query.getPageSize());
    try {
      return jdbcTemplate.query(SQL, params, rowMapper);
    } catch (final Exception e) {
      LogHelper.error(log, "DemoJdbcRepository.searchCustom() is error : {}.", e.getMessage(), e);
      throw e;
    }
  }
}

Hook / Task / Listener / Filter / Handler 层

这类类虽然不在标准业务主链的四层内,但往往承担运行时关键动作,因此也必须统一使用 @Slf4j

规则如下:

  • 定时任务、初始化器:开始/完成用 info,中间步骤用 debug
  • 鉴权过滤器、会话过滤器:轻量入口日志用 debug,认证失败或拒绝访问由专门处理器记录。
  • 监听器:成功事件、失败事件优先 debug;真正异常再用 error
  • Handler:不重复打印业务异常栈;聚焦处理器自身的分支与兜底异常。

一次方法的最小日志结构

对自定义方法,推荐最少遵守下面这个结构:

  1. 入口日志
  2. 关键节点日志
  3. 成功完成日志
  4. 异常日志

最小模板:

LogHelper.info(log, "DemoService.doSomething(), id = {}", id);
try {
  LogHelper.debug(log, "DemoService.doSomething(), step = check");
  // business
} catch (final Exception e) {
  LogHelper.error(log, "DemoService.doSomething() is error : {}.", e.getMessage(), e);
  throw e;
}

与现有 Ineed-Core 代码的对齐关系

当前方案不是重新发明一套日志风格,而是基于现有 Ineed-Core 代码收敛出来的统一规则:

  • ICreateControllerILogicDeleteController 等已经负责标准 Web 入口日志
  • ICreateServiceIAccountServiceILogicDeleteService 等已经负责标准服务入口日志与异常日志
  • WebMvcOperateInfoFilter / WebFluxOperateInfoFilter 已经负责请求上下文采集
  • GlobalExceptionHandler 已经负责统一异常输出
  • JpaEntityCallbackJdbcEntityCallbackMybatisFlexEntityCallbackR2dbcEntityCallback 已经负责底层实体回调日志

本规范要求新增代码与这些真实锚点保持一致,而不是平行造轮子。

追踪信息来源

请求级追踪信息来自 WebMvcOperateInfoFilter / WebFluxOperateInfoFilter,不需要每个业务重复采集。

当前至少可以稳定拿到以下上下文字段:

  • requestUrl
  • requestMethod
  • signature
  • applicationKey
  • ip
  • userAgent

业务日志在需要补充追踪时,应优先复用这些已有上下文,而不是重新拼装另一套请求描述模型。

业务代码日志硬约束

新增或修改 Ineed-Core 业务代码时,必须同时遵守:

  • 日志门面必须走 SLF4J:具体类用 @Slf4j,interface 默认方法用 LoggerFactory.getLogger(...)
  • Web 侧具体类统一使用 @Slf4j 生成的 log,调用 LogHelper / ReactiveLogHelper 时只传入这个已有 log,不自定义 Logger 或日志对象,不重复构建请求上下文
  • 已经具备 context 依赖的 Engine Service / Manager / Repository 业务链路日志统一使用 LogHelper(阻塞式)或 ReactiveLogHelper(响应式),不再使用 log.xxx(),确保每条日志自动携带操作用户信息
  • 无 context 依赖的纯 core、runtime、agent、工具、测试辅助模块不要仅为日志新增依赖;继续保留 @Slf4j + log.xxx(...)
  • 标准支持接口已有的默认日志不要重复包一层
  • 自定义 Controller / Service / Manager / Hook / Repository 方法必须显式补日志
  • 只打印必要的关键参数,不打印密码、Token、密钥、完整凭证
  • 发生异常时记录 error 后继续抛出

落地检查清单

  • 是否先区分逻辑删除与物理删除,再映射删除后钩子
  • 逻辑删除前保护是否落在 beforeLogicDelete
  • 直接 FK 引用(非 M2M)是否在 checkCustomCanDelete 中查询引用点,抛 DataInUseException 并使用 FK i18n key
  • FK i18n key 是否符合 {referencingTableName}.fk.in-use.{constraintName} / {referencingTableName}.fk.not-found.{constraintName} 格式,且三语同步
  • 物理删除前保护是否落在 beforePhysicsDelete
  • 物理删除时是否在 beforePhysicsDelete 中级联删除所有关联数据的从表/引用行
  • 逻辑删除后的可恢复扩展是否落在 afterLogicDelete
  • 级联物理清理是否落在 afterPhysicsDelete
  • 恢复后补偿是否落在 afterRecovery
  • 新写具体类是否统一使用 @Slf4j
  • 是否正确复用了框架默认 Controller / Service 日志而没有重复套娃
  • 自定义方法是否包含动作名、关键入参、异常对象
  • Web 侧具体类是否使用 @Slf4j 生成的 log 调用 LogHelper / ReactiveLogHelper,没有引入自定义 Logger、日志对象或重复请求上下文
  • 具备 context 依赖的 Engine Service / Manager / Repository 业务链路是否使用 LogHelper(阻塞式)或 ReactiveLogHelper(响应式)输出日志,确保用户信息已注入
  • 无 context 依赖的底层模块是否没有为了日志新增依赖,且仍遵守 SLF4J 与敏感信息规则
  • 是否避免打印密码、Token、密钥、完整凭证
  • 是否继续复用 WebMvcOperateInfoFilter / WebFluxOperateInfoFilterGlobalExceptionHandlerResultBuild

最后更新:2026-05-16

仍有疑问?按反馈清单整理复现信息 →