使用 Node.js 开发在线评论和评分应用程序
引言
大家好,欢迎来到今天的讲座!今天我们要一起探讨如何使用 Node.js 来开发一个在线评论和评分应用程序。如果你对编程感兴趣,或者想了解如何构建一个功能丰富的 Web 应用程序,那么你来对地方了!我们将从头到尾一步步地讲解整个开发过程,确保每个步骤都清晰易懂。😊
在开始之前,让我们先明确一下我们要实现的目标:我们希望创建一个用户可以发表评论、给产品或服务打分的应用程序。用户还可以查看其他用户的评论和评分,并根据这些信息做出决策。听起来是不是很有趣?那就让我们开始吧!
1. 环境准备
1.1 安装 Node.js 和 npm
首先,我们需要安装 Node.js 和 npm(Node Package Manager)。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,它允许我们在服务器端运行 JavaScript 代码。npm 是 Node.js 的包管理工具,可以帮助我们轻松安装和管理第三方库。
要安装 Node.js 和 npm,你可以访问 Node.js 官方网站 下载最新版本的安装包。安装完成后,打开终端或命令行工具,输入以下命令来验证是否安装成功:
node -v
npm -v
如果一切正常,你应该会看到类似如下的输出:
v16.13.0
7.24.0
1.2 初始化项目
接下来,我们需要为我们的项目创建一个新的文件夹,并初始化一个 package.json
文件。package.json
是 Node.js 项目的配置文件,它包含了项目的依赖、脚本等信息。
在终端中,导航到你想要创建项目的目录,然后执行以下命令:
mkdir comment-rating-app
cd comment-rating-app
npm init -y
npm init -y
会自动生成一个默认的 package.json
文件,而不需要你手动填写所有信息。现在,你的项目结构应该如下所示:
comment-rating-app/
├── package.json
1.3 安装必要的依赖
为了简化开发过程,我们将使用一些流行的 Node.js 框架和库。以下是我们将要安装的主要依赖:
- Express:一个轻量级的 Web 框架,用于处理 HTTP 请求和响应。
- Mongoose:一个 MongoDB 对象建模工具,帮助我们与 MongoDB 数据库进行交互。
- Body-parser:用于解析 HTTP 请求体中的数据。
- EJS:一个简单的模板引擎,用于渲染 HTML 页面。
- dotenv:用于加载环境变量,方便我们在不同的环境中配置应用程序。
在终端中,执行以下命令来安装这些依赖:
npm install express mongoose body-parser ejs dotenv
安装完成后,你的 package.json
文件中应该会多出一个 dependencies
部分,类似于这样:
"dependencies": {
"body-parser": "^1.19.0",
"dotenv": "^10.0.0",
"ejs": "^3.1.6",
"express": "^4.17.1",
"mongoose": "^5.12.3"
}
1.4 配置环境变量
为了保护敏感信息(如数据库连接字符串),我们将使用 .env
文件来存储环境变量。在项目根目录下创建一个名为 .env
的文件,并添加以下内容:
PORT=3000
MONGO_URI=mongodb://localhost:27017/comment-rating-app
PORT
是应用程序运行的端口号,MONGO_URI
是 MongoDB 数据库的连接字符串。如果你使用的是本地 MongoDB 实例,默认端口是 27017
。如果你使用的是远程数据库(例如 MongoDB Atlas),请将 MONGO_URI
替换为相应的连接字符串。
1.5 创建基本的 Express 服务器
现在我们已经准备好开始编写代码了!首先,我们需要创建一个基本的 Express 服务器。在项目根目录下创建一个名为 app.js
的文件,并添加以下代码:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const app = express();
const PORT = process.env.PORT || 3000;
// 解析 JSON 和 URL 编码的请求体
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 设置 EJS 为模板引擎
app.set('view engine', 'ejs');
// 连接 MongoDB 数据库
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
console.log('Connected to MongoDB');
}).catch((err) => {
console.error('Failed to connect to MongoDB:', err);
});
// 启动服务器
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
这段代码做了以下几件事:
- 加载环境变量并配置 Express 应用程序。
- 使用
body-parser
解析请求体中的数据。 - 设置 EJS 作为模板引擎。
- 连接到 MongoDB 数据库。
- 启动服务器并监听指定的端口。
保存文件后,在终端中运行以下命令启动服务器:
node app.js
如果一切正常,你应该会在终端中看到类似如下的输出:
Connected to MongoDB
Server is running on http://localhost:3001
恭喜你,你已经成功创建了一个基本的 Express 服务器!接下来,我们将继续完善应用程序的功能。
2. 设计数据库模型
2.1 创建评论和评分模型
为了让用户能够发表评论和评分,我们需要设计一个合理的数据库模型。我们将使用 Mongoose 来定义两个模型:Comment
和 Rating
。
2.1.1 Comment 模型
Comment
模型将包含以下字段:
username
:评论者的用户名。content
:评论的内容。createdAt
:评论创建的时间戳。product
:评论所属的产品 ID(假设我们有一个产品列表)。
在项目根目录下创建一个名为 models
的文件夹,并在其中创建一个名为 Comment.js
的文件。添加以下代码:
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
username: { type: String, required: true },
content: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }
});
module.exports = mongoose.model('Comment', commentSchema);
2.1.2 Rating 模型
Rating
模型将包含以下字段:
username
:评分者的用户名。score
:评分的分数(1 到 5 之间的整数)。createdAt
:评分创建的时间戳。product
:评分所属的产品 ID。
在 models
文件夹中创建一个名为 Rating.js
的文件,并添加以下代码:
const mongoose = require('mongoose');
const ratingSchema = new mongoose.Schema({
username: { type: String, required: true },
score: { type: Number, min: 1, max: 5, required: true },
createdAt: { type: Date, default: Date.now },
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }
});
module.exports = mongoose.model('Rating', ratingSchema);
2.2 创建产品模型
为了让评论和评分有归属的对象,我们还需要创建一个 Product
模型。Product
模型将包含以下字段:
name
:产品的名称。description
:产品的描述。price
:产品的价格。image
:产品的图片 URL(可选)。
在 models
文件夹中创建一个名为 Product.js
的文件,并添加以下代码:
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
image: { type: String }
});
module.exports = mongoose.model('Product', productSchema);
2.3 种子数据
为了方便测试,我们可以创建一些种子数据(即初始数据)。在项目根目录下创建一个名为 seeds.js
的文件,并添加以下代码:
const mongoose = require('mongoose');
const Product = require('./models/Product');
const Comment = require('./models/Comment');
const Rating = require('./models/Rating');
const seedProducts = [
{
name: 'Apple iPhone 13',
description: 'The latest iPhone with advanced camera and A15 Bionic chip.',
price: 999.99,
image: 'https://example.com/iphone-13.jpg'
},
{
name: 'Samsung Galaxy S21',
description: 'A powerful Android smartphone with a stunning display and triple camera system.',
price: 799.99,
image: 'https://example.com/galaxy-s21.jpg'
}
];
const seedComments = [
{
username: 'John Doe',
content: 'Great phone! The camera is amazing!',
product: mongoose.Types.ObjectId('60c3b2a1e7f0b8001b6d4e00')
},
{
username: 'Jane Smith',
content: 'I love the design of this phone. Very sleek!',
product: mongoose.Types.ObjectId('60c3b2a1e7f0b8001b6d4e01')
}
];
const seedRatings = [
{
username: 'John Doe',
score: 5,
product: mongoose.Types.ObjectId('60c3b2a1e7f0b8001b6d4e00')
},
{
username: 'Jane Smith',
score: 4,
product: mongoose.Types.ObjectId('60c3b2a1e7f0b8001b6d4e01')
}
];
async function seedDB() {
try {
await Product.deleteMany({});
await Comment.deleteMany({});
await Rating.deleteMany({});
await Product.insertMany(seedProducts);
await Comment.insertMany(seedComments);
await Rating.insertMany(seedRatings);
console.log('Database seeded successfully!');
} catch (err) {
console.error('Error seeding database:', err);
}
mongoose.connection.close();
}
seedDB();
这段代码会删除现有的产品、评论和评分数据,并插入一些新的数据。要运行种子脚本,请在终端中执行以下命令:
node seeds.js
如果你没有看到任何错误消息,说明种子数据已经成功插入到数据库中。
3. 构建路由和控制器
3.1 创建路由
为了处理不同的 HTTP 请求,我们需要为应用程序创建路由。我们将创建以下路由:
/products
:显示所有产品的列表。/products/:id
:显示特定产品的详细信息,包括评论和评分。/products/:id/comments
:提交新评论。/products/:id/ratings
:提交新评分。
在项目根目录下创建一个名为 routes
的文件夹,并在其中创建一个名为 productRoutes.js
的文件。添加以下代码:
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');
const Comment = require('../models/Comment');
const Rating = require('../models/Rating');
// 显示所有产品
router.get('/', async (req, res) => {
try {
const products = await Product.find();
res.render('products/index', { products });
} catch (err) {
res.status(500).send(err.message);
}
});
// 显示特定产品的详细信息
router.get('/:id', async (req, res) => {
try {
const product = await Product.findById(req.params.id).populate('comments ratings');
if (!product) return res.status(404).send('Product not found');
res.render('products/show', { product });
} catch (err) {
res.status(500).send(err.message);
}
});
// 提交新评论
router.post('/:id/comments', async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).send('Product not found');
const comment = new Comment({
username: req.body.username,
content: req.body.content,
product: product._id
});
await comment.save();
product.comments.push(comment);
await product.save();
res.redirect(`/products/${product._id}`);
} catch (err) {
res.status(500).send(err.message);
}
});
// 提交新评分
router.post('/:id/ratings', async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).send('Product not found');
const rating = new Rating({
username: req.body.username,
score: req.body.score,
product: product._id
});
await rating.save();
product.ratings.push(rating);
await product.save();
res.redirect(`/products/${product._id}`);
} catch (err) {
res.status(500).send(err.message);
}
});
module.exports = router;
3.2 注册路由
接下来,我们需要在 app.js
中注册这些路由。打开 app.js
文件,并在 app.listen
之前添加以下代码:
const productRoutes = require('./routes/productRoutes');
// 注册产品路由
app.use('/products', productRoutes);
3.3 创建控制器
为了保持代码的整洁和可维护性,我们可以将逻辑从路由中提取出来,放到控制器中。在项目根目录下创建一个名为 controllers
的文件夹,并在其中创建一个名为 productController.js
的文件。将 productRoutes.js
中的逻辑移到 productController.js
中,并在 productRoutes.js
中调用控制器方法。
productController.js
示例:
const Product = require('../models/Product');
const Comment = require('../models/Comment');
const Rating = require('../models/Rating');
exports.index = async (req, res) => {
try {
const products = await Product.find();
res.render('products/index', { products });
} catch (err) {
res.status(500).send(err.message);
}
};
exports.show = async (req, res) => {
try {
const product = await Product.findById(req.params.id).populate('comments ratings');
if (!product) return res.status(404).send('Product not found');
res.render('products/show', { product });
} catch (err) {
res.status(500).send(err.message);
}
};
exports.createComment = async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).send('Product not found');
const comment = new Comment({
username: req.body.username,
content: req.body.content,
product: product._id
});
await comment.save();
product.comments.push(comment);
await product.save();
res.redirect(`/products/${product._id}`);
} catch (err) {
res.status(500).send(err.message);
}
};
exports.createRating = async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).send('Product not found');
const rating = new Rating({
username: req.body.username,
score: req.body.score,
product: product._id
});
await rating.save();
product.ratings.push(rating);
await product.save();
res.redirect(`/products/${product._id}`);
} catch (err) {
res.status(500).send(err.message);
}
};
productRoutes.js
示例:
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
// 显示所有产品
router.get('/', productController.index);
// 显示特定产品的详细信息
router.get('/:id', productController.show);
// 提交新评论
router.post('/:id/comments', productController.createComment);
// 提交新评分
router.post('/:id/ratings', productController.createRating);
module.exports = router;
4. 创建视图
4.1 创建产品列表页面
接下来,我们需要创建视图来展示产品列表和详细信息。我们将使用 EJS 作为模板引擎。在项目根目录下创建一个名为 views
的文件夹,并在其中创建一个名为 products
的文件夹。
在 views/products
文件夹中创建一个名为 index.ejs
的文件,并添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Products</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<h1>Product List</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% products.forEach(product => { %>
<tr>
<td><%= product.name %></td>
<td><%= product.description %></td>
<td>$<%= product.price.toFixed(2) %></td>
<td>
<a href="/products/<%= product._id %>">View Details</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
</body>
</html>
4.2 创建产品详细页面
在 views/products
文件夹中创建一个名为 show.ejs
的文件,并添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= product.name %></title>
<style>
.container {
max-width: 800px;
margin: 0 auto;
}
.product-image {
max-width: 100%;
height: auto;
}
.comments, .ratings {
margin-top: 20px;
}
.comment-form, .rating-form {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1><%= product.name %></h1>
<p><%= product.description %></p>
<p>Price: $<%= product.price.toFixed(2) %></p>
<% if (product.image) { %>
<img src="<%= product.image %>" alt="<%= product.name %>" class="product-image">
<% } %>
<h2>Comments</h2>
<div class="comments">
<% if (product.comments.length > 0) { %>
<ul>
<% product.comments.forEach(comment => { %>
<li>
<strong><%= comment.username %>:</strong> <%= comment.content %> (<%= new Date(comment.createdAt).toLocaleString() %>)
</li>
<% }) %>
</ul>
<% } else { %>
<p>No comments yet.</p>
<% } %>
</div>
<h2>Ratings</h2>
<div class="ratings">
<% if (product.ratings.length > 0) { %>
<ul>
<% product.ratings.forEach(rating => { %>
<li>
<strong><%= rating.username %>:</strong> <%= rating.score %> stars (<%= new Date(rating.createdAt).toLocaleString() %>)
</li>
<% }) %>
</ul>
<% } else { %>
<p>No ratings yet.</p>
<% } %>
</div>
<h2>Leave a Comment</h2>
<form action="/products/<%= product._id %>/comments" method="POST" class="comment-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="content">Comment:</label>
<textarea id="content" name="content" required></textarea>
<br>
<button type="submit">Submit Comment</button>
</form>
<h2>Rate This Product</h2>
<form action="/products/<%= product._id %>/ratings" method="POST" class="rating-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="score">Score (1-5):</label>
<input type="number" id="score" name="score" min="1" max="5" required>
<br>
<button type="submit">Submit Rating</button>
</form>
<a href="/products">Back to Product List</a>
</div>
</body>
</html>
4.3 创建布局文件
为了保持页面的一致性,我们可以创建一个布局文件来包含公共的 HTML 结构。在 views
文件夹中创建一个名为 layout.ejs
的文件,并添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
header {
background-color: #333;
color: white;
padding: 10px;
text-align: center;
}
main {
padding: 20px;
}
</style>
</head>
<body>
<header>
<h1>Online Comment and Rating App</h1>
</header>
<main>
<%- body %>
</main>
</body>
</html>
4.4 修改视图渲染
最后,我们需要修改 app.js
中的视图渲染逻辑,以使用布局文件。打开 app.js
文件,并在 app.set('view engine', 'ejs')
之后添加以下代码:
app.set('views', path.join(__dirname, 'views'));
app.set('view options', { layout: 'layout' });
5. 测试应用程序
现在,我们的应用程序已经基本完成了!让我们来测试一下它的功能。
-
启动服务器:
node app.js
-
打开浏览器,访问
http://localhost:3000/products
,你应该会看到产品列表页面。 -
点击某个产品的“View Details”链接,进入产品详细页面。在这里,你可以查看该产品的评论和评分,也可以提交新的评论和评分。
-
尝试提交一些评论和评分,看看它们是否正确地保存到了数据库中,并显示在页面上。
6. 总结与展望
恭喜你,你已经成功创建了一个完整的在线评论和评分应用程序!通过这个项目,我们学习了如何使用 Node.js、Express、Mongoose 和 EJS 来构建一个功能丰富的 Web 应用程序。我们还了解了如何设计数据库模型、创建路由和控制器、以及使用模板引擎来渲染页面。
当然,这只是一个基础版本的应用程序。如果你有兴趣,可以尝试为它添加更多功能,例如:
- 用户认证和授权:允许用户注册、登录,并限制只有登录用户才能提交评论和评分。
- 分页和搜索:当产品数量较多时,可以实现分页功能,并允许用户根据关键字搜索产品。
- API 接口:为应用程序提供 RESTful API,方便其他系统与之集成。
- 前端优化:使用 React 或 Vue.js 等前端框架来提升用户体验。
希望今天的讲座对你有所帮助!如果你有任何问题或建议,欢迎随时与我交流。祝你在编程的道路上越走越远,编码愉快!🚀
感谢大家的聆听,我们下次再见!👋