node.js实现一个新闻列表页面的新增和查看

1、设计路由

当用户请求 / 或 /index 时,显示新闻列表 - get请求

当用户请求 /detail 时,显示新闻详情 - get请求

当用户请求 /submit 时,显示添加新闻页面 - get请求

当用户请求 /add 时,将用户提交的新闻保存到 data.json 文件中 - get请求

当用户请求 /add 时,将用户提交的新闻保存到 data.json 文件中 - post请求

当用户请求是以'/resources'开头,就认为是要请求静态资源 - get请求

其他的请求返回404

2、搭建代码结构

index.js  根据不同路由响应不同的HTML页面

// 当前项目(包)的入口文件

// 加载模块
const http = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime');

// 封装一个读取文件的函数render
function render(filename, res) {
  fs.readFile(filename, (err, data) => {
    if (err) {
      res.writeHead(404, 'not found', {'Contnet-Type': 'text/html; charset=utf-8'});
      res.end('404, Not Found');
      return;
    }
    res.setHeader('Contnet-Type', mime.getType(filename));
    res.end(data);
  })
}

// 创建服务
http.createServer((req, res) => {

  // 将用户请求的 url 和 method 转换为小写字母
  // toLowerCase() 方法用于把字符串转换为小写
  req.url = req.url.toLowerCase();
  req.method = req.method.toLowerCase();

  // 先根据用户请求的路径(路由),显示对应的HTML页面
  if (req.url === '/' || req.url === '/index' && req.method === 'get') {
    // 读取index.html并返回
    render(path.join(__dirname, 'views', 'index.html'), res);

  } else if (req.url === '/detail' && req.method === 'get') {
    // 读取detail.html并返回
    render(path.join(__dirname, 'views', 'detail.html'), res);

  } else if (req.url === '/submit' && req.method === 'get') {
    // 读取submit.html并返回
    render(path.join(__dirname, 'views', 'aubmit.html'), res);

  } else if (req.url === '/add' && req.method === 'get') {
    // get方法提交一条新闻
  } else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
  } else if (req.url.startsWith('/resources') && req.method === 'get') {
    // 如果用户请求是以'/resources'开头,且是get请求,就认为是要请求静态资源
    // 例如:/resource/images/index.jpg
    // A.startsWith(B) 用于判断字符串A是否以字符串B开头
    render(path.join(__dirname, req.url), res);
    
  } else {
    res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
    res.end('404, Not Found! 页面不存在');
  }
}).listen(9090, () => {
  console.log('http://localhost:9090');
});

3、封装一个读取文件的函数render,挂载到res上,通过res.render()调用

index.js

// 当前项目(包)的入口文件

// 加载模块
const http = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime');

// 创建服务
http.createServer((req, res) => {

  // 封装一个读取文件的函数render,挂载到res上,通过res.render()调用
  res.render = function (filename) {
    fs.readFile(filename, (err, data) => {
      if (err) {
        res.writeHead(404, 'not found', {'Contnet-Type': 'text/html; charset=utf-8'});
        res.end('404, Not Found');
        return;
      }
      res.setHeader('Contnet-Type', mime.getType(filename));
      res.end(data);
    })
  }

  // 将用户请求的 url 和 method 转换为小写字母
  // toLowerCase() 方法用于把字符串转换为小写
  req.url = req.url.toLowerCase();
  req.method = req.method.toLowerCase();

  if (req.url === '/' || req.url === '/index' && req.method === 'get') {
    // 读取index.html并返回
    res.render(path.join(__dirname, 'views', 'index.html'));

  } else if (req.url === '/detail' && req.method === 'get') {
    // 读取detail.html并返回
    res.render(path.join(__dirname, 'views', 'detail.html'));

  } else if (req.url === '/submit' && req.method === 'get') {
    // 读取submit.html并返回
    res.render(path.join(__dirname, 'views', 'aubmit.html'));

  } else if (req.url === '/add' && req.method === 'get') {
    // get方法提交一条新闻
  } else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
  } else if (req.url.startsWith('/resources') && req.method === 'get') {
    // 请求静态资源
    res.render(path.join(__dirname, req.url));

  } else {
    res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
    res.end('404, Not Found! 页面不存在');
  }
}).listen(9090, () => {
  console.log('http://localhost:9090');
});

4、submit.html

  <form method="get" action="/add">
    <table>
      <tr>
        <td>title:</td>
        <td><input type="text" name="title" value="" size="50"/></td>
      </tr>
      <tr>
        <td>text:</td>
        <td><textarea name="text" id="" cols="50" rows="4"></textarea></td>
      </tr>
      <tr>
        <td><input type="submit" value="提交"></td>
        <td><input type="reset" value="重置"></td>
      </tr>     
    </table>
  </form>

5、get方法提交一条新闻

思路:

(1)获取用户 get 提交过来的新闻数据

(2)把用户提交的新闻数据保存到 data.json 文件中

(3)跳转到新闻列表页面(服务器端通过设置http响应报文头实现浏览器重定向操作)

get 方法提交的表单数据,会被拼接在URL地址中,因此,通过req.url就可以直接获取到get 方法提交的表单数据,但是这样使用还需要自己去截取字符串,使用起来很不方便。

这就需要用到node.js的一个内置模块:url模块,将用户get提交的数据,解析成一个json对象

先加载url模块

const url = require('url');

因为get提交的表单数据会拼接在用户请求的路由中,因此用户请求的路由只是以'/add'开头的

 else if (req.url.startsWith('/add') && req.method === 'get') {
    // get方法提交一条新闻
    // 1、调用url.parse()方法解析用户请求的路由req.url
    var urlObj = url.parse(req.url, true);
    console.log(urlObj);
    // 获取用户get提交过来的新闻数据:urlObj.query,是一个对象
    // 2、把用户提交的新闻数据保存到list数组中
    var list = [];
    list.push(urlObj.query);
    // 3、把list数组中的数据写入到data.json文件中
    fs.writeFile(path.join(__dirname, 'data', 'data.json'), JSON.stringify(list), err => {
      if (err) throw err;
      console.log('ok');
      // 4、设置响应报文头,通过响应报文头告诉浏览器,执行一次页面跳转操作
      // 重定向,跳转到新闻列表页面
      res.statusCode = 302;
      res.statusMessage = 'Found';
      res.setHeader('Location', '/');
      res.end();
    });
  }

6、get方式提交新闻,解决新闻被覆盖掉的问题

在原来list数组的基础上添加新闻,而不是每次定义一个空的list数组,再添加新闻

 else if (req.url.startsWith('/add') && req.method === 'get') {
    // get方法提交一条新闻
    // 1、读取data.json中的数据,并将数据转换成数组
    fs.readFile(path.join(__dirname, 'data', 'data.json'), 'utf8', (err, data) => {
      // 第一次访问网站,data.json肯定是不存在的,所以有错误,但这种错误,我们不认为是网站错误,不抛出异常
      if(err && err.code !== 'ENOETN') throw err;
      // 如果读取到数据,就把读取到的数据转换成数组,如果没有读取到数据,就把'[]'转换成数组,然后赋值给list
      var list = JSON.parse(data || '[]');
      // 2、把用户提交的新闻数据保存到list数组中
      var urlObj = url.parse(req.url, true);
      list.push(urlObj.query);
      // 3、把list数组中的数据写入到data.json文件中
      fs.writeFile(path.join(__dirname, 'data', 'data.json'), JSON.stringify(list), err => {
        if (err) throw err;
        console.log('ok');
        // 4、设置响应报文头,重定向,跳转到新闻列表页面
        res.statusCode = 302;
        res.statusMessage = 'Found';
        res.setHeader('Location', '/');
        res.end();
      });
    })
  }

7、post方法提交一条新闻

思路:

(1)读取data.json文件中的数据

(2)将读取的数据转换为list数组

(3)获取用户post提交的新闻数据

(4)向list数组中push一条新闻

(5)把list数组中的数据重新写入到data.json文件中

(6)重定向到新闻列表页面

注意:submit.html中的method改为post,才能用post提交数据

因为post提交数据的时候,数据量可能比较大,所以会分多次进行提交,每次提交一部分数据;

因为每次浏览器提交一部分数据到服务器,就会触发一次data事件,因此,想要在服务器中获取到用户提交的所有数据,就必须监听request对象的data事件;

当request对象的end事件被触发的时候,就表示浏览器把所有的数据都提交到服务器了,因此也要监听request对象的end事件。

 else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
    // (1)读取data.json文件中的数据   
    fs.readFile(path.join(__dirname, 'data', 'data.json'), 'utf8', (err, data) => {
      if(err && err.code !== 'ENOETN') throw err;
      // (2)将读取的数据转换为list数组   
      var list = JSON.parse(data || '[]');
      // (3)获取用户post提交的新闻数据
      // 声明一个数组,用来保存用户每次提交过来的数据
      var array = [];
      // 监听request对象的data事件
      req.on('data', chunk => {
        // 此处的chunk参数,就是浏览器本次提交过来的一部分数据
        // chunk 的数据类型是Buffer (chunk就是一个Buffer对象)
        array.push(chunk);
      });
      // 监听request对象的end事件
      // 当end事件被触发的时候,表示所有数据已经提交完毕了
      req.on('end', () => {
        // 把 array 中的所有数据汇总起来,然后把array中的Buffer对象集合起来,转换成一个Buffer对象
        // Buffer.concat方法将一组Buffer对象合并为一个Buffer对象。
        var postBody = Buffer.concat(array);
        // 将获得的Buffer对象转换成一个字符串
        postBody = postBody.toString('utf8');
        // 将post请求的查询字符串,转换成一个JSON对象,用到node.js中的 querystring 模块
        // querystring.parse()将字符串解析成JSON对象
        postBody = querystring.parse(postBody);
        // (4)向list数组中push一条新闻
        list.push(postBody);
        // (5)把list数组中的数据重新写入到data.json文件中
        fs.writeFile(path.join(__dirname, 'data', 'data.json'), JSON.stringify(list), err => {
          if (err) throw err;
          console.log('post ok');
          // (6)重定向到新闻列表页面
          res.statusCode = 302;
          res.statusMessage = 'Found';
          res.setHeader('Location', '/');
          res.end();
        });
      });
    });
  }

8、封装读写data.json文件的方法

// 封装读取data.json文件的方法
function readData(callback) {
  fs.readFile(path.join(__dirname, 'data', 'data.json'), 'utf8', (err, data) => {
    if (err && err.code !== 'ENOENT') throw err;
    // 将读取到的字符串数据转换成JSON对象
    var list = JSON.parse(data || '[]');
    callback(list);
  })
}

// 封装写入data.json文件的方法
function writeData(data, callback) {
  fs.writeFile(path.join(__dirname, 'data', 'data.json'), data, err => {
    if(err) throw err;
    callback();
  })
}

那么,get方法提交一条新闻就可以简化为如下代码

    // get方法提交一条新闻
    readData(list => {
      var urlObj = url.parse(req.url, true);
      list.push(urlObj.query);
      writeData(JSON.stringify(list), () => {
        console.log('ok');
        // 设置响应报文头,重定向,跳转到新闻列表页面
        res.statusCode = 302;
        res.statusMessage = 'Found';
        res.setHeader('Location', '/');
        res.end();
      });
    });

9、封装获取用户post提交的数据的方法

// 封装获取用户post提交的数据的方法
function getPostData(req, callback) {
  var array = [];
  req.on('data', chunk => {
    array.push(chunk);
  });
  req.on('end', () => {
    var postData = Buffer.concat(array);
    postData = postData.toString('utf8');
    postData = querystring.parse(postData);
    callback(postData);
  });
}

那么,post方法提交一条新闻就可以简化为如下代码

    // post方法提交一条新闻
    readData(list => {
      getPostData(req, postData => {
        list.push(postData);
        writeData(JSON.stringify(list), () => {
          console.log('post ok');
          // 重定向到新闻列表页面
          res.statusCode = 302;
          res.statusMessage = 'Found';
          res.setHeader('Location', '/');
          res.end();
        });
      });
    });

10、保存新闻数据时增加一个id属性

在把新闻添加到list之前,先为新闻数据对象增加一个id属性,再将新闻数据push到list中,然后将list转换成字符串写入到data.json文件中

else if (req.url.startsWith('/add') && req.method === 'get') {
    // get方法提交一条新闻
    readData(list => {
      var urlObj = url.parse(req.url, true);
      // 保存新闻数据时增加一个id属性
      urlObj.query.id = list.length;
      list.push(urlObj.query);
      writeData(JSON.stringify(list), () => {
        console.log('ok');
        res.statusCode = 302;
        res.statusMessage = 'Found';
        res.setHeader('Location', '/');
        res.end();
      });
    });
  } else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
    readData(list => {
      getPostData(req, postData => {
        // 保存新闻数据时增加一个id属性
        postData.id = list.length;
        list.push(postData);
        writeData(JSON.stringify(list), () => {
          console.log('post ok');
          // 重定向到新闻列表页面
          res.statusCode = 302;
          res.statusMessage = 'Found';
          res.setHeader('Location', '/');
          res.end();
        });
      });
    });
  }

11、渲染index.html页面新闻列表数据

渲染index.html页面,需要用到模板数据,因此给render函数增加第二个参数,用来传递HTML页面中要使用的模板数据

传递模板数据,需要用到underscore.js中的template方法

  // 封装一个读取文件的函数render,挂载到res上,通过res.render()调用
  // 渲染index.html页面,需要用到模板数据,因此给render函数增加第二个参数,用来传递HTML页面中要使用的模板数据
  res.render = function (filename, telData) {
    fs.readFile(filename, (err, data) => {
      if (err) {
        res.writeHead(404, 'not found', {'Contnet-Type': 'text/html; charset=utf-8'});
        res.end('404, Not Found');
        return;
      }
      // 如果用户传递了模板数据,就使用underscore的template方法进行替换
      // 如果用户没有传递模板数据,就不进行替换
      if(telData) {
        var fn = _.template(data.toString('utf8'));
        data = fn(telData);
      }
      res.setHeader('Contnet-Type', mime.getType(filename));
      res.end(data);
    })
  }

实现首页显示新闻列表

  if (req.url === '/' || req.url === '/index' && req.method === 'get') {
    // 实现首页显示新闻列表
    // 1、获取到新闻列表数据
    readData(list => {
      // 2、在服务器端使用模板引擎,将list中的数据和index.html文件中的内容结合,渲染给客户端
      res.render(path.join(__dirname, 'views', 'index.html'), {list: list});
    });
  }

index.html

  <ul>
    <% for(var i = 0; i < list.length; i++) { %>
      <li>
        <span><%= list[i].id+1 %>.</span>
        <a href=""><%= list[i].title %></a>
      </li>
    <% } %>
  </ul>

12、显示新闻详情

思路:

(1)实现点击某条新闻跳转到该新闻详情页面

index.html  (将新闻id拼接到新闻详情页面的路由中)

  <ul>
    <% for(var i = 0; i < list.length; i++) { %>
      <li>
        <span><%= list[i].id+1 %>.</span>
        <a href="/detail?id=<%= list[i].id %>"><%= list[i].title %></a>
      </li>
    <% } %>
  </ul>

(2)获取当前用户请求的新闻的id

(3)读取data.json文件中的数据,根据id找到对应的新闻数据

(4)调用res.render()函数进行模板引擎的渲染

else if (req.url.startsWith('/detail') && req.method === 'get') {
    // 1、获取当前用户请求的新闻的id
    var urlObj = url.parse(req.url, true);
    console.log(urlObj.query.id);
    // 2、读取data.json文件中的数据,根据id找到对应的新闻数据
    readData(list => {
      var model = null;
      for(var i = 0; i < list.length; i++) {
        if(list[i].id.toString() === urlObj.query.id) {
          model = list[i];
          break;
        }
      }
      if(model) {
        // 3、调用res.render()函数进行模板引擎的渲染
        res.render(path.join(__dirname, 'views', 'detail.html'), {item: model});
      } else {
        res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
        res.end('404, Not Found! 页面不存在');
      }
    });
  }

detail.html

  <h1><%= item.title %></h1>
  <p><%= item.text %></p>

13、总结

index.js

// 当前项目(包)的入口文件

// 加载模块
const http = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime');
const url = require('url');
const querystring = require('querystring');
const _ = require('underscore');

// 封装读取data.json文件的方法
function readData(callback) {
  fs.readFile(path.join(__dirname, 'data', 'data.json'), 'utf8', (err, data) => {
    if (err && err.code !== 'ENOENT') throw err;
    // 将读取到的字符串数据转换成JSON对象
    var list = JSON.parse(data || '[]');
    // 通过调用回调函数,将读取到的数据list传递出去
    callback(list);
  })
}

// 封装写入data.json文件的方法
function writeData(data, callback) {
  fs.writeFile(path.join(__dirname, 'data', 'data.json'), data, err => {
    if(err) throw err;
    callback();
  })
}

// 封装获取用户post提交的数据的方法
function getPostData(req, callback) {
  // 声明一个数组,用来保存用户每次提交过来的数据
  var array = [];
  req.on('data', chunk => {
    // 此处的chunk参数,就是浏览器本次提交过来的一部分数据
    // chunk 的数据类型是Buffer (chunk就是一个Buffer对象)
    array.push(chunk);
  });
  req.on('end', () => {
    // 把 array 中的所有数据汇总起来,然后把array中的Buffer对象集合起来,转换成一个Buffer对象
    // Buffer.concat方法将一组Buffer对象合并为一个Buffer对象。
    var postData = Buffer.concat(array);
    // 将获得的Buffer对象转换成一个字符串
    postData = postData.toString('utf8');
    // 将post请求的查询字符串,转换成一个JSON对象,用到node.js中的 querystring 模块
    postData = querystring.parse(postData);
    callback(postData);
  });
}

// 创建服务
http.createServer((req, res) => {

  // 封装一个读取文件的函数render,挂载到res上,通过res.render()调用
  // 渲染index.html页面,需要用到模板数据,因此给render函数增加第二个参数,用来传递HTML页面中要使用的模板数据
  res.render = function (filename, telData) {
    fs.readFile(filename, (err, data) => {
      if (err) {
        res.writeHead(404, 'not found', {'Contnet-Type': 'text/html; charset=utf-8'});
        res.end('404, Not Found');
        return;
      }
      // 如果用户传递了模板数据,就使用underscore的template方法进行替换
      // 如果用户没有传递模板数据,就不进行替换
      if(telData) {
        var fn = _.template(data.toString('utf8'));
        data = fn(telData);
      }
      res.setHeader('Contnet-Type', mime.getType(filename));
      res.end(data);
    })
  }

  // 将用户请求的 url 和 method 转换为小写字母
  // toLowerCase() 方法用于把字符串转换为小写
  req.url = req.url.toLowerCase();
  req.method = req.method.toLowerCase();

  // 根据用户请求的路由,将对应的HTML页面显示出来
  if (req.url === '/' || req.url === '/index' && req.method === 'get') {
    // 实现首页显示新闻列表
    // 1、获取到新闻列表数据
    readData(list => {
      // 2、在服务器端使用模板引擎,将list中的数据和index.html文件中的内容结合,渲染给客户端
      res.render(path.join(__dirname, 'views', 'index.html'), {list: list});
    });

  } else if (req.url.startsWith('/detail') && req.method === 'get') {
    // 1、获取当前用户请求的新闻的id
    var urlObj = url.parse(req.url, true);
    console.log(urlObj.query.id);
    // 2、读取data.json文件中的数据,根据id找到对应的新闻数据
    readData(list => {
      var model = null;
      for(var i = 0; i < list.length; i++) {
        if(list[i].id.toString() === urlObj.query.id) {
          model = list[i];
          break;
        }
      }
      if(model) {
        // 3、调用res.render()函数进行模板引擎的渲染
        res.render(path.join(__dirname, 'views', 'detail.html'), {item: model});
      } else {
        res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
        res.end('404, Not Found! 页面不存在');
      }
    });

  } else if (req.url === '/submit' && req.method === 'get') {
    // 读取submit.html并返回
    res.render(path.join(__dirname, 'views', 'submit.html'));

  } else if (req.url.startsWith('/add') && req.method === 'get') {
    // get方法提交一条新闻
    readData(list => {
      var urlObj = url.parse(req.url, true);
      // 保存新闻数据时增加一个id属性
      urlObj.query.id = list.length;
      list.push(urlObj.query);
      writeData(JSON.stringify(list), () => {
        console.log('ok');
        res.statusCode = 302;
        res.statusMessage = 'Found';
        res.setHeader('Location', '/');
        res.end();
      });
    });

  } else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
    readData(list => {
      getPostData(req, postData => {
        // 保存新闻数据时增加一个id属性
        postData.id = list.length;
        list.push(postData);
        writeData(JSON.stringify(list), () => {
          console.log('post ok');
          // 重定向到新闻列表页面
          res.statusCode = 302;
          res.statusMessage = 'Found';
          res.setHeader('Location', '/');
          res.end();
        });
      });
    });

  } else if (req.url.startsWith('/resources') && req.method === 'get') {
    // 请求静态资源
    res.render(path.join(__dirname, req.url));

  } else {
    res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
    res.end('404, Not Found! 页面不存在');
  }

}).listen(9090, function () {
  console.log('http://localhost:9090');
});

缺点:包括启动服务、路由、扩展、数据操作等,所有代码基本集中在index.js文件中。因此,代码需要优化。

优化的话,可以分成不同的模块进行操作

node.js实现一个新闻列表页面的新增和查看(模块化)

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值