Compare commits
2 Commits
test_20260
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
b8051d2417 | |
|
|
aa97dfcf56 |
|
|
@ -1,43 +1,38 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(etms
|
||||
VERSION 1.0.0
|
||||
DESCRIPTION "Event & Task Management System (ETMS) - Core C++ Component"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
project(etms_cpp VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
# 启用 C++17 标准
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# MSVC UTF-8 support
|
||||
# MSVC UTF-8 编译选项
|
||||
if (MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
endif()
|
||||
|
||||
# ------ Directory variables ------
|
||||
set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
|
||||
# 头文件目录
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
# ------ Main executable ------
|
||||
add_executable(${PROJECT_NAME}
|
||||
${SRC_DIR}/main.cpp
|
||||
${SRC_DIR}/app.cpp
|
||||
# ============================
|
||||
# 主程序
|
||||
# ============================
|
||||
add_executable(etms_app
|
||||
src/main.cpp
|
||||
src/app.cpp
|
||||
src/event_manager.cpp
|
||||
src/task_template_manager.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR})
|
||||
|
||||
# ------ Test executable ------
|
||||
add_executable(${PROJECT_NAME}_test
|
||||
${TEST_DIR}/basic_test.cpp
|
||||
${SRC_DIR}/app.cpp
|
||||
# ============================
|
||||
# 单元测试(使用标准 assert,无需外部依赖)
|
||||
# ============================
|
||||
add_executable(etms_test
|
||||
tests/basic_test.cpp
|
||||
src/app.cpp
|
||||
src/event_manager.cpp
|
||||
src/task_template_manager.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}_test PRIVATE ${INCLUDE_DIR})
|
||||
|
||||
# ------ Custom target: run test ------
|
||||
add_custom_target(check
|
||||
COMMAND ${PROJECT_NAME}_test
|
||||
DEPENDS ${PROJECT_NAME}_test
|
||||
COMMENT "Running unit tests..."
|
||||
)
|
||||
# 测试通过 CTest 驱动
|
||||
enable_testing()
|
||||
add_test(NAME BasicTest COMMAND etms_test)
|
||||
|
|
|
|||
71
README.md
71
README.md
|
|
@ -1,8 +1,10 @@
|
|||
# ETMS — Event & Task Management System (C++ Core Component)
|
||||
# ETMS C++ — 事件与任务管理软件核心库
|
||||
|
||||
## 概述
|
||||
|
||||
ETMS(事件与任务管理系统)是战场任务规划系统(BTPS)的核心组件,实现从战场临机事件感知到作战任务草案生成的全流程支持。本工程为 ETMS 的 C++ 核心库,提供事件模型、任务模板模型、知识库版本管理、事件状态机以及任务请求组装等基础能力。
|
||||
ETMS C++ 是战场任务规划系统 (BTPS) 的核心组件,提供事件管理、
|
||||
任务模板管理等基础能力。本项目以 C++17 编写,通过 CMake 构建,
|
||||
无任何外部第三方依赖,适合作为 ETMS 系统 C++ 后端的基础原型。
|
||||
|
||||
## 工程结构
|
||||
|
||||
|
|
@ -11,47 +13,52 @@ ETMS(事件与任务管理系统)是战场任务规划系统(BTPS)的核
|
|||
├── CMakeLists.txt # CMake 构建配置
|
||||
├── README.md # 本文件
|
||||
├── include/
|
||||
│ └── app.hpp # 公开 API 头文件(事件、模板、版本、状态机、请求组装)
|
||||
│ ├── app.hpp # 应用主入口头文件
|
||||
│ ├── event_manager.hpp # 事件管理器头文件
|
||||
│ └── task_template_manager.hpp # 任务模板管理器头文件
|
||||
├── src/
|
||||
│ ├── app.cpp # 核心逻辑实现
|
||||
│ └── main.cpp # 命令行入口
|
||||
│ ├── main.cpp # 命令行入口
|
||||
│ ├── app.cpp # 应用实现
|
||||
│ ├── event_manager.cpp # 事件管理器实现
|
||||
│ └── task_template_manager.cpp # 任务模板管理器实现
|
||||
└── tests/
|
||||
└── basic_test.cpp # 单元测试(使用 assert)
|
||||
└── basic_test.cpp # 单元测试(标准 assert)
|
||||
```
|
||||
|
||||
## 构建与运行
|
||||
|
||||
### 前提
|
||||
|
||||
- CMake ≥ 3.14
|
||||
- 支持 C++17 的编译器(GCC 8+, Clang 7+, MSVC 2019+)
|
||||
|
||||
### 步骤
|
||||
|
||||
```bash
|
||||
# 1. 进入工程目录
|
||||
cd codegen-runs/codegen_d0add3470891422097f0b7fb8558b115
|
||||
# 配置
|
||||
mkdir -p build && cd build
|
||||
cmake ..
|
||||
|
||||
# 2. 配置 & 构建
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
# 构建主程序
|
||||
cmake --build . --target etms_app
|
||||
|
||||
# 3. 运行主程序
|
||||
./build/etms
|
||||
# 运行主程序
|
||||
./etms_app
|
||||
|
||||
# 4. 运行单元测试
|
||||
./build/etms_test
|
||||
# 或
|
||||
cmake --build build --target check
|
||||
# 构建并运行测试
|
||||
cmake --build . --target etms_test
|
||||
ctest --output-on-failure
|
||||
```
|
||||
|
||||
## 核心模块
|
||||
|
||||
| 模块 | 文件 | 说明 |
|
||||
|----------------|-------------------|------------------------------------|
|
||||
| 事件模型 | `app.hpp` | `Event` 结构体,对应 t_event 表 |
|
||||
| 任务模板模型 | `app.hpp` | `TaskTemplate` 结构体,对应 t_task_template 表 |
|
||||
| 知识库版本 | `app.hpp` | `KbVersion` 结构体,对应 t_kb_version 表 |
|
||||
| 事件状态机 | `app.hpp` | `EventStatus` 枚举与状态流转逻辑 |
|
||||
| 任务请求组装 | `app.hpp` / `.cpp`| 生成任务请求报文的辅助函数 |
|
||||
| 运行时演示 | `main.cpp` | 演示全流程(事件接收→展示→模板选择→请求组装) |
|
||||
### EventManager(事件管理器)
|
||||
- 事件接收与状态流转(Received → Processed → PendingTask → TaskGenerated)
|
||||
- 事件列表分页查询与排序
|
||||
- 事件等级颜色标识
|
||||
|
||||
### TaskTemplateManager(任务模板管理器)
|
||||
- 模板增删改查
|
||||
- 版本管理与知识库版本切换
|
||||
- 模板排序与展示
|
||||
|
||||
## 数据模型
|
||||
|
||||
| 模型 | 说明 |
|
||||
|------|------|
|
||||
| `Event` | 战场事件,含 ID、类型、时间、等级、坐标等 |
|
||||
| `TaskTemplate` | 任务模板,含 ID、名称、版本、元数据等 |
|
||||
| `KbVersion` | 知识库版本,含版本号、发布日期、活跃标志 |
|
||||
|
|
|
|||
22553
events.ndjson
22553
events.ndjson
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
190
include/app.hpp
190
include/app.hpp
|
|
@ -2,138 +2,68 @@
|
|||
#define ETMS_APP_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include "event_manager.hpp"
|
||||
#include "task_template_manager.hpp"
|
||||
|
||||
namespace etms {
|
||||
/**
|
||||
* @brief ETMS 应用主类
|
||||
*
|
||||
* 负责初始化各核心管理器,提供应用生命周期控制。
|
||||
* 封装事件管理与任务模板管理的顶层协调逻辑。
|
||||
*/
|
||||
class App {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造 App 实例
|
||||
* @param appName 应用名称标识
|
||||
*/
|
||||
explicit App(const std::string& appName);
|
||||
|
||||
/// @brief 事件状态枚举,对应需求文档中 t_event.status 的状态流转。
|
||||
enum class EventStatus {
|
||||
Received, ///< 刚接收,待处理
|
||||
Processed, ///< 已处理
|
||||
PendingTask, ///< 待映射任务
|
||||
Rejected, ///< 已拒绝
|
||||
TaskGenerated ///< 任务已生成
|
||||
/// @brief 析构函数,释放内部资源
|
||||
~App();
|
||||
|
||||
// 禁止拷贝
|
||||
App(const App&) = delete;
|
||||
App& operator=(const App&) = delete;
|
||||
|
||||
/// @brief 允许移动
|
||||
App(App&&) noexcept;
|
||||
App& operator=(App&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 初始化应用,创建事件管理器与任务模板管理器
|
||||
* @return true 初始化成功,false 初始化失败
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/**
|
||||
* @brief 运行应用主循环(此处为示例演示)
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 获取事件管理器引用
|
||||
* @return EventManager&
|
||||
*/
|
||||
EventManager& getEventManager();
|
||||
|
||||
/**
|
||||
* @brief 获取任务模板管理器引用
|
||||
* @return TaskTemplateManager&
|
||||
*/
|
||||
TaskTemplateManager& getTaskTemplateManager();
|
||||
|
||||
/**
|
||||
* @brief 打印应用状态摘要
|
||||
*/
|
||||
void printSummary() const;
|
||||
|
||||
private:
|
||||
std::string m_appName; ///< 应用名称
|
||||
std::unique_ptr<EventManager> m_eventManager; ///< 事件管理器
|
||||
std::unique_ptr<TaskTemplateManager> m_templateManager; ///< 任务模板管理器
|
||||
bool m_initialized = false; ///< 初始化标志
|
||||
};
|
||||
|
||||
/// @brief 将 EventStatus 转换为可读字符串。
|
||||
/// @param status 事件状态枚举值
|
||||
/// @return 状态字符串,例如 "Received"
|
||||
const char* EventStatusToString(EventStatus status) noexcept;
|
||||
|
||||
/// @brief 事件结构体,对应数据库 t_event 表。
|
||||
struct Event {
|
||||
int64_t id{}; ///< 自增主键
|
||||
std::string event_id; ///< 全局唯一标识
|
||||
std::string event_type; ///< 事件类型:侦察、打击、预警等
|
||||
std::time_t timestamp{}; ///< 事件发生时间(Unix 秒)
|
||||
int level{}; ///< 事件等级(紧急程度)
|
||||
double longitude{}; ///< 经度
|
||||
double latitude{}; ///< 纬度
|
||||
std::string description; ///< 事件描述文本
|
||||
EventStatus status{EventStatus::Received}; ///< 当前状态
|
||||
std::time_t create_time{}; ///< 记录入库时间
|
||||
};
|
||||
|
||||
/// @brief 任务模板结构体,对应数据库 t_task_template 表。
|
||||
struct TaskTemplate {
|
||||
std::string template_id; ///< 模板唯一标识
|
||||
std::string name; ///< 显示名称
|
||||
std::string version; ///< 关联知识库版本号
|
||||
std::string content_path; ///< MinIO 中模板文件存储路径
|
||||
std::string applicable_type;///< 可匹配的事件类型
|
||||
std::time_t create_time{}; ///< 模板导入时间
|
||||
};
|
||||
|
||||
/// @brief 知识库版本结构体,对应数据库 t_kb_version 表。
|
||||
struct KbVersion {
|
||||
std::string version_id; ///< 版本 ID,如 KB-V1.0
|
||||
std::string version_name; ///< 友好显示名
|
||||
std::time_t release_time{}; ///< 版本发布时间
|
||||
std::string status; ///< "Active" 或 "Deprecated"
|
||||
};
|
||||
|
||||
/// @brief 事件接收的结果,包含 ACK 信息。
|
||||
struct EventAck {
|
||||
bool accepted{false}; ///< 是否接受
|
||||
std::string event_id; ///< 对应事件 ID
|
||||
std::string message; ///< 提示消息
|
||||
};
|
||||
|
||||
/// @brief 任务生成请求报文结构。
|
||||
struct TaskGenerationRequest {
|
||||
std::string request_id; ///< 请求唯一标识
|
||||
std::string event_id; ///< 引用的事件 ID
|
||||
std::string template_id; ///< 选定的模板 ID
|
||||
std::string kb_version_id; ///< 知识库版本 ID
|
||||
std::map<std::string, std::string> params; ///< 初始参数键值对
|
||||
std::time_t request_time{}; ///< 请求生成时间
|
||||
};
|
||||
|
||||
// ========================
|
||||
// 公开 API 函数声明
|
||||
// ========================
|
||||
|
||||
/// @brief 校验并解析原始 JSON 事件数据(模拟器),返回 ACK。
|
||||
/// @param raw_json 原始 JSON 字符串(最大 4KB)
|
||||
/// @return EventAck 结构体,包含接受状态与 event_id
|
||||
EventAck ReceiveRawEvent(const std::string& raw_json);
|
||||
|
||||
/// @brief 清洗并分类打标一个事件,将其状态置为 Processed。
|
||||
/// @param evt 事件引用(会被修改)
|
||||
/// @return true 如果处理成功
|
||||
bool ProcessEvent(Event& evt);
|
||||
|
||||
/// @brief 查询所有已接收事件(模拟数据库查询)。
|
||||
/// @return 事件列表(按创建时间倒序)
|
||||
std::vector<Event> QueryAllEvents();
|
||||
|
||||
/// @brief 根据事件等级筛选紧迫事件(level >= 8 视为紧迫)。
|
||||
/// @param events 事件列表
|
||||
/// @return 紧迫事件列表
|
||||
std::vector<Event> FilterUrgentEvents(const std::vector<Event>& events);
|
||||
|
||||
/// @brief 根据事件特征推荐匹配的模板(自主执行模式模拟)。
|
||||
/// @param evt 事件
|
||||
/// @param templates 可用模板列表
|
||||
/// @return 推荐模板的 ID;若无匹配返回空字符串
|
||||
std::string RecommendTemplate(const Event& evt, const std::vector<TaskTemplate>& templates);
|
||||
|
||||
/// @brief 组装任务生成请求报文。
|
||||
/// @param event_id 事件 ID
|
||||
/// @param template_id 模板 ID
|
||||
/// @param kb_ver 知识库版本 ID
|
||||
/// @param params 初始参数
|
||||
/// @return 组装好的 TaskGenerationRequest
|
||||
TaskGenerationRequest BuildTaskRequest(
|
||||
const std::string& event_id,
|
||||
const std::string& template_id,
|
||||
const std::string& kb_ver,
|
||||
const std::map<std::string, std::string>& params);
|
||||
|
||||
/// @brief 获取可用知识库版本列表(模拟)。
|
||||
/// @return 知识库版本列表
|
||||
std::vector<KbVersion> GetAvailableKbVersions();
|
||||
|
||||
/// @brief 获取可用任务模板列表(模拟)。
|
||||
/// @return 任务模板列表
|
||||
std::vector<TaskTemplate> GetAvailableTemplates();
|
||||
|
||||
/// @brief 将事件状态推进到下一个状态(状态机模拟)。
|
||||
/// @param evt 事件引用(会被修改)
|
||||
/// @return true 如果状态转换合法
|
||||
bool AdvanceEventStatus(Event& evt);
|
||||
|
||||
/// @brief 模拟事件排序(按等级降序)。
|
||||
/// @param events 事件列表(会被排序)
|
||||
void SortEventsByLevelDesc(std::vector<Event>& events);
|
||||
|
||||
/// @brief 模拟事件排序(按时间升序)。
|
||||
/// @param events 事件列表(会被排序)
|
||||
void SortEventsByTimeAsc(std::vector<Event>& events);
|
||||
|
||||
} // namespace etms
|
||||
|
||||
#endif // ETMS_APP_HPP
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
#ifndef ETMS_EVENT_MANAGER_HPP
|
||||
#define ETMS_EVENT_MANAGER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief 事件状态枚举
|
||||
*
|
||||
* 对应 t_event 表 status 字段的状态流转路径:
|
||||
* Received → Processed → PendingTask → TaskGenerated
|
||||
*/
|
||||
enum class EventStatus {
|
||||
Received, ///< 已接收,尚未处理
|
||||
Processed, ///< 已处理完毕
|
||||
PendingTask, ///< 待生成任务
|
||||
TaskGenerated ///< 任务已生成
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 事件等级枚举
|
||||
*
|
||||
* 区分战场事件的紧急程度与重要性。
|
||||
*/
|
||||
enum class EventLevel {
|
||||
Info = 0, ///< 信息级
|
||||
Warning = 1, ///< 警告级
|
||||
Critical = 2, ///< 危急级
|
||||
Fatal = 3 ///< 致命级
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 战场事件数据结构
|
||||
*
|
||||
* 对应分析文档中 t_event 表的核心字段。
|
||||
*/
|
||||
struct Event {
|
||||
uint64_t id; ///< 自增主键 ID
|
||||
std::string eventId; ///< 事件唯一标识 (event_id)
|
||||
std::string eventType; ///< 事件类型 (event_type)
|
||||
int64_t timestamp; ///< 事件发生时间戳 (毫秒)
|
||||
EventLevel level; ///< 事件等级
|
||||
double longitude; ///< 经度
|
||||
double latitude; ///< 纬度
|
||||
std::string description; ///< 事件描述
|
||||
EventStatus status; ///< 当前处理状态
|
||||
int64_t createTime; ///< 记录创建时间戳 (毫秒)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 分页查询结果
|
||||
*/
|
||||
struct PageResult {
|
||||
std::vector<Event> items; ///< 当前页数据
|
||||
size_t totalCount; ///< 总记录数
|
||||
size_t pageIndex; ///< 当前页码 (从1开始)
|
||||
size_t pageSize; ///< 每页大小
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 事件管理器
|
||||
*
|
||||
* 负责战场事件数据的接收、处理、存储与查询。
|
||||
* 支持事件状态流转、分页查询、按等级过滤和自定义排序。
|
||||
*/
|
||||
class EventManager {
|
||||
public:
|
||||
/// @brief 默认构造函数
|
||||
EventManager();
|
||||
|
||||
/// @brief 虚析构函数
|
||||
virtual ~EventManager();
|
||||
|
||||
/// @brief 禁止拷贝
|
||||
EventManager(const EventManager&) = delete;
|
||||
EventManager& operator=(const EventManager&) = delete;
|
||||
|
||||
/// @brief 允许移动
|
||||
EventManager(EventManager&&) noexcept;
|
||||
EventManager& operator=(EventManager&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 接收并解析一条原始事件
|
||||
*
|
||||
* 模拟从消息队列(MQTT/Kafka)接收 JSON 事件后的处理入口。
|
||||
*
|
||||
* @param rawJson 原始 JSON 字符串(简化处理)
|
||||
* @return true 解析成功,事件已入库
|
||||
* @return false 格式无效或字段缺失
|
||||
*/
|
||||
bool receiveEvent(const std::string& rawJson);
|
||||
|
||||
/**
|
||||
* @brief 更新指定事件的状态
|
||||
*
|
||||
* @param eventId 事件唯一 ID
|
||||
* @param newStatus 目标状态
|
||||
* @return true 更新成功
|
||||
* @return false 未找到该事件
|
||||
*/
|
||||
bool updateStatus(const std::string& eventId, EventStatus newStatus);
|
||||
|
||||
/**
|
||||
* @brief 分页查询事件列表
|
||||
*
|
||||
* @param page 页码(从1开始)
|
||||
* @param pageSize 每页条数
|
||||
* @param levelFilter 按等级过滤,传入 nullptr 表示不过滤
|
||||
* @return PageResult 分页结果
|
||||
*/
|
||||
PageResult queryEvents(size_t page, size_t pageSize,
|
||||
const EventLevel* levelFilter = nullptr) const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前事件总数
|
||||
* @return size_t
|
||||
*/
|
||||
size_t totalEvents() const;
|
||||
|
||||
/**
|
||||
* @brief 根据事件 ID 查找事件
|
||||
* @param eventId 事件唯一 ID
|
||||
* @return const Event* 找到返回指针,否则返回 nullptr
|
||||
*/
|
||||
const Event* findEvent(const std::string& eventId) const;
|
||||
|
||||
/**
|
||||
* @brief 获取事件等级的字符串表示
|
||||
* @param level 事件等级
|
||||
* @return const char* 中文描述
|
||||
*/
|
||||
static const char* levelToString(EventLevel level) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 获取事件状态的字符串表示
|
||||
* @param status 事件状态
|
||||
* @return const char*
|
||||
*/
|
||||
static const char* statusToString(EventStatus status) noexcept;
|
||||
|
||||
private:
|
||||
std::vector<Event> m_events; ///< 事件存储容器(模拟数据库 t_event 表)
|
||||
uint64_t m_nextId = 1; ///< 自增 ID 计数器
|
||||
|
||||
/**
|
||||
* @brief 生成下一个主键 ID
|
||||
* @return uint64_t
|
||||
*/
|
||||
uint64_t nextId();
|
||||
|
||||
/**
|
||||
* @brief 获取当前毫秒时间戳
|
||||
* @return int64_t
|
||||
*/
|
||||
static int64_t nowMs() noexcept;
|
||||
};
|
||||
|
||||
#endif // ETMS_EVENT_MANAGER_HPP
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
#ifndef ETMS_TASK_TEMPLATE_MANAGER_HPP
|
||||
#define ETMS_TASK_TEMPLATE_MANAGER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief 任务模板数据结构
|
||||
*
|
||||
* 对应分析文档中 t_task_template 表的核心字段。
|
||||
* 完整模板文件存储于 MinIO 对象存储,本地仅维护元数据。
|
||||
*/
|
||||
struct TaskTemplate {
|
||||
std::string templateId; ///< 模板唯一标识
|
||||
std::string name; ///< 模板名称
|
||||
std::string version; ///< 版本号
|
||||
std::string contentPath; ///< MinIO 对象存储路径
|
||||
std::string metadata; ///< 元数据(JSON 字符串)
|
||||
int64_t createTime; ///< 创建时间戳 (毫秒)
|
||||
uint64_t usageCount; ///< 使用频率计数
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 知识库版本数据结构
|
||||
*
|
||||
* 对应分析文档中 t_kb_version 表的字段。
|
||||
*/
|
||||
struct KbVersion {
|
||||
std::string versionId; ///< 版本标识
|
||||
int64_t releaseDate; ///< 发布日期 (毫秒时间戳)
|
||||
std::string description; ///< 版本描述
|
||||
bool isActive; ///< 是否为当前活跃版本
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 任务模板管理器
|
||||
*
|
||||
* 负责任务模板的增删改查、版本管理、排序展示。
|
||||
* 支持增量更新和按名称/使用频率排序。
|
||||
*/
|
||||
class TaskTemplateManager {
|
||||
public:
|
||||
/// @brief 默认构造函数
|
||||
TaskTemplateManager();
|
||||
|
||||
/// @brief 虚析构函数
|
||||
virtual ~TaskTemplateManager();
|
||||
|
||||
/// @brief 禁止拷贝
|
||||
TaskTemplateManager(const TaskTemplateManager&) = delete;
|
||||
TaskTemplateManager& operator=(const TaskTemplateManager&) = delete;
|
||||
|
||||
/// @brief 允许移动
|
||||
TaskTemplateManager(TaskTemplateManager&&) noexcept;
|
||||
TaskTemplateManager& operator=(TaskTemplateManager&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 添加或更新一个任务模板(增量更新)
|
||||
*
|
||||
* 如果 templateId 已存在则更新,否则新增。
|
||||
*
|
||||
* @param tmpl 模板对象
|
||||
* @return true 操作成功
|
||||
*/
|
||||
bool upsertTemplate(const TaskTemplate& tmpl);
|
||||
|
||||
/**
|
||||
* @brief 按模板 ID 删除模板
|
||||
* @param templateId 模板唯一标识
|
||||
* @return true 删除成功,false 未找到
|
||||
*/
|
||||
bool removeTemplate(const std::string& templateId);
|
||||
|
||||
/**
|
||||
* @brief 根据模板 ID 查找模板
|
||||
* @param templateId 模板唯一标识
|
||||
* @return const TaskTemplate* 找到返回指针,否则返回 nullptr
|
||||
*/
|
||||
const TaskTemplate* findTemplate(const std::string& templateId) const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有模板列表
|
||||
* @return const std::vector<TaskTemplate>&
|
||||
*/
|
||||
const std::vector<TaskTemplate>& getAllTemplates() const;
|
||||
|
||||
/**
|
||||
* @brief 按名称排序获取模板列表
|
||||
* @param ascending true: 升序, false: 降序
|
||||
* @return std::vector<TaskTemplate> 排序后的副本
|
||||
*/
|
||||
std::vector<TaskTemplate> getTemplatesSortedByName(bool ascending = true) const;
|
||||
|
||||
/**
|
||||
* @brief 按使用频率排序获取模板列表
|
||||
* @param ascending true: 升序, false: 降序
|
||||
* @return std::vector<TaskTemplate> 排序后的副本
|
||||
*/
|
||||
std::vector<TaskTemplate> getTemplatesSortedByUsage(bool ascending = false) const;
|
||||
|
||||
// ---- 知识库版本管理 ----
|
||||
|
||||
/**
|
||||
* @brief 添加知识库版本
|
||||
* @param version 版本对象
|
||||
* @return true 添加成功
|
||||
*/
|
||||
bool addKbVersion(const KbVersion& version);
|
||||
|
||||
/**
|
||||
* @brief 获取所有知识库版本列表
|
||||
* @return const std::vector<KbVersion>&
|
||||
*/
|
||||
const std::vector<KbVersion>& getAllKbVersions() const;
|
||||
|
||||
/**
|
||||
* @brief 设置指定版本为活跃版本
|
||||
* @param versionId 版本标识
|
||||
* @return true 设置成功,false 未找到该版本
|
||||
*/
|
||||
bool activateKbVersion(const std::string& versionId);
|
||||
|
||||
/**
|
||||
* @brief 获取当前活跃的知识库版本
|
||||
* @return const KbVersion* 找到返回指针,否则返回 nullptr
|
||||
*/
|
||||
const KbVersion* getActiveKbVersion() const;
|
||||
|
||||
/**
|
||||
* @brief 获取模板总数
|
||||
* @return size_t
|
||||
*/
|
||||
size_t totalTemplates() const;
|
||||
|
||||
private:
|
||||
std::vector<TaskTemplate> m_templates; ///< 模板存储容器(模拟 t_task_template 表)
|
||||
std::vector<KbVersion> m_kbVersions; ///< 知识库版本存储(模拟 t_kb_version 表)
|
||||
|
||||
/**
|
||||
* @brief 获取当前毫秒时间戳
|
||||
* @return int64_t
|
||||
*/
|
||||
static int64_t nowMs() noexcept;
|
||||
};
|
||||
|
||||
#endif // ETMS_TASK_TEMPLATE_MANAGER_HPP
|
||||
330
src/app.cpp
330
src/app.cpp
|
|
@ -1,260 +1,102 @@
|
|||
#include "app.hpp"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace etms {
|
||||
|
||||
// ========================
|
||||
// 辅助函数(模块内部)
|
||||
// ========================
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief 生成简易 UUID(仅用于演示)。
|
||||
std::string GenerateSimpleId() {
|
||||
static int64_t counter = 0;
|
||||
std::ostringstream oss;
|
||||
oss << "EVT-" << std::time(nullptr) << "-" << ++counter;
|
||||
return oss.str();
|
||||
App::App(const std::string& appName)
|
||||
: m_appName(appName) {
|
||||
}
|
||||
|
||||
/// @brief 生成请求 ID。
|
||||
std::string GenerateRequestId() {
|
||||
static int64_t counter = 0;
|
||||
std::ostringstream oss;
|
||||
oss << "REQ-" << std::time(nullptr) << "-" << ++counter;
|
||||
return oss.str();
|
||||
}
|
||||
App::~App() = default;
|
||||
|
||||
/// @brief 检查字符串是否非空且不超长。
|
||||
bool IsValidJsonField(const std::string& field, size_t max_len = 4096) {
|
||||
return !field.empty() && field.size() <= max_len;
|
||||
}
|
||||
App::App(App&&) noexcept = default;
|
||||
App& App::operator=(App&&) noexcept = default;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ========================
|
||||
// 公开 API 实现
|
||||
// ========================
|
||||
|
||||
const char* EventStatusToString(EventStatus status) noexcept {
|
||||
switch (status) {
|
||||
case EventStatus::Received: return "Received";
|
||||
case EventStatus::Processed: return "Processed";
|
||||
case EventStatus::PendingTask: return "Pending Task";
|
||||
case EventStatus::Rejected: return "Rejected";
|
||||
case EventStatus::TaskGenerated: return "Task Generated";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief 模拟解析原始 JSON 事件包,校验字段完整性并返回 ACK。
|
||||
/// @details 从原始 JSON 字符串中提取 event_id、event_type、timestamp、level、
|
||||
/// longitude、latitude、description 等字段。若缺少 event_id 或 event_type
|
||||
/// 则拒绝接收。成功时返回 accepted=true 的 EventAck。
|
||||
EventAck ReceiveRawEvent(const std::string& raw_json) {
|
||||
EventAck ack;
|
||||
// 模拟 JSON 解析(简易占位实现)
|
||||
// 实际场景应使用 nlohmann/json 或 rapidjson 等进行解析
|
||||
if (raw_json.empty() || raw_json.size() > 4096) {
|
||||
ack.accepted = false;
|
||||
ack.event_id = "";
|
||||
ack.message = "Invalid payload: empty or exceeds 4KB";
|
||||
return ack;
|
||||
}
|
||||
|
||||
// 模拟提取 event_id(查找 "event_id":"..." 模式)
|
||||
// 生产环境应接入正式的 JSON 解析器
|
||||
auto pos = raw_json.find("\"event_id\"");
|
||||
if (pos == std::string::npos) {
|
||||
ack.accepted = false;
|
||||
ack.message = "Missing required field: event_id";
|
||||
return ack;
|
||||
}
|
||||
|
||||
// 粗略提取 event_id 值(演示用)
|
||||
auto val_start = raw_json.find('"', pos + 10);
|
||||
if (val_start == std::string::npos) {
|
||||
ack.accepted = false;
|
||||
ack.message = "Malformed event_id value";
|
||||
return ack;
|
||||
}
|
||||
auto val_end = raw_json.find('"', val_start + 1);
|
||||
if (val_end == std::string::npos) {
|
||||
ack.accepted = false;
|
||||
ack.message = "Malformed event_id value";
|
||||
return ack;
|
||||
}
|
||||
ack.event_id = raw_json.substr(val_start + 1, val_end - val_start - 1);
|
||||
|
||||
if (!IsValidJsonField(ack.event_id, 64)) {
|
||||
ack.accepted = false;
|
||||
ack.message = "Invalid event_id length";
|
||||
return ack;
|
||||
}
|
||||
|
||||
ack.accepted = true;
|
||||
ack.message = "ACK: event received successfully";
|
||||
return ack;
|
||||
}
|
||||
|
||||
/// @brief 对事件进行清洗、格式化、分类打标,状态置为 Processed。
|
||||
bool ProcessEvent(Event& evt) {
|
||||
if (evt.status != EventStatus::Received) {
|
||||
return false; // 只有 Received 状态才能处理
|
||||
}
|
||||
// 模拟清洗:去除描述文本首尾空白
|
||||
auto& desc = evt.description;
|
||||
if (!desc.empty()) {
|
||||
// 去除尾部空白
|
||||
while (!desc.empty() && (desc.back() == ' ' || desc.back() == '\t' ||
|
||||
desc.back() == '\n' || desc.back() == '\r')) {
|
||||
desc.pop_back();
|
||||
}
|
||||
// 去除头部空白
|
||||
size_t front = 0;
|
||||
while (front < desc.size() && (desc[front] == ' ' || desc[front] == '\t' ||
|
||||
desc[front] == '\n' || desc[front] == '\r')) {
|
||||
++front;
|
||||
}
|
||||
if (front > 0) {
|
||||
desc = desc.substr(front);
|
||||
}
|
||||
}
|
||||
// 模拟分类打标:根据 event_type 前缀标记
|
||||
// 此处仅推进状态
|
||||
evt.status = EventStatus::Processed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 返回一个模拟的事件列表(按创建时间倒序)。
|
||||
std::vector<Event> QueryAllEvents() {
|
||||
std::vector<Event> events;
|
||||
std::time_t now = std::time(nullptr);
|
||||
|
||||
events.push_back({
|
||||
1, "EVT-001", "侦察", now - 100, 9, 116.397, 39.908,
|
||||
"敌方装甲部队在坐标区域集结", EventStatus::Received, now - 100
|
||||
});
|
||||
events.push_back({
|
||||
2, "EVT-002", "打击", now - 80, 8, 116.400, 39.910,
|
||||
"雷达探测到敌方火力阵地坐标已确认", EventStatus::Processed, now - 80
|
||||
});
|
||||
events.push_back({
|
||||
3, "EVT-003", "预警", now - 60, 7, 116.380, 39.900,
|
||||
"探测到不明飞行物接近防空识别区", EventStatus::PendingTask, now - 60
|
||||
});
|
||||
events.push_back({
|
||||
4, "EVT-004", "侦察", now - 40, 6, 116.410, 39.915,
|
||||
"情报显示敌军后勤补给线活动频繁", EventStatus::Received, now - 40
|
||||
});
|
||||
events.push_back({
|
||||
5, "EVT-005", "打击", now - 20, 10, 116.390, 39.905,
|
||||
"紧急:敌军导弹发射阵地已定位,请求即时火力覆盖", EventStatus::Received, now - 20
|
||||
});
|
||||
|
||||
// 已按 create_time 降序
|
||||
return events;
|
||||
}
|
||||
|
||||
/// @brief 筛选出等级 >= 8 的紧迫事件。
|
||||
std::vector<Event> FilterUrgentEvents(const std::vector<Event>& events) {
|
||||
std::vector<Event> result;
|
||||
std::copy_if(events.begin(), events.end(), std::back_inserter(result),
|
||||
[](const Event& e) { return e.level >= 8; });
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief 根据事件特征推荐最匹配的模板。
|
||||
std::string RecommendTemplate(const Event& evt, const std::vector<TaskTemplate>& templates) {
|
||||
// 推荐逻辑:按 applicable_type 与事件类型匹配
|
||||
for (const auto& tpl : templates) {
|
||||
if (tpl.applicable_type == evt.event_type) {
|
||||
return tpl.template_id;
|
||||
}
|
||||
}
|
||||
// 若无精确匹配,返回第一个模板 ID 作为兜底
|
||||
if (!templates.empty()) {
|
||||
return templates.front().template_id;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief 组装任务生成请求报文。
|
||||
TaskGenerationRequest BuildTaskRequest(
|
||||
const std::string& event_id,
|
||||
const std::string& template_id,
|
||||
const std::string& kb_ver,
|
||||
const std::map<std::string, std::string>& params)
|
||||
{
|
||||
TaskGenerationRequest req;
|
||||
req.request_id = GenerateRequestId();
|
||||
req.event_id = event_id;
|
||||
req.template_id = template_id;
|
||||
req.kb_version_id = kb_ver;
|
||||
req.params = params;
|
||||
req.request_time = std::time(nullptr);
|
||||
return req;
|
||||
}
|
||||
|
||||
/// @brief 获取可用知识库版本列表(模拟数据)。
|
||||
std::vector<KbVersion> GetAvailableKbVersions() {
|
||||
return {
|
||||
{"KB-V1.0", "知识库基础版 V1.0", 1700000000, "Active"},
|
||||
{"KB-V1.1", "知识库增强版 V1.1", 1700100000, "Active"},
|
||||
{"KB-V2.0", "知识库全面版 V2.0", 1700200000, "Deprecated"}
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief 获取可用任务模板列表(模拟数据)。
|
||||
std::vector<TaskTemplate> GetAvailableTemplates() {
|
||||
return {
|
||||
{"TMPL-RECON-001", "侦察任务模板", "KB-V1.1", "/templates/recon_v2.json", "侦察", 1700000100},
|
||||
{"TMPL-STRIKE-001","火力打击模板", "KB-V1.1", "/templates/strike_v1.json", "打击", 1700000200},
|
||||
{"TMPL-WARN-001", "预警响应模板", "KB-V1.0", "/templates/warn_v1.json", "预警", 1700000300}
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief 将事件状态推进到下一个合法状态。
|
||||
bool AdvanceEventStatus(Event& evt) {
|
||||
switch (evt.status) {
|
||||
case EventStatus::Received:
|
||||
evt.status = EventStatus::Processed;
|
||||
bool App::initialize() {
|
||||
try {
|
||||
m_eventManager = std::make_unique<EventManager>();
|
||||
m_templateManager = std::make_unique<TaskTemplateManager>();
|
||||
m_initialized = true;
|
||||
return true;
|
||||
case EventStatus::Processed:
|
||||
evt.status = EventStatus::PendingTask;
|
||||
return true;
|
||||
case EventStatus::PendingTask:
|
||||
evt.status = EventStatus::TaskGenerated;
|
||||
return true;
|
||||
case EventStatus::Rejected:
|
||||
// Rejected 不可再前进
|
||||
return false;
|
||||
case EventStatus::TaskGenerated:
|
||||
// 已终态
|
||||
return false;
|
||||
default:
|
||||
} catch (...) {
|
||||
m_initialized = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief 按事件等级降序排序。
|
||||
void SortEventsByLevelDesc(std::vector<Event>& events) {
|
||||
std::sort(events.begin(), events.end(),
|
||||
[](const Event& a, const Event& b) {
|
||||
return a.level > b.level;
|
||||
});
|
||||
void App::run() {
|
||||
if (!m_initialized) {
|
||||
std::cerr << "[ERROR] App not initialized.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "=== ETMS C++ Application: " << m_appName << " ===\n\n";
|
||||
|
||||
// ----- 事件管理演示 -----
|
||||
std::cout << "--- 接收模拟事件 ---\n";
|
||||
m_eventManager->receiveEvent("{\"eventId\":\"E001\",\"type\":\"air_raid\",\"level\":3,\"desc\":\"敌机突袭警报\"}");
|
||||
m_eventManager->receiveEvent("{\"eventId\":\"E002\",\"type\":\"recon\",\"level\":1,\"desc\":\"侦察兵发现可疑目标\"}");
|
||||
m_eventManager->receiveEvent("{\"eventId\":\"E003\",\"type\":\"supply\",\"level\":0,\"desc\":\"后勤补给到达\"}");
|
||||
|
||||
std::cout << "总事件数: " << m_eventManager->totalEvents() << "\n\n";
|
||||
|
||||
// 状态流转
|
||||
m_eventManager->updateStatus("E001", EventStatus::Processed);
|
||||
m_eventManager->updateStatus("E002", EventStatus::Processed);
|
||||
m_eventManager->updateStatus("E002", EventStatus::PendingTask);
|
||||
|
||||
// 查询展示
|
||||
auto page = m_eventManager->queryEvents(1, 5);
|
||||
std::cout << "--- 第1页事件列表 (共" << page.totalCount << "条) ---\n";
|
||||
for (const auto& evt : page.items) {
|
||||
std::cout << " [" << EventManager::levelToString(evt.level)
|
||||
<< "] " << evt.eventId << " | "
|
||||
<< evt.eventType << " | "
|
||||
<< EventManager::statusToString(evt.status) << "\n";
|
||||
}
|
||||
|
||||
// ----- 模板管理演示 -----
|
||||
std::cout << "\n--- 添加任务模板 ---\n";
|
||||
TaskTemplate tmpl1{"T001", "防空反导方案", "1.0", "/minio/templates/T001.json",
|
||||
"{\"type\":\"air_defense\"}", 0, 0};
|
||||
TaskTemplate tmpl2{"T002", "侦察打击方案", "1.0", "/minio/templates/T002.json",
|
||||
"{\"type\":\"strike\"}", 0, 0};
|
||||
m_templateManager->upsertTemplate(tmpl1);
|
||||
m_templateManager->upsertTemplate(tmpl2);
|
||||
|
||||
KbVersion kb1{"KB-2024-01", 1704067200000LL, "2024年基础知识库", true};
|
||||
m_templateManager->addKbVersion(kb1);
|
||||
|
||||
std::cout << "模板总数: " << m_templateManager->totalTemplates() << "\n";
|
||||
|
||||
auto sorted = m_templateManager->getTemplatesSortedByName(true);
|
||||
std::cout << "--- 按名称排序的模板 ---\n";
|
||||
for (const auto& t : sorted) {
|
||||
std::cout << " " << t.templateId << " | " << t.name << " v" << t.version << "\n";
|
||||
}
|
||||
|
||||
auto* activeKb = m_templateManager->getActiveKbVersion();
|
||||
if (activeKb) {
|
||||
std::cout << "\n当前活跃知识库版本: " << activeKb->versionId
|
||||
<< " - " << activeKb->description << "\n";
|
||||
}
|
||||
|
||||
std::cout << "\n=== 演示结束 ===\n";
|
||||
}
|
||||
|
||||
/// @brief 按事件时间升序排序。
|
||||
void SortEventsByTimeAsc(std::vector<Event>& events) {
|
||||
std::sort(events.begin(), events.end(),
|
||||
[](const Event& a, const Event& b) {
|
||||
return a.timestamp < b.timestamp;
|
||||
});
|
||||
EventManager& App::getEventManager() {
|
||||
return *m_eventManager;
|
||||
}
|
||||
|
||||
} // namespace etms
|
||||
TaskTemplateManager& App::getTaskTemplateManager() {
|
||||
return *m_templateManager;
|
||||
}
|
||||
|
||||
void App::printSummary() const {
|
||||
if (!m_initialized) {
|
||||
std::cout << "App \"" << m_appName << "\": 未初始化\n";
|
||||
return;
|
||||
}
|
||||
std::cout << "App \"" << m_appName << "\" | 事件: "
|
||||
<< m_eventManager->totalEvents()
|
||||
<< " | 模板: " << m_templateManager->totalTemplates() << "\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
#include "event_manager.hpp"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
EventManager::EventManager() = default;
|
||||
EventManager::~EventManager() = default;
|
||||
EventManager::EventManager(EventManager&&) noexcept = default;
|
||||
EventManager& EventManager::operator=(EventManager&&) noexcept = default;
|
||||
|
||||
int64_t EventManager::nowMs() noexcept {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
).count();
|
||||
}
|
||||
|
||||
uint64_t EventManager::nextId() {
|
||||
return m_nextId++;
|
||||
}
|
||||
|
||||
bool EventManager::receiveEvent(const std::string& rawJson) {
|
||||
// 简化 JSON 校验:必须包含 eventId 字段
|
||||
if (rawJson.find("\"eventId\"") == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Event evt{};
|
||||
evt.id = nextId();
|
||||
evt.eventId = extractField(rawJson, "eventId");
|
||||
evt.eventType = extractField(rawJson, "type");
|
||||
evt.timestamp = nowMs();
|
||||
evt.level = EventLevel::Info;
|
||||
evt.longitude = 0.0;
|
||||
evt.latitude = 0.0;
|
||||
evt.description = extractField(rawJson, "desc");
|
||||
evt.status = EventStatus::Received;
|
||||
evt.createTime = nowMs();
|
||||
|
||||
// 从 "level" 字段解析等级
|
||||
std::string levelStr = extractField(rawJson, "level");
|
||||
if (!levelStr.empty()) {
|
||||
int lv = std::atoi(levelStr.c_str());
|
||||
if (lv >= 0 && lv <= 3) {
|
||||
evt.level = static_cast<EventLevel>(lv);
|
||||
}
|
||||
}
|
||||
|
||||
m_events.push_back(evt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EventManager::updateStatus(const std::string& eventId, EventStatus newStatus) {
|
||||
for (auto& e : m_events) {
|
||||
if (e.eventId == eventId) {
|
||||
e.status = newStatus;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PageResult EventManager::queryEvents(size_t page, size_t pageSize,
|
||||
const EventLevel* levelFilter) const {
|
||||
PageResult result{};
|
||||
result.pageIndex = (page < 1) ? 1 : page;
|
||||
result.pageSize = (pageSize < 1) ? 10 : pageSize;
|
||||
|
||||
std::vector<Event> filtered;
|
||||
for (const auto& e : m_events) {
|
||||
if (levelFilter && e.level != *levelFilter) {
|
||||
continue;
|
||||
}
|
||||
filtered.push_back(e);
|
||||
}
|
||||
|
||||
// 按 createTime 倒序排序
|
||||
std::sort(filtered.begin(), filtered.end(),
|
||||
[](const Event& a, const Event& b) {
|
||||
return a.createTime > b.createTime;
|
||||
});
|
||||
|
||||
result.totalCount = filtered.size();
|
||||
|
||||
size_t start = (result.pageIndex - 1) * result.pageSize;
|
||||
size_t end = start + result.pageSize;
|
||||
if (start >= filtered.size()) {
|
||||
return result;
|
||||
}
|
||||
if (end > filtered.size()) {
|
||||
end = filtered.size();
|
||||
}
|
||||
|
||||
result.items.assign(filtered.begin() + static_cast<ptrdiff_t>(start),
|
||||
filtered.begin() + static_cast<ptrdiff_t>(end));
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t EventManager::totalEvents() const {
|
||||
return m_events.size();
|
||||
}
|
||||
|
||||
const Event* EventManager::findEvent(const std::string& eventId) const {
|
||||
for (const auto& e : m_events) {
|
||||
if (e.eventId == eventId) {
|
||||
return &e;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* EventManager::levelToString(EventLevel level) noexcept {
|
||||
switch (level) {
|
||||
case EventLevel::Info: return "\u4fe1\u606f"; // 信息
|
||||
case EventLevel::Warning: return "\u8b66\u544a"; // 警告
|
||||
case EventLevel::Critical: return "\u5371\u6025"; // 危急
|
||||
case EventLevel::Fatal: return "\u81f4\u547d"; // 致命
|
||||
default: return "\u672a\u77e5"; // 未知
|
||||
}
|
||||
}
|
||||
|
||||
const char* EventManager::statusToString(EventStatus status) noexcept {
|
||||
switch (status) {
|
||||
case EventStatus::Received: return "\u5df2\u63a5\u6536"; // 已接收
|
||||
case EventStatus::Processed: return "\u5df2\u5904\u7406"; // 已处理
|
||||
case EventStatus::PendingTask: return "\u5f85\u751f\u6210\u4efb\u52a1"; // 待生成任务
|
||||
case EventStatus::TaskGenerated: return "\u4efb\u52a1\u5df2\u751f\u6210"; // 任务已生成
|
||||
default: return "\u672a\u77e5"; // 未知
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 私有辅助函数 ----------
|
||||
|
||||
/**
|
||||
* @brief 简易 JSON 字段提取
|
||||
*
|
||||
* 从形如 {"key":"value"} 的字符串中提取指定 key 对应的值。
|
||||
* 支持带引号的字符串值和不带引号的数值。
|
||||
*
|
||||
* @param json 输入 JSON 字符串
|
||||
* @param key 字段名
|
||||
* @return std::string 提取到的值,未找到返回空字符串
|
||||
*/
|
||||
std::string EventManager::extractField(const std::string& json,
|
||||
const std::string& key) const {
|
||||
std::string search = "\"" + key + "\"";
|
||||
auto pos = json.find(search);
|
||||
if (pos == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
pos = json.find(':', pos + search.size());
|
||||
if (pos == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
// 跳过空白字符
|
||||
pos++;
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
if (pos >= json.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result;
|
||||
if (json[pos] == '"') {
|
||||
// 带引号的字符串值
|
||||
pos++;
|
||||
while (pos < json.size() && json[pos] != '"') {
|
||||
if (json[pos] == '\\' && pos + 1 < json.size()) {
|
||||
result.push_back(json[pos + 1]);
|
||||
pos += 2;
|
||||
} else {
|
||||
result.push_back(json[pos]);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 数值或布尔值
|
||||
while (pos < json.size() && json[pos] != ',' && json[pos] != '}' && json[pos] != ']') {
|
||||
if (json[pos] != ' ' && json[pos] != '\t' && json[pos] != '\n') {
|
||||
result.push_back(json[pos]);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
156
src/main.cpp
156
src/main.cpp
|
|
@ -1,149 +1,27 @@
|
|||
/// @file main.cpp
|
||||
/// @brief ETMS 主程序入口,演示全流程:事件接收 → 展示 → 排序 → 模板选择 → 任务请求组装。
|
||||
#include "app.hpp"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
/// @brief 打印事件列表。
|
||||
static void PrintEvents(const std::vector<etms::Event>& events) {
|
||||
std::cout << "\n============================================\n";
|
||||
std::cout << " 事件列表(共 " << events.size() << " 条)\n";
|
||||
std::cout << "============================================\n";
|
||||
std::cout << std::left
|
||||
<< std::setw(12) << "ID"
|
||||
<< std::setw(14) << "事件ID"
|
||||
<< std::setw(10) << "类型"
|
||||
<< std::setw(6) << "等级"
|
||||
<< std::setw(18) << "状态"
|
||||
<< "描述\n";
|
||||
std::cout << "--------------------------------------------\n";
|
||||
|
||||
for (const auto& evt : events) {
|
||||
std::cout << std::left
|
||||
<< std::setw(12) << evt.id
|
||||
<< std::setw(14) << evt.event_id
|
||||
<< std::setw(10) << evt.event_type
|
||||
<< std::setw(6) << evt.level
|
||||
<< std::setw(18) << etms::EventStatusToString(evt.status)
|
||||
<< (evt.description.size() > 40
|
||||
? evt.description.substr(0, 40) + "..."
|
||||
: evt.description)
|
||||
<< '\n';
|
||||
}
|
||||
std::cout << "============================================\n\n";
|
||||
}
|
||||
|
||||
/// @brief 打印知识库版本列表。
|
||||
static void PrintKbVersions(const std::vector<etms::KbVersion>& versions) {
|
||||
std::cout << "--- 可用知识库版本 ---\n";
|
||||
for (const auto& v : versions) {
|
||||
std::cout << " " << v.version_id
|
||||
<< " | " << v.version_name
|
||||
<< " | " << v.status << "\n";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
/// @brief 打印任务模板列表。
|
||||
static void PrintTemplates(const std::vector<etms::TaskTemplate>& templates) {
|
||||
std::cout << "--- 可用任务模板 ---\n";
|
||||
for (const auto& t : templates) {
|
||||
std::cout << " " << t.template_id
|
||||
<< " | " << t.name
|
||||
<< " | 适用: " << t.applicable_type
|
||||
<< " | 版本: " << t.version << "\n";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
/// @brief 主函数:演示 ETMS 核心流程。
|
||||
/**
|
||||
* @brief ETMS C++ 命令行入口
|
||||
*
|
||||
* 创建 App 实例,初始化并运行演示流程。
|
||||
* 返回 0 表示正常结束。
|
||||
*/
|
||||
int main() {
|
||||
std::cout << "=== ETMS — Event & Task Management System (C++ Core) ===\n\n";
|
||||
App app("ETMS-CPP-Demo");
|
||||
|
||||
// 1. 模拟接收原始事件
|
||||
std::cout << "[1] 接收原始事件 ...\n";
|
||||
const std::string mock_json =
|
||||
R"({"event_id":"EVT-006","event_type":"侦察","timestamp":1700000500,)"
|
||||
R"("level":9,"longitude":116.42,"latitude":39.92,"description":"发现敌军前沿观察哨"})";
|
||||
auto ack = etms::ReceiveRawEvent(mock_json);
|
||||
std::cout << " ACK: " << (ack.accepted ? "Accepted" : "Rejected")
|
||||
<< " | event_id=" << ack.event_id
|
||||
<< " | msg=" << ack.message << "\n\n";
|
||||
|
||||
// 2. 查询所有事件并展示
|
||||
std::cout << "[2] 查询事件列表(默认顺序:创建时间倒序)\n";
|
||||
auto events = etms::QueryAllEvents();
|
||||
PrintEvents(events);
|
||||
|
||||
// 3. 筛选紧迫事件
|
||||
std::cout << "[3] 筛选紧迫事件(等级 >= 8) ...\n";
|
||||
auto urgent = etms::FilterUrgentEvents(events);
|
||||
PrintEvents(urgent);
|
||||
|
||||
// 4. 事件排序:按等级降序
|
||||
std::cout << "[4] 按等级降序排序 ...\n";
|
||||
etms::SortEventsByLevelDesc(events);
|
||||
PrintEvents(events);
|
||||
|
||||
// 5. 事件状态推进(模拟状态机)
|
||||
std::cout << "[5] 推进事件状态 ...\n";
|
||||
for (auto& evt : events) {
|
||||
if (evt.status == etms::EventStatus::Received) {
|
||||
// 先处理
|
||||
etms::ProcessEvent(evt);
|
||||
std::cout << " 事件 " << evt.event_id
|
||||
<< " → " << etms::EventStatusToString(evt.status) << "\n";
|
||||
// 再推进到 PendingTask
|
||||
etms::AdvanceEventStatus(evt);
|
||||
std::cout << " 事件 " << evt.event_id
|
||||
<< " → " << etms::EventStatusToString(evt.status) << "\n";
|
||||
}
|
||||
if (!app.initialize()) {
|
||||
std::cerr << "[FATAL] 应用初始化失败,退出。\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
// 6. 获取知识库版本与模板
|
||||
std::cout << "[6] 获取知识库版本与模板 ...\n";
|
||||
auto versions = etms::GetAvailableKbVersions();
|
||||
PrintKbVersions(versions);
|
||||
|
||||
auto templates = etms::GetAvailableTemplates();
|
||||
PrintTemplates(templates);
|
||||
|
||||
// 7. 自主推荐(模拟自主执行模式)
|
||||
std::cout << "[7] 事件匹配推荐(自主执行模式) ...\n";
|
||||
for (const auto& evt : events) {
|
||||
auto recommended = etms::RecommendTemplate(evt, templates);
|
||||
if (!recommended.empty()) {
|
||||
std::cout << " 事件 " << evt.event_id << " (" << evt.event_type << ")"
|
||||
<< " → 推荐模板: " << recommended << "\n";
|
||||
}
|
||||
try {
|
||||
app.run();
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "[FATAL] 运行时异常: " << ex.what() << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
// 8. 组装任务生成请求
|
||||
std::cout << "[8] 组装任务生成请求 ...\n";
|
||||
auto selected_event = events.front();
|
||||
auto selected_tpl = templates.front();
|
||||
std::map<std::string, std::string> params = {
|
||||
{"target_coord", std::to_string(selected_event.longitude) + ","
|
||||
+ std::to_string(selected_event.latitude)},
|
||||
{"priority", std::to_string(selected_event.level)},
|
||||
{"description", selected_event.description.substr(0, 100)}
|
||||
};
|
||||
auto request = etms::BuildTaskRequest(
|
||||
selected_event.event_id,
|
||||
selected_tpl.template_id,
|
||||
versions.front().version_id,
|
||||
params
|
||||
);
|
||||
std::cout << " 请求ID: " << request.request_id << "\n"
|
||||
<< " 事件ID: " << request.event_id << "\n"
|
||||
<< " 模板ID: " << request.template_id << "\n"
|
||||
<< " 知识库版本: " << request.kb_version_id << "\n"
|
||||
<< " 参数数量: " << request.params.size() << "\n"
|
||||
<< " 请求时间: " << std::ctime(&request.request_time);
|
||||
|
||||
std::cout << "\n=== ETMS 演示流程完成 ===\n";
|
||||
return 0;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
#include "task_template_manager.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
TaskTemplateManager::TaskTemplateManager() = default;
|
||||
TaskTemplateManager::~TaskTemplateManager() = default;
|
||||
TaskTemplateManager::TaskTemplateManager(TaskTemplateManager&&) noexcept = default;
|
||||
TaskTemplateManager& TaskTemplateManager::operator=(TaskTemplateManager&&) noexcept = default;
|
||||
|
||||
int64_t TaskTemplateManager::nowMs() noexcept {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
).count();
|
||||
}
|
||||
|
||||
bool TaskTemplateManager::upsertTemplate(const TaskTemplate& tmpl) {
|
||||
for (auto& t : m_templates) {
|
||||
if (t.templateId == tmpl.templateId) {
|
||||
// 更新已有模板
|
||||
t.name = tmpl.name;
|
||||
t.version = tmpl.version;
|
||||
t.contentPath = tmpl.contentPath;
|
||||
t.metadata = tmpl.metadata;
|
||||
t.createTime = tmpl.createTime > 0 ? tmpl.createTime : nowMs();
|
||||
t.usageCount = tmpl.usageCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 新增
|
||||
TaskTemplate copy = tmpl;
|
||||
if (copy.createTime == 0) {
|
||||
copy.createTime = nowMs();
|
||||
}
|
||||
m_templates.push_back(copy);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TaskTemplateManager::removeTemplate(const std::string& templateId) {
|
||||
auto it = std::remove_if(m_templates.begin(), m_templates.end(),
|
||||
[&](const TaskTemplate& t) {
|
||||
return t.templateId == templateId;
|
||||
});
|
||||
if (it == m_templates.end()) {
|
||||
return false;
|
||||
}
|
||||
m_templates.erase(it, m_templates.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
const TaskTemplate* TaskTemplateManager::findTemplate(const std::string& templateId) const {
|
||||
for (const auto& t : m_templates) {
|
||||
if (t.templateId == templateId) {
|
||||
return &t;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<TaskTemplate>& TaskTemplateManager::getAllTemplates() const {
|
||||
return m_templates;
|
||||
}
|
||||
|
||||
std::vector<TaskTemplate> TaskTemplateManager::getTemplatesSortedByName(bool ascending) const {
|
||||
auto result = m_templates;
|
||||
std::sort(result.begin(), result.end(),
|
||||
[ascending](const TaskTemplate& a, const TaskTemplate& b) {
|
||||
if (ascending) return a.name < b.name;
|
||||
return a.name > b.name;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<TaskTemplate> TaskTemplateManager::getTemplatesSortedByUsage(bool ascending) const {
|
||||
auto result = m_templates;
|
||||
std::sort(result.begin(), result.end(),
|
||||
[ascending](const TaskTemplate& a, const TaskTemplate& b) {
|
||||
if (ascending) return a.usageCount < b.usageCount;
|
||||
return a.usageCount > b.usageCount;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TaskTemplateManager::addKbVersion(const KbVersion& version) {
|
||||
// 如果设了 isActive,先把其他版本取消
|
||||
if (version.isActive) {
|
||||
for (auto& kv : m_kbVersions) {
|
||||
kv.isActive = false;
|
||||
}
|
||||
}
|
||||
m_kbVersions.push_back(version);
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<KbVersion>& TaskTemplateManager::getAllKbVersions() const {
|
||||
return m_kbVersions;
|
||||
}
|
||||
|
||||
bool TaskTemplateManager::activateKbVersion(const std::string& versionId) {
|
||||
bool found = false;
|
||||
for (auto& kv : m_kbVersions) {
|
||||
kv.isActive = (kv.versionId == versionId);
|
||||
if (kv.versionId == versionId) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
const KbVersion* TaskTemplateManager::getActiveKbVersion() const {
|
||||
for (const auto& kv : m_kbVersions) {
|
||||
if (kv.isActive) {
|
||||
return &kv;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t TaskTemplateManager::totalTemplates() const {
|
||||
return m_templates.size();
|
||||
}
|
||||
|
|
@ -1,249 +1,124 @@
|
|||
/// @file basic_test.cpp
|
||||
/// @brief ETMS 核心模块单元测试(使用标准库 assert,无外部依赖)。
|
||||
#include "app.hpp"
|
||||
#include "event_manager.hpp"
|
||||
#include "task_template_manager.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
/// @brief 测试事件状态枚举与字符串转换。
|
||||
static void test_EventStatusToString() {
|
||||
std::cout << " [test] EventStatusToString ... ";
|
||||
assert(std::string(etms::EventStatusToString(etms::EventStatus::Received)) == "Received");
|
||||
assert(std::string(etms::EventStatusToString(etms::EventStatus::Processed)) == "Processed");
|
||||
assert(std::string(etms::EventStatusToString(etms::EventStatus::PendingTask)) == "Pending Task");
|
||||
assert(std::string(etms::EventStatusToString(etms::EventStatus::Rejected)) == "Rejected");
|
||||
assert(std::string(etms::EventStatusToString(etms::EventStatus::TaskGenerated)) == "Task Generated");
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试接收原始事件(正常情况)。
|
||||
static void test_ReceiveRawEvent_Valid() {
|
||||
std::cout << " [test] ReceiveRawEvent (valid JSON) ... ";
|
||||
const std::string json = R"({"event_id":"EVT-TEST-001","event_type":"侦察"})";
|
||||
auto ack = etms::ReceiveRawEvent(json);
|
||||
assert(ack.accepted == true);
|
||||
assert(ack.event_id == "EVT-TEST-001");
|
||||
assert(!ack.message.empty());
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试接收原始事件(空载荷应拒绝)。
|
||||
static void test_ReceiveRawEvent_Empty() {
|
||||
std::cout << " [test] ReceiveRawEvent (empty) ... ";
|
||||
auto ack = etms::ReceiveRawEvent("");
|
||||
assert(ack.accepted == false);
|
||||
assert(ack.event_id.empty());
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试接收原始事件(缺少 event_id 应拒绝)。
|
||||
static void test_ReceiveRawEvent_MissingId() {
|
||||
std::cout << " [test] ReceiveRawEvent (missing event_id) ... ";
|
||||
const std::string json = R"({"event_type":"打击"})";
|
||||
auto ack = etms::ReceiveRawEvent(json);
|
||||
assert(ack.accepted == false);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试处理事件:状态从 Received → Processed。
|
||||
static void test_ProcessEvent() {
|
||||
std::cout << " [test] ProcessEvent ... ";
|
||||
etms::Event evt;
|
||||
evt.event_id = "EVT-PROC-001";
|
||||
evt.status = etms::EventStatus::Received;
|
||||
bool ok = etms::ProcessEvent(evt);
|
||||
assert(ok == true);
|
||||
assert(evt.status == etms::EventStatus::Processed);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试处理事件:非 Received 状态不应被处理。
|
||||
static void test_ProcessEvent_WrongStatus() {
|
||||
std::cout << " [test] ProcessEvent (wrong status) ... ";
|
||||
etms::Event evt;
|
||||
evt.status = etms::EventStatus::TaskGenerated;
|
||||
bool ok = etms::ProcessEvent(evt);
|
||||
assert(ok == false);
|
||||
assert(evt.status == etms::EventStatus::TaskGenerated);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试事件查询返回非空列表。
|
||||
static void test_QueryAllEvents() {
|
||||
std::cout << " [test] QueryAllEvents ... ";
|
||||
auto events = etms::QueryAllEvents();
|
||||
assert(!events.empty());
|
||||
// 默认应倒序(create_time 递减)
|
||||
for (size_t i = 1; i < events.size(); ++i) {
|
||||
assert(events[i - 1].create_time >= events[i].create_time);
|
||||
}
|
||||
std::cout << "PASSED (" << events.size() << " events)\n";
|
||||
}
|
||||
|
||||
/// @brief 测试紧迫事件筛选。
|
||||
static void test_FilterUrgentEvents() {
|
||||
std::cout << " [test] FilterUrgentEvents ... ";
|
||||
auto events = etms::QueryAllEvents();
|
||||
auto urgent = etms::FilterUrgentEvents(events);
|
||||
for (const auto& e : urgent) {
|
||||
assert(e.level >= 8);
|
||||
}
|
||||
assert(!urgent.empty());
|
||||
std::cout << "PASSED (" << urgent.size() << " urgent)\n";
|
||||
}
|
||||
|
||||
/// @brief 测试模板推荐(匹配类型应返回对应模板 ID)。
|
||||
static void test_RecommendTemplate() {
|
||||
std::cout << " [test] RecommendTemplate ... ";
|
||||
auto templates = etms::GetAvailableTemplates();
|
||||
|
||||
etms::Event recon_evt;
|
||||
recon_evt.event_type = "侦察";
|
||||
auto id1 = etms::RecommendTemplate(recon_evt, templates);
|
||||
assert(id1 == "TMPL-RECON-001");
|
||||
|
||||
etms::Event strike_evt;
|
||||
strike_evt.event_type = "打击";
|
||||
auto id2 = etms::RecommendTemplate(strike_evt, templates);
|
||||
assert(id2 == "TMPL-STRIKE-001");
|
||||
|
||||
// 不匹配类型时应返回兜底(第一个模板)
|
||||
etms::Event unknown_evt;
|
||||
unknown_evt.event_type = "未知";
|
||||
auto id3 = etms::RecommendTemplate(unknown_evt, templates);
|
||||
assert(id3 == templates.front().template_id);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试空模板列表时的推荐行为。
|
||||
static void test_RecommendTemplate_EmptyTemplates() {
|
||||
std::cout << " [test] RecommendTemplate (empty list) ... ";
|
||||
std::vector<etms::TaskTemplate> empty;
|
||||
etms::Event evt;
|
||||
evt.event_type = "侦察";
|
||||
auto id = etms::RecommendTemplate(evt, empty);
|
||||
assert(id.empty());
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试任务请求组装。
|
||||
static void test_BuildTaskRequest() {
|
||||
std::cout << " [test] BuildTaskRequest ... ";
|
||||
std::map<std::string, std::string> params = {{"key1", "val1"}, {"key2", "val2"}};
|
||||
auto req = etms::BuildTaskRequest("EVT-REQ-001", "TMPL-001", "KB-V1.0", params);
|
||||
assert(req.request_id.find("REQ-") == 0);
|
||||
assert(req.event_id == "EVT-REQ-001");
|
||||
assert(req.template_id == "TMPL-001");
|
||||
assert(req.kb_version_id == "KB-V1.0");
|
||||
assert(req.params.size() == 2);
|
||||
assert(req.request_time > 0);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试知识库版本查询。
|
||||
static void test_GetAvailableKbVersions() {
|
||||
std::cout << " [test] GetAvailableKbVersions ... ";
|
||||
auto versions = etms::GetAvailableKbVersions();
|
||||
assert(!versions.empty());
|
||||
bool has_active = false;
|
||||
for (const auto& v : versions) {
|
||||
if (v.status == "Active") has_active = true;
|
||||
}
|
||||
assert(has_active);
|
||||
std::cout << "PASSED (" << versions.size() << " versions)\n";
|
||||
}
|
||||
|
||||
/// @brief 测试模板查询。
|
||||
static void test_GetAvailableTemplates() {
|
||||
std::cout << " [test] GetAvailableTemplates ... ";
|
||||
auto templates = etms::GetAvailableTemplates();
|
||||
assert(!templates.empty());
|
||||
for (const auto& t : templates) {
|
||||
assert(!t.template_id.empty());
|
||||
assert(!t.name.empty());
|
||||
}
|
||||
std::cout << "PASSED (" << templates.size() << " templates)\n";
|
||||
}
|
||||
|
||||
/// @brief 测试事件状态推进机。
|
||||
static void test_AdvanceEventStatus() {
|
||||
std::cout << " [test] AdvanceEventStatus ... ";
|
||||
etms::Event evt;
|
||||
evt.status = etms::EventStatus::Received;
|
||||
|
||||
// Received → Processed
|
||||
assert(etms::AdvanceEventStatus(evt) == true);
|
||||
assert(evt.status == etms::EventStatus::Processed);
|
||||
|
||||
// Processed → PendingTask
|
||||
assert(etms::AdvanceEventStatus(evt) == true);
|
||||
assert(evt.status == etms::EventStatus::PendingTask);
|
||||
|
||||
// PendingTask → TaskGenerated
|
||||
assert(etms::AdvanceEventStatus(evt) == true);
|
||||
assert(evt.status == etms::EventStatus::TaskGenerated);
|
||||
|
||||
// TaskGenerated → 不可再推进
|
||||
assert(etms::AdvanceEventStatus(evt) == false);
|
||||
assert(evt.status == etms::EventStatus::TaskGenerated);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试 Rejected 状态不可推进。
|
||||
static void test_AdvanceEventStatus_Rejected() {
|
||||
std::cout << " [test] AdvanceEventStatus (Rejected) ... ";
|
||||
etms::Event evt;
|
||||
evt.status = etms::EventStatus::Rejected;
|
||||
assert(etms::AdvanceEventStatus(evt) == false);
|
||||
assert(evt.status == etms::EventStatus::Rejected);
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试排序函数(按等级降序)。
|
||||
static void test_SortEventsByLevelDesc() {
|
||||
std::cout << " [test] SortEventsByLevelDesc ... ";
|
||||
auto events = etms::QueryAllEvents();
|
||||
etms::SortEventsByLevelDesc(events);
|
||||
for (size_t i = 1; i < events.size(); ++i) {
|
||||
assert(events[i - 1].level >= events[i].level);
|
||||
}
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 测试排序函数(按时间升序)。
|
||||
static void test_SortEventsByTimeAsc() {
|
||||
std::cout << " [test] SortEventsByTimeAsc ... ";
|
||||
auto events = etms::QueryAllEvents();
|
||||
etms::SortEventsByTimeAsc(events);
|
||||
for (size_t i = 1; i < events.size(); ++i) {
|
||||
assert(events[i - 1].timestamp <= events[i].timestamp);
|
||||
}
|
||||
std::cout << "PASSED\n";
|
||||
}
|
||||
|
||||
/// @brief 主测试入口。
|
||||
/**
|
||||
* @brief ETMS 单元测试
|
||||
*
|
||||
* 使用标准 assert 宏进行基本功能验证,无需引入外部测试框架。
|
||||
*/
|
||||
int main() {
|
||||
std::cout << "=== ETMS Basic Unit Tests ===\n\n";
|
||||
std::cout << "[TEST] 启动 ETMS 基础单元测试...\n\n";
|
||||
|
||||
test_EventStatusToString();
|
||||
test_ReceiveRawEvent_Valid();
|
||||
test_ReceiveRawEvent_Empty();
|
||||
test_ReceiveRawEvent_MissingId();
|
||||
test_ProcessEvent();
|
||||
test_ProcessEvent_WrongStatus();
|
||||
test_QueryAllEvents();
|
||||
test_FilterUrgentEvents();
|
||||
test_RecommendTemplate();
|
||||
test_RecommendTemplate_EmptyTemplates();
|
||||
test_BuildTaskRequest();
|
||||
test_GetAvailableKbVersions();
|
||||
test_GetAvailableTemplates();
|
||||
test_AdvanceEventStatus();
|
||||
test_AdvanceEventStatus_Rejected();
|
||||
test_SortEventsByLevelDesc();
|
||||
test_SortEventsByTimeAsc();
|
||||
// ============================
|
||||
// 1. EventManager 测试
|
||||
// ============================
|
||||
std::cout << "[TEST] EventManager 测试...\n";
|
||||
|
||||
std::cout << "\n=== All " << 17 << " tests PASSED ===\n";
|
||||
EventManager evtMgr;
|
||||
|
||||
// 1.1 接收合法事件
|
||||
bool ok = evtMgr.receiveEvent("{\"eventId\":\"E001\",\"type\":\"air_raid\",\"level\":3,\"desc\":\"\u654c\u673a\u7a81\u88ad\"}");
|
||||
assert(ok);
|
||||
assert(evtMgr.totalEvents() == 1);
|
||||
|
||||
// 1.2 接收另一个事件
|
||||
ok = evtMgr.receiveEvent("{\"eventId\":\"E002\",\"type\":\"recon\",\"level\":1,\"desc\":\"\u4fa6\u5bdf\"}");
|
||||
assert(ok);
|
||||
assert(evtMgr.totalEvents() == 2);
|
||||
|
||||
// 1.3 接收非法事件(缺少 eventId)
|
||||
ok = evtMgr.receiveEvent("{\"type\":\"invalid\"}");
|
||||
assert(!ok); // should be rejected
|
||||
assert(evtMgr.totalEvents() == 2); // 数量不变
|
||||
|
||||
// 1.4 状态流转
|
||||
ok = evtMgr.updateStatus("E001", EventStatus::Processed);
|
||||
assert(ok);
|
||||
|
||||
const Event* evt1 = evtMgr.findEvent("E001");
|
||||
assert(evt1 != nullptr);
|
||||
assert(evt1->status == EventStatus::Processed);
|
||||
|
||||
// 1.5 查询不存在的 ID
|
||||
const Event* none = evtMgr.findEvent("NONEXIST");
|
||||
assert(none == nullptr);
|
||||
|
||||
// 1.6 分页查询
|
||||
auto page = evtMgr.queryEvents(1, 10);
|
||||
assert(page.totalCount == 2);
|
||||
assert(page.items.size() == 2);
|
||||
|
||||
// 1.7 levelToString / statusToString
|
||||
assert(std::strcmp(EventManager::levelToString(EventLevel::Critical), "\u5371\u6025") == 0);
|
||||
assert(std::strcmp(EventManager::statusToString(EventStatus::Received), "\u5df2\u63a5\u6536") == 0);
|
||||
|
||||
std::cout << " [PASS] EventManager \u5168\u90e8\u6d4b\u8bd5\u901a\u8fc7\n\n";
|
||||
|
||||
// ============================
|
||||
// 2. TaskTemplateManager 测试
|
||||
// ============================
|
||||
std::cout << "[TEST] TaskTemplateManager \u6d4b\u8bd5...\n";
|
||||
|
||||
TaskTemplateManager tmplMgr;
|
||||
|
||||
// 2.1 添加模板
|
||||
TaskTemplate t1{"T001", "\u9632\u7a7a\u53cd\u5bfc\u65b9\u6848", "1.0",
|
||||
"/minio/templates/T001.json", "{\"type\":\"air_defense\"}", 0, 5};
|
||||
TaskTemplate t2{"T002", "\u4fa6\u5bdf\u6253\u51fb\u65b9\u6848", "1.2",
|
||||
"/minio/templates/T002.json", "{\"type\":\"strike\"}", 0, 3};
|
||||
|
||||
assert(tmplMgr.upsertTemplate(t1));
|
||||
assert(tmplMgr.upsertTemplate(t2));
|
||||
assert(tmplMgr.totalTemplates() == 2);
|
||||
|
||||
// 2.2 查找模板
|
||||
const TaskTemplate* found = tmplMgr.findTemplate("T001");
|
||||
assert(found != nullptr);
|
||||
assert(found->name == "\u9632\u7a7a\u53cd\u5bfc\u65b9\u6848");
|
||||
|
||||
// 2.3 更新模板(增量更新)
|
||||
TaskTemplate t1Updated{"T001", "\u9632\u7a7a\u53cd\u5bfc\u65b9\u6848v2", "2.0",
|
||||
"/minio/templates/T001_v2.json", "{}", 0, 10};
|
||||
assert(tmplMgr.upsertTemplate(t1Updated));
|
||||
const TaskTemplate* updated = tmplMgr.findTemplate("T001");
|
||||
assert(updated != nullptr);
|
||||
assert(updated->version == "2.0");
|
||||
|
||||
// 2.4 按名称排序
|
||||
auto sortedByName = tmplMgr.getTemplatesSortedByName(true);
|
||||
assert(sortedByName.size() == 2);
|
||||
assert(sortedByName[0].templateId == "T001"); // "防空" < "侦察" in GBK/Pinyin... well, just check size
|
||||
|
||||
// 2.5 按使用频率排序(降序)
|
||||
auto sortedByUsage = tmplMgr.getTemplatesSortedByUsage(false);
|
||||
assert(sortedByUsage.size() == 2);
|
||||
assert(sortedByUsage[0].usageCount >= sortedByUsage[1].usageCount);
|
||||
|
||||
// 2.6 删除模板
|
||||
assert(tmplMgr.removeTemplate("T002"));
|
||||
assert(tmplMgr.totalTemplates() == 1);
|
||||
assert(tmplMgr.findTemplate("T002") == nullptr);
|
||||
|
||||
// 2.7 知识库版本管理
|
||||
KbVersion kb1{"KB-2024-01", 1704067200000LL, "2024\u5e74\u57fa\u7840\u77e5\u8bc6\u5e93", true};
|
||||
KbVersion kb2{"KB-2024-02", 1706745600000LL, "2024\u5e74\u66f4\u65b0\u77e5\u8bc6\u5e93", false};
|
||||
|
||||
assert(tmplMgr.addKbVersion(kb1));
|
||||
assert(tmplMgr.addKbVersion(kb2));
|
||||
assert(tmplMgr.getAllKbVersions().size() == 2);
|
||||
|
||||
// 切换活跃版本
|
||||
assert(tmplMgr.activateKbVersion("KB-2024-02"));
|
||||
const KbVersion* active = tmplMgr.getActiveKbVersion();
|
||||
assert(active != nullptr);
|
||||
assert(active->versionId == "KB-2024-02");
|
||||
assert(active->isActive);
|
||||
|
||||
std::cout << " [PASS] TaskTemplateManager \u5168\u90e8\u6d4b\u8bd5\u901a\u8fc7\n\n";
|
||||
|
||||
std::cout << "[TEST] \u5168\u90e8\u6d4b\u8bd5\u901a\u8fc7\u2714\n";
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue