Bladeren bron

牦牛资产档案

wwh 1 maand geleden
bovenliggende
commit
fade79e47d

+ 26 - 9
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/YakAssetGrowthLiveService.java

@@ -12,6 +12,7 @@ import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
12 12
 import com.ruoyi.web.modules.industryservice.domain.BizYakGrowthRecord;
13 13
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenWeighingDto;
14 14
 import com.ruoyi.web.modules.industryservice.domain.vo.YakAssetGrowthChartVo;
15
+import com.ruoyi.web.modules.industryservice.mapper.BizYakGrowthRecordMapper;
15 16
 import com.ruoyi.web.modules.industryservice.support.ThirdPartyFarmingProperties;
16 17
 import com.ruoyi.web.modules.industryservice.support.ThirdPartyIotWeighingClient;
17 18
 import com.ruoyi.web.modules.industryservice.support.YakAssetGrowthSupport;
@@ -21,7 +22,7 @@ import com.ruoyi.web.modules.industryservice.support.YakAssetRules;
21 22
  * 牦牛档案详情「生长性状」实时数据服务。
22 23
  * <p>
23 24
  * 体重来自 {@code GET /open-api/v1/iot/weighings}:
24
- * {@code cattleNo} = 档案 {@code external_id} 字符串
25
+ * {@code cattleId} = 档案 {@code external_id}
25 26
  * 称重开始/结束时间默认近一年至当前。
26 27
  * 采集时间 = {@code weighingTime}。日龄、体高、胸围、体长暂无接口,保持 null。
27 28
  * </p>
@@ -34,6 +35,9 @@ public class YakAssetGrowthLiveService
34 35
     @Autowired
35 36
     private ThirdPartyIotWeighingClient weighingClient;
36 37
 
38
+    @Autowired
39
+    private BizYakGrowthRecordMapper growthRecordMapper;
40
+
37 41
     @Autowired
38 42
     private ThirdPartyFarmingProperties properties;
39 43
 
@@ -44,12 +48,25 @@ public class YakAssetGrowthLiveService
44 48
                 safeFetchWeighings(asset, YakAssetRules.DEFAULT_GROWTH_LOOKBACK_DAYS));
45 49
     }
46 50
 
47
-    /** 生长曲线:仅体重有数据,其余指标返回空 */
51
+    /** 生长曲线:体重来自称重 OpenAPI;体高/胸围/体长读本地子表(暂无第三方接口) */
48 52
     public YakAssetGrowthChartVo fetchGrowthChart(BizYakAsset asset, String metric)
49 53
     {
50
-        List<BizYakGrowthRecord> rows = YakAssetGrowthSupport.toGrowthRecords(
51
-                safeFetchWeighings(asset, YakAssetRules.DEFAULT_GROWTH_LOOKBACK_DAYS));
52
-        return YakAssetGrowthSupport.toGrowthChart(rows, metric);
54
+        if (asset == null || StringUtils.isEmpty(metric))
55
+        {
56
+            return new YakAssetGrowthChartVo();
57
+        }
58
+        String m = metric.trim().toLowerCase();
59
+        List<BizYakGrowthRecord> rows;
60
+        if (YakAssetRules.METRIC_WEIGHT.equals(m))
61
+        {
62
+            rows = YakAssetGrowthSupport.toGrowthRecords(
63
+                    safeFetchWeighings(asset, YakAssetRules.DEFAULT_GROWTH_LOOKBACK_DAYS));
64
+        }
65
+        else
66
+        {
67
+            rows = growthRecordMapper.selectByYakAssetId(asset.getId());
68
+        }
69
+        return YakAssetGrowthSupport.toGrowthChart(rows, m);
53 70
     }
54 71
 
55 72
     private List<OpenWeighingDto> safeFetchWeighings(BizYakAsset asset, int lookbackDays)
@@ -58,8 +75,8 @@ public class YakAssetGrowthLiveService
58 75
         {
59 76
             return Collections.emptyList();
60 77
         }
61
-        String cattleNo = YakAssetGrowthSupport.resolveWeighingCattleNoParam(asset);
62
-        if (StringUtils.isEmpty(cattleNo))
78
+        Long cattleId = YakAssetGrowthSupport.resolveWeighingCattleIdParam(asset);
79
+        if (cattleId == null)
63 80
         {
64 81
             return Collections.emptyList();
65 82
         }
@@ -67,11 +84,11 @@ public class YakAssetGrowthLiveService
67 84
         Date startTime = new Date(endTime.getTime() - lookbackDays * 24L * 60 * 60 * 1000L);
68 85
         try
69 86
         {
70
-            return weighingClient.fetchByCattleNo(cattleNo, startTime, endTime);
87
+            return weighingClient.fetchByCattleId(cattleId, startTime, endTime);
71 88
         }
72 89
         catch (Exception ex)
73 90
         {
74
-            log.warn("读取牛只称重数据失败 cattleNo={}: {}", cattleNo, ex.getMessage());
91
+            log.warn("读取牛只称重数据失败 cattleId={}: {}", cattleId, ex.getMessage());
75 92
             return Collections.emptyList();
76 93
         }
77 94
     }

+ 4 - 4
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/IotSyncSupport.java

@@ -186,15 +186,15 @@ public final class IotSyncSupport
186 186
     }
187 187
 
188 188
     /**
189
-     * 牛只称重查询参数:{@code cattleNo} 必传(产品约定传档案 {@code external_id} 字符串);
189
+     * 牛只称重查询参数:{@code cattleId} 必传(档案 {@code external_id});
190 190
      * 可选 {@code startTime}/{@code endTime} 称重时间窗口。
191 191
      */
192
-    public static Map<String, String> buildWeighingQueryParams(String cattleNo, Date startTime, Date endTime)
192
+    public static Map<String, String> buildWeighingQueryParams(Long cattleId, Date startTime, Date endTime)
193 193
     {
194 194
         Map<String, String> params = new LinkedHashMap<>();
195
-        if (StringUtils.isNotEmpty(cattleNo))
195
+        if (cattleId != null)
196 196
         {
197
-            params.put("cattleNo", cattleNo.trim());
197
+            params.put("cattleId", String.valueOf(cattleId));
198 198
         }
199 199
         appendTimeRangeParams(params, startTime, endTime);
200 200
         return params;

+ 2 - 2
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/ThirdPartyIotWeighingClient.java

@@ -20,7 +20,7 @@ public interface ThirdPartyIotWeighingClient
20 20
     /**
21 21
      * 按牛只查询称重记录;可选称重时间窗口。
22 22
      *
23
-     * @param cattleNo 开放接口参数名 cattleNo;产品约定值为档案 {@code external_id} 的字符串形式
23
+     * @param cattleId 开放接口参数 cattleId,值为档案 {@code external_id}
24 24
      */
25
-    List<OpenWeighingDto> fetchByCattleNo(String cattleNo, Date startTime, Date endTime);
25
+    List<OpenWeighingDto> fetchByCattleId(Long cattleId, Date startTime, Date endTime);
26 26
 }

+ 8 - 20
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/ThirdPartyIotWeighingClientImpl.java

@@ -58,30 +58,30 @@ public class ThirdPartyIotWeighingClientImpl implements ThirdPartyIotWeighingCli
58 58
     }
59 59
 
60 60
     @Override
61
-    public List<OpenWeighingDto> fetchByCattleNo(String cattleNo, Date startTime, Date endTime)
61
+    public List<OpenWeighingDto> fetchByCattleId(Long cattleId, Date startTime, Date endTime)
62 62
     {
63 63
         if (!properties.isEnabled())
64 64
         {
65 65
             throw new ServiceException("第三方 IoT 称重同步未启用");
66 66
         }
67
-        if (com.ruoyi.common.utils.StringUtils.isEmpty(cattleNo))
67
+        if (cattleId == null)
68 68
         {
69
-            throw new ServiceException("牛只编号不能为空");
69
+            throw new ServiceException("牛只 ID 不能为空");
70 70
         }
71 71
         if ("http".equalsIgnoreCase(properties.getMode()))
72 72
         {
73
-            Map<String, String> params = IotSyncSupport.buildWeighingQueryParams(cattleNo, startTime, endTime);
73
+            Map<String, String> params = IotSyncSupport.buildWeighingQueryParams(cattleId, startTime, endTime);
74 74
             return ThirdPartyFarmingPageFetcher.fetchAllPages(properties, objectMapper, LIST_PATH,
75 75
                     new TypeReference<OpenApiResponse<OpenApiPageResult<OpenWeighingDto>>>() { }, "牛只称重", params);
76 76
         }
77 77
         return filterStubRows(ThirdPartyFarmingPageFetcher.fetchFromStub(objectMapper, STUB_RESOURCE,
78
-                new TypeReference<List<OpenWeighingDto>>() { }, "牛只称重"), cattleNo.trim(), startTime, endTime);
78
+                new TypeReference<List<OpenWeighingDto>>() { }, "牛只称重"), cattleId, startTime, endTime);
79 79
     }
80 80
 
81
-    private static List<OpenWeighingDto> filterStubRows(List<OpenWeighingDto> all, String cattleNo,
81
+    private static List<OpenWeighingDto> filterStubRows(List<OpenWeighingDto> all, Long cattleId,
82 82
             Date startTime, Date endTime)
83 83
     {
84
-        if (startTime == null && endTime == null && cattleNo == null)
84
+        if (startTime == null && endTime == null && cattleId == null)
85 85
         {
86 86
             return all;
87 87
         }
@@ -92,7 +92,7 @@ public class ThirdPartyIotWeighingClientImpl implements ThirdPartyIotWeighingCli
92 92
             {
93 93
                 continue;
94 94
             }
95
-            if (cattleNo != null && !matchesCattleNoParam(cattleNo, row))
95
+            if (cattleId != null && !cattleId.equals(row.getCattleId()))
96 96
             {
97 97
                 continue;
98 98
             }
@@ -104,16 +104,4 @@ public class ThirdPartyIotWeighingClientImpl implements ThirdPartyIotWeighingCli
104 104
         }
105 105
         return filtered;
106 106
     }
107
-
108
-    /**
109
-     * Stub 联调:cattleNo 参数可匹配 DTO 的 cattleNo 或 cattleId(与档案 external_id 传参约定一致)。
110
-     */
111
-    private static boolean matchesCattleNoParam(String cattleNoParam, OpenWeighingDto row)
112
-    {
113
-        if (cattleNoParam.equalsIgnoreCase(row.getCattleNo() != null ? row.getCattleNo().trim() : null))
114
-        {
115
-            return true;
116
-        }
117
-        return row.getCattleId() != null && cattleNoParam.equals(String.valueOf(row.getCattleId()));
118
-    }
119 107
 }

+ 42 - 12
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/YakAssetGrowthSupport.java

@@ -5,6 +5,7 @@ import java.util.ArrayList;
5 5
 import java.util.Comparator;
6 6
 import java.util.Date;
7 7
 import java.util.List;
8
+import com.ruoyi.common.utils.StringUtils;
8 9
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
9 10
 import com.ruoyi.web.modules.industryservice.domain.BizYakGrowthRecord;
10 11
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenWeighingDto;
@@ -25,15 +26,11 @@ public final class YakAssetGrowthSupport
25 26
     }
26 27
 
27 28
     /**
28
-     * 称重接口 cattleNo 查询参数:产品约定传档案 {@code external_id} 字符串
29
+     * 称重接口 cattleId 查询参数:档案 {@code external_id}
29 30
      */
30
-    public static String resolveWeighingCattleNoParam(BizYakAsset asset)
31
+    public static Long resolveWeighingCattleIdParam(BizYakAsset asset)
31 32
     {
32
-        if (asset == null || asset.getExternalId() == null)
33
-        {
34
-            return null;
35
-        }
36
-        return String.valueOf(asset.getExternalId());
33
+        return asset == null ? null : asset.getExternalId();
37 34
     }
38 35
 
39 36
     public static Date parseWeighingTime(OpenWeighingDto dto)
@@ -71,27 +68,60 @@ public final class YakAssetGrowthSupport
71 68
     }
72 69
 
73 70
     /**
74
-     * 组装生长曲线:仅体重指标有实测点;体高/胸围/体长返回空曲线
71
+     * 组装生长曲线:按 metric 取对应字段;实测/预测按 dataKind 分组
75 72
      */
76 73
     public static YakAssetGrowthChartVo toGrowthChart(List<BizYakGrowthRecord> rows, String metric)
77 74
     {
78 75
         YakAssetGrowthChartVo chart = new YakAssetGrowthChartVo();
79
-        if (!YakAssetRules.METRIC_WEIGHT.equals(metric) || rows == null)
76
+        if (rows == null || StringUtils.isEmpty(metric))
80 77
         {
81 78
             return chart;
82 79
         }
80
+        String m = metric.trim().toLowerCase();
83 81
         for (BizYakGrowthRecord row : rows)
84 82
         {
85
-            if (row == null || row.getWeightKg() == null)
83
+            if (row == null)
84
+            {
85
+                continue;
86
+            }
87
+            BigDecimal value = resolveGrowthValue(row, m);
88
+            if (value == null)
86 89
             {
87 90
                 continue;
88 91
             }
89 92
             YakAssetGrowthChartPointVo point = new YakAssetGrowthChartPointVo();
90 93
             point.setDayAge(row.getDayAge());
91
-            point.setValue(row.getWeightKg());
94
+            point.setValue(value);
92 95
             point.setCollectTime(row.getCollectTime());
93
-            chart.getActual().add(point);
96
+            if (row.getDataKind() != null && row.getDataKind() == YakAssetRules.DATA_KIND_FORECAST)
97
+            {
98
+                chart.getForecast().add(point);
99
+            }
100
+            else
101
+            {
102
+                chart.getActual().add(point);
103
+            }
94 104
         }
95 105
         return chart;
96 106
     }
107
+
108
+    public static BigDecimal resolveGrowthValue(BizYakGrowthRecord row, String metric)
109
+    {
110
+        if (row == null || StringUtils.isEmpty(metric))
111
+        {
112
+            return null;
113
+        }
114
+        switch (metric.trim().toLowerCase())
115
+        {
116
+            case YakAssetRules.METRIC_HEIGHT:
117
+                return row.getHeightCm();
118
+            case YakAssetRules.METRIC_CHEST:
119
+                return row.getChestCm();
120
+            case YakAssetRules.METRIC_LENGTH:
121
+                return row.getLengthCm();
122
+            case YakAssetRules.METRIC_WEIGHT:
123
+            default:
124
+                return row.getWeightKg();
125
+        }
126
+    }
97 127
 }

+ 20 - 6
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/service/YakAssetGrowthLiveServiceTest.java

@@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.eq;
6 6
 import static org.mockito.Mockito.verify;
7 7
 import static org.mockito.Mockito.when;
8 8
 
9
+import java.math.BigDecimal;
9 10
 import java.util.Collections;
10 11
 import org.junit.jupiter.api.DisplayName;
11 12
 import org.junit.jupiter.api.Test;
@@ -14,7 +15,10 @@ import org.mockito.InjectMocks;
14 15
 import org.mockito.Mock;
15 16
 import org.mockito.junit.jupiter.MockitoExtension;
16 17
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
18
+import com.ruoyi.web.modules.industryservice.domain.BizYakGrowthRecord;
17 19
 import com.ruoyi.web.modules.industryservice.domain.dto.OpenWeighingDto;
20
+import com.ruoyi.web.modules.industryservice.domain.vo.YakAssetGrowthChartVo;
21
+import com.ruoyi.web.modules.industryservice.mapper.BizYakGrowthRecordMapper;
18 22
 import com.ruoyi.web.modules.industryservice.support.ThirdPartyFarmingProperties;
19 23
 import com.ruoyi.web.modules.industryservice.support.ThirdPartyIotWeighingClient;
20 24
 import com.ruoyi.web.modules.industryservice.support.YakAssetRules;
@@ -26,6 +30,9 @@ class YakAssetGrowthLiveServiceTest
26 30
     @Mock
27 31
     private ThirdPartyIotWeighingClient weighingClient;
28 32
 
33
+    @Mock
34
+    private BizYakGrowthRecordMapper growthRecordMapper;
35
+
29 36
     @Mock
30 37
     private ThirdPartyFarmingProperties properties;
31 38
 
@@ -40,19 +47,26 @@ class YakAssetGrowthLiveServiceTest
40 47
         OpenWeighingDto dto = new OpenWeighingDto();
41 48
         dto.setWeight("300");
42 49
         dto.setWeighingTime("2026-05-20 08:30:00");
43
-        when(weighingClient.fetchByCattleNo(eq("4001"), any(), any())).thenReturn(Collections.singletonList(dto));
50
+        when(weighingClient.fetchByCattleId(eq(4001L), any(), any())).thenReturn(Collections.singletonList(dto));
44 51
 
45 52
         BizYakAsset asset = new BizYakAsset();
46 53
         asset.setExternalId(4001L);
47 54
         assertEquals(1, service.fetchGrowthRecords(asset).size());
48
-        verify(weighingClient).fetchByCattleNo(eq("4001"), any(), any());
55
+        verify(weighingClient).fetchByCattleId(eq(4001L), any(), any());
49 56
     }
50 57
 
51 58
     @Test
52
-    @DisplayName("无 external_id 不请求接口")
53
-    void fetchWhenNoExternalId()
59
+    @DisplayName("体高曲线读本地生长子表")
60
+    void fetchGrowthChartHeight()
54 61
     {
55
-        when(properties.isEnabled()).thenReturn(true);
56
-        assertEquals(0, service.fetchGrowthRecords(new BizYakAsset()).size());
62
+        BizYakGrowthRecord row = new BizYakGrowthRecord();
63
+        row.setHeightCm(new BigDecimal("95"));
64
+        row.setDataKind(YakAssetRules.DATA_KIND_ACTUAL);
65
+        when(growthRecordMapper.selectByYakAssetId(1L)).thenReturn(Collections.singletonList(row));
66
+
67
+        BizYakAsset asset = new BizYakAsset();
68
+        asset.setId(1L);
69
+        YakAssetGrowthChartVo chart = service.fetchGrowthChart(asset, "height");
70
+        assertEquals(1, chart.getActual().size());
57 71
     }
58 72
 }

+ 19 - 6
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/YakAssetGrowthSupportTest.java

@@ -16,12 +16,12 @@ import com.ruoyi.web.modules.industryservice.domain.vo.YakAssetGrowthChartVo;
16 16
 class YakAssetGrowthSupportTest
17 17
 {
18 18
     @Test
19
-    @DisplayName("称重 cattleNo 参数取 external_id")
20
-    void resolveWeighingCattleNoParam()
19
+    @DisplayName("称重 cattleId 参数取 external_id")
20
+    void resolveWeighingCattleIdParam()
21 21
     {
22 22
         BizYakAsset asset = new BizYakAsset();
23 23
         asset.setExternalId(4001L);
24
-        assertEquals("4001", YakAssetGrowthSupport.resolveWeighingCattleNoParam(asset));
24
+        assertEquals(4001L, YakAssetGrowthSupport.resolveWeighingCattleIdParam(asset));
25 25
     }
26 26
 
27 27
     @Test
@@ -41,11 +41,24 @@ class YakAssetGrowthSupportTest
41 41
     }
42 42
 
43 43
     @Test
44
-    @DisplayName("非体重指标曲线为空")
45
-    void toGrowthChartNonWeight()
44
+    @DisplayName("体高指标从生长记录组装曲线")
45
+    void toGrowthChartHeight()
46 46
     {
47 47
         BizYakGrowthRecord row = new BizYakGrowthRecord();
48
-        row.setHeightCm(new BigDecimal("120"));
48
+        row.setDayAge(120);
49
+        row.setHeightCm(new BigDecimal("95"));
50
+        row.setDataKind(YakAssetRules.DATA_KIND_ACTUAL);
51
+        YakAssetGrowthChartVo chart = YakAssetGrowthSupport.toGrowthChart(Collections.singletonList(row), "height");
52
+        assertEquals(1, chart.getActual().size());
53
+        assertEquals(new BigDecimal("95"), chart.getActual().get(0).getValue());
54
+    }
55
+
56
+    @Test
57
+    @DisplayName("体高指标无数据时曲线为空")
58
+    void toGrowthChartHeightEmpty()
59
+    {
60
+        BizYakGrowthRecord row = new BizYakGrowthRecord();
61
+        row.setWeightKg(new BigDecimal("200"));
49 62
         YakAssetGrowthChartVo chart = YakAssetGrowthSupport.toGrowthChart(Collections.singletonList(row), "height");
50 63
         assertEquals(0, chart.getActual().size());
51 64
     }