从零打造个人任务管理器 CLI 工具:Node.js + SQLite 实战
折
折腾侠
2026/03/27 发布
14约 10 分钟1459 字 / 1294 词00
从零打造个人任务管理器 CLI 工具:Node.js + SQLite 实战
项目背景
在日常开发工作中,我们经常需要管理各种任务:待办事项、项目进度、会议安排等等。虽然市面上有很多优秀的任务管理工具,但作为一个开发者,为什么不自己动手打造一个轻量级、完全可控的 CLI 任务管理器呢?
本文将带你从零开始,使用 Node.js 和 SQLite 构建一个功能完整的命令行任务管理工具。这个项目不仅实用,还能帮助你深入理解 CLI 开发、数据库操作和项目架构设计。
项目功能说明
核心功能
- 任务创建:支持添加新任务,包含标题、描述、优先级、截止日期
- 任务列表:查看所有任务,支持按状态、优先级筛选
- 任务更新:修改任务信息,标记完成状态
- 任务删除:删除单个任务或批量清理已完成任务
- 任务统计:显示任务完成情况统计信息
- 数据持久化:使用 SQLite 本地存储,数据安全可靠
扩展功能
- 支持任务分类/标签
- 支持子任务
- 支持任务提醒(结合系统通知)
- 支持数据导出(JSON/CSV 格式)
技术栈选择
核心技术
| 技术 | 版本 | 选择理由 |
|---|---|---|
| Node.js | v18+ | 成熟的运行时生态,丰富的 npm 包支持 |
| SQLite | 3.x | 轻量级嵌入式数据库,零配置,单文件存储 |
| Commander.js | v11+ | 流行的 CLI 框架,API 简洁易用 |
| Better-SQLite3 | v9+ | SQLite 的同步 Node.js 绑定,性能优秀 |
开发工具
| 工具 | 用途 |
|---|---|
| ESLint | 代码质量检查 |
| Prettier | 代码格式化 |
| Jest | 单元测试 |
| Nodemon | 开发时自动重启 |
为什么选择这些技术?
SQLite 相比其他数据库的优势:
- 无需安装数据库服务器,开箱即用
- 单文件存储,便于备份和迁移
- 支持完整的 SQL 语法,功能强大
- 读写性能优秀,适合本地应用
Better-SQLite3 相比其他 SQLite 库:
- 同步 API,代码更简洁直观
- 性能比异步方案更好
- 类型支持完善,配合 TypeScript 友好
项目结构设计
task-cli/
├── src/
│ ├── index.js # 入口文件
│ ├── db/
│ │ ├── database.js # 数据库连接与初始化
│ │ └── schema.sql # 数据库表结构
│ ├── commands/
│ │ ├── add.js # 添加任务命令
│ │ ├── list.js # 列表命令
│ │ ├── update.js # 更新命令
│ │ ├── delete.js # 删除命令
│ │ ├── complete.js # 完成命令
│ │ └── stats.js # 统计命令
│ ├── utils/
│ │ ├── formatter.js # 输出格式化
│ │ └── validator.js # 输入验证
│ └── config/
│ └── constants.js # 常量配置
├── tests/
│ ├── db.test.js
│ └── commands.test.js
├── package.json
├── README.md
└── .eslintrc.js
目录设计原则
- 按功能模块划分:commands 目录存放所有命令实现
- 关注点分离:数据库、工具函数、配置各自独立
- 易于测试:每个模块可独立单元测试
- 便于扩展:新增命令只需在 commands 目录添加文件
核心代码实现
1. 数据库模块 (src/db/database.js)
JavaScript
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');
const DB_PATH = path.join(process.env.HOME, '.task-cli', 'tasks.db');
// 确保数据库目录存在
function ensureDbDir() {
const dir = path.dirname(DB_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
// 初始化数据库
function initDatabase() {
ensureDbDir();
const db = new Database(DB_PATH);
// 启用外键约束
db.pragma('foreign_keys = ON');
// 创建表结构
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 2 CHECK(priority BETWEEN 1 AND 3),
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed')),
due_date DATETIME,
category TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// 创建索引提升查询性能
db.exec(`
CREATE INDEX IF NOT EXISTS idx_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_priority ON tasks(priority);
CREATE INDEX IF NOT EXISTS idx_due_date ON tasks(due_date);
`);
return db;
}
module.exports = { initDatabase, DB_PATH };
2. 添加任务命令 (src/commands/add.js)
JavaScript
const { validateTaskInput } = require('../utils/validator');
function addTask(db, options) {
// 验证输入
const validation = validateTaskInput(options);
if (!validation.valid) {
console.error(`❌ 输入错误:${validation.error}`);
process.exit(1);
}
const { title, description, priority, due, category } = options;
// 插入数据库
const stmt = db.prepare(`
INSERT INTO tasks (title, description, priority, due_date, category)
VALUES (?, ?, ?, ?, ?)
`);
const result = stmt.run(
title,
description || null,
priority || 2,
due || null,
category || null
);
console.log(`✅ 任务已创建 (ID: ${result.lastInsertRowid})`);
console.log(` 标题:${title}`);
if (priority) {
const priorityMap = { 1: '高', 2: '中', 3: '低' };
console.log(` 优先级:${priorityMap[priority]}`);
}
if (due) {
console.log(` 截止日期:${due}`);
}
}
module.exports = addTask;
3. 列表命令 (src/commands/list.js)
JavaScript
const { formatTasks } = require('../utils/formatter');
function listTasks(db, options) {
const { status, priority, category, limit } = options;
// 构建动态查询
let query = 'SELECT * FROM tasks WHERE 1=1';
const params = [];
if (status) {
query += ' AND status = ?';
params.push(status);
}
if (priority) {
query += ' AND priority = ?';
params.push(priority);
}
if (category) {
query += ' AND category = ?';
params.push(category);
}
query += ' ORDER BY priority ASC, due_date ASC';
if (limit) {
query += ' LIMIT ?';
params.push(limit);
}
const stmt = db.prepare(query);
const tasks = stmt.all(...params);
if (tasks.length === 0) {
console.log('📭 没有找到符合条件的任务');
return;
}
console.log(`\n📋 共找到 ${tasks.length} 个任务:\n`);
formatTasks(tasks);
}
module.exports = listTasks;
4. 输出格式化 (src/utils/formatter.js)
JavaScript
const chalk = require('chalk');
const PRIORITY_MAP = {
1: { label: '高', color: 'red' },
2: { label: '中', color: 'yellow' },
3: { label: '低', color: 'green' }
};
const STATUS_MAP = {
pending: { label: '待处理', icon: '⏳', color: 'gray' },
in_progress: { label: '进行中', icon: '🔄', color: 'blue' },
completed: { label: '已完成', icon: '✅', color: 'green' }
};
function formatTasks(tasks) {
tasks.forEach((task, index) => {
const priority = PRIORITY_MAP[task.priority];
const status = STATUS_MAP[task.status];
console.log(
chalk.bold(`[${task.id}]`),
chalk[status.color](status.icon),
chalk[priority.color](`[${priority.label}]`),
task.title
);
if (task.description) {
console.log(chalk.gray(` ${task.description}`));
}
if (task.due_date) {
const isOverdue = new Date(task.due_date) < new Date() && task.status !== 'completed';
const color = isOverdue ? 'red' : 'gray';
console.log(chalk[color](` 📅 截止:${task.due_date}`));
}
if (task.category) {
console.log(chalk.gray(` 🏷️ 分类:${task.category}`));
}
console.log('');
});
}
module.exports = { formatTasks, PRIORITY_MAP, STATUS_MAP };
5. 主入口文件 (src/index.js)
JavaScript
#!/usr/bin/env node
const { Command } = require('commander');
const { initDatabase } = require('./db/database');
const program = new Command();
program
.name('task-cli')
.description('个人任务管理器 CLI 工具')
.version('1.0.0');
// 添加任务
program
.command('add <title>')
.description('添加新任务')
.option('-d, --description <text>', '任务描述')
.option('-p, --priority <1|2|3>', '优先级 (1:高, 2:中, 3:低)', '2')
.option('--due <date>', '截止日期 (YYYY-MM-DD)')
.option('-c, --category <name>', '任务分类')
.action((title, options) => {
const db = initDatabase();
require('./commands/add')(db, { ...options, title });
db.close();
});
// 列表任务
program
.command('list')
.description('查看任务列表')
.option('-s, --status <status>', '按状态筛选')
.option('-p, --priority <1|2|3>', '按优先级筛选')
.option('-c, --category <name>', '按分类筛选')
.option('-l, --limit <number>', '限制显示数量')
.action((options) => {
const db = initDatabase();
require('./commands/list')(db, options);
db.close();
});
// 完成任务
program
.command('complete <id>')
.description('标记任务为完成')
.action((id, options) => {
const db = initDatabase();
require('./commands/complete')(db, { id });
db.close();
});
// 删除任务
program
.command('delete <id>')
.description('删除任务')
.option('-f, --force', '跳过确认')
.action((id, options) => {
const db = initDatabase();
require('./commands/delete')(db, { id, ...options });
db.close();
});
// 统计信息
program
.command('stats')
.description('显示任务统计')
.action((options) => {
const db = initDatabase();
require('./commands/stats')(db);
db.close();
});
program.parse();
运行步骤
第一步:环境准备
Bash
# 检查 Node.js 版本 (需要 v18+)
node --version
# 创建项目目录
mkdir task-cli && cd task-cli
# 初始化项目
npm init -y
第二步:安装依赖
Bash
# 安装核心依赖
npm install better-sqlite3 commander chalk
# 安装开发依赖
npm install --save-dev eslint prettier jest nodemon
第三步:创建项目结构
Bash
# 创建目录结构
mkdir -p src/{db,commands,utils,config}
mkdir -p tests
# 创建入口文件并添加执行权限
touch src/index.js
chmod +x src/index.js
第四步:配置 package.json
JSON
{
"name": "task-cli",
"version": "1.0.0",
"description": "个人任务管理器 CLI 工具",
"main": "src/index.js",
"bin": {
"task": "./src/index.js"
},
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest",
"lint": "eslint src/"
},
"keywords": ["cli", "task", "manager"],
"author": "",
"license": "MIT"
}
第五步:全局安装使用
Bash
# 链接到全局
npm link
# 现在可以在任何地方使用
task --help
task add "完成项目文档" -p 1 --due 2024-04-01
task list
task complete 1
task stats
使用示例
Bash
# 添加高优先级任务
$ task add "完成季度报告" -p 1 --due 2024-04-15 -c 工作
✅ 任务已创建 (ID: 1)
标题:完成季度报告
优先级:高
截止日期:2024-04-15
🏷️ 分类:工作
# 查看所有待处理任务
$ task list -s pending
📋 共找到 3 个任务:
[1] ⏳ [高] 完成季度报告
📅 截止:2024-04-15
🏷️ 分类:工作
[2] ⏳ [中] 更新项目文档
📅 截止:2024-04-20
[3] ⏳ [低] 整理代码规范
🏷️ 分类:学习
# 查看统计信息
$ task stats
📊 任务统计
━━━━━━━━━━━━━━━━
总任务数:15
已完成:8 (53%)
进行中:2 (13%)
待处理:5 (33%)
高优先级待处理:2 ⚠️
即将到期 (3 天内): 3
项目扩展方向
完成基础功能后,可以考虑以下扩展:
1. 子任务支持
SQL
CREATE TABLE subtasks (
id INTEGER PRIMARY KEY,
parent_task_id INTEGER,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT 0,
FOREIGN KEY (parent_task_id) REFERENCES tasks(id)
);
2. 任务提醒
结合 INLINE_CODE_0 实现系统通知:
JavaScript
const notifier = require('node-notifier');
function notifyTask(task) {
notifier.notify({
title: '任务提醒',
message: `任务 "${task.title}" 即将到期`,
urgency: 'critical'
});
}
3. 数据同步
添加远程同步功能,支持多设备数据同步:
- 使用 GitHub Gist 作为存储后端
- 或自建同步服务器
- 支持导入/导出 JSON 备份
4. 交互式界面
使用 INLINE_CODE_1 添加交互式命令:
JavaScript
const inquirer = require('inquirer');
async function interactiveAdd() {
const answers = await inquirer.prompt([
{ type: 'input', name: 'title', message: '任务标题' },
{ type: 'input', name: 'description', message: '任务描述' },
{ type: 'list', name: 'priority', choices: ['高', '中', '低'] }
]);
// 处理添加逻辑
}
总结
通过这个项目,我们完成了:
- ✅ 一个功能完整的 CLI 任务管理器
- ✅ 使用 SQLite 实现数据持久化
- ✅ 模块化架构设计,易于维护和扩展
- ✅ 丰富的命令行选项和友好的输出
- ✅ 完整的开发工具链配置
这个项目不仅实用,还能帮助你掌握:
- Node.js CLI 开发流程
- SQLite 数据库设计与操作
- 模块化代码组织
- 命令行参数解析
- 输出格式化技巧
你可以在此基础上继续扩展,添加更多实用功能,或者将其作为学习 Node.js 开发的实践项目。源码已准备好,现在就开始动手构建属于你的任务管理器吧!
项目源码:可根据本文代码自行创建,或访问 GitHub 获取完整示例 技术栈:Node.js | SQLite | Commander.js | Better-SQLite3 难度:⭐⭐⭐☆☆ 中级 预计耗时:2-4 小时