React-nodejs 练习 个人博客
1.主要功能模块:
- 文章管理:CRUD操作
- 用户系统:注册、登录、权限控制
- 评论系统:文章评论功能
-
2.技术栈:
- 前端:React + Ant Design + React Router
- 后端:Express + MongoDB
- 通信:RESTful API + Axios
这个项目采用了典型的MVC架构,前后端完全分离,通过API进行通信。前端负责用户界面和交互,后端负责数据处理和业务逻辑。
3.文件结构
1. blog-project (前端)
---
├── src/
│ ├── App.jsx # 根组件,路由配置
│ ├── App.css # 全局样式
│ ├── main.jsx # 入口文件
│ ├── pages/ # 页面组件
│ │ ├── Home/ # 首页目录
│ │ │ └── index.jsx # 首页组件
│ │ ├── Articles/ # 文章相关页面目录
│ │ │ ├── index.jsx # 文章列表页
│ │ │ └── detail.jsx # 文章详情页
│ │ └── Login/ # 登录页面目录
│ │ └── index.jsx # 登录组件
│ ├── components/ # 可复用组件
│ │ └── Layout/ # 布局组件
│ │ └── index.jsx # 布局组件实现
│ ├── api/ # API接口
│ │ ├── article.js # 文章相关API
│ │ ├── user.js # 用户相关API
│ │ └── request.js # axios请求封装
│ ├── utils/ # 工具函数
│ └── assets/ # 静态资源
---
└── package.json # 项目依赖配置
2. blog-server (后端)
---
├── src/
│ ├── index.js # 服务器入口文件
│ ├── routes/ # 路由定义
│ │ ├── postRoutes.js # 文章路由
│ │ └── userRoutes.js # 用户路由
│ ├── controllers/ # 控制器
│ │ ├── postController.js # 文章控制器
│ │ └── userController.js # 用户控制器
│ ├── models/ # 数据模型
│ │ ├── post.js # 文章模型
│ │ └── user.js # 用户模型
│ ├── middleware/ # 中间件
│ │ └── auth.js # 认证中间件
│ ├── utils/ # 工具函数
│ └── config/ # 配置文件
---
└── package.json # 项目依赖配置
4. 流程原理
1. 整体架构思想
前后端分离架构
- 前端(React + Ant Design)专注于用户界面和交互
- 后端(Express + MongoDB)专注于数据处理和业务逻辑
- 通过 HTTP API 进行通信,实现解耦
MVC 架构模式
- Model(数据模型):MongoDB 的 Schema 定义
- View(视图):React 组件
- Controller(控制器):Express 的路由和控制器
整体工作流程
客户端(React) → 服务端(Express) → 数据库(MongoDB)
-
客户端发起请求
- 用户点击 React Router 的 Link 组件或触发事件
- React 组件通过 axios 发送 HTTP 请求
- 请求经过 axios 拦截器处理(添加 token 等)
-
服务端接收请求
- Express 服务器接收请求
- 通过路由(routes)匹配对应的处理函数
- 控制器(controllers)处理业务逻辑
- 与 MongoDB 数据库交互
-
数据返回流程
- MongoDB 返回查询结果
- Express 处理数据并返回响应
- axios 拦截器处理响应
- React 组件更新状态并重新渲染
2 请求流程示例
用户点击文章列表
↓
React Router 的 Link 组件触发路由变化
↓
React 组件加载,调用 axios 请求
↓
axios 拦截器添加 token
↓
请求发送到 Express 服务器
↓
Express 路由匹配到 postRoutes
↓
控制器调用 MongoDB 查询
↓
数据返回给前端
↓
React 组件更新显示
3 关键组件的工作方式
1. axios 拦截器
- 请求拦截:添加 token、处理请求参数
- 响应拦截:处理响应数据、处理错误
2. Express 路由
- 匹配 URL 和 HTTP 方法
- 调用对应的控制器函数
- 处理请求参数
3. MongoDB 操作
- 通过 Mongoose 模型定义数据结构
- 执行 CRUD 操作
- 返回查询结果
4.错误处理
前端错误:
React 组件 → axios 拦截器 → 显示错误信息
后端错误:
MongoDB 错误 → Express 控制器 → 返回错误响应 → axios 拦截器 → React 组件显示错误
5.具体代码:
前端部分
App.jsx
import { BrowserRouter as Router, Routes, Route, Link, useNavigate } from 'react-router-dom';
import { Layout, Menu, Button } from 'antd';
import './App.css';
import Login from './pages/Login';
import Home from './pages/Home';
import Articles from './pages/Articles';
import { useState } from 'react';
// 后续会创建这些组件
const About = () => <div>关于我</div>;
const { Header, Content, Footer } = Layout;
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(!!localStorage.getItem('token'));
return (
<Router>
<Layout className="layout">
<Header className="header">
<div className="logo">我的个人博客</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['/']}
items={[
{
key: '/',
label: <Link to="/">首页</Link>,
},
{
key: '/articles',
label: <Link to="/articles">文章列表</Link>,
},
{
key: '/about',
label: <Link to="/about">关于我</Link>,
},
]}
/>
{isLoggedIn ? (
<Button onClick={() => {
localStorage.removeItem('token');
setIsLoggedIn(false);
}}>退出</Button>
) : (
<Link to="/login">
<Button type="primary">登录</Button>
</Link>
)}
</Header>
<Content>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<Home />} />
<Route path="/articles/*" element={<Articles />} />
<Route path="/about" element={<About />} />
</Routes>
</Content>
<Footer style={{ textAlign: 'center' }}>
个人博客 ©2024 Created by shandian
</Footer>
</Layout>
</Router>
);
}
export default App;
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import 'antd/dist/reset.css'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
article.js
import request from './request';
// 获取文章列表
export const getArticles = () => {
return request({
url: '/posts',
method: 'get'
});
};
// 获取文章详情
export const getArticleById = (id) => {
console.log('Calling API for article:', id); // 添加日志
return request({
url: `/posts/${id}`,
method: 'get'
});
};
// 创建文章
export const createArticle = (data) => {
return request({
url: '/posts',
method: 'post',
data
});
};
// 更新文章
export const updateArticle = (id, data) => {
return request({
url: `/posts/${id}`,
method: 'put',
data
});
};
// 删除文章
export const deleteArticle = (id) => {
return request({
url: `/posts/${id}`,
method: 'delete'
});
};
// 获取标签列表
export const getTags = () => {
return request({
url: '/posts/tags',
method: 'get'
});
};
request.js
import axios from 'axios';
import { message } from 'antd';
// 创建axios实例
const request = axios.create({
baseURL: 'http://localhost:3000/api',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
}
});
// 请求拦截器
request.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加请求日志
console.log('Request:', {
url: config.url,
method: config.method,
data: config.data,
params: config.params
});
return config;
},
error => {
console.error('Request Error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
response => {
// 添加响应日志
console.log('Response:', {
url: response.config.url,
status: response.status,
data: response.data
});
return response.data;
},
error => {
console.error('Response Error:', {
url: error.config?.url,
status: error.response?.status,
data: error.response?.data,
message: error.message
});
if (error.response) {
switch (error.response.status) {
case 401:
message.error('请先登录');
localStorage.removeItem('token');
window.location.href = '/login';
break;
case 403:
message.error('没有权限');
break;
case 404:
message.error('请求的资源不存在');
break;
case 500:
message.error('服务器错误:' + (error.response.data?.message || '未知错误'));
break;
default:
message.error(`请求错误 (${error.response.status}): ${error.response.data?.message || '未知错误'}`);
}
} else if (error.request) {
message.error('无法连接到服务器,请检查网络连接');
} else {
message.error('请求配置错误:' + error.message);
}
return Promise.reject(error);
}
);
export default request;
user.js
import request from './request';
// 用户登录
export const login = (data) => {
return request({
url: '/users/login',
method: 'post',
data
});
};
// 用户注册
export const register = (data) => {
return request({
url: '/users/register',
method: 'post',
data
});
};
// 获取用户信息
export const getUserInfo = () => {
return request({
url: '/users/info',
method: 'get'
});
};
// 更新用户信息
export const updateUserInfo = (data) => {
return request({
url: '/user/update',
method: 'put',
data
});
};
index.jsx
import { Outlet } from 'react-router-dom';
import Navigation from '../Navigation';
function Layout() {
return (
<div>
<Navigation />
<main>
<Outlet />
</main>
</div>
);
}
export default Layout;
CreateArticle.jsx
import React, { useState } from 'react';
import { Card, Form, Input, Button, Select, message } from 'antd';
import { useNavigate } from 'react-router-dom';
import { createArticle } from '../../api/article';
const { TextArea } = Input;
const CreateArticle = () => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const onFinish = async (values) => {
try {
setLoading(true);
// 处理标签,将字符串转换为数组
const tags = values.tags.split(',').map(tag => tag.trim());
await createArticle({
...values,
tags
});
message.success('文章创建成功!');
navigate('/articles'); // 创建成功后返回文章列表
} catch (error) {
message.error('创建文章失败:' + (error.message || '未知错误'));
} finally {
setLoading(false);
}
};
return (
<div className="create-article-container">
<Card title="创建新文章" className="create-article-card">
<Form
form={form}
layout="vertical"
onFinish={onFinish}
>
<Form.Item
name="title"
label="文章标题"
rules={[{ required: true, message: '请输入文章标题' }]}
>
<Input placeholder="请输入文章标题" />
</Form.Item>
<Form.Item
name="content"
label="文章内容"
rules={[{ required: true, message: '请输入文章内容' }]}
>
<TextArea
rows={15}
placeholder="请输入文章内容"
style={{ resize: 'none' }}
/>
</Form.Item>
<Form.Item
name="tags"
label="文章标签"
help="多个标签请用逗号分隔,如:React,JavaScript,前端"
>
<Input placeholder="请输入标签,用逗号分隔" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
发布文章
</Button>
</Form.Item>
</Form>
</Card>
</div>
);
};
export default CreateArticle;
home.jsx
import React, { useState, useEffect } from 'react';
import { Card, List, Space, Tag, Spin } from 'antd';
import { ClockCircleOutlined, UserOutlined, EyeOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { getArticles, getTags } from '../../api/article';
import { getUserInfo } from '../../api/user';
import './index.css';
const Home = () => {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(false);
const [userInfo, setUserInfo] = useState(null);
const [popularTags, setPopularTags] = useState([]);
// 获取最新文章
const fetchLatestArticles = async () => {
try {
setLoading(true);
const res = await getArticles({
page: 1,
pageSize: 5,
sort: 'createTime',
order: 'desc'
});
setArticles(res.list);
} catch (error) {
console.error('获取最新文章失败:', error);
} finally {
setLoading(false);
}
};
// 获取热门标签
const fetchPopularTags = async () => {
try {
const res = await getTags();
setPopularTags(res.slice(0, 10)); // 只显示前10个标签
} catch (error) {
console.error('获取热门标签失败:', error);
}
};
// 获取用户信息
const fetchUserInfo = async () => {
try {
const res = await getUserInfo();
setUserInfo(res);
} catch (error) {
console.error('获取用户信息失败:', error);
}
};
useEffect(() => {
fetchLatestArticles();
fetchPopularTags();
fetchUserInfo();
}, []);
return (
<div className="home-container">
<div className="article-list">
<Spin spinning={loading}>
<List
itemLayout="vertical"
size="large"
dataSource={articles}
renderItem={(item) => (
<List.Item
key={item.id}
actions={[
<Space>
<ClockCircleOutlined /> {item.createTime}
</Space>,
<Space>
<UserOutlined /> {item.author}
</Space>,
<Space>
<EyeOutlined /> {item.views} 次浏览
</Space>
]}
>
<Card hoverable className="article-card">
<Link to={`/article/${item.id}`}>
<List.Item.Meta
title={<h2>{item.title}</h2>}
description={
<div>
<p className="article-summary">{item.summary}</p>
<Space className="article-tags">
{item.tags.map(tag => (
<Tag key={tag} color="blue">{tag}</Tag>
))}
</Space>
</div>
}
/>
</Link>
</Card>
</List.Item>
)}
/>
</Spin>
</div>
<div className="sidebar">
<Card title="关于博主" className="about-card">
{userInfo ? (
<>
<p>{userInfo.bio || '热爱编程的前端开发者'}</p>
<p>文章数:{userInfo.articleCount || 0}</p>
<p>访问量:{userInfo.totalViews || 0}</p>
</>
) : (
<p>加载中...</p>
)}
</Card>
<Card title="热门标签" className="tags-card">
<Space wrap>
{popularTags.map(tag => (
<Tag
key={tag}
color="blue"
onClick={() => window.location.href = `/articles?tag=${tag}`}
style={{ cursor: 'pointer' }}
>
{tag}
</Tag>
))}
</Space>
</Card>
</div>
</div>
);
};
export default Home;
login.jsx
import React, { useState } from 'react';
import { Form, Input, Button, Card, message } from 'antd';
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { login, register } from '../../api/user';
import './index.css';
const Login = () => {
const [isLogin, setIsLogin] = useState(true);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const [form] = Form.useForm();
const onFinish = async (values) => {
try {
setLoading(true);
if (isLogin) {
// 登录请求
const res = await login({
email: values.email,
password: values.password
});
// 保存token
localStorage.setItem('token', res.token);
message.success('登录成功!');
navigate('/'); // 登录成功后跳转到首页
} else {
// 注册请求
if (values.password !== values.confirmPassword) {
message.error('两次输入的密码不一致!');
return;
}
await register({
username: values.username,
email: values.email,
password: values.password
});
message.success('注册成功!');
setIsLogin(true); // 注册成功后切换到登录界面
form.resetFields();
}
} catch (error) {
// 错误已经在request.js中统一处理
console.error('操作失败:', error);
} finally {
setLoading(false);
}
};
const switchMode = () => {
setIsLogin(!isLogin);
form.resetFields();
};
return (
<div className="login-container">
<Card title={isLogin ? "登录" : "注册"} className="login-card">
<Form
form={form}
name="normal_login"
className="login-form"
onFinish={onFinish}
>
{!isLogin && (
<Form.Item
name="username"
rules={[
{ required: true, message: '请输入用户名!' },
{ min: 3, message: '用户名至少3个字符!' }
]}
>
<Input
prefix={<UserOutlined />}
placeholder="用户名"
/>
</Form.Item>
)}
<Form.Item
name="email"
rules={[
{ required: true, message: '请输入邮箱!' },
{ type: 'email', message: '请输入有效的邮箱地址!' }
]}
>
<Input
prefix={<MailOutlined />}
placeholder="邮箱"
/>
</Form.Item>
<Form.Item
name="password"
rules={[
{ required: true, message: '请输入密码!' },
{ min: 6, message: '密码至少6个字符!' }
]}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder="密码"
/>
</Form.Item>
{!isLogin && (
<Form.Item
name="confirmPassword"
dependencies={['password']}
rules={[
{ required: true, message: '请确认密码!' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致!'));
},
}),
]}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder="确认密码"
/>
</Form.Item>
)}
<Form.Item>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
loading={loading}
>
{isLogin ? '登录' : '注册'}
</Button>
<Button type="link" onClick={switchMode}>
{isLogin ? '还没有账号?去注册' : '已有账号?去登录'}
</Button>
</Form.Item>
</Form>
</Card>
</div>
);
};
export default Login;
后端部分
postController.js
const Post = require('../models/post');
// 创建新文章
exports.createPost = async (req, res) => {
try {
const { title, content, tags } = req.body;
// 添加调试日志
console.log('userId:', req.userId);
console.log('request body:', req.body);
const post = new Post({
title,
content,
tags,
author: req.userId // 确保这里使用的是 req.userId
});
const savedPost = await post.save();
res.status(201).json({ message: '文章创建成功', post: savedPost });
} catch (err) {
console.error('Error creating post:', err); // 添加错误日志
res.status(400).json({ message: err.message });
}
};
// 获取所有文章
exports.getPosts = async (req, res) => {
try {
console.log('Getting posts...'); // 添加调试日志
const posts = await Post.find()
.populate('author', 'email') // 关联作者信息
.sort({ createdAt: -1 }); // 按创建时间倒序
console.log('Found posts:', posts); // 添加调试日志
res.json(posts);
} catch (err) {
console.error('Error in getPosts:', err); // 添加错误日志
res.status(500).json({ message: err.message });
}
};
// 获取单个文章
exports.getPost = async (req, res) => {
try {
const post = await Post.findById(req.params.id)
.populate('author', 'username email');
if (!post) {
return res.status(404).json({ message: '文章不存在' });
}
// 增加阅读量
post.views = (post.views || 0) + 1;
await post.save();
res.json(post);
} catch (err) {
console.error('获取文章详情失败:', err);
res.status(500).json({ message: err.message });
}
};
// 更新文章
exports.updatePost = async (req, res) => {
try {
const { title, content, tags, status } = req.body;
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ message: '文章不存在' });
}
// 检查是否是文章作者
if (post.author.toString() !== req.userId) {
return res.status(403).json({ message: '没有权限修改此文章' });
}
post.title = title || post.title;
post.content = content || post.content;
post.tags = tags || post.tags;
post.status = status || post.status;
await post.save();
res.json({ message: '文章更新成功', post });
} catch (err) {
res.status(400).json({ message: err.message });
}
};
// 删除文章
exports.deletePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ message: '文章不存在' });
}
if (post.author.toString() !== req.userId) {
return res.status(403).json({ message: '没有权限删除此文章' });
}
await Post.deleteOne({ _id: req.params.id });
res.json({ message: '文章删除成功' });
} catch (err) {
res.status(500).json({ message: err.message });
}
};
// 获取所有标签
exports.getTags = async (req, res) => {
try {
const posts = await Post.find();
// 获取所有文章的标签,去重
const tags = [...new Set(posts.flatMap(post => post.tags))];
res.json(tags);
} catch (err) {
console.error('Error getting tags:', err); // 添加错误日志
res.status(500).json({ message: err.message });
}
};
userController.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
// 注册新用户
exports.register = async (req, res) => {
try {
const { username, email, password } = req.body;
const user = new User({ username, email, password });
await user.save();
res.status(201).json({ message: '用户创建成功' });
} catch (err) {
res.status(400).json({ message: err.message });
}
};
// 登录
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: '用户不存在' });
}
// 验证密码
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({ message: '密码错误' });
}
// 生成 JWT token
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
// 返回用户信息和 token
res.json({
token,
user: {
id: user._id,
username: user.username,
email: user.email,
role: user.role
}
});
} catch (err) {
res.status(500).json({ message: err.message });
}
};
// 获取所有用户
exports.getUsers = async (req, res) => {
try {
const users = await User.find({}, '-password');
res.json(users);
} catch (err) {
res.status(500).json({ message: err.message });
}
};
// 获取单个用户
exports.getUser = async (req, res) => {
try {
const user = await User.findById(req.params.id, '-password');
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
res.json(user);
} catch (err) {
res.status(500).json({ message: err.message });
}
};
// 获取用户信息
exports.getUserInfo = async (req, res) => {
try {
console.log('Getting user info for userId:', req.userId); // 添加调试日志
const user = await User.findById(req.userId).select('-password');
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
console.log('Found user:', user); // 添加调试日志
res.json(user);
} catch (err) {
console.error('Error in getUserInfo:', err); // 添加错误日志
res.status(500).json({ message: err.message });
}
};
module.exports = exports;
auth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
// 添加调试日志
console.log('Auth Header:', req.headers.authorization);
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: '未提供认证token' });
}
const token = authHeader.split(' ')[1];
console.log('Using JWT_SECRET:', process.env.JWT_SECRET); // 添加调试日志
console.log('Extracted token:', token); // 调试日志
if (!token) {
return res.status(401).json({ message: 'token格式错误' });
}
// 确保使用正确的 JWT_SECRET
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log('Decoded token:', decoded); // 调试日志
req.userId = decoded.userId;
next();
} catch (err) {
console.error('Auth error:', err); // 错误日志
res.status(401).json({ message: '认证失败' });
}
};
post.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
content: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
tags: [{
type: String,
trim: true
}],
status: {
type: String,
enum: ['draft', 'published'],
default: 'published'
},
views: {
type: Number,
default: 0
}
}, {
timestamps: true // 自动添加 createdAt 和 updatedAt 字段
});
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
user.js
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true
},
password: {
type: String,
required: true,
minlength: 6
},
avatar: {
type: String,
default: ''
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
}
}, {
timestamps: true // 自动添加 createdAt 和 updatedAt 字段
});
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
userSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
const User = mongoose.model('User', userSchema);
module.exports = User;
postRoutes.js
const express = require('express');
const router = express.Router();
const postController = require('../controllers/postController');
const auth = require('../middleware/auth');
// 公开路由
router.get('/tags', postController.getTags);
router.get('/', postController.getPosts);
router.get('/:id', postController.getPost);
// 需要认证的路由
router.post('/', auth, postController.createPost);
router.put('/:id', auth, postController.updatePost);
router.delete('/:id', auth, postController.deletePost);
module.exports = router;
userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const auth = require('../middleware/auth');
// 公开路由
router.post('/register', userController.register);
router.post('/login', userController.login);
// 需要认证的路由
router.get('/info', auth, userController.getUserInfo);
router.get('/', auth, userController.getUsers);
router.get('/:id', auth, userController.getUser);
module.exports = router;
处理
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
// 修改这一行,使用正确的相对路径
const userRoutes = require('./routes/userRoutes.js'); // 确保加上 .js 扩展名
const postRoutes = require('./routes/postRoutes.js'); // 添加这行
// 连接MongoDB数据库
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/blog')
.then(() => console.log('MongoDB 连接成功!'))
.catch(err => console.error('MongoDB 连接失败:', err));
const app = express();
// 更详细的 CORS 配置
app.use(cors({
origin: 'http://localhost:5173', // 您的前端地址
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
app.use(express.json());
// 测试路由
app.get('/api/test', (req, res) => {
res.json({ message: '后端服务器运行正常!' });
});
// 在其他路由之前添加
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// 使用路由
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes); // 添加这行
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});