删除钩子、分层日志与异常追踪规范
区分逻辑删除、物理删除、恢复、引用保护与日志责任。
浏览全部手册
本页目录
文档来源与 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。
forceDelete 与 physicsDelete 的差异只应体现在是否跳过状态判断并直接进入物理删除流程;不要为 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=分类类型定义数据被分类项(树形)使用,不能删除或更新
DataInUseException 的 userObjectName 最终通过 I18nExceptionHandler 走 I18nUtil.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 的区别:checkCustomCanDelete 是 LogicDeleteManager 定义的独立扩展点,在 checkCanDelete(null、租户、固定/锁定状态)之后、beforeLogicDelete 之前执行。它只负责「FK 引用计数查询」,不混合其他业务拦截。
如果某个动作的目标是”阻止删除发生”,就应该落在 beforeLogicDelete(通用拦截)或 checkCustomCanDelete(FK 引用拦截),而不是等删除完成后再补救。
恢复后补偿
恢复后的关联处理统一落在 afterRecovery。
适合放在 afterRecovery 的动作包括:
- 恢复后重建关联关系
- 恢复后补齐状态同步
- 恢复后执行重新开放或重新挂接动作
如果某个动作依赖“数据已经恢复成功”,就不应放在 beforeRecovery,而应放在 afterRecovery。
日志总体原则
0. SLF4J 唯一允许的日志门面
项目中所有日志输出必须通过 SLF4J。禁止使用以下方式:
System.out.println/System.err.printlnjava.util.logging(JUL)Log4j/Log4j2原生 APIcommons-logging(JCL)
简单判断:只要能拿到 org.slf4j.Logger 实例,就必须用它;不要绕开 SLF4J 直接调用底层实现。
唯一的例外:interface 的默认方法无法使用 @Slf4j(Lombok 不支持接口字段),此时允许通过 LoggerFactory.getLogger(...) 声明静态 Logger,这也是 ineed-core 框架接口当前的做法。
1. 新写具体类统一使用 @Slf4j
开发者、代码生成器或自动化工具在新增具体业务类时,统一直接使用 Lombok 的 @Slf4j:
ControllerServiceManagerRepository / EntityManager / Operation的自定义实现类- 删除钩子实现类
FilterHandlerListenerTask / Initializer / Engine
不要在新生成的具体类里继续手写 LoggerFactory.getLogger(...)。
例外只有一类:Ineed-Core 现有的接口默认实现,例如 ICreateController、ICreateService、ILogicDeleteController、IAccountService 等,由于它们本身是接口默认方法,已经使用接口内静态 Logger,这是框架底座实现,不是新增业务代码应该模仿的写法。
2. 标准支持接口已有默认日志
当前框架已经在标准支持接口里提供了一部分默认日志:
| 层次 | 已有默认日志入口 |
|---|---|
Controller |
ICreateController、IUpdateController、ILogicDeleteController、IAccountController、IEnableController、ISearchController 等 |
Service |
ICreateService、IUpdateService、ILogicDeleteService、IAccountService、ISearchService、IOrderService 等 |
| 数据回调 / 基础设施 | JpaEntityCallback、JdbcEntityCallback、MybatisFlexEntityCallback、R2dbcEntityCallback、WebMvcOperateInfoFilter、WebFluxOperateInfoFilter、GlobalExceptionHandler 等 |
这意味着:
- 如果某个
Controller/Service只是纯粹继承标准接口、没有任何手写业务方法,默认日志已经覆盖了标准 CRUD / Query / DataStatus / Account 主链。 - 即便如此,具体类仍然建议保留
@Slf4j,便于后续补充自定义接口时直接沿统一写法扩展。 - 不要为了“每层都打日志”而在只调用接口默认方法的空壳类里再人为包一层重复日志。
3. 自定义方法必须显式补日志
凡是自己手写的方法,特别是以下几类,必须显式补日志:
- 自定义
Controller接口 - 自定义
Service编排方法 Manager中的业务规则方法- 删除 / 恢复钩子方法
- 自定义
Repository/EntityManager/Operation - 定时任务、初始化器、监听器、过滤器、鉴权处理器
4. 日志级别统一建议
| 级别 | 使用场景 |
|---|---|
info |
写操作入口、登录、安全敏感动作、批量任务开始/完成、初始化开始/完成、跨表业务主编排开始/完成 |
debug |
读操作入口、关键分支判断、关联数量、命中条件、动态查询参数摘要、批处理中间节点 |
warn |
可预期但值得关注的异常分支,例如重复跳过、可恢复失败、缺少可选依赖、补偿动作未执行 |
error |
已捕获异常且准备继续抛出,或者进入明确失败分支 |
5. 敏感信息禁止直接打印
日志里禁止直接输出以下内容:
- 明文密码
- 新密码 / 旧密码
- Token 全串
- 密钥 / 私钥 / 密文原文
- 完整凭证对象
- 大体量二进制内容
可以输出的替代信息包括:
principalIdidentityTypetenantKeyresourceKeyididSet.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-context,ReactiveLogHelper位于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。
用户信息来源
LogHelper从ContextHandler.getOperationUserJson()获取,底层基于 ThreadLocalReactiveLogHelper从ReactiveContextHandler.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 | ICreateService、IUpdateService、ILogicDeleteService、IPhysicsDeleteService、IDefaultService、IEnableService、ILockService、IPrimaryService、IPublishService、IAccountService、IOrderService、IDuplicateService、IFindByIdService、ISearchService 等 |
| Engine Service (Reactive) | 同上,对应 reactive 版本 |
| WebMvc Controller | ICreateController、IUpdateController、ILogicDeleteController、IPhysicsDeleteController、IDefaultController、IEnableController、ILockController、IPrimaryController、IPublishController、IAccountController、IOrderController、IImportController、IExportController、IDuplicateController 等 |
| WebFlux Controller | 同上,对应 webflux 版本 |
业务代码建议
- 业务自定义
Controller方法在 WebMvc 中使用LogHelper,在 WebFlux 中使用ReactiveLogHelper;入口日志仍然放在手动校验之前,保证校验失败也能留下调用痕迹 - WebFlux 自定义
Controller方法的入口日志必须放在Mono.defer(() -> { ... })内,与ResultBuild.success(serviceMono)一起延迟到订阅期执行,避免链外提前读取响应式上下文或提前产生副作用 - 业务自定义
Service、Manager、Repository方法在业务链路模块中使用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,不要再打一遍低价值日志。
推荐记录内容:
ididSet.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:不重复打印业务异常栈;聚焦处理器自身的分支与兜底异常。
一次方法的最小日志结构
对自定义方法,推荐最少遵守下面这个结构:
- 入口日志
- 关键节点日志
- 成功完成日志
- 异常日志
最小模板:
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 代码收敛出来的统一规则:
ICreateController、ILogicDeleteController等已经负责标准 Web 入口日志ICreateService、IAccountService、ILogicDeleteService等已经负责标准服务入口日志与异常日志WebMvcOperateInfoFilter/WebFluxOperateInfoFilter已经负责请求上下文采集GlobalExceptionHandler已经负责统一异常输出JpaEntityCallback、JdbcEntityCallback、MybatisFlexEntityCallback、R2dbcEntityCallback已经负责底层实体回调日志
本规范要求新增代码与这些真实锚点保持一致,而不是平行造轮子。
追踪信息来源
请求级追踪信息来自 WebMvcOperateInfoFilter / WebFluxOperateInfoFilter,不需要每个业务重复采集。
当前至少可以稳定拿到以下上下文字段:
requestUrlrequestMethodsignatureapplicationKeyipuserAgent
业务日志在需要补充追踪时,应优先复用这些已有上下文,而不是重新拼装另一套请求描述模型。
业务代码日志硬约束
新增或修改 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/WebFluxOperateInfoFilter、GlobalExceptionHandler、ResultBuild
最后更新:2026-05-16
仍有疑问?按反馈清单整理复现信息 →