前言
默认大家已经看过前面的文章,并成功完成前面步骤。这章主要写评论的CURD功能api的开发
评论功能
创建评论模型models/Comment.js
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
content: {
type: String,
required: [true, '评论内容不能为空'],
trim: true,
maxlength: [1000, '评论最多1000个字符']
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: [true, '评论必须关联到文章']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: [true, '评论必须关联到用户']
},
parentComment: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
},
status: {
type: String,
enum: ['active', 'deleted'],
default: 'active'
}
}, {
timestamps: true
});
const Comment = mongoose.model('Comment', commentSchema);
module.exports = Comment;
创建评论控制器controllers/commentController.js
const Comment = require('../models/Comment');
const Post = require('../models/Post');
// 创建评论
const createComment = async (req, res) => {
try {
const { content, postId, parentCommentId } = req.body;
// 检查文章是否存在
const post = await Post.findById(postId);
if (!post) {
return res.status(404).json({ message: '文章不存在' });
}
// 如果是回复评论,检查父评论是否存在
if (parentCommentId) {
// 查找父评论,并确保它属于同一篇文章且未被删除
const parentComment = await Comment.findOne({
_id: parentCommentId,
post: postId, // 确保父评论属于同一篇文章
status: 'active' // 确保父评论未被删除
});
if (!parentComment) {
return res.status(404).json({
message: '父评论不存在或已被删除'
});
}
}
const comment = await Comment.create({
content,
post: postId,
author: req.user._id,
parentComment: parentCommentId
});
// 填充作者信息
await comment.populate('author', 'username');
res.status(201).json(comment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// 获取文章的所有评论
exports.getComments = async (req, res) => {
try {
const comments = await Comment.find({ post: req.params.postId })
.populate('author', 'name')
.populate('parentComment')
.sort('-createdAt');
res.json(comments);
} catch (error) {
res.status(500).json({ message: '获取评论失败' });
}
};
// 更新评论
exports.updateComment = async (req, res) => {
try {
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({ message: '评论不存在' });
}
// 检查是否是评论作者
if (comment.author.toString() !== req.user._id.toString()) {
return res.status(403).json({ message: '没有权限修改此评论' });
}
comment.content = req.body.content;
await comment.save();
res.json(comment);
} catch (error) {
res.status(500).json({ message: '更新评论失败' });
}
};
// 删除评论
exports.deleteComment = async (req, res) => {
try {
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({ message: '评论不存在' });
}
// 检查是否是评论作者
if (comment.author.toString() !== req.user._id.toString()) {
return res.status(403).json({ message: '没有权限删除此评论' });
}
await comment.remove();
res.json({ message: '评论已删除' });
} catch (error) {
res.status(500).json({ message: '删除评论失败' });
}
};
创建评论路由routes/comment.js
const express = require('express');
const router = express.Router();
const {
createComment,
getComments,
updateComment,
deleteComment,
toggleLike
} = require('../controllers/commentController');
const { protect } = require('../middleware/auth');
// 创建评论(需要登录)
router.post('/', protect, createComment);
// 获取文章的所有评论
router.get('/post/:postId', getComments);
// 更新评论(需要登录)
router.put('/:id', protect, updateComment);
// 删除评论(需要登录)
router.delete('/:id', protect, deleteComment);
// 点赞/取消点赞评论(需要登录)
router.post('/:id/like', protect, toggleLike);
module.exports = router;
在app.js
注册评论路由
const commentRoutes = require('./routes/comment');
// 使用 commentRoutes 路由
app.use('/api/comments', commentRoutes);
解释下创建方法使用populate问题
// 填充作者信息
await comment.populate('author', 'username');
这里在返回之前加了这行代码。为什么这么做。
- 前端不需要再单独请求用户信息
- 一次请求就能获取所有需要的数据
- 可以指定只返回需要的字段
总结来说,前端要什么我们给什么。省去前端在去调用查询用户信息的接口了。
说的有点模糊,直接上数据把
// 1. 创建评论
const comment = await Comment.create({
content: "这是一条评论",
author: req.user._id,
post: postId
});
// 2. 填充作者信息
await comment.populate('author', 'username');
// 3. 返回给前端
res.json(comment);
// 前端收到的数据:
{
"_id": "comment123",
"content": "这是一条评论",
"author": {
"_id": "user456",
"username": "张三"
},
"post": "post789",
"createdAt": "2024-01-01T00:00:00.000Z"
}
// 4. 数据库中实际存储:
{
"_id": "comment123",
"content": "这是一条评论",
"author": "user456", // 仍然是用户ID
"post": "post789",
"createdAt": "2024-01-01T00:00:00.000Z"
}
测试:
token和文章id用之前的方法生成一个
POST /api/comments
Content-Type: application/json
Authorization: Bearer your_token_here
{
"content": "这是一条评论",
"postId": "文章ID"
}
返回:
{
"content": "这是一条评论",
"post": "6825e0d1ab18230b995814ca",
"author": {
"_id": "68234d0c35c1531af74f8b21",
"username": "测试用户"
},
"status": "active",
"_id": "68260100b2c059a075973668",
"createdAt": "2025-05-15T14:58:08.489Z",
"updatedAt": "2025-05-15T14:58:08.489Z",
"__v": 0
}
获取所有评论
POST http://localhost:3000/api/comments/post/6825e0d1ab18230b995814ca
Content-Type: application/json
Authorization: Bearer your_token_here
返回
{
"comments": [
{
"_id": "68260100b2c059a075973668",
"content": "这是一条评论",
"post": "6825e0d1ab18230b995814ca",
"author": {
"_id": "68234d0c35c1531af74f8b21",
"username": "测试用户"
},
"status": "active",
"createdAt": "2025-05-15T14:58:08.489Z",
"updatedAt": "2025-05-15T14:58:08.489Z",
"__v": 0,
"replies": [
{
"_id": "68260250b2c059a07597367a",
"content": "这是一条回复评论",
"post": "6825e0d1ab18230b995814ca",
"author": {
"_id": "68234d0c35c1531af74f8b21",
"username": "测试用户"
},
"parentComment": "68260100b2c059a075973668",
"status": "active",
"createdAt": "2025-05-15T15:03:44.560Z",
"updatedAt": "2025-05-15T15:03:44.560Z",
"__v": 0
}
]
}
],
"totalPages": 1,
"currentPage": 1
}
评论接口完工!