mqtt模块新增智能插座开关,websocket模块
pdf生成功能
This commit is contained in:
24
pom.xml
24
pom.xml
@@ -39,11 +39,10 @@
|
||||
<logback.version>1.2.13</logback.version>
|
||||
<spring-security.version>5.7.14</spring-security.version>
|
||||
<spring-framework.version>5.3.39</spring-framework.version>
|
||||
|
||||
<!-- ========== 新增:统一版本管理(只加这3个) ========== -->
|
||||
<lombok.version>1.18.32</lombok.version>
|
||||
<paho.mqtt.version>1.2.5</paho.mqtt.version>
|
||||
<slf4j.version>1.7.36</slf4j.version>
|
||||
<itext.version>7.2.5</itext.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -237,9 +236,8 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- ================== 只新增这3个依赖 ================== -->
|
||||
|
||||
<!-- 1) SLF4J(你要求显式加;版本走 properties) -->
|
||||
<!-- 1) SLF4J -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
@@ -281,6 +279,24 @@
|
||||
<version>3.5.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>kernel</artifactId>
|
||||
<version>${itext.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>layout</artifactId>
|
||||
<version>${itext.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
23
src/main/java/com/shzg/common/utils/DeptScopeUtils.java
Normal file
23
src/main/java/com/shzg/common/utils/DeptScopeUtils.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.shzg.common.utils;
|
||||
|
||||
import com.shzg.common.utils.spring.SpringUtils;
|
||||
import com.shzg.project.system.service.ISysDeptService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门数据范围工具类
|
||||
*/
|
||||
public class DeptScopeUtils
|
||||
{
|
||||
/**
|
||||
* 获取当前用户部门 + 子部门ID集合
|
||||
*/
|
||||
public static List<Long> getDeptScope()
|
||||
{
|
||||
Long deptId = SecurityUtils.getDeptId();
|
||||
|
||||
return SpringUtils.getBean(ISysDeptService.class)
|
||||
.selectDeptAndChildIds(deptId);
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,9 @@ public class SecurityConfig
|
||||
"/register",
|
||||
"/unique/code/**",
|
||||
"/device/cmd/**",
|
||||
"/worn/socket/**",
|
||||
"/worn/inboundBill/**",
|
||||
"/ws/**",
|
||||
"/captchaImage").permitAll()
|
||||
// 静态资源,可匿名访问
|
||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
@@ -42,6 +44,12 @@ public class BaseEntity implements Serializable
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, Object> params;
|
||||
|
||||
/**
|
||||
* 部门数据范围(用于数据隔离)
|
||||
*/
|
||||
@JsonIgnore
|
||||
private List<Long> dataScopeDeptIds;
|
||||
|
||||
public String getSearchValue()
|
||||
{
|
||||
return searchValue;
|
||||
@@ -115,4 +123,14 @@ public class BaseEntity implements Serializable
|
||||
{
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public List<Long> getDataScopeDeptIds()
|
||||
{
|
||||
return dataScopeDeptIds;
|
||||
}
|
||||
|
||||
public void setDataScopeDeptIds(List<Long> dataScopeDeptIds)
|
||||
{
|
||||
this.dataScopeDeptIds = dataScopeDeptIds;
|
||||
}
|
||||
}
|
||||
@@ -124,4 +124,12 @@ public interface SysDeptMapper
|
||||
*/
|
||||
public List<SysDept> selectDeptAndChildren(@Param("deptId") Long deptId,
|
||||
@Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 根据部门ID查询当前部门及其所有子部门ID集合
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 部门ID集合
|
||||
*/
|
||||
List<Long> selectDeptAndChildIds(Long deptId);
|
||||
}
|
||||
|
||||
@@ -129,4 +129,12 @@ public interface ISysDeptService
|
||||
* @return 部门信息
|
||||
*/
|
||||
public List<SysDept> selectDeptAndChildrenByUserId(Long userId, String keyword);
|
||||
|
||||
/**
|
||||
* 根据部门ID查询部门以及子部门ID列表
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 部门ID列表
|
||||
*/
|
||||
List<Long> selectDeptAndChildIds(Long deptId);
|
||||
}
|
||||
|
||||
@@ -354,4 +354,10 @@ public class SysDeptServiceImpl implements ISysDeptService
|
||||
|
||||
return deptMapper.selectDeptAndChildren(deptId, keyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> selectDeptAndChildIds(Long deptId)
|
||||
{
|
||||
return deptMapper.selectDeptAndChildIds(deptId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import com.shzg.common.exception.ServiceException;
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import com.shzg.common.utils.DeptScopeUtils;
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import com.shzg.common.utils.StringUtils;
|
||||
import com.shzg.framework.aspectj.lang.annotation.DataScope;
|
||||
@@ -89,13 +90,12 @@ public class WornUniqueCodeServiceImpl implements IWornUniqueCodeService
|
||||
* @param wornUniqueCode 唯一码管理
|
||||
* @return 唯一码管理
|
||||
*/
|
||||
@DataScope(deptAlias = "d")
|
||||
@Override
|
||||
public List<WornUniqueCode> selectWornUniqueCodeList(WornUniqueCode wornUniqueCode)
|
||||
{
|
||||
if (!SecurityUtils.isAdmin())
|
||||
{
|
||||
wornUniqueCode.setProjectId(SecurityUtils.getDeptId());
|
||||
wornUniqueCode.setDataScopeDeptIds(DeptScopeUtils.getDeptScope());
|
||||
}
|
||||
return wornUniqueCodeMapper.selectWornUniqueCodeList(wornUniqueCode);
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.shzg.project.worn.controller;
|
||||
|
||||
import com.shzg.framework.web.controller.BaseController;
|
||||
import com.shzg.framework.web.domain.AjaxResult;
|
||||
import com.shzg.project.worn.service.IDeviceCommandService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/device/cmd")
|
||||
public class DeviceCommandController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IDeviceCommandService deviceCommandService;
|
||||
|
||||
/**
|
||||
* 烟雾传感器 - 持续消警
|
||||
*/
|
||||
@PostMapping("/smoke/stop")
|
||||
public AjaxResult smokeStop(@RequestParam Long deviceId) {
|
||||
deviceCommandService.smokeStop(deviceId);
|
||||
return AjaxResult.success("消警指令已发送");
|
||||
}
|
||||
|
||||
/**
|
||||
* 烟雾传感器 - 单次消警(80秒)
|
||||
*/
|
||||
@PostMapping("/smoke/stopOnce")
|
||||
public AjaxResult smokeStopOnce(@RequestParam Long deviceId) {
|
||||
deviceCommandService.smokeStopOnce(deviceId);
|
||||
return AjaxResult.success("单次消警指令已发送");
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.shzg.project.worn.controller;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.shzg.project.worn.controller;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.shzg.framework.aspectj.lang.annotation.Log;
|
||||
import com.shzg.framework.aspectj.lang.enums.BusinessType;
|
||||
import com.shzg.project.worn.domain.MqttTopicConfig;
|
||||
import com.shzg.project.worn.service.IMqttTopicConfigService;
|
||||
import com.shzg.framework.web.controller.BaseController;
|
||||
import com.shzg.framework.web.domain.AjaxResult;
|
||||
import com.shzg.common.utils.poi.ExcelUtil;
|
||||
import com.shzg.framework.web.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* MQTT主题配置Controller
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/worn/mqttConfig")
|
||||
public class MqttTopicConfigController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IMqttTopicConfigService mqttTopicConfigService;
|
||||
|
||||
/**
|
||||
* 查询MQTT主题配置列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
startPage();
|
||||
List<MqttTopicConfig> list = mqttTopicConfigService.selectMqttTopicConfigList(mqttTopicConfig);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出MQTT主题配置列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:export')")
|
||||
@Log(title = "MQTT主题配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
List<MqttTopicConfig> list = mqttTopicConfigService.selectMqttTopicConfigList(mqttTopicConfig);
|
||||
ExcelUtil<MqttTopicConfig> util = new ExcelUtil<MqttTopicConfig>(MqttTopicConfig.class);
|
||||
util.exportExcel(response, list, "MQTT主题配置数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MQTT主题配置详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id)
|
||||
{
|
||||
return success(mqttTopicConfigService.selectMqttTopicConfigById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增MQTT主题配置
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:add')")
|
||||
@Log(title = "MQTT主题配置", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
return toAjax(mqttTopicConfigService.insertMqttTopicConfig(mqttTopicConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改MQTT主题配置
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:edit')")
|
||||
@Log(title = "MQTT主题配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
return toAjax(mqttTopicConfigService.updateMqttTopicConfig(mqttTopicConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除MQTT主题配置
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('worn:mqttConfig:remove')")
|
||||
@Log(title = "MQTT主题配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids)
|
||||
{
|
||||
return toAjax(mqttTopicConfigService.deleteMqttTopicConfigByIds(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.shzg.project.worn.controller;
|
||||
|
||||
import com.shzg.framework.web.domain.AjaxResult;
|
||||
import com.shzg.project.worn.service.SocketControlService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/worn/socket")
|
||||
public class SocketController {
|
||||
|
||||
@Autowired
|
||||
private SocketControlService socketControlService;
|
||||
|
||||
/**
|
||||
* 控制插座开关
|
||||
*/
|
||||
@PostMapping("/control")
|
||||
public AjaxResult control(@RequestParam String devEui,
|
||||
@RequestParam Integer status) {
|
||||
|
||||
boolean on = status == 1;
|
||||
|
||||
socketControlService.controlSocket(devEui, on);
|
||||
|
||||
return AjaxResult.success("指令已发送");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.shzg.project.worn.controller;
|
||||
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ws/test")
|
||||
public class WebSocketTestController {
|
||||
|
||||
@Autowired
|
||||
private WebSocketSessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* 测试推送(广播)
|
||||
*/
|
||||
@GetMapping("/broadcast")
|
||||
public String broadcast() {
|
||||
sessionManager.sendAll("🔥 测试广播消息");
|
||||
return "OK";
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按dept推送
|
||||
*/
|
||||
@GetMapping("/dept/{deptId}")
|
||||
public String sendToDept(@PathVariable Long deptId) {
|
||||
sessionManager.sendToDept(deptId, "📦 这是dept推送消息");
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
@@ -113,4 +113,19 @@ public class WornInboundBillController extends BaseController
|
||||
{
|
||||
return toAjax(wornInboundBillService.voidBill(wornInboundBill.getBillNo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印入库单
|
||||
*/
|
||||
@GetMapping("/print/{id}")
|
||||
public void print(@PathVariable Long id, HttpServletResponse response) throws Exception
|
||||
{
|
||||
byte[] pdf = wornInboundBillService.generatePdf(id);
|
||||
|
||||
response.setContentType("application/pdf");
|
||||
response.setHeader("Content-Disposition", "inline; filename=inbound.pdf");
|
||||
|
||||
response.getOutputStream().write(pdf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.shzg.project.worn.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import com.shzg.framework.web.domain.BaseEntity;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
@@ -9,9 +10,6 @@ import com.shzg.framework.aspectj.lang.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 设备数据对象 mqtt_sensor_data
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-01
|
||||
*/
|
||||
public class MqttSensorData extends BaseEntity
|
||||
{
|
||||
@@ -24,16 +22,22 @@ public class MqttSensorData extends BaseEntity
|
||||
@Excel(name = "设备ID")
|
||||
private Long deviceId;
|
||||
|
||||
/** 部门ID */
|
||||
@Excel(name = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
private List<Long> deptIds;
|
||||
|
||||
/** MQTT Topic */
|
||||
@Excel(name = "MQTT Topic")
|
||||
private String topic;
|
||||
|
||||
/** 项目(如 tangshan) */
|
||||
@Excel(name = "项目", readConverterExp = "如=,t=angshan")
|
||||
/** 项目 */
|
||||
@Excel(name = "项目")
|
||||
private String project;
|
||||
|
||||
/** 仓库(如 dianchi) */
|
||||
@Excel(name = "仓库", readConverterExp = "如=,d=ianchi")
|
||||
/** 仓库 */
|
||||
@Excel(name = "仓库")
|
||||
private String warehouse;
|
||||
|
||||
/** 原始消息 */
|
||||
@@ -68,187 +72,86 @@ public class MqttSensorData extends BaseEntity
|
||||
@Excel(name = "烟雾浓度")
|
||||
private Long concentration;
|
||||
|
||||
/** ✅ 水浸状态(0=正常,1=报警) */
|
||||
/** 水浸状态 */
|
||||
@Excel(name = "水浸状态")
|
||||
private Integer waterStatus;
|
||||
|
||||
/** 插座状态(0关 1开) */
|
||||
@Excel(name = "插座状态")
|
||||
private Integer switchStatus;
|
||||
|
||||
/** 删除标识 */
|
||||
@Excel(name = "删除标识")
|
||||
private String isDelete;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
// ================== getter / setter ==================
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public void setDeviceId(Long deviceId)
|
||||
{
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
public Long getDeviceId() { return deviceId; }
|
||||
public void setDeviceId(Long deviceId) { this.deviceId = deviceId; }
|
||||
|
||||
public Long getDeviceId()
|
||||
{
|
||||
return deviceId;
|
||||
}
|
||||
public Long getDeptId() { return deptId; }
|
||||
public void setDeptId(Long deptId) { this.deptId = deptId; }
|
||||
|
||||
public void setTopic(String topic)
|
||||
{
|
||||
this.topic = topic;
|
||||
}
|
||||
public List<Long> getDeptIds() { return deptIds; }
|
||||
public void setDeptIds(List<Long> deptIds) { this.deptIds = deptIds; }
|
||||
|
||||
public String getTopic()
|
||||
{
|
||||
return topic;
|
||||
}
|
||||
public String getTopic() { return topic; }
|
||||
public void setTopic(String topic) { this.topic = topic; }
|
||||
|
||||
public void setProject(String project)
|
||||
{
|
||||
this.project = project;
|
||||
}
|
||||
public String getProject() { return project; }
|
||||
public void setProject(String project) { this.project = project; }
|
||||
|
||||
public String getProject()
|
||||
{
|
||||
return project;
|
||||
}
|
||||
public String getWarehouse() { return warehouse; }
|
||||
public void setWarehouse(String warehouse) { this.warehouse = warehouse; }
|
||||
|
||||
public void setWarehouse(String warehouse)
|
||||
{
|
||||
this.warehouse = warehouse;
|
||||
}
|
||||
public String getPayload() { return payload; }
|
||||
public void setPayload(String payload) { this.payload = payload; }
|
||||
|
||||
public String getWarehouse()
|
||||
{
|
||||
return warehouse;
|
||||
}
|
||||
public String getDataJson() { return dataJson; }
|
||||
public void setDataJson(String dataJson) { this.dataJson = dataJson; }
|
||||
|
||||
public void setPayload(String payload)
|
||||
{
|
||||
this.payload = payload;
|
||||
}
|
||||
public Long getBattery() { return battery; }
|
||||
public void setBattery(Long battery) { this.battery = battery; }
|
||||
|
||||
public String getPayload()
|
||||
{
|
||||
return payload;
|
||||
}
|
||||
public BigDecimal getTemperature() { return temperature; }
|
||||
public void setTemperature(BigDecimal temperature) { this.temperature = temperature; }
|
||||
|
||||
public void setDataJson(String dataJson)
|
||||
{
|
||||
this.dataJson = dataJson;
|
||||
}
|
||||
public BigDecimal getHumidity() { return humidity; }
|
||||
public void setHumidity(BigDecimal humidity) { this.humidity = humidity; }
|
||||
|
||||
public String getDataJson()
|
||||
{
|
||||
return dataJson;
|
||||
}
|
||||
public BigDecimal getNh3() { return nh3; }
|
||||
public void setNh3(BigDecimal nh3) { this.nh3 = nh3; }
|
||||
|
||||
public void setBattery(Long battery)
|
||||
{
|
||||
this.battery = battery;
|
||||
}
|
||||
public BigDecimal getH2s() { return h2s; }
|
||||
public void setH2s(BigDecimal h2s) { this.h2s = h2s; }
|
||||
|
||||
public Long getBattery()
|
||||
{
|
||||
return battery;
|
||||
}
|
||||
public Long getConcentration() { return concentration; }
|
||||
public void setConcentration(Long concentration) { this.concentration = concentration; }
|
||||
|
||||
public void setTemperature(BigDecimal temperature)
|
||||
{
|
||||
this.temperature = temperature;
|
||||
}
|
||||
public Integer getWaterStatus() { return waterStatus; }
|
||||
public void setWaterStatus(Integer waterStatus) { this.waterStatus = waterStatus; }
|
||||
|
||||
public BigDecimal getTemperature()
|
||||
{
|
||||
return temperature;
|
||||
}
|
||||
public Integer getSwitchStatus() { return switchStatus; }
|
||||
public void setSwitchStatus(Integer switchStatus) { this.switchStatus = switchStatus; }
|
||||
|
||||
public void setHumidity(BigDecimal humidity)
|
||||
{
|
||||
this.humidity = humidity;
|
||||
}
|
||||
public String getIsDelete() { return isDelete; }
|
||||
public void setIsDelete(String isDelete) { this.isDelete = isDelete; }
|
||||
|
||||
public BigDecimal getHumidity()
|
||||
{
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public void setNh3(BigDecimal nh3)
|
||||
{
|
||||
this.nh3 = nh3;
|
||||
}
|
||||
|
||||
public BigDecimal getNh3()
|
||||
{
|
||||
return nh3;
|
||||
}
|
||||
|
||||
public void setH2s(BigDecimal h2s)
|
||||
{
|
||||
this.h2s = h2s;
|
||||
}
|
||||
|
||||
public BigDecimal getH2s()
|
||||
{
|
||||
return h2s;
|
||||
}
|
||||
|
||||
public void setConcentration(Long concentration)
|
||||
{
|
||||
this.concentration = concentration;
|
||||
}
|
||||
|
||||
public Long getConcentration()
|
||||
{
|
||||
return concentration;
|
||||
}
|
||||
|
||||
public void setWaterStatus(Integer waterStatus)
|
||||
{
|
||||
this.waterStatus = waterStatus;
|
||||
}
|
||||
|
||||
public Integer getWaterStatus()
|
||||
{
|
||||
return waterStatus;
|
||||
}
|
||||
|
||||
public void setIsDelete(String isDelete)
|
||||
{
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
public String getIsDelete()
|
||||
{
|
||||
return isDelete;
|
||||
}
|
||||
// ================== toString ==================
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("deviceId", getDeviceId())
|
||||
.append("deptId", getDeptId())
|
||||
.append("topic", getTopic())
|
||||
.append("project", getProject())
|
||||
.append("warehouse", getWarehouse())
|
||||
.append("payload", getPayload())
|
||||
.append("dataJson", getDataJson())
|
||||
.append("battery", getBattery())
|
||||
.append("temperature", getTemperature())
|
||||
.append("humidity", getHumidity())
|
||||
.append("nh3", getNh3())
|
||||
.append("h2s", getH2s())
|
||||
.append("concentration", getConcentration())
|
||||
.append("waterStatus", getWaterStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("switchStatus", getSwitchStatus())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.shzg.framework.aspectj.lang.annotation.Excel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备事件对象 mqtt_sensor_event
|
||||
*
|
||||
@@ -22,8 +24,12 @@ public class MqttSensorEvent extends BaseEntity
|
||||
@Excel(name = "设备ID")
|
||||
private Long deviceId;
|
||||
|
||||
/** 部门ID(项目/仓库) */
|
||||
@Excel(name = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/** 事件类型(alarm / threshold / normal) */
|
||||
@Excel(name = "事件类型", readConverterExp = "a=larm,/=,t=hreshold,/=,n=ormal")
|
||||
@Excel(name = "事件类型", readConverterExp = "alarm=报警,threshold=阈值,normal=正常")
|
||||
private String eventType;
|
||||
|
||||
/** 事件描述 */
|
||||
@@ -31,7 +37,7 @@ public class MqttSensorEvent extends BaseEntity
|
||||
private String eventDesc;
|
||||
|
||||
/** 等级(LOW/MEDIUM/HIGH) */
|
||||
@Excel(name = "等级", readConverterExp = "L=OW/MEDIUM/HIGH")
|
||||
@Excel(name = "等级", readConverterExp = "LOW=低,MEDIUM=中,HIGH=高")
|
||||
private String level;
|
||||
|
||||
/** 处理状态(0未处理 1已处理) */
|
||||
@@ -42,6 +48,10 @@ public class MqttSensorEvent extends BaseEntity
|
||||
@Excel(name = "删除标识")
|
||||
private String isDelete;
|
||||
|
||||
private List<Long> deptIds;
|
||||
|
||||
// ==================== getter / setter ====================
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
@@ -62,6 +72,16 @@ public class MqttSensorEvent extends BaseEntity
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeptId(Long deptId)
|
||||
{
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public Long getDeptId()
|
||||
{
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType)
|
||||
{
|
||||
this.eventType = eventType;
|
||||
@@ -112,21 +132,32 @@ public class MqttSensorEvent extends BaseEntity
|
||||
return isDelete;
|
||||
}
|
||||
|
||||
public List<Long> getDeptIds() {
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
public void setDeptIds(List<Long> deptIds) {
|
||||
this.deptIds = deptIds;
|
||||
}
|
||||
|
||||
// ==================== toString ====================
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("deviceId", getDeviceId())
|
||||
.append("eventType", getEventType())
|
||||
.append("eventDesc", getEventDesc())
|
||||
.append("level", getLevel())
|
||||
.append("status", getStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
.append("id", getId())
|
||||
.append("deviceId", getDeviceId())
|
||||
.append("deptId", getDeptId())
|
||||
.append("eventType", getEventType())
|
||||
.append("eventDesc", getEventDesc())
|
||||
.append("level", getLevel())
|
||||
.append("status", getStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.shzg.project.worn.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import com.shzg.framework.web.domain.BaseEntity;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
@@ -21,11 +22,18 @@ public class MqttSensorThreshold extends BaseEntity
|
||||
private Long id;
|
||||
|
||||
/** 设备ID(关联 mqtt_sensor_device.id) */
|
||||
@Excel(name = "设备ID", readConverterExp = "关=联,m=qtt_sensor_device.id")
|
||||
@Excel(name = "设备ID")
|
||||
private Long deviceId;
|
||||
|
||||
/** 部门ID(项目/仓库) */
|
||||
@Excel(name = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/** 数据权限(部门ID集合,用于数据隔离) */
|
||||
private List<Long> deptIds;
|
||||
|
||||
/** 指标类型(temperature/humidity/nh3/h2s/battery) */
|
||||
@Excel(name = "指标类型", readConverterExp = "t=emperature/humidity/nh3/h2s/battery")
|
||||
@Excel(name = "指标类型")
|
||||
private String metricType;
|
||||
|
||||
/** 预警最小值 */
|
||||
@@ -45,135 +53,136 @@ public class MqttSensorThreshold extends BaseEntity
|
||||
private BigDecimal alarmMax;
|
||||
|
||||
/** 单位(℃/%/ppm) */
|
||||
@Excel(name = "单位", readConverterExp = "℃=/%/ppm")
|
||||
@Excel(name = "单位")
|
||||
private String unit;
|
||||
|
||||
/** 状态(1启用 0停用) */
|
||||
@Excel(name = "状态", readConverterExp = "1=启用,0=停用")
|
||||
@Excel(name = "状态")
|
||||
private String status;
|
||||
|
||||
/** 是否删除(0正常 1删除) */
|
||||
@Excel(name = "是否删除", readConverterExp = "0=正常,1=删除")
|
||||
@Excel(name = "是否删除")
|
||||
private String isDelete;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
// ==================== getter/setter ====================
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setDeviceId(Long deviceId)
|
||||
{
|
||||
this.deviceId = deviceId;
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getDeviceId()
|
||||
{
|
||||
public Long getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setMetricType(String metricType)
|
||||
{
|
||||
this.metricType = metricType;
|
||||
public void setDeviceId(Long deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getMetricType()
|
||||
{
|
||||
public Long getDeptId() {
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setDeptId(Long deptId) {
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public List<Long> getDeptIds() {
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
public void setDeptIds(List<Long> deptIds) {
|
||||
this.deptIds = deptIds;
|
||||
}
|
||||
|
||||
public String getMetricType() {
|
||||
return metricType;
|
||||
}
|
||||
|
||||
public void setWarnMin(BigDecimal warnMin)
|
||||
{
|
||||
this.warnMin = warnMin;
|
||||
public void setMetricType(String metricType) {
|
||||
this.metricType = metricType;
|
||||
}
|
||||
|
||||
public BigDecimal getWarnMin()
|
||||
{
|
||||
public BigDecimal getWarnMin() {
|
||||
return warnMin;
|
||||
}
|
||||
|
||||
public void setWarnMax(BigDecimal warnMax)
|
||||
{
|
||||
this.warnMax = warnMax;
|
||||
public void setWarnMin(BigDecimal warnMin) {
|
||||
this.warnMin = warnMin;
|
||||
}
|
||||
|
||||
public BigDecimal getWarnMax()
|
||||
{
|
||||
public BigDecimal getWarnMax() {
|
||||
return warnMax;
|
||||
}
|
||||
|
||||
public void setAlarmMin(BigDecimal alarmMin)
|
||||
{
|
||||
this.alarmMin = alarmMin;
|
||||
public void setWarnMax(BigDecimal warnMax) {
|
||||
this.warnMax = warnMax;
|
||||
}
|
||||
|
||||
public BigDecimal getAlarmMin()
|
||||
{
|
||||
public BigDecimal getAlarmMin() {
|
||||
return alarmMin;
|
||||
}
|
||||
|
||||
public void setAlarmMax(BigDecimal alarmMax)
|
||||
{
|
||||
this.alarmMax = alarmMax;
|
||||
public void setAlarmMin(BigDecimal alarmMin) {
|
||||
this.alarmMin = alarmMin;
|
||||
}
|
||||
|
||||
public BigDecimal getAlarmMax()
|
||||
{
|
||||
public BigDecimal getAlarmMax() {
|
||||
return alarmMax;
|
||||
}
|
||||
|
||||
public void setUnit(String unit)
|
||||
{
|
||||
this.unit = unit;
|
||||
public void setAlarmMax(BigDecimal alarmMax) {
|
||||
this.alarmMax = alarmMax;
|
||||
}
|
||||
|
||||
public String getUnit()
|
||||
{
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setIsDelete(String isDelete)
|
||||
{
|
||||
this.isDelete = isDelete;
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getIsDelete()
|
||||
{
|
||||
public String getIsDelete() {
|
||||
return isDelete;
|
||||
}
|
||||
|
||||
public void setIsDelete(String isDelete) {
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
// ==================== toString ====================
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("deviceId", getDeviceId())
|
||||
.append("metricType", getMetricType())
|
||||
.append("warnMin", getWarnMin())
|
||||
.append("warnMax", getWarnMax())
|
||||
.append("alarmMin", getAlarmMin())
|
||||
.append("alarmMax", getAlarmMax())
|
||||
.append("unit", getUnit())
|
||||
.append("status", getStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("deviceId", getDeviceId())
|
||||
.append("deptId", getDeptId())
|
||||
.append("metricType", getMetricType())
|
||||
.append("warnMin", getWarnMin())
|
||||
.append("warnMax", getWarnMax())
|
||||
.append("alarmMin", getAlarmMin())
|
||||
.append("alarmMax", getAlarmMax())
|
||||
.append("unit", getUnit())
|
||||
.append("status", getStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
164
src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java
Normal file
164
src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package com.shzg.project.worn.domain;
|
||||
|
||||
import com.shzg.framework.aspectj.lang.annotation.Excel;
|
||||
import com.shzg.framework.web.domain.BaseEntity;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MQTT主题配置对象 mqtt_topic_config
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
public class MqttTopicConfig extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
private Long id;
|
||||
|
||||
/** 项目编码(如 tangshan) */
|
||||
@Excel(name = "项目编码")
|
||||
private String project;
|
||||
|
||||
/** 仓库编码(如 dianchi) */
|
||||
@Excel(name = "仓库编码")
|
||||
private String warehouse;
|
||||
|
||||
/** 所属部门ID */
|
||||
@Excel(name = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/** 🔥 数据权限(部门ID集合) */
|
||||
private List<Long> deptIds;
|
||||
|
||||
/** 上行订阅Topic */
|
||||
@Excel(name = "上行Topic")
|
||||
private String topicUp;
|
||||
|
||||
/** 下行Topic前缀 */
|
||||
@Excel(name = "下行Topic前缀")
|
||||
private String topicDownPrefix;
|
||||
|
||||
/** 状态(0启用 1停用) */
|
||||
@Excel(name = "状态", readConverterExp = "0=启用,1=停用")
|
||||
private String status;
|
||||
|
||||
/** 删除标识(0正常 1删除) */
|
||||
@Excel(name = "删除标识", readConverterExp = "0=正常,1=删除")
|
||||
private String isDelete;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setProject(String project)
|
||||
{
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public String getProject()
|
||||
{
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setWarehouse(String warehouse)
|
||||
{
|
||||
this.warehouse = warehouse;
|
||||
}
|
||||
|
||||
public String getWarehouse()
|
||||
{
|
||||
return warehouse;
|
||||
}
|
||||
|
||||
public void setDeptId(Long deptId)
|
||||
{
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public Long getDeptId()
|
||||
{
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setDeptIds(List<Long> deptIds)
|
||||
{
|
||||
this.deptIds = deptIds;
|
||||
}
|
||||
|
||||
public List<Long> getDeptIds()
|
||||
{
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
public void setTopicUp(String topicUp)
|
||||
{
|
||||
this.topicUp = topicUp;
|
||||
}
|
||||
|
||||
public String getTopicUp()
|
||||
{
|
||||
return topicUp;
|
||||
}
|
||||
|
||||
public void setTopicDownPrefix(String topicDownPrefix)
|
||||
{
|
||||
this.topicDownPrefix = topicDownPrefix;
|
||||
}
|
||||
|
||||
public String getTopicDownPrefix()
|
||||
{
|
||||
return topicDownPrefix;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setIsDelete(String isDelete)
|
||||
{
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
public String getIsDelete()
|
||||
{
|
||||
return isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("project", getProject())
|
||||
.append("warehouse", getWarehouse())
|
||||
.append("deptId", getDeptId())
|
||||
.append("deptIds", getDeptIds())
|
||||
.append("topicUp", getTopicUp())
|
||||
.append("topicDownPrefix", getTopicDownPrefix())
|
||||
.append("status", getStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("isDelete", getIsDelete())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,7 @@ public class WornInboundItem extends BaseEntity
|
||||
private Long unitId;
|
||||
/** 单位名称 */
|
||||
private String unitName;
|
||||
private String operatorName;
|
||||
|
||||
/** 主单据状态 */
|
||||
private String billStatus;
|
||||
@@ -237,6 +238,15 @@ public class WornInboundItem extends BaseEntity
|
||||
{
|
||||
return unitName;
|
||||
}
|
||||
public String getOperatorName()
|
||||
{
|
||||
return operatorName;
|
||||
}
|
||||
|
||||
public void setOperatorName(String operatorName)
|
||||
{
|
||||
this.operatorName = operatorName;
|
||||
}
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.shzg.project.worn.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 入库单明细打印VO
|
||||
*/
|
||||
@Data
|
||||
public class InboundItemVO {
|
||||
|
||||
/** 物料名称 */
|
||||
private String materialName;
|
||||
|
||||
/** 单位 */
|
||||
private String unit;
|
||||
|
||||
/** 数量 */
|
||||
private BigDecimal quantity;
|
||||
|
||||
/** 唯一码 */
|
||||
private String uniqueCode;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.shzg.project.worn.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 入库单打印VO
|
||||
*/
|
||||
@Data
|
||||
public class InboundPrintVO {
|
||||
|
||||
/** 单据号 */
|
||||
private String billNo;
|
||||
|
||||
/** 仓库名称 */
|
||||
private String warehouseName;
|
||||
|
||||
/** 库区名称 */
|
||||
private String areaName;
|
||||
|
||||
/** 入库时间 */
|
||||
private Date inboundTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 明细列表 */
|
||||
private List<InboundItemVO> items;
|
||||
}
|
||||
@@ -66,4 +66,13 @@ public interface MqttSensorThresholdMapper
|
||||
* @return
|
||||
*/
|
||||
MqttSensorThreshold selectByDeviceAndMetric(Long deviceId, String metricType);
|
||||
|
||||
/**
|
||||
* 根据部门id和指标类型查询阈值配置
|
||||
* @param deptId
|
||||
* @param metricType
|
||||
* @return
|
||||
*/
|
||||
MqttSensorThreshold selectByDeptAndMetric(Long deptId, String metricType);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.shzg.project.worn.mapper;
|
||||
|
||||
import com.shzg.project.worn.domain.MqttTopicConfig;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MQTT主题配置Mapper接口
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
public interface MqttTopicConfigMapper
|
||||
{
|
||||
/**
|
||||
* 根据ID查询MQTT主题配置
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return MQTT主题配置
|
||||
*/
|
||||
public MqttTopicConfig selectMqttTopicConfigById(Long id);
|
||||
|
||||
/**
|
||||
* 查询MQTT主题配置列表
|
||||
*
|
||||
* @param mqttTopicConfig 查询条件
|
||||
* @return MQTT主题配置集合
|
||||
*/
|
||||
public List<MqttTopicConfig> selectMqttTopicConfigList(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 查询启用状态的MQTT主题配置
|
||||
*
|
||||
* @return MQTT主题配置集合
|
||||
*/
|
||||
public List<MqttTopicConfig> selectEnabledMqttTopicConfigList();
|
||||
|
||||
/**
|
||||
* 新增MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertMqttTopicConfig(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 修改MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateMqttTopicConfig(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 删除MQTT主题配置
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteMqttTopicConfigById(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除MQTT主题配置
|
||||
*
|
||||
* @param ids 主键ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteMqttTopicConfigByIds(Long[] ids);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.shzg.project.worn.sensor.config;
|
||||
|
||||
import com.shzg.project.worn.domain.MqttTopicConfig;
|
||||
import com.shzg.project.worn.sensor.mqtt.dispatcher.MqttMessageDispatcher;
|
||||
import com.shzg.project.worn.service.IMqttTopicConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
@@ -10,44 +12,81 @@ import org.springframework.context.annotation.Configuration;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.annotation.Resource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* MQTT 客户端初始化配置类
|
||||
* MQTT客户端配置类
|
||||
*
|
||||
* 作用:
|
||||
* 1. 初始化 MQTT 客户端连接
|
||||
* 2. 设置连接参数(心跳、自动重连等)
|
||||
* 3. 注册回调函数(消息接收、连接状态等)
|
||||
* 4. 自动订阅数据库中配置的 topic
|
||||
* 5. 应用关闭时释放资源
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class MqttClientConfig {
|
||||
|
||||
/**
|
||||
* MQTT客户端实例(全局唯一)
|
||||
*/
|
||||
private MqttClient mqttClient;
|
||||
|
||||
/**
|
||||
* MQTT消息分发器(核心组件)
|
||||
* 负责将不同topic的数据分发到对应Handler
|
||||
*/
|
||||
@Resource
|
||||
private MqttMessageDispatcher mqttMessageDispatcher;
|
||||
|
||||
/**
|
||||
* topic配置服务(从数据库读取订阅主题)
|
||||
*/
|
||||
@Resource
|
||||
private IMqttTopicConfigService mqttTopicConfigService;
|
||||
|
||||
/**
|
||||
* 初始化 MQTT 客户端 Bean
|
||||
*
|
||||
* @param props MQTT配置(来自配置文件)
|
||||
*/
|
||||
@Bean
|
||||
public MqttClient mqttClient(MqttProperties props) throws MqttException {
|
||||
|
||||
// ================== 1️⃣ 开关控制 ==================
|
||||
// 如果MQTT未启用,直接跳过初始化
|
||||
if (!props.isEnabled()) {
|
||||
log.warn("[MQTT] mqtt.enabled=false,MQTT 功能未启用");
|
||||
log.warn("[MQTT] mqtt.enabled=false, skip initialization");
|
||||
return null;
|
||||
}
|
||||
|
||||
// ✅ clientId 动态生成
|
||||
// ================== 2️⃣ 客户端ID生成 ==================
|
||||
// 使用随机UUID,避免多个服务clientId冲突
|
||||
String clientId = "worn-backend-" + UUID.randomUUID();
|
||||
|
||||
mqttClient = new MqttClient(
|
||||
props.getBroker(),
|
||||
clientId,
|
||||
new MemoryPersistence()
|
||||
);
|
||||
// 创建MQTT客户端(内存存储方式)
|
||||
mqttClient = new MqttClient(props.getBroker(), clientId, new MemoryPersistence());
|
||||
|
||||
// 连接参数
|
||||
// ================== 3️⃣ 连接参数配置 ==================
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
|
||||
// 是否清除会话(true:每次都是新会话)
|
||||
options.setCleanSession(props.isCleanSession());
|
||||
|
||||
// 心跳间隔(秒)
|
||||
options.setKeepAliveInterval(props.getKeepAlive());
|
||||
|
||||
// 连接超时时间(秒)
|
||||
options.setConnectionTimeout(props.getTimeout());
|
||||
|
||||
// 自动重连(非常重要)
|
||||
options.setAutomaticReconnect(true);
|
||||
|
||||
// 用户名密码(可选)
|
||||
if (props.getUsername() != null && !props.getUsername().trim().isEmpty()) {
|
||||
options.setUserName(props.getUsername().trim());
|
||||
}
|
||||
@@ -55,70 +94,125 @@ public class MqttClientConfig {
|
||||
options.setPassword(props.getPassword().toCharArray());
|
||||
}
|
||||
|
||||
// 回调
|
||||
// ================== 4️⃣ 回调函数 ==================
|
||||
mqttClient.setCallback(new MqttCallbackExtended() {
|
||||
|
||||
/**
|
||||
* 连接成功回调(首次连接 + 重连都会触发)
|
||||
*/
|
||||
@Override
|
||||
public void connectComplete(boolean reconnect, String serverURI) {
|
||||
log.info("[MQTT] 连接成功 reconnect={}, serverURI={}", reconnect, serverURI);
|
||||
log.info("[MQTT] connected reconnect={}, serverURI={}", reconnect, serverURI);
|
||||
|
||||
// 每次连接成功后重新订阅(防止重连丢订阅)
|
||||
subscribe(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接丢失回调
|
||||
*/
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
log.warn("[MQTT] 连接断开", cause);
|
||||
log.warn("[MQTT] connection lost", cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到消息回调(核心入口)
|
||||
*/
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) {
|
||||
|
||||
// 将byte[]转为字符串
|
||||
String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
|
||||
|
||||
log.info("🔥 收到MQTT消息 topic={} payload={}", topic, payload);
|
||||
log.info("[MQTT] message arrived topic={} payload={}", topic, payload);
|
||||
|
||||
// 分发给业务处理器(你写的Dispatcher)
|
||||
mqttMessageDispatcher.dispatch(topic, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息发送完成(发布消息时用)
|
||||
*/
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
// 当前项目未使用,可扩展日志或确认机制
|
||||
}
|
||||
});
|
||||
|
||||
// 连接
|
||||
log.info("[MQTT] 正在连接 Broker:{}", props.getBroker());
|
||||
// ================== 5️⃣ 建立连接 ==================
|
||||
log.info("[MQTT] connecting broker={}", props.getBroker());
|
||||
mqttClient.connect(options);
|
||||
log.info("[MQTT] MQTT 已连接");
|
||||
log.info("[MQTT] connected");
|
||||
|
||||
// 启动订阅
|
||||
// ================== 6️⃣ 订阅主题 ==================
|
||||
subscribe(props);
|
||||
|
||||
return mqttClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订阅主题(从数据库动态加载)
|
||||
*/
|
||||
private void subscribe(MqttProperties props) {
|
||||
|
||||
// 如果客户端未连接,直接跳过
|
||||
if (mqttClient == null || !mqttClient.isConnected()) {
|
||||
log.warn("[MQTT] 订阅失败:客户端未连接");
|
||||
log.warn("[MQTT] subscribe skipped because client is not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// ================== 1️⃣ 查询数据库配置 ==================
|
||||
List<MqttTopicConfig> topicConfigs =
|
||||
mqttTopicConfigService.selectEnabledMqttTopicConfigList();
|
||||
|
||||
mqttClient.subscribe("worn/tangshan/dianchi/up", props.getQos());
|
||||
// 使用Set去重,保证不会重复订阅
|
||||
Set<String> topics = new LinkedHashSet<>();
|
||||
|
||||
log.info("[MQTT] 已订阅 Topic:worn/tangshan/dianchi/up,QoS={}", props.getQos());
|
||||
for (MqttTopicConfig topicConfig : topicConfigs) {
|
||||
|
||||
// 只订阅上行topic(设备上报数据)
|
||||
if (topicConfig.getTopicUp() != null
|
||||
&& !topicConfig.getTopicUp().trim().isEmpty()) {
|
||||
|
||||
topics.add(topicConfig.getTopicUp().trim());
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 2️⃣ 空校验 ==================
|
||||
if (topics.isEmpty()) {
|
||||
log.warn("[MQTT] no enabled topic_up config found");
|
||||
return;
|
||||
}
|
||||
|
||||
// ================== 3️⃣ 执行订阅 ==================
|
||||
for (String topic : topics) {
|
||||
|
||||
mqttClient.subscribe(topic, props.getQos());
|
||||
|
||||
log.info("[MQTT] subscribed topic={} qos={}", topic, props.getQos());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MQTT] 订阅 Topic 失败", e);
|
||||
log.error("[MQTT] subscribe failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用关闭时释放MQTT资源
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
try {
|
||||
if (mqttClient != null) {
|
||||
|
||||
// 如果连接存在,先断开
|
||||
if (mqttClient.isConnected()) {
|
||||
mqttClient.disconnect();
|
||||
}
|
||||
|
||||
// 关闭客户端
|
||||
mqttClient.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
@@ -8,11 +8,10 @@ import org.springframework.stereotype.Component;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* MQTT 消息发布客户端
|
||||
* MQTT 消息发布客户端(最终版 - LoRaWAN专用)
|
||||
*
|
||||
* 支持:
|
||||
* 1. JSON消息发送
|
||||
* 2. HEX指令发送(设备控制)
|
||||
* 所有下行统一使用:
|
||||
* JSON + Base64
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -24,7 +23,7 @@ public class MqttPublishClient {
|
||||
this.mqttClient = mqttClient;
|
||||
}
|
||||
|
||||
// ================== JSON发送 ==================
|
||||
// ================== ⭐ 标准下行发送 ==================
|
||||
public void publish(String topic, String payload) {
|
||||
publish(topic, payload, 1, false);
|
||||
}
|
||||
@@ -39,38 +38,12 @@ public class MqttPublishClient {
|
||||
|
||||
mqttClient.publish(topic, msg);
|
||||
|
||||
log.info("[MQTT] JSON发送成功 topic={}, payload={}, qos={}, retained={}",
|
||||
log.info("[MQTT] 下行发送成功 topic={}, payload={}, qos={}, retained={}",
|
||||
topic, payload, qos, retained);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MQTT] JSON发送失败 topic=" + topic + ", payload=" + payload, e);
|
||||
throw new RuntimeException("MQTT JSON发送失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================== ⭐ HEX发送 ==================
|
||||
public void publishHex(String topic, String hexPayload) {
|
||||
publishHex(topic, hexPayload, 1, false);
|
||||
}
|
||||
|
||||
public void publishHex(String topic, String hexPayload, int qos, boolean retained) {
|
||||
checkClient();
|
||||
|
||||
try {
|
||||
byte[] bytes = hexStringToByteArray(hexPayload);
|
||||
|
||||
MqttMessage msg = new MqttMessage(bytes);
|
||||
msg.setQos(qos);
|
||||
msg.setRetained(retained);
|
||||
|
||||
mqttClient.publish(topic, msg);
|
||||
|
||||
log.info("[MQTT] HEX发送成功 topic={}, hex={}, length={}, qos={}, retained={}",
|
||||
topic, hexPayload, bytes.length, qos, retained);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MQTT] HEX发送失败 topic=" + topic + ", hex=" + hexPayload, e);
|
||||
throw new RuntimeException("MQTT HEX发送失败", e);
|
||||
log.error("[MQTT] 下行发送失败 topic=" + topic + ", payload=" + payload, e);
|
||||
throw new RuntimeException("MQTT发送失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,41 +56,4 @@ public class MqttPublishClient {
|
||||
throw new IllegalStateException("MQTT客户端未连接到Broker");
|
||||
}
|
||||
}
|
||||
|
||||
// ================== HEX工具 ==================
|
||||
/**
|
||||
* HEX字符串 → byte[]
|
||||
* 支持:
|
||||
* - 大小写
|
||||
* - 自动去空格
|
||||
*
|
||||
* 示例:
|
||||
* 2218000000C6 → [0x22,0x18,0x00,0x00,0x00,0xC6]
|
||||
*/
|
||||
private byte[] hexStringToByteArray(String hex) {
|
||||
|
||||
if (hex == null || hex.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("HEX字符串不能为空");
|
||||
}
|
||||
|
||||
// 去空格 + 转大写
|
||||
hex = hex.replace(" ", "").toUpperCase();
|
||||
|
||||
if (hex.length() % 2 != 0) {
|
||||
throw new IllegalArgumentException("HEX字符串长度必须为偶数:" + hex);
|
||||
}
|
||||
|
||||
int len = hex.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
|
||||
try {
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("HEX字符串解析失败:" + hex, e);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.shzg.project.worn.domain.MqttSensorDevice;
|
||||
import com.shzg.project.worn.sensor.mqtt.handler.EnvSensorHandler;
|
||||
import com.shzg.project.worn.sensor.mqtt.handler.SmokeSensorHandler;
|
||||
import com.shzg.project.worn.sensor.mqtt.handler.WaterSensorHandler;
|
||||
import com.shzg.project.worn.sensor.mqtt.handler.SmartSocketHandler;
|
||||
import com.shzg.project.worn.unit.MqttDeviceCache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -15,7 +16,7 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* MQTT消息分发器(支持烟雾 / 环境 / 水浸)
|
||||
* MQTT消息分发器(支持烟雾 / 环境 / 水浸 / 插座)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -30,10 +31,12 @@ public class MqttMessageDispatcher {
|
||||
@Autowired
|
||||
private EnvSensorHandler envSensorHandler;
|
||||
|
||||
// ✅ 新增:水浸处理器
|
||||
@Autowired
|
||||
private WaterSensorHandler waterSensorHandler;
|
||||
|
||||
@Autowired
|
||||
private SmartSocketHandler smartSocketHandler;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mqttExecutor")
|
||||
private Executor executor;
|
||||
@@ -72,7 +75,6 @@ public class MqttMessageDispatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
// 👉 统一小写(关键)
|
||||
devEui = devEui.toLowerCase();
|
||||
|
||||
// =========================
|
||||
@@ -110,12 +112,18 @@ public class MqttMessageDispatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
// 💧 水浸(新增)
|
||||
// 💧 水浸
|
||||
if (deviceType.contains("water")) {
|
||||
waterSensorHandler.handle(device, topic, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔌 智能排风插座
|
||||
if (deviceType.contains("socket")) {
|
||||
smartSocketHandler.handle(device, topic, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ 未识别
|
||||
log.warn("[MQTT] 未识别设备类型 deviceType={}", deviceType);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.shzg.project.worn.domain.MqttSensorThreshold;
|
||||
import com.shzg.project.worn.service.IMqttSensorDataService;
|
||||
import com.shzg.project.worn.service.IMqttSensorEventService;
|
||||
import com.shzg.project.worn.service.IMqttSensorThresholdService;
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -18,7 +19,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 综合环境传感器 Handler(最终生产版:防重复报警 + 状态机)
|
||||
* 综合环境传感器 Handler
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -33,18 +34,27 @@ public class EnvSensorHandler {
|
||||
@Autowired
|
||||
private IMqttSensorThresholdService thresholdService;
|
||||
|
||||
@Autowired
|
||||
private WebSocketSessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* 状态缓存(核心:防重复)
|
||||
* key = deviceId_metric
|
||||
* value = normal / warning / alarm
|
||||
* 状态缓存(防重复报警)
|
||||
*/
|
||||
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 主入口
|
||||
*/
|
||||
|
||||
public void handle(MqttSensorDevice device, String topic, String payload) {
|
||||
|
||||
if (device == null) {
|
||||
log.error("[ENV] device为空,忽略消息");
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getDeptId() == null) {
|
||||
log.error("[ENV] device未绑定deptId,数据隔离失效!deviceId={}", device.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[ENV] deviceId={}, topic={}, payload={}", device.getId(), topic, payload);
|
||||
|
||||
JSONObject json = parseJson(payload);
|
||||
@@ -55,14 +65,48 @@ public class EnvSensorHandler {
|
||||
|
||||
TopicInfo topicInfo = parseTopic(topic);
|
||||
|
||||
// 1️⃣ 数据入库
|
||||
// 1️⃣ 入库
|
||||
saveData(device, topic, payload, topicInfo, val);
|
||||
|
||||
// 2️⃣ 状态检测(核心)
|
||||
// 2️⃣ WebSocket推送
|
||||
pushWebSocket(device, val);
|
||||
|
||||
// 3️⃣ 事件检测
|
||||
handleEvent(device, val);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WebSocket推送
|
||||
*/
|
||||
private void pushWebSocket(MqttSensorDevice device, SensorValue v) {
|
||||
|
||||
try {
|
||||
JSONObject msg = new JSONObject();
|
||||
|
||||
msg.put("type", "env");
|
||||
msg.put("deviceId", device.getId());
|
||||
msg.put("deviceName", device.getDeviceName());
|
||||
msg.put("deptId", device.getDeptId());
|
||||
|
||||
msg.put("temperature", v.temperature);
|
||||
msg.put("humidity", v.humidity);
|
||||
msg.put("nh3", v.nh3);
|
||||
msg.put("h2s", v.h2s);
|
||||
msg.put("battery", v.battery);
|
||||
|
||||
msg.put("time", System.currentTimeMillis());
|
||||
|
||||
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ENV] WebSocket推送失败 deviceId={}", device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== 事件处理 ==================
|
||||
|
||||
private void handleEvent(MqttSensorDevice device, SensorValue v) {
|
||||
|
||||
check(device, "temperature", v.temperature, "温度");
|
||||
@@ -70,9 +114,13 @@ public class EnvSensorHandler {
|
||||
check(device, "nh3", v.nh3, "氨气");
|
||||
check(device, "h2s", v.h2s, "硫化氢");
|
||||
|
||||
// 电量(单独处理)
|
||||
// 电量
|
||||
if (v.battery != null) {
|
||||
MqttSensorThreshold t = thresholdService.getThreshold(device.getId(), "battery");
|
||||
MqttSensorThreshold t = thresholdService.getThreshold(
|
||||
device.getId(),
|
||||
device.getDeptId(),
|
||||
"battery"
|
||||
);
|
||||
if (t != null) {
|
||||
BigDecimal val = new BigDecimal(v.battery);
|
||||
String status = calcStatus(val, t);
|
||||
@@ -81,7 +129,7 @@ public class EnvSensorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 通用检测 ==================
|
||||
|
||||
private void check(MqttSensorDevice device,
|
||||
String metric,
|
||||
BigDecimal value,
|
||||
@@ -89,7 +137,12 @@ public class EnvSensorHandler {
|
||||
|
||||
if (value == null) return;
|
||||
|
||||
MqttSensorThreshold t = thresholdService.getThreshold(device.getId(), metric);
|
||||
MqttSensorThreshold t = thresholdService.getThreshold(
|
||||
device.getId(),
|
||||
device.getDeptId(),
|
||||
metric
|
||||
);
|
||||
|
||||
if (t == null) return;
|
||||
|
||||
String status = calcStatus(value, t);
|
||||
@@ -97,26 +150,18 @@ public class EnvSensorHandler {
|
||||
changeStatus(device, metric, status, name + ":" + value);
|
||||
}
|
||||
|
||||
// ================== 状态计算 ==================
|
||||
|
||||
private String calcStatus(BigDecimal value, MqttSensorThreshold t) {
|
||||
|
||||
if (t.getAlarmMax() != null && value.compareTo(t.getAlarmMax()) > 0) {
|
||||
return "alarm";
|
||||
}
|
||||
if (t.getAlarmMin() != null && value.compareTo(t.getAlarmMin()) < 0) {
|
||||
return "alarm";
|
||||
}
|
||||
if (t.getWarnMax() != null && value.compareTo(t.getWarnMax()) > 0) {
|
||||
return "warning";
|
||||
}
|
||||
if (t.getWarnMin() != null && value.compareTo(t.getWarnMin()) < 0) {
|
||||
return "warning";
|
||||
}
|
||||
if (t.getAlarmMax() != null && value.compareTo(t.getAlarmMax()) > 0) return "alarm";
|
||||
if (t.getAlarmMin() != null && value.compareTo(t.getAlarmMin()) < 0) return "alarm";
|
||||
if (t.getWarnMax() != null && value.compareTo(t.getWarnMax()) > 0) return "warning";
|
||||
if (t.getWarnMin() != null && value.compareTo(t.getWarnMin()) < 0) return "warning";
|
||||
|
||||
return "normal";
|
||||
}
|
||||
|
||||
// ================== 状态变更 ==================
|
||||
|
||||
private void changeStatus(MqttSensorDevice device,
|
||||
String metric,
|
||||
String newStatus,
|
||||
@@ -125,15 +170,10 @@ public class EnvSensorHandler {
|
||||
String key = device.getId() + "_" + metric;
|
||||
String oldStatus = STATUS_CACHE.get(key);
|
||||
|
||||
// ❗ 防重复:状态没变,不处理
|
||||
if (newStatus.equals(oldStatus)) {
|
||||
return;
|
||||
}
|
||||
if (newStatus.equals(oldStatus)) return;
|
||||
|
||||
// 更新缓存
|
||||
STATUS_CACHE.put(key, newStatus);
|
||||
|
||||
// 状态变化 → 记录事件
|
||||
if ("alarm".equals(newStatus)) {
|
||||
triggerEvent(device, "alarm", "HIGH", desc);
|
||||
} else if ("warning".equals(newStatus)) {
|
||||
@@ -143,7 +183,7 @@ public class EnvSensorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 事件记录 ==================
|
||||
|
||||
private void triggerEvent(MqttSensorDevice device,
|
||||
String type,
|
||||
String level,
|
||||
@@ -153,7 +193,10 @@ public class EnvSensorHandler {
|
||||
device.getId(), type, level, desc);
|
||||
|
||||
MqttSensorEvent event = new MqttSensorEvent();
|
||||
|
||||
event.setDeviceId(device.getId());
|
||||
event.setDeptId(device.getDeptId());
|
||||
|
||||
event.setEventType(type);
|
||||
event.setLevel(level);
|
||||
event.setEventDesc(desc);
|
||||
@@ -162,13 +205,11 @@ public class EnvSensorHandler {
|
||||
event.setCreateTime(new Date());
|
||||
|
||||
eventService.insertMqttSensorEvent(event);
|
||||
|
||||
// TODO: D2D联动(风机/排风)
|
||||
// TODO: 短信通知
|
||||
// TODO: APP推送
|
||||
}
|
||||
|
||||
// ================== 数据入库 ==================
|
||||
|
||||
// ================== 入库 ==================
|
||||
|
||||
private void saveData(MqttSensorDevice device,
|
||||
String topic,
|
||||
String payload,
|
||||
@@ -178,6 +219,8 @@ public class EnvSensorHandler {
|
||||
MqttSensorData data = new MqttSensorData();
|
||||
|
||||
data.setDeviceId(device.getId());
|
||||
data.setDeptId(device.getDeptId());
|
||||
|
||||
data.setTopic(topic);
|
||||
data.setProject(topicInfo.project);
|
||||
data.setWarehouse(topicInfo.warehouse);
|
||||
@@ -197,7 +240,9 @@ public class EnvSensorHandler {
|
||||
sensorDataService.insertMqttSensorData(data);
|
||||
}
|
||||
|
||||
// ================== JSON解析 ==================
|
||||
|
||||
// ================== 工具 ==================
|
||||
|
||||
private JSONObject parseJson(String payload) {
|
||||
try {
|
||||
return JSONObject.parseObject(payload);
|
||||
@@ -207,7 +252,6 @@ public class EnvSensorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 构建数据 ==================
|
||||
private SensorValue buildValue(JSONObject json) {
|
||||
SensorValue v = new SensorValue();
|
||||
v.temperature = json.getBigDecimal("temperature");
|
||||
@@ -218,7 +262,6 @@ public class EnvSensorHandler {
|
||||
return v;
|
||||
}
|
||||
|
||||
// ================== topic解析 ==================
|
||||
private TopicInfo parseTopic(String topic) {
|
||||
TopicInfo info = new TopicInfo();
|
||||
try {
|
||||
@@ -231,7 +274,7 @@ public class EnvSensorHandler {
|
||||
return info;
|
||||
}
|
||||
|
||||
// ================== 内部结构 ==================
|
||||
|
||||
private static class SensorValue {
|
||||
BigDecimal temperature;
|
||||
BigDecimal humidity;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.shzg.project.worn.sensor.mqtt.handler;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.shzg.project.worn.domain.MqttSensorData;
|
||||
import com.shzg.project.worn.domain.MqttSensorDevice;
|
||||
import com.shzg.project.worn.domain.MqttSensorEvent;
|
||||
import com.shzg.project.worn.service.IMqttSensorDataService;
|
||||
import com.shzg.project.worn.service.IMqttSensorEventService;
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 智能插座(排风)Handler
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SmartSocketHandler {
|
||||
|
||||
@Autowired
|
||||
private IMqttSensorDataService dataService;
|
||||
|
||||
@Autowired
|
||||
private IMqttSensorEventService eventService;
|
||||
|
||||
@Autowired
|
||||
private WebSocketSessionManager sessionManager;
|
||||
|
||||
public void handle(MqttSensorDevice device, String topic, String payload) {
|
||||
|
||||
if (device == null) {
|
||||
log.error("[SOCKET] device为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getDeptId() == null) {
|
||||
log.error("[SOCKET] device未绑定deptId!");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[SOCKET] deviceId={}, topic={}, payload={}",
|
||||
device.getId(), topic, payload);
|
||||
|
||||
JSONObject json;
|
||||
try {
|
||||
json = JSONObject.parseObject(payload);
|
||||
} catch (Exception e) {
|
||||
log.error("[SOCKET] JSON解析失败 payload={}", payload, e);
|
||||
return;
|
||||
}
|
||||
|
||||
Integer status = json.getInteger("socket_status");
|
||||
if (status == null) {
|
||||
log.warn("[SOCKET] 未包含socket_status");
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容两种协议:
|
||||
// 旧:16=关,17=开
|
||||
// 新:0=关,1=开
|
||||
Integer switchStatus;
|
||||
if (status == 17 || status == 1) {
|
||||
switchStatus = 1;
|
||||
} else if (status == 16 || status == 0) {
|
||||
switchStatus = 0;
|
||||
} else {
|
||||
log.warn("[SOCKET] 未识别的socket_status={}, payload={}", status, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
MqttSensorData data = new MqttSensorData();
|
||||
data.setDeviceId(device.getId());
|
||||
data.setDeptId(device.getDeptId());
|
||||
data.setTopic(topic);
|
||||
data.setPayload(payload);
|
||||
data.setDataJson(payload);
|
||||
data.setSwitchStatus(switchStatus);
|
||||
data.setCreateTime(new Date());
|
||||
data.setIsDelete("0");
|
||||
|
||||
dataService.insertMqttSensorData(data);
|
||||
|
||||
try {
|
||||
JSONObject msg = new JSONObject();
|
||||
msg.put("type", "socket");
|
||||
msg.put("deviceId", device.getId());
|
||||
msg.put("deviceName", device.getDeviceName());
|
||||
msg.put("deptId", device.getDeptId());
|
||||
msg.put("status", switchStatus);
|
||||
msg.put("statusDesc", switchStatus == 1 ? "通电" : "断电");
|
||||
msg.put("time", System.currentTimeMillis());
|
||||
|
||||
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
|
||||
} catch (Exception e) {
|
||||
log.error("[SOCKET] WebSocket推送失败", e);
|
||||
}
|
||||
|
||||
MqttSensorEvent event = new MqttSensorEvent();
|
||||
event.setDeviceId(device.getId());
|
||||
event.setDeptId(device.getDeptId());
|
||||
event.setEventType(switchStatus == 1 ? "socket_on" : "socket_off");
|
||||
event.setEventDesc(switchStatus == 1 ? "插座开启(通电)" : "插座关闭(断电)");
|
||||
event.setLevel("LOW");
|
||||
event.setStatus("0");
|
||||
event.setIsDelete("0");
|
||||
event.setCreateTime(new Date());
|
||||
|
||||
eventService.insertMqttSensorEvent(event);
|
||||
|
||||
log.info("[SOCKET] 原始状态={}, 转换状态={}, 描述={}, 已入库+已推送",
|
||||
status,
|
||||
switchStatus,
|
||||
switchStatus == 1 ? "通电" : "断电");
|
||||
}
|
||||
}
|
||||
@@ -6,63 +6,88 @@ import com.shzg.project.worn.domain.MqttSensorDevice;
|
||||
import com.shzg.project.worn.domain.MqttSensorEvent;
|
||||
import com.shzg.project.worn.service.IMqttSensorDataService;
|
||||
import com.shzg.project.worn.service.IMqttSensorEventService;
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 烟雾传感器 Handler
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SmokeSensorHandler {
|
||||
|
||||
/**
|
||||
* 状态缓存(防重复事件)
|
||||
*/
|
||||
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private IMqttSensorDataService dataService;
|
||||
|
||||
@Autowired
|
||||
private IMqttSensorEventService eventService;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Autowired
|
||||
private WebSocketSessionManager sessionManager;
|
||||
|
||||
|
||||
public void handle(MqttSensorDevice device, String topic, String payload) {
|
||||
|
||||
// ================= 安全校验 =================
|
||||
if (device == null) {
|
||||
log.error("[SMOKE] device为空,忽略消息");
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getDeptId() == null) {
|
||||
log.error("[SMOKE] device未绑定deptId,数据隔离失效!deviceId={}", device.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[SMOKE] deviceId={}, deviceName={}, topic={}, payload={}",
|
||||
device.getId(), device.getDeviceName(), topic, payload);
|
||||
|
||||
// ================== 1. JSON解析 ==================
|
||||
JSONObject json;
|
||||
|
||||
try {
|
||||
json = JSONObject.parseObject(payload);
|
||||
} catch (Exception e) {
|
||||
log.error("[SMOKE] JSON解析失败 payload={}", payload, e);
|
||||
log.error("[SMOKE] parse payload failed payload={}", payload, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// ================== topic解析 ==================
|
||||
// ================== 2. topic解析 ==================
|
||||
String project = null;
|
||||
String warehouse = null;
|
||||
|
||||
try {
|
||||
String[] arr = topic.split("/");
|
||||
if (arr.length >= 3) {
|
||||
project = arr[1]; // tangshan
|
||||
warehouse = arr[2]; // dianchi
|
||||
project = arr[1];
|
||||
warehouse = arr[2];
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[SMOKE] topic解析失败 topic={}", topic);
|
||||
log.warn("[SMOKE] parse topic failed topic={}", topic);
|
||||
}
|
||||
|
||||
// ================== 字段 ==================
|
||||
// ================== 3. 业务字段 ==================
|
||||
String event = json.getString("event");
|
||||
Integer battery = json.getInteger("battery");
|
||||
Integer concentration = json.getInteger("concentration");
|
||||
Integer temperature = json.getInteger("temperature");
|
||||
|
||||
// ================== 入库 ==================
|
||||
// ================== 4. 数据落库 ==================
|
||||
MqttSensorData data = new MqttSensorData();
|
||||
|
||||
data.setDeviceId(device.getId());
|
||||
|
||||
data.setDeptId(device.getDeptId());
|
||||
|
||||
data.setTopic(topic);
|
||||
data.setProject(project);
|
||||
data.setWarehouse(warehouse);
|
||||
@@ -74,50 +99,110 @@ public class SmokeSensorHandler {
|
||||
data.setConcentration(concentration != null ? concentration.longValue() : null);
|
||||
data.setTemperature(temperature != null ? new BigDecimal(temperature) : null);
|
||||
|
||||
data.setCreateTime(new Date());
|
||||
data.setIsDelete("0");
|
||||
|
||||
dataService.insertMqttSensorData(data);
|
||||
|
||||
// ================== 事件 ==================
|
||||
// ================== 5. WebSocket推送 ==================
|
||||
pushWebSocket(device, event, battery, concentration, temperature);
|
||||
|
||||
// ================== 6. 事件逻辑 ==================
|
||||
if ("silent".equals(event)) {
|
||||
insertEvent(device, "silent", "人为静音报警", "LOW");
|
||||
changeStatus(device, "silent", "silent", "烟雾已消音", "LOW");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("alarm".equals(event) || (concentration != null && concentration > 0)) {
|
||||
insertEvent(device, "alarm", "烟雾报警", "HIGH");
|
||||
changeStatus(device, "alarm", "alarm", "烟雾报警", "HIGH");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("removed".equals(event)) {
|
||||
insertEvent(device, "removed", "设备被拆/离位", "MEDIUM");
|
||||
changeStatus(device, "removed", "removed", "设备被拆除", "MEDIUM");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("low_battery".equals(event)) {
|
||||
insertEvent(device, "low_battery", "低电量告警", "LOW");
|
||||
changeStatus(device, "low_battery", "low_battery", "电量低", "LOW");
|
||||
return;
|
||||
}
|
||||
|
||||
if (battery != null && battery < 20) {
|
||||
log.info("[SMOKE] 电量偏低 deviceId={}, battery={}",
|
||||
device.getId(), battery);
|
||||
}
|
||||
changeStatus(device, "normal", "recovery", "烟雾恢复正常", "LOW");
|
||||
|
||||
log.info("[SMOKE] 正常数据 deviceId={}, battery={}, temp={}",
|
||||
log.info("[SMOKE] normal data deviceId={}, battery={}, temp={}",
|
||||
device.getId(), battery, temperature);
|
||||
}
|
||||
|
||||
private void insertEvent(MqttSensorDevice device, String type, String desc, String level) {
|
||||
|
||||
/**
|
||||
* WebSocket推送(按dept隔离)
|
||||
*/
|
||||
private void pushWebSocket(MqttSensorDevice device,
|
||||
String event,
|
||||
Integer battery,
|
||||
Integer concentration,
|
||||
Integer temperature) {
|
||||
|
||||
try {
|
||||
JSONObject msg = new JSONObject();
|
||||
msg.put("type", "smoke");
|
||||
msg.put("deviceId", device.getId());
|
||||
msg.put("deviceName", device.getDeviceName());
|
||||
msg.put("deptId", device.getDeptId());
|
||||
|
||||
msg.put("event", event);
|
||||
msg.put("battery", battery);
|
||||
msg.put("concentration", concentration);
|
||||
msg.put("temperature", temperature);
|
||||
|
||||
msg.put("time", System.currentTimeMillis());
|
||||
|
||||
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[WebSocket] 推送失败 deviceId={}", device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void changeStatus(MqttSensorDevice device,
|
||||
String newStatus,
|
||||
String eventType,
|
||||
String desc,
|
||||
String level) {
|
||||
|
||||
String key = device.getId() + "_smoke";
|
||||
String oldStatus = STATUS_CACHE.get(key);
|
||||
|
||||
if (newStatus.equals(oldStatus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
STATUS_CACHE.put(key, newStatus);
|
||||
insertEvent(device, eventType, desc, level);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 事件入库(已补 deptId)
|
||||
*/
|
||||
private void insertEvent(MqttSensorDevice device,
|
||||
String type,
|
||||
String desc,
|
||||
String level) {
|
||||
|
||||
MqttSensorEvent event = new MqttSensorEvent();
|
||||
|
||||
event.setDeviceId(device.getId());
|
||||
|
||||
event.setDeptId(device.getDeptId());
|
||||
|
||||
event.setEventType(type);
|
||||
event.setEventDesc(desc);
|
||||
event.setLevel(level);
|
||||
event.setStatus("0");
|
||||
event.setIsDelete("0");
|
||||
event.setCreateTime(new Date());
|
||||
|
||||
eventService.insertMqttSensorEvent(event);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,16 @@ import com.shzg.project.worn.domain.MqttSensorData;
|
||||
import com.shzg.project.worn.domain.MqttSensorEvent;
|
||||
import com.shzg.project.worn.service.IMqttSensorDataService;
|
||||
import com.shzg.project.worn.service.IMqttSensorEventService;
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 水浸传感器 Handler(对齐 Env 结构:状态机 + 防重复)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WaterSensorHandler {
|
||||
@@ -27,86 +26,119 @@ public class WaterSensorHandler {
|
||||
@Autowired
|
||||
private IMqttSensorEventService eventService;
|
||||
|
||||
/**
|
||||
* 状态缓存(防重复报警)
|
||||
* key = deviceId_water
|
||||
*/
|
||||
@Autowired
|
||||
private WebSocketSessionManager sessionManager;
|
||||
|
||||
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 主入口
|
||||
*/
|
||||
|
||||
public void handle(MqttSensorDevice device, String topic, String payload) {
|
||||
|
||||
// ================= 安全校验 =================
|
||||
if (device == null) {
|
||||
log.error("[WATER] device为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getDeptId() == null) {
|
||||
log.error("[WATER] device未绑定deptId!deviceId={}", device.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[WATER] deviceId={}, topic={}, payload={}", device.getId(), topic, payload);
|
||||
|
||||
JSONObject json = parseJson(payload);
|
||||
if (json == null) return;
|
||||
|
||||
// 👉 从 data 字段解析(Base64)
|
||||
Integer water = parseWater(json);
|
||||
if (water == null) {
|
||||
log.warn("[WATER] 无法解析水浸状态");
|
||||
WaterInfo info = parsePayload(json);
|
||||
if (info == null) {
|
||||
log.warn("[WATER] payload解析失败");
|
||||
return;
|
||||
}
|
||||
|
||||
TopicInfo topicInfo = parseTopic(topic);
|
||||
|
||||
// 1️⃣ 数据入库
|
||||
saveData(device, topic, payload, topicInfo, water);
|
||||
// ================== 1. 入库 ==================
|
||||
saveData(device, topic, payload, topicInfo, info);
|
||||
|
||||
// 2️⃣ 状态处理
|
||||
handleEvent(device, water);
|
||||
// ================== 2. 普通数据推送 ==================
|
||||
pushWebSocket(device, info);
|
||||
|
||||
// ================== 3. 事件处理 ==================
|
||||
handleEvent(device, info.getWater());
|
||||
}
|
||||
|
||||
|
||||
// ================== WebSocket推送 ==================
|
||||
private void pushWebSocket(MqttSensorDevice device, WaterInfo info) {
|
||||
|
||||
try {
|
||||
JSONObject msg = new JSONObject();
|
||||
msg.put("type", "water");
|
||||
msg.put("deviceId", device.getId());
|
||||
msg.put("deviceName", device.getDeviceName());
|
||||
msg.put("deptId", device.getDeptId());
|
||||
|
||||
msg.put("battery", info.getBattery());
|
||||
msg.put("water", info.getWater());
|
||||
|
||||
msg.put("status", (info.getWater() != null && info.getWater() == 1) ? "alarm" : "normal");
|
||||
msg.put("time", System.currentTimeMillis());
|
||||
|
||||
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[WATER] WebSocket推送失败 deviceId={}", device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== 事件处理 ==================
|
||||
private void handleEvent(MqttSensorDevice device, Integer water) {
|
||||
|
||||
String status;
|
||||
if (water == null) return;
|
||||
|
||||
// 👉 水浸:1=有水(报警),0=正常
|
||||
if (water == 1) {
|
||||
status = "alarm";
|
||||
} else {
|
||||
status = "normal";
|
||||
}
|
||||
|
||||
changeStatus(device, status, water);
|
||||
String status = (water == 1) ? "alarm" : "normal";
|
||||
changeStatus(device, status);
|
||||
}
|
||||
|
||||
// ================== 状态变更 ==================
|
||||
private void changeStatus(MqttSensorDevice device,
|
||||
String newStatus,
|
||||
Integer water) {
|
||||
|
||||
private void changeStatus(MqttSensorDevice device, String newStatus) {
|
||||
|
||||
String key = device.getId() + "_water";
|
||||
String oldStatus = STATUS_CACHE.get(key);
|
||||
|
||||
// ❗ 防重复
|
||||
if (newStatus.equals(oldStatus)) {
|
||||
return;
|
||||
}
|
||||
if (newStatus.equals(oldStatus)) return;
|
||||
|
||||
STATUS_CACHE.put(key, newStatus);
|
||||
|
||||
if ("alarm".equals(newStatus)) {
|
||||
triggerEvent(device, "alarm", "HIGH", "浸水预警!");
|
||||
} else if ("normal".equals(newStatus)) {
|
||||
triggerEvent(device, "recovery", "LOW", "水浸正常");
|
||||
} else {
|
||||
triggerEvent(device, "recovery", "LOW", "水浸恢复正常");
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 事件记录 ==================
|
||||
|
||||
/**
|
||||
* 🔥 事件写入 + 报警推送
|
||||
*/
|
||||
private void triggerEvent(MqttSensorDevice device,
|
||||
String type,
|
||||
String level,
|
||||
String desc) {
|
||||
|
||||
log.warn("[WATER事件] deviceId={}, type={}, level={}, desc={}",
|
||||
device.getId(), type, level, desc);
|
||||
log.warn("[WATER事件] deviceId={}, type={}, desc={}",
|
||||
device.getId(), type, desc);
|
||||
|
||||
// ================== 1. 写库 ==================
|
||||
MqttSensorEvent event = new MqttSensorEvent();
|
||||
|
||||
event.setDeviceId(device.getId());
|
||||
|
||||
// ✅ 核心(必须)
|
||||
event.setDeptId(device.getDeptId());
|
||||
|
||||
event.setEventType(type);
|
||||
event.setLevel(level);
|
||||
event.setEventDesc(desc);
|
||||
@@ -116,27 +148,51 @@ public class WaterSensorHandler {
|
||||
|
||||
eventService.insertMqttSensorEvent(event);
|
||||
|
||||
// TODO: 联动排水 / 告警推送
|
||||
// ================== 2. 报警推送 ==================
|
||||
try {
|
||||
JSONObject msg = new JSONObject();
|
||||
|
||||
msg.put("type", "alarm"); // 🔥关键
|
||||
msg.put("deviceId", device.getId());
|
||||
msg.put("deviceName", device.getDeviceName());
|
||||
msg.put("deptId", device.getDeptId());
|
||||
|
||||
msg.put("eventType", type);
|
||||
msg.put("level", level);
|
||||
msg.put("eventDesc", desc);
|
||||
|
||||
msg.put("time", System.currentTimeMillis());
|
||||
|
||||
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[WATER] 报警推送失败 deviceId={}", device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 数据入库 ==================
|
||||
|
||||
// ================== 入库 ==================
|
||||
private void saveData(MqttSensorDevice device,
|
||||
String topic,
|
||||
String payload,
|
||||
TopicInfo topicInfo,
|
||||
Integer water) {
|
||||
WaterInfo info) {
|
||||
|
||||
MqttSensorData data = new MqttSensorData();
|
||||
|
||||
data.setDeviceId(device.getId());
|
||||
|
||||
// ✅ 数据隔离
|
||||
data.setDeptId(device.getDeptId());
|
||||
|
||||
data.setTopic(topic);
|
||||
data.setProject(topicInfo.project);
|
||||
data.setWarehouse(topicInfo.warehouse);
|
||||
|
||||
data.setPayload(payload);
|
||||
data.setDataJson(payload);
|
||||
|
||||
data.setWaterStatus(water);
|
||||
data.setWaterStatus(info.getWater());
|
||||
data.setBattery(info.getBattery() == null ? null : info.getBattery().longValue());
|
||||
|
||||
data.setCreateTime(new Date());
|
||||
data.setIsDelete("0");
|
||||
@@ -144,7 +200,8 @@ public class WaterSensorHandler {
|
||||
sensorDataService.insertMqttSensorData(data);
|
||||
}
|
||||
|
||||
// ================== JSON解析 ==================
|
||||
|
||||
// ================== JSON ==================
|
||||
private JSONObject parseJson(String payload) {
|
||||
try {
|
||||
return JSONObject.parseObject(payload);
|
||||
@@ -154,46 +211,8 @@ public class WaterSensorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 解析水浸 ==================
|
||||
private Integer parseWater(JSONObject json) {
|
||||
|
||||
try {
|
||||
// 👉 LoRaWAN 原始数据(Base64)
|
||||
String data = json.getString("data");
|
||||
if (data == null) return null;
|
||||
|
||||
byte[] bytes = java.util.Base64.getDecoder().decode(data);
|
||||
|
||||
/**
|
||||
* Milesight水浸协议(关键)
|
||||
* 一般结构:
|
||||
* [channel_id][channel_type][value]
|
||||
*
|
||||
* 水浸:
|
||||
* channel_id = 0x05
|
||||
* channel_type = 0x00
|
||||
* value = 0/1
|
||||
*/
|
||||
|
||||
for (int i = 0; i < bytes.length - 2; i++) {
|
||||
|
||||
int channelId = bytes[i] & 0xFF;
|
||||
int channelType = bytes[i + 1] & 0xFF;
|
||||
|
||||
// 👉 水浸
|
||||
if (channelId == 0x05 && channelType == 0x00) {
|
||||
return bytes[i + 2] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[WATER] 解析payload失败", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ================== topic解析 ==================
|
||||
// ================== topic ==================
|
||||
private TopicInfo parseTopic(String topic) {
|
||||
TopicInfo info = new TopicInfo();
|
||||
try {
|
||||
@@ -206,9 +225,61 @@ public class WaterSensorHandler {
|
||||
return info;
|
||||
}
|
||||
|
||||
// ================== 内部结构 ==================
|
||||
|
||||
// ================== payload解析 ==================
|
||||
private WaterInfo parsePayload(JSONObject json) {
|
||||
|
||||
try {
|
||||
String data = json.getString("data");
|
||||
if (data == null) return null;
|
||||
|
||||
byte[] bytes = Base64.getDecoder().decode(data);
|
||||
|
||||
WaterInfo result = new WaterInfo();
|
||||
|
||||
for (int i = 0; i < bytes.length - 2; ) {
|
||||
|
||||
int channelId = bytes[i] & 0xFF;
|
||||
int channelType = bytes[i + 1] & 0xFF;
|
||||
|
||||
if (channelId == 0x01 && channelType == 0x75) {
|
||||
result.setBattery(bytes[i + 2] & 0xFF);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (channelId == 0x05 && channelType == 0x00) {
|
||||
result.setWater(bytes[i + 2] & 0xFF);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[WATER] 解析payload失败", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class TopicInfo {
|
||||
String project;
|
||||
String warehouse;
|
||||
}
|
||||
|
||||
private static class WaterInfo {
|
||||
private Integer battery;
|
||||
private Integer water;
|
||||
|
||||
public Integer getBattery() { return battery; }
|
||||
public void setBattery(Integer battery) { this.battery = battery; }
|
||||
|
||||
public Integer getWater() { return water; }
|
||||
public void setWater(Integer water) { this.water = water; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.shzg.project.worn.service;
|
||||
|
||||
public interface IDeviceCommandService {
|
||||
|
||||
void smokeStop(Long deviceId);
|
||||
|
||||
void smokeStopOnce(Long deviceId);
|
||||
}
|
||||
@@ -59,11 +59,14 @@ public interface IMqttSensorThresholdService
|
||||
*/
|
||||
public int deleteMqttSensorThresholdById(Long id);
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备ID和指标类型获取阈值
|
||||
* @param deviceId
|
||||
* @param metricType
|
||||
* 根据设备ID和部门ID获取阈值
|
||||
* @param id
|
||||
* @param deptId
|
||||
* @param battery
|
||||
* @return
|
||||
*/
|
||||
MqttSensorThreshold getThreshold(Long deviceId, String metricType);
|
||||
MqttSensorThreshold getThreshold(Long id, Long deptId, String battery);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.shzg.project.worn.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.shzg.project.worn.domain.MqttTopicConfig;
|
||||
|
||||
/**
|
||||
* MQTT主题配置Service接口
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
public interface IMqttTopicConfigService
|
||||
{
|
||||
/**
|
||||
* 查询MQTT主题配置
|
||||
*
|
||||
* @param id MQTT主题配置主键
|
||||
* @return MQTT主题配置
|
||||
*/
|
||||
public MqttTopicConfig selectMqttTopicConfigById(Long id);
|
||||
|
||||
/**
|
||||
* 查询MQTT主题配置列表
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return MQTT主题配置集合
|
||||
*/
|
||||
public List<MqttTopicConfig> selectMqttTopicConfigList(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 查询所有启用的 MQTT 主题配置
|
||||
*
|
||||
* @return MQTT 主题配置集合
|
||||
*/
|
||||
public List<MqttTopicConfig> selectEnabledMqttTopicConfigList();
|
||||
|
||||
/**
|
||||
* 新增MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertMqttTopicConfig(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 修改MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateMqttTopicConfig(MqttTopicConfig mqttTopicConfig);
|
||||
|
||||
/**
|
||||
* 批量删除MQTT主题配置
|
||||
*
|
||||
* @param ids 需要删除的MQTT主题配置主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteMqttTopicConfigByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 删除MQTT主题配置信息
|
||||
*
|
||||
* @param id MQTT主题配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteMqttTopicConfigById(Long id);
|
||||
}
|
||||
@@ -74,4 +74,9 @@ public interface IWornInboundBillService
|
||||
* @return 结果
|
||||
*/
|
||||
int voidBill(String billNo);
|
||||
|
||||
/**
|
||||
* 生成入库单PDF
|
||||
*/
|
||||
byte[] generatePdf(Long id) throws Exception;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.shzg.project.worn.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.shzg.project.worn.sensor.config.MqttPublishClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SocketControlService {
|
||||
|
||||
@Autowired
|
||||
private MqttPublishClient mqttPublishClient;
|
||||
|
||||
public void controlSocket(String devEui, boolean on) {
|
||||
|
||||
String topic = "worn/tangshan/dianchi/downlink/" + devEui.toLowerCase();
|
||||
|
||||
String base64 = on ? "CAEA/w==" : "CAAA/w==";
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("confirmed", true);
|
||||
json.put("fport", 85);
|
||||
json.put("data", base64);
|
||||
|
||||
mqttPublishClient.publish(topic, json.toJSONString());
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.shzg.project.worn.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.shzg.project.worn.domain.MqttSensorCommand;
|
||||
import com.shzg.project.worn.domain.MqttSensorDevice;
|
||||
import com.shzg.project.worn.mapper.MqttSensorDeviceMapper;
|
||||
import com.shzg.project.worn.sensor.config.MqttPublishClient;
|
||||
import com.shzg.project.worn.service.IDeviceCommandService;
|
||||
import com.shzg.project.worn.service.IMqttSensorCommandService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
||||
|
||||
@Autowired
|
||||
private MqttPublishClient mqttPublishClient;
|
||||
|
||||
@Autowired
|
||||
private MqttSensorDeviceMapper deviceMapper;
|
||||
|
||||
@Autowired
|
||||
private IMqttSensorCommandService commandService;
|
||||
|
||||
// ================== Topic ==================
|
||||
|
||||
private String buildDownTopic(String devEui) {
|
||||
return "worn/tangshan/dianchi/downlink/" + devEui;
|
||||
}
|
||||
|
||||
// ================== 指令 ==================
|
||||
|
||||
@Override
|
||||
public void smokeStop(Long deviceId) {
|
||||
|
||||
MqttSensorDevice device = getDevice(deviceId);
|
||||
|
||||
String topic = buildDownTopic(device.getDevEui());
|
||||
String base64 = hexToBase64("2219000000C5");
|
||||
|
||||
String payload = buildPayload(base64);
|
||||
|
||||
executeCommand(deviceId, topic, "smokeStop", payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smokeStopOnce(Long deviceId) {
|
||||
|
||||
MqttSensorDevice device = getDevice(deviceId);
|
||||
|
||||
String topic = buildDownTopic(device.getDevEui());
|
||||
String base64 = hexToBase64("2218000000C6");
|
||||
|
||||
String payload = buildPayload(base64);
|
||||
|
||||
executeCommand(deviceId, topic, "smokeStopOnce", payload);
|
||||
}
|
||||
|
||||
// ================== 指令执行 ==================
|
||||
|
||||
private void executeCommand(Long deviceId, String topic, String command, String payload) {
|
||||
|
||||
MqttSensorCommand cmd = new MqttSensorCommand();
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setTopic(topic);
|
||||
cmd.setCommand(command);
|
||||
cmd.setPayload(payload);
|
||||
cmd.setStatus("0");
|
||||
cmd.setSendTime(new Date());
|
||||
|
||||
commandService.insertMqttSensorCommand(cmd);
|
||||
|
||||
try {
|
||||
mqttPublishClient.publish(topic, payload);
|
||||
|
||||
cmd.setStatus("1");
|
||||
commandService.updateMqttSensorCommand(cmd);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
cmd.setStatus("2");
|
||||
commandService.updateMqttSensorCommand(cmd);
|
||||
|
||||
throw new RuntimeException("指令发送失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 公共方法 ==================
|
||||
|
||||
private MqttSensorDevice getDevice(Long deviceId) {
|
||||
|
||||
if (deviceId == null) {
|
||||
throw new RuntimeException("设备ID不能为空");
|
||||
}
|
||||
|
||||
MqttSensorDevice device = deviceMapper.selectMqttSensorDeviceById(deviceId);
|
||||
|
||||
if (device == null) {
|
||||
throw new RuntimeException("设备不存在");
|
||||
}
|
||||
|
||||
if (device.getDevEui() == null || device.getDevEui().isEmpty()) {
|
||||
throw new RuntimeException("设备未配置devEUI");
|
||||
}
|
||||
|
||||
if (!"0".equals(device.getStatus())) {
|
||||
throw new RuntimeException("设备已禁用");
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
// ================== 工具方法 ==================
|
||||
|
||||
private String hexToBase64(String hex) {
|
||||
byte[] bytes = hexStringToByteArray(hex);
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
|
||||
private String buildPayload(String base64) {
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("confirmed", true);
|
||||
json.put("fport", 85);
|
||||
json.put("data", base64);
|
||||
|
||||
return json.toJSONString();
|
||||
}
|
||||
|
||||
private byte[] hexStringToByteArray(String hex) {
|
||||
|
||||
hex = hex.replace(" ", "").toUpperCase();
|
||||
|
||||
int len = hex.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.shzg.project.worn.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import com.shzg.common.utils.DeptScopeUtils;
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.shzg.project.worn.mapper.MqttSensorDataMapper;
|
||||
@@ -41,6 +43,10 @@ public class MqttSensorDataServiceImpl implements IMqttSensorDataService
|
||||
@Override
|
||||
public List<MqttSensorData> selectMqttSensorDataList(MqttSensorData mqttSensorData)
|
||||
{
|
||||
if (!SecurityUtils.isAdmin())
|
||||
{
|
||||
mqttSensorData.setDeptIds(DeptScopeUtils.getDeptScope());
|
||||
}
|
||||
return mqttSensorDataMapper.selectMqttSensorDataList(mqttSensorData);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.shzg.project.worn.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import com.shzg.common.utils.DeptScopeUtils;
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.shzg.project.worn.mapper.MqttSensorEventMapper;
|
||||
@@ -41,6 +43,12 @@ public class MqttSensorEventServiceImpl implements IMqttSensorEventService
|
||||
@Override
|
||||
public List<MqttSensorEvent> selectMqttSensorEventList(MqttSensorEvent mqttSensorEvent)
|
||||
{
|
||||
// ================= 数据权限控制 =================
|
||||
if (!SecurityUtils.isAdmin())
|
||||
{
|
||||
mqttSensorEvent.setDeptIds(DeptScopeUtils.getDeptScope());
|
||||
}
|
||||
|
||||
return mqttSensorEventMapper.selectMqttSensorEventList(mqttSensorEvent);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.shzg.project.worn.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import com.shzg.common.utils.DeptScopeUtils;
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.shzg.project.worn.mapper.MqttSensorThresholdMapper;
|
||||
@@ -9,100 +15,128 @@ import com.shzg.project.worn.domain.MqttSensorThreshold;
|
||||
import com.shzg.project.worn.service.IMqttSensorThresholdService;
|
||||
|
||||
/**
|
||||
* 传感器阈值配置Service业务层处理
|
||||
* 传感器阈值配置Service业务层处理(最终版)
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-01
|
||||
* 规则:
|
||||
* 1. 设备优先
|
||||
* 2. 仓库兜底
|
||||
* 3. 统一入口(无旧方法)
|
||||
*/
|
||||
@Service
|
||||
public class MqttSensorThresholdServiceImpl implements IMqttSensorThresholdService
|
||||
{
|
||||
/**
|
||||
* 阈值缓存
|
||||
* key: deviceId:deptId:metric
|
||||
*/
|
||||
private final ConcurrentMap<String, Optional<MqttSensorThreshold>> thresholdCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private MqttSensorThresholdMapper mqttSensorThresholdMapper;
|
||||
|
||||
/**
|
||||
* 查询传感器阈值配置
|
||||
*
|
||||
* @param id 传感器阈值配置主键
|
||||
* @return 传感器阈值配置
|
||||
*/
|
||||
// ==================== 基础CRUD ====================
|
||||
|
||||
@Override
|
||||
public MqttSensorThreshold selectMqttSensorThresholdById(Long id)
|
||||
{
|
||||
return mqttSensorThresholdMapper.selectMqttSensorThresholdById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询传感器阈值配置列表
|
||||
*
|
||||
* @param mqttSensorThreshold 传感器阈值配置
|
||||
* @return 传感器阈值配置
|
||||
*/
|
||||
@Override
|
||||
public List<MqttSensorThreshold> selectMqttSensorThresholdList(MqttSensorThreshold mqttSensorThreshold)
|
||||
{
|
||||
if (!SecurityUtils.isAdmin())
|
||||
{
|
||||
mqttSensorThreshold.setDeptIds(DeptScopeUtils.getDeptScope());
|
||||
}
|
||||
return mqttSensorThresholdMapper.selectMqttSensorThresholdList(mqttSensorThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增传感器阈值配置
|
||||
*
|
||||
* @param mqttSensorThreshold 传感器阈值配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold)
|
||||
{
|
||||
mqttSensorThreshold.setCreateTime(DateUtils.getNowDate());
|
||||
return mqttSensorThresholdMapper.insertMqttSensorThreshold(mqttSensorThreshold);
|
||||
int rows = mqttSensorThresholdMapper.insertMqttSensorThreshold(mqttSensorThreshold);
|
||||
clearThresholdCache();
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改传感器阈值配置
|
||||
*
|
||||
* @param mqttSensorThreshold 传感器阈值配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold)
|
||||
{
|
||||
mqttSensorThreshold.setUpdateTime(DateUtils.getNowDate());
|
||||
return mqttSensorThresholdMapper.updateMqttSensorThreshold(mqttSensorThreshold);
|
||||
int rows = mqttSensorThresholdMapper.updateMqttSensorThreshold(mqttSensorThreshold);
|
||||
clearThresholdCache();
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除传感器阈值配置
|
||||
*
|
||||
* @param ids 需要删除的传感器阈值配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteMqttSensorThresholdByIds(Long[] ids)
|
||||
{
|
||||
return mqttSensorThresholdMapper.deleteMqttSensorThresholdByIds(ids);
|
||||
int rows = mqttSensorThresholdMapper.deleteMqttSensorThresholdByIds(ids);
|
||||
clearThresholdCache();
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除传感器阈值配置信息
|
||||
*
|
||||
* @param id 传感器阈值配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteMqttSensorThresholdById(Long id)
|
||||
{
|
||||
return mqttSensorThresholdMapper.deleteMqttSensorThresholdById(id);
|
||||
int rows = mqttSensorThresholdMapper.deleteMqttSensorThresholdById(id);
|
||||
clearThresholdCache();
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备id和指标类型获取阈值配置
|
||||
* @param deviceId
|
||||
* @param metricType
|
||||
* @return
|
||||
* 获取阈值(设备优先 → 仓库兜底)
|
||||
*/
|
||||
@Override
|
||||
public MqttSensorThreshold getThreshold(Long deviceId, String metricType)
|
||||
public MqttSensorThreshold getThreshold(Long deviceId, Long deptId, String metricType)
|
||||
{
|
||||
return mqttSensorThresholdMapper.selectByDeviceAndMetric(deviceId, metricType);
|
||||
if (metricType == null || metricType.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String cacheKey = buildCacheKey(deviceId, deptId, metricType);
|
||||
|
||||
Optional<MqttSensorThreshold> cached = thresholdCache.computeIfAbsent(cacheKey, key -> {
|
||||
|
||||
// 1️⃣ 设备优先
|
||||
if (deviceId != null) {
|
||||
MqttSensorThreshold t =
|
||||
mqttSensorThresholdMapper.selectByDeviceAndMetric(deviceId, metricType);
|
||||
if (t != null) {
|
||||
return Optional.of(t);
|
||||
}
|
||||
}
|
||||
|
||||
// 2️⃣ 仓库兜底
|
||||
if (deptId != null) {
|
||||
MqttSensorThreshold t =
|
||||
mqttSensorThresholdMapper.selectByDeptAndMetric(deptId, metricType);
|
||||
if (t != null) {
|
||||
return Optional.of(t);
|
||||
}
|
||||
}
|
||||
|
||||
// 3️⃣ 未配置
|
||||
return Optional.empty();
|
||||
});
|
||||
|
||||
return cached.orElse(null);
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
private String buildCacheKey(Long deviceId, Long deptId, String metricType)
|
||||
{
|
||||
return (deviceId == null ? "0" : deviceId)
|
||||
+ ":" +
|
||||
(deptId == null ? "0" : deptId)
|
||||
+ ":" +
|
||||
metricType.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void clearThresholdCache()
|
||||
{
|
||||
thresholdCache.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.shzg.project.worn.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.shzg.project.worn.mapper.MqttTopicConfigMapper;
|
||||
import com.shzg.project.worn.domain.MqttTopicConfig;
|
||||
import com.shzg.project.worn.service.IMqttTopicConfigService;
|
||||
|
||||
/**
|
||||
* MQTT主题配置Service业务层处理
|
||||
*
|
||||
* @author shzg
|
||||
* @date 2026-04-08
|
||||
*/
|
||||
@Service
|
||||
public class MqttTopicConfigServiceImpl implements IMqttTopicConfigService
|
||||
{
|
||||
@Autowired
|
||||
private MqttTopicConfigMapper mqttTopicConfigMapper;
|
||||
|
||||
/**
|
||||
* 查询MQTT主题配置
|
||||
*
|
||||
* @param id MQTT主题配置主键
|
||||
* @return MQTT主题配置
|
||||
*/
|
||||
@Override
|
||||
public MqttTopicConfig selectMqttTopicConfigById(Long id)
|
||||
{
|
||||
return mqttTopicConfigMapper.selectMqttTopicConfigById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询MQTT主题配置列表
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return MQTT主题配置
|
||||
*/
|
||||
@Override
|
||||
public List<MqttTopicConfig> selectMqttTopicConfigList(MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
return mqttTopicConfigMapper.selectMqttTopicConfigList(mqttTopicConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有启用的 MQTT 主题配置
|
||||
*
|
||||
* @return MQTT 主题配置集合
|
||||
*/
|
||||
@Override
|
||||
public List<MqttTopicConfig> selectEnabledMqttTopicConfigList()
|
||||
{
|
||||
return mqttTopicConfigMapper.selectEnabledMqttTopicConfigList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertMqttTopicConfig(MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
mqttTopicConfig.setCreateTime(DateUtils.getNowDate());
|
||||
return mqttTopicConfigMapper.insertMqttTopicConfig(mqttTopicConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改MQTT主题配置
|
||||
*
|
||||
* @param mqttTopicConfig MQTT主题配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateMqttTopicConfig(MqttTopicConfig mqttTopicConfig)
|
||||
{
|
||||
mqttTopicConfig.setUpdateTime(DateUtils.getNowDate());
|
||||
return mqttTopicConfigMapper.updateMqttTopicConfig(mqttTopicConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除MQTT主题配置
|
||||
*
|
||||
* @param ids 需要删除的MQTT主题配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteMqttTopicConfigByIds(Long[] ids)
|
||||
{
|
||||
return mqttTopicConfigMapper.deleteMqttTopicConfigByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除MQTT主题配置信息
|
||||
*
|
||||
* @param id MQTT主题配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteMqttTopicConfigById(Long id)
|
||||
{
|
||||
return mqttTopicConfigMapper.deleteMqttTopicConfigById(id);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,27 @@
|
||||
package com.shzg.project.worn.service.impl;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.itextpdf.kernel.font.PdfFont;
|
||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||
import com.itextpdf.kernel.geom.PageSize;
|
||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||
import com.itextpdf.layout.Document;
|
||||
import com.itextpdf.layout.element.Cell;
|
||||
import com.itextpdf.layout.element.Paragraph;
|
||||
import com.itextpdf.layout.element.Table;
|
||||
import com.itextpdf.layout.properties.TextAlignment;
|
||||
import com.itextpdf.layout.properties.VerticalAlignment;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import com.shzg.common.utils.DateUtils;
|
||||
import com.shzg.common.utils.SecurityUtils;
|
||||
import com.shzg.common.utils.StringUtils;
|
||||
@@ -14,12 +34,16 @@ import com.shzg.project.worn.domain.dto.WornInboundPartialFinishDTO;
|
||||
import com.shzg.project.worn.domain.dto.WornInboundPartialFinishItemDTO;
|
||||
import com.shzg.project.worn.mapper.WornInboundItemMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.shzg.project.worn.mapper.WornInboundBillMapper;
|
||||
import com.shzg.project.worn.domain.WornInboundBill;
|
||||
import com.shzg.project.worn.service.IWornInboundBillService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
* 入库库存Service业务层处理
|
||||
*
|
||||
@@ -41,6 +65,12 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
|
||||
@Autowired
|
||||
private WornUniqueCodeEventMapper eventMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@Value("${worn.pdf-font-locations:classpath:fonts/simhei.ttf,/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc,/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/usr/share/fonts/truetype/arphic/ukai.ttc,C:/Windows/Fonts/simhei.ttf,C:/Windows/Fonts/simsun.ttc}")
|
||||
private String pdfFontLocations;
|
||||
|
||||
|
||||
/**
|
||||
* 查询入库库存
|
||||
@@ -92,6 +122,7 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
|
||||
/* ================== 3. 主表赋值 ================== */
|
||||
wornInboundBill.setCreateTime(DateUtils.getNowDate());
|
||||
wornInboundBill.setCreateBy(String.valueOf(userId));
|
||||
wornInboundBill.setInboundTime(DateUtils.getNowDate());
|
||||
wornInboundBill.setBillType("0");
|
||||
wornInboundBill.setStatus("0");
|
||||
wornInboundBill.setIsDelete("0");
|
||||
@@ -475,7 +506,189 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
|
||||
|
||||
return 1;
|
||||
}
|
||||
@Override
|
||||
public byte[] generatePdf(Long id) throws Exception
|
||||
{
|
||||
WornInboundBill bill = wornInboundBillMapper.selectWornInboundBillById(id);
|
||||
if (bill == null)
|
||||
{
|
||||
throw new RuntimeException("入库单不存在");
|
||||
}
|
||||
|
||||
List<WornInboundItem> itemList = wornInboundItemMapper.selectWornInboundItemByBillId(id);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
PdfWriter writer = new PdfWriter(out);
|
||||
PdfDocument pdf = new PdfDocument(writer);
|
||||
Document document = new Document(pdf, PageSize.A4);
|
||||
|
||||
// ===== 中文字体 =====
|
||||
PdfFont font = createPdfFont();
|
||||
document.setFont(font);
|
||||
|
||||
// ===== 标题 =====
|
||||
document.add(new Paragraph("智汇APP报表系统")
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setFontSize(16));
|
||||
|
||||
document.add(new Paragraph("入库单")
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setFontSize(14)
|
||||
.setBold());
|
||||
|
||||
document.add(new Paragraph("\n"));
|
||||
|
||||
// ===== 表格 =====
|
||||
// 新增“存储设施类型”后,列数改为11列
|
||||
float[] widths = {2, 6, 6, 5, 4, 4, 4, 2, 3, 3, 4};
|
||||
Table table = new Table(widths).useAllAvailableWidth();
|
||||
|
||||
// ===== 表头 =====
|
||||
table.addCell(cell("序号"));
|
||||
table.addCell(cell("入库批次编号"));
|
||||
table.addCell(cell("入库时间"));
|
||||
table.addCell(cell("存储设施类型"));
|
||||
table.addCell(cell("物料名称"));
|
||||
table.addCell(cell("规格型号"));
|
||||
table.addCell(cell("入库数量"));
|
||||
table.addCell(cell("单位"));
|
||||
table.addCell(cell("操作人"));
|
||||
table.addCell(cell("备注"));
|
||||
table.addCell(cell("唯一码"));
|
||||
|
||||
// ===== 时间格式 =====
|
||||
String datePart = "";
|
||||
String timePart = "";
|
||||
if (bill.getInboundTime() != null)
|
||||
{
|
||||
datePart = new SimpleDateFormat("yyyy-MM-dd").format(bill.getInboundTime());
|
||||
timePart = new SimpleDateFormat("HH:mm:ss").format(bill.getInboundTime());
|
||||
}
|
||||
|
||||
// ===== 数据 =====
|
||||
int index = 1;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
for (WornInboundItem item : itemList)
|
||||
{
|
||||
table.addCell(cell(String.valueOf(index++)));
|
||||
table.addCell(cell(bill.getBillNo()));
|
||||
|
||||
Cell timeCell = new Cell()
|
||||
.add(new Paragraph(datePart + "\n" + timePart))
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setVerticalAlignment(VerticalAlignment.MIDDLE);
|
||||
table.addCell(timeCell);
|
||||
|
||||
// ===== 新增:存储设施类型(warehouse_name)=====
|
||||
table.addCell(cell(bill.getWarehouseName()));
|
||||
|
||||
table.addCell(cell(item.getMaterialName()));
|
||||
table.addCell(cell(buildSpecificationText(item)));
|
||||
table.addCell(cell(item.getQuantity() == null ? "" : item.getQuantity().toString()));
|
||||
table.addCell(cell(item.getUnitName()));
|
||||
table.addCell(cell(item.getOperatorName()));
|
||||
table.addCell(cell(item.getRemark()));
|
||||
table.addCell(cell(item.getUniqueCode() == null ? "" : item.getUniqueCode().toString()));
|
||||
|
||||
if (item.getQuantity() != null)
|
||||
{
|
||||
total = total.add(item.getQuantity());
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 合计 =====
|
||||
// 前6列合并:序号、入库批次编号、入库时间、存储设施类型、物料名称、规格型号
|
||||
table.addCell(new Cell(1, 6)
|
||||
.add(new Paragraph("合计"))
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setVerticalAlignment(VerticalAlignment.MIDDLE));
|
||||
|
||||
// 入库数量
|
||||
table.addCell(cell(total.toString()));
|
||||
|
||||
// 剩余4列留空:单位、操作人、备注、唯一码
|
||||
table.addCell(new Cell(1, 4)
|
||||
.add(new Paragraph(""))
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setVerticalAlignment(VerticalAlignment.MIDDLE));
|
||||
|
||||
document.add(table);
|
||||
|
||||
document.close();
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private Cell cell(String text)
|
||||
{
|
||||
return new Cell()
|
||||
.add(new Paragraph(text == null ? "" : text))
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setVerticalAlignment(VerticalAlignment.MIDDLE);
|
||||
}
|
||||
|
||||
private String buildSpecificationText(WornInboundItem item)
|
||||
{
|
||||
String specification = item.getSpecification();
|
||||
String model = item.getModel();
|
||||
|
||||
if (StringUtils.isNotEmpty(specification) && StringUtils.isNotEmpty(model)) {
|
||||
return specification + "/" + model;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(specification)) {
|
||||
return specification;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(model)) {
|
||||
return model;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private PdfFont createPdfFont() throws IOException
|
||||
{
|
||||
for (String location : pdfFontLocations.split(",")) {
|
||||
String trimmed = location.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] fontBytes = loadFontBytes(trimmed);
|
||||
if (fontBytes == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return PdfFontFactory.createFont(
|
||||
fontBytes,
|
||||
"Identity-H",
|
||||
PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED
|
||||
);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("未找到可用的PDF中文字体,请检查 worn.pdf-font-locations 配置");
|
||||
}
|
||||
|
||||
private byte[] loadFontBytes(String location) throws IOException
|
||||
{
|
||||
if (location.startsWith("classpath:")) {
|
||||
Resource resource = resourceLoader.getResource(location);
|
||||
if (!resource.exists()) {
|
||||
return null;
|
||||
}
|
||||
return IOUtils.toByteArray(resource.getInputStream());
|
||||
}
|
||||
|
||||
Path path = location.startsWith("file:")
|
||||
? Paths.get(location.substring("file:".length()))
|
||||
: Paths.get(location);
|
||||
|
||||
if (!Files.exists(path) || Files.isDirectory(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Files.readAllBytes(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.shzg.project.worn.websocket.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
/**
|
||||
* WebSocket配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
/**
|
||||
* 开启WebSocket支持
|
||||
*/
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.shzg.project.worn.websocket.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* WebSocket用户信息
|
||||
*/
|
||||
@Data
|
||||
public class WsUserInfo {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 是否管理员
|
||||
*/
|
||||
private boolean isAdmin;
|
||||
|
||||
/**
|
||||
* 可访问的部门ID集合
|
||||
*/
|
||||
private Set<Long> deptIds;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.shzg.project.worn.websocket.endpoint;
|
||||
|
||||
import com.shzg.project.system.domain.SysDept;
|
||||
import com.shzg.project.system.mapper.SysDeptMapper;
|
||||
import com.shzg.project.worn.websocket.domain.WsUserInfo;
|
||||
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@ServerEndpoint("/ws")
|
||||
public class WornWebSocketServer {
|
||||
|
||||
private static WebSocketSessionManager sessionManager;
|
||||
private static SysDeptMapper deptMapper;
|
||||
|
||||
@Autowired
|
||||
public void setSessionManager(WebSocketSessionManager manager) {
|
||||
WornWebSocketServer.sessionManager = manager;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setDeptMapper(SysDeptMapper mapper) {
|
||||
WornWebSocketServer.deptMapper = mapper;
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
|
||||
log.info("[WebSocket] 连接建立 sessionId={}", session.getId());
|
||||
|
||||
String query = session.getQueryString();
|
||||
|
||||
if (query == null || !query.contains("deptId")) {
|
||||
closeSession(session, "missing deptId");
|
||||
return;
|
||||
}
|
||||
|
||||
Long deptId;
|
||||
|
||||
try {
|
||||
deptId = parseDeptId(query);
|
||||
} catch (Exception e) {
|
||||
closeSession(session, "invalid deptId");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SysDept> deptList = deptMapper.selectDeptAndChildren(deptId, null);
|
||||
|
||||
Set<Long> deptIds = new HashSet<>();
|
||||
for (SysDept dept : deptList) {
|
||||
deptIds.add(dept.getDeptId());
|
||||
}
|
||||
|
||||
// 构造用户
|
||||
WsUserInfo userInfo = new WsUserInfo();
|
||||
userInfo.setUserId(1L);
|
||||
userInfo.setUserName("test");
|
||||
userInfo.setAdmin(false);
|
||||
userInfo.setDeptIds(deptIds);
|
||||
|
||||
sessionManager.register(session, userInfo);
|
||||
|
||||
log.info("[WebSocket] 注册成功 sessionId={}, deptIds={}",
|
||||
session.getId(), deptIds);
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
sessionManager.remove(session);
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable error) {
|
||||
if (session != null) {
|
||||
sessionManager.remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
private Long parseDeptId(String query) {
|
||||
|
||||
String[] params = query.split("&");
|
||||
|
||||
for (String param : params) {
|
||||
if (param.startsWith("deptId=")) {
|
||||
return Long.parseLong(param.substring(7));
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("deptId not found");
|
||||
}
|
||||
|
||||
private void closeSession(Session session, String reason) {
|
||||
try {
|
||||
session.close(new CloseReason(
|
||||
CloseReason.CloseCodes.CANNOT_ACCEPT,
|
||||
reason
|
||||
));
|
||||
} catch (IOException e) {
|
||||
log.error("关闭失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.shzg.project.worn.websocket.manager;
|
||||
|
||||
import com.shzg.project.worn.websocket.domain.WsUserInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.Session;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* WebSocket Session 管理器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WebSocketSessionManager {
|
||||
|
||||
/**
|
||||
* sessionId -> 用户信息
|
||||
*/
|
||||
private final ConcurrentHashMap<String, WsUserInfo> sessionUserMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* deptId -> session集合
|
||||
*/
|
||||
private final ConcurrentHashMap<Long, CopyOnWriteArraySet<Session>> deptSessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 注册连接
|
||||
*/
|
||||
public void register(Session session, WsUserInfo userInfo) {
|
||||
|
||||
String sessionId = session.getId();
|
||||
|
||||
// 保存用户信息
|
||||
sessionUserMap.put(sessionId, userInfo);
|
||||
|
||||
// 加入部门分组
|
||||
for (Long deptId : userInfo.getDeptIds()) {
|
||||
deptSessionMap
|
||||
.computeIfAbsent(deptId, k -> new CopyOnWriteArraySet<>())
|
||||
.add(session);
|
||||
}
|
||||
|
||||
log.info("[WebSocket] 连接注册 sessionId={}, userId={}, deptIds={}",
|
||||
sessionId, userInfo.getUserId(), userInfo.getDeptIds());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除连接
|
||||
*/
|
||||
public void remove(Session session) {
|
||||
|
||||
String sessionId = session.getId();
|
||||
WsUserInfo userInfo = sessionUserMap.remove(sessionId);
|
||||
|
||||
if (userInfo != null) {
|
||||
for (Long deptId : userInfo.getDeptIds()) {
|
||||
|
||||
Set<Session> sessions = deptSessionMap.get(deptId);
|
||||
if (sessions != null) {
|
||||
sessions.remove(session);
|
||||
|
||||
// 清理空集合(优化点)
|
||||
if (sessions.isEmpty()) {
|
||||
deptSessionMap.remove(deptId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[WebSocket] 连接移除 sessionId={}", sessionId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 广播(所有连接)
|
||||
*/
|
||||
public void sendAll(String message) {
|
||||
|
||||
for (String sessionId : sessionUserMap.keySet()) {
|
||||
Session session = getSession(sessionId);
|
||||
send(session, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按部门推送
|
||||
*/
|
||||
public void sendToDept(Long deptId, String message) {
|
||||
|
||||
Set<Session> sessions = deptSessionMap.get(deptId);
|
||||
if (sessions == null || sessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Session session : sessions) {
|
||||
send(session, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送消息(统一封装)
|
||||
*/
|
||||
private void send(Session session, String message) {
|
||||
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 关键:判活
|
||||
if (!session.isOpen()) {
|
||||
remove(session);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
session.getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
|
||||
log.error("[WebSocket] 推送失败 sessionId={}", session.getId(), e);
|
||||
|
||||
// 🔥 失败直接清理
|
||||
remove(session);
|
||||
|
||||
try {
|
||||
session.close();
|
||||
} catch (IOException ex) {
|
||||
log.error("[WebSocket] 关闭连接失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取session(内部用)
|
||||
*/
|
||||
private Session getSession(String sessionId) {
|
||||
|
||||
WsUserInfo userInfo = sessionUserMap.get(sessionId);
|
||||
if (userInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 从deptMap反查(简单处理)
|
||||
for (Set<Session> sessions : deptSessionMap.values()) {
|
||||
for (Session s : sessions) {
|
||||
if (s.getId().equals(sessionId)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ ruoyi:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8082
|
||||
port: 8081
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
@@ -179,3 +179,6 @@ mqtt:
|
||||
|
||||
# 默认 QoS
|
||||
qos: 1
|
||||
worn:
|
||||
# PDF 打印字体查找顺序;部署到 Linux 时可把实际存在的中文字体路径放在最前面
|
||||
pdf-font-locations: classpath:fonts/simhei.ttf,/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc,/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/usr/share/fonts/truetype/arphic/ukai.ttc,C:/Windows/Fonts/simhei.ttf,C:/Windows/Fonts/simsun.ttc
|
||||
|
||||
BIN
src/main/resources/fonts/simhei.ttf
Normal file
BIN
src/main/resources/fonts/simhei.ttf
Normal file
Binary file not shown.
@@ -47,7 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by d.parent_id, d.order_num
|
||||
</select>
|
||||
|
||||
<select id="selectDeptListByRoleId" resultType="Long">
|
||||
<select id="selectDeptListByRoleId" resultType="java.lang.Long">
|
||||
select d.dept_id
|
||||
from sys_dept d
|
||||
left join sys_role_dept rd on d.dept_id = rd.dept_id
|
||||
@@ -169,5 +169,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
AND dept_name LIKE CONCAT('%', #{keyword}, '%')
|
||||
</if>
|
||||
</select>
|
||||
<select id="selectDeptAndChildIds" resultType="java.lang.Long">
|
||||
SELECT dept_id
|
||||
FROM sys_dept
|
||||
WHERE FIND_IN_SET(#{deptId}, ancestors)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -45,21 +45,27 @@
|
||||
FROM worn_unique_code t
|
||||
LEFT JOIN sys_dept d ON t.project_id = d.dept_id
|
||||
<where>
|
||||
AND (t.is_delete = '0' OR t.is_delete IS NULL)
|
||||
|
||||
${params.dataScope}
|
||||
<if test="dataScopeDeptIds != null and dataScopeDeptIds.size() > 0">
|
||||
AND t.project_id IN
|
||||
<foreach collection="dataScopeDeptIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="code != null and code != ''">
|
||||
AND t.code = #{code}
|
||||
</if>
|
||||
|
||||
<if test="billNo != null and billNo != ''">
|
||||
AND t.bill_no = #{billNo}
|
||||
</if>
|
||||
<if test="projectId != null">
|
||||
AND t.project_id = #{projectId}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
|
||||
<if test="status != null">
|
||||
AND t.status = #{status}
|
||||
</if>
|
||||
|
||||
<if test="rfidCode != null and rfidCode != ''">
|
||||
AND t.rfid_code = #{rfidCode}
|
||||
</if>
|
||||
@@ -70,6 +76,7 @@
|
||||
<select id="selectWornUniqueCodeById" parameterType="Long" resultMap="WornUniqueCodeResult">
|
||||
<include refid="selectWornUniqueCodeVo"/>
|
||||
where id = #{id}
|
||||
AND (is_delete = '0' OR is_delete IS NULL)
|
||||
</select>
|
||||
|
||||
<select id="selectIdByCode" resultType="java.lang.Long">
|
||||
@@ -136,6 +143,7 @@
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where code = #{code}
|
||||
AND (is_delete = '0' OR is_delete IS NULL)
|
||||
</update>
|
||||
|
||||
<delete id="deleteWornUniqueCodeByIds" parameterType="Long">
|
||||
@@ -144,6 +152,7 @@
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="selectByCode" resultType="com.shzg.project.unique.domain.WornUniqueCode">
|
||||
SELECT
|
||||
id,
|
||||
@@ -196,7 +205,7 @@
|
||||
|
||||
WHERE uc.code = #{code}
|
||||
AND (uc.is_delete = '0' OR uc.is_delete IS NULL)
|
||||
AND (ucm.is_delete = '0' OR ucm.is_delete IS NULL)
|
||||
AND (ucm.id IS NULL OR ucm.is_delete = '0')
|
||||
AND (m.is_delete = '0' OR m.is_delete IS NULL)
|
||||
|
||||
ORDER BY ucm.id ASC
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<resultMap type="MqttSensorData" id="MqttSensorDataResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
|
||||
<result property="topic" column="topic"/>
|
||||
<result property="project" column="project"/>
|
||||
<result property="warehouse" column="warehouse"/>
|
||||
@@ -18,7 +20,6 @@
|
||||
<result property="nh3" column="nh3"/>
|
||||
<result property="h2s" column="h2s"/>
|
||||
<result property="concentration" column="concentration"/>
|
||||
|
||||
<result property="waterStatus" column="water_status"/>
|
||||
|
||||
<result property="remark" column="remark"/>
|
||||
@@ -31,33 +32,57 @@
|
||||
|
||||
<sql id="selectMqttSensorDataVo">
|
||||
select
|
||||
id, device_id, topic, project, warehouse, payload, data_json,
|
||||
battery, temperature, humidity, nh3, h2s, concentration,
|
||||
water_status,
|
||||
remark, create_by, create_time, update_by, update_time, is_delete
|
||||
id,
|
||||
device_id,
|
||||
dept_id,
|
||||
topic,
|
||||
project,
|
||||
warehouse,
|
||||
payload,
|
||||
data_json,
|
||||
battery,
|
||||
temperature,
|
||||
humidity,
|
||||
nh3,
|
||||
h2s,
|
||||
concentration,
|
||||
water_status,
|
||||
remark,
|
||||
create_by,
|
||||
create_time,
|
||||
update_by,
|
||||
update_time,
|
||||
is_delete
|
||||
from mqtt_sensor_data
|
||||
</sql>
|
||||
|
||||
<select id="selectMqttSensorDataList" parameterType="MqttSensorData" resultMap="MqttSensorDataResult">
|
||||
<include refid="selectMqttSensorDataVo"/>
|
||||
<where>
|
||||
<if test="deviceId != null "> and device_id = #{deviceId}</if>
|
||||
<if test="topic != null and topic != ''"> and topic = #{topic}</if>
|
||||
<if test="project != null and project != ''"> and project = #{project}</if>
|
||||
<if test="warehouse != null and warehouse != ''"> and warehouse = #{warehouse}</if>
|
||||
<if test="payload != null and payload != ''"> and payload = #{payload}</if>
|
||||
<if test="dataJson != null and dataJson != ''"> and data_json = #{dataJson}</if>
|
||||
<if test="battery != null "> and battery = #{battery}</if>
|
||||
<if test="temperature != null "> and temperature = #{temperature}</if>
|
||||
<if test="humidity != null "> and humidity = #{humidity}</if>
|
||||
<if test="nh3 != null "> and nh3 = #{nh3}</if>
|
||||
<if test="h2s != null "> and h2s = #{h2s}</if>
|
||||
<if test="concentration != null "> and concentration = #{concentration}</if>
|
||||
<if test="deviceId != null"> and device_id = #{deviceId}</if>
|
||||
|
||||
<if test="waterStatus != null "> and water_status = #{waterStatus}</if>
|
||||
<if test="deptIds != null and deptIds.size > 0">
|
||||
and dept_id in
|
||||
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
|
||||
<if test="topic != null and topic != ''"> and topic = #{topic}</if>
|
||||
<if test="project != null and project != ''"> and project = #{project}</if>
|
||||
<if test="warehouse != null and warehouse != ''"> and warehouse = #{warehouse}</if>
|
||||
<if test="payload != null and payload != ''"> and payload = #{payload}</if>
|
||||
<if test="dataJson != null and dataJson != ''"> and data_json = #{dataJson}</if>
|
||||
<if test="battery != null"> and battery = #{battery}</if>
|
||||
<if test="temperature != null"> and temperature = #{temperature}</if>
|
||||
<if test="humidity != null"> and humidity = #{humidity}</if>
|
||||
<if test="nh3 != null"> and nh3 = #{nh3}</if>
|
||||
<if test="h2s != null"> and h2s = #{h2s}</if>
|
||||
<if test="concentration != null"> and concentration = #{concentration}</if>
|
||||
<if test="waterStatus != null"> and water_status = #{waterStatus}</if>
|
||||
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectMqttSensorDataById" parameterType="Long" resultMap="MqttSensorDataResult">
|
||||
@@ -69,6 +94,8 @@
|
||||
insert into mqtt_sensor_data
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id,</if>
|
||||
<if test="deptId != null">dept_id,</if>
|
||||
|
||||
<if test="topic != null">topic,</if>
|
||||
<if test="project != null">project,</if>
|
||||
<if test="warehouse != null">warehouse,</if>
|
||||
@@ -80,7 +107,6 @@
|
||||
<if test="nh3 != null">nh3,</if>
|
||||
<if test="h2s != null">h2s,</if>
|
||||
<if test="concentration != null">concentration,</if>
|
||||
|
||||
<if test="waterStatus != null">water_status,</if>
|
||||
|
||||
<if test="remark != null">remark,</if>
|
||||
@@ -90,8 +116,11 @@
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="isDelete != null">is_delete,</if>
|
||||
</trim>
|
||||
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">#{deviceId},</if>
|
||||
<if test="deptId != null">#{deptId},</if>
|
||||
|
||||
<if test="topic != null">#{topic},</if>
|
||||
<if test="project != null">#{project},</if>
|
||||
<if test="warehouse != null">#{warehouse},</if>
|
||||
@@ -104,6 +133,7 @@
|
||||
<if test="h2s != null">#{h2s},</if>
|
||||
<if test="concentration != null">#{concentration},</if>
|
||||
<if test="waterStatus != null">#{waterStatus},</if>
|
||||
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
@@ -117,6 +147,8 @@
|
||||
update mqtt_sensor_data
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id = #{deviceId},</if>
|
||||
<if test="deptId != null">dept_id = #{deptId},</if>
|
||||
|
||||
<if test="topic != null">topic = #{topic},</if>
|
||||
<if test="project != null">project = #{project},</if>
|
||||
<if test="warehouse != null">warehouse = #{warehouse},</if>
|
||||
@@ -128,8 +160,6 @@
|
||||
<if test="nh3 != null">nh3 = #{nh3},</if>
|
||||
<if test="h2s != null">h2s = #{h2s},</if>
|
||||
<if test="concentration != null">concentration = #{concentration},</if>
|
||||
|
||||
<!-- ✅ 新增 -->
|
||||
<if test="waterStatus != null">water_status = #{waterStatus},</if>
|
||||
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
@@ -152,4 +182,5 @@
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@@ -1,38 +1,51 @@
|
||||
<?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">
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorEventMapper">
|
||||
|
||||
<resultMap type="MqttSensorEvent" id="MqttSensorEventResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="deviceId" column="device_id" />
|
||||
<result property="eventType" column="event_type" />
|
||||
<result property="eventDesc" column="event_desc" />
|
||||
<result property="level" column="level" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
<result property="id" column="id"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
<result property="eventType" column="event_type"/>
|
||||
<result property="eventDesc" column="event_desc"/>
|
||||
<result property="level" column="level"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectMqttSensorEventVo">
|
||||
select id, device_id, event_type, event_desc, level, status, remark, create_by, create_time, update_by, update_time, is_delete from mqtt_sensor_event
|
||||
select id, device_id, dept_id, event_type, event_desc, level, status, remark, create_by, create_time, update_by, update_time, is_delete
|
||||
from mqtt_sensor_event
|
||||
</sql>
|
||||
|
||||
<select id="selectMqttSensorEventList" parameterType="MqttSensorEvent" resultMap="MqttSensorEventResult">
|
||||
<include refid="selectMqttSensorEventVo"/>
|
||||
<where>
|
||||
<if test="deviceId != null "> and device_id = #{deviceId}</if>
|
||||
<if test="eventType != null and eventType != ''"> and event_type = #{eventType}</if>
|
||||
<if test="eventDesc != null and eventDesc != ''"> and event_desc = #{eventDesc}</if>
|
||||
<if test="level != null and level != ''"> and level = #{level}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
|
||||
<if test="deviceId != null">and device_id = #{deviceId}</if>
|
||||
<if test="deptId != null">and dept_id = #{deptId}</if>
|
||||
|
||||
<!-- 数据权限 -->
|
||||
<if test="deptIds != null and deptIds.size > 0">
|
||||
AND dept_id IN
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="eventType != null and eventType != ''">and event_type = #{eventType}</if>
|
||||
<if test="eventDesc != null and eventDesc != ''">and event_desc = #{eventDesc}</if>
|
||||
<if test="level != null and level != ''">and level = #{level}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectMqttSensorEventById" parameterType="Long" resultMap="MqttSensorEventResult">
|
||||
@@ -44,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
insert into mqtt_sensor_event
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id,</if>
|
||||
<if test="deptId != null">dept_id,</if>
|
||||
<if test="eventType != null and eventType != ''">event_type,</if>
|
||||
<if test="eventDesc != null">event_desc,</if>
|
||||
<if test="level != null">level,</if>
|
||||
@@ -54,9 +68,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="isDelete != null">is_delete,</if>
|
||||
</trim>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">#{deviceId},</if>
|
||||
<if test="deptId != null">#{deptId},</if>
|
||||
<if test="eventType != null and eventType != ''">#{eventType},</if>
|
||||
<if test="eventDesc != null">#{eventDesc},</if>
|
||||
<if test="level != null">#{level},</if>
|
||||
@@ -67,13 +82,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="isDelete != null">#{isDelete},</if>
|
||||
</trim>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateMqttSensorEvent" parameterType="MqttSensorEvent">
|
||||
update mqtt_sensor_event
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id = #{deviceId},</if>
|
||||
<if test="deptId != null">dept_id = #{deptId},</if>
|
||||
<if test="eventType != null and eventType != ''">event_type = #{eventType},</if>
|
||||
<if test="eventDesc != null">event_desc = #{eventDesc},</if>
|
||||
<if test="level != null">level = #{level},</if>
|
||||
@@ -98,4 +114,5 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@@ -1,43 +1,55 @@
|
||||
<?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">
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorThresholdMapper">
|
||||
|
||||
<resultMap type="MqttSensorThreshold" id="MqttSensorThresholdResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="deviceId" column="device_id" />
|
||||
<result property="metricType" column="metric_type" />
|
||||
<result property="warnMin" column="warn_min" />
|
||||
<result property="warnMax" column="warn_max" />
|
||||
<result property="alarmMin" column="alarm_min" />
|
||||
<result property="alarmMax" column="alarm_max" />
|
||||
<result property="unit" column="unit" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
<result property="id" column="id"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
<result property="metricType" column="metric_type"/>
|
||||
<result property="warnMin" column="warn_min"/>
|
||||
<result property="warnMax" column="warn_max"/>
|
||||
<result property="alarmMin" column="alarm_min"/>
|
||||
<result property="alarmMax" column="alarm_max"/>
|
||||
<result property="unit" column="unit"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectMqttSensorThresholdVo">
|
||||
select id, device_id, metric_type, warn_min, warn_max, alarm_min, alarm_max, unit, status, remark, create_by, create_time, update_by, update_time, is_delete from mqtt_sensor_threshold
|
||||
select id, device_id, dept_id, metric_type, warn_min, warn_max, alarm_min, alarm_max,
|
||||
unit, status, remark, create_by, create_time, update_by, update_time, is_delete
|
||||
from mqtt_sensor_threshold
|
||||
</sql>
|
||||
|
||||
<select id="selectMqttSensorThresholdList" parameterType="MqttSensorThreshold" resultMap="MqttSensorThresholdResult">
|
||||
<include refid="selectMqttSensorThresholdVo"/>
|
||||
<where>
|
||||
<if test="deviceId != null "> and device_id = #{deviceId}</if>
|
||||
<if test="metricType != null and metricType != ''"> and metric_type = #{metricType}</if>
|
||||
<if test="warnMin != null "> and warn_min = #{warnMin}</if>
|
||||
<if test="warnMax != null "> and warn_max = #{warnMax}</if>
|
||||
<if test="alarmMin != null "> and alarm_min = #{alarmMin}</if>
|
||||
<if test="alarmMax != null "> and alarm_max = #{alarmMax}</if>
|
||||
<if test="unit != null and unit != ''"> and unit = #{unit}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
|
||||
<if test="deviceId != null">and device_id = #{deviceId}</if>
|
||||
<if test="deptId != null">and dept_id = #{deptId}</if>
|
||||
|
||||
<if test="deptIds != null and deptIds.size > 0">
|
||||
and dept_id in
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="metricType != null and metricType != ''">and metric_type = #{metricType}</if>
|
||||
<if test="warnMin != null">and warn_min = #{warnMin}</if>
|
||||
<if test="warnMax != null">and warn_max = #{warnMax}</if>
|
||||
<if test="alarmMin != null">and alarm_min = #{alarmMin}</if>
|
||||
<if test="alarmMax != null">and alarm_max = #{alarmMax}</if>
|
||||
<if test="unit != null and unit != ''">and unit = #{unit}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -50,6 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
insert into mqtt_sensor_threshold
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id,</if>
|
||||
<if test="deptId != null">dept_id,</if>
|
||||
<if test="metricType != null and metricType != ''">metric_type,</if>
|
||||
<if test="warnMin != null">warn_min,</if>
|
||||
<if test="warnMax != null">warn_max,</if>
|
||||
@@ -63,9 +76,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="isDelete != null">is_delete,</if>
|
||||
</trim>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="deviceId != null">#{deviceId},</if>
|
||||
<if test="deptId != null">#{deptId},</if>
|
||||
<if test="metricType != null and metricType != ''">#{metricType},</if>
|
||||
<if test="warnMin != null">#{warnMin},</if>
|
||||
<if test="warnMax != null">#{warnMax},</if>
|
||||
@@ -79,13 +93,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="isDelete != null">#{isDelete},</if>
|
||||
</trim>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateMqttSensorThreshold" parameterType="MqttSensorThreshold">
|
||||
update mqtt_sensor_threshold
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="deviceId != null">device_id = #{deviceId},</if>
|
||||
<if test="deptId != null">dept_id = #{deptId},</if>
|
||||
<if test="metricType != null and metricType != ''">metric_type = #{metricType},</if>
|
||||
<if test="warnMin != null">warn_min = #{warnMin},</if>
|
||||
<if test="warnMax != null">warn_max = #{warnMax},</if>
|
||||
@@ -94,8 +109,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="unit != null">unit = #{unit},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="createBy != null">create_by = #{createBy},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="isDelete != null">is_delete = #{isDelete},</if>
|
||||
@@ -114,6 +127,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 按设备+指标查询 -->
|
||||
<select id="selectByDeviceAndMetric" resultMap="MqttSensorThresholdResult">
|
||||
select *
|
||||
from mqtt_sensor_threshold
|
||||
@@ -123,4 +137,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and is_delete = '0'
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<!-- 按部门+指标查询 -->
|
||||
<select id="selectByDeptAndMetric" resultMap="MqttSensorThresholdResult">
|
||||
select *
|
||||
from mqtt_sensor_threshold
|
||||
where dept_id = #{deptId}
|
||||
and metric_type = #{metricType}
|
||||
and status = '1'
|
||||
and is_delete = '0'
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectByPriority" resultMap="MqttSensorThresholdResult">
|
||||
select *
|
||||
from mqtt_sensor_threshold
|
||||
where metric_type = #{metricType}
|
||||
and status = '1'
|
||||
and is_delete = '0'
|
||||
and (
|
||||
device_id = #{deviceId}
|
||||
or dept_id = #{deptId}
|
||||
)
|
||||
order by case when device_id = #{deviceId} then 1 else 2 end
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
138
src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml
Normal file
138
src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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="com.shzg.project.worn.mapper.MqttTopicConfigMapper">
|
||||
|
||||
<!-- ================== ResultMap ================== -->
|
||||
<resultMap type="MqttTopicConfig" id="MqttTopicConfigResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="project" column="project" />
|
||||
<result property="warehouse" column="warehouse" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="topicUp" column="topic_up" />
|
||||
<result property="topicDownPrefix" column="topic_down_prefix" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
</resultMap>
|
||||
|
||||
<!-- ================== 基础查询SQL ================== -->
|
||||
<sql id="selectMqttTopicConfigVo">
|
||||
select id, project, warehouse, dept_id, topic_up, topic_down_prefix,
|
||||
status, remark, create_by, create_time, update_by, update_time, is_delete
|
||||
from mqtt_topic_config
|
||||
</sql>
|
||||
|
||||
<!-- ================== 列表查询(支持数据隔离) ================== -->
|
||||
<select id="selectMqttTopicConfigList" parameterType="MqttTopicConfig" resultMap="MqttTopicConfigResult">
|
||||
<include refid="selectMqttTopicConfigVo"/>
|
||||
<where>
|
||||
<if test="project != null and project != ''">and project = #{project}</if>
|
||||
<if test="warehouse != null and warehouse != ''">and warehouse = #{warehouse}</if>
|
||||
<if test="topicUp != null and topicUp != ''">and topic_up = #{topicUp}</if>
|
||||
<if test="topicDownPrefix != null and topicDownPrefix != ''">and topic_down_prefix = #{topicDownPrefix}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if>
|
||||
|
||||
<if test="deptIds != null and deptIds.size() > 0">
|
||||
and dept_id in
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- ================== 根据ID查询 ================== -->
|
||||
<select id="selectMqttTopicConfigById" parameterType="Long" resultMap="MqttTopicConfigResult">
|
||||
<include refid="selectMqttTopicConfigVo"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- ================== 查询启用的Topic(MQTT用) ================== -->
|
||||
<select id="selectEnabledMqttTopicConfigList" resultMap="MqttTopicConfigResult">
|
||||
<include refid="selectMqttTopicConfigVo"/>
|
||||
where status = '0'
|
||||
and is_delete = '0'
|
||||
and topic_up is not null
|
||||
and topic_up != ''
|
||||
|
||||
<!-- 🔥 防止跨项目订阅 -->
|
||||
<if test="deptIds != null and deptIds.size() > 0">
|
||||
and dept_id in
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- ================== 新增 ================== -->
|
||||
<insert id="insertMqttTopicConfig" parameterType="MqttTopicConfig" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into mqtt_topic_config
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="project != null and project != ''">project,</if>
|
||||
<if test="warehouse != null and warehouse != ''">warehouse,</if>
|
||||
<if test="deptId != null">dept_id,</if>
|
||||
<if test="topicUp != null and topicUp != ''">topic_up,</if>
|
||||
<if test="topicDownPrefix != null and topicDownPrefix != ''">topic_down_prefix,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="remark != null and remark != ''">remark,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="isDelete != null and isDelete != ''">is_delete,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="project != null and project != ''">#{project},</if>
|
||||
<if test="warehouse != null and warehouse != ''">#{warehouse},</if>
|
||||
<if test="deptId != null">#{deptId},</if>
|
||||
<if test="topicUp != null and topicUp != ''">#{topicUp},</if>
|
||||
<if test="topicDownPrefix != null and topicDownPrefix != ''">#{topicDownPrefix},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="remark != null and remark != ''">#{remark},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="isDelete != null and isDelete != ''">#{isDelete},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!-- ================== 修改 ================== -->
|
||||
<update id="updateMqttTopicConfig" parameterType="MqttTopicConfig">
|
||||
update mqtt_topic_config
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="project != null and project != ''">project = #{project},</if>
|
||||
<if test="warehouse != null and warehouse != ''">warehouse = #{warehouse},</if>
|
||||
<if test="deptId != null">dept_id = #{deptId},</if>
|
||||
<if test="topicUp != null and topicUp != ''">topic_up = #{topicUp},</if>
|
||||
<if test="topicDownPrefix != null and topicDownPrefix != ''">topic_down_prefix = #{topicDownPrefix},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="isDelete != null">is_delete = #{isDelete},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- ================== 删除(单个) ================== -->
|
||||
<delete id="deleteMqttTopicConfigById" parameterType="Long">
|
||||
delete from mqtt_topic_config where id = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- ================== 批量删除================== -->
|
||||
<delete id="deleteMqttTopicConfigByIds">
|
||||
delete from mqtt_topic_config where id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@@ -35,6 +35,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="typeParentNames" column="type_parent_names"/>
|
||||
<result property="unitId" column="unit_id"/>
|
||||
<result property="unitName" column="unit_name"/>
|
||||
<result property="operatorName" column="operator_name"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectWornInboundItemVo">
|
||||
@@ -193,22 +194,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
parameterType="Long"
|
||||
resultMap="WornInboundItemResult">
|
||||
SELECT
|
||||
id,
|
||||
bill_id,
|
||||
bill_no,
|
||||
material_id,
|
||||
quantity,
|
||||
unique_code,
|
||||
remark,
|
||||
status,
|
||||
create_by,
|
||||
create_time,
|
||||
update_by,
|
||||
update_time,
|
||||
is_delete
|
||||
FROM worn_inbound_item
|
||||
WHERE bill_id = #{billId}
|
||||
AND (is_delete = '0' OR is_delete IS NULL)
|
||||
ORDER BY id ASC
|
||||
i.id,
|
||||
i.bill_id,
|
||||
i.bill_no,
|
||||
i.material_id,
|
||||
i.quantity,
|
||||
i.unique_code,
|
||||
i.remark,
|
||||
i.status,
|
||||
i.create_by,
|
||||
i.create_time,
|
||||
i.update_by,
|
||||
i.update_time,
|
||||
i.is_delete,
|
||||
b.inbound_time,
|
||||
b.warehouse_code,
|
||||
b.warehouse_name,
|
||||
b.area_code,
|
||||
b.area_name,
|
||||
b.remark AS bill_remark,
|
||||
b.status AS bill_status,
|
||||
b.bill_type,
|
||||
m.material_name,
|
||||
m.material_short_name,
|
||||
m.material_code,
|
||||
m.specification,
|
||||
m.model,
|
||||
m.weight,
|
||||
m.unit_id,
|
||||
mu.unit_name,
|
||||
u.nick_name AS operator_name
|
||||
FROM worn_inbound_item i
|
||||
LEFT JOIN worn_inbound_bill b ON i.bill_id = b.id
|
||||
LEFT JOIN worn_material m ON i.material_id = m.id
|
||||
LEFT JOIN worn_material_unit mu ON m.unit_id = mu.id
|
||||
LEFT JOIN sys_user u ON CAST(b.create_by AS UNSIGNED) = u.user_id
|
||||
WHERE i.bill_id = #{billId}
|
||||
AND (i.is_delete = '0' OR i.is_delete IS NULL)
|
||||
AND (b.is_delete = '0' OR b.is_delete IS NULL)
|
||||
ORDER BY i.id ASC
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user