常见原因包括未传宽高、参数顺序错误、缺少图形环境、复用Captcha实例、响应头未设Content-Type、Session键名不一致等;需显式指定尺寸、每次新建实例、正确设置响应头与校验逻辑。

生成验证码图片时返回空或报 NullPointerException
常见原因是没传入宽高,或者 CaptchaUtil.createCircleCaptcha() 等工厂方法参数顺序搞错了。Hutool 的验证码构造器默认不校验参数合法性,null 或 0 值会一路透传到绘图阶段,最终在 Graphics2D.drawString() 时炸出空指针。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 务必显式指定宽、高、字符数,例如:
CaptchaUtil.createCircleCaptcha(120, 40, 4),不要依赖默认值 - 如果用
CaptchaUtil.createGifCaptcha(),注意它内部会创建多帧,对内存更敏感,测试时先用静态图(CircleCaptcha或ShearCaptcha)定位问题 - 确保 JVM 启动时有图形环境支持;Linux 服务器没装字体或 headless 模式下,可能静默失败——加
-Djava.awt.headless=true反而会让部分验证码类型直接抛异常
验证码字符总是重复或太简单
Hutool 默认用 RandomGenerator 生成 4 位纯数字,且未开启干扰线/噪点时,肉眼难区分。更关键的是:如果你反复调用同一个 Captcha 实例的 getCode(),返回的永远是首次生成的字符串——它不是动态刷新的。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 换字符集:用
new CircleCaptcha(120, 40, 4, new RandomGenerator("ABCDEFGHKLMNPQRSTUVWXYZ23456789")) - 每次 HTTP 请求都新建实例,别复用对象;
Captcha类不是线程安全的,也别塞进 Spring Bean 单例里 - 需要中文?得自己加载字体文件,
Font构造器传入绝对路径,否则 Linux 下大概率显示方块
前端拿不到验证码图片或提示 MIME 类型错误
典型表现是浏览器地址栏直接打开接口返回乱码,或控制台报 Failed to load resource: net::ERR_INVALID_RESPONSE。根本原因:没设响应头 Content-Type: image/png(或 image/gif),或者写入输出流后忘了 flush() / close()。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- Spring MVC 中,别用
@ResponseBody返回BufferedImage——它不会自动转图片流;必须手动写入HttpServletResponse.getOutputStream() - 写完立刻
response.getOutputStream().flush(),否则 Nginx 或某些代理可能截断响应 - GIF 验证码要设
Content-Type: image/gif,PNG 类型必须是image/png,错一个字母浏览器就拒收
验证码校验总失败:前后端编码或存储不一致
最常踩的坑是:后端把验证码存进 Session 时用了 key "captcha",前端提交时却传了字段名 verifyCode;或者 Session 超时时间比前端页面停留时间还短,用户填完再提交已失效。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 校验前先检查
request.getSession().getAttribute("key_used_when_set")是否为null,避免空指针同时暴露流程问题 - 别用
Captcha.getCode()直接比对——它返回的是原始字符串,而用户输入可能带空格或大小写混输;统一转大写 + 去空格再比:captcha.getCode().toUpperCase().trim().equals(input.toUpperCase().trim()) - 校验成功后立刻
removeAttribute(),防止一次验证码被多次使用
真正麻烦的不是生成图片,而是保证「生成 → 存储 → 传输 → 提交 → 校验」整条链路每个环节的字符和生命周期完全对齐。少一个 trim,少一次 remove,甚至 session timeout 设成 30 秒但用户看了 40 秒,都会让验证码看起来“不好使”。










