mqtt模块新增智能插座开关,websocket模块

pdf生成功能
This commit is contained in:
2026-04-10 16:14:36 +08:00
parent 24d0aba49f
commit 897bb2ffb3
54 changed files with 2620 additions and 879 deletions

24
pom.xml
View File

@@ -39,11 +39,10 @@
<logback.version>1.2.13</logback.version> <logback.version>1.2.13</logback.version>
<spring-security.version>5.7.14</spring-security.version> <spring-security.version>5.7.14</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version> <spring-framework.version>5.3.39</spring-framework.version>
<!-- ========== 新增统一版本管理只加这3个 ========== -->
<lombok.version>1.18.32</lombok.version> <lombok.version>1.18.32</lombok.version>
<paho.mqtt.version>1.2.5</paho.mqtt.version> <paho.mqtt.version>1.2.5</paho.mqtt.version>
<slf4j.version>1.7.36</slf4j.version> <slf4j.version>1.7.36</slf4j.version>
<itext.version>7.2.5</itext.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -237,9 +236,8 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- ================== 只新增这3个依赖 ================== -->
<!-- 1) SLF4J(你要求显式加;版本走 properties --> <!-- 1) SLF4J -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
@@ -281,6 +279,24 @@
<version>3.5.3</version> <version>3.5.3</version>
</dependency> </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> </dependencies>
<build> <build>

View 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);
}
}

View File

@@ -115,6 +115,9 @@ public class SecurityConfig
"/register", "/register",
"/unique/code/**", "/unique/code/**",
"/device/cmd/**", "/device/cmd/**",
"/worn/socket/**",
"/worn/inboundBill/**",
"/ws/**",
"/captchaImage").permitAll() "/captchaImage").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()

View File

@@ -4,13 +4,15 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
/** /**
* Entity基类 * Entity基类
* *
* @author ruoyi * @author ruoyi
*/ */
public class BaseEntity implements Serializable public class BaseEntity implements Serializable
@@ -42,6 +44,12 @@ public class BaseEntity implements Serializable
@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params; private Map<String, Object> params;
/**
* 部门数据范围(用于数据隔离)
*/
@JsonIgnore
private List<Long> dataScopeDeptIds;
public String getSearchValue() public String getSearchValue()
{ {
return searchValue; return searchValue;
@@ -115,4 +123,14 @@ public class BaseEntity implements Serializable
{ {
this.params = params; this.params = params;
} }
}
public List<Long> getDataScopeDeptIds()
{
return dataScopeDeptIds;
}
public void setDataScopeDeptIds(List<Long> dataScopeDeptIds)
{
this.dataScopeDeptIds = dataScopeDeptIds;
}
}

View File

@@ -124,4 +124,12 @@ public interface SysDeptMapper
*/ */
public List<SysDept> selectDeptAndChildren(@Param("deptId") Long deptId, public List<SysDept> selectDeptAndChildren(@Param("deptId") Long deptId,
@Param("keyword") String keyword); @Param("keyword") String keyword);
/**
* 根据部门ID查询当前部门及其所有子部门ID集合
*
* @param deptId 部门ID
* @return 部门ID集合
*/
List<Long> selectDeptAndChildIds(Long deptId);
} }

View File

@@ -129,4 +129,12 @@ public interface ISysDeptService
* @return 部门信息 * @return 部门信息
*/ */
public List<SysDept> selectDeptAndChildrenByUserId(Long userId, String keyword); public List<SysDept> selectDeptAndChildrenByUserId(Long userId, String keyword);
/**
* 根据部门ID查询部门以及子部门ID列表
*
* @param deptId 部门ID
* @return 部门ID列表
*/
List<Long> selectDeptAndChildIds(Long deptId);
} }

View File

@@ -354,4 +354,10 @@ public class SysDeptServiceImpl implements ISysDeptService
return deptMapper.selectDeptAndChildren(deptId, keyword); return deptMapper.selectDeptAndChildren(deptId, keyword);
} }
@Override
public List<Long> selectDeptAndChildIds(Long deptId)
{
return deptMapper.selectDeptAndChildIds(deptId);
}
} }

View File

@@ -4,6 +4,7 @@ import java.util.List;
import com.shzg.common.exception.ServiceException; import com.shzg.common.exception.ServiceException;
import com.shzg.common.utils.DateUtils; import com.shzg.common.utils.DateUtils;
import com.shzg.common.utils.DeptScopeUtils;
import com.shzg.common.utils.SecurityUtils; import com.shzg.common.utils.SecurityUtils;
import com.shzg.common.utils.StringUtils; import com.shzg.common.utils.StringUtils;
import com.shzg.framework.aspectj.lang.annotation.DataScope; import com.shzg.framework.aspectj.lang.annotation.DataScope;
@@ -89,13 +90,12 @@ public class WornUniqueCodeServiceImpl implements IWornUniqueCodeService
* @param wornUniqueCode 唯一码管理 * @param wornUniqueCode 唯一码管理
* @return 唯一码管理 * @return 唯一码管理
*/ */
@DataScope(deptAlias = "d")
@Override @Override
public List<WornUniqueCode> selectWornUniqueCodeList(WornUniqueCode wornUniqueCode) public List<WornUniqueCode> selectWornUniqueCodeList(WornUniqueCode wornUniqueCode)
{ {
if (!SecurityUtils.isAdmin()) if (!SecurityUtils.isAdmin())
{ {
wornUniqueCode.setProjectId(SecurityUtils.getDeptId()); wornUniqueCode.setDataScopeDeptIds(DeptScopeUtils.getDeptScope());
} }
return wornUniqueCodeMapper.selectWornUniqueCodeList(wornUniqueCode); return wornUniqueCodeMapper.selectWornUniqueCodeList(wornUniqueCode);
} }

View File

@@ -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("单次消警指令已发送");
}
}

View File

@@ -2,6 +2,8 @@ package com.shzg.project.worn.controller;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.shzg.common.utils.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;

View File

@@ -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));
}
}

View File

@@ -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("指令已发送");
}
}

View File

@@ -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";
}
}

View File

@@ -113,4 +113,19 @@ public class WornInboundBillController extends BaseController
{ {
return toAjax(wornInboundBillService.voidBill(wornInboundBill.getBillNo())); 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);
}
} }

View File

@@ -1,6 +1,7 @@
package com.shzg.project.worn.domain; package com.shzg.project.worn.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
import com.shzg.framework.web.domain.BaseEntity; import com.shzg.framework.web.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -9,9 +10,6 @@ import com.shzg.framework.aspectj.lang.annotation.Excel;
/** /**
* 设备数据对象 mqtt_sensor_data * 设备数据对象 mqtt_sensor_data
*
* @author shzg
* @date 2026-04-01
*/ */
public class MqttSensorData extends BaseEntity public class MqttSensorData extends BaseEntity
{ {
@@ -24,16 +22,22 @@ public class MqttSensorData extends BaseEntity
@Excel(name = "设备ID") @Excel(name = "设备ID")
private Long deviceId; private Long deviceId;
/** 部门ID */
@Excel(name = "部门ID")
private Long deptId;
private List<Long> deptIds;
/** MQTT Topic */ /** MQTT Topic */
@Excel(name = "MQTT Topic") @Excel(name = "MQTT Topic")
private String topic; private String topic;
/** 项目(如 tangshan */ /** 项目 */
@Excel(name = "项目", readConverterExp = "如=,t=angshan") @Excel(name = "项目")
private String project; private String project;
/** 仓库(如 dianchi */ /** 仓库 */
@Excel(name = "仓库", readConverterExp = "如=,d=ianchi") @Excel(name = "仓库")
private String warehouse; private String warehouse;
/** 原始消息 */ /** 原始消息 */
@@ -68,187 +72,86 @@ public class MqttSensorData extends BaseEntity
@Excel(name = "烟雾浓度") @Excel(name = "烟雾浓度")
private Long concentration; private Long concentration;
/** 水浸状态0=正常1=报警) */ /** 水浸状态 */
@Excel(name = "水浸状态") @Excel(name = "水浸状态")
private Integer waterStatus; private Integer waterStatus;
/** 插座状态0关 1开 */
@Excel(name = "插座状态")
private Integer switchStatus;
/** 删除标识 */ /** 删除标识 */
@Excel(name = "删除标识") @Excel(name = "删除标识")
private String isDelete; private String isDelete;
public void setId(Long id) // ================== getter / setter ==================
{
this.id = id;
}
public Long getId() public Long getId() { return id; }
{ public void setId(Long id) { this.id = id; }
return id;
}
public void setDeviceId(Long deviceId) public Long getDeviceId() { return deviceId; }
{ public void setDeviceId(Long deviceId) { this.deviceId = deviceId; }
this.deviceId = deviceId;
}
public Long getDeviceId() public Long getDeptId() { return deptId; }
{ public void setDeptId(Long deptId) { this.deptId = deptId; }
return deviceId;
}
public void setTopic(String topic) public List<Long> getDeptIds() { return deptIds; }
{ public void setDeptIds(List<Long> deptIds) { this.deptIds = deptIds; }
this.topic = topic;
}
public String getTopic() public String getTopic() { return topic; }
{ public void setTopic(String topic) { this.topic = topic; }
return topic;
}
public void setProject(String project) public String getProject() { return project; }
{ public void setProject(String project) { this.project = project; }
this.project = project;
}
public String getProject() public String getWarehouse() { return warehouse; }
{ public void setWarehouse(String warehouse) { this.warehouse = warehouse; }
return project;
}
public void setWarehouse(String warehouse) public String getPayload() { return payload; }
{ public void setPayload(String payload) { this.payload = payload; }
this.warehouse = warehouse;
}
public String getWarehouse() public String getDataJson() { return dataJson; }
{ public void setDataJson(String dataJson) { this.dataJson = dataJson; }
return warehouse;
}
public void setPayload(String payload) public Long getBattery() { return battery; }
{ public void setBattery(Long battery) { this.battery = battery; }
this.payload = payload;
}
public String getPayload() public BigDecimal getTemperature() { return temperature; }
{ public void setTemperature(BigDecimal temperature) { this.temperature = temperature; }
return payload;
}
public void setDataJson(String dataJson) public BigDecimal getHumidity() { return humidity; }
{ public void setHumidity(BigDecimal humidity) { this.humidity = humidity; }
this.dataJson = dataJson;
}
public String getDataJson() public BigDecimal getNh3() { return nh3; }
{ public void setNh3(BigDecimal nh3) { this.nh3 = nh3; }
return dataJson;
}
public void setBattery(Long battery) public BigDecimal getH2s() { return h2s; }
{ public void setH2s(BigDecimal h2s) { this.h2s = h2s; }
this.battery = battery;
}
public Long getBattery() public Long getConcentration() { return concentration; }
{ public void setConcentration(Long concentration) { this.concentration = concentration; }
return battery;
}
public void setTemperature(BigDecimal temperature) public Integer getWaterStatus() { return waterStatus; }
{ public void setWaterStatus(Integer waterStatus) { this.waterStatus = waterStatus; }
this.temperature = temperature;
}
public BigDecimal getTemperature() public Integer getSwitchStatus() { return switchStatus; }
{ public void setSwitchStatus(Integer switchStatus) { this.switchStatus = switchStatus; }
return temperature;
}
public void setHumidity(BigDecimal humidity) public String getIsDelete() { return isDelete; }
{ public void setIsDelete(String isDelete) { this.isDelete = isDelete; }
this.humidity = humidity;
}
public BigDecimal getHumidity() // ================== toString ==================
{
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;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("deviceId", getDeviceId()) .append("deviceId", getDeviceId())
.append("deptId", getDeptId())
.append("topic", getTopic()) .append("topic", getTopic())
.append("project", getProject())
.append("warehouse", getWarehouse())
.append("payload", getPayload()) .append("payload", getPayload())
.append("dataJson", getDataJson()) .append("switchStatus", getSwitchStatus())
.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("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("isDelete", getIsDelete())
.toString(); .toString();
} }
} }

View File

@@ -5,9 +5,11 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.shzg.framework.aspectj.lang.annotation.Excel; import com.shzg.framework.aspectj.lang.annotation.Excel;
import java.util.List;
/** /**
* 设备事件对象 mqtt_sensor_event * 设备事件对象 mqtt_sensor_event
* *
* @author shzg * @author shzg
* @date 2026-04-01 * @date 2026-04-01
*/ */
@@ -22,8 +24,12 @@ public class MqttSensorEvent extends BaseEntity
@Excel(name = "设备ID") @Excel(name = "设备ID")
private Long deviceId; private Long deviceId;
/** 部门ID项目/仓库) */
@Excel(name = "部门ID")
private Long deptId;
/** 事件类型alarm / threshold / normal */ /** 事件类型alarm / threshold / normal */
@Excel(name = "事件类型", readConverterExp = "a=larm,/=,t=hreshold,/=,n=ormal") @Excel(name = "事件类型", readConverterExp = "alarm=报警,threshold=阈值,normal=正常")
private String eventType; private String eventType;
/** 事件描述 */ /** 事件描述 */
@@ -31,7 +37,7 @@ public class MqttSensorEvent extends BaseEntity
private String eventDesc; private String eventDesc;
/** 等级LOW/MEDIUM/HIGH */ /** 等级LOW/MEDIUM/HIGH */
@Excel(name = "等级", readConverterExp = "L=OW/MEDIUM/HIGH") @Excel(name = "等级", readConverterExp = "LOW=低,MEDIUM=中,HIGH=高")
private String level; private String level;
/** 处理状态0未处理 1已处理 */ /** 处理状态0未处理 1已处理 */
@@ -42,91 +48,116 @@ public class MqttSensorEvent extends BaseEntity
@Excel(name = "删除标识") @Excel(name = "删除标识")
private String isDelete; private String isDelete;
public void setId(Long id) private List<Long> deptIds;
// ==================== getter / setter ====================
public void setId(Long id)
{ {
this.id = id; this.id = id;
} }
public Long getId() public Long getId()
{ {
return id; return id;
} }
public void setDeviceId(Long deviceId) public void setDeviceId(Long deviceId)
{ {
this.deviceId = deviceId; this.deviceId = deviceId;
} }
public Long getDeviceId() public Long getDeviceId()
{ {
return deviceId; return deviceId;
} }
public void setEventType(String eventType) public void setDeptId(Long deptId)
{
this.deptId = deptId;
}
public Long getDeptId()
{
return deptId;
}
public void setEventType(String eventType)
{ {
this.eventType = eventType; this.eventType = eventType;
} }
public String getEventType() public String getEventType()
{ {
return eventType; return eventType;
} }
public void setEventDesc(String eventDesc) public void setEventDesc(String eventDesc)
{ {
this.eventDesc = eventDesc; this.eventDesc = eventDesc;
} }
public String getEventDesc() public String getEventDesc()
{ {
return eventDesc; return eventDesc;
} }
public void setLevel(String level) public void setLevel(String level)
{ {
this.level = level; this.level = level;
} }
public String getLevel() public String getLevel()
{ {
return level; return level;
} }
public void setStatus(String status) public void setStatus(String status)
{ {
this.status = status; this.status = status;
} }
public String getStatus() public String getStatus()
{ {
return status; return status;
} }
public void setIsDelete(String isDelete) public void setIsDelete(String isDelete)
{ {
this.isDelete = isDelete; this.isDelete = isDelete;
} }
public String getIsDelete() public String getIsDelete()
{ {
return isDelete; return isDelete;
} }
public List<Long> getDeptIds() {
return deptIds;
}
public void setDeptIds(List<Long> deptIds) {
this.deptIds = deptIds;
}
// ==================== toString ====================
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("deviceId", getDeviceId()) .append("deviceId", getDeviceId())
.append("eventType", getEventType()) .append("deptId", getDeptId())
.append("eventDesc", getEventDesc()) .append("eventType", getEventType())
.append("level", getLevel()) .append("eventDesc", getEventDesc())
.append("status", getStatus()) .append("level", getLevel())
.append("remark", getRemark()) .append("status", getStatus())
.append("createBy", getCreateBy()) .append("remark", getRemark())
.append("createTime", getCreateTime()) .append("createBy", getCreateBy())
.append("updateBy", getUpdateBy()) .append("createTime", getCreateTime())
.append("updateTime", getUpdateTime()) .append("updateBy", getUpdateBy())
.append("isDelete", getIsDelete()) .append("updateTime", getUpdateTime())
.toString(); .append("isDelete", getIsDelete())
.toString();
} }
} }

View File

@@ -1,6 +1,7 @@
package com.shzg.project.worn.domain; package com.shzg.project.worn.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
import com.shzg.framework.web.domain.BaseEntity; import com.shzg.framework.web.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -9,7 +10,7 @@ import com.shzg.framework.aspectj.lang.annotation.Excel;
/** /**
* 传感器阈值配置对象 mqtt_sensor_threshold * 传感器阈值配置对象 mqtt_sensor_threshold
* *
* @author shzg * @author shzg
* @date 2026-04-01 * @date 2026-04-01
*/ */
@@ -21,11 +22,18 @@ public class MqttSensorThreshold extends BaseEntity
private Long id; private Long id;
/** 设备ID关联 mqtt_sensor_device.id */ /** 设备ID关联 mqtt_sensor_device.id */
@Excel(name = "设备ID", readConverterExp = "关=联,m=qtt_sensor_device.id") @Excel(name = "设备ID")
private Long deviceId; private Long deviceId;
/** 部门ID项目/仓库) */
@Excel(name = "部门ID")
private Long deptId;
/** 数据权限部门ID集合用于数据隔离 */
private List<Long> deptIds;
/** 指标类型temperature/humidity/nh3/h2s/battery */ /** 指标类型temperature/humidity/nh3/h2s/battery */
@Excel(name = "指标类型", readConverterExp = "t=emperature/humidity/nh3/h2s/battery") @Excel(name = "指标类型")
private String metricType; private String metricType;
/** 预警最小值 */ /** 预警最小值 */
@@ -45,135 +53,136 @@ public class MqttSensorThreshold extends BaseEntity
private BigDecimal alarmMax; private BigDecimal alarmMax;
/** 单位(℃/%/ppm */ /** 单位(℃/%/ppm */
@Excel(name = "单位", readConverterExp = "℃=/%/ppm") @Excel(name = "单位")
private String unit; private String unit;
/** 状态1启用 0停用 */ /** 状态1启用 0停用 */
@Excel(name = "状态", readConverterExp = "1=启用,0=停用") @Excel(name = "状态")
private String status; private String status;
/** 是否删除0正常 1删除 */ /** 是否删除0正常 1删除 */
@Excel(name = "是否删除", readConverterExp = "0=正常,1=删除") @Excel(name = "是否删除")
private String isDelete; private String isDelete;
public void setId(Long id) // ==================== getter/setter ====================
{
this.id = id;
}
public Long getId() public Long getId() {
{
return id; return id;
} }
public void setDeviceId(Long deviceId) public void setId(Long id) {
{ this.id = id;
this.deviceId = deviceId;
} }
public Long getDeviceId() public Long getDeviceId() {
{
return deviceId; return deviceId;
} }
public void setMetricType(String metricType) public void setDeviceId(Long deviceId) {
{ this.deviceId = deviceId;
this.metricType = metricType;
} }
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; return metricType;
} }
public void setWarnMin(BigDecimal warnMin) public void setMetricType(String metricType) {
{ this.metricType = metricType;
this.warnMin = warnMin;
} }
public BigDecimal getWarnMin() public BigDecimal getWarnMin() {
{
return warnMin; return warnMin;
} }
public void setWarnMax(BigDecimal warnMax) public void setWarnMin(BigDecimal warnMin) {
{ this.warnMin = warnMin;
this.warnMax = warnMax;
} }
public BigDecimal getWarnMax() public BigDecimal getWarnMax() {
{
return warnMax; return warnMax;
} }
public void setAlarmMin(BigDecimal alarmMin) public void setWarnMax(BigDecimal warnMax) {
{ this.warnMax = warnMax;
this.alarmMin = alarmMin;
} }
public BigDecimal getAlarmMin() public BigDecimal getAlarmMin() {
{
return alarmMin; return alarmMin;
} }
public void setAlarmMax(BigDecimal alarmMax) public void setAlarmMin(BigDecimal alarmMin) {
{ this.alarmMin = alarmMin;
this.alarmMax = alarmMax;
} }
public BigDecimal getAlarmMax() public BigDecimal getAlarmMax() {
{
return alarmMax; return alarmMax;
} }
public void setUnit(String unit) public void setAlarmMax(BigDecimal alarmMax) {
{ this.alarmMax = alarmMax;
this.unit = unit;
} }
public String getUnit() public String getUnit() {
{
return unit; return unit;
} }
public void setStatus(String status) public void setUnit(String unit) {
{ this.unit = unit;
this.status = status;
} }
public String getStatus() public String getStatus() {
{
return status; return status;
} }
public void setIsDelete(String isDelete) public void setStatus(String status) {
{ this.status = status;
this.isDelete = isDelete;
} }
public String getIsDelete() public String getIsDelete() {
{
return isDelete; return isDelete;
} }
public void setIsDelete(String isDelete) {
this.isDelete = isDelete;
}
// ==================== toString ====================
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("deviceId", getDeviceId()) .append("deviceId", getDeviceId())
.append("metricType", getMetricType()) .append("deptId", getDeptId())
.append("warnMin", getWarnMin()) .append("metricType", getMetricType())
.append("warnMax", getWarnMax()) .append("warnMin", getWarnMin())
.append("alarmMin", getAlarmMin()) .append("warnMax", getWarnMax())
.append("alarmMax", getAlarmMax()) .append("alarmMin", getAlarmMin())
.append("unit", getUnit()) .append("alarmMax", getAlarmMax())
.append("status", getStatus()) .append("unit", getUnit())
.append("remark", getRemark()) .append("status", getStatus())
.append("createBy", getCreateBy()) .append("remark", getRemark())
.append("createTime", getCreateTime()) .append("createBy", getCreateBy())
.append("updateBy", getUpdateBy()) .append("createTime", getCreateTime())
.append("updateTime", getUpdateTime()) .append("updateBy", getUpdateBy())
.append("isDelete", getIsDelete()) .append("updateTime", getUpdateTime())
.toString(); .append("isDelete", getIsDelete())
.toString();
} }
} }

View 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();
}
}

View File

@@ -108,6 +108,7 @@ public class WornInboundItem extends BaseEntity
private Long unitId; private Long unitId;
/** 单位名称 */ /** 单位名称 */
private String unitName; private String unitName;
private String operatorName;
/** 主单据状态 */ /** 主单据状态 */
private String billStatus; private String billStatus;
@@ -237,6 +238,15 @@ public class WornInboundItem extends BaseEntity
{ {
return unitName; return unitName;
} }
public String getOperatorName()
{
return operatorName;
}
public void setOperatorName(String operatorName)
{
this.operatorName = operatorName;
}
public void setStatus(String status) public void setStatus(String status)
{ {
this.status = status; this.status = status;
@@ -273,4 +283,4 @@ public class WornInboundItem extends BaseEntity
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.toString(); .toString();
} }
} }

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -66,4 +66,13 @@ public interface MqttSensorThresholdMapper
* @return * @return
*/ */
MqttSensorThreshold selectByDeviceAndMetric(Long deviceId, String metricType); MqttSensorThreshold selectByDeviceAndMetric(Long deviceId, String metricType);
/**
* 根据部门id和指标类型查询阈值配置
* @param deptId
* @param metricType
* @return
*/
MqttSensorThreshold selectByDeptAndMetric(Long deptId, String metricType);
} }

View File

@@ -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);
}

View File

@@ -1,6 +1,8 @@
package com.shzg.project.worn.sensor.config; 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.sensor.mqtt.dispatcher.MqttMessageDispatcher;
import com.shzg.project.worn.service.IMqttTopicConfigService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 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.PreDestroy;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
* MQTT 客户端初始化配置类 * MQTT客户端配置类
*
* 作用:
* 1. 初始化 MQTT 客户端连接
* 2. 设置连接参数(心跳、自动重连等)
* 3. 注册回调函数(消息接收、连接状态等)
* 4. 自动订阅数据库中配置的 topic
* 5. 应用关闭时释放资源
*/ */
@Slf4j @Slf4j
@Configuration @Configuration
public class MqttClientConfig { public class MqttClientConfig {
/**
* MQTT客户端实例全局唯一
*/
private MqttClient mqttClient; private MqttClient mqttClient;
/**
* MQTT消息分发器核心组件
* 负责将不同topic的数据分发到对应Handler
*/
@Resource @Resource
private MqttMessageDispatcher mqttMessageDispatcher; private MqttMessageDispatcher mqttMessageDispatcher;
/**
* topic配置服务从数据库读取订阅主题
*/
@Resource
private IMqttTopicConfigService mqttTopicConfigService;
/**
* 初始化 MQTT 客户端 Bean
*
* @param props MQTT配置来自配置文件
*/
@Bean @Bean
public MqttClient mqttClient(MqttProperties props) throws MqttException { public MqttClient mqttClient(MqttProperties props) throws MqttException {
// ================== 1⃣ 开关控制 ==================
// 如果MQTT未启用直接跳过初始化
if (!props.isEnabled()) { if (!props.isEnabled()) {
log.warn("[MQTT] mqtt.enabled=falseMQTT 功能未启用"); log.warn("[MQTT] mqtt.enabled=false, skip initialization");
return null; return null;
} }
// ✅ clientId 动态生成 // ================== 2⃣ 客户端ID生成 ==================
// 使用随机UUID避免多个服务clientId冲突
String clientId = "worn-backend-" + UUID.randomUUID(); String clientId = "worn-backend-" + UUID.randomUUID();
mqttClient = new MqttClient( // 创建MQTT客户端内存存储方式
props.getBroker(), mqttClient = new MqttClient(props.getBroker(), clientId, new MemoryPersistence());
clientId,
new MemoryPersistence()
);
// 连接参数 // ================== 3⃣ 连接参数配置 ==================
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
// 是否清除会话true每次都是新会话
options.setCleanSession(props.isCleanSession()); options.setCleanSession(props.isCleanSession());
// 心跳间隔(秒)
options.setKeepAliveInterval(props.getKeepAlive()); options.setKeepAliveInterval(props.getKeepAlive());
// 连接超时时间(秒)
options.setConnectionTimeout(props.getTimeout()); options.setConnectionTimeout(props.getTimeout());
// 自动重连(非常重要)
options.setAutomaticReconnect(true); options.setAutomaticReconnect(true);
// 用户名密码(可选)
if (props.getUsername() != null && !props.getUsername().trim().isEmpty()) { if (props.getUsername() != null && !props.getUsername().trim().isEmpty()) {
options.setUserName(props.getUsername().trim()); options.setUserName(props.getUsername().trim());
} }
@@ -55,70 +94,125 @@ public class MqttClientConfig {
options.setPassword(props.getPassword().toCharArray()); options.setPassword(props.getPassword().toCharArray());
} }
// 回调 // ================== 4⃣ 回调函数 ==================
mqttClient.setCallback(new MqttCallbackExtended() { mqttClient.setCallback(new MqttCallbackExtended() {
/**
* 连接成功回调(首次连接 + 重连都会触发)
*/
@Override @Override
public void connectComplete(boolean reconnect, String serverURI) { public void connectComplete(boolean reconnect, String serverURI) {
log.info("[MQTT] 连接成功 reconnect={}, serverURI={}", reconnect, serverURI); log.info("[MQTT] connected reconnect={}, serverURI={}", reconnect, serverURI);
// 每次连接成功后重新订阅(防止重连丢订阅)
subscribe(props); subscribe(props);
} }
/**
* 连接丢失回调
*/
@Override @Override
public void connectionLost(Throwable cause) { public void connectionLost(Throwable cause) {
log.warn("[MQTT] 连接断开", cause); log.warn("[MQTT] connection lost", cause);
} }
/**
* 收到消息回调(核心入口)
*/
@Override @Override
public void messageArrived(String topic, MqttMessage message) { public void messageArrived(String topic, MqttMessage message) {
// 将byte[]转为字符串
String payload = new String(message.getPayload(), StandardCharsets.UTF_8); 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); mqttMessageDispatcher.dispatch(topic, payload);
} }
/**
* 消息发送完成(发布消息时用)
*/
@Override @Override
public void deliveryComplete(IMqttDeliveryToken token) { public void deliveryComplete(IMqttDeliveryToken token) {
// 当前项目未使用,可扩展日志或确认机制
} }
}); });
// 连接 // ================== 5⃣ 建立连接 ==================
log.info("[MQTT] 正在连接 Broker{}", props.getBroker()); log.info("[MQTT] connecting broker={}", props.getBroker());
mqttClient.connect(options); mqttClient.connect(options);
log.info("[MQTT] MQTT 已连接"); log.info("[MQTT] connected");
// 启动订阅 // ================== 6⃣ 订阅主题 ==================
subscribe(props); subscribe(props);
return mqttClient; return mqttClient;
} }
/**
* 订阅主题(从数据库动态加载)
*/
private void subscribe(MqttProperties props) { private void subscribe(MqttProperties props) {
// 如果客户端未连接,直接跳过
if (mqttClient == null || !mqttClient.isConnected()) { if (mqttClient == null || !mqttClient.isConnected()) {
log.warn("[MQTT] 订阅失败:客户端未连接"); log.warn("[MQTT] subscribe skipped because client is not connected");
return; return;
} }
try { try {
// ================== 1⃣ 查询数据库配置 ==================
List<MqttTopicConfig> topicConfigs =
mqttTopicConfigService.selectEnabledMqttTopicConfigList();
mqttClient.subscribe("worn/tangshan/dianchi/up", props.getQos()); // 使用Set去重保证不会重复订阅
Set<String> topics = new LinkedHashSet<>();
log.info("[MQTT] 已订阅 Topicworn/tangshan/dianchi/upQoS={}", 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) { } catch (Exception e) {
log.error("[MQTT] 订阅 Topic 失败", e); log.error("[MQTT] subscribe failed", e);
} }
} }
/**
* 应用关闭时释放MQTT资源
*/
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
try { try {
if (mqttClient != null) { if (mqttClient != null) {
// 如果连接存在,先断开
if (mqttClient.isConnected()) { if (mqttClient.isConnected()) {
mqttClient.disconnect(); mqttClient.disconnect();
} }
// 关闭客户端
mqttClient.close(); mqttClient.close();
} }
} catch (Exception ignored) { } catch (Exception ignored) {

View File

@@ -8,11 +8,10 @@ import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** /**
* MQTT 消息发布客户端 * MQTT 消息发布客户端(最终版 - LoRaWAN专用
* *
* 支持 * 所有下行统一使用
* 1. JSON消息发送 * JSON + Base64
* 2. HEX指令发送设备控制
*/ */
@Slf4j @Slf4j
@Component @Component
@@ -24,7 +23,7 @@ public class MqttPublishClient {
this.mqttClient = mqttClient; this.mqttClient = mqttClient;
} }
// ================== JSON发送 ================== // ================== ⭐ 标准下行发送 ==================
public void publish(String topic, String payload) { public void publish(String topic, String payload) {
publish(topic, payload, 1, false); publish(topic, payload, 1, false);
} }
@@ -39,38 +38,12 @@ public class MqttPublishClient {
mqttClient.publish(topic, msg); mqttClient.publish(topic, msg);
log.info("[MQTT] JSON发送成功 topic={}, payload={}, qos={}, retained={}", log.info("[MQTT] 下行发送成功 topic={}, payload={}, qos={}, retained={}",
topic, payload, qos, retained); topic, payload, qos, retained);
} catch (Exception e) { } catch (Exception e) {
log.error("[MQTT] JSON发送失败 topic=" + topic + ", payload=" + payload, e); log.error("[MQTT] 下行发送失败 topic=" + topic + ", payload=" + payload, e);
throw new RuntimeException("MQTT JSON发送失败", e); throw new RuntimeException("MQTT发送失败", 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);
} }
} }
@@ -83,41 +56,4 @@ public class MqttPublishClient {
throw new IllegalStateException("MQTT客户端未连接到Broker"); 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;
}
} }

View File

@@ -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.EnvSensorHandler;
import com.shzg.project.worn.sensor.mqtt.handler.SmokeSensorHandler; 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.WaterSensorHandler;
import com.shzg.project.worn.sensor.mqtt.handler.SmartSocketHandler;
import com.shzg.project.worn.unit.MqttDeviceCache; import com.shzg.project.worn.unit.MqttDeviceCache;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -15,7 +16,7 @@ import org.springframework.stereotype.Component;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
* MQTT消息分发器支持烟雾 / 环境 / 水浸) * MQTT消息分发器支持烟雾 / 环境 / 水浸 / 插座
*/ */
@Slf4j @Slf4j
@Component @Component
@@ -30,10 +31,12 @@ public class MqttMessageDispatcher {
@Autowired @Autowired
private EnvSensorHandler envSensorHandler; private EnvSensorHandler envSensorHandler;
// ✅ 新增:水浸处理器
@Autowired @Autowired
private WaterSensorHandler waterSensorHandler; private WaterSensorHandler waterSensorHandler;
@Autowired
private SmartSocketHandler smartSocketHandler;
@Autowired @Autowired
@Qualifier("mqttExecutor") @Qualifier("mqttExecutor")
private Executor executor; private Executor executor;
@@ -72,7 +75,6 @@ public class MqttMessageDispatcher {
return; return;
} }
// 👉 统一小写(关键)
devEui = devEui.toLowerCase(); devEui = devEui.toLowerCase();
// ========================= // =========================
@@ -110,12 +112,18 @@ public class MqttMessageDispatcher {
return; return;
} }
// 💧 水浸(新增) // 💧 水浸
if (deviceType.contains("water")) { if (deviceType.contains("water")) {
waterSensorHandler.handle(device, topic, payload); waterSensorHandler.handle(device, topic, payload);
return; return;
} }
// 🔌 智能排风插座
if (deviceType.contains("socket")) {
smartSocketHandler.handle(device, topic, payload);
return;
}
// ❌ 未识别 // ❌ 未识别
log.warn("[MQTT] 未识别设备类型 deviceType={}", deviceType); log.warn("[MQTT] 未识别设备类型 deviceType={}", deviceType);

View File

@@ -8,6 +8,7 @@ import com.shzg.project.worn.domain.MqttSensorThreshold;
import com.shzg.project.worn.service.IMqttSensorDataService; import com.shzg.project.worn.service.IMqttSensorDataService;
import com.shzg.project.worn.service.IMqttSensorEventService; import com.shzg.project.worn.service.IMqttSensorEventService;
import com.shzg.project.worn.service.IMqttSensorThresholdService; import com.shzg.project.worn.service.IMqttSensorThresholdService;
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -18,7 +19,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* 综合环境传感器 Handler(最终生产版:防重复报警 + 状态机) * 综合环境传感器 Handler
*/ */
@Slf4j @Slf4j
@Component @Component
@@ -33,18 +34,27 @@ public class EnvSensorHandler {
@Autowired @Autowired
private IMqttSensorThresholdService thresholdService; private IMqttSensorThresholdService thresholdService;
@Autowired
private WebSocketSessionManager sessionManager;
/** /**
* 状态缓存(核心:防重复) * 状态缓存(防重复报警
* key = deviceId_metric
* value = normal / warning / alarm
*/ */
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>(); private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
/**
* 主入口
*/
public void handle(MqttSensorDevice device, String topic, String payload) { 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); log.info("[ENV] deviceId={}, topic={}, payload={}", device.getId(), topic, payload);
JSONObject json = parseJson(payload); JSONObject json = parseJson(payload);
@@ -55,14 +65,48 @@ public class EnvSensorHandler {
TopicInfo topicInfo = parseTopic(topic); TopicInfo topicInfo = parseTopic(topic);
// 1数据入库 // 1⃣ 入库
saveData(device, topic, payload, topicInfo, val); saveData(device, topic, payload, topicInfo, val);
// 2状态检测(核心) // 2WebSocket推送
pushWebSocket(device, val);
// 3⃣ 事件检测
handleEvent(device, val); 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) { private void handleEvent(MqttSensorDevice device, SensorValue v) {
check(device, "temperature", v.temperature, "温度"); check(device, "temperature", v.temperature, "温度");
@@ -70,9 +114,13 @@ public class EnvSensorHandler {
check(device, "nh3", v.nh3, "氨气"); check(device, "nh3", v.nh3, "氨气");
check(device, "h2s", v.h2s, "硫化氢"); check(device, "h2s", v.h2s, "硫化氢");
// 电量(单独处理) // 电量
if (v.battery != null) { if (v.battery != null) {
MqttSensorThreshold t = thresholdService.getThreshold(device.getId(), "battery"); MqttSensorThreshold t = thresholdService.getThreshold(
device.getId(),
device.getDeptId(),
"battery"
);
if (t != null) { if (t != null) {
BigDecimal val = new BigDecimal(v.battery); BigDecimal val = new BigDecimal(v.battery);
String status = calcStatus(val, t); String status = calcStatus(val, t);
@@ -81,7 +129,7 @@ public class EnvSensorHandler {
} }
} }
// ================== 通用检测 ==================
private void check(MqttSensorDevice device, private void check(MqttSensorDevice device,
String metric, String metric,
BigDecimal value, BigDecimal value,
@@ -89,7 +137,12 @@ public class EnvSensorHandler {
if (value == null) return; if (value == null) return;
MqttSensorThreshold t = thresholdService.getThreshold(device.getId(), metric); MqttSensorThreshold t = thresholdService.getThreshold(
device.getId(),
device.getDeptId(),
metric
);
if (t == null) return; if (t == null) return;
String status = calcStatus(value, t); String status = calcStatus(value, t);
@@ -97,26 +150,18 @@ public class EnvSensorHandler {
changeStatus(device, metric, status, name + ":" + value); changeStatus(device, metric, status, name + ":" + value);
} }
// ================== 状态计算 ==================
private String calcStatus(BigDecimal value, MqttSensorThreshold t) { private String calcStatus(BigDecimal value, MqttSensorThreshold t) {
if (t.getAlarmMax() != null && value.compareTo(t.getAlarmMax()) > 0) { if (t.getAlarmMax() != null && value.compareTo(t.getAlarmMax()) > 0) return "alarm";
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.getAlarmMin() != null && value.compareTo(t.getAlarmMin()) < 0) { if (t.getWarnMin() != null && value.compareTo(t.getWarnMin()) < 0) return "warning";
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"; return "normal";
} }
// ================== 状态变更 ==================
private void changeStatus(MqttSensorDevice device, private void changeStatus(MqttSensorDevice device,
String metric, String metric,
String newStatus, String newStatus,
@@ -125,15 +170,10 @@ public class EnvSensorHandler {
String key = device.getId() + "_" + metric; String key = device.getId() + "_" + metric;
String oldStatus = STATUS_CACHE.get(key); String oldStatus = STATUS_CACHE.get(key);
// ❗ 防重复:状态没变,不处理 if (newStatus.equals(oldStatus)) return;
if (newStatus.equals(oldStatus)) {
return;
}
// 更新缓存
STATUS_CACHE.put(key, newStatus); STATUS_CACHE.put(key, newStatus);
// 状态变化 → 记录事件
if ("alarm".equals(newStatus)) { if ("alarm".equals(newStatus)) {
triggerEvent(device, "alarm", "HIGH", desc); triggerEvent(device, "alarm", "HIGH", desc);
} else if ("warning".equals(newStatus)) { } else if ("warning".equals(newStatus)) {
@@ -143,7 +183,7 @@ public class EnvSensorHandler {
} }
} }
// ================== 事件记录 ==================
private void triggerEvent(MqttSensorDevice device, private void triggerEvent(MqttSensorDevice device,
String type, String type,
String level, String level,
@@ -153,7 +193,10 @@ public class EnvSensorHandler {
device.getId(), type, level, desc); device.getId(), type, level, desc);
MqttSensorEvent event = new MqttSensorEvent(); MqttSensorEvent event = new MqttSensorEvent();
event.setDeviceId(device.getId()); event.setDeviceId(device.getId());
event.setDeptId(device.getDeptId());
event.setEventType(type); event.setEventType(type);
event.setLevel(level); event.setLevel(level);
event.setEventDesc(desc); event.setEventDesc(desc);
@@ -162,13 +205,11 @@ public class EnvSensorHandler {
event.setCreateTime(new Date()); event.setCreateTime(new Date());
eventService.insertMqttSensorEvent(event); eventService.insertMqttSensorEvent(event);
// TODO: D2D联动风机/排风)
// TODO: 短信通知
// TODO: APP推送
} }
// ================== 数据入库 ==================
// ================== 入库 ==================
private void saveData(MqttSensorDevice device, private void saveData(MqttSensorDevice device,
String topic, String topic,
String payload, String payload,
@@ -178,6 +219,8 @@ public class EnvSensorHandler {
MqttSensorData data = new MqttSensorData(); MqttSensorData data = new MqttSensorData();
data.setDeviceId(device.getId()); data.setDeviceId(device.getId());
data.setDeptId(device.getDeptId());
data.setTopic(topic); data.setTopic(topic);
data.setProject(topicInfo.project); data.setProject(topicInfo.project);
data.setWarehouse(topicInfo.warehouse); data.setWarehouse(topicInfo.warehouse);
@@ -197,7 +240,9 @@ public class EnvSensorHandler {
sensorDataService.insertMqttSensorData(data); sensorDataService.insertMqttSensorData(data);
} }
// ================== JSON解析 ==================
// ================== 工具 ==================
private JSONObject parseJson(String payload) { private JSONObject parseJson(String payload) {
try { try {
return JSONObject.parseObject(payload); return JSONObject.parseObject(payload);
@@ -207,7 +252,6 @@ public class EnvSensorHandler {
} }
} }
// ================== 构建数据 ==================
private SensorValue buildValue(JSONObject json) { private SensorValue buildValue(JSONObject json) {
SensorValue v = new SensorValue(); SensorValue v = new SensorValue();
v.temperature = json.getBigDecimal("temperature"); v.temperature = json.getBigDecimal("temperature");
@@ -218,7 +262,6 @@ public class EnvSensorHandler {
return v; return v;
} }
// ================== topic解析 ==================
private TopicInfo parseTopic(String topic) { private TopicInfo parseTopic(String topic) {
TopicInfo info = new TopicInfo(); TopicInfo info = new TopicInfo();
try { try {
@@ -231,7 +274,7 @@ public class EnvSensorHandler {
return info; return info;
} }
// ================== 内部结构 ==================
private static class SensorValue { private static class SensorValue {
BigDecimal temperature; BigDecimal temperature;
BigDecimal humidity; BigDecimal humidity;

View File

@@ -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 ? "通电" : "断电");
}
}

View File

@@ -6,63 +6,88 @@ import com.shzg.project.worn.domain.MqttSensorDevice;
import com.shzg.project.worn.domain.MqttSensorEvent; import com.shzg.project.worn.domain.MqttSensorEvent;
import com.shzg.project.worn.service.IMqttSensorDataService; import com.shzg.project.worn.service.IMqttSensorDataService;
import com.shzg.project.worn.service.IMqttSensorEventService; import com.shzg.project.worn.service.IMqttSensorEventService;
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 烟雾传感器 Handler
*/
@Slf4j @Slf4j
@Component @Component
public class SmokeSensorHandler { public class SmokeSensorHandler {
/**
* 状态缓存(防重复事件)
*/
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
@Autowired @Autowired
private IMqttSensorDataService dataService; private IMqttSensorDataService dataService;
@Autowired @Autowired
private IMqttSensorEventService eventService; private IMqttSensorEventService eventService;
/** @Autowired
* private WebSocketSessionManager sessionManager;
*/
public void handle(MqttSensorDevice device, String topic, String payload) { 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={}", log.info("[SMOKE] deviceId={}, deviceName={}, topic={}, payload={}",
device.getId(), device.getDeviceName(), topic, payload); device.getId(), device.getDeviceName(), topic, payload);
// ================== 1. JSON解析 ==================
JSONObject json; JSONObject json;
try { try {
json = JSONObject.parseObject(payload); json = JSONObject.parseObject(payload);
} catch (Exception e) { } catch (Exception e) {
log.error("[SMOKE] JSON解析失败 payload={}", payload, e); log.error("[SMOKE] parse payload failed payload={}", payload, e);
return; return;
} }
// ================== topic解析 ================== // ================== 2. topic解析 ==================
String project = null; String project = null;
String warehouse = null; String warehouse = null;
try { try {
String[] arr = topic.split("/"); String[] arr = topic.split("/");
if (arr.length >= 3) { if (arr.length >= 3) {
project = arr[1]; // tangshan project = arr[1];
warehouse = arr[2]; // dianchi warehouse = arr[2];
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[SMOKE] topic解析失败 topic={}", topic); log.warn("[SMOKE] parse topic failed topic={}", topic);
} }
// ================== 字段 ================== // ================== 3. 业务字段 ==================
String event = json.getString("event"); String event = json.getString("event");
Integer battery = json.getInteger("battery"); Integer battery = json.getInteger("battery");
Integer concentration = json.getInteger("concentration"); Integer concentration = json.getInteger("concentration");
Integer temperature = json.getInteger("temperature"); Integer temperature = json.getInteger("temperature");
// ================== 库 ================== // ================== 4. 数据落库 ==================
MqttSensorData data = new MqttSensorData(); MqttSensorData data = new MqttSensorData();
data.setDeviceId(device.getId()); data.setDeviceId(device.getId());
data.setDeptId(device.getDeptId());
data.setTopic(topic); data.setTopic(topic);
data.setProject(project); data.setProject(project);
data.setWarehouse(warehouse); data.setWarehouse(warehouse);
@@ -74,50 +99,110 @@ public class SmokeSensorHandler {
data.setConcentration(concentration != null ? concentration.longValue() : null); data.setConcentration(concentration != null ? concentration.longValue() : null);
data.setTemperature(temperature != null ? new BigDecimal(temperature) : null); data.setTemperature(temperature != null ? new BigDecimal(temperature) : null);
data.setCreateTime(new Date());
data.setIsDelete("0"); data.setIsDelete("0");
dataService.insertMqttSensorData(data); dataService.insertMqttSensorData(data);
// ================== 事件 ================== // ================== 5. WebSocket推送 ==================
pushWebSocket(device, event, battery, concentration, temperature);
// ================== 6. 事件逻辑 ==================
if ("silent".equals(event)) { if ("silent".equals(event)) {
insertEvent(device, "silent", "人为静音报警", "LOW"); changeStatus(device, "silent", "silent", "烟雾已消音", "LOW");
return; return;
} }
if ("alarm".equals(event) || (concentration != null && concentration > 0)) { if ("alarm".equals(event) || (concentration != null && concentration > 0)) {
insertEvent(device, "alarm", "烟雾报警", "HIGH"); changeStatus(device, "alarm", "alarm", "烟雾报警", "HIGH");
return; return;
} }
if ("removed".equals(event)) { if ("removed".equals(event)) {
insertEvent(device, "removed", "设备被拆/离位", "MEDIUM"); changeStatus(device, "removed", "removed", "设备被拆", "MEDIUM");
return; return;
} }
if ("low_battery".equals(event)) { if ("low_battery".equals(event)) {
insertEvent(device, "low_battery", "电量告警", "LOW"); changeStatus(device, "low_battery", "low_battery", "电量", "LOW");
return; return;
} }
if (battery != null && battery < 20) { changeStatus(device, "normal", "recovery", "烟雾恢复正常", "LOW");
log.info("[SMOKE] 电量偏低 deviceId={}, battery={}",
device.getId(), battery);
}
log.info("[SMOKE] 正常数据 deviceId={}, battery={}, temp={}", log.info("[SMOKE] normal data deviceId={}, battery={}, temp={}",
device.getId(), battery, temperature); 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(); MqttSensorEvent event = new MqttSensorEvent();
event.setDeviceId(device.getId()); event.setDeviceId(device.getId());
event.setDeptId(device.getDeptId());
event.setEventType(type); event.setEventType(type);
event.setEventDesc(desc); event.setEventDesc(desc);
event.setLevel(level); event.setLevel(level);
event.setStatus("0"); event.setStatus("0");
event.setIsDelete("0");
event.setCreateTime(new Date());
eventService.insertMqttSensorEvent(event); eventService.insertMqttSensorEvent(event);
} }

View File

@@ -6,17 +6,16 @@ import com.shzg.project.worn.domain.MqttSensorData;
import com.shzg.project.worn.domain.MqttSensorEvent; import com.shzg.project.worn.domain.MqttSensorEvent;
import com.shzg.project.worn.service.IMqttSensorDataService; import com.shzg.project.worn.service.IMqttSensorDataService;
import com.shzg.project.worn.service.IMqttSensorEventService; import com.shzg.project.worn.service.IMqttSensorEventService;
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/**
* 水浸传感器 Handler对齐 Env 结构:状态机 + 防重复)
*/
@Slf4j @Slf4j
@Component @Component
public class WaterSensorHandler { public class WaterSensorHandler {
@@ -27,86 +26,119 @@ public class WaterSensorHandler {
@Autowired @Autowired
private IMqttSensorEventService eventService; private IMqttSensorEventService eventService;
/** @Autowired
* 状态缓存(防重复报警) private WebSocketSessionManager sessionManager;
* key = deviceId_water
*/
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>(); private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
/**
* 主入口
*/
public void handle(MqttSensorDevice device, String topic, String payload) { 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未绑定deptIddeviceId={}", device.getId());
return;
}
log.info("[WATER] deviceId={}, topic={}, payload={}", device.getId(), topic, payload); log.info("[WATER] deviceId={}, topic={}, payload={}", device.getId(), topic, payload);
JSONObject json = parseJson(payload); JSONObject json = parseJson(payload);
if (json == null) return; if (json == null) return;
// 👉 从 data 字段解析Base64 WaterInfo info = parsePayload(json);
Integer water = parseWater(json); if (info == null) {
if (water == null) { log.warn("[WATER] payload解析失败");
log.warn("[WATER] 无法解析水浸状态");
return; return;
} }
TopicInfo topicInfo = parseTopic(topic); TopicInfo topicInfo = parseTopic(topic);
// 1⃣ 数据入库 // ================== 1. 入库 ==================
saveData(device, topic, payload, topicInfo, water); saveData(device, topic, payload, topicInfo, info);
// 2⃣ 状态处理 // ================== 2. 普通数据推送 ==================
handleEvent(device, water); 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) { private void handleEvent(MqttSensorDevice device, Integer water) {
String status; if (water == null) return;
// 👉 水浸1=有水报警0=正常 String status = (water == 1) ? "alarm" : "normal";
if (water == 1) { changeStatus(device, status);
status = "alarm";
} else {
status = "normal";
}
changeStatus(device, status, water);
} }
// ================== 状态变更 ==================
private void changeStatus(MqttSensorDevice device, private void changeStatus(MqttSensorDevice device, String newStatus) {
String newStatus,
Integer water) {
String key = device.getId() + "_water"; String key = device.getId() + "_water";
String oldStatus = STATUS_CACHE.get(key); String oldStatus = STATUS_CACHE.get(key);
// ❗ 防重复 if (newStatus.equals(oldStatus)) return;
if (newStatus.equals(oldStatus)) {
return;
}
STATUS_CACHE.put(key, newStatus); STATUS_CACHE.put(key, newStatus);
if ("alarm".equals(newStatus)) { if ("alarm".equals(newStatus)) {
triggerEvent(device, "alarm", "HIGH", "浸水预警!"); triggerEvent(device, "alarm", "HIGH", "浸水预警!");
} else if ("normal".equals(newStatus)) { } else {
triggerEvent(device, "recovery", "LOW", "水浸正常"); triggerEvent(device, "recovery", "LOW", "水浸恢复正常");
} }
} }
// ================== 事件记录 ==================
/**
* 🔥 事件写入 + 报警推送
*/
private void triggerEvent(MqttSensorDevice device, private void triggerEvent(MqttSensorDevice device,
String type, String type,
String level, String level,
String desc) { String desc) {
log.warn("[WATER事件] deviceId={}, type={}, level={}, desc={}", log.warn("[WATER事件] deviceId={}, type={}, desc={}",
device.getId(), type, level, desc); device.getId(), type, desc);
// ================== 1. 写库 ==================
MqttSensorEvent event = new MqttSensorEvent(); MqttSensorEvent event = new MqttSensorEvent();
event.setDeviceId(device.getId()); event.setDeviceId(device.getId());
// ✅ 核心(必须)
event.setDeptId(device.getDeptId());
event.setEventType(type); event.setEventType(type);
event.setLevel(level); event.setLevel(level);
event.setEventDesc(desc); event.setEventDesc(desc);
@@ -116,27 +148,51 @@ public class WaterSensorHandler {
eventService.insertMqttSensorEvent(event); 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, private void saveData(MqttSensorDevice device,
String topic, String topic,
String payload, String payload,
TopicInfo topicInfo, TopicInfo topicInfo,
Integer water) { WaterInfo info) {
MqttSensorData data = new MqttSensorData(); MqttSensorData data = new MqttSensorData();
data.setDeviceId(device.getId()); data.setDeviceId(device.getId());
// ✅ 数据隔离
data.setDeptId(device.getDeptId());
data.setTopic(topic); data.setTopic(topic);
data.setProject(topicInfo.project); data.setProject(topicInfo.project);
data.setWarehouse(topicInfo.warehouse); data.setWarehouse(topicInfo.warehouse);
data.setPayload(payload); data.setPayload(payload);
data.setDataJson(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.setCreateTime(new Date());
data.setIsDelete("0"); data.setIsDelete("0");
@@ -144,7 +200,8 @@ public class WaterSensorHandler {
sensorDataService.insertMqttSensorData(data); sensorDataService.insertMqttSensorData(data);
} }
// ================== JSON解析 ==================
// ================== JSON ==================
private JSONObject parseJson(String payload) { private JSONObject parseJson(String payload) {
try { try {
return JSONObject.parseObject(payload); return JSONObject.parseObject(payload);
@@ -154,46 +211,8 @@ public class WaterSensorHandler {
} }
} }
// ================== 解析水浸 ==================
private Integer parseWater(JSONObject json) {
try { // ================== topic ==================
// 👉 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解析 ==================
private TopicInfo parseTopic(String topic) { private TopicInfo parseTopic(String topic) {
TopicInfo info = new TopicInfo(); TopicInfo info = new TopicInfo();
try { try {
@@ -206,9 +225,61 @@ public class WaterSensorHandler {
return info; 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 { private static class TopicInfo {
String project; String project;
String warehouse; 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; }
}
} }

View File

@@ -1,8 +0,0 @@
package com.shzg.project.worn.service;
public interface IDeviceCommandService {
void smokeStop(Long deviceId);
void smokeStopOnce(Long deviceId);
}

View File

@@ -59,11 +59,14 @@ public interface IMqttSensorThresholdService
*/ */
public int deleteMqttSensorThresholdById(Long id); public int deleteMqttSensorThresholdById(Long id);
/** /**
* 根据设备ID和指标类型获取阈值 * 根据设备ID和部门ID获取阈值
* @param deviceId * @param id
* @param metricType * @param deptId
* @param battery
* @return * @return
*/ */
MqttSensorThreshold getThreshold(Long deviceId, String metricType); MqttSensorThreshold getThreshold(Long id, Long deptId, String battery);
} }

View File

@@ -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);
}

View File

@@ -74,4 +74,9 @@ public interface IWornInboundBillService
* @return 结果 * @return 结果
*/ */
int voidBill(String billNo); int voidBill(String billNo);
/**
* 生成入库单PDF
*/
byte[] generatePdf(Long id) throws Exception;
} }

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -2,6 +2,8 @@ package com.shzg.project.worn.service.impl;
import java.util.List; import java.util.List;
import com.shzg.common.utils.DateUtils; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.MqttSensorDataMapper; import com.shzg.project.worn.mapper.MqttSensorDataMapper;
@@ -41,6 +43,10 @@ public class MqttSensorDataServiceImpl implements IMqttSensorDataService
@Override @Override
public List<MqttSensorData> selectMqttSensorDataList(MqttSensorData mqttSensorData) public List<MqttSensorData> selectMqttSensorDataList(MqttSensorData mqttSensorData)
{ {
if (!SecurityUtils.isAdmin())
{
mqttSensorData.setDeptIds(DeptScopeUtils.getDeptScope());
}
return mqttSensorDataMapper.selectMqttSensorDataList(mqttSensorData); return mqttSensorDataMapper.selectMqttSensorDataList(mqttSensorData);
} }

View File

@@ -2,6 +2,8 @@ package com.shzg.project.worn.service.impl;
import java.util.List; import java.util.List;
import com.shzg.common.utils.DateUtils; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.MqttSensorEventMapper; import com.shzg.project.worn.mapper.MqttSensorEventMapper;
@@ -41,6 +43,12 @@ public class MqttSensorEventServiceImpl implements IMqttSensorEventService
@Override @Override
public List<MqttSensorEvent> selectMqttSensorEventList(MqttSensorEvent mqttSensorEvent) public List<MqttSensorEvent> selectMqttSensorEventList(MqttSensorEvent mqttSensorEvent)
{ {
// ================= 数据权限控制 =================
if (!SecurityUtils.isAdmin())
{
mqttSensorEvent.setDeptIds(DeptScopeUtils.getDeptScope());
}
return mqttSensorEventMapper.selectMqttSensorEventList(mqttSensorEvent); return mqttSensorEventMapper.selectMqttSensorEventList(mqttSensorEvent);
} }

View File

@@ -1,7 +1,13 @@
package com.shzg.project.worn.service.impl; package com.shzg.project.worn.service.impl;
import java.util.List; 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.DateUtils;
import com.shzg.common.utils.DeptScopeUtils;
import com.shzg.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.MqttSensorThresholdMapper; 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; import com.shzg.project.worn.service.IMqttSensorThresholdService;
/** /**
* 传感器阈值配置Service业务层处理 * 传感器阈值配置Service业务层处理(最终版)
* *
* @author shzg * 规则:
* @date 2026-04-01 * 1. 设备优先
* 2. 仓库兜底
* 3. 统一入口(无旧方法)
*/ */
@Service @Service
public class MqttSensorThresholdServiceImpl implements IMqttSensorThresholdService public class MqttSensorThresholdServiceImpl implements IMqttSensorThresholdService
{ {
/**
* 阈值缓存
* key: deviceId:deptId:metric
*/
private final ConcurrentMap<String, Optional<MqttSensorThreshold>> thresholdCache = new ConcurrentHashMap<>();
@Autowired @Autowired
private MqttSensorThresholdMapper mqttSensorThresholdMapper; private MqttSensorThresholdMapper mqttSensorThresholdMapper;
/** // ==================== 基础CRUD ====================
* 查询传感器阈值配置
*
* @param id 传感器阈值配置主键
* @return 传感器阈值配置
*/
@Override @Override
public MqttSensorThreshold selectMqttSensorThresholdById(Long id) public MqttSensorThreshold selectMqttSensorThresholdById(Long id)
{ {
return mqttSensorThresholdMapper.selectMqttSensorThresholdById(id); return mqttSensorThresholdMapper.selectMqttSensorThresholdById(id);
} }
/**
* 查询传感器阈值配置列表
*
* @param mqttSensorThreshold 传感器阈值配置
* @return 传感器阈值配置
*/
@Override @Override
public List<MqttSensorThreshold> selectMqttSensorThresholdList(MqttSensorThreshold mqttSensorThreshold) public List<MqttSensorThreshold> selectMqttSensorThresholdList(MqttSensorThreshold mqttSensorThreshold)
{ {
if (!SecurityUtils.isAdmin())
{
mqttSensorThreshold.setDeptIds(DeptScopeUtils.getDeptScope());
}
return mqttSensorThresholdMapper.selectMqttSensorThresholdList(mqttSensorThreshold); return mqttSensorThresholdMapper.selectMqttSensorThresholdList(mqttSensorThreshold);
} }
/**
* 新增传感器阈值配置
*
* @param mqttSensorThreshold 传感器阈值配置
* @return 结果
*/
@Override @Override
public int insertMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold) public int insertMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold)
{ {
mqttSensorThreshold.setCreateTime(DateUtils.getNowDate()); mqttSensorThreshold.setCreateTime(DateUtils.getNowDate());
return mqttSensorThresholdMapper.insertMqttSensorThreshold(mqttSensorThreshold); int rows = mqttSensorThresholdMapper.insertMqttSensorThreshold(mqttSensorThreshold);
clearThresholdCache();
return rows;
} }
/**
* 修改传感器阈值配置
*
* @param mqttSensorThreshold 传感器阈值配置
* @return 结果
*/
@Override @Override
public int updateMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold) public int updateMqttSensorThreshold(MqttSensorThreshold mqttSensorThreshold)
{ {
mqttSensorThreshold.setUpdateTime(DateUtils.getNowDate()); mqttSensorThreshold.setUpdateTime(DateUtils.getNowDate());
return mqttSensorThresholdMapper.updateMqttSensorThreshold(mqttSensorThreshold); int rows = mqttSensorThresholdMapper.updateMqttSensorThreshold(mqttSensorThreshold);
clearThresholdCache();
return rows;
} }
/**
* 批量删除传感器阈值配置
*
* @param ids 需要删除的传感器阈值配置主键
* @return 结果
*/
@Override @Override
public int deleteMqttSensorThresholdByIds(Long[] ids) public int deleteMqttSensorThresholdByIds(Long[] ids)
{ {
return mqttSensorThresholdMapper.deleteMqttSensorThresholdByIds(ids); int rows = mqttSensorThresholdMapper.deleteMqttSensorThresholdByIds(ids);
clearThresholdCache();
return rows;
} }
/**
* 删除传感器阈值配置信息
*
* @param id 传感器阈值配置主键
* @return 结果
*/
@Override @Override
public int deleteMqttSensorThresholdById(Long id) public int deleteMqttSensorThresholdById(Long id)
{ {
return mqttSensorThresholdMapper.deleteMqttSensorThresholdById(id); int rows = mqttSensorThresholdMapper.deleteMqttSensorThresholdById(id);
clearThresholdCache();
return rows;
} }
/** /**
* 根据设备id和指标类型获取阈值配置 * 获取阈值(设备优先 → 仓库兜底)
* @param deviceId
* @param metricType
* @return
*/ */
@Override @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();
}
}

View File

@@ -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);
}
}

View File

@@ -1,7 +1,27 @@
package com.shzg.project.worn.service.impl; 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.Date;
import java.util.List; 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.DateUtils;
import com.shzg.common.utils.SecurityUtils; import com.shzg.common.utils.SecurityUtils;
import com.shzg.common.utils.StringUtils; 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.domain.dto.WornInboundPartialFinishItemDTO;
import com.shzg.project.worn.mapper.WornInboundItemMapper; import com.shzg.project.worn.mapper.WornInboundItemMapper;
import org.springframework.beans.factory.annotation.Autowired; 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 org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.WornInboundBillMapper; import com.shzg.project.worn.mapper.WornInboundBillMapper;
import com.shzg.project.worn.domain.WornInboundBill; import com.shzg.project.worn.domain.WornInboundBill;
import com.shzg.project.worn.service.IWornInboundBillService; import com.shzg.project.worn.service.IWornInboundBillService;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* 入库库存Service业务层处理 * 入库库存Service业务层处理
* *
@@ -27,7 +51,7 @@ import org.springframework.transaction.annotation.Transactional;
* @date 2026-03-26 * @date 2026-03-26
*/ */
@Service @Service
public class WornInboundBillServiceImpl implements IWornInboundBillService public class WornInboundBillServiceImpl implements IWornInboundBillService
{ {
@Autowired @Autowired
private WornInboundBillMapper wornInboundBillMapper; private WornInboundBillMapper wornInboundBillMapper;
@@ -41,6 +65,12 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
@Autowired @Autowired
private WornUniqueCodeEventMapper eventMapper; 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. 主表赋值 ================== */ /* ================== 3. 主表赋值 ================== */
wornInboundBill.setCreateTime(DateUtils.getNowDate()); wornInboundBill.setCreateTime(DateUtils.getNowDate());
wornInboundBill.setCreateBy(String.valueOf(userId)); wornInboundBill.setCreateBy(String.valueOf(userId));
wornInboundBill.setInboundTime(DateUtils.getNowDate());
wornInboundBill.setBillType("0"); wornInboundBill.setBillType("0");
wornInboundBill.setStatus("0"); wornInboundBill.setStatus("0");
wornInboundBill.setIsDelete("0"); wornInboundBill.setIsDelete("0");
@@ -475,7 +506,189 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
return 1; 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);
}
} }

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -16,7 +16,7 @@ ruoyi:
# 开发环境配置 # 开发环境配置
server: server:
# 服务器的HTTP端口默认为8080 # 服务器的HTTP端口默认为8080
port: 8082 port: 8081
servlet: servlet:
# 应用的访问路径 # 应用的访问路径
context-path: / context-path: /
@@ -178,4 +178,7 @@ mqtt:
timeout: 10 timeout: 10
# 默认 QoS # 默认 QoS
qos: 1 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

Binary file not shown.

View File

@@ -47,7 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by d.parent_id, d.order_num order by d.parent_id, d.order_num
</select> </select>
<select id="selectDeptListByRoleId" resultType="Long"> <select id="selectDeptListByRoleId" resultType="java.lang.Long">
select d.dept_id select d.dept_id
from sys_dept d from sys_dept d
left join sys_role_dept rd on d.dept_id = rd.dept_id 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}, '%') AND dept_name LIKE CONCAT('%', #{keyword}, '%')
</if> </if>
</select> </select>
<select id="selectDeptAndChildIds" resultType="java.lang.Long">
SELECT dept_id
FROM sys_dept
WHERE FIND_IN_SET(#{deptId}, ancestors)
</select>
</mapper> </mapper>

View File

@@ -45,21 +45,27 @@
FROM worn_unique_code t FROM worn_unique_code t
LEFT JOIN sys_dept d ON t.project_id = d.dept_id LEFT JOIN sys_dept d ON t.project_id = d.dept_id
<where> <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 != ''"> <if test="code != null and code != ''">
AND t.code = #{code} AND t.code = #{code}
</if> </if>
<if test="billNo != null and billNo != ''"> <if test="billNo != null and billNo != ''">
AND t.bill_no = #{billNo} AND t.bill_no = #{billNo}
</if> </if>
<if test="projectId != null">
AND t.project_id = #{projectId} <if test="status != null">
</if>
<if test="status != null and status != ''">
AND t.status = #{status} AND t.status = #{status}
</if> </if>
<if test="rfidCode != null and rfidCode != ''"> <if test="rfidCode != null and rfidCode != ''">
AND t.rfid_code = #{rfidCode} AND t.rfid_code = #{rfidCode}
</if> </if>
@@ -70,6 +76,7 @@
<select id="selectWornUniqueCodeById" parameterType="Long" resultMap="WornUniqueCodeResult"> <select id="selectWornUniqueCodeById" parameterType="Long" resultMap="WornUniqueCodeResult">
<include refid="selectWornUniqueCodeVo"/> <include refid="selectWornUniqueCodeVo"/>
where id = #{id} where id = #{id}
AND (is_delete = '0' OR is_delete IS NULL)
</select> </select>
<select id="selectIdByCode" resultType="java.lang.Long"> <select id="selectIdByCode" resultType="java.lang.Long">
@@ -136,6 +143,7 @@
update_by = #{updateBy}, update_by = #{updateBy},
update_time = #{updateTime} update_time = #{updateTime}
where code = #{code} where code = #{code}
AND (is_delete = '0' OR is_delete IS NULL)
</update> </update>
<delete id="deleteWornUniqueCodeByIds" parameterType="Long"> <delete id="deleteWornUniqueCodeByIds" parameterType="Long">
@@ -144,6 +152,7 @@
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
<select id="selectByCode" resultType="com.shzg.project.unique.domain.WornUniqueCode"> <select id="selectByCode" resultType="com.shzg.project.unique.domain.WornUniqueCode">
SELECT SELECT
id, id,
@@ -196,7 +205,7 @@
WHERE uc.code = #{code} WHERE uc.code = #{code}
AND (uc.is_delete = '0' OR uc.is_delete IS NULL) 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) AND (m.is_delete = '0' OR m.is_delete IS NULL)
ORDER BY ucm.id ASC ORDER BY ucm.id ASC

View File

@@ -7,6 +7,8 @@
<resultMap type="MqttSensorData" id="MqttSensorDataResult"> <resultMap type="MqttSensorData" id="MqttSensorDataResult">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="deviceId" column="device_id"/> <result property="deviceId" column="device_id"/>
<result property="deptId" column="dept_id"/>
<result property="topic" column="topic"/> <result property="topic" column="topic"/>
<result property="project" column="project"/> <result property="project" column="project"/>
<result property="warehouse" column="warehouse"/> <result property="warehouse" column="warehouse"/>
@@ -18,7 +20,6 @@
<result property="nh3" column="nh3"/> <result property="nh3" column="nh3"/>
<result property="h2s" column="h2s"/> <result property="h2s" column="h2s"/>
<result property="concentration" column="concentration"/> <result property="concentration" column="concentration"/>
<result property="waterStatus" column="water_status"/> <result property="waterStatus" column="water_status"/>
<result property="remark" column="remark"/> <result property="remark" column="remark"/>
@@ -31,33 +32,57 @@
<sql id="selectMqttSensorDataVo"> <sql id="selectMqttSensorDataVo">
select select
id, device_id, topic, project, warehouse, payload, data_json, id,
battery, temperature, humidity, nh3, h2s, concentration, device_id,
water_status, dept_id,
remark, create_by, create_time, update_by, update_time, is_delete 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 from mqtt_sensor_data
</sql> </sql>
<select id="selectMqttSensorDataList" parameterType="MqttSensorData" resultMap="MqttSensorDataResult"> <select id="selectMqttSensorDataList" parameterType="MqttSensorData" resultMap="MqttSensorDataResult">
<include refid="selectMqttSensorDataVo"/> <include refid="selectMqttSensorDataVo"/>
<where> <where>
<if test="deviceId != null "> and device_id = #{deviceId}</if> <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="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> </where>
order by create_time desc
</select> </select>
<select id="selectMqttSensorDataById" parameterType="Long" resultMap="MqttSensorDataResult"> <select id="selectMqttSensorDataById" parameterType="Long" resultMap="MqttSensorDataResult">
@@ -69,6 +94,8 @@
insert into mqtt_sensor_data insert into mqtt_sensor_data
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceId != null">device_id,</if> <if test="deviceId != null">device_id,</if>
<if test="deptId != null">dept_id,</if>
<if test="topic != null">topic,</if> <if test="topic != null">topic,</if>
<if test="project != null">project,</if> <if test="project != null">project,</if>
<if test="warehouse != null">warehouse,</if> <if test="warehouse != null">warehouse,</if>
@@ -80,7 +107,6 @@
<if test="nh3 != null">nh3,</if> <if test="nh3 != null">nh3,</if>
<if test="h2s != null">h2s,</if> <if test="h2s != null">h2s,</if>
<if test="concentration != null">concentration,</if> <if test="concentration != null">concentration,</if>
<if test="waterStatus != null">water_status,</if> <if test="waterStatus != null">water_status,</if>
<if test="remark != null">remark,</if> <if test="remark != null">remark,</if>
@@ -90,8 +116,11 @@
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if> <if test="isDelete != null">is_delete,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if> <if test="deviceId != null">#{deviceId},</if>
<if test="deptId != null">#{deptId},</if>
<if test="topic != null">#{topic},</if> <if test="topic != null">#{topic},</if>
<if test="project != null">#{project},</if> <if test="project != null">#{project},</if>
<if test="warehouse != null">#{warehouse},</if> <if test="warehouse != null">#{warehouse},</if>
@@ -104,6 +133,7 @@
<if test="h2s != null">#{h2s},</if> <if test="h2s != null">#{h2s},</if>
<if test="concentration != null">#{concentration},</if> <if test="concentration != null">#{concentration},</if>
<if test="waterStatus != null">#{waterStatus},</if> <if test="waterStatus != null">#{waterStatus},</if>
<if test="remark != null">#{remark},</if> <if test="remark != null">#{remark},</if>
<if test="createBy != null">#{createBy},</if> <if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if> <if test="createTime != null">#{createTime},</if>
@@ -117,6 +147,8 @@
update mqtt_sensor_data update mqtt_sensor_data
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">device_id = #{deviceId},</if> <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="topic != null">topic = #{topic},</if>
<if test="project != null">project = #{project},</if> <if test="project != null">project = #{project},</if>
<if test="warehouse != null">warehouse = #{warehouse},</if> <if test="warehouse != null">warehouse = #{warehouse},</if>
@@ -128,8 +160,6 @@
<if test="nh3 != null">nh3 = #{nh3},</if> <if test="nh3 != null">nh3 = #{nh3},</if>
<if test="h2s != null">h2s = #{h2s},</if> <if test="h2s != null">h2s = #{h2s},</if>
<if test="concentration != null">concentration = #{concentration},</if> <if test="concentration != null">concentration = #{concentration},</if>
<!-- ✅ 新增 -->
<if test="waterStatus != null">water_status = #{waterStatus},</if> <if test="waterStatus != null">water_status = #{waterStatus},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
@@ -152,4 +182,5 @@
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@@ -1,40 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorEventMapper"> <mapper namespace="com.shzg.project.worn.mapper.MqttSensorEventMapper">
<resultMap type="MqttSensorEvent" id="MqttSensorEventResult"> <resultMap type="MqttSensorEvent" id="MqttSensorEventResult">
<result property="id" column="id" /> <result property="id" column="id"/>
<result property="deviceId" column="device_id" /> <result property="deviceId" column="device_id"/>
<result property="eventType" column="event_type" /> <result property="deptId" column="dept_id"/>
<result property="eventDesc" column="event_desc" /> <result property="eventType" column="event_type"/>
<result property="level" column="level" /> <result property="eventDesc" column="event_desc"/>
<result property="status" column="status" /> <result property="level" column="level"/>
<result property="remark" column="remark" /> <result property="status" column="status"/>
<result property="createBy" column="create_by" /> <result property="remark" column="remark"/>
<result property="createTime" column="create_time" /> <result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by" /> <result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time" /> <result property="updateBy" column="update_by"/>
<result property="isDelete" column="is_delete" /> <result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
</resultMap> </resultMap>
<sql id="selectMqttSensorEventVo"> <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> </sql>
<select id="selectMqttSensorEventList" parameterType="MqttSensorEvent" resultMap="MqttSensorEventResult"> <select id="selectMqttSensorEventList" parameterType="MqttSensorEvent" resultMap="MqttSensorEventResult">
<include refid="selectMqttSensorEventVo"/> <include refid="selectMqttSensorEventVo"/>
<where> <where>
<if test="deviceId != null "> and device_id = #{deviceId}</if> <if test="deviceId != null">and device_id = #{deviceId}</if>
<if test="eventType != null and eventType != ''"> and event_type = #{eventType}</if> <if test="deptId != null">and dept_id = #{deptId}</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="deptIds != null and deptIds.size > 0">
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if> 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> </where>
order by create_time desc
</select> </select>
<select id="selectMqttSensorEventById" parameterType="Long" resultMap="MqttSensorEventResult"> <select id="selectMqttSensorEventById" parameterType="Long" resultMap="MqttSensorEventResult">
<include refid="selectMqttSensorEventVo"/> <include refid="selectMqttSensorEventVo"/>
where id = #{id} where id = #{id}
@@ -44,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into mqtt_sensor_event insert into mqtt_sensor_event
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceId != null">device_id,</if> <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="eventType != null and eventType != ''">event_type,</if>
<if test="eventDesc != null">event_desc,</if> <if test="eventDesc != null">event_desc,</if>
<if test="level != null">level,</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="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if> <if test="isDelete != null">is_delete,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if> <if test="deviceId != null">#{deviceId},</if>
<if test="deptId != null">#{deptId},</if>
<if test="eventType != null and eventType != ''">#{eventType},</if> <if test="eventType != null and eventType != ''">#{eventType},</if>
<if test="eventDesc != null">#{eventDesc},</if> <if test="eventDesc != null">#{eventDesc},</if>
<if test="level != null">#{level},</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="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
<if test="isDelete != null">#{isDelete},</if> <if test="isDelete != null">#{isDelete},</if>
</trim> </trim>
</insert> </insert>
<update id="updateMqttSensorEvent" parameterType="MqttSensorEvent"> <update id="updateMqttSensorEvent" parameterType="MqttSensorEvent">
update mqtt_sensor_event update mqtt_sensor_event
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">device_id = #{deviceId},</if> <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="eventType != null and eventType != ''">event_type = #{eventType},</if>
<if test="eventDesc != null">event_desc = #{eventDesc},</if> <if test="eventDesc != null">event_desc = #{eventDesc},</if>
<if test="level != null">level = #{level},</if> <if test="level != null">level = #{level},</if>
@@ -93,9 +109,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete> </delete>
<delete id="deleteMqttSensorEventByIds" parameterType="String"> <delete id="deleteMqttSensorEventByIds" parameterType="String">
delete from mqtt_sensor_event where id in delete from mqtt_sensor_event where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@@ -1,46 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorThresholdMapper"> <mapper namespace="com.shzg.project.worn.mapper.MqttSensorThresholdMapper">
<resultMap type="MqttSensorThreshold" id="MqttSensorThresholdResult"> <resultMap type="MqttSensorThreshold" id="MqttSensorThresholdResult">
<result property="id" column="id" /> <result property="id" column="id"/>
<result property="deviceId" column="device_id" /> <result property="deviceId" column="device_id"/>
<result property="metricType" column="metric_type" /> <result property="deptId" column="dept_id"/>
<result property="warnMin" column="warn_min" /> <result property="metricType" column="metric_type"/>
<result property="warnMax" column="warn_max" /> <result property="warnMin" column="warn_min"/>
<result property="alarmMin" column="alarm_min" /> <result property="warnMax" column="warn_max"/>
<result property="alarmMax" column="alarm_max" /> <result property="alarmMin" column="alarm_min"/>
<result property="unit" column="unit" /> <result property="alarmMax" column="alarm_max"/>
<result property="status" column="status" /> <result property="unit" column="unit"/>
<result property="remark" column="remark" /> <result property="status" column="status"/>
<result property="createBy" column="create_by" /> <result property="remark" column="remark"/>
<result property="createTime" column="create_time" /> <result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by" /> <result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time" /> <result property="updateBy" column="update_by"/>
<result property="isDelete" column="is_delete" /> <result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
</resultMap> </resultMap>
<sql id="selectMqttSensorThresholdVo"> <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> </sql>
<select id="selectMqttSensorThresholdList" parameterType="MqttSensorThreshold" resultMap="MqttSensorThresholdResult"> <select id="selectMqttSensorThresholdList" parameterType="MqttSensorThreshold" resultMap="MqttSensorThresholdResult">
<include refid="selectMqttSensorThresholdVo"/> <include refid="selectMqttSensorThresholdVo"/>
<where> <where>
<if test="deviceId != null "> and device_id = #{deviceId}</if> <if test="deviceId != null">and device_id = #{deviceId}</if>
<if test="metricType != null and metricType != ''"> and metric_type = #{metricType}</if> <if test="deptId != null">and dept_id = #{deptId}</if>
<if test="warnMin != null "> and warn_min = #{warnMin}</if>
<if test="warnMax != null "> and warn_max = #{warnMax}</if> <if test="deptIds != null and deptIds.size > 0">
<if test="alarmMin != null "> and alarm_min = #{alarmMin}</if> and dept_id in
<if test="alarmMax != null "> and alarm_max = #{alarmMax}</if> <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
<if test="unit != null and unit != ''"> and unit = #{unit}</if> #{deptId}
<if test="status != null and status != ''"> and status = #{status}</if> </foreach>
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if> </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> </where>
</select> </select>
<select id="selectMqttSensorThresholdById" parameterType="Long" resultMap="MqttSensorThresholdResult"> <select id="selectMqttSensorThresholdById" parameterType="Long" resultMap="MqttSensorThresholdResult">
<include refid="selectMqttSensorThresholdVo"/> <include refid="selectMqttSensorThresholdVo"/>
where id = #{id} where id = #{id}
@@ -50,6 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into mqtt_sensor_threshold insert into mqtt_sensor_threshold
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceId != null">device_id,</if> <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="metricType != null and metricType != ''">metric_type,</if>
<if test="warnMin != null">warn_min,</if> <if test="warnMin != null">warn_min,</if>
<if test="warnMax != null">warn_max,</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="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if> <if test="isDelete != null">is_delete,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if> <if test="deviceId != null">#{deviceId},</if>
<if test="deptId != null">#{deptId},</if>
<if test="metricType != null and metricType != ''">#{metricType},</if> <if test="metricType != null and metricType != ''">#{metricType},</if>
<if test="warnMin != null">#{warnMin},</if> <if test="warnMin != null">#{warnMin},</if>
<if test="warnMax != null">#{warnMax},</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="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
<if test="isDelete != null">#{isDelete},</if> <if test="isDelete != null">#{isDelete},</if>
</trim> </trim>
</insert> </insert>
<update id="updateMqttSensorThreshold" parameterType="MqttSensorThreshold"> <update id="updateMqttSensorThreshold" parameterType="MqttSensorThreshold">
update mqtt_sensor_threshold update mqtt_sensor_threshold
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">device_id = #{deviceId},</if> <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="metricType != null and metricType != ''">metric_type = #{metricType},</if>
<if test="warnMin != null">warn_min = #{warnMin},</if> <if test="warnMin != null">warn_min = #{warnMin},</if>
<if test="warnMax != null">warn_max = #{warnMax},</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="unit != null">unit = #{unit},</if>
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</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="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if> <if test="updateTime != null">update_time = #{updateTime},</if>
<if test="isDelete != null">is_delete = #{isDelete},</if> <if test="isDelete != null">is_delete = #{isDelete},</if>
@@ -108,12 +121,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete> </delete>
<delete id="deleteMqttSensorThresholdByIds" parameterType="String"> <delete id="deleteMqttSensorThresholdByIds" parameterType="String">
delete from mqtt_sensor_threshold where id in delete from mqtt_sensor_threshold where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
<!-- 按设备+指标查询 -->
<select id="selectByDeviceAndMetric" resultMap="MqttSensorThresholdResult"> <select id="selectByDeviceAndMetric" resultMap="MqttSensorThresholdResult">
select * select *
from mqtt_sensor_threshold from mqtt_sensor_threshold
@@ -123,4 +137,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and is_delete = '0' and is_delete = '0'
limit 1 limit 1
</select> </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> </mapper>

View 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>
<!-- ================== 查询启用的TopicMQTT用 ================== -->
<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>

View File

@@ -35,6 +35,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="typeParentNames" column="type_parent_names"/> <result property="typeParentNames" column="type_parent_names"/>
<result property="unitId" column="unit_id"/> <result property="unitId" column="unit_id"/>
<result property="unitName" column="unit_name"/> <result property="unitName" column="unit_name"/>
<result property="operatorName" column="operator_name"/>
</resultMap> </resultMap>
<sql id="selectWornInboundItemVo"> <sql id="selectWornInboundItemVo">
@@ -193,22 +194,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
parameterType="Long" parameterType="Long"
resultMap="WornInboundItemResult"> resultMap="WornInboundItemResult">
SELECT SELECT
id, i.id,
bill_id, i.bill_id,
bill_no, i.bill_no,
material_id, i.material_id,
quantity, i.quantity,
unique_code, i.unique_code,
remark, i.remark,
status, i.status,
create_by, i.create_by,
create_time, i.create_time,
update_by, i.update_by,
update_time, i.update_time,
is_delete i.is_delete,
FROM worn_inbound_item b.inbound_time,
WHERE bill_id = #{billId} b.warehouse_code,
AND (is_delete = '0' OR is_delete IS NULL) b.warehouse_name,
ORDER BY id ASC 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> </select>
</mapper> </mapper>