mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 06:36:38 +08:00
refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps
- Merge all files inside openflare-server to the repository root directory. - Relocate agent, relay, and flared subprojects from internal/ to internal/apps/. - Combine docker-compose files and update build context paths to root. - Update GitHub workflows and Dockerfiles to refer to new directories and package names. - Rewrite Go package imports across all files. - Resolve database renew test race condition and clean up docs.
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
# wavelet 部署指南
|
||||
|
||||
本文档详细介绍了 **wavelet** 脚手架系统在不同业务阶段的部署方案,涵盖从**最小化单机部署**到**最大化高可用分布式部署**的全生命周期架构。
|
||||
|
||||
---
|
||||
|
||||
## 一、 系统组件概览
|
||||
|
||||
在部署系统前,请了解各运行组件及其角色:
|
||||
|
||||
| 组件名称 | 运行命令/形式 | 职责说明 | 必选/可选 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **HTTP API 服务** | `bin/wavelet api` | 接收并处理前端及第三方的 RESTful API 请求 | **必选** |
|
||||
| **异步任务工作进程** | `bin/wavelet worker` | 消费并处理异步队列任务(如邮件发送、清理上传文件等) | **必选** |
|
||||
| **定时任务调度器** | `bin/wavelet scheduler` | 定时向 Redis 队列下发 Cron 任务(仅负责触发,不负责执行) | **必选** |
|
||||
| **前端服务 (Node.js)** | `pnpm start` | 提供 React/Next.js 页面服务(在分离部署时使用) | 分离模式必选 |
|
||||
| **PostgreSQL** | 关系型主数据库 | 存储用户、系统配置、认证源、任务执行记录等核心数据 | **必选** |
|
||||
| **Redis** | 缓存与消息队列中间件 | 存储 Session 会话、临时缓存以及 Asynq 异步任务队列数据 | **必选** |
|
||||
| **ClickHouse** | 分析型数据库 | 存储历史数据同步或进行高性能分析 | 可选 |
|
||||
| **对象存储 (S3)** | 兼容 S3 的云存储/私有云 | 存放用户上传的静态文件、图片等 | 可选 |
|
||||
|
||||
---
|
||||
|
||||
## 二、 部署配置准备
|
||||
|
||||
系统在启动前会从当前目录加载 `config.yaml` 配置文件。
|
||||
生产环境部署前,请复制 `config.example.yaml` 为 `config.yaml`,并至少确认以下关键参数的配置:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
env: "production" # 生产环境标识
|
||||
addr: ":8000" # API 服务监听端口
|
||||
session_secret: "prod-random-secret" # 极其重要的加密密钥,首发启动后不可更改
|
||||
session_domain: ".yourdomain.com" # 跨域共享 Session 时需配置
|
||||
|
||||
database:
|
||||
host: "db.yourdomain.com"
|
||||
port: 5432
|
||||
username: "postgres"
|
||||
password: "YOUR_DB_PASSWORD"
|
||||
database: "refreshing"
|
||||
|
||||
redis:
|
||||
addrs:
|
||||
- "redis.yourdomain.com:6379"
|
||||
password: "YOUR_REDIS_PASSWORD"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、 方案一:最小部署 — 单机嵌入式极简版 (推荐)
|
||||
|
||||
此部署方案将**前端静态网页全部直接打入 Go 后端二进制文件中**,极大地简化了部署运维,是中小型应用、内部系统、SaaS 早期阶段的首选。
|
||||
|
||||
### 📊 架构设计
|
||||
- **服务载体**:单台云服务器 (1核2G 即可)。
|
||||
- **依赖服务**:在一台机器上启动轻量级 PostgreSQL 与 Redis(可采用 Docker 部署)。
|
||||
- **进程管理**:在一台机器上直接拉起打包好的 Go 单文件,并分别运行 `api`、`worker`、`scheduler` 进程。
|
||||
- **前端托管**:Go 服务直接在 8000 端口承载前端的所有页面,不需要额外配置 Node.js 生产服务器。
|
||||
|
||||
### 🛠️ 步骤说明
|
||||
|
||||
#### 1. 单机依赖服务初始化 (使用 Docker Compose)
|
||||
在机器上准备以下 `docker-compose.yml` 快速启动 PostgreSQL 和 Redis:
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: refreshing-db
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: YOUR_DB_PASSWORD
|
||||
POSTGRES_DB: refreshing
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./data/pg:/var/lib/postgresql/data
|
||||
restart: always
|
||||
|
||||
redis:
|
||||
image: valkey/valkey:8.0-alpine
|
||||
container_name: refreshing-redis
|
||||
command: valkey-server --requirepass YOUR_REDIS_PASSWORD
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
restart: always
|
||||
```
|
||||
执行命令启动:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### 2. 前后端一键嵌入式打包
|
||||
在开发或编译机上,运行编译指令:
|
||||
```bash
|
||||
make build-embedded
|
||||
```
|
||||
该命令会自动完成前端的静态编译导出 (`frontend/out`)、复制到 Go 后端目录,最后使用 `-tags embed_frontend` 生成后端单文件:
|
||||
- 产物路径:`bin/wavelet`
|
||||
|
||||
#### 3. 进程管理 (使用 Systemd)
|
||||
将 `bin/wavelet` 拷贝到生产服务器 `/usr/local/bin/wavelet`,并为 `api`、`worker` 和 `scheduler` 配置 Systemd 管理服务。
|
||||
|
||||
新建 API 进程服务文件 `/etc/systemd/system/wavelet-api.service`:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Refreshing API Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/app
|
||||
ExecStart=/usr/local/bin/wavelet api
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
同理,新建 Worker 服务 `/etc/systemd/system/wavelet-worker.service`(将命令改为 `wavelet worker`),以及 Scheduler 服务 `/etc/systemd/system/wavelet-scheduler.service`(将命令改为 `wavelet scheduler`)。
|
||||
|
||||
启动并启用所有服务:
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now refreshing-api refreshing-worker refreshing-scheduler
|
||||
```
|
||||
|
||||
#### 4. 配置 Nginx 证书
|
||||
配置 Nginx 作为反向代理并启用 HTTPS 证书:
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name yourdomain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name yourdomain.com;
|
||||
|
||||
ssl_certificate /path/to/cert.crt;
|
||||
ssl_certificate_key /path/to/cert.key;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、 方案二:标准部署 — 前后端物理分离架构
|
||||
|
||||
此方案中前端与后端彻底解耦。前端采用 SSR/ISR (Next.js Node 服务) 运行,后端采用独立的 API 服务运行。
|
||||
|
||||
### 📊 架构设计
|
||||
- **前端部署**:单独部署到 Node.js 托管环境(如多台前端机器或 Vercel/Cloudflare Pages)。
|
||||
- **后端部署**:多台后端云服务器,统一指向云数据库 RDS 与云缓存 Redis。
|
||||
- **通信方式**:前后端通过 Nginx 规则路由或独立域名(如 `app.yourdomain.com` 访问前端,`api.yourdomain.com` 访问后端)进行跨域通信。
|
||||
|
||||
### 🛠️ 步骤说明
|
||||
|
||||
#### 1. 部署后端 Go 服务
|
||||
1. 编译后端:
|
||||
```bash
|
||||
go build -o bin/wavelet main.go
|
||||
```
|
||||
2. 在后端服务器上,同样使用 Systemd 或 Docker 守护启动 `wavelet api`、`wavelet worker` 和 `wavelet scheduler`。
|
||||
3. 配置后端 Nginx 将客户端 API 请求(如 `/api/...`)反向代理至后端绑定的端口(如 `:8000`)。
|
||||
|
||||
#### 2. 部署前端 Next.js 服务
|
||||
1. 前端服务器环境确保已安装 Node.js 和 pnpm。
|
||||
2. 安装依赖并编译生产版本:
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
3. 使用 PM2 守护前端 Node.js 服务运行。新建 `ecosystem.config.js`:
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'refreshing-frontend',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
args: 'start -p 3000',
|
||||
instances: 'max',
|
||||
exec_mode: 'cluster',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
WAVELET_BACKEND_URL: 'https://api.yourdomain.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
启动前端服务:
|
||||
```bash
|
||||
pm2 start ecosystem.config.js
|
||||
```
|
||||
|
||||
#### 3. 跨域与 Cookie 说明
|
||||
- 若前后端使用**不同子域名**部署(例如 `app.yourdomain.com` 和 `api.yourdomain.com`),必须在 `config.yaml` 中将 `app.session_domain` 显式设置为顶级域名(`.yourdomain.com`),以确保 Session Cookie 可以在子域间顺利透传。
|
||||
- 在跨域状态下,前端请求必须配置 `withCredentials: true`,API 端的跨域中间件(`corsMiddleware`)会自动将该域添加至允许源中。
|
||||
|
||||
---
|
||||
|
||||
## 五、 方案三:最大部署 — 企业级高可用分布式架构 (Max)
|
||||
|
||||
当系统面临高并发流量、海量后台任务或极高的可用性要求时,需要将所有组件拆分为无状态水平扩容,并引入高可用的云基础设施。
|
||||
|
||||
### 📊 架构设计图
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ 域名 / 负载均衡器 │
|
||||
│ (SLB / Cloudflare) │
|
||||
└──────────┬─────────────┘
|
||||
│
|
||||
┌──────────────────┴──────────────────┐
|
||||
▼ ▼
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 前端集群 │ │ 后端 API 集群 │
|
||||
│ (Next.js Node) │ │ (Go 无状态实例) │
|
||||
│ [弹性扩容 / 8台+] │ │ [弹性扩容 / 8台+] │
|
||||
└─────────────────────┘ └──────────┬──────────┘
|
||||
│
|
||||
┌────────────────────────────────────────┼────────────────────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
|
||||
│ 异步 Worker 集群 │ │ 定时 Scheduler │ │ S3 对象存储集群 │
|
||||
│ (多节点并发处理) │ │ (主备模式,限单节点)│ │(R2/MinIO/AWS S3) │
|
||||
└─────────┬─────────┘ └─────────┬─────────┘ └───────────────────┘
|
||||
│ │
|
||||
└───────────────────┬────────────────────┘
|
||||
│
|
||||
┌───────────────────┴────────────────────┐
|
||||
▼ ▼
|
||||
┌───────────────────────────────────┐ ┌───────────────────────────────────┐
|
||||
│ Redis 哨兵/集群 │ │ PG 主从读写分离集群 │
|
||||
│ (高可用缓存/Asynq 队列) │ │ (RDS Primary-Replica) │
|
||||
└───────────────────────────────────┘ └───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### ⚙️ 最大部署配置要点
|
||||
|
||||
#### 1. 数据库高可用 (主从读写分离)
|
||||
在 `config.yaml` 中配置 `database` 的主库写与从库读:
|
||||
```yaml
|
||||
database:
|
||||
enabled: true
|
||||
host: "pg-primary.yourdomain.com" # 主库地址(写)
|
||||
port: 5432
|
||||
username: "postgres"
|
||||
password: "YOUR_DB_PASSWORD"
|
||||
database: "refreshing"
|
||||
# 配置读写分离只读副本(GORM 自动轮询读,支持配置多个从库)
|
||||
replicas:
|
||||
- host: "pg-replica-1.yourdomain.com"
|
||||
port: 5432
|
||||
username: "postgres"
|
||||
password: "YOUR_DB_PASSWORD"
|
||||
- host: "pg-replica-2.yourdomain.com"
|
||||
port: 5432
|
||||
username: "postgres"
|
||||
password: "YOUR_DB_PASSWORD"
|
||||
```
|
||||
|
||||
#### 2. Redis 高可用 (哨兵/Sentinel 或集群)
|
||||
- **Sentinel 哨兵模式**:通过配置 `redis.master_name` 启用,SDK 会自动监视 Master 的主备切换。
|
||||
- **Cluster 集群模式**:将 `redis.cluster_mode` 设为 `true`,并提供所有集群节点的 `addrs`。
|
||||
```yaml
|
||||
redis:
|
||||
addrs:
|
||||
- "redis-node-1.yourdomain.com:6379"
|
||||
- "redis-node-2.yourdomain.com:6379"
|
||||
- "redis-node-3.yourdomain.com:6379"
|
||||
cluster_mode: true
|
||||
```
|
||||
|
||||
#### 3. 对象存储与缓存分离 (S3 + Local Cache)
|
||||
高可用集群下,本地文件系统不再可共享。文件存储必须启用 S3 兼容服务,并在多节点间开启本地高速磁盘缓存加速读取:
|
||||
```yaml
|
||||
s3:
|
||||
enabled: true
|
||||
endpoint: "https://your-r2-or-s3-id.r2.cloudflarestorage.com"
|
||||
region: "auto"
|
||||
bucket: "refreshing-assets"
|
||||
access_key_id: "YOUR_S3_KEY"
|
||||
secret_access_key: "YOUR_S3_SECRET"
|
||||
local_cache:
|
||||
enabled: true # 开启本地磁盘缓存
|
||||
cache_dir: "/data/s3_cache" # 本地高性能 SSD 挂载点
|
||||
```
|
||||
|
||||
#### 4. 后端进程横向拆分部署
|
||||
- **API 集群**:启动数十个甚至上百个 `wavelet api` 无状态容器。它们可以通过负载均衡器直接挂载,支持随时弹性缩容扩容。
|
||||
- **Worker 集群**:启动多个 `wavelet worker` 容器。因为 `Asynq` 基于 Redis 分布式处理,多个 Worker 进程可以安全地同时运行并竞抢同一队列的异步任务,自动保障任务的并发吞吐能力。
|
||||
- **Scheduler 独占**:**【注意】** 为避免重复触发定时 Cron 任务,`wavelet scheduler` 定时调度器进程**同一时间应仅运行单个活跃实例**(主备高可用可以通过容器平台的单实例保障或 K8s Job 机制来限制实例数为 1)。
|
||||
|
||||
#### 5. ClickHouse 高并发同步
|
||||
在大数据量、高频支付结算场景下,开启 ClickHouse 以接收系统的历史数据同步,通过定时器把 PostgreSQL 的压力转移到 ClickHouse 列式存储中。
|
||||
```yaml
|
||||
clickhouse:
|
||||
enabled: true
|
||||
hosts:
|
||||
- "ch-node-1.yourdomain.com:9000"
|
||||
- "ch-node-2.yourdomain.com:9000"
|
||||
```
|
||||
|
||||
#### 6. OpenTelemetry 分布式链路追踪
|
||||
最大部署架构必须引入链路追踪(Jaeger 或 OTel Collector)以便排查节点间请求延迟或网络问题。
|
||||
在生产环境,通过配置 OTel 将 Span 发送至公共日志分析平台。
|
||||
```yaml
|
||||
otel:
|
||||
sampling_rate: 0.05 # 开启 5% 的流量追踪采样率以减少开销
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、 部署方案对比与选择建议
|
||||
|
||||
| 指标维度 | 方案一:最小单机嵌入版 | 方案二:标准前后端分离版 | 方案三:最大高可用分布式版 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **支持流量/并发** | 1,000 ~ 5,000 QPS (视机器性能) | 5,000 ~ 20,000 QPS | 20,000 ~ 100,000+ QPS (无限扩展) |
|
||||
| **服务器数量** | 1 台 | 3 ~ 5 台 | 10 台以上集群 |
|
||||
| **运维复杂度** | 极简 (只需部署一个程序) | 中等 (需维护 Node 和 Go 两套环境) | 较高 (K8s/多组件集群维护) |
|
||||
| **适合场景** | 个人项目、内部系统、SaaS 早期起步 | 正常线上运营项目、有中等规模团队 | 大型企业级应用、高并发核心交易系统 |
|
||||
@@ -0,0 +1,501 @@
|
||||
# Wavelet 系统性能分析与优化建议
|
||||
|
||||
> 分析日期:2026-06-17
|
||||
> 范围:Go 后端 + Next.js 前端
|
||||
> 目标:识别可能在生产环境真实出现的性能问题,并给出高 ROI 优化路线
|
||||
|
||||
**状态图例**:`✅ 已完成` · `🔶 部分完成` · `⬜ 待做`
|
||||
|
||||
| 修复批次 | 范围 | 状态 |
|
||||
|----------|------|------|
|
||||
| P0 后端 #1–#4 | WebP 锁、文件路径缓存、增量统计、复合索引 | ✅ |
|
||||
| P0 前端 #6–#7 | 认证并行化、日志虚拟化 | ✅ |
|
||||
| P1 #9 | 公共配置 Redis 列表缓存 | ✅ |
|
||||
| P1 参数中心 | 系统配置 Otter RAM 缓存 + 统一失效 + 多节点 pub/sub | ✅ |
|
||||
| P1 CAPTCHA | 运行时配置快照 + 批量加载 + pub/sub 失效 | ✅ |
|
||||
| P0 前端 #12–#19 | dynamic 分割、React Query、登录并行、Tooltip、lazy、barrel 收窄 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [架构概览与核心瓶颈](#架构概览与核心瓶颈)
|
||||
- [Critical — 高概率生产问题](#critical--高概率生产问题)
|
||||
- [Medium — 中等风险](#medium--中等风险)
|
||||
- [高价值优化路线图](#高价值优化路线图)
|
||||
- [已做得好的设计](#已做得好的设计)
|
||||
- [场景风险矩阵](#场景风险矩阵)
|
||||
- [优先行动清单](#优先行动清单)
|
||||
|
||||
---
|
||||
|
||||
## 架构概览与核心瓶颈
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph frontend["前端 (Static Export)"]
|
||||
A[HTML 静态壳] --> B[Hydrate]
|
||||
B --> C["UserProvider.getUserInfo()"]
|
||||
C --> D[页面数据请求]
|
||||
D --> E[渲染]
|
||||
end
|
||||
|
||||
subgraph backend["后端热点路径"]
|
||||
F["/f/{id}?quality=..."] --> G[DB 查 upload]
|
||||
G --> H[迁移状态 DB 查询]
|
||||
H --> I[白名单 Redis/DB]
|
||||
I --> J{WebP 缓存命中?}
|
||||
J -->|否| K["全量读文件 + 编码 + 磁盘缓存(全局锁)"]
|
||||
J -->|是| L[返回]
|
||||
end
|
||||
|
||||
C -.->|已解除阻塞| D
|
||||
```
|
||||
|
||||
**参数中心读路径**(`SystemConfig.GetByKey`):
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
R[业务调用 GetByKey] --> A{RAM 命中?}
|
||||
A -->|是| Z[返回]
|
||||
A -->|否| B{Redis HGET 命中?}
|
||||
B -->|是| C[写入 RAM]
|
||||
C --> Z
|
||||
B -->|否| D[查 PostgreSQL]
|
||||
D --> E[回写 Redis + RAM]
|
||||
E --> Z
|
||||
|
||||
W[管理员 Create/Update] --> F[写 DB]
|
||||
F --> G["InvalidateSystemConfigCache(key)"]
|
||||
G --> H[清本机 RAM + Redis field]
|
||||
G --> I[pub/sub 通知其他节点清 RAM]
|
||||
```
|
||||
|
||||
当前最大的结构性问题(2026-06-17 更新):
|
||||
|
||||
1. **前端**:~~全局认证瀑布流~~ ✅ 已改为 layout 即时渲染 + 子页面 `RequireAuth` 自行处理未登录态;~~Admin 重模块无 `dynamic()` 分割~~ ✅ database/logs/settings 已懒加载子模块。其余路由 `page.tsx` 仍为 `"use client"`(静态导出下 RSC 收益有限,待逐步薄壳化)。
|
||||
2. **后端**:文件服务路径(`/f/{id}`)仍是最高频热点;~~磁盘缓存全局互斥锁~~ ✅ 已改为 `RWMutex` + `singleflight`,但 WebP miss 仍在请求线程内同步编码,部署预热与异步回退原图尚未落地。
|
||||
3. **参数中心**:~~`GetByKey` 每次直打 Redis~~ ✅ 已加 Otter v2 进程内缓存(`pkg/cache/ram`),读路径为 RAM → Redis → DB;管理员写配置后统一失效 RAM + Redis,并通过 pub/sub 同步多节点本地缓存。
|
||||
|
||||
---
|
||||
|
||||
## Critical — 高概率生产问题
|
||||
|
||||
### 1. 图片 WebP 服务:请求路径阻塞 + 全局锁串行化 `🔶 部分完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `internal/apps/upload/file_server.go`
|
||||
- `pkg/cache/disk/cache.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
缓存未命中时,在 HTTP 请求 goroutine 内执行:
|
||||
|
||||
1. `io.ReadAll` 将原始文件全量读入内存
|
||||
2. 进程内 WebP 解码 + 编码
|
||||
3. 写入磁盘缓存
|
||||
|
||||
同时,磁盘缓存 `Get`/`Set` 使用**全局 `sync.Mutex`**,所有并发图片请求在缓存层完全串行。
|
||||
|
||||
```go
|
||||
// file_server.go — 缓存 miss 时的重操作
|
||||
origBytes, err := getOriginalFileBytes(ctx, upload) // io.ReadAll
|
||||
webpBytes, err = CompressImageToWebP(bytes.NewReader(origBytes), quality)
|
||||
cache.Set(cacheKey, webpBytes, diskcache.NoExpiration)
|
||||
|
||||
// pkg/cache/disk/cache.go — 全局互斥锁
|
||||
func (c *Cache) Get(key string) ([]byte, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 首次访问或缓存淘汰后,P99 延迟从几十毫秒飙升到数秒
|
||||
- 并发图片请求形成「隐形队列」
|
||||
- 大文件全量读入带来内存尖峰,可能触发 OOM 或 GC 停顿
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 磁盘缓存改用 `RWMutex`,读路径不互斥 — `pkg/cache/disk/cache.go`
|
||||
- [x] ✅ 对同一 cache key 使用 `singleflight` 合并并发 miss — `internal/apps/upload/file_server.go`
|
||||
- [ ] 部署后强制执行 `upload:warm_image_cache` 异步预热任务
|
||||
- [ ] 考虑 miss 时先返回原图,后台异步生成 WebP
|
||||
|
||||
---
|
||||
|
||||
### 2. 文件访问路径:每次请求多次 DB/Redis 查询 `✅ 已完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `internal/apps/upload/storage_ops.go`
|
||||
- `internal/apps/upload/file_server.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
存储迁移状态**无进程内缓存**,每次文件操作都查询 `w_task_executions`:
|
||||
|
||||
```go
|
||||
// storage_ops.go
|
||||
func StorageReadOnly(ctx context.Context) bool {
|
||||
execution, ok, err := latestStorageMigrationExecution(ctx)
|
||||
// ...
|
||||
}
|
||||
|
||||
func backendForStoredDriver(ctx context.Context, driver storage.Driver) (storage.Backend, error) {
|
||||
// 可能再次调用 currentMigrationTargetConfig → 又一次相同 DB 查询
|
||||
}
|
||||
```
|
||||
|
||||
公开文件白名单每次走 Redis/DB:
|
||||
|
||||
```go
|
||||
// file_server.go
|
||||
func isFilePublic(ctx context.Context, uploadType string) bool {
|
||||
sc.GetByKey(ctx, model.ConfigKeyFileAccessWhitelist)
|
||||
// JSON 解析 + 遍历
|
||||
}
|
||||
```
|
||||
|
||||
对比:`storage.Active()` 已有 5 秒内存缓存 + Redis pub/sub 失效机制,迁移状态却未复用该模式。
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 每个 `/f/{id}` 请求额外 2–4 次 DB/Redis 往返
|
||||
- 图片站/CDN 场景下 QPS 放大后 PostgreSQL 连接池压力明显
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 为 `StorageReadOnly` / `latestStorageMigrationExecution` 增加 5s TTL 进程内缓存 — `internal/apps/upload/access_cache.go`
|
||||
- [x] ✅ 配置变更或迁移状态变化时通过 Redis pub/sub 失效 — `access_cache.go` + `system_config/routers.go`
|
||||
- [x] ✅ `file_access_whitelist` 增加进程内缓存,复用 `GetByKey` 的失效机制 — `access_cache.go`
|
||||
|
||||
---
|
||||
|
||||
### 3. Admin 文件统计:无界全表扫描 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`internal/apps/upload/stats.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
```go
|
||||
err = db.DB(ctx).Model(&model.Upload{}).
|
||||
Select("extension, mime_type, file_size").
|
||||
Where("status != ?", model.UploadStatusDeleted).
|
||||
Scan(&fileRaws).Error
|
||||
// 然后在 Go 中遍历全量结果做分类统计
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 10 万+ 文件时,管理端「文件统计」接口耗时数秒
|
||||
- 占用数百 MB 内存,可能拖垮 admin API
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [ ] 改为 SQL `GROUP BY` + `CASE WHEN` 聚合(未采用)
|
||||
- [x] ✅ 维护增量统计表,上传/删除时更新计数 — `w_upload_stats` + `stats_counter.go` + `GetFileStats` 读统计表
|
||||
|
||||
---
|
||||
|
||||
### 4. `w_uploads` 索引缺口 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`internal/db/migrator/goose/postgres/202606090001_initial_schema.sql`
|
||||
|
||||
**当前索引**:`user_id`, `file_path`, `hash`, `type`
|
||||
|
||||
**缺失的高频查询索引**:
|
||||
|
||||
| 查询场景 | 建议索引 |
|
||||
|----------|----------|
|
||||
| 清理任务 `status + created_at` | `(status, created_at)` |
|
||||
| 存储迁移 `storage_driver + status` | `(storage_driver, status)` |
|
||||
| 秒传去重 `hash + file_size + status` | `(hash, file_size, status)` |
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 数据量增长后,清理 worker、迁移任务、上传去重退化为顺序扫描
|
||||
- 后台任务积压,admin 操作变慢
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 通过 goose migration 新增上述复合索引(PostgreSQL + SQLite 双方言)— `202606170001_add_upload_composite_indexes.sql`
|
||||
|
||||
---
|
||||
|
||||
### 5. 批量 ZIP 下载:无上限 + 同步阻塞
|
||||
|
||||
**涉及文件**:`internal/apps/upload/routers.go` — `BatchDownloadFiles`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- `req.IDs` 无数量上限
|
||||
- 在请求 goroutine 内串行打开每个文件并 `io.Copy` 到 ZIP
|
||||
- 远端 S3 场景下单个文件就可能耗时数秒
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 网关超时、连接耗尽
|
||||
- Admin 批量下载操作卡死
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [ ] 限制单次批量数量(如 max 50)
|
||||
- [ ] 或改为 Asynq 后台任务生成 ZIP,前端轮询下载链接
|
||||
|
||||
---
|
||||
|
||||
### 6. 前端全局认证瀑布流 `✅ 已完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `frontend/contexts/user-context.tsx`
|
||||
- `frontend/app/(main)/layout.tsx`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
```tsx
|
||||
// user-context.tsx — 挂载时获取用户
|
||||
useEffect(() => {
|
||||
fetchUser()
|
||||
}, [fetchUser])
|
||||
|
||||
// layout.tsx — 阻塞所有子页面渲染
|
||||
if (loading || !user) {
|
||||
return <LoadingPage text="登录状态" badgeText="Auth" />
|
||||
}
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 每次进入 `/home`、`/files`、`/admin/*` 都先等 `getUserInfo`(约 200–800ms)
|
||||
- 页面级数据请求无法并行启动,TTI 被硬性拉长
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ Layout 不阻塞渲染,子页面自行处理未登录状态 — `layout.tsx` + `RequireAuth` / `RequireAdminAuth`
|
||||
- [ ] 或 Server Component 通过 cookie 预取 session,消除客户端首屏等待
|
||||
- [x] ✅ `/login`、`/register` 跳过 `getUserInfo` — `user-context.tsx`
|
||||
|
||||
---
|
||||
|
||||
### 7. 实时日志面板:2000 行 DOM 无虚拟化 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`frontend/components/common/admin/app-logs.tsx`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- 日志上限 2000 行(内存有界,但 DOM 无界)
|
||||
- 每行渲染完整 `<div>`,无虚拟滚动
|
||||
- `@tanstack/react-virtual` 已在 `package.json` 但未使用
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 管理员开着日志 Tab 时 CPU/内存持续升高
|
||||
- 滚动卡顿,长时间运行拖慢整台机器
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 使用 `useVirtualizer` 只渲染可视区域行 — `app-logs.tsx`
|
||||
- [x] ✅ 行组件 `React.memo` 避免无效重渲染 — `LogLine`
|
||||
|
||||
---
|
||||
|
||||
## Medium — 中等风险
|
||||
|
||||
| # | 问题 | 位置 | 影响 |
|
||||
|---|------|------|------|
|
||||
| 1 | ~~公共配置接口无 Redis 缓存~~ ✅ | `internal/model/system_configs.go` — `ListVisibleSystemConfigs` | ~~每次前端启动/登录直查 PostgreSQL~~ → Redis 列表缓存 + Create/Update 时失效 |
|
||||
| 2 | ~~CAPTCHA 每次 5 次独立 `GetByKey`~~ ✅ | `internal/apps/cap/runtime_settings.go` | ~~登录高峰 5× 配置读取~~ → `CurrentSettings` 快照一次加载 6 个 key,`Generate`/`Redeem`/中间件零 `GetByKey` |
|
||||
| 3 | ~~系统配置单 key 无进程内缓存~~ ✅ | `system_config_cache.go`, `pkg/cache/ram` | ~~热路径重复 Redis HGET~~ → Otter RAM + 写后 `InvalidateSystemConfigCache` + pub/sub |
|
||||
| 4 | OIDC 每次 `oidc.NewProvider` 无缓存 | `internal/apps/oauth/sources.go:164` | 登录发起/回调多一次外部 HTTP |
|
||||
| 5 | CORS 每次跨域查 `server_address` 配置 `🔶` | `internal/router/middlewares.go:75` | 预检请求仍每次调用 `GetByKey`,但 `server_address` 已受益于 RAM 缓存 |
|
||||
| 6 | 推送通知无界 goroutine + 逐 target DB 查询 | `internal/apps/admin/push/events.go:102` | 通知风暴时 goroutine/DB 双压 |
|
||||
| 7 | 上传清理:每文件一个事务 | `internal/apps/upload/cleanup.go` | 大量 pending 文件时 commit 风暴 |
|
||||
| 8 | ClickHouse 风控:每请求 `json.Marshal` 全部 headers | `internal/apps/risk_control/middleware.go:58` | 高 QPS 时 CPU 开销(写入本身已异步批处理) |
|
||||
| 9 | 存储迁移日志大量写 Redis | `internal/apps/upload/storage_migration_task.go` | 迁移期间 Redis CPU/内存压力 |
|
||||
| 10 | 存储迁移后二次 SHA 全量读取验证 | `storage_migration_task.go` | 迁移期间对象 I/O 翻倍 |
|
||||
| 11 | Admin 状态页 5s 轮询 | `frontend/components/common/admin/status.tsx` | Tab 常驻时持续打后端 |
|
||||
| 12 | 路由切换 500ms fade 动画 | `frontend/app/(main)/layout.tsx:53-60` | 即使数据已缓存,感知仍慢 |
|
||||
| 13 | ~~无 `next/dynamic` 代码分割~~ ✅ | `database/`, `logs/`, `settings/` page-client | Admin 重模块拆分为独立 chunk |
|
||||
| 14 | 19/24 个 `page.tsx` 为 `"use client"` `🔶` | 各路由 | database/logs/settings 已薄壳化;其余待迁移 |
|
||||
| 15 | ~~Admin 部分页面用 `useEffect` 而非 React Query~~ ✅ | `access-logs.tsx`, `task-executions.tsx` | 列表/详情走 React Query 缓存去重 |
|
||||
| 16 | ~~登录页 OIDC sources 等待 public config~~ ✅ | `login-form.tsx` | public config 与 auth sources 并行请求 |
|
||||
| 17 | ~~Users 表每行嵌套 3 个 `TooltipProvider`~~ ✅ | `admin/users/page.tsx` | 表格外层单一 Provider |
|
||||
| 18 | ~~缩略图用原生 `<img>` 无 lazy loading~~ ✅ | `file-list.tsx`, `file-manager.tsx` | `loading="lazy"` + `decoding="async"` |
|
||||
| 19 | ~~`@/lib/services` barrel 导入~~ ✅ | 全前端消费侧 | 改为 `@/lib/services/<module>` 直接导入 |
|
||||
| 20 | SQLite 模式无连接池调优 | `internal/db/postgres.go` | 默认 SQLite 写锁瓶颈 |
|
||||
| 21 | Session Redis 仅用第一个地址 | `internal/router/router.go` | Sentinel/Cluster 场景不一致 |
|
||||
|
||||
---
|
||||
|
||||
## 高价值优化路线图
|
||||
|
||||
### P0 — 立即做(1–2 周,收益最大)
|
||||
|
||||
| # | 优化项 | 涉及模块 | 预期收益 | 复杂度 | 状态 |
|
||||
|---|--------|----------|----------|--------|------|
|
||||
| 1 | WebP:`singleflight` + `RWMutex` + 强制预热 | `file_server.go`, `pkg/cache/disk/` | 图片 P99 ↓ 80%+,并发吞吐 ↑ 5–10x | 中 | 🔶 锁与去重已完成,预热待做 |
|
||||
| 2 | 缓存 `StorageReadOnly` / 迁移状态 | `access_cache.go` | 每文件请求减少 1–3 次 DB | 低 | ✅ |
|
||||
| 3 | 内存缓存 `file_access_whitelist` | `access_cache.go` | 每公开文件请求减少 1 次 Redis | 低 | ✅ |
|
||||
| 4 | `GetFileStats` 增量统计表 | `stats.go`, `w_upload_stats` | Admin 统计从 O(n) → O(1) | 低 | ✅ |
|
||||
| 5 | 新增 `w_uploads` 复合索引 | goose migration | 清理/迁移/秒传全面加速 | 低 | ✅ |
|
||||
| 6 | 前端日志虚拟化 | `app-logs.tsx` | Admin 日志 Tab 流畅度质变 | 低 | ✅ |
|
||||
| 7 | Admin 重模块 `dynamic()` 懒加载 | `database/page-client.tsx`, `logs/page-client.tsx`, `settings/page-client.tsx` | 首包 JS ↓ 150–300KB | 低 | ✅ |
|
||||
|
||||
### P1 — 短期(2–4 周)
|
||||
|
||||
| # | 优化项 | 预期收益 | 状态 |
|
||||
|---|--------|----------|------|
|
||||
| 8 | 认证并行化:layout 不阻塞 / Server 预取 session | TTI ↓ 200–800ms | 🔶 客户端并行化已完成,RSC 预取待做 |
|
||||
| 9 | `ListVisibleSystemConfigs` 加 Redis 缓存 | 前端冷启动加速 | ✅ |
|
||||
| 10 | 系统配置 Otter RAM 缓存 + 统一失效 | 热路径 `GetByKey` 零 Redis RTT(命中后) | ✅ |
|
||||
| 11 | CAPTCHA 运行时配置快照 | 验证码路径配置读取 → O(1) 快照 | ✅ |
|
||||
| 12 | OIDC Provider/JWKS 进程内缓存(TTL 1h) | 登录延迟 ↓ 100–500ms | ⬜ |
|
||||
| 13 | 批量下载限制(max 50)或异步任务 | 消除网关超时风险 | ⬜ |
|
||||
| 14 | Admin `useEffect` 数据获取迁移到 React Query | 去重、缓存、后台刷新 | 🔶 access-logs / task-executions 已完成 |
|
||||
| 15 | 登录页并行请求 public config + auth sources | 登录页 ↓ 100–300ms | ✅ |
|
||||
| 16 | 状态轮询在 `document.hidden` 时暂停 | 降低后台 + 客户端负载 | ⬜ |
|
||||
|
||||
### P2 — 中期架构演进
|
||||
|
||||
| # | 优化项 | 预期收益 |
|
||||
|---|--------|----------|
|
||||
| 16 | 批量 ZIP 改为 Asynq 后台任务 | 彻底解耦长耗时操作 |
|
||||
| 17 | 存储迁移日志降噪 + 跳过已验证文件二次 SHA | 迁移期间 Redis/I/O ↓ 50% |
|
||||
| 18 | 推送通知 target 批量解析(`WHERE id IN ?`) | 通知风暴 DB 查询 ↓ N 倍 |
|
||||
| 19 | 上传清理改为批量 UPDATE + 异步存储删除 | 减少 DB commit 频率 |
|
||||
| 20 | 路由动画 0.5s → 0.15s 或纯 CSS | 导航感知速度 ↑ |
|
||||
| 21 | ~~服务导入收窄(直接 import 具体 Service)~~ ✅ | 每路由 bundle ↓ 10–30KB |
|
||||
| 22 | Admin 路由级 `loading.tsx` + Suspense | 渐进式渲染体验 |
|
||||
| 23 | ~~缩略图 `loading="lazy"` + 固定尺寸~~ ✅ | 文件管理页初始 paint 加速 |
|
||||
|
||||
---
|
||||
|
||||
## 已做得好的设计
|
||||
|
||||
以下设计说明团队已有性能意识,优化应在此基础上增量改进,**不必重复造轮子**:
|
||||
|
||||
| # | 设计 | 位置 |
|
||||
|---|------|------|
|
||||
| 1 | 系统配置三层缓存 RAM → Redis → DB | `pkg/cache/ram`, `system_config_cache.go`, `GetByKey` |
|
||||
| 2 | 系统配置统一失效 + 多节点 pub/sub | `InvalidateSystemConfigCache`, `InvalidateAllSystemConfigCaches` |
|
||||
| 3 | Storage Backend 单例 + 5s TTL + pub/sub 失效 | `internal/storage/storage.go` — `Active()` |
|
||||
| 4 | 推送事件/渠道 24h Redis 缓存 + GORM hook 失效 | `internal/model/push_event.go`, `push_channel.go` |
|
||||
| 5 | 风控日志异步批写 ClickHouse(1 万缓冲 + 1000 条/1s + 429 背压) | `internal/apps/risk_control/` |
|
||||
| 6 | HTTP 连接池统一(`httppool` + OTel) | `pkg/httppool/` |
|
||||
| 7 | DB/Redis 连接池显式配置 | `config.yaml`, `internal/db/` |
|
||||
| 8 | 游标分批处理(`id > ? LIMIT n`) | `cleanup.go`, image warmup |
|
||||
| 9 | 存储迁移并发上限 `errgroup.SetLimit(10)` | `storage_migration_task.go` |
|
||||
| 10 | 邮件/推送走 Asynq,不在 HTTP 路径同步发送 | `user/logics.go`, `push/events.go` |
|
||||
| 11 | 文件服务 ETag/304 + 原图 `DataFromReader` 流式返回 | `file_server.go` |
|
||||
| 12 | 无 GORM `Preload` 滥用 | 全项目 |
|
||||
| 13 | 前端 API 请求去重(`pendingRequests` Map) | `frontend/lib/services/core/api-client.ts` |
|
||||
| 14 | React Query 全局 30s `staleTime` | `frontend/components/providers/query-provider.tsx` |
|
||||
| 15 | React Compiler 已启用 | `frontend/next.config.ts` |
|
||||
| 16 | 读副本支持(`dbresolver`) | `internal/db/postgres.go` |
|
||||
| 17 | 任务执行日志 Redis 缓冲 + 批量回写 | `internal/model/task_execution.go` |
|
||||
| 18 | 公共配置列表 Redis 缓存 + 写后失效 | `ListVisibleSystemConfigs`, `InvalidateVisibleSystemConfigsCache` |
|
||||
| 19 | 上传文件统计增量表 `w_upload_stats` | `stats_counter.go`, 上传/删除 hook |
|
||||
| 20 | 文件访问路径进程内缓存 + pub/sub | `internal/apps/upload/access_cache.go` |
|
||||
| 21 | 磁盘缓存读路径 `RWMutex` + WebP `singleflight` | `pkg/cache/disk/cache.go`, `file_server.go` |
|
||||
| 22 | 前端认证非阻塞 + 页面级鉴权 | `use-auth-redirect.ts`, `require-auth.tsx` |
|
||||
| 23 | Admin 实时日志虚拟滚动 | `frontend/components/common/admin/app-logs.tsx` |
|
||||
| 24 | CAPTCHA 运行时配置快照 + 批量加载 | `runtime_settings.go`, `ListSystemConfigsByKeys` |
|
||||
|
||||
---
|
||||
|
||||
## 场景风险矩阵
|
||||
|
||||
| 场景 | 最可能爆的点 | 对应优先级 |
|
||||
|------|-------------|-----------|
|
||||
| 图片站 / 公开相册 | WebP miss(锁/白名单已优化) | P0 #1 预热待做 |
|
||||
| 文件量 10 万+ | 清理慢(统计/索引已优化) | P2 #19 清理批量化 |
|
||||
| 管理端日常使用 | ~~大 bundle~~(dynamic 分割 + barrel 收窄已落地) | P2 #22 路由 loading.tsx |
|
||||
| 存储迁移进行中 | Redis 日志风暴 | P2 #17 |
|
||||
| 登录高峰 | OIDC discovery 无缓存 | P1 #12 OIDC |
|
||||
| 多租户 / 跨域前端 | CORS 仍每次调 `GetByKey`(`server_address` 已 RAM 缓存) | 可选 CORS 快照 |
|
||||
| 参数热更新 | 多节点 RAM 一致性 | ✅ `system:config_invalidation` pub/sub |
|
||||
| 批量文件操作 | ZIP 同步打包无上限 | P0 #5, P1 #12 |
|
||||
|
||||
---
|
||||
|
||||
## 优先行动清单
|
||||
|
||||
如果只选 **3 件事** 先做(预计用户感知延迟降低 50–70%):
|
||||
|
||||
1. ~~**WebP 路径解耦**~~ ✅ `singleflight` + `RWMutex` 已落地;**下一步**:部署后预热 + miss 异步回退原图
|
||||
2. ~~**文件路径查询缓存**~~ ✅ 迁移状态 + 白名单进程内缓存已落地
|
||||
3. ~~**前端认证与首屏并行化**~~ ✅ 全局 auth gate 已移除;~~Admin `dynamic()` 代码分割~~ ✅ 已落地;**下一步**:其余 Admin 路由薄壳化 + `loading.tsx`
|
||||
|
||||
### 实施检查清单
|
||||
|
||||
```
|
||||
P0 后端
|
||||
[x] disk cache RWMutex + singleflight ✅ 2026-06-17
|
||||
[x] StorageReadOnly 5s 缓存 + pub/sub 失效 ✅ 2026-06-17
|
||||
[x] file_access_whitelist 进程内缓存 ✅ 2026-06-17
|
||||
[x] GetFileStats 增量统计表 (w_upload_stats) ✅ 2026-06-17
|
||||
[x] w_uploads 复合索引 migration ✅ 2026-06-17
|
||||
[ ] 批量下载数量上限
|
||||
[ ] WebP 部署预热 + miss 异步回退原图
|
||||
|
||||
P0 前端
|
||||
[x] app-logs.tsx 虚拟滚动 ✅ 2026-06-17
|
||||
[x] SQLConsole / Settings Tabs / Logs Tabs dynamic import ✅ 2026-06-17
|
||||
[x] 认证 gate 并行化 ✅ 2026-06-17
|
||||
[x] 登录页 public config + auth sources 并行 ✅ 2026-06-17
|
||||
[x] access-logs / task-executions → React Query ✅ 2026-06-17
|
||||
[x] Users TooltipProvider 合并 ✅ 2026-06-17
|
||||
[x] 缩略图 loading="lazy" ✅ 2026-06-17
|
||||
[x] @/lib/services barrel 导入收窄 ✅ 2026-06-17
|
||||
|
||||
P1
|
||||
[x] ListVisibleSystemConfigs Redis 缓存 ✅ 2026-06-17
|
||||
[x] 系统配置 Otter RAM 缓存 + 统一失效 + pub/sub ✅ 2026-06-17
|
||||
[x] CAPTCHA 运行时配置快照 ✅ 2026-06-17
|
||||
[ ] OIDC Provider 缓存
|
||||
[ ] Admin useEffect → React Query 统一(database overview 等待)
|
||||
[ ] 状态轮询 visibility 感知
|
||||
[ ] Server Component session 预取
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键代码路径索引
|
||||
|
||||
| 路径 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 图片服务 | `internal/apps/upload/file_server.go` | `/f/{id}` 热点 |
|
||||
| 磁盘缓存 | `pkg/cache/disk/cache.go` | ✅ RWMutex 读路径 |
|
||||
| 迁移/白名单缓存 | `internal/apps/upload/access_cache.go` | ✅ 5s TTL + pub/sub |
|
||||
| 文件统计 | `internal/apps/upload/stats.go` | ✅ 读 `w_upload_stats` |
|
||||
| 公共配置列表 | `internal/model/system_configs.go` | ✅ Redis 列表缓存 |
|
||||
| RAM 缓存封装 | `pkg/cache/ram/cache.go` | ✅ Otter v2 薄封装 |
|
||||
| 系统配置缓存 | `internal/model/system_config_cache.go` | ✅ RAM + 失效 + pub/sub |
|
||||
| 参数失效 API | `InvalidateSystemConfigCache` | ✅ 清 RAM + Redis field |
|
||||
| CAPTCHA 快照 | `internal/apps/cap/runtime_settings.go` | ✅ `CurrentSettings` + pub/sub |
|
||||
| 批量下载 | `internal/apps/upload/routers.go` | 同步 ZIP |
|
||||
| 上传索引 | `internal/db/migrator/goose/*202606170001*.sql` | ✅ 复合索引已加 |
|
||||
| 认证 gate | `frontend/app/(main)/layout.tsx` | ✅ 即时渲染 + `useAuthRedirect` |
|
||||
| 页面鉴权 | `frontend/components/auth/require-auth.tsx` | ✅ 子页面按需拦截 |
|
||||
| 用户上下文 | `frontend/contexts/user-context.tsx` | ✅ 登录/注册页跳过 fetch |
|
||||
| 实时日志 | `frontend/components/common/admin/app-logs.tsx` | ✅ `useVirtualizer` |
|
||||
| API 去重 | `frontend/lib/services/core/api-client.ts` | 已有,可复用模式 |
|
||||
+41
-43
@@ -66,17 +66,15 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
|
||||
| 路径 | 职责 |
|
||||
| ---------------------- | ---------------------------------------------------- |
|
||||
| `openflare-server` | Gin + GORM + SQLite/PostgreSQL 单体控制面 |
|
||||
| `openflare-server/frontend` | Next.js App Router 管理端前端,由 Go Server 嵌入托管 |
|
||||
| `cmd` | 各组件的命令行启动入口及主函数(server, agent, relay, flared) |
|
||||
| `internal` | 各组件的内部业务逻辑,通过子包隔离控制(agent, relay, flared, 核心控制面等) |
|
||||
| `frontend` | Next.js App Router 管理端前端,由 Go Server 嵌入托管 |
|
||||
| `pkg` | 跨组件复用的协议类型与通用工具包 |
|
||||
| `openflare-agent` | Go 单体 Agent,运行在节点侧 |
|
||||
| `openflare-relay` | Tunnel 中继代理,运行在公网边缘管理 frps 进程 |
|
||||
| `openflared` | Tunnel 客户端,运行在内网服务器侧管理 frpc 进程 |
|
||||
| `scripts` | 安装、自更新等系统辅助脚本 |
|
||||
| `docs` | VitePress 文档站、设计基线、开发规范、部署与配置文档 |
|
||||
| `docs/en` | 英文版文档 |
|
||||
| `docker` | 各组件 of Dockerfile 构建文件 |
|
||||
|
||||
### 1. Server 分层 (`openflare-server/`)
|
||||
### 1. Server 分层 (`internal/` / `cmd/server/`)
|
||||
|
||||
| 目录 | 职责 |
|
||||
| ----------------------- | ------------------------------------------------ |
|
||||
@@ -90,7 +88,7 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
| `internal/common/` | 配置、全局状态与初始化入口 |
|
||||
| `internal/job/` | 定时任务(各业务定时逻辑在独立文件中定义,cron.go 仅用于初始化调度) |
|
||||
| `internal/utils/` | 仅 Server 内部使用的基础能力包,如 ACME、限流、验证码、邮件、安全校验等 |
|
||||
| `pkg/protocol/` | Server、Relay、OpenFlared 之间共享的 HTTP/WS 协议结构 |
|
||||
| `pkg/protocol/` | Server、Relay、OpenFlared 之间共享 of HTTP/WS 协议结构 |
|
||||
| `pkg/utils/` | 跨组件可复用的纯工具函数 |
|
||||
| `pkg/geoip`、`pkg/render`、`pkg/wsclient` | 被多个组件复用的 GeoIP、OpenResty 配置渲染与 WebSocket 客户端能力 |
|
||||
| `upload/` | 运行时本地临时文件上传目录(在 .gitignore 中忽略) |
|
||||
@@ -98,27 +96,27 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
| `docs/` | API 文档(Swagger) |
|
||||
| `data/` | 静态数据(如 GeoIP 数据库) |
|
||||
|
||||
### 2. Agent 模块 (`openflare-agent/`)
|
||||
### 2. Agent 模块 (`internal/apps/agent/` / `cmd/agent/`)
|
||||
|
||||
| 目录/模块 | 职责 |
|
||||
| ----------------------------- | -------------------------------------------- |
|
||||
| `cmd/agent/` | Agent 命令行启动入口及主函数 |
|
||||
| `internal/config/` | 配置读取与默认值 |
|
||||
| `internal/heartbeat/` | 心跳与版本摘要判断 |
|
||||
| `internal/sync/` | 配置拉取与应用编排 |
|
||||
| `internal/nginx/` | OpenResty 文件写入、校验、reload、启动与回滚 |
|
||||
| `internal/state/` | 本地状态与观测补报缓冲 |
|
||||
| `internal/httpclient/` | Server 通信 |
|
||||
| `internal/wsclient/` | WebSocket 客户端通信 |
|
||||
| `internal/protocol/` | Agent API 协议类型 |
|
||||
| `internal/updater/` | Agent 自更新逻辑 |
|
||||
| `internal/logging/` | 日志处理 |
|
||||
| `internal/observability/` | 可观测性(指标、链路等) |
|
||||
| `internal/geoipdata/` | GeoIP 数据处理 |
|
||||
| `internal/geoipupdate/` | GeoIP 数据更新 |
|
||||
| `internal/agent/` | 核心 Agent 逻辑与生命周期 |
|
||||
| `internal/apps/agent/config/` | 配置读取与默认值 |
|
||||
| `internal/apps/agent/heartbeat/` | 心跳与版本摘要判断 |
|
||||
| `internal/apps/agent/sync/` | 配置拉取与应用编排 |
|
||||
| `internal/apps/agent/nginx/` | OpenResty 文件写入、校验、reload、启动与回滚 |
|
||||
| `internal/apps/agent/state/` | 本地状态与观测补报缓冲 |
|
||||
| `internal/apps/agent/httpclient/` | Server 通信 |
|
||||
| `internal/apps/agent/wsclient/` | WebSocket 客户端通信 |
|
||||
| `internal/apps/agent/protocol/` | Agent API 协议类型 |
|
||||
| `internal/apps/agent/updater/` | Agent 自更新逻辑 |
|
||||
| `internal/apps/agent/logging/` | 日志处理 |
|
||||
| `internal/apps/agent/observability/`| 可观测性(指标、链路等) |
|
||||
| `internal/apps/agent/geoipdata/` | GeoIP 数据处理 |
|
||||
| `internal/apps/agent/geoipupdate/` | GeoIP 数据更新 |
|
||||
| `internal/apps/agent/agent/` | 核心 Agent 逻辑与生命周期 |
|
||||
|
||||
### 3. Frontend 分层 (`openflare-server/web/`)
|
||||
### 3. Frontend 分层 (`frontend/`)
|
||||
|
||||
| 目录 | 职责 |
|
||||
| ------------- | -------------------------------------------- |
|
||||
@@ -133,34 +131,34 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
| `scripts/` | 构建和部署相关脚本 |
|
||||
| `public/` | 静态资源 |
|
||||
|
||||
### 4. Relay 模块 (`openflare-relay/`)
|
||||
### 4. Relay 模块 (`internal/apps/relay/` / `cmd/relay/`)
|
||||
|
||||
| 模块 | 职责 |
|
||||
| ---------------- | ------------------------------------------------ |
|
||||
| `cmd/` | Relay 命令行启动入口及初始化主函数 |
|
||||
| `internal/config/`| 本地配置文件解析与默认参数初始化 |
|
||||
| `internal/frps/` | 管理 frps 进程生命周期、端口与 Token 并监控运行 |
|
||||
| `internal/heartbeat/`| 周期性 HTTP 心跳通信、上报状态并获取更新请求 |
|
||||
| `internal/httpclient/`| Server 的通用 API 客户端调用工具类 |
|
||||
| `internal/observability/`| 采集本地宿主机、frps 的基础运行指标并进行预聚合 |
|
||||
| `internal/relay/` | 协调中继的核心生命周期、初始化与清理 |
|
||||
| `internal/state/` | 本地运行时状态、错误记录与持久化缓存 |
|
||||
| `internal/updater/`| Relay 升级检查、下载安装与重启机制 |
|
||||
| `internal/wsclient/`| 与 Server 保持的长连接 WebSocket 双向通信管道 |
|
||||
| `internal/apps/relay/config/`| 本地配置文件解析与默认参数初始化 |
|
||||
| `internal/apps/relay/frps/` | 管理 frps 进程生命周期、端口与 Token 并监控运行 |
|
||||
| `internal/apps/relay/heartbeat/`| 周期性 HTTP 心跳通信、上报状态并获取更新请求 |
|
||||
| `internal/apps/relay/httpclient/`| Server 的通用 API 客户端调用工具类 |
|
||||
| `internal/apps/relay/observability/`| 采集本地宿主机、frps 的基础运行指标并进行预聚合 |
|
||||
| `internal/apps/relay/relay/` | 协调中继的核心生命周期、初始化与清理 |
|
||||
| `internal/apps/relay/state/` | 本地运行时状态、错误记录与持久化缓存 |
|
||||
| `internal/apps/relay/updater/`| Relay 升级检查、下载安装与重启机制 |
|
||||
| `internal/apps/relay/wsclient/`| 与 Server 保持的长连接 WebSocket 双向通信管道 |
|
||||
|
||||
### 5. OpenFlared (Client) 模块 (`openflared/`)
|
||||
### 5. OpenFlared (Client) 模块 (`internal/apps/flared/` / `cmd/flared/`)
|
||||
|
||||
| 模块 | 职责 |
|
||||
| ---------------- | ------------------------------------------------ |
|
||||
| `cmd/` | Client 命令行启动入口及初始化主函数 |
|
||||
| `internal/config/`| 本地客户端配置加载与解析 |
|
||||
| `internal/flared/`| 内网穿透客户端的核心调度与状态管理机制 |
|
||||
| `internal/frpc/` | 热重载/动态生成多 Relay 的 `frpc.toml` 并监控 frpc |
|
||||
| `internal/heartbeat/`| 与控制面进行的心跳通信,包含 Token 校验机制 |
|
||||
| `internal/httpclient/`| 客户端通用 API 通信客户端 |
|
||||
| `internal/sync/` | 增量拉取最新 Tunnel 路由绑定关系、生成快照并应用 |
|
||||
| `internal/updater/`| 客户端自更新、新版检查与更新落地逻辑 |
|
||||
| `internal/wsclient/`| 用于实时监听 Server 端隧道配置变更推送的 WS 信道 |
|
||||
| `internal/apps/flared/config/`| 本地客户端配置加载与解析 |
|
||||
| `internal/apps/flared/flared/`| 内网穿透客户端的核心调度与状态管理机制 |
|
||||
| `internal/apps/flared/frpc/` | 热重载/动态生成多 Relay 的 `frpc.toml` 并监控 frpc |
|
||||
| `internal/apps/flared/heartbeat/`| 与控制面进行的心跳通信,包含 Token 校验机制 |
|
||||
| `internal/apps/flared/httpclient/`| 客户端通用 API 通信客户端 |
|
||||
| `internal/apps/flared/sync/` | 增量拉取最新 Tunnel 路由绑定关系、生成快照并应用 |
|
||||
| `internal/apps/flared/updater/`| 客户端自更新、新版检查与更新落地逻辑 |
|
||||
| `internal/apps/flared/wsclient/`| 用于实时监听 Server 端隧道配置变更推送的 WS 信道 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+17153
File diff suppressed because it is too large
Load Diff
+17128
File diff suppressed because it is too large
Load Diff
+10628
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user