From a08f15904db8ffda0dee3a99220ea3f65bf28fe3 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+copilot@users.noreply.github.com> Date: Sun, 10 May 2026 01:48:24 +0800 Subject: [PATCH] feat: introduce TenantOwned interface for type-safe tenant filtering Replace reflective getTenantId() in BaseController with a typed TenantOwned interface. All tenant-scoped BO classes now implement TenantOwned, enabling compile-time safety instead of runtime reflection. Co-Authored-By: Claude Opus 4.7 --- .../pnoker/common/auth/entity/bo/RoleBO.java | 3 +- .../pnoker/common/dal/entity/bo/GroupBO.java | 3 +- .../pnoker/common/dal/entity/bo/LabelBO.java | 3 +- .../common/manager/entity/bo/DeviceBO.java | 3 +- .../manager/entity/bo/DriverAttributeBO.java | 3 +- .../entity/bo/DriverAttributeConfigBO.java | 3 +- .../common/manager/entity/bo/DriverBO.java | 3 +- .../manager/entity/bo/PointAttributeBO.java | 3 +- .../entity/bo/PointAttributeConfigBO.java | 3 +- .../common/manager/entity/bo/PointBO.java | 3 +- .../common/manager/entity/bo/ProfileBO.java | 3 +- .../common/entity/common/TenantOwned.java | 31 +++++++++++++++++++ .../pnoker/common/base/BaseController.java | 20 +++--------- 13 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java index 901233062..10e707819 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.auth.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.RoleExt; import io.github.pnoker.common.enums.EnableFlagEnum; import lombok.AllArgsConstructor; @@ -38,7 +39,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class RoleBO extends BaseBO { +public class RoleBO extends BaseBO implements TenantOwned { /** * ID diff --git a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java index 216149db8..5488ec803 100644 --- a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java +++ b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.dal.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.enums.EnableFlagEnum; import io.github.pnoker.common.enums.GroupTypeFlagEnum; import lombok.AllArgsConstructor; @@ -38,7 +39,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class GroupBO extends BaseBO { +public class GroupBO extends BaseBO implements TenantOwned { /** * Parent group ID. diff --git a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java index 253b114a3..4fed34c1e 100644 --- a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java +++ b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.dal.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.enums.EnableFlagEnum; import io.github.pnoker.common.enums.EntityTypeFlagEnum; import lombok.AllArgsConstructor; @@ -38,7 +39,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class LabelBO extends BaseBO { +public class LabelBO extends BaseBO implements TenantOwned { /** * Label name. diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java index 9c2b8b9ff..5daa5f31b 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.DeviceExt; import io.github.pnoker.common.enums.EnableFlagEnum; import lombok.AllArgsConstructor; @@ -40,7 +41,7 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class DeviceBO extends BaseBO { +public class DeviceBO extends BaseBO implements TenantOwned { /** * Device Name diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java index 8307d39fd..ec53ce627 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.DriverAttributeExt; import io.github.pnoker.common.enums.AttributeTypeFlagEnum; import io.github.pnoker.common.enums.EnableFlagEnum; @@ -39,7 +40,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class DriverAttributeBO extends BaseBO { +public class DriverAttributeBO extends BaseBO implements TenantOwned { /** * Name diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java index 76a98bb9a..584ecc020 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.JsonExt; import io.github.pnoker.common.enums.EnableFlagEnum; import lombok.AllArgsConstructor; @@ -38,7 +39,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class DriverAttributeConfigBO extends BaseBO { +public class DriverAttributeConfigBO extends BaseBO implements TenantOwned { /** * ID diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java index e07b867bb..4b0ef3d5a 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.DriverExt; import io.github.pnoker.common.enums.DriverTypeFlagEnum; import io.github.pnoker.common.enums.EnableFlagEnum; @@ -39,7 +40,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class DriverBO extends BaseBO { +public class DriverBO extends BaseBO implements TenantOwned { /** * Name diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java index 33067a46b..9c54b1b14 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.PointAttributeExt; import io.github.pnoker.common.enums.AttributeTypeFlagEnum; import io.github.pnoker.common.enums.EnableFlagEnum; @@ -39,7 +40,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class PointAttributeBO extends BaseBO { +public class PointAttributeBO extends BaseBO implements TenantOwned { /** * Name diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java index b4eec66ae..8f72feff2 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.JsonExt; import io.github.pnoker.common.enums.EnableFlagEnum; import lombok.AllArgsConstructor; @@ -38,7 +39,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class PointAttributeConfigBO extends BaseBO { +public class PointAttributeConfigBO extends BaseBO implements TenantOwned { /** * ID diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java index 6ea181559..c039cc0cb 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.PointExt; import io.github.pnoker.common.enums.EnableFlagEnum; import io.github.pnoker.common.enums.PointTypeFlagEnum; @@ -42,7 +43,7 @@ import java.math.BigDecimal; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class PointBO extends BaseBO { +public class PointBO extends BaseBO implements TenantOwned { /** * Name diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java index a46e97f71..427455703 100644 --- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java +++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java @@ -18,6 +18,7 @@ package io.github.pnoker.common.manager.entity.bo; import io.github.pnoker.common.entity.base.BaseBO; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.entity.ext.ProfileExt; import io.github.pnoker.common.enums.EnableFlagEnum; import io.github.pnoker.common.enums.ProfileShareFlagEnum; @@ -40,7 +41,7 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) -public class ProfileBO extends BaseBO { +public class ProfileBO extends BaseBO implements TenantOwned { /** * Name diff --git a/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java b/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java new file mode 100644 index 000000000..7e2bf2bcf --- /dev/null +++ b/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.entity.common; + +/** + * Marker contract for tenant-scoped objects. + * + * @author pnoker + * @version 2026.5.10 + * @since 2026.5.10 + */ +public interface TenantOwned { + + Long getTenantId(); + +} diff --git a/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java b/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java index 9b855e123..722de536c 100644 --- a/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java +++ b/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java @@ -19,12 +19,12 @@ package io.github.pnoker.common.base; import io.github.pnoker.common.entity.R; import io.github.pnoker.common.entity.common.RequestHeader; +import io.github.pnoker.common.entity.common.TenantOwned; import io.github.pnoker.common.exception.NotFoundException; import io.github.pnoker.common.utils.UserHeaderUtil; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import java.lang.reflect.Method; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -65,8 +65,8 @@ public interface BaseController { * Fail closed when an ID-based lookup returns an entity outside the caller's tenant. * Returning 404 avoids exposing whether a cross-tenant resource exists. */ - default T requireTenant(Long tenantId, T entity) { - if (Objects.isNull(entity) || !Objects.equals(tenantId, resolveTenantId(entity))) { + default T requireTenant(Long tenantId, T entity) { + if (Objects.isNull(entity) || !Objects.equals(tenantId, entity.getTenantId())) { throw new NotFoundException("Resource does not exist"); } return entity; @@ -75,26 +75,16 @@ public interface BaseController { /** * Keep only entities that belong to the caller's tenant for bulk lookup endpoints. */ - default List filterTenant(Long tenantId, Collection entities) { + default List filterTenant(Long tenantId, Collection entities) { if (Objects.isNull(entities) || entities.isEmpty()) { return List.of(); } return entities.stream() .filter(Objects::nonNull) - .filter(entity -> Objects.equals(tenantId, resolveTenantId(entity))) + .filter(entity -> Objects.equals(tenantId, entity.getTenantId())) .toList(); } - private Long resolveTenantId(Object entity) { - try { - Method method = entity.getClass().getMethod("getTenantId"); - Object tenantId = method.invoke(entity); - return tenantId instanceof Long value ? value : null; - } catch (ReflectiveOperationException e) { - throw new NotFoundException("Resource does not exist"); - } - } - /** * Get user ID from user header *