# 牦牛存栏数据填报 — 技术方案 > 依据:同目录 `牦牛存栏数据填报功能需求.md`;模板 `E:\luhao\工作交接\巴青项目\农牧数据\农业农村和科技水利局-业务办\畜群结构\2026年.xlsx`(第一个 Sheet)。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(springboot2):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **前端** | 若依 Vue2 + Element UI | | **Excel** | Apache POI 或项目已有 EasyExcel(与若依导入规范一致) | | **组织** | `sys_dept` 乡镇字典;`SysDeptMapper` 查询二级节点 | **分层**:`BizYakHerdInventoryController` → `IBizYakHerdInventoryService` → `BizYakHerdInventoryMapper` → `domain`;Excel 由 `YakHerdInventoryExcelParser`(导入解析)、`YakHerdInventoryExcelExporter`(按年导出)承担。 **代码位置**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`(与产业数据模型同包)。 **模板文件**:`baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`(导入、导出、下载模板共用)。 --- ## 2. 数据库设计 ### 2.1 表 `biz_yak_herd_inventory`(牦牛存栏数据填报) | 字段 | 类型 | 非空 | 说明 | | --- | --- | --- | --- | | `id` | `bigint(20)` | Y | 主键 | | `stat_year` | `int(11)` | Y | 所属年份(自然年) | | `stat_month` | `int(11)` | Y | 所属月份(1~12) | | `town_dept_id` | `bigint(20)` | Y | 所属乡镇,`sys_dept.dept_id` | | `town_name` | `varchar(64)` | Y | 乡镇名称冗余,便于列表展示 | | `village_dept_id` | `bigint(20)` | N | **预留**所属村居,`sys_dept.dept_id`(乡镇下三级节点) | | `village_name` | `varchar(128)` | N | **预留**所属村居名称冗余 | | `village_count` | `int(11)` | N | 村居数 | | `herdsman_household_count` | `int(11)` | N | 牧户数 | | `yak_total` | `int(11)` | N | 牦牛存栏数 | | `bull_total` | `int(11)` | N | 公牛存栏数 | | `cow_total` | `int(11)` | N | 母牛存栏数 | | `breeding_bull_count` | `int(11)` | N | 种公牛存栏数 | | `bull_castrated_count` | `int(11)` | N | 公牛/阉畜存栏数 | | `cow_under_1_count` | `int(11)` | N | 1 岁以下母牛存栏数 | | `cow_1_3_count` | `int(11)` | N | 1~3 岁母牛存栏数 | | `cow_3_12_count` | `int(11)` | N | 3~12 岁母牛存栏数 | | `cow_over_12_count` | `int(11)` | N | 12 岁以上母牛存栏数 | | `create_by` / `create_time` | — | — | 审计 | | `update_by` / `update_time` | — | — | 审计 | | `remark` | `varchar(500)` | N | 备注 | **说明**:本表**不使用逻辑删除**,无 `del_flag` 字段;删除操作为物理删除。 **索引与约束** - `UNIQUE uk_year_month_town (stat_year, stat_month, town_dept_id)` — 年月+乡镇唯一,支撑导入 upsert。 - `KEY idx_stat_year_month (stat_year, stat_month)` - `KEY idx_town_dept_id (town_dept_id)` **DDL 文件**:`sql/biz_yak_herd_inventory.sql`(新库);已有库执行 `sql/biz_yak_herd_inventory_stat_month.sql`(历史行 `stat_month` 默认 **12**)。 ### 2.2 乡镇字典查询(`sys_dept`) ```sql SELECT dept_id, dept_name, order_num FROM sys_dept WHERE parent_id = 100 AND order_num > 12 AND del_flag = '0' AND status = '0' ORDER BY order_num; ``` 前端乡镇下拉、导入匹配均调用封装方法 `TownDeptSupport.listReportTowns()`(名称与根 `parent_id` 可配置化 `ruoyi.town-dept-parent-id=100`、`ruoyi.town-dept-min-order-num=12`)。 ### 2.3 可选年份 ```java // 当前年起向前 5 年,共 6 个 int current = Year.now().getValue(); for (int y = current; y >= current - 5; y--) { ... } ``` ### 2.3 可选月份 `YakHerdInventoryMonthSupport.resolveMonthOptions()` 返回 **1~12**;`validateStatPeriod(year, month)` 校验年份在 **§2.2** 范围内、月份合法且**非未来月**。 --- ## 3. 接口设计 **Base Path**:`/dataModel/yakHerdInventory` **权限前缀**:`dataModel:yakHerdInventory` | # | 说明 | 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 | `/townOptions` | `list` | | 3.7 | 年份下拉 | GET | `/yearOptions` | `list` | | 3.7.1 | 月份下拉 | GET | `/monthOptions` | `list` | | 3.8 | 导入 | POST | `/importData` | `import` | | 3.9 | 下载导入模板 | GET | `/importTemplate` | `import` | | 3.10 | 按年月导出 | GET | `/export` | `export` | #### 3.1.1 列表 Query | 参数 | 类型 | 说明 | | --- | --- | --- | | `pageNum` / `pageSize` | int | 默认 1 / 20 | | `statYear` | int | 所属年份,精确 | | `statMonth` | int | 所属月份(1~12),精确 | | `townDeptId` | long | 所属乡镇 dept_id,精确 | **列表 VO 字段**(列标题与需求 **§5.1** 一致): | 列标题 | 字段 | | --- | --- | | 所属年份 | `statYear` | | 所属月份 | `statMonth` | | 乡镇 | `townName` | | 村居数 | `villageCount` | | 牧户数 | `herdsmanHouseholdCount` | | 存栏数 | `yakTotal` | | 公牛存栏数 | `bullTotal` | | 母牛存栏数 | `cowTotal` | #### 3.2.1 详情 返回 `BizYakHerdInventory` **全字段**(驼峰),含预留村居、分龄母牛、种公牛、阉畜、备注及 `createBy` / `createTime` / `updateBy` / `updateTime` 等。 #### 3.3.1 新增/修改 Body | 字段 | 必填 | 校验 | | --- | --- | --- | | `statYear` | Y | 在 `yearOptions` 范围内 | | `statMonth` | Y | 1~12;不可为未来月 | | `townDeptId` | Y | 须在乡镇字典内 | | `villageDeptId` | N | **预留**;若填写须为有效 `sys_dept.dept_id`,且 `parent_id = townDeptId`(可选校验) | | `villageName` | N | **预留**村居名称 | | 各 `*Count` | N | `>= 0` 整数 | | `id` | 修改必填 | — | **唯一性**:`countByYearMonthAndTownExcludeId(statYear, statMonth, townDeptId, excludeId) > 0` 则拒绝(**手工新增/修改**);**导入**走 upsert,不受此限。 #### 3.8 导入 **请求**:`multipart/form-data` | 参数 | 必填 | 说明 | | --- | --- | --- | | `file` | Y | `.xlsx`(如 `牦牛存栏数据.xlsx`) | | `statYear` | Y | 填报年份 | | `statMonth` | Y | 填报月份(1~12) | **Upsert 规则**(`BizYakHerdInventoryServiceImpl.upsertImportRow`): | 步骤 | 说明 | | --- | --- | | 1 | 按 `statYear` + `statMonth` + `townDeptId` 查询 `selectByYearMonthAndTown` | | 2 | 无记录 → `insert`;`insertCount++` | | 3 | 有记录 → `updateBizYakHerdInventory` 全字段覆盖;`updateCount++` | 同一文件对**同一填报年月**多次导入:各乡镇行均为更新,不产生重复有效记录。 **响应 `data`** | 字段 | 说明 | | --- | --- | | `insertCount` | 新增条数 | | `updateCount` | 更新条数 | | `failCount` | 失败条数 | | `failMessages` | 如 `第8行[杂色镇]: 乡镇未识别` | | `warnMessages` | 分龄加总与母牛合计不一致等警告(可选) | #### 3.8.1 Excel 第一个 Sheet 解析约定(`2026年.xlsx`) | 项 | 约定 | | --- | --- | | Sheet | `workbook.getSheetAt(0)` | | 标题行 | 前若干行为「巴青县…畜群结构…统计表」及填表说明,**自动扫描**首列含「序号」且相邻列含「乡镇」的行作为表头行 `headerRow` | | 数据行 | `headerRow + 1` 起至「乡镇」列为空 | | 列映射(默认,实施对照模板微调) | | | 列号(0-based) | Excel 列 | 实体字段 | | --- | --- | --- | | 1 | B | 乡镇(按 `dept_name` 解析 `townDeptId`) | | 2 | C | `villageCount` | | 3 | D | `herdsmanHouseholdCount` | | 4 | E | `yakTotal`(存栏数) | | 5 | F | 与 E 同值(牦牛公母分类合计) | | 6 | G | `bullTotal`(公牛(头)) | | 7 | H | `cowTotal`(牦牛公母分类 — 母牛(头)) | | 8 | I | 与 G 同值(公牛区块合计) | | 9 | J | `breedingBullCount` | | 10 | K | `bullCastratedCount` | | 11 | L | `cowTotal` | | 12 | M | `cowUnder1Count` | | 13 | N | `cow13Count` | | 14 | O | `cow312Count` | | 15 | P | `cowOver12Count` | | 16 | Q | `remark` | > 数据区起始行:`headerRow + 3`(表头行由「序号」「乡镇」自动识别)。模板存在合并单元格,列号以代码常量为准。 **导入事务**:逐乡镇独立处理,单乡镇失败不影响其余。 #### 3.10 按年月导出 **请求**:`GET /export?statYear=2026&statMonth=7` | 参数 | 必填 | 说明 | | --- | --- | --- | | `statYear` | Y | 导出年份,须在 `yearOptions` 范围内 | | `statMonth` | Y | 导出月份(1~12);须通过 `validateStatPeriod` | **响应**:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 文件流;文件名 `{statYear}年{statMonth}月牦牛存栏数据.xlsx`。 **实现**:`BizYakHerdInventoryServiceImpl.exportByYearMonth` → `YakHerdInventoryExcelExporter.export`: 1. 加载 classpath 模板 `YakHerdInventoryExcelExporter.TEMPLATE_CLASSPATH` 2. 替换表头中的四位年份为 `statYear` 3. 查询 `stat_year = statYear AND stat_month = statMonth` 的全部记录,按 `town_dept_id` 建 Map 4. 遍历 `TownDeptSupport.listReportTowns()`,逐乡镇写行;无数据则仅写乡镇名 5. 末行写「合计」,对已填报乡镇数值列求和 **与 3.9 区别**:`/importTemplate` 返回空白模板;`/export` 返回填充后的全县报表。 --- ## 4. 前端设计(要点) | 项 | 说明 | | --- | --- | | **路径** | `ruoyi-ui/src/views/dataModel/yakHerdInventory/index.vue` | | **API** | `ruoyi-ui/src/api/dataModel/yakHerdInventory.js` | | **列表** | 年份、**月份**、乡镇筛选;**进入页面不默认年月**;列:所属年份、所属月份、乡镇、村居数、牧户数、存栏数、公牛/母牛存栏数 | | **详情** | 抽屉/对话框只读展示全部字段 | | **表单** | 抽屉或对话框;年份/月份/乡镇下拉;新增默认**当前年月**;数字 `el-input-number` `:min="0"` `:precision="0"` | | **导入** | 先选「填报年份」「填报月份」(默认当前年月)→ 上传 → 展示导入结果摘要 | | **模板下载** | `GET /importTemplate`;空白模板 | | **导出** | 弹窗选年月(默认当前年月)→ `GET /export?statYear=&statMonth=` | --- ## 5. 菜单与权限(示例) | 类型 | 名称 | 权限标识 | | --- | --- | --- | | 菜单 | 牦牛存栏数据填报 | `dataModel:yakHerdInventory:list` | | 按钮 | 查询 | `dataModel:yakHerdInventory:query` | | 按钮 | 新增 | `dataModel:yakHerdInventory:add` | | 按钮 | 修改 | `dataModel:yakHerdInventory:edit` | | 按钮 | 删除 | `dataModel:yakHerdInventory:remove` | | 按钮 | 导入 | `dataModel:yakHerdInventory:import` | | 按钮 | 导出 | `dataModel:yakHerdInventory:export` | 挂载目录:**产业数据模型及服务**。菜单 SQL 见 `sql/biz_sys_menu_yak_herd_inventory.sql`。 --- ## 6. 与养殖产业数据看板 / 大屏的关系 「养殖产业数据」看板、大屏首页产业总览中的**全县存栏合计**、地图乡镇存栏标注,读取 `biz_yak_herd_inventory` 时: | 场景 | 口径 | | --- | --- | | 年度全县合计 | `stat_year = 当前自然年 − 1` 且 **`stat_month = 12`**(`YakHerdInventoryRules.YEAR_END_STAT_MONTH`) | | 地图乡镇存栏 | 同上:`herdStatYear = 当前年 − 1`,`herdStatMonth = 12` | 避免同一年多个月份重复累加。月度明细在本模块列表按年月筛选查看。 --- ## 7. 交付清单 - [x] `sql/biz_yak_herd_inventory.sql` - [x] `BizYakHerdInventory` Domain / Mapper / XML / Service / Controller - [x] `YakHerdInventoryExcelParser` + `YakHerdInventoryExcelExporter` - [x] `TownDeptSupport` 乡镇字典 - [x] 前端列表/表单/导入/导出 - [x] 单元测试 + 导入/导出集成测试(`yak_herd_inventory_2026.xlsx`) --- ## 8. 修订记录 | 版本 | 说明 | | --- | --- | | 1.0 | 初稿:表结构、CRUD、导入、sys_dept 乡镇、年份前推 5 年 | | 1.1 | 增加预留字段 `village_dept_id`(非必填) | | 1.2 | 实现按年份导出全县乡镇 Excel;列映射与代码对齐;交付清单勾选 | | 1.3 | 导入 upsert:`selectByYearAndTown` + `updateBizYakHerdInventory`;支持重复导入 | | 1.4 | 取消逻辑删除:无 `del_flag`;`deleteBizYakHerdInventoryByIds` 物理删除;`uk_year_town` 唯一约束 | | 1.5 | 按年月填报:`stat_month`;`uk_year_month_town`;`YakHerdInventoryMonthSupport`;导入/导出/列表/CRUD 支持月份;大屏年度汇总取 12 月 |