koa框架构建完整项目

284 阅读3分钟

项目实践中,有后端全用nodejs写后端的,更广泛的是nodej是作为中间层。

使用nodejs处理某些简单业务场景做预处理,nodejs可以做路由处理,更多是nodejs调用其它后端处理请求并返回结果。

比如,nodejs层处理登录session处理,连接redis缓存等,后端网关可以做进一步预处理,比如鉴权

web端 -> nginx负载均衡 -> 多个nodejs程序 -> 后端网关 -> 后端微服务

本文后续主要说明下面一些:

nodejs层需要做路由,分层controller,静态资源处理,对请求做压缩,实现路由和controller结合,nodejs部署,合理规划文件命令和模块规范

项目源码

koa 本身提供中间件扩展,最重要的是使用中间件

npm i @koa/router koa-bodyparser koa-static koa-combine-routers koa-compress axios  jsonfile  qs pm2 nodemon
  • @koa/router 路由

  • koa-bodyparser 请求体信息参数拿到,原生是http.on('data')事件获取

  • koa-static 静态文件处理

  • koa-compress 请求体压缩

  • koa-combine-routers 合并多个路由

  • axios 可以用在客户端或服务器端发送http请求

  • jsonfile 读取解析json文件

  • qs 字符串查询

  • nodemon 开发调试自动重启node进程

  • pm2 生产环境node进程管理

curl 测试

curl -X POST -H "Content-Type:application/json;charset=utf-8" -d '{"name":"hello","password":"123"}' http://localhost:3000/api/login

基本

实例化Koa后,use里面操作response返回值

const Koa = require("koa");
const app = new Koa();

app.use(async (ctx) => {
  // ctx.body = "hello";
  // ctx.resquest 和 ctx.response 不是http中request和response,二是封装后增强的
  // ctx.req 和 ctx.res 才是原生http中request和response
  ctx.response.type = "text/html";
  ctx.response.body = "<html><h1>hello world</h1></html>"; // 完全等价与 ctx.body
});

app.listen(3000, () => {
  console.log("listen port 3000");
});

洋葱模型

根据use的注册函数,一层一层执行,next是执行下一个函数

app.use(async (ctx, next) => {
  console.log("first start");
  await next();
  console.log("first end");
});

app.use(async (ctx) => {
  console.log("second start");
  console.log("second end");
});

输出结果

first start second start second end first end

目录定义

app 主目录编写主文件代码,包括引入路由器,压缩器 common
config 路由配置的基本信息 config/server 将node作为服务器 config/client 将node作为客户端 controller 路由控制 service 处理实际业务逻辑 dao 数据库处理 dist 单页面html等内容 scripts 启动脚本 projectConfig.json 项目配置文件 router 路由和controller映射

接口封装

封装GET\POST\PUT\DELETE

const axios = require("axios");
const projectConfig = require("../util/projectConfigResolver");

const hostBaseUrl = projectConfig.hostBaseUrl;

exports.doHttpGetRequest = function (ctx, requestUrl, params) {
 return doHttpRequest(ctx, requestUrl, params, "GET");
};

exports.doHttpPostRequest = function (ctx, requestUrl, params) {
 return doHttpRequest(ctx, requestUrl, params, "POST");
};

exports.doHttpPutRequest = function (ctx, requestUrl, params) {
 return doHttpRequest(ctx, requestUrl, params, "PUT");
};

exports.doHttpDeleteRequest = function (ctx, requestUrl, params) {
 return doHttpRequest(ctx, requestUrl, params, "DELETE");
};

function doHttpRequest(ctx, requestUrl, params, method) {
 if ("GET" == method.toUpperCase()) {
   return axios({
     baseURL: hostBaseUrl,
     url: requestUrl,
     method: "GET",
     params: params,
   });
 } else if (["PUT", "POST", "DELETE"].includes(method.toUpperCase())) {
   return axios({
     baseURL: hostBaseUrl,
     url: requestUrl,
     method: "GET",
     data: params,
   });
 }
}

用户Controller

根据url路由,编写对应的controller,用@koa/router绑定对应关系

const qs = require("qs");
const baseHttpClient = require("../common/baseHttpClient");
const userRequestUrlMappingResolver = require("../config/client/userRequestUrlMappingResolver");

/**
{
   result:{
       code:0,
       description: 'success'
   },
   data:{

   }
}
*/

class UserController {
 async login(ctx) {
   const requestUrl = userRequestUrlMappingResolver.login;
   console.log(ctx.request.body);
   const response = await baseHttpClient.doHttpPostRequest(
     ctx,
     requestUrl,
     JSON.stringify(ctx.request.body)
   );
   const responseData = qs.parse(response.data);
   const responseDataCode = responseData.result.code;

   // login successful
   if (0 === responseDataCode) {
     ctx.body = responseData;
   } else {
     ctx.body = responseData;
   }
 }
}

module.exports = new UserController();

路由绑定controller

const Router = require("@koa/router");
const userRouter = new Router();
const userController = require("../controller/userController");
const userServerUrlMappingResolver = require("../config/server/userServerUrlMappingResolver");

userRouter.post(userServerUrlMappingResolver.login, userController.login);

module.exports = userRouter;

定义路由

userServerUrlMapping.json

{
  "login": "/api/login"
}

userServerUrlMappingResolver.js

const path = require("path");
const jsonfile = require("jsonfile");

module.exports = jsonfile.readFileSync(
  path.join(__dirname, "userServerUrlMapping.json")
);

入口启动koa

const Koa = require("koa");
const path = require("path");
const combineRouters = require("koa-combine-routers");
const bodyParser = require("koa-bodyparser");
const koaStatic = require("koa-static");
const compress = require("koa-compress");
const app = new Koa();

const router = require("./router");

app.use(
  compress({
    threshold: 2048, // 传输数据量超过2k会压缩响应
  })
);

app.use(bodyParser()); // 请求体

app.use(koaStatic(path.join(__dirname, "dist"))); // 静态资源

const unifiedRouters = combineRouters.apply(null, router)(); // 路由合并
app.use(unifiedRouters);

module.exports = app;