程序员转型实战:登录接口完善与跨域整合
时间:2026-07-23 | 作者:火苗实验室 | 阅读:0
# 登录接口与前端对接实战:DB Token、跨域与最佳实践
这个系列博客,我会以 PHP 全栈工程师的身份,借助 AI 工具(Claude Code、Codex、DeepSeek、豆包等),从零开始学习 Go 语言,完成 ai-go-mall 开源项目,全程记录分享。
上期任务完成了目录结构更新和 Token 系统完善。
这期要落地的事情更加偏实战:完成登录接口、对接前端登录页面,以及解决跨域问题。
---
## 登录接口完善
### 为什么坚持用 DB Token 而不是 JWT?
聊聊一个老生常谈,但又常常被忽略的问题:业务系统中,JWT 到底适不适合做后台管理系统的会话方案?
**先说结论——它不适合。** 理由其实挺硬的:
1. **JWT 无法即时作废**。这是最大的硬伤。一旦 token 泄露,你没法立刻吊销,只能干瞪眼。**仅此一条,后台管理系统就不该碰 JWT。**
2. **数据只是 Base64 编码**,不是加密。任何人拿到 token,解码后就能看到用户 ID、角色之类的信息。这不是安全,这叫掩耳盗铃。
3. **续期逻辑别扭**。要么搞滑动续期,要么整双 Token(Access + Refresh)方案,复杂度蹭蹭往上涨。
4. **密钥管理一旦出问题**,攻击者可以直接伪造任意用户的令牌。到时候整个系统的信任体系就崩塌了。
有人可能会说:那我在服务器端加个二次验证,再维护一个黑名单?
这样一来,JWT 的无状态特性就彻底没了,何必还要用 JWT 呢?
不如直接用有状态的 **DB Token**,既能即时作废,控制权也完全在自己手里。
---
### 登录接口优化规划
明确了技术选型,接下来的实现就很清晰了:
1. 在 `@internal/service/admin/admin_service.go` 的 `LoginRequest` 中增加 `remember`(记住我)字段。
2. 登录成功时,用 `github.com/google/uuid` 的 `NewV7` 方法生成 token 值,并通过 Token 管理器创建记录。
3. `remember` 为 `true` 时,token 有效期 30 天,否则 3 天(未来可考虑做成可配置项)。
需求很明确,跟 AI 沟通后,核心改动集中在两个方面:**新增 `uuid` 依赖**,以及在登录成功逻辑里插入 Token 生成代码。
```go
// 增加导入
import (
"time"
"ai-go-mall/internal/infra/token"
"github.com/google/uuid"
)
// 登录方法中,登录成功后增加生成令牌的代码
tokenStr := uuid.Must(uuid.NewV7()).String()
// 计算过期时间:记住登录为 30 天,否则为 3 天
expiredAt := time.Now().Add(3 * 24 * time.Hour)
if req.Remember {
expiredAt = time.Now().Add(30 * 24 * time.Hour)
}
// 创建令牌
tk := &model.Token{
Token: tokenStr,
Type: "admin_login",
UserID: admin.ID,
CreatedAt: time.Now(),
ExpiredAt: expiredAt,
}
if err := token.Instance().Create(c.Request.Context(), tk); err != nil {
return nil, errors.New("保存令牌失败")
}
```
---
## 登录页接口整合
### 对接步骤与初版实现
前端对接有几个关键步骤:
1. 在 `web/src/api/admin` 目录下创建登录 API 请求函数。
2. 在 `web/src/views/admin/login.vue` 中调用登录请求。
3. 登录成功后,把后端返回的数据填充到 `adminInfo` 状态商店。
4. 成功则跳转到 `/admin`。
为了让后续测试顺利,我手工给数据库加了一条管理员记录,密码直接用 AI 生成密文(提示词:使用 `bcrypt.CompareHashAndPassword`,生成密码 "123456" 的密文)。这招挺省事的。
初版跑通后,对变更做了简单总结:
- 新增 `web/src/api/admin/login.ts`,定义了 `LoginRequest` 和 `LoginResponse` 接口,用 `URLSearchParams` 提交(application/x-www-form-urlencoded 格式)。
- 更新 `web/src/api/admin/login.ts`,原来占位的 `setTimeout` 替换为真实的 API 调用和状态填充逻辑。
---
### 第二轮人工 Review 与优化
代码能跑只是第一步,质量是另一回事。Review 后整理了几条优化点:
1. **文件名 `login.ts` 改成 `index.ts`**,后续登录、首页等公共接口都放进来,命名更合理。
2. **删除 `LoginRequest` 和 `LoginResponse` 两个接口**。参数类型由 `login.vue` 自己提供,无需重复定义;响应类型如果不被复用,也无需单独声明。
3. **调用 API 时改用 `.then()` 链式语法**,替代 `await`,代码更清爽。
4. **`useAdminInfo` 在组件顶层提前定义**,避免在 `onSubmit` 里反复调用。
5. **失败处理交给 `request` 拦截器自动提示**,不用单独 `catch`。
改完后,逻辑更加清晰紧凑。但浏览器一测试,又翻车了——**跨域问题**跳了出来。
---
## 解决跨域问题
这时候我有个习惯:保持工作区干净。
先用 `git stash` 暂存现有改动(记得先 `add`,因为新增文件需要用 `add` 才能一起储藏),然后开始解决跨域。
经过对比,最终选了 **`gin-contrib/cors`** 这个库,社区活跃,Star 多。给 AI 的提示词也很明确:
1. 整合 `gin-contrib/cors`。
2. 新建配置文件,存放允许跨域的域名,默认允许所有请求头和请求方法,预检请求缓存 24 小时。
3. 单独建立跨域中间件文件,在 `api/main.go` 注册。
Review 时发现 AI 把允许的请求方法列了一大串——`GET, POST, PUT, DELETE...` 逐条写。这哪有直接允许所有方法来得干脆?于是追问了一句:**能不能用 `*` 代替?**
接下来又整理了一轮优化点:
- 把 `AllowMethod`、`AllowHeaders`、`MaxAge` 等配置项都放到 `config/cors.yaml` 里。
- 默认只允许 `localhost` 和 `127.0.0.1`,不搞 `*`。
- 域名不写协议,中间件自动同时处理 `http` 和 `https`。
这里有个挺关键的坑:**AI 对默认值的理解是硬编码在代码里**——如果配置文件为空,代码里就给一组硬编码默认值。但我的期望是:默认值全部放在 `config/cors.yaml` 中,如果清空了某项配置,就代表不允许该项。举个例子:
```go
// 这是 AI 的写法,存在隐患
methods := cfg.Methods
if len(methods) == 0 {
methods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}
}
```
如果我清空了 `Methods` 配置,它反而又开启了全量方法,这就违背了配置的初衷。
**Review 的重要性在这里体现得淋漓尽致**——跨域配置看似小事,一旦出错,轻则功能异常,重则造成数据泄露或权限提升。
---
### 继续测试,发现新问题
解决完跨域配置,用 `git stash pop` 恢复工作现场,再次测试——**还是跨域**。
排查后发现是端口号的问题——gin-contrib/cors 配置域名时,需要带上端口号。于是让 AI 调整配置:允许某个域名的所有端口。
测试终于通过了,但人工测试又发现了两个问题。
---
**问题一:请求绑定方式不匹配**
```go
type LoginRequest struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
Remember bool `form:"remember"`
}
```
前端传的是 `application/json`,但结构体用的却是 `form` 绑定。虽然用 `ShouldBind` 时会自动选择绑定引擎,但 tag 不匹配终究不够规范。修改如下:
```go
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Remember bool `json:"remember"`
}
```
---
**问题二:JSON 字段命名风格不统一**
服务端输出的字段是 `PascalCase`,比如 `Username`、`CreatedAt`,跟前端状态商店的字段名对不上。行业标准是 `snake_case`。暂时不建 DTO,直接让 AI 修改模型的 json tag:
```go
type Admin struct {
ID uint `gorm:"comment:ID;primarykey;autoIncrement" json:"id"`
Username string `gorm:"comment:用户名;type:varchar(64)" json:"username"`
Nickname string `gorm:"comment:昵称;type:varchar(64)" json:"nickname"`
A vatar string `gorm:"comment:头像;type:varchar(255)" json:"a vatar"`
Email string `gorm:"comment:邮箱;type:varchar(128)" json:"email"`
Mobile string `gorm:"comment:手机号;type:varchar(16)" json:"mobile"`
LoginFailure uint `gorm:"comment:连续登录失败次数;not null;default:0" json:"-"`
LastLoginAt time.Time `gorm:"comment:上次登录时间" json:"last_login_at"`
LastLoginIP string `gorm:"comment:上次登录IP;type:varchar(64)" json:"last_login_ip"`
Password string `gorm:"comment:密码;type:varchar(255)" json:"-"`
Bio string `gorm:"comment:个人简介;type:varchar(255)" json:"bio"`
Status string `gorm:"comment:状态:enable=启用,disable=禁用;type:varchar(64)" json:"status"`
UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"`
CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"`
DeletedAt gorm.DeletedAt `gorm:"comment:删除时间;index" json:"-"`
}
```
字段调整后,敏感字段(如 `password`、`login_failure`)也用了 `json:"-"` 做了忽略。
---
**问题三:状态商店的数据填充存在问题**
`dataFill` 方法用 `this.$patch(state)` 批量填充时,如果后端返回了 `AdminInfo` 中不存在的字段(比如 `email`),也会偷偷写入 store。需要做**白名单过滤**。
一开始 AI 给的方案是手动写 forEach,但项目已经引入了 `lodash-es`,完全可以用现成的函数。最终采用了更优雅的写法:
```ts
import { pick, difference } from 'lodash-es'
const allowedKeys = difference(Object.keys(this.$state), exclude)
this.$patch(pick(state, allowedKeys))
```
一行搞定白名单过滤+字段排除,比手写 forEach 和 includes 简洁多了,可读性也更好。
---
## 总结
这一期从登录接口的技术选型,到前后端对接,再到跨域配置,最后落到代码质量和状态管理细节,每一步都有意外,每一步也都有收获。
**Review 的价值在这一期体现得特别充分**——AI 能帮你写代码,但能不能写对、写得好,最终还是得靠人把关。
下期将继续推进项目,欢迎持续关注。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- PHP创建数据库的SQL语句教程
- 时间:2026-08-23
-
- PHP安全替换配置文件内容的方法与实战技巧
- 时间:2026-08-20
-
- PHP后台管理系统开发从入门到精通实战指南
- 时间:2026-08-19
-
- PHP中如何验证含星号通配符的括号字符串是否有效
- 时间:2026-08-18
-
- PHP中如何验证带星号通配符的括号字符串是否有效
- 时间:2026-08-18
-
- PHP中按分号分割含嵌套分号的多条SQL语句方法
- 时间:2026-08-18
-
- PHP循环中下拉框选项变更事件绑定方法及键盘导航支持
- 时间:2026-08-18
-
- PHP表单提交多个选中月份并显示对应数据的方法
- 时间:2026-08-18
精选合集
更多大家都在玩
大家都在看
更多-
- 为什么湿头发更容易断裂 蚂蚁庄园今日答案9.15
- 时间:2026-09-14
-
- 蚂蚁庄园今天答题答案2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园答题今日答案2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园小课堂2026年9月15日最新题目答案
- 时间:2026-09-14
-
- 小鸡答题今天的答案是什么2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园每日答题答案2026年9月15日
- 时间:2026-09-14
-
- 糖尿病患者禁食所有含糖食物吗 蚂蚁庄园今日答案9月15日
- 时间:2026-09-14
-
- 2026年9月14日蚂蚁新村答案
- 时间:2026-09-14