优化
This commit is contained in:
+161
-7
@@ -1,22 +1,25 @@
|
||||
# Print PDF 服务接入指南
|
||||
# Print PDF 内部调用契约
|
||||
|
||||
本文面向需要生成入仓标签 PDF 的业务服务。Print 是无数据库的内部渲染服务:调用方提交完整标签数据,Print 同步返回一次性 PDF,不保存任务、文件或业务数据。
|
||||
本文是其他 Java 业务模块及代码生成 AI 接入 Print 的实现约束,不是终端用户操作手册。Print 是无数据库的内部渲染服务:调用方提交完整打印数据,Print 同步返回一次性 PDF,不保存任务、文件或业务数据。
|
||||
|
||||
完整机器可读契约见 [`src/main/resources/openapi/print-internal-v1.yaml`](src/main/resources/openapi/print-internal-v1.yaml),版式与技术决策见 [`../docs/2026-08-04-无状态PDF打印服务设计.md`](../docs/2026-08-04-无状态PDF打印服务设计.md)。
|
||||
机器可读 HTTP 契约以 [`src/main/resources/openapi/print-internal-v1.yaml`](src/main/resources/openapi/print-internal-v1.yaml) 为准;本文件补充调用方必须实现、但 OpenAPI 无法完整表达的权限、字段映射、响应分支和日志约束。调用方必须自行声明 DTO,不得依赖 Print 模块的 Java 实现类。
|
||||
|
||||
## 1. 调用边界
|
||||
|
||||
- 服务发现名:`print-service`。
|
||||
- 内部地址:`POST /internal/print/v1/pdfs/inbound-marks`。
|
||||
- A4 海运单据内部地址:`POST /internal/print/v1/pdfs/freight-a4-documents`。
|
||||
- 本地直连:`http://127.0.0.1:9207`。
|
||||
- 请求必须携带 `X-Internal-Service-Token`,值使用各服务已有的 `baiyue.security.internal-service-token` 配置。
|
||||
- 不把该接口加入 Gateway,也不允许 Admin 浏览器直接携带内部令牌访问 Print。
|
||||
- Admin 请求业务服务;业务服务完成 JWT、客户归属、权限和数据范围校验,组装完整 `labels[]` 后调用 Print,再把 PDF 响应转发给浏览器。JWT 中的 `tenant_id` 当前是客户隔离兼容字段,Print 不解析或引入真实租户层。
|
||||
- Admin 请求业务服务;业务服务完成 JWT、客户归属、权限和数据范围校验,组装完整 `labels[]` 或 `documents[]` 后调用 Print,再把 PDF 响应转发给浏览器。JWT 中的 `tenant_id` 当前是客户隔离兼容字段,Print 不解析或引入真实租户层。
|
||||
|
||||
调用方不得要求 Print 查询订单、补箱号、排序或修正数据。数组顺序就是 PDF 页序,每个数组元素生成一页。
|
||||
调用方必须从本模块拥有的数据自动组装打印字段,不得把 Print 请求 DTO 暴露成要求用户逐项填写的表单。调用方不得要求 Print 查询订单、补箱号、排序、推导或修正数据。数组顺序就是 PDF 页序,每个数组元素生成一页。
|
||||
|
||||
## 2. 请求契约
|
||||
|
||||
### 2.1 入仓标签
|
||||
|
||||
```http
|
||||
POST /internal/print/v1/pdfs/inbound-marks HTTP/1.1
|
||||
Content-Type: application/json
|
||||
@@ -69,6 +72,57 @@ X-Request-Id: <optional-correlation-id>
|
||||
|
||||
未传 `downloadFileName` 时,服务使用 `inbound-marks-{requestId}.pdf`。请求体最大 2 MiB,PDF 最大 32 MiB。
|
||||
|
||||
### 2.2 海运 A4 单据
|
||||
|
||||
```json
|
||||
{
|
||||
"templateCode": "SEA_FREIGHT_SHIPPING_INSTRUCTION_A4_V1",
|
||||
"pageSpec": {
|
||||
"widthMm": 210,
|
||||
"heightMm": 297
|
||||
},
|
||||
"downloadFileName": "BYSE2607044-托运书.pdf",
|
||||
"documents": [
|
||||
{
|
||||
"fields": {
|
||||
"toCompany": "宁波胜泽远航国际物流有限公司",
|
||||
"shipper": "ZHEJIANG BAIYUE TRADE CO.,LIMITED\nROOM 8613...",
|
||||
"consignee": "SANATRK FOR IMPORT AND TRADE OF AUTO SPARE PARTS",
|
||||
"jobNumber": "BYSE2607044"
|
||||
},
|
||||
"cargoRows": [
|
||||
{
|
||||
"marksAndNos": "N/M",
|
||||
"quantity": "700",
|
||||
"packaging": "PACKAGES",
|
||||
"descriptionOfGoods": "CAR ACCESSORIES\nACID: 4120661061019510011",
|
||||
"grossWeight": "28000.00 KGS",
|
||||
"measurement": "28.000 CBM"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `templateCode` | 是 | `SEA_FREIGHT_SHIPPING_INSTRUCTION_A4_V1` 或 `BILL_OF_LADING_CONFIRMATION_A4_V1` |
|
||||
| `pageSpec.widthMm` | 是 | 固定数值 `210` |
|
||||
| `pageSpec.heightMm` | 是 | 固定数值 `297` |
|
||||
| `downloadFileName` | 否 | 最多 120 字符,必须以 `.pdf` 结尾,不得含路径或文件系统保留字符 |
|
||||
| `documents` | 是 | 1-2000 项;一个元素对应一页,按数组顺序生成 |
|
||||
| `documents[].fields` | 否 | 字符串字典,字段值允许换行,单值最多 4000 字符 |
|
||||
| `documents[].cargoRows` | 否 | 0-1 项;当前 V1 模板只渲染一行货物,调用方不得发送第二行 |
|
||||
|
||||
托运书模板支持的完整 `fields` 键:`toCompany`、`issuerNameCn`、`issuerNameEn`、`issuerAddress`、`issuerPostalCode`、`issuerTel`、`issuerFax`、`shipper`、`consignee`、`notifyParty`、`picName`、`picTel`、`picFax`、`documentName`、`documentTel`、`documentFax`、`jobNumber`、`etdDate`、`serviceTerm`、`freightTerm`、`apCode`、`apName`、`cargoQuantity`、`transshipment`、`partialShipment`、`oceanVesselVoyage`、`portOfLoading`、`portOfDischarge`、`placeOfDelivery`、`totalPackagesInWords`、`remark`、`carrier`、`bookingAgent`、`printDate`。
|
||||
|
||||
提单确认件模板支持的完整 `fields` 键:`shipper`、`consignee`、`notifyParty`、`bookingNumber`、`ensAmsCutoffDate`、`customsCutoffDate`、`expectedDepartureDate`、`oceanVesselVoyage`、`portOfLoading`、`portOfDischarge`、`placeOfDelivery`、`finalDestination`、`serviceTerm`、`freightTerm`、`shipperLoadCountSeal`、`containerSpec`、`shippedOnBoard`、`draftText`、`totalPackagesInWords`、`containerSummary`、`notes`、`issuePlace`、`issueDate`。
|
||||
|
||||
调用方 MUST 只发送所选模板支持的键。当前服务会忽略未知键而不报错,因此调用方必须通过单元测试固定字段映射,避免拼写错误生成缺字段 PDF。空值字段可以省略;不得为了填满模板要求用户重复录入订单、客户、航线或货物中已经存在的数据。
|
||||
|
||||
未传 `downloadFileName` 时,服务使用 `freight-a4-documents-{requestId}.pdf`。请求体最大 2 MiB,PDF 最大 32 MiB。
|
||||
|
||||
## 3. 响应处理
|
||||
|
||||
调用方必须同时判断 HTTP 状态和 `Content-Type`,不能只判断 HTTP 200:
|
||||
@@ -114,6 +168,12 @@ public interface PrintPdfClient {
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
|
||||
@RequestBody InboundMarkPdfRequest request);
|
||||
|
||||
@PostMapping(value = "/freight-a4-documents", consumes = "application/json")
|
||||
ResponseEntity<byte[]> generateFreightA4Documents(
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
|
||||
@RequestBody FreightA4PdfRequest request);
|
||||
|
||||
record InboundMarkPdfRequest(
|
||||
String templateCode,
|
||||
PageSpec pageSpec,
|
||||
@@ -128,6 +188,22 @@ public interface PrintPdfClient {
|
||||
String cartonText,
|
||||
String routeText,
|
||||
String qrContent) {}
|
||||
|
||||
record FreightA4PdfRequest(
|
||||
String templateCode,
|
||||
PageSpec pageSpec,
|
||||
String downloadFileName,
|
||||
List<FreightDocument> documents) {}
|
||||
|
||||
record FreightDocument(Map<String, String> fields, List<CargoRow> cargoRows) {}
|
||||
|
||||
record CargoRow(
|
||||
String marksAndNos,
|
||||
String quantity,
|
||||
String packaging,
|
||||
String descriptionOfGoods,
|
||||
String grossWeight,
|
||||
String measurement) {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -176,13 +252,91 @@ spring:
|
||||
- 不建议对该 POST 做无限或无退避自动重试;超时或 `A0429` 最多有限重试,并加入退避和抖动。
|
||||
- 单次最多 2000 页。大批量请求应设置不低于 30 秒的读取超时,并避免调用方自身重复复制 PDF 字节。
|
||||
- 浏览器下载场景由业务服务原样转发 PDF,不要把 PDF Base64 塞入 JSON,也不要为了“一次性下载”写入数据库。
|
||||
- 日志记录模板、页数、耗时、PDF 字节数、`requestId`、`traceId` 和错误码;不要记录内部令牌、完整 `labels[]`、二维码内容或 PDF 二进制。
|
||||
- 日志记录模板、页数、耗时、PDF 字节数、`requestId`、`traceId` 和错误码;不要记录内部令牌、完整 `labels[]`、完整 `documents[]`、二维码内容或 PDF 二进制。
|
||||
|
||||
## 6. 联调检查清单
|
||||
|
||||
1. `GET /actuator/health` 返回 `UP`。
|
||||
2. 使用正确内部令牌提交 1 页,响应为 `application/pdf`。
|
||||
3. 检查页面为严格 `100mm x 100mm`,`X-Print-Page-Count` 与 `labels.length` 一致。
|
||||
3. 检查入仓标签页面为严格 `100mm x 100mm`,A4 单据页面为严格 `210mm x 297mm`,`X-Print-Page-Count` 与数组长度一致。
|
||||
4. 提交多页并检查页序与数组顺序一致。
|
||||
5. 分别验证错误令牌、非法尺寸、空数组、超过 2000 页和超过 2 MiB 请求体。
|
||||
6. Admin 下载链路不得在浏览器请求或日志中出现 `X-Internal-Service-Token`。
|
||||
|
||||
## 7. AI 实施与验证清单
|
||||
|
||||
修改其他业务模块接入 Print 时,按以下顺序实施,不得从 Admin 浏览器直连 Print:
|
||||
|
||||
1. 从 OpenAPI 和本文件在调用模块内声明请求 DTO 与 Feign Client;不得新增对 `print-service` Java 包的编译依赖。
|
||||
2. 在调用模块的 Service 中完成权限、客户归属和数据范围校验,再从已有订单、客户、航线及货物数据自动映射打印字段。
|
||||
3. `labels[]`、`documents[]` 和 `cargoRows` 顺序必须由调用模块明确决定;A4 V1 的 `cargoRows` 只能包含 0 或 1 项。
|
||||
4. 同时处理 `application/pdf`、HTTP 200 的 `application/json` 业务错误及 HTTP 413;不得把任意 HTTP 200 当作成功 PDF。
|
||||
5. 只向浏览器转发安全响应头,不得转发或记录 `X-Internal-Service-Token`,不得记录完整打印请求或 PDF 二进制。
|
||||
6. 为字段映射、PDF/JSON 响应分支、`A0403`、`A0422`、`A0429`、HTTP 413 和超时增加调用模块测试。
|
||||
|
||||
只验证 Print 本身时,从 `baiyue-firstleg/` 构建并启动单个服务;不需要启动业务服务、Nacos 或数据库:
|
||||
|
||||
```powershell
|
||||
mvn -pl print -am package -DskipTests
|
||||
$env:PRINT_PORT="9207"
|
||||
$env:SPRING_PROFILES_ACTIVE="dev"
|
||||
$env:INTERNAL_SERVICE_TOKEN="print-local-contract-test-token-20260806"
|
||||
$env:SPRING_CLOUD_NACOS_CONFIG_ENABLED="false"
|
||||
$env:NACOS_DISCOVERY_ENABLED="false"
|
||||
$env:NACOS_REGISTER_ENABLED="false"
|
||||
java -jar print\target\print-service-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
启动后向 `POST http://127.0.0.1:9207/internal/print/v1/pdfs/freight-a4-documents` 发送 `Content-Type: application/json`、`Accept: application/pdf, application/json`、上述内部令牌及任意 `X-Request-Id`。托运书验证请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"templateCode": "SEA_FREIGHT_SHIPPING_INSTRUCTION_A4_V1",
|
||||
"pageSpec": {
|
||||
"widthMm": 210,
|
||||
"heightMm": 297
|
||||
},
|
||||
"downloadFileName": "BYSE2607044-托运书.pdf",
|
||||
"documents": [
|
||||
{
|
||||
"fields": {
|
||||
"toCompany": "宁波胜泽远航国际物流有限公司",
|
||||
"issuerNameCn": "浙江柏悦贸易有限公司(香港)",
|
||||
"issuerNameEn": "ZHEJIANG BAIYUE TRADE CO.,LIMITED",
|
||||
"issuerAddress": "地址:浙江省义乌市稠州北路 1399 号新光国际 8613 室",
|
||||
"issuerPostalCode": "322000",
|
||||
"issuerTel": "0579-85875557",
|
||||
"issuerFax": "0579-85601277",
|
||||
"shipper": "ZHEJIANG BAIYUE TRADE CO.,LIMITED\nROOM 8613,NO.1399,CHOUZHOU NORTH ROAD,FUTIAN STREET,YIWU CITY,ZHEJIANG PROVINCE\nTEL:0579-85695557",
|
||||
"consignee": "SANATRK FOR IMPORT AND TRADE OF AUTO SPARE PARTS\n3 ATIA ELZEKRY ST- OMAR EBN ELKHTAB CITY SHOBRA\nELKHIMA QALYUBIA SECOND-OWNER/ MENA MATTA GABALLA\nMATTA\nTEL:01225672354",
|
||||
"notifyParty": "SAME AS CONSIGNEE",
|
||||
"picName": "苏天翔",
|
||||
"jobNumber": "BYSE2607044",
|
||||
"etdDate": "2026/07/24",
|
||||
"serviceTerm": "CY-CY",
|
||||
"freightTerm": "FREIGHT PREPAID",
|
||||
"cargoQuantity": "1*20GP",
|
||||
"portOfLoading": "NINGBO",
|
||||
"portOfDischarge": "EL DEKHEILA",
|
||||
"placeOfDelivery": "EL DEKHEILA",
|
||||
"totalPackagesInWords": "SAY SIX HUNDRED AND FIFTY NINE PACKAGES ONLY.",
|
||||
"carrier": "万海航运(香港)股份有限公司",
|
||||
"bookingAgent": "宁波胜泽远航国际物流有限公司",
|
||||
"printDate": "2026 年 07 月 16 日"
|
||||
},
|
||||
"cargoRows": [
|
||||
{
|
||||
"marksAndNos": "N/M",
|
||||
"quantity": "700",
|
||||
"packaging": "PACKAGES",
|
||||
"descriptionOfGoods": "CAR ACCESSORIES\nACID: 4120661061019510011\n申请目的港 21 天免用箱",
|
||||
"grossWeight": "28000.00 KGS",
|
||||
"measurement": "28.000 CBM"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
提单确认件必须把 `templateCode` 改为 `BILL_OF_LADING_CONFIRMATION_A4_V1`,并仅传上一节列出的提单确认件字段。成功响应必须是 `Content-Type: application/pdf`;`application/json` 必须按响应体 `code/message` 作为失败处理。
|
||||
|
||||
Reference in New Issue
Block a user