一、前言
本节内容主要对小案例做一个总结:
1、如何开始搭建小项目
2、路由设计
3、模块应用
4、项目源码以及实现过程github地址:
项目演示如下:
二、主要内容
1、项目的关键性js源码:
项目的入口:
/**
* app.js 入门模块
* 职责:
* 创建服务
* 做一些服务相关配置
* 模板引擎
* body-parser 解析表单 post 请求体
* 提供静态资源服务
* 挂载路由
* 监听端口启动服务
*/ var express = require('express')
var router = require('./router')
var bodyParser = require('body-parser') var app = express() app.use('/node_modules/', express.static('./node_modules/'))
app.use('/public/', express.static('./public/')) app.engine('html', require('express-art-template')) // 配置模板引擎和 body-parser 一定要在 app.use(router) 挂载路由之前
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json()) // 把路由容器挂载到 app 服务中
app.use(router) app.listen(3000, function () {
console.log('running 3000...')
}) module.exports = app
app.js
路由设计模块:
/**
* router.js 路由模块
* 职责:
* 处理路由
* 根据不同的请求方法+请求路径设置具体的请求处理函数
* 模块职责要单一,不要乱写
* 我们划分模块的目的就是为了增强项目代码的可维护性
* 提升开发效率
*/ var fs = require('fs')
var Student = require('./student') // Express 提供了一种更好的方式
// 专门用来包装路由的
var express = require('express') // 1. 创建一个路由容器
var router = express.Router() // 2. 把路由都挂载到 router 路由容器中 /*
* 渲染学生列表页面
*/
router.get('/students', function (req, res) {
Student.find(function (err, students) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子'
],
students: students
})
})
}) /*
* 渲染添加学生页面
*/
router.get('/students/new', function (req, res) {
res.render('new.html')
}) /*
* 处理添加学生
*/
router.post('/students/new', function (req, res) {
// 1. 获取表单数据
// 2. 处理
// 将数据保存到 db.json 文件中用以持久化
// 3. 发送响应
Student.save(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
}) /*
* 渲染编辑学生页面
*/
router.get('/students/edit', function (req, res) {
// 1. 在客户端的列表页中处理链接问题(需要有 id 参数)
// 2. 获取要编辑的学生 id
//
// 3. 渲染编辑页面
// 根据 id 把学生信息查出来
// 使用模板引擎渲染页面 Student.findById(parseInt(req.query.id), function (err, student) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('edit.html', {
student: student
})
})
}) /*
* 处理编辑学生
*/
router.post('/students/edit', function (req, res) {
// 1. 获取表单数据
// req.body
// 2. 更新
// Student.updateById()
// 3. 发送响应
Student.updateById(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
}) /*
* 处理删除学生
*/
router.get('/students/delete', function (req, res) {
// 1. 获取要删除的 id
// 2. 根据 id 执行删除操作
// 3. 根据操作结果发送响应数据 Student.deleteById(req.query.id, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
}) // 3. 把 router 导出
module.exports = router // 这样也不方便
// module.exports = function (app) {
// app.get('/students', function (req, res) {
// // readFile 的第二个参数是可选的,传入 utf8 就是告诉它把读取到的文件直接按照 utf8 编码转成我们能认识的字符
// // 除了这样来转换之外,也可以通过 data.toString() 的方式
// fs.readFile('./db.json', 'utf8', function (err, data) {
// if (err) {
// return res.status(500).send('Server error.')
// } // // 从文件中读取到的数据一定是字符串
// // 所以这里一定要手动转成对象
// var students = JSON.parse(data).students // res.render('index.html', {
// fruits: [
// '苹果',
// '香蕉',
// '橘子'
// ],
// students: students
// })
// })
// }) // app.get('/students/new', function (req, res) { // }) // app.get('/students/new', function (req, res) { // }) // app.get('/students/new', function (req, res) { // }) // app.get('/students/new', function (req, res) { // }) // app.get('/students/new', function (req, res) { // })
// }
功能实现模块
/**
* student.js
* 数据操作文件模块
* 职责:操作文件中的数据,只处理数据,不关心业务
*
* 这里才是我们学习 Node 的精华部分:奥义之所在
* 封装异步 API
*/ var fs = require('fs') var dbPath = './db.json' /**
* 获取学生列表
* @param {Function} callback 回调函数
*/
exports.find = function (callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
callback(null, JSON.parse(data).students)
})
} /**
* 根据 id 获取学生信息对象
* @param {Number} id 学生 id
* @param {Function} callback 回调函数
*/
exports.findById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
var ret = students.find(function (item) {
return item.id === parseInt(id)
})
callback(null, ret)
})
} /**
* 添加保存学生
* @param {Object} student 学生对象
* @param {Function} callback 回调函数
*/
exports.save = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students // 添加 id ,唯一不重复
student.id = students[students.length - 1].id + 1 // 把用户传递的对象保存到数组中
students.push(student) // 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
}) // 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
} /**
* 更新学生
*/
exports.updateById = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students // 注意:这里记得把 id 统一转换为数字类型
student.id = parseInt(student.id) // 你要修改谁,就需要把谁找出来
// EcmaScript 6 中的一个数组方法:find
// 需要接收一个函数作为参数
// 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项
var stu = students.find(function (item) {
return item.id === student.id
}) // 这种方式你就写死了,有 100 个难道就写 100 次吗?
// stu.name = student.name
// stu.age = student.age // 遍历拷贝对象
for (var key in student) {
stu[key] = student[key]
} // 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
}) // 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
} /**
* 删除学生
*/
exports.deleteById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students // findIndex 方法专门用来根据条件查找元素的下标
var deleteId = students.findIndex(function (item) {
return item.id === parseInt(id)
}) // 根据下标从数组中删除对应的学生对象
students.splice(deleteId, 1) // 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
}) // 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
student.js
2、项目搭建以及具体实现
第一步:搭建目录,用public文件夹来存放css img等静态文件,用views来存放页面
第二步:app.js作为整个项目的入口,先用express创建好服务器,
安装好express: npm install express
//引入express
var express = require('express'); //创建
var app = new express();
//请求访问
app.get('/',function (req, res){
res.send('hello student')
})
//设置端口
app.listen(3000, function () { console.log('server is running')
})
第三步:配置开放静态资源 :项目中我们一般让public目录下的资源作为公共资源,可以给用户访问
//配置开放静态资源
app.use('/public/', express.static('./public/'))
app.use('/node_moudles/', express.static('./node_moudles/'))
第四步:读取数据文件将首页渲染出来,这里需要用到express-art-template包
安装:npm install express-art-template
配置如下:
//配置express-art-template
app.engine('html', require('express-art-template'))
//使用模板来渲染
app.get('/',function (req, res){
//这里会默认到views文件下面去找index.html
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子'
]
})
})
第五步:读取数据,将数据渲染到页面中,先输出一下看看data里面的内容,
data中的内容如下:发现为一串十六进制数据,
用JSON.parse()函数可以将字符串转换为对象,如下图所示,现在我们可以拿到里面的对象了
第六步:需要进行路由设计,路由就相当于一张记录有所有路径信息的一张表,我们将所有要访问的路径存放在一张表中,利用node.js中的模块化思想,将每一个功能分块实现
(1) 路由表设计如下:
请求方法 |
请求路径 |
GET |
POST |
备注作用 |
GET |
/ students |
渲染首页 |
||
GET GET |
/students/new /students/edit |
点击 跳到一个新的添加页面, 跳到编辑页 |
||
POST |
/students |
Name age gender |
点击Submit 按钮,跳到首页 |
|
post |
/students/edit |
Id |
Name age gender |
点击“编辑”,跳到编辑那个页面 |
GET |
/students/delete |
id |
操作删除 |
(2)设计好路由表之后先将路由表的壳子搭好
//按照上面的表格配置好路由壳子
router.get('/', function (req, res) { }); router.get('/students/new', function (req, res){ });
router.get('/students/edit', function (req, res){ }); router.post('/students', function (req, res) { }); router.post('/students/edit', function (req, res) { }); router.get('/students/delete', function(req, res) { });
(3)我们要在app.js中引用这个自定义的路由模块,所以需要在router.js中导出, 在app.js中引用
router.js中导出方法:
//最后还要导出这个路由
module.exprots = router;
在app.js中引用
//引入路由
var router = require(./router.js) //使用路由模块
app.use(router)
将第五步中的请求/的一条路由放到router.js里面,测试一下是能请求成功的
第七步:上面的router.js中既有get 路由的功能,又有fs查询读取文件的功能,我们也可以用模块化 的思维,将router里面的增删改功能给抽取出来放到一个新的student.js模块中,让这个新的模块实现我们需要的功能
(1)同样先搭建好壳子,也需要导出,这里我们就用exports导出
//查找学生信息
exports.find = function(){} //添加保存学生信息
exports.save = function(){} //修改更新学生信息
exports.updateById = function(){} //删除学生信息
exports.deleteById = function(){}
(2)同样也需要在那边引用
//加入student模块
var Student = require('./student.js') //调用
Student.find()
第八步:查找实现,由于这个函数里面还用到了读取文件的操作,是异步的,所以不能用传统的函数调用的方式,要获取里面的数据需要用到回调函数来拿到数据
(1)在student.js中写查找方法
//可以先模拟调用写法
//data=Student.find()由于函数中是异步的所以不能用传统的方式
Student.find( function (data) {
console.log(data)
})
exports.find = function (callback) { fs.readFile('./data.json', function(err, data) { if (err) {
return callback(err); //如果文件读取错误,就结束让回调函数处理 } //文件读取成功,第一个参数为null说明成功,这里需要将得到的二进制字符串转化为对象
callback(null, JSON.parse(data).students) }) }
(2) router.js中调用如下:
//请求路径为/students的时候,调用find方法,并在页面中渲染数据
router.get('/students', function (req, res) { Student.find(function (err, students) {
if(err) {
return res.status(500).send('Server err');
} res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子'
],
students: students
})
}) });
第九步:
(1)在student.js中写save添加方法,同样也可以先模拟调用再来写这个方法,添加的时候需要将要添加的对象传进去,然后在文件中添加传进去的对象,由于data文件是一个对象,还要将数组转化为对象
exports.save = function (student, callback ) { fs.readFile('./data.json', function (err, data) {
if(err){
return callback(err) //回调函数中如何处理错误,在这里我们不用关心
} //接受文件,这里得到的为数组
var students = JSON.parse(data).students
student.id = students[students.length-1].id+1; //push向数组中添加新的
students.push(student); //执行完上面几步之后得到的是一个数组,但是在data中存入的是json格式的对象
var fileData = JSON.stringify({
students: students
}) //然后将文件写入data里面
fs.writeFile('./data.json', fileData, function (err){
if(err){
callback(err);
} callback(null);
}) })
}
(2)在router.js中调用:由于上面添加的过程提交的数据是以post方式提交,而express是不支持post提交的,所以要先安装一个body-parser包
安装:npm install body-parser
配置:
//安装好后引用包
var bodyParse = require('body-parse'); //配置
app.use(bodyParse.urlencoded({ extentend: false}))
app.use(bodyParse.json())
点击按钮跳到添加页面,然后点击提交按钮,将数据提交,并且页面重新跳到首页
router.get('/students/new', function (req, res){
res.render('new.html')
}); router.post('/students/new', function (req, res) {
//先打印提交的内容
console.log(req.body);
Student.save(req.body, function (err) {
if(err) {
return res.status(500).send('Server err');
}
//重新跳会首页
res.redirect('/students')
}) });
第十步:实现点击“编辑”跳到编辑页面
(1)这里需要通过id查询到点击的那条数据,并且在编辑页中渲染
//封装一个通过id请求的的函数 exports.findById = function(id, callback) {
fs.readFile('./data.json', 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
//这个方法是es6提供的,当满足了item.id === parseInt(id),的时候会会返回整个对象
var ret = students.find(function (item) {
return item.id === parseInt(id);
}) callback(null, ret);
});
}
(2)在router.js函数中get方式请求/students/edit
/这一次通过get到那个编辑页面
router.get('/students/edit', function (req, res) { //调用得到的是保存之后的数据
//1.如何获取数据
//2.处理数据
//3.发送响应
//4.将数据再次写入data.json文件
Student.findById(req.query.id, function (err, student) {
if (err) {
return res.status(500).send('Server err');
}
//console.log(student);
res.render('edit.html',{ student: student
})
}) });
(3)更新数据,需要以post方式提交数据到/students/edit,先在student
exports.find = function (callback){
fs.readFile('./data.json', function (err, data) {
if(err){
return callback(err) //回调函数中如何处理错误,在这里我们不用关心
}
//这里的err=null说明请求成功
callback(null, JSON.parse(data).students); }) }
(4)router.js中调用
router.post('/students/edit', function (req, res) { Student.updateById(req.body, function(err){
if (err){
return res.status(500).send('Server err');
} res.redirect('/students')
}) });
第十一步:实现删除,删除的时候也要通过url地址拿到删除的id,然后查询到删除的对象
exports.deleteById = function (id, callback) { fs.readFile('./data.json','utf8', function (err, data) {
if(err){
return callback(err) //回调函数中如何处理错误,在这里我们不用关心
}
//得到的是一个数组
var students = JSON.parse(data).students; var deleteId = students.findIndex(function (item) {
return item.id === parseInt(id);
}) //做删除操作
students.splice(deleteId, 1); //删除之后吧数组转化为对象
var fileData = JSON.stringify({
students:students
}) fs.writeFile('./data.json', fileData, function(err){
if(err) {
return callback(err)
} callback(null);
})
}); }
三、总结以及踩坑
1、做项目的时候可以将一些允许用户访问的静态资源文件放到一个public文件中,开放静态文件
2、要善于将项目中的功能分成模块化来处理
3、req.body拿到的是二进制或者十六进制数据,要对数据进行操作必须要用JSON.parse(data)转化为对象
4、将数组转化为对象用.
var fileData = JSON.stringify({
students:students
})