feat(iot): 优化设备管理功能

- 添加附件预览功能
- 修复设备列表查询条件
- 优化产品和设备控制器
- 调整 WebSocket连接逻辑
This commit is contained in:
zhuangtianxiang 2025-03-20 19:34:00 +08:00
parent 182d4c7961
commit 790aed76ea
12 changed files with 74 additions and 26 deletions

View File

@ -145,7 +145,6 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -26,16 +26,14 @@ public class WebSocketInterceptor implements HandshakeInterceptor {
private ProductService productService;
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
if (request instanceof ServletServerHttpRequest) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
// 获取路径变量
// 获取完整路径
String path = servletRequest.getRequestURI();
String[] pathParts = path.split("/");
String pathVariable = pathParts[pathParts.length - 1];
// 根据路径变量设置不同的业务逻辑 Supplier
switch (pathVariable) {
// 根据路径设置不同的业务逻辑 Supplier
switch (path) {
case "/api/rest/device/ws/device/status":
attributes.put("dataSupplier", (Supplier<String>) () -> String.valueOf(deviceService.status()));
break;
@ -46,7 +44,7 @@ public class WebSocketInterceptor implements HandshakeInterceptor {
attributes.put("dataSupplier", (Supplier<String>) () -> String.valueOf(productService.status()));
break;
default:
attributes.put("dataSupplier", (Supplier<String>) () -> "Unknown path: " + pathVariable);
attributes.put("dataSupplier", (Supplier<String>) () -> "Unknown path: " + path);
break;
}
}

View File

@ -1,6 +1,7 @@
package com.zsc.edu.gateway.modules.attachment.controller;
import com.zsc.edu.gateway.exception.StorageException;
import com.zsc.edu.gateway.framework.storage.exception.StorageFileNotFoundException;
import com.zsc.edu.gateway.modules.attachment.entity.Attachment;
import com.zsc.edu.gateway.modules.attachment.service.AttachmentService;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation;
@ -8,6 +9,7 @@ import lombok.AllArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;
@ -73,6 +75,43 @@ public class AttachmentController {
}
return ResponseEntity.ok(wrapper.resource);
}
/**
* 预览附件
*
* @param id 附件ID
* @return 附件文件内容直接预览
*/
@GetMapping("/preview/{id}")
public ResponseEntity<?> preview(
@PathVariable("id") String id
) {
try {
Attachment.Wrapper wrapper = service.loadAsWrapper(id);
String mimeType = wrapper.attachment.mimeType;
// 如果是图片或 PDF直接返回文件流
if (mimeType.startsWith("image/") || mimeType.equals("application/pdf")) {
ContentDisposition contentDisposition = ContentDisposition.builder("inline")
.filename(wrapper.attachment.fileName, StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CONTENT_TYPE, mimeType)
.body(wrapper.resource);
} else {
// 如果是文本文件直接返回文本内容
String content = new String(wrapper.resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
return ResponseEntity.ok(content);
}
} catch (StorageFileNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件读取失败");
}
}
/**
* 根据附件ID获取附件信息
* */

View File

@ -4,7 +4,6 @@ import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zsc.edu.gateway.framework.message.sse.SseConfig;
import com.zsc.edu.gateway.framework.mybatisplus.DataPermission;
import com.zsc.edu.gateway.modules.iot.device.dto.BatchDeviceDto;
import com.zsc.edu.gateway.modules.iot.device.dto.DeviceDto;
@ -38,8 +37,6 @@ public class DeviceController {
DeviceService service;
RecordDataService recordService;
@Resource
private SseConfig sseConfig;
/**
* 创建设备

View File

@ -64,4 +64,5 @@ public class DeviceDto {
*/
public String previewId;
}

View File

@ -121,7 +121,7 @@ public class Device extends BaseEntity {
* 设备预览图附件ID
*/
public String previewId;
//TODO 设备运行状态
public enum Status implements IEnum<Integer>, IState<Status> {
UNACTIVATED(0, "未激活"),

View File

@ -43,10 +43,7 @@ public class DeviceQuery {
* 设态是否在线
*/
public Boolean isOnline;
/**
* 产品名称
*/
public String productName;
public LambdaQueryWrapper<Device> wrapper() {
LambdaQueryWrapper<Device> queryWrapper = new LambdaQueryWrapper<>();

View File

@ -32,4 +32,6 @@ public interface DeviceRepository extends BaseMapper<Device> {
Device findByClientId(@Param("clientId") String clientId);
List<Device> selectListByName(@Param("name") String name);
IPage<Device> selectPageByConditions(Page<Device> page, @Param("query") DeviceQuery query);
}

View File

@ -172,9 +172,10 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceRepository, Device> imp
/**
* 查询设备列表
*/
//TODO 解决分页条件问题
@Override
public IPage<Device> query(Page<Device> page, DeviceQuery query) {
return deviceRepo.selectPage(page, query.wrapper());
return baseMapper.selectPageByConditions(page, query);
}
/**

View File

@ -126,7 +126,6 @@ public class DeviceVo {
*/
public Attachment preview;
/**
* 所属产品ID
*/

View File

@ -1,14 +1,12 @@
package com.zsc.edu.gateway.modules.iot.product.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zsc.edu.gateway.framework.message.sse.SseConfig;
import com.zsc.edu.gateway.framework.mybatisplus.DataPermission;
import com.zsc.edu.gateway.modules.iot.product.dto.ProductDto;
import com.zsc.edu.gateway.modules.iot.product.entity.Product;
import com.zsc.edu.gateway.modules.iot.product.query.ProductQuery;
import com.zsc.edu.gateway.modules.iot.product.service.ProductService;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -25,8 +23,6 @@ public class ProductController {
private final ProductService service;
@Resource
SseConfig sseConfig;
/**
* 创建产品

View File

@ -82,10 +82,27 @@
where d.id = #{id}
</select>
<select id="selectList" resultType="com.zsc.edu.gateway.modules.iot.device.entity.Device">
SELECT d.*, p.name AS productName
<select id="selectPageByConditions" resultType="com.zsc.edu.gateway.modules.iot.device.entity.Device">
SELECT d.*, p.*
FROM iot_device d
LEFT JOIN iot_product p ON d.product_id = p.id
LEFT JOIN iot_product p ON d.product_id = p.id
<where>
<if test="query.name != null and query.name != ''">
AND d.name LIKE concat('%', #{query.name}, '%')
</if>
<if test="query.clientId != null and query.clientId != ''">
AND d.client_id = #{query.clientId}
</if>
<if test="query.state != null and query.state != ''">
AND d.state = #{query.state}
</if>
<if test="query.isOnline != null and query.isOnline != ''">
AND d.online = #{query.isOnline}
</if>
<if test="query.productId != null and query.productId != ''">
AND d.product_id = #{query.productId}
</if>
</where>
</select>
<select id="selectListByName" resultMap="BaseResultMap">
@ -105,6 +122,8 @@
left join iot_param ip on p.id = ip.foreign_id and ip.foreign_type = 3
left join attachment ai on d.icon_id = ai.id
left join attachment ap on d.preview_id = ap.id
WHERE d.name LIKE concat('%', #{name}, '%')
<if test="name != null">
WHERE d.name LIKE concat('%', #{name}, '%')
</if>
</select>
</mapper>