From b2a278472462a6b56270d872054162aa97385ac1 Mon Sep 17 00:00:00 2001 From: wenshijun Date: Wed, 15 Apr 2026 17:08:18 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E7=BB=9F=E8=AE=A1=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=BC=80=E5=8F=91=20websocket=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/shzg/common/utils/DeptScopeUtils.java | 17 +- .../security/service/TokenService.java | 193 +++++++----------- .../system/controller/SysDeptController.java | 1 + .../project/system/mapper/SysDeptMapper.java | 17 ++ .../system/service/ISysDeptService.java | 18 ++ .../service/impl/SysDeptServiceImpl.java | 58 +++++- .../worn/controller/SysHomeController.java | 83 ++++++++ .../project/worn/domain/MqttSensorDevice.java | 73 ++++--- .../project/worn/domain/MqttTopicConfig.java | 15 ++ .../worn/mapper/MqttSensorDeviceMapper.java | 6 + .../worn/mapper/MqttSensorEventMapper.java | 18 ++ .../mqtt/handler/SmartSocketHandler.java | 10 +- .../worn/service/IMqttSensorEventService.java | 18 ++ .../impl/MqttSensorEventServiceImpl.java | 42 +++- .../endpoint/WornWebSocketServer.java | 192 +++++++++++------ .../mybatis/system/SysDeptMapper.xml | 87 +++++++- .../mybatis/worn/MqttSensorDeviceMapper.xml | 112 +++++++--- .../mybatis/worn/MqttSensorEventMapper.xml | 67 ++++++ .../mybatis/worn/MqttTopicConfigMapper.xml | 79 ++++--- 19 files changed, 827 insertions(+), 279 deletions(-) create mode 100644 src/main/java/com/shzg/project/worn/controller/SysHomeController.java diff --git a/src/main/java/com/shzg/common/utils/DeptScopeUtils.java b/src/main/java/com/shzg/common/utils/DeptScopeUtils.java index 607357b..4d9281c 100644 --- a/src/main/java/com/shzg/common/utils/DeptScopeUtils.java +++ b/src/main/java/com/shzg/common/utils/DeptScopeUtils.java @@ -3,6 +3,7 @@ package com.shzg.common.utils; import com.shzg.common.utils.spring.SpringUtils; import com.shzg.project.system.service.ISysDeptService; +import java.util.ArrayList; import java.util.List; /** @@ -17,7 +18,21 @@ public class DeptScopeUtils { Long deptId = SecurityUtils.getDeptId(); - return SpringUtils.getBean(ISysDeptService.class) + // 查询子部门 + List deptIds = SpringUtils.getBean(ISysDeptService.class) .selectDeptAndChildIds(deptId); + + // 防止返回null + if (deptIds == null) + { + deptIds = new ArrayList<>(); + } + + if (!deptIds.contains(deptId)) + { + deptIds.add(deptId); + } + + return deptIds; } } \ No newline at end of file diff --git a/src/main/java/com/shzg/framework/security/service/TokenService.java b/src/main/java/com/shzg/framework/security/service/TokenService.java index 1ca231c..3e4e519 100644 --- a/src/main/java/com/shzg/framework/security/service/TokenService.java +++ b/src/main/java/com/shzg/framework/security/service/TokenService.java @@ -4,11 +4,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; + import com.shzg.common.constant.CacheConstants; import com.shzg.common.constant.Constants; import com.shzg.common.utils.ServletUtils; @@ -19,100 +15,103 @@ import com.shzg.common.utils.ip.IpUtils; import com.shzg.common.utils.uuid.IdUtils; import com.shzg.framework.redis.RedisCache; import com.shzg.framework.security.LoginUser; + import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -/** - * token验证处理 - * - * @author ruoyi - */ +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + @Component -public class TokenService -{ +public class TokenService { + private static final Logger log = LoggerFactory.getLogger(TokenService.class); - // 令牌自定义标识 @Value("${token.header}") private String header; - // 令牌秘钥 @Value("${token.secret}") private String secret; - // 令牌有效期(默认30分钟) @Value("${token.expireTime}") private int expireTime; protected static final long MILLIS_SECOND = 1000; - protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; - private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L; @Autowired private RedisCache redisCache; - /** - * 获取用户身份信息 - * - * @return 用户信息 - */ - public LoginUser getLoginUser(HttpServletRequest request) - { - // 获取请求携带的令牌 + public LoginUser getLoginUser(HttpServletRequest request) { String token = getToken(request); - if (StringUtils.isNotEmpty(token)) - { - try - { + if (StringUtils.isNotEmpty(token)) { + try { Claims claims = parseToken(token); - // 解析对应的权限以及用户信息 String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); String userKey = getTokenKey(uuid); - LoginUser user = redisCache.getCacheObject(userKey); - return user; - } - catch (Exception e) - { + return redisCache.getCacheObject(userKey); + } catch (Exception e) { log.error("获取用户信息异常'{}'", e.getMessage()); } } return null; } - /** - * 设置用户身份信息 - */ - public void setLoginUser(LoginUser loginUser) - { - if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) - { + public LoginUser getLoginUserByToken(String token) { + + if (StringUtils.isEmpty(token)) { + return null; + } + + try { + // 1. 解析JWT + Claims claims = parseToken(token); + + if (claims == null) { + return null; + } + + // 2. 获取uuid(关键) + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + + if (StringUtils.isEmpty(uuid)) { + return null; + } + + // 3. 拼Redis key + String userKey = getTokenKey(uuid); + + // 4. 查缓存 + LoginUser loginUser = redisCache.getCacheObject(userKey); + + return loginUser; + + } catch (Exception e) { + log.error("WebSocket获取用户失败: {}", e.getMessage()); + } + + return null; + } + + public void setLoginUser(LoginUser loginUser) { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { refreshToken(loginUser); } } - /** - * 删除用户身份信息 - */ - public void delLoginUser(String token) - { - if (StringUtils.isNotEmpty(token)) - { + public void delLoginUser(String token) { + if (StringUtils.isNotEmpty(token)) { String userKey = getTokenKey(token); redisCache.deleteObject(userKey); } } - /** - * 创建令牌 - * - * @param loginUser 用户信息 - * @return 令牌 - */ - public String createToken(LoginUser loginUser) - { + public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); loginUser.setToken(token); setUserAgent(loginUser); @@ -121,46 +120,26 @@ public class TokenService Map claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); claims.put(Constants.JWT_USERNAME, loginUser.getUsername()); + return createToken(claims); } - /** - * 验证令牌有效期,相差不足20分钟,自动刷新缓存 - * - * @param loginUser 登录信息 - * @return 令牌 - */ - public void verifyToken(LoginUser loginUser) - { + public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); - if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) - { + if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) { refreshToken(loginUser); } } - /** - * 刷新令牌有效期 - * - * @param loginUser 登录信息 - */ - public void refreshToken(LoginUser loginUser) - { + public void refreshToken(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); - // 根据uuid将loginUser缓存 String userKey = getTokenKey(loginUser.getToken()); redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); } - /** - * 设置用户代理信息 - * - * @param loginUser 登录信息 - */ - public void setUserAgent(LoginUser loginUser) - { + public void setUserAgent(LoginUser loginUser) { String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); String ip = IpUtils.getIpAddr(); loginUser.setIpaddr(ip); @@ -169,64 +148,34 @@ public class TokenService loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent)); } - /** - * 从数据声明生成令牌 - * - * @param claims 数据声明 - * @return 令牌 - */ - private String createToken(Map claims) - { - String token = Jwts.builder() + private String createToken(Map claims) { + return Jwts.builder() .setClaims(claims) - .signWith(SignatureAlgorithm.HS512, secret).compact(); - return token; + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); } - /** - * 从令牌中获取数据声明 - * - * @param token 令牌 - * @return 数据声明 - */ - private Claims parseToken(String token) - { + private Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } - /** - * 从令牌中获取用户名 - * - * @param token 令牌 - * @return 用户名 - */ - public String getUsernameFromToken(String token) - { + public String getUsernameFromToken(String token) { Claims claims = parseToken(token); return claims.getSubject(); } - /** - * 获取请求token - * - * @param request - * @return token - */ - private String getToken(HttpServletRequest request) - { + private String getToken(HttpServletRequest request) { String token = request.getHeader(header); - if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) - { + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { token = token.replace(Constants.TOKEN_PREFIX, ""); } return token; } - private String getTokenKey(String uuid) - { + private String getTokenKey(String uuid) { return CacheConstants.LOGIN_TOKEN_KEY + uuid; } -} +} \ No newline at end of file diff --git a/src/main/java/com/shzg/project/system/controller/SysDeptController.java b/src/main/java/com/shzg/project/system/controller/SysDeptController.java index f9a9119..b19b265 100644 --- a/src/main/java/com/shzg/project/system/controller/SysDeptController.java +++ b/src/main/java/com/shzg/project/system/controller/SysDeptController.java @@ -129,4 +129,5 @@ public class SysDeptController extends BaseController deptService.checkDeptDataScope(deptId); return toAjax(deptService.deleteDeptById(deptId)); } + } diff --git a/src/main/java/com/shzg/project/system/mapper/SysDeptMapper.java b/src/main/java/com/shzg/project/system/mapper/SysDeptMapper.java index a45694f..c0d1301 100644 --- a/src/main/java/com/shzg/project/system/mapper/SysDeptMapper.java +++ b/src/main/java/com/shzg/project/system/mapper/SysDeptMapper.java @@ -1,6 +1,8 @@ package com.shzg.project.system.mapper; import java.util.List; +import java.util.Map; + import org.apache.ibatis.annotations.Param; import com.shzg.project.system.domain.SysDept; @@ -132,4 +134,19 @@ public interface SysDeptMapper * @return 部门ID集合 */ List selectDeptAndChildIds(Long deptId); + + /** + * 项目总数(按权限) + */ + int countProjectByDeptIds(@Param("deptIds") List deptIds); + + /** + * 仓库总数(按权限) + */ + int countWarehouseByDeptIds(@Param("deptIds") List deptIds); + + /** + * 仓库列表(按权限) + */ + List> selectWarehouseList(@Param("deptIds") List deptIds); } diff --git a/src/main/java/com/shzg/project/system/service/ISysDeptService.java b/src/main/java/com/shzg/project/system/service/ISysDeptService.java index 52a13cf..d0a2e3a 100644 --- a/src/main/java/com/shzg/project/system/service/ISysDeptService.java +++ b/src/main/java/com/shzg/project/system/service/ISysDeptService.java @@ -1,6 +1,8 @@ package com.shzg.project.system.service; import java.util.List; +import java.util.Map; + import com.shzg.framework.web.domain.TreeSelect; import com.shzg.project.system.domain.SysDept; @@ -137,4 +139,20 @@ public interface ISysDeptService * @return 部门ID列表 */ List selectDeptAndChildIds(Long deptId); + + /** + * 首页统计 + */ + Map getHomeStat(); + + /** + * 设备统计 + */ + Map getDeviceStat(); + + /** + * 仓库运行状态 + */ + List> selectWarehouseList(); + } diff --git a/src/main/java/com/shzg/project/system/service/impl/SysDeptServiceImpl.java b/src/main/java/com/shzg/project/system/service/impl/SysDeptServiceImpl.java index 4815443..c2bb925 100644 --- a/src/main/java/com/shzg/project/system/service/impl/SysDeptServiceImpl.java +++ b/src/main/java/com/shzg/project/system/service/impl/SysDeptServiceImpl.java @@ -1,12 +1,12 @@ package com.shzg.project.system.service.impl; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; +import com.shzg.common.utils.DeptScopeUtils; import com.shzg.project.system.domain.SysUser; import com.shzg.project.system.mapper.SysUserMapper; +import com.shzg.project.worn.mapper.MqttSensorDeviceMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.shzg.common.constant.UserConstants; @@ -40,6 +40,10 @@ public class SysDeptServiceImpl implements ISysDeptService @Autowired private SysUserMapper userMapper; + @Autowired + private MqttSensorDeviceMapper deviceMapper; + + /** * 查询部门管理数据 * @@ -360,4 +364,52 @@ public class SysDeptServiceImpl implements ISysDeptService { return deptMapper.selectDeptAndChildIds(deptId); } + + @Override + public Map getHomeStat() + { + Map map = new HashMap<>(); + + // 当前用户可见部门范围 + List deptIds = DeptScopeUtils.getDeptScope(); + + // 项目总数(顶级部门) + int projectCount = deptMapper.countProjectByDeptIds(deptIds); + + // 仓库总数(子部门) + int warehouseCount = deptMapper.countWarehouseByDeptIds(deptIds); + + map.put("projectCount", projectCount); + map.put("warehouseCount", warehouseCount); + + return map; + } + + @Override + public Map getDeviceStat() + { + Map map = new HashMap<>(); + + // 当前用户权限范围(核心) + List deptIds = DeptScopeUtils.getDeptScope(); + + Map stat = deviceMapper.countDeviceStat(deptIds); + + map.put("total", stat.get("total")); + map.put("online", stat.get("online")); + map.put("offline", stat.get("offline")); + map.put("onlineRate", stat.get("onlineRate")); + + return map; + } + + @Override + public List> selectWarehouseList() + { + // 当前用户权限范围 + List deptIds = DeptScopeUtils.getDeptScope(); + + return deptMapper.selectWarehouseList(deptIds); + } + } diff --git a/src/main/java/com/shzg/project/worn/controller/SysHomeController.java b/src/main/java/com/shzg/project/worn/controller/SysHomeController.java new file mode 100644 index 0000000..3c29ffe --- /dev/null +++ b/src/main/java/com/shzg/project/worn/controller/SysHomeController.java @@ -0,0 +1,83 @@ +package com.shzg.project.worn.controller; + +import com.shzg.framework.web.controller.BaseController; +import com.shzg.framework.web.domain.AjaxResult; +import com.shzg.framework.web.page.TableDataInfo; +import com.shzg.project.system.domain.SysDept; +import com.shzg.project.system.service.ISysDeptService; +import com.shzg.project.worn.service.IMqttSensorEventService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/home") +public class SysHomeController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + @Autowired + private IMqttSensorEventService eventService; + + /** + * 首页统计 + */ + @GetMapping("/stat") + public AjaxResult stat() + { + Map data = deptService.getHomeStat(); + return AjaxResult.success(data); + } + + /** + * 设备统计(总数 / 在线 / 离线) + */ + @GetMapping("/device/stat") + public AjaxResult deviceStat() + { + Map data = deptService.getDeviceStat(); + return AjaxResult.success(data); + } + + /** + * 告警统计(今日告警 / 未处理告警) + */ + @GetMapping("/alarm/stat") + public AjaxResult alarmStat() + { + Map data = eventService.getAlarmStat(); + return AjaxResult.success(data); + } + + /** + * 告警趋势(近7天) + */ + @GetMapping("/alarm/trend") + public AjaxResult alarmTrend() + { + return AjaxResult.success(eventService.getAlarmTrend()); + } + + /** + * 告警类型占比 + */ + @GetMapping("/alarm/type") + public AjaxResult alarmType() + { + return AjaxResult.success(eventService.getAlarmTypeStat()); + } + + /** + * 仓库运行状态(全部仓库) + */ + @GetMapping("/warehouse/list") + public AjaxResult warehouseList() + { + List> list = deptService.selectWarehouseList(); + return AjaxResult.success(list); + } + +} \ No newline at end of file diff --git a/src/main/java/com/shzg/project/worn/domain/MqttSensorDevice.java b/src/main/java/com/shzg/project/worn/domain/MqttSensorDevice.java index c5743e6..8f1926d 100644 --- a/src/main/java/com/shzg/project/worn/domain/MqttSensorDevice.java +++ b/src/main/java/com/shzg/project/worn/domain/MqttSensorDevice.java @@ -7,7 +7,7 @@ import com.shzg.framework.aspectj.lang.annotation.Excel; /** * MQTT设备对象 mqtt_sensor_device - * + * * @author shzg * @date 2026-04-01 */ @@ -34,6 +34,10 @@ public class MqttSensorDevice extends BaseEntity @Excel(name = "所属部门ID") private Long deptId; + /** 所属部门名称 */ + @Excel(name = "所属部门名称") + private String deptName; + /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; @@ -42,72 +46,82 @@ public class MqttSensorDevice extends BaseEntity @Excel(name = "删除标识", readConverterExp = "0=正常,1=删除") private String isDelete; - public void setId(Long id) + public void setId(Long id) { this.id = id; } - public Long getId() + public Long getId() { return id; } - public void setDevEui(String devEui) + public void setDevEui(String devEui) { this.devEui = devEui; } - public String getDevEui() + public String getDevEui() { return devEui; } - public void setDeviceName(String deviceName) + public void setDeviceName(String deviceName) { this.deviceName = deviceName; } - public String getDeviceName() + public String getDeviceName() { return deviceName; } - public void setDeviceType(String deviceType) + public void setDeviceType(String deviceType) { this.deviceType = deviceType; } - public String getDeviceType() + public String getDeviceType() { return deviceType; } - public void setDeptId(Long deptId) + public void setDeptId(Long deptId) { this.deptId = deptId; } - public Long getDeptId() + public Long getDeptId() { return deptId; } - public void setStatus(String status) + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getDeptName() + { + return deptName; + } + + public void setStatus(String status) { this.status = status; } - public String getStatus() + public String getStatus() { return status; } - public void setIsDelete(String isDelete) + public void setIsDelete(String isDelete) { this.isDelete = isDelete; } - public String getIsDelete() + public String getIsDelete() { return isDelete; } @@ -115,18 +129,19 @@ public class MqttSensorDevice extends BaseEntity @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) - .append("id", getId()) - .append("devEui", getDevEui()) - .append("deviceName", getDeviceName()) - .append("deviceType", getDeviceType()) - .append("deptId", getDeptId()) - .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("devEui", getDevEui()) + .append("deviceName", getDeviceName()) + .append("deviceType", getDeviceType()) + .append("deptId", getDeptId()) + .append("deptName", getDeptName()) + .append("status", getStatus()) + .append("remark", getRemark()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("isDelete", getIsDelete()) + .toString(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java b/src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java index 42bbe0d..787e67e 100644 --- a/src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java +++ b/src/main/java/com/shzg/project/worn/domain/MqttTopicConfig.java @@ -32,6 +32,10 @@ public class MqttTopicConfig extends BaseEntity @Excel(name = "部门ID") private Long deptId; + /** 所属部门名称 */ + @Excel(name = "部门名称") + private String deptName; + /** 🔥 数据权限(部门ID集合) */ private List deptIds; @@ -91,6 +95,16 @@ public class MqttTopicConfig extends BaseEntity return deptId; } + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getDeptName() + { + return deptName; + } + public void setDeptIds(List deptIds) { this.deptIds = deptIds; @@ -149,6 +163,7 @@ public class MqttTopicConfig extends BaseEntity .append("project", getProject()) .append("warehouse", getWarehouse()) .append("deptId", getDeptId()) + .append("deptName", getDeptName()) .append("deptIds", getDeptIds()) .append("topicUp", getTopicUp()) .append("topicDownPrefix", getTopicDownPrefix()) diff --git a/src/main/java/com/shzg/project/worn/mapper/MqttSensorDeviceMapper.java b/src/main/java/com/shzg/project/worn/mapper/MqttSensorDeviceMapper.java index 3b4a51b..2e4761d 100644 --- a/src/main/java/com/shzg/project/worn/mapper/MqttSensorDeviceMapper.java +++ b/src/main/java/com/shzg/project/worn/mapper/MqttSensorDeviceMapper.java @@ -1,6 +1,8 @@ package com.shzg.project.worn.mapper; import java.util.List; +import java.util.Map; + import com.shzg.project.worn.domain.MqttSensorDevice; import io.lettuce.core.dynamic.annotation.Param; @@ -67,4 +69,8 @@ public interface MqttSensorDeviceMapper */ MqttSensorDevice selectByDevEui(@Param("devEui") String devEui); + /** + * 设备统计(按权限) + */ + Map countDeviceStat(@Param("deptIds") List deptIds); } diff --git a/src/main/java/com/shzg/project/worn/mapper/MqttSensorEventMapper.java b/src/main/java/com/shzg/project/worn/mapper/MqttSensorEventMapper.java index 7252246..3cc3ccc 100644 --- a/src/main/java/com/shzg/project/worn/mapper/MqttSensorEventMapper.java +++ b/src/main/java/com/shzg/project/worn/mapper/MqttSensorEventMapper.java @@ -1,7 +1,10 @@ package com.shzg.project.worn.mapper; import java.util.List; +import java.util.Map; + import com.shzg.project.worn.domain.MqttSensorEvent; +import io.lettuce.core.dynamic.annotation.Param; /** * 设备事件Mapper接口 @@ -58,4 +61,19 @@ public interface MqttSensorEventMapper * @return 结果 */ public int deleteMqttSensorEventByIds(Long[] ids); + + /** + * 告警统计(今日 / 未处理)- 按权限 + */ + Map countAlarmStat(@Param("deptIds") List deptIds); + + /** + * 告警趋势(按权限) + */ + List> selectAlarmTrend(@Param("deptIds") List deptIds); + + /** + * 告警类型占比(按权限) + */ + List> selectAlarmTypeStat(@Param("deptIds") List deptIds); } diff --git a/src/main/java/com/shzg/project/worn/sensor/mqtt/handler/SmartSocketHandler.java b/src/main/java/com/shzg/project/worn/sensor/mqtt/handler/SmartSocketHandler.java index 9cea28d..461bf1c 100644 --- a/src/main/java/com/shzg/project/worn/sensor/mqtt/handler/SmartSocketHandler.java +++ b/src/main/java/com/shzg/project/worn/sensor/mqtt/handler/SmartSocketHandler.java @@ -106,9 +106,7 @@ public class SmartSocketHandler { ); // 没变化直接返回(不写事件、不推送) - if (!changed) { - return; - } + // 状态没变化也继续向前端推送周期数据,事件入库仍在推送后去重。 // ================== 查询部门 ================== String deptName = null; @@ -135,6 +133,10 @@ public class SmartSocketHandler { } // ================== 事件记录(只在变化时) ================== + if (!changed) { + return; + } + MqttSensorEvent event = new MqttSensorEvent(); event.setDeviceId(device.getId()); event.setDeptId(device.getDeptId()); @@ -153,4 +155,4 @@ public class SmartSocketHandler { status, switchStatus); } -} \ No newline at end of file +} diff --git a/src/main/java/com/shzg/project/worn/service/IMqttSensorEventService.java b/src/main/java/com/shzg/project/worn/service/IMqttSensorEventService.java index 247531d..624e58f 100644 --- a/src/main/java/com/shzg/project/worn/service/IMqttSensorEventService.java +++ b/src/main/java/com/shzg/project/worn/service/IMqttSensorEventService.java @@ -1,6 +1,8 @@ package com.shzg.project.worn.service; import java.util.List; +import java.util.Map; + import com.shzg.project.worn.domain.MqttSensorEvent; /** @@ -58,4 +60,20 @@ public interface IMqttSensorEventService * @return 结果 */ public int deleteMqttSensorEventById(Long id); + + /** + * 告警统计 + */ + Map getAlarmStat(); + + /** + * 告警趋势(近7天) + */ + List> getAlarmTrend(); + + /** + * 告警类型占比 + */ + List> getAlarmTypeStat(); + } diff --git a/src/main/java/com/shzg/project/worn/service/impl/MqttSensorEventServiceImpl.java b/src/main/java/com/shzg/project/worn/service/impl/MqttSensorEventServiceImpl.java index 46483c0..992a516 100644 --- a/src/main/java/com/shzg/project/worn/service/impl/MqttSensorEventServiceImpl.java +++ b/src/main/java/com/shzg/project/worn/service/impl/MqttSensorEventServiceImpl.java @@ -1,6 +1,9 @@ package com.shzg.project.worn.service.impl; +import java.util.HashMap; import java.util.List; +import java.util.Map; + import com.shzg.common.utils.DateUtils; import com.shzg.common.utils.DeptScopeUtils; import com.shzg.common.utils.SecurityUtils; @@ -17,7 +20,7 @@ import com.shzg.project.worn.service.IMqttSensorEventService; * @date 2026-04-01 */ @Service -public class MqttSensorEventServiceImpl implements IMqttSensorEventService +public class MqttSensorEventServiceImpl implements IMqttSensorEventService { @Autowired private MqttSensorEventMapper mqttSensorEventMapper; @@ -101,4 +104,41 @@ public class MqttSensorEventServiceImpl implements IMqttSensorEventService { return mqttSensorEventMapper.deleteMqttSensorEventById(id); } + + @Autowired + private MqttSensorEventMapper eventMapper; + + @Override + public Map getAlarmStat() + { + // 当前用户权限范围 + List deptIds = DeptScopeUtils.getDeptScope(); + + Map stat = eventMapper.countAlarmStat(deptIds); + + Map result = new HashMap<>(); + + result.put("today", stat == null ? 0 : stat.getOrDefault("today", 0)); + result.put("unhandled", stat == null ? 0 : stat.getOrDefault("unhandled", 0)); + + return result; + } + + @Override + public List> getAlarmTrend() + { + // 当前用户权限范围 + List deptIds = DeptScopeUtils.getDeptScope(); + + return eventMapper.selectAlarmTrend(deptIds); + } + + @Override + public List> getAlarmTypeStat() + { + // 当前用户权限范围 + List deptIds = DeptScopeUtils.getDeptScope(); + + return eventMapper.selectAlarmTypeStat(deptIds); + } } diff --git a/src/main/java/com/shzg/project/worn/websocket/endpoint/WornWebSocketServer.java b/src/main/java/com/shzg/project/worn/websocket/endpoint/WornWebSocketServer.java index 479a000..70aec26 100644 --- a/src/main/java/com/shzg/project/worn/websocket/endpoint/WornWebSocketServer.java +++ b/src/main/java/com/shzg/project/worn/websocket/endpoint/WornWebSocketServer.java @@ -1,5 +1,7 @@ package com.shzg.project.worn.websocket.endpoint; +import com.shzg.framework.security.LoginUser; +import com.shzg.framework.security.service.TokenService; import com.shzg.project.system.domain.SysDept; import com.shzg.project.system.mapper.SysDeptMapper; import com.shzg.project.worn.websocket.domain.WsUserInfo; @@ -8,10 +10,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.websocket.*; +import javax.websocket.CloseReason; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.URLDecoder; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -23,6 +30,7 @@ public class WornWebSocketServer { private static WebSocketSessionManager sessionManager; private static SysDeptMapper deptMapper; + private static TokenService tokenService; @Autowired public void setSessionManager(WebSocketSessionManager manager) { @@ -34,112 +42,164 @@ public class WornWebSocketServer { WornWebSocketServer.deptMapper = mapper; } + @Autowired + public void setTokenService(TokenService service) { + WornWebSocketServer.tokenService = service; + } + @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"); + if (query == null || !query.contains("token=")) { + closeSession(session, "missing token"); return; } - Long deptId; - try { - deptId = parseDeptId(query); + // ===== 1. 解析token ===== + String token = parseToken(query); + if (token == null || token.trim().isEmpty()) { + closeSession(session, "invalid token"); + return; + } + + // ===== 2. 获取登录用户 ===== + LoginUser loginUser = tokenService.getLoginUserByToken(token); + if (loginUser == null) { + closeSession(session, "invalid token"); + return; + } + + Long userId = loginUser.getUserId(); + Long deptId = loginUser.getDeptId(); + String userName = loginUser.getUsername(); + + boolean isAdmin = false; + if (loginUser.getUser() != null) { + isAdmin = loginUser.getUser().isAdmin(); + } + + if (deptId == null) { + closeSession(session, "deptId is null"); + return; + } + + // ===== 3. 计算部门范围(🔥重点修改)===== + Set deptIds = new HashSet<>(); + + if (isAdmin) { + // 🔥 管理员:全部部门 + SysDept queryDept = new SysDept(); + List allDept = deptMapper.selectDeptList(queryDept); + if (allDept != null && !allDept.isEmpty()) { + for (SysDept d : allDept) { + if (d != null && d.getDeptId() != null) { + deptIds.add(d.getDeptId()); + } + } + } + } else { + // 普通用户:当前部门 + 子部门 + List deptList = deptMapper.selectDeptAndChildren(deptId, null); + + if (deptList != null && !deptList.isEmpty()) { + for (SysDept dept : deptList) { + if (dept != null && dept.getDeptId() != null) { + deptIds.add(dept.getDeptId()); + } + } + } else { + deptIds.add(deptId); + } + } + + // ===== 4. 构造用户信息 ===== + WsUserInfo userInfo = new WsUserInfo(); + userInfo.setUserId(userId); + userInfo.setUserName(userName); + userInfo.setAdmin(isAdmin); + userInfo.setDeptIds(deptIds); + + // ===== 5. 获取IP ===== + String ip = getIp(session); + session.getUserProperties().put("ip", ip); + + // ===== 6. 注册连接 ===== + boolean success = sessionManager.register(session, userInfo); + if (!success) { + log.warn("[WebSocket] 连接被拒绝(限流)sessionId={}, ip={}", session.getId(), ip); + closeSession(session, "too many connections"); + return; + } + + log.info("[WebSocket] 注册成功 sessionId={}, userId={}, userName={}, ip={}, deptIds={}", + session.getId(), userId, userName, ip, deptIds); + } catch (Exception e) { - closeSession(session, "invalid deptId"); - return; + log.error("[WebSocket] 连接异常 sessionId={}", session.getId(), e); + closeSession(session, "error"); } - - List deptList = deptMapper.selectDeptAndChildren(deptId, null); - - Set 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); - - // ===== 获取IP(新增)===== - String ip = getIp(session); - - // 存入 session(给 manager 用) - session.getUserProperties().put("ip", ip); - - // ===== 注册连接(关键:接入限流)===== - boolean success = sessionManager.register(session, userInfo); - - if (!success) { - log.warn("[WebSocket] 连接被拒绝(限流)sessionId={}, ip={}", session.getId(), ip); - closeSession(session, "too many connections"); - return; - } - - log.info("[WebSocket] 注册成功 sessionId={}, ip={}, deptIds={}", - session.getId(), ip, deptIds); } @OnClose public void onClose(Session session) { sessionManager.remove(session); + log.info("[WebSocket] 连接关闭 sessionId={}", session.getId()); } @OnError public void onError(Session session, Throwable error) { if (session != null) { sessionManager.remove(session); + log.error("[WebSocket] 异常 sessionId={}", session.getId(), error); + } else { + log.error("[WebSocket] 异常", error); } } - private Long parseDeptId(String query) { - + private String parseToken(String query) throws Exception { String[] params = query.split("&"); - for (String param : params) { - if (param.startsWith("deptId=")) { - return Long.parseLong(param.substring(7)); + if (param.startsWith("token=")) { + String token = param.substring(6); + token = URLDecoder.decode(token, "UTF-8"); + + if (token.startsWith("Bearer ")) { + token = token.substring(7); + } + return token; } } - - throw new IllegalArgumentException("deptId not found"); + throw new IllegalArgumentException("token not found"); } - /** - * 获取客户端IP - */ private String getIp(Session session) { - try { - // 标准方式(部分容器支持) Object addr = session.getUserProperties().get("javax.websocket.endpoint.remoteAddress"); - if (addr instanceof InetSocketAddress) { - return ((InetSocketAddress) addr).getAddress().getHostAddress(); + InetSocketAddress inetSocketAddress = (InetSocketAddress) addr; + if (inetSocketAddress.getAddress() != null) { + return inetSocketAddress.getAddress().getHostAddress(); + } } - - } catch (Exception ignored) {} - - // fallback + } catch (Exception e) { + log.warn("[WebSocket] 获取客户端IP失败 sessionId={}", session.getId(), e); + } return "unknown"; } private void closeSession(Session session, String reason) { try { - session.close(new CloseReason( - CloseReason.CloseCodes.CANNOT_ACCEPT, - reason - )); + if (session != null && session.isOpen()) { + session.close(new CloseReason( + CloseReason.CloseCodes.CANNOT_ACCEPT, + reason + )); + } } catch (IOException e) { - log.error("关闭失败", e); + log.error("[WebSocket] 关闭连接失败 sessionId={}", session != null ? session.getId() : "null", e); } } -} \ No newline at end of file +} diff --git a/src/main/resources/mybatis/system/SysDeptMapper.xml b/src/main/resources/mybatis/system/SysDeptMapper.xml index 7545619..b06dfb5 100644 --- a/src/main/resources/mybatis/system/SysDeptMapper.xml +++ b/src/main/resources/mybatis/system/SysDeptMapper.xml @@ -175,4 +175,89 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE FIND_IN_SET(#{deptId}, ancestors) - \ No newline at end of file + + + + + + + diff --git a/src/main/resources/mybatis/worn/MqttSensorDeviceMapper.xml b/src/main/resources/mybatis/worn/MqttSensorDeviceMapper.xml index 9ba4c9e..7cef66a 100644 --- a/src/main/resources/mybatis/worn/MqttSensorDeviceMapper.xml +++ b/src/main/resources/mybatis/worn/MqttSensorDeviceMapper.xml @@ -1,45 +1,79 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + - - - - - - - - - - - - + + + + + + + + + + + + + + - select id, dev_eui, device_name, device_type, dept_id, status, remark, create_by, create_time, update_by, update_time, is_delete from mqtt_sensor_device + select + d.id, + d.dev_eui, + d.device_name, + d.device_type, + d.dept_id, + dept.dept_name, + d.status, + d.remark, + d.create_by, + d.create_time, + d.update_by, + d.update_time, + d.is_delete + from mqtt_sensor_device d + left join sys_dept dept on d.dept_id = dept.dept_id + + + insert into mqtt_sensor_device @@ -54,7 +88,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update_by, update_time, is_delete, - + #{devEui}, #{deviceName}, @@ -67,9 +101,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{updateBy}, #{updateTime}, #{isDelete}, - + + update mqtt_sensor_device @@ -88,6 +123,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where id = #{id} + delete from mqtt_sensor_device where id = #{id} @@ -99,10 +135,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + \ No newline at end of file diff --git a/src/main/resources/mybatis/worn/MqttSensorEventMapper.xml b/src/main/resources/mybatis/worn/MqttSensorEventMapper.xml index 68da760..c3c7ed0 100644 --- a/src/main/resources/mybatis/worn/MqttSensorEventMapper.xml +++ b/src/main/resources/mybatis/worn/MqttSensorEventMapper.xml @@ -115,4 +115,71 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml b/src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml index b9411ee..66c290b 100644 --- a/src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml +++ b/src/main/resources/mybatis/worn/MqttTopicConfigMapper.xml @@ -2,6 +2,7 @@ + @@ -10,6 +11,7 @@ + @@ -21,26 +23,53 @@ - + - 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 + select + t.id, + t.project, + t.warehouse, + t.dept_id, + d.dept_name, + t.topic_up, + t.topic_down_prefix, + t.status, + t.remark, + t.create_by, + t.create_time, + t.update_by, + t.update_time, + t.is_delete + from mqtt_topic_config t + left join sys_dept d on t.dept_id = d.dept_id - where id = #{id} + where t.id = #{id} - - where status = '0' - and is_delete = '0' - and topic_up is not null - and topic_up != '' + where t.status = '0' + and t.is_delete = '0' + and t.topic_up is not null + and t.topic_up != '' - + - and dept_id in + and t.dept_id in #{deptId} @@ -122,12 +151,11 @@ where id = #{id} - + delete from mqtt_topic_config where id = #{id} - delete from mqtt_topic_config where id in @@ -135,11 +163,12 @@ - - where dept_id = #{deptId} - and status = '0' - and is_delete = '0' + where t.dept_id = #{deptId} + and t.status = '0' + and t.is_delete = '0' limit 1