GO学习笔记 | 第五章节 用户基本功能与 Gin|GORM 入门| 跨域、中间件与 GORM 入门
GO学习笔记 | 第五章节 用户基本功能与 Gin|GORM 入门| 跨域、中间件与 GORM 入门
核心内容:跨域问题(CORS)、中间件(Middleware)、GORM 增删改查、Docker Compose 启动数据库、项目分层设计
前置知识:Gin 路由注册、请求绑定、参数校验
一、跨域问题(CORS)

1.1 什么是跨域?

当你用浏览器打开前端页面,前端向后端发请求时,如果前端和后端的「家」不一样,浏览器就会阻止这次请求——这就是跨域。
怎么判断是不是「同一家」? 浏览器看三个东西:
| 判定维度 | 举例:同一家 | 举例:不是同一家 |
|---|---|---|
| 协议 | 都是 http:// |
http:// vs https:// |
| 域名 | 都是 localhost |
qq.com vs wechat.com |
| 端口 | 都是 :3000 |
:3000 vs :8080 |
三个只要有一个不同,浏览器就认为是跨域,直接拦住。
为什么浏览器要多管闲事?防止黑客在你的网页里植入恶意脚本,偷偷往别的服务器发请求。
怎么识别跨域错误? 打开浏览器 F12 → Console,看到 CORS 或 Access-Control-Allow-Origin 关键词,就是跨域问题。
1.2 跨域是怎么被拦住的?
前端发请求前,浏览器会先发一个**「试探请求」**(Preflight Request),问后端:
「喂,你是 localhost:8080 对吧?有个 localhost:3000 的页面想请求你,你接不接受?」
后端必须明确回答「我接受」,浏览器才会把真正的请求发过去。
Preflight 请求的特征:
- 请求方法是 OPTIONS(不是 GET/POST)
- 你在 Network 面板会看到同一个路径有两条请求:第一条是 OPTIONS(预检),第二条才是真正的 GET/POST
一句话总结:浏览器先派个侦察兵(OPTIONS)去问后端同不同意,同意了主力部队(真正的请求)才出发。

1.3 后端怎么告诉浏览器「我接受」?

后端在 OPTIONS 请求的响应里加上特殊的响应头:
1 | Access-Control-Allow-Origin: http://localhost:3000 |
逐个解释:
| 响应头 | 含义 |
|---|---|
Allow-Origin |
允许哪个前端来源访问。写 * 表示谁都行(有坑,见 1.4) |
Allow-Methods |
允许哪些 HTTP 方法 |
Allow-Headers |
允许请求带哪些自定义头 |
Allow-Credentials |
是否允许携带 Cookie / Authorization |
Max-Age |
这次预检的有效期(秒),有效期内部再重复预检 |
1.4 ⚠️ Allow-Origin: * 的坑
1 | // 有风险:当 Allow-Credentials = true 时,不能用 * |
正确做法:要么用具体的 origin(如 http://localhost:3000),要么动态获取请求的 origin 并写到响应头里。
用函数动态返回 origin,一行代码搞定,灵活可控。

图中就是用的函数动态返回origin
规范上,只有“当 Allow-Credentials 为 true 时,才不能使用 \*”。如果 Allow-Credentials 是 false(或者不加这个头),使用 \* 是合法的。
不过在实际业务场景中,这个担忧是合理的,因为只要涉及跨域携带 Cookie 或 Token,Credentials 就必须为 true。一旦这个开关开了,通配符 * 就会直接被浏览器拦截。
1. 正确且严谨的 CORS 规则
Allow-Credentials |
Allow-Origin |
浏览器行为 | 说明 |
|---|---|---|---|
false (或不传) |
\* |
✅ 正常通过 | 允许所有来源,但不带凭证。 |
true |
\* |
🚫 拒绝执行 | 这是安全禁令,浏览器会报你图片里的错误。 |
true |
http://localhost:3000 |
✅ 正常通过 | 必须指定具体的、精确的源。 |
2. 为什么 true 绝对不能配合 *?
这是非常硬核的安全限制。
- 如果
Access-Control-Allow-Origin: *,意味着任何网站(哪怕是一个恶意钓鱼网站http://evil.com)都可以向你的服务器发请求。 - 如果同时
Access-Control-Allow-Credentials: true,意味着任何网站发来的请求,浏览器都会自动带上用户登录在当前浏览器里的 Cookie。 - 结果:一个恶意网站只要贴一张
<img>标签或者写一段 JS 脚本,就可以利用用户之前登录过的身份,向你的服务器发起攻击(比如 CSRF 跨站请求伪造,或者盗取数据)。这是绝对不允许的。
1.5 Gin 中解决跨域:官方 CORS 中间件


Gin 提供了现成的 CORS 插件,不需自己造轮子:
1 | import ( |
关键点:
AllowOrigin要和前端请求头里的Origin一一对应AllowHeaders要和前端请求头里的自定义 Header 一一对应- 配完后在 Network 面板看 OPTIONS 请求是否返回
204 No Content,是就说明跨域搞定了


二、中间件(Middleware)

2.1 中间件是干什么的?
把中间件想象成安检通道:
1 | 浏览器请求 → [安检1] → [安检2] → 你的业务逻辑 → 响应 |
所有请求都要先经过安检,安检通过了才能到达你的业务代码。所有业务都关心的公共问题(如跨域、登录校验、日志、限流),用中间件统一解决,不需要每个接口都写一遍。
这种编程思想叫 AOP(面向切面编程)——把横切关注点集中处理。
2.2 中间件 vs 路由处理函数

1 | // 路由处理函数:只作用于这一条路由 |
ctx.Next()是关键的枢纽——调用它,请求才会继续往后走。不调用ctx.Next(),请求就被拦截了。
2.3 中间件的执行顺序
1 | server.Use(middleware1) // 先注册 |
先注册的先执行,和叠洋葱一样——一层层进去,再一层层出来。

2.4 怎么用现成的中间件?
Gin 官方维护了一系列中间件:github.com/gin-contrib
1 | # 以 CORS 中间件为例 |
然后在代码里 server.Use(cors.New(...)) 即可。
官方中间件质量参差不齐,有些有并发问题,用之前最好看一眼源码。目前 CORS 中间件是可靠的。
三、GORM 入门

3.1 什么是 GORM
GORM 是 Go 语言最流行的 ORM 框架(对象关系映射)。简单说就是:用 Go 的结构体来操作数据库,不用手写 SQL。

- 支持 MySQL、PostgreSQL、SQLite、SQL Server 等
- 支持增删改查、事务、关联关系、自动建表
- 有完整的中文文档
模型定义

1 | //定义表结构 |

3.2 快速起步

泛型 API (>= v1.30.0)
1 | package main |
传统 API
1 | package main |
3.3 Debug 模式
1 | db = db.Debug() // 开启后,每次数据库操作都会打印 SQL 到控制台 |
开发阶段一定要开! 可以看到 GORM 实际生成的 SQL,确保和预期一致。
3.4 MySQL 的连接字符串
1 | 用户名:密码@tcp(主机:端口)/数据库名?charset=utf8mb4&parseTime=True |
示例:root:root@tcp(localhost:13316)/vbook?charset=utf8mb4&parseTime=True
1 | [username[:password]@] [protocol[(address) ]]/dbname[?param1=value1& ...¶mN=valueN] |
1 | //这是博主的 |
不需要死记。复制公司已有项目的配置,改账号密码就行。
3.5 AutoMigrate:自动建表
1 | db.AutoMigrate(&User{}, &Article{}) |
- 表不存在 → 建表
- 表已存在 → 同步字段(新增列)
- 不会删除已有列,也不支持改名
- 生产环境慎用,一般只在开发/测试中用
3.6 软删除
GORM 默认开启软删除:执行 Delete 时并不真正删除数据,而是设置 deleted_at 字段为当前时间。
1 | db.Delete(&product) // 实际 SQL: UPDATE products SET deleted_at=NOW() WHERE id=? |
老师观点:ORM 框架不应该内置软删除,这应该是应用层自己封装的事情。
3.7 GORM 的「坑」:Builder 模式返回值
1 | db = db.Debug() // Debug 返回的是新的 DB 实例,必须重新赋值! |
所有链式调用都要记得把返回值接住。
3.8 学习方法

- GORM 中文文档很全面,不需要死记硬背
Where、First、Update等方法大多接受interface{},传 string、map、struct 行为不同- 一切以 SQL 输出为准——打开 debug 模式,看生成的 SQL 是否符合预期
四、Docker Compose 启动 MySQL

4.1 为什么用 Docker
不用在本机装 MySQL,一行命令就能起一个干净的环境,用完就停,不污染系统。
4.2 配置文件:docker-compose.yaml
1 | version: '3.0' |
关键点:
ports的格式是本机端口:容器端口volumes可挂载初始化脚本:容器启动时自动执行init.sql
4.3 init.sql 示例
1 | create database webook; |
4.4 常用命令

1 | # 启动(前台,能看到日志) |
怎么确认启动成功? 看到日志中 ready for connections 就表示 MySQL 就绪了。
可能遇到的问题
执行up命令的时候失败了,可能原因是你没有启动docker,需要先启动。另外拉取mysql的时候需要等待一段时间,这是成功后的截图

4.5 GoLand 连接数据库
GoLand 内置数据库工具:右侧边栏 → Database → + → Data Source → MySQL
填写连接信息:
- Host:
localhost - Port:
13316(和 docker-compose 里映射的一致) - User:
root - Password:
你的密码(和docker-ccompose里面一致) - Database:
webook
点 Test Connection → 看到 Succeeded 即成功。
注意:
如果要让你下载mysql驱动的话那就直接下载,不用有顾虑
然后测试连接一定要弄通顺,注意端口是13316不是3306,注意密码是和配置文件里面的一样而不是和你电脑上面那个mysql的密码一样
五、项目分层设计




| 包 | 职责 | 举例 |
|---|---|---|
domain |
业务领域对象,和真实世界对应 | User、Article |
repository |
数据存储的抽象接口 | UserRepository 定义 FindByID |
dao |
具体的数据库操作 | UserDAO 用 GORM 实现查询 |
service |
组合各种 repository,完成业务功能 | UserService 调用 UserRepository + Cache |
核心思想:
repository只关心「存数据」这件事,不关心存到哪(DB、缓存、文件、甚至 RPC 调用)dao才是真正操作数据库的地方service是总指挥,调度资源完成业务domain中的对象可能和数据库表结构不一样
六、核心概念速查
| 概念 | 一句话解释 |
|---|---|
| 跨域 | 前端和后端不同源,浏览器不让发请求 |
| Preflight (OPTIONS) | 浏览器先问后端「同不同意」,同意了才发真正请求 |
| CORS 中间件 | 后端在响应里加上 Allow-Origin 等头,告诉浏览器「我同意」 |
| Middleware | 请求和业务逻辑之间的安检通道,解决所有路由的公共问题 |
ctx.Next() |
中间件里放行请求去下一站 |
| ORM | 用结构体操作数据库,不用手写 SQL |
| AutoMigrate | 自动根据结构体建表或更新表结构 |
| 软删除 | Delete 时只标记删除时间,不真删数据 |
| Docker Compose | 一键启动 MySQL,用完即停 |












