Phase 1: 核心基础 — 项目骨架、数据模型、Repository、ModelService/GroupService,27个单元测试全部通过
This commit is contained in:
commit
b23291c5bf
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
# === 操作系统生成文件 ===
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# === IDE & 编辑器 ===
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# === .NET 构建产物 ===
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
*.suo
|
||||
*.DotSettings.user
|
||||
|
||||
# === 依赖 ===
|
||||
node_modules/
|
||||
vendor/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
|
||||
# === 日志 ===
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# === 临时文件 ===
|
||||
*.tmp
|
||||
*.temp
|
||||
tmp/
|
||||
|
||||
# === 测试 ===
|
||||
coverage/
|
||||
.nyc_output/
|
||||
*.coverage
|
||||
TestResults/
|
||||
|
||||
# === 环境变量(敏感信息) ===
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# === 缓存 ===
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
71
AGENTS.md
Normal file
71
AGENTS.md
Normal file
@ -0,0 +1,71 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## 1. Think Before Coding
|
||||
|
||||
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||
|
||||
Before implementing:
|
||||
- State your assumptions explicitly. If uncertain, ask.
|
||||
- If multiple interpretations exist, present them - don't pick silently.
|
||||
- If a simpler approach exists, say so. Push back when warranted.
|
||||
- If something is unclear, stop. Name what's confusing. Ask.
|
||||
|
||||
## 2. Simplicity First
|
||||
|
||||
**Minimum code that solves the problem. Nothing speculative.**
|
||||
|
||||
- No features beyond what was asked.
|
||||
- No abstractions for single-use code.
|
||||
- No "flexibility" or "configurability" that wasn't requested.
|
||||
- No error handling for impossible scenarios.
|
||||
- If you write 200 lines and it could be 50, rewrite it.
|
||||
|
||||
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||
|
||||
## 3. Surgical Changes
|
||||
|
||||
**Touch only what you must. Clean up only your own mess.**
|
||||
|
||||
When editing existing code:
|
||||
- Don't "improve" adjacent code, comments, or formatting.
|
||||
- Don't refactor things that aren't broken.
|
||||
- Match existing style, even if you'd do it differently.
|
||||
- If you notice unrelated dead code, mention it - don't delete it.
|
||||
|
||||
When your changes create orphans:
|
||||
- Remove imports/variables/functions that YOUR changes made unused.
|
||||
- Don't remove pre-existing dead code unless asked.
|
||||
|
||||
The test: Every changed line should trace directly to the user's request.
|
||||
|
||||
## 4. Goal-Driven Execution
|
||||
|
||||
**Define success criteria. Loop until verified.**
|
||||
|
||||
Transform tasks into verifiable goals:
|
||||
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||
- "Refactor X" → "Ensure tests pass before and after"
|
||||
|
||||
For multi-step tasks, state a brief plan:
|
||||
```
|
||||
1. [Step] → verify: [check]
|
||||
2. [Step] → verify: [check]
|
||||
3. [Step] → verify: [check]
|
||||
```
|
||||
|
||||
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||
|
||||
---
|
||||
|
||||
## 5. Use PowerShell for Shell Commands
|
||||
|
||||
**All shell commands, scripts, rg/fd calls run through pwsh, never cmd or bash.**
|
||||
|
||||
- Prefix every shell invocation with `pwsh -Command "..."`
|
||||
- Example: `pwsh -Command "rg -n 'class' src/"`
|
||||
- Example: `pwsh -Command "./build.bat"`
|
||||
|
||||
---
|
||||
|
||||
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||
34
CHANGELOG.md
Normal file
34
CHANGELOG.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
本文件记录项目对外发布的变更历史(用户视角)。
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
||||
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
格式说明:
|
||||
- `Added` — 新增功能
|
||||
- `Changed` — 现有功能的变更
|
||||
- `Deprecated` — 即将移除的功能
|
||||
- `Removed` — 已移除的功能
|
||||
- `Fixed` — 问题修复
|
||||
- `Security` — 安全修复
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- (待发布的新功能)
|
||||
|
||||
### Changed
|
||||
- (待发布的变更)
|
||||
|
||||
---
|
||||
|
||||
## [0.1.0] - YYYY-MM-DD
|
||||
|
||||
### Added
|
||||
- 项目初始化。
|
||||
|
||||
---
|
||||
|
||||
> 更多格式说明见:https://keepachangelog.com/zh-CN/1.1.0/
|
||||
51
CONTRIBUTING.md
Normal file
51
CONTRIBUTING.md
Normal file
@ -0,0 +1,51 @@
|
||||
# 贡献指南
|
||||
|
||||
感谢你对本项目的关注!请遵循以下规范参与开发。
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. **认领任务** — 从 `docs/implementation/tasks/` 中选取或创建任务
|
||||
2. **创建分支** — 分支命名:`feature/<描述>` / `fix/<描述>` / `docs/<描述>`
|
||||
3. **编写代码** — 遵循项目代码规范,添加必要的测试
|
||||
4. **自测** — 确保测试通过,功能正常
|
||||
5. **提交代码** — Commit message 遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/)
|
||||
6. **发起 Merge Request / Pull Request** — 描述变更内容,关联对应的需求/问题
|
||||
7. **代码审查** — 至少一人 Review 通过后合并
|
||||
|
||||
## Commit Message 规范
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
类型(type):
|
||||
feat — 新功能
|
||||
fix — 修复
|
||||
docs — 文档
|
||||
style — 格式(不影响功能)
|
||||
refactor — 重构
|
||||
test — 测试
|
||||
chore — 构建/工具
|
||||
|
||||
示例:
|
||||
feat(auth): add login by SMS
|
||||
fix(api): correct user list pagination
|
||||
```
|
||||
|
||||
## 代码规范
|
||||
|
||||
- 保持代码风格与项目一致
|
||||
- 公共接口必须有注释
|
||||
- 新增功能必须编写测试
|
||||
- 修改现有功能需同步更新相关文档
|
||||
|
||||
## 测试要求
|
||||
|
||||
- 单元测试覆盖率目标:> [XX]%
|
||||
- 提交前确保所有测试通过
|
||||
- 涉及接口变更需补充集成测试
|
||||
|
||||
## 文档要求
|
||||
|
||||
- 功能设计文档放在 `docs/features/`
|
||||
- 架构变更需在 `docs/design/architecture/` 添加 ADR(架构决策记录)
|
||||
- 需求变更在 `docs/requirements/changes/` 记录
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [2026] [北京泰奥理科技]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# [项目名称]
|
||||
|
||||
> 一句话描述项目:是什么,解决什么问题,面向谁。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
├── README.md # 项目概述与快速开始(本文件)
|
||||
├── CHANGELOG.md # 对外发布变更记录(用户视角)
|
||||
├── VERSION # 语义化版本号
|
||||
├── LICENSE # 许可证
|
||||
├── CONTRIBUTING.md # 贡献规范
|
||||
├── .gitignore # Git 忽略规则
|
||||
├── AGENTS.md # AI 编码助手上下文
|
||||
│
|
||||
├── src/ # 源代码(所有业务模块)
|
||||
├── data/ # 项目数据(知识库源、SQL dump 等非代码数据)
|
||||
├── test/ # 测试代码
|
||||
│ ├── unit/ # 单元测试
|
||||
│ ├── integration/ # 集成测试
|
||||
│ └── e2e/ # 端到端测试
|
||||
│
|
||||
├── docs/ # 开发文档
|
||||
│ ├── design/ # 设计方案(架构 + 技术方案)
|
||||
│ │ ├── architecture/ # 架构设计 & ADR
|
||||
│ │ └── technical/ # 技术方案 / 详细设计
|
||||
│ ├── features/ # 功能规格说明
|
||||
│ ├── requirements/ # 需求与变更管理(可选)
|
||||
│ ├── implementation/ # 实施计划与任务跟踪(可选)
|
||||
│ └── verify/ # 人工检查清单(可选)
|
||||
│
|
||||
├── skills/ # AI 辅助技能(pi 专用,可选)
|
||||
├── scripts/ # 构建、部署、工具脚本
|
||||
└── config/ # 项目级配置文件(非代码)
|
||||
```
|
||||
|
||||
> - **可选目录**:已有 Jira/TAPD 等工单系统的团队可删除 `requirements/` 和 `implementation/`;不用 pi 编码助手的可删除 `skills/`
|
||||
> - **`src/` vs 根目录**:业务代码放 `src/`,入口文件(`app.py`、`main.go`)和配置文件(`pytest.ini`、`ruff.toml`、`.env.example`)留根目录
|
||||
> - **`data/` vs `test/fixtures/`**:`data/` 放项目级数据(知识库源文件等),`test/fixtures/` 放测试专用 fixture 数据
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone <repo-url>
|
||||
cd <project-name>
|
||||
|
||||
# [安装 / 构建 / 运行步骤,根据项目补充]
|
||||
```
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 你想做的事 | 去哪看 |
|
||||
|------------|--------|
|
||||
| 了解项目背景和架构 | `docs/design/architecture/` |
|
||||
| 查看需求与变更 | `docs/requirements/`(可选) |
|
||||
| 查看功能设计 | `docs/features/` |
|
||||
| 查看任务进度 | `docs/implementation/tasks/`(可选) |
|
||||
| 参与开发 | `CONTRIBUTING.md` |
|
||||
| 查看版本历史 | `CHANGELOG.md` |
|
||||
|
||||
## 维护者
|
||||
|
||||
- [@维护者](https://github.com/xxx)
|
||||
|
||||
## 许可证
|
||||
|
||||
[LICENSE](./LICENSE)
|
||||
17
data/index.md
Normal file
17
data/index.md
Normal file
@ -0,0 +1,17 @@
|
||||
# data — 项目数据
|
||||
|
||||
本目录存放非代码的项目数据文件,区别于源代码和文档。
|
||||
|
||||
## 用途
|
||||
|
||||
- 初始化/种子数据
|
||||
- 数据库导出快照(用于参考或迁移)
|
||||
- 静态数据文件(如地理信息、字典数据等)
|
||||
- 其他非代码的静态项目数据
|
||||
|
||||
## 原则
|
||||
|
||||
1. 不含可执行代码或业务配置文件
|
||||
2. 文件应可被版本控制追踪(大文件请使用 Git LFS)
|
||||
3. 按数据类型或用途建立子目录
|
||||
4. 与 `test/fixtures/` 区分:`data/` 放项目级数据,`test/fixtures/` 放测试专用数据
|
||||
53
docs/design/architecture/index.md
Normal file
53
docs/design/architecture/index.md
Normal file
@ -0,0 +1,53 @@
|
||||
# docs/design/architecture — 架构设计
|
||||
|
||||
存放系统架构文档和架构决策记录(ADR)。
|
||||
|
||||
## 文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [总体架构设计](./总体架构设计.md) | 系统整体架构、分层设计、数据模型、仿真引擎、接口设计 |
|
||||
|
||||
## 架构决策记录(ADR)
|
||||
|
||||
当需要做出影响系统结构的重大技术决策时,请创建一条 ADR。
|
||||
|
||||
### 文件命名
|
||||
|
||||
```
|
||||
ADR-<序号>-<标题>.md
|
||||
```
|
||||
|
||||
### ADR 模板
|
||||
|
||||
```markdown
|
||||
# ADR-001: [决策标题]
|
||||
|
||||
- **状态**:提议 / 已接受 / 已废弃 / 已替代
|
||||
- **日期**:YYYY-MM-DD
|
||||
- **决策者**:[姓名]
|
||||
- **替代**:[如替代了之前的 ADR]
|
||||
|
||||
## 背景
|
||||
|
||||
> 为什么需要做这个决策?面临什么问题?
|
||||
|
||||
## 决策
|
||||
|
||||
> 我们决定做什么?
|
||||
|
||||
## 备选方案
|
||||
|
||||
| 方案 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| 方案 A | ... | ... |
|
||||
| 方案 B | ... | ... |
|
||||
|
||||
## 后果
|
||||
|
||||
> 决策后带来的影响(好的和需要关注的)。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 关于 ADR 的更多说明,见 [adr.github.io](https://adr.github.io/)
|
||||
1384
docs/design/architecture/总体架构设计.md
Normal file
1384
docs/design/architecture/总体架构设计.md
Normal file
File diff suppressed because it is too large
Load Diff
26
docs/design/index.md
Normal file
26
docs/design/index.md
Normal file
@ -0,0 +1,26 @@
|
||||
# docs/design — 设计方案
|
||||
|
||||
本目录存放架构设计和技术方案文档。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
design/
|
||||
├── architecture/ # 架构设计 & 架构决策记录(ADR)
|
||||
└── technical/ # 技术方案 / 详细设计
|
||||
```
|
||||
|
||||
## architecture/ — 架构设计
|
||||
|
||||
- **总体架构文档**:系统架构图、模块划分、技术选型、核心交互流程
|
||||
- **ADR(Architecture Decision Record)**:记录重大架构决策及其背景
|
||||
|
||||
## technical/ — 技术方案
|
||||
|
||||
- 各模块的详细技术设计
|
||||
- 接口设计、数据模型设计
|
||||
- 关键技术难点的攻关方案
|
||||
|
||||
---
|
||||
|
||||
> **提示**:架构图和流程图推荐使用 Mermaid(Markdown 原生支持)或 PlantUML,方便版本控制和 AI 工具解析。
|
||||
51
docs/design/technical/index.md
Normal file
51
docs/design/technical/index.md
Normal file
@ -0,0 +1,51 @@
|
||||
# docs/design/technical — 技术方案
|
||||
|
||||
本目录存放各模块的详细技术设计方案。
|
||||
|
||||
## 文件命名
|
||||
|
||||
```
|
||||
<模块名>-技术方案.md
|
||||
```
|
||||
|
||||
## 技术方案模板
|
||||
|
||||
```markdown
|
||||
# [模块名] 技术方案
|
||||
|
||||
- **版本**:v[版本号]
|
||||
- **作者**:[姓名]
|
||||
- **日期**:YYYY-MM-DD
|
||||
- **状态**:草稿 / 评审中 / 已定稿
|
||||
|
||||
## 1. 概述
|
||||
|
||||
> 本方案解决什么问题?适用范围是什么?
|
||||
|
||||
## 2. 设计目标
|
||||
|
||||
- 功能目标
|
||||
- 非功能目标(性能、安全、可扩展性等)
|
||||
|
||||
## 3. 方案描述
|
||||
|
||||
### 3.1 整体流程
|
||||
|
||||
<!-- 推荐使用 Mermaid 绘制流程图 -->
|
||||
|
||||
### 3.2 接口设计
|
||||
|
||||
### 3.3 数据模型
|
||||
|
||||
### 3.4 关键逻辑
|
||||
|
||||
## 4. 风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对措施 |
|
||||
|------|------|----------|
|
||||
| ... | ... | ... |
|
||||
|
||||
## 5. 参考
|
||||
|
||||
- [相关文档链接]
|
||||
```
|
||||
56
docs/features/index.md
Normal file
56
docs/features/index.md
Normal file
@ -0,0 +1,56 @@
|
||||
# docs/features — 功能规格说明
|
||||
|
||||
每个功能模块对应一个功能规格说明文档,用于描述该功能的完整定义。
|
||||
|
||||
## 文件命名
|
||||
|
||||
```
|
||||
<功能名称>.md
|
||||
```
|
||||
|
||||
## 功能规格模板
|
||||
|
||||
```markdown
|
||||
# [功能名称]
|
||||
|
||||
- **版本**:v[版本号]
|
||||
- **状态**:规划中 / 开发中 / 已完成
|
||||
- **负责人**:[姓名]
|
||||
- **关联需求**:[需求编号或链接]
|
||||
|
||||
## 1. 功能概述
|
||||
|
||||
> 一句话描述 + 详细说明(用户视角,这个功能能做什么、解决什么问题)。
|
||||
|
||||
## 2. 用户场景
|
||||
|
||||
- 作为 [角色],我希望 [做什么],以便 [达到什么目的]
|
||||
|
||||
## 3. 功能详情
|
||||
|
||||
### 3.1 前置条件
|
||||
|
||||
### 3.2 主流程
|
||||
|
||||
### 3.3 异常处理
|
||||
|
||||
- 异常情况 1:[描述] → [预期行为]
|
||||
- 异常情况 2:[描述] → [预期行为]
|
||||
|
||||
## 4. 验收标准
|
||||
|
||||
- [ ] 标准 1
|
||||
- [ ] 标准 2
|
||||
- [ ] 标准 3
|
||||
|
||||
## 5. 约束与限制
|
||||
|
||||
- 性能要求
|
||||
- 兼容性要求
|
||||
- 其他限制
|
||||
|
||||
## 6. 关联
|
||||
|
||||
- 设计方案:`docs/design/technical/<相关方案>.md`
|
||||
- 任务拆分:`docs/implementation/tasks/<相关任务>.md`
|
||||
```
|
||||
19
docs/implementation/index.md
Normal file
19
docs/implementation/index.md
Normal file
@ -0,0 +1,19 @@
|
||||
# docs/implementation — 实施与跟踪
|
||||
|
||||
> **可选**:如果团队已使用 Jira/TAPD/飞书等工单系统管理任务,可直接删除此目录。
|
||||
|
||||
本目录用于管理开发实施计划和任务进度。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
implementation/
|
||||
├── index.md # 本文件
|
||||
└── tasks/ # 任务拆分与进度跟踪
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. 将功能需求拆分为可执行的任务
|
||||
2. 每个迭代/功能一个任务文件,记录在 `tasks/` 下
|
||||
3. 任务文件中用 Checkbox 标记进度
|
||||
51
docs/implementation/tasks/index.md
Normal file
51
docs/implementation/tasks/index.md
Normal file
@ -0,0 +1,51 @@
|
||||
# docs/implementation/tasks — 任务跟踪
|
||||
|
||||
每个功能或迭代对应一个任务文件。
|
||||
|
||||
## 文件命名
|
||||
|
||||
```
|
||||
<迭代/功能名称>-任务.md
|
||||
```
|
||||
|
||||
## 任务文件模板
|
||||
|
||||
```markdown
|
||||
# [迭代/功能名称] 任务跟踪
|
||||
|
||||
- **开始日期**:YYYY-MM-DD
|
||||
- **目标日期**:YYYY-MM-DD
|
||||
- **负责人**:[姓名]
|
||||
|
||||
## 任务列表
|
||||
|
||||
### 阶段 1:[阶段名称](目标:YYYY-MM-DD)
|
||||
|
||||
- [ ] 任务 1 — @负责人
|
||||
- [ ] 任务 2 — @负责人
|
||||
- [ ] 子任务 2.1
|
||||
- [ ] 子任务 2.2
|
||||
|
||||
### 阶段 2:[阶段名称](目标:YYYY-MM-DD)
|
||||
|
||||
- [ ] 任务 3 — @负责人
|
||||
- [ ] 任务 4 — @负责人
|
||||
|
||||
## 进度
|
||||
|
||||
- ✅ 已完成:X / Y
|
||||
- 🚧 进行中:X
|
||||
- ⬜ 待开始:X
|
||||
|
||||
## 风险与阻塞
|
||||
|
||||
| 问题 | 影响 | 责任人 | 状态 |
|
||||
|------|------|--------|------|
|
||||
| ... | ... | ... | 解决中 / 已解决 |
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 变更内容 |
|
||||
|------|----------|
|
||||
| YYYY-MM-DD | ... |
|
||||
```
|
||||
281
docs/implementation/tasks/实施计划与任务跟踪.md
Normal file
281
docs/implementation/tasks/实施计划与任务跟踪.md
Normal file
@ -0,0 +1,281 @@
|
||||
# 实施计划与任务跟踪
|
||||
|
||||
> **项目**:反无人机仿真系统后端
|
||||
> **文档版本**:V1.0
|
||||
> **创建日期**:2026-06-11
|
||||
|
||||
---
|
||||
|
||||
## 一、阶段总览
|
||||
|
||||
```
|
||||
Phase 1 ■■■■■ 核心基础(6天)
|
||||
Phase 2 ■■■■■ 想定管理(5天)
|
||||
Phase 3 ■■■■■ 算法层(7天)
|
||||
Phase 4 ■■■■■ 仿真引擎(7天)
|
||||
Phase 5 ■■■ 报告生成(3天)
|
||||
Phase 6 ■■■■ Unity 集成(5天)
|
||||
Phase 7 ■■ 打磨收尾(3天)
|
||||
────────────────────────────
|
||||
合计 约 36 天(7~8 周)
|
||||
```
|
||||
|
||||
> 依赖关系:Phase 1 → Phase 2 → Phase 3+4(可并行) → Phase 5 → Phase 6 → Phase 7
|
||||
|
||||
---
|
||||
|
||||
## 二、各阶段详情
|
||||
|
||||
### Phase 1:核心基础
|
||||
|
||||
**目标**:搭建项目骨架,数据层可读写,单元测试全绿。
|
||||
**时间**:6 天
|
||||
**验证**:`dotnet test` 全绿,所有 Repository 和基础 Service 可独立运行。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 1.1 | 创建 `CounterDrone.Core` 类库项目(.NET Standard 2.1) | 2h | ⬜ |
|
||||
| 1.2 | 创建 `CounterDrone.Core.Tests` xUnit 测试项目 | 1h | ⬜ |
|
||||
| 1.3 | 定义所有枚举(17 个)和数据模型(11 张表 POCO) | 4h | ⬜ |
|
||||
| 1.4 | 集成 sqlite-net,编写 `DatabaseManager`(建表 / 迁移) | 3h | ⬜ |
|
||||
| 1.5 | 实现 `IPathProvider` 接口 + 测试用实现 | 1h | ⬜ |
|
||||
| 1.6 | 实现 `ModelRepository`(CRUD + 文件存储路径管理) | 3h | ⬜ |
|
||||
| 1.7 | 实现 `AmmunitionSpecRepository`(加载第三方 JSON 配置) | 2h | ⬜ |
|
||||
| 1.8 | 实现 `GroupRepository` | 2h | ⬜ |
|
||||
| 1.9 | 实现 `IModelService` + `ModelService` | 3h | ⬜ |
|
||||
| 1.10 | 实现 `IGroupService` + `GroupService` | 2h | ⬜ |
|
||||
| 1.11 | 编写 ModelService / GroupService 单元测试 | 4h | ⬜ |
|
||||
| 1.12 | 编写 Repository 单元测试(SQLite 文件模式) | 4h | ⬜ |
|
||||
|
||||
**Phase 1 验收标准**:
|
||||
- [ ] 11 张表可在 SQLite 中自动建表
|
||||
- [ ] ModelInfo、AmmunitionSpec、Group 的完整 CRUD 通过测试
|
||||
- [ ] 第三方 AmmunitionSpec JSON 可成功导入数据库
|
||||
- [ ] dotnet test 全部通过
|
||||
|
||||
---
|
||||
|
||||
### Phase 2:想定管理
|
||||
|
||||
**目标**:仿真任务的完整 CRUD + 5 步配置保存/加载 + 搜索分页。
|
||||
**时间**:5 天
|
||||
**验证**:可创建想定、逐步骤配置保存、草稿恢复、搜索分页。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 2.1 | 实现 `SimTaskRepository` + `SimTask` 增删改查 | 2h | ⬜ |
|
||||
| 2.2 | 实现 `CombatSceneRepository` | 1h | ⬜ |
|
||||
| 2.3 | 实现 `ControlZoneRepository` | 1h | ⬜ |
|
||||
| 2.4 | 实现 `TargetConfigRepository` | 1h | ⬜ |
|
||||
| 2.5 | 实现 `EquipmentDeploymentRepository` | 1h | ⬜ |
|
||||
| 2.6 | 实现 `CloudDispersalRepository` | 1h | ⬜ |
|
||||
| 2.7 | 实现 `RoutePlanRepository` + `WaypointRepository` | 2h | ⬜ |
|
||||
| 2.8 | 实现 `IScenarioService` — CreateTask / DeleteTask | 2h | ⬜ |
|
||||
| 2.9 | 实现步骤保存方法(SaveScene / SaveTarget / ... / SaveRoute) | 3h | ⬜ |
|
||||
| 2.10 | 实现 `GetTaskDetail`(聚合 5 步配置) | 2h | ⬜ |
|
||||
| 2.11 | 实现 `SearchTasks`(关键词 + 时间范围 + 分页) | 3h | ⬜ |
|
||||
| 2.12 | 实现 UpdateStep(步骤状态管理) | 1h | ⬜ |
|
||||
| 2.13 | 实现输入校验(必填校验、数值范围校验) | 2h | ⬜ |
|
||||
| 2.14 | 编写 ScenarioService 单元测试 | 6h | ⬜ |
|
||||
|
||||
**Phase 2 验收标准**:
|
||||
- [ ] 创建任务 → 5 步逐步骤保存 → 草稿恢复 → 搜索分页 全链路可走通
|
||||
- [ ] 按任务名称搜索、按日期筛选、分页浏览通过测试
|
||||
- [ ] 必填校验:任务名称为空时拒绝创建
|
||||
- [ ] `GetTaskDetail` 返回完整的 5 步配置聚合对象
|
||||
|
||||
---
|
||||
|
||||
### Phase 3:算法层
|
||||
|
||||
**目标**:高斯烟团扩散模型、三种毁伤模型、防御推荐算法全部实现并通过确定性测试。
|
||||
**时间**:7 天
|
||||
**验证**:固定输入 → 固定输出,可精确断言计算结果。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 3.1 | 实现 `GaussianPuffDispersion`(简化球体膨胀 + 风速漂移 + 密度衰减) | 8h | ⬜ |
|
||||
| 3.2 | 编写扩散模型单元测试(无风/有风/消散/密度衰减 4 场景) | 4h | ⬜ |
|
||||
| 3.3 | 实现 `InertGasDamageModel`(阈值型吸入式灭火) | 3h | ⬜ |
|
||||
| 3.4 | 实现 `ActiveMaterialDamageModel`(触发型爆燃式) | 3h | ⬜ |
|
||||
| 3.5 | 实现 `ActiveFuelDamageModel`(指数累积型吸入式爆炸) | 3h | ⬜ |
|
||||
| 3.6 | 实现 `DamageModelRouter`(根据 AerosolType 路由到对应模型) | 1h | ⬜ |
|
||||
| 3.7 | 编写毁伤模型单元测试(每种机制 3 个场景) | 4h | ⬜ |
|
||||
| 3.8 | 实现 `DefaultDefenseAdvisor` — Step A 气溶胶选型规则 | 3h | ⬜ |
|
||||
| 3.9 | 实现 `DefaultDefenseAdvisor` — Step B 时空交汇优化 | 6h | ⬜ |
|
||||
| 3.10 | 实现 `DefaultDefenseAdvisor` — Step C 反推平台部署 | 4h | ⬜ |
|
||||
| 3.11 | 实现 `AlgorithmFactory`(注册/创建) | 2h | ⬜ |
|
||||
| 3.12 | 编写 DefenseAdvisor 单元测试(活塞→惰性气体 / 喷气→活性材料) | 4h | ⬜ |
|
||||
|
||||
**Phase 3 验收标准**:
|
||||
- [ ] 扩散模型:给定初始半径+风速+60s → 断言最终半径和中心偏移
|
||||
- [ ] 毁伤模型:活塞发动机 5s 暴露于惰性气体 → 断言 HP ≤ 阈值
|
||||
- [ ] 防御推荐:活塞发动机航路 → 推荐惰性气体,拦截概率 > 0
|
||||
- [ ] AlgorithmFactory.Register 可替换任一算法实现
|
||||
- [ ] 所有测试结果完全可复现(确定性)
|
||||
|
||||
---
|
||||
|
||||
### Phase 4:仿真引擎
|
||||
|
||||
**目标**:完整 8 阶段仿真引擎,支持运行/暂停/停止,录制帧数据和事件。
|
||||
**时间**:7 天
|
||||
**验证**:加载想定 → 运行完整仿真 → 检查事件序列和帧数据。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 4.1 | 实现 `DroneEntity`(航路移动 + 风偏 + 编队排列) | 4h | ⬜ |
|
||||
| 4.2 | 实现 `PlatformEntity`(空基/地基 + 冷却计时) | 3h | ⬜ |
|
||||
| 4.3 | 实现 `MunitionEntity`(自由落体 + 抛物线弹道) | 4h | ⬜ |
|
||||
| 4.4 | 实现 `CloudEntity`(集成扩散模型 + 毁伤判定) | 3h | ⬜ |
|
||||
| 4.5 | 实现 `DetectionEntity`(探测扫描) | 2h | ⬜ |
|
||||
| 4.6 | 实现 `ControlZone` 多边形判定(Point-in-Polygon) | 2h | ⬜ |
|
||||
| 4.7 | 实现 `EventQueue` | 2h | ⬜ |
|
||||
| 4.8 | 实现 `SimulationEngine` — Initialize(加载配置,实例化实体) | 4h | ⬜ |
|
||||
| 4.9 | 实现 `SimulationEngine` — 8 阶段 Tick 流程 | 6h | ⬜ |
|
||||
| 4.10 | 实现 `SimulationEngine` — Pause/Resume/Stop + State 管理 | 2h | ⬜ |
|
||||
| 4.11 | 实现 `IFrameDataStore` — CreateTaskDb / WriteFrame / ReadFrames | 4h | ⬜ |
|
||||
| 4.12 | 实现 `SimEvent` 写入主库 + 查询 | 2h | ⬜ |
|
||||
| 4.13 | 编写仿真集成测试(单无人机无拦截 → 到达目标) | 3h | ⬜ |
|
||||
| 4.14 | 编写仿真集成测试(单无人机 → 拦截成功 → 被摧毁) | 3h | ⬜ |
|
||||
| 4.15 | 编写仿真集成测试(多无人机 + 编队 + 蜂群) | 3h | ⬜ |
|
||||
| 4.16 | 编写仿真集成测试(管控区域侵入) | 2h | ⬜ |
|
||||
|
||||
**Phase 4 验收标准**:
|
||||
- [ ] 加载完整想定配置后可启动仿真
|
||||
- [ ] 8 个阶段的 Tick 顺序正确,事件队列正常产出
|
||||
- [ ] 集成测试:单无人机无拦截 → 断言 "DroneReachedTarget" 事件
|
||||
- [ ] 集成测试:单无人机被拦截 → 断言 "DroneDestroyed" + "CloudGenerated"
|
||||
- [ ] 帧数据可正确写入 `frames/{taskId}.db`,回读验证帧数一致
|
||||
- [ ] Pause/Resume 不影响仿真状态一致性
|
||||
|
||||
---
|
||||
|
||||
### Phase 5:报告生成
|
||||
|
||||
**目标**:仿真结束后自动生成报告,支持 PDF/Word 导出。
|
||||
**时间**:3 天
|
||||
**验证**:跑完仿真 → 生成报告 → 导出文件存在且内容正确。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 5.1 | 实现 `ReportGenerator`(从 SimEvent + 配置聚合统计数据) | 4h | ⬜ |
|
||||
| 5.2 | 实现对抗结果判定逻辑(成功/部分/失败) | 1h | ⬜ |
|
||||
| 5.3 | 实现报告模板引擎(占位符填充) | 3h | ⬜ |
|
||||
| 5.4 | 实现 PDF 导出(QuestPDF 或替代方案调研) | 4h | ⬜ |
|
||||
| 5.5 | 实现 Word 导出 | 3h | ⬜ |
|
||||
| 5.6 | 实现 `SimulationReportRepository` | 1h | ⬜ |
|
||||
| 5.7 | 实现 `IReportService`(Generate / Get / Delete / Search / Export) | 3h | ⬜ |
|
||||
| 5.8 | 编写报告生成单元测试 | 3h | ⬜ |
|
||||
|
||||
**Phase 5 验收标准**:
|
||||
- [ ] 仿真完成后报告自动生成,包含仿真前/中/后三阶段数据
|
||||
- [ ] 对抗结果判定正确
|
||||
- [ ] PDF 导出文件可正常打开
|
||||
- [ ] 报告搜索分页功能正常
|
||||
|
||||
---
|
||||
|
||||
### Phase 6:Unity 集成
|
||||
|
||||
**目标**:将 Core.dll 集成到 Unity,实现完整运行链路。
|
||||
**时间**:5 天
|
||||
**验证**:在 Unity Editor 中完成"创建想定 → 配置 5 步 → 算法推荐 → 启动仿真 → 查看回放"全流程。
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 6.1 | 创建 Unity 22.3.62 项目,导入 Core.dll 到 Assets/Plugins | 1h | ⬜ |
|
||||
| 6.2 | 实现 `UnityPathProvider`(返回 persistentDataPath) | 1h | ⬜ |
|
||||
| 6.3 | 实现 `ModelManager` MonoBehaviour(桥接 IModelService) | 3h | ⬜ |
|
||||
| 6.4 | 实现 `ScenarioManager` MonoBehaviour(桥接 IScenarioService) | 4h | ⬜ |
|
||||
| 6.5 | 实现 `SimulationRunner` MonoBehaviour(Update 驱动引擎) | 3h | ⬜ |
|
||||
| 6.6 | 实现 `ReplayController` MonoBehaviour(协程回放) | 3h | ⬜ |
|
||||
| 6.7 | 实现 `ReportManager` MonoBehaviour(桥接 IReportService) | 2h | ⬜ |
|
||||
| 6.8 | 实现 3D 实体可视化(根据 Snapshot 更新 GameObject Transform) | 4h | ⬜ |
|
||||
| 6.9 | 实现粒子系统参数传递(CloudEntity → ParticleSystem) | 3h | ⬜ |
|
||||
| 6.10 | 端到端联调 + 问题修复 | 6h | ⬜ |
|
||||
|
||||
**Phase 6 验收标准**:
|
||||
- [ ] Unity Editor 中创建想定 → 配置保存 → 算法推荐 → 启动仿真全链路可走通
|
||||
- [ ] 仿真过程中 3D 实体实时移动
|
||||
- [ ] 回放功能正常
|
||||
- [ ] 无 Crash,无内存泄漏
|
||||
|
||||
---
|
||||
|
||||
### Phase 7:打磨收尾
|
||||
|
||||
**目标**:性能优化、边界处理、TTL 清理、文档。
|
||||
**时间**:3 天
|
||||
|
||||
| # | 任务 | 预估 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 7.1 | 性能优化:Tick() P99 < 5ms | 4h | ⬜ |
|
||||
| 7.2 | TTL 清理实现 + 测试 | 2h | ⬜ |
|
||||
| 7.3 | 边界场景测试(0 实体、超大航路、极端风速) | 3h | ⬜ |
|
||||
| 7.4 | 错误处理加固 | 2h | ⬜ |
|
||||
| 7.5 | 补充缺失的单元测试(目标覆盖率 > 80%) | 4h | ⬜ |
|
||||
| 7.6 | 文档更新 + API 注释 | 3h | ⬜ |
|
||||
|
||||
**Phase 7 验收标准**:
|
||||
- [ ] Tick() 单帧耗时 P99 < 5ms(16 实体场景)
|
||||
- [ ] 过期帧数据库可自动清理
|
||||
- [ ] 单元测试覆盖率 > 80%
|
||||
- [ ] 所有公开 API 有 XML 文档注释
|
||||
|
||||
---
|
||||
|
||||
## 三、依赖关系
|
||||
|
||||
```
|
||||
Phase 1 (核心基础)
|
||||
│
|
||||
▼
|
||||
Phase 2 (想定管理)
|
||||
│
|
||||
├──────────────────┐
|
||||
▼ ▼
|
||||
Phase 3 (算法层) Phase 4 (仿真引擎)
|
||||
│ │
|
||||
└────────┬─────────┘
|
||||
▼
|
||||
Phase 5 (报告生成)
|
||||
│
|
||||
▼
|
||||
Phase 6 (Unity 集成)
|
||||
│
|
||||
▼
|
||||
Phase 7 (打磨收尾)
|
||||
```
|
||||
|
||||
> Phase 3 和 Phase 4 可部分并行:算法层完成后仿真引擎才能完整串联,但仿真实体模型可先开工。
|
||||
|
||||
---
|
||||
|
||||
## 四、里程碑
|
||||
|
||||
| 里程碑 | 截止 | 标志 |
|
||||
|------|------|------|
|
||||
| M1 — 数据层就绪 | Phase 1 结束 | 11 张表 CRUD 通过 |
|
||||
| M2 — 想定可配置 | Phase 2 结束 | 5 步配置保存/恢复/搜索 |
|
||||
| M3 — 算法可计算 | Phase 3 结束 | 扩散/毁伤/推荐输出正确 |
|
||||
| M4 — 仿真可运行 | Phase 4 结束 | 集成测试通过 |
|
||||
| M5 — 报告可生成 | Phase 5 结束 | PDF/Word 导出 |
|
||||
| M6 — Unity 可演示 | Phase 6 结束 | 端到端全链路 |
|
||||
| M7 — 交付就绪 | Phase 7 结束 | 覆盖率 > 80%,P99 < 5ms |
|
||||
|
||||
---
|
||||
|
||||
## 五、状态图例
|
||||
|
||||
| 符号 | 含义 |
|
||||
|------|------|
|
||||
| ⬜ | 未开始 |
|
||||
| 🔄 | 进行中 |
|
||||
| ✅ | 已完成 |
|
||||
| ⏸️ | 阻塞 |
|
||||
| ❌ | 放弃 |
|
||||
|
||||
---
|
||||
|
||||
> **下一步**:确认阶段划分和时间计划后,可以开始 Phase 1,逐项打勾推进。
|
||||
22
docs/index.md
Normal file
22
docs/index.md
Normal file
@ -0,0 +1,22 @@
|
||||
# docs — 开发文档
|
||||
|
||||
本目录存放项目的全部开发文档,涵盖需求、设计、功能规格、实施跟踪和质量验证。
|
||||
|
||||
## 目录说明
|
||||
|
||||
| 子目录 | 用途 | 受众 | 必选 |
|
||||
|--------|------|------|:--:|
|
||||
| `requirements/` | 需求与变更管理 | 产品、开发、测试 | 可选 |
|
||||
| `design/` | 架构设计与技术方案 | 开发、架构师 | ✅ |
|
||||
| `features/` | 功能规格说明 | 开发、测试、产品 | ✅ |
|
||||
| `implementation/` | 实施计划与任务跟踪 | 开发、项目管理 | 可选 |
|
||||
| `verify/` | 人工检查清单(上线/代码审查/安全审计) | 开发、运维 | 可选 |
|
||||
|
||||
> - 标记"可选"的目录:如果团队已使用 Jira/TAPD/飞书等工单系统管理需求和任务,可直接删除,避免双写不同步。
|
||||
> - `verify/` 与 `test/` 的区别:`test/` 是**自动化代码测试**,`verify/` 是**人工检查清单和验证流程**。
|
||||
|
||||
## 使用原则
|
||||
|
||||
1. 文档**与代码同步更新**,不是一次性交付物
|
||||
2. 优先用 Markdown,便于版本控制和 AI 工具阅读
|
||||
3. 每个文档文件保持聚焦,一个文件讲清一件事
|
||||
266
docs/requirements/V1.0_反无人机仿真系统功能需求260611.txt
Normal file
266
docs/requirements/V1.0_反无人机仿真系统功能需求260611.txt
Normal file
@ -0,0 +1,266 @@
|
||||
反无人机仿真系统功能需求
|
||||
|
||||
一、整体导航需求
|
||||
|
||||
系统顶部设置固定导航栏,包含三个核心模块入口:模型管理、想定管理、仿真报告。点击对应导航按钮可切换显示对应模块页面,当前激活模块按钮高亮显示。
|
||||
|
||||
|
||||
二、模型管理模块功能需求
|
||||
|
||||
1. 页面入口
|
||||
|
||||
点击顶部导航栏「模型管理」,进入模型资源库页面,以网格卡片形式展示所有已导入的模型。
|
||||
|
||||
2. 导入新模型
|
||||
|
||||
1.点击页面「导入新模型」按钮,打开导入新模型弹窗
|
||||
2.支持两种导入方式:
|
||||
|
||||
•拖拽模型文件到指定拖拽区域
|
||||
•点击「从文件选择」按钮选中本地模型文件
|
||||
|
||||
3.系统自动提取文件名填充模型名称,支持用户手动修改
|
||||
4.支持格式:.obj .stl .glb,单文件大小不超过500MB
|
||||
5.操作:
|
||||
|
||||
•输入模型名称后点击「确认导入」完成导入,导入成功后关闭弹窗并刷新模型列表
|
||||
•点击「取消」、弹窗关闭按钮或点击弹窗空白区域,可关闭导入弹窗,清空已填写内容
|
||||
|
||||
6.必填校验:模型名称为空时无法导入,提示用户输入模型名称
|
||||
|
||||
3. 模型预览
|
||||
|
||||
点击模型卡片的「预览」按钮,打开模型预览弹窗,展示模型预览内容,支持拖拽旋转、滚轮缩放操作;点击「关闭」、弹窗关闭按钮或点击弹窗空白区域可关闭预览弹窗。
|
||||
|
||||
4. 删除模型
|
||||
|
||||
点击模型卡片的删除按钮,弹出删除确认弹窗,提示「确定要删除该模型吗?删除后无法恢复」;点击「确认删除」完成模型删除并从列表移除,点击「取消」或点击弹窗空白区域取消删除操作。
|
||||
|
||||
|
||||
三、想定管理模块功能需求
|
||||
|
||||
1. 页面入口
|
||||
|
||||
点击顶部导航栏「想定管理」,进入仿真任务管理页面,展示已创建的仿真任务列表。
|
||||
|
||||
2. 任务列表功能
|
||||
|
||||
1.筛选搜索:支持输入任务名称关键词、选择时间范围,点击「搜索」展示匹配结果;点击「筛选」完成条件设置。
|
||||
2.分页浏览:支持点击页码按钮、前后翻页按钮切换分页,浏览不同页的任务。
|
||||
3.列表展示内容:任务名称、任务编号、创建时间、任务状态(已完成/草稿/配置中),每个任务提供「继续配置」操作入口。
|
||||
4.继续配置:点击任务右侧「继续配置」按钮,进入该任务的步骤化配置页面,保留已配置的内容。
|
||||
|
||||
3. 新建仿真任务
|
||||
|
||||
1.点击页面「新增仿真任务」按钮,打开新建仿真任务弹窗
|
||||
2.系统根据当前日期自动生成任务编号(格式:SIM-yyyyMMdd-xxx),支持用户手动修改
|
||||
3.任务名称为必填项,为空无法创建,提示用户输入
|
||||
4.操作:
|
||||
|
||||
•点击「确认创建」,关闭弹窗后进入任务步骤化配置页面,从第一步开始配置
|
||||
•点击「取消」、弹窗关闭按钮或点击弹窗空白区域,关闭新建弹窗
|
||||
|
||||
4. 步骤化配置仿真任务
|
||||
|
||||
配置分为5个步骤,页面顶部展示步骤进度条,实时展示当前配置进度和已完成步骤;配置过程中返回任务列表时,已配置内容自动保存为草稿。
|
||||
|
||||
步骤1:作战场景设置
|
||||
|
||||
•可配置项:
|
||||
•场景选择:支持城市作战场景、平原野外场景、山区地形场景、海岸边防场景
|
||||
•天气类型:支持晴天、阴天、雾天、雨天、夜间
|
||||
•风速:滑动条调整,范围0-30m/s,实时展示当前数值,默认值5m/s
|
||||
•风向:支持8个方向选择
|
||||
•温度:输入数值,默认值20℃
|
||||
•湿度:输入数值,默认值60%RH
|
||||
•气压:输入数值,默认值1013hPa
|
||||
•能见度:滑动条调整,范围100-10000m,步长100m,实时展示当前数值,默认值5000m
|
||||
•操作:
|
||||
•点击「返回列表」:确认后返回任务列表,已配置内容自动保存为草稿
|
||||
•点击「下一步」:进入步骤2目标配置
|
||||
|
||||
步骤2:目标配置
|
||||
|
||||
•可配置项:
|
||||
•目标类型:选择目标类型后自动填充对应翼展、典型速度、典型飞行高度默认参数,支持手动修改:| 目标类型 | 翼展(m) | 典型速度(km/h) | 典型飞行高度(m) |
|
||||
| -------- | ------- | -------------- | --------------- |
|
||||
| 旋翼 | 1.2 | 60 | 300 |
|
||||
| 固定翼 | 3.5 | 150 | 1000 |
|
||||
| 电推 | 1.8 | 100 | 500 |
|
||||
| 活塞 | 2.5 | 120 | 800 |
|
||||
| 高速目标 | 1.5 | 300 | 2000 |
|
||||
•目标数量:输入数值,默认值1
|
||||
•动力类型:支持电推式、活塞式、喷吸气式选择
|
||||
•翼展:可手动修改
|
||||
•典型速度:可手动修改
|
||||
•典型飞行高度:可手动修改
|
||||
•操作:
|
||||
•点击「上一步」:返回步骤1作战场景设置
|
||||
•点击「保存草稿」:保存当前配置,提示保存成功
|
||||
•点击「下一步」:进入步骤3装备部署
|
||||
|
||||
步骤3:装备部署
|
||||
|
||||
•可配置项:
|
||||
•部署对象类型:支持防御装备、布撒设备、探测设备选择
|
||||
•部署数量:输入数值,默认值3
|
||||
•选择编组:支持选择已有编组,也可点击新建编组创建新编组
|
||||
•操作:
|
||||
•点击「上一步」:返回步骤2目标配置
|
||||
•点击「保存草稿」:保存当前配置,提示保存成功
|
||||
•点击「下一步」:进入步骤4云团抛撒配置
|
||||
|
||||
步骤4:云团抛撒配置
|
||||
|
||||
•可配置项:
|
||||
•气溶胶类型:支持惰性气体、活性材料、活性燃料选择
|
||||
•抛撒高度:输入数值,默认值300m
|
||||
•触发模式:支持时间触发、区域触发、手动触发选择
|
||||
•扩散方向:输入角度数值,默认值0度
|
||||
•释放方式:支持单次释放、连续释放、脉冲释放选择
|
||||
•初始规模:输入数值,默认值1000m³
|
||||
•持续时间:输入数值,默认值60s
|
||||
•释放间隔:输入数值,默认值5s
|
||||
•释放总量:输入数值,默认值50kg
|
||||
•抛撒位置定位:支持三维点选、手动输入两种方式
|
||||
•操作:
|
||||
•点击「上一步」:返回步骤3装备部署
|
||||
•点击「保存草稿」:保存当前配置,提示保存成功
|
||||
•点击「下一步」:进入步骤5航路规划
|
||||
|
||||
步骤5:航路规划
|
||||
|
||||
•可配置项:
|
||||
•航路规划画布:支持添加/调整航路点,支持实时航迹预览,可对已有航路点调整顺序、删除操作,支持放大缩小画布
|
||||
•编队模式:支持单机、编队、蜂群选择
|
||||
•编队间距:输入数值,默认值50m
|
||||
•预计到达时间:选择时间
|
||||
•航路点列表:展示所有已添加航路点的基础信息,支持调整顺序、删除单个航路点
|
||||
•操作:
|
||||
•点击「上一步」:返回步骤4云团抛撒配置
|
||||
•点击「仅保存」:保存当前任务配置,提示保存成功
|
||||
•点击「启动仿真」:保存所有配置后,启动仿真流程
|
||||
•通用操作:配置过程中任意步骤点击页面顶部「返回任务列表」,确认后返回任务列表,已配置内容自动保存为草稿。
|
||||
|
||||
|
||||
|
||||
四、仿真报告模块功能需求
|
||||
|
||||
1. 页面入口
|
||||
|
||||
点击顶部导航栏「仿真报告」,进入仿真报告管理页面,展示所有已完成仿真的报告列表。
|
||||
|
||||
2. 筛选搜索
|
||||
|
||||
支持输入任务名称关键词、选择开始时间、结束时间范围;点击「重置」清空所有筛选条件,点击「搜索」展示匹配的报告结果。
|
||||
|
||||
3. 分页浏览
|
||||
|
||||
支持点击页码按钮、前后翻页按钮切换分页,浏览不同页的报告。
|
||||
|
||||
4. 报告列表展示
|
||||
|
||||
展示内容:任务名称、任务编号、完成时间、对抗结果(成功拦截/部分拦截/拦截失败),提供预览、导出、删除三个操作入口。
|
||||
|
||||
5. 预览报告
|
||||
|
||||
点击对应报告的「预览」按钮,打开报告详情页面查看完整报告内容。
|
||||
|
||||
6. 导出报告
|
||||
|
||||
1.点击对应报告的「导出」按钮,打开导出报告弹窗
|
||||
2.支持选择导出格式:PDF、Word,默认选中PDF
|
||||
3.点击「开始导出」触发报告导出下载,点击「取消」、关闭按钮或点击弹窗空白区域关闭导出弹窗
|
||||
|
||||
7. 删除报告
|
||||
|
||||
点击对应报告的删除按钮,弹出删除确认弹窗,提示「确定要删除该仿真报告吗?删除后无法恢复」;点击「确认删除」完成报告删除并从列表移除,点击「取消」或点击弹窗空白区域取消删除操作。
|
||||
|
||||
|
||||
五、业务字段总结
|
||||
|
||||
1. 模型业务字段
|
||||
|
||||
字段名 类型 说明
|
||||
模型ID 字符串 模型唯一标识
|
||||
模型名称 字符串 模型展示名称
|
||||
模型类型 字符串 模型分类:旋翼/固定翼/探测设备/防御装备等
|
||||
模型文件 文件 上传的模型源文件
|
||||
文件大小 数值 模型文件大小,单位MB
|
||||
模型预览图 图片 模型预览缩略图
|
||||
导入时间 日期 模型导入系统的时间
|
||||
|
||||
2. 仿真任务业务字段
|
||||
|
||||
字段名 类型 说明
|
||||
任务ID 字符串 任务唯一标识
|
||||
任务名称 字符串 用户输入的任务名称
|
||||
任务编号 字符串 自动生成,支持修改,格式 SIM-yyyyMMdd-xxx
|
||||
任务状态 枚举 草稿/配置中/已完成
|
||||
创建时间 日期 任务创建时间
|
||||
更新时间 日期 任务最后修改时间
|
||||
|
||||
3. 作战场景配置字段
|
||||
|
||||
字段名 类型 说明
|
||||
场景类型 枚举 城市/平原/山区/海岸
|
||||
天气类型 枚举 晴天/阴天/雾天/雨天/夜间
|
||||
风速 数值 单位m/s,范围0-30
|
||||
风向 枚举 八个方位
|
||||
温度 数值 单位℃
|
||||
湿度 数值 单位%RH
|
||||
气压 数值 单位hPa
|
||||
能见度 数值 单位m,范围100-10000
|
||||
|
||||
4. 目标配置字段
|
||||
|
||||
字段名 类型 说明
|
||||
目标类型 枚举 旋翼/固定翼/电推/活塞/高速目标
|
||||
目标数量 数值 目标总数量
|
||||
动力类型 枚举 电推式/活塞式/喷吸气式
|
||||
翼展 数值 单位m
|
||||
典型速度 数值 单位km/h
|
||||
典型飞行高度 数值 单位m
|
||||
|
||||
5. 装备部署配置字段
|
||||
|
||||
字段名 类型 说明
|
||||
部署对象类型 枚举 防御装备/布撒设备/探测设备
|
||||
部署数量 数值 部署的装备数量
|
||||
所属编组ID 字符串 关联编组唯一标识
|
||||
|
||||
6. 云团抛撒配置字段
|
||||
|
||||
字段名 类型 说明
|
||||
气溶胶类型 枚举 惰性气体/活性材料/活性燃料
|
||||
抛撒高度 数值 单位m
|
||||
触发模式 枚举 时间触发/区域触发/手动触发
|
||||
扩散方向 数值 单位度
|
||||
释放方式 枚举 单次释放/连续释放/脉冲释放
|
||||
初始规模 数值 单位m³
|
||||
持续时间 数值 单位s
|
||||
释放间隔 数值 单位s
|
||||
释放总量 数值 单位kg
|
||||
抛撒位置 坐标 抛撒点经纬度/三维坐标
|
||||
定位方式 枚举 三维点选/手动输入
|
||||
|
||||
7. 航路规划配置字段
|
||||
|
||||
字段名 类型 说明
|
||||
编队模式 枚举 单机/编队/蜂群
|
||||
编队间距 数值 单位m
|
||||
预计到达时间 时间 目标点预计到达时间
|
||||
航路点列表 数组 每个航路点包含坐标、高度、速度信息
|
||||
|
||||
8. 仿真报告业务字段
|
||||
|
||||
字段名 类型 说明
|
||||
报告ID 字符串 报告唯一标识
|
||||
关联任务ID 字符串 关联对应仿真任务
|
||||
任务名称 字符串 对应仿真任务名称
|
||||
任务编号 字符串 对应仿真任务编号
|
||||
完成时间 时间 仿真完成生成报告的时间
|
||||
目标数量 数值 仿真配置的目标总数量
|
||||
装备数量 数值 仿真配置的装备总数量
|
||||
对抗结果 枚举 成功拦截/部分拦截/拦截失败
|
||||
报告内容 富文本/结构化数据 仿真报告完整内容
|
||||
11
docs/requirements/changes/index.md
Normal file
11
docs/requirements/changes/index.md
Normal file
@ -0,0 +1,11 @@
|
||||
# docs/requirements/changes — 需求变更记录
|
||||
|
||||
每条需求变更一个文件,便于追溯和评审。
|
||||
|
||||
## 变更记录文件命名
|
||||
|
||||
```
|
||||
YYYY-MM-DD-<简短描述>.md
|
||||
```
|
||||
|
||||
示例:`2025-01-15-登录支持短信验证码.md`
|
||||
55
docs/requirements/index.md
Normal file
55
docs/requirements/index.md
Normal file
@ -0,0 +1,55 @@
|
||||
# docs/requirements — 需求与变更管理
|
||||
|
||||
> **可选**:如果团队已使用 Jira/TAPD/飞书等工单系统,可直接删除此目录,避免双写。
|
||||
|
||||
本目录用于管理项目需求及其变更记录。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
requirements/
|
||||
├── index.md # 本文件
|
||||
└── changes/ # 需求变更记录(每条变更一个文件)
|
||||
```
|
||||
|
||||
## 需求变更记录
|
||||
|
||||
当需求发生变更时,请在 `changes/` 下创建一条记录。
|
||||
|
||||
### 文件命名规范
|
||||
|
||||
```
|
||||
YYYY-MM-DD-<简短描述>.md
|
||||
```
|
||||
|
||||
### 变更记录模板
|
||||
|
||||
```markdown
|
||||
# [变更标题]
|
||||
|
||||
- **日期**:YYYY-MM-DD
|
||||
- **提出人**:[姓名]
|
||||
- **关联需求**:[原始需求编号或链接]
|
||||
- **优先级**:高 / 中 / 低
|
||||
|
||||
## 变更描述
|
||||
|
||||
> 说明变更内容、变更原因。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- [ ] 接口变更
|
||||
- [ ] 数据库变更
|
||||
- [ ] UI 变更
|
||||
- [ ] 文档变更
|
||||
|
||||
## 审批
|
||||
|
||||
- [ ] 产品确认
|
||||
- [ ] 技术评估
|
||||
- [ ] 测试评估
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 每条变更记录对应一个 `.md` 文件,便于版本控制和追溯。
|
||||
45
docs/requirements/技术要求终版.txt
Normal file
45
docs/requirements/技术要求终版.txt
Normal file
@ -0,0 +1,45 @@
|
||||
本文件“新型惰性/活性材料的防空毁伤仿真”系统的相关技术要求
|
||||
2.技术要求
|
||||
2.1总体要求
|
||||
本系统对新型惰性/活性材料的三种防空毁伤机制[三种威胁源目标(三类无人机):
|
||||
|
||||
吸入式灭火、爆燃式、吸入式爆炸
|
||||
哪种型号
|
||||
]和效果进行数字化仿真。通过参数化配置与可视化推演,量化评估新型气溶胶武器[我方装备配置:
|
||||
搭载装备:大型无人机(空基)、炮弹(地基)]对不同类型空中威胁源目标(动力系统)的毁伤效能。
|
||||
2.2功能要求
|
||||
1.支持仿真过程前置步骤设置,涵盖装备部署、航迹标绘、管控区域设置[装备部署:我方装备配置
|
||||
航迹标绘:威胁源航迹标绘
|
||||
管控区域设置:标绘电子围栏,不允许威胁源进入]
|
||||
2.支持对于布撒环境、布撒设备、布撒无人机参数设置和动态调整
|
||||
3.系统能够实现云团扩撒效果[三维效果:有数值]、无人机穿云效果、毁伤效果。
|
||||
4.支持仿真过程回放,效果数据分析[仿真评估报告数据分析:,
|
||||
|
||||
1,时序图;
|
||||
2,仿真前:我方配置情况、
|
||||
3,仿真中:
|
||||
4,仿真后:
|
||||
]
|
||||
5.支持仿真评估报告生成和输出
|
||||
6.支持预定作战场景仿真
|
||||
2.3仿真可视化要求
|
||||
2.3.1.模型管理
|
||||
(1)支持空中威胁源目标的三维模型的导入和删除,支持FBX、OBJ、GLB、GLTF等格式,支持2K及以上分辨率贴图的三维模型。
|
||||
(2)对三维模型进行预览及详细参数查看,支持模型放大缩小旋转等功能,模型参数支持标签化展示。
|
||||
2.3.2仿真配置
|
||||
(1)抛撒配置[我方装备部署:
|
||||
|
||||
类型、数量、挂载气溶胶的类型、数量(一发炮弹打出的云团大概覆盖体积)
|
||||
|
||||
根据算法自动推荐晕图案的抛洒位置、抛洒时机,
|
||||
最佳值和最危险值
|
||||
]:支持自定义云团的抛撒位置、抛撒时机、风速风向参数。
|
||||
(2)目标配置:预置典型目标库,包括多种无人机。
|
||||
(3)航路规划[航迹标绘,以三维形式进行点选
|
||||
]:支持设置目标的航路点、飞行速度、高度及航迹。
|
||||
(4)气象条件设置:系统支持预设气象条件,支持24小时制仿真时间段,实现对应可视效果。
|
||||
2.4性能要求
|
||||
(1)最大目标数量:针对无人机蜂群场景,单次仿真支持不少于5个独立运动实体。
|
||||
(2)渲染效果:基于粒子系统实现逼真渲染云团扩散、流动、色彩;动态展示目标的毁伤状态,包括尾焰异常、机体姿态失控等物理效果。
|
||||
(3)空间范围:仿真场景有效空域最低要求:4*100*10(。
|
||||
(4)视景帧率:满载运行(含粒子特效与多目标)帧率 ≥ 30FPS。
|
||||
36
docs/verify/checklist/index.md
Normal file
36
docs/verify/checklist/index.md
Normal file
@ -0,0 +1,36 @@
|
||||
# verify/checklist — 检查清单
|
||||
|
||||
本目录存放各环节的检查清单。
|
||||
|
||||
## 清单模板
|
||||
|
||||
以**上线检查清单**为例:
|
||||
|
||||
```markdown
|
||||
# 上线检查清单
|
||||
|
||||
## 代码质量
|
||||
- [ ] 代码审查已完成
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 无已知 P0/P1 Bug
|
||||
- [ ] 代码已合并到主分支
|
||||
|
||||
## 性能与安全
|
||||
- [ ] 性能测试通过
|
||||
- [ ] 安全扫描通过
|
||||
- [ ] 敏感信息已清理
|
||||
|
||||
## 运维准备
|
||||
- [ ] 数据库变更脚本就绪
|
||||
- [ ] 回滚方案已准备
|
||||
- [ ] 监控和告警已配置
|
||||
|
||||
## 文档
|
||||
- [ ] CHANGELOG 已更新
|
||||
- [ ] 版本号已更新
|
||||
- [ ] API 文档已更新(如有)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 根据项目实际情况增删条目。
|
||||
20
docs/verify/index.md
Normal file
20
docs/verify/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
# verify — 验证与质量保障
|
||||
|
||||
本目录存放人工检查清单和验证流程文档。
|
||||
|
||||
> 已从顶层目录迁移至 `docs/verify/`,以下为历史兼容说明。**新项目请直接使用 `docs/verify/`**。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
verify/
|
||||
├── index.md # 本文件
|
||||
└── checklist/ # 检查清单
|
||||
```
|
||||
|
||||
## 用途
|
||||
|
||||
- 上线前检查清单
|
||||
- 发布验收清单
|
||||
- 代码审查清单
|
||||
- 安全审计清单
|
||||
9
scripts/index.md
Normal file
9
scripts/index.md
Normal file
@ -0,0 +1,9 @@
|
||||
# scripts — 工具脚本
|
||||
|
||||
本目录存放项目的辅助脚本:构建、部署、数据迁移、环境初始化等。
|
||||
|
||||
## 原则
|
||||
|
||||
- 每个脚本做好注释,说明用途和运行方式
|
||||
- 脚本应尽量幂等(多次运行结果一致)
|
||||
- 避免将敏感信息(密码、密钥)硬编码在脚本中
|
||||
13
skills/index.md
Normal file
13
skills/index.md
Normal file
@ -0,0 +1,13 @@
|
||||
# skills — AI 辅助技能
|
||||
|
||||
本目录存放 AI 编码助手的辅助技能定义(遵循 [Agent Skills 标准](https://agentskills.io))。
|
||||
|
||||
> **注意**:本目录仅适用于使用 pi 编码助手或兼容 Agent Skills 标准的工具。[其他工具可删除此目录。]
|
||||
|
||||
## 使用方式
|
||||
|
||||
在对话中通过 `/skill:技能名` 调用,或由 AI 自动加载。
|
||||
|
||||
## 现有技能
|
||||
|
||||
当前尚无自定义技能。
|
||||
14
src/CounterDrone.Core/CounterDrone.Core.csproj
Normal file
14
src/CounterDrone.Core/CounterDrone.Core.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
69
src/CounterDrone.Core/DatabaseManager.cs
Normal file
69
src/CounterDrone.Core/DatabaseManager.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using CounterDrone.Core.Models;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
/// <summary>数据库管理器 — 建表 / 连接管理</summary>
|
||||
public class DatabaseManager
|
||||
{
|
||||
private readonly IPathProvider _paths;
|
||||
|
||||
public DatabaseManager(IPathProvider paths)
|
||||
{
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
/// <summary>初始化主数据库(建表)</summary>
|
||||
public SQLiteConnection OpenMainDb()
|
||||
{
|
||||
var dbPath = _paths.GetMainDbPath();
|
||||
var db = new SQLiteConnection(dbPath);
|
||||
CreateMainTables(db);
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>创建/打开任务帧数据库</summary>
|
||||
public SQLiteConnection OpenFrameDb(string taskId)
|
||||
{
|
||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
||||
var db = new SQLiteConnection(dbPath);
|
||||
db.CreateTable<SimFrameRecord>();
|
||||
db.CreateIndex("SimFrameRecord", new[] { "TaskId", "FrameIndex" });
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>删除任务帧数据库文件</summary>
|
||||
public void DeleteFrameDb(string taskId)
|
||||
{
|
||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
||||
if (File.Exists(dbPath))
|
||||
File.Delete(dbPath);
|
||||
}
|
||||
|
||||
private void CreateMainTables(SQLiteConnection db)
|
||||
{
|
||||
db.CreateTable<ModelInfo>();
|
||||
db.CreateTable<AmmunitionSpec>();
|
||||
db.CreateTable<SimTask>();
|
||||
db.CreateTable<CombatScene>();
|
||||
db.CreateTable<ControlZone>();
|
||||
db.CreateTable<TargetConfig>();
|
||||
db.CreateTable<EquipmentDeployment>();
|
||||
db.CreateTable<CloudDispersal>();
|
||||
db.CreateTable<RoutePlan>();
|
||||
db.CreateTable<Waypoint>();
|
||||
db.CreateTable<Group>();
|
||||
db.CreateTable<SimulationReport>();
|
||||
db.CreateTable<SimEvent>();
|
||||
|
||||
db.CreateIndex("SimTask", "TaskNumber");
|
||||
db.CreateIndex("ControlZone", "TaskId");
|
||||
db.CreateIndex("TargetConfig", "TaskId");
|
||||
db.CreateIndex("EquipmentDeployment", "TaskId");
|
||||
db.CreateIndex("Waypoint", "TaskId");
|
||||
db.CreateIndex("SimEvent", "TaskId");
|
||||
db.CreateIndex("SimulationReport", "TaskId");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/CounterDrone.Core/IPathProvider.cs
Normal file
11
src/CounterDrone.Core/IPathProvider.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
/// <summary>路径提供者接口 — 隔离文件系统依赖</summary>
|
||||
public interface IPathProvider
|
||||
{
|
||||
string GetDataRoot();
|
||||
string GetMainDbPath();
|
||||
string GetFramesDir();
|
||||
string GetModelsDir();
|
||||
}
|
||||
}
|
||||
42
src/CounterDrone.Core/Models/AmmunitionSpec.cs
Normal file
42
src/CounterDrone.Core/Models/AmmunitionSpec.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>弹药基础参数(第三方数据)</summary>
|
||||
[Table("AmmunitionSpec")]
|
||||
public class AmmunitionSpec
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
public int AerosolType { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
// 初始扩散状态
|
||||
public double InitialRadius { get; set; }
|
||||
|
||||
public double InitialVolume { get; set; }
|
||||
|
||||
public double CoreDensity { get; set; }
|
||||
|
||||
public double EdgeDensity { get; set; }
|
||||
|
||||
public double InitialTemperature { get; set; }
|
||||
|
||||
public double BuoyancyFactor { get; set; }
|
||||
|
||||
// 弹药基础参数
|
||||
public double EffectiveConcentration { get; set; }
|
||||
|
||||
public double DispersionRateBase { get; set; }
|
||||
|
||||
public double MaxRadius { get; set; }
|
||||
|
||||
public double MaxDuration { get; set; }
|
||||
|
||||
/// <summary>粒子渲染参数 JSON</summary>
|
||||
public string ParticlesJson { get; set; } = "{}";
|
||||
}
|
||||
}
|
||||
49
src/CounterDrone.Core/Models/CloudDispersal.cs
Normal file
49
src/CounterDrone.Core/Models/CloudDispersal.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤4:云团抛撒配置</summary>
|
||||
[Table("CloudDispersal")]
|
||||
public class CloudDispersal
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public int AerosolType { get; set; } = (int)Models.AerosolType.InertGas;
|
||||
|
||||
public double DisperseHeight { get; set; } = 300.0;
|
||||
|
||||
public int TriggerMode { get; set; } = (int)Models.TriggerMode.Time;
|
||||
|
||||
public double SpreadAngle { get; set; } = 0.0;
|
||||
|
||||
public int ReleaseMode { get; set; } = (int)Models.ReleaseMode.Single;
|
||||
|
||||
public double InitialScale { get; set; } = 1000.0;
|
||||
|
||||
public double Duration { get; set; } = 60.0;
|
||||
|
||||
public double ReleaseInterval { get; set; } = 5.0;
|
||||
|
||||
public double TotalAmount { get; set; } = 50.0;
|
||||
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
|
||||
public int PositionMode { get; set; } = (int)Models.PositionMode.ManualInput;
|
||||
|
||||
public string Source { get; set; } = "Manual";
|
||||
|
||||
// 算法推荐结果
|
||||
public double? RecommendedPosX { get; set; }
|
||||
public double? RecommendedPosY { get; set; }
|
||||
public double? RecommendedPosZ { get; set; }
|
||||
public double? RecommendedTiming { get; set; }
|
||||
|
||||
public double? CriticalPosX { get; set; }
|
||||
public double? CriticalPosY { get; set; }
|
||||
public double? CriticalPosZ { get; set; }
|
||||
public double? CriticalTiming { get; set; }
|
||||
}
|
||||
}
|
||||
36
src/CounterDrone.Core/Models/CombatScene.cs
Normal file
36
src/CounterDrone.Core/Models/CombatScene.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤1:作战场景</summary>
|
||||
[Table("CombatScene")]
|
||||
public class CombatScene
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public int SceneType { get; set; } = (int)Models.SceneType.Plain;
|
||||
|
||||
public string TimeOfDay { get; set; } = "12:00";
|
||||
|
||||
public int WeatherType { get; set; } = (int)Models.WeatherType.Sunny;
|
||||
|
||||
public double WindSpeed { get; set; } = 5.0;
|
||||
|
||||
public int WindDirection { get; set; } = (int)Models.WindDirection.N;
|
||||
|
||||
public double Temperature { get; set; } = 20.0;
|
||||
|
||||
public double Humidity { get; set; } = 60.0;
|
||||
|
||||
public double Pressure { get; set; } = 1013.0;
|
||||
|
||||
public double Visibility { get; set; } = 5000.0;
|
||||
|
||||
public double SceneWidth { get; set; } = 4000.0;
|
||||
|
||||
public double SceneLength { get; set; } = 10000.0;
|
||||
|
||||
public double SceneHeight { get; set; } = 1000.0;
|
||||
}
|
||||
}
|
||||
28
src/CounterDrone.Core/Models/ControlZone.cs
Normal file
28
src/CounterDrone.Core/Models/ControlZone.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>管控区域 / 电子围栏</summary>
|
||||
[Table("ControlZone")]
|
||||
public class ControlZone
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>多边形顶点 JSON:[{"x":0,"y":0,"z":0},...]</summary>
|
||||
[NotNull]
|
||||
public string VerticesJson { get; set; } = "[]";
|
||||
|
||||
public double MinAltitude { get; set; }
|
||||
|
||||
public double MaxAltitude { get; set; }
|
||||
|
||||
public int OrderIndex { get; set; }
|
||||
}
|
||||
}
|
||||
152
src/CounterDrone.Core/Models/Enums.cs
Normal file
152
src/CounterDrone.Core/Models/Enums.cs
Normal file
@ -0,0 +1,152 @@
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
// === 任务 ===
|
||||
public enum TaskStatus
|
||||
{
|
||||
Draft = 0,
|
||||
Configuring = 1,
|
||||
Completed = 2
|
||||
}
|
||||
|
||||
// === 场景 ===
|
||||
public enum SceneType
|
||||
{
|
||||
Urban = 0,
|
||||
Plain = 1,
|
||||
Mountain = 2,
|
||||
Coast = 3
|
||||
}
|
||||
|
||||
public enum WeatherType
|
||||
{
|
||||
Sunny = 0,
|
||||
Overcast = 1,
|
||||
Fog = 2,
|
||||
Rain = 3,
|
||||
Night = 4
|
||||
}
|
||||
|
||||
public enum WindDirection
|
||||
{
|
||||
N = 0, NE = 1, E = 2, SE = 3,
|
||||
S = 4, SW = 5, W = 6, NW = 7
|
||||
}
|
||||
|
||||
// === 目标 ===
|
||||
public enum TargetType
|
||||
{
|
||||
Rotor = 0,
|
||||
FixedWing = 1,
|
||||
Electric = 2,
|
||||
Piston = 3,
|
||||
HighSpeed = 4
|
||||
}
|
||||
|
||||
public enum PowerType
|
||||
{
|
||||
Electric = 0,
|
||||
Piston = 1,
|
||||
Jet = 2
|
||||
}
|
||||
|
||||
// === 装备 & 搭载平台 ===
|
||||
public enum PlatformType
|
||||
{
|
||||
AirBased = 0,
|
||||
GroundBased = 1
|
||||
}
|
||||
|
||||
public enum EquipmentRole
|
||||
{
|
||||
Detection = 0,
|
||||
LaunchPlatform = 1
|
||||
}
|
||||
|
||||
// === 气溶胶 & 毁伤 ===
|
||||
public enum AerosolType
|
||||
{
|
||||
InertGas = 0,
|
||||
ActiveMaterial = 1,
|
||||
ActiveFuel = 2
|
||||
}
|
||||
|
||||
public enum ReleaseMode
|
||||
{
|
||||
Single = 0,
|
||||
Continuous = 1,
|
||||
Pulse = 2
|
||||
}
|
||||
|
||||
public enum TriggerMode
|
||||
{
|
||||
Time = 0,
|
||||
Area = 1,
|
||||
Manual = 2
|
||||
}
|
||||
|
||||
public enum DamageStage
|
||||
{
|
||||
Normal = 0,
|
||||
EngineAnomaly = 1,
|
||||
AttitudeLoss = 2,
|
||||
Destroyed = 3
|
||||
}
|
||||
|
||||
// === 编队 & 编组 ===
|
||||
public enum FormationMode
|
||||
{
|
||||
Single = 0,
|
||||
Formation = 1,
|
||||
Swarm = 2
|
||||
}
|
||||
|
||||
public enum GroupType
|
||||
{
|
||||
DroneFleet = 0,
|
||||
EquipmentGroup = 1
|
||||
}
|
||||
|
||||
// === 运行时 ===
|
||||
public enum EntityType
|
||||
{
|
||||
Drone = 0,
|
||||
Platform = 1,
|
||||
DetectionEquip = 2,
|
||||
Cloud = 3,
|
||||
Munition = 4
|
||||
}
|
||||
|
||||
public enum SimEventType
|
||||
{
|
||||
TargetDetected = 0,
|
||||
MunitionLaunched = 1,
|
||||
CloudGenerated = 2,
|
||||
DroneEnteredCloud = 3,
|
||||
DroneDestroyed = 4,
|
||||
DroneReachedTarget = 5,
|
||||
ZoneIntruded = 6,
|
||||
WaypointReached = 7,
|
||||
SimulationEnd = 8
|
||||
}
|
||||
|
||||
// === 报告 ===
|
||||
public enum InterceptResult
|
||||
{
|
||||
Success = 0,
|
||||
Partial = 1,
|
||||
Failed = 2
|
||||
}
|
||||
|
||||
public enum PositionMode
|
||||
{
|
||||
PointSelect = 0,
|
||||
ManualInput = 1,
|
||||
AlgorithmRecommended = 2
|
||||
}
|
||||
|
||||
public enum ConfigSource
|
||||
{
|
||||
Manual = 0,
|
||||
Algorithm = 1
|
||||
}
|
||||
}
|
||||
44
src/CounterDrone.Core/Models/EquipmentDeployment.cs
Normal file
44
src/CounterDrone.Core/Models/EquipmentDeployment.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤3:装备部署</summary>
|
||||
[Table("EquipmentDeployment")]
|
||||
public class EquipmentDeployment
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public int EquipmentRole { get; set; } = (int)Models.EquipmentRole.LaunchPlatform;
|
||||
|
||||
public int Quantity { get; set; } = 1;
|
||||
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
// 发射平台专用
|
||||
public int? PlatformType { get; set; }
|
||||
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
|
||||
public int? AerosolType { get; set; }
|
||||
|
||||
public int? MunitionCount { get; set; }
|
||||
|
||||
public int Source { get; set; } = (int)ConfigSource.Manual;
|
||||
|
||||
public double? MuzzleVelocity { get; set; }
|
||||
|
||||
public double? ReleaseAltitude { get; set; }
|
||||
|
||||
public double Cooldown { get; set; } = 5.0;
|
||||
|
||||
// 探测设备专用
|
||||
public double? DetectionRadius { get; set; }
|
||||
}
|
||||
}
|
||||
22
src/CounterDrone.Core/Models/Group.cs
Normal file
22
src/CounterDrone.Core/Models/Group.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>编组</summary>
|
||||
[Table("Group")]
|
||||
public class Group
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public int GroupType { get; set; } = (int)Models.GroupType.DroneFleet;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("o");
|
||||
}
|
||||
}
|
||||
28
src/CounterDrone.Core/Models/ModelInfo.cs
Normal file
28
src/CounterDrone.Core/Models/ModelInfo.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>模型元数据</summary>
|
||||
[Table("ModelInfo")]
|
||||
public class ModelInfo
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string ModelType { get; set; } = string.Empty;
|
||||
|
||||
public string FilePath { get; set; } = string.Empty;
|
||||
|
||||
public double FileSize { get; set; }
|
||||
|
||||
public string PreviewPath { get; set; } = string.Empty;
|
||||
|
||||
public bool HasHighResTexture { get; set; }
|
||||
|
||||
public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("o");
|
||||
}
|
||||
}
|
||||
20
src/CounterDrone.Core/Models/RoutePlan.cs
Normal file
20
src/CounterDrone.Core/Models/RoutePlan.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤5:航路规划</summary>
|
||||
[Table("RoutePlan")]
|
||||
public class RoutePlan
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
public int FormationMode { get; set; } = (int)Models.FormationMode.Single;
|
||||
|
||||
public double FormationSpacing { get; set; } = 50.0;
|
||||
|
||||
public string ETA { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
28
src/CounterDrone.Core/Models/SimEvent.cs
Normal file
28
src/CounterDrone.Core/Models/SimEvent.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>仿真事件(时序图数据源)— 存主库</summary>
|
||||
[Table("SimEvent")]
|
||||
public class SimEvent
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public double OccurredAt { get; set; }
|
||||
|
||||
public int EventType { get; set; }
|
||||
|
||||
public string SourceId { get; set; } = string.Empty;
|
||||
|
||||
public string TargetId { get; set; } = string.Empty;
|
||||
|
||||
public string DataJson { get; set; } = "{}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
34
src/CounterDrone.Core/Models/SimFrameRecord.cs
Normal file
34
src/CounterDrone.Core/Models/SimFrameRecord.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>逐帧位置记录(回放用)— 存任务分库</summary>
|
||||
[Table("SimFrameRecord")]
|
||||
public class SimFrameRecord
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public int FrameIndex { get; set; }
|
||||
|
||||
public double Timestamp { get; set; }
|
||||
|
||||
public string EntityId { get; set; } = string.Empty;
|
||||
|
||||
public int EntityType { get; set; }
|
||||
|
||||
public double PosX { get; set; }
|
||||
public double PosY { get; set; }
|
||||
public double PosZ { get; set; }
|
||||
|
||||
public double RotX { get; set; }
|
||||
public double RotY { get; set; }
|
||||
public double RotZ { get; set; }
|
||||
|
||||
/// <summary>JSON:damageStage, cloudRadius, cloudOpacity 等</summary>
|
||||
public string StateData { get; set; } = "{}";
|
||||
}
|
||||
}
|
||||
29
src/CounterDrone.Core/Models/SimTask.cs
Normal file
29
src/CounterDrone.Core/Models/SimTask.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>仿真任务主表</summary>
|
||||
[Table("SimTask")]
|
||||
public class SimTask
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Unique]
|
||||
public string TaskNumber { get; set; } = string.Empty;
|
||||
|
||||
public int Status { get; set; } = (int)TaskStatus.Draft;
|
||||
|
||||
public int CurrentStep { get; set; } = 1;
|
||||
|
||||
public int TickRate { get; set; } = 20;
|
||||
|
||||
public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("o");
|
||||
|
||||
public string UpdatedAt { get; set; } = DateTime.UtcNow.ToString("o");
|
||||
}
|
||||
}
|
||||
31
src/CounterDrone.Core/Models/SimulationReport.cs
Normal file
31
src/CounterDrone.Core/Models/SimulationReport.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>仿真报告</summary>
|
||||
[Table("SimulationReport")]
|
||||
public class SimulationReport
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public string TaskName { get; set; } = string.Empty;
|
||||
|
||||
public string TaskNumber { get; set; } = string.Empty;
|
||||
|
||||
public string CompletedAt { get; set; } = string.Empty;
|
||||
|
||||
public int TargetCount { get; set; }
|
||||
|
||||
public int EquipmentCount { get; set; }
|
||||
|
||||
public int InterceptResult { get; set; }
|
||||
|
||||
/// <summary>报告富文本 / 结构化 JSON</summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
30
src/CounterDrone.Core/Models/TargetConfig.cs
Normal file
30
src/CounterDrone.Core/Models/TargetConfig.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤2:目标配置</summary>
|
||||
[Table("TargetConfig")]
|
||||
public class TargetConfig
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
public int TargetType { get; set; } = (int)Models.TargetType.Rotor;
|
||||
|
||||
public int Quantity { get; set; } = 1;
|
||||
|
||||
public int PowerType { get; set; } = (int)Models.PowerType.Electric;
|
||||
|
||||
public double Wingspan { get; set; } = 1.2;
|
||||
|
||||
public double TypicalSpeed { get; set; } = 60.0;
|
||||
|
||||
public double TypicalAltitude { get; set; } = 300.0;
|
||||
}
|
||||
}
|
||||
27
src/CounterDrone.Core/Models/TaskFullConfig.cs
Normal file
27
src/CounterDrone.Core/Models/TaskFullConfig.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>想定完整配置(聚合 5 步配置)</summary>
|
||||
public class TaskFullConfig
|
||||
{
|
||||
public SimTask Task { get; set; } = new();
|
||||
public CombatScene Scene { get; set; } = new();
|
||||
public List<ControlZone> ControlZones { get; set; } = new();
|
||||
public List<TargetConfig> Targets { get; set; } = new();
|
||||
public List<EquipmentDeployment> Equipment { get; set; } = new();
|
||||
public CloudDispersal Cloud { get; set; } = new();
|
||||
public RoutePlan Route { get; set; } = new();
|
||||
public List<Waypoint> Waypoints { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>分页结果</summary>
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public List<T> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalPages => (TotalCount + PageSize - 1) / PageSize;
|
||||
}
|
||||
}
|
||||
27
src/CounterDrone.Core/Models/Waypoint.cs
Normal file
27
src/CounterDrone.Core/Models/Waypoint.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>航路点</summary>
|
||||
[Table("Waypoint")]
|
||||
public class Waypoint
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
[NotNull]
|
||||
public int OrderIndex { get; set; }
|
||||
|
||||
public double PosX { get; set; }
|
||||
public double PosY { get; set; }
|
||||
public double PosZ { get; set; }
|
||||
|
||||
public double Altitude { get; set; }
|
||||
|
||||
public double Speed { get; set; }
|
||||
}
|
||||
}
|
||||
19
src/CounterDrone.Core/Repository/AmmunitionSpecRepository.cs
Normal file
19
src/CounterDrone.Core/Repository/AmmunitionSpecRepository.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core.Models;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Repository
|
||||
{
|
||||
public class AmmunitionSpecRepository : BaseRepository<AmmunitionSpec>
|
||||
{
|
||||
public AmmunitionSpecRepository(SQLiteConnection db) : base(db) { }
|
||||
|
||||
public List<AmmunitionSpec> GetByAerosolType(int aerosolType)
|
||||
{
|
||||
return Db.Table<AmmunitionSpec>()
|
||||
.Where(a => a.AerosolType == aerosolType)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/CounterDrone.Core/Repository/BaseRepository.cs
Normal file
46
src/CounterDrone.Core/Repository/BaseRepository.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Repository
|
||||
{
|
||||
/// <summary>泛型仓储基类</summary>
|
||||
public abstract class BaseRepository<T> where T : new()
|
||||
{
|
||||
protected readonly SQLiteConnection Db;
|
||||
|
||||
protected BaseRepository(SQLiteConnection db)
|
||||
{
|
||||
Db = db;
|
||||
}
|
||||
|
||||
public virtual T GetById(object id)
|
||||
{
|
||||
return Db.Find<T>(id);
|
||||
}
|
||||
|
||||
public virtual List<T> GetAll()
|
||||
{
|
||||
return Db.Table<T>().ToList();
|
||||
}
|
||||
|
||||
public virtual int Insert(T entity)
|
||||
{
|
||||
return Db.Insert(entity);
|
||||
}
|
||||
|
||||
public virtual int Update(T entity)
|
||||
{
|
||||
return Db.Update(entity);
|
||||
}
|
||||
|
||||
public virtual int Delete(object id)
|
||||
{
|
||||
return Db.Delete<T>(id);
|
||||
}
|
||||
|
||||
public virtual void InsertAll(IEnumerable<T> entities)
|
||||
{
|
||||
Db.InsertAll(entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/CounterDrone.Core/Repository/GroupRepository.cs
Normal file
19
src/CounterDrone.Core/Repository/GroupRepository.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core.Models;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Repository
|
||||
{
|
||||
public class GroupRepository : BaseRepository<Group>
|
||||
{
|
||||
public GroupRepository(SQLiteConnection db) : base(db) { }
|
||||
|
||||
public List<Group> GetByType(int groupType)
|
||||
{
|
||||
return Db.Table<Group>()
|
||||
.Where(g => g.GroupType == groupType)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/CounterDrone.Core/Repository/ModelRepository.cs
Normal file
10
src/CounterDrone.Core/Repository/ModelRepository.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using CounterDrone.Core.Models;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Repository
|
||||
{
|
||||
public class ModelRepository : BaseRepository<ModelInfo>
|
||||
{
|
||||
public ModelRepository(SQLiteConnection db) : base(db) { }
|
||||
}
|
||||
}
|
||||
50
src/CounterDrone.Core/Services/GroupService.cs
Normal file
50
src/CounterDrone.Core/Services/GroupService.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
public class GroupService : IGroupService
|
||||
{
|
||||
private readonly GroupRepository _repo;
|
||||
|
||||
public GroupService(GroupRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public Group CreateGroup(string name, GroupType type, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("编组名称不能为空");
|
||||
|
||||
var group = new Group
|
||||
{
|
||||
Name = name,
|
||||
GroupType = (int)type,
|
||||
Description = description,
|
||||
};
|
||||
|
||||
_repo.Insert(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
public void DeleteGroup(string id)
|
||||
{
|
||||
_repo.Delete(id);
|
||||
}
|
||||
|
||||
public List<Group> GetGroups(GroupType? type)
|
||||
{
|
||||
if (type.HasValue)
|
||||
return _repo.GetByType((int)type.Value);
|
||||
return _repo.GetAll();
|
||||
}
|
||||
|
||||
public Group GetGroup(string id)
|
||||
{
|
||||
return _repo.GetById(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/CounterDrone.Core/Services/IGroupService.cs
Normal file
13
src/CounterDrone.Core/Services/IGroupService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
public interface IGroupService
|
||||
{
|
||||
Group CreateGroup(string name, GroupType type, string description);
|
||||
void DeleteGroup(string id);
|
||||
List<Group> GetGroups(GroupType? type);
|
||||
Group GetGroup(string id);
|
||||
}
|
||||
}
|
||||
13
src/CounterDrone.Core/Services/IModelService.cs
Normal file
13
src/CounterDrone.Core/Services/IModelService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
public interface IModelService
|
||||
{
|
||||
ModelInfo ImportModel(string filePath, string name);
|
||||
void DeleteModel(string id);
|
||||
List<ModelInfo> GetAllModels();
|
||||
ModelInfo GetModel(string id);
|
||||
}
|
||||
}
|
||||
59
src/CounterDrone.Core/Services/ModelService.cs
Normal file
59
src/CounterDrone.Core/Services/ModelService.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
public class ModelService : IModelService
|
||||
{
|
||||
private readonly ModelRepository _repo;
|
||||
private readonly IPathProvider _paths;
|
||||
|
||||
public ModelService(ModelRepository repo, IPathProvider paths)
|
||||
{
|
||||
_repo = repo;
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
public ModelInfo ImportModel(string filePath, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("模型名称不能为空");
|
||||
|
||||
var ext = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
if (ext != ".fbx" && ext != ".obj" && ext != ".stl" && ext != ".glb" && ext != ".gltf")
|
||||
throw new ArgumentException($"不支持的模型格式:{ext}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
if (fileInfo.Length > 500 * 1024 * 1024)
|
||||
throw new ArgumentException("模型文件大小超过 500MB 限制");
|
||||
|
||||
var model = new ModelInfo
|
||||
{
|
||||
Name = name,
|
||||
FilePath = filePath,
|
||||
FileSize = fileInfo.Length / (1024.0 * 1024.0),
|
||||
};
|
||||
|
||||
_repo.Insert(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
public void DeleteModel(string id)
|
||||
{
|
||||
_repo.Delete(id);
|
||||
}
|
||||
|
||||
public List<ModelInfo> GetAllModels()
|
||||
{
|
||||
return _repo.GetAll();
|
||||
}
|
||||
|
||||
public ModelInfo GetModel(string id)
|
||||
{
|
||||
return _repo.GetById(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/CounterDrone.Core/TestPathProvider.cs
Normal file
35
src/CounterDrone.Core/TestPathProvider.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
/// <summary>IPathProvider 的实现,用于单元测试环境</summary>
|
||||
public class TestPathProvider : IPathProvider
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public TestPathProvider(string rootDir)
|
||||
{
|
||||
_root = rootDir;
|
||||
Directory.CreateDirectory(_root);
|
||||
}
|
||||
|
||||
public string GetDataRoot() => _root;
|
||||
|
||||
public string GetMainDbPath() => Path.Combine(_root, "main.db");
|
||||
|
||||
public string GetFramesDir()
|
||||
{
|
||||
var dir = Path.Combine(_root, "frames");
|
||||
Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public string GetModelsDir()
|
||||
{
|
||||
var dir = Path.Combine(_root, "models");
|
||||
Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/index.md
Normal file
9
src/index.md
Normal file
@ -0,0 +1,9 @@
|
||||
# src — 源代码
|
||||
|
||||
本目录存放项目的全部业务源代码。
|
||||
|
||||
## 原则
|
||||
|
||||
1. 所有 Python/Go/Java 等业务模块统一收入 `src/` 下
|
||||
2. 根目录仅保留入口文件(如 `app.py`、`main.go`)、配置文件和文档
|
||||
3. 按功能模块建立子目录,保持扁平层次(不超过 3 层为宜)
|
||||
14
test/e2e/index.md
Normal file
14
test/e2e/index.md
Normal file
@ -0,0 +1,14 @@
|
||||
# test/e2e — 端到端测试
|
||||
|
||||
模拟真实用户行为,从系统入口到出口的完整链路测试。
|
||||
|
||||
## 原则
|
||||
|
||||
- **用户视角** — 模拟用户操作流程
|
||||
- **环境一致** — 尽量在类生产环境中运行
|
||||
- **场景驱动** — 聚焦核心用户场景,避免测试琐碎细节
|
||||
|
||||
## 示例
|
||||
|
||||
- 用户注册 → 登录 → 操作 → 退出 完整流程
|
||||
- 订单创建 → 支付 → 状态变更 完整链路
|
||||
18
test/index.md
Normal file
18
test/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
# test — 测试代码
|
||||
|
||||
本目录存放项目的全部测试代码。测试代码的组织结构应**镜像** `src/` 的结构,便于定位。
|
||||
|
||||
## 目录说明
|
||||
|
||||
| 子目录 | 用途 | 特点 |
|
||||
|--------|------|------|
|
||||
| `unit/` | 单元测试 | 测试最小单元(函数、方法),不依赖外部系统 |
|
||||
| `integration/` | 集成测试 | 测试模块间交互,或与外部系统(数据库、API)的集成 |
|
||||
| `e2e/` | 端到端测试 | 模拟用户行为,从入口到出口的完整链路 |
|
||||
|
||||
## 测试原则
|
||||
|
||||
1. **先写测试,再写代码**(推荐 TDD)
|
||||
2. 每个 bug 修复都应伴随对应的回归测试
|
||||
3. 测试用例命名应清晰描述测试场景:`<被测方法>_<场景>_<预期结果>`
|
||||
4. 保持测试独立:单个测试不应依赖其他测试的运行顺序
|
||||
15
test/integration/index.md
Normal file
15
test/integration/index.md
Normal file
@ -0,0 +1,15 @@
|
||||
# test/integration — 集成测试
|
||||
|
||||
测试多个模块之间的协作,或与外部系统(数据库、消息队列、API)的集成。
|
||||
|
||||
## 原则
|
||||
|
||||
- **真实交互** — 连接真实的(或测试专用)外部系统
|
||||
- **关键路径** — 优先覆盖核心业务流程
|
||||
- **可重复** — 每次运行结果一致(注意数据准备和清理)
|
||||
|
||||
## 示例
|
||||
|
||||
- 数据库读写操作的正确性
|
||||
- API 请求/响应完整链路
|
||||
- 消息队列的生产/消费
|
||||
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class AmmunitionSpecRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly AmmunitionSpecRepository _repo;
|
||||
|
||||
public AmmunitionSpecRepositoryTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_repo = new AmmunitionSpecRepository(_db);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_And_GetById_ReturnsSpec()
|
||||
{
|
||||
var spec = new AmmunitionSpec
|
||||
{
|
||||
AerosolType = (int)AerosolType.InertGas,
|
||||
Name = "惰性气体弹-A1",
|
||||
InitialRadius = 50.0,
|
||||
InitialVolume = 500000.0,
|
||||
CoreDensity = 1.2,
|
||||
EffectiveConcentration = 0.05,
|
||||
DispersionRateBase = 5.0,
|
||||
MaxRadius = 200.0,
|
||||
MaxDuration = 60.0
|
||||
};
|
||||
|
||||
_repo.Insert(spec);
|
||||
|
||||
var result = _repo.GetById(spec.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("惰性气体弹-A1", result.Name);
|
||||
Assert.Equal(50.0, result.InitialRadius);
|
||||
Assert.Equal((int)AerosolType.InertGas, result.AerosolType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByAerosolType_FiltersCorrectly()
|
||||
{
|
||||
_repo.Insert(new AmmunitionSpec
|
||||
{
|
||||
Name = "Inert-A", AerosolType = (int)AerosolType.InertGas
|
||||
});
|
||||
_repo.Insert(new AmmunitionSpec
|
||||
{
|
||||
Name = "Inert-B", AerosolType = (int)AerosolType.InertGas
|
||||
});
|
||||
_repo.Insert(new AmmunitionSpec
|
||||
{
|
||||
Name = "Active-A", AerosolType = (int)AerosolType.ActiveMaterial
|
||||
});
|
||||
|
||||
var inertSpecs = _repo.GetByAerosolType((int)AerosolType.InertGas);
|
||||
Assert.Equal(2, inertSpecs.Count);
|
||||
|
||||
var activeSpecs = _repo.GetByAerosolType((int)AerosolType.ActiveMaterial);
|
||||
Assert.Single(activeSpecs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportFromJson_LoadsSpecs()
|
||||
{
|
||||
var json = @"[
|
||||
{""Id"":""spec-1"",""AerosolType"":0,""Name"":""惰性气体弹"",""InitialRadius"":50,""InitialVolume"":500000,""CoreDensity"":1.2,""EdgeDensity"":0.1,""InitialTemperature"":300,""BuoyancyFactor"":0.5,""EffectiveConcentration"":0.05,""DispersionRateBase"":5,""MaxRadius"":200,""MaxDuration"":60,""ParticlesJson"":""{}""},
|
||||
{""Id"":""spec-2"",""AerosolType"":1,""Name"":""活性材料弹"",""InitialRadius"":30,""InitialVolume"":100000,""CoreDensity"":2.0,""EdgeDensity"":0.2,""InitialTemperature"":500,""BuoyancyFactor"":0.3,""EffectiveConcentration"":0.1,""DispersionRateBase"":3,""MaxRadius"":150,""MaxDuration"":45,""ParticlesJson"":""{}""}
|
||||
]";
|
||||
|
||||
var specs = JsonSerializer.Deserialize<AmmunitionSpec[]>(json);
|
||||
Assert.NotNull(specs);
|
||||
_repo.InsertAll(specs);
|
||||
|
||||
var all = _repo.GetAll();
|
||||
Assert.Equal(2, all.Count);
|
||||
Assert.Contains(all, s => s.Name == "惰性气体弹");
|
||||
Assert.Contains(all, s => s.Name == "活性材料弹");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\CounterDrone.Core\CounterDrone.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
89
test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs
Normal file
89
test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class DatabaseManagerTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly IPathProvider _paths;
|
||||
private readonly DatabaseManager _dbManager;
|
||||
|
||||
public DatabaseManagerTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
_paths = new TestPathProvider(_testDir);
|
||||
_dbManager = new DatabaseManager(_paths);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenMainDb_CreatesAllTables()
|
||||
{
|
||||
var db = _dbManager.OpenMainDb();
|
||||
|
||||
// 验证所有 13 张表存在
|
||||
var tables = new[]
|
||||
{
|
||||
"ModelInfo", "AmmunitionSpec", "SimTask", "CombatScene",
|
||||
"ControlZone", "TargetConfig", "EquipmentDeployment",
|
||||
"CloudDispersal", "RoutePlan", "Waypoint", "Group",
|
||||
"SimulationReport", "SimEvent"
|
||||
};
|
||||
|
||||
foreach (var table in tables)
|
||||
{
|
||||
var count = db.ExecuteScalar<int>(
|
||||
$"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='{table}'");
|
||||
Assert.Equal(1, count);
|
||||
}
|
||||
|
||||
db.Close();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenFrameDb_CreatesFrameRecordTable()
|
||||
{
|
||||
var db = _dbManager.OpenFrameDb("task_001");
|
||||
|
||||
var count = db.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='SimFrameRecord'");
|
||||
Assert.Equal(1, count);
|
||||
|
||||
db.Close();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteFrameDb_RemovesFile()
|
||||
{
|
||||
var taskId = "task_del";
|
||||
var db = _dbManager.OpenFrameDb(taskId);
|
||||
db.Close();
|
||||
|
||||
_dbManager.DeleteFrameDb(taskId);
|
||||
|
||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
||||
Assert.False(File.Exists(dbPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenMainDb_IsIdempotent()
|
||||
{
|
||||
var db1 = _dbManager.OpenMainDb();
|
||||
db1.Close();
|
||||
|
||||
// 第二次打开不抛异常
|
||||
var db2 = _dbManager.OpenMainDb();
|
||||
db2.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
test/unit/CounterDrone.Core.Tests/GroupRepositoryTests.cs
Normal file
75
test/unit/CounterDrone.Core.Tests/GroupRepositoryTests.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class GroupRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly GroupRepository _repo;
|
||||
|
||||
public GroupRepositoryTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_repo = new GroupRepository(_db);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_And_GetById_ReturnsGroup()
|
||||
{
|
||||
var group = new Group
|
||||
{
|
||||
Name = "Alpha Fleet",
|
||||
GroupType = (int)GroupType.DroneFleet,
|
||||
Description = "Test drone formation"
|
||||
};
|
||||
|
||||
_repo.Insert(group);
|
||||
|
||||
var result = _repo.GetById(group.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Alpha Fleet", result.Name);
|
||||
Assert.Equal((int)GroupType.DroneFleet, result.GroupType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByType_FiltersCorrectly()
|
||||
{
|
||||
_repo.Insert(new Group { Name = "Fleet A", GroupType = (int)GroupType.DroneFleet });
|
||||
_repo.Insert(new Group { Name = "Fleet B", GroupType = (int)GroupType.DroneFleet });
|
||||
_repo.Insert(new Group { Name = "Equip G1", GroupType = (int)GroupType.EquipmentGroup });
|
||||
|
||||
var fleets = _repo.GetByType((int)GroupType.DroneFleet);
|
||||
Assert.Equal(2, fleets.Count);
|
||||
|
||||
var equipGroups = _repo.GetByType((int)GroupType.EquipmentGroup);
|
||||
Assert.Single(equipGroups);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_RemovesGroup()
|
||||
{
|
||||
var group = new Group { Name = "ToDelete" };
|
||||
_repo.Insert(group);
|
||||
_repo.Delete(group.Id);
|
||||
|
||||
Assert.Null(_repo.GetById(group.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
83
test/unit/CounterDrone.Core.Tests/GroupServiceTests.cs
Normal file
83
test/unit/CounterDrone.Core.Tests/GroupServiceTests.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Services;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class GroupServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly IGroupService _service;
|
||||
|
||||
public GroupServiceTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_service = new GroupService(new Repository.GroupRepository(_db));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateGroup_Valid_ReturnsGroup()
|
||||
{
|
||||
var group = _service.CreateGroup("Alpha", GroupType.DroneFleet, "Test fleet");
|
||||
|
||||
Assert.NotNull(group);
|
||||
Assert.Equal("Alpha", group.Name);
|
||||
Assert.NotEmpty(group.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateGroup_EmptyName_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
_service.CreateGroup("", GroupType.DroneFleet, ""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGroups_WithTypeFilter()
|
||||
{
|
||||
_service.CreateGroup("Fleet A", GroupType.DroneFleet, "");
|
||||
_service.CreateGroup("Fleet B", GroupType.DroneFleet, "");
|
||||
_service.CreateGroup("Equip X", GroupType.EquipmentGroup, "");
|
||||
|
||||
var fleets = _service.GetGroups(GroupType.DroneFleet);
|
||||
Assert.Equal(2, fleets.Count);
|
||||
|
||||
var all = _service.GetGroups(null);
|
||||
Assert.Equal(3, all.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteGroup_RemovesGroup()
|
||||
{
|
||||
var group = _service.CreateGroup("DeleteMe", GroupType.DroneFleet, "");
|
||||
_service.DeleteGroup(group.Id);
|
||||
|
||||
Assert.Null(_service.GetGroup(group.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGroup_ById_ReturnsCorrectGroup()
|
||||
{
|
||||
var created = _service.CreateGroup("Target", GroupType.EquipmentGroup, "desc");
|
||||
var fetched = _service.GetGroup(created.Id);
|
||||
|
||||
Assert.Equal("Target", fetched.Name);
|
||||
Assert.Equal("desc", fetched.Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
test/unit/CounterDrone.Core.Tests/ModelRepositoryTests.cs
Normal file
94
test/unit/CounterDrone.Core.Tests/ModelRepositoryTests.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class ModelRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly ModelRepository _repo;
|
||||
|
||||
public ModelRepositoryTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_repo = new ModelRepository(_db);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_And_GetById_ReturnsModel()
|
||||
{
|
||||
var model = new ModelInfo
|
||||
{
|
||||
Name = "Test Drone",
|
||||
FilePath = "/models/drone.fbx",
|
||||
FileSize = 2.5
|
||||
};
|
||||
|
||||
_repo.Insert(model);
|
||||
|
||||
var result = _repo.GetById(model.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Test Drone", result.Name);
|
||||
Assert.Equal(2.5, result.FileSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ReturnsAllModels()
|
||||
{
|
||||
_repo.Insert(new ModelInfo { Name = "A" });
|
||||
_repo.Insert(new ModelInfo { Name = "B" });
|
||||
_repo.Insert(new ModelInfo { Name = "C" });
|
||||
|
||||
var all = _repo.GetAll();
|
||||
Assert.Equal(3, all.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_RemovesModel()
|
||||
{
|
||||
var model = new ModelInfo { Name = "ToDelete" };
|
||||
_repo.Insert(model);
|
||||
|
||||
_repo.Delete(model.Id);
|
||||
|
||||
var result = _repo.GetById(model.Id);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ModifiesModel()
|
||||
{
|
||||
var model = new ModelInfo { Name = "Original" };
|
||||
_repo.Insert(model);
|
||||
|
||||
model.Name = "Updated";
|
||||
_repo.Update(model);
|
||||
|
||||
var result = _repo.GetById(model.Id);
|
||||
Assert.Equal("Updated", result.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetById_NonExistent_ReturnsNull()
|
||||
{
|
||||
var result = _repo.GetById("nonexistent");
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
102
test/unit/CounterDrone.Core.Tests/ModelServiceTests.cs
Normal file
102
test/unit/CounterDrone.Core.Tests/ModelServiceTests.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Services;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class ModelServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly IModelService _service;
|
||||
private readonly IPathProvider _paths;
|
||||
|
||||
public ModelServiceTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
_paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(_paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_service = new ModelService(new Repository.ModelRepository(_db), _paths);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
private string CreateTempModelFile(string extension)
|
||||
{
|
||||
var path = Path.Combine(_testDir, $"test_model{extension}");
|
||||
File.WriteAllText(path, "dummy content");
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportModel_ValidObj_Succeeds()
|
||||
{
|
||||
var filePath = CreateTempModelFile(".obj");
|
||||
var model = _service.ImportModel(filePath, "TestModel");
|
||||
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("TestModel", model.Name);
|
||||
Assert.NotEmpty(model.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportModel_EmptyName_Throws()
|
||||
{
|
||||
var filePath = CreateTempModelFile(".fbx");
|
||||
Assert.Throws<ArgumentException>(() => _service.ImportModel(filePath, ""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportModel_UnsupportedFormat_Throws()
|
||||
{
|
||||
var filePath = CreateTempModelFile(".xyz");
|
||||
Assert.Throws<ArgumentException>(() => _service.ImportModel(filePath, "BadFormat"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportModel_AllSupportedFormats()
|
||||
{
|
||||
foreach (var ext in new[] { ".fbx", ".obj", ".stl", ".glb", ".gltf" })
|
||||
{
|
||||
var filePath = CreateTempModelFile(ext);
|
||||
var model = _service.ImportModel(filePath, $"Model_{ext}");
|
||||
Assert.NotNull(model);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAllModels_ReturnsImportedModels()
|
||||
{
|
||||
_service.ImportModel(CreateTempModelFile(".fbx"), "A");
|
||||
_service.ImportModel(CreateTempModelFile(".obj"), "B");
|
||||
|
||||
var all = _service.GetAllModels();
|
||||
Assert.Equal(2, all.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteModel_RemovesModel()
|
||||
{
|
||||
var model = _service.ImportModel(CreateTempModelFile(".fbx"), "ToDelete");
|
||||
_service.DeleteModel(model.Id);
|
||||
|
||||
var result = _service.GetModel(model.Id);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModel_NonExistent_ReturnsNull()
|
||||
{
|
||||
Assert.Null(_service.GetModel("not-exist"));
|
||||
}
|
||||
}
|
||||
}
|
||||
17
test/unit/index.md
Normal file
17
test/unit/index.md
Normal file
@ -0,0 +1,17 @@
|
||||
# test/unit — 单元测试
|
||||
|
||||
测试最小单元:单个函数、方法、类。不依赖网络、数据库、文件系统等外部资源。
|
||||
|
||||
## 原则
|
||||
|
||||
- **快速** — 毫秒级完成,可频繁运行
|
||||
- **隔离** — 不依赖外部系统,使用 Mock / Stub 替代
|
||||
- **全覆盖** — 正常路径、异常路径、边界条件
|
||||
|
||||
## 命名示例
|
||||
|
||||
```
|
||||
UserService.test.ts
|
||||
loginHandler_test.go
|
||||
test_calculator.py
|
||||
```
|
||||
Loading…
Reference in New Issue
Block a user