Commit 09aa9ad97f2463a034fe596b2d587194179aa38a

Authored by YunaiV
2 parents ea5fd5d9 173264e2

Merge remote-tracking branch 'origin/master-jdk17' into master-jdk17

# Conflicts:
#	yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java
Showing 310 changed files with 4866 additions and 809 deletions
yudao-dependencies/pom.xml
@@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
34 <kingbase.jdbc.version>9.0.1.jre7</kingbase.jdbc.version> 34 <kingbase.jdbc.version>9.0.1.jre7</kingbase.jdbc.version>
35 <opengauss.jdbc.version>7.0.0-RC3-og</opengauss.jdbc.version> 35 <opengauss.jdbc.version>7.0.0-RC3-og</opengauss.jdbc.version>
36 <taos.version>3.9.2</taos.version> 36 <taos.version>3.9.2</taos.version>
  37 + <iotdb.version>2.0.8</iotdb.version> <!-- TODO 特殊:暂不升级 2.0.10/2.0.11,其 TablePreparedStatement.execute 不设置 ResultSet,导致 Mapper 的 SELECT 为空、COUNT 为 null -->
37 <!-- 消息队列 --> 38 <!-- 消息队列 -->
38 <rocketmq-spring.version>2.3.6</rocketmq-spring.version> 39 <rocketmq-spring.version>2.3.6</rocketmq-spring.version>
39 <!-- 服务保障相关 --> 40 <!-- 服务保障相关 -->
@@ -58,7 +59,7 @@ @@ -58,7 +59,7 @@
58 <mapstruct.version>1.7.0.Beta2</mapstruct.version> 59 <mapstruct.version>1.7.0.Beta2</mapstruct.version>
59 <hutool-5.version>5.8.47</hutool-5.version> 60 <hutool-5.version>5.8.47</hutool-5.version>
60 <hutool-6.version>6.0.0-M22</hutool-6.version> 61 <hutool-6.version>6.0.0-M22</hutool-6.version>
61 - <fastexcel.version>1.3.0</fastexcel.version> 62 + <fesod.version>2.1.0-incubating</fesod.version>
62 <aviator.version>5.4.4</aviator.version> 63 <aviator.version>5.4.4</aviator.version>
63 <velocity.version>2.4.1</velocity.version> 64 <velocity.version>2.4.1</velocity.version>
64 <fastjson2.version>2.0.65</fastjson2.version> 65 <fastjson2.version>2.0.65</fastjson2.version>
@@ -312,6 +313,12 @@ @@ -312,6 +313,12 @@
312 <version>${taos.version}</version> 313 <version>${taos.version}</version>
313 </dependency> 314 </dependency>
314 315
  316 + <dependency>
  317 + <groupId>org.apache.iotdb</groupId>
  318 + <artifactId>iotdb-jdbc</artifactId>
  319 + <version>${iotdb.version}</version>
  320 + </dependency>
  321 +
315 <!-- Job 定时任务相关 --> 322 <!-- Job 定时任务相关 -->
316 <dependency> 323 <dependency>
317 <groupId>cn.iocoder.boot</groupId> 324 <groupId>cn.iocoder.boot</groupId>
@@ -509,9 +516,9 @@ @@ -509,9 +516,9 @@
509 </dependency> 516 </dependency>
510 517
511 <dependency> 518 <dependency>
512 - <groupId>cn.idev.excel</groupId>  
513 - <artifactId>fastexcel</artifactId>  
514 - <version>${fastexcel.version}</version> 519 + <groupId>org.apache.fesod</groupId>
  520 + <artifactId>fesod-sheet</artifactId>
  521 + <version>${fesod.version}</version>
515 </dependency> 522 </dependency>
516 523
517 <dependency> 524 <dependency>
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
@@ -42,8 +42,8 @@ @@ -42,8 +42,8 @@
42 42
43 <!-- 工具类相关 --> 43 <!-- 工具类相关 -->
44 <dependency> 44 <dependency>
45 - <groupId>cn.idev.excel</groupId>  
46 - <artifactId>fastexcel</artifactId> 45 + <groupId>org.apache.fesod</groupId>
  46 + <artifactId>fesod-sheet</artifactId>
47 </dependency> 47 </dependency>
48 48
49 <dependency> 49 <dependency>
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
@@ -3,13 +3,13 @@ package cn.iocoder.yudao.framework.excel.core.convert; @@ -3,13 +3,13 @@ package cn.iocoder.yudao.framework.excel.core.convert;
3 import cn.hutool.core.convert.Convert; 3 import cn.hutool.core.convert.Convert;
4 import cn.iocoder.yudao.framework.ip.core.Area; 4 import cn.iocoder.yudao.framework.ip.core.Area;
5 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 5 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
6 -import cn.idev.excel.converters.Converter;  
7 -import cn.idev.excel.enums.CellDataTypeEnum;  
8 -import cn.idev.excel.metadata.GlobalConfiguration;  
9 -import cn.idev.excel.metadata.data.ReadCellData;  
10 -import cn.idev.excel.metadata.data.WriteCellData;  
11 -import cn.idev.excel.metadata.property.ExcelContentProperty;  
12 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
  7 +import org.apache.fesod.sheet.converters.Converter;
  8 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  9 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  10 +import org.apache.fesod.sheet.metadata.data.ReadCellData;
  11 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  12 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
13 13
14 /** 14 /**
15 * Excel 数据地区转换器 15 * Excel 数据地区转换器
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
@@ -3,13 +3,13 @@ package cn.iocoder.yudao.framework.excel.core.convert; @@ -3,13 +3,13 @@ package cn.iocoder.yudao.framework.excel.core.convert;
3 import cn.hutool.core.convert.Convert; 3 import cn.hutool.core.convert.Convert;
4 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 4 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 -import cn.idev.excel.converters.Converter;  
7 -import cn.idev.excel.enums.CellDataTypeEnum;  
8 -import cn.idev.excel.metadata.GlobalConfiguration;  
9 -import cn.idev.excel.metadata.data.ReadCellData;  
10 -import cn.idev.excel.metadata.data.WriteCellData;  
11 -import cn.idev.excel.metadata.property.ExcelContentProperty;  
12 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
  7 +import org.apache.fesod.sheet.converters.Converter;
  8 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  9 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  10 +import org.apache.fesod.sheet.metadata.data.ReadCellData;
  11 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  12 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
13 13
14 /** 14 /**
15 * Excel 数据字典转换器 15 * Excel 数据字典转换器
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java
1 package cn.iocoder.yudao.framework.excel.core.convert; 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 -import cn.idev.excel.converters.Converter;  
5 -import cn.idev.excel.enums.CellDataTypeEnum;  
6 -import cn.idev.excel.metadata.GlobalConfiguration;  
7 -import cn.idev.excel.metadata.data.WriteCellData;  
8 -import cn.idev.excel.metadata.property.ExcelContentProperty; 4 +import org.apache.fesod.sheet.converters.Converter;
  5 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  6 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  7 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  8 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
9 9
10 /** 10 /**
11 * Excel Json 转换器 11 * Excel Json 转换器
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
1 package cn.iocoder.yudao.framework.excel.core.convert; 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 -import cn.idev.excel.converters.Converter;  
4 -import cn.idev.excel.enums.CellDataTypeEnum;  
5 -import cn.idev.excel.metadata.GlobalConfiguration;  
6 -import cn.idev.excel.metadata.data.WriteCellData;  
7 -import cn.idev.excel.metadata.property.ExcelContentProperty; 3 +import org.apache.fesod.sheet.converters.Converter;
  4 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  5 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  6 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  7 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.math.RoundingMode; 10 import java.math.RoundingMode;
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MultiDictConvert.java
@@ -4,13 +4,13 @@ import cn.hutool.core.convert.Convert; @@ -4,13 +4,13 @@ import cn.hutool.core.convert.Convert;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 5 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
6 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 6 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
7 -import cn.idev.excel.converters.Converter;  
8 -import cn.idev.excel.enums.CellDataTypeEnum;  
9 -import cn.idev.excel.metadata.GlobalConfiguration;  
10 -import cn.idev.excel.metadata.data.ReadCellData;  
11 -import cn.idev.excel.metadata.data.WriteCellData;  
12 -import cn.idev.excel.metadata.property.ExcelContentProperty;  
13 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
  8 +import org.apache.fesod.sheet.converters.Converter;
  9 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  10 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  11 +import org.apache.fesod.sheet.metadata.data.ReadCellData;
  12 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  13 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
14 14
15 import java.lang.reflect.Array; 15 import java.lang.reflect.Array;
16 import java.lang.reflect.Field; 16 import java.lang.reflect.Field;
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/StringListConvert.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.framework.excel.core.convert; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 -import cn.idev.excel.converters.Converter;  
6 -import cn.idev.excel.metadata.GlobalConfiguration;  
7 -import cn.idev.excel.metadata.data.ReadCellData;  
8 -import cn.idev.excel.metadata.data.WriteCellData;  
9 -import cn.idev.excel.metadata.property.ExcelContentProperty; 5 +import org.apache.fesod.sheet.converters.Converter;
  6 +import org.apache.fesod.sheet.metadata.GlobalConfiguration;
  7 +import org.apache.fesod.sheet.metadata.data.ReadCellData;
  8 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  9 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
10 10
11 import java.util.Collections; 11 import java.util.Collections;
12 import java.util.List; 12 import java.util.List;
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategy.java
1 package cn.iocoder.yudao.framework.excel.core.handler; 1 package cn.iocoder.yudao.framework.excel.core.handler;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.idev.excel.enums.CellDataTypeEnum;  
5 -import cn.idev.excel.metadata.Head;  
6 -import cn.idev.excel.metadata.data.WriteCellData;  
7 -import cn.idev.excel.util.MapUtils;  
8 -import cn.idev.excel.write.metadata.holder.WriteSheetHolder;  
9 -import cn.idev.excel.write.style.column.AbstractColumnWidthStyleStrategy;  
10 -import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; 4 +import org.apache.fesod.common.util.MapUtils;
  5 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  6 +import org.apache.fesod.sheet.metadata.Head;
  7 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  8 +import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
  9 +import org.apache.fesod.sheet.write.style.column.AbstractColumnWidthStyleStrategy;
  10 +import org.apache.fesod.sheet.write.style.column.LongestMatchColumnWidthStyleStrategy;
11 import org.apache.poi.ss.usermodel.Cell; 11 import org.apache.poi.ss.usermodel.Cell;
12 12
13 import java.nio.charset.StandardCharsets; 13 import java.nio.charset.StandardCharsets;
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
@@ -10,13 +10,13 @@ import cn.iocoder.yudao.framework.common.core.KeyValue; @@ -10,13 +10,13 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
10 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 10 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
11 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; 11 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
12 import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction; 12 import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
13 -import cn.idev.excel.annotation.ExcelIgnore;  
14 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
15 -import cn.idev.excel.annotation.ExcelProperty;  
16 -import cn.idev.excel.write.handler.SheetWriteHandler;  
17 -import cn.idev.excel.write.metadata.holder.WriteSheetHolder;  
18 -import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;  
19 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
  14 +import org.apache.fesod.sheet.annotation.ExcelIgnore;
  15 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  16 +import org.apache.fesod.sheet.annotation.ExcelProperty;
  17 +import org.apache.fesod.sheet.write.handler.SheetWriteHandler;
  18 +import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
  19 +import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
20 import org.apache.poi.hssf.usermodel.HSSFDataValidation; 20 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
21 import org.apache.poi.ss.usermodel.*; 21 import org.apache.poi.ss.usermodel.*;
22 import org.apache.poi.ss.util.CellRangeAddressList; 22 import org.apache.poi.ss.util.CellRangeAddressList;
@@ -86,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -86,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
86 86
87 /** 87 /**
88 * 判断字段是否是静态的、最终的、 transient 的 88 * 判断字段是否是静态的、最终的、 transient 的
89 - * 原因:FastExcel 默认是忽略 static final 或 transient 的字段,所以需要判断 89 + * 原因:Fesod 默认是忽略 static final 或 transient 的字段,所以需要判断
90 * 90 *
91 * @param field 字段 91 * @param field 字段
92 * @return 是否是静态的、最终的、transient 的 92 * @return 是否是静态的、最终的、transient 的
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
3 -import cn.idev.excel.FastExcelFactory;  
4 -import cn.idev.excel.context.AnalysisContext;  
5 -import cn.idev.excel.converters.longconverter.LongStringConverter;  
6 -import cn.idev.excel.read.listener.ReadListener;  
7 import cn.iocoder.yudao.framework.common.util.http.HttpUtils; 3 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
8 import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy; 4 import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
9 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; 5 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
10 import jakarta.servlet.http.HttpServletResponse; 6 import jakarta.servlet.http.HttpServletResponse;
  7 +import org.apache.fesod.sheet.FesodSheet;
  8 +import org.apache.fesod.sheet.context.AnalysisContext;
  9 +import org.apache.fesod.sheet.converters.Converter;
  10 +import org.apache.fesod.sheet.converters.longconverter.LongStringConverter;
  11 +import org.apache.fesod.sheet.enums.CellDataTypeEnum;
  12 +import org.apache.fesod.sheet.read.listener.ReadListener;
11 import org.springframework.web.multipart.MultipartFile; 13 import org.springframework.web.multipart.MultipartFile;
12 14
13 import java.io.IOException; 15 import java.io.IOException;
@@ -25,6 +27,21 @@ import java.util.Map; @@ -25,6 +27,21 @@ import java.util.Map;
25 public class ExcelUtils { 27 public class ExcelUtils {
26 28
27 /** 29 /**
  30 + * Long 转 String 的转换器,避免 Long 类型丢失精度
  31 + *
  32 + * 参考 https://t.zsxq.com/a0w1V 帖子:Fesod 写入 xlsx 时,按 supportExcelTypeKey 为 null 查找全局转换器,
  33 + * 所以需要返回 null,否则 registerConverter 的 LongStringConverter 不生效。关联 https://github.com/apache/fesod/issues/648
  34 + */
  35 + private static final Converter<Long> LONG_STRING_CONVERTER = new LongStringConverter() {
  36 +
  37 + @Override
  38 + public CellDataTypeEnum supportExcelTypeKey() {
  39 + return null;
  40 + }
  41 +
  42 + };
  43 +
  44 + /**
28 * 将列表以 Excel 响应给前端 45 * 将列表以 Excel 响应给前端
29 * 46 *
30 * @param response 响应 47 * @param response 响应
@@ -38,11 +55,11 @@ public class ExcelUtils { @@ -38,11 +55,11 @@ public class ExcelUtils {
38 public static <T> void write(HttpServletResponse response, String filename, String sheetName, 55 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
39 Class<T> head, List<T> data) throws IOException { 56 Class<T> head, List<T> data) throws IOException {
40 // 输出 Excel 57 // 输出 Excel
41 - FastExcelFactory.write(response.getOutputStream(), head) 58 + FesodSheet.write(response.getOutputStream(), head)
42 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 59 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
43 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 60 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
44 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框 61 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
45 - .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 62 + .registerConverter(LONG_STRING_CONVERTER) // 避免 Long 类型丢失精度
46 .sheet(sheetName).doWrite(data); 63 .sheet(sheetName).doWrite(data);
47 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 64 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
48 response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); 65 response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
@@ -62,10 +79,10 @@ public class ExcelUtils { @@ -62,10 +79,10 @@ public class ExcelUtils {
62 public static void write(HttpServletResponse response, String filename, String sheetName, 79 public static void write(HttpServletResponse response, String filename, String sheetName,
63 List<List<String>> head, List<List<Object>> data) throws IOException { 80 List<List<String>> head, List<List<Object>> data) throws IOException {
64 // 输出 Excel 81 // 输出 Excel
65 - FastExcelFactory.write(response.getOutputStream()) 82 + FesodSheet.write(response.getOutputStream())
66 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 83 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
67 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 84 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
68 - .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 85 + .registerConverter(LONG_STRING_CONVERTER) // 避免 Long 类型丢失精度
69 .head(head).sheet(sheetName).doWrite(data); 86 .head(head).sheet(sheetName).doWrite(data);
70 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 87 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
71 response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); 88 response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
@@ -78,7 +95,7 @@ public class ExcelUtils { @@ -78,7 +95,7 @@ public class ExcelUtils {
78 } 95 }
79 // 参考 https://t.zsxq.com/zM77F 帖子,增加 try 处理,兼容 windows 场景 96 // 参考 https://t.zsxq.com/zM77F 帖子,增加 try 处理,兼容 windows 场景
80 try (InputStream inputStream = file.getInputStream()) { 97 try (InputStream inputStream = file.getInputStream()) {
81 - return FastExcelFactory.read(inputStream, head, null) 98 + return FesodSheet.read(inputStream, head, null)
82 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 99 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
83 .doReadAllSync(); 100 .doReadAllSync();
84 } 101 }
@@ -101,7 +118,7 @@ public class ExcelUtils { @@ -101,7 +118,7 @@ public class ExcelUtils {
101 } 118 }
102 List<T> rows = new ArrayList<>(Math.min(maxRowCount, 1024)); 119 List<T> rows = new ArrayList<>(Math.min(maxRowCount, 1024));
103 try (InputStream inputStream = file.getInputStream()) { 120 try (InputStream inputStream = file.getInputStream()) {
104 - FastExcelFactory.read(inputStream, head, new ReadListener<T>() { 121 + FesodSheet.read(inputStream, head, new ReadListener<T>() {
105 122
106 @Override 123 @Override
107 public void invoke(T data, AnalysisContext context) { 124 public void invoke(T data, AnalysisContext context) {
@@ -136,7 +153,7 @@ public class ExcelUtils { @@ -136,7 +153,7 @@ public class ExcelUtils {
136 } 153 }
137 // 参考 https://t.zsxq.com/zM77F 帖子,增加 try 处理,兼容 windows 场景 154 // 参考 https://t.zsxq.com/zM77F 帖子,增加 try 处理,兼容 windows 场景
138 try (InputStream inputStream = file.getInputStream()) { 155 try (InputStream inputStream = file.getInputStream()) {
139 - return FastExcelFactory.read(inputStream) 156 + return FesodSheet.read(inputStream)
140 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 157 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
141 .headRowNumber(1) 158 .headRowNumber(1)
142 .sheet() 159 .sheet()
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/PoiExcelUtils.java
@@ -27,7 +27,7 @@ import java.util.Collection; @@ -27,7 +27,7 @@ import java.util.Collection;
27 /** 27 /**
28 * 原生 POI 的 Excel 工具类 28 * 原生 POI 的 Excel 工具类
29 * 29 *
30 - * 全局 {@link ExcelUtils}(FastExcel)无法表达单元格锁定、公式、动态列等复杂模板时,各模块基于本类实现自定义 Excel 读写 30 + * 全局 {@link ExcelUtils}(Fesod)无法表达单元格锁定、公式、动态列等复杂模板时,各模块基于本类实现自定义 Excel 读写
31 * 31 *
32 * @author 芋道源码 32 * @author 芋道源码
33 */ 33 */
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java
1 /** 1 /**
2 - * 基于 FastExcel 实现 Excel 相关的操作 2 + * 基于 Fesod 实现 Excel 相关的操作
3 */ 3 */
4 package cn.iocoder.yudao.framework.excel; 4 package cn.iocoder.yudao.framework.excel;
yudao-framework/yudao-spring-boot-starter-excel/src/test/java/cn/iocoder/yudao/framework/excel/core/convert/MultiDictConvertTest.java
1 package cn.iocoder.yudao.framework.excel.core.convert; 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 import cn.hutool.core.collection.ListUtil; 3 import cn.hutool.core.collection.ListUtil;
4 -import cn.idev.excel.metadata.data.ReadCellData;  
5 -import cn.idev.excel.metadata.data.WriteCellData;  
6 -import cn.idev.excel.metadata.property.ExcelContentProperty;  
7 import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; 4 import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
8 import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; 5 import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
9 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 6 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
10 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 7 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
11 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 8 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  9 +import org.apache.fesod.sheet.metadata.data.ReadCellData;
  10 +import org.apache.fesod.sheet.metadata.data.WriteCellData;
  11 +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
12 import org.junit.jupiter.api.BeforeEach; 12 import org.junit.jupiter.api.BeforeEach;
13 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
14 import org.mockito.Mock; 14 import org.mockito.Mock;
yudao-framework/yudao-spring-boot-starter-excel/src/test/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategyTest.java
1 package cn.iocoder.yudao.framework.excel.core.handler; 1 package cn.iocoder.yudao.framework.excel.core.handler;
2 2
3 -import cn.idev.excel.FastExcelFactory;  
4 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.FesodSheet;
  4 +import org.apache.fesod.sheet.annotation.ExcelProperty;
5 import org.apache.poi.xssf.usermodel.XSSFWorkbook; 5 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
6 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
7 7
@@ -59,7 +59,7 @@ public class ColumnWidthMatchStyleStrategyTest { @@ -59,7 +59,7 @@ public class ColumnWidthMatchStyleStrategyTest {
59 59
60 private XSSFWorkbook write(List<TestRow> rows) throws Exception { 60 private XSSFWorkbook write(List<TestRow> rows) throws Exception {
61 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 61 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
62 - FastExcelFactory.write(outputStream, TestRow.class) 62 + FesodSheet.write(outputStream, TestRow.class)
63 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()).sheet().doWrite(rows); 63 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()).sheet().doWrite(rows);
64 return new XSSFWorkbook(new ByteArrayInputStream(outputStream.toByteArray())); 64 return new XSSFWorkbook(new ByteArrayInputStream(outputStream.toByteArray()));
65 } 65 }
yudao-framework/yudao-spring-boot-starter-excel/src/test/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtilsTest.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
3 -import cn.idev.excel.FastExcelFactory;  
4 -import cn.idev.excel.annotation.ExcelProperty; 3 +import cn.hutool.core.collection.ListUtil;
  4 +import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
  5 +import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
  6 +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
  7 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  8 +import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
  9 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  10 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  11 +import lombok.Data;
  12 +import lombok.experimental.Accessors;
  13 +import org.apache.fesod.sheet.FesodSheet;
  14 +import org.apache.fesod.sheet.annotation.ExcelProperty;
  15 +import org.apache.poi.ss.usermodel.Cell;
  16 +import org.apache.poi.ss.usermodel.CellType;
  17 +import org.apache.poi.ss.usermodel.Sheet;
  18 +import org.apache.poi.ss.usermodel.Workbook;
  19 +import org.apache.poi.ss.usermodel.WorkbookFactory;
  20 +import org.junit.jupiter.api.BeforeEach;
5 import org.junit.jupiter.api.Test; 21 import org.junit.jupiter.api.Test;
  22 +import org.mockito.Mock;
  23 +import org.springframework.mock.web.MockHttpServletResponse;
6 import org.springframework.mock.web.MockMultipartFile; 24 import org.springframework.mock.web.MockMultipartFile;
7 25
  26 +import java.io.ByteArrayInputStream;
8 import java.io.ByteArrayOutputStream; 27 import java.io.ByteArrayOutputStream;
9 import java.util.Arrays; 28 import java.util.Arrays;
  29 +import java.util.Collections;
10 import java.util.List; 30 import java.util.List;
11 31
  32 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
12 import static org.junit.jupiter.api.Assertions.assertEquals; 33 import static org.junit.jupiter.api.Assertions.assertEquals;
  34 +import static org.junit.jupiter.api.Assertions.assertNotNull;
  35 +import static org.mockito.ArgumentMatchers.eq;
  36 +import static org.mockito.Mockito.when;
13 37
14 /** 38 /**
15 * {@link ExcelUtils} 的单元测试 39 * {@link ExcelUtils} 的单元测试
16 * 40 *
17 * @author 芋道源码 41 * @author 芋道源码
18 */ 42 */
19 -public class ExcelUtilsTest { 43 +public class ExcelUtilsTest extends BaseMockitoUnitTest {
  44 +
  45 + private static final String DICT_TYPE = "test_status";
  46 +
  47 + @Mock
  48 + private DictDataCommonApi dictDataApi;
  49 +
  50 + @BeforeEach
  51 + public void setUp() {
  52 + DictFrameworkUtils.init(dictDataApi);
  53 + DictFrameworkUtils.clearCache();
  54 + }
  55 +
  56 + /**
  57 + * 字段未配置 converter 的 Long 编号,需要导出为文本,避免丢失精度
  58 + */
  59 + @Test
  60 + public void testWrite_class() throws Exception {
  61 + // mock 数据
  62 + mockDictDatas();
  63 + // 准备参数
  64 + MockHttpServletResponse response = new MockHttpServletResponse();
  65 + List<TestUser> list = Arrays.asList(
  66 + new TestUser().setId(1981234567890123456L).setAge(18).setStatus(0),
  67 + new TestUser().setId(Long.MAX_VALUE).setAge(20).setStatus(1));
  68 +
  69 + // 调用
  70 + ExcelUtils.write(response, "用户.xls", "数据", TestUser.class, list);
  71 + // 断言
  72 + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
  73 + Sheet sheet = workbook.getSheet("数据");
  74 + // 断言 Long 编号
  75 + assertStringCell("1981234567890123456", sheet.getRow(1).getCell(0));
  76 + assertStringCell("9223372036854775807", sheet.getRow(2).getCell(0));
  77 + // 断言普通数值
  78 + assertNumericCell(18, sheet.getRow(1).getCell(1));
  79 + assertNumericCell(20, sheet.getRow(2).getCell(1));
  80 + // 断言字典转换
  81 + assertStringCell("开启", sheet.getRow(1).getCell(2));
  82 + assertStringCell("关闭", sheet.getRow(2).getCell(2));
  83 + // 断言下拉框
  84 + assertNotNull(workbook.getSheet("字典sheet"));
  85 + assertEquals(1, sheet.getDataValidations().size());
  86 + // 断言列宽:19 位 Long 字符串,加上 2 个字符的留白
  87 + assertEquals((19 + 2) * 256, sheet.getColumnWidth(0));
  88 + }
  89 + }
  90 +
  91 + @Test
  92 + public void testWrite_dynamicHead() throws Exception {
  93 + // 准备参数
  94 + MockHttpServletResponse response = new MockHttpServletResponse();
  95 + List<List<String>> head = Arrays.asList(Collections.singletonList("编号"), Collections.singletonList("年龄"));
  96 + List<List<Object>> list = Arrays.asList(
  97 + Arrays.asList(1981234567890123456L, 18),
  98 + Arrays.asList(Long.MAX_VALUE, 20));
  99 +
  100 + // 调用
  101 + ExcelUtils.write(response, "用户.xls", "数据", head, list);
  102 + // 断言
  103 + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
  104 + Sheet sheet = workbook.getSheet("数据");
  105 + assertStringCell("编号", sheet.getRow(0).getCell(0));
  106 + // 断言 Long 编号
  107 + assertStringCell("1981234567890123456", sheet.getRow(1).getCell(0));
  108 + assertStringCell("9223372036854775807", sheet.getRow(2).getCell(0));
  109 + // 断言普通数值
  110 + assertNumericCell(18, sheet.getRow(1).getCell(1));
  111 + assertNumericCell(20, sheet.getRow(2).getCell(1));
  112 + }
  113 + }
20 114
21 @Test 115 @Test
22 public void testRead_maxRowCount() throws Exception { 116 public void testRead_maxRowCount() throws Exception {
23 // mock 数据 117 // mock 数据
24 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 118 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
25 - FastExcelFactory.write(outputStream, TestRow.class).sheet().doWrite(Arrays.asList( 119 + FesodSheet.write(outputStream, TestRow.class).sheet().doWrite(Arrays.asList(
26 new TestRow("员工一"), new TestRow("员工二"), new TestRow("员工三"))); 120 new TestRow("员工一"), new TestRow("员工二"), new TestRow("员工三")));
27 MockMultipartFile file = new MockMultipartFile( 121 MockMultipartFile file = new MockMultipartFile(
28 "file", "employees.xlsx", null, outputStream.toByteArray()); 122 "file", "employees.xlsx", null, outputStream.toByteArray());
@@ -36,6 +130,41 @@ public class ExcelUtilsTest { @@ -36,6 +130,41 @@ public class ExcelUtilsTest {
36 assertEquals("员工二", rows.get(1).getName()); 130 assertEquals("员工二", rows.get(1).getName());
37 } 131 }
38 132
  133 + private void mockDictDatas() {
  134 + List<DictDataRespDTO> dictDatas = ListUtil.of(
  135 + randomPojo(DictDataRespDTO.class, o -> o.setDictType(DICT_TYPE).setValue("0").setLabel("开启")),
  136 + randomPojo(DictDataRespDTO.class, o -> o.setDictType(DICT_TYPE).setValue("1").setLabel("关闭"))
  137 + );
  138 + when(dictDataApi.getDictDataList(eq(DICT_TYPE))).thenReturn(dictDatas);
  139 + }
  140 +
  141 + private static void assertStringCell(String expected, Cell cell) {
  142 + assertEquals(CellType.STRING, cell.getCellType());
  143 + assertEquals(expected, cell.getStringCellValue());
  144 + }
  145 +
  146 + private static void assertNumericCell(double expected, Cell cell) {
  147 + assertEquals(CellType.NUMERIC, cell.getCellType());
  148 + assertEquals(expected, cell.getNumericCellValue());
  149 + }
  150 +
  151 + @Data
  152 + @Accessors(chain = true)
  153 + public static class TestUser {
  154 +
  155 + @ExcelProperty("编号")
  156 + private Long id;
  157 +
  158 + @ExcelProperty("年龄")
  159 + private Integer age;
  160 +
  161 + @ExcelProperty(value = "状态", converter = DictConvert.class)
  162 + @DictFormat(DICT_TYPE)
  163 + @ExcelColumnSelect(dictType = DICT_TYPE)
  164 + private Integer status;
  165 +
  166 + }
  167 +
39 public static class TestRow { 168 public static class TestRow {
40 169
41 @ExcelProperty("姓名") 170 @ExcelProperty("姓名")
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
@@ -4,11 +4,15 @@ import cn.hutool.core.collection.CollUtil; @@ -4,11 +4,15 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; 6 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
  7 +import cn.iocoder.yudao.framework.mybatis.core.injector.YudaoSqlInjector;
  8 +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap;
7 import com.baomidou.mybatisplus.annotation.DbType; 9 import com.baomidou.mybatisplus.annotation.DbType;
  10 +import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
8 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; 11 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
9 import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler; 12 import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
10 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 13 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
11 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; 14 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
  15 +import com.baomidou.mybatisplus.core.injector.ISqlInjector;
12 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; 16 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
13 import com.baomidou.mybatisplus.extension.incrementer.*; 17 import com.baomidou.mybatisplus.extension.incrementer.*;
14 import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; 18 import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
@@ -17,12 +21,15 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; @@ -17,12 +21,15 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
17 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 21 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
18 import com.fasterxml.jackson.databind.ObjectMapper; 22 import com.fasterxml.jackson.databind.ObjectMapper;
19 import org.apache.ibatis.annotations.Mapper; 23 import org.apache.ibatis.annotations.Mapper;
  24 +import org.apache.ibatis.mapping.ResultMap;
20 import org.mybatis.spring.annotation.MapperScan; 25 import org.mybatis.spring.annotation.MapperScan;
21 import org.springframework.boot.autoconfigure.AutoConfiguration; 26 import org.springframework.boot.autoconfigure.AutoConfiguration;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
23 import org.springframework.context.annotation.Bean; 28 import org.springframework.context.annotation.Bean;
  29 +import org.springframework.context.annotation.Primary;
24 import org.springframework.core.env.ConfigurableEnvironment; 30 import org.springframework.core.env.ConfigurableEnvironment;
25 31
  32 +import java.util.ArrayList;
26 import java.util.List; 33 import java.util.List;
27 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.TimeUnit;
28 35
@@ -59,6 +66,19 @@ public class YudaoMybatisAutoConfiguration { @@ -59,6 +66,19 @@ public class YudaoMybatisAutoConfiguration {
59 } 66 }
60 67
61 @Bean 68 @Bean
  69 + @Primary // 特殊:使用 @Primary 的原因,避免 MyBatis Plus Join 再创建 MPJSqlInjector 包装它
  70 + public ISqlInjector yudaoSqlInjector() {
  71 + return new YudaoSqlInjector(); // selectMaps 的结果行,使用 CaseInsensitiveResultMap
  72 + }
  73 +
  74 + @Bean
  75 + public ConfigurationCustomizer caseInsensitiveResultMapConfigurationCustomizer() {
  76 + // 注册 CaseInsensitiveResultMap 的 resultMap,用于注解 SQL 的 @ResultMap
  77 + return configuration -> configuration.addResultMap(new ResultMap.Builder(configuration,
  78 + CaseInsensitiveResultMap.RESULT_MAP_ID, CaseInsensitiveResultMap.class, new ArrayList<>()).build());
  79 + }
  80 +
  81 + @Bean
62 @ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT") 82 @ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT")
63 public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) { 83 public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) {
64 DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment); 84 DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment);
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/injector/YudaoSqlInjector.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.injector;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap;
  4 +import com.baomidou.mybatisplus.core.injector.AbstractMethod;
  5 +import com.baomidou.mybatisplus.core.metadata.TableInfo;
  6 +import com.github.yulichang.injector.MPJSqlInjector;
  7 +import com.github.yulichang.method.mp.SelectMaps;
  8 +import org.apache.ibatis.mapping.MappedStatement;
  9 +import org.apache.ibatis.mapping.SqlSource;
  10 +import org.apache.ibatis.session.Configuration;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * 自定义 SQL 注入器
  16 + *
  17 + * 在 MyBatis Plus Join 的 {@link MPJSqlInjector} 基础上,将 selectMaps 的结果行替换为 {@link CaseInsensitiveResultMap},
  18 + * 解决 PostgreSQL、Oracle 等数据库列标签大小写折叠,导致按固定 key 取不到值的问题
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class YudaoSqlInjector extends MPJSqlInjector {
  23 +
  24 + @Override
  25 + public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {
  26 + List<AbstractMethod> methods = super.getMethodList(configuration, mapperClass, tableInfo);
  27 + methods.replaceAll(method -> method instanceof SelectMaps ? new CaseInsensitiveSelectMaps() : method);
  28 + return methods;
  29 + }
  30 +
  31 + /**
  32 + * 结果行为 {@link CaseInsensitiveResultMap} 的 selectMaps
  33 + *
  34 + * 仅修改结果类型,SQL 仍由 MyBatis Plus Join 的 {@link SelectMaps} 生成,兼容 MPJ 的 Wrapper
  35 + */
  36 + private static class CaseInsensitiveSelectMaps extends SelectMaps {
  37 +
  38 + @Override
  39 + protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,
  40 + Class<?> resultType) {
  41 + return super.addSelectMappedStatementForOther(mapperClass, id, sqlSource, CaseInsensitiveResultMap.class);
  42 + }
  43 +
  44 + }
  45 +
  46 +}
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.pojo.SortablePageParam; 6 import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
7 import cn.iocoder.yudao.framework.common.pojo.SortingField; 7 import cn.iocoder.yudao.framework.common.pojo.SortingField;
8 import cn.iocoder.yudao.framework.common.util.object.PageUtils; 8 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
  9 +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap;
9 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; 10 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
10 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; 11 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
11 import com.baomidou.mybatisplus.annotation.DbType; 12 import com.baomidou.mybatisplus.annotation.DbType;
@@ -14,6 +15,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -14,6 +15,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
14 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 15 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
15 import com.baomidou.mybatisplus.core.mapper.BaseMapper; 16 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
16 import com.baomidou.mybatisplus.core.metadata.IPage; 17 import com.baomidou.mybatisplus.core.metadata.IPage;
  18 +import com.baomidou.mybatisplus.core.toolkit.Constants;
17 import com.baomidou.mybatisplus.core.toolkit.support.SFunction; 19 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
18 import com.baomidou.mybatisplus.extension.toolkit.Db; 20 import com.baomidou.mybatisplus.extension.toolkit.Db;
19 import com.github.yulichang.base.MPJBaseMapper; 21 import com.github.yulichang.base.MPJBaseMapper;
@@ -23,6 +25,7 @@ import org.apache.ibatis.annotations.Param; @@ -23,6 +25,7 @@ import org.apache.ibatis.annotations.Param;
23 25
24 import java.util.Collection; 26 import java.util.Collection;
25 import java.util.List; 27 import java.util.List;
  28 +import java.util.Map;
26 29
27 /** 30 /**
28 * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 31 * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
@@ -99,6 +102,24 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; { @@ -99,6 +102,24 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
99 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); 102 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
100 } 103 }
101 104
  105 + @Override
  106 + @SuppressWarnings("unchecked")
  107 + default Map<String, Object> selectJoinMap(@Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) {
  108 + return selectJoinOne(CaseInsensitiveResultMap.class, wrapper);
  109 + }
  110 +
  111 + @Override
  112 + @SuppressWarnings("unchecked")
  113 + default List<Map<String, Object>> selectJoinMaps(@Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) {
  114 + return (List<Map<String, Object>>) ((Object) selectJoinList(CaseInsensitiveResultMap.class, wrapper));
  115 + }
  116 +
  117 + @Override
  118 + @SuppressWarnings({"unchecked", "rawtypes"})
  119 + default <P extends IPage<Map<String, Object>>> P selectJoinMapsPage(P page, @Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) {
  120 + return (P) selectJoinPage((IPage) page, CaseInsensitiveResultMap.class, wrapper);
  121 + }
  122 +
102 default T selectOne(String field, Object value) { 123 default T selectOne(String field, Object value) {
103 return selectOne(new QueryWrapper<T>().eq(field, value)); 124 return selectOne(new QueryWrapper<T>().eq(field, value));
104 } 125 }
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CaseInsensitiveResultMap.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.util;
  2 +
  3 +import org.springframework.util.LinkedCaseInsensitiveMap;
  4 +
  5 +import java.util.Locale;
  6 +
  7 +/**
  8 + * key 大小写不敏感的 MyBatis 查询结果 Map
  9 + *
  10 + * 背景:使用 Map 接收查询结果时,key 为 JDBC 返回的列标签。PostgreSQL 等会将未加引号的列名、别名折叠为小写,
  11 + * Oracle、达梦等会折叠为大写,导致 map.get("conversationId") 这类按固定 key 取值的代码,在 MySQL 以外的数据库取不到值。
  12 + *
  13 + * 实现:基于 {@link LinkedCaseInsensitiveMap},get / containsKey / remove 时忽略 key 的大小写,
  14 + * 同时保留原始的列标签作为 key(遍历、序列化时不变)。固定使用 {@link Locale#ROOT},避免服务器默认 Locale(例如土耳其语)影响 key 的匹配。
  15 + *
  16 + * 使用方式:由 MyBatis 直接创建结果行,Mapper 的返回类型仍声明为 Map,业务无需感知该类
  17 + * 1. MyBatis Plus 的 selectMaps:由 YudaoSqlInjector 注入,自动使用该类
  18 + * 2. MyBatis Plus Join 的 selectJoinMaps 等:由 BaseMapperX 覆盖,自动使用该类
  19 + * 3. XML SQL:将 resultType 设置为该类
  20 + * 4. 注解 SQL:添加 @ResultMap({@link #RESULT_MAP_ID})
  21 + *
  22 + * 注意:仅大小写不同的多个 key,会合并为一个,保留后写入的 key 和值
  23 + *
  24 + * @author 芋道源码
  25 + * @see <a href="https://t.zsxq.com/kPvqm">星球讨论:Map 查询结果的跨数据库兼容问题</a>
  26 + */
  27 +public class CaseInsensitiveResultMap<V> extends LinkedCaseInsensitiveMap<V> {
  28 +
  29 + /**
  30 + * 注解 SQL 使用的 resultMap 编号
  31 + *
  32 + * 由 YudaoMybatisAutoConfiguration 注册到 MyBatis 全局配置;包含“.”,所以不会拼接 Mapper 的 namespace
  33 + */
  34 + public static final String RESULT_MAP_ID = "cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap";
  35 +
  36 + /**
  37 + * 创建空的结果 Map
  38 + *
  39 + * 用途:MyBatis 通过无参构造方法,实例化结果行
  40 + */
  41 + public CaseInsensitiveResultMap() {
  42 + super(Locale.ROOT);
  43 + }
  44 +
  45 +}
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/CaseInsensitiveResultMapTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.util;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import org.junit.jupiter.api.Test;
  6 +import org.springframework.util.LinkedCaseInsensitiveMap;
  7 +
  8 +import java.util.*;
  9 +
  10 +import static org.junit.jupiter.api.Assertions.*;
  11 +
  12 +/**
  13 + * {@link CaseInsensitiveResultMap} 的单元测试
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public class CaseInsensitiveResultMapTest {
  18 +
  19 + @Test
  20 + public void testGet_ignoreCase() {
  21 + // 准备参数
  22 + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>();
  23 + map.put("conversationId", 1024L);
  24 +
  25 + // 调用,并断言:PostgreSQL 小写、Oracle 大写,都可以取到值
  26 + assertEquals(1024L, map.get("conversationId"));
  27 + assertEquals(1024L, map.get("conversationid"));
  28 + assertEquals(1024L, map.get("CONVERSATIONID"));
  29 + assertTrue(map.containsKey("CONVERSATIONID"));
  30 + assertEquals(1024L, MapUtil.getLong(map, "conversationid"));
  31 + // 断言:保留原始的 key
  32 + assertEquals(Collections.singletonList("conversationId"), new ArrayList<>(map.keySet()));
  33 + }
  34 +
  35 + @Test
  36 + public void testPut_nullValue() {
  37 + // 准备参数
  38 + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>();
  39 + map.put("sum_count", null);
  40 +
  41 + // 调用,并断言
  42 + assertTrue(map.containsKey("SUM_COUNT"));
  43 + assertNull(map.get("SUM_COUNT"));
  44 + assertEquals(0, MapUtil.getInt(map, "SUM_COUNT", 0));
  45 + }
  46 +
  47 + @Test
  48 + public void testPut_sameKeyIgnoreCase() {
  49 + // 准备参数
  50 + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>();
  51 +
  52 + // 调用:仅大小写不同的 key
  53 + map.put("id", 1L);
  54 + map.put("ID", 2L);
  55 +
  56 + // 断言:合并为一个,保留后写入的 key 和值
  57 + assertEquals(1, map.size());
  58 + assertEquals(2L, map.get("id"));
  59 + assertEquals(Collections.singletonList("ID"), new ArrayList<>(map.keySet()));
  60 + }
  61 +
  62 + @Test
  63 + public void testGet_turkishDefaultLocale() {
  64 + Locale defaultLocale = Locale.getDefault();
  65 + try {
  66 + // 准备参数:土耳其语下,大写 I 转小写为无点的 ı
  67 + Locale.setDefault(Locale.forLanguageTag("tr-TR"));
  68 + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>();
  69 + map.put("CONVERSATIONID", 1024L);
  70 + LinkedCaseInsensitiveMap<Object> defaultLocaleMap = new LinkedCaseInsensitiveMap<>();
  71 + defaultLocaleMap.put("CONVERSATIONID", 1024L);
  72 +
  73 + // 调用,并断言:固定 Locale.ROOT,不受默认 Locale 影响
  74 + assertEquals(1024L, map.get("conversationId"));
  75 + // 断言:使用默认 Locale 时,取不到值
  76 + assertNull(defaultLocaleMap.get("conversationId"));
  77 + } finally {
  78 + Locale.setDefault(defaultLocale);
  79 + }
  80 + }
  81 +
  82 + @Test
  83 + public void testConvertMap_numberKey() {
  84 + // 准备参数:模拟 Oracle 返回的结果行
  85 + List<Map<String, Object>> rows = Arrays.asList(buildRow(1024L, 3), buildRow(2048L, 5));
  86 +
  87 + // 调用:业务转换为数字 key 的 Map
  88 + Map<Long, Integer> result = CollectionUtils.convertMap(rows,
  89 + row -> MapUtil.getLong(row, "conversationId"),
  90 + row -> MapUtil.getInt(row, "count"));
  91 +
  92 + // 断言:数字 key 的业务 Map 不受影响
  93 + assertInstanceOf(HashMap.class, result);
  94 + assertEquals(3, result.get(1024L));
  95 + assertEquals(5, result.get(2048L));
  96 + }
  97 +
  98 + private static Map<String, Object> buildRow(Long conversationId, Integer count) {
  99 + CaseInsensitiveResultMap<Object> row = new CaseInsensitiveResultMap<>();
  100 + row.put("CONVERSATIONID", conversationId);
  101 + row.put("COUNT", count);
  102 + return row;
  103 + }
  104 +
  105 +}
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.chat;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  6 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  7 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  8 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
  12 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  14 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  16 +import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
  17 +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
  18 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
  19 +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  20 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
  21 +import cn.iocoder.yudao.module.ai.service.model.AiToolService;
  22 +import lombok.Data;
  23 +import org.junit.jupiter.api.AfterEach;
  24 +import org.junit.jupiter.api.Test;
  25 +import org.mockito.ArgumentCaptor;
  26 +import org.mockito.InjectMocks;
  27 +import org.mockito.Mock;
  28 +import org.springframework.ai.chat.model.ToolContext;
  29 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  30 +import org.springframework.ai.deepseek.api.DeepSeekApi;
  31 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletion;
  32 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionChunk;
  33 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionFinishReason;
  34 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage;
  35 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage.ChatCompletionFunction;
  36 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage.Role;
  37 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage.ToolCall;
  38 +import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionRequest;
  39 +import org.springframework.ai.tool.ToolCallback;
  40 +import org.springframework.ai.tool.function.FunctionToolCallback;
  41 +import org.springframework.ai.tool.resolution.ToolCallbackResolver;
  42 +import org.springframework.http.ResponseEntity;
  43 +import org.springframework.mock.web.MockHttpServletRequest;
  44 +import org.springframework.security.core.context.SecurityContextHolder;
  45 +import reactor.core.publisher.Flux;
  46 +
  47 +import java.util.List;
  48 +import java.util.Map;
  49 +import java.util.concurrent.CopyOnWriteArrayList;
  50 +import java.util.concurrent.atomic.AtomicLong;
  51 +import java.util.stream.Collectors;
  52 +
  53 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  54 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_STREAM_ERROR;
  55 +import static cn.iocoder.yudao.module.ai.util.AiUtils.TOOL_CONTEXT_LOGIN_USER;
  56 +import static cn.iocoder.yudao.module.ai.util.AiUtils.TOOL_CONTEXT_TENANT_ID;
  57 +import static java.util.Arrays.asList;
  58 +import static java.util.Collections.singletonList;
  59 +import static org.junit.jupiter.api.Assertions.*;
  60 +import static org.mockito.ArgumentMatchers.any;
  61 +import static org.mockito.ArgumentMatchers.eq;
  62 +import static org.mockito.Mockito.*;
  63 +
  64 +/**
  65 + * {@link AiChatMessageServiceImpl} 的单元测试类
  66 + *
  67 + * 使用真实的 {@link DeepSeekChatModel} 和工具调用,仅 mock 底层的 {@link DeepSeekApi}
  68 + *
  69 + * @author 芋道源码
  70 + */
  71 +public class AiChatMessageServiceImplTest extends BaseMockitoUnitTest {
  72 +
  73 + private static final String MODEL_NAME = "deepseek-test";
  74 + private static final Double TEMPERATURE = 0.5;
  75 + private static final Integer MAX_TOKENS = 1024;
  76 +
  77 + private static final String TOOL_NAME = "query_weather";
  78 + private static final String TOOL_RESULT = "杭州:晴,25℃";
  79 + private static final ChatCompletionMessage TOOL_CALL_MESSAGE = new ChatCompletionMessage("我来查询一下杭州的天气",
  80 + Role.ASSISTANT, null, null, singletonList(new ToolCall("call_1", "function",
  81 + new ChatCompletionFunction(TOOL_NAME, "{\"city\":\"杭州\"}"))));
  82 +
  83 + private static final List<String> TOOL_ANSWER_CHUNKS = asList("杭州今天", "晴,25℃");
  84 + private static final String TOOL_ANSWER = String.join("", TOOL_ANSWER_CHUNKS);
  85 + private static final List<String> PLAIN_ANSWER_CHUNKS = asList("你好,", "我是 AI 助手");
  86 + private static final String PLAIN_ANSWER = String.join("", PLAIN_ANSWER_CHUNKS);
  87 +
  88 + /**
  89 + * assistant 消息编号:先插入 user 消息(编号 1),再插入 assistant 消息(编号 2)
  90 + */
  91 + private static final Long ASSISTANT_MESSAGE_ID = 2L;
  92 +
  93 + @InjectMocks
  94 + private AiChatMessageServiceImpl chatMessageService;
  95 +
  96 + @Mock
  97 + private AiChatMessageMapper chatMessageMapper;
  98 + @Mock
  99 + private AiChatConversationService chatConversationService;
  100 + @Mock
  101 + private AiChatRoleService chatRoleService;
  102 + @Mock
  103 + private AiModelService modalService;
  104 + @Mock
  105 + private AiKnowledgeDocumentService knowledgeDocumentService;
  106 + @Mock
  107 + private AiToolService toolService;
  108 + @Mock
  109 + private ToolCallbackResolver toolCallbackResolver;
  110 + @Mock
  111 + private DeepSeekApi deepSeekApi;
  112 +
  113 + @AfterEach
  114 + public void after() {
  115 + SecurityContextHolder.clearContext();
  116 + TenantContextHolder.clear();
  117 + }
  118 +
  119 + @Test
  120 + public void testSendMessage_toolCall() {
  121 + // 准备参数
  122 + Long userId = randomLongId();
  123 + Long roleId = randomLongId();
  124 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  125 + .setContent("杭州天气怎么样?");
  126 + // mock 对话、模型
  127 + mockConversation(reqVO.getConversationId(), userId, roleId);
  128 + // mock 工具
  129 + List<ToolContext> toolContexts = new CopyOnWriteArrayList<>();
  130 + mockWeatherTool(roleId, toolContexts);
  131 + // mock 登录用户、租户
  132 + Long tenantId = randomLongId();
  133 + mockLoginUser(userId, tenantId);
  134 + // mock deepSeekApi 的方法:先请求工具,再返回回答
  135 + when(deepSeekApi.chatCompletionEntity(any())).thenReturn(buildChatCompletion(TOOL_CALL_MESSAGE),
  136 + buildChatCompletion(new ChatCompletionMessage(TOOL_ANSWER, Role.ASSISTANT)));
  137 +
  138 + // 调用
  139 + AiChatMessageSendRespVO respVO = chatMessageService.sendMessage(reqVO, userId);
  140 + // 断言
  141 + assertEquals(TOOL_ANSWER, respVO.getReceive().getContent());
  142 + ArgumentCaptor<ChatCompletionRequest> requestCaptor = ArgumentCaptor.forClass(ChatCompletionRequest.class);
  143 + verify(deepSeekApi, times(2)).chatCompletionEntity(requestCaptor.capture());
  144 + assertToolExecuted(requestCaptor.getAllValues(), toolContexts, userId, tenantId);
  145 + // 断言模型参数
  146 + ChatCompletionRequest request = requestCaptor.getAllValues().get(0);
  147 + assertEquals(MODEL_NAME, request.model());
  148 + assertEquals(TEMPERATURE, request.temperature());
  149 + assertEquals(MAX_TOKENS, request.maxTokens());
  150 + // 断言消息保存
  151 + assertAssistantMessageUpdated(TOOL_ANSWER);
  152 + }
  153 +
  154 + @Test
  155 + public void testSendMessage_withoutTool() {
  156 + // 准备参数
  157 + Long userId = randomLongId();
  158 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  159 + .setContent("你好");
  160 + // mock 对话、模型
  161 + mockConversation(reqVO.getConversationId(), userId, null);
  162 + // mock deepSeekApi 的方法
  163 + when(deepSeekApi.chatCompletionEntity(any()))
  164 + .thenReturn(buildChatCompletion(new ChatCompletionMessage(PLAIN_ANSWER, Role.ASSISTANT)));
  165 +
  166 + // 调用
  167 + AiChatMessageSendRespVO respVO = chatMessageService.sendMessage(reqVO, userId);
  168 + // 断言
  169 + assertEquals(PLAIN_ANSWER, respVO.getReceive().getContent());
  170 + verify(deepSeekApi).chatCompletionEntity(any());
  171 + // 断言消息保存
  172 + assertAssistantMessageUpdated(PLAIN_ANSWER);
  173 + }
  174 +
  175 + @Test
  176 + public void testSendMessage_toolCallThenModelError() {
  177 + // 准备参数
  178 + Long userId = randomLongId();
  179 + Long roleId = randomLongId();
  180 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  181 + .setContent("杭州天气怎么样?");
  182 + // mock 对话、模型
  183 + mockConversation(reqVO.getConversationId(), userId, roleId);
  184 + // mock 工具
  185 + List<ToolContext> toolContexts = new CopyOnWriteArrayList<>();
  186 + mockWeatherTool(roleId, toolContexts);
  187 + // mock 登录用户、租户
  188 + mockLoginUser(userId, randomLongId());
  189 + // mock deepSeekApi 的方法:先请求工具,再抛出异常
  190 + RuntimeException modelException = new IllegalStateException("模型服务异常");
  191 + when(deepSeekApi.chatCompletionEntity(any())).thenReturn(buildChatCompletion(TOOL_CALL_MESSAGE))
  192 + .thenThrow(modelException);
  193 +
  194 + // 调用,并断言异常
  195 + RuntimeException exception = assertThrows(RuntimeException.class,
  196 + () -> chatMessageService.sendMessage(reqVO, userId));
  197 + assertSame(modelException, exception);
  198 + // 断言工具已执行、消息未保存
  199 + verify(deepSeekApi, times(2)).chatCompletionEntity(any());
  200 + assertEquals(1, toolContexts.size());
  201 + verify(chatMessageMapper, never()).updateById(any(AiChatMessageDO.class));
  202 + }
  203 +
  204 + @Test
  205 + public void testSendChatMessageStream_toolCall() {
  206 + // 准备参数
  207 + Long userId = randomLongId();
  208 + Long roleId = randomLongId();
  209 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  210 + .setContent("杭州天气怎么样?");
  211 + // mock 对话、模型
  212 + mockConversation(reqVO.getConversationId(), userId, roleId);
  213 + // mock 工具
  214 + List<ToolContext> toolContexts = new CopyOnWriteArrayList<>();
  215 + mockWeatherTool(roleId, toolContexts);
  216 + // mock 登录用户、租户
  217 + Long tenantId = randomLongId();
  218 + mockLoginUser(userId, tenantId);
  219 + // mock deepSeekApi 的方法:先请求工具,再流式返回回答
  220 + when(deepSeekApi.chatCompletionStream(any())).thenReturn(Flux.just(buildChatCompletionChunk(TOOL_CALL_MESSAGE)),
  221 + buildAnswerChunks(TOOL_ANSWER_CHUNKS));
  222 +
  223 + // 调用
  224 + List<CommonResult<AiChatMessageSendRespVO>> results = chatMessageService
  225 + .sendChatMessageStream(reqVO, userId).collectList().block();
  226 + // 断言(工具调用轮次不下发)
  227 + assertNotNull(results);
  228 + assertTrue(results.stream().allMatch(CommonResult::isSuccess));
  229 + assertEquals(TOOL_ANSWER, joinContent(results));
  230 + ArgumentCaptor<ChatCompletionRequest> requestCaptor = ArgumentCaptor.forClass(ChatCompletionRequest.class);
  231 + verify(deepSeekApi, times(2)).chatCompletionStream(requestCaptor.capture());
  232 + assertToolExecuted(requestCaptor.getAllValues(), toolContexts, userId, tenantId);
  233 + // 断言消息保存
  234 + assertAssistantMessageUpdated(TOOL_ANSWER);
  235 + }
  236 +
  237 + @Test
  238 + public void testSendChatMessageStream_withoutTool() {
  239 + // 准备参数
  240 + Long userId = randomLongId();
  241 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  242 + .setContent("你好");
  243 + // mock 对话、模型
  244 + mockConversation(reqVO.getConversationId(), userId, null);
  245 + // mock deepSeekApi 的方法
  246 + when(deepSeekApi.chatCompletionStream(any())).thenReturn(buildAnswerChunks(PLAIN_ANSWER_CHUNKS));
  247 +
  248 + // 调用
  249 + List<CommonResult<AiChatMessageSendRespVO>> results = chatMessageService
  250 + .sendChatMessageStream(reqVO, userId).collectList().block();
  251 + // 断言
  252 + assertNotNull(results);
  253 + assertTrue(results.stream().allMatch(CommonResult::isSuccess));
  254 + assertEquals(PLAIN_ANSWER, joinContent(results));
  255 + verify(deepSeekApi).chatCompletionStream(any());
  256 + // 断言消息保存
  257 + assertAssistantMessageUpdated(PLAIN_ANSWER);
  258 + }
  259 +
  260 + @Test
  261 + public void testSendChatMessageStream_toolCallThenModelError() {
  262 + // 准备参数
  263 + Long userId = randomLongId();
  264 + Long roleId = randomLongId();
  265 + AiChatMessageSendReqVO reqVO = new AiChatMessageSendReqVO().setConversationId(randomLongId())
  266 + .setContent("杭州天气怎么样?");
  267 + // mock 对话、模型
  268 + mockConversation(reqVO.getConversationId(), userId, roleId);
  269 + // mock 工具
  270 + List<ToolContext> toolContexts = new CopyOnWriteArrayList<>();
  271 + mockWeatherTool(roleId, toolContexts);
  272 + // mock 登录用户、租户
  273 + mockLoginUser(userId, randomLongId());
  274 + // mock deepSeekApi 的方法:先请求工具,再返回异常
  275 + when(deepSeekApi.chatCompletionStream(any())).thenReturn(Flux.just(buildChatCompletionChunk(TOOL_CALL_MESSAGE)),
  276 + Flux.error(new IllegalStateException("模型服务异常")));
  277 +
  278 + // 调用
  279 + List<CommonResult<AiChatMessageSendRespVO>> results = chatMessageService
  280 + .sendChatMessageStream(reqVO, userId).collectList().block();
  281 + // 断言
  282 + assertNotNull(results);
  283 + assertEquals(CHAT_STREAM_ERROR.getCode(), CollUtil.getLast(results).getCode());
  284 + verify(deepSeekApi, times(2)).chatCompletionStream(any());
  285 + assertEquals(1, toolContexts.size());
  286 + // 断言消息删除(无内容)
  287 + verify(chatMessageMapper).deleteById(ASSISTANT_MESSAGE_ID);
  288 + verify(chatMessageMapper, never()).updateById(any(AiChatMessageDO.class));
  289 + }
  290 +
  291 + private void mockConversation(Long conversationId, Long userId, Long roleId) {
  292 + // mock 对话
  293 + AiChatConversationDO conversation = new AiChatConversationDO().setId(conversationId).setUserId(userId)
  294 + .setRoleId(roleId).setModelId(randomLongId()).setTemperature(TEMPERATURE).setMaxTokens(MAX_TOKENS);
  295 + when(chatConversationService.validateChatConversationExists(eq(conversationId))).thenReturn(conversation);
  296 + // mock 模型:真实的 DeepSeekChatModel
  297 + AiModelDO model = new AiModelDO().setId(conversation.getModelId()).setModel(MODEL_NAME)
  298 + .setPlatform(AiPlatformEnum.DEEP_SEEK.getPlatform());
  299 + when(modalService.validateModel(eq(model.getId()))).thenReturn(model);
  300 + when(modalService.getChatModel(eq(model.getId())))
  301 + .thenReturn(DeepSeekChatModel.builder().deepSeekApi(deepSeekApi).build());
  302 + // mock 消息插入:依次分配编号
  303 + AtomicLong messageId = new AtomicLong();
  304 + doAnswer(invocation -> {
  305 + invocation.<AiChatMessageDO>getArgument(0).setId(messageId.incrementAndGet());
  306 + return 1;
  307 + }).when(chatMessageMapper).insert(any(AiChatMessageDO.class));
  308 + }
  309 +
  310 + private void mockWeatherTool(Long roleId, List<ToolContext> toolContexts) {
  311 + // mock 工具:真实的 FunctionToolCallback
  312 + Long toolId = randomLongId();
  313 + ToolCallback toolCallback = FunctionToolCallback.<WeatherRequest, String>builder(TOOL_NAME,
  314 + (request, toolContext) -> {
  315 + toolContexts.add(toolContext);
  316 + return request.getCity() + ":晴,25℃";
  317 + }).description("查询城市天气").inputType(WeatherRequest.class).build();
  318 + when(chatRoleService.getChatRole(eq(roleId))).thenReturn(new AiChatRoleDO().setId(roleId)
  319 + .setToolIds(singletonList(toolId)));
  320 + when(toolService.getToolList(eq(singletonList(toolId))))
  321 + .thenReturn(singletonList(new AiToolDO().setId(toolId).setName(TOOL_NAME)));
  322 + when(toolCallbackResolver.resolve(eq(TOOL_NAME))).thenReturn(toolCallback);
  323 + }
  324 +
  325 + private static void mockLoginUser(Long userId, Long tenantId) {
  326 + SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(userId), new MockHttpServletRequest());
  327 + TenantContextHolder.setTenantId(tenantId);
  328 + }
  329 +
  330 + private static void assertToolExecuted(List<ChatCompletionRequest> requests, List<ToolContext> toolContexts,
  331 + Long userId, Long tenantId) {
  332 + // 断言首轮请求带上工具
  333 + assertEquals(1, requests.get(0).tools().size());
  334 + // 断言工具执行,并拿到登录用户、租户
  335 + assertEquals(1, toolContexts.size());
  336 + Map<String, Object> context = toolContexts.get(0).getContext();
  337 + assertEquals(userId, ((LoginUser) context.get(TOOL_CONTEXT_LOGIN_USER)).getId());
  338 + assertEquals(tenantId, context.get(TOOL_CONTEXT_TENANT_ID));
  339 + // 断言次轮请求回传工具结果
  340 + ChatCompletionMessage toolMessage = CollUtil.getLast(requests.get(1).messages());
  341 + assertEquals(Role.TOOL, toolMessage.role());
  342 + assertTrue(toolMessage.content().contains(TOOL_RESULT));
  343 + }
  344 +
  345 + private void assertAssistantMessageUpdated(String content) {
  346 + ArgumentCaptor<AiChatMessageDO> messageCaptor = ArgumentCaptor.forClass(AiChatMessageDO.class);
  347 + verify(chatMessageMapper).updateById(messageCaptor.capture());
  348 + assertEquals(ASSISTANT_MESSAGE_ID, messageCaptor.getValue().getId());
  349 + assertEquals(content, messageCaptor.getValue().getContent());
  350 + }
  351 +
  352 + private static String joinContent(List<CommonResult<AiChatMessageSendRespVO>> results) {
  353 + return results.stream().map(result -> result.getData().getReceive().getContent())
  354 + .collect(Collectors.joining());
  355 + }
  356 +
  357 + private static ResponseEntity<ChatCompletion> buildChatCompletion(ChatCompletionMessage message) {
  358 + ChatCompletion.Choice choice = new ChatCompletion.Choice(buildFinishReason(message), 0, message, null);
  359 + return ResponseEntity.ok(new ChatCompletion("chatcmpl-1", singletonList(choice), null, MODEL_NAME,
  360 + null, null, null));
  361 + }
  362 +
  363 + private static Flux<ChatCompletionChunk> buildAnswerChunks(List<String> contents) {
  364 + return Flux.fromIterable(contents)
  365 + .map(content -> buildChatCompletionChunk(new ChatCompletionMessage(content, Role.ASSISTANT)));
  366 + }
  367 +
  368 + private static ChatCompletionChunk buildChatCompletionChunk(ChatCompletionMessage delta) {
  369 + ChatCompletionChunk.ChunkChoice choice = new ChatCompletionChunk.ChunkChoice(buildFinishReason(delta),
  370 + 0, delta, null);
  371 + return new ChatCompletionChunk("chatcmpl-1", singletonList(choice), null, MODEL_NAME,
  372 + null, null, null, null);
  373 + }
  374 +
  375 + private static ChatCompletionFinishReason buildFinishReason(ChatCompletionMessage message) {
  376 + return CollUtil.isNotEmpty(message.toolCalls()) ? ChatCompletionFinishReason.TOOL_CALLS
  377 + : ChatCompletionFinishReason.STOP;
  378 + }
  379 +
  380 + @Data
  381 + public static class WeatherRequest {
  382 +
  383 + private String city;
  384 +
  385 + }
  386 +
  387 +}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression; 1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
7 import lombok.Data; 5 import lombok.Data;
8 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
10 import lombok.ToString; 8 import lombok.ToString;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 13
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
10 import lombok.ToString; 8 import lombok.ToString;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 13
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; 1 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
7 import lombok.Data; 5 import lombok.Data;
8 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
@@ -5,11 +5,11 @@ import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; @@ -5,11 +5,11 @@ import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction; 7 import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
8 -import cn.idev.excel.annotation.ExcelProperty;  
9 import lombok.AllArgsConstructor; 8 import lombok.AllArgsConstructor;
10 import lombok.Builder; 9 import lombok.Builder;
11 import lombok.Data; 10 import lombok.Data;
12 import lombok.NoArgsConstructor; 11 import lombok.NoArgsConstructor;
  12 +import org.apache.fesod.sheet.annotation.ExcelProperty;
13 13
14 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; 14 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
15 15
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 import java.util.List; 11 import java.util.List;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo; 1 package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
6 6
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; 1 package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; 5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
8 -import cn.idev.excel.annotation.ExcelProperty; 7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 import org.dromara.core.trans.anno.Trans; 9 import org.dromara.core.trans.anno.Trans;
10 import org.dromara.core.trans.constant.TransType; 10 import org.dromara.core.trans.constant.TransType;
11 import org.dromara.core.trans.vo.VO; 11 import org.dromara.core.trans.vo.VO;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2
3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java
@@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; 5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
8 -import cn.idev.excel.annotation.ExcelProperty;  
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 4 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment; 1 package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt; 1 package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.product.vo.category; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.product.vo.category;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; 1 package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 4 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in; 1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order; 1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns; 1 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer; 1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer;
2 2
  3 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 6 import lombok.*;
  7 +import org.apache.fesod.sheet.annotation.*;
  8 +
5 import java.math.BigDecimal; 9 import java.math.BigDecimal;
6 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
7 -import cn.idev.excel.annotation.*;  
8 -import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;  
9 -import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;  
10 11
11 @Schema(description = "管理后台 - ERP 客户 Response VO") 12 @Schema(description = "管理后台 - ERP 客户 Response VO")
12 @Data 13 @Data
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order; 1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out; 1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns; 1 package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckRespVO.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.erp.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.erp.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockRespVO.java
1 package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock; 1 package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 9
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportExcelVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem; 1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import jakarta.validation.constraints.NotBlank; 3 import jakarta.validation.constraints.NotBlank;
5 import jakarta.validation.constraints.Size; 4 import jakarta.validation.constraints.Size;
6 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 /** 8 /**
9 * FMS 辅助核算项目 Excel 导入 VO 9 * FMS 辅助核算项目 Excel 导入 VO
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem; 1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceExcelVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance; 1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import lombok.Data; 3 import lombok.Data;
  4 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 import java.math.BigDecimal; 7 import java.math.BigDecimal;
8 8
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportExcelVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject; 1 package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import jakarta.validation.constraints.NotBlank; 3 import jakarta.validation.constraints.NotBlank;
5 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
6 import lombok.Builder; 5 import lombok.Builder;
7 import lombok.Data; 6 import lombok.Data;
8 import lombok.NoArgsConstructor; 7 import lombok.NoArgsConstructor;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 /** 10 /**
11 * FMS 科目 Excel 导入 VO 11 * FMS 科目 Excel 导入 VO
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectRespVO.java
@@ -4,11 +4,11 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,11 +4,11 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.StringListConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.StringListConvert;
6 import cn.iocoder.yudao.module.fms.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.fms.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
8 -import cn.idev.excel.annotation.ExcelProperty;  
9 import com.fasterxml.jackson.annotation.JsonIgnore; 7 import com.fasterxml.jackson.annotation.JsonIgnore;
10 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
11 import lombok.Data; 9 import lombok.Data;
  10 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  11 +import org.apache.fesod.sheet.annotation.ExcelProperty;
12 12
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
14 import java.util.List; 14 import java.util.List;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/auxiliarybalance/FmsLedgerAuxiliaryBalanceRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarybalance; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarybalance;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 10
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/auxiliarydetail/FmsLedgerAuxiliaryDetailRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarydetail; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarydetail;
2 2
3 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDate; 10 import java.time.LocalDate;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/detail/FmsLedgerDetailExportRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import lombok.Data; 3 import lombok.Data;
  4 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 import java.math.BigDecimal; 7 import java.math.BigDecimal;
8 import java.time.LocalDate; 8 import java.time.LocalDate;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/detail/FmsLedgerDetailRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail;
2 2
3 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDate; 10 import java.time.LocalDate;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/general/FmsLedgerGeneralRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.general; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.general;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 9
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/quantitydetail/FmsLedgerQuantityDetailRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitydetail; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitydetail;
2 2
3 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDate; 10 import java.time.LocalDate;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/quantitygeneral/FmsLedgerQuantityGeneralRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitygeneral; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitygeneral;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnore;  
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnore;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.util.ArrayList; 10 import java.util.ArrayList;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/vo/subjectbalance/FmsLedgerSubjectBalanceRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.subjectbalance; 1 package cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.subjectbalance;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.util.ArrayList; 10 import java.util.ArrayList;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/report/vo/FmsReportItemRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.report.vo; 1 package cn.iocoder.yudao.module.fms.controller.admin.report.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 9
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/report/vo/balance/FmsBalanceSheetRowRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.report.vo.balance; 1 package cn.iocoder.yudao.module.fms.controller.admin.report.vo.balance;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 9
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/voucher/vo/FmsVoucherExportRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.voucher.vo; 1 package cn.iocoder.yudao.module.fms.controller.admin.voucher.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 -import cn.idev.excel.annotation.format.DateTimeFormat;  
6 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
  7 +import org.apache.fesod.sheet.annotation.format.DateTimeFormat;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/voucher/vo/FmsVoucherStatisticsRespVO.java
1 package cn.iocoder.yudao.module.fms.controller.admin.voucher.vo; 1 package cn.iocoder.yudao.module.fms.controller.admin.voucher.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 9
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/clock/HrmAttendanceClockRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.clock; 1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.clock;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants;
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/leave/HrmAttendanceLeaveRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.leave; 1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.leave;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants;
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/statistics/HrmAttendanceDailyDetailRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.statistics; 1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.statistics;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.clock.HrmAttendanceClockRespVO; 3 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.clock.HrmAttendanceClockRespVO;
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/statistics/HrmAttendanceMonthRecordRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.statistics; 1 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.statistics;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/employee/HrmEmployeeImportExcelVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.employee; 1 package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.employee;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; 3 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
6 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
7 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
@@ -14,6 +12,8 @@ import lombok.AllArgsConstructor; @@ -14,6 +12,8 @@ import lombok.AllArgsConstructor;
14 import lombok.Builder; 12 import lombok.Builder;
15 import lombok.Data; 13 import lombok.Data;
16 import lombok.NoArgsConstructor; 14 import lombok.NoArgsConstructor;
  15 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  16 +import org.apache.fesod.sheet.annotation.ExcelProperty;
17 17
18 import java.time.LocalDateTime; 18 import java.time.LocalDateTime;
19 19
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/employee/HrmEmployeeRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.employee; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.employee;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
10 import lombok.experimental.Accessors; 8 import lombok.experimental.Accessors;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 13
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/changerecord/HrmSalaryChangeRecordRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.changerecord; 1 package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.changerecord;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.option.HrmSalaryOptionValueVO; 5 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.option.HrmSalaryOptionValueVO;
8 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/employeeinfo/HrmSalaryEmployeeInfoRespVO.java
1 package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.employeeinfo; 1 package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.employeeinfo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.option.HrmSalaryOptionValueVO; 5 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.option.HrmSalaryOptionValueVO;
8 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.hrm.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.statistics; 1 package cn.iocoder.yudao.module.im.dal.mysql.statistics;
2 2
  3 +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap;
3 import org.apache.ibatis.annotations.Mapper; 4 import org.apache.ibatis.annotations.Mapper;
4 import org.apache.ibatis.annotations.Param; 5 import org.apache.ibatis.annotations.Param;
  6 +import org.apache.ibatis.annotations.ResultMap;
5 import org.apache.ibatis.annotations.Select; 7 import org.apache.ibatis.annotations.Select;
6 8
7 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
@@ -59,6 +61,7 @@ public interface ImStatisticsManagerMapper { @@ -59,6 +61,7 @@ public interface ImStatisticsManagerMapper {
59 @Select("SELECT DATE(create_time) AS date, COUNT(*) AS count FROM system_users " + 61 @Select("SELECT DATE(create_time) AS date, COUNT(*) AS count FROM system_users " +
60 "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " + 62 "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " +
61 "GROUP BY DATE(create_time)") 63 "GROUP BY DATE(create_time)")
  64 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
62 List<Map<String, Object>> selectNewUserDailyCount(@Param("beginTime") LocalDateTime beginTime, 65 List<Map<String, Object>> selectNewUserDailyCount(@Param("beginTime") LocalDateTime beginTime,
63 @Param("endTime") LocalDateTime endTime); 66 @Param("endTime") LocalDateTime endTime);
64 67
@@ -74,6 +77,7 @@ public interface ImStatisticsManagerMapper { @@ -74,6 +77,7 @@ public interface ImStatisticsManagerMapper {
74 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 77 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
75 " AND send_time >= #{beginTime} AND send_time < #{endTime}" + 78 " AND send_time >= #{beginTime} AND send_time < #{endTime}" +
76 ") t GROUP BY day") 79 ") t GROUP BY day")
  80 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
77 List<Map<String, Object>> selectActiveUserDailyCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); 81 List<Map<String, Object>> selectActiveUserDailyCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime);
78 82
79 // ==================== 群 ==================== 83 // ==================== 群 ====================
@@ -111,6 +115,7 @@ public interface ImStatisticsManagerMapper { @@ -111,6 +115,7 @@ public interface ImStatisticsManagerMapper {
111 " GROUP BY g.id" + 115 " GROUP BY g.id" +
112 ") t " + 116 ") t " +
113 "GROUP BY `range`") 117 "GROUP BY `range`")
  118 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
114 List<Map<String, Object>> selectGroupSizeDistribution(); 119 List<Map<String, Object>> selectGroupSizeDistribution();
115 120
116 // ==================== 消息 ==================== 121 // ==================== 消息 ====================
@@ -140,6 +145,7 @@ public interface ImStatisticsManagerMapper { @@ -140,6 +145,7 @@ public interface ImStatisticsManagerMapper {
140 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 145 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
141 " AND send_time >= #{beginTime} AND send_time < #{endTime} " + 146 " AND send_time >= #{beginTime} AND send_time < #{endTime} " +
142 "GROUP BY DATE(send_time)") 147 "GROUP BY DATE(send_time)")
  148 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
143 List<Map<String, Object>> selectPrivateMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, 149 List<Map<String, Object>> selectPrivateMessageDailyCount(@Param("beginTime") LocalDateTime beginTime,
144 @Param("endTime") LocalDateTime endTime); 150 @Param("endTime") LocalDateTime endTime);
145 151
@@ -150,6 +156,7 @@ public interface ImStatisticsManagerMapper { @@ -150,6 +156,7 @@ public interface ImStatisticsManagerMapper {
150 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 156 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
151 " AND send_time >= #{beginTime} AND send_time < #{endTime} " + 157 " AND send_time >= #{beginTime} AND send_time < #{endTime} " +
152 "GROUP BY DATE(send_time)") 158 "GROUP BY DATE(send_time)")
  159 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
153 List<Map<String, Object>> selectGroupMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, 160 List<Map<String, Object>> selectGroupMessageDailyCount(@Param("beginTime") LocalDateTime beginTime,
154 @Param("endTime") LocalDateTime endTime); 161 @Param("endTime") LocalDateTime endTime);
155 162
@@ -167,6 +174,7 @@ public interface ImStatisticsManagerMapper { @@ -167,6 +174,7 @@ public interface ImStatisticsManagerMapper {
167 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 174 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
168 " AND send_time >= #{beginTime} AND send_time < #{endTime}" + 175 " AND send_time >= #{beginTime} AND send_time < #{endTime}" +
169 ") t GROUP BY type") 176 ") t GROUP BY type")
  177 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
170 List<Map<String, Object>> selectMessageTypeDistribution(@Param("beginTime") LocalDateTime beginTime, 178 List<Map<String, Object>> selectMessageTypeDistribution(@Param("beginTime") LocalDateTime beginTime,
171 @Param("endTime") LocalDateTime endTime); 179 @Param("endTime") LocalDateTime endTime);
172 180
@@ -184,6 +192,7 @@ public interface ImStatisticsManagerMapper { @@ -184,6 +192,7 @@ public interface ImStatisticsManagerMapper {
184 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 192 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
185 " AND send_time >= #{beginTime} AND send_time < #{endTime}" + 193 " AND send_time >= #{beginTime} AND send_time < #{endTime}" +
186 ") t GROUP BY user_id ORDER BY messageCount DESC LIMIT #{limit}") 194 ") t GROUP BY user_id ORDER BY messageCount DESC LIMIT #{limit}")
  195 + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
187 List<Map<String, Object>> selectTopSenders(@Param("beginTime") LocalDateTime beginTime, 196 List<Map<String, Object>> selectTopSenders(@Param("beginTime") LocalDateTime beginTime,
188 @Param("endTime") LocalDateTime endTime, 197 @Param("endTime") LocalDateTime endTime,
189 @Param("limit") int limit); 198 @Param("limit") int limit);
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java
@@ -228,6 +228,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -228,6 +228,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
228 assertEquals(3L, ((Number) tops.get(0).get("messageCount")).longValue()); 228 assertEquals(3L, ((Number) tops.get(0).get("messageCount")).longValue());
229 assertEquals(2L, ((Number) tops.get(1).get("userId")).longValue()); 229 assertEquals(2L, ((Number) tops.get(1).get("userId")).longValue());
230 assertEquals(2L, ((Number) tops.get(1).get("messageCount")).longValue()); 230 assertEquals(2L, ((Number) tops.get(1).get("messageCount")).longValue());
  231 + // 断言:key 大小写不敏感,兼容 PostgreSQL 小写、Oracle 大写的列标签
  232 + assertEquals(1L, ((Number) tops.get(0).get("userid")).longValue());
  233 + assertEquals(3L, ((Number) tops.get(0).get("MESSAGECOUNT")).longValue());
231 } 234 }
232 235
233 // ========== 工具方法 ========== 236 // ========== 工具方法 ==========
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/config/vo/ConfigRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.config.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.config.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryRespVO.java
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/erp/vo/Demo03StudentErpRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.erp.vo; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.erp.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/inner/vo/Demo03StudentInnerRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.inner.vo; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.inner.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/normal/vo/Demo03StudentNormalRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.normal.vo; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.normal.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/job/vo/job/JobRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.infra.controller.admin.job.vo.job; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.infra.controller.admin.job.vo.job;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import jakarta.validation.constraints.NotNull; 7 import jakarta.validation.constraints.NotNull;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 13
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/job/vo/log/JobLogRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.job.vo.log; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.job.vo.log;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-infra/src/main/resources/codegen/java/controller/vo/importExcelVO.vm
1 package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; 1 package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm
@@ -19,7 +19,7 @@ import java.time.LocalDateTime; @@ -19,7 +19,7 @@ import java.time.LocalDateTime;
19 #end 19 #end
20 #end 20 #end
21 ## 处理 Excel 导出 21 ## 处理 Excel 导出
22 -import cn.idev.excel.annotation.*; 22 +import org.apache.fesod.sheet.annotation.*;
23 #foreach ($column in $columns) 23 #foreach ($column in $columns)
24 #if ("$!column.dictType" != "")## 有设置数据字典 24 #if ("$!column.dictType" != "")## 有设置数据字典
25 import ${DictFormatClassName}; 25 import ${DictFormatClassName};
yudao-module-infra/src/main/resources/codegen/java/dal/do.vm
@@ -15,7 +15,7 @@ import ${BaseDOClassName}; @@ -15,7 +15,7 @@ import ${BaseDOClassName};
15 ## 处理 Excel 导出 + Schema 注解(仅 DO 模式) 15 ## 处理 Excel 导出 + Schema 注解(仅 DO 模式)
16 #if ($voType == 20) 16 #if ($voType == 20)
17 import io.swagger.v3.oas.annotations.media.Schema; 17 import io.swagger.v3.oas.annotations.media.Schema;
18 -import cn.idev.excel.annotation.*; 18 +import org.apache.fesod.sheet.annotation.*;
19 #foreach ($column in $columns) 19 #foreach ($column in $columns)
20 #if ("$!column.dictType" != "")## 有设置数据字典 20 #if ("$!column.dictType" != "")## 有设置数据字典
21 import ${DictFormatClassName}; 21 import ${DictFormatClassName};
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_one/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vue2_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue2_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue2_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue2_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue2_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vue2_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue2_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-infra/src/test/resources/codegen/vue3_master_erp/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_master_inner/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_master_normal/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_one/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vue3_one/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_one_importEnable/java/InfraStudentImportExcelVO
1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; 1 package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty; 3 +import org.apache.fesod.sheet.annotation.ExcelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Builder; 5 import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
yudao-module-infra/src/test/resources/codegen/vue3_one_importEnable/java/InfraStudentRespVO
@@ -5,7 +5,7 @@ import lombok.*; @@ -5,7 +5,7 @@ import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 import org.springframework.format.annotation.DateTimeFormat; 6 import org.springframework.format.annotation.DateTimeFormat;
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 -import cn.idev.excel.annotation.*; 8 +import org.apache.fesod.sheet.annotation.*;
9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 9 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 10 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
11 11
yudao-module-infra/src/test/resources/codegen/vue3_tree/java/InfraCategoryRespVO
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.*; 4 import lombok.*;
5 import java.util.*; 5 import java.util.*;
6 -import cn.idev.excel.annotation.*; 6 +import org.apache.fesod.sheet.annotation.*;
7 7
8 @Schema(description = "管理后台 - 分类 Response VO") 8 @Schema(description = "管理后台 - 分类 Response VO")
9 @Data 9 @Data
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -50,6 +50,10 @@ @@ -50,6 +50,10 @@
50 <groupId>com.taosdata.jdbc</groupId> 50 <groupId>com.taosdata.jdbc</groupId>
51 <artifactId>taos-jdbcdriver</artifactId> 51 <artifactId>taos-jdbcdriver</artifactId>
52 </dependency> 52 </dependency>
  53 + <dependency>
  54 + <groupId>org.apache.iotdb</groupId>
  55 + <artifactId>iotdb-jdbc</artifactId>
  56 + </dependency>
53 57
54 <dependency> 58 <dependency>
55 <groupId>cn.iocoder.boot</groupId> 59 <groupId>cn.iocoder.boot</groupId>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceM @@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceM
10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO; 10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO;
11 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 11 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
13 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;  
14 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 13 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
15 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService; 14 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
16 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; 15 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
@@ -41,8 +40,6 @@ public class IotDeviceMessageController { @@ -41,8 +40,6 @@ public class IotDeviceMessageController {
41 private IotDeviceService deviceService; 40 private IotDeviceService deviceService;
42 @Resource 41 @Resource
43 private IotThingModelService thingModelService; 42 private IotThingModelService thingModelService;
44 - @Resource  
45 - private IotDeviceMessageMapper deviceMessageMapper;  
46 43
47 @GetMapping("/page") 44 @GetMapping("/page")
48 @Operation(summary = "获得设备消息分页") 45 @Operation(summary = "获得设备消息分页")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; 1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
6 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
7 import lombok.Builder; 6 import lombok.Builder;
8 import lombok.Data; 7 import lombok.Data;
9 import lombok.NoArgsConstructor; 8 import lombok.NoArgsConstructor;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 /** 11 /**
12 * 设备 Excel 导入 VO 12 * 设备 Excel 导入 VO
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; 1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.iot.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.iot.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor; @@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
11 /** 11 /**
12 * IoT 设备消息数据 DO 12 * IoT 设备消息数据 DO
13 * 13 *
14 - * 目前使用 TDengine 存储 14 + * 支持 TDengine、IoTDB 存储,由 yudao.iot.storage.type 选择
15 * 15 *
16 * @author alwayssuper 16 * @author alwayssuper
17 */ 17 */
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelProperty.java
@@ -21,6 +21,16 @@ import java.util.List; @@ -21,6 +21,16 @@ import java.util.List;
21 public class ThingModelProperty { 21 public class ThingModelProperty {
22 22
23 /** 23 /**
  24 + * STRUCT、ARRAY 等 JSON 属性值的长度上限:按 UTF-8 编码后的字节数计算,不是字符数
  25 + *
  26 + * 1. 写入前:JSON 字符串超过该长度时,转换为 null,不写入该属性
  27 + * 2. 各存储后端共用,例如说 TDengine 的 VARCHAR 长度
  28 + *
  29 + * 注意:TDengine 一行(所有列)有总长度上限,调大该长度,会减少一个产品可以定义的 STRUCT、ARRAY 属性数量
  30 + */
  31 + public static final int LENGTH_JSON = 1024;
  32 +
  33 + /**
24 * 属性标识符 34 * 属性标识符
25 */ 35 */
26 @NotEmpty(message = "属性标识符不能为空") 36 @NotEmpty(message = "属性标识符不能为空")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IoTDBSystemMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.IoTDBUtils;
  4 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.annotation.IoTDBDS;
  5 +import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +import org.apache.ibatis.annotations.Param;
  8 +import org.apache.ibatis.annotations.Select;
  9 +
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * IoTDB 系统信息 Mapper:只读取配置,用于启动前提检查
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Mapper
  19 +@IoTDBDS
  20 +@InterceptorIgnore(tenantLine = "true", dataPermission = "true") // 避免 SQL 解析,因为 JSqlParser 对 IoTDB 的 SQL 解析会报错
  21 +public interface IoTDBSystemMapper {
  22 +
  23 + @Select("SHOW VARIABLES")
  24 + List<Map<String, Object>> selectVariableList();
  25 +
  26 + @Select("SHOW DATANODES")
  27 + List<Map<String, Object>> selectDataNodeList();
  28 +
  29 + /**
  30 + * 获得指定 DataNode 的全部配置
  31 + *
  32 + * @param nodeId DataNode 编号,来自 SHOW DATANODES 的结果,不接收外部输入
  33 + * @return 配置列表
  34 + */
  35 + @Select("SHOW ALL CONFIGURATION ON ${nodeId}")
  36 + List<Map<String, Object>> selectAllConfigurationList(@Param("nodeId") Integer nodeId);
  37 +
  38 + /**
  39 + * 校验 IoTDB 的前提条件,不满足时抛出异常。只检查,不修改任何配置:
  40 + * 1. 数据库已预建:连接 URL 中的数据库不存在时,获取连接即失败
  41 + * 2. TimestampPrecision 为 ms
  42 + * 3. 所有 DataNode 为 Running,且 enable_partial_insert=false
  43 + */
  44 + default void verifyPrerequisites() {
  45 + IoTDBUtils.validateTimestampPrecision(selectVariableList());
  46 + List<Integer> nodeIds = IoTDBUtils.validateDataNodes(selectDataNodeList());
  47 + for (Integer nodeId : nodeIds) {
  48 + IoTDBUtils.validatePartialInsertDisabled(nodeId, selectAllConfigurationList(nodeId));
  49 + }
  50 + }
  51 +
  52 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDeviceMessageIoTDBCodec.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
  7 +
  8 +/**
  9 + * IoTDB 设备消息的编解码器
  10 + *
  11 + * 为什么需要编码?IoTDB 相同 (device_id, time) 再次写入时,NULL 值不会清空旧值,导致“整行替换”的语义不成立,
  12 + * 例如说同一设备、同一毫秒的两条消息(id 不同),会混合成一行。因此,所有 FIELD 都编码为非 NULL 值,每次写入都设置全部 FIELD
  13 + * (id 为 FIELD,原样存储,不编码):
  14 + * 1. Boolean:-1 为 null,0 为 false,1 为 true
  15 + * 2. 其它:n: 为 null,v: + 字符串为非 null 值。例如说:null -> "n:","" -> "v:",123 -> "v:123"
  16 + *
  17 + * 查询条件中的值,也需要使用相同的编码
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +public class IotDeviceMessageIoTDBCodec {
  22 +
  23 + public static final int BOOLEAN_NULL = -1;
  24 + public static final int BOOLEAN_FALSE = 0;
  25 + public static final int BOOLEAN_TRUE = 1;
  26 +
  27 + public static final String PREFIX_NULL = "n:";
  28 + public static final String PREFIX_VALUE = "v:";
  29 +
  30 + // ========== 消息 ==========
  31 +
  32 + public static IotDeviceMessageIoTDBRow encode(IotDeviceMessageDO message) {
  33 + Assert.notNull(message.getTs(), "消息的 ts 不能为空");
  34 + Assert.notNull(message.getDeviceId(), "消息的 deviceId 不能为空");
  35 + Assert.notEmpty(message.getId(), "消息的 id 不能为空");
  36 + return new IotDeviceMessageIoTDBRow().setTs(message.getTs())
  37 + .setDeviceId(encodeDeviceId(message.getDeviceId())).setId(message.getId())
  38 + .setUpstream(encodeBoolean(message.getUpstream())).setReply(encodeBoolean(message.getReply()))
  39 + .setMethod(encodeString(message.getMethod())).setIdentifier(encodeString(message.getIdentifier()))
  40 + .setRequestId(encodeString(message.getRequestId())).setServerId(encodeString(message.getServerId()))
  41 + .setParams(encodeString(toJsonString(message.getParams())))
  42 + .setData(encodeString(toJsonString(message.getData())))
  43 + .setMsg(encodeString(message.getMsg())).setReportTime(encodeString(message.getReportTime()))
  44 + .setTenantId(encodeString(message.getTenantId())).setCode(encodeString(message.getCode()));
  45 + }
  46 +
  47 + public static IotDeviceMessageDO decode(IotDeviceMessageIoTDBRow row) {
  48 + String reportTime = decodeString(row.getReportTime());
  49 + String tenantId = decodeString(row.getTenantId());
  50 + String code = decodeString(row.getCode());
  51 + return new IotDeviceMessageDO().setTs(row.getTs())
  52 + .setDeviceId(row.getDeviceId() != null ? Long.valueOf(row.getDeviceId()) : null).setId(row.getId())
  53 + .setUpstream(decodeBoolean(row.getUpstream())).setReply(decodeBoolean(row.getReply()))
  54 + .setMethod(decodeString(row.getMethod())).setIdentifier(decodeString(row.getIdentifier()))
  55 + .setRequestId(decodeString(row.getRequestId())).setServerId(decodeString(row.getServerId()))
  56 + .setParams(decodeString(row.getParams())).setData(decodeString(row.getData()))
  57 + .setMsg(decodeString(row.getMsg()))
  58 + .setReportTime(reportTime != null ? Long.valueOf(reportTime) : null)
  59 + .setTenantId(tenantId != null ? Long.valueOf(tenantId) : null)
  60 + .setCode(code != null ? Integer.valueOf(code) : null);
  61 + }
  62 +
  63 + /**
  64 + * 转换 params、data 为字符串:Service 写入前已转为 JSON 字符串,这里兼容其它对象
  65 + */
  66 + private static String toJsonString(Object value) {
  67 + if (value == null || value instanceof String) {
  68 + return (String) value;
  69 + }
  70 + return JsonUtils.toJsonString(value);
  71 + }
  72 +
  73 + // ========== 单个值 ==========
  74 +
  75 + public static String encodeDeviceId(Long deviceId) {
  76 + return deviceId != null ? deviceId.toString() : null;
  77 + }
  78 +
  79 + public static int encodeBoolean(Boolean value) {
  80 + if (value == null) {
  81 + return BOOLEAN_NULL;
  82 + }
  83 + return value ? BOOLEAN_TRUE : BOOLEAN_FALSE;
  84 + }
  85 +
  86 + /**
  87 + * 解码 Boolean
  88 + *
  89 + * @param value 存储值;为 null 时,说明该行不是本系统写入的,按 null 处理
  90 + * @return Boolean
  91 + */
  92 + public static Boolean decodeBoolean(Integer value) {
  93 + if (value == null || value == BOOLEAN_NULL) {
  94 + return null;
  95 + }
  96 + if (value == BOOLEAN_TRUE) {
  97 + return true;
  98 + }
  99 + if (value == BOOLEAN_FALSE) {
  100 + return false;
  101 + }
  102 + throw new IllegalArgumentException(StrUtil.format("无法解码 Boolean 存储值({})", value));
  103 + }
  104 +
  105 + public static String encodeString(Object value) {
  106 + if (value == null) {
  107 + return PREFIX_NULL;
  108 + }
  109 + return PREFIX_VALUE + value;
  110 + }
  111 +
  112 + /**
  113 + * 解码字符串
  114 + *
  115 + * @param value 存储值;为 null 时,说明该行不是本系统写入的,按 null 处理
  116 + * @return 字符串
  117 + */
  118 + public static String decodeString(String value) {
  119 + if (value == null || PREFIX_NULL.equals(value)) {
  120 + return null;
  121 + }
  122 + if (value.startsWith(PREFIX_VALUE)) {
  123 + return value.substring(PREFIX_VALUE.length());
  124 + }
  125 + throw new IllegalArgumentException(StrUtil.format("无法解码字符串存储值({})", value));
  126 + }
  127 +
  128 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDeviceMessageIoTDBDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.object.PageUtils;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
  12 +import cn.iocoder.yudao.module.iot.dal.storage.IotDeviceMessageDAO;
  13 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.IoTDBUtils;
  14 +import cn.iocoder.yudao.module.iot.framework.storage.config.IotStorageProperties;
  15 +import jakarta.annotation.Resource;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  18 +import org.springframework.stereotype.Repository;
  19 +
  20 +import java.util.*;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +import static cn.iocoder.yudao.module.iot.dal.iotdb.IotDeviceMessageIoTDBCodec.*;
  24 +
  25 +/**
  26 + * 基于 IoTDB(表模型)的 {@link IotDeviceMessageDAO} 实现类
  27 + *
  28 + * 一张全局表 device_message,身份为 (device_id, time),编解码见 {@link IotDeviceMessageIoTDBCodec}
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Repository
  33 +@ConditionalOnProperty(prefix = IotStorageProperties.PREFIX, name = "type", havingValue = "iotdb")
  34 +@Slf4j
  35 +public class IotDeviceMessageIoTDBDAO implements IotDeviceMessageDAO {
  36 +
  37 + private static final String CATEGORY_TAG = "TAG";
  38 + private static final String CATEGORY_FIELD = "FIELD";
  39 +
  40 + /**
  41 + * 唯一的 TAG 列
  42 + */
  43 + private static final String COLUMN_DEVICE_ID = "device_id";
  44 + /**
  45 + * 消息表的列:列名 -> 数据类型。除 {@link #COLUMN_DEVICE_ID} 为 TAG 外,其它都是 FIELD
  46 + */
  47 + private static final Map<String, String> COLUMN_TYPES = MapUtil.builder(new LinkedHashMap<String, String>())
  48 + .put(COLUMN_DEVICE_ID, "STRING").put("id", "STRING")
  49 + .put("upstream", "INT32").put("reply", "INT32")
  50 + .put("method", "STRING").put("identifier", "STRING").put("request_id", "STRING").put("server_id", "STRING")
  51 + .put("params", "STRING").put("data", "STRING").put("msg", "STRING")
  52 + .put("report_time", "STRING").put("tenant_id", "STRING").put("code", "STRING")
  53 + .build();
  54 +
  55 + @Resource
  56 + private IotDeviceMessageIoTDBMapper deviceMessageMapper;
  57 + @Resource
  58 + private IoTDBSystemMapper systemMapper;
  59 +
  60 + @Override
  61 + public void initialize() {
  62 + // 1. 校验前提条件:只读取配置,不修改
  63 + systemMapper.verifyPrerequisites();
  64 + // 2. 获得消息表的列:表不存在时,创建后再读取
  65 + List<Map<String, Object>> columns = getColumnList();
  66 + if (columns == null) {
  67 + deviceMessageMapper.createTable();
  68 + log.info("[initialize][设备消息表不存在,创建成功]");
  69 + columns = deviceMessageMapper.selectColumnList();
  70 + }
  71 + // 3. 校验表结构:不兼容时,需要人工处理,不会自动修改、删除
  72 + validateColumns(columns);
  73 + log.info("[initialize][设备消息表已就绪]");
  74 + }
  75 +
  76 + /**
  77 + * 获得消息表的列
  78 + *
  79 + * @return 列列表;表不存在时,返回 null
  80 + */
  81 + private List<Map<String, Object>> getColumnList() {
  82 + try {
  83 + return deviceMessageMapper.selectColumnList();
  84 + } catch (Exception exception) {
  85 + // 只有表不存在(550)返回 null;权限、连接等其它错误原样抛出
  86 + if (IoTDBUtils.isTableNotExists(exception)) {
  87 + return null;
  88 + }
  89 + throw exception;
  90 + }
  91 + }
  92 +
  93 + /**
  94 + * 校验消息表的结构:device_id 为唯一的 TAG,其它列都是类型一致的 FIELD
  95 + *
  96 + * 例如说,旧结构的 id 为 TAG,身份不同,且每条消息会产生一个 IoTDB 设备,所以不兼容
  97 + *
  98 + * @param columns DESC 的结果,包括 ColumnName、DataType、Category
  99 + */
  100 + private static void validateColumns(List<Map<String, Object>> columns) {
  101 + // 1. 解析 DB 里的列:列名 -> 类别、类型
  102 + Map<String, String> categories = new HashMap<>();
  103 + Map<String, String> types = new HashMap<>();
  104 + for (Map<String, Object> column : columns) {
  105 + String name = StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "ColumnName"));
  106 + categories.put(name, StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "Category")));
  107 + types.put(name, StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "DataType")));
  108 + }
  109 +
  110 + // 2.1 TAG 只能是 device_id:多出的 TAG 会改变身份
  111 + List<String> errors = new ArrayList<>();
  112 + Set<String> tags = new TreeSet<>();
  113 + categories.forEach((name, category) -> {
  114 + if (StrUtil.equalsIgnoreCase(category, CATEGORY_TAG)) {
  115 + tags.add(name);
  116 + }
  117 + });
  118 + if (!tags.equals(Collections.singleton(COLUMN_DEVICE_ID))) {
  119 + errors.add(StrUtil.format("TAG 只能为 {},当前为 {}", COLUMN_DEVICE_ID, tags));
  120 + }
  121 + // 2.2 各列的类别、类型
  122 + COLUMN_TYPES.forEach((name, type) -> {
  123 + if (!categories.containsKey(name)) {
  124 + errors.add(StrUtil.format("缺少列 {}", name));
  125 + return;
  126 + }
  127 + String category = COLUMN_DEVICE_ID.equals(name) ? CATEGORY_TAG : CATEGORY_FIELD;
  128 + if (!StrUtil.equalsIgnoreCase(categories.get(name), category)
  129 + || !StrUtil.equalsIgnoreCase(types.get(name), type)) {
  130 + errors.add(StrUtil.format("列 {} 应为 {} {},当前为 {} {}",
  131 + name, type, category, types.get(name), categories.get(name)));
  132 + }
  133 + });
  134 + if (errors.isEmpty()) {
  135 + return;
  136 + }
  137 + throw new IllegalStateException(StrUtil.format("[IoTDB 设备消息表] device_message 的结构与当前版本不兼容:{}。" +
  138 + "请人工处理(例如说,导出需要保留的数据后删除该表,或者更换数据库),应用不会自动修改、删除表",
  139 + String.join(";", errors)));
  140 + }
  141 +
  142 + @Override
  143 + public void insert(IotDeviceMessageDO message) {
  144 + deviceMessageMapper.insert(encode(message));
  145 + }
  146 +
  147 + @Override
  148 + public PageResult<IotDeviceMessageDO> selectPage(IotDeviceMessagePageReqVO reqVO) {
  149 + // 1. 编码查询条件:空字符串与 TDengine 保持一致,视为不过滤
  150 + String deviceId = encodeDeviceId(reqVO.getDeviceId());
  151 + String method = StrUtil.isNotEmpty(reqVO.getMethod()) ? encodeString(reqVO.getMethod()) : null;
  152 + Integer upstream = reqVO.getUpstream() != null ? encodeBoolean(reqVO.getUpstream()) : null;
  153 + Integer reply = reqVO.getReply() != null ? encodeBoolean(reqVO.getReply()) : null;
  154 + String identifier = StrUtil.isNotEmpty(reqVO.getIdentifier()) ? encodeString(reqVO.getIdentifier()) : null;
  155 + try {
  156 + // 2.1 查询总数
  157 + Long total = deviceMessageMapper.selectCount(deviceId, method, upstream, reply, identifier);
  158 + if (total == null || total == 0) {
  159 + return PageResult.empty();
  160 + }
  161 + // 2.2 查询列表:pageSize 为 -1 时,不分页
  162 + boolean pageable = ObjUtil.notEqual(reqVO.getPageSize(), PageParam.PAGE_SIZE_NONE);
  163 + List<IotDeviceMessageIoTDBRow> rows = deviceMessageMapper.selectList(deviceId, method, upstream, reply, identifier,
  164 + pageable ? PageUtils.getStart(reqVO) : null, pageable ? reqVO.getPageSize() : null);
  165 + return new PageResult<>(convertList(rows, IotDeviceMessageIoTDBCodec::decode), total);
  166 + } catch (Exception exception) {
  167 + if (IoTDBUtils.isTableNotExists(exception)) {
  168 + return PageResult.empty();
  169 + }
  170 + throw exception;
  171 + }
  172 + }
  173 +
  174 + @Override
  175 + public List<IotDeviceMessageDO> selectListByRequestIdsAndReply(Long deviceId, Collection<String> requestIds, Boolean reply) {
  176 + // reply 为空时,与 TDengine 的 reply = NULL 一致,匹配不到任何消息
  177 + if (reply == null) {
  178 + return Collections.emptyList();
  179 + }
  180 + // requestId 为空的消息,存储值为 n:,不应被 null 匹配到,所以过滤掉
  181 + List<String> encodedRequestIds = new ArrayList<>();
  182 + if (requestIds != null) {
  183 + requestIds.stream().filter(Objects::nonNull).distinct()
  184 + .forEach(requestId -> encodedRequestIds.add(encodeString(requestId)));
  185 + }
  186 + if (CollUtil.isEmpty(encodedRequestIds)) {
  187 + return Collections.emptyList();
  188 + }
  189 + List<IotDeviceMessageIoTDBRow> rows = deviceMessageMapper.selectListByRequestIdsAndReply(
  190 + encodeDeviceId(deviceId), encodedRequestIds, encodeBoolean(reply));
  191 + return convertList(rows, IotDeviceMessageIoTDBCodec::decode);
  192 + }
  193 +
  194 + @Override
  195 + public Long selectCountByCreateTime(Long createTime) {
  196 + return deviceMessageMapper.selectCountByCreateTime(createTime);
  197 + }
  198 +
  199 + @Override
  200 + public List<Map<String, Object>> selectDeviceMessageCountGroupByDate(Long startTime, Long endTime) {
  201 + List<Map<String, Object>> counts = deviceMessageMapper.selectCountGroupByHour(startTime, endTime);
  202 + return convertList(counts, count -> IotDeviceMessageDAO.buildMessageCount(
  203 + IoTDBUtils.getIgnoreCase(count, COUNT_TIME),
  204 + IoTDBUtils.getIgnoreCase(count, COUNT_UPSTREAM),
  205 + IoTDBUtils.getIgnoreCase(count, COUNT_DOWNSTREAM)));
  206 + }
  207 +
  208 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDeviceMessageIoTDBMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.annotation.IoTDBDS;
  4 +import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +import org.apache.ibatis.annotations.Param;
  7 +
  8 +import java.util.Collection;
  9 +import java.util.List;
  10 +import java.util.Map;
  11 +
  12 +/**
  13 + * IoTDB 设备消息 Mapper
  14 + *
  15 + * 注意:参数均为 {@link IotDeviceMessageIoTDBCodec} 编码后的存储值;分页通过手写 LIMIT/OFFSET 实现,不依赖 MyBatis Plus 分页插件
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Mapper
  20 +@IoTDBDS
  21 +@InterceptorIgnore(tenantLine = "true", dataPermission = "true") // 避免 SQL 解析,因为 JSqlParser 对 IoTDB 的 SQL 解析会报错;租户由 Service 层控制
  22 +public interface IotDeviceMessageIoTDBMapper {
  23 +
  24 + /**
  25 + * 获得消息表的列
  26 + *
  27 + * @return 列列表,包括 ColumnName、DataType、Category 等
  28 + */
  29 + List<Map<String, Object>> selectColumnList();
  30 +
  31 + void createTable();
  32 +
  33 + void insert(IotDeviceMessageIoTDBRow row);
  34 +
  35 + List<IotDeviceMessageIoTDBRow> selectList(@Param("deviceId") String deviceId,
  36 + @Param("method") String method,
  37 + @Param("upstream") Integer upstream,
  38 + @Param("reply") Integer reply,
  39 + @Param("identifier") String identifier,
  40 + @Param("offset") Integer offset,
  41 + @Param("limit") Integer limit);
  42 +
  43 + Long selectCount(@Param("deviceId") String deviceId,
  44 + @Param("method") String method,
  45 + @Param("upstream") Integer upstream,
  46 + @Param("reply") Integer reply,
  47 + @Param("identifier") String identifier);
  48 +
  49 + List<IotDeviceMessageIoTDBRow> selectListByRequestIdsAndReply(@Param("deviceId") String deviceId,
  50 + @Param("requestIds") Collection<String> requestIds,
  51 + @Param("reply") Integer reply);
  52 +
  53 + Long selectCountByCreateTime(@Param("createTime") Long createTime);
  54 +
  55 + List<Map<String, Object>> selectCountGroupByHour(@Param("startTime") Long startTime,
  56 + @Param("endTime") Long endTime);
  57 +
  58 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDeviceMessageIoTDBRow.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +/**
  7 + * IoTDB 设备消息表 device_message 的一行,字段为编码后的存储值
  8 + *
  9 + * 身份为 (deviceId, ts);编解码见 {@link IotDeviceMessageIoTDBCodec}
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Data
  14 +@Accessors(chain = true)
  15 +public class IotDeviceMessageIoTDBRow {
  16 +
  17 + /**
  18 + * 存储时间(毫秒),对应隐式 time 列
  19 + */
  20 + private Long ts;
  21 + /**
  22 + * 设备编号,唯一的 TAG
  23 + */
  24 + private String deviceId;
  25 + /**
  26 + * 消息编号,FIELD,原样存储
  27 + */
  28 + private String id;
  29 +
  30 + /**
  31 + * 是否上行:-1 为空,0 为否,1 为是
  32 + */
  33 + private Integer upstream;
  34 + /**
  35 + * 是否回复:-1 为空,0 为否,1 为是
  36 + */
  37 + private Integer reply;
  38 +
  39 + // ========== 以下字段,统一使用 n:/v: 编码 ==========
  40 +
  41 + private String method;
  42 + private String identifier;
  43 + private String requestId;
  44 + private String serverId;
  45 + private String params;
  46 + private String data;
  47 + private String msg;
  48 + private String reportTime;
  49 + private String tenantId;
  50 + private String code;
  51 +
  52 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDevicePropertyIoTDBDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.LocalDateTimeUtil;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  11 +import cn.iocoder.yudao.module.iot.dal.storage.IotDevicePropertyDAO;
  12 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
  13 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.IoTDBUtils;
  14 +import cn.iocoder.yudao.module.iot.framework.storage.config.IotStorageProperties;
  15 +import cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils;
  16 +import jakarta.annotation.Resource;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  19 +import org.springframework.stereotype.Repository;
  20 +
  21 +import java.util.*;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  24 +
  25 +/**
  26 + * 基于 IoTDB(表模型)的 {@link IotDevicePropertyDAO} 实现类
  27 + *
  28 + * 每个产品一张表 product_property_{productId},设备共用产品表,通过 device_id TAG 区分。
  29 + * 用户属性列统一为 p_ + 中立字段名,避免与 time、device_id、report_time 等内置列冲突。
  30 + *
  31 + * 与 TDengine 的差异:
  32 + * 1. STRING 没有长度,物模型 TEXT 的长度变化不会改表;长度由写入前的业务转换校验
  33 + * 2. INT、ENUM、BOOL 都映射为 INT32,三者之间的逻辑类型变化不会改表,历史数据保留
  34 + * 3. 物理类型变化通过 DROP + ADD 完成,旧列的历史数据不会保留
  35 + * 4. IoTDB 的 DDL 不参与事务,也无法回滚;执行中途失败时,再次定义即可收敛(ADD/DROP 均为幂等语句)
  36 + *
  37 + * @author 芋道源码
  38 + */
  39 +@Repository
  40 +@ConditionalOnProperty(prefix = IotStorageProperties.PREFIX, name = "type", havingValue = "iotdb")
  41 +@Slf4j
  42 +public class IotDevicePropertyIoTDBDAO implements IotDevicePropertyDAO {
  43 +
  44 + /**
  45 + * 用户属性列的前缀
  46 + */
  47 + public static final String COLUMN_PREFIX = "p_";
  48 +
  49 + public static final String TYPE_INT32 = "INT32";
  50 + public static final String TYPE_FLOAT = "FLOAT";
  51 + public static final String TYPE_DOUBLE = "DOUBLE";
  52 + public static final String TYPE_STRING = "STRING";
  53 + public static final String TYPE_TIMESTAMP = "TIMESTAMP";
  54 +
  55 + /**
  56 + * DESC 结果中,FIELD 列的类别
  57 + */
  58 + private static final String CATEGORY_FIELD = "FIELD";
  59 +
  60 + /**
  61 + * 物模型的数据类型,与 IoTDB 数据类型的映射关系
  62 + *
  63 + * @see <a href="https://iotdb.apache.org/zh/UserGuide/latest-Table/Background-knowledge/Data-Type.html">IoTDB 数据类型</a>
  64 + */
  65 + private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
  66 + .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TYPE_INT32)
  67 + .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TYPE_FLOAT)
  68 + .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TYPE_DOUBLE)
  69 + .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TYPE_INT32)
  70 + .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TYPE_INT32)
  71 + .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TYPE_STRING)
  72 + .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TYPE_TIMESTAMP)
  73 + .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TYPE_STRING)
  74 + .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TYPE_STRING)
  75 + .build();
  76 +
  77 + @Resource
  78 + private IotDevicePropertyIoTDBMapper devicePropertyMapper;
  79 + @Resource
  80 + private IoTDBSystemMapper systemMapper;
  81 +
  82 + @Override
  83 + public void initialize() {
  84 + // 产品属性表在产品发布、手动同步时创建,启动时只校验前提条件
  85 + systemMapper.verifyPrerequisites();
  86 + }
  87 +
  88 + @Override
  89 + public void defineProductPropertyTable(Long productId, List<IotThingModelDO> propertyThingModels) {
  90 + // 1.1 校验属性标识符,可以作为列名
  91 + IotStorageFieldUtils.validatePropertyIdentifiers(convertList(propertyThingModels, IotThingModelDO::getIdentifier));
  92 + List<IotDevicePropertyIoTDBMapper.Column> newColumns = buildColumnList(propertyThingModels);
  93 + // 1.2 解析 DB 里的用户属性列
  94 + Map<String, String> oldColumns = getUserColumnTypes(productId);
  95 +
  96 + // 2.1 情况一:如果是新增的时候,需要创建表
  97 + if (oldColumns == null) {
  98 + if (CollUtil.isEmpty(newColumns)) {
  99 + log.info("[defineProductPropertyTable][productId({}) 没有需要定义的属性]", productId);
  100 + return;
  101 + }
  102 + devicePropertyMapper.createTable(productId, newColumns);
  103 + return;
  104 + }
  105 + // 2.2 情况二:如果是修改的时候,需要更新表
  106 + Set<String> newColumnNames = new HashSet<>(convertList(newColumns, IotDevicePropertyIoTDBMapper.Column::getName));
  107 + // 2.2.1 删除不再需要的列
  108 + oldColumns.keySet().stream().filter(name -> !newColumnNames.contains(name))
  109 + .forEach(name -> devicePropertyMapper.dropColumn(productId, name));
  110 + for (IotDevicePropertyIoTDBMapper.Column column : newColumns) {
  111 + String oldType = oldColumns.get(column.getName());
  112 + // 2.2.2 新增的列
  113 + if (oldType == null) {
  114 + devicePropertyMapper.addColumn(productId, column);
  115 + continue;
  116 + }
  117 + // 2.2.3 物理类型变化的列:IoTDB 不支持修改列类型,只能 DROP + ADD,旧列的数据不会保留
  118 + if (!StrUtil.equalsIgnoreCase(oldType, column.getType())) {
  119 + log.warn("[defineProductPropertyTable][productId({}) 列({}) 类型从 {} 变为 {},删除后重建,历史数据不保留]",
  120 + productId, column.getName(), oldType, column.getType());
  121 + devicePropertyMapper.dropColumn(productId, column.getName());
  122 + devicePropertyMapper.addColumn(productId, column);
  123 + }
  124 + }
  125 + }
  126 +
  127 + /**
  128 + * 获得产品属性表的用户属性列
  129 + *
  130 + * @param productId 产品编号
  131 + * @return 列名与类型的映射;表不存在时,返回 null
  132 + */
  133 + private Map<String, String> getUserColumnTypes(Long productId) {
  134 + List<Map<String, Object>> columns;
  135 + try {
  136 + columns = devicePropertyMapper.selectColumnList(productId);
  137 + } catch (Exception exception) {
  138 + if (IoTDBUtils.isTableNotExists(exception)) {
  139 + return null;
  140 + }
  141 + throw exception;
  142 + }
  143 + // 只比较 p_ 开头的 FIELD 列:time、device_id、report_time 由系统维护,不参与差异
  144 + Map<String, String> userColumns = new LinkedHashMap<>();
  145 + for (Map<String, Object> column : columns) {
  146 + String name = StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "ColumnName"));
  147 + String category = StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "Category"));
  148 + if (StrUtil.startWith(name, COLUMN_PREFIX) && StrUtil.equalsIgnoreCase(category, CATEGORY_FIELD)) {
  149 + userColumns.put(name, StrUtil.toStringOrNull(IoTDBUtils.getIgnoreCase(column, "DataType")));
  150 + }
  151 + }
  152 + return userColumns;
  153 + }
  154 +
  155 + private List<IotDevicePropertyIoTDBMapper.Column> buildColumnList(List<IotThingModelDO> thingModels) {
  156 + return convertList(thingModels, thingModel -> {
  157 + String type = TYPE_MAPPING.get(thingModel.getProperty().getDataType());
  158 + if (type == null) {
  159 + throw new IllegalArgumentException(StrUtil.format("属性({}) 的数据类型({}) 不支持存储",
  160 + thingModel.getIdentifier(), thingModel.getProperty().getDataType()));
  161 + }
  162 + return new IotDevicePropertyIoTDBMapper.Column(buildColumnName(thingModel.getIdentifier()), type, null);
  163 + });
  164 + }
  165 +
  166 + @Override
  167 + public void insert(IotDeviceDO device, Map<String, Object> properties, Long ts, Long reportTime) {
  168 + // 校验属性标识符:在拼接 SQL 之前,避免保留字段、归一化后重名的属性写入
  169 + IotStorageFieldUtils.validatePropertyIdentifiers(properties.keySet());
  170 + if (CollUtil.isEmpty(properties)) {
  171 + return;
  172 + }
  173 + List<IotDevicePropertyIoTDBMapper.Column> columns = new ArrayList<>(properties.size());
  174 + properties.forEach((identifier, value) -> columns.add(
  175 + new IotDevicePropertyIoTDBMapper.Column(buildColumnName(identifier), null, convertValue(value))));
  176 + devicePropertyMapper.insert(device.getProductId(), String.valueOf(device.getId()), ts, reportTime, columns);
  177 + }
  178 +
  179 + /**
  180 + * 转换属性值为 IoTDB 驱动支持的类型
  181 + *
  182 + * 原因:ENUM、BOOL 业务转换后为 Byte(对应 TDengine 的 TINYINT),而 IoTDB 驱动不支持 setByte,需要转为 INT32 对应的 Integer
  183 + */
  184 + private static Object convertValue(Object value) {
  185 + if (value instanceof Byte) {
  186 + return ((Byte) value).intValue();
  187 + }
  188 + return value;
  189 + }
  190 +
  191 + @Override
  192 + public List<IotDevicePropertyRespVO> selectHistoryList(Long productId, IotDevicePropertyHistoryListReqVO reqVO) {
  193 + try {
  194 + return devicePropertyMapper.selectHistoryList(productId, buildColumnName(reqVO.getIdentifier()),
  195 + String.valueOf(reqVO.getDeviceId()),
  196 + LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]),
  197 + LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1]));
  198 + } catch (Exception exception) {
  199 + // 只有表不存在(产品尚未定义属性)返回空;列不存在等其它错误原样抛出
  200 + if (IoTDBUtils.isTableNotExists(exception)) {
  201 + return Collections.emptyList();
  202 + }
  203 + throw exception;
  204 + }
  205 + }
  206 +
  207 + /**
  208 + * 构建属性列名:p_ + 中立字段名
  209 + *
  210 + * @param identifier 属性标识符,调用前需已通过 {@link IotStorageFieldUtils#validatePropertyIdentifiers(Collection)} 校验
  211 + * @return 列名
  212 + */
  213 + public static String buildColumnName(String identifier) {
  214 + if (!IotStorageFieldUtils.isPropertyFieldName(identifier)) {
  215 + throw new IllegalArgumentException(StrUtil.format("属性标识符({}) 不能作为存储列", identifier));
  216 + }
  217 + return COLUMN_PREFIX + IotStorageFieldUtils.buildFieldName(identifier);
  218 + }
  219 +
  220 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/iotdb/IotDevicePropertyIoTDBMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.iotdb;
  2 +
  3 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
  4 +import cn.iocoder.yudao.module.iot.framework.iotdb.core.annotation.IoTDBDS;
  5 +import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
  6 +import lombok.AllArgsConstructor;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Param;
  11 +
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +/**
  16 + * IoTDB 设备属性 Mapper
  17 + *
  18 + * 注意:表名、列名需要动态拼接,只允许传入 Long 产品编号、DAO 归一化并校验后的列名、白名单内的类型;值统一使用 #{} 绑定
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Mapper
  23 +@IoTDBDS
  24 +@InterceptorIgnore(tenantLine = "true", dataPermission = "true") // 避免 SQL 解析,因为 JSqlParser 对 IoTDB 的 SQL 解析会报错;租户由 Service 层控制
  25 +public interface IotDevicePropertyIoTDBMapper {
  26 +
  27 + /**
  28 + * 获得产品属性表的列
  29 + *
  30 + * @param productId 产品编号
  31 + * @return 列列表,包括 ColumnName、DataType、Category 等
  32 + */
  33 + List<Map<String, Object>> selectColumnList(@Param("productId") Long productId);
  34 +
  35 + void createTable(@Param("productId") Long productId,
  36 + @Param("columns") List<Column> columns);
  37 +
  38 + void addColumn(@Param("productId") Long productId,
  39 + @Param("column") Column column);
  40 +
  41 + void dropColumn(@Param("productId") Long productId,
  42 + @Param("columnName") String columnName);
  43 +
  44 + void insert(@Param("productId") Long productId,
  45 + @Param("deviceId") String deviceId,
  46 + @Param("ts") Long ts,
  47 + @Param("reportTime") Long reportTime,
  48 + @Param("columns") List<Column> columns);
  49 +
  50 + List<IotDevicePropertyRespVO> selectHistoryList(@Param("productId") Long productId,
  51 + @Param("columnName") String columnName,
  52 + @Param("deviceId") String deviceId,
  53 + @Param("startTime") Long startTime,
  54 + @Param("endTime") Long endTime);
  55 +
  56 + /**
  57 + * 属性列
  58 + */
  59 + @Data
  60 + @NoArgsConstructor
  61 + @AllArgsConstructor
  62 + class Column {
  63 +
  64 + /**
  65 + * 列名,格式为 p_ + 中立字段名
  66 + */
  67 + private String name;
  68 + /**
  69 + * 数据类型,例如说 INT32、STRING;写入时为空
  70 + */
  71 + private String type;
  72 + /**
  73 + * 值;DDL 时为空
  74 + */
  75 + private Object value;
  76 +
  77 + }
  78 +
  79 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/storage/IotDeviceMessageDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.storage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
  6 +
  7 +import javax.annotation.Nullable;
  8 +import java.sql.Timestamp;
  9 +import java.util.*;
  10 +
  11 +/**
  12 + * IoT 设备【消息】日志的存储 DAO 接口
  13 + *
  14 + * 由 yudao.iot.storage.type 选择 TDengine、IoTDB 其中一个实现。各实现的消息身份不同:
  15 + * 1. TDengine:(deviceId, ts, id),按 ts DESC, id DESC 排序。同一设备、同一毫秒、不同 id 的消息都会保留
  16 + * 2. IoTDB:(deviceId, ts),id 为普通字段,按 ts DESC 排序。同一设备、同一毫秒再次写入时,后写的整行替换先写的,即使 id 不同。
  17 + * 原因:id 作为 TAG 时,每个设备下每出现一个新的消息 id,就产生一个 IoTDB 元数据设备,元数据随消息量无限增长
  18 + *
  19 + * 重复写入时:
  20 + * 1. 相同 (deviceId, ts, id):两者都只保留一行。IoTDB 由编码保证整行替换(null 字段也会清空旧值);TDengine 沿用原有写入语义
  21 + * 2. 重投、重发等重新获取 ts 的写入,按各自的物理键判断:TDengine 为 (deviceId, ts, id),与已有行都不同时新增,否则覆盖该行;
  22 + * IoTDB 为 (deviceId, ts),该设备该毫秒没有行时新增,已有行时覆盖该行(可能是另一条消息)
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +public interface IotDeviceMessageDAO {
  27 +
  28 + /**
  29 + * 按小时统计结果的 key:整点时间,类型为 {@link Timestamp}
  30 + */
  31 + String COUNT_TIME = "time";
  32 + /**
  33 + * 按小时统计结果的 key:上行消息数,类型为 {@link Long}
  34 + */
  35 + String COUNT_UPSTREAM = "upstream_count";
  36 + /**
  37 + * 按小时统计结果的 key:下行消息数,类型为 {@link Long}
  38 + */
  39 + String COUNT_DOWNSTREAM = "downstream_count";
  40 +
  41 + /**
  42 + * 初始化:系统启动时,校验存储后端的前提条件,并创建消息表
  43 + */
  44 + void initialize();
  45 +
  46 + /**
  47 + * 写入设备消息
  48 + *
  49 + * @param message 设备消息
  50 + */
  51 + void insert(IotDeviceMessageDO message);
  52 +
  53 + /**
  54 + * 获得设备消息分页
  55 + *
  56 + * 注意:与现有行为保持一致,不按 reqVO.times 过滤
  57 + *
  58 + * @param reqVO 分页查询条件
  59 + * @return 设备消息分页;消息表不存在时,返回空分页
  60 + */
  61 + PageResult<IotDeviceMessageDO> selectPage(IotDeviceMessagePageReqVO reqVO);
  62 +
  63 + /**
  64 + * 按照 requestIds 批量查询消息
  65 + *
  66 + * 各存储后端保持一致:requestIds 中的 null 忽略、重复去重;忽略后为空,或 reply 为 null 时,返回空列表
  67 + *
  68 + * @param deviceId 设备编号
  69 + * @param requestIds 请求编号集合
  70 + * @param reply 是否回复消息
  71 + * @return 消息列表
  72 + */
  73 + List<IotDeviceMessageDO> selectListByRequestIdsAndReply(Long deviceId, Collection<String> requestIds, Boolean reply);
  74 +
  75 + /**
  76 + * 统计设备消息数量
  77 + *
  78 + * @param createTime 创建时间(毫秒),如果为空,则统计所有消息数量
  79 + * @return 消息数量
  80 + */
  81 + Long selectCountByCreateTime(@Nullable Long createTime);
  82 +
  83 + /**
  84 + * 按照时间范围(小时),统计设备的消息数量
  85 + *
  86 + * @param startTime 开始时间(毫秒),包含
  87 + * @param endTime 结束时间(毫秒),包含
  88 + * @return 每个整点的统计,key 见 {@link #COUNT_TIME}、{@link #COUNT_UPSTREAM}、{@link #COUNT_DOWNSTREAM}
  89 + */
  90 + List<Map<String, Object>> selectDeviceMessageCountGroupByDate(Long startTime, Long endTime);
  91 +
  92 + /**
  93 + * 构建按小时统计的一行结果,统一各存储后端的 key 与 Java 类型
  94 + *
  95 + * @param time 整点时间,支持 Timestamp、毫秒数值
  96 + * @param upstreamCount 上行消息数,支持任意 Number
  97 + * @param downstreamCount 下行消息数,支持任意 Number
  98 + * @return 统计结果
  99 + */
  100 + @SuppressWarnings("PatternVariableCanBeUsed") // 保持 JDK8 兼容性
  101 + static Map<String, Object> buildMessageCount(Object time, Object upstreamCount, Object downstreamCount) {
  102 + // 1. 整点时间
  103 + Timestamp timestamp;
  104 + if (time instanceof Timestamp) {
  105 + timestamp = (Timestamp) time;
  106 + } else if (time instanceof Date) {
  107 + timestamp = new Timestamp(((Date) time).getTime());
  108 + } else if (time instanceof Number) {
  109 + timestamp = new Timestamp(((Number) time).longValue());
  110 + } else {
  111 + throw new IllegalArgumentException("无法转换为整点时间:" + time);
  112 + }
  113 + // 2. 上下行数量:SUM、COUNT 等聚合结果的 Number 子类因库而异,统一转为 Long
  114 + Map<String, Object> count = new HashMap<>();
  115 + count.put(COUNT_TIME, timestamp);
  116 + count.put(COUNT_UPSTREAM, upstreamCount != null ? ((Number) upstreamCount).longValue() : 0L);
  117 + count.put(COUNT_DOWNSTREAM, downstreamCount != null ? ((Number) downstreamCount).longValue() : 0L);
  118 + return count;
  119 + }
  120 +
  121 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/storage/IotDevicePropertyDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.storage;
  2 +
  3 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  7 +
  8 +import java.util.List;
  9 +import java.util.Map;
  10 +
  11 +// TODO DONE @AI:storage 包下,是不是加个 package-info.java,说明这个包的作用?已新增 package-info.java
  12 +/**
  13 + * IoT 设备【属性】历史数据的存储 DAO 接口
  14 + *
  15 + * 由 yudao.iot.storage.type 选择 TDengine、IoTDB 其中一个实现。
  16 + * 厂商相关的字段映射、DDL、字段差异、缺表判断等,都在实现类中处理。
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public interface IotDevicePropertyDAO {
  21 +
  22 + /**
  23 + * 初始化:系统启动时,校验存储后端的前提条件
  24 + */
  25 + void initialize();
  26 +
  27 + /**
  28 + * 定义(创建或修改)产品的属性表结构
  29 + *
  30 + * @param productId 产品编号
  31 + * @param propertyThingModels 产品的属性类物模型列表
  32 + */
  33 + void defineProductPropertyTable(Long productId, List<IotThingModelDO> propertyThingModels);
  34 +
  35 + /**
  36 + * 写入设备属性。只记录本次上报的属性
  37 + *
  38 + * @param device 设备
  39 + * @param properties 属性,key 为物模型 identifier,value 为 Service 转换后的值
  40 + * @param ts 存储时间(毫秒)
  41 + * @param reportTime 上报时间(毫秒)
  42 + */
  43 + void insert(IotDeviceDO device, Map<String, Object> properties, Long ts, Long reportTime);
  44 +
  45 + /**
  46 + * 查询设备属性的历史数据,按存储时间倒序
  47 + *
  48 + * 调用前,Service 已经校验:设备存在(包括逻辑删除)、属性标识符合法、时间范围完整
  49 + *
  50 + * @param productId 产品编号
  51 + * @param reqVO 查询条件
  52 + * @return 历史数据;属性表不存在时,返回空列表
  53 + */
  54 + List<IotDevicePropertyRespVO> selectHistoryList(Long productId, IotDevicePropertyHistoryListReqVO reqVO);
  55 +
  56 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/storage/package-info.java 0 → 100644
  1 +/**
  2 + * iot 模块的时序数据存储 DAO 接口:设备属性、设备消息的统一契约,与具体的时序库无关
  3 + *
  4 + * 1. Service 只依赖本包的 DAO 接口
  5 + * 2. 实现类分别在 dal/tdengine、dal/iotdb 包下,由 yudao.iot.storage.type 统一选择其中一个
  6 + */
  7 +package cn.iocoder.yudao.module.iot.dal.storage;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageTDengineDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.tdengine;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
  8 +import cn.iocoder.yudao.module.iot.dal.storage.IotDeviceMessageDAO;
  9 +import cn.iocoder.yudao.module.iot.framework.storage.config.IotStorageProperties;
  10 +import com.baomidou.mybatisplus.core.metadata.IPage;
  11 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  12 +import jakarta.annotation.Resource;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  15 +import org.springframework.stereotype.Repository;
  16 +
  17 +import java.util.Collection;
  18 +import java.util.Collections;
  19 +import java.util.List;
  20 +import java.util.Map;
  21 +import java.util.Objects;
  22 +import java.util.stream.Collectors;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +/**
  27 + * 基于 TDengine 的 {@link IotDeviceMessageDAO} 实现类
  28 + *
  29 + * 一张超级表 device_message,每个设备一张子表 device_message_{deviceId}
  30 + *
  31 + * @author 芋道源码
  32 + */
  33 +@Repository
  34 +@ConditionalOnProperty(prefix = IotStorageProperties.PREFIX, name = "type", havingValue = "tdengine", matchIfMissing = true)
  35 +@Slf4j
  36 +public class IotDeviceMessageTDengineDAO implements IotDeviceMessageDAO {
  37 +
  38 + @Resource
  39 + private IotDeviceMessageMapper deviceMessageMapper;
  40 +
  41 + @Override
  42 + public void initialize() {
  43 + if (StrUtil.isNotEmpty(deviceMessageMapper.showSTable())) {
  44 + log.info("[initialize][设备消息超级表已存在,创建跳过]");
  45 + return;
  46 + }
  47 + log.info("[initialize][设备消息超级表不存在,创建开始...]");
  48 + deviceMessageMapper.createSTable();
  49 + log.info("[initialize][设备消息超级表不存在,创建成功]");
  50 + }
  51 +
  52 + @Override
  53 + public void insert(IotDeviceMessageDO message) {
  54 + deviceMessageMapper.insert(message);
  55 + }
  56 +
  57 + @Override
  58 + public PageResult<IotDeviceMessageDO> selectPage(IotDeviceMessagePageReqVO reqVO) {
  59 + try {
  60 + IPage<IotDeviceMessageDO> page = deviceMessageMapper.selectPage(
  61 + new Page<>(reqVO.getPageNo(), reqVO.getPageSize()), reqVO);
  62 + return new PageResult<>(fillDeviceId(page.getRecords(), reqVO.getDeviceId()), page.getTotal());
  63 + } catch (Exception exception) {
  64 + if (StrUtil.contains(exception.getMessage(), "Table does not exist")) {
  65 + return PageResult.empty();
  66 + }
  67 + throw exception;
  68 + }
  69 + }
  70 +
  71 + @Override
  72 + public List<IotDeviceMessageDO> selectListByRequestIdsAndReply(Long deviceId, Collection<String> requestIds, Boolean reply) {
  73 + // reply 为空时,reply = NULL 匹配不到任何消息,无需查询
  74 + if (reply == null || requestIds == null) {
  75 + return Collections.emptyList();
  76 + }
  77 + // 与 IoTDB 保持一致:忽略 null、去重,避免 IN 列表中绑定 NULL
  78 + List<String> validRequestIds = requestIds.stream().filter(Objects::nonNull).distinct()
  79 + .collect(Collectors.toList());
  80 + if (CollUtil.isEmpty(validRequestIds)) {
  81 + return Collections.emptyList();
  82 + }
  83 + return fillDeviceId(deviceMessageMapper.selectListByRequestIdsAndReply(deviceId, validRequestIds, reply), deviceId);
  84 + }
  85 +
  86 + /**
  87 + * 补全消息的设备编号
  88 + *
  89 + * 原因:查询的是设备子表 device_message_{deviceId},SELECT 未包含 device_id 标签,读出的 deviceId 为空
  90 + *
  91 + * @param messages 消息列表
  92 + * @param deviceId 设备编号
  93 + * @return 消息列表
  94 + */
  95 + private static List<IotDeviceMessageDO> fillDeviceId(List<IotDeviceMessageDO> messages, Long deviceId) {
  96 + if (messages != null) {
  97 + messages.forEach(message -> message.setDeviceId(deviceId));
  98 + }
  99 + return messages;
  100 + }
  101 +
  102 + @Override
  103 + public Long selectCountByCreateTime(Long createTime) {
  104 + return deviceMessageMapper.selectCountByCreateTime(createTime);
  105 + }
  106 +
  107 + @Override
  108 + public List<Map<String, Object>> selectDeviceMessageCountGroupByDate(Long startTime, Long endTime) {
  109 + List<Map<String, Object>> counts = deviceMessageMapper.selectDeviceMessageCountGroupByDate(startTime, endTime);
  110 + return convertList(counts, count -> IotDeviceMessageDAO.buildMessageCount(
  111 + count.get(COUNT_TIME), count.get(COUNT_UPSTREAM), count.get(COUNT_DOWNSTREAM)));
  112 + }
  113 +
  114 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyTDengineDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.tdengine;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
  12 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
  13 +import cn.iocoder.yudao.module.iot.dal.storage.IotDevicePropertyDAO;
  14 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
  15 +import cn.iocoder.yudao.module.iot.framework.storage.config.IotStorageProperties;
  16 +import cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils;
  17 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  18 +import jakarta.annotation.Resource;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  21 +import org.springframework.stereotype.Repository;
  22 +
  23 +import java.util.*;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  26 +
  27 +/**
  28 + * 基于 TDengine 的 {@link IotDevicePropertyDAO} 实现类
  29 + *
  30 + * 每个产品一张超级表 product_property_{productId},每个设备一张子表 device_property_{deviceId}
  31 + *
  32 + * @author 芋道源码
  33 + */
  34 +@Repository
  35 +@ConditionalOnProperty(prefix = IotStorageProperties.PREFIX, name = "type", havingValue = "tdengine", matchIfMissing = true)
  36 +@Slf4j
  37 +public class IotDevicePropertyTDengineDAO implements IotDevicePropertyDAO {
  38 +
  39 + /**
  40 + * 物模型的数据类型,与 TDengine 数据类型的映射关系
  41 + *
  42 + * @see <a href="https://docs.taosdata.com/reference/taos-sql/data-type/">TDEngine 数据类型</a>
  43 + */
  44 + private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
  45 + .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
  46 + .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
  47 + .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
  48 + .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT)
  49 + .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT)
  50 + .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_VARCHAR)
  51 + .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
  52 + .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_VARCHAR)
  53 + .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_VARCHAR)
  54 + .build();
  55 +
  56 + @Resource
  57 + private IotDevicePropertyMapper devicePropertyMapper;
  58 +
  59 + @Override
  60 + public void initialize() {
  61 + // 产品属性表在产品发布、手动同步时创建,启动时无需初始化
  62 + }
  63 +
  64 + @Override
  65 + public void defineProductPropertyTable(Long productId, List<IotThingModelDO> propertyThingModels) {
  66 + // 1.1 校验属性标识符,可以作为字段名
  67 + IotStorageFieldUtils.validatePropertyIdentifiers(convertList(propertyThingModels, IotThingModelDO::getIdentifier));
  68 + // 1.2 解析 DB 里的字段
  69 + List<TDengineTableField> oldFields = new ArrayList<>();
  70 + try {
  71 + oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(productId));
  72 + } catch (Exception e) {
  73 + if (!isTableNotExists(e)) {
  74 + throw e;
  75 + }
  76 + }
  77 +
  78 + // 2.1 情况一:如果是新增的时候,需要创建表
  79 + List<TDengineTableField> newFields = buildTableFieldList(propertyThingModels);
  80 + if (CollUtil.isEmpty(oldFields)) {
  81 + if (CollUtil.isEmpty(newFields)) {
  82 + log.info("[defineProductPropertyTable][productId({}) 没有需要定义的属性]", productId);
  83 + return;
  84 + }
  85 + devicePropertyMapper.createProductPropertySTable(productId, newFields);
  86 + return;
  87 + }
  88 + // 2.2 情况二:如果是修改的时候,需要更新表
  89 + devicePropertyMapper.alterProductPropertySTable(productId, oldFields, newFields);
  90 + }
  91 +
  92 + private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
  93 + return convertList(thingModels, thingModel -> {
  94 + TDengineTableField field = new TDengineTableField(
  95 + IotStorageFieldUtils.buildFieldName(thingModel.getIdentifier()),
  96 + TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
  97 + String dataType = thingModel.getProperty().getDataType();
  98 + if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
  99 + field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
  100 + } else if (ObjectUtils.equalsAny(dataType, IotDataSpecsDataTypeEnum.STRUCT.getDataType(),
  101 + IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
  102 + field.setLength(ThingModelProperty.LENGTH_JSON);
  103 + }
  104 + return field;
  105 + });
  106 + }
  107 +
  108 + @Override
  109 + public void insert(IotDeviceDO device, Map<String, Object> properties, Long ts, Long reportTime) {
  110 + // 校验属性标识符:在拼接 SQL 之前,避免保留字段、归一化后重名的属性写入
  111 + IotStorageFieldUtils.validatePropertyIdentifiers(properties.keySet());
  112 + devicePropertyMapper.insert(device, properties, ts, reportTime);
  113 + }
  114 +
  115 + @Override
  116 + public List<IotDevicePropertyRespVO> selectHistoryList(Long productId, IotDevicePropertyHistoryListReqVO reqVO) {
  117 + try {
  118 + return devicePropertyMapper.selectListByHistory(reqVO);
  119 + } catch (Exception exception) {
  120 + if (isTableNotExists(exception)) {
  121 + return Collections.emptyList();
  122 + }
  123 + throw exception;
  124 + }
  125 + }
  126 +
  127 + private static boolean isTableNotExists(Exception exception) {
  128 + return StrUtil.contains(exception.getMessage(), "Table does not exist");
  129 + }
  130 +
  131 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -46,6 +46,11 @@ public interface ErrorCodeConstants { @@ -46,6 +46,11 @@ public interface ErrorCodeConstants {
46 ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能"); 46 ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能");
47 ErrorCode DEVICE_REGISTER_SECRET_INVALID = new ErrorCode(1_050_003_211, "产品密钥验证失败"); 47 ErrorCode DEVICE_REGISTER_SECRET_INVALID = new ErrorCode(1_050_003_211, "产品密钥验证失败");
48 ErrorCode DEVICE_REGISTER_ALREADY_EXISTS = new ErrorCode(1_050_003_212, "设备已存在,不允许重复注册"); 48 ErrorCode DEVICE_REGISTER_ALREADY_EXISTS = new ErrorCode(1_050_003_212, "设备已存在,不允许重复注册");
  49 + // 设备属性存储相关错误码 1-050-003-300
  50 + ErrorCode DEVICE_PROPERTY_HISTORY_TIMES_INVALID = new ErrorCode(1_050_003_300, "设备属性历史的时间范围,必须包含开始、结束两个时间");
  51 + ErrorCode DEVICE_PROPERTY_FIELD_NAME_INVALID = new ErrorCode(1_050_003_301, "属性标识符({}) 不合法,无法作为存储字段");
  52 + ErrorCode DEVICE_PROPERTY_FIELD_NAME_RESERVED = new ErrorCode(1_050_003_302, "属性标识符({}) 与存储保留字段({}) 冲突");
  53 + ErrorCode DEVICE_PROPERTY_FIELD_NAME_DUPLICATE = new ErrorCode(1_050_003_303, "属性标识符({}) 与({}) 的存储字段({}) 重复");
49 54
50 // ========== 产品分类 1-050-004-000 ========== 55 // ========== 产品分类 1-050-004-000 ==========
51 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); 56 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iotdb/core/IoTDBUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.iotdb.core;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import org.apache.iotdb.rpc.StatementExecutionException;
  6 +
  7 +import java.sql.SQLException;
  8 +import java.util.*;
  9 +
  10 +/**
  11 + * IoTDB 工具类
  12 + *
  13 + * 1. 异常分类:识别“表不存在”
  14 + * 2. 启动前提检查:只读取配置并校验,不修改 IoTDB 的任何配置
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public class IoTDBUtils {
  19 +
  20 + /**
  21 + * IoTDB 状态码 - 表不存在
  22 + */
  23 + public static final int STATUS_CODE_TABLE_NOT_EXISTS = 550;
  24 +
  25 + /**
  26 + * 要求的时间精度:毫秒
  27 + */
  28 + public static final String TIMESTAMP_PRECISION_MS = "ms";
  29 + /**
  30 + * 要求关闭的配置项:部分写入。开启时,一行中类型错误的列会失败,其它列仍然写入
  31 + */
  32 + public static final String CONFIG_ENABLE_PARTIAL_INSERT = "enable_partial_insert";
  33 + /**
  34 + * DataNode 的运行状态
  35 + */
  36 + public static final String DATA_NODE_STATUS_RUNNING = "Running";
  37 +
  38 + // ========== 异常分类 ==========
  39 +
  40 + /**
  41 + * 判断异常是否为“表不存在”
  42 + *
  43 + * 只识别明确的 550 状态码,并沿 cause 链查找:外层 SQLException 的 errorCode 可能为 0,
  44 + * 真实状态码在内层的 SQLException 或 {@link StatementExecutionException} 中。
  45 + * 列不存在(616)、类型不匹配(507)、权限等其它错误,都返回 false,由调用方原样抛出。
  46 + *
  47 + * @param throwable 异常
  48 + * @return 是否表不存在
  49 + */
  50 + @SuppressWarnings("PatternVariableCanBeUsed") // 保持 JDK8 兼容性
  51 + public static boolean isTableNotExists(Throwable throwable) {
  52 + Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<>());
  53 + for (Throwable cause = throwable; cause != null && visited.add(cause); cause = cause.getCause()) {
  54 + if (cause instanceof StatementExecutionException
  55 + && ((StatementExecutionException) cause).getStatusCode() == STATUS_CODE_TABLE_NOT_EXISTS) {
  56 + return true;
  57 + }
  58 + if (cause instanceof SQLException
  59 + && ((SQLException) cause).getErrorCode() == STATUS_CODE_TABLE_NOT_EXISTS) {
  60 + return true;
  61 + }
  62 + }
  63 + return false;
  64 + }
  65 +
  66 + // ========== 启动前提检查 ==========
  67 +
  68 + /**
  69 + * 校验时间精度为毫秒
  70 + *
  71 + * @param variables SHOW VARIABLES 的结果
  72 + */
  73 + public static void validateTimestampPrecision(List<Map<String, Object>> variables) {
  74 + Map<String, Object> variable = CollUtil.findOne(variables,
  75 + row -> StrUtil.equalsIgnoreCase(getString(row, "Variable"), "TimestampPrecision"));
  76 + if (variable == null) {
  77 + throw new IllegalStateException("[IoTDB 前提检查] 无法读取 TimestampPrecision,请确认账号有查询权限");
  78 + }
  79 + String precision = getString(variable, "Value");
  80 + if (!StrUtil.equals(TIMESTAMP_PRECISION_MS, precision)) {
  81 + throw new IllegalStateException(StrUtil.format(
  82 + "[IoTDB 前提检查] TimestampPrecision 必须为 ms,当前为 {}", precision));
  83 + }
  84 + }
  85 +
  86 + /**
  87 + * 校验所有 DataNode 都处于运行状态,并返回它们的编号
  88 + *
  89 + * @param dataNodes SHOW DATANODES 的结果
  90 + * @return DataNode 编号列表
  91 + */
  92 + public static List<Integer> validateDataNodes(List<Map<String, Object>> dataNodes) {
  93 + if (CollUtil.isEmpty(dataNodes)) {
  94 + throw new IllegalStateException("[IoTDB 前提检查] 无法读取 DataNode 列表,请确认账号有查询权限");
  95 + }
  96 + List<Integer> nodeIds = new ArrayList<>(dataNodes.size());
  97 + for (Map<String, Object> dataNode : dataNodes) {
  98 + Object nodeId = getIgnoreCase(dataNode, "NodeID");
  99 + String status = getString(dataNode, "Status");
  100 + if (!(nodeId instanceof Number)) {
  101 + throw new IllegalStateException(StrUtil.format("[IoTDB 前提检查] 无法识别 DataNode 编号:{}", dataNode));
  102 + }
  103 + if (!StrUtil.equalsIgnoreCase(DATA_NODE_STATUS_RUNNING, status)) {
  104 + throw new IllegalStateException(StrUtil.format(
  105 + "[IoTDB 前提检查] DataNode({}) 状态为 {},必须全部为 Running 才能确认部分写入配置", nodeId, status));
  106 + }
  107 + nodeIds.add(((Number) nodeId).intValue());
  108 + }
  109 + return nodeIds;
  110 + }
  111 +
  112 + /**
  113 + * 校验 DataNode 关闭了部分写入(enable_partial_insert=false)
  114 + *
  115 + * @param nodeId DataNode 编号
  116 + * @param configurations SHOW ALL CONFIGURATION ON nodeId 的结果
  117 + */
  118 + public static void validatePartialInsertDisabled(Integer nodeId, List<Map<String, Object>> configurations) {
  119 + Map<String, Object> configuration = CollUtil.findOne(configurations,
  120 + row -> StrUtil.equals(getString(row, "name"), CONFIG_ENABLE_PARTIAL_INSERT));
  121 + if (configuration == null) {
  122 + throw new IllegalStateException(StrUtil.format(
  123 + "[IoTDB 前提检查] DataNode({}) 无法读取 {} 配置,请确认 IoTDB 版本与账号权限", nodeId, CONFIG_ENABLE_PARTIAL_INSERT));
  124 + }
  125 + String value = getString(configuration, "value");
  126 + if (!StrUtil.equalsIgnoreCase(Boolean.FALSE.toString(), value)) {
  127 + throw new IllegalStateException(StrUtil.format(
  128 + "[IoTDB 前提检查] DataNode({}) 的 {} 必须为 false,当前为 {}。请修改所有 DataNode 的配置文件,应用不会自动修改",
  129 + nodeId, CONFIG_ENABLE_PARTIAL_INSERT, value));
  130 + }
  131 + }
  132 +
  133 + /**
  134 + * 忽略大小写,获得结果行的列值。原因:IoTDB 系统语句的列名为 NodeID、Variable 等,不同版本大小写可能不同
  135 + */
  136 + public static Object getIgnoreCase(Map<String, Object> row, String column) {
  137 + for (Map.Entry<String, Object> entry : row.entrySet()) {
  138 + if (StrUtil.equalsIgnoreCase(entry.getKey(), column)) {
  139 + return entry.getValue();
  140 + }
  141 + }
  142 + return null;
  143 + }
  144 +
  145 + private static String getString(Map<String, Object> row, String column) {
  146 + Object value = getIgnoreCase(row, column);
  147 + return value != null ? value.toString() : null;
  148 + }
  149 +
  150 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iotdb/core/annotation/IoTDBDS.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.iotdb.core.annotation;
  2 +
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
  4 +
  5 +import java.lang.annotation.*;
  6 +
  7 +/**
  8 + * IoTDB 数据源
  9 + *
  10 + * 对应 spring.datasource.dynamic.datasource.iotdb 条目
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Target({ElementType.TYPE, ElementType.METHOD})
  15 +@Retention(RetentionPolicy.RUNTIME)
  16 +@Documented
  17 +@DS("iotdb")
  18 +public @interface IoTDBDS {
  19 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iotdb/package-info.java 0 → 100644
  1 +/**
  2 + * iot 模块的 IoTDB 拓展封装
  3 + */
  4 +package cn.iocoder.yudao.module.iot.framework.iotdb;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/config/IotStorageConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.config;
  2 +
  3 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  4 +import org.springframework.context.annotation.Configuration;
  5 +
  6 +// TODO DONE @AI:按照项目的习惯,这个是不是不用写单测?已移除重复的装配单测 IotStorageConfigurationTest,实际装配由实库验收覆盖
  7 +/**
  8 + * IoT 时序存储的配置类
  9 + *
  10 + * 由 yudao.iot.storage.type 选择存储后端,对应的 DAO 实现通过 @ConditionalOnProperty 装配
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Configuration(proxyBeanMethods = false)
  15 +@EnableConfigurationProperties(IotStorageProperties.class)
  16 +public class IotStorageConfiguration {
  17 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/config/IotStorageInitRunner.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.storage.IotDeviceMessageDAO;
  4 +import cn.iocoder.yudao.module.iot.dal.storage.IotDevicePropertyDAO;
  5 +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
  6 +import jakarta.annotation.Resource;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.boot.ApplicationArguments;
  9 +import org.springframework.boot.ApplicationRunner;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.sql.DataSource;
  13 +
  14 +// TODO DONE @AI:按照项目的习惯,这个是不是不用写单测?已移除独立单测 IotStorageInitRunnerTest,关键的缺数据源防回落 master 断言并入实库验收
  15 +/**
  16 + * IoT 时序存储的初始化 Runner:校验数据源、前提条件,并创建消息表
  17 + *
  18 + * 只初始化 yudao.iot.storage.type 选中的存储后端。失败时退出系统,避免带着不可用的存储运行
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Component
  23 +@Slf4j
  24 +public class IotStorageInitRunner implements ApplicationRunner {
  25 +
  26 + @Resource
  27 + private IotStorageProperties storageProperties;
  28 + @Resource
  29 + private DataSource dataSource;
  30 +
  31 + @Resource
  32 + private IotDevicePropertyDAO devicePropertyDAO;
  33 + @Resource
  34 + private IotDeviceMessageDAO deviceMessageDAO;
  35 +
  36 + @Override
  37 + public void run(ApplicationArguments args) {
  38 + try {
  39 + initialize();
  40 + } catch (Exception ex) {
  41 + // 初始化失败时打印错误消息并退出系统
  42 + log.error("[run][IoT 时序存储({}) 初始化失败,系统无法正常运行,即将退出]", storageProperties.getType(), ex);
  43 + System.exit(1);
  44 + }
  45 + }
  46 +
  47 + /**
  48 + * 初始化选中的存储后端
  49 + */
  50 + public void initialize() {
  51 + // 1. 校验数据源已配置:dynamic-datasource 找不到数据源时,会回退到 master,必须提前拦截
  52 + validateDataSourceExists();
  53 + // 2. 初始化 DAO
  54 + devicePropertyDAO.initialize();
  55 + deviceMessageDAO.initialize();
  56 + log.info("[initialize][IoT 时序存储({}) 初始化完成]", storageProperties.getType());
  57 + }
  58 +
  59 + private void validateDataSourceExists() {
  60 + String dataSourceName = storageProperties.getType().getDataSourceName();
  61 + if (!(dataSource instanceof DynamicRoutingDataSource)) {
  62 + throw new IllegalStateException(String.format("[IoT 时序存储] 需要使用 dynamic-datasource 多数据源,当前为 %s",
  63 + dataSource.getClass().getName()));
  64 + }
  65 + if (!((DynamicRoutingDataSource) dataSource).getDataSources().containsKey(dataSourceName)) {
  66 + throw new IllegalStateException(String.format("[IoT 时序存储] 未配置数据源 spring.datasource.dynamic.datasource.%s," +
  67 + "请检查 yudao.iot.storage.type=%s 对应的数据源配置", dataSourceName, storageProperties.getType().getType()));
  68 + }
  69 + }
  70 +
  71 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/config/IotStorageProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.framework.storage.core.enums.IotStorageTypeEnum;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import org.springframework.boot.context.properties.ConfigurationProperties;
  7 +import org.springframework.validation.annotation.Validated;
  8 +
  9 +/**
  10 + * IoT 时序数据存储的配置项
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@ConfigurationProperties(prefix = IotStorageProperties.PREFIX)
  15 +@Validated
  16 +@Data
  17 +public class IotStorageProperties {
  18 +
  19 + public static final String PREFIX = "yudao.iot.storage";
  20 +
  21 + /**
  22 + * 存储类型,同时决定属性历史、消息日志的存储后端
  23 + *
  24 + * 写错值时,配置绑定失败,应用启动失败
  25 + */
  26 + @NotNull(message = "IoT 存储类型不能为空")
  27 + private IotStorageTypeEnum type = IotStorageTypeEnum.TDENGINE;
  28 +
  29 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/core/enums/IotStorageTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.core.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IoT 时序数据(属性历史、消息日志)的存储类型枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum IotStorageTypeEnum {
  14 +
  15 + TDENGINE("tdengine", "tdengine"),
  16 + IOTDB("iotdb", "iotdb");
  17 +
  18 + /**
  19 + * 类型,对应 yudao.iot.storage.type 配置项
  20 + */
  21 + private final String type;
  22 + /**
  23 + * 数据源名,对应 spring.datasource.dynamic.datasource 下的条目
  24 + */
  25 + private final String dataSourceName;
  26 +
  27 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/core/util/IotStorageFieldUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.core.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  5 +
  6 +import java.util.Collection;
  7 +import java.util.HashMap;
  8 +import java.util.Locale;
  9 +import java.util.Map;
  10 +import java.util.Set;
  11 +import java.util.regex.Pattern;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  14 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  15 +
  16 +/**
  17 + * IoT 时序存储的字段名工具类
  18 + *
  19 + * 属性标识符(identifier)统一归一化为小写下划线的“中立字段名”,各存储后端在此基础上映射物理列名:
  20 + * 1. TDengine:直接使用中立字段名
  21 + * 2. IoTDB:使用 p_ + 中立字段名,避免与 time 等内置列冲突
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +public class IotStorageFieldUtils {
  26 +
  27 + /**
  28 + * 系统字段 - 存储时间
  29 + */
  30 + public static final String FIELD_TS = "ts";
  31 + /**
  32 + * 系统字段 - 上报时间
  33 + */
  34 + public static final String FIELD_REPORT_TIME = "report_time";
  35 + /**
  36 + * 系统字段 - 设备编号
  37 + */
  38 + public static final String FIELD_DEVICE_ID = "device_id";
  39 +
  40 + /**
  41 + * 保留字段名:属性标识符归一化后,不允许与它们相同
  42 + */
  43 + public static final Set<String> RESERVED_FIELD_NAMES = SetUtils.asSet(FIELD_TS, FIELD_REPORT_TIME, FIELD_DEVICE_ID);
  44 +
  45 + /**
  46 + * 中立字段名的格式:只允许小写字母、数字、下划线,且以字母开头
  47 + *
  48 + * 动态拼接到 SQL 结构名(列名)前,必须通过该格式校验
  49 + */
  50 + private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("^[a-z][a-z0-9_]{0,63}$");
  51 +
  52 + /**
  53 + * 构建中立字段名:转为小写下划线,需要和建表、写入、查询保持一致。
  54 + * 例如:PT -> pt,PfT -> pf_t。
  55 + *
  56 + * @param identifier 属性标识符
  57 + * @return 中立字段名
  58 + */
  59 + public static String buildFieldName(String identifier) {
  60 + if (StrUtil.isBlank(identifier)) {
  61 + return identifier;
  62 + }
  63 + return StrUtil.toUnderlineCase(identifier).toLowerCase(Locale.ROOT);
  64 + }
  65 +
  66 + /**
  67 + * 判断属性标识符,归一化后是否为合法的属性字段名(格式合法,且不是保留字段)
  68 + *
  69 + * @param identifier 属性标识符
  70 + * @return 是否合法
  71 + */
  72 + public static boolean isPropertyFieldName(String identifier) {
  73 + String fieldName = buildFieldName(identifier);
  74 + return fieldName != null && FIELD_NAME_PATTERN.matcher(fieldName).matches()
  75 + && !RESERVED_FIELD_NAMES.contains(fieldName);
  76 + }
  77 +
  78 + /**
  79 + * 校验属性标识符列表,可以作为存储字段。define、insert 拼接 SQL 之前,都需要校验原始标识符:
  80 + * 1. 归一化后格式合法
  81 + * 2. 归一化后不是 ts、report_time、device_id 保留字段
  82 + * 3. 归一化后不重复,例如说 fooBar 与 foo_bar
  83 + *
  84 + * @param identifiers 属性标识符列表
  85 + */
  86 + public static void validatePropertyIdentifiers(Collection<String> identifiers) {
  87 + Map<String, String> fieldNames = new HashMap<>();
  88 + for (String identifier : identifiers) {
  89 + String fieldName = buildFieldName(identifier);
  90 + if (fieldName == null || !FIELD_NAME_PATTERN.matcher(fieldName).matches()) {
  91 + throw exception(DEVICE_PROPERTY_FIELD_NAME_INVALID, identifier);
  92 + }
  93 + if (RESERVED_FIELD_NAMES.contains(fieldName)) {
  94 + throw exception(DEVICE_PROPERTY_FIELD_NAME_RESERVED, identifier, fieldName);
  95 + }
  96 + String existsIdentifier = fieldNames.putIfAbsent(fieldName, identifier);
  97 + if (existsIdentifier != null) {
  98 + throw exception(DEVICE_PROPERTY_FIELD_NAME_DUPLICATE, identifier, existsIdentifier, fieldName);
  99 + }
  100 + }
  101 + }
  102 +
  103 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/storage/package-info.java 0 → 100644
  1 +/**
  2 + * iot 模块的时序数据存储封装:通过 yudao.iot.storage.type 选择 TDengine 或 IoTDB
  3 + */
  4 +package cn.iocoder.yudao.module.iot.framework.storage;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitRunner.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.tdengine.config;  
2 -  
3 -import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;  
4 -import lombok.RequiredArgsConstructor;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -import org.springframework.boot.ApplicationArguments;  
7 -import org.springframework.boot.ApplicationRunner;  
8 -import org.springframework.stereotype.Component;  
9 -  
10 -/**  
11 - * TDengine 表初始化的 Configuration  
12 - *  
13 - * @author alwayssuper  
14 - */  
15 -@Component  
16 -@RequiredArgsConstructor  
17 -@Slf4j  
18 -public class TDengineTableInitRunner implements ApplicationRunner {  
19 -  
20 - private final IotDeviceMessageService deviceMessageService;  
21 -  
22 - @Override  
23 - public void run(ApplicationArguments args) {  
24 - try {  
25 - // 初始化设备消息表  
26 - deviceMessageService.defineDeviceMessageStable();  
27 - } catch (Exception ex) {  
28 - // 初始化失败时打印错误消息并退出系统  
29 - log.error("[run][TDengine初始化设备消息表结构失败,系统无法正常运行,即将退出]", ex);  
30 - System.exit(1);  
31 - }  
32 - }  
33 -  
34 -}  
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableField.java
1 package cn.iocoder.yudao.module.iot.framework.tdengine.core; 1 package cn.iocoder.yudao.module.iot.framework.tdengine.core;
2 2
3 -import cn.hutool.core.util.StrUtil; 3 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
5 import lombok.Data; 5 import lombok.Data;
6 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
7 7
8 -import java.util.Locale;  
9 -  
10 /** 8 /**
11 * TDEngine 表字段 9 * TDEngine 表字段
12 */ 10 */
@@ -30,9 +28,9 @@ public class TDengineTableField { @@ -30,9 +28,9 @@ public class TDengineTableField {
30 public static final String TYPE_TIMESTAMP = "TIMESTAMP"; 28 public static final String TYPE_TIMESTAMP = "TIMESTAMP";
31 29
32 /** 30 /**
33 - * 字段长度 - VARCHAR 默认长度 31 + * 字段长度 - VARCHAR 默认长度,与 STRUCT、ARRAY 等 JSON 属性的长度上限保持一致
34 */ 32 */
35 - public static final int LENGTH_VARCHAR = 1024; 33 + public static final int LENGTH_VARCHAR = ThingModelProperty.LENGTH_JSON;
36 34
37 /** 35 /**
38 * 注释 - TAG 字段 36 * 注释 - TAG 字段
@@ -64,15 +62,4 @@ public class TDengineTableField { @@ -64,15 +62,4 @@ public class TDengineTableField {
64 this.type = type; 62 this.type = type;
65 } 63 }
66 64
67 - /**  
68 - * 构建字段名:TDengine 默认会将字段名转为小写,需要和建表、写入、查询保持一致。  
69 - * 例如:PT -> pt,PfT -> pf_t。  
70 - */  
71 - public static String buildFieldName(String field) {  
72 - if (StrUtil.isBlank(field)) {  
73 - return field;  
74 - }  
75 - return StrUtil.toUnderlineCase(field).toLowerCase(Locale.ROOT);  
76 - }  
77 -  
78 } 65 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageService.java
@@ -21,13 +21,6 @@ import java.util.List; @@ -21,13 +21,6 @@ import java.util.List;
21 public interface IotDeviceMessageService { 21 public interface IotDeviceMessageService {
22 22
23 /** 23 /**
24 - * 初始化设备消息的 TDengine 超级表  
25 - *  
26 - * 系统启动时,会自动初始化一次  
27 - */  
28 - void defineDeviceMessageStable();  
29 -  
30 - /**  
31 * 发送设备消息 24 * 发送设备消息
32 * 25 *
33 * @param message 消息(“codec(编解码)字段” 部分字段) 26 * @param message 消息(“codec(编解码)字段” 部分字段)
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
@@ -25,12 +25,10 @@ import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqD @@ -25,12 +25,10 @@ import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqD
25 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 25 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
26 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 26 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
27 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO; 27 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
28 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper; 28 +import cn.iocoder.yudao.module.iot.dal.storage.IotDeviceMessageDAO;
29 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 29 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
30 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; 30 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
31 import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService; 31 import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
32 -import com.baomidou.mybatisplus.core.metadata.IPage;  
33 -import com.baomidou.mybatisplus.extension.plugins.pagination.Page;  
34 import com.google.common.base.Objects; 32 import com.google.common.base.Objects;
35 import jakarta.annotation.Resource; 33 import jakarta.annotation.Resource;
36 import lombok.extern.slf4j.Slf4j; 34 import lombok.extern.slf4j.Slf4j;
@@ -67,22 +65,11 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -67,22 +65,11 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
67 private IotOtaTaskRecordService otaTaskRecordService; 65 private IotOtaTaskRecordService otaTaskRecordService;
68 66
69 @Resource 67 @Resource
70 - private IotDeviceMessageMapper deviceMessageMapper; 68 + private IotDeviceMessageDAO deviceMessageDAO;
71 69
72 @Resource 70 @Resource
73 private IotDeviceMessageProducer deviceMessageProducer; 71 private IotDeviceMessageProducer deviceMessageProducer;
74 72
75 - @Override  
76 - public void defineDeviceMessageStable() {  
77 - if (StrUtil.isNotEmpty(deviceMessageMapper.showSTable())) {  
78 - log.info("[defineDeviceMessageStable][设备消息超级表已存在,创建跳过]");  
79 - return;  
80 - }  
81 - log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建开始...]");  
82 - deviceMessageMapper.createSTable();  
83 - log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建成功]");  
84 - }  
85 -  
86 @Async 73 @Async
87 void createDeviceLogAsync(IotDeviceMessage message) { 74 void createDeviceLogAsync(IotDeviceMessage message) {
88 IotDeviceMessageDO messageDO = BeanUtils.toBean(message, IotDeviceMessageDO.class) 75 IotDeviceMessageDO messageDO = BeanUtils.toBean(message, IotDeviceMessageDO.class)
@@ -102,7 +89,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -102,7 +89,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
102 messageDO.setTs(System.currentTimeMillis()); 89 messageDO.setTs(System.currentTimeMillis());
103 } 90 }
104 try { 91 try {
105 - deviceMessageMapper.insert(messageDO); 92 + deviceMessageDAO.insert(messageDO);
106 } catch (Exception ex) { 93 } catch (Exception ex) {
107 // 特殊:@Async 方法的异常默认会被 handler 吞掉,这里显式记录便于排查 94 // 特殊:@Async 方法的异常默认会被 handler 吞掉,这里显式记录便于排查
108 log.error("[createDeviceLogAsync][消息日志写入失败 deviceId({}) messageId({}) paramsLen({}) dataLen({})]", 95 log.error("[createDeviceLogAsync][消息日志写入失败 deviceId({}) messageId({}) paramsLen({}) dataLen({})]",
@@ -337,16 +324,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -337,16 +324,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
337 324
338 @Override 325 @Override
339 public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) { 326 public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) {
340 - try {  
341 - IPage<IotDeviceMessageDO> page = deviceMessageMapper.selectPage(  
342 - new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO);  
343 - return new PageResult<>(page.getRecords(), page.getTotal());  
344 - } catch (Exception exception) {  
345 - if (exception.getMessage().contains("Table does not exist")) {  
346 - return PageResult.empty();  
347 - }  
348 - throw exception;  
349 - } 327 + return deviceMessageDAO.selectPage(pageReqVO);
350 } 328 }
351 329
352 @Override 330 @Override
@@ -354,12 +332,12 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -354,12 +332,12 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
354 if (CollUtil.isEmpty(requestIds)) { 332 if (CollUtil.isEmpty(requestIds)) {
355 return ListUtil.of(); 333 return ListUtil.of();
356 } 334 }
357 - return deviceMessageMapper.selectListByRequestIdsAndReply(deviceId, requestIds, reply); 335 + return deviceMessageDAO.selectListByRequestIdsAndReply(deviceId, requestIds, reply);
358 } 336 }
359 337
360 @Override 338 @Override
361 public Long getDeviceMessageCount(LocalDateTime createTime) { 339 public Long getDeviceMessageCount(LocalDateTime createTime) {
362 - return deviceMessageMapper.selectCountByCreateTime( 340 + return deviceMessageDAO.selectCountByCreateTime(
363 createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null); 341 createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null);
364 } 342 }
365 343
@@ -367,7 +345,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -367,7 +345,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
367 public List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate( 345 public List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate(
368 IotStatisticsDeviceMessageReqVO reqVO) { 346 IotStatisticsDeviceMessageReqVO reqVO) {
369 // 1. 按小时统计,获取分项统计数据 347 // 1. 按小时统计,获取分项统计数据
370 - List<Map<String, Object>> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate( 348 + List<Map<String, Object>> countList = deviceMessageDAO.selectDeviceMessageCountGroupByDate(
371 LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]), 349 LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]),
372 LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1])); 350 LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1]));
373 351
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java
@@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.iot.service.device.property; @@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.iot.service.device.property;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.date.LocalDateTimeUtil; 4 import cn.hutool.core.date.LocalDateTimeUtil;
5 -import cn.hutool.core.map.MapUtil;  
6 import cn.hutool.core.util.ObjUtil; 5 import cn.hutool.core.util.ObjUtil;
7 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;  
10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO; 8 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
11 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO; 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
12 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 10 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -14,14 +12,12 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; @@ -14,14 +12,12 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
14 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
15 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 13 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
16 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 14 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
17 -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;  
18 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO; 15 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
19 import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO; 16 import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO;
20 import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceServerIdRedisDAO; 17 import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceServerIdRedisDAO;
21 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper;  
22 -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 18 +import cn.iocoder.yudao.module.iot.dal.storage.IotDevicePropertyDAO;
23 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 19 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
24 -import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; 20 +import cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils;
25 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
26 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
27 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; 23 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
@@ -35,8 +31,10 @@ import java.math.BigDecimal; @@ -35,8 +31,10 @@ import java.math.BigDecimal;
35 import java.time.LocalDateTime; 31 import java.time.LocalDateTime;
36 import java.util.*; 32 import java.util.*;
37 33
  34 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
38 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
39 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.getBigDecimal; 36 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.getBigDecimal;
  37 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_PROPERTY_HISTORY_TIMES_INVALID;
40 38
41 /** 39 /**
42 * IoT 设备【属性】数据 Service 实现类 40 * IoT 设备【属性】数据 Service 实现类
@@ -47,23 +45,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.getBigD @@ -47,23 +45,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.getBigD
47 @Slf4j 45 @Slf4j
48 public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { 46 public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
49 47
50 - /**  
51 - * 物模型的数据类型,与 TDengine 数据类型的映射关系  
52 - *  
53 - * @see <a href="https://docs.taosdata.com/reference/taos-sql/data-type/">TDEngine 数据类型</a>  
54 - */  
55 - private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()  
56 - .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)  
57 - .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)  
58 - .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)  
59 - .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT)  
60 - .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT)  
61 - .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_VARCHAR)  
62 - .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)  
63 - .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_VARCHAR)  
64 - .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_VARCHAR)  
65 - .build();  
66 -  
67 @Resource 48 @Resource
68 private IotThingModelService thingModelService; 49 private IotThingModelService thingModelService;
69 @Resource 50 @Resource
@@ -81,54 +62,18 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -81,54 +62,18 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
81 private DeviceServerIdRedisDAO deviceServerIdRedisDAO; 62 private DeviceServerIdRedisDAO deviceServerIdRedisDAO;
82 63
83 @Resource 64 @Resource
84 - private IotDevicePropertyMapper devicePropertyMapper; 65 + private IotDevicePropertyDAO devicePropertyDAO;
85 66
86 // ========== 设备属性相关操作 ========== 67 // ========== 设备属性相关操作 ==========
87 68
88 @Override 69 @Override
89 public void defineDevicePropertyData(Long productId) { 70 public void defineDevicePropertyData(Long productId) {
90 - // 1.1 查询产品和物模型 71 + // 1. 查询产品和物模型
91 IotProductDO product = productService.validateProductExists(productId); 72 IotProductDO product = productService.validateProductExists(productId);
92 List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId), 73 List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
93 thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType())); 74 thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
94 - // 1.2 解析 DB 里的字段  
95 - List<TDengineTableField> oldFields = new ArrayList<>();  
96 - try {  
97 - oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(product.getId()));  
98 - } catch (Exception e) {  
99 - if (!e.getMessage().contains("Table does not exist")) {  
100 - throw e;  
101 - }  
102 - }  
103 -  
104 - // 2.1 情况一:如果是新增的时候,需要创建表  
105 - List<TDengineTableField> newFields = buildTableFieldList(thingModels);  
106 - if (CollUtil.isEmpty(oldFields)) {  
107 - if (CollUtil.isEmpty(newFields)) {  
108 - log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);  
109 - return;  
110 - }  
111 - devicePropertyMapper.createProductPropertySTable(product.getId(), newFields);  
112 - return;  
113 - }  
114 - // 2.2 情况二:如果是修改的时候,需要更新表  
115 - devicePropertyMapper.alterProductPropertySTable(product.getId(), oldFields, newFields);  
116 - }  
117 -  
118 - private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {  
119 - return convertList(thingModels, thingModel -> {  
120 - TDengineTableField field = new TDengineTableField(  
121 - TDengineTableField.buildFieldName(thingModel.getIdentifier()),  
122 - TYPE_MAPPING.get(thingModel.getProperty().getDataType()));  
123 - String dataType = thingModel.getProperty().getDataType();  
124 - if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {  
125 - field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());  
126 - } else if (ObjectUtils.equalsAny(dataType, IotDataSpecsDataTypeEnum.STRUCT.getDataType(),  
127 - IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {  
128 - field.setLength(TDengineTableField.LENGTH_VARCHAR);  
129 - }  
130 - return field;  
131 - }); 75 + // 2. 定义存储结构:字段映射、差异比较、建表改表,由存储 DAO 实现
  76 + devicePropertyDAO.defineProductPropertyTable(product.getId(), thingModels);
132 } 77 }
133 78
134 @Override 79 @Override
@@ -174,7 +119,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -174,7 +119,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
174 log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message); 119 log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message);
175 } else { 120 } else {
176 // 2.1 保存设备属性【数据】 121 // 2.1 保存设备属性【数据】
177 - devicePropertyMapper.insert(device, properties, 122 + devicePropertyDAO.insert(device, properties,
178 System.currentTimeMillis(), 123 System.currentTimeMillis(),
179 LocalDateTimeUtil.toEpochMilli(message.getReportTime())); 124 LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
180 125
@@ -213,14 +158,26 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -213,14 +158,26 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
213 158
214 @Override 159 @Override
215 public List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(IotDevicePropertyHistoryListReqVO listReqVO) { 160 public List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(IotDevicePropertyHistoryListReqVO listReqVO) {
216 - try {  
217 - return devicePropertyMapper.selectListByHistory(listReqVO);  
218 - } catch (Exception exception) {  
219 - if (exception.getMessage().contains("Table does not exist")) {  
220 - return Collections.emptyList();  
221 - }  
222 - throw exception; 161 + // 1.1 校验时间范围:必须包含开始、结束两个时间;任一为空时,无法确定范围,返回空
  162 + LocalDateTime[] times = listReqVO.getTimes();
  163 + if (times == null || times.length != 2) {
  164 + throw exception(DEVICE_PROPERTY_HISTORY_TIMES_INVALID);
  165 + }
  166 + if (times[0] == null || times[1] == null) {
  167 + return Collections.emptyList();
  168 + }
  169 + // 1.2 校验属性标识符:无法作为存储字段的标识符,不会有历史数据
  170 + if (!IotStorageFieldUtils.isPropertyFieldName(listReqVO.getIdentifier())) {
  171 + return Collections.emptyList();
223 } 172 }
  173 + // 1.3 校验设备:按当前租户查询,且过滤已删除的设备;不存在时,不查询历史数据
  174 + IotDeviceDO device = deviceService.getDevice(listReqVO.getDeviceId());
  175 + if (device == null) {
  176 + return Collections.emptyList();
  177 + }
  178 +
  179 + // 2. 查询历史数据
  180 + return devicePropertyDAO.selectHistoryList(device.getProductId(), listReqVO);
224 } 181 }
225 182
226 // ========== 设备时间相关操作 ========== 183 // ========== 设备时间相关操作 ==========
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
@@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelS @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelS
18 import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert; 18 import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
19 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 19 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
20 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 20 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  21 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
21 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelBoolOrEnumDataSpecs; 22 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelBoolOrEnumDataSpecs;
22 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs; 23 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
23 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; 24 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
@@ -25,7 +26,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper; @@ -25,7 +26,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
25 import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; 26 import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
26 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; 27 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
27 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 28 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
28 -import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;  
29 import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; 29 import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
30 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 30 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
31 import jakarta.annotation.Resource; 31 import jakarta.annotation.Resource;
@@ -193,8 +193,8 @@ public class IotThingModelServiceImpl implements IotThingModelService { @@ -193,8 +193,8 @@ public class IotThingModelServiceImpl implements IotThingModelService {
193 String dataType = thingModel.getProperty().getDataType(); 193 String dataType = thingModel.getProperty().getDataType();
194 if (ObjectUtils.equalsAny(dataType, 194 if (ObjectUtils.equalsAny(dataType,
195 IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) { 195 IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
196 - // 特殊:STRUCT 和 ARRAY 类型,在 TDengine 里,没有对应数据类型,只能通过 JSON 来存储  
197 - return convertToVarchar(JsonUtils.toJsonString(value), TDengineTableField.LENGTH_VARCHAR); 196 + // 特殊:STRUCT 和 ARRAY 类型,在时序库里,没有对应数据类型,只能通过 JSON 来存储
  197 + return convertToVarchar(JsonUtils.toJsonString(value), ThingModelProperty.LENGTH_JSON);
198 } 198 }
199 if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) { 199 if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) {
200 return convertToInt(value); 200 return convertToInt(value);
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 TAGS ('${device.id}') 51 TAGS ('${device.id}')
52 (ts, report_time, 52 (ts, report_time,
53 <foreach item="key" collection="properties.keys" separator=","> 53 <foreach item="key" collection="properties.keys" separator=",">
54 - ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(key)} 54 + ${@cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils@buildFieldName(key)}
55 </foreach> 55 </foreach>
56 ) 56 )
57 VALUES 57 VALUES
@@ -68,9 +68,9 @@ @@ -68,9 +68,9 @@
68 68
69 <select id="selectListByHistory" 69 <select id="selectListByHistory"
70 resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO"> 70 resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO">
71 - SELECT ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(reqVO.identifier)} AS `value`, ts AS update_time 71 + SELECT ${@cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils@buildFieldName(reqVO.identifier)} AS `value`, ts AS update_time
72 FROM device_property_${reqVO.deviceId} 72 FROM device_property_${reqVO.deviceId}
73 - WHERE ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(reqVO.identifier)} IS NOT NULL 73 + WHERE ${@cn.iocoder.yudao.module.iot.framework.storage.core.util.IotStorageFieldUtils@buildFieldName(reqVO.identifier)} IS NOT NULL
74 AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])} 74 AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])}
75 AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])} 75 AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])}
76 ORDER BY ts DESC 76 ORDER BY ts DESC
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/iotdb/IotDeviceMessageIoTDBMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper
  3 + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.iotdb.IotDeviceMessageIoTDBMapper">
  6 +
  7 + <!--
  8 + 一张全局表 device_message:
  9 + 1. time:Service 写入日志时的毫秒 ts
  10 + 2. device_id:唯一的 TAG。身份为 device_id + time:同一设备、同一毫秒再次写入时,后写的整行替换先写的,即使 id 不同
  11 + 3. id:消息编号,FIELD。不作为 TAG,避免每条消息产生一个 IoTDB 设备,导致元数据随消息量无限增长
  12 + 4. upstream、reply:INT32,-1/0/1
  13 + 5. 其它 FIELD:STRING,n:/v: 编码,均为非 NULL
  14 + -->
  15 +
  16 + <select id="selectColumnList" resultType="java.util.Map">
  17 + DESC device_message
  18 + </select>
  19 +
  20 + <update id="createTable">
  21 + CREATE TABLE IF NOT EXISTS device_message (
  22 + device_id STRING TAG,
  23 + id STRING FIELD,
  24 + upstream INT32 FIELD,
  25 + reply INT32 FIELD,
  26 + method STRING FIELD,
  27 + identifier STRING FIELD,
  28 + request_id STRING FIELD,
  29 + server_id STRING FIELD,
  30 + params STRING FIELD,
  31 + data STRING FIELD,
  32 + msg STRING FIELD,
  33 + report_time STRING FIELD,
  34 + tenant_id STRING FIELD,
  35 + code STRING FIELD
  36 + )
  37 + </update>
  38 +
  39 + <insert id="insert">
  40 + INSERT INTO device_message (
  41 + time, device_id, id, upstream, reply,
  42 + method, identifier, request_id, server_id, params,
  43 + data, msg, report_time, tenant_id, code
  44 + )
  45 + VALUES (
  46 + #{ts}, #{deviceId}, #{id}, #{upstream}, #{reply},
  47 + #{method}, #{identifier}, #{requestId}, #{serverId}, #{params},
  48 + #{data}, #{msg}, #{reportTime}, #{tenantId}, #{code}
  49 + )
  50 + </insert>
  51 +
  52 + <sql id="selectColumns">
  53 + SELECT time AS ts, device_id, id, upstream, reply,
  54 + method, identifier, request_id, server_id, params,
  55 + data, msg, report_time, tenant_id, code
  56 + FROM device_message
  57 + </sql>
  58 +
  59 + <sql id="pageWhere">
  60 + WHERE device_id = #{deviceId}
  61 + <if test="method != null">
  62 + AND method = #{method}
  63 + </if>
  64 + <if test="upstream != null">
  65 + AND upstream = #{upstream}
  66 + </if>
  67 + <if test="reply != null">
  68 + AND reply = #{reply}
  69 + </if>
  70 + <if test="identifier != null">
  71 + AND identifier = #{identifier}
  72 + </if>
  73 + </sql>
  74 +
  75 + <select id="selectList" resultType="cn.iocoder.yudao.module.iot.dal.iotdb.IotDeviceMessageIoTDBRow">
  76 + <include refid="selectColumns" />
  77 + <include refid="pageWhere" />
  78 + ORDER BY time DESC
  79 + <if test="limit != null">
  80 + LIMIT #{limit} OFFSET #{offset}
  81 + </if>
  82 + </select>
  83 +
  84 + <select id="selectCount" resultType="Long">
  85 + SELECT count(*)
  86 + FROM device_message
  87 + <include refid="pageWhere" />
  88 + </select>
  89 +
  90 + <select id="selectListByRequestIdsAndReply" resultType="cn.iocoder.yudao.module.iot.dal.iotdb.IotDeviceMessageIoTDBRow">
  91 + <include refid="selectColumns" />
  92 + WHERE device_id = #{deviceId}
  93 + AND reply = #{reply}
  94 + AND request_id IN
  95 + <foreach collection="requestIds" item="requestId" open="(" close=")" separator=",">
  96 + #{requestId}
  97 + </foreach>
  98 + ORDER BY time DESC
  99 + </select>
  100 +
  101 + <select id="selectCountByCreateTime" resultType="Long">
  102 + SELECT count(*)
  103 + FROM device_message
  104 + <where>
  105 + <if test="createTime != null">
  106 + AND time &gt;= #{createTime}
  107 + </if>
  108 + </where>
  109 + </select>
  110 +
  111 + <!-- 按 epoch 整点分桶;upstream 为 -1 的消息不计入上行、下行 -->
  112 + <select id="selectCountGroupByHour" resultType="java.util.Map">
  113 + SELECT
  114 + date_bin(1h, time) AS time,
  115 + count_if(upstream = 1) AS upstream_count,
  116 + count_if(upstream = 0) AS downstream_count
  117 + FROM device_message
  118 + <where>
  119 + <if test="startTime != null">
  120 + AND time &gt;= #{startTime}
  121 + </if>
  122 + <if test="endTime != null">
  123 + AND time &lt;= #{endTime}
  124 + </if>
  125 + </where>
  126 + GROUP BY date_bin(1h, time)
  127 + </select>
  128 +
  129 +</mapper>
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/iotdb/IotDevicePropertyIoTDBMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper
  3 + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.iotdb.IotDevicePropertyIoTDBMapper">
  6 +
  7 + <!--
  8 + 每个产品一张表 product_property_{productId}:
  9 + 1. time:存储时间(毫秒)
  10 + 2. device_id:设备编号,TAG
  11 + 3. report_time:上报时间,FIELD
  12 + 4. p_*:用户属性,FIELD,按物模型增删、改类型
  13 +
  14 + 注意:${} 只用于 Long 产品编号、DAO 校验后的列名和类型,值全部使用 #{} 绑定
  15 + -->
  16 +
  17 + <select id="selectColumnList" resultType="java.util.Map">
  18 + DESC product_property_${productId}
  19 + </select>
  20 +
  21 + <update id="createTable">
  22 + CREATE TABLE IF NOT EXISTS product_property_${productId} (
  23 + device_id STRING TAG,
  24 + report_time TIMESTAMP FIELD
  25 + <foreach item="column" collection="columns">
  26 + , "${column.name}" ${column.type} FIELD
  27 + </foreach>
  28 + )
  29 + </update>
  30 +
  31 + <update id="addColumn">
  32 + ALTER TABLE product_property_${productId}
  33 + ADD COLUMN IF NOT EXISTS "${column.name}" ${column.type} FIELD
  34 + </update>
  35 +
  36 + <update id="dropColumn">
  37 + ALTER TABLE product_property_${productId}
  38 + DROP COLUMN IF EXISTS "${columnName}"
  39 + </update>
  40 +
  41 + <insert id="insert">
  42 + INSERT INTO product_property_${productId} (time, device_id
  43 + <if test="reportTime != null">
  44 + , report_time
  45 + </if>
  46 + <foreach item="column" collection="columns">
  47 + , "${column.name}"
  48 + </foreach>
  49 + )
  50 + VALUES (#{ts}, #{deviceId}
  51 + <if test="reportTime != null">
  52 + , #{reportTime}
  53 + </if>
  54 + <foreach item="column" collection="columns">
  55 + , #{column.value}
  56 + </foreach>
  57 + )
  58 + </insert>
  59 +
  60 + <select id="selectHistoryList"
  61 + resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO">
  62 + SELECT "${columnName}" AS "value", time AS update_time
  63 + FROM product_property_${productId}
  64 + WHERE device_id = #{deviceId}
  65 + AND time &gt;= #{startTime}
  66 + AND time &lt;= #{endTime}
  67 + AND "${columnName}" IS NOT NULL
  68 + ORDER BY time DESC
  69 + </select>
  70 +
  71 +</mapper>
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/framework/iotdb/core/IoTDBUtilsTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.iotdb.core;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import org.apache.iotdb.common.rpc.thrift.TSStatus;
  5 +import org.apache.iotdb.jdbc.IoTDBSQLException;
  6 +import org.apache.iotdb.rpc.StatementExecutionException;
  7 +import org.apache.iotdb.rpc.TSStatusCode;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.springframework.jdbc.UncategorizedSQLException;
  10 +
  11 +import java.util.*;
  12 +
  13 +import static org.junit.jupiter.api.Assertions.*;
  14 +
  15 +/**
  16 + * {@link IoTDBUtils} 的单元测试
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class IoTDBUtilsTest {
  21 +
  22 + // ========== isTableNotExists ==========
  23 +
  24 + @Test
  25 + public void testIsTableNotExists_statusCode() {
  26 + assertEquals(TSStatusCode.TABLE_NOT_EXISTS.getStatusCode(), IoTDBUtils.STATUS_CODE_TABLE_NOT_EXISTS);
  27 + }
  28 +
  29 + @Test
  30 + public void testIsTableNotExists_true() {
  31 + // 驱动抛出的异常:errorCode 为 IoTDB 状态码
  32 + IoTDBSQLException sqlException = new IoTDBSQLException("550: Table 'db.product_property_1' does not exist",
  33 + new TSStatus(TSStatusCode.TABLE_NOT_EXISTS.getStatusCode()));
  34 + assertTrue(IoTDBUtils.isTableNotExists(sqlException));
  35 + // Spring、MyBatis 包装后的异常:沿 cause 链查找
  36 + assertTrue(IoTDBUtils.isTableNotExists(new UncategorizedSQLException("select", "SELECT 1", sqlException)));
  37 + assertTrue(IoTDBUtils.isTableNotExists(new RuntimeException(new RuntimeException(sqlException))));
  38 + // Session 风格的异常
  39 + assertTrue(IoTDBUtils.isTableNotExists(new RuntimeException(
  40 + new StatementExecutionException(new TSStatus(TSStatusCode.TABLE_NOT_EXISTS.getStatusCode())))));
  41 + }
  42 +
  43 + @Test
  44 + public void testIsTableNotExists_false() {
  45 + // 列不存在、类型不匹配:不是表不存在,需要原样抛出
  46 + assertFalse(IoTDBUtils.isTableNotExists(new IoTDBSQLException("616",
  47 + new TSStatus(TSStatusCode.COLUMN_NOT_EXISTS.getStatusCode()))));
  48 + assertFalse(IoTDBUtils.isTableNotExists(new StatementExecutionException(
  49 + new TSStatus(TSStatusCode.DATA_TYPE_MISMATCH.getStatusCode()))));
  50 + // 只靠异常文案,不识别为表不存在
  51 + assertFalse(IoTDBUtils.isTableNotExists(new RuntimeException("Table does not exist")));
  52 + assertFalse(IoTDBUtils.isTableNotExists(null));
  53 + }
  54 +
  55 + @Test
  56 + public void testIsTableNotExists_causeLoop() {
  57 + // 准备参数:cause 链成环
  58 + RuntimeException a = new RuntimeException("a");
  59 + RuntimeException b = new RuntimeException("b");
  60 + a.initCause(b);
  61 + b.initCause(a);
  62 +
  63 + // 调用,并断言:不会死循环
  64 + assertFalse(IoTDBUtils.isTableNotExists(a));
  65 + }
  66 +
  67 + // ========== validateTimestampPrecision ==========
  68 +
  69 + @Test
  70 + public void testValidateTimestampPrecision_success() {
  71 + List<Map<String, Object>> variables = Arrays.asList(
  72 + row("Variable", "ClusterName", "Value", "defaultCluster"),
  73 + row("variable", "TimestampPrecision", "value", "ms"));
  74 + assertDoesNotThrow(() -> IoTDBUtils.validateTimestampPrecision(variables));
  75 + }
  76 +
  77 + @Test
  78 + public void testValidateTimestampPrecision_failure() {
  79 + // 精度不是 ms
  80 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateTimestampPrecision(
  81 + Collections.singletonList(row("Variable", "TimestampPrecision", "Value", "ns"))));
  82 + // 无法读取
  83 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateTimestampPrecision(
  84 + Collections.singletonList(row("Variable", "ClusterName", "Value", "defaultCluster"))));
  85 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateTimestampPrecision(Collections.emptyList()));
  86 + }
  87 +
  88 + // ========== validateDataNodes ==========
  89 +
  90 + @Test
  91 + public void testValidateDataNodes_success() {
  92 + List<Map<String, Object>> dataNodes = Arrays.asList(
  93 + row("NodeID", 1, "Status", "Running"),
  94 + row("NodeID", 3, "Status", "running"));
  95 + assertEquals(Arrays.asList(1, 3), IoTDBUtils.validateDataNodes(dataNodes));
  96 + }
  97 +
  98 + @Test
  99 + public void testValidateDataNodes_failure() {
  100 + // 没有 DataNode
  101 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateDataNodes(Collections.emptyList()));
  102 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateDataNodes(null));
  103 + // 有 DataNode 不是 Running:无法确认它的配置
  104 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateDataNodes(Arrays.asList(
  105 + row("NodeID", 1, "Status", "Running"),
  106 + row("NodeID", 2, "Status", "Unknown"))));
  107 + // 编号无法识别
  108 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateDataNodes(
  109 + Collections.singletonList(row("NodeID", "abc", "Status", "Running"))));
  110 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validateDataNodes(
  111 + Collections.singletonList(row("Status", "Running"))));
  112 + }
  113 +
  114 + // ========== validatePartialInsertDisabled ==========
  115 +
  116 + @Test
  117 + public void testValidatePartialInsertDisabled_success() {
  118 + List<Map<String, Object>> configurations = Arrays.asList(
  119 + row("name", "dn_rpc_port", "value", "6667", "default_value", "6667"),
  120 + row("name", "enable_partial_insert", "value", "false", "default_value", "true"));
  121 + assertDoesNotThrow(() -> IoTDBUtils.validatePartialInsertDisabled(1, configurations));
  122 + }
  123 +
  124 + @Test
  125 + public void testValidatePartialInsertDisabled_failure() {
  126 + // 开启了部分写入
  127 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validatePartialInsertDisabled(1,
  128 + Collections.singletonList(row("name", "enable_partial_insert", "value", "true"))));
  129 + // 无法读取配置
  130 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validatePartialInsertDisabled(1,
  131 + Collections.singletonList(row("name", "dn_rpc_port", "value", "6667"))));
  132 + assertThrows(IllegalStateException.class, () -> IoTDBUtils.validatePartialInsertDisabled(1,
  133 + Collections.singletonList(row("name", "enable_partial_insert"))));
  134 + }
  135 +
  136 + // ========== getIgnoreCase ==========
  137 +
  138 + @Test
  139 + public void testGetIgnoreCase() {
  140 + Map<String, Object> row = row("NodeID", 1);
  141 + assertEquals(1, IoTDBUtils.getIgnoreCase(row, "nodeid"));
  142 + assertNull(IoTDBUtils.getIgnoreCase(row, "Status"));
  143 + }
  144 +
  145 + private static Map<String, Object> row(Object... keyValues) {
  146 + Map<String, Object> row = new LinkedHashMap<>();
  147 + for (int i = 0; i < keyValues.length; i += 2) {
  148 + row.put((String) keyValues[i], keyValues[i + 1]);
  149 + }
  150 + return MapUtil.unmodifiable(row);
  151 + }
  152 +
  153 +}
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/framework/storage/core/util/IotStorageFieldUtilsTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.storage.core.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import org.junit.jupiter.api.Test;
  5 +
  6 +import java.util.Arrays;
  7 +import java.util.Collections;
  8 +
  9 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  10 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  11 +import static org.junit.jupiter.api.Assertions.*;
  12 +
  13 +/**
  14 + * {@link IotStorageFieldUtils} 的单元测试
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public class IotStorageFieldUtilsTest {
  19 +
  20 + @Test
  21 + public void testBuildFieldName_success() {
  22 + assertEquals("ua", IotStorageFieldUtils.buildFieldName("Ua"));
  23 + assertEquals("pf_t", IotStorageFieldUtils.buildFieldName("PfT"));
  24 + assertEquals("pt", IotStorageFieldUtils.buildFieldName("PT"));
  25 + assertEquals("pa", IotStorageFieldUtils.buildFieldName("PA"));
  26 + assertEquals("geo_location", IotStorageFieldUtils.buildFieldName("GeoLocation"));
  27 + assertEquals("light_status", IotStorageFieldUtils.buildFieldName("LightStatus"));
  28 + assertEquals("foo_bar", IotStorageFieldUtils.buildFieldName("foo_bar"));
  29 + }
  30 +
  31 + @Test
  32 + public void testBuildFieldName_blank() {
  33 + // 空白原样返回,由 isPropertyFieldName、validatePropertyIdentifiers 判定为不合法
  34 + assertNull(IotStorageFieldUtils.buildFieldName(null));
  35 + assertEquals("", IotStorageFieldUtils.buildFieldName(""));
  36 + assertEquals(" ", IotStorageFieldUtils.buildFieldName(" "));
  37 + }
  38 +
  39 + @Test
  40 + public void testIsPropertyFieldName_true() {
  41 + assertTrue(IotStorageFieldUtils.isPropertyFieldName("Temperature"));
  42 + assertTrue(IotStorageFieldUtils.isPropertyFieldName("PfT"));
  43 + assertTrue(IotStorageFieldUtils.isPropertyFieldName("a1_b2"));
  44 + assertTrue(IotStorageFieldUtils.isPropertyFieldName(StrUtil.repeat('a', 64)));
  45 + }
  46 +
  47 + @Test
  48 + public void testIsPropertyFieldName_false() {
  49 + // 保留字段:归一化后与系统字段相同
  50 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("ts"));
  51 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("TS"));
  52 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("reportTime"));
  53 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("report_time"));
  54 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("deviceId"));
  55 + // 格式不合法:不能安全地拼接到 SQL 结构名
  56 + assertFalse(IotStorageFieldUtils.isPropertyFieldName(null));
  57 + assertFalse(IotStorageFieldUtils.isPropertyFieldName(""));
  58 + assertFalse(IotStorageFieldUtils.isPropertyFieldName(" "));
  59 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("1abc"));
  60 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("温度"));
  61 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("a-b"));
  62 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("a\"b"));
  63 + assertFalse(IotStorageFieldUtils.isPropertyFieldName("a` OR 1=1"));
  64 + assertFalse(IotStorageFieldUtils.isPropertyFieldName(StrUtil.repeat('a', 65)));
  65 + }
  66 +
  67 + @Test
  68 + public void testValidatePropertyIdentifiers_success() {
  69 + // 调用,并断言
  70 + assertDoesNotThrow(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  71 + Arrays.asList("Temperature", "PfT", "geo_location")));
  72 + assertDoesNotThrow(() -> IotStorageFieldUtils.validatePropertyIdentifiers(Collections.emptyList()));
  73 + }
  74 +
  75 + @Test
  76 + public void testValidatePropertyIdentifiers_invalid() {
  77 + assertServiceException(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  78 + Arrays.asList("Temperature", "温度")), DEVICE_PROPERTY_FIELD_NAME_INVALID, "温度");
  79 + assertServiceException(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  80 + Collections.singletonList(null)), DEVICE_PROPERTY_FIELD_NAME_INVALID, (Object) null);
  81 + }
  82 +
  83 + @Test
  84 + public void testValidatePropertyIdentifiers_reserved() {
  85 + assertServiceException(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  86 + Collections.singletonList("reportTime")), DEVICE_PROPERTY_FIELD_NAME_RESERVED, "reportTime", "report_time");
  87 + assertServiceException(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  88 + Collections.singletonList("deviceId")), DEVICE_PROPERTY_FIELD_NAME_RESERVED, "deviceId", "device_id");
  89 + }
  90 +
  91 + @Test
  92 + public void testValidatePropertyIdentifiers_duplicate() {
  93 + assertServiceException(() -> IotStorageFieldUtils.validatePropertyIdentifiers(
  94 + Arrays.asList("fooBar", "foo_bar")), DEVICE_PROPERTY_FIELD_NAME_DUPLICATE, "foo_bar", "fooBar", "foo_bar");
  95 + }
  96 +
  97 +}
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableFieldTest.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.tdengine.core;  
2 -  
3 -import org.junit.jupiter.api.Test;  
4 -  
5 -import static org.junit.jupiter.api.Assertions.assertEquals;  
6 -import static org.junit.jupiter.api.Assertions.assertNull;  
7 -  
8 -public class TDengineTableFieldTest {  
9 -  
10 - @Test  
11 - public void testBuildFieldName() {  
12 - assertEquals("ua", TDengineTableField.buildFieldName("Ua"));  
13 - assertEquals("pf_t", TDengineTableField.buildFieldName("PfT"));  
14 - assertEquals("pt", TDengineTableField.buildFieldName("PT"));  
15 - assertEquals("pa", TDengineTableField.buildFieldName("PA"));  
16 - assertEquals("geo_location", TDengineTableField.buildFieldName("GeoLocation"));  
17 - assertEquals("light_status", TDengineTableField.buildFieldName("LightStatus"));  
18 - }  
19 -  
20 - @Test  
21 - public void testBuildFieldName_blank() {  
22 - assertNull(TDengineTableField.buildFieldName(null));  
23 - assertEquals("", TDengineTableField.buildFieldName(""));  
24 - assertEquals(" ", TDengineTableField.buildFieldName(" "));  
25 - }  
26 -  
27 -}  
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImplTest.java
@@ -2,39 +2,43 @@ package cn.iocoder.yudao.module.iot.service.device.message; @@ -2,39 +2,43 @@ package cn.iocoder.yudao.module.iot.service.device.message;
2 2
3 import cn.hutool.core.date.LocalDateTimeUtil; 3 import cn.hutool.core.date.LocalDateTimeUtil;
4 import cn.iocoder.yudao.framework.common.exception.ServiceException; 4 import cn.iocoder.yudao.framework.common.exception.ServiceException;
  5 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 7 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO; 8 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
8 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 11 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 12 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; 13 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
11 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 14 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO; 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
13 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper; 16 +import cn.iocoder.yudao.module.iot.dal.storage.IotDeviceMessageDAO;
14 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 17 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
15 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; 18 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
16 import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService; 19 import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
17 -import com.baomidou.mybatisplus.extension.plugins.pagination.Page;  
18 import org.junit.jupiter.api.Test; 20 import org.junit.jupiter.api.Test;
19 import org.mockito.ArgumentCaptor; 21 import org.mockito.ArgumentCaptor;
20 import org.mockito.InjectMocks; 22 import org.mockito.InjectMocks;
21 import org.mockito.Mock; 23 import org.mockito.Mock;
22 24
  25 +import java.sql.Timestamp;
23 import java.time.LocalDateTime; 26 import java.time.LocalDateTime;
  27 +import java.util.Arrays;
24 import java.util.Collections; 28 import java.util.Collections;
25 import java.util.HashMap; 29 import java.util.HashMap;
26 import java.util.List; 30 import java.util.List;
27 31
28 import static org.junit.jupiter.api.Assertions.*; 32 import static org.junit.jupiter.api.Assertions.*;
29 import static org.mockito.ArgumentMatchers.any; 33 import static org.mockito.ArgumentMatchers.any;
30 -import static org.mockito.ArgumentMatchers.eq;  
31 import static org.mockito.ArgumentMatchers.isNull; 34 import static org.mockito.ArgumentMatchers.isNull;
  35 +import static org.mockito.ArgumentMatchers.same;
32 import static org.mockito.Mockito.*; 36 import static org.mockito.Mockito.*;
33 37
34 /** 38 /**
35 * {@link IotDeviceMessageServiceImpl} 的单元测试 39 * {@link IotDeviceMessageServiceImpl} 的单元测试
36 * 40 *
37 - * 注:TDengine 数据源没有 embedded 替代,mapper 与依赖 service 走 mock; 41 + * 注:时序库没有 embedded 替代,存储 DAO 与依赖 service 走 mock;
38 * handleUpstreamDeviceMessage 与 sendDeviceMessage 下行成功路径依赖 SpringUtil.getBean 的自调用 42 * handleUpstreamDeviceMessage 与 sendDeviceMessage 下行成功路径依赖 SpringUtil.getBean 的自调用
39 * createDeviceLogAsync,更适合放到集成测试,本类不展开。 43 * createDeviceLogAsync,更适合放到集成测试,本类不展开。
40 * 44 *
@@ -52,36 +56,10 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -52,36 +56,10 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
52 @Mock 56 @Mock
53 private IotOtaTaskRecordService otaTaskRecordService; 57 private IotOtaTaskRecordService otaTaskRecordService;
54 @Mock 58 @Mock
55 - private IotDeviceMessageMapper deviceMessageMapper; 59 + private IotDeviceMessageDAO deviceMessageDAO;
56 @Mock 60 @Mock
57 private IotDeviceMessageProducer deviceMessageProducer; 61 private IotDeviceMessageProducer deviceMessageProducer;
58 62
59 - // ========== defineDeviceMessageStable ==========  
60 -  
61 - @Test  
62 - public void testDefineDeviceMessageStable_whenTableExists_skipCreate() {  
63 - // 准备:showSTable 返回非空 → 表已存在  
64 - when(deviceMessageMapper.showSTable()).thenReturn("device_message");  
65 -  
66 - // 调用  
67 - service.defineDeviceMessageStable();  
68 -  
69 - // 断言:跳过 createSTable  
70 - verify(deviceMessageMapper, never()).createSTable();  
71 - }  
72 -  
73 - @Test  
74 - public void testDefineDeviceMessageStable_whenTableMissing_create() {  
75 - // 准备:showSTable 返回空 → 表不存在  
76 - when(deviceMessageMapper.showSTable()).thenReturn("");  
77 -  
78 - // 调用  
79 - service.defineDeviceMessageStable();  
80 -  
81 - // 断言:触发 createSTable  
82 - verify(deviceMessageMapper, times(1)).createSTable();  
83 - }  
84 -  
85 // ========== createDeviceLogAsync ========== 63 // ========== createDeviceLogAsync ==========
86 64
87 @Test 65 @Test
@@ -96,7 +74,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -96,7 +74,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
96 74
97 // 断言:mapper.insert 接收到的 messageDO 已被填上 ts,值在 [before, after] 区间 75 // 断言:mapper.insert 接收到的 messageDO 已被填上 ts,值在 [before, after] 区间
98 ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class); 76 ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class);
99 - verify(deviceMessageMapper).insert(captor.capture()); 77 + verify(deviceMessageDAO).insert(captor.capture());
100 Long actualTs = captor.getValue().getTs(); 78 Long actualTs = captor.getValue().getTs();
101 assertNotNull(actualTs, "ts 不应为空"); 79 assertNotNull(actualTs, "ts 不应为空");
102 assertTrue(actualTs >= before && actualTs <= after, 80 assertTrue(actualTs >= before && actualTs <= after,
@@ -114,7 +92,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -114,7 +92,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
114 92
115 // 断言:mapper.insert 接收到的 messageDO 已补充 id 93 // 断言:mapper.insert 接收到的 messageDO 已补充 id
116 ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class); 94 ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class);
117 - verify(deviceMessageMapper).insert(captor.capture()); 95 + verify(deviceMessageDAO).insert(captor.capture());
118 assertNotNull(captor.getValue().getId(), "id 不应为空"); 96 assertNotNull(captor.getValue().getId(), "id 不应为空");
119 } 97 }
120 98
@@ -122,11 +100,11 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -122,11 +100,11 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
122 public void testCreateDeviceLogAsync_swallowMapperException() { 100 public void testCreateDeviceLogAsync_swallowMapperException() {
123 // 准备:mapper.insert 抛异常,验证 @Async 方法内部 try/catch 兜底,不向上抛 101 // 准备:mapper.insert 抛异常,验证 @Async 方法内部 try/catch 兜底,不向上抛
124 IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); 102 IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
125 - doThrow(new RuntimeException("DB unavailable")).when(deviceMessageMapper).insert(any()); 103 + doThrow(new RuntimeException("DB unavailable")).when(deviceMessageDAO).insert(any());
126 104
127 // 调用 & 断言 105 // 调用 & 断言
128 assertDoesNotThrow(() -> service.createDeviceLogAsync(message)); 106 assertDoesNotThrow(() -> service.createDeviceLogAsync(message));
129 - verify(deviceMessageMapper).insert(any(IotDeviceMessageDO.class)); 107 + verify(deviceMessageDAO).insert(any(IotDeviceMessageDO.class));
130 } 108 }
131 109
132 // ========== sendDeviceMessage ========== 110 // ========== sendDeviceMessage ==========
@@ -170,49 +148,18 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -170,49 +148,18 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
170 reqVO.setPageNo(1); 148 reqVO.setPageNo(1);
171 reqVO.setPageSize(10); 149 reqVO.setPageSize(10);
172 IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1"); 150 IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1");
173 - Page<IotDeviceMessageDO> page = new Page<>(1, 10, 1L);  
174 - page.setRecords(Collections.singletonList(record));  
175 - when(deviceMessageMapper.selectPage(any(), eq(reqVO))).thenReturn(page); 151 + when(deviceMessageDAO.selectPage(same(reqVO)))
  152 + .thenReturn(new PageResult<>(Collections.singletonList(record), 1L));
176 153
177 // 调用 154 // 调用
178 PageResult<IotDeviceMessageDO> result = service.getDeviceMessagePage(reqVO); 155 PageResult<IotDeviceMessageDO> result = service.getDeviceMessagePage(reqVO);
179 156
180 - // 断言 157 + // 断言:表不存在等存储差异,由 DAO 处理(见各 DAO 的单元测试)
181 assertEquals(1L, result.getTotal()); 158 assertEquals(1L, result.getTotal());
182 assertEquals(1, result.getList().size()); 159 assertEquals(1, result.getList().size());
183 assertEquals("msg-1", result.getList().get(0).getId()); 160 assertEquals("msg-1", result.getList().get(0).getId());
184 } 161 }
185 162
186 - @Test  
187 - public void testGetDeviceMessagePage_whenTableMissing_returnEmpty() {  
188 - // 准备:mapper 抛 "Table does not exist" → 视为表未创建,返回空结果  
189 - IotDeviceMessagePageReqVO reqVO = new IotDeviceMessagePageReqVO();  
190 - reqVO.setPageNo(1);  
191 - reqVO.setPageSize(10);  
192 - when(deviceMessageMapper.selectPage(any(), any()))  
193 - .thenThrow(new RuntimeException("Table does not exist"));  
194 -  
195 - // 调用  
196 - PageResult<IotDeviceMessageDO> result = service.getDeviceMessagePage(reqVO);  
197 -  
198 - // 断言  
199 - assertEquals(0L, result.getTotal());  
200 - assertTrue(result.getList().isEmpty());  
201 - }  
202 -  
203 - @Test  
204 - public void testGetDeviceMessagePage_otherException_rethrow() {  
205 - // 准备:mapper 抛非「表不存在」的异常 → 应向上抛  
206 - IotDeviceMessagePageReqVO reqVO = new IotDeviceMessagePageReqVO();  
207 - reqVO.setPageNo(1);  
208 - reqVO.setPageSize(10);  
209 - when(deviceMessageMapper.selectPage(any(), any()))  
210 - .thenThrow(new RuntimeException("Connection refused"));  
211 -  
212 - // 调用 & 断言  
213 - assertThrows(RuntimeException.class, () -> service.getDeviceMessagePage(reqVO));  
214 - }  
215 -  
216 // ========== getDeviceMessageListByRequestIdsAndReply ========== 163 // ========== getDeviceMessageListByRequestIdsAndReply ==========
217 164
218 @Test 165 @Test
@@ -222,7 +169,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -222,7 +169,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
222 1L, Collections.emptyList(), true); 169 1L, Collections.emptyList(), true);
223 170
224 assertTrue(result.isEmpty()); 171 assertTrue(result.isEmpty());
225 - verify(deviceMessageMapper, never()).selectListByRequestIdsAndReply(any(), any(), any()); 172 + verify(deviceMessageDAO, never()).selectListByRequestIdsAndReply(any(), any(), any());
226 } 173 }
227 174
228 @Test 175 @Test
@@ -230,7 +177,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -230,7 +177,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
230 // 准备 177 // 准备
231 List<String> requestIds = Collections.singletonList("req-1"); 178 List<String> requestIds = Collections.singletonList("req-1");
232 IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1"); 179 IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1");
233 - when(deviceMessageMapper.selectListByRequestIdsAndReply(1L, requestIds, true)) 180 + when(deviceMessageDAO.selectListByRequestIdsAndReply(1L, requestIds, true))
234 .thenReturn(Collections.singletonList(record)); 181 .thenReturn(Collections.singletonList(record));
235 182
236 // 调用 183 // 调用
@@ -247,14 +194,14 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -247,14 +194,14 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
247 @Test 194 @Test
248 public void testGetDeviceMessageCount_whenCreateTimeNull_passNullToMapper() { 195 public void testGetDeviceMessageCount_whenCreateTimeNull_passNullToMapper() {
249 // 准备 196 // 准备
250 - when(deviceMessageMapper.selectCountByCreateTime(isNull())).thenReturn(123L); 197 + when(deviceMessageDAO.selectCountByCreateTime(isNull())).thenReturn(123L);
251 198
252 // 调用 199 // 调用
253 Long count = service.getDeviceMessageCount(null); 200 Long count = service.getDeviceMessageCount(null);
254 201
255 // 断言 202 // 断言
256 assertEquals(123L, count); 203 assertEquals(123L, count);
257 - verify(deviceMessageMapper).selectCountByCreateTime(isNull()); 204 + verify(deviceMessageDAO).selectCountByCreateTime(isNull());
258 } 205 }
259 206
260 @Test 207 @Test
@@ -262,7 +209,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -262,7 +209,7 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
262 // 准备:service 内部用 LocalDateTimeUtil.toEpochMilli 做转换,断言时也用同函数得到期望值 209 // 准备:service 内部用 LocalDateTimeUtil.toEpochMilli 做转换,断言时也用同函数得到期望值
263 LocalDateTime createTime = LocalDateTime.of(2026, 1, 1, 0, 0); 210 LocalDateTime createTime = LocalDateTime.of(2026, 1, 1, 0, 0);
264 long expectedMs = LocalDateTimeUtil.toEpochMilli(createTime); 211 long expectedMs = LocalDateTimeUtil.toEpochMilli(createTime);
265 - when(deviceMessageMapper.selectCountByCreateTime(expectedMs)).thenReturn(456L); 212 + when(deviceMessageDAO.selectCountByCreateTime(expectedMs)).thenReturn(456L);
266 213
267 // 调用 214 // 调用
268 Long count = service.getDeviceMessageCount(createTime); 215 Long count = service.getDeviceMessageCount(createTime);
@@ -271,6 +218,33 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { @@ -271,6 +218,33 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {
271 assertEquals(456L, count); 218 assertEquals(456L, count);
272 } 219 }
273 220
  221 + // ========== getDeviceMessageSummaryByDate ==========
  222 +
  223 + @Test
  224 + public void testGetDeviceMessageSummaryByDate_success() {
  225 + // 准备参数:按天合并 2026-01-01 的小时统计
  226 + IotStatisticsDeviceMessageReqVO reqVO = new IotStatisticsDeviceMessageReqVO();
  227 + reqVO.setInterval(DateIntervalEnum.DAY.getInterval());
  228 + LocalDateTime startTime = LocalDateTime.of(2026, 1, 1, 0, 0);
  229 + LocalDateTime endTime = LocalDateTime.of(2026, 1, 1, 23, 59, 59);
  230 + reqVO.setTimes(new LocalDateTime[]{startTime, endTime});
  231 +
  232 + // mock 方法:DAO 统一返回 Timestamp 时间、Long 数量
  233 + when(deviceMessageDAO.selectDeviceMessageCountGroupByDate(
  234 + LocalDateTimeUtil.toEpochMilli(startTime), LocalDateTimeUtil.toEpochMilli(endTime)))
  235 + .thenReturn(Arrays.asList(
  236 + IotDeviceMessageDAO.buildMessageCount(Timestamp.valueOf(startTime.withHour(1)), 3L, 1L),
  237 + IotDeviceMessageDAO.buildMessageCount(Timestamp.valueOf(startTime.withHour(2)), 2L, 0L)));
  238 +
  239 + // 调用
  240 + List<IotStatisticsDeviceMessageSummaryByDateRespVO> list = service.getDeviceMessageSummaryByDate(reqVO);
  241 +
  242 + // 断言
  243 + assertEquals(1, list.size());
  244 + assertEquals(5, list.get(0).getUpstreamCount());
  245 + assertEquals(1, list.get(0).getDownstreamCount());
  246 + }
  247 +
274 // ========== 辅助方法 ========== 248 // ========== 辅助方法 ==========
275 249
276 /** 构造一条最简消息(指定 method 决定上下行分支) */ 250 /** 构造一条最简消息(指定 method 决定上下行分支) */
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImplTest.java
1 package cn.iocoder.yudao.module.iot.service.device.property; 1 package cn.iocoder.yudao.module.iot.service.device.property;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
4 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 8 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
@@ -9,10 +13,10 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; @@ -9,10 +13,10 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 13 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
10 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty; 14 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
11 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO; 15 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
12 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper; 16 +import cn.iocoder.yudao.module.iot.dal.storage.IotDevicePropertyDAO;
13 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 17 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
14 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 18 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
15 -import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; 19 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
16 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 20 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
17 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; 21 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
18 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
@@ -22,12 +26,14 @@ import org.mockito.Mock; @@ -22,12 +26,14 @@ import org.mockito.Mock;
22 26
23 import java.time.LocalDateTime; 27 import java.time.LocalDateTime;
24 import java.util.Arrays; 28 import java.util.Arrays;
25 -import java.util.Collections;  
26 import java.util.HashMap; 29 import java.util.HashMap;
27 import java.util.List; 30 import java.util.List;
28 import java.util.Map; 31 import java.util.Map;
29 -import java.util.stream.Collectors;  
30 32
  33 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  34 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  35 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_PROPERTY_HISTORY_TIMES_INVALID;
  36 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
31 import static java.util.Collections.singletonList; 37 import static java.util.Collections.singletonList;
32 import static org.junit.jupiter.api.Assertions.*; 38 import static org.junit.jupiter.api.Assertions.*;
33 import static org.mockito.ArgumentMatchers.*; 39 import static org.mockito.ArgumentMatchers.*;
@@ -48,7 +54,9 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -48,7 +54,9 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
48 @Mock 54 @Mock
49 private IotProductService productService; 55 private IotProductService productService;
50 @Mock 56 @Mock
51 - private IotDevicePropertyMapper devicePropertyMapper; 57 + private IotDeviceService deviceService;
  58 + @Mock
  59 + private IotDevicePropertyDAO devicePropertyDAO;
52 @Mock 60 @Mock
53 private DevicePropertyRedisDAO deviceDataRedisDAO; 61 private DevicePropertyRedisDAO deviceDataRedisDAO;
54 62
@@ -97,7 +105,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -97,7 +105,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
97 service.saveDeviceProperty(device, message); 105 service.saveDeviceProperty(device, message);
98 106
99 // 断言:没有合法属性,不会写入 TDengine 与 Redis 107 // 断言:没有合法属性,不会写入 TDengine 与 Redis
100 - verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong()); 108 + verify(devicePropertyDAO, never()).insert(any(), any(), anyLong(), anyLong());
101 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any()); 109 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
102 } 110 }
103 111
@@ -119,7 +127,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -119,7 +127,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
119 assertDoesNotThrow(() -> service.saveDeviceProperty(device, message)); 127 assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
120 128
121 // 断言:没有合法属性,不会写入 TDengine 与 Redis 129 // 断言:没有合法属性,不会写入 TDengine 与 Redis
122 - verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong()); 130 + verify(devicePropertyDAO, never()).insert(any(), any(), anyLong(), anyLong());
123 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any()); 131 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
124 } 132 }
125 133
@@ -141,7 +149,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -141,7 +149,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
141 149
142 // 断言:跳过空值,不会转换属性值,也不会写入 TDengine 与 Redis 150 // 断言:跳过空值,不会转换属性值,也不会写入 TDengine 与 Redis
143 verify(thingModelService, never()).convertThingModelPropertyValue(any(), any()); 151 verify(thingModelService, never()).convertThingModelPropertyValue(any(), any());
144 - verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong()); 152 + verify(devicePropertyDAO, never()).insert(any(), any(), anyLong(), anyLong());
145 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any()); 153 verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
146 } 154 }
147 155
@@ -170,31 +178,149 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -170,31 +178,149 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
170 } 178 }
171 179
172 @Test 180 @Test
173 - public void testDefineDevicePropertyData_fieldNameToLowerCase() {  
174 - // 准备参数:全大写缩写和驼峰缩写都需要转换为 TDengine 实际的小写字段名 181 + public void testDefineDevicePropertyData_success() {
  182 + // 准备参数:只有属性类型的物模型,才需要定义存储结构
175 Long productId = 2L; 183 Long productId = 2L;
176 IotProductDO product = IotProductDO.builder().id(productId).build(); 184 IotProductDO product = IotProductDO.builder().id(productId).build();
177 - List<IotThingModelDO> thingModels = Arrays.asList(  
178 - buildThingModel("Ua", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),  
179 - buildThingModel("PfT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),  
180 - buildThingModel("PT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),  
181 - buildThingModel("PA", IotDataSpecsDataTypeEnum.FLOAT.getDataType()));  
182 - thingModels.forEach(thingModel -> thingModel.setType(IotThingModelTypeEnum.PROPERTY.getType())); 185 + IotThingModelDO temperature = buildThingModel("Temperature", IotDataSpecsDataTypeEnum.FLOAT.getDataType());
  186 + temperature.setType(IotThingModelTypeEnum.PROPERTY.getType());
  187 + IotThingModelDO alarm = buildThingModel("Alarm", IotDataSpecsDataTypeEnum.INT.getDataType());
  188 + alarm.setType(IotThingModelTypeEnum.EVENT.getType());
183 189
184 // mock 方法 190 // mock 方法
185 when(productService.validateProductExists(productId)).thenReturn(product); 191 when(productService.validateProductExists(productId)).thenReturn(product);
186 - when(thingModelService.getThingModelListByProductId(productId)).thenReturn(thingModels);  
187 - when(devicePropertyMapper.getProductPropertySTableFieldList(productId)).thenReturn(Collections.emptyList()); 192 + when(thingModelService.getThingModelListByProductId(productId)).thenReturn(Arrays.asList(temperature, alarm));
188 193
189 // 调用 194 // 调用
190 service.defineDevicePropertyData(productId); 195 service.defineDevicePropertyData(productId);
191 196
192 - // 断言:字段名统一为小写下划线,避免 PT 和数据库中的 pt 被误判为不同字段  
193 - ArgumentCaptor<List<TDengineTableField>> captor = ArgumentCaptor.forClass(List.class);  
194 - verify(devicePropertyMapper).createProductPropertySTable(eq(productId), captor.capture());  
195 - assertEquals(Arrays.asList("ua", "pf_t", "pt", "pa"), captor.getValue().stream()  
196 - .map(TDengineTableField::getField)  
197 - .collect(Collectors.toList())); 197 + // 断言:只传入属性物模型,由存储 DAO 完成字段映射与差异比较
  198 + verify(devicePropertyDAO).defineProductPropertyTable(eq(productId), eq(singletonList(temperature)));
  199 + }
  200 +
  201 + @Test
  202 + public void testDefineDevicePropertyData_productNotExists() {
  203 + // 准备参数
  204 + Long productId = 2L;
  205 +
  206 + // mock 方法:产品不存在
  207 + when(productService.validateProductExists(productId))
  208 + .thenThrow(exception(PRODUCT_NOT_EXISTS));
  209 +
  210 + // 调用,并断言
  211 + assertServiceException(() -> service.defineDevicePropertyData(productId), PRODUCT_NOT_EXISTS);
  212 + verify(devicePropertyDAO, never()).defineProductPropertyTable(any(), any());
  213 + }
  214 +
  215 + @Test
  216 + public void testSaveDeviceProperty_success() {
  217 + // 准备参数
  218 + IotDeviceDO device = buildDevice();
  219 + IotThingModelDO thingModel = buildThingModel("Temperature", IotDataSpecsDataTypeEnum.INT.getDataType());
  220 + Map<String, Object> params = new HashMap<>();
  221 + params.put("Temperature", 36);
  222 + IotDeviceMessage message = buildMessage(params);
  223 +
  224 + // mock 方法
  225 + when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
  226 + .thenReturn(singletonList(thingModel));
  227 + when(thingModelService.convertThingModelPropertyValue(thingModel, 36)).thenReturn(36);
  228 +
  229 + // 调用
  230 + long before = System.currentTimeMillis();
  231 + service.saveDeviceProperty(device, message);
  232 + long after = System.currentTimeMillis();
  233 +
  234 + // 断言:ts 为存储时间,reportTime 为上报时间
  235 + ArgumentCaptor<Long> tsCaptor = ArgumentCaptor.forClass(Long.class);
  236 + verify(devicePropertyDAO).insert(eq(device), eq(MapUtil.of("Temperature", 36)), tsCaptor.capture(),
  237 + eq(LocalDateTimeUtil.toEpochMilli(message.getReportTime())));
  238 + assertTrue(tsCaptor.getValue() >= before && tsCaptor.getValue() <= after);
  239 + }
  240 +
  241 + // ========== getHistoryDevicePropertyList ==========
  242 +
  243 + @Test
  244 + public void testGetHistoryDevicePropertyList_success() {
  245 + // 准备参数
  246 + IotDevicePropertyHistoryListReqVO reqVO = buildHistoryReqVO("Temperature",
  247 + LocalDateTime.of(2026, 1, 1, 0, 0), LocalDateTime.of(2026, 1, 2, 0, 0));
  248 + IotDevicePropertyRespVO respVO = new IotDevicePropertyRespVO();
  249 + respVO.setValue(36);
  250 + respVO.setUpdateTime(1L);
  251 +
  252 + // mock 方法
  253 + when(deviceService.getDevice(eq(1L))).thenReturn(buildDevice());
  254 + when(devicePropertyDAO.selectHistoryList(eq(2L), same(reqVO))).thenReturn(singletonList(respVO));
  255 +
  256 + // 调用
  257 + List<IotDevicePropertyRespVO> list = service.getHistoryDevicePropertyList(reqVO);
  258 +
  259 + // 断言:按设备的产品编号查询时序库;使用按租户、逻辑删除过滤的 getDevice,而不是忽略租户的缓存查询
  260 + assertEquals(singletonList(respVO), list);
  261 + verify(deviceService, never()).getDeviceFromCache(anyLong());
  262 + }
  263 +
  264 + @Test
  265 + public void testGetHistoryDevicePropertyList_timesNull() {
  266 + // 准备参数
  267 + IotDevicePropertyHistoryListReqVO reqVO = buildHistoryReqVO("Temperature");
  268 + reqVO.setTimes(null);
  269 +
  270 + // 调用,并断言
  271 + assertServiceException(() -> service.getHistoryDevicePropertyList(reqVO), DEVICE_PROPERTY_HISTORY_TIMES_INVALID);
  272 + verifyNoInteractions(deviceService, devicePropertyDAO);
  273 + }
  274 +
  275 + @Test
  276 + public void testGetHistoryDevicePropertyList_timesLengthInvalid() {
  277 + // 准备参数
  278 + IotDevicePropertyHistoryListReqVO reqVO = buildHistoryReqVO("Temperature", LocalDateTime.now());
  279 +
  280 + // 调用,并断言
  281 + assertServiceException(() -> service.getHistoryDevicePropertyList(reqVO), DEVICE_PROPERTY_HISTORY_TIMES_INVALID);
  282 + verifyNoInteractions(deviceService, devicePropertyDAO);
  283 + }
  284 +
  285 + @Test
  286 + public void testGetHistoryDevicePropertyList_timeNull() {
  287 + // 准备参数:开始时间为空、结束时间为空
  288 + IotDevicePropertyHistoryListReqVO reqVO1 = buildHistoryReqVO("Temperature", null, LocalDateTime.now());
  289 + IotDevicePropertyHistoryListReqVO reqVO2 = buildHistoryReqVO("Temperature", LocalDateTime.now(), null);
  290 +
  291 + // 调用,并断言:返回空,不查询设备与时序库
  292 + assertTrue(service.getHistoryDevicePropertyList(reqVO1).isEmpty());
  293 + assertTrue(service.getHistoryDevicePropertyList(reqVO2).isEmpty());
  294 + verifyNoInteractions(deviceService, devicePropertyDAO);
  295 + }
  296 +
  297 + @Test
  298 + public void testGetHistoryDevicePropertyList_identifierInvalid() {
  299 + // 准备参数:保留字段、非法格式
  300 + LocalDateTime now = LocalDateTime.now();
  301 + IotDevicePropertyHistoryListReqVO reqVO1 = buildHistoryReqVO("reportTime", now, now);
  302 + IotDevicePropertyHistoryListReqVO reqVO2 = buildHistoryReqVO("ts", now, now);
  303 + IotDevicePropertyHistoryListReqVO reqVO3 = buildHistoryReqVO("a` OR 1=1", now, now);
  304 +
  305 + // 调用,并断言:返回空,不查询设备与时序库
  306 + assertTrue(service.getHistoryDevicePropertyList(reqVO1).isEmpty());
  307 + assertTrue(service.getHistoryDevicePropertyList(reqVO2).isEmpty());
  308 + assertTrue(service.getHistoryDevicePropertyList(reqVO3).isEmpty());
  309 + verifyNoInteractions(deviceService, devicePropertyDAO);
  310 + }
  311 +
  312 + @Test
  313 + public void testGetHistoryDevicePropertyList_deviceNotExists() {
  314 + // 准备参数
  315 + LocalDateTime now = LocalDateTime.now();
  316 + IotDevicePropertyHistoryListReqVO reqVO = buildHistoryReqVO("Temperature", now, now);
  317 +
  318 + // mock 方法:设备不存在、已删除,或者不属于当前租户
  319 + when(deviceService.getDevice(eq(1L))).thenReturn(null);
  320 +
  321 + // 调用,并断言:返回空,不查询时序库
  322 + assertTrue(service.getHistoryDevicePropertyList(reqVO).isEmpty());
  323 + verifyNoInteractions(devicePropertyDAO);
198 } 324 }
199 325
200 // ========== 辅助方法 ========== 326 // ========== 辅助方法 ==========
@@ -228,12 +354,23 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { @@ -228,12 +354,23 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
228 } 354 }
229 355
230 /** 356 /**
231 - * 抓取 mapper.insert 的 properties 入参 357 + * 构造属性历史查询条件,设备编号固定为 1
  358 + */
  359 + private IotDevicePropertyHistoryListReqVO buildHistoryReqVO(String identifier, LocalDateTime... times) {
  360 + IotDevicePropertyHistoryListReqVO reqVO = new IotDevicePropertyHistoryListReqVO();
  361 + reqVO.setDeviceId(1L);
  362 + reqVO.setIdentifier(identifier);
  363 + reqVO.setTimes(times);
  364 + return reqVO;
  365 + }
  366 +
  367 + /**
  368 + * 抓取 DAO.insert 的 properties 入参
232 */ 369 */
233 @SuppressWarnings("unchecked") 370 @SuppressWarnings("unchecked")
234 private Map<String, Object> captureMapperInsertProperties() { 371 private Map<String, Object> captureMapperInsertProperties() {
235 ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class); 372 ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class);
236 - verify(devicePropertyMapper).insert(any(IotDeviceDO.class), captor.capture(), anyLong(), anyLong()); 373 + verify(devicePropertyDAO).insert(any(IotDeviceDO.class), captor.capture(), anyLong(), anyLong());
237 return captor.getValue(); 374 return captor.getValue();
238 } 375 }
239 376
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImplTest.java
1 package cn.iocoder.yudao.module.iot.service.thingmodel; 1 package cn.iocoder.yudao.module.iot.service.thingmodel;
2 2
3 import cn.hutool.core.date.LocalDateTimeUtil; 3 import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
@@ -15,11 +16,14 @@ import org.junit.jupiter.api.Test; @@ -15,11 +16,14 @@ import org.junit.jupiter.api.Test;
15 import org.springframework.context.annotation.Import; 16 import org.springframework.context.annotation.Import;
16 import org.springframework.test.context.bean.override.mockito.MockitoBean; 17 import org.springframework.test.context.bean.override.mockito.MockitoBean;
17 18
  19 +import java.nio.charset.StandardCharsets;
18 import java.time.LocalDateTime; 20 import java.time.LocalDateTime;
19 import java.util.Arrays; 21 import java.util.Arrays;
20 import java.util.HashMap; 22 import java.util.HashMap;
  23 +import java.util.List;
21 import java.util.Map; 24 import java.util.Map;
22 25
  26 +import static java.util.Collections.singletonList;
23 import static org.junit.jupiter.api.Assertions.*; 27 import static org.junit.jupiter.api.Assertions.*;
24 28
25 /** 29 /**
@@ -162,6 +166,51 @@ public class IotThingModelServiceImplTest extends BaseDbUnitTest { @@ -162,6 +166,51 @@ public class IotThingModelServiceImplTest extends BaseDbUnitTest {
162 assertEquals(JsonUtils.toJsonString(value), result); 166 assertEquals(JsonUtils.toJsonString(value), result);
163 } 167 }
164 168
  169 + @Test
  170 + public void testConvertThingModelPropertyValue_jsonMaxLength() {
  171 + // 准备参数:JSON 恰好 LENGTH_JSON 字节,包含中文、emoji 等多字节字符
  172 + Map<String, Object> structValue = buildJsonValue(ThingModelProperty.LENGTH_JSON);
  173 + List<Object> arrayValue = singletonList(buildJsonValue(ThingModelProperty.LENGTH_JSON - 2));
  174 + String structJson = JsonUtils.toJsonString(structValue);
  175 + String arrayJson = JsonUtils.toJsonString(arrayValue);
  176 + assertEquals(ThingModelProperty.LENGTH_JSON, structJson.getBytes(StandardCharsets.UTF_8).length);
  177 + assertEquals(ThingModelProperty.LENGTH_JSON, arrayJson.getBytes(StandardCharsets.UTF_8).length);
  178 + assertTrue(structJson.length() < ThingModelProperty.LENGTH_JSON); // 按字节而不是字符计算
  179 +
  180 + // 调用,并断言:STRUCT、ARRAY 都可以写入
  181 + assertEquals(structJson, thingModelService.convertThingModelPropertyValue(
  182 + buildThingModel("Location", IotDataSpecsDataTypeEnum.STRUCT.getDataType()), structValue));
  183 + assertEquals(arrayJson, thingModelService.convertThingModelPropertyValue(
  184 + buildThingModel("Points", IotDataSpecsDataTypeEnum.ARRAY.getDataType()), arrayValue));
  185 + }
  186 +
  187 + @Test
  188 + public void testConvertThingModelPropertyValue_jsonOverLength() {
  189 + // 准备参数:JSON 超过 LENGTH_JSON 1 个字节,包含中文、emoji 等多字节字符
  190 + Map<String, Object> structValue = buildJsonValue(ThingModelProperty.LENGTH_JSON + 1);
  191 + List<Object> arrayValue = singletonList(buildJsonValue(ThingModelProperty.LENGTH_JSON - 1));
  192 + assertEquals(ThingModelProperty.LENGTH_JSON + 1, JsonUtils.toJsonString(structValue).getBytes(StandardCharsets.UTF_8).length);
  193 + assertEquals(ThingModelProperty.LENGTH_JSON + 1, JsonUtils.toJsonString(arrayValue).getBytes(StandardCharsets.UTF_8).length);
  194 +
  195 + // 调用,并断言:STRUCT、ARRAY 都返回 null,不写入
  196 + assertNull(thingModelService.convertThingModelPropertyValue(
  197 + buildThingModel("Location", IotDataSpecsDataTypeEnum.STRUCT.getDataType()), structValue));
  198 + assertNull(thingModelService.convertThingModelPropertyValue(
  199 + buildThingModel("Points", IotDataSpecsDataTypeEnum.ARRAY.getDataType()), arrayValue));
  200 + }
  201 +
  202 + /**
  203 + * 构建 JSON 序列化后恰好 jsonBytes 字节(UTF-8)的结构体,包含中文、emoji 等多字节字符
  204 + */
  205 + private static Map<String, Object> buildJsonValue(int jsonBytes) {
  206 + String prefix = "中文😀";
  207 + Map<String, Object> value = new HashMap<>();
  208 + value.put("name", prefix);
  209 + int padding = jsonBytes - JsonUtils.toJsonString(value).getBytes(StandardCharsets.UTF_8).length;
  210 + value.put("name", prefix + StrUtil.repeat('x', padding));
  211 + return value;
  212 + }
  213 +
165 private void assertBoolValueConvertedToByte(Object reportedValue, byte expected) { 214 private void assertBoolValueConvertedToByte(Object reportedValue, byte expected) {
166 IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType()); 215 IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType());
167 216
yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql
@@ -43,20 +43,23 @@ CREATE TABLE IF NOT EXISTS &quot;iot_product&quot; ( @@ -43,20 +43,23 @@ CREATE TABLE IF NOT EXISTS &quot;iot_product&quot; (
43 CREATE TABLE IF NOT EXISTS "iot_device" ( 43 CREATE TABLE IF NOT EXISTS "iot_device" (
44 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 44 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
45 "device_name" varchar(255) NOT NULL DEFAULT '', 45 "device_name" varchar(255) NOT NULL DEFAULT '',
46 - "product_id" bigint NOT NULL,  
47 - "device_key" varchar(100) NOT NULL DEFAULT '',  
48 - "device_secret" varchar(100) NOT NULL DEFAULT '',  
49 "nickname" varchar(255) DEFAULT NULL, 46 "nickname" varchar(255) DEFAULT NULL,
50 - "status" tinyint NOT NULL DEFAULT '0',  
51 - "status_last_update_time" timestamp DEFAULT NULL,  
52 - "last_online_time" timestamp DEFAULT NULL,  
53 - "last_offline_time" timestamp DEFAULT NULL,  
54 - "active_time" timestamp DEFAULT NULL,  
55 - "ip" varchar(50) DEFAULT NULL,  
56 - "firmware_version" varchar(50) DEFAULT NULL,  
57 - "device_type" tinyint NOT NULL DEFAULT '0', 47 + "serial_number" varchar(255) DEFAULT NULL,
  48 + "pic_url" varchar(255) DEFAULT NULL,
  49 + "group_ids" varchar(255) DEFAULT NULL,
  50 + "product_id" bigint NOT NULL,
  51 + "product_key" varchar(100) DEFAULT NULL,
  52 + "device_type" tinyint DEFAULT NULL,
58 "gateway_id" bigint DEFAULT NULL, 53 "gateway_id" bigint DEFAULT NULL,
59 - "sub_device_count" int NOT NULL DEFAULT '0', 54 + "state" tinyint DEFAULT NULL,
  55 + "online_time" timestamp DEFAULT NULL,
  56 + "offline_time" timestamp DEFAULT NULL,
  57 + "active_time" timestamp DEFAULT NULL,
  58 + "firmware_id" bigint DEFAULT NULL,
  59 + "device_secret" varchar(100) DEFAULT NULL,
  60 + "latitude" decimal(10, 6) DEFAULT NULL,
  61 + "longitude" decimal(10, 6) DEFAULT NULL,
  62 + "config" varchar(4096) DEFAULT NULL,
60 "creator" varchar(64) DEFAULT '', 63 "creator" varchar(64) DEFAULT '',
61 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 64 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
62 "updater" varchar(64) DEFAULT '', 65 "updater" varchar(64) DEFAULT '',
yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java
1 package cn.iocoder.yudao.module.product.controller.admin.history.vo; 1 package cn.iocoder.yudao.module.product.controller.admin.history.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
6 6
7 @Schema(description = "管理后台 - 商品浏览记录 Response VO") 7 @Schema(description = "管理后台 - 商品浏览记录 Response VO")
8 @Data 8 @Data
yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java
@@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
6 import cn.iocoder.yudao.module.product.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
8 -import cn.idev.excel.annotation.ExcelProperty;  
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 import java.util.List; 13 import java.util.List;
yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java
1 package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity; 1 package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity;
2 2
3 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO; 3 import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO;
4 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
5 -import cn.idev.excel.annotation.ExcelProperty;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
10 import java.util.List; 10 import java.util.List;
yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java
1 package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product; 1 package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
6 6
7 @Schema(description = "管理后台 - 积分商城商品 Response VO") 7 @Schema(description = "管理后台 - 积分商城商品 Response VO")
8 @Data 8 @Data
yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java
1 package cn.iocoder.yudao.module.promotion.controller.app.point.vo; 1 package cn.iocoder.yudao.module.promotion.controller.app.point.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 @Schema(description = "用户 App - 积分商城活动 Response VO") 7 @Schema(description = "用户 App - 积分商城活动 Response VO")
8 @Data 8 @Data
yudao-module-mall/yudao-module-statistics/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/product/vo/ProductStatisticsRespVO.java
1 package cn.iocoder.yudao.module.statistics.controller.admin.product.vo; 1 package cn.iocoder.yudao.module.statistics.controller.admin.product.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.time.LocalDate; 9 import java.time.LocalDate;
10 10
yudao-module-mall/yudao-module-statistics/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryExcelVO.java
1 package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; 1 package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; 3 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 -import cn.idev.excel.annotation.format.DateTimeFormat;  
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
  6 +import org.apache.fesod.sheet.annotation.format.DateTimeFormat;
7 7
8 import java.time.LocalDate; 8 import java.time.LocalDate;
9 9
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/express/DeliveryExpressExcelVO.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
10 10
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/team/vo/MesCalTeamRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkplan/vo/MesDvCheckPlanRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.dv.checkplan.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.dv.checkplan.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkrecord/vo/MesDvCheckRecordRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.checkrecord.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.checkrecord.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkrecord/vo/line/MesDvCheckRecordLineRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.checkrecord.vo.line; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.checkrecord.vo.line;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
6 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
@@ -8,6 +7,7 @@ import lombok.AllArgsConstructor; @@ -8,6 +7,7 @@ import lombok.AllArgsConstructor;
8 import lombok.Builder; 7 import lombok.Builder;
9 import lombok.Data; 8 import lombok.Data;
10 import lombok.NoArgsConstructor; 9 import lombok.NoArgsConstructor;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 /** 12 /**
13 * 设备台账 Excel 导入 VO 13 * 设备台账 Excel 导入 VO
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/type/MesDvMachineryTypeRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.type; 1 package cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.type;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/maintenrecord/vo/MesDvMaintenRecordRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.maintenrecord.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.maintenrecord.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/maintenrecord/vo/line/MesDvMaintenRecordLineRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.maintenrecord.vo.line; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.maintenrecord.vo.line;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/repair/vo/MesDvRepairRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.repair.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.repair.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/repair/vo/line/MesDvRepairLineRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.dv.repair.vo.line; 1 package cn.iocoder.yudao.module.mes.controller.admin.dv.repair.vo.line;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/subject/vo/MesDvSubjectRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.subject.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.dv.subject.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/autocode/vo/part/MesMdAutoCodePartRespVO.java
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part; @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 6 import lombok.Data;
  7 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/autocode/vo/rule/MesMdAutoCodeRuleRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.rule; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.rule;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/client/vo/MesMdClientImportExcelVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.md.client.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.md.client.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
6 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
@@ -8,6 +7,7 @@ import lombok.AllArgsConstructor; @@ -8,6 +7,7 @@ import lombok.AllArgsConstructor;
8 import lombok.Builder; 7 import lombok.Builder;
9 import lombok.Data; 8 import lombok.Data;
10 import lombok.NoArgsConstructor; 9 import lombok.NoArgsConstructor;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 /** 12 /**
13 * 客户 Excel 导入 VO 13 * 客户 Excel 导入 VO
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/client/vo/MesMdClientRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.client.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.client.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemImportExcelVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
6 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
@@ -8,6 +7,7 @@ import lombok.AllArgsConstructor; @@ -8,6 +7,7 @@ import lombok.AllArgsConstructor;
8 import lombok.Builder; 7 import lombok.Builder;
9 import lombok.Data; 8 import lombok.Data;
10 import lombok.NoArgsConstructor; 9 import lombok.NoArgsConstructor;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 13
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/unitmeasure/vo/MesMdUnitMeasureRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.unitmeasure.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.unitmeasure.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/vendor/vo/MesMdVendorImportExcelVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.md.vendor.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.md.vendor.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
6 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
@@ -8,6 +7,7 @@ import lombok.AllArgsConstructor; @@ -8,6 +7,7 @@ import lombok.AllArgsConstructor;
8 import lombok.Builder; 7 import lombok.Builder;
9 import lombok.Data; 8 import lombok.Data;
10 import lombok.NoArgsConstructor; 9 import lombok.NoArgsConstructor;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 /** 12 /**
13 * 供应商 Excel 导入 VO 13 * 供应商 Excel 导入 VO
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/vendor/vo/MesMdVendorRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.vendor.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.md.vendor.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/workstation/vo/MesMdWorkstationRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.md.workstation.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.md.workstation.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/workstation/vo/workshop/MesMdWorkshopRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.md.workstation.vo.workshop; 1 package cn.iocoder.yudao.module.mes.controller.admin.md.workstation.vo.workshop;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/andon/vo/record/MesProAndonRecordRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.andon.vo.record; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.andon.vo.record;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/card/vo/MesProCardRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/MesProRouteRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/bom/MesProWorkOrderBomRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom; 1 package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workrecord/vo/MesProWorkRecordLogRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.workrecord.vo; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.workrecord.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
10 import lombok.experimental.Accessors; 8 import lombok.experimental.Accessors;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 13
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/defect/vo/MesQcDefectRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.qc.defect.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.qc.defect.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicator/vo/MesQcIndicatorRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.qc.indicator.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.qc.indicator.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/vo/MesQcOqcRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/vo/MesQcTemplateRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 import java.util.List; 9 import java.util.List;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/vo/indicator/MesQcTemplateIndicatorRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/template/vo/item/MesQcTemplateItemRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item; 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/vo/MesTmToolRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/vo/type/MesTmToolTypeRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/vo/MesWmBarcodeRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/vo/config/MesWmBarcodeConfigRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo.config; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo.config;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/MesWmItemReceiptRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/MesWmOutsourceIssueRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourcereceipt/vo/MesWmOutsourceReceiptRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/vo/MesWmPackageRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.packages.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.packages.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productreceipt/vo/MesWmProductReceiptRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/MesWmProductSalesRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticeRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnGroupRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
6 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
6 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.plan.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.plan.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/result/MesWmStockTakingTaskResultRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/area/MesWmWarehouseAreaRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.area; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.area;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/location/MesWmWarehouseLocationRespVO.java
1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.location; 1 package cn.iocoder.yudao.module.mes.controller.admin.wm.warehouse.vo.location;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.math.BigDecimal; 8 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/plan/MesCalPlanMapper.java
@@ -8,6 +8,9 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; @@ -8,6 +8,9 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
8 import com.baomidou.mybatisplus.core.toolkit.Wrappers; 8 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
9 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
10 10
  11 +import java.time.LocalDate;
  12 +import java.util.List;
  13 +
11 /** 14 /**
12 * MES 排班计划 Mapper 15 * MES 排班计划 Mapper
13 * 16 *
@@ -32,6 +35,15 @@ public interface MesCalPlanMapper extends BaseMapperX&lt;MesCalPlanDO&gt; { @@ -32,6 +35,15 @@ public interface MesCalPlanMapper extends BaseMapperX&lt;MesCalPlanDO&gt; {
32 return selectOne(MesCalPlanDO::getCode, code); 35 return selectOne(MesCalPlanDO::getCode, code);
33 } 36 }
34 37
  38 + default List<MesCalPlanDO> selectListByStatusAndDay(Integer status, LocalDate day) {
  39 + return selectList(new LambdaQueryWrapperX<MesCalPlanDO>()
  40 + .eq(MesCalPlanDO::getStatus, status)
  41 + // TODO @AI:dayend 获取下?应该有方法,这样可以 le,更统一点;
  42 + .lt(MesCalPlanDO::getStartDate, day.plusDays(1).atStartOfDay())
  43 + .ge(MesCalPlanDO::getEndDate, day.atStartOfDay())
  44 + .orderByAsc(MesCalPlanDO::getId));
  45 + }
  46 +
35 default void updateWithShiftFieldCleanup(MesCalPlanDO plan, boolean clearShiftMethod, boolean clearShiftCount) { 47 default void updateWithShiftFieldCleanup(MesCalPlanDO plan, boolean clearShiftMethod, boolean clearShiftCount) {
36 update(plan, Wrappers.<MesCalPlanDO>lambdaUpdate() 48 update(plan, Wrappers.<MesCalPlanDO>lambdaUpdate()
37 .eq(MesCalPlanDO::getId, plan.getId()) 49 .eq(MesCalPlanDO::getId, plan.getId())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -140,6 +140,9 @@ public interface ErrorCodeConstants { @@ -140,6 +140,9 @@ public interface ErrorCodeConstants {
140 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH = new ErrorCode(1_040_201_202, "班次数量不满足轮班方式要求"); 140 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH = new ErrorCode(1_040_201_202, "班次数量不满足轮班方式要求");
141 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID = new ErrorCode(1_040_201_203, "按天倒班必须填写合法的倒班天数"); 141 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID = new ErrorCode(1_040_201_203, "按天倒班必须填写合法的倒班天数");
142 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID = new ErrorCode(1_040_201_204, "不支持的倒班方式:{}"); 142 ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID = new ErrorCode(1_040_201_204, "不支持的倒班方式:{}");
  143 + ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID = new ErrorCode(1_040_201_205, "班次【{}】的开始或结束时间格式不正确,应为 HH:mm,结束时间可为 24:00");
  144 + ErrorCode CAL_TEAM_SHIFT_GENERATE_REST_INVALID = new ErrorCode(1_040_201_206,
  145 + "班组【{}】{} {}与 {} {}的上班时间重叠或首尾相接,没有休息间隔,请调整班次时间或倒班方式");
143 146
144 // ========== MES 日历排班-排班计划(1-040-202-000) ========== 147 // ========== MES 日历排班-排班计划(1-040-202-000) ==========
145 ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "排班计划不存在"); 148 ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "排班计划不存在");
@@ -152,6 +155,8 @@ public interface ErrorCodeConstants { @@ -152,6 +155,8 @@ public interface ErrorCodeConstants {
152 155
153 // ========== MES 日历排班-假期设置(1-040-203-000) ========== 156 // ========== MES 日历排班-假期设置(1-040-203-000) ==========
154 ErrorCode CAL_HOLIDAY_NOT_EXISTS = new ErrorCode(1_040_203_000, "假期设置不存在"); 157 ErrorCode CAL_HOLIDAY_NOT_EXISTS = new ErrorCode(1_040_203_000, "假期设置不存在");
  158 + ErrorCode CAL_HOLIDAY_PLAN_CONFIRMED = new ErrorCode(1_040_203_001,
  159 + "{} 在已确认的排班计划【{}】范围内,修改是否节假日会与已生成的排班不一致,不允许修改");
155 160
156 // ========== MES 设备管理-设备类型(1-040-300-000) ========== 161 // ========== MES 设备管理-设备类型(1-040-300-000) ==========
157 ErrorCode DV_MACHINERY_TYPE_NOT_EXISTS = new ErrorCode(1_040_300_000, "设备类型不存在"); 162 ErrorCode DV_MACHINERY_TYPE_NOT_EXISTS = new ErrorCode(1_040_300_000, "设备类型不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/holiday/MesCalHolidayServiceImpl.java
1 package cn.iocoder.yudao.module.mes.service.cal.holiday; 1 package cn.iocoder.yudao.module.mes.service.cal.holiday;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.mes.controller.admin.cal.holiday.vo.MesCalHolidaySaveReqVO; 6 import cn.iocoder.yudao.module.mes.controller.admin.cal.holiday.vo.MesCalHolidaySaveReqVO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
6 import cn.iocoder.yudao.module.mes.dal.mysql.cal.holiday.MesCalHolidayMapper; 9 import cn.iocoder.yudao.module.mes.dal.mysql.cal.holiday.MesCalHolidayMapper;
  10 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum;
  11 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum;
  12 +import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
7 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
  14 +import org.springframework.context.annotation.Lazy;
8 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
9 import org.springframework.validation.annotation.Validated; 16 import org.springframework.validation.annotation.Validated;
10 17
11 import java.time.LocalDateTime; 18 import java.time.LocalDateTime;
12 import java.util.List; 19 import java.util.List;
13 20
  21 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_HOLIDAY_PLAN_CONFIRMED;
  24 +
14 /** 25 /**
15 * MES 假期设置 Service 实现类 26 * MES 假期设置 Service 实现类
16 * 27 *
@@ -23,10 +34,17 @@ public class MesCalHolidayServiceImpl implements MesCalHolidayService { @@ -23,10 +34,17 @@ public class MesCalHolidayServiceImpl implements MesCalHolidayService {
23 @Resource 34 @Resource
24 private MesCalHolidayMapper holidayMapper; 35 private MesCalHolidayMapper holidayMapper;
25 36
  37 + @Resource
  38 + @Lazy
  39 + private MesCalPlanService planService;
  40 +
26 @Override 41 @Override
27 public Long saveHoliday(MesCalHolidaySaveReqVO saveReqVO) { 42 public Long saveHoliday(MesCalHolidaySaveReqVO saveReqVO) {
28 - // Upsert 逻辑:如果该日期已有记录,则更新 43 + // 1. 校验是否节假日的变化,不影响已确认的排班计划
29 MesCalHolidayDO existing = holidayMapper.selectByDay(saveReqVO.getDay()); 44 MesCalHolidayDO existing = holidayMapper.selectByDay(saveReqVO.getDay());
  45 + validateHolidayChangeForConfirmedPlan(existing, saveReqVO);
  46 +
  47 + // 2.1 Upsert 逻辑:如果该日期已有记录,则更新
30 if (existing != null) { 48 if (existing != null) {
31 MesCalHolidayDO updateObj = BeanUtils.toBean(saveReqVO, MesCalHolidayDO.class); 49 MesCalHolidayDO updateObj = BeanUtils.toBean(saveReqVO, MesCalHolidayDO.class);
32 updateObj.setId(existing.getId()); 50 updateObj.setId(existing.getId());
@@ -34,12 +52,33 @@ public class MesCalHolidayServiceImpl implements MesCalHolidayService { @@ -34,12 +52,33 @@ public class MesCalHolidayServiceImpl implements MesCalHolidayService {
34 return existing.getId(); 52 return existing.getId();
35 } 53 }
36 54
37 - // 插入新记录 55 + // 2.2 插入新记录
38 MesCalHolidayDO holiday = BeanUtils.toBean(saveReqVO, MesCalHolidayDO.class); 56 MesCalHolidayDO holiday = BeanUtils.toBean(saveReqVO, MesCalHolidayDO.class);
39 holidayMapper.insert(holiday); 57 holidayMapper.insert(holiday);
40 return holiday.getId(); 58 return holiday.getId();
41 } 59 }
42 60
  61 + /**
  62 + * 校验是否节假日的变化,不影响已确认的排班计划
  63 + *
  64 + * 排班计划确认时按“节假日停班”生成排班记录(按天倒班时还会顺延后续倒班),确认后不会重新生成。
  65 + * 因此已确认计划范围内的日期,不允许在“节假日”与“非节假日”之间切换,避免与已生成的排班不一致 https://t.zsxq.com/XdcRW
  66 + */
  67 + private void validateHolidayChangeForConfirmedPlan(MesCalHolidayDO existing, MesCalHolidaySaveReqVO saveReqVO) {
  68 + boolean oldHoliday = existing != null && MesCalHolidayTypeEnum.HOLIDAY.getType().equals(existing.getType());
  69 + boolean newHoliday = MesCalHolidayTypeEnum.HOLIDAY.getType().equals(saveReqVO.getType());
  70 + if (oldHoliday == newHoliday) {
  71 + return;
  72 + }
  73 + List<MesCalPlanDO> plans = planService.getPlanListByStatusAndDay(
  74 + MesCalPlanStatusEnum.CONFIRMED.getStatus(), saveReqVO.getDay().toLocalDate());
  75 + if (CollUtil.isEmpty(plans)) {
  76 + return;
  77 + }
  78 + throw exception(CAL_HOLIDAY_PLAN_CONFIRMED, saveReqVO.getDay().toLocalDate(),
  79 + StrUtil.join("、", convertList(plans, MesCalPlanDO::getName)));
  80 + }
  81 +
43 @Override 82 @Override
44 public List<MesCalHolidayDO> getHolidayList(LocalDateTime startDay, LocalDateTime endDay) { 83 public List<MesCalHolidayDO> getHolidayList(LocalDateTime startDay, LocalDateTime endDay) {
45 return holidayMapper.selectList(startDay, endDay); 84 return holidayMapper.selectList(startDay, endDay);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveRe @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveRe
6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.time.LocalDate;
9 import java.util.Collection; 10 import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 import java.util.Map; 12 import java.util.Map;
@@ -90,4 +91,13 @@ public interface MesCalPlanService { @@ -90,4 +91,13 @@ public interface MesCalPlanService {
90 */ 91 */
91 PageResult<MesCalPlanDO> getPlanPage(MesCalPlanPageReqVO pageReqVO); 92 PageResult<MesCalPlanDO> getPlanPage(MesCalPlanPageReqVO pageReqVO);
92 93
  94 + /**
  95 + * 获得指定状态、且起止日期包含指定日期的排班计划列表
  96 + *
  97 + * @param status 状态
  98 + * @param day 日期
  99 + * @return 排班计划列表
  100 + */
  101 + List<MesCalPlanDO> getPlanListByStatusAndDay(Integer status, LocalDate day);
  102 +
93 } 103 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImpl.java
@@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service;
18 import org.springframework.transaction.annotation.Transactional; 18 import org.springframework.transaction.annotation.Transactional;
19 import org.springframework.validation.annotation.Validated; 19 import org.springframework.validation.annotation.Validated;
20 20
  21 +import java.time.LocalDate;
21 import java.util.Collection; 22 import java.util.Collection;
22 import java.util.Collections; 23 import java.util.Collections;
23 import java.util.List; 24 import java.util.List;
@@ -143,6 +144,11 @@ public class MesCalPlanServiceImpl implements MesCalPlanService { @@ -143,6 +144,11 @@ public class MesCalPlanServiceImpl implements MesCalPlanService {
143 } 144 }
144 145
145 @Override 146 @Override
  147 + public List<MesCalPlanDO> getPlanListByStatusAndDay(Integer status, LocalDate day) {
  148 + return planMapper.selectListByStatusAndDay(status, day);
  149 + }
  150 +
  151 + @Override
146 public MesCalPlanDO validatePlanPrepare(Long planId) { 152 public MesCalPlanDO validatePlanPrepare(Long planId) {
147 MesCalPlanDO plan = validatePlanExists(planId); 153 MesCalPlanDO plan = validatePlanExists(planId);
148 if (ObjUtil.notEqual(MesCalPlanStatusEnum.PREPARE.getStatus(), plan.getStatus())) { 154 if (ObjUtil.notEqual(MesCalPlanStatusEnum.PREPARE.getStatus(), plan.getStatus())) {
@@ -171,11 +177,13 @@ public class MesCalPlanServiceImpl implements MesCalPlanService { @@ -171,11 +177,13 @@ public class MesCalPlanServiceImpl implements MesCalPlanService {
171 177
172 /** 178 /**
173 * 确认排班计划时,校验班组数量是否与轮班方式匹配 179 * 确认排班计划时,校验班组数量是否与轮班方式匹配
  180 + *
  181 + * 排班只会使用轮班方式所需数量的班组,多出的班组不会参与轮休或替班,因此要求数量一致,避免被静默忽略 https://t.zsxq.com/XdcRW
174 */ 182 */
175 private void validateTeamCountForConfirm(Long planId, Integer shiftType) { 183 private void validateTeamCountForConfirm(Long planId, Integer shiftType) {
176 Long teamCount = planTeamService.getPlanTeamCountByPlanId(planId); 184 Long teamCount = planTeamService.getPlanTeamCountByPlanId(planId);
177 MesCalShiftTypeEnum shiftTypeEnum = MesCalShiftTypeEnum.valueOf(shiftType); 185 MesCalShiftTypeEnum shiftTypeEnum = MesCalShiftTypeEnum.valueOf(shiftType);
178 - if (teamCount < shiftTypeEnum.getRequiredTeamCount()) { 186 + if (teamCount == null || teamCount != shiftTypeEnum.getRequiredTeamCount()) {
179 throw exception(CAL_PLAN_TEAM_COUNT_NOT_MATCH); 187 throw exception(CAL_PLAN_TEAM_COUNT_NOT_MATCH);
180 } 188 }
181 } 189 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
1 package cn.iocoder.yudao.module.mes.service.cal.team; 1 package cn.iocoder.yudao.module.mes.service.cal.team;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO; 4 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
4 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO; 8 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO; 10 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
8 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper; 11 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
  12 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum;
9 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum; 13 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
10 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum; 14 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
  15 +import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
11 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService; 16 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
12 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService; 17 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
13 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService; 18 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
@@ -20,14 +25,25 @@ import org.springframework.validation.annotation.Validated; @@ -20,14 +25,25 @@ import org.springframework.validation.annotation.Validated;
20 25
21 import java.time.LocalDate; 26 import java.time.LocalDate;
22 import java.time.LocalDateTime; 27 import java.time.LocalDateTime;
  28 +import java.time.LocalTime;
23 import java.time.temporal.ChronoUnit; 29 import java.time.temporal.ChronoUnit;
24 import java.util.ArrayList; 30 import java.util.ArrayList;
  31 +import java.util.Comparator;
  32 +import java.util.HashMap;
25 import java.util.List; 33 import java.util.List;
  34 +import java.util.Map;
  35 +import java.util.Set;
  36 +import java.util.regex.Matcher;
  37 +import java.util.regex.Pattern;
26 38
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 39 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  42 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_REST_INVALID;
28 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID; 43 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID;
29 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID; 44 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID;
30 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH; 45 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH;
  46 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID;
31 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH; 47 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH;
32 48
33 /** 49 /**
@@ -39,6 +55,11 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIF @@ -39,6 +55,11 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIF
39 @Validated 55 @Validated
40 public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { 56 public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
41 57
  58 + /**
  59 + * 班次时间格式:H:mm 或 HH:mm
  60 + */
  61 + private static final Pattern SHIFT_TIME_PATTERN = Pattern.compile("^(\\d{1,2}):(\\d{2})$");
  62 +
42 @Resource 63 @Resource
43 private MesCalTeamShiftMapper teamShiftMapper; 64 private MesCalTeamShiftMapper teamShiftMapper;
44 65
@@ -51,6 +72,12 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -51,6 +72,12 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
51 @Resource 72 @Resource
52 @Lazy 73 @Lazy
53 private MesCalPlanTeamService planTeamService; 74 private MesCalPlanTeamService planTeamService;
  75 + @Resource
  76 + @Lazy
  77 + private MesCalHolidayService holidayService;
  78 + @Resource
  79 + @Lazy
  80 + private MesCalTeamService teamService;
54 81
55 @Transactional(rollbackFor = Exception.class) 82 @Transactional(rollbackFor = Exception.class)
56 @Override 83 @Override
@@ -71,25 +98,118 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -71,25 +98,118 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
71 throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH); 98 throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH);
72 } 99 }
73 100
74 - // 2. 计算日期跨度,遍历每天生成排班记录,最后统一写入数据库 101 + // 2.1 计算日期跨度,查询范围内的节假日
75 LocalDate startDate = plan.getStartDate().toLocalDate(); 102 LocalDate startDate = plan.getStartDate().toLocalDate();
76 LocalDate endDate = plan.getEndDate().toLocalDate(); 103 LocalDate endDate = plan.getEndDate().toLocalDate();
77 long days = ChronoUnit.DAYS.between(startDate, endDate) + 1; 104 long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
  105 + Set<LocalDate> holidayDates = getHolidayDates(startDate, endDate);
  106 + // 2.2 遍历每天生成排班记录,最后统一写入数据库
  107 + // 假期停班:节假日不生成排班,按天倒班只累计有效排班日(假期不消耗倒班天数);按周/月/季度仍按日历周期换班 https://t.zsxq.com/XdcRW
  108 + boolean dayMethod = MesCalShiftMethodEnum.DAY.getMethod().equals(plan.getShiftMethod());
78 List<MesCalTeamShiftDO> allRecords = new ArrayList<>(); 109 List<MesCalTeamShiftDO> allRecords = new ArrayList<>();
79 int shiftIndex = 0; 110 int shiftIndex = 0;
  111 + int workDayOffset = 0;
80 for (int i = 0; i < days; i++) { 112 for (int i = 0; i < days; i++) {
81 LocalDate currentDate = startDate.plusDays(i); 113 LocalDate currentDate = startDate.plusDays(i);
82 LocalDateTime currentDay = currentDate.atStartOfDay(); 114 LocalDateTime currentDay = currentDate.atStartOfDay();
83 - // 2.2.1 根据倒班方式计算 shiftIndex 单白班不倒班,跳过  
84 - if (!MesCalShiftTypeEnum.SINGLE.getType().equals(plan.getShiftType())) {  
85 - shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan); 115 + boolean holiday = holidayDates.contains(currentDate);
  116 + // 2.2.1 根据倒班方式计算 shiftIndex 单白班不倒班,跳过;周期起点恰逢假期时,也照常换班
  117 + if (!MesCalShiftTypeEnum.SINGLE.getType().equals(plan.getShiftType()) && !(dayMethod && holiday)) {
  118 + shiftIndex = calculateShiftIndex(shiftIndex, dayMethod ? workDayOffset : i, currentDate, startDate, plan);
  119 + }
  120 + if (holiday) {
  121 + continue;
86 } 122 }
  123 + workDayOffset++;
87 // 2.2.2 根据轮班方式生成排班记录,收集到列表 124 // 2.2.2 根据轮班方式生成排班记录,收集到列表
88 buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts); 125 buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts);
89 } 126 }
  127 + // 2.3 校验同一班组相邻两次排班之间留有休息间隔 https://t.zsxq.com/XdcRW
  128 + validateTeamShiftRest(allRecords, shifts);
90 129
91 // 3. 批量插入排班记录 130 // 3. 批量插入排班记录
92 - teamShiftMapper.insertBatch(allRecords); 131 + if (CollUtil.isNotEmpty(allRecords)) {
  132 + teamShiftMapper.insertBatch(allRecords);
  133 + }
  134 + }
  135 +
  136 + /**
  137 + * 获得日期范围内的节假日(日期类型为节假日的,排班时停班)
  138 + */
  139 + private Set<LocalDate> getHolidayDates(LocalDate startDate, LocalDate endDate) {
  140 + List<MesCalHolidayDO> holidays = holidayService.getHolidayList(
  141 + startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
  142 + return convertSet(holidays, holiday -> holiday.getDay().toLocalDate(),
  143 + holiday -> MesCalHolidayTypeEnum.HOLIDAY.getType().equals(holiday.getType()));
  144 + }
  145 +
  146 + /**
  147 + * 校验同一班组相邻两次排班之间留有休息间隔
  148 + *
  149 + * 按全部排班的上班开始时刻排序,前一班的结束时刻不早于同一班组下一班的开始时刻,
  150 + * 即上班时间重叠或首尾相接(例如夜班次日 08:00 下班后立即接 08:00 的白班),不允许生成
  151 + */
  152 + private void validateTeamShiftRest(List<MesCalTeamShiftDO> records, List<MesCalPlanShiftDO> shifts) {
  153 + // 1. 解析班次的开始、结束时间
  154 + Map<Long, MesCalPlanShiftDO> shiftMap = convertMap(shifts, MesCalPlanShiftDO::getId);
  155 + Map<Long, LocalTime[]> shiftTimeMap = convertMap(shifts, MesCalPlanShiftDO::getId,
  156 + shift -> new LocalTime[]{parseShiftTime(shift, shift.getStartTime(), false),
  157 + parseShiftTime(shift, shift.getEndTime(), true)});
  158 +
  159 + // 2. 按上班开始时刻排序,逐条与同一班组的上一次排班比较
  160 + List<MesCalTeamShiftDO> sortedRecords = new ArrayList<>(records);
  161 + sortedRecords.sort(Comparator.comparing(record -> buildShiftTimeRange(record, shiftTimeMap)[0]));
  162 + Map<Long, MesCalTeamShiftDO> lastRecordMap = new HashMap<>();
  163 + for (MesCalTeamShiftDO record : sortedRecords) {
  164 + MesCalTeamShiftDO lastRecord = lastRecordMap.put(record.getTeamId(), record);
  165 + if (lastRecord == null || buildShiftTimeRange(lastRecord, shiftTimeMap)[1]
  166 + .isBefore(buildShiftTimeRange(record, shiftTimeMap)[0])) {
  167 + continue;
  168 + }
  169 + MesCalTeamDO team = teamService.getTeam(record.getTeamId());
  170 + throw exception(CAL_TEAM_SHIFT_GENERATE_REST_INVALID, team != null ? team.getName() : record.getTeamId(),
  171 + lastRecord.getDay().toLocalDate(), shiftMap.get(lastRecord.getShiftId()).getName(),
  172 + record.getDay().toLocalDate(), shiftMap.get(record.getShiftId()).getName());
  173 + }
  174 + }
  175 +
  176 + /**
  177 + * 计算排班记录的实际上班时间段
  178 + *
  179 + * 开始时间在排班日期当天;结束时间不晚于开始时间(例如 20:00~08:00、16:00~00:00)时,跨午夜到次日结束
  180 + *
  181 + * @return [开始时刻, 结束时刻]
  182 + */
  183 + private static LocalDateTime[] buildShiftTimeRange(MesCalTeamShiftDO record, Map<Long, LocalTime[]> shiftTimeMap) {
  184 + LocalTime[] shiftTime = shiftTimeMap.get(record.getShiftId());
  185 + LocalDate day = record.getDay().toLocalDate();
  186 + LocalDateTime start = day.atTime(shiftTime[0]);
  187 + LocalDateTime end = shiftTime[1].isAfter(shiftTime[0]) ? day.atTime(shiftTime[1])
  188 + : day.plusDays(1).atTime(shiftTime[1]);
  189 + return new LocalDateTime[]{start, end};
  190 + }
  191 +
  192 + /**
  193 + * 解析班次时间,兼容 H:mm(例如 8:00)
  194 + *
  195 + * 结束时间允许 24:00,表示当天 24:00,按 00:00 处理后由“结束时间不晚于开始时间时跨午夜到次日”规则得到次日 00:00 https://t.zsxq.com/XdcRW
  196 + *
  197 + * @param endTime 是否为结束时间
  198 + */
  199 + private static LocalTime parseShiftTime(MesCalPlanShiftDO shift, String time, boolean endTime) {
  200 + Matcher matcher = time != null ? SHIFT_TIME_PATTERN.matcher(time.trim()) : null;
  201 + if (matcher == null || !matcher.matches()) {
  202 + throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID, shift.getName());
  203 + }
  204 + int hour = Integer.parseInt(matcher.group(1));
  205 + int minute = Integer.parseInt(matcher.group(2));
  206 + if (endTime && hour == 24 && minute == 0) {
  207 + return LocalTime.MIDNIGHT;
  208 + }
  209 + if (hour > 23 || minute > 59) {
  210 + throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID, shift.getName());
  211 + }
  212 + return LocalTime.of(hour, minute);
93 } 213 }
94 214
95 /** 215 /**
@@ -180,7 +300,7 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -180,7 +300,7 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
180 .sort(1).build()); 300 .sort(1).build());
181 } 301 }
182 } else if (MesCalShiftTypeEnum.THREE.getType().equals(shiftType)) { 302 } else if (MesCalShiftTypeEnum.THREE.getType().equals(shiftType)) {
183 - // 三班倒 303 + // 三班倒:按 白班 → 夜班 → 中班 轮转。原 白班 → 中班 → 夜班 轮转时,换班当天中班 24:00 下班的班组会立即接 00:00 的夜班 https://t.zsxq.com/XdcRW
184 if (shiftIndex % 3 == 0) { 304 if (shiftIndex % 3 == 0) {
185 // A 组上白班、B 组上中班、C 组上夜班 305 // A 组上白班、B 组上中班、C 组上夜班
186 allRecords.add(MesCalTeamShiftDO.builder() 306 allRecords.add(MesCalTeamShiftDO.builder()
@@ -199,39 +319,39 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -199,39 +319,39 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
199 .shiftId(shifts.get(2).getId()) 319 .shiftId(shifts.get(2).getId())
200 .sort(3).build()); 320 .sort(3).build());
201 } else if (shiftIndex % 3 == 1) { 321 } else if (shiftIndex % 3 == 1) {
202 - // A 组上中班、B 组上夜班、C 组上白班 322 + // A 组上夜班、B 组上白班、C 组上中班
203 allRecords.add(MesCalTeamShiftDO.builder() 323 allRecords.add(MesCalTeamShiftDO.builder()
204 .planId(planId).day(currentDay) 324 .planId(planId).day(currentDay)
205 .teamId(teams.get(0).getTeamId()) 325 .teamId(teams.get(0).getTeamId())
206 - .shiftId(shifts.get(1).getId())  
207 - .sort(2).build());  
208 - allRecords.add(MesCalTeamShiftDO.builder()  
209 - .planId(planId).day(currentDay)  
210 - .teamId(teams.get(1).getTeamId())  
211 .shiftId(shifts.get(2).getId()) 326 .shiftId(shifts.get(2).getId())
212 .sort(3).build()); 327 .sort(3).build());
213 allRecords.add(MesCalTeamShiftDO.builder() 328 allRecords.add(MesCalTeamShiftDO.builder()
214 .planId(planId).day(currentDay) 329 .planId(planId).day(currentDay)
215 - .teamId(teams.get(2).getTeamId()) 330 + .teamId(teams.get(1).getTeamId())
216 .shiftId(shifts.get(0).getId()) 331 .shiftId(shifts.get(0).getId())
217 .sort(1).build()); 332 .sort(1).build());
  333 + allRecords.add(MesCalTeamShiftDO.builder()
  334 + .planId(planId).day(currentDay)
  335 + .teamId(teams.get(2).getTeamId())
  336 + .shiftId(shifts.get(1).getId())
  337 + .sort(2).build());
218 } else { 338 } else {
219 - // A 组上夜班、B 组上白班、C 组上中班 339 + // A 组上中班、B 组上夜班、C 组上白班
220 allRecords.add(MesCalTeamShiftDO.builder() 340 allRecords.add(MesCalTeamShiftDO.builder()
221 .planId(planId).day(currentDay) 341 .planId(planId).day(currentDay)
222 .teamId(teams.get(0).getTeamId()) 342 .teamId(teams.get(0).getTeamId())
223 - .shiftId(shifts.get(2).getId())  
224 - .sort(3).build()); 343 + .shiftId(shifts.get(1).getId())
  344 + .sort(2).build());
225 allRecords.add(MesCalTeamShiftDO.builder() 345 allRecords.add(MesCalTeamShiftDO.builder()
226 .planId(planId).day(currentDay) 346 .planId(planId).day(currentDay)
227 .teamId(teams.get(1).getTeamId()) 347 .teamId(teams.get(1).getTeamId())
228 - .shiftId(shifts.get(0).getId())  
229 - .sort(1).build()); 348 + .shiftId(shifts.get(2).getId())
  349 + .sort(3).build());
230 allRecords.add(MesCalTeamShiftDO.builder() 350 allRecords.add(MesCalTeamShiftDO.builder()
231 .planId(planId).day(currentDay) 351 .planId(planId).day(currentDay)
232 .teamId(teams.get(2).getTeamId()) 352 .teamId(teams.get(2).getTeamId())
233 - .shiftId(shifts.get(1).getId())  
234 - .sort(2).build()); 353 + .shiftId(shifts.get(0).getId())
  354 + .sort(1).build());
235 } 355 }
236 } 356 }
237 } 357 }
yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.home.MesHomeStatisticsMapper"> 3 <mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.home.MesHomeStatisticsMapper">
4 4
5 <!-- 按状态统计工单数量 --> 5 <!-- 按状态统计工单数量 -->
6 - <select id="selectWorkOrderCountGroupByStatus" resultType="java.util.Map"> 6 + <select id="selectWorkOrderCountGroupByStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
7 SELECT status, COUNT(*) AS count 7 SELECT status, COUNT(*) AS count
8 FROM mes_pro_work_order 8 FROM mes_pro_work_order
9 WHERE deleted = 0 9 WHERE deleted = 0
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 </select> 11 </select>
12 12
13 <!-- 统计报工产量合计 --> 13 <!-- 统计报工产量合计 -->
14 - <select id="selectFeedbackSummary" resultType="java.util.Map"> 14 + <select id="selectFeedbackSummary" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
15 SELECT IFNULL(SUM(feedback_quantity), 0) AS feedbackQuantity, 15 SELECT IFNULL(SUM(feedback_quantity), 0) AS feedbackQuantity,
16 IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity, 16 IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity,
17 IFNULL(SUM(unqualified_quantity), 0) AS unqualifiedQuantity 17 IFNULL(SUM(unqualified_quantity), 0) AS unqualifiedQuantity
@@ -25,7 +25,7 @@ @@ -25,7 +25,7 @@
25 </select> 25 </select>
26 26
27 <!-- 按设备状态统计设备台数 --> 27 <!-- 按设备状态统计设备台数 -->
28 - <select id="selectMachineryCountGroupByStatus" resultType="java.util.Map"> 28 + <select id="selectMachineryCountGroupByStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
29 SELECT status, COUNT(*) AS count 29 SELECT status, COUNT(*) AS count
30 FROM mes_dv_machinery 30 FROM mes_dv_machinery
31 WHERE deleted = 0 31 WHERE deleted = 0
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 </select> 51 </select>
52 52
53 <!-- 按天聚合报工产量 --> 53 <!-- 按天聚合报工产量 -->
54 - <select id="selectDailyFeedbackTrend" resultType="java.util.Map"> 54 + <select id="selectDailyFeedbackTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
55 SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d') AS date, 55 SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d') AS date,
56 IFNULL(SUM(feedback_quantity), 0) AS quantity, 56 IFNULL(SUM(feedback_quantity), 0) AS quantity,
57 IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity, 57 IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity,
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/holiday/MesCalHolidayServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.cal.holiday;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.cal.holiday.vo.MesCalHolidaySaveReqVO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
  7 +import cn.iocoder.yudao.module.mes.dal.mysql.cal.holiday.MesCalHolidayMapper;
  8 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum;
  9 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum;
  10 +import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
  11 +import jakarta.annotation.Resource;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.springframework.context.annotation.Import;
  14 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
  15 +
  16 +import java.time.LocalDateTime;
  17 +
  18 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  19 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  20 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  21 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_HOLIDAY_PLAN_CONFIRMED;
  22 +import static java.util.Collections.emptyList;
  23 +import static java.util.Collections.singletonList;
  24 +import static org.junit.jupiter.api.Assertions.*;
  25 +import static org.mockito.Mockito.*;
  26 +
  27 +/**
  28 + * {@link MesCalHolidayServiceImpl} 的单元测试
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Import(MesCalHolidayServiceImpl.class)
  33 +public class MesCalHolidayServiceImplTest extends BaseDbUnitTest {
  34 +
  35 + @Resource
  36 + private MesCalHolidayServiceImpl holidayService;
  37 +
  38 + @Resource
  39 + private MesCalHolidayMapper holidayMapper;
  40 +
  41 + @MockitoBean
  42 + private MesCalPlanService planService;
  43 +
  44 + @Test
  45 + public void testSaveHoliday_addHolidayInConfirmedPlan() {
  46 + // 准备参数
  47 + MesCalHolidaySaveReqVO reqVO = randomPojo(MesCalHolidaySaveReqVO.class, o -> {
  48 + o.setId(null); // 避免 id 被赋值
  49 + o.setDay(LocalDateTime.of(2026, 10, 20, 0, 0));
  50 + o.setType(MesCalHolidayTypeEnum.HOLIDAY.getType());
  51 + });
  52 + // mock planService 的方法:该日期在已确认的排班计划范围内
  53 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> o.setStatus(MesCalPlanStatusEnum.CONFIRMED.getStatus()));
  54 + when(planService.getPlanListByStatusAndDay(eq(MesCalPlanStatusEnum.CONFIRMED.getStatus()),
  55 + eq(reqVO.getDay().toLocalDate()))).thenReturn(singletonList(plan));
  56 +
  57 + // 调用,并断言异常
  58 + assertServiceException(() -> holidayService.saveHoliday(reqVO), CAL_HOLIDAY_PLAN_CONFIRMED,
  59 + reqVO.getDay().toLocalDate(), plan.getName());
  60 + // 断言:未写入假期设置
  61 + assertNull(holidayMapper.selectByDay(reqVO.getDay()));
  62 + }
  63 +
  64 + @Test
  65 + public void testSaveHoliday_cancelHolidayInConfirmedPlan() {
  66 + // mock 数据:已有节假日
  67 + MesCalHolidayDO dbHoliday = randomPojo(MesCalHolidayDO.class, o -> {
  68 + o.setId(null); // 避免 id 被赋值
  69 + o.setDay(LocalDateTime.of(2026, 10, 20, 0, 0));
  70 + o.setType(MesCalHolidayTypeEnum.HOLIDAY.getType());
  71 + });
  72 + holidayMapper.insert(dbHoliday);
  73 + // 准备参数:改为工作日
  74 + MesCalHolidaySaveReqVO reqVO = randomPojo(MesCalHolidaySaveReqVO.class, o -> {
  75 + o.setId(null); // 避免 id 被赋值
  76 + o.setDay(dbHoliday.getDay());
  77 + o.setType(MesCalHolidayTypeEnum.WORKDAY.getType());
  78 + });
  79 + // mock planService 的方法:该日期在已确认的排班计划范围内
  80 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> o.setStatus(MesCalPlanStatusEnum.CONFIRMED.getStatus()));
  81 + when(planService.getPlanListByStatusAndDay(eq(MesCalPlanStatusEnum.CONFIRMED.getStatus()),
  82 + eq(reqVO.getDay().toLocalDate()))).thenReturn(singletonList(plan));
  83 +
  84 + // 调用,并断言异常
  85 + assertServiceException(() -> holidayService.saveHoliday(reqVO), CAL_HOLIDAY_PLAN_CONFIRMED,
  86 + reqVO.getDay().toLocalDate(), plan.getName());
  87 + // 断言:假期设置未被修改
  88 + assertPojoEquals(dbHoliday, holidayMapper.selectById(dbHoliday.getId()));
  89 + }
  90 +
  91 + @Test
  92 + public void testSaveHoliday_updateRemarkInConfirmedPlan() {
  93 + // mock 数据:已有节假日
  94 + MesCalHolidayDO dbHoliday = randomPojo(MesCalHolidayDO.class, o -> {
  95 + o.setId(null); // 避免 id 被赋值
  96 + o.setDay(LocalDateTime.of(2026, 10, 20, 0, 0));
  97 + o.setType(MesCalHolidayTypeEnum.HOLIDAY.getType());
  98 + });
  99 + holidayMapper.insert(dbHoliday);
  100 + // 准备参数:仍为节假日,只修改备注
  101 + MesCalHolidaySaveReqVO reqVO = randomPojo(MesCalHolidaySaveReqVO.class, o -> {
  102 + o.setId(null); // 避免 id 被赋值
  103 + o.setDay(dbHoliday.getDay());
  104 + o.setType(MesCalHolidayTypeEnum.HOLIDAY.getType());
  105 + });
  106 +
  107 + // 调用
  108 + Long id = holidayService.saveHoliday(reqVO);
  109 + // 断言:更新已有记录,且是否节假日未变化,无需校验排班计划
  110 + assertEquals(dbHoliday.getId(), id);
  111 + assertPojoEquals(reqVO, holidayMapper.selectById(id), "id");
  112 + verify(planService, never()).getPlanListByStatusAndDay(any(), any());
  113 + }
  114 +
  115 + @Test
  116 + public void testSaveHoliday_createWithoutConfirmedPlan() {
  117 + // 准备参数
  118 + MesCalHolidaySaveReqVO reqVO = randomPojo(MesCalHolidaySaveReqVO.class, o -> {
  119 + o.setId(null); // 避免 id 被赋值
  120 + o.setDay(LocalDateTime.of(2026, 10, 20, 0, 0));
  121 + o.setType(MesCalHolidayTypeEnum.HOLIDAY.getType());
  122 + });
  123 + // mock planService 的方法:该日期没有已确认的排班计划
  124 + when(planService.getPlanListByStatusAndDay(eq(MesCalPlanStatusEnum.CONFIRMED.getStatus()),
  125 + eq(reqVO.getDay().toLocalDate()))).thenReturn(emptyList());
  126 +
  127 + // 调用
  128 + Long id = holidayService.saveHoliday(reqVO);
  129 + // 断言
  130 + assertPojoEquals(reqVO, holidayMapper.selectById(id), "id");
  131 + }
  132 +
  133 +}
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java
1 package cn.iocoder.yudao.module.mes.service.cal.plan; 1 package cn.iocoder.yudao.module.mes.service.cal.plan;
2 2
  3 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
3 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
4 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveReqVO; 5 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveReqVO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
@@ -14,9 +15,17 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -14,9 +15,17 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean;
14 15
15 import jakarta.annotation.Resource; 16 import jakarta.annotation.Resource;
16 17
  18 +import java.time.LocalDate;
17 import java.time.LocalDateTime; 19 import java.time.LocalDateTime;
  20 +import java.util.List;
18 21
  22 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  24 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
19 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 25 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  26 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_PLAN_NOT_PREPARE;
  27 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_PLAN_TEAM_COUNT_NOT_MATCH;
  28 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_REST_INVALID;
20 import static org.junit.jupiter.api.Assertions.*; 29 import static org.junit.jupiter.api.Assertions.*;
21 import static org.mockito.Mockito.*; 30 import static org.mockito.Mockito.*;
22 31
@@ -88,6 +97,85 @@ public class MesCalPlanServiceImplTest extends BaseDbUnitTest { @@ -88,6 +97,85 @@ public class MesCalPlanServiceImplTest extends BaseDbUnitTest {
88 assertEquals(MesCalPlanStatusEnum.CONFIRMED.getStatus(), updatePlan.getStatus()); 97 assertEquals(MesCalPlanStatusEnum.CONFIRMED.getStatus(), updatePlan.getStatus());
89 // 断言 2:触发生成班组排班记录 98 // 断言 2:触发生成班组排班记录
90 verify(teamShiftService).generateTeamShiftRecords(plan.getId()); 99 verify(teamShiftService).generateTeamShiftRecords(plan.getId());
  100 + // 断言 3:重复确认被拒绝,且不会再次生成班组排班记录
  101 + assertServiceException(() -> planService.confirmPlan(plan.getId()), CAL_PLAN_NOT_PREPARE);
  102 + verify(teamShiftService, times(1)).generateTeamShiftRecords(plan.getId());
  103 + }
  104 +
  105 + @Test
  106 + public void testConfirmPlan_generateFailed_rollbackStatus() {
  107 + // mock 数据:插入一条草稿状态的两班倒排班计划
  108 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
  109 + o.setCalendarType(1);
  110 + o.setStartDate(LocalDateTime.of(2026, 10, 19, 0, 0));
  111 + o.setEndDate(LocalDateTime.of(2026, 10, 21, 0, 0));
  112 + o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
  113 + o.setShiftType(MesCalShiftTypeEnum.TWO.getType());
  114 + o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
  115 + o.setShiftCount(1);
  116 + });
  117 + planMapper.insert(plan);
  118 + // mock 方法:班组数量满足要求,但生成排班时校验到没有休息间隔
  119 + when(planTeamService.getPlanTeamCountByPlanId(plan.getId())).thenReturn(2L);
  120 + doThrow(exception(CAL_TEAM_SHIFT_GENERATE_REST_INVALID, "乙班",
  121 + LocalDate.of(2026, 10, 19), "夜班", LocalDate.of(2026, 10, 20), "白班"))
  122 + .when(teamShiftService).generateTeamShiftRecords(plan.getId());
  123 +
  124 + // 调用,并断言异常
  125 + assertServiceException(() -> planService.confirmPlan(plan.getId()), CAL_TEAM_SHIFT_GENERATE_REST_INVALID,
  126 + "乙班", LocalDate.of(2026, 10, 19), "夜班", LocalDate.of(2026, 10, 20), "白班");
  127 + // 断言:事务回滚,计划仍为草稿
  128 + MesCalPlanDO dbPlan = planMapper.selectById(plan.getId());
  129 + assertEquals(MesCalPlanStatusEnum.PREPARE.getStatus(), dbPlan.getStatus());
  130 + }
  131 +
  132 + @Test
  133 + public void testConfirmPlan_teamCountExceed() {
  134 + // mock 数据:插入一条草稿状态的两班倒排班计划
  135 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
  136 + o.setCalendarType(1);
  137 + o.setStartDate(LocalDateTime.of(2026, 10, 19, 0, 0));
  138 + o.setEndDate(LocalDateTime.of(2026, 10, 21, 0, 0));
  139 + o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
  140 + o.setShiftType(MesCalShiftTypeEnum.TWO.getType());
  141 + o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
  142 + o.setShiftCount(1);
  143 + });
  144 + planMapper.insert(plan);
  145 + // mock 方法:分配了 3 个班组,多出的班组不会参与排班
  146 + when(planTeamService.getPlanTeamCountByPlanId(plan.getId())).thenReturn(3L);
  147 +
  148 + // 调用,并断言异常
  149 + assertServiceException(() -> planService.confirmPlan(plan.getId()), CAL_PLAN_TEAM_COUNT_NOT_MATCH);
  150 + // 断言:计划仍为草稿,且未生成排班记录
  151 + MesCalPlanDO dbPlan = planMapper.selectById(plan.getId());
  152 + assertEquals(MesCalPlanStatusEnum.PREPARE.getStatus(), dbPlan.getStatus());
  153 + verify(teamShiftService, never()).generateTeamShiftRecords(plan.getId());
  154 + }
  155 +
  156 + @Test
  157 + public void testGetPlanListByStatusAndDay() {
  158 + // mock 数据:已确认,结束日期为当天
  159 + MesCalPlanDO plan01 = randomPlan(LocalDateTime.of(2026, 10, 1, 0, 0),
  160 + LocalDateTime.of(2026, 10, 19, 0, 0), MesCalPlanStatusEnum.CONFIRMED.getStatus());
  161 + planMapper.insert(plan01);
  162 + // mock 数据:已确认,开始日期为当天
  163 + MesCalPlanDO plan02 = randomPlan(LocalDateTime.of(2026, 10, 19, 0, 0),
  164 + LocalDateTime.of(2026, 10, 31, 0, 0), MesCalPlanStatusEnum.CONFIRMED.getStatus());
  165 + planMapper.insert(plan02);
  166 + // mock 数据:已确认,但不包含当天
  167 + planMapper.insert(randomPlan(LocalDateTime.of(2026, 10, 20, 0, 0),
  168 + LocalDateTime.of(2026, 10, 31, 0, 0), MesCalPlanStatusEnum.CONFIRMED.getStatus()));
  169 + // mock 数据:包含当天,但为草稿
  170 + planMapper.insert(randomPlan(LocalDateTime.of(2026, 10, 1, 0, 0),
  171 + LocalDateTime.of(2026, 10, 31, 0, 0), MesCalPlanStatusEnum.PREPARE.getStatus()));
  172 +
  173 + // 调用
  174 + List<MesCalPlanDO> list = planService.getPlanListByStatusAndDay(
  175 + MesCalPlanStatusEnum.CONFIRMED.getStatus(), LocalDate.of(2026, 10, 19));
  176 +
  177 + // 断言:只返回包含当天的已确认计划(起止日期为当天也算)
  178 + assertEquals(SetUtils.asSet(plan01.getId(), plan02.getId()), convertSet(list, MesCalPlanDO::getId));
91 } 179 }
92 180
93 @Test 181 @Test
@@ -116,4 +204,16 @@ public class MesCalPlanServiceImplTest extends BaseDbUnitTest { @@ -116,4 +204,16 @@ public class MesCalPlanServiceImplTest extends BaseDbUnitTest {
116 verify(teamShiftService, never()).deleteByPlanId(plan.getId()); 204 verify(teamShiftService, never()).deleteByPlanId(plan.getId());
117 } 205 }
118 206
  207 + private static MesCalPlanDO randomPlan(LocalDateTime startDate, LocalDateTime endDate, Integer status) {
  208 + return randomPojo(MesCalPlanDO.class, o -> {
  209 + o.setCalendarType(1);
  210 + o.setStartDate(startDate);
  211 + o.setEndDate(endDate);
  212 + o.setStatus(status);
  213 + o.setShiftType(MesCalShiftTypeEnum.TWO.getType());
  214 + o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
  215 + o.setShiftCount(1);
  216 + });
  217 + }
  218 +
119 } 219 }
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java
@@ -2,13 +2,17 @@ package cn.iocoder.yudao.module.mes.service.cal.team; @@ -2,13 +2,17 @@ package cn.iocoder.yudao.module.mes.service.cal.team;
2 2
3 import cn.hutool.core.collection.ListUtil; 3 import cn.hutool.core.collection.ListUtil;
4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO; 8 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
8 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO; 10 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
9 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper; 11 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
  12 +import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum;
10 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum; 13 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
11 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum; 14 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
  15 +import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
12 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService; 16 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
13 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService; 17 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
14 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService; 18 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
@@ -18,10 +22,15 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -18,10 +22,15 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean;
18 22
19 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
20 24
  25 +import java.time.LocalDate;
21 import java.time.LocalDateTime; 26 import java.time.LocalDateTime;
  27 +import java.util.ArrayList;
22 import java.util.Comparator; 28 import java.util.Comparator;
23 import java.util.List; 29 import java.util.List;
24 30
  31 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  32 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_REST_INVALID;
  33 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID;
25 import static org.junit.jupiter.api.Assertions.*; 34 import static org.junit.jupiter.api.Assertions.*;
26 import static org.mockito.Mockito.*; 35 import static org.mockito.Mockito.*;
27 36
@@ -45,6 +54,10 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest { @@ -45,6 +54,10 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest {
45 private MesCalPlanShiftService planShiftService; 54 private MesCalPlanShiftService planShiftService;
46 @MockitoBean 55 @MockitoBean
47 private MesCalPlanTeamService planTeamService; 56 private MesCalPlanTeamService planTeamService;
  57 + @MockitoBean
  58 + private MesCalHolidayService holidayService;
  59 + @MockitoBean
  60 + private MesCalTeamService teamService;
48 61
49 @Test 62 @Test
50 public void testGenerateTeamShiftRecords_threeShift_rotateThreeStates() { 63 public void testGenerateTeamShiftRecords_threeShift_rotateThreeStates() {
@@ -79,22 +92,22 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest { @@ -79,22 +92,22 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest {
79 // 调用 92 // 调用
80 teamShiftService.generateTeamShiftRecords(planId); 93 teamShiftService.generateTeamShiftRecords(planId);
81 94
82 - // 断言:3 天 × 3 班次 = 9 条排班记录,班组按天轮转  
83 - List<MesCalTeamShiftDO> records = teamShiftMapper.selectListByPlanId(planId);  
84 - records.sort(Comparator.comparing(MesCalTeamShiftDO::getDay).thenComparing(MesCalTeamShiftDO::getSort)); 95 + // 断言:3 天 × 3 班次 = 9 条排班记录,班组按 白班 → 夜班 → 中班 轮转
  96 + // 说明:原先按 白班 → 中班 → 夜班 轮转时,B 组 4/1 中班 24:00 下班后会立即接 4/2 00:00 的夜班,没有休息间隔
  97 + List<MesCalTeamShiftDO> records = getRecords(planId);
85 assertEquals(9, records.size()); 98 assertEquals(9, records.size());
86 // 断言:第 1 天(shiftIndex=0):A 白班、B 中班、C 夜班 99 // 断言:第 1 天(shiftIndex=0):A 白班、B 中班、C 夜班
87 assertRecord(records.get(0), LocalDateTime.of(2026, 4, 1, 0, 0), 1, 101L, 11L); 100 assertRecord(records.get(0), LocalDateTime.of(2026, 4, 1, 0, 0), 1, 101L, 11L);
88 assertRecord(records.get(1), LocalDateTime.of(2026, 4, 1, 0, 0), 2, 102L, 12L); 101 assertRecord(records.get(1), LocalDateTime.of(2026, 4, 1, 0, 0), 2, 102L, 12L);
89 assertRecord(records.get(2), LocalDateTime.of(2026, 4, 1, 0, 0), 3, 103L, 13L); 102 assertRecord(records.get(2), LocalDateTime.of(2026, 4, 1, 0, 0), 3, 103L, 13L);
90 - // 断言:第 2 天(shiftIndex=1):C 白班、A 中班、B 夜班  
91 - assertRecord(records.get(3), LocalDateTime.of(2026, 4, 2, 0, 0), 1, 103L, 11L);  
92 - assertRecord(records.get(4), LocalDateTime.of(2026, 4, 2, 0, 0), 2, 101L, 12L);  
93 - assertRecord(records.get(5), LocalDateTime.of(2026, 4, 2, 0, 0), 3, 102L, 13L);  
94 - // 断言:第 3 天(shiftIndex=2):B 白班、C 中班、A 夜班  
95 - assertRecord(records.get(6), LocalDateTime.of(2026, 4, 3, 0, 0), 1, 102L, 11L);  
96 - assertRecord(records.get(7), LocalDateTime.of(2026, 4, 3, 0, 0), 2, 103L, 12L);  
97 - assertRecord(records.get(8), LocalDateTime.of(2026, 4, 3, 0, 0), 3, 101L, 13L); 103 + // 断言:第 2 天(shiftIndex=1):B 白班、C 中班、A 夜班(A、B 休息 8 小时,C 休息 32 小时)
  104 + assertRecord(records.get(3), LocalDateTime.of(2026, 4, 2, 0, 0), 1, 102L, 11L);
  105 + assertRecord(records.get(4), LocalDateTime.of(2026, 4, 2, 0, 0), 2, 103L, 12L);
  106 + assertRecord(records.get(5), LocalDateTime.of(2026, 4, 2, 0, 0), 3, 101L, 13L);
  107 + // 断言:第 3 天(shiftIndex=2):C 白班、A 中班、B 夜班
  108 + assertRecord(records.get(6), LocalDateTime.of(2026, 4, 3, 0, 0), 1, 103L, 11L);
  109 + assertRecord(records.get(7), LocalDateTime.of(2026, 4, 3, 0, 0), 2, 101L, 12L);
  110 + assertRecord(records.get(8), LocalDateTime.of(2026, 4, 3, 0, 0), 3, 102L, 13L);
98 } 111 }
99 112
100 @Test 113 @Test
@@ -121,6 +134,392 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest { @@ -121,6 +134,392 @@ public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest {
121 assertRecord(records.get(1), start.plusDays(1), 1, 101L, 11L); 134 assertRecord(records.get(1), start.plusDays(1), 1, 101L, 11L);
122 } 135 }
123 136
  137 + @Test
  138 + public void testGenerateTeamShiftRecords_singleCrossMidnight() {
  139 + // 准备参数
  140 + Long planId = 3003L;
  141 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  142 + LocalDate endDate = LocalDate.of(2026, 10, 21);
  143 + // mock 方法:单班次 22:00~06:00,跨午夜到次日结束,每天之间休息 16 小时
  144 + mockPlan(planId, MesCalShiftTypeEnum.SINGLE, null, null, startDate, endDate);
  145 + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(ListUtil.of(
  146 + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("夜班")
  147 + .startTime("22:00").endTime("06:00").build()));
  148 + mockTeams(planId, 101L);
  149 +
  150 + // 调用
  151 + teamShiftService.generateTeamShiftRecords(planId);
  152 +
  153 + // 断言:每天 1 条排班记录
  154 + List<MesCalTeamShiftDO> records = getRecords(planId);
  155 + assertEquals(3, records.size());
  156 + assertRecord(records.get(0), startDate.atStartOfDay(), 1, 101L, 11L);
  157 + assertRecord(records.get(1), startDate.plusDays(1).atStartOfDay(), 1, 101L, 11L);
  158 + assertRecord(records.get(2), endDate.atStartOfDay(), 1, 101L, 11L);
  159 + }
  160 +
  161 + @Test
  162 + public void testGenerateTeamShiftRecords_twoShiftFullDay_rotateWithoutRest() {
  163 + // 准备参数
  164 + Long planId = 3004L;
  165 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  166 + LocalDate endDate = LocalDate.of(2026, 10, 21);
  167 + // mock 方法:两班倒、按天倒班 1 天,默认班次 08:00~20:00 / 20:00~08:00 覆盖全天
  168 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  169 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:00");
  170 + mockTeams(planId, 101L, 102L);
  171 + when(teamService.getTeam(eq(102L))).thenReturn(MesCalTeamDO.builder().id(102L).name("乙班").build());
  172 +
  173 + // 调用,并断言异常:B 组 10-19 夜班次日 08:00 下班后,立即接 10-20 08:00 的白班,连续上班 24 小时
  174 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  175 + CAL_TEAM_SHIFT_GENERATE_REST_INVALID, "乙班",
  176 + LocalDate.of(2026, 10, 19), "夜班", LocalDate.of(2026, 10, 20), "白班");
  177 + // 断言:没有写入任何排班记录
  178 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  179 + }
  180 +
  181 + @Test
  182 + public void testGenerateTeamShiftRecords_twoShiftFullDay_longerShiftCountOnlyDelay() {
  183 + // 准备参数
  184 + Long planId = 3005L;
  185 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  186 + LocalDate endDate = LocalDate.of(2026, 10, 23);
  187 + // mock 方法:两班倒、按天倒班 3 天,默认班次覆盖全天
  188 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 3, startDate, endDate);
  189 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:00");
  190 + mockTeams(planId, 101L, 102L);
  191 + when(teamService.getTeam(eq(102L))).thenReturn(MesCalTeamDO.builder().id(102L).name("乙班").build());
  192 +
  193 + // 调用,并断言异常:增加倒班天数只是推迟到 10-22 换班,B 组仍然夜班下班后立即接白班
  194 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  195 + CAL_TEAM_SHIFT_GENERATE_REST_INVALID, "乙班",
  196 + LocalDate.of(2026, 10, 21), "夜班", LocalDate.of(2026, 10, 22), "白班");
  197 + // 断言:没有写入任何排班记录
  198 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  199 + }
  200 +
  201 + @Test
  202 + public void testGenerateTeamShiftRecords_twoShiftOverlap() {
  203 + // 准备参数
  204 + Long planId = 3006L;
  205 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  206 + LocalDate endDate = LocalDate.of(2026, 10, 20);
  207 + // mock 方法:两班倒、按天倒班 1 天,夜班 20:00~08:30 与次日 08:00 的白班重叠
  208 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  209 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:30");
  210 + mockTeams(planId, 101L, 102L);
  211 + when(teamService.getTeam(eq(102L))).thenReturn(MesCalTeamDO.builder().id(102L).name("乙班").build());
  212 +
  213 + // 调用,并断言异常
  214 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  215 + CAL_TEAM_SHIFT_GENERATE_REST_INVALID, "乙班",
  216 + LocalDate.of(2026, 10, 19), "夜班", LocalDate.of(2026, 10, 20), "白班");
  217 + // 断言:没有写入任何排班记录
  218 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  219 + }
  220 +
  221 + @Test
  222 + public void testGenerateTeamShiftRecords_twoShiftOneMinuteRest() {
  223 + // 准备参数
  224 + Long planId = 3007L;
  225 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  226 + LocalDate endDate = LocalDate.of(2026, 10, 21);
  227 + // mock 方法:两班倒、按天倒班 1 天,夜班 20:00~07:59,与次日 08:00 的白班之间留 1 分钟
  228 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  229 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "07:59");
  230 + mockTeams(planId, 101L, 102L);
  231 +
  232 + // 调用
  233 + teamShiftService.generateTeamShiftRecords(planId);
  234 +
  235 + // 断言:3 天 × 2 班次 = 6 条排班记录,每天换班
  236 + List<MesCalTeamShiftDO> records = getRecords(planId);
  237 + assertEquals(6, records.size());
  238 + assertRecord(records.get(0), LocalDateTime.of(2026, 10, 19, 0, 0), 1, 101L, 11L);
  239 + assertRecord(records.get(1), LocalDateTime.of(2026, 10, 19, 0, 0), 2, 102L, 12L);
  240 + assertRecord(records.get(2), LocalDateTime.of(2026, 10, 20, 0, 0), 1, 102L, 11L);
  241 + assertRecord(records.get(3), LocalDateTime.of(2026, 10, 20, 0, 0), 2, 101L, 12L);
  242 + assertRecord(records.get(4), LocalDateTime.of(2026, 10, 21, 0, 0), 1, 101L, 11L);
  243 + assertRecord(records.get(5), LocalDateTime.of(2026, 10, 21, 0, 0), 2, 102L, 12L);
  244 + }
  245 +
  246 + @Test
  247 + public void testGenerateTeamShiftRecords_twoShiftFullDay_singleDay() {
  248 + // 准备参数
  249 + Long planId = 3008L;
  250 + LocalDate day = LocalDate.of(2026, 10, 19);
  251 + // mock 方法:两班倒、默认班次覆盖全天,只排 1 天,不发生换班
  252 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, day, day);
  253 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:00");
  254 + mockTeams(planId, 101L, 102L);
  255 +
  256 + // 调用
  257 + teamShiftService.generateTeamShiftRecords(planId);
  258 +
  259 + // 断言:1 天 × 2 班次 = 2 条排班记录
  260 + List<MesCalTeamShiftDO> records = getRecords(planId);
  261 + assertEquals(2, records.size());
  262 + assertRecord(records.get(0), day.atStartOfDay(), 1, 101L, 11L);
  263 + assertRecord(records.get(1), day.atStartOfDay(), 2, 102L, 12L);
  264 + }
  265 +
  266 + @Test
  267 + public void testGenerateTeamShiftRecords_shiftTimeInvalid() {
  268 + // 准备参数
  269 + Long planId = 3009L;
  270 + LocalDate day = LocalDate.of(2026, 10, 19);
  271 + // mock 方法:白班结束时间不合法
  272 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, day, day);
  273 + mockTwoShifts(planId, "08:00", "25:00", "20:00", "08:00");
  274 + mockTeams(planId, 101L, 102L);
  275 +
  276 + // 调用,并断言异常
  277 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  278 + CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID, "白班");
  279 + // 断言:没有写入任何排班记录
  280 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  281 + }
  282 +
  283 + @Test
  284 + public void testGenerateTeamShiftRecords_threeShift_hourWithoutZeroAndEnd24() {
  285 + // 准备参数
  286 + Long planId = 3016L;
  287 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  288 + LocalDate endDate = LocalDate.of(2026, 10, 20);
  289 + // mock 方法:三班倒、按天倒班 1 天,时间写作 8:00、0:00,中班结束写作 24:00
  290 + mockPlan(planId, MesCalShiftTypeEnum.THREE, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  291 + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(ListUtil.of(
  292 + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班").startTime("8:00").endTime("16:00").build(),
  293 + MesCalPlanShiftDO.builder().id(12L).planId(planId).sort(2).name("中班").startTime("16:00").endTime("24:00").build(),
  294 + MesCalPlanShiftDO.builder().id(13L).planId(planId).sort(3).name("夜班").startTime("0:00").endTime("8:00").build()));
  295 + mockTeams(planId, 101L, 102L, 103L);
  296 +
  297 + // 调用
  298 + teamShiftService.generateTeamShiftRecords(planId);
  299 +
  300 + // 断言:与默认班次一致,2 天 × 3 班次 = 6 条排班记录,换班后休息 8 / 8 / 32 小时
  301 + List<MesCalTeamShiftDO> records = getRecords(planId);
  302 + assertEquals(6, records.size());
  303 + assertRecord(records.get(0), startDate.atStartOfDay(), 1, 101L, 11L);
  304 + assertRecord(records.get(1), startDate.atStartOfDay(), 2, 102L, 12L);
  305 + assertRecord(records.get(2), startDate.atStartOfDay(), 3, 103L, 13L);
  306 + assertRecord(records.get(3), endDate.atStartOfDay(), 1, 102L, 11L);
  307 + assertRecord(records.get(4), endDate.atStartOfDay(), 2, 103L, 12L);
  308 + assertRecord(records.get(5), endDate.atStartOfDay(), 3, 101L, 13L);
  309 + }
  310 +
  311 + @Test
  312 + public void testGenerateTeamShiftRecords_singleFullDayEnd24_withoutRest() {
  313 + // 准备参数
  314 + Long planId = 3017L;
  315 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  316 + LocalDate endDate = LocalDate.of(2026, 10, 20);
  317 + // mock 方法:单班次 00:00~24:00,结束于次日 00:00,与次日的班次首尾相接
  318 + mockPlan(planId, MesCalShiftTypeEnum.SINGLE, null, null, startDate, endDate);
  319 + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(ListUtil.of(
  320 + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("全天班")
  321 + .startTime("00:00").endTime("24:00").build()));
  322 + mockTeams(planId, 101L);
  323 + when(teamService.getTeam(eq(101L))).thenReturn(MesCalTeamDO.builder().id(101L).name("甲班").build());
  324 +
  325 + // 调用,并断言异常:24:00 不放宽连班校验
  326 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  327 + CAL_TEAM_SHIFT_GENERATE_REST_INVALID, "甲班", startDate, "全天班", endDate, "全天班");
  328 + // 断言:没有写入任何排班记录
  329 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  330 + }
  331 +
  332 + @Test
  333 + public void testGenerateTeamShiftRecords_shiftTimeInvalid_boundary() {
  334 + // 准备参数:开始时间不允许 24:00;结束时间只允许 24:00 整;分钟、小时越界;非 H:mm 格式
  335 + String[][] cases = {
  336 + {"24:00", "08:00"}, {"08:00", "24:30"}, {"08:60", "16:00"},
  337 + {"08:00", "16:5"}, {"8点", "16:00"}, {"08:00:00", "16:00"}
  338 + };
  339 + for (int i = 0; i < cases.length; i++) {
  340 + Long planId = 3020L + i;
  341 + LocalDate day = LocalDate.of(2026, 10, 19);
  342 + // mock 方法:单班次
  343 + mockPlan(planId, MesCalShiftTypeEnum.SINGLE, null, null, day, day);
  344 + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(ListUtil.of(
  345 + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班")
  346 + .startTime(cases[i][0]).endTime(cases[i][1]).build()));
  347 + mockTeams(planId, 101L);
  348 +
  349 + // 调用,并断言异常
  350 + assertServiceException(() -> teamShiftService.generateTeamShiftRecords(planId),
  351 + CAL_TEAM_SHIFT_GENERATE_SHIFT_TIME_INVALID, "白班");
  352 + // 断言:没有写入任何排班记录
  353 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  354 + }
  355 + }
  356 +
  357 + @Test
  358 + public void testGenerateTeamShiftRecords_dayMethod_holidayNotConsumeShiftCount() {
  359 + // 准备参数
  360 + Long planId = 3010L;
  361 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  362 + LocalDate endDate = LocalDate.of(2026, 10, 22);
  363 + // mock 方法:两班倒、按天倒班 2 天,班次之间留有空档
  364 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 2, startDate, endDate);
  365 + mockTwoShifts(planId, "08:00", "17:00", "20:00", "05:00");
  366 + mockTeams(planId, 101L, 102L);
  367 + // mock 方法:10-20 为节假日;10-21 为工作日,照常排班
  368 + mockHolidays(startDate, endDate, buildHoliday(LocalDate.of(2026, 10, 20), MesCalHolidayTypeEnum.HOLIDAY),
  369 + buildHoliday(LocalDate.of(2026, 10, 21), MesCalHolidayTypeEnum.WORKDAY));
  370 +
  371 + // 调用
  372 + teamShiftService.generateTeamShiftRecords(planId);
  373 +
  374 + // 断言:节假日停班,3 个有效排班日 × 2 班次 = 6 条排班记录
  375 + List<MesCalTeamShiftDO> records = getRecords(planId);
  376 + assertEquals(6, records.size());
  377 + // 断言:10-19、10-21 为第 1、2 个有效排班日,A 组白班
  378 + assertRecord(records.get(0), LocalDateTime.of(2026, 10, 19, 0, 0), 1, 101L, 11L);
  379 + assertRecord(records.get(1), LocalDateTime.of(2026, 10, 19, 0, 0), 2, 102L, 12L);
  380 + assertRecord(records.get(2), LocalDateTime.of(2026, 10, 21, 0, 0), 1, 101L, 11L);
  381 + assertRecord(records.get(3), LocalDateTime.of(2026, 10, 21, 0, 0), 2, 102L, 12L);
  382 + // 断言:10-22 为第 3 个有效排班日,才换班
  383 + assertRecord(records.get(4), LocalDateTime.of(2026, 10, 22, 0, 0), 1, 102L, 11L);
  384 + assertRecord(records.get(5), LocalDateTime.of(2026, 10, 22, 0, 0), 2, 101L, 12L);
  385 + }
  386 +
  387 + @Test
  388 + public void testGenerateTeamShiftRecords_dayMethod_firstDayHoliday() {
  389 + // 准备参数
  390 + Long planId = 3011L;
  391 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  392 + LocalDate endDate = LocalDate.of(2026, 10, 21);
  393 + // mock 方法:两班倒、按天倒班 1 天,首日 10-19 为节假日
  394 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  395 + mockTwoShifts(planId, "08:00", "17:00", "20:00", "05:00");
  396 + mockTeams(planId, 101L, 102L);
  397 + mockHolidays(startDate, endDate, buildHoliday(startDate, MesCalHolidayTypeEnum.HOLIDAY));
  398 +
  399 + // 调用
  400 + teamShiftService.generateTeamShiftRecords(planId);
  401 +
  402 + // 断言:从 10-20 开始排班,10-20 为第 1 个有效排班日,不换班
  403 + List<MesCalTeamShiftDO> records = getRecords(planId);
  404 + assertEquals(4, records.size());
  405 + assertRecord(records.get(0), LocalDateTime.of(2026, 10, 20, 0, 0), 1, 101L, 11L);
  406 + assertRecord(records.get(1), LocalDateTime.of(2026, 10, 20, 0, 0), 2, 102L, 12L);
  407 + assertRecord(records.get(2), LocalDateTime.of(2026, 10, 21, 0, 0), 1, 102L, 11L);
  408 + assertRecord(records.get(3), LocalDateTime.of(2026, 10, 21, 0, 0), 2, 101L, 12L);
  409 + }
  410 +
  411 + @Test
  412 + public void testGenerateTeamShiftRecords_allHoliday() {
  413 + // 准备参数
  414 + Long planId = 3012L;
  415 + LocalDate startDate = LocalDate.of(2026, 10, 19);
  416 + LocalDate endDate = LocalDate.of(2026, 10, 20);
  417 + // mock 方法:两班倒、计划范围内全部为节假日
  418 + mockPlan(planId, MesCalShiftTypeEnum.TWO, MesCalShiftMethodEnum.DAY, 1, startDate, endDate);
  419 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:00");
  420 + mockTeams(planId, 101L, 102L);
  421 + mockHolidays(startDate, endDate, buildHoliday(startDate, MesCalHolidayTypeEnum.HOLIDAY),
  422 + buildHoliday(endDate, MesCalHolidayTypeEnum.HOLIDAY));
  423 +
  424 + // 调用
  425 + teamShiftService.generateTeamShiftRecords(planId);
  426 +
  427 + // 断言:不生成排班记录
  428 + assertTrue(teamShiftMapper.selectListByPlanId(planId).isEmpty());
  429 + }
  430 +
  431 + @Test
  432 + public void testGenerateTeamShiftRecords_weekMethod_holidayOnWeekStart() {
  433 + // 10-18 周日、10-19 周一为节假日、10-20 周二
  434 + assertRotateAcrossHoliday(3013L, MesCalShiftMethodEnum.WEEK,
  435 + LocalDate.of(2026, 10, 18), LocalDate.of(2026, 10, 19));
  436 + }
  437 +
  438 + @Test
  439 + public void testGenerateTeamShiftRecords_monthMethod_holidayOnMonthStart() {
  440 + // 09-30、10-01 月初为节假日、10-02
  441 + assertRotateAcrossHoliday(3014L, MesCalShiftMethodEnum.MONTH,
  442 + LocalDate.of(2026, 9, 30), LocalDate.of(2026, 10, 1));
  443 + }
  444 +
  445 + @Test
  446 + public void testGenerateTeamShiftRecords_quarterMethod_holidayOnQuarterStart() {
  447 + // 06-30、07-01 季度初为节假日、07-02
  448 + assertRotateAcrossHoliday(3015L, MesCalShiftMethodEnum.QUARTER,
  449 + LocalDate.of(2026, 6, 30), LocalDate.of(2026, 7, 1));
  450 + }
  451 +
  452 + /**
  453 + * 断言按周/月/季度倒班时,周期起点恰逢节假日也照常换班
  454 + *
  455 + * 默认两班倒班次覆盖全天,换班前一天夜班的班组,隔着节假日停班一天再上白班,留有 24 小时休息
  456 + */
  457 + private void assertRotateAcrossHoliday(Long planId, MesCalShiftMethodEnum shiftMethod,
  458 + LocalDate startDate, LocalDate holidayDate) {
  459 + // 准备参数
  460 + LocalDate endDate = holidayDate.plusDays(1);
  461 + // mock 方法:两班倒、默认班次覆盖全天,周期起点为节假日
  462 + mockPlan(planId, MesCalShiftTypeEnum.TWO, shiftMethod, null, startDate, endDate);
  463 + mockTwoShifts(planId, "08:00", "20:00", "20:00", "08:00");
  464 + mockTeams(planId, 101L, 102L);
  465 + mockHolidays(startDate, endDate, buildHoliday(holidayDate, MesCalHolidayTypeEnum.HOLIDAY));
  466 +
  467 + // 调用
  468 + teamShiftService.generateTeamShiftRecords(planId);
  469 +
  470 + // 断言:节假日停班,2 天 × 2 班次 = 4 条排班记录
  471 + List<MesCalTeamShiftDO> records = getRecords(planId);
  472 + assertEquals(4, records.size());
  473 + // 断言:周期起点前,A 组白班、B 组夜班
  474 + assertRecord(records.get(0), startDate.atStartOfDay(), 1, 101L, 11L);
  475 + assertRecord(records.get(1), startDate.atStartOfDay(), 2, 102L, 12L);
  476 + // 断言:周期起点虽为节假日,仍然换班:B 组白班、A 组夜班
  477 + assertRecord(records.get(2), endDate.atStartOfDay(), 1, 102L, 11L);
  478 + assertRecord(records.get(3), endDate.atStartOfDay(), 2, 101L, 12L);
  479 + }
  480 +
  481 + private void mockPlan(Long planId, MesCalShiftTypeEnum shiftType, MesCalShiftMethodEnum shiftMethod,
  482 + Integer shiftCount, LocalDate startDate, LocalDate endDate) {
  483 + when(planService.getPlan(planId)).thenReturn(MesCalPlanDO.builder()
  484 + .id(planId).code("PLAN-" + planId).name("排班计划" + planId)
  485 + .calendarType(1).startDate(startDate.atStartOfDay()).endDate(endDate.atStartOfDay())
  486 + .shiftType(shiftType.getType())
  487 + .shiftMethod(shiftMethod != null ? shiftMethod.getMethod() : null)
  488 + .shiftCount(shiftCount).build());
  489 + }
  490 +
  491 + private void mockTwoShifts(Long planId, String dayStartTime, String dayEndTime,
  492 + String nightStartTime, String nightEndTime) {
  493 + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(ListUtil.of(
  494 + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班")
  495 + .startTime(dayStartTime).endTime(dayEndTime).build(),
  496 + MesCalPlanShiftDO.builder().id(12L).planId(planId).sort(2).name("夜班")
  497 + .startTime(nightStartTime).endTime(nightEndTime).build()));
  498 + }
  499 +
  500 + private void mockTeams(Long planId, Long... teamIds) {
  501 + List<MesCalPlanTeamDO> teams = new ArrayList<>();
  502 + for (int i = 0; i < teamIds.length; i++) {
  503 + teams.add(MesCalPlanTeamDO.builder().id(21L + i).planId(planId).teamId(teamIds[i]).build());
  504 + }
  505 + when(planTeamService.getPlanTeamListByPlanId(planId)).thenReturn(teams);
  506 + }
  507 +
  508 + private void mockHolidays(LocalDate startDate, LocalDate endDate, MesCalHolidayDO... holidays) {
  509 + when(holidayService.getHolidayList(startDate.atStartOfDay(), endDate.atTime(23, 59, 59)))
  510 + .thenReturn(ListUtil.of(holidays));
  511 + }
  512 +
  513 + private static MesCalHolidayDO buildHoliday(LocalDate day, MesCalHolidayTypeEnum type) {
  514 + return MesCalHolidayDO.builder().day(day.atStartOfDay()).type(type.getType()).build();
  515 + }
  516 +
  517 + private List<MesCalTeamShiftDO> getRecords(Long planId) {
  518 + List<MesCalTeamShiftDO> records = teamShiftMapper.selectListByPlanId(planId);
  519 + records.sort(Comparator.comparing(MesCalTeamShiftDO::getDay).thenComparing(MesCalTeamShiftDO::getSort));
  520 + return records;
  521 + }
  522 +
124 private static void assertRecord(MesCalTeamShiftDO actual, LocalDateTime day, Integer sort, 523 private static void assertRecord(MesCalTeamShiftDO actual, LocalDateTime day, Integer sort,
125 Long teamId, Long shiftId) { 524 Long teamId, Long shiftId) {
126 assertEquals(day, actual.getDay()); 525 assertEquals(day, actual.getDay());
yudao-module-mes/src/test/resources/sql/clean.sql
@@ -2,6 +2,7 @@ DELETE FROM &quot;mes_cal_team_shift&quot;; @@ -2,6 +2,7 @@ DELETE FROM &quot;mes_cal_team_shift&quot;;
2 DELETE FROM "mes_cal_plan_team"; 2 DELETE FROM "mes_cal_plan_team";
3 DELETE FROM "mes_cal_plan_shift"; 3 DELETE FROM "mes_cal_plan_shift";
4 DELETE FROM "mes_cal_plan"; 4 DELETE FROM "mes_cal_plan";
  5 +DELETE FROM "mes_cal_holiday";
5 DELETE FROM "mes_md_auto_code_rule"; 6 DELETE FROM "mes_md_auto_code_rule";
6 DELETE FROM "mes_md_auto_code_part"; 7 DELETE FROM "mes_md_auto_code_part";
7 DELETE FROM "mes_md_auto_code_record"; 8 DELETE FROM "mes_md_auto_code_record";
yudao-module-mes/src/test/resources/sql/create_tables.sql
@@ -80,6 +80,23 @@ CREATE TABLE IF NOT EXISTS &quot;mes_cal_team_shift&quot; ( @@ -80,6 +80,23 @@ CREATE TABLE IF NOT EXISTS &quot;mes_cal_team_shift&quot; (
80 ); 80 );
81 81
82 -- ---------------------------- 82 -- ----------------------------
  83 +-- MES 假期设置
  84 +-- ----------------------------
  85 +CREATE TABLE IF NOT EXISTS "mes_cal_holiday" (
  86 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  87 + "day" timestamp DEFAULT NULL,
  88 + "type" tinyint DEFAULT NULL,
  89 + "remark" varchar(500) DEFAULT NULL,
  90 + "creator" varchar(64) DEFAULT '',
  91 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  92 + "updater" varchar(64) DEFAULT '',
  93 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  94 + "deleted" bit NOT NULL DEFAULT FALSE,
  95 + "tenant_id" bigint NOT NULL DEFAULT 0,
  96 + PRIMARY KEY ("id")
  97 +);
  98 +
  99 +-- ----------------------------
83 -- MES 编码规则表 100 -- MES 编码规则表
84 -- ---------------------------- 101 -- ----------------------------
85 CREATE TABLE IF NOT EXISTS "mes_md_auto_code_rule" ( 102 CREATE TABLE IF NOT EXISTS "mes_md_auto_code_rule" (
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelProperty;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java
@@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; @@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
7 7
8 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
9 -import cn.idev.excel.annotation.ExcelProperty;  
10 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
11 import lombok.*; 9 import lombok.*;
  10 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  11 +import org.apache.fesod.sheet.annotation.ExcelProperty;
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 import java.util.Map; 13 import java.util.Map;
14 14
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/workitem/vo/workitem/PmsWorkItemImportExcelVO.java
1 package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem; 1 package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; 4 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
7 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@@ -15,6 +13,8 @@ import lombok.AllArgsConstructor; @@ -15,6 +13,8 @@ import lombok.AllArgsConstructor;
15 import lombok.Builder; 13 import lombok.Builder;
16 import lombok.Data; 14 import lombok.Data;
17 import lombok.NoArgsConstructor; 15 import lombok.NoArgsConstructor;
  16 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  17 +import org.apache.fesod.sheet.annotation.ExcelProperty;
18 18
19 import java.time.LocalDateTime; 19 import java.time.LocalDateTime;
20 import java.util.List; 20 import java.util.List;
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/workitem/vo/workitem/PmsWorkItemRespVO.java
1 package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem; 1 package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label.PmsWorkItemLabelRespVO; 3 import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label.PmsWorkItemLabelRespVO;
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
10 import java.util.List; 10 import java.util.List;
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java
@@ -45,6 +45,16 @@ public interface PmsProjectService { @@ -45,6 +45,16 @@ public interface PmsProjectService {
45 PmsProjectDO getProject(Long id); 45 PmsProjectDO getProject(Long id);
46 46
47 /** 47 /**
  48 + * 获得项目,并锁定项目行
  49 + *
  50 + * 需在事务中调用,锁保持至事务结束
  51 + *
  52 + * @param id 项目编号
  53 + * @return 项目
  54 + */
  55 + PmsProjectDO getProjectForUpdate(Long id);
  56 +
  57 + /**
48 * 获得项目详情并记录访问时间 58 * 获得项目详情并记录访问时间
49 * 59 *
50 * @param id 项目编号 60 * @param id 项目编号
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java
@@ -118,6 +118,11 @@ public class PmsProjectServiceImpl implements PmsProjectService { @@ -118,6 +118,11 @@ public class PmsProjectServiceImpl implements PmsProjectService {
118 } 118 }
119 119
120 @Override 120 @Override
  121 + public PmsProjectDO getProjectForUpdate(Long id) {
  122 + return projectMapper.selectOneForUpdate(PmsProjectDO::getId, id);
  123 + }
  124 +
  125 + @Override
121 public PmsProjectDO getProjectAndUpdateVisitTime(Long id, Long userId) { 126 public PmsProjectDO getProjectAndUpdateVisitTime(Long id, Long userId) {
122 // 1. 校验当前用户可以查看项目 127 // 1. 校验当前用户可以查看项目
123 PmsProjectDO project = projectMemberService.validateProjectReadable(id, userId); 128 PmsProjectDO project = projectMemberService.validateProjectReadable(id, userId);
@@ -307,8 +312,11 @@ public class PmsProjectServiceImpl implements PmsProjectService { @@ -307,8 +312,11 @@ public class PmsProjectServiceImpl implements PmsProjectService {
307 @Override 312 @Override
308 @Transactional(rollbackFor = Exception.class) 313 @Transactional(rollbackFor = Exception.class)
309 public void deleteProject(Long id, Long userId) { 314 public void deleteProject(Long id, Long userId) {
310 - // 1.1 校验项目存在  
311 - PmsProjectDO project = validateProjectExists(id); 315 + // 1.1 锁定项目并校验存在,与个人排序保持一致的加锁顺序(https://t.zsxq.com/V48Hl)
  316 + PmsProjectDO project = getProjectForUpdate(id);
  317 + if (project == null) {
  318 + throw exception(PROJECT_NOT_EXISTS);
  319 + }
312 // 1.2 校验项目位于回收站 320 // 1.2 校验项目位于回收站
313 if (ObjectUtil.notEqual(PmsProjectStatusEnum.RECYCLED.getStatus(), project.getStatus())) { 321 if (ObjectUtil.notEqual(PmsProjectStatusEnum.RECYCLED.getStatus(), project.getStatus())) {
314 throw exception(PROJECT_STATUS_INVALID); 322 throw exception(PROJECT_STATUS_INVALID);
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java
1 package cn.iocoder.yudao.module.pms.service.pm.workitem; 1 package cn.iocoder.yudao.module.pms.service.pm.workitem;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
4 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO; 5 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
5 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; 6 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
6 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper; 7 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper;
  8 +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
  9 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService;
7 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
  11 +import org.springframework.context.annotation.Lazy;
8 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
9 import org.springframework.transaction.annotation.Transactional; 13 import org.springframework.transaction.annotation.Transactional;
10 import org.springframework.validation.annotation.Validated; 14 import org.springframework.validation.annotation.Validated;
@@ -13,7 +17,10 @@ import java.util.Comparator; @@ -13,7 +17,10 @@ import java.util.Comparator;
13 import java.util.List; 17 import java.util.List;
14 import java.util.Map; 18 import java.util.Map;
15 19
  20 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 21 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  22 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
  23 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
17 24
18 /** 25 /**
19 * PMS 工作项个人排序 Service 实现类 26 * PMS 工作项个人排序 Service 实现类
@@ -27,13 +34,27 @@ public class PmsWorkItemUserSortServiceImpl implements PmsWorkItemUserSortServic @@ -27,13 +34,27 @@ public class PmsWorkItemUserSortServiceImpl implements PmsWorkItemUserSortServic
27 @Resource 34 @Resource
28 private PmsWorkItemUserSortMapper workItemUserSortMapper; 35 private PmsWorkItemUserSortMapper workItemUserSortMapper;
29 36
  37 + @Lazy // 延迟加载,避免循环依赖
  38 + @Resource
  39 + private PmsProjectService projectService;
  40 +
30 @Override 41 @Override
31 @Transactional(rollbackFor = Exception.class) 42 @Transactional(rollbackFor = Exception.class)
32 public void updateWorkItemUserSort(Long projectId, List<Long> workItemIds, Long userId) { 43 public void updateWorkItemUserSort(Long projectId, List<Long> workItemIds, Long userId) {
33 - // 1. 删除原有个人排序 44 + // 1.1 锁定项目,避免并发更新个人排序产生重复记录(https://t.zsxq.com/V48Hl)
  45 + PmsProjectDO project = projectService.getProjectForUpdate(projectId);
  46 + // 1.2 校验锁定后的项目状态,避免项目并发回收、删除后继续写入个人排序
  47 + if (project == null) {
  48 + throw exception(PROJECT_NOT_EXISTS);
  49 + }
  50 + if (!PmsProjectStatusEnum.ACTIVE.getStatus().equals(project.getStatus())) {
  51 + throw exception(PROJECT_STATUS_INVALID);
  52 + }
  53 +
  54 + // 2. 删除原有个人排序
34 workItemUserSortMapper.deleteByProjectIdAndUserId(projectId, userId); 55 workItemUserSortMapper.deleteByProjectIdAndUserId(projectId, userId);
35 56
36 - // 2. 按请求顺序保存个人排序 57 + // 3. 按请求顺序保存个人排序
37 for (int index = 0; index < workItemIds.size(); index++) { 58 for (int index = 0; index < workItemIds.size(); index++) {
38 workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(projectId) 59 workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(projectId)
39 .setWorkItemId(workItemIds.get(index)).setUserId(userId).setSort(index + 1)); 60 .setWorkItemId(workItemIds.get(index)).setUserId(userId).setSort(index + 1));
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java
@@ -33,6 +33,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic @@ -33,6 +33,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
33 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 33 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
34 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 34 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
35 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_ADMIN_REQUIRED; 35 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_ADMIN_REQUIRED;
  36 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
36 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_OWNER_REQUIRED; 37 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_OWNER_REQUIRED;
37 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID; 38 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
38 import static java.util.Arrays.asList; 39 import static java.util.Arrays.asList;
@@ -237,6 +238,35 @@ public class PmsProjectServiceImplTest extends BaseDbUnitTest { @@ -237,6 +238,35 @@ public class PmsProjectServiceImplTest extends BaseDbUnitTest {
237 } 238 }
238 239
239 @Test 240 @Test
  241 + public void testDeleteProject_success() {
  242 + // mock 数据
  243 + Long userId = randomLongId();
  244 + PmsProjectDO project = randomProjectDO(PmsProjectStatusEnum.RECYCLED.getStatus());
  245 + projectMapper.insert(project);
  246 + when(projectMemberService.hasProjectOwnerPermission(project.getId(), userId)).thenReturn(true);
  247 +
  248 + // 调用
  249 + projectService.deleteProject(project.getId(), userId);
  250 +
  251 + // 断言
  252 + assertNull(projectMapper.selectById(project.getId()));
  253 + verify(workItemWorkLogService).deleteWorkItemWorkLogListByProjectId(project.getId());
  254 + verify(workItemService).deleteWorkItemListByProjectId(project.getId());
  255 + verify(iterationService).deleteIterationListByProjectId(project.getId());
  256 + verify(projectMemberService).deleteProjectMemberListByProjectId(project.getId());
  257 + }
  258 +
  259 + @Test
  260 + public void testDeleteProject_projectNotExists() {
  261 + // 准备参数
  262 + Long projectId = randomLongId();
  263 + Long userId = randomLongId();
  264 +
  265 + // 调用,并断言异常
  266 + assertServiceException(() -> projectService.deleteProject(projectId, userId), PROJECT_NOT_EXISTS);
  267 + }
  268 +
  269 + @Test
240 public void testDeleteProject_statusInvalid() { 270 public void testDeleteProject_statusInvalid() {
241 // mock 数据 271 // mock 数据
242 Long userId = randomLongId(); 272 Long userId = randomLongId();
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java
@@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemMemberD @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemMemberD
24 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO; 24 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
25 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; 25 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
26 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemWorkLogDO; 26 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemWorkLogDO;
  27 +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper;
27 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemBoardMapper; 28 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemBoardMapper;
28 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemCommentMapper; 29 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemCommentMapper;
29 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemMapper; 30 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemMapper;
@@ -39,9 +40,14 @@ import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum; @@ -39,9 +40,14 @@ import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum;
39 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum; 40 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
40 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum; 41 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum;
41 import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService; 42 import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
  43 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
  44 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
  45 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
42 import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService; 46 import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
  47 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl;
43 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; 48 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
44 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; 49 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
  50 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
45 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 51 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
46 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 52 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
47 import jakarta.annotation.Resource; 53 import jakarta.annotation.Resource;
@@ -88,7 +94,8 @@ import static org.mockito.Mockito.when; @@ -88,7 +94,8 @@ import static org.mockito.Mockito.when;
88 * 94 *
89 * @author 芋道源码 95 * @author 芋道源码
90 */ 96 */
91 -@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsWorkItemStatusServiceImpl.class, 97 +@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class,
  98 + PmsWorkItemStatusServiceImpl.class,
92 PmsWorkItemBoardServiceImpl.class, 99 PmsWorkItemBoardServiceImpl.class,
93 PmsWorkItemCommentServiceImpl.class, PmsWorkItemWorkLogServiceImpl.class}) 100 PmsWorkItemCommentServiceImpl.class, PmsWorkItemWorkLogServiceImpl.class})
94 public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { 101 public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
@@ -110,10 +117,18 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { @@ -110,10 +117,18 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
110 private PmsWorkItemWorkLogMapper workItemWorkLogMapper; 117 private PmsWorkItemWorkLogMapper workItemWorkLogMapper;
111 @Resource 118 @Resource
112 private PmsWorkItemUserSortMapper workItemUserSortMapper; 119 private PmsWorkItemUserSortMapper workItemUserSortMapper;
  120 + @Resource
  121 + private PmsProjectMapper projectMapper;
113 122
114 @MockitoBean 123 @MockitoBean
115 private PmsProjectMemberService projectMemberService; 124 private PmsProjectMemberService projectMemberService;
116 @MockitoBean 125 @MockitoBean
  126 + private PmsProjectGroupService projectGroupService;
  127 + @MockitoBean
  128 + private PmsProjectFavoriteService projectFavoriteService;
  129 + @MockitoBean
  130 + private PmsProjectAnnouncementService projectAnnouncementService;
  131 + @MockitoBean
117 private PmsIterationService iterationService; 132 private PmsIterationService iterationService;
118 @MockitoBean 133 @MockitoBean
119 private PmsWorkItemLabelService workItemLabelService; 134 private PmsWorkItemLabelService workItemLabelService;
@@ -126,6 +141,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { @@ -126,6 +141,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
126 private NotifyMessageSendApi notifyMessageSendApi; 141 private NotifyMessageSendApi notifyMessageSendApi;
127 @MockitoBean 142 @MockitoBean
128 private AdminUserApi adminUserApi; 143 private AdminUserApi adminUserApi;
  144 + @MockitoBean
  145 + private PermissionApi permissionApi;
129 146
130 @Test 147 @Test
131 public void testCreateWorkItem_success() { 148 public void testCreateWorkItem_success() {
@@ -510,6 +527,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { @@ -510,6 +527,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
510 // mock 数据 527 // mock 数据
511 Long userId = randomLongId(); 528 Long userId = randomLongId();
512 Long projectId = randomLongId(); 529 Long projectId = randomLongId();
  530 + PmsProjectDO project = randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType());
  531 + projectMapper.insert(project);
513 PmsWorkItemStatusDO taskStatus = randomStatusDO(projectId, PmsWorkItemTypeEnum.TASK.getType(), 532 PmsWorkItemStatusDO taskStatus = randomStatusDO(projectId, PmsWorkItemTypeEnum.TASK.getType(),
514 PmsWorkItemStatusTypeEnum.PENDING.getType(), true, 1); 533 PmsWorkItemStatusTypeEnum.PENDING.getType(), true, 1);
515 workItemStatusMapper.insert(taskStatus); 534 workItemStatusMapper.insert(taskStatus);
@@ -521,7 +540,7 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { @@ -521,7 +540,7 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
521 PmsWorkItemDO second = randomWorkItemDO(projectId, PmsWorkItemTypeEnum.DEFECT.getType(), defectStatus, 2); 540 PmsWorkItemDO second = randomWorkItemDO(projectId, PmsWorkItemTypeEnum.DEFECT.getType(), defectStatus, 2);
522 workItemMapper.insert(second); 541 workItemMapper.insert(second);
523 when(projectMemberService.validateProjectWritable(projectId, userId)) 542 when(projectMemberService.validateProjectWritable(projectId, userId))
524 - .thenReturn(randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType())); 543 + .thenReturn(project);
525 // 准备参数 544 // 准备参数
526 PmsWorkItemPlanningSortReqVO reqVO = new PmsWorkItemPlanningSortReqVO().setProjectId(projectId) 545 PmsWorkItemPlanningSortReqVO reqVO = new PmsWorkItemPlanningSortReqVO().setProjectId(projectId)
527 .setWorkItemIds(Arrays.asList(second.getId(), first.getId())); 546 .setWorkItemIds(Arrays.asList(second.getId(), first.getId()));
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java
@@ -5,8 +5,18 @@ import cn.iocoder.yudao.framework.tenant.config.TenantProperties; @@ -5,8 +5,18 @@ import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
5 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 5 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
6 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; 6 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  8 +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
8 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; 9 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
  10 +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper;
9 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper; 11 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper;
  12 +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
  13 +import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
  14 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
  15 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
  16 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
  17 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
  18 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl;
  19 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
10 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 20 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
11 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; 21 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
12 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
@@ -17,15 +27,25 @@ import org.springframework.boot.test.context.TestConfiguration; @@ -17,15 +27,25 @@ import org.springframework.boot.test.context.TestConfiguration;
17 import org.springframework.context.annotation.Bean; 27 import org.springframework.context.annotation.Bean;
18 import org.springframework.context.annotation.Import; 28 import org.springframework.context.annotation.Import;
19 import org.springframework.jdbc.core.JdbcTemplate; 29 import org.springframework.jdbc.core.JdbcTemplate;
  30 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
20 31
21 import javax.sql.DataSource; 32 import javax.sql.DataSource;
  33 +import java.util.ArrayList;
22 import java.util.Arrays; 34 import java.util.Arrays;
23 import java.util.Collections; 35 import java.util.Collections;
24 import java.util.List; 36 import java.util.List;
  37 +import java.util.concurrent.CountDownLatch;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +import java.util.concurrent.Future;
  41 +import java.util.concurrent.TimeUnit;
25 42
26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 43 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
27 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 44 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  45 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
28 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 46 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  47 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
  48 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
29 import static org.junit.jupiter.api.Assertions.assertEquals; 49 import static org.junit.jupiter.api.Assertions.assertEquals;
30 50
31 /** 51 /**
@@ -33,18 +53,41 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,18 +53,41 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
33 * 53 *
34 * @author 芋道源码 54 * @author 芋道源码
35 */ 55 */
36 -@Import({PmsWorkItemUserSortServiceImpl.class, PmsWorkItemUserSortServiceImplTest.TenantTestConfiguration.class}) 56 +@Import({PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class,
  57 + PmsWorkItemUserSortServiceImplTest.TenantTestConfiguration.class})
37 public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { 58 public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
38 59
39 @Resource 60 @Resource
40 private PmsWorkItemUserSortServiceImpl workItemUserSortService; 61 private PmsWorkItemUserSortServiceImpl workItemUserSortService;
  62 +
41 @Resource 63 @Resource
42 private PmsWorkItemUserSortMapper workItemUserSortMapper; 64 private PmsWorkItemUserSortMapper workItemUserSortMapper;
43 @Resource 65 @Resource
  66 + private PmsProjectMapper projectMapper;
  67 + @Resource
44 private JdbcTemplate jdbcTemplate; 68 private JdbcTemplate jdbcTemplate;
45 @Resource 69 @Resource
46 private TenantLineInnerInterceptor tenantLineInnerInterceptor; 70 private TenantLineInnerInterceptor tenantLineInnerInterceptor;
47 71
  72 + @MockitoBean
  73 + private PmsProjectMemberService projectMemberService;
  74 + @MockitoBean
  75 + private PmsProjectGroupService projectGroupService;
  76 + @MockitoBean
  77 + private PmsProjectFavoriteService projectFavoriteService;
  78 + @MockitoBean
  79 + private PmsProjectAnnouncementService projectAnnouncementService;
  80 + @MockitoBean
  81 + private PmsIterationService iterationService;
  82 + @MockitoBean
  83 + private PmsWorkItemService workItemService;
  84 + @MockitoBean
  85 + private PmsWorkItemStatusService workItemStatusService;
  86 + @MockitoBean
  87 + private PmsWorkItemWorkLogService workItemWorkLogService;
  88 + @MockitoBean
  89 + private PermissionApi permissionApi;
  90 +
48 @BeforeEach 91 @BeforeEach
49 public void before() { 92 public void before() {
50 TenantContextHolder.setTenantId(0L); 93 TenantContextHolder.setTenantId(0L);
@@ -56,9 +99,33 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { @@ -56,9 +99,33 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
56 } 99 }
57 100
58 @Test 101 @Test
  102 + public void testUpdateWorkItemUserSort_success() {
  103 + // mock 数据
  104 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  105 + projectMapper.insert(project);
  106 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 238L);
  107 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 239L);
  108 +
  109 + // 调用:连续调整个人排序,并移除不再位于待规划区域的工作项
  110 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L);
  111 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L), 238L);
  112 +
  113 + // 断言:仅保留当前顺序,不影响其他用户的个人排序
  114 + List<PmsWorkItemUserSortDO> userSorts = workItemUserSortMapper
  115 + .selectListByProjectIdAndUserId(project.getId(), 238L);
  116 + assertEquals(List.of(50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId));
  117 + assertEquals(1, userSorts.get(0).getSort());
  118 + assertEquals(List.of(50L, 51L), convertList(workItemUserSortMapper
  119 + .selectListByProjectIdAndUserId(project.getId(), 239L), PmsWorkItemUserSortDO::getWorkItemId));
  120 + }
  121 +
  122 + @Test
59 public void testUpdateWorkItemUserSort_repeated() { 123 public void testUpdateWorkItemUserSort_repeated() {
  124 + // mock 数据
  125 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  126 + projectMapper.insert(project);
60 // 准备参数 127 // 准备参数
61 - Long projectId = randomLongId(); 128 + Long projectId = project.getId();
62 Long userId = randomLongId(); 129 Long userId = randomLongId();
63 Long firstId = randomLongId(); 130 Long firstId = randomLongId();
64 Long secondId = randomLongId(); 131 Long secondId = randomLongId();
@@ -78,7 +145,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { @@ -78,7 +145,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
78 @Test 145 @Test
79 public void testUpdateWorkItemUserSort_deletedRecords() { 146 public void testUpdateWorkItemUserSort_deletedRecords() {
80 // mock 数据 147 // mock 数据
81 - Long projectId = randomLongId(); 148 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  149 + projectMapper.insert(project);
  150 + Long projectId = project.getId();
82 Long userId = randomLongId(); 151 Long userId = randomLongId();
83 Long workItemId = randomLongId(); 152 Long workItemId = randomLongId();
84 insertUserSort(projectId, workItemId, userId, false); 153 insertUserSort(projectId, workItemId, userId, false);
@@ -99,7 +168,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { @@ -99,7 +168,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
99 @Test 168 @Test
100 public void testUpdateWorkItemUserSort_empty() { 169 public void testUpdateWorkItemUserSort_empty() {
101 // mock 数据 170 // mock 数据
102 - Long projectId = randomLongId(); 171 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  172 + projectMapper.insert(project);
  173 + Long projectId = project.getId();
103 Long userId = randomLongId(); 174 Long userId = randomLongId();
104 Long workItemId = randomLongId(); 175 Long workItemId = randomLongId();
105 insertUserSort(projectId, workItemId, userId, false); 176 insertUserSort(projectId, workItemId, userId, false);
@@ -113,6 +184,87 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { @@ -113,6 +184,87 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
113 } 184 }
114 185
115 @Test 186 @Test
  187 + public void testUpdateWorkItemUserSort_projectNotExists() {
  188 + // 调用,并断言异常
  189 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(999L,
  190 + List.of(50L), 238L), PROJECT_NOT_EXISTS);
  191 + assertEquals(0L, workItemUserSortMapper.selectCount());
  192 + }
  193 +
  194 + @Test
  195 + public void testUpdateWorkItemUserSort_projectDeleted() {
  196 + // mock 数据
  197 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  198 + projectMapper.insert(project);
  199 + projectMapper.deleteById(project.getId());
  200 +
  201 + // 调用,并断言异常
  202 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(),
  203 + List.of(50L), 238L), PROJECT_NOT_EXISTS);
  204 + assertEquals(0L, workItemUserSortMapper.selectCount());
  205 + }
  206 +
  207 + @Test
  208 + public void testUpdateWorkItemUserSort_projectStatusInvalid() {
  209 + // mock 数据
  210 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1)
  211 + .setStatus(PmsProjectStatusEnum.RECYCLED.getStatus());
  212 + projectMapper.insert(project);
  213 + workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(project.getId())
  214 + .setWorkItemId(50L).setUserId(238L).setSort(1));
  215 +
  216 + // 调用,并断言异常
  217 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(),
  218 + List.of(51L), 238L), PROJECT_STATUS_INVALID);
  219 + // 断言:拒绝更新时保留原有个人排序
  220 + assertEquals(List.of(50L), convertList(workItemUserSortMapper
  221 + .selectListByProjectIdAndUserId(project.getId(), 238L), PmsWorkItemUserSortDO::getWorkItemId));
  222 + }
  223 +
  224 + @Test
  225 + public void testUpdateWorkItemUserSort_concurrent() throws Exception {
  226 + // mock 数据
  227 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  228 + projectMapper.insert(project);
  229 + // 准备参数
  230 + CountDownLatch startLatch = new CountDownLatch(1);
  231 + ExecutorService executor = Executors.newFixedThreadPool(4);
  232 + List<Future<?>> futures = new ArrayList<>();
  233 +
  234 + // 调用:同一项目、同一用户并发保存个人排序
  235 + try {
  236 + for (int index = 0; index < 4; index++) {
  237 + futures.add(executor.submit(() -> {
  238 + // 工作线程使用与测试线程相同的租户,结束后清理,避免租户上下文残留
  239 + TenantContextHolder.setTenantId(0L);
  240 + try {
  241 + startLatch.await();
  242 + for (int count = 0; count < 5; count++) {
  243 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L);
  244 + }
  245 + return null;
  246 + } finally {
  247 + TenantContextHolder.clear();
  248 + }
  249 + }));
  250 + }
  251 + startLatch.countDown();
  252 + for (Future<?> future : futures) {
  253 + future.get(20, TimeUnit.SECONDS);
  254 + }
  255 + } finally {
  256 + executor.shutdownNow();
  257 + executor.awaitTermination(10, TimeUnit.SECONDS);
  258 + }
  259 +
  260 + // 断言:没有重复的有效记录,顺序与最后一次保存一致
  261 + List<PmsWorkItemUserSortDO> userSorts = workItemUserSortMapper
  262 + .selectListByProjectIdAndUserId(project.getId(), 238L);
  263 + assertEquals(List.of(51L, 50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId));
  264 + assertEquals(List.of(1, 2), convertList(userSorts, PmsWorkItemUserSortDO::getSort));
  265 + }
  266 +
  267 + @Test
116 public void testDeleteWorkItemUserSortByWorkItemId_success() { 268 public void testDeleteWorkItemUserSortByWorkItemId_success() {
117 // mock 数据 269 // mock 数据
118 Long projectId = randomLongId(); 270 Long projectId = randomLongId();
@@ -151,7 +303,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { @@ -151,7 +303,9 @@ public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
151 @Test 303 @Test
152 public void testDeleteWorkItemUserSort_tenantIsolation() { 304 public void testDeleteWorkItemUserSort_tenantIsolation() {
153 // mock 数据:两个租户使用相同的业务编号 305 // mock 数据:两个租户使用相同的业务编号
154 - Long projectId = randomLongId(); 306 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  307 + projectMapper.insert(project);
  308 + Long projectId = project.getId();
155 Long userId = randomLongId(); 309 Long userId = randomLongId();
156 Long workItemId = randomLongId(); 310 Long workItemId = randomLongId();
157 insertUserSort(projectId, workItemId, userId, false); 311 insertUserSort(projectId, workItemId, userId, false);
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/post/PostRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dept.vo.post; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dept.vo.post;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/post/PostSimpleRespVO.java
1 package cn.iocoder.yudao.module.system.controller.admin.dept.vo.post; 1 package cn.iocoder.yudao.module.system.controller.admin.dept.vo.post;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelProperty;
6 6
7 @Schema(description = "管理后台 - 岗位信息的精简 Response VO") 7 @Schema(description = "管理后台 - 岗位信息的精简 Response VO")
8 @Data 8 @Data
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/vo/data/DictDataRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dict.vo.data; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dict.vo.data;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/vo/type/DictTypeRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dict.vo.type; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.dict.vo.type;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/logger/vo/loginlog/LoginLogRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.logger.vo.loginlog; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.logger.vo.loginlog;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java
@@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog; @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 4 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
5 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
6 -import cn.idev.excel.annotation.ExcelProperty;  
7 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 import org.dromara.core.trans.anno.Trans; 8 import org.dromara.core.trans.anno.Trans;
9 import org.dromara.core.trans.constant.TransType; 9 import org.dromara.core.trans.constant.TransType;
10 import org.dromara.core.trans.vo.VO; 10 import org.dromara.core.trans.vo.VO;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.system.controller.admin.permission.vo.role; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.system.controller.admin.permission.vo.role;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import jakarta.validation.constraints.NotBlank; 7 import jakarta.validation.constraints.NotBlank;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 import java.util.Set; 13 import java.util.Set;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/log/SmsLogRespVO.java
@@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,10 +4,10 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.JsonConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.JsonConvert;
6 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
7 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
8 -import cn.idev.excel.annotation.ExcelProperty;  
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
13 import java.util.Map; 13 import java.util.Map;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/template/SmsTemplateRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.sms.vo.template; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.sms.vo.template;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 import java.util.List; 12 import java.util.List;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java
1 package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; 1 package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 import java.util.List; 12 import java.util.List;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
1 package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; 1 package cn.iocoder.yudao.module.system.controller.admin.user.vo.user;
2 2
3 -import cn.idev.excel.annotation.ExcelProperty;  
4 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
6 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
@@ -8,6 +7,7 @@ import lombok.AllArgsConstructor; @@ -8,6 +7,7 @@ import lombok.AllArgsConstructor;
8 import lombok.Builder; 7 import lombok.Builder;
9 import lombok.Data; 8 import lombok.Data;
10 import lombok.NoArgsConstructor; 9 import lombok.NoArgsConstructor;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 /** 12 /**
13 * 用户 Excel 导入 VO 13 * 用户 Excel 导入 VO
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserRespVO.java
@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.system.controller.admin.user.vo.user;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
6 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
7 -import cn.idev.excel.annotation.ExcelProperty;  
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 import java.util.Set; 12 import java.util.Set;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifySendServiceImpl.java
@@ -63,7 +63,7 @@ public class NotifySendServiceImpl implements NotifySendService { @@ -63,7 +63,7 @@ public class NotifySendServiceImpl implements NotifySendService {
63 NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode); 63 NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode);
64 // 站内信模板不存在 64 // 站内信模板不存在
65 if (template == null) { 65 if (template == null) {
66 - throw exception(NOTICE_NOT_FOUND); 66 + throw exception(NOTIFY_TEMPLATE_NOT_EXISTS);
67 } 67 }
68 return template; 68 return template;
69 } 69 }
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifySendServiceImplTest.java
@@ -17,8 +17,8 @@ import java.util.Map; @@ -17,8 +17,8 @@ import java.util.Map;
17 import static cn.hutool.core.util.RandomUtil.randomEle; 17 import static cn.hutool.core.util.RandomUtil.randomEle;
18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
19 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; 19 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
20 -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTICE_NOT_FOUND;  
21 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS; 20 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS;
  21 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
22 import static org.junit.jupiter.api.Assertions.assertEquals; 22 import static org.junit.jupiter.api.Assertions.assertEquals;
23 import static org.junit.jupiter.api.Assertions.assertNull; 23 import static org.junit.jupiter.api.Assertions.assertNull;
24 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.ArgumentMatchers.eq;
@@ -150,14 +150,32 @@ class NotifySendServiceImplTest extends BaseMockitoUnitTest { @@ -150,14 +150,32 @@ class NotifySendServiceImplTest extends BaseMockitoUnitTest {
150 } 150 }
151 151
152 @Test 152 @Test
153 - public void testCheckMailTemplateValid_notExists() { 153 + public void testSendSingleNotifyToAdmin_templateNotExists() {
  154 + // 准备参数
  155 + Long userId = randomLongId();
  156 + String templateCode = randomString();
  157 + Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
  158 + .put("op", "login").build();
  159 + // mock NotifyTemplateService 的方法
  160 + when(notifyTemplateService.getNotifyTemplateByCodeFromCache(eq(templateCode))).thenReturn(null);
  161 +
  162 + // 调用,并断言异常
  163 + assertServiceException(() -> notifySendService.sendSingleNotifyToAdmin(userId, templateCode, templateParams),
  164 + NOTIFY_TEMPLATE_NOT_EXISTS);
  165 + // 断言
  166 + verify(notifyTemplateService, never()).formatNotifyTemplateContent(anyString(), anyMap());
  167 + verify(notifyMessageService, never()).createNotifyMessage(anyLong(), anyInt(), any(), anyString(), anyMap());
  168 + }
  169 +
  170 + @Test
  171 + public void testValidateNotifyTemplate_notExists() {
154 // 准备参数 172 // 准备参数
155 String templateCode = randomString(); 173 String templateCode = randomString();
156 // mock 方法 174 // mock 方法
157 175
158 // 调用,并断言异常 176 // 调用,并断言异常
159 assertServiceException(() -> notifySendService.validateNotifyTemplate(templateCode), 177 assertServiceException(() -> notifySendService.validateNotifyTemplate(templateCode),
160 - NOTICE_NOT_FOUND); 178 + NOTIFY_TEMPLATE_NOT_EXISTS);
161 } 179 }
162 180
163 @Test 181 @Test
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/brand/WmsItemBrandRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.brand; 1 package cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.brand;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/item/vo/item/WmsItemRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item; 1 package cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.item;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuRespVO; 3 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuRespVO;
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 5 import lombok.Data;
  6 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  7 +import org.apache.fesod.sheet.annotation.ExcelProperty;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
10 import java.util.List; 10 import java.util.List;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/merchant/vo/WmsMerchantRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.md.merchant.vo; 1 package cn.iocoder.yudao.module.wms.controller.admin.md.merchant.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
8 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 7 import lombok.Data;
  8 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  9 +import org.apache.fesod.sheet.annotation.ExcelProperty;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/md/warehouse/vo/WmsWarehouseRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.md.warehouse.vo; 1 package cn.iocoder.yudao.module.wms.controller.admin.md.warehouse.vo;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
  5 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  6 +import org.apache.fesod.sheet.annotation.ExcelProperty;
7 7
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/check/vo/order/WmsCheckOrderRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.order.check.vo.order; 1 package cn.iocoder.yudao.module.wms.controller.admin.order.check.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.wms.controller.admin.order.check.vo.detail.WmsCheckOrderDetailRespVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.order.check.vo.detail.WmsCheckOrderDetailRespVO;
8 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/movement/vo/order/WmsMovementOrderRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.order; 1 package cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.detail.WmsMovementOrderDetailRespVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.detail.WmsMovementOrderDetailRespVO;
8 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/receipt/vo/order/WmsReceiptOrderRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.order.receipt.vo.order; 1 package cn.iocoder.yudao.module.wms.controller.admin.order.receipt.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.wms.controller.admin.order.receipt.vo.detail.WmsReceiptOrderDetailRespVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.order.receipt.vo.detail.WmsReceiptOrderDetailRespVO;
8 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderRespVO.java
1 package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order; 1 package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order;
2 2
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailRespVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailRespVO;
8 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.wms.enums.DictTypeConstants;
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
  10 +import org.apache.fesod.sheet.annotation.ExcelProperty;
11 11
12 import java.math.BigDecimal; 12 import java.math.BigDecimal;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
yudao-module-wms/src/main/resources/mapper/home/WmsHomeStatisticsMapper.xml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <mapper namespace="cn.iocoder.yudao.module.wms.dal.mysql.home.WmsHomeStatisticsMapper"> 3 <mapper namespace="cn.iocoder.yudao.module.wms.dal.mysql.home.WmsHomeStatisticsMapper">
4 4
5 <!-- 按单据类型和状态统计单据数量 --> 5 <!-- 按单据类型和状态统计单据数量 -->
6 - <select id="selectOrderCountGroupByTypeAndStatus" resultType="java.util.Map"> 6 + <select id="selectOrderCountGroupByTypeAndStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
7 SELECT orderType, status, SUM(count) AS count 7 SELECT orderType, status, SUM(count) AS count
8 FROM ( 8 FROM (
9 SELECT ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@RECEIPT.type} AS orderType, 9 SELECT ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@RECEIPT.type} AS orderType,
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 </select> 51 </select>
52 52
53 <!-- 按天聚合单据数量 --> 53 <!-- 按天聚合单据数量 -->
54 - <select id="selectDailyOrderTrend" resultType="java.util.Map"> 54 + <select id="selectDailyOrderTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
55 SELECT date, orderType, SUM(count) AS count 55 SELECT date, orderType, SUM(count) AS count
56 FROM ( 56 FROM (
57 SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, 57 SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date,
@@ -117,7 +117,7 @@ @@ -117,7 +117,7 @@
117 </select> 117 </select>
118 118
119 <!-- 按商品统计库存数量排行 --> 119 <!-- 按商品统计库存数量排行 -->
120 - <select id="selectInventoryItemRank" resultType="java.util.Map"> 120 + <select id="selectInventoryItemRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
121 SELECT item.id AS id, 121 SELECT item.id AS id,
122 item.name AS name, 122 item.name AS name,
123 IFNULL(SUM(inventory.quantity), 0) AS quantity 123 IFNULL(SUM(inventory.quantity), 0) AS quantity
@@ -135,7 +135,7 @@ @@ -135,7 +135,7 @@
135 </select> 135 </select>
136 136
137 <!-- 按仓库统计库存数量排行 --> 137 <!-- 按仓库统计库存数量排行 -->
138 - <select id="selectInventoryWarehouseRank" resultType="java.util.Map"> 138 + <select id="selectInventoryWarehouseRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
139 SELECT warehouse.id AS id, 139 SELECT warehouse.id AS id,
140 warehouse.name AS name, 140 warehouse.name AS name,
141 IFNULL(SUM(inventory.quantity), 0) AS quantity 141 IFNULL(SUM(inventory.quantity), 0) AS quantity
yudao-server/src/main/resources/application-dev.yaml
@@ -55,6 +55,24 @@ spring: @@ -55,6 +55,24 @@ spring:
55 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 55 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
56 username: root 56 username: root
57 password: 123456 57 password: 123456
  58 +# tdengine: # IoT 数据库(需要 IoT 物联网,且 yudao.iot.storage.type 为 tdengine 再开启噢!)
  59 +# lazy: true # 开启懒加载,保证启动速度
  60 +# url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # 参见:https://t.zsxq.com/qaX9c,开启 TDengine WebSocket 自动重连
  61 +# driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
  62 +# username: root
  63 +# password: taosdata
  64 +# druid:
  65 +# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL
  66 +# iotdb: # IoT 数据库(需要 IoT 物联网,且 yudao.iot.storage.type 为 iotdb 再开启噢!)
  67 +# lazy: true # 开启懒加载,保证启动速度
  68 +# type: com.zaxxer.hikari.HikariDataSource # IoTDB 不使用 Druid 连接池
  69 +# url: jdbc:iotdb://127.0.0.1:6667/ruoyi_vue_pro?sql_dialect=table # 表模型;数据库需要预先创建,且时间精度为 ms
  70 +# driver-class-name: org.apache.iotdb.jdbc.IoTDBDriver
  71 +# username: root
  72 +# password: root
  73 +# hikari:
  74 +# maximum-pool-size: 8
  75 +# connection-test-query: SELECT 1 # IoTDB 数据源的有效性检查 SQL
58 76
59 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 77 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
60 data: 78 data:
yudao-server/src/main/resources/application-local.yaml
@@ -71,7 +71,7 @@ spring: @@ -71,7 +71,7 @@ spring:
71 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true 71 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
72 username: root 72 username: root
73 password: 123456 73 password: 123456
74 -# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) 74 +# tdengine: # IoT 数据库(需要 IoT 物联网,且 yudao.iot.storage.type 为 tdengine 再开启噢!)
75 # lazy: true # 开启懒加载,保证启动速度 75 # lazy: true # 开启懒加载,保证启动速度
76 # url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # 参见:https://t.zsxq.com/qaX9c,开启 TDengine WebSocket 自动重连 76 # url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # 参见:https://t.zsxq.com/qaX9c,开启 TDengine WebSocket 自动重连
77 # driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver 77 # driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
@@ -79,6 +79,16 @@ spring: @@ -79,6 +79,16 @@ spring:
79 # password: taosdata 79 # password: taosdata
80 # druid: 80 # druid:
81 # validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL 81 # validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL
  82 +# iotdb: # IoT 数据库(需要 IoT 物联网,且 yudao.iot.storage.type 为 iotdb 再开启噢!)
  83 +# lazy: true # 开启懒加载,保证启动速度
  84 +# type: com.zaxxer.hikari.HikariDataSource # IoTDB 不使用 Druid 连接池
  85 +# url: jdbc:iotdb://127.0.0.1:6667/ruoyi_vue_pro?sql_dialect=table # 表模型;数据库需要预先创建,且时间精度为 ms
  86 +# driver-class-name: org.apache.iotdb.jdbc.IoTDBDriver
  87 +# username: root
  88 +# password: root
  89 +# hikari:
  90 +# maximum-pool-size: 8
  91 +# connection-test-query: SELECT 1 # IoTDB 数据源的有效性检查 SQL
82 92
83 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 93 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
84 data: 94 data:
yudao-server/src/main/resources/application.yaml
@@ -368,5 +368,7 @@ yudao: @@ -368,5 +368,7 @@ yudao:
368 iot: 368 iot:
369 message-bus: 369 message-bus:
370 type: redis # 消息总线的类型 370 type: redis # 消息总线的类型
  371 + storage:
  372 + type: tdengine # 时序存储的类型:tdengine、iotdb,需要配置同名的 spring.datasource.dynamic.datasource 数据源
371 373
372 debug: false 374 debug: false