fix(passport): e2e双P0修复——匿名写路径审计列兜底×5写点 + 手机号/验证码明文日志两面合围
- P0-1: @PermitAll匿名无登录态→DefaultDBFieldHandler不填creator/updater撞NOT NULL(V11漏继承审计列,与M-b执行器updater雷同根)。修=Service写点显式兜底"0"(注册/会话开局/会话收尾UPDATE形态/遥测batch+beacon共点/ad计费台账),归属维度user_id/anon_id/creator_user_id不受影响 - P0-2: ApiAccessLogInterceptor非prod无条件打印原始请求体(不读@ApiAccessLog注解,framework不可改)→staging抬该logger至WARN关控制台面+三端点sanitizeKeys脱敏持久化面;SmsSendConsumer脱敏mobile、code不打印 - 单测: 4模块匿名落库creator='0'断言+登录态不强置负向对照 - e2e第一轮判定: E3/E8/E12/UI过门;E2/E4/E6/E7/E1/E13被P0-1阻断待二轮重跑 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
12e0c8f912
commit
f0c4e810cb
@ -156,6 +156,14 @@ public class AdRevenueServiceImpl implements AdRevenueService {
|
||||
revenueDO.setTraceId(traceId);
|
||||
revenueDO.setSettleStatus(AdSettleStatusEnum.UNSETTLED.getStatus());
|
||||
revenueDO.setStatDate(LocalDate.now());
|
||||
// 2026-06-10 鉴权波 e2e 静态定论(P0-1):本波两计费端点(report/impression、report/reward)已 @PermitAll,
|
||||
// 计费按 gameId 反查作者归因、不依赖调用方身份;匿名调用时 handler 无登录态可填(撞 NOT NULL),
|
||||
// 带 token 调用(如 test1 批跑)虽可被 handler 填登录 id,但台账审计列统一记 "0"=系统计费写入口径,避免匿名/登录两态值漂移。
|
||||
// DefaultDBFieldHandler.insertFill 仅在 getLoginUserId()!=null 时填 creator/updater
|
||||
// (见 yudao-spring-boot-starter-mybatis .../handler/DefaultDBFieldHandler.java:38-43),匿名两列保持 null 撞 NOT NULL → 500。
|
||||
// 故无条件显式兜底 "0"(与系统身份 id=0 同口径,显式值优先于 handler)。收入归属另由 creator_user_id 列承载,不受影响。
|
||||
revenueDO.setCreator("0");
|
||||
revenueDO.setUpdater("0");
|
||||
try {
|
||||
adRevenueMapper.insert(revenueDO);
|
||||
return new AdBillingResult(revenue, true);
|
||||
|
||||
@ -144,6 +144,10 @@ class AdRevenueServiceImplTest extends BaseMockitoUnitTest {
|
||||
verify(adRevenueMapper).insert(captor.capture());
|
||||
assertEquals(AdEventTypeEnum.REWARD.getType(), captor.getValue().getEventType());
|
||||
assertEquals(5L, captor.getValue().getRevenueAmount());
|
||||
// 2026-06-10 鉴权波 e2e 静态定论(P0-1):计费端点已 @PermitAll、bill() 无 userId 入参 → 落台账必无登录态,
|
||||
// 显式兜底审计列 creator/updater="0",规避 DefaultDBFieldHandler 不填撞 NOT NULL 的 500
|
||||
assertEquals("0", captor.getValue().getCreator(), "匿名计费落台账 creator 应兜底 0");
|
||||
assertEquals("0", captor.getValue().getUpdater(), "匿名计费落台账 updater 应兜底 0");
|
||||
}
|
||||
|
||||
// ============================== 拒计:停用 / 归因失败 ==============================
|
||||
|
||||
@ -59,6 +59,14 @@ public class RuntimeSessionServiceImpl implements RuntimeSessionService {
|
||||
session.setDurationMs(0L);
|
||||
session.setTraceId(reqVO.getTraceId());
|
||||
session.setStartTime(LocalDateTime.now()); // 服务端记开局时间(时长以服务端 end-start 为准)
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):匿名试玩 @PermitAll 无登录态,DefaultDBFieldHandler.insertFill 仅在
|
||||
// getLoginUserId()!=null 时填 creator/updater(见 yudao-spring-boot-starter-mybatis .../handler/DefaultDBFieldHandler.java:38-43),
|
||||
// 匿名(userId==null)两列保持 null 撞 NOT NULL → SQLIntegrityConstraintViolationException 500。
|
||||
// 故此处显式兜底 "0"(与 M-b 执行器系统身份 LoginUser id=0 同口径);显式值优先于 handler(handler 仅 isNull 才填,不覆盖)。
|
||||
if (userId == null) {
|
||||
session.setCreator("0");
|
||||
session.setUpdater("0");
|
||||
}
|
||||
runtimeSessionMapper.insert(session);
|
||||
// 关键链路日志:含归属维度(userId 可空=匿名,anonId 归属),便于匿名/登录会话排障与对账
|
||||
log.info("[startSession] 新建试玩会话 sessionId={}, gameId={}, versionId={}, scene={}, userId={}, anonId={}",
|
||||
@ -86,6 +94,13 @@ public class RuntimeSessionServiceImpl implements RuntimeSessionService {
|
||||
update.setDurationMs(durationMs);
|
||||
update.setEndReason(StringUtils.hasText(reqVO.getEndReason()) ? reqVO.getEndReason() : "game_end");
|
||||
update.setEndTime(endTime);
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1·M-b 雷=UPDATE SET updater=null 形态):匿名收尾 @PermitAll 无登录态,
|
||||
// DefaultDBFieldHandler.updateFill 仅在 getLoginUserId()!=null 时填 updater(见 .../handler/DefaultDBFieldHandler.java:57-61),
|
||||
// 匿名(当前无登录态 userId==null 或会话本就为匿名归属 player_user_id 为空)时 updater 保持 null 撞 NOT NULL → 500。
|
||||
// 故显式兜底 updater="0"(与开局/系统身份同口径);显式值优先于 handler(仅 isNull 才填,不覆盖)。
|
||||
if (userId == null || session.getPlayerUserId() == null) {
|
||||
update.setUpdater("0");
|
||||
}
|
||||
runtimeSessionMapper.updateById(update);
|
||||
// TODO 对接点【telemetry 回灌】:会话收尾后投递 play_end 事件至 telemetry(契约 #5 events.schema.json)。
|
||||
// 约定 sessionId(int64) 透传进 telemetry envelope.session_id(转 string)作对账键;
|
||||
|
||||
@ -90,6 +90,24 @@ class RuntimeSessionServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertNull(saved.getPlayerUserId(), "匿名会话 player_user_id 应为 NULL");
|
||||
assertEquals("anon-abc", saved.getAnonId(), "匿名会话应落 anonId 归属");
|
||||
assertEquals(SessionStatusEnum.PLAYING.getStatus(), saved.getStatus());
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):匿名开局显式兜底审计列 creator/updater="0",规避 DefaultDBFieldHandler 不填撞 NOT NULL 的 500
|
||||
assertEquals("0", saved.getCreator(), "匿名开局 creator 应兜底 0");
|
||||
assertEquals("0", saved.getUpdater(), "匿名开局 updater 应兜底 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStartSession_loggedIn_creatorNotForced() {
|
||||
// 登录态开局(userId 非空):不显式兜底审计列,交由 DefaultDBFieldHandler 按登录身份填充(creator/updater 保持 null 等 handler 填)
|
||||
when(runtimeSessionMapper.selectByClientPlayToken("ptk_login")).thenReturn(null);
|
||||
when(runtimeSessionMapper.insert(any(RuntimeSessionDO.class))).thenReturn(1);
|
||||
|
||||
runtimeSessionService.startSession(startReq("ptk_login", "play"), 99L); // 登录态
|
||||
|
||||
ArgumentCaptor<RuntimeSessionDO> captor = ArgumentCaptor.forClass(RuntimeSessionDO.class);
|
||||
verify(runtimeSessionMapper).insert(captor.capture());
|
||||
RuntimeSessionDO saved = captor.getValue();
|
||||
assertNull(saved.getCreator(), "登录态开局不应显式兜底 creator(交 handler 按登录身份填)");
|
||||
assertNull(saved.getUpdater(), "登录态开局不应显式兜底 updater(交 handler 按登录身份填)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -127,6 +145,7 @@ class RuntimeSessionServiceImplTest extends BaseMockitoUnitTest {
|
||||
session.setId(888L);
|
||||
session.setStatus(SessionStatusEnum.PLAYING.getStatus());
|
||||
session.setStartTime(LocalDateTime.now().minusMinutes(2));
|
||||
session.setPlayerUserId(99L); // 登录态会话(明确非匿名,避免触发匿名 updater 兜底分支)
|
||||
when(runtimeSessionMapper.selectById(888L)).thenReturn(session);
|
||||
|
||||
SessionEndReqVO reqVO = new SessionEndReqVO();
|
||||
@ -142,6 +161,28 @@ class RuntimeSessionServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertTrue(update.getDurationMs() >= 119000L, "服务端时长应约 120000ms,实际=" + update.getDurationMs());
|
||||
assertEquals("game_end", update.getEndReason());
|
||||
assertNotNull(update.getEndTime());
|
||||
assertNull(update.getUpdater(), "登录态收尾不应显式兜底 updater(交 handler 按登录身份填)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEndSession_anonymous_updaterFallbackZero() {
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1·M-b 雷=UPDATE SET updater=null):匿名收尾(当前无登录态 userId=null)
|
||||
// 显式兜底 update.updater="0",规避 DefaultDBFieldHandler.updateFill 不填 updater 撞 NOT NULL 的 500。
|
||||
RuntimeSessionDO session = new RuntimeSessionDO();
|
||||
session.setId(889L);
|
||||
session.setStatus(SessionStatusEnum.PLAYING.getStatus());
|
||||
session.setStartTime(LocalDateTime.now().minusMinutes(1));
|
||||
session.setPlayerUserId(null); // 匿名会话归属
|
||||
when(runtimeSessionMapper.selectById(889L)).thenReturn(session);
|
||||
|
||||
SessionEndReqVO reqVO = new SessionEndReqVO();
|
||||
reqVO.setSessionId(889L);
|
||||
reqVO.setEndReason("game_end");
|
||||
runtimeSessionService.endSession(reqVO, null); // 匿名收尾
|
||||
|
||||
ArgumentCaptor<RuntimeSessionDO> captor = ArgumentCaptor.forClass(RuntimeSessionDO.class);
|
||||
verify(runtimeSessionMapper).updateById(captor.capture());
|
||||
assertEquals("0", captor.getValue().getUpdater(), "匿名收尾 updater 应兜底 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -334,6 +334,15 @@ public class EventIngestServiceImpl implements EventIngestService {
|
||||
// props 为 null 时透传 null(列可空);空对象序列化为 "{}"(合法 JSON)。
|
||||
eventDO.setProps(envelope.getProps() == null ? null : JsonUtils.toJsonString(envelope.getProps()));
|
||||
eventDO.setProcessStatus(EventProcessStatusEnum.PENDING.getStatus());
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):匿名上报(batch/beacon 两端点 @PermitAll 共用本点)无登录态,
|
||||
// DefaultDBFieldHandler.insertFill 仅在 getLoginUserId()!=null 时填 creator/updater
|
||||
// (见 yudao-spring-boot-starter-mybatis .../handler/DefaultDBFieldHandler.java:38-43),匿名两列保持 null 撞 NOT NULL → 500。
|
||||
// 归属维度已用 user_id/anon_id 承载,审计列匿名时显式兜底 "0"(与系统身份 id=0 同口径,显式值优先于 handler)。
|
||||
// 注:后续 markAggregated 的 updateById 仅更新 process_status,其 updater=null 不进 UPDATE SET(MP 跳过 null 字段),不覆盖本行 "0"。
|
||||
if (eventDO.getUserId() == null) {
|
||||
eventDO.setCreator("0");
|
||||
eventDO.setUpdater("0");
|
||||
}
|
||||
return eventDO;
|
||||
}
|
||||
|
||||
|
||||
@ -313,6 +313,41 @@ class EventIngestServiceImplTest extends BaseMockitoUnitTest {
|
||||
verifyNoInteractions(feedApi);
|
||||
}
|
||||
|
||||
// ============================== 匿名信封审计列兜底(2026-06-10 鉴权波 P0-1)==============================
|
||||
|
||||
@Test
|
||||
void testIngestOne_anonymousEnvelope_creatorUpdaterFallbackZero() {
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):匿名上报(信封无 user.userId 且当前无登录态 userId=null)→ 解析 user_id=null,
|
||||
// 显式兜底审计列 creator/updater="0",规避 DefaultDBFieldHandler.insertFill 不填撞 NOT NULL 的 500(batch/beacon 共用此点)。
|
||||
when(telemetryEventMapper.selectByEventId(anyString())).thenReturn(null);
|
||||
EnvelopeReqVO env = validEnvelope("game_play_start", "t-anon"); // 无 user 段 → 匿名
|
||||
|
||||
eventIngestService.ingestBatch(batchOf(env), null); // userId=null=匿名
|
||||
|
||||
ArgumentCaptor<TelemetryEventDO> captor = ArgumentCaptor.forClass(TelemetryEventDO.class);
|
||||
verify(telemetryEventMapper).insert(captor.capture());
|
||||
TelemetryEventDO saved = captor.getValue();
|
||||
assertNull(saved.getUserId(), "匿名信封 user_id 应为 NULL");
|
||||
assertEquals("0", saved.getCreator(), "匿名上报 creator 应兜底 0");
|
||||
assertEquals("0", saved.getUpdater(), "匿名上报 updater 应兜底 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIngestOne_loggedInEnvelope_creatorNotForced() {
|
||||
// 登录态上报(当前登录 userId=99):不显式兜底审计列,交由 DefaultDBFieldHandler 按登录身份填充(保持 null 等 handler 填)
|
||||
when(telemetryEventMapper.selectByEventId(anyString())).thenReturn(null);
|
||||
EnvelopeReqVO env = validEnvelope("game_play_start", "t-login");
|
||||
|
||||
eventIngestService.ingestBatch(batchOf(env), 99L); // 登录态
|
||||
|
||||
ArgumentCaptor<TelemetryEventDO> captor = ArgumentCaptor.forClass(TelemetryEventDO.class);
|
||||
verify(telemetryEventMapper).insert(captor.capture());
|
||||
TelemetryEventDO saved = captor.getValue();
|
||||
assertEquals(99L, saved.getUserId());
|
||||
assertNull(saved.getCreator(), "登录态上报不应显式兜底 creator(交 handler 按登录身份填)");
|
||||
assertNull(saved.getUpdater(), "登录态上报不应显式兜底 updater(交 handler 按登录身份填)");
|
||||
}
|
||||
|
||||
// ============================== 测试夹具 ==============================
|
||||
|
||||
/** 构造一条信封必填项齐全的事件(含 eventId;无 context,不触发聚合) */
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.controller.app.passport;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
|
||||
@ -48,6 +49,9 @@ public class AppPassportController {
|
||||
|
||||
@PostMapping("/send-sms-code")
|
||||
@PermitAll // 免登:种子期 Debug 渠道(码落 system_sms_code 表,运营后台查码)
|
||||
// 2026-06-10 鉴权波 E11 安全实证(响应安全基线 §5.1):脱敏持久化访问日志(system_api_access_log)的 mobile 键,禁明文落库。
|
||||
// 注:sanitizeKeys 仅由 ApiAccessLogFilter 消费(持久化日志),不影响 ApiAccessLogInterceptor 的控制台明文(框架层不可改,控制台面已由 staging logging.level 关闭,见 application-staging.yaml)。
|
||||
@ApiAccessLog(sanitizeKeys = {"mobile"})
|
||||
@Operation(summary = "发送登录短信验证码", description = "场景固定 MEMBER_LOGIN;响应不区分手机号是否已注册(防枚举)")
|
||||
// R6 安全前置(§7.2):IP 维度限频 10 次/小时/IP,配合 Service 层 IP 每日/每小时 Redis 计数双闸;切真实渠道前另补图形验证码
|
||||
@RateLimiter(time = 1, timeUnit = TimeUnit.HOURS, count = SMS_RATE_LIMIT_COUNT,
|
||||
@ -60,6 +64,8 @@ public class AppPassportController {
|
||||
|
||||
@PostMapping("/sms-login")
|
||||
@PermitAll // 免登:已注册登录 / 未注册自动注册一体(验证码即占有证明,拍板3)
|
||||
// 2026-06-10 鉴权波 E11 安全实证(§5.1):脱敏持久化访问日志的 mobile/code 键(手机号+验证码禁明文落库;同口径见 send-sms-code 注释)。
|
||||
@ApiAccessLog(sanitizeKeys = {"mobile", "code"})
|
||||
@Operation(summary = "手机号验证码登录", description = "已注册→登录;未注册→自动注册一体(register_channel=sms)")
|
||||
public CommonResult<LoginRespVO> smsLogin(@Valid @RequestBody SmsLoginReqVO reqVO) {
|
||||
return success(passportService.smsLogin(reqVO, ServletUtils.getClientIP()));
|
||||
@ -67,6 +73,8 @@ public class AppPassportController {
|
||||
|
||||
@PostMapping("/invite-register")
|
||||
@PermitAll // 免登:报备前陌生玩家旁路(受退役开关管控,§5.4)
|
||||
// 2026-06-10 鉴权波 E11 安全实证(§5.1):脱敏持久化访问日志的 mobile/inviteCode 键(手机号+邀请码禁明文落库;同口径见 send-sms-code 注释)。
|
||||
@ApiAccessLog(sanitizeKeys = {"mobile", "inviteCode"})
|
||||
@Operation(summary = "邀请码受限激活注册", description = "手机号+邀请码,零短信依赖;受退役开关 wanxiang.passport.invite-register-enabled 管控")
|
||||
public CommonResult<LoginRespVO> inviteRegister(@Valid @RequestBody InviteRegisterReqVO reqVO) {
|
||||
return success(passportService.inviteRegister(reqVO, ServletUtils.getClientIP()));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.mq.consumer.sms;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -24,7 +25,13 @@ public class SmsSendConsumer {
|
||||
@EventListener
|
||||
@Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
|
||||
public void onMessage(SmsSendMessage message) {
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
// 2026-06-10 鉴权波 E11 安全实证(响应安全基线 §5.1):原 log.info("[消息内容({})]", message) 经 SmsSendMessage.toString()
|
||||
// 会打印 mobile 明文 + templateParams(含登录验证码 code)明文,造成手机号/验证码击穿。
|
||||
// 改为脱敏输出:mobile 走 DesensitizedUtil.mobilePhone;code 一律不打印(templateParams 整体不落日志);
|
||||
// 仅保留 logId/channelId/apiTemplateId 供排障与对账(关联 system_sms_code / system_sms_log 表)。
|
||||
log.info("[onMessage][短信发送 logId({}) mobile({}) channelId({}) apiTemplateId({})]",
|
||||
message.getLogId(), DesensitizedUtil.mobilePhone(message.getMobile()),
|
||||
message.getChannelId(), message.getApiTemplateId());
|
||||
smsSendService.doSendSms(message);
|
||||
}
|
||||
|
||||
|
||||
@ -183,6 +183,12 @@ public class PassportServiceImpl implements PassportService {
|
||||
// 目标3:anonId 仅注册时落一次;缺失落空串(归因降级不阻断登录,§12)
|
||||
player.setFirstAnonId(StringUtils.hasText(anonId) ? anonId : "");
|
||||
player.setLoginDate(LocalDateTime.now());
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):免登注册(sms-login 自动注册 / invite-register 共用此点)@PermitAll 无登录态,
|
||||
// 且注册者本人 id 此刻尚未生成;DefaultDBFieldHandler.insertFill 仅在 getLoginUserId()!=null 时填 creator/updater
|
||||
// (见 yudao-spring-boot-starter-mybatis .../handler/DefaultDBFieldHandler.java:38-43),匿名两列保持 null 撞 NOT NULL → 500。
|
||||
// 故无条件显式兜底 "0"(与系统身份 id=0 同口径);显式值优先于 handler(仅 isNull 才填,不覆盖)。
|
||||
player.setCreator("0");
|
||||
player.setUpdater("0");
|
||||
try {
|
||||
playerMapper.insert(player);
|
||||
log.info("[registerPlayer] 新玩家注册成功 userId={}, channel={}, mobile={}, hasAnonId={}",
|
||||
|
||||
@ -193,6 +193,9 @@ class PassportServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertEquals("sms", captor.getValue().getRegisterChannel());
|
||||
assertEquals("anon-x", captor.getValue().getFirstAnonId());
|
||||
assertEquals(0, captor.getValue().getCreatorFlag()); // 默认非创作者
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):免登注册匿名路径显式兜底审计列 creator/updater="0",规避 NOT NULL 撞库 500
|
||||
assertEquals("0", captor.getValue().getCreator(), "匿名注册 creator 应兜底 0");
|
||||
assertEquals("0", captor.getValue().getUpdater(), "匿名注册 updater 应兜底 0");
|
||||
// 发真 token,响应带 userId
|
||||
assertEquals(2048L, resp.getUserId());
|
||||
assertEquals("happy-token", resp.getAccessToken());
|
||||
@ -283,6 +286,9 @@ class PassportServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertEquals(77L, captor.getValue().getInviteCodeId()); // 关联核销的邀请码
|
||||
assertEquals("anon-y", captor.getValue().getFirstAnonId());
|
||||
assertEquals("小明", captor.getValue().getNickname()); // 传入昵称生效
|
||||
// 2026-06-10 鉴权波 e2e 实证(P0-1):邀请码注册同为免登匿名路径,审计列 creator/updater 显式兜底 "0"
|
||||
assertEquals("0", captor.getValue().getCreator(), "匿名注册 creator 应兜底 0");
|
||||
assertEquals("0", captor.getValue().getUpdater(), "匿名注册 updater 应兜底 0");
|
||||
assertEquals(2048L, resp.getUserId());
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,11 @@ spring:
|
||||
logging:
|
||||
level:
|
||||
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR
|
||||
# 2026-06-10 鉴权波 E11 安全实证(响应安全基线 §5.1):ApiAccessLogInterceptor.preHandle 在非 prod 会 log.info 打印原始请求体
|
||||
# (见 yudao-spring-boot-starter-web .../interceptor/ApiAccessLogInterceptor.java:51),passport 三端点请求体含 mobile/code/inviteCode 明文。
|
||||
# 该拦截器属 framework 层不可改、且无条件注册(不读 @ApiAccessLog 注解),唯一不改框架的关停手段=抬高其日志级别至 WARN(信任边界=配置)。
|
||||
# 控制台明文面由此关闭;持久化访问日志面由三端点 @ApiAccessLog(sanitizeKeys) 脱敏,两面合围。
|
||||
cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor: WARN
|
||||
|
||||
debug: false
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user