node.js(2)
Demo1--留言板
1. 前置知识
1.1 path路径操作模块(url)
参考文档:https://nodejs.org/docs/latest-v13.x/api/path.html
path.basename:获取路径的文件名,默认包含扩展名
path.dirname:获取路径中的目录部分
path.extname:获取一个路径中的扩展名部分
path.parse:把路径转换为对象
root:根路径
dir:目录
base:包含后缀名的文件名
ext:后缀名
name:不包含后缀名的文件名
path.join:拼接路径
path.isAbsolute:判断一个路径是否为绝对路径
a. parse 和query
cmd终端输入:node + 回车可进入node控制台
用来对url地址进行归类,以方便进行提取和进行相关的操作
var url = require('url')var obj = url.parse('/pinglun?name=jack&message=hello', true)console.log(obj)console.log(obj.query)输出:Url { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: '?name=jack&message=hello', query: [Object: null prototype] { name: 'jack', message: 'hello' }, pathname: '/pinglun', path: '/pinglun?name=jack&message=hello', href: '/pinglun?name=jack&message=hello'}[Object: null prototype] { name: 'jack', message: 'hello' }
1.2 Node中的其它成员
(__dirname,__filename)
在每个模块中,除了require
,exports
等模块相关的API之外,还有两个特殊的成员:
__dirname
,是一个成员,可以用来动态获取当前文件模块所属目录的绝对路径__filename
,可以用来动态获取当前文件的绝对路径(包含文件名)__dirname
和filename
是不受执行node命令所属路径影响的
在文件操作中,使用相对路径是不可靠的,因为node中文件操作的路径被设计为相对于执行node命令所处的路径。
所以为了解决这个问题,只需要把相对路径变为绝对路径(绝对路径不受任何影响)就可以了。
就可以使用__dirname
或者__filename
来帮助我们解决这个问题
在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,推荐使用path.join()
来辅助拼接
var fs = require('fs');var path = require('path');// console.log(__dirname + 'a.txt');// path.join方法会将文件操作中的相对路径都统一的转为动态的绝对路径fs.readFile(path.join(__dirname + '/a.txt'),'utf8',function(err,data){ if(err){ throw err } console.log(data);});
补充:模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)
注意:
模块中的路径标识和文件操作中的相对路径标识不一致
模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响
1.3 静态服务
统一处理:
我们为了方便的统一处理这些静态资源,所以我们约定把所有的静态资源都存放在 public 目录中
然后开放 public 目录中的静态资源
当请求 /public/xxx 的时候,读取响应 public 目录中的具体资源
所以我们就直接可以把请求路径当作文件路径来直接进行读取
哪些资源能被用户访问,哪些资源不能被用户访问,我现在可以通过代码来进行非常灵活的控制
解决什么问题
浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析,当在解析的过程中,
如果发现:link script img iframe video audio
,等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求,造成了资源的加载失败。
这里用else if (pathname.indexOf('/public/') === 0)
来判断url是否以/public
开头
这里/ index.html
和整个/public
目录中的资源都允许被访问
var url = require('url')var parseObj = url.parse(req.url, true)// 单独获取不包含查询字符串的路径部分(该路径不包含 ? 之后的内容)var pathname = parseObj.pathname else if (pathname.indexOf('/public/') === 0) { // /public/css/main.css // /public/js/main.js // /public/lib/jquery.js // 统一处理: // 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源 // 所以我们就直接可以把请求路径当作文件路径来直接进行读取 fs.readFile('.' + pathname, function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) }) }
1.4 页面跳转
跳转页面404
else { // 其它的都处理成 404 找不到 fs.readFile('./views/404.html', function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) }) }
跳转
<a class="btn btn-success" href="/post">发表留言</a>//================== else if (pathname === '/post') { // 其它的都处理成 404 找不到 fs.readFile('./views/post.html', function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) })
1.5 模板引擎渲染数据
var htmlStr = template.render(data.toString(), { comments: comments })
1.6 get表单提交
action 就是表单提交的地址,说白了就是请求的 url 地址
method 请求方法 get post
<form action="/pinglun" method="get"> //================================== else if (pathname === '/pinglun') { var comment = parseObj.query comment.dateTime = '2017-11-2 17:11:22' comments.unshift(comment) res.statusCode = 302 res.setHeader('Location', '/') res.end() }
1.7 表单提交重定向
如何通过服务器让客户端重定向?
状态码设置为 302 临时重定向
statusCode
在响应头中通过 Location 告诉客户端往哪儿重定向
setHeader
如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求, 所以你就能看到客户端自动跳转了
res.statusCode = 302res.setHeader('Location', '/')res.end()
2. 留言板案例
a. 案例步骤
/ index.html
开放 public 目录中的静态资源,当请求 /public/xxx 的时候,读取响应 public 目录中的具体资源
/post post.html
/pinglun
4.1 接收表单提交数据
4.2 存储表单提交的数据
4.3 让表单重定向到 /
statusCode,setHeader
[图片上传失败...(image-e8dce6-1603348970667)]
点击发表后提交到图一评论列表
[图片上传失败...(image-5c71e6-1603348970667)]
b. 案例代码
目录结构
feedback node_modules public css img js views index.html post.html 404.html
app.js
// app application 应用程序var http = require('http')var fs = require('fs')var url = require('url')var template = require('art-template')var comments = [ { name: '张三', message: '今天天气不错!', dateTime: '2015-10-16' }, { name: '张三2', message: '今天天气不错!', dateTime: '2015-10-16' }, { name: '张三3', message: '今天天气不错!', dateTime: '2015-10-16' }, { name: '张三4', message: '今天天气不错!', dateTime: '2015-10-16' }, { name: '张三5', message: '今天天气不错!', dateTime: '2015-10-16' }]// /pinglun?name=的撒的撒&message=的撒的撒的撒// 对于这种表单提交的请求路径,由于其中具有用户动态填写的内容// 所以你不可能通过去判断完整的 url 路径来处理这个请求// // 结论:对于我们来讲,其实只需要判定,如果你的请求路径是 /pinglun 的时候,那我就认为你提交表单的请求过来了http .createServer(function (req, res) { // 简写方式,该函数会直接被注册为 server 的 request 请求事件处理函数 // 使用 url.parse 方法将路径解析为一个方便操作的对象,第二个参数为 true 表示直接将查询字符串转为一个对象(通过 query 属性来访问) var parseObj = url.parse(req.url, true) // 单独获取不包含查询字符串的路径部分(该路径不包含 ? 之后的内容) var pathname = parseObj.pathname if (pathname === '/') { fs.readFile('./views/index.html', function (err, data) { if (err) { return res.end('404 Not Found.') } var htmlStr = template.render(data.toString(), { comments: comments }) res.end(htmlStr) }) } else if (pathname === '/post') { // 其它的都处理成 404 找不到 fs.readFile('./views/post.html', function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) }) } else if (pathname.indexOf('/public/') === 0) { // /public/css/main.css // /public/js/main.js // /public/lib/jquery.js // 统一处理: // 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源 // 所以我们就直接可以把请求路径当作文件路径来直接进行读取 fs.readFile('.' + pathname, function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) }) } else if (pathname === '/pinglun') { // 注意:这个时候无论 /pinglun?xxx 之后是什么,我都不用担心了,因为我的 pathname 是不包含 ? 之后的那个路径 // 一次请求对应一次响应,响应结束这次请求也就结束了 // res.end(JSON.stringify(parseObj.query)) // 我们已经使用 url 模块的 parse 方法把请求路径中的查询字符串给解析成一个对象了 // 所以接下来要做的就是: // 1. 获取表单提交的数据 parseObj.query // 2. 将当前时间日期添加到数据对象中,然后存储到数组中 // 3. 让用户重定向跳转到首页 / // 当用户重新请求 / 的时候,我数组中的数据已经发生变化了,所以用户看到的页面也就变了 var comment = parseObj.query comment.dateTime = '2017-11-2 17:11:22' comments.unshift(comment) // 服务端这个时候已经把数据存储好了,接下来就是让用户重新请求 / 首页,就可以看到最新的留言内容了//如何通过服务器让客户端重定向?// 状态码设置为 302 临时重定向 statusCode//在响应头中通过 Location 告诉客户端往哪儿重定向 setHeader// 如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求, 所以你就能看到客户端自动跳转了 res.statusCode = 302 res.setHeader('Location', '/') res.end() } else { // 其它的都处理成 404 找不到 fs.readFile('./views/404.html', function (err, data) { if (err) { return res.end('404 Not Found.') } res.end(data) }) } }) .listen(3000, function () { console.log('running...') })
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>留言本</title> <!-- 注意:在服务端中,文件中的路径就不要去写相对路径了。 因为这个时候所有的资源都是通过 url 标识来获取的 我的服务器开放了 /public/ 目录 所以这里的请求路径都写成:/public/xxx / 在这里就是 url 根路径的意思。 浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上 不要再想文件路径了,把所有的路径都想象成 url 地址 --> <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css"></head><body> <div class="header container"> <div class="page-header"> <h1>Example page header <small>Subtext for header</small></h1> <a class="btn btn-success" href="/post">发表留言</a> </div> </div> <div class="comments container"> <ul class="list-group"> {{each comments}} <li class="list-group-item"> {{ $value.name }}说:{{ $value.message }} <span class="pull-right">{{ $value.dateTime }}</span> </li> {{/each}} </ul> </div></body></html>
post.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css"></head><body> <div class="header container"> <div class="page-header"> <h1><a href="/">首页</a> <small>发表评论</small></h1> </div> </div> <div class="comments container"> <!-- 以前表单是如何提交的? 表单中需要提交的表单控件元素必须具有 name 属性 表单提交分为: 1. 默认的提交行为 2. 表单异步提交 action 就是表单提交的地址,说白了就是请求的 url 地址 method 请求方法 get post --> <form action="/pinglun" method="get"> <div class="form-group"> <label for="input_name">你的大名</label> <input type="text" class="form-control" required minlength="2" maxlength="10" id="input_name" name="name" placeholder="请写入你的姓名"> </div> <div class="form-group"> <label for="textarea_message">留言内容</label> <textarea class="form-control" name="message" id="textarea_message" cols="30" rows="10" required minlength="5" maxlength="20"></textarea> </div> <button type="submit" class="btn btn-default">发表</button> </form> </div></body></html>
3. 补充
exports 和 module.exports补充
exports 和 module.exports 的区别
每个模块中都有一个 module 对象
module 对象中有一个 exports 对象
我们可以把需要导出的成员都挂载到 module.exports 接口对象中
也就是:
moudle.exports.xxx = xxx
的方式但是每次都
moudle.exports.xxx = xxx
很麻烦,点儿的太多了所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:
exports
exports === module.exports
结果为true
s所以对于:
moudle.exports.xxx = xxx
的方式 完全可以:expots.xxx = xxx
当一个模块需要导出单个成员的时候,这个时候必须使用:
module.exports = xxx
的方式不要使用
exports = xxx
不管用因为每个模块最终向外
return
的是module.exports
而
exports
只是module.exports
的一个引用所以即便你为
exports = xx
重新赋值,也不会影响module.exports
但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的之所以让大家明白这个道理,是希望可以更灵活的去用它
Node 是一个比肩 Java、PHP 的一个平台
JavaScript 既能写前端也能写服务端
Demo2--留言板(express)
1. Express(快速的)
作者:TJ
原生的http在某些方面表现不足以应对我们的开发需求,所以就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码高度统一。
在node中有很多web开发框架。这里主要学习express
http://expressjs.com/
,其中主要封装的是http。
1.1 起步
安装
cnpm install express
hello express
// 1. 引入expressvar express = require('express');// 2. 创建app,创建服务器应用程序,也就是原来的http.createServer();var app = express();// 3. app.get('/',function(req,res){ // 1 代码多,一般不用 // res.write('Hello'); // res.write('World'); // res.end() // 2 同样支持express原生 // res.end('hello world'); // 3 express推荐 res.send('hello world');})app.listen(3000,function(){ console.log('express app is runing...');})
1.2 修改代码后自动重启
这里使用第三方命令行工具**nodemon **自动重启,解决频繁修改代码,不断重启服务器问题
nodemon 是一个基于node.js开发的一个第三方命令行工具
全局安装
npm install --global nodemon
使用
全局安装完成即可直接使用
node xxx.js# nodemon使用,在命令行输入即可nodemon xxx.js nodemon --version //验证是否安装成功
1.3 基本路由
路由:
请求方法(get、post)
请求路径
请求处理函数(请求路径时,执行相应函数)
<form action="/post" method="post">
get:
//当你以get方法请求/的时候,执行对应的处理函数app.get('/',function(req,res){ res.send('hello world');})
post:
//当你以post方法请求/的时候,执行对应的处理函数app.post('/',function(req,res){ res.send('hello world');})
1.4 Express静态服务API
公开指定目录
只要通过这样做了,就可以通过/public/xx的方式来访问public目录中的所有资源
在Express中开放资源就是一个API的事
// app.use不仅仅是用来处理静态资源的,还可以做很多工作(body-parser的配置)// 当省略第一个参数的时候,则可以通过 省略 /public 的方式来访问// 这种方式的好处就是可以省略 /public/,直接写文件名访问public目录app.use(express.static('/public'));app.use(express.static('files'));// 有第一个参数,当以 /public开头的时候,去 ./public 目录中找找对应的资源// 这种方式更容易辨识,推荐这种方式// app.use('/public', express.static('./public'))// 必须是 /a/puiblic目录中的资源具体路径// app.use('/abc/d/', express.static('./public/'))app.use('/public',express.static('./public/'));app.use('/public',express.static('/public/'));
使用
// 引入expressvar express = require('express');// 创建appvar app = express();// 开放静态资源// 1.当以/public/开头的时候,去./public/目录中找对应资源// 访问:http://127.0.0.1:3000/public/login.htmlapp.use('/public/',express.static('./public/')); // 2.当省略第一个参数的时候,可以通过省略/public的方式来访问// 访问:http://127.0.0.1:3000/login.html// app.use(express.static('./public/')); // 3.访问:http://127.0.0.1:3000/a/login.html// a相当于public的别名// app.use('/a/',express.static('./public/')); // app.get('/',function(req,res){ res.end('hello world');});app.listen(3000,function(){ console.log('express app is runing...');});
1.5 Express中使用模板引擎
在Express中配置使用art-templete
模板引擎
art-template官方文档
在node中,有很多第三方模板引擎都可以使用,不是只有
art-template
比如还有ejs,jade(pug),handlebars,nunjucks
express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中
安装:
npm install --save art-templatenpm install --save express-art-template// 虽然外面这里不需要加载 art-template 但是也必须安装// 原因就在于 express-art-template 依赖了 art-templatenpm i --save art-template express-art-template
配置使用 art-template 模板引擎
第一个参数表示,当渲染以 .art 结尾的文件的时候(这里换成了 .html),使用 art-template 模板引擎
app.engine('html', require('express-art-template'));
使用:
app.get('/',function(req,res){ // express默认会去views目录找index.html res.render('index.html',{ title:'hello world' });})
如果希望修改默认的views
视图渲染存储目录,可以:
// 第一个参数views千万不要写错app.set('views',目录路径);
1.6 Express获取表单请求数据
获取get请求数据:
Express内置了一个api,可以直接通过req.query
来获取数据
// 通过requery方法获取用户输入的数据// req.query只能拿到get请求的数据 var comment = req.query;
获取post请求数据:
在Express中没有内置获取表单post请求体的api,这里我们需要使用一个第三方包body-parser
来获取数据。
安装:
npm install --save body-parser;
配置:
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
var express = require('express')// 引包var bodyParser = require('body-parser')var app = express()// 配置body-parser// 只要加入这个配置,则在req请求对象上会多出来一个属性:body// 也就是说可以直接通过req.body来获取表单post请求数据// parse application/x-www-form-urlencodedapp.use(bodyParser.urlencoded({ extended: false }))// parse application/jsonapp.use(bodyParser.json())
使用:
app.use(function (req, res) { res.setHeader('Content-Type', 'text/plain') res.write('you posted:\n') // 可以通过req.body来获取表单请求数据 res.end(JSON.stringify(req.body, null, 2))})
1.7 重定向
express已经封装好了,可直接使用
res.redirect('/') // res.statusCode = 302 // res.setHeader('Location', '/')
2. 重写留言板
案例目录结构
feedback node_modules public css img js views index.html post.html 404.html
案例代码
app.js
var express = require('express')var bodyParser = require('body-parser')var app = express()app.use('/public/', express.static('./public/'))// 配置使用 art-template 模板引擎// 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎app.engine('html', require('express-art-template'))// Express 为 Response 相应对象提供了一个方法:render// render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了// res.render('html模板名', {模板数据})// 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件// 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中// 如果想要修改默认的 views 目录,则可以// app.set('views', render函数的默认路径)// 配置 body-parser 中间件(插件,专门用来解析表单 POST 请求体)// parse application/x-www-form-urlencodedapp.use(bodyParser.urlencoded({ extended: false }))// parse application/jsonapp.use(bodyParser.json())var comments = [{ name: '张三', message: '今天天气不错!', dateTime: '2015-10-16' }, { name: '张三2', message: '今天天气不错!', dateTime: '2020-10-16' }, { name: '张三3', message: '今天天气不错!', dateTime: '2020-10-16' }, { name: '张三4', message: '今天天气不错!', dateTime: '2020-10-16' }, { name: '张三5', message: '今天天气不错!', dateTime: '2020-10-16' }]app.get('/', function (req, res) { res.render('index.html', { comments: comments })})app.get('/post', function (req, res) { res.render('post.html')})// 当以 POST 请求 /post 的时候,执行指定的处理函数// 这样的话我们就可以利用不同的请求方法让一个请求路径使用多次app.post('/post', function (req, res) { // 1. 获取表单 POST 请求体数据 // 2. 处理 // 3. 发送响应 // req.query 只能拿 get 请求参数 // console.log(req.query) var comment = req.body comment.dateTime = '2020-11-5 10:58:51' comments.unshift(comment) // res.send // res.redirect // 这些方法 Express 会自动结束响应 res.redirect('/') // res.statusCode = 302 // res.setHeader('Location', '/') })// app.get('/pinglun', function (req, res) {// var comment = req.query// comment.dateTime = '2017-11-5 10:58:51'// comments.unshift(comment)// res.redirect('/')// // res.statusCode = 302// // res.setHeader('Location', '/')// })app.listen(3000, function () { console.log('running...')})
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>留言本</title> <!-- 浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析, 当在解析的过程中,如果发现: link script img iframe video audio 等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求。 --> <!-- 注意:在服务端中,文件中的路径就不要去写相对路径了。 因为这个时候所有的资源都是通过 url 标识来获取的 我的服务器开放了 /public/ 目录 所以这里的请求路径都写成:/public/xxx / 在这里就是 url 根路径的意思。 浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上 不要再想文件路径了,把所有的路径都想象成 url 地址 --> <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css"></head><body> <div class="header container"> <div class="page-header"> <h1>Example page header <small>Subtext for header</small></h1> <a class="btn btn-success" href="/post">发表留言</a> </div> </div> <div class="comments container"> <ul class="list-group"> {{each comments}} <li class="list-group-item">{{ $value.name }}说:{{ $value.message }} <span class="pull-right">{{ $value.dateTime }}</span></li> {{/each}} </ul> </div></body></html>
post.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css"></head><body> <div class="header container"> <div class="page-header"> <h1><a href="/">首页</a> <small>发表评论</small></h1> </div> </div> <div class="comments container"> <!-- 以前表单是如何提交的? 表单中需要提交的表单控件元素必须具有 name 属性 表单提交分为: 1. 默认的提交行为 2. 表单异步提交 action 就是表单提交的地址,说白了就是请求的 url 地址 method 请求方法 get post --> <form action="/post" method="post"> <div class="form-group"> <label for="input_name">你的大名</label> <input type="text" class="form-control" required minlength="2" maxlength="10" id="input_name" name="name" placeholder="请写入你的姓名"> </div> <div class="form-group"> <label for="textarea_message">留言内容</label> <textarea class="form-control" name="message" id="textarea_message" cols="30" rows="10" required minlength="5" maxlength="20"></textarea> </div> <button type="submit" class="btn btn-default">发表</button> </form> </div></body></html>
Demo3--Crud
案例知识
起步
初始化
模板处理
路由设计
请求方法 | 请求路径 | get参数 | post参数 | 备注 |
---|---|---|---|---|
GET | /students | 渲染首页 | ||
GET | /students/new | 渲染添加学生页面 | ||
POST | /students/new | name,age,gender,hobbies | 处理添加学生请求 | |
GET | /students/edit | id | 渲染编辑页面 | |
POST | /students/edit | id,name,age,gender,hobbies | 处理编辑请求 | |
GET | /students/delete | id | 处理删除请求 |
提取路由模块
三种方式
直接通过exports和require导出导入app
//router.jsvar app = require('./app')//app.jsmodule.exports = app
导出函数
//router.jsmodule.exports = function(app){ app.get(...){...}}//app.jsvar router = requirt('./router')router(app)module.exports = app;
Express方式
router.js:
/** * router.js路由模块 * 职责: * 处理路由 * 根据不同的请求方法+请求路径设置具体的请求函数 * 模块职责要单一,我们划分模块的目的就是增强代码的可维护性,提升开发效率 */var fs = require('fs');// Express专门提供了一种更好的方式// 专门用来提供路由的var express = require('express');// 1 创建一个路由容器var router = express.Router();// 2 把路由都挂载到路由容器中router.get('/students', function(req, res) { // res.send('hello world'); // readFile的第二个参数是可选的,传入utf8就是告诉他把读取到的文件直接按照utf8编码,直接转成我们认识的字符 // 除了这样来转换,也可以通过data.toString()来转换 fs.readFile('./db.json', 'utf8', function(err, data) { if (err) { return res.status(500).send('Server error.') } // 读取到的文件数据是string类型的数据 // console.log(data); // 从文件中读取到的数据一定是字符串,所以一定要手动转换成对象 var students = JSON.parse(data).students; res.render('index.html', { // 读取文件数据 students:students }) })});router.get('/students/new',function(req,res){ res.render('new.html')});router.get('/students/edit',function(req,res){ });router.post('/students/edit',function(req,res){ });router.get('/students/delete',function(req,res){ });// 3 把router导出module.exports = router;
app.js:
var router = require('./router');// router(app);// 把路由容器挂载到app服务中// 挂载路由app.use(router);
设计操作数据的API文件模块
es6中的find和findIndex:
find接受一个方法作为参数,方法内部返回一个条件
find会便利所有的元素,执行你给定的带有条件返回值的函数
符合该条件的元素会作为find方法的返回值
如果遍历结束还没有符合该条件的元素,则返回undefined
/** * student.js * 数据操作文件模块 * 职责:操作文件中的数据,只处理数据,不关心业务 */var fs = require('fs'); /** * 获取所有学生列表 * return [] */exports.find = function(){ } /** * 获取添加保存学生 */exports.save = function(){ }/** * 更新学生 */exports.update = function(){ } /** * 删除学生 */exports.delete = function(){ }
CRUD案例
目录结构
[图片上传失败...(image-b28c1c-1603348970667)]
案例代码
app.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-urlencodedapp.use(bodyParser.urlencoded({ extended: false }))// parse application/jsonapp.use(bodyParser.json())// 把路由容器挂载到 app 服务中app.use(router)app.listen(3000, function () { console.log('running 3000...')})module.exports = app
db.json
{"students": [ {"id":1,"name":"张三三","gender":"0","age":"22","hobbies":"吃饭、睡觉、打豆豆"}, {"id":2,"name":"张三三","gender":"0","age":"22","hobbies":"吃饭、睡觉、打豆豆"}, {"id":3,"name":"张三三","gender":"0","age":"22","hobbies":"吃饭、睡觉、打豆豆"}, {"id":4,"name":"张三三","gender":"0","age":"22","hobbies":"吃饭、睡觉、打豆豆"} ]}
router.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
/** * 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) }) })}
edit.html
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="../../favicon.ico"> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/public/css/main.css" rel="stylesheet"></head><body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..."> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="/students">学生管理 <span class="sr-only">(current)</span></a></li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加学生</h2> <form action="/students/edit" method="post"> <!-- 用来放一些不希望被用户看见,但是需要被提交到服务端的数据 --> <input type="hidden" name="id" value="{{ student.id }}"> <div class="form-group"> <label for="">姓名</label> <input type="text" class="form-control" id="" name="name" required minlength="2" maxlength="10" value="{{ student.name }}"> </div> <div class="form-group"> <label for="">性别</label> <div> <label class="radio-inline"> <input type="radio" name="gender" id="" value="0" checked> 男 </label> <label class="radio-inline"> <input type="radio" name="gender" id="" value="1"> 女 </label> </div> </div> <div class="form-group"> <label for="">年龄</label> <input class="form-control" type="number" id="" name="age" value="{{ student.age }}" required min="1" max="150"> </div> <div class="form-group"> <label for="">爱好</label> <input class="form-control" type="text" id="" name="hobbies" value="{{ student.hobbies }}"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div></body></html>
Index.html
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="../../favicon.ico"> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/public/css/main.css" rel="stylesheet"></head><body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..."> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="/">学生管理 <span class="sr-only">(current)</span></a></li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">Dashboard</h1> <div class="row placeholders"> {{ each fruits }} <div class="col-xs-6 col-sm-3 placeholder"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail"> <h4>{{ $value }}</h4> <span class="text-muted">Something else</span> </div> {{ /each }} </div> <h2 class="sub-header">Section title</h2> <a class="btn btn-success" href="/students/new">添加学生</a> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>姓名</th> <th>性别</th> <th>年龄</th> <th>爱好</th> <th>操作</th> </tr> </thead> <tbody> {{ each students }} <tr> <td>{{ $value.id }}</td> <td>{{ $value.name }}</td> <td>{{ $value.gender }}</td> <td>{{ $value.age }}</td> <td>{{ $value.hobbies }}</td> <td> <a href="/students/edit?id={{ $value.id }}">编辑</a> <a href="/students/delete?id={{ $value.id }}">删除</a> </td> </tr> {{ /each }} </tbody> </table> </div> </div> </div> </div></body></html>
new.html
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="../../favicon.ico"> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/public/css/main.css" rel="stylesheet"></head><body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..."> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="/students">学生管理 <span class="sr-only">(current)</span></a></li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加学生</h2> <form action="/students/new" method="post"> <div class="form-group"> <label for="">姓名</label> <input type="text" class="form-control" id="" name="name" required minlength="2" maxlength="10"> </div> <div class="form-group"> <label for="">性别</label> <div> <label class="radio-inline"> <input type="radio" name="gender" id="" value="0" checked> 男 </label> <label class="radio-inline"> <input type="radio" name="gender" id="" value="1"> 女 </label> </div> </div> <div class="form-group"> <label for="">年龄</label> <input class="form-control" type="number" id="" name="age" required min="1" max="150"> </div> <div class="form-group"> <label for="">爱好</label> <input class="form-control" type="text" id="" name="hobbies"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div></body></html>
作者:Daeeman
链接:https://www.jianshu.com/p/9917a907d174
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。