从 PHP 到 AI + Golang,程序员自救转型手记(十六):目录结构更新、完善 token 系统(AI 表示 token 入库无需加密?)

📅 2026/7/2 5:02:52
从 PHP 到 AI + Golang,程序员自救转型手记(十六):目录结构更新、完善 token 系统(AI 表示 token 入库无需加密?)
这是一个系列 Blog作者将以一个 PHP 全栈工程师的身份利用 AI 工具claude code、codex、deepseek、豆包等从零开始学习 golang 语言并最终完成 ai-go-mallgithub | gitee开源项目的制作全程记录分享。在上一期我们已经完成 “优化细节、网络请求封装”本期将完成目录结构更新、完善 token 系统对 AI 的小考验今天在完善 token 系统之前特意考验了一下 AI token 作为管理员身份令牌其值生成打算使用UUID v7入库是否还有必要加密不需要。 UUID v7 的安全性已足够 UUID v7 结构为48位时间戳 74位随机数每秒可生成2^74 ≈1.8×10²² 个不重复值。暴力枚举一个128位的 UUID 在计算上不可行。 哈希的代价巴拉巴拉... 真正该加哈希的场景巴拉巴拉...我去这不对吧加密最大的意义是防止被拖库token 泄露黑客直接就登录到后台了而有加密的话黑客必须同时获得程序源码和 token 密文才能入侵到后台怎么能不加密呢目录结构更新新增 infra 目录目前我们的数据库初始化函数放在internal/database/database.go文件中考虑到接下来要增加的 token多驱动设计、captcha、upload多驱动设计等按原来的计划我们的目录结构未来会变成├── internal/ │ ├── handler/ │ ├── model/ │ ├── repository/ │ ├── router/ │ ├── database/ │ ├── captcha/ │ ├── upload/ │ ├── response/ │ ├── middleware/ │ └── service/其中的 database、token、captcha、upload 非常的偏底层和 handler、model 等业务层的有点不搭所以我决定再加一层infra目录存放基础设施新的目录结构如下├── internal/ │ ├── infra/ │ │ ├── token/ │ │ │ ├── driver │ │ │ │ ├── database.go │ │ │ │ └── redis.go │ │ │ └─── token.go │ │ ├── captcha/ │ │ └── upload/ │ ├── handler/ │ ├── model/ │ ├── repository/ │ ├── router/ │ ├── response/ │ ├── middleware/ │ └── service/即新增infra目录将database、token、captcha、upload这类偏底层的放于其中router和response我没有选择移进去其中router和业务层的放在一起是合理的它是业务入口response算是业务出口属于可移可不移选择不移。配置解析config.go 文件移入 infra 目录项目是一个应用非库而 config 模块是项目内部实现细节且属于带状态的基础设施现在我们已经建立了infra目录那么配置解析逻辑config/config.go文件非yaml配置文件最理想的存放位置当然是internal\infra\config\config.goAI时代我们直接让 cc 将其移入其中即可这类需求只需要一句话无需人工找文件替换基本不会出问题最多全项目搜索 /config 确定一下。PSconfig/*.yaml 无需移动该文件属于运行时配置放在 /config 是符合社区习惯的做法。完善 token 系统token 系统规划如下于 internal/model/common.go 建立 token 模型有 token、type字符串、user_id、创建时间、过期时间字段字段全部带有中文注释于 internal/infra/token/token.go 建立 token 管理接口和结构体使用多驱动模式所有驱动存放于internal/infra/token/driver目录一个驱动一个文件目前实现 database 一种驱动增加token.driver配置项默认值为databaseinternal/infra/token/token.go内读取驱动配置返回对应驱动的 token 管理实例。token 驱动实现Create、Get、Delete、Clear删除指定会员指定类型的所有token方法token 管理器的结构体除驱动的所有方法外额外实现Check方法使用 Get 方法读取 token 信息后检查是否过期token 入库使用 SHA256 加密目前 token 系统还未考虑额外的全局秘钥后续应该会考虑也未考虑【SHA256 索引 bcrypt 校验】双字段方案但配合验权接口节流就算泄露了 Token SHA256抗爆破能力还是够的将之前的规划发给 cc最终核心代码如下# config/config.yaml 增加 token 驱动配置目前只实现了 database 驱动# 未来可以增加 redis 等驱动得益于 AI 的帮助加驱动基本上只需要一句话token:driver:database// internal\model\common.go 文件用于存放 captcha、area 等公共模型// Token 令牌模型用于存储各类用户令牌typeTokenstruct{Tokenstringgorm:comment:令牌;type:varchar(64);primaryKey json:-Typestringgorm:comment:令牌类型;type:varchar(32);not null json:typeUserIDuintgorm:comment:用户ID;not null;index json:user_idCreatedAt time.Timegorm:comment:创建时间 json:created_atExpiredAt time.Timegorm:comment:过期时间;not null;index json:expired_at}// internal\infra\token\token.go 文件用于存放 token 管理接口和结构体packagetokenimport(contextcrypto/sha256fmtsynctimeai-go-mall/internal/infra/configai-go-mall/internal/infra/token/driverai-go-mall/internal/model)// Driver 令牌存储驱动接口typeDriverinterface{Create(ctx context.Context,token*model.Token)errorGet(ctx context.Context,tokenstring)(*model.Token,error)Delete(ctx context.Context,tokenstring)errorClear(ctx context.Context,userIDuint,tokenTypestring)error}// Manager 令牌管理器typeManagerstruct{driver Driver}// NewManager 创建令牌管理器funcNewManager(driver Driver)*Manager{returnManager{driver:driver}}// Create 创建令牌入库前自动对 Token 做 SHA256func(m*Manager)Create(ctx context.Context,token*model.Token)error{token.Tokensha256Hex(token.Token)returnm.driver.Create(ctx,token)}// Get 获取令牌信息func(m*Manager)Get(ctx context.Context,tokenstring)(*model.Token,error){returnm.driver.Get(ctx,sha256Hex(token))}// Check 检查令牌是否存在且未过期func(m*Manager)Check(ctx context.Context,tokenstring)bool{t,err:m.Get(ctx,token)iferr!nil||tnil{returnfalse}returntime.Now().Before(t.ExpiredAt)}// Delete 删除令牌func(m*Manager)Delete(ctx context.Context,tokenstring)error{returnm.driver.Delete(ctx,sha256Hex(token))}// Clear 清除指定用户指定类型的所有令牌func(m*Manager)Clear(ctx context.Context,userIDuint,tokenTypestring)error{returnm.driver.Clear(ctx,userID,tokenType)}// sha256Hex 返回 raw 的 SHA256 十六进制字符串funcsha256Hex(rawstring)string{sum:sha256.Sum256([]byte(raw))returnfmt.Sprintf(%x,sum)}// 全局单例 var(instance*Manager once sync.Once)// Instance 返回全局令牌管理器实例首次调用时根据配置自动初始化funcInstance()*Manager{once.Do(func(){instanceNewManager(newDriver(config.Get().Token.Driver))})returninstance}// newDriver 根据配置创建存储驱动funcnewDriver(namestring)Driver{switchname{default:returndriver.NewDatabase()}}// internal\infra\token\driver\database.go 文件token 数据库驱动packagedriverimport(contexterrorsai-go-mall/internal/infra/databaseai-go-mall/internal/modelgorm.io/gorm)// Database 基于关系型数据库的令牌驱动typeDatabasestruct{}// NewDatabase 创建数据库令牌驱动funcNewDatabase()*Database{returnDatabase{}}// Create 创建令牌func(d*Database)Create(ctx context.Context,t*model.Token)error{returngorm.G[model.Token](database.DB()).Create(ctx,t)}// Get 获取令牌信息func(d*Database)Get(ctx context.Context,tokenstring)(*model.Token,error){t,err:gorm.G[model.Token](database.DB()).Where(token ?,token).First(ctx)iferr!nil{iferrors.Is(err,gorm.ErrRecordNotFound){returnnil,nil}returnnil,err}returnt,nil}// Delete 删除令牌func(d*Database)Delete(ctx context.Context,tokenstring)error{_,err:gorm.G[model.Token](database.DB()).Where(token ?,token).Delete(ctx)returnerr}// Clear 清除指定用户指定类型的所有令牌func(d*Database)Clear(ctx context.Context,userIDuint,tokenTypestring)error{_,err:gorm.G[model.Token](database.DB()).Where(user_id ? AND type ?,userID,tokenType).Delete(ctx)returnerr}