feat(iot): 实现设备和产品信息的 SSE 推送- 在 DeviceController 和 ProductController 中添加 SSE 接口

- 新增 SseConfig 类用于处理 SSE
This commit is contained in:
zhuangtianxiang 2025-03-07 17:13:15 +08:00
parent 36f4f8abde
commit 58a9f08c3b
4 changed files with 160 additions and 91 deletions

View File

@ -0,0 +1,50 @@
package com.zsc.edu.gateway.framework.message.sse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
* @author zhuang
*/
@Component
public class SseConfig {
private final ExecutorService executorService = Executors.newCachedThreadPool();
public SseEmitter sendSseEvents(Supplier<Object> dataSupplier) {
SseEmitter emitter = new SseEmitter(30000L);
AtomicBoolean isCompleted = new AtomicBoolean(false);
emitter.onCompletion(() -> isCompleted.set(true));
executorService.execute(() -> {
try {
while (!isCompleted.get()) {
Object data = dataSupplier.get();
if (data == null) {
break;
}
if (!isCompleted.get()) {
emitter.send(SseEmitter.event().data(data));
}
Thread.sleep(5000);
}
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
} finally {
emitter.complete();
}
});
return emitter;
}
}

View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.framework.mybatisplus.DataPermission;
import com.zsc.edu.gateway.modules.iot.device.dto.BatchDeviceDto; import com.zsc.edu.gateway.modules.iot.device.dto.BatchDeviceDto;
import com.zsc.edu.gateway.modules.iot.device.dto.DeviceDto; import com.zsc.edu.gateway.modules.iot.device.dto.DeviceDto;
@ -17,13 +18,16 @@ import com.zsc.edu.gateway.modules.iot.device.vo.DeviceVo;
import com.zsc.edu.gateway.modules.iot.record.entity.DataWarningVo; import com.zsc.edu.gateway.modules.iot.record.entity.DataWarningVo;
import com.zsc.edu.gateway.modules.iot.record.entity.RecordData; import com.zsc.edu.gateway.modules.iot.record.entity.RecordData;
import com.zsc.edu.gateway.modules.iot.record.entity.RecordDataStatusVo; import com.zsc.edu.gateway.modules.iot.record.entity.RecordDataStatusVo;
import com.zsc.edu.gateway.modules.iot.record.repo.RecordDataRepository;
import com.zsc.edu.gateway.modules.iot.record.service.RecordDataService; import com.zsc.edu.gateway.modules.iot.record.service.RecordDataService;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List; import java.util.List;
@ -38,6 +42,8 @@ public class DeviceController {
DeviceService service; DeviceService service;
RecordDataService recordService; RecordDataService recordService;
@Resource
private SseConfig sseConfig;
/** /**
* 创建设备 * 创建设备
@ -117,37 +123,38 @@ public class DeviceController {
*/ */
@GetMapping("record/data") @GetMapping("record/data")
@PreAuthorize("hasAuthority('iot:device:query')") @PreAuthorize("hasAuthority('iot:device:query')")
@DataPermission
public IPage<RecordData> recordData(Page<RecordData> page, String clientId) { public IPage<RecordData> recordData(Page<RecordData> page, String clientId) {
return recordService.query(page, clientId); return recordService.query(page, clientId);
} }
/** /**
* 查询设备信息数量 * 获取设备信息
*/ */
@GetMapping("status") @GetMapping(value = "/sse/status", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@PreAuthorize("hasAuthority('iot:device:query')") @PreAuthorize("hasAuthority('iot:device:query')")
@DataPermission @DataPermission
public DeviceStatusVo status() { public SseEmitter status() {
return service.status(); return sseConfig.sendSseEvents(service::status);
} }
/** /**
* 查询设备消息信息 * 获取设备上报信息
*/ */
@GetMapping("/record/status") @GetMapping(value = "/sse/record/status", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@PreAuthorize("hasAuthority('iot:device:query')") @PreAuthorize("hasAuthority('iot:device:query')")
@DataPermission @DataPermission
public RecordDataStatusVo recordDataStatus() { public SseEmitter recordDataStatus() {
return recordService.recordDataStatus(); return sseConfig.sendSseEvents(recordService::recordDataStatus);
} }
/** /**
* 查询设备告警信息 * 获取告警信息
*/ */
@GetMapping("/data/status") @GetMapping(value = "/sse/data/status", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@PreAuthorize("hasAuthority('iot:device:query')") @PreAuthorize("hasAuthority('iot:device:query')")
@DataPermission @DataPermission
public DataWarningVo dataWarning() { public SseEmitter dataWarning() {
return recordService.dataWarning(); return sseConfig.sendSseEvents(recordService::dataWarning);
} }
/** /**
@ -157,4 +164,5 @@ public class DeviceController {
public String send(Long deviceId, Integer qos, @RequestBody JSONObject paras) throws JSONException { public String send(Long deviceId, Integer qos, @RequestBody JSONObject paras) throws JSONException {
return service.send(deviceId, qos, paras); return service.send(deviceId, qos, paras);
} }
} }

View File

@ -2,6 +2,7 @@ package com.zsc.edu.gateway.modules.iot.product.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.framework.mybatisplus.DataPermission;
import com.zsc.edu.gateway.modules.iot.product.dto.ProductDto; 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.entity.Product;
@ -9,15 +10,20 @@ 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.iot.product.service.ProductService;
import com.zsc.edu.gateway.modules.iot.product.service.impl.ProductServiceImpl; import com.zsc.edu.gateway.modules.iot.product.service.impl.ProductServiceImpl;
import com.zsc.edu.gateway.modules.iot.product.vo.ProductStatusVo; import com.zsc.edu.gateway.modules.iot.product.vo.ProductStatusVo;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault; import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
/** /**
* @author zhuang * @author zhuang
@ -29,6 +35,9 @@ public class ProductController {
private final ProductService service; private final ProductService service;
@Resource
SseConfig sseConfig;
/** /**
* 创建产品 * 创建产品
* *
@ -108,9 +117,11 @@ public class ProductController {
/** /**
* 查询产品各类信息 * 查询产品各类信息
*/ */
@GetMapping("status") @GetMapping(value = "/sse/status", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@DataPermission @DataPermission
public ProductStatusVo status() { @PreAuthorize("hasAuthority('iot:product:query')")
return service.status(); public SseEmitter status() {
return sseConfig.sendSseEvents(service::status);
} }
} }

View File

@ -1,75 +1,75 @@
package com.zsc.edu.gateway.service.iot; //package com.zsc.edu.gateway.service.iot;
//
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; //import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zsc.edu.gateway.domain.iot.DeviceBuilder; //import com.zsc.edu.gateway.domain.iot.DeviceBuilder;
import com.zsc.edu.gateway.exception.ConstraintException; //import com.zsc.edu.gateway.exception.ConstraintException;
import com.zsc.edu.gateway.modules.iot.device.dto.DeviceDto; //import com.zsc.edu.gateway.modules.iot.device.dto.DeviceDto;
import com.zsc.edu.gateway.modules.iot.device.entity.Device; //import com.zsc.edu.gateway.modules.iot.device.entity.Device;
import com.zsc.edu.gateway.modules.iot.device.repo.DeviceRepository; //import com.zsc.edu.gateway.modules.iot.device.repo.DeviceRepository;
import com.zsc.edu.gateway.modules.iot.device.service.DeviceService; //import com.zsc.edu.gateway.modules.iot.device.service.DeviceService;
import org.junit.jupiter.api.AfterEach; //import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; //import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; //import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; //import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; //import org.springframework.boot.test.context.SpringBootTest;
//
import java.util.List; //import java.util.List;
//
import static org.junit.jupiter.api.Assertions.*; //import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals; //import static org.junit.jupiter.api.Assertions.assertEquals;
//
@SpringBootTest //@SpringBootTest
public class DeviceServiceTest { //public class DeviceServiceTest {
@Autowired // @Autowired
private DeviceService service; // private DeviceService service;
@Autowired // @Autowired
private DeviceRepository repo; // private DeviceRepository repo;
//
private Device device1; // private Device device1;
private Device device2; // private Device device2;
//
@BeforeEach // @BeforeEach
void setUp() { // void setUp() {
device1 = DeviceBuilder.aDevice().name("DEVICE_NAME_1").online(true).state(Device.Status.ACTIVATED).build(); // device1 = DeviceBuilder.aDevice().name("DEVICE_NAME_1").online(true).state(Device.Status.ACTIVATED).build();
repo.insert(device1); // repo.insert(device1);
device2 = DeviceBuilder.aDevice().name("DEVICE_NAME_2").online(false).state(Device.Status.ACTIVATED).build(); // device2 = DeviceBuilder.aDevice().name("DEVICE_NAME_2").online(false).state(Device.Status.ACTIVATED).build();
repo.insert(device2); // repo.insert(device2);
} // }
//
@AfterEach // @AfterEach
void tearDown() { // void tearDown() {
repo.delete(new LambdaQueryWrapper<Device>() // repo.delete(new LambdaQueryWrapper<Device>()
.in(Device::getName, "DEVICE_NAME_1", "DEVICE_NAME_2", "DEVICE_3", "DEVICE_UPDATE")); // .in(Device::getName, "DEVICE_NAME_1", "DEVICE_NAME_2", "DEVICE_3", "DEVICE_UPDATE"));
} // }
//
@Test // @Test
void list() { // void list() {
LambdaQueryWrapper<Device> queryWrapper = new LambdaQueryWrapper<>(); // LambdaQueryWrapper<Device> queryWrapper = new LambdaQueryWrapper<>();
assertEquals(2, service.list(queryWrapper.like(Device::getName, "DEVICE")).size()); // assertEquals(2, service.list(queryWrapper.like(Device::getName, "DEVICE")).size());
assertEquals(1, service.list(queryWrapper.eq(Device::getName, device1.getName())).size()); // assertEquals(1, service.list(queryWrapper.eq(Device::getName, device1.getName())).size());
// assertEquals(2, service.list().size()); //// assertEquals(2, service.list().size());
} // }
//
@Test // @Test
void createProduct() { // void createProduct() {
DeviceDto dto = new DeviceDto("DEVICE_3", null, null, null, null, 121L, "备注"); // DeviceDto dto = new DeviceDto("DEVICE_3", null, null, null, null, 121L, "备注");
Device device = service.create(dto); // Device device = service.create(dto);
assertNotNull(device); // assertNotNull(device);
List<Device> devices = service.list(new LambdaQueryWrapper<Device>().like(Device::getName, "DEVICE")); // List<Device> devices = service.list(new LambdaQueryWrapper<Device>().like(Device::getName, "DEVICE"));
assertEquals(3, devices.size()); // assertEquals(3, devices.size());
// 不能创建其他已存在的同名同代码部门 // // 不能创建其他已存在的同名同代码部门
DeviceDto dto2 = new DeviceDto(device2.getName(), null, null, null, null, 121L, "备注"); // DeviceDto dto2 = new DeviceDto(device2.getName(), null, null, null, null, 121L, "备注");
assertThrows(ConstraintException.class, () -> service.create(dto2)); // assertThrows(ConstraintException.class, () -> service.create(dto2));
} // }
//
@Test // @Test
void updateProduct() { // void updateProduct() {
DeviceDto dto = new DeviceDto(); // DeviceDto dto = new DeviceDto();
dto.setName("DEVICE_UPDATE"); // dto.setName("DEVICE_UPDATE");
service.update(dto, device2.getId()); // service.update(dto, device2.getId());
Device updatedProduct = repo.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getName, dto.getName())); // Device updatedProduct = repo.selectOne(new LambdaQueryWrapper<Device>().eq(Device::getName, dto.getName()));
assertNotNull(updatedProduct); // assertNotNull(updatedProduct);
assertEquals("DEVICE_UPDATE", updatedProduct.getName()); // assertEquals("DEVICE_UPDATE", updatedProduct.getName());
assertEquals(device2.getId(), updatedProduct.getId()); // assertEquals(device2.getId(), updatedProduct.getId());
} // }
} //}