首页统计接口开发

websocket模块优化
This commit is contained in:
2026-04-15 17:08:18 +08:00
parent 7210c13ae1
commit b2a2784724
19 changed files with 827 additions and 279 deletions

View File

@@ -3,6 +3,7 @@ package com.shzg.common.utils;
import com.shzg.common.utils.spring.SpringUtils; import com.shzg.common.utils.spring.SpringUtils;
import com.shzg.project.system.service.ISysDeptService; import com.shzg.project.system.service.ISysDeptService;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -17,7 +18,21 @@ public class DeptScopeUtils
{ {
Long deptId = SecurityUtils.getDeptId(); Long deptId = SecurityUtils.getDeptId();
return SpringUtils.getBean(ISysDeptService.class) // 查询子部门
List<Long> deptIds = SpringUtils.getBean(ISysDeptService.class)
.selectDeptAndChildIds(deptId); .selectDeptAndChildIds(deptId);
// 防止返回null
if (deptIds == null)
{
deptIds = new ArrayList<>();
}
if (!deptIds.contains(deptId))
{
deptIds.add(deptId);
}
return deptIds;
} }
} }

View File

@@ -4,11 +4,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; 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.CacheConstants;
import com.shzg.common.constant.Constants; import com.shzg.common.constant.Constants;
import com.shzg.common.utils.ServletUtils; 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.common.utils.uuid.IdUtils;
import com.shzg.framework.redis.RedisCache; import com.shzg.framework.redis.RedisCache;
import com.shzg.framework.security.LoginUser; import com.shzg.framework.security.LoginUser;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
/** import org.slf4j.Logger;
* token验证处理 import org.slf4j.LoggerFactory;
* import org.springframework.beans.factory.annotation.Autowired;
* @author ruoyi import org.springframework.beans.factory.annotation.Value;
*/ import org.springframework.stereotype.Component;
@Component @Component
public class TokenService public class TokenService {
{
private static final Logger log = LoggerFactory.getLogger(TokenService.class); private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 令牌自定义标识
@Value("${token.header}") @Value("${token.header}")
private String header; private String header;
// 令牌秘钥
@Value("${token.secret}") @Value("${token.secret}")
private String secret; private String secret;
// 令牌有效期默认30分钟
@Value("${token.expireTime}") @Value("${token.expireTime}")
private int expireTime; private int expireTime;
protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L; private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
/** public LoginUser getLoginUser(HttpServletRequest request) {
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// 获取请求携带的令牌
String token = getToken(request); String token = getToken(request);
if (StringUtils.isNotEmpty(token)) if (StringUtils.isNotEmpty(token)) {
{ try {
try
{
Claims claims = parseToken(token); Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid); String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey); return redisCache.getCacheObject(userKey);
return user; } catch (Exception e) {
}
catch (Exception e)
{
log.error("获取用户信息异常'{}'", e.getMessage()); log.error("获取用户信息异常'{}'", e.getMessage());
} }
} }
return null; return null;
} }
/** public LoginUser getLoginUserByToken(String token) {
* 设置用户身份信息
*/ if (StringUtils.isEmpty(token)) {
public void setLoginUser(LoginUser loginUser) return null;
{ }
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{ 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); refreshToken(loginUser);
} }
} }
/** public void delLoginUser(String token) {
* 删除用户身份信息 if (StringUtils.isNotEmpty(token)) {
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token); String userKey = getTokenKey(token);
redisCache.deleteObject(userKey); redisCache.deleteObject(userKey);
} }
} }
/** public String createToken(LoginUser loginUser) {
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser)
{
String token = IdUtils.fastUUID(); String token = IdUtils.fastUUID();
loginUser.setToken(token); loginUser.setToken(token);
setUserAgent(loginUser); setUserAgent(loginUser);
@@ -121,46 +120,26 @@ public class TokenService
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token); claims.put(Constants.LOGIN_USER_KEY, token);
claims.put(Constants.JWT_USERNAME, loginUser.getUsername()); claims.put(Constants.JWT_USERNAME, loginUser.getUsername());
return createToken(claims); return createToken(claims);
} }
/** public void verifyToken(LoginUser loginUser) {
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
* @param loginUser 登录信息
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
long expireTime = loginUser.getExpireTime(); long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) {
{
refreshToken(loginUser); refreshToken(loginUser);
} }
} }
/** public void refreshToken(LoginUser loginUser) {
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken()); String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
} }
/** public void setUserAgent(LoginUser loginUser) {
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
String ip = IpUtils.getIpAddr(); String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip); loginUser.setIpaddr(ip);
@@ -169,64 +148,34 @@ public class TokenService
loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent)); loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent));
} }
/** private String createToken(Map<String, Object> claims) {
* 从数据声明生成令牌 return Jwts.builder()
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims) .setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact(); .signWith(SignatureAlgorithm.HS512, secret)
return token; .compact();
} }
/** private Claims parseToken(String token) {
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
return Jwts.parser() return Jwts.parser()
.setSigningKey(secret) .setSigningKey(secret)
.parseClaimsJws(token) .parseClaimsJws(token)
.getBody(); .getBody();
} }
/** public String getUsernameFromToken(String token) {
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token); Claims claims = parseToken(token);
return claims.getSubject(); return claims.getSubject();
} }
/** private String getToken(HttpServletRequest request) {
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header); 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, ""); token = token.replace(Constants.TOKEN_PREFIX, "");
} }
return token; return token;
} }
private String getTokenKey(String uuid) private String getTokenKey(String uuid) {
{
return CacheConstants.LOGIN_TOKEN_KEY + uuid; return CacheConstants.LOGIN_TOKEN_KEY + uuid;
} }
} }

View File

@@ -129,4 +129,5 @@ public class SysDeptController extends BaseController
deptService.checkDeptDataScope(deptId); deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId)); return toAjax(deptService.deleteDeptById(deptId));
} }
} }

View File

@@ -1,6 +1,8 @@
package com.shzg.project.system.mapper; package com.shzg.project.system.mapper;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import com.shzg.project.system.domain.SysDept; import com.shzg.project.system.domain.SysDept;
@@ -132,4 +134,19 @@ public interface SysDeptMapper
* @return 部门ID集合 * @return 部门ID集合
*/ */
List<Long> selectDeptAndChildIds(Long deptId); List<Long> selectDeptAndChildIds(Long deptId);
/**
* 项目总数(按权限)
*/
int countProjectByDeptIds(@Param("deptIds") List<Long> deptIds);
/**
* 仓库总数(按权限)
*/
int countWarehouseByDeptIds(@Param("deptIds") List<Long> deptIds);
/**
* 仓库列表(按权限)
*/
List<Map<String, Object>> selectWarehouseList(@Param("deptIds") List<Long> deptIds);
} }

View File

@@ -1,6 +1,8 @@
package com.shzg.project.system.service; package com.shzg.project.system.service;
import java.util.List; import java.util.List;
import java.util.Map;
import com.shzg.framework.web.domain.TreeSelect; import com.shzg.framework.web.domain.TreeSelect;
import com.shzg.project.system.domain.SysDept; import com.shzg.project.system.domain.SysDept;
@@ -137,4 +139,20 @@ public interface ISysDeptService
* @return 部门ID列表 * @return 部门ID列表
*/ */
List<Long> selectDeptAndChildIds(Long deptId); List<Long> selectDeptAndChildIds(Long deptId);
/**
* 首页统计
*/
Map<String, Object> getHomeStat();
/**
* 设备统计
*/
Map<String, Object> getDeviceStat();
/**
* 仓库运行状态
*/
List<Map<String, Object>> selectWarehouseList();
} }

View File

@@ -1,12 +1,12 @@
package com.shzg.project.system.service.impl; package com.shzg.project.system.service.impl;
import java.util.ArrayList; import java.util.*;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.shzg.common.utils.DeptScopeUtils;
import com.shzg.project.system.domain.SysUser; import com.shzg.project.system.domain.SysUser;
import com.shzg.project.system.mapper.SysUserMapper; import com.shzg.project.system.mapper.SysUserMapper;
import com.shzg.project.worn.mapper.MqttSensorDeviceMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.shzg.common.constant.UserConstants; import com.shzg.common.constant.UserConstants;
@@ -40,6 +40,10 @@ public class SysDeptServiceImpl implements ISysDeptService
@Autowired @Autowired
private SysUserMapper userMapper; private SysUserMapper userMapper;
@Autowired
private MqttSensorDeviceMapper deviceMapper;
/** /**
* 查询部门管理数据 * 查询部门管理数据
* *
@@ -360,4 +364,52 @@ public class SysDeptServiceImpl implements ISysDeptService
{ {
return deptMapper.selectDeptAndChildIds(deptId); return deptMapper.selectDeptAndChildIds(deptId);
} }
@Override
public Map<String, Object> getHomeStat()
{
Map<String, Object> map = new HashMap<>();
// 当前用户可见部门范围
List<Long> 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<String, Object> getDeviceStat()
{
Map<String, Object> map = new HashMap<>();
// 当前用户权限范围(核心)
List<Long> deptIds = DeptScopeUtils.getDeptScope();
Map<String, Object> 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<Map<String, Object>> selectWarehouseList()
{
// 当前用户权限范围
List<Long> deptIds = DeptScopeUtils.getDeptScope();
return deptMapper.selectWarehouseList(deptIds);
}
} }

View File

@@ -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<String, Object> data = deptService.getHomeStat();
return AjaxResult.success(data);
}
/**
* 设备统计(总数 / 在线 / 离线)
*/
@GetMapping("/device/stat")
public AjaxResult deviceStat()
{
Map<String, Object> data = deptService.getDeviceStat();
return AjaxResult.success(data);
}
/**
* 告警统计(今日告警 / 未处理告警)
*/
@GetMapping("/alarm/stat")
public AjaxResult alarmStat()
{
Map<String, Object> 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<Map<String, Object>> list = deptService.selectWarehouseList();
return AjaxResult.success(list);
}
}

View File

@@ -34,6 +34,10 @@ public class MqttSensorDevice extends BaseEntity
@Excel(name = "所属部门ID") @Excel(name = "所属部门ID")
private Long deptId; private Long deptId;
/** 所属部门名称 */
@Excel(name = "所属部门名称")
private String deptName;
/** 状态0正常 1停用 */ /** 状态0正常 1停用 */
@Excel(name = "状态", readConverterExp = "0=正常,1=停用") @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
private String status; private String status;
@@ -92,6 +96,16 @@ public class MqttSensorDevice extends BaseEntity
return deptId; return deptId;
} }
public void setDeptName(String deptName)
{
this.deptName = deptName;
}
public String getDeptName()
{
return deptName;
}
public void setStatus(String status) public void setStatus(String status)
{ {
this.status = status; this.status = status;
@@ -115,18 +129,19 @@ public class MqttSensorDevice extends BaseEntity
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("devEui", getDevEui()) .append("devEui", getDevEui())
.append("deviceName", getDeviceName()) .append("deviceName", getDeviceName())
.append("deviceType", getDeviceType()) .append("deviceType", getDeviceType())
.append("deptId", getDeptId()) .append("deptId", getDeptId())
.append("status", getStatus()) .append("deptName", getDeptName())
.append("remark", getRemark()) .append("status", getStatus())
.append("createBy", getCreateBy()) .append("remark", getRemark())
.append("createTime", getCreateTime()) .append("createBy", getCreateBy())
.append("updateBy", getUpdateBy()) .append("createTime", getCreateTime())
.append("updateTime", getUpdateTime()) .append("updateBy", getUpdateBy())
.append("isDelete", getIsDelete()) .append("updateTime", getUpdateTime())
.toString(); .append("isDelete", getIsDelete())
.toString();
} }
} }

View File

@@ -32,6 +32,10 @@ public class MqttTopicConfig extends BaseEntity
@Excel(name = "部门ID") @Excel(name = "部门ID")
private Long deptId; private Long deptId;
/** 所属部门名称 */
@Excel(name = "部门名称")
private String deptName;
/** 🔥 数据权限部门ID集合 */ /** 🔥 数据权限部门ID集合 */
private List<Long> deptIds; private List<Long> deptIds;
@@ -91,6 +95,16 @@ public class MqttTopicConfig extends BaseEntity
return deptId; return deptId;
} }
public void setDeptName(String deptName)
{
this.deptName = deptName;
}
public String getDeptName()
{
return deptName;
}
public void setDeptIds(List<Long> deptIds) public void setDeptIds(List<Long> deptIds)
{ {
this.deptIds = deptIds; this.deptIds = deptIds;
@@ -149,6 +163,7 @@ public class MqttTopicConfig extends BaseEntity
.append("project", getProject()) .append("project", getProject())
.append("warehouse", getWarehouse()) .append("warehouse", getWarehouse())
.append("deptId", getDeptId()) .append("deptId", getDeptId())
.append("deptName", getDeptName())
.append("deptIds", getDeptIds()) .append("deptIds", getDeptIds())
.append("topicUp", getTopicUp()) .append("topicUp", getTopicUp())
.append("topicDownPrefix", getTopicDownPrefix()) .append("topicDownPrefix", getTopicDownPrefix())

View File

@@ -1,6 +1,8 @@
package com.shzg.project.worn.mapper; package com.shzg.project.worn.mapper;
import java.util.List; import java.util.List;
import java.util.Map;
import com.shzg.project.worn.domain.MqttSensorDevice; import com.shzg.project.worn.domain.MqttSensorDevice;
import io.lettuce.core.dynamic.annotation.Param; import io.lettuce.core.dynamic.annotation.Param;
@@ -67,4 +69,8 @@ public interface MqttSensorDeviceMapper
*/ */
MqttSensorDevice selectByDevEui(@Param("devEui") String devEui); MqttSensorDevice selectByDevEui(@Param("devEui") String devEui);
/**
* 设备统计(按权限)
*/
Map<String, Object> countDeviceStat(@Param("deptIds") List<Long> deptIds);
} }

View File

@@ -1,7 +1,10 @@
package com.shzg.project.worn.mapper; package com.shzg.project.worn.mapper;
import java.util.List; import java.util.List;
import java.util.Map;
import com.shzg.project.worn.domain.MqttSensorEvent; import com.shzg.project.worn.domain.MqttSensorEvent;
import io.lettuce.core.dynamic.annotation.Param;
/** /**
* 设备事件Mapper接口 * 设备事件Mapper接口
@@ -58,4 +61,19 @@ public interface MqttSensorEventMapper
* @return 结果 * @return 结果
*/ */
public int deleteMqttSensorEventByIds(Long[] ids); public int deleteMqttSensorEventByIds(Long[] ids);
/**
* 告警统计(今日 / 未处理)- 按权限
*/
Map<String, Object> countAlarmStat(@Param("deptIds") List<Long> deptIds);
/**
* 告警趋势(按权限)
*/
List<Map<String, Object>> selectAlarmTrend(@Param("deptIds") List<Long> deptIds);
/**
* 告警类型占比(按权限)
*/
List<Map<String, Object>> selectAlarmTypeStat(@Param("deptIds") List<Long> deptIds);
} }

View File

@@ -106,9 +106,7 @@ public class SmartSocketHandler {
); );
// 没变化直接返回(不写事件、不推送) // 没变化直接返回(不写事件、不推送)
if (!changed) { // 状态没变化也继续向前端推送周期数据,事件入库仍在推送后去重。
return;
}
// ================== 查询部门 ================== // ================== 查询部门 ==================
String deptName = null; String deptName = null;
@@ -135,6 +133,10 @@ public class SmartSocketHandler {
} }
// ================== 事件记录(只在变化时) ================== // ================== 事件记录(只在变化时) ==================
if (!changed) {
return;
}
MqttSensorEvent event = new MqttSensorEvent(); MqttSensorEvent event = new MqttSensorEvent();
event.setDeviceId(device.getId()); event.setDeviceId(device.getId());
event.setDeptId(device.getDeptId()); event.setDeptId(device.getDeptId());

View File

@@ -1,6 +1,8 @@
package com.shzg.project.worn.service; package com.shzg.project.worn.service;
import java.util.List; import java.util.List;
import java.util.Map;
import com.shzg.project.worn.domain.MqttSensorEvent; import com.shzg.project.worn.domain.MqttSensorEvent;
/** /**
@@ -58,4 +60,20 @@ public interface IMqttSensorEventService
* @return 结果 * @return 结果
*/ */
public int deleteMqttSensorEventById(Long id); public int deleteMqttSensorEventById(Long id);
/**
* 告警统计
*/
Map<String, Object> getAlarmStat();
/**
* 告警趋势近7天
*/
List<Map<String, Object>> getAlarmTrend();
/**
* 告警类型占比
*/
List<Map<String, Object>> getAlarmTypeStat();
} }

View File

@@ -1,6 +1,9 @@
package com.shzg.project.worn.service.impl; package com.shzg.project.worn.service.impl;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.shzg.common.utils.DateUtils; import com.shzg.common.utils.DateUtils;
import com.shzg.common.utils.DeptScopeUtils; import com.shzg.common.utils.DeptScopeUtils;
import com.shzg.common.utils.SecurityUtils; import com.shzg.common.utils.SecurityUtils;
@@ -101,4 +104,41 @@ public class MqttSensorEventServiceImpl implements IMqttSensorEventService
{ {
return mqttSensorEventMapper.deleteMqttSensorEventById(id); return mqttSensorEventMapper.deleteMqttSensorEventById(id);
} }
@Autowired
private MqttSensorEventMapper eventMapper;
@Override
public Map<String, Object> getAlarmStat()
{
// 当前用户权限范围
List<Long> deptIds = DeptScopeUtils.getDeptScope();
Map<String, Object> stat = eventMapper.countAlarmStat(deptIds);
Map<String, Object> 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<Map<String, Object>> getAlarmTrend()
{
// 当前用户权限范围
List<Long> deptIds = DeptScopeUtils.getDeptScope();
return eventMapper.selectAlarmTrend(deptIds);
}
@Override
public List<Map<String, Object>> getAlarmTypeStat()
{
// 当前用户权限范围
List<Long> deptIds = DeptScopeUtils.getDeptScope();
return eventMapper.selectAlarmTypeStat(deptIds);
}
} }

View File

@@ -1,5 +1,7 @@
package com.shzg.project.worn.websocket.endpoint; 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.domain.SysDept;
import com.shzg.project.system.mapper.SysDeptMapper; import com.shzg.project.system.mapper.SysDeptMapper;
import com.shzg.project.worn.websocket.domain.WsUserInfo; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; 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 javax.websocket.server.ServerEndpoint;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -23,6 +30,7 @@ public class WornWebSocketServer {
private static WebSocketSessionManager sessionManager; private static WebSocketSessionManager sessionManager;
private static SysDeptMapper deptMapper; private static SysDeptMapper deptMapper;
private static TokenService tokenService;
@Autowired @Autowired
public void setSessionManager(WebSocketSessionManager manager) { public void setSessionManager(WebSocketSessionManager manager) {
@@ -34,112 +42,164 @@ public class WornWebSocketServer {
WornWebSocketServer.deptMapper = mapper; WornWebSocketServer.deptMapper = mapper;
} }
@Autowired
public void setTokenService(TokenService service) {
WornWebSocketServer.tokenService = service;
}
@OnOpen @OnOpen
public void onOpen(Session session) { public void onOpen(Session session) {
log.info("[WebSocket] 连接建立 sessionId={}", session.getId()); log.info("[WebSocket] 连接建立 sessionId={}", session.getId());
String query = session.getQueryString(); String query = session.getQueryString();
if (query == null || !query.contains("token=")) {
if (query == null || !query.contains("deptId")) { closeSession(session, "missing token");
closeSession(session, "missing deptId");
return; return;
} }
Long deptId;
try { 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<Long> deptIds = new HashSet<>();
if (isAdmin) {
// 🔥 管理员:全部部门
SysDept queryDept = new SysDept();
List<SysDept> 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<SysDept> 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) { } catch (Exception e) {
closeSession(session, "invalid deptId"); log.error("[WebSocket] 连接异常 sessionId={}", session.getId(), e);
return; closeSession(session, "error");
} }
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);
// ===== 获取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 @OnClose
public void onClose(Session session) { public void onClose(Session session) {
sessionManager.remove(session); sessionManager.remove(session);
log.info("[WebSocket] 连接关闭 sessionId={}", session.getId());
} }
@OnError @OnError
public void onError(Session session, Throwable error) { public void onError(Session session, Throwable error) {
if (session != null) { if (session != null) {
sessionManager.remove(session); 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("&"); String[] params = query.split("&");
for (String param : params) { for (String param : params) {
if (param.startsWith("deptId=")) { if (param.startsWith("token=")) {
return Long.parseLong(param.substring(7)); String token = param.substring(6);
token = URLDecoder.decode(token, "UTF-8");
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}
return token;
} }
} }
throw new IllegalArgumentException("token not found");
throw new IllegalArgumentException("deptId not found");
} }
/**
* 获取客户端IP
*/
private String getIp(Session session) { private String getIp(Session session) {
try { try {
// 标准方式(部分容器支持)
Object addr = session.getUserProperties().get("javax.websocket.endpoint.remoteAddress"); Object addr = session.getUserProperties().get("javax.websocket.endpoint.remoteAddress");
if (addr instanceof InetSocketAddress) { if (addr instanceof InetSocketAddress) {
return ((InetSocketAddress) addr).getAddress().getHostAddress(); InetSocketAddress inetSocketAddress = (InetSocketAddress) addr;
if (inetSocketAddress.getAddress() != null) {
return inetSocketAddress.getAddress().getHostAddress();
}
} }
} catch (Exception e) {
} catch (Exception ignored) {} log.warn("[WebSocket] 获取客户端IP失败 sessionId={}", session.getId(), e);
}
// fallback
return "unknown"; return "unknown";
} }
private void closeSession(Session session, String reason) { private void closeSession(Session session, String reason) {
try { try {
session.close(new CloseReason( if (session != null && session.isOpen()) {
CloseReason.CloseCodes.CANNOT_ACCEPT, session.close(new CloseReason(
reason CloseReason.CloseCodes.CANNOT_ACCEPT,
)); reason
));
}
} catch (IOException e) { } catch (IOException e) {
log.error("关闭失败", e); log.error("[WebSocket] 关闭连接失败 sessionId={}", session != null ? session.getId() : "null", e);
} }
} }
} }

View File

@@ -175,4 +175,89 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE FIND_IN_SET(#{deptId}, ancestors) WHERE FIND_IN_SET(#{deptId}, ancestors)
</select> </select>
<select id="countProjectByDeptIds" resultType="int">
SELECT COUNT(1)
FROM sys_dept d
WHERE d.del_flag = '0'
AND d.status = '0'
AND d.parent_id != 0
AND EXISTS (
SELECT 1 FROM sys_dept c
WHERE c.parent_id = d.dept_id
AND c.del_flag = '0'
)
<!-- ✅ 权限范围 -->
AND d.dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="countWarehouseByDeptIds" resultType="int">
SELECT COUNT(1)
FROM sys_dept d
WHERE d.del_flag = '0'
AND d.status = '0'
AND NOT EXISTS (
SELECT 1 FROM sys_dept c
WHERE c.parent_id = d.dept_id
AND c.del_flag = '0'
)
AND d.dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="selectWarehouseList" resultType="map">
SELECT
w.dept_id AS deptId,
w.dept_name AS warehouseName,
/* 上级(项目) */
p.dept_name AS parentName,
/* 在线率 */
CASE
WHEN COUNT(d.id) = 0 THEN 0
ELSE ROUND(
SUM(CASE WHEN d.status = 0 THEN 1 ELSE 0 END) * 1.0 / COUNT(d.id) * 100,
1
)
END AS onlineRate
FROM sys_dept w
/* 上级 */
LEFT JOIN sys_dept p
ON w.parent_id = p.dept_id
/* 设备 */
LEFT JOIN mqtt_sensor_device d
ON d.dept_id = w.dept_id
AND d.is_delete = 0
WHERE w.del_flag = '0'
/* ✅ 只查仓库(叶子节点) */
AND NOT EXISTS (
SELECT 1 FROM sys_dept c
WHERE c.parent_id = w.dept_id
AND c.del_flag = '0'
)
/* ✅ 权限控制(核心) */
AND w.dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
GROUP BY w.dept_id
ORDER BY w.dept_id DESC
</select>
</mapper> </mapper>

View File

@@ -1,45 +1,79 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttSensorDeviceMapper"> <mapper namespace="com.shzg.project.worn.mapper.MqttSensorDeviceMapper">
<!-- ================= resultMap ================= -->
<resultMap type="MqttSensorDevice" id="MqttSensorDeviceResult"> <resultMap type="MqttSensorDevice" id="MqttSensorDeviceResult">
<result property="id" column="id" /> <result property="id" column="id"/>
<result property="devEui" column="dev_eui" /> <result property="devEui" column="dev_eui"/>
<result property="deviceName" column="device_name" /> <result property="deviceName" column="device_name"/>
<result property="deviceType" column="device_type" /> <result property="deviceType" column="device_type"/>
<result property="deptId" column="dept_id" /> <result property="deptId" column="dept_id"/>
<result property="status" column="status" /> <result property="deptName" column="dept_name"/>
<result property="remark" column="remark" /> <result property="status" column="status"/>
<result property="createBy" column="create_by" /> <result property="remark" column="remark"/>
<result property="createTime" column="create_time" /> <result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by" /> <result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time" /> <result property="updateBy" column="update_by"/>
<result property="isDelete" column="is_delete" /> <result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
</resultMap> </resultMap>
<!-- ================= 公共查询SQL ================= -->
<sql id="selectMqttSensorDeviceVo"> <sql id="selectMqttSensorDeviceVo">
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
</sql> </sql>
<!-- ================= 查询列表 ================= -->
<select id="selectMqttSensorDeviceList" parameterType="MqttSensorDevice" resultMap="MqttSensorDeviceResult"> <select id="selectMqttSensorDeviceList" parameterType="MqttSensorDevice" resultMap="MqttSensorDeviceResult">
<include refid="selectMqttSensorDeviceVo"/> <include refid="selectMqttSensorDeviceVo"/>
<where> <where>
<if test="devEui != null and devEui != ''"> and dev_eui = #{devEui}</if> <if test="devEui != null and devEui != ''">
<if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if> and d.dev_eui = #{devEui}
<if test="deviceType != null and deviceType != ''"> and device_type = #{deviceType}</if> </if>
<if test="deptId != null "> and dept_id = #{deptId}</if> <if test="deviceName != null and deviceName != ''">
<if test="status != null and status != ''"> and status = #{status}</if> and d.device_name like concat('%', #{deviceName}, '%')
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if> </if>
<if test="deviceType != null and deviceType != ''">
and d.device_type = #{deviceType}
</if>
<if test="deptId != null">
and d.dept_id = #{deptId}
</if>
<if test="status != null and status != ''">
and d.status = #{status}
</if>
<if test="isDelete != null and isDelete != ''">
and d.is_delete = #{isDelete}
</if>
</where> </where>
</select> </select>
<!-- ================= 根据ID查询 ================= -->
<select id="selectMqttSensorDeviceById" parameterType="Long" resultMap="MqttSensorDeviceResult"> <select id="selectMqttSensorDeviceById" parameterType="Long" resultMap="MqttSensorDeviceResult">
<include refid="selectMqttSensorDeviceVo"/> <include refid="selectMqttSensorDeviceVo"/>
where id = #{id} where d.id = #{id}
</select> </select>
<!-- ================= 新增 ================= -->
<insert id="insertMqttSensorDevice" parameterType="MqttSensorDevice" useGeneratedKeys="true" keyProperty="id"> <insert id="insertMqttSensorDevice" parameterType="MqttSensorDevice" useGeneratedKeys="true" keyProperty="id">
insert into mqtt_sensor_device insert into mqtt_sensor_device
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -54,7 +88,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by,</if> <if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="isDelete != null">is_delete,</if> <if test="isDelete != null">is_delete,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="devEui != null and devEui != ''">#{devEui},</if> <if test="devEui != null and devEui != ''">#{devEui},</if>
<if test="deviceName != null">#{deviceName},</if> <if test="deviceName != null">#{deviceName},</if>
@@ -67,9 +101,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">#{updateBy},</if> <if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
<if test="isDelete != null">#{isDelete},</if> <if test="isDelete != null">#{isDelete},</if>
</trim> </trim>
</insert> </insert>
<!-- ================= 修改 ================= -->
<update id="updateMqttSensorDevice" parameterType="MqttSensorDevice"> <update id="updateMqttSensorDevice" parameterType="MqttSensorDevice">
update mqtt_sensor_device update mqtt_sensor_device
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
@@ -88,6 +123,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where id = #{id} where id = #{id}
</update> </update>
<!-- ================= 删除 ================= -->
<delete id="deleteMqttSensorDeviceById" parameterType="Long"> <delete id="deleteMqttSensorDeviceById" parameterType="Long">
delete from mqtt_sensor_device where id = #{id} delete from mqtt_sensor_device where id = #{id}
</delete> </delete>
@@ -99,10 +135,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
<!-- ================= 根据DevEUI查询 ================= -->
<select id="selectByDevEui" parameterType="String" resultMap="MqttSensorDeviceResult"> <select id="selectByDevEui" parameterType="String" resultMap="MqttSensorDeviceResult">
<include refid="selectMqttSensorDeviceVo"/> <include refid="selectMqttSensorDeviceVo"/>
WHERE dev_eui = #{devEui} where d.dev_eui = #{devEui}
AND is_delete = '0' and d.is_delete = '0'
LIMIT 1 limit 1
</select> </select>
<select id="countDeviceStat" resultType="map">
SELECT
COUNT(1) AS total,
COALESCE(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END),0) AS online,
COALESCE(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END),0) AS offline,
CASE
WHEN COUNT(1) = 0 THEN 0
ELSE ROUND(
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) / COUNT(1) * 100,
1
)
END AS onlineRate
FROM mqtt_sensor_device
WHERE is_delete = 0
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper> </mapper>

View File

@@ -115,4 +115,71 @@
</foreach> </foreach>
</delete> </delete>
<select id="countAlarmStat" resultType="map">
SELECT
/* 今日告警次数:今天新增的 alarm 事件数 */
(
SELECT COUNT(1)
FROM mqtt_sensor_event
WHERE is_delete = 0
AND event_type = 'alarm'
AND DATE(create_time) = CURDATE()
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
) AS today,
/* 未处理告警数:每个设备最新事件仍然是 alarm 的数量 */
(
SELECT COUNT(1)
FROM mqtt_sensor_event e
INNER JOIN (
SELECT device_id, MAX(create_time) AS max_time
FROM mqtt_sensor_event
WHERE is_delete = 0
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
GROUP BY device_id
) t
ON e.device_id = t.device_id
AND e.create_time = t.max_time
WHERE e.is_delete = 0
AND e.event_type = 'alarm'
) AS unhandled
</select>
<select id="selectAlarmTrend" resultType="map">
SELECT
DATE(create_time) AS date,
COUNT(1) AS count
FROM mqtt_sensor_event
WHERE is_delete = 0
AND event_type = 'alarm'
AND create_time >= DATE_SUB(CURDATE(), INTERVAL 6 DAY)
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
GROUP BY DATE(create_time)
ORDER BY date
</select>
<select id="selectAlarmTypeStat" resultType="map">
SELECT
event_desc AS name,
COUNT(1) AS value
FROM mqtt_sensor_event
WHERE is_delete = 0
AND event_type = 'alarm'
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
GROUP BY event_desc
ORDER BY value DESC
</select>
</mapper> </mapper>

View File

@@ -2,6 +2,7 @@
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shzg.project.worn.mapper.MqttTopicConfigMapper"> <mapper namespace="com.shzg.project.worn.mapper.MqttTopicConfigMapper">
<!-- ================== ResultMap ================== --> <!-- ================== ResultMap ================== -->
@@ -10,6 +11,7 @@
<result property="project" column="project" /> <result property="project" column="project" />
<result property="warehouse" column="warehouse" /> <result property="warehouse" column="warehouse" />
<result property="deptId" column="dept_id" /> <result property="deptId" column="dept_id" />
<result property="deptName" column="dept_name" /> <!-- ✅新增 -->
<result property="topicUp" column="topic_up" /> <result property="topicUp" column="topic_up" />
<result property="topicDownPrefix" column="topic_down_prefix" /> <result property="topicDownPrefix" column="topic_down_prefix" />
<result property="status" column="status" /> <result property="status" column="status" />
@@ -21,26 +23,53 @@
<result property="isDelete" column="is_delete" /> <result property="isDelete" column="is_delete" />
</resultMap> </resultMap>
<!-- ================== 基础查询SQL ================== --> <!-- ================== 基础查询SQL(连表) ================== -->
<sql id="selectMqttTopicConfigVo"> <sql id="selectMqttTopicConfigVo">
select id, project, warehouse, dept_id, topic_up, topic_down_prefix, select
status, remark, create_by, create_time, update_by, update_time, is_delete t.id,
from mqtt_topic_config 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
</sql> </sql>
<!-- ================== 列表查询(支持数据隔离) ================== --> <!-- ================== 列表查询(支持数据隔离) ================== -->
<select id="selectMqttTopicConfigList" parameterType="MqttTopicConfig" resultMap="MqttTopicConfigResult"> <select id="selectMqttTopicConfigList" parameterType="MqttTopicConfig" resultMap="MqttTopicConfigResult">
<include refid="selectMqttTopicConfigVo"/> <include refid="selectMqttTopicConfigVo"/>
<where> <where>
<if test="project != null and project != ''">and project = #{project}</if> <if test="project != null and project != ''">
<if test="warehouse != null and warehouse != ''">and warehouse = #{warehouse}</if> and t.project = #{project}
<if test="topicUp != null and topicUp != ''">and topic_up = #{topicUp}</if> </if>
<if test="topicDownPrefix != null and topicDownPrefix != ''">and topic_down_prefix = #{topicDownPrefix}</if> <if test="warehouse != null and warehouse != ''">
<if test="status != null and status != ''">and status = #{status}</if> and t.warehouse = #{warehouse}
<if test="isDelete != null and isDelete != ''">and is_delete = #{isDelete}</if> </if>
<if test="topicUp != null and topicUp != ''">
and t.topic_up = #{topicUp}
</if>
<if test="topicDownPrefix != null and topicDownPrefix != ''">
and t.topic_down_prefix = #{topicDownPrefix}
</if>
<if test="status != null and status != ''">
and t.status = #{status}
</if>
<if test="isDelete != null and isDelete != ''">
and t.is_delete = #{isDelete}
</if>
<!-- 🔥 数据权限 -->
<if test="deptIds != null and deptIds.size() > 0"> <if test="deptIds != null and deptIds.size() > 0">
and dept_id in and t.dept_id in
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")"> <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
#{deptId} #{deptId}
</foreach> </foreach>
@@ -51,20 +80,20 @@
<!-- ================== 根据ID查询 ================== --> <!-- ================== 根据ID查询 ================== -->
<select id="selectMqttTopicConfigById" parameterType="Long" resultMap="MqttTopicConfigResult"> <select id="selectMqttTopicConfigById" parameterType="Long" resultMap="MqttTopicConfigResult">
<include refid="selectMqttTopicConfigVo"/> <include refid="selectMqttTopicConfigVo"/>
where id = #{id} where t.id = #{id}
</select> </select>
<!-- ================== 查询启用的TopicMQTT用 ================== --> <!-- ================== 查询启用的TopicMQTT用 ================== -->
<select id="selectEnabledMqttTopicConfigList" resultMap="MqttTopicConfigResult"> <select id="selectEnabledMqttTopicConfigList" parameterType="MqttTopicConfig" resultMap="MqttTopicConfigResult">
<include refid="selectMqttTopicConfigVo"/> <include refid="selectMqttTopicConfigVo"/>
where status = '0' where t.status = '0'
and is_delete = '0' and t.is_delete = '0'
and topic_up is not null and t.topic_up is not null
and topic_up != '' and t.topic_up != ''
<!-- 🔥 防止跨项目订阅 --> <!-- 🔥 数据隔离 -->
<if test="deptIds != null and deptIds.size() > 0"> <if test="deptIds != null and deptIds.size() > 0">
and dept_id in and t.dept_id in
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")"> <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
#{deptId} #{deptId}
</foreach> </foreach>
@@ -122,12 +151,11 @@
where id = #{id} where id = #{id}
</update> </update>
<!-- ================== 删除(单个) ================== --> <!-- ================== 删除 ================== -->
<delete id="deleteMqttTopicConfigById" parameterType="Long"> <delete id="deleteMqttTopicConfigById" parameterType="Long">
delete from mqtt_topic_config where id = #{id} delete from mqtt_topic_config where id = #{id}
</delete> </delete>
<!-- ================== 批量删除================== -->
<delete id="deleteMqttTopicConfigByIds"> <delete id="deleteMqttTopicConfigByIds">
delete from mqtt_topic_config where id in delete from mqtt_topic_config where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
@@ -135,11 +163,12 @@
</foreach> </foreach>
</delete> </delete>
<select id="selectByDeptId" resultMap="MqttTopicConfigResult"> <!-- ================== 根据部门查询 ================== -->
<select id="selectByDeptId" parameterType="Long" resultMap="MqttTopicConfigResult">
<include refid="selectMqttTopicConfigVo"/> <include refid="selectMqttTopicConfigVo"/>
where dept_id = #{deptId} where t.dept_id = #{deptId}
and status = '0' and t.status = '0'
and is_delete = '0' and t.is_delete = '0'
limit 1 limit 1
</select> </select>