simulation-backend/src/main/java/com/hivekion/room/bean/AbtParentTask.java
2025-09-21 06:23:47 +08:00

524 lines
19 KiB
Java

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.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 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;
/**
* [类的简要说明]
* <p>
* [详细描述,可选]
* <p>
*
* @author LiDongYU
* @since 2025/7/22
*/
@Slf4j
public abstract class AbtParentTask implements TaskAction {
/**
* 油料消耗速率
*/
protected double fuelConsumption = 0;
protected double fuelThreshold = 0;
/**
* 开始点坐标
*/
private final AtomicReference<Double> startPoint = new AtomicReference<>();
/**
* 距离和坐标的对应关系
*/
protected final TreeMap<Double, Coordinate> distanceInfoMap = new TreeMap<>();
//任务数据
protected final ScenarioTask scenarioTask;
//房间ID
protected final String roomId;
//http请求
protected WebClient webClient = WebClient.create();
protected final AtomicReference<Coordinate> coordinateReference = new AtomicReference<>();
/**
* 需求产生标志
*/
private final AtomicBoolean requestFlag = new AtomicBoolean(false);
private StatisticBean statisticBean;
//线程池
// protected ThreadPoolExecutor executor = new ThreadPoolExecutor(
// 5, // 核心线程数
// 10, // 最大线程数
// 60L, // 空闲线程存活时间
// TimeUnit.SECONDS, // 时间单位
// new LinkedBlockingQueue<>(100), // 任务队列
// new CustomThreadFactory("MyPool"), // 线程工厂
// new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
// );
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, 5, 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<String, ScenarioResource> 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<String, Object> 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;
}
//设置第一个开始位置
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 {
if (this.getRoomStatus()) {
double currentFuel = getCurrentFuel();
double totalFuel = statisticBean.getFuel().getTotal();
if (currentFuel <= 0 || totalFuel <= 0) {
log.error("{}:油量为零停止移动", this.scenarioTask.getResourceId());
return;
}
log.info("{}-当前比值{},阈值{}", scenarioTask.getResourceId(),
currentFuel * 100 / totalFuel,
fuelThreshold);
if (currentFuel * 100 / totalFuel < fuelThreshold && !requestFlag.get()) {
log.info("{}-油料不足,需要补充,新建需求和任务", scenarioTask.getResourceId());
requestFlag.set(true);
//需要产生需求
produceFuelRequest();
//产生任务
produceTask(currentFuel);
return;
}
if (currentFuel * 100 / totalFuel < fuelThreshold) {
log.error("{}:油量不足停止移动,等待补给", this.scenarioTask.getResourceId());
return;
}
if (distanceInfoMap.isEmpty()) {
return;
}
if (duringAction != null) {
duringAction.doSomeThing();
}
log.info("移动中.....");
//跑动距离
double distance = duringTime.getAndAdd(RoomManager.getMag(roomId)) * speed;
//获取大与此距离的第一个路线点key
Entry<Double, Coordinate> endPoint = distanceInfoMap.ceilingEntry(distance);
if (endPoint == null) {
endPoint = distanceInfoMap.lastEntry();
}
//ws数据
List<double[]> dataList = new ArrayList<>();
HashMap<Object, Object> 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<Double, Coordinate> 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);
pushStatus(scenarioTask.getResourceId());
} else if (Double.compare(distance, endPoint.getKey()) == 0) {
NavigableMap<Double, Coordinate> 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();
}
//完成路径
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) {
SpringUtil.getBean(RedisUtil.class).hset(
this.scenarioTask.getScenarioId() + "-" + roomId + "-" + scenarioTask.getResourceId(),
"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<String> 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");
ResponseCmdInfo<String> respUpdObj = new ResponseCmdInfo<>();
respUpdObj.setData(updJsonStr);
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);
}
private void produceTask(double fuel) {
try {
log.info("{}-产生自动保障任务", this.scenarioTask.getResourceId());
List<ScenarioResource> resourceList = SpringUtil.getBean(BattleSupplierServiceImpl.class)
.selectSupplierResource(scenarioTask.getResourceId());
log.info("{}-可选保障分队长度{}", scenarioTask.getResourceId(), resourceList.size());
if (!resourceList.isEmpty()) {
ScenarioTask task = new ScenarioTask();
task.setId(IdUtils.simpleUUID());
task.setScenarioId(scenarioTask.getScenarioId());
task.setResourceId(resourceList.get(0).getId());
task.setTaskType("6");
task.setInsureResourceId(scenarioTask.getResourceId());
task.setSupplierNum(statisticBean.getFuel().getTotal() - fuel);
task.setToLat(this.coordinateReference.get().getLat() + "");
task.setToLng(this.coordinateReference.get().getLng() + "");
task.setStartTime(LocalDateTime.now());
task.setFromLat(resourceList.get(0).getLat());
task.setFromLng(resourceList.get(0).getLng());
task.setFromSource("general");
log.info("{}-保障分队id::{},from::{},to::{}", this.scenarioTask.getInsureResourceId(),
task.getResourceId(), task.getFromLat() + "," + task.getFromLng(),
task.getToLat() + "," + task.getToLng());
SpringUtil.getBean(ScenarioTaskServiceImpl.class).save(task);
//增加到房间任务
SupplierTask supplierTask = new SupplierTask(task, roomId);
//立即执行
RoomManager.addAction(roomId, 0, supplierTask);
} else {
log.error("{}-没有保障分队可以选择", scenarioTask.getResourceId());
}
} catch (Exception e) {
log.error("produceTask exception", e);
}
}
}
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;
}
}