npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flexem/aj-captcha

v1.0.2

Published

AJ-Captcha 前端 JS SDK,支持滑动拼图和点选文字验证码

Readme

AJ-Captcha .NET 版

服务器域名: captcha.fbox360.com

行为验证码 C# WebAPI 实现,从 anji-plus/captcha Java 版翻译而来。

支持两种验证码类型:

  • blockPuzzle — 滑动拼图(拖动滑块到缺口位置)
  • clickWord — 点选文字(按顺序点击图片中指定的汉字)

API 接口

所有接口均为 POSTContent-Type: application/json

POST /captcha/get — 获取验证码

请求体

{
  "captchaType": "blockPuzzle"
}

captchaType 可选值:blockPuzzle / clickWord / default(默认使用 blockPuzzle)

响应(blockPuzzle)

{
  "repCode": "0000",
  "repMsg": "success",
  "repData": {
    "token": "a1b2c3d4...",
    "secretKey": "AbCdEfGh12345678",
    "originalImageBase64": "iVBORw0KGgo...",
    "jigsawImageBase64": "iVBORw0KGgo..."
  }
}

响应(clickWord)

{
  "repCode": "0000",
  "repMsg": "success",
  "repData": {
    "token": "a1b2c3d4...",
    "secretKey": "AbCdEfGh12345678",
    "originalImageBase64": "iVBORw0KGgo...",
    "wordList": ["验", "码", "点"]
  }
}

POST /captcha/check — 校验用户操作

请求体(blockPuzzle)

{
  "captchaType": "blockPuzzle",
  "token": "a1b2c3d4...",
  "pointJson": "<AES加密后的坐标JSON>"
}

pointJson 是前端用 secretKey 对坐标对象 {"x":200,"y":5} 做 AES/ECB/PKCS7 加密后的 Base64 字符串。

请求体(clickWord)

{
  "captchaType": "clickWord",
  "token": "a1b2c3d4...",
  "pointJson": "<AES加密后的坐标数组JSON>"
}

pointJson 解密后为点击坐标数组:[{"x":85,"y":34},{"x":129,"y":56},{"x":233,"y":27}]

响应(校验通过)

{
  "repCode": "0000",
  "repMsg": "success",
  "repData": {
    "token": "a1b2c3d4...",
    "result": true
  }
}

POST /captcha/internal/verify-v1 — 服务端二次校验

业务后端调用,用于最终确认前端的验证结果有效。 请求头 带上 "x-secret" ,值的话问服务端后台人员单独给

请求体

{
  "captchaVerification": "<二次校验值>"
}

captchaVerification 的计算方式见下文"两阶段验证协议"章节。

响应(校验通过)

{
  "repCode": "0000",
  "repMsg": "success"
}

错误码

| repCode | 含义 | |---------|------| | 0000 | 成功 | | 6110 | 验证码已失效(token 过期或已使用),请重新获取 | | 6111 | 坐标校验失败(位置不对) | | 6112 | 获取验证码失败(底图未初始化) |


工作原理

整体流程

前端                      验证码服务                    业务后端
 │                            │                            │
 │─── POST /captcha/get ─────>│                            │
 │<── token + secretKey + 图  │                            │
 │    base64底图              │                            │
 │                            │                            │
 │  用户完成交互              │                            │
 │  (拖滑块 / 点文字)       │                            │
 │                            │                            │
 │─── POST /captcha/check ───>│                            │
 │    token + AES加密坐标     │                            │
 │<── result: true            │                            │
 │                            │                            │
 │  前端计算 captchaVerification                           │
 │                            │                            │
 │─────────────── 携带 captchaVerification 提交业务表单 ──>│
 │                            │                            │
 │                            │<── POST /captcha/verify ───│
 │                            │    captchaVerification     │
 │                            │──> repCode: 0000 ─────────>│
 │                            │                            │
 │<──────────────────── 业务响应(登录成功等) ───────────│

两阶段验证协议

防止重放攻击的核心机制:

阶段一:get + check

  1. GET /captcha/get

    • 服务端生成随机坐标 (x, y) 和 AES 密钥 secretKey
    • token → {x, y, secretKey} 存入缓存,有效期 2 分钟
    • 返回给前端:tokensecretKey、图片 base64
  2. POST /captcha/check

    • 前端用 secretKey 对坐标加密后发送
    • 服务端从缓存取出坐标,立即删除(一次性使用,防重放)
    • 验证坐标是否匹配(blockPuzzle 允许 ±5px 误差)
    • 验证通过后,计算:
      value = AES_Encrypt(token + "---" + pointJson, secretKey)
    • "second-{value}" → token 存入缓存,有效期 3 分钟
    • 返回 result: true

阶段二:verify

  1. 前端(或业务后端)计算 captchaVerification

    // 前端用同样的 secretKey 计算(与服务端一致)
    captchaVerification = AES_Encrypt(token + "---" + pointJson, secretKey)
  2. POST /captcha/verify

    • 服务端查找 "second-{captchaVerification}" 是否在缓存中
    • 找到则立即删除,返回成功(一次性使用)
    • 找不到则返回 6110

安全保证:

  • token 一次性:check 后立即失效,防止重复提交
  • secondKey 一次性:verify 后立即失效,防止重放
  • captchaVerification 由 check 时的参数派生,无法伪造

blockPuzzle 图片生成算法

原始底图 (310×155)          模板滑块图 (约56×56, 含透明通道)
      │                              │
      └──── 随机生成坐标 (x, y) ────┘
                  │
    遍历模板每个像素 (i, j):
    ┌─────────────────────────────────────────┐
    │ 若模板像素 alpha > 0(非透明):           │
    │   → 将原图 (x+i, y+j) 的像素复制到新滑块 │
    │   → 对原图该像素做 3×3 高斯模糊(挖洞)  │
    │                                         │
    │ 若当前像素与右邻/下邻透明性不同(边界):  │
    │   → 新滑块该像素设为白色(描边)         │
    │   → 原图对应位置设为白色(轮廓提示)      │
    └─────────────────────────────────────────┘
                  │
      ┌───────────┴───────────┐
      │                       │
   返回原图(含模糊洞+白色轮廓)  返回新滑块(透明背景+原图像素)

高斯模糊算法(对应 Java avgMatrix):

取目标像素周围 3×3 邻域的 6 个像素(跳过中间列)
avg_R = (R1+R2+R3+R4+R5+R6) / 8
avg_G = (G1+G2+G3+G4+G5+G6) / 8
avg_B = (B1+B2+B3+B4+B5+B6) / 8

clickWord 文字生成算法

1. 加载随机点选底图
2. 随机选取 N 个不重复汉字(来自 500+ 常用汉字集)
3. 对每个汉字:
   - 按宽度分区随机放置坐标(避免重叠)
   - 随机颜色 RGB(1-255, 1-255, 1-255)
   - 随机旋转角度 [-45°, 45°]
   - 用 WenQuanZhengHei.ttf 字体绘制
4. 随机隐藏其中 1 个汉字(从 wordList 和校验列表中排除)
5. 在右下角绘制水印
6. 返回:底图 base64 + wordList(需点击的 N-1 个汉字,按顺序)
7. 缓存:N-1 个汉字的坐标,用于 check 时校验

AES 加密规范

与前端交互时的加密参数:

| 项目 | 值 | |------|-----| | 算法 | AES | | 模式 | ECB | | 填充 | PKCS7 | | 密钥长度 | 128 bit(16字节) | | 密钥来源 | GET /captcha/get 返回的 secretKey | | 编码 | Base64 |

blockPuzzle pointJson 加密内容:

{"x": 215, "y": 5}

clickWord pointJson 加密内容:

[{"x": 85, "y": 34}, {"x": 129, "y": 56}, {"x": 233, "y": 27}]

配置项

appsettings.jsonCaptcha 节:

| 配置项 | 默认值 | 说明 | |--------|--------|------| | Type | default | 验证码类型:default/blockPuzzle/clickWord | | CacheType | local | 缓存类型:local(内存)| | WaterMark | 我的水印 | 右下角水印文字 | | WaterFont | WenQuanZhengHei.ttf | 水印字体 | | FontType | WenQuanZhengHei.ttf | 点选验证码字体 | | FontSize | 25 | 点选文字大小(px) | | SlipOffset | 5 | 滑动验证允许误差(px) | | AesStatus | true | 是否开启 AES 坐标加密 | | InterferenceOptions | 2 | 干扰滑块数量:0/1/2 | | ClickWordCount | 4 | 点选文字总数(其中 1 个不参与校验) | | Jigsaw | null | 自定义滑动验证底图目录(空=使用默认) | | PicClick | null | 自定义点选验证底图目录(空=使用默认) |


项目结构

src/AjCaptcha.WebApi/
├── Configuration/
│   └── CaptchaOptions.cs          配置项映射类
├── Controllers/
│   └── CaptchaController.cs       HTTP 接口(get/check/verify)
├── Models/
│   ├── CaptchaVO.cs               请求/响应数据对象
│   ├── PointVO.cs                 坐标对象(x, y, secretKey)
│   └── ResponseModel.cs           统一响应格式
├── Services/
│   ├── ICaptchaService.cs         验证码服务接口
│   ├── ICaptchaCacheService.cs    缓存服务接口
│   ├── CaptchaServiceFactory.cs   按类型路由服务
│   └── Impl/
│       ├── AbstractCaptchaService.cs    check/verify 公共逻辑
│       ├── BlockPuzzleCaptchaService.cs 滑动拼图实现
│       ├── ClickWordCaptchaService.cs   点选文字实现
│       └── MemoryCaptchaCacheService.cs 内存缓存实现
├── Utils/
│   ├── AesUtil.cs      AES 加解密
│   ├── ImageUtils.cs   底图加载 + SkiaSharp 图片处理
│   ├── RandomUtils.cs  随机数/UUID/随机字符串
│   └── Md5Util.cs      MD5 工具
└── Resources/
    ├── defaultImages/
    │   ├── jigsaw/original/       滑动验证底图(7张)
    │   ├── jigsaw/slidingBlock/   滑块模板图(6张,带透明通道)
    │   └── pic-click/             点选验证底图(17张)
    └── fonts/
        └── WenQuanZhengHei.ttf    开源中文字体

扩展说明

接入 Redis 缓存

  1. 添加 NuGet 包:StackExchange.Redis
  2. 实现 ICaptchaCacheService 接口,使用 Redis 操作替换内存操作
  3. Program.cs 中替换注册:
    // 替换这行
    builder.Services.AddSingleton<ICaptchaCacheService, MemoryCaptchaCacheService>();
    // 改为
    builder.Services.AddSingleton<ICaptchaCacheService, RedisCaptchaCacheService>();

多节点部署时必须使用 Redis,内存缓存只适合单节点。

使用自定义底图

将图片(PNG/JPG)放入任意目录,在 appsettings.json 中配置路径:

{
  "Captcha": {
    "Jigsaw": "D:/myimages/jigsaw",
    "PicClick": "D:/myimages/pic-click"
  }
}

添加新的验证码类型

  1. 继承 AbstractCaptchaService,实现 Get()ValidateCoordinates()
  2. Program.cs 注册:
    builder.Services.AddSingleton<ICaptchaService, MyNewCaptchaService>();

前端 JS SDK

零依赖,兼容 IE9+,支持 TypeScript。

安装

npm install @flexem/aj-captcha

或直接引入:

<script src="aj-captcha.min.js"></script>

TypeScript 导入

// esModuleInterop: true
import AJCaptcha from '@flexem/aj-captcha';

// 或 CommonJS 风格
import AJCaptcha = require('@flexem/aj-captcha');

弹窗模式(推荐)

AJCaptcha.popup({
  server: 'https://captcha.fbox360.com',
  captchaType: 'blockPuzzle', // 或 'clickWord'
  title: '请完成安全验证',
  lang: {
    sliderTip: '向右拖动滑块填充拼图',
    clickTip: '请依次点击:',
    refreshTitle: '刷新',
    checking: '验证中...',
    verifySuccess: '验证通过',
    verifyFail: '验证失败,请重试',
    getFail: '获取验证码失败',
    checkFail: '校验失败',
    parseFail: '解析响应失败',
    networkError: '网络错误: ',
    popupTitle: '请完成安全验证'
  },
  onSuccess: function (verification) {
    console.log('验证通过:', verification);
    // 将 verification 提交给业务接口做二次校验
  },
  onFail: function (err) {
    console.error('验证失败:', err);
  },
  onClose: function () {
    console.log('用户关闭弹窗');
  }
});

lang 中所有字段均可选,未传则使用默认中文文案。

内嵌模式

var captcha = new AJCaptcha({
  server: 'https://captcha.fbox360.com',
  captchaType: 'clickWord',
  el: '#captcha-container', // 或传入 DOM 元素
  onSuccess: function (verification) {
    console.log(verification);
  }
});
captcha.render();

API

| 方法 | 说明 | |------|------| | AJCaptcha.popup(options) | 弹窗验证,返回 { close, instance } | | instance.render() | 渲染验证码到 el | | instance.get(callback) | 手动获取验证码数据 | | instance.check(pointData, callback) | 手动校验坐标 | | instance.getCaptchaVerification() | 获取加密后的验证凭证 | | AJCaptcha.AesUtil.encrypt(text, key) | AES/ECB/PKCS7 加密工具 |