Commit 6b9cca0b79612fc3ca26f04b73b23d9f973dd873
1 parent
bd18e730
【代码评审】IOT:设备的 review
Showing
14 changed files
with
143 additions
and
438 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStatusEnum.java
| 1 | package cn.iocoder.yudao.module.iot.enums.device; | 1 | package cn.iocoder.yudao.module.iot.enums.device; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | ||
| 3 | import lombok.Getter; | 4 | import lombok.Getter; |
| 4 | 5 | ||
| 6 | +import java.util.Arrays; | ||
| 7 | + | ||
| 5 | /** | 8 | /** |
| 6 | * IoT 设备状态枚举 | 9 | * IoT 设备状态枚举 |
| 7 | - * 设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用 | 10 | + * |
| 11 | + * @author haohao | ||
| 8 | */ | 12 | */ |
| 9 | @Getter | 13 | @Getter |
| 10 | -public enum IotDeviceStatusEnum { | 14 | +public enum IotDeviceStatusEnum implements IntArrayValuable { |
| 11 | 15 | ||
| 12 | INACTIVE(0, "未激活"), | 16 | INACTIVE(0, "未激活"), |
| 13 | ONLINE(1, "在线"), | 17 | ONLINE(1, "在线"), |
| 14 | OFFLINE(2, "离线"), | 18 | OFFLINE(2, "离线"), |
| 15 | DISABLED(3, "已禁用"); | 19 | DISABLED(3, "已禁用"); |
| 16 | 20 | ||
| 21 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotDeviceStatusEnum::getStatus).toArray(); | ||
| 22 | + | ||
| 17 | /** | 23 | /** |
| 18 | * 状态 | 24 | * 状态 |
| 19 | */ | 25 | */ |
| @@ -40,4 +46,10 @@ public enum IotDeviceStatusEnum { | @@ -40,4 +46,10 @@ public enum IotDeviceStatusEnum { | ||
| 40 | public static boolean isValidStatus(Integer status) { | 46 | public static boolean isValidStatus(Integer status) { |
| 41 | return fromStatus(status) != null; | 47 | return fromStatus(status) != null; |
| 42 | } | 48 | } |
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public int[] array() { | ||
| 52 | + return ARRAYS; | ||
| 53 | + } | ||
| 54 | + | ||
| 43 | } | 55 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
| @@ -37,14 +37,14 @@ public class IotDeviceController { | @@ -37,14 +37,14 @@ public class IotDeviceController { | ||
| 37 | private IotDeviceService deviceService; | 37 | private IotDeviceService deviceService; |
| 38 | 38 | ||
| 39 | @PostMapping("/create") | 39 | @PostMapping("/create") |
| 40 | - @Operation(summary = "创建IoT 设备") | 40 | + @Operation(summary = "创建设备") |
| 41 | @PreAuthorize("@ss.hasPermission('iot:device:create')") | 41 | @PreAuthorize("@ss.hasPermission('iot:device:create')") |
| 42 | public CommonResult<Long> createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) { | 42 | public CommonResult<Long> createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) { |
| 43 | return success(deviceService.createDevice(createReqVO)); | 43 | return success(deviceService.createDevice(createReqVO)); |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | @PutMapping("/update-status") | 46 | @PutMapping("/update-status") |
| 47 | - @Operation(summary = "更新IoT 设备状态") | 47 | + @Operation(summary = "更新设备状态") |
| 48 | @Parameter(name = "id", description = "编号", required = true) | 48 | @Parameter(name = "id", description = "编号", required = true) |
| 49 | @Parameter(name = "status", description = "状态", required = true, example = "1") | 49 | @Parameter(name = "status", description = "状态", required = true, example = "1") |
| 50 | @PreAuthorize("@ss.hasPermission('iot:device:update')") | 50 | @PreAuthorize("@ss.hasPermission('iot:device:update')") |
| @@ -55,7 +55,7 @@ public class IotDeviceController { | @@ -55,7 +55,7 @@ public class IotDeviceController { | ||
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | @PutMapping("/update") | 57 | @PutMapping("/update") |
| 58 | - @Operation(summary = "更新IoT 设备") | 58 | + @Operation(summary = "更新设备") |
| 59 | @PreAuthorize("@ss.hasPermission('iot:device:update')") | 59 | @PreAuthorize("@ss.hasPermission('iot:device:update')") |
| 60 | public CommonResult<Boolean> updateDevice(@Valid @RequestBody IotDeviceSaveReqVO updateReqVO) { | 60 | public CommonResult<Boolean> updateDevice(@Valid @RequestBody IotDeviceSaveReqVO updateReqVO) { |
| 61 | deviceService.updateDevice(updateReqVO); | 61 | deviceService.updateDevice(updateReqVO); |
| @@ -63,7 +63,7 @@ public class IotDeviceController { | @@ -63,7 +63,7 @@ public class IotDeviceController { | ||
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | @DeleteMapping("/delete") | 65 | @DeleteMapping("/delete") |
| 66 | - @Operation(summary = "删除IoT 设备") | 66 | + @Operation(summary = "删除设备") |
| 67 | @Parameter(name = "id", description = "编号", required = true) | 67 | @Parameter(name = "id", description = "编号", required = true) |
| 68 | @PreAuthorize("@ss.hasPermission('iot:device:delete')") | 68 | @PreAuthorize("@ss.hasPermission('iot:device:delete')") |
| 69 | public CommonResult<Boolean> deleteDevice(@RequestParam("id") Long id) { | 69 | public CommonResult<Boolean> deleteDevice(@RequestParam("id") Long id) { |
| @@ -72,7 +72,7 @@ public class IotDeviceController { | @@ -72,7 +72,7 @@ public class IotDeviceController { | ||
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | @GetMapping("/get") | 74 | @GetMapping("/get") |
| 75 | - @Operation(summary = "获得IoT 设备") | 75 | + @Operation(summary = "获得设备") |
| 76 | @Parameter(name = "id", description = "编号", required = true, example = "1024") | 76 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 77 | @PreAuthorize("@ss.hasPermission('iot:device:query')") | 77 | @PreAuthorize("@ss.hasPermission('iot:device:query')") |
| 78 | public CommonResult<IotDeviceRespVO> getDevice(@RequestParam("id") Long id) { | 78 | public CommonResult<IotDeviceRespVO> getDevice(@RequestParam("id") Long id) { |
| @@ -81,7 +81,7 @@ public class IotDeviceController { | @@ -81,7 +81,7 @@ public class IotDeviceController { | ||
| 81 | } | 81 | } |
| 82 | 82 | ||
| 83 | @GetMapping("/page") | 83 | @GetMapping("/page") |
| 84 | - @Operation(summary = "获得IoT 设备分页") | 84 | + @Operation(summary = "获得设备分页") |
| 85 | @PreAuthorize("@ss.hasPermission('iot:device:query')") | 85 | @PreAuthorize("@ss.hasPermission('iot:device:query')") |
| 86 | public CommonResult<PageResult<IotDeviceRespVO>> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) { | 86 | public CommonResult<PageResult<IotDeviceRespVO>> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) { |
| 87 | PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO); | 87 | PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO); |
| @@ -89,7 +89,7 @@ public class IotDeviceController { | @@ -89,7 +89,7 @@ public class IotDeviceController { | ||
| 89 | } | 89 | } |
| 90 | 90 | ||
| 91 | @GetMapping("/export-excel") | 91 | @GetMapping("/export-excel") |
| 92 | - @Operation(summary = "导出IoT 设备 Excel") | 92 | + @Operation(summary = "导出设备 Excel") |
| 93 | @PreAuthorize("@ss.hasPermission('iot:device:export')") | 93 | @PreAuthorize("@ss.hasPermission('iot:device:export')") |
| 94 | @ApiAccessLog(operateType = EXPORT) | 94 | @ApiAccessLog(operateType = EXPORT) |
| 95 | public void exportDeviceExcel(@Valid IotDevicePageReqVO pageReqVO, | 95 | public void exportDeviceExcel(@Valid IotDevicePageReqVO pageReqVO, |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDevicePageReqVO.java
| 1 | package cn.iocoder.yudao.module.iot.controller.admin.device.vo; | 1 | package cn.iocoder.yudao.module.iot.controller.admin.device.vo; |
| 2 | 2 | ||
| 3 | -import lombok.*; | ||
| 4 | -import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageParam; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 6 | -import java.math.BigDecimal; | 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 5 | +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; | ||
| 6 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.EqualsAndHashCode; | ||
| 9 | +import lombok.ToString; | ||
| 7 | import org.springframework.format.annotation.DateTimeFormat; | 10 | import org.springframework.format.annotation.DateTimeFormat; |
| 11 | + | ||
| 12 | +import java.math.BigDecimal; | ||
| 8 | import java.time.LocalDateTime; | 13 | import java.time.LocalDateTime; |
| 9 | 14 | ||
| 10 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | 15 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| @@ -15,28 +20,32 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ | @@ -15,28 +20,32 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ | ||
| 15 | @ToString(callSuper = true) | 20 | @ToString(callSuper = true) |
| 16 | public class IotDevicePageReqVO extends PageParam { | 21 | public class IotDevicePageReqVO extends PageParam { |
| 17 | 22 | ||
| 18 | - @Schema(description = "设备唯一标识符,全局唯一,用于识别设备") | 23 | + // TODO @芋艿:需要去掉一些多余的字段; |
| 24 | + | ||
| 25 | + @Schema(description = "设备唯一标识符", example = "24602") | ||
| 19 | private String deviceKey; | 26 | private String deviceKey; |
| 20 | 27 | ||
| 21 | - @Schema(description = "设备名称,在产品内唯一,用于标识设备", example = "王五") | 28 | + @Schema(description = "设备名称", example = "王五") |
| 22 | private String deviceName; | 29 | private String deviceName; |
| 23 | 30 | ||
| 24 | - @Schema(description = "产品 ID,关联 iot_product 表的 id", example = "26202") | 31 | + @Schema(description = "产品编号", example = "26202") |
| 25 | private Long productId; | 32 | private Long productId; |
| 26 | 33 | ||
| 27 | - @Schema(description = "产品 Key,关联 iot_product 表的 product_key") | 34 | + @Schema(description = "产品标识") |
| 28 | private String productKey; | 35 | private String productKey; |
| 29 | 36 | ||
| 30 | - @Schema(description = "设备类型:0 - 直连设备,1 - 网关子设备,2 - 网关设备", example = "1") | 37 | + @Schema(description = "设备类型", example = "1") |
| 38 | + // TODO @haohao:需要有个设备类型的枚举 | ||
| 31 | private Integer deviceType; | 39 | private Integer deviceType; |
| 32 | 40 | ||
| 33 | - @Schema(description = "设备备注名称,供用户自定义备注", example = "张三") | 41 | + @Schema(description = "备注名称", example = "张三") |
| 34 | private String nickname; | 42 | private String nickname; |
| 35 | 43 | ||
| 36 | - @Schema(description = "网关设备 ID,子设备需要关联的网关设备 ID", example = "16380") | 44 | + @Schema(description = "网关设备 ID", example = "16380") |
| 37 | private Long gatewayId; | 45 | private Long gatewayId; |
| 38 | 46 | ||
| 39 | - @Schema(description = "设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用", example = "1") | 47 | + @Schema(description = "设备状态", example = "1") |
| 48 | + @InEnum(IotDeviceStatusEnum.class) | ||
| 40 | private Integer status; | 49 | private Integer status; |
| 41 | 50 | ||
| 42 | @Schema(description = "设备状态最后更新时间") | 51 | @Schema(description = "设备状态最后更新时间") |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceRespVO.java
| @@ -13,7 +13,7 @@ import java.time.LocalDateTime; | @@ -13,7 +13,7 @@ import java.time.LocalDateTime; | ||
| 13 | @ExcelIgnoreUnannotated | 13 | @ExcelIgnoreUnannotated |
| 14 | public class IotDeviceRespVO { | 14 | public class IotDeviceRespVO { |
| 15 | 15 | ||
| 16 | - @Schema(description = "设备 ID,主键,自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "177") | 16 | + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177") |
| 17 | private Long id; | 17 | private Long id; |
| 18 | 18 | ||
| 19 | @Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED) | 19 | @Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED) |
| @@ -24,11 +24,11 @@ public class IotDeviceRespVO { | @@ -24,11 +24,11 @@ public class IotDeviceRespVO { | ||
| 24 | @ExcelProperty("设备名称备") | 24 | @ExcelProperty("设备名称备") |
| 25 | private String deviceName; | 25 | private String deviceName; |
| 26 | 26 | ||
| 27 | - @Schema(description = "产品 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") | 27 | + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") |
| 28 | @ExcelProperty("产品 ID") | 28 | @ExcelProperty("产品 ID") |
| 29 | private Long productId; | 29 | private Long productId; |
| 30 | 30 | ||
| 31 | - @Schema(description = "产品 Key", requiredMode = Schema.RequiredMode.REQUIRED) | 31 | + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) |
| 32 | @ExcelProperty("产品 Key") | 32 | @ExcelProperty("产品 Key") |
| 33 | private String productKey; | 33 | private String productKey; |
| 34 | 34 | ||
| @@ -72,7 +72,7 @@ public class IotDeviceRespVO { | @@ -72,7 +72,7 @@ public class IotDeviceRespVO { | ||
| 72 | @ExcelProperty("设备的固件版本") | 72 | @ExcelProperty("设备的固件版本") |
| 73 | private String firmwareVersion; | 73 | private String firmwareVersion; |
| 74 | 74 | ||
| 75 | - @Schema(description = "设备密钥,用于设备认证,需安全存储") | 75 | + @Schema(description = "设备密钥,用于设备认证") |
| 76 | @ExcelProperty("设备密钥") | 76 | @ExcelProperty("设备密钥") |
| 77 | private String deviceSecret; | 77 | private String deviceSecret; |
| 78 | 78 | ||
| @@ -92,16 +92,17 @@ public class IotDeviceRespVO { | @@ -92,16 +92,17 @@ public class IotDeviceRespVO { | ||
| 92 | @ExcelProperty("认证类型(如一机一密、动态注册)") | 92 | @ExcelProperty("认证类型(如一机一密、动态注册)") |
| 93 | private String authType; | 93 | private String authType; |
| 94 | 94 | ||
| 95 | - @Schema(description = "设备位置的纬度,范围 -90.000000 ~ 90.000000") | ||
| 96 | - @ExcelProperty("设备位置的纬度,范围 -90.000000 ~ 90.000000") | 95 | + // TODO @haohao:经纬度:可能 double 就够啦 |
| 96 | + @Schema(description = "设备位置的纬度,范围") | ||
| 97 | + @ExcelProperty("设备位置的纬度") | ||
| 97 | private BigDecimal latitude; | 98 | private BigDecimal latitude; |
| 98 | 99 | ||
| 99 | - @Schema(description = "设备位置的经度,范围 -180.000000 ~ 180.000000") | ||
| 100 | - @ExcelProperty("设备位置的经度,范围 -180.000000 ~ 180.000000") | 100 | + @Schema(description = "设备位置的经度") |
| 101 | + @ExcelProperty("设备位置的经度") | ||
| 101 | private BigDecimal longitude; | 102 | private BigDecimal longitude; |
| 102 | 103 | ||
| 103 | - @Schema(description = "地区编码,符合国家地区编码标准,关联地区表", example = "16995") | ||
| 104 | - @ExcelProperty("地区编码,符合国家地区编码标准,关联地区表") | 104 | + @Schema(description = "地区编码", example = "16995") |
| 105 | + @ExcelProperty("地区编码") | ||
| 105 | private Integer areaId; | 106 | private Integer areaId; |
| 106 | 107 | ||
| 107 | @Schema(description = "设备详细地址") | 108 | @Schema(description = "设备详细地址") |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceSaveReqVO.java
| @@ -7,16 +7,16 @@ import lombok.Data; | @@ -7,16 +7,16 @@ import lombok.Data; | ||
| 7 | @Data | 7 | @Data |
| 8 | public class IotDeviceSaveReqVO { | 8 | public class IotDeviceSaveReqVO { |
| 9 | 9 | ||
| 10 | - @Schema(description = "设备 ID,主键,自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "177") | 10 | + @Schema(description = "设备编号", example = "177") |
| 11 | private Long id; | 11 | private Long id; |
| 12 | 12 | ||
| 13 | - @Schema(description = "设备名称,在产品内唯一,用于标识设备", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") | 13 | + @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") |
| 14 | private String deviceName; | 14 | private String deviceName; |
| 15 | 15 | ||
| 16 | - @Schema(description = "设备备注名称,供用户自定义备注", example = "张三") | 16 | + @Schema(description = "备注名称", example = "张三") |
| 17 | private String nickname; | 17 | private String nickname; |
| 18 | 18 | ||
| 19 | - @Schema(description = "产品 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") | 19 | + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") |
| 20 | private Long productId; | 20 | private Long productId; |
| 21 | 21 | ||
| 22 | } | 22 | } |
| 23 | \ No newline at end of file | 23 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 5 | +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; | ||
| 5 | import com.baomidou.mybatisplus.annotation.KeySequence; | 6 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 6 | import com.baomidou.mybatisplus.annotation.TableId; | 7 | import com.baomidou.mybatisplus.annotation.TableId; |
| 7 | import com.baomidou.mybatisplus.annotation.TableName; | 8 | import com.baomidou.mybatisplus.annotation.TableName; |
| @@ -13,7 +14,7 @@ import java.time.LocalDateTime; | @@ -13,7 +14,7 @@ import java.time.LocalDateTime; | ||
| 13 | /** | 14 | /** |
| 14 | * IoT 设备 DO | 15 | * IoT 设备 DO |
| 15 | * | 16 | * |
| 16 | - * @author 芋道源码 | 17 | + * @author haohao |
| 17 | */ | 18 | */ |
| 18 | @TableName("iot_device") | 19 | @TableName("iot_device") |
| 19 | @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 | 20 | @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| @@ -39,33 +40,48 @@ public class IotDeviceDO extends BaseDO { | @@ -39,33 +40,48 @@ public class IotDeviceDO extends BaseDO { | ||
| 39 | */ | 40 | */ |
| 40 | private String deviceName; | 41 | private String deviceName; |
| 41 | /** | 42 | /** |
| 42 | - * 产品 ID,关联 iot_product 表的 id | 43 | + * 设备备注名称 |
| 44 | + */ | ||
| 45 | + private String nickname; | ||
| 46 | + /** | ||
| 47 | + * 设备序列号 | ||
| 48 | + */ | ||
| 49 | + private String serialNumber; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 产品编号 | ||
| 53 | + * | ||
| 43 | * 关联 {@link IotProductDO#getId()} | 54 | * 关联 {@link IotProductDO#getId()} |
| 44 | */ | 55 | */ |
| 45 | private Long productId; | 56 | private Long productId; |
| 46 | /** | 57 | /** |
| 47 | - * 产品 Key,关联 iot_product 表的 product_key | ||
| 48 | - * 关联 {@link IotProductDO#getProductKey()} | 58 | + * 产品标识 |
| 59 | + * | ||
| 60 | + * 冗余 {@link IotProductDO#getProductKey()} | ||
| 49 | */ | 61 | */ |
| 50 | private String productKey; | 62 | private String productKey; |
| 51 | /** | 63 | /** |
| 52 | - * 设备类型:0 - 直连设备,1 - 网关子设备,2 - 网关设备 | ||
| 53 | - * 关联 {@link IotProductDO#getDeviceType()} | 64 | + * 设备类型 |
| 65 | + * | ||
| 66 | + * 冗余 {@link IotProductDO#getDeviceType()} | ||
| 54 | */ | 67 | */ |
| 55 | private Integer deviceType; | 68 | private Integer deviceType; |
| 69 | + | ||
| 56 | /** | 70 | /** |
| 57 | - * 设备备注名称,供用户自定义备注 | 71 | + * 设备状态 |
| 72 | + * | ||
| 73 | + * 枚举 {@link IotDeviceStatusEnum} | ||
| 58 | */ | 74 | */ |
| 59 | - private String nickname; | 75 | + private Integer status; |
| 60 | /** | 76 | /** |
| 61 | - * 网关设备 ID,子设备需要关联的网关设备 ID | 77 | + * 网关设备编号 |
| 78 | + * | ||
| 79 | + * 子设备需要关联的网关设备 ID | ||
| 80 | + * | ||
| 81 | + * 关联 {@link IotDeviceDO#getId()} | ||
| 62 | */ | 82 | */ |
| 63 | private Long gatewayId; | 83 | private Long gatewayId; |
| 64 | - /** | ||
| 65 | - * 设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用 | ||
| 66 | - * 关联 {@link cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum} | ||
| 67 | - */ | ||
| 68 | - private Integer status; | 84 | + |
| 69 | /** | 85 | /** |
| 70 | * 设备状态最后更新时间 | 86 | * 设备状态最后更新时间 |
| 71 | */ | 87 | */ |
| @@ -82,6 +98,7 @@ public class IotDeviceDO extends BaseDO { | @@ -82,6 +98,7 @@ public class IotDeviceDO extends BaseDO { | ||
| 82 | * 设备激活时间 | 98 | * 设备激活时间 |
| 83 | */ | 99 | */ |
| 84 | private LocalDateTime activeTime; | 100 | private LocalDateTime activeTime; |
| 101 | + | ||
| 85 | /** | 102 | /** |
| 86 | * 设备的 IP 地址 | 103 | * 设备的 IP 地址 |
| 87 | */ | 104 | */ |
| @@ -90,6 +107,7 @@ public class IotDeviceDO extends BaseDO { | @@ -90,6 +107,7 @@ public class IotDeviceDO extends BaseDO { | ||
| 90 | * 设备的固件版本 | 107 | * 设备的固件版本 |
| 91 | */ | 108 | */ |
| 92 | private String firmwareVersion; | 109 | private String firmwareVersion; |
| 110 | + | ||
| 93 | /** | 111 | /** |
| 94 | * 设备密钥,用于设备认证,需安全存储 | 112 | * 设备密钥,用于设备认证,需安全存储 |
| 95 | */ | 113 | */ |
| @@ -109,26 +127,26 @@ public class IotDeviceDO extends BaseDO { | @@ -109,26 +127,26 @@ public class IotDeviceDO extends BaseDO { | ||
| 109 | /** | 127 | /** |
| 110 | * 认证类型(如一机一密、动态注册) | 128 | * 认证类型(如一机一密、动态注册) |
| 111 | */ | 129 | */ |
| 130 | + // TODO @haohao:是不是要枚举哈 | ||
| 112 | private String authType; | 131 | private String authType; |
| 132 | + | ||
| 113 | /** | 133 | /** |
| 114 | - * 设备位置的纬度,范围 -90.000000 ~ 90.000000 | 134 | + * 设备位置的纬度 |
| 115 | */ | 135 | */ |
| 116 | private BigDecimal latitude; | 136 | private BigDecimal latitude; |
| 117 | /** | 137 | /** |
| 118 | - * 设备位置的经度,范围 -180.000000 ~ 180.000000 | 138 | + * 设备位置的经度 |
| 119 | */ | 139 | */ |
| 120 | private BigDecimal longitude; | 140 | private BigDecimal longitude; |
| 121 | /** | 141 | /** |
| 122 | - * 地区编码,符合国家地区编码标准,关联地区表 | 142 | + * 地区编码 |
| 143 | + * | ||
| 144 | + * 关联 Area 的 id | ||
| 123 | */ | 145 | */ |
| 124 | private Integer areaId; | 146 | private Integer areaId; |
| 125 | /** | 147 | /** |
| 126 | * 设备详细地址 | 148 | * 设备详细地址 |
| 127 | */ | 149 | */ |
| 128 | private String address; | 150 | private String address; |
| 129 | - /** | ||
| 130 | - * 设备序列号 | ||
| 131 | - */ | ||
| 132 | - private String serialNumber; | ||
| 133 | 151 | ||
| 134 | } | 152 | } |
| 135 | \ No newline at end of file | 153 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
| @@ -53,4 +53,5 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> { | @@ -53,4 +53,5 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> { | ||
| 53 | default long selectCountByGatewayId(Long id) { | 53 | default long selectCountByGatewayId(Long id) { |
| 54 | return selectCount(IotDeviceDO::getGatewayId, id); | 54 | return selectCount(IotDeviceDO::getGatewayId, id); |
| 55 | } | 55 | } |
| 56 | + | ||
| 56 | } | 57 | } |
| 57 | \ No newline at end of file | 58 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
| 1 | package cn.iocoder.yudao.module.iot.service.device; | 1 | package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.IdUtil; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| @@ -34,6 +35,7 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -34,6 +35,7 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 34 | 35 | ||
| 35 | @Resource | 36 | @Resource |
| 36 | private IotDeviceMapper deviceMapper; | 37 | private IotDeviceMapper deviceMapper; |
| 38 | + // TODO @haohao:不直接调用 productmapper,通过 productservice;每一个模型,不直接使用对方的 | ||
| 37 | @Resource | 39 | @Resource |
| 38 | private IotProductMapper productMapper; | 40 | private IotProductMapper productMapper; |
| 39 | 41 | ||
| @@ -46,40 +48,33 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -46,40 +48,33 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 46 | @Override | 48 | @Override |
| 47 | @Transactional(rollbackFor = Exception.class) | 49 | @Transactional(rollbackFor = Exception.class) |
| 48 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { | 50 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { |
| 49 | - // 1. 转换 VO 为 DO | ||
| 50 | - IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class); | ||
| 51 | - | ||
| 52 | - // 2. 根据产品 ID 查询产品信息 | 51 | + // 1.1 校验产品是否存在 |
| 53 | IotProductDO product = productMapper.selectById(createReqVO.getProductId()); | 52 | IotProductDO product = productMapper.selectById(createReqVO.getProductId()); |
| 54 | if (product == null) { | 53 | if (product == null) { |
| 55 | throw exception(PRODUCT_NOT_EXISTS); | 54 | throw exception(PRODUCT_NOT_EXISTS); |
| 56 | } | 55 | } |
| 57 | - device.setProductKey(product.getProductKey()); | ||
| 58 | - device.setDeviceType(product.getDeviceType()); | ||
| 59 | - | ||
| 60 | - // 3. DeviceName 可以为空,当为空时,自动生成产品下的唯一标识符作为 DeviceName | ||
| 61 | - if (StrUtil.isBlank(device.getDeviceName())) { | ||
| 62 | - device.setDeviceName(generateUniqueDeviceName(createReqVO.getProductId())); | 56 | + // 1.2 校验设备名称在同一产品下是否唯一 |
| 57 | + if (StrUtil.isBlank(createReqVO.getDeviceName())) { | ||
| 58 | + createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey())); | ||
| 59 | + } else { | ||
| 60 | + validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName()); | ||
| 63 | } | 61 | } |
| 64 | 62 | ||
| 65 | - // 4. 校验设备名称在同一产品下是否唯一 | ||
| 66 | - validateDeviceNameUnique(device.getProductKey(), device.getDeviceName()); | ||
| 67 | - | ||
| 68 | - // 5. 生成并设置必要的字段 | 63 | + // 2.1 转换 VO 为 DO |
| 64 | + IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class) | ||
| 65 | + .setProductKey(product.getProductKey()) | ||
| 66 | + .setDeviceType(product.getDeviceType()); | ||
| 67 | + // 2.2 生成并设置必要的字段 | ||
| 69 | device.setDeviceKey(generateUniqueDeviceKey()); | 68 | device.setDeviceKey(generateUniqueDeviceKey()); |
| 70 | device.setDeviceSecret(generateDeviceSecret()); | 69 | device.setDeviceSecret(generateDeviceSecret()); |
| 71 | device.setMqttClientId(generateMqttClientId()); | 70 | device.setMqttClientId(generateMqttClientId()); |
| 72 | device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey())); | 71 | device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey())); |
| 73 | device.setMqttPassword(generateMqttPassword()); | 72 | device.setMqttPassword(generateMqttPassword()); |
| 74 | - | ||
| 75 | - // 6. 设置设备状态为未激活 | 73 | + // 2.3 设置设备状态为未激活 |
| 76 | device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()); | 74 | device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()); |
| 77 | device.setStatusLastUpdateTime(LocalDateTime.now()); | 75 | device.setStatusLastUpdateTime(LocalDateTime.now()); |
| 78 | - | ||
| 79 | - // 7. 插入到数据库 | 76 | + // 2.4 插入到数据库 |
| 80 | deviceMapper.insert(device); | 77 | deviceMapper.insert(device); |
| 81 | - | ||
| 82 | - // 8. 返回生成的设备 ID | ||
| 83 | return device.getId(); | 78 | return device.getId(); |
| 84 | } | 79 | } |
| 85 | 80 | ||
| @@ -111,7 +106,7 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -111,7 +106,7 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 111 | * @return 生成的 deviceSecret | 106 | * @return 生成的 deviceSecret |
| 112 | */ | 107 | */ |
| 113 | private String generateDeviceSecret() { | 108 | private String generateDeviceSecret() { |
| 114 | - // 32 位随机字符串 | 109 | + // TODO @haohao:return IdUtil.fastSimpleUUID() |
| 115 | return UUID.randomUUID().toString().replace("-", ""); | 110 | return UUID.randomUUID().toString().replace("-", ""); |
| 116 | } | 111 | } |
| 117 | 112 | ||
| @@ -141,39 +136,25 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -141,39 +136,25 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 141 | * @return 生成的 MQTT Password | 136 | * @return 生成的 MQTT Password |
| 142 | */ | 137 | */ |
| 143 | private String generateMqttPassword() { | 138 | private String generateMqttPassword() { |
| 144 | - // 在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希 | 139 | + // TODO @haohao:【后续优化】在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希 |
| 145 | return UUID.randomUUID().toString(); | 140 | return UUID.randomUUID().toString(); |
| 146 | } | 141 | } |
| 147 | 142 | ||
| 148 | /** | 143 | /** |
| 149 | * 生成唯一的 DeviceName | 144 | * 生成唯一的 DeviceName |
| 150 | * | 145 | * |
| 151 | - * @param productId 产品 ID | 146 | + * @param productKey 产品标识 |
| 152 | * @return 生成的唯一 DeviceName | 147 | * @return 生成的唯一 DeviceName |
| 153 | */ | 148 | */ |
| 154 | - private String generateUniqueDeviceName(Long productId) { | ||
| 155 | - // 实现逻辑以在产品下生成唯一的设备名称 | ||
| 156 | - String deviceName; | ||
| 157 | - String productKey = getProductKey(productId); | ||
| 158 | - do { | ||
| 159 | - // 20 位随机字符串 | ||
| 160 | - deviceName = UUID.randomUUID().toString().replace("-", "").substring(0, 20); | ||
| 161 | - } while (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null); | ||
| 162 | - return deviceName; | ||
| 163 | - } | ||
| 164 | - | ||
| 165 | - /** | ||
| 166 | - * 获取产品 Key | ||
| 167 | - * | ||
| 168 | - * @param productId 产品 ID | ||
| 169 | - * @return 产品 Key | ||
| 170 | - */ | ||
| 171 | - private String getProductKey(Long productId) { | ||
| 172 | - IotProductDO product = productMapper.selectById(productId); | ||
| 173 | - if (product == null) { | ||
| 174 | - throw exception(PRODUCT_NOT_EXISTS); | 149 | + private String generateUniqueDeviceName(String productKey) { |
| 150 | + // TODO @haohao:业务逻辑里,尽量避免 while true。万一 bug = =;虽然这个不会哈。我先改了下 | ||
| 151 | + for (int i = 0; i < Short.MAX_VALUE; i++) { | ||
| 152 | + String deviceName = IdUtil.fastSimpleUUID().substring(0, 20); | ||
| 153 | + if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) { | ||
| 154 | + return deviceName; | ||
| 155 | + } | ||
| 175 | } | 156 | } |
| 176 | - return product.getProductKey(); | 157 | + throw new IllegalArgumentException("生成 DeviceName 失败"); |
| 177 | } | 158 | } |
| 178 | 159 | ||
| 179 | @Override | 160 | @Override |
| @@ -183,6 +164,7 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -183,6 +164,7 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 183 | IotDeviceDO existingDevice = validateDeviceExists(updateReqVO.getId()); | 164 | IotDeviceDO existingDevice = validateDeviceExists(updateReqVO.getId()); |
| 184 | 165 | ||
| 185 | // 设备名称 和 产品 ID 不能修改 | 166 | // 设备名称 和 产品 ID 不能修改 |
| 167 | + // TODO @haohao:这种,直接设置为 null 就不会更新了。忽略前端的传参 | ||
| 186 | if (updateReqVO.getDeviceName() != null && !updateReqVO.getDeviceName().equals(existingDevice.getDeviceName())) { | 168 | if (updateReqVO.getDeviceName() != null && !updateReqVO.getDeviceName().equals(existingDevice.getDeviceName())) { |
| 187 | throw exception(DEVICE_NAME_CANNOT_BE_MODIFIED); | 169 | throw exception(DEVICE_NAME_CANNOT_BE_MODIFIED); |
| 188 | } | 170 | } |
| @@ -200,18 +182,14 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -200,18 +182,14 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 200 | @Override | 182 | @Override |
| 201 | @Transactional(rollbackFor = Exception.class) | 183 | @Transactional(rollbackFor = Exception.class) |
| 202 | public void deleteDevice(Long id) { | 184 | public void deleteDevice(Long id) { |
| 203 | - // 校验存在 | ||
| 204 | - IotDeviceDO iotDeviceDO = validateDeviceExists(id); | ||
| 205 | - | ||
| 206 | - // 如果是网关设备,检查是否有子设备 | ||
| 207 | - if (iotDeviceDO.getGatewayId() != null) { | ||
| 208 | - long childCount = deviceMapper.selectCountByGatewayId(id); | ||
| 209 | - if (childCount > 0) { | ||
| 210 | - throw exception(DEVICE_HAS_CHILDREN); | ||
| 211 | - } | 185 | + // 1.1 校验存在 |
| 186 | + IotDeviceDO device = validateDeviceExists(id); | ||
| 187 | + // 1.2 如果是网关设备,检查是否有子设备 | ||
| 188 | + if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) { | ||
| 189 | + throw exception(DEVICE_HAS_CHILDREN); | ||
| 212 | } | 190 | } |
| 213 | 191 | ||
| 214 | - // 删除设备 | 192 | + // 2. 删除设备 |
| 215 | deviceMapper.deleteById(id); | 193 | deviceMapper.deleteById(id); |
| 216 | } | 194 | } |
| 217 | 195 | ||
| @@ -222,11 +200,11 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -222,11 +200,11 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 222 | * @return 设备对象 | 200 | * @return 设备对象 |
| 223 | */ | 201 | */ |
| 224 | private IotDeviceDO validateDeviceExists(Long id) { | 202 | private IotDeviceDO validateDeviceExists(Long id) { |
| 225 | - IotDeviceDO iotDeviceDO = deviceMapper.selectById(id); | ||
| 226 | - if (iotDeviceDO == null) { | 203 | + IotDeviceDO device = deviceMapper.selectById(id); |
| 204 | + if (device == null) { | ||
| 227 | throw exception(DEVICE_NOT_EXISTS); | 205 | throw exception(DEVICE_NOT_EXISTS); |
| 228 | } | 206 | } |
| 229 | - return iotDeviceDO; | 207 | + return device; |
| 230 | } | 208 | } |
| 231 | 209 | ||
| 232 | @Override | 210 | @Override |
| @@ -244,21 +222,20 @@ public class DeviceServiceImpl implements IotDeviceService { | @@ -244,21 +222,20 @@ public class DeviceServiceImpl implements IotDeviceService { | ||
| 244 | } | 222 | } |
| 245 | 223 | ||
| 246 | @Override | 224 | @Override |
| 247 | - @Transactional(rollbackFor = Exception.class) | ||
| 248 | public void updateDeviceStatus(Long id, Integer status) { | 225 | public void updateDeviceStatus(Long id, Integer status) { |
| 249 | // 校验存在 | 226 | // 校验存在 |
| 250 | validateDeviceExists(id); | 227 | validateDeviceExists(id); |
| 251 | 228 | ||
| 229 | + // TODO @haohao:这个可以直接用 swagger 注解哈 | ||
| 252 | // 校验状态是否合法 | 230 | // 校验状态是否合法 |
| 253 | if (!IotDeviceStatusEnum.isValidStatus(status)) { | 231 | if (!IotDeviceStatusEnum.isValidStatus(status)) { |
| 254 | throw exception(DEVICE_INVALID_DEVICE_STATUS); | 232 | throw exception(DEVICE_INVALID_DEVICE_STATUS); |
| 255 | } | 233 | } |
| 256 | 234 | ||
| 257 | // 更新状态和更新时间 | 235 | // 更新状态和更新时间 |
| 258 | - IotDeviceDO updateObj = new IotDeviceDO() | ||
| 259 | - .setId(id) | ||
| 260 | - .setStatus(status) | 236 | + IotDeviceDO updateObj = new IotDeviceDO().setId(id).setStatus(status) |
| 261 | .setStatusLastUpdateTime(LocalDateTime.now()); | 237 | .setStatusLastUpdateTime(LocalDateTime.now()); |
| 262 | deviceMapper.updateById(updateObj); | 238 | deviceMapper.updateById(updateObj); |
| 263 | } | 239 | } |
| 240 | + | ||
| 264 | } | 241 | } |
| 265 | \ No newline at end of file | 242 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
| @@ -13,7 +13,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -13,7 +13,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 13 | public interface IotDeviceService { | 13 | public interface IotDeviceService { |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| 16 | - * 创建IoT 设备 | 16 | + * 创建设备 |
| 17 | * | 17 | * |
| 18 | * @param createReqVO 创建信息 | 18 | * @param createReqVO 创建信息 |
| 19 | * @return 编号 | 19 | * @return 编号 |
| @@ -21,21 +21,21 @@ public interface IotDeviceService { | @@ -21,21 +21,21 @@ public interface IotDeviceService { | ||
| 21 | Long createDevice(@Valid IotDeviceSaveReqVO createReqVO); | 21 | Long createDevice(@Valid IotDeviceSaveReqVO createReqVO); |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | - * 更新IoT 设备 | 24 | + * 更新设备 |
| 25 | * | 25 | * |
| 26 | * @param updateReqVO 更新信息 | 26 | * @param updateReqVO 更新信息 |
| 27 | */ | 27 | */ |
| 28 | void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); | 28 | void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); |
| 29 | 29 | ||
| 30 | /** | 30 | /** |
| 31 | - * 删除IoT 设备 | 31 | + * 删除设备 |
| 32 | * | 32 | * |
| 33 | * @param id 编号 | 33 | * @param id 编号 |
| 34 | */ | 34 | */ |
| 35 | void deleteDevice(Long id); | 35 | void deleteDevice(Long id); |
| 36 | 36 | ||
| 37 | /** | 37 | /** |
| 38 | - * 获得IoT 设备 | 38 | + * 获得设备 |
| 39 | * | 39 | * |
| 40 | * @param id 编号 | 40 | * @param id 编号 |
| 41 | * @return IoT 设备 | 41 | * @return IoT 设备 |
| @@ -43,7 +43,7 @@ public interface IotDeviceService { | @@ -43,7 +43,7 @@ public interface IotDeviceService { | ||
| 43 | IotDeviceDO getDevice(Long id); | 43 | IotDeviceDO getDevice(Long id); |
| 44 | 44 | ||
| 45 | /** | 45 | /** |
| 46 | - * 获得IoT 设备分页 | 46 | + * 获得设备分页 |
| 47 | * | 47 | * |
| 48 | * @param pageReqVO 分页查询 | 48 | * @param pageReqVO 分页查询 |
| 49 | * @return IoT 设备分页 | 49 | * @return IoT 设备分页 |
| @@ -51,10 +51,11 @@ public interface IotDeviceService { | @@ -51,10 +51,11 @@ public interface IotDeviceService { | ||
| 51 | PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO); | 51 | PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO); |
| 52 | 52 | ||
| 53 | /** | 53 | /** |
| 54 | - * 更新IoT 设备状态 | 54 | + * 更新设备状态 |
| 55 | * | 55 | * |
| 56 | * @param id 编号 | 56 | * @param id 编号 |
| 57 | * @param status 状态 | 57 | * @param status 状态 |
| 58 | */ | 58 | */ |
| 59 | void updateDeviceStatus(Long id, Integer status); | 59 | void updateDeviceStatus(Long id, Integer status); |
| 60 | + | ||
| 60 | } | 61 | } |
| 61 | \ No newline at end of file | 62 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java
| @@ -170,7 +170,6 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe | @@ -170,7 +170,6 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe | ||
| 170 | 170 | ||
| 171 | // 3.1 使用 diffList 方法比较新旧列表 | 171 | // 3.1 使用 diffList 方法比较新旧列表 |
| 172 | List<List<IotThinkModelFunctionDO>> diffResult = diffList(oldFunctionList, newFunctionList, | 172 | List<List<IotThinkModelFunctionDO>> diffResult = diffList(oldFunctionList, newFunctionList, |
| 173 | - // TODO @haohao:是不是用 id 比较相同就 ok 哈。如果可以的化,下面的 update 可以更简单 | ||
| 174 | // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。 | 173 | // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。 |
| 175 | (oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier()) | 174 | (oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier()) |
| 176 | && Objects.equals(oldFunc.getType(), newFunc.getType())); | 175 | && Objects.equals(oldFunc.getType(), newFunc.getType())); |
| @@ -191,6 +190,7 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe | @@ -191,6 +190,7 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe | ||
| 191 | thinkModelFunctionMapper.updateById(updateFunc); | 190 | thinkModelFunctionMapper.updateById(updateFunc); |
| 192 | } | 191 | } |
| 193 | } | 192 | } |
| 193 | + // TODO @haohao:seckillProductMapper.updateBatch(diffList.get(1)); 可以直接类似这么操作哇? | ||
| 194 | } | 194 | } |
| 195 | if (CollUtil.isNotEmpty(deleteList)) { | 195 | if (CollUtil.isNotEmpty(deleteList)) { |
| 196 | Set<Long> idsToDelete = CollectionUtils.convertSet(deleteList, IotThinkModelFunctionDO::getId); | 196 | Set<Long> idsToDelete = CollectionUtils.convertSet(deleteList, IotThinkModelFunctionDO::getId); |
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMapper.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | -<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper"> | ||
| 4 | - | ||
| 5 | - <!-- | ||
| 6 | - 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。 | ||
| 7 | - 无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。 | ||
| 8 | - 代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。 | ||
| 9 | - 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ | ||
| 10 | - --> | ||
| 11 | - | ||
| 12 | -</mapper> | ||
| 13 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/thinkmodelfunction/IotThinkModelFunctionMapper.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | -<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper"> | ||
| 4 | - | ||
| 5 | - <!-- | ||
| 6 | - 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。 | ||
| 7 | - 无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。 | ||
| 8 | - 代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。 | ||
| 9 | - 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ | ||
| 10 | - --> | ||
| 11 | - | ||
| 12 | -</mapper> | ||
| 13 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImplTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.service.device; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Disabled; | ||
| 4 | -import org.junit.jupiter.api.Test; | ||
| 5 | - | ||
| 6 | -import jakarta.annotation.Resource; | ||
| 7 | - | ||
| 8 | -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||
| 9 | - | ||
| 10 | -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*; | ||
| 11 | -import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | ||
| 12 | -import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper; | ||
| 13 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 14 | - | ||
| 15 | -import org.springframework.context.annotation.Import; | ||
| 16 | - | ||
| 17 | -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; | ||
| 18 | -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; | ||
| 19 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | ||
| 20 | -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; | ||
| 21 | -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; | ||
| 22 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 23 | - | ||
| 24 | -/** | ||
| 25 | - * {@link DeviceServiceImpl} 的单元测试类 | ||
| 26 | - * | ||
| 27 | - * @author 芋道源码 | ||
| 28 | - */ | ||
| 29 | -@Import(DeviceServiceImpl.class) | ||
| 30 | -public class DeviceServiceImplTest extends BaseDbUnitTest { | ||
| 31 | - | ||
| 32 | - @Resource | ||
| 33 | - private DeviceServiceImpl deviceService; | ||
| 34 | - | ||
| 35 | - @Resource | ||
| 36 | - private IotDeviceMapper deviceMapper; | ||
| 37 | - | ||
| 38 | - @Test | ||
| 39 | - public void testCreateDevice_success() { | ||
| 40 | - // 准备参数 | ||
| 41 | - IotDeviceSaveReqVO createReqVO = randomPojo(IotDeviceSaveReqVO.class).setId(null); | ||
| 42 | - | ||
| 43 | - // 调用 | ||
| 44 | - Long deviceId = deviceService.createDevice(createReqVO); | ||
| 45 | - // 断言 | ||
| 46 | - assertNotNull(deviceId); | ||
| 47 | - // 校验记录的属性是否正确 | ||
| 48 | - IotDeviceDO device = deviceMapper.selectById(deviceId); | ||
| 49 | - assertPojoEquals(createReqVO, device, "id"); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - @Test | ||
| 53 | - public void testUpdateDevice_success() { | ||
| 54 | - // mock 数据 | ||
| 55 | - IotDeviceDO dbDevice = randomPojo(IotDeviceDO.class); | ||
| 56 | - deviceMapper.insert(dbDevice);// @Sql: 先插入出一条存在的数据 | ||
| 57 | - // 准备参数 | ||
| 58 | - IotDeviceSaveReqVO updateReqVO = randomPojo(IotDeviceSaveReqVO.class, o -> { | ||
| 59 | - o.setId(dbDevice.getId()); // 设置更新的 ID | ||
| 60 | - }); | ||
| 61 | - | ||
| 62 | - // 调用 | ||
| 63 | - deviceService.updateDevice(updateReqVO); | ||
| 64 | - // 校验是否更新正确 | ||
| 65 | - IotDeviceDO device = deviceMapper.selectById(updateReqVO.getId()); // 获取最新的 | ||
| 66 | - assertPojoEquals(updateReqVO, device); | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - @Test | ||
| 70 | - public void testUpdateDevice_notExists() { | ||
| 71 | - // 准备参数 | ||
| 72 | - IotDeviceSaveReqVO updateReqVO = randomPojo(IotDeviceSaveReqVO.class); | ||
| 73 | - | ||
| 74 | - // 调用, 并断言异常 | ||
| 75 | - assertServiceException(() -> deviceService.updateDevice(updateReqVO), DEVICE_NOT_EXISTS); | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - @Test | ||
| 79 | - public void testDeleteDevice_success() { | ||
| 80 | - // mock 数据 | ||
| 81 | - IotDeviceDO dbDevice = randomPojo(IotDeviceDO.class); | ||
| 82 | - deviceMapper.insert(dbDevice);// @Sql: 先插入出一条存在的数据 | ||
| 83 | - // 准备参数 | ||
| 84 | - Long id = dbDevice.getId(); | ||
| 85 | - | ||
| 86 | - // 调用 | ||
| 87 | - deviceService.deleteDevice(id); | ||
| 88 | - // 校验数据不存在了 | ||
| 89 | - assertNull(deviceMapper.selectById(id)); | ||
| 90 | - } | ||
| 91 | - | ||
| 92 | - @Test | ||
| 93 | - public void testDeleteDevice_notExists() { | ||
| 94 | - // 准备参数 | ||
| 95 | - Long id = randomLongId(); | ||
| 96 | - | ||
| 97 | - // 调用, 并断言异常 | ||
| 98 | - assertServiceException(() -> deviceService.deleteDevice(id), DEVICE_NOT_EXISTS); | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - @Test | ||
| 102 | - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 | ||
| 103 | - public void testGetDevicePage() { | ||
| 104 | - // mock 数据 | ||
| 105 | - IotDeviceDO dbDevice = randomPojo(IotDeviceDO.class, o -> { // 等会查询到 | ||
| 106 | - o.setDeviceKey(null); | ||
| 107 | - o.setDeviceName(null); | ||
| 108 | - o.setProductId(null); | ||
| 109 | - o.setProductKey(null); | ||
| 110 | - o.setDeviceType(null); | ||
| 111 | - o.setNickname(null); | ||
| 112 | - o.setGatewayId(null); | ||
| 113 | - o.setStatus(null); | ||
| 114 | - o.setStatusLastUpdateTime(null); | ||
| 115 | - o.setLastOnlineTime(null); | ||
| 116 | - o.setLastOfflineTime(null); | ||
| 117 | - o.setActiveTime(null); | ||
| 118 | - o.setIp(null); | ||
| 119 | - o.setFirmwareVersion(null); | ||
| 120 | - o.setDeviceSecret(null); | ||
| 121 | - o.setMqttClientId(null); | ||
| 122 | - o.setMqttUsername(null); | ||
| 123 | - o.setMqttPassword(null); | ||
| 124 | - o.setAuthType(null); | ||
| 125 | - o.setLatitude(null); | ||
| 126 | - o.setLongitude(null); | ||
| 127 | - o.setAreaId(null); | ||
| 128 | - o.setAddress(null); | ||
| 129 | - o.setSerialNumber(null); | ||
| 130 | - o.setCreateTime(null); | ||
| 131 | - }); | ||
| 132 | - deviceMapper.insert(dbDevice); | ||
| 133 | - // 测试 deviceKey 不匹配 | ||
| 134 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setDeviceKey(null))); | ||
| 135 | - // 测试 deviceName 不匹配 | ||
| 136 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setDeviceName(null))); | ||
| 137 | - // 测试 productId 不匹配 | ||
| 138 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setProductId(null))); | ||
| 139 | - // 测试 productKey 不匹配 | ||
| 140 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setProductKey(null))); | ||
| 141 | - // 测试 deviceType 不匹配 | ||
| 142 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setDeviceType(null))); | ||
| 143 | - // 测试 nickname 不匹配 | ||
| 144 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setNickname(null))); | ||
| 145 | - // 测试 gatewayId 不匹配 | ||
| 146 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setGatewayId(null))); | ||
| 147 | - // 测试 status 不匹配 | ||
| 148 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setStatus(null))); | ||
| 149 | - // 测试 statusLastUpdateTime 不匹配 | ||
| 150 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setStatusLastUpdateTime(null))); | ||
| 151 | - // 测试 lastOnlineTime 不匹配 | ||
| 152 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setLastOnlineTime(null))); | ||
| 153 | - // 测试 lastOfflineTime 不匹配 | ||
| 154 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setLastOfflineTime(null))); | ||
| 155 | - // 测试 activeTime 不匹配 | ||
| 156 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setActiveTime(null))); | ||
| 157 | - // 测试 ip 不匹配 | ||
| 158 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setIp(null))); | ||
| 159 | - // 测试 firmwareVersion 不匹配 | ||
| 160 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setFirmwareVersion(null))); | ||
| 161 | - // 测试 deviceSecret 不匹配 | ||
| 162 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setDeviceSecret(null))); | ||
| 163 | - // 测试 mqttClientId 不匹配 | ||
| 164 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setMqttClientId(null))); | ||
| 165 | - // 测试 mqttUsername 不匹配 | ||
| 166 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setMqttUsername(null))); | ||
| 167 | - // 测试 mqttPassword 不匹配 | ||
| 168 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setMqttPassword(null))); | ||
| 169 | - // 测试 authType 不匹配 | ||
| 170 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setAuthType(null))); | ||
| 171 | - // 测试 latitude 不匹配 | ||
| 172 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setLatitude(null))); | ||
| 173 | - // 测试 longitude 不匹配 | ||
| 174 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setLongitude(null))); | ||
| 175 | - // 测试 areaId 不匹配 | ||
| 176 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setAreaId(null))); | ||
| 177 | - // 测试 address 不匹配 | ||
| 178 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setAddress(null))); | ||
| 179 | - // 测试 serialNumber 不匹配 | ||
| 180 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setSerialNumber(null))); | ||
| 181 | - // 测试 createTime 不匹配 | ||
| 182 | - deviceMapper.insert(cloneIgnoreId(dbDevice, o -> o.setCreateTime(null))); | ||
| 183 | - // 准备参数 | ||
| 184 | - IotDevicePageReqVO reqVO = new IotDevicePageReqVO(); | ||
| 185 | - reqVO.setDeviceKey(null); | ||
| 186 | - reqVO.setDeviceName(null); | ||
| 187 | - reqVO.setProductId(null); | ||
| 188 | - reqVO.setProductKey(null); | ||
| 189 | - reqVO.setDeviceType(null); | ||
| 190 | - reqVO.setNickname(null); | ||
| 191 | - reqVO.setGatewayId(null); | ||
| 192 | - reqVO.setStatus(null); | ||
| 193 | - reqVO.setStatusLastUpdateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | ||
| 194 | - reqVO.setLastOnlineTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | ||
| 195 | - reqVO.setLastOfflineTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | ||
| 196 | - reqVO.setActiveTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | ||
| 197 | - reqVO.setIp(null); | ||
| 198 | - reqVO.setFirmwareVersion(null); | ||
| 199 | - reqVO.setDeviceSecret(null); | ||
| 200 | - reqVO.setMqttClientId(null); | ||
| 201 | - reqVO.setMqttUsername(null); | ||
| 202 | - reqVO.setMqttPassword(null); | ||
| 203 | - reqVO.setAuthType(null); | ||
| 204 | - reqVO.setLatitude(null); | ||
| 205 | - reqVO.setLongitude(null); | ||
| 206 | - reqVO.setAreaId(null); | ||
| 207 | - reqVO.setAddress(null); | ||
| 208 | - reqVO.setSerialNumber(null); | ||
| 209 | - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | ||
| 210 | - | ||
| 211 | - // 调用 | ||
| 212 | - PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(reqVO); | ||
| 213 | - // 断言 | ||
| 214 | - assertEquals(1, pageResult.getTotal()); | ||
| 215 | - assertEquals(1, pageResult.getList().size()); | ||
| 216 | - assertPojoEquals(dbDevice, pageResult.getList().get(0)); | ||
| 217 | - } | ||
| 218 | - | ||
| 219 | -} | ||
| 220 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImplTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.service.thinkmodelfunction; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Test; | ||
| 4 | - | ||
| 5 | -import jakarta.annotation.Resource; | ||
| 6 | - | ||
| 7 | -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||
| 8 | - | ||
| 9 | -import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.*; | ||
| 10 | -import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; | ||
| 11 | -import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper; | ||
| 12 | - | ||
| 13 | -import org.springframework.context.annotation.Import; | ||
| 14 | - | ||
| 15 | -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; | ||
| 16 | -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; | ||
| 17 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | ||
| 18 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 19 | - | ||
| 20 | -/** | ||
| 21 | - * {@link IotThinkModelFunctionServiceImpl} 的单元测试类 | ||
| 22 | - * | ||
| 23 | - * @author 芋道源码 | ||
| 24 | - */ | ||
| 25 | -@Import(IotThinkModelFunctionServiceImpl.class) | ||
| 26 | -public class IotThinkModelFunctionServiceImplTest extends BaseDbUnitTest { | ||
| 27 | - | ||
| 28 | - @Resource | ||
| 29 | - private IotThinkModelFunctionServiceImpl thinkModelFunctionService; | ||
| 30 | - | ||
| 31 | - @Resource | ||
| 32 | - private IotThinkModelFunctionMapper thinkModelFunctionMapper; | ||
| 33 | - | ||
| 34 | - @Test | ||
| 35 | - public void testCreateThinkModelFunction_success() { | ||
| 36 | - // 准备参数 | ||
| 37 | - IotThinkModelFunctionSaveReqVO createReqVO = randomPojo(IotThinkModelFunctionSaveReqVO.class); | ||
| 38 | - | ||
| 39 | - // 调用 | ||
| 40 | - Long thinkModelFunctionId = thinkModelFunctionService.createThinkModelFunction(createReqVO); | ||
| 41 | - // 断言 | ||
| 42 | - assertNotNull(thinkModelFunctionId); | ||
| 43 | - // 校验记录的属性是否正确 | ||
| 44 | - IotThinkModelFunctionDO thinkModelFunction = thinkModelFunctionMapper.selectById(thinkModelFunctionId); | ||
| 45 | - assertPojoEquals(createReqVO, thinkModelFunction, "id"); | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - @Test | ||
| 49 | - public void testDeleteThinkModelFunction_success() { | ||
| 50 | - // mock 数据 | ||
| 51 | - IotThinkModelFunctionDO dbThinkModelFunction = randomPojo(IotThinkModelFunctionDO.class); | ||
| 52 | - thinkModelFunctionMapper.insert(dbThinkModelFunction);// @Sql: 先插入出一条存在的数据 | ||
| 53 | - // 准备参数 | ||
| 54 | - Long id = dbThinkModelFunction.getId(); | ||
| 55 | - | ||
| 56 | - // 调用 | ||
| 57 | - thinkModelFunctionService.deleteThinkModelFunction(id); | ||
| 58 | - // 校验数据不存在了 | ||
| 59 | - assertNull(thinkModelFunctionMapper.selectById(id)); | ||
| 60 | - } | ||
| 61 | - | ||
| 62 | - @Test | ||
| 63 | - public void testDeleteThinkModelFunction_notExists() { | ||
| 64 | - // 准备参数 | ||
| 65 | - Long id = randomLongId(); | ||
| 66 | - | ||
| 67 | - // 调用, 并断言异常 | ||
| 68 | - assertServiceException(() -> thinkModelFunctionService.deleteThinkModelFunction(id), THINK_MODEL_FUNCTION_NOT_EXISTS); | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | -} | ||
| 72 | \ No newline at end of file | 0 | \ No newline at end of file |