当前位置: 首页 > article >正文

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)

  1. 客户端发起请求
  • 用户点击 React Router 的 Link 组件或触发事件
  • React 组件通过 axios 发送 HTTP 请求
  • 请求经过 axios 拦截器处理(添加 token 等)
  1. 服务端接收请求
  • Express 服务器接收请求
  • 通过路由(routes)匹配对应的处理函数
  • 控制器(controllers)处理业务逻辑
  • 与 MongoDB 数据库交互
  1. 数据返回流程
  • 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}`);
});


http://www.kler.cn/a/611272.html

相关文章:

  • Es结合kibana查询
  • Docker容器之Dockerfile
  • CosyVoice2在Windows系统上本地部署的详细步骤
  • LlamaFactory部署及模型微调【win10环境】
  • 如何分析和解决服务器的僵尸进程问题
  • Spring Boot 连接 MySQL 配置参数详解
  • 使用ucharts写的小程序,然后让圆环中间的空白位置变大
  • Django之旅:第六节--mysql数据库操作增删改查(二)
  • 网络编程和计算机网络五层模型的关系
  • 日语Learn,英语再认识(4)
  • Docker 数据卷与文件挂载
  • pyqt SQL Server 数据库查询-优化
  • 根据模板将 Excel 明细数据生成 PDF 文档 | PDF实现邮件合并功能
  • MyBatis打印SQL日志的配置
  • 英伟达黄仁勋谈人工智能趋势,首提代理式AI,后续机器人将登场
  • 算法及数据结构系列 - 滑动窗口
  • SpringCloud微服务框架搭建指南
  • 图解AI对话系统架构:一次讲透核心技术
  • 使用 HBuilder 打包 ruoyi-mall-uniapp 并在微信开发者工具中模拟运行的教程
  • SQL Optimization