koa是express的一层封装,语法比express更加简洁。所以有必要了解下koa的相关开发方法。
代码实现
- package.json
{
"name": "koapp",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"dev": "npx nodemon src/app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"jsonwebtoken": "^9.0.2",
"koa": "^2.15.3",
"koa-bodyparser": "^4.4.1",
"koa-jwt": "^4.0.4",
"koa-router": "^13.0.1",
"koa2-swagger-ui": "^5.11.0",
"mysql": "^2.18.1",
"swagger-jsdoc": "^6.2.8"
}
}
- 入口文件app.js
const Koa = require("koa");
const logger = require("./mid/logger");
const hello = require("./mid/hello");
const userRouter = require("./router/user");
const publicRouter = require("./router/public");
const sysRouter = require("./router/sys");
const bodyParser = require("koa-bodyparser");
const handlerError = require("./mid/errorHandle");
const Result = require("./utils/result");
const jwt = require("koa-jwt");
const secret = "zhangsanfeng";
const app = new Koa();
app.use(handlerError);
// 中间件 自定义了 401 响应,将用户验证失败的相关信息返回给浏览器
app.use(function (ctx, next) {
return next().catch((err) => {
if (401 == err.status) {
ctx.status = 401;
ctx.body = Result.error(401, "无效的token");
} else {
throw err;
}
});
});
app.use(
jwt({
secret,
// passthrough: true,
// cookie: "token", // 从 cookie 中获取token
debugger: true,
}).unless({ path: [/^\/public/, /^\/sys\/login/] })
);
// 注册路由中间件
app.use(bodyParser());
app.use(userRouter.routes()).use(userRouter.allowedMethods());
app.use(publicRouter.routes()).use(publicRouter.allowedMethods());
app.use(sysRouter.routes()).use(sysRouter.allowedMethods());
// 全局异常处理;
app.on("error", (error, ctx) => {
console.log("ssss", error.status);
});
app.listen(3000);
- 错误中间件
const Result = require("../utils/result");
async function handlerError(ctx, next) {
try {
await next(); // 执行后代的代码
if (!ctx.body) {
// 没有资源
ctx.status = 404;
// ctx.body = "404";
}
} catch (error) {
// 如果后面的代码报错 返回500
ctx.status = error.status;
if (error.status == 404) {
ctx.body = Result.error("找不到资源");
} else if (error.status == 500) {
console.log("🚀 ~ app.on ~ error.status:", error.status);
ctx.body = Result.error("服务器异常,请稍后再试");
} else if (error.status === 401) {
ctx.body = Result.error("未授权 " + error.message);
} else {
ctx.body = Result.error("未知错误");
}
// ctx.body = "500";
}
}
module.exports = handlerError;
- 日志中间件
async function logger(ctx, next) {
console.log("logger before...");
await next();
const rt = await ctx.response.get("X-Response-Time");
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
console.log(ctx.username, "from mid");
console.log("logger after...");
}
module.exports = logger;
- 路由
不需要拦截的路由
const Router = require("koa-router");
const router = new Router();
router.prefix("/public");
router.get("/", async (ctx) => {
console.log("🚀 ~ router.get ~ ctx:", ctx.params);
ctx.body = {
id: ctx.params.id,
name: "小明1111",
age: 18,
sex: "男",
};
// ctx.throw(401);
});
router.get("/:id", async (ctx) => {
console.log("🚀 ~ router.get ~ ctx:", ctx.params);
ctx.body = {
id: ctx.params.id,
name: "小明",
age: 18,
sex: "男",
};
// ctx.throw(401);
});
module.exports = router;
- 登录获取token的路由
const Router = require("koa-router");
const router = new Router();
const jwt = require("koa-jwt");
const { sign } = require("jsonwebtoken");
const Result = require("../utils/result");
const secret = "zhangsanfeng";
router.prefix("/sys");
router.post("/login", async (ctx, next) => {
const body = ctx.request.body;
if (body.username !== "zhangsan" || body.pwd !== "123456") {
ctx.body = Result.error("账号或密码错误");
// ctx.throw(401, "账号或密码错误");
} else {
const token = sign({ abcsd: "aaaaaa" }, secret, { expiresIn: "1h" });
ctx.body = Result.success({ token });
}
});
router.get("/userInfo", async (ctx, next) => {
ctx.body = {
name: "zhangsanfeng",
age: 20,
sex: "男",
};
});
router.get("/logout", async (ctx, next) => {
ctx.body = Result.success("退出成功");
ctx.cookies.set("token", "", { signed: false, maxAge: 0 });
});
module.exports = router;
- 用户路由
const Router = require("koa-router");
const router = new Router();
router.prefix("/user/s");
router.get("/:id", async (ctx) => {
console.log("🚀 ~ router.get ~ ctx:", ctx.params);
ctx.body = {
id: ctx.params.id,
name: "小明2222",
age: 18,
sex: "男",
};
// ctx.throw(401);
});
router.get("/", async (ctx) => {
console.log("🚀 ~ router.get ~ ctx:", ctx.query);
ctx.body = {
id: ctx.params.id,
name: "小明3333",
age: 18,
sex: "男",
};
});
router.post("/", async (ctx) => {
console.log("🚀 ~ router.get ~ ctx:", ctx.request.body);
ctx.body = {
id: ctx.params.id,
name: "小明4444",
age: 18,
sex: "男",
};
});
module.exports = router;
- 工具方法
class Result {
constructor(message, code, data) {
this.message = message;
this.code = code;
this.data = data;
}
static success() {
return new Result("success", 0, null);
}
static success(data) {
return new Result("success", 0, data);
}
static error(message) {
return new Result(message, null, 1);
}
}
module.exports = Result;
实现效果
-
未登录获取列表信息
-
登录账号或密码错误
- 登录成功
- 获取用户信息成功
swagger文档配置
- 配置参考文档:https://swagger.io/docs/specification/v3_0/paths-and-operations/
- post接口文档的书写
const Router = require("koa-router");
const router = new Router();
const jwt = require("koa-jwt");
const { sign } = require("jsonwebtoken");
const Result = require("../utils/result");
const secret = "zhangsanfeng";
const mysql = require("../mysql");
router.prefix("/sys");
/**
* @swagger
* tags:
* name: Users
* description: User management
*/
/**
* @swagger
* components:
* schemas:
* User:
* type: object
* properties:
* username:
* type: string
* description: username.
* password:
* type: string
* description: password.
* required:
* - username
* - password
*/
/**
* @swagger
* /sys/login:
* post:
* summary: login system.
* tags: [Users]
* requestBody:
* description: Optional description in *Markdown*
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* username:
* type: string
* example: zhangsan
* pwd:
* type: string
* example: 123456
* description: User login system.
* responses:
* 200:
* description: user login system.
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
router.post("/login", async (ctx, next) => {
const body = ctx.request.body;
if (body.username !== "zhangsan" || body.pwd !== "123456") {
ctx.body = Result.error("账号或密码错误");
// ctx.throw(401, "账号或密码错误");
} else {
const token = sign({ abcsd: "aaaaaa" }, secret, { expiresIn: "1h" });
ctx.body = Result.success({ token });
}
});
router.get("/userInfo", async (ctx, next) => {
const id = 2;
// 查询数据库
mysql.query("select * from tb_user where id = ?", [id], (err, result) => {
console.log("🚀 ~ mysql.query ~ result:", result);
if (err) {
ctx.body = Result.error("查询失败");
} else {
ctx.body = Result.success(result[0]);
}
});
ctx.body = {
name: "zhangsanfeng",
age: 20,
sex: "男",
};
});
router.get("/logout", async (ctx, next) => {
ctx.body = Result.success("退出成功");
ctx.cookies.set("token", "", { signed: false, maxAge: 0 });
});
module.exports = router;
- 全局配置swaager
const Router = require("koa-router");
const router = new Router();
const swaggerJSdoc = require("swagger-jsdoc");
const path = require("path");
const swaggerDefinition = {
openapi: "3.0.0",
info: {
title: "koa2搭建的app项目接口API",
version: "1.0.0",
description: "测试项目的接口文档",
},
host: "localhost:8080",
basePath: "/",
};
const options = {
swaggerDefinition,
apis: [path.join(__dirname, "../router/*.js")],
};
const swaggerSpec = swaggerJSdoc(options);
router.get("/swagger.json", (ctx) => {
ctx.set("Content-Type", "application/json");
ctx.body = swaggerSpec;
});
module.exports = router;
- 入口文件配置swagger
const Koa = require("koa");
const { koaSwagger } = require("koa2-swagger-ui");
console.log("🚀 ~ koaSwagger:", koaSwagger);
const logger = require("./mid/logger");
const hello = require("./mid/hello");
const userRouter = require("./router/user");
const publicRouter = require("./router/public");
const sysRouter = require("./router/sys");
const bodyParser = require("koa-bodyparser");
const handlerError = require("./mid/errorHandle");
const Result = require("./utils/result");
const swagger = require("./utils/swagger");
const jwt = require("koa-jwt");
const secret = "zhangsanfeng";
// 数据库连接
const mysql = require("./mysql/index");
mysql.connect();
const app = new Koa();
app.use(handlerError);
// 中间件 自定义了 401 响应,将用户验证失败的相关信息返回给浏览器
app.use(function (ctx, next) {
return next().catch((err) => {
if (401 == err.status) {
ctx.status = 401;
ctx.body = Result.error(401, "无效的token");
} else {
throw err;
}
});
});
app.use(
jwt({
secret,
// passthrough: true,
// cookie: "token", // 从 cookie 中获取token
debugger: true,
}).unless({
path: [/^\/public/, /^\/sys\/login/, /^\/swagger/, /^\/apidocs/],
})
);
// 注册路由中间件
app.use(bodyParser());
app.use(userRouter.routes()).use(userRouter.allowedMethods());
app.use(publicRouter.routes()).use(publicRouter.allowedMethods());
app.use(sysRouter.routes()).use(sysRouter.allowedMethods());
app.use(swagger.routes()).use(swagger.allowedMethods());
app.use(
koaSwagger({
routePrefix: "/swagger",
swaggerOptions: {
url: "/swagger.json",
},
})
);
// 全局异常处理;
app.on("error", (error, ctx) => {
console.log("ssss", error.status);
});
app.listen(3000);
效果测试
测试登录成功