# 畜牧监控 — 技术方案

> 依据：同目录 `畜牧监控功能需求.md`（v1.3）。监控点位 **CRUD** + **逻辑删除** + **第三方同步**（仅列表 upsert）；**实时播放地址**按需调用第三方 play-url，**不落库**；列表**名称模糊**、**所属主体精确**筛选；主体下拉为养殖主体（正常）+ 交易市场（正常）合并选项。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2；列表「查看实时监控」调用本系统 `playUrl` 接口，**LivePlayer** 播放 HLS/m3u8 |
| **第三方** | 与牧场管理共用 `third-party.farming` 配置；开放接口鉴权 `X-App-Key` / `X-App-Secret` |

**分层**：`Controller` → `Service`（`LivestockMonitorValidation`、主体有效性校验）→ `Mapper`/XML → `domain`；同步编排见 `LivestockMonitorSyncService`；实时地址见 `ThirdPartyCameraClient.fetchPlayUrl`。

**代码包**：`com.ruoyi.web.modules.video`

**业务摘要**

| 场景 | 行为 |
| --- | --- |
| 新增 | 校验字段；若填写主体则校验存在且正常；冗余 `owner_name`；`del_flag=0` |
| 修改 | 全字段可改；可更换主体；更新 `owner_name` |
| 列表/详情 | 仅 `del_flag=0`；排序 `create_time DESC, id DESC` |
| 列表筛选 | `monitor_name` 模糊；`owner_type` + `owner_id` **精确**（成对） |
| 删除 | `del_flag=2`；**不**校验下游引用 |
| 第三方同步 | 拉取监控列表 upsert（按 `monitor_id`）；**不**拉取、**不**持久化播放地址 |
| 实时预览 | 按 `monitorId` 调第三方 play-url，返回 `videoUrl`（仅接口响应，不写表） |
| 唯一性 | **不**校验名称重复；同步以 `monitor_id` 为 upsert 键 |

---

## 2. 数据库设计

### 2.1 表 `biz_livestock_monitor`（畜牧监控点位）

| 字段 | 类型 | 非空 | 说明 |
| --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 主键 |
| `monitor_id` | `varchar(32)` | Y | 摄像头 ID（第三方 `cameraId`，同步 upsert 键） |
| `monitor_name` | `varchar(32)` | Y | 监控名称（**1～10** 字） |
| `owner_type` | `tinyint(4)` | N | 1 养殖主体 / 2 交易市场 |
| `owner_id` | `bigint(20)` | N | 主体主键 |
| `owner_name` | `varchar(64)` | N | 主体名称冗余 |
| `enclosure_name` | `varchar(64)` | N | 圈舍 |
| `install_location` | `varchar(32)` | N | 安装位置（**≤10** 字） |
| `video_source` | `varchar(32)` | Y | 视频来源：`wvp` / `ys7` / `url` |
| `del_flag` | `char(1)` | Y | `0` 存在 `2` 删除 |
| 审计字段 | 若依惯例 | — | `create_by` / `create_time` / `update_by` / `update_time` |

**索引**：`PRIMARY KEY (id)`；`KEY idx_monitor_id (monitor_id)`；`KEY idx_monitor_name`；`KEY idx_owner`；`KEY idx_create_time`；`KEY idx_del_flag`。

> 已移除历史字段 `ip_address`、`video_url`（v1.3 播放地址不落库）。DDL 与升级脚本见 `sql/biz_livestock_monitor.sql`。

### 2.2 枚举

**所属主体类型 `owner_type`**

| 值 | 含义 | 关联表 |
| --- | --- | --- |
| 1 | 养殖主体 | `biz_pasture`（`biz_status=1` 且 `del_flag=0`） |
| 2 | 交易市场 | `biz_trade_market`（`operate_status=1` 且 `del_flag=0`） |

**视频来源 `video_source`**

| 值 | 含义 |
| --- | --- |
| wvp | WVP 平台 |
| ys7 | 萤石云 |
| url | 直链 URL |

**删除标记 `del_flag`**

| 值 | 含义 |
| --- | --- |
| 0 | 正常 |
| 2 | 已逻辑删除 |

### 2.3 主体主数据校验（Service）

保存/修改时，**仅当** `owner_type` 与 `owner_id` **均非空**时校验：

| `owner_type` | 校验要点 |
| --- | --- |
| 1 | `biz_pasture`：`id=#{ownerId}` AND `del_flag='0'` AND `biz_status=1` |
| 2 | `biz_trade_market`：`id=#{ownerId}` AND `del_flag='0'` AND `operate_status=1` |

仅填其一则报「所属主体信息不完整」。两者皆空则主体字段置空。

---

## 3. 接口设计

**统一响应**：`AjaxResult` / `TableDataInfo`（`code`、`msg`、`rows`/`data`）。

**权限标识**：`videoSurveillance:livestockMonitor:list|query|add|edit|remove|sync`

**Base Path**：`/videoSurveillance/livestockMonitor`

| # | 说明 | Method | URI | 权限 |
| --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/list` | `list` |
| 3.2 | 详情 | GET | `/{id}` | `query` |
| 3.3 | 新增 | POST | `/` | `add` |
| 3.4 | 修改 | PUT | `/` | `edit` |
| 3.5 | 删除 | DELETE | `/{ids}` | `remove` |
| 3.6 | 所属主体选项 | GET | `/ownerOptions` | `list` |
| 3.7 | 第三方同步 | POST | `/sync` | `sync` |
| 3.8 | 实时播放地址 | GET | `/playUrl?monitorId=` | `query` |

### 3.1 列表 `GET /list`

**Query**：`pageNum`、`pageSize`、`monitorName`（模糊）、`ownerType`+`ownerId`（成对精确）。

**列表行（驼峰）**：`id`、`monitorId`、`monitorName`、`ownerType`、`ownerTypeName`、`ownerId`、`ownerName`、`enclosureName`、`installLocation`、`videoSource`、`videoSourceName`、`createTime`。

### 3.2 详情 `GET /{id}`

返回上述字段及审计字段；仅 `del_flag=0`。

### 3.3 新增 / 3.4 修改

**Body（驼峰）**

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `id` | 修改必填 | 主键 |
| `monitorId` | Y | 摄像头 ID，≤32 字符 |
| `monitorName` | Y | 1～10 字 |
| `ownerType` / `ownerId` | N | 成对可选 |
| `enclosureName` | N | ≤64 字符 |
| `installLocation` | N | ≤10 字符 |
| `videoSource` | Y | `wvp` / `ys7` / `url` |

### 3.5 删除 `DELETE /{ids}`

逻辑删除，`del_flag='2'`。

### 3.6 所属主体选项 `GET /ownerOptions`

**Query**：`keyword`（可选，模糊）。

**响应 `data[]`**：`ownerType`、`ownerTypeName`、`ownerId`、`ownerName`。

### 3.7 第三方同步 `POST /sync`

**说明**：从第三方生产管理系统**仅**拉取监控点位列表并 upsert。编排类 `LivestockMonitorSyncService`；返回 `YakAssetSyncResultVo`（与牧场同步结构一致）。

**响应 `data`**

| 字段 | 说明 |
| --- | --- |
| `insertCount` | 新增条数 |
| `updateCount` | 更新条数 |
| `failCount` | 失败条数（单条落库失败） |
| `failMessages` | 失败明细，键名为 `{cameraId}: …` |
| `syncTime` | 同步完成时间 |

**并发**：同步进行中再次调用返回「正在同步，请稍候」。

**更新时保留本地字段**：`owner_type`、`owner_id`、`install_location`（不同步覆盖）。

### 3.8 实时播放地址 `GET /playUrl`

**Query**：`monitorId`（必填，对应 `biz_livestock_monitor.monitor_id` / 第三方 `cameraId`）。

**流程**：

1. 校验本地存在且 `del_flag=0` 的监控记录；
2. 调用第三方 `GET /open-api/v1/iot/cameras/{cameraId}/play-url`；
3. 返回 `LivestockMonitorPlayUrlVo`，**不写入数据库**。

**响应 `data`（驼峰）**

| 字段 | 说明 |
| --- | --- |
| `monitorId` | 摄像头 ID |
| `monitorName` | 本地监控名称 |
| `videoSource` | 第三方返回或本地冗余 |
| `videoSourceName` | 展示用中文 |
| `videoUrl` | 可播放地址（通常 HLS/m3u8） |

**失败**：监控不存在、第三方未启用、play-url 为空或业务码非 0 时返回可读 `msg`。

### 3.9 服务端校验（`LivestockMonitorValidation`）

| 项 | 规则 |
| --- | --- |
| 摄像头 ID | 非空；≤32 字符 |
| 监控名称 | trim 后 1～10 字 |
| 视频来源 | 非空；∈ {wvp, ys7, url} |
| 圈舍 | 可选；≤64 字符 |
| 安装位置 | 可选；≤10 字符 |
| 主体 | 成对可选；填则 §2.3 校验 |

### 3.10 第三方开放接口（Apifox）

与牧场、牦牛档案等同源，配置前缀 **`third-party.farming`**：

| 配置项 | 说明 |
| --- | --- |
| `enabled` | 是否允许调用第三方（同步与 play-url 共用） |
| `mode` | `stub`（classpath 样例）/ `http`（远程） |
| `base-url` | 第三方根地址 |
| `app-key` / `app-secret` | 请求头鉴权 |
| `page-size` | 列表分页大小（上限 200） |

**查询监控数据（同步用）**

- `GET /open-api/v1/iot/cameras`
- Query：`pageNum`、`pageSize`；可选 `farmId`、`enclosureId`、`updatedAfter`
- 响应：`OpenApiResponse<OpenApiPageResult<OpenCameraDto>>`，`code=0` 成功
- 单条 `records[]` 字段映射：

| 第三方字段 | 本地字段 |
| --- | --- |
| `cameraId` | `monitor_id` |
| `deviceName` | `monitor_name`（超长截断至 10 字） |
| `farmName` | `owner_name`（仅文本，不绑定 owner_type/id） |
| `enclosureName` | `enclosure_name` |
| `videoSource` | `video_source` |

**获取监控视频播放地址（实时预览用）**

- `GET /open-api/v1/iot/cameras/{cameraId}/play-url`
- 响应 `data`：`cameraId`、`videoSource`、`videoUrl`
- 本系统经 `GET /playUrl` 转发，**不**落库

**实现类**

| 类 | 职责 |
| --- | --- |
| `ThirdPartyCameraClient` / `ThirdPartyCameraClientImpl` | HTTP / stub：`fetchAllCameras`、`fetchPlayUrl` |
| `LivestockMonitorSyncService` | 同步编排、防重入（仅列表 upsert） |
| `LivestockMonitorSyncTxService` | 单条 upsert（`REQUIRES_NEW`） |
| `BizLivestockMonitorServiceImpl.getPlayUrlByMonitorId` | 本地校验 + 调 play-url |
| `ThirdPartyFarmingHttpSupport` | 共用 RestTemplate、鉴权头（`industryservice` 包） |

**Stub 样例**

- `thirdparty/stub-cameras.json` — 监控列表（同步）
- `thirdparty/stub-camera-play-urls.json` — 播放地址（实时预览）

---

## 4. 菜单与权限（示例）

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 目录 | 视频监控系统 | — |
| 菜单 | 畜牧监控 | `videoSurveillance:livestockMonitor:list` |
| 按钮 | 查询 | `videoSurveillance:livestockMonitor:query`（含详情、playUrl） |
| 按钮 | 新增 | `videoSurveillance:livestockMonitor:add` |
| 按钮 | 修改 | `videoSurveillance:livestockMonitor:edit` |
| 按钮 | 删除 | `videoSurveillance:livestockMonitor:remove` |
| 按钮 | 同步 | `videoSurveillance:livestockMonitor:sync` |

组件路径：`videoSurveillance/livestockMonitor/index`

---

## 5. 模块关联

| 模块 | 关系 |
| --- | --- |
| 牧场管理 | `owner_type=1`；第三方同步养殖场列表同源配置 |
| 交易市场管理 | `owner_type=2` |
| 第三方生产管理系统 | 监控列表 + 播放地址开放接口 |

---

## 6. 交付清单

- [x] `sql/biz_livestock_monitor.sql`
- [x] `BizLivestockMonitor`、`BizLivestockMonitorMapper`/XML、`BizLivestockMonitorServiceImpl`、`BizLivestockMonitorController`
- [x] `LivestockMonitorValidation`、`LivestockMonitorSyncService`、`ThirdPartyCameraClientImpl`
- [x] `GET /playUrl` 实时播放地址
- [x] 前端 `livestockMonitor/index.vue`（LivePlayer + `getLivestockMonitorPlayUrl`）、`livestockMonitor.js`、同步按钮
- [x] 单元测试 / MockMvc（含 `/sync`、`/playUrl`）

---

## 7. 修订记录

| 版本 | 说明 |
| --- | --- |
| 1.0 | 初稿：CRUD + `ownerOptions`；含 `ip_address` |
| 1.1 | 表结构：`monitor_id`、`enclosure_name`、`video_source`；移除 `ip_address`；主体/安装位置可选 |
| 1.2 | 第三方同步：`POST /sync`；对接 `/iot/cameras` 与 play-url 两阶段落库 `video_url` |
| 1.3 | **移除 `video_url` 字段**；同步仅 upsert 列表；**新增 `GET /playUrl`** 按需拉取播放地址；前端 LivePlayer 实时预览 |
