package jnpf.audit.sdk.annotation;
|
|
import jnpf.audit.AuditConsts;
|
|
import java.lang.annotation.ElementType;
|
import java.lang.annotation.Retention;
|
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.Target;
|
|
/**
|
* 层 2 审计注解:给非 CRUD 动作或需要精确动作名的场景一行声明语义。
|
*
|
* <p>与 lims 旧 {@code @BizLog} 的关系(M2 影子期):**并存不互斥**,同方法双标注、各写各表,
|
* 靠 operationId 精确配对做 A/B;M3 达标后才删旧的。
|
*
|
* <p>已知限制沿袭:同类自调用绕开代理时注解不生效(与 @BizLog 同)。
|
*/
|
@Target(ElementType.METHOD)
|
@Retention(RetentionPolicy.RUNTIME)
|
public @interface AuditLog {
|
|
/** 事件类型,受控枚举(DATA_CHANGE / BIZ_ACTION / E_SIGNATURE),默认业务动作 */
|
String eventType() default AuditConsts.TYPE_BIZ_ACTION;
|
|
/** 动作码:基础三动作 CREATE/UPDATE/DELETE 或业务自定义大写蛇形 */
|
String action();
|
|
/**
|
* 方法抛异常时用这个动作码记一条**失败事件**;留空(默认)= 抛异常时不记录。
|
*
|
* <p>默认留空是为了**向后兼容**:M2 已有的层 2 埋点行为一字不变(只在成功后记录)。
|
* 想要"失败也留痕"的场景(电子签名、权限校验、密码修改等)显式声明即可,
|
* 例如 {@code action="SIGN", failureAction="SIGN_FAIL"}。
|
*
|
* <p>为什么拆成两个动作码而不是共用一个加标记:`audit_events` 上有
|
* {@code idx_audit_events_action_code} 索引,按动作码筛是最快路径;而"签名失败尝试"
|
* 在合规上是独立关注对象(连续失败可能是有人在试密码)。共用一个码就只能去 extra
|
* 的 text 列里捞。
|
*
|
* <p>⚠️ 失败事件求值时 {@code #result} 恒为 null(方法没有返回值)。凡引用了
|
* {@code #result} 的 SpEL 在失败路径上取不到值——切面对**每个字段单独兜底**
|
* (求值异常只丢该字段并 WARN,不会丢掉整条事件),但写表达式时优先用安全导航
|
* {@code #result?.id},让意图显式。
|
*/
|
String failureAction() default "";
|
|
/** 失败事件的人读动作名,留空则回落 {@link #failureAction()} */
|
String failureLabel() default "";
|
|
/**
|
* 成功时是否记录事件。默认 true(M2 既有埋点行为一字不变)。
|
*
|
* <p>声明 {@code false} 的唯一正当场景是:<b>成功事实要由别处更完整地记录</b>。
|
* 当前只有电子签名——签名成功当场落库会产生「签名有记录、业务失败没记录」的孤儿行,
|
* 药厂合规上不接受(签名 + 业务是一个整体)。成功侧改由层 0 在<b>业务保存成功后</b>
|
* 据表单里的签名字段补写,失败侧({@code SIGN_FAIL})仍在此记录——
|
* 失败的签名尝试是独立的安全关注对象(连续失败可能是有人在试密码),
|
* 它不隶属于任何一次成功业务,没有"跟着业务一起不记"的道理。
|
*
|
* <p>⚠️ 声明 false 前必须先确认成功事实<b>确实</b>有另一条链路在记。
|
* 否则这就是在静默地删掉一个埋点。
|
*/
|
boolean recordSuccess() default true;
|
|
/**
|
* {@link #bizCodeExpr()} 求值为空时是否**跳过**记录。默认 true(保持 M2 原行为)。
|
*
|
* <p>声明 {@code false} 表示"没有业务单号也照记"——审计红线是**这次操作发生过不能缺**,
|
* bizCode 只是便于检索。层 0 早就是这个取舍(见 {@code AuditVisualLogListener} 类注释:
|
* 旧实现取不到单号就整条丢弃,新实现留空照记)。
|
*
|
* <p>典型场景:**电子签名**。签名往往先于业务对象存在(先签名拿 biz_sign、再创建请验单),
|
* 此时 dataId 天然为空。留着默认 true 会让签名事件被静默跳过、只在日志里留一行 WARN。
|
*/
|
boolean bizCodeRequired() default true;
|
|
/** 人读动作名,如"请验单 撤回" */
|
String label() default "";
|
|
/** 业务对象类型(对应 audit_events.biz_type) */
|
String bizType() default "";
|
|
/** 业务单号 SpEL,如 {@code "#qingyandan.jianyanLiushuihao"};求值为空则跳过记录并告警 */
|
String bizCodeExpr();
|
|
/** 附加元数据 SpEL,求值结果须为 Map;写进 audit_events.extra */
|
String extraExpr() default "";
|
|
/**
|
* 操作原因 SpEL,写进 {@code audit_events.reason} 独立列。典型是用户在界面填的
|
* "说明 / 原因"(如签名的 note、退回的原因)。成功与失败**都求值**(它取自入参,失败时同样在)。
|
*
|
* <p>🔴 **本列的语义固定为"操作者陈述的业务原因",不随成败切换含义**。失败的技术原因
|
* (异常 message)由切面写进 {@code extra.failureReason},不占用本列——否则查询侧
|
* 拿到一个 reason 值时无法判断它是"用户填的"还是"系统报的",这一列就没法解释了。
|
*/
|
String reasonExpr() default "";
|
|
/** 目标表名(对应 audit_events.target_table) */
|
String targetTable() default "";
|
|
/** 目标行主键 SpEL(对应 audit_events.target_id) */
|
String targetIdExpr() default "";
|
|
/**
|
* 批量目标主键 SpEL,结果可为 Collection、Iterable、数组或逗号分隔字符串。
|
*
|
* <p>配置后,切面对每个主键分别查询修改前后数据,并在每个 field_diffs 项中写入
|
* targetTable/targetId。事件顶层 target_id 仍由 targetIdExpr 决定,通常取第一条。
|
*/
|
String targetIdsExpr() default "";
|
|
/** 是否做字段级 diff;true 时 entityClass + targetIdExpr/targetIdsExpr 至少配置一项 */
|
boolean diff() default false;
|
|
/** diff=true 时用于反射定位 Mapper 的实体类 */
|
Class<?> entityClass() default Void.class;
|
|
}
|