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

1、模块化思路

模块一(服务模块):负责启动服务

模块二(扩展模块):负责扩展 req 和 res 对象,封装一些好用的API

模块三(路由模块):负责路由判断

模块四(业务模块):负责处理具体路由的业务代码

模块五(数据操作模块):负责进行数据库操作

模块六(配置模块):负责各个模块中用到的配置信息

封装一个模块的步骤:

(1)思考,该模块要封装什么代码,实现什么功能?

(2)思考,该模块是否用到了外部的数据?如果有,是否需要通过参数将这些数据传递过来

(3)当前模块要对外暴露什么,即module.exports的值

2、封装扩展模块

context.js

// 扩展模块
// 该模块负责扩展 req 和 res 对象
const url = require('url');
const fs = require('fs');
const mime = require('mime');
const _ = require('underscore');

// 当前模块向外暴露一个函数,通过这个函数,在index.js中,将req 和 res 对象传递到这个模块中
module.exports = function (req, res) {
  
  // toLowerCase() 方法用于将字符串转换为小写
  var urlObj = url.parse(req.url.toLowerCase(), true);

  // 1、为req增加query属性,保存用户get提交过来的数据,是一个JSON对象
  req.query = urlObj.query;

  // 2、为req增加pathname属性,保存用户请求的路由
  req.pathname = urlObj.pathname;

  // 3、将用户请求的method 转换为小写字母
  req.method = req.method.toLowerCase();

  // 4、为res增加一个render函数
  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);
    })
  }
};

3、配置模块

config.js

const path = require('path');
module.exports = {
  "port": 9090,  // 端口号
  "dataPath": path.join(__dirname, 'data', 'data.json'),
  "viewPath": path.join(__dirname, 'views')
};

4、方法模块:封装一些重复利用的方法

method.js

// 封装一些方法
const fs = require('fs');
const path = require('path');
const querystring = require('querystring');
const config = require('./config.js');

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

// 封装写入data.json文件的方法
module.exports.writeData = function (data, callback) {
  fs.writeFile(config.dataPath, data, err => {
    if(err) throw err;
    callback();
  })
}

// 封装获取用户post提交的数据的方法
module.exports.getPostData = function (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);
  });
}

5、业务模块

handler.js

// 业务模块:负责处理具体路由的业务代码
const method = require('./method.js');
const path = require('path');
const config = require('./config.js');

// 新闻列表页面
module.exports.index = function (req, res) {
  // 1、获取到新闻列表数据
  method.readData(list => {
    // 2、在服务器端使用模板引擎,将list中的数据和index.html文件中的内容结合,渲染给客户端
    res.render(path.join(config.viewPath, 'index.html'), {list: list});
  });
};

// 新增新闻页面
module.exports.submit = function (req, res) {
  res.render(path.join(config.viewPath, 'submit.html'));
};

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

// get方法提交一条新闻
module.exports.addGet = function (req, res) {
  method.readData(list => {
    // 保存新闻数据时增加一个id属性
    req.query.id = list.length;
    list.push(req.query);
    method.writeData(JSON.stringify(list), () => {
      console.log('ok');
      res.statusCode = 302;
      res.statusMessage = 'Found';
      res.setHeader('Location', '/');
      res.end();
    });
  });
};

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

// 处理静态资源请求
module.exports.static = function (req, res) {
  res.render(path.join(__dirname, req.url));
};

// 处理404错误请求
module.exports.handleErrors = function (req, res) {
  res.writeHead(404, 'not found', {'Content-Type': 'text/html; charset=utf-8'});
  res.end('404, Not Found! 页面不存在');
};

6、路由模块

router.js

// 路由模块
const handler = require('./handler.js');

module.exports = function (req, res) {
  // 根据用户请求的路由,将对应的HTML页面显示出来
  if (req.url === '/' || req.url === '/index' && req.method === 'get') {
    // 新闻列表页面
    handler.index(req, res);
  } else if (req.pathname === '/detail' && req.method === 'get') {
    // 新闻详情页面
    handler.detail(req, res);
  } else if (req.url === '/submit' && req.method === 'get') {
    // 新增新闻页面
    handler.submit(req, res);
  } else if (req.pathname === '/add' && req.method === 'get') {
    // get方法提交一条新闻
    handler.addGet(req, res);
  } else if (req.url === '/add' && req.method === 'post') {
    // post方法提交一条新闻
    handler.addPost(req, res);
  } else if (req.url.startsWith('/resources') && req.method === 'get') {
    // 处理静态资源请求
    handler.static(req, res);
  } else {    
    // 处理404错误请求
    handler.handleErrors(req, res);
  }
};

7、服务模块

index.js

// 服务模块:负责启动服务
// 当前项目(包)的入口文件

// 加载模块
const http = require('http');
const context = require('./context.js');
const router = require('./router.js');
const config = require('./config.js');

// 创建服务
http.createServer((req, res) => {
  // 调用context.js模块的函数,并将req和res对象传递过去
  context(req,res);
  // 调用router.js模块的函数,并将req和res对象传递过去
  router(req,res);
}).listen(config.port, () => {
  console.log('http://localhost:' + config.port);
});

8、三个HTML页面代码

index.html

  <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>

detail.html

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

submit.html

  <form method="post" 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>

9、模块化后代码执行顺序

启动服务:

index.js模块中先调用context.js模块,然后再调用router.js模块;

在router.js模块中,会调用handler.js模块;

在handler.js模块中,会调用method.js模块;

在很多模块都会用到config.js模块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值