- 新增 SensorType 枚举(Radar=0, EO=1, IR=2)供前端探测设备 UI 分类 - EnumMetadata + GetEnums() 加入 SensorType(含中文翻译) - 对接文档同步更新 - 262 测试通过,Unity 编译通过
491 lines
18 KiB
C#
491 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using CounterDrone.Core.Models;
|
|
using CounterDrone.Core.Repository;
|
|
|
|
namespace CounterDrone.Core.Services
|
|
{
|
|
public class ScenarioService : IScenarioService
|
|
{
|
|
private readonly ScenarioRepository _scenarioRepo;
|
|
private readonly CombatSceneRepository _sceneRepo;
|
|
private readonly ControlZoneRepository _zoneRepo;
|
|
private readonly ScenarioDroneRepository _droneRepo;
|
|
private readonly ScenarioUnitRepository _unitRepo;
|
|
private readonly CloudDispersalRepository _cloudRepo;
|
|
private readonly RoutePlanRepository _routeRepo;
|
|
private readonly WaypointRepository _waypointRepo;
|
|
|
|
public ScenarioService(
|
|
ScenarioRepository scenarioRepo,
|
|
CombatSceneRepository sceneRepo,
|
|
ControlZoneRepository zoneRepo,
|
|
ScenarioDroneRepository targetRepo,
|
|
ScenarioUnitRepository equipRepo,
|
|
CloudDispersalRepository cloudRepo,
|
|
RoutePlanRepository routeRepo,
|
|
WaypointRepository waypointRepo)
|
|
{
|
|
_scenarioRepo = scenarioRepo;
|
|
_sceneRepo = sceneRepo;
|
|
_zoneRepo = zoneRepo;
|
|
_droneRepo = targetRepo;
|
|
_unitRepo = equipRepo;
|
|
_cloudRepo = cloudRepo;
|
|
_routeRepo = routeRepo;
|
|
_waypointRepo = waypointRepo;
|
|
}
|
|
|
|
// ========== scenario CRUD ==========
|
|
|
|
public Scenario CreateScenario(string name, string ScenarioNumber)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new ArgumentException("任务名称不能为空");
|
|
|
|
if (string.IsNullOrWhiteSpace(ScenarioNumber))
|
|
ScenarioNumber = GenerateScenarioNumber();
|
|
|
|
var existing = _scenarioRepo.GetByScenarioNumber(ScenarioNumber);
|
|
if (existing != null)
|
|
throw new ArgumentException($"任务编号 {ScenarioNumber} 已存在");
|
|
|
|
var scenario = new Scenario
|
|
{
|
|
Name = name,
|
|
ScenarioNumber = ScenarioNumber,
|
|
Status = (int)ScenarioStatus.Configuring,
|
|
CurrentStep = 1,
|
|
};
|
|
|
|
_scenarioRepo.Insert(scenario);
|
|
return scenario;
|
|
}
|
|
|
|
public void DeleteScenario(string id)
|
|
{
|
|
_scenarioRepo.Delete(id);
|
|
}
|
|
|
|
public PagedResult<Scenario> SearchScenarios(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
|
|
{
|
|
if (page < 1) page = 1;
|
|
if (pageSize < 1) pageSize = 10;
|
|
var offset = (page - 1) * pageSize;
|
|
|
|
var items = _scenarioRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total);
|
|
|
|
return new PagedResult<Scenario>
|
|
{
|
|
Items = items,
|
|
TotalCount = total,
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
};
|
|
}
|
|
|
|
public ScenarioConfig? GetScenarioDetail(string id)
|
|
{
|
|
var scenario = _scenarioRepo.GetById(id);
|
|
if (scenario == null) return null;
|
|
|
|
return new ScenarioConfig
|
|
{
|
|
Info = scenario,
|
|
Scene = _sceneRepo.GetById(id) ?? new CombatScene { ScenarioId = id },
|
|
ControlZones = _zoneRepo.GetByScenarioId(id),
|
|
Drones = _droneRepo.GetByScenarioId(id),
|
|
Units = _unitRepo.GetByScenarioId(id),
|
|
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { ScenarioId = id },
|
|
Routes = _routeRepo.GetByScenarioId(id),
|
|
WaypointGroups = _waypointRepo.GetByScenarioId(id)
|
|
.GroupBy(w => w.WaveId)
|
|
.ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()),
|
|
};
|
|
}
|
|
|
|
// ========== Step Save ==========
|
|
|
|
public void SaveScene(string scenarioId, CombatScene scene)
|
|
{
|
|
scene.ScenarioId = scenarioId;
|
|
var existing = _sceneRepo.GetById(scenarioId);
|
|
if (existing != null)
|
|
_sceneRepo.Update(scene);
|
|
else
|
|
_sceneRepo.Insert(scene);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public void SaveControlZones(string scenarioId, List<ControlZone> zones)
|
|
{
|
|
_zoneRepo.DeleteByScenarioId(scenarioId);
|
|
for (int i = 0; i < zones.Count; i++)
|
|
{
|
|
zones[i].ScenarioId = scenarioId;
|
|
zones[i].OrderIndex = i;
|
|
}
|
|
_zoneRepo.InsertAll(zones);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public ScenarioDrone SaveScenarioDrone(string scenarioId, ScenarioDrone target)
|
|
{
|
|
target.ScenarioId = scenarioId;
|
|
if (string.IsNullOrEmpty(target.Id))
|
|
target.Id = Guid.NewGuid().ToString();
|
|
|
|
var existing = _droneRepo.GetById(target.Id);
|
|
if (existing != null)
|
|
_droneRepo.Update(target);
|
|
else
|
|
_droneRepo.Insert(target);
|
|
TouchScenario(scenarioId);
|
|
return target;
|
|
}
|
|
|
|
public void SaveDeployment(string scenarioId, List<ScenarioUnit> equips)
|
|
{
|
|
_unitRepo.DeleteByScenarioId(scenarioId);
|
|
foreach (var e in equips)
|
|
{
|
|
e.ScenarioId = scenarioId;
|
|
if (string.IsNullOrEmpty(e.Id))
|
|
e.Id = Guid.NewGuid().ToString();
|
|
}
|
|
_unitRepo.InsertAll(equips);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public void AddDetection(string scenarioId, ScenarioUnit detection)
|
|
{
|
|
detection.ScenarioId = scenarioId;
|
|
detection.EquipmentRole = (int)EquipmentRole.Detection;
|
|
if (string.IsNullOrEmpty(detection.Id))
|
|
detection.Id = Guid.NewGuid().ToString();
|
|
_unitRepo.Insert(detection);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public void DeleteDetection(string detectionId)
|
|
{
|
|
_unitRepo.Delete(detectionId);
|
|
}
|
|
|
|
public List<ScenarioUnit> GetDetections(string scenarioId)
|
|
{
|
|
return _unitRepo.GetByScenarioIdAndRole(scenarioId, (int)EquipmentRole.Detection);
|
|
}
|
|
|
|
public void SaveCloudDispersal(string scenarioId, CloudDispersal cloud)
|
|
{
|
|
cloud.ScenarioId = scenarioId;
|
|
var existing = _cloudRepo.GetById(scenarioId);
|
|
if (existing != null)
|
|
_cloudRepo.Update(cloud);
|
|
else
|
|
_cloudRepo.Insert(cloud);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> waypoints)
|
|
{
|
|
route.ScenarioId = scenarioId;
|
|
route.WaveId = waveId;
|
|
var existing = _routeRepo.GetByScenarioAndWave(scenarioId, waveId);
|
|
if (existing != null)
|
|
{
|
|
route.Id = existing.Id;
|
|
_routeRepo.Update(route);
|
|
}
|
|
else
|
|
_routeRepo.Insert(route);
|
|
|
|
// 删除该批次的旧航路点,插入新的
|
|
var oldWps = _waypointRepo.GetByScenarioAndWave(scenarioId, waveId);
|
|
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
|
|
for (int i = 0; i < waypoints.Count; i++)
|
|
{
|
|
waypoints[i].ScenarioId = scenarioId;
|
|
waypoints[i].WaveId = waveId;
|
|
waypoints[i].OrderIndex = i;
|
|
if (string.IsNullOrEmpty(waypoints[i].Id))
|
|
waypoints[i].Id = Guid.NewGuid().ToString();
|
|
}
|
|
_waypointRepo.InsertAll(waypoints);
|
|
TouchScenario(scenarioId);
|
|
}
|
|
|
|
public void UpdateStep(string scenarioId, int step)
|
|
{
|
|
var scenario = _scenarioRepo.GetById(scenarioId);
|
|
if (scenario == null) return;
|
|
|
|
if (step < 1) step = 1;
|
|
if (step > 5) step = 5;
|
|
|
|
scenario.CurrentStep = step;
|
|
_scenarioRepo.Update(scenario);
|
|
}
|
|
|
|
public EnumMetadata GetEnums()
|
|
{
|
|
return new EnumMetadata
|
|
{
|
|
DroneType = EnumToList<Models.DroneType>(TranslateDrone),
|
|
PowerType = EnumToList<Models.PowerType>(TranslatePower),
|
|
PlatformType = EnumToList<Models.PlatformType>(TranslatePlatform),
|
|
AerosolType = EnumToList<Models.AerosolType>(TranslateAerosol),
|
|
WeatherType = EnumToList<Models.WeatherType>(TranslateWeather),
|
|
WindDirection = EnumToList<Models.WindDirection>(TranslateWindDirection),
|
|
SceneType = EnumToList<Models.SceneType>(TranslateScene),
|
|
FormationMode = EnumToList<Models.FormationMode>(TranslateFormation),
|
|
TriggerMode = EnumToList<Models.TriggerMode>(TranslateTrigger),
|
|
ReleaseMode = EnumToList<Models.ReleaseMode>(TranslateRelease),
|
|
EquipmentRole = EnumToList<Models.EquipmentRole>(TranslateEquipmentRole),
|
|
EntityType = EnumToList<Models.EntityType>(TranslateEntityType),
|
|
SensorType = EnumToList<Models.SensorType>(TranslateSensorType),
|
|
};
|
|
}
|
|
|
|
private static List<EnumItem> EnumToList<T>(Func<T, string> translate) where T : Enum
|
|
{
|
|
var list = new List<EnumItem>();
|
|
foreach (T v in Enum.GetValues(typeof(T)))
|
|
list.Add(new EnumItem { Name = v.ToString(), ChineseName = translate(v), Value = Convert.ToInt32(v) });
|
|
return list;
|
|
}
|
|
|
|
private static Dictionary<string, int> EnumToDict<T>() where T : Enum
|
|
{
|
|
var dict = new Dictionary<string, int>();
|
|
foreach (T v in Enum.GetValues(typeof(T)))
|
|
dict[v.ToString()] = Convert.ToInt32(v);
|
|
return dict;
|
|
}
|
|
|
|
// ═══ 中文翻译 ═══
|
|
private static string TranslateDrone(Models.DroneType t) => t switch
|
|
{
|
|
Models.DroneType.Rotor => "旋翼",
|
|
Models.DroneType.FixedWing => "固定翼",
|
|
Models.DroneType.HighSpeed => "高速目标",
|
|
_ => t.ToString(),
|
|
};
|
|
private static string TranslatePower(Models.PowerType p) => p switch
|
|
{
|
|
Models.PowerType.Electric => "电推",
|
|
Models.PowerType.Piston => "活塞",
|
|
Models.PowerType.Jet => "喷气",
|
|
_ => p.ToString(),
|
|
};
|
|
private static string TranslatePlatform(Models.PlatformType p) => p switch
|
|
{
|
|
Models.PlatformType.AirBased => "空基",
|
|
Models.PlatformType.GroundBased => "地基",
|
|
_ => p.ToString(),
|
|
};
|
|
private static string TranslateAerosol(Models.AerosolType t) => t switch
|
|
{
|
|
Models.AerosolType.InertGas => "惰性气体(吸入式灭火)",
|
|
Models.AerosolType.ActiveMaterial => "活性材料(爆燃式)",
|
|
Models.AerosolType.ActiveFuel => "活性燃料(吸入式爆炸)",
|
|
_ => t.ToString(),
|
|
};
|
|
private static string TranslateWeather(Models.WeatherType w) => w switch
|
|
{
|
|
Models.WeatherType.Sunny => "晴天",
|
|
Models.WeatherType.Overcast => "阴天",
|
|
Models.WeatherType.Fog => "雾天",
|
|
Models.WeatherType.Rain => "雨天",
|
|
Models.WeatherType.Night => "夜间",
|
|
_ => w.ToString(),
|
|
};
|
|
private static string TranslateWindDirection(Models.WindDirection d) => d switch
|
|
{
|
|
Models.WindDirection.N => "北 (N)",
|
|
Models.WindDirection.NE => "东北 (NE)",
|
|
Models.WindDirection.E => "东 (E)",
|
|
Models.WindDirection.SE => "东南 (SE)",
|
|
Models.WindDirection.S => "南 (S)",
|
|
Models.WindDirection.SW => "西南 (SW)",
|
|
Models.WindDirection.W => "西 (W)",
|
|
Models.WindDirection.NW => "西北 (NW)",
|
|
_ => d.ToString(),
|
|
};
|
|
private static string TranslateScene(Models.SceneType s) => s switch
|
|
{
|
|
Models.SceneType.Urban => "城市",
|
|
Models.SceneType.Plain => "平原",
|
|
Models.SceneType.Mountain => "山区",
|
|
Models.SceneType.Coast => "海岸",
|
|
_ => s.ToString(),
|
|
};
|
|
private static string TranslateFormation(Models.FormationMode f) => f switch
|
|
{
|
|
Models.FormationMode.Single => "单机",
|
|
Models.FormationMode.Formation => "编队",
|
|
Models.FormationMode.Swarm => "蜂群",
|
|
_ => f.ToString(),
|
|
};
|
|
private static string TranslateTrigger(Models.TriggerMode t) => t switch
|
|
{
|
|
Models.TriggerMode.Time => "时间触发",
|
|
Models.TriggerMode.Area => "区域触发",
|
|
Models.TriggerMode.Manual => "手动触发",
|
|
_ => t.ToString(),
|
|
};
|
|
private static string TranslateRelease(Models.ReleaseMode r) => r switch
|
|
{
|
|
Models.ReleaseMode.Single => "单次",
|
|
Models.ReleaseMode.Continuous => "连续",
|
|
Models.ReleaseMode.Pulse => "脉冲",
|
|
_ => r.ToString(),
|
|
};
|
|
private static string TranslateEquipmentRole(Models.EquipmentRole r) => r switch
|
|
{
|
|
Models.EquipmentRole.Detection => "探测设备",
|
|
Models.EquipmentRole.LaunchPlatform => "发射平台",
|
|
_ => r.ToString(),
|
|
};
|
|
|
|
private static string TranslateSensorType(Models.SensorType t) => t switch
|
|
{
|
|
Models.SensorType.Radar => "雷达",
|
|
Models.SensorType.EO => "光电",
|
|
Models.SensorType.IR => "红外",
|
|
_ => t.ToString(),
|
|
};
|
|
|
|
private static string TranslateEntityType(Models.EntityType t) => t switch
|
|
{
|
|
Models.EntityType.Drone => "无人机",
|
|
Models.EntityType.Platform => "发射平台",
|
|
Models.EntityType.DetectionEquip => "探测设备",
|
|
Models.EntityType.Cloud => "云团",
|
|
Models.EntityType.Munition => "弹药",
|
|
_ => t.ToString(),
|
|
};
|
|
|
|
// ========== Scenario CRUD ==========
|
|
|
|
public Scenario UpdateScenario(Scenario scenario)
|
|
{
|
|
var existing = _scenarioRepo.GetById(scenario.Id);
|
|
if (existing == null)
|
|
throw new ArgumentException($"想定 {scenario.Id} 不存在");
|
|
scenario.UpdatedAt = DateTime.UtcNow.ToString("o");
|
|
_scenarioRepo.Update(scenario);
|
|
return scenario;
|
|
}
|
|
|
|
// ========== CombatScene ==========
|
|
|
|
public CombatScene GetScene(string scenarioId)
|
|
{
|
|
return _sceneRepo.GetById(scenarioId) ?? new CombatScene { ScenarioId = scenarioId };
|
|
}
|
|
|
|
// ========== ControlZone ==========
|
|
|
|
public List<ControlZone> GetControlZones(string scenarioId)
|
|
=> _zoneRepo.GetByScenarioId(scenarioId);
|
|
|
|
public void DeleteControlZone(string id)
|
|
=> _zoneRepo.Delete(id);
|
|
|
|
// ========== ScenarioDrone ==========
|
|
|
|
public ScenarioDrone AddScenarioDrone(string scenarioId, ScenarioDrone drone)
|
|
=> SaveScenarioDrone(scenarioId, drone);
|
|
|
|
public void UpdateScenarioDrone(ScenarioDrone drone)
|
|
{
|
|
_droneRepo.Update(drone);
|
|
TouchScenario(drone.ScenarioId);
|
|
}
|
|
|
|
public void DeleteScenarioDrone(string id)
|
|
=> _droneRepo.Delete(id);
|
|
|
|
public List<ScenarioDrone> GetScenarioDrones(string scenarioId)
|
|
=> _droneRepo.GetByScenarioId(scenarioId);
|
|
|
|
// ========== ScenarioUnit ==========
|
|
|
|
public ScenarioUnit AddDeploymentUnit(string scenarioId, ScenarioUnit unit)
|
|
{
|
|
unit.ScenarioId = scenarioId;
|
|
if (string.IsNullOrEmpty(unit.Id))
|
|
unit.Id = Guid.NewGuid().ToString();
|
|
_unitRepo.Insert(unit);
|
|
TouchScenario(scenarioId);
|
|
return unit;
|
|
}
|
|
|
|
public void UpdateDeploymentUnit(ScenarioUnit unit)
|
|
=> _unitRepo.Update(unit);
|
|
|
|
public void DeleteDeploymentUnit(string id)
|
|
=> _unitRepo.Delete(id);
|
|
|
|
public List<ScenarioUnit> GetDeploymentUnits(string scenarioId)
|
|
=> _unitRepo.GetByScenarioId(scenarioId);
|
|
|
|
// ========== CloudDispersal ==========
|
|
|
|
public CloudDispersal GetCloudDispersal(string scenarioId)
|
|
=> _cloudRepo.GetById(scenarioId) ?? new CloudDispersal { ScenarioId = scenarioId };
|
|
|
|
public void DeleteCloudDispersal(string scenarioId)
|
|
=> _cloudRepo.Delete(scenarioId);
|
|
|
|
// ========== Route & Waypoint ==========
|
|
|
|
public List<RoutePlan> GetRoutes(string scenarioId)
|
|
=> _routeRepo.GetByScenarioId(scenarioId);
|
|
|
|
public void DeleteRoute(string id)
|
|
{
|
|
var r = _routeRepo.GetById(id);
|
|
if (r == null) return;
|
|
var wps = _waypointRepo.GetByScenarioAndWave(r.ScenarioId, r.WaveId);
|
|
foreach (var w in wps) _waypointRepo.Delete(w.Id);
|
|
_routeRepo.Delete(id);
|
|
}
|
|
|
|
public List<Waypoint> GetWaypoints(string scenarioId)
|
|
=> _waypointRepo.GetByScenarioId(scenarioId);
|
|
|
|
public List<Waypoint> GetWaypointsByWave(string scenarioId, string waveId)
|
|
=> _waypointRepo.GetByScenarioAndWave(scenarioId, waveId);
|
|
|
|
public void DeleteWaypoint(string id)
|
|
=> _waypointRepo.Delete(id);
|
|
|
|
// ========== Helpers ==========
|
|
|
|
private void TouchScenario(string scenarioId)
|
|
{
|
|
var scenario = _scenarioRepo.GetById(scenarioId);
|
|
if (scenario != null)
|
|
{
|
|
scenario.UpdatedAt = DateTime.UtcNow.ToString("o");
|
|
_scenarioRepo.Update(scenario);
|
|
}
|
|
}
|
|
|
|
private static int _counter;
|
|
|
|
public static string GenerateScenarioNumber()
|
|
{
|
|
var now = DateTime.Now;
|
|
var datePart = now.ToString("yyyyMMdd");
|
|
var seq = Interlocked.Increment(ref _counter);
|
|
return $"SIM-{datePart}-{seq:D3}";
|
|
}
|
|
}
|
|
}
|