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

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",
"/unique/code/**",
"/device/cmd/**",
"/worn/socket/**",
"/worn/inboundBill/**",
"/ws/**",
"/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()

View File

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

View File

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

View File

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

View File

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

View File

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

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 javax.servlet.http.HttpServletResponse;
import com.shzg.common.utils.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;

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

View File

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

View File

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

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 String unitName;
private String operatorName;
/** 主单据状态 */
private String billStatus;
@@ -237,6 +238,15 @@ public class WornInboundItem extends BaseEntity
{
return unitName;
}
public String getOperatorName()
{
return operatorName;
}
public void setOperatorName(String operatorName)
{
this.operatorName = operatorName;
}
public void setStatus(String status)
{
this.status = status;

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

View File

@@ -8,11 +8,10 @@ import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
/**
* MQTT 消息发布客户端
* MQTT 消息发布客户端(最终版 - LoRaWAN专用
*
* 支持
* 1. JSON消息发送
* 2. HEX指令发送设备控制
* 所有下行统一使用
* JSON + Base64
*/
@Slf4j
@Component
@@ -24,7 +23,7 @@ public class MqttPublishClient {
this.mqttClient = mqttClient;
}
// ================== JSON发送 ==================
// ================== ⭐ 标准下行发送 ==================
public void publish(String topic, String payload) {
publish(topic, payload, 1, false);
}
@@ -39,38 +38,12 @@ public class MqttPublishClient {
mqttClient.publish(topic, msg);
log.info("[MQTT] JSON发送成功 topic={}, payload={}, qos={}, retained={}",
log.info("[MQTT] 下行发送成功 topic={}, payload={}, qos={}, retained={}",
topic, payload, qos, retained);
} catch (Exception e) {
log.error("[MQTT] JSON发送失败 topic=" + topic + ", payload=" + payload, e);
throw new RuntimeException("MQTT JSON发送失败", e);
}
}
// ================== ⭐ HEX发送 ==================
public void publishHex(String topic, String hexPayload) {
publishHex(topic, hexPayload, 1, false);
}
public void publishHex(String topic, String hexPayload, int qos, boolean retained) {
checkClient();
try {
byte[] bytes = hexStringToByteArray(hexPayload);
MqttMessage msg = new MqttMessage(bytes);
msg.setQos(qos);
msg.setRetained(retained);
mqttClient.publish(topic, msg);
log.info("[MQTT] HEX发送成功 topic={}, hex={}, length={}, qos={}, retained={}",
topic, hexPayload, bytes.length, qos, retained);
} catch (Exception e) {
log.error("[MQTT] HEX发送失败 topic=" + topic + ", hex=" + hexPayload, e);
throw new RuntimeException("MQTT HEX发送失败", e);
log.error("[MQTT] 下行发送失败 topic=" + topic + ", payload=" + payload, e);
throw new RuntimeException("MQTT发送失败", e);
}
}
@@ -83,41 +56,4 @@ public class MqttPublishClient {
throw new IllegalStateException("MQTT客户端未连接到Broker");
}
}
// ================== HEX工具 ==================
/**
* HEX字符串 → byte[]
* 支持:
* - 大小写
* - 自动去空格
*
* 示例:
* 2218000000C6 → [0x22,0x18,0x00,0x00,0x00,0xC6]
*/
private byte[] hexStringToByteArray(String hex) {
if (hex == null || hex.trim().isEmpty()) {
throw new IllegalArgumentException("HEX字符串不能为空");
}
// 去空格 + 转大写
hex = hex.replace(" ", "").toUpperCase();
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException("HEX字符串长度必须为偶数" + hex);
}
int len = hex.length();
byte[] data = new byte[len / 2];
try {
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
}
} catch (Exception e) {
throw new IllegalArgumentException("HEX字符串解析失败" + hex, e);
}
return data;
}
}

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

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

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

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.service.IMqttSensorDataService;
import com.shzg.project.worn.service.IMqttSensorEventService;
import com.shzg.project.worn.websocket.manager.WebSocketSessionManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 水浸传感器 Handler对齐 Env 结构:状态机 + 防重复)
*/
@Slf4j
@Component
public class WaterSensorHandler {
@@ -27,86 +26,119 @@ public class WaterSensorHandler {
@Autowired
private IMqttSensorEventService eventService;
/**
* 状态缓存(防重复报警)
* key = deviceId_water
*/
@Autowired
private WebSocketSessionManager sessionManager;
private static final Map<String, String> STATUS_CACHE = new ConcurrentHashMap<>();
/**
* 主入口
*/
public void handle(MqttSensorDevice device, String topic, String payload) {
// ================= 安全校验 =================
if (device == null) {
log.error("[WATER] device为空");
return;
}
if (device.getDeptId() == null) {
log.error("[WATER] device未绑定deptIddeviceId={}", device.getId());
return;
}
log.info("[WATER] deviceId={}, topic={}, payload={}", device.getId(), topic, payload);
JSONObject json = parseJson(payload);
if (json == null) return;
// 👉 从 data 字段解析Base64
Integer water = parseWater(json);
if (water == null) {
log.warn("[WATER] 无法解析水浸状态");
WaterInfo info = parsePayload(json);
if (info == null) {
log.warn("[WATER] payload解析失败");
return;
}
TopicInfo topicInfo = parseTopic(topic);
// 1⃣ 数据入库
saveData(device, topic, payload, topicInfo, water);
// ================== 1. 入库 ==================
saveData(device, topic, payload, topicInfo, info);
// 2⃣ 状态处理
handleEvent(device, water);
// ================== 2. 普通数据推送 ==================
pushWebSocket(device, info);
// ================== 3. 事件处理 ==================
handleEvent(device, info.getWater());
}
// ================== WebSocket推送 ==================
private void pushWebSocket(MqttSensorDevice device, WaterInfo info) {
try {
JSONObject msg = new JSONObject();
msg.put("type", "water");
msg.put("deviceId", device.getId());
msg.put("deviceName", device.getDeviceName());
msg.put("deptId", device.getDeptId());
msg.put("battery", info.getBattery());
msg.put("water", info.getWater());
msg.put("status", (info.getWater() != null && info.getWater() == 1) ? "alarm" : "normal");
msg.put("time", System.currentTimeMillis());
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
} catch (Exception e) {
log.error("[WATER] WebSocket推送失败 deviceId={}", device.getId(), e);
}
}
// ================== 事件处理 ==================
private void handleEvent(MqttSensorDevice device, Integer water) {
String status;
if (water == null) return;
// 👉 水浸1=有水报警0=正常
if (water == 1) {
status = "alarm";
} else {
status = "normal";
}
changeStatus(device, status, water);
String status = (water == 1) ? "alarm" : "normal";
changeStatus(device, status);
}
// ================== 状态变更 ==================
private void changeStatus(MqttSensorDevice device,
String newStatus,
Integer water) {
private void changeStatus(MqttSensorDevice device, String newStatus) {
String key = device.getId() + "_water";
String oldStatus = STATUS_CACHE.get(key);
// ❗ 防重复
if (newStatus.equals(oldStatus)) {
return;
}
if (newStatus.equals(oldStatus)) return;
STATUS_CACHE.put(key, newStatus);
if ("alarm".equals(newStatus)) {
triggerEvent(device, "alarm", "HIGH", "浸水预警!");
} else if ("normal".equals(newStatus)) {
triggerEvent(device, "recovery", "LOW", "水浸正常");
} else {
triggerEvent(device, "recovery", "LOW", "水浸恢复正常");
}
}
// ================== 事件记录 ==================
/**
* 🔥 事件写入 + 报警推送
*/
private void triggerEvent(MqttSensorDevice device,
String type,
String level,
String desc) {
log.warn("[WATER事件] deviceId={}, type={}, level={}, desc={}",
device.getId(), type, level, desc);
log.warn("[WATER事件] deviceId={}, type={}, desc={}",
device.getId(), type, desc);
// ================== 1. 写库 ==================
MqttSensorEvent event = new MqttSensorEvent();
event.setDeviceId(device.getId());
// ✅ 核心(必须)
event.setDeptId(device.getDeptId());
event.setEventType(type);
event.setLevel(level);
event.setEventDesc(desc);
@@ -116,27 +148,51 @@ public class WaterSensorHandler {
eventService.insertMqttSensorEvent(event);
// TODO: 联动排水 / 告警推送
// ================== 2. 报警推送 ==================
try {
JSONObject msg = new JSONObject();
msg.put("type", "alarm"); // 🔥关键
msg.put("deviceId", device.getId());
msg.put("deviceName", device.getDeviceName());
msg.put("deptId", device.getDeptId());
msg.put("eventType", type);
msg.put("level", level);
msg.put("eventDesc", desc);
msg.put("time", System.currentTimeMillis());
sessionManager.sendToDept(device.getDeptId(), msg.toJSONString());
} catch (Exception e) {
log.error("[WATER] 报警推送失败 deviceId={}", device.getId(), e);
}
}
// ================== 数据入库 ==================
// ================== 入库 ==================
private void saveData(MqttSensorDevice device,
String topic,
String payload,
TopicInfo topicInfo,
Integer water) {
WaterInfo info) {
MqttSensorData data = new MqttSensorData();
data.setDeviceId(device.getId());
// ✅ 数据隔离
data.setDeptId(device.getDeptId());
data.setTopic(topic);
data.setProject(topicInfo.project);
data.setWarehouse(topicInfo.warehouse);
data.setPayload(payload);
data.setDataJson(payload);
data.setWaterStatus(water);
data.setWaterStatus(info.getWater());
data.setBattery(info.getBattery() == null ? null : info.getBattery().longValue());
data.setCreateTime(new Date());
data.setIsDelete("0");
@@ -144,7 +200,8 @@ public class WaterSensorHandler {
sensorDataService.insertMqttSensorData(data);
}
// ================== JSON解析 ==================
// ================== JSON ==================
private JSONObject parseJson(String payload) {
try {
return JSONObject.parseObject(payload);
@@ -154,46 +211,8 @@ public class WaterSensorHandler {
}
}
// ================== 解析水浸 ==================
private Integer parseWater(JSONObject json) {
try {
// 👉 LoRaWAN 原始数据Base64
String data = json.getString("data");
if (data == null) return null;
byte[] bytes = java.util.Base64.getDecoder().decode(data);
/**
* Milesight水浸协议关键
* 一般结构:
* [channel_id][channel_type][value]
*
* 水浸:
* channel_id = 0x05
* channel_type = 0x00
* value = 0/1
*/
for (int i = 0; i < bytes.length - 2; i++) {
int channelId = bytes[i] & 0xFF;
int channelType = bytes[i + 1] & 0xFF;
// 👉 水浸
if (channelId == 0x05 && channelType == 0x00) {
return bytes[i + 2] & 0xFF;
}
}
} catch (Exception e) {
log.error("[WATER] 解析payload失败", e);
}
return null;
}
// ================== topic解析 ==================
// ================== topic ==================
private TopicInfo parseTopic(String topic) {
TopicInfo info = new TopicInfo();
try {
@@ -206,9 +225,61 @@ public class WaterSensorHandler {
return info;
}
// ================== 内部结构 ==================
// ================== payload解析 ==================
private WaterInfo parsePayload(JSONObject json) {
try {
String data = json.getString("data");
if (data == null) return null;
byte[] bytes = Base64.getDecoder().decode(data);
WaterInfo result = new WaterInfo();
for (int i = 0; i < bytes.length - 2; ) {
int channelId = bytes[i] & 0xFF;
int channelType = bytes[i + 1] & 0xFF;
if (channelId == 0x01 && channelType == 0x75) {
result.setBattery(bytes[i + 2] & 0xFF);
i += 3;
continue;
}
if (channelId == 0x05 && channelType == 0x00) {
result.setWater(bytes[i + 2] & 0xFF);
i += 3;
continue;
}
i++;
}
return result;
} catch (Exception e) {
log.error("[WATER] 解析payload失败", e);
}
return null;
}
private static class TopicInfo {
String project;
String warehouse;
}
private static class WaterInfo {
private Integer battery;
private Integer water;
public Integer getBattery() { return battery; }
public void setBattery(Integer battery) { this.battery = battery; }
public Integer getWater() { return water; }
public void setWater(Integer water) { this.water = water; }
}
}

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);
/**
* 根据设备ID和指标类型获取阈值
* @param deviceId
* @param metricType
* 根据设备ID和部门ID获取阈值
* @param id
* @param deptId
* @param battery
* @return
*/
MqttSensorThreshold getThreshold(Long deviceId, String metricType);
MqttSensorThreshold getThreshold(Long id, Long deptId, String battery);
}

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

View File

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

View File

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

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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;
import org.apache.commons.io.IOUtils;
import com.shzg.common.utils.DateUtils;
import com.shzg.common.utils.SecurityUtils;
import com.shzg.common.utils.StringUtils;
@@ -14,12 +34,16 @@ import com.shzg.project.worn.domain.dto.WornInboundPartialFinishDTO;
import com.shzg.project.worn.domain.dto.WornInboundPartialFinishItemDTO;
import com.shzg.project.worn.mapper.WornInboundItemMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.WornInboundBillMapper;
import com.shzg.project.worn.domain.WornInboundBill;
import com.shzg.project.worn.service.IWornInboundBillService;
import org.springframework.transaction.annotation.Transactional;
/**
* 入库库存Service业务层处理
*
@@ -41,6 +65,12 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
@Autowired
private WornUniqueCodeEventMapper eventMapper;
@Autowired
private ResourceLoader resourceLoader;
@Value("${worn.pdf-font-locations:classpath:fonts/simhei.ttf,/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc,/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/usr/share/fonts/truetype/arphic/ukai.ttc,C:/Windows/Fonts/simhei.ttf,C:/Windows/Fonts/simsun.ttc}")
private String pdfFontLocations;
/**
* 查询入库库存
@@ -92,6 +122,7 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
/* ================== 3. 主表赋值 ================== */
wornInboundBill.setCreateTime(DateUtils.getNowDate());
wornInboundBill.setCreateBy(String.valueOf(userId));
wornInboundBill.setInboundTime(DateUtils.getNowDate());
wornInboundBill.setBillType("0");
wornInboundBill.setStatus("0");
wornInboundBill.setIsDelete("0");
@@ -475,7 +506,189 @@ public class WornInboundBillServiceImpl implements IWornInboundBillService
return 1;
}
@Override
public byte[] generatePdf(Long id) throws Exception
{
WornInboundBill bill = wornInboundBillMapper.selectWornInboundBillById(id);
if (bill == null)
{
throw new RuntimeException("入库单不存在");
}
List<WornInboundItem> itemList = wornInboundItemMapper.selectWornInboundItemByBillId(id);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(out);
PdfDocument pdf = new PdfDocument(writer);
Document document = new Document(pdf, PageSize.A4);
// ===== 中文字体 =====
PdfFont font = createPdfFont();
document.setFont(font);
// ===== 标题 =====
document.add(new Paragraph("智汇APP报表系统")
.setTextAlignment(TextAlignment.CENTER)
.setFontSize(16));
document.add(new Paragraph("入库单")
.setTextAlignment(TextAlignment.CENTER)
.setFontSize(14)
.setBold());
document.add(new Paragraph("\n"));
// ===== 表格 =====
// 新增“存储设施类型”后列数改为11列
float[] widths = {2, 6, 6, 5, 4, 4, 4, 2, 3, 3, 4};
Table table = new Table(widths).useAllAvailableWidth();
// ===== 表头 =====
table.addCell(cell("序号"));
table.addCell(cell("入库批次编号"));
table.addCell(cell("入库时间"));
table.addCell(cell("存储设施类型"));
table.addCell(cell("物料名称"));
table.addCell(cell("规格型号"));
table.addCell(cell("入库数量"));
table.addCell(cell("单位"));
table.addCell(cell("操作人"));
table.addCell(cell("备注"));
table.addCell(cell("唯一码"));
// ===== 时间格式 =====
String datePart = "";
String timePart = "";
if (bill.getInboundTime() != null)
{
datePart = new SimpleDateFormat("yyyy-MM-dd").format(bill.getInboundTime());
timePart = new SimpleDateFormat("HH:mm:ss").format(bill.getInboundTime());
}
// ===== 数据 =====
int index = 1;
BigDecimal total = BigDecimal.ZERO;
for (WornInboundItem item : itemList)
{
table.addCell(cell(String.valueOf(index++)));
table.addCell(cell(bill.getBillNo()));
Cell timeCell = new Cell()
.add(new Paragraph(datePart + "\n" + timePart))
.setTextAlignment(TextAlignment.CENTER)
.setVerticalAlignment(VerticalAlignment.MIDDLE);
table.addCell(timeCell);
// ===== 新增存储设施类型warehouse_name=====
table.addCell(cell(bill.getWarehouseName()));
table.addCell(cell(item.getMaterialName()));
table.addCell(cell(buildSpecificationText(item)));
table.addCell(cell(item.getQuantity() == null ? "" : item.getQuantity().toString()));
table.addCell(cell(item.getUnitName()));
table.addCell(cell(item.getOperatorName()));
table.addCell(cell(item.getRemark()));
table.addCell(cell(item.getUniqueCode() == null ? "" : item.getUniqueCode().toString()));
if (item.getQuantity() != null)
{
total = total.add(item.getQuantity());
}
}
// ===== 合计 =====
// 前6列合并序号、入库批次编号、入库时间、存储设施类型、物料名称、规格型号
table.addCell(new Cell(1, 6)
.add(new Paragraph("合计"))
.setTextAlignment(TextAlignment.CENTER)
.setVerticalAlignment(VerticalAlignment.MIDDLE));
// 入库数量
table.addCell(cell(total.toString()));
// 剩余4列留空单位、操作人、备注、唯一码
table.addCell(new Cell(1, 4)
.add(new Paragraph(""))
.setTextAlignment(TextAlignment.CENTER)
.setVerticalAlignment(VerticalAlignment.MIDDLE));
document.add(table);
document.close();
return out.toByteArray();
}
private Cell cell(String text)
{
return new Cell()
.add(new Paragraph(text == null ? "" : text))
.setTextAlignment(TextAlignment.CENTER)
.setVerticalAlignment(VerticalAlignment.MIDDLE);
}
private String buildSpecificationText(WornInboundItem item)
{
String specification = item.getSpecification();
String model = item.getModel();
if (StringUtils.isNotEmpty(specification) && StringUtils.isNotEmpty(model)) {
return specification + "/" + model;
}
if (StringUtils.isNotEmpty(specification)) {
return specification;
}
if (StringUtils.isNotEmpty(model)) {
return model;
}
return "";
}
private PdfFont createPdfFont() throws IOException
{
for (String location : pdfFontLocations.split(",")) {
String trimmed = location.trim();
if (trimmed.isEmpty()) {
continue;
}
byte[] fontBytes = loadFontBytes(trimmed);
if (fontBytes == null) {
continue;
}
return PdfFontFactory.createFont(
fontBytes,
"Identity-H",
PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED
);
}
throw new IllegalStateException("未找到可用的PDF中文字体请检查 worn.pdf-font-locations 配置");
}
private byte[] loadFontBytes(String location) throws IOException
{
if (location.startsWith("classpath:")) {
Resource resource = resourceLoader.getResource(location);
if (!resource.exists()) {
return null;
}
return IOUtils.toByteArray(resource.getInputStream());
}
Path path = location.startsWith("file:")
? Paths.get(location.substring("file:".length()))
: Paths.get(location);
if (!Files.exists(path) || Files.isDirectory(path)) {
return null;
}
return Files.readAllBytes(path);
}
}

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:
# 服务器的HTTP端口默认为8080
port: 8082
port: 8081
servlet:
# 应用的访问路径
context-path: /
@@ -179,3 +179,6 @@ mqtt:
# 默认 QoS
qos: 1
worn:
# PDF 打印字体查找顺序;部署到 Linux 时可把实际存在的中文字体路径放在最前面
pdf-font-locations: classpath:fonts/simhei.ttf,/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc,/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/usr/share/fonts/truetype/arphic/ukai.ttc,C:/Windows/Fonts/simhei.ttf,C:/Windows/Fonts/simsun.ttc

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

View File

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

View File

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

View File

@@ -1,38 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorEventMapper">
<resultMap type="MqttSensorEvent" id="MqttSensorEventResult">
<result property="id" column="id" />
<result property="deviceId" column="device_id" />
<result property="eventType" column="event_type" />
<result property="eventDesc" column="event_desc" />
<result property="level" column="level" />
<result property="status" column="status" />
<result property="remark" column="remark" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="isDelete" column="is_delete" />
<result property="id" column="id"/>
<result property="deviceId" column="device_id"/>
<result property="deptId" column="dept_id"/>
<result property="eventType" column="event_type"/>
<result property="eventDesc" column="event_desc"/>
<result property="level" column="level"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
</resultMap>
<sql id="selectMqttSensorEventVo">
select id, device_id, event_type, event_desc, level, status, remark, create_by, create_time, update_by, update_time, is_delete from mqtt_sensor_event
select id, device_id, dept_id, event_type, event_desc, level, status, remark, create_by, create_time, update_by, update_time, is_delete
from mqtt_sensor_event
</sql>
<select id="selectMqttSensorEventList" parameterType="MqttSensorEvent" resultMap="MqttSensorEventResult">
<include refid="selectMqttSensorEventVo"/>
<where>
<if test="deviceId != null "> and device_id = #{deviceId}</if>
<if test="eventType != null and eventType != ''"> and event_type = #{eventType}</if>
<if test="eventDesc != null and eventDesc != ''"> and event_desc = #{eventDesc}</if>
<if test="level != null and level != ''"> and level = #{level}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
<if test="deviceId != null">and device_id = #{deviceId}</if>
<if test="deptId != null">and dept_id = #{deptId}</if>
<!-- 数据权限 -->
<if test="deptIds != null and deptIds.size > 0">
AND dept_id IN
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
#{deptId}
</foreach>
</if>
<if test="eventType != null and eventType != ''">and event_type = #{eventType}</if>
<if test="eventDesc != null and eventDesc != ''">and event_desc = #{eventDesc}</if>
<if test="level != null and level != ''">and level = #{level}</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if>
</where>
order by create_time desc
</select>
<select id="selectMqttSensorEventById" parameterType="Long" resultMap="MqttSensorEventResult">
@@ -44,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into mqtt_sensor_event
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceId != null">device_id,</if>
<if test="deptId != null">dept_id,</if>
<if test="eventType != null and eventType != ''">event_type,</if>
<if test="eventDesc != null">event_desc,</if>
<if test="level != null">level,</if>
@@ -54,9 +68,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if>
</trim>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if>
<if test="deptId != null">#{deptId},</if>
<if test="eventType != null and eventType != ''">#{eventType},</if>
<if test="eventDesc != null">#{eventDesc},</if>
<if test="level != null">#{level},</if>
@@ -67,13 +82,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="isDelete != null">#{isDelete},</if>
</trim>
</trim>
</insert>
<update id="updateMqttSensorEvent" parameterType="MqttSensorEvent">
update mqtt_sensor_event
<trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="eventType != null and eventType != ''">event_type = #{eventType},</if>
<if test="eventDesc != null">event_desc = #{eventDesc},</if>
<if test="level != null">level = #{level},</if>
@@ -98,4 +114,5 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -1,43 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorThresholdMapper">
<resultMap type="MqttSensorThreshold" id="MqttSensorThresholdResult">
<result property="id" column="id" />
<result property="deviceId" column="device_id" />
<result property="metricType" column="metric_type" />
<result property="warnMin" column="warn_min" />
<result property="warnMax" column="warn_max" />
<result property="alarmMin" column="alarm_min" />
<result property="alarmMax" column="alarm_max" />
<result property="unit" column="unit" />
<result property="status" column="status" />
<result property="remark" column="remark" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="isDelete" column="is_delete" />
<result property="id" column="id"/>
<result property="deviceId" column="device_id"/>
<result property="deptId" column="dept_id"/>
<result property="metricType" column="metric_type"/>
<result property="warnMin" column="warn_min"/>
<result property="warnMax" column="warn_max"/>
<result property="alarmMin" column="alarm_min"/>
<result property="alarmMax" column="alarm_max"/>
<result property="unit" column="unit"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
</resultMap>
<sql id="selectMqttSensorThresholdVo">
select id, device_id, metric_type, warn_min, warn_max, alarm_min, alarm_max, unit, status, remark, create_by, create_time, update_by, update_time, is_delete from mqtt_sensor_threshold
select id, device_id, dept_id, metric_type, warn_min, warn_max, alarm_min, alarm_max,
unit, status, remark, create_by, create_time, update_by, update_time, is_delete
from mqtt_sensor_threshold
</sql>
<select id="selectMqttSensorThresholdList" parameterType="MqttSensorThreshold" resultMap="MqttSensorThresholdResult">
<include refid="selectMqttSensorThresholdVo"/>
<where>
<if test="deviceId != null "> and device_id = #{deviceId}</if>
<if test="metricType != null and metricType != ''"> and metric_type = #{metricType}</if>
<if test="warnMin != null "> and warn_min = #{warnMin}</if>
<if test="warnMax != null "> and warn_max = #{warnMax}</if>
<if test="alarmMin != null "> and alarm_min = #{alarmMin}</if>
<if test="alarmMax != null "> and alarm_max = #{alarmMax}</if>
<if test="unit != null and unit != ''"> and unit = #{unit}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
<if test="deviceId != null">and device_id = #{deviceId}</if>
<if test="deptId != null">and dept_id = #{deptId}</if>
<if test="deptIds != null and deptIds.size > 0">
and dept_id in
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
#{deptId}
</foreach>
</if>
<if test="metricType != null and metricType != ''">and metric_type = #{metricType}</if>
<if test="warnMin != null">and warn_min = #{warnMin}</if>
<if test="warnMax != null">and warn_max = #{warnMax}</if>
<if test="alarmMin != null">and alarm_min = #{alarmMin}</if>
<if test="alarmMax != null">and alarm_max = #{alarmMax}</if>
<if test="unit != null and unit != ''">and unit = #{unit}</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if>
</where>
</select>
@@ -50,6 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into mqtt_sensor_threshold
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceId != null">device_id,</if>
<if test="deptId != null">dept_id,</if>
<if test="metricType != null and metricType != ''">metric_type,</if>
<if test="warnMin != null">warn_min,</if>
<if test="warnMax != null">warn_max,</if>
@@ -63,9 +76,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if>
</trim>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if>
<if test="deptId != null">#{deptId},</if>
<if test="metricType != null and metricType != ''">#{metricType},</if>
<if test="warnMin != null">#{warnMin},</if>
<if test="warnMax != null">#{warnMax},</if>
@@ -79,13 +93,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="isDelete != null">#{isDelete},</if>
</trim>
</trim>
</insert>
<update id="updateMqttSensorThreshold" parameterType="MqttSensorThreshold">
update mqtt_sensor_threshold
<trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="metricType != null and metricType != ''">metric_type = #{metricType},</if>
<if test="warnMin != null">warn_min = #{warnMin},</if>
<if test="warnMax != null">warn_max = #{warnMax},</if>
@@ -94,8 +109,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="unit != null">unit = #{unit},</if>
<if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="isDelete != null">is_delete = #{isDelete},</if>
@@ -114,6 +127,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<!-- 按设备+指标查询 -->
<select id="selectByDeviceAndMetric" resultMap="MqttSensorThresholdResult">
select *
from mqtt_sensor_threshold
@@ -123,4 +137,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and is_delete = '0'
limit 1
</select>
<!-- 按部门+指标查询 -->
<select id="selectByDeptAndMetric" resultMap="MqttSensorThresholdResult">
select *
from mqtt_sensor_threshold
where dept_id = #{deptId}
and metric_type = #{metricType}
and status = '1'
and is_delete = '0'
limit 1
</select>
<select id="selectByPriority" resultMap="MqttSensorThresholdResult">
select *
from mqtt_sensor_threshold
where metric_type = #{metricType}
and status = '1'
and is_delete = '0'
and (
device_id = #{deviceId}
or dept_id = #{deptId}
)
order by case when device_id = #{deviceId} then 1 else 2 end
limit 1
</select>
</mapper>

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="unitId" column="unit_id"/>
<result property="unitName" column="unit_name"/>
<result property="operatorName" column="operator_name"/>
</resultMap>
<sql id="selectWornInboundItemVo">
@@ -193,22 +194,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
parameterType="Long"
resultMap="WornInboundItemResult">
SELECT
id,
bill_id,
bill_no,
material_id,
quantity,
unique_code,
remark,
status,
create_by,
create_time,
update_by,
update_time,
is_delete
FROM worn_inbound_item
WHERE bill_id = #{billId}
AND (is_delete = '0' OR is_delete IS NULL)
ORDER BY id ASC
i.id,
i.bill_id,
i.bill_no,
i.material_id,
i.quantity,
i.unique_code,
i.remark,
i.status,
i.create_by,
i.create_time,
i.update_by,
i.update_time,
i.is_delete,
b.inbound_time,
b.warehouse_code,
b.warehouse_name,
b.area_code,
b.area_name,
b.remark AS bill_remark,
b.status AS bill_status,
b.bill_type,
m.material_name,
m.material_short_name,
m.material_code,
m.specification,
m.model,
m.weight,
m.unit_id,
mu.unit_name,
u.nick_name AS operator_name
FROM worn_inbound_item i
LEFT JOIN worn_inbound_bill b ON i.bill_id = b.id
LEFT JOIN worn_material m ON i.material_id = m.id
LEFT JOIN worn_material_unit mu ON m.unit_id = mu.id
LEFT JOIN sys_user u ON CAST(b.create_by AS UNSIGNED) = u.user_id
WHERE i.bill_id = #{billId}
AND (i.is_delete = '0' OR i.is_delete IS NULL)
AND (b.is_delete = '0' OR b.is_delete IS NULL)
ORDER BY i.id ASC
</select>
</mapper>