优化后端框架

This commit is contained in:
BBIT-Kai
2026-05-07 10:25:02 +08:00
parent c932419c73
commit f7a27d99e1
73 changed files with 1742 additions and 1596 deletions
+315
View File
@@ -0,0 +1,315 @@
# 农产品收购发票开票平台系统设计
## 1. 系统定位
本平台作为“业务系统”和“票通数电发票平台”之间的开票中台:
- 前端 Web:登录、查看待开票农产品收购数据、选择数据、发起开票、处理票通认证、查看开票结果。
- Ktor 后端:统一封装我方业务接口、票通接口、票通加密签名、开票状态机、结果回写、重试与审计。
- 我方业务系统:提供公司、待开票列表、开票前写库、开票结果回写等接口。
- 票通系统:提供数电账号认证、短信登录、实名认证二维码、蓝字发票开具、发票查询、结果推送等接口。
当前文档资料中,本业务主要对应票通“直接开票”场景:调用 `invoiceBlue.pt` 开具蓝字数电发票;开票结果通过票通推送或主动查询同步。票通要求业务报文使用 UTF-8,业务 JSON 先 3DES 加密,外层报文再按字段排序后用 RSA 签名。
## 2. 总体架构
```mermaid
flowchart LR
Web["Web 前端"]
Ktor["Ktor 后端"]
Biz["我方业务系统"]
DB["平台数据库"]
PT["票通 OpenAPI"]
Web -->|"登录/查询/开票/认证"| Ktor
Ktor -->|"公司、待开票、预写库、状态回写"| Biz
Ktor -->|"开票任务、状态、票通账号、审计日志"| DB
Ktor -->|"加密签名请求"| PT
PT -->|"推送结果 callback"| Ktor
Ktor -->|"状态更新"| Web
```
建议 Ktor 后端承担“防重复、状态机、签名加密、票通异常兼容”的职责,前端不要直接调用票通,也不要持有票通密钥。
## 3. 核心业务流程
### 3.1 平台登录与公司上下文
1. 用户登录 Web。
2. Ktor 调用我方登录或用户信息接口,获取 `userId``companyId`、公司名称、纳税人识别号 `taxpayerNum`
3. Ktor 建立平台会话/JWT,前端后续请求都带当前公司上下文。
### 3.2 查询待开票列表
1. 前端调用 `GET /api/invoice-candidates`
2. Ktor 按当前 `companyId` 调用我方“根据公司 id 查询待开票列表”接口。
3. Ktor 对列表做轻量规范化:金额、税率、商品名称、收购方/销售方信息、是否可开票、已锁定状态。
4. 前端展示并支持勾选。
### 3.3 发起开票
1. 前端选中待开票数据,调用 `POST /api/invoice-jobs`
2. Ktor 校验当前公司票通配置和数电账号状态。
3. 如果 `getTaxBureauAccountAuthStatus.pt` 返回需要认证:
- `operationProposed=1`:提示扫码认证;
- `operationProposed=2`:展示扫码和短信两种方式;
- `operationProposed=3`:提示短信登录;
- `operationProposed=0`:继续开票。
4. Ktor 调用我方“开票前写库”接口,锁定这些业务数据,并取得我方开票批次号或业务流水号。
5. Ktor 生成稳定且唯一的 `invoiceReqSerialNo`。同一张发票重试必须复用同一个流水号,避免重复开票。
6. Ktor 按票通字段组装 `invoiceBlue.pt` 业务报文,调用票通。
7. Ktor 记录票通同步返回结果:
- 成功受理或开票中:本地状态置为 `PROCESSING`
- 立即成功:置为 `SUCCESS`,回写我方系统;
- 失败:置为 `FAILED``NEED_AUTH`,回写我方系统。
### 3.4 认证流程
票通文档中认证不是简单登录态,包含登录认证和风险认证。推荐做成统一认证面板。
- 查询认证状态:`getTaxBureauAccountAuthStatus.pt`
- 获取短信验证码:`sendLoginSmsCode.pt`
- 短信登录:`smsLogin.pt`
- 获取实名认证二维码:`getAuthenticationQrcode.pt`
- 查询二维码扫码状态:`queryAuthQrcodeScanStatus.pt`
- 退出电子税局登录:`logout` 类接口,对应文档 2.46
前端行为:
- `operationProposed=1`:调用后端获取二维码,轮询扫码状态。
- `operationProposed=2`:同时展示“扫码认证”和“短信认证”。
- `operationProposed=3`:展示手机号/账号、验证码输入、发送验证码按钮。
- 认证完成后,用户点击“继续开票”,后端用原 `invoiceReqSerialNo` 继续或重试。
### 3.5 开票结果同步
建议同时支持两条链路:
1. 票通推送:实现 `POST /api/callbacks/piaotong/invoice`,接收票通推送发票主要信息或全票面信息。
2. 主动查询:后台任务定时调用 `queryInvoice.pt` 查询 `PROCESSING``NEED_AUTH_RETRYING` 的发票。
票通状态码映射:
| 票通 code | 含义 | 本地状态 |
| --- | --- | --- |
| `0000` | 开票成功 | `SUCCESS` |
| `6666` | 未开票 | `PENDING``PROCESSING` |
| `7777` | 开票中 | `PROCESSING` |
| `9999` | 开票失败 | `FAILED` |
| `3999` | 需要扫码或短信认证 | `NEED_AUTH` |
| `4999` | 红字确认单申请中 | 后续冲红扩展 |
| `5999` | 红字确认单审核中 | 后续冲红扩展 |
结果落库后,Ktor 调用我方“写状态”接口,把成功/失败、票号、数电发票号码、失败原因、票通流水号等回写。
## 4. 后端模块设计
建议 Ktor 后端按以下模块拆分:
```text
server/
src/main/kotlin/com/bbit/agriinvoice/
Application.kt
config/
AppConfig.kt
PiaotongConfig.kt
BizSystemConfig.kt
routes/
AuthRoutes.kt
InvoiceCandidateRoutes.kt
InvoiceJobRoutes.kt
PiaotongAuthRoutes.kt
PiaotongCallbackRoutes.kt
domain/
Company.kt
InvoiceCandidate.kt
InvoiceJob.kt
InvoiceStatus.kt
TaxAccount.kt
service/
LoginService.kt
InvoiceCandidateService.kt
InvoiceIssueService.kt
InvoiceStatusService.kt
PiaotongAuthService.kt
PiaotongCallbackService.kt
integration/
biz/
BizSystemClient.kt
BizDtos.kt
piaotong/
PiaotongClient.kt
PiaotongCrypto.kt
PiaotongDtos.kt
PiaotongEndpoints.kt
repository/
InvoiceJobRepository.kt
TaxAccountRepository.kt
AuditLogRepository.kt
```
关键服务职责:
- `PiaotongCrypto`:3DES 加解密、RSA 签名验签、外层报文构造、响应解密。
- `PiaotongClient`:封装票通 endpoint,所有票通接口只接受/返回业务 DTO。
- `InvoiceIssueService`:开票主流程、幂等控制、状态推进。
- `PiaotongAuthService`:数电账号状态查询、短信登录、二维码认证。
- `InvoiceStatusService`:票通状态映射、本地落库、我方业务系统状态回写。
## 5. Web 页面设计
### 5.1 页面
- 登录页:登录平台并进入公司上下文。
- 待开票列表页:筛选、勾选、查看金额合计、发起开票。
- 开票确认弹窗:展示本次开票单据、购买方/销售方、商品行、价税合计。
- 票通认证弹窗:根据后端返回展示扫码或短信认证。
- 开票结果页:展示处理中、成功、失败、需要认证,支持刷新和重试。
- 系统配置页:维护票通数电账号、开票员、税号、默认商品编码等。
### 5.2 前端状态
列表项建议展示:
- `未开票`
- `已锁定`
- `开票中`
- `待认证`
- `开票成功`
- `开票失败`
前端不要自己判断票通接口细节,只消费后端聚合后的 `status``actionRequired``authOptions``message`
## 6. Ktor 对外接口草案
```http
POST /api/auth/login
GET /api/me
GET /api/invoice-candidates
POST /api/invoice-jobs
GET /api/invoice-jobs/{jobId}
POST /api/invoice-jobs/{jobId}/retry
GET /api/piaotong/accounts/{account}/auth-status
POST /api/piaotong/accounts/{account}/sms-code
POST /api/piaotong/accounts/{account}/sms-login
POST /api/piaotong/accounts/{account}/auth-qrcode
GET /api/piaotong/accounts/{account}/auth-qrcode/{authId}
POST /api/piaotong/accounts/{account}/logout
POST /api/callbacks/piaotong/invoice
```
`POST /api/invoice-jobs` 请求示例:
```json
{
"candidateIds": ["A001", "A002"],
"taxAccount": "18900000000",
"invoiceKind": "82",
"buyer": {
"name": "购买方名称",
"taxpayerNum": "XX0000000000000000"
}
}
```
响应示例:
```json
{
"jobId": "job_20260424153000001",
"status": "NEED_AUTH",
"message": "当前数电账号需要短信或扫码认证",
"authOptions": ["QRCODE", "SMS"]
}
```
## 7. 数据库表建议
### 7.1 invoice_job
| 字段 | 说明 |
| --- | --- |
| id | 平台开票任务 ID |
| company_id | 公司 ID |
| taxpayer_num | 销方纳税人识别号 |
| invoice_req_serial_no | 票通发票请求流水号,唯一 |
| biz_batch_no | 我方系统开票前写库返回批次号 |
| tax_account | 数电账号 |
| invoice_kind | 发票种类,数电普票通常为 `82` |
| status | 本地状态 |
| pt_code | 票通状态码 |
| pt_message | 票通返回信息 |
| invoice_no | 发票号码 |
| all_ele_inv_no | 数电发票号码 |
| invoice_date | 开票日期 |
| total_amount | 合计金额 |
| total_tax | 合计税额 |
| raw_request | 脱敏后的票通业务请求 |
| raw_response | 脱敏后的票通响应 |
| created_at / updated_at | 时间 |
### 7.2 invoice_job_item
保存本次开票关联的我方待开票数据 ID、商品行、金额、税率、数量、税收分类编码。
### 7.3 tax_account
保存公司下可用数电账号、姓名、身份类型、手机号、最近认证状态。密码或密钥类数据必须加密保存,不能明文落库。
### 7.4 audit_log
保存登录、开票、认证、回写、回调、重试等操作审计。
## 8. 票通报文封装
参考现有 Java Demo,后端需要实现:
1. 业务 JSON 使用 UTF-8 序列化。
2. 使用票通提供的 3DES key 加密业务 JSON,得到外层 `content`
3. 外层字段包含:
- `platformCode`
- `signType=RSA`
- `format=JSON`
- `version=1.0`
- `content`
- `timestamp`
- `serialNo`
4. 对外层字段按 key 排序,拼成 `key=value&key=value`,忽略 null,使用 `SHA1WithRSA` 和我方私钥签名。
5. 响应先用票通公钥验签,再用 3DES 解密 `content`
配置项必须放在环境变量或配置中心:
```properties
PIAOTONG_BASE_URL=https://fpkj.vpiaotong.com/tp/openapi
PIAOTONG_PLATFORM_CODE=...
PIAOTONG_PLATFORM_ALIAS=...
PIAOTONG_3DES_KEY=...
PIAOTONG_PRIVATE_KEY=...
PIAOTONG_PUBLIC_KEY=...
```
## 9. 幂等与异常策略
- `invoiceReqSerialNo` 是开票幂等核心。同一张发票重试必须复用,不能重新生成。
- 调用我方“开票前写库”成功后,即使票通调用超时,也不能直接判失败,应进入 `PROCESSING` 并主动查询。
- 票通返回 `3999` 时,不应生成新发票任务,应把原任务置为 `NEED_AUTH`,认证后继续用原流水号重试或查询。
- 我方状态回写失败时,本地记录为 `BIZ_SYNC_FAILED`,后台补偿重试。
- 回调接口必须验签、去重、按状态版本更新,避免乱序回调覆盖成功状态。
## 10. 推荐一期范围
一期先做最小闭环:
1. 登录并获取公司 ID。
2. 查询待开票列表。
3. 选择数据并发起蓝字数电普票开具。
4. 票通短信登录和扫码认证。
5. 开票状态查询和结果回写。
6. 票通回调接收。
7. 开票日志和失败重试。
冲红、二维码开票、发票文件下载、微信/支付宝卡包、企业注册入驻可放到二期。