chore: 添加输入输出文件 + .claude记忆和计划

输入文件:
- 租赁任务考核_2026年{1,2,3}月.xlsx (考核源数据)
- {1,2}月.xlsx (客户盈亏表)
- 车辆里程考核与奖金发放规则(V.1.2).docx

输出文件:
- 里程任务考核_{1,2,3}月核算.xlsx (月度核算结果)
- 里程任务考核_Q1汇总.xlsx (含车辆台账)
- 3月客户盈亏表(待填写).xlsx (模版)

.claude_memory: 项目记忆(规则/偏好/架构/测试车辆)
.claude_plans: 历次计划文件

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-07 14:09:24 +08:00
parent da487c41d4
commit 573f8397a6
32 changed files with 3491 additions and 0 deletions

View File

@@ -0,0 +1,701 @@
# Phase 2 ETC 模块完成计划Controller + API Client + 前端)
## Context
Phase 1 基础设施层FeeType 枚举、DeductionRequest、IDeductionService 重构、ContractMatchService、ReviewConfig 扩展)已完成并编译通过。
Phase 2 ETC 模块后端核心Entity/Mapper/Service/Event/Listener已完成并编译通过。
**本次任务:** 完成 Phase 2 剩余部分:
1. ETC 后端 Controller 层3 个 Controller
2. EtczjApiClientetczj.com HTTP 客户端 + OCR 验证码)
3. 前端 ETC 配置页面 + ETC 账单查询页面
**用户决策:**
- 账户结构:共用一个能源账户(三种费用从同一余额扣款)
- 账单模式:各费用类型独立账单
- 业务流程ETC/电费与氢费完全一致(合同匹配 → 审核 → 扣款)
- 数据源:先设计通用框架,具体 API/Excel 格式后续对接
---
## 架构方案:独立明细表 + 共享账户层
```
modules/
station/ ← 已有:氢费数据源(不动)
etc/ ← 新增ETC 数据源
electricity/ ← 新增:电费数据源
energy/ ← 已有:共享结算层(小幅扩展)
payment/ ← 已有:款项管理(小幅扩展)
```
**核心原则:** 三种费用的明细/账单各自独立(字段差异大),但扣款引擎、账户、流水、充值、款项管理完全共享。
---
## Phase 1: 基础设施层改造energy 域)
### 1.1 新增 `FeeType` 枚举
**文件:** `modules/energy/enums/FeeType.java`(新建)
```java
public enum FeeType {
HYDROGEN(1, "氢费"),
ETC(2, "高速通行费"),
ELECTRICITY(3, "电费");
private final int code;
private final String label;
}
```
### 1.2 重构 `IDeductionService` → 接受通用扣款请求
**改动文件:**
- `modules/energy/service/IDeductionService.java`
- `modules/energy/service/impl/DeductionServiceImpl.java`
**改动内容:**
新增通用 DTO `DeductionRequest`
```java
@Data @Builder
public class DeductionRequest {
private FeeType feeType;
private Long detailId; // 明细ID在各自的明细表中
private Long customerId;
private String customerName;
private Long contractId;
private BigDecimal amount; // 扣款金额
private Integer paymentMode; // 1-预充值 2-月结 3-自行结算
private Integer deductionStatus; // 当前扣款状态(幂等守卫用)
}
```
接口签名变更:
```java
// 原DeductionResult deduct(EnergyHydrogenDetail detail);
// 新:
DeductionResult deduct(DeductionRequest request);
void reDeduct(DeductionRequest request, BigDecimal newAmount);
```
`DeductionServiceImpl` 改动点:
- `deduct()` 方法参数从 `EnergyHydrogenDetail` 改为 `DeductionRequest`,逻辑不变(读 customerId/amount/contractId/paymentMode
- **移除** `detailMapper.updateById(detail)` —— 扣款服务不再直接更新明细表,改为返回 `DeductionResult`,由调用方负责更新各自的明细表扣款状态
- 流水 description 加入 `feeType.getLabel()`(如 "ETC扣款" / "电费扣款"
- `reDeduct()` 同理,不再直接操作 `detailMapper`
**向后兼容:**`EnergyDetailImportListener`(氢费监听器)中构造 `DeductionRequest.fromHydrogenDetail(detail)`,调用新接口后手动更新氢费明细扣款状态。
### 1.3 `energy_account_transaction` 表加 `fee_type` 列
**DDL 迁移脚本:** `db/energy/V2__add_fee_type_to_transaction.sql`
```sql
ALTER TABLE energy_account_transaction
ADD COLUMN fee_type TINYINT NOT NULL DEFAULT 1 COMMENT '费用类型1-氢费 2-ETC 3-电费';
ALTER TABLE energy_account_transaction
ADD INDEX idx_eat_fee_type (fee_type);
```
`EnergyAccountTransaction.java` 新增 `feeType` 字段。
### 1.4 `customer_payment_item` 款项解构扩展
`fee_type` 枚举扩展(已有字段):
- 现有1-租赁费 2-氢费充值 3-押金 4-违约金 5-其他
- 新增6-ETC充值 7-电费充值
`PaymentFlowService` 的流转逻辑需根据新 fee_type 创建对应充值单。
### 1.5 通用合同匹配抽取
现有合同匹配逻辑在 `EnergyDetailImportListener` 中(车牌+时间→合同)。
抽取为共享服务:
**新建:** `modules/energy/service/IContractMatchService.java`
```java
public interface IContractMatchService {
ContractMatchResult matchContract(String plateNumber, Date eventTime);
}
```
返回 `ContractMatchResult`customerId, customerName, contractId, contractCode, costType, paymentMode
ETC/电费/氢费三个监听器共用此服务。
### 1.6 审核配置扩展
`energy_review_config` 表加 `fee_type` 列:
```sql
ALTER TABLE energy_review_config
ADD COLUMN fee_type TINYINT NOT NULL DEFAULT 1 COMMENT '费用类型1-氢费 2-ETC 3-电费';
DROP INDEX uk_erc_level_customer ON energy_review_config;
CREATE UNIQUE INDEX uk_erc_level_customer_fee ON energy_review_config (config_level, customer_id, fee_type);
```
每种费用类型可独立配置"是否需要审核后才扣款"。
---
## Phase 2: ETC 模块(`modules/etc/`
### 2.0 ETC 数据源逆向分析etczj.com - 浙江货车ETC
**平台信息:** https://www.etczj.com Nuxt.js + Element UI 前端nginx/1.27.2 后端)
**认证流程:**
```
1. GET /createCaptcha?verifyCodeSize=null&tmp={random}&secret={guid} → 图形验证码图片
2. POST /member/toLogin?secret={guid}
Body: { acctId: "手机号", password: "Base64编码密码", checkCode: "验证码", msgCode: "", secret: guid }
Response: { state: "1", payload: { randomVal: "xxx", signState: "1" } }
3. 后续请求 Header: Randomval: {randomVal}(存 localStorage
```
**核心业务 API**
| API | 方法 | 说明 | 关键参数 |
|-----|------|------|----------|
| `/vehicleManage/queryVehicleConsumptionDetailPage` | POST | **通行记录分页** | transTimeBegin/End, postingTimeBegin/End, vehicleCode, licenseColor, vcEnStation, vcExStation, currPage, pageSize |
| `/vehicleManage/sumVehicleToll` | POST | **通行费汇总** | 同上(返回 totalPassAmount, totalServiceAmount |
| `/vehicleManage/viewVehiclePage` | POST | **车辆列表** | vehicleCode, currPage, pageSize |
| `/largeFileExport/billDetails` | POST | **通行记录导出** | postingTimeBegin/End, transTimeBegin/End, vehicleCode |
| `/member/flowList` | POST | **账单流水** | - |
| `/member/viewMember` | POST | **用户信息** | - |
**通行记录响应字段映射 → `etc_toll_record` 表:**
| etczj 字段 | 含义 | → 表字段 |
|------------|------|----------|
| vehicleCode | 车牌号码 | plate_number |
| licenseColor | 车牌颜色(0-6) | license_color |
| cardType | 卡类型 | card_type |
| cardCode | 通行卡号 | etc_card_number |
| postingTimeStr | 记账日期 | posting_time |
| transDate + transTime | 通行日期+时间 | trans_time |
| enStation | 入口站 | entry_station_name |
| exStation | 出口站 | exit_station_name |
| dToll | 通行费用(元) | toll_amount |
| serviceFee | 服务费(元) | service_fee |
| appealStatus | 申诉状态 | appeal_status |
**同步策略设计要点:**
- 登录需图形验证码 → 需 OCR 或人工介入。建议:首次登录获取 session 后缓存 `randomVal`session 过期时告警人工重新登录
- 增量同步:按 `postingTimeBegin/End`(记账日期)拉取,每次从 `lastSyncTime` 开始
- 去重键:`vehicleCode + transDate + transTime + enStation + exStation`(平台无唯一订单号)
- 分页:`currPage` + `pageSize`,默认 pageSize=5建议调大到 100
- 日期限制:时间范围最多 31 天
### 2.1 数据库表
**`etc_toll_record`ETC通行记录表**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| record_code | VARCHAR(32) | 记录编码(系统生成,唯一) |
| etc_card_number | VARCHAR(64) | 通行卡号(cardCode) |
| card_type | VARCHAR(32) | 卡类型(cardType) |
| plate_number | VARCHAR(16) | 车牌号 |
| license_color | TINYINT | 车牌颜色(0-6) |
| vehicle_id | BIGINT | 车辆ID匹配后 |
| contract_id | BIGINT | 合同ID匹配后 |
| customer_id | BIGINT | 客户ID匹配后 |
| customer_name | VARCHAR(128) | 客户名称 |
| entry_station_name | VARCHAR(128) | 入口站 |
| exit_station_name | VARCHAR(128) | 出口站 |
| trans_time | DATETIME | 通行时间transDate+transTime |
| posting_time | DATETIME | 记账日期(postingTimeStr) |
| toll_amount | DECIMAL(10,2) | 通行费(元)(dToll) |
| service_fee | DECIMAL(10,2) | 服务费(元)(serviceFee) |
| total_amount | DECIMAL(10,2) | 合计金额(toll+service) |
| appeal_status | TINYINT | 申诉状态 |
| cost_type | TINYINT | 费用承担方 |
| payment_mode | TINYINT | 付款模式 |
| contract_matched | TINYINT | 合同匹配状态 |
| review_status | TINYINT | 审核状态 |
| deduction_status | TINYINT | 扣款状态 |
| bill_id | BIGINT | 关联账单ID |
| source_type | TINYINT | 来源1-API同步 |
| source_dedup_key | VARCHAR(128) | 去重键(车牌+通行时间+入口+出口) |
| is_oneos_vehicle | TINYINT | 是否OneOS车辆 |
| + BaseEntity 审计字段 | | |
**`etc_sync_config`** 和 **`etc_sync_log`**:复用 station 的 sync_config/sync_log 结构,`provider_type` 固定为 `ETCZJ`。额外字段:`acct_id`(手机号)、`password`(加密存储)。
**`energy_etc_bill`ETC账单表**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| bill_code | VARCHAR(32) | 账单编码 |
| customer_id | BIGINT | 客户ID |
| bill_period_start/end | DATE | 账单周期 |
| total_toll_count | INT | 通行笔数 |
| total_toll_amount | DECIMAL(12,2) | 通行总金额 |
| total_service_fee | DECIMAL(12,2) | 服务费总额 |
| receivable_amount | DECIMAL(12,2) | 应收金额 |
| actual_amount | DECIMAL(12,2) | 实收金额 |
| adjustment_amount | DECIMAL(12,2) | 调整额 |
| payment_status | TINYINT | 支付状态 |
| review_status | TINYINT | 审核状态 |
| + 审核/财务/审计字段 | | |
### 2.2 代码结构
```
modules/etc/
entity/
record/po/EtcTollRecord.java
record/vo/EtcTollRecordVO.java
record/query/EtcTollRecordQuery.java
record/req/UpdateEtcRecordReq.java
sync/po/EtcSyncConfig.java
sync/po/EtcSyncLog.java
bill/po/EnergyEtcBill.java
bill/vo/EtcBillVO.java
bill/req/EtcBillGenerateReq.java
mapper/
EtcTollRecordMapper.java
EtcSyncConfigMapper.java
EtcSyncLogMapper.java
EnergyEtcBillMapper.java
service/
IEtcTollRecordService.java
IEtcSyncConfigService.java
IEtcBillService.java
impl/EtcTollRecordServiceImpl.java
impl/EtcSyncConfigServiceImpl.java
impl/EtcBillServiceImpl.java
sync/EtcSyncStrategy.java ← 策略接口
sync/EtczjSyncStrategy.java ← etczj.com 具体实现(登录+分页拉取+字段映射)
sync/EtczjApiClient.java ← HTTP 客户端(登录/session管理/通行记录查询)
sync/EtczjApiResponse.java ← 响应 DTO
ingest/EtcIngestTemplate.java ← 模板方法extract→validate→dedup→match→persist→event
controller/
EtcTollRecordController.java
EtcSyncConfigController.java
EtcBillController.java
event/
EtcRecordImportedEvent.java
listener/
EtcDetailImportListener.java ← 监听 EtcRecordImportedEvent执行合同匹配+扣款
validation/
EtcValidationChain.java
```
### 2.3 数据流
```
EtczjSyncStrategy 定时/手动同步:
1. EtczjApiClient.login(acctId, password) → 获取 randomVal session
2. 按日期范围分页调用 /vehicleManage/queryVehicleConsumptionDetailPage
- 从 lastSyncTime 开始每次最多31天
- pageSize=100循环翻页直到无更多数据
3. 字段映射etczj 响应 → EtcTollRecordDTO
4. → EtcIngestTemplate.ingest()
→ extract → validate → dedup(source_dedup_key) → matchVehicle → persist(etc_toll_record)
→ publishEvent(EtcRecordImportedEvent)
5. → EtcDetailImportListener
→ 筛选 is_oneos_vehicle=1
→ contractMatchService.matchContract(plateNumber, transTime)
→ 审核配置检查FeeType.ETC
→ deductionService.deduct(DeductionRequest)
→ 更新 etc_toll_record.deduction_status
Session 管理:
- randomVal 缓存在 Rediskey: `etc:session:{configId}`,过期后自动重新登录
- 图形验证码处理OCR 自动识别Tesseract/百度OCR
- GET /createCaptcha?secret={guid} → 图片
- OCR 识别 → 4位数字
- 识别失败重试刷新验证码重试最多3次
- 3次均失败 → 告警通知人工介入
- 去重策略vehicleCode + transTime + enStation + exStation 组合键
```
### 2.4 API 接口(约 20 个)
| 模块 | 接口数 |
|------|--------|
| ETC 通行记录 CRUD + 审核 + 导出 | 8 |
| ETC 同步配置 + 手动触发 + 日志 | 7 |
| ETC 账单生成 + 审核 + 调整项 | 8 |
---
## Phase 3: 电费模块(`modules/electricity/`
### 3.1 数据库表
**`electricity_charge_record`(充电记录表):**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| record_code | VARCHAR(32) | 记录编码 |
| charging_station_id | BIGINT | 充电站ID关联已有 charging_station 表) |
| charging_station_name | VARCHAR(128) | 充电站名称 |
| plate_number | VARCHAR(16) | 车牌号 |
| vehicle_id / contract_id / customer_id | BIGINT | 匹配后填充 |
| charging_start_time | DATETIME | 充电开始 |
| charging_end_time | DATETIME | 充电结束 |
| charging_duration | INT | 时长(分钟) |
| kwh | DECIMAL(10,4) | 充电量(度) |
| unit_price | DECIMAL(10,4) | 单价(元/度) |
| charge_amount | DECIMAL(10,2) | 电费(元) |
| service_fee | DECIMAL(10,2) | 服务费(元) |
| total_amount | DECIMAL(10,2) | 合计金额 |
| cost_type / payment_mode / contract_matched / review_status / deduction_status / bill_id | | 与氢费/ETC 一致 |
| source_type | TINYINT | 来源1-Excel导入 2-RPA导入 |
| source_row_key | VARCHAR(64) | 源Excel行唯一键去重 |
| is_oneos_vehicle | TINYINT | 是否OneOS车辆 |
| + BaseEntity 审计字段 | | |
**`energy_electricity_bill`(电费账单表):** 结构类似 ETC 账单,特有字段:`total_kwh`, `total_charge_amount`, `total_service_fee`
### 3.2 代码结构
与 ETC 模块镜像,核心差异:
- **无 API 同步**(暂时),数据入口为 Excel 导入(手动上传 / RPA 落盘后系统扫描)
- `ElectricityExcelIngestStrategy` 实现 `ElectricityIngestTemplate`
- 关联已有 `chargingstation/` 模块的充电站主数据
- 校验链包含:电量/金额一致性校验、充电站匹配校验
### 3.3 数据流
```
Excel 手动导入 / RPA 定期落盘
→ ElectricityIngestTemplate.ingest()
→ extract(解析Excel) → validate → dedup(source_row_key) → matchVehicle → persist
→ publishEvent(ElectricityRecordImportedEvent)
→ ElectricityDetailImportListener
→ 合同匹配 → 审核配置检查FeeType.ELECTRICITY→ 扣款 → 更新状态
```
### 3.4 API 接口(约 15 个)
| 模块 | 接口数 |
|------|--------|
| 充电记录 CRUD + 审核 + 导入/导出 | 8 |
| 电费账单生成 + 审核 + 调整项 | 7 |
---
## Phase 4: 账户层统一视图
### 4.1 流水查询增加 fee_type 过滤
**改动文件:**
- `TransactionQuery.java` — 新增 `feeType` 可选参数
- `EnergyAccountController.java` — 流水分页接口支持按费用类型筛选
### 4.2 账户汇总接口
**新增接口:** `GET /energy/account/{id}/fee-summary`
返回各费用类型的扣款/充值汇总:
```json
{
"hydrogen": { "totalDeducted": 50000, "count": 320 },
"etc": { "totalDeducted": 12000, "count": 580 },
"electricity": { "totalDeducted": 8000, "count": 150 }
}
```
---
## Phase 5: 前端菜单与页面
### 5.1 菜单结构
```
能源管理
├── 加氢明细(已有)
├── ETC 通行记录(新增)
├── 充电记录(新增)
├── 能源账户(已有,增加 fee_type 筛选)
├── 充值单管理(已有)
├── 氢费账单(已有)
├── ETC 账单(新增)
├── 电费账单(新增)
└── 审核配置(已有,增加 fee_type 维度)
加氢站管理(已有,不动)
ETC 管理(新增)
├── ETC 同步配置
└── ETC 同步日志
款项管理(已有)
└── fee_type 解构类型新增 ETC充值/电费充值
```
---
## 实施顺序
| 步骤 | 内容 | 依赖 |
|------|------|------|
| **1** | Phase 1 基础设施FeeType枚举、DeductionRequest、IDeductionService重构、DB迁移、ContractMatchService抽取、ReviewConfig扩展 | 无 |
| **2** | Phase 1 向后兼容(更新氢费监听器/服务适配新接口) | Step 1 |
| **3** | Phase 2 ETC 模块实体→Mapper→Service→Controller→事件→监听器→测试 | Step 2 |
| **4** | Phase 3 电费模块(同上) | Step 2可与 Step 3 并行 |
| **5** | Phase 4 账户层统一视图 | Step 3 & 4 |
| **6** | Phase 5 前端页面 | Step 3 & 4 |
| **7** | Payment 域扩展fee_type 解构+流转) | Step 1 |
---
## 关键改动文件清单
### 修改已有文件
| 文件 | 改动 |
|------|------|
| `energy/service/IDeductionService.java` | 接口签名改为 `DeductionRequest` |
| `energy/service/impl/DeductionServiceImpl.java` | 实现适配,移除 detailMapper 直接操作 |
| `energy/entity/account/po/EnergyAccountTransaction.java` | 新增 `feeType` 字段 |
| `energy/listener/EnergyDetailImportListener.java` | 适配新 DeductionRequest抽取合同匹配逻辑 |
| `energy/entity/review/po/EnergyReviewConfig.java` | 新增 `feeType` 字段 |
| `energy/service/impl/ReviewConfigServiceImpl.java` | 查询时加 feeType 条件 |
| `energy/controller/EnergyAccountController.java` | 流水查询加 feeType 过滤 |
| `energy/entity/account/query/TransactionQuery.java` | 新增 feeType 参数 |
| `payment/entity/po/CustomerPaymentItem.java` | fee_type 枚举扩展文档 |
| `payment/service/impl/PaymentFlowServiceImpl.java` | 支持 ETC/电费充值流转 |
### 新建文件(按 Phase
- Phase 1: ~5 文件FeeType, DeductionRequest, ContractMatchService 接口+实现, V2迁移脚本
- Phase 2: ~20 文件ETC 全套 entity/mapper/service/controller/event/listener+ 3 张表 DDL
- Phase 3: ~18 文件(电费全套)+ 2 张表 DDL
- Phase 4: ~2 文件改动
---
## 验证方案
### 单元测试
- `DeductionServiceTest` —— 验证新接口支持三种 FeeType
- `EtcIngestTemplateTest` —— ETC 数据导入全流程
- `ElectricityIngestTemplateTest` —— 电费 Excel 导入全流程
- `ContractMatchServiceTest` —— 合同匹配逻辑
### 集成测试
- `EtcModuleIntegrationTest` —— ETC 同步 → 导入 → 合同匹配 → 审核 → 扣款 → 账单生成
- `ElectricityModuleIntegrationTest` —— Excel 导入 → 合同匹配 → 审核 → 扣款 → 账单生成
- `MultiFeeBillingIntegrationTest` —— 三种费用共享账户扣款 → 流水 fee_type 正确 → 各自独立出账单
### 回归验证
- 现有氢费全链路测试通过(`EnergyModuleIntegrationTest`
- 现有款项管理测试通过(`PaymentModuleIntegrationTest`
---
## Phase 2 剩余实施计划(本次任务)
### 已完成
- [x] Phase 1 基础设施层FeeType/DeductionRequest/IDeductionService/ContractMatchService/ReviewConfig
- [x] Phase 2 DDL`db/etc/V1__create_etc_tables.sql` - 4 张表)
- [x] Phase 2 Entity POEtcTollRecord/EtcSyncConfig/EtcSyncLog/EnergyEtcBill
- [x] Phase 2 DTOEtcTollRecordVO/EtcTollRecordQuery/EtcBillVO/EtcBillGenerateReq
- [x] Phase 2 Mapper4 个)
- [x] Phase 2 Service 接口 + 实现IEtcTollRecordService/IEtcSyncConfigService/IEtcBillService
- [x] Phase 2 EventEtcRecordImportedEvent
- [x] Phase 2 ListenerEtcDetailImportListener
- [x] 编译通过
### Step 1: ETC 后端 Controller 层
**3 个 Controller沿用 `EnergyBillController` 的精确模式:**
#### 1.1 `EtcTollRecordController.java`
**路径:** `modules/etc/controller/EtcTollRecordController.java`
**前缀:** `@RequestMapping("/etc/record")`
**Tag** `@Tag(name = "ETC通行记录管理")`
| 方法 | 路径 | 说明 | 调用 |
|------|------|------|------|
| GET | /page | 分页查询 | service.pageList(query) |
| GET | /detail/{id} | 详情 | service.getDetail(id) |
| PUT | /review | 单条审核 | service.review(id, approved, remark) |
| PUT | /batch-review | 批量审核 | service.batchReview(ids, approved) |
| PUT | /manual-match | 手动匹配合同 | service.manualMatch(detailId, contractId) |
| POST | /export | 导出 | TODO |
| GET | /statistics | 统计数据 | service.getStatistics() |
#### 1.2 `EtcSyncConfigController.java`
**路径:** `modules/etc/controller/EtcSyncConfigController.java`
**前缀:** `@RequestMapping("/etc/sync-config")`
**Tag** `@Tag(name = "ETC同步配置")`
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /page | 分页查询 |
| GET | /detail/{id} | 详情 |
| POST | / | 新增 |
| PUT | / | 编辑 |
| DELETE | /{id} | 删除 |
| PUT | /toggle/{id} | 启用/停用 |
| POST | /trigger/{id} | 手动触发同步 |
| GET | /log/{configId} | 同步日志 |
#### 1.3 `EtcBillController.java`
**路径:** `modules/etc/controller/EtcBillController.java`
**前缀:** `@RequestMapping("/etc/bill")`
**Tag** `@Tag(name = "ETC账单管理")`
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /page | 分页查询 |
| GET | /detail/{id} | 详情 |
| POST | /generate/preview | 生成预览 |
| POST | /generate | 确认生成 |
| PUT | /review | 审核 |
| PUT | /submit-finance/{id} | 提交财务 |
| DELETE | /{id} | 删除 |
| POST | /export | 导出 |
| GET | /statistics | 统计 |
| GET | /record/page/{billId} | 关联通行记录分页 |
**参考文件:**
- `ln-asset-management/src/main/java/com/ln/asset/modules/energy/controller/EnergyBillController.java`
- `ln-asset-management/src/main/java/com/ln/asset/modules/station/controller/SyncConfigController.java`
### Step 2: EtczjApiClientetczj.com HTTP 客户端)
**新建文件:**
- `modules/etc/service/sync/EtczjApiClient.java` — HTTP 客户端
- `modules/etc/service/sync/EtczjApiResponse.java` — 响应 DTO
- `modules/etc/service/sync/EtczjTollRecordDTO.java` — 通行记录 DTO
- `modules/etc/service/sync/EtczjSyncStrategy.java` — 同步策略实现
**EtczjApiClient 核心方法:**
```java
@Slf4j
@Component
public class EtczjApiClient {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
// 登录(获取 randomVal session
public String login(String baseUrl, String acctId, String password) {
// 1. GET /createCaptcha?secret={guid} → 验证码图片
// 2. OCR 识别验证码Tesseract
// 3. POST /member/toLogin?secret={guid}
// Body: { acctId, password: Base64(password), checkCode, secret }
// 4. 返回 randomVal
}
// 查询通行记录
public EtczjApiResponse<List<EtczjTollRecordDTO>> queryTollRecords(
String baseUrl, String randomVal,
String transTimeBegin, String transTimeEnd,
int currPage, int pageSize) {
// POST /vehicleManage/queryVehicleConsumptionDetailPage
// Header: Randomval: {randomVal}
}
// 查询通行费汇总
public EtczjApiResponse<Map<String, Object>> sumToll(
String baseUrl, String randomVal,
String transTimeBegin, String transTimeEnd) {
// POST /vehicleManage/sumVehicleToll
}
}
```
**参考文件:**
- `ln-asset-management/src/main/java/com/ln/asset/modules/station/feign/HecriApiClient.java`RestTemplate + ObjectMapper 模式)
### Step 3: 前端 ETC 页面
**技术栈:** Vben Admin (Vue 3 + TypeScript + Ant Design Vue + VxeTable)
**模式参考:**
- 路由:`playground/src/router/routes/modules/system.ts`
- API`playground/src/api/system/role.ts`
- 列表页:`playground/src/views/system/role/list.vue`
- 列定义:`playground/src/views/system/role/data.ts`
#### 3.1 API 层
**新建文件:** `playground/src/api/etc/index.ts`
```typescript
export namespace EtcApi {
export interface EtcSyncConfig { id: string; providerType: string; acctId: string; syncEnabled: 0|1; syncCron: string; lastSyncTime: string; lastSyncStatus: 0|1; }
export interface EtcTollRecord { id: string; recordCode: string; plateNumber: string; entryStationName: string; exitStationName: string; transTime: string; tollAmount: number; serviceFee: number; totalAmount: number; reviewStatus: number; deductionStatus: number; }
export interface EtcBill { id: string; billCode: string; customerName: string; billPeriodStart: string; billPeriodEnd: string; totalTollCount: number; totalTollAmount: number; receivableAmount: number; actualAmount: number; reviewStatus: number; paymentStatus: number; }
}
// ETC 同步配置 API
async function getEtcSyncConfigPage(params) { return requestClient.get('/etc/sync-config/page', { params }); }
async function addEtcSyncConfig(data) { return requestClient.post('/etc/sync-config', data); }
async function updateEtcSyncConfig(data) { return requestClient.put('/etc/sync-config', data); }
async function deleteEtcSyncConfig(id) { return requestClient.delete(`/etc/sync-config/${id}`); }
async function toggleEtcSync(id) { return requestClient.put(`/etc/sync-config/toggle/${id}`); }
async function triggerEtcSync(id) { return requestClient.post(`/etc/sync-config/trigger/${id}`); }
async function getEtcSyncLog(configId, params) { return requestClient.get(`/etc/sync-config/log/${configId}`, { params }); }
// ETC 账单 API
async function getEtcBillPage(params) { return requestClient.get('/etc/bill/page', { params }); }
async function getEtcBillDetail(id) { return requestClient.get(`/etc/bill/detail/${id}`); }
async function reviewEtcBill(data) { return requestClient.put('/etc/bill/review', data); }
// ... 其他
```
#### 3.2 ETC 同步配置页面
**新建文件:**
- `playground/src/views/etc/sync-config/list.vue` — 配置列表useVbenVxeGrid
- `playground/src/views/etc/sync-config/data.ts` — 列定义 + 表单 schema
- `playground/src/views/etc/sync-config/modules/form.vue` — 新增/编辑表单useVbenDrawer
**页面功能:**
- 表格列供应商类型、登录账号、API地址、同步频率、上次同步时间/状态、启用状态
- 操作:编辑、删除、启用/停用切换、手动触发同步
- 新增/编辑抽屉账号、密码、API地址、同步 cron 表达式
#### 3.3 ETC 账单查询页面
**新建文件:**
- `playground/src/views/etc/bill/list.vue` — 账单列表
- `playground/src/views/etc/bill/data.ts` — 列定义 + 搜索表单
- `playground/src/views/etc/bill/modules/detail.vue` — 账单详情抽屉
**页面功能:**
- 搜索条件:客户名称、账单周期、审核状态、支付状态
- 表格列:账单编码、客户名称、账期、通行笔数、通行费总额、服务费总额、应收、实收、审核状态、支付状态
- 操作:查看详情、审核、提交财务、删除
- 详情抽屉:账单信息 + 关联通行记录分页列表
#### 3.4 路由配置
**新建文件:** `playground/src/router/routes/modules/etc.ts`
```typescript
const routes: RouteRecordRaw[] = [{
meta: { icon: 'mdi:highway', order: 30, title: 'ETC管理' },
name: 'Etc',
path: '/etc',
children: [
{ path: '/etc/sync-config', name: 'EtcSyncConfig', meta: { icon: 'mdi:sync', title: 'ETC同步配置' }, component: () => import('#/views/etc/sync-config/list.vue') },
{ path: '/etc/bill', name: 'EtcBill', meta: { icon: 'mdi:file-document-outline', title: 'ETC账单' }, component: () => import('#/views/etc/bill/list.vue') },
],
}];
```
### 实施顺序
| 步骤 | 内容 | 文件数 |
|------|------|--------|
| 1 | 3 个 ETC Controller | 3 |
| 2 | EtczjApiClient + DTO + SyncStrategy | 4 |
| 3 | 前端 API 层 | 1 |
| 4 | 前端 ETC 同步配置页面 | 3 |
| 5 | 前端 ETC 账单查询页面 | 3 |
| 6 | 前端路由配置 | 1 |
| **合计** | | **15 个文件** |
### 验证方案
- 后端:`mvn compile` 编译通过
- 前端:`cd ln-one-os-web/playground && pnpm dev` 启动无报错
- ETC 同步配置页面:可访问 /etc/sync-config表格加载正常
- ETC 账单页面:可访问 /etc/bill表格加载正常
- Swagger`/swagger-ui.html` 可看到 ETC 相关 3 组 API