package com.hivekion.room.bean; import cn.hutool.extra.spring.SpringUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.hivekion.Global; import com.hivekion.baseData.entity.Scenario; import com.hivekion.baseData.service.ScenarioService; import com.hivekion.common.MultiPointGeoPosition; import com.hivekion.common.entity.ResponseCmdInfo; import com.hivekion.common.redis.RedisUtil; import com.hivekion.common.uuid.IdUtils; import com.hivekion.enums.WsCmdTypeEnum; import com.hivekion.room.RoomManager; import com.hivekion.room.func.TaskAction; import com.hivekion.scenario.entity.ScenarioResource; import com.hivekion.scenario.entity.ScenarioTask; import com.hivekion.scenario.service.impl.BattleSupplierServiceImpl; import com.hivekion.scenario.service.impl.ScenarioResourceServiceImpl; import com.hivekion.scenario.service.impl.ScenarioTaskServiceImpl; import com.hivekion.statistic.bean.EditScenarioInfo; import com.hivekion.statistic.bean.ScenarioInfo; import com.hivekion.statistic.bean.StatisticBean; import com.hivekion.statistic.service.impl.StatisticServiceImpl; import com.hivekion.supplier.entity.SupplierRequest; import com.hivekion.supplier.service.impl.SupplierRequestServiceImpl; import com.hivekion.team.entity.Teaminfo; import com.hivekion.team.service.impl.TeaminfoServiceImpl; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.TreeMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.core.env.Environment; import org.springframework.web.reactive.function.client.WebClient; /** * [类的简要说明] *

* [详细描述,可选] *

* * @author LiDongYU * @since 2025/7/22 */ @Slf4j public abstract class AbtParentTask implements TaskAction { protected final AtomicBoolean taskFinishedStatus = new AtomicBoolean(false); /** * 油料消耗速率 */ protected double fuelConsumption = 0; protected double fuelThreshold = 98; /** * 开始点坐标 */ private final AtomicReference startPoint = new AtomicReference<>(); /** * 距离和坐标的对应关系 */ protected final TreeMap distanceInfoMap = new TreeMap<>(); //任务数据 protected final ScenarioTask scenarioTask; //房间ID protected final String roomId; //http请求 protected WebClient webClient = WebClient.create(); protected final AtomicReference coordinateReference = new AtomicReference<>(); /** * 需求产生标志 */ protected final AtomicBoolean requestFlag = new AtomicBoolean(false); private StatisticBean statisticBean; public AbtParentTask(ScenarioTask scenarioTask, String roomId) { this.scenarioTask = scenarioTask; this.roomId = roomId; Scenario scenario = SpringUtil.getBean(ScenarioService.class) .getScenarioById(scenarioTask.getScenarioId()); statisticBean = SpringUtil.getBean(StatisticServiceImpl.class) .statistic(scenarioTask.getResourceId()); initEnv(); } public void addScheduledExecutorServiceRefenceToRoom( ScheduledExecutorService scheduledExecutorService) { RoomManager.addFuture(scheduledExecutorService, this.roomId); } @Override public void doSomeThing() { } @Override public String getId() { return scenarioTask.getId(); } @Override public String getType() { return scenarioTask.getTaskType(); } //获取房间的持续时间 public long getDuringTime() { return RoomManager.getRoomDuringTime(this.roomId); } //获取房间状态 public boolean getRoomStatus() { return RoomManager.isRunning(roomId); } public void createBattleTaskOnTimingHandle(BizTaskOnTiming bizTaskOnTiming) { ScheduledExecutorService schedule = Executors.newScheduledThreadPool( 1); schedule.scheduleWithFixedDelay(() -> { bizTaskOnTiming.execTask(); }, 0, 1, TimeUnit.SECONDS); //房间统一管理定时器;房间关闭后,定时器销毁 addScheduledExecutorServiceRefenceToRoom(schedule); } /** * 初始化环境 */ private void initEnv() { try { //获取油品消耗规则 String fuelConsumptionStr = SpringUtil.getBean(Environment.class) .getProperty("fuel.spreed"); fuelConsumption = Double.parseDouble(fuelConsumptionStr == null ? "0" : fuelConsumptionStr); fuelThreshold = Double.parseDouble(SpringUtil.getBean(Environment.class) .getProperty("fuel.warn", "0")); log.info("初始化::{}-油料消耗速度::{},油料最低阈值::{},当前油料::{}", this.scenarioTask.getResourceId(), fuelConsumptionStr, fuelThreshold, getCurrentFuel()); } catch (Exception e) { log.error("init env exception", e); } } protected void initPath() { try { log.info("init path"); String url = SpringUtil.getBean(Environment.class).getProperty("path.planning.url"); String params = url + "?" + "profile=car" + "&point=" + scenarioTask.getFromLat() + "," + scenarioTask.getFromLng() + "&point=" + scenarioTask.getToLat() + "," + scenarioTask.getToLng() + "&points_encoded=false" + "&algorithm=alternative_route&alternative_route.max_paths=3"; log.info("params:;{}", params); Room room = RoomManager.getRoom(this.roomId); Map resourceMap = room.getScenarioResourceMap(); //获取路线信息 String result = webClient.get().uri(params) .retrieve() .bodyToMono(String.class) .block(); log.info("init path finished ::{}", result); JSONObject pointJson = JSON.parseObject(result); //获取路径点 if (pointJson != null) { JSONObject pointsObj = pointJson.getJSONArray("paths").getJSONObject(0) .getJSONObject("points"); JSONArray coordinates = pointsObj.getJSONArray("coordinates"); //组装信息 Map dataMap = new HashMap<>(); dataMap.put("resourceId", scenarioTask.getResourceId()); dataMap.put("points", coordinates); dataMap.put("teamType", resourceMap.get(this.scenarioTask.getResourceId()).getType()); if (RoomManager.getRoom(roomId) != null) { RoomManager.getRoom(roomId) .addResourcePath(this.scenarioTask.getResourceId(), coordinates); } //推送路径任务 Global.sendCmdInfoQueue.add( ResponseCmdInfo.create(WsCmdTypeEnum.PATH_INIT.getCode(), roomId, scenarioTask.getScenarioId(), dataMap)); SpringUtil.getBean(RedisUtil.class).hset( scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "init_path", JSON.toJSONString(coordinates)); //计算各个点的累计距离和坐标的对应关系 double beforeLng = Double.parseDouble(scenarioTask.getFromLng()); double beforeLat = Double.parseDouble(scenarioTask.getFromLat()); double total = 0; for (int i = 0; i < coordinates.size(); i++) { JSONArray coordinate = coordinates.getJSONArray(i); Double lng = coordinate.getDouble(0); Double lat = coordinate.getDouble(1); double distance = MultiPointGeoPosition.haversine(beforeLat, beforeLng, lat, lng); //当前总距离 total = total + distance; //定义坐标对象 Coordinate coordinateInfo = new Coordinate(); coordinateInfo.setLat(lat); coordinateInfo.setLng(lng); //记录距离和数组列表直接的索引关系 distanceInfoMap.put(total, coordinateInfo); beforeLng = lng; beforeLat = lat; } log.info("路线节点个数::{},总距离::{}",distanceInfoMap.size(),distanceInfoMap.lastKey()); //设置第一个开始位置 startPoint.set(distanceInfoMap.firstKey()); } } catch (Exception e) { log.error("error::", e); } } protected void updatePath(double speed, TaskAction duringAction, TaskAction finishedAction) { AtomicLong duringTime = new AtomicLong(0); ScheduledExecutorService schedule = Executors.newScheduledThreadPool( 1); schedule.scheduleWithFixedDelay(() -> { try { Room room = RoomManager.getRoom(this.roomId); if (room == null || room.isTimeExpired()) { log.error("房间不存在或者已经到达想定结束时间"); return; } if (this.getRoomStatus()) { //自动生成的任务不需要判断油量;不要在生成新的任务 if (!"general".equals(scenarioTask.getFromSource())) { double currentFuel = getCurrentFuel(); double totalFuel = statisticBean.getFuel().getTotal(); if (currentFuel <= 0 || totalFuel <= 0) { log.error("{}:油量为零停止移动", this.scenarioTask.getResourceId()); return; } if (currentFuel * 100 / totalFuel < fuelThreshold && !requestFlag.get()) { log.error("{}-油料不足,需要补充,新建需求和任务", scenarioTask.getResourceId()); requestFlag.set(true); //需要产生需求 produceFuelRequest(); //产生任务 produceTask(currentFuel); return; }else{ log.info("======油料充足====={}=={}==={}===={}=======",currentFuel,totalFuel,fuelThreshold,requestFlag.get()); } if (currentFuel * 100 / totalFuel < fuelThreshold) { return; } } if (distanceInfoMap.isEmpty()) { return; } if (duringAction != null) { duringAction.doSomeThing(); } //跑动距离 double distance = duringTime.getAndAdd(RoomManager.getMag(roomId)) * speed; //获取大与此距离的第一个路线点key Entry endPoint = distanceInfoMap.ceilingEntry(distance); if (endPoint == null) { endPoint = distanceInfoMap.lastEntry(); } log.info("当前距离{}",distance); //ws数据 List dataList = new ArrayList<>(); HashMap dataMap = new HashMap<>(); dataMap.put("resourceId", scenarioTask.getResourceId()); dataMap.put("points", dataList); if (Double.compare(distance, endPoint.getKey()) < 0) { //获取小于最大值的第一个key Double lowerKey = distanceInfoMap.lowerKey(endPoint.getKey()); if (lowerKey == null) { lowerKey = endPoint.getKey(); } NavigableMap subPathMap = distanceInfoMap.subMap(startPoint.get(), true, lowerKey, true); for (Double key : subPathMap.keySet()) { Coordinate coordinate = subPathMap.get(key); dataList.add(new double[]{coordinate.getLng(), coordinate.getLat()}); } double diff = distance - lowerKey; //插入值 double[] insertPoints = MultiPointGeoPosition.pointAlong( distanceInfoMap.get(lowerKey).getLat(), distanceInfoMap.get(lowerKey).getLng(), endPoint.getValue().getLat(), endPoint.getValue().getLng(), diff); dataList.add(new double[]{insertPoints[1], insertPoints[0]}); Coordinate coordinate = new Coordinate(); coordinate.setLat(insertPoints[0]); coordinate.setLng(insertPoints[1]); distanceInfoMap.put(distance, coordinate); startPoint.set(distance); coordinateReference.set(coordinate); SpringUtil.getBean(RedisUtil.class).hset( scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "position", JSON.toJSONString(coordinate)); Global.sendCmdInfoQueue.add( ResponseCmdInfo.create(WsCmdTypeEnum.PATH_UPDATE.getCode(), roomId, scenarioTask.getScenarioId(), dataMap)); //修改位置信息 EditScenarioInfo editScenarioInfo = getEditScenarioInfo( this.scenarioTask.getResourceId()); editScenarioInfo.getJbxx().getTeam().setLat(coordinate.getLat() + ""); editScenarioInfo.getJbxx().getTeam().setLng(coordinate.getLng() + ""); setEditScenarioInfo(editScenarioInfo, scenarioTask.getResourceId()); pushStatus(scenarioTask.getResourceId()); } else if (Double.compare(distance, endPoint.getKey()) == 0) { NavigableMap subPathMap = distanceInfoMap.subMap(startPoint.get(), true, endPoint.getKey(), true); for (Double key : subPathMap.keySet()) { Coordinate coordinate = subPathMap.get(key); dataList.add(new double[]{coordinate.getLng(), coordinate.getLat()}); } coordinateReference.set(endPoint.getValue()); startPoint.set(endPoint.getKey()); Global.sendCmdInfoQueue.add( ResponseCmdInfo.create(WsCmdTypeEnum.PATH_UPDATE.getCode(), roomId, scenarioTask.getScenarioId(), dataMap)); } else { if (finishedAction != null) { finishedAction.doSomeThing(); } taskFinishedStatus.set(true); //完成路径 Global.sendCmdInfoQueue.add( ResponseCmdInfo.create(WsCmdTypeEnum.PATH_FINISHED.getCode(), roomId, scenarioTask.getScenarioId(), dataMap)); //任务终止 schedule.shutdown(); } } } catch (Exception e) { log.error("error::", e); } }, 0, 1, TimeUnit.SECONDS); //房间统一管理定时器;房间关闭后,定时器销毁 addScheduledExecutorServiceRefenceToRoom(schedule); } private RedisUtil redisUtil; protected EditScenarioInfo getEditScenarioInfo(String resourceId) { String updJsonStr = (String) SpringUtil.getBean(RedisUtil.class).hget( this.scenarioTask.getScenarioId() + "-" + roomId + "-" + resourceId, "updScenarioInfo"); return JSON.parseObject(updJsonStr, EditScenarioInfo.class); } protected void setEditScenarioInfo(EditScenarioInfo editScenarioInfo, String resourceId) { SpringUtil.getBean(RedisUtil.class).hset( this.scenarioTask.getScenarioId() + "-" + roomId + "-" + resourceId, "updScenarioInfo", JSON.toJSONString(editScenarioInfo)); } //统一推送方法 protected void pushStatus(String resourceId) { if (StringUtils.isBlank(resourceId)) { return; } if (redisUtil == null) { redisUtil = SpringUtil.getBean(RedisUtil.class); } String jsonStr = (String) redisUtil.hget( this.scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "scenarioInfo"); ResponseCmdInfo respObj = new ResponseCmdInfo<>(); respObj.setData(jsonStr); respObj.setRoom(roomId); respObj.setScenarioId(scenarioTask.getScenarioId()); respObj.setCmdType("scenarioInfo"); Global.sendCmdInfoQueue.add(respObj); String updJsonStr = (String) redisUtil.hget( this.scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "updScenarioInfo"); EditScenarioInfo editScenarioInfo = JSON.parseObject(updJsonStr, EditScenarioInfo.class); //设置食品 editScenarioInfo.getJbxx().getFood().setCurrent(new BigDecimal( editScenarioInfo.getJbxx().getFood().getCurrent()).setScale(3, RoundingMode.HALF_UP).doubleValue()); //设置油 editScenarioInfo.getJbxx().getFuel().setCurrent(new BigDecimal( editScenarioInfo.getJbxx().getFuel().getCurrent()).setScale(3, RoundingMode.HALF_UP).doubleValue()); //设置弹药 editScenarioInfo.getJbxx().getAmmunition().setCurrent(new BigDecimal( editScenarioInfo.getJbxx().getAmmunition().getCurrent()).setScale(3, RoundingMode.HALF_UP).doubleValue()); //设置药材 editScenarioInfo.getJbxx().getMedical().setCurrent(new BigDecimal( editScenarioInfo.getJbxx().getMedical().getCurrent()).setScale(3, RoundingMode.HALF_UP).doubleValue()); //设置水 editScenarioInfo.getJbxx().getWater().setCurrent(new BigDecimal( editScenarioInfo.getJbxx().getWater().getCurrent()).setScale(3, RoundingMode.HALF_UP).doubleValue()); ResponseCmdInfo respUpdObj = new ResponseCmdInfo<>(); respUpdObj.setData(JSON.toJSONString(editScenarioInfo)); respUpdObj.setRoom(roomId); respUpdObj.setScenarioId(scenarioTask.getScenarioId()); respUpdObj.setCmdType("updScenarioInfo"); Global.sendCmdInfoQueue.add(respUpdObj); } protected double getCurrentFuel() { Object statisticObj = SpringUtil.getBean(RedisUtil.class).hget( scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "scenarioInfo"); if (statisticObj != null) { ScenarioInfo scenarioInfo = JSON.parseObject(statisticObj.toString(), ScenarioInfo.class); return scenarioInfo.getFuel().getCurrent(); } return 0; } private void produceFuelRequest() { log.info("{}-产生油料保障需求", this.scenarioTask.getResourceId()); SupplierRequest supplierRequest = new SupplierRequest(); supplierRequest.setId(IdUtils.simpleUUID()); supplierRequest.setFromResourceId(scenarioTask.getResourceId()); supplierRequest.setSupplierNum(String.valueOf(statisticBean.getFuel().getTotal())); supplierRequest.setSupplierType("fuel"); supplierRequest.setGeneralTime(LocalDateTime.now()); supplierRequest.setLat(scenarioTask.getToLat()); supplierRequest.setLng(scenarioTask.getToLng()); supplierRequest.setHandleFlag(1); SpringUtil.getBean(SupplierRequestServiceImpl.class).save(supplierRequest); String jsonStr = (String)SpringUtil.getBean(RedisUtil.class).hget(scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(),"scenarioInfo"); ScenarioInfo scenarioInfoOnTime = JSONObject.parseObject(jsonStr,ScenarioInfo.class); String updJsonStr = (String) SpringUtil.getBean(RedisUtil.class).hget(scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "updScenarioInfo"); EditScenarioInfo updScenarioInfo = JSON.parseObject(updJsonStr, EditScenarioInfo.class); scenarioInfoOnTime.getSupplierRequests().add(supplierRequest); updScenarioInfo.getSupplierRequests().add(supplierRequest); SpringUtil.getBean(RedisUtil.class).hset(scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "scenarioInfo", JSONObject.toJSONString(scenarioInfoOnTime)); SpringUtil.getBean(RedisUtil.class).hset(scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(), "updScenarioInfo", JSON.toJSONString(updScenarioInfo)); } private void produceTask(double fuel) { try { Map teamInfoMap = SpringUtil.getBean(TeaminfoServiceImpl.class) .teamInfoMap(); log.info("{}-产生自动保障任务", this.scenarioTask.getResourceId()); List resourceList = SpringUtil.getBean(BattleSupplierServiceImpl.class) .selectSupplierResource(scenarioTask.getResourceId()); log.info("{}-可选保障分队长度{}", scenarioTask.getResourceId(), resourceList.size()); if (!resourceList.isEmpty()) { ScenarioResource supplierResource = null; // 找出油料保障分队 for (ScenarioResource resource : resourceList) { Teaminfo teaminfo = teamInfoMap.get(resource.getResourceId()); if ("SUPPLIER_FUEL".equals(teaminfo.getRoleCode())) { supplierResource = resource; break; } } if (supplierResource == null) { log.error("找不到对应的油料保障分队"); return; } //找出油料仓库 List resources = SpringUtil.getBean(ScenarioResourceServiceImpl.class) .selectResourceByRoleCode(scenarioTask.getScenarioId(), "WARE_FUEL_HOUSE"); if (resources.isEmpty()) { log.error("找不到油料仓库"); return; } produceMoveTask(supplierResource, resources.get(0), this.coordinateReference.get(), fuel); } else { log.error("{}-没有保障分队可以选择", scenarioTask.getResourceId()); } } catch (Exception e) { log.error("produceTask exception", e); } } private void produceMoveTask(ScenarioResource supplierResource, ScenarioResource fuelResource, Coordinate coordinate, double minusFuel) { ScenarioTask task = new ScenarioTask(); task.setId(IdUtils.simpleUUID()); task.setScenarioId(scenarioTask.getScenarioId()); task.setResourceId(supplierResource.getId()); task.setTaskType("1"); task.setName("油料保障任务-移动第一节点(自动)"); task.setFromLat(supplierResource.getLat()); task.setFromLng(supplierResource.getLng()); task.setToLat(fuelResource.getLat()); task.setToLng(fuelResource.getLng()); task.setStartTime(LocalDateTime.now()); task.setFromSource("general"); log.info("油料保障任务-移动resourceId::{}", supplierResource.getId()); SpringUtil.getBean(ScenarioTaskServiceImpl.class).save(task); MoveTask moveTask = new MoveTask(task, this.roomId, new TaskAction() { @Override public void doSomeThing() { if(coordinate!=null){ //创建一个送达任务 log.info("目标地址::{},{}",coordinate.getLat(),coordinate.getLng()); ScenarioTask task = new ScenarioTask(); task.setId(IdUtils.simpleUUID()); task.setScenarioId(scenarioTask.getScenarioId()); task.setResourceId(supplierResource.getId()); task.setTaskType("6"); task.setName("油料保障任务-移动第二节点(自动)"); task.setInsureResourceId(scenarioTask.getResourceId()); task.setSupplierNum(statisticBean.getFuel().getTotal() - minusFuel); task.setToLat(coordinate.getLat() + ""); task.setToLng(coordinate.getLng() + ""); task.setStartTime(LocalDateTime.now()); task.setFromLat(fuelResource.getLat()); task.setFromLng(fuelResource.getLng()); task.setFromSource("general"); SpringUtil.getBean(ScenarioTaskServiceImpl.class).save(task); SupplierTask supplierTask = new SupplierTask(task, roomId); RoomManager.addAction(roomId, 0, supplierTask); }else{ log.error("没有目标地址"); } } @Override public String getId() { return ""; } @Override public String getType() { return ""; } }); //立即执行 RoomManager.addAction(roomId, 0, moveTask); } } interface BizTaskOnTiming { public void execTask(); } // 自定义线程工厂 class CustomThreadFactory implements ThreadFactory { private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public CustomThreadFactory(String namePrefix) { this.namePrefix = namePrefix + "-thread-"; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement()); thread.setDaemon(false); // 设置为非守护线程 thread.setPriority(Thread.NORM_PRIORITY); return thread; } }