基于koa搭建node接口转发服务(二)

文章介绍了如何使用Koa框架实现接口配置转发,包括创建js文件配置接口、封装通用接口处理方法以及创建接口路由。此外,还详细讲解了文件下载、导入和导出的处理,涉及静态文件模板、superagent库和文件流操作。最后,提到了快速添加转发接口和自定义接口的方法。
摘要由CSDN通过智能技术生成

在上一篇文章我们通过使用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接口转发服务的全部代码内容了,都只是写了一个大概,很多细化的东西还没有进行处理,希望封装对你有帮助,如有好的建议可以提出来一起学习分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值