selectLatestSleepByDateRange(@Param("empNo") String empNo, @Param("startDate") String startDate, @Param("endDate") String endDate);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosLogMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosLogMapper.java
new file mode 100644
index 0000000..d6457c0
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosLogMapper.java
@@ -0,0 +1,16 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchSosLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ *
+ * 腕表sos报警日志表 Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-03-18
+ */
+public interface WatchSosLogMapper extends BaseMapper {
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosMapper.java
new file mode 100644
index 0000000..3f43799
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSosMapper.java
@@ -0,0 +1,20 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchSos;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.vo.controllerVO.SosPhoneVO;
+
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-05
+ */
+public interface WatchSosMapper extends BaseMapper {
+
+ List selectQueryVO(SosPhoneVO sosPhoneVo);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchStepMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchStepMapper.java
new file mode 100644
index 0000000..43b1f0a
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchStepMapper.java
@@ -0,0 +1,19 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchStep;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.vo.controllerVO.HealthQueryVO;
+import com.smartlive.bswatch.vo.controllerVO.WatchStepVO;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-05
+ */
+public interface WatchStepMapper extends BaseMapper {
+
+ WatchStepVO queryVO(HealthQueryVO healthQueryVO);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysDeptMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysDeptMapper.java
new file mode 100644
index 0000000..1df0f70
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysDeptMapper.java
@@ -0,0 +1,18 @@
+package com.smartlive.bswatch.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.entity.WatchSysDept;
+import com.smartlive.bswatch.vo.commandVO.SysDeptVO;
+
+/**
+ *
+ * 部门表 Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-07
+ */
+public interface WatchSysDeptMapper extends BaseMapper {
+
+ SysDeptVO getSysDeptVO(Integer deptId);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysOrganiMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysOrganiMapper.java
new file mode 100644
index 0000000..5573e05
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchSysOrganiMapper.java
@@ -0,0 +1,16 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.SysOrgani;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ *
+ * 组织表 Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-07
+ */
+public interface WatchSysOrganiMapper extends BaseMapper {
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchTemperatureMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchTemperatureMapper.java
new file mode 100644
index 0000000..c0996de
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchTemperatureMapper.java
@@ -0,0 +1,34 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchTemperature;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.vo.commandVO.HeathReportVO;
+import com.smartlive.bswatch.vo.controllerVO.HealthQueryVO;
+import com.smartlive.bswatch.vo.controllerVO.TemperatureVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-05
+ */
+public interface WatchTemperatureMapper extends BaseMapper {
+
+ List queryVO(HealthQueryVO healthQueryVO);
+
+ List selectByEmpAndTime(@Param("empNo") String empNo, @Param("startDate") String startDate, @Param("endDate") String endDate);
+
+ /**
+ * 查询指定时间段内每天最新的体温记录
+ * @param startDate 开始时间
+ * @param endDate 结束时间
+ * @param empNo 员工编号
+ * @return 每天最新的体温记录列表
+ */
+ List selectLatestTemperatureByDateRange(@Param("empNo") String empNo, @Param("startDate") String startDate, @Param("endDate") String endDate);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWarnMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWarnMapper.java
new file mode 100644
index 0000000..7bd5ac6
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWarnMapper.java
@@ -0,0 +1,20 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchWarn;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.vo.controllerVO.WatchWarnVO;
+
+import java.util.List;
+
+/**
+ *
+ * 腕表定位警报表 Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-08
+ */
+public interface WatchWarnMapper extends BaseMapper {
+
+ List selectQueryVO(WatchWarnVO watchWarnVO);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWaterMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWaterMapper.java
new file mode 100644
index 0000000..582d3d7
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWaterMapper.java
@@ -0,0 +1,17 @@
+package com.smartlive.bswatch.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.entity.Water;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-04-01
+ */
+public interface WatchWaterMapper extends BaseMapper {
+
+ Water queryWater();
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhiteMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhiteMapper.java
new file mode 100644
index 0000000..5be9a5b
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhiteMapper.java
@@ -0,0 +1,20 @@
+package com.smartlive.bswatch.mapper;
+
+import com.smartlive.bswatch.entity.WatchWhite;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.vo.controllerVO.WatchWhiteVO;
+
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-05
+ */
+public interface WatchWhiteMapper extends BaseMapper {
+
+ List queryByVO(WatchWhiteVO watchWhiteVO);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhitePeriodMapper.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhitePeriodMapper.java
new file mode 100644
index 0000000..6c7f8cf
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/mapper/WatchWhitePeriodMapper.java
@@ -0,0 +1,20 @@
+package com.smartlive.bswatch.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.smartlive.bswatch.entity.WatchWhitePeriod;
+import com.smartlive.bswatch.vo.controllerVO.WatchWhitePeriodVO;
+
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author lilj
+ * @since 2025-02-05
+ */
+public interface WatchWhitePeriodMapper extends BaseMapper {
+
+ List selectQueryVO(WatchWhitePeriodVO watchWhitePeriodVO);
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/channel/WatchChannelMap.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/channel/WatchChannelMap.java
new file mode 100644
index 0000000..419eb01
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/channel/WatchChannelMap.java
@@ -0,0 +1,42 @@
+package com.smartlive.bswatch.netty.channel;
+
+import io.netty.channel.Channel;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+public class WatchChannelMap {
+
+ private static ConcurrentHashMap channelHashMap = new ConcurrentHashMap<>();
+
+ public static ConcurrentHashMap getChannelHashMap() {
+ return channelHashMap;
+ }
+
+ public static void addChannel(String sign, Channel context) {
+ /* log.info("addChannel---待添加的channel,hashCode=" + context.hashCode());
+ Channel channel = getChannel(sign);
+ log.info("addChannel---当前channel有效,hashCode=" + (channel == null ? "null" : channel.hashCode()));*/
+ channelHashMap.put(sign, context);
+ log.info("addChannel---设置新的channel,hashCode=" + context.hashCode());
+ }
+
+ public static Channel getChannel(String sign) {
+
+ if (channelHashMap == null || channelHashMap.isEmpty()) {
+ log.error("channelmap异常");
+ return null;
+ }
+ // 通道有效性检测
+ Channel channel = channelHashMap.get(sign);
+ // 通道未激活,移除通道
+ if (channel != null && !channel.isActive()) {
+ log.error("channel无效,执行移除操作,hashCode=" + channel.hashCode());
+ channelHashMap.remove(sign);
+ return null;
+ }
+ return channel;
+ }
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/TestEvent.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/TestEvent.java
new file mode 100644
index 0000000..e8ba1d3
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/TestEvent.java
@@ -0,0 +1,22 @@
+package com.smartlive.bswatch.netty.event;
+
+import lombok.Data;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author:lilj
+ * @date:2025/2/7
+ */
+public class TestEvent extends ApplicationEvent {
+
+ private String message;
+
+ public TestEvent(Object source,String message) {
+ super(source);
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/WatchCommandBatchEvent.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/WatchCommandBatchEvent.java
new file mode 100644
index 0000000..5da913f
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/event/WatchCommandBatchEvent.java
@@ -0,0 +1,25 @@
+package com.smartlive.bswatch.netty.event;
+
+import com.smartlive.bswatch.netty.watchvo.MessageVO;
+import org.springframework.context.ApplicationEvent;
+
+import java.util.List;
+
+/**
+ * @author:lilj
+ * @date:2025/2/11
+ */
+
+public class WatchCommandBatchEvent extends ApplicationEvent {
+
+ private List messageVOS;
+
+ public WatchCommandBatchEvent(Object source,List messageVOS) {
+ super(source);
+ this.messageVOS = messageVOS;
+ }
+
+ public List getMessage() {
+ return messageVOS;
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/handler/WatchHandler.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/handler/WatchHandler.java
new file mode 100644
index 0000000..d65617e
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/handler/WatchHandler.java
@@ -0,0 +1,723 @@
+package com.smartlive.bswatch.netty.handler;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.smartlive.bswatch.TO.LocationTO;
+import com.smartlive.bswatch.config.TestConfig;
+import com.smartlive.bswatch.costant.WatchConstant;
+import com.smartlive.bswatch.enmus.WatchApiEnum;
+import com.smartlive.bswatch.netty.channel.WatchChannelMap;
+import com.smartlive.bswatch.netty.watchvo.MessageHeaderVO;
+import com.smartlive.bswatch.netty.watchvo.MessageVO;
+import com.smartlive.bswatch.netty.watchvo.req.deviceUpload.PositionUploadReq;
+import com.smartlive.bswatch.netty.watchvo.resp.commonResp.CommonUploadResp;
+import com.smartlive.bswatch.netty.watchvo.resp.commonResp.ErrorResp;
+import com.smartlive.bswatch.netty.watchvo.resp.deviceResp.*;
+import com.smartlive.bswatch.netty.watchvo.resp.entity.GPSLocation;
+import com.smartlive.bswatch.netty.watchvo.resp.entity.LbsLocation;
+import com.smartlive.bswatch.netty.watchvo.resp.entity.WifiLocation;
+import com.smartlive.bswatch.service.IDevicePullDataService;
+import com.smartlive.bswatch.service.IDeviceUploadService;
+import com.smartlive.bswatch.test.entity.WatchCurrentLocationLog;
+import com.smartlive.bswatch.test.service.impl.WatchCurrentLocationLogServiceImpl;
+import com.smartlive.bswatch.utils.CardAesUtil;
+import com.smartlive.bswatch.utils.PositionUtil;
+import com.smartlive.bswatch.utils.WatchMsgParseUtil;
+import com.smartlive.common.core.redis.RedisCache;
+import com.smartlive.common.utils.spring.SpringUtils;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * @author:lilj
+ * @date:2025/1/18
+ */
+@Slf4j
+@Component
+public class WatchHandler {
+ private static final WatchHandler watchHandler = new WatchHandler();
+
+ private RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
+
+
+ // 设备数据上传处理的service
+ private IDeviceUploadService deviceUploadService = SpringUtils.getBean(IDeviceUploadService.class);
+
+ // 设备拉取数据的service
+ private IDevicePullDataService devicePullDataService = SpringUtils.getBean(IDevicePullDataService.class);
+
+ private WatchCurrentLocationLogServiceImpl watchCurrentLocationLogServiceImpl = SpringUtils.getBean(WatchCurrentLocationLogServiceImpl.class);
+
+ private static RestTemplate restTemplate = new RestTemplate();
+
+ // 测试完成后要删除的代码
+ private static final String INFOCODE_KEY = "infocode";
+ private static final String RESULT_KEY = "result";
+ private static final String DESC_KEY = "desc";
+ private static final String LOCATION_KEY = "location";
+ private static final String REGEOCODE_KEY = "regeocode";
+ private static final String ADDRESS_KEY = "formatted_address";
+ private static final String AREA_CODE_KEY = "adcode";
+ private static final String ADDRESS_COMPONENT_KEY = "addressComponent";
+
+
+ private WatchHandler() {
+ }
+
+ public static WatchHandler getInstance() {
+ return watchHandler;
+ }
+
+ public void dealMsg(ChannelHandlerContext ctx, String msg) throws ParseException {
+
+ // 根据消息判断业务类型
+ // 解析消息:自定解密并转换为实体类
+ // 已经自动处理异常,如果消息转换出现异常会返回null
+ MessageVO messageVO = WatchMsgParseUtil.getMsg(msg);
+
+ // 消息解析失败
+ if (messageVO == null) {
+ log.error("消息解析失败,暂不做处理,消息为:" + msg);
+ return;
+ }
+
+ if (messageVO.getMessageHeader() == null || messageVO.getMessageHeader().getMsgType() == null) {
+ log.error("消息头请求类型缺失,暂不做处理,消息为:" + msg);
+ return;
+ }
+
+ // 对于非登录请求且设备主动发起的请求,判断是否已经登录.
+ if (!WatchApiEnum.LOGIN.getApiName().equals(messageVO.getMessageHeader().getApiName()) &&
+ WatchConstant.MSG_TYPE_UP_REQ.equals(messageVO.getMessageHeader().getMsgType())) {
+ Boolean isLogin = redisCache.getCacheObject(WatchConstant.LOGIN_PREFIX + messageVO.getMessageHeader().getWatchSign());
+ if (isLogin == null || !isLogin) {
+ log.error("设备未登录,断开连接,消息为:" + msg);
+ ctx.channel().close();
+ return;
+ }
+ }
+ // 响应数据
+ dealMsg(ctx.channel(), messageVO);
+ // 错误发生导致链接断开,避免心跳请求间隔过长,导致下发指令长时间失败;因此每次消息处理都尝试保存连接
+ Channel channel = ctx.channel();
+ WatchChannelMap.addChannel(messageVO.getMessageHeader().getWatchSign(), channel);
+ }
+
+ /**
+ * 响应消息解析处理.
+ *
+ * @param channel
+ * @param messageVO
+ * @throws ParseException
+ */
+ public void dealMsg(Channel channel, MessageVO messageVO) throws ParseException {
+ try {
+ WatchApiEnum byApiName = WatchApiEnum.getByApiName(messageVO.getMessageHeader().getApiName());
+ switch (byApiName) {
+ // 设备请求
+ case LOGIN:
+ // 登录请求
+ dealLogin(channel, messageVO);
+ break;
+ case GET_BUTTON:
+ // 获取正长按键
+ dealGetButton(channel, messageVO);
+ break;
+ case GET_INCOME_CALL:
+ // 获取可呼入号码
+ dealIncomeCall(channel, messageVO);
+ break;
+ case GET_CLASS_MODEL:
+ dealGetClassModel(channel, messageVO);
+ break;
+ case GET_SMS_PORT:
+ case GET_SKIP_MAC:
+ dealGetSkipMac(channel, messageVO);
+ break;
+ case GET_WEATHER_NOW:
+ dealGetWeatherNow(channel, messageVO);
+ break;
+ case GET_WEATHER_FORCAST:
+ case GET_WEATHER_INFO:
+ case ERROR_RESP:
+ // 响应数据
+ requestCommonResp(channel, messageVO);
+ break;
+ case GET_STUDENT_INFO:
+ dealGetStudentInfo(channel, messageVO);
+ break;
+
+
+
+ // 设备数据上传
+ // 电量告警
+ case ALARM_POWER:
+ deviceUploadService.recordPower(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ case REPORT_SMS_READ:
+ case REPORT_SOS:
+ deviceUploadService.recordSos(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ case REPORT_HEART_HEALTH:
+ deviceUploadService.recordHealth(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ case REPORT_LOCATION_INFO:
+ deviceUploadService.recordLocation(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ // 通话记录上报
+ case REPORT_CALL_LOG:
+ deviceUploadService.recordCallLog(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ // 心跳请求
+ case REPORT_HEARTBEAT:
+ dealHearBeatReq(channel, messageVO);
+ break;
+ case DEVICE_STATUS:
+ deviceUploadService.recordWatchModeLog(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ case REPORT_SLEEP_DATA:
+ deviceUploadService.recordSleep(messageVO);
+ uploadCommonResp(channel, messageVO);
+ break;
+ // 指令下发后设备的响应
+ case SET_INCOMING_CALL:
+ case SET_NORMAL_BUTTON:
+ case REQUEST_CALL:
+ case SEND_SMS:
+ case FREQUENCY_LOCATION_SET:
+ case SET_HEARTBEAT:
+ case SET_CLASS_MODEL:
+ case SET_STUDENT_INFO:
+ case SET_REDAY_MODE:
+ case SET_LOCATION_MODE:
+ log.info("watchHandler收到下发命令后设备的响应:{}", messageVO);
+ break;
+ case LOCATION_INFO_GET:
+ log.info("watchHandler收到下发命令后设备的响应:{}", messageVO);
+ deviceUploadService.recordLocation(messageVO);
+ // updateCurrentLocationLog(messageVO);
+ break;
+ default:
+ log.error("未知的腕表消息类型,接口:" + messageVO.getMessageHeader().getApiName());
+ }
+ } catch (Exception e) {
+ log.error("watchHandler处理消息异常,messageVO:" + messageVO, e);
+
+ // 消息解析成功,做个异常响应
+ if (messageVO != null) {
+ MessageHeaderVO headerVO = messageVO.getMessageHeader();
+ // 其他情况不做处理
+ if (headerVO != null && headerVO.getMsgType() != null) {
+ // 1:设备请求消息 2:平台响应消息 出现异常返回异常消息体
+ if (WatchConstant.MSG_TYPE_UP_REQ.equals(headerVO.getMsgType()) || WatchConstant.MSG_TYPE_UP_RESP.equals(headerVO.getMsgType())) {
+ msgExceptionResponse(channel, messageVO);
+ }
+
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 收到设备获取实时位置的响应处理
+ *
+ * @param messageVO
+ */
+ public void updateCurrentLocationLog(MessageVO messageVO) {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ WatchCurrentLocationLog watchCurrentLocationLog = new WatchCurrentLocationLog();
+ try {
+ // 实时定位数据获取
+ Integer id = redisCache.getCacheObject(header.getApiName() + ":" + header.getBusinessNo());
+ Long locationId = Long.valueOf(id);
+ watchCurrentLocationLog.setLocationId(locationId);
+ watchCurrentLocationLog.setWatchMsg(JSON.toJSONString(messageVO));
+
+ if (messageVO.getMessageBody() == null || "1".equals(messageVO.getMessageBody())) {
+ watchCurrentLocationLog.setWatchMsg("[" + header.toString() + ",1]");
+ // 定位失败
+ watchCurrentLocationLog.setType(2);
+ watchCurrentLocationLog.setGaodeMsg("定位失败");
+ log.error("获取位置信息定位失败");
+ return;
+ }
+ PositionUploadReq positionUploadReq = (PositionUploadReq) messageVO.getMessageBody();
+ // 尝试获取地址信息
+ String posType = positionUploadReq.getPosType();
+ if ("1".equals(posType)) {
+ gpsPosition(positionUploadReq.getGpsLocation(), watchCurrentLocationLog);
+ } else if ("0".equals(posType)) {
+ smartPosition(positionUploadReq.getLbsLocation(), positionUploadReq.getWifiLocationList(), watchCurrentLocationLog);
+ }
+
+ } finally {
+ if (watchCurrentLocationLog.getLocationId() != null) {
+ watchCurrentLocationLogServiceImpl.updateById(watchCurrentLocationLog);
+ }
+ }
+
+ // watchCurrentLocationLogServiceImpl
+ }
+
+ // 解析地址信息
+ public void gpsPosition(GPSLocation gpsLocation, WatchCurrentLocationLog watchCurrentLocationLog) {
+ // key:97509bb13f02a18faf682d0f3853635e
+ // GPS定位key17d09033036d5e2b3d5ccf646727a5ae
+ String gpsUrl = "https://restapi.amap.com/v3/geocode/regeo";
+ String location = gpsLocation.toString();
+ String fullUrl = UriComponentsBuilder.fromHttpUrl(gpsUrl)
+ .queryParam("key", "17d09033036d5e2b3d5ccf646727a5ae")
+ .queryParam("location", location)
+ .toUriString();
+ log.info("fullUrl:" + fullUrl);
+ // 发送 GET 请求
+ try {
+ ResponseEntity response = restTemplate.getForEntity(fullUrl, String.class);
+ watchCurrentLocationLog.setGaodeMsg(response.toString());
+ watchCurrentLocationLog.setGaodeUrl(fullUrl);
+
+ if (response.getStatusCode() == HttpStatus.OK) {
+ String body = response.getBody();
+ JSONObject jsonObject = JSONObject.parseObject(body, JSONObject.class);
+ String infocode = jsonObject.getString(INFOCODE_KEY);
+ if ("10000".equals(infocode)) {
+ JSONObject result = jsonObject.getJSONObject(REGEOCODE_KEY);
+ if (result != null) {
+ String address = result.getString(ADDRESS_KEY);
+ JSONObject addressComponent = result.getJSONObject(ADDRESS_COMPONENT_KEY);
+ String areaCode = null;
+ if(addressComponent != null){
+ areaCode = addressComponent.getString(AREA_CODE_KEY);
+ }
+ LocationTO locationTO = PositionUtil.buildLocationTO(gpsLocation.getLongitude(), gpsLocation.getLatitude(), address,areaCode);
+
+ watchCurrentLocationLog.setType(0);
+ } else {
+ String errorInfo = "高德GPS定位接口返回的结果为空";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } else {
+ String errorInfo = "高德GPS定位接口正常,但返回错误信息";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+
+ }
+ } else {
+ String errorInfo = "高德GPS定位接口调用失败";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } catch (RestClientException e) {
+ String errorInfo = "高德GPS定位接口调用失败:" + e.getMessage();
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+
+ } catch (Exception e) {
+ String errorInfo = "高德GPS定位接口调用失败:" + e.getMessage();
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+
+ }
+
+ // 解析地址信息
+ public void smartPosition(LbsLocation lbsLocation, List wifiLocationList, WatchCurrentLocationLog watchCurrentLocationLog) {
+ // 请求示例:
+ // http://apilocate.amap.com/position?accesstype=0&key=97509bb13f02a18faf682d0f3853635e&bts=460,01,11433,68256005,-56&macs=F4:83:CD:CA:67:3E&macs=F6:96:DE:96:FE:EB&macs=54:35:30:2F:26:BA
+ String smartUrl = "http://apilocate.amap.com/position";
+ UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(smartUrl)
+ .queryParam("accesstype", 0)
+ .queryParam("key", "122dc1af53c8267b71c869220e25780d")
+ .queryParam("bts", lbsLocation.toQueryString()); // LBS信息
+ if (!CollectionUtils.isEmpty(wifiLocationList)) {
+ for (WifiLocation wifiLocation : wifiLocationList) {
+ uriComponentsBuilder.queryParam("macs", wifiLocation.getMac());
+ }
+ }
+ String fullUrl = uriComponentsBuilder.toUriString();
+ log.info("fullUrl:" + fullUrl);
+ // 发送 GET 请求
+ try {
+ ResponseEntity response = restTemplate.getForEntity(fullUrl, String.class);
+
+ watchCurrentLocationLog.setGaodeMsg(response.toString());
+ watchCurrentLocationLog.setGaodeUrl(fullUrl);
+ if (response.getStatusCode() == HttpStatus.OK) {
+ String body = response.getBody();
+ JSONObject jsonObject = JSONObject.parseObject(body, JSONObject.class);
+ String infocode = jsonObject.getString(INFOCODE_KEY);
+ if ("10000".equals(infocode)) {
+ JSONObject result = jsonObject.getJSONObject(RESULT_KEY);
+ if (result != null) {
+ String address = result.getString(DESC_KEY);
+ String location = result.getString(LOCATION_KEY);
+ String areaCode = result.getString(AREA_CODE_KEY);
+ if (location != null && location.contains(",")) {
+ String[] split = location.split(",");
+ if (split.length == 2) {
+ LocationTO locationTO = PositionUtil.buildLocationTO(split[0], split[1], address,areaCode);
+ watchCurrentLocationLog.setType(0);
+ } else {
+
+ String errorInfo = "高德智能定位接口返回的位置格式不正确,不予记录";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } else {
+ String errorInfo = "高德智能定位接口返回的位置为空或格式不正确";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } else {
+ String errorInfo = "高德智能定位接口返回的结果为空";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } else {
+ String errorInfo = "高德智能定位接口正常,但返回错误信息";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } else {
+ String errorInfo = "高德智能定位接口调用失败";
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ } catch (RestClientException e) {
+ String errorInfo = "高德智能定位接口调用失败:" + e.getMessage();
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ } catch (Exception e) {
+ String errorInfo = "高德智能定位接口调用失败:" + e.getMessage();
+ watchCurrentLocationLog.setErroInfo(errorInfo);
+ watchCurrentLocationLog.setType(4);
+ }
+ }
+
+ private void dealHearBeatReq(Channel channel, MessageVO messageVO) {
+ try {
+ WatchChannelMap.addChannel(messageVO.getMessageHeader().getWatchSign(), channel);
+ // 记录步数数据
+ deviceUploadService.recordStep(messageVO);
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ messageVO.setMessageBody("0");
+ msgResponse(channel, messageVO);
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ private void dealGetStudentInfo(Channel channel, MessageVO messageVO) {
+ try {
+
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ // 获取按键回复异常
+ // StudentInfoResp respVO = StudentInfoResp.defaultResp();
+ StudentInfoResp respVO = devicePullDataService.getStudentInfoResp(messageVO);
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ if (TestConfig.realResp == true || !messageVO.getMessageHeader().getApiName().equals(TestConfig.bizType)) {
+ messageVO.setMessageBody(respVO);
+ } else {
+ // 使用模拟请求
+ respVO = JSON.to(StudentInfoResp.class, TestConfig.paramObject);
+ messageVO.setMessageBody(respVO);
+ }
+
+
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+
+ /**
+ * 登录处理
+ *
+ * @param channel
+ * @param messageVO
+ */
+ private void dealLogin(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ String watchSign = header.getWatchSign();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 执行业务逻辑:缓存信息,数据变动更新,未变动不更新.
+ // 可能会变动的字段:watch_sign(查询用),protocol_version,rfid,iccid
+ boolean isSuccess = deviceUploadService.login(messageVO);
+ // 登录回复
+ LoginResp respVO = isSuccess ? LoginResp.successLogin() : LoginResp.failRegister();
+ if (isSuccess) {
+ // 保存设备channel
+ WatchChannelMap.addChannel(watchSign, channel);
+ redisCache.setCacheObject(WatchConstant.LOGIN_PREFIX + watchSign, true);
+ }
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ if (TestConfig.realResp == true || !messageVO.getMessageHeader().getApiName().equals(TestConfig.bizType)) {
+ messageVO.setMessageBody(respVO);
+ } else {
+ // 使用模拟请求
+ respVO = JSON.to(LoginResp.class, TestConfig.paramObject);
+ messageVO.setMessageBody(respVO);
+ }
+
+ msgResponse(channel, messageVO);
+ if (!isSuccess) {
+ // 断开连接
+ channel.close();
+ }
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+
+ /**
+ * 获取按键处理
+ *
+ * @param channel
+ * @param messageVO
+ */
+ private void dealGetButton(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ // 获取按键回复异常
+ // 查询watch_sos以及emp_phone的亲情电话
+ // NormalButtonResp respVO = new NormalButtonResp();
+ NormalButtonResp respVO = devicePullDataService.getButton(messageVO);
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ if (TestConfig.realResp == true || !messageVO.getMessageHeader().getApiName().equals(TestConfig.bizType)) {
+ messageVO.setMessageBody(respVO);
+ } else {
+ // 使用模拟请求
+ respVO = JSON.to(NormalButtonResp.class, TestConfig.paramObject);
+ messageVO.setMessageBody(respVO);
+ }
+
+
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ /**
+ * 获取可呼入号码处理
+ *
+ * @param channel
+ * @param messageVO
+ */
+ private void dealIncomeCall(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 获取按键回复异常
+ // IncomeCallResp respVO = new IncomeCallResp();
+ // List times = new ArrayList<>();
+ // times.add(WatchConstant.ALL_TIME);
+ // respVO.addPhoneNode("18639771948", times, "lhs");
+ // respVO.setPeriod(WatchConstant.ALL_PERIOD);
+ // respVO.setIncallLimit("0");
+ IncomeCallResp respVO = devicePullDataService.getIncomeCall(messageVO);
+
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ if (TestConfig.realResp == true || !messageVO.getMessageHeader().getApiName().equals(TestConfig.bizType)) {
+ messageVO.setMessageBody(respVO);
+ } else {
+ // 使用模拟请求
+ respVO = JSON.to(IncomeCallResp.class, TestConfig.paramObject);
+ messageVO.setMessageBody(respVO);
+ }
+
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ /**
+ * 获取课堂模式处理
+ *
+ * @param channel
+ * @param messageVO
+ */
+ private void dealGetClassModel(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 获取按键回复异常
+ // ClassModelResp respVO = ClassModelResp.getDefault();
+ ClassModelResp respVO = devicePullDataService.getClassModelResp(messageVO);
+ if (TestConfig.realResp == true || !messageVO.getMessageHeader().getApiName().equals(TestConfig.bizType)) {
+ messageVO.setMessageBody(respVO);
+ } else {
+ // 使用模拟请求
+ respVO = JSON.to(ClassModelResp.class, TestConfig.paramObject);
+ messageVO.setMessageBody(respVO);
+ }
+
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ private void dealGetSkipMac(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 获取按键回复异常
+ // ClassModelResp respVO = ClassModelResp.getDefault();
+ String mac = devicePullDataService.getSkipMac(messageVO);
+ // 0 代表无设置
+ mac = StringUtils.isBlank(mac) ? "0" : mac;
+ messageVO.setMessageBody(mac);
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ private void dealGetWeatherNow(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 使用缓存测试用
+ String resp = redisCache.getCacheObject("weather_config:weather_info");
+ String useCache = redisCache.getCacheObject("weather_config:weather_use_cache");
+ if (StringUtils.isNotBlank(resp) && "1".equals(useCache)) {
+ messageVO.setMessageBody(resp);
+ msgResponse(channel, messageVO);
+ return;
+ }
+ PositionUploadReq req = (PositionUploadReq) messageVO.getMessageBody();
+ WeatherNowResp weatherNowResp = PositionUtil.getWeatherNow(req);
+ messageVO.setMessageBody(weatherNowResp);
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+ public void msgResponse(Channel channel, MessageVO messageVO) throws UnsupportedEncodingException {
+ String resp = WatchMsgParseUtil.msgResp(messageVO);
+ log.info("响应数据为:" + resp);
+ String enResp = new String(CardAesUtil.encryptBsj(resp.getBytes()), "UTF-8");
+ log.info("加密响应密文为:" + enResp);
+ channel.writeAndFlush(enResp);
+ }
+
+ public void msgExceptionResponse(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 暂不使用,直接返回未设置
+ ErrorResp respVO = ErrorResp.UNKNOWN_ERROR;
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ messageVO.setMessageBody(respVO);
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+
+ }
+
+ /**
+ * 设备数据上传公用响应
+ *
+ * @param channel
+ * @param messageVO
+ */
+ private void uploadCommonResp(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ // 获取按键回复正常
+ CommonUploadResp respVO = CommonUploadResp.NORMAL;
+ messageVO.setMessageBody(respVO);
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "接口上传数据回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "上传数据回复异常");
+ }
+ }
+
+ /**
+ * 设备请求,统一未设置异常响应
+ */
+ private void requestCommonResp(Channel channel, MessageVO messageVO) {
+ try {
+ MessageHeaderVO header = messageVO.getMessageHeader();
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+
+ // 暂不使用,直接返回未设置
+ ErrorResp respVO = ErrorResp.NO_SETTING;
+ header.setMsgType(WatchConstant.MSG_TYPE_UP_RESP);
+ messageVO.setMessageBody(respVO);
+ msgResponse(channel, messageVO);
+
+ } catch (Exception e) {
+ String apiName = messageVO.getMessageHeader().getApiName();
+ log.error(apiName + "回复异常:" + e.getMessage());
+ throw new RuntimeException(apiName + "回复异常");
+ }
+ }
+
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/EmpPhoneModifyListener.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/EmpPhoneModifyListener.java
new file mode 100644
index 0000000..7f963f8
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/EmpPhoneModifyListener.java
@@ -0,0 +1,31 @@
+package com.smartlive.bswatch.netty.listener;
+
+import com.smartlive.bswatch.service.CommonService;
+import com.smartlive.common.watchEvent.EmpPhoneModifyEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author:lilj
+ * @date:2025/2/12
+ */
+@Slf4j
+@Component
+public class EmpPhoneModifyListener implements ApplicationListener {
+ @Autowired
+ private CommonService commonService;
+ @Async
+ @Override
+ public void onApplicationEvent(EmpPhoneModifyEvent event) {
+ String watchSign = event.getWatchSign();
+ log.info("收到监听到修改手机号的事件,watchSign:"+watchSign);
+
+ log.info("=========> 开始调用commonService的setButton,下发命令");
+ commonService.setButton(watchSign);
+ log.info("==========> 结束调用commonService的setWhitePhone,下发命令完成");
+
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/TestListener.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/TestListener.java
new file mode 100644
index 0000000..0e94fe4
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/TestListener.java
@@ -0,0 +1,34 @@
+package com.smartlive.bswatch.netty.listener;
+
+import com.smartlive.bswatch.netty.event.TestEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author:lilj
+ * @date:2025/2/7
+ */
+@Slf4j
+@Component
+public class TestListener implements ApplicationListener {
+
+ /**
+ * 事件异步处理
+ * @param event
+ */
+ @Async()
+ @Override
+ public void onApplicationEvent(TestEvent event) {
+ try {
+ TimeUnit.SECONDS.sleep(10L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ log.info("监听者的线程:"+Thread.currentThread().getName());
+ log.info("接收到事件的消息:"+event.getMessage());
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/WatchBatchCommandListener.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/WatchBatchCommandListener.java
new file mode 100644
index 0000000..b6fdf52
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/listener/WatchBatchCommandListener.java
@@ -0,0 +1,39 @@
+package com.smartlive.bswatch.netty.listener;
+
+import com.smartlive.bswatch.netty.event.WatchCommandBatchEvent;
+import com.smartlive.bswatch.netty.watchvo.MessageVO;
+import com.smartlive.bswatch.utils.WatchMsgSendUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * @author:lilj
+ * @date:2025/2/7
+ */
+@Slf4j
+@Component
+public class WatchBatchCommandListener implements ApplicationListener {
+
+ /**
+ * 事件异步处理
+ * @param event
+ */
+ @Async()
+ @Override
+ public void onApplicationEvent(WatchCommandBatchEvent event) {
+ log.info("批量命令监听者的线程:"+Thread.currentThread().getName());
+ if(event==null || CollectionUtils.isEmpty(event.getMessage())){
+ log.info("批量命令为空,直接跳过");
+ return;
+ }
+ log.info("开始批量执行命令");
+ for (MessageVO messageVO : event.getMessage()){
+ WatchMsgSendUtil.sendCommandWithoutException(messageVO);
+ }
+ log.info("批量命令执行成功");
+ }
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/TestPublisher.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/TestPublisher.java
new file mode 100644
index 0000000..87f1efd
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/TestPublisher.java
@@ -0,0 +1,25 @@
+package com.smartlive.bswatch.netty.publisher;
+
+import com.smartlive.bswatch.netty.event.TestEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.reactivestreams.Publisher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author:lilj
+ * @date:2025/2/7
+ */
+@Slf4j
+@Component
+public class TestPublisher {
+ @Autowired
+ private ApplicationEventPublisher publisher;
+
+ public void publish(String message) {
+ log.info("发布者的线程:"+Thread.currentThread().getName());
+ TestEvent testEvent = new TestEvent(this, message);
+ publisher.publishEvent(testEvent);
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/WatchBatchCommandPublisher.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/WatchBatchCommandPublisher.java
new file mode 100644
index 0000000..04746e6
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/publisher/WatchBatchCommandPublisher.java
@@ -0,0 +1,28 @@
+package com.smartlive.bswatch.netty.publisher;
+
+import com.smartlive.bswatch.netty.event.WatchCommandBatchEvent;
+import com.smartlive.bswatch.netty.watchvo.MessageVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author:lilj
+ * @date:2025/2/11
+ */
+
+@Slf4j
+@Component
+public class WatchBatchCommandPublisher {
+
+ @Autowired
+ private ApplicationEventPublisher publisher;
+ public void publish(List messageVOS) {
+ log.info("批量命令发布者的线程:"+Thread.currentThread().getName());
+ WatchCommandBatchEvent event = new WatchCommandBatchEvent(this, messageVOS);
+ publisher.publishEvent(event);
+ }
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/ConvertCode.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/ConvertCode.java
new file mode 100644
index 0000000..808cdb7
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/ConvertCode.java
@@ -0,0 +1,214 @@
+package com.smartlive.bswatch.netty.tcp;
+
+public class ConvertCode {
+ /**
+ * @Title:bytes2HexString
+ * @Description:字节数组转16进制字符串
+ * @param b
+ * 字节数组
+ * @return 16进制字符串
+ * @throws
+ */
+ public static String bytes2HexString(byte[] b) {
+ StringBuffer result = new StringBuffer();
+ String hex;
+ for (int i = 0; i < b.length; i++) {
+ hex = Integer.toHexString(b[i] & 0xFF);
+ if (hex.length() == 1) {
+ hex = '0' + hex;
+ }
+ result.append(hex.toUpperCase());
+ }
+ return result.toString();
+ }
+ /**
+ * @Title:hexString2Bytes
+ * @Description:16进制字符串转字节数组
+ * @param src 16进制字符串
+ * @return 字节数组
+ */
+ public static byte[] hexString2Bytes(String src) {
+ int l = src.length() / 2;
+ byte[] ret = new byte[l];
+ for (int i = 0; i < l; i++) {
+ ret[i] = (byte) Integer
+ .valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
+ }
+ return ret;
+ }
+ /**
+ * @Title:string2HexString
+ * @Description:字符串转16进制字符串
+ * @param strPart 字符串
+ * @return 16进制字符串
+ */
+ public static String string2HexString(String strPart) {
+ StringBuffer hexString = new StringBuffer();
+ for (int i = 0; i < strPart.length(); i++) {
+ int ch = (int) strPart.charAt(i);
+ String strHex = Integer.toHexString(ch);
+ hexString.append(strHex);
+ }
+ return hexString.toString();
+ }
+ /**
+ * @Title:hexString2String
+ * @Description:16进制字符串转字符串
+ * @param src
+ * 16进制字符串
+ * @return 字节数组
+ * @throws
+ */
+ public static String hexString2String(String src) {
+ String temp = "";
+ for (int i = 0; i < src.length() / 2; i++) {
+ //System.out.println(Integer.valueOf(src.substring(i * 2, i * 2 + 2),16).byteValue());
+ temp = temp+ (char)Integer.valueOf(src.substring(i * 2, i * 2 + 2),16).byteValue();
+ }
+ return temp;
+ }
+ /**
+ * @Title:char2Byte
+ * @Description:字符转成字节数据char-->integer-->byte
+ * @param src
+ * @return
+ * @throws
+ */
+ public static Byte char2Byte(Character src) {
+ return Integer.valueOf((int)src).byteValue();
+ }
+ /**
+ * @Title:intToHexString
+ * @Description:10进制数字转成16进制
+ * @param a 转化数据
+ * @param len 占用字节数
+ * @return
+ * @throws
+ */
+ public static String intToHexString(int a,int len){
+ len<<=1;
+ String hexString = Integer.toHexString(a);
+ int b = len -hexString.length();
+ if(b>0){
+ for(int i=0;i1
+ 1 xor f7-->f6
+ f6 xor 0f-->f9
+ ....
+ 62 xor 84-->e6
+ 即,得到的一字节校验码为:e6
+ * @return
+ */
+ public static String xor(String strHex_X,String strHex_Y){
+ //将x、y转成二进制形式
+ String anotherBinary=Integer.toBinaryString(Integer.valueOf(strHex_X,16));
+ String thisBinary=Integer.toBinaryString(Integer.valueOf(strHex_Y,16));
+ String result = "";
+ //判断是否为8位二进制,否则左补零
+ if(anotherBinary.length() != 8){
+ for (int i = anotherBinary.length(); i <8; i++) {
+ anotherBinary = "0"+anotherBinary;
+ }
+ }
+ if(thisBinary.length() != 8){
+ for (int i = thisBinary.length(); i <8; i++) {
+ thisBinary = "0"+thisBinary;
+ }
+ }
+ //异或运算
+ for(int i=0;i"07dd"
+ * @param input 需要补位的字符串
+ * @param size 补位后的最终长度
+ * @param symbol 按symol补充 如'0'
+ * @return
+ * N_TimeCheck中用到了
+ */
+ public static String fill(String input, int size, char symbol) {
+ while (input.length() < size) {
+ input = symbol + input;
+ }
+ return input;
+ }
+ public static String getReply(Integer schoolId,Integer num,Integer volume){
+ String prefix = string2HexString("ZF");
+ String schoolIds = intToHexString(schoolId, 1);
+ String nums = intToHexString(num, 1);
+ if (volume==0){
+ return prefix+"20"+schoolIds+nums+"0D0A";
+ }
+ String volumes = intToHexString(volume, 1);
+ return prefix+"40"+schoolIds+nums+volumes+"0D0A";
+ }
+ public static void main(String args[]) {
+ String prefix = string2HexString("ZF");
+ String schoolId = intToHexString(10, 1);
+ String num = intToHexString(251, 1);
+ String vo = intToHexString(3, 1);
+ String a=prefix+"40"+schoolId+num+vo+"0D0A";
+ System.out.println(a);
+ }
+
+}
diff --git a/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/MyDecoder.java b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/MyDecoder.java
new file mode 100644
index 0000000..8ac4b7f
--- /dev/null
+++ b/smart-position-watch/src/main/java/com/smartlive/bswatch/netty/tcp/MyDecoder.java
@@ -0,0 +1,76 @@
+package com.smartlive.bswatch.netty.tcp;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+public class MyDecoder extends ByteToMessageDecoder {
+ /**
+ * 正式环境使用UTF8
+ * windows端模拟TCP使用的编码是GBK\
+ * 需要拼包处理
+ */
+ public final static String encoding = "UTF-8";
+
+ public final static String suffix = "#kdsjafjalsdjg#170";
+ private final int suffixLength = suffix.getBytes(encoding).length;
+ private final ByteBuf cumulation = Unpooled.buffer();
+
+ public MyDecoder() throws UnsupportedEncodingException {
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List