From 6d6cfcb6ff697c346d1ff328c83b71c7e7b1ca6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com>
Date: Mon, 7 Sep 2026 15:08:43 +0800
Subject: [PATCH] =?UTF-8?q?fix(=E6=9D=83=E9=99=90):=20=E5=BA=97=E9=93=BA?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=87=8D=E5=A4=8D=E6=A3=80=E6=9F=A5=E4=BB=85?=
=?UTF-8?q?=E5=8B=BE=E8=8F=9C=E5=8D=95=E6=97=A0=E3=80=8C=E5=BA=97=E9=93=BA?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=AE=B0=E5=BD=95=E3=80=8D=E4=BB=8D403?=
=?UTF-8?q?=E2=80=94=E2=80=94=E6=A0=A1=E9=AA=8C=E5=8F=A3=E5=BE=84=E4=B8=8E?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E6=9D=83=E5=AF=B9=E9=BD=90(?=
=?UTF-8?q?=E4=BB=BB=E5=8A=A1/=E9=87=8D=E5=A4=8D=E6=A3=80=E6=9F=A5?=
=?UTF-8?q?=E4=BB=BB=E4=B8=80=E8=8F=9C=E5=8D=95+=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E6=9D=83=E9=99=90=E5=8D=B3=E5=8F=AF=E6=94=BE=E8=A1=8C,?=
=?UTF-8?q?=E9=95=9C=E5=83=8F=E9=AD=8F=E6=8C=AF=E5=B3=B0=E5=8F=8D=E9=A6=88?=
=?UTF-8?q?:=20=E5=8F=AA=E5=8B=BE103=E6=9C=AA=E5=8B=BE94=E4=B8=94=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E6=9D=83=E9=99=90=E5=B7=B2=E8=A1=A5=E4=BB=8D=E8=A2=AB?=
=?UTF-8?q?=E6=8B=92);=20=E9=A1=BA=E5=B8=A6=E4=BF=AE=E5=A4=8D=20controller?=
=?UTF-8?q?=20=E7=9B=B4=E8=BF=9E=20mapper=20=E6=9E=B6=E6=9E=84=E8=BF=9D?=
=?UTF-8?q?=E8=A7=84=20resolveVisibleShopNameKeys=20=E4=B8=8B=E6=B2=89=20S?=
=?UTF-8?q?hopDataCrawlAdminTasksService?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../service/PermissionMenuService.java | 17 ++-
.../AdminShopDataCrawlTasksController.java | 13 +-
.../ShopDataCrawlAdminTasksService.java | 15 +++
.../service/PermissionMenuServiceTest.java | 126 ++++++++++++++++++
4 files changed, 156 insertions(+), 15 deletions(-)
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java
index 005c2793..1c5f898a 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java
@@ -377,7 +377,15 @@ public class PermissionMenuService {
return requestedIds.size();
}
- /** Verifies both the visible admin menu and its separately managed data grant. */
+ /**
+ * Verifies a visible shop-data admin menu and its separately managed data grant.
+ *
+ *
校验口径与 {@link #autoGrantShopDataDataPermission} 保持一致:「店铺数据记录」
+ * {@code admin_shop_data_crawl_tasks} 或「店铺数据重复检查」{@code admin_shop_data_duplicate_check}
+ * 任一可见菜单,并同时持有内部数据权限 {@code admin_shop_data_crawl_task_data}。
+ * 只勾了「店铺数据重复检查」而未勾「店铺数据记录」的管理员(数据权限已被自动补上)
+ * 不应再被 403 拒绝。
+ */
public void requireShopDataCrawlTaskAccess(AdminUserEntity operator) {
ensureAdminOperatorIfPresent(operator);
if (operator == null || operator.getId() == null || operator.getId() <= 0) {
@@ -388,10 +396,13 @@ public class PermissionMenuService {
}
PermissionMenuEntity taskMenu = requireDataPermission(
"admin_shop_data_crawl_tasks", "店铺数据任务管理菜单");
+ PermissionMenuEntity duplicateCheckMenu = requireDataPermission(
+ "admin_shop_data_duplicate_check", "店铺数据重复检查菜单");
PermissionMenuEntity dataPermission = requireDataPermission(
SHOP_DATA_CRAWL_DATA_PERMISSION_KEY, "店铺数据任务数据");
- if (!hasEffectiveColumnPermission(operator.getId(), taskMenu.getId())
- || !hasEffectiveColumnPermission(operator.getId(), dataPermission.getId())) {
+ boolean hasShopDataMenu = hasEffectiveColumnPermission(operator.getId(), taskMenu.getId())
+ || hasEffectiveColumnPermission(operator.getId(), duplicateCheckMenu.getId());
+ if (!hasShopDataMenu || !hasEffectiveColumnPermission(operator.getId(), dataPermission.getId())) {
throw new BusinessException(403, "无权查看店铺数据任务");
}
}
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/controller/AdminShopDataCrawlTasksController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/controller/AdminShopDataCrawlTasksController.java
index 9db9a143..dfa823c5 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/controller/AdminShopDataCrawlTasksController.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/controller/AdminShopDataCrawlTasksController.java
@@ -7,7 +7,6 @@ import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlDownloadRowDto;
-import com.nanri.aiimage.modules.shopduplicatecheck.mapper.ShopDuplicateCheckSourceMapper;
import com.nanri.aiimage.modules.shopdatacrawl.service.ShopDataCrawlAdminTasksService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,7 +38,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@@ -77,7 +75,6 @@ public class AdminShopDataCrawlTasksController {
private final AdminAuthSupport adminAuthSupport;
private final PermissionMenuService permissionMenuService;
private final ShopDataCrawlAdminTasksService adminTasksService;
- private final ShopDuplicateCheckSourceMapper duplicateCheckSourceMapper;
@GetMapping
@Operation(summary = "店铺数据抓取记录:按店铺分组分页列表(每店最新结果文件)")
@@ -256,15 +253,7 @@ public class AdminShopDataCrawlTasksController {
/** 非超管返回其可管店铺名(trim+小写去重,与撞款可见口径一致);超管调用方传 null 表示全量。 */
private List resolveVisibleShopNameKeys(Long operatorId) {
- List names = duplicateCheckSourceMapper.selectManagedShopNames(operatorId);
- if (names == null || names.isEmpty()) {
- return List.of();
- }
- return names.stream()
- .filter(name -> name != null && !name.isBlank())
- .map(name -> name.trim().toLowerCase(Locale.ROOT))
- .distinct()
- .toList();
+ return adminTasksService.resolveVisibleShopNameKeys(operatorId);
}
private AdminUserEntity resolveInternalOperator(HttpServletRequest request) {
diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/service/ShopDataCrawlAdminTasksService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/service/ShopDataCrawlAdminTasksService.java
index 4fedd315..c721a57f 100644
--- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/service/ShopDataCrawlAdminTasksService.java
+++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopdatacrawl/service/ShopDataCrawlAdminTasksService.java
@@ -9,6 +9,7 @@ import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlAdminGroup
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlAdminGroupRow;
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlAdminRow;
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlDownloadRowDto;
+import com.nanri.aiimage.modules.shopduplicatecheck.mapper.ShopDuplicateCheckSourceMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -38,6 +39,7 @@ public class ShopDataCrawlAdminTasksService {
private static final Set SUPPORTED_COUNTRIES = Set.of("DE", "UK", "FR", "IT", "ES");
private final ShopDataCrawlAdminTasksMapper adminTasksMapper;
+ private final ShopDuplicateCheckSourceMapper duplicateCheckSourceMapper;
private final ObjectMapper objectMapper;
/**
@@ -94,6 +96,19 @@ public class ShopDataCrawlAdminTasksService {
return adminTasksMapper.selectDownloadRows(p);
}
+ /** 非超管可管店铺名(trim+小写去重,与撞款可见口径一致);超管调用方传 null 表示全量。 */
+ public List resolveVisibleShopNameKeys(Long operatorId) {
+ List names = duplicateCheckSourceMapper.selectManagedShopNames(operatorId);
+ if (names == null || names.isEmpty()) {
+ return List.of();
+ }
+ return names.stream()
+ .filter(name -> name != null && !name.isBlank())
+ .map(name -> name.trim().toLowerCase(Locale.ROOT))
+ .distinct()
+ .toList();
+ }
+
private List