erikwang2013 / poster-php
PHP captcha (click/rotate/slider) and poster generation — framework-agnostic with adapters for Laravel, ThinkPHP, Webman, Hyperf.
Requires
- php: >=8.0
- ext-gd: *
- ext-mbstring: *
Requires (Dev)
- phpunit/phpunit: ^9.0 || ^10.0 || ^11.0
Suggests
- ext-imagick: For ImageMagick image driver (more features, better performance)
- ext-redis: For Redis captcha storage (distributed deployments)
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.3.0
- v1.2.19
- v1.2.18
- v1.2.17
- v1.2.16
- v1.2.15
- v1.2.14
- v1.2.13
- v1.2.12
- v1.2.11
- v1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2026-09-25 15:42:07 UTC
README
PHP 图片验证码与海报生成工具包 —— 框架无关核心 + Laravel / ThinkPHP / Webman / Hyperf 适配。
English Documentation | 架构设计文档 | 全部语言
中文 | English | 日本語 | 한국어 | Русский | Deutsch | Français | Español | Português | हिन्दी | العربية | বাংলা | Bahasa Indonesia
项目简介
poster-php 是一个 PHP 图像工具包,只做两件事,并且做到够用:
| 能力 | 说明 |
|---|---|
| 验证码 | 点击 / 旋转 / 滑块三种人机校验 + 随机切换,纯 PHP 生成图片与答案,不依赖第三方服务 |
| 海报生成 | 链式 Builder API,14 种元素覆盖文字、图片、二维码、表格、图表、日历等排版需求 |
| 框架无关 | 核心只依赖 PHP ≥ 8.0 + GD,可作为普通 Composer 包使用,无需框架 |
| 开箱即用 | 3 个全局辅助函数 + 4 种框架适配(Laravel / ThinkPHP / Webman / Hyperf) |
| 可替换 | 图像驱动(GD / ImageMagick)、存储后端(File / Session / Redis)均为接口实现,按需替换 |
项目宠物 Posty —— 一只由海报本体、二维码卡片与滑块拼图组成的吉祥物,正好对应这个包的两大能力:出图与验证。它随包分发(
assets/pet.svg/assets/pet.png),可用->addPet()画进海报,也可配置为缺图占位图。
项目结构
poster-php/
├── src/ # 核心代码:64 个 PHP 文件 / 约 6093 行
│ ├── Captcha/ # 验证码模块:接口 + 抽象基类 + 3 种实现 + 工厂 + 管理器
│ │ # + RateLimiter(限流)/ TrajectoryVerifier(轨迹校验)
│ ├── Poster/ # 海报模块(Elements/ElementRegistry.php 为元素单点注册表)
│ │ ├── PosterBuilder.php # 链式 Builder,14 个 addXxx() 方法
│ │ ├── PosterTemplate.php # JSON 模板 → {{变量}} 替换
│ │ └── Elements/ # 14 种元素渲染器 + ElementInterface + 抽象基类
│ ├── Drivers/ # 图像驱动:ImageDriverInterface / GdDriver / ImagickDriver
│ ├── Storage/ # 验证数据存储:File / Session / Redis / PSR-16 缓存
│ ├── Qrcode/ # 纯 PHP 二维码生成器(Model 2,v1-40,零扩展依赖)
│ ├── Adapters/ # 框架适配:Laravel / ThinkPHP / Webman / Hyperf
│ ├── PosterConfig.php # 配置读取(默认值兜底 + 框架配置合并)
│ └── Installer.php # composer 安装后自动复制配置文件
├── config/
│ └── poster.php # 默认配置(验证码 / 图像驱动)
├── assets/
│ ├── backgrounds/ # 6 张内置验证码背景图(400×250 PNG)
│ ├── pet.svg # 项目宠物 Posty(矢量源文件)
│ └── pet.png # 由 pet.svg 栅格化:addPet() 与缺图占位图使用
├── helpers.php # 全局函数:captcha_create / captcha_verify / poster_create
├── native.php # 原生 PHP 入口:无需 Composer,require 即用
├── tests/ # PHPUnit 测试,53 个文件,目录结构与 src/ 镜像
├── examples/ # 可直接运行的示例脚本
├── docs/ # 架构文档、设计与生命周期图(SVG)、收款码
└── composer.json # PSR-4:Erikwang2013\Poster\ → src/
架构与设计
系统架构设计
分层依赖:上层只调用下层接口,替换驱动或存储实现时业务代码零改动。
功能设计
两大模块的功能拆解:验证码的四种交互与安全特性、海报的 14 种元素与模板系统。
生命周期
一次验证码校验(创建 → 生成 → 存储 → 下发 → 校验 → 通过 / 失败 / 过期)与一张海报生成(初始化 → 背景 → 元素 → 模板 → 渲染 → 输出)的完整链路。
功能
验证码(三种方式 + 随机切换)
| 类型 | 说明 |
|---|---|
点击验证 click |
用户按顺序点击图片上的目标文字 |
旋转验证 rotate |
用户拖动滑块将图片旋转回正确角度 |
滑块验证 slider |
用户拖动拼图块到缺口位置 |
随机切换 random |
随机选取以上三种验证码之一 |
海报生成
链式 Builder API,支持 14 种元素:
| 元素 | 方法 | 说明 |
|---|---|---|
| 文字 | addText() |
自动换行,对齐,多行 |
| 图片 | addImage() |
缩放裁剪,圆角,阴影 |
| 头像 | addAvatar() |
圆形裁剪,边框 |
| 二维码 | addQrcode() |
纯 PHP 生成,中心 Logo,底部文案 |
| 形状 | addShape() |
矩形/圆形/圆角,填充/描边 |
| 分割线 | addLine() |
颜色,宽度 |
| 水印 | addWatermark() |
平铺文字,角度,间距 |
| 表格 | addTable() |
表头,斑马纹,列宽 |
| 图表 | addChart() |
柱状图 / 折线图 / 饼图 |
| 日历 | addCalendar() |
月历,高亮日期,标注 |
| 艺术字体 | addArtisticText() |
描边 / 阴影 / 渐变 / 霓虹 |
| Emoji | addEmoji() |
彩色 emoji 表情渲染 |
| 字体图标 | addIcon() |
FontAwesome 图标渲染 |
| 颜文字 | addEmoticon() |
日式颜文字 / 自定义表情 |
安装
composer require erikwang2013/poster-php
系统要求:PHP >= 8.0,GD 扩展。
可选扩展:
ext-imagick:ImageMagick 图像驱动(性能更好,功能更强)ext-redis:Redis 验证码存储(分布式部署)
不用 Composer(原生 PHP)
把 poster-php/ 整个目录放进项目,直接引入 native.php 即可:它会注册 PSR-4 自动加载并载入全局函数,不需要 Composer、也不需要任何框架。
require '/path/to/poster-php/native.php'; // 注册自动加载 + 全局函数 $result = captcha_create('click'); $builder = poster_create(750, 1334);
native.php 可重复引入,也能与 Composer 或项目自带的自动加载器共存(重复安装时优先用 vendor/autoload.php 即可)。
使用说明
一、验证码
1. 点击验证码 (ClickCaptcha)
用户需要按顺序点击图片上的目标文字(如"树""鸟""花"),验证人类操作。
// 通过辅助函数(框架无关) $result = captcha_create('click', [ 'difficulty' => 'medium', // 'easy'(2目标) | 'medium'(3目标) | 'hard'(4目标) 'background' => null, // 自定义背景图路径,null=程序化渐变背景(随机风格) ]); // 返回结果 // $result = [ // 'key' => 'abc123...', // 验证唯一标识,传给前端 // 'image' => 'data:image/png;base64,...', // 图片 base64 // 'extra' => [ // 'texts' => [ // ['order' => 1, 'text' => '树'], // ['order' => 2, 'text' => '鸟'], // ['order' => 3, 'text' => '花'], // ], // ], // ]; // 前端按 order 顺序展示提示文字,用户依次点击对应位置(目标坐标不返回,仅服务端校验) // 前端提交用户点击坐标 [[x1,y1], [x2,y2], [x3,y3]] $pass = captcha_verify($result['key'], 'click', [[120, 80], [200, 150], [310, 95]]); // 返回 true / false,容差半径 18px // 通过 CaptchaManager(完整 API) use Erikwang2013\Poster\Captcha\CaptchaManager; use Erikwang2013\Poster\Drivers\DriverFactory; use Erikwang2013\Poster\Storage\FileStorage; $manager = new CaptchaManager(DriverFactory::create(), new FileStorage()); $captcha = $manager->create('click') ->setDifficulty('hard') // easy=2目标 | medium=3目标 | hard=4目标 ->setTargetType('text') // 'text' 文字 | 'icon' 图标 ->setWords(['猫', '狗', '鸟', '鱼']) // 自定义文字池(可选) ->setBackground('/path/to/bg.jpg'); $result = $captcha->generate(); $pass = $manager->verify($result['key'], [ 'type' => 'click', 'data' => [[120, 80], [200, 150], [310, 95], [180, 60]], ]);
setTargetType('icon') 可把目标文字换成程序化生成的矢量图形(11 种,用 GD 图元绘制,无需图片素材):
extra['texts'] 每项会多一个 thumb(该图形的 base64 小图),供前端展示点击提示;校验仍是坐标比对。
2. 旋转验证码 (RotateCaptcha)
系统随机旋转图片 30°~330°,用户拖动滑块将图片旋转回正。
// 通过辅助函数 $result = captcha_create('rotate'); // $result['extra'] 不含角度(验证答案),前端只展示旋转后的图片 $pass = captcha_verify($result['key'], 'rotate', 185); // 用户旋转角度,±5° 容差 // 通过 CaptchaManager $captcha = $manager->create('rotate') ->setSize(200) // 圆形直径 60-400(默认 200) ->setAngleRange(45, 315) // 自定义旋转角度范围 ->generate();
3. 滑块验证码 (SliderCaptcha)
系统从背景切出拼图块并偏移,用户拖动拼图到缺口位置。
// 通过辅助函数 $result = captcha_create('slider'); // $result = [ // 'image' => '...', // 带缺口的背景图 // 'extra' => [ // 'puzzle' => '...', // 拼图块图片 // 'puzzle_w' => 50, // 拼图宽度 // 'puzzle_h' => 50, // 拼图高度 // ], // ]; $pass = captcha_verify($result['key'], 'slider', 173); // 用户滑动的 x 像素,±4px 容差
4. 随机切换 (RandomCaptcha)
系统随机从 click / rotate / slider 中选取一种验证码,增加破解难度。
// 通过辅助函数 — 一行代码随机生成 $result = captcha_create('random'); // $result['type'] 返回实际选中的类型: 'click' | 'rotate' | 'slider' // 前端根据 type 渲染对应的交互组件 switch ($result['type']) { case 'click': // 渲染点击组件:展示图片,用户依次点击 extra.texts 提示文字 break; case 'rotate': // 渲染旋转组件:展示图片,用户拖动旋转 break; case 'slider': // 渲染滑块组件:展示缺口图 + 拼图块 break; } // 验证时传入实际类型和用户操作数据 $pass = captcha_verify($result['key'], $result['type'], $userData); // click: $userData = [[x1,y1],[x2,y2],...] // rotate: $userData = 185 (角度) // slider: $userData = 173 (像素) // 通过 CaptchaManager $captcha = $manager->create('random')->generate(); $pass = $manager->verify($captcha['key'], [ 'type' => $captcha['type'], 'data' => $userData, ]);
验证安全特性
| 特性 | 说明 |
|---|---|
| 一次性 | 验证成功/超过最大次数后 key 删除 |
| 防暴力 | 默认最多验证 3 次(可配置) |
| 有效期 | 默认 300 秒(可配置) |
| 随机性 | 每次生成的背景颜色、噪声、目标位置均随机;点击目标逐目标随机色相与旋转角度 |
| 会话级限流 | 跨 key 生效的窗口限流(默认 60 秒内 30 次),堵住「每次换新 key 再猜一次」的盲猜 |
| 行为轨迹 | 可选(默认关闭):校验拖动轨迹的点数/耗时/线性度,脚本直接 POST 答案会被拒 |
| 背景美化 | 程序化渐变背景,三种风格(简约/活泼/自然)随机切换,支持配置默认背景图目录 |
| 画布下限 | 背景过小时直接报错而不是退化(点击验证码最小 120×120,滑块最小需容纳 4×2 拼图块) |
行为轨迹校验(可选)
默认关闭(避免误伤触屏与无障碍设备)。开启后 slider / rotate 需要前端提交拖动轨迹,服务端校验点数、耗时与轨迹线性度:
// config/poster.php 'captcha' => [ 'trajectory' => [ 'enabled' => true, 'min_points' => 4, // 最少采样点 'min_duration' => 300, // 最短耗时(毫秒) 'max_duration' => 5000, // 最长耗时(毫秒) 'max_linearity' => 0.99, // 线性度高于此值判为机器(脚本拖动是条直线) ], ], // 前端提交:旧写法传数值仍然兼容 captcha_verify($key, 'slider', 173); // 开启轨迹校验后需带轨迹 captcha_verify($key, 'slider', ['x' => 173, 'trail' => [[12, 3, 0], [40, 9, 22], /* … */], 'duration' => 1200]);
背景图片配置
验证码背景支持三级优先级:
- 单张图片 — 通过
setBackground('/path/to/bg.jpg')指定 - 图片目录 — 配置
captcha.background_dir指向图片目录,默认指向assets/backgrounds/(内置 6 张精美渐变背景图) - 程序化生成 — 设
background_dir为null时启用,三种风格随机切换
// 方式一:代码指定单张图片 $captcha = $manager->create('click')->setBackground('/path/to/bg.jpg'); // 方式二:替换默认背景图(config/poster.php) 'captcha' => [ // 把自己的背景图放到这个目录,自动随机选用 'background_dir' => '/path/to/my-backgrounds', // 设为 null 则使用程序化渐变背景 // 'background_dir' => null, ], // 方式三:什么都不做,自动使用内置默认背景图(assets/backgrounds/)
默认背景图:assets/backgrounds/ 自带 6 张 400×250 PNG 渐变背景,风格包括蓝紫、日落、清新绿、暗黑、粉彩、海洋蓝。
三种程序化风格:
| 风格 | 说明 |
|---|---|
minimal 简约 |
柔和渐变 + 大尺寸低透明度圆形 + 几何线条 + 稀疏细点 |
vibrant 活泼 |
明亮渐变 + 多种大小彩色圆形 + 中等密度噪点 |
natural 自然 |
暖色渐变 + 不规则色块模拟纸张纹理 + 细微密点 |
二、海报生成
基础用法
use Erikwang2013\Poster\Poster\PosterBuilder; use Erikwang2013\Poster\Drivers\DriverFactory; // 通过辅助函数 $builder = poster_create(750, 1334); // 宽×高 // 或直接实例化 $builder = new PosterBuilder(DriverFactory::create()); $builder->width(750)->height(1334); // 设置背景 $builder->background('#FFFFFF'); // 纯色背景 $builder->background('/path/to/bg.jpg'); // 图片背景(自动缩放) $builder->backgroundGradient('#FF6B6B', '#FF8E53', 'vertical'); // 渐变背景 // 方向: vertical | horizontal // 输出 $builder->save('/output/poster.jpg', 90); // 保存到文件(路径, 质量 0-100) // 格式按扩展名推断:jpg/jpeg/png/webp/gif // 不传质量时 JPEG 读 poster.jpeg_quality、PNG 读 poster.png_compression $dataUrl = $builder->output('png', 90); // 获取 base64 data URL
文字 addText()
$builder->addText('新品首发', [ 'x' => 80, // 横坐标 'y' => 120, // 纵坐标(基线位置) 'size' => 48, // 字号 'color' => '#333333', // 颜色 'font' => '/path/to/font.ttf', // 字体文件,null=GD 内置 'align' => 'center', // left | center | right 'maxWidth' => 600, // 最大宽度(自动换行) 'lineHeight' => 72, // 行高 'angle' => 0, // 旋转角度 ]);
图片 addImage()
$builder->addImage('/path/to/product.jpg', [ 'x' => 75, 'y' => 280, 'width' => 600, // 渲染宽度(自动缩放) 'height' => 600, // 渲染高度 'radius' => 12, // 圆角半径 'shadow' => [ // 阴影(可选) 'color' => '#00000033', 'offsetX' => 4, 'offsetY' => 4, 'blur' => 10, ], ]);
头像 addAvatar()
$builder->addAvatar('/path/to/avatar.jpg', [ 'x' => 80, 'y' => 60, 'size' => 120, // 头像尺寸(正方形) 'border' => '#FF6B6B', // 边框颜色(可选) ]);
二维码 addQrcode()
$builder->addQrcode('https://example.com/page/123', [ 'x' => 275, 'y' => 1050, 'size' => 200, // 二维码尺寸 'level' => 'H', // 容错级别 L | M | Q | H 'logo' => '/path/to/logo.png', // 中心 Logo(可选) 'label' => '扫码查看详情', // 底部文案(可选) 'label_size' => 14, 'label_color' => '#999999', ]);
容量超出该版本上限时(例如 H 级约 1273 字节以上)会抛 InvalidArgumentException,不再静默产出扫不出来的码。
形状 addShape()
// 矩形 $builder->addShape('rect', [ 'x' => 0, 'y' => 0, 'width' => 750, 'height' => 60, 'color' => '#FF6B6B', 'filled' => true, // true=填充 false=描边 'radius' => 8, // 圆角半径 'opacity' => 0.8, // 透明度 0-1 ]); // 圆形 $builder->addShape('circle', [ 'x' => 100, 'y' => 100, 'width' => 80, 'height' => 80, 'color' => '#4ECDC4', ]);
分割线 addLine()
$builder->addLine([ 'x1' => 75, 'y1' => 800, 'x2' => 675, 'y2' => 800, 'color' => '#EEEEEE', 'width' => 1, ]);
水印 addWatermark()
$builder->addWatermark('CONFIDENTIAL', [ 'size' => 24, 'color' => '#00000020', // 半透明 'font' => '/font.ttf', 'angle' => 30, // 倾斜角度 'spacing' => 200, // 间距 ]);
表格 addTable()
$builder->addTable([ 'x' => 50, 'y' => 800, 'width' => 650, 'columns' => [150, 350, 150], // 列宽 'header' => ['序号', '项目', '价格'], 'rows' => [ ['1', '商品A', '¥99'], ['2', '商品B', '¥199'], ['3', '商品C', '¥299'], ], 'headerBg' => '#333333', 'headerColor' => '#FFFFFF', 'rowBg' => ['#FFFFFF', '#F5F5F5'], // 斑马纹 'rowColor' => '#333333', 'fontSize' => 24, 'cellPadding' => 10, ]);
图表 addChart()
// 柱状图 $builder->addChart('bar', [ ['label' => '一月', 'value' => 120], ['label' => '二月', 'value' => 200], ['label' => '三月', 'value' => 150], ['label' => '四月', 'value' => 300], ], [ 'x' => 50, 'y' => 100, 'width' => 650, 'height' => 400, 'colors' => ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4'], ]); // 折线图 $builder->addChart('line', [ ['label' => '周一', 'value' => 10], ['label' => '周二', 'value' => 35], ['label' => '周三', 'value' => 25], ['label' => '周四', 'value' => 45], ['label' => '周五', 'value' => 30], ], [ 'x' => 50, 'y' => 100, 'width' => 650, 'height' => 400, 'colors' => ['#FF6B6B'], ]); // 饼图 $builder->addChart('pie', [ ['label' => '电商', 'value' => 45], ['label' => '社交', 'value' => 25], ['label' => '搜索', 'value' => 15], ['label' => '其他', 'value' => 15], ], [ 'x' => 75, 'y' => 100, 'width' => 600, 'height' => 600, 'colors' => ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4'], ]);
日历 addCalendar()
$builder->addCalendar([ 'x' => 50, 'y' => 200, 'year' => 2026, 'month' => 5, // 1-12 'cellSize' => 60, // 格子大小 'startDay' => 0, // 0=周日 1=周一 'title' => '2026年5月', // 标题(默认自动生成) 'highlights' => [ // 高亮日期 '2026-05-01' => ['bg' => '#FF6B6B', 'text' => '劳动节'], '2026-05-16' => ['bg' => '#FFEAA7', 'text' => '今天'], ], 'headerBg' => '#333333', // 标题栏背景 'headerColor' => '#FFFFFF', // 标题栏文字颜色 'cellBg' => '#FFFFFF', // 格子背景 'cellBorder' => '#DDDDDD', // 格子边框 'todayBg' => '#FF6B6B', // 今天背景色 'highlightBg' => '#FFF3CD', // 高亮默认背景色 'textColor' => '#333333', // 日期文字颜色 'dimColor' => '#CCCCCC', // 非本月/空白颜色 ]);
艺术字体 addArtisticText()
// 描边效果 $builder->addArtisticText('SALE', 'stroke', [ 'x' => 80, 'y' => 120, 'size' => 72, 'color' => '#FF6B6B', // 填充颜色 'strokeColor' => '#000000', // 描边颜色 'strokeWidth' => 3, // 描边宽度 ]); // 阴影效果 $builder->addArtisticText('新品', 'shadow', [ 'x' => 80, 'y' => 120, 'size' => 48, 'color' => '#333333', 'shadowColor' => '#00000033', 'shadowOffsetX' => 4, 'shadowOffsetY' => 4, ]); // 渐变效果 $builder->addArtisticText('VIP', 'gradient', [ 'x' => 80, 'y' => 120, 'size' => 60, 'color' => '#FF6B6B', // 顶部颜色 'color2' => '#FF8E53', // 底部颜色 ]); // 霓虹发光效果 $builder->addArtisticText('HOT', 'neon', [ 'x' => 80, 'y' => 120, 'size' => 56, 'color' => '#FF1493', 'glowColor' => '#FF1493', ]);
Emoji addEmoji()
// 直接使用 emoji 字符 $builder->addEmoji('😀', ['x' => 100, 'y' => 100, 'size' => 64]); $builder->addEmoji('🎉', ['x' => 180, 'y' => 100, 'size' => 64]); // 使用 unicode 码点 $builder->addEmoji('', [ 'x' => 100, 'y' => 100, 'size' => 64, 'codepoint' => 'U+1F600', // 等同于 😀 ]); // 指定 emoji 字体(需系统支持彩色字体) $builder->addEmoji('😀', [ 'x' => 100, 'y' => 100, 'size' => 64, 'font' => '/System/Library/Fonts/Apple Color Emoji.ttc', ]);
系统会自动检测 macOS / Linux / Windows 上的 emoji 字体路径。
注意:能否画出 emoji 取决于字体本身。Linux 上常见的
NotoColorEmoji.ttf是 CBDT 位图彩色字体,GD 的 FreeType 通道无法加载(imagettftext()直接失败),此时 emoji 不会被绘制;请改用系统内可被 FreeType 正常加载的 emoji 字体。
字体图标 addIcon()
// 使用内置 FontAwesome 图标名(需提供图标字体文件) $builder->addIcon('heart', [ 'x' => 20, 'y' => 40, 'size' => 32, 'color' => '#E74C3C', 'font' => '/path/to/fa-solid-900.ttf', // 必须提供 FontAwesome TTF 字体 ]); $builder->addIcon('star', ['x' => 60, 'y' => 40, 'color' => '#F39C12', 'font' => '/path/to/fa-solid-900.ttf']); $builder->addIcon('check', ['x' => 100, 'y' => 40, 'color' => '#27AE60', 'font' => '/path/to/fa-solid-900.ttf']); // 使用自定义 unicode 码点 $builder->addIcon('', [ 'x' => 20, 'y' => 40, 'size' => 32, 'codepoint' => '\\u{F3C5}', // map-marker 'color' => '#E74C3C', 'font' => '/path/to/fa-solid-900.ttf', ]); // 内置图标名列表 // heart, star, user, clock, home, cog, check, times, search, // envelope, phone, camera, play, pause, shopping-cart, tag, // map-marker, calendar, comment, share, download, upload, // lock, globe, link, image, music, video, bell, bookmark, // thumbs-up, eye, trash, edit, plus, minus, arrow-*, // location-dot, fire, gift, rocket
颜文字 addEmoticon()
// 使用内置颜文字 $builder->addEmoticon('happy', ['x' => 20, 'y' => 40, 'size' => 24]); // 渲染: (。•̀ᴗ-)✧ $builder->addEmoticon('love', ['x' => 20, 'y' => 80, 'size' => 24]); // 渲染: (♡°▽°♡) $builder->addEmoticon('cry', ['x' => 20, 'y' => 120, 'size' => 24]); // 渲染: (╥﹏╥) // 自定义表情文字 $builder->addEmoticon('', [ 'x' => 20, 'y' => 40, 'size' => 24, 'text' => '(╯°□°)╯︵ ┻━┻', // 自定义文字 'color' => '#333333', ]); // 内置颜文字表达式 // happy, love, cry, angry, surprised, cool, sleepy, // wave, think, shrug, tableflip, lenny
项目宠物 addPet()
内置吉祥物 Posty(assets/pet.png,由 assets/pet.svg 栅格化)可直接画进海报,等价于 addImage(PosterBuilder::petPath(), $options):
$builder->addPet([ 'x' => 555, 'y' => 140, 'width' => 150, 'height' => 130, // 按给定宽高缩放,建议保持 600:520 比例 'radius' => 0, // 支持 addImage() 的全部选项 ]); // 也可取路径自行使用(例如作为二维码中心 Logo) $logo = PosterBuilder::petPath();
缺图占位图:addImage() / addAvatar() 遇到不存在的文件时默认跳过不绘制。把 poster.placeholder 指向吉祥物,缺图位置就会画出 Posty,一眼看出哪张图漏了:
// config/poster.php 'poster' => [ 'placeholder' => dirname(__DIR__) . '/assets/pet.png', ],
三、模板系统
use Erikwang2013\Poster\Poster\PosterTemplate; // 定义模板(JSON 可序列化) $template = PosterTemplate::fromConfig([ 'width' => 750, 'height' => 1334, 'elements' => [ ['type' => 'shape', 'color' => '#FF6B6B', 'x' => 0, 'y' => 0, 'width' => 750, 'height' => 300], ['type' => 'text', 'text' => '{{title}}', 'x' => 80, 'y' => 100, 'size' => 48, 'color' => '#FFFFFF'], ['type' => 'text', 'text' => '{{subtitle}}', 'x' => 80, 'y' => 180, 'size' => 28, 'color' => '#FFE0E0'], ['type' => 'image', 'src' => '{{cover}}', 'x' => 75, 'y' => 350, 'width' => 600, 'height' => 600, 'radius' => 12], ['type' => 'qrcode', 'content' => '{{url}}', 'x' => 275, 'y' => 1050, 'size' => 200, 'label' => '扫码查看详情'], ], ]); // 使用模板 + 变量渲染 $builder->useTemplate($template)->with([ 'title' => '新品首发', 'subtitle' => '限时特惠 · 买一送一', 'cover' => '/path/to/product.jpg', 'url' => 'https://m.example.com/product/123', ])->save('/output/poster.jpg'); // 模板支持的元素类型: text, image, qrcode, avatar, shape, line, watermark, table, // chart, calendar, artistic-text, emoji, icon, emoticon
useTemplate() 默认替换此前的 addXxx() 元素(保持原有语义);要「模板打底 + 再叠手写元素」用第二个参数:
$builder->replaceElements(false)->useTemplate($template)->with($vars)->addPet(['x' => 20, 'y' => 20, 'width' => 80]); // 反向导出:把当前 builder(或单个元素)转成模板结构,可再次喂回 fromConfig() $config = $builder->toArray(); // ['width'=>…, 'height'=>…, 'elements'=>[…]] $template2 = PosterTemplate::fromConfig($config); // 导出 → 再导入,结构一致 // 新增元素类型只要在 ElementRegistry 注册一次,Builder 与模板同时生效 $builder->add('text', ['text' => 'hello', 'x' => 10, 'y' => 30, 'size' => 20]);
注意:
AbstractElement::toArray()自本版本起返回「短类型名 + 拍平选项」(此前是['type' => 类名, 'options' => [...]]),以便与模板结构往返一致。
框架集成
Laravel
use Erikwang2013\Poster\Adapters\Laravel\Facades\Captcha; use Erikwang2013\Poster\Adapters\Laravel\Facades\Poster; $result = Captcha::create('click')->generate(); Poster::width(750)->height(1334)->background('#FFF')->save('poster.jpg');
// 配置 config/poster.php 里的 captcha.route.enabled = true 后,适配器会注册图片端点: // GET /captcha/{key} → 直接返回 PNG(Content-Type: image/png,Cache-Control: no-store) // 前端用 URL 即可,无需再传 base64(体积小 33%,且可被浏览器/CDN 缓存) $result = Captcha::create('click')->generate(); // $result['image'] 仍是 data URI;$result['url'] 是可直接放进 <img src> 的地址 // 表单校验:规则名为 captcha,参数是 image key $request->validate([ 'captcha_key' => 'required|string', 'captcha_code' => 'required|captcha:captcha_key', ]);
php artisan vendor:publish --tag=poster-config
ThinkPHP
config/web.php:
'services' => [ Erikwang2013\Poster\Adapters\ThinkPHP\CaptchaService::class, Erikwang2013\Poster\Adapters\ThinkPHP\PosterService::class, ],
Webman
config/bootstrap.php:
return [ Erikwang2013\Poster\Adapters\Webman\CaptchaPlugin::class, Erikwang2013\Poster\Adapters\Webman\PosterPlugin::class, ];
Hyperf
通过 ConfigProvider 自动注册。
配置
composer require 后自动将 config/poster.php 复制到项目 config/ 目录(已存在则跳过)。兼容 Laravel / ThinkPHP / Webman(config/poster.php)和 Hyperf(config/autoload/poster.php)。
主要配置项:
| 配置项 | 默认值 | 说明 |
|---|---|---|
captcha.default_type |
random |
默认验证码类型:click / rotate / slider / random |
captcha.default_difficulty |
medium |
默认难度:easy / medium / hard |
captcha.click_words |
[合,家,欢,...] |
click 验证码文字池,可自定义 |
captcha.background_dir |
assets/backgrounds/ |
背景图目录,null 则程序化生成 |
captcha.ttl |
300 |
验证码有效期(秒) |
captcha.max_attempts |
3 |
最大验证次数 |
captcha.tolerance |
{click:18,rotate:5,slider:4} |
各类型容差 |
image.driver |
auto |
图像驱动:auto / gd / imagick |
poster.placeholder |
null |
缺失图片的占位图路径,null 跳过不绘制;设为吉祥物路径可在缺图处绘制 Posty |
captcha.rate_limit |
{max:30,window:60} |
会话/账号级窗口限流;身份默认取 session_id,无会话时取客户端 IP |
captcha.trajectory |
{enabled:false,…} |
行为轨迹校验(默认关闭) |
captcha.cache.pool |
null |
PSR-16 池对象(storage=cache 时用),也可运行时 StorageFactory::setPsr16Pool() |
captcha.route |
{enabled:false,path:'/captcha'} |
Laravel 适配器:注册图片端点 GET {path}/{key} 直接返回 PNG |
开源不易,欢迎支持
| 微信 | 支付宝 |
|---|---|
![]() |
![]() |
License
MIT License — Copyright (c) 2026 erik erik@erik.xyz — https://erik.xyz

