This repository has been archived on 2026-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
todo/server/index.js
2025-06-13 15:31:12 +08:00

612 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const mysql = require('mysql2/promise');
const cors = require('cors');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 5000;
const JWT_SECRET = 'weiMonkey2024_secret_key';
// 中间件
app.use(cors());
app.use(express.json());
// 数据库连接配置
const dbConfig = {
host: 'localhost',
port: 3306,
user: 'root',
password: '123456',
database: 'myuser'
};
// 创建数据库连接池
const pool = mysql.createPool(dbConfig);
// 初始化数据库表
async function initDatabase() {
try {
const connection = await pool.getConnection();
// 创建用户表
await connection.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
is_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// 创建待办事项表
await connection.execute(`
CREATE TABLE IF NOT EXISTS todos (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
completed BOOLEAN DEFAULT FALSE,
suspended BOOLEAN DEFAULT FALSE,
date DATE NOT NULL,
suspended_date DATE NULL,
user_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// 添加user_id字段如果表已存在但没有该字段
try {
await connection.execute(`
ALTER TABLE todos
ADD COLUMN user_id INT NOT NULL,
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
`);
} catch (error) {
// 字段可能已存在,忽略错误
console.log('User_id column may already exist');
}
// 添加suspended字段如果表已存在但没有该字段
try {
await connection.execute(`
ALTER TABLE todos
ADD COLUMN suspended BOOLEAN DEFAULT FALSE,
ADD COLUMN suspended_date DATE NULL
`);
} catch (error) {
// 字段可能已存在,忽略错误
console.log('Suspended columns may already exist');
}
// 添加description字段如果表已存在但没有该字段
try {
await connection.execute(`
ALTER TABLE todos
ADD COLUMN description TEXT
`);
} catch (error) {
// 字段可能已存在,忽略错误
console.log('Description column may already exist');
}
// 创建默认管理员用户
const adminPassword = await bcrypt.hash('weiMonkey2024', 10);
try {
await connection.execute(`
INSERT INTO users (username, password, is_admin)
VALUES ('admin', ?, TRUE)
`, [adminPassword]);
console.log('默认管理员用户创建成功');
} catch (error) {
// 用户可能已存在
console.log('默认管理员用户可能已存在');
}
connection.release();
console.log('数据库表初始化成功');
} catch (error) {
console.error('数据库初始化失败:', error);
}
}
// 验证密码的中间件
const authenticatePassword = (req, res, next) => {
const { password } = req.body;
const correctPassword = 'weiMonkey2024';
if (password !== correctPassword) {
return res.status(401).json({ error: '密码错误' });
}
next();
};
// 验证token的中间件
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '需要认证' });
}
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: '无效的token' });
}
req.user = user;
next();
});
};
// 验证管理员权限的中间件
const authenticateAdmin = (req, res, next) => {
if (!req.user.is_admin) {
return res.status(403).json({ error: '需要管理员权限' });
}
next();
};
// 路由
// 用户登录
app.post('/api/auth/login', async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '用户名和密码是必需的' });
}
const connection = await pool.getConnection();
// 查找用户
const [users] = await connection.execute(
'SELECT * FROM users WHERE username = ?',
[username]
);
connection.release();
if (users.length === 0) {
return res.status(401).json({ error: '用户名或密码错误' });
}
const user = users[0];
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: '用户名或密码错误' });
}
// 生成token
const token = jwt.sign(
{
userId: user.id,
username: user.username,
is_admin: user.is_admin
},
JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({
success: true,
token,
user: {
id: user.id,
username: user.username,
is_admin: user.is_admin
}
});
} catch (error) {
console.error('登录失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 管理员添加用户
app.post('/api/auth/register', authenticateToken, authenticateAdmin, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '用户名和密码是必需的' });
}
if (password.length < 6) {
return res.status(400).json({ error: '密码长度至少6位' });
}
const connection = await pool.getConnection();
// 检查用户名是否已存在
const [existingUsers] = await connection.execute(
'SELECT id FROM users WHERE username = ?',
[username]
);
if (existingUsers.length > 0) {
connection.release();
return res.status(400).json({ error: '用户名已存在' });
}
// 创建新用户
const hashedPassword = await bcrypt.hash(password, 10);
const [result] = await connection.execute(
'INSERT INTO users (username, password, is_admin) VALUES (?, ?, FALSE)',
[username, hashedPassword]
);
connection.release();
res.json({
success: true,
message: '用户创建成功',
userId: result.insertId
});
} catch (error) {
console.error('注册用户失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 获取所有用户(仅管理员)
app.get('/api/users', authenticateToken, authenticateAdmin, async (req, res) => {
try {
const connection = await pool.getConnection();
const [users] = await connection.execute(
'SELECT id, username, is_admin, created_at FROM users ORDER BY created_at DESC'
);
connection.release();
res.json(users);
} catch (error) {
console.error('获取用户列表失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 获取历史待办事项(一周之前的)
app.get('/api/todos/history', authenticateToken, async (req, res) => {
try {
const connection = await pool.getConnection();
// 获取一周之前的历史数据
const today = new Date();
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
// 管理员可以查看所有用户的数据,普通用户只能查看自己的
let query = 'SELECT * FROM todos WHERE date < ? AND user_id = ? ORDER BY date DESC, priority DESC, created_at DESC';
let params = [oneWeekAgo.toISOString().split('T')[0]];
params.push(req.user.userId);
const [rows] = await connection.execute(query, params);
connection.release();
res.json(rows);
} catch (error) {
console.error('获取历史待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 获取最近一周的待办事项(排除挂起的)
app.get('/api/todos', authenticateToken, async (req, res) => {
try {
const connection = await pool.getConnection();
// 获取最近一周的日期范围
const today = new Date();
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
// 管理员可以查看所有用户的数据,普通用户只能查看自己的
let query = 'SELECT * FROM todos WHERE date >= ? AND date <= ? AND suspended = FALSE AND user_id = ? ORDER BY date DESC, priority DESC, created_at DESC';
let params = [oneWeekAgo.toISOString().split('T')[0], today.toISOString().split('T')[0]];
params.push(req.user.userId);
const [rows] = await connection.execute(query, params);
connection.release();
res.json(rows);
} catch (error) {
console.error('获取待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 创建新的待办事项
app.post('/api/todos', authenticateToken, async (req, res) => {
try {
const { title, priority, date, description } = req.body;
if (!title || !date) {
return res.status(400).json({ error: '标题和日期是必需的' });
}
const connection = await pool.getConnection();
const [result] = await connection.execute(
'INSERT INTO todos (title, description, priority, date, user_id) VALUES (?, ?, ?, ?, ?)',
[title, description || null, priority || 'medium', date, req.user.userId]
);
const [newTodo] = await connection.execute(
'SELECT * FROM todos WHERE id = ?',
[result.insertId]
);
connection.release();
res.status(201).json(newTodo[0]);
} catch (error) {
console.error('创建待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 更新待办事项状态
app.put('/api/todos/:id', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const { completed, title, priority, date, description } = req.body;
const connection = await pool.getConnection();
// 检查待办事项是否存在且属于当前用户(管理员可以修改所有)
let checkQuery = 'SELECT * FROM todos WHERE id = ?';
let checkParams = [id];
if (!req.user.is_admin) {
checkQuery = 'SELECT * FROM todos WHERE id = ? AND user_id = ?';
checkParams.push(req.user.userId);
}
const [existingTodos] = await connection.execute(checkQuery, checkParams);
if (existingTodos.length === 0) {
connection.release();
return res.status(404).json({ error: '待办事项不存在或无权限访问' });
}
// 构建更新字段和值
const updates = [];
const values = [];
if (completed !== undefined) {
updates.push('completed = ?');
values.push(completed);
}
if (title !== undefined) {
updates.push('title = ?');
values.push(title);
}
if (priority !== undefined) {
updates.push('priority = ?');
values.push(priority);
}
if (date !== undefined) {
updates.push('date = ?');
values.push(date);
}
if (description !== undefined) {
updates.push('description = ?');
values.push(description || null);
}
if (updates.length === 0) {
connection.release();
return res.status(400).json({ error: '没有提供要更新的字段' });
}
// 添加 id 到值数组
values.push(id);
// 执行更新
await connection.execute(
`UPDATE todos SET ${updates.join(', ')} WHERE id = ?`,
values
);
// 获取更新后的待办事项
const [updatedTodo] = await connection.execute(
'SELECT * FROM todos WHERE id = ?',
[id]
);
connection.release();
res.json(updatedTodo[0]);
} catch (error) {
console.error('更新待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 删除待办事项
app.delete('/api/todos/:id', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const connection = await pool.getConnection();
// 检查待办事项是否存在且属于当前用户(管理员可以删除所有)
let checkQuery = 'SELECT * FROM todos WHERE id = ?';
let checkParams = [id];
if (!req.user.is_admin) {
checkQuery = 'SELECT * FROM todos WHERE id = ? AND user_id = ?';
checkParams.push(req.user.userId);
}
const [existingTodos] = await connection.execute(checkQuery, checkParams);
if (existingTodos.length === 0) {
connection.release();
return res.status(404).json({ error: '待办事项不存在或无权限访问' });
}
await connection.execute('DELETE FROM todos WHERE id = ?', [id]);
connection.release();
res.json({ success: true });
} catch (error) {
console.error('删除待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 挂起今天的待办事项
app.put('/api/todos/:id/suspend', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const today = new Date().toISOString().split('T')[0];
const connection = await pool.getConnection();
// 检查是否是今天的待办事项且属于当前用户
let checkQuery = 'SELECT * FROM todos WHERE id = ? AND date = ? AND user_id = ?';
let checkParams = [id, today];
checkParams.push(req.user.userId);
const [todoCheck] = await connection.execute(checkQuery, checkParams);
if (todoCheck.length === 0) {
connection.release();
return res.status(400).json({ error: '只能挂起今天的待办事项或无权限访问' });
}
// 更新挂起状态
await connection.execute(
'UPDATE todos SET suspended = TRUE, suspended_date = ? WHERE id = ?',
[today, id]
);
const [updatedTodo] = await connection.execute(
'SELECT * FROM todos WHERE id = ?',
[id]
);
connection.release();
res.json(updatedTodo[0]);
} catch (error) {
console.error('挂起待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 获取所有挂起的待办事项
app.get('/api/todos/suspended', authenticateToken, async (req, res) => {
try {
const connection = await pool.getConnection();
// 管理员可以查看所有用户的挂起待办,普通用户只能查看自己的
let query = 'SELECT * FROM todos WHERE suspended = TRUE AND user_id = ? ORDER BY suspended_date DESC, priority DESC, created_at DESC';
let params = [];
params.push(req.user.userId);
const [rows] = await connection.execute(query, params);
connection.release();
res.json(rows);
} catch (error) {
console.error('获取挂起待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 恢复挂起的待办事项到今天
app.put('/api/todos/:id/resume', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const today = new Date().toISOString().split('T')[0];
const connection = await pool.getConnection();
// 检查是否是挂起的待办事项且属于当前用户
let checkQuery = 'SELECT * FROM todos WHERE id = ? AND suspended = TRUE AND user_id = ?';
let checkParams = [id];
checkParams.push(req.user.userId);
const [todoCheck] = await connection.execute(checkQuery, checkParams);
if (todoCheck.length === 0) {
connection.release();
return res.status(400).json({ error: '待办事项未处于挂起状态或无权限访问' });
}
// 恢复待办事项到今天
await connection.execute(
'UPDATE todos SET suspended = FALSE, suspended_date = NULL, date = ?, completed = FALSE WHERE id = ?',
[today, id]
);
const [updatedTodo] = await connection.execute(
'SELECT * FROM todos WHERE id = ?',
[id]
);
connection.release();
res.json(updatedTodo[0]);
} catch (error) {
console.error('恢复待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 将历史未完成待办迁移到今天
app.post('/api/todos/migrate-pending', authenticateToken, async (req, res) => {
try {
const connection = await pool.getConnection();
// 获取今天的日期
const today = new Date().toISOString().split('T')[0];
// 查找今天之前所有未完成且未挂起的待办事项
let query = 'SELECT * FROM todos WHERE date < ? AND completed = FALSE AND suspended = FALSE AND user_id = ? ORDER BY date DESC, priority DESC';
let params = [today];
params.push(req.user.userId);
const [pendingTodos] = await connection.execute(query, params);
let migratedCount = 0;
// 将每个未完成的待办事项迁移到今天
for (const todo of pendingTodos) {
await connection.execute(
'UPDATE todos SET date = ? WHERE id = ?',
[today, todo.id]
);
migratedCount++;
}
connection.release();
res.json({
success: true,
migratedCount,
message: `已迁移 ${migratedCount} 个未完成的待办事项到今天`
});
} catch (error) {
console.error('迁移待办事项失败:', error);
res.status(500).json({ error: '服务器错误' });
}
});
// 启动服务器
app.listen(PORT, async () => {
console.log(`服务器运行在端口 ${PORT}`);
await initDatabase();
});