在上一篇文章我们通过使用koa搭建了一个简单可访问的后台服务,这篇文章我们接着上一篇进行叙述,通过接口配置的方式实现接口转发,同时封装一些常用的上传、下载等通用的方法。
1、接口配置实现接口转发
(1)要实现接口配置我们需要进行创建一个js文件进行接口的配置,创建一个apiConfig文件,其下创建一个requestType.js文件,代码格式如下:(只用进行method方法、uri真实地址的配置,对象名称是调用接口的唯一标识)
// 常量定义
const { MUSIC_REQUEST_URL } = require('../constant');
module.exports = {
SEARCH_MUSIC: {
method: 'GET',
uri: `${MUSIC_REQUEST_URL}/search`,
},
SEARCH_HIGHQUALITY_MUSIC: {
method: 'GET',
uri: `${MUSIC_REQUEST_URL}/top/playlist/highquality`
},
}
(2)封装通用、统一的接口处理方法,通过传递_m_参数来识别真实接口配置。在utils创建一个api_generator.js文件,代码如下:
const colors = require('colors-console')
const { request } = require('./http');
const requestType = require('../apiConfig/requestType');
const { generateToken, parseToken } = require('./token_utils');
// 参数处理
const payloadProcess = (ctx) => {
const _m_ = ctx.query._m_ || 'none';
const reqC = requestType[_m_];
if(reqC) {
reqC.data = ctx.request.body || {};
reqC.params = ctx.request.query || {}
};
return reqC || {};
}
const apiDecorator = async (ctx, config = {}) => {
const temp = payloadProcess(ctx);
config.url = config.url || temp.uri;
config.method = (config.method || temp.method || 'get').toLocaleLowerCase();
config.data = config.data || temp.data;
config.params = config.params || temp.params;
if(!config.url) {
return ctx.body = {
errCode: 404,
errMsg: '找不到当前配置地址,请确认后重试'
}
}
const name = config.name || config.params._m_;
delete config.params._m_;
// 生成token 用于鉴权
const user = parseToken(ctx.cookies.get("token"));
const token = generateToken(user);
config.headers = config.headers || { Authorization: token };
// 处理地址{}参数替换为(params > data)参数
config.url = config.url.replace(/\{([a-zA-Z0-9]+)\}/g, (match, name) => {
return config.params[name] || config.data[name] || '';
});
console.log(colors('green', `/******************** ${name} send to server ***********************/`));
console.log(JSON.stringify(config));
try {
const result = await request(config);
console.log(colors('green', `/******************** ${name} send to server result ***********************/`));
console.log(JSON.stringify(result.data));
ctx.body = result.data;
} catch (error) {
console.log(colors('red', `/******************** ${name} send to server error ***********************/`));
console.log(JSON.stringify(error));
ctx.body = { code: '-1', msg: error.message };
}
}
module.exports = apiDecorator
(3)创建通用接口路由/api/process,引入apiDecorator进行统一接口处理,代码如下:
const apiDecorator = require('../utils/api_generator');
// 通用接口转发地址
router.all('/api/process', async function(ctx) {
await apiDecorator(ctx)
})
// 接口异常访问
router.all('/api/(.*)', async function (ctx) {
ctx.body = { code : -1, msg: '没有找到对应的接口' }
});
(4)调用方式:
// _m_传递的requestType配置的对象名
http://localhost:7001/api/process?_m_=SEARCH_MUSIC&keywords=海阔天空
http://localhost:7001/api/process?_m_=SEARCH_HIGHQUALITY_MUSIC
2、用于通用的封装接口不能够兼容文件类型的处理,需要封装新的接口进行文件导出、文件导入
(1)创建controller文件夹,创建common.js文件进行文件导入导出的处理,静态文件模板可以通过放在public/templates下进行下载,代码如下:
const fs = require("fs");
const path = require("path");
const superagent = require("superagent");
const requestType = require('../apiConfig');
const { generateToken, parseToken } = require('../utils/token_utils');
module.exports = {
// 模板下载
async templateDownload(ctx) {
const { ident, filename = "模板.xlsx" } = ctx.request.query;
if(!ident) {
ctx.body = { code: 1, msg: "参数有误,请检查或联系开发者" };
return;
}
try {
const file = fs.readFileSync(path.resolve(__dirname, `../public/templates/${ident}`));
ctx.type = "application/octet-stream";
ctx.set({"content-disposition": `form-data; name="attachment"; filename="${encodeURIComponent(filename)}"`});
ctx.body = file;
} catch (error) {
ctx.body = { code: 1, msg: "参数有误,没有找到对应文件" };
}
},
// 导出文件
async downloadProcess(ctx) {
const query = ctx.query || {};
const { uri, method } = requestType[query["_m_"]] || {};
if (!method || !uri) {
ctx.body = { code: 1, msg: "参数有误,请检查或联系开发者" };
return;
}
const type = method.toLocaleLowerCase() || "get";
const payload = ctx.request.body.data || {};
const params = ctx.request.body.params || {};
delete query["_m_"];
// 生成token
const user = parseToken(ctx.cookies.get("token"));
const token = generateToken(user);
const ret = await new Promise((resolve, reject) => {
superagent[type](uri)
.query(params)
.send(payload)
.set("Authorization", token)
.timeout({ response: 18000000, deadline: 18000000 })
.accept("application/octet-stream")
.parse((res, callback) => {
res.setEncoding("binary");
res.data = "";
res.on("data", chunk => res.data += chunk);
res.on("end", function () {
if ( res.headers["content-type"] && res.headers["content-type"].indexOf("application/json") > -1) {
const buf = new Buffer(res.data, "binary");
callback(JSON.parse(buf.toString()));
} else {
callback(null, new Buffer(res.data, "binary"));
}
});
})
.buffer()
.end(function (err, result) {
if (err) reject(null);
resolve(result);
});
});
if (ret) {
ctx.body = ret.body;
} else {
ctx.body = { code: 1, msg: "参数有误,请检查或联系开发者" };
}
},
// 导入
async importProcess(ctx) {
const queries = ctx.query;
const { fields, files, params } = ctx.request.body;
const { file } = files || {};
const { uri } = requestType[queries["_m_"]] || {};
if (!file || !uri) {
ctx.body = { status: 1, msg: "参数有误,请检查或联系开发者" };
return;
}
// 生成token
const user = parseToken(ctx.cookies.get("token"));
const token = generateToken(user);
const formData = {
file: {
value: fs.createReadStream(file.path),
options: {
filename: file.name,
contentType: file.type,
},
},
}
for (let key in fields) {
formData[key] = fields[key];
}
const ret = await new Promise((resolve, reject) => {
superagent
.post(uri)
.set('Content-Type', 'multipart/form-data')
.set("Authorization", token)
.send(formData)
.query(params)
.end(function (err, result) {
if (err) reject(null);
resolve(result);
});
});
ctx.body = JSON.parse(ret);
},
};
(2)controller文件下创建index.js文件,编写读取controller下文件方法,方便对所有模块方法进行导出。
const fs = require("fs");
// 读取所有的模块方法
fs.readdirSync(__dirname).forEach((item) => {
const filePath = `${__dirname}/${item}`;
if (fs.existsSync(filePath) && item !== "index.js") {
const module = require(filePath);
const moduleName = item.substring(0, item.lastIndexOf("."));
exports[moduleName] = module;
}
});
(3)创建路由,routers文件下添加静态模板下载、文件导入导出的路由接口
const Controler = require('../controller');
// 文件下载
router.all('/api/common/template/download', Controler['common'].templateDownload);
// 文件导入
router.all('/api/common/import', Controler['common'].importProcess);
// 文件导出
router.all('/api/common/download', Controler['common'].downloadProcess);
(4)调用方式
静态文件下载:文件存放在public/templates下,通过接口进行访问 http://localhost:7001/api/common/template/download?ident=文件名
文件导入:requestType配置接口,访问接口http://localhost:7001/api/common/import?m=配置名称 通过formData进行传参数(file)
文件导出:requestType配置接口,访问接口http://localhost:7001/api/common/download?m=配置名称 进行文件导出
3、其他用法补充
(1)如果需要快速添加转发接口,只需要在requestType文件进行接口配置,通过http://localhost:7001/api/process?m=配置名称方式就可调用
(2)如果需要添加自定义接口,则只需在controller创建模块文件并写入自己想要的方法进行导出,进入routers文件进行路由配置,例如:router.all(url, Controler[‘xxx’].xxx)
(3)访问静态资源文件地址http://localhost:7001/static/xxx
这里就是基于koa搭建node接口转发服务的全部代码内容了,都只是写了一个大概,很多细化的东西还没有进行处理,希望封装对你有帮助,如有好的建议可以提出来一起学习分享。