1. 项目背景与核心价值
最近在牛客网刷题时发现一个痛点:虽然平台提供了丰富的题库资源,但缺乏系统化的刷题进度追踪和每日任务管理功能。作为一个有强迫症的程序员,我决定自己动手开发一个"多米诺骨牌"式的刷题追踪系统,这个名字来源于刷题就像推倒多米诺骨牌一样,需要持续不断的积累才能产生连锁效应。
这个工具主要解决三个核心问题:
- 可视化刷题进度(特别是牛客网的《剑指Offer》和《程序员面试金典》系列)
- 自动生成每日一题推荐
- 记录每道题的解题思路和代码模板
2. 系统架构设计
2.1 技术选型分析
前端采用React + Ant Design组合,主要考虑因素:
- 牛客网本身使用React技术栈,方便后续可能的浏览器插件开发
- Ant Design的Table组件非常适合展示题目列表和进度追踪
- 社区生态丰富,遇到问题容易找到解决方案
后端选择Node.js + Express轻量级框架:
- 与前端技术栈同属JavaScript生态,开发效率高
- 足够支撑个人刷题场景的数据处理需求
- 便于部署到主流云服务平台
数据库选用MongoDB:
- 题目和用户数据的结构不固定,NoSQL更灵活
- 文档型数据库天然适合存储题解和代码片段
- 开发阶段可以使用免费的MongoDB Atlas服务
2.2 数据模型设计
核心集合设计如下:
javascript复制// 题目集合
const ProblemSchema = new Schema({
pid: Number, // 牛客题目ID
title: String,
difficulty: { type: String, enum: ['简单', '中等', '困难'] },
tags: [String],
url: String,
frequency: { type: Number, default: 0 } // 被推荐次数
});
// 用户进度集合
const ProgressSchema = new Schema({
userId: String,
problems: [{
pid: Number,
status: { type: String, enum: ['未开始', '进行中', '已完成'] },
lastPractice: Date,
notes: String,
code: String
}],
streak: { type: Number, default: 0 } // 连续打卡天数
});
3. 核心功能实现
3.1 牛客题目数据获取
通过分析牛客网页面结构,发现可以直接调用其内部API获取题目数据:
javascript复制async function fetchNowcoderProblems() {
const apiUrl = 'https://www.nowcoder.com/api/questiontraining/coding/question/list';
const response = await axios.post(apiUrl, {
tagIds: [], // 空数组获取全部题目
page: 1,
size: 1000
});
return response.data.data.questions.map(q => ({
pid: q.questionId,
title: q.questionTitle,
difficulty: ['简单', '中等', '困难'][q.difficulty - 1],
tags: q.questionTag.split(','),
url: `https://www.nowcoder.com/practice/${q.questionId}`
}));
}
注意:实际使用中需要添加适当的请求头和错误处理,避免被反爬机制限制
3.2 每日一题推荐算法
推荐策略综合考虑以下因素:
- 题目难度与用户当前水平匹配
- 未完成题目优先
- 长期未复习的题目
- 高频考点题目
javascript复制function recommendDailyProblem(userId) {
// 获取用户进度
const progress = await Progress.findOne({ userId });
// 获取所有题目
const allProblems = await Problem.find();
// 计算推荐权重
const scoredProblems = allProblems.map(p => {
const userProblem = progress.problems.find(up => up.pid === p.pid);
let score = 0;
// 未做过的题目基础分更高
if (!userProblem || userProblem.status === '未开始') {
score += 50;
}
// 根据难度调整分数
score += p.difficulty === '简单' ? 10 :
p.difficulty === '中等' ? 20 : 30;
// 长期未复习的题目加分
if (userProblem?.lastPractice) {
const daysSinceLast = (new Date() - userProblem.lastPractice) / (1000*60*60*24);
score += Math.min(daysSinceLast * 2, 30);
}
// 高频考点题目加分
score += Math.min(p.frequency * 0.5, 20);
return { ...p._doc, score };
});
// 按分数降序排序并返回最高分题目
return scoredProblems.sort((a, b) => b.score - a.score)[0];
}
3.3 进度可视化实现
使用Ant Design的Table和Progress组件展示刷题进度:
jsx复制function ProgressTable({ problems }) {
const columns = [
{
title: '题目',
dataIndex: 'title',
render: (text, record) => (
<a href={record.url} target="_blank" rel="noopener noreferrer">
{text}
</a>
)
},
{
title: '难度',
dataIndex: 'difficulty',
render: text => {
const color = text === '简单' ? 'green' :
text === '中等' ? 'orange' : 'red';
return <Tag color={color}>{text}</Tag>;
}
},
{
title: '状态',
dataIndex: 'status',
render: (text, record) => (
<Progress
percent={text === '已完成' ? 100 :
text === '进行中' ? 50 : 0}
status={text === '已完成' ? 'success' : 'active'}
/>
)
}
];
return <Table
columns={columns}
dataSource={problems}
rowKey="pid"
pagination={false}
/>;
}
4. 部署与使用指南
4.1 本地开发环境搭建
- 安装依赖:
bash复制git clone https://github.com/your-repo/nowcoder-tracker.git
cd nowcoder-tracker
npm install
- 配置环境变量:
env复制MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.example.mongodb.net/nowcoder-tracker
SESSION_SECRET=your_secret_key
- 启动开发服务器:
bash复制npm run dev
4.2 生产环境部署
推荐使用Vercel一键部署:
- 新建Vercel项目并连接GitHub仓库
- 配置环境变量(同开发环境)
- 部署完成后设置自定义域名
提示:MongoDB Atlas提供免费的512MB存储空间,完全够个人使用
5. 常见问题与解决方案
5.1 数据同步问题
问题现象:牛客网题目更新后本地数据库不同步
解决方案:
javascript复制// 每周自动同步一次题目数据
const syncProblems = async () => {
const ncProblems = await fetchNowcoderProblems();
const localProblems = await Problem.find();
const newProblems = ncProblems.filter(ncp =>
!localProblems.some(lp => lp.pid === ncp.pid)
);
if (newProblems.length) {
await Problem.insertMany(newProblems);
console.log(`新增${newProblems.length}道题目`);
}
};
// 使用node-schedule设置每周任务
schedule.scheduleJob('0 0 * * 0', syncProblems);
5.2 推荐算法优化
问题现象:推荐的题目难度不适合当前水平
调整方案:
- 在用户模型中添加能力评估字段:
javascript复制ability: {
easy: { type: Number, default: 0 }, // 简单题正确率
medium: { type: Number, default: 0 },
hard: { type: Number, default: 0 }
}
- 修改推荐算法中的难度评分逻辑:
javascript复制// 根据用户能力调整难度分
const abilityScore = user.ability[p.difficulty === '简单' ? 'easy' :
p.difficulty === '中等' ? 'medium' : 'hard'];
score += abilityScore * 0.5; // 能力越高,该难度题目得分越高
6. 扩展功能思路
- 刷题成就系统:设置里程碑奖励,如"连续7天打卡"、"完成50道中等题"等
- 题目收藏夹:允许用户创建自定义题单,如"动态规划专题"
- 代码对比工具:记录同一题目的多次提交,可视化改进过程
- 面试模拟功能:随机抽取3-5道题目组成模拟面试题集
这个项目我已经持续使用了3个月,最大的感受是可视化进度确实能带来很强的成就感激励。特别是连续打卡记录被中断时,那种"不甘心"的感觉反而促使我更规律地刷题。目前我的剑指Offer完成率从最初的23%提升到了87%,推荐给所有正在准备技术面试的朋友。