From 1d9f7a74323e7a51bb8a7a7d9c26445a4da9e31f Mon Sep 17 00:00:00 2001 From: bujinyuan Date: Wed, 4 Mar 2026 10:51:38 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E8=A7=84=E8=8C=83=E6=96=87=E6=A1=A3=20+=20?= =?UTF-8?q?=E6=8B=86=E5=88=86=E9=9B=86=E6=88=90=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 backend-module-architecture.md(10 章:模块分层/接口注入/Wire/日志/错误处理/批量查询/系统消息/WS 推送/Store 封装) - 从 frontend-backend-integration.md 拆分(621→360 行),符合 ≤500 行规范 - project-context.mdc 新增规则 #10 引用后端架构规范 Made-with: Cursor --- .cursor/rules/project-context.mdc | 9 +- .../backend-module-architecture.md | 299 ++++++++++++++++++ .../frontend-backend-integration.md | 21 +- 3 files changed, 319 insertions(+), 10 deletions(-) create mode 100644 docs/conventions/backend-module-architecture.md diff --git a/.cursor/rules/project-context.mdc b/.cursor/rules/project-context.mdc index 19c066e..e28682b 100644 --- a/.cursor/rules/project-context.mdc +++ b/.cursor/rules/project-context.mdc @@ -44,10 +44,11 @@ Phase 2c 待实现模块:group(群聊管理)、file(文件上传/MinIO 7. **JWT 策略**:有状态 JWT,Token 存 Redis(按 clientType 隔离:`echo:auth:token:{frontend|admin}:{user_id}`),前后台互不影响 8. **角色等级体系**:`auth_roles.level` 字段(值越小权限越高:1=超管, 10=管理员, 100=普通用户),所有管理操作强制执行层级权限校验 9. **代码注释**:所有公开函数、组件、Store 必须有详细注释 -10. **文档自动同步(强制)**:每个 Task 或功能开发完成后,必须自动执行文档同步,详见下方「文档自动同步规则」 -11. **验证方式**:使用 Playwright MCP 进行页面自动化验证 -12. **代码审查**:每个 Task 完成后,使用 `code-reviewer` 子代理进行结构化审查,确保代码质量和计划一致性 -13. **完成验证**:使用 `verification-before-completion` 技能,在声称完成前运行验证命令并确认输出 +10. **后端架构规范**:详见 `docs/conventions/backend-module-architecture.md`(模块分层/接口注入/日志/错误处理/批量查询/系统消息/Store 封装等) +11. **文档自动同步(强制)**:每个 Task 或功能开发完成后,必须自动执行文档同步,详见下方「文档自动同步规则」 +12. **验证方式**:使用 Playwright MCP 进行页面自动化验证 +13. **代码审查**:每个 Task 完成后,使用 `code-reviewer` 子代理进行结构化审查,确保代码质量和计划一致性 +14. **完成验证**:使用 `verification-before-completion` 技能,在声称完成前运行验证命令并确认输出 ## 文档自动同步规则(强制执行) diff --git a/docs/conventions/backend-module-architecture.md b/docs/conventions/backend-module-architecture.md new file mode 100644 index 0000000..2a297c8 --- /dev/null +++ b/docs/conventions/backend-module-architecture.md @@ -0,0 +1,299 @@ +# Go 后端模块架构规范 + +> **适用范围**:EchoChat Go 后端(`backend/go-service/`) +> **创建日期**:2026-03-04 +> **最后更新**:2026-03-04(Phase 2c 设计阶段整理) +> **关联文档**:`docs/conventions/frontend-backend-integration.md` + +--- + +## 一、模块分层架构 + +每个业务模块采用四层架构,目录结构如下: + +``` +app/{module_name}/ +├── controller/ +│ └── {module}_controller.go # HTTP 请求处理(参数绑定/校验 → 调用 Service → 响应) +├── service/ +│ └── {module}_service.go # 业务逻辑(事务协调、权限校验、多 DAO 编排) +├── dao/ +│ └── {module}_dao.go # 数据访问(GORM 操作,不含业务逻辑) +├── model/ +│ └── {module}.go # 数据模型(GORM 结构体,对应数据库表) +├── handler/ +│ └── {module}_handler.go # [可选] WS 事件处理器 +├── router.go # 路由注册 +└── provider.go # Wire ProviderSet +``` + +--- + +## 二、层间调用规则 + +``` +Controller → Service → DAO → 数据库 + ↓ + 其他模块接口(通过注入的 interface) + ↓ + PubSub / Redis +``` + +- Controller **只处理 HTTP 关注点**:参数绑定、调用 Service、返回响应 +- Service **只处理业务逻辑**:权限校验、业务规则、事务编排、推送通知 +- DAO **只处理数据存取**:GORM 查询、批量操作、不含业务逻辑 +- **禁止跨层调用**:Controller 不能直接调用 DAO,DAO 不能调用 Service + +--- + +## 三、跨模块接口注入模式 + +模块间通信通过 **interface injection** 实现,禁止直接 import 其他模块包。 + +### 3.1 标准流程 + +``` +步骤1: 在消费方 Service 中定义 interface(如 im/service → GroupMemberChecker) +步骤2: 在提供方 DAO 中实现该接口(如 group/dao.GroupDAO) +步骤3: 在 app/provider/wire.go 中用 wire.Bind 绑定接口和实现 +步骤4: 重新生成 wire_gen.go +``` + +### 3.2 接口命名约定 + +| 接口类型 | 命名模式 | 示例 | +|---------|---------|------| +| 数据查询 | `{Entity}{Action}er` | `FriendIDsGetter`, `GroupInfoGetter` | +| 状态检查 | `{Entity}{State}Checker` | `GroupMemberChecker`, `OnlineChecker` | +| 操作执行 | `{Entity}{Action}er` | `OfflineMessagePusher` | + +### 3.3 已有接口注入清单 + +| 接口 | 定义方 | 实现方 | 用途 | +|------|--------|--------|------| +| FriendIDsGetter | ws/handler | contact/dao | 获取用户好友 ID 列表 | +| FriendChecker | im/service | contact/dao | 检查是否为好友 | +| UserInfoGetter | im/service | auth/dao | 获取用户信息 | +| OnlineChecker | contact/service | ws/service | 检查在线状态 | +| OfflineMessagePusher | im/service | ws/handler | 离线消息推送 | +| GroupMemberChecker | im/service | group/dao | 检查群成员身份(Phase 2c) | +| GroupInfoGetter | im/service | group/dao | 获取群信息(Phase 2c) | +| GroupRoleChecker | im/service | group/dao | 检查用户群角色(Phase 2c) | + +--- + +## 四、Wire 依赖注入规范 + +- 每个模块在 `provider.go` 中导出 `ProviderSet`(`wire.NewSet(...)`) +- 接口绑定统一在 `app/provider/wire.go` 中声明 +- 修改 wire.go 后必须重新运行 `wire gen ./app/provider/` 生成 wire_gen.go +- Wire 有过手动 patch 历史(Phase 2b),修改后需检查 wire_gen.go 一致性 + +--- + +## 五、日志记录标准 + +所有 DAO 和 Service 的公开方法必须记录入口和出口日志: + +```go +func (d *SomeDAO) SomeMethod(ctx context.Context, param int64) (result *Model, err error) { + funcName := "dao.some_dao.SomeMethod" + + logs.LogFunctionEntry(ctx, funcName, map[string]interface{}{ + "param": param, + }) + + defer func() { + logs.LogFunctionExit(ctx, funcName, result, err) + }() + + // 业务逻辑 + return result, err +} +``` + +**funcName 命名规则:** `{层级}.{模块}_{文件}.{方法名}` +- DAO 层:`dao.group_dao.CreateGroup` +- Service 层:`service.group_service.CreateGroup` +- Controller 层:`controller.group_controller.CreateGroup` + +--- + +## 六、Controller 错误处理标准 + +```go +func (ctrl *Controller) HandleAction(c *gin.Context) { + funcName := "controller.module.HandleAction" + + // 参数绑定 + var req dto.SomeRequest + if err := c.ShouldBindJSON(&req); err != nil { + utils.ResponseBadRequest(c, "参数校验失败: "+err.Error()) + return + } + + // 调用 Service + result, err := ctrl.service.DoAction(c.Request.Context(), &req) + if err != nil { + ctrl.handleError(c, funcName, err) + return + } + + utils.ResponseOK(c, "操作成功", result) +} + +// handleError 必须覆盖所有已知业务错误 +func (ctrl *Controller) handleError(c *gin.Context, funcName string, err error) { + switch err { + case service.ErrNotFound: + utils.ResponseNotFound(c, err.Error()) + case service.ErrPermission: + utils.ResponseForbidden(c, err.Error()) + // ... 覆盖所有已知错误 + default: + logs.Error(c.Request.Context(), funcName, "操作失败", zap.Error(err)) + utils.ResponseError(c, "操作失败") + } +} +``` + +**关键要求:** +- `handleError` 必须覆盖所有已知业务错误,不能用 `default` 笼统处理 +- 错误 message 使用中文,面向用户 +- 未知错误必须记录日志 + +--- + +## 七、批量查询优化 + +获取关联信息时,**必须使用批量查询 + Map 映射,严禁 N+1 查询**: + +```go +// ✅ 正确:批量查询 +userIDs := extractUserIDs(members) +users, _ := userDAO.GetByIDs(ctx, userIDs) +userMap := make(map[int64]*User) +for _, u := range users { + userMap[u.ID] = u +} +for _, m := range members { + m.Nickname = userMap[m.UserID].Nickname +} + +// ❌ 错误:N+1 查询 +for _, m := range members { + user, _ := userDAO.GetByID(ctx, m.UserID) + m.Nickname = user.Nickname +} +``` + +--- + +## 八、系统消息规范(Phase 2c+) + +### 8.1 系统消息定义 + +系统消息是由服务端自动生成的提示类消息,写入 `im_messages` 表,type=10(`MessageTypeSystem`)。 + +### 8.2 内容格式 + +系统消息的 `content` 字段使用**纯文本格式**,不使用 JSON 结构。 + +### 8.3 前端渲染 + +系统消息在聊天页面中**居中显示,灰色小字体,无头像,无气泡**。 + +### 8.4 sender_id + +系统消息的 `sender_id` 设为 0(表示系统),前端根据 sender_id=0 和 type=10 判断为系统消息。 + +--- + +## 九、WS 事件推送模式 + +### 9.1 S→C 推送(Service 层触发) + +```go +pubsub.PublishToUser(ctx, targetUserID, &ws.Message{ + Event: "group.member.join", + Data: map[string]interface{}{...}, +}) + +pubsub.PublishToUsers(ctx, memberIDs, &ws.Message{ + Event: "im.message.new", + Data: messageDTO, +}) +``` + +### 9.2 C→S 事件处理(Hub 事件路由表注册) + +```go +hub.RegisterEvent("im.message.send", handler.HandleSendMessage) +hub.RegisterEvent("im.message.read", handler.HandleReadMessage) +``` + +--- + +## 十、前端 Store 与 API 封装规范 + +### 10.1 API 封装标准 + +文件位置:`frontend/src/api/{module}.js` + +```javascript +import request from '@/utils/request' + +// 获取群详情 +export function getGroupDetail(groupId) { + return request.get(`/groups/${groupId}`) +} + +// 创建群聊 +export function createGroup(data) { + return request.post('/groups', data) +} +``` + +**命名规则:** +- GET 请求:`get{Entity}` / `get{Entity}List` +- POST 请求:`create{Entity}` / `{action}{Entity}` +- PUT 请求:`update{Entity}` / `set{Entity}{Field}` +- DELETE 请求:`delete{Entity}` / `remove{Entity}` + +### 10.2 Pinia Store 标准结构 + +文件位置:`frontend/src/store/{module}.js` + +```javascript +import { defineStore } from 'pinia' + +export const useGroupStore = defineStore('group', { + state: () => ({ + conversations: [], // 群会话列表 + currentGroup: null, // 当前群详情 + messages: {}, // {conversationId: Message[]} + members: {}, // {groupId: Member[]} + }), + + getters: { + unreadTotal: (state) => { ... }, + }, + + actions: { + // 初始化 WS 事件监听(在 App.vue onLaunch 中调用) + initWsListeners() { ... }, + + // 加载群会话列表 + async loadConversations() { ... }, + + // 发送群消息 + async sendMessage(conversationId, content, atUserIds) { ... }, + } +}) +``` + +**Store 设计原则:** +1. **单一职责**:每个模块一个 Store,不混合不同模块数据 +2. **WS 监听统一初始化**:所有 Store 的 WS 事件监听在 `App.vue` 的 `_initGlobalWS` 中统一调用 +3. **消息缓存**:按 conversationId 键值对缓存,避免重复请求 +4. **乐观更新**:发送消息时先本地插入,再等待服务端 ack 确认 diff --git a/docs/conventions/frontend-backend-integration.md b/docs/conventions/frontend-backend-integration.md index 068883d..9baec6e 100644 --- a/docs/conventions/frontend-backend-integration.md +++ b/docs/conventions/frontend-backend-integration.md @@ -315,20 +315,28 @@ JWT Token 的 Claims 中包含 `client_type` 字段,用于: --- -## 8. WebSocket 事件联动规范 +## 8. Go 后端架构与编码规范 -### 8.1 适用范围 +> 详细规范已拆分为独立文档,见 `docs/conventions/backend-module-architecture.md` +> +> 包含:模块分层架构、层间调用规则、跨模块接口注入模式、Wire 依赖注入、日志标准、Controller 错误处理、批量查询优化、系统消息规范、WS 事件推送模式、前端 Store/API 封装规范 + +--- + +## 9. WebSocket 事件联动规范 + +### 9.1 适用范围 仅前台用户端(frontend)使用 WebSocket 实时通讯,管理端(admin)使用 REST 轮询。 -### 8.2 连接管理 +### 9.2 连接管理 - WebSocket 地址:`ws(s)://host/ws?token=xxx` - 认证方式:URL Query 参数传递 JWT Token(前台 frontend Token) - 心跳间隔:30 秒 ping/pong - 断线重连:指数退避(1s → 2s → 4s → 8s → 30s max) -### 8.3 事件命名规范 +### 9.3 事件命名规范 格式:`{模块}.{对象}.{动作}` @@ -340,12 +348,13 @@ JWT Token 的 Claims 中包含 `client_type` 字段,用于: | `user.status.online` | 服务端 → 客户端 | 好友上线 | | `user.status.offline` | 服务端 → 客户端 | 好友下线 | -### 8.4 前端事件处理原则 +### 9.4 前端事件处理原则 1. **WebSocket Store 统一管理**:连接状态、事件监听、消息发送由 `store/websocket.js` 管理 2. **业务 Store 订阅事件**:各模块 Store(如 `contact.js`)通过 WebSocket Store 注册事件回调 3. **避免页面直接操作 WebSocket**:页面组件通过 Store 间接与 WebSocket 交互 -### 8.5 WebSocket 详细协议 +### 9.5 WebSocket 详细协议 > 完整事件协议见 `docs/api/frontend/websocket.md` +> WS 事件推送模式和代码示例见 `docs/conventions/backend-module-architecture.md` 第九章