Node.js基于Express框架搭建服务器最全详解

/*模板渲染引擎 可用*/ /*物理路径*/ 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');
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。 /*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}`); }); //hbsHelper.js var helper = { hello: function (user) { return '我是helper使用示例 hello ' + user; } } module.exports = helper //ad.html <div style="width: 100%; height: 20px; text-align: center; font-size: 12px; line-height: 20px; color: #413F43; "> 这是一段广告</div> <h3> {{hello 'liangxl'}}</h3> //error.html <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>{{title}}</title> </head>
<body> <h1 style="color: #64B587">错误</h1> <h2>{{info}}</h2> </body>
</html> //layout.html <!DOCTYPE html> <html>
<head> <meta charset="UTF-8"> <title>{{title}}</title> </head>
<body> <h1>我是模板元素</h1>
<header style="width: 100%; height: 80px; text-align: center; line-height: 80px; color: #ffffff;"> 这是头部</header>
{{>ad}} {{{body}}} <footer style="position: fixed; bottom:0; width: 100%; height: 80px; text-align: center; line-height: 80px; color: #ffffff;"> 这是底部</footer>
</body>
</html> //layoutdemo.html <h1>hello {{user}}</h1> //render.html <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>{{title}}</title> </head>
<body> <h1 style="color: #64B587">人物介绍</h1>
{{#each personInfoList}} <h2>昵称:{{this.name}}</h2> <h2>年龄:{{this.age}}</h2> <hr> {{/each}}
</body>
</html>

更多精彩