# zcloud-gbs-primeport 项目梳理文档

## 一、项目概览

**系统名称**：港口口门门禁管理系统（primeport），应用名 `zcloud-gbs-primeport`，网关前缀 `/primeport`

**技术栈**：
- Spring Boot 2.6.6 + Spring Cloud Alibaba（Nacos 配置中心/服务发现）
- COLA 4.0 分层架构 + MyBatis-Plus + Druid + MySQL/PostgreSQL 双数据源
- Redis、RocketMQ、XXL-JOB、Flyway、Swagger、Hutool、fastjson
- 自研 jjb-saas 框架（网关/认证/系统客户端）
- 大华 ICC SDK（`com.dahuatech.icc:java-sdk-oauth:1.0.13.15`）

**分层架构**（6 个 Maven 模块）：

```
start(启动) ──> web-adapter(控制器) ──> web-app(业务编排) ──> web-client(API契约+DTO) ──> web-domain(领域模型)
                      │                      │                      │
                      └──────────────────────┴──> web-infrastructure(基础设施+第三方对接) ──> web-domain
```

调用链路示例：`Controller → ServiceImpl → Exe(命令执行器) → Gateway(领域接口) → GatewayImpl(基础设施) → Repository → Mapper → DB`

---

## 二、业务模块总览

系统按 **三类角色** 组织功能：监管端（JGD）、分公司端（FGS）、相关方/驻港单位端（XGF/ZGDW），通过 `MenuEnum` 控制约 60 项菜单权限。

### 1. 一级口门基础管理

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| 口门信息 | MkmjController | 口门名称、级别(1一级/2二级)、类型(人行/车行/综合)、所属港区、经纬度 |
| 门禁通道 | MkmjPassageController | 通道类型、所属口门、状态 |
| 闸机管理 | MkmjGateController | 闸机标识、型号、所属通道 |
| 审批人 | MkmjApprovalUserController | 审批人企业/部门/岗位，人员/车辆/临时三类审核权限 |
| 摄像头 | VideoController | 绑定口门或闸机，移动/平台类型 |
| 刷卡记录 | MkmjSwipeCardRecordController | 接收大华推送的刷卡记录(`/dockSave`)，分页查询 |

### 2. 封闭区域管理（二级口门）

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| 封闭区域 | ClosedAreaController | 树级结构(parentId)、按港区/管辖单位查询、统计 |
| 人员申请 | ClosedAreaPersonApplyController | 申请/审批/授权(含二级门禁)、随行人员管理 |
| 车辆申请 | ClosedAreaCarApplyController | 新增/分页/审批(auditFlag=2通过/3驳回) |
| 临时申请 | ClosedTmpApplyController | 统一入口，按 TmpApplyEnum 路由到4种申请类型 |

### 3. 车辆管理

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| 车辆申请 | VehicleApplyController | 9种车辆归属、长期录入、车牌校验、相关方授权 |
| 车辆审批 | VehicleAuditController | 待审批/审批记录/审批操作 |
| 车辆黑名单 | VehicleBlackController | 新增/分页/删除 |
| 车辆违规 | VehicleViolationsController | 违规记录，超阈值自动拉黑 |
| 车辆门禁信息 | VehicleMessageController | 审批通过的固化信息，用于门禁对接 |
| 车辆对接日志 | VehicleDockLogController | 一级/二级口门对接日志 |

### 4. 人员管理

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| 人员申请 | PersonApplyController | 相关方人员申请/审批/授权、临时访客H5申请、分公司人员管理 |
| 人员门禁信息 | PersonMessageController | 审批通过的人员信息，用于门禁对接 |

### 5. 电动车数据接入

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| 电动车基础信息 | EVehicleBasicInfoController | 车辆备案(长期/一次性/内部/临时)、EDW数据溯源 |
| 到离港信息 | EVehicleArrivalDepartureInfoController | 到达/离开闸口与时间记录 |

### 6. App 统计

| 模块 | Controller | 核心职责 |
|------|-----------|---------|
| App角标 | AppCountController | App端统计角标数据 |

---

## 三、核心业务流程

### 流程 A：口门车辆申请全链路

```
申请提交 → VehicleApplyAddExe
  ├─ 按归属类型回填(股份个人车直接 mkmjPermission=1)
  ├─ 初始状态: auditFlag=1(待审), mkmjPermission=2(无权限)
  ├─ 插入 VehicleAuditE 审批记录(changeAfter快照)
  └─ 推待办给审批人(RocketMQ)
    ↓
修改 → VehicleApplyUpdateExe (锁定车型/车牌/归属不可改, 重置auditFlag=1, 记录变更对比)
    ↓
审核 → VehicleAuditUpdateExe.audit
  ├─ 通过(2): mkmjPermission=1 → CarDockUtil同步一级车行
  │    ├─ 临时/相关方车 → tmpVehicleSave(按港区portId)
  │    └─ 内部车 → executeInternalVehicle(先删后存)
  └─ 驳回(3): mkmjPermission=2
```

### 流程 B：封闭区域车辆通行全链路

```
申请 → ClosedAreaCarApplyAddExe (回填用户信息, auditFlag=1, 推待办)
  ↓
审批 → ClosedAreaCarApplyUpdateExe.auditCarApply
  └─ 通过时 → TwoCarUtil.levelTwoCarCompanyByApplyId
       ├─ 二级海康: HKDeviceUtil.saveOrUpdCar + vehicleTopUp(当日有效)
       └─ 一级车行: 先查vehicle_apply是否已有有效权限，无则新增(不覆盖)
```

### 流程 C：封闭区域人员授权链路（最复杂）

```
申请 → ClosedAreaPersonApplyAddExe (auditFlag=1, 推待办)
  ↓
审核 → ClosedAreaPersonApplyUpdateExe.auditPersonApply
  └─ 通过时 → TwoPersonUtil.levelTwoPeopleCompanyByApplyId (海康+大华同步)
  ↓
授权 → ClosedAreaPersonApplyUpdateExe.authPerson
  ├─ 删旧auth+area，重建auth+area(区域关联)
  └─ mkmjPermission=1时 → OnePersonUtil:
       ├─ 查大华是否已有该身份证用户
       ├─ 无 → dockLongTermUserByUserCardAndFaceImg (账号hbqa+身份证, 当日有效)
       └─ 有 → 保留不覆盖
```

### 流程 D：车辆违规自动拉黑链路

```
违规录入 → VehicleViolationsAddExe
  ├─ 查历史违规数，若 (历史+1) >= 配置上限 → 自动拉黑
  │    ├─ vehicleBlackGateway.saveIfAbsent
  │    └─ vehicleApplyGateway.updateEvehicleBlockedByLicenceNo(...,1)
  └─ 新增违规记录(处理状态=2未处理)
```

### 流程 E：相关方人员申请链路

```
PersonApplyAddExe.xgfPersonSave
  ├─ personBelongType∈{1,2} → auditFlag=2(自动通过) + mkmjPermission=1
  ├─ 其他 → auditFlag=1(待审) + mkmjPermission=2
  ├─ 推待办
  └─ 子表PersonApplyE批量保存(绑定主表XgfApplyPersonE)
```

---

## 四、第三方接口对接

### 对接系统总览

| # | 第三方系统 | 对接方式 | 用途 | 级别 |
|---|----------|---------|------|-----|
| 1 | 大华 ICC 门禁 | SDK + HTTP(RSA加密) | 一级人员门禁：用户创建/有效期/冻结 | 一级 |
| 2 | 海康威视门禁 | SDK(静态工具类) | 二级口门人员/车辆授权 | 二级 |
| 3 | 秦皇岛港车行系统 | Hutool HTTP POST | 一级口门车辆(内部/临时/黑名单) | 一级 |
| 4 | zcloud 用户中心 | Dubbo RPC | 用户信息、人脸图片base64 | 平台内 |
| 5 | 待办列表 MQ | RocketMQ | 审批待办推送/完成 | 平台内 |
| 6 | 大华刷卡推送 | HTTP 被动接收 | 刷卡记录入库 | 一级 |

### 1. 大华 ICC 对接（一级人员门禁）

**配置类**：
- `web-domain/.../config/DaHuaOauthProperties.java`（`@ConfigurationProperties(prefix = "dahua.oauth")`：host / clientId / clientSecret / username / password / http / port）
- `web-domain/.../config/DaHuaSdkConfig.java`（注入 `OauthConfigUserPwdInfo` Bean）
- `start/src/main/resources/sdk.yml` 中 `dahua.oauth` 配置

**对接工具类**：
- `web-infrastructure/.../plan/mjDevice/OnePersonUtil.java`
- `web-infrastructure/.../plan/mjDevice/TwoPersonUtil.java`
- `web-infrastructure/.../plan/mjDevice/OauthUtil.java`

**核心方法**：
- `dockLongTermUserByUserCardAndFaceImg` - 按身份证查用户，不存在则新增(含人脸图片、有效期)
- `expirationDateRefreshedUserCard` - 刷新用户门禁有效期
- `freezeByUserCard` - 用户冻结(2)/解冻(1)
- `getMkmjUserInfoByUserCard` - 按身份证批量查询用户

**OauthUtil 调用流程**：
1. `getPublicKey()` - GET 公钥接口（忽略SSL证书的 RestTemplate）
2. `getToken()` - POST 获取 Token（密码通过 RSA 加密）
3. `getGenerateId() / getGenerateIdList(idCount)` - 获取全局用户ID（单个/批量）
4. `getDepartmentId() / getDepartmentIdList(idCount)` - 获取部门ID
5. `imgInspect(map)` - 图片质量检测，支持文件/URL/base64 三种格式

**配置项**（Nacos 下发，`icc.*`）：
- `icc.clientId` / `icc.clientSecret` / `icc.username` / `icc.password`
- `icc.url` - ICC 基础地址
- `icc.publicKeyUrl` - 公钥获取地址
- `icc.request.getGenerateIdUrl` / `icc.request.getGenerateIdListUrl` - 全局用户ID接口
- `icc.request.imgInspectUrl` - 图片质检接口
- `icc.request.getDepartmentIdUrl` - 部门ID接口

### 2. 海康威视对接（二级口门）

**对接工具类**：
- `web-infrastructure/.../plan/mjDevice/TwoCarUtil.java`
- `web-infrastructure/.../plan/mjDevice/TwoPersonUtil.java`

**核心方法**：
- 车辆：
  - `HKDeviceUtil.saveOrUpdCar(licenceNo, 0)` - 新增/更新车辆
  - `HKDeviceUtil.vehicleTopUp(licenceNo, 0, hkIndexCode, khParkSysCode, startDate, endDate)` - 车辆授权充值
- 人员：
  - `HKDeviceUtil.getUserListByPhone(phoneList)` - 按手机号查人（限20人/次）
  - `HKDeviceUtil.getUserListByUserCard(userCard)` - 按身份证查人
  - `HKDeviceUtil.saveUser(name, cardNo, faceBase64, phone)` - 新增人员
  - `HKDeviceUtil.authPersonArea(personIds, resourceInfos, startTime, endTime)` - 授权门禁范围（限1000人/次）

**业务要点**：
- 同步前通过 Dubbo `listBase64ByUrl` 把人脸URL转base64
- 随行人员一并同步授权
- 手机号查不到再用身份证查（处理手机号变更场景）

### 3. 秦皇岛港车行系统对接（一级口门车辆）

**对接工具类**：
- `web-infrastructure/.../plan/mjDevice/OneLevelCarUtil.java`
- `web-infrastructure/.../plan/mjDevice/CarDockUtil.java`

**配置**：
- `${kmmj.dockflag}` - 对接开关（1=启用）
- `${oneLevelCar.prefix}` - 服务前缀

**接口列表**（HTTP POST，前缀 `HD_QHD_IGATE_1.0`）：

| 方法 | URL后缀 | 用途 |
|------|--------|------|
| internalVehicleSave | `/webresources/login/CTruck/qaSaveInternal` | 内部车辆保存 |
| internalVehicleRemove | `/webresources/login/CTruck/qaDeleteInternal` | 内部车辆删除 |
| tmpVehicleSave | `/webresources/login/CTruck/qaSaveTemp` | 临时车辆保存 |
| tmpVehicleRemove | `/webresources/login/CTruck/qaDeleTemp` | 临时车辆删除 |
| blackVehicleSave | `/webresources/login/CTruck/qaSaveBlack` | 黑名单新增 |
| blackVehicleRemove | `/webresources/login/CTruck/qaDeleteBlack` | 黑名单删除 |

**CarDockUtil 协调器**：
- `execute(licenceNo)` - 临时/相关方车走 `tmpVehicleSave`
- `executeInternalVehicle(applyE)` - 内部车先 `internalVehicleRemove` 再 `internalVehicleSave`（覆盖更新）
- `executeBlackSave / executeBlackRemove` - 黑名单新增/删除

每次对接写入 `VehicleDockLogDO` 日志表（level=1一级、dockFlag=2已对接、dockStatus=返回code、reasonsRefusal=返回msg）。

### 4. zcloud 用户中心（Dubbo）

通过 `@DubboReference ZcloudUserFacade`：

| 调用位置 | 方法 | 用途 |
|---------|------|------|
| TwoPersonUtil | `listBase64ByUrl(zcloudUserImgQry)` | 根据人脸URL列表批量获取 base64 |
| ClosedAreaCarApplyAddExe | Dubbo 引用 | 闭区车辆申请新增时回填用户信息 |
| ClosedAreaPersonApplyAddExe | Dubbo 引用 | 闭区人员申请新增时回填用户信息 |
| ClosedAreaPersonApplyUpdateExe.authPerson | `getInfoByUserId(userId)` | 授权时获取用户身份证/姓名/人脸URL |

### 5. 待办列表 MQ（RocketMQ）

依赖：`spring-cloud-starter-stream-rocketmq`，工具类 `TodoListEventPusherUtil`

| 触发时机 | 事件类型 | 场景 |
|---------|---------|------|
| 申请提交 | TodoListAddEvent | 车辆/闭区车辆/闭区人员申请 |
| 审批完成 | TodoListCompleteEvent | 车辆/闭区车辆/闭区人员审批 |

### 6. 大华刷卡记录推送（被动接收）

- **接收入口**：`POST /primeport/mkmjSwipeCardRecord/dockSave`
- **数据结构**：`SwipeRecordPushVo`（外层）→ `SwipeRecordPushInfo`（info，含节点编码/设备编号/报警时间/报警状态）→ `SwipeRecordPushExtend`（extend，含刷卡人详细信息/进出/开门结果/抓拍图/卡信息）
- **处理逻辑**：Controller 将三层嵌套结构扁平化为 `MkmjSwipeCardRecordAddCmd`，调用 Service 入库到 `mkmj_swipe_card_record` 表

---

## 五、定时任务（XXL-JOB）

全部位于 `web-infrastructure/plan/` 目录，每日凌晨执行：

| 任务类 | Cron | 职责 | 调用 |
|-------|------|------|------|
| VehicleExpiredExpirationXxlJob | `0 10 0 * * ?` | 车辆过期处理(更新状态+关闭门禁权限) | 仅更新数据库，门禁移除已注释 |
| OneLevelUserXxlJon | `0 14 0 * * ?` | 一级人员门禁当日授权(相关方/临时访客) | `onePersonUtil.execute()` |
| VehicleXgfVehicleXxlJob | `0 15 0 * * ?` | 相关方/临时车辆当日可进港区判断+推送一级门禁 | `carDockUtil.execute(param)` |
| TwoLevelPersonXxlJob | `0 20 0 * * ?` | 二级门禁人员同步(含一级大华) | `twoPersonUtil.scheduledPeopleCompany()` |
| TwoLevelCarXxlJob | `0 25 0 * * ?` | 二级门禁车辆同步(含一级车行) | `twoCarUtil.scheduledCarCompany()` |

> 二级任务支持"单个异常跳过不影响其他"模式。

---

## 六、领域模型

| 实体 | 中文 | 关键字段 |
|------|------|---------|
| MkmjE | 门口信息 | mkmjName、mkmjLevel(1一级/2二级)、mkmjType(人行/车行/综合)、hgAuthArea、出入方向区域、经纬度、closedAreaId |
| MkmjPassageE | 通道 | passageName、passageType、mkmjId |
| MkmjGateE | 闸机 | gateNumber、gateName、gateType、passageId、mkmjId |
| MkmjApprovalUserE | 门禁审批人 | corpId/deptId/userId、人员/车辆/临时审核权限 |
| MkmjSwipeCardRecordE | 刷卡记录 | 节点/报警/通道/人员/卡号/进出/开门结果/抓拍图 |
| ClosedAreaE | 封闭区域 | parentId、hgAuthArea、closedAreaName、jurisdictionalCorpId、location |
| ClosedAreaPersonApplyE | 封闭区域人员申请 | personBelongType、一级/二级口门、closedAreaId、申请/审批信息、随行人员 |
| ClosedAreaCarApplyE | 封闭区域车辆申请 | carBelongType、车牌、一级/二级口门、行驶证、随行人员 |
| ClosedAreaFgsPersonAreaE | 分公司人员区域授权 | fgsPersonAuthId、closedAreaId |
| ClosedAreaFgsPersonAuthE | 分公司人员授权 | corpId、departmentId、userId、mkmjPermission |
| VehicleApplyE | 车辆申请 | 车牌/类型/所属类型(1-9)、授权范围、审核状态、访问时间、企业/部门/申请人/审批人 |
| VehicleAuditE | 车辆审批 | vehicleApplyId、batchType(普通/危化)、审批人、审批状态、变更前后 |
| VehicleBlackE | 车辆黑名单 | licenceNo、blockReason |
| VehicleViolationsE | 车辆违规 | vehicleId、violationType、location、illegalImagesId、processingStatus |
| VehicleMessageE | 车辆门禁对接信息 | vehicleApplyId、syncStatus、syncRemarks |
| VehicleDockLogE | 车辆对接日志 | level(1一级/2二级)、portArea(0全部/1东港/2西港)、dockFlag/dockStatus |
| PersonApplyE | 人员申请 | 企业/部门/人员、头像/手机/身份证 |
| XgfApplyPersonE | 相关方人员申请 | personBelongType、mkmjId、gateLevelAuthArea、审批/驳回信息 |
| PersonMessageE | 人员门禁对接信息 | personApplyId、syncStatus、syncRemarks |
| VideoE | 摄像头 | foreignId、deviceType(1口门/2闸机)、videoType(移动/平台) |
| EVehicleBasicInfoE | 一级口门车辆基础信息 | vehicleRecordId、备案字段、EDW数据溯源字段 |
| EVehicleArrivalDepartureInfoE | 一级口门车辆到离信息 | 到达/离开闸口与时间、车辆序列号 |

---

## 七、关键状态枚举

| 枚举类 | 值 | 说明 |
|-------|---|------|
| **AuditEnum** | 1审核中 / 2通过 / 3驳回 / 4无需审批(检查部门车) | 审批状态 |
| **VehicleBelongTypeEnum** | 1股份员工 / 2股份单位 / 3分公司员工 / 4分公司单位 / 5相关方 / 6临时 / 7检查部门 / 8驻港单位员工 / 9驻港单位公车 | 车辆归属类型 |
| **CloseCarBelongTypeEnum** | 1股份 / 2分公司 / 3相关方 / 4临时车 / 5分公司内部车 | 封闭区域车辆申请归属 |
| **TmpApplyEnum** | CAR_APPLY/PERSON_APPLY × 一级/二级 | 临时申请类型 |
| **FileTypeEnum** | File / url / base64 | 文件格式 |
| **SaveTypeEnum** | 保留 / 删除 / 检测通过保留 | 图片保存策略 |
| **MenuEnum** | 约60项（JGD_*/FGS_*/XGS_*/ZGDW_*） | 菜单与权限枚举 |

---

## 八、审批触发对接入口汇总

| 执行类 | 方法 | 触发对接 |
|-------|------|---------|
| VehicleAuditUpdateExe.audit | 车辆审批通过(auditStatus=2) | 临时/相关方车 → `carDockUtil.execute`；其他 → `carDockUtil.executeInternalVehicle` |
| VehicleBlackAddExe.execute | 车辆拉黑 | `carDockUtil.executeBlackSave` |
| VehicleBlackRemoveExe.execute | 车辆解除黑名单 | `carDockUtil.executeBlackRemove` |
| ClosedAreaCarApplyUpdateExe.auditCarApply | 闭区车辆审批通过 | `twoCarUtil.levelTwoCarCompanyByApplyId`（先二级海康，再一级车行） |
| ClosedAreaPersonApplyUpdateExe.auditPersonApply | 闭区人员审批通过 | `twoPersonUtil.levelTwoPeopleCompanyByApplyId`（二级海康+一级大华） |
| ClosedAreaPersonApplyUpdateExe.authPerson | 人员授权开启(mkmjPermission=1) | 查大华已有权限，没有则 `onePersonUtil.dockLongTermUserByUserCardAndFaceImg` |

---

## 九、配置文件结构

| 文件 | 作用 |
|------|------|
| bootstrap.yml | 主入口，import prodnacos.yml + sdk.yml + swagger.yml |
| prodnacos.yml | 生产 Nacos 配置（prod-nacos:8848，namespace=jjb-dragon，profile=prod） |
| nacos.yml | 测试 Nacos 配置（192.168.20.100:30290，profile=test） |
| ds.yml | 多数据源配置：MySQL(Druid) + PostgreSQL |
| sdk.yml | SDK网关路由、开放平台OpenAPI(SM2加密)、大华oauth配置 |
| swagger.yml | Swagger 文档开关与路径 |

**Nacos 共享配置**：`config-common.yml`、`config-port.yml`、`config-mq.yml`、`config-log.yml`、`config-sdk-server.yml`、`config-actuator.yml`、`config-job.yml`、`config-mysql.yml`、`config-redis.yml`、`config-cache.yml`、`config-spring.yml`、`config-mybatis.yml`、`config-sdk.yml`、`config-flyway.yml` 以及 `${application.name}-conf.yml`

---

## 十、关键风险点

1. **硬编码凭据**：OnePersonUtil/TwoPersonUtil 中大华 URL、用户名、密码、clientId/clientSecret 全部硬编码（`https://skqhdg.porthebei.com:9008/yjkmRy/`、`shuangkong`、`qinan@202604`），存在安全隐患
2. **对接开关失效**：`dockFlag` 是 static 字段且默认 0，`@Value` 注解被注释掉，无法通过配置切换对接开关
3. **有效期仅当日**：大华用户新增时有效期仅 `今天 00:00~23:59`，依赖每日定时任务刷新，任务中断则权限失效
4. **事务边界风险**：外部调用（Dubbo/HK/大华/MQ）在 `@Transactional` 内执行，外部系统已写入的数据无法回滚，存在数据不一致风险
5. **不覆盖策略**：一级门禁对接普遍采用"先查再决定是否新增"，避免覆盖已有长期权限；但二级海康按当日有效期充值
6. **电动车模块薄弱**：两个 Exe 均为纯 CRUD，到离港记录未与基本信息做关联校验，无状态机
7. **残留代码**：ClosedAreaPersonApplyAddExe 含 main 方法（Base64 测试）；ClosedAreaCountExe 的 corpId 过滤被注释
8. **架构不一致**：MkmjApprovalUserAddExe 直接注入 Repository 而非 Gateway，绕过了领域层抽象
9. **硬编码状态值**：部分 Exe 使用字面量（如 `1`、`equals(2)`）而非 AuditEnum 枚举，可读性差
10. **DaHuaSdkUtil 是占位类**：login() 返回 null，实际大华调用走的是 gbscommon 包中的 DaHuaDeviceCommon
