1. 项目缘起与技术选型:为什么是Vue+Express+MongoDB?
大家好,我是老张,一个在前后端领域摸爬滚打了十来年的老码农。这些年我做过不少项目,从企业OA到电商平台,但要说最有趣、最能体现全栈技术魅力的,我觉得还得是这种带点“玩乐”性质的应用,比如我们今天要聊的KTV点歌系统。
为什么选这个项目来分享呢?首先,它场景足够具体,每个人都能理解它的功能;其次,它技术栈覆盖全面,从前端UI交互、后端API设计,到数据库建模、文件上传、用户认证,一个不落;最后,它实战性强,做完这个项目,你基本就摸清了现代Web应用开发的完整流程。
那么,技术栈为什么锁定 Vue + Express + MongoDB 这个组合呢?我给大家掰扯掰扯我的考量。
前端用Vue.js,这几乎是现在国内前端开发的首选框架了。它上手快,文档友好,生态繁荣。特别是配合 ElementUI 和 iViewUI 这类成熟的UI组件库,开发后台管理系统和复杂交互界面,效率能提升好几倍。你不需要从零开始写一个漂亮的按钮或者复杂的表格,直接拿过来用,样式统一,功能稳定。对于KTV点歌系统这种需要丰富UI组件(如歌曲列表、分页、弹窗、表单)的项目,简直是绝配。
后端用Express.js,这是Node.js生态里最老牌、最经典的Web框架。它足够轻量,没有太多“约定俗成”的条条框框,你可以按自己的想法灵活组织代码结构。对于刚接触Node.js后端开发的朋友来说,Express的学习曲线非常平缓,你能清晰地理解一个HTTP请求是如何被接收、处理,并返回响应的。用它来构建RESTful API,为前端提供数据接口,再合适不过。
数据库用MongoDB,这是一个基于文档的NoSQL数据库。为什么不用传统的关系型数据库如MySQL呢?因为歌曲、用户、订单这些数据,用JSON文档的形式来存储,天然更贴合我们JavaScript对象的思维。一首歌的信息,可能包含歌名、歌手、语种、风格、海报URL、歌曲文件路径等,这些字段组合在一起,就是一个完整的文档,直接存进MongoDB的一个集合(Collection)里,查询和更新都非常直观。而且它的Schema比较灵活,后期如果想给歌曲增加一个“热度评分”字段,直接加就行,不用去改表结构。
这个组合,构成了一个非常典型的 JAMstack(JavaScript, API, Markup)或说 MEVN(MongoDB, Express, Vue, Node)全栈架构。前后端完全分离,前端通过Axios等库调用后端API,后端专心处理业务逻辑和数据存取,职责清晰,便于团队协作和后期维护。
2. 环境准备与项目骨架搭建:从零到一的启动
光说不练假把式,咱们直接动手。首先,确保你的电脑上已经安装了 Node.js(建议14.x或16.x以上LTS版本)和 MongoDB。Node.js去官网下载安装包就行,安装时会自带npm包管理器。MongoDB可以下载社区版,安装后记得把它启动起来,通常命令是 mongod。如果你觉得命令行操作数据库不方便,强烈安利一个图形化管理工具 MongoDB Compass,直观又省事。
接下来,我们为整个项目创建一个总目录,比如就叫 ktv-music-system。在这个目录下,我们会创建三个独立的子项目:后端服务器、前台点歌客户端、后台管理系统。这种多项目(Monorepo)的结构,虽然初期配置稍麻烦,但后期管理和代码复用会非常方便。
2.1 后端Express服务器初始化
进入 ktv-music-system 目录,我们先来搭建后端。
# 创建server目录,并初始化npm项目
mkdir server && cd server
npm init -y
接下来,安装我们需要的核心依赖。这里我直接给出 package.json 里 dependencies 部分的一个参考,你可以一次性安装。
{
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"multer": "^1.4.5-lts.1",
"svg-captcha": "^1.4.0",
"cors": "^2.8.5",
"dotenv": "^16.0.3"
}
}
安装命令:npm install express mongoose jsonwebtoken bcryptjs multer svg-captcha cors dotenv --save。
我来简单解释下这些包的作用:
express: 主角,Web框架。mongoose: MongoDB的优雅对象模型工具,用来定义数据模型和操作数据库。jsonwebtoken: 生成和验证JWT(JSON Web Token),用于用户登录认证。bcryptjs: 加密用户密码,绝对不能明文存储。multer: 处理文件上传,我们的歌曲MP3文件和海报图片就靠它了。svg-captcha: 生成图形验证码,防止恶意注册或登录。cors: 处理跨域请求,因为前端和后端在不同端口运行。dotenv: 从.env文件加载环境变量,保护敏感配置(如数据库连接字符串、JWT密钥)。
安装好后,我们创建项目的入口文件 index.js 和一个基本的应用结构。
// server/index.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3001;
// 中间件
app.use(cors()); // 允许跨域
app.use(express.json()); // 解析JSON格式的请求体
app.use(express.urlencoded({ extended: true })); // 解析URL-encoded格式的请求体
// 静态资源服务(稍后用于提供上传的歌曲和图片)
app.use('/static', express.static('static'));
// 连接MongoDB
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('MongoDB连接成功'))
.catch(err => console.error('MongoDB连接失败:', err));
// 一个简单的测试路由
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'KTV后端服务运行正常' });
});
// 在这里引入后续定义的路由,例如:
// const userRoutes = require('./api/user');
// app.use('/api/user', userRoutes);
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
同时,在 server 目录下创建 .env 文件,存放你的MongoDB连接地址和JWT密钥(记得不要提交到Git!)。
MONGODB_URI=mongodb://localhost:27017/ktv_music_db
JWT_SECRET=your_super_secret_jwt_key_here_change_me
现在,运行 node index.js,如果看到“MongoDB连接成功”和端口提示,恭喜你,后端服务的基础骨架就立起来了!
2.2 前端Vue项目初始化(前台与后台)
前端我们使用Vue CLI来快速搭建两个独立的项目。先退回 ktv-music-system 根目录。
# 安装Vue CLI(如果已安装请忽略)
npm install -g @vue/cli
# 创建前台点歌项目
vue create ktv-client
# 创建后台管理项目
vue create ktv-admin
在创建过程中,Vue CLI会询问你配置。对于这个项目,我建议手动选择特性:
- 选中 Babel 和 Router(Vue Router)是必须的。
- Vuex(状态管理)也建议选上,用于管理全局的歌曲播放状态、用户登录状态等。
- CSS预处理器,按个人喜好,选 Sass/SCSS 不错。
- 剩下的像Linter/Formatter,初学者可以先不选,避免被代码规范困扰。
创建完成后,分别进入两个前端项目安装UI组件库。以 ktv-client 为例:
cd ktv-client
vue add element
# 或者手动安装 npm install element-ui -S
对于 ktv-admin,你可以选择安装 element-ui 或 view-ui(即iView),或者两个都装,看哪个组件更符合你的审美。我个人的经验是,ElementUI的表格和表单组件特别强大,适合后台管理;而iView的某些特色组件也很不错。安装命令类似:npm install view-design --save。
别忘了安装 Axios 用于发起HTTP请求:npm install axios --save。
至此,我们三个项目的骨架都搭建完毕了。目录结构看起来应该是这样的:
ktv-music-system/
├── server/ (Express后端)
│ ├── index.js
│ ├── .env
│ ├── package.json
│ ├── api/ (路由文件夹,待创建)
│ ├── models/ (数据模型,待创建)
│ └── static/ (静态资源,待创建)
├── ktv-client/ (Vue前台)
│ └── src/
│ ├── views/ (页面组件)
│ ├── components/ (可复用组件)
│ ├── router/
│ ├── store/
│ └── App.vue
└── ktv-admin/ (Vue后台)
└── src/
├── views/
├── components/
├── router/
├── store/
└── App.vue
3. 数据库建模与Mongoose实战:定义核心数据
后端服务跑起来了,接下来就要设计数据库了。我们用Mongoose来定义数据模型(Schema),这相当于传统SQL数据库里的“建表”。
在 server/models 目录下,我们创建几个核心的模型文件。
3.1 用户模型 (User.js)
用户分两种:普通点歌用户和后台管理员。我们可以用一个字段来区分。
// server/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true },
// 用户类型:'user' 普通用户, 'admin' 管理员
role: { type: String, enum: ['user', 'admin'], default: 'user' },
// 用户状态:'active' 正常, 'inactive' 停用, 'frozen' 冻结(余额不足等)
status: { type: String, enum: ['active', 'inactive', 'frozen'], default: 'active' },
// 账户余额(以分钟计)或套餐剩余时间
balance: { type: Number, default: 0 },
// 开户时间、上机时间(用于计算剩余时长)
createdAt: { type: Date, default: Date.now },
lastLoginAt: { type: Date },
// 用户头像等扩展信息
avatar: { type: String, default: '' }
});
// 在保存用户之前,对密码进行加密
userSchema.pre('save', async function(next) {
// 只有当密码被修改(或新建)时才进行加密
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// 实例方法:比较密码
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
这个模型定义了用户的基本信息。注意 pre('save') 这个中间件,它会在文档保存前自动执行,确保存入数据库的密码是加密后的哈希值,而不是明文。comparePassword 方法则用于登录时校验密码。
3.2 歌曲模型 (Song.js)
这是系统的核心数据。
// server/models/Song.js
const mongoose = require('mongoose');
const songSchema = new mongoose.Schema({
title: { type: String, required: true, index: true }, // 歌名,加索引方便搜索
artist: { type: String, required: true, index: true }, // 歌手
language: { type: String, enum: ['国语', '粤语', '英语', '日语'


603

被折叠的 条评论
为什么被折叠?



