nodejs+express服务搭建最全详解
Posted bashanshushui
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nodejs+express服务搭建最全详解相关的知识,希望对你有一定的参考价值。
var express = require("express");
var bodyParser = require("body-parser");
var sm2 = require(‘sm.js‘).sm2;
var app = express();
var path = require(‘path‘);
var exphbs = require(‘express-handlebars‘);
var hbsHelper = require(‘./util/hbsHelper‘);
/*静态资源路径 静态资源不会进入路由匹配 因此没有权限验证*/
app.use(express.static(path.join(__dirname, ‘public‘)));
/*模板渲染引擎 可用*/
/*物理路径*/
app.set(‘views‘, path.join(__dirname, ‘views‘));
/*exphbs参数中 还有layouts布局(框架,制定好页面整体框架,其它views页面body填充)属性可用.默认模板路径为views/layout.html*/
/*目前推崇前后端分离,exphbs不做深入研究.毕竟流行java服务+vue前端等架构*/
app.engine(‘html‘, exphbs({
/*片段试图路径 使用如 layout.html中 {{>ad}}*/
partialsDir: ‘views/partials‘,
layoutsDir: ‘views‘,
defaultLayout: ‘layout‘,
extname: ‘.html‘,
/*帮助函数 可以直接在html中使用 例如ad.html中 {{hello ‘liangxl‘}}*/
helpers:hbsHelper
}));
app.set(‘view engine‘, ‘html‘);
/*session模块*/
var session = require(‘express-session‘);
var FileStore = require(‘session-file-store‘)(session);
/*用户模块 系统存在的用户 未在该users内的用户均将登陆失败 真实情况应该是从mysql数据库取用户信息*/
var users = require(‘./users.js‘).items;
/*支持两种常用参数解析方式*/
/*强烈建议使用json格式传参*/
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
/*返回的对象是一个键值对,当extended为false的时候,键值对中的值就为‘String‘或‘Array‘形式,为true的时候,则可为任何数据类型。*/
extended: false
}));
app.use(session({
/*session名称*/
name: ‘nodeexpresstoken‘,
/*用来对session id相关的cookie进行签名*/
secret: ‘ktctav‘,
/*是否自动保存未初始化的会话(非代码编写保存session,不建议),建议false*/
saveUninitialized: false,
/*本地存储session(文本文件,也可以选择其他store,比如redis、mysql.默认内存) 非内存存储,即使服务挂了,再重启,只要session没过期,请求依然生效.当然请求的路由中,应是未失效的session*/
/*创建的文件不会消失,即使过期*/
store: new FileStore({
/*文件路径 默认./sessions 当前路径/sessions*/
//path: ‘d:/nodeexpresstoken‘
}),
/*true 不论值是否改变,一旦有携带有效cookie的任何请求均重新设置session,对应的超时时间更新为最大值.false 只是在值改变时才重新设置,如果值未改变,那么超时时间未变*/
resave: true,
cookie: {
/*有效期,单位是毫秒*/
maxAge: 60 * 1000
}
}));
var hostName = ‘0.0.0.0‘;
var port = 7878;
/*命令行参数处理*/
const argv = process.argv
if (argv.length > 2) {
for (let i = 2; i < argv.length; i++) {
if (argv[i] == "-hostname") {
hostName = argv[++i];
} else if (argv[i] == "-port") {
port = argv[++i];
}
}
}
/*.all精确匹配请求路由 这里可以对全局所有请求路由进行权限控制、跨域允许等前期操作*/
app.all(‘*‘, function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By", ‘ 3.2.1‘);
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
/*所有请求 判断是否登录*/
app.use(function (req, res, next) {
/*非登录验证*/
next();
/*登录验证*/
// if (!req.session.token) {
// if (req.url == "/login") {
// /*如果请求的地址是登录则通过,进行下一个请求*/
// next();
// } else {
// /*res.redirect() 会重新发送路由请求*/
// res.send(‘login fail‘);
// }
// } else if (req.session.token) {
// /*接口权限验证 TODO*/
// next();
// }
});
/*全球唯一识别码*/
var guid = function () {
return ‘xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx‘.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == ‘x‘ ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
/*真实使用 应从mysql数据库中获取用户信息 进行验证*/
var findUser = function (name, password) {
return users.find(function (item) {
//注意 如果是int与string类型比较 那么需要转换为一样的string类型进行比较
return item.username.toString() === name.toString() && item.password.toString() === password.toString();
});
};
/*layoutTest示例*/
app.get(‘/layoutDemo‘, function (req, res) {
res.setHeader(‘Content-Type‘, ‘text/html‘);
res.render(‘layoutdemo‘, {
/*当layout:false时,仅仅渲染layoutdemo.html.*/
/*当layout:‘layout‘时,渲染组合layout.html+layoutdemo.html.*/
/*当无layout时,使用默认模板layout.html,如果layout.html不存在,那么抛出异常*/
layout: ‘layout‘,
title: "layoutTest示例",
user: "liangxl"
});
});
/*renderTest示例*/
app.get(‘/renderDemo‘, function (req, res) {
res.setHeader(‘Content-Type‘, ‘text/html‘);
res.render(‘render‘, {
/*如果无layout属性,那么会渲染组合layout.html+layoutdemo.html,因为渲染组合layout.html、render.html均是完整的html文件,因此效果可能因合并而紊乱,因此layoutdemo.html强烈要求为layoutdemo.html样式,否则请置layout: false*/
layout: false,
title: "首页",
personInfoList: [{
name: "liangxl(超人)",
age: 20
}, {
name: "jiangwen(美琴)",
age: 15
}]
});
});
/*sendFileTest示例*/
app.get(‘/sendFileDemo‘, function (req, res) {
/*不同类型的文件需要设置对应类型的类型,否则网页显示不出预期效果.如果是html页面,sendFile无渲染效果*/
/*如果这里文件未找到 那么将会抛出全局异常,注意全局异常的Content-Type需要作修改*/
res.setHeader(‘Content-Type‘, ‘image/png‘);
res.sendFile(`${__dirname}/public/324202.jpg`);
});
/*登录*/
app.post(‘/login‘, function (req, res) {
if (req.body) {
let user = {
‘username‘: req.body.username,
‘password‘: req.body.password,
‘token‘: guid()
};
/*合法用户 否则undefine*/
let legaluser = findUser(user.username, user.password);
if (legaluser) {
/*创建并存储session*/
req.session.regenerate(function (err) {
if (err) {
res.send(‘login fail‘);
}
req.session.token = user.token;
/*可render到html页面*/
res.send(‘login success‘);
});
} else {
/*可render到html页面*/
res.send(‘login fail‘);
}
/*可在这里进行用户合法性验证 如果不合法 则不允许后面的操作*/
/*登录成功,可以返回一个token给前端,且服务保存token与与user关系(可以用redis,mysql数据库).此后前端请求均需带上该token*/
/*服务端在redis,mysql数据库存储中查找该token是否存在,如果不存在或过期,那么请求失败,说明是伪造的token或token超时*/
/*需要注意的存储token与user的概念不一样,前面session存储的只是user.token(可以存在内存、数据库等,自动超时),而token与user关系需要自行存储与超时维护*/
//req.session.token = user.token;
/*登录成功*/
//res.send(‘login success‘);
} else {
/*可render到html页面*/
res.send(‘login fail‘);
}
});
/*登出*/
app.post(‘/logout‘, function (req, res) {
req.session.destroy(function (err) {
if (err) {
res.send(‘logout fail‘);
return;
}
//req.session.token = null;
res.clearCookie(‘nodeexpresstoken‘);
res.send(‘logout success‘);
});
});
/*仅匹配/hello的请求路由,类型可以是 post get put等.*/
app.all(‘/hello‘, function (req, res, next) {
next();
});
/*中间件,可以匹配/hello或以/hello或/hello/..开头的请求路由.主要进行请求数据前期操作*/
app.use(‘/hello‘, function (req, res, next) {
/*.all .use如果没有res.send或异常抛出 那么一定需要next(),否则请求者会一直等待反馈,直到超时*/
next();
});
/*如果.all或.use位于该get路由之后,那么这里如果该路由匹配成功,那么就不会再进行后面的.all或.use,除非其函数内有意next()*/
/*因此想要.all或.use一定要位于想进行处理路由的前面*/
/*所有的请求的路由均是从上到下依次匹配.一旦post get put匹配到,则结束下面的匹配,除非被匹配函数内有意next()*/
/*可以在req, res后加next,然后就可以进行next()调用,继续向下执行*/
app.get(‘/hello‘, function (req, res, next) {
/*获取get传参 req.query参数json体*/
console.log(req.query);
res.send(‘hello world‘);
/*send后不能再调next(),否则抛出异常*/
//next();
});
/*主页*/
app.get(‘/‘, function (req, res) {
res.send(`SM2 CertificateServer is running on http://${hostName}:${port}`);
});
/*签名 传入参数{"publicKey":"***","privateKey":"***","data":"***"}*/
app.post("/signature", function (req, res) {
let result = {
code: 0,
signature: null,
error: null
};
try {
console.log("publicKey", req.body.publicKey);
console.log("privateKey", req.body.privateKey);
console.log("data", req.body.data);
let key = new sm2.SM2KeyPair(req.body.publicKey, req.body.privateKey);
let signature = key.sign(req.body.data);
console.log("signature.r", signature.r)
console.log("signature.s", signature.s)
result.signature = signature;
console.info(‘signature success‘);
res.send(result);
} catch (e) {
result.code = -1;
result.error = "signature fail:" + e;
console.error(result.error);
res.send(result);
}
});
/*验签 传入参数{"publicKey":"***","signature.r":"***","signature.s":"***","data":"***"}*/
app.post("/verify", function (req, res) {
let result = {
code: 0,
error: null
};
try {
console.log("publicKey", req.body.publicKey);
console.log("signature_r", req.body.signature_r);
console.log("signature_s", req.body.signature_s);
console.log("data", req.body.data);
let key = new sm2.SM2KeyPair(req.body.publicKey);
if (key.verify(req.body.data, req.body.signature_r, req.body.signature_s)) {
result.code = 0;
console.info(‘verify pass‘);
} else {
result.code = -1;
result.error = "verify fail"
console.error(‘verify fail‘);
}
res.send(result);
} catch (e) {
result.code = -1;
result.error = "verify fail:" + e;
console.error(result.error);
res.send(result);
}
});
/*404 注意一定是放置为最后 前面的均无法匹配则会进入这里*/
/*其实.use是中间件的概念 当一个请求到达时,会依次从前面进行匹配,如果前面没有匹配成功,*/
/* 那么会进入这里进行中间件匹配,因为这里匹配路径是默认的/,因此所有的前面未匹配成功的路由均会进入这里*/
/* 如果是app.use(‘/user‘, function (req, res, next) 那么仅仅匹配 /user或/user/..的路由 且路由函数如app.post("/user", function(req, res) 必须位于*/
/* 中间件定义的后面.否则找到了匹配的路由,那么中间件自然进入该中间件匹配.*/
app.use(function (req, res, next) {
res.setHeader(‘Content-Type‘, ‘text/html‘);
res.render(‘error‘, {
layout: false,
title: "404",
info: "sorry can‘t find path:" + req.path
});
// console.error("sorry can‘t find path:" + req.path);
// res.status(404).send("sorry can‘t find path:" + req.path);
});
/*通用错误 由catch的next(e)进入或者由未catch处理的异常由系统抛出后进入*/
app.use(function (err, req, res, next) {
res.setHeader(‘Content-Type‘, ‘text/html‘);
res.render(‘error‘, {
layout: false,
title: "system error",
info: ‘system error:‘ + err
});
// console.error(‘system error:‘ + err);
// res.status(500).send(‘system error:‘ + err);
});
/*启动监听*/
app.listen(port, hostName, function () {
console.log(`SM2 CertificateServer is running on http://${hostName}:${port}`);
});
以上是关于nodejs+express服务搭建最全详解的主要内容,如果未能解决你的问题,请参考以下文章
vue+elementUI框架,nodejs+router搭建本地服务器,数据库用mongoose,axios请求碰到的一些问题