# 牦牛疾病预警 — 技术方案

> 依据：同目录 `牦牛疾病预警功能需求.md`。本期 **列表 + 详情只读** + **第三方同步写入**；无手工增删改接口。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2（本方案不展开） |
| **第三方** | HTTP 客户端（与牦牛资产同源或独立告警接口）；`application.yml` 配置地址、鉴权、超时 |
| **关联表** | `biz_pasture`（牧场映射 `pasture_id`）；`biz_yak_asset`（`yak_no` 关联，非强外键） |

**分层**：`Controller` → `YakDiseaseWarningSyncService`（拉取三类健康源 → 落库 → `YakDiseaseWarningAnalyzer` 分析生成预警）→ `IBizYakDiseaseWarningService`（查询、数据权限）→ `Mapper`/XML → `domain`。

**代码位置（建议）**：`baqing-admin` → `com.ruoyi.web.modules.industryservice`（与牧场、牦牛资产同包）。

| 场景 | 行为 |
| --- | --- |
| 健康源同步 | 分页拉取检疫/诊疗/养殖过程 OpenAPI；以 `external_id`（`Q:`/`D:`/`B:` + 第三方 ID）**唯一** upsert 至 `biz_yak_health_source_record` |
| 预警生成 | 对 `analyzed_flag=0` 记录集中分析；检疫异常/阳性、诊疗同区同日 ≥3 条写入 `biz_yak_disease_warning`；分析后标记源记录已分析 |
| 预警生成 | 健康源分析后**插入**预警记录；源记录 `analyzed_flag=1` 后不再重复分析 |
| 列表/详情 | 无 `del_flag` 删除需求；全量展示有效同步记录；排序 `alert_time DESC, id DESC` |
| 日期筛选 | `startDate`/`endDate`（`yyyy-MM-dd`）转当日 00:00:00～23:59:59（`Asia/Shanghai`） |
| 牧场权限 | 列表/详情/移动端按用户可见 `pasture_id` 过滤（`@DataScope` 或 Service 注入牧场 ID 集合） |
| 并发同步 | 接口加锁（`synchronized` / Redis 锁），防重复点击 |

---

## 2. 数据库设计

### 2.1 表 `biz_yak_disease_warning`（牦牛疾病预警）

| 字段 | 类型 | 非空 | 说明 |
| --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 主键 |
| `yak_no` | `varchar(64)` | Y | 牦牛编号 |
| `pasture_id` | `bigint(20)` | N | 映射 `biz_pasture.id` 成功时写入 |
| `pasture_name` | `varchar(128)` | N | 所属牧场名称（冗余展示） |
| `alert_time` | `datetime` | Y | 预警时间 |
| `alert_message` | `varchar(1024)` | Y | 预警信息（自然语言全文） |
| `warning_type` | `tinyint(4)` | N | 预警类型 **§2.2**；列表可不展示 |
| `measured_value` | `varchar(128)` | N | 实测值描述，如 `39.8℃` |
| `reference_range` | `varchar(128)` | N | 参考范围描述 |
| `device_no` | `varchar(64)` | N | 设备/耳标编号（第三方有则存） |
| `collect_time` | `datetime` | N | 指标采集时间 |
| `yak_avatar_url` | `varchar(512)` | N | 牦牛头像 URL（移动端展示） |
| `data_source` | `varchar(32)` | N | 数据来源，默认 `third_party` |
| `last_sync_time` | `datetime` | N | 本条最近同步时间 |
| `create_time` / `update_time` | `datetime` | — | 审计 |

**索引**

| 索引 | 字段 | 用途 |
| --- | --- | --- |
| `PRIMARY` | `id` | 主键 |
| `KEY idx_alert_time` | `alert_time` | 列表排序、日期范围 |
| `KEY idx_pasture_time` | `pasture_id`, `alert_time` | 牧场 + 时间筛选 |
| `KEY idx_yak_no` | `yak_no` | 关联档案、产业统计去重 |
| `KEY idx_yak_alert_time` | `yak_no`, `alert_time` | 按牛统计周期内预警 |

### 2.2 枚举 `warning_type`（可选入库）

| 值 | 含义 |
| --- | --- |
| `1` | 体温异常 |
| `2` | 运动量异常 |
| `3` | 检疫集中预警 |
| `4` | 诊疗集中预警 |
| `5` | AI问诊集中预警 |
| `6` | ~~草畜失衡~~（已迁至独立表 `biz_grass_livestock_imbalance_warning`） |
| `9` | 其他 |

第三方无类型码时，仅写 `alert_message`，`warning_type` 可空或由文案解析填充。

### 2.3 DDL（MySQL 5.7）

```sql
CREATE TABLE `biz_yak_disease_warning` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `yak_no` varchar(64) NOT NULL COMMENT '牦牛编号',
  `pasture_id` bigint(20) DEFAULT NULL COMMENT '牧场ID',
  `pasture_name` varchar(128) DEFAULT NULL COMMENT '所属牧场名称',
  `alert_time` datetime NOT NULL COMMENT '预警时间',
  `alert_message` varchar(1024) NOT NULL COMMENT '预警信息',
  `warning_type` tinyint(4) DEFAULT NULL COMMENT '1体温 2运动量 3检疫集中 4诊疗集中 9其他',
  `measured_value` varchar(128) DEFAULT NULL COMMENT '实测值',
  `reference_range` varchar(128) DEFAULT NULL COMMENT '参考范围',
  `device_no` varchar(64) DEFAULT NULL COMMENT '设备编号',
  `collect_time` datetime DEFAULT NULL COMMENT '采集时间',
  `yak_avatar_url` varchar(512) DEFAULT NULL COMMENT '头像URL',
  `data_source` varchar(32) DEFAULT 'local_analysis' COMMENT '数据来源',
  `last_sync_time` datetime DEFAULT NULL COMMENT '最近同步时间',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_alert_time` (`alert_time`),
  KEY `idx_pasture_time` (`pasture_id`,`alert_time`),
  KEY `idx_yak_no` (`yak_no`),
  KEY `idx_yak_alert_time` (`yak_no`,`alert_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='牦牛疾病预警';
```

**可选**：`biz_yak_warning_sync_log`（同步批次日志：操作人、起止时间、新增/更新/失败条数 JSON），与资产档案同步日志模式一致。

### 2.4 表 `biz_yak_health_source_record`（健康源数据）

第三方检疫/诊疗/养殖过程记录落库表；同步后由 `YakDiseaseWarningAnalyzer` 分析，分析完毕置 `analyzed_flag=1`。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | bigint | 主键 |
| `external_id` | varchar(64) | 唯一键，`Q:`/`D:`/`B:` + 第三方 ID |
| `record_type` | tinyint | `1` 检疫、`2` 诊疗、`3` 养殖过程 |
| `record_time` | datetime | 业务时间 |
| `region_key` | varchar(128) | 地区键（县+乡镇或牧场），集中判断分组 |
| `farm_id` / `farm_name` | — | 第三方农场 |
| `yak_no` | varchar(64) | 耳标/场内编号 |
| `result_code` / `result_text` | — | 检疫结果、病种、过程类型等 |
| `analyzed_flag` | char(1) | `0` 未分析 / `1` 已分析 |
| `last_sync_time` | datetime | 最近同步时间 |

DDL 见 `sql/biz_yak_health_source_record.sql`。

### 2.5 产业统计引用（只读）

「养殖产业数据」预警牦牛头数：

```sql
-- 示例：周期内去重头数（周期参数由统计模块传入）
SELECT COUNT(DISTINCT yak_no)
FROM biz_yak_disease_warning
WHERE alert_time >= ? AND alert_time < ?
  AND (pasture_id IN (?) OR ? IS NULL);
```

---

## 3. 接口设计

**统一响应**：`AjaxResult`（`code` / `msg` / `data`）或分页 `TableDataInfo`（`rows` / `total`）。

**权限标识**：`dataModel:yakDiseaseWarning:list|query|sync`

**后台 Base Path**：`/dataModel/yakDiseaseWarning`

| # | 说明 | Method | URI | 权限 | 要点 |
| --- | --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/list` | `list` | Query **§3.1.1**；牧场数据权限 |
| 3.2 | 预警详情 | GET | `/{id}` | `query` | 全字段；越权牧场返回业务失败 |
| 3.3 | 第三方同步 | POST | `/sync` | `sync` | 返回 **§3.3.1**；`@Log` |
| 3.4 | 牧场下拉（复用） | GET | `/dataModel/pasture/list` | `dataModel:pasture:list` | 前端筛选用；`pageSize` 放大或专用精简接口 |

本期**不提供** `POST/PUT/DELETE` 预警维护接口。

#### 3.1.1 列表 Query

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `pageNum` / `pageSize` | int | N | 默认 `1` / `20` |
| `startDate` | string | N | 预警开始日 `yyyy-MM-dd`；空不过滤 |
| `endDate` | string | N | 预警结束日 `yyyy-MM-dd`；空不过滤 |
| `pastureId` | long | N | 所属牧场 **精确**匹配 `pasture_id` |

**服务端校验**

| 规则 | 处理 |
| --- | --- |
| `startDate` > `endDate` | `AjaxResult.error("开始日期不能晚于结束日期")` |
| 仅 `startDate` | `alert_time >= startDate 00:00:00` 且 `<= 当日 23:59:59`（结束缺省为今天） |
| 仅 `endDate` | `alert_time <= endDate 23:59:59` |
| 均为空 | 不按日期过滤 |

**列表 `rows[]` 字段（驼峰）**

| 字段 | 对应列 |
| --- | --- |
| `id` | 主键 |
| `alertTime` | 预警时间 |
| `yakNo` | 牦牛编号 |
| `pastureName` | 所属牧场 |
| `alertMessage` | 预警信息 |

排序：`alert_time DESC, id DESC`。

#### 3.2.1 详情响应 `data`

| 字段 | 说明 |
| --- | --- |
| 列表字段 | `id`、`yakNo`、`pastureId`、`pastureName`、`alertTime`、`alertMessage` |
| 扩展字段 | `warningType`、`measuredValue`、`referenceRange`、`deviceNo`、`collectTime`、`yakAvatarUrl` |
| 元数据 | `dataSource`、`lastSyncTime`、`createTime`、`updateTime` |
| 可选 | `yakAssetId`：按 `yak_no` 查 `biz_yak_asset` 有则返回，供前端跳转档案 |

记录不存在或牧场越权：`msg` 如「预警记录不存在」/「无权查看该预警」。

#### 3.3.1 同步响应 `data`

| 字段 | 说明 |
| --- | --- |
| `insertCount` | 新增条数 |
| `updateCount` | 更新条数 |
| `failCount` | 失败条数 |
| `failMessages` | 失败摘要（可选，最多 N 条） |
| `syncTime` | 完成时间 |

同步中重复请求：返回「正在同步，请稍候」。

---

### 3.5 移动端接口

**Base Path**：`/app/healthAlert`（与界面「健康预警」一致）

| # | 说明 | Method | URI | 鉴权 | 要点 |
| --- | --- | --- | --- | --- | --- |
| M1 | 预警列表 | GET | `/list` | 登录 Token | Query 仅 `pageNum`/`pageSize`；**同 §3.1 牧场数据权限**；排序同后台 |
| M2 | 预警详情 | GET | `/{id}` | 登录 Token | **本期可选**；原型列表直显全文时可不做 |

**M1 `rows[]` 建议字段**：`id`、`yakNo`、`alertTime`、`alertMessage`、`yakAvatarUrl`（无则前端占位图）。

本期移动端**不提供** `startDate`/`pastureId` 筛选（见功能需求 §5.5 扩展项）。

---

## 4. 第三方同步（实现要点）

| 项 | 说明 |
| --- | --- |
| 配置键 | `third-party.farming`（与牧场、牦牛资产同源：`base-url`、`app-key`、`app-secret`、超时） |
| 客户端 | `ThirdPartyHealthRecordClient`：`fetchQuarantineRecords()`、`fetchDiagnosisRecords()`、`fetchBreedingProcessRecords()` |
| OpenAPI 路径 | `/open-api/v1/farming/quarantine-records`、`diagnosis-records`、`breeding-process-records`（以 Apifox 为准） |
| 映射 | `HealthRecordOpenApiMapper` → `BizYakHealthSourceRecord`；`YakHealthSourceRecordSyncTxService` 按 `external_id` upsert |
| 分析 | `YakDiseaseWarningAnalyzer`：检疫同 `region_key`+自然日异常/阳性 ≥3 → `warning_type=3`；诊疗同组 ≥3 → `warning_type=4`；养殖过程仅 `markAnalyzed` |
| 阈值 | `YakDiseaseWarningAnalysisRules.CONCENTRATION_THRESHOLD = 3` |
| 预警入库 | `YakDiseaseWarningSyncTxService.persistAlert`；牧场名精确匹配 `biz_pasture.pasture_name` |
| 文案 | 集中预警由分析器生成 `alert_message`；体温/运动量类仍可按实测值拼接 |
| 失败策略 | 单条源记录失败记 `failCount`；第三方整体不可用抛异常且**不清库** |
| Stub | `thirdparty/stub-quarantine-records.json` 等（`third-party.farming.stub-enabled=true`） |
| 与资产同步 | 疾病预警 `POST /dataModel/yakDiseaseWarning/sync` **仅**健康源+分析；生物资产 `POST /dataModel/yakAsset/sync` 为五段全量；IoT 称重/定位/环境为独立定时任务（见牦牛资产技术方案 **§3.9**） |

---

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

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 牦牛疾病预警 | `dataModel:yakDiseaseWarning:list` |
| 按钮 | 查询 | `dataModel:yakDiseaseWarning:query` |
| 按钮 | 同步 | `dataModel:yakDiseaseWarning:sync` |

组件路径建议：`dataModel/yakDiseaseWarning/index`（挂载「产业数据模型及体系」目录）。

移动端菜单/路由由小程序或 H5 工程配置，**不依赖**上述后台菜单权限；接口走登录态 + 牧场数据范围。

---

## 6. 类清单与交付

| 交付项 | 说明 |
| --- | --- |
| `sql/biz_yak_disease_warning.sql` | 预警表建表脚本 |
| `sql/biz_yak_health_source_record.sql` | 健康源表建表脚本 |
| `BizYakHealthSourceRecord` + Mapper | 源记录 CRUD、未分析查询、标记已分析 |
| `ThirdPartyHealthRecordClient`、`HealthRecordOpenApiMapper` | 三类 OpenAPI 拉取与映射 |
| `YakHealthSourceRecordSyncTxService`、`YakDiseaseWarningAnalyzer` | 源记录 upsert 与规则分析 |
| `YakDiseaseWarningSyncService` | 编排：拉源 → 落库 → 分析 |
| `BizYakDiseaseWarning` + Mapper | 预警列表、详情 |
| `IBizYakDiseaseWarningService`、`BizYakDiseaseWarningController` | 查询、同步接口 |
| 单元测试 / MockMvc | 日期边界、源同步、分析阈值、同步幂等、越权详情 |

---

## 7. 修订记录

| 版本 | 日期 | 说明 |
| --- | --- | --- |
| 1.0 | 2026-05-21 | 初版：单表 + 后台/移动端接口 + 第三方同步 |
| 1.1 | 2026-05-20 | 对齐草稿：三类健康源 OpenAPI 落库 + 集中分析生成预警；新增 `biz_yak_health_source_record`；预警类型 3/4 |
