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
719 lines
26 KiB
Markdown
719 lines
26 KiB
Markdown
# Auth 认证方案
|
||
|
||
## 架构总览
|
||
|
||
```
|
||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||
│ Client │ │ appks │ │ imks │
|
||
│ (浏览器/ │ │ (core) │ │ (IM服务) │
|
||
│ APP) │ │ │ │ │
|
||
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
||
│ │ │
|
||
│ 1. POST /api/v1/auth/login │
|
||
│───────────────────▶│ │
|
||
│ 2. {access_token, refresh_token} │
|
||
│◀───────────────────│ │
|
||
│ │ │
|
||
│ 3. WS/gRPC/HTTP 携带 JWT │
|
||
│──────────────────────────────────────▶│
|
||
│ │ │
|
||
│ │ 4a. VerifyToken RPC (RPC模式)
|
||
│ │◀─────────────────│
|
||
│ │ 4b. GetSigningKeys (本地模式)
|
||
│ │◀─────────────────│
|
||
│ │ │
|
||
│ │ 5. TokenClaims / SigningKeys
|
||
│ │─────────────────▶│
|
||
│ │ │
|
||
│ 6. 业务响应 │ │
|
||
│◀─────────────────────────────────────│
|
||
```
|
||
|
||
**角色分工:**
|
||
|
||
| 服务 | 职责 |
|
||
|------------------|----------------------------------------------------|
|
||
| **appks** (core) | 颁发 JWT、刷新 JWT、撤销 JWT、管理签名密钥、提供 `TokenService` gRPC |
|
||
| **imks** (IM) | 接收客户端 JWT,通过 RPC 或本地密钥验证用户身份 |
|
||
|
||
## Proto 契约
|
||
|
||
定义在 `proto/core/auth.proto`,package `appks.core.v1`。
|
||
|
||
appks 和 imks 各自维护一份相同的 proto 文件:
|
||
- appks 编译为 **server** stub(提供服务)
|
||
- imks 编译为 **client** stub(调用服务)
|
||
|
||
### TokenService RPC
|
||
|
||
```protobuf
|
||
service TokenService {
|
||
// 令牌生命周期 (appks 内部调用)
|
||
rpc IssueToken(IssueTokenRequest) returns (IssueTokenResponse);
|
||
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
|
||
rpc RevokeToken(RevokeTokenRequest) returns (RevokeTokenResponse);
|
||
|
||
// imks 验证 (RPC 模式)
|
||
rpc VerifyToken(VerifyTokenRequest) returns (VerifyTokenResponse);
|
||
|
||
// imks 密钥拉取 (本地验证模式)
|
||
rpc GetSigningKeys(GetSigningKeysRequest) returns (GetSigningKeysResponse);
|
||
}
|
||
```
|
||
|
||
## JWT 令牌
|
||
|
||
### 结构
|
||
|
||
JWT Header:
|
||
```json
|
||
{
|
||
"alg": "HS256",
|
||
"typ": "JWT",
|
||
"kid": "01909a..." // 签名密钥 ID,用于匹配 SigningKey
|
||
}
|
||
```
|
||
|
||
JWT Payload (`TokenClaims`):
|
||
```json
|
||
{
|
||
"sub": "user-uuid",
|
||
"iss": "appks",
|
||
"iat": 1718000000,
|
||
"exp": 1718003600,
|
||
"jti": "01909b...",
|
||
"scope": "im:read im:write",
|
||
"extra": {
|
||
"workspace_id": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### 令牌类型
|
||
|
||
| 类型 | 格式 | 存储 | 用途 |
|
||
|-------------------|----------------------|----------------------------------------------|---------------------------------------|
|
||
| **access_token** | JWT (HS256) | 无状态,客户端持有 | 每次请求携带,验证用户身份 |
|
||
| **refresh_token** | `rt_{UUIDv7}` 不透明字符串 | Redis `core:token:refresh:{token}` → user_id | 换取新的 access_token + refresh_token(旋转) |
|
||
|
||
## 双模式验证
|
||
|
||
imks 可选择以下任一模式验证客户端 JWT:
|
||
|
||
### 模式 A:RPC 验证(`VerifyToken`)
|
||
|
||
```
|
||
imks → appks TokenService.VerifyToken(jwt) → {valid, claims}
|
||
```
|
||
|
||
- **优点**:实时权威,能感知撤销
|
||
- **缺点**:每次请求增加一次 RPC 往返
|
||
- **适用场景**:高安全要求操作(管理员操作、敏感数据)
|
||
|
||
### 模式 B:本地验证(`GetSigningKeys`)
|
||
|
||
```
|
||
imks 启动时 → appks TokenService.GetSigningKeys() → 缓存密钥到本地
|
||
后续请求 → imks 用本地密钥解码 JWT(HS256 验签)
|
||
定期刷新 → 根据 next_rotation_at 拉取新密钥
|
||
```
|
||
|
||
- **优点**:零 RPC 延迟,appks 不可用时仍能验证
|
||
- **缺点**:撤销有最多一个密钥窗口(3h)的延迟
|
||
- **适用场景**:高频低延迟操作(消息收发、实时通信)
|
||
|
||
### 推荐策略
|
||
|
||
混合使用:
|
||
- 普通操作(发消息、读频道)→ 本地验证
|
||
- 敏感操作(踢人、删频道、改权限)→ RPC 验证
|
||
|
||
## 签名密钥管理
|
||
|
||
### 密钥窗口
|
||
|
||
```
|
||
时间轴:
|
||
─────────┬──────────┬──────────┬────────
|
||
│ key A │ key B │ key C
|
||
│ (过期) │ (活跃) │ (未来)
|
||
└──────────┴──────────┴────────
|
||
issued_at issued_at issued_at
|
||
+3h +3h +3h
|
||
```
|
||
|
||
- 每个签名密钥有效期 **3 小时**
|
||
- 同一时刻可能有 **2 个有效密钥**(滚动窗口,平滑过渡)
|
||
- JWT header 的 `kid` 字段标识使用哪个密钥签名
|
||
|
||
### 密钥轮换流程
|
||
|
||
```
|
||
1. 当前密钥到达 3h → TokenService.rotate_if_needed()
|
||
2. Redis 分布式锁 (core:token:rotation_lock, 10s TTL) 防止多实例竞争
|
||
3. 旧密钥标记 active=false,仍保留在 Redis 用于验证旧 token
|
||
4. 生成新密钥,active=true
|
||
5. ArcSwap 原子替换当前签名密钥
|
||
6. 旧密钥 TTL = 6h (2× window) 后从 Redis 自动清除
|
||
```
|
||
|
||
### 密钥存储(Redis)
|
||
|
||
```
|
||
core:token:active_key → kid (当前活跃密钥 ID)
|
||
core:token:key:{kid} → SigningKey JSON (TTL = 6h)
|
||
core:token:rotation_lock → "1" (TTL = 10s, 分布式锁)
|
||
```
|
||
|
||
### SigningKey 结构
|
||
|
||
```rust
|
||
pub struct SigningKeyInfo {
|
||
pub kid: String, // UUIDv7
|
||
pub algorithm: String, // "HS256"
|
||
pub key_material: String, // base64(32 bytes random)
|
||
pub issued_at: i64,
|
||
pub expires_at: i64, // issued_at + 3h
|
||
pub active: bool,
|
||
}
|
||
```
|
||
|
||
## 撤销机制
|
||
|
||
### Redis 布局
|
||
|
||
```
|
||
core:token:revoked:{jti} → "1" (TTL = token 剩余有效期)
|
||
core:token:refresh:{token} → user_id (TTL = 7d)
|
||
```
|
||
|
||
### 撤销方式
|
||
|
||
| 操作 | RPC | 效果 |
|
||
|--------------|------------------------|-----------------------|
|
||
| 撤销单个 token | `RevokeToken(jti)` | 将 jti 加入撤销列表 |
|
||
| 撤销用户所有 token | `RevokeToken(user_id)` | 删除该用户所有 refresh token |
|
||
|
||
### 撤销感知延迟
|
||
|
||
| 验证模式 | 延迟 |
|
||
|-----------------------|------------------------------|
|
||
| RPC (`VerifyToken`) | **实时** — 每次检查撤销列表 |
|
||
| 本地 (`GetSigningKeys`) | **最多 3h** — 密钥过期前无法感知 jti 撤销 |
|
||
|
||
## appks 实现
|
||
|
||
### 模块结构
|
||
|
||
```
|
||
service/internal_auth.rs → TokenService (业务逻辑)
|
||
grpc/auth.rs → TokenGrpcService (gRPC handler)
|
||
grpc/mod.rs → TokenServiceServer 注册到 tonic server
|
||
api/internal/issue_api_key.rs → REST: POST /api/v1/internal/tokens
|
||
```
|
||
|
||
### TokenService 核心
|
||
|
||
```rust
|
||
pub struct TokenService {
|
||
redis: AppRedis,
|
||
current_key: Arc<ArcSwap<SigningKeyInfo>>, // 无锁读
|
||
}
|
||
```
|
||
|
||
- 启动时从 Redis 加载活跃密钥,无则生成
|
||
- 签名使用 `jsonwebtoken` crate (HS256)
|
||
- 密钥轮换使用 Redis 分布式锁,支持多实例部署
|
||
- `ArcSwap` 保证签名密钥读取无锁、写入原子
|
||
|
||
## imks 实现指南
|
||
|
||
### 启动流程
|
||
|
||
```rust
|
||
// 1. 连接 appks TokenService
|
||
let mut token_client = TokenServiceClient::connect(appks_addr).await?;
|
||
|
||
// 2. 拉取签名密钥
|
||
let resp = token_client.get_signing_keys(GetSigningKeysRequest { kid: "" }).await?;
|
||
let keys = resp.keys;
|
||
let next_rotation = resp.next_rotation_at;
|
||
|
||
// 3. 缓存密钥到本地 (HashMap<kid, SigningKey>)
|
||
key_store.insert_all(keys);
|
||
|
||
// 4. 安排定时刷新
|
||
tokio::spawn(async move {
|
||
loop {
|
||
let delay = next_rotation - now();
|
||
tokio::time::sleep(Duration::from_secs(delay as u64)).await;
|
||
let resp = token_client.get_signing_keys(...).await;
|
||
key_store.update(resp.keys);
|
||
}
|
||
});
|
||
```
|
||
|
||
### 连接时验证
|
||
|
||
```rust
|
||
// 客户端建立 WebSocket/gRPC 连接时携带 JWT
|
||
fn on_connect(headers: &Headers) -> Result<TokenClaims, AuthError> {
|
||
let token = headers.get("Authorization")
|
||
.and_then(|v| v.strip_prefix("Bearer "))
|
||
.ok_or(AuthError::MissingToken)?;
|
||
|
||
// 本地验证 (快速路径)
|
||
let header = decode_header(token)?;
|
||
let kid = header.kid.ok_or(AuthError::MissingKid)?;
|
||
let key = key_store.get(&kid).ok_or(AuthError::UnknownKey)?;
|
||
|
||
let mut validation = Validation::new(Algorithm::HS256);
|
||
validation.set_issuer(&["appks"]);
|
||
validation.validate_exp = true;
|
||
|
||
let data = decode::<TokenClaims>(token, &key, &validation)?;
|
||
Ok(data.claims)
|
||
}
|
||
```
|
||
|
||
### 敏感操作验证
|
||
|
||
```rust
|
||
// 敏感操作走 RPC 验证 (权威路径)
|
||
async fn on_sensitive_action(token: &str) -> Result<TokenClaims, AuthError> {
|
||
let resp = token_client.verify_token(VerifyTokenRequest {
|
||
token: token.to_string(),
|
||
}).await?;
|
||
|
||
if resp.valid {
|
||
Ok(resp.claims.unwrap())
|
||
} else {
|
||
Err(AuthError::from(resp.reason))
|
||
}
|
||
}
|
||
```
|
||
|
||
## 安全考虑
|
||
|
||
1. **密钥传输**:appks → imks 的 gRPC 连接应使用 mTLS,防止密钥在传输中被截获
|
||
2. **密钥生命周期**:3h 窗口平衡了安全性和可用性;缩短窗口可减少撤销延迟但增加轮换频率
|
||
3. **HS256 vs 非对称**:当前使用 HS256(对称密钥),imks 拿到的密钥可以伪造 token。如果 imks 不可完全信任,应改用 RS256/EdDSA,imks 只持有公钥
|
||
4. **Refresh Token 安全**:每次刷新都旋转(旧 token 立即失效),防止重放
|
||
5. **撤销列表 TTL**:与 token 剩余有效期对齐,过期 token 无需保留撤销记录
|
||
|
||
---
|
||
|
||
# IM 服务 Proto 说明书
|
||
|
||
以下是 imks 侧 `proto/core/` 下各 gRPC 服务的完整说明。所有 IM 服务定义在 `appks.im.v1` 包下,由 appks 提供 server 端,imks 消费 client 端。
|
||
|
||
## 服务总览
|
||
|
||
| Proto 文件 | 服务 | RPC 数量 | 职责 |
|
||
|---|---|---|---|
|
||
| `auth.proto` | TokenService | 5 | JWT 令牌生命周期 + 验证 + 密钥分发 |
|
||
| `channel.proto` | ChannelService | 10 | 频道/分类 CRUD + 统计 |
|
||
| `member.proto` | MemberService | 7 | 成员邀请/踢出/加入/离开/查询 |
|
||
| `permission.proto` | PermissionService | 7 | 权限检查 + 覆盖规则 + 频道解析 |
|
||
| `channel_settings.proto` | ChannelRoleService | 4 | 频道自定义角色 |
|
||
| | ChannelInvitationService | 4 | 邀请生命周期 |
|
||
| | ChannelWebhookService | 4 | Webhook CRUD |
|
||
| | ChannelSlashCommandService | 4 | 斜杠命令注册 |
|
||
| | ChannelRepoLinkService | 3 | 频道 ↔ 代码仓库关联 |
|
||
| | ImIntegrationService | 4 | 外部平台集成(Slack/Discord 等) |
|
||
| | CustomEmojiService | 3 | 工作区自定义表情 |
|
||
| | ForumTagService | 4 | 论坛频道标签 |
|
||
| | VoiceService | 2 | 语音频道参与者状态 |
|
||
| | StageService | 4 | 舞台频道管理 |
|
||
| | ChannelAuditService | 1 | 频道审计日志查询 |
|
||
|
||
---
|
||
|
||
## ChannelService(`channel.proto`)
|
||
|
||
频道和分类的 CRUD 管理,以及频道统计。
|
||
|
||
### 枚举
|
||
|
||
**ChannelType** — 频道类型:
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| `PUBLIC` | 公开频道,workspace 内所有人可见 |
|
||
| `PRIVATE` | 私有频道,仅被邀请成员可见 |
|
||
| `DIRECT` | 私聊(一对一) |
|
||
| `GROUP` | 群聊(多人私聊) |
|
||
| `REPO` | 仓库关联频道(自动与 git repo 绑定) |
|
||
| `SYSTEM` | 系统频道(公告、通知等,只读) |
|
||
|
||
**ChannelKind** — 频道形态:
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| `TEXT` | 文本频道 |
|
||
| `VOICE` | 语音频道 |
|
||
| `STAGE` | 舞台频道(主持人+观众模式) |
|
||
| `FORUM` | 论坛频道(帖子/主题式讨论) |
|
||
| `ANNOUNCEMENT` | 公告频道(仅管理员可发消息) |
|
||
|
||
**Visibility** — 可见性级别(从低到高):
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| `PUBLIC` | 所有人可见(含未登录用户) |
|
||
| `WORKSPACE` | workspace 成员可见 |
|
||
| `INTERNAL` | 内部可见(组织成员) |
|
||
| `PRIVATE` | 仅频道成员可见 |
|
||
| `PROTECTED` | 受保护(不可被搜索/索引) |
|
||
| `HIDDEN` | 隐藏(不显示在频道列表中) |
|
||
| `SECRET` | 机密(仅通过直链访问) |
|
||
|
||
### RPC 列表
|
||
|
||
```
|
||
GetChannel(channel_id) → Channel 获取频道详情
|
||
ListChannels(workspace, ...) → [Channel], total 列出频道(支持分类/类型/形态过滤)
|
||
CreateChannel(workspace, name) → Channel 创建频道
|
||
UpdateChannel(channel_id, ...) → Channel 更新频道属性
|
||
DeleteChannel(channel_id) → {} 删除频道
|
||
GetChannelStats(channel_id) → ChannelStats 获取频道统计(成员/消息/线程/反应数)
|
||
|
||
ListCategories(workspace) → [ChannelCategory] 列出分类
|
||
CreateCategory(workspace, name) → ChannelCategory 创建分类
|
||
UpdateCategory(category_id, ...) → ChannelCategory 更新分类
|
||
DeleteCategory(category_id) → {} 删除分类
|
||
```
|
||
|
||
### 核心消息
|
||
|
||
**Channel** — 频道主体:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `id` | UUID | 频道 ID |
|
||
| `workspace_id` | UUID | 所属 workspace |
|
||
| `category_id` | UUID? | 所属分类(可选) |
|
||
| `parent_channel_id` | UUID? | 父频道(用于子频道/线程) |
|
||
| `name` | string | 频道名称 |
|
||
| `topic` / `description` | string? | 主题 / 描述 |
|
||
| `channel_type` | ChannelType | 频道类型 |
|
||
| `channel_kind` | ChannelKind | 频道形态 |
|
||
| `visibility` | Visibility | 可见性 |
|
||
| `position` | int32 | 排序位置 |
|
||
| `nsfw` | bool | NSFW 标记 |
|
||
| `read_only` | bool | 只读(仅管理员可发消息) |
|
||
| `archived` | bool | 已归档 |
|
||
| `rate_limit_per_user` | int32? | 慢速模式(秒/消息) |
|
||
| `last_message_id` / `last_message_at` | — | 最后一条消息信息 |
|
||
|
||
**ChannelStats** — 频道统计:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `members_count` | 成员数 |
|
||
| `messages_count` | 消息数 |
|
||
| `threads_count` | 线程数 |
|
||
| `reactions_count` | 反应数 |
|
||
| `mentions_count` | @提及数 |
|
||
| `files_count` | 文件数 |
|
||
| `last_activity_at` | 最后活跃时间 |
|
||
|
||
---
|
||
|
||
## MemberService(`member.proto`)
|
||
|
||
频道成员管理。
|
||
|
||
### 枚举
|
||
|
||
**Role** — 角色层级(从高到低):
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| `OWNER` | 频道所有者 |
|
||
| `ADMIN` | 管理员(全部权限) |
|
||
| `MAINTAINER` | 维护者(管理频道设置、成员) |
|
||
| `MODERATOR` | 版主(管理消息、踢人) |
|
||
| `MEMBER` | 普通成员 |
|
||
| `CONTRIBUTOR` | 贡献者(可发消息,部分限制) |
|
||
| `VIEWER` | 观察者(只读) |
|
||
| `GUEST` | 访客(临时访问) |
|
||
| `BOT` | 机器人 |
|
||
|
||
**MemberStatus** — 成员状态:
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| `ACTIVE` | 活跃成员 |
|
||
| `INVITED` | 已邀请(尚未加入) |
|
||
| `LEFT` | 已离开 |
|
||
| `KICKED` | 被踢出 |
|
||
| `BANNED` | 被封禁 |
|
||
|
||
### RPC 列表
|
||
|
||
```
|
||
ListMembers(channel_id, ...) → [ChannelMember], total 列出成员(支持状态过滤)
|
||
InviteMember(channel_id, user_id) → ChannelMember 邀请用户加入频道
|
||
UpdateMember(channel_id, user_id) → ChannelMember 更新成员(角色/禁言/置顶)
|
||
KickMember(channel_id, user_id) → {} 踢出成员
|
||
JoinChannel(channel_id, user_id) → ChannelMember 用户主动加入
|
||
LeaveChannel(channel_id, user_id) → {} 用户主动离开
|
||
IsMember(channel_id, user_id) → is_member, role 检查是否为成员
|
||
```
|
||
|
||
### 核心消息
|
||
|
||
**ChannelMember** — 频道成员:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `channel_id` / `user_id` | 频道 + 用户 |
|
||
| `role` | 角色(Role 枚举值字符串) |
|
||
| `status` | 状态(MemberStatus 枚举值字符串) |
|
||
| `muted` | 是否被禁言 |
|
||
| `pinned` | 是否被置顶(频道侧标记) |
|
||
| `last_read_message_id` / `last_read_at` | 已读进度 |
|
||
| `joined_at` / `left_at` | 加入/离开时间 |
|
||
|
||
---
|
||
|
||
## PermissionService(`permission.proto`)
|
||
|
||
频道级权限系统,独立于 workspace/repo 的通用权限。
|
||
|
||
### 权限枚举(ImPermission)
|
||
|
||
| 权限 | 说明 |
|
||
|---|---|
|
||
| `READ_CHANNEL` | 查看频道 |
|
||
| `SEND_MESSAGE` | 发送消息 |
|
||
| `MANAGE_THREADS` | 管理线程 |
|
||
| `MANAGE_REACTIONS` | 管理反应 |
|
||
| `MANAGE_PINS` | 管理置顶消息 |
|
||
| `INVITE_MEMBERS` | 邀请成员 |
|
||
| `KICK_MEMBERS` | 踢出成员 |
|
||
| `MANAGE_CHANNEL` | 管理频道设置 |
|
||
| `MANAGE_ROLES` | 管理角色 |
|
||
| `MANAGE_WEBHOOKS` | 管理 Webhook |
|
||
| `MANAGE_EMOJIS` | 管理自定义表情 |
|
||
| `VIEW_AUDIT_LOG` | 查看审计日志 |
|
||
| `MANAGE_INTEGRATIONS` | 管理外部集成 |
|
||
| `SEND_TTS` | 发送 TTS 消息 |
|
||
| `USE_SLASH_COMMANDS` | 使用斜杠命令 |
|
||
| `ATTACH_FILES` | 上传文件 |
|
||
| `MENTION_EVERYONE` | @所有人 |
|
||
| `MANAGE_MESSAGES` | 管理消息(删除他人消息) |
|
||
| `ADMIN` | 管理员(拥有所有权限) |
|
||
|
||
### RPC 列表
|
||
|
||
```
|
||
CheckPermission(channel, user, perm) → allowed, role 检查单项权限
|
||
GetPermissions(channel, user) → [ImPermission] 获取用户全部权限
|
||
SetPermissionOverwrite(channel, target) → Overwrite 设置权限覆盖
|
||
GetPermissionOverwrites(channel) → [Overwrite] 获取覆盖列表
|
||
DeletePermissionOverwrite(channel, target) → {} 删除覆盖
|
||
|
||
ResolveChannel(channel_id) → 频道摘要信息 解析频道元数据
|
||
EnsureReadable(channel, user) → allowed 确保用户可读(快速检查)
|
||
```
|
||
|
||
### 权限覆盖(PermissionOverwrite)
|
||
|
||
权限覆盖允许对特定用户/角色在特定频道上覆盖默认权限:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `target_type` | `"user"` 或 `"role"` |
|
||
| `target_id` | 用户 ID 或角色 ID |
|
||
| `allow` | 显式允许的权限列表 |
|
||
| `deny` | 显式拒绝的权限列表 |
|
||
|
||
权限解析优先级:`deny 覆盖 > allow 覆盖 > 角色权限`
|
||
|
||
---
|
||
|
||
## ChannelSettings 服务组(`channel_settings.proto`)
|
||
|
||
所有频道配置相关的服务定义在同一个 proto 文件中。
|
||
|
||
### ChannelRoleService — 频道自定义角色
|
||
|
||
频道级别的自定义角色(不同于 `member.proto` 中的全局 Role 枚举)。
|
||
|
||
```
|
||
ListChannelRoles(channel_id) → [ChannelRole]
|
||
CreateChannelRole(channel, name) → ChannelRole
|
||
UpdateChannelRole(role_id, ...) → ChannelRole
|
||
DeleteChannelRole(role_id) → {}
|
||
```
|
||
|
||
**ChannelRole** 字段:`name`, `permissions[]`(ImPermission 字符串列表), `assignable`(是否可被普通成员分配)
|
||
|
||
### ChannelInvitationService — 邀请管理
|
||
|
||
```
|
||
ListInvitations(channel_id) → [ChannelInvitation]
|
||
CreateInvitation(channel, user) → ChannelInvitation
|
||
AcceptInvitation(invitation_id) → ChannelInvitation
|
||
RevokeInvitation(invitation_id) → {}
|
||
```
|
||
|
||
**ChannelInvitation** 字段:`invited_by`, `invited_user_id`, `role`(预设角色), `status`
|
||
|
||
### ChannelWebhookService — Webhook 管理
|
||
|
||
```
|
||
ListWebhooks(channel_id) → [ChannelWebhook]
|
||
CreateWebhook(channel, name, url) → ChannelWebhook
|
||
UpdateWebhook(webhook_id, ...) → ChannelWebhook
|
||
DeleteWebhook(webhook_id) → {}
|
||
```
|
||
|
||
**ChannelWebhook** 字段:`name`, `url`, `secret`(签名验证用), `events[]`(订阅事件列表), `active`
|
||
|
||
### ChannelSlashCommandService — 斜杠命令注册
|
||
|
||
```
|
||
ListSlashCommands(channel_id) → [ChannelSlashCommand]
|
||
CreateSlashCommand(channel, cmd, url) → ChannelSlashCommand
|
||
UpdateSlashCommand(command_id, ...) → ChannelSlashCommand
|
||
DeleteSlashCommand(command_id) → {}
|
||
```
|
||
|
||
**ChannelSlashCommand** 字段:`command`(命令名如 `/deploy`), `description`, `request_url`(回调地址), `scopes[]`
|
||
|
||
### ChannelRepoLinkService — 仓库关联
|
||
|
||
将频道与代码仓库关联,自动推送仓库事件到频道。
|
||
|
||
```
|
||
ListRepoLinks(channel_id) → [ChannelRepoLink]
|
||
CreateRepoLink(channel, repo, type) → ChannelRepoLink
|
||
DeleteRepoLink(link_id) → {}
|
||
```
|
||
|
||
**ChannelRepoLink** 字段:`repo_id`, `link_type`, `events[]`(订阅的仓库事件:push、pr、issue 等)
|
||
|
||
### ImIntegrationService — 外部平台集成
|
||
|
||
与 Slack、Discord 等外部平台的消息同步。
|
||
|
||
```
|
||
ListIntegrations(channel_id) → [ImIntegration]
|
||
CreateIntegration(channel, provider, ...)→ ImIntegration
|
||
UpdateIntegration(integration_id, ...) → ImIntegration
|
||
DeleteIntegration(integration_id) → {}
|
||
```
|
||
|
||
**ImIntegration** 字段:`provider`(平台名), `external_channel_id`(外部频道 ID), `sync_direction`(`inbound`/`outbound`/`bidirectional`), `active`
|
||
|
||
### CustomEmojiService — 自定义表情
|
||
|
||
工作区级别的自定义表情管理。
|
||
|
||
```
|
||
ListCustomEmojis(workspace_id) → [CustomEmoji]
|
||
CreateCustomEmoji(workspace, name, url) → CustomEmoji
|
||
DeleteCustomEmoji(emoji_id) → {}
|
||
```
|
||
|
||
**CustomEmoji** 字段:`workspace_id`, `name`(表情名如 `:appks:`), `image_url`
|
||
|
||
### ForumTagService — 论坛标签
|
||
|
||
论坛频道(`ChannelKind::FORUM`)的帖子分类标签。
|
||
|
||
```
|
||
ListForumTags(channel_id) → [ForumTag]
|
||
CreateForumTag(channel, name, ...) → ForumTag
|
||
UpdateForumTag(tag_id, ...) → ForumTag
|
||
DeleteForumTag(tag_id) → {}
|
||
```
|
||
|
||
**ForumTag** 字段:`name`, `moderated`(是否需要管理员审核), `position`
|
||
|
||
### VoiceService — 语音频道
|
||
|
||
语音频道的参与者状态管理。
|
||
|
||
```
|
||
ListVoiceParticipants(channel_id) → [VoiceParticipant]
|
||
UpdateVoiceState(channel, user, ...) → VoiceParticipant
|
||
```
|
||
|
||
**VoiceParticipant** 字段:`user_id`, `muted`(静音), `deafened`(屏蔽音频), `joined_at`
|
||
|
||
### StageService — 舞台频道
|
||
|
||
舞台频道(`ChannelKind::STAGE`)的管理。主持人说话,观众收听。
|
||
|
||
```
|
||
GetStage(channel_id) → Stage
|
||
CreateStage(channel, topic, ...) → Stage
|
||
UpdateStage(stage_id, ...) → Stage
|
||
DeleteStage(stage_id) → {}
|
||
```
|
||
|
||
**Stage** 字段:`topic`(当前话题), `privacy_level`, `discoverable`(是否可被发现), `started_at` / `ended_at`
|
||
|
||
### ChannelAuditService — 审计日志
|
||
|
||
频道操作审计日志查询(只读)。
|
||
|
||
```
|
||
ListChannelEvents(channel_id, ...) → [ChannelAuditEvent], total
|
||
```
|
||
|
||
**ChannelAuditEvent** 字段:`actor_id`(操作者), `event_type`(事件类型字符串), `target_type` / `target_id`(操作对象), `old_value` / `new_value`(变更前后值)
|
||
|
||
---
|
||
|
||
## imks 与 appks 的调用关系
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ imks │
|
||
│ │
|
||
│ Socket.IO / WebSocket / WebTransport │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 连接握手 ──→ TokenService.VerifyToken() 或 本地密钥验证 │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 消息收发 ──→ ChannelService + MemberService │
|
||
│ │ PermissionService.EnsureReadable() │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 频道管理 ──→ ChannelService CRUD │
|
||
│ │ ChannelRoleService │
|
||
│ │ ChannelInvitationService │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 语音/舞台 ──→ VoiceService + StageService │
|
||
│ │
|
||
│ 集成/扩展 ──→ WebhookService + SlashCommandService │
|
||
│ RepoLinkService + ImIntegrationService │
|
||
│ │
|
||
│ 审计查询 ──→ ChannelAuditService │
|
||
└────────────────────────┬───────────────────────────────────┘
|
||
│ gRPC
|
||
▼
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ appks │
|
||
│ TokenService server │ Channel/Member/Permission server │
|
||
│ Redis (JWT keys) │ Postgres (channel data) │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### imks 本地缓存建议
|
||
|
||
| 数据 | 缓存策略 | 刷新时机 |
|
||
|--------------------------|--------------|--------------------------|
|
||
| 签名密钥 (`SigningKey[]`) | 内存 HashMap | `next_rotation_at` 到达时拉取 |
|
||
| 频道信息 (`Channel`) | LRU + TTL | 频道更新事件 (NATS) |
|
||
| 成员列表 (`ChannelMember[]`) | LRU + TTL | 成员变更事件 (NATS) |
|
||
| 权限缓存 | 短期 TTL (30s) | 权限变更事件 (NATS) |
|
||
| 自定义表情 | 全量加载 + 事件增量 | emoji 增删事件 (NATS) |
|