# 博客系统

URL: https://caijiao.org/express/chapter11/1-blog
Source: docs/express/chapter11/1-blog.md
Description: 本章节将介绍如何使用 Express 构建一个简单的博客系统。我们将从设计数据库、创建路由和控制器，到实现用户身份验证、文章发布与编辑、评论功能等逐步完成一个功能完整的博客系统。通过这个项目，您将掌握 Express 开发中常见的功能实现。

在本章节中，我们将使用 Express 创建一个简单的博客系统。我们将覆盖从数据库设计、路由创建、文章发布、用户注册与登录、评论功能等多方面的内容。通过实践这一项目，您将对 Express 开发有更深入的了解，并学会如何处理实际项目中的常见功能需求。

## 1. 项目概述

本博客系统将包含以下基本功能：

1. **用户身份验证**：允许用户注册、登录和退出。
2. **文章管理**：支持用户发布、编辑、删除和查看文章。
3. **评论功能**：允许用户对文章进行评论。
4. **前后端分离**：前端可以通过 API 获取数据并展示。

## 2. 数据库设计

我们将使用 MongoDB 来存储数据，并通过 Mongoose 来操作数据库。博客系统的主要数据模型如下：

### 2.1 用户模型

用户模型包含以下字段：

- `username`: 用户名，唯一。
- `email`: 用户邮箱，唯一。
- `password`: 用户密码，哈希存储。

```js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
});

userSchema.methods.hashPassword = async function(password) {
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(password, salt);
};

userSchema.methods.comparePassword = async function(password) {
  return bcrypt.compare(password, this.password);
};

const User = mongoose.model('User', userSchema);

module.exports = User;
````

### 2.2 文章模型

文章模型包含以下字段：

* `title`: 文章标题。
* `content`: 文章内容。
* `author`: 文章作者，引用用户模型。
* `createdAt`: 创建时间。

```js
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  createdAt: { type: Date, default: Date.now },
});

const Post = mongoose.model('Post', postSchema);

module.exports = Post;
```

### 2.3 评论模型

评论模型包含以下字段：

* `post`: 关联文章。
* `author`: 评论作者。
* `content`: 评论内容。
* `createdAt`: 创建时间。

```js
const mongoose = require('mongoose');

const commentSchema = new mongoose.Schema({
  post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true },
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  content: { type: String, required: true },
  createdAt: { type: Date, default: Date.now },
});

const Comment = mongoose.model('Comment', commentSchema);

module.exports = Comment;
```

## 3. 用户身份验证

我们将使用 JWT 来进行用户身份验证。当用户登录时，系统将生成一个 JWT 令牌，客户端将这个令牌保存在本地并用于后续请求的身份验证。

### 3.1 用户注册

用户注册时，需要提供用户名、邮箱和密码。密码将被哈希后存储在数据库中。

```js
const express = require('express');
const bcrypt = require('bcrypt');
const User = require('../models/User');
const jwt = require('jsonwebtoken');

const router = express.Router();

router.post('/register', async (req, res) => {
  const { username, email, password } = req.body;
  
  try {
    const user = new User({ username, email });
    await user.hashPassword(password);
    await user.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (err) {
    res.status(400).json({ message: 'Error registering user', error: err });
  }
});

module.exports = router;
```

### 3.2 用户登录

用户登录时，我们将验证其邮箱和密码，若验证成功，生成 JWT 令牌并返回给客户端。

```js
router.post('/login', async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }

    const isMatch = await user.comparePassword(password);
    if (!isMatch) {
      return res.status(400).json({ message: 'Invalid password' });
    }

    const token = jwt.sign({ userId: user._id }, 'secretkey', { expiresIn: '1h' });
    res.json({ token });
  } catch (err) {
    res.status(500).json({ message: 'Error logging in', error: err });
  }
});
```

### 3.3 验证 JWT 令牌

在需要身份验证的 API 路由中，使用 JWT 令牌进行身份验证。

```js
const jwt = require('jsonwebtoken');

const authenticate = (req, res, next) => {
  const token = req.header('Authorization');
  if (!token) {
    return res.status(401).json({ message: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, 'secretkey');
    req.userId = decoded.userId;
    next();
  } catch (err) {
    res.status(401).json({ message: 'Invalid token' });
  }
};

module.exports = authenticate;
```

## 4. 文章功能

### 4.1 创建文章

用户可以创建文章，输入标题和内容后保存到数据库。

```js
const Post = require('../models/Post');

router.post('/posts', authenticate, async (req, res) => {
  const { title, content } = req.body;
  
  try {
    const post = new Post({
      title,
      content,
      author: req.userId,
    });

    await post.save();
    res.status(201).json({ message: 'Post created successfully', post });
  } catch (err) {
    res.status(500).json({ message: 'Error creating post', error: err });
  }
});
```

### 4.2 获取所有文章

用户可以查看所有文章的列表。

```js
router.get('/posts', async (req, res) => {
  try {
    const posts = await Post.find().populate('author', 'username').exec();
    res.json(posts);
  } catch (err) {
    res.status(500).json({ message: 'Error fetching posts', error: err });
  }
});
```

### 4.3 编辑文章

用户可以编辑自己发布的文章。

```js
router.put('/posts/:id', authenticate, async (req, res) => {
  const { title, content } = req.body;
  try {
    const post = await Post.findById(req.params.id);
    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }

    if (post.author.toString() !== req.userId) {
      return res.status(403).json({ message: 'Unauthorized' });
    }

    post.title = title || post.title;
    post.content = content || post.content;
    await post.save();

    res.json({ message: 'Post updated successfully', post });
  } catch (err) {
    res.status(500).json({ message: 'Error updating post', error: err });
  }
});
```

## 5. 评论功能

### 5.1 创建评论

用户可以对文章进行评论，评论将与文章关联。

```js
const Comment = require('../models/Comment');

router.post('/posts/:postId/comments', authenticate, async (req, res) => {
  const { content } = req.body;

  try {
    const post = await Post.findById(req.params.postId);
    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }

    const comment = new Comment({
      post: post._id,
      author: req.userId,
      content,
    });

    await comment.save();
    res.status(201).json({ message: 'Comment added successfully', comment });
  } catch (err) {
    res.status(500).json({ message: 'Error adding comment', error: err });
  }
});
```

### 5.2 获取评论

获取某篇文章下的所有评论。

```js
router.get('/posts/:postId/comments', async (req, res) => {
  try {
    const comments = await Comment.find({ post: req.params.postId }).populate('author', 'username').exec();
    res.json(comments);
  } catch (err) {
    res.status(500).json({ message: 'Error fetching comments', error: err });
  }
});
```

## 6. 总结

通过本章节的


实现，您已经掌握了如何使用 Express 构建一个简单的博客系统。您学会了如何处理用户身份验证、文章发布、评论功能等常见需求，并了解了如何设计数据库模型和实现基本的 CRUD 操作。在此基础上，您可以扩展更多功能，如文章分类、搜索、分页等，进一步完善您的博客系统。
