feat: 基础数据 CRUD — 统一命名,建表入库,DataService 提供全 CRUD

类名规范化:
- FireUnitTemplate → FireUnitSpec(火力单元规格)
- TargetPreset → DroneSpec(无人机规格)
- DetectionPreset → SensorSpec(传感器规格)
- WeatherPreset → EnvironmentSpec(环境规格)
- RoutePreset → RouteTemplate(航线模板)

DefaultData 属性重命名:
- Targets → Drones, DetectionEquipment → Sensors, Weather → Environments
- JSON 兼容: JsonPropertyName 保持原键名

新增: IDataService + DataService — 7 类基础数据全 CRUD
新增: 6 个 Repository(SpecRepositories.cs)
新增: DatabaseManager 自动建表 + 种子数据(RouteTemplate.Waypoints JSON 序列化存储)

测试: 243 通过
This commit is contained in:
tian 2026-06-17 12:36:49 +08:00
parent e250093a16
commit 120f2805ec
7 changed files with 229 additions and 35 deletions

View File

@ -62,6 +62,16 @@ namespace CounterDrone.Core
// 插入或更新默认数据(按 Id 覆盖,不删除用户自建数据)
foreach (var a in defaults.Ammunition) db.InsertOrReplace(a);
foreach (var f in defaults.FireUnits) db.InsertOrReplace(f);
foreach (var d in defaults.Drones) db.InsertOrReplace(d);
foreach (var s in defaults.Sensors) db.InsertOrReplace(s);
foreach (var e in defaults.Environments) db.InsertOrReplace(e);
foreach (var f in defaults.Formations) db.InsertOrReplace(f);
foreach (var r in defaults.Routes)
{
r.WaypointsJson = System.Text.Json.JsonSerializer.Serialize(r.Waypoints);
db.InsertOrReplace(r);
}
db.InsertOrReplace(new MetaEntry { Key = "defaultsVersion", Value = defaults.Version });
}
@ -100,6 +110,12 @@ namespace CounterDrone.Core
db.CreateIndex("RoutePlan", new[] { "TaskId", "WaveId" }, true);
db.CreateTable<Waypoint>();
db.CreateTable<SimulationReport>();
db.CreateTable<FireUnitSpec>();
db.CreateTable<DroneSpec>();
db.CreateTable<SensorSpec>();
db.CreateTable<EnvironmentSpec>();
db.CreateTable<FormationTemplate>();
db.CreateTable<RouteTemplate>();
db.CreateIndex("SimTask", "TaskNumber");
db.CreateIndex("ControlZone", "TaskId");

View File

@ -16,11 +16,15 @@ namespace CounterDrone.Core
public string Version { get; set; } = "0";
public List<AmmunitionSpec> Ammunition { get; set; } = new();
public List<FormationTemplate> Formations { get; set; } = new();
public List<RoutePreset> Routes { get; set; } = new();
public List<FireUnitTemplate> FireUnits { get; set; } = new();
public List<TargetPreset> Targets { get; set; } = new();
public List<DetectionPreset> DetectionEquipment { get; set; } = new();
public List<WeatherPreset> Weather { get; set; } = new();
public List<RouteTemplate> Routes { get; set; } = new();
[System.Text.Json.Serialization.JsonPropertyName("fireUnits")]
public List<FireUnitSpec> FireUnits { get; set; } = new();
[System.Text.Json.Serialization.JsonPropertyName("targets")]
public List<DroneSpec> Drones { get; set; } = new();
[System.Text.Json.Serialization.JsonPropertyName("detectionEquipment")]
public List<SensorSpec> Sensors { get; set; } = new();
[System.Text.Json.Serialization.JsonPropertyName("weather")]
public List<EnvironmentSpec> Environments { get; set; } = new();
/// <summary>从 data/defaults.json 加载。文件缺失或解析失败即抛异常。</summary>
public static DefaultData Load(IPathProvider paths)
@ -57,12 +61,12 @@ namespace CounterDrone.Core
throw new InvalidOperationException($"默认数据缺少 routes: {source}");
if (data.FireUnits == null || data.FireUnits.Count == 0)
throw new InvalidOperationException($"默认数据缺少 fireUnits: {source}");
if (data.Targets == null || data.Targets.Count == 0)
throw new InvalidOperationException($"默认数据缺少 targets: {source}");
if (data.Weather == null || data.Weather.Count == 0)
throw new InvalidOperationException($"默认数据缺少 weather: {source}");
if (data.DetectionEquipment == null)
data.DetectionEquipment = new();
if (data.Drones == null || data.Drones.Count == 0)
throw new InvalidOperationException($"默认数据缺少 drones: {source}");
if (data.Environments == null || data.Environments.Count == 0)
throw new InvalidOperationException($"默认数据缺少 environments: {source}");
if (data.Sensors == null)
data.Sensors = new();
}
}
@ -90,9 +94,11 @@ namespace CounterDrone.Core
}
}
/// <summary>火力单元模板</summary>
public class FireUnitTemplate
/// <summary>火力单元规格(基础数据模板)</summary>
[SQLite.Table("FireUnitSpec")]
public class FireUnitSpec
{
[SQLite.PrimaryKey]
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public int PlatformType { get; set; }
@ -104,6 +110,7 @@ namespace CounterDrone.Core
public double MuzzleVelocity { get; set; }
public double CruiseSpeed { get; set; }
public double ReleaseAltitude { get; set; }
[SQLite.Ignore]
public List<int> AmmoTypes { get; set; } = new();
public double RadarRange { get; set; }
public double EORange { get; set; }
@ -145,9 +152,11 @@ namespace CounterDrone.Core
}
}
/// <summary>无人机/威胁目标预设</summary>
public class TargetPreset
/// <summary>无人机规格(基础数据模板)</summary>
[SQLite.Table("DroneSpec")]
public class DroneSpec
{
[SQLite.PrimaryKey]
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public int TargetType { get; set; }
@ -171,9 +180,11 @@ namespace CounterDrone.Core
}
}
/// <summary>独立探测设备预设</summary>
public class DetectionPreset
/// <summary>传感器规格(基础数据模板)</summary>
[SQLite.Table("SensorSpec")]
public class SensorSpec
{
[SQLite.PrimaryKey]
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public double RadarRange { get; set; }
@ -207,9 +218,11 @@ namespace CounterDrone.Core
}
}
/// <summary>天气/场景预设</summary>
public class WeatherPreset
/// <summary>环境规格(基础数据模板)</summary>
[SQLite.Table("EnvironmentSpec")]
public class EnvironmentSpec
{
[SQLite.PrimaryKey]
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public int WeatherType { get; set; }
@ -235,11 +248,16 @@ namespace CounterDrone.Core
}
}
/// <summary>航线预设 — 航路点坐标集合,速度由目标 TypicalSpeed 提供</summary>
public class RoutePreset
/// <summary>航线模板(基础数据模板)</summary>
[SQLite.Table("RouteTemplate")]
public class RouteTemplate
{
[SQLite.PrimaryKey]
public string Id { get; set; } = "";
public string Name { get; set; } = "";
/// <summary>航路点 JSONWaypointCoord 序列化SQLite 存储用</summary>
public string WaypointsJson { get; set; } = "[]";
[SQLite.Ignore]
public List<WaypointCoord> Waypoints { get; set; } = new();
public List<Waypoint> ToWaypoints(double speed)

View File

@ -36,8 +36,8 @@ namespace CounterDrone.Core
private static void SeedNoDefense(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 无防御-无人机抵达目标", "");
s.SaveScene(t.Id, d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Targets.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h400", 600, d));
s.SaveCloudDispersal(t.Id, new CloudDispersal());
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
@ -47,8 +47,8 @@ namespace CounterDrone.Core
private static void SeedZoneIntrusion(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 管控区域侵入", "");
s.SaveScene(t.Id, d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Targets.First(p => p.Id == "electric-scout").ToTargetConfig());
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "electric-scout").ToTargetConfig());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h300", 300, d));
s.SaveCloudDispersal(t.Id, new CloudDispersal());
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
@ -67,11 +67,11 @@ namespace CounterDrone.Core
private static void SeedPistonWindy(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 活塞拦截-西风5ms", "");
var scene = d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene();
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.W;
s.SaveScene(t.Id, scene);
s.SaveTarget(t.Id, d.Targets.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
@ -88,8 +88,8 @@ namespace CounterDrone.Core
private static void SeedJetActiveMaterial(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 喷气式拦截-活性材料", "");
s.SaveScene(t.Id, d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Targets.First(p => p.Id == "cruise-missile").ToTargetConfig());
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "cruise-missile").ToTargetConfig());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
@ -106,11 +106,11 @@ namespace CounterDrone.Core
private static void SeedAirBasedWindy(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 空基拦截-东风5ms", "");
var scene = d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene();
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;
s.SaveScene(t.Id, scene);
s.SaveTarget(t.Id, d.Targets.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("20km-h500", 200, d));
s.SaveDeployment(t.Id, new List<EquipmentDeployment>
{
@ -123,11 +123,11 @@ namespace CounterDrone.Core
private static void Seed3DronesAirBased(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 3架空基编队拦截", "");
var scene = d.Weather.First(w => w.Id == "sunny-calm").ToCombatScene();
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;
s.SaveScene(t.Id, scene);
var target = d.Targets.First(p => p.Id == "shahed").ToTargetConfig();
var target = d.Drones.First(p => p.Id == "shahed").ToTargetConfig();
target.Quantity = 3;
s.SaveTarget(t.Id, target);
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "line-3").ToRoutePlan(), R("20km-h500", 200, d));

View File

@ -0,0 +1,35 @@
using CounterDrone.Core;
using SQLite;
namespace CounterDrone.Core.Repository
{
public class FireUnitSpecRepository : BaseRepository<FireUnitSpec>
{
public FireUnitSpecRepository(SQLiteConnection db) : base(db) { }
}
public class DroneSpecRepository : BaseRepository<DroneSpec>
{
public DroneSpecRepository(SQLiteConnection db) : base(db) { }
}
public class SensorSpecRepository : BaseRepository<SensorSpec>
{
public SensorSpecRepository(SQLiteConnection db) : base(db) { }
}
public class EnvironmentSpecRepository : BaseRepository<EnvironmentSpec>
{
public EnvironmentSpecRepository(SQLiteConnection db) : base(db) { }
}
public class FormationTemplateRepository : BaseRepository<FormationTemplate>
{
public FormationTemplateRepository(SQLiteConnection db) : base(db) { }
}
public class RouteTemplateRepository : BaseRepository<RouteTemplate>
{
public RouteTemplateRepository(SQLiteConnection db) : base(db) { }
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using CounterDrone.Core.Models;
using CounterDrone.Core.Repository;
namespace CounterDrone.Core.Services
{
public class DataService : IDataService
{
private readonly AmmunitionSpecRepository _ammoRepo;
private readonly FireUnitSpecRepository _fireUnitRepo;
private readonly DroneSpecRepository _droneRepo;
private readonly SensorSpecRepository _sensorRepo;
private readonly EnvironmentSpecRepository _envRepo;
private readonly FormationTemplateRepository _formationRepo;
private readonly RouteTemplateRepository _routeRepo;
public DataService(AmmunitionSpecRepository ammoRepo, FireUnitSpecRepository fireUnitRepo,
DroneSpecRepository droneRepo, SensorSpecRepository sensorRepo,
EnvironmentSpecRepository envRepo, FormationTemplateRepository formationRepo,
RouteTemplateRepository routeRepo)
{
_ammoRepo = ammoRepo;
_fireUnitRepo = fireUnitRepo;
_droneRepo = droneRepo;
_sensorRepo = sensorRepo;
_envRepo = envRepo;
_formationRepo = formationRepo;
_routeRepo = routeRepo;
}
// ═══ AmmunitionSpec ═══
public List<AmmunitionSpec> GetAllAmmo() => _ammoRepo.GetAll();
public AmmunitionSpec GetAmmo(string id) => _ammoRepo.GetById(id);
public void SaveAmmo(AmmunitionSpec spec) { if (_ammoRepo.GetById(spec.Id) != null) _ammoRepo.Update(spec); else _ammoRepo.Insert(spec); }
public void DeleteAmmo(string id) => _ammoRepo.Delete(id);
// ═══ FireUnitSpec ═══
public List<FireUnitSpec> GetAllFireUnits() => _fireUnitRepo.GetAll();
public FireUnitSpec GetFireUnit(string id) => _fireUnitRepo.GetById(id);
public void SaveFireUnit(FireUnitSpec spec) { if (_fireUnitRepo.GetById(spec.Id) != null) _fireUnitRepo.Update(spec); else _fireUnitRepo.Insert(spec); }
public void DeleteFireUnit(string id) => _fireUnitRepo.Delete(id);
// ═══ DroneSpec ═══
public List<DroneSpec> GetAllDrones() => _droneRepo.GetAll();
public DroneSpec GetDrone(string id) => _droneRepo.GetById(id);
public void SaveDrone(DroneSpec spec) { if (_droneRepo.GetById(spec.Id) != null) _droneRepo.Update(spec); else _droneRepo.Insert(spec); }
public void DeleteDrone(string id) => _droneRepo.Delete(id);
// ═══ SensorSpec ═══
public List<SensorSpec> GetAllSensors() => _sensorRepo.GetAll();
public SensorSpec GetSensor(string id) => _sensorRepo.GetById(id);
public void SaveSensor(SensorSpec spec) { if (_sensorRepo.GetById(spec.Id) != null) _sensorRepo.Update(spec); else _sensorRepo.Insert(spec); }
public void DeleteSensor(string id) => _sensorRepo.Delete(id);
// ═══ EnvironmentSpec ═══
public List<EnvironmentSpec> GetAllEnvironments() => _envRepo.GetAll();
public EnvironmentSpec GetEnvironment(string id) => _envRepo.GetById(id);
public void SaveEnvironment(EnvironmentSpec spec) { if (_envRepo.GetById(spec.Id) != null) _envRepo.Update(spec); else _envRepo.Insert(spec); }
public void DeleteEnvironment(string id) => _envRepo.Delete(id);
// ═══ FormationTemplate ═══
public List<FormationTemplate> GetAllFormations() => _formationRepo.GetAll();
public FormationTemplate GetFormation(string id) => _formationRepo.GetById(id);
public void SaveFormation(FormationTemplate spec) { if (_formationRepo.GetById(spec.Id) != null) _formationRepo.Update(spec); else _formationRepo.Insert(spec); }
public void DeleteFormation(string id) => _formationRepo.Delete(id);
// ═══ RouteTemplate ═══
public List<RouteTemplate> GetAllRoutes() => _routeRepo.GetAll();
public RouteTemplate GetRoute(string id) => _routeRepo.GetById(id);
public void SaveRoute(RouteTemplate spec) { if (_routeRepo.GetById(spec.Id) != null) _routeRepo.Update(spec); else _routeRepo.Insert(spec); }
public void DeleteRoute(string id) => _routeRepo.Delete(id);
}
}

View File

@ -0,0 +1,51 @@
using System.Collections.Generic;
using CounterDrone.Core.Models;
namespace CounterDrone.Core.Services
{
/// <summary>基础数据管理服务 — 弹药/火力单元/无人机/传感器/环境/编队/航线 全 CRUD</summary>
public interface IDataService
{
// ═══ AmmunitionSpec ═══
List<AmmunitionSpec> GetAllAmmo();
AmmunitionSpec GetAmmo(string id);
void SaveAmmo(AmmunitionSpec spec);
void DeleteAmmo(string id);
// ═══ FireUnitSpec ═══
List<FireUnitSpec> GetAllFireUnits();
FireUnitSpec GetFireUnit(string id);
void SaveFireUnit(FireUnitSpec spec);
void DeleteFireUnit(string id);
// ═══ DroneSpec ═══
List<DroneSpec> GetAllDrones();
DroneSpec GetDrone(string id);
void SaveDrone(DroneSpec spec);
void DeleteDrone(string id);
// ═══ SensorSpec ═══
List<SensorSpec> GetAllSensors();
SensorSpec GetSensor(string id);
void SaveSensor(SensorSpec spec);
void DeleteSensor(string id);
// ═══ EnvironmentSpec ═══
List<EnvironmentSpec> GetAllEnvironments();
EnvironmentSpec GetEnvironment(string id);
void SaveEnvironment(EnvironmentSpec spec);
void DeleteEnvironment(string id);
// ═══ FormationTemplate ═══
List<FormationTemplate> GetAllFormations();
FormationTemplate GetFormation(string id);
void SaveFormation(FormationTemplate spec);
void DeleteFormation(string id);
// ═══ RouteTemplate ═══
List<RouteTemplate> GetAllRoutes();
RouteTemplate GetRoute(string id);
void SaveRoute(RouteTemplate spec);
void DeleteRoute(string id);
}
}

View File

@ -63,8 +63,8 @@ namespace CounterDrone.Unity
var task = scenario.CreateTask("Unity Demo", "");
taskId = task.Id;
scenario.SaveScene(taskId, defaults.Weather.First(w => w.Id == "sunny-calm").ToCombatScene());
scenario.SaveTarget(taskId, defaults.Targets.First(p => p.Id == "shahed").ToTargetConfig());
scenario.SaveScene(taskId, defaults.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
scenario.SaveTarget(taskId, defaults.Drones.First(p => p.Id == "shahed").ToTargetConfig());
scenario.SaveRoute(taskId, "default",
defaults.Formations.First(f => f.Id == "single").ToRoutePlan(),
defaults.Routes.First(r => _routeLength > 6000 ? r.Id == "10km-h500" : r.Id == "5km-h500")