package com.mydreamjob.common.logging; import jakarta.servlet.http.HttpServletRequest; /** 对三类简历 AI 接口收紧日志元数据,其他接口保持现状。 */ public final class SensitiveEndpointLogPolicy { private static final String ROOT="/profile/me/work-experience-optimization"; private static final java.util.List ROOTS=java.util.List.of(ROOT, "/profile/me/project-experience-optimization", "/profile/me/self-evaluation-optimization"); private SensitiveEndpointLogPolicy() {} public static boolean isWorkExperience(HttpServletRequest request) { return isAiOptimization(request); } public static boolean isAiOptimization(HttpServletRequest request) { return rootForRequest(request)!=null; } private static String rootForRequest(HttpServletRequest request) { // servletPath 通常已解码且去掉路径参数;原始 URI 作为前置过滤器和单测的兜底。 String servletRoot=rootOf(request.getServletPath()); if (servletRoot!=null) return servletRoot; String path=request.getRequestURI(); String context=request.getContextPath(); if(!context.isEmpty() && path.startsWith(context))path=path.substring(context.length()); // 同时兼容过滤器单测不设置 contextPath 的标准 /api/v1 请求。 if(path.startsWith("/api/v1/"))path=path.substring("/api/v1".length()); try { path=org.springframework.web.util.UriUtils.decode(path,java.nio.charset.StandardCharsets.UTF_8); } catch(IllegalArgumentException ignored) { /* 非法编码由既有 HTTP/安全链拒绝,不记录异常原文。 */ } if(path.startsWith("/api/v1/"))path=path.substring("/api/v1".length()); path=path.replaceAll(";[^/]*", ""); return rootOf(path); } private static String rootOf(String path) { if(path==null)return null; for(String root:ROOTS)if(path.equals(root)||path.startsWith(root+"/"))return root; return null; } public static String canonicalPath() { return ROOT; } public static String canonicalPath(HttpServletRequest request) { String root=rootForRequest(request); if(root!=null)return root; // 只输出已知常量;不将任何原始路径、查询参数或客户端文本写入日志。 return "/profile/me/resume-ai-optimization"; } }