From e9aa2ac047d6002348b1947fc1e1372f2324598d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 10 Sep 2026 11:18:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/sql/README.md | 14 +++- deploy/sql/头程数据库统一增量-MySQL8.sql | 17 ++++- deploy/sql/权限点初始化-MySQL8.sql | 66 +++++++++++++++++++ .../PlatformInboundOrderController.java | 15 +++++ .../firstleg/dto/InboundCartonLabels.java | 5 ++ .../repository/FbaPricingRepository.java | 29 +++++--- .../firstleg/service/FbaPricingService.java | 7 +- .../InboundCartonLabelPrintService.java | 21 ++++++ .../service/InboundCartonLabelService.java | 5 +- .../main/resources/openapi/firstleg-v1.yaml | 33 ++++++++++ .../InboundOrderControllerContractTest.java | 3 + .../FbaPricingRepositoryRuleListTest.java | 26 +++++++- .../service/FbaPricingServiceTest.java | 24 ++++--- .../InboundCartonLabelPrintServiceTest.java | 34 ++++++++++ .../InboundCartonLabelServiceTest.java | 16 +++++ .../order-declaration-planning-permission.sql | 20 ++++++ .../main/resources/db/phase1-admin-rbac.sql | 7 +- ...rmissionInitializationSqlContractTest.java | 44 +++++++++++++ .../resources/db/verify-phase1-admin-rbac.sql | 9 +++ .../db/platform-order-declaration-edit.sql | 17 +---- ...eOrderExportPermissionSqlContractTest.java | 4 +- .../OrderDeclarationUpdateContractTest.java | 10 ++- 22 files changed, 374 insertions(+), 52 deletions(-) create mode 100644 deploy/sql/权限点初始化-MySQL8.sql create mode 100644 iam/src/main/resources/db/order-declaration-planning-permission.sql create mode 100644 iam/src/test/java/com/baiyue/iam/repository/PermissionInitializationSqlContractTest.java diff --git a/deploy/sql/README.md b/deploy/sql/README.md index e8d961513..91f69e6e5 100644 --- a/deploy/sql/README.md +++ b/deploy/sql/README.md @@ -1,8 +1,9 @@ # 测试环境 SQL 执行说明 -## 唯一数据库初始化与发布入口 +## 数据库初始化与权限发布入口 -- 国内区域固定入口:`头程数据库统一增量-MySQL8.sql`。文件名保留历史兼容,内容维护为当前完整数据库基线和存量环境兼容升级,不再按日期拆分发布脚本。 +- 国内区域全量入口:`头程数据库统一增量-MySQL8.sql`。文件名保留历史兼容,内容维护为当前完整数据库基线和存量环境升级,不再按日期拆分结构发布脚本。 +- 通用权限点独立入口:`权限点初始化-MySQL8.sql`。国内与海外业务的 Admin/Client 权限主数据均在 `baiyue_iam` 统一管理。当发布内容只新增或更新 IAM 权限资源及默认角色授权,且 IAM 基础表、父菜单和系统角色已存在时,可只执行该文件。它不建表,不创建账号、客户、仓库或业务数据。 - 海外区域固定入口:`海外区域统一增量-MySQL8.sql`。该文件已经内联 Lastleg 与 Warehouse Overseas 的完整空库基线,会自行创建并初始化 `baiyue_lastleg`、`baiyue_lastlag_warehouse` 和独立的 `baiyue_event_bridge_overseas`,清库后不得再额外执行模块基线。IAM 安全事件 Inbox 和可恢复投影位于 `baiyue_lastleg.ll_security_*`,Redis 只作可重建镜像。 - Lastleg 基线和海外固定增量仅确定性初始化国内固定 `platform_admin`(`authUserId=1000001`、`tenantId=0`)的 USER/AUTHORIZATION version=0 安全投影;AUTHORIZATION 同步冻结当前 `PLATFORM_ADMIN` 角色及 IAM 跨区前缀下的完整权限集合,保证空库重建后海外 Gateway 可立即鉴权。统一 SQL 重放只修正仍处于固定 baseline version=0 的该行,其他既有开发账号不建立兼容表或长期补投入口。 - 国内、海外 Compose 部署默认设置 `SPRING_SQL_INIT_MODE=never`,执行任一 SQL 均需用户单独授权。 @@ -25,6 +26,13 @@ 4. 每个增量区块末尾增加只读断言,并同步维护统一脚本末尾的总体验证。 5. MySQL DDL 会隐式提交,执行前仍应在目标环境确认连接和授权;不能把跨库脚本视为可整体回滚事务。 +### 权限点独立入口边界 + +- 新增权限时,同步维护 IAM 模块来源、`权限点初始化-MySQL8.sql`、对应区域的全量统一 SQL、前端按钮/路由、Controller `@PreAuthorize` 和 OpenAPI。 +- 权限点入口只用于既有 IAM 基线上的权限新增或元数据/默认授权更新。涉及建表、字段、索引、角色创建或其他业务数据时,仍执行全量统一 SQL。 +- 独立入口必须可重复执行,执行前校验稳定权限 ID/权限码没有被其他语义占用,执行后只在授权完整时返回 `permission_initialization_status = PASS`。 +- 权限写入后不修改 Redis 中已签发的会话快照;受影响用户必须退出并重新登录。权限撤销不通过该 SQL 直接处理,应走 IAM 角色授权命令以同步撤销会话。 + ## Finance 首期人工财务基线 - Finance 数据库为 `baiyue_finance`,模块溯源脚本为 `finance/src/main/resources/db/finance.sql`,测试和存量环境仍只执行统一入口。 @@ -53,7 +61,7 @@ 固定入口同时幂等补齐审核自动开户所需的全局 `MERCHANT_OWNER` 角色模板、全局商户菜单/按钮权限和角色授权;执行后应使用 `iam/src/test/resources/db/verify-phase1-admin-rbac.sql` 确认模板存在且权限完整。 -测试环境只执行当前固定入口;历史脚本和模块脚本不应在固定入口执行后再次串行执行。 +测试环境结构发布只执行全量固定入口;纯权限点发布可改用权限点独立入口。历史脚本和模块脚本不应在任一发布入口执行后再次串行执行。 ## 执行方式 diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index bf3caab7e..b26c862a2 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -1345,18 +1345,19 @@ VALUES (5102019, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:create', '平台代客创建订单', NULL, NULL, 90, 'ACTIVE', 0), (5102024, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:edit', '平台编辑订单草稿', NULL, NULL, 100, 'ACTIVE', 0), (5102025, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:submit', '平台代客提交订单', NULL, NULL, 110, 'ACTIVE', 0), - (5102026, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:export', '导出订单列表', NULL, NULL, 120, 'ACTIVE', 0) + (5102026, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:export', '导出订单列表', NULL, NULL, 120, 'ACTIVE', 0), + (5102027, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:declaration:edit', '修改订单申报与运营预估', NULL, NULL, 130, 'ACTIVE', 0) ON DUPLICATE KEY UPDATE permission_name = VALUES(permission_name), parent_id = VALUES(parent_id), status = 'ACTIVE', deleted = 0; INSERT INTO iam_role_permission (role_id, permission_id, tenant_id) SELECT 4000001, id, 0 FROM iam_permission -WHERE id IN (5102019, 5102024, 5102025, 5102026) +WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027) ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id); INSERT INTO iam_role_permission (role_id, permission_id, tenant_id) SELECT 4000003, id, 0 FROM iam_permission -WHERE id IN (5102019, 5102024, 5102025, 5102026) +WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027) ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id); INSERT INTO iam_membership_role (membership_id, role_id, tenant_id) VALUES @@ -15719,6 +15720,16 @@ BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:管理员或订单运营角色缺少订单导出与下载中心权限'; END IF; + IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission + WHERE id = 5102027 AND tenant_id = 0 AND app_code = 'PLATFORM' + AND permission_code = 'platform:order:declaration:edit' + AND parent_id = 5102010 AND status = 'ACTIVE' AND deleted = 0) <> 1 + OR (SELECT COUNT(DISTINCT role_id) FROM baiyue_iam.iam_role_permission + WHERE tenant_id = 0 AND permission_id = 5102027 + AND role_id IN (4000001, 4000003)) <> 2 THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = '统一迁移失败:订单申报与运营预估权限未正确授予平台管理员和订单运营角色'; + END IF; IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND id BETWEEN 5102121 AND 5102129 diff --git a/deploy/sql/权限点初始化-MySQL8.sql b/deploy/sql/权限点初始化-MySQL8.sql new file mode 100644 index 000000000..651f45d01 --- /dev/null +++ b/deploy/sql/权限点初始化-MySQL8.sql @@ -0,0 +1,66 @@ +-- 通用 IAM 权限点独立初始化入口(MySQL 8.0)。 +-- 国内与海外业务的 Admin/Client 权限主数据均在 baiyue_iam 统一管理。 +-- 只允许写入 baiyue_iam 的权限资源和角色授权; +-- 不建表、不创建账号/客户/仓库,不写业务测试数据。 +-- 前置:IAM 基础表、相关父菜单及系统角色已由全量基线创建。 +USE baiyue_iam; +SET NAMES utf8mb4; + +DROP PROCEDURE IF EXISTS apply_permission_points; +DELIMITER $$ +CREATE PROCEDURE apply_permission_points() +BEGIN + IF NOT EXISTS (SELECT 1 FROM iam_permission WHERE id = 5102010 AND tenant_id = 0 + AND permission_code = 'platform:order:list' AND permission_type = 'MENU') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = '权限初始化失败:订单列表父菜单不存在,请先执行全量基线'; + END IF; + IF (SELECT COUNT(*) FROM iam_role WHERE id IN (4000001, 4000003) AND tenant_id = 0 + AND app_code = 'PLATFORM' AND status = 'ACTIVE' AND deleted = 0) <> 2 THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = '权限初始化失败:平台管理员或订单运营角色不存在'; + END IF; + IF EXISTS (SELECT 1 FROM iam_permission WHERE id = 5102027 + AND permission_code <> 'platform:order:declaration:edit') + OR EXISTS (SELECT 1 FROM iam_permission + WHERE permission_code = 'platform:order:declaration:edit' AND id <> 5102027) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = '权限初始化失败:5102027 或权限码已被其他语义占用'; + END IF; + + START TRANSACTION; + INSERT INTO iam_permission + (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, + route_path, http_method, sort_no, status, deleted) + VALUES + (5102027, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:declaration:edit', + '修改订单申报与运营预估', NULL, NULL, 130, 'ACTIVE', 0) + ON DUPLICATE KEY UPDATE + permission_name = VALUES(permission_name), parent_id = VALUES(parent_id), + permission_type = VALUES(permission_type), app_code = VALUES(app_code), + sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0; + + INSERT INTO iam_role_permission (role_id, permission_id, tenant_id) + SELECT role_id, 5102027, 0 + FROM (SELECT 4000001 AS role_id UNION ALL SELECT 4000003 AS role_id) roles + ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id); + + IF (SELECT COUNT(*) FROM iam_permission WHERE id = 5102027 AND tenant_id = 0 + AND app_code = 'PLATFORM' AND permission_type = 'BUTTON' AND parent_id = 5102010 + AND permission_code = 'platform:order:declaration:edit' + AND status = 'ACTIVE' AND deleted = 0) <> 1 + OR (SELECT COUNT(DISTINCT role_id) FROM iam_role_permission + WHERE tenant_id = 0 AND permission_id = 5102027 + AND role_id IN (4000001, 4000003)) <> 2 THEN + ROLLBACK; + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = '权限初始化失败:订单申报与运营预估权限未完整写入'; + END IF; + COMMIT; +END$$ +DELIMITER ; + +CALL apply_permission_points(); +DROP PROCEDURE IF EXISTS apply_permission_points; + +SELECT 'PASS' AS permission_initialization_status; diff --git a/firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundOrderController.java b/firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundOrderController.java index c53158e70..57cfbf124 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundOrderController.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundOrderController.java @@ -10,6 +10,7 @@ import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.firstleg.dto.InboundOrderDetail; import com.baiyue.firstleg.dto.InboundOrderQuery; import com.baiyue.firstleg.dto.InboundOrderSummary; +import com.baiyue.firstleg.dto.InboundCartonLabels; import com.baiyue.firstleg.dto.InboundHoldDtos.CommandRequest; import com.baiyue.firstleg.dto.InboundHoldDtos.Detail; import com.baiyue.firstleg.service.FirstlegScopeGuard; @@ -22,6 +23,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; @@ -29,6 +31,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestParam; @@ -160,6 +163,18 @@ public class PlatformInboundOrderController { return cartonLabelPrintService.generate(tenantId, inboundId, cartonIndex, requestId); } + @Operation(summary = "批量生成入仓箱唛 PDF", + description = "按所选客户和入库单聚合生成一个 PDF;首次调用冻结各入库单整单箱号。") + @PostMapping("/carton-label-pdf") + @PreAuthorize("hasAuthority('platform:firstleg:inbound:view')") + public ResponseEntity printCartonLabelsBatch( + @AuthenticationPrincipal AuthenticatedUser user, + @RequestBody @Size(min = 1, max = 200) List<@Valid InboundCartonLabels.PlatformBatchItem> items, + @RequestHeader(value = "X-Request-Id", required = false) String requestId) { + requirePlatform(user); + return cartonLabelPrintService.generateForPlatformInbounds(items, requestId); + } + private static void requirePlatformTenant(AuthenticatedUser user, long tenantId) { FirstlegScopeGuard.platform(user, tenantId); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/dto/InboundCartonLabels.java b/firstleg/src/main/java/com/baiyue/firstleg/dto/InboundCartonLabels.java index 937a23367..cec38faa7 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/dto/InboundCartonLabels.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/dto/InboundCartonLabels.java @@ -3,6 +3,8 @@ package com.baiyue.firstleg.dto; import java.math.BigDecimal; import java.util.List; +import jakarta.validation.constraints.Min; + /** 稳定箱号分配、补打和测量解析契约。 */ public final class InboundCartonLabels { private InboundCartonLabels() { @@ -12,6 +14,9 @@ public final class InboundCartonLabels { List