diff --git a/pom.xml b/pom.xml index c64337f34..2b21cf122 100644 --- a/pom.xml +++ b/pom.xml @@ -17,12 +17,12 @@ yudao-module-infra yudao-module-iot - + yudao-module-bpm - + yudao-module-crm diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java new file mode 100644 index 000000000..6a21de7cb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.iot.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * iot 错误码枚举类 + *

+ * iot 系统,使用 1-050-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 产品相关 1-050-001-000 ============ + ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在"); + ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在"); +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDataFormatEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDataFormatEnum.java new file mode 100644 index 000000000..be3470982 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDataFormatEnum.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.enums; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 产品数据格式枚举类 + * 1. 标准数据格式(JSON)2. 透传/自定义 + */ +@AllArgsConstructor +@Getter +public enum ProductDataFormatEnum implements IntArrayValuable { + + JSON(1, "标准数据格式(JSON)"), + SCRIPT(2, "透传/自定义"); + + /** + * 类型 + */ + private final Integer type; + + /** + * 描述 + */ + private final String description; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductDataFormatEnum::getType).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDeviceTypeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDeviceTypeConstants.java new file mode 100644 index 000000000..d44a47102 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductDeviceTypeConstants.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.iot.enums; + +/** + * 产品设备类型常量 + */ +public interface ProductDeviceTypeConstants { + + // ========== 产品设备类型 ============ + String DEVICE = "device"; // 直连设备 + String GATEWAY = "gateway"; // 网关设备 + String GATEWAY_SUB = "gateway_sub"; // 网关子设备 + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductProtocolTypeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductProtocolTypeConstants.java new file mode 100644 index 000000000..fe033bf5d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductProtocolTypeConstants.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.iot.enums; + +/** + * 产品传输协议类型常量 + */ +public interface ProductProtocolTypeConstants { + + // ========== 产品传输协议类型 ============ + String MQTT = "mqtt"; // MQTT + String COAP = "coap"; // COAP + String HTTP = "http"; // HTTP + String HTTPS = "https"; // HTTPS +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductStatusEnum.java new file mode 100644 index 000000000..94ec21f61 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ProductStatusEnum.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.iot.enums; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 产品状态枚举类 + * 禁用 启用 + */ +@AllArgsConstructor +@Getter +public enum ProductStatusEnum implements IntArrayValuable { + + DISABLE(0, "禁用"), + ENABLE(1, "启用"); + + /** + * 类型 + */ + private final Integer type; + + /** + * 描述 + */ + private final String description; + + public static final int[] ARRAYS = {1, 2}; + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index 4615bce9f..25a97a1b1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -47,6 +47,12 @@ yudao-spring-boot-starter-test + + + cn.iocoder.boot + yudao-spring-boot-starter-excel + + org.eclipse.paho diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/ProductController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/ProductController.java new file mode 100644 index 000000000..739e13320 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/ProductController.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.ProductPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.ProductRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.ProductSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.ProductDO; +import cn.iocoder.yudao.module.iot.service.product.ProductService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - iot 产品") +@RestController +@RequestMapping("/iot/product") +@Validated +public class ProductController { + + @Resource + private ProductService productService; + + @PostMapping("/create") + @Operation(summary = "创建产品") + @PreAuthorize("@ss.hasPermission('iot:product:create')") + public CommonResult createProduct(@Valid @RequestBody ProductSaveReqVO createReqVO) { + return success(productService.createProduct(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品") + @PreAuthorize("@ss.hasPermission('iot:product:update')") + public CommonResult updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) { + productService.updateProduct(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:product:delete')") + public CommonResult deleteProduct(@RequestParam("id") Long id) { + productService.deleteProduct(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:product:query')") + public CommonResult getProduct(@RequestParam("id") Long id) { + ProductDO product = productService.getProduct(id); + return success(BeanUtils.toBean(product, ProductRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品分页") + @PreAuthorize("@ss.hasPermission('iot:product:query')") + public CommonResult> getProductPage(@Valid ProductPageReqVO pageReqVO) { + PageResult pageResult = productService.getProductPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProductRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品 Excel") + @PreAuthorize("@ss.hasPermission('iot:product:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProductExcel(@Valid ProductPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = productService.getProductPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品.xls", "数据", ProductRespVO.class, + BeanUtils.toBean(list, ProductRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductPageReqVO.java new file mode 100644 index 000000000..11404eaf5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductPageReqVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - iot 产品分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPageReqVO extends PageParam { + + @Schema(description = "产品名称", example = "李四") + private String name; + + @Schema(description = "产品标识") + private String identification; + + @Schema(description = "设备类型:device、gatway、gatway_sub", example = "1") + private String deviceType; + + @Schema(description = "厂商名称", example = "李四") + private String manufacturerName; + + @Schema(description = "产品型号") + private String model; + + @Schema(description = "数据格式:1. 标准数据格式(JSON)2. 透传/自定义,脚本解析") + private Integer dataFormat; + + @Schema(description = "设备接入平台的协议类型,默认为MQTT", example = "2") + private String protocolType; + + @Schema(description = "产品描述", example = "随便") + private String description; + + @Schema(description = "产品状态 (0: 启用, 1: 停用)", example = "2") + private Integer status; + + @Schema(description = "物模型定义") + private String metadata; + + @Schema(description = "消息协议ID") + private Long messageProtocol; + + @Schema(description = "消息协议名称", example = "芋艿") + private String protocolName; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductRespVO.java new file mode 100644 index 000000000..e5d251c02 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductRespVO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - iot 产品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProductRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "778") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("产品名称") + private String name; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品标识") + private String identification; + + @Schema(description = "设备类型:device、gatway、gatway_sub", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("设备类型:device、gatway、gatway_sub") + private String deviceType; + + @Schema(description = "厂商名称", example = "李四") + @ExcelProperty("厂商名称") + private String manufacturerName; + + @Schema(description = "产品型号") + @ExcelProperty("产品型号") + private String model; + + @Schema(description = "数据格式:1. 标准数据格式(JSON)2. 透传/自定义,脚本解析") + @ExcelProperty("数据格式:1. 标准数据格式(JSON)2. 透传/自定义,脚本解析") + private Integer dataFormat; + + @Schema(description = "设备接入平台的协议类型,默认为MQTT", example = "2") + @ExcelProperty("设备接入平台的协议类型,默认为MQTT") + private String protocolType; + + @Schema(description = "产品描述", example = "随便") + @ExcelProperty("产品描述") + private String description; + + @Schema(description = "产品状态 (0: 启用, 1: 停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("产品状态 (0: 启用, 1: 停用)") + private Integer status; + + @Schema(description = "物模型定义", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("物模型定义") + private String metadata; + + @Schema(description = "消息协议ID", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("消息协议ID") + private Long messageProtocol; + + @Schema(description = "消息协议名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("消息协议名称") + private String protocolName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductSaveReqVO.java new file mode 100644 index 000000000..305dd651a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/ProductSaveReqVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - iot 产品新增/修改 Request VO") +@Data +public class ProductSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "778") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温湿度") + @NotEmpty(message = "产品名称不能为空") + private String name; + + @Schema(description = "产品标识", example = "123456") + private String identification; + + @Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "device") + @NotEmpty(message = "设备类型不能为空") + private String deviceType; + + @Schema(description = "数据格式:1. 标准数据格式(JSON)2. 透传/自定义", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "数据格式不能为空") + private Integer dataFormat; + + @Schema(description = "设备接入平台的协议类型,默认为MQTT", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt") + @NotEmpty(message = "设备接入平台的协议类型不能为空") + private String protocolType; + + @Schema(description = "厂商名称", example = "电信") + private String manufacturerName; + + @Schema(description = "产品型号", example = "wsd-01") + private String model; + + @Schema(description = "产品描述", example = "随便") + private String description; + +// @Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") +// private Integer status; +// +// @Schema(description = "物模型定义", requiredMode = Schema.RequiredMode.REQUIRED) +// private String metadata; +// +// @Schema(description = "消息协议ID", requiredMode = Schema.RequiredMode.REQUIRED) +// private Long messageProtocol; +// +// @Schema(description = "消息协议名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") +// private String protocolName; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/ProductDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/ProductDO.java new file mode 100644 index 000000000..c7c775b11 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/ProductDO.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.product; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * iot 产品 DO + * + * @author 芋道源码 + */ +@TableName("iot_product") +@KeySequence("iot_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 产品名称 + */ + private String name; + /** + * 产品标识 + */ + private String identification; + /** + * 设备类型:device、gatway、gatway_sub + */ + private String deviceType; + /** + * 厂商名称 + */ + private String manufacturerName; + /** + * 产品型号 + */ + private String model; + /** + * 数据格式:1. 标准数据格式(JSON)2. 透传/自定义,脚本解析 + */ + private Integer dataFormat; + /** + * 设备接入平台的协议类型,默认为MQTT + */ + private String protocolType; + /** + * 产品描述 + */ + private String description; + /** + * 产品状态 (0: 启用, 1: 停用) + */ + private Integer status; + /** + * 物模型定义 + */ + private String metadata; + /** + * 消息协议ID + */ + private Long messageProtocol; + /** + * 消息协议名称 + */ + private String protocolName; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/ProductMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/ProductMapper.java new file mode 100644 index 000000000..525ae5335 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/ProductMapper.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.product; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.ProductDO; +import jakarta.validation.constraints.NotEmpty; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.*; + +/** + * iot 产品 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProductMapper extends BaseMapperX { + + default PageResult selectPage(ProductPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProductDO::getName, reqVO.getName()) + .eqIfPresent(ProductDO::getIdentification, reqVO.getIdentification()) + .eqIfPresent(ProductDO::getDeviceType, reqVO.getDeviceType()) + .likeIfPresent(ProductDO::getManufacturerName, reqVO.getManufacturerName()) + .eqIfPresent(ProductDO::getModel, reqVO.getModel()) + .eqIfPresent(ProductDO::getDataFormat, reqVO.getDataFormat()) + .eqIfPresent(ProductDO::getProtocolType, reqVO.getProtocolType()) + .eqIfPresent(ProductDO::getDescription, reqVO.getDescription()) + .eqIfPresent(ProductDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ProductDO::getMetadata, reqVO.getMetadata()) + .eqIfPresent(ProductDO::getMessageProtocol, reqVO.getMessageProtocol()) + .likeIfPresent(ProductDO::getProtocolName, reqVO.getProtocolName()) + .betweenIfPresent(ProductDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProductDO::getId)); + } + + default ProductDO selectByIdentification(String identification){ + return selectOne(new LambdaQueryWrapperX().eq(ProductDO::getIdentification, identification)); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductService.java new file mode 100644 index 000000000..5895e1677 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductService.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import java.util.*; +import jakarta.validation.*; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.ProductDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; + +/** + * iot 产品 Service 接口 + * + * @author 芋道源码 + */ +public interface ProductService { + + /** + * 创建iot 产品 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProduct(@Valid ProductSaveReqVO createReqVO); + + /** + * 更新iot 产品 + * + * @param updateReqVO 更新信息 + */ + void updateProduct(@Valid ProductSaveReqVO updateReqVO); + + /** + * 删除iot 产品 + * + * @param id 编号 + */ + void deleteProduct(Long id); + + /** + * 获得iot 产品 + * + * @param id 编号 + * @return iot 产品 + */ + ProductDO getProduct(Long id); + + /** + * 获得iot 产品分页 + * + * @param pageReqVO 分页查询 + * @return iot 产品分页 + */ + PageResult getProductPage(ProductPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImpl.java new file mode 100644 index 000000000..ea9130fc4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImpl.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.ProductPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.ProductSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.ProductDO; +import cn.iocoder.yudao.module.iot.dal.mysql.product.ProductMapper; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotEmpty; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_IDENTIFICATION_EXISTS; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS; + +/** + * iot 产品 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductServiceImpl implements ProductService { + + @Resource + private ProductMapper productMapper; + + @Override + public Long createProduct(ProductSaveReqVO createReqVO) { + // 不传自动生成产品标识 + createIdentification(createReqVO); + // 校验产品标识是否重复 + validateProductIdentification(createReqVO.getIdentification()); + // 插入 + ProductDO product = BeanUtils.toBean(createReqVO, ProductDO.class); + productMapper.insert(product); + // 返回 + return product.getId(); + } + + private void validateProductIdentification(@NotEmpty(message = "产品标识不能为空") String identification) { + if (productMapper.selectByIdentification(identification) != null) { + throw exception(PRODUCT_IDENTIFICATION_EXISTS); + } + } + + private void createIdentification(ProductSaveReqVO createReqVO) { + if (StrUtil.isNotBlank(createReqVO.getIdentification())) { + return; + } + // 生成 19 位数字 + createReqVO.setIdentification(String.valueOf(IdUtil.getSnowflake(1, 1).nextId())); + } + + @Override + public void updateProduct(ProductSaveReqVO updateReqVO) { + // 校验存在 + validateProductExists(updateReqVO.getId()); + // 更新 + ProductDO updateObj = BeanUtils.toBean(updateReqVO, ProductDO.class); + productMapper.updateById(updateObj); + } + + @Override + public void deleteProduct(Long id) { + // 校验存在 + validateProductExists(id); + // 删除 + productMapper.deleteById(id); + } + + private void validateProductExists(Long id) { + if (productMapper.selectById(id) == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + } + + @Override + public ProductDO getProduct(Long id) { + return productMapper.selectById(id); + } + + @Override + public PageResult getProductPage(ProductPageReqVO pageReqVO) { + return productMapper.selectPage(pageReqVO); + } + + public static void main(String[] args) { + System.out.println(String.valueOf(IdUtil.getSnowflake(1, 1).nextId())); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/product/ProductMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/product/ProductMapper.xml new file mode 100644 index 000000000..69352f8ea --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/product/ProductMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImplTest.java new file mode 100644 index 000000000..d49794b45 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/ProductServiceImplTest.java @@ -0,0 +1,178 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; + +import jakarta.annotation.Resource; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; + +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.ProductDO; +import cn.iocoder.yudao.module.iot.dal.mysql.product.ProductMapper; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Import; +import java.util.*; +import java.time.LocalDateTime; + +import static cn.hutool.core.util.RandomUtil.*; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link ProductServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(ProductServiceImpl.class) +public class ProductServiceImplTest extends BaseDbUnitTest { + + @Resource + private ProductServiceImpl productService; + + @Resource + private ProductMapper productMapper; + + @Test + public void testCreateProduct_success() { + // 准备参数 + ProductSaveReqVO createReqVO = randomPojo(ProductSaveReqVO.class).setId(null); + + // 调用 + Long productId = productService.createProduct(createReqVO); + // 断言 + assertNotNull(productId); + // 校验记录的属性是否正确 + ProductDO product = productMapper.selectById(productId); + assertPojoEquals(createReqVO, product, "id"); + } + + @Test + public void testUpdateProduct_success() { + // mock 数据 + ProductDO dbProduct = randomPojo(ProductDO.class); + productMapper.insert(dbProduct);// @Sql: 先插入出一条存在的数据 + // 准备参数 + ProductSaveReqVO updateReqVO = randomPojo(ProductSaveReqVO.class, o -> { + o.setId(dbProduct.getId()); // 设置更新的 ID + }); + + // 调用 + productService.updateProduct(updateReqVO); + // 校验是否更新正确 + ProductDO product = productMapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, product); + } + + @Test + public void testUpdateProduct_notExists() { + // 准备参数 + ProductSaveReqVO updateReqVO = randomPojo(ProductSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> productService.updateProduct(updateReqVO), PRODUCT_NOT_EXISTS); + } + + @Test + public void testDeleteProduct_success() { + // mock 数据 + ProductDO dbProduct = randomPojo(ProductDO.class); + productMapper.insert(dbProduct);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbProduct.getId(); + + // 调用 + productService.deleteProduct(id); + // 校验数据不存在了 + assertNull(productMapper.selectById(id)); + } + + @Test + public void testDeleteProduct_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> productService.deleteProduct(id), PRODUCT_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetProductPage() { + // mock 数据 + ProductDO dbProduct = randomPojo(ProductDO.class, o -> { // 等会查询到 + o.setName(null); + o.setIdentification(null); + o.setDeviceType(null); + o.setManufacturerName(null); + o.setModel(null); + o.setDataFormat(null); + o.setProtocolType(null); + o.setDescription(null); + o.setStatus(null); + o.setMetadata(null); + o.setMessageProtocol(null); + o.setProtocolName(null); + o.setCreateTime(null); + }); + productMapper.insert(dbProduct); + // 测试 name 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setName(null))); + // 测试 identification 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setIdentification(null))); + // 测试 deviceType 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setDeviceType(null))); + // 测试 manufacturerName 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setManufacturerName(null))); + // 测试 model 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setModel(null))); + // 测试 dataFormat 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setDataFormat(null))); + // 测试 protocolType 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setProtocolType(null))); + // 测试 description 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setDescription(null))); + // 测试 status 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setStatus(null))); + // 测试 metadata 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setMetadata(null))); + // 测试 messageProtocol 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setMessageProtocol(null))); + // 测试 protocolName 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setProtocolName(null))); + // 测试 createTime 不匹配 + productMapper.insert(cloneIgnoreId(dbProduct, o -> o.setCreateTime(null))); + // 准备参数 + ProductPageReqVO reqVO = new ProductPageReqVO(); + reqVO.setName(null); + reqVO.setIdentification(null); + reqVO.setDeviceType(null); + reqVO.setManufacturerName(null); + reqVO.setModel(null); + reqVO.setDataFormat(null); + reqVO.setProtocolType(null); + reqVO.setDescription(null); + reqVO.setStatus(null); + reqVO.setMetadata(null); + reqVO.setMessageProtocol(null); + reqVO.setProtocolName(null); + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + + // 调用 + PageResult pageResult = productService.getProductPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbProduct, pageResult.getList().get(0)); + } + +} \ No newline at end of file diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 6ed31f148..3fe8ef48d 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -46,11 +46,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-bpm-biz + ${revision} + @@ -88,11 +88,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-crm-biz + ${revision} + diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 20110708c..27588d045 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -174,6 +174,7 @@ logging: cn.iocoder.yudao.module.statistics.dal.mysql: debug cn.iocoder.yudao.module.crm.dal.mysql: debug cn.iocoder.yudao.module.erp.dal.mysql: debug + cn.iocoder.yudao.module.iot.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 debug: false