Commit 09aa9ad97f2463a034fe596b2d587194179aa38a
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
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<T> extends MPJBaseMapper<T> { | @@ -99,6 +102,24 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | ||
| 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
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
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
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 >= #{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 >= #{startTime} | ||
| 121 | + </if> | ||
| 122 | + <if test="endTime != null"> | ||
| 123 | + AND time <= #{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 >= #{startTime} | ||
| 66 | + AND time <= #{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 "iot_product" ( | @@ -43,20 +43,23 @@ CREATE TABLE IF NOT EXISTS "iot_product" ( | ||
| 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<MesCalPlanDO> { | @@ -32,6 +35,15 @@ public interface MesCalPlanMapper extends BaseMapperX<MesCalPlanDO> { | ||
| 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 "mes_cal_team_shift"; | @@ -2,6 +2,7 @@ DELETE FROM "mes_cal_team_shift"; | ||
| 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 "mes_cal_team_shift" ( | @@ -80,6 +80,23 @@ CREATE TABLE IF NOT EXISTS "mes_cal_team_shift" ( | ||
| 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 |