Skip to main content

2 posts tagged with "koa2"

View All Tags

· 5 min read

我在实际项目开发过程中,经常要自测,这个时候并不能完全指望服务端 mock 接口和数据,所以其实一段时间了,前前后后沉淀了有这么个 ts-koa 的应用(koa-lite),多用于用于调试接口和 mock 数据,当然,做个改装,也可以用于正式的服务端项目

当然有人要问,为啥不用 nest、egg 这些脚手架,主要原因很简单吧,这些脚手架有额外的上手成本,你要熟悉他们的代码架构,然后相对应的代码文件也比较多,并且功能很齐全。但是咱不需要那么齐全,毕竟初衷是为了用于个人自测项目和 Node.js 学习,一开始就用那么齐全的库,效果反而适得其反

回到项目本身吧,做了一些基础且顺应技术趋势的事情

支持 ts

用 ts-node 编译 ts 代码,nodemon 可以监听文件和热更(根目录下可以通过 nodemon.json 配置规则,参考 nodemon 的配置文件

入口文件

提供了 http 和 websocket 两种应用(app.ts 和 wsApp.ts),分别占用不同的端口,可以配置不同的 script 命令,比如:

"dev": "nodemon -e ts app",
"dev:ws": "nodemon -e ts wsApp",

可以开两个终端同时启动俩应用

websocket 应用是借助了koa-websocket这个中间件实现的,wsApp.ts的基础代码如下:

import Koa from "koa";
import consola from "consola";
import websockify from "koa-websocket";

const wsOptions = {};
const app = websockify(new Koa(), wsOptions);

app.ws.use((ctx, next) => {
return next(ctx);
});

app.ws.use((ctx) => {
ctx.websocket.send("===Hello");
ctx.websocket.on("message", function (msg) {
console.log("===recive msg", msg);
});
ctx.websocket.on("close", () => {
console.log("===close websocket");
});
});

/**
* Create HTTP server.
*/
const host = process.env.HOST || "localhost";
const port = Number(process.env.PORT) || 3001;

const server = app.listen(port, host, () => {
consola.ready({
message: `websocket server listening on ws://${host}:${port}`,
badge: true,
});
});

http

rest api

借助 koa-router,具体 get、post 接口的实现可以参考项目里的示例

建议不同模块按照前缀区分,然后分为不同文件,在入口文件中引入

//...
app.use(homeRouter.routes()).use(homeRouter.allowedMethods());
app.use(userRouter.routes()).use(router.allowedMethods());

文件接口

借助 koa-body 实现上传。这个中间件不仅用于处理 post 请求的数据,还能处理文件类型,即 multipart/form-data。如果是 koa-bodyparser,还要结合 koa-multer 处理文件数据,而且不能和 koa-body 共用,会有冲突

// app.ts
import koaBody from "koa-body";
//...
app.use(
koaBody({
multipart: true,
formidable: {
// the max size of uploading file
maxFileSize: 200 * 1024 * 1024,
},
})
);
// homeRouter.ts
function saveFile(file) {
// 文件读写流
const reader = fs.createReadStream(file.path);
let filePath = path.join(__dirname, "/static/upload/") + `/${file.name}`;
const upStream = fs.createWriteStream(filePath);
reader.pipe(upStream);
}

homeRouter.post("/upload", async (ctx, next) => {
// console.log(ctx.request.files);
const files = ctx.request.files.file;
// 支持多文件
if (files.length > 1) {
for (let file of files) {
saveFile(file);
}
} else if (files) {
saveFile(files);
}
ctx.body = "Upload successfully.";
});

借助 koa-send 实现下载

import send from "koa-send";
// homeRouter.ts
homeRouter.get("/download/:filename", async (ctx, next) => {
let filename = ctx.params.filename;
let filePath = `./static/download/${filename}`;
// mime type, active the download window in browser
ctx.attachment(filePath);
await send(ctx, filePath);
console.log("download successfully.");
});

之前写过一篇 mock 文件接口的文章,参考 mock 文件接口

跨域处理

借助 koa2-cors 定义允许跨域的规则

import cors from "koa2-cors";
//...
app.use(
cors({
origin: function (ctx) {
// 设置允许来自指定域名请求
if (ctx.url === "/test") {
return "*";
}
return "http://localhost:8080";
},
maxAge: 5,
credentials: true,
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization", "Accept"],
exposeHeaders: ["WWW-Authenticate", "Server-Authorization"],
})
);
//...

模板引擎

基于 koa-viewsejs。当然也可以用其他模板引擎,看个人喜好了,我主要是觉得 ejs 更贴近于 html 的形式(对比 pug,缩进那一套确实不太能 get)

import views from "koa-views";
//...
app.use(
views(path.join(__dirname, "./views"), {
extension: "ejs",
})
);

静态资源托管

基于 koa-static,指定一个单独的文件夹来存放静态资源文件

import koaStatic from "koa-static";
//...
// 静态资源目录(相对入口文件app.ts)
const staticPath = "./static";
app.use(koaStatic(path.join(__dirname, staticPath)));

其他

项目相对比较简单,欢迎 comment 和 fork(别忘了 star 哈)。后面主要是个人使用过程中持续迭代,有好的建议欢迎提呀~

仓库地址: https://github.com/GitHubJackson/koa-lite

· 4 min read

实际开发项目,经常会遇到文件相关的接口,比如文件上传和下载,这里针对前端总结一些 mock 文件接口在本地调试的方法

文件下载

基于Node.js快速启动一个 web 服务,借助中间件 koa-send 实现下载功能

const path = require('path');
const Koa = require('koa');
const staticServe = require('koa-static');
const router = require('koa-router')();
const send = require('koa-send');
const app = new Koa();
const staticPath = '/static';
const absolutePath = path.join(__dirname + staticPath);

// 指定静态目录
app.use(staticServe(absolutePath));
app.use(async (ctx) => {
ctx.body = 'hello word'
});
router.get('/file/:name', async (ctx){
const name = ctx.params.name;
const path = `static/${name}`;
ctx.attachment(path);
await send(ctx, path);
})

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
console.log('server is listening in 3000...');
})

koa-send 内部其实用了 fs.createReadStream,以块的形式发送出去,这样前端可以更快地接收数据。fs.readFile 还会将整个文件加载到内存中

前端调用接口

fetch

fetch()
.then((res) => {
return res.blob();
// txt、base64或其他文本类的文件可用以下方式解析
// return res.text()
})
.then((res) => {
// 下载文件示例
let url = URL.createObjectURL(new Blob([res]));
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.download = `image.png`;
document.body.appendChild(link);
link.click();
// 解析base64
// const data = window.atob(res)
});

如果是 axios,需要额外配置 responseType: 'blob',其实也就是 XMLHttpRequest 的配置

类似的中间件有 koa-sendfile,主要是解决中文编码的问题

文件上传

前端主要是通过表单来上传文件,但是文件数据在服务器端并不能像普通参数一样通过 ctx.request.body 获取。这里我们可以借助 koa-body 实现上传功能,该中间件的作用主要是将文件数据拼接到 ctx.request.body.files.file

const koaBody = require('koa-body');
// ...
app.use(koaBody({
multipart: true,
// formidable: {
// 默认限制只有2M,超过会报错
// maxFileSize: 100*1024*1024
// }
}));
router.post('/upload', async (ctx){
const file = ctx.request.body.files.file;
const reader = fs.createReadStream(file.path);
const upStream = fs.createWriteStream(`upload/${file.name}`);
reader.pipe(upStream);
return ctx.body = '上传成功';
})
// ...

前端拼接文件数据

const formData = new FormData();
formData.append("file", input.files[0]);
formData.append("user", "Lucas");
fetch(url, {
method: "POST",
body: formData,
});

不起 web 服务

纯 js 由于浏览器的安全考虑是没法直接操作文件的,如果不想启动服务,还可以借助文件选择或拖拽来模拟读取文件接口。以下示例基于 React Hook(只是提供获取文件的方法,具体使用还需要自行验证)

文件选择:

const fileInputRef = useRef();
// ...
// 监听文件选择
fileInputRef.current.onchange = function (event) {
const file = event.target.files[0];
};
// ...
<input type="file" name="test" ref={fileInputRef} />;

拖拽:

const dragDropRef = useRef();
// ...
// 监听
dragDropRef.current.addEventListener(
"drop",
function (event) {
const file = event.dataTransfer.files[0];
},
false
);
// ...
<div ref={dragDropRef} />;

更多前端处理二进制数据的方法可以参考 https://blog.zhouweibin.top/js/%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E6%8D%AE/