0dbac480ae
- Add OpenTelemetry SDK, OTLP exporter, Prometheus integration - Implement connection tracking with active/total/disconnection metrics - Add health endpoint with uptime and connection counts - Integrate tracing spans for socket events and engine messages - Add metrics collection for event handling duration - Update health endpoint to include live runtime state - Add graceful telemetry shutdown in main function - Implement engine session active metrics tracking - Add namespace-specific attributes to connection metrics - Introduce message edit history retrieval endpoint - Add scheduled message CRUD operations and dispatcher - Update Socket.IO event registration with observability - Refactor component update to remove dead code allowance - Add comprehensive environment variables documentation - Implement detailed development guidelines in AGENTS.md
805 lines
32 KiB
Markdown
805 lines
32 KiB
Markdown
# AGENTS.md — 开发规范 / Development Guidelines
|
||
|
||
> 本文件为所有 AI 编码助手(Claude Code、pi、Cursor 等)提供统一的开发指导。
|
||
> This file provides unified development guidelines for all AI coding assistants.
|
||
|
||
**最后更新 / Last Updated**: 2026-06-11
|
||
|
||
---
|
||
|
||
## 目录 / Table of Contents
|
||
|
||
1. [语言 / Language](#1-语言--language)
|
||
2. [代码风格 / Code Style](#2-代码风格--code-style)
|
||
3. [禁止模式 / Forbidden Patterns](#3-禁止模式--forbidden-patterns)
|
||
4. [错误处理 / Error Handling](#4-错误处理--error-handling)
|
||
5. [安全规范 / Security](#5-安全规范--security)
|
||
6. [数据库规范 / Database](#6-数据库规范--database)
|
||
7. [Socket.IO 事件规范 / Socket.IO Event Conventions](#7-socketio-事件规范--socketio-event-conventions)
|
||
8. [日志与可观测性 / Logging & Observability](#8-日志与可观测性--logging--observability)
|
||
9. [性能规范 / Performance](#9-性能规范--performance)
|
||
10. [测试规范 / Testing](#10-测试规范--testing)
|
||
11. [Git 规范 / Git Workflow](#11-git-规范--git-workflow)
|
||
12. [工作流程 / Workflow](#12-工作流程--workflow)
|
||
13. [架构决策记录 / ADR](#13-架构决策记录--adr)
|
||
14. [审查清单 / Review Checklist](#14-审查清单--review-checklist)
|
||
|
||
---
|
||
|
||
## 1. 语言 / Language
|
||
|
||
**Always respond in Chinese (中文).** Use the user's language for all conversations and explanations. Code, commands, and technical terms can remain in English.
|
||
|
||
始终使用中文回复。代码、命令和技术术语可以保留英文。
|
||
|
||
---
|
||
|
||
## 2. 代码风格 / Code Style
|
||
|
||
### 2.1 基本原则 / Basic Principles
|
||
|
||
| 规则 | 说明 |
|
||
|---|---|
|
||
| 遵循现有风格 | Follow existing project conventions |
|
||
| 有意义命名 | Use meaningful variable names; avoid single-letter names except loop counters |
|
||
| 函数长度 | Keep functions under **50 lines**; split complex logic into smaller functions |
|
||
| 嵌套深度 | Maximum nesting depth: **3 levels**; use early returns to flatten logic |
|
||
| 注释 | Add comments for complex logic only; prefer self-documenting code |
|
||
| 文档注释 | Public items must have `///` doc comments; private items only when logic is non-obvious |
|
||
|
||
### 2.2 Rust 最佳实践 / Rust Best Practices
|
||
|
||
```rust
|
||
// ✅ 正确 / Correct
|
||
fn get_message(id: Uuid) -> Result<Message, sqlx::Error> {
|
||
let msg = db.find_message(id).await?; // 使用 ? 传播错误
|
||
Ok(msg)
|
||
}
|
||
|
||
// ❌ 错误 / Incorrect
|
||
fn get_message(id: Uuid) -> Message {
|
||
db.find_message(id).await.unwrap() // 禁止 unwrap()
|
||
}
|
||
```
|
||
|
||
| 规则 | 说明 |
|
||
|---|---|
|
||
| 错误传播 | Use `?` operator for error propagation; never use `unwrap()` or `expect()` in non-test code |
|
||
| `unsafe` | Avoid `unsafe` blocks; if necessary, add a `// SAFETY:` comment explaining why |
|
||
| `clone()` | Minimize `clone()` usage; prefer references or `Arc` for shared ownership |
|
||
| 魔法数字 | No magic numbers; define named constants with `const` |
|
||
| 硬编码字符串 | No hardcoded strings for config/status; use enums or constants |
|
||
| 死代码 | Remove dead code; don't leave commented-out code blocks |
|
||
| 未完成代码 | Don't commit `unimplemented!()`, `todo!()`, or `FIXME` without a tracking issue |
|
||
|
||
### 2.3 导入规范 / Import Guidelines
|
||
|
||
```rust
|
||
// 标准库 → 第三方 crate → 本地模块
|
||
// stdlib → third-party crates → local modules
|
||
use std::sync::Arc;
|
||
|
||
use chrono::{DateTime, Utc};
|
||
use serde::{Deserialize, Serialize};
|
||
use uuid::Uuid;
|
||
|
||
use crate::models::message::Message;
|
||
use crate::socket::packet::Packet;
|
||
```
|
||
|
||
### 2.4 模型设计规范 / Model Design Guidelines
|
||
|
||
imks 的 models 层采用 **一文件一实体** 的拆分策略:
|
||
|
||
```
|
||
models/
|
||
├── mod.rs # 模块声明 + 公开 re-export
|
||
├── message.rs # 核心 Message + MessageDetail + AuthorInfo + MessageType
|
||
├── message_attachment.rs # 附件
|
||
├── message_bookmark.rs # 书签/收藏
|
||
├── message_draft.rs # 草稿
|
||
├── message_edit.rs # 编辑历史
|
||
├── message_embed.rs # 富媒体嵌入 + EmbedField
|
||
├── message_mention.rs # @提及
|
||
├── message_pin.rs # 置顶消息
|
||
├── message_poll.rs # 投票 + Option + Vote
|
||
├── message_reaction.rs # 表情反应
|
||
├── message_read_state.rs # 已读状态
|
||
└── message_thread.rs # 消息线程
|
||
```
|
||
|
||
每个模型文件包含:
|
||
- Row struct(`sqlx::FromRow`)
|
||
- Summary/detail struct(API 响应用,带 `From` 转换)
|
||
- 查询 SQL 常量(`$1, $2...` 占位符)
|
||
- `CREATE_TABLE_SQL` 迁移 DDL + 索引
|
||
- `#[cfg(test)]` 序列化/转换测试
|
||
|
||
---
|
||
|
||
## 3. 禁止模式 / Forbidden Patterns
|
||
|
||
以下代码模式在项目中严格禁止:
|
||
|
||
| 禁止项 | 说明 |
|
||
|-------------------------------|------------------------------------------------|
|
||
| `// ── xxxx ──────────` | 禁止使用此类分隔线注释 |
|
||
| `unwrap()` / `expect()` (非测试) | 在非测试代码中禁止使用;使用 `?` 或 `unwrap_or` 等安全替代 |
|
||
| `panic!()` / `unreachable!()` | 除极少数不可能到达的分支外禁止使用 |
|
||
| 未处理的 `todo!()` | 不得提交包含 `todo!()` 的代码,除非有对应的 issue 追踪 |
|
||
| 注释掉的代码 | 不得提交被注释的代码块;使用 Git 历史追溯 |
|
||
| 过深嵌套 (≥4层) | 使用 early return、`match`、`map`/`and_then` 扁平化逻辑 |
|
||
| 过长函数 (>50行) | 拆分为更小的、职责单一的函数 |
|
||
| 魔法数字 | 使用 `const` 定义命名常量 |
|
||
| 硬编码字符串 | 使用枚举或常量定义配置值/状态值 |
|
||
| 死代码 | 删除未使用的代码、导入和变量 |
|
||
| `Box<dyn Error>` 在公共 API | 使用具体错误类型替代 trait object |
|
||
|
||
---
|
||
|
||
## 4. 错误处理 / Error Handling
|
||
|
||
### 4.1 错误类型体系 / Error Type System
|
||
|
||
imks 使用统一的 `ImksError` 枚举和 `ImksResult<T>` 类型别名,与 appks 的 `AppError`/`AppResult` 保持一致风格。
|
||
|
||
```rust
|
||
// error.rs — 统一错误类型
|
||
use thiserror::Error;
|
||
|
||
#[derive(Debug, Error)]
|
||
pub enum ImksError {
|
||
// Protocol layer (engine)
|
||
#[error("invalid engine packet type: {0}")]
|
||
InvalidEnginePacketType(u8),
|
||
#[error("invalid engine packet type char: {0}")]
|
||
InvalidEnginePacketTypeChar(char),
|
||
#[error("empty engine packet")]
|
||
EmptyEnginePacket,
|
||
#[error("invalid base64: {0}")]
|
||
InvalidBase64(#[from] base64::DecodeError),
|
||
#[error("invalid utf8 in packet: {0}")]
|
||
InvalidPacketUtf8(#[from] FromUtf8Error),
|
||
#[error("engine serialization error: {0}")]
|
||
EngineSerialization(String),
|
||
|
||
// Transport upgrade
|
||
#[error("session not found for upgrade")]
|
||
UpgradeSessionNotFound,
|
||
#[error("session already closed, cannot upgrade")]
|
||
UpgradeSessionClosed,
|
||
#[error("invalid session state for upgrade")]
|
||
UpgradeInvalidState,
|
||
|
||
// Socket.IO layer
|
||
#[error("invalid socket packet type: {0}")]
|
||
InvalidSocketPacketType(u8),
|
||
#[error("invalid socket packet type char: {0}")]
|
||
InvalidSocketPacketTypeChar(char),
|
||
#[error("empty socket packet")]
|
||
EmptySocketPacket,
|
||
#[error("invalid socket packet format: {0}")]
|
||
InvalidSocketPacketFormat(String),
|
||
#[error("missing namespace in socket packet")]
|
||
MissingNamespace,
|
||
#[error("invalid attachment count in binary event")]
|
||
InvalidAttachmentCount,
|
||
|
||
// Socket namespace
|
||
#[error("namespace error: {0}")]
|
||
Namespace(String),
|
||
#[error("socket not found: {0}")]
|
||
SocketNotFound(String),
|
||
#[error("failed to send packet to socket: channel full")]
|
||
SocketSendFull,
|
||
|
||
// Adapter layer
|
||
#[error("adapter redis error: {0}")]
|
||
AdapterRedis(String),
|
||
#[error("adapter nats error: {0}")]
|
||
AdapterNats(String),
|
||
#[error("adapter message bus error: {0}")]
|
||
AdapterMessageBus(String),
|
||
#[error("adapter serialization error: {0}")]
|
||
AdapterSerialization(String),
|
||
#[error("adapter room error: {0}")]
|
||
AdapterRoom(String),
|
||
|
||
// Database
|
||
#[error("database error: {0}")]
|
||
Database(#[from] sqlx::Error),
|
||
|
||
// gRPC
|
||
#[error("gRPC error: {0}")]
|
||
GrpcStatus(#[from] tonic::Status),
|
||
#[error("gRPC transport error: {0}")]
|
||
GrpcTransport(#[from] tonic::transport::Error),
|
||
|
||
// Serialization
|
||
#[error("JSON error: {0}")]
|
||
Json(#[from] serde_json::Error),
|
||
|
||
// Auth
|
||
#[error("auth error: {0}")]
|
||
Auth(String),
|
||
#[error("token expired")]
|
||
TokenExpired,
|
||
|
||
// General
|
||
#[error("not found: {0}")]
|
||
NotFound(String),
|
||
#[error("invalid input: {0}")]
|
||
InvalidInput(String),
|
||
#[error("internal error: {0}")]
|
||
Internal(String),
|
||
}
|
||
|
||
pub type ImksResult<T> = Result<T, ImksError>;
|
||
```
|
||
|
||
### 4.2 错误处理原则 / Error Handling Principles
|
||
|
||
| 原则 | 说明 |
|
||
|-----------|-------------------------------------------------------------------|
|
||
| 统一类型 | 所有公共 API 返回 `ImksResult<T>`,不暴露子模块错误 |
|
||
| 显式处理 | Handle all errors explicitly; no silent failures |
|
||
| `From` 转换 | 外部库错误通过 `#[from]` 自动转换,减少 `map_err` 样板 |
|
||
| 异步传播 | Use `?` operator; don't suppress errors in spawned tasks |
|
||
| 通道满 | Handle `mpsc::TrySendError` gracefully(buffer or log),don't panic |
|
||
| gRPC 错误 | Map `tonic::Status` → `ImksError::Grpc` |
|
||
|
||
### 4.3 错误日志格式 / Error Logging Format
|
||
|
||
```rust
|
||
// 记录错误时包含完整上下文
|
||
tracing::error!(
|
||
error = %err,
|
||
socket_sid = %sid,
|
||
event = %event_name,
|
||
"Failed to handle socket event"
|
||
);
|
||
```
|
||
|
||
### 4.4 现有子模块迁移 / Migration from Submodule Errors
|
||
|
||
当前部分子模块使用独立的 `thiserror` enum(`PacketError`、`AdapterError`),需要逐步迁移到 `ImksError`:
|
||
|
||
```rust
|
||
// 旧 / Old
|
||
pub enum PacketError { ... }
|
||
pub fn decode(data: &str) -> Result<Packet, PacketError> { ... }
|
||
|
||
// 新 / New
|
||
pub fn decode(data: &str) -> ImksResult<Packet> {
|
||
// 内部仍可用 thiserror,对外转为 ImksError
|
||
inner_decode(data).map_err(|e| ImksError::Packet(e.to_string()))
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 安全规范 / Security
|
||
|
||
> 用户认证、授权、密码管理、2FA 等企业级安全由 appks 统一处理。
|
||
> imks 作为内部消息服务,仅负责消息层面的安全。
|
||
|
||
### 5.1 基础安全 / Basic Security
|
||
|
||
| 规则 | 说明 |
|
||
|---------|-----------------------------------------------------------------|
|
||
| 密钥管理 | Never hardcode secrets or API keys; use environment variables |
|
||
| 输入验证 | Always validate and sanitize user input(消息体、事件名、namespace path) |
|
||
| SQL 注入 | Use parameterized queries(sqlx handles this automatically) |
|
||
| gRPC 安全 | appks ↔ imks 的 gRPC 连接应使用 mTLS,防止密钥在传输中被截获 |
|
||
|
||
### 5.2 JWT 验证双模式 / Dual-Mode JWT Verification
|
||
|
||
imks 支持两种 JWT 验证模式(详见 `rpc.md`):
|
||
|
||
| 模式 | 方式 | 延迟 | 适用场景 |
|
||
|------------|---------------------------------------|-----|------------|
|
||
| **RPC 验证** | 调用 appks `TokenService.VerifyToken()` | 实时 | 敏感操作 |
|
||
| **本地验证** | 启动时拉取 `GetSigningKeys()` 缓存到本地 | 零延迟 | 高频操作(消息收发) |
|
||
|
||
推荐策略:普通操作(发消息、读频道)→ 本地验证,敏感操作 → RPC 验证。
|
||
|
||
### 5.3 连接安全 / Connection Security
|
||
|
||
| 要求 | 说明 |
|
||
|--------|------------------------------------------------|
|
||
| 命名空间验证 | 验证 namespace path(`/` 开头,≤256 字符,无控制字符),防止 DoS |
|
||
| 消息体大小 | 限制单条消息大小(`EngineConfig.max_payload`) |
|
||
| 速率限制 | 按 socket 限制消息发送频率 |
|
||
|
||
---
|
||
|
||
## 6. 数据库规范 / Database
|
||
|
||
### 6.1 基础规范 / Basic Rules
|
||
|
||
| 规则 | 说明 |
|
||
|---------|------------------------------------------------------------------|
|
||
| 参数化查询 | Always use parameterized queries (sqlx does this by default) |
|
||
| 迁移规范 | All schema changes must go through migration files in `migrate/` |
|
||
| UUID v7 | 所有主键使用 UUID v7(时间有序),便于索引和游标分页 |
|
||
| 软删除 | 使用 `deleted_at` 字段进行软删除,不用硬删除 |
|
||
| 去规范化 | 对于高频读取的聚合字段(如 `total_votes`、`unread_count`),可在表中冗余存储 |
|
||
|
||
### 6.2 imks 管理的数据库表 / imks-Managed Tables
|
||
|
||
imks 仅管理消息相关表,用户/频道/成员/权限等由 appks 核心服务管理。
|
||
|
||
| 表 | 对应模型文件 | 说明 |
|
||
|-----------------------|--------------------------------|-------------------|
|
||
| `message` | `models/message.rs` | 核心消息表(由 appks 创建) |
|
||
| `message_attachment` | `models/message_attachment.rs` | 文件附件 |
|
||
| `message_embed` | `models/message_embed.rs` | 富媒体嵌入 |
|
||
| `message_embed_field` | `models/message_embed.rs` | 嵌入字段 |
|
||
| `message_poll` | `models/message_poll.rs` | 投票 |
|
||
| `message_poll_option` | `models/message_poll.rs` | 投票选项 |
|
||
| `message_poll_vote` | `models/message_poll.rs` | 投票记录 |
|
||
| `message_pin` | `models/message_pin.rs` | 置顶消息 |
|
||
| `message_read_state` | `models/message_read_state.rs` | 已读状态 |
|
||
| `message_draft` | `models/message_draft.rs` | 草稿 |
|
||
| `message_edit` | `models/message_edit.rs` | 编辑历史 |
|
||
|
||
### 6.3 性能优化 / Performance Optimization
|
||
|
||
| 规则 | 说明 |
|
||
|-------------|---------------------------------------------------------------------------------|
|
||
| N+1 防护 | Use `JOIN` or batch queries instead of N+1 patterns |
|
||
| 游标分页 | Use UUID v7 cursor-based pagination(`WHERE id < $3 ORDER BY id DESC`),不用 OFFSET |
|
||
| 索引规范 | Add indexes for frequently queried columns; document index rationale |
|
||
| ON CONFLICT | Use `INSERT ... ON CONFLICT` for upsert patterns(draft、read_state) |
|
||
|
||
---
|
||
|
||
## 7. Socket.IO 事件规范 / Socket.IO Event Conventions
|
||
|
||
### 7.1 事件命名 / Event Naming
|
||
|
||
imks 使用 Socket.IO 协议进行实时通信,事件名遵循以下约定:
|
||
|
||
```
|
||
// 客户端 → 服务端(发送操作)
|
||
"message:send" // 发送消息
|
||
"message:edit" // 编辑消息
|
||
"message:delete" // 删除消息
|
||
"typing:start" // 开始输入
|
||
"typing:stop" // 停止输入
|
||
"reaction:add" // 添加反应
|
||
"reaction:remove" // 移除反应
|
||
|
||
// 服务端 → 客户端(推送事件)
|
||
"message:new" // 新消息
|
||
"message:updated" // 消息已更新
|
||
"message:deleted" // 消息已删除
|
||
"typing" // 用户输入状态
|
||
"reaction:updated" // 反应已更新
|
||
"presence:update" // 在线状态变更
|
||
```
|
||
|
||
### 7.2 事件数据结构 / Event Data Structure
|
||
|
||
```json
|
||
// 客户端发送消息事件
|
||
// Client sends: "message:send"
|
||
{
|
||
"channel_id": "01909a...",
|
||
"body": "hello world",
|
||
"thread_id": null,
|
||
"reply_to_message_id": null
|
||
}
|
||
|
||
// 服务端广播新消息
|
||
// Server broadcasts: "message:new"
|
||
{
|
||
"id": "01909b...",
|
||
"channel_id": "01909a...",
|
||
"author": { "id": "...", "username": "alice" },
|
||
"body": "hello world",
|
||
"created_at": "2026-06-11T10:00:00Z",
|
||
"reactions": {},
|
||
"attachment_count": 0
|
||
}
|
||
```
|
||
|
||
### 7.3 房间(Room)机制 / Room Mechanism
|
||
|
||
```rust
|
||
// channel_id 作为房间名,频道内消息只广播给加入该房间的 sockets
|
||
// Use channel_id as room name; messages broadcast only to sockets in that room
|
||
namespace.emit_to_room(&channel_id, "message:new", message_data).await;
|
||
```
|
||
|
||
### 7.4 适配器(Adapter)模式 / Adapter Pattern
|
||
|
||
imks 通过 Adapter trait 支持水平扩展,将 Socket.IO 事件广播到多节点:
|
||
|
||
```
|
||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||
│ Node 1 │────→│ NATS / │←────│ Node 2 │
|
||
│ (imks) │ │ Redis │ │ (imks) │
|
||
└─────────┘ └─────────┘ └─────────┘
|
||
```
|
||
|
||
| Adapter | 适用场景 |
|
||
|---|---|
|
||
| `LocalAdapter` | 单节点开发/测试 |
|
||
| `RedisAdapter` | 生产环境 Redis Pub/Sub |
|
||
| `NatsAdapter` | 生产环境 NATS(更低延迟) |
|
||
|
||
---
|
||
|
||
## 8. 日志与可观测性 / Logging & Observability
|
||
|
||
### 8.1 日志规范 / Logging Standards
|
||
|
||
```rust
|
||
// 使用 tracing crate 进行结构化日志
|
||
use tracing::{info, warn, error, debug};
|
||
|
||
info!(
|
||
socket_sid = %socket.sid,
|
||
engine_sid = %socket.engine_sid,
|
||
namespace = %namespace.path,
|
||
"Socket connected"
|
||
);
|
||
|
||
warn!(
|
||
socket_sid = %sid,
|
||
"Adapter register error: {}",
|
||
e
|
||
);
|
||
|
||
error!(
|
||
error = %err,
|
||
engine_sid = %sid,
|
||
"Failed to handle engine message"
|
||
);
|
||
```
|
||
|
||
| 级别 | 用途 |
|
||
|---------|-------------------------------------|
|
||
| `error` | 错误需要立即关注(连接失败、数据库错误) |
|
||
| `warn` | 异常但可恢复的情况(adapter 注册失败、socket 发送失败) |
|
||
| `info` | 关键业务操作记录(连接/断开、命名空间创建) |
|
||
| `debug` | 开发调试信息(数据包收发详情) |
|
||
| `trace` | 详细执行路径 |
|
||
|
||
### 8.2 关键指标 / Key Metrics
|
||
|
||
| 指标 | 说明 |
|
||
|------------|-----------------------------------------------------|
|
||
| 活跃连接数 | Active WebSocket + Polling + WebTransport sessions |
|
||
| 消息吞吐量 | Messages sent/received per second |
|
||
| 广播延迟 | P50/P95/P99 broadcast latency across nodes |
|
||
| 事件处理延迟 | Event handling time (receive → broadcast → deliver) |
|
||
| Adapter 延迟 | NATS/Redis broadcast delay between nodes |
|
||
| 连接错误率 | Connection failure rate |
|
||
| 数据库查询延迟 | Message insert/select latency |
|
||
|
||
### 8.3 健康检查 / Health Check
|
||
|
||
```json
|
||
// GET /health
|
||
{
|
||
"status": "healthy",
|
||
"version": "0.1.0",
|
||
"uptime": 3600,
|
||
"checks": {
|
||
"postgres": { "status": "up", "latency_ms": 5 },
|
||
"redis": { "status": "up", "latency_ms": 2 },
|
||
"nats": { "status": "up", "latency_ms": 1 },
|
||
"appks_grpc": { "status": "up", "latency_ms": 3 }
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 性能规范 / Performance
|
||
|
||
### 9.1 实时消息 SLA / Real-time Messaging SLA
|
||
|
||
| 指标 | 目标 |
|
||
|--------------|---------------|
|
||
| 消息端到端延迟(P50) | <50ms |
|
||
| 消息端到端延迟(P95) | <200ms |
|
||
| 消息端到端延迟(P99) | <500ms |
|
||
| 连接建立时间 | <100ms |
|
||
| 消息吞吐量(单节点) | >10,000 msg/s |
|
||
| 错误率 | <0.1% |
|
||
|
||
### 9.2 性能原则 / Performance Principles
|
||
|
||
| 原则 | 说明 |
|
||
|------|---------------------------------------------------------------------|
|
||
| 零拷贝 | Minimize data copying; use references where possible |
|
||
| 批量操作 | Batch adapter broadcasts; use pipelining for Redis |
|
||
| 无锁优先 | Use `DashMap` (lock-free) for hot-path data; `RwLock` for cold-path |
|
||
| 背压控制 | Use bounded `mpsc::channel` (256) to prevent memory blowout |
|
||
| 连接复用 | Reuse gRPC channels to appks |
|
||
|
||
### 9.3 优化策略 / Optimization Strategies
|
||
|
||
| 场景 | 策略 |
|
||
|---------|----------------------------------|
|
||
| 跨节点广播 | NATS(<1ms P50)优于 Redis(~2ms P50) |
|
||
| 消息持久化 | 异步写入 + 批量 COMMIT |
|
||
| 会话查找 | `DashMap` 直接查找,无锁竞争 |
|
||
| gRPC 调用 | 连接池 + 本地密钥缓存减少 RPC 往返 |
|
||
|
||
---
|
||
|
||
## 10. 测试规范 / Testing
|
||
|
||
### 10.1 基础要求 / Basic Requirements
|
||
|
||
| 规则 | 说明 |
|
||
|--------|-------------------------------------------------------------|
|
||
| 新功能 | All new features must have unit tests |
|
||
| Bug 修复 | Bug fixes must include regression tests |
|
||
| 模型测试 | All model files must have serialization/conversion tests |
|
||
| 测试隔离 | Tests must be independent and not depend on execution order |
|
||
|
||
### 10.2 测试命令 / Test Commands
|
||
|
||
```bash
|
||
cargo test # 运行所有测试
|
||
cargo test --lib # 仅运行 lib 测试
|
||
cargo test models:: # 运行 models 模块测试
|
||
cargo test socket::parser:: # 运行 socket parser 测试
|
||
cargo test -- --nocapture # 显示输出
|
||
```
|
||
|
||
### 10.3 测试文件组织 / Test File Organization
|
||
|
||
```
|
||
tests/
|
||
├── engine_io_tests.rs # Engine.IO 协议测试
|
||
├── socket_io_tests.rs # Socket.IO 协议测试
|
||
├── adapter_tests.rs # Adapter 测试
|
||
└── session_tests.rs # 会话管理测试
|
||
|
||
# 单元测试 (inline)
|
||
models/message.rs → #[cfg(test)] mod tests { ... }
|
||
models/message_poll.rs → #[cfg(test)] mod tests { ... }
|
||
socket/parser.rs → #[cfg(test)] mod tests { ... }
|
||
engine/codec.rs → #[cfg(test)] mod tests { ... }
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Git 规范 / Git Workflow
|
||
|
||
### 11.1 提交信息格式 / Commit Message Format
|
||
|
||
使用 Angular 风格,全部英文:
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
[optional body]
|
||
|
||
[optional footer]
|
||
```
|
||
|
||
| Type | 说明 |
|
||
|------------|--------|
|
||
| `feat` | 新功能 |
|
||
| `fix` | Bug 修复 |
|
||
| `refactor` | 重构 |
|
||
| `docs` | 文档 |
|
||
| `test` | 测试 |
|
||
| `chore` | 构建/工具 |
|
||
| `perf` | 性能优化 |
|
||
| `style` | 代码格式 |
|
||
|
||
**示例 / Examples:**
|
||
```
|
||
feat(models): add MessagePin, MessageReadState, MessageDraft, MessageEdit models
|
||
fix(socket): handle namespace validation on connect
|
||
refactor(engine): extract session store to dedicated module
|
||
docs(readme): add architecture overview
|
||
test(parser): add edge cases for binary event decoding
|
||
chore(deps): update tonic to 0.14
|
||
```
|
||
|
||
### 11.2 提交原则 / Commit Principles
|
||
|
||
| 原则 | 说明 |
|
||
|--------|----------------------------------------------------------|
|
||
| 原子提交 | Each commit should address one concern |
|
||
| 完整性 | Each commit should leave the codebase in a working state |
|
||
| 禁止强制推送 | Never force push to main branch |
|
||
| 提交前检查 | Run `cargo check` and `cargo test` before committing |
|
||
|
||
### 11.3 分支策略 / Branch Strategy
|
||
|
||
| 分支 | 用途 |
|
||
|-------------|--------|
|
||
| `main` | 生产就绪代码 |
|
||
| `feat/*` | 功能开发 |
|
||
| `fix/*` | Bug 修复 |
|
||
| `release/*` | 发布准备 |
|
||
|
||
---
|
||
|
||
## 12. 工作流程 / Workflow
|
||
|
||
### 12.1 开发流程 / Development Process
|
||
|
||
1. **理解先于编写** — Read before write; understand context first
|
||
2. **最小变更** — Minimal changes; don't refactor unrelated code
|
||
3. **验证变更** — Verify after changes; run tests or check output
|
||
4. **文档同步** — Update documentation when changing public APIs
|
||
|
||
### 12.2 AI 助手工作规范 / AI Assistant Guidelines
|
||
|
||
| 规则 | 说明 |
|
||
|--------|-----------------------------------------------------|
|
||
| 先读后写 | Always read existing code before making changes |
|
||
| 最小侵入 | Make minimal changes; don't refactor unrelated code |
|
||
| 验证结果 | Run `cargo check` or `cargo test` after changes |
|
||
| 解释变更 | Explain what you changed and why |
|
||
| 询问不确定 | Ask when unsure about requirements |
|
||
| 遵守禁止模式 | Never use `// ── xxxx ──────────` style comments |
|
||
|
||
### 12.3 常用命令 / Common Commands
|
||
|
||
```bash
|
||
cargo build # 构建
|
||
cargo check # 快速检查(推荐开发时使用)
|
||
cargo test # 运行测试
|
||
cargo test --lib # 仅运行 lib 测试
|
||
cargo clippy # Lint 检查
|
||
cargo fmt # 格式化
|
||
cargo doc --no-deps # 生成文档
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 架构决策记录 / ADR
|
||
|
||
架构决策记录存放在 `docs/adr/` 目录下,使用 Markdown 格式。
|
||
|
||
### 当前决策 / Current Decisions
|
||
|
||
| ADR | 标题 | 状态 |
|
||
|---|---|---|
|
||
| — | Socket.IO 作为实时通信协议 | Accepted |
|
||
| — | UUID v7 作为主键实现游标分页 | Accepted |
|
||
| — | 双模式 JWT 验证(本地 + RPC) | Accepted |
|
||
| — | Adapter 模式支持多节点水平扩展 | Accepted |
|
||
| — | 消息表由 appks 管理,imks 仅扩展富内容表 | Accepted |
|
||
|
||
### ADR 模板 / ADR Template
|
||
|
||
```markdown
|
||
# ADR-NNN: 标题
|
||
|
||
## 状态
|
||
Accepted | Superseded | Deprecated
|
||
|
||
## 背景
|
||
描述问题背景
|
||
|
||
## 决策
|
||
描述做出的决策
|
||
|
||
## 后果
|
||
描述正面和负面影响
|
||
```
|
||
|
||
---
|
||
|
||
## 14. 审查清单 / Review Checklist
|
||
|
||
### 代码审查 / Code Review
|
||
|
||
- [ ] 代码风格符合项目规范(无 `// ──` 分隔线)
|
||
- [ ] 没有使用禁止模式(unwrap、panic、todo 等)
|
||
- [ ] 错误处理完整(?传播、具体类型)
|
||
- [ ] 安全考虑已处理(JWT 验证模式选择正确)
|
||
- [ ] 性能影响已评估(无 N+1、无阻塞调用)
|
||
- [ ] 测试已添加(model 文件必须有测试)
|
||
- [ ] 文档已更新(新 struct 有 doc comment)
|
||
|
||
### PR 审查 / PR Review
|
||
|
||
- [ ] 提交信息符合 Angular 风格
|
||
- [ ] 每个提交只关注一个问题
|
||
- [ ] 变更范围合理
|
||
- [ ] 没有遗留的 TODO/FIXME
|
||
- [ ] `cargo check` 和 `cargo test` 通过
|
||
|
||
### 发布前审查 / Pre-release Review
|
||
|
||
- [ ] 所有测试通过
|
||
- [ ] 无 clippy warning
|
||
- [ ] 迁移 SQL 已包含在 `migrate/` 中
|
||
- [ ] 依赖安全审计通过(`cargo audit`)
|
||
|
||
---
|
||
|
||
## 附录 / Appendix
|
||
|
||
### 项目架构速查 / Quick Architecture Reference
|
||
|
||
```
|
||
imks — IM 实时消息服务 / Real-time Messaging Service
|
||
|
||
┌──────────────────────────────────────────────┐
|
||
│ imks │
|
||
│ │
|
||
│ ┌─────────────┐ ┌─────────────────────────┐ │
|
||
│ │ engine/ │ │ socket/ │ │
|
||
│ │ │ │ │ │
|
||
│ │ • websocket │ │ • server (Socket.IO) │ │
|
||
│ │ • webtransport│ │ • namespace (rooms) │ │
|
||
│ │ • polling │ │ • parser (protocol) │ │
|
||
│ │ • session │ │ • adapter (scale-out) │ │
|
||
│ │ • packet │ │ ├─ local │ │
|
||
│ │ • codec │ │ ├─ redis │ │
|
||
│ │ • heartbeat │ │ └─ nats │ │
|
||
│ │ • server │ │ • message_bus │ │
|
||
│ └─────────────┘ │ • session_store │ │
|
||
│ └─────────────────────────┘ │
|
||
│ ┌─────────────┐ ┌─────────────────────────┐ │
|
||
│ │ models/ │ │ pb/ │ │
|
||
│ │ │ │ │ │
|
||
│ │ 12 个消息 │ │ gRPC client stubs │ │
|
||
│ │ 领域模型 │ │ → appks TokenService │ │
|
||
│ │ │ │ → appks ChannelService │ │
|
||
│ │ message │ │ → appks MemberService │ │
|
||
│ │ attachment │ │ → appks PermissionService│ │
|
||
│ │ embed/poll │ │ │ │
|
||
│ │ reaction │ │ │ │
|
||
│ │ thread/pin │ │ │ │
|
||
│ │ draft/edit │ │ │ │
|
||
│ │ mention │ │ │ │
|
||
│ │ bookmark │ │ │ │
|
||
│ │ read_state │ │ │ │
|
||
│ └─────────────┘ └─────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────┐ │
|
||
│ │ migrate/ │ │
|
||
│ │ │ │
|
||
│ │ SQL 迁移 │ │
|
||
│ └─────────────┘ │
|
||
└─────────────────────┬────────────────────────┘
|
||
│ gRPC
|
||
▼
|
||
┌──────────────────────────────────────────────┐
|
||
│ appks (core) │
|
||
│ │
|
||
│ TokenService │ ChannelService │ MemberSvc │
|
||
│ PermissionSvc │ WebhookSvc │ EmojiSvc │
|
||
│ │
|
||
│ Postgres (users, channels, members, ...) │
|
||
│ Redis (JWT keys, sessions, rate limiting) │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 基础设施速查 / Infrastructure Quick Reference
|
||
|
||
| 服务 | 用途 | 协议/库 |
|
||
|---|---|---|
|
||
| Postgres | 消息数据持久化 | sqlx |
|
||
| Redis | Adapter 广播 / 会话存储 | fred |
|
||
| NATS | Adapter 广播(低延迟替代) | async-nats |
|
||
| appks gRPC | JWT 验证 / 频道/成员/权限查询 | tonic |
|
||
|
||
### 传输层对比 / Transport Comparison
|
||
|
||
| 传输 | 适用场景 | 特点 |
|
||
|------------------|-------------------|--------------------|
|
||
| **Polling** | 浏览器不支持 WS 时的降级 | 兼容性最好,延迟高 |
|
||
| **WebSocket** | 主流浏览器/移动端 | 全双工,低延迟 |
|
||
| **WebTransport** | 现代浏览器(Chrome 97+) | 基于 QUIC,多路复用,不队头阻塞 |
|
||
|
||
---
|
||
|
||
*This document is maintained by the development team. For questions or suggestions, please open an issue.*
|