任务管理系统
简洁、一目了然的任务管理服务(C++/CMake + MySQL),适用于轻量级企业或个人的任务调度与管理场景。
✨ 简介
任务管理系统 是一个基于 C++ 的任务管理后台服务,使用 CMake 构建,支持 MySQL 存储。项目关注:高可读性、易部署、可扩展的业务逻辑(任务与用户管理)。适合用于学习、快速原型或作为微服务组件。
🚀 核心特性
- ✅ 支持通过 MySQL 持久化任务与用户数据
- 🔧 使用 CMake 多平台构建(Windows / Linux)
- ♻️ 清晰的模块分层:
include/、src/、database/、logic/、dao/ - 🧰 日志支持(内置简单 Logger)
- 🧪 易于扩展:清晰的 Dao 与 Manager 分层,便于单元测试与替换实现
🔧核心架构
各组件关系

本地持久化数据的方式是将数据存储在 MySQL 中(这里使用 docker 配置)。
1. 触发层(Client & Command)
系统的起点位于最下方。
- Client(客户端)通过"使用命令"的方式向系统发起请求。
- Command(命令层)作为统一的入口,负责接收 Client 的请求,并根据请求类型,向上"调用逻辑接口"将任务分发给具体的业务管理器。
2. 业务逻辑层(TaskManager & UserManager)
这一层处理核心的业务逻辑,实现了业务的垂直解耦。
- TaskManager(任务管理器):专门负责"管理任务"相关的业务逻辑。
- UserManager(用户管理器):专门负责"管理用户"相关的业务逻辑。
- 两者在处理完各自的业务后,会同时向中央的 MysqlMgr(MySQL管理器) 发起"调用DML"的请求,准备进行数据持久化。
3. 数据访问管理层(MysqlMgr)
作为业务层与具体底层数据对象之间的桥梁。
- MysqlMgr 统一接收来自任务和用户管理器的 DML(数据操作语言)调用。
- 它负责协调并进一步分发这些操作给更具体的实体 DAO 模块:向左调用 UserDao ,向右调用 TaskDao。
4. 数据访问对象层(UserDao & TaskDao & MysqlPool)
这一层直接负责与数据库连接及具体的表操作交互。
- UserDao 和 TaskDao 分别是用户表和任务表的数据访问对象。
- 它们在执行具体的 DML 操作前,都需要从中央的 MysqlPool(MySQL连接池) 中"获得连接"。
- MysqlPool 负责管理和分配与底层数据库的健康长连接。
5. 持久化层(Mysql)
位于架构的最顶端,作为整个系统的数据终点。
- MysqlPool 负责维护与它的连接状态。
- UserDao 和 TaskDao 在拿到连接池分配的句柄后,直接对 Mysql 数据库执行最终的"调用DML"(如 Insert、Update、Delete 等操作),完成数据的持久化存储。
数据库设计

tasks :存储所有任务数据,以任务 ID 为主键,user_id 为外键联结 users 用户表(描述该任务属于哪个任务)。其他属性均可独自设计。
users :存储所有用户数据,以用户 ID 为主键,name 和 password 用来用户的登录与注册。
使用示例

用户先进入登录注册界面,根据注册的用户名和密码进行登录,实现多用户分任务管理。

成功登录后进入任务管理界面,用户可选择 add、delete、list、update、logout 和 exit 命令来进行不同的操作,注意:任务处理命令需要较严格的参数形式,否则会处理失败。
Code 实现
cpp
TasksManager (项目根目录)
├── build/ // 编译输出目录(CMake 生成的中间文件与可执行程序)
├── include/ // 所有头文件 (.h)
│ ├── common/ // 公共辅助工具
│ │ └── Logger.h // 日志类:负责格式化输出系统运行状态与错误
│ ├── dao/ // 数据访问层接口
│ │ ├── TaskDao.h // 任务表操作:负责 tasks 表的增删改查声明
│ │ └── UserDao.h // 用户表操作:负责 users 表的校验与注册声明
│ ├── database/ // 数据库核心支持
│ │ ├── MysqlMgr.h // 数据库管理器:封装单例,管理所有 DAO 实例
│ │ └── MysqlPool.h // 连接池:维护与 MySQL 的长连接,防止连接频繁断开
│ └── logic/ // 业务逻辑与指令处理
│ ├── Command.h // 命令模式基类:定义 execute() 接口
│ ├── TaskManager.h // 任务业务层:处理任务排序、日期校验等核心逻辑
│ └── UserManager.h // 用户业务层:处理登录态管理 (static id) 与权限
├── mysql/ // 可能存放的 SQL 初始化脚本或数据库配置
├── src/ // 所有源文件实现 (.cpp)
│ ├── common/
│ │ └── Logger.cpp // 实现日志打印逻辑
│ ├── dao/
│ │ ├── TaskDao.cpp // 实现 SQL 语句拼接(select/insert/update/delete)
│ │ └── UserDao.cpp // 实现用户验证的 SQL 逻辑
│ ├── database/
│ │ ├── MysqlMgr.cpp // 实现 DAO 的初始化与资源分配
│ │ └── MysqlPool.cpp // 实现连接池的获取、归还与心跳检测
│ ├── logic/
│ │ ├── Command.cpp // 实现各类 Command 子类(Add/List/Login)的 executeImpl
│ │ ├── TaskManager.cpp // 实现任务逻辑的具体流程
│ │ └── UserManager.cpp // 实现用户注册/登录的业务判断逻辑
│ └── main.cpp // 程序总入口:初始化各层级,驱动 HandleWithUser 循环
├── .gitignore // Git 忽略文件(忽略 build/ 和敏感信息)
├── CMakeLists.txt // 项目构建脚本:定义包含路径、库依赖和生成目标
└── docker-compose.yml // 环境编排:定义 MySQL 容器版本、端口及初始化账号
cpp
#pragma once
#include <condition_variable>
#include <fstream>
#include <mutex>
#include <queue>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include <format>
// 日志级别
enum class LogLevel { INFO, DEBUG, ERROR };
class LogQueue {
public:
LogQueue() = default;
~LogQueue() = default;
//@msg:格式化日志消息
//function:生产线程将格式化消息推入日志队列
void push(const std::string& msg);
//@msg:接受从日志队列取出的日志消息
//function:消费线程从日志队列中取出日志消息
bool pop(std::string& msg);
//关闭日志队列,设置队列状态
void shutdown();
private:
//消息队列:存储格式化后的消息
std::queue<std::string> msg_queue_;
//互斥锁:解决多线程互斥问题
std::mutex mtx_;
//条件变量:解决多线程同步问题:push/pop
std::condition_variable cond_;
//日志队列状态,默认为开启
bool is_shutdown_ = false;
};
class Logger {
public:
// 获取单例实例
static Logger& getInstance();
// 禁止拷贝和赋值
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
template<typename... Args>
//@format:日志消息的标签,必须为字符串
//@args:可变参数,多个不同类型的消息参数
//万能引用,即可接受左值也可接受右值
//function:将format和args格式化为标准的日志消息字符串
//并将格式化日志消息加入日志队列中
void log(LogLevel level, const std::string& format, Args&&... args) {
std::string level_str;
switch (level) {
case LogLevel::INFO: level_str = "[INFO] "; break;
case LogLevel::DEBUG: level_str = "[DEBUG] "; break;
case LogLevel::ERROR: level_str = "[ERROR] "; break;
}
log_queue_.push(level_str + formatMessage(format, std::forward<Args>(args)...));
}
private:
//@filename:日志文件路径
//function:构造函数,打开日志文件,并启动工作线程
explicit Logger(const std::string& filename);
//function:析构函数,关闭日志队列,日志文件以及回收工作线程
~Logger();
//function:工作线程的工作函数,从日志队列中取出日志消息,并将日志消息输出到日志文件中
void processQueue();
//获取当前时间
std::string getCurrentTime();
/**
*@arg:需要转换为字符串的可变参数
*@format:日志消息的标签,必须为字符串
*functin:将format和args转变为格式化日志消息
*/
template<typename... Args>
std::string formatMessage(std::string_view format, Args&&... args) {
try {
return "[" + getCurrentTime() + "] " + std::vformat(format, std::make_format_args(args...));
}
catch (const std::format_error&) {
std::vector<std::string> arg_strings = {
std::format("{}", std::forward<Args>(args))...
};
std::string result{ format };
size_t arg_index = 0;
size_t pos = 0;
while (arg_index < arg_strings.size() &&
(pos = result.find("{}", pos)) != std::string::npos) {
result.replace(pos, 2, arg_strings[arg_index++]);
pos += arg_strings[arg_index - 1].length();
}
while (arg_index < arg_strings.size()) {
result += arg_strings[arg_index++];
}
return "[" + getCurrentTime() + "] " + result;
}
}
//日志队列:存取消息的容器
LogQueue log_queue_;
//工作/消费线程:负责将日志队列中的消息输出到日志文件中
std::thread work_thread_;
//日志文件:存储程序写入的日志消息,供客户查看
std::ofstream log_file_;
};
cpp
#include "common/Logger.h"
#include <chrono>
#include <ctime>
#include <stdexcept>
Logger& Logger::getInstance() {
static Logger instance("./log.txt");
return instance;
}
void LogQueue::push(const std::string& msg) {
std::lock_guard<std::mutex> lock(mtx_);
if (!is_shutdown_) {
msg_queue_.push(msg);
cond_.notify_one();
}
}
bool LogQueue::pop(std::string& msg) {
std::unique_lock<std::mutex> lock(mtx_);
while (msg_queue_.empty() && !is_shutdown_) {
cond_.wait(lock);
}
if (is_shutdown_ && msg_queue_.empty()) {
return false;
}
msg = msg_queue_.front();
msg_queue_.pop();
return true;
}
void LogQueue::shutdown() {
std::lock_guard<std::mutex> lock(mtx_);
is_shutdown_ = true;
cond_.notify_all();
}
Logger::Logger(const std::string& filename)
: log_file_(filename, std::ios::out | std::ios::app) {
if (!log_file_.is_open()) {
throw std::runtime_error("无法打开日志文件");
}
work_thread_ = std::thread(&Logger::processQueue, this);
}
Logger::~Logger() {
log_queue_.shutdown();
if (log_file_.is_open()) {
log_file_.close();
}
if (work_thread_.joinable()) {
work_thread_.join();
}
}
void Logger::processQueue() {
std::string msg;
while (log_queue_.pop(msg)) {
log_file_ << msg << std::endl;
}
}
std::string Logger::getCurrentTime() {
auto now = std::chrono::system_clock::now();
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now_time));
return std::string(buffer);
}
cpp
#pragma once
#include"database/MysqlPool.h"
#include"logic/UserManager.h"
#include<unordered_map>
enum class SortOption{
ID,
PRIORITY,
CREATTIME,
DEADLINE
};
class TaskDao
{
private:
//关联的Mysql
MySqlPool&pool_;
/**
* 查询表中是否有该任务
* @task:查询的任务
*/
bool searchTask(const std::string& description,const std::string& deadline,int priority=2);
public:
static std::unordered_map<std::string,SortOption>options;
/**
* 构造函数:获得数据库连接并打开任务表
*
*/
TaskDao();
/**
* 析构函数:关闭数据库连接
*/
~TaskDao();
/**
* 增加一个完整的任务
*/
void addTask(const std::string& description,const std::string& deadline,int priority=2);
/**
* 根据任务ID,删除一个任务
* @id:任务ID
*/
void deleteTask(int id);
/**
* 修改一个任务
*/
void updateTask(const std::string& description,const std::string& deadline,int priority=2);
/**
* 根据排序选项来展示所有任务,默认为升序排序
* @option:排序选项
*/
void listTasks(SortOption option);
};
cpp
#include "dao/TaskDao.h"
std::unordered_map<std::string, SortOption> TaskDao::options;
bool TaskDao::searchTask(const std::string &description, const std::string &deadline, int priority)
{
try
{
// 获取session连接
SessionPtr ptr = pool_.getConnection();
// 获取表
mysqlx::Table tasks = ptr->getDefaultSchema().getTable("tasks");
// 查询结果
mysqlx::RowResult rows = tasks.select("id")
.where("description=:desp and deadline=:ddl and priority=:prio")
.bind("desp", description)
.bind("ddl", deadline)
.bind("prio", priority)
.execute();
// 检查结果
return rows.count() > 0;
}
catch (mysqlx::Error &e)
{
Logger::getInstance().log(LogLevel::ERROR, "task search error:", e.what());
return false;
}
}
TaskDao::TaskDao() : pool_(MySqlPool::getInstance())
{
options.emplace("id", SortOption::ID);
options.emplace("priority", SortOption::PRIORITY);
options.emplace("deadline", SortOption::DEADLINE);
options.emplace("createtime", SortOption::CREATTIME);
}
TaskDao::~TaskDao()
{
}
/**
* 增加一个完整的任务
* @task:需要增加的任务
*/
void TaskDao::addTask(const std::string &description, const std::string &deadline, int priority)
{
try
{
// 获取session连接
SessionPtr ptr = pool_.getConnection();
// 获取表
mysqlx::Table tasks = ptr->getDefaultSchema().getTable("tasks");
// 查询是否重合
if (!searchTask(description, deadline, priority))
{
tasks.insert("description", "priority", "deadline", "user_id")
.values(description, priority, deadline, UserManager::id)
.execute();
}
}
catch (mysqlx::Error &e)
{
Logger::getInstance().log(LogLevel::ERROR, "task add error:", e.what());
}
}
/**
* 根据任务ID,删除一个任务
* @id:任务ID
*/
void TaskDao::deleteTask(int id)
{
try
{
// 获取session连接
SessionPtr ptr = pool_.getConnection();
// 获取表
mysqlx::Table tasks = ptr->getDefaultSchema().getTable("tasks");
// 查询是否重合
mysqlx::RowResult rows = tasks.select("id")
.where("id=:_id")
.bind("_id", id)
.execute();
if (rows.count() > 0)
{
tasks.remove().where("id=:_id and user_id=:u_id").bind("_id", id).bind("u_id", UserManager::id).execute();
}
}
catch (mysqlx::Error &e)
{
Logger::getInstance().log(LogLevel::ERROR, "task delete error:", e.what());
}
}
/**
* 修改一个任务
* @task: 需要修改的任务
*/
void TaskDao::updateTask(const std::string &description, const std::string &deadline, int priority)
{
try
{
// 获取session连接
SessionPtr ptr = pool_.getConnection();
// 获取表
mysqlx::Table tasks = ptr->getDefaultSchema().getTable("tasks");
// 查询是否重合
if (searchTask(description, deadline, priority))
{
tasks.update().set("description", description).set("deadline", deadline).set("priority", priority).where("description=:desp and deadline=:ddl and priority=:prio and user_id=:_id").bind("desp", description).bind("ddl", deadline).bind("prio", priority).bind("_id", UserManager::id).execute();
}
}
catch (mysqlx::Error &e)
{
Logger::getInstance().log(LogLevel::ERROR, "task update error:", e.what());
}
}
/**
* 根据排序选项来展示所有任务,默认为升序排序
* @option:排序选项
*/
void TaskDao::listTasks(SortOption option)
{
try
{
// 获取session连接
SessionPtr ptr = pool_.getConnection();
// 获取表
mysqlx::Table tasks = ptr->getDefaultSchema().getTable("tasks");
mysqlx::RowResult rows;
// 获取该用户的所有行
switch (option)
{
case SortOption::ID:
rows = tasks.select("id",
"description",
"priority",
"DATE_FORMAT(deadline, '%Y-%m-%d')",
"DATE_FORMAT(createtime, '%Y-%m-%d %H:%i:%s')")
.where("user_id=:_id")
.orderBy("id")
.bind("_id", UserManager::id)
.execute();
break;
case SortOption::PRIORITY:
rows = tasks.select("id",
"description",
"priority",
"DATE_FORMAT(deadline, '%Y-%m-%d')",
"DATE_FORMAT(createtime, '%Y-%m-%d %H:%i:%s')")
.where("user_id=:_id")
.orderBy("priority")
.bind("_id", UserManager::id)
.execute();
break;
case SortOption::CREATTIME:
rows = tasks.select("id",
"description",
"priority",
"DATE_FORMAT(deadline, '%Y-%m-%d')",
"DATE_FORMAT(createtime, '%Y-%m-%d %H:%i:%s')")
.where("user_id=:_id")
.orderBy("createtime")
.bind("_id", UserManager::id)
.execute();
break;
case SortOption::DEADLINE:
rows = tasks.select("id",
"description",
"priority",
"DATE_FORMAT(deadline, '%Y-%m-%d')",
"DATE_FORMAT(createtime, '%Y-%m-%d %H:%i:%s')")
.where("user_id=:_id")
.orderBy("deadline")
.bind("_id", UserManager::id)
.execute();
break;
default:
break;
}
// 输出行
for (const auto &row : rows)
{
// 使用 get<std::string>() 显式获取格式化后的字符串
std::string deadlineStr = row[3].isNull() ? "None" : row[3].get<std::string>();
std::string createStr = row[4].isNull() ? "None" : row[4].get<std::string>();
std::cout << "id:" << row[0]
<< " description:" << row[1].get<std::string>()
<< " priority:" << row[2]
<< " deadline:" << deadlineStr
<< " createtime:" << createStr
<< std::endl;
}
}
catch (mysqlx::Error &e)
{
Logger::getInstance().log(LogLevel::ERROR, "task list error:", e.what());
std::cerr << "task list error:" << e.what() << std::endl;
}
}
cpp
#pragma once
#include"database/MysqlPool.h"
class UserDao
{
private:
//关联的Mysql
MySqlPool&pool_;
public:
/**
* 构造函数:获得数据库连接并打开任务表
*
*/
UserDao();
/**
* 析构函数:关闭数据库连接
*/
~UserDao();
/// @brief 查询是否有该用户
/// @param name:用户名
/// @param password :用户密码
/// @return 若有返回true,无返回false
int searchUser(const std::string&name,const std::string&password);
/// @brief 增加用户
/// @param name :用户名
/// @param password :用户密码
void addUser(const std::string&name,const std::string&password);
};
cpp
#include"dao/UserDao.h"
/**
* 构造函数:获得数据库连接并打开任务表
*
*/
UserDao::UserDao():pool_(MySqlPool::getInstance()){
}
/**
* 析构函数:关闭数据库连接
*/
UserDao::~UserDao(){
}
/// @brief 查询是否有该用户
/// @param name:用户名
/// @param password :用户密码
/// @return 若有返回true,无返回false
int UserDao::searchUser(const std::string&name,const std::string&password){
try{
//获取连接
SessionPtr ptr=pool_.getConnection();
//获取用户表
mysqlx::Table users=ptr->getDefaultSchema().getTable("users");
mysqlx::RowResult rows=users.select("id").where("name=:_name and password=:_password")
.bind("_name",name).bind("_password",password)
.execute();
mysqlx::Row row=rows.fetchOne();
return row[0];
}catch(mysqlx::Error&e){
Logger::getInstance().log(LogLevel::ERROR,"user search error:",e.what());
return false;
}
}
/// @brief 增加用户
/// @param name :用户名
/// @param password :用户密码
void UserDao::addUser(const std::string&name,const std::string&password){
try{
//获取连接
SessionPtr ptr=pool_.getConnection();
//获取用户表
mysqlx::Table users=ptr->getDefaultSchema().getTable("users");
if(!searchUser(name,password)){
users.insert("name","password").values(name,password).execute();
}
}catch(mysqlx::Error&e){
Logger::getInstance().log(LogLevel::ERROR,"user add error:",e.what());
}
}
cpp
#pragma once
#include"dao/TaskDao.h"
#include"dao/UserDao.h"
// 前置声明:告诉编译器 TaskDao 是个类,先别报错
class TaskDao;
class UserDao;
class MysqlMgr{
public:
//获取单例对象
static MysqlMgr& getInstance();
// 禁止拷贝和赋值
MysqlMgr(const MysqlMgr&) = delete;
MysqlMgr& operator=(const MysqlMgr&) = delete;
// 内部管理的 DAO 对象
std::unique_ptr<UserDao> _user_dao;
std::unique_ptr<TaskDao> _task_dao;
private:
// 在构造函数中统一初始化
MysqlMgr();
};
cpp
#include"database/MysqlMgr.h"
MysqlMgr::MysqlMgr(){
_user_dao = std::make_unique<UserDao>();
_task_dao = std::make_unique<TaskDao>();
}
//获取单例对象
MysqlMgr& MysqlMgr::getInstance(){
static MysqlMgr Instance;
return Instance;
}
cpp
#ifndef MYSQL_POOL_H
#define MYSQL_POOL_H
#include <iostream>
#include <string>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <memory>
#include <common/Logger.h>
#include<mysqlx/xdevapi.h>
// 前置声明
class MySqlPool;
/**
* @brief 自定义删除器
*/
struct SessionDeleter {
MySqlPool* pool_;
// 这里的 operator() 必须在 .cpp 中实现,因为它需要调用 MySqlPool 的成员函数
void operator()(mysqlx::Session* session) const;
};
// 使用别名简化智能指针写法
using SessionPtr = std::unique_ptr<mysqlx::Session, SessionDeleter>;
class MySqlPool : public std::enable_shared_from_this<MySqlPool> {
public:
// 获取连接
SessionPtr getConnection();
// 归还连接
void returnConnection(mysqlx::Session* session);
// 关闭池
void Close();
// 获取单例实例
static MySqlPool& getInstance();
// 禁止拷贝和赋值
MySqlPool(const MySqlPool&) = delete;
MySqlPool& operator=(const MySqlPool&) = delete;
private:
std::string url_;
int poolSize_;
std::queue<std::unique_ptr<mysqlx::Session>> pool_;
std::mutex mutex_;
std::condition_variable cond_;
std::atomic<bool> b_stop_;
MySqlPool(const std::string& url, int poolSize);
~MySqlPool();
};
#endif // MYSQL_POOL_H
cpp
#include "database/MysqlPool.h"
// 构造函数
//@url: mysqlx://user:password@host:port/database
MySqlPool::MySqlPool(const std::string& url, int poolSize)
: url_(url), poolSize_(poolSize), b_stop_(false) {
try {
for (int i = 0; i < poolSize_; ++i) {
auto session = std::make_unique<mysqlx::Session>(url_);
pool_.push(std::move(session));
}
Logger::getInstance().log(LogLevel::INFO, "Mysql pool init succeed");
}
catch (const mysqlx::Error& err) {
Logger::getInstance().log(LogLevel::ERROR, "Mysql pool init failed: ", err.what());
}
}
// 析构函数
MySqlPool::~MySqlPool() {
Close();
Logger::getInstance().log(LogLevel::INFO,"Mysql pool destroyed");
}
// 获取连接
SessionPtr MySqlPool::getConnection() {
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this] {
return b_stop_ || !pool_.empty();
});
if (b_stop_ || pool_.empty()) {
return nullptr;
}
std::unique_ptr<mysqlx::Session> raw_session = std::move(pool_.front());
pool_.pop();
// 传入 this 指针给删除器
// 注意:在实际生产中,为了更安全,这里可以传入 shared_from_this() 给删除器(需修改删除器成员为 shared_ptr)
// 但为了保持你原代码逻辑不变,这里沿用 this 指针
return SessionPtr(raw_session.release(), SessionDeleter{this});
}
// 归还连接
void MySqlPool::returnConnection(mysqlx::Session* session) {
if (session == nullptr) return;
std::unique_lock<std::mutex> lock(mutex_);
if (b_stop_) {
delete session;
return;
}
pool_.push(std::unique_ptr<mysqlx::Session>(session));
cond_.notify_one();
}
// 关闭池
void MySqlPool::Close() {
bool expected = false;
if (!b_stop_.compare_exchange_strong(expected, true)) {
return; // 防止重复关闭
}
cond_.notify_all();
std::unique_lock<std::mutex> lock(mutex_);
while (!pool_.empty()) {
pool_.pop(); // unique_ptr 会自动释放 Session 内存
}
}
MySqlPool& MySqlPool::getInstance(){
static MySqlPool Instance(std::string("mysqlx://root:root@127.0.0.1:33060/mysql"),5);
return Instance;
}
// 实现删除器的操作符
void SessionDeleter::operator()(mysqlx::Session* session) const {
if (pool_ && session) {
pool_->returnConnection(session);
}
}
cpp
#pragma once
#include"database/MysqlMgr.h"
class TaskManager
{
private:
MysqlMgr& mysql_mgr_;
/**
* 验证日期字符串是否符合 MySQL DATE 格式 (YYYY-MM-DD)
* 且是一个逻辑上合法的日期
*/
bool isValidMySQLDate(const std::string& dateStr);
public:
TaskManager();
~TaskManager();
/**
* 增加一个完整的任务
*/
void addTask(const std::string description,const std::string deadline,int priority=2);
/**
* 根据任务ID,删除一个任务
* @id:任务ID
*/
void deleteTask(int id);
/**
* 修改一个任务
*/
bool updateTask(const std::string description,const std::string deadline,int priority=2);
/**
* 根据排序选项来展示所有任务,默认为升序排序
* @option:排序选项
*/
void listTasks(SortOption option);
};
cpp
#include"logic/TaskManager.h"
#include <iostream>
#include <string>
#include <regex>
#include <iomanip>
#include <sstream>
/**
* 验证日期字符串是否符合 MySQL DATE 格式 (YYYY-MM-DD)
* 且是一个逻辑上合法的日期
*/
bool TaskManager::isValidMySQLDate(const std::string& dateStr) {
// 1. 初步检查:必须是 10 位长度 YYYY-MM-DD
if (dateStr.length() != 10) return false;
// 2. 正则表达式检查基本格式
// 匹配数字-数字-数字,确保基本排版正确
std::regex pattern(R"(\d{4}-\d{2}-\d{2})");
if (!std::regex_match(dateStr, pattern)) return false;
// 3. 逻辑验证:检查日期是否存在(如 13月、32号、闰年2月等)
int year, month, day;
char dash1, dash2;
std::stringstream ss(dateStr);
// 解析 YYYY-MM-DD
if (!(ss >> year >> dash1 >> month >> dash2 >> day)) return false;
// 月份校验
if (month < 1 || month > 12) return false;
// 天数校验(考虑不同月份和闰年)
int daysInMonth[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// 针对闰年调整 2 月天数
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
daysInMonth[2] = 29;
}
if (day < 1 || day > daysInMonth[month]) return false;
// MySQL DATE 范围通常是 1000-01-01 到 9999-12-31
if (year < 1000 || year > 9999) return false;
return true;
}
TaskManager::TaskManager():mysql_mgr_(MysqlMgr::getInstance())
{
}
TaskManager::~TaskManager(){
}
/**
* 增加一个完整的任务
*/
void TaskManager::addTask(const std::string description,const std::string deadline,int priority){
if(!description.empty()&&isValidMySQLDate(deadline)&&priority>=1&&priority<=3){
mysql_mgr_._task_dao->addTask(description,deadline,priority);
}else std::cout<<"输入不符合规范"<<std::endl;
}
/**
* 根据任务ID,删除一个任务
* @id:任务ID
*/
void TaskManager::deleteTask(int id){
mysql_mgr_._task_dao->deleteTask(id);
}
/**
* 修改一个任务
*/
bool TaskManager::updateTask(const std::string description,const std::string deadline,int priority){
if(!description.empty()&&isValidMySQLDate(deadline)&&priority>=1&&priority<=3){
mysql_mgr_._task_dao->updateTask(description,deadline,priority);
return true;
}else{
std::cout<<"输入不符合规范"<<std::endl;
return false;
}
}
/**
* 根据排序选项来展示所有任务,默认为升序排序
* @option:排序选项
*/
void TaskManager::listTasks(SortOption option){
mysql_mgr_._task_dao->listTasks(option);
}
cpp
#pragma once
#include"database/MysqlMgr.h"
class MysqlMgr;
class UserManager
{
private:
MysqlMgr& mysql_mgr_;
public:
static int id;
/**
* 构造函数:初始化并连接数据库
*/
UserManager();
/**
* 析构函数:关闭数据库连接
*/
~UserManager();
/**
* 处理用户注册操作
* @user:用户注册的用户名
* @password:用户注册的用户密码
* @return:false,用户名冲突;true,注册成功
*/
bool registerUser(const std::string&user,const std::string&password);
/**
* 处理用户登录操作
*@user:用户登录的用户名
*@password:用户登录的用户密码
*@return:false,用户名或密码错误;true,登录成功
*/
bool loginUser(const std::string&user,const std::string&password);
};
cpp
#include"logic/UserManager.h"
int UserManager::id=0;
/**
* 构造函数:初始化并连接数据库
*/
UserManager::UserManager():mysql_mgr_(MysqlMgr::getInstance()){
}
/**
* 析构函数:关闭数据库连接
*/
UserManager::~UserManager(){
}
/**
* 处理用户注册操作
* @user:用户注册的用户名
* @password:用户注册的用户密码
* @return:false,用户名冲突;true,注册成功
*/
bool UserManager::registerUser(const std::string&user,const std::string&password){
if(!user.empty()&&!password.empty()){
mysql_mgr_._user_dao->addUser(user,password);
return true;
}else{
std::cout<<"输入不符合规范"<<std::endl;
return false;
}
}
/**
* 处理用户登录操作
*@user:用户登录的用户名
*@password:用户登录的用户密码
*@return:false,用户名或密码错误;true,登录成功
*/
bool UserManager::loginUser(const std::string&user,const std::string&password){
if(!user.empty()&&!password.empty()){
bool res=mysql_mgr_._user_dao->searchUser(user,password);
if(res){
UserManager::id=res;
return true;
}else return false;
}else{
std::cout<<"输入不符合规范"<<std::endl;
return false;
}
}
cpp
#include"logic/UserManager.h"
int UserManager::id=0;
/**
* 构造函数:初始化并连接数据库
*/
UserManager::UserManager():mysql_mgr_(MysqlMgr::getInstance()){
}
/**
* 析构函数:关闭数据库连接
*/
UserManager::~UserManager(){
}
/**
* 处理用户注册操作
* @user:用户注册的用户名
* @password:用户注册的用户密码
* @return:false,用户名冲突;true,注册成功
*/
bool UserManager::registerUser(const std::string&user,const std::string&password){
if(!user.empty()&&!password.empty()){
mysql_mgr_._user_dao->addUser(user,password);
return true;
}else{
std::cout<<"输入不符合规范"<<std::endl;
return false;
}
}
/**
* 处理用户登录操作
*@user:用户登录的用户名
*@password:用户登录的用户密码
*@return:false,用户名或密码错误;true,登录成功
*/
bool UserManager::loginUser(const std::string&user,const std::string&password){
if(!user.empty()&&!password.empty()){
bool res=mysql_mgr_._user_dao->searchUser(user,password);
if(res){
UserManager::id=res;
return true;
}else return false;
}else{
std::cout<<"输入不符合规范"<<std::endl;
return false;
}
}
cpp
#include "logic/Command.h"
#include<sstream>
void AddCommand::executeImpl(const std::string &args)
{
// 简单的参数解析:描述,优先级,截止日期
size_t pos1 = args.find(',');
size_t pos2 = args.find(',', pos1 + 1);
if (pos1 == std::string::npos || pos2 == std::string::npos)
{
std::cout << "参数格式错误。请使用: add <描述>,<优先级>,<截止日期>" << std::endl;
return;
}
std::string description = args.substr(0, pos1);
int priority = std::stoi(args.substr(pos1 + 1, pos2 - pos1 - 1));
std::string dueDate = args.substr(pos2 + 1);
taskManager.addTask(description, dueDate, priority);
std::cout << "任务添加成功。" << std::endl;
}
void DeleteCommand::executeImpl(const std::string &args)
{
try
{
size_t pos;
int id = std::stoi(args, &pos);
if (pos != args.length())
{
std::cout << "参数格式错误。请使用: delete <ID>" << std::endl;
return;
}
taskManager.deleteTask(id);
std::cout << "任务删除成功。" << std::endl;
}
catch (const std::invalid_argument &e)
{
std::cout << "参数格式错误。请使用: delete <ID>" << std::endl;
return;
}
catch (const std::out_of_range &e)
{
std::cout << "ID超出范围。请使用有效的任务ID。" << std::endl;
return;
}
}
void ListCommand::executeImpl(const std::string &args)
{
if(TaskDao::options.contains(args)){
SortOption sortOption=TaskDao::options[args];
taskManager.listTasks(sortOption);
}else{
std::cout<<"参数格式错误。请使用: list <sortOption>"<<std::endl;
}
}
void UpdateCommand::executeImpl(const std::string &args)
{
// 预期参数格式: <描述>,<优先级>,<截止日期>
// 示例输入: "修复Bug,1,2026-05-20"
// 1. 初步校验:确保至少有 2 个逗号(对应 3 个字段)
size_t pos1 = args.find(',');
size_t pos2 = args.find(',', pos1 + 1);
if (pos1 == std::string::npos || pos2 == std::string::npos)
{
std::cout << "参数格式错误。使用方法: update <描述>,<优先级>,<截止日期>" << std::endl;
return;
}
// 2. 使用 stringstream 进行流式解析
std::stringstream ss(args);
std::string description, priorityStr, deadline;
// 解析第一个参数:描述 (读取到第一个逗号)
if (!std::getline(ss, description, ',')) return;
// 解析第二个参数:优先级 (先按字符串读到逗号,再转为 int)
if (!std::getline(ss, priorityStr, ',')) return;
// 解析第三个参数:截止日期 (读完剩下的内容)
if (!std::getline(ss, deadline)) return;
// 3. 数据转换与逻辑校验
int priority = 2; // 默认中优先级
try {
// 将优先级字符串转换为整数
priority = std::stoi(priorityStr);
} catch (...) {
std::cout << "错误:优先级必须是数字 (1:高, 2:中, 3:低)" << std::endl;
return;
}
// 4. 调用业务逻辑层
// 注意:这里需要确保 TaskManager 中有对应的接口
// 且 updateTask 内部应该根据任务描述去查找并更新
if (taskManager.updateTask(description, deadline, priority)) {
std::cout << "任务 [" << description << "] 更新成功。" << std::endl;
} else {
std::cout << "任务更新失败,请检查任务名称是否正确。" << std::endl;
}
}
void RegisterCommand::executeImpl(const std::string& args){
std::stringstream ss(args);
std::string name, password;
// 1. 尝试读取用户名
if (!std::getline(ss, name, ',')) {
// 处理错误:格式不对
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 2. 尝试读取密码(用 getline 读完剩下所有,防止密码里有空格)
if (!(ss >> password)) {
// 处理错误:没写密码
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 3. 去除用户名两端的空格(选做,增加鲁棒性)
name.erase(0, name.find_first_not_of(" "));
name.erase(name.find_last_not_of(" ") + 1);
// 4. 调用逻辑层
if(userManager.registerUser(name,password)){
std::cout<<"用户注册成功"<<std::endl;
}else std::cout<<"用户注册失败"<<std::endl;
}
void LoginCommand::executeImpl(const std::string& args){
std::stringstream ss(args);
std::string name, password;
// 1. 尝试读取用户名
if (!std::getline(ss, name, ',')) {
// 处理错误:格式不对
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 2. 尝试读取密码(用 getline 读完剩下所有,防止密码里有空格)
if (!(ss >> password)) {
// 处理错误:没写密码
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 3. 去除用户名两端的空格(选做,增加鲁棒性)
name.erase(0, name.find_first_not_of(" "));
name.erase(name.find_last_not_of(" ") + 1);
// 4. 调用逻辑层
if(userManager.loginUser(name,password)){
std::cout<<"用户注册成功"<<std::endl;
}else std::cout<<"用户注册失败"<<std::endl;
}
cpp
#include "logic/Command.h"
#include<sstream>
void AddCommand::executeImpl(const std::string &args)
{
// 简单的参数解析:描述,优先级,截止日期
size_t pos1 = args.find(',');
size_t pos2 = args.find(',', pos1 + 1);
if (pos1 == std::string::npos || pos2 == std::string::npos)
{
std::cout << "参数格式错误。请使用: add <描述>,<优先级>,<截止日期>" << std::endl;
return;
}
std::string description = args.substr(0, pos1);
int priority = std::stoi(args.substr(pos1 + 1, pos2 - pos1 - 1));
std::string dueDate = args.substr(pos2 + 1);
taskManager.addTask(description, dueDate, priority);
std::cout << "任务添加成功。" << std::endl;
}
void DeleteCommand::executeImpl(const std::string &args)
{
try
{
size_t pos;
int id = std::stoi(args, &pos);
if (pos != args.length())
{
std::cout << "参数格式错误。请使用: delete <ID>" << std::endl;
return;
}
taskManager.deleteTask(id);
std::cout << "任务删除成功。" << std::endl;
}
catch (const std::invalid_argument &e)
{
std::cout << "参数格式错误。请使用: delete <ID>" << std::endl;
return;
}
catch (const std::out_of_range &e)
{
std::cout << "ID超出范围。请使用有效的任务ID。" << std::endl;
return;
}
}
void ListCommand::executeImpl(const std::string &args)
{
if(TaskDao::options.contains(args)){
SortOption sortOption=TaskDao::options[args];
taskManager.listTasks(sortOption);
}else{
std::cout<<"参数格式错误。请使用: list <sortOption>"<<std::endl;
}
}
void UpdateCommand::executeImpl(const std::string &args)
{
// 预期参数格式: <描述>,<优先级>,<截止日期>
// 示例输入: "修复Bug,1,2026-05-20"
// 1. 初步校验:确保至少有 2 个逗号(对应 3 个字段)
size_t pos1 = args.find(',');
size_t pos2 = args.find(',', pos1 + 1);
if (pos1 == std::string::npos || pos2 == std::string::npos)
{
std::cout << "参数格式错误。使用方法: update <描述>,<优先级>,<截止日期>" << std::endl;
return;
}
// 2. 使用 stringstream 进行流式解析
std::stringstream ss(args);
std::string description, priorityStr, deadline;
// 解析第一个参数:描述 (读取到第一个逗号)
if (!std::getline(ss, description, ',')) return;
// 解析第二个参数:优先级 (先按字符串读到逗号,再转为 int)
if (!std::getline(ss, priorityStr, ',')) return;
// 解析第三个参数:截止日期 (读完剩下的内容)
if (!std::getline(ss, deadline)) return;
// 3. 数据转换与逻辑校验
int priority = 2; // 默认中优先级
try {
// 将优先级字符串转换为整数
priority = std::stoi(priorityStr);
} catch (...) {
std::cout << "错误:优先级必须是数字 (1:高, 2:中, 3:低)" << std::endl;
return;
}
// 4. 调用业务逻辑层
// 注意:这里需要确保 TaskManager 中有对应的接口
// 且 updateTask 内部应该根据任务描述去查找并更新
if (taskManager.updateTask(description, deadline, priority)) {
std::cout << "任务 [" << description << "] 更新成功。" << std::endl;
} else {
std::cout << "任务更新失败,请检查任务名称是否正确。" << std::endl;
}
}
void RegisterCommand::executeImpl(const std::string& args){
std::stringstream ss(args);
std::string name, password;
// 1. 尝试读取用户名
if (!std::getline(ss, name, ',')) {
// 处理错误:格式不对
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 2. 尝试读取密码(用 getline 读完剩下所有,防止密码里有空格)
if (!(ss >> password)) {
// 处理错误:没写密码
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 3. 去除用户名两端的空格(选做,增加鲁棒性)
name.erase(0, name.find_first_not_of(" "));
name.erase(name.find_last_not_of(" ") + 1);
// 4. 调用逻辑层
if(userManager.registerUser(name,password)){
std::cout<<"用户注册成功"<<std::endl;
}else std::cout<<"用户注册失败"<<std::endl;
}
void LoginCommand::executeImpl(const std::string& args){
std::stringstream ss(args);
std::string name, password;
// 1. 尝试读取用户名
if (!std::getline(ss, name, ',')) {
// 处理错误:格式不对
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 2. 尝试读取密码(用 getline 读完剩下所有,防止密码里有空格)
if (!(ss >> password)) {
// 处理错误:没写密码
std::cout<<"参数格式错误。请使用: register <name>,<Password>"<<std::endl;
return;
}
// 3. 去除用户名两端的空格(选做,增加鲁棒性)
name.erase(0, name.find_first_not_of(" "));
name.erase(name.find_last_not_of(" ") + 1);
// 4. 调用逻辑层
if(userManager.loginUser(name,password)){
std::cout<<"用户注册成功"<<std::endl;
}else std::cout<<"用户注册失败"<<std::endl;
}
cmake
cmake_minimum_required(VERSION 3.20)
project(TASKSMANAGER CXX)
if(MSVC)
add_compile_options(/utf-8)
endif()
#包含头文件
include_directories(${CMAKE_SOURCE_DIR}/include)
# 设置 C++ 标准为 20
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 寻找库
find_package(unofficial-mysql-connector-cpp CONFIG REQUIRED)
# 5. 搜集所有的源文件
file(GLOB_RECURSE SOURCES
"src/database/*.cpp"
"src/dao/*.cpp"
"src/logic/*.cpp"
"src/main.cpp"
"src/common/*.cpp"
)
#构建主要目标文件
add_executable(target ${SOURCES})
target_link_libraries(target PUBLIC
unofficial::mysql-connector-cpp::connector)
yaml
version: '3.8'
services:
# MySQL 服务配置
mysql-db:
image: mysql:8.0
container_name: llfcmysql
restart: always
ports:
- "3306:3306" # 宿主机端口:容器端口
- "33060:33060" #xdevapi
environment:
MYSQL_ROOT_PASSWORD: "root"
volumes:
# 映射配置文件、数据目录和日志
- ./mysql/config/my.cnf:/etc/mysql/my.cnf
- ./mysql/data:/var/lib/mysql
- ./mysql/logs:/logs
networks:
- app-network
networks:
app-network:
driver: bridge