从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检

这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(claude code、codex、deepseek、豆包等):从零开始学习 golang 语言,并最终完成 ai-go-mall(github | gitee)开源项目的制作,全程记录分享。

在上一期,我们已经完成 "点选验证码初版、封装随机数与文件系统包",本期将完成:点选验证码代码逐行目检

点选验证码代码逐行目检

命名细节改动

以下结构能用,但是 ImageBase64 有 Image 前缀,而 Width 和 Height 也代表了最终图片的宽高,却不带 Image 前缀,让 AI 补上

go 复制代码
// ClickResult 创建验证码的返回结果
type ClickResult struct {
	Key         string   `json:"key"`
	Width       int      `json:"width"`
	Height      int      `json:"height"`
	Elements    []string `json:"elements"`
	ImageBase64 string   `json:"image_base64"`
}

可封装的函数

有一个用于读取背景图片和图标的 readDir 函数,可将其迁移至 pkg/filesystem 包,并改为支持递归读取子目录,获取所有文件的列表

go 复制代码
func readDir(dir string) ([]string, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	var paths []string
	for _, e := range entries {
		if !e.IsDir() {
			paths = append(paths, filepath.Join(dir, e.Name()))
		}
	}
	return paths, nil
}

变量异常流转

如下提取代码,有个 iconPaths 变量,在 Create 函数内读取到之后,会多处传递使用

go 复制代码
iconPaths, err := readDir(cfg.IconDir)

correctElements := generateElements(cfg.Length, cfg.Elements, iconPaths)
confusionElements := generateElements(cfg.ConfusionLength, cfg.Elements, iconPaths)
info, rect, drawErr := drawElement(bg, elem, bgW, bgH, iconPaths, placedRects)
drawIcon(bg, elem, bgW, bgH, iconPaths, placed)
  1. 改为验证码配置文件中(icon 路径配置处)声明一个 icons 数组,数组中存储 icon 文件名称、中文名称(通过文件名称翻译)、文件路径,全局读取后使用,无需四处传递
  2. Create 函数输出的 Elements 若为 icon,应使用中文名称,名称使用 <> 包裹,如 <图标中文名称>、<汉字>、<大写字母>

预感这几点修改会比较大,先让它做,之后重新 review

bash 复制代码
全部完成。四个问题的改动总结...

配置文件可读性优化

这一轮中,图标数据放到了配置文件中,但是不太好看:

yaml 复制代码
# 图标元数据
icons:
- name: "aeroplane"
    cn_name: "飞机"
- name: "apple"
    cn_name: "苹果"

让它重新以文件名为 en_name,中文名为 cn_name,且保留未来可能增加其他语言的 name 的可能性:

yaml 复制代码
  icons:
    - en_name: "aeroplane"
      cn_name: "飞机"
    - en_name: "apple"
      cn_name: "苹果"
    - en_name: "banana"
      cn_name: "香蕉"
    # ......

稍微好看了一点,不过还是将 icons 放在配置文件的最下面去,会好一些。

代码可读性优化

目前的 internal\infra\captcha\click.go 文件中,先于 Create(创建验证码函数) 内调用了 清理过期验证码图标初始化 函数;

然后在文件较下面的 init 函数中初始化了中文字符池,内容大概如下:

go 复制代码
pool := "的一是在不了有和人这中大为上个国我以要他..."
chineseRunes = []rune(pool)

相当于有个 init,但是又将 清理过期验证码图标初始化 这两样放在了 Create 才执行,这可能是我之前某句提示词误导了它,图标初始化 放到 Create 还勉强可以理解,清理过期验证码 其实是放 init 更合理的,这里干脆保持统一,初始化相关动作全部都放到 initCreate 不再单独初始化某些必要数据,而是直接使用,让其主打自己该做的工作。

同时,将 init 函数移到文件的最上面,提高人类的可阅读性(PS:initgo 语言中一个特殊的函数,当某包被导入时,会自动执行包的 init,有多个 init 函数时会按顺序执行)。

让 AI 修改时,它告诉我这样做是有原因的:

bash 复制代码
init() 执行时 config.Init() 和 database.Init() 尚未运行,但 icons 依赖配置、cleanExpired 依赖数据库,所以它们无法放入 init()。
最接近你意图的方案:用单个 sync.Once 引导函数统一三者,Create 只需一行调用。

AI 的意思是 init 时无法完成所有的初始化工作,因为 config.Init() 尚未调用,然后它实现了一个 bootstrap 函数,在 Create 开头调用一次,init 还是保留了中文字符池的初始化功能,相当于还是有两个初始化函数,这里干脆让它将 init 去掉,初始化工作全部放到 bootstrap 函数中完成,内部分为两部分,只需要执行一次的放入 sync.Once清理过期验证码 需要重复执行则不能放入 sync.Once

既然有统一的全局初始化函数了,再让 AI 将 cfg := config.Get().Captcha 中的 cfg 这类公共变量,也都提取为全局变量,不要四处单独赋值了。

理解代码

代码目测无结构问题,无多余封装,整体干净简洁,逻辑顺畅清晰,但是其中很多代码我也不能理解,比如:

go 复制代码
func loadImage(path string) (*image.RGBA, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	img, err := png.Decode(f)
	if err != nil {
		return nil, err
	}
	rgba := image.NewRGBA(img.Bounds())
	draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
	return rgba, nil
}

这是加载背景图片的函数,其中使用了 image.NewRGBAimg.Bounds()draw.Draw 等我从来没见过的东西,这里直接复制出来,让豆包给我解释一下。

我们的目标是利用 AI 学习 go 语言,出现看不懂的代码,正好是学习的时候了,而且作为开发者,我们最终也需要实现整个文件所有代码的理解,先干了什么,后干了什么,怎么干的,甚至做一些微调或个性化的改变,先理解,再人工微调:

go 复制代码
img, err := png.Decode(f)
if err != nil {
	return nil, err
}

// 改为

img, _, err := image.Decode(f)
if err != nil {
	return nil, err
}

// ==============================================

rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
return rgba, nil

// 改为

bounds := img.Bounds()
rgba := image.NewRGBA(bounds)
draw.Draw(rgba, rgba.Bounds(), img, bounds.Min, draw.Src)
return rgba, nil
相关推荐
meilindehuzi_a1 小时前
Vue3组件样式隔离与组件通信实战:从 scoped、props 校验到记事本应用
前端·javascript·vue.js
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-07-28
前端·神经网络·react.js·搜索引擎·前端框架
陈随易8 小时前
在 VSCode 里,把项目一键部署到服务器
前端·后端·程序员
Highcharts.js8 小时前
教程:基于 React + Highcharts 构建一个单页应用程序、按需数据拉取与图表渲染
开发语言·前端·数据结构·react.js·前端框架·highcharts·页面应用
To_OC9 小时前
我被 useState 坑了两次之后,终于把它的脾气摸透了
前端·javascript·react.js
鱼樱前端9 小时前
我用 Claude Code 后,编码效率翻 3 倍。但更值钱的是别的。
前端·后端·ai编程
犀利豆9 小时前
Claude code tools 研究系列-开篇(AskUserQuestion)
后端
_lucas9 小时前
给知识库网站接入AI问答
前端·ai编程·全栈
前端糕手10 小时前
前端面试题大全:JavaScript + Vue3 + React + TypeScript + 工程化 + 性能优化
前端
我不叫武11 小时前
一个用 Rust 写的离线编码查询桌面工具
前端