:管理员登录态保持相关优化)
这是一个系列 Blog作者将以一个 PHP 全栈工程师的身份利用 AI 工具claude code、codex、deepseek、豆包等从零开始学习 golang 语言并最终完成 ai-go-admingithub | gitee开源项目的制作全程记录分享。在上一期我们进行了 “管理员登录验证码开关”本期将完成管理员登录态保持优化、验权中间件管理员登录态保持相关优化管理员身份 token 的有效期默认是 3 天勾选保持登录时有效期是 30 天有效期目前是硬编码在源代码中的现在将其改为动态配置增加管理员token过期时间配置项默认值记住登录 30 天否则为 3 天于internal/service/admin/admin.go中应用这些配置项配置项加在config/config.yaml的token配置之下增加有效期字段AI 写好的核心代码如下人工review没发现啥问题# config\config.yaml 文件token:driver:databaseexpire:admin:72# 管理员 token 有效期单位小时3 天admin_remember:720# 管理员 token 有效期记住登录单位小时30 天登录接口内计算过期时间的逻辑修改// 计算过期时间expireCfg:config.Get().Token.Expire expiredAt:time.Now().Add(time.Duration(expireCfg.Admin)*time.Hour)ifreq.Remember{expiredAttime.Now().Add(time.Duration(expireCfg.AdminRemember)*time.Hour)}由于我们已经将token有效期改为动态配置前端登录页面的 “记住 30 天” 的选项就不合理了将其改为 “记住我” 即可。最后对 AI 写的代码进行实测直接从数据库检查勾选记住我之后的 token 过期时间是否正确。验权中间件接下来完成token的接受和验证按照社区惯例我选择使用中间件来完成这项工作先让 CC 做个初版出来看看效果再决定后续步骤帮我实现admin_auth中间件接受Authorization: Bearer token格式的token并使用token管理器 internal/infra/token/token.go 获取用户信息保存到请求上下文中。● 编译已通过。internal/middleware/admin_auth.go 实现了 - 提取 — 从 Authorization: Bearertoken头解析 token - 校验 — 通过 token.Instance().Get()查询数据库验证令牌存在且未过期SHA256 哈希在 manager 层自动处理 - 注入 — 将 user_id 和 token_type 存入 gin.Context下游通过常量取值 // 使用示例 r.Group(/admin).Use(middleware.AdminAuth())// handler 中读取 userID :c.GetUint(middleware.CtxUserID)tokenType :c.GetString(middleware.CtxTokenType)✻ Brewedfor47s必要的常量定义这里首先发现了一个题外的问题token 管理器中的token_type应该使用常量存储了比如这次做验权肯定会使用它四处硬编码并不合理这里先在token管理器内加上了token type常量目前就这一种token不过未来大概率会增加// internal\infra\token\token.go 文件// 令牌类型标识const(TypeAdminLoginadmin_login// 管理员登录令牌)两个验权中间件AdminAuth中间件正常的验权中间件它解析 token 没有 token 提示需要登录token 验证失败也需要登录白说了只要使用了该中间件不登录就是不行的AdminAuthOptional中间件可选的认证中间件有 token 则解析并注入管理员信息没有 token 也继续放行这样设计的目的是比如有的接口不登录也能访问但是如果你登录了我会响应更多数据。比如获取登录配置的接口已登录的我就直接跳转到后台不需要重复登录。…向请求上下文注入更多信息目前 AI 给的方案是将登录用户的user_id和token_type存入gin.Context下游通过常量取值userID:c.GetUint(middleware.CtxUserID)tokenType:c.GetString(middleware.CtxTokenType)我们的中间件名为AdminAuth明显是管理员专用的这里直接让它在中间件里边检查数据库内的token的type为TypeAdminLogin然后直接将对应管理员的整行数据获取到完整注入到上下文中。使用 AdminAuthOptional 中间件直接于获取登录配置接口/admin/login-config中用上AdminAuthOptional中间件这是可选登录中间件如果用户传递了 token并且认证成功Handler内就可以直接告诉用户不用登录了前端跳转到后台首页。token 检查不完善// 校验令牌tk,err:token.Instance().Get(c.Request.Context(),parts[1])iferr!nil||tknil{returnnil,身份认证令牌失效请重新登录}AI 错误的认为Get方法已经对 token 的有效期进行了检查但实际上没有额外加上有效期检查逻辑// 校验令牌tk,err:token.Instance().Get(c.Request.Context(),parts[1])iferr!nil||tknil||time.Now().After(tk.ExpiredAt){returnnil,身份认证令牌失效请重新登录}这里现场于数据内修改 token 有效期字段进行了测试确定 token 有效期检查逻辑无误、有效。完整代码我们能完美的描述需求和找到的问题AI解决起来也更加得心应手比如从 header 提取 token、数据Set(常量 key, value)到请求上下文等都没有问题中间件的代码如下// internal\middleware\admin_auth.gopackagemiddlewareimport(net/httpstringstimeai-go-mall/internal/infra/tokenai-go-mall/internal/modelrepoAdminai-go-mall/internal/repository/adminai-go-mall/internal/responsegithub.com/gin-gonic/gin)const(// 上下文中存储管理员信息的键CtxAdminKeyadmin)// 标识常量const(FlagNeedLoginneed_login// 需要登录FlagLoggedInlogged_in// 已经登录)// AdminAuth 管理员认证中间件未登录时阻断请求funcAdminAuth()gin.HandlerFunc{returnfunc(c*gin.Context){admin,msg:extractAdmin(c)ifadmin!nil{c.Set(CtxAdminKey,admin)c.Next()}else{response.Fail(c,response.WithMessage(msg),response.WithCode(http.StatusUnauthorized),response.WithData(gin.H{type:FlagNeedLogin}),)c.Abort()}}}// AdminAuthOptional 可选管理员认证中间件有 token 则注入管理员信息没有也放行funcAdminAuthOptional()gin.HandlerFunc{returnfunc(c*gin.Context){ifadmin,_:extractAdmin(c);admin!nil{c.Set(CtxAdminKey,admin)}c.Next()}}// GetAdmin 从上下文中取出管理员信息未登录时返回 nilfuncGetAdmin(c*gin.Context)*model.Admin{admin,ok:c.Get(CtxAdminKey)if!ok{returnnil}returnadmin.(*model.Admin)}// extractAdmin 提取并验证 token返回 (管理员信息, 错误消息)funcextractAdmin(c*gin.Context)(*model.Admin,string){// 提取请求 tokenauthHeader:c.GetHeader(authorization)parts:strings.SplitN(authHeader, ,2)iflen(parts)!2||!strings.EqualFold(parts[0],Bearer){returnnil,请先登录}// 校验令牌tk,err:token.Instance().Get(c.Request.Context(),parts[1])iferr!nil||tknil||time.Now().After(tk.ExpiredAt){returnnil,身份认证令牌失效请重新登录}// 仅允许管理员登录类型的令牌iftk.Type!token.TypeAdminLogin{returnnil,请重新登录}// 查询管理员信息adminRepo:repoAdmin.NewAdminRepository()admin,err:adminRepo.Get(c,tk.UserID)iferr!nil{returnnil,请重新登录}returnadmin,}