Compare commits
107 Commits
feature/ui
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 01427e230e | |||
| b6629696d6 | |||
|
|
c8678bc0a0 | ||
| 9d54bdcb1a | |||
| bc89ccca79 | |||
| efe8787d53 | |||
| e7c6b36f6d | |||
| 9913b8a732 | |||
| 4ceac73519 | |||
| 702c6a87f4 | |||
| 3e99132ad7 | |||
| efaf5fe8f4 | |||
| 6ecc833a2c | |||
| e4ec7a13eb | |||
| 94b91c1290 | |||
| 0d88e161d9 | |||
| d694ea8835 | |||
| c79c868c41 | |||
| 1c4633881d | |||
| 5fef8eec51 | |||
| 3cbeba9832 | |||
| c0d25ef0e7 | |||
| c0ba5fc4b9 | |||
| 71c56c6eb3 | |||
| 5f83223d9a | |||
| 1336bb16e6 | |||
| a70852fdc9 | |||
| 4425fcd7e6 | |||
| a4cf6130c3 | |||
| e256cd5e68 | |||
|
|
3a503701af | ||
|
|
cbb1a22161 | ||
|
|
281c11b1e7 | ||
|
|
0d21c8c6f3 | ||
|
|
16c659a42d | ||
|
|
d62777bc91 | ||
| 15b14a94f6 | |||
| a580eb204a | |||
|
|
eed05660ab | ||
|
|
9f5fa31bb1 | ||
|
|
40813356a6 | ||
| 8115e54fcc | |||
|
|
689f6cf360 | ||
|
|
30f8b822e8 | ||
|
|
af1517b350 | ||
| e272988ada | |||
|
|
d6d3213f72 | ||
|
|
137603cddb | ||
|
|
c7b1438d02 | ||
| feedaaabb4 | |||
|
|
57de48e899 | ||
|
|
108bf78bfe | ||
|
|
d53d2510df | ||
|
|
4a394849a1 | ||
|
|
425e4b158e | ||
|
|
b6418ffeae | ||
| 1293f891db | |||
|
|
aac2bcbfd5 | ||
|
|
6495661e1f | ||
|
|
e33ff7aa71 | ||
|
|
528003e39e | ||
|
|
0d3e977f76 | ||
|
|
66f283a8a4 | ||
|
|
705b004d62 | ||
|
|
4ced686ed2 | ||
|
|
3e15355609 | ||
|
|
499037fca6 | ||
|
|
4570a8f9b3 | ||
|
|
702b83ed25 | ||
| 694783cc23 | |||
| b7dcfc40ac | |||
|
|
7224ece179 | ||
|
|
1e78245b5f | ||
|
|
0b3ff272c5 | ||
|
|
7d6d9b29b2 | ||
| 0b9bda229f | |||
| a220fbb1ef | |||
|
|
c594d8b22e | ||
| 2c2fc7dc2f | |||
| d97c2c880d | |||
|
|
188f1303e5 | ||
|
|
199dc8c092 | ||
| 23a13922ca | |||
| d388b848e5 | |||
|
|
5deeb3c70b | ||
|
|
19205ea510 | ||
|
|
a007b35d18 | ||
|
|
d60faf2db3 | ||
| 9f6cd91639 | |||
|
|
c5243ab08b | ||
| 254f54925a | |||
|
|
59143eec4a | ||
|
|
3ea86962a0 | ||
|
|
b011289fdf | ||
|
|
e3870f6680 | ||
| 31b4047d86 | |||
|
|
6552adb4b1 | ||
|
|
0864ca38c9 | ||
|
|
48f32c8d2b | ||
|
|
83528f0cc4 | ||
|
|
c9337e0777 | ||
|
|
c82f87c3dc | ||
|
|
f90c61c676 | ||
|
|
65b02c3e90 | ||
|
|
cdd5599ea9 | ||
| f9ae16563d | |||
| 3d39e5d07e |
1
pom.xml
1
pom.xml
@ -18,6 +18,7 @@
|
|||||||
<module>sec-ntrip-proxy</module>
|
<module>sec-ntrip-proxy</module>
|
||||||
<module>sec-exapi</module>
|
<module>sec-exapi</module>
|
||||||
<module>sec-beidou-ehm</module>
|
<module>sec-beidou-ehm</module>
|
||||||
|
<module>sec-vermgr</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
18
readme.txt
18
readme.txt
@ -28,6 +28,7 @@ beidou-fwd 9906 解算结果推送
|
|||||||
ntrip-proxy 9910 11001(外) ntrip服务器代理
|
ntrip-proxy 9910 11001(外) ntrip服务器代理
|
||||||
beidou-exapi 9908(外) API
|
beidou-exapi 9908(外) API
|
||||||
beidou-ehm 9912 健康检查、SIM卡检查
|
beidou-ehm 9912 健康检查、SIM卡检查
|
||||||
|
vermgr 9914 9916(外) 版本管理服务
|
||||||
|
|
||||||
2024-9
|
2024-9
|
||||||
算法:
|
算法:
|
||||||
@ -55,6 +56,17 @@ beidou-ehm 9912 健康检查、SIM卡检查
|
|||||||
2)健康检查增加连续无有效解的时长之和统计
|
2)健康检查增加连续无有效解的时长之和统计
|
||||||
|
|
||||||
3)批量升级:
|
3)批量升级:
|
||||||
a)在设备页面增加勾选框和“准备升级”、“完成升级”按钮
|
a)在系统管理里增加一页固件升级页面,包括固件列表和设备列表
|
||||||
b)点击“准备升级”,把模式改为“待升级”,同时发连接版本服务器指令
|
1)固件列表:固件名、固件大小、创建时间。每次升级只能单选一个固件
|
||||||
c)点击“完成升级”,把模式改为“正常”
|
2)设备列表:所属组织、设备号、项目、桩号、版本号、使用状态(正常、维护、停用)、当前状态(工作、休眠、离线)、最近一次版本升级时间
|
||||||
|
3)设备可多选,筛选条件:所属组织、设备号、项目、桩号、版本号
|
||||||
|
b)升级过程:
|
||||||
|
1)WEB服务:选择固件->筛选设备->勾选设备
|
||||||
|
2)WEB服务:点击“升级”,系统检查所勾选的设备批次与所选固件是否一致,如果有不一致,弹出“固件不匹配”提示窗口,结束升级
|
||||||
|
3)WEB服务:向版本服务发升级命令UpgradeCmd(deviceList),版本服务如果应答UpgradeAck,则把待升级的设备改为“维护”状态,发连接服务器指令,升级按钮变灰;否则提示“版本服务未启动”的提示,结束升级
|
||||||
|
4)版本服务:当收到WEB服务发来的升级命令UpgradeCmd(deviceList),回应答UpgradeAck,为每个待升级设备启动30s升级定时器
|
||||||
|
5)版本服务:当收到TCP连接响应,则向对端发版本查询指令,开始版本传输流程,升级过程向WEB服务发送进度指示UpgradeInd(deviceid,progress),每次收到ACK则刷新定时器。
|
||||||
|
6)版本服务:当设备升级完毕,或升级定时器超时,结束升级流程,向WEB服务发升级完成指示UpgradeCompleteInd(deviceid,result)。当所有设备升级结束,再给WEB发一个全部升级完成的指示UpgradeCompleteInd(all)
|
||||||
|
7)WEB服务:当收到全部升级结束指示,按钮变正常,升级设备状态改为正常
|
||||||
|
8)WEB服务:保存升级记录
|
||||||
|
c)增加一页固件升级记录表,包括所属组织、设备号、项目、桩号、升级时间、升级固件、是否成功
|
||||||
|
|||||||
@ -41,4 +41,10 @@ public interface BeidouClient {
|
|||||||
@RequestParam(name = "tenantId") Integer tenantId,
|
@RequestParam(name = "tenantId") Integer tenantId,
|
||||||
@RequestParam(name = "uploadTime") LocalDateTime uploadTime);
|
@RequestParam(name = "uploadTime") LocalDateTime uploadTime);
|
||||||
|
|
||||||
|
@PostMapping("/upgrade_progress")
|
||||||
|
String onUpgradeProgress(@RequestParam(name = "deviceId") String deviceId,
|
||||||
|
@RequestParam(name = "msg") String msg);
|
||||||
|
|
||||||
|
@PostMapping("/upgrade_complete")
|
||||||
|
String onUpgradeComplete();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public interface RtcmClient {
|
|||||||
@GetMapping(value = "/get_device_info")
|
@GetMapping(value = "/get_device_info")
|
||||||
public HttpResp getDeviceInfo(@RequestParam(name = "deviceId") String deviceId, @RequestParam(name = "cmd") String cmd);
|
public HttpResp getDeviceInfo(@RequestParam(name = "deviceId") String deviceId, @RequestParam(name = "cmd") String cmd);
|
||||||
@PostMapping("/device_param_changed")
|
@PostMapping("/device_param_changed")
|
||||||
HttpResp deviceParamChanged(@RequestParam(name = "deviceId") String deviceId,@RequestParam(name = "oldParentId") String oldParentId);
|
HttpResp deviceParamChanged(@RequestParam(name = "deviceId") String deviceId,@RequestParam(name = "oldParentId") String oldParentId,@RequestParam(name = "oldParentId1") String oldParentId1);
|
||||||
|
|
||||||
@PostMapping("/group_param_changed")
|
@PostMapping("/group_param_changed")
|
||||||
HttpResp groupParamChanged();
|
HttpResp groupParamChanged();
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.imdroid.secapi.client;
|
||||||
|
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
@FeignClient(name="VersionClient",url = "http://localhost:9916/ver_mgr")
|
||||||
|
public interface VersionClient {
|
||||||
|
@PostMapping("/upgrade_cmd")
|
||||||
|
HttpResp upgrade(@RequestParam(name = "deviceList") String deviceList, @RequestParam(name = "verFile") String verFile);
|
||||||
|
}
|
||||||
@ -3,9 +3,13 @@ package com.imdroid.secapi.dto;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.imdroid.common.util.ByteUtil;
|
||||||
|
import com.imdroid.common.util.HexUtil;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GNSS设备配置数据
|
* GNSS设备配置数据
|
||||||
@ -21,6 +25,7 @@ public class GnssDevice {
|
|||||||
public static final short OP_MODE_USE = 0;
|
public static final short OP_MODE_USE = 0;
|
||||||
public static final short OP_MODE_CHECK = 1;
|
public static final short OP_MODE_CHECK = 1;
|
||||||
public static final short OP_MODE_UNUSE = 2;
|
public static final short OP_MODE_UNUSE = 2;
|
||||||
|
public static final short OP_MODE_UPGRADING = 3;
|
||||||
|
|
||||||
public static final short MODEL_G505 = 0; //F9P
|
public static final short MODEL_G505 = 0; //F9P
|
||||||
public static final short MODEL_G510 = 1; //博通
|
public static final short MODEL_G510 = 1; //博通
|
||||||
@ -28,6 +33,9 @@ public class GnssDevice {
|
|||||||
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式( 仅D3F0和D3F2 )
|
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式( 仅D3F0和D3F2 )
|
||||||
public static final short LOGGING_MODE_FULL = 1; // 完整模式
|
public static final short LOGGING_MODE_FULL = 1; // 完整模式
|
||||||
|
|
||||||
|
public static final int PARA_MASK_HAS_BATTERY = 0x01;
|
||||||
|
public static final int PARA_MASK_VOLTAGE_FACTOR = 0x02;
|
||||||
|
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
private Integer tenantid;
|
private Integer tenantid;
|
||||||
@ -48,6 +56,8 @@ public class GnssDevice {
|
|||||||
private Integer calc_group_id = 1;
|
private Integer calc_group_id = 1;
|
||||||
private String fwd_group_id;
|
private String fwd_group_id;
|
||||||
private String fwd_group_id2;
|
private String fwd_group_id2;
|
||||||
|
private Boolean has_battery;
|
||||||
|
private Byte voltage_factor; //电压测量分压比
|
||||||
private Boolean syn; //组参数是否同步
|
private Boolean syn; //组参数是否同步
|
||||||
private String pictures;
|
private String pictures;
|
||||||
private Double ipose; //初始位置
|
private Double ipose; //初始位置
|
||||||
@ -68,4 +78,46 @@ public class GnssDevice {
|
|||||||
private String remark;
|
private String remark;
|
||||||
private String iccid;
|
private String iccid;
|
||||||
|
|
||||||
|
// 参数改变
|
||||||
|
private Integer change_flag = 0;
|
||||||
|
|
||||||
|
private String parentid1;
|
||||||
|
|
||||||
|
private String getBatteryConfigCmd(){
|
||||||
|
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
|
||||||
|
"55"+(has_battery?"01":"00");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getVoltageFactorConfigCmd(){
|
||||||
|
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
|
||||||
|
"52"+HexUtil.Byte2HexString(voltage_factor);
|
||||||
|
}
|
||||||
|
public List<String> getConfigCmd(){
|
||||||
|
List<String> cmdList = new ArrayList<>();
|
||||||
|
if((change_flag&PARA_MASK_HAS_BATTERY) !=0 ){
|
||||||
|
cmdList.add(getBatteryConfigCmd());
|
||||||
|
}
|
||||||
|
if((change_flag&PARA_MASK_VOLTAGE_FACTOR) !=0 ){
|
||||||
|
cmdList.add(getVoltageFactorConfigCmd());
|
||||||
|
}
|
||||||
|
return cmdList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean clearChangeFlag(String ack){
|
||||||
|
boolean result = false;
|
||||||
|
if(change_flag!=0) {
|
||||||
|
byte[] ackBytes = ByteUtil.hexStringTobyte(ack);
|
||||||
|
if(ackBytes[ackBytes.length-1] == 1) {
|
||||||
|
if (ackBytes[8] == 0x55) {
|
||||||
|
change_flag &= ~PARA_MASK_HAS_BATTERY;
|
||||||
|
result = true;
|
||||||
|
} else if (ackBytes[8] == 0x52) {
|
||||||
|
change_flag &= ~PARA_MASK_VOLTAGE_FACTOR;
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,4 +12,8 @@ public interface GnssDeviceMapper extends MPJBaseMapper<GnssDevice> {
|
|||||||
|
|
||||||
@Update({"update gnssdevices set syn=false where group_id=#{group_id}"})
|
@Update({"update gnssdevices set syn=false where group_id=#{group_id}"})
|
||||||
int setSynFlagByGroupId(int group_id);
|
int setSynFlagByGroupId(int group_id);
|
||||||
|
|
||||||
|
@Update({"update gnssdevices set iccid=#{iccid} where deviceid=#{deviceId}"})
|
||||||
|
int updateIccidByDeviceId(String deviceId, String iccid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class GnssGroup implements Serializable {
|
|||||||
Short gnss_sample_s;
|
Short gnss_sample_s;
|
||||||
|
|
||||||
public String getConfigCmd(GnssDevice device){
|
public String getConfigCmd(GnssDevice device){
|
||||||
String cmd = "D3110009";
|
String cmd = "D311000a";
|
||||||
cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid()))
|
cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid()))
|
||||||
+HexUtil.Byte2HexString((byte) work_cycle.intValue())
|
+HexUtil.Byte2HexString((byte) work_cycle.intValue())
|
||||||
+HexUtil.Byte2HexString((byte) active_time.intValue())
|
+HexUtil.Byte2HexString((byte) active_time.intValue())
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@ExcelProperty("更新时间")
|
||||||
|
private Date updatetime;
|
||||||
|
|
||||||
|
@ExcelProperty("查询状态")
|
||||||
|
private Integer querystatus;
|
||||||
|
}
|
||||||
@ -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<TrafficCard> {
|
||||||
|
|
||||||
|
@Select("select * from traffic_cards where iccid = #{iccid} limit 1")
|
||||||
|
TrafficCard findByIccid(String iccid);
|
||||||
|
|
||||||
|
@Update("UPDATE traffic_cards SET " +
|
||||||
|
"updatetime = #{updatetime}, " +
|
||||||
|
"msisdn = #{msisdn}, " +
|
||||||
|
"status = #{status}, " +
|
||||||
|
"remaining = #{remaining}, " +
|
||||||
|
"total = #{total}, " +
|
||||||
|
"used = #{used} " +
|
||||||
|
"WHERE iccid = #{iccid}")
|
||||||
|
int updateCardInfo(TrafficCard trafficCard);
|
||||||
|
|
||||||
|
@Update("UPDATE traffic_cards SET querystatus = #{querystatus} WHERE iccid = #{iccid}")
|
||||||
|
int updateQueryStatus(String iccid,int queryStatus);
|
||||||
|
|
||||||
|
@Update("UPDATE traffic_cards SET " +
|
||||||
|
"updatetime = #{updatetime}, " +
|
||||||
|
"remaining = #{remaining}, " +
|
||||||
|
"total = #{total}, " +
|
||||||
|
"used = #{used} " +
|
||||||
|
"WHERE iccid = #{iccid} AND iccid IS NOT NULL AND iccid != ''")
|
||||||
|
int updateCardTrafficInfo(TrafficCard trafficCard);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
private Date starttime;
|
||||||
|
|
||||||
|
private Date endtime; // 结束使用时间,NULL表示当前正在使用
|
||||||
|
}
|
||||||
@ -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<TrafficDeviceMapping> {
|
||||||
|
@Select("SELECT * FROM traffic_device_mappings WHERE deviceid = #{deviceId} AND endtime IS NULL LIMIT 1")
|
||||||
|
TrafficDeviceMapping findActiveByDeviceId(String deviceId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM traffic_device_mappings WHERE iccid = #{iccid} AND endtime IS NULL LIMIT 1")
|
||||||
|
TrafficDeviceMapping findActiveByIccid(String iccid);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM traffic_device_mappings WHERE deviceid = #{deviceId} ORDER BY starttime DESC")
|
||||||
|
List<TrafficDeviceMapping> findHistoryByDeviceId(String deviceId);
|
||||||
|
|
||||||
|
@Update("UPDATE traffic_device_mappings SET endtime = NOW() WHERE id = #{id}")
|
||||||
|
int endMapping(Integer id);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
private Date recordtime;
|
||||||
|
|
||||||
|
private Integer remaining; // 剩余流量(MB×1000)
|
||||||
|
private Integer used; // 已用流量(MB×1000)
|
||||||
|
private Integer total; // 总流量(MB×1000)
|
||||||
|
}
|
||||||
@ -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<TrafficRecord> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UpgradeState {
|
||||||
|
static final public byte STATE_DISCONNECTED = 0;
|
||||||
|
static final public byte STATE_INFO_ACQUIRING = 1;
|
||||||
|
static final public byte STATE_UPGRADING = 2;
|
||||||
|
static final public byte STATE_UPGRADED = 3;
|
||||||
|
String deviceId;
|
||||||
|
byte progress=0;
|
||||||
|
byte state=STATE_DISCONNECTED; //0:未连接;1:获取信息;2:升级中;3:升级完成
|
||||||
|
byte code=0;
|
||||||
|
}
|
||||||
@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.JavaType;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.MapperFeature;
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imdroid.secapi.dto.GnssStatusJoin;
|
import com.imdroid.secapi.dto.*;
|
||||||
import com.imdroid.secapi.dto.SimCard;
|
|
||||||
import com.imdroid.secapi.dto.SimCardsMapper;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -35,7 +32,10 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{
|
|||||||
private String KEY;
|
private String KEY;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
SimCardsMapper simCardsMapper;
|
TrafficCardMapper trafficCardMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
GnssDeviceMapper gnssDeviceMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseResponse<CardInfoData> queryCardInfo(GnssStatusJoin device) {
|
public BaseResponse<CardInfoData> queryCardInfo(GnssStatusJoin device) {
|
||||||
@ -59,47 +59,82 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{
|
|||||||
|
|
||||||
private <T> BaseResponse<T> executeQuery(GnssStatusJoin device, String path, Class<T> responseType) {
|
private <T> BaseResponse<T> executeQuery(GnssStatusJoin device, String path, Class<T> responseType) {
|
||||||
try {
|
try {
|
||||||
Map<String, String> params = new HashMap<>();
|
BaseResponse<T> response = queryByParams(device.getIccid(), path, responseType);
|
||||||
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);
|
向 DTU发送AT指令查询 ICCID 返回的值比如是:89861124224084565106,但实际是 8986112422408456510B,
|
||||||
String response = sendHttpPost(path, params);
|
从而导致卡商无法查询到该卡,正确的方法是舍弃最后一位,比如 8986112422408456510
|
||||||
logger.info("查询响应: 设备={}, ICCID={}, 响应={}",
|
|
||||||
device.getDeviceid(), device.getIccid(), response);
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
所以,当出现 " 无效的卡号 " 错误的时候,应该尝试去舍弃最后一位, 这样才是正确的 ICCID 号码
|
||||||
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
|
|
||||||
|
|
||||||
// 特殊处理CardStatus的数组响应
|
*/
|
||||||
if (responseType == CardStatusData.class) {
|
if (shouldTryTruncatedIccid(response) && device.getIccid() != null && device.getIccid().length() > 1) {
|
||||||
JsonNode root = mapper.readTree(response);
|
String truncatedIccid = device.getIccid().substring(0, device.getIccid().length() - 1);
|
||||||
if (root.get("Status").asInt() == 1 && root.get("Data").isArray()) {
|
BaseResponse<T> retryResponse = queryByParams(truncatedIccid, path, responseType);
|
||||||
CardStatusData statusData = new CardStatusData();
|
if (retryResponse != null && retryResponse.getStatus() == 1) {
|
||||||
statusData.setStatusCode(root.get("Data").get(0).asInt());
|
updateGnssDeviceIccid(device, truncatedIccid);
|
||||||
statusData.setStatusDesc(root.get("Data").get(1).asText());
|
device.setIccid(truncatedIccid);
|
||||||
BaseResponse<CardStatusData> baseResponse = new BaseResponse<>();
|
return retryResponse;
|
||||||
baseResponse.setStatus(1);
|
|
||||||
baseResponse.setMessage("Success");
|
|
||||||
baseResponse.setData(statusData);
|
|
||||||
return (BaseResponse<T>) baseResponse;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaType type = mapper.getTypeFactory().constructParametricType(BaseResponse.class, responseType);
|
return response;
|
||||||
return mapper.readValue(response, type);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("查询失败: 设备={}, 错误={}", device.getDeviceid(), e.getMessage());
|
logger.error("查询失败: 设备={}, 错误={}", device.getDeviceid(), e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T> BaseResponse<T> queryByParams(String iccid, String path, Class<T> responseType) throws Exception {
|
||||||
|
Map<String, String> params = buildQueryParams(iccid);
|
||||||
|
String response = sendHttpPost(path, params);
|
||||||
|
logger.debug("查询响应: ICCID={}, 响应={}", iccid, response);
|
||||||
|
|
||||||
|
return parseResponse(response, responseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> buildQueryParams(String iccid) {
|
||||||
|
Map<String, String> 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 <T> BaseResponse<T> parseResponse(String response, Class<T> 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<CardStatusData> baseResponse = new BaseResponse<>();
|
||||||
|
baseResponse.setStatus(1);
|
||||||
|
baseResponse.setMessage("Success");
|
||||||
|
baseResponse.setData(statusData);
|
||||||
|
return (BaseResponse<T>) 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<String, String> params) {
|
private String calculateSignature(Map<String, String> params) {
|
||||||
try {
|
try {
|
||||||
List<String> paramList = new ArrayList<>();
|
List<String> paramList = new ArrayList<>();
|
||||||
@ -147,30 +182,34 @@ public class SimCardQueryServiceImpl implements SimCardQueryService{
|
|||||||
return device.getIccid() != null && !device.getIccid().trim().isEmpty();
|
return device.getIccid() != null && !device.getIccid().trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimCard CreateOrUpdateSimCard(GnssStatusJoin device) {
|
public TrafficCard createOrUpdateTrafficCard(GnssStatusJoin device) {
|
||||||
SimCard simCard = simCardsMapper.queryByDeviceId(device.getDeviceid());
|
TrafficCard card = trafficCardMapper.selectById(device.getIccid());
|
||||||
if (simCard == null) {
|
if (card == null) {
|
||||||
simCard = createNewSimCard(device);
|
card = new TrafficCard();
|
||||||
|
card.setIccid(device.getIccid());
|
||||||
|
card.setStatus(-1);
|
||||||
|
card.setUpdatetime(new Date());
|
||||||
|
trafficCardMapper.insert(card);
|
||||||
}
|
}
|
||||||
return simCard;
|
return card;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidResponse(BaseResponse<?> response) {
|
public boolean isValidResponse(BaseResponse<?> response) {
|
||||||
return response != null && response.getStatus() == 1;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
// 重置MSISDN以强制更新SIM卡号
|
||||||
|
// 换卡了必须重置这些内容,否则会显示旧卡号,后续的查询会出现问题
|
||||||
|
resetCardMsisdn(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());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetCardMsisdn(String iccid) {
|
||||||
|
TrafficCard card = trafficCardMapper.findByIccid(iccid);
|
||||||
|
if (card != null && card.getMsisdn() != null && !card.getMsisdn().isEmpty()) {
|
||||||
|
card.setMsisdn("");
|
||||||
|
trafficCardMapper.updateCardInfo(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package com.imdroid.beidou_ehm.task;
|
package com.imdroid.beidou_ehm.task;
|
||||||
|
|
||||||
import com.alibaba.excel.util.StringUtils;
|
|
||||||
import com.imdroid.beidou_ehm.service.WarningService;
|
import com.imdroid.beidou_ehm.service.WarningService;
|
||||||
import com.imdroid.beidou_ehm.simcard.*;
|
import com.imdroid.beidou_ehm.simcard.*;
|
||||||
import com.imdroid.common.util.ThreadManager;
|
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.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -24,29 +21,38 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class SimStatusChecker {
|
public class SimStatusChecker {
|
||||||
// SIM 卡流量和状态查询定时任务
|
// SIM 卡流量和状态查询定时任务
|
||||||
// 1. 每小时任务
|
// 1. 每小时任务
|
||||||
// a. 通过CardInfo 检查 iccid 和 sim卡号
|
// a. 通过 CardInfo 检查 iccid 和 sim卡号
|
||||||
// b. 通过QueryCardStatus 检查SIM卡当前的状态
|
// b. 通过 QueryCardStatus 检查SIM卡当前的状态
|
||||||
// 2. 每两小时任务
|
// 2. 每两小时任务
|
||||||
// a. QueryGprs 检查 SIM 流量
|
// a. QueryGprs 检查 SIM 流量
|
||||||
final Logger logger = LoggerFactory.getLogger(SimStatusChecker.class);
|
final Logger logger = LoggerFactory.getLogger(SimStatusChecker.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private GnssStatusMapper gnssStatusMapper;
|
private GnssStatusMapper gnssStatusMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SimCardsMapper simCardsMapper;
|
private GnssDeviceMapper deviceMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficCardMapper trafficCardMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficRecordMapper trafficRecordMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WarningService warningService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficCardService trafficCardService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SimCardQueryServiceImpl simCardQueryServiceImpl;
|
private SimCardQueryServiceImpl simCardQueryServiceImpl;
|
||||||
@Autowired
|
|
||||||
WarningService warningService;
|
|
||||||
|
|
||||||
// 每小时执行一次状态检查调度
|
// 每小时执行一次状态检查调度
|
||||||
@Scheduled(cron = "0 0 * * * ?")
|
@Scheduled(cron = "0 0 * * * ?")
|
||||||
//@Scheduled(cron = "0 */10 * * * ?")
|
|
||||||
private void scheduleSimCardStatusCheck() {
|
private void scheduleSimCardStatusCheck() {
|
||||||
List<GnssStatusJoin> onlineDevices = gnssStatusMapper.queryOnline();
|
List<GnssStatusJoin> onlineDevices = gnssStatusMapper.queryOnline();
|
||||||
logger.debug("当前在线设备数量: {}", onlineDevices.size());
|
// logger.debug("当前在线设备数量: {}", onlineDevices.size());
|
||||||
|
|
||||||
for (GnssStatusJoin onlineDevice : onlineDevices) {
|
for (GnssStatusJoin onlineDevice : onlineDevices) {
|
||||||
int delay = Math.abs(onlineDevice.getDeviceid().hashCode() % 3600 );
|
int delay = Math.abs(onlineDevice.getDeviceid().hashCode() % 3600 );
|
||||||
//logger.debug("- 设备: {}, SIM状态查询,延迟执行: {}秒", onlineDevice.getDeviceid(), delay);
|
//logger.debug("- 设备: {}, SIM状态查询,延迟执行: {}秒", onlineDevice.getDeviceid(), delay);
|
||||||
@ -79,13 +85,23 @@ public class SimStatusChecker {
|
|||||||
|
|
||||||
private void checkDeviceSimCardStatus(GnssStatusJoin device) {
|
private void checkDeviceSimCardStatus(GnssStatusJoin device) {
|
||||||
try {
|
try {
|
||||||
// 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询
|
|
||||||
if (!simCardQueryServiceImpl.hasValidIccid(device)) {
|
if (!simCardQueryServiceImpl.hasValidIccid(device)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// SimCards 表中没有数据就初始化,这其实说明它在自检中刚获得属于自己的 ICCID 号
|
boolean iccidChanged = trafficCardService.checkAndHandleICCIDChanges(device);
|
||||||
SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device);
|
if (iccidChanged) {
|
||||||
updateSimCardInfo(device, simCard);
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error("设备{}状态查询失败: ", device.getDeviceid(), e);
|
logger.error("设备{}状态查询失败: ", device.getDeviceid(), e);
|
||||||
@ -94,75 +110,103 @@ public class SimStatusChecker {
|
|||||||
|
|
||||||
private void checkDeviceSimCardTraffic(GnssStatusJoin device) {
|
private void checkDeviceSimCardTraffic(GnssStatusJoin device) {
|
||||||
try {
|
try {
|
||||||
// 不允许尚未从自检得到 ICCID 的设备参加 SIM 卡状态查询
|
|
||||||
if (!simCardQueryServiceImpl.hasValidIccid(device)) {
|
if (!simCardQueryServiceImpl.hasValidIccid(device)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// SimCards 表中没有数据就初始化,这说明它在自检中获得属于自己的 ICCID 号
|
|
||||||
SimCard simCard = simCardQueryServiceImpl.CreateOrUpdateSimCard(device);
|
TrafficCard trafficCard = trafficCardService.getOrCreateTrafficCard(device.getIccid());
|
||||||
// 如果该卡状态不是已激活,而是其他状态,那么不运行它参与 SIM 卡流量检测
|
|
||||||
if(simCard.getStatus() != SimCard.STATUS_ACTIVATED){
|
// 检查查询状态,如果已标记为非当前卡商或查询失败,则跳过
|
||||||
|
if (trafficCard.getQuerystatus() != null &&
|
||||||
|
trafficCard.getQuerystatus() != TrafficCard.QUERY_STATUS_NORMAL) {
|
||||||
|
logger.debug("设备 {} 的SIM卡 {} 查询状态为 {},跳过流量查询",
|
||||||
|
device.getDeviceid(),
|
||||||
|
device.getIccid(),
|
||||||
|
trafficCard.getQuerystatus());
|
||||||
return;
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error("设备{}查询失败: ", device.getDeviceid(), e);
|
logger.error("设备{}流量查询失败: ", device.getDeviceid(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSimCardInfo(GnssStatusJoin device, SimCard simCard) throws Exception {
|
private void updateTrafficCardBasicInfoFromAPI(GnssStatusJoin device, TrafficCard trafficCard) {
|
||||||
// 自检中只是获取保存了设备的 ICCID ,所有如果判断如果没有 MSISDN,先更新基本信息
|
|
||||||
if (StringUtils.isBlank(simCard.getMsisdn())) {
|
|
||||||
updateSimCardBasicInfoFromAPI(device, simCard);
|
|
||||||
}
|
|
||||||
// 更新状态
|
|
||||||
updateSimCardStatusFromAPI(device, simCard);
|
|
||||||
}
|
|
||||||
private void updateSimCardBasicInfoFromAPI(GnssStatusJoin device, SimCard simCard) {
|
|
||||||
try {
|
try {
|
||||||
BaseResponse<CardInfoData> response = simCardQueryServiceImpl.queryCardInfo(device);
|
BaseResponse<CardInfoData> 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardInfoData info = response.getData();
|
CardInfoData info = response.getData();
|
||||||
simCard.setUpdatetime(new Date());
|
trafficCard.setUpdatetime(new Date());
|
||||||
simCard.setMsisdn(info.getMsisdn());
|
trafficCard.setMsisdn(info.getMsisdn());
|
||||||
simCardsMapper.updateSimCardInfo(simCard);
|
trafficCardMapper.updateCardInfo(trafficCard);
|
||||||
|
|
||||||
logger.debug("更新SIM卡基本信息 - imsi: {}, msisdn: {}, iccid: {}",
|
logger.debug("更新SIM卡基本信息 - imsi: {}, msisdn: {}, iccid: {}",
|
||||||
info.getImsi(), info.getMsisdn(), info.getIccid());
|
info.getImsi(), info.getMsisdn(), info.getIccid());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("更新设备{}的SIM卡基本信息失败: ", device.getDeviceid(), e);
|
logger.error("更新设备{}的SIM卡基本信息失败: ", device.getDeviceid(), e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void updateSimCardStatusFromAPI(GnssStatusJoin device, SimCard simCard) {
|
|
||||||
|
private void updateTrafficCardStatusFromAPI(GnssStatusJoin device, TrafficCard trafficCard) {
|
||||||
try {
|
try {
|
||||||
BaseResponse<CardStatusData> response = simCardQueryServiceImpl.queryCardStatus(device);
|
BaseResponse<CardStatusData> 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;
|
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: {}, 描述: {}",
|
logger.debug("更新SIM卡状态 - Code: {}, 描述: {}",
|
||||||
status.getStatusCode(), status.getStatusDesc());
|
status.getStatusCode(), status.getStatusDesc());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("更新设备{}的SIM卡状态失败: ", device.getDeviceid(), e);
|
logger.error("更新设备{}的SIM卡状态失败: ", device.getDeviceid(), e);
|
||||||
// throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSimCardTrafficFromAPI(GnssStatusJoin device, SimCard simCard) {
|
private void updateTrafficCardTrafficFromAPI(GnssStatusJoin device, TrafficCard trafficCard) {
|
||||||
try {
|
try {
|
||||||
BaseResponse<GprsData> response = simCardQueryServiceImpl.queryGprs(device);
|
BaseResponse<GprsData> 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,43 +217,55 @@ public class SimStatusChecker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
simCard.setUpdatetime(new Date());
|
trafficCard.setUpdatetime(new Date());
|
||||||
simCard.setRemaining(BigDecimal.valueOf(usage.getLeft()));
|
// 将浮点数转换为整数存储(MB值×1000)
|
||||||
simCard.setUsed(BigDecimal.valueOf(usage.getUsed()));
|
trafficCard.setRemaining((int)(usage.getLeft() * 1000));
|
||||||
simCard.setTotal(BigDecimal.valueOf(usage.getTotal()));
|
trafficCard.setUsed((int)(usage.getUsed() * 1000));
|
||||||
simCardsMapper.updateCardTrafficInfo(simCard);
|
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",
|
logger.debug("更新流量信息成功 - deviceId: {}, 剩余: {}MB, 总量: {}MB, 已用: {}MB",
|
||||||
device.getIccid(),
|
device.getIccid(),
|
||||||
simCard.getRemaining(),
|
trafficCard.getRemaining() / 1000.0,
|
||||||
simCard.getTotal(),
|
trafficCard.getTotal() / 1000.0,
|
||||||
simCard.getUsed());
|
trafficCard.getUsed() / 1000.0);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("设备{}更新SIM卡流量失败: ", device.getDeviceid(), 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());
|
GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid());
|
||||||
if (status == null) return;
|
if (status == null) return;
|
||||||
|
|
||||||
boolean isUpdated = false;
|
boolean isUpdated = false;
|
||||||
|
|
||||||
BigDecimal usedPercentage = simCard.getUsed()
|
// 计算流量使用百分比
|
||||||
.divide(simCard.getTotal(), 4, RoundingMode.HALF_UP)
|
int usedPercentage = 0;
|
||||||
.multiply(BigDecimal.valueOf(100));
|
if (trafficCard.getTotal() > 0) {
|
||||||
|
usedPercentage = (int)((trafficCard.getUsed() * 100.0) / trafficCard.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
// 检查流量使用情况
|
// 检查流量使用情况
|
||||||
if (warningService.check(status, WarningCfg.TYPE_SIM_LOW_TRAFFIC,
|
if (warningService.check(status, WarningCfg.TYPE_SIM_LOW_TRAFFIC,
|
||||||
WarningCfg.TYPE_NAME_SIM_LOW_TRAFFIC,
|
WarningCfg.TYPE_NAME_SIM_LOW_TRAFFIC,
|
||||||
false, // 大于等于流量门限值,那么就报警
|
false, // 大于等于流量门限值,那么就报警
|
||||||
usedPercentage.intValue(),
|
usedPercentage,
|
||||||
null,
|
null,
|
||||||
String.format("流量已使用 %.2f%%", usedPercentage.doubleValue()))) {
|
String.format("流量已使用 %d%%", usedPercentage))) {
|
||||||
isUpdated = true;
|
isUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,27 +275,26 @@ public class SimStatusChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查SIM卡状态
|
public void checkTrafficCardStatus(GnssStatusJoin device, TrafficCard trafficCard) {
|
||||||
public void checkSimCardStatus(GnssStatusJoin device, SimCard simCard) {
|
|
||||||
GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid());
|
GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid());
|
||||||
if (status == null) return;
|
if (status == null) return;
|
||||||
|
|
||||||
boolean isUpdated = false;
|
boolean isUpdated = false;
|
||||||
|
|
||||||
// 检查SIM卡状态是否异常(停机、注销、失效)
|
// 检查SIM卡状态是否异常(停机、注销、失效)
|
||||||
if (simCard.getStatus() == SimCard.STATUS_SUSPENDED ||
|
if (trafficCard.getStatus() == TrafficCard.STATUS_SUSPENDED ||
|
||||||
simCard.getStatus() == SimCard.STATUS_CANCELLED ||
|
trafficCard.getStatus() == TrafficCard.STATUS_CANCELLED ||
|
||||||
simCard.getStatus() == SimCard.STATUS_INVALID) {
|
trafficCard.getStatus() == TrafficCard.STATUS_INVALID) {
|
||||||
|
|
||||||
String statusDesc;
|
String statusDesc;
|
||||||
switch(simCard.getStatus()) {
|
switch(trafficCard.getStatus()) {
|
||||||
case SimCard.STATUS_SUSPENDED:
|
case TrafficCard.STATUS_SUSPENDED:
|
||||||
statusDesc = "停机";
|
statusDesc = "停机";
|
||||||
break;
|
break;
|
||||||
case SimCard.STATUS_CANCELLED:
|
case TrafficCard.STATUS_CANCELLED:
|
||||||
statusDesc = "注销";
|
statusDesc = "注销";
|
||||||
break;
|
break;
|
||||||
case SimCard.STATUS_INVALID:
|
case TrafficCard.STATUS_INVALID:
|
||||||
statusDesc = "失效";
|
statusDesc = "失效";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -248,12 +303,13 @@ public class SimStatusChecker {
|
|||||||
|
|
||||||
if (warningService.check(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL,
|
if (warningService.check(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL,
|
||||||
WarningCfg.TYPE_NAME_SIM_STATUS_ABNORMAL, false,
|
WarningCfg.TYPE_NAME_SIM_STATUS_ABNORMAL, false,
|
||||||
simCard.getStatus(), null,
|
trafficCard.getStatus(),
|
||||||
"SIM卡状态: " + statusDesc)) {
|
null,
|
||||||
|
"SIM卡状态异常: " + statusDesc)) {
|
||||||
isUpdated = true;
|
isUpdated = true;
|
||||||
}
|
}
|
||||||
} else if (simCard.getStatus() == SimCard.STATUS_ACTIVATED) {
|
} else {
|
||||||
// 状态正常(已激活),清除告警
|
// 清除状态异常告警
|
||||||
if ((status.getWarningcode() & WarningCfg.TYPE_SIM_STATUS_ABNORMAL) != 0) {
|
if ((status.getWarningcode() & WarningCfg.TYPE_SIM_STATUS_ABNORMAL) != 0) {
|
||||||
warningService.clearWarning(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL);
|
warningService.clearWarning(status, WarningCfg.TYPE_SIM_STATUS_ABNORMAL);
|
||||||
isUpdated = true;
|
isUpdated = true;
|
||||||
@ -265,5 +321,4 @@ public class SimStatusChecker {
|
|||||||
gnssStatusMapper.updateById(status);
|
gnssStatusMapper.updateById(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -18,4 +18,8 @@ app.format.date = yyyy-MM-dd
|
|||||||
app.format.time = HH:mm:ss
|
app.format.time = HH:mm:ss
|
||||||
app.format.datetime = yyyy-MM-dd 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
|
mybatis-plus.configuration.map-underscore-to-camel-case=false
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.imdroid.beidou_fwd.entity;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MQTT数据推送 api
|
||||||
|
*
|
||||||
|
* @author LiGang
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ZNYMQTTData {
|
||||||
|
private String timestamp;
|
||||||
|
// gnss数据
|
||||||
|
private double rpose;
|
||||||
|
private double rposn;
|
||||||
|
private double rposu;
|
||||||
|
}
|
||||||
@ -41,6 +41,10 @@ public class MQTTClient {
|
|||||||
client.connect(options);
|
client.connect(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isConnected(){
|
||||||
|
return client.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean publish(String topic, String message) {
|
public boolean publish(String topic, String message) {
|
||||||
if(!client.isConnected()){
|
if(!client.isConnected()){
|
||||||
logger.info("mqtt disconnected");
|
logger.info("mqtt disconnected");
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class Forwarder {
|
|||||||
* @param fwdNameType,推送设备名:deviceid或fwdid或projectid-sector-name
|
* @param fwdNameType,推送设备名:deviceid或fwdid或projectid-sector-name
|
||||||
* @param cycle,发送周期
|
* @param cycle,发送周期
|
||||||
*/
|
*/
|
||||||
void init(String fwdGroupId, String desc, Integer tenantId, byte fwdNameType, int cycle){
|
public void init(String fwdGroupId, String desc, Integer tenantId, byte fwdNameType, int cycle){
|
||||||
this.fwdGroupId = fwdGroupId;
|
this.fwdGroupId = fwdGroupId;
|
||||||
this.description = desc;
|
this.description = desc;
|
||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
@ -90,10 +90,18 @@ public class Forwarder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void forwardCurrentGnss() {
|
||||||
|
if(forwardGnssRecords(LocalDateTime.now(), null)){
|
||||||
|
// 推送失败记录、补发记录
|
||||||
|
ThreadManager.getScheduledThreadPool().schedule(() -> {
|
||||||
|
this.forwardHistoryGnss();
|
||||||
|
},100, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 推送指定企业、设备ID、时间的GNSS记录,查找指定时间前cycle分钟的有效数据推送
|
* 推送指定企业、设备ID、时间的GNSS记录,查找指定时间前cycle分钟的有效数据推送
|
||||||
* @param sendTime:要推送的记录的时间
|
* @param sendTime:要推送的记录的时间
|
||||||
* @param resendRecord:重发记录
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private boolean forwardGnssRecords(LocalDateTime sendTime, ResendRecord resendRecord) {
|
private boolean forwardGnssRecords(LocalDateTime sendTime, ResendRecord resendRecord) {
|
||||||
@ -101,24 +109,45 @@ public class Forwarder {
|
|||||||
String beginTime = sendTime.minusMinutes(fwdCycleMinutes).format(formatter);
|
String beginTime = sendTime.minusMinutes(fwdCycleMinutes).format(formatter);
|
||||||
|
|
||||||
// 查找属于指定推送组的设备列表
|
// 查找属于指定推送组的设备列表
|
||||||
/* QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
|
List<GnssDeviceJoin> gnssDeviceList;
|
||||||
if(deviceId != null){
|
MPJQueryWrapper jquery = null;
|
||||||
queryWrapper.eq("deviceid", deviceId);
|
|
||||||
|
if(resendRecord!=null && resendRecord.getDeviceid()!=null){
|
||||||
|
jquery = new MPJQueryWrapper<GnssDevice> ()
|
||||||
|
.selectAll(GnssDevice.class)
|
||||||
|
.select("d.latitude as latitude")
|
||||||
|
.select("d.longitude as longitude")
|
||||||
|
.leftJoin("gnssstatus d on t.deviceid = d.deviceid")
|
||||||
|
.and(warpper->warpper.eq("fwd_group_id", fwdGroupId)
|
||||||
|
.or()
|
||||||
|
.eq("fwd_group_id2", fwdGroupId)).
|
||||||
|
eq("deviceid", resendRecord.getDeviceid());
|
||||||
}
|
}
|
||||||
queryWrapper.eq("fwd_group_id", fwdGroupId)
|
else if(resendRecord!=null && resendRecord.getProjectid()!=null){
|
||||||
.or()
|
jquery = new MPJQueryWrapper<GnssDevice> ()
|
||||||
.eq("fwd_group_id2", fwdGroupId);
|
.selectAll(GnssDevice.class)
|
||||||
List<GnssDevice> gnssDeviceList = deviceMapper.selectList(queryWrapper);
|
.select("d.latitude as latitude")
|
||||||
*/
|
.select("d.longitude as longitude")
|
||||||
MPJQueryWrapper jquery = new MPJQueryWrapper<GnssDevice> ()
|
.leftJoin("gnssstatus d on t.deviceid = d.deviceid")
|
||||||
.selectAll(GnssDevice.class)
|
.and(warpper->warpper.eq("fwd_group_id", fwdGroupId)
|
||||||
.select("d.latitude as latitude")
|
.or()
|
||||||
.select("d.longitude as longitude")
|
.eq("fwd_group_id2", fwdGroupId))
|
||||||
.leftJoin("gnssstatus d on t.deviceid = d.deviceid")
|
.and(warpper->warpper.eq("project_id",resendRecord.getProjectid())
|
||||||
.eq("fwd_group_id", fwdGroupId)
|
.or()
|
||||||
.or()
|
.eq("project2_id",resendRecord.getProjectid()));
|
||||||
.eq("fwd_group_id2", fwdGroupId);
|
}
|
||||||
List<GnssDeviceJoin> gnssDeviceList = deviceMapper.selectJoinList(GnssDeviceJoin.class, jquery);
|
else{
|
||||||
|
jquery = new MPJQueryWrapper<GnssDevice> ()
|
||||||
|
.selectAll(GnssDevice.class)
|
||||||
|
.select("d.latitude as latitude")
|
||||||
|
.select("d.longitude as longitude")
|
||||||
|
.leftJoin("gnssstatus d on t.deviceid = d.deviceid")
|
||||||
|
.and(warpper->warpper.eq("fwd_group_id", fwdGroupId)
|
||||||
|
.or()
|
||||||
|
.eq("fwd_group_id2", fwdGroupId));
|
||||||
|
}
|
||||||
|
gnssDeviceList = deviceMapper.selectJoinList(GnssDeviceJoin.class, jquery);
|
||||||
|
logger.debug("candidate fwd devices {}", gnssDeviceList.size());
|
||||||
|
|
||||||
// 查询最近半小时的GNSS记录
|
// 查询最近半小时的GNSS记录
|
||||||
QueryWrapper<GnssCalcData> gnssQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<GnssCalcData> gnssQueryWrapper = new QueryWrapper<>();
|
||||||
@ -131,7 +160,9 @@ public class Forwarder {
|
|||||||
if(resendRecord != null && resendRecord.getDeviceid()!=null){
|
if(resendRecord != null && resendRecord.getDeviceid()!=null){
|
||||||
gnssQueryWrapper.eq("deviceid", resendRecord.getDeviceid());
|
gnssQueryWrapper.eq("deviceid", resendRecord.getDeviceid());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GnssCalcData> locationRecords = gnssDataMapper.selectList(gnssQueryWrapper);
|
List<GnssCalcData> locationRecords = gnssDataMapper.selectList(gnssQueryWrapper);
|
||||||
|
logger.debug("candidate fwd records {}", locationRecords.size());
|
||||||
if(locationRecords.size() == 0) return false;
|
if(locationRecords.size() == 0) return false;
|
||||||
|
|
||||||
// 构造按项目id分类的GNSS记录
|
// 构造按项目id分类的GNSS记录
|
||||||
@ -216,15 +247,15 @@ public class Forwarder {
|
|||||||
fwdResult = false;
|
fwdResult = false;
|
||||||
fwdRecord.setResult(FwdRecord.RESULT_FAILED);
|
fwdRecord.setResult(FwdRecord.RESULT_FAILED);
|
||||||
// 新增重发记录
|
// 新增重发记录
|
||||||
resendRecord = new ResendRecord();
|
ResendRecord resendRecord1 = new ResendRecord();
|
||||||
resendRecord.setProjectid(projectId);
|
resendRecord1.setProjectid(projectId);
|
||||||
resendRecord.setTenantid(tenantId);
|
resendRecord1.setTenantid(tenantId);
|
||||||
resendRecord.setCreatetime(LocalDateTime.now());
|
resendRecord1.setCreatetime(LocalDateTime.now());
|
||||||
resendRecord.setStarttime(sendTime);
|
resendRecord1.setStarttime(sendTime);
|
||||||
resendRecord.setEndtime(sendTime);
|
resendRecord1.setEndtime(sendTime);
|
||||||
resendRecord.setFwd_group_id(fwdGroupId);
|
resendRecord1.setFwd_group_id(fwdGroupId);
|
||||||
resendRecord.setState(ResendRecord.STATE_FWD_FAILED);
|
resendRecord1.setState(ResendRecord.STATE_FWD_FAILED);
|
||||||
resendRecordMapper.insert(resendRecord);
|
resendRecordMapper.insert(resendRecord1);
|
||||||
}
|
}
|
||||||
fwdRecordsMapper.insert(fwdRecord);
|
fwdRecordsMapper.insert(fwdRecord);
|
||||||
}
|
}
|
||||||
@ -239,14 +270,6 @@ public class Forwarder {
|
|||||||
return fwdResult;
|
return fwdResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
void forwardCurrentGnss() {
|
|
||||||
if(forwardGnssRecords(LocalDateTime.now(),null)) {
|
|
||||||
// 推送失败记录、补发记录
|
|
||||||
ThreadManager.getScheduledThreadPool().schedule(() -> {
|
|
||||||
this.forwardHistoryGnss();
|
|
||||||
},100, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void forwardHistoryGnss() {
|
void forwardHistoryGnss() {
|
||||||
// 1.从转发记录表里检索待补传记录时间表,含设备Id,时间段
|
// 1.从转发记录表里检索待补传记录时间表,含设备Id,时间段
|
||||||
@ -257,7 +280,7 @@ public class Forwarder {
|
|||||||
queryWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
queryWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
||||||
List<ResendRecord> resendRecordsList = resendRecordMapper.selectList(queryWrapper);
|
List<ResendRecord> resendRecordsList = resendRecordMapper.selectList(queryWrapper);
|
||||||
|
|
||||||
if(resendRecordsList!=null){
|
if(resendRecordsList!=null && resendRecordsList.size()>0){
|
||||||
//修改状态
|
//修改状态
|
||||||
UpdateWrapper<ResendRecord> updateWrapper = new UpdateWrapper<>();
|
UpdateWrapper<ResendRecord> updateWrapper = new UpdateWrapper<>();
|
||||||
updateWrapper.eq("fwd_group_id",fwdGroupId);
|
updateWrapper.eq("fwd_group_id",fwdGroupId);
|
||||||
@ -266,11 +289,11 @@ public class Forwarder {
|
|||||||
updateWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
updateWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
||||||
updateWrapper.set("state",ResendRecord.STATE_FWDING);
|
updateWrapper.set("state",ResendRecord.STATE_FWDING);
|
||||||
int updateNum = resendRecordMapper.update(null, updateWrapper);
|
int updateNum = resendRecordMapper.update(null, updateWrapper);
|
||||||
logger.info("{} forward history records: {}, update {}",fwdGroupId, resendRecordsList.size(),updateNum);
|
logger.debug("{} forward history records: {}, update {}",fwdGroupId, resendRecordsList.size(),updateNum);
|
||||||
// 2.检索这个这个时间段的解算结果,如果有数据则单个终端转发,标志记录为已补传
|
// 2.检索这个这个时间段的解算结果,如果有数据则单个终端转发,标志记录为已补传
|
||||||
for(ResendRecord record:resendRecordsList){
|
for(ResendRecord record:resendRecordsList){
|
||||||
if(record.getProjectid()!=null)
|
if(record.getProjectid()!=null)
|
||||||
logger.info("{} forward history {}",fwdGroupId, record.getProjectid());
|
logger.debug("{} forward history {}",fwdGroupId, record.getProjectid());
|
||||||
forwardBatchGnssRecords(record);
|
forwardBatchGnssRecords(record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,7 +427,7 @@ public class Forwarder {
|
|||||||
tranData.setDeviceType(2);
|
tranData.setDeviceType(2);
|
||||||
tranData.setDeviceSn(locationRecord.getDeviceid());
|
tranData.setDeviceSn(locationRecord.getDeviceid());
|
||||||
String json = GsonUtil.toJson(tranData);
|
String json = GsonUtil.toJson(tranData);
|
||||||
logger.info("forward to GZY mqtt: {}",json);
|
logger.debug("forward to GZY mqtt: {}",json);
|
||||||
|
|
||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,12 @@ public class GXJKForwarder extends Forwarder {
|
|||||||
void registerMe() throws MqttException {
|
void registerMe() throws MqttException {
|
||||||
init(FORWARDER_NAME, "MQTT "+brokerUrl,7,FWD_DEVICE_NAME,10);
|
init(FORWARDER_NAME, "MQTT "+brokerUrl,7,FWD_DEVICE_NAME,10);
|
||||||
mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
|
mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
|
||||||
mqttClient.connect();
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("gxjk mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,8 +51,16 @@ public class GXJKForwarder extends Forwarder {
|
|||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("gxjk forwardGnss");
|
logger.debug("gxjk forwardGnss");
|
||||||
forwardCurrentGnss();
|
if(mqttClient.isConnected()) forwardCurrentGnss();
|
||||||
|
else{
|
||||||
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("gxjk mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,8 +89,8 @@ public class GXJKForwarder extends Forwarder {
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = "#" + GsonUtil.toJson(sendData) + "!";
|
String json = "#" + GsonUtil.toJson(sendData) + "!";
|
||||||
logger.info("project " + projectId + ": push calculation result to GXJK");
|
logger.debug("project " + projectId + ": push calculation result to GXJK");
|
||||||
logger.info(json);
|
logger.debug(json);
|
||||||
// /slope/项目号-all/gnss/all/publish
|
// /slope/项目号-all/gnss/all/publish
|
||||||
String topic = "/slope/"+projectId+"-all/gnss/all/publish";
|
String topic = "/slope/"+projectId+"-all/gnss/all/publish";
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import java.util.List;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
public class GXXfzForwarder extends Forwarder{
|
public class GXXfzForwarder extends Forwarder{
|
||||||
private String FORWARDER_NAME = "广西新发展";
|
private final String FORWARDER_NAME = "广西新发展";
|
||||||
@Value("${xfz.server.host}")
|
@Value("${xfz.server.host}")
|
||||||
private String host;
|
private String host;
|
||||||
|
|
||||||
@ -69,13 +69,13 @@ public class GXXfzForwarder extends Forwarder{
|
|||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("xfz forwardGnss");
|
logger.debug("xfz forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
||||||
private void checkDevice() {
|
private void checkDevice() {
|
||||||
//logger.info("zny checkDevice");
|
//logger.debug("zny checkDevice");
|
||||||
checkOfflineDevice("2345053","2350106","2350124");
|
checkOfflineDevice("2345053","2350106","2350124");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
@ -107,8 +107,8 @@ public class GXXfzForwarder extends Forwarder{
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
|
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
|
||||||
logger.info("project " + projectId + ": push calculation result to XFZ");
|
logger.debug("project " + projectId + ": push calculation result to XFZ");
|
||||||
logger.info(json);
|
logger.debug(json);
|
||||||
try {
|
try {
|
||||||
listener.clear();
|
listener.clear();
|
||||||
xfzTcpClient.writeAndFlush(json);
|
xfzTcpClient.writeAndFlush(json);
|
||||||
|
|||||||
@ -40,7 +40,7 @@ public class GZYForwarder extends Forwarder{
|
|||||||
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
||||||
// @Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
// @Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("gzy UDP forwardGnss");
|
logger.debug("gzy UDP forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ public class GZYForwarder extends Forwarder{
|
|||||||
tranData.setZ(d);
|
tranData.setZ(d);
|
||||||
gzyData.setTranData(tranData);
|
gzyData.setTranData(tranData);
|
||||||
String msg = "JGKJ" + GsonUtil.toJson(gzyData) + "#!";
|
String msg = "JGKJ" + GsonUtil.toJson(gzyData) + "#!";
|
||||||
logger.info("forward to GZY");
|
logger.debug("forward to GZY");
|
||||||
logger.info(msg);
|
logger.debug(msg);
|
||||||
udpClient.sendMessage(msg);
|
udpClient.sendMessage(msg);
|
||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,12 @@ public class GZYMQTTForwarder extends Forwarder {
|
|||||||
void registerMe() throws MqttException {
|
void registerMe() throws MqttException {
|
||||||
init(FORWARDER_NAME, "MQTT "+brokerUrl,2,FWD_DEVICE_ALIAS_NAME,30);
|
init(FORWARDER_NAME, "MQTT "+brokerUrl,2,FWD_DEVICE_ALIAS_NAME,30);
|
||||||
mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
|
mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
|
||||||
mqttClient.connect();
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("gzy mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,9 +61,19 @@ public class GZYMQTTForwarder extends Forwarder {
|
|||||||
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
||||||
//@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
|
//@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("gzy mqtt forwardGnss");
|
logger.debug("gzy mqtt forwardGnss");
|
||||||
forwardCurrentGnss();
|
if(mqttClient.isConnected()) {
|
||||||
//forwardAngleData();
|
forwardCurrentGnss();
|
||||||
|
//forwardAngleData();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("gzy mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,7 +92,7 @@ public class GZYMQTTForwarder extends Forwarder {
|
|||||||
tranData.setDeviceType(2);
|
tranData.setDeviceType(2);
|
||||||
tranData.setDeviceSn(locationRecord.getDeviceid());
|
tranData.setDeviceSn(locationRecord.getDeviceid());
|
||||||
String json = GsonUtil.toJson(tranData);
|
String json = GsonUtil.toJson(tranData);
|
||||||
logger.info("forward to GZY mqtt: {}",json);
|
logger.debug("forward to GZY mqtt: {}",json);
|
||||||
try {
|
try {
|
||||||
if(!mqttClient.publish(topic, json)) break;
|
if(!mqttClient.publish(topic, json)) break;
|
||||||
Thread.sleep(50);
|
Thread.sleep(50);
|
||||||
@ -116,7 +131,7 @@ public class GZYMQTTForwarder extends Forwarder {
|
|||||||
tranData.setAngleAz(NumberUtils.scale((double) az,2));
|
tranData.setAngleAz(NumberUtils.scale((double) az,2));
|
||||||
|
|
||||||
String json = GsonUtil.toJson(tranData);
|
String json = GsonUtil.toJson(tranData);
|
||||||
logger.info("forward to GZY mqtt angles: {}",json);
|
logger.debug("forward to GZY mqtt angles: {}",json);
|
||||||
try {
|
try {
|
||||||
if(!mqttClient.publish(topic, json)) break;
|
if(!mqttClient.publish(topic, json)) break;
|
||||||
Thread.sleep(10);
|
Thread.sleep(10);
|
||||||
@ -125,7 +140,7 @@ public class GZYMQTTForwarder extends Forwarder {
|
|||||||
}
|
}
|
||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
logger.info("total number of angles sent to GZY: {}",sendNum);
|
logger.debug("total number of angles sent to GZY: {}",sendNum);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public class KingMaForwarder extends Forwarder{
|
|||||||
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
||||||
//@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
|
//@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("kingma forwardGnss");
|
logger.debug("kingma forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,12 +88,12 @@ public class KingMaForwarder extends Forwarder{
|
|||||||
if(header == null || nowTime.isAfter(lastTokenTime.plusMinutes(59))){
|
if(header == null || nowTime.isAfter(lastTokenTime.plusMinutes(59))){
|
||||||
try {
|
try {
|
||||||
if (!updateToken()) {
|
if (!updateToken()) {
|
||||||
logger.info("update token failed!");
|
logger.debug("update token failed!");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e){
|
catch (Exception e){
|
||||||
logger.info("update token failed!");
|
logger.debug("update token failed!");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
lastTokenTime = nowTime;
|
lastTokenTime = nowTime;
|
||||||
@ -135,10 +135,10 @@ public class KingMaForwarder extends Forwarder{
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = GsonUtil.toJson(dataList);
|
String json = GsonUtil.toJson(dataList);
|
||||||
logger.info(json);
|
logger.debug(json);
|
||||||
String result = HttpUtils.postJson(data_host,header,json);
|
String result = HttpUtils.postJson(data_host,header,json);
|
||||||
logger.info("project " + projectId + ": push calculation result to Kingma");
|
logger.debug("project " + projectId + ": push calculation result to Kingma");
|
||||||
logger.info("result: "+result);
|
logger.debug("result: "+result);
|
||||||
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
||||||
String msg = obj.getString("message");
|
String msg = obj.getString("message");
|
||||||
if(msg.equals("Success")) return sendNum;
|
if(msg.equals("Success")) return sendNum;
|
||||||
|
|||||||
@ -75,7 +75,7 @@ public class SaasForwarder extends Forwarder{
|
|||||||
@Scheduled(cron = "0 20,50 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 20,50 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
if(enabled) {
|
if(enabled) {
|
||||||
logger.info("saas forwardGnss");
|
logger.debug("saas forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,8 +107,8 @@ public class SaasForwarder extends Forwarder{
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = "#" + GsonUtil.toJson(tcpMessage) + "!";
|
String json = "#" + GsonUtil.toJson(tcpMessage) + "!";
|
||||||
logger.info("project " + projectId + ": push calculation result to SAAS");
|
logger.debug("project " + projectId + ": push calculation result to SAAS");
|
||||||
logger.info(json);
|
logger.debug(json);
|
||||||
try {
|
try {
|
||||||
listener.clear();
|
listener.clear();
|
||||||
tcpClient.writeAndFlush(json);
|
tcpClient.writeAndFlush(json);
|
||||||
|
|||||||
@ -34,14 +34,14 @@ public class ZNYForwarder extends Forwarder{
|
|||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
||||||
private void checkDevice() {
|
private void checkDevice() {
|
||||||
//logger.info("zny checkDevice");
|
//logger.debug("zny checkDevice");
|
||||||
//checkNoDataDevice("2345078","2345065","2345073");
|
//checkNoDataDevice("2345078","2345065","2345073");
|
||||||
//checkNoDataDevice("2345085","2345068","2345075");
|
//checkNoDataDevice("2345085","2345068","2345075");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("zny forwardGnss");
|
logger.debug("zny forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,12 +60,12 @@ public class ZNYForwarder extends Forwarder{
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = GsonUtil.toJson(defoData);
|
String json = GsonUtil.toJson(defoData);
|
||||||
logger.info("发送数据到武汉中南设计院平台:{}", json);
|
logger.debug("发送数据到武汉中南设计院平台:{}", json);
|
||||||
//return sendNum;
|
//return sendNum;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String result = HttpUtils.postJson(data_host, json);
|
String result = HttpUtils.postJson(data_host, json);
|
||||||
logger.info("发送数据到武汉中南设计院平台返回结果:{}", result);
|
logger.debug("发送数据到武汉中南设计院平台返回结果:{}", result);
|
||||||
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
||||||
String msg = obj.getString("msg");
|
String msg = obj.getString("msg");
|
||||||
if (msg.contains("成功")) return sendNum;
|
if (msg.contains("成功")) return sendNum;
|
||||||
|
|||||||
@ -34,12 +34,12 @@ public class ZNYForwarder2 extends Forwarder{
|
|||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
logger.info("zny2 forwardGnss");
|
logger.debug("zny2 forwardGnss");
|
||||||
forwardCurrentGnss();
|
forwardCurrentGnss();
|
||||||
}
|
}
|
||||||
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
|
||||||
private void checkDevice() {
|
private void checkDevice() {
|
||||||
//logger.info("zny checkDevice");
|
//logger.debug("zny checkDevice");
|
||||||
//checkOfflineDevice("2419374","2410188","2410194");
|
//checkOfflineDevice("2419374","2410188","2410194");
|
||||||
//checkOfflineDevice("2419350","2410232","2410232");
|
//checkOfflineDevice("2419350","2410232","2410232");
|
||||||
//checkOfflineDevice("2345074","2345065","2345089");
|
//checkOfflineDevice("2345074","2345065","2345089");
|
||||||
@ -59,12 +59,12 @@ public class ZNYForwarder2 extends Forwarder{
|
|||||||
sendNum++;
|
sendNum++;
|
||||||
}
|
}
|
||||||
String json = GsonUtil.toJson(defoData);
|
String json = GsonUtil.toJson(defoData);
|
||||||
logger.info("发送数据到武汉中南设计院2.0平台:{}", json);
|
logger.debug("发送数据到武汉中南设计院2.0平台:{}", json);
|
||||||
//return sendNum;
|
//return sendNum;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String result = HttpUtils.postJson(data_host, json);
|
String result = HttpUtils.postJson(data_host, json);
|
||||||
logger.info("武汉中南设计院2.0平台返回结果:{}", result);
|
logger.debug("武汉中南设计院2.0平台返回结果:{}", result);
|
||||||
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
JSONObject obj = (JSONObject) JSONObject.parse(result);
|
||||||
String msg = obj.getString("msg");
|
String msg = obj.getString("msg");
|
||||||
if (msg.contains("成功")) return sendNum;
|
if (msg.contains("成功")) return sendNum;
|
||||||
|
|||||||
@ -0,0 +1,108 @@
|
|||||||
|
package com.imdroid.beidou_fwd.task;
|
||||||
|
|
||||||
|
|
||||||
|
import com.imdroid.beidou_fwd.entity.ZNYMQTTData;
|
||||||
|
import com.imdroid.beidou_fwd.service.MQTTClient;
|
||||||
|
import com.imdroid.common.util.Des3Utils;
|
||||||
|
import com.imdroid.common.util.GsonUtil;
|
||||||
|
import com.imdroid.common.util.NumberUtils;
|
||||||
|
import com.imdroid.secapi.dto.GnssCalcData;
|
||||||
|
import com.imdroid.secapi.dto.GnssStatusMsgMapper;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
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.PostConstruct;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Configuration
|
||||||
|
@EnableScheduling
|
||||||
|
public class ZNYMQTTForwarder extends Forwarder {
|
||||||
|
static final String FORWARDER_NAME = "中南院MQTT";
|
||||||
|
final DateTimeFormatter formatterMs = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
|
||||||
|
|
||||||
|
static byte[] key = new byte[] { (byte) 0x8c, (byte) 0xc7, 0x2b, 0x05, 0x70, 0x5d, 0x5c, 0x46, (byte) 0xf4, 0x12, (byte) 0xaf, (byte) 0x8c, (byte) 0xbe,
|
||||||
|
(byte) 0xd5, 0x5a, (byte) 0xad, (byte) 0x8c, (byte) 0xc7, 0x2b, 0x05, 0x70, 0x5d, 0x5c, 0x46 };
|
||||||
|
static byte[] iv = new byte[] { 0x66, 0x7b, 0x02, (byte) 0xa8, 0x5c, 0x61, (byte) 0xc7, (byte) 0x86 };
|
||||||
|
|
||||||
|
@Value("${zny.mqtt.brokerUrl}")
|
||||||
|
private String brokerUrl;
|
||||||
|
@Value("${zny.mqtt.username}")
|
||||||
|
private String username;
|
||||||
|
@Value("${zny.mqtt.password}")
|
||||||
|
private String password;
|
||||||
|
@Value("${zny.mqtt.clientid}")
|
||||||
|
private String clientid;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
GnssStatusMsgMapper statusMsgMapper;
|
||||||
|
MQTTClient mqttClient;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void registerMe() throws MqttException {
|
||||||
|
init(FORWARDER_NAME, "MQTT "+brokerUrl,4,FWD_DEVICE_ALIAS_NAME,30);
|
||||||
|
mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
|
||||||
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("zny mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每半小时转发GNSS解算结果
|
||||||
|
*/
|
||||||
|
//@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
|
||||||
|
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
||||||
|
private void forwardGnss() {
|
||||||
|
logger.debug("zny mqtt forwardGnss");
|
||||||
|
if(mqttClient.isConnected()) forwardCurrentGnss();
|
||||||
|
else{
|
||||||
|
try{
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logger.error("zny mqtt connect failed: {}",e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int send(String projectId, List<GnssCalcData> records, LocalDateTime sentTime) {
|
||||||
|
int sendNum = 0;
|
||||||
|
|
||||||
|
for (GnssCalcData locationRecord : records) {
|
||||||
|
ZNYMQTTData tranData = new ZNYMQTTData();
|
||||||
|
tranData.setTimestamp(locationRecord.getCreatetime().format(formatterMs));
|
||||||
|
double n = NumberUtils.scale(locationRecord.getRposn(), 2);
|
||||||
|
double e = NumberUtils.scale(locationRecord.getRpose(), 2);
|
||||||
|
double d = NumberUtils.scale(locationRecord.getRposd(), 2);
|
||||||
|
tranData.setRposn(n);
|
||||||
|
tranData.setRpose(e);
|
||||||
|
tranData.setRposu(d);
|
||||||
|
String json = GsonUtil.toJson(tranData);
|
||||||
|
String cryptJson = Des3Utils.encrypt(json,key,iv);
|
||||||
|
logger.info("forward to ZNY mqtt: {}, {}",json, cryptJson);
|
||||||
|
String topic = "/GnssData/"+locationRecord.getDeviceid()+"/Increment";
|
||||||
|
try {
|
||||||
|
if(!mqttClient.publish(topic, cryptJson)) break;
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (Exception e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
sendNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -60,3 +60,9 @@ gxjk.mqtt.server.clientid = GXJK_client
|
|||||||
sass.server.host = 127.0.0.1
|
sass.server.host = 127.0.0.1
|
||||||
sass.server.port = 9933
|
sass.server.port = 9933
|
||||||
sass.server.enabled = false
|
sass.server.enabled = false
|
||||||
|
|
||||||
|
zny.mqtt.brokerUrl=tcp://42.194.196.91:51883
|
||||||
|
zny.mqtt.username=testuser
|
||||||
|
zny.mqtt.password=Test@20b39#
|
||||||
|
zny.mqtt.clientid = ZNY_client
|
||||||
|
zny.mqtt.topic = /GnssData/testgnss/Increment
|
||||||
|
|||||||
@ -0,0 +1,179 @@
|
|||||||
|
package com.imdroid.sideslope.bd;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTCM 1005 message generation
|
||||||
|
* Converted from rtcm1005.c
|
||||||
|
*/
|
||||||
|
public class Rtcm1005 {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(Rtcm1005.class);
|
||||||
|
private static final int MAXBUFF = 1024;
|
||||||
|
|
||||||
|
static class RTCM {
|
||||||
|
byte[] buff = new byte[MAXBUFF]; // 消息缓冲区
|
||||||
|
int nbit; // 缓冲区中的位数
|
||||||
|
int staid; // 站点ID
|
||||||
|
double[] sta_pos = new double[3]; // 站点位置(ECEF)(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置无符号位
|
||||||
|
* @param buff 缓冲区
|
||||||
|
* @param pos 起始位置
|
||||||
|
* @param len 位长度
|
||||||
|
* @param data 要设置的数据
|
||||||
|
*/
|
||||||
|
private static void setbitu(byte[] buff, int pos, int len, long data) {
|
||||||
|
long mask = 1L << (len - 1);
|
||||||
|
|
||||||
|
if (len <= 0 || 32 < len) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++, mask >>= 1) {
|
||||||
|
if ((data & mask) != 0)
|
||||||
|
buff[pos / 8] |= (byte)(0x80 >> (pos % 8));
|
||||||
|
else
|
||||||
|
buff[pos / 8] &= (byte)(~(0x80 >> (pos % 8)));
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置有符号38位字段
|
||||||
|
* @param buff 缓冲区
|
||||||
|
* @param pos 起始位置
|
||||||
|
* @param value 要设置的值
|
||||||
|
*/
|
||||||
|
private static void set38bits(byte[] buff, int pos, double value) {
|
||||||
|
long word = Math.round(value);
|
||||||
|
int word_h = (int)(word >> 32);
|
||||||
|
int word_l = (int)(word & 0xFFFFFFFFL);
|
||||||
|
|
||||||
|
setbitu(buff, pos, 6, word_h);
|
||||||
|
setbitu(buff, pos + 6, 32, word_l);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算CRC24Q校验和
|
||||||
|
* @param buff 缓冲区
|
||||||
|
* @param len 长度
|
||||||
|
* @return 校验和
|
||||||
|
*/
|
||||||
|
private static int crc24q(byte[] buff, int len) {
|
||||||
|
int crc = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
crc ^= (buff[i] & 0xFF) << 16;
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
crc <<= 1;
|
||||||
|
if ((crc & 0x1000000) != 0) crc ^= 0x1864CFB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc & 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成RTCM 1005消息
|
||||||
|
* @param rtcm RTCM控制结构
|
||||||
|
* @param sync 同步标志(1:同步,0:异步)
|
||||||
|
* @return 状态(1:成功,0:错误)
|
||||||
|
*/
|
||||||
|
private static int encode_type1005(RTCM rtcm, int sync) {
|
||||||
|
double[] p = rtcm.sta_pos;
|
||||||
|
int i = 0;
|
||||||
|
int crc;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
// RTCM消息头
|
||||||
|
setbitu(rtcm.buff, i, 8, 0xD3); i += 8; // 前导码
|
||||||
|
setbitu(rtcm.buff, i, 6, 0); i += 6; // 保留位
|
||||||
|
setbitu(rtcm.buff, i, 10, 19); i += 10; // 消息长度
|
||||||
|
setbitu(rtcm.buff, i, 12, 1005); i += 12; // 消息号
|
||||||
|
setbitu(rtcm.buff, i, 12, rtcm.staid); i += 12; // 参考站ID
|
||||||
|
setbitu(rtcm.buff, i, 6, 0); i += 6; // ITRF参考年份
|
||||||
|
setbitu(rtcm.buff, i, 1, 1); i += 1; // GPS指示器
|
||||||
|
setbitu(rtcm.buff, i, 1, 1); i += 1; // GLONASS指示器
|
||||||
|
setbitu(rtcm.buff, i, 1, 0); i += 1; // Galileo指示器
|
||||||
|
setbitu(rtcm.buff, i, 1, 0); i += 1; // 参考站指示器
|
||||||
|
|
||||||
|
// 站点坐标
|
||||||
|
set38bits(rtcm.buff, i, p[0] / 0.0001); i += 38; // 天线参考点ECEF-X
|
||||||
|
setbitu(rtcm.buff, i, 1, 1); i += 1; // 振荡器指示器
|
||||||
|
setbitu(rtcm.buff, i, 1, 0); i += 1; // 保留位
|
||||||
|
set38bits(rtcm.buff, i, p[1] / 0.0001); i += 38; // 天线参考点ECEF-Y
|
||||||
|
setbitu(rtcm.buff, i, 2, 0); i += 2; // 四分之一周期指示器
|
||||||
|
set38bits(rtcm.buff, i, p[2] / 0.0001); i += 38; // 天线参考点ECEF-Z
|
||||||
|
|
||||||
|
// 添加CRC24Q
|
||||||
|
len = i / 8;
|
||||||
|
crc = crc24q(rtcm.buff, len);
|
||||||
|
setbitu(rtcm.buff, i, 24, crc); i += 24;
|
||||||
|
|
||||||
|
rtcm.nbit = i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从ECEF坐标生成RTCM 1005消息
|
||||||
|
* @param ecef ECEF坐标(m) [x,y,z]
|
||||||
|
* @param staid 站点ID
|
||||||
|
* @param buff 输出缓冲区
|
||||||
|
* @return 输出缓冲区长度(字节),失败返回-1
|
||||||
|
*/
|
||||||
|
public static int gen_rtcm1005(double[] ecef, int staid, byte[] buff) {
|
||||||
|
RTCM rtcm = new RTCM();
|
||||||
|
|
||||||
|
// 检查输入
|
||||||
|
if (ecef == null || buff == null) return -1;
|
||||||
|
|
||||||
|
// 设置站点信息
|
||||||
|
rtcm.staid = staid;
|
||||||
|
System.arraycopy(ecef, 0, rtcm.sta_pos, 0, 3);
|
||||||
|
|
||||||
|
// 生成消息
|
||||||
|
int ret = encode_type1005(rtcm, 0);
|
||||||
|
if (ret == 0) return -1;
|
||||||
|
|
||||||
|
// 复制消息到输出缓冲区
|
||||||
|
int len = (rtcm.nbit + 7) / 8;
|
||||||
|
System.arraycopy(rtcm.buff, 0, buff, 0, len);
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字节数组转换为十六进制字符串
|
||||||
|
* @param bytes 字节数组
|
||||||
|
* @param len 长度
|
||||||
|
* @param upperCase 是否大写
|
||||||
|
* @param addSpaces 是否添加空格
|
||||||
|
* @return 十六进制字符串
|
||||||
|
*/
|
||||||
|
public static String bytesToHex(byte[] bytes, int len, boolean upperCase, boolean addSpaces) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String format = upperCase ? "%02X" : "%02x";
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
sb.append(String.format(format, bytes[i] & 0xFF));
|
||||||
|
if (addSpaces && i < len - 1) sb.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成RTCM 1005消息并返回十六进制字符串
|
||||||
|
* @param ecef ECEF坐标(m) [x,y,z]
|
||||||
|
* @param staid 站点ID
|
||||||
|
* @return RTCM 1005消息的十六进制字符串,失败返回null
|
||||||
|
*/
|
||||||
|
public static String generateRtcm1005Hex(double[] ecef, int staid) {
|
||||||
|
byte[] buff = new byte[MAXBUFF];
|
||||||
|
int len = gen_rtcm1005(ecef, staid, buff);
|
||||||
|
if (len > 0) {
|
||||||
|
return bytesToHex(buff, len, false, false);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import com.imdroid.secapi.client.BeidouClient;
|
|||||||
import com.imdroid.secapi.dto.GnssDevice;
|
import com.imdroid.secapi.dto.GnssDevice;
|
||||||
import com.imdroid.common.util.ByteUtil;
|
import com.imdroid.common.util.ByteUtil;
|
||||||
import com.imdroid.sideslope.bd.Gga;
|
import com.imdroid.sideslope.bd.Gga;
|
||||||
|
import com.imdroid.sideslope.bd.Rtcm1005;
|
||||||
import com.imdroid.sideslope.message.D331RtcmMessage;
|
import com.imdroid.sideslope.message.D331RtcmMessage;
|
||||||
import com.imdroid.sideslope.ntrip.UdpNtripServer;
|
import com.imdroid.sideslope.ntrip.UdpNtripServer;
|
||||||
import com.imdroid.sideslope.service.Device;
|
import com.imdroid.sideslope.service.Device;
|
||||||
@ -20,10 +21,12 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Layton
|
* @author Layton
|
||||||
@ -34,6 +37,15 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
// 基站状态管理
|
||||||
|
private static final Map<String, BaseStationStatus> baseStationStatusMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 测站当前使用的基站映射
|
||||||
|
private static final Map<String, String> deviceCurrentBaseMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 基站切换监听器列表
|
||||||
|
private static final List<BaseStationSwitchListener> switchListeners = new ArrayList<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceService deviceService;
|
private DeviceService deviceService;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -43,77 +55,527 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
@Autowired
|
@Autowired
|
||||||
UdpNtripServer ntripServer;
|
UdpNtripServer ntripServer;
|
||||||
|
|
||||||
|
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
|
||||||
|
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
|
||||||
|
private static final long D300_FORWARD_INTERVAL = 5000; // 5秒,单位毫秒
|
||||||
|
|
||||||
|
private static final Map<String, Long> deviceLastTime = new ConcurrentHashMap<>();
|
||||||
|
private static final long LOG_INTERVAL = 10 * 60 * 1000L;
|
||||||
|
|
||||||
|
// 基站在线检查间隔 - 修改为秒
|
||||||
|
private static final long BASE_STATION_CHECK_INTERVAL = 30; // 30秒
|
||||||
|
private static final long BASE_STATION_OFFLINE_TIMEOUT = 5 * 60; // 5分钟,单位:秒
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基站状态枚举
|
||||||
|
*/
|
||||||
|
public enum BaseStationStatusEnum {
|
||||||
|
ONLINE,
|
||||||
|
OFFLINE,
|
||||||
|
SWITCHING
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基站状态信息
|
||||||
|
*/
|
||||||
|
public static class BaseStationStatus {
|
||||||
|
private String baseStationId;
|
||||||
|
private BaseStationStatusEnum status;
|
||||||
|
private LocalDateTime lastActiveTime;
|
||||||
|
private LocalDateTime statusChangeTime;
|
||||||
|
private Set<String> servingDevices; // 当前服务的测站列表
|
||||||
|
|
||||||
|
public BaseStationStatus(String baseStationId) {
|
||||||
|
this.baseStationId = baseStationId;
|
||||||
|
this.status = BaseStationStatusEnum.OFFLINE;
|
||||||
|
this.lastActiveTime = LocalDateTime.now();
|
||||||
|
this.statusChangeTime = LocalDateTime.now();
|
||||||
|
this.servingDevices = ConcurrentHashMap.newKeySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getBaseStationId() { return baseStationId; }
|
||||||
|
public BaseStationStatusEnum getStatus() { return status; }
|
||||||
|
public void setStatus(BaseStationStatusEnum status) {
|
||||||
|
if (this.status != status) {
|
||||||
|
this.status = status;
|
||||||
|
this.statusChangeTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public LocalDateTime getLastActiveTime() { return lastActiveTime; }
|
||||||
|
public void setLastActiveTime(LocalDateTime lastActiveTime) { this.lastActiveTime = lastActiveTime; }
|
||||||
|
public LocalDateTime getStatusChangeTime() { return statusChangeTime; }
|
||||||
|
public Set<String> getServingDevices() { return servingDevices; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基站切换监听器接口
|
||||||
|
*/
|
||||||
|
public interface BaseStationSwitchListener {
|
||||||
|
void onBaseStationSwitch(String deviceId, String fromBaseId, String toBaseId, String reason);
|
||||||
|
void onBaseStationStatusChange(String baseStationId, BaseStationStatusEnum oldStatus, BaseStationStatusEnum newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的基站切换监听器实现
|
||||||
|
*/
|
||||||
|
private class DefaultBaseStationSwitchListener implements BaseStationSwitchListener {
|
||||||
|
@Override
|
||||||
|
public void onBaseStationSwitch(String deviceId, String fromBaseId, String toBaseId, String reason) {
|
||||||
|
logger.info("Base station switch - Device: {}, From: {} To: {}, Reason: {}",
|
||||||
|
deviceId, fromBaseId, toBaseId, reason);
|
||||||
|
|
||||||
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
|
try {
|
||||||
|
notifyBaseStationSwitch(deviceId, fromBaseId, toBaseId, reason);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to send base station switch notification", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBaseStationStatusChange(String baseStationId, BaseStationStatusEnum oldStatus, BaseStationStatusEnum newStatus) {
|
||||||
|
logger.info("Base station status change - BaseStation: {}, From: {} To: {}",
|
||||||
|
baseStationId, oldStatus, newStatus);
|
||||||
|
|
||||||
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
|
try {
|
||||||
|
notifyBaseStationStatusChange(baseStationId, oldStatus, newStatus);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to send base station status notification", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化默认监听器
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
switchListeners.add(new DefaultBaseStationSwitchListener());
|
||||||
|
logger.info("D331RtcmMessageExecutor initialized");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void execute(D331RtcmMessage message) {
|
public Void execute(D331RtcmMessage message) {
|
||||||
String id = message.getId();
|
String id = message.getId();
|
||||||
|
|
||||||
|
// 更新基站状态
|
||||||
|
updateBaseStationStatus(id);
|
||||||
|
|
||||||
// 补齐tenantId
|
// 补齐tenantId
|
||||||
Device deviceBs = deviceService.findByDeviceId(id);
|
Device deviceBs = deviceService.findByDeviceId(id);
|
||||||
if(deviceBs == null || deviceBs.getOpMode() == GnssDevice.OP_MODE_UNUSE) return null;
|
if(deviceBs == null || deviceBs.getOpMode() == GnssDevice.OP_MODE_UNUSE || deviceBs.getOpMode() == GnssDevice.OP_MODE_CHECK) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 推送基站数据
|
// 推送基站数据
|
||||||
if(deviceBs.getOpMode() == GnssDevice.OP_MODE_USE) {
|
if(deviceBs.getOpMode() == GnssDevice.OP_MODE_USE) {
|
||||||
byte[] forwardBytes = message.getSrcData();
|
byte[] forwardBytes = message.getSrcData();
|
||||||
// 要求快速转发,因此用缓存,不要每次都查数据库
|
|
||||||
List<Device> deviceList = deviceService.findByParentId(id);
|
// 获取使用该基站(包括作为主基站和备选基站)的所有测站
|
||||||
//logger.debug("base station {} has {} rovers: ", message.getId(),deviceList.size());
|
List<Device> primaryDevices = deviceService.findByParentId(id);
|
||||||
|
List<Device> backupDevices = deviceService.findByParentId1(id);
|
||||||
|
|
||||||
|
// 合并两个列表
|
||||||
|
List<Device> allDevices = new ArrayList<>();
|
||||||
|
allDevices.addAll(primaryDevices);
|
||||||
|
allDevices.addAll(backupDevices);
|
||||||
|
|
||||||
DeviceChannel deviceChannel = null;
|
DeviceChannel deviceChannel = null;
|
||||||
for (Device device : deviceList) {
|
for (Device device : allDevices) {
|
||||||
if (device.getOpMode() != GnssDevice.OP_MODE_USE) continue;
|
if (device.getOpMode() != GnssDevice.OP_MODE_USE) continue;
|
||||||
if (device.getFixedNum()>0 && device.getGnssSampleRate()>1
|
if ((device.getModel()==GnssDevice.MODEL_G510) &&
|
||||||
|
(device.getFixedNum()>100) && (device.getGnssSampleRate()>1)
|
||||||
&& (deviceBs.getD3xxCount()%device.getGnssSampleRate()) != 0) {
|
&& (deviceBs.getD3xxCount()%device.getGnssSampleRate()) != 0) {
|
||||||
//if(!UBXUtil.has1005(forwardBytes)) continue; //1005必推
|
//if(!UBXUtil.has1005(forwardBytes)) continue; //1005必推
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String deviceId = device.getDeviceId();
|
String deviceId = device.getDeviceId();
|
||||||
|
|
||||||
|
// 智能基站选择和切换
|
||||||
|
String selectedBaseId = selectOptimalBaseStation(device);
|
||||||
|
|
||||||
|
if (selectedBaseId == null || !selectedBaseId.equals(id)) {
|
||||||
|
continue; // 当前基站不是该设备的最优选择
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证兼容性(双重检查)
|
||||||
|
if (!isBaseStationCompatibleWithDevice(selectedBaseId, device)) {
|
||||||
|
logger.warn("Base station {} selected but not compatible with device {}, skipping",
|
||||||
|
selectedBaseId, deviceId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取要转发的数据(可能需要修改基站ID)
|
||||||
|
byte[] deviceForwardBytes = prepareForwardData(forwardBytes, device, selectedBaseId);
|
||||||
|
|
||||||
|
// 获取设备通道并发送数据
|
||||||
if(device.getDataChannelType() == Device.CHANNEL_TYPE_UDP) {
|
if(device.getDataChannelType() == Device.CHANNEL_TYPE_UDP) {
|
||||||
deviceChannel = OnlineChannels.INSTANCE.getDataChannel(deviceId);
|
deviceChannel = OnlineChannels.INSTANCE.getDataChannel(deviceId);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
deviceChannel = OnlineChannels.INSTANCE.getConfigChannel(deviceId);
|
deviceChannel = OnlineChannels.INSTANCE.getConfigChannel(deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(deviceChannel!=null && deviceChannel.isOnline()){
|
// 根据基站和设备类型处理数据转发
|
||||||
if (logger.isDebugEnabled()) {
|
if (shouldForwardData(deviceBs, device)) {
|
||||||
logger.debug("forward d331 rtcm to device {}", deviceId);
|
forwardDataToDevice(deviceChannel, deviceForwardBytes, device, deviceBs, id, deviceId);
|
||||||
}
|
|
||||||
if (deviceId.startsWith("2307")) {
|
|
||||||
forwardBytes[2] = (byte) (forwardBytes[2] & 0x07);//兼容不带序号的测站
|
|
||||||
}
|
|
||||||
ByteBuf buf = Unpooled.buffer();
|
|
||||||
buf.writeBytes(forwardBytes);
|
|
||||||
deviceChannel.writeAndFlush(buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果30分钟内收到不到d3f0和d3f2,则根据UDP最后一个报文触发状态更新和统计
|
// 处理设备状态和统计信息
|
||||||
if(deviceBs.getD3xxbytes()>0){
|
processDeviceStatistics(deviceBs, message);
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
if(deviceBs.getLastRxTime().isBefore(now.minusMinutes(1)) &&
|
|
||||||
(deviceBs.getLastD3f2Time() == null ||
|
|
||||||
deviceBs.getLastD3f2Time().isBefore(now.minusMinutes(30)))) {
|
|
||||||
// new cycle
|
|
||||||
logger.info("device {} rx {} d331 in a cycle while not d3f0f2",deviceBs.getDeviceId(),deviceBs.getD3xxCount());
|
|
||||||
|
|
||||||
Device lastCycleDevice = new Device();
|
// 添加NTRIP处理
|
||||||
lastCycleDevice.setDeviceId(deviceBs.getDeviceId());
|
byte[] srcdata = message.getSrcData();
|
||||||
lastCycleDevice.setDeviceType(deviceBs.getDeviceType());
|
String rtcm = ByteUtil.bytesToHexString(srcdata);
|
||||||
lastCycleDevice.setTenantId(deviceBs.getTenantId());
|
sendToNtrip(id, rtcm);
|
||||||
lastCycleDevice.setD341bytes(deviceBs.getD341bytes());
|
|
||||||
lastCycleDevice.setD341Count(deviceBs.getD341Count());
|
// 日志记录
|
||||||
lastCycleDevice.setFixedNum(deviceBs.getFixedNum());
|
logMessageIfNeeded(deviceBs, message);
|
||||||
lastCycleDevice.setFloatNum(deviceBs.getFloatNum());
|
|
||||||
lastCycleDevice.setNoFixedAndFloatResult(deviceBs.getNoFixedAndFloatResult());
|
return null;
|
||||||
lastCycleDevice.setLastRxTime(deviceBs.getLastRxTime());
|
}
|
||||||
lastCycleDevice.setLastValidCalcDataTime(deviceBs.getLastValidCalcDataTime());
|
|
||||||
lastCycleDevice.setSatelitesInUse(deviceBs.getSatelitesInUse());
|
/**
|
||||||
|
* 更新基站状态
|
||||||
|
*/
|
||||||
|
private void updateBaseStationStatus(String baseStationId) {
|
||||||
|
BaseStationStatus status = baseStationStatusMap.computeIfAbsent(baseStationId,
|
||||||
|
k -> new BaseStationStatus(baseStationId));
|
||||||
|
|
||||||
|
BaseStationStatusEnum oldStatus = status.getStatus();
|
||||||
|
status.setLastActiveTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
if (oldStatus == BaseStationStatusEnum.OFFLINE) {
|
||||||
|
status.setStatus(BaseStationStatusEnum.ONLINE);
|
||||||
|
logger.info("Base station {} changed status from {} to {}", baseStationId, oldStatus, BaseStationStatusEnum.ONLINE);
|
||||||
|
notifyStatusChange(baseStationId, oldStatus, BaseStationStatusEnum.ONLINE);
|
||||||
|
|
||||||
|
// 检查是否有设备需要切换回主基站
|
||||||
|
checkAndSwitchBackToPrimary(baseStationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并切换回主基站
|
||||||
|
*/
|
||||||
|
private void checkAndSwitchBackToPrimary(String primaryBaseId) {
|
||||||
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
|
try {
|
||||||
|
List<Device> devicesUsingPrimary = deviceService.findByParentId(primaryBaseId);
|
||||||
|
|
||||||
|
for (Device device : devicesUsingPrimary) {
|
||||||
|
String currentBase = deviceCurrentBaseMap.get(device.getDeviceId());
|
||||||
|
|
||||||
|
if (currentBase != null && !currentBase.equals(primaryBaseId)) {
|
||||||
|
// 设备当前使用备用基站,需要切换回主基站
|
||||||
|
logger.info("Switching device {} back to primary base station {} from {}",
|
||||||
|
device.getDeviceId(), primaryBaseId, currentBase);
|
||||||
|
switchDeviceToBaseStation(device.getDeviceId(), primaryBaseId,
|
||||||
|
"Primary base station back online");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error checking devices for primary base station switch back", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能选择最优基站
|
||||||
|
*/
|
||||||
|
private String selectOptimalBaseStation(Device device) {
|
||||||
|
String primaryBaseId = device.getParentId();
|
||||||
|
String backupBaseId = device.getParentId1();
|
||||||
|
String deviceId = device.getDeviceId();
|
||||||
|
|
||||||
|
// 获取当前使用的基站
|
||||||
|
String currentBaseId = deviceCurrentBaseMap.get(deviceId);
|
||||||
|
|
||||||
|
// 获取所有候选基站,按优先级排序
|
||||||
|
List<String> candidateBases = getCandidateBaseStations(device);
|
||||||
|
|
||||||
|
for (String baseId : candidateBases) {
|
||||||
|
// 检查基站是否在线
|
||||||
|
boolean isOnline = isBaseStationOnline(baseId);
|
||||||
|
|
||||||
|
if (!isOnline) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查基站与设备的兼容性
|
||||||
|
if (!isBaseStationCompatibleWithDevice(baseId, device)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前基站发生变化,记录切换
|
||||||
|
if (currentBaseId != null && !currentBaseId.equals(baseId)) {
|
||||||
|
switchDeviceToBaseStation(deviceId, baseId,
|
||||||
|
String.format("Switch from %s to %s (priority/availability)", currentBaseId, baseId));
|
||||||
|
} else if (currentBaseId == null) {
|
||||||
|
// 首次连接
|
||||||
|
deviceCurrentBaseMap.put(deviceId, baseId);
|
||||||
|
BaseStationStatus status = baseStationStatusMap.get(baseId);
|
||||||
|
if (status != null) {
|
||||||
|
status.getServingDevices().add(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("No compatible and available base station found for device: {}", deviceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查基站与设备的兼容性
|
||||||
|
*/
|
||||||
|
private boolean isBaseStationCompatibleWithDevice(String baseStationId, Device device) {
|
||||||
|
// 获取基站设备信息
|
||||||
|
Device baseStation = deviceService.findByDeviceId(baseStationId);
|
||||||
|
if (baseStation == null) {
|
||||||
|
logger.warn("Base station {} not found", baseStationId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查基站是否处于可用状态
|
||||||
|
if (baseStation.getOpMode() != GnssDevice.OP_MODE_USE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用已有的兼容性判断逻辑
|
||||||
|
return shouldForwardData(baseStation, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取候选基站列表(按优先级排序)
|
||||||
|
*/
|
||||||
|
private List<String> getCandidateBaseStations(Device device) {
|
||||||
|
List<String> candidates = new ArrayList<>();
|
||||||
|
|
||||||
|
// 主基站优先级最高
|
||||||
|
if (device.getParentId() != null) {
|
||||||
|
candidates.add(device.getParentId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备用基站
|
||||||
|
if (device.getParentId1() != null) {
|
||||||
|
candidates.add(device.getParentId1());
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换设备到指定基站
|
||||||
|
*/
|
||||||
|
private void switchDeviceToBaseStation(String deviceId, String newBaseId, String reason) {
|
||||||
|
String oldBaseId = deviceCurrentBaseMap.get(deviceId);
|
||||||
|
|
||||||
|
logger.info("Executing base station switch for device {}: {} -> {}, Reason: {}",
|
||||||
|
deviceId, oldBaseId, newBaseId, reason);
|
||||||
|
|
||||||
|
// 更新映射
|
||||||
|
deviceCurrentBaseMap.put(deviceId, newBaseId);
|
||||||
|
|
||||||
|
// 更新基站服务列表
|
||||||
|
if (oldBaseId != null) {
|
||||||
|
BaseStationStatus oldStatus = baseStationStatusMap.get(oldBaseId);
|
||||||
|
if (oldStatus != null) {
|
||||||
|
oldStatus.getServingDevices().remove(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseStationStatus newStatus = baseStationStatusMap.get(newBaseId);
|
||||||
|
if (newStatus != null) {
|
||||||
|
newStatus.getServingDevices().add(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知监听器
|
||||||
|
for (BaseStationSwitchListener listener : switchListeners) {
|
||||||
|
try {
|
||||||
|
listener.onBaseStationSwitch(deviceId, oldBaseId, newBaseId, reason);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error notifying base station switch listener", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 准备转发数据(可能需要修改基站ID)
|
||||||
|
*/
|
||||||
|
private byte[] prepareForwardData(byte[] originalData, Device device, String selectedBaseId) {
|
||||||
|
String primaryBaseId = device.getParentId();
|
||||||
|
|
||||||
|
// 如果选择的基站不是主基站,需要修改RTCM数据中的基站ID
|
||||||
|
if (primaryBaseId != null && !selectedBaseId.equals(primaryBaseId)) {
|
||||||
|
try {
|
||||||
|
byte[] modifyData = originalData.clone();
|
||||||
|
// 修复:使用Long.parseLong而不是Integer.parseInt,因为基站ID可能很大
|
||||||
|
long primaryBaseIdLong = Long.parseLong(primaryBaseId);
|
||||||
|
String hexPrimaryBase = String.format("%06x", primaryBaseIdLong);
|
||||||
|
|
||||||
|
if (hexPrimaryBase.length() >= 6) {
|
||||||
|
modifyData[5] = (byte) Integer.parseInt(hexPrimaryBase.substring(0, 2), 16);
|
||||||
|
modifyData[6] = (byte) Integer.parseInt(hexPrimaryBase.substring(2, 4), 16);
|
||||||
|
modifyData[7] = (byte) Integer.parseInt(hexPrimaryBase.substring(4, 6), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifyData;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("Error parsing base station ID: {}", primaryBaseId, e);
|
||||||
|
return originalData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否应该转发数据
|
||||||
|
* 基站类型说明:
|
||||||
|
* - model = 1: f9p基站,所有设备都可以接收
|
||||||
|
* - model = 0: 博通基站,只有博通设备(model=0)和兼容设备(model=1)可以接收
|
||||||
|
*/
|
||||||
|
private boolean shouldForwardData(Device baseStation, Device device) {
|
||||||
|
Short baseStationModel = baseStation.getModel();
|
||||||
|
if (baseStationModel == null) {
|
||||||
|
baseStationModel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Short deviceModel = device.getModel();
|
||||||
|
if (deviceModel == null) {
|
||||||
|
deviceModel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseStationModel == 1) {
|
||||||
|
// f9p基站,所有设备都可以接收
|
||||||
|
return true;
|
||||||
|
} else if (baseStationModel == 0) {
|
||||||
|
// 博通基站,只支持特定设备
|
||||||
|
return (deviceModel == 0 || deviceModel == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发数据到设备
|
||||||
|
*/
|
||||||
|
private void forwardDataToDevice(DeviceChannel deviceChannel, byte[] forwardBytes,
|
||||||
|
Device device, Device deviceBs, String baseStationId, String deviceId) {
|
||||||
|
if (deviceChannel == null || !deviceChannel.isOnline()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Short deviceModel = device.getModel();
|
||||||
|
if (deviceModel == null) {
|
||||||
|
deviceModel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("forward d331 rtcm from {} to device {}", baseStationId, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
buf.writeBytes(forwardBytes);
|
||||||
|
|
||||||
|
// 特殊处理设备类型
|
||||||
|
if (deviceModel == 0 && deviceId.startsWith("2307")) {
|
||||||
|
// 处理2307型号的测站
|
||||||
|
byte[] modifiedData = buf.array();
|
||||||
|
modifiedData[2] = (byte) (modifiedData[2] & 0x07); // 兼容不带序号的测站
|
||||||
|
} else if (deviceModel == 1) {
|
||||||
|
// 处理类型1的设备,可能需要添加D300数据
|
||||||
|
buf = handleDeviceType1(buf, device, deviceBs, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceChannel.writeAndFlush(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理设备类型1的特殊逻辑
|
||||||
|
*/
|
||||||
|
private ByteBuf handleDeviceType1(ByteBuf buf, Device device, Device deviceBs, String deviceId) {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
Long lastForwardTime = lastD300ForwardTimeMap.getOrDefault(deviceId, 0L);
|
||||||
|
|
||||||
|
if (currentTime - lastForwardTime >= D300_FORWARD_INTERVAL) {
|
||||||
|
byte[] originalData = buf.array();
|
||||||
|
String originalHex = ByteUtil.bytesToHexString(originalData);
|
||||||
|
|
||||||
|
int d300Index = originalHex.indexOf("d300");
|
||||||
|
int d301Index = originalHex.indexOf("d301");
|
||||||
|
|
||||||
|
int insertIndex = -1;
|
||||||
|
if (d300Index != -1 && d301Index != -1) {
|
||||||
|
insertIndex = Math.min(d300Index, d301Index);
|
||||||
|
} else if (d300Index != -1) {
|
||||||
|
insertIndex = d300Index;
|
||||||
|
} else if (d301Index != -1) {
|
||||||
|
insertIndex = d301Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertIndex != -1) {
|
||||||
|
// 检查基站是否有有效的ECEF坐标
|
||||||
|
Double ecefX = deviceBs.getEcefx();
|
||||||
|
Double ecefY = deviceBs.getEcefy();
|
||||||
|
Double ecefZ = deviceBs.getEcefz();
|
||||||
|
|
||||||
|
// 验证坐标有效性
|
||||||
|
if (isValidEcefCoordinates(ecefX, ecefY, ecefZ)) {
|
||||||
|
ByteBuf newBuf = Unpooled.buffer();
|
||||||
|
newBuf.writeBytes(originalData, 0, insertIndex / 2);
|
||||||
|
|
||||||
|
double[] ecef = new double[3];
|
||||||
|
ecef[0] = ecefX.doubleValue();
|
||||||
|
ecef[1] = ecefY.doubleValue();
|
||||||
|
ecef[2] = ecefZ.doubleValue();
|
||||||
|
|
||||||
|
String rtcm1005 = Rtcm1005.generateRtcm1005Hex(ecef, 0);
|
||||||
|
if (rtcm1005 != null && !rtcm1005.isEmpty()) {
|
||||||
|
byte[] rtcm1005Bytes = ByteUtil.hexStringTobyte(rtcm1005);
|
||||||
|
newBuf.writeBytes(rtcm1005Bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
newBuf.writeBytes(originalData, insertIndex / 2, originalData.length - insertIndex / 2);
|
||||||
|
buf = newBuf;
|
||||||
|
} else {
|
||||||
|
logger.warn("Base station {} has invalid ECEF coordinates, skipping RTCM 1005 generation for device {}",
|
||||||
|
deviceBs.getDeviceId(), deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastD300ForwardTimeMap.put(deviceId, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理设备统计信息
|
||||||
|
*/
|
||||||
|
private void processDeviceStatistics(Device deviceBs, D331RtcmMessage message) {
|
||||||
|
if (deviceBs.getD3xxbytes() > 0) {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
if (deviceBs.getLastRxTime() != null &&
|
||||||
|
deviceBs.getLastRxTime().isBefore(now.minusMinutes(1)) &&
|
||||||
|
(deviceBs.getLastD3f2Time() == null ||
|
||||||
|
deviceBs.getLastD3f2Time().isBefore(now.minusMinutes(30)))) {
|
||||||
|
|
||||||
|
logger.info("device {} rx {} d331 in a cycle while not d3f0f2",
|
||||||
|
deviceBs.getDeviceId(), deviceBs.getD3xxCount());
|
||||||
|
|
||||||
|
Device lastCycleDevice = createLastCycleDevice(deviceBs);
|
||||||
deviceBs.clearStat();
|
deviceBs.clearStat();
|
||||||
|
|
||||||
ThreadManager.getFixedThreadPool().submit(() -> {
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
// 通知上线
|
|
||||||
try {
|
try {
|
||||||
beidouClient.onDeviceActive(deviceBs.getDeviceId(), deviceBs.getTenantId());
|
beidouClient.onDeviceActive(deviceBs.getDeviceId(), deviceBs.getTenantId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.toString());
|
logger.error("Error notifying device active", e);
|
||||||
}
|
}
|
||||||
dataPersistService.updateDeviceState(lastCycleDevice);
|
dataPersistService.updateDeviceState(lastCycleDevice);
|
||||||
});
|
});
|
||||||
@ -121,53 +583,231 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update trx
|
// update trx
|
||||||
deviceBs.updateRx(message.getHeader(),message.getLen(),message.getPacketNum());
|
deviceBs.updateRx(message.getHeader(), message.getLen(), message.getPacketNum());
|
||||||
|
|
||||||
// update gga
|
// update gga
|
||||||
Gga gga = message.getGga();
|
Gga gga = message.getGga();
|
||||||
if(gga != null) {
|
if (gga != null) {
|
||||||
deviceBs.updateSatelitesNum(gga.getSatellitesInUsed());
|
deviceBs.updateSatelitesNum(gga.getSatellitesInUsed());
|
||||||
|
// 修复:正确处理Double类型的坐标
|
||||||
deviceBs.setLatitude(gga.getLatitude());
|
deviceBs.setLatitude(gga.getLatitude());
|
||||||
deviceBs.setLongitude(gga.getLongitude());
|
deviceBs.setLongitude(gga.getLongitude());
|
||||||
deviceBs.setAltitude(gga.getAltitude());
|
deviceBs.setAltitude(gga.getAltitude());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加NTRIP处理
|
|
||||||
byte[] srcdata = message.getSrcData();
|
|
||||||
String rtcm = ByteUtil.bytesToHexString(srcdata);
|
|
||||||
sendToNtrip(id, rtcm);
|
|
||||||
|
|
||||||
ThreadManager.getFixedThreadPool().submit(() -> {
|
|
||||||
// 原始码流输出到日志文件 -- INFO 级别
|
|
||||||
// 只有测站开了日志记录,或者消息来自基站,才将原码记录到日志文件
|
|
||||||
if(deviceBs.getLoggingmode() == GnssDevice.LOGGING_MODE_FULL){
|
|
||||||
logger.info("receive {} d331 message: {}", message.getId(), DataTypeUtil.getHexString(message.getSrcData()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendToNtrip(String mountpoint, String hexData) {
|
/**
|
||||||
try {
|
* 创建上一周期设备信息
|
||||||
|
*/
|
||||||
|
private Device createLastCycleDevice(Device deviceBs) {
|
||||||
|
Device lastCycleDevice = new Device();
|
||||||
|
lastCycleDevice.setDeviceId(deviceBs.getDeviceId());
|
||||||
|
lastCycleDevice.setDeviceType(deviceBs.getDeviceType());
|
||||||
|
lastCycleDevice.setTenantId(deviceBs.getTenantId());
|
||||||
|
lastCycleDevice.setD341bytes(deviceBs.getD341bytes());
|
||||||
|
lastCycleDevice.setD341Count(deviceBs.getD341Count());
|
||||||
|
lastCycleDevice.setFixedNum(deviceBs.getFixedNum());
|
||||||
|
lastCycleDevice.setFloatNum(deviceBs.getFloatNum());
|
||||||
|
lastCycleDevice.setNoFixedAndFloatResult(deviceBs.getNoFixedAndFloatResult());
|
||||||
|
lastCycleDevice.setLastRxTime(deviceBs.getLastRxTime());
|
||||||
|
lastCycleDevice.setLastValidCalcDataTime(deviceBs.getLastValidCalcDataTime());
|
||||||
|
lastCycleDevice.setSatelitesInUse(deviceBs.getSatelitesInUse());
|
||||||
|
return lastCycleDevice;
|
||||||
|
}
|
||||||
|
|
||||||
// 将原始字节转换为16进制字符串用于RTCM提取
|
/**
|
||||||
//String hexData = ByteUtil.bytesToHexString(rawData);
|
* 记录日志(如果需要)
|
||||||
//System.out.println(hexData);
|
*/
|
||||||
|
private void logMessageIfNeeded(Device deviceBs, D331RtcmMessage message) {
|
||||||
// 提取RTCM数据并发送到NtripServer,使用设备ID作为挂载点
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
Optional.ofNullable(RtcmGgaUtil.getRtcms(hexData))
|
if (deviceBs.getLoggingmode() != null && deviceBs.getLoggingmode() == GnssDevice.LOGGING_MODE_FULL) {
|
||||||
.ifPresent(rtcm -> {
|
logger.info("receive {} d331 message: {}", message.getId(),
|
||||||
//System.out.println("挂载点: " + mountpoint);
|
DataTypeUtil.getHexString(message.getSrcData()));
|
||||||
//System.out.println("RTCM数据: " + rtcm);
|
}
|
||||||
ntripServer.send(mountpoint, rtcm);
|
});
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("处理NTRIP数据失败, 挂载点: {}, 错误: {}", mountpoint, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<?> getMessageType() {
|
public Class<?> getMessageType() {
|
||||||
return D331RtcmMessage.class;
|
return D331RtcmMessage.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送数据到NTRIP
|
||||||
|
*/
|
||||||
|
private void sendToNtrip(String mountpoint, String hexData) {
|
||||||
|
try {
|
||||||
|
Optional.ofNullable(RtcmGgaUtil.getRtcms(hexData))
|
||||||
|
.ifPresent(rtcm -> ntripServer.send(mountpoint, rtcm));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("处理NTRIP数据失败, 挂载点: {}, 错误: {}", mountpoint, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断基站是否在线
|
||||||
|
*/
|
||||||
|
private boolean isBaseStationOnline(String baseStationId) {
|
||||||
|
if (baseStationId == null) return false;
|
||||||
|
|
||||||
|
BaseStationStatus status = baseStationStatusMap.get(baseStationId);
|
||||||
|
if (status == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return status.getLastActiveTime() != null &&
|
||||||
|
status.getLastActiveTime().isAfter(now.minusSeconds(BASE_STATION_OFFLINE_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断基站设备是否在线(兼容原有方法)
|
||||||
|
*/
|
||||||
|
private boolean isBaseStationOnline(Device baseStation) {
|
||||||
|
if (baseStation == null) return false;
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return baseStation.getLastRxTime() != null &&
|
||||||
|
baseStation.getLastRxTime().isAfter(now.minusMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知状态变化
|
||||||
|
*/
|
||||||
|
private void notifyStatusChange(String baseStationId, BaseStationStatusEnum oldStatus, BaseStationStatusEnum newStatus) {
|
||||||
|
for (BaseStationSwitchListener listener : switchListeners) {
|
||||||
|
try {
|
||||||
|
listener.onBaseStationStatusChange(baseStationId, oldStatus, newStatus);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error notifying base station status change listener", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知基站切换(可扩展为调用外部服务)
|
||||||
|
*/
|
||||||
|
private void notifyBaseStationSwitch(String deviceId, String fromBaseId, String toBaseId, String reason) {
|
||||||
|
logger.info("Sending external notification for base station switch: Device={}, From={}, To={}, Reason={}",
|
||||||
|
deviceId, fromBaseId, toBaseId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知基站状态变化(可扩展为调用外部服务)
|
||||||
|
*/
|
||||||
|
private void notifyBaseStationStatusChange(String baseStationId, BaseStationStatusEnum oldStatus, BaseStationStatusEnum newStatus) {
|
||||||
|
logger.info("Sending external notification for base station status change: BaseStation={}, From={}, To={}",
|
||||||
|
baseStationId, oldStatus, newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期检查基站状态
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 30000) // 30秒执行一次
|
||||||
|
public void checkBaseStationStatus() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
for (BaseStationStatus status : baseStationStatusMap.values()) {
|
||||||
|
if (status.getStatus() == BaseStationStatusEnum.ONLINE) {
|
||||||
|
// 检查是否超时离线
|
||||||
|
if (status.getLastActiveTime().isBefore(now.minusSeconds(BASE_STATION_OFFLINE_TIMEOUT))) {
|
||||||
|
BaseStationStatusEnum oldStatus = status.getStatus();
|
||||||
|
status.setStatus(BaseStationStatusEnum.OFFLINE);
|
||||||
|
|
||||||
|
logger.warn("Base station {} went offline (timeout). Last active: {}",
|
||||||
|
status.getBaseStationId(), status.getLastActiveTime());
|
||||||
|
|
||||||
|
notifyStatusChange(status.getBaseStationId(), oldStatus, BaseStationStatusEnum.OFFLINE);
|
||||||
|
|
||||||
|
// 触发设备切换到备用基站
|
||||||
|
triggerDeviceSwitchToBackup(status.getBaseStationId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发设备切换到备用基站
|
||||||
|
*/
|
||||||
|
private void triggerDeviceSwitchToBackup(String offlineBaseId) {
|
||||||
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
|
try {
|
||||||
|
BaseStationStatus status = baseStationStatusMap.get(offlineBaseId);
|
||||||
|
if (status != null) {
|
||||||
|
Set<String> affectedDevices = new HashSet<>(status.getServingDevices());
|
||||||
|
|
||||||
|
for (String deviceId : affectedDevices) {
|
||||||
|
// 查找设备信息并触发切换
|
||||||
|
Device device = deviceService.findByDeviceId(deviceId);
|
||||||
|
if (device != null) {
|
||||||
|
String newBaseId = selectOptimalBaseStation(device);
|
||||||
|
if (newBaseId != null && !newBaseId.equals(offlineBaseId)) {
|
||||||
|
switchDeviceToBaseStation(deviceId, newBaseId,
|
||||||
|
"Primary base station offline: " + offlineBaseId);
|
||||||
|
} else {
|
||||||
|
logger.warn("No suitable backup base station found for device {} (was using {})",
|
||||||
|
deviceId, offlineBaseId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error triggering device switch to backup base station", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加基站切换监听器
|
||||||
|
*/
|
||||||
|
public static void addBaseStationSwitchListener(BaseStationSwitchListener listener) {
|
||||||
|
switchListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除基站切换监听器
|
||||||
|
*/
|
||||||
|
public static void removeBaseStationSwitchListener(BaseStationSwitchListener listener) {
|
||||||
|
switchListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取基站状态信息
|
||||||
|
*/
|
||||||
|
public static BaseStationStatus getBaseStationStatus(String baseStationId) {
|
||||||
|
return baseStationStatusMap.get(baseStationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有基站状态
|
||||||
|
*/
|
||||||
|
public static Map<String, BaseStationStatus> getAllBaseStationStatus() {
|
||||||
|
return new HashMap<>(baseStationStatusMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备当前使用的基站
|
||||||
|
*/
|
||||||
|
public static String getDeviceCurrentBase(String deviceId) {
|
||||||
|
return deviceCurrentBaseMap.get(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证ECEF坐标是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidEcefCoordinates(Double x, Double y, Double z) {
|
||||||
|
if (x == null || y == null || z == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为零坐标
|
||||||
|
if (x == 0.0 && y == 0.0 && z == 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查坐标是否在地球表面合理范围内(大致6.3M到6.4M米)
|
||||||
|
double distance = Math.sqrt(x * x + y * y + z * z);
|
||||||
|
return distance >= 6300000 && distance <= 6400000;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -32,6 +32,9 @@ import java.util.TimerTask;
|
|||||||
public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessage, Void> {
|
public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessage, Void> {
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
final String F9PInitCmd = "f9";
|
||||||
|
final String GNSSPowerOff = "25 00";
|
||||||
|
final String GNSSPowerOn = "25 01";
|
||||||
final String F9PColdStartCmd = "b56206040400000002001068";
|
final String F9PColdStartCmd = "b56206040400000002001068";
|
||||||
final String BTCloseMneaCmd = "424be8e1020a00040000ffff";
|
final String BTCloseMneaCmd = "424be8e1020a00040000ffff";
|
||||||
final String BTOpenGGACmd = "424b852c0208000400000001";
|
final String BTOpenGGACmd = "424b852c0208000400000001";
|
||||||
@ -81,6 +84,11 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
|
|||||||
checkAndResetBTGnss(device);
|
checkAndResetBTGnss(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
if(device.getModel() == GnssDevice.MODEL_G510){
|
||||||
|
checkAndResetBTBsGnss(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 通知beidou服务设备上线,这里会触发参数同步
|
// 通知beidou服务设备上线,这里会触发参数同步
|
||||||
GnssStatus lastGnssStatus = dataPersistService.getDeviceState(message.getId());
|
GnssStatus lastGnssStatus = dataPersistService.getDeviceState(message.getId());
|
||||||
try {
|
try {
|
||||||
@ -172,9 +180,18 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
|
|||||||
String sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len)+
|
String sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len)+
|
||||||
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceId()))+gnssCmd;
|
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceId()))+gnssCmd;
|
||||||
rtcmClient.config(device.getDeviceId(), sendCmd);
|
rtcmClient.config(device.getDeviceId(), sendCmd);
|
||||||
saveMsg(device.getDeviceId(), device.getTenantId(),0xD310, sendCmd, true);
|
saveMsg(device.getDeviceId(), device.getTenantId(),msgType, sendCmd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//example: d31300050080F2B3f9
|
||||||
|
void sendDebugCommand(Device device, String cmd){
|
||||||
|
int msgType = 0xD313;
|
||||||
|
short len = (short) (cmd.length()/2+4);
|
||||||
|
String sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len)+
|
||||||
|
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceId()))+cmd;
|
||||||
|
rtcmClient.config(device.getDeviceId(), sendCmd);
|
||||||
|
saveMsg(device.getDeviceId(), device.getTenantId(),msgType, sendCmd, true);
|
||||||
|
}
|
||||||
void sendD3F1Ack(Device device){
|
void sendD3F1Ack(Device device){
|
||||||
int flag = 0xD3F1;
|
int flag = 0xD3F1;
|
||||||
short len = (short) (4);
|
short len = (short) (4);
|
||||||
@ -196,14 +213,22 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkAndResetBTBsGnss(Device device){
|
||||||
|
if(device.getD341Count() < 50){
|
||||||
|
startBTBsResetTask(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void startF9PColdStartTask(Device device){
|
void startF9PColdStartTask(Device device){
|
||||||
Timer timer = new Timer();
|
Timer timer = new Timer();
|
||||||
timer.schedule(new TimerTask() {
|
for(int delay=1; delay<=10; delay++) {
|
||||||
@Override
|
timer.schedule(new TimerTask() {
|
||||||
public void run() {
|
@Override
|
||||||
sendGnssCommand(device, F9PColdStartCmd);
|
public void run() {
|
||||||
}
|
sendGnssCommand(device, F9PColdStartCmd);
|
||||||
},1000);
|
}
|
||||||
|
}, delay*1500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startBTHotStartTask(Device device){
|
void startBTHotStartTask(Device device){
|
||||||
@ -216,6 +241,22 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
|
|||||||
},1000);
|
},1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startBTBsResetTask(Device device){
|
||||||
|
Timer timer = new Timer();
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sendGnssCommand(device, GNSSPowerOff);
|
||||||
|
}
|
||||||
|
},1000);
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sendGnssCommand(device, GNSSPowerOn);
|
||||||
|
}
|
||||||
|
},11000);
|
||||||
|
}
|
||||||
|
|
||||||
void startBTResetTask(Device device){
|
void startBTResetTask(Device device){
|
||||||
Timer timer = new Timer();
|
Timer timer = new Timer();
|
||||||
timer.schedule(new TimerTask() {
|
timer.schedule(new TimerTask() {
|
||||||
@ -255,4 +296,6 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
|
|||||||
}
|
}
|
||||||
},16000);
|
},16000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,6 +90,8 @@ public class Device {
|
|||||||
byte cfgChannelType = CHANNEL_TYPE_TCP; // 0:TCP;1:DUP
|
byte cfgChannelType = CHANNEL_TYPE_TCP; // 0:TCP;1:DUP
|
||||||
byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP
|
byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP
|
||||||
|
|
||||||
|
private String parentId1;
|
||||||
|
|
||||||
public void updateRx(int head, int bytes,int count){
|
public void updateRx(int head, int bytes,int count){
|
||||||
lastRxHead = head;
|
lastRxHead = head;
|
||||||
|
|
||||||
|
|||||||
@ -11,4 +11,6 @@ public interface DeviceService {
|
|||||||
Device findByDeviceId(String deviceId);
|
Device findByDeviceId(String deviceId);
|
||||||
|
|
||||||
List<Device> findByParentId(String parentId);
|
List<Device> findByParentId(String parentId);
|
||||||
|
|
||||||
|
List<Device> findByParentId1(String parentId1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public class LocalDeviceServiceImpl implements DeviceService {
|
|||||||
device.setEcefy(gnssDevice.getEcefy());
|
device.setEcefy(gnssDevice.getEcefy());
|
||||||
device.setEcefz(gnssDevice.getEcefz());
|
device.setEcefz(gnssDevice.getEcefz());
|
||||||
device.setLoggingmode(gnssDevice.getLoggingmode());
|
device.setLoggingmode(gnssDevice.getLoggingmode());
|
||||||
|
device.setParentId1(gnssDevice.getParentid1());
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +66,19 @@ public class LocalDeviceServiceImpl implements DeviceService {
|
|||||||
}
|
}
|
||||||
return deviceList;
|
return deviceList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Device> getDeviceListFromDBByParentId1(String parentId1) {
|
||||||
|
QueryWrapper<GnssDevice> query = new QueryWrapper();
|
||||||
|
query.eq("parentid1", parentId1);
|
||||||
|
List<GnssDevice> gnssDeviceList = gnssDeviceMapper.selectList(query);
|
||||||
|
List<Device> deviceList = new ArrayList<>(gnssDeviceList.size());
|
||||||
|
for (GnssDevice gnssDevice : gnssDeviceList) {
|
||||||
|
Device device = findByDeviceId(gnssDevice.getDeviceid());
|
||||||
|
if(device!=null) deviceList.add(device);
|
||||||
|
}
|
||||||
|
return deviceList;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Device findByDeviceId(String deviceId) {
|
public Device findByDeviceId(String deviceId) {
|
||||||
Device device = deviceCache.getIfPresent(deviceId);
|
Device device = deviceCache.getIfPresent(deviceId);
|
||||||
@ -79,23 +93,43 @@ public class LocalDeviceServiceImpl implements DeviceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Device> findByParentId(String parentId) {
|
public List<Device> findByParentId(String parentId) {
|
||||||
List<Device> devices = subDeviceCache.getIfPresent(parentId);
|
String cacheKey = "PARENTID_" + parentId;
|
||||||
|
List<Device> devices = subDeviceCache.getIfPresent(cacheKey);
|
||||||
if (devices == null) {
|
if (devices == null) {
|
||||||
devices = getDeviceListFromDBByParentId(parentId);
|
devices = getDeviceListFromDBByParentId(parentId);
|
||||||
if (devices != null) {
|
if (devices != null) {
|
||||||
subDeviceCache.put(parentId, devices);
|
subDeviceCache.put(cacheKey, devices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(String deviceId, String oldParentId){
|
@Override
|
||||||
|
public List<Device> findByParentId1(String parentId1) {
|
||||||
|
String cacheKey = "PARENTID1_" + parentId1;
|
||||||
|
List<Device> devices = subDeviceCache.getIfPresent(cacheKey);
|
||||||
|
if (devices == null) {
|
||||||
|
devices = getDeviceListFromDBByParentId1(parentId1);
|
||||||
|
if (devices != null) {
|
||||||
|
subDeviceCache.put(cacheKey, devices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh(String deviceId, String oldParentId, String oldParentId1){
|
||||||
Device device = deviceCache.getIfPresent(deviceId);
|
Device device = deviceCache.getIfPresent(deviceId);
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
if(device.getParentId()!=null) {
|
if(device.getParentId()!=null) {
|
||||||
subDeviceCache.invalidate(device.getParentId());
|
subDeviceCache.invalidate("PARENTID_" + device.getParentId());
|
||||||
if(oldParentId!=null && !oldParentId.equals(device.getParentId())){
|
if(oldParentId!=null && !oldParentId.equals(device.getParentId())){
|
||||||
subDeviceCache.invalidate(oldParentId);
|
subDeviceCache.invalidate("PARENTID_" + oldParentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(device.getParentId1()!=null) {
|
||||||
|
subDeviceCache.invalidate("PARENTID1_" + device.getParentId1());
|
||||||
|
if(oldParentId1!=null && !oldParentId1.equals(device.getParentId1())){
|
||||||
|
subDeviceCache.invalidate("PARENTID1_" + oldParentId1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deviceCache.invalidate(deviceId);
|
deviceCache.invalidate(deviceId);
|
||||||
|
|||||||
@ -154,9 +154,9 @@ public class ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/device_param_changed")
|
@PostMapping("/device_param_changed")
|
||||||
public HttpResp deviceParamChanged(String deviceId, String oldParentId) {
|
public HttpResp deviceParamChanged(String deviceId, String oldParentId, String oldParentId1) {
|
||||||
// 更新设备缓存
|
// 更新设备缓存
|
||||||
localDeviceService.refresh(deviceId, oldParentId);
|
localDeviceService.refresh(deviceId, oldParentId, oldParentId1);
|
||||||
calcService.refreshDeviceCalc(deviceId);
|
calcService.refreshDeviceCalc(deviceId);
|
||||||
|
|
||||||
HttpResp resp = new HttpResp();
|
HttpResp resp = new HttpResp();
|
||||||
|
|||||||
@ -12,10 +12,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class APIController extends BasicController{
|
public class APIController extends BasicController {
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
|
||||||
|
final String verServerConnectCmd = "AT+QIOPEN=1,2,\"TCP\",\"iot.im-droid.com\",7665,0,1";
|
||||||
@Autowired
|
@Autowired
|
||||||
RtcmClient rtcmClient;
|
RtcmClient rtcmClient;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -30,58 +32,63 @@ public class APIController extends BasicController{
|
|||||||
DeviceCacheCmdMapper cacheCmdMapper;
|
DeviceCacheCmdMapper cacheCmdMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
WarningMsgMapper warningMsgMapper;
|
WarningMsgMapper warningMsgMapper;
|
||||||
|
@Autowired
|
||||||
|
GnssStatusMapper gnssStatusMapper;
|
||||||
|
|
||||||
/****** config ack *******/
|
/****** config ack *******/
|
||||||
@PostMapping(value = "/api/config_ack")
|
@PostMapping(value = "/api/config_ack")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String onConfigAck(String deviceId, Integer tenantId, String configAck) {
|
public String onConfigAck(String deviceId, Integer tenantId, String configAck) {
|
||||||
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
||||||
if(device == null) return null;
|
if (device == null) return null;
|
||||||
|
|
||||||
int msgType = Integer.parseInt(configAck.substring(0,4),16);
|
int msgType = Integer.parseInt(configAck.substring(0, 4), 16);
|
||||||
// 配置是否成功
|
// 配置是否成功
|
||||||
if(msgType == 0xd311){
|
if (msgType == 0xd311) {
|
||||||
//最后一个字节为1表示配置成功,0表示配置失败
|
//最后一个字节为1表示配置成功,0表示配置失败
|
||||||
if(configAck.endsWith("01")){
|
if (configAck.endsWith("01")) {
|
||||||
device.setSyn(true);
|
device.setSyn(true);
|
||||||
deviceMapper.updateById(device);
|
deviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
} else if (msgType == 0xd312) {
|
||||||
else if(msgType == 0xd312){
|
|
||||||
// 工作周期一致性检查
|
// 工作周期一致性检查
|
||||||
checkWorkCycle(device, configAck);
|
checkWorkCycle(device, configAck);
|
||||||
|
} else if (msgType == 0xd313) {
|
||||||
|
if(device.getChange_flag()!=0) {
|
||||||
|
if(device.clearChangeFlag(configAck)){
|
||||||
|
deviceMapper.updateById(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存, debug 01 02上来的原始数据不保存
|
// 保存, debug 01 02上来的原始数据不保存
|
||||||
if(msgType != 0xd313 || configAck.length()<100) {
|
if (msgType != 0xd313 || configAck.length() < 100) {
|
||||||
saveMsg(deviceId, tenantId, msgType, configAck, false);
|
saveMsg(deviceId, tenantId, msgType, configAck, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 命令行显示
|
// 命令行显示
|
||||||
String rxInfo = "RX "+ dateFormat.format(System.currentTimeMillis())+
|
String rxInfo = "RX " + dateFormat.format(System.currentTimeMillis()) +
|
||||||
" "+deviceId+" ";
|
" " + deviceId + " ";
|
||||||
if(msgType == 0xd31a || msgType == 0xd31b){
|
if (msgType == 0xd31a || msgType == 0xd31b) {
|
||||||
//转成字符串
|
//转成字符串
|
||||||
String dtuAck = configAck.substring(9*2);
|
String dtuAck = configAck.substring(9 * 2);
|
||||||
rxInfo += configAck+"("+HexUtil.HexString2String(dtuAck)+")";
|
rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")";
|
||||||
|
|
||||||
// 检查是否需要更新 ICCID
|
// 检查是否需要更新 ICCID
|
||||||
updateICCID(device,dtuAck);
|
updateICCID(device, dtuAck);
|
||||||
}
|
} else if (msgType == 0xd313 && configAck.length() >= 90) {
|
||||||
else if(msgType == 0xd313&&configAck.length()>=90){
|
|
||||||
//转成字符串
|
//转成字符串
|
||||||
String dtuAck = configAck.substring(26*2);
|
String dtuAck = configAck.substring(26 * 2);
|
||||||
rxInfo += configAck+"("+HexUtil.HexString2String(dtuAck)+")";
|
rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")";
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
rxInfo += configAck;
|
rxInfo += configAck;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketServer.sendMessageToAll(rxInfo);
|
CmdLineWebSocketServer.sendMessageToAll(rxInfo);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkWorkCycle(GnssDevice device, String cfgData){
|
void checkWorkCycle(GnssDevice device, String cfgData) {
|
||||||
// d3 12 00 12 00 81 e7 6f 01 0a09000001c2dd7ae1419ee148
|
// d3 12 00 12 00 81 e7 6f 01 0a09000001c2dd7ae1419ee148
|
||||||
int pos = 9;
|
int pos = 9;
|
||||||
byte[] hexValues = ByteUtil.hexStringTobyte(cfgData);
|
byte[] hexValues = ByteUtil.hexStringTobyte(cfgData);
|
||||||
@ -90,10 +97,10 @@ public class APIController extends BasicController{
|
|||||||
byte workOffset = hexValues[pos++];
|
byte workOffset = hexValues[pos++];
|
||||||
|
|
||||||
GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id());
|
GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id());
|
||||||
if(gnssGroup != null){
|
if (gnssGroup != null) {
|
||||||
if(gnssGroup.getWork_cycle()!=workCycle ||
|
if (gnssGroup.getWork_cycle() != workCycle ||
|
||||||
gnssGroup.getActive_time()!=workDur ||
|
gnssGroup.getActive_time() != workDur ||
|
||||||
gnssGroup.getActive_offset()!=workOffset){
|
gnssGroup.getActive_offset() != workOffset) {
|
||||||
//产生告警
|
//产生告警
|
||||||
WarningMsg warningMsg = new WarningMsg();
|
WarningMsg warningMsg = new WarningMsg();
|
||||||
warningMsg.setDeviceid(device.getDeviceid());
|
warningMsg.setDeviceid(device.getDeviceid());
|
||||||
@ -108,7 +115,7 @@ public class APIController extends BasicController{
|
|||||||
String sendCmd = gnssGroup.getConfigCmd(device);
|
String sendCmd = gnssGroup.getConfigCmd(device);
|
||||||
rtcmClient.config(device.getDeviceid(), sendCmd);
|
rtcmClient.config(device.getDeviceid(), sendCmd);
|
||||||
// 保存
|
// 保存
|
||||||
saveMsg(device.getDeviceid(), device.getTenantid(),0xD311, sendCmd, true);
|
saveMsg(device.getDeviceid(), device.getTenantid(), 0xD311, sendCmd, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,19 +124,19 @@ public class APIController extends BasicController{
|
|||||||
@PostMapping(value = "/api/device_online")
|
@PostMapping(value = "/api/device_online")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String onLine(String deviceId, Integer tenantId, LocalDateTime lastOnlineTime) {
|
public String onLine(String deviceId, Integer tenantId, LocalDateTime lastOnlineTime) {
|
||||||
onDeviceActive(deviceId,tenantId);
|
onDeviceActive(deviceId, tenantId);
|
||||||
// 检查参数一致性
|
// 检查参数一致性
|
||||||
String getWorkCycleCmd = "d3120005"+
|
String getWorkCycleCmd = "d3120005" +
|
||||||
HexUtil.Int2HexString(Integer.parseInt(deviceId))+"01";
|
HexUtil.Int2HexString(Integer.parseInt(deviceId)) + "01";
|
||||||
rtcmClient.config(deviceId, getWorkCycleCmd);
|
rtcmClient.config(deviceId, getWorkCycleCmd);
|
||||||
// 保存
|
// 保存
|
||||||
saveMsg(deviceId, tenantId,0xD312, getWorkCycleCmd, true);
|
saveMsg(deviceId, tenantId, 0xD312, getWorkCycleCmd, true);
|
||||||
|
|
||||||
// 检查是否自动补传
|
// 检查是否自动补传
|
||||||
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
||||||
if(device == null) return null;
|
if (device == null) return null;
|
||||||
GnssGroupCalc groupCalc = groupCalcMapper.selectById(device.getCalc_group_id());
|
GnssGroupCalc groupCalc = groupCalcMapper.selectById(device.getCalc_group_id());
|
||||||
if(groupCalc==null || !groupCalc.getAuto_upload()) return null;
|
if (groupCalc == null || !groupCalc.getAuto_upload()) return null;
|
||||||
|
|
||||||
// 检查上次是否离线,如果是则启动补传
|
// 检查上次是否离线,如果是则启动补传
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
@ -149,7 +156,7 @@ public class APIController extends BasicController{
|
|||||||
+ HexUtil.Byte2HexString((byte) (now.getMinute()));
|
+ HexUtil.Byte2HexString((byte) (now.getMinute()));
|
||||||
rtcmClient.config(deviceId, uploadCmd);
|
rtcmClient.config(deviceId, uploadCmd);
|
||||||
// 保存
|
// 保存
|
||||||
saveMsg(deviceId, tenantId,0xD312, uploadCmd, true);
|
saveMsg(deviceId, tenantId, 0xD312, uploadCmd, true);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -161,33 +168,40 @@ public class APIController extends BasicController{
|
|||||||
|
|
||||||
// 检查有没有待配置的参数
|
// 检查有没有待配置的参数
|
||||||
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
GnssDevice device = deviceMapper.queryByDeviceId(deviceId);
|
||||||
if(device == null) return null;
|
if (device == null) return null;
|
||||||
|
|
||||||
if(!device.getSyn()){
|
if (!device.getSyn()) {
|
||||||
GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id());
|
GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id());
|
||||||
if(gnssGroup != null){
|
if (gnssGroup != null) {
|
||||||
String config = gnssGroup.getConfigCmd(device);
|
String config = gnssGroup.getConfigCmd(device);
|
||||||
if(config != null){
|
if (config != null) {
|
||||||
rtcmClient.config(deviceId, config);
|
rtcmClient.config(deviceId, config);
|
||||||
// 保存
|
// 保存
|
||||||
saveMsg(deviceId, tenantId,0xd311, config, true);
|
saveMsg(deviceId, tenantId, 0xd311, config, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
List<String> deviceCmds = device.getConfigCmd();
|
||||||
|
for(String deviceCmd:deviceCmds){
|
||||||
|
rtcmClient.config(deviceId, deviceCmd);
|
||||||
|
// 保存
|
||||||
|
saveMsg(deviceId, tenantId, 0xd312, deviceCmd, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 检查有没有待发送的指令
|
// 检查有没有待发送的指令
|
||||||
QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("deviceid",deviceId);
|
queryWrapper.eq("deviceid", deviceId);
|
||||||
queryWrapper.eq("syn",false);
|
queryWrapper.eq("syn", false);
|
||||||
queryWrapper.last("limit 1");
|
queryWrapper.last("limit 1");
|
||||||
DeviceCacheCmd cacheCmd = cacheCmdMapper.selectOne(queryWrapper);
|
DeviceCacheCmd cacheCmd = cacheCmdMapper.selectOne(queryWrapper);
|
||||||
if(cacheCmd != null){
|
if (cacheCmd != null) {
|
||||||
rtcmClient.config(deviceId, cacheCmd.getCmd());
|
rtcmClient.config(deviceId, cacheCmd.getCmd());
|
||||||
cacheCmd.setSyn(true);
|
cacheCmd.setSyn(true);
|
||||||
cacheCmd.setUpdatetime(LocalDateTime.now());
|
cacheCmd.setUpdatetime(LocalDateTime.now());
|
||||||
cacheCmdMapper.updateById(cacheCmd);
|
cacheCmdMapper.updateById(cacheCmd);
|
||||||
// 保存
|
// 保存
|
||||||
saveMsg(deviceId, tenantId,cacheCmd.getMsgtype(), cacheCmd.getCmd(), true);
|
saveMsg(deviceId, tenantId, cacheCmd.getMsgtype(), cacheCmd.getCmd(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查iccid
|
// 检查iccid
|
||||||
@ -207,53 +221,64 @@ public class APIController extends BasicController{
|
|||||||
/****** gnss upload *******/
|
/****** gnss upload *******/
|
||||||
@PostMapping(value = "/api/gnss_upload")
|
@PostMapping(value = "/api/gnss_upload")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String onGnssUpload(String deviceId, Integer tenantId,LocalDateTime uploadTime,String info) {
|
public String onGnssUpload(String deviceId, Integer tenantId, LocalDateTime uploadTime, String info) {
|
||||||
saveMsg(deviceId, tenantId,0xd342,
|
saveMsg(deviceId, tenantId, 0xd342,
|
||||||
"gnss data upload from "+uploadTime+", "+info,false);
|
"gnss data upload from " + uploadTime + ", " + info, false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/api/gnss_upload_pause")
|
@PostMapping(value = "/api/gnss_upload_pause")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String onGnssUploadPause(String deviceId, Integer tenantId) {
|
public String onGnssUploadPause(String deviceId, Integer tenantId) {
|
||||||
saveMsg(deviceId, tenantId,0xd342, "gnss data upload pause",false);
|
saveMsg(deviceId, tenantId, 0xd342, "gnss data upload pause", false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/api/gnss_upload_complete")
|
@PostMapping(value = "/api/gnss_upload_complete")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String onGnssUploadComplete(String deviceId, Integer tenantId, LocalDateTime uploadTime) {
|
public String onGnssUploadComplete(String deviceId, Integer tenantId, LocalDateTime uploadTime) {
|
||||||
saveMsg(deviceId, tenantId,0xd342, "gnss data upload completely at "+uploadTime,false);
|
saveMsg(deviceId, tenantId, 0xd342, "gnss data upload completely at " + uploadTime, false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMsg(String deviceId, int tenantId, int msgType, String content,boolean isTx){
|
void saveMsg(String deviceId, int tenantId, int msgType, String content, boolean isTx) {
|
||||||
GnssMsg gnssMsg = new GnssMsg();
|
GnssMsg gnssMsg = new GnssMsg();
|
||||||
gnssMsg.setCreatetime(LocalDateTime.now());
|
gnssMsg.setCreatetime(LocalDateTime.now());
|
||||||
gnssMsg.setTenantid(tenantId);
|
gnssMsg.setTenantid(tenantId);
|
||||||
gnssMsg.setDeviceid(deviceId);
|
gnssMsg.setDeviceid(deviceId);
|
||||||
gnssMsg.setMsgtype(msgType);
|
gnssMsg.setMsgtype(msgType);
|
||||||
gnssMsg.setTx(isTx);
|
gnssMsg.setTx(isTx);
|
||||||
if(content==null) content="";
|
if (content == null) content = "";
|
||||||
gnssMsg.setMsglen(content.length()/2);
|
gnssMsg.setMsglen(content.length() / 2);
|
||||||
int saveContentLen = content.length();
|
int saveContentLen = content.length();
|
||||||
if(saveContentLen<=128)
|
if (saveContentLen <= 128)
|
||||||
gnssMsg.setContent(content);
|
gnssMsg.setContent(content);
|
||||||
else
|
else
|
||||||
gnssMsg.setContent(content.substring(0,128));
|
gnssMsg.setContent(content.substring(0, 128));
|
||||||
msgMapper.insert(gnssMsg);
|
msgMapper.insert(gnssMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkAndAskICCID(GnssDevice device){
|
void checkAndAskICCID(GnssDevice device) {
|
||||||
if(device.getIccid() == null || device.getIccid().trim().isEmpty()) {
|
GnssStatus status = gnssStatusMapper.getByDeviceId(device.getDeviceid());
|
||||||
String sendCmd = "AT+ICCID";
|
|
||||||
int msgType = 0xD310 + 10; // DTU
|
// 1. 检查设备是否有 ICCID记录 (初始化)
|
||||||
short len = (short) (sendCmd.length() + 5);
|
// 2. 检查设备是否从离线恢复 (防止更换 SIM 卡)
|
||||||
sendCmd = Integer.toHexString(msgType) + HexUtil.Short2HexString(len) +
|
// 3. 如果两种情况都不满足,则不查询 ICCID
|
||||||
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) +
|
|
||||||
"01" + HexUtil.String2HexString(sendCmd);
|
boolean isDeviceReconnecting = status != null && status.getState() == GnssStatus.STATE_OFFLINE;
|
||||||
rtcmClient.config(device.getDeviceid(), sendCmd);
|
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) {
|
void updateICCID(GnssDevice device, String dtuAck) {
|
||||||
// 只检查 "ICCID:" 的十六进制部分
|
// 只检查 "ICCID:" 的十六进制部分
|
||||||
@ -262,11 +287,45 @@ public class APIController extends BasicController{
|
|||||||
}
|
}
|
||||||
String content = HexUtil.HexString2String(dtuAck);
|
String content = HexUtil.HexString2String(dtuAck);
|
||||||
if(content.contains("+ICCID:")){
|
if(content.contains("+ICCID:")){
|
||||||
//System.out.println(content);
|
|
||||||
String iccid = content.substring(content.indexOf("+ICCID:") + 8).trim();
|
String iccid = content.substring(content.indexOf("+ICCID:") + 8).trim();
|
||||||
iccid = iccid.split("\r\n")[0].trim();
|
iccid = iccid.split("\r\n")[0].trim();
|
||||||
device.setIccid(iccid);
|
device.setIccid(iccid);
|
||||||
deviceMapper.updateById(device);
|
deviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****** upgrade operations *******/
|
||||||
|
@PostMapping(value = "/api/upgrade_ack")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradeAck() {
|
||||||
|
//发连接服务器指令
|
||||||
|
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("opmode", GnssDevice.OP_MODE_UPGRADING);
|
||||||
|
List<GnssDevice> gnssDeviceList = deviceMapper.selectList(queryWrapper);
|
||||||
|
for (GnssDevice device : gnssDeviceList) {
|
||||||
|
short len = (short) (verServerConnectCmd.length() + 5);
|
||||||
|
String sendCmd = "d31a" + HexUtil.Short2HexString(len) +
|
||||||
|
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) +
|
||||||
|
"01" + HexUtil.String2HexString(verServerConnectCmd);
|
||||||
|
rtcmClient.config(device.getDeviceid(), sendCmd);
|
||||||
|
}
|
||||||
|
//界面的升级按钮变灰
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade ack");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/upgrading_ind")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradingInd(String deviceId, Byte progress) {
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade ind " + deviceId + "," + progress);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/upgrade_complete_ind")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradeCompleteInd(String deviceId, Byte code) {
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade complete ind " + deviceId + "," + code);
|
||||||
|
// 保存升级记录
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
//单例
|
//单例
|
||||||
|
|
||||||
@ServerEndpoint(value = "/websocket",configurator = WebSocketConfig.class)
|
@ServerEndpoint(value = "/websocket/cmdline",configurator = WebSocketConfig.class)
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebSocketServer {
|
public class CmdLineWebSocketServer {
|
||||||
// thread safety counter
|
// thread safety counter
|
||||||
// thread safety set to hold websocket objects
|
// thread safety set to hold websocket objects
|
||||||
private static Map<String,Session> webSocketSet = new ConcurrentHashMap<>();
|
private static final Map<String,Session> webSocketSet = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@OnOpen
|
@OnOpen
|
||||||
public void onOpen(Session session){
|
public void onOpen(Session session){
|
||||||
@ -109,6 +109,13 @@ public class GnssDeviceController extends BasicController{
|
|||||||
tenants = tenantMapper.selectList(queryWrapper);
|
tenants = tenantMapper.selectList(queryWrapper);
|
||||||
}
|
}
|
||||||
GnssDevice device = gnssDeviceMapper.selectById(id);
|
GnssDevice device = gnssDeviceMapper.selectById(id);
|
||||||
|
if(device.getDeviceid().startsWith("6") || device.getDeviceid().startsWith("7")){
|
||||||
|
if(!device.getHas_battery()){
|
||||||
|
device.setHas_battery(true);
|
||||||
|
device.setChange_flag(GnssDevice.PARA_MASK_HAS_BATTERY);
|
||||||
|
gnssDeviceMapper.updateById(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
m.addAttribute("tenant_list", tenants);
|
m.addAttribute("tenant_list", tenants);
|
||||||
m.addAttribute("gnss_group_list", gnssGroups);
|
m.addAttribute("gnss_group_list", gnssGroups);
|
||||||
m.addAttribute("gnss_group_calc_list", gnssGroupCalcs);
|
m.addAttribute("gnss_group_calc_list", gnssGroupCalcs);
|
||||||
@ -165,6 +172,11 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if (StringUtils.hasText(appver)) {
|
if (StringUtils.hasText(appver)) {
|
||||||
queryWrapper.like("appver", appver);
|
queryWrapper.like("appver", appver);
|
||||||
}
|
}
|
||||||
|
//IMEI
|
||||||
|
String imei = search.getString("imei");
|
||||||
|
if (StringUtils.hasText(imei)) {
|
||||||
|
queryWrapper.like("imei", imei);
|
||||||
|
}
|
||||||
//备注
|
//备注
|
||||||
String remark = search.getString("remark");
|
String remark = search.getString("remark");
|
||||||
if (StringUtils.hasText(remark)) {
|
if (StringUtils.hasText(remark)) {
|
||||||
@ -175,6 +187,17 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if (opmode != null && opmode != QUERY_ALL) {
|
if (opmode != null && opmode != QUERY_ALL) {
|
||||||
queryWrapper.eq("opmode", opmode);
|
queryWrapper.eq("opmode", opmode);
|
||||||
}
|
}
|
||||||
|
Integer hasBattery = search.getInteger("has_battery");
|
||||||
|
if(hasBattery != null && hasBattery != QUERY_ALL){
|
||||||
|
if(hasBattery == 1) {
|
||||||
|
queryWrapper.and(wrapper -> wrapper.eq("has_battery", 1)
|
||||||
|
.or().likeRight("deviceid","6")
|
||||||
|
.or().likeRight("deviceid","7"));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
queryWrapper.eq("has_battery", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
//推送状
|
//推送状
|
||||||
Integer fwd = search.getInteger("fwd_group_id");
|
Integer fwd = search.getInteger("fwd_group_id");
|
||||||
if (fwd != null && fwd != QUERY_ALL) {
|
if (fwd != null && fwd != QUERY_ALL) {
|
||||||
@ -240,6 +263,12 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if(!old_device.getGroup_id().equals(device.getGroup_id())){
|
if(!old_device.getGroup_id().equals(device.getGroup_id())){
|
||||||
device.setSyn(false);
|
device.setSyn(false);
|
||||||
}
|
}
|
||||||
|
if(diff.contains("has_battery")){
|
||||||
|
device.setChange_flag(GnssDevice.PARA_MASK_HAS_BATTERY);
|
||||||
|
}
|
||||||
|
if(diff.contains("voltage_factor")){
|
||||||
|
device.setChange_flag(GnssDevice.PARA_MASK_VOLTAGE_FACTOR);
|
||||||
|
}
|
||||||
num = gnssDeviceMapper.updateById(device);
|
num = gnssDeviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,7 +287,7 @@ public class GnssDeviceController extends BasicController{
|
|||||||
} else {
|
} else {
|
||||||
// 更新组参数的关联个数
|
// 更新组参数的关联个数
|
||||||
updateBasicGroupAssociatedNum(device,old_device);
|
updateBasicGroupAssociatedNum(device,old_device);
|
||||||
rtcmClient.deviceParamChanged(device.getDeviceid(), device.getParentid());
|
rtcmClient.deviceParamChanged(device.getDeviceid(), old_device != null ? old_device.getParentid() : null, old_device != null ? old_device.getParentid1() : null);
|
||||||
return HttpResult.ok();
|
return HttpResult.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,7 +409,7 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
return HttpResult.failed();
|
return HttpResult.failed();
|
||||||
} else{
|
} else{
|
||||||
rtcmClient.deviceParamChanged(del_id, null);
|
rtcmClient.deviceParamChanged(del_id, null, null);
|
||||||
return HttpResult.ok();
|
return HttpResult.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,9 +63,13 @@ public class GnssGroupController extends BasicController {
|
|||||||
|
|
||||||
@PostMapping("/gnss/group/update")
|
@PostMapping("/gnss/group/update")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String update(HttpSession session, @RequestBody JSONObject object) throws Exception {
|
public HttpResult update(HttpSession session, @RequestBody JSONObject object) throws Exception {
|
||||||
int num = 0;
|
int num = 0;
|
||||||
GnssGroup group = JSONObject.toJavaObject(object,GnssGroup.class);
|
GnssGroup group = JSONObject.toJavaObject(object,GnssGroup.class);
|
||||||
|
if(group.getActive_time()+ group.getActive_offset()>= group.getWork_cycle()){
|
||||||
|
return HttpResult.fail("参数组合错误!");
|
||||||
|
}
|
||||||
|
|
||||||
GnssGroup oldGroup = gnssGroupMapper.selectById(group.getId());
|
GnssGroup oldGroup = gnssGroupMapper.selectById(group.getId());
|
||||||
|
|
||||||
// no changed
|
// no changed
|
||||||
@ -90,12 +94,12 @@ public class GnssGroupController extends BasicController {
|
|||||||
group.getName());
|
group.getName());
|
||||||
}
|
}
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
return HttpResult.failed();
|
return HttpResult.fail("更新数据失败");
|
||||||
} else{
|
} else{
|
||||||
// 更新所有的device同步标志
|
// 更新所有的device同步标志
|
||||||
deviceMapper.setSynFlagByGroupId(group.getId());
|
deviceMapper.setSynFlagByGroupId(group.getId());
|
||||||
rtcmClient.groupParamChanged();
|
rtcmClient.groupParamChanged();
|
||||||
return HttpResult.ok();
|
return HttpResult.success("更新成功");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.imdroid.beidou.controller;
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.imdroid.beidou.service.CommonExcelService;
|
import com.imdroid.beidou.service.CommonExcelService;
|
||||||
import com.imdroid.secapi.dto.GnssStatusMsg;
|
import com.imdroid.secapi.dto.GnssStatusMsg;
|
||||||
@ -11,11 +12,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态消息 控制器
|
* 状态消息 控制器
|
||||||
@ -31,6 +34,10 @@ public class GnssMsgStatusController extends BasicController implements CommonEx
|
|||||||
GnssStatusMsgMapper statusMsgMapper;
|
GnssStatusMsgMapper statusMsgMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
OpLogManager opLogManager;
|
OpLogManager opLogManager;
|
||||||
|
|
||||||
|
// 滑动窗口大小
|
||||||
|
private static final int WINDOW_SIZE = 5;
|
||||||
|
|
||||||
@RequestMapping("/page/gnss_msg_status")
|
@RequestMapping("/page/gnss_msg_status")
|
||||||
public String gnssStatusMsg(Model m, HttpSession session) {
|
public String gnssStatusMsg(Model m, HttpSession session) {
|
||||||
initModel(m, session);
|
initModel(m, session);
|
||||||
@ -40,7 +47,129 @@ public class GnssMsgStatusController extends BasicController implements CommonEx
|
|||||||
@RequestMapping("/gnss/msg/status/list")
|
@RequestMapping("/gnss/msg/status/list")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public JSONObject listStatusMsg(HttpSession session, Integer page, Integer limit, String searchParams) {
|
public JSONObject listStatusMsg(HttpSession session, Integer page, Integer limit, String searchParams) {
|
||||||
return this.pageList(session, page, limit, searchParams);
|
JSONObject obj = this.pageList(session, page, limit, searchParams);
|
||||||
|
List<GnssStatusMsg> msgList = (List<GnssStatusMsg>) obj.get("data");
|
||||||
|
for(GnssStatusMsg msg:msgList){
|
||||||
|
if(msg.getChargecurrency()!=null && msg.getSolarvoltage()!=null){
|
||||||
|
if(msg.getVoltage()>msg.getSolarvoltage()+3000){
|
||||||
|
msg.setChargecurrency(0);
|
||||||
|
msg.setChargewatt(0.0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取特定设备的数据,并应用滑动平均
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param limit 获取条数,默认150条
|
||||||
|
* @return 处理后的数据
|
||||||
|
*/
|
||||||
|
@RequestMapping("/gnss/msg/status/device_data")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject getDeviceData(@RequestParam String deviceId,
|
||||||
|
@RequestParam(required = false, defaultValue = "150") Integer limit) {
|
||||||
|
// 查询指定设备的最近数据
|
||||||
|
QueryWrapper<GnssStatusMsg> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("deviceid", deviceId)
|
||||||
|
.orderByDesc("createtime")
|
||||||
|
.last("limit " + limit);
|
||||||
|
|
||||||
|
List<GnssStatusMsg> dataList = statusMsgMapper.selectList(queryWrapper);
|
||||||
|
// 反转列表,使其按时间升序排列
|
||||||
|
Collections.reverse(dataList);
|
||||||
|
|
||||||
|
// 应用滑动平均
|
||||||
|
List<GnssStatusMsg> smoothedData = applyMovingAverage(dataList, WINDOW_SIZE);
|
||||||
|
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
result.put("code", 0);
|
||||||
|
result.put("msg", "");
|
||||||
|
result.put("count", smoothedData.size());
|
||||||
|
result.put("data", smoothedData);
|
||||||
|
result.put("originalData", dataList);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用滑动平均算法
|
||||||
|
*
|
||||||
|
* @param dataList 原始数据列表
|
||||||
|
* @param windowSize 窗口大小
|
||||||
|
* @return 处理后的数据列表
|
||||||
|
*/
|
||||||
|
private List<GnssStatusMsg> applyMovingAverage(List<GnssStatusMsg> dataList, int windowSize) {
|
||||||
|
if (dataList.size() <= 1) {
|
||||||
|
return new ArrayList<>(dataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GnssStatusMsg> result = new ArrayList<>(dataList.size());
|
||||||
|
|
||||||
|
// 初始窗口
|
||||||
|
Deque<Float> rollWindow = new LinkedList<>();
|
||||||
|
Deque<Float> pitchWindow = new LinkedList<>();
|
||||||
|
Deque<Float> yawWindow = new LinkedList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < dataList.size(); i++) {
|
||||||
|
GnssStatusMsg currentMsg = dataList.get(i);
|
||||||
|
GnssStatusMsg smoothedMsg = new GnssStatusMsg();
|
||||||
|
|
||||||
|
// 复制基本属性
|
||||||
|
smoothedMsg.setId(currentMsg.getId());
|
||||||
|
smoothedMsg.setDeviceid(currentMsg.getDeviceid());
|
||||||
|
smoothedMsg.setCreatetime(currentMsg.getCreatetime());
|
||||||
|
smoothedMsg.setDevicetime(currentMsg.getDevicetime());
|
||||||
|
smoothedMsg.setTenantid(currentMsg.getTenantid());
|
||||||
|
smoothedMsg.setDtustate(currentMsg.getDtustate());
|
||||||
|
smoothedMsg.setRssi(currentMsg.getRssi());
|
||||||
|
smoothedMsg.setVoltage(currentMsg.getVoltage());
|
||||||
|
smoothedMsg.setSolarvoltage(currentMsg.getSolarvoltage());
|
||||||
|
smoothedMsg.setChargecurrency(currentMsg.getChargecurrency());
|
||||||
|
smoothedMsg.setChargewatt(currentMsg.getChargewatt());
|
||||||
|
smoothedMsg.setTemperature(currentMsg.getTemperature());
|
||||||
|
smoothedMsg.setHumidity(currentMsg.getHumidity());
|
||||||
|
|
||||||
|
// 添加当前值到窗口
|
||||||
|
if (currentMsg.getRoll() != null) rollWindow.addLast(currentMsg.getRoll());
|
||||||
|
if (currentMsg.getPitch() != null) pitchWindow.addLast(currentMsg.getPitch());
|
||||||
|
if (currentMsg.getYaw() != null) yawWindow.addLast(currentMsg.getYaw());
|
||||||
|
|
||||||
|
// 保持窗口大小
|
||||||
|
if (rollWindow.size() > windowSize) rollWindow.removeFirst();
|
||||||
|
if (pitchWindow.size() > windowSize) pitchWindow.removeFirst();
|
||||||
|
if (yawWindow.size() > windowSize) yawWindow.removeFirst();
|
||||||
|
|
||||||
|
// 计算平均值
|
||||||
|
if (!rollWindow.isEmpty()) {
|
||||||
|
float rollSum = 0;
|
||||||
|
for (Float val : rollWindow) rollSum += val;
|
||||||
|
smoothedMsg.setRoll(rollSum / rollWindow.size());
|
||||||
|
} else {
|
||||||
|
smoothedMsg.setRoll(currentMsg.getRoll());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pitchWindow.isEmpty()) {
|
||||||
|
float pitchSum = 0;
|
||||||
|
for (Float val : pitchWindow) pitchSum += val;
|
||||||
|
smoothedMsg.setPitch(pitchSum / pitchWindow.size());
|
||||||
|
} else {
|
||||||
|
smoothedMsg.setPitch(currentMsg.getPitch());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yawWindow.isEmpty()) {
|
||||||
|
float yawSum = 0;
|
||||||
|
for (Float val : yawWindow) yawSum += val;
|
||||||
|
smoothedMsg.setYaw(yawSum / yawWindow.size());
|
||||||
|
} else {
|
||||||
|
smoothedMsg.setYaw(currentMsg.getYaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(smoothedMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.imdroid.beidou.controller;
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@ -21,8 +22,10 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class SimCardController extends BasicController {
|
public class SimCardController extends BasicController {
|
||||||
@ -34,7 +37,16 @@ public class SimCardController extends BasicController {
|
|||||||
private String KEY;
|
private String KEY;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SimCardsMapper simCardsMapper;
|
private GnssDeviceMapper gnssDeviceMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficCardMapper trafficCardMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficDeviceMappingMapper trafficDeviceMappingMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TrafficRecordMapper trafficRecordMapper;
|
||||||
|
|
||||||
@RequestMapping("/page/sim_status")
|
@RequestMapping("/page/sim_status")
|
||||||
public String simStatus(Model m, HttpSession session) {
|
public String simStatus(Model m, HttpSession session) {
|
||||||
@ -43,6 +55,20 @@ public class SimCardController extends BasicController {
|
|||||||
return "/page/sim_status";
|
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")
|
@RequestMapping("/sim/list")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public JSONObject list(HttpSession session,
|
public JSONObject list(HttpSession session,
|
||||||
@ -51,37 +77,102 @@ public class SimCardController extends BasicController {
|
|||||||
String searchType,
|
String searchType,
|
||||||
String searchContent,
|
String searchContent,
|
||||||
Integer status) {
|
Integer status) {
|
||||||
Page<SimCard> pageable = new Page<>(page, limit);
|
|
||||||
|
|
||||||
QueryWrapper<SimCard> queryWrapper = new QueryWrapper<>();
|
|
||||||
|
|
||||||
|
QueryWrapper<GnssDevice> deviceQueryWrapper = new QueryWrapper<>();
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(searchContent)) {
|
if (!StringUtils.isEmpty(searchContent)) {
|
||||||
switch(searchType) {
|
switch(searchType) {
|
||||||
case "deviceId":
|
case "deviceId":
|
||||||
queryWrapper.like("deviceid", searchContent.trim());
|
deviceQueryWrapper.like("deviceid", searchContent.trim());
|
||||||
break;
|
break;
|
||||||
case "iccid":
|
case "iccid":
|
||||||
queryWrapper.like("iccid", searchContent.trim());
|
deviceQueryWrapper.like("iccid", searchContent.trim());
|
||||||
|
break;
|
||||||
|
case "imei":
|
||||||
|
deviceQueryWrapper.like("imei", searchContent.trim());
|
||||||
break;
|
break;
|
||||||
case "simNumber":
|
case "simNumber":
|
||||||
queryWrapper.like("msisdn", searchContent.trim());
|
// 通过SIM号查找对应的ICCID,然后查询设备
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
deviceQueryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
deviceQueryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 只查询有ICCID的设备
|
||||||
|
deviceQueryWrapper.isNotNull("iccid");
|
||||||
|
deviceQueryWrapper.ne("iccid", "");
|
||||||
|
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
queryWrapper.eq("status", status);
|
List<TrafficCard> cardsWithStatus = trafficCardMapper.selectList(
|
||||||
|
new QueryWrapper<TrafficCard>().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<String> iccids = new ArrayList<>();
|
||||||
|
for (TrafficCard card : cardsWithStatus) {
|
||||||
|
iccids.add(card.getIccid());
|
||||||
|
}
|
||||||
|
deviceQueryWrapper.in("iccid", iccids);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryWrapper.orderByDesc("updatetime");
|
deviceQueryWrapper.orderByDesc("deviceid");
|
||||||
IPage<SimCard> cs = simCardsMapper.selectPage(pageable, queryWrapper);
|
|
||||||
|
Page<GnssDevice> pageable = new Page<>(page, limit);
|
||||||
|
IPage<GnssDevice> devices = gnssDeviceMapper.selectPage(pageable, deviceQueryWrapper);
|
||||||
|
|
||||||
|
List<Map<String, Object>> 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<String, Object> deviceData = new HashMap<>();
|
||||||
|
deviceData.put("id", device.getId());
|
||||||
|
deviceData.put("deviceid", device.getDeviceid());
|
||||||
|
deviceData.put("iccid", device.getIccid());
|
||||||
|
deviceData.put("imei", device.getImei());
|
||||||
|
|
||||||
|
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 jsonObject = new JSONObject();
|
||||||
jsonObject.put("code", 0);
|
jsonObject.put("code", 0);
|
||||||
jsonObject.put("msg", "");
|
jsonObject.put("msg", "");
|
||||||
jsonObject.put("count", cs.getTotal());
|
jsonObject.put("count", devices.getTotal());
|
||||||
jsonObject.put("data", cs.getRecords());
|
jsonObject.put("data", enrichedData);
|
||||||
System.out.println(jsonObject.toString());
|
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +205,24 @@ public class SimCardController extends BasicController {
|
|||||||
if (content.trim().isEmpty()) {
|
if (content.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("设备ID不能为空");
|
throw new IllegalArgumentException("设备ID不能为空");
|
||||||
}
|
}
|
||||||
SimCard simCard = simCardsMapper.queryByDeviceId(content.trim());
|
GnssDevice device = gnssDeviceMapper.queryByDeviceId(content.trim());
|
||||||
if (simCard == null) {
|
if (device == null || device.getIccid() == null || device.getIccid().trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("未找到该设备ID对应的SIM卡信息");
|
throw new IllegalArgumentException("未找到该设备ID或设备没有ICCID信息");
|
||||||
}
|
}
|
||||||
params.put("card", simCard.getIccid());
|
params.put("card", device.getIccid());
|
||||||
|
break;
|
||||||
|
case "imei":
|
||||||
|
if (content.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("IMEI不能为空");
|
||||||
|
}
|
||||||
|
QueryWrapper<GnssDevice> deviceByImeiQueryWrapper = new QueryWrapper<>();
|
||||||
|
deviceByImeiQueryWrapper.eq("imei", content.trim());
|
||||||
|
deviceByImeiQueryWrapper.last("limit 1");
|
||||||
|
GnssDevice deviceByImei = gnssDeviceMapper.selectOne(deviceByImeiQueryWrapper);
|
||||||
|
if (deviceByImei == null || deviceByImei.getIccid() == null || deviceByImei.getIccid().trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("未找到该IMEI对应的设备或设备没有ICCID信息");
|
||||||
|
}
|
||||||
|
params.put("card", deviceByImei.getIccid());
|
||||||
break;
|
break;
|
||||||
case "simNumber":
|
case "simNumber":
|
||||||
params.put("card", content);
|
params.put("card", content);
|
||||||
@ -176,6 +280,197 @@ public class SimCardController extends BasicController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sim/traffic-records")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject getTrafficRecords(HttpSession session,
|
||||||
|
int page,
|
||||||
|
int limit,
|
||||||
|
String searchType,
|
||||||
|
String searchContent,
|
||||||
|
String startTime,
|
||||||
|
String endTime) {
|
||||||
|
try {
|
||||||
|
Page<TrafficRecord> pageable = new Page<>(page, limit);
|
||||||
|
QueryWrapper<TrafficRecord> 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 "imei":
|
||||||
|
QueryWrapper<GnssDevice> deviceByImeiQueryWrapper = new QueryWrapper<>();
|
||||||
|
deviceByImeiQueryWrapper.eq("imei", searchContent.trim());
|
||||||
|
deviceByImeiQueryWrapper.last("limit 1");
|
||||||
|
GnssDevice deviceByImei = gnssDeviceMapper.selectOne(deviceByImeiQueryWrapper);
|
||||||
|
if (deviceByImei != null && deviceByImei.getIccid() != null && !deviceByImei.getIccid().trim().isEmpty()) {
|
||||||
|
queryWrapper.eq("iccid", deviceByImei.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "simNumber":
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
queryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(startTime)) {
|
||||||
|
queryWrapper.ge("recordtime", startTime);
|
||||||
|
}
|
||||||
|
if (!StringUtils.isEmpty(endTime)) {
|
||||||
|
queryWrapper.le("recordtime", endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryWrapper.orderByDesc("recordtime");
|
||||||
|
IPage<TrafficRecord> records = trafficRecordMapper.selectPage(pageable, queryWrapper);
|
||||||
|
|
||||||
|
List<Map<String, Object>> enrichedData = new ArrayList<>();
|
||||||
|
for (TrafficRecord record : records.getRecords()) {
|
||||||
|
Map<String, Object> 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<GnssDevice>().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<TrafficDeviceMapping> pageable = new Page<>(page, limit);
|
||||||
|
QueryWrapper<TrafficDeviceMapping> 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 "imei":
|
||||||
|
QueryWrapper<GnssDevice> deviceByImeiQueryWrapper = new QueryWrapper<>();
|
||||||
|
deviceByImeiQueryWrapper.eq("imei", searchContent.trim());
|
||||||
|
deviceByImeiQueryWrapper.last("limit 1");
|
||||||
|
GnssDevice deviceByImei = gnssDeviceMapper.selectOne(deviceByImeiQueryWrapper);
|
||||||
|
if (deviceByImei != null && deviceByImei.getIccid() != null && !deviceByImei.getIccid().trim().isEmpty()) {
|
||||||
|
queryWrapper.eq("iccid", deviceByImei.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "simNumber":
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
queryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryWrapper.orderByDesc("starttime");
|
||||||
|
IPage<TrafficDeviceMapping> mappings = trafficDeviceMappingMapper.selectPage(pageable, queryWrapper);
|
||||||
|
|
||||||
|
List<Map<String, Object>> enrichedData = new ArrayList<>();
|
||||||
|
for (TrafficDeviceMapping mapping : mappings.getRecords()) {
|
||||||
|
Map<String, Object> 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<String, String> params) {
|
private String calculateSignature(Map<String, String> params) {
|
||||||
try {
|
try {
|
||||||
List<String> paramList = new ArrayList<>();
|
List<String> paramList = new ArrayList<>();
|
||||||
@ -201,6 +496,486 @@ public class SimCardController extends BasicController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sim/export")
|
||||||
|
@ResponseBody
|
||||||
|
public void exportSimList(HttpServletResponse response, String searchType, String searchContent, Integer status, Boolean exportAll, Integer pageSize) {
|
||||||
|
try {
|
||||||
|
System.out.println("开始导出SIM卡数据, 参数: searchType=" + searchType + ", searchContent=" + searchContent +
|
||||||
|
", status=" + status + ", exportAll=" + exportAll + ", pageSize=" + pageSize);
|
||||||
|
|
||||||
|
QueryWrapper<GnssDevice> deviceQueryWrapper = new QueryWrapper<>();
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(searchContent)) {
|
||||||
|
switch(searchType) {
|
||||||
|
case "deviceId":
|
||||||
|
deviceQueryWrapper.like("deviceid", searchContent.trim());
|
||||||
|
break;
|
||||||
|
case "iccid":
|
||||||
|
deviceQueryWrapper.like("iccid", searchContent.trim());
|
||||||
|
break;
|
||||||
|
case "imei":
|
||||||
|
deviceQueryWrapper.like("imei", searchContent.trim());
|
||||||
|
break;
|
||||||
|
case "simNumber":
|
||||||
|
// 通过SIM号查找对应的ICCID,然后查询设备
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
deviceQueryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
deviceQueryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceQueryWrapper.isNotNull("iccid");
|
||||||
|
deviceQueryWrapper.ne("iccid", "");
|
||||||
|
|
||||||
|
if (status != null) {
|
||||||
|
List<TrafficCard> cardsWithStatus = trafficCardMapper.selectList(
|
||||||
|
new QueryWrapper<TrafficCard>().eq("status", status)
|
||||||
|
);
|
||||||
|
if (!cardsWithStatus.isEmpty()) {
|
||||||
|
List<String> iccids = new ArrayList<>();
|
||||||
|
for (TrafficCard card : cardsWithStatus) {
|
||||||
|
iccids.add(card.getIccid());
|
||||||
|
}
|
||||||
|
deviceQueryWrapper.in("iccid", iccids);
|
||||||
|
} else {
|
||||||
|
deviceQueryWrapper.eq("iccid", "不存在的ICCID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceQueryWrapper.orderByDesc("deviceid");
|
||||||
|
|
||||||
|
List<GnssDevice> devices;
|
||||||
|
if (Boolean.TRUE.equals(exportAll)) {
|
||||||
|
devices = gnssDeviceMapper.selectList(deviceQueryWrapper);
|
||||||
|
} else {
|
||||||
|
int limit = pageSize != null && pageSize > 0 ? pageSize : 20; // 默认20条,与前端表格默认一致
|
||||||
|
Page<GnssDevice> pageable = new Page<>(1, limit);
|
||||||
|
IPage<GnssDevice> devicePage = gnssDeviceMapper.selectPage(pageable, deviceQueryWrapper);
|
||||||
|
devices = devicePage.getRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devices.isEmpty()) {
|
||||||
|
devices = new ArrayList<>();
|
||||||
|
GnssDevice emptyDevice = new GnssDevice();
|
||||||
|
emptyDevice.setDeviceid("无数据");
|
||||||
|
emptyDevice.setIccid("");
|
||||||
|
devices.add(emptyDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Object>> exportList = new ArrayList<>();
|
||||||
|
List<String> headList = Arrays.asList("设备号", "ICCID", "SIM卡号", "IMEI", "状态",
|
||||||
|
"剩余流量(MB)", "总流量(MB)", "已用流量(MB)", "更新时间");
|
||||||
|
|
||||||
|
for (GnssDevice device : devices) {
|
||||||
|
List<Object> rowData = new ArrayList<>();
|
||||||
|
rowData.add(device.getDeviceid());
|
||||||
|
rowData.add(device.getIccid());
|
||||||
|
|
||||||
|
TrafficCard trafficCard = null;
|
||||||
|
if (device.getIccid() != null && !device.getIccid().trim().isEmpty()) {
|
||||||
|
trafficCard = trafficCardMapper.findByIccid(device.getIccid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trafficCard != null) {
|
||||||
|
rowData.add(trafficCard.getMsisdn()); // SIM卡号
|
||||||
|
rowData.add(device.getImei() != null ? device.getImei() : ""); // IMEI
|
||||||
|
|
||||||
|
String statusStr;
|
||||||
|
switch(trafficCard.getStatus()) {
|
||||||
|
case 1: statusStr = "待激活"; break;
|
||||||
|
case 2: statusStr = "已激活"; break;
|
||||||
|
case 3: statusStr = "停机"; break;
|
||||||
|
case 4: statusStr = "注销"; break;
|
||||||
|
case 5: statusStr = "库存"; break;
|
||||||
|
case 6: statusStr = "可测试"; break;
|
||||||
|
case 7: statusStr = "失效"; break;
|
||||||
|
default: statusStr = "未知";
|
||||||
|
}
|
||||||
|
rowData.add(statusStr);
|
||||||
|
// 单位转换:从KB到MB
|
||||||
|
rowData.add(trafficCard.getRemaining() != null ?
|
||||||
|
String.format("%.2f", trafficCard.getRemaining() / 1000.0) : "0"); // 剩余流量
|
||||||
|
rowData.add(trafficCard.getTotal() != null ?
|
||||||
|
String.format("%.2f", trafficCard.getTotal() / 1000.0) : "0"); // 总流量
|
||||||
|
rowData.add(trafficCard.getUsed() != null ?
|
||||||
|
String.format("%.2f", trafficCard.getUsed() / 1000.0) : "0"); // 已用流量
|
||||||
|
rowData.add(trafficCard.getUpdatetime()); // 更新时间
|
||||||
|
} else {
|
||||||
|
rowData.add("-"); // SIM卡号
|
||||||
|
rowData.add(device.getImei() != null ? device.getImei() : ""); // IMEI
|
||||||
|
rowData.add("未知"); // 状态
|
||||||
|
rowData.add("0"); // 剩余流量
|
||||||
|
rowData.add("0"); // 总流量
|
||||||
|
rowData.add("0"); // 已用流量
|
||||||
|
rowData.add(""); // 更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
exportList.add(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
String fileName = URLEncoder.encode("SIM卡信息", "UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||||
|
|
||||||
|
EasyExcel.write(response.getOutputStream())
|
||||||
|
.head(createExcelHead(headList))
|
||||||
|
.sheet("SIM卡信息")
|
||||||
|
.doWrite(exportList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<List<String>> createExcelHead(List<String> headList) {
|
||||||
|
List<List<String>> result = new ArrayList<>();
|
||||||
|
for (String head : headList) {
|
||||||
|
List<String> column = new ArrayList<>();
|
||||||
|
column.add(head);
|
||||||
|
result.add(column);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sim/traffic-records/export")
|
||||||
|
@ResponseBody
|
||||||
|
public void exportTrafficRecords(HttpServletResponse response, String searchType, String searchContent, String startTime, String endTime) {
|
||||||
|
try {
|
||||||
|
System.out.println("开始导出SIM卡流量记录, 参数: searchType=" + searchType + ", searchContent=" + searchContent +
|
||||||
|
", startTime=" + startTime + ", endTime=" + endTime);
|
||||||
|
|
||||||
|
QueryWrapper<TrafficRecord> 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 "imei":
|
||||||
|
QueryWrapper<GnssDevice> deviceByImeiQueryWrapper = new QueryWrapper<>();
|
||||||
|
deviceByImeiQueryWrapper.eq("imei", searchContent.trim());
|
||||||
|
deviceByImeiQueryWrapper.last("limit 1");
|
||||||
|
GnssDevice deviceByImei = gnssDeviceMapper.selectOne(deviceByImeiQueryWrapper);
|
||||||
|
if (deviceByImei != null && deviceByImei.getIccid() != null && !deviceByImei.getIccid().trim().isEmpty()) {
|
||||||
|
queryWrapper.eq("iccid", deviceByImei.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "simNumber":
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
queryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(startTime)) {
|
||||||
|
queryWrapper.ge("recordtime", startTime);
|
||||||
|
}
|
||||||
|
if (!StringUtils.isEmpty(endTime)) {
|
||||||
|
queryWrapper.le("recordtime", endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryWrapper.orderByDesc("recordtime");
|
||||||
|
|
||||||
|
List<TrafficRecord> records = trafficRecordMapper.selectList(queryWrapper);
|
||||||
|
|
||||||
|
System.out.println("查询到流量记录数量: " + records.size());
|
||||||
|
if (records.isEmpty()) {
|
||||||
|
records = new ArrayList<>();
|
||||||
|
TrafficRecord emptyRecord = new TrafficRecord();
|
||||||
|
emptyRecord.setIccid("无数据");
|
||||||
|
records.add(emptyRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Object>> exportList = new ArrayList<>();
|
||||||
|
List<String> headList = Arrays.asList("设备号", "ICCID", "SIM卡号", "记录时间",
|
||||||
|
"剩余流量(MB)", "总流量(MB)", "已用流量(MB)");
|
||||||
|
|
||||||
|
for (TrafficRecord record : records) {
|
||||||
|
List<Object> rowData = new ArrayList<>();
|
||||||
|
|
||||||
|
GnssDevice device = gnssDeviceMapper.selectOne(
|
||||||
|
new QueryWrapper<GnssDevice>().eq("iccid", record.getIccid())
|
||||||
|
);
|
||||||
|
rowData.add(device != null ? device.getDeviceid() : "无绑定设备"); // 设备号
|
||||||
|
rowData.add(record.getIccid()); // ICCID
|
||||||
|
|
||||||
|
TrafficCard trafficCard = trafficCardMapper.findByIccid(record.getIccid());
|
||||||
|
rowData.add(trafficCard != null ? trafficCard.getMsisdn() : "-"); // SIM卡号
|
||||||
|
|
||||||
|
rowData.add(record.getRecordtime()); // 记录时间
|
||||||
|
|
||||||
|
rowData.add(record.getRemaining() != null ?
|
||||||
|
String.format("%.2f", record.getRemaining() / 1000.0) : "0"); // 剩余流量
|
||||||
|
rowData.add(record.getTotal() != null ?
|
||||||
|
String.format("%.2f", record.getTotal() / 1000.0) : "0"); // 总流量
|
||||||
|
rowData.add(record.getUsed() != null ?
|
||||||
|
String.format("%.2f", record.getUsed() / 1000.0) : "0"); // 已用流量
|
||||||
|
|
||||||
|
exportList.add(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
String fileName = URLEncoder.encode("SIM卡流量记录", "UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||||
|
|
||||||
|
EasyExcel.write(response.getOutputStream())
|
||||||
|
.head(createExcelHead(headList))
|
||||||
|
.sheet("SIM卡流量记录")
|
||||||
|
.doWrite(exportList);
|
||||||
|
|
||||||
|
System.out.println("导出完成,流量记录数: " + exportList.size());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("导出流量记录失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sim/device-mapping/export")
|
||||||
|
@ResponseBody
|
||||||
|
public void exportDeviceMapping(HttpServletResponse response, String searchType, String searchContent) {
|
||||||
|
try {
|
||||||
|
QueryWrapper<TrafficDeviceMapping> 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 "imei":
|
||||||
|
QueryWrapper<GnssDevice> deviceByImeiQueryWrapper = new QueryWrapper<>();
|
||||||
|
deviceByImeiQueryWrapper.eq("imei", searchContent.trim());
|
||||||
|
deviceByImeiQueryWrapper.last("limit 1");
|
||||||
|
GnssDevice deviceByImei = gnssDeviceMapper.selectOne(deviceByImeiQueryWrapper);
|
||||||
|
if (deviceByImei != null && deviceByImei.getIccid() != null && !deviceByImei.getIccid().trim().isEmpty()) {
|
||||||
|
queryWrapper.eq("iccid", deviceByImei.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "simNumber":
|
||||||
|
TrafficCard cardByMsisdn = trafficCardMapper.selectOne(
|
||||||
|
new QueryWrapper<TrafficCard>().like("msisdn", searchContent.trim())
|
||||||
|
);
|
||||||
|
if (cardByMsisdn != null) {
|
||||||
|
queryWrapper.eq("iccid", cardByMsisdn.getIccid());
|
||||||
|
} else {
|
||||||
|
queryWrapper.eq("iccid", "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryWrapper.orderByDesc("starttime");
|
||||||
|
|
||||||
|
List<TrafficDeviceMapping> mappings = trafficDeviceMappingMapper.selectList(queryWrapper);
|
||||||
|
|
||||||
|
if (mappings.isEmpty()) {
|
||||||
|
mappings = new ArrayList<>();
|
||||||
|
TrafficDeviceMapping emptyMapping = new TrafficDeviceMapping();
|
||||||
|
emptyMapping.setDeviceid("无数据");
|
||||||
|
emptyMapping.setIccid("无数据");
|
||||||
|
mappings.add(emptyMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Object>> exportList = new ArrayList<>();
|
||||||
|
List<String> headList = Arrays.asList("设备号", "ICCID", "SIM卡号", "开始时间", "结束时间", "是否激活");
|
||||||
|
|
||||||
|
for (TrafficDeviceMapping mapping : mappings) {
|
||||||
|
List<Object> rowData = new ArrayList<>();
|
||||||
|
|
||||||
|
rowData.add(mapping.getDeviceid()); // 设备号
|
||||||
|
rowData.add(mapping.getIccid()); // ICCID
|
||||||
|
|
||||||
|
TrafficCard trafficCard = trafficCardMapper.findByIccid(mapping.getIccid());
|
||||||
|
rowData.add(trafficCard != null ? trafficCard.getMsisdn() : "-"); // SIM卡号
|
||||||
|
|
||||||
|
rowData.add(mapping.getStarttime()); // 开始时间
|
||||||
|
rowData.add(mapping.getEndtime()); // 结束时间
|
||||||
|
rowData.add(mapping.getEndtime() == null ? "是" : "否"); // 是否激活
|
||||||
|
|
||||||
|
exportList.add(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
String fileName = URLEncoder.encode("设备SIM卡映射记录", "UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||||
|
|
||||||
|
EasyExcel.write(response.getOutputStream())
|
||||||
|
.head(createExcelHead(headList))
|
||||||
|
.sheet("设备SIM卡映射记录")
|
||||||
|
.doWrite(exportList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("导出设备映射记录失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sim/query/export")
|
||||||
|
@ResponseBody
|
||||||
|
public void exportSimDetail(HttpServletResponse response, String type, String content) {
|
||||||
|
try {
|
||||||
|
JSONObject apiResult = proxySimQuery(type, content);
|
||||||
|
|
||||||
|
if (apiResult.getInteger("Status") != 1 || apiResult.getJSONObject("Data") == null) {
|
||||||
|
response.setContentType("text/html;charset=utf-8");
|
||||||
|
response.getWriter().write("查询失败: " + apiResult.getString("Message"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject cardData = apiResult.getJSONObject("Data");
|
||||||
|
List<List<Object>> exportList = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Object> basicInfo = new ArrayList<>();
|
||||||
|
basicInfo.add("基本信息");
|
||||||
|
basicInfo.add("");
|
||||||
|
exportList.add(basicInfo);
|
||||||
|
|
||||||
|
addExportRow(exportList, "运营商", getOperatorText(cardData.getInteger("operatortype")));
|
||||||
|
addExportRow(exportList, "ICCID", cardData.getString("iccid"));
|
||||||
|
addExportRow(exportList, "SIM卡号", cardData.getString("msisdn"));
|
||||||
|
addExportRow(exportList, "IMSI", cardData.getString("imsi"));
|
||||||
|
|
||||||
|
addExportRow(exportList, "套餐信息", "");
|
||||||
|
addExportRow(exportList, "套餐ID", cardData.getString("packageid"));
|
||||||
|
addExportRow(exportList, "套餐名称", cardData.getString("packagename"));
|
||||||
|
|
||||||
|
addExportRow(exportList, "时间信息", "");
|
||||||
|
addExportRow(exportList, "激活时间", cardData.getString("activetime"));
|
||||||
|
addExportRow(exportList, "开始时间", cardData.getString("starttime"));
|
||||||
|
addExportRow(exportList, "结束时间", cardData.getString("stoptime"));
|
||||||
|
addExportRow(exportList, "沉默期结束", cardData.getString("silentdate"));
|
||||||
|
|
||||||
|
addExportRow(exportList, "状态", getStatusText(cardData.getInteger("status")));
|
||||||
|
|
||||||
|
// APN信息
|
||||||
|
if (cardData.getJSONObject("apn") != null) {
|
||||||
|
JSONObject apn = cardData.getJSONObject("apn");
|
||||||
|
addExportRow(exportList, "APN信息", "");
|
||||||
|
addExportRow(exportList, "APN ID", apn.getString("apnid"));
|
||||||
|
addExportRow(exportList, "状态", apn.getString("status").equals("01") ? "在线" : "离线");
|
||||||
|
addExportRow(exportList, "IP", apn.getString("ip"));
|
||||||
|
addExportRow(exportList, "网络", getNetworkText(apn.getString("rat")));
|
||||||
|
addExportRow(exportList, "设备状态", apn.getString("onoffstatus").equals("1") ? "开机" : "关机");
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPRS流量
|
||||||
|
if (cardData.getJSONObject("gprs") != null) {
|
||||||
|
JSONObject gprs = cardData.getJSONObject("gprs");
|
||||||
|
addExportRow(exportList, "GPRS流量", "");
|
||||||
|
addExportRow(exportList, "总流量(MB)", formatDouble(parseDouble(gprs.getString("total"))));
|
||||||
|
addExportRow(exportList, "已用流量(MB)", formatDouble(parseDouble(gprs.getString("used"))));
|
||||||
|
addExportRow(exportList, "剩余流量(MB)", formatDouble(parseDouble(gprs.getString("left"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
String fileName = URLEncoder.encode("SIM卡详情-" + content, "UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||||
|
|
||||||
|
List<List<String>> head = new ArrayList<>();
|
||||||
|
List<String> keyHead = new ArrayList<>();
|
||||||
|
keyHead.add("属性");
|
||||||
|
head.add(keyHead);
|
||||||
|
|
||||||
|
List<String> valueHead = new ArrayList<>();
|
||||||
|
valueHead.add("值");
|
||||||
|
head.add(valueHead);
|
||||||
|
|
||||||
|
EasyExcel.write(response.getOutputStream())
|
||||||
|
.head(head)
|
||||||
|
.sheet("SIM卡详情")
|
||||||
|
.doWrite(exportList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("导出SIM卡详情失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addExportRow(List<List<Object>> exportList, String key, String value) {
|
||||||
|
List<Object> row = new ArrayList<>();
|
||||||
|
row.add(key);
|
||||||
|
row.add(value != null ? value : "");
|
||||||
|
exportList.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOperatorText(Integer type) {
|
||||||
|
if (type == null) return "未知运营商";
|
||||||
|
switch(type) {
|
||||||
|
case 1: return "中国移动";
|
||||||
|
case 2: return "中国联通";
|
||||||
|
case 3: return "中国电信";
|
||||||
|
default: return "未知运营商";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStatusText(Integer status) {
|
||||||
|
if (status == null) return "未知状态";
|
||||||
|
switch(status) {
|
||||||
|
case 1: return "待激活";
|
||||||
|
case 2: return "已激活";
|
||||||
|
case 3: return "停机";
|
||||||
|
case 4: return "注销";
|
||||||
|
case 5: return "库存";
|
||||||
|
case 6: return "可测试";
|
||||||
|
case 7: return "失效";
|
||||||
|
default: return "未知状态";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNetworkText(String rat) {
|
||||||
|
if (rat == null) return "未知网络";
|
||||||
|
switch(rat) {
|
||||||
|
case "1": return "3G";
|
||||||
|
case "2": return "2G";
|
||||||
|
case "6": return "4G";
|
||||||
|
case "8": return "NB";
|
||||||
|
default: return "未知网络";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double parseDouble(String value) {
|
||||||
|
try {
|
||||||
|
return value != null ? Double.parseDouble(value) : 0.0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDouble(double value) {
|
||||||
|
return String.format("%.2f", value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
|
import com.imdroid.beidou.config.WebSocketConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.websocket.*;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
//单例
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/websocket/upgrade",configurator = WebSocketConfig.class)
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class UpgradeWebSocketServer{
|
||||||
|
// thread safety counter
|
||||||
|
// thread safety set to hold websocket objects
|
||||||
|
private static final Map<String, Session> webSocketSet = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session){
|
||||||
|
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
|
||||||
|
webSocketSet.put(httpSession.getId(), session);
|
||||||
|
log.info("new websocket opened, total socket num: "+ webSocketSet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClose
|
||||||
|
public void onClose(Session session){
|
||||||
|
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
|
||||||
|
if(httpSession!=null) {
|
||||||
|
webSocketSet.remove(httpSession.getId());
|
||||||
|
}
|
||||||
|
log.info("websocket closed, total socket num: "+ webSocketSet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(String msg, Session session){
|
||||||
|
//log.info("websocket: "+msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Session session, Throwable error){
|
||||||
|
error.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessageToAll(String msg){
|
||||||
|
try {
|
||||||
|
for (Session item : webSocketSet.values()) {
|
||||||
|
item.getBasicRemote().sendText(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
log.error("websocket send msg error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(HttpSession httpSession, String msg) {
|
||||||
|
Session session = webSocketSet.get(httpSession.getId());
|
||||||
|
if(session != null){
|
||||||
|
try {
|
||||||
|
session.getBasicRemote().sendText(msg);
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
log.error("websocket send msg error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,267 @@
|
|||||||
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.imdroid.beidou.common.HttpResult;
|
||||||
|
import com.imdroid.common.util.HexUtil;
|
||||||
|
import com.imdroid.secapi.client.HttpResp;
|
||||||
|
import com.imdroid.secapi.client.RtcmClient;
|
||||||
|
import com.imdroid.secapi.client.VersionClient;
|
||||||
|
import com.imdroid.secapi.dto.*;
|
||||||
|
import lombok.Data;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class VersionController extends BasicController{
|
||||||
|
@Value("${version.path}")
|
||||||
|
private String version_path;
|
||||||
|
@Autowired
|
||||||
|
GnssDeviceMapper gnssDeviceMapper;
|
||||||
|
@Autowired
|
||||||
|
private GnssStatusMapper gnssStatusMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RtcmClient rtcmClient;
|
||||||
|
@Autowired
|
||||||
|
OpLogManager opLogManager;
|
||||||
|
@Autowired
|
||||||
|
TenantMapper tenantMapper;
|
||||||
|
@Autowired
|
||||||
|
VersionClient versionClient;
|
||||||
|
@Value("${ver_server.connect.cmd}")
|
||||||
|
String verServerConnectCmd;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FirmwareInfo{
|
||||||
|
String name;
|
||||||
|
long length;
|
||||||
|
long create_time;
|
||||||
|
|
||||||
|
public FirmwareInfo(String n, long l, long t){
|
||||||
|
name = n;
|
||||||
|
length = l;
|
||||||
|
create_time = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正在升级的设备map,deviceId <-> UpgradeInfo(高字节:升级结果;低字节:进度)
|
||||||
|
final Map<String, Short> upgradingDeviceList = new ConcurrentHashMap<>();
|
||||||
|
final Map<String, String> deviceSNtoFirmwareMap=new HashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init(){
|
||||||
|
deviceSNtoFirmwareMap.put("2353","2353");
|
||||||
|
deviceSNtoFirmwareMap.put("2410","2410");
|
||||||
|
deviceSNtoFirmwareMap.put("2412","2410");
|
||||||
|
deviceSNtoFirmwareMap.put("2415","2415");
|
||||||
|
deviceSNtoFirmwareMap.put("2419","2415");
|
||||||
|
deviceSNtoFirmwareMap.put("2421","2421");
|
||||||
|
deviceSNtoFirmwareMap.put("2435","2421");
|
||||||
|
deviceSNtoFirmwareMap.put("2439","2439");
|
||||||
|
deviceSNtoFirmwareMap.put("2445","2439");
|
||||||
|
deviceSNtoFirmwareMap.put("8450","8450");
|
||||||
|
deviceSNtoFirmwareMap.put("8507","8450");
|
||||||
|
deviceSNtoFirmwareMap.put("8512","8450");
|
||||||
|
deviceSNtoFirmwareMap.put("8513","8450");
|
||||||
|
deviceSNtoFirmwareMap.put("8516","8515");
|
||||||
|
deviceSNtoFirmwareMap.put("8522","8515");
|
||||||
|
deviceSNtoFirmwareMap.put("9450","9450");
|
||||||
|
deviceSNtoFirmwareMap.put("9513","9450");
|
||||||
|
deviceSNtoFirmwareMap.put("9522","9515");
|
||||||
|
deviceSNtoFirmwareMap.put("6516","6515");
|
||||||
|
deviceSNtoFirmwareMap.put("6521","6515");
|
||||||
|
deviceSNtoFirmwareMap.put("6522","6515");
|
||||||
|
}
|
||||||
|
/**** 推送页面 *****/
|
||||||
|
@RequestMapping("/sys/ver_mgr")
|
||||||
|
public String gnssDevCfg(Model m, HttpSession session) {
|
||||||
|
initModel(m, session);
|
||||||
|
List<Tenant> tenants = tenantMapper.selectList(null);
|
||||||
|
m.addAttribute("tenant_list", tenants);
|
||||||
|
|
||||||
|
return "/page/gnss_ver_mgr";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** 推送数据 *****/
|
||||||
|
@RequestMapping("/sys/ver_mgr/firmware_list")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject listFirmware(int page, int limit) {
|
||||||
|
List<FirmwareInfo> firmwareInfoList = new ArrayList();
|
||||||
|
int total = getFirmwareList(page, limit,firmwareInfoList);
|
||||||
|
//用PageInfo对结果进行包装,传入连续显示的页数
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("code", 0);
|
||||||
|
jsonObject.put("msg", "");
|
||||||
|
jsonObject.put("count", total);
|
||||||
|
jsonObject.put("data", firmwareInfoList);
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sys/ver_mgr/gnss_device_list")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject list(HttpSession session, int page, int limit, String searchParams) {
|
||||||
|
Page<GnssDevice> pageable = new Page<>(page, limit);
|
||||||
|
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.orderByAsc("deviceid");
|
||||||
|
|
||||||
|
// 条件查询
|
||||||
|
if(searchParams != null) {
|
||||||
|
JSONObject search = (JSONObject) JSONObject.parse(searchParams);
|
||||||
|
//设备号
|
||||||
|
String deviceid = search.getString("deviceid");
|
||||||
|
if (StringUtils.hasText(deviceid)) {
|
||||||
|
queryWrapper.like("deviceid", deviceid);
|
||||||
|
}
|
||||||
|
//项目号
|
||||||
|
String project_id = search.getString("project_id");
|
||||||
|
if (StringUtils.hasText(project_id)) {
|
||||||
|
queryWrapper.like("project_id", project_id);
|
||||||
|
}
|
||||||
|
//所属组织
|
||||||
|
String tenantname = search.getString("tenantname");
|
||||||
|
if (StringUtils.hasText(tenantname)) {
|
||||||
|
if(tenantname.startsWith("非SAAS"))
|
||||||
|
queryWrapper.ne("tenantname",Tenant.SAAS_PROVIDER_NAME);
|
||||||
|
else queryWrapper.like("tenantname", tenantname);
|
||||||
|
}
|
||||||
|
//桩号
|
||||||
|
String sector = search.getString("sector");
|
||||||
|
if (StringUtils.hasText(sector)) {
|
||||||
|
queryWrapper.like("sector", sector);
|
||||||
|
}
|
||||||
|
//设备类型
|
||||||
|
Integer devicetype = search.getInteger("devicetype");
|
||||||
|
if (devicetype != null && devicetype != QUERY_ALL) {
|
||||||
|
queryWrapper.eq("devicetype", devicetype);
|
||||||
|
}
|
||||||
|
//版本
|
||||||
|
String appver = search.getString("appver");
|
||||||
|
if (StringUtils.hasText(appver)) {
|
||||||
|
queryWrapper.like("appver", appver);
|
||||||
|
}
|
||||||
|
//备注
|
||||||
|
String remark = search.getString("remark");
|
||||||
|
if (StringUtils.hasText(remark)) {
|
||||||
|
queryWrapper.like("remark", remark);
|
||||||
|
}
|
||||||
|
//使用状态
|
||||||
|
Integer opmode = search.getInteger("opmode");
|
||||||
|
if (opmode != null && opmode != QUERY_ALL) {
|
||||||
|
queryWrapper.eq("opmode", opmode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPage<GnssDevice> cs = gnssDeviceMapper.selectPage(pageable, queryWrapper);
|
||||||
|
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("code", 0);
|
||||||
|
jsonObject.put("msg", "");
|
||||||
|
jsonObject.put("count", cs.getTotal());
|
||||||
|
jsonObject.put("data", cs.getRecords());
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/sys/ver_mgr/upgrade")
|
||||||
|
@ResponseBody
|
||||||
|
public HttpResult upgradeApp(@RequestParam String firmware, @RequestParam String id_list) throws Exception {
|
||||||
|
HttpResp rsp;
|
||||||
|
if(firmware!=null && !firmware.equals("undefined")) {
|
||||||
|
String[] deviceList = id_list.split(";");
|
||||||
|
//请求版本服务升级
|
||||||
|
try {
|
||||||
|
String invalidId = checkUpgradeList(deviceList, firmware);
|
||||||
|
if(invalidId==null) {
|
||||||
|
rsp = versionClient.upgrade(id_list, firmware);
|
||||||
|
}
|
||||||
|
else return HttpResult.fail("设备型号和固件不匹配:"+invalidId);
|
||||||
|
//System.out.println(id_list);
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
return HttpResult.fail("版本服务未启动");
|
||||||
|
}
|
||||||
|
if(rsp.getCode() == HttpResp.HTTP_RSP_OK){
|
||||||
|
//设置设备的使用状态为升级,下发连接版本服务器指令
|
||||||
|
startUpgrade(deviceList);
|
||||||
|
return HttpResult.success("开始升级");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return HttpResult.fail(rsp.getResponseMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResult.fail("非法固件");
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFirmwareList(int page, int limit, List firmwareInfo){
|
||||||
|
File folder = new File(version_path);
|
||||||
|
File[] files = folder.listFiles((file) -> file.getName().endsWith(".bin"));
|
||||||
|
if(files==null) return 0;
|
||||||
|
|
||||||
|
Arrays.sort(files, new Comparator<File>() {
|
||||||
|
public int compare(File f1, File f2) {
|
||||||
|
long diff = f1.lastModified() - f2.lastModified();
|
||||||
|
if (diff > 0)
|
||||||
|
return -1;
|
||||||
|
else if (diff == 0)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 1;//如果 if 中修改为 返回-1 同时此处修改为返回 1 排序就会是递减
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for(File file:files){
|
||||||
|
if((int)count/limit+1 == page) {
|
||||||
|
firmwareInfo.add(new FirmwareInfo(file.getName(), file.length(), file.lastModified()));
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
String checkUpgradeList(String[] deviceList, String firmware){
|
||||||
|
for(String deviceId:deviceList){
|
||||||
|
String snHigh = deviceId.substring(0,4);
|
||||||
|
if(!firmware.contains(deviceSNtoFirmwareMap.get(snHigh))){
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
void startUpgrade(String[] deviceList){
|
||||||
|
for(String deviceId:deviceList){
|
||||||
|
UpdateWrapper<GnssDevice> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("deviceid",deviceId).set("opmode",GnssDevice.OP_MODE_UPGRADING);
|
||||||
|
gnssDeviceMapper.update(null, updateWrapper);
|
||||||
|
rtcmClient.config(deviceId,getConnectVerServerCmd(deviceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getConnectVerServerCmd(String deviceId){
|
||||||
|
short len = (short) (verServerConnectCmd.length() + 5);
|
||||||
|
return "d31a" + HexUtil.Short2HexString(len)+
|
||||||
|
HexUtil.Int2HexString(Integer.parseInt(deviceId))+
|
||||||
|
"01"+HexUtil.String2HexString(verServerConnectCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -199,8 +199,12 @@ public interface CommonExcelService<T, R> {
|
|||||||
* @param paraValue 查询参数值
|
* @param paraValue 查询参数值
|
||||||
*/
|
*/
|
||||||
default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
|
// null
|
||||||
|
if (paraName.startsWith("NULL") && StringUtils.hasText((String)paraValue)) {
|
||||||
|
addNullQueryWrapper(queryWrapper, paraName, paraValue);
|
||||||
|
}
|
||||||
// String
|
// String
|
||||||
if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) {
|
else if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) {
|
||||||
addStringQueryWrapper(queryWrapper, paraName, paraValue);
|
addStringQueryWrapper(queryWrapper, paraName, paraValue);
|
||||||
}
|
}
|
||||||
// Number,-1表示全部
|
// Number,-1表示全部
|
||||||
@ -221,6 +225,17 @@ public interface CommonExcelService<T, R> {
|
|||||||
return paraName.substring(paraName.indexOf("_") + 1);
|
return paraName.substring(paraName.indexOf("_") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void addNullQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
|
String column = getColumn(paraName);
|
||||||
|
String value = paraValue.toString();
|
||||||
|
if (value.equals("0")) {
|
||||||
|
queryWrapper.isNull(column);
|
||||||
|
}
|
||||||
|
else if(value.equals("1")){
|
||||||
|
queryWrapper.isNotNull(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
String column = getColumn(paraName);
|
String column = getColumn(paraName);
|
||||||
String value = paraValue.toString();
|
String value = paraValue.toString();
|
||||||
|
|||||||
@ -69,6 +69,9 @@ CREATE TABLE IF NOT EXISTS `gnssdevices` (
|
|||||||
`model` smallint DEFAULT 0,
|
`model` smallint DEFAULT 0,
|
||||||
`loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式',
|
`loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式',
|
||||||
`iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号,唯一',
|
`iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号,唯一',
|
||||||
|
`has_battery` bit(1) DEFAULT 0 COMMENT '是否内置电池',
|
||||||
|
`change_flag` int DEFAULT 0 COMMENT '参数改变标识',
|
||||||
|
`voltage_factor` tinyint DEFAULT NULL COMMENT '分压比',
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
@ -352,49 +355,50 @@ CREATE TABLE IF NOT EXISTS `gnssdevicesinglerecords` (
|
|||||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='GNSS单次解算记录表';
|
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='GNSS单次解算记录表';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
SIM卡记录表
|
流量卡信息表
|
||||||
*/
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS `simcards` (
|
CREATE TABLE IF NOT EXISTS `traffic_cards` (
|
||||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一',
|
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一',
|
||||||
`updatetime` DATETIME DEFAULT NULL COMMENT '最新一次查询接口记录的时间',
|
`iccid` VARCHAR(20) NOT NULL COMMENT 'ICCID号,唯一标识SIM卡',
|
||||||
`iccid` VARCHAR(36) NOT NULL COMMENT 'ICCID号,唯一',
|
`msisdn` VARCHAR(20) NOT NULL COMMENT '物联卡号码',
|
||||||
`msisdn` VARCHAR(20) NOT NULL COMMENT '物联卡号码,SIM号,唯一',
|
`status` INT DEFAULT -1 COMMENT 'SIM卡状态(-1:未知, 1:待激活, 2:已激活, 3:停机, 4:注销, 5:库存, 6:可测试, 7:失效, 99:号码不存在)',
|
||||||
`deviceid` VARCHAR(20) DEFAULT NULL COMMENT '设备ID号',
|
`remaining` INT DEFAULT 0 COMMENT '剩余流量,单位MB,为避免小数,存储值为实际值的1000倍',
|
||||||
`status` TINYINT DEFAULT -1 COMMENT 'SIM卡状态(-1:未知, 1:待激活, 2:已激活, 3:停机, 4:注销, 5:库存, 6:可测试, 7:失效, 99:号码不存在)',
|
`total` INT DEFAULT 0 COMMENT '总流量,单位MB,同上',
|
||||||
`remaining` DECIMAL(10,2) DEFAULT 0.00 COMMENT '剩余流量,单位为MB,保留两位小数',
|
`used` INT DEFAULT 0 COMMENT '已用流量,单位MB,同上',
|
||||||
`total` DECIMAL(10,2) DEFAULT 0.00 COMMENT '总流量,单位为MB,保留两位小数',
|
`updatetime` DATETIME DEFAULT NULL COMMENT '最后更新时间',
|
||||||
`used` DECIMAL(10,2) DEFAULT 0.00 COMMENT '已用流量,单位为MB,保留两位小数',
|
`querystatus` TINYINT DEFAULT 0 COMMENT 'SIM卡查询状态:0=正常,1=非当前卡商,2=其他错误',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_iccid` (`iccid`),
|
UNIQUE KEY `uk_iccid` (`iccid`),
|
||||||
UNIQUE KEY `uk_msisd` (`msisdn`),
|
INDEX `idx_status` (`status`),
|
||||||
INDEX `idx_deviceid` (`deviceid`),
|
INDEX `idx_querystatus` (`querystatus`)
|
||||||
INDEX `idx_status` (`status`)
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量卡信息表';
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='SIM卡信息表';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `ehm` (
|
/***
|
||||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
设备-SIM卡映射表
|
||||||
`tenantid` int DEFAULT 0,
|
*/
|
||||||
`deviceid` varchar(64) NOT NULL COMMENT '设备ID',
|
CREATE TABLE IF NOT EXISTS `traffic_device_mappings` (
|
||||||
`createtime` datetime(3) NOT NULL COMMENT '创建时间',
|
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一',
|
||||||
`offlinestatdays` smallint DEFAULT NULL COMMENT '离线统计周期',
|
`deviceid` VARCHAR(20) NOT NULL COMMENT '设备ID',
|
||||||
`offlinecount` int DEFAULT NULL COMMENT '离线统计周期内的离线次数',
|
`iccid` VARCHAR(20) NOT NULL COMMENT 'SIM卡ICCID',
|
||||||
`offlineminutes` int DEFAULT NULL COMMENT '离线统计周期内的离线时长',
|
`starttime` DATETIME NOT NULL COMMENT '开始使用时间',
|
||||||
`invalidresultminutes` int DEFAULT NULL COMMENT '离线统计周期内的连续无效解时长',
|
`endtime` DATETIME DEFAULT NULL COMMENT '结束使用时间,NULL表示当前正在使用',
|
||||||
`calcstathours` int DEFAULT NULL COMMENT '数据分析周期',
|
|
||||||
`stdeve` float DEFAULT NULL COMMENT '标准差',
|
|
||||||
`stdevn` float DEFAULT NULL COMMENT '标准差',
|
|
||||||
`stdevu` float DEFAULT NULL COMMENT '标准差',
|
|
||||||
`fixrate` float DEFAULT NULL COMMENT '固定率',
|
|
||||||
`validrate` float DEFAULT NULL COMMENT '有效解比例',
|
|
||||||
`jumpcount` int DEFAULT NULL COMMENT '周跳次数',
|
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_deviceid_createtime` (`deviceid`,`createtime`),
|
KEY `idx_deviceid` (`deviceid`),
|
||||||
KEY `idx_createtime` (`createtime`)
|
KEY `idx_iccid` (`iccid`),
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
KEY `idx_starttime` (`starttime`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备-SIM卡映射表';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `ehmconfig` (
|
/***
|
||||||
`updatetime` datetime(3) NOT NULL COMMENT '更新时间',
|
流量使用记录表
|
||||||
`offlinestatdays` smallint DEFAULT NULL COMMENT '离线统计周期',
|
*/
|
||||||
`calcstathours` int DEFAULT NULL COMMENT '数据分析周期',
|
CREATE TABLE IF NOT EXISTS `traffic_records` (
|
||||||
PRIMARY KEY (`updatetime`)
|
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,唯一',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
`iccid` VARCHAR(20) NOT NULL COMMENT 'SIM卡ICCID',
|
||||||
|
`recordtime` DATETIME NOT NULL COMMENT '记录时间',
|
||||||
|
`remaining` INT DEFAULT NULL COMMENT '剩余流量,单位MB,为避免小数,存储值为实际值的1000倍',
|
||||||
|
`used` INT DEFAULT NULL COMMENT '已用流量MB',
|
||||||
|
`total` INT DEFAULT NULL COMMENT '总流量MB',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_iccid` (`iccid`),
|
||||||
|
KEY `idx_recordtime` (`recordtime`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量使用记录表';
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
{
|
{
|
||||||
"title": "健康检查报告",
|
"title": "健康检查报告",
|
||||||
"href": "page/gnss_ehm",
|
"href": "page/gnss_ehm",
|
||||||
"icon": "fa fa-tachometer",
|
"icon": "fa fa-heartbeat",
|
||||||
"target": "_self"
|
"target": "_self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -130,7 +130,19 @@
|
|||||||
"target": "_self"
|
"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",
|
"href": "page/sim_traffic_query",
|
||||||
"icon": "fa fa-minus",
|
"icon": "fa fa-minus",
|
||||||
"target": "_self"
|
"target": "_self"
|
||||||
@ -200,6 +212,12 @@
|
|||||||
"href": "sys/apikey",
|
"href": "sys/apikey",
|
||||||
"icon": "fa fa-key",
|
"icon": "fa fa-key",
|
||||||
"target": "_self"
|
"target": "_self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "版本管理",
|
||||||
|
"href": "sys/ver_mgr",
|
||||||
|
"icon": "fa fa-file-code-o",
|
||||||
|
"target": "_self"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
354
sec-beidou/src/main/resources/static/css/ol.css
Normal file
354
sec-beidou/src/main/resources/static/css/ol.css
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
:root,
|
||||||
|
:host {
|
||||||
|
--ol-background-color: white;
|
||||||
|
--ol-accent-background-color: #F5F5F5;
|
||||||
|
--ol-subtle-background-color: rgba(128, 128, 128, 0.25);
|
||||||
|
--ol-partial-background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
--ol-foreground-color: #333333;
|
||||||
|
--ol-subtle-foreground-color: #666666;
|
||||||
|
--ol-brand-color: #00AAFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-box {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1.5px solid var(--ol-background-color);
|
||||||
|
background-color: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-mouse-position {
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-line-inner {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
border-top: none;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1px;
|
||||||
|
will-change: contents, width;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-bar-inner {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-marker {
|
||||||
|
width: 1px;
|
||||||
|
height: 15px;
|
||||||
|
background-color: var(--ol-foreground-color);
|
||||||
|
float: right;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-step-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
font-size: 10px;
|
||||||
|
z-index: 11;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-text {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
bottom: 25px;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
z-index: 9;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-even {
|
||||||
|
background-color: var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-scale-singlebar-odd {
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-unsupported {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport,
|
||||||
|
.ol-unselectable {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport canvas {
|
||||||
|
all: unset;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-viewport {
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-selectable {
|
||||||
|
-webkit-touch-callout: default;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grabbing {
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-grab {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--ol-subtle-background-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom {
|
||||||
|
top: .5em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate {
|
||||||
|
top: .5em;
|
||||||
|
right: .5em;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate.ol-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity .25s linear, visibility 0s linear .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent {
|
||||||
|
top: 4.643em;
|
||||||
|
left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-full-screen {
|
||||||
|
right: .5em;
|
||||||
|
top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button {
|
||||||
|
display: block;
|
||||||
|
margin: 1px;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: inherit;
|
||||||
|
text-align: center;
|
||||||
|
height: 1.375em;
|
||||||
|
width: 1.375em;
|
||||||
|
line-height: .4em;
|
||||||
|
background-color: var(--ol-background-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button::-moz-focus-inner {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom-extent button {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-compass {
|
||||||
|
display: block;
|
||||||
|
font-weight: normal;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-control button {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoom-extent {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-control button:hover,
|
||||||
|
.ol-control button:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
outline: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-in {
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoom .ol-zoom-out {
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution {
|
||||||
|
text-align: right;
|
||||||
|
bottom: .5em;
|
||||||
|
right: .5em;
|
||||||
|
max-width: calc(100% - 1.3em);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution a {
|
||||||
|
color: var(--ol-subtle-foreground-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px .5em;
|
||||||
|
color: var(--ol-foreground-color);
|
||||||
|
text-shadow: 0 0 2px var(--ol-background-color);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li {
|
||||||
|
display: inline;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution li:not(:last-child):after {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution img {
|
||||||
|
max-height: 2em;
|
||||||
|
max-width: inherit;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-collapsed ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-partial-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible img {
|
||||||
|
margin-top: -.2em;
|
||||||
|
max-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-attribution.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider {
|
||||||
|
top: 4.5em;
|
||||||
|
left: .5em;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-zoomslider button {
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-touch .ol-zoomslider {
|
||||||
|
top: 5.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap {
|
||||||
|
left: 0.5em;
|
||||||
|
bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-uncollapsible {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-map {
|
||||||
|
border: 1px solid var(--ol-subtle-foreground-color);
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) button {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
|
||||||
|
.ol-overviewmap.ol-uncollapsible button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap:not(.ol-collapsed) {
|
||||||
|
background: var(--ol-subtle-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap-box {
|
||||||
|
border: 1.5px dotted var(--ol-subtle-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-overviewmap-box:hover {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-overviewmap .ol-viewport:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* 坐标转换工具模块
|
||||||
|
* 提供WGS84和GCJ-02坐标系之间的转换功能
|
||||||
|
*/
|
||||||
|
var CoordinateUtils = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var pi = 3.14159265358979324;
|
||||||
|
var a = 6378245.0;
|
||||||
|
var ee = 0.00669342162296594323;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否在国内,不在国内则不做偏移
|
||||||
|
*/
|
||||||
|
function outOfChina(lon, lat) {
|
||||||
|
if ((lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformLat(x, y) {
|
||||||
|
var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformLon(x, y) {
|
||||||
|
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function transform(wgLat, wgLon) {
|
||||||
|
var mars_point = {lon: 0, lat: 0};
|
||||||
|
if (outOfChina(wgLon, wgLat)) {
|
||||||
|
mars_point.lat = wgLat;
|
||||||
|
mars_point.lon = wgLon;
|
||||||
|
return mars_point;
|
||||||
|
}
|
||||||
|
var dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
|
||||||
|
var dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
|
||||||
|
var radLat = wgLat / 180.0 * pi;
|
||||||
|
var magic = Math.sin(radLat);
|
||||||
|
magic = 1 - ee * magic * magic;
|
||||||
|
var sqrtMagic = Math.sqrt(magic);
|
||||||
|
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||||
|
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||||
|
mars_point.lat = wgLat + dLat;
|
||||||
|
mars_point.lon = wgLon + dLon;
|
||||||
|
return mars_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getMapCoordinates(lat, lon, mapType) {
|
||||||
|
var coordinates;
|
||||||
|
if (mapType === 'amap' || mapType === 'amap_satellite') {
|
||||||
|
// 高德地图 WGS84 转换为 GCJ-02
|
||||||
|
var gcjCoord = transform(lat, lon);
|
||||||
|
coordinates = ol.proj.fromLonLat([gcjCoord.lon, gcjCoord.lat]);
|
||||||
|
} else if (mapType.startsWith('google_')) {
|
||||||
|
// Google地图使用WGS84坐标系
|
||||||
|
coordinates = ol.proj.fromLonLat([lon, lat]);
|
||||||
|
} else {
|
||||||
|
// 天地图 CGCS2000,与WGS84实质一样
|
||||||
|
coordinates = ol.proj.fromLonLat([lon, lat]);
|
||||||
|
}
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
transform: transform,
|
||||||
|
getMapCoordinates: getMapCoordinates,
|
||||||
|
outOfChina: outOfChina
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,369 @@
|
|||||||
|
/**
|
||||||
|
* 设备标记管理模块
|
||||||
|
* 负责设备标记的创建、分类和显示管理
|
||||||
|
*/
|
||||||
|
var DeviceMarkers = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var greenFeatures = [];
|
||||||
|
var orangeFeatures = [];
|
||||||
|
var redFeatures = [];
|
||||||
|
var allFeatures = [];
|
||||||
|
var myLocationFeature = null;
|
||||||
|
var myLocationInterval = null;
|
||||||
|
var showDeviceId = true;
|
||||||
|
var minZoomForLabels = 4;
|
||||||
|
|
||||||
|
|
||||||
|
var map = null;
|
||||||
|
var vectorSource = null;
|
||||||
|
var vectorLayer = null;
|
||||||
|
|
||||||
|
|
||||||
|
function init(mapInstance, vectorSourceInstance, vectorLayerInstance) {
|
||||||
|
map = mapInstance;
|
||||||
|
vectorSource = vectorSourceInstance;
|
||||||
|
vectorLayer = vectorLayerInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createDeviceStyle(feature) {
|
||||||
|
if (feature.get('isMyLocation')) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Icon({
|
||||||
|
anchor: [0.5, 1],
|
||||||
|
src: '../images/loc_blue.png',
|
||||||
|
scale: 0.7
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceInfo = feature.get('deviceInfo');
|
||||||
|
if (!deviceInfo) return null;
|
||||||
|
|
||||||
|
var iconSrc;
|
||||||
|
var color = '#000';
|
||||||
|
var isHovered = feature.get('hovered') === true;
|
||||||
|
var scale = isHovered ? 0.85 : 0.7;
|
||||||
|
|
||||||
|
// 根据告警级别选择图标
|
||||||
|
if (deviceInfo.warning == 2) {
|
||||||
|
iconSrc = '../images/loc1_red.png';
|
||||||
|
} else if (deviceInfo.warning == 1) {
|
||||||
|
iconSrc = '../images/loc1_orange.png';
|
||||||
|
} else {
|
||||||
|
iconSrc = '../images/loc1_green.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = new ol.style.Style({
|
||||||
|
image: new ol.style.Icon({
|
||||||
|
anchor: [0.5, 1],
|
||||||
|
src: iconSrc,
|
||||||
|
scale: scale
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据缩放级别和设置决定是否显示设备ID
|
||||||
|
if (showDeviceId && map && map.getView().getZoom() >= minZoomForLabels) {
|
||||||
|
style.setText(new ol.style.Text({
|
||||||
|
text: deviceInfo.deviceid,
|
||||||
|
offsetY: -30,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: isHovered ? '#1aa094' : color
|
||||||
|
}),
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#fff',
|
||||||
|
width: isHovered ? 3 : 2
|
||||||
|
}),
|
||||||
|
font: isHovered ? 'bold 12px Arial' : '12px Arial'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addDeviceMarkers(deviceList) {
|
||||||
|
if (!vectorSource || !deviceList) return;
|
||||||
|
|
||||||
|
var savedMyLocationFeature = myLocationFeature;
|
||||||
|
|
||||||
|
vectorSource.clear();
|
||||||
|
greenFeatures = [];
|
||||||
|
orangeFeatures = [];
|
||||||
|
redFeatures = [];
|
||||||
|
allFeatures = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < deviceList.length; i++) {
|
||||||
|
var device = deviceList[i];
|
||||||
|
var currentMapType = getCurrentMapType();
|
||||||
|
var mapCoordinates = CoordinateUtils.getMapCoordinates(
|
||||||
|
device.latitude,
|
||||||
|
device.longitude,
|
||||||
|
currentMapType
|
||||||
|
);
|
||||||
|
|
||||||
|
var feature = new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(mapCoordinates),
|
||||||
|
deviceInfo: device
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按告警级别分类
|
||||||
|
if (device.warning == 2) {
|
||||||
|
redFeatures.push(feature);
|
||||||
|
} else if (device.warning == 1) {
|
||||||
|
orangeFeatures.push(feature);
|
||||||
|
} else {
|
||||||
|
greenFeatures.push(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
allFeatures.push(feature);
|
||||||
|
vectorSource.addFeature(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedMyLocationFeature) {
|
||||||
|
vectorSource.addFeature(savedMyLocationFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制更新样式
|
||||||
|
if (vectorLayer) {
|
||||||
|
vectorLayer.changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getCurrentMapType() {
|
||||||
|
var mapTypeSelect = document.getElementById('mapTypeSelectNew');
|
||||||
|
return mapTypeSelect ? mapTypeSelect.value : 'tianditu_satellite';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showAllDevices() {
|
||||||
|
if (!vectorSource) return;
|
||||||
|
|
||||||
|
var savedMyLocationFeature = myLocationFeature;
|
||||||
|
vectorSource.clear();
|
||||||
|
|
||||||
|
if (savedMyLocationFeature) {
|
||||||
|
vectorSource.addFeature(savedMyLocationFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < allFeatures.length; i++) {
|
||||||
|
vectorSource.addFeature(allFeatures[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning1Devices() {
|
||||||
|
if (!vectorSource) return;
|
||||||
|
|
||||||
|
var savedMyLocationFeature = myLocationFeature;
|
||||||
|
vectorSource.clear();
|
||||||
|
|
||||||
|
if (savedMyLocationFeature) {
|
||||||
|
vectorSource.addFeature(savedMyLocationFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < allFeatures.length; i++) {
|
||||||
|
vectorSource.addFeature(allFeatures[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideGreenFeatures();
|
||||||
|
hideRedFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showWarning2Devices() {
|
||||||
|
if (!vectorSource) return;
|
||||||
|
|
||||||
|
var savedMyLocationFeature = myLocationFeature;
|
||||||
|
vectorSource.clear();
|
||||||
|
|
||||||
|
if (savedMyLocationFeature) {
|
||||||
|
vectorSource.addFeature(savedMyLocationFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < allFeatures.length; i++) {
|
||||||
|
vectorSource.addFeature(allFeatures[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideGreenFeatures();
|
||||||
|
hideOrangeFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideGreenFeatures() {
|
||||||
|
for (var i = 0; i < greenFeatures.length; i++) {
|
||||||
|
vectorSource.removeFeature(greenFeatures[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOrangeFeatures() {
|
||||||
|
for (var i = 0; i < orangeFeatures.length; i++) {
|
||||||
|
vectorSource.removeFeature(orangeFeatures[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideRedFeatures() {
|
||||||
|
for (var i = 0; i < redFeatures.length; i++) {
|
||||||
|
vectorSource.removeFeature(redFeatures[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDeviceById(deviceId) {
|
||||||
|
// console.log('搜索设备:', deviceId, '(类型:', typeof deviceId, ')');
|
||||||
|
// console.log('当前设备总数:', allFeatures.length);
|
||||||
|
|
||||||
|
var searchTerm = String(deviceId).trim();
|
||||||
|
|
||||||
|
for (var i = 0; i < allFeatures.length; i++) {
|
||||||
|
var feature = allFeatures[i];
|
||||||
|
var deviceInfo = feature.get('deviceInfo');
|
||||||
|
if (deviceInfo) {
|
||||||
|
var currentDeviceId = String(deviceInfo.deviceid);
|
||||||
|
// console.log('检查设备:', currentDeviceId, '(类型:', typeof deviceInfo.deviceid, ')');
|
||||||
|
|
||||||
|
if (currentDeviceId === searchTerm) {
|
||||||
|
// console.log('精确匹配找到设备:', currentDeviceId);
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDeviceId.indexOf(searchTerm) !== -1) {
|
||||||
|
// console.log('部分匹配找到设备:', currentDeviceId);
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDeviceId.replace(/\s+/g, '') === searchTerm.replace(/\s+/g, '')) {
|
||||||
|
// console.log('去除空格后匹配找到设备:', currentDeviceId);
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('未找到设备:', deviceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function locateDevice(deviceId) {
|
||||||
|
var feature = findDeviceById(deviceId);
|
||||||
|
if (feature && map) {
|
||||||
|
var geometry = feature.getGeometry();
|
||||||
|
var coordinates = geometry.getCoordinates();
|
||||||
|
|
||||||
|
map.getView().animate({
|
||||||
|
center: coordinates,
|
||||||
|
zoom: Math.max(map.getView().getZoom(), 15),
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的位置
|
||||||
|
*/
|
||||||
|
function getMyLocation() {
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function(position) {
|
||||||
|
var lat = position.coords.latitude;
|
||||||
|
var lon = position.coords.longitude;
|
||||||
|
|
||||||
|
var currentMapType = getCurrentMapType();
|
||||||
|
var coordinates = CoordinateUtils.getMapCoordinates(lat, lon, currentMapType);
|
||||||
|
|
||||||
|
if (myLocationFeature) {
|
||||||
|
vectorSource.removeFeature(myLocationFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
myLocationFeature = new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(coordinates),
|
||||||
|
isMyLocation: true
|
||||||
|
});
|
||||||
|
|
||||||
|
vectorSource.addFeature(myLocationFeature);
|
||||||
|
|
||||||
|
map.getView().animate({
|
||||||
|
center: coordinates,
|
||||||
|
zoom: 15,
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
console.error('获取位置失败:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function startLocationUpdates() {
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
myLocationInterval = setInterval(function() {
|
||||||
|
getMyLocation();
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function stopLocationUpdates() {
|
||||||
|
if (myLocationInterval) {
|
||||||
|
clearInterval(myLocationInterval);
|
||||||
|
myLocationInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateMyLocationForMapType(mapType) {
|
||||||
|
if (myLocationFeature) {
|
||||||
|
var geometry = myLocationFeature.getGeometry();
|
||||||
|
var coordinates = geometry.getCoordinates();
|
||||||
|
var lonLat = ol.proj.toLonLat(coordinates);
|
||||||
|
|
||||||
|
var newCoordinates = CoordinateUtils.getMapCoordinates(lonLat[1], lonLat[0], mapType);
|
||||||
|
|
||||||
|
myLocationFeature.setGeometry(new ol.geom.Point(newCoordinates));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setShowDeviceId(show) {
|
||||||
|
showDeviceId = show;
|
||||||
|
if (vectorLayer) {
|
||||||
|
vectorLayer.changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getDeviceStats() {
|
||||||
|
return {
|
||||||
|
total: allFeatures.length,
|
||||||
|
green: greenFeatures.length,
|
||||||
|
orange: orangeFeatures.length,
|
||||||
|
red: redFeatures.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
createDeviceStyle: createDeviceStyle,
|
||||||
|
addDeviceMarkers: addDeviceMarkers,
|
||||||
|
showAllDevices: showAllDevices,
|
||||||
|
showWarning1Devices: showWarning1Devices,
|
||||||
|
showWarning2Devices: showWarning2Devices,
|
||||||
|
hideGreenFeatures: hideGreenFeatures,
|
||||||
|
hideOrangeFeatures: hideOrangeFeatures,
|
||||||
|
hideRedFeatures: hideRedFeatures,
|
||||||
|
findDeviceById: findDeviceById,
|
||||||
|
locateDevice: locateDevice,
|
||||||
|
getMyLocation: getMyLocation,
|
||||||
|
startLocationUpdates: startLocationUpdates,
|
||||||
|
stopLocationUpdates: stopLocationUpdates,
|
||||||
|
updateMyLocationForMapType: updateMyLocationForMapType,
|
||||||
|
setShowDeviceId: setShowDeviceId,
|
||||||
|
getDeviceStats: getDeviceStats,
|
||||||
|
|
||||||
|
getAllFeatures: function() { return allFeatures; },
|
||||||
|
getGreenFeatures: function() { return greenFeatures; },
|
||||||
|
getOrangeFeatures: function() { return orangeFeatures; },
|
||||||
|
getRedFeatures: function() { return redFeatures; },
|
||||||
|
getMyLocationFeature: function() { return myLocationFeature; }
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* 设备总览主入口文件
|
||||||
|
* 负责整个设备总览模块的初始化和全局函数暴露
|
||||||
|
*/
|
||||||
|
var DeviceOverview = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 全局变量(从原HTML中提取)
|
||||||
|
var deviceList = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化设备总览模块
|
||||||
|
* @param {Array} devices - 设备列表数据
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
*/
|
||||||
|
function init(devices, options) {
|
||||||
|
deviceList = devices || [];
|
||||||
|
|
||||||
|
// 设置全局变量供其他模块使用
|
||||||
|
window.deviceList = deviceList;
|
||||||
|
window.userRole = options && options.role ? options.role : 'USER';
|
||||||
|
|
||||||
|
// 等待layui加载完成后初始化
|
||||||
|
layui.use(['form'], function(){
|
||||||
|
var form = layui.form;
|
||||||
|
|
||||||
|
// 绑定表单事件
|
||||||
|
form.on('select(mapTypeNew)', function(data){
|
||||||
|
MapCore.switchMapType(data.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化地图核心
|
||||||
|
MapCore.initialize(deviceList);
|
||||||
|
|
||||||
|
// 默认显示所有设备
|
||||||
|
document.getElementById('warningFilter').value = 'all';
|
||||||
|
SearchFilter.showAllDevices();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露给HTML使用的全局函数
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图类型变化处理
|
||||||
|
*/
|
||||||
|
function onMapTypeChange() {
|
||||||
|
return MapCore.onMapTypeChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索设备
|
||||||
|
*/
|
||||||
|
function searchDeviceNew() {
|
||||||
|
return MapCore.searchDeviceNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警过滤变化处理
|
||||||
|
*/
|
||||||
|
function onWarningFilterChange() {
|
||||||
|
return MapCore.onWarningFilterChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换地图功能菜单
|
||||||
|
*/
|
||||||
|
function toggleMapFunctionsMenu() {
|
||||||
|
return MapCore.toggleMapFunctionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换设备ID显示
|
||||||
|
*/
|
||||||
|
function toggleDeviceId() {
|
||||||
|
return MapCore.toggleDeviceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换集群显示
|
||||||
|
*/
|
||||||
|
function toggleCluster() {
|
||||||
|
return MapCore.toggleCluster();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换测距功能
|
||||||
|
*/
|
||||||
|
function toggleMeasureDistance() {
|
||||||
|
return MeasureTools.toggleMeasureDistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成测量
|
||||||
|
*/
|
||||||
|
function finishMeasuring() {
|
||||||
|
return MeasureTools.finishMeasuring();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除测距
|
||||||
|
*/
|
||||||
|
function clearMeasure() {
|
||||||
|
return MeasureTools.clearMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换天气预报功能
|
||||||
|
*/
|
||||||
|
function toggleWeatherForecast() {
|
||||||
|
return WeatherForecast.toggleWeatherForecast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭天气卡片
|
||||||
|
*/
|
||||||
|
function closeWeatherCard() {
|
||||||
|
return WeatherForecast.closeWeatherCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示上一个天气预报
|
||||||
|
*/
|
||||||
|
function showPrevForecast() {
|
||||||
|
return WeatherForecast.showPrevForecast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示下一个天气预报
|
||||||
|
*/
|
||||||
|
function showNextForecast() {
|
||||||
|
return WeatherForecast.showNextForecast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备
|
||||||
|
* @param {string} statusType - 状态类型
|
||||||
|
*/
|
||||||
|
function queryDevices(statusType) {
|
||||||
|
return SearchFilter.queryDevices(statusType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位设备到地图
|
||||||
|
* @param {string} deviceId - 设备ID
|
||||||
|
* @param {number} latitude - 纬度
|
||||||
|
* @param {number} longitude - 经度
|
||||||
|
*/
|
||||||
|
function locateDeviceOnMap(deviceId, latitude, longitude) {
|
||||||
|
return SearchFilter.locateDeviceOnMap(deviceId, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接定位设备
|
||||||
|
* @param {string} deviceId - 设备ID
|
||||||
|
*/
|
||||||
|
function locateDeviceDirectly(deviceId) {
|
||||||
|
return SearchFilter.locateDeviceDirectly(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换地图类型
|
||||||
|
* @param {string} mapType - 地图类型
|
||||||
|
*/
|
||||||
|
function switchMapType(mapType) {
|
||||||
|
return MapCore.switchMapType(mapType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公开API
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
|
||||||
|
// 地图相关
|
||||||
|
onMapTypeChange: onMapTypeChange,
|
||||||
|
switchMapType: switchMapType,
|
||||||
|
|
||||||
|
// 搜索和过滤
|
||||||
|
searchDeviceNew: searchDeviceNew,
|
||||||
|
onWarningFilterChange: onWarningFilterChange,
|
||||||
|
queryDevices: queryDevices,
|
||||||
|
locateDeviceOnMap: locateDeviceOnMap,
|
||||||
|
locateDeviceDirectly: locateDeviceDirectly,
|
||||||
|
|
||||||
|
// 地图功能
|
||||||
|
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
|
||||||
|
toggleDeviceId: toggleDeviceId,
|
||||||
|
toggleCluster: toggleCluster,
|
||||||
|
|
||||||
|
// 测距工具
|
||||||
|
toggleMeasureDistance: toggleMeasureDistance,
|
||||||
|
finishMeasuring: finishMeasuring,
|
||||||
|
clearMeasure: clearMeasure,
|
||||||
|
|
||||||
|
// 天气预报
|
||||||
|
toggleWeatherForecast: toggleWeatherForecast,
|
||||||
|
closeWeatherCard: closeWeatherCard,
|
||||||
|
showPrevForecast: showPrevForecast,
|
||||||
|
showNextForecast: showNextForecast
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 将主要函数暴露到全局作用域,供HTML中的onclick等使用
|
||||||
|
window.DeviceOverview = DeviceOverview;
|
||||||
|
window.onMapTypeChange = DeviceOverview.onMapTypeChange;
|
||||||
|
window.searchDeviceNew = DeviceOverview.searchDeviceNew;
|
||||||
|
window.onWarningFilterChange = DeviceOverview.onWarningFilterChange;
|
||||||
|
window.toggleMapFunctionsMenu = DeviceOverview.toggleMapFunctionsMenu;
|
||||||
|
window.toggleDeviceId = DeviceOverview.toggleDeviceId;
|
||||||
|
window.toggleCluster = DeviceOverview.toggleCluster;
|
||||||
|
window.toggleMeasureDistance = DeviceOverview.toggleMeasureDistance;
|
||||||
|
window.finishMeasuring = DeviceOverview.finishMeasuring;
|
||||||
|
window.clearMeasure = DeviceOverview.clearMeasure;
|
||||||
|
window.toggleWeatherForecast = DeviceOverview.toggleWeatherForecast;
|
||||||
|
window.closeWeatherCard = DeviceOverview.closeWeatherCard;
|
||||||
|
window.showPrevForecast = DeviceOverview.showPrevForecast;
|
||||||
|
window.showNextForecast = DeviceOverview.showNextForecast;
|
||||||
|
window.queryDevices = DeviceOverview.queryDevices;
|
||||||
|
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
|
||||||
|
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
|
||||||
@ -0,0 +1,360 @@
|
|||||||
|
/**
|
||||||
|
* 地图核心模块
|
||||||
|
* 负责地图初始化、事件处理和核心功能管理
|
||||||
|
*/
|
||||||
|
var MapCore = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var map = null;
|
||||||
|
var vectorSource = null;
|
||||||
|
var vectorLayer = null;
|
||||||
|
var clusterSource = null;
|
||||||
|
var clusterLayer = null;
|
||||||
|
var currentBaseLayer = null;
|
||||||
|
var showDeviceId = true;
|
||||||
|
var showCluster = true;
|
||||||
|
var minZoomForLabels = 4;
|
||||||
|
var maxZoomForClustering = 8;
|
||||||
|
var hoveredFeature = null;
|
||||||
|
|
||||||
|
|
||||||
|
function initialize(deviceList) {
|
||||||
|
// 创建矢量数据源和图层
|
||||||
|
vectorSource = new ol.source.Vector();
|
||||||
|
vectorLayer = new ol.layer.Vector({
|
||||||
|
source: vectorSource,
|
||||||
|
style: function(feature) {
|
||||||
|
return DeviceMarkers.createDeviceStyle(feature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建集群数据源和图层
|
||||||
|
clusterSource = new ol.source.Cluster({
|
||||||
|
distance: 40,
|
||||||
|
source: vectorSource
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterLayer = new ol.layer.Vector({
|
||||||
|
source: clusterSource,
|
||||||
|
style: function(feature) {
|
||||||
|
var size = feature.get('features').length;
|
||||||
|
var style = new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 15,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: '#3399CC'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: size.toString(),
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: '#fff'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var initialMapType = document.getElementById('mapTypeSelectNew').value || 'tianditu_satellite';
|
||||||
|
currentBaseLayer = MapLayers.getLayer(initialMapType);
|
||||||
|
|
||||||
|
map = new ol.Map({
|
||||||
|
target: 'map-container',
|
||||||
|
layers: [
|
||||||
|
currentBaseLayer,
|
||||||
|
clusterLayer,
|
||||||
|
vectorLayer
|
||||||
|
],
|
||||||
|
view: new ol.View({
|
||||||
|
center: ol.proj.fromLonLat([116.404, 39.915]),
|
||||||
|
zoom: 7
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
DeviceMarkers.init(map, vectorSource, vectorLayer);
|
||||||
|
MeasureTools.init(map);
|
||||||
|
WeatherForecast.init();
|
||||||
|
|
||||||
|
var scaleLineControl = new ol.control.ScaleLine();
|
||||||
|
map.addControl(scaleLineControl);
|
||||||
|
|
||||||
|
var initialZoom = map.getView().getZoom();
|
||||||
|
updateLayerVisibility(initialZoom);
|
||||||
|
|
||||||
|
map.getView().on('change:resolution', function() {
|
||||||
|
var zoom = map.getView().getZoom();
|
||||||
|
updateLayerVisibility(zoom);
|
||||||
|
vectorLayer.changed();
|
||||||
|
});
|
||||||
|
|
||||||
|
bindMouseEvents();
|
||||||
|
|
||||||
|
if (deviceList && deviceList.length > 0) {
|
||||||
|
setCenterFromDevices(deviceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceMarkers.addDeviceMarkers(deviceList);
|
||||||
|
|
||||||
|
DeviceMarkers.getMyLocation();
|
||||||
|
DeviceMarkers.startLocationUpdates();
|
||||||
|
|
||||||
|
document.getElementById('showDeviceIdSwitch').checked = showDeviceId;
|
||||||
|
document.getElementById('showClusterSwitch').checked = showCluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLayerVisibility(zoom) {
|
||||||
|
if (showCluster) {
|
||||||
|
if (zoom >= maxZoomForClustering) {
|
||||||
|
clusterLayer.setVisible(false);
|
||||||
|
vectorLayer.setVisible(true);
|
||||||
|
} else {
|
||||||
|
clusterLayer.setVisible(true);
|
||||||
|
vectorLayer.setVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clusterLayer.setVisible(false);
|
||||||
|
vectorLayer.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindMouseEvents() {
|
||||||
|
map.on('pointermove', function(evt) {
|
||||||
|
if (evt.dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixel = map.getEventPixel(evt.originalEvent);
|
||||||
|
var hit = map.hasFeatureAtPixel(pixel);
|
||||||
|
|
||||||
|
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
|
||||||
|
|
||||||
|
var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
|
||||||
|
return feature;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hoveredFeature && hoveredFeature !== feature) {
|
||||||
|
hoveredFeature.set('hovered', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature) {
|
||||||
|
// 处理集群
|
||||||
|
var features = feature.get('features');
|
||||||
|
if (features && features.length === 1) {
|
||||||
|
features[0].set('hovered', true);
|
||||||
|
hoveredFeature = features[0];
|
||||||
|
} else if (!features && feature.get('deviceInfo')) {
|
||||||
|
feature.set('hovered', true);
|
||||||
|
hoveredFeature = feature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vectorLayer.changed();
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('click', function(evt) {
|
||||||
|
var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
|
||||||
|
return feature;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (feature) {
|
||||||
|
var features = feature.get('features');
|
||||||
|
if (features && features.length > 1) {
|
||||||
|
// 集群点击,扩展视图
|
||||||
|
var extent = vectorSource.getExtent();
|
||||||
|
map.getView().fit(extent, {
|
||||||
|
padding: [50, 50, 50, 50],
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
} else if (features && features.length === 1) {
|
||||||
|
// 单个设备点击
|
||||||
|
var deviceInfo = features[0].get('deviceInfo');
|
||||||
|
if (deviceInfo) {
|
||||||
|
showDeviceInfo(deviceInfo);
|
||||||
|
// 如果天气预测开启,显示天气卡片
|
||||||
|
if (WeatherForecast.isEnabled()) {
|
||||||
|
WeatherForecast.showWeatherForecast(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (feature.get('deviceInfo')) {
|
||||||
|
var deviceInfo = feature.get('deviceInfo');
|
||||||
|
showDeviceInfo(deviceInfo);
|
||||||
|
// 如果天气预测开启,显示天气卡片
|
||||||
|
if (WeatherForecast.isEnabled()) {
|
||||||
|
WeatherForecast.showWeatherForecast(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showDeviceInfo(deviceInfo) {
|
||||||
|
var statusText = '';
|
||||||
|
|
||||||
|
if (deviceInfo.warning === 2) {
|
||||||
|
statusText = '严重告警';
|
||||||
|
} else if (deviceInfo.warning === 1) {
|
||||||
|
statusText = '一般告警';
|
||||||
|
} else {
|
||||||
|
statusText = '正常';
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoMsg = ' 设备: ' + deviceInfo.deviceid +
|
||||||
|
' | 状态: ' + statusText +
|
||||||
|
' | 坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4);
|
||||||
|
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg(infoMsg, {
|
||||||
|
time: 3000,
|
||||||
|
area: ['auto', 'auto'],
|
||||||
|
offset: 'auto'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCenterFromDevices(deviceList) {
|
||||||
|
if (!deviceList || deviceList.length === 0) return;
|
||||||
|
|
||||||
|
var minLat = deviceList[0].latitude;
|
||||||
|
var maxLat = deviceList[0].latitude;
|
||||||
|
var minLon = deviceList[0].longitude;
|
||||||
|
var maxLon = deviceList[0].longitude;
|
||||||
|
|
||||||
|
for (var i = 1; i < deviceList.length; i++) {
|
||||||
|
var device = deviceList[i];
|
||||||
|
minLat = Math.min(minLat, device.latitude);
|
||||||
|
maxLat = Math.max(maxLat, device.latitude);
|
||||||
|
minLon = Math.min(minLon, device.longitude);
|
||||||
|
maxLon = Math.max(maxLon, device.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
var centerLat = (minLat + maxLat) / 2;
|
||||||
|
var centerLon = (minLon + maxLon) / 2;
|
||||||
|
|
||||||
|
map.getView().setCenter(ol.proj.fromLonLat([centerLon, centerLat]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchMapType(mapType) {
|
||||||
|
if (!MapLayers.hasLayer(mapType)) {
|
||||||
|
console.error('未知的地图类型:', mapType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeLayer(currentBaseLayer);
|
||||||
|
currentBaseLayer = MapLayers.getLayer(mapType);
|
||||||
|
map.getLayers().insertAt(0, currentBaseLayer);
|
||||||
|
|
||||||
|
DeviceMarkers.updateMyLocationForMapType(mapType);
|
||||||
|
|
||||||
|
// 重新获取设备列表并添加标记
|
||||||
|
var deviceList = window.deviceList || [];
|
||||||
|
DeviceMarkers.addDeviceMarkers(deviceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDeviceId() {
|
||||||
|
showDeviceId = document.getElementById('showDeviceIdSwitch').checked;
|
||||||
|
DeviceMarkers.setShowDeviceId(showDeviceId);
|
||||||
|
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCluster() {
|
||||||
|
showCluster = document.getElementById('showClusterSwitch').checked;
|
||||||
|
var zoom = map.getView().getZoom();
|
||||||
|
updateLayerVisibility(zoom);
|
||||||
|
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMapFunctionsMenu() {
|
||||||
|
var menu = document.getElementById('mapFunctionsMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.toggle('show');
|
||||||
|
if (menu.classList.contains('show')) {
|
||||||
|
document.addEventListener('click', closeMapFunctionsMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭地图功能菜单
|
||||||
|
* @param {Event} event - 点击事件
|
||||||
|
*/
|
||||||
|
function closeMapFunctionsMenu(event) {
|
||||||
|
var dropdown = document.getElementById('mapFunctionsMenu');
|
||||||
|
var toggleBtn = document.querySelector('.dropdown-toggle');
|
||||||
|
|
||||||
|
if (dropdown && !dropdown.contains(event.target) && !toggleBtn.contains(event.target)) {
|
||||||
|
dropdown.classList.remove('show');
|
||||||
|
document.removeEventListener('click', closeMapFunctionsMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图类型变化处理
|
||||||
|
*/
|
||||||
|
function onMapTypeChange() {
|
||||||
|
var mapType = document.getElementById('mapTypeSelectNew').value;
|
||||||
|
switchMapType(mapType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWarningFilterChange() {
|
||||||
|
var filterValue = document.getElementById('warningFilter').value;
|
||||||
|
switch(filterValue) {
|
||||||
|
case 'all':
|
||||||
|
SearchFilter.showAllDevices();
|
||||||
|
break;
|
||||||
|
case 'warning1':
|
||||||
|
SearchFilter.showWarning1Devices();
|
||||||
|
break;
|
||||||
|
case 'warning2':
|
||||||
|
SearchFilter.showWarning2Devices();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchDeviceNew() {
|
||||||
|
var searchInput = document.getElementById('deviceSearchNew');
|
||||||
|
if (searchInput) {
|
||||||
|
var deviceId = searchInput.value.trim();
|
||||||
|
if (deviceId) {
|
||||||
|
SearchFilter.searchDevice(deviceId);
|
||||||
|
} else {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('请输入设备编号');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVectorSource() {
|
||||||
|
return vectorSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVectorLayer() {
|
||||||
|
return vectorLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialize: initialize,
|
||||||
|
switchMapType: switchMapType,
|
||||||
|
toggleDeviceId: toggleDeviceId,
|
||||||
|
toggleCluster: toggleCluster,
|
||||||
|
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
|
||||||
|
onMapTypeChange: onMapTypeChange,
|
||||||
|
onWarningFilterChange: onWarningFilterChange,
|
||||||
|
searchDeviceNew: searchDeviceNew,
|
||||||
|
getMap: getMap,
|
||||||
|
getVectorSource: getVectorSource,
|
||||||
|
getVectorLayer: getVectorLayer
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
/**
|
||||||
|
* 地图图层管理模块
|
||||||
|
* 管理不同类型的地图图层(天地图、高德、谷歌等)
|
||||||
|
*/
|
||||||
|
var MapLayers = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 天地图 API 密钥 (fengyarnom@gmail.com)
|
||||||
|
var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac';
|
||||||
|
|
||||||
|
function createTiandituTileLoadFunction() {
|
||||||
|
return function(imageTile, src) {
|
||||||
|
imageTile.getImage().src = src;
|
||||||
|
imageTile.getImage().onerror = function() {
|
||||||
|
// 天地图加载失败时切换到高德地图
|
||||||
|
var mapTypeSelect = document.getElementById('mapTypeSelectNew');
|
||||||
|
if(mapTypeSelect && mapTypeSelect.value.startsWith('tianditu_')) {
|
||||||
|
mapTypeSelect.value = 'amap';
|
||||||
|
if (window.DeviceOverview && typeof window.DeviceOverview.switchMapType === 'function') {
|
||||||
|
window.DeviceOverview.switchMapType('amap');
|
||||||
|
}
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('天地图加载失败,已自动切换到高德地图');
|
||||||
|
}
|
||||||
|
if (window.layui && window.layui.form) {
|
||||||
|
window.layui.form.render('select');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var mapLayers = {
|
||||||
|
// 高德地图
|
||||||
|
amap: new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 高德卫星图
|
||||||
|
amap_satellite: new ol.layer.Group({
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 谷歌卫星图
|
||||||
|
google_satellite: new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://mt{0-3}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 谷歌地形图
|
||||||
|
google_terrain: new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://mt{0-3}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 谷歌道路图
|
||||||
|
google_roadmap: new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 谷歌混合图
|
||||||
|
google_hybrid: new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://mt{0-3}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}',
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 天地图卫星影像
|
||||||
|
tianditu_satellite: new ol.layer.Group({
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 天地图矢量图
|
||||||
|
tianditu_normal: new ol.layer.Group({
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 天地图地形图
|
||||||
|
tianditu_terrain: new ol.layer.Group({
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 天地图地形混合图
|
||||||
|
tianditu_terrain_hybrid: new ol.layer.Group({
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.XYZ({
|
||||||
|
url: 'https://t{0-7}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
|
||||||
|
tileLoadFunction: createTiandituTileLoadFunction()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getLayer(mapType) {
|
||||||
|
return mapLayers[mapType];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getAllLayers() {
|
||||||
|
return mapLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function hasLayer(mapType) {
|
||||||
|
return mapLayers.hasOwnProperty(mapType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
getLayer: getLayer,
|
||||||
|
getAllLayers: getAllLayers,
|
||||||
|
hasLayer: hasLayer
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
* 测距工具模块
|
||||||
|
* 提供地图测距功能
|
||||||
|
*/
|
||||||
|
var MeasureTools = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var measureActive = false;
|
||||||
|
var measureDraw = null;
|
||||||
|
var measureSource = null;
|
||||||
|
var measureLayer = null;
|
||||||
|
var measureTooltips = [];
|
||||||
|
var measureFeatures = [];
|
||||||
|
var currentMeasureTooltips = [];
|
||||||
|
var currentSketch = null;
|
||||||
|
var currentListener = null;
|
||||||
|
var segmentTooltips = [];
|
||||||
|
var map = null;
|
||||||
|
|
||||||
|
function init(mapInstance) {
|
||||||
|
map = mapInstance;
|
||||||
|
|
||||||
|
// 测距专用图层
|
||||||
|
measureSource = new ol.source.Vector();
|
||||||
|
measureLayer = new ol.layer.Vector({
|
||||||
|
source: measureSource,
|
||||||
|
style: new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
|
||||||
|
stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
|
||||||
|
image: new ol.style.RegularShape({
|
||||||
|
points: 4,
|
||||||
|
radius: 8,
|
||||||
|
radius2: 0,
|
||||||
|
angle: Math.PI / 4,
|
||||||
|
stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (map) {
|
||||||
|
map.addLayer(measureLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMeasureDistance() {
|
||||||
|
if (measureActive) {
|
||||||
|
deactivateMeasure();
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('测距功能已关闭');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activateMeasure();
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('点击左键添加距离节点,点击右键结束测量 :)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu = document.getElementById('mapFunctionsMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.remove('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishMeasuring() {
|
||||||
|
if (measureActive && currentSketch && measureDraw) {
|
||||||
|
measureDraw.finishDrawing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateMeasure() {
|
||||||
|
if (!map || !measureSource) return;
|
||||||
|
|
||||||
|
measureActive = true;
|
||||||
|
|
||||||
|
// 显示测量状态指示器
|
||||||
|
var measureStatus = document.getElementById('measureStatus');
|
||||||
|
if (measureStatus) {
|
||||||
|
measureStatus.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
measureDraw = new ol.interaction.Draw({
|
||||||
|
source: measureSource,
|
||||||
|
type: 'LineString',
|
||||||
|
style: new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
|
||||||
|
stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
|
||||||
|
image: new ol.style.RegularShape({
|
||||||
|
points: 4,
|
||||||
|
radius: 8,
|
||||||
|
radius2: 0,
|
||||||
|
angle: Math.PI / 4,
|
||||||
|
stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addInteraction(measureDraw);
|
||||||
|
|
||||||
|
map.getViewport().addEventListener('contextmenu', function(e) {
|
||||||
|
if (measureActive && currentSketch) {
|
||||||
|
e.preventDefault();
|
||||||
|
measureDraw.finishDrawing();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
measureDraw.on('drawstart', function(evt) {
|
||||||
|
currentSketch = evt.feature;
|
||||||
|
currentMeasureTooltips = [];
|
||||||
|
segmentTooltips = [];
|
||||||
|
|
||||||
|
currentListener = currentSketch.getGeometry().on('change', function(e) {
|
||||||
|
var geom = e.target;
|
||||||
|
var coords = geom.getCoordinates();
|
||||||
|
|
||||||
|
clearTemporaryTooltips();
|
||||||
|
clearSegmentTooltips();
|
||||||
|
|
||||||
|
// 计算并显示每个节点的距离
|
||||||
|
var total = 0;
|
||||||
|
for (var i = 0; i < coords.length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
// 起点
|
||||||
|
var startTooltip = createMeasureTooltip(coords[0], '起点');
|
||||||
|
currentMeasureTooltips.push(startTooltip);
|
||||||
|
} else {
|
||||||
|
// 计算段距离
|
||||||
|
var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
|
||||||
|
var segmentLength = ol.sphere.getLength(seg);
|
||||||
|
total += segmentLength;
|
||||||
|
|
||||||
|
// 显示每个节点的累计距离
|
||||||
|
var output = formatLength(total);
|
||||||
|
var tooltip = createMeasureTooltip(coords[i], output);
|
||||||
|
segmentTooltips.push(tooltip);
|
||||||
|
|
||||||
|
if (i === coords.length - 1) {
|
||||||
|
currentMeasureTooltips.push(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绘制结束
|
||||||
|
measureDraw.on('drawend', function(evt) {
|
||||||
|
var coords = evt.feature.getGeometry().getCoordinates();
|
||||||
|
|
||||||
|
clearTemporaryTooltips();
|
||||||
|
clearSegmentTooltips();
|
||||||
|
|
||||||
|
var total = 0;
|
||||||
|
for (var i = 0; i < coords.length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
var startTooltip = createMeasureTooltip(coords[0], '起点', true);
|
||||||
|
measureTooltips.push(startTooltip);
|
||||||
|
} else {
|
||||||
|
var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
|
||||||
|
total += ol.sphere.getLength(seg);
|
||||||
|
|
||||||
|
var output = formatLength(total);
|
||||||
|
var tooltip = createMeasureTooltip(coords[i], output, true);
|
||||||
|
measureTooltips.push(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
measureFeatures.push(evt.feature);
|
||||||
|
|
||||||
|
if (currentListener) {
|
||||||
|
ol.Observable.unByKey(currentListener);
|
||||||
|
}
|
||||||
|
currentSketch = null;
|
||||||
|
currentListener = null;
|
||||||
|
|
||||||
|
var measureStatus = document.getElementById('measureStatus');
|
||||||
|
if (measureStatus) {
|
||||||
|
measureStatus.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeInteraction(measureDraw);
|
||||||
|
measureActive = false;
|
||||||
|
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('测量完成');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivateMeasure() {
|
||||||
|
measureActive = false;
|
||||||
|
|
||||||
|
var measureStatus = document.getElementById('measureStatus');
|
||||||
|
if (measureStatus) {
|
||||||
|
measureStatus.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (measureDraw && map) {
|
||||||
|
map.removeInteraction(measureDraw);
|
||||||
|
measureDraw = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentListener) {
|
||||||
|
ol.Observable.unByKey(currentListener);
|
||||||
|
currentListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSketch = null;
|
||||||
|
clearTemporaryTooltips();
|
||||||
|
clearSegmentTooltips();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMeasureTooltip(coord, text, isStatic) {
|
||||||
|
var elem = document.createElement('div');
|
||||||
|
elem.className = isStatic ? 'ol-tooltip ol-tooltip-static' : 'ol-tooltip ol-tooltip-measure';
|
||||||
|
elem.innerHTML = text;
|
||||||
|
|
||||||
|
var overlay = new ol.Overlay({
|
||||||
|
element: elem,
|
||||||
|
offset: [0, -15],
|
||||||
|
positioning: 'bottom-center'
|
||||||
|
});
|
||||||
|
|
||||||
|
overlay.setPosition(coord);
|
||||||
|
|
||||||
|
if (map) {
|
||||||
|
map.addOverlay(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLength(length) {
|
||||||
|
if (length > 1000) {
|
||||||
|
return (Math.round(length / 100) / 10) + ' km';
|
||||||
|
} else {
|
||||||
|
return (Math.round(length * 10) / 10) + ' m';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllMeasureTooltips() {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
for (var i = 0; i < measureTooltips.length; i++) {
|
||||||
|
map.removeOverlay(measureTooltips[i]);
|
||||||
|
}
|
||||||
|
measureTooltips = [];
|
||||||
|
|
||||||
|
clearTemporaryTooltips();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTemporaryTooltips() {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
for (var i = 0; i < currentMeasureTooltips.length; i++) {
|
||||||
|
map.removeOverlay(currentMeasureTooltips[i]);
|
||||||
|
}
|
||||||
|
currentMeasureTooltips = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSegmentTooltips() {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
for (var i = 0; i < segmentTooltips.length; i++) {
|
||||||
|
map.removeOverlay(segmentTooltips[i]);
|
||||||
|
}
|
||||||
|
segmentTooltips = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMeasure() {
|
||||||
|
if (measureSource) {
|
||||||
|
measureSource.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllMeasureTooltips();
|
||||||
|
measureFeatures = [];
|
||||||
|
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('测距标记已清除');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭地图功能菜单
|
||||||
|
var menu = document.getElementById('mapFunctionsMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.remove('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive() {
|
||||||
|
return measureActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMeasureCount() {
|
||||||
|
return measureFeatures.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
toggleMeasureDistance: toggleMeasureDistance,
|
||||||
|
finishMeasuring: finishMeasuring,
|
||||||
|
clearMeasure: clearMeasure,
|
||||||
|
isActive: isActive,
|
||||||
|
getMeasureCount: getMeasureCount
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* 搜索和过滤功能模块
|
||||||
|
* 负责设备搜索、过滤和查询功能
|
||||||
|
*/
|
||||||
|
var SearchFilter = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var currentSearchedDevice = null;
|
||||||
|
var markerState = 3; // 1:all; 2:orange; 3:red
|
||||||
|
|
||||||
|
function searchDevice(deviceId) {
|
||||||
|
if (!deviceId || !deviceId.trim()) {
|
||||||
|
clearSearch();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceId = deviceId.trim();
|
||||||
|
currentSearchedDevice = deviceId;
|
||||||
|
|
||||||
|
var success = DeviceMarkers.locateDevice(deviceId);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('已定位到设备: ' + deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备信息并显示天气预测(如果启用)
|
||||||
|
var targetFeature = DeviceMarkers.findDeviceById(deviceId);
|
||||||
|
if (targetFeature && window.WeatherForecast && window.WeatherForecast.isEnabled()) {
|
||||||
|
var deviceInfo = targetFeature.get('deviceInfo');
|
||||||
|
if (deviceInfo) {
|
||||||
|
window.WeatherForecast.showWeatherForecast(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('未找到设备: ' + deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
currentSearchedDevice = null;
|
||||||
|
var searchInput = document.getElementById('deviceSearchNew');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复到当前的过滤状态
|
||||||
|
applyCurrentFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCurrentFilter() {
|
||||||
|
switch(markerState) {
|
||||||
|
case 1:
|
||||||
|
showAllDevices();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
showWarning1Devices();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
showWarning2Devices();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAllDevices() {
|
||||||
|
currentSearchedDevice = null;
|
||||||
|
clearSearchInput();
|
||||||
|
DeviceMarkers.showAllDevices();
|
||||||
|
markerState = 1;
|
||||||
|
updateFilterSelect('all');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning1Devices() {
|
||||||
|
currentSearchedDevice = null;
|
||||||
|
clearSearchInput();
|
||||||
|
DeviceMarkers.showWarning1Devices();
|
||||||
|
markerState = 2;
|
||||||
|
updateFilterSelect('warning1');
|
||||||
|
}
|
||||||
|
function showWarning2Devices() {
|
||||||
|
currentSearchedDevice = null;
|
||||||
|
clearSearchInput();
|
||||||
|
DeviceMarkers.showWarning2Devices();
|
||||||
|
markerState = 3;
|
||||||
|
updateFilterSelect('warning2');
|
||||||
|
}
|
||||||
|
function clearSearchInput() {
|
||||||
|
var searchInput = document.getElementById('deviceSearchNew');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilterSelect(filterValue) {
|
||||||
|
var filterSelect = document.getElementById('warningFilter');
|
||||||
|
if (filterSelect) {
|
||||||
|
filterSelect.value = filterValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterDevicesByStatus(statusType) {
|
||||||
|
switch(statusType) {
|
||||||
|
case 'warning1':
|
||||||
|
showWarning1Devices();
|
||||||
|
break;
|
||||||
|
case 'warning2':
|
||||||
|
showWarning2Devices();
|
||||||
|
break;
|
||||||
|
case 'offline':
|
||||||
|
case 'no_fwd':
|
||||||
|
case 'nofixed':
|
||||||
|
case 'nogga':
|
||||||
|
// 对于这些状态,显示所有设备,让用户在弹窗中选择
|
||||||
|
showAllDevices();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showAllDevices();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryDevices(statusType) {
|
||||||
|
filterDevicesByStatus(statusType);
|
||||||
|
|
||||||
|
// 打开设备列表弹窗
|
||||||
|
if (window.layer && typeof window.layer.open === 'function') {
|
||||||
|
var index = window.layer.open({
|
||||||
|
title: '设备列表',
|
||||||
|
type: 2,
|
||||||
|
shade: 0.2,
|
||||||
|
maxmin: true,
|
||||||
|
shadeClose: true,
|
||||||
|
anim: 2,
|
||||||
|
offset: 'rb',
|
||||||
|
area: ['100%', '50%'],
|
||||||
|
content: '../page/gnss_q_status?query=' + statusType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function locateDeviceOnMap(deviceId, latitude, longitude) {
|
||||||
|
currentSearchedDevice = deviceId;
|
||||||
|
var success = DeviceMarkers.locateDevice(deviceId);
|
||||||
|
|
||||||
|
if (success && window.layer && typeof window.layer.closeAll === 'function') {
|
||||||
|
window.layer.closeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
function locateDeviceDirectly(deviceId) {
|
||||||
|
currentSearchedDevice = deviceId;
|
||||||
|
return DeviceMarkers.locateDevice(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSearchedDevice() {
|
||||||
|
return currentSearchedDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkerState() {
|
||||||
|
return markerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMarkerState(state) {
|
||||||
|
markerState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchDevice: searchDevice,
|
||||||
|
clearSearch: clearSearch,
|
||||||
|
showAllDevices: showAllDevices,
|
||||||
|
showWarning1Devices: showWarning1Devices,
|
||||||
|
showWarning2Devices: showWarning2Devices,
|
||||||
|
filterDevicesByStatus: filterDevicesByStatus,
|
||||||
|
queryDevices: queryDevices,
|
||||||
|
locateDeviceOnMap: locateDeviceOnMap,
|
||||||
|
locateDeviceDirectly: locateDeviceDirectly,
|
||||||
|
getCurrentSearchedDevice: getCurrentSearchedDevice,
|
||||||
|
getMarkerState: getMarkerState,
|
||||||
|
setMarkerState: setMarkerState
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,307 @@
|
|||||||
|
var WeatherForecast = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var weatherApiKey = 'Uxh4IdMuAvhSiBnsf4UUDVGF4e3YAp2B';
|
||||||
|
var weatherEnabled = false;
|
||||||
|
var weatherData = null;
|
||||||
|
var currentForecastIndex = 0;
|
||||||
|
var currentWeatherDevice = null;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
// 天气预报模块初始化完成
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWeatherForecast() {
|
||||||
|
var role = window.userRole || 'USER';
|
||||||
|
if (role !== 'SUPER_ADMIN') {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('您没有权限使用此功能');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableSwitch = document.getElementById('enableWeatherSwitch');
|
||||||
|
weatherEnabled = enableSwitch ? enableSwitch.checked : false;
|
||||||
|
|
||||||
|
if (weatherEnabled) {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg(
|
||||||
|
'搜索设备或点击地图设备图标即可自动查询天气预测',
|
||||||
|
{time: 3000, area: ['300px', '80px']}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('天气预测功能已关闭');
|
||||||
|
}
|
||||||
|
closeWeatherCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu = document.getElementById('mapFunctionsMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.remove('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWeatherForecast(deviceInfo) {
|
||||||
|
currentWeatherDevice = deviceInfo;
|
||||||
|
weatherData = null;
|
||||||
|
|
||||||
|
var role = window.userRole || 'USER';
|
||||||
|
if (role !== 'SUPER_ADMIN') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weatherEnabled) {
|
||||||
|
if (window.layer && typeof window.layer.msg === 'function') {
|
||||||
|
window.layer.msg('天气预测功能未启用', {time: 2000});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceIdElement = document.getElementById('weatherDeviceId');
|
||||||
|
var deviceCoordsElement = document.getElementById('weatherDeviceCoords');
|
||||||
|
|
||||||
|
if (deviceIdElement) {
|
||||||
|
deviceIdElement.textContent = '设备: ' + deviceInfo.deviceid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceCoordsElement) {
|
||||||
|
deviceCoordsElement.textContent =
|
||||||
|
'坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
var weatherCard = document.getElementById('weatherForecastCard');
|
||||||
|
if (weatherCard) {
|
||||||
|
weatherCard.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentElement = document.getElementById('weatherForecastContent');
|
||||||
|
if (contentElement) {
|
||||||
|
contentElement.innerHTML =
|
||||||
|
'<div class="weather-loading">' +
|
||||||
|
'<i class="layui-icon layui-icon-loading layui-icon-anim-rotate"></i>' +
|
||||||
|
'<p>请确保网络可访问 Windy API 服务</p>' +
|
||||||
|
'<p>正在获取天气预测数据...</p>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevBtn = document.getElementById('prevForecast');
|
||||||
|
var nextBtn = document.getElementById('nextForecast');
|
||||||
|
var timeDisplay = document.getElementById('forecastTimeDisplay');
|
||||||
|
|
||||||
|
if (prevBtn) prevBtn.disabled = true;
|
||||||
|
if (nextBtn) nextBtn.disabled = true;
|
||||||
|
if (timeDisplay) timeDisplay.textContent = '--:--';
|
||||||
|
|
||||||
|
fetchWeatherData(deviceInfo.latitude, deviceInfo.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWeatherCard() {
|
||||||
|
var weatherCard = document.getElementById('weatherForecastCard');
|
||||||
|
if (weatherCard) {
|
||||||
|
weatherCard.style.display = 'none';
|
||||||
|
}
|
||||||
|
currentWeatherDevice = null;
|
||||||
|
weatherData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPrevForecast() {
|
||||||
|
if (!weatherData || currentForecastIndex <= 0) return;
|
||||||
|
|
||||||
|
currentForecastIndex--;
|
||||||
|
displayCurrentForecast();
|
||||||
|
updateForecastNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNextForecast() {
|
||||||
|
if (!weatherData || !weatherData.ts || currentForecastIndex >= weatherData.ts.length - 1) return;
|
||||||
|
|
||||||
|
currentForecastIndex++;
|
||||||
|
displayCurrentForecast();
|
||||||
|
updateForecastNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateForecastNavigation() {
|
||||||
|
if (!weatherData || !weatherData.ts) return;
|
||||||
|
|
||||||
|
var prevBtn = document.getElementById('prevForecast');
|
||||||
|
var nextBtn = document.getElementById('nextForecast');
|
||||||
|
var timeDisplay = document.getElementById('forecastTimeDisplay');
|
||||||
|
|
||||||
|
if (prevBtn) prevBtn.disabled = (currentForecastIndex <= 0);
|
||||||
|
if (nextBtn) nextBtn.disabled = (currentForecastIndex >= weatherData.ts.length - 1);
|
||||||
|
|
||||||
|
if (timeDisplay) {
|
||||||
|
var timestamp = weatherData.ts[currentForecastIndex];
|
||||||
|
var date = new Date(timestamp);
|
||||||
|
timeDisplay.textContent = formatDateTime(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchWeatherData(lat, lon) {
|
||||||
|
var requestBody = {
|
||||||
|
"lat": parseFloat(lat.toFixed(2)),
|
||||||
|
"lon": parseFloat(lon.toFixed(2)),
|
||||||
|
"model": "gfs",
|
||||||
|
"parameters": ["temp", "wind", "precip", "pressure", "rh", "windGust"],
|
||||||
|
"levels": ["surface"],
|
||||||
|
"key": weatherApiKey,
|
||||||
|
"hours": 72
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('https://api.windy.com/api/point-forecast/v2', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('网络响应状态: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
weatherData = data;
|
||||||
|
|
||||||
|
var currentTime = new Date().getTime();
|
||||||
|
var closestIndex = 0;
|
||||||
|
var futureIndex = -1;
|
||||||
|
|
||||||
|
if (weatherData.ts && weatherData.ts.length > 0) {
|
||||||
|
for (var i = 0; i < weatherData.ts.length; i++) {
|
||||||
|
if (weatherData.ts[i] > currentTime) {
|
||||||
|
futureIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (futureIndex >= 0) {
|
||||||
|
closestIndex = futureIndex;
|
||||||
|
} else {
|
||||||
|
var smallestDiff = Number.MAX_VALUE;
|
||||||
|
for (var i = 0; i < weatherData.ts.length; i++) {
|
||||||
|
var diff = Math.abs(weatherData.ts[i] - currentTime);
|
||||||
|
if (diff < smallestDiff) {
|
||||||
|
smallestDiff = diff;
|
||||||
|
closestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentForecastIndex = closestIndex;
|
||||||
|
displayCurrentForecast();
|
||||||
|
updateForecastNavigation();
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error('天气数据获取失败:', error);
|
||||||
|
displayWeatherError('获取天气数据失败: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayCurrentForecast() {
|
||||||
|
if (!weatherData || !weatherData.ts || weatherData.ts.length === 0) {
|
||||||
|
displayWeatherError('无可用的天气预测数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = currentForecastIndex;
|
||||||
|
var forecastHtml = '<div class="weather-forecast-item"><div class="weather-param-grid">';
|
||||||
|
|
||||||
|
if (weatherData['temp-surface'] && weatherData['temp-surface'][i] !== null) {
|
||||||
|
var temp = (weatherData['temp-surface'][i] - 273.15).toFixed(1);
|
||||||
|
forecastHtml += createWeatherParam('温度', temp + '°C');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherData['wind_u-surface'] && weatherData['wind_v-surface'] &&
|
||||||
|
weatherData['wind_u-surface'][i] !== null && weatherData['wind_v-surface'][i] !== null) {
|
||||||
|
var windU = weatherData['wind_u-surface'][i];
|
||||||
|
var windV = weatherData['wind_v-surface'][i];
|
||||||
|
var windSpeed = Math.sqrt(windU * windU + windV * windV).toFixed(1);
|
||||||
|
var windDir = getWindDirection(windU, windV);
|
||||||
|
forecastHtml += createWeatherParam('风速', windSpeed + ' m/s');
|
||||||
|
forecastHtml += createWeatherParam('风向', windDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherData['past3hprecip-surface'] && weatherData['past3hprecip-surface'][i] !== null) {
|
||||||
|
var precip = weatherData['past3hprecip-surface'][i].toFixed(1);
|
||||||
|
forecastHtml += createWeatherParam('降水', precip + ' mm');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherData['rh-surface'] && weatherData['rh-surface'][i] !== null) {
|
||||||
|
var humidity = weatherData['rh-surface'][i].toFixed(0);
|
||||||
|
forecastHtml += createWeatherParam('湿度', humidity + '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherData['pressure-surface'] && weatherData['pressure-surface'][i] !== null) {
|
||||||
|
var pressure = (weatherData['pressure-surface'][i] / 100).toFixed(0);
|
||||||
|
forecastHtml += createWeatherParam('气压', pressure + ' hPa');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherData['gust-surface'] && weatherData['gust-surface'][i] !== null) {
|
||||||
|
var gust = weatherData['gust-surface'][i].toFixed(1);
|
||||||
|
forecastHtml += createWeatherParam('阵风', gust + ' m/s');
|
||||||
|
}
|
||||||
|
|
||||||
|
forecastHtml += '</div></div>';
|
||||||
|
|
||||||
|
var contentElement = document.getElementById('weatherForecastContent');
|
||||||
|
if (contentElement) {
|
||||||
|
contentElement.innerHTML = forecastHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWeatherError(message) {
|
||||||
|
var contentElement = document.getElementById('weatherForecastContent');
|
||||||
|
if (contentElement) {
|
||||||
|
contentElement.innerHTML =
|
||||||
|
'<div class="weather-error">' +
|
||||||
|
'<i class="layui-icon layui-icon-close"></i>' +
|
||||||
|
'<p>' + message + '</p>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWeatherParam(label, value) {
|
||||||
|
return '<div class="weather-param">' +
|
||||||
|
'<span class="weather-param-label">' + label + '</span>' +
|
||||||
|
'<span class="weather-param-value">' + value + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(date) {
|
||||||
|
var month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
var day = date.getDate().toString().padStart(2, '0');
|
||||||
|
var hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
var minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
return month + '-' + day + ' ' + hours + ':' + minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindDirection(u, v) {
|
||||||
|
var angle = Math.atan2(-u, -v) * 180 / Math.PI;
|
||||||
|
angle = (angle + 360) % 360;
|
||||||
|
|
||||||
|
var directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
|
||||||
|
var index = Math.round(angle / 45) % 8;
|
||||||
|
return directions[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEnabled() {
|
||||||
|
return weatherEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
toggleWeatherForecast: toggleWeatherForecast,
|
||||||
|
showWeatherForecast: showWeatherForecast,
|
||||||
|
closeWeatherCard: closeWeatherCard,
|
||||||
|
showPrevForecast: showPrevForecast,
|
||||||
|
showNextForecast: showNextForecast,
|
||||||
|
isEnabled: isEnabled
|
||||||
|
};
|
||||||
|
})();
|
||||||
2
sec-beidou/src/main/resources/static/js/ol.js
Normal file
2
sec-beidou/src/main/resources/static/js/ol.js
Normal file
File diff suppressed because one or more lines are too long
@ -219,7 +219,7 @@
|
|||||||
|
|
||||||
//建立webSocket连接
|
//建立webSocket连接
|
||||||
var webSocktPath;
|
var webSocktPath;
|
||||||
webSocktPath = (basePath+"/websocket").replace("http","ws");
|
webSocktPath = (basePath+"/websocket/cmdline").replace("http","ws");
|
||||||
websocket = new WebSocket(webSocktPath);
|
websocket = new WebSocket(webSocktPath);
|
||||||
|
|
||||||
//打开webSokcet连接时,回调该函数
|
//打开webSokcet连接时,回调该函数
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -62,6 +62,12 @@
|
|||||||
<input type="text" name="appver" autocomplete="off" class="layui-input">
|
<input type="text" name="appver" autocomplete="off" class="layui-input">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">IMEI</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="imei" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">使用状态</label>
|
<label class="layui-form-label">使用状态</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
@ -90,6 +96,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">内置电池</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="has_battery" id="has_battery" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">无</option>
|
||||||
|
<option value="1">是</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,6 +144,7 @@
|
|||||||
{field: 'group_id', title: '基本参数组', width: 60, sort: true},
|
{field: 'group_id', title: '基本参数组', width: 60, sort: true},
|
||||||
{field: 'calc_group_id', title: '解算参数组', width: 60, sort: true},
|
{field: 'calc_group_id', title: '解算参数组', width: 60, sort: true},
|
||||||
{field: 'parentid', title: '基站编号', width: 80, sort: true},
|
{field: 'parentid', title: '基站编号', width: 80, sort: true},
|
||||||
|
{field: 'parentid1', title: '备用基站', width: 80, sort: true},
|
||||||
{field: 'tenantname', title: '所属组织', width: 120},
|
{field: 'tenantname', title: '所属组织', width: 120},
|
||||||
{field: 'fwd_group_id', title: '推送组', width: 80},
|
{field: 'fwd_group_id', title: '推送组', width: 80},
|
||||||
{field: 'fwd_group_id2', title: '推送2', width: 80},
|
{field: 'fwd_group_id2', title: '推送2', width: 80},
|
||||||
@ -135,6 +152,7 @@
|
|||||||
{field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'},
|
{field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'},
|
||||||
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
||||||
{field: 'appver', title: '固件版本', width: 80},
|
{field: 'appver', title: '固件版本', width: 80},
|
||||||
|
{field: 'voltage_factor', title: '分压系数', width: 60},
|
||||||
{field: 'imei', title: 'IMEI', width: 100},
|
{field: 'imei', title: 'IMEI', width: 100},
|
||||||
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
|
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -21,7 +21,17 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">设备号</label>
|
<label class="layui-form-label">设备号</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
<input type="text" name="s_deviceid" autocomplete="off" class="layui-input">
|
<input type="text" name="s_deviceid" id="deviceid" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">内置电池</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="NULL_solarvoltage" id="NULL_solarvoltage" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">无</option>
|
||||||
|
<option value="1">是</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -34,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
||||||
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i>导出</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i>导出</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -42,17 +52,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
|
<div class="layui-tab layui-tab-card" lay-filter="data-tab">
|
||||||
|
<ul class="layui-tab-title">
|
||||||
|
<li class="layui-this">数据表格</li>
|
||||||
|
<li>姿态曲线</li>
|
||||||
|
</ul>
|
||||||
|
<div class="layui-tab-content">
|
||||||
|
<div class="layui-tab-item layui-show">
|
||||||
|
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
|
||||||
|
</div>
|
||||||
|
<div class="layui-tab-item">
|
||||||
|
<div id="echarts-attitude" style="min-height:500px;padding: 10px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="../js/lay-module/echarts/echartsTheme.js" charset="utf-8"></script>
|
||||||
|
<script src="../js/lay-module/echarts/echarts.js" charset="utf-8"></script>
|
||||||
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
layui.use(['form', 'table', 'laydate'], function () {
|
layui.use(['form', 'table', 'laydate', 'element'], function () {
|
||||||
var $ = layui.$;
|
var $ = layui.$;
|
||||||
var form = layui.form,
|
var form = layui.form,
|
||||||
table = layui.table,
|
table = layui.table,
|
||||||
laydate = layui.laydate;
|
laydate = layui.laydate,
|
||||||
|
element = layui.element;
|
||||||
|
|
||||||
|
var echartsAttitude = echarts.init(document.getElementById('echarts-attitude'), 'walden');
|
||||||
|
var chartDataLoaded = false;
|
||||||
|
|
||||||
var cfg_cols = [
|
var cfg_cols = [
|
||||||
{field: 'deviceid', title: '设备号', width: 100},
|
{field: 'deviceid', title: '设备号', width: 100},
|
||||||
@ -68,9 +97,7 @@
|
|||||||
{field: 'temperature', title: '温度(°C)'},
|
{field: 'temperature', title: '温度(°C)'},
|
||||||
{field: 'humidity', title: '湿度(%)'}
|
{field: 'humidity', title: '湿度(%)'}
|
||||||
];
|
];
|
||||||
/**
|
|
||||||
* 初始化表单,要加上,不然刷新部分组件可能会不加载
|
|
||||||
*/
|
|
||||||
form.render();
|
form.render();
|
||||||
|
|
||||||
laydate.render({
|
laydate.render({
|
||||||
@ -85,7 +112,7 @@
|
|||||||
table.render({
|
table.render({
|
||||||
elem: '#currentTableId',
|
elem: '#currentTableId',
|
||||||
url: '/gnss/msg/status/list',
|
url: '/gnss/msg/status/list',
|
||||||
toolbar: '#toolbarDemo',//开启头部工具栏
|
toolbar: '#toolbarDemo',
|
||||||
defaultToolbar: ['filter'],
|
defaultToolbar: ['filter'],
|
||||||
cols: [
|
cols: [
|
||||||
cfg_cols
|
cfg_cols
|
||||||
@ -96,11 +123,9 @@
|
|||||||
skin: 'line'
|
skin: 'line'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听搜索操作
|
|
||||||
form.on('submit(data-search-btn)', function (data) {
|
form.on('submit(data-search-btn)', function (data) {
|
||||||
var result = JSON.stringify(data.field);
|
var result = JSON.stringify(data.field);
|
||||||
|
|
||||||
//执行搜索重载
|
|
||||||
table.reload('currentTableId', {
|
table.reload('currentTableId', {
|
||||||
page: {
|
page: {
|
||||||
curr: 1
|
curr: 1
|
||||||
@ -113,7 +138,6 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听导出操作
|
|
||||||
form.on('submit(data-export-btn)', function (data) {
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
var result = $('#searchFrm').serialize();
|
var result = $('#searchFrm').serialize();
|
||||||
var u = "/gnss/msg/status/export?" + result;
|
var u = "/gnss/msg/status/export?" + result;
|
||||||
@ -121,6 +145,173 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
element.on('tab(data-tab)', function(data){
|
||||||
|
if (data.index == 1) {
|
||||||
|
echartsAttitude.resize();
|
||||||
|
|
||||||
|
if (!chartDataLoaded) {
|
||||||
|
var deviceId = $('#deviceid').val();
|
||||||
|
if (deviceId) {
|
||||||
|
loadChartData(deviceId);
|
||||||
|
} else {
|
||||||
|
layer.msg('请先在搜索框中输入设备号');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadChartData(deviceId) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/gnss/msg/status/device_data',
|
||||||
|
type: 'GET',
|
||||||
|
data: {
|
||||||
|
deviceId: deviceId
|
||||||
|
},
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
showChart(res.data, res.originalData);
|
||||||
|
chartDataLoaded = true;
|
||||||
|
} else {
|
||||||
|
layer.msg('获取数据失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
layer.msg('获取数据失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showChart(smoothedData, originalData) {
|
||||||
|
var t = [];
|
||||||
|
var roll = [];
|
||||||
|
var pitch = [];
|
||||||
|
var yaw = [];
|
||||||
|
var smoothRoll = [];
|
||||||
|
var smoothPitch = [];
|
||||||
|
var smoothYaw = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < originalData.length; i++) {
|
||||||
|
t.push(originalData[i].createtime);
|
||||||
|
roll.push(originalData[i].roll);
|
||||||
|
pitch.push(originalData[i].pitch);
|
||||||
|
yaw.push(originalData[i].yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < smoothedData.length; i++) {
|
||||||
|
smoothRoll.push(smoothedData[i].roll);
|
||||||
|
smoothPitch.push(smoothedData[i].pitch);
|
||||||
|
smoothYaw.push(smoothedData[i].yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
var option = {
|
||||||
|
title: {
|
||||||
|
text: '姿态数据曲线'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#6a7985'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['Roll', 'Pitch', 'Yaw', '平滑Roll', '平滑Pitch', '平滑Yaw']
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
feature: {
|
||||||
|
saveAsImage: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: t,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function(value) {
|
||||||
|
var date = new Date(value);
|
||||||
|
return date.getFullYear() + '-' +
|
||||||
|
(date.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
date.getDate().toString().padStart(2, '0') + ' ' +
|
||||||
|
date.getHours().toString().padStart(2, '0') + ':' +
|
||||||
|
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||||||
|
date.getSeconds().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Roll',
|
||||||
|
type: 'line',
|
||||||
|
data: roll,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Pitch',
|
||||||
|
type: 'line',
|
||||||
|
data: pitch,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yaw',
|
||||||
|
type: 'line',
|
||||||
|
data: yaw,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '平滑Roll',
|
||||||
|
type: 'line',
|
||||||
|
data: smoothRoll,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '平滑Pitch',
|
||||||
|
type: 'line',
|
||||||
|
data: smoothPitch,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '平滑Yaw',
|
||||||
|
type: 'line',
|
||||||
|
data: smoothYaw,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
echartsAttitude.setOption(option);
|
||||||
|
|
||||||
|
window.onresize = function() {
|
||||||
|
echartsAttitude.resize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
237
sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html
Normal file
237
sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>设备参数</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
|
||||||
|
<link rel="stylesheet" href="../css/public.css" media="all">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layuimini-container">
|
||||||
|
<div class="layuimini-main">
|
||||||
|
<div class="layui-card layui-panel">
|
||||||
|
<div class="layui-card-header">
|
||||||
|
<h3>请选择要升级的固件</h3>
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<table class="layui-hide" id="firmware-table" lay-filter="firmware-table"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div class="layui-card layui-panel">
|
||||||
|
<div class="layui-card-header" name="app-title">
|
||||||
|
<h3>终端固件信息</h3>
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<fieldset class="table-search-fieldset">
|
||||||
|
<legend>搜索信息</legend>
|
||||||
|
<div style="margin: 10px 10px 10px 10px">
|
||||||
|
<form class="layui-form layui-form-pane" action="">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline" th:if="${tenant_id==0}">
|
||||||
|
<label class="layui-form-label">所属部门</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="tenantname" id="tenantname" lay-search="">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="非SAAS服务商">非SAAS服务商</option>
|
||||||
|
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.name}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">设备号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="deviceid" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">项目号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="project_id" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">桩号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="sector" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">固件版本</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="appver" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">使用状态</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="opmode" id="opmode" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">正常</option>
|
||||||
|
<option value="1">维护</option>
|
||||||
|
<option value="2">停用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<table class="layui-hide" id="app-table" lay-filter="app-table"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<script type="text/html" id="app-toolbar">
|
||||||
|
<div class="layui-btn-container">
|
||||||
|
<button class="layui-btn layui-btn-sm" lay-event="upgrade" id="btn_upgrade">升级</button>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui.use(['form', 'table'], function () {
|
||||||
|
var $ = layui.$,
|
||||||
|
form = layui.form,
|
||||||
|
table = layui.table;
|
||||||
|
var cfg_cols = [
|
||||||
|
{type: 'checkbox', fixed: 'left'},
|
||||||
|
{field: 'deviceid', title: '设备号', width: 100, sort: true},
|
||||||
|
{field: 'remark', title: '备注', width: 100},
|
||||||
|
{field: 'project_id', title: '项目号', width: 120, sort: true},
|
||||||
|
{field: 'sector', title: '桩号', width: 120, sort: true},
|
||||||
|
{field: 'name', title: '监测点号', width: 80},
|
||||||
|
{field: 'devicetype', title: '类型', width: 80,templet: "<div>{{d.devicetype==0?'监测站':'基准站'}}</div>"},
|
||||||
|
{field: 'parentid', title: '基站编号', width: 80, sort: true},
|
||||||
|
{field: 'tenantname', title: '所属组织', width: 120},
|
||||||
|
{field: 'opmode', title: '使用状态', width: 80,templet: '#modeTrans'},
|
||||||
|
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
||||||
|
{field: 'appver', title: '固件版本', width: 80}
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 初始化表单,要加上,不然刷新部分组件可能会不加载
|
||||||
|
*/
|
||||||
|
form.render();
|
||||||
|
|
||||||
|
table.render({
|
||||||
|
elem: '#app-table',
|
||||||
|
url: '/sys/ver_mgr/gnss_device_list',
|
||||||
|
toolbar: '#app-toolbar',
|
||||||
|
defaultToolbar: ['filter','exports'],
|
||||||
|
cols: [
|
||||||
|
cfg_cols
|
||||||
|
],
|
||||||
|
limits: [10, 20, 50],
|
||||||
|
limit: 10,
|
||||||
|
page: true,
|
||||||
|
skin: 'line'
|
||||||
|
});
|
||||||
|
|
||||||
|
table.render({
|
||||||
|
elem: '#firmware-table'
|
||||||
|
,url:'/sys/ver_mgr/firmware_list'
|
||||||
|
,cols: [
|
||||||
|
[
|
||||||
|
{type: 'radio', fixed: 'left'}
|
||||||
|
,{field:'name', title:'固件', sort: true}
|
||||||
|
,{field:'length', title:'长度(字节)'}
|
||||||
|
,{field:'create_time', title:'时间', sort: true, templet: "<div>{{layui.util.toDateString(d.create_time, 'yyyy-MM-dd HH:mm:ss')}}</div>"}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
,page: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听搜索操作
|
||||||
|
form.on('submit(data-search-btn)', function (data) {
|
||||||
|
var result = JSON.stringify(data.field);
|
||||||
|
|
||||||
|
//执行搜索重载
|
||||||
|
table.reload('app-table', {
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
, where: {
|
||||||
|
searchParams: result
|
||||||
|
}
|
||||||
|
}, 'data');
|
||||||
|
table.render();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toolbar事件监听
|
||||||
|
*/
|
||||||
|
//头工具栏事件
|
||||||
|
table.on('toolbar(app-table)', function(obj){
|
||||||
|
let $ = layui.$;
|
||||||
|
switch(obj.event){
|
||||||
|
case 'upgrade':
|
||||||
|
var optionStatus = table.checkStatus('firmware-table')//获取选中行状态
|
||||||
|
var fdata = optionStatus.data;
|
||||||
|
var checkStatus = table.checkStatus('app-table')//获取选中行状态
|
||||||
|
var adata = checkStatus.data;
|
||||||
|
if(fdata.length>0 && adata.length>0){
|
||||||
|
var firmware = fdata[0]["name"];
|
||||||
|
var device_list = "";
|
||||||
|
for(var i=0;i<adata.length; i++){
|
||||||
|
device_list = device_list+adata[i]["deviceid"]+";";
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
type:"POST",
|
||||||
|
url:"/sys/ver_mgr/upgrade",
|
||||||
|
data: {
|
||||||
|
'firmware': firmware,
|
||||||
|
'id_list': device_list
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
if(result.code == 0) layer.msg('升级进行中...');
|
||||||
|
else layer.alert(result.msg);
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
console.log("ajax error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function onDeviceCfgUpdated(){
|
||||||
|
layui.table.reload('currentTableId');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="modeTrans">
|
||||||
|
{{# if(d.opmode == 0){ }}
|
||||||
|
<span class="layui-badge layui-bg-green">正常</span>
|
||||||
|
{{# } else if(d.opmode == 1){ }}
|
||||||
|
<span class="layui-badge layui-bg-blue">维护</span>
|
||||||
|
{{# } else { }}
|
||||||
|
<span class="layui-badge layui-bg-gray">停用</span>
|
||||||
|
{{# } }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="synTrans">
|
||||||
|
{{# if(d.syn == 0){ }}
|
||||||
|
<span class="layui-badge layui-bg-orange">否</span>
|
||||||
|
{{# } else { }}
|
||||||
|
<span class="layui-badge layui-bg-green">是</span>
|
||||||
|
{{# } }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>设备SIM卡映射记录</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
|
||||||
|
<link rel="stylesheet" href="../css/public.css" media="all">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layuimini-container">
|
||||||
|
<div class="layuimini-main">
|
||||||
|
<fieldset class="table-search-fieldset">
|
||||||
|
<legend>搜索信息</legend>
|
||||||
|
<div style="margin: 10px 10px 10px 10px">
|
||||||
|
<form class="layui-form layui-form-pane" action="" id="searchForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">查询类型</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="searchType" lay-verify="required">
|
||||||
|
<option value="deviceId">设备号</option>
|
||||||
|
<option value="iccid">ICCID</option>
|
||||||
|
<option value="simNumber">SIM卡号</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">搜索内容</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="searchContent" id="searchInput" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i> 搜 索</button>
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i> 导出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div class="layui-tab layui-tab-card" lay-filter="data-tab">
|
||||||
|
<ul class="layui-tab-title">
|
||||||
|
<li class="layui-this">设备映射记录</li>
|
||||||
|
</ul>
|
||||||
|
<div class="layui-tab-content">
|
||||||
|
<div class="layui-tab-item layui-show">
|
||||||
|
<table class="layui-hide" id="deviceMappingTable" lay-filter="deviceMappingFilter"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/html" id="statusTpl">
|
||||||
|
{{# if(d.isActive){ }}
|
||||||
|
<span class="layui-badge layui-bg-green">使用中</span>
|
||||||
|
{{# } else { }}
|
||||||
|
<span class="layui-badge">已结束</span>
|
||||||
|
{{# } }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui.use(['form', 'table','layer','element'], function () {
|
||||||
|
var table = layui.table
|
||||||
|
,form = layui.form
|
||||||
|
,layer = layui.layer
|
||||||
|
,element = layui.element;
|
||||||
|
|
||||||
|
var data_cols = [
|
||||||
|
{field: 'deviceId', title: '设备号'},
|
||||||
|
{field: 'iccid', title: 'ICCID'},
|
||||||
|
{field: 'msisdn', title: 'SIM卡号'},
|
||||||
|
{field: 'startTime', title: '开始时间', templet: "<div>{{layui.util.toDateString(d.startTime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
|
{field: 'endTime', title: '结束时间', templet: function(d) {
|
||||||
|
return d.endTime ? layui.util.toDateString(d.endTime, 'yyyy-MM-dd HH:mm:ss') : '-';
|
||||||
|
}},
|
||||||
|
{field: 'isActive', title: '状态', templet: '#statusTpl'},
|
||||||
|
{field: 'duration', title: '使用时长', templet: function(d) {
|
||||||
|
if (!d.startTime) return '-';
|
||||||
|
|
||||||
|
var start = new Date(d.startTime);
|
||||||
|
var end = d.endTime ? new Date(d.endTime) : new Date();
|
||||||
|
var diffMs = end - start;
|
||||||
|
|
||||||
|
if (diffMs < 0) return '-';
|
||||||
|
|
||||||
|
var days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
var hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
return days + '天' + hours + '小时';
|
||||||
|
} else if (hours > 0) {
|
||||||
|
return hours + '小时';
|
||||||
|
} else {
|
||||||
|
var minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
return minutes + '分钟';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
];
|
||||||
|
|
||||||
|
form.render();
|
||||||
|
table.render({
|
||||||
|
elem: '#deviceMappingTable',
|
||||||
|
url: '/sim/device-mapping',
|
||||||
|
defaultToolbar: ['filter'],
|
||||||
|
cols: [
|
||||||
|
data_cols
|
||||||
|
],
|
||||||
|
limits: [20, 50, 100, 200, 300],
|
||||||
|
limit: 20,
|
||||||
|
page: true,
|
||||||
|
skin: 'line',
|
||||||
|
done: function(res) {
|
||||||
|
if(res.code !== 0) {
|
||||||
|
layer.msg(res.msg || '查询失败', {icon: 2});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.verify({
|
||||||
|
searchContent: function(value, item) {
|
||||||
|
var type = $('select[name="searchType"]').val();
|
||||||
|
if(!value) {
|
||||||
|
return '请输入搜索内容';
|
||||||
|
}
|
||||||
|
if(type === 'iccid' && !/^\d{19,20}$/.test(value)) {
|
||||||
|
return 'ICCID必须是19-20位数字';
|
||||||
|
}
|
||||||
|
if(type === 'simNumber' && !/^\d{11,13}$/.test(value)) {
|
||||||
|
return 'SIM卡号必须是11-13位数字';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(searchSubmit)', function(data){
|
||||||
|
var loadIndex = layer.load(1);
|
||||||
|
table.reload('deviceMappingTable', {
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
,where: {
|
||||||
|
searchType: data.field.searchType,
|
||||||
|
searchContent: data.field.searchContent
|
||||||
|
}
|
||||||
|
,done: function(res) {
|
||||||
|
layer.close(loadIndex);
|
||||||
|
if(res.code !== 0) {
|
||||||
|
layer.msg(res.msg || '查询失败', {icon: 2});
|
||||||
|
} else if(res.count === 0) {
|
||||||
|
layer.msg('未找到符合条件的数据', {icon: 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
|
var searchData = data.field;
|
||||||
|
var url = '/sim/device-mapping/export?';
|
||||||
|
if (searchData.searchType) {
|
||||||
|
url += 'searchType=' + searchData.searchType;
|
||||||
|
}
|
||||||
|
if (searchData.searchContent) {
|
||||||
|
url += '&searchContent=' + encodeURIComponent(searchData.searchContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<fieldset class="table-search-fieldset">
|
<fieldset class="table-search-fieldset">
|
||||||
<legend>搜索信息</legend>
|
<legend>搜索信息</legend>
|
||||||
<div style="margin: 10px 10px 10px 10px">
|
<div style="margin: 10px 10px 10px 10px">
|
||||||
<form class="layui-form layui-form-pane" action="" id="searchForm">
|
<form class="layui-form layui-form-pane" action="" id="searchForm" lay-filter="searchForm">
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">查询类型</label>
|
<label class="layui-form-label">查询类型</label>
|
||||||
@ -24,6 +24,7 @@
|
|||||||
<option value="deviceId">设备号</option>
|
<option value="deviceId">设备号</option>
|
||||||
<option value="iccid">ICCID</option>
|
<option value="iccid">ICCID</option>
|
||||||
<option value="simNumber">SIM卡号</option>
|
<option value="simNumber">SIM卡号</option>
|
||||||
|
<option value="imei">IMEI</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,6 +53,7 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i> 搜 索</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i> 搜 索</button>
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i> 导出</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -110,9 +112,9 @@
|
|||||||
function updateFlowChart(simData) {
|
function updateFlowChart(simData) {
|
||||||
initEcharts();
|
initEcharts();
|
||||||
|
|
||||||
var used = parseFloat(simData.used) || 0;
|
var used = simData.used ? (simData.used / 1000) : 0;
|
||||||
var remaining = parseFloat(simData.remaining) || 0;
|
var remaining = simData.remaining ? (simData.remaining / 1000) : 0;
|
||||||
var total = parseFloat(simData.total) || (used + remaining);
|
var total = simData.total ? (simData.total / 1000) : (used + remaining);
|
||||||
|
|
||||||
var option = {
|
var option = {
|
||||||
title: {
|
title: {
|
||||||
@ -191,11 +193,18 @@
|
|||||||
{field: 'deviceid', title: '设备号'},
|
{field: 'deviceid', title: '设备号'},
|
||||||
{field: 'iccid', title: 'ICCID'},
|
{field: 'iccid', title: 'ICCID'},
|
||||||
{field: 'msisdn', title: 'SIM 卡号'},
|
{field: 'msisdn', title: 'SIM 卡号'},
|
||||||
{field: 'updatetime', title: '更新时间',templet: "<div>{{layui.util.toDateString(d.updatetime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
{field: 'imei', title: 'IMEI'},
|
||||||
|
{field: 'updateTime', title: '更新时间',templet: "<div>{{layui.util.toDateString(d.updateTime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
{field: 'status', title: '状态',templet: '#statusTpl'},
|
{field: 'status', title: '状态',templet: '#statusTpl'},
|
||||||
{field: 'remaining', title: '剩余流量(MB)'},
|
{field: 'remaining', title: '剩余流量(MB)', templet: function(d) {
|
||||||
{field: 'used', title: '已使用流量(MB)'},
|
return d.remaining ? (d.remaining / 1000).toFixed(2) : '0';
|
||||||
{field: 'total', title: '总流量(MB)'}
|
}},
|
||||||
|
{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';
|
||||||
|
}}
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -259,6 +268,70 @@
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
title: '导出选项',
|
||||||
|
shadeClose: true,
|
||||||
|
content: `
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<div class="layui-form" lay-filter="exportForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">导出范围</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="exportRange" value="current" title="当前页" checked>
|
||||||
|
<input type="radio" name="exportRange" value="all" title="全部数据">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn" lay-submit lay-filter="confirmExport">确定</button>
|
||||||
|
<button type="reset" class="layui-btn layui-btn-primary" onclick="layer.closeAll()">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
success: function(layero, index) {
|
||||||
|
form.render('radio', 'exportForm');
|
||||||
|
|
||||||
|
// 监听导出确认
|
||||||
|
form.on('submit(confirmExport)', function(formData) {
|
||||||
|
var searchData = data.field;
|
||||||
|
var url = '/sim/export?';
|
||||||
|
if (searchData.searchType) {
|
||||||
|
url += 'searchType=' + searchData.searchType;
|
||||||
|
}
|
||||||
|
if (searchData.searchContent) {
|
||||||
|
url += '&searchContent=' + encodeURIComponent(searchData.searchContent);
|
||||||
|
}
|
||||||
|
if (searchData.status) {
|
||||||
|
url += '&status=' + searchData.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前表格的页大小(limit)
|
||||||
|
var currentLimit = layui.table.cache.simTable ? layui.table.cache.simTable.limit : 20;
|
||||||
|
|
||||||
|
// 添加导出范围参数
|
||||||
|
var exportAll = formData.field.exportRange === 'all';
|
||||||
|
url += '&exportAll=' + exportAll;
|
||||||
|
|
||||||
|
// 如果导出当前页,则传递页大小
|
||||||
|
if (!exportAll) {
|
||||||
|
url += '&pageSize=' + currentLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
layer.close(index);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
//监听Tab切换,重新resize图表,否则显示不出来
|
//监听Tab切换,重新resize图表,否则显示不出来
|
||||||
element.on('tab(data-tab)', function(data){
|
element.on('tab(data-tab)', function(data){
|
||||||
if(data.index === 1 && flowChart) {
|
if(data.index === 1 && flowChart) {
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<button type="button" class="layui-btn" onclick="searchSIM()" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i>搜索</button>
|
<button type="button" class="layui-btn" onclick="searchSIM()" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i>搜索</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i> 导出</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -63,6 +64,30 @@
|
|||||||
searchSIM();
|
searchSIM();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const searchContent = searchInput.value.trim();
|
||||||
|
const searchType = document.querySelector('select[name="searchType"]').value;
|
||||||
|
|
||||||
|
if (!searchContent) {
|
||||||
|
layer.msg('请输入搜索内容');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType === 'iccid' && !/^\d{19,20}$/.test(searchContent)) {
|
||||||
|
layer.msg('ICCID必须是19-20位数字');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType === 'simNumber' && !/^\d{11,13}$/.test(searchContent)) {
|
||||||
|
layer.msg('SIM卡号必须是11-13位数字');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = `/sim/query/export?type=${searchType}&content=${encodeURIComponent(searchContent)}`;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function searchSIM() {
|
function searchSIM() {
|
||||||
|
|||||||
@ -0,0 +1,184 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>SIM卡流量使用记录</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
|
||||||
|
<link rel="stylesheet" href="../css/public.css" media="all">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layuimini-container">
|
||||||
|
<div class="layuimini-main">
|
||||||
|
<fieldset class="table-search-fieldset">
|
||||||
|
<legend>搜索信息</legend>
|
||||||
|
<div style="margin: 10px 10px 10px 10px">
|
||||||
|
<form class="layui-form layui-form-pane" action="" id="searchForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">查询类型</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="searchType" lay-verify="required">
|
||||||
|
<option value="deviceId">设备号</option>
|
||||||
|
<option value="iccid">ICCID</option>
|
||||||
|
<option value="simNumber">SIM卡号</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">搜索内容</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="searchContent" id="searchInput" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">时间范围</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="startTime" autocomplete="off" id="ID-laydate-start-date" class="layui-input" placeholder="开始日期">
|
||||||
|
</div>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="endTime" autocomplete="off" id="ID-laydate-end-date" class="layui-input" placeholder="结束日期">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="searchSubmit"><i class="layui-icon"></i> 搜 索</button>
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i> 导出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div class="layui-tab layui-tab-card" lay-filter="data-tab">
|
||||||
|
<ul class="layui-tab-title">
|
||||||
|
<li class="layui-this">流量使用记录</li>
|
||||||
|
</ul>
|
||||||
|
<div class="layui-tab-content">
|
||||||
|
<div class="layui-tab-item layui-show">
|
||||||
|
<table class="layui-hide" id="trafficRecordsTable" lay-filter="trafficRecordsFilter"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui.use(['form', 'table','layer','element','laydate'], function () {
|
||||||
|
var table = layui.table
|
||||||
|
,form = layui.form
|
||||||
|
,layer = layui.layer
|
||||||
|
,element = layui.element
|
||||||
|
,laydate = layui.laydate;
|
||||||
|
|
||||||
|
var data_cols = [
|
||||||
|
{field: 'deviceid', title: '设备号'},
|
||||||
|
{field: 'iccid', title: 'ICCID'},
|
||||||
|
{field: 'msisdn', title: 'SIM卡号'},
|
||||||
|
{field: 'recordTime', title: '记录时间', templet: "<div>{{layui.util.toDateString(d.recordTime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
|
{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';
|
||||||
|
}}
|
||||||
|
];
|
||||||
|
|
||||||
|
form.render();
|
||||||
|
laydate.render({
|
||||||
|
elem: '#ID-laydate-start-date',
|
||||||
|
type: 'datetime'
|
||||||
|
});
|
||||||
|
laydate.render({
|
||||||
|
elem: '#ID-laydate-end-date',
|
||||||
|
type: 'datetime'
|
||||||
|
});
|
||||||
|
|
||||||
|
table.render({
|
||||||
|
elem: '#trafficRecordsTable',
|
||||||
|
url: '/sim/traffic-records',
|
||||||
|
defaultToolbar: ['filter'],
|
||||||
|
cols: [
|
||||||
|
data_cols
|
||||||
|
],
|
||||||
|
limits: [20, 50, 100, 200, 300],
|
||||||
|
limit: 20,
|
||||||
|
page: true,
|
||||||
|
skin: 'line',
|
||||||
|
done: function(res) {
|
||||||
|
if(res.code !== 0) {
|
||||||
|
layer.msg(res.msg || '查询失败', {icon: 2});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.verify({
|
||||||
|
searchContent: function(value, item) {
|
||||||
|
var type = $('select[name="searchType"]').val();
|
||||||
|
if(!value) {
|
||||||
|
return '请输入搜索内容';
|
||||||
|
}
|
||||||
|
if(type === 'iccid' && !/^\d{19,20}$/.test(value)) {
|
||||||
|
return 'ICCID必须是19-20位数字';
|
||||||
|
}
|
||||||
|
if(type === 'simNumber' && !/^\d{11,13}$/.test(value)) {
|
||||||
|
return 'SIM卡号必须是11-13位数字';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(searchSubmit)', function(data){
|
||||||
|
var loadIndex = layer.load(1);
|
||||||
|
table.reload('trafficRecordsTable', {
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
,where: {
|
||||||
|
searchType: data.field.searchType,
|
||||||
|
searchContent: data.field.searchContent,
|
||||||
|
startTime: data.field.startTime,
|
||||||
|
endTime: data.field.endTime
|
||||||
|
}
|
||||||
|
,done: function(res) {
|
||||||
|
layer.close(loadIndex);
|
||||||
|
if(res.code !== 0) {
|
||||||
|
layer.msg(res.msg || '查询失败', {icon: 2});
|
||||||
|
} else if(res.count === 0) {
|
||||||
|
layer.msg('未找到符合条件的数据', {icon: 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
|
var searchData = data.field;
|
||||||
|
var url = '/sim/traffic-records/export?';
|
||||||
|
if (searchData.searchType) {
|
||||||
|
url += 'searchType=' + searchData.searchType;
|
||||||
|
}
|
||||||
|
if (searchData.searchContent) {
|
||||||
|
url += '&searchContent=' + encodeURIComponent(searchData.searchContent);
|
||||||
|
}
|
||||||
|
if (searchData.startTime) {
|
||||||
|
url += '&startTime=' + encodeURIComponent(searchData.startTime);
|
||||||
|
}
|
||||||
|
if (searchData.endTime) {
|
||||||
|
url += '&endTime=' + encodeURIComponent(searchData.endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label required">设备编号</label>
|
<label class="layui-form-label required">设备编号</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input">
|
<input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input" onblur="checkDeviceId()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -57,14 +57,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline" pane="">
|
<div class="layui-inline" pane="">
|
||||||
<label class="layui-form-label">星座</label>
|
<label class="layui-form-label">内置电池</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-inline">
|
||||||
<input type="checkbox" name="like1[write]" lay-skin="primary" title="北斗" checked="" disabled="">
|
<select name="has_battery" id="has_battery" th:field="*{has_battery}" lay-verify="required" lay-search="">
|
||||||
<input type="checkbox" name="like1[read]" lay-skin="primary" title="GPS" disabled="">
|
<option value="0">否</option>
|
||||||
<input type="checkbox" name="like1[game]" lay-skin="primary" title="GLONASS" disabled="">
|
<option value="1">是</option>
|
||||||
<input type="checkbox" name="like1[game]" lay-skin="primary" title="Galileo" disabled="">
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-inline" pane="">
|
||||||
|
<label class="layui-form-label">电压系数</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="voltage_factor" id="voltage_factor" th:field="*{voltage_factor}" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-mid layui-word-aux">*0.1</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -82,6 +89,12 @@
|
|||||||
<input type="number" name="parentid" id="parentid" th:field="*{parentid}" placeholder="请输入关联基准站编号" value="" class="layui-input">
|
<input type="number" name="parentid" id="parentid" th:field="*{parentid}" placeholder="请输入关联基准站编号" value="" class="layui-input">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-inline" >
|
||||||
|
<label class="layui-form-label">备选基站</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="number" name="parentid1" id="parentid1" th:field="*{parentid1}" placeholder="请输入备选基准站编号" value="" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item" id="ecef_div">
|
<div class="layui-form-item" id="ecef_div">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -288,6 +301,7 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function setEcefEditor(){
|
function setEcefEditor(){
|
||||||
var $ = layui.$;
|
var $ = layui.$;
|
||||||
console.log($('#devicetype').val(), $('#model').val());
|
console.log($('#devicetype').val(), $('#model').val());
|
||||||
@ -299,6 +313,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkDeviceId(){
|
||||||
|
var $ = layui.$;
|
||||||
|
var value = $("#deviceid").val();
|
||||||
|
console.log(value);
|
||||||
|
if(value.startsWith("6") || value.startsWith("7")){
|
||||||
|
$("#has_battery").val('1');
|
||||||
|
$("#has_battery").attr('disabled',true);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#has_battery").attr('disabled',false);
|
||||||
|
}
|
||||||
|
layui.form.render();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -91,8 +91,12 @@
|
|||||||
contentType: "application/json;charset=UTF-8",
|
contentType: "application/json;charset=UTF-8",
|
||||||
data: JSON.stringify(data.field),
|
data: JSON.stringify(data.field),
|
||||||
success: function (result) {
|
success: function (result) {
|
||||||
parent.onBaseParaUpdated();
|
if(result.code == 0) {
|
||||||
parent.layer.close(iframeIndex);
|
parent.onBaseParaUpdated();
|
||||||
|
parent.layer.close(iframeIndex);
|
||||||
|
}
|
||||||
|
else layer.alert(result.msg);
|
||||||
|
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function () {
|
||||||
console.log("ajax error");
|
console.log("ajax error");
|
||||||
|
|||||||
@ -56,6 +56,12 @@
|
|||||||
<version>5.5.8</version>
|
<version>5.5.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.78.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
109
sec-common/src/main/java/com/imdroid/common/util/Des3Utils.java
Normal file
109
sec-common/src/main/java/com/imdroid/common/util/Des3Utils.java
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package com.imdroid.common.util;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.DESedeKeySpec;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
public class Des3Utils {
|
||||||
|
private static final String KEY_ALGORITHM = "DESede";
|
||||||
|
private static final String CIPHER_ALGORITHM_CBC = "DESede/CBC/PKCS5Padding";
|
||||||
|
private static final String CIPHER_ALGORITHM_ECB = "DESede/ECB/PKCS5Padding";
|
||||||
|
|
||||||
|
|
||||||
|
static String mode = "DESede/CFB/NoPadding";
|
||||||
|
|
||||||
|
public static byte[] encrypt(Key deskey, IvParameterSpec desiv, String mode, byte[] data) {
|
||||||
|
byte[] res = null;
|
||||||
|
|
||||||
|
Cipher cipher;
|
||||||
|
try {
|
||||||
|
cipher = Cipher.getInstance(mode);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, deskey, desiv);
|
||||||
|
res = cipher.doFinal(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encrypt(String data, byte[] key, byte[] iv) {
|
||||||
|
String res = "";
|
||||||
|
|
||||||
|
ByteBuf buf = Unpooled.buffer(256);
|
||||||
|
byte[] databyte = data.getBytes(StandardCharsets.UTF_8);
|
||||||
|
buf.writeBytes(databyte);
|
||||||
|
int a = (8 - databyte.length % 8);
|
||||||
|
for (int c = 0; c < a; c++) {
|
||||||
|
buf.writeByte(0x00);
|
||||||
|
}
|
||||||
|
databyte = new byte[buf.readableBytes()];
|
||||||
|
buf.readBytes(databyte);
|
||||||
|
|
||||||
|
DESedeKeySpec spec;
|
||||||
|
SecretKeyFactory keyfactory;
|
||||||
|
Key deskey;
|
||||||
|
try {
|
||||||
|
keyfactory = SecretKeyFactory.getInstance("DESede");
|
||||||
|
spec = new DESedeKeySpec(key);
|
||||||
|
deskey = keyfactory.generateSecret(spec);
|
||||||
|
IvParameterSpec desiv = new IvParameterSpec(iv);
|
||||||
|
|
||||||
|
byte[] encryptbyte = encrypt(deskey, desiv, mode, databyte);
|
||||||
|
res = Base64.encodeBase64String(encryptbyte);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encryptCBC(byte[] key, byte[] iv, String data) throws Exception {
|
||||||
|
DESedeKeySpec spec = new DESedeKeySpec(key);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key desKey = keyFactory.generateSecret(spec);
|
||||||
|
Cipher cipher = Cipher.getInstance(mode);
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, desKey, ips);
|
||||||
|
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Base64.encodeBase64String(encryptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decryptCBC(byte[] key, byte[] iv, String data) throws Exception {
|
||||||
|
DESedeKeySpec spec = new DESedeKeySpec(key);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key desKey = keyFactory.generateSecret(spec);
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_CBC);
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, desKey, ips);
|
||||||
|
byte[] decryptedData = cipher.doFinal(Base64.decodeBase64(data));
|
||||||
|
return new String(decryptedData, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encryptECB(byte[] key, String data) throws Exception {
|
||||||
|
DESedeKeySpec spec = new DESedeKeySpec(key);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key desKey = keyFactory.generateSecret(spec);
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_ECB);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, desKey);
|
||||||
|
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Base64.encodeBase64String(encryptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decryptECB(byte[] key, String data) throws Exception {
|
||||||
|
DESedeKeySpec spec = new DESedeKeySpec(key);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key desKey = keyFactory.generateSecret(spec);
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_ECB);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, desKey);
|
||||||
|
byte[] decryptedData = cipher.doFinal(Base64.decodeBase64(data));
|
||||||
|
return new String(decryptedData, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,8 +3,12 @@ package com.imdroid.ntripproxy.service;
|
|||||||
public class Ntrip2Channels {
|
public class Ntrip2Channels {
|
||||||
final private String localHost="127.0.0.1";
|
final private String localHost="127.0.0.1";
|
||||||
final private int localPort=9903;
|
final private int localPort=9903;
|
||||||
final private String remoteHost="47.107.50.52";
|
// 将远程主机改为本地,端口改为12000
|
||||||
final private int remotePort=9903;
|
final private String remoteHost="127.0.0.1";
|
||||||
|
//final private String remoteHost="100.91.37.6";
|
||||||
|
//final private String remoteHost="47.107.50.52";
|
||||||
|
//final private String remoteHost="8.134.185.53";
|
||||||
|
final private int remotePort=12000;
|
||||||
|
|
||||||
public static final Ntrip2Channels INSTANCE = new Ntrip2Channels();
|
public static final Ntrip2Channels INSTANCE = new Ntrip2Channels();
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,185 @@
|
|||||||
|
package com.imdroid.ntripproxy.service;
|
||||||
|
|
||||||
|
import com.imdroid.common.util.DataTypeUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTCM数据过滤服务器
|
||||||
|
* 监听UDP 12000端口,接收RTCM数据
|
||||||
|
* 开启TCP 12002端口,转发指定deviceId的RTCM数据
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RtcmFilterServer implements ApplicationRunner {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RtcmFilterServer.class);
|
||||||
|
|
||||||
|
// 配置参数
|
||||||
|
private static final int UDP_PORT = 12000;
|
||||||
|
private static final int TCP_PORT = 12002;
|
||||||
|
private static final String TARGET_DEVICE_ID = "3530795";
|
||||||
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
// TCP客户端连接列表
|
||||||
|
private final List<SocketChannel> tcpClients = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
// 线程池
|
||||||
|
private final ExecutorService executorService = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
// 启动UDP监听服务
|
||||||
|
executorService.submit(this::startUdpServer);
|
||||||
|
|
||||||
|
// 启动TCP服务器
|
||||||
|
executorService.submit(this::startTcpServer);
|
||||||
|
|
||||||
|
logger.info("RTCM过滤服务已启动 - UDP监听端口:{}, TCP服务端口:{}, 目标设备ID:{}",
|
||||||
|
UDP_PORT, TCP_PORT, TARGET_DEVICE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动UDP服务器监听12000端口
|
||||||
|
*/
|
||||||
|
private void startUdpServer() {
|
||||||
|
try (DatagramSocket socket = new DatagramSocket(UDP_PORT)) {
|
||||||
|
logger.info("UDP服务已启动,监听端口: {}", UDP_PORT);
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||||
|
socket.receive(packet);
|
||||||
|
|
||||||
|
// 解析数据包
|
||||||
|
byte[] data = new byte[packet.getLength()];
|
||||||
|
System.arraycopy(packet.getData(), packet.getOffset(), data, 0, packet.getLength());
|
||||||
|
|
||||||
|
// 提取设备ID
|
||||||
|
String deviceId = extractDeviceId(data);
|
||||||
|
|
||||||
|
// 如果是目标设备ID,则转发到TCP客户端
|
||||||
|
if (TARGET_DEVICE_ID.equals(deviceId)) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("接收到目标设备 {} 的RTCM数据,长度: {}", deviceId, data.length);
|
||||||
|
}
|
||||||
|
forwardToTcpClients(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("UDP服务异常: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动TCP服务器监听12002端口
|
||||||
|
*/
|
||||||
|
private void startTcpServer() {
|
||||||
|
try {
|
||||||
|
Selector selector = Selector.open();
|
||||||
|
ServerSocketChannel serverChannel = ServerSocketChannel.open();
|
||||||
|
serverChannel.configureBlocking(false);
|
||||||
|
serverChannel.socket().bind(new InetSocketAddress(TCP_PORT));
|
||||||
|
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||||
|
|
||||||
|
logger.info("TCP服务已启动,监听端口: {}", TCP_PORT);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
selector.select();
|
||||||
|
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
|
||||||
|
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
SelectionKey key = keys.next();
|
||||||
|
keys.remove();
|
||||||
|
|
||||||
|
if (!key.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.isAcceptable()) {
|
||||||
|
acceptConnection(selector, serverChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("TCP服务异常: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受新的TCP客户端连接
|
||||||
|
*/
|
||||||
|
private void acceptConnection(Selector selector, ServerSocketChannel serverChannel) throws IOException {
|
||||||
|
SocketChannel clientChannel = serverChannel.accept();
|
||||||
|
clientChannel.configureBlocking(false);
|
||||||
|
clientChannel.register(selector, SelectionKey.OP_READ);
|
||||||
|
|
||||||
|
tcpClients.add(clientChannel);
|
||||||
|
logger.info("新的TCP客户端已连接: {}", clientChannel.getRemoteAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据包中提取设备ID
|
||||||
|
*/
|
||||||
|
private String extractDeviceId(byte[] data) {
|
||||||
|
// 按照NtripMessage格式解析,设备ID从第4字节开始,4字节无符号整型
|
||||||
|
if (data.length >= 8) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data, 4, 4);
|
||||||
|
long deviceId = buffer.getInt() & 0xFFFFFFFFL; // 转为无符号整型
|
||||||
|
return String.valueOf(deviceId);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据转发到所有TCP客户端
|
||||||
|
*/
|
||||||
|
private void forwardToTcpClients(byte[] data) {
|
||||||
|
if (tcpClients.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SocketChannel> disconnectedClients = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SocketChannel client : tcpClients) {
|
||||||
|
try {
|
||||||
|
if (client.isOpen()) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
client.write(buffer);
|
||||||
|
} else {
|
||||||
|
disconnectedClients.add(client);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("向TCP客户端发送数据失败: {}", e.getMessage());
|
||||||
|
disconnectedClients.add(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除断开连接的客户端
|
||||||
|
for (SocketChannel client : disconnectedClients) {
|
||||||
|
tcpClients.remove(client);
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
logger.info("已关闭断开连接的TCP客户端");
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("关闭TCP客户端异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
sec-vermgr/pom.xml
Normal file
104
sec-vermgr/pom.xml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>security-monitor</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>sec-vermgr</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.78.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>sec-api</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>sec-common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<!-- 矩阵工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ejml</groupId>
|
||||||
|
<artifactId>ejml-all</artifactId>
|
||||||
|
<version>0.41</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<name>ali-mirror</name>
|
||||||
|
<url>https://maven.aliyun.com/repository/central</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
</project>
|
||||||
25
sec-vermgr/src/main/java/com/imdroid/vermgr/VerMgrApp.java
Normal file
25
sec-vermgr/src/main/java/com/imdroid/vermgr/VerMgrApp.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package com.imdroid.vermgr;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Layton
|
||||||
|
* @date 2023/1/31 20:33
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(scanBasePackages = {"com.imdroid"})
|
||||||
|
@MapperScan({"com.imdroid.secapi","com.imdroid.beidou.entity"})
|
||||||
|
@ComponentScan({"com.imdroid.*"})
|
||||||
|
@EntityScan({"com.imdroid.*"})
|
||||||
|
@EnableFeignClients(basePackages = "com.imdroid.*")
|
||||||
|
public class VerMgrApp {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(VerMgrApp.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.imdroid.vermgr.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
|
//member name must be same as the field name in mysql
|
||||||
|
@Data
|
||||||
|
public class DeviceApp {
|
||||||
|
//0:未连接;1:连接;2:升级中;3:传输完成;4:升级失败
|
||||||
|
public static final short STATE_DISCONNECTED = 0;
|
||||||
|
public static final short STATE_CONNECTED = 1;
|
||||||
|
public static final short STATE_UPGRADING = 2;
|
||||||
|
public static final short STATE_TX_COMPLETED = 3;
|
||||||
|
public static final short STATE_TX_FAILED = 4;
|
||||||
|
public static final String[] state_description = {
|
||||||
|
"未连接","连接","升级中","发送完成","发送失败"
|
||||||
|
};
|
||||||
|
public static final String[] result_description = {
|
||||||
|
"成功","其他错误","传输中断","超时","数据错误","超时","保存失败","文件错误"
|
||||||
|
};
|
||||||
|
|
||||||
|
long device_sn = 0;
|
||||||
|
String version;
|
||||||
|
String firmware;
|
||||||
|
short upgrade_state = STATE_DISCONNECTED;
|
||||||
|
short upgrade_result; //失败原因
|
||||||
|
Timestamp upgrade_time;
|
||||||
|
String strState;
|
||||||
|
String strResult;
|
||||||
|
|
||||||
|
public void translateCode() {
|
||||||
|
if(upgrade_state>STATE_TX_FAILED) upgrade_state=STATE_DISCONNECTED;
|
||||||
|
strState = state_description[upgrade_state];
|
||||||
|
if(upgrade_result>7) upgrade_result=7;
|
||||||
|
strResult = result_description[upgrade_result];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,290 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import com.imdroid.secapi.client.BeidouClient;
|
||||||
|
import com.imdroid.secapi.client.HttpResp;
|
||||||
|
import com.imdroid.vermgr.entity.DeviceApp;
|
||||||
|
import com.imdroid.vermgr.utils.CRC16;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/******************************************************
|
||||||
|
* 批量升级终端版本
|
||||||
|
* 单例,要求线程安全
|
||||||
|
* 状态机:Idle---收到UpgradeDeviceList--->Upgrading
|
||||||
|
* ^ |
|
||||||
|
* | |
|
||||||
|
* +------Timerout or Complete <-----+
|
||||||
|
*
|
||||||
|
* 流程:
|
||||||
|
* 1)收到待升级的设备列表UpgradeDeviceList。如果当前处于升级完毕状态,则更新待升级列表。回应答:开始升级/升级未结束
|
||||||
|
* 2)TCP通道激活 ---> 获取版本信息 ---> 关联TCP通道和待升级的设备
|
||||||
|
* 3)启动升级会话 ---> 实时更新会话
|
||||||
|
* 数据结构:deviceId,cur_ver,upgrade_firmware,TCP_channel,upgrade_session
|
||||||
|
* ****************************************************/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
public class UpgradeManager {
|
||||||
|
// 版本管理协议
|
||||||
|
public static final byte GET_APP_INFO = (byte) 0xA0;
|
||||||
|
public static final byte APP_INFO_IND = (byte) 0xA1;
|
||||||
|
public static final byte UPGRADE_IND = (byte) 0xA2;
|
||||||
|
public static final byte NOT_UPGRADE_IND = (byte) 0xA3;
|
||||||
|
public static final byte UPGRADE_ACK = (byte) 0xA4;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
static public UpgradeManager INSTANCE;
|
||||||
|
@Autowired
|
||||||
|
BeidouClient beidouClient;
|
||||||
|
|
||||||
|
Map<Long, DeviceApp> upgradeDeviceList = new ConcurrentHashMap<>();
|
||||||
|
Map<Long, UpgradeSession> upgradeSessionMap = new ConcurrentHashMap<>();
|
||||||
|
boolean isIdleState = true;
|
||||||
|
TimerTask operationTimerTask = null;
|
||||||
|
TimerTask txProgressTimerTask = null;
|
||||||
|
Timer timer; //30s定时器
|
||||||
|
|
||||||
|
@PostMapping("/upgrade_cmd")
|
||||||
|
HttpResp upgrade(@RequestParam(name = "deviceList") String deviceList, @RequestParam(name = "verFile") String verFile){
|
||||||
|
HttpResp resp = new HttpResp();
|
||||||
|
if(onRxUpgradeDeviceList(deviceList.split(";"),verFile)){
|
||||||
|
resp.setCode(HttpResp.HTTP_RSP_OK);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resp.setCode(HttpResp.HTTP_RSP_FAILED);
|
||||||
|
resp.setResponseMessage("正在升级中");
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean onRxUpgradeDeviceList(String[] deviceList, String firmware){
|
||||||
|
if(isIdleState){
|
||||||
|
isIdleState = false;
|
||||||
|
//清空状态信息和升级会话
|
||||||
|
upgradeDeviceList.clear();
|
||||||
|
upgradeSessionMap.clear();
|
||||||
|
//创建新的状态信息
|
||||||
|
for(String deviceId:deviceList){
|
||||||
|
Long deviceSn = Long.valueOf(deviceId);
|
||||||
|
if(deviceSn!=null) {
|
||||||
|
DeviceApp deviceApp = new DeviceApp();
|
||||||
|
deviceApp.setDevice_sn(deviceSn);
|
||||||
|
deviceApp.setFirmware(firmware);
|
||||||
|
upgradeDeviceList.put(deviceSn, deviceApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//启动20s定时器,如果这期间没有收到任何数据,则超时失败
|
||||||
|
startTimer();
|
||||||
|
startTxProgressTimerTask();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void onChannelActive(Channel channel){
|
||||||
|
// get app info
|
||||||
|
Long deviceId = (Long) channel.attr(AttributeKey.valueOf("deviceId")).get();
|
||||||
|
if(deviceId == null){
|
||||||
|
getVerInfo(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onChannelInactive(Channel channel){
|
||||||
|
// 更新状态为”未连接“
|
||||||
|
long sn = (long) channel.attr(AttributeKey.valueOf("sn")).get();
|
||||||
|
if(sn > 0){
|
||||||
|
DeviceApp deviceApp = upgradeDeviceList.get(sn);
|
||||||
|
if (deviceApp != null) {
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_DISCONNECTED);
|
||||||
|
}
|
||||||
|
upgradeSessionMap.remove(sn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onRxData(Channel channel, byte[] rx_data){
|
||||||
|
if(isIdleState){
|
||||||
|
// 临时,空闲状态下,如果连续3次收到C,则认为有终端主动请求升级
|
||||||
|
if(rx_data[0]==UpgradeSession.CC && rx_data.length<=4){
|
||||||
|
//onUpgradeRequest(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (rx_data[0] == APP_INFO_IND && rx_data.length > 2) {
|
||||||
|
onVerInfoReceived(channel, rx_data);
|
||||||
|
restartTimer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Long sn = (Long) channel.attr(AttributeKey.valueOf("deviceId")).get();
|
||||||
|
if(sn==null) return;
|
||||||
|
UpgradeSession session = upgradeSessionMap.get(sn);
|
||||||
|
if (session == null) return;
|
||||||
|
|
||||||
|
boolean session_done = false;
|
||||||
|
|
||||||
|
if (rx_data[0] == UPGRADE_ACK) {
|
||||||
|
log.info("upgrade ack");
|
||||||
|
//outputHex(rx_data);
|
||||||
|
if (rx_data[5] != 0) {
|
||||||
|
//终端拒绝升级,原因是产品序列号不对
|
||||||
|
session.reject();
|
||||||
|
session_done = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session_done = session.procYmodem(channel, rx_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session_done) {
|
||||||
|
if (isAllUpgraded()) {
|
||||||
|
//全部发完,等待新版本启动回应答
|
||||||
|
stopTimer();
|
||||||
|
onUpgradeComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpgradeComplete(){
|
||||||
|
isIdleState = true;
|
||||||
|
stopTxProgressTimerTask();
|
||||||
|
refreshTxProgress();
|
||||||
|
beidouClient.onUpgradeComplete();
|
||||||
|
}
|
||||||
|
/***************************************************
|
||||||
|
* 定时器处理
|
||||||
|
****************************************************/
|
||||||
|
void startTimer(){
|
||||||
|
if(operationTimerTask == null) {
|
||||||
|
operationTimerTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onTimerout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
timer.schedule(operationTimerTask,15000);
|
||||||
|
}
|
||||||
|
void restartTimer(){
|
||||||
|
stopTimer();
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
void stopTimer(){
|
||||||
|
if(operationTimerTask != null){
|
||||||
|
operationTimerTask.cancel();
|
||||||
|
operationTimerTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTimerout(){
|
||||||
|
// 超时处理
|
||||||
|
for(UpgradeSession session:upgradeSessionMap.values()){
|
||||||
|
if(session.getState() == DeviceApp.STATE_UPGRADING) {
|
||||||
|
session.onTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
onUpgradeComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTxProgressTimerTask(){
|
||||||
|
if(txProgressTimerTask==null){
|
||||||
|
txProgressTimerTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
refreshTxProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
timer.schedule(txProgressTimerTask,1000,1000);
|
||||||
|
}
|
||||||
|
void stopTxProgressTimerTask(){
|
||||||
|
if(txProgressTimerTask != null){
|
||||||
|
txProgressTimerTask.cancel();
|
||||||
|
txProgressTimerTask = null;
|
||||||
|
log.info("stopTxProgressTimerTask");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/***************************************************
|
||||||
|
* 获取终端信息
|
||||||
|
****************************************************/
|
||||||
|
public void getVerInfo(Channel channel){
|
||||||
|
// 向所有连接终端发读取版本请求
|
||||||
|
byte get_app_info[] = {GET_APP_INFO,0};
|
||||||
|
byte tailer_buff[] = {0,0};
|
||||||
|
int crc = CRC16.calculate(get_app_info);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(get_app_info);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onVerInfoReceived(Channel channel, byte[] rx_data){
|
||||||
|
log.info(channel.remoteAddress()+": received app info");
|
||||||
|
// 记录获取到的版本信息
|
||||||
|
if(rx_data.length<9) return;
|
||||||
|
int crc = CRC16.calculate(rx_data, 7);
|
||||||
|
int crc0 = (((short)rx_data[7]&0xFF)<<8) | ((short)rx_data[8]&0xFF);
|
||||||
|
if(crc != crc0) {
|
||||||
|
log.info(channel.remoteAddress()+": crc error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int i=1;
|
||||||
|
long sn = 0;
|
||||||
|
for(i=1;i<=4;i++){
|
||||||
|
sn = (sn<<8) | ((short)rx_data[i]&0xFF);
|
||||||
|
}
|
||||||
|
String cur_ver = ((short)rx_data[i++]&0xFF) + "." + ((short)rx_data[i++]&0xFF);
|
||||||
|
|
||||||
|
// 更新APP记录并检查此终端是否需要升级
|
||||||
|
DeviceApp deviceApp = upgradeDeviceList.get(sn);
|
||||||
|
|
||||||
|
if (deviceApp == null) return;
|
||||||
|
|
||||||
|
deviceApp.setVersion(cur_ver);
|
||||||
|
//记录这个channel是有应答的
|
||||||
|
channel.attr(AttributeKey.valueOf("deviceId")).set(sn);
|
||||||
|
// 如果原来是未连接状态,则改为连接;如果是其他状态则保持不变
|
||||||
|
if(deviceApp.getUpgrade_state() == DeviceApp.STATE_DISCONNECTED) {
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_CONNECTED);
|
||||||
|
}
|
||||||
|
//启动升级
|
||||||
|
UpgradeSession session = new UpgradeSession(deviceApp);
|
||||||
|
upgradeSessionMap.put(sn, session);
|
||||||
|
session.start(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************
|
||||||
|
* 升级
|
||||||
|
****************************************************/
|
||||||
|
synchronized public boolean isAllUpgraded(){
|
||||||
|
for(DeviceApp deviceApp:upgradeDeviceList.values()){
|
||||||
|
if(deviceApp.getUpgrade_state() != DeviceApp.STATE_TX_COMPLETED &&
|
||||||
|
deviceApp.getUpgrade_state() != DeviceApp.STATE_TX_FAILED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshTxProgress(){
|
||||||
|
for(UpgradeSession session:upgradeSessionMap.values()){
|
||||||
|
if(session.getState() == DeviceApp.STATE_UPGRADING) {
|
||||||
|
beidouClient.onUpgradeProgress(Long.toString(session.getSN()), session.getTxPercentage() + "%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(DeviceApp deviceApp:upgradeDeviceList.values()){
|
||||||
|
if(deviceApp.getUpgrade_state()!=DeviceApp.STATE_UPGRADING){
|
||||||
|
beidouClient.onUpgradeProgress(Long.toString(deviceApp.getDevice_sn()),deviceApp.getStrState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,394 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import com.imdroid.vermgr.entity.DeviceApp;
|
||||||
|
import com.imdroid.vermgr.utils.CRC16;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Arrays;
|
||||||
|
@Slf4j
|
||||||
|
public class UpgradeSession {
|
||||||
|
public static final byte COM_OK = 0;
|
||||||
|
public static final byte COM_ERROR = 1;
|
||||||
|
public static final byte COM_ABORT = 2;
|
||||||
|
public static final byte COM_TIMEOUT = 3;
|
||||||
|
public static final byte COM_DATA = 4;
|
||||||
|
public static final byte COM_LIMIT = 5;
|
||||||
|
public static final byte COM_SAVE_ERR = 6;
|
||||||
|
public static final byte COM_FILE_ERR = 7;
|
||||||
|
|
||||||
|
public static final byte SOH = 0x01; /* start of 128-byte data packet */
|
||||||
|
public static final byte STX = 0x02; /* start of 1024-byte data packet */
|
||||||
|
public static final byte EOT = 0x04; /* end of transmission */
|
||||||
|
public static final byte ACK = 0x06; /* acknowledge */
|
||||||
|
public static final byte NAK = 0x15; /* negative acknowledge */
|
||||||
|
public static final byte CA = 0x18; /* two of these in succession aborts transfer */
|
||||||
|
public static final byte CC = 0x43; /* 'C' == 0x43, request 16-bit CRC */
|
||||||
|
|
||||||
|
public static final short PACKET_HEADER_SIZE = 3;
|
||||||
|
public static final short PACKET_TRAILER_SIZE = 2;
|
||||||
|
public static final short PACKET_SIZE = 128;
|
||||||
|
public static final short PACKET_1K_SIZE = 1024;
|
||||||
|
|
||||||
|
static private String version_path="firmware";
|
||||||
|
|
||||||
|
private DeviceApp deviceApp;
|
||||||
|
File firmware;
|
||||||
|
FileInputStream reader;
|
||||||
|
short tx_seq = 0;
|
||||||
|
int tx_bytes = 0;
|
||||||
|
byte nak_num = 0;
|
||||||
|
|
||||||
|
byte[] header_buff;
|
||||||
|
byte[] tailer_buff;
|
||||||
|
byte[] soh_buff;
|
||||||
|
byte[] stx_buff;
|
||||||
|
boolean session_done = false;
|
||||||
|
boolean file_done = false;
|
||||||
|
boolean is_file_accepted = false;
|
||||||
|
byte result = COM_OK;
|
||||||
|
|
||||||
|
public UpgradeSession(DeviceApp app) {
|
||||||
|
deviceApp = app;
|
||||||
|
firmware = null;
|
||||||
|
reader = null;
|
||||||
|
header_buff = new byte[PACKET_HEADER_SIZE];
|
||||||
|
tailer_buff = new byte[PACKET_TRAILER_SIZE];
|
||||||
|
soh_buff = new byte[PACKET_SIZE];
|
||||||
|
stx_buff = new byte[PACKET_1K_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getResult(){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public DeviceApp getDeviceApp(){return deviceApp;}
|
||||||
|
public long getSN(){return deviceApp.getDevice_sn();}
|
||||||
|
public int getState() {return deviceApp.getUpgrade_state();}
|
||||||
|
public int getTxPercentage() {
|
||||||
|
if(firmware == null) return 0;
|
||||||
|
return (int) (tx_bytes*100/firmware.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(){
|
||||||
|
session_done = true;
|
||||||
|
file_done = true;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
tx_bytes = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
log.error(firmware+" reader.close()",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start(Channel channel) {
|
||||||
|
session_done = false;
|
||||||
|
file_done = false;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
tx_bytes = 0;
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
|
||||||
|
String file_name = deviceApp.getFirmware();
|
||||||
|
if(file_name != null){
|
||||||
|
firmware = new File(version_path+"/" + file_name);
|
||||||
|
if (firmware.exists()) {
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
reader = new FileInputStream(firmware);
|
||||||
|
result = COM_OK;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
log.error(firmware+" reader open",e);
|
||||||
|
reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(firmware+" reader open",e);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发升级指示
|
||||||
|
if(result != COM_OK){
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
long sn = deviceApp.getDevice_sn();
|
||||||
|
byte[] ind_msg = new byte[5];
|
||||||
|
ind_msg[0] = UpgradeManager.UPGRADE_IND;
|
||||||
|
ind_msg[1] = (byte) (sn >> 24);
|
||||||
|
ind_msg[2] = (byte) (sn >> 16);
|
||||||
|
ind_msg[3] = (byte) (sn >> 8);
|
||||||
|
ind_msg[4] = (byte) sn;
|
||||||
|
byte tailer_buff[] = {0, 0};
|
||||||
|
int crc = CRC16.calculate(ind_msg);
|
||||||
|
tailer_buff[0] = (byte) (crc >> 8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(ind_msg);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send UPGRADE_IND");
|
||||||
|
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_UPGRADING);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean restart(Channel channel) {
|
||||||
|
session_done = false;
|
||||||
|
file_done = false;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
reader = new FileInputStream(firmware);
|
||||||
|
result = COM_OK;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
log.error(firmware+" reader open failed: ",e);
|
||||||
|
reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(firmware+" reader open failed: ",e);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result == COM_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reject(){
|
||||||
|
result = COM_ERROR;
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTimeout(){
|
||||||
|
result = COM_TIMEOUT;
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean procYmodem(Channel channel, byte[] rx_data){
|
||||||
|
if(session_done) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (rx_data[0]) {
|
||||||
|
case CC:
|
||||||
|
log.info("C");
|
||||||
|
if(tx_seq!=0){
|
||||||
|
//这种情况说明上次接收端意外中断了,需要重新开始
|
||||||
|
restart(channel);
|
||||||
|
}
|
||||||
|
if (!is_file_accepted) {
|
||||||
|
sendFileInfo(channel);
|
||||||
|
} else {
|
||||||
|
tx_seq++;
|
||||||
|
sendPacket(channel);
|
||||||
|
//sendSmallPacket(channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ACK:
|
||||||
|
if(rx_data.length == 4)
|
||||||
|
log.info("ACK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
|
||||||
|
else log.info("ACK");
|
||||||
|
nak_num = 0;
|
||||||
|
if (!is_file_accepted) {
|
||||||
|
is_file_accepted = true;
|
||||||
|
}
|
||||||
|
else if(!file_done){
|
||||||
|
tx_seq++;
|
||||||
|
sendPacket(channel);
|
||||||
|
//sendSmallPacket(channel);
|
||||||
|
}
|
||||||
|
if(file_done) {
|
||||||
|
is_file_accepted=false;
|
||||||
|
tx_seq=0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NAK:
|
||||||
|
if(rx_data.length == 4)
|
||||||
|
log.info("NAK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
|
||||||
|
else log.info("NAK");
|
||||||
|
nak_num++;
|
||||||
|
if(nak_num>=3){
|
||||||
|
log.info("too much NAK, cancel transmit");
|
||||||
|
result = COM_ERROR;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
else resendPacket(channel);
|
||||||
|
break;
|
||||||
|
case CA:
|
||||||
|
log.info("CA");
|
||||||
|
result = rx_data[1];
|
||||||
|
reset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.info("unknown ind received: "+rx_data[0]+" "+rx_data[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
reset();
|
||||||
|
log.error(firmware+" reader error: ",e);
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result == COM_ERROR || result==COM_FILE_ERR){
|
||||||
|
header_buff[0] = CA;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(session_done){
|
||||||
|
if(result == COM_OK) {
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_COMPLETED);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
}
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
return session_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendFileInfo(Channel channel){
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
Arrays.fill(soh_buff, (byte) 0);
|
||||||
|
if(file_done) {
|
||||||
|
reset();//end of session
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] file_name = firmware.getName().getBytes();
|
||||||
|
for (int i = 0; i < file_name.length; i++) soh_buff[pos++] = file_name[i];
|
||||||
|
soh_buff[pos++] = 0;
|
||||||
|
String strFileSize = "" + firmware.length();
|
||||||
|
byte[] file_size = strFileSize.getBytes();
|
||||||
|
for (int i = 0; i < file_size.length; i++) soh_buff[pos++] = file_size[i];
|
||||||
|
soh_buff[pos++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate(soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write(soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send file info");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPacket(Channel channel) throws IOException {
|
||||||
|
if(tx_seq==0) return;
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_buff[0] = STX;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
int read_bytes = reader.read(stx_buff);
|
||||||
|
if(read_bytes != -1) tx_bytes+=read_bytes;
|
||||||
|
|
||||||
|
if(read_bytes == -1 || read_bytes==0){
|
||||||
|
// send EOT
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
file_done = true;
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(read_bytes>=PACKET_1K_SIZE){
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(read_bytes<PACKET_1K_SIZE && read_bytes>PACKET_SIZE){
|
||||||
|
for(int i=read_bytes; i<PACKET_1K_SIZE; i++) stx_buff[i]=0x1A;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
Arrays.fill(soh_buff, (byte) 0x1A);
|
||||||
|
for(int i=0; i<read_bytes; i++) soh_buff[i] = stx_buff[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate((header_buff[0] == STX)?stx_buff:soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write((header_buff[0] == STX)?stx_buff:soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
|
||||||
|
}
|
||||||
|
void sendSmallPacket(Channel channel) throws IOException {
|
||||||
|
if(tx_seq==0) return;
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
int read_bytes = reader.read(soh_buff);
|
||||||
|
if(read_bytes != -1) tx_bytes+=read_bytes;
|
||||||
|
|
||||||
|
if(read_bytes == -1 || read_bytes==0){
|
||||||
|
// send EOT
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
file_done = true;
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(read_bytes>=PACKET_SIZE){
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
for(int i=read_bytes; i<PACKET_SIZE; i++) soh_buff[i]=0x1A;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate(soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write(soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resendPacket(Channel channel){
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write((header_buff[0] == STX) ? stx_buff : soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
}
|
||||||
|
log.info(deviceApp.getDevice_sn()+": resend packet "+header_buff[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.timeout.IdleState;
|
||||||
|
import io.netty.handler.timeout.IdleStateEvent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class VersionHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.channelActive(ctx);
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
UpgradeManager.INSTANCE.onChannelActive(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if(evt instanceof IdleStateEvent) {
|
||||||
|
IdleStateEvent event = (IdleStateEvent) evt;
|
||||||
|
|
||||||
|
if (event.state() == IdleState.READER_IDLE) {
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
log.info(channel.remoteAddress() + " idle too long to be closed");
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx,
|
||||||
|
Object msg) throws Exception{
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
UpgradeManager.INSTANCE.onRxData(channel, (byte[]) msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx)
|
||||||
|
throws Exception {
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
UpgradeManager.INSTANCE.onChannelInactive(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.codec.bytes.ByteArrayDecoder;
|
||||||
|
import io.netty.handler.codec.bytes.ByteArrayEncoder;
|
||||||
|
import io.netty.handler.logging.LogLevel;
|
||||||
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class VersionServer {
|
||||||
|
@Value("${version_server_port}")
|
||||||
|
private int data_port;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
|
||||||
|
// 创建socket服务,线程阻塞
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
log.info("dtu config server starting...");
|
||||||
|
// Configure the server.
|
||||||
|
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||||
|
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerBootstrap b = new ServerBootstrap();
|
||||||
|
b.group(bossGroup, workerGroup)
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.option(ChannelOption.SO_BACKLOG, 100)
|
||||||
|
//.option(ChannelOption.SO_REUSEADDR, true)
|
||||||
|
.handler(new LoggingHandler(LogLevel.INFO))
|
||||||
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
ChannelPipeline p = ch.pipeline();
|
||||||
|
//p.addLast(new LoggingHandler(LogLevel.INFO));
|
||||||
|
p.addLast(new ByteArrayDecoder());
|
||||||
|
p.addLast(new ByteArrayEncoder());
|
||||||
|
p.addLast(new IdleStateHandler(300, 300, 300)); //设置心跳超时时间,秒
|
||||||
|
p.addLast(new VersionHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server.
|
||||||
|
ChannelFuture f = b.bind(data_port).sync();
|
||||||
|
|
||||||
|
// Wait until the server socket is closed.
|
||||||
|
f.channel().closeFuture().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("error occurred during version server running: "+e);
|
||||||
|
} finally {
|
||||||
|
// Shut down all event loops to terminate all threads.
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
87
sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java
Normal file
87
sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package com.imdroid.vermgr.utils;
|
||||||
|
|
||||||
|
public class CRC16 {
|
||||||
|
static final char[] TABLE1021 = { /* CRC1021余式表 */
|
||||||
|
|
||||||
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
|
||||||
|
|
||||||
|
0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231,
|
||||||
|
|
||||||
|
0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339,
|
||||||
|
|
||||||
|
0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462,
|
||||||
|
|
||||||
|
0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a,
|
||||||
|
|
||||||
|
0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653,
|
||||||
|
|
||||||
|
0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b,
|
||||||
|
|
||||||
|
0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4,
|
||||||
|
|
||||||
|
0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc,
|
||||||
|
|
||||||
|
0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5,
|
||||||
|
|
||||||
|
0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd,
|
||||||
|
|
||||||
|
0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6,
|
||||||
|
|
||||||
|
0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
|
||||||
|
|
||||||
|
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97,
|
||||||
|
|
||||||
|
0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f,
|
||||||
|
|
||||||
|
0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188,
|
||||||
|
|
||||||
|
0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080,
|
||||||
|
|
||||||
|
0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9,
|
||||||
|
|
||||||
|
0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1,
|
||||||
|
|
||||||
|
0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea,
|
||||||
|
|
||||||
|
0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2,
|
||||||
|
|
||||||
|
0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db,
|
||||||
|
|
||||||
|
0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3,
|
||||||
|
|
||||||
|
0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c,
|
||||||
|
|
||||||
|
0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844,
|
||||||
|
|
||||||
|
0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d,
|
||||||
|
|
||||||
|
0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75,
|
||||||
|
|
||||||
|
0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
|
||||||
|
|
||||||
|
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26,
|
||||||
|
|
||||||
|
0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f,
|
||||||
|
|
||||||
|
0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17,
|
||||||
|
|
||||||
|
0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };
|
||||||
|
|
||||||
|
public static int calculate(byte[] buf, int length) {
|
||||||
|
|
||||||
|
int counter;
|
||||||
|
int crc = 0;
|
||||||
|
|
||||||
|
if(length > buf.length) return 0;
|
||||||
|
|
||||||
|
for (counter = 0; counter < length; counter++) {
|
||||||
|
crc = (crc << 8) ^ TABLE1021[((crc >> 8) ^ buf[counter]) & 0x00FF];
|
||||||
|
}
|
||||||
|
return crc & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int calculate(byte[] buf) {
|
||||||
|
return calculate(buf, buf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
sec-vermgr/src/main/resources/application.properties
Normal file
10
sec-vermgr/src/main/resources/application.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
server.port=9914
|
||||||
|
server.servlet.context-path=/
|
||||||
|
|
||||||
|
spring.application.name=vermgr
|
||||||
|
spring.application.build=20250519
|
||||||
|
|
||||||
|
version_server_port = 9916
|
||||||
|
|
||||||
|
#version manage
|
||||||
|
version.path = firmware
|
||||||
Loading…
x
Reference in New Issue
Block a user