wwh преди 2 седмици
родител
ревизия
c379cea10e
променени са 14 файла, в които са добавени 438 реда и са изтрити 7 реда
  1. 25 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/controller/ResearchOpenApiController.java
  2. 2 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/dto/ResearchTokenVo.java
  3. 8 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchPastureVo.java
  4. 15 4
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchYakAssetVo.java
  5. 22 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchYakHerdInventoryVo.java
  6. 215 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchYakOutboundReportVo.java
  7. 10 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/service/IResearchOpenApiService.java
  8. 33 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/service/impl/ResearchOpenApiServiceImpl.java
  9. 28 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/support/ResearchOpenApiSupport.java
  10. 6 0
      baqing-admin/src/main/resources/mapper/industryservice/BizYakOutboundReportMapper.xml
  11. 30 0
      baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/research/controller/ResearchOpenApiControllerApiTest.java
  12. 17 0
      doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口功能需求.md
  13. 22 2
      doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口技术方案.md
  14. 5 1
      doc/产业数据模型及服务/科研开放数据接口/科研开放数据接口测试用例.md

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

@@ -17,6 +17,7 @@ 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 19
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
20
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakOutboundReportVo;
20 21
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
21 22
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiSupport;
22 23
 
@@ -120,4 +121,28 @@ public class ResearchOpenApiController
120 121
             return ResearchOpenApiSupport.error(code, ex.getMessage());
121 122
         }
122 123
     }
124
+
125
+    /**
126
+     * 牦牛出栏数据列表(村级汇总)。
127
+     */
128
+    @GetMapping("/yak-outbound-reports")
129
+    public OpenApiResponse<OpenApiPageResult<ResearchYakOutboundReportVo>> listYakOutboundReports(
130
+            @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
131
+            @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
132
+            @RequestParam(value = "statYear", required = false) Integer statYear,
133
+            @RequestParam(value = "statMonth", required = false) Integer statMonth,
134
+            @RequestParam(value = "townName", required = false) String townName,
135
+            @RequestParam(value = "villageName", required = false) String villageName)
136
+    {
137
+        try
138
+        {
139
+            return ResearchOpenApiSupport.success(researchOpenApiService.listYakOutboundReports(
140
+                    pageNum, pageSize, statYear, statMonth, townName, villageName));
141
+        }
142
+        catch (ServiceException ex)
143
+        {
144
+            int code = ex.getCode() != null ? ex.getCode() : 500;
145
+            return ResearchOpenApiSupport.error(code, ex.getMessage());
146
+        }
147
+    }
123 148
 }

+ 2 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/dto/ResearchTokenVo.java

@@ -5,8 +5,10 @@ package com.ruoyi.web.modules.industryservice.research.domain.dto;
5 5
  */
6 6
 public class ResearchTokenVo
7 7
 {
8
+    /** 访问令牌(Bearer Token) */
8 9
     private String accessToken;
9 10
 
11
+    /** 令牌类型,固定为 Bearer */
10 12
     private String tokenType;
11 13
 
12 14
     /** 有效期(秒) */

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

@@ -8,12 +8,16 @@ public class ResearchPastureVo
8 8
     /** 本地牧场 ID(不含第三方编号) */
9 9
     private Long pastureId;
10 10
 
11
+    /** 牧场名称 */
11 12
     private String pastureName;
12 13
 
14
+    /** 牧场类型 */
13 15
     private String farmType;
14 16
 
17
+    /** 所属区县 */
15 18
     private String county;
16 19
 
20
+    /** 所属乡镇 */
17 21
     private String town;
18 22
 
19 23
     /** 区域摘要(区县+乡镇,不含详细地址) */
@@ -25,12 +29,16 @@ public class ResearchPastureVo
25 29
     /** 经度(约百米精度) */
26 30
     private Double longitude;
27 31
 
32
+    /** 建筑/占地面积 */
28 33
     private String floorArea;
29 34
 
35
+    /** 养殖规模 */
30 36
     private String scaleBreeding;
31 37
 
38
+    /** 养殖品种 */
32 39
     private String breedSpecies;
33 40
 
41
+    /** 简介 */
34 42
     private String introduction;
35 43
 
36 44
     /** 负责人(脱敏) */

+ 15 - 4
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/research/domain/vo/ResearchYakAssetVo.java

@@ -7,35 +7,46 @@ import java.math.BigDecimal;
7 7
  */
8 8
 public class ResearchYakAssetVo
9 9
 {
10
-    /** 科研主体 pseudonym,非明文牦牛编号 */
10
+    /** 科研主体码(pseudonym,非明文牦牛编号) */
11 11
     private String subjectCode;
12 12
 
13
+    /** 所属牧场名称 */
13 14
     private String pastureName;
14 15
 
16
+    /** 所属圈舍名称 */
15 17
     private String enclosureName;
16 18
 
19
+    /** 性别(公/母) */
17 20
     private String gender;
18 21
 
22
+    /** 品种 */
19 23
     private String cattleVariety;
20 24
 
25
+    /** 月龄 */
21 26
     private Integer ageMonths;
22 27
 
23 28
     /** 出生年(不含具体日) */
24 29
     private Integer birthYear;
25 30
 
26
-    /** 出生月 1~12 */
31
+    /** 出生月(1~12) */
27 32
     private Integer birthMonth;
28 33
 
34
+    /** 资产状态:1正常 2死淘 3丢失 4出栏 */
29 35
     private Integer assetStatus;
30 36
 
37
+    /** 资产状态文案 */
31 38
     private String assetStatusLabel;
32 39
 
40
+    /** 入栏体重(公斤) */
33 41
     private BigDecimal entryWeightKg;
34 42
 
43
+    /** 来源 */
35 44
     private String source;
36 45
 
46
+    /** 养殖方式 */
37 47
     private String breedingMethod;
38 48
 
49
+    /** 入栏周期 */
39 50
     private String entryCycle;
40 51
 
41 52
     /** 体温快照(℃) */
@@ -47,10 +58,10 @@ public class ResearchYakAssetVo
47 58
     /** 环境温度快照(℃) */
48 59
     private BigDecimal envTemp;
49 60
 
50
-    /** 父系 pseudonym */
61
+    /** 父系科研主体码(pseudonym) */
51 62
     private String fatherSubjectCode;
52 63
 
53
-    /** 母系 pseudonym */
64
+    /** 母系科研主体码(pseudonym) */
54 65
     private String motherSubjectCode;
55 66
 
56 67
     public String getSubjectCode()

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

@@ -5,48 +5,70 @@ package com.ruoyi.web.modules.industryservice.research.domain.vo;
5 5
  */
6 6
 public class ResearchYakHerdInventoryVo
7 7
 {
8
+    /** 存栏记录 ID */
8 9
     private Long inventoryId;
9 10
 
11
+    /** 所属年份 */
10 12
     private Integer statYear;
11 13
 
14
+    /** 所属月份(1~12) */
12 15
     private Integer statMonth;
13 16
 
17
+    /** 所属乡镇名称 */
14 18
     private String townName;
15 19
 
20
+    /** 所属村名称 */
16 21
     private String villageName;
17 22
 
23
+    /** 村居数 */
18 24
     private Integer villageCount;
19 25
 
26
+    /** 牧户数 */
20 27
     private Integer herdsmanHouseholdCount;
21 28
 
29
+    /** 种公牛存栏数 */
22 30
     private Integer breedingBullCount;
23 31
 
32
+    /** 公牛/阉畜存栏数 */
24 33
     private Integer bullCastratedCount;
25 34
 
35
+    /** 公牛总存栏数(种公牛 + 公牛/阉畜) */
26 36
     private Integer bullTotal;
27 37
 
38
+    /** 0~1.5 岁幼畜存栏数(不包括 1.5 岁) */
28 39
     private Integer cowYoungCount;
29 40
 
41
+    /** 1.5 岁以上成畜存栏数(包括 1.5 岁) */
30 42
     private Integer cowAdultCount;
31 43
 
44
+    /** 母牛总存栏数(幼畜 + 成畜) */
32 45
     private Integer cowTotal;
33 46
 
47
+    /** 产奶母牛存栏数 */
34 48
     private Integer milkingCowCount;
35 49
 
50
+    /** 能繁母牛存栏数 */
36 51
     private Integer fertileCowCount;
37 52
 
53
+    /** 奶牛总存栏数(产奶母牛 + 能繁母牛) */
38 54
     private Integer dairyTotal;
39 55
 
56
+    /** 羊总存栏数 */
40 57
     private Integer sheepTotal;
41 58
 
59
+    /** 产奶羊存栏数 */
42 60
     private Integer milkingSheepCount;
43 61
 
62
+    /** 保险牦牛存栏数 */
44 63
     private Integer insuredYakCount;
45 64
 
65
+    /** 牦牛总存栏数(公牛 + 母牛 + 保险) */
46 66
     private Integer yakTotal;
47 67
 
68
+    /** 总存栏数(牦牛 + 奶牛 + 羊) */
48 69
     private Integer grandTotal;
49 70
 
71
+    /** 总量(牦牛 + 羊) */
50 72
     private Integer yakSheepTotal;
51 73
 
52 74
     public Long getInventoryId()

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

@@ -0,0 +1,215 @@
1
+package com.ruoyi.web.modules.industryservice.research.domain.vo;
2
+
3
+/**
4
+ * 科研开放接口 — 牦牛出栏填报记录(村级汇总,无个体敏感字段)。
5
+ */
6
+public class ResearchYakOutboundReportVo
7
+{
8
+    /** 出栏记录 ID */
9
+    private Long outboundId;
10
+
11
+    /** 所属年份 */
12
+    private Integer statYear;
13
+
14
+    /** 所属月份(1~12) */
15
+    private Integer statMonth;
16
+
17
+    /** 所属乡镇名称 */
18
+    private String townName;
19
+
20
+    /** 所属村名称 */
21
+    private String villageName;
22
+
23
+    /** 农牧户数 */
24
+    private Integer farmerHouseholdCount;
25
+
26
+    /** 农牧人数 */
27
+    private Integer farmerPopulationCount;
28
+
29
+    /** 牦牛出栏数 */
30
+    private Integer yakOutboundCount;
31
+
32
+    /** 黄牛/奶牛/犏牛出栏数 */
33
+    private Integer cattleOutboundCount;
34
+
35
+    /** 绵羊出栏数 */
36
+    private Integer sheepOutboundCount;
37
+
38
+    /** 山羊出栏数 */
39
+    private Integer goatOutboundCount;
40
+
41
+    /** 马骡出栏数 */
42
+    private Integer horseMuleOutboundCount;
43
+
44
+    /** 驴出栏数 */
45
+    private Integer donkeyOutboundCount;
46
+
47
+    /** 自食牛数 */
48
+    private Integer selfConsumptionCattleCount;
49
+
50
+    /** 自食绵羊数 */
51
+    private Integer selfConsumptionSheepCount;
52
+
53
+    /** 自食山羊数 */
54
+    private Integer selfConsumptionGoatCount;
55
+
56
+    public Long getOutboundId()
57
+    {
58
+        return outboundId;
59
+    }
60
+
61
+    public void setOutboundId(Long outboundId)
62
+    {
63
+        this.outboundId = outboundId;
64
+    }
65
+
66
+    public Integer getStatYear()
67
+    {
68
+        return statYear;
69
+    }
70
+
71
+    public void setStatYear(Integer statYear)
72
+    {
73
+        this.statYear = statYear;
74
+    }
75
+
76
+    public Integer getStatMonth()
77
+    {
78
+        return statMonth;
79
+    }
80
+
81
+    public void setStatMonth(Integer statMonth)
82
+    {
83
+        this.statMonth = statMonth;
84
+    }
85
+
86
+    public String getTownName()
87
+    {
88
+        return townName;
89
+    }
90
+
91
+    public void setTownName(String townName)
92
+    {
93
+        this.townName = townName;
94
+    }
95
+
96
+    public String getVillageName()
97
+    {
98
+        return villageName;
99
+    }
100
+
101
+    public void setVillageName(String villageName)
102
+    {
103
+        this.villageName = villageName;
104
+    }
105
+
106
+    public Integer getFarmerHouseholdCount()
107
+    {
108
+        return farmerHouseholdCount;
109
+    }
110
+
111
+    public void setFarmerHouseholdCount(Integer farmerHouseholdCount)
112
+    {
113
+        this.farmerHouseholdCount = farmerHouseholdCount;
114
+    }
115
+
116
+    public Integer getFarmerPopulationCount()
117
+    {
118
+        return farmerPopulationCount;
119
+    }
120
+
121
+    public void setFarmerPopulationCount(Integer farmerPopulationCount)
122
+    {
123
+        this.farmerPopulationCount = farmerPopulationCount;
124
+    }
125
+
126
+    public Integer getYakOutboundCount()
127
+    {
128
+        return yakOutboundCount;
129
+    }
130
+
131
+    public void setYakOutboundCount(Integer yakOutboundCount)
132
+    {
133
+        this.yakOutboundCount = yakOutboundCount;
134
+    }
135
+
136
+    public Integer getCattleOutboundCount()
137
+    {
138
+        return cattleOutboundCount;
139
+    }
140
+
141
+    public void setCattleOutboundCount(Integer cattleOutboundCount)
142
+    {
143
+        this.cattleOutboundCount = cattleOutboundCount;
144
+    }
145
+
146
+    public Integer getSheepOutboundCount()
147
+    {
148
+        return sheepOutboundCount;
149
+    }
150
+
151
+    public void setSheepOutboundCount(Integer sheepOutboundCount)
152
+    {
153
+        this.sheepOutboundCount = sheepOutboundCount;
154
+    }
155
+
156
+    public Integer getGoatOutboundCount()
157
+    {
158
+        return goatOutboundCount;
159
+    }
160
+
161
+    public void setGoatOutboundCount(Integer goatOutboundCount)
162
+    {
163
+        this.goatOutboundCount = goatOutboundCount;
164
+    }
165
+
166
+    public Integer getHorseMuleOutboundCount()
167
+    {
168
+        return horseMuleOutboundCount;
169
+    }
170
+
171
+    public void setHorseMuleOutboundCount(Integer horseMuleOutboundCount)
172
+    {
173
+        this.horseMuleOutboundCount = horseMuleOutboundCount;
174
+    }
175
+
176
+    public Integer getDonkeyOutboundCount()
177
+    {
178
+        return donkeyOutboundCount;
179
+    }
180
+
181
+    public void setDonkeyOutboundCount(Integer donkeyOutboundCount)
182
+    {
183
+        this.donkeyOutboundCount = donkeyOutboundCount;
184
+    }
185
+
186
+    public Integer getSelfConsumptionCattleCount()
187
+    {
188
+        return selfConsumptionCattleCount;
189
+    }
190
+
191
+    public void setSelfConsumptionCattleCount(Integer selfConsumptionCattleCount)
192
+    {
193
+        this.selfConsumptionCattleCount = selfConsumptionCattleCount;
194
+    }
195
+
196
+    public Integer getSelfConsumptionSheepCount()
197
+    {
198
+        return selfConsumptionSheepCount;
199
+    }
200
+
201
+    public void setSelfConsumptionSheepCount(Integer selfConsumptionSheepCount)
202
+    {
203
+        this.selfConsumptionSheepCount = selfConsumptionSheepCount;
204
+    }
205
+
206
+    public Integer getSelfConsumptionGoatCount()
207
+    {
208
+        return selfConsumptionGoatCount;
209
+    }
210
+
211
+    public void setSelfConsumptionGoatCount(Integer selfConsumptionGoatCount)
212
+    {
213
+        this.selfConsumptionGoatCount = selfConsumptionGoatCount;
214
+    }
215
+}

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

@@ -5,6 +5,7 @@ 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 7
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
8
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakOutboundReportVo;
8 9
 
9 10
 /**
10 11
  * 科研开放数据接口服务。
@@ -26,4 +27,13 @@ public interface IResearchOpenApiService
26 27
      */
27 28
     OpenApiPageResult<ResearchYakHerdInventoryVo> listYakHerdInventories(int pageNum, int pageSize,
28 29
             Integer statYear, Integer statMonth, String townName, String villageName);
30
+
31
+    /**
32
+     * 牦牛出栏填报列表。
33
+     *
34
+     * @param townName 所属乡镇名称模糊匹配
35
+     * @param villageName 所属村名称模糊匹配
36
+     */
37
+    OpenApiPageResult<ResearchYakOutboundReportVo> listYakOutboundReports(int pageNum, int pageSize,
38
+            Integer statYear, Integer statMonth, String townName, String villageName);
29 39
 }

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

@@ -11,13 +11,16 @@ import com.ruoyi.common.utils.StringUtils;
11 11
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
12 12
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
13 13
 import com.ruoyi.web.modules.industryservice.domain.BizYakHerdInventory;
14
+import com.ruoyi.web.modules.industryservice.domain.BizYakOutboundReport;
14 15
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiPageResult;
15 16
 import com.ruoyi.web.modules.industryservice.mapper.BizYakHerdInventoryMapper;
17
+import com.ruoyi.web.modules.industryservice.mapper.BizYakOutboundReportMapper;
16 18
 import com.ruoyi.web.modules.industryservice.research.config.ResearchOpenApiProperties;
17 19
 import com.ruoyi.web.modules.industryservice.research.domain.dto.ResearchTokenVo;
18 20
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
19 21
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
20 22
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
23
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakOutboundReportVo;
21 24
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
22 25
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiSupport;
23 26
 import com.ruoyi.web.modules.industryservice.research.support.ResearchOpenApiTokenService;
@@ -42,6 +45,9 @@ public class ResearchOpenApiServiceImpl implements IResearchOpenApiService
42 45
     @Autowired
43 46
     private BizYakHerdInventoryMapper bizYakHerdInventoryMapper;
44 47
 
48
+    @Autowired
49
+    private BizYakOutboundReportMapper bizYakOutboundReportMapper;
50
+
45 51
     @Override
46 52
     public ResearchTokenVo authenticate(String appKey, String appSecret)
47 53
     {
@@ -110,6 +116,33 @@ public class ResearchOpenApiServiceImpl implements IResearchOpenApiService
110 116
                 rows.stream().map(ResearchOpenApiSupport::toYakHerdInventoryVo).collect(Collectors.toList()));
111 117
     }
112 118
 
119
+    @Override
120
+    public OpenApiPageResult<ResearchYakOutboundReportVo> listYakOutboundReports(int pageNum, int pageSize,
121
+            Integer statYear, Integer statMonth, String townName, String villageName)
122
+    {
123
+        tokenService.assertEnabled();
124
+        int safePageNum = normalizePageNum(pageNum);
125
+        int safePageSize = normalizePageSize(pageSize);
126
+        BizYakOutboundReport query = new BizYakOutboundReport();
127
+        query.setStatYear(statYear);
128
+        query.setStatMonth(statMonth);
129
+        if (StringUtils.isNotEmpty(townName))
130
+        {
131
+            query.setTownName(townName.trim());
132
+        }
133
+        if (StringUtils.isNotEmpty(villageName))
134
+        {
135
+            query.setVillageName(villageName.trim());
136
+        }
137
+        // 开放接口无登录上下文,清空 dataScope,避免走填报部门权限
138
+        query.getParams().put("dataScope", "");
139
+        PageHelper.startPage(safePageNum, safePageSize);
140
+        List<BizYakOutboundReport> rows = bizYakOutboundReportMapper.selectBizYakOutboundReportList(query);
141
+        PageInfo<BizYakOutboundReport> page = new PageInfo<>(rows);
142
+        return toPageResult(safePageNum, safePageSize, page.getTotal(),
143
+                rows.stream().map(ResearchOpenApiSupport::toYakOutboundReportVo).collect(Collectors.toList()));
144
+    }
145
+
113 146
     private int normalizePageNum(int pageNum)
114 147
     {
115 148
         return pageNum > 0 ? pageNum : 1;

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

@@ -5,10 +5,12 @@ 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 7
 import com.ruoyi.web.modules.industryservice.domain.BizYakHerdInventory;
8
+import com.ruoyi.web.modules.industryservice.domain.BizYakOutboundReport;
8 9
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenApiResponse;
9 10
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchPastureVo;
10 11
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakAssetVo;
11 12
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
13
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakOutboundReportVo;
12 14
 
13 15
 /**
14 16
  * 科研开放接口响应组装与实体脱敏转换。
@@ -124,4 +126,30 @@ public final class ResearchOpenApiSupport
124 126
         vo.setYakSheepTotal(row.getYakSheepTotal());
125 127
         return vo;
126 128
     }
129
+
130
+    public static ResearchYakOutboundReportVo toYakOutboundReportVo(BizYakOutboundReport row)
131
+    {
132
+        if (row == null)
133
+        {
134
+            return null;
135
+        }
136
+        ResearchYakOutboundReportVo vo = new ResearchYakOutboundReportVo();
137
+        vo.setOutboundId(row.getId());
138
+        vo.setStatYear(row.getStatYear());
139
+        vo.setStatMonth(row.getStatMonth());
140
+        vo.setTownName(row.getTownName());
141
+        vo.setVillageName(row.getVillageName());
142
+        vo.setFarmerHouseholdCount(row.getFarmerHouseholdCount());
143
+        vo.setFarmerPopulationCount(row.getFarmerPopulationCount());
144
+        vo.setYakOutboundCount(row.getYakOutboundCount());
145
+        vo.setCattleOutboundCount(row.getCattleOutboundCount());
146
+        vo.setSheepOutboundCount(row.getSheepOutboundCount());
147
+        vo.setGoatOutboundCount(row.getGoatOutboundCount());
148
+        vo.setHorseMuleOutboundCount(row.getHorseMuleOutboundCount());
149
+        vo.setDonkeyOutboundCount(row.getDonkeyOutboundCount());
150
+        vo.setSelfConsumptionCattleCount(row.getSelfConsumptionCattleCount());
151
+        vo.setSelfConsumptionSheepCount(row.getSelfConsumptionSheepCount());
152
+        vo.setSelfConsumptionGoatCount(row.getSelfConsumptionGoatCount());
153
+        return vo;
154
+    }
127 155
 }

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

@@ -82,6 +82,12 @@
82 82
             <if test="villageDeptId != null">
83 83
                 and r.village_dept_id = #{villageDeptId}
84 84
             </if>
85
+            <if test="townName != null and townName != ''">
86
+                and r.town_name like concat('%', #{townName}, '%')
87
+            </if>
88
+            <if test="villageName != null and villageName != ''">
89
+                and r.village_name like concat('%', #{villageName}, '%')
90
+            </if>
85 91
             ${params.dataScope}
86 92
         </where>
87 93
         order by r.stat_year desc, r.stat_month desc, d.order_num asc, r.village_dept_id asc, r.id desc

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

@@ -23,6 +23,7 @@ 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 25
 import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakHerdInventoryVo;
26
+import com.ruoyi.web.modules.industryservice.research.domain.vo.ResearchYakOutboundReportVo;
26 27
 import com.ruoyi.web.modules.industryservice.research.service.IResearchOpenApiService;
27 28
 
28 29
 @ExtendWith(MockitoExtension.class)
@@ -109,4 +110,33 @@ class ResearchOpenApiControllerApiTest
109 110
                 .andExpect(jsonPath("$.data.records[0].townName").value("雅安镇"))
110 111
                 .andExpect(jsonPath("$.data.records[0].villageName").value("荣嘎村"));
111 112
     }
113
+
114
+    @Test
115
+    @DisplayName("GET /yak-outbound-reports 成功")
116
+    void listYakOutboundReports() throws Exception
117
+    {
118
+        ResearchYakOutboundReportVo row = new ResearchYakOutboundReportVo();
119
+        row.setOutboundId(1L);
120
+        row.setStatYear(2026);
121
+        row.setStatMonth(5);
122
+        row.setTownName("雅安镇");
123
+        row.setVillageName("荣嘎村");
124
+        OpenApiPageResult<ResearchYakOutboundReportVo> page = new OpenApiPageResult<>();
125
+        page.setPageNum(1);
126
+        page.setPageSize(20);
127
+        page.setTotal(1L);
128
+        page.setRecords(Collections.singletonList(row));
129
+        when(researchOpenApiService.listYakOutboundReports(anyInt(), anyInt(), any(), any(), any(), any()))
130
+                .thenReturn(page);
131
+
132
+        mockMvc.perform(get("/open-api/v1/research/yak-outbound-reports")
133
+                .param("statYear", "2026")
134
+                .param("statMonth", "5")
135
+                .param("townName", "雅安")
136
+                .param("villageName", "荣嘎"))
137
+                .andExpect(status().isOk())
138
+                .andExpect(jsonPath("$.code").value(200))
139
+                .andExpect(jsonPath("$.data.records[0].townName").value("雅安镇"))
140
+                .andExpect(jsonPath("$.data.records[0].villageName").value("荣嘎村"));
141
+    }
112 142
 }

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

@@ -29,6 +29,7 @@
29 29
 | 牧场列表 | 分页返回脱敏牧场数据 |
30 30
 | 牦牛资产档案列表 | 分页返回脱敏档案数据(种群健康研究字段) |
31 31
 | 牦牛存栏数据列表 | 分页返回村级存栏填报汇总 |
32
+| 牦牛出栏数据列表 | 分页返回村级出栏填报汇总 |
32 33
 
33 34
 **本期不实现:**
34 35
 
@@ -129,6 +130,20 @@ sequenceDiagram
129 130
 
130 131
 **不返回**:部门 ID、创建人、备注等内部字段。
131 132
 
133
+### 6.5 牦牛出栏数据列表
134
+
135
+| 项 | 说明 |
136
+| --- | --- |
137
+| 方法 | GET |
138
+| 路径 | `/yak-outbound-reports` |
139
+| 鉴权 | Bearer Token |
140
+| 分页 | 同 §6.2 |
141
+| 筛选 | `statYear`(所属年份)、`statMonth`(所属月份)、`townName`(所属乡镇模糊)、`villageName`(所属村模糊) |
142
+
143
+**返回字段**:`outboundId`、`statYear`、`statMonth`、`townName`、`villageName`、农牧户/人口及各类出栏数量(牦牛/牛/绵羊/山羊/马骡/驴、自食牛羊等)。
144
+
145
+**不返回**:部门 ID、创建人、备注等内部字段。
146
+
132 147
 ---
133 148
 
134 149
 ## 7. 脱敏规则摘要
@@ -167,6 +182,7 @@ sequenceDiagram
167 182
 | 牧场管理 | 数据源 `biz_pasture` |
168 183
 | 牦牛资产档案管理 | 数据源 `biz_yak_asset` |
169 184
 | 牦牛存栏数据填报 | 数据源 `biz_yak_herd_inventory` |
185
+| 牦牛出栏数据填报 | 数据源 `biz_yak_outbound_report` |
170 186
 | 若依权限体系 | **无**关联;开放接口走独立 Token |
171 187
 
172 188
 ---
@@ -179,5 +195,6 @@ sequenceDiagram
179 195
 | 牧场列表 | §6.2、§7 |
180 196
 | 牦牛档案列表 | §6.3、§7 |
181 197
 | 牦牛存栏列表 | §6.4 |
198
+| 牦牛出栏列表 | §6.5 |
182 199
 | 脱敏合规 | §7 |
183 200
 | 本期不实现 | §3 |

+ 22 - 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`、`biz_yak_herd_inventory` |
12
+| **数据库** | 只读查询既有表 `biz_pasture`、`biz_yak_asset`、`biz_yak_herd_inventory`、`biz_yak_outbound_report` |
13 13
 | **鉴权** | 独立 JWT + Servlet Filter;`@Anonymous` 绕过若依登录,Filter 校验 Bearer Token |
14 14
 | **响应** | `OpenApiResponse<T>` + `OpenApiPageResult<T>`(与第三方 farming OpenAPI 同结构) |
15 15
 
@@ -143,6 +143,19 @@ Authorization: Bearer <accessToken>
143 143
 
144 144
 实现说明:直接查 `BizYakHerdInventoryMapper`(**不**走填报 Service 的部门数据权限)。
145 145
 
146
+### 3.5 牦牛出栏数据列表
147
+
148
+```
149
+GET /open-api/v1/research/yak-outbound-reports?pageNum=1&pageSize=20&statYear=2026&statMonth=5&townName=&villageName=
150
+Authorization: Bearer <accessToken>
151
+```
152
+
153
+**查询参数:** 同 §3.4(`statYear` / `statMonth` / `townName` / `villageName` / 分页)。
154
+
155
+**`records[]` 元素(ResearchYakOutboundReportVo):** 含 `outboundId`、年月、乡镇村名及出栏汇总数量;**不含**部门 ID、操作人、备注。
156
+
157
+实现说明:直接查 `BizYakOutboundReportMapper`(清空 `params.dataScope`)。
158
+
146 159
 ---
147 160
 
148 161
 ## 4. 鉴权流程
@@ -165,8 +178,9 @@ Token 申请:`appKey`/`appSecret` 与配置项精确匹配后签发 JWT(HS51
165 178
 | 牧场列表 | `IBizPastureService.selectBizPastureList` | `biz_pasture` | `del_flag=0` |
166 179
 | 档案列表 | `IBizYakAssetService.selectBizYakAssetList` | `biz_yak_asset` | `del_flag=0`;支持 `assetStatus`、`gender`、`pastureId` |
167 180
 | 存栏列表 | `BizYakHerdInventoryMapper.selectBizYakHerdInventoryList` | `biz_yak_herd_inventory` | 年/月精确;乡镇/村名模糊;清空 `params.dataScope` |
181
+| 出栏列表 | `BizYakOutboundReportMapper.selectBizYakOutboundReportList` | `biz_yak_outbound_report` | 同上 |
168 182
 
169
-实体 → VO:`ResearchOpenApiSupport.toPastureVo` / `toYakAssetVo` / `toYakHerdInventoryVo`。
183
+实体 → VO:`ResearchOpenApiSupport.toPastureVo` / `toYakAssetVo` / `toYakHerdInventoryVo` / `toYakOutboundReportVo`
170 184
 
171 185
 ---
172 186
 
@@ -215,6 +229,10 @@ curl "http://localhost:8010/open-api/v1/research/yak-assets?pageNum=1&pageSize=1
215 229
 # 4. 牦牛存栏
216 230
 curl "http://localhost:8010/open-api/v1/research/yak-herd-inventories?pageNum=1&pageSize=10&statYear=2026&townName=雅安" \
217 231
   -H "Authorization: Bearer <accessToken>"
232
+
233
+# 5. 牦牛出栏
234
+curl "http://localhost:8010/open-api/v1/research/yak-outbound-reports?pageNum=1&pageSize=10&statYear=2026&townName=雅安" \
235
+  -H "Authorization: Bearer <accessToken>"
218 236
 ```
219 237
 
220 238
 ---
@@ -237,6 +255,7 @@ curl "http://localhost:8010/open-api/v1/research/yak-herd-inventories?pageNum=1&
237 255
 | §6.2 牧场 | **§3.2**、**§5** BizPasture |
238 256
 | §6.3 档案 | **§3.3**、**§5** BizYakAsset |
239 257
 | §6.4 存栏 | **§3.4**、**§5** BizYakHerdInventory |
258
+| §6.5 出栏 | **§3.5**、**§5** BizYakOutboundReport |
240 259
 | §7 脱敏 | **§6** Masking |
241 260
 | §3 不实现机构区分 | 单一 app-key/secret,JWT 无 clientId |
242 261
 
@@ -246,5 +265,6 @@ curl "http://localhost:8010/open-api/v1/research/yak-herd-inventories?pageNum=1&
246 265
 
247 266
 | 版本 | 日期 | 说明 |
248 267
 | --- | --- | --- |
268
+| 1.2 | 2026-07-22 | 新增牦牛出栏开放列表(年/月/乡镇村模糊+分页) |
249 269
 | 1.1 | 2026-07-22 | 新增牦牛存栏开放列表(年/月/乡镇村模糊+分页) |
250 270
 | 1.0 | 2026-07-04 | 初版:Token + 牧场/档案脱敏列表;全局一对凭证 |

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

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