接口通过Node转发在实际开发中存在很多好处,比如接口安全性、解决跨域、统一数据规范等,接下来简单分享给大家如何搭建一个Node服务进行接口转发,下图为项目结构截图
具体过程如下:
1、创建一个项目并进行项目初始化
// 创建一个项目
sudo mkdir -v [project name]
// 初始化项目
npm init -y
// package.json的scripts添加代码如下(需全局安装 nodemon)
"scripts": {
"dev": "nodemon index.js"
},
2、安装依赖koa、koa-body、koa-jwt、koa-logger、koa-router、koa-views、swig
yarn add koa // 主要核心依赖
yarn add koa-body // 处理请求体,用于文件上传配置
yarn add koa-jwt // token生成
yarn add koa-logger // 控制台打印日志
yarn add koa-router // 路由控制
yarn add koa-view swig // 视图控制
3、根目录创建一个index.js文件,使用koa创建一个服务,引入控koa-logger和koa-body中间件,跑起一个简单的服务。
const path = require('path');
const Koa = require('koa’);
const { koaBody } = require('koa-body');
const logger = require('koa-logger');
const app = new Koa();
const PORT = ‘7001’;
// 控制台日志
app.use(logger());
// 文件上传需要,更多配置参考koa-body文档
app.use(koaBody({multipart: true}));
app.listen(PORT);
console.log('Server listen in:' + PORT);
4、添加路由,创建一个视图
(1)创建一个routers文件夹,其下创建一个index.js文件,代码如下:
const router = require('koa-router')();
// 页面访问
router.get('/(.*)', async function (ctx) {
await ctx.render('index', {
title: '测试项目',
env: process.env.CONFIG_ENV
});
});
module.exports = router;
(2)根目录index文件引入路由配置文件和视图配置
const views = require('koa-views');
const router = require('./routers');
// 模板
app.use(views(path.join(__dirname, './template'), { map: {html: 'swig'}}));
//路由
app.use(router.routes());
(3)创建template文件夹,创建一个index.html文件,作为主页被访问
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{title}}</title>
</head>
<body>
Hello World!
</div>
</body>
</html>
4)运行yarn dev 发现可以正常访问一个页面
5、添加静态资源访问路径
(1)创建一个public目录,其目录下创建一个static目录存放静态文件
(2)根目录index.js引入静态资源中间件,其中有两种方式,其一是直接使用koa-static,其二是自己写一个中间件。
// 可以通过引入koa-static实现,但是只能配置单个目录,不方便
const koaStatic = require("koa-static");
app.use(koaStatic(__dirname + “/public/static"));
/*———————-----------------------------——————————*/
// 采用自己书写中间件方式实现资源访问,过程如下:
//(1)创建一个middleware目录,创建一个static文件
//(2)安装koa-send用于处理静态文件访问,minimatch用于对路径匹配,具体代码如下:
const send = require('koa-send');
const minimatch = require('minimatch');
const CACHE_STATIC_PATH = {};
const matchFilePath = (match, path) => {
if(CACHE_STATIC_PATH[path]) return path;
const type = typeof match;
switch(type) {
case 'string':
if(minimatch(path, match)) {
CACHE_STATIC_PATH[path] = 1;
return path;
}
break;
case 'object':
for(let i = 0, length = match.length; i < length; i++) {
if(minimatch(path, match[i])){
CACHE_STATIC_PATH[path] = 1;
return path;
}
}
break;
default: break;
}
return false;
}
module.exports = function static(match, options) {
return async (ctx, next) => {
const curUrl = ctx.path;
let fileUrl = matchFilePath(match, curUrl);
if (!fileUrl) {
return await next();
}
await send(ctx, curUrl, { root: options.dir, maxage: options.maxage });
}
}
//(3)引入中间件(可以同时配置多个路径为静态文件被访问)
const middles = require('./middleware');
app.use(middles.static([ '/static/js/*', '/static/css/*'],{
dir: __dirname + '/public',
maxage: 60 * 60 * 1000
}))
6、添加jwt鉴权校验
(1)根目录index.js添加代码如下:(代码放在前面,第一个处理的中间件,用于对异常的捕获)
// 异常捕获处理
app.use((ctx, next) => {
return next().then(() => {
const token = ctx.cookies.get('token');
if (token) {
ctx.cookies.set('token', token, { httpOnly: true, overwrite: true, maxAge: 12 * 3600 * 1000 });
}
}).catch(err => {
// 验证
if(err.status === 401) {
ctx.status = 401;
ctx.body = '没有权限,请登'
} else {
throw err;
}
})
})
(2)根目录index.js添加jwt校验中间件
// 密钥,这个参数对加密解密很重要,需要保证其安全性
const JWT_SECRET='shared-secret';
// jwt鉴权
app.use(jwt({
secret: JWT_SECRET,
cookie: 'token',
getToken: (ctx) => ctx.request.query.token, // 如果地址栏有token,优先取地址栏的token进行校验
}).unless({ // 不需要走校验的路径
path: [/^\/public/, /\/api/login/]
}));
(3)创建一个utils文件,其下创建一个token_utils.js文件,主要进行token生成和解密,代码如下:
const fs = require('fs');
const jwt = require('jsonwebtoken');
// 需要和中间件校验的jwt_secret保持一致,可以用一个constant进行维护访问
const JWT_SECRET='shared-secret';
module.exports = {
// 解析token
parseToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (e) {
return false;
}
},
// 生成token
generateToken(user, exp) {
if (!user) return null;
!exp && (exp = Math.floor(Date.now() / 1000) + (30 * 60));// 默认30min
return jwt.sign({
exp,
sub: user,
iat: Math.floor(Date.now() / 1000) - 30, // 向前追溯30s
}, JWT_SECRET)
}
}
(4)运行项目进行访问,发现报错“没有权限,请登陆”
(5)routers创建登陆接口路由,用于向浏览器cookie写入token,具体代码如下:
const { generateToken } = require('../utils/token_utils');
router.get('/api/login', async function(ctx){
const { name: username } = ctx.request.query || {};
if(!username) {
ctx.body = { code: -1, msg: '请传入name参数'}
return;
}
const token = generateToken(username);
ctx.cookies.set('token', token, { httpOnly: true, overwrite: true, maxAge: 12 * 3600 * 1000 });
ctx.redirect('/');
})
})
(6)访问地址进行登陆http://localhost:7001/api/login?name=zhuqianyang(这里只是测试,实际登陆业务需更详细),发现跳转主页,登陆成功
测试项目github:https://github.com/reai99/create-node-server
这样就搭建了一个基本的Node服务了,这篇文章先分享到这里,接下来我会详细讲述如何封装接口形成配置进行接口转发。