什麼是nodejs中間層?
就是前端---請求---> nodejs ----請求---->後端 ----響應--->nodejs--資料處理---響應---->前端。這麼一個流程,這個流程的好處就是當業務邏輯過多,或者業務需求在不斷變更的時候,前端不需要過多當去改變業務邏輯,與後端低耦合。前端即顯示,渲染。後端獲取和儲存資料。中間層處理資料結構,返回給前端可用可渲染的資料結構。
nodejs是起中間層的作用,即根據客戶端不同請求來做相應的處理或渲染頁面,處理時可以是把獲取的資料做簡單的處理交由底層java那邊做真正的資料持久化或資料更新,也可以是從底層獲取資料做簡單的處理返回給客戶端。
專案構建:
1.技術選型,我選擇了express框架開發中間層。
2.$ npm install express --save
下載express包.
3.$npm install express-generator -g
使用express生成器,生成一個完成的專案結構。
3.$npm install pm2 -g
下載pm2應用全域性.
4.$pm2 start express專案名稱/bin/www --watch
express專案,並監聽一些檔案的改變,一旦程式碼變動,自動重啟nodejs服務.
中介軟體建立:
- 在跟目錄建立一個檔案,來存放自己自己寫的中介軟體,這一個用來當作攔截器(node中間層核心);
6.開始編寫這個中介軟體:
interceptor.js
const log = require('../util/log');
//引用async庫。
const async = require('async'),
NO_TOKEN = [
//無需 校驗token的介面
'/api/login'
],
//介面列表
URL = require('../config');
/* 請求攔截器 */
const interceptor = (req, res ,next) =>{
res.result = {
code: 200,
data:""
}
console.log('請求攔截');
log.info("*********請求攔截**********");
//log.info("HOST:" + req.headers.host);
log.info("Authorization:" + req.headers['authorization']);
// res.send('**********repoert******');
try {
console.log('進入了');
const origina_url = req.originalUrl;
const method = req.method;
log.info(origina_url);
log.info(method);
let count = 0, //路由匹配計數器
matchUrl = {}; //儲存匹配中的路由規則
// if(NO_TOKEN.indexOf(origina_url) < 0){
// if(req.headers){
// if(!req.headers['authorization'] || req.headers['authorization'] == ''){
// log.warn('未帶上 token 請求介面');
// console.log('未帶上 token 請求介面');
// res.result['code'] = 403;
// // next();
// return;
// }
// }
// }
log.info(`攜帶了token`);
log.info(req.query);
// 匹配: url+ 請求方式 + 引數
for(let i = 0; i < URL.length; i++){
let el = URL[i],
url = el['url'],
type = el['type'];
log.info(url);
log.info(method);
log.info(type);
log.info(url.test(origina_url));
if(url.test(origina_url) && type.test(method)){
log.info('進入了匹配')
// 對post 請求, 做引數校驗
if(method !== 'GET'){
let init_params = el['params'],
init_params_length = init_params.length,
request_params = Object.keys(req.body),
request_params_length = request_params.length;
//請求引數長度 與 公共引數 長度不一致, 則跳出本次迴圈
if(request_params_length !== init_params_length){
log.warn("請求引數長度 與 公共引數長度 不一致 :" + request_params_length + " --- " + init_params_length);
console.log("請求引數長度 與 公共引數長度 不一致 :" + request_params_length + " --- " + init_params_length);
continue;
}
//公共引數長度 非0 才進行 請求引數 與 公共引數 的對比
//公共引數長度 為0,表示 post 請求不需要引數,當作 get 請求處理
if(init_params_length !== 0){
let params_count = 0; // 引數匹配計數器
for(let sub_i = 0; sub_i < request_params_length; sub_i++){
let item = request_params[sub_i];
//請求引數 與 公共引數中的某一個匹配了, 則計數器++;
if(init_params.indexOf(item) != -1){
params_count++;
}
}
// 引數匹配計數器 與 公共引數長度不一致, 則跳出本次迴圈
if(params_count !== init_params_length){
log.info('========== 請求引數 與 公共引數不匹配 ========== ');
log.info('請求引數:' + request_params);
log.info('公共引數:' + init_params);
log.info(' ========== 請求引數 與 公共引數不匹配 ========== ');
continue;
}
}
}
count++;
matchUrl = el;
break;
} else {
console.log('沒進入匹配');
log.info('沒進入匹配');
}
}
// 匹配項 大於 0, 則進行轉發
if(count > 0){
log.info('存在匹配專案:' + matchUrl.name);
// 獲取請求引數
let request_params = JSON.stringify(method == "GET" ? req.query : req.body);
//請求後臺介面服務
let target = matchUrl['target'],
target_length = target.length;
//async 庫控制流程
if(target_length > 0){
let async_func = [
function (cb){
let _params = JSON.parse(request_params);
log.info('傳入引數:'+ request_params);
//判斷是否有傳入 token ,如果有傳則 設定 token;
req.headers['authorization'] ?
function(){
log.info('傳入的 token:' + req.headers['authorization']);
_params['authorization'] = req.headers['authorization'];
}() : function(){
log.info('未傳入 token')
}();
cb(null, _params);
}
].concat(target);
log.info(target);
async_func = async_func.concat([
function(data,cb){
log.info('返回前端資料:'+ JSON.stringify(data));
cb(null, data);
}
]);
//waterfall: 按順序依次執行一組函式。每個函式產生的值,都將傳給下一個。
async.waterfall(async_func, (err, resultend)=>{
if(err){
res.result['code'] = err['httpStatus'];
next();
}
res.result['code'] = 200;
res.result['data'] = resultend;
next();
})
}
}
}
catch(err){
log.error('======== NODE 伺服器異常 =========');
log.error(err);
let errCode = err.statusCode;
switch(errCode){
case 400:
res.result['code'] = 400;
break;
default:
res.result['code'] = 500;
}
next();
}
}
module.exports = interceptor;
複製程式碼
介面列表劃分:
- 劃分介面列表:
index.js;
const $http = require('../util/superagent');
const ENV = require('../config/url.config')
let login = [];
login = [
{
//前端介面描述
name: '登入',
//前端介面請求地址
url: /^\/list\/login$/i,
//請求方式
type: /^post$/i,
//公共引數
params: ['memberCellphone', 'loginPwd'],
//呼叫後端介面列表
target:[
function(initParam, cb){
let headers = {
"Content-Type":"application/json;",
}
$http('http://webapi.test.sxmaps.com/' + 'sapp/sapp-api/notfilter/member/login', 'post',initParam,{},function(err,res){
if(err){
cb(err,{});
return false;
}
// 這裡可以對資料進行處 理, 拼接成給前端的資料
let res_data = res;
cb(null, JSON.parse(res_data.text));
})
}
]
}
]
module.exports = login;
複製程式碼
8.$http檔案的封裝. 本文使用superagent庫.
superagent是一個輕量級、靈活的、易讀的、低學習曲線的客戶端請求代理模組,使用在NodeJS環境中。 superagent: visionmedia.github.io/superagent/
//
const superagent = require('superagent');
const NODE_ENV = process.env.NODE_ENV;
// module.exports = superagent
/**
* 請求轉發 公共方法
* $http(url, method, data, callback)
* @param {String} url 請求地址
* @param {String} method 請求方式
* @param {Obejct} data 請求引數,無參傳入空物件
* @param {Object} headers 請求頭設定,無需設定傳入空物件
* @param {Function} callback 請求回撥函式
*/
const $http = (url,method,data,headers,callback)=>{
const URL = url;
if(!url || !method || !data || !callback){
throw new Error(' ========== 配置檔案請求引數配置異常 ========== ');
return false;
}
let _http = superagent;
log.info('轉發後臺地址:' + url);
log.info('轉發後臺引數:' + JSON.stringify(data));
switch (method) {
case 'get':
_http = _http.get(url).query(data);
break;
case 'put':
_http = _http.put(url).send(data);
break;
case 'delete':
_http = _http.del(url).send(data);
break;
default:
_http = _http.post(url).send(data);
break;
}
_http.end((err,res)=>{
callback(null, res);
})
}
module.exports = $http;
複製程式碼
中介軟體的載入:
9.重新改造 app.js 讓app使用攔截器, app.use(interceptor);
- 對攔截器返回的資料,進行處理-->>
app.use(interceptor); app.use((req,res,next)=>{ } 複製程式碼
例如:
app.use(interceptor);
app.use((req,res,next)=>{
let code = res.result['code'];
/* 禁止快取 */
res.setHeader('Cache-Control', 'no-store');
switch(code){
case 403:
res.send({
code:403,
msg:'請攜帶token',
});
default:
res.send(res.result['data']);
break;
}
let t = new Date() - req.time,
conntectStr = req.method + ' ' + req.url + ' ' + t + ' ms';
log4js.info(conntectStr);
next();
});
複製程式碼
環境變數設定:
11.環境變數的設定: 在根目錄的 package.json種設定命令列的執行命令,執行不同的指令碼:
在建立一個讀取url地址的檔案;
pm2的執行環境變數,新建一個pm2檔案, 設定讀取配置。
裡面的watch是pm2 監聽的檔案,log_file是 輸出到 log日誌。instances是執行的執行緒數量
專案啟動:
- 可以放上伺服器, 如果不想埠是 nodejs的3000的話, 可以在nginx對域名進行 對映代理。
中介軟體的引數校驗,還待完善,沒有很合理。