类比:就像餐厅的服务员,接收客人的请求,然后告诉厨房(处理函数)做什么菜
gin.Default() = 创建一个 Gin 服务器(就像开一家餐厅)r.POST("/路径", 处理函数) = 注册一个路由(就像在菜单上写:客人点这个菜,叫这个厨师做)r.Run(":8080") = 启动服务器,监听 8080 端口(就像开门营业)为什么需要?
类比:就像翻译官,帮你把 Go 代码翻译成 SQL 语句
db.Create(&user) = 插入数据(翻译成 INSERT INTO users ...)db.Where("username=?", name).First(&user) = 查询数据(翻译成 SELECT * FROM users WHERE username=?)db.AutoMigrate(&User{}) = 自动创建表(根据结构体自动建表)为什么需要?
类比:就像餐厅的配置表,记录"数据库地址"、"密码"等敏感信息
godotenv.Load() = 从 .env 文件读取配置os.Getenv("DB_HOST") = 获取环境变量的值为什么需要?
类比:就像餐厅的"检查站",每个请求都要经过这些检查
r.Use(middleware.Logger()) = 记录每个请求的日志(就像记录每个客人点了什么)r.Use(middleware.CORS()) = 允许跨域请求(就像允许其他网站访问我们的 API)为什么需要?
类比:就像餐厅的厨师,负责处理具体的业务逻辑
func Register(db *gorm.DB) gin.HandlerFunc = 注册用户的处理函数c.ShouldBindJSON(&u) = 把请求的 JSON 数据解析成结构体c.JSON(200, gin.H{"message": "成功"}) = 返回 JSON 响应给前端为什么需要?
类比:就像餐厅开业时,自动准备一些基础食材
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. 加载 .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 端口)
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
1. 前端发送 GET /conversations 请求
2. Gin 框架接收请求
3. 经过中间件(记录日志、允许跨域)
4. 找到对应的处理函数(ListConversations)
5. 查询数据库(db.Where("status != ?", "closed"))
6. 按更新时间倒序排列(Order("updated_at desc"))
7. 返回 JSON 响应(对话列表)
8. 前端收到响应,显示对话列表
1. 加载 .env 文件
2. 连接数据库(NewDB)
3. 自动创建表(AutoMigrate)
4. 检查是否有管理员账号(initDefaultAdmin)
5. 如果没有,创建默认管理员(admin/admin123)
6. 创建 Gin 服务器
7. 注册路由和中间件
8. 启动服务器
1. 定义结构体(models.User)
2. GORM 自动创建表(AutoMigrate)
3. 查询:db.Where("条件").First(&结果)
4. 插入:db.Create(&数据)
5. 更新:db.Save(&数据)
6. 删除:db.Delete(&数据)
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 + 数据库
关键职责拆分:
FindOpenByVisitorID、ListActive、MarkMessagesRead 等数据库操作hub.go 管理连接字典,client.go 负责读写协程,handler.go 升级 HTTP连接GetClientIP、ParseUserAgent 等通用方法ConversationSummary、MessageItem 等跨服务共享结构体重构之后:新增服务时,只需新增仓储方法 + 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.go、client.go |
维护对话房间 → 客户端列表,负责广播消息/已读事件 |
utils.GetClientIP / ParseUserAgent |
utils/request.go |
请求上下文工具函数,供访客信息采集、日志使用 |
service/types.go |
- | 定义 ConversationSummary、CreateMessageInput 等跨层 DTO,保持类型统一 |
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:包含请求和响应的信息if err != nil {
// 处理错误
return
}
// 继续执行
为什么这样写?
var user models.User
if err := db.Where("username=?", name).First(&user).Error; err != nil {
// 没找到
return
}
// 找到了,使用 user
为什么这样写?
First:找到第一条记录,没找到返回错误&user:传入指针,结果存到 user 里错误:db.Create(&user) 不检查错误
正确:
if err := db.Create(&user).Error; err != nil {
// 处理错误
return
}
错误:os.Getenv("DB_HOST") 返回空字符串
解决:
.env 文件是否存在godotenv.Load() 是否执行错误:数据库连接失败
解决:
.env 配置是否正确错误:前端请求被阻止 解决:
错误:密码明文存储或验证失败 解决:
bcrypt.GenerateFromPassword 加密bcrypt.CompareHashAndPassword 验证错误:首次启动时没有管理员账号,无法登录 解决:
initDefaultAdmin 函数在首次启动时自动创建log.Printf("调试信息: %v", 变量)
类比:就像对讲机,可以实时双向通信
为什么需要?
Hub(中心管理器)
├── conversations(对话ID → 客户端列表)
├── register(注册新客户端)
├── unregister(注销客户端)
└── broadcast(广播消息)
Client(客户端连接)
├── hub(所属的 Hub)
├── conn(WebSocket 连接)
├── send(发送消息的通道)
└── conversationID(所属的对话ID)
1. 客户端建立 WebSocket 连接
2. 客户端发送对话ID,注册到 Hub
3. Hub 将客户端加入对应对话的客户端列表
4. 当有新消息时,Hub 广播给该对话的所有客户端
5. 客户端收到消息,更新 UI
为什么需要?
backend/
├─ main.go // 程序入口,只负责依赖注入和启动
├─ router/ // 路由注册(把 URL 映射到控制器)
├─ controller/ // 控制器层,处理 HTTP 请求/响应
├─ service/ // 业务层,编排业务逻辑
├─ repository/ // 数据访问层,封装 GORM 操作
├─ utils/ // 工具函数(IP、User-Agent 等)
└─ websocket/ // WebSocket Hub、Client 等
HTTP 请求
↓
router → controller → service → repository
↓
MySQL
(若需要 WebSocket 推送,service 会在写库后调用 hub.BroadcastMessage。)
记得运行 gofmt(PowerShell 举例):
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)。
记住:后端开发就像搭建一个服务系统,框架帮你处理底层细节,你只需要关注业务逻辑!🚀