From 6552adb4b1a7249dce03749078207f22771fa083 Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Tue, 3 Jun 2025 16:24:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E6=B5=81=E9=87=8F?= =?UTF-8?q?=E5=8D=A1=20API=20=E4=B8=AD=E7=9A=84=E5=B8=B8=E9=87=8F=20-=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20ehm=20=E4=B8=AD=E7=9A=84=20application.pro?= =?UTF-8?q?perties=20=E4=B8=AD=E6=B5=81=E9=87=8F=E5=8D=A1=E5=8D=A1?= =?UTF-8?q?=E5=95=86=20API=E7=9B=B8=E5=85=B3=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sec-beidou-ehm/src/main/resources/application.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sec-beidou-ehm/src/main/resources/application.properties b/sec-beidou-ehm/src/main/resources/application.properties index ace2f1f3..0bfcb2f1 100644 --- a/sec-beidou-ehm/src/main/resources/application.properties +++ b/sec-beidou-ehm/src/main/resources/application.properties @@ -18,4 +18,8 @@ app.format.date = yyyy-MM-dd app.format.time = HH:mm:ss app.format.datetime = yyyy-MM-dd HH:mm:ss +sim.url = http://120.78.169.220:8089 +sim.username = gzyzdz +sim.key = 632629d1269a202c9d49a574623e4e4c + mybatis-plus.configuration.map-underscore-to-camel-case=false From e3870f66803ed4ffb118cf812c24c5ed03aa50aa Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Tue, 3 Jun 2025 16:32:51 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E5=8D=A1=E7=9B=B8=E5=85=B3=E5=AE=9E=E4=BD=93=E7=B1=BB?= =?UTF-8?q?=20-=20=E6=96=B0=E5=A2=9E=20TrafficCard=20-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20TrafficDeviceMapping=20-=20=E6=96=B0=E5=A2=9E=20Tra?= =?UTF-8?q?fficRecord?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/imdroid/secapi/dto/TrafficCard.java | 59 +++++++++++++++++++ .../imdroid/secapi/dto/TrafficCardMapper.java | 37 ++++++++++++ .../secapi/dto/TrafficDeviceMapping.java | 27 +++++++++ .../dto/TrafficDeviceMappingMapper.java | 24 ++++++++ .../com/imdroid/secapi/dto/TrafficRecord.java | 26 ++++++++ .../secapi/dto/TrafficRecordMapper.java | 10 ++++ sec-beidou/src/main/resources/db/schema.sql | 49 +++++++++++++++ 7 files changed, 232 insertions(+) create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCard.java create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCardMapper.java create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMapping.java create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMappingMapper.java create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecord.java create mode 100644 sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecordMapper.java diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCard.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCard.java new file mode 100644 index 00000000..84972ec2 --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCard.java @@ -0,0 +1,59 @@ +package com.imdroid.secapi.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("traffic_cards") +public class TrafficCard { + 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; // 号码不存在 + + // 查询状态常量定义 + public static final int QUERY_STATUS_NORMAL = 0; // 正常状态 + public static final int QUERY_STATUS_NOT_CURRENT_VENDOR = 1; // 非当前卡商 + public static final int QUERY_STATUS_OTHER_ERROR = 2; // 其他错误 + + @TableId(type = IdType.AUTO) + @ExcelProperty("ID") + private Integer id; + + @ExcelProperty("ICCID") + private String iccid; + + @ExcelProperty("物联卡号码") + private String msisdn; + + @ExcelProperty("状态") + private Integer status; + + @ExcelProperty("剩余流量(MB)") + private Integer remaining; + + @ExcelProperty("总流量(MB)") + private Integer total; + + @ExcelProperty("已用流量(MB)") + private Integer used; + + @TableField("update_time") + @ExcelProperty("更新时间") + private Date updateTime; + + @TableField("query_status") + @ExcelProperty("查询状态") + private Integer queryStatus; +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCardMapper.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCardMapper.java new file mode 100644 index 00000000..38babdaf --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficCardMapper.java @@ -0,0 +1,37 @@ +package com.imdroid.secapi.dto; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface TrafficCardMapper extends BaseMapper { + + @Select("select * from traffic_cards where iccid = #{iccid} limit 1") + TrafficCard findByIccid(String iccid); + + @Update("UPDATE traffic_cards SET " + + "update_time = #{updateTime}, " + + "msisdn = #{msisdn}, " + + "status = #{status}, " + + "remaining = #{remaining}, " + + "total = #{total}, " + + "used = #{used} " + + "WHERE iccid = #{iccid}") + int updateCardInfo(TrafficCard trafficCard); + + @Update("UPDATE traffic_cards SET query_status = #{queryStatus} WHERE iccid = #{iccid}") + int updateQueryStatus(String iccid,int queryStatus); + + @Update("UPDATE traffic_cards SET " + + "update_time = #{updateTime}, " + + "remaining = #{remaining}, " + + "total = #{total}, " + + "used = #{used} " + + "WHERE iccid = #{iccid} AND iccid IS NOT NULL AND iccid != ''") + int updateCardTrafficInfo(TrafficCard trafficCard); +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMapping.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMapping.java new file mode 100644 index 00000000..c3dc243b --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMapping.java @@ -0,0 +1,27 @@ +package com.imdroid.secapi.dto; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("traffic_device_mappings") +public class TrafficDeviceMapping { + + @TableId(type = IdType.AUTO) + private Integer id; + + private String deviceid; + + private String iccid; + + @TableField("start_time") + private Date startTime; + + @TableField("end_time") + private Date endTime; // 结束使用时间,NULL表示当前正在使用 +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMappingMapper.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMappingMapper.java new file mode 100644 index 00000000..097c94e3 --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficDeviceMappingMapper.java @@ -0,0 +1,24 @@ +package com.imdroid.secapi.dto; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface TrafficDeviceMappingMapper extends BaseMapper { + @Select("SELECT * FROM traffic_device_mappings WHERE deviceid = #{deviceId} AND end_time IS NULL LIMIT 1") + TrafficDeviceMapping findActiveByDeviceId(String deviceId); + + @Select("SELECT * FROM traffic_device_mappings WHERE iccid = #{iccid} AND end_time IS NULL LIMIT 1") + TrafficDeviceMapping findActiveByIccid(String iccid); + + @Select("SELECT * FROM traffic_device_mappings WHERE deviceid = #{deviceId} ORDER BY start_time DESC") + List findHistoryByDeviceId(String deviceId); + + @Update("UPDATE traffic_device_mappings SET end_time = NOW() WHERE id = #{id}") + int endMapping(Integer id); +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecord.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecord.java new file mode 100644 index 00000000..42da916b --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecord.java @@ -0,0 +1,26 @@ +package com.imdroid.secapi.dto; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("traffic_records") +public class TrafficRecord { + + @TableId(type = IdType.AUTO) + private Integer id; + + private String iccid; + + @TableField("record_time") + private Date recordTime; + + private Integer remaining; // 剩余流量(MB×1000) + private Integer used; // 已用流量(MB×1000) + private Integer total; // 总流量(MB×1000) +} \ No newline at end of file diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecordMapper.java b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecordMapper.java new file mode 100644 index 00000000..8189105b --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/TrafficRecordMapper.java @@ -0,0 +1,10 @@ +package com.imdroid.secapi.dto; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + + +@Mapper +public interface TrafficRecordMapper extends BaseMapper { + +} \ 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 50381969..d77648ef 100644 --- a/sec-beidou/src/main/resources/db/schema.sql +++ b/sec-beidou/src/main/resources/db/schema.sql @@ -396,3 +396,52 @@ CREATE TABLE IF NOT EXISTS `ehmconfig` ( `calcstathours` int DEFAULT NULL COMMENT '数据分析周期', PRIMARY KEY (`updatetime`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +/*** + 流量卡信息表 + */ +CREATE TABLE IF NOT EXISTS `traffic_cards` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一', + `iccid` VARCHAR(20) NOT NULL COMMENT 'ICCID号,唯一标识SIM卡', + `msisdn` VARCHAR(20) NOT NULL COMMENT '物联卡号码', + `status` INT DEFAULT -1 COMMENT 'SIM卡状态(-1:未知, 1:待激活, 2:已激活, 3:停机, 4:注销, 5:库存, 6:可测试, 7:失效, 99:号码不存在)', + `remaining` INT DEFAULT 0 COMMENT '剩余流量,单位MB,为避免小数,存储值为实际值的1000倍', + `total` INT DEFAULT 0 COMMENT '总流量,单位MB,同上', + `used` INT DEFAULT 0 COMMENT '已用流量,单位MB,同上', + `update_time` DATETIME DEFAULT NULL COMMENT '最后更新时间', + `query_status` TINYINT DEFAULT 0 COMMENT 'SIM卡查询状态:0=正常,1=非当前卡商,2=其他错误', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_iccid` (`iccid`), + INDEX `idx_status` (`status`), + INDEX `idx_query_status` (`query_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量卡信息表'; + +/*** + 设备-SIM卡映射表 + */ +CREATE TABLE IF NOT EXISTS `traffic_device_mappings` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一', + `deviceid` VARCHAR(20) NOT NULL COMMENT '设备ID', + `iccid` VARCHAR(20) NOT NULL COMMENT 'SIM卡ICCID', + `start_time` DATETIME NOT NULL COMMENT '开始使用时间', + `end_time` DATETIME DEFAULT NULL COMMENT '结束使用时间,NULL表示当前正在使用', + PRIMARY KEY (`id`), + KEY `idx_deviceid` (`deviceid`), + KEY `idx_iccid` (`iccid`), + KEY `idx_start_time` (`start_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备-SIM卡映射表'; + +/*** + 流量使用记录表 + */ +CREATE TABLE IF NOT EXISTS `traffic_records` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一', + `iccid` VARCHAR(20) NOT NULL COMMENT 'SIM卡ICCID', + `record_time` DATETIME NOT NULL COMMENT '记录时间', + `remaining` INT DEFAULT NULL COMMENT '剩余流量,单位MB,为避免小数,存储值为实际值的1000倍', + `used` INT DEFAULT NULL COMMENT '已用流量,单位MB,为避免小数,存储值为实际值的1000倍', + `total` INT DEFAULT NULL COMMENT '总流量,单位MB,为避免小数,存储值为实际值的1000倍', + PRIMARY KEY (`id`), + KEY `idx_iccid` (`iccid`), + KEY `idx_record_time` (`record_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量使用记录表'; From b011289fdf65b8055f5d2614dd46a6453995708f Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Tue, 3 Jun 2025 16:34:07 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=20ICCID=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=20-=20=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E5=8F=88=E9=87=8D=E6=96=B0=E4=B8=8A=E7=BA=BF?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E6=98=AF=E5=90=A6=E6=9C=89=E6=8D=A2=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beidou/controller/APIController.java | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) 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 7f6f88ac..398343f2 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 @@ -30,6 +30,8 @@ public class APIController extends BasicController{ DeviceCacheCmdMapper cacheCmdMapper; @Autowired WarningMsgMapper warningMsgMapper; + @Autowired + GnssStatusMapper gnssStatusMapper; /****** config ack *******/ @PostMapping(value = "/api/config_ack") @@ -151,6 +153,7 @@ public class APIController extends BasicController{ // 保存 saveMsg(deviceId, tenantId,0xD312, uploadCmd, true); + return null; } @@ -189,10 +192,8 @@ public class APIController extends BasicController{ // 保存 saveMsg(deviceId, tenantId,cacheCmd.getMsgtype(), cacheCmd.getCmd(), true); } - - // 检查iccid + // 设备上线后,检查是否需要重新查询更新 ICCID checkAndAskICCID(device); - return null; } @@ -244,16 +245,27 @@ public class APIController extends BasicController{ msgMapper.insert(gnssMsg); } - void checkAndAskICCID(GnssDevice device){ - if(device.getIccid() == null || device.getIccid().trim().isEmpty()) { - String sendCmd = "AT+ICCID"; - int msgType = 0xD310 + 10; // DTU - short len = (short) (sendCmd.length() + 5); - sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len) + - HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) + - "01" + HexUtil.String2HexString(sendCmd); - rtcmClient.config(device.getDeviceid(), sendCmd); + void checkAndAskICCID(GnssDevice device) { + GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid()); + + // 1. 检查设备是否有 ICCID记录 (初始化) + // 2. 检查设备是否从离线恢复 (防止更换 SIM 卡) + // 3. 如果两种情况都不满足,则不查询 ICCID + + boolean isDeviceReconnecting = status != null && status.getState() == GnssStatus.STATE_OFFLINE; + boolean isIccidEmpty = device.getIccid() == null || device.getIccid().trim().isEmpty(); + + if (!isDeviceReconnecting && !isIccidEmpty) { + return; } + + String sendCmd = "AT+ICCID"; + int msgType = 0xD310 + 10; // DTU + short len = (short) (sendCmd.length() + 5); + sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len) + + HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) + + "01" + HexUtil.String2HexString(sendCmd); + rtcmClient.config(device.getDeviceid(), sendCmd); } void updateICCID(GnssDevice device, String dtuAck) { // 只检查 "ICCID:" 的十六进制部分 @@ -262,7 +274,6 @@ public class APIController extends BasicController{ } 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); From 3ea86962a09769c3017777e9f1f0e1eef8ba0820 Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Tue, 3 Jun 2025 16:45:52 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20SIM=20?= =?UTF-8?q?=E6=B5=81=E9=87=8F=E5=8D=A1=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imdroid/secapi/dto/GnssDeviceMapper.java | 4 + .../simcard/SimCardQueryServiceImpl.java | 147 +++++++----- .../simcard/TrafficCardService.java | 104 +++++++++ .../beidou_ehm/task/SimStatusChecker.java | 213 +++++++++++------- 4 files changed, 335 insertions(+), 133 deletions(-) create mode 100644 sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/TrafficCardService.java diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDeviceMapper.java b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDeviceMapper.java index 60e526a7..8fb70ea0 100644 --- a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDeviceMapper.java +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDeviceMapper.java @@ -12,4 +12,8 @@ public interface GnssDeviceMapper extends MPJBaseMapper { @Update({"update gnssdevices set syn=false where group_id=#{group_id}"}) int setSynFlagByGroupId(int group_id); + + @Update({"update gnssdevices set iccid=#{iccid} where deviceid=#{deviceId}"}) + int updateIccidByDeviceId(String deviceId, String iccid); + } diff --git a/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/SimCardQueryServiceImpl.java b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/SimCardQueryServiceImpl.java index 9784c99c..0175dc24 100644 --- a/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/SimCardQueryServiceImpl.java +++ b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/SimCardQueryServiceImpl.java @@ -4,9 +4,7 @@ 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.GnssStatusJoin; -import com.imdroid.secapi.dto.SimCard; -import com.imdroid.secapi.dto.SimCardsMapper; +import com.imdroid.secapi.dto.*; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -20,7 +18,6 @@ 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.*; @@ -35,7 +32,10 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{ private String KEY; @Autowired - SimCardsMapper simCardsMapper; + TrafficCardMapper trafficCardMapper; + + @Autowired + GnssDeviceMapper gnssDeviceMapper; @Override public BaseResponse queryCardInfo(GnssStatusJoin device) { @@ -59,47 +59,82 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{ private BaseResponse executeQuery(GnssStatusJoin 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)); + BaseResponse response = queryByParams(device.getIccid(), path, responseType); - 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); + 向 DTU发送AT指令查询 ICCID 返回的值比如是:89861124224084565106,但实际是 8986112422408456510B, + 从而导致卡商无法查询到该卡,正确的方法是舍弃最后一位,比如 8986112422408456510 - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + 所以,当出现 " 无效的卡号 " 错误的时候,应该尝试去舍弃最后一位, 这样才是正确的 ICCID 号码 - // 特殊处理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; + */ + if (shouldTryTruncatedIccid(response) && device.getIccid() != null && device.getIccid().length() > 1) { + String truncatedIccid = device.getIccid().substring(0, device.getIccid().length() - 1); + BaseResponse retryResponse = queryByParams(truncatedIccid, path, responseType); + if (retryResponse != null && retryResponse.getStatus() == 1) { + updateGnssDeviceIccid(device, truncatedIccid); + device.setIccid(truncatedIccid); + return retryResponse; } } - - JavaType type = mapper.getTypeFactory().constructParametricType(BaseResponse.class, responseType); - return mapper.readValue(response, type); - + + return response; } catch (Exception e) { logger.error("查询失败: 设备={}, 错误={}", device.getDeviceid(), e.getMessage()); return null; } } + private BaseResponse queryByParams(String iccid, String path, Class responseType) throws Exception { + Map params = buildQueryParams(iccid); + String response = sendHttpPost(path, params); + logger.debug("查询响应: ICCID={}, 响应={}", iccid, response); + + return parseResponse(response, responseType); + } + + private Map buildQueryParams(String iccid) { + Map params = new HashMap<>(); + params.put("username", USERNAME); + params.put("key", KEY); + params.put("card", iccid); + params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + params.put("signature", calculateSignature(params)); + return params; + } + + private BaseResponse parseResponse(String response, Class responseType) throws Exception { + 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); + } + + private boolean shouldTryTruncatedIccid(BaseResponse response) { + return response != null && + (response.getStatus() == 0 || response.getStatus() == -6) && + response.getMessage() != null && + response.getMessage().contains("无效的卡号"); + } + private String calculateSignature(Map params) { try { List paramList = new ArrayList<>(); @@ -147,30 +182,34 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{ return device.getIccid() != null && !device.getIccid().trim().isEmpty(); } - public SimCard CreateOrUpdateSimCard(GnssStatusJoin device) { - SimCard simCard = simCardsMapper.queryByDeviceId(device.getDeviceid()); - if (simCard == null) { - simCard = createNewSimCard(device); + public TrafficCard createOrUpdateTrafficCard(GnssStatusJoin device) { + TrafficCard card = trafficCardMapper.selectById(device.getIccid()); + if (card == null) { + card = new TrafficCard(); + card.setIccid(device.getIccid()); + card.setStatus(-1); + card.setUpdateTime(new Date()); + trafficCardMapper.insert(card); } - return simCard; - } - - public SimCard createNewSimCard(GnssStatusJoin 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; + return card; } public boolean isValidResponse(BaseResponse response) { return response != null && response.getStatus() == 1; } + + private void updateGnssDeviceIccid(GnssStatusJoin device, String newIccid) { + try { + String originalIccid = device.getIccid(); + + GnssDevice gnssDevice = gnssDeviceMapper.queryByDeviceId(device.getDeviceid()); + if (gnssDevice != null) { + gnssDeviceMapper.updateIccidByDeviceId(device.getDeviceid(), newIccid); + logger.debug("更新设备ICCID: 设备={}, 原ICCID={}, 新ICCID={}", + device.getDeviceid(), originalIccid, newIccid); + } + } catch (Exception e) { + logger.error("更新设备ICCID失败: 设备={}, 错误={}", device.getDeviceid(), e.getMessage()); + } + } } diff --git a/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/TrafficCardService.java b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/TrafficCardService.java new file mode 100644 index 00000000..b1f02dc5 --- /dev/null +++ b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/simcard/TrafficCardService.java @@ -0,0 +1,104 @@ +package com.imdroid.beidou_ehm.simcard; + +import com.imdroid.secapi.dto.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; + +@Service +public class TrafficCardService { + + @Autowired + private TrafficCardMapper trafficCardMapper; + + @Autowired + private TrafficDeviceMappingMapper mappingMapper; + + @Autowired + private GnssDeviceMapper deviceMapper; + + @Transactional + public boolean checkAndHandleICCIDChanges(GnssStatusJoin device) { + if (device == null || device.getIccid() == null || device.getIccid().trim().isEmpty()) { + return false; + } + + String currentIccid = device.getIccid().trim(); + + // 查询当前设备的活跃映射记录 + TrafficDeviceMapping activeMapping = mappingMapper.findActiveByDeviceId(device.getDeviceid()); + if (activeMapping == null) { + // 第一次为设备创建映射 + createNewMapping(device.getDeviceid(), currentIccid); + return true; + } else if (!currentIccid.equals(activeMapping.getIccid())) { + // ICCID变更,关闭旧映射,创建新映射 + closeExistingMapping(activeMapping); + createNewMapping(device.getDeviceid(), currentIccid); + return true; + } + + return false; + } + + public TrafficCard getOrCreateTrafficCard(String iccid) { + TrafficCard card = trafficCardMapper.findByIccid(iccid); + + if (card == null) { + card = new TrafficCard(); + card.setIccid(iccid); + card.setMsisdn(""); + card.setStatus(TrafficCard.STATUS_UNKNOWN); + card.setRemaining(0); + card.setTotal(0); + card.setUsed(0); + card.setUpdateTime(new Date()); + card.setQueryStatus(TrafficCard.QUERY_STATUS_NORMAL); + trafficCardMapper.insert(card); + } + + return card; + } + + @Transactional + public void updateDeviceSimMapping(String deviceId, String iccid) { + getOrCreateTrafficCard(iccid); + + // 查询现有映射 + TrafficDeviceMapping activeMapping = mappingMapper.findActiveByDeviceId(deviceId); + + if (activeMapping != null) { + // 如果ICCID变化,关闭现有映射并创建新映射 + if (!iccid.equals(activeMapping.getIccid())) { + closeExistingMapping(activeMapping); + createNewMapping(deviceId, iccid); + } + } else { + // 没有现有映射,创建新映射 + createNewMapping(deviceId, iccid); + } + } + + public void markCardAsNotCurrentVendor(String iccid) { + trafficCardMapper.updateQueryStatus(iccid, TrafficCard.QUERY_STATUS_NOT_CURRENT_VENDOR); + } + + public void markCardAsQueryFailed(String iccid) { + trafficCardMapper.updateQueryStatus(iccid, TrafficCard.QUERY_STATUS_OTHER_ERROR); + } + + private void createNewMapping(String deviceId, String iccid) { + TrafficDeviceMapping newMapping = new TrafficDeviceMapping(); + newMapping.setDeviceid(deviceId); + newMapping.setIccid(iccid); + newMapping.setStartTime(new Date()); + newMapping.setEndTime(null); + mappingMapper.insert(newMapping); + } + + private void closeExistingMapping(TrafficDeviceMapping mapping) { + mappingMapper.endMapping(mapping.getId()); + } +} \ No newline at end of file diff --git a/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/task/SimStatusChecker.java b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/task/SimStatusChecker.java index b0060dd1..225d9b29 100644 --- a/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/task/SimStatusChecker.java +++ b/sec-beidou-ehm/src/main/java/com/imdroid/beidou_ehm/task/SimStatusChecker.java @@ -1,6 +1,5 @@ package com.imdroid.beidou_ehm.task; -import com.alibaba.excel.util.StringUtils; import com.imdroid.beidou_ehm.service.WarningService; import com.imdroid.beidou_ehm.simcard.*; import com.imdroid.common.util.ThreadManager; @@ -13,8 +12,6 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.*; import java.util.concurrent.TimeUnit; @@ -24,29 +21,38 @@ import java.util.concurrent.TimeUnit; public class SimStatusChecker { // SIM 卡流量和状态查询定时任务 // 1. 每小时任务 - // a. 通过CardInfo 检查 iccid 和 sim卡号 - // b. 通过QueryCardStatus 检查SIM卡当前的状态 + // a. 通过 CardInfo 检查 iccid 和 sim卡号 + // b. 通过 QueryCardStatus 检查SIM卡当前的状态 // 2. 每两小时任务 // a. QueryGprs 检查 SIM 流量 final Logger logger = LoggerFactory.getLogger(SimStatusChecker.class); + @Autowired private GnssStatusMapper gnssStatusMapper; - + @Autowired - private SimCardsMapper simCardsMapper; - + private GnssDeviceMapper deviceMapper; + + @Autowired + private TrafficCardMapper trafficCardMapper; + + @Autowired + private TrafficRecordMapper trafficRecordMapper; + + @Autowired + private WarningService warningService; + + @Autowired + private TrafficCardService trafficCardService; + @Autowired private SimCardQueryServiceImpl simCardQueryServiceImpl; - @Autowired - WarningService warningService; // 每小时执行一次状态检查调度 @Scheduled(cron = "0 0 * * * ?") - //@Scheduled(cron = "0 */10 * * * ?") private void scheduleSimCardStatusCheck() { List onlineDevices = gnssStatusMapper.queryOnline(); - logger.debug("当前在线设备数量: {}", onlineDevices.size()); - + // logger.debug("当前在线设备数量: {}", onlineDevices.size()); for (GnssStatusJoin onlineDevice : onlineDevices) { int delay = Math.abs(onlineDevice.getDeviceid().hashCode() % 3600 ); //logger.debug("- 设备: {}, SIM状态查询,延迟执行: {}秒", onlineDevice.getDeviceid(), delay); @@ -79,13 +85,23 @@ public class SimStatusChecker { private void checkDeviceSimCardStatus(GnssStatusJoin device) { try { - // 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询 if (!simCardQueryServiceImpl.hasValidIccid(device)) { return; } - // SimCards 表中没有数据就初始化,这其实说明它在自检中刚获得属于自己的 ICCID 号 - SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device); - updateSimCardInfo(device, simCard); + boolean iccidChanged = trafficCardService.checkAndHandleICCIDChanges(device); + if (iccidChanged) { + logger.info("设备 {} ICCID变更已处理: {}", device.getDeviceid(), device.getIccid()); + } + + TrafficCard trafficCard = trafficCardService.getOrCreateTrafficCard(device.getIccid()); + + // 如果 MSISDN 为空,说明是首次创建的记录,要先去查询基本信息 + if (trafficCard.getMsisdn() == null || trafficCard.getMsisdn().isEmpty()) { + updateTrafficCardBasicInfoFromAPI(device, trafficCard); + } + + // 查询SIM卡状态 + updateTrafficCardStatusFromAPI(device, trafficCard); } catch (Exception e) { logger.error("设备{}状态查询失败: ", device.getDeviceid(), e); @@ -94,75 +110,103 @@ public class SimStatusChecker { private void checkDeviceSimCardTraffic(GnssStatusJoin device) { try { - // 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询 if (!simCardQueryServiceImpl.hasValidIccid(device)) { return; } - // SimCards 表中没有数据就初始化,这说明它在自检中获得属于自己的 ICCID 号 - SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device); - // 如果该卡状态不是已激活,而是其他状态,那么不运行它参与 SIM 卡流量检测 - if(simCard.getStatus() != SimCard.STATUS_ACTIVATED){ + + TrafficCard trafficCard = trafficCardService.getOrCreateTrafficCard(device.getIccid()); + + // 检查查询状态,如果已标记为非当前卡商或查询失败,则跳过 + if (trafficCard.getQueryStatus() != null && + trafficCard.getQueryStatus() != TrafficCard.QUERY_STATUS_NORMAL) { + logger.debug("设备 {} 的SIM卡 {} 查询状态为 {},跳过流量查询", + device.getDeviceid(), + device.getIccid(), + trafficCard.getQueryStatus()); return; } - updateSimCardTrafficFromAPI(device, simCard); + + // 如果该卡状态不是已激活,跳过流量查询 + if(trafficCard.getStatus() != TrafficCard.STATUS_ACTIVATED) { + logger.debug("设备 {} 的SIM卡 {} 状态为 {},跳过流量查询", + device.getDeviceid(), + device.getIccid(), + trafficCard.getStatus()); + return; + } + + // 查询流量信息 + try { + updateTrafficCardTrafficFromAPI(device, trafficCard); + } catch (Exception e) { + // 如果查询失败,标记为查询失败状态 + if (e.getMessage() != null && e.getMessage().contains("不属于当前卡商")) { + trafficCardService.markCardAsNotCurrentVendor(device.getIccid()); + } else { + trafficCardService.markCardAsQueryFailed(device.getIccid()); + } + throw e; + } } catch (Exception e) { - logger.error("设备{}查询失败: ", device.getDeviceid(), e); + logger.error("设备{}流量查询失败: ", device.getDeviceid(), e); } } - private void updateSimCardInfo(GnssStatusJoin device, SimCard simCard) throws Exception { - // 自检中只是获取保存了设备的 ICCID ,所有如果判断如果没有 MSISDN,先更新基本信息 - if (StringUtils.isBlank(simCard.getMsisdn())) { - updateSimCardBasicInfoFromAPI(device, simCard); - } - // 更新状态 - updateSimCardStatusFromAPI(device, simCard); - } - private void updateSimCardBasicInfoFromAPI(GnssStatusJoin device, SimCard simCard) { + private void updateTrafficCardBasicInfoFromAPI(GnssStatusJoin device, TrafficCard trafficCard) { try { BaseResponse response = simCardQueryServiceImpl.queryCardInfo(device); - if (!simCardQueryServiceImpl.isValidResponse(response)) { + if (response == null || response.getStatus() != 1 || response.getData() == null) { + logger.warn("设备 {} 的SIM卡基本信息查询失败: {}", + device.getDeviceid(), + response != null ? response.getMessage() : "无响应"); return; } CardInfoData info = response.getData(); - simCard.setUpdatetime(new Date()); - simCard.setMsisdn(info.getMsisdn()); - simCardsMapper.updateSimCardInfo(simCard); + trafficCard.setUpdateTime(new Date()); + trafficCard.setMsisdn(info.getMsisdn()); + trafficCardMapper.updateCardInfo(trafficCard); logger.debug("更新SIM卡基本信息 - imsi: {}, msisdn: {}, iccid: {}", - info.getImsi(), info.getMsisdn(), info.getIccid()); + info.getImsi(), info.getMsisdn(), info.getIccid()); } catch (Exception e) { logger.error("更新设备{}的SIM卡基本信息失败: ", device.getDeviceid(), e); throw e; } } - private void updateSimCardStatusFromAPI(GnssStatusJoin device, SimCard simCard) { + + private void updateTrafficCardStatusFromAPI(GnssStatusJoin device, TrafficCard trafficCard) { try { BaseResponse response = simCardQueryServiceImpl.queryCardStatus(device); - if (!simCardQueryServiceImpl.isValidResponse(response)) { + if (response == null || response.getStatus() != 1 || response.getData() == null) { + logger.warn("设备 {} 的SIM卡状态查询失败: {}", + device.getDeviceid(), + response != null ? response.getMessage() : "无响应"); return; } - CardStatusData status = response.getData(); - simCard.setUpdatetime(new Date()); - simCard.setStatus(status.getStatusCode()); - simCardsMapper.updateCardStatusInfo(simCard); - checkSimCardStatus(device, simCard); + CardStatusData status = response.getData(); + trafficCard.setUpdateTime(new Date()); + trafficCard.setStatus(status.getStatusCode()); + trafficCardMapper.updateCardInfo(trafficCard); + + checkTrafficCardStatus(device, trafficCard); logger.debug("更新SIM卡状态 - Code: {}, 描述: {}", status.getStatusCode(), status.getStatusDesc()); } catch (Exception e) { logger.error("更新设备{}的SIM卡状态失败: ", device.getDeviceid(), e); - // throw e; } } - private void updateSimCardTrafficFromAPI(GnssStatusJoin device, SimCard simCard) { + private void updateTrafficCardTrafficFromAPI(GnssStatusJoin device, TrafficCard trafficCard) { try { BaseResponse response = simCardQueryServiceImpl.queryGprs(device); - if (!simCardQueryServiceImpl.isValidResponse(response)) { + if (response == null || response.getStatus() != 1 || response.getData() == null) { + logger.warn("设备 {} 的SIM卡流量查询失败: {}", + device.getDeviceid(), + response != null ? response.getMessage() : "无响应"); return; } @@ -173,43 +217,55 @@ public class SimStatusChecker { return; } - simCard.setUpdatetime(new Date()); - simCard.setRemaining(BigDecimal.valueOf(usage.getLeft())); - simCard.setUsed(BigDecimal.valueOf(usage.getUsed())); - simCard.setTotal(BigDecimal.valueOf(usage.getTotal())); - simCardsMapper.updateCardTrafficInfo(simCard); + trafficCard.setUpdateTime(new Date()); + // 将浮点数转换为整数存储(MB值×1000) + trafficCard.setRemaining((int)(usage.getLeft() * 1000)); + trafficCard.setUsed((int)(usage.getUsed() * 1000)); + trafficCard.setTotal((int)(usage.getTotal() * 1000)); - checkSimCardTraffic(device, simCard); + trafficCardMapper.updateCardTrafficInfo(trafficCard); + + TrafficRecord record = new TrafficRecord(); + record.setIccid(trafficCard.getIccid()); + record.setRecordTime(new Date()); + record.setRemaining(trafficCard.getRemaining()); + record.setTotal(trafficCard.getTotal()); + record.setUsed(trafficCard.getUsed()); + trafficRecordMapper.insert(record); + + checkTrafficCardTraffic(device, trafficCard); logger.debug("更新流量信息成功 - deviceId: {}, 剩余: {}MB, 总量: {}MB, 已用: {}MB", device.getIccid(), - simCard.getRemaining(), - simCard.getTotal(), - simCard.getUsed()); + trafficCard.getRemaining() / 1000.0, + trafficCard.getTotal() / 1000.0, + trafficCard.getUsed() / 1000.0); } catch (Exception e) { logger.error("设备{}更新SIM卡流量失败: ", device.getDeviceid(), e); - //throw e; + throw e; } } - public void checkSimCardTraffic(GnssStatusJoin device, SimCard simCard) { + public void checkTrafficCardTraffic(GnssStatusJoin device, TrafficCard trafficCard) { GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid()); if (status == null) return; boolean isUpdated = false; - BigDecimal usedPercentage = simCard.getUsed() - .divide(simCard.getTotal(), 4, RoundingMode.HALF_UP) - .multiply(BigDecimal.valueOf(100)); + // 计算流量使用百分比 + int usedPercentage = 0; + if (trafficCard.getTotal() > 0) { + usedPercentage = (int)((trafficCard.getUsed() * 100.0) / trafficCard.getTotal()); + } // 检查流量使用情况 if (warningService.check(status, WarningCfg.TYPE_SIM_LOW_TRAFFIC, WarningCfg.TYPE_NAME_SIM_LOW_TRAFFIC, false, // 大于等于流量门限值,那么就报警 - usedPercentage.intValue(), + usedPercentage, null, - String.format("流量已使用 %.2f%%", usedPercentage.doubleValue()))) { + String.format("流量已使用 %d%%", usedPercentage))) { isUpdated = true; } @@ -219,27 +275,26 @@ public class SimStatusChecker { } } - // 检查SIM卡状态 - public void checkSimCardStatus(GnssStatusJoin device, SimCard simCard) { + public void checkTrafficCardStatus(GnssStatusJoin device, TrafficCard trafficCard) { GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid()); if (status == null) return; boolean isUpdated = false; // 检查SIM卡状态是否异常(停机、注销、失效) - if (simCard.getStatus() == SimCard.STATUS_SUSPENDED || - simCard.getStatus() == SimCard.STATUS_CANCELLED || - simCard.getStatus() == SimCard.STATUS_INVALID) { + if (trafficCard.getStatus() == TrafficCard.STATUS_SUSPENDED || + trafficCard.getStatus() == TrafficCard.STATUS_CANCELLED || + trafficCard.getStatus() == TrafficCard.STATUS_INVALID) { String statusDesc; - switch(simCard.getStatus()) { - case SimCard.STATUS_SUSPENDED: + switch(trafficCard.getStatus()) { + case TrafficCard.STATUS_SUSPENDED: statusDesc = "停机"; break; - case SimCard.STATUS_CANCELLED: + case TrafficCard.STATUS_CANCELLED: statusDesc = "注销"; break; - case SimCard.STATUS_INVALID: + case TrafficCard.STATUS_INVALID: statusDesc = "失效"; break; default: @@ -248,12 +303,13 @@ public class SimStatusChecker { if (warningService.check(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL, WarningCfg.TYPE_NAME_SIM_STATUS_ABNORMAL, false, - simCard.getStatus(), null, - "SIM卡状态: " + statusDesc)) { + trafficCard.getStatus(), + null, + "SIM卡状态异常: " + statusDesc)) { isUpdated = true; } - } else if (simCard.getStatus() == SimCard.STATUS_ACTIVATED) { - // 状态正常(已激活),清除告警 + } else { + // 清除状态异常告警 if ((status.getWarningcode() & WarningCfg.TYPE_SIM_STATUS_ABNORMAL) != 0) { warningService.clearWarning(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL); isUpdated = true; @@ -265,5 +321,4 @@ public class SimStatusChecker { gnssStatusMapper.updateById(status); } } - -} +} \ No newline at end of file From 59143eec4aebb251a7f8561a29afd89ac8a2676f Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Tue, 3 Jun 2025 16:49:03 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20SIM=20?= =?UTF-8?q?=E6=B5=81=E9=87=8F=E5=8D=A1=E7=AE=A1=E7=90=86=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beidou/controller/SimCardController.java | 282 ++++++++++++++++-- .../static/api/init_super_admin.json | 14 +- .../templates/page/sim_device_mapping.html | 164 ++++++++++ .../resources/templates/page/sim_status.html | 20 +- .../templates/page/sim_traffic_records.html | 140 +++++++++ 5 files changed, 593 insertions(+), 27 deletions(-) create mode 100644 sec-beidou/src/main/resources/templates/page/sim_device_mapping.html create mode 100644 sec-beidou/src/main/resources/templates/page/sim_traffic_records.html 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 index 69c3eaaf..14ece89a 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/SimCardController.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/SimCardController.java @@ -34,7 +34,16 @@ public class SimCardController extends BasicController { private String KEY; @Autowired - private SimCardsMapper simCardsMapper; + private GnssDeviceMapper gnssDeviceMapper; + + @Autowired + private TrafficCardMapper trafficCardMapper; + + @Autowired + private TrafficDeviceMappingMapper trafficDeviceMappingMapper; + + @Autowired + private TrafficRecordMapper trafficRecordMapper; @RequestMapping("/page/sim_status") public String simStatus(Model m, HttpSession session) { @@ -43,6 +52,20 @@ public class SimCardController extends BasicController { return "/page/sim_status"; } + @RequestMapping("/page/sim_traffic_records") + public String simTrafficRecords(Model m, HttpSession session) { + initModel(m, session); + + return "/page/sim_traffic_records"; + } + + @RequestMapping("/page/sim_device_mapping") + public String simDeviceMapping(Model m, HttpSession session) { + initModel(m, session); + + return "/page/sim_device_mapping"; + } + @RequestMapping("/sim/list") @ResponseBody public JSONObject list(HttpSession session, @@ -51,37 +74,98 @@ public class SimCardController extends BasicController { String searchType, String searchContent, Integer status) { - Page pageable = new Page<>(page, limit); - - QueryWrapper queryWrapper = new QueryWrapper<>(); + QueryWrapper deviceQueryWrapper = new QueryWrapper<>(); if (!StringUtils.isEmpty(searchContent)) { switch(searchType) { case "deviceId": - queryWrapper.like("deviceid", searchContent.trim()); + deviceQueryWrapper.like("deviceid", searchContent.trim()); break; case "iccid": - queryWrapper.like("iccid", searchContent.trim()); + deviceQueryWrapper.like("iccid", searchContent.trim()); break; case "simNumber": - queryWrapper.like("msisdn", searchContent.trim()); + // 通过SIM号查找对应的ICCID,然后查询设备 + TrafficCard cardByMsisdn = trafficCardMapper.selectOne( + new QueryWrapper().like("msisdn", searchContent.trim()) + ); + if (cardByMsisdn != null) { + deviceQueryWrapper.eq("iccid", cardByMsisdn.getIccid()); + } else { + deviceQueryWrapper.eq("iccid", ""); + } break; } } - if (status != null) { - queryWrapper.eq("status", status); - } - queryWrapper.orderByDesc("updatetime"); - IPage cs = simCardsMapper.selectPage(pageable, queryWrapper); + // 只查询有ICCID的设备 + deviceQueryWrapper.isNotNull("iccid"); + deviceQueryWrapper.ne("iccid", ""); + + if (status != null) { + List cardsWithStatus = trafficCardMapper.selectList( + new QueryWrapper().eq("status", status) + ); + if (cardsWithStatus.isEmpty()) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 0); + jsonObject.put("msg", ""); + jsonObject.put("count", 0); + jsonObject.put("data", new ArrayList<>()); + return jsonObject; + } + + List iccids = new ArrayList<>(); + for (TrafficCard card : cardsWithStatus) { + iccids.add(card.getIccid()); + } + deviceQueryWrapper.in("iccid", iccids); + } + + deviceQueryWrapper.orderByDesc("deviceid"); + + Page pageable = new Page<>(page, limit); + IPage devices = gnssDeviceMapper.selectPage(pageable, deviceQueryWrapper); + + List> enrichedData = new ArrayList<>(); + for (GnssDevice device : devices.getRecords()) { + TrafficCard trafficCard = null; + if (device.getIccid() != null && !device.getIccid().trim().isEmpty()) { + trafficCard = trafficCardMapper.findByIccid(device.getIccid()); + } + + Map deviceData = new HashMap<>(); + deviceData.put("id", device.getId()); + deviceData.put("deviceid", device.getDeviceid()); + deviceData.put("iccid", device.getIccid()); + + if (trafficCard != null) { + deviceData.put("msisdn", trafficCard.getMsisdn()); + deviceData.put("status", trafficCard.getStatus()); + deviceData.put("remaining", trafficCard.getRemaining()); + deviceData.put("total", trafficCard.getTotal()); + deviceData.put("used", trafficCard.getUsed()); + deviceData.put("updateTime", trafficCard.getUpdateTime()); + deviceData.put("queryStatus", trafficCard.getQueryStatus()); + } else { + deviceData.put("msisdn", "-"); + deviceData.put("status", TrafficCard.STATUS_UNKNOWN); + deviceData.put("remaining", 0); + deviceData.put("total", 0); + deviceData.put("used", 0); + deviceData.put("updateTime", null); + deviceData.put("queryStatus", TrafficCard.QUERY_STATUS_NORMAL); + } + + enrichedData.add(deviceData); + } 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()); + jsonObject.put("count", devices.getTotal()); + jsonObject.put("data", enrichedData); return jsonObject; } @@ -114,11 +198,11 @@ public class SimCardController extends BasicController { if (content.trim().isEmpty()) { throw new IllegalArgumentException("设备ID不能为空"); } - SimCard simCard = simCardsMapper.queryByDeviceId(content.trim()); - if (simCard == null) { - throw new IllegalArgumentException("未找到该设备ID对应的SIM卡信息"); + GnssDevice device = gnssDeviceMapper.queryByDeviceId(content.trim()); + if (device == null || device.getIccid() == null || device.getIccid().trim().isEmpty()) { + throw new IllegalArgumentException("未找到该设备ID或设备没有ICCID信息"); } - params.put("card", simCard.getIccid()); + params.put("card", device.getIccid()); break; case "simNumber": params.put("card", content); @@ -176,6 +260,166 @@ public class SimCardController extends BasicController { } } + @RequestMapping("/sim/traffic-records") + @ResponseBody + public JSONObject getTrafficRecords(HttpSession session, + int page, + int limit, + String searchType, + String searchContent) { + try { + Page pageable = new Page<>(page, limit); + QueryWrapper queryWrapper = new QueryWrapper<>(); + + if (!StringUtils.isEmpty(searchContent)) { + switch(searchType) { + case "deviceId": + GnssDevice device = gnssDeviceMapper.queryByDeviceId(searchContent.trim()); + if (device != null && device.getIccid() != null && !device.getIccid().trim().isEmpty()) { + queryWrapper.eq("iccid", device.getIccid()); + } else { + queryWrapper.eq("iccid", ""); + } + break; + case "iccid": + queryWrapper.like("iccid", searchContent.trim()); + break; + case "simNumber": + TrafficCard cardByMsisdn = trafficCardMapper.selectOne( + new QueryWrapper().like("msisdn", searchContent.trim()) + ); + if (cardByMsisdn != null) { + queryWrapper.eq("iccid", cardByMsisdn.getIccid()); + } else { + queryWrapper.eq("iccid", ""); + } + break; + } + } + + queryWrapper.orderByDesc("record_time"); + IPage records = trafficRecordMapper.selectPage(pageable, queryWrapper); + + List> enrichedData = new ArrayList<>(); + for (TrafficRecord record : records.getRecords()) { + Map recordData = new HashMap<>(); + recordData.put("id", record.getId()); + recordData.put("iccid", record.getIccid()); + recordData.put("recordTime", record.getRecordTime()); + recordData.put("remaining", record.getRemaining()); + recordData.put("total", record.getTotal()); + recordData.put("used", record.getUsed()); + + GnssDevice device = gnssDeviceMapper.selectOne( + new QueryWrapper().eq("iccid", record.getIccid()) + ); + if (device != null) { + recordData.put("deviceid", device.getDeviceid()); + } else { + recordData.put("deviceid", "无绑定设备"); + } + + TrafficCard trafficCard = trafficCardMapper.findByIccid(record.getIccid()); + if (trafficCard != null) { + recordData.put("msisdn", trafficCard.getMsisdn()); + } else { + recordData.put("msisdn", "-"); + } + + enrichedData.add(recordData); + } + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 0); + jsonObject.put("msg", ""); + jsonObject.put("count", records.getTotal()); + jsonObject.put("data", enrichedData); + return jsonObject; + + } catch (Exception e) { + JSONObject errorResponse = new JSONObject(); + errorResponse.put("code", 1); + errorResponse.put("msg", "查询失败: " + e.getMessage()); + errorResponse.put("count", 0); + errorResponse.put("data", new ArrayList<>()); + return errorResponse; + } + } + + @RequestMapping("/sim/device-mapping") + @ResponseBody + public JSONObject getDeviceMapping(HttpSession session, + int page, + int limit, + String searchType, + String searchContent) { + try { + 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": + TrafficCard cardByMsisdn = trafficCardMapper.selectOne( + new QueryWrapper().like("msisdn", searchContent.trim()) + ); + if (cardByMsisdn != null) { + queryWrapper.eq("iccid", cardByMsisdn.getIccid()); + } else { + queryWrapper.eq("iccid", ""); + } + break; + } + } + + queryWrapper.orderByDesc("start_time"); + IPage mappings = trafficDeviceMappingMapper.selectPage(pageable, queryWrapper); + + List> enrichedData = new ArrayList<>(); + for (TrafficDeviceMapping mapping : mappings.getRecords()) { + Map mappingData = new HashMap<>(); + mappingData.put("id", mapping.getId()); + mappingData.put("deviceId", mapping.getDeviceid()); + mappingData.put("iccid", mapping.getIccid()); + mappingData.put("startTime", mapping.getStartTime()); + mappingData.put("endTime", mapping.getEndTime()); + + mappingData.put("isActive", mapping.getEndTime() == null); + + // 查找对应的流量卡信息 + TrafficCard trafficCard = trafficCardMapper.findByIccid(mapping.getIccid()); + if (trafficCard != null) { + mappingData.put("msisdn", trafficCard.getMsisdn()); + } else { + mappingData.put("msisdn", "-"); + } + + enrichedData.add(mappingData); + } + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 0); + jsonObject.put("msg", ""); + jsonObject.put("count", mappings.getTotal()); + jsonObject.put("data", enrichedData); + return jsonObject; + + } catch (Exception e) { + JSONObject errorResponse = new JSONObject(); + errorResponse.put("code", 1); + errorResponse.put("msg", "查询失败: " + e.getMessage()); + errorResponse.put("count", 0); + errorResponse.put("data", new ArrayList<>()); + return errorResponse; + } + } + private String calculateSignature(Map params) { try { List paramList = new ArrayList<>(); 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 c50fc81f..e2d06ffb 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 @@ -130,7 +130,19 @@ "target": "_self" }, { - "title": "卡信息查询", + "title": "流量使用记录", + "href": "page/sim_traffic_records", + "icon": "fa fa-minus", + "target": "_self" + }, + { + "title": "设备映射记录", + "href": "page/sim_device_mapping", + "icon": "fa fa-minus", + "target": "_self" + }, + { + "title": "卡信息聚合查询", "href": "page/sim_traffic_query", "icon": "fa fa-minus", "target": "_self" diff --git a/sec-beidou/src/main/resources/templates/page/sim_device_mapping.html b/sec-beidou/src/main/resources/templates/page/sim_device_mapping.html new file mode 100644 index 00000000..bdaae8a8 --- /dev/null +++ b/sec-beidou/src/main/resources/templates/page/sim_device_mapping.html @@ -0,0 +1,164 @@ + + + + + 设备SIM卡映射记录 + + + + + + + +
+
+
+ 搜索信息 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
    +
  • 设备映射记录
  • +
+
+
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/sec-beidou/src/main/resources/templates/page/sim_status.html b/sec-beidou/src/main/resources/templates/page/sim_status.html index 7e1950fe..d186eb16 100644 --- a/sec-beidou/src/main/resources/templates/page/sim_status.html +++ b/sec-beidou/src/main/resources/templates/page/sim_status.html @@ -110,9 +110,9 @@ function updateFlowChart(simData) { initEcharts(); - var used = parseFloat(simData.used) || 0; - var remaining = parseFloat(simData.remaining) || 0; - var total = parseFloat(simData.total) || (used + remaining); + var used = simData.used ? (simData.used / 1000) : 0; + var remaining = simData.remaining ? (simData.remaining / 1000) : 0; + var total = simData.total ? (simData.total / 1000) : (used + remaining); var option = { title: { @@ -191,11 +191,17 @@ {field: 'deviceid', title: '设备号'}, {field: 'iccid', title: 'ICCID'}, {field: 'msisdn', title: 'SIM 卡号'}, - {field: 'updatetime', title: '更新时间',templet: "
{{layui.util.toDateString(d.updatetime, 'yyyy-MM-dd HH:mm:ss')}}
"}, + {field: 'updateTime', title: '更新时间',templet: "
{{layui.util.toDateString(d.updateTime, 'yyyy-MM-dd HH:mm:ss')}}
"}, {field: 'status', title: '状态',templet: '#statusTpl'}, - {field: 'remaining', title: '剩余流量(MB)'}, - {field: 'used', title: '已使用流量(MB)'}, - {field: 'total', title: '总流量(MB)'} + {field: 'remaining', title: '剩余流量(MB)', templet: function(d) { + return d.remaining ? (d.remaining / 1000).toFixed(2) : '0'; + }}, + {field: 'used', title: '已使用流量(MB)', templet: function(d) { + return d.used ? (d.used / 1000).toFixed(2) : '0'; + }}, + {field: 'total', title: '总流量(MB)', templet: function(d) { + return d.total ? (d.total / 1000).toFixed(2) : '0'; + }} ]; diff --git a/sec-beidou/src/main/resources/templates/page/sim_traffic_records.html b/sec-beidou/src/main/resources/templates/page/sim_traffic_records.html new file mode 100644 index 00000000..34868e7c --- /dev/null +++ b/sec-beidou/src/main/resources/templates/page/sim_traffic_records.html @@ -0,0 +1,140 @@ + + + + + SIM卡流量使用记录 + + + + + + + +
+
+
+ 搜索信息 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
    +
  • 流量使用记录
  • +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file