Compare commits
No commits in common. "2d159953842abe29b9a0b228f5d18dc4189c27b9" and "cc5f3d07722d0ce2674d75c65a696739f5b33f51" have entirely different histories.
2d15995384
...
cc5f3d0772
@ -19,8 +19,7 @@ public class OpLogManager {
|
|||||||
public final static Short OP_OBJ_GROUP = 6;
|
public final static Short OP_OBJ_GROUP = 6;
|
||||||
public final static Short OP_OBJ_WARNING = 7;
|
public final static Short OP_OBJ_WARNING = 7;
|
||||||
public final static Short OP_OBJ_SYS = 8;
|
public final static Short OP_OBJ_SYS = 8;
|
||||||
public final static Short OP_OBJ_FWD_RECORD = 9;
|
public final static Short OP_OBJ_DEVICE_MSG = 9;
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
OpLogMapper opLogMapper;
|
OpLogMapper opLogMapper;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.imdroid.beidou_fwd.service;
|
package com.imdroid.beidou_fwd.service;
|
||||||
|
|
||||||
|
import com.imdroid.common.util.ThreadManager;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -12,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class TCPClient {
|
public class TCPClient {
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
@ -27,7 +29,7 @@ public class TCPClient {
|
|||||||
TCPListener listener;
|
TCPListener listener;
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
//new Thread(this::connect, host+":"+port+" forwarder tcp-client").start();
|
new Thread(this::connect, host+":"+port+" forwarder tcp-client").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(String dest_addr, int dest_port, TCPListener listener) {
|
public void init(String dest_addr, int dest_port, TCPListener listener) {
|
||||||
@ -62,14 +64,14 @@ public class TCPClient {
|
|||||||
if (!future.isSuccess()) {
|
if (!future.isSuccess()) {
|
||||||
logger.info("{}:{} tcp connect failed. {}",host,port,future.cause().toString());
|
logger.info("{}:{} tcp connect failed. {}",host,port,future.cause().toString());
|
||||||
//重连交给后端线程执行
|
//重连交给后端线程执行
|
||||||
/*future.channel().eventLoop().schedule(() -> {
|
future.channel().eventLoop().schedule(() -> {
|
||||||
logger.info("{}:{} tcp client reconnect",host,port);
|
logger.info("{}:{} tcp client reconnect",host,port);
|
||||||
try {
|
try {
|
||||||
connect();
|
connect();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}, 5000, TimeUnit.MILLISECONDS);*/
|
}, 5000, TimeUnit.MILLISECONDS);
|
||||||
} else {
|
} else {
|
||||||
/*future.channel().config().setWriteBufferWaterMark(new WriteBufferWaterMark(
|
/*future.channel().config().setWriteBufferWaterMark(new WriteBufferWaterMark(
|
||||||
1024 * 1024, // low
|
1024 * 1024, // low
|
||||||
@ -87,26 +89,9 @@ public class TCPClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean tryReconnect() throws Exception{
|
|
||||||
new Thread(this::connect, host+":"+port+" forwarder tcp-client").start();
|
|
||||||
for(int i=0; i<20; i++){
|
|
||||||
Thread.sleep(50);
|
|
||||||
if(channel!=null && channel.isActive()) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeAndFlush(String json) {
|
public void writeAndFlush(String json) {
|
||||||
ByteBuf sendBuffer = Unpooled.buffer();
|
ByteBuf sendBuffer = Unpooled.buffer();
|
||||||
sendBuffer.writeBytes(json.getBytes(StandardCharsets.UTF_8));
|
sendBuffer.writeBytes(json.getBytes(StandardCharsets.UTF_8));
|
||||||
//logger.info("send to {}: {}",host,json);
|
|
||||||
if(channel==null || !channel.isActive()){
|
|
||||||
try {
|
|
||||||
if(!tryReconnect()) return;
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
channel.writeAndFlush(sendBuffer).addListener(future -> {
|
channel.writeAndFlush(sendBuffer).addListener(future -> {
|
||||||
if (future.isSuccess()) {
|
if (future.isSuccess()) {
|
||||||
logger.info("send to tcp:"+host+" succeed.");
|
logger.info("send to tcp:"+host+" succeed.");
|
||||||
@ -124,7 +109,7 @@ public class TCPClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onDisconnect(boolean isIdle){
|
public void onDisconnect(boolean isIdle){
|
||||||
/*if(connectTime.isBefore(LocalDateTime.now().minusMinutes(1))) {
|
if(connectTime.isBefore(LocalDateTime.now().minusMinutes(1))) {
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@ -135,7 +120,7 @@ public class TCPClient {
|
|||||||
logger.error(e.toString());
|
logger.error(e.toString());
|
||||||
}
|
}
|
||||||
},isIdle?30:10, TimeUnit.SECONDS);
|
},isIdle?30:10, TimeUnit.SECONDS);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMessage(String msg){
|
public void onMessage(String msg){
|
||||||
|
|||||||
@ -37,17 +37,17 @@ public class Forwarder {
|
|||||||
static boolean isFwdTableInit = true;//false;
|
static boolean isFwdTableInit = true;//false;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
GnssDeviceMapper deviceMapper;
|
private GnssDeviceMapper deviceMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
GnssCalcDataMapper gnssDataMapper;
|
private GnssCalcDataMapper gnssDataMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
FwdRecordMapper fwdRecordsMapper;
|
private FwdRecordMapper fwdRecordsMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
ResendRecordMapper resendRecordMapper;
|
private ResendRecordMapper resendRecordMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
GnssStatusMapper gnssStatusMapper;
|
private GnssStatusMapper gnssStatusMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
GnssGroupFwdMapper fwdMapper;
|
GnssGroupFwdMapper fwdMapper;
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
package com.imdroid.beidou_fwd.task;
|
package com.imdroid.beidou_fwd.task;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.imdroid.beidou_fwd.service.TCPClient;
|
import com.imdroid.beidou_fwd.service.TCPClient;
|
||||||
import com.imdroid.secapi.dto.ResendRecord;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
@ -11,8 +8,6 @@ import org.springframework.scheduling.annotation.Scheduled;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -39,7 +34,7 @@ public class GXJSForwarder extends GXXfzForwarder{
|
|||||||
/**
|
/**
|
||||||
* 每半小时转发GNSS解算结果
|
* 每半小时转发GNSS解算结果
|
||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
|
@Scheduled(cron = "0 15,45 * * * ?") // 每30分钟执行一次
|
||||||
private void forwardGnss() {
|
private void forwardGnss() {
|
||||||
if(!enabled) return;
|
if(!enabled) return;
|
||||||
logger.debug("gxjs forwardGnss");
|
logger.debug("gxjs forwardGnss");
|
||||||
@ -48,28 +43,6 @@ public class GXJSForwarder extends GXXfzForwarder{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void forwardHistoryGnss(){
|
void forwardHistoryGnss(){
|
||||||
// 1.从转发记录表里检索待补传记录时间表,含设备Id,时间段
|
|
||||||
QueryWrapper<ResendRecord> queryWrapper = new QueryWrapper<>();
|
|
||||||
queryWrapper.eq("fwd_group_id",fwdGroupId);
|
|
||||||
queryWrapper.eq("state",ResendRecord.STATE_BREAK_POINT);
|
|
||||||
queryWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
|
||||||
List<ResendRecord> resendRecordsList = resendRecordMapper.selectList(queryWrapper);
|
|
||||||
|
|
||||||
if(resendRecordsList!=null && resendRecordsList.size()>0){
|
|
||||||
//修改状态
|
|
||||||
UpdateWrapper<ResendRecord> updateWrapper = new UpdateWrapper<>();
|
|
||||||
updateWrapper.eq("fwd_group_id",fwdGroupId);
|
|
||||||
updateWrapper.eq("state",ResendRecord.STATE_BREAK_POINT);
|
|
||||||
updateWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
|
|
||||||
updateWrapper.set("state",ResendRecord.STATE_FWDING);
|
|
||||||
int updateNum = resendRecordMapper.update(null, updateWrapper);
|
|
||||||
logger.debug("{} forward history records: {}, update {}",fwdGroupId, resendRecordsList.size(),updateNum);
|
|
||||||
// 2.检索这个这个时间段的解算结果,如果有数据则单个终端转发,标志记录为已补传
|
|
||||||
for(ResendRecord record:resendRecordsList){
|
|
||||||
if(record.getProjectid()!=null)
|
|
||||||
logger.debug("{} forward history {}",fwdGroupId, record.getProjectid());
|
|
||||||
forwardBatchGnssRecords(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,10 +71,10 @@ public class GXXfzForwarder extends Forwarder{
|
|||||||
// 发送
|
// 发送
|
||||||
batchNum++;
|
batchNum++;
|
||||||
|
|
||||||
if(batchNum==20){
|
if(batchNum==50){
|
||||||
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
|
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
|
||||||
//logger.debug("project {}: forwad {} gnss records to {}",projectId, dataList.size(),fwdGroupId);
|
logger.debug("project {}: forwad {} gnss records to {}",projectId, dataList.size(),fwdGroupId);
|
||||||
//logger.debug(json);
|
logger.debug(json);
|
||||||
try {
|
try {
|
||||||
listener.clear();
|
listener.clear();
|
||||||
xfzTcpClient.writeAndFlush(json);
|
xfzTcpClient.writeAndFlush(json);
|
||||||
@ -107,8 +107,8 @@ public class GXXfzForwarder extends Forwarder{
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean checkResult() throws InterruptedException {
|
boolean checkResult() throws InterruptedException {
|
||||||
// 等待应答,最多等500ms
|
// 等待应答,最多等1s
|
||||||
for(int i=0; i<10; i++){
|
for(int i=0; i<20; i++){
|
||||||
Thread.sleep(50);
|
Thread.sleep(50);
|
||||||
if(listener.state == XFZTCPListener.STATE_OK) return true;
|
if(listener.state == XFZTCPListener.STATE_OK) return true;
|
||||||
else if(listener.state == XFZTCPListener.STATE_FAILED) return false;
|
else if(listener.state == XFZTCPListener.STATE_FAILED) return false;
|
||||||
|
|||||||
@ -48,7 +48,7 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
|
|
||||||
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
|
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
|
||||||
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
|
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
|
||||||
private static final long D300_FORWARD_INTERVAL = 5000; // 5秒,单位毫秒
|
private static final long D300_FORWARD_INTERVAL = 10000; // 10秒,单位毫秒
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void execute(D331RtcmMessage message) {
|
public Void execute(D331RtcmMessage message) {
|
||||||
|
|||||||
@ -2,16 +2,18 @@ package com.imdroid.beidou.controller;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.imdroid.beidou.common.HttpResult;
|
|
||||||
import com.imdroid.beidou.service.CommonExcelService;
|
import com.imdroid.beidou.service.CommonExcelService;
|
||||||
import com.imdroid.secapi.dto.*;
|
import com.imdroid.secapi.dto.GnssGroupFwd;
|
||||||
|
import com.imdroid.secapi.dto.GnssGroupFwdMapper;
|
||||||
|
import com.imdroid.secapi.dto.ResendRecord;
|
||||||
|
import com.imdroid.secapi.dto.ResendRecordMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.*;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@ -20,10 +22,6 @@ public class GnssResendController extends BasicController implements CommonExcel
|
|||||||
GnssGroupFwdMapper gnssGroupFwdMapper;
|
GnssGroupFwdMapper gnssGroupFwdMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
ResendRecordMapper resendRecordMapper;
|
ResendRecordMapper resendRecordMapper;
|
||||||
@Autowired
|
|
||||||
TenantMapper tenantMapper;
|
|
||||||
@Autowired
|
|
||||||
OpLogManager opLogManager;
|
|
||||||
|
|
||||||
/********* 推送页面 *********/
|
/********* 推送页面 *********/
|
||||||
@RequestMapping("/page/resend_records")
|
@RequestMapping("/page/resend_records")
|
||||||
@ -35,19 +33,6 @@ public class GnssResendController extends BasicController implements CommonExcel
|
|||||||
|
|
||||||
return "/page/resend_records";
|
return "/page/resend_records";
|
||||||
}
|
}
|
||||||
@RequestMapping("/page/table/resend_record_add")
|
|
||||||
public String gnssAddDev(Model m, HttpSession session) {
|
|
||||||
initModel(m, session);
|
|
||||||
//以下用于下拉框数据
|
|
||||||
List<Tenant> tenants = tenantMapper.selectList(null);
|
|
||||||
List<GnssGroupFwd> gnssGroupFwds = gnssGroupFwdMapper.selectList(null);
|
|
||||||
|
|
||||||
m.addAttribute("tenant_list", tenants);
|
|
||||||
m.addAttribute("gnss_group_fwd_list", gnssGroupFwds);
|
|
||||||
m.addAttribute("device", new GnssDevice());
|
|
||||||
|
|
||||||
return "/page/table/resend_record_add";
|
|
||||||
}
|
|
||||||
|
|
||||||
/********* 推送数据 *********/
|
/********* 推送数据 *********/
|
||||||
@RequestMapping("/fwd/resend_records")
|
@RequestMapping("/fwd/resend_records")
|
||||||
@ -56,40 +41,6 @@ public class GnssResendController extends BasicController implements CommonExcel
|
|||||||
return this.pageList(session, page, limit, searchParams);
|
return this.pageList(session, page, limit, searchParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/fwd/resend_records/add")
|
|
||||||
@ResponseBody
|
|
||||||
public String update(HttpSession session, @RequestBody JSONObject object) throws Exception {
|
|
||||||
// 从请求参数中创建对象
|
|
||||||
ResendRecord resendRecord = JSONObject.toJavaObject(object,ResendRecord.class);
|
|
||||||
resendRecord.setCreatetime(LocalDateTime.now());
|
|
||||||
resendRecord.setState(ResendRecord.STATE_BREAK_POINT);
|
|
||||||
int num = resendRecordMapper.insert(resendRecord);
|
|
||||||
if (num == 0) {
|
|
||||||
return HttpResult.failed();
|
|
||||||
} else {
|
|
||||||
opLogManager.addLog(getLoginUser(session),getTenantId(session),
|
|
||||||
OpLogManager.OP_TYPE_ADD,
|
|
||||||
OpLogManager.OP_OBJ_FWD_RECORD,
|
|
||||||
"create new resend record");
|
|
||||||
return HttpResult.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/fwd/resend_records/delete")
|
|
||||||
@ResponseBody
|
|
||||||
public String delete(HttpSession session, @RequestParam String del_id) throws Exception {
|
|
||||||
int num = resendRecordMapper.deleteById(del_id);
|
|
||||||
opLogManager.addLog(getLoginUser(session),getTenantId(session),
|
|
||||||
OpLogManager.OP_TYPE_DEL,
|
|
||||||
OpLogManager.OP_OBJ_FWD_RECORD,
|
|
||||||
del_id + " deleted");
|
|
||||||
if (num == 0) {
|
|
||||||
return HttpResult.failed();
|
|
||||||
} else{
|
|
||||||
return HttpResult.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取实体类的class
|
* 获取实体类的class
|
||||||
*
|
*
|
||||||
|
|||||||
@ -95,10 +95,6 @@ var DeviceOverview = (function() {
|
|||||||
return DeviceMarkers.locateMyLocation();
|
return DeviceMarkers.locateMyLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearDeviceFilter() {
|
|
||||||
return SearchFilter.clearDeviceFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
|
|
||||||
@ -111,7 +107,6 @@ var DeviceOverview = (function() {
|
|||||||
locateDeviceOnMap: locateDeviceOnMap,
|
locateDeviceOnMap: locateDeviceOnMap,
|
||||||
locateDeviceDirectly: locateDeviceDirectly,
|
locateDeviceDirectly: locateDeviceDirectly,
|
||||||
locateMyLocation: locateMyLocation,
|
locateMyLocation: locateMyLocation,
|
||||||
clearDeviceFilter: clearDeviceFilter,
|
|
||||||
|
|
||||||
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
|
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
|
||||||
toggleDeviceId: toggleDeviceId,
|
toggleDeviceId: toggleDeviceId,
|
||||||
@ -146,4 +141,3 @@ window.queryDevices = DeviceOverview.queryDevices;
|
|||||||
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
|
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
|
||||||
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
|
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
|
||||||
window.locateMyLocation = DeviceOverview.locateMyLocation;
|
window.locateMyLocation = DeviceOverview.locateMyLocation;
|
||||||
window.clearDeviceFilter = DeviceOverview.clearDeviceFilter;
|
|
||||||
@ -3,8 +3,6 @@ var SearchFilter = (function() {
|
|||||||
|
|
||||||
var currentSearchedDevice = null;
|
var currentSearchedDevice = null;
|
||||||
var markerState = 3; // 1:all; 2:orange; 3:red
|
var markerState = 3; // 1:all; 2:orange; 3:red
|
||||||
var filteredDeviceIds = [];
|
|
||||||
var isFilterActive = false;
|
|
||||||
|
|
||||||
function searchDevice(deviceId) {
|
function searchDevice(deviceId) {
|
||||||
if (!deviceId || !deviceId.trim()) {
|
if (!deviceId || !deviceId.trim()) {
|
||||||
@ -13,13 +11,6 @@ var SearchFilter = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deviceId = deviceId.trim();
|
deviceId = deviceId.trim();
|
||||||
|
|
||||||
// 多设备
|
|
||||||
if (deviceId.indexOf(',') !== -1) {
|
|
||||||
return handleMultiDeviceSearch(deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单设备
|
|
||||||
currentSearchedDevice = deviceId;
|
currentSearchedDevice = deviceId;
|
||||||
|
|
||||||
var success = DeviceMarkers.locateDevice(deviceId);
|
var success = DeviceMarkers.locateDevice(deviceId);
|
||||||
@ -46,126 +37,6 @@ var SearchFilter = (function() {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMultiDeviceSearch(deviceIdsString) {
|
|
||||||
var deviceIds = deviceIdsString.split(',').map(function(id) {
|
|
||||||
return id.trim();
|
|
||||||
}).filter(function(id) {
|
|
||||||
return id !== '';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deviceIds.length === 0) {
|
|
||||||
clearSearch();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredDeviceIds = deviceIds;
|
|
||||||
|
|
||||||
var foundDevices = filterDevicesByIds(deviceIds);
|
|
||||||
|
|
||||||
if (foundDevices.length > 0) {
|
|
||||||
showFilterStatus(deviceIds.length);
|
|
||||||
|
|
||||||
isFilterActive = true;
|
|
||||||
|
|
||||||
var firstDevice = foundDevices[0];
|
|
||||||
var geometry = firstDevice.getGeometry();
|
|
||||||
var coordinates = geometry.getCoordinates();
|
|
||||||
|
|
||||||
if (MapCore.getMap()) {
|
|
||||||
MapCore.getMap().getView().animate({
|
|
||||||
center: coordinates,
|
|
||||||
zoom: 12,
|
|
||||||
duration: 1000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.layer && typeof window.layer.msg === 'function') {
|
|
||||||
window.layer.msg('已过滤显示 ' + foundDevices.length + ' 个设备');
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (window.layer && typeof window.layer.msg === 'function') {
|
|
||||||
window.layer.msg('未找到任何匹配的设备');
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterDevicesByIds(deviceIds) {
|
|
||||||
if (!DeviceMarkers.getAllFeatures) return [];
|
|
||||||
|
|
||||||
var allFeatures = DeviceMarkers.getAllFeatures();
|
|
||||||
var foundDevices = [];
|
|
||||||
var vectorSource = MapCore.getVectorSource();
|
|
||||||
|
|
||||||
if (!vectorSource) return [];
|
|
||||||
|
|
||||||
var myLocationFeature = DeviceMarkers.getMyLocationFeature();
|
|
||||||
|
|
||||||
vectorSource.clear();
|
|
||||||
|
|
||||||
if (myLocationFeature) {
|
|
||||||
vectorSource.addFeature(myLocationFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < allFeatures.length; i++) {
|
|
||||||
var feature = allFeatures[i];
|
|
||||||
var deviceInfo = feature.get('deviceInfo');
|
|
||||||
|
|
||||||
if (deviceInfo) {
|
|
||||||
var deviceId = String(deviceInfo.deviceid).trim();
|
|
||||||
|
|
||||||
for (var j = 0; j < deviceIds.length; j++) {
|
|
||||||
var searchId = String(deviceIds[j]).trim();
|
|
||||||
|
|
||||||
if (deviceId === searchId ||
|
|
||||||
deviceId.indexOf(searchId) !== -1 ||
|
|
||||||
deviceId.replace(/\s+/g, '') === searchId.replace(/\s+/g, '')) {
|
|
||||||
vectorSource.addFeature(feature);
|
|
||||||
foundDevices.push(feature);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return foundDevices;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFilterStatus(count) {
|
|
||||||
var filterStatus = document.getElementById('filterStatus');
|
|
||||||
|
|
||||||
if (filterStatus) {
|
|
||||||
filterStatus.innerHTML =
|
|
||||||
'<span style="color: #1aa094; font-weight: bold; font-size: 12px;">定位中</span>' +
|
|
||||||
'<button class="toolbar-btn" onclick="clearDeviceFilter()" style="background: #f56565; margin-left: 6px;">' +
|
|
||||||
'<i class="layui-icon layui-icon-close" style="font-size: 12px;"></i>' +
|
|
||||||
'</button>';
|
|
||||||
|
|
||||||
filterStatus.style.display = 'flex';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideFilterStatus() {
|
|
||||||
var filterStatus = document.getElementById('filterStatus');
|
|
||||||
if (filterStatus) {
|
|
||||||
filterStatus.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearDeviceFilter() {
|
|
||||||
isFilterActive = false;
|
|
||||||
filteredDeviceIds = [];
|
|
||||||
hideFilterStatus();
|
|
||||||
|
|
||||||
applyCurrentFilter();
|
|
||||||
|
|
||||||
if (window.layer && typeof window.layer.msg === 'function') {
|
|
||||||
window.layer.msg('已清除设备过滤');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSearch() {
|
function clearSearch() {
|
||||||
currentSearchedDevice = null;
|
currentSearchedDevice = null;
|
||||||
var searchInput = document.getElementById('deviceSearchNew');
|
var searchInput = document.getElementById('deviceSearchNew');
|
||||||
@ -173,12 +44,8 @@ var SearchFilter = (function() {
|
|||||||
searchInput.value = '';
|
searchInput.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFilterActive) {
|
// 恢复到当前的过滤状态
|
||||||
clearDeviceFilter();
|
applyCurrentFilter();
|
||||||
} else {
|
|
||||||
// 恢复到当前的过滤状态
|
|
||||||
applyCurrentFilter();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCurrentFilter() {
|
function applyCurrentFilter() {
|
||||||
@ -299,14 +166,6 @@ var SearchFilter = (function() {
|
|||||||
markerState = state;
|
markerState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFilterModeActive() {
|
|
||||||
return isFilterActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilteredDeviceIds() {
|
|
||||||
return filteredDeviceIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchDevice: searchDevice,
|
searchDevice: searchDevice,
|
||||||
clearSearch: clearSearch,
|
clearSearch: clearSearch,
|
||||||
@ -319,9 +178,6 @@ var SearchFilter = (function() {
|
|||||||
locateDeviceDirectly: locateDeviceDirectly,
|
locateDeviceDirectly: locateDeviceDirectly,
|
||||||
getCurrentSearchedDevice: getCurrentSearchedDevice,
|
getCurrentSearchedDevice: getCurrentSearchedDevice,
|
||||||
getMarkerState: getMarkerState,
|
getMarkerState: getMarkerState,
|
||||||
setMarkerState: setMarkerState,
|
setMarkerState: setMarkerState
|
||||||
clearDeviceFilter: clearDeviceFilter,
|
|
||||||
isFilterModeActive: isFilterModeActive,
|
|
||||||
getFilteredDeviceIds: getFilteredDeviceIds
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@ -526,9 +526,11 @@
|
|||||||
|
|
||||||
/* 测量状态指示器样式 */
|
/* 测量状态指示器样式 */
|
||||||
#measureStatus {
|
#measureStatus {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
border: 1px solid rgba(72, 187, 120, 0.3);
|
border: 1px solid rgba(72, 187, 120, 0.3);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(72, 187, 120, 0.2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
@ -539,20 +541,6 @@
|
|||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#filterStatus {
|
|
||||||
border: 1px solid rgba(26, 160, 148, 0.3);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filterStatus .toolbar-btn {
|
|
||||||
padding: 2px 6px;
|
|
||||||
height: 24px;
|
|
||||||
min-width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-forecast-card {
|
.weather-forecast-card {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 55%;
|
top: 55%;
|
||||||
@ -804,14 +792,6 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 6px 0px;
|
padding: 6px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#filterStatus {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 6px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -916,7 +896,7 @@
|
|||||||
<div class="toolbar-divider"></div>
|
<div class="toolbar-divider"></div>
|
||||||
|
|
||||||
<div class="toolbar-item">
|
<div class="toolbar-item">
|
||||||
<input type="text" id="deviceSearchNew" class="toolbar-input" placeholder="设备编号">
|
<input type="text" id="deviceSearchNew" class="toolbar-input" placeholder="搜索设备编号">
|
||||||
<button class="toolbar-btn" onclick="searchDeviceNew()">搜索</button>
|
<button class="toolbar-btn" onclick="searchDeviceNew()">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -982,14 +962,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="measureStatus" class="toolbar-item" style="display: none;">
|
<div id="measureStatus" class="toolbar-item" style="display: none;">
|
||||||
<span style="color: #1aa094; font-weight: bold; font-size: 12px;">测量中</span>
|
<span style="color: #48bb78; font-weight: bold; font-size: 12px;">测量中</span>
|
||||||
<button class="toolbar-btn" onclick="finishMeasuring()" style="background: #f56565; margin-left: 6px;">
|
<button class="toolbar-btn" onclick="finishMeasuring()" style="background: #f56565; margin-left: 6px;">
|
||||||
<i class="layui-icon layui-icon-close" style="font-size: 12px;"></i>
|
<i class="layui-icon layui-icon-close" style="font-size: 12px;"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="filterStatus" class="toolbar-item" style="display: none;">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -1049,7 +1026,6 @@
|
|||||||
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
|
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
|
||||||
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
|
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
|
||||||
window.switchMapType = DeviceOverview.switchMapType;
|
window.switchMapType = DeviceOverview.switchMapType;
|
||||||
window.locateMyLocation = DeviceOverview.locateMyLocation;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -67,9 +67,9 @@
|
|||||||
var cfg_cols = [
|
var cfg_cols = [
|
||||||
{field: 'deviceid', title: '设备号', width: 100},
|
{field: 'deviceid', title: '设备号', width: 100},
|
||||||
{field: 'createtime', title: '上报时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
{field: 'createtime', title: '上报时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
{field: 'roll', title: 'roll', templet: "<div>{{d.roll.toFixed(3)}}</div>"},
|
{field: 'roll', title: 'roll', templet: "<div>{{d.roll.toFixed(2)}}</div>"},
|
||||||
{field: 'pitch', title: 'pitch', templet: "<div>{{d.pitch.toFixed(3)}}</div>"},
|
{field: 'pitch', title: 'pitch', templet: "<div>{{d.pitch.toFixed(2)}}</div>"},
|
||||||
{field: 'yaw', title: 'yaw', templet: "<div>{{d.yaw.toFixed(3)}}</div>"},
|
{field: 'yaw', title: 'yaw', templet: "<div>{{d.yaw.toFixed(2)}}</div>"},
|
||||||
{field: 'rssi', title: '信号强度'},
|
{field: 'rssi', title: '信号强度'},
|
||||||
{field: 'voltage', title: '电压(mV)'},
|
{field: 'voltage', title: '电压(mV)'},
|
||||||
{field: 'solarvoltage', title: '光伏电压(mV)'},
|
{field: 'solarvoltage', title: '光伏电压(mV)'},
|
||||||
|
|||||||
@ -53,38 +53,16 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<table class="layui-hide" id="forwardParaTableId" lay-filter="forwardParaTableFilter"></table>
|
<table class="layui-hide" id="forwardParaTableId" lay-filter="forwardParaTableFilter"></table>
|
||||||
<script type="text/html" id="currentTableBar">
|
|
||||||
<a class="layui-btn layui-btn-xs layui-btn-danger data-count-delete" lay-event="delete">删除</a>
|
|
||||||
</script>
|
|
||||||
<script type="text/html" id="toolbarTop">
|
|
||||||
<div class="layui-btn-container" th:if="${role=='SUPER_ADMIN'}">
|
|
||||||
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add">添加</button>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<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="none">
|
||||||
layui.use(['form', 'table'], function () {
|
layui.use(['form', 'table'], function () {
|
||||||
var $ = layui.$,
|
var $ = layui.$,
|
||||||
form = layui.form,
|
form = layui.form,
|
||||||
table = layui.table,
|
table = layui.table,
|
||||||
laydate = layui.laydate;
|
laydate = layui.laydate;
|
||||||
var cfg_cols = [
|
|
||||||
{field: 'projectid', title: '项目号', sort: true},
|
|
||||||
{field: 'deviceid', title: '设备号'},
|
|
||||||
{field: 'createtime', title: '推送时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
|
||||||
{field: 'starttime', title: '数据开始时间', templet: "<div>{{layui.util.toDateString(d.starttime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
|
||||||
{field: 'endtime', title: '数据结束时间', templet: "<div>{{layui.util.toDateString(d.endtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
|
||||||
{field: 'fwd_group_id', title: '推送组'},
|
|
||||||
{field: 'state', title: '状态',templet: '#stateTrans'},
|
|
||||||
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
|
|
||||||
];
|
|
||||||
if([[${role}]] != "SUPER_ADMIN") {
|
|
||||||
cfg_cols[7].hide = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
laydate.render({
|
laydate.render({
|
||||||
elem: '#ID-laydate-start-date1',
|
elem: '#ID-laydate-start-date1',
|
||||||
type: 'datetime'
|
type: 'datetime'
|
||||||
@ -107,11 +85,17 @@
|
|||||||
table.render({
|
table.render({
|
||||||
elem: '#forwardParaTableId',
|
elem: '#forwardParaTableId',
|
||||||
url: '/fwd/resend_records',
|
url: '/fwd/resend_records',
|
||||||
toolbar: '#toolbarTop',
|
toolbar: '#toolbarTable',
|
||||||
defaultToolbar: ['filter'],
|
defaultToolbar: ['filter'],
|
||||||
cols: [
|
cols: [[
|
||||||
cfg_cols
|
{field: 'projectid', title: '项目号', sort: true},
|
||||||
],
|
{field: 'deviceid', title: '设备号'},
|
||||||
|
{field: 'createtime', title: '推送时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
|
{field: 'starttime', title: '数据开始时间', templet: "<div>{{layui.util.toDateString(d.starttime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
|
{field: 'endtime', title: '数据结束时间', templet: "<div>{{layui.util.toDateString(d.endtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
|
||||||
|
{field: 'fwd_group_id', title: '推送组'},
|
||||||
|
{field: 'state', title: '状态',templet: '#stateTrans'}
|
||||||
|
]],
|
||||||
limits: [10, 20, 50, 100, 150],
|
limits: [10, 20, 50, 100, 150],
|
||||||
limit: 10,
|
limit: 10,
|
||||||
page: true,
|
page: true,
|
||||||
@ -135,50 +119,8 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('toolbar(forwardParaTableFilter)', function (obj) {
|
});
|
||||||
if (obj.event === 'add') { // 监听添加操作
|
|
||||||
var index = layer.open({
|
|
||||||
title: '添加补传记录',
|
|
||||||
type: 2,
|
|
||||||
shade: 0.2,
|
|
||||||
maxmin:true,
|
|
||||||
shadeClose: true,
|
|
||||||
area: ['100%', '100%'],
|
|
||||||
content: '../page/table/resend_record_add'
|
|
||||||
});
|
|
||||||
$(window).on("resize", function () {
|
|
||||||
layer.full(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table.on('tool(forwardParaTableFilter)', function (obj) {
|
|
||||||
var data = obj.data;
|
|
||||||
if (obj.event === 'delete') {
|
|
||||||
layer.confirm('确定删除?', function(index){
|
|
||||||
$.ajax({
|
|
||||||
type:"POST",
|
|
||||||
url:"/fwd/resend_records/delete",
|
|
||||||
data:{
|
|
||||||
'del_id':data.id
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
table.reload('forwardParaTableId');
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
console.log("ajax error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
layer.close(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function onUpdated(){
|
|
||||||
layui.table.reload('forwardParaTableId');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/html" id="stateTrans">
|
<script type="text/html" id="stateTrans">
|
||||||
@ -187,9 +129,9 @@
|
|||||||
{{# } else if(d.state == 1){ }}
|
{{# } else if(d.state == 1){ }}
|
||||||
<span class="layui-badge layui-bg-red">推送失败</span>
|
<span class="layui-badge layui-bg-red">推送失败</span>
|
||||||
{{# } else if(d.state == 2){ }}
|
{{# } else if(d.state == 2){ }}
|
||||||
<span class="layui-badge layui-bg-orange">断点补传</span>
|
<span class="layui-badge layui-bg-red">断点补传</span>
|
||||||
{{# } else { }}
|
{{# } else { }}
|
||||||
<span class="layui-badge layui-bg-blue">推送中</span>
|
<span class="layui-badge layui-bg-orange">推送中</span>
|
||||||
{{# } }}
|
{{# } }}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,136 +0,0 @@
|
|||||||
<!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">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="layui-form layuimini-form">
|
|
||||||
<input type="hidden" name="id" id="id">
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">所属部门</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<select name="tenantid" id="tenantid" lay-search="" lay-filter="tenant">
|
|
||||||
<option value="">全部</option>
|
|
||||||
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.id}"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">所属项目</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<select name="project_id" id="projectid" lay-search="" lay-filter="project">
|
|
||||||
<option value="">全部</option>
|
|
||||||
<option th:each="item : ${project_list}" th:text="${item.project_id}" th:value="${item.project_id}"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">设备编号</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<input type="text" name="deviceid" id="deviceid" class="layui-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label required">推送组</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<select name="fwd_group_id" id="fwd_group_id" lay-verify="required" lay-reqtext="不能为空" lay-search="">
|
|
||||||
<option th:each="item : ${gnss_group_fwd_list}" th:text="${item.name}" th:value="${item.name}"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label required">时间段</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<input type="text" name="starttime" autocomplete="off" lay-verify="required" lay-reqtext="不能为空" id="ID-laydate-start-date" class="layui-input" placeholder="开始日期">
|
|
||||||
</div>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<input type="text" name="endtime" autocomplete="off" lay-verify="required" lay-reqtext="不能为空" id="ID-laydate-end-date" class="layui-input" placeholder="结束日期">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<div class="layui-input-block">
|
|
||||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="../../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
|
||||||
<script>
|
|
||||||
layui.use(['form'], function () {
|
|
||||||
var form = layui.form,
|
|
||||||
$ = layui.$,
|
|
||||||
laydate = layui.laydate;
|
|
||||||
var iframeIndex = parent.layer.getFrameIndex(window.name);
|
|
||||||
|
|
||||||
laydate.render({
|
|
||||||
elem: '#ID-laydate-start-date',
|
|
||||||
type: 'datetime'
|
|
||||||
});
|
|
||||||
laydate.render({
|
|
||||||
elem: '#ID-laydate-end-date',
|
|
||||||
type: 'datetime'
|
|
||||||
});
|
|
||||||
// 所属部门下拉框改变,修改项目下拉框
|
|
||||||
form.on('select(tenant)', function (data) {
|
|
||||||
console.log(data.value);
|
|
||||||
$.ajax({
|
|
||||||
type:"GET",
|
|
||||||
url:"/gnss/device/q_project",
|
|
||||||
data:{
|
|
||||||
'tenantid':data.value
|
|
||||||
},
|
|
||||||
success: function(result) {
|
|
||||||
$('#projectid').empty();
|
|
||||||
$('#projectid').append(new Option("全部", ""));
|
|
||||||
$('#deviceid').empty();
|
|
||||||
//console.log(result);
|
|
||||||
$.each(result.data, function (index, item) {
|
|
||||||
$('#projectid')
|
|
||||||
.append(new Option(item.project_id, item.project_id));
|
|
||||||
});
|
|
||||||
layui.form.render("select");
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
console.log("ajax error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//监听提交
|
|
||||||
form.on('submit(saveBtn)', function (data) {
|
|
||||||
$.ajax({
|
|
||||||
type:"POST",
|
|
||||||
url:"/fwd/resend_records/add",
|
|
||||||
contentType: "application/json;charset=UTF-8",
|
|
||||||
data: JSON.stringify(data.field),
|
|
||||||
success: function (result) {
|
|
||||||
parent.onUpdated();
|
|
||||||
parent.layer.close(iframeIndex);
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
console.log("ajax error");
|
|
||||||
parent.layer.close(iframeIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,173 +0,0 @@
|
|||||||
package com.imdroid.ntripproxy.controller;
|
|
||||||
|
|
||||||
import com.imdroid.ntripproxy.service.DeviceManager;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.ApplicationArguments;
|
|
||||||
import org.springframework.boot.ApplicationRunner;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备管理命令行控制器
|
|
||||||
* 提供命令行接口管理设备描述
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class DeviceController implements ApplicationRunner {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DeviceController.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DeviceManager deviceManager;
|
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(ApplicationArguments args) {
|
|
||||||
// 启动命令行交互线程
|
|
||||||
executor.submit(this::startCommandLineInterface);
|
|
||||||
|
|
||||||
logger.info("设备管理命令行控制器已启动,输入 'help' 查看可用命令");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动命令行交互界面
|
|
||||||
*/
|
|
||||||
private void startCommandLineInterface() {
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
|
|
||||||
logger.info("设备管理命令行已启动,输入 'help' 查看帮助");
|
|
||||||
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.trim().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = line.trim().split("\\s+", 2);
|
|
||||||
String command = parts[0].toLowerCase();
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (command) {
|
|
||||||
case "help":
|
|
||||||
printHelp();
|
|
||||||
break;
|
|
||||||
case "list":
|
|
||||||
listDevices();
|
|
||||||
break;
|
|
||||||
case "desc":
|
|
||||||
if (parts.length < 2) {
|
|
||||||
System.out.println("错误: 缺少参数,格式: desc <deviceId> <description>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String[] descParts = parts[1].split("\\s+", 2);
|
|
||||||
if (descParts.length < 2) {
|
|
||||||
System.out.println("错误: 缺少描述,格式: desc <deviceId> <description>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
setDeviceDescription(descParts[0], descParts[1]);
|
|
||||||
break;
|
|
||||||
case "get":
|
|
||||||
if (parts.length < 2) {
|
|
||||||
System.out.println("错误: 缺少设备ID,格式: get <deviceId>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
getDeviceInfo(parts[1]);
|
|
||||||
break;
|
|
||||||
case "exit":
|
|
||||||
System.out.println("退出命令行界面");
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
System.out.println("未知命令: " + command);
|
|
||||||
printHelp();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("命令执行出错: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("命令行界面异常: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印帮助信息
|
|
||||||
*/
|
|
||||||
private void printHelp() {
|
|
||||||
System.out.println("可用命令:");
|
|
||||||
System.out.println(" help - 显示帮助信息");
|
|
||||||
System.out.println(" list - 列出所有设备");
|
|
||||||
System.out.println(" desc <deviceId> <description> - 设置设备描述");
|
|
||||||
System.out.println(" get <deviceId> - 获取设备信息");
|
|
||||||
System.out.println(" exit - 退出命令行界面");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 列出所有设备
|
|
||||||
*/
|
|
||||||
private void listDevices() {
|
|
||||||
Map<String, Integer> devicePorts = deviceManager.getAllDevicePorts();
|
|
||||||
|
|
||||||
if (devicePorts.isEmpty()) {
|
|
||||||
System.out.println("当前没有注册的设备");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("设备列表:");
|
|
||||||
System.out.println("------------------------------------------------------");
|
|
||||||
System.out.printf("%-15s %-10s %-30s%n", "设备ID", "端口", "描述");
|
|
||||||
System.out.println("------------------------------------------------------");
|
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> entry : devicePorts.entrySet()) {
|
|
||||||
String deviceId = entry.getKey();
|
|
||||||
int port = entry.getValue();
|
|
||||||
String description = deviceManager.getDeviceDescription(deviceId);
|
|
||||||
|
|
||||||
System.out.printf("%-15s %-10d %-30s%n", deviceId, port, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("------------------------------------------------------");
|
|
||||||
System.out.println("共 " + devicePorts.size() + " 个设备");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置设备描述
|
|
||||||
*/
|
|
||||||
private void setDeviceDescription(String deviceId, String description) {
|
|
||||||
int port = deviceManager.getDevicePort(deviceId);
|
|
||||||
|
|
||||||
if (port <= 0) {
|
|
||||||
System.out.println("警告: 设备 " + deviceId + " 尚未注册或分配端口");
|
|
||||||
System.out.println("描述将被保存,但只有当设备连接后才会显示在设备列表中");
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceManager.registerDeviceDescription(deviceId, description);
|
|
||||||
System.out.println("已为设备 " + deviceId + " 设置描述: " + description);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取设备信息
|
|
||||||
*/
|
|
||||||
private void getDeviceInfo(String deviceId) {
|
|
||||||
int port = deviceManager.getDevicePort(deviceId);
|
|
||||||
|
|
||||||
if (port <= 0) {
|
|
||||||
System.out.println("设备 " + deviceId + " 未找到或未分配端口");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String description = deviceManager.getDeviceDescription(deviceId);
|
|
||||||
|
|
||||||
System.out.println("设备信息:");
|
|
||||||
System.out.println(" 设备ID: " + deviceId);
|
|
||||||
System.out.println(" 端口: " + port);
|
|
||||||
System.out.println(" 描述: " + description);
|
|
||||||
System.out.println(" 连接命令: rtkrcv -t 127.0.0.1:" + port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,180 +0,0 @@
|
|||||||
package com.imdroid.ntripproxy.service;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备管理器,管理设备端口映射并提供API查询
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/gnss/device")
|
|
||||||
public class DeviceManager {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DeviceManager.class);
|
|
||||||
|
|
||||||
// 设备端口映射
|
|
||||||
private final Map<String, Integer> devicePorts = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
// 设备描述映射
|
|
||||||
private final Map<String, String> deviceDescriptions = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
// 设备描述文件
|
|
||||||
private static final String DEVICE_DESCRIPTIONS_FILE = "device_descriptions.properties";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化,从文件加载设备描述
|
|
||||||
*/
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
loadDeviceDescriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务关闭前,保存设备描述到文件
|
|
||||||
*/
|
|
||||||
@PreDestroy
|
|
||||||
public void destroy() {
|
|
||||||
saveDeviceDescriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从文件加载设备描述
|
|
||||||
*/
|
|
||||||
private void loadDeviceDescriptions() {
|
|
||||||
File file = new File(DEVICE_DESCRIPTIONS_FILE);
|
|
||||||
if (!file.exists()) {
|
|
||||||
logger.info("设备描述文件不存在,将在首次保存时创建");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream input = new FileInputStream(file)) {
|
|
||||||
Properties prop = new Properties();
|
|
||||||
prop.load(input);
|
|
||||||
|
|
||||||
for (String key : prop.stringPropertyNames()) {
|
|
||||||
deviceDescriptions.put(key, prop.getProperty(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("已从文件加载 {} 个设备描述", deviceDescriptions.size());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("加载设备描述文件失败: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存设备描述到文件
|
|
||||||
*/
|
|
||||||
public void saveDeviceDescriptions() {
|
|
||||||
try (OutputStream output = new FileOutputStream(DEVICE_DESCRIPTIONS_FILE)) {
|
|
||||||
Properties prop = new Properties();
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : deviceDescriptions.entrySet()) {
|
|
||||||
prop.setProperty(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
prop.store(output, "Device Descriptions");
|
|
||||||
logger.info("已保存 {} 个设备描述到文件", deviceDescriptions.size());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("保存设备描述文件失败: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册设备端口
|
|
||||||
*/
|
|
||||||
public void registerDevicePort(String deviceId, int port) {
|
|
||||||
devicePorts.put(deviceId, port);
|
|
||||||
logger.info("注册设备端口映射: {} -> {}", deviceId, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册设备描述
|
|
||||||
*/
|
|
||||||
public void registerDeviceDescription(String deviceId, String description) {
|
|
||||||
deviceDescriptions.put(deviceId, description);
|
|
||||||
// 每次更新描述时保存文件
|
|
||||||
saveDeviceDescriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取设备端口
|
|
||||||
*/
|
|
||||||
public int getDevicePort(String deviceId) {
|
|
||||||
return devicePorts.getOrDefault(deviceId, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取设备描述
|
|
||||||
*/
|
|
||||||
public String getDeviceDescription(String deviceId) {
|
|
||||||
return deviceDescriptions.getOrDefault(deviceId, "未知设备");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有设备端口映射
|
|
||||||
*/
|
|
||||||
public Map<String, Integer> getAllDevicePorts() {
|
|
||||||
return new HashMap<>(devicePorts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API: 获取所有设备端口映射
|
|
||||||
*/
|
|
||||||
@GetMapping("/ports")
|
|
||||||
public Map<String, Object> getDevicePorts() {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
Map<String, Map<String, Object>> devices = new HashMap<>();
|
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> entry : devicePorts.entrySet()) {
|
|
||||||
String deviceId = entry.getKey();
|
|
||||||
int port = entry.getValue();
|
|
||||||
String description = getDeviceDescription(deviceId);
|
|
||||||
|
|
||||||
Map<String, Object> deviceInfo = new HashMap<>();
|
|
||||||
deviceInfo.put("port", port);
|
|
||||||
deviceInfo.put("description", description);
|
|
||||||
|
|
||||||
devices.put(deviceId, deviceInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.put("devices", devices);
|
|
||||||
result.put("count", devices.size());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API: 获取特定设备的端口
|
|
||||||
*/
|
|
||||||
@GetMapping("/port/{deviceId}")
|
|
||||||
public Map<String, Object> getDevicePortById(@PathVariable String deviceId) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
|
|
||||||
int port = getDevicePort(deviceId);
|
|
||||||
if (port > 0) {
|
|
||||||
result.put("deviceId", deviceId);
|
|
||||||
result.put("port", port);
|
|
||||||
result.put("description", getDeviceDescription(deviceId));
|
|
||||||
result.put("success", true);
|
|
||||||
} else {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("message", "设备未找到或未分配端口");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,9 +7,11 @@ import javax.annotation.PostConstruct;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class RtcmClient extends UDPClient{
|
public class RtcmClient extends UDPClient{
|
||||||
// 修改为固定的本地地址和12000端口,确保RTCM数据转发到我们的筛选服务
|
@Value("${rtcm.server.host}")
|
||||||
private String ntripHost = "127.0.0.1";
|
private String ntripHost;
|
||||||
private int ntripPort = 12000;
|
|
||||||
|
@Value("${rtcm.server.port}")
|
||||||
|
private int ntripPort;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
void init(){
|
void init(){
|
||||||
|
|||||||
@ -1,233 +0,0 @@
|
|||||||
package com.imdroid.ntripproxy.service;
|
|
||||||
|
|
||||||
import com.imdroid.common.util.DataTypeUtil;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.ApplicationArguments;
|
|
||||||
import org.springframework.boot.ApplicationRunner;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.*;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RTCM数据筛选服务器
|
|
||||||
* 监听UDP 12000端口,接收RTCM数据
|
|
||||||
* 为每个deviceId创建独立的TCP服务器,供rtkrcv连接
|
|
||||||
*/
|
|
||||||
@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 BASE_TCP_PORT = 12001; // TCP端口起始号
|
|
||||||
private static final int BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
// 设备TCP服务器映射
|
|
||||||
private final Map<String, DeviceTcpServer> deviceServers = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
// 线程池
|
|
||||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
|
||||||
|
|
||||||
// 设备管理器
|
|
||||||
@Autowired
|
|
||||||
private DeviceManager deviceManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(ApplicationArguments args) {
|
|
||||||
// 启动UDP监听服务
|
|
||||||
executorService.submit(this::startUdpServer);
|
|
||||||
|
|
||||||
logger.info("RTCM筛选服务已启动 - UDP监听端口:{}, TCP服务端口范围:{}-{}",
|
|
||||||
UDP_PORT, BASE_TCP_PORT, BASE_TCP_PORT + 999);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动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);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("接收到设备 {} 的RTCM数据,长度: {}", deviceId, data.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取或创建设备的TCP服务器
|
|
||||||
DeviceTcpServer server = getOrCreateTcpServer(deviceId);
|
|
||||||
|
|
||||||
// 转发数据到TCP服务器
|
|
||||||
server.sendData(data);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("UDP服务异常: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从数据包中提取设备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 "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取或创建设备的TCP服务器
|
|
||||||
*/
|
|
||||||
private DeviceTcpServer getOrCreateTcpServer(String deviceId) {
|
|
||||||
return deviceServers.computeIfAbsent(deviceId, id -> {
|
|
||||||
int port = allocatePort(id);
|
|
||||||
DeviceTcpServer server = new DeviceTcpServer(id, port);
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
// 注册设备端口到设备管理器
|
|
||||||
deviceManager.registerDevicePort(id, port);
|
|
||||||
|
|
||||||
return server;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为设备分配TCP端口
|
|
||||||
*/
|
|
||||||
private int allocatePort(String deviceId) {
|
|
||||||
// 特殊设备ID处理,如果是3530795,则固定使用12002端口
|
|
||||||
if ("3530795".equals(deviceId)) {
|
|
||||||
return 12002;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他设备使用哈希分配端口
|
|
||||||
return BASE_TCP_PORT + (Math.abs(deviceId.hashCode()) % 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备TCP服务器,为每个设备创建独立的TCP服务器
|
|
||||||
*/
|
|
||||||
private class DeviceTcpServer {
|
|
||||||
private final String deviceId;
|
|
||||||
private final int port;
|
|
||||||
private ServerSocket serverSocket;
|
|
||||||
private final List<Socket> clients = new CopyOnWriteArrayList<>();
|
|
||||||
private boolean running = false;
|
|
||||||
|
|
||||||
public DeviceTcpServer(String deviceId, int port) {
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
if (running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
running = true;
|
|
||||||
executorService.submit(this::runServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runServer() {
|
|
||||||
try {
|
|
||||||
serverSocket = new ServerSocket(port);
|
|
||||||
logger.info("设备 {} 的TCP服务器已启动,端口: {}", deviceId, port);
|
|
||||||
|
|
||||||
// 接受客户端连接
|
|
||||||
while (running && !Thread.currentThread().isInterrupted()) {
|
|
||||||
try {
|
|
||||||
Socket client = serverSocket.accept();
|
|
||||||
clients.add(client);
|
|
||||||
logger.info("设备 {} 的新客户端已连接: {}", deviceId, client.getRemoteSocketAddress());
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!serverSocket.isClosed()) {
|
|
||||||
logger.error("接受客户端连接失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("设备 {} 的TCP服务器启动失败: {}", deviceId, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendData(byte[] data) {
|
|
||||||
if (clients.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Socket> disconnectedClients = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Socket client : clients) {
|
|
||||||
try {
|
|
||||||
if (client.isConnected() && !client.isClosed()) {
|
|
||||||
OutputStream out = client.getOutputStream();
|
|
||||||
out.write(data);
|
|
||||||
out.flush();
|
|
||||||
} else {
|
|
||||||
disconnectedClients.add(client);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("向设备 {} 的TCP客户端发送数据失败: {}", deviceId, e.getMessage());
|
|
||||||
disconnectedClients.add(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除断开连接的客户端
|
|
||||||
for (Socket client : disconnectedClients) {
|
|
||||||
clients.remove(client);
|
|
||||||
try {
|
|
||||||
client.close();
|
|
||||||
logger.info("已关闭设备 {} 的断开连接的TCP客户端", deviceId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("关闭TCP客户端异常: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
running = false;
|
|
||||||
|
|
||||||
// 关闭所有客户端连接
|
|
||||||
for (Socket client : clients) {
|
|
||||||
try {
|
|
||||||
client.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("关闭客户端连接失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clients.clear();
|
|
||||||
|
|
||||||
// 关闭服务器
|
|
||||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
|
||||||
try {
|
|
||||||
serverSocket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("关闭TCP服务器失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user