新增:增加 IOT 物模型

This commit is contained in:
安浩浩 2024-09-11 23:00:33 +08:00
parent 1aef8515e2
commit 18e789d4fb
13 changed files with 588 additions and 4 deletions

View File

@ -9,8 +9,11 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
// ========== 产品相关 1-050-001-000 ============ // ========== IoT 产品相关 1-050-001-000 ============
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在"); ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在"); ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除"); ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
// ========== IoT 产品物模型 1-050-002-000 ============
ErrorCode THINK_MODEL_FUNCTION_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
} }

View File

@ -0,0 +1,96 @@
### 请求 /iot/think-model-function/create 接口 => 成功
POST {{baseUrl}}/iot/think-model-function/create
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
{
"productKey": "123456",
"properties": [
{
"identifier": "CurrentTemperature",
"name": "当前温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": "-40",
"max": "120",
"unit": "°C",
"unitName": "摄氏度",
"step": "0.1"
}
}
},
{
"identifier": "CurrentHumidity",
"name": "当前湿度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": "0",
"max": "100",
"unit": "%",
"unitName": "百分比",
"step": "0.1"
}
}
}
],
"services": "{}",
"events": "{}"
}
### 请求 /iot/think-model-function/update 接口 => 成功
PUT {{baseUrl}}/iot/think-model-function/update
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
{
"productKey": "123456",
"properties": [
{
"identifier": "CurrentTemperature",
"name": "当前温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": "-40",
"max": "130",
"unit": "°C",
"unitName": "摄氏度",
"step": "0.1"
}
}
},
{
"identifier": "CurrentHumidity",
"name": "当前湿度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": "0",
"max": "100",
"unit": "%",
"unitName": "百分比",
"step": "0.1"
}
}
}
],
"services": "{}",
"events": "{}"
}
### 请求 /iot/think-model-function/get 接口 => 成功
GET {{baseUrl}}/iot/think-model-function/get?productKey=123456
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.*;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
@Tag(name = "管理后台 - IoT 产品物模型")
@RestController
@RequestMapping("/iot/think-model-function")
@Validated
public class IotThinkModelFunctionController {
@Resource
private IotThinkModelFunctionService thinkModelFunctionService;
@PostMapping("/create")
@Operation(summary = "创建IoT 产品物模型")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:create')")
public CommonResult<Long> createThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO createReqVO) {
return success(thinkModelFunctionService.createThinkModelFunction(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新IoT 产品物模型")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:update')")
public CommonResult<Boolean> updateThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO updateReqVO) {
thinkModelFunctionService.updateThinkModelFunctionByProductKey(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除IoT 产品物模型")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:think-model-function:delete')")
public CommonResult<Boolean> deleteThinkModelFunction(@RequestParam("id") Long id) {
thinkModelFunctionService.deleteThinkModelFunction(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得IoT 产品物模型")
@Parameter(name = "productKey", description = "产品Key", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:query')")
public CommonResult<IotThinkModelFunctionRespVO> getThinkModelFunctionByProductKey(@RequestParam("productKey") String productKey) {
IotThinkModelFunctionDO thinkModelFunction = thinkModelFunctionService.getThinkModelFunctionByProductKey(productKey);
return success(BeanUtils.toBean(thinkModelFunction, IotThinkModelFunctionRespVO.class));
}
}

View File

@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - IoT 产品物模型属性")
@Data
public class IotThingModelProperty {
@Schema(description = "属性标识符")
private String identifier;
@Schema(description = "属性名称")
private String name;
@Schema(description = "访问模式 (r/rw)")
private String accessMode;
@Schema(description = "是否必需")
private boolean required;
@Schema(description = "数据类型")
private DataType dataType;
@Schema(description = "数据类型")
@Data
public static class DataType {
@Schema(description = "数据类型float, double, struct, enum等")
private String type;
@Schema(description = "单一类型的规格适用于float, double等")
private Specs specs;
@Schema(description = "结构体字段适用于struct类型")
private List<StructField> structSpecs;
@Schema(description = "规格")
@Data
public static class Specs {
@Schema(description = "最小值")
private String min;
@Schema(description = "最大值")
private String max;
@Schema(description = "单位符号")
private String unit;
@Schema(description = "单位名称")
private String unitName;
@Schema(description = "步进值")
private String step;
}
@Schema(description = "结构体字段")
@Data
public static class StructField {
@Schema(description = "字段标识符")
private String identifier;
@Schema(description = "字段名称")
private String name;
@Schema(description = "字段的数据类型")
private DataType dataType;
}
}
@Schema(description = "枚举规格")
@Data
public static class EnumSpecs {
@Schema(description = "枚举值")
private int value;
@Schema(description = "枚举名称")
private String name;
public EnumSpecs(int value, String name) {
this.value = value;
this.name = name;
}
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - IoT 产品物模型 Response VO")
@Data
@ExcelIgnoreUnannotated
public class IotThinkModelFunctionRespVO {
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
@ExcelProperty("产品ID")
private Long id;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("产品标识")
private String productKey;
@Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("属性列表")
private String properties;
@Schema(description = "服务列表")
@ExcelProperty("服务列表")
private String services;
@Schema(description = "事件列表")
@ExcelProperty("事件列表")
private String events;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
@Schema(description = "管理后台 - IoT 产品物模型新增/修改 Request VO")
@Data
public class IotThinkModelFunctionSaveReqVO {
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "产品标识不能为空")
private String productKey;
@Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "属性列表不能为空")
private List<IotThingModelProperty> properties;
@Schema(description = "服务列表")
private String services;
@Schema(description = "事件列表")
private String events;
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction;
import lombok.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* IoT 产品物模型 DO
*
* @author 芋道源码
*/
@TableName("iot_think_model_function")
@KeySequence("iot_think_model_function_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotThinkModelFunctionDO extends BaseDO {
/**
* 产品ID
*/
@TableId
private Long id;
/**
* 产品标识
*/
private String productKey;
/**
* 属性列表
*/
private String properties;
/**
* 服务列表
*/
private String services;
/**
* 事件列表
*/
private String events;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import org.apache.ibatis.annotations.Mapper;
/**
* IoT 产品物模型 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotThinkModelFunctionMapper extends BaseMapperX<IotThinkModelFunctionDO> {
default IotThinkModelFunctionDO selectByProductKey(String productKey) {
return selectOne(new LambdaQueryWrapperX<IotThinkModelFunctionDO>().eq(IotThinkModelFunctionDO::getProductKey, productKey));
}
default int updateByProductKey(IotThinkModelFunctionDO thinkModelFunction) {
return update(thinkModelFunction, new LambdaQueryWrapperX<IotThinkModelFunctionDO>()
.eq(IotThinkModelFunctionDO::getProductKey, thinkModelFunction.getProductKey())
);
}
}

View File

@ -16,8 +16,7 @@ import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_STATUS_NOT_DELETE;
/** /**
* IoT 产品 Service 实现类 * IoT 产品 Service 实现类
@ -54,7 +53,7 @@ public class IotProductServiceImpl implements IotProductService {
} }
// 2. 校验唯一性 // 2. 校验唯一性
if (productMapper.selectByProductKey(productKey) != null) { if (productMapper.selectByProductKey(productKey) != null) {
throw exception(PRODUCT_NOT_EXISTS); throw exception(PRODUCT_IDENTIFICATION_EXISTS);
} }
createReqVO.setProductKey(productKey); createReqVO.setProductKey(productKey);
} }

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.iot.service.thinkmodelfunction;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import jakarta.validation.Valid;
/**
* IoT 产品物模型 Service 接口
*
* @author 芋道源码
*/
public interface IotThinkModelFunctionService {
/**
* 创建IoT 产品物模型
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createThinkModelFunction(@Valid IotThinkModelFunctionSaveReqVO createReqVO);
/**
* 删除IoT 产品物模型
*
* @param id 编号
*/
void deleteThinkModelFunction(Long id);
/**
* 获得IoT 产品物模型
*
* @param productKey 产品Key
* @return IoT 产品物模型
*/
IotThinkModelFunctionDO getThinkModelFunctionByProductKey(String productKey);
/**
* 更新IoT 产品物模型
*
* @param updateReqVO 更新信息
*/
void updateThinkModelFunctionByProductKey(@Valid IotThinkModelFunctionSaveReqVO updateReqVO);
}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.iot.service.thinkmodelfunction;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper;
import jakarta.annotation.Resource;
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.THINK_MODEL_FUNCTION_NOT_EXISTS;
/**
* IoT 产品物模型 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionService {
@Resource
private IotThinkModelFunctionMapper thinkModelFunctionMapper;
@Override
public Long createThinkModelFunction(IotThinkModelFunctionSaveReqVO createReqVO) {
// 插入
IotThinkModelFunctionDO thinkModelFunction = BeanUtils.toBean(createReqVO, IotThinkModelFunctionDO.class);
// properties 字段需要转换成 JSON
thinkModelFunction.setProperties(JSONUtil.toJsonStr(createReqVO.getProperties()));
thinkModelFunctionMapper.insert(thinkModelFunction);
// 返回
return thinkModelFunction.getId();
}
@Override
public void deleteThinkModelFunction(Long id) {
// 校验存在
validateThinkModelFunctionExists(id);
// 删除
thinkModelFunctionMapper.deleteById(id);
}
private void validateThinkModelFunctionExists(Long id) {
if (thinkModelFunctionMapper.selectById(id) == null) {
throw exception(THINK_MODEL_FUNCTION_NOT_EXISTS);
}
}
private void validateThinkModelFunctionExistsByProductKey(String productKey) {
if (thinkModelFunctionMapper.selectByProductKey(productKey) == null) {
throw exception(THINK_MODEL_FUNCTION_NOT_EXISTS);
}
}
@Override
public IotThinkModelFunctionDO getThinkModelFunctionByProductKey(String productKey) {
return thinkModelFunctionMapper.selectByProductKey(productKey);
}
@Override
public void updateThinkModelFunctionByProductKey(IotThinkModelFunctionSaveReqVO updateReqVO) {
// 校验存在
validateThinkModelFunctionExistsByProductKey(updateReqVO.getProductKey());
// 更新
IotThinkModelFunctionDO thinkModelFunction = BeanUtils.toBean(updateReqVO, IotThinkModelFunctionDO.class);
// properties 字段需要转换成 JSON
thinkModelFunction.setProperties(JSONUtil.toJsonStr(updateReqVO.getProperties()));
thinkModelFunctionMapper.updateByProductKey(thinkModelFunction);
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.iot.service.thinkmodelfunction;
import org.junit.jupiter.api.Test;
import jakarta.annotation.Resource;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper;
import org.springframework.context.annotation.Import;
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 org.junit.jupiter.api.Assertions.*;
/**
* {@link IotThinkModelFunctionServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(IotThinkModelFunctionServiceImpl.class)
public class IotThinkModelFunctionServiceImplTest extends BaseDbUnitTest {
@Resource
private IotThinkModelFunctionServiceImpl thinkModelFunctionService;
@Resource
private IotThinkModelFunctionMapper thinkModelFunctionMapper;
@Test
public void testCreateThinkModelFunction_success() {
// 准备参数
IotThinkModelFunctionSaveReqVO createReqVO = randomPojo(IotThinkModelFunctionSaveReqVO.class);
// 调用
Long thinkModelFunctionId = thinkModelFunctionService.createThinkModelFunction(createReqVO);
// 断言
assertNotNull(thinkModelFunctionId);
// 校验记录的属性是否正确
IotThinkModelFunctionDO thinkModelFunction = thinkModelFunctionMapper.selectById(thinkModelFunctionId);
assertPojoEquals(createReqVO, thinkModelFunction, "id");
}
@Test
public void testDeleteThinkModelFunction_success() {
// mock 数据
IotThinkModelFunctionDO dbThinkModelFunction = randomPojo(IotThinkModelFunctionDO.class);
thinkModelFunctionMapper.insert(dbThinkModelFunction);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbThinkModelFunction.getId();
// 调用
thinkModelFunctionService.deleteThinkModelFunction(id);
// 校验数据不存在了
assertNull(thinkModelFunctionMapper.selectById(id));
}
@Test
public void testDeleteThinkModelFunction_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> thinkModelFunctionService.deleteThinkModelFunction(id), THINK_MODEL_FUNCTION_NOT_EXISTS);
}
}