使用 Node.js 构建一个 CAPTCHA 生成器
CAPTCHA无法访问,在某些情况下甚至无效,但通过生成我们自己的 CAPTCHA,我们可以学到很多东西!
本文的源代码位于healeycodes/captcha-api
垃圾邮件的解决方案
假设有一位客户需要解决机器人垃圾邮件问题。他们要求提供一张图片和一串图片文字。你回想起了那些你苦苦挣扎却无法解决的、令人费解的字母和数字组合。尽管如此,你还是同意了这项任务。
该客户拥有一系列网站。不同位置需要不同大小的验证码。他们会提供宽度和高度。这描述了我们 API 的规范。
JavaScript 非常适合生成图像,因为我们可以依赖Canvas API。我发现,当我遇到很多 Stackoverflow 内容卡住的时候,用它非常方便。
我们不想在浏览器领域生成我们的验证码,因为我们试图阻止的机器人可以检查源代码,找到内存中的值,并尝试各种其他棘手的策略。
Node.js 服务
让我们把它移到后端,变成一个可以按需调用的服务。有人已经解决了在没有 Web API 的情况下访问该 API 的问题,方法是使用node-canvas或npm i canvas
。
[canvas] 是 Web Canvas API 的一个实现,并尽可能紧密地实现该 API。
我们每次都需要生成一些随机文本。所以,让我们编写两个函数来帮助我们。对于我们的 API,我们将逻辑分解成几个函数,每个函数只做一件事(并且要把这件事做好),这样最终的结果就更容易理解和维护了。
/* captcha.js */
// We'll need this later
const { createCanvas } = require("canvas");
// https://gist.github.com/wesbos/1bb53baf84f6f58080548867290ac2b5
const alternateCapitals = str =>
[...str].map((char, i) => char[`to${i % 2 ? "Upper" : "Lower"}Case`]()).join("");
// Get a random string of alphanumeric characters
const randomText = () =>
alternateCapitals(
Math.random()
.toString(36)
.substring(2, 8)
);
画布中无法自动缩放文本(就像浏览器中的weeps一样),所以我们还需要一些辅助函数来实现。根据验证码的长度以及文本在图像中的显示位置,你可能需要进行测试运行。以下是我之前准备的一些变量。
const FONTBASE = 200;
const FONTSIZE = 35;
// Get a font size relative to base size and canvas width
const relativeFont = width => {
const ratio = FONTSIZE / FONTBASE;
const size = width * ratio;
return `${size}px serif`;
};
这样就可以缩放文本,只要画布的比例保持不变,我们就可以得到相似的图像。
对于本文,我们只需旋转文本,但有很多方法可以扭曲文本以将其隐藏在机器人之外,我很想看看您想出了什么(尝试搜索“透视变换画布 javascript”)。
旋转画布时,我们传递的值是弧度,因此我们需要将随机度数乘以Math.PI / 180
。
// Get a float between min and max
const arbitraryRandom = (min, max) => Math.random() * (max - min) + min;
// Get a rotation between -degrees and degrees converted to radians
const randomRotation = (degrees = 15) => (arbitraryRandom(-degrees, degrees) * Math.PI) / 180;
我保证,不再使用辅助函数了。我们现在要开始真正的核心部分了。逻辑被分解成两个函数。configureText
一个函数接受一个 Canvas 对象,并添加和居中我们的随机文本。generate
另一个函数接受一个宽度和高度值(还记得我们之前给出的规范吗?),并返回一个 PNG 图像的数据 URL——也就是我们的验证码。
数据 URL(以
data:
方案为前缀的 URL)允许内容创建者在文档中嵌入小文件。
// Configure captcha text
const configureText = (ctx, width, height) => {
ctx.font = relativeFont(width);
ctx.textBaseline = "middle";
ctx.textAlign = "center";
const text = randomText();
ctx.fillText(text, width / 2, height / 2);
return text;
};
// Get a PNG dataURL of a captcha image
const generate = (width, height) => {
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.rotate(randomRotation());
const text = configureText(ctx, width, height);
return {
image: canvas.toDataURL(),
text: text
};
};
我们可以将除 之外的所有函数视为generate
私有函数,这些函数不应在其他地方使用,因此我们只需导出此函数。
module.exports = generate;
Express 提供的 API
目前为止,我们有一个文件,captcha.js
其中包含图像生成逻辑。为了让其他人可以调用此功能,我们将通过 HTTP API 提供它。Express 为此类任务提供了最强大的社区支持。
我们将举办的路线是:
/test/:width?/:height?/
- 用于获取手动测试的图像标签。
/captcha/:width?/:height?/
- 用于获取 CAPTCHA 对象以供正确使用。
此处路由中的问号是 Express 中可选 URL 参数的语法。这意味着客户端可以不提供任何参数,也可以只提供第一个参数,或者两个参数都提供。我们将验证传入的值是否为整数(这是 Canvas 的必需参数),如果不是,我们将使用合理的默认值。
Express 应用程序完整内容:
/* app.js */
const captcha = require("./captcha");
const express = require("express");
const app = express();
// Human checkable test path, returns image for browser
app.get("/test/:width?/:height?/", (req, res) => {
const width = parseInt(req.params.width) || 200;
const height = parseInt(req.params.height) || 100;
const { image } = captcha(width, height);
res.send(`<img class="generated-captcha" src="${image}">`);
});
// Captcha generation, returns PNG data URL and validation text
app.get("/captcha/:width?/:height?/", (req, res) => {
const width = parseInt(req.params.width) || 200;
const height = parseInt(req.params.height) || 100;
const { image, text } = captcha(width, height);
res.send({ image, text });
});
module.exports = app;
此 Express 应用已导出,以便我们进行测试。此时我们的 API 已可用。我们只需提供以下文件负责的服务即可。
/* server.js */
const app = require("./app");
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`captcha-api listening on ${port}!`));
导航至 会http://localhost:3000/test
奖励我们基本的验证码。如果省略,浏览器会添加body
and标签。html
有效的数据 URL
是时候写一些测试了,但首先,别再用那些笨重的正则表达式了。有一个库已经解决了这个问题。valid-data-url
它的功能和它的名字完全一致。
我喜欢用 Jest 作为我的测试运行器。原因很简单,就是它总是对我有用,即使它没用,我也能找到解决办法。我的设置是像这样设置scripts
密钥package.json
:
"scripts": {
"test": "jest"
}
这样我就可以输入了npm test
(这也是许多 CI 系统的默认设置)。然后 Jest 会查找并运行我们所有的测试。
我们应用的测试文件导入了 Express 应用程序对象,并用supertest
它来模拟 HTTP 请求。我们使用 async/await 语法来减少回调。
/* app.test.js */
const request = require("supertest");
const assert = require("assert");
const validDataURL = require("valid-data-url");
const app = require("../app");
describe("captcha", () => {
describe("testing captcha default", () => {
it("should respond with a valid data URL", async () => {
const image = await request(app)
.get("/captcha")
.expect(200)
.then(res => res.body.image);
assert(validDataURL(image));
});
});
describe("testing captcha default with custom params", () => {
it("should respond with a valid data URL", async () => {
const image = await request(app)
.get("/captcha/300/150")
.expect(200)
.then(res => res.body.image);
assert(validDataURL(image));
});
});
});
考虑到这个应用程序的大小(小),我满足于对其进行两次集成测试。
与 GitHub 工作流持续集成
由于我们使用了标准的 npm test 命令(npm test
)来配置我们的仓库,因此只需点击几下即可设置 GitHub 工作流。这样,每次推送代码时,我们的应用程序都会被构建和测试。
现在我们有一个甜蜜的徽章可以炫耀!
与 150 多名订阅我的关于编程和个人成长的新闻通讯的人一起!
我发布有关科技的推文@healeycodes。
鏂囩珷鏉ユ簮锛�https://dev.to/healeycodes/let-s-build-a-captcha-generator-with-node-js-165i