Compare commits

...

2 Commits

Author SHA1 Message Date
root b8051d2417 生成代码工程 2026-05-21 07:09:58 +00:00
root aa97dfcf56 生成代码工程 2026-05-20 09:17:29 +00:00
12 changed files with 13258 additions and 11080 deletions

View File

@ -1,43 +1,38 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(etms project(etms_cpp VERSION 1.0.0 LANGUAGES CXX)
VERSION 1.0.0
DESCRIPTION "Event & Task Management System (ETMS) - Core C++ Component"
LANGUAGES CXX
)
# C++17
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# MSVC UTF-8 support # MSVC UTF-8
if (MSVC) if (MSVC)
add_compile_options(/utf-8) add_compile_options(/utf-8)
endif() endif()
# ------ Directory variables ------ #
set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) include_directories(${CMAKE_SOURCE_DIR}/include)
set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
# ------ 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}) # ============================
# 使 assert
# ------ Test executable ------ # ============================
add_executable(${PROJECT_NAME}_test add_executable(etms_test
${TEST_DIR}/basic_test.cpp tests/basic_test.cpp
${SRC_DIR}/app.cpp src/app.cpp
src/event_manager.cpp
src/task_template_manager.cpp
) )
target_include_directories(${PROJECT_NAME}_test PRIVATE ${INCLUDE_DIR}) # CTest
enable_testing()
# ------ Custom target: run test ------ add_test(NAME BasicTest COMMAND etms_test)
add_custom_target(check
COMMAND ${PROJECT_NAME}_test
DEPENDS ${PROJECT_NAME}_test
COMMENT "Running unit tests..."
)

View File

@ -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 构建配置 ├── CMakeLists.txt # CMake 构建配置
├── README.md # 本文件 ├── README.md # 本文件
├── include/ ├── include/
│ └── app.hpp # 公开 API 头文件(事件、模板、版本、状态机、请求组装) │ ├── app.hpp # 应用主入口头文件
│ ├── event_manager.hpp # 事件管理器头文件
│ └── task_template_manager.hpp # 任务模板管理器头文件
├── src/ ├── src/
│ ├── app.cpp # 核心逻辑实现 │ ├── main.cpp # 命令行入口
│ └── main.cpp # 命令行入口 │ ├── app.cpp # 应用实现
│ ├── event_manager.cpp # 事件管理器实现
│ └── task_template_manager.cpp # 任务模板管理器实现
└── tests/ └── tests/
└── basic_test.cpp # 单元测试(使用 assert └── basic_test.cpp # 单元测试(标准 assert
``` ```
## 构建与运行 ## 构建与运行
### 前提
- CMake ≥ 3.14
- 支持 C++17 的编译器GCC 8+, Clang 7+, MSVC 2019+
### 步骤
```bash ```bash
# 1. 进入工程目录 # 配置
cd codegen-runs/codegen_d0add3470891422097f0b7fb8558b115 mkdir -p build && cd build
cmake ..
# 2. 配置 & 构建 # 构建主程序
cmake -B build cmake --build . --target etms_app
cmake --build build
# 3. 运行主程序 # 运行主程序
./build/etms ./etms_app
# 4. 运行单元测试 # 构建并运行测试
./build/etms_test cmake --build . --target etms_test
# 或 ctest --output-on-failure
cmake --build build --target check
``` ```
## 核心模块 ## 核心模块
| 模块 | 文件 | 说明 | ### EventManager事件管理器
|----------------|-------------------|------------------------------------| - 事件接收与状态流转Received → Processed → PendingTask → TaskGenerated
| 事件模型 | `app.hpp` | `Event` 结构体,对应 t_event 表 | - 事件列表分页查询与排序
| 任务模板模型 | `app.hpp` | `TaskTemplate` 结构体,对应 t_task_template 表 | - 事件等级颜色标识
| 知识库版本 | `app.hpp` | `KbVersion` 结构体,对应 t_kb_version 表 |
| 事件状态机 | `app.hpp` | `EventStatus` 枚举与状态流转逻辑 | ### TaskTemplateManager任务模板管理器
| 任务请求组装 | `app.hpp` / `.cpp`| 生成任务请求报文的辅助函数 | - 模板增删改查
| 运行时演示 | `main.cpp` | 演示全流程(事件接收→展示→模板选择→请求组装) | - 版本管理与知识库版本切换
- 模板排序与展示
## 数据模型
| 模型 | 说明 |
|------|------|
| `Event` | 战场事件,含 ID、类型、时间、等级、坐标等 |
| `TaskTemplate` | 任务模板,含 ID、名称、版本、元数据等 |
| `KbVersion` | 知识库版本,含版本号、发布日期、活跃标志 |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,138 +2,68 @@
#define ETMS_APP_HPP #define ETMS_APP_HPP
#include <string> #include <string>
#include <vector> #include <memory>
#include <cstdint> #include "event_manager.hpp"
#include <ctime> #include "task_template_manager.hpp"
#include <map>
namespace etms { /**
* @brief ETMS
*
*
*
*/
class App {
public:
/**
* @brief App
* @param appName
*/
explicit App(const std::string& appName);
/// @brief 事件状态枚举,对应需求文档中 t_event.status 的状态流转。 /// @brief 析构函数,释放内部资源
enum class EventStatus { ~App();
Received, ///< 刚接收,待处理
Processed, ///< 已处理 // 禁止拷贝
PendingTask, ///< 待映射任务 App(const App&) = delete;
Rejected, ///< 已拒绝 App& operator=(const App&) = delete;
TaskGenerated ///< 任务已生成
/// @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 #endif // ETMS_APP_HPP

160
include/event_manager.hpp Normal file
View File

@ -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

View File

@ -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

View File

@ -1,260 +1,102 @@
#include "app.hpp" #include "app.hpp"
#include <algorithm> #include <iostream>
#include <sstream> #include <sstream>
#include <cstring>
#include <ctime>
#include <stdexcept>
namespace etms { App::App(const std::string& appName)
: m_appName(appName) {
// ========================
// 辅助函数(模块内部)
// ========================
namespace {
/// @brief 生成简易 UUID仅用于演示
std::string GenerateSimpleId() {
static int64_t counter = 0;
std::ostringstream oss;
oss << "EVT-" << std::time(nullptr) << "-" << ++counter;
return oss.str();
} }
/// @brief 生成请求 ID。 App::~App() = default;
std::string GenerateRequestId() {
static int64_t counter = 0;
std::ostringstream oss;
oss << "REQ-" << std::time(nullptr) << "-" << ++counter;
return oss.str();
}
/// @brief 检查字符串是否非空且不超长。 App::App(App&&) noexcept = default;
bool IsValidJsonField(const std::string& field, size_t max_len = 4096) { App& App::operator=(App&&) noexcept = default;
return !field.empty() && field.size() <= max_len;
}
} // anonymous namespace bool App::initialize() {
try {
// ======================== m_eventManager = std::make_unique<EventManager>();
// 公开 API 实现 m_templateManager = std::make_unique<TaskTemplateManager>();
// ======================== m_initialized = true;
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; return true;
} } catch (...) {
m_initialized = false;
/// @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;
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:
return false; return false;
} }
} }
/// @brief 按事件等级降序排序。 void App::run() {
void SortEventsByLevelDesc(std::vector<Event>& events) { if (!m_initialized) {
std::sort(events.begin(), events.end(), std::cerr << "[ERROR] App not initialized.\n";
[](const Event& a, const Event& b) { return;
return a.level > b.level;
});
} }
/// @brief 按事件时间升序排序。 std::cout << "=== ETMS C++ Application: " << m_appName << " ===\n\n";
void SortEventsByTimeAsc(std::vector<Event>& events) {
std::sort(events.begin(), events.end(), // ----- 事件管理演示 -----
[](const Event& a, const Event& b) { std::cout << "--- 接收模拟事件 ---\n";
return a.timestamp < b.timestamp; 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";
} }
} // namespace etms // ----- 模板管理演示 -----
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";
}
EventManager& App::getEventManager() {
return *m_eventManager;
}
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";
}

187
src/event_manager.cpp Normal file
View File

@ -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;
}

View File

@ -1,149 +1,27 @@
/// @file main.cpp
/// @brief ETMS 主程序入口,演示全流程:事件接收 → 展示 → 排序 → 模板选择 → 任务请求组装。
#include "app.hpp" #include "app.hpp"
#include <iostream> #include <iostream>
#include <iomanip> #include <cstdlib>
#include <ctime>
/// @brief 打印事件列表。 /**
static void PrintEvents(const std::vector<etms::Event>& events) { * @brief ETMS C++
std::cout << "\n============================================\n"; *
std::cout << " 事件列表(共 " << events.size() << " 条)\n"; * App
std::cout << "============================================\n"; * 0
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 核心流程。
int main() { int main() {
std::cout << "=== ETMS — Event & Task Management System (C++ Core) ===\n\n"; App app("ETMS-CPP-Demo");
// 1. 模拟接收原始事件 if (!app.initialize()) {
std::cout << "[1] 接收原始事件 ...\n"; std::cerr << "[FATAL] 应用初始化失败,退出。\n";
const std::string mock_json = return EXIT_FAILURE;
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";
} }
try {
app.run();
} catch (const std::exception& ex) {
std::cerr << "[FATAL] 运行时异常: " << ex.what() << "\n";
return EXIT_FAILURE;
} }
std::cout << "\n";
// 6. 获取知识库版本与模板 return EXIT_SUCCESS;
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";
}
}
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;
} }

View File

@ -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();
}

View File

@ -1,249 +1,124 @@
/// @file basic_test.cpp #include "event_manager.hpp"
/// @brief ETMS 核心模块单元测试(使用标准库 assert无外部依赖 #include "task_template_manager.hpp"
#include "app.hpp"
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
#include <sstream> #include <cstring>
#include <string>
/// @brief 测试事件状态枚举与字符串转换。 /**
static void test_EventStatusToString() { * @brief ETMS
std::cout << " [test] EventStatusToString ... "; *
assert(std::string(etms::EventStatusToString(etms::EventStatus::Received)) == "Received"); * 使 assert
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 主测试入口。
int main() { int main() {
std::cout << "=== ETMS Basic Unit Tests ===\n\n"; std::cout << "[TEST] 启动 ETMS 基础单元测试...\n\n";
test_EventStatusToString(); // ============================
test_ReceiveRawEvent_Valid(); // 1. EventManager 测试
test_ReceiveRawEvent_Empty(); // ============================
test_ReceiveRawEvent_MissingId(); std::cout << "[TEST] EventManager 测试...\n";
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();
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; return 0;
} }