소스 검색

三资具体问题上报

wwh 1 주 전
부모
커밋
9b3900b5d9

+ 25 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/controller/ResearchOpenApiController.java

@@ -16,6 +16,7 @@ import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenRe
16 16
 import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenVo;
17 17
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
18 18
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
19
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
19 20
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
20 21
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiSupport;
21 22
 
@@ -95,4 +96,28 @@ public class ResearchOpenApiController
95 96
             return ResearchOpenApiSupport.error(code, ex.getMessage());
96 97
         }
97 98
     }
99
+
100
+    /**
101
+     * 牦牛存栏数据列表(村级汇总)。
102
+     */
103
+    @GetMapping("/yak-herd-inventories")
104
+    public OpenApiResponse<OpenApiPageResult<ResearchYakHerdInventoryVo>> listYakHerdInventories(
105
+            @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
106
+            @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
107
+            @RequestParam(value = "statYear", required = false) Integer statYear,
108
+            @RequestParam(value = "statMonth", required = false) Integer statMonth,
109
+            @RequestParam(value = "townName", required = false) String townName,
110
+            @RequestParam(value = "villageName", required = false) String villageName)
111
+    {
112
+        try
113
+        {
114
+            return ResearchOpenApiSupport.success(researchOpenApiService.listYakHerdInventories(
115
+                    pageNum, pageSize, statYear, statMonth, townName, villageName));
116
+        }
117
+        catch (ServiceException ex)
118
+        {
119
+            int code = ex.getCode() != null ? ex.getCode() : 500;
120
+            return ResearchOpenApiSupport.error(code, ex.getMessage());
121
+        }
122
+    }
98 123
 }

+ 271 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchYakHerdInventoryVo.java

@@ -0,0 +1,271 @@
1
+package com.ruoyi.web.modules.industryservice.research.domain.vo;
2
+
3
+/**
4
+ * 科研开放接口 — 牦牛存栏填报记录(村级汇总,无个体敏感字段)。
5
+ */
6
+public class ResearchYakHerdInventoryVo
7
+{
8
+    private Long inventoryId;
9
+
10
+    private Integer statYear;
11
+
12
+    private Integer statMonth;
13
+
14
+    private String townName;
15
+
16
+    private String villageName;
17
+
18
+    private Integer villageCount;
19
+
20
+    private Integer herdsmanHouseholdCount;
21
+
22
+    private Integer breedingBullCount;
23
+
24
+    private Integer bullCastratedCount;
25
+
26
+    private Integer bullTotal;
27
+
28
+    private Integer cowYoungCount;
29
+
30
+    private Integer cowAdultCount;
31
+
32
+    private Integer cowTotal;
33
+
34
+    private Integer milkingCowCount;
35
+
36
+    private Integer fertileCowCount;
37
+
38
+    private Integer dairyTotal;
39
+
40
+    private Integer sheepTotal;
41
+
42
+    private Integer milkingSheepCount;
43
+
44
+    private Integer insuredYakCount;
45
+
46
+    private Integer yakTotal;
47
+
48
+    private Integer grandTotal;
49
+
50
+    private Integer yakSheepTotal;
51
+
52
+    public Long getInventoryId()
53
+    {
54
+        return inventoryId;
55
+    }
56
+
57
+    public void setInventoryId(Long inventoryId)
58
+    {
59
+        this.inventoryId = inventoryId;
60
+    }
61
+
62
+    public Integer getStatYear()
63
+    {
64
+        return statYear;
65
+    }
66
+
67
+    public void setStatYear(Integer statYear)
68
+    {
69
+        this.statYear = statYear;
70
+    }
71
+
72
+    public Integer getStatMonth()
73
+    {
74
+        return statMonth;
75
+    }
76
+
77
+    public void setStatMonth(Integer statMonth)
78
+    {
79
+        this.statMonth = statMonth;
80
+    }
81
+
82
+    public String getTownName()
83
+    {
84
+        return townName;
85
+    }
86
+
87
+    public void setTownName(String townName)
88
+    {
89
+        this.townName = townName;
90
+    }
91
+
92
+    public String getVillageName()
93
+    {
94
+        return villageName;
95
+    }
96
+
97
+    public void setVillageName(String villageName)
98
+    {
99
+        this.villageName = villageName;
100
+    }
101
+
102
+    public Integer getVillageCount()
103
+    {
104
+        return villageCount;
105
+    }
106
+
107
+    public void setVillageCount(Integer villageCount)
108
+    {
109
+        this.villageCount = villageCount;
110
+    }
111
+
112
+    public Integer getHerdsmanHouseholdCount()
113
+    {
114
+        return herdsmanHouseholdCount;
115
+    }
116
+
117
+    public void setHerdsmanHouseholdCount(Integer herdsmanHouseholdCount)
118
+    {
119
+        this.herdsmanHouseholdCount = herdsmanHouseholdCount;
120
+    }
121
+
122
+    public Integer getBreedingBullCount()
123
+    {
124
+        return breedingBullCount;
125
+    }
126
+
127
+    public void setBreedingBullCount(Integer breedingBullCount)
128
+    {
129
+        this.breedingBullCount = breedingBullCount;
130
+    }
131
+
132
+    public Integer getBullCastratedCount()
133
+    {
134
+        return bullCastratedCount;
135
+    }
136
+
137
+    public void setBullCastratedCount(Integer bullCastratedCount)
138
+    {
139
+        this.bullCastratedCount = bullCastratedCount;
140
+    }
141
+
142
+    public Integer getBullTotal()
143
+    {
144
+        return bullTotal;
145
+    }
146
+
147
+    public void setBullTotal(Integer bullTotal)
148
+    {
149
+        this.bullTotal = bullTotal;
150
+    }
151
+
152
+    public Integer getCowYoungCount()
153
+    {
154
+        return cowYoungCount;
155
+    }
156
+
157
+    public void setCowYoungCount(Integer cowYoungCount)
158
+    {
159
+        this.cowYoungCount = cowYoungCount;
160
+    }
161
+
162
+    public Integer getCowAdultCount()
163
+    {
164
+        return cowAdultCount;
165
+    }
166
+
167
+    public void setCowAdultCount(Integer cowAdultCount)
168
+    {
169
+        this.cowAdultCount = cowAdultCount;
170
+    }
171
+
172
+    public Integer getCowTotal()
173
+    {
174
+        return cowTotal;
175
+    }
176
+
177
+    public void setCowTotal(Integer cowTotal)
178
+    {
179
+        this.cowTotal = cowTotal;
180
+    }
181
+
182
+    public Integer getMilkingCowCount()
183
+    {
184
+        return milkingCowCount;
185
+    }
186
+
187
+    public void setMilkingCowCount(Integer milkingCowCount)
188
+    {
189
+        this.milkingCowCount = milkingCowCount;
190
+    }
191
+
192
+    public Integer getFertileCowCount()
193
+    {
194
+        return fertileCowCount;
195
+    }
196
+
197
+    public void setFertileCowCount(Integer fertileCowCount)
198
+    {
199
+        this.fertileCowCount = fertileCowCount;
200
+    }
201
+
202
+    public Integer getDairyTotal()
203
+    {
204
+        return dairyTotal;
205
+    }
206
+
207
+    public void setDairyTotal(Integer dairyTotal)
208
+    {
209
+        this.dairyTotal = dairyTotal;
210
+    }
211
+
212
+    public Integer getSheepTotal()
213
+    {
214
+        return sheepTotal;
215
+    }
216
+
217
+    public void setSheepTotal(Integer sheepTotal)
218
+    {
219
+        this.sheepTotal = sheepTotal;
220
+    }
221
+
222
+    public Integer getMilkingSheepCount()
223
+    {
224
+        return milkingSheepCount;
225
+    }
226
+
227
+    public void setMilkingSheepCount(Integer milkingSheepCount)
228
+    {
229
+        this.milkingSheepCount = milkingSheepCount;
230
+    }
231
+
232
+    public Integer getInsuredYakCount()
233
+    {
234
+        return insuredYakCount;
235
+    }
236
+
237
+    public void setInsuredYakCount(Integer insuredYakCount)
238
+    {
239
+        this.insuredYakCount = insuredYakCount;
240
+    }
241
+
242
+    public Integer getYakTotal()
243
+    {
244
+        return yakTotal;
245
+    }
246
+
247
+    public void setYakTotal(Integer yakTotal)
248
+    {
249
+        this.yakTotal = yakTotal;
250
+    }
251
+
252
+    public Integer getGrandTotal()
253
+    {
254
+        return grandTotal;
255
+    }
256
+
257
+    public void setGrandTotal(Integer grandTotal)
258
+    {
259
+        this.grandTotal = grandTotal;
260
+    }
261
+
262
+    public Integer getYakSheepTotal()
263
+    {
264
+        return yakSheepTotal;
265
+    }
266
+
267
+    public void setYakSheepTotal(Integer yakSheepTotal)
268
+    {
269
+        this.yakSheepTotal = yakSheepTotal;
270
+    }
271
+}

+ 10 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/service/IResearchOpenApiService.java

@@ -4,6 +4,7 @@ import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiPageResult;
4 4
 import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenVo;
5 5
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
6 6
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
7
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
7 8
 
8 9
 /**
9 10
  * 科研开放数据接口服务。
@@ -16,4 +17,13 @@ public interface IResearchOpenApiService
16 17
 
17 18
     OpenApiPageResult<ResearchYakAssetVo> listYakAssets(int pageNum, int pageSize,
18 19
             Integer assetStatus, String gender, Long pastureId);
20
+
21
+    /**
22
+     * 牦牛存栏填报列表。
23
+     *
24
+     * @param townName 所属乡镇名称模糊匹配
25
+     * @param villageName 所属村名称模糊匹配
26
+     */
27
+    OpenApiPageResult<ResearchYakHerdInventoryVo> listYakHerdInventories(int pageNum, int pageSize,
28
+            Integer statYear, Integer statMonth, String townName, String villageName);
19 29
 }

+ 34 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/service/impl/ResearchOpenApiServiceImpl.java

@@ -7,13 +7,17 @@ import org.springframework.stereotype.Service;
7 7
 import com.github.pagehelper.PageHelper;
8 8
 import com.github.pagehelper.PageInfo;
9 9
 import com.ruoyi.common.exception.ServiceException;
10
+import com.ruoyi.common.utils.StringUtils;
10 11
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
11 12
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
13
+import com.ruoyi.web.modules.industryservice.domain.BizYakHerdInventory;
12 14
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiPageResult;
15
+import com.ruoyi.web.modules.industryservice.mapper.BizYakHerdInventoryMapper;
13 16
 import com.ruoyi.web.modules.industryservice.research.config.ResearchOpenApiProperties;
14 17
 import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenVo;
15 18
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
16 19
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
20
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
17 21
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
18 22
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiSupport;
19 23
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiTokenService;
@@ -35,6 +39,9 @@ public class ResearchOpenApiServiceImpl implements IResearchOpenApiService
35 39
     @Autowired
36 40
     private IBizYakAssetService bizYakAssetService;
37 41
 
42
+    @Autowired
43
+    private BizYakHerdInventoryMapper bizYakHerdInventoryMapper;
44
+
38 45
     @Override
39 46
     public ResearchTokenVo authenticate(String appKey, String appSecret)
40 47
     {
@@ -76,6 +83,33 @@ public class ResearchOpenApiServiceImpl implements IResearchOpenApiService
76 83
                 rows.stream().map(ResearchOpenApiSupport::toYakAssetVo).collect(Collectors.toList()));
77 84
     }
78 85
 
86
+    @Override
87
+    public OpenApiPageResult<ResearchYakHerdInventoryVo> listYakHerdInventories(int pageNum, int pageSize,
88
+            Integer statYear, Integer statMonth, String townName, String villageName)
89
+    {
90
+        tokenService.assertEnabled();
91
+        int safePageNum = normalizePageNum(pageNum);
92
+        int safePageSize = normalizePageSize(pageSize);
93
+        BizYakHerdInventory query = new BizYakHerdInventory();
94
+        query.setStatYear(statYear);
95
+        query.setStatMonth(statMonth);
96
+        if (StringUtils.isNotEmpty(townName))
97
+        {
98
+            query.setTownName(townName.trim());
99
+        }
100
+        if (StringUtils.isNotEmpty(villageName))
101
+        {
102
+            query.setVillageName(villageName.trim());
103
+        }
104
+        // 开放接口无登录上下文,清空 dataScope,避免走填报部门权限
105
+        query.getParams().put("dataScope", "");
106
+        PageHelper.startPage(safePageNum, safePageSize);
107
+        List<BizYakHerdInventory> rows = bizYakHerdInventoryMapper.selectBizYakHerdInventoryList(query);
108
+        PageInfo<BizYakHerdInventory> page = new PageInfo<>(rows);
109
+        return toPageResult(safePageNum, safePageSize, page.getTotal(),
110
+                rows.stream().map(ResearchOpenApiSupport::toYakHerdInventoryVo).collect(Collectors.toList()));
111
+    }
112
+
79 113
     private int normalizePageNum(int pageNum)
80 114
     {
81 115
         return pageNum > 0 ? pageNum : 1;

+ 34 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/support/ResearchOpenApiSupport.java

@@ -4,9 +4,11 @@ import java.time.Instant;
4 4
 import java.util.UUID;
5 5
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
6 6
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
7
+import com.ruoyi.web.modules.industryservice.domain.BizYakHerdInventory;
7 8
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiResponse;
8 9
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
9 10
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
11
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
10 12
 
11 13
 /**
12 14
  * 科研开放接口响应组装与实体脱敏转换。
@@ -90,4 +92,36 @@ public final class ResearchOpenApiSupport
90 92
         vo.setMotherSubjectCode(ResearchOpenApiMasking.pseudonymCode(row.getMotherYakNo(), "YM"));
91 93
         return vo;
92 94
     }
95
+
96
+    public static ResearchYakHerdInventoryVo toYakHerdInventoryVo(BizYakHerdInventory row)
97
+    {
98
+        if (row == null)
99
+        {
100
+            return null;
101
+        }
102
+        ResearchYakHerdInventoryVo vo = new ResearchYakHerdInventoryVo();
103
+        vo.setInventoryId(row.getId());
104
+        vo.setStatYear(row.getStatYear());
105
+        vo.setStatMonth(row.getStatMonth());
106
+        vo.setTownName(row.getTownName());
107
+        vo.setVillageName(row.getVillageName());
108
+        vo.setVillageCount(row.getVillageCount());
109
+        vo.setHerdsmanHouseholdCount(row.getHerdsmanHouseholdCount());
110
+        vo.setBreedingBullCount(row.getBreedingBullCount());
111
+        vo.setBullCastratedCount(row.getBullCastratedCount());
112
+        vo.setBullTotal(row.getBullTotal());
113
+        vo.setCowYoungCount(row.getCowYoungCount());
114
+        vo.setCowAdultCount(row.getCowAdultCount());
115
+        vo.setCowTotal(row.getCowTotal());
116
+        vo.setMilkingCowCount(row.getMilkingCowCount());
117
+        vo.setFertileCowCount(row.getFertileCowCount());
118
+        vo.setDairyTotal(row.getDairyTotal());
119
+        vo.setSheepTotal(row.getSheepTotal());
120
+        vo.setMilkingSheepCount(row.getMilkingSheepCount());
121
+        vo.setInsuredYakCount(row.getInsuredYakCount());
122
+        vo.setYakTotal(row.getYakTotal());
123
+        vo.setGrandTotal(row.getGrandTotal());
124
+        vo.setYakSheepTotal(row.getYakSheepTotal());
125
+        return vo;
126
+    }
93 127
 }

+ 6 - 0
baqing-admin/src/main/resources/mapper/industryservice/BizYakHerdInventoryMapper.xml

@@ -108,6 +108,12 @@
108 108
             <if test="villageDeptId != null">
109 109
                 and h.village_dept_id = #{villageDeptId}
110 110
             </if>
111
+            <if test="townName != null and townName != ''">
112
+                and h.town_name like concat('%', #{townName}, '%')
113
+            </if>
114
+            <if test="villageName != null and villageName != ''">
115
+                and h.village_name like concat('%', #{villageName}, '%')
116
+            </if>
111 117
             ${params.dataScope}
112 118
         </where>
113 119
         order by h.stat_year desc, h.stat_month desc, d.order_num asc, v.order_num asc, h.id desc

+ 30 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/research/controller/ResearchOpenApiControllerApiTest.java

@@ -22,6 +22,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
22 22
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiPageResult;
23 23
 import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenVo;
24 24
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
25
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
25 26
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
26 27
 
27 28
 @ExtendWith(MockitoExtension.class)
@@ -79,4 +80,33 @@ class ResearchOpenApiControllerApiTest
79 80
                 .andExpect(jsonPath("$.code").value(200))
80 81
                 .andExpect(jsonPath("$.data.records[0].pastureName").value("测试牧场"));
81 82
     }
83
+
84
+    @Test
85
+    @DisplayName("GET /yak-herd-inventories 成功")
86
+    void listYakHerdInventories() throws Exception
87
+    {
88
+        ResearchYakHerdInventoryVo row = new ResearchYakHerdInventoryVo();
89
+        row.setInventoryId(1L);
90
+        row.setStatYear(2026);
91
+        row.setStatMonth(5);
92
+        row.setTownName("雅安镇");
93
+        row.setVillageName("荣嘎村");
94
+        OpenApiPageResult<ResearchYakHerdInventoryVo> page = new OpenApiPageResult<>();
95
+        page.setPageNum(1);
96
+        page.setPageSize(20);
97
+        page.setTotal(1L);
98
+        page.setRecords(Collections.singletonList(row));
99
+        when(researchOpenApiService.listYakHerdInventories(anyInt(), anyInt(), any(), any(), any(), any()))
100
+                .thenReturn(page);
101
+
102
+        mockMvc.perform(get("/open-api/v1/research/yak-herd-inventories")
103
+                .param("statYear", "2026")
104
+                .param("statMonth", "5")
105
+                .param("townName", "雅安")
106
+                .param("villageName", "荣嘎"))
107
+                .andExpect(status().isOk())
108
+                .andExpect(jsonPath("$.code").value(200))
109
+                .andExpect(jsonPath("$.data.records[0].townName").value("雅安镇"))
110
+                .andExpect(jsonPath("$.data.records[0].villageName").value("荣嘎村"));
111
+    }
82 112
 }

+ 17 - 0
doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口功能需求.md

@@ -28,6 +28,7 @@
28 28
 | 访问令牌 | 使用全局 `appKey` + `appSecret` 申请 Bearer Token |
29 29
 | 牧场列表 | 分页返回脱敏牧场数据 |
30 30
 | 牦牛资产档案列表 | 分页返回脱敏档案数据(种群健康研究字段) |
31
+| 牦牛存栏数据列表 | 分页返回村级存栏填报汇总 |
31 32
 
32 33
 **本期不实现:**
33 34
 
@@ -114,6 +115,20 @@ sequenceDiagram
114 115
 
115 116
 **不返回**:`yakNo`、耳标号、第三方 ID、登记人、照片 URL、精确 GPS、备注等。
116 117
 
118
+### 6.4 牦牛存栏数据列表
119
+
120
+| 项 | 说明 |
121
+| --- | --- |
122
+| 方法 | GET |
123
+| 路径 | `/yak-herd-inventories` |
124
+| 鉴权 | Bearer Token |
125
+| 分页 | 同 §6.2 |
126
+| 筛选 | `statYear`(所属年份)、`statMonth`(所属月份)、`townName`(所属乡镇模糊)、`villageName`(所属村模糊) |
127
+
128
+**返回字段**:`inventoryId`、`statYear`、`statMonth`、`townName`、`villageName`、畜群结构汇总数量字段(公牛/母牛/奶牛/羊/保险及各类合计等)。
129
+
130
+**不返回**:部门 ID、创建人、备注等内部字段。
131
+
117 132
 ---
118 133
 
119 134
 ## 7. 脱敏规则摘要
@@ -151,6 +166,7 @@ sequenceDiagram
151 166
 | --- | --- |
152 167
 | 牧场管理 | 数据源 `biz_pasture` |
153 168
 | 牦牛资产档案管理 | 数据源 `biz_yak_asset` |
169
+| 牦牛存栏数据填报 | 数据源 `biz_yak_herd_inventory` |
154 170
 | 若依权限体系 | **无**关联;开放接口走独立 Token |
155 171
 
156 172
 ---
@@ -162,5 +178,6 @@ sequenceDiagram
162 178
 | Token 认证 | §4、§5、§6.1 |
163 179
 | 牧场列表 | §6.2、§7 |
164 180
 | 牦牛档案列表 | §6.3、§7 |
181
+| 牦牛存栏列表 | §6.4 |
165 182
 | 脱敏合规 | §7 |
166 183
 | 本期不实现 | §3 |

+ 30 - 2
doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口技术方案.md

@@ -9,7 +9,7 @@
9 9
 | 项 | 说明 |
10 10
 | --- | --- |
11 11
 | **后端** | RuoYi **v3.9.2**(**springboot2** 分支):JDK 8、Spring MVC、MyBatis |
12
-| **数据库** | 只读查询既有表 `biz_pasture`、`biz_yak_asset` |
12
+| **数据库** | 只读查询既有表 `biz_pasture`、`biz_yak_asset`、`biz_yak_herd_inventory` |
13 13
 | **鉴权** | 独立 JWT + Servlet Filter;`@Anonymous` 绕过若依登录,Filter 校验 Bearer Token |
14 14
 | **响应** | `OpenApiResponse<T>` + `OpenApiPageResult<T>`(与第三方 farming OpenAPI 同结构) |
15 15
 
@@ -122,6 +122,27 @@ Authorization: Bearer <accessToken>
122 122
 | `realtimeTemp` / `realtimeSteps` / `envTemp` | number | IoT 快照 |
123 123
 | `fatherSubjectCode` / `motherSubjectCode` | string | 系谱 pseudonym |
124 124
 
125
+### 3.4 牦牛存栏数据列表
126
+
127
+```
128
+GET /open-api/v1/research/yak-herd-inventories?pageNum=1&pageSize=20&statYear=2026&statMonth=5&townName=&villageName=
129
+Authorization: Bearer <accessToken>
130
+```
131
+
132
+**查询参数:**
133
+
134
+| 参数 | 说明 |
135
+| --- | --- |
136
+| `statYear` | 所属年份(精确) |
137
+| `statMonth` | 所属月份(精确) |
138
+| `townName` | 所属乡镇名称模糊匹配 |
139
+| `villageName` | 所属村名称模糊匹配 |
140
+| `pageNum` / `pageSize` | 分页,同牧场列表 |
141
+
142
+**`records[]` 元素(ResearchYakHerdInventoryVo):** 含 `inventoryId`、`statYear`、`statMonth`、`townName`、`villageName` 及畜群汇总数量字段;**不含** `townDeptId` / `villageDeptId`、操作人、备注。
143
+
144
+实现说明:直接查 `BizYakHerdInventoryMapper`(**不**走填报 Service 的部门数据权限)。
145
+
125 146
 ---
126 147
 
127 148
 ## 4. 鉴权流程
@@ -143,8 +164,9 @@ Token 申请:`appKey`/`appSecret` 与配置项精确匹配后签发 JWT(HS51
143 164
 | --- | --- | --- | --- |
144 165
 | 牧场列表 | `IBizPastureService.selectBizPastureList` | `biz_pasture` | `del_flag=0` |
145 166
 | 档案列表 | `IBizYakAssetService.selectBizYakAssetList` | `biz_yak_asset` | `del_flag=0`;支持 `assetStatus`、`gender`、`pastureId` |
167
+| 存栏列表 | `BizYakHerdInventoryMapper.selectBizYakHerdInventoryList` | `biz_yak_herd_inventory` | 年/月精确;乡镇/村名模糊;清空 `params.dataScope` |
146 168
 
147
-实体 → VO:`ResearchOpenApiSupport.toPastureVo` / `toYakAssetVo`(内部调用 `ResearchOpenApiMasking`)
169
+实体 → VO:`ResearchOpenApiSupport.toPastureVo` / `toYakAssetVo` / `toYakHerdInventoryVo`
148 170
 
149 171
 ---
150 172
 
@@ -189,6 +211,10 @@ curl "http://localhost:8010/open-api/v1/research/pastures?pageNum=1&pageSize=10"
189 211
 # 3. 牦牛档案
190 212
 curl "http://localhost:8010/open-api/v1/research/yak-assets?pageNum=1&pageSize=10&assetStatus=1" \
191 213
   -H "Authorization: Bearer <accessToken>"
214
+
215
+# 4. 牦牛存栏
216
+curl "http://localhost:8010/open-api/v1/research/yak-herd-inventories?pageNum=1&pageSize=10&statYear=2026&townName=雅安" \
217
+  -H "Authorization: Bearer <accessToken>"
192 218
 ```
193 219
 
194 220
 ---
@@ -210,6 +236,7 @@ curl "http://localhost:8010/open-api/v1/research/yak-assets?pageNum=1&pageSize=1
210 236
 | §5 认证 | **§2** 配置、**§4** Filter + TokenService |
211 237
 | §6.2 牧场 | **§3.2**、**§5** BizPasture |
212 238
 | §6.3 档案 | **§3.3**、**§5** BizYakAsset |
239
+| §6.4 存栏 | **§3.4**、**§5** BizYakHerdInventory |
213 240
 | §7 脱敏 | **§6** Masking |
214 241
 | §3 不实现机构区分 | 单一 app-key/secret,JWT 无 clientId |
215 242
 
@@ -219,4 +246,5 @@ curl "http://localhost:8010/open-api/v1/research/yak-assets?pageNum=1&pageSize=1
219 246
 
220 247
 | 版本 | 日期 | 说明 |
221 248
 | --- | --- | --- |
249
+| 1.1 | 2026-07-22 | 新增牦牛存栏开放列表(年/月/乡镇村模糊+分页) |
222 250
 | 1.0 | 2026-07-04 | 初版:Token + 牧场/档案脱敏列表;全局一对凭证 |

+ 5 - 1
doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口测试用例.md

@@ -43,6 +43,8 @@
43 43
 | KYKF-API-010 | 分页 | pageSize 超限 | 接口测试 | curl | 技术§2 | — | GET `pageSize=500` | `code=400` |
44 44
 | KYKF-API-011 | 开关 | enabled=false | 接口测试 | curl | §5 | 关闭 enabled 重启 | POST `/auth/token` | HTTP 503 或 `code=503` |
45 45
 | KYKF-API-012 | 脱敏 | 响应无敏感原文字段 | 接口测试 | curl | §7 | 库含手机/编号 | GET 两接口 | JSON 无 `yakNo`、`earTagNumber`、`externalId`、`detailAddress` |
46
+| KYKF-API-013 | 存栏 | 分页列表 | 接口测试 | curl | §6.4 | 有效 Token;库有存栏数据 | GET `/yak-herd-inventories?pageNum=1&pageSize=10` | `code=200`;含 `statYear`/`townName` 等;无部门 ID |
47
+| KYKF-API-014 | 存栏 | 年/月/乡镇村筛选 | 接口测试 | curl | §6.4 | 已知年月与乡镇名片段 | GET `statYear=&statMonth=&townName=&villageName=` | 条件生效;名称模糊匹配 |
46 48
 
47 49
 ---
48 50
 
@@ -53,11 +55,12 @@
53 55
 | §5 Token 认证 | UT-005~006 | API-001~004、011 |
54 56
 | §6.2 牧场列表 | UT-003 | API-005~006 |
55 57
 | §6.3 档案列表 | UT-004 | API-007~009 |
58
+| §6.4 存栏列表 | — | API-013~014 |
56 59
 | §7 脱敏 | UT-001~004 | API-012 |
57 60
 | §2 分页 | — | API-010 |
58 61
 | §3 无机构区分 | UT-005 | API-001(单一凭证) |
59 62
 
60
-**用例统计**:单元 **6**、接口 **12**,合计 **18** 条。
63
+**用例统计**:单元 **6**、接口 **14**,合计 **20** 条。
61 64
 
62 65
 ---
63 66
 
@@ -72,6 +75,7 @@ TOKEN=$(curl -s -X POST "$BASE/auth/token" \
72 75
 
73 76
 curl -s "$BASE/pastures?pageNum=1&pageSize=5" -H "Authorization: Bearer $TOKEN"
74 77
 curl -s "$BASE/yak-assets?pageNum=1&pageSize=5&assetStatus=1" -H "Authorization: Bearer $TOKEN"
78
+curl -s "$BASE/yak-herd-inventories?pageNum=1&pageSize=5&statYear=2026" -H "Authorization: Bearer $TOKEN"
75 79
 ```
76 80
 
77 81
 (Windows 环境可用 PowerShell `Invoke-RestMethod`,见技术方案 **§8**。)