cursor done.
This commit is contained in:
11
server/config.js
Normal file
11
server/config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const path = require('path');
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
const DB_PATH = path.join(__dirname, '..', 'data.db');
|
||||
|
||||
module.exports = {
|
||||
PORT,
|
||||
JWT_SECRET,
|
||||
DB_PATH
|
||||
};
|
||||
54
server/database.js
Normal file
54
server/database.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const { DB_PATH } = require('./config');
|
||||
|
||||
const db = new sqlite3.Database(DB_PATH, (err) => {
|
||||
if (err) {
|
||||
console.error('数据库连接失败:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('已连接到 SQLite 数据库');
|
||||
});
|
||||
|
||||
function initializeDatabase() {
|
||||
db.serialize(() => {
|
||||
db.run(`CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
db.run(`CREATE TABLE IF NOT EXISTS calendar_marks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, date),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`);
|
||||
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_user_date ON calendar_marks(user_id, date)`);
|
||||
});
|
||||
}
|
||||
|
||||
initializeDatabase();
|
||||
|
||||
function closeDatabase() {
|
||||
return new Promise((resolve) => {
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error('关闭数据库失败:', err.message);
|
||||
} else {
|
||||
console.log('数据库连接已关闭');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
db,
|
||||
closeDatabase
|
||||
};
|
||||
23
server/middleware/auth.js
Normal file
23
server/middleware/auth.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { JWT_SECRET } = require('../config');
|
||||
|
||||
function authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ success: false, error: '未提供认证令牌' });
|
||||
}
|
||||
|
||||
jwt.verify(token, JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({ success: false, error: '无效的认证令牌' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
authenticateToken
|
||||
};
|
||||
86
server/routes/auth.js
Normal file
86
server/routes/auth.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { db } = require('../database');
|
||||
const { JWT_SECRET } = require('../config');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ success: false, error: '用户名和密码不能为空' });
|
||||
}
|
||||
|
||||
if (username.length < 3) {
|
||||
return res.status(400).json({ success: false, error: '用户名至少需要3个字符' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ success: false, error: '密码至少需要6个字符' });
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
|
||||
db.run(
|
||||
'INSERT INTO users (username, password_hash) VALUES (?, ?)',
|
||||
[username, passwordHash],
|
||||
function (err) {
|
||||
if (err) {
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
return res.status(400).json({ success: false, error: '用户名已存在' });
|
||||
}
|
||||
return res.status(500).json({ success: false, error: '注册失败' });
|
||||
}
|
||||
|
||||
const token = jwt.sign({ userId: this.lastID, username }, JWT_SECRET, { expiresIn: '7d' });
|
||||
res.json({ success: true, token, userId: this.lastID, username });
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/login', (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ success: false, error: '用户名和密码不能为空' });
|
||||
}
|
||||
|
||||
db.get(
|
||||
'SELECT * FROM users WHERE username = ?',
|
||||
[username],
|
||||
async (err, user) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: '登录失败' });
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ success: false, error: '用户名或密码错误' });
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, user.password_hash);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ success: false, error: '用户名或密码错误' });
|
||||
}
|
||||
|
||||
const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
|
||||
res.json({ success: true, token, userId: user.id, username: user.username });
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/me', authenticateToken, (req, res) => {
|
||||
res.json({ success: true, userId: req.user.userId, username: req.user.username });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
92
server/routes/calendar.js
Normal file
92
server/routes/calendar.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const express = require('express');
|
||||
const { db } = require('../database');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
|
||||
db.all(
|
||||
'SELECT date, status FROM calendar_marks WHERE user_id = ?',
|
||||
[userId],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: '获取数据失败' });
|
||||
}
|
||||
|
||||
const markedDates = {};
|
||||
rows.forEach((row) => {
|
||||
markedDates[row.date] = row.status;
|
||||
});
|
||||
|
||||
res.json({ success: true, markedDates });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const { markedDates } = req.body || {};
|
||||
|
||||
if (!markedDates || typeof markedDates !== 'object') {
|
||||
return res.status(400).json({ success: false, error: '无效的数据格式' });
|
||||
}
|
||||
|
||||
const dbOperations = Object.entries(markedDates).map(([date, status]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT INTO calendar_marks (user_id, date, status, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(user_id, date) DO UPDATE SET
|
||||
status = excluded.status,
|
||||
updated_at = CURRENT_TIMESTAMP`,
|
||||
[userId, date, status],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const dates = Object.keys(markedDates);
|
||||
if (dates.length > 0) {
|
||||
const placeholders = dates.map(() => '?').join(',');
|
||||
dbOperations.push(
|
||||
new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`DELETE FROM calendar_marks WHERE user_id = ? AND date NOT IN (${placeholders})`,
|
||||
[userId, ...dates],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dbOperations.push(
|
||||
new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'DELETE FROM calendar_marks WHERE user_id = ?',
|
||||
[userId],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all(dbOperations)
|
||||
.then(() => {
|
||||
res.json({ success: true, message: '数据保存成功' });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('保存数据失败:', err);
|
||||
res.status(500).json({ success: false, error: '保存数据失败' });
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
41
server/utils/shutdown.js
Normal file
41
server/utils/shutdown.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { closeDatabase } = require('../database');
|
||||
|
||||
function registerShutdown(server) {
|
||||
let isShuttingDown = false;
|
||||
|
||||
async function shutdown(reason) {
|
||||
if (isShuttingDown) return;
|
||||
isShuttingDown = true;
|
||||
|
||||
if (reason) {
|
||||
console.log(`\n收到 ${reason},正在关闭服务器...`);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
server.close(() => {
|
||||
console.log('HTTP 服务器已关闭');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await closeDatabase();
|
||||
process.exit(reason === 'uncaughtException' || reason === 'unhandledRejection' ? 1 : 0);
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('未捕获的异常:', err);
|
||||
shutdown('uncaughtException');
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('未处理的 Promise 拒绝:', reason);
|
||||
shutdown('unhandledRejection');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerShutdown
|
||||
};
|
||||
Reference in New Issue
Block a user