游乐游手机版
首页/AI热点日报/热点详情

PHP到AI+Go程序员转型:目录更新与token系统完善

类型:热点整理2026-07-02
上一期我们探讨了优化细节与网络请求封装的方法,本期继续推进——重点介绍目录结构更新和token系统的完善。在此之前,先分享一个有意思的小插曲。 对AI的小考验 在动手完善token系统之前,我特意给AI出了一道题:token作为管理员身份令牌,计划使用UUID v7生成值,那么入库时还有必要加密吗?

上一期我们探讨了优化细节与网络请求封装的方法,本期继续推进——重点介绍目录结构更新和token系统的完善。在此之前,先分享一个有意思的小插曲。

对AI的小考验

在动手完善token系统之前,我特意给AI出了一道题:token作为管理员身份令牌,计划使用UUID v7生成值,那么入库时还有必要加密吗?

AI的回答很干脆:没必要。它搬出一套理论——UUID v7结构是48位时间戳加74位随机数,每秒能生成2^74个不重复值,暴力枚举128位UUID在计算上不现实。还列举了一堆“哈希的代价”、“真正该加哈希的场景”之类的说辞。

这有点不太对劲。加密最大的意义从来不是防暴力枚举,而是防拖库。token一旦泄露,黑客就能直接登录后台;但如果我们加了加密,黑客必须同时拿到程序源码和token密文才能渗透进来。怎么能省掉这道安全屏障?

所以最终拍板:token入库必须走SHA256加密。

目录结构更新

当前数据库初始化函数放在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/

把database、token、captcha、upload这类底层模块移入infra。至于router和response——router是业务入口,response算是业务出口,都是可移可不移的类型,最终选择留在原地不动。

另外,配置解析逻辑(config/config.go文件,不是yaml配置文件)也跟着移入了infra目录,最终路径是internal/infra/config/config.go。这种操作在AI时代就是一句话的事儿,基本不会翻车,最多全项目搜一下/config确认就行。不过config/*.yaml文件不用动——运行时配置放在项目根目录的/config下,是社区标准的做法。

完善token系统

token系统的规划如下:

  1. internal/model/common.go建立Token模型,字段包括token、type(字符串)、user_id、创建时间、过期时间,每个字段带中文注释
  2. internal/infra/token/token.go建立token管理接口和结构体,采用多驱动模式。所有驱动放在internal/infra/token/driver目录下,一个驱动一个文件。目前只实现database一种驱动
  3. 增加token.driver配置项,默认值databasetoken.go读取驱动配置并返回对应的驱动实例
  4. 驱动需要实现CreateGetDeleteClear(删除指定会员指定类型的所有token)四个方法。管理器结构体额外实现Check方法——通过Get读取token信息后检查是否过期
  5. token入库前走SHA256加密

目前还没引入额外的全局秘钥(后续可能会考虑),也没用“SHA256索引+bcrypt校验”双字段方案。不过配合验权接口的节流逻辑,即便token的SHA256泄露了,抗爆破能力依然够用。

把这些规划交给Claude Code(cc),最终生成的核心代码如下:

# config/config.yaml 增加 token 驱动配置,目前只实现了 database 驱动
# 未来可以增加 redis 等驱动,得益于 AI 的帮助,加驱动基本上只需要一句话

token:
  driver: database
// internal/model/common.go 文件,用于存放 captcha、area 等公共模型

// Token 令牌模型,用于存储各类用户令牌
type Token struct {
    Token     string    `gorm:"comment:令牌;type:varchar(64);primaryKey" json:"-"`
    Type      string    `gorm:"comment:令牌类型;type:varchar(32);not null" json:"type"`
    UserID    uint      `gorm:"comment:用户ID;not null;index" json:"user_id"`
    CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"`
    ExpiredAt time.Time `gorm:"comment:过期时间;not null;index" json:"expired_at"`
}
// internal/infra/token/token.go 文件,用于存放 token 管理接口和结构体

package token

import (
    "context"
    "crypto/sha256"
    "fmt"
    "sync"
    "time"

    "ai-go-mall/internal/infra/config"
    "ai-go-mall/internal/infra/token/driver"
    "ai-go-mall/internal/model"
)

// Driver 令牌存储驱动接口
type Driver interface {
    Create(ctx context.Context, token *model.Token) error
    Get(ctx context.Context, token string) (*model.Token, error)
    Delete(ctx context.Context, token string) error
    Clear(ctx context.Context, userID uint, tokenType string) error
}

// Manager 令牌管理器
type Manager struct {
    driver Driver
}

// NewManager 创建令牌管理器
func NewManager(driver Driver) *Manager {
    return &Manager{driver: driver}
}

// Create 创建令牌,入库前自动对 Token 做 SHA256
func (m *Manager) Create(ctx context.Context, token *model.Token) error {
    token.Token = sha256Hex(token.Token)
    return m.driver.Create(ctx, token)
}

// Get 获取令牌信息
func (m *Manager) Get(ctx context.Context, token string) (*model.Token, error) {
    return m.driver.Get(ctx, sha256Hex(token))
}

// Check 检查令牌是否存在且未过期
func (m *Manager) Check(ctx context.Context, token string) bool {
    t, err := m.Get(ctx, token)
    if err != nil || t == nil {
        return false
    }
    return time.Now().Before(t.ExpiredAt)
}

// Delete 删除令牌
func (m *Manager) Delete(ctx context.Context, token string) error {
    return m.driver.Delete(ctx, sha256Hex(token))
}

// Clear 清除指定用户指定类型的所有令牌
func (m *Manager) Clear(ctx context.Context, userID uint, tokenType string) error {
    return m.driver.Clear(ctx, userID, tokenType)
}

// sha256Hex 返回 raw 的 SHA256 十六进制字符串
func sha256Hex(raw string) string {
    sum := sha256.Sum256([]byte(raw))
    return fmt.Sprintf("%x", sum)
}

// ==================== 全局单例 ====================

var (
    instance *Manager
    once     sync.Once
)

// Instance 返回全局令牌管理器实例,首次调用时根据配置自动初始化
func Instance() *Manager {
    once.Do(func() {
        instance = NewManager(newDriver(config.Get().Token.Driver))
    })
    return instance
}

// newDriver 根据配置创建存储驱动
func newDriver(name string) Driver {
    switch name {
    default:
        return driver.NewDatabase()
    }
}
// internal/infra/token/driver/database.go 文件,token 数据库驱动

package driver

import (
    "context"
    "errors"

    "ai-go-mall/internal/infra/database"
    "ai-go-mall/internal/model"

    "gorm.io/gorm"
)

// Database 基于关系型数据库的令牌驱动
type Database struct{}

// NewDatabase 创建数据库令牌驱动
func NewDatabase() *Database {
    return &Database{}
}

// Create 创建令牌
func (d *Database) Create(ctx context.Context, t *model.Token) error {
    return gorm.G[model.Token](database.DB()).Create(ctx, t)
}

// Get 获取令牌信息
func (d *Database) Get(ctx context.Context, token string) (*model.Token, error) {
    t, err := gorm.G[model.Token](database.DB()).
        Where("token = ?", token).
        First(ctx)
    if err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, nil
        }
        return nil, err
    }
    return &t, nil
}

// Delete 删除令牌
func (d *Database) Delete(ctx context.Context, token string) error {
    _, err := gorm.G[model.Token](database.DB()).Where("token = ?", token).Delete(ctx)
    return err
}

// Clear 清除指定用户指定类型的所有令牌
func (d *Database) Clear(ctx context.Context, userID uint, tokenType string) error {
    _, err := gorm.G[model.Token](database.DB()).
        Where("user_id = ? AND type = ?", userID, tokenType).
        Delete(ctx)
    return err
}

一段小总结:目录结构本次调整幅度不算大,主要是为后续多个基础设施模块铺平了道路。token系统的核心逻辑已经跑通,尤其是SHA256加密入库和Check过期检查,这两个点是后续所有鉴权流程的基础。下一期会聊中间件整合和验权接口的联调,感兴趣的朋友可以先思考一下:token管理器已经配好了,验权中间件该怎么做才能既安全又不显得笨重?

来源:https://segmentfault.com/a/1190000047950089

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。