阅读 83

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,可以用来动态获取当前文件的绝对路径(包含文件名)

  • __dirnamefilename是不受执行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 表单提交重定向

如何通过服务器让客户端重定向?

  1. 状态码设置为 302 临时重定向

statusCode

  1. 在响应头中通过 Location 告诉客户端往哪儿重定向

setHeader
    如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求, 所以你就能看到客户端自动跳转了

res.statusCode = 302res.setHeader('Location', '/')res.end()

2. 留言板案例

a. 案例步骤

  1. / index.html

  2. 开放 public 目录中的静态资源,当请求 /public/xxx 的时候,读取响应 public 目录中的具体资源

  3. /post   post.html

  4. /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 结果为  trues

    • 所以对于: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/editid
渲染编辑页面
POST/students/edit
id,name,age,gender,hobbies处理编辑请求
GET/students/deleteid
处理删除请求

提取路由模块

三种方式

  • 直接通过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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐