初始化
This commit is contained in:
42
server/package.json
Normal file
42
server/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "TodoList 后端服务",
|
||||
"main": "dist/app.js",
|
||||
"scripts": {
|
||||
"start": "node dist/app.js",
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/app.ts",
|
||||
"build": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/mongoose": "^5.11.97",
|
||||
"@types/node": "^24.0.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mongoose": "^8.15.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^24.0.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.5",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
36
server/src/app.ts
Normal file
36
server/src/app.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import connectDB from './config/db';
|
||||
import todoRoutes from './routes/todoRoutes';
|
||||
import authRoutes from './routes/authRoutes';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
|
||||
// 连接数据库
|
||||
connectDB();
|
||||
|
||||
// 中间件
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:5173',
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
// 路由
|
||||
app.use('/api/todos', todoRoutes);
|
||||
app.use('/api/auth', authRoutes);
|
||||
|
||||
// 错误处理中间件
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({ message: '服务器内部错误' });
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 5050;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`服务器运行在端口 ${PORT}`);
|
||||
});
|
||||
16
server/src/config/db.ts
Normal file
16
server/src/config/db.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const connectDB = async () => {
|
||||
try {
|
||||
const conn = await mongoose.connect(process.env.MONGODB_URI as string);
|
||||
console.log(`MongoDB 连接成功: ${conn.connection.host}`);
|
||||
} catch (error) {
|
||||
console.error('MongoDB 连接失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export default connectDB;
|
||||
42
server/src/controllers/authController.ts
Normal file
42
server/src/controllers/authController.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User, { IUser } from '../models/User';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret_key';
|
||||
|
||||
// 用户注册
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
const { username, password } = req.body;
|
||||
try {
|
||||
const existingUser = await User.findOne({ username });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ message: '用户名已存在' });
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const user = new User({ username, password: hashedPassword });
|
||||
await user.save();
|
||||
res.status(201).json({ message: '注册成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: '注册失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 用户登录
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
const { username, password } = req.body;
|
||||
try {
|
||||
const user = await User.findOne({ username });
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: '用户不存在' });
|
||||
}
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ message: '密码错误' });
|
||||
}
|
||||
const token = jwt.sign({ userId: user._id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
|
||||
res.status(200).json({ token, username: user.username });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: '登录失败', error });
|
||||
}
|
||||
};
|
||||
83
server/src/controllers/todoController.ts
Normal file
83
server/src/controllers/todoController.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import Todo, { ITodo } from '../models/Todo';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret_key';
|
||||
|
||||
// 鉴权中间件,解析 token 并注入 req.user
|
||||
export const authMiddleware = (req: any, res: Response, next: NextFunction): void => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
res.status(401).json({ message: '未授权' });
|
||||
return;
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: '无效的 token' });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有待办事项(只返回当前用户的)
|
||||
export const getAllTodos = async (req: Request & { user?: any }, res: Response) => {
|
||||
try {
|
||||
const todos = await Todo.find({ user: req.user.userId }).sort({ createdAt: -1 });
|
||||
res.status(200).json(todos);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: '获取待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 创建新的待办事项(自动绑定 user)
|
||||
export const createTodo = async (req: Request & { user?: any }, res: Response) => {
|
||||
try {
|
||||
const { title, priority = 'medium' } = req.body;
|
||||
const todo = new Todo({
|
||||
title,
|
||||
priority,
|
||||
user: req.user.userId
|
||||
});
|
||||
const savedTodo = await todo.save();
|
||||
res.status(201).json(savedTodo);
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: '创建待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新待办事项
|
||||
export const updateTodo = async (req: Request & { user?: any }, res: Response) => {
|
||||
try {
|
||||
const todo = await Todo.findOne({ _id: req.params.id, user: req.user.userId });
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: '待办事项不存在' });
|
||||
}
|
||||
|
||||
const updatedTodo = await Todo.findByIdAndUpdate(
|
||||
req.params.id,
|
||||
{ ...req.body },
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
res.status(200).json(updatedTodo);
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: '更新待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 删除待办事项
|
||||
export const deleteTodo = async (req: Request & { user?: any }, res: Response) => {
|
||||
try {
|
||||
const todo = await Todo.findOne({ _id: req.params.id, user: req.user.userId });
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: '待办事项不存在' });
|
||||
}
|
||||
|
||||
await Todo.findByIdAndDelete(req.params.id);
|
||||
res.status(200).json({ message: '待办事项已删除' });
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: '删除待办事项失败', error });
|
||||
}
|
||||
};
|
||||
44
server/src/models/Todo.ts
Normal file
44
server/src/models/Todo.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface ITodo extends Document {
|
||||
title: string;
|
||||
description: string;
|
||||
completed: boolean;
|
||||
priority: 'low' | 'medium' | 'high';
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
user: mongoose.Types.ObjectId;
|
||||
}
|
||||
|
||||
const TodoSchema: Schema = new Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: [true, '标题是必需的'],
|
||||
trim: true,
|
||||
maxlength: [100, '标题不能超过100个字符']
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: [500, '描述不能超过500个字符']
|
||||
},
|
||||
completed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
priority: {
|
||||
type: String,
|
||||
enum: ['low', 'medium', 'high'],
|
||||
default: 'medium',
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
export default mongoose.model<ITodo>('Todo', TodoSchema);
|
||||
13
server/src/models/User.ts
Normal file
13
server/src/models/User.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface IUser extends Document {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const UserSchema: Schema = new Schema({
|
||||
username: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true }
|
||||
});
|
||||
|
||||
export default mongoose.model<IUser>('User', UserSchema);
|
||||
9
server/src/routes/authRoutes.ts
Normal file
9
server/src/routes/authRoutes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Router } from 'express';
|
||||
import { register, login } from '../controllers/authController';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post('/register', register);
|
||||
router.post('/login', login);
|
||||
|
||||
export default router;
|
||||
17
server/src/routes/todoRoutes.ts
Normal file
17
server/src/routes/todoRoutes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Router } from 'express';
|
||||
import {
|
||||
getAllTodos,
|
||||
createTodo,
|
||||
updateTodo,
|
||||
deleteTodo,
|
||||
authMiddleware
|
||||
} from '../controllers/todoController';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', authMiddleware, getAllTodos);
|
||||
router.post('/', authMiddleware, createTodo);
|
||||
router.put('/:id', authMiddleware, updateTodo);
|
||||
router.delete('/:id', authMiddleware, deleteTodo);
|
||||
|
||||
export default router;
|
||||
14
server/tsconfig.json
Normal file
14
server/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user