diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java index 1aa5fcdc..756f2ea3 100644 --- a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java @@ -66,4 +66,6 @@ public class GnssDevice { // 日志记录控制 private Short loggingmode; private String remark; + private String iccid; + } diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/SimCard.java b/sec-api/src/main/java/com/imdroid/secapi/dto/SimCard.java new file mode 100644 index 00000000..b5f2d3d7 --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/SimCard.java @@ -0,0 +1,51 @@ +package com.imdroid.secapi.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@TableName(value = "simcards") +public class SimCard { + public static final int STATUS_UNKNOWN = -1; // 未知 + public static final int STATUS_WAIT_ACTIVE = 1; // 待激活 + public static final int STATUS_ACTIVATED = 2; // 已激活 + public static final int STATUS_SUSPENDED = 3; // 停机 + public static final int STATUS_CANCELLED = 4; // 注销 + public static final int STATUS_IN_STOCK = 5; // 库存 + public static final int STATUS_TESTABLE = 6; // 可测试 + public static final int STATUS_INVALID = 7; // 失效 + public static final int STATUS_NOT_EXIST = 99; // 号码不存在 + + @TableId(type = IdType.AUTO) + @ExcelProperty("ID") + private Integer id; + + @ExcelProperty("更新时间") + private Date updatetime; + + @ExcelProperty("ICCID") + private String iccid; + + @ExcelProperty("物联卡号码") + private String msisdn; + + @ExcelProperty("设备ID") + private String deviceid; + + @ExcelProperty("状态") + private Integer status; + + @ExcelProperty("剩余流量(MB)") + private BigDecimal remaining; + + @ExcelProperty("总流量(MB)") + private BigDecimal total; + + @ExcelProperty("已用流量(MB)") + private BigDecimal used; +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/SimCardsMapper.java b/sec-api/src/main/java/com/imdroid/secapi/dto/SimCardsMapper.java new file mode 100644 index 00000000..6a8d5e35 --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/SimCardsMapper.java @@ -0,0 +1,40 @@ +package com.imdroid.secapi.dto; + +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface SimCardsMapper extends MPJBaseMapper{ + @Select({"select * from simcards where deviceid = #{deviceId} limit 1"}) + SimCard queryByDeviceId(String deviceId); + + @Select("select * from simcards where iccid = #{iccid} limit 1") + SimCard queryByIccid(String iccid); + + @Update("UPDATE simcards SET " + + "updatetime = #{updatetime}, " + + "msisdn = #{msisdn}, " + + "status = #{status}, " + + "remaining = #{remaining}, " + + "total = #{total}, " + + "used = #{used} " + + "WHERE deviceid = #{deviceid}") + int updateSimCardInfo(SimCard simCard); + + @Update("UPDATE simcards SET " + + "updatetime = #{updatetime}, " + + "remaining = #{remaining}, " + + "total = #{total}, " + + "used = #{used} " + + "WHERE deviceid = #{deviceid}") + int updateFlowInfo(SimCard simCard); + + @Update("UPDATE simcards SET " + + "updatetime = #{updatetime}, " + + "status = #{status} " + + "WHERE deviceid = #{deviceid}") + int updateStatus(SimCard simCard); +} + diff --git a/sec-beidou-fwd/src/main/resources/application.properties b/sec-beidou-fwd/src/main/resources/application.properties index 1a98155e..e1bf8846 100644 --- a/sec-beidou-fwd/src/main/resources/application.properties +++ b/sec-beidou-fwd/src/main/resources/application.properties @@ -8,6 +8,7 @@ spring.jpa.show-sql = true spring.jpa.hibernate.ddl-auto = none spring.jpa.database-platform = org.hibernate.dialect.MySQLDialect spring.datasource.url = jdbc:mysql://localhost:3306/beidou?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai +#spring.datasource.url=jdbc:mysql://139.9.51.237:3306/beidou?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai spring.datasource.username = admin spring.datasource.password = DBMgr_2022 spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver @@ -29,6 +30,9 @@ xfz.server.port = 31035 gxlj.server.host = 222.216.2.131 gxlj.server.port = 895 +gzb.server.host = 58.49.25.171 +gzb.server.port = 3011 + #gzy.server.host = 8.134.84.223 #gzy.server.port = 8088 gzy.server.host = 127.0.0.1 @@ -46,4 +50,13 @@ kingma.server.login_host = https://www.everiaction.com/IOT-ADAPTER-CUSTOM/auth/a kingma.server.data_host = https://www.everiaction.com/IOT-DATA-GATHER/receive/data/formula zny.server.host = http://119.3.203.174:8080/cs-detection-1.2/bsn/xyz/addDefo -zny.server.host2 = http://119.3.203.174:8080/cs-detection-2.0/bsn/xyz/addDefo \ No newline at end of file +zny.server.host2 = http://119.3.203.174:8080/cs-detection-2.0/bsn/xyz/addDefo + +gxjk.mqtt.server.brokerUrl = tcp://111.59.245.231:5220 +gxjk.mqtt.server.username = slopeUser01 +gxjk.mqtt.server.password = Slope#pd23@1201 +gxjk.mqtt.server.clientid = GXJK_client + +sass.server.host = 127.0.0.1 +sass.server.port = 9933 +sass.server.enabled = false \ No newline at end of file diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/FocusCalculator3.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/FocusCalculator3.java index eb94fd01..a0c3909d 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/FocusCalculator3.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/FocusCalculator3.java @@ -15,7 +15,7 @@ import static com.imdroid.sideslope.bd.GeoCoordConverterUtil.*; */ public class FocusCalculator3 extends FocusCalculator1{ //final static long scale = 100000000L;//地球1°:111km,放大到mm乘以100,000,000 - final static int bad_change_mm = 500;//固定解跳变连续10次超过500mm,认为是周跳 + final static int bad_change_mm = 300;//固定解跳变连续10次超过500mm,认为是周跳 final static int bad_duration = 10; int bad_count = 0; diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/RtcmGgaUtil.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/RtcmGgaUtil.java index 134d297f..0aea37d9 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/RtcmGgaUtil.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/bd/RtcmGgaUtil.java @@ -5,6 +5,9 @@ import com.imdroid.common.util.ByteUtil; import com.imdroid.common.util.StringUtil; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import static java.lang.Math.*; @@ -13,6 +16,110 @@ public class RtcmGgaUtil { //gga样本:*后面跟的是校验和,其中76代表校验和,对$和*之间的数据(不包括这两个字符)按字节进行异或运算(二进制)的结果 private static final String ggaExample = "$GNGGA,020850.50,2258.10508,N,11317.67958,E,4,12,0.74,3.9,M,-5.4,M,1.3,0000*76"; + + + /** + * 提取多条rtcm + * 因为存在d331...rtcm...d331...rtcm..这样的数据 + * 还存在d331...rtcm rtcm rtcm...这样的数据 + * @param hex + * @return + */ + public static List getRtcms(String hex){ + return splitStartWith(hex,"d300","d301","d302").stream() + .map(com.imdroid.sideslope.bd.RtcmGgaUtil::getRtcm) + .filter(s -> s != null && !s.isEmpty()) + .collect(Collectors.toList()); + } + + /** + * 提取一条rtcm + * @param hex + * @return + */ + public static String getRtcm(String hex){ + try { + int index = getIndex(hex,"d300","d301","d302"); + if (index != -1 && index < hex.length()-6) { + //d300数据长度 + int length = Integer.parseInt(hex.substring(index + 3, index + 6), 16); + if(index + (3+length +3)*2 <= hex.length()){ + return hex.substring(index, index + (3 + length + 3) * 2); + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return null; + } + + private static int getIndex(String... regex){ + for (int i = 1; i < regex.length; i++) { + if(regex[0].contains(regex[i])){ + return regex[0].indexOf(regex[i]); + } + } + return -1; + } + + /** + * 按前缀切割分段 + * @param regex 第一个是要处理的数据,其他是要检测的前缀 + * @return + */ + public static List splitStartWith(String... regex){ + ArrayList list = new ArrayList<>(); + try{ + List indexs = getIndexs(regex); + int start = 0; + for (int i = 0; i < indexs.size(); i++) { + if(indexs.get(i) != 0){ + list.add(regex[0].substring(start,indexs.get(i))); + start = indexs.get(i); + } + if(i == indexs.size() -1){ + list.add(regex[0].substring(indexs.get(i))); + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return list; + } + + /** + * 按多个规则搜索索引 + * @param regex + * @return + */ + public static List getIndexs(String... regex){ + List list =new ArrayList<>(); + for (int i = 1; i < regex.length; i++) { + list.addAll(findAllIndex(regex[0],0,regex[i])); + } + list.sort((o1, o2) -> o1-o2); + return list; + } + + /** + * 按规则搜索所有索引 + * @param string + * @param index + * @param findStr + * @return + */ + public static List findAllIndex(String string, int index, String findStr){ + List list =new ArrayList<>(); + int num = string.indexOf(findStr,index); + if(num != -1){ + list.add(num); + //递归进行查找 + List myList = findAllIndex(string,num+1,findStr); + list.addAll(myList); + } + return list; + } + /** * 获取rtcm数据类型 * @param bytes diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/calc/SingleLineGNSSCalcService.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/calc/SingleLineGNSSCalcService.java index d1260ed8..d683555b 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/calc/SingleLineGNSSCalcService.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/calc/SingleLineGNSSCalcService.java @@ -172,8 +172,6 @@ public class SingleLineGNSSCalcService implements GNSSDataCalcService { } } - - } @Override diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D331RtcmMessageExecutor.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D331RtcmMessageExecutor.java index 7e30d7d1..8d643ea0 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D331RtcmMessageExecutor.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D331RtcmMessageExecutor.java @@ -4,13 +4,17 @@ import com.imdroid.common.util.DataTypeUtil; import com.imdroid.common.util.ThreadManager; import com.imdroid.secapi.client.BeidouClient; import com.imdroid.secapi.dto.GnssDevice; +import com.imdroid.common.util.ByteUtil; import com.imdroid.sideslope.bd.Gga; import com.imdroid.sideslope.message.D331RtcmMessage; +import com.imdroid.sideslope.ntrip.UdpNtripServer; import com.imdroid.sideslope.sal.Device; import com.imdroid.sideslope.sal.DeviceService; import com.imdroid.sideslope.server.DeviceChannel; import com.imdroid.sideslope.server.OnlineChannels; import com.imdroid.sideslope.service.DataPersistService; +import com.imdroid.sideslope.bd.RtcmGgaUtil; +//import com.imdroid.common.util.RtcmGgaUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.slf4j.Logger; @@ -21,6 +25,8 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; + /** * @author Layton @@ -37,6 +43,9 @@ public class D331RtcmMessageExecutor implements Executor private BeidouClient beidouClient; @Autowired private DataPersistService dataPersistService; + @Autowired + UdpNtripServer ntripServer; + @Override public Void execute(D331RtcmMessage message) { String id = message.getId(); @@ -115,13 +124,16 @@ public class D331RtcmMessageExecutor implements Executor Gga gga = message.getGga(); if(gga != null) { deviceBs.updateSatelitesNum(gga.getSatellitesInUsed()); - //if(gga.isFixed()) { //基站的quality不会是4 - deviceBs.setLatitude(gga.getLatitude()); - deviceBs.setLongitude(gga.getLongitude()); - deviceBs.setAltitude(gga.getAltitude()); - //} + deviceBs.setLatitude(gga.getLatitude()); + deviceBs.setLongitude(gga.getLongitude()); + deviceBs.setAltitude(gga.getAltitude()); } + // 添加NTRIP处理 + byte[] srcdata = message.getSrcData(); + String rtcm = ByteUtil.bytesToHexString(srcdata); + sendToNtrip(id, rtcm); + ThreadManager.getFixedThreadPool().submit(() -> { // 原始码流输出到日志文件 -- INFO 级别 // 只有测站开了日志记录,或者消息来自基站,才将原码记录到日志文件 @@ -133,6 +145,25 @@ public class D331RtcmMessageExecutor implements Executor return null; } + private void sendToNtrip(String mountpoint, String hexData) { + try { + + // 将原始字节转换为16进制字符串用于RTCM提取 + //String hexData = ByteUtil.bytesToHexString(rawData); + //System.out.println(hexData); + + // 提取RTCM数据并发送到NtripServer,使用设备ID作为挂载点 + Optional.ofNullable(RtcmGgaUtil.getRtcms(hexData)) + .ifPresent(rtcm -> { + //System.out.println("挂载点: " + mountpoint); + //System.out.println("RTCM数据: " + rtcm); + ntripServer.send(mountpoint, rtcm); + }); + } catch (Exception e) { + logger.error("处理NTRIP数据失败, 挂载点: {}, 错误: {}", mountpoint, e.getMessage()); + } + } + @Override public Class getMessageType() { return D331RtcmMessage.class; diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D3F0SelfCheckMessageExecutor.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D3F0SelfCheckMessageExecutor.java index dd8ef688..83ec82e6 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D3F0SelfCheckMessageExecutor.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/executor/D3F0SelfCheckMessageExecutor.java @@ -41,6 +41,8 @@ public class D3F0SelfCheckMessageExecutor implements Executor { + // 检查是否需要更新设备的 ICCID + checkAndAskICCID(device); // 检查是否需要对设备的F9P进行冷启动操作 if(device.getDeviceType() == Device.DEVICE_ROVER){ if(device.getModel() == GnssDevice.MODEL_G505){ @@ -179,6 +183,28 @@ public class D3F0SelfCheckMessageExecutor implements Executor0 &&device.getAbnormalD341Num()>10){ if(isBaseStationFwd(device)) startBTResetTask(device); diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/HttpNtripServer.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/HttpNtripServer.java new file mode 100644 index 00000000..68357d71 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/HttpNtripServer.java @@ -0,0 +1,31 @@ +package com.imdroid.sideslope.ntrip; + +import com.imdroid.common.util.HttpUtils; +import com.imdroid.common.util.ThreadManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * @author likongyong + * @date 2023/10/19 11:49 + */ +public class HttpNtripServer { + + private static final Logger logger = LoggerFactory.getLogger(HttpNtripServer.class); + + /** + * 修改挂载点获取xingyu差分的gga + * @param mount + * @param gga + */ + public static void sendGGA(String mount, String gga){ + ThreadManager.getSingleThreadPool(HttpNtripServer.class.getName()).execute(()->{ + try{ + String url = "http://localhost:11001/ntrip/sendGGA/" + mount + "/" + gga; + String result = HttpUtils.getUrl(url); + logger.debug("{} send gga {} {}",mount, gga, result); + }catch (Exception e){ + logger.error(e.toString()); + } + }); + } +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/UdpNtripServer.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/UdpNtripServer.java new file mode 100644 index 00000000..efb64fdc --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/ntrip/UdpNtripServer.java @@ -0,0 +1,72 @@ +package com.imdroid.sideslope.ntrip; + +import com.imdroid.common.util.ByteUtil; +import com.imdroid.common.util.ThreadManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.util.List; + +/** + * 本地udp发送差分数据到ntrip-forward-server项目 + */ +@Service +public class UdpNtripServer { + private final Logger logger = LoggerFactory.getLogger(UdpNtripServer.class); + @Value("${ntrip_server.port:9903}") + private Integer port; + private DatagramSocket socket; + private DatagramPacket outPacket; + + /** + * 向某挂载点提供差分信息 + * @param mount 挂载点名称 + * @param hexRtcm 差分信息16进制字符串 + */ + public void send(String mount, String hexRtcm){ + ThreadManager.getSingleThreadPool(UdpNtripServer.class.getName()).execute(()->{ + try{ + //随机端口 + if(socket == null) socket = new DatagramSocket(); + if(outPacket == null) outPacket = new DatagramPacket(new byte[0],0, InetAddress.getByName("localhost"),port); + outPacket.setData(encode(ByteUtil.addBytes(mount.getBytes(), ByteUtil.hexStringTobyte(hexRtcm)))); + socket.send(outPacket); + }catch (Exception e){ + socket = null; + outPacket = null; + e.printStackTrace(); + } + }); + } + + /** + * 向某挂载点提供差分信息 + * @param mount 挂载点名称 + * @param hexRtcm 差分信息16进制字符串 + */ + public void send(String mount, List hexRtcm){ + logger.debug(mount + ":" + hexRtcm.size()); + for (String s : hexRtcm) { + send(mount,s); + } + } + + //加入校验位,用于校验数据是否完整 + private byte[] encode(byte[] bytes) { + byte[] bytes1 = new byte[bytes.length + 1]; + //校验位 + byte b = bytes[0]; + bytes1[0] = bytes[0]; + for (int i = 1; i < bytes.length; i++) { + b ^= bytes[i]; + bytes1[i] = bytes[i]; + } + bytes1[bytes1.length - 1] = b; + return bytes1; + } +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/DbDeviceServiceImpl.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/DbDeviceServiceImpl.java index b192aadf..4ea65df1 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/DbDeviceServiceImpl.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/DbDeviceServiceImpl.java @@ -44,6 +44,7 @@ public class DbDeviceServiceImpl implements DeviceService { device.setEcefy(gnssDevice.getEcefy()); device.setEcefz(gnssDevice.getEcefz()); device.setLoggingmode(gnssDevice.getLoggingmode()); + device.setIccid(gnssDevice.getIccid()); return device; } diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/Device.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/Device.java index 9e35dfd8..80e45b4f 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/Device.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/sal/Device.java @@ -5,6 +5,7 @@ import com.imdroid.sideslope.bd.Gga; import com.imdroid.sideslope.bd.UBXUtil; import lombok.Data; +import java.math.BigDecimal; import java.time.LocalDateTime; /** @@ -67,6 +68,11 @@ public class Device { Double iPosn; Double iPosd; + String iccid; + private BigDecimal remaining; + private BigDecimal total; + private BigDecimal used; + LocalDateTime lastRxTime; LocalDateTime lastD3f2Time; short noFixedAndFloatResult=0; diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryService.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryService.java new file mode 100644 index 00000000..f2d12878 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryService.java @@ -0,0 +1,20 @@ +package com.imdroid.sideslope.service; + +import com.imdroid.sideslope.sal.Device; +import com.imdroid.sideslope.simcard.*; + +public interface SimCardQueryService { + + // 查询卡基本信息 + BaseResponse queryCardInfo(Device device); + + // 查询卡状态 + BaseResponse queryCardStatus(Device device); + + // 查询流量信息 + BaseResponse queryGprs(Device device); + + // 查询卡详细信息 + BaseResponse queryCardDetail(Device device); + +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryServiceImpl.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryServiceImpl.java new file mode 100644 index 00000000..0ea95b47 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/SimCardQueryServiceImpl.java @@ -0,0 +1,178 @@ +package com.imdroid.sideslope.service; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.imdroid.secapi.dto.SimCard; +import com.imdroid.secapi.dto.SimCardsMapper; +import com.imdroid.sideslope.sal.Device; +import com.imdroid.sideslope.sal.DeviceService; +import com.imdroid.sideslope.simcard.*; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +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.Service; + +import java.math.BigDecimal; +import java.security.MessageDigest; +import java.util.*; + +@Service +public class SimCardQueryServiceImpl implements SimCardQueryService{ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + @Value("${sim.url}") + private String BASE_URL; + @Value("${sim.username}") + private String USERNAME; + @Value("${sim.key}") + private String KEY; + + @Autowired + SimCardsMapper simCardsMapper; + + @Override + public BaseResponse queryCardInfo(Device device) { + return executeQuery(device, "/api/Service/Cardinfo", CardInfoData.class); + } + + @Override + public BaseResponse queryCardStatus(Device device) { + return executeQuery(device, "/api/Service/QueryCardStatus", CardStatusData.class); + } + + @Override + public BaseResponse queryGprs(Device device) { + return executeQuery(device, "/api/Service/QueryGprs", GprsData.class); + } + + @Override + public BaseResponse queryCardDetail(Device device) { + return executeQuery(device, "/api/Service/QueryCard", CardDetailData.class); + } + + private BaseResponse executeQuery(Device device, String path, Class responseType) { + try { + Map params = new HashMap<>(); + params.put("username", USERNAME); + params.put("key", KEY); + params.put("card", device.getIccid()); + params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + + String signature = calculateSignature(params); + params.put("signature", signature); + + logger.info("Request params: {}", params); + String response = sendHttpPost(path, params); + logger.info("查询响应: 设备={}, ICCID={}, 响应={}", + device.getDeviceId(), device.getIccid(), response); + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + + // 特殊处理CardStatus的数组响应 + if (responseType == CardStatusData.class) { + JsonNode root = mapper.readTree(response); + if (root.get("Status").asInt() == 1 && root.get("Data").isArray()) { + CardStatusData statusData = new CardStatusData(); + statusData.setStatusCode(root.get("Data").get(0).asInt()); + statusData.setStatusDesc(root.get("Data").get(1).asText()); + BaseResponse baseResponse = new BaseResponse<>(); + baseResponse.setStatus(1); + baseResponse.setMessage("Success"); + baseResponse.setData(statusData); + return (BaseResponse) baseResponse; + } + } + + JavaType type = mapper.getTypeFactory().constructParametricType(BaseResponse.class, responseType); + return mapper.readValue(response, type); + + } catch (Exception e) { + logger.error("查询失败: 设备={}, 错误={}", device.getDeviceId(), e.getMessage()); + return null; + } + } + + private String calculateSignature(Map params) { + try { + List paramList = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + paramList.add(entry.getKey() + "=" + entry.getValue()); + } + Collections.sort(paramList); + + String paramString = String.join("&", paramList); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(paramString.getBytes()); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + String signature = sb.toString(); + logger.debug("Signature: {}", signature); + return signature; + + } catch (Exception e) { + logger.error("签名计算失败: {}", e.getMessage()); + return null; + } + } + + private String sendHttpPost(String path, Map params) throws Exception { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost httpPost = new HttpPost(BASE_URL + path); + + List pairs = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + + httpPost.setEntity(new UrlEncodedFormEntity(pairs)); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + return EntityUtils.toString(response.getEntity()); + } + } + } + + public boolean hasValidIccid(Device device) { + return device.getIccid() != null && !device.getIccid().trim().isEmpty(); + } + + public SimCard CreateOrUpdateSimCard(Device device) { + SimCard simCard = simCardsMapper.queryByDeviceId(device.getDeviceId()); + if (simCard == null) { + simCard = createNewSimCard(device); + } + return simCard; + } + + public SimCard createNewSimCard(Device device) { + SimCard newCard = new SimCard(); + newCard.setDeviceid(device.getDeviceId()); + newCard.setUpdatetime(new Date()); + newCard.setIccid(device.getIccid()); + newCard.setStatus(-1); + newCard.setMsisdn(""); + newCard.setRemaining(BigDecimal.ZERO); + newCard.setUsed(BigDecimal.ZERO); + newCard.setTotal(BigDecimal.ZERO); + + simCardsMapper.insert(newCard); + return newCard; + } + + public boolean isValidResponse(BaseResponse response) { + return response != null && response.getStatus() == 1; + } +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/WarningServiceImpl.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/WarningServiceImpl.java index e4f594e3..20e29f7f 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/WarningServiceImpl.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/service/WarningServiceImpl.java @@ -261,7 +261,7 @@ public class WarningServiceImpl implements WarningService { } } } -/* + // 检查SIM卡状态 public void checkSimCardStatus(Device device, SimCard simCard) { GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceId()); @@ -334,7 +334,7 @@ public class WarningServiceImpl implements WarningService { gnssStatusMapper.updateById(status); } } -*/ + public void generate_warning_logs(String device_id,int warning_type,String warning_type_name){ // 连续无固定解 和 掉电 警告 if (warning_type == WarningCfg.TYPE_NO_FIXED_RESULT || warning_type == WarningCfg.TYPE_LOW_VOLTAGE) { diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/BaseResponse.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/BaseResponse.java new file mode 100644 index 00000000..6f6e42a9 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/BaseResponse.java @@ -0,0 +1,10 @@ +package com.imdroid.sideslope.simcard; + +import lombok.Data; + +@Data +public class BaseResponse { + private Integer status; + private String message; + private T data; +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardDetailData.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardDetailData.java new file mode 100644 index 00000000..5f1a5b14 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardDetailData.java @@ -0,0 +1,38 @@ +package com.imdroid.sideslope.simcard; + +import lombok.Data; + +@Data +public class CardDetailData { + private Integer operatortype; + private String activetime; + private String starttime; + private String stoptime; + private String silentdate; + private Integer status; + private String msisdn; + private String iccid; + private String imsi; + private Integer packageid; + private String packagename; + private String net; + + private GprsInfo gprs; + private ApnInfo apn; + + @Data + public static class GprsInfo { + private Double total; + private Double used; + private Double left; + } + + @Data + public static class ApnInfo { + private String apnid; + private String status; + private String ip; + private String rat; + private String onoffstatus; + } +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardInfoData.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardInfoData.java new file mode 100644 index 00000000..80db3d36 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardInfoData.java @@ -0,0 +1,10 @@ +package com.imdroid.sideslope.simcard; + +import lombok.Data; + +@Data +public class CardInfoData { + private String imsi; + private String msisdn; + private String iccid; +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardStatusData.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardStatusData.java new file mode 100644 index 00000000..43f8ec9f --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/CardStatusData.java @@ -0,0 +1,10 @@ +package com.imdroid.sideslope.simcard; + +import lombok.Data; + + +@Data +public class CardStatusData { + private Integer statusCode; + private String statusDesc; +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/GprsData.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/GprsData.java new file mode 100644 index 00000000..caeb4041 --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/simcard/GprsData.java @@ -0,0 +1,41 @@ +package com.imdroid.sideslope.simcard; + +import lombok.Data; + +import java.util.List; + +@Data +public class GprsData { + private Integer operatortype; + private List gps; + + @Data + public static class GprsUsage { + private Double left; + private Double total; + private Double used; + } + + // 返回数据格式 + // { + // "Status": 1, + // "Message": "Success", + // "Data": { + // "operatortype": 1, + // "GPS": [ + // { + // "Left": "2023", + // "Total": "2.0", + // "Used": "25" + // } + // ] + // } + // } + + public GprsUsage getFirstGprsUsage() { + if (gps != null && !gps.isEmpty()) { + return gps.get(0); + } + return null; + } +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/task/SimStatusChecker.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/task/SimStatusChecker.java new file mode 100644 index 00000000..0400b19a --- /dev/null +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/task/SimStatusChecker.java @@ -0,0 +1,207 @@ +package com.imdroid.sideslope.task; + +import com.alibaba.excel.util.StringUtils; +import com.imdroid.common.util.ThreadManager; +import com.imdroid.secapi.dto.*; +import com.imdroid.sideslope.sal.Device; +import com.imdroid.sideslope.sal.DeviceService; +import com.imdroid.sideslope.service.SimCardQueryServiceImpl; +import com.imdroid.sideslope.service.WarningServiceImpl; +import com.imdroid.sideslope.simcard.BaseResponse; +import com.imdroid.sideslope.simcard.CardInfoData; +import com.imdroid.sideslope.simcard.CardStatusData; +import com.imdroid.sideslope.simcard.GprsData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Component +@Configuration +@EnableScheduling +public class SimStatusChecker { + // SIM 卡流量和状态查询定时任务 + // 1. 每小时任务 + // a. 通过CardInfo 检查 iccid 和 sim卡号 + // b. 通过QueryCardStatus 检查SIM卡当前的状态 + // 2. 每两小时任务 + // a. QueryGprs 检查 SIM 流量 + final Logger logger = LoggerFactory.getLogger(SimStatusChecker.class); + @Autowired + private GnssStatusMapper gnssStatusMapper; + + @Autowired + private WarningServiceImpl warningService; + + @Autowired + private SimCardsMapper simCardsMapper; + @Resource(name = "local") + private DeviceService deviceService; + + @Autowired + private SimCardQueryServiceImpl simCardQueryServiceImpl; + + // 每小时执行一次状态检查调度 + @Scheduled(cron = "0 0 * * * ?") + //@Scheduled(cron = "0 */10 * * * ?") + private void scheduleSimCardStatusCheck() { + List onlineDevices = gnssStatusMapper.queryOnline(); + logger.info("当前在线设备数量: {}", onlineDevices.size()); + + for (GnssStatusJoin onlineDevice : onlineDevices) { + int delay = Math.abs(onlineDevice.getDeviceid().hashCode() % 3600 ); + logger.info("- 设备: {}, SIM状态查询,延迟执行: {}秒", onlineDevice.getDeviceid(), delay); + + ThreadManager.getScheduledThreadPool().schedule( + () -> checkDeviceSimCardStatus(onlineDevice.getDeviceid()), + delay, + TimeUnit.SECONDS + ); + } + } + + // 每两小时执行一次流量检查调度 + @Scheduled(cron = "0 0 0/2 * * ?") + private void scheduleSimCardTrafficCheck() { + List onlineDevices = gnssStatusMapper.queryOnline(); + logger.info("当前在线设备数量: {}", onlineDevices.size()); + + for (GnssStatusJoin onlineDevice : onlineDevices) { + int delay = Math.abs(onlineDevice.getDeviceid().hashCode() % 7200); + logger.debug("- 设备: {}, SIM流量查询,延迟执行: {}秒", onlineDevice.getDeviceid(), delay); + + ThreadManager.getScheduledThreadPool().schedule( + () -> checkDeviceSimCardTraffic(onlineDevice.getDeviceid()), + delay, + TimeUnit.SECONDS + ); + } + } + + private void checkDeviceSimCardStatus(String deviceId) { + try { + Device device = deviceService.findByDeviceId(deviceId); + // 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询 + if (!simCardQueryServiceImpl.hasValidIccid(device)) { + return; + } + // SimCards 表中没有数据就初始化,这其实说明它在自检中刚获得属于自己的 ICCID 号 + SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device); + updateSimCardInfo(device, simCard); + + } catch (Exception e) { + logger.error("设备{}状态查询失败: ", deviceId, e); + } + } + + private void checkDeviceSimCardTraffic(String deviceId) { + try { + Device device = deviceService.findByDeviceId(deviceId); + // 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询 + if (!simCardQueryServiceImpl.hasValidIccid(device)) { + return; + } + // SimCards 表中没有数据就初始化,这说明它在自检中获得属于自己的 ICCID 号 + SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device); + // 如果该卡状态不是已激活,而是其他状态,那么不运行它参与 SIM 卡流量检测 + if(simCard.getStatus() != SimCard.STATUS_ACTIVATED){ + return; + } + updateSimCardTrafficFromAPI(device, simCard); + + } catch (Exception e) { + logger.error("设备{}查询失败: ", deviceId, e); + } + } + + private void updateSimCardInfo(Device device, SimCard simCard) throws Exception { + // 自检中只是获取保存了设备的 ICCID ,所有如果判断如果没有 MSISDN,先更新基本信息 + if (StringUtils.isBlank(simCard.getMsisdn())) { + updateSimCardBasicInfoFromAPI(device, simCard); + } + // 更新状态 + updateSimCardStatusFromAPI(device, simCard); + } + private void updateSimCardBasicInfoFromAPI(Device device, SimCard simCard) { + try { + BaseResponse response = simCardQueryServiceImpl.queryCardInfo(device); + if (!simCardQueryServiceImpl.isValidResponse(response)) { + return; + } + + CardInfoData info = response.getData(); + simCard.setUpdatetime(new Date()); + simCard.setMsisdn(info.getMsisdn()); + simCardsMapper.updateSimCardInfo(simCard); + + logger.info("更新SIM卡基本信息 - imsi: {}, msisdn: {}, iccid: {}", + info.getImsi(), info.getMsisdn(), info.getIccid()); + } catch (Exception e) { + logger.error("更新设备{}的SIM卡基本信息失败: ", device.getDeviceId(), e); + throw e; + } + } + private void updateSimCardStatusFromAPI(Device device, SimCard simCard) { + try { + BaseResponse response = simCardQueryServiceImpl.queryCardStatus(device); + if (!simCardQueryServiceImpl.isValidResponse(response)) { + return; + } + CardStatusData status = response.getData(); + simCard.setUpdatetime(new Date()); + simCard.setStatus(status.getStatusCode()); + simCardsMapper.updateSimCardInfo(simCard); + + warningService.checkSimCardStatus(device, simCard); + + logger.info("更新SIM卡状态 - Code: {}, 描述: {}", + status.getStatusCode(), status.getStatusDesc()); + } catch (Exception e) { + logger.error("更新设备{}的SIM卡状态失败: ", device.getDeviceId(), e); + throw e; + } + } + + private void updateSimCardTrafficFromAPI(Device device, SimCard simCard) { + try { + BaseResponse response = simCardQueryServiceImpl.queryGprs(device); + if (!simCardQueryServiceImpl.isValidResponse(response)) { + return; + } + + GprsData gprsData = response.getData(); + GprsData.GprsUsage usage = gprsData.getFirstGprsUsage(); + if (usage == null) { + logger.warn("无可用流量数据查询, deviceId: {}", device.getDeviceId()); + return; + } + + simCard.setUpdatetime(new Date()); + simCard.setRemaining(BigDecimal.valueOf(usage.getLeft())); + simCard.setUsed(BigDecimal.valueOf(usage.getUsed())); + simCard.setTotal(BigDecimal.valueOf(usage.getTotal())); + simCardsMapper.updateSimCardInfo(simCard); + + warningService.checkSimCardTraffic(device, simCard); + + logger.info("更新流量信息成功 - deviceId: {}, 剩余: {}MB, 总量: {}MB, 已用: {}MB", + device.getIccid(), + simCard.getRemaining(), + simCard.getTotal(), + simCard.getUsed()); + + } catch (Exception e) { + logger.error("设备{}更新SIM卡流量失败: ", device.getDeviceId(), e); + throw e; + } + } + +} diff --git a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/web/ApiController.java b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/web/ApiController.java index a844f494..1b2075ce 100644 --- a/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/web/ApiController.java +++ b/sec-beidou-rtcm/src/main/java/com/imdroid/sideslope/web/ApiController.java @@ -136,7 +136,6 @@ public class ApiController { Map status = new HashMap<>(); HttpResp resp = new HttpResp(); // get channel0/channel1 type - String[] paras = cmd.split("=| "); Device device = localDeviceService.findByDeviceId(deviceId); if(device!=null){ resp.setResponseMessage("set OK."); diff --git a/sec-beidou-rtcm/src/main/resources/application.properties b/sec-beidou-rtcm/src/main/resources/application.properties index 0210c779..9d0f6821 100644 --- a/sec-beidou-rtcm/src/main/resources/application.properties +++ b/sec-beidou-rtcm/src/main/resources/application.properties @@ -35,4 +35,6 @@ xfz.server.host = 171.106.48.63 xfz.server.port = 52000 xfz.server.data.send = false -warning.log.directory=./log \ No newline at end of file +warning.log.directory=./log + +ntrip_server.port = 11100 \ No newline at end of file diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java index 4b31e751..d3913ff2 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java @@ -59,6 +59,9 @@ public class APIController extends BasicController{ //转成字符串 String dtuAck = configAck.substring(9*2); rxInfo += configAck+"("+HexUtil.HexString2String(dtuAck)+")"; + + // 检查是否需要更新 ICCID + updateICCID(device,dtuAck); } else if(msgType == 0xd313&&configAck.length()>=100){ //转成字符串 @@ -192,4 +195,18 @@ public class APIController extends BasicController{ msgMapper.insert(gnssMsg); } + void updateICCID(GnssDevice device, String dtuAck) { + // 只检查 "ICCID:" 的十六进制部分 + if(!dtuAck.contains("49434349443a")){ + return; + } + String content = HexUtil.HexString2String(dtuAck); + if(content.contains("+ICCID:")){ + //System.out.println(content); + String iccid = content.substring(content.indexOf("+ICCID:") + 8).trim(); + iccid = iccid.split("\r\n")[0].trim(); + device.setIccid(iccid); + deviceMapper.updateById(device); + } + } } diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssCalcDataController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssCalcDataController.java index 24ebd8c2..62cb72fb 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssCalcDataController.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssCalcDataController.java @@ -5,13 +5,16 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.github.yulichang.query.MPJQueryWrapper; +import com.imdroid.beidou.common.HttpResult; import com.imdroid.beidou.service.CommonExcelService; import com.imdroid.secapi.dto.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; @@ -34,6 +37,8 @@ public class GnssCalcDataController extends BasicController implements CommonExc TenantMapper tenantMapper; @Autowired GnssDeviceMapper deviceMapper; + @Autowired + OpLogManager opLogManager; boolean isJoinQuery = false; final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @@ -54,6 +59,29 @@ public class GnssCalcDataController extends BasicController implements CommonExc return "/page/gnss_data_calc"; } + @PostMapping("/page/gnssUpdateEnabled") + @ResponseBody + public String gnssUpdateEnabled(HttpSession session, @RequestParam Long id) throws Exception { + int num = 0; + GnssCalcData calcData = dataMapper.selectById(id); + if(calcData != null){ + calcData.setEnabled(!calcData.getEnabled()); // 使用Boolean类型 + calcData.setUpdatetime(LocalDateTime.now()); + num = dataMapper.updateById(calcData); + } + + opLogManager.addLog(getLoginUser(session),getTenantId(session), + OpLogManager.OP_TYPE_DEL, + OpLogManager.OP_OBJ_DEVICE, + calcData.getDeviceid() + " "+calcData.getCreatetime()+" gnss data changed"); + if (num == 0) { + return HttpResult.failed(); + } else{ + return HttpResult.ok(); + } + } + + /**** 推送数据 *****/ @RequestMapping("/gnss/data/list_calc") @ResponseBody diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/SimCardController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/SimCardController.java new file mode 100644 index 00000000..69c3eaaf --- /dev/null +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/SimCardController.java @@ -0,0 +1,206 @@ +package com.imdroid.beidou.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.imdroid.secapi.dto.*; +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpSession; +import java.security.MessageDigest; +import java.util.*; + +@Controller +public class SimCardController extends BasicController { + @Value("${sim.url}") + private String BASE_URL; + @Value("${sim.username}") + private String USERNAME; + @Value("${sim.key}") + private String KEY; + + @Autowired + private SimCardsMapper simCardsMapper; + + @RequestMapping("/page/sim_status") + public String simStatus(Model m, HttpSession session) { + initModel(m, session); + + return "/page/sim_status"; + } + + @RequestMapping("/sim/list") + @ResponseBody + public JSONObject list(HttpSession session, + int page, + int limit, + String searchType, + String searchContent, + Integer status) { + Page pageable = new Page<>(page, limit); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + + + if (!StringUtils.isEmpty(searchContent)) { + switch(searchType) { + case "deviceId": + queryWrapper.like("deviceid", searchContent.trim()); + break; + case "iccid": + queryWrapper.like("iccid", searchContent.trim()); + break; + case "simNumber": + queryWrapper.like("msisdn", searchContent.trim()); + break; + } + } + if (status != null) { + queryWrapper.eq("status", status); + } + + queryWrapper.orderByDesc("updatetime"); + IPage cs = simCardsMapper.selectPage(pageable, queryWrapper); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 0); + jsonObject.put("msg", ""); + jsonObject.put("count", cs.getTotal()); + jsonObject.put("data", cs.getRecords()); + System.out.println(jsonObject.toString()); + return jsonObject; + } + + @RequestMapping("/page/sim_traffic_query") + public String simTrafficQuery(Model m, HttpSession session) { + initModel(m, session); + + return "/page/sim_traffic_query"; + } + + // 代理转发 + @RequestMapping("/api/proxy/sim/query") + @ResponseBody + public JSONObject proxySimQuery( + @RequestParam String type, + @RequestParam String content + ){ + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + try { + Map params = new HashMap<>(); + params.put("username", USERNAME); + params.put("key", KEY); + + switch(type) { + case "iccid": + params.put("card", content); + break; + case "deviceId": + if (content.trim().isEmpty()) { + throw new IllegalArgumentException("设备ID不能为空"); + } + SimCard simCard = simCardsMapper.queryByDeviceId(content.trim()); + if (simCard == null) { + throw new IllegalArgumentException("未找到该设备ID对应的SIM卡信息"); + } + params.put("card", simCard.getIccid()); + break; + case "simNumber": + params.put("card", content); + break; + default: + throw new IllegalArgumentException("无效的查询类型: " + type); + } + + params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + + String signature = calculateSignature(params); + if (signature == null) { + throw new Exception("签名计算失败"); + } + + StringBuilder urlBuilder = new StringBuilder(BASE_URL); + urlBuilder.append("/api/Service/QueryCard?"); + urlBuilder.append("username=").append(USERNAME); + urlBuilder.append("&key=").append(KEY); + urlBuilder.append("&card=").append(params.get("card")); + urlBuilder.append("×tamp=").append(params.get("timestamp")); + urlBuilder.append("&signature=").append(signature); + + httpClient = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(urlBuilder.toString()); + + // System.out.println("Sending request: " + urlBuilder.toString()); + response = httpClient.execute(httpGet); + HttpEntity entity = response.getEntity(); + + + String result = EntityUtils.toString(entity); + // System.out.println("Received response: " + result); + + return JSONObject.parseObject(result); + + } catch (Exception e) { + // System.out.println("Query failed: " + e.getMessage()); + JSONObject errorResponse = new JSONObject(); + errorResponse.put("Status", 0); + errorResponse.put("Message", "查询失败: " + e.getMessage()); + return errorResponse; + + } finally { + try { + if (response != null) { + response.close(); + } + if (httpClient != null) { + httpClient.close(); + } + } catch (Exception e) { + + } + } + } + + private String calculateSignature(Map params) { + try { + List paramList = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + paramList.add(entry.getKey() + "=" + entry.getValue()); + } + Collections.sort(paramList); + + String paramString = String.join("&", paramList); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(paramString.getBytes()); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + String signature = sb.toString(); + // System.out.println("Signature: " + signature); + return signature; + + } catch (Exception e) { + //System.out.println("Signature: " + e.getMessage()); + return null; + } + } + +} + + diff --git a/sec-beidou/src/main/resources/application.properties b/sec-beidou/src/main/resources/application.properties index b458234c..e4c80366 100644 --- a/sec-beidou/src/main/resources/application.properties +++ b/sec-beidou/src/main/resources/application.properties @@ -47,4 +47,8 @@ aliyun.oss.accessSecret = GHVzHKLLor8i5ZR1qyeoi4KMf3mjHb aliyun.oss.bucket = imdroid-device-management aliyun.oss.publicReadUrl = https://imdroid-device-management.oss-cn-shanghai.aliyuncs.com -warning.log.directory=./log \ No newline at end of file +warning.log.directory=./log + +sim.url = http://120.78.169.220:8089 +sim.username = gzyzdz +sim.key = 632629d1269a202c9d49a574623e4e4c \ No newline at end of file diff --git a/sec-beidou/src/main/resources/db/schema.sql b/sec-beidou/src/main/resources/db/schema.sql index acc502e9..0be53fe4 100644 --- a/sec-beidou/src/main/resources/db/schema.sql +++ b/sec-beidou/src/main/resources/db/schema.sql @@ -68,6 +68,7 @@ CREATE TABLE IF NOT EXISTS `gnssdevices` ( `imei` varchar(16) DEFAULT NULL, `model` smallint DEFAULT 0, `loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式', + `iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号,唯一', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -328,6 +329,9 @@ CREATE TABLE IF NOT EXISTS `ApiKey` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*** + GNSS 单次解记录表 + */ CREATE TABLE IF NOT EXISTS `gnssdevicesinglerecords` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `deviceid` varchar(64) NOT NULL COMMENT '设备ID', @@ -342,6 +346,26 @@ CREATE TABLE IF NOT EXISTS `gnssdevicesinglerecords` ( KEY `idx_createtime` (`createtime`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='GNSS单次解算记录表'; +/*** + SIM卡记录表 + */ +CREATE TABLE IF NOT EXISTS `simcards` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一', + `updatetime` DATETIME DEFAULT NULL COMMENT '最新一次查询接口记录的时间', + `iccid` VARCHAR(36) NOT NULL COMMENT 'ICCID号,唯一', + `msisd` VARCHAR(20) NOT NULL COMMENT '物联卡号码,SIM号,唯一', + `deviceid` VARCHAR(20) DEFAULT NULL COMMENT '设备ID号', + `status` TINYINT DEFAULT -1 COMMENT 'SIM卡状态(-1:未知, 1:待激活, 2:已激活, 3:停机, 4:注销, 5:库存, 6:可测试, 7:失效, 99:号码不存在)', + `remaining` DECIMAL(10,2) DEFAULT 0.00 COMMENT '剩余流量,单位为MB,保留两位小数', + `total` DECIMAL(10,2) DEFAULT 0.00 COMMENT '总流量,单位为MB,保留两位小数', + `used` DECIMAL(10,2) DEFAULT 0.00 COMMENT '已用流量,单位为MB,保留两位小数', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_iccid` (`iccid`), + UNIQUE KEY `uk_msisd` (`msisd`), + INDEX `idx_deviceid` (`deviceid`), + INDEX `idx_status` (`status`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='SIM卡信息表'; + CREATE TABLE IF NOT EXISTS `ehm` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenantid` int DEFAULT 0, diff --git a/sec-beidou/src/main/resources/static/api/init_super_admin.json b/sec-beidou/src/main/resources/static/api/init_super_admin.json index 89b13842..c50fc81f 100644 --- a/sec-beidou/src/main/resources/static/api/init_super_admin.json +++ b/sec-beidou/src/main/resources/static/api/init_super_admin.json @@ -117,6 +117,26 @@ } ] }, + { + "title": "SIM卡管理", + "href": "", + "icon": "fa fa-signal", + "target": "_self", + "child": [ + { + "title": "总览", + "href": "page/sim_status", + "icon": "fa fa-minus", + "target": "_self" + }, + { + "title": "卡信息查询", + "href": "page/sim_traffic_query", + "icon": "fa fa-minus", + "target": "_self" + } + ] + }, { "title": "数据推送", "href": "", diff --git a/sec-beidou/src/main/resources/static/api/init_sys_admin.json b/sec-beidou/src/main/resources/static/api/init_sys_admin.json index 31fa2579..caba7f78 100644 --- a/sec-beidou/src/main/resources/static/api/init_sys_admin.json +++ b/sec-beidou/src/main/resources/static/api/init_sys_admin.json @@ -93,6 +93,26 @@ } ] }, + { + "title": "SIM卡管理", + "href": "", + "icon": "fa fa-signal", + "target": "_self", + "child": [ + { + "title": "总览", + "href": "page/sim_status", + "icon": "fa fa-minus", + "target": "_self" + }, + { + "title": "卡信息查询", + "href": "page/sim_traffic_query", + "icon": "fa fa-minus", + "target": "_self" + } + ] + }, { "title": "数据推送", "href": "", diff --git a/sec-beidou/src/main/resources/templates/page/gnss_data_calc.html b/sec-beidou/src/main/resources/templates/page/gnss_data_calc.html index b9d29c95..6f1334b8 100644 --- a/sec-beidou/src/main/resources/templates/page/gnss_data_calc.html +++ b/sec-beidou/src/main/resources/templates/page/gnss_data_calc.html @@ -97,10 +97,12 @@ - - + + @@ -129,12 +131,14 @@ {field: 'r9250e', title: '拟合东', templet: "
{{d.r9250e==null?'':d.r9250e.toFixed(2)}}
"}, {field: 'r9250n', title: '拟合北', templet: "
{{d.r9250n==null?'':d.r9250n.toFixed(2)}}
"}, {field: 'r9250d', title: '拟合天', templet: "
{{d.r9250d==null?'':d.r9250d.toFixed(2)}}
"}, - {field: 'enabled', title: '有效',templet: '#enabledTrans'} + {field: 'enabled', title: '有效',templet: '#enabledTrans'}, + {title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 100} ]; if([[${role}]] != "SUPER_ADMIN") { data_cols[9].hide = true; data_cols[10].hide = true; data_cols[11].hide = true; + data_cols[13].hide = true; } /** * 初始化表单,要加上,不然刷新部分组件可能会不加载 @@ -174,6 +178,32 @@ } }); + // 监听工具条事件 + table.on('tool(currentTableFilter)', function(obj) { + var data = obj.data; + if (obj.event === 'edit') { + layer.confirm('确定要修改状态吗?', { + btn: ['确定', '取消'] + }, function(index) { + $.ajax({ + url: '/page/gnssUpdateEnabled', + type: 'POST', + data: { + id: data.id, + }, + success: function(res) { + table.reload('currentTableId'); + layer.close(index); + }, + error: function(xhr) { + layer.msg('操作失败'); + layer.close(index); + } + }); + }); + } + }); + // 监听搜索操作 form.on('submit(data-search-btn)', function (data) { var result = JSON.stringify(data.field); diff --git a/sec-beidou/src/main/resources/templates/page/sim_status.html b/sec-beidou/src/main/resources/templates/page/sim_status.html new file mode 100644 index 00000000..e220984b --- /dev/null +++ b/sec-beidou/src/main/resources/templates/page/sim_status.html @@ -0,0 +1,278 @@ + + + + + SIM卡信息查询 + + + + + + + +
+
+
+ 搜索信息 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
    +
  • 数据表格
  • +
  • 流量图表
  • +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/sec-beidou/src/main/resources/templates/page/sim_traffic_query.html b/sec-beidou/src/main/resources/templates/page/sim_traffic_query.html new file mode 100644 index 00000000..1d96e4f0 --- /dev/null +++ b/sec-beidou/src/main/resources/templates/page/sim_traffic_query.html @@ -0,0 +1,290 @@ + + + + + SIM卡信息查询 + + + + + + + +
+
+
+ SIM卡相关信息聚合查询 +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html b/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html index 311601e3..68214b0b 100644 --- a/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html +++ b/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html @@ -207,6 +207,12 @@
+
+ +
+ +
+
@@ -320,6 +326,7 @@ $('#model').val(data.model); $('#sector').val(data.sector); $('#remark').val(data.remark); + $('#iccid').val(data.iccid); setEcefEditor(); form.render(); }