多用户任务管理器

任务管理系统

简洁、一目了然的任务管理服务(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)

这一层直接负责与数据库连接及具体的表操作交互。

  • UserDaoTaskDao 分别是用户表和任务表的数据访问对象。
  • 它们在执行具体的 DML 操作前,都需要从中央的 MysqlPool(MySQL连接池) 中"获得连接"。
  • MysqlPool 负责管理和分配与底层数据库的健康长连接。
5. 持久化层(Mysql)

位于架构的最顶端,作为整个系统的数据终点。

  • MysqlPool 负责维护与它的连接状态。
  • UserDaoTaskDao 在拿到连接池分配的句柄后,直接对 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
相关推荐
跨境数据猎手1 小时前
跨境电商平台系统开发全流程
爬虫·系统架构·个人开发
mmz12071 小时前
深度优先搜索DFS3(c++)
c++·算法·深度优先
故事和你911 小时前
洛谷-【图论2-1】树6
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
不知名的老吴1 小时前
在C++中不用宏怎么打日志的使用建议
开发语言·c++·算法
蜡笔小马2 小时前
09.C++设计模式-外观模式
c++·设计模式·外观模式
晚会者荣2 小时前
C++11_3:包装器,智能指针
c++
handler012 小时前
TCP(传输控制协议)核心机制与底层原理
linux·网络·c++·笔记·网络协议·tcp/ip·操作系统
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串综合】:遍历问题
c++·字符串·csp·高频考点·信奥赛
yoyo_zzm2 小时前
五大编程语言对比:PHP、C、C++、C#、易语言
c语言·c++·php