feat: expand gen2D deck with deep technical slides and interactive elements
Deploy Slides / build-and-deploy (push) Successful in 1m8s
Deploy Slides / build-and-deploy (push) Successful in 1m8s
- Add 5 new slide pages: eino-deep-dive, ratelimit-overview, ratelimit-lua, async-deep-dive, harness - Add 4 new drawio SVGs: pipeline-detail, ratelimit, async-task, harness - Update all existing slides with v-clicks progressive reveal - Add presenter notes and interactive prompts - Expand slides.md to register all new pages (16 → 25+ slides) - Fix text overflow by trimming content per Item block - Add Eino Graph deep dive: WithGenLocalState, Pre/Post Handler, branch routing - Add rate limiting section: algorithm comparison, Lua script, Gin middleware - Add async task deep dive: TaskQueue FIFO, context progress injection - Add consistency section: style/pipeline/data/deployment 4-layer guarantees
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# 系统架构全景
|
||||
|
||||
<div class="mt-2 flex justify-center">
|
||||
<img src="../public/architecture.svg" class="w-full max-h-[500px] object-contain" />
|
||||
<img src="../public/architecture.svg" class="w-full max-h-[450px] object-contain" />
|
||||
</div>
|
||||
|
||||
<!--
|
||||
指向架构图各层讲解:前端 → API → 管线 → 存储
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# 异步任务 — 队列与调度
|
||||
|
||||
FIFO 串行执行,信号驱动调度
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go {1-6|8-12|all}
|
||||
type TaskQueue struct {
|
||||
mu sync.Mutex
|
||||
jobs []*TaskJob
|
||||
ready chan struct{} // 新任务到达信号
|
||||
stop chan struct{} // 优雅关闭信号
|
||||
}
|
||||
|
||||
func (q *TaskQueue) Enqueue(job *TaskJob) {
|
||||
q.mu.Lock()
|
||||
q.jobs = append(q.jobs, job)
|
||||
q.mu.Unlock()
|
||||
q.ready <- struct{}{} // 唤醒 run() 协程
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="Enqueue → run() → processNext() 流程">
|
||||
提交任务入队并发送信号。`run()` 协程阻塞等待信号,收到后调用 `processNext()` 取队首任务执行。FIFO 保证任务按提交顺序串行处理。
|
||||
</Item>
|
||||
|
||||
<Item title="每个 Job 是一个闭包">
|
||||
`TaskJob` 封装了 `context.Context` 和执行函数。闭包捕获任务参数,context 控制超时和取消,确保每个任务独立且可中断。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
---
|
||||
|
||||
# 进度推送与 WebSocket
|
||||
|
||||
Context 注入 + 多通道推送
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go {1-3|5-8|all}
|
||||
func WithProgressReporter(ctx context.Context, r ProgressReporter) context.Context {
|
||||
return context.WithValue(ctx, progressCtxKey, r)
|
||||
}
|
||||
|
||||
// 管线节点中使用
|
||||
reporter := GetProgressReporter(ctx)
|
||||
reporter.Report(Progress{Stage: "prompt", Percent: 25})
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="进度阶段划分">
|
||||
5%(任务创建)→ 25%(提示词优化完成)→ 60%(素材生成完成)→ 80%(质检通过)→ 100%(格式适配 + 上传)。每个阶段由对应节点触发回调。
|
||||
</Item>
|
||||
|
||||
<Item title="双通道推送">
|
||||
**WebSocket**:`ws://host/api/v1/tasks/:taskId/ws`,实时推送进度和结果。**HTTP 轮询**:`GET /api/v1/tasks/:taskId`,兼容降级方案。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<div class="flex justify-center mt-2">
|
||||
<img src="../public/async-task.svg" class="w-full max-h-[180px] object-contain" />
|
||||
</div>
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
提交即返回,后台并发执行
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="📤 任务提交 → 后台执行">
|
||||
|
||||
1. 提交请求 → 创建 Task(`running`)→ 立即返回 `taskId`
|
||||
@@ -22,3 +24,5 @@
|
||||
质检重试最多 3 次,任务级重试 1 次,超限降级输出。
|
||||
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
四表结构,清晰的实体关系
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img src="../public/data-model.svg" class="w-full max-h-[250px] object-contain" />
|
||||
<img src="../public/data-model.svg" class="w-full max-h-[220px] object-contain" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mt-2">
|
||||
|
||||
<Item title="👤 USER + PROJECT">
|
||||
- 用户:`id`(base62)、`username`、`password_hash`(bcrypt)
|
||||
- 工程:`id`、`user_id`、`name`、`description`
|
||||
用户:`id`(base62)、`username`、`password_hash`(bcrypt)
|
||||
工程:`id`、`user_id`、`name`
|
||||
</Item>
|
||||
|
||||
<Item title="🎨 STYLE + TASK + ASSET">
|
||||
- 风格表:`project_id` 一对一,`kv_pairs` JSON 存储
|
||||
- 任务:`prompt`、`status`、`style_override`
|
||||
- 素材:`file_url`、`metadata`,存于七牛云 Kodo
|
||||
风格表:`project_id` 一对一,`kv_pairs` JSON
|
||||
任务:`prompt`、`status`、`progress`
|
||||
素材:`file_url`、`metadata`
|
||||
</Item>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
两项关键的工程优化
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🔍 请求去重(Request Dedup)">
|
||||
`hash(prompt + assetType + params)` 生成唯一键,`sync.Map` 内存缓存。相同参数并发请求自动合并,返回已有 `taskId`,节省 API 调用成本。
|
||||
</Item>
|
||||
@@ -9,3 +11,5 @@
|
||||
<Item title="📡 WebSocket 实时推送">
|
||||
建立 `ws://host/api/v1/tasks/:taskId/ws` 连接,管线各阶段推送进度、质检重试信息、最终结果。同时兼容 HTTP 轮询方案。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
Docker Compose 双容器 + 一键部署
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img src="../public/deployment.svg" class="w-full max-h-[300px] object-contain" />
|
||||
<img src="../public/deployment.svg" class="w-full max-h-[260px] object-contain" />
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🐳 容器化方案">
|
||||
nginx 容器:Vite 构建产物 + 反向代理 `/api`。app 容器:Go 二进制 + SQLite 挂载。内部网络通信,nginx 暴露 8080 端口。
|
||||
nginx 容器:Vite 构建产物 + 反向代理 `/api`。app 容器:Go 二进制 + SQLite 挂载。nginx 暴露 8080 端口。
|
||||
</Item>
|
||||
|
||||
<Item title="🚀 一键部署">
|
||||
`docker compose up -d --build` 或 `./deploy.sh`。Gitee CI/CD 自动触发,SQLite 嵌入式零外部依赖。
|
||||
`docker compose up -d --build` 或 `./deploy.sh`。Gitee CI/CD 自动触发,SQLite 零外部依赖。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Eino Graph 深入 — 构建与编排
|
||||
|
||||
有向图构建与本地状态管理
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go {1-5|7-11|all}
|
||||
g := compose.NewGraph[PipelineInput, PipelineOutput](
|
||||
compose.WithGenLocalState(func(ctx context.Context) *PipelineState {
|
||||
return &PipelineState{} // 每次调用获得全新状态
|
||||
}),
|
||||
)
|
||||
|
||||
g.AddLambdaNode(nodePromptOptimizer, &compose.Lambda{
|
||||
StatePreHandler: preparePromptInput, // 从 state 读取输入
|
||||
StatePostHandler: storePromptOutput, // 结果写回 state
|
||||
})
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="WithGenLocalState — 隔离的执行上下文">
|
||||
每次 `graph.Invoke()` 调用生成独立的 `PipelineState`,避免并发调用间的状态污染。State 在节点间通过 Pre/Post Handler 流转。
|
||||
</Item>
|
||||
|
||||
<Item title="StatePreHandler / StatePostHandler 模式">
|
||||
PreHandler 从全局 State 提取当前节点所需字段;PostHandler 将节点输出写回 State。节点本身无状态,所有数据经 State 中转。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
---
|
||||
|
||||
# Eino Graph 深入 — 分支路由与重试
|
||||
|
||||
QualitySupervisor 驱动的条件分支
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go {1-10|all}
|
||||
g.AddBranch(nodeQualitySupervisor, compose.NewGraphBranch(
|
||||
func(ctx context.Context, _ PipelineInput) (string, error) {
|
||||
var next string
|
||||
_ = compose.ProcessState[*PipelineState](ctx,
|
||||
func(_ context.Context, state *PipelineState) error {
|
||||
next = state.NextNode // 质检结果决定路由
|
||||
return nil
|
||||
})
|
||||
return next, nil
|
||||
},
|
||||
map[string]bool{
|
||||
nodePromptOptimizer: true,
|
||||
nodeFormatAdapter: true,
|
||||
},
|
||||
))
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="路由逻辑">
|
||||
质检通过 → FormatAdapter 直接输出;不通过且 retry < 3 → PromptOptimizer 重新优化;不通过且 retry >= 3 → FormatAdapter 降级输出。
|
||||
</Item>
|
||||
|
||||
<Item title="管线全貌">
|
||||
下图展示了完整的分支路由与重试机制,包括状态流转和降级路径。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<div class="flex justify-center mt-2">
|
||||
<img src="../public/pipeline-detail.svg" class="w-full max-h-[200px] object-contain" />
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go
|
||||
```go {all|1-4|6-10|all}
|
||||
type PipelineInput struct {
|
||||
UserID, ProjectID, TaskID string
|
||||
Prompt, AssetType string
|
||||
@@ -22,6 +22,8 @@ type PipelineOutput struct {
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="graph.Build() — 构建管线">
|
||||
`compose.NewGraph` 创建有向图,注册四节点。质检节点条件分支:通过 → 格式适配,不通过 → 回退提示词优化。
|
||||
</Item>
|
||||
@@ -29,3 +31,5 @@ type PipelineOutput struct {
|
||||
<Item title="graph.Invoke() — 执行管线">
|
||||
传入 `PipelineInput`,按拓扑顺序执行。每节点完成回调更新进度(5% → 25% → 60% → 80% → 100%)。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# 一致性保障 — 风格与管线
|
||||
|
||||
多层一致性确保输出可控
|
||||
|
||||
<Item title="风格一致性 — 合并策略">
|
||||
Project Style(基底)+ Task Override(覆盖)→ `mergeStyle()` 逐键合并。工程风格定义全局基调,任务风格按需微调,互不干扰。
|
||||
</Item>
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```go
|
||||
func mergeStyle(projectStyle, taskStyle map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for k, v := range projectStyle { result[k] = v }
|
||||
for k, v := range taskStyle { result[k] = v } // 覆盖
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="管线一致性 — Eino Graph 保证">
|
||||
Graph 强制拓扑顺序执行,State 每次调用独立生成。节点无状态,数据经 State 中转,避免并发污染。
|
||||
</Item>
|
||||
|
||||
<Item title="质量一致性 — 质检 + 重试 + 降级">
|
||||
QualitySupervisor 自动质检,不合格重试(最多 3 次),超限降级输出。确保最终产物始终可用。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
---
|
||||
|
||||
# 一致性保障 — 数据与部署
|
||||
|
||||
从存储到部署的全链路一致性
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="数据一致性 — ACID 事务">
|
||||
SQLite 单文件 ACID + GORM 事务包裹关键操作。CASCADE 删除保证级联清理,外键约束维护引用完整性。
|
||||
</Item>
|
||||
|
||||
<Item title="ID 一致性 — 前缀 + 随机">
|
||||
格式:`prefix_base62`(如 `task_9bN4vR`)。`crypto/rand` 生成 base62 编码,全局唯一且可读。前缀标识实体类型,便于调试。
|
||||
</Item>
|
||||
|
||||
<Item title="部署一致性 — 容器化 + 确定性构建">
|
||||
Docker Compose 双容器(nginx + app),确定性构建保证环境一致。CI/CD 推送 master 自动触发部署。
|
||||
</Item>
|
||||
|
||||
<Item title="配置一致性 — Viper 多层覆盖">
|
||||
配置优先级:配置文件 → 环境变量 → 默认值。`Viper` 统一管理,开发/测试/生产环境无缝切换。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<div class="flex justify-center mt-2">
|
||||
<img src="../public/harness.svg" class="w-full max-h-[160px] object-contain" />
|
||||
</div>
|
||||
@@ -1,5 +1,7 @@
|
||||
# 核心亮点总结
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🤖 多智能体协作管线">
|
||||
Eino `compose.Graph` 编排四阶段智能体,支持质检重试(最多 3 次)与降级输出。
|
||||
</Item>
|
||||
@@ -15,3 +17,5 @@ WebSocket 推送各阶段进度与质检信息。相同参数并发请求自动
|
||||
<Item title="🎨 风格系统 + 🚀 一键部署">
|
||||
两层风格架构(Project + Task Override)保证一致性。Docker Compose + Gitee CI/CD 零依赖部署。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -2,18 +2,26 @@
|
||||
|
||||
基于 Eino `compose.Graph` 编排的四阶段智能体协作管线
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="① PromptOptimizer — 提示词优化">
|
||||
合并工程风格 + 任务风格覆盖,生成三段式规范提示词。调用 Eino Chain 驱动 LLM 优化,无 Key 自动回退模板。
|
||||
合并工程风格 + 任务风格覆盖,生成三段式规范提示词。LLM 优化,无 Key 自动回退模板。
|
||||
</Item>
|
||||
|
||||
<Item title="② AssetGenerator — 素材生成">
|
||||
调用 OpenAI 兼容的文生图 API(默认 Stability AI),根据优化后提示词生成素材。
|
||||
调用 OpenAI 兼容的文生图 API,根据优化后提示词生成素材。
|
||||
</Item>
|
||||
|
||||
<Item title="③ QualitySupervisor → ④ FormatAdapter">
|
||||
视觉模型自动质检,不合格触发重优化(最多 3 次)。通过后组装 Spritesheet + 元数据,上传七牛云 Kodo。
|
||||
<Item title="③ QualitySupervisor — 质检">
|
||||
视觉模型自动质检,不合格触发重优化(最多 3 次)。
|
||||
</Item>
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<img src="../public/pipeline.svg" class="w-full max-h-[180px] object-contain" />
|
||||
<Item title="④ FormatAdapter — 格式适配">
|
||||
组装 Spritesheet + 元数据,上传七牛云 Kodo。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<div class="mt-2 flex justify-center">
|
||||
<img src="../public/pipeline.svg" class="w-full max-h-[140px] object-contain" />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 痛点:2D 游戏素材制作
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🎨 美术成本高">
|
||||
专业美术人员费用高,独立开发者难以负担。外包单张 50-500 元,迭代周期长,试错成本高。
|
||||
专业美术费用高,独立开发者难以负担。外包单张 50-500 元,迭代周期长。
|
||||
</Item>
|
||||
|
||||
<Item title="🔧 管线适配难">
|
||||
@@ -9,9 +11,15 @@
|
||||
</Item>
|
||||
|
||||
<Item title="🤖 现有 AI 工具的局限">
|
||||
通用文生图模型缺乏游戏素材领域知识,无法理解"像素风"等术语,输出不可控,无质检重试。
|
||||
通用文生图模型缺乏游戏素材领域知识,无法理解"像素风"等术语,输出不可控。
|
||||
</Item>
|
||||
|
||||
<div class="mt-4 text-center text-gray-400">
|
||||
</v-clicks>
|
||||
|
||||
<div v-click class="mt-4 text-center text-gray-400">
|
||||
<b>核心诉求</b>:低门槛 · 风格一致 · 管线友好 · 质量可控
|
||||
</div>
|
||||
|
||||
<!--
|
||||
提问互动:在座各位有没有做过 2D 游戏?素材制作是不是最头疼的环节?
|
||||
-->
|
||||
|
||||
@@ -2,18 +2,22 @@
|
||||
|
||||
Eino Chain 驱动的智能提示词生成
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="① 风格合并">
|
||||
`finalStyle = merge(projectStyle, taskStyle)` — 工程风格为基底,任务风格逐键覆盖。自定义标签自动提取。
|
||||
`finalStyle = merge(projectStyle, taskStyle)` — 工程风格为基底,任务风格逐键覆盖。
|
||||
</Item>
|
||||
|
||||
<Item title="② 三段式提示词组装">
|
||||
**主体描述**(用户输入)+ **风格约束**(美术风格、色调、线条)+ **技术规格**(尺寸、格式、透明背景)
|
||||
**【主题】** 画面主体与场景 + **【风格】** 艺术风格与视觉特征 + **【技术】** 分辨率、格式等参数
|
||||
</Item>
|
||||
|
||||
<Item title="③ LLM 优化 + 回退机制">
|
||||
调用 Eino Chain 发给 DeepSeek 润色。**无 API Key 自动回退模板**,失败时注入重试上下文。
|
||||
调用 Eino Chain 发给 DeepSeek 润色。无 API Key 自动回退模板,失败时注入重试上下文。
|
||||
</Item>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img src="../public/prompt-agent.svg" class="w-full max-h-[160px] object-contain" />
|
||||
</v-clicks>
|
||||
|
||||
<div class="flex justify-center mt-2">
|
||||
<img src="../public/prompt-agent.svg" class="w-full max-h-[140px] object-contain" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# 令牌桶 — Redis Lua 脚本
|
||||
|
||||
原子操作,单次 Redis 往返
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
```lua {1-2|4-8|10-14|all}
|
||||
local data = redis.call('HMGET', key, 'tokens', 'ts')
|
||||
local tokens = tonumber(data[1])
|
||||
local last_ts = tonumber(data[2])
|
||||
-- 首次访问,初始化满桶
|
||||
if tokens == nil then
|
||||
tokens = burst
|
||||
last_ts = now
|
||||
end
|
||||
-- 计算时间差,补充令牌
|
||||
local delta = now - last_ts
|
||||
if delta > 0 then
|
||||
local refill = (delta * rate) / 1000
|
||||
tokens = math.min(burst, tokens + refill)
|
||||
end
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="为什么用 Lua 脚本?">
|
||||
整个令牌桶逻辑(读取 → 计算 → 扣减 → 写回)在 Redis 服务端原子执行,避免竞态条件。单次网络往返,性能最优。
|
||||
</Item>
|
||||
|
||||
<Item title="关键逻辑">
|
||||
**初始化**:首次访问设满桶令牌。**补充**:按时间差 × 速率补充,上限为 burst。**扣减**:请求到达时 tokens - 1,不足则拒绝。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
---
|
||||
|
||||
# 限流中间件集成
|
||||
|
||||
Gin 中间件 + 统一错误格式
|
||||
|
||||
<Item title="中间件签名">
|
||||
`func RateLimit(limiter Limiter, keyFunc func(c *gin.Context) string) gin.HandlerFunc` — 可配置的 key 提取函数,支持按用户ID、IP、端点等维度限流。
|
||||
</Item>
|
||||
|
||||
<Item title="注册方式">
|
||||
|
||||
- **用户级**:auth 路由组,key = userID,保护个人配额
|
||||
- **全局级**:`/generate` 端点,key = endpoint,保护总容量
|
||||
|
||||
</Item>
|
||||
|
||||
<Item title="响应规范">
|
||||
|
||||
- 通过:设置 `X-RateLimit-Remaining` 头
|
||||
- 拒绝:HTTP 429 + `Retry-After` 头 + 统一 JSON 错误体
|
||||
|
||||
</Item>
|
||||
@@ -0,0 +1,34 @@
|
||||
# 分布式限流 — 算法选型与架构
|
||||
|
||||
保护 API 端点,防止资源滥用
|
||||
|
||||
<div class="mt-2">
|
||||
|
||||
| 算法 | 优点 | 缺点 | 适用场景 |
|
||||
|------|------|------|----------|
|
||||
| 固定窗口计数器 | 实现简单 | 窗口边界突发 | 低精度场景 |
|
||||
| 滑动窗口 | 边界平滑 | 内存开销大 | 中等精度 |
|
||||
| 漏桶 | 严格平滑 | 无法应对突发 | 流量整形 |
|
||||
| **令牌桶** | **平滑 + 允许突发** | 实现稍复杂 | **API 限流** |
|
||||
|
||||
</div>
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="选择令牌桶 — 平滑限速 + 突发容忍">
|
||||
令牌按固定速率填充,请求消耗令牌。桶满时可应对短时突发流量,同时保持长期平均速率稳定。
|
||||
</Item>
|
||||
|
||||
<Item title="两级限流架构">
|
||||
**用户级**:per-user 维度,防止单用户滥用。**全局级**:per-endpoint 维度,保护后端服务总容量。两级独立配置,互不影响。
|
||||
</Item>
|
||||
|
||||
<Item title="容错策略 — Fail-Open">
|
||||
Redis 不可用时限流器自动放行(fail-open),保障核心业务不因限流组件故障而中断。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<div class="flex justify-center mt-2">
|
||||
<img src="../public/ratelimit.svg" class="w-full max-h-[180px] object-contain" />
|
||||
</div>
|
||||
@@ -20,10 +20,10 @@
|
||||
|
||||
- WebSocket 实时进度推送
|
||||
- 批量生成与队列调度
|
||||
- 文生图多模型适配(DALL-E、Midjourney)
|
||||
- 文生图多模型适配
|
||||
- 素材版本管理与历史记录
|
||||
- 分布式令牌桶限流(Redis + Lua)
|
||||
- 自定义协程池(NumCPU × 4 workers)
|
||||
- 自定义协程池
|
||||
- RabbitMQ 消息队列解耦
|
||||
- Prometheus 监控 + Grafana 看板
|
||||
|
||||
|
||||
@@ -6,12 +6,20 @@
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🗣️ 自然语言驱动 + 🎨 风格一致">
|
||||
用文字描述素材需求,AI 自动生成。工程级风格预设保证同一项目素材风格统一。
|
||||
<Item title="🗣️ 自然语言驱动">
|
||||
用文字描述素材需求,AI 自动生成。降低创作门槛。
|
||||
</Item>
|
||||
|
||||
<Item title="📦 管线友好 + ✅ 质量可控">
|
||||
标准尺寸输出,适配 Unity / Godot。自动质检 + 重试(最多 3 次)+ 降级兜底。
|
||||
<Item title="🎨 风格一致">
|
||||
工程级风格预设保证同一项目素材风格统一。
|
||||
</Item>
|
||||
|
||||
<Item title="📦 管线友好">
|
||||
标准尺寸输出,适配 Unity / Godot。
|
||||
</Item>
|
||||
|
||||
<Item title="✅ 质量可控">
|
||||
自动质检 + 重试(最多 3 次)+ 降级兜底。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
保证一致性与灵活性的风格管理方案
|
||||
|
||||
<v-clicks>
|
||||
|
||||
<Item title="🏗️ 工程风格(Project Style)">
|
||||
全局风格配置,以 kvPairs 存储(如 `artStyle=pixel`)。同一工程下所有素材自动继承,保证风格统一。
|
||||
全局风格配置,以 kvPairs 存储(如 `artStyle=pixel`)。同一工程下所有素材自动继承。
|
||||
</Item>
|
||||
|
||||
<Item title="🔀 任务风格覆盖(Task Override)">
|
||||
@@ -13,3 +15,10 @@
|
||||
<Item title="⚙️ 合并逻辑">
|
||||
`finalStyle = merge(projectStyle, taskStyle)` — 工程风格为基底,任务风格逐键覆盖。自定义标签(`custom:` 前缀)自动提取注入提示词。
|
||||
</Item>
|
||||
|
||||
</v-clicks>
|
||||
|
||||
<!--
|
||||
演示:假设工程风格 artStyle=pixel, palette=warm
|
||||
任务覆盖 palette=cool → 最终 palette=cool,其他继承
|
||||
-->
|
||||
|
||||
@@ -36,3 +36,10 @@
|
||||
</Item>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
重点:为什么选 Go?为什么选 Eino?为什么 SQLite?
|
||||
Go: 高并发 goroutine,编译型语言性能好
|
||||
Eino: 字节跳动出品,Graph 编排天然支持分支重试
|
||||
SQLite: 单实例部署,零配置,足够用
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" font-family="system-ui, sans-serif" font-size="13">
|
||||
<defs>
|
||||
<filter id="shadow" x="-2%" y="-2%" width="104%" height="104%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="2" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#60a5fa"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#4ade80"/>
|
||||
</marker>
|
||||
<marker id="arrow-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f87171"/>
|
||||
</marker>
|
||||
<marker id="arrow-yellow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#fbbf24"/>
|
||||
</marker>
|
||||
<marker id="arrow-purple" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#c084fc"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="400" fill="#0f172a" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" font-size="16" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Async Task Lifecycle</text>
|
||||
|
||||
<!-- POST /generate (client request) -->
|
||||
<rect x="20" y="75" width="120" height="50" rx="6" fill="#1a1a2e" stroke="#60a5fa" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="80" y="97" font-size="12" fill="#e2e8f0" text-anchor="middle" font-weight="bold">POST</text>
|
||||
<text x="80" y="113" font-size="11" fill="#60a5fa" text-anchor="middle">/generate</text>
|
||||
|
||||
<!-- Arrow: POST → Create Task -->
|
||||
<line x1="142" y1="100" x2="178" y2="100" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- Create Task -->
|
||||
<rect x="180" y="70" width="140" height="60" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="250" y="95" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Create Task</text>
|
||||
<text x="250" y="112" font-size="10" fill="#fbbf24" text-anchor="middle">status = running</text>
|
||||
<text x="250" y="124" font-size="10" fill="#94a3b8" text-anchor="middle">→ DB insert</text>
|
||||
|
||||
<!-- Arrow: Create Task → Return taskId (immediate response) -->
|
||||
<path d="M 250,130 L 250,155 L 110,155 L 110,127" fill="none" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
<rect x="130" y="142" width="150" height="22" rx="4" fill="#1e293b"/>
|
||||
<text x="205" y="158" font-size="10" fill="#4ade80" text-anchor="middle">return taskId immediately</text>
|
||||
|
||||
<!-- Background goroutine label -->
|
||||
<rect x="350" y="50" width="430" height="310" rx="6" fill="none" stroke="#334155" stroke-width="1" stroke-dasharray="6,3"/>
|
||||
<text x="565" y="70" font-size="12" fill="#64748b" text-anchor="middle" font-style="italic">Background: goroutine</text>
|
||||
|
||||
<!-- Arrow: Create Task → TaskQueue -->
|
||||
<line x1="322" y1="100" x2="378" y2="100" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- TaskQueue (FIFO) -->
|
||||
<rect x="380" y="70" width="130" height="60" rx="6" fill="#1a1a2e" stroke="#c084fc" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="445" y="92" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">TaskQueue</text>
|
||||
<text x="445" y="108" font-size="11" fill="#c084fc" text-anchor="middle">FIFO</text>
|
||||
<text x="445" y="122" font-size="10" fill="#94a3b8" text-anchor="middle">chan GenerateJob</text>
|
||||
|
||||
<!-- FIFO items visualization -->
|
||||
<rect x="390" y="135" width="22" height="16" rx="3" fill="#c084fc" opacity="0.7"/>
|
||||
<rect x="416" y="135" width="22" height="16" rx="3" fill="#c084fc" opacity="0.5"/>
|
||||
<rect x="442" y="135" width="22" height="16" rx="3" fill="#c084fc" opacity="0.3"/>
|
||||
<text x="482" y="148" font-size="9" fill="#94a3b8">jobs...</text>
|
||||
|
||||
<!-- Arrow: TaskQueue → Pipeline Execution -->
|
||||
<line x1="512" y1="100" x2="548" y2="100" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- Pipeline Execution -->
|
||||
<rect x="550" y="70" width="170" height="60" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="635" y="92" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Pipeline Execution</text>
|
||||
<text x="635" y="108" font-size="10" fill="#94a3b8" text-anchor="middle">4-stage Eino pipeline</text>
|
||||
<text x="635" y="122" font-size="10" fill="#64748b" text-anchor="middle">prompt → asset → quality → format</text>
|
||||
|
||||
<!-- Progress bar visualization -->
|
||||
<rect x="380" y="175" width="370" height="30" rx="6" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<!-- Progress segments -->
|
||||
<rect x="382" y="177" width="36" height="26" rx="4" fill="#818cf8" opacity="0.8"/>
|
||||
<text x="400" y="195" font-size="9" fill="#fff" text-anchor="middle">5%</text>
|
||||
<rect x="420" y="177" width="92" height="26" rx="4" fill="#60a5fa" opacity="0.8"/>
|
||||
<text x="466" y="195" font-size="9" fill="#fff" text-anchor="middle">25%</text>
|
||||
<rect x="514" y="177" width="100" height="26" rx="4" fill="#fbbf24" opacity="0.8"/>
|
||||
<text x="564" y="195" font-size="9" fill="#fff" text-anchor="middle">60%</text>
|
||||
<rect x="616" y="177" width="64" height="26" rx="4" fill="#4ade80" opacity="0.8"/>
|
||||
<text x="648" y="195" font-size="9" fill="#fff" text-anchor="middle">80%</text>
|
||||
<rect x="682" y="177" width="66" height="26" rx="4" fill="#34d399" opacity="0.9"/>
|
||||
<text x="715" y="195" font-size="9" fill="#fff" text-anchor="middle">100%</text>
|
||||
|
||||
<!-- Arrow: Pipeline → Update Progress -->
|
||||
<line x1="635" y1="132" x2="635" y2="173" stroke="#fbbf24" stroke-width="1.5" marker-end="url(#arrow-yellow)"/>
|
||||
|
||||
<!-- WebSocket push label -->
|
||||
<rect x="530" y="215" width="210" height="35" rx="6" fill="#1a1a2e" stroke="#c084fc" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="635" y="230" font-size="12" fill="#c084fc" text-anchor="middle" font-weight="bold">WebSocket Push</text>
|
||||
<text x="635" y="244" font-size="10" fill="#94a3b8" text-anchor="middle">real-time progress updates</text>
|
||||
|
||||
<!-- Arrow: progress bar → WebSocket -->
|
||||
<line x1="635" y1="205" x2="635" y2="213" stroke="#c084fc" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- Client receiving WebSocket -->
|
||||
<rect x="20" y="215" width="120" height="35" rx="6" fill="#1a1a2e" stroke="#c084fc" stroke-width="1.5" stroke-dasharray="4,3" filter="url(#shadow)"/>
|
||||
<text x="80" y="230" font-size="11" fill="#c084fc" text-anchor="middle" font-weight="bold">Client</text>
|
||||
<text x="80" y="244" font-size="9" fill="#94a3b8" text-anchor="middle">ws:// .../progress</text>
|
||||
|
||||
<!-- Arrow: WebSocket → Client (across diagram) -->
|
||||
<path d="M 530,232 L 200,232 L 142,232" fill="none" stroke="#c084fc" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- Completion path -->
|
||||
<!-- Arrow: Pipeline → Completion -->
|
||||
<line x1="720" y1="132" x2="720" y2="270" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- Save Assets -->
|
||||
<rect x="550" y="270" width="140" height="50" rx="6" fill="#1a1a2e" stroke="#4ade80" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="620" y="292" font-size="12" fill="#4ade80" text-anchor="middle" font-weight="bold">Save Assets</text>
|
||||
<text x="620" y="308" font-size="10" fill="#94a3b8" text-anchor="middle">→ disk / object store</text>
|
||||
|
||||
<!-- Arrow: Save Assets → Update DB -->
|
||||
<line x1="620" y1="322" x2="620" y2="348" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- Update DB -->
|
||||
<rect x="550" y="350" width="140" height="35" rx="6" fill="#1a1a2e" stroke="#4ade80" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="620" y="372" font-size="12" fill="#4ade80" text-anchor="middle" font-weight="bold">Update DB (done)</text>
|
||||
|
||||
<!-- Arrow: Update DB → status=done (notification to client) -->
|
||||
<path d="M 550,367 L 200,367 L 200,252" fill="none" stroke="#4ade80" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-green)"/>
|
||||
<text x="370" y="360" font-size="10" fill="#4ade80" text-anchor="middle">status = done + assets</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="20" y="370" width="500" height="24" rx="6" fill="#1e293b" stroke="#334155" stroke-width="1" opacity="0"/>
|
||||
<line x1="20" y1="390" x2="50" y2="390" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="60" y="394" font-size="10" fill="#94a3b8">Sync flow</text>
|
||||
<line x1="130" y1="390" x2="160" y2="390" stroke="#c084fc" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-purple)"/>
|
||||
<text x="170" y="394" font-size="10" fill="#94a3b8">WebSocket push</text>
|
||||
<line x1="280" y1="390" x2="310" y2="390" stroke="#4ade80" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-green)"/>
|
||||
<text x="320" y="394" font-size="10" fill="#94a3b8">Completion notify</text>
|
||||
<line x1="440" y1="390" x2="470" y2="390" stroke="#fbbf24" stroke-width="1.5" marker-end="url(#arrow-yellow)"/>
|
||||
<text x="480" y="394" font-size="10" fill="#94a3b8">Progress update</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,69 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, sans-serif">
|
||||
<rect width="800" height="420" fill="#0f172a" rx="8"/>
|
||||
<text x="400" y="30" text-anchor="middle" fill="#e2e8f0" font-size="16" font-weight="bold">一致性保障 — 全链路设计</text>
|
||||
<!-- Layer 1 -->
|
||||
<rect x="30" y="50" width="740" height="80" fill="#1e293b" rx="6" stroke="#3b82f6" stroke-width="1.5"/>
|
||||
<text x="50" y="72" fill="#60a5fa" font-size="13" font-weight="bold">风格一致性</text>
|
||||
<rect x="50" y="82" width="140" height="36" fill="#1e3a5f" rx="4"/>
|
||||
<text x="120" y="105" text-anchor="middle" fill="#93c5fd" font-size="11">Project Style (基底)</text>
|
||||
<line x1="190" y1="100" x2="230" y2="100" stroke="#4ade80" stroke-width="2" marker-end="url(#arrowG)"/>
|
||||
<rect x="230" y="82" width="100" height="36" fill="#1e3a5f" rx="4"/>
|
||||
<text x="280" y="105" text-anchor="middle" fill="#93c5fd" font-size="11">mergeStyle()</text>
|
||||
<line x1="330" y1="100" x2="370" y2="100" stroke="#4ade80" stroke-width="2" marker-end="url(#arrowG)"/>
|
||||
<rect x="370" y="82" width="140" height="36" fill="#1e3a5f" rx="4"/>
|
||||
<text x="440" y="105" text-anchor="middle" fill="#93c5fd" font-size="11">Task Override (覆盖)</text>
|
||||
<line x1="510" y1="100" x2="550" y2="100" stroke="#4ade80" stroke-width="2" marker-end="url(#arrowG)"/>
|
||||
<rect x="550" y="82" width="200" height="36" fill="#166534" rx="4"/>
|
||||
<text x="650" y="105" text-anchor="middle" fill="#4ade80" font-size="11">Final Style (统一风格输出)</text>
|
||||
<!-- Layer 2 -->
|
||||
<rect x="30" y="140" width="740" height="80" fill="#1e293b" rx="6" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<text x="50" y="162" fill="#a78bfa" font-size="13" font-weight="bold">管线一致性</text>
|
||||
<rect x="50" y="172" width="110" height="36" fill="#2e1065" rx="4"/>
|
||||
<text x="105" y="195" text-anchor="middle" fill="#c4b5fd" font-size="11">Graph 拓扑排序</text>
|
||||
<line x1="160" y1="190" x2="200" y2="190" stroke="#a78bfa" stroke-width="2" marker-end="url(#arrowP)"/>
|
||||
<rect x="200" y="172" width="130" height="36" fill="#2e1065" rx="4"/>
|
||||
<text x="265" y="195" text-anchor="middle" fill="#c4b5fd" font-size="11">State 隔离 (每次独立)</text>
|
||||
<line x1="330" y1="190" x2="370" y2="190" stroke="#a78bfa" stroke-width="2" marker-end="url(#arrowP)"/>
|
||||
<rect x="370" y="172" width="140" height="36" fill="#2e1065" rx="4"/>
|
||||
<text x="440" y="195" text-anchor="middle" fill="#c4b5fd" font-size="11">Pre/Post Handler</text>
|
||||
<line x1="510" y1="190" x2="550" y2="190" stroke="#a78bfa" stroke-width="2" marker-end="url(#arrowP)"/>
|
||||
<rect x="550" y="172" width="200" height="36" fill="#166534" rx="4"/>
|
||||
<text x="650" y="195" text-anchor="middle" fill="#4ade80" font-size="11">质检重试 + 降级兜底</text>
|
||||
<!-- Layer 3 -->
|
||||
<rect x="30" y="230" width="740" height="80" fill="#1e293b" rx="6" stroke="#f59e0b" stroke-width="1.5"/>
|
||||
<text x="50" y="252" fill="#f59e0b" font-size="13" font-weight="bold">数据一致性</text>
|
||||
<rect x="50" y="262" width="120" height="36" fill="#451a03" rx="4"/>
|
||||
<text x="110" y="285" text-anchor="middle" fill="#fcd34d" font-size="11">SQLite ACID 事务</text>
|
||||
<line x1="170" y1="280" x2="210" y2="280" stroke="#f59e0b" stroke-width="2" marker-end="url(#arrowY)"/>
|
||||
<rect x="210" y="262" width="120" height="36" fill="#451a03" rx="4"/>
|
||||
<text x="270" y="285" text-anchor="middle" fill="#fcd34d" font-size="11">GORM 外键约束</text>
|
||||
<line x1="330" y1="280" x2="370" y2="280" stroke="#f59e0b" stroke-width="2" marker-end="url(#arrowY)"/>
|
||||
<rect x="370" y="262" width="140" height="36" fill="#451a03" rx="4"/>
|
||||
<text x="440" y="285" text-anchor="middle" fill="#fcd34d" font-size="11">CASCADE 级联删除</text>
|
||||
<line x1="510" y1="280" x2="550" y2="280" stroke="#f59e0b" stroke-width="2" marker-end="url(#arrowY)"/>
|
||||
<rect x="550" y="262" width="200" height="36" fill="#166534" rx="4"/>
|
||||
<text x="650" y="285" text-anchor="middle" fill="#4ade80" font-size="11">ID: prefix_base62 唯一</text>
|
||||
<!-- Layer 4 -->
|
||||
<rect x="30" y="320" width="740" height="80" fill="#1e293b" rx="6" stroke="#f87171" stroke-width="1.5"/>
|
||||
<text x="50" y="342" fill="#f87171" font-size="13" font-weight="bold">部署一致性</text>
|
||||
<rect x="50" y="352" width="130" height="36" fill="#450a0a" rx="4"/>
|
||||
<text x="115" y="375" text-anchor="middle" fill="#fca5a5" font-size="11">Docker Compose 双容器</text>
|
||||
<line x1="180" y1="370" x2="220" y2="370" stroke="#f87171" stroke-width="2" marker-end="url(#arrowR)"/>
|
||||
<rect x="220" y="352" width="130" height="36" fill="#450a0a" rx="4"/>
|
||||
<text x="285" y="375" text-anchor="middle" fill="#fca5a5" font-size="11">Gitee CI/CD 自动化</text>
|
||||
<line x1="350" y1="370" x2="390" y2="370" stroke="#f87171" stroke-width="2" marker-end="url(#arrowR)"/>
|
||||
<rect x="390" y="352" width="140" height="36" fill="#450a0a" rx="4"/>
|
||||
<text x="460" y="375" text-anchor="middle" fill="#fca5a5" font-size="11">Viper 多层配置覆盖</text>
|
||||
<line x1="530" y1="370" x2="570" y2="370" stroke="#f87171" stroke-width="2" marker-end="url(#arrowR)"/>
|
||||
<rect x="570" y="352" width="180" height="36" fill="#166534" rx="4"/>
|
||||
<text x="660" y="375" text-anchor="middle" fill="#4ade80" font-size="11">确定性构建 · 环境一致</text>
|
||||
<!-- Bottom summary -->
|
||||
<rect x="30" y="400" width="740" height="15" fill="transparent"/>
|
||||
<text x="400" y="412" text-anchor="middle" fill="#64748b" font-size="10">风格 → 管线 → 数据 → 部署:四层一致性保障,确保输出始终可控</text>
|
||||
<defs>
|
||||
<marker id="arrowG" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#4ade80"/></marker>
|
||||
<marker id="arrowP" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#a78bfa"/></marker>
|
||||
<marker id="arrowY" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#f59e0b"/></marker>
|
||||
<marker id="arrowR" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#f87171"/></marker>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,116 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 450" font-family="system-ui, sans-serif" font-size="13">
|
||||
<defs>
|
||||
<filter id="shadow" x="-2%" y="-2%" width="104%" height="104%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="2" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<!-- Arrow markers -->
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#60a5fa"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#4ade80"/>
|
||||
</marker>
|
||||
<marker id="arrow-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f87171"/>
|
||||
</marker>
|
||||
<marker id="arrow-gray" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#94a3b8"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="900" height="450" fill="#0f172a" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="450" y="30" font-size="16" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Pipeline Detail — 4-Stage Flow with Retry Logic</text>
|
||||
|
||||
<!-- PipelineState shared state box (dashed border, center) -->
|
||||
<rect x="310" y="190" width="280" height="70" rx="6" fill="none" stroke="#fbbf24" stroke-width="2" stroke-dasharray="8,4" filter="url(#shadow)"/>
|
||||
<rect x="310" y="190" width="280" height="70" rx="6" fill="#1e293b" opacity="0.9"/>
|
||||
<text x="450" y="218" font-size="14" fill="#fbbf24" text-anchor="middle" font-weight="bold">PipelineState</text>
|
||||
<text x="450" y="238" font-size="11" fill="#94a3b8" text-anchor="middle">Shared state across all stages</text>
|
||||
<text x="450" y="252" font-size="10" fill="#64748b" text-anchor="middle">prompt / asset / quality / result</text>
|
||||
|
||||
<!-- START node -->
|
||||
<rect x="20" y="90" width="80" height="44" rx="6" fill="#1a1a2e" stroke="#60a5fa" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="60" y="116" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">START</text>
|
||||
|
||||
<!-- Arrow: START → PromptOptimizer -->
|
||||
<line x1="102" y1="112" x2="138" y2="112" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- PromptOptimizer -->
|
||||
<rect x="140" y="72" width="150" height="80" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="215" y="95" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">PromptOptimizer</text>
|
||||
<text x="215" y="112" font-size="10" fill="#94a3b8" text-anchor="middle">prompt_builder</text>
|
||||
<text x="215" y="126" font-size="10" fill="#60a5fa" text-anchor="middle">string → Prompt</text>
|
||||
<text x="215" y="142" font-size="10" fill="#fbbf24" text-anchor="middle">progress: 5-25%</text>
|
||||
|
||||
<!-- Arrow: PromptOptimizer → AssetGenerator -->
|
||||
<line x1="292" y1="112" x2="328" y2="112" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- AssetGenerator -->
|
||||
<rect x="330" y="72" width="150" height="80" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="405" y="95" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">AssetGenerator</text>
|
||||
<text x="405" y="112" font-size="10" fill="#94a3b8" text-anchor="middle">asset_generator</text>
|
||||
<text x="405" y="126" font-size="10" fill="#60a5fa" text-anchor="middle">Prompt → Image</text>
|
||||
<text x="405" y="142" font-size="10" fill="#fbbf24" text-anchor="middle">progress: 25-60%</text>
|
||||
|
||||
<!-- Arrow: AssetGenerator → QualitySupervisor -->
|
||||
<line x1="482" y1="112" x2="518" y2="112" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- QualitySupervisor (diamond shape) -->
|
||||
<polygon points="590,60 660,112 590,164 520,112" fill="#1a1a2e" stroke="#fbbf24" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="590" y="108" font-size="12" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Quality</text>
|
||||
<text x="590" y="122" font-size="12" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Supervisor</text>
|
||||
|
||||
<!-- QualitySupervisor detail label -->
|
||||
<text x="590" y="180" font-size="10" fill="#94a3b8" text-anchor="middle">quality_supervisor</text>
|
||||
<text x="590" y="194" font-size="10" fill="#60a5fa" text-anchor="middle">Image → QualityReport</text>
|
||||
<text x="590" y="208" font-size="10" fill="#fbbf24" text-anchor="middle">progress: 60-80%</text>
|
||||
|
||||
<!-- PASS path: QualitySupervisor → FormatAdapter -->
|
||||
<line x1="662" y1="100" x2="698" y2="80" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
<text x="680" y="82" font-size="11" fill="#4ade80" text-anchor="middle" font-weight="bold">PASS</text>
|
||||
|
||||
<!-- FormatAdapter -->
|
||||
<rect x="700" y="55" width="160" height="55" rx="6" fill="#1a1a2e" stroke="#4ade80" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="780" y="78" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">FormatAdapter</text>
|
||||
<text x="780" y="95" font-size="10" fill="#94a3b8" text-anchor="middle">format_adapter 80-100%</text>
|
||||
|
||||
<!-- Arrow: FormatAdapter → END -->
|
||||
<line x1="862" y1="82" x2="880" y2="82" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- END node -->
|
||||
<rect x="822" y="135" width="60" height="36" rx="6" fill="#1a1a2e" stroke="#4ade80" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="852" y="157" font-size="13" fill="#4ade80" text-anchor="middle" font-weight="bold">END</text>
|
||||
<line x1="840" y1="112" x2="852" y2="135" stroke="#4ade80" stroke-width="1.5"/>
|
||||
|
||||
<!-- FAIL path: QualitySupervisor → back to PromptOptimizer (retry < 3) -->
|
||||
<path d="M 590,166 L 590,310 L 215,310 L 215,154" fill="none" stroke="#f87171" stroke-width="2" stroke-dasharray="6,3" marker-end="url(#arrow-red)"/>
|
||||
<rect x="330" y="295" width="120" height="24" rx="4" fill="#1e293b" stroke="#f87171" stroke-width="1"/>
|
||||
<text x="390" y="312" font-size="11" fill="#f87171" text-anchor="middle" font-weight="bold">FAIL: retry < 3</text>
|
||||
|
||||
<!-- DEGRADED path: QualitySupervisor → FormatAdapter (retry >= 3) -->
|
||||
<path d="M 660,164 L 700,164 L 700,112" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-gray)"/>
|
||||
<rect x="640" y="148" width="110" height="20" rx="4" fill="#1e293b"/>
|
||||
<text x="695" y="162" font-size="10" fill="#94a3b8" text-anchor="middle">retry ≥ 3 (degraded)</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="20" y="370" width="860" height="60" rx="6" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<text x="40" y="390" font-size="12" fill="#94a3b8" font-weight="bold">Legend:</text>
|
||||
<line x1="100" y1="386" x2="140" y2="386" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="150" y="390" font-size="11" fill="#94a3b8">Normal flow</text>
|
||||
<line x1="240" y1="386" x2="280" y2="386" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
<text x="290" y="390" font-size="11" fill="#94a3b8">Pass / Success</text>
|
||||
<line x1="400" y1="386" x2="440" y2="386" stroke="#f87171" stroke-width="2" stroke-dasharray="6,3" marker-end="url(#arrow-red)"/>
|
||||
<text x="450" y="390" font-size="11" fill="#94a3b8">Fail / Retry</text>
|
||||
<line x1="540" y1="386" x2="580" y2="386" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-gray)"/>
|
||||
<text x="590" y="390" font-size="11" fill="#94a3b8">Degraded fallback</text>
|
||||
<rect x="710" y="378" width="60" height="18" rx="3" fill="none" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="6,3"/>
|
||||
<text x="740" y="392" font-size="11" fill="#94a3b8" text-anchor="middle">Shared state</text>
|
||||
|
||||
<!-- PipelineState connections (dashed lines to each node) -->
|
||||
<line x1="215" y1="152" x2="350" y2="190" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3,3" opacity="0.5"/>
|
||||
<line x1="405" y1="152" x2="450" y2="190" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3,3" opacity="0.5"/>
|
||||
<line x1="590" y1="164" x2="530" y2="190" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3,3" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,120 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" font-family="system-ui, sans-serif" font-size="13">
|
||||
<defs>
|
||||
<filter id="shadow" x="-2%" y="-2%" width="104%" height="104%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="2" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#60a5fa"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#4ade80"/>
|
||||
</marker>
|
||||
<marker id="arrow-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f87171"/>
|
||||
</marker>
|
||||
<marker id="arrow-yellow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#fbbf24"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="400" fill="#0f172a" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="30" font-size="16" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Rate Limiting Architecture</text>
|
||||
|
||||
<!-- Request (incoming) -->
|
||||
<rect x="20" y="140" width="100" height="50" rx="6" fill="#1a1a2e" stroke="#60a5fa" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="70" y="160" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Request</text>
|
||||
<text x="70" y="178" font-size="10" fill="#94a3b8" text-anchor="middle">POST /generate</text>
|
||||
|
||||
<!-- Arrow: Request → Gin Middleware -->
|
||||
<line x1="122" y1="165" x2="168" y2="165" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- Gin Middleware -->
|
||||
<rect x="170" y="130" width="140" height="70" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="240" y="158" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Gin Middleware</text>
|
||||
<text x="240" y="175" font-size="10" fill="#94a3b8" text-anchor="middle">RateLimiter()</text>
|
||||
<text x="240" y="190" font-size="10" fill="#64748b" text-anchor="middle">IP + Auth extraction</text>
|
||||
|
||||
<!-- Arrow: Middleware → User Limiter -->
|
||||
<line x1="312" y1="152" x2="358" y2="112" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="340" y="128" font-size="10" fill="#94a3b8" text-anchor="middle">per-user</text>
|
||||
|
||||
<!-- Arrow: Middleware → Global Limiter -->
|
||||
<line x1="312" y1="178" x2="358" y2="218" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="340" y="204" font-size="10" fill="#94a3b8" text-anchor="middle">global</text>
|
||||
|
||||
<!-- User-level Limiter -->
|
||||
<rect x="360" y="72" width="170" height="80" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="445" y="95" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">User-Level Limiter</text>
|
||||
<text x="445" y="112" font-size="10" fill="#94a3b8" text-anchor="middle">key: userID</text>
|
||||
<text x="445" y="126" font-size="10" fill="#fbbf24" text-anchor="middle">rate: 3 req/min</text>
|
||||
<text x="445" y="140" font-size="10" fill="#fbbf24" text-anchor="middle">burst: 15</text>
|
||||
|
||||
<!-- Global-level Limiter -->
|
||||
<rect x="360" y="195" width="170" height="80" rx="6" fill="#1a1a2e" stroke="#818cf8" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="445" y="218" font-size="13" fill="#e2e8f0" text-anchor="middle" font-weight="bold">Global-Level Limiter</text>
|
||||
<text x="445" y="235" font-size="10" fill="#94a3b8" text-anchor="middle">key: "generate"</text>
|
||||
<text x="445" y="249" font-size="10" fill="#fbbf24" text-anchor="middle">rate: 20 req/min</text>
|
||||
<text x="445" y="263" font-size="10" fill="#fbbf24" text-anchor="middle">burst: 50</text>
|
||||
|
||||
<!-- Arrow: User Limiter → Redis -->
|
||||
<line x1="532" y1="112" x2="578" y2="140" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- Arrow: Global Limiter → Redis -->
|
||||
<line x1="532" y1="235" x2="578" y2="210" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- Redis (Token Bucket Lua Script) -->
|
||||
<rect x="580" y="120" width="180" height="105" rx="6" fill="#1a1a2e" stroke="#f87171" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="670" y="145" font-size="14" fill="#f87171" text-anchor="middle" font-weight="bold">Redis</text>
|
||||
<text x="670" y="163" font-size="11" fill="#e2e8f0" text-anchor="middle">Token Bucket</text>
|
||||
<text x="670" y="180" font-size="11" fill="#e2e8f0" text-anchor="middle">Lua Script</text>
|
||||
<text x="670" y="200" font-size="10" fill="#94a3b8" text-anchor="middle">INCR + EXPIRE + EVAL</text>
|
||||
<text x="670" y="215" font-size="10" fill="#64748b" text-anchor="middle">Atomic operation</text>
|
||||
|
||||
<!-- Pass path: both limiters → proceed -->
|
||||
<path d="M 445,152 L 445,320 L 200,320 L 200,260" fill="none" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)" opacity="0"/>
|
||||
<line x1="445" y1="152" x2="445" y2="310" stroke="#4ade80" stroke-width="0" opacity="0"/>
|
||||
|
||||
<!-- Decision: Allow? -->
|
||||
<!-- Proceed to handler (both pass) -->
|
||||
<rect x="260" y="310" width="120" height="40" rx="6" fill="#1a1a2e" stroke="#4ade80" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="320" y="335" font-size="13" fill="#4ade80" text-anchor="middle" font-weight="bold">→ Handler</text>
|
||||
|
||||
<!-- Success flow: from middleware area down -->
|
||||
<path d="M 240,200 L 240,330 L 258,330" fill="none" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
<text x="230" y="270" font-size="10" fill="#4ade80" text-anchor="middle" transform="rotate(-90 230 270)">allow</text>
|
||||
|
||||
<!-- HTTP 429 response path -->
|
||||
<rect x="520" y="310" width="130" height="40" rx="6" fill="#1a1a2e" stroke="#f87171" stroke-width="1.5" filter="url(#shadow)"/>
|
||||
<text x="585" y="328" font-size="13" fill="#f87171" text-anchor="middle" font-weight="bold">HTTP 429</text>
|
||||
<text x="585" y="343" font-size="10" fill="#94a3b8" text-anchor="middle">Too Many Requests</text>
|
||||
|
||||
<!-- Arrow: Redis → 429 (rate exceeded) -->
|
||||
<path d="M 670,225 L 670,330 L 652,330" fill="none" stroke="#f87171" stroke-width="2" marker-end="url(#arrow-red)"/>
|
||||
<text x="670" y="275" font-size="10" fill="#f87171" text-anchor="middle">exceeded</text>
|
||||
|
||||
<!-- Fail-open path (Redis unavailable) -->
|
||||
<rect x="600" y="50" width="160" height="40" rx="6" fill="#1a1a2e" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="6,3" filter="url(#shadow)"/>
|
||||
<text x="680" y="68" font-size="12" fill="#fbbf24" text-anchor="middle" font-weight="bold">Fail-Open</text>
|
||||
<text x="680" y="82" font-size="10" fill="#94a3b8" text-anchor="middle">Redis unavailable → allow</text>
|
||||
|
||||
<!-- Arrow: Redis → Fail-Open -->
|
||||
<path d="M 720,120 L 720,92" fill="none" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-yellow)"/>
|
||||
<text x="738" y="110" font-size="9" fill="#fbbf24" text-anchor="start">error</text>
|
||||
|
||||
<!-- Arrow: Fail-Open → Handler -->
|
||||
<path d="M 600,70 L 320,70 L 320,308" fill="none" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-yellow)"/>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="20" y="365" width="760" height="28" rx="6" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<line x1="40" y1="379" x2="70" y2="379" stroke="#60a5fa" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="80" y="383" font-size="10" fill="#94a3b8">Normal</text>
|
||||
<line x1="140" y1="379" x2="170" y2="379" stroke="#4ade80" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
<text x="180" y="383" font-size="10" fill="#94a3b8">Allow</text>
|
||||
<line x1="220" y1="379" x2="250" y2="379" stroke="#f87171" stroke-width="2" marker-end="url(#arrow-red)"/>
|
||||
<text x="260" y="383" font-size="10" fill="#94a3b8">Rate Exceeded</text>
|
||||
<line x1="370" y1="379" x2="400" y2="379" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-yellow)"/>
|
||||
<text x="410" y="383" font-size="10" fill="#94a3b8">Fail-Open (Redis down)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
+31
-1
@@ -22,6 +22,11 @@ AI 驱动的 2D 游戏素材生成工具
|
||||
<b>赛道</b>:七牛云 XEngineer 暑期实训营
|
||||
</div>
|
||||
|
||||
<!--
|
||||
大家好,我是栈无不胜。今天给大家分享我们的项目 gen2D —— 一个 AI 驱动的 2D 游戏素材生成工具。
|
||||
接下来我会从痛点、方案、技术实现三个维度来展开。
|
||||
-->
|
||||
|
||||
---
|
||||
src: ./pages/problem.md
|
||||
---
|
||||
@@ -53,23 +58,43 @@ src: ./pages/tech-stack.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '核心实现'
|
||||
section: '核心实现 — Eino 管线'
|
||||
src: ./pages/eino-graph.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/eino-deep-dive.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '核心实现 — 提示词优化'
|
||||
src: ./pages/prompt-agent.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '核心实现 — 异步任务'
|
||||
src: ./pages/async-task.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/async-deep-dive.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/dedup-ws.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '核心实现 — 分布式限流'
|
||||
src: ./pages/ratelimit-overview.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/ratelimit-lua.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '数据与部署'
|
||||
src: ./pages/data-model.md
|
||||
---
|
||||
|
||||
@@ -77,6 +102,11 @@ src: ./pages/data-model.md
|
||||
src: ./pages/deployment.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '一致性保障'
|
||||
src: ./pages/harness.md
|
||||
---
|
||||
|
||||
---
|
||||
section: '总结'
|
||||
src: ./pages/highlights.md
|
||||
|
||||
Reference in New Issue
Block a user