diff --git a/common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java b/common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java index 09b668c..89f535c 100644 --- a/common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java +++ b/common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java @@ -27,6 +27,20 @@ import java.util.concurrent.TimeoutException; @RestControllerAdvice("apelet") public class MyExceptionHandler { + /** + * 数据不存在或不属于当前租户:统一返回 404,避免越权探测(PRD FR-4.16)。 + * + * @param ex 异常对象。 + * @param request http请求。 + * @return 应答对象。 + */ + @ExceptionHandler(value = DataNotFoundException.class) + public ResponseResult dataNotFoundExceptionHandle(DataNotFoundException ex, HttpServletRequest request) { + log.warn("Data not found or not owned by current tenant from URL [{}]", request.getRequestURI()); + ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_NOT_FOUND); + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + /** * 通用异常处理方法。 * diff --git a/common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java b/common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java index 32dd3e2..086d1bb 100644 --- a/common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java +++ b/common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java @@ -1,5 +1,7 @@ package apelet.common.core.cache; +import apelet.common.core.object.TokenData; + /** * 在线表单缓存 * @author Soon @@ -19,6 +21,15 @@ public class CacheKey { */ public final static String FORM_FIELD_BY_HEAD_Id = "FORM_FIELD_BY_HEAD_Id:"; + /** + * 租户维度片段:A 端为租户Id,B 端及无请求上下文(MQ、定时任务)统一为 base。 + * 租户库的表单元数据 id 可能相同(由管理端复制而来),不加租户维度会导致缓存互相覆盖(PRD FR-4.18)。 + */ + private static String tenantSegment() { + Long tenantId = TokenData.currentTenantId(); + return tenantId == null ? "base" : String.valueOf(tenantId); + } + public static String makeEntityTableKey(String datasourceVariableName) { return "EntityTable:" + datasourceVariableName; } @@ -26,32 +37,32 @@ public class CacheKey { public static String makeTableKey(Long id) { - return "FORM_HEAD_TABLE:" + id; + return "FORM_HEAD_TABLE:" + tenantSegment() + ":" + id; } public static String makeFormHeadByTableId(Long id) { - return "FORM_HEAD_BY_TABLE_ID:" + id; + return "FORM_HEAD_BY_TABLE_ID:" + tenantSegment() + ":" + id; } public static String makeTableKey(String tableName) { - return "FORM_HEAD_TABLE_NAME:" + tableName; + return "FORM_HEAD_TABLE_NAME:" + tenantSegment() + ":" + tableName; } public static String makeChildFieldListKey(Long id) { - return "FORM_CHILD_FIELD:" + id; + return "FORM_CHILD_FIELD:" + tenantSegment() + ":" + id; } public static String makeOneToManyTable(String tableName) { - return "FORM_ONETOMANY_TABLE:" + tableName; + return "FORM_ONETOMANY_TABLE:" + tenantSegment() + ":" + tableName; } public static String getFormFieldByHeadId(long headId) { - return FORM_FIELD_BY_HEAD_Id + headId; + return FORM_FIELD_BY_HEAD_Id + tenantSegment() + ":" + headId; } diff --git a/common/common-core/src/main/java/apelet/common/core/exception/DataNotFoundException.java b/common/common-core/src/main/java/apelet/common/core/exception/DataNotFoundException.java new file mode 100644 index 0000000..d5acf3a --- /dev/null +++ b/common/common-core/src/main/java/apelet/common/core/exception/DataNotFoundException.java @@ -0,0 +1,20 @@ +package apelet.common.core.exception; + +/** + * 数据不存在或不属于当前租户。 + * + *

用于按 id 查询/更新/删除时的归属校验(PRD FR-4.16): + * 记录不存在与记录属于其他租户统一返回 404,避免通过状态码探测数据是否存在。

+ * + * @author apelet + */ +public class DataNotFoundException extends MyRuntimeException { + + public DataNotFoundException() { + super(); + } + + public DataNotFoundException(String msg) { + super(msg); + } +} diff --git a/common/common-core/src/main/java/apelet/common/core/object/TokenData.java b/common/common-core/src/main/java/apelet/common/core/object/TokenData.java index d1c49ad..edf688d 100644 --- a/common/common-core/src/main/java/apelet/common/core/object/TokenData.java +++ b/common/common-core/src/main/java/apelet/common/core/object/TokenData.java @@ -144,6 +144,27 @@ public class TokenData { } /** + * 当前请求的租户Id。B 端(管理端)为 null,A 端(租户端)为登录用户的租户Id。 + * 无 HTTP 请求上下文(如 MQ 消费、定时任务)时同样返回 null。 + * + * @return 当前租户Id,可能为 null。 + */ + public static Long currentTenantId() { + TokenData tokenData = takeFromRequest(); + return tokenData == null ? null : tokenData.getTenantId(); + } + + /** + * 当前请求的应用编码。为空表示不按应用维度过滤(PRD FR-4.14)。 + * + * @return 当前应用编码,可能为 null。 + */ + public static String currentAppCode() { + TokenData tokenData = takeFromRequest(); + return tokenData == null ? null : tokenData.getAppCode(); + } + + /** * 从Http Request对象中获取令牌对象。 * * @return 令牌对象。