refactor(auth): 优化401处理逻辑并提取登录跳转函数

将重复的401处理逻辑提取为redirectToLogin函数
修复刷新接口401时的循环重试问题
docs: 更新项目结构文档和快速启动指南
This commit is contained in:
2025-12-15 23:46:36 +08:00
parent 12830b7cf6
commit cbbcd3121f
2 changed files with 96 additions and 12 deletions

View File

@ -2,6 +2,89 @@
欢迎来到 UdminAI 项目文档中心。本文档集合提供了项目的完整技术文档涵盖了架构设计、模块说明、API 文档和最佳实践等内容。
## 🗂 项目结构
```
udmin_ai/
├─ backend/ # Rust 后端axum + tokio + sea-orm
│ └─ src/
│ ├─ flow/ # 流程执行核心
│ │ ├─ mod.rs
│ │ ├─ domain.rs # ChainDef/Node/Link/Group 等领域模型
│ │ ├─ context.rs # 执行选项/流式事件
│ │ ├─ engine.rs # FlowEngine 引擎(并发/分支/屏障/事件)
│ │ ├─ dsl.rs # Design JSON/DSL 解析与构建
│ │ ├─ mappers.rs # 设计映射到 ctxnodes.<id>.<executor>
│ │ ├─ log_handler.rs # 运行日志与事件推送抽象
│ │ ├─ task.rs # 任务注册表与执行器抽象
│ │ └─ executors/ # http/db/variable/script_* / condition
│ ├─ routes/ # API 路由层
│ │ ├─ flows.rs # /flows CRUD 与运行(同步/SSE/WS
│ │ └─ flow_run_logs.rs # /flow_run_logs 查询与删除
│ ├─ services/ # 业务编排与持久化
│ │ ├─ flow_service.rs # 流程加载/解析/驱动与日志
│ │ └─ flow_run_log_service.rs # 运行日志分页与批量删除
│ ├─ middlewares/ # 中间件服务
│ │ ├─ sse.rs # SSE 服务与事件封装
│ │ ├─ ws.rs # WebSocket 服务与转发
│ │ ├─ jwt.rs # 认证鉴权与用户提取
│ │ └─ http_client.rs # HTTP 客户端封装reqwest
│ ├─ models/ # SeaORM 数据模型
│ │ ├─ flow.rs # flows 表
│ │ └─ flow_run_log.rs # flow_run_logs 表
│ ├─ db.rs # 数据库初始化与全局句柄
│ └─ redis.rs # Redis 访问与令牌校验
├─ frontend/ # TypeScript 前端React + Vite
│ └─ src/
│ ├─ flows/ # 可视化流程编辑器与运行面板
│ │ ├─ editor.tsx # 设计器入口(自由布局)
│ │ ├─ services/custom-service.ts # 保存与运行HTTP/SSE/WS
│ │ └─ components/testrun/... # 测试运行面板与实时输出
│ ├─ pages/
│ │ ├─ FlowList.tsx # 流程列表/新建/编辑
│ │ └─ FlowRunLogs.tsx # 运行日志列表
│ ├─ utils/
│ │ ├─ axios.ts # 统一请求拦截401 刷新/跳转登录)
│ │ └─ token.ts # 本地令牌与用户信息管理
│ ├─ App.tsx / main.tsx # 入口与路由
│ └─ styles / layouts ... # 样式与布局
├─ docs/ # 文档
│ ├─ flow_architecture.md # Flow 架构与执行图Mermaid
│ └─ ... # 其他专题文档(概览/后端/前端/示例)
└─ .trae/rules/project_rules.md # 项目代码规范Rust 文件顺序等)
```
## ⚙️ 快速启动
- 后端启动(默认读取 `.env`端口HTTP 9898SSE 8866WS 8855
- `cd backend && ENV_FILE=dev cargo run`
- 前端启动(默认开发端口 8888
- `cd frontend && npm install && npm run dev`
## 🔌 主要接口
- 流程管理
- `POST /api/flows` 新建流程(支持 YAML 与 design_json
- `GET /api/flows` 分页查询
- `GET/PUT/DELETE /api/flows/{id}` 详情/更新/删除
- `POST /api/flows/{id}/run` 同步运行,返回 `{ ok, ctx, logs }`
- `POST /api/flows/{id}/run/stream` SSE 流式运行,推送节点事件与完成事件
- `GET /api/flows/{id}/run/ws` WebSocket 流式运行
- 运行日志
- `GET /api/flow_run_logs` 分页查询(支持按 flow_id/flow_code/user/ok 过滤)
- `DELETE /api/flow_run_logs/{ids}` 批量删除
## 🧩 架构与执行
- 引擎:自研 `FlowEngine`(递归驱动图,合流屏障,分支并行,组等待)
- 模式:`sync`(同步)/ `async`(组内异步)/ `queued`(组队列)/ `bounded`(组限并发)
- 条件JSON 条件集合或 `rhai` 表达式;无匹配时回退无条件边
- 执行器:`http``db``variable``script_rhai/js/python``condition`
- 文档详见:`docs/flow_architecture.md`
## 📚 文档导航
### 🏗️ 架构文档
@ -17,6 +100,7 @@
- **[路由层](docs/ROUTES.md)** - API 路由的设计规范、接口定义和中间件集成
- **[数据模型](docs/MODELS.md)** - 数据模型的设计原则、实体定义和关系映射
- **[中间件](docs/MIDDLEWARES.md)** - 中间件系统的设计理念、核心组件和使用方式
- **[Flow 架构与执行图](docs/flow_architecture.md)** - Flow 端到端架构图与执行流程
### 🛠️ 基础设施
@ -135,4 +219,4 @@
**UdminAI 团队**
*构建智能化的流程管理平台*
> 💡 **提示**: 建议将本文档加入浏览器书签,方便随时查阅。文档会持续更新,请关注最新版本。
> 💡 **提示**: 建议将本文档加入浏览器书签,方便随时查阅。文档会持续更新,请关注最新版本。

View File

@ -63,11 +63,7 @@ async function tryRefreshAndRetry(original: RetryConfig, message?: string): Prom
} catch (e: any) {
pendingQueue.forEach(p => p.reject(e))
pendingQueue = []
clearToken()
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
window.location.href = '/login'
}
return Promise.reject(e)
return redirectToLogin((e && e.message) || message)
} finally {
isRefreshing = false
}
@ -104,6 +100,10 @@ api.interceptors.response.use(
const msg = body?.message || '未登录或登录已过期'
return Promise.reject(new Error(msg))
}
// 刷新接口返回业务 401直接跳转登录避免循环重试
if (reqUrl.includes('/auth/refresh')) {
return redirectToLogin(body?.message)
}
return tryRefreshAndRetry(original, body?.message)
}
return r
@ -120,6 +120,10 @@ api.interceptors.response.use(
const msg = resp?.message || '用户名或密码错误'
return Promise.reject(new Error(msg))
}
// 刷新接口返回 401直接跳转登录避免循环重试
if (reqUrl.includes('/auth/refresh')) {
return redirectToLogin(resp?.message)
}
// 已经重试过了,直接拒绝
if (original._retry) {
@ -144,14 +148,10 @@ api.interceptors.response.use(
pendingQueue = []
return api(original)
}
} catch (e) {
} catch (e: any) {
pendingQueue.forEach(p => p.reject(e))
pendingQueue = []
clearToken()
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
window.location.href = '/login'
}
return Promise.reject(e)
return redirectToLogin((e && e.message) || resp?.message)
} finally {
isRefreshing = false
}