Node后台管理系统服务器搭建
前言
使用express框架结合mongoose搭建后台管理系统服务器。
一、中间件
// server.js
// 声明使用静态中间件
app.use(express.static('public'))
// 声明使用解析post请求的中间件
// 可以使用urlencoded方式来解析表单数据 extended: true ==> 表示使用第三方模块qs来处理
app.use(express.urlencoded({
extended: true })) // 请求体参数是: name=tom&pwd=123
// 可以使用json格式来处理表单数据
app.use(express.json()) // 请求体参数是json结构: {name: tom, pwd: 123}
// 声明使用解析cookie数据的中间件
const cookieParser = require('cookie-parser')
app.use(cookieParser())
// 声明使用路由器中间件
const indexRouter = require('./routers')
app.use('/', indexRouter) //
- 当连接到数据库后再启动服务器
// 通过mongoose连接数据库
mongoose.connect('mongodb://localhost/server_db2', {
useNewUrlParser: true })
.then(() => {
console.log('连接数据库成功!!!')
// 只有当连接上数据库后才去启动服务器
app.listen('5000', () => {
console.log('服务器启动成功, 请访问: http://localhost:5000')
})
})
.catch(error => {
console.error('连接数据库失败', error)
})
二、路由配置
routers/index.js
1.配置路由对象
const router = express.Router()
2.登录
// 登陆
router.post('/login', (req, res) => {
const {
username, password} = req.body
// 根据username和password查询数据库users, 如果没有, 返回提示错误的信息, 如果有, 返回登陆成功信息(包含user)
UserModel.findOne({
username, password: md5(password)})
.then(user => {
if (user) {
// 登陆成功
// 生成一个cookie(userid: user._id), 并交给浏览器保存
res.cookie('userid', user._id, {
maxAge: 1000 * 60 * 60 * 24})
if (user.role_id) {
RoleModel.findOne({
_id: user.role_id})
.then(role => {
user._doc.role = role
console.log('role user', user)
res.send({
status: 0, data: user})
})
} else {
user._doc.role = {
menus: []}
// 返回登陆成功信息(包含user)
res.send({
status: 0, data: user})
}
} else {
// 登陆失败
res.send({
status: 1, msg: '用户名或密码不正确!'})
}
})
.catch(error => {
console.error('登陆异常', error)
res.send({
status: 1, msg: '登陆异常, 请重新尝试'})
})
})
3.添加用户
router.post('/manage/user/add', (req, res) => {
// 读取请求参数数据
const {
username, password} = req.body
// 处理: 判断用户是否已经存在, 如果存在, 返回提示错误的信息, 如果不存在, 保存
// 查询(根据username)
UserModel.findOne({
username})
.then(user => {
// 如果user有值(已存在)
if (user) {
// 返回提示错误的信息
res.send({
status: 1, msg: '此用户已存在'})
return new Promise(() => {
})
} else {
// 没值(不存在)
// 保存
return UserModel.create({
...req.body, password: md5(password || 'atguigu')})
}
})
.then(user => {
// 返回包含user的json数据
res.send({
status: 0, data: user})
})
.catch(error => {
console.error('注册异常', error)
res.send({
status: 1, msg: '添加用户异常, 请重新尝试'})
})
})
4.添加角色
// 添加角色
router.post('/manage/role/add', (req, res) => {
const {
roleName} = req.body
RoleModel.create({
name: roleName})
.then(role => {
res.send({
status: 0, data: role})
})
.catch(error => {
console.error('添加角色异常', error)
res.send({
status: 1, msg: '添加角色异常, 请重新尝试'})
})
})
5.获取产品分页列表
// 获取产品分页列表
router.get('/manage/product/list', (req, res) => {
const {
pageNum, pageSize} = req.query
ProductModel.find({
})
.then(products => {
res.send({
status: 0, data: pageFilter(products, pageNum, pageSize)})
})
.catch(error => {
console.error('获取商品列表异常', error)
res.send({
status: 1, msg: '获取商品列表异常, 请重新尝试'})
})
})
6.获取所有用户列表
// 获取所有用户列表
router.get('/manage/user/list', (req, res) => {
UserModel.find({
username: {
'$ne': 'admin'}})
.then(users => {
RoleModel.find().then(roles => {
res.send({
status: 0, data: {
users, roles}})
})
})
.catch(error => {
console.error('获取用户列表异常', error)
res.send({
status: 1, msg: '获取用户列表异常, 请重新尝试'})
})
})
7.添加分类
// 添加分类
router.post('/manage/category/add', (req, res) => {
const {
categoryName, parentId} = req.body
CategoryModel.create({
name: categoryName, parentId: parentId || '0'})
.then(category => {
res.send({
status: 0, data: category})
})
.catch(error => {
console.error('添加分类异常', error)
res.send({
status: 1, msg: '添加分类异常, 请重新尝试'})
})
})
8.分页
/*
得到指定数组的分页信息对象
*/
function pageFilter(arr, pageNum, pageSize) {
pageNum = pageNum * 1
pageSize = pageSize * 1
const total = arr.length
const pages = Math.floor((total + pageSize - 1) / pageSize)
const start = pageSize * (pageNum - 1)
const end = start + pageSize <= total ? start + pageSize : total
const list = []
for (var i = start; i < end; i++) {
list.push(arr[i])
}
return {
pageNum,
total,
pages,
pageSize,
list
}
}
7.处理上传文件的路由
使用中间件multer来处理上传图片
- 创建storage配置对象
const upload = multer({
storage})
- 创建上传图片的函数
const uploadSingle = upload.single('image')
/*
处理文件上传的路由
*/
const multer = require('multer')
const path = require('path')
const fs = require('fs')
const dirPath = path.join(__dirname, '..', 'public/upload')
const storage = multer.diskStorage({
// destination: 'upload', //string时,服务启动将会自动创建文件夹
destination: function (req, file, cb) {
//函数需手动创建文件夹
// console.log('destination()', file)
if (!fs.existsSync(dirPath)) {
fs.mkdir(dirPath, function (err) {
if (err) {
console.log(err)
} else {
cb(null, dirPath)
}
})
} else {
cb(null, dirPath)
}
},
filename: function (req, file, cb) {
// console.log('filename()', file)
var ext = path.extname(file.originalname)
cb(null, file.fieldname + '-' + Date.now() + ext)
}
})
const upload = multer({
storage})
const uploadSingle = upload.single('image')
module.exports = function fileUpload(router) {
// 上传图片
router.post('/manage/img/upload', (req, res) => {
uploadSingle(req, res, function (err) {
//错误处理
if (err) {
return res.send({
status: 1,
msg: '上传文件失败'
})
}
var file = req.file
res.send({
status: 0,
data: {
name: file.filename,
url: 'http://localhost:5000/upload/' + file.filename
}
})
})
})
// 删除图片
router.post('/manage/img/delete', (req, res) => {
const {
name} = req.body
fs.unlink(path.join(dirPath, name), (err) => {
if (err) {
console.log(err)
res.send({
status: 1,
msg: '删除文件失败'
})
} else {
res.send({
status: 0
})
}
})
})
}
三、MongoDB数据库模块配置
mysql采用table和结构化的sql语句来处理数据,在mysql中需要预先定义数据结构schema,并定义table中数据字段的关系。
mongoDB采用类JSON的documents来存储数据。
1.UserModel
- 定义Schema(描述文档结构)
const roleSchema = new mongoose.Schema({
name: {
type: String, required: true}, // 角色名称
auth_name: String, // 授权人
auth_time: Number, // 授权时间
create_time: {
type: Number, default: Date.now}, // 创建时间
menus: Array // 所有有权限操作的菜单path的数组
})
- 定义Model(与集合对应, 可以操作集合)
const UserModel = mongoose.model('users', userSchema)
- 初始化默认超级管理员用户: admin/admin
UserModel.findOne({
username: 'admin'}).then(user => {
if(!user) {
UserModel.create({
username: 'admin', password: md5('admin')})
.then(user => {
console.log('初始化用户: 用户名: admin 密码为: admin')
})
}
})
2.RoleModle
// 1.引入mongoose
const mongoose = require('mongoose')
// 2.字义Schema(描述文档结构)
const roleSchema = new mongoose.Schema({
name: {
type: String, required: true}, // 角色名称
auth_name: String, // 授权人
auth_time: Number, // 授权时间
create_time: {
type: Number, default: Date.now}, // 创建时间
menus: Array // 所有有权限操作的菜单path的数组
})
// 3. 定义Model(与集合对应, 可以操作集合)
const RoleModel = mongoose.model('roles', roleSchema)
// 4. 向外暴露Model
module.exports = RoleModel
3.ProductModel
// 1.引入mongoose
const mongoose = require('mongoose')
// 2.字义Schema(描述文档结构)
const productSchema = new mongoose.Schema({
categoryId: {
type: String, required: true}, // 所属分类的id
pCategoryId: {
type: String, required: true}, // 所属分类的父分类id
name: {
type: String, required: true}, // 名称
price: {
type: Number, required: true}, // 价格
desc: {
type: String},
status: {
type: Number, default: 1}, // 商品状态: 1:在售, 2: 下架了
imgs: {
type: Array, default: []}, // n个图片文件名的json字符串
detail: {
type: String}
})
// 3. 定义Model(与集合对应, 可以操作集合)
const ProductModel = mongoose.model('products', productSchema)
// 4. 向外暴露Model
module.exports = ProductModel
4.CategoryModel
// 1.引入mongoose
const mongoose = require('mongoose')
// 2.字义Schema(描述文档结构)
const categorySchema = new mongoose.Schema({
name: {
type: String, required: true},
parentId: {
type: String, required: true, default: '0'}
})
// 3. 定义Model(与集合对应, 可以操作集合)
const CategoryModel = mongoose.model('categorys', categorySchema)
// 4. 向外暴露Model
module.exports = CategoryModel
解决前端BrowserRouter问题
该APP使用前端路由,因此当访问服务器时,服务器中没有响应路由就会报404错误。
解决办法:当服务器没有相对应的路由时,就返回index.html让前端进行路由匹配。
app.use((req, res) => {
fs.readFile(__dirname + '/public/index.html', (err, data) => {
if (err) {
console.log(err)
res.send('后台错误')
} else {
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
});
res.end(data)
}
})
})
四、MongoDB和关系型数据库有什么区别?
MongoDB不是关系型数据库,没有"表"的概念,没有"字段"的概念,没有"行"的概念。
MongoDB 没有 Schema(模式、数据模型),就不需要改动数据库,只需要在应用层做必要的改动。
MongoDB 通过键值对的方式来存储数据而不是基于行的表格型存储。
与传统关系数据库有统一的SQL语言操作接口不同,MongoDB 系统通常有自己特有的API接口