Files
AI-CS/doc/后端学习笔记.md
T
2025-11-17 18:05:00 +08:00

18 KiB
Raw Blame History

后端学习笔记(Go + Gin + GORM

一、核心概念理解

1. Gin 框架(Web框架)

类比:就像餐厅的服务员,接收客人的请求,然后告诉厨房(处理函数)做什么菜

  • gin.Default() = 创建一个 Gin 服务器(就像开一家餐厅)
  • r.POST("/路径", 处理函数) = 注册一个路由(就像在菜单上写:客人点这个菜,叫这个厨师做)
  • r.Run(":8080") = 启动服务器,监听 8080 端口(就像开门营业)

为什么需要?

  • 没有框架:需要自己处理 HTTP 请求、解析 JSON、路由等(很麻烦)
  • 有了框架:只需要写业务逻辑,框架帮你处理其他事情(简单)

2. GORMORM框架)

类比:就像翻译官,帮你把 Go 代码翻译成 SQL 语句

  • db.Create(&user) = 插入数据(翻译成 INSERT INTO users ...
  • db.Where("username=?", name).First(&user) = 查询数据(翻译成 SELECT * FROM users WHERE username=?
  • db.AutoMigrate(&User{}) = 自动创建表(根据结构体自动建表)

为什么需要?

  • 不用 GORM:需要手写 SQL 语句(容易出错,不安全)
  • 用了 GORM:用 Go 代码操作数据库,自动生成 SQL(安全、简单)

3. 环境变量(.env 文件)

类比:就像餐厅的配置表,记录"数据库地址"、"密码"等敏感信息

  • godotenv.Load() = 从 .env 文件读取配置
  • os.Getenv("DB_HOST") = 获取环境变量的值

为什么需要?

  • 不写在代码里:密码等信息不会泄露,不同环境可以不同配置
  • 写在代码里:密码硬编码,不安全,换环境要改代码

4. 中间件(Middleware

类比:就像餐厅的"检查站",每个请求都要经过这些检查

  • r.Use(middleware.Logger()) = 记录每个请求的日志(就像记录每个客人点了什么)
  • r.Use(middleware.CORS()) = 允许跨域请求(就像允许其他网站访问我们的 API)

为什么需要?

  • 每个路由都要写日志、跨域处理等代码(重复)
  • 用中间件:统一处理,代码简洁

5. Handler(处理函数)

类比:就像餐厅的厨师,负责处理具体的业务逻辑

  • func Register(db *gorm.DB) gin.HandlerFunc = 注册用户的处理函数
  • c.ShouldBindJSON(&u) = 把请求的 JSON 数据解析成结构体
  • c.JSON(200, gin.H{"message": "成功"}) = 返回 JSON 响应给前端

为什么需要?

  • 接收前端请求 → 处理业务逻辑 → 返回结果

6. 初始化默认数据

类比:就像餐厅开业时,自动准备一些基础食材

  • initDefaultAdmin(db) = 在首次启动时自动创建默认管理员账号
  • 检查数据库是否已有管理员,如果没有则创建

为什么需要?

  • 系统首次启动时,需要有一个管理员账号才能登录
  • 避免手动创建管理员账号的麻烦

二、常用英文单词记忆

英文 中文意思 记忆技巧
handler 处理函数 记住:handler = 处理者(处理请求的人)
middleware 中间件 记住:middle(中间)+ ware(件)= 中间件
router 路由 记住:router = 路由器(把请求路由到对应的处理函数)
context 上下文 记住:context = 上下文(包含请求和响应的信息)
migrate 迁移 记住:migrate = 迁移(把结构体迁移成数据库表)
query 查询 记住:query = 查询(从数据库查数据)
bind 绑定 记住:bind = 绑定(把 JSON 绑定到结构体)
hash 哈希 记住:hash = 哈希(密码加密)
bcrypt 加密算法 记住:bcrypt = 密码加密算法(B + crypt = 加密)
env 环境变量 记住:env = environment(环境)的缩写
error 错误 记住:error = 错误(Go 语言中常用)
return 返回 记住:return = 返回(返回结果或退出函数)

三、代码执行流程(就像讲故事)

1. 服务器启动(main.go

1. 加载 .env 文件(读取数据库配置)
2. 连接数据库(NewDB
3. 自动创建表(AutoMigrate
4. 初始化默认管理员(initDefaultAdmin
5. 创建 Gin 服务器(gin.Default
6. 注册中间件(Logger、CORS
7. 注册路由(POST /login, POST /conversation/init, GET /conversations, PUT /conversations/:id/contact 等)
8. 启动服务器(监听 8080 端口)

2. 处理一个请求(比如登录)

1. 前端发送 POST /login 请求
2. Gin 框架接收请求
3. 经过中间件(记录日志、允许跨域)
4. 找到对应的处理函数(Login
5. 解析 JSON 数据(ShouldBindJSON
6. 查询用户是否存在(db.Where
7. 验证密码(bcrypt.CompareHashAndPassword
8. 返回 JSON 响应(包含 user_id、username、role
9. 前端收到响应,保存到 localStorage

3. 处理一个请求(比如获取对话列表)

1. 前端发送 GET /conversations 请求
2. Gin 框架接收请求
3. 经过中间件(记录日志、允许跨域)
4. 找到对应的处理函数(ListConversations
5. 查询数据库(db.Where("status != ?", "closed")
6. 按更新时间倒序排列(Order("updated_at desc")
7. 返回 JSON 响应(对话列表)
8. 前端收到响应,显示对话列表

4. 服务器首次启动流程

1. 加载 .env 文件
2. 连接数据库(NewDB
3. 自动创建表(AutoMigrate
4. 检查是否有管理员账号(initDefaultAdmin
5. 如果没有,创建默认管理员(admin/admin123
6. 创建 Gin 服务器
7. 注册路由和中间件
8. 启动服务器

5. 数据库操作流程

1. 定义结构体(models.User
2. GORM 自动创建表(AutoMigrate
3. 查询:db.Where("条件").First(&结果)
4. 插入:db.Create(&数据)
5. 更新:db.Save(&数据)
6. 删除:db.Delete(&数据)

四、为什么这样写?

为什么用 Gin 框架?

  • 如果不用:需要自己处理 HTTP 协议、解析请求、路由等(很复杂)
  • 用了:只需要写业务逻辑,框架帮你处理其他事情(简单高效)

为什么用 GORM

  • 如果不用:需要手写 SQL,容易出错,有 SQL 注入风险
  • 用了:用 Go 代码操作数据库,自动生成安全的 SQL(安全、简单)

为什么用环境变量?

  • 如果不用:密码硬编码在代码里,不安全,换环境要改代码
  • 用了:配置和代码分离,安全,灵活

为什么用中间件?

  • 如果不用:每个路由都要写日志、跨域等代码(重复)
  • 用了:统一处理,代码简洁,易维护

为什么用 bcrypt 加密密码?

  • 如果不用:密码明文存储,泄露了别人就能登录
  • 用了:密码加密存储,即使泄露也无法还原(只能验证)

五、代码结构说明

项目结构

backend/
├── main.go               # 入口:环境加载、依赖初始化、路由注册
├── controller/           # HTTP 层:接收请求、参数校验、调用业务服务
├── service/              # 业务层:会话/消息/认证逻辑,使用仓储与 WebSocket
├── service/types.go      # 业务层通用请求/返回结构体
├── repository/           # 仓储层:封装 GORM 操作(User/Conversation/Message
├── router/               # 路由注册集中处(RegisterRoutes
├── utils/                # 工具函数(请求信息解析、UA 解析等)
├── websocket/            # Hub/Client/Handler:管理长连接与广播
├── infra/                # 基础设施(数据库连接 NewDB 等)
├── middleware/           # 中间件(日志、CORS
├── models/               # 数据模型(User、Conversation、Message
└── .env                  # 环境变量配置

分层架构

main.go
  ↓ 初始化仓储 / 服务 / 控制器 / WebSocket Hub
router.RegisterRoutes
  ↓ controller 层(解析请求 → 调用 service)
  ↓ service 层(编排业务 → 调用 repository、websocket
  ↓ repository 层(GORM 操作数据库)
  ↓ models + 数据库

关键职责拆分:

  • controller:只做输入输出转换,避免直接访问数据库
  • service:编排业务流程(例如发送消息时刷新会话更新时间、广播 WebSocket)
  • repository:封装 FindOpenByVisitorIDListActiveMarkMessagesRead 等数据库操作
  • websockethub.go 管理连接字典,client.go 负责读写协程,handler.go 升级 HTTP连接
  • utils/request.goGetClientIPParseUserAgent 等通用方法
  • service/types.goConversationSummaryMessageItem 等跨服务共享结构体

重构之后:新增服务时,只需新增仓储方法 + service + controller,不再在 main.go 里堆业务代码。

关键结构体与函数速查

名称 所在文件 作用与要点
controller.AuthController controller/auth_controller.go 处理登录/退出,依赖 AuthService,统一 JSON 响应格式
controller.ConversationController controller/conversation_controller.go 初始化会话、列表、搜索、状态更新、详情查询、更新访客联系信息
controller.MessageController controller/message_controller.go 创建消息、拉取消息、批量已读
service.AuthService service/auth_service.go 登录校验、账号创建(管理员)
service.ConversationService service/conversation_service.go 会话编排:初始化系统消息、列表拼装、搜索、状态/联系信息更新
service.MessageService service/message_service.go 发送消息、拉取消息、已读逻辑 + WebSocket 广播
service.AdminService service/admin_service.go 创建客服/管理员账号
repository.ConversationRepository repository/conversation_repository.go 会话数据库操作:按访客查找、批量查询、更新状态/字段等
repository.MessageRepository repository/message_repository.go 消息数据库操作:保存、按会话拉取、统计未读、标记已读
repository.UserRepository repository/user_repository.go 用户查询/创建
router.RegisterRoutes router/router.go 集中注册所有 REST + WebSocket 路由(含 /conversations/:id/contact
websocket.Hub / Client websocket/hub.goclient.go 维护对话房间 → 客户端列表,负责广播消息/已读事件
utils.GetClientIP / ParseUserAgent utils/request.go 请求上下文工具函数,供访客信息采集、日志使用
service/types.go - 定义 ConversationSummaryCreateMessageInput 等跨层 DTO,保持类型统一

六、常见代码模式

1. Handler 函数模式

func Register(db *gorm.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 1. 解析请求数据
        var u models.User
        if err := c.ShouldBindJSON(&u); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 2. 业务逻辑处理
        // ...
        
        // 3. 返回响应
        c.JSON(200, gin.H{"message": "成功"})
    }
}

为什么这样写?

  • 返回 gin.HandlerFunc:可以传入数据库连接等参数
  • c *gin.Context:包含请求和响应的信息
  • 先检查错误,再处理:提前返回,代码清晰

2. 错误处理模式

if err != nil {
    // 处理错误
    return
}
// 继续执行

为什么这样写?

  • Go 语言的习惯:错误优先处理
  • 提前返回:避免嵌套,代码清晰

3. 数据库查询模式

var user models.User
if err := db.Where("username=?", name).First(&user).Error; err != nil {
    // 没找到
    return
}
// 找到了,使用 user

为什么这样写?

  • First:找到第一条记录,没找到返回错误
  • &user:传入指针,结果存到 user 里
  • 检查错误:确保数据存在才继续

七、记忆口诀

  1. GinWeb框架,处理请求
  2. GORMORM框架,操作数据库
  3. Handler:处理函数,处理业务逻辑
  4. Middleware:中间件,统一处理
  5. Context:上下文,包含请求响应
  6. Error:错误优先,提前返回

八、常见错误和解决

1. 忘记处理错误

错误db.Create(&user) 不检查错误 正确

if err := db.Create(&user).Error; err != nil {
    // 处理错误
    return
}

2. 环境变量没加载

错误os.Getenv("DB_HOST") 返回空字符串 解决

  • 检查 .env 文件是否存在
  • 检查文件编码(不能有 BOM
  • 检查 godotenv.Load() 是否执行

3. 数据库连接失败

错误数据库连接失败 解决

  • 检查 MySQL 是否启动
  • 检查 .env 配置是否正确
  • 检查数据库是否存在

4. 跨域问题

错误:前端请求被阻止 解决

  • 检查是否添加了 CORS 中间件
  • 检查中间件配置是否正确

5. 密码加密问题

错误:密码明文存储或验证失败 解决

  • 注册时用 bcrypt.GenerateFromPassword 加密
  • 登录时用 bcrypt.CompareHashAndPassword 验证

6. 默认管理员创建问题

错误:首次启动时没有管理员账号,无法登录 解决

  • 使用 initDefaultAdmin 函数在首次启动时自动创建
  • 检查是否已存在,避免重复创建

九、调试技巧

1. 查看日志

  • 后端启动时看日志输出
  • 请求处理时看日志记录
  • 错误时看错误信息

2. 使用 Postman 测试

  • 不用写前端,直接测试 API
  • 可以测试各种情况(成功、失败等)

3. 检查数据库

  • 用 MySQL 客户端查看数据
  • 确认数据是否正确保存

4. 打印调试信息

log.Printf("调试信息: %v", 变量)

十、学习建议

  1. 先理解概念:理解框架的作用,不要死记硬背
  2. 多看代码:看别人的代码,理解写法
  3. 多写注释:每行代码都写注释,理解为什么这样写
  4. 小步调试:每次只改一小部分,测试看看效果
  5. 多看错误:出错时看错误信息,学会查问题
  6. 画流程图:把代码执行流程画出来,理清思路

十一、WebSocket 实时通信(已实现)

1. WebSocket 的作用

类比:就像对讲机,可以实时双向通信

  • HTTP:只能客户端主动请求,服务器被动响应
  • WebSocket:客户端和服务器可以随时互相发送消息

为什么需要?

  • 新消息需要实时推送给客服,无需手动刷新
  • 对话状态更新需要实时同步
  • 提升用户体验,感觉更流畅

2. WebSocket 架构

Hub(中心管理器)
  ├── conversations(对话ID → 客户端列表)
  ├── register(注册新客户端)
  ├── unregister(注销客户端)
  └── broadcast(广播消息)

Client(客户端连接)
  ├── hub(所属的 Hub
  ├── connWebSocket 连接)
  ├── send(发送消息的通道)
  └── conversationID(所属的对话ID

3. 消息流程

1. 客户端建立 WebSocket 连接
2. 客户端发送对话ID,注册到 Hub
3. Hub 将客户端加入对应对话的客户端列表
4. 当有新消息时,Hub 广播给该对话的所有客户端
5. 客户端收到消息,更新 UI

4. 心跳机制

  • 客户端定期发送 ping 消息
  • 服务器回复 pong 消息
  • 如果长时间没有收到 ping,断开连接

为什么需要?

  • 检测连接是否断开
  • 保持连接活跃
  • 自动清理断开的连接

十二、后端分层架构(2025-11 重构版)

1. 目录结构(核心部分)

backend/
├─ main.go                 // 程序入口,只负责依赖注入和启动
├─ router/                 // 路由注册(把 URL 映射到控制器)
├─ controller/             // 控制器层,处理 HTTP 请求/响应
├─ service/                // 业务层,编排业务逻辑
├─ repository/             // 数据访问层,封装 GORM 操作
├─ utils/                  // 工具函数(IP、User-Agent 等)
└─ websocket/              // WebSocket Hub、Client 等

2. 各层职责

  • router:统一注册所有路由,中间件分组时也只在这里改。
  • controller:只关心“请求 → 调用服务 → 返回响应”,不会出现 SQL 或业务细节。
  • service:聚焦业务规则,可以组合多个 repository、触发 WebSocket 推送等。
  • repository:集中管理所有数据库读写,便于修改查询逻辑或引入缓存。
  • utils:存放通用工具,避免重复代码。

3. 调用顺序

HTTP 请求
   ↓
router → controller → service → repository
                                  ↓
                                MySQL

(若需要 WebSocket 推送,service 会在写库后调用 hub.BroadcastMessage。)

4. 为什么这样做?

  • 代码职责更清晰:想改接口,看 controller;想改业务,看 service;想改 SQL 看 repository。
  • 更容易写单元测试:service 不依赖 ginrepository 可以替换成 mock。
  • 方便扩展:以后要接入 gRPC/消息队列,只需在 service 层复用已有逻辑。

5. 迁移提示

  • 新增接口时先写 service & repository,再写 controller 和 router。
  • 记得运行 gofmtPowerShell 举例):
    cd backend
    Get-ChildItem main.go, router, controller, service, repository, utils -Recurse -Filter *.go -File |
      ForEach-Object { gofmt -w $_.FullName }
    
  • main.go 只负责依赖注入(NewRepository → NewService → NewController)。

记住:后端开发就像搭建一个服务系统,框架帮你处理底层细节,你只需要关注业务逻辑!🚀