【Node.js】簡單實現Hacker News部分頁面後臺(js原生->模塊化->Express改寫)

本篇文章意在用node.js簡單實現 https://news.ycombinator.com 的部分服務端功能,且nodejs的基本語法、html頁面模板部分再也不作描述。尚有不足,望指正,謝謝!

<實現效果>
index
details
submithtml

1、原生js實現

<目錄結構>
圖片描述node

一、引入模塊express

//加載須要的核心模塊
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const querystring = require('querystring');

//加載須要的第三方模塊,加載前需先在命令行經過npm下載,如npm i mime --save
const mime = require('mime');
const template = require('art-template');

二、功能實現apache

//建立服務器
const server = http.createServer();

//該全局常量存放數據加載地址(此例中用data.json存儲數據,以後會寫從mongoDB中獲取數據)
const data_path = path.join(__dirname, './data.json');

//處理請求
server.on('request', (req, res) => {
    // --------[首頁模塊]---------
    
    //設置路由
    if (req.url == '/' || req.url == '/index') {
        //調用封裝的從讀取數據功能函數
        readData(data_path,  data=> {
            let file_path = path.join(__dirname, './views/index.html');
            let obj = { list: data };
            let html = template(file_path, obj);
            res.end(html);
        });
        
        //--------如下注釋部分代碼爲未封裝讀取數據功能前的代碼(下文再也不重複此部分代碼)---------
        // 讀取數據,將數據渲染到頁面結構,再將結果響應給客戶端
        // fs.readFile(data_path, 'utf8', (err, data) => {
        //     data = JSON.parse(data);
       
        //     let file_path = path.join(__dirname, './views/index.html');
        //     let obj = { list: data };
        
        //     將數據渲染到模板中並返回響應:調用封裝的response對象的成員函數
               res.render(file_path,obj);
        // });
    }

    // -------------[詳情頁]--------------
    else if (req.url.startsWith('/details')) {
        readData(data_path, data => {
            // 根據url中的id找到數據中的數據項,渲染到頁面,將渲染結果響應給瀏覽器
            let id = url.parse(req.url, true).query.id;
            let item = data.find((v, i) => {
                //【這裏用'=='而不用'==='的緣由:v.id是number型,id是string型】
                return v.id == id;
            });
            let file_path = path.join(__dirname, './views/details.html');
            
            res.render(file_path,item);
        });
    }

    // -------------------[提交頁:需針對不一樣請求方式進行處理]----------------
    else if (req.url == '/submit') {
        fs.readFile(path.join(__dirname, './views/submit.html'), (err, data) => {
            if (err) {
                return console.log('404 not found');
            }
            res.end(data);
        });
    }
    
    // 【若爲get請求】提交後跳轉到數據添加過渡頁,需處理數據並重定向到首頁
    else if (req.url.startsWith('/add') && req.method == 'GET') {
        // 解析url,將查詢數據轉換爲對象,並添加id屬性
        let item = url.parse(req.url, true).query;
        item.id = +new Date(); //確保id的惟一
        
        // 獲取json數據,將新對象追加到轉爲數組的數據中
        readData(data_path, data => {
            data.unshift(item);
            
            //調用封裝的將數據覆蓋寫入data.json的功能函數
            writeData(data,() => {
                // 重定向並返回響應:調用封裝的response對象的成員函數
                res.redirect();
            });
        });
    }
    
    // 【若爲post請求】特別地,上傳參數以數據流(二進制)的形式傳輸
    else if (req.url.startsWith('/add') && req.method == 'POST') {
        // 監聽查詢數據上傳,並將數據流拼接爲字符串
        let str = '';
        req.on('data', (chunk) => {
            str += chunk;
        });
        // 監聽到數據上傳完成,將查詢字符串解析爲查詢對象
        req.on('end', () => {
            let item = querystring.parse(str);
            // 給此條對象添加id屬性,並追加到數據中,再將數據從新覆蓋寫入本地
            item.id = +new Date();
            readData(data_path, data => {
                data.unshift(item);
                writeData(data,() => {
                    res.redirect();
                });
            })
        });
    }

    // ------------[靜態資源:可模擬apache,開放靜態資源目錄下的全部資源]------------
    else if (req.url.startsWith('/assets')) {
        let rs_path = path.join(__dirname, req.url);
        fs.readFile(path.join(__dirname, req.url), (err, data) => {
            if (err) {
                return console.log('404 not found');
            }
            res.setHeader('content-type', mime.getType(rs_path));
            res.end(data);
        });
    }
    
    // -----------非路由請求頁面處理-----------
    else {
        res.end('404');
    }
    
    //-------[將重定向和渲染功能(都返回響應)封裝爲response的成員函數]----------
    res.redirect=function(){
        res.statusCode = 302;
        res.setHeader('location', '/index');
        res.end();
    }
    
    res.render=function(file_path,obj){
        let html = template(file_path, obj);
            res.end(html);
    }
});
//-------------功能函數封裝--------------

// 功能:從data.json中讀取數據並轉換數據格式
function readData(data_path, callback) {
    fs.readFile(data_path, 'utf8', (err, data) => {
        if (err) {
            return console.log(err);
        }
        data = JSON.parse(data || '[]');
        callback(data);
    });
}

// 功能:轉換數據格式並將數據覆蓋寫入data.json
function writeData(data, callback) {
    data = JSON.stringify(data, null, 2);
    fs.writeFile(data_path, data, err => {
        if (err) {
            return console.log(err);
        }
        // 若寫入成功則執行回調
        callback();
    });
}

三、開啓服務器npm

server.listen(8001, () => {
    console.log('server is started ,pls visit http://localhost:8001');
})

2、模塊化

在原結構基礎上,將主功能頁面hn.js進行自定義模塊劃分,即json

  • hn.js的功能:建立服務器、總體結構;
  • router.js的功能:路由規則結構;
  • handle.js的功能:每一個路由規則的具體處理;
  • extend.js的功能:封裝爲request對象的成員函數。

另外,針對每一個模塊,要考慮其依賴模塊、依賴參數以及依賴的其餘函數。數組

<hn.js>瀏覽器

// 內置模塊
const http = require('http');

// 引入自定義模塊
const router = require('./router.js');
const extend = require('./extend.js');

const server = http.createServer();

server.on('request', (req, res) => {
     //調用res拓展模塊功能
     extend(res);
     //調用路由規則模塊功能
     router(req,res);  
});

server.listen(8001, () => {
    console.log('server is started ,pls visit http://localhost:8001');
})

<router.js>服務器

//依賴模塊
const handle=require('./handle.js');
//導出功能函數
module.exports = function (req, res) {
    // 首頁
    if (req.url == '/' || req.url == '/index') {
        handle.index(req,res);
    }
    // 詳情頁
    else if (req.url.startsWith('/details')) {
        handle.details(req,res);
    }
    // 提交頁
    else if (req.url == '/submit') {
       handle.submit(req,res);
    }
    // 若添加操做爲get請求
    else if (req.url.startsWith('/add') && req.method == 'GET') {
        handle.addGet(req,res);
    }
    // 若添加操做爲post請求
    else if (req.url.startsWith('/add') && req.method == 'POST') {
       handle.addPost(req,res);
    }
    // 靜態資源
    else if (req.url.startsWith('/assets')) {
        handle.assets(req,res);
    }
    // 非指定頁面處理
    else {
        handle.others(req,res);
    }
}

<handle.js>app

// 一、依賴模塊
const url = require('url');
const querystring = require('querystring');
const mime = require('mime');
const path = require('path');
const fs = require('fs');

// 二、依賴參數:req,res,data_path
const data_path = path.join(__dirname, './data.json');

//導出功能函數
module.exports.index = function (req, res) {
    readData(data_path, function (data) {
        let file_path = path.join(__dirname, './views/index.html');
        let obj = { list: data };
        res.render(file_path, obj);
    });
}

module.exports.details = function (req, res) {
    readData(data_path, function (data) {
        let id = url.parse(req.url, true).query.id;
        let item = data.find((v, i) => {
            return v.id == id;
        });
        let file_path = path.join(__dirname, './views/details.html');
        res.render(file_path, item);
    });
}

module.exports.submit = function (req, res) {
    fs.readFile(path.join(__dirname, './views/submit.html'), (err, data) => {
        if (err) {
            return console.log('404 not found');
        }
        res.end(data);
    });
}

module.exports.addGet = function (req, res) {
    let item = url.parse(req.url, true).query;
    item.id = +new Date();
    readData(data_path, function (data) {
        data.unshift(item);
        writeData(data, function () {
            res.redirect();
        });
    });
}

module.exports.addPost = function (req, res) {
    let str = '';
    req.on('data', (chunk) => {
        str += chunk;
    });
    req.on('end', () => {
        let item = querystring.parse(str);
        item.id = +new Date();
        readData(data_path, function (data) {
            data.unshift(item);
            writeData(data, function () {
                res.redirect();
            });
        })
    });
}

module.exports.assets = function (req,res) {
    let rs_path = path.join(__dirname, req.url);
    fs.readFile(path.join(__dirname, req.url), (err, data) => {
        if (err)  return console.log('404 not found');
        res.setHeader('content-type', mime.getType(rs_path));
        res.end(data);
    });
}

module.exports.others = function (req,res) {
    res.end('404 not found');
}

// 三、依賴的其餘函數
function readData(data_path, callback) {
    fs.readFile(data_path, 'utf8', (err, data) => {
        if (err) {
            return console.log(err);
        }
        data = JSON.parse(data || '[]');
        callback(data);
    });
}

function writeData(data, callback) {
    data = JSON.stringify(data, null, 2);
    fs.writeFile(data_path, data, (err) => {
        if (err) {
            return console.log(err);
        }
        callback();
    });
}

<extend.js>

//依賴模塊
const template = require('art-template');
//導出功能
module.exports = function (res) {
    res.redirect = function () {
        res.statusCode = 302;
        res.setHeader('location', '/index');
        res.end();
    }
    
    res.render = function (file_path, obj) {
        let html = template(file_path, obj);
        res.end(html);
    }
}

3、express實現

<npm安裝>

npm init
npm i express --save
npm i art-template express-art-template --save
npm i body-parser --save

<main.js>

// 引入express框架
const express = require('express');

// 實例化express對象
const app = express();

// 託管靜態資源
app.use('/assets',express.static('./assets'));

// 配置模板引擎
app.engine('html',require('express-art-template'));

// 引入並配置body-parser
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended:true}));

// 引入外置路由模塊,並掛載到app上
const router = require('./router.js');
app.use(router);

//啓動服務器
app.listen(8001,()=>{
    console.log('server is started' );
});

<router.js>

//引入依賴模塊
const express = require('express');
const path = require('path');
const fs = require('fs');

// 實例化外置路由
const router = express.Router();

// 註冊路由

//-----------------首頁------------------
router.get('/', (req, res) => {
    res.redirect('/index');
});
router.get('/index', (req, res) => {
    read_data(function (data) {
        // 渲染文件並將渲染結果響應給瀏覽器
        res.render('index.html', { list: data });
    })
});

// --------------詳情頁-------------------
router.get('/details', (req, res) => {
    // 獲取查詢對象
    let obj = req.query;
    
    read_data(function (data) {
        // 經過id找到全部數據中的匹配條目
        let item = data.find(v => {
            console.log(typeof v.id, typeof obj.id);
            return v.id == obj.id;
        });
        res.render('details.html', item);
    });
});

// -------------提交頁-----------------
router.all('/submit', (req, res) => {
    //將文件響應給瀏覽器
    res.sendFile(path.join(__dirname, './views/submit.html'))
});

// 若經過get請求添加數據
router.get('/add', (req, res) => {
    let item = req.query;
    item.id = +new Date();

    read_data(function (data) {
        data.unshift(item);

        write_data(data, function () {
            res.redirect('/index');
        });
    })
});

// 若經過post請求添加數據
router.post('/add', (req, res) => {
    let item = req.body;
    item.id = +new Date();
    
    read_data(function (data) {
        data.unshift(item);
        
        write_data(data, function () {
            res.redirect('/index');
        });
    })
});

// 導出router對象
module.exports = router;

// ------封裝讀取數據和寫入數據的功能函數--------
const data_path = path.join(__dirname, './data.json');

function read_data(callback) {
    fs.readFile(data_path, 'utf8', (err, data) => {
        if (err) {
            return console.log('404 not found');
        }
        data = JSON.parse(data || '[]');
        callback(data);
    });
}

function write_data(data, callback) {
    data = JSON.stringify(data, null, 2);
    fs.writeFile(data_path, data, err => {
        if (err) {
            return console.log('fail to write');
        }
        callback();
    });
}
相關文章
相關標籤/搜索