This commit is contained in:
2026-08-20 20:54:30 +08:00
parent acc5bec3b1
commit 96feb8faff
8 changed files with 55 additions and 17 deletions
+1
View File
@@ -37,6 +37,7 @@
| 海外 API 路由、海外 Token、Audience/JWKS | `gateway-overseas/` | `gateway-overseas/AGENTS.md` |
| 尾程订单、海外入仓计划、换标、库存、采购、本土卡车 | `lastleg-service/` | `lastleg-service/AGENTS.md`,再按 `inboundplan/`、`relabel/`、`inventory/`、`procurement/`、`trucking/` 定位 |
| 海外入仓分页、FBA 预约、PDA 卸柜照片 | `lastleg-service/` | `inboundplan/controller/InboundPlanController.java`、`InboundPlanQueryMapper.java`、`InboundFbaAppointmentService.java`、`inboundplan/pda/` |
| 海外仓 PDA 到仓、卸柜、实收、上架、合托 | `lastleg-service/`、`warehouse-overseas-service/` | 业务流程 `lastleg-service/inboundplan/pda/`;库位、托盘和资源锁归 `warehouse-overseas-service/`;设备端 `../apps/pda-overseas-uniapp/`,公网入口 `gateway-overseas/` |
| 海外仓库、海外库位、海外托盘、资源锁 | `warehouse-overseas-service/` | `warehouse-overseas-service/AGENTS.md` |
| PDF 标签、审计、跨区双向事件 | `print/`、`audit-service/`、`event-bridge-service/` | 事件模型先读 `common-event-contract/AGENTS.md`;可靠投递与 CN/OVERSEAS 目标路由读 `event-bridge-service/AGENTS.md` |
Vendored
+4 -2
View File
@@ -62,7 +62,8 @@ pipeline {
booleanParam(name: 'SERVICE_INTEGRATION', defaultValue: false, description: '第三方集成服务(integration-service)')
booleanParam(name: 'SERVICE_LASTLEG', defaultValue: false, description: '海外尾程与入仓服务(lastleg-service)')
booleanParam(name: 'SERVICE_WAREHOUSE_OVERSEAS', defaultValue: false, description: '海外仓资源服务(warehouse-overseas-service)')
booleanParam(name: 'SERVICE_EVENT_BRIDGE', defaultValue: false, description: '跨区域事件桥(event-bridge-service;由环境凭据 REGION_CODE 显式选择 CN 或 OVERSEAS 实例)')
booleanParam(name: 'SERVICE_EVENT_BRIDGE', defaultValue: false, description: '跨区域事件桥(event-bridge-service;由 EVENT_BRIDGE_REGION 选择实例)')
choice(name: 'EVENT_BRIDGE_REGION', choices: ['CN', 'OVERSEAS'], description: '仅部署 event-bridge-service 时生效;明确选择国内或海外实例')
booleanParam(name: 'SERVICE_AUDIT', defaultValue: false, description: '审计服务(audit-service)')
choice(name: 'ENVIRONMENT', choices: environmentChoicesForJob(env.JOB_BASE_NAME), description: '部署环境')
booleanParam(name: 'DEPLOY', defaultValue: false, description: '各服务构建成功后是否独立部署')
@@ -207,7 +208,8 @@ pipeline {
booleanParam(name: 'DEPLOY', value: params.DEPLOY),
booleanParam(name: 'SKIP_TESTS', value: params.SKIP_TESTS),
string(name: 'IMAGE_REPOSITORY', value: env.RESOLVED_IMAGE_REPOSITORY),
string(name: 'DOCKER_BASE_IMAGE', value: params.DOCKER_BASE_IMAGE)
string(name: 'DOCKER_BASE_IMAGE', value: params.DOCKER_BASE_IMAGE),
string(name: 'EVENT_BRIDGE_REGION', value: params.EVENT_BRIDGE_REGION)
]
)
downstreamResults[service] = downstreamBuild.result ?: 'UNKNOWN'
+6 -1
View File
@@ -38,6 +38,7 @@ pipeline {
choice(name: 'ENVIRONMENT', choices: ['test', 'development', 'staging', 'production'], description: '部署环境')
booleanParam(name: 'DEPLOY', defaultValue: false, description: '构建成功后是否部署本服务')
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '是否跳过测试')
choice(name: 'EVENT_BRIDGE_REGION', choices: ['CN', 'OVERSEAS'], description: '仅 event-bridge-service 使用;通常由总控 Job 传入')
string(name: 'IMAGE_REPOSITORY', defaultValue: '172.31.198.7:10082/java-project/baiyue-firstleg', description: '内网共享镜像仓库基础路径,不包含服务名和标签')
string(name: 'DOCKER_BASE_IMAGE', defaultValue: 'docker.m.daocloud.io/library/eclipse-temurin:17-jre-alpine', description: 'Java 17 基础镜像')
}
@@ -193,6 +194,7 @@ pipeline {
withEnv([
"TARGET_IS_LOCAL=${targetIsLocal}",
"EVENT_BRIDGE_REGION_OVERRIDE=${params.EVENT_BRIDGE_REGION}",
"TARGET_SSH_TARGET=${remoteTarget}",
"TARGET_SSH_KEY=${remoteKey}",
"TARGET_SSH_KNOWN_HOSTS=${remoteKnownHosts}",
@@ -233,7 +235,10 @@ pipeline {
withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) {
try {
sh 'printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "' + registryHost + '"'
withEnv(["DEVELOPMENT_DEPLOY_LOCAL=${localDevelopmentDeploy}"]) {
withEnv([
"DEVELOPMENT_DEPLOY_LOCAL=${localDevelopmentDeploy}",
"EVENT_BRIDGE_REGION_OVERRIDE=${params.EVENT_BRIDGE_REGION}"
]) {
withCredentials([file(credentialsId: credentialId, variable: 'DEPLOY_ENV_FILE')]) {
sh "sh deploy/scripts/deploy-services.sh '${params.ENVIRONMENT}' '${env.RESOLVED_IMAGE_REPOSITORY}' '${env.RESOLVED_IMAGE_TAG}' '${env.RESOLVED_SERVICE}' \"\$DEPLOY_ENV_FILE\""
}
+7 -3
View File
@@ -49,11 +49,15 @@ case "$SERVICE" in
PROJECT_NAME="firstleg-$DEPLOY_ENV-overseas"
;;
event-bridge-service)
if ! env_file_has_key REGION_CODE; then
BRIDGE_REGION="${EVENT_BRIDGE_REGION_OVERRIDE:-}"
if [ -z "$BRIDGE_REGION" ] && env_file_has_key REGION_CODE; then
BRIDGE_REGION="$(env_file_value REGION_CODE)"
fi
if [ -z "$BRIDGE_REGION" ]; then
echo "部署 event-bridge-service 时,环境文件必须显式包含 REGION_CODE=CN 或 REGION_CODE=OVERSEAS" >&2
exit 2
fi
case "$(env_file_value REGION_CODE)" in
case "$BRIDGE_REGION" in
CN) ;;
OVERSEAS)
COMPOSE_FILE="deploy/docker-compose.overseas.yml"
@@ -123,7 +127,7 @@ if [ "$SERVICE" = "event-bridge-service" ]; then
if [ "$PROJECT_NAME" = "firstleg-$DEPLOY_ENV-overseas" ]; then
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_OVERSEAS_DB_URL EVENT_BRIDGE_OVERSEAS_DB_USERNAME EVENT_BRIDGE_OVERSEAS_DB_PASSWORD EVENT_CONSUMER_URL"
else
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD EVENT_CONSUMER_URL"
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL"
fi
for key in $EVENT_BRIDGE_KEYS; do
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
+7 -3
View File
@@ -41,11 +41,15 @@ for service in $SERVICES; do
case "$service" in
gateway-overseas|lastleg-service|warehouse-overseas-service) DEPLOY_REGION=OVERSEAS ;;
event-bridge-service)
if ! env_file_has_key REGION_CODE; then
BRIDGE_REGION="${EVENT_BRIDGE_REGION_OVERRIDE:-}"
if [ -z "$BRIDGE_REGION" ] && env_file_has_key REGION_CODE; then
BRIDGE_REGION="$(env_file_value REGION_CODE)"
fi
if [ -z "$BRIDGE_REGION" ]; then
echo "部署 event-bridge-service 时,环境文件必须显式包含 REGION_CODE=CN 或 REGION_CODE=OVERSEAS" >&2
exit 2
fi
case "$(env_file_value REGION_CODE)" in
case "$BRIDGE_REGION" in
CN) ;;
OVERSEAS) DEPLOY_REGION=OVERSEAS ;;
*) echo "部署 event-bridge-service 时,REGION_CODE 只能为 CN 或 OVERSEAS" >&2; exit 2 ;;
@@ -123,7 +127,7 @@ case " $SERVICES " in
if [ "$DEPLOY_REGION" = "OVERSEAS" ]; then
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_OVERSEAS_DB_URL EVENT_BRIDGE_OVERSEAS_DB_USERNAME EVENT_BRIDGE_OVERSEAS_DB_PASSWORD EVENT_CONSUMER_URL"
else
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD EVENT_CONSUMER_URL"
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL"
fi
for key in $EVENT_BRIDGE_KEYS; do
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
+2
View File
@@ -16,6 +16,7 @@
| 安全配置项 | `config/GatewaySecurityProperties.java` |
| 可信身份头传递 | `filter/TrustedIdentityHeaderFilter.java` |
| 环境配置 | `src/main/resources/application.yml`、`../config/nacos/` |
| 海外 PDA 路由 | 设备端 `../../apps/pda-overseas-uniapp/`,服务端入口 `/api/pda/v1/last-leg/**`;必须在 Gateway 路由中显式声明并保持海外 Token/权限校验,不得让设备直连 Lastleg |
| 测试 | `src/test/java/com/baiyue/gateway/config/` |
## 安全规则
@@ -23,6 +24,7 @@
- 生产令牌 `aud` 必须包含 `lastleg-api`,并包含配置要求的身份声明。
- 网关必须清除外部请求伪造的可信身份头,仅传递验签后生成的身份信息。
- JWKS 地址、Issuer 和 Audience 必须外部配置;不得在源码写死生产密钥。
- merchant、platform 与 PDA 路径分别显式维护路由和授权边界;新增 PDA Controller 不会自动成为公网路由,Gateway 未声明前不得把设备链路标记为已联通。
## 修改与验证
+24 -8
View File
@@ -40,12 +40,14 @@ $rootAgentsPath = Join-Path $workspaceRoot 'AGENTS.md'
$adminAgentsPath = Join-Path $workspaceRoot 'apps/admin-web/AGENTS.md'
$clientAgentsPath = Join-Path $workspaceRoot 'apps/client-web/AGENTS.md'
$pdaAgentsPath = Join-Path $workspaceRoot 'apps/pda-uniapp/AGENTS.md'
$overseasPdaAgentsPath = Join-Path $workspaceRoot 'apps/pda-overseas-uniapp/AGENTS.md'
$backendAgentsPath = Join-Path $backendRoot 'AGENTS.md'
$rootAgents = Read-RequiredText $rootAgentsPath
$adminAgents = Read-RequiredText $adminAgentsPath
$clientAgents = Read-RequiredText $clientAgentsPath
$pdaAgents = Read-RequiredText $pdaAgentsPath
$overseasPdaAgents = Read-RequiredText $overseasPdaAgentsPath
$backendAgents = Read-RequiredText $backendAgentsPath
# 部署单元必须能从根索引进入。
@@ -65,14 +67,20 @@ foreach ($application in $applications) {
}
}
# PDA 只检查 pages.json 正式注册页面,不把空目录或未注册原型纳入索引。
$pagesJsonPath = Join-Path $workspaceRoot 'apps/pda-uniapp/pages.json'
$pagesJson = Get-Content -LiteralPath $pagesJsonPath -Raw | ConvertFrom-Json
$registeredPageRoots = $pagesJson.pages.path |
ForEach-Object { $parts = $_ -split '/'; "$($parts[0])/$($parts[1])/" } |
Sort-Object -Unique
foreach ($pageRoot in $registeredPageRoots) {
Assert-Contains $pdaAgents $pageRoot 'PDA AGENTS.md'
# 国内/海外 PDA 只检查 pages.json 正式注册页面,不把空目录或未注册原型纳入索引。
$pdaApplications = @(
@{ Pages = 'apps/pda-uniapp/pages.json'; Text = $pdaAgents; Scope = 'PDA AGENTS.md'; Prefix = '' },
@{ Pages = 'apps/pda-overseas-uniapp/src/pages.json'; Text = $overseasPdaAgents; Scope = 'Overseas PDA AGENTS.md'; Prefix = 'src/' }
)
foreach ($pdaApplication in $pdaApplications) {
$pagesJsonPath = Join-Path $workspaceRoot $pdaApplication.Pages
$pagesJson = Get-Content -LiteralPath $pagesJsonPath -Raw | ConvertFrom-Json
$registeredPageRoots = $pagesJson.pages.path |
ForEach-Object { $parts = $_ -split '/'; "$($pdaApplication.Prefix)$($parts[0])/$($parts[1])/" } |
Sort-Object -Unique
foreach ($pageRoot in $registeredPageRoots) {
Assert-Contains $pdaApplication.Text $pageRoot $pdaApplication.Scope
}
}
# Maven 聚合声明的全部历史/当前模块必须在后端根指引中可定位。
@@ -91,6 +99,13 @@ $criticalPaths = @(
'apps/admin-web/src/modules/overseas-master-data/api/overseasMasterData.ts',
'apps/pda-uniapp/pages/tasks/tasks.vue',
'apps/pda-uniapp/utils/evidence-upload-queue.js',
'apps/pda-overseas-uniapp/package.json',
'apps/pda-overseas-uniapp/src/pages.json',
'apps/pda-overseas-uniapp/src/data/overseas-workbench.js',
'apps/pda-overseas-uniapp/src/pages/operation/operation.vue',
'apps/pda-overseas-uniapp/src/config/runtime.js',
'apps/pda-overseas-uniapp/src/api/auth.js',
'apps/pda-overseas-uniapp/src/utils/scan-broadcast.js',
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/service/FeePaymentSnapshotValidator.java',
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/client/FeePaymentVoucherFileClient.java',
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java',
@@ -107,6 +122,7 @@ $indexedAgentFiles = @(
$adminAgentsPath,
$clientAgentsPath,
$pdaAgentsPath,
$overseasPdaAgentsPath,
$backendAgentsPath,
(Join-Path $backendRoot 'firstleg/AGENTS.md'),
(Join-Path $backendRoot 'finance/AGENTS.md'),
@@ -18,6 +18,8 @@ test('Jenkins pipelines recognize all overseas deployable modules', async () =>
assert.match(service, /gateway-overseas\/target\/gateway-overseas-1\.0\.0-SNAPSHOT\.jar/)
assert.match(service, /lastleg-service\/target\/lastleg-service-1\.0\.0-SNAPSHOT\.jar/)
assert.match(service, /warehouse-overseas-service\/target\/warehouse-overseas-service-1\.0\.0-SNAPSHOT\.jar/)
assert.match(controller, /EVENT_BRIDGE_REGION/)
assert.match(service, /EVENT_BRIDGE_REGION_OVERRIDE=\$\{params\.EVENT_BRIDGE_REGION\}/)
})
test('service map freezes unique internal ports and moves event bridge away from 9300', async () => {
@@ -66,6 +68,8 @@ test('CN and overseas event bridges have explicit opposite-region consumers', as
assert.match(scripts, /docker-compose\.overseas\.yml/)
assert.match(scripts, /REGION_CODE=CN 或 REGION_CODE=OVERSEAS/)
assert.match(scripts, /env_file_value REGION_CODE/)
assert.match(scripts, /EVENT_BRIDGE_REGION_OVERRIDE/)
assert.match(scripts, /FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL/)
for (const key of [
'NACOS_SERVER_ADDR', 'OVERSEAS_NACOS_NAMESPACE', 'INTERNAL_SERVICE_TOKEN',
'JWT_ISSUER', 'OVERSEAS_JWT_AUDIENCE', 'JWT_JWK_SET_URI'