diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml
new file mode 100644
index 0000000..de2750a
--- /dev/null
+++ b/.github/workflows/test-build.yml
@@ -0,0 +1,73 @@
+name: Test Build
+
+on:
+ pull_request:
+ branches: [master, main, develop]
+ push:
+ branches: [feature/stats-ui-demo]
+
+jobs:
+ test-build:
+ runs-on: ubuntu-latest
+
+ steps:
+ # 1. 检出代码
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ # 2. 设置 Node.js 环境
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+ cache: 'npm'
+ cache-dependency-path: web/package-lock.json
+
+ # 3. 安装前端依赖
+ - name: Install Frontend Dependencies
+ run: |
+ cd web
+ npm ci --production=false
+
+ # 4. 构建前端
+ - name: Build Frontend
+ run: |
+ cd web
+ npm run build
+ env:
+ VITE_SHORT_BASE: "https://example.com"
+ VITE_API_BASE: "https://api.example.com"
+
+ # 5. 设置 Java 环境
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+
+ # 6. 运行后端测试
+ - name: Run Backend Tests
+ run: mvn clean test
+ env:
+ APP_DOMAIN: "https://example.com"
+ DB_PASSWORD: "test"
+ JWT_SECRET: "test-secret"
+ REDIS_PASSWORD: "test-redis"
+
+ # 7. 简单编译检查(不实际打包)
+ - name: Compile Backend
+ run: mvn compile -q
+ env:
+ APP_DOMAIN: "https://example.com"
+ DB_PASSWORD: "test"
+ JWT_SECRET: "test-secret"
+ REDIS_PASSWORD: "test-redis"
+
+ # 8. 验证构建产物
+ - name: Verify Build Output
+ run: |
+ echo "✅ 前端构建验证"
+ ls -la web/dist/
+ echo "✅ 后端编译验证"
+ ls -la target/classes/
\ No newline at end of file
diff --git a/README.md b/README.md
index 006581a..be5e3a2 100644
--- a/README.md
+++ b/README.md
@@ -1,849 +1,188 @@
-# TinyFlow - 高性能短链接系统
-
-
-
-
-**世界上没有两个相同的雨滴,就像每一个短链接都独一无二**
-
-**雨滴汇聚成河流 (Flow),链接汇聚成数据洪流**
-
-[](https://opensource.org/licenses/MIT)
-[](https://spring.io/projects/spring-boot)
-[](https://vuejs.org/)
-[](https://www.oracle.com/java/)
-
-一款现代化、高并发的短链接生成与统计系统
-
-压测验证可稳定支撑 **3000+ QPS**,P99 延迟 **< 100ms**
-
-[在线演示](http://your-demo-url.com) · [快速开始](#快速开始) · [技术架构](#技术栈与架构) · [部署指南](#部署到生产环境)
-
+
+

+
TinyFlow
----
-
-## 📖 目录
-
-- [项目简介](#项目简介)
-- [核心特性](#核心特性)
-- [技术栈与架构](#技术栈与架构)
-- [系统架构](#系统架构)
-- [性能指标](#性能指标)
-- [快速开始](#快速开始)
-- [项目结构](#项目结构)
-- [配置说明](#配置说明)
-- [开发与调试](#开发与调试)
-- [部署到生产环境](#部署到生产环境)
-- [性能优化](#性能优化)
-- [常见问题](#常见问题)
-- [后续拓展计划](#后续拓展计划)
-- [贡献指南](#贡献指南)
-- [开源协议](#开源协议)
-- [联系方式](#联系方式)
-
----
-
-## 🌟 项目简介
-
-### 为什么叫 TinyFlow?
-
-> 世界上没有两个完全相同的雨滴,就像这个系统中的每一个短链接都是独一无二的。
->
-> 雨滴汇聚到一起就成了河流(Flow),无数次的点击汇聚成数据洪流。
->
-> 微小而独特(Tiny),汇聚成流(Flow),这就是 TinyFlow 的设计哲学。
-
-TinyFlow 是一款面向高并发场景的短链接生成与统计系统,采用**号段模式 + 多级缓存 + 消息队列**架构,解决了传统短链系统在高并发下的性能瓶颈问题。
-
-### 核心亮点
-
-- 🚀 **高性能**:压测验证支撑 3000+ QPS,P99 延迟 < 100ms
-- 🔐 **无冲突**:号段模式 + Hashids + Base58 生成 6 位短码
-- 💾 **多级缓存**:Caffeine (L1) + Redis (L2) + MySQL,缓存命中率 85%+
-- ⚡ **异步持久化**:短链生成先写缓存,RabbitMQ 异步持久化到 MySQL,响应速度 < 1ms
-- 📊 **异步统计**:点击事件 RabbitMQ 消息队列解耦,消息丢失率 < 0.01%
-- 🛡️ **熔断降级**:Resilience4j 保障服务稳定性,MQ 不可用自动降级
-- 📈 **可观测性**:Prometheus + Grafana + Zipkin 全链路监控
-
-### 适用场景
-
-- ✅ 营销活动短链生成与数据统计
-- ✅ 社交媒体分享链接追踪
-- ✅ 移动端 App 推广链接
-- ✅ 二维码生成与管理
-- ✅ 学习高并发系统设计
-
----
-
-## ✨ 核心特性
-
-### 用户功能
-
-- **自定义别名**:可自定义短链后缀,增强品牌一致性
-- **一键生成**:输入长链接,秒级生成短链接
-- **二维码生成**:自动生成二维码,支持下载
-- **历史记录管理**:分页、筛选、编辑、删除
-- **访问统计**:点击趋势、来源分布、设备统计等
-- **多语言支持**:中英文切换
-
-### 技术特性
-
-- **分布式 ID 生成**:号段模式预分配 ID,双 Buffer 异步加载
-- **短码生成策略**:Hashids 算法 + Base58 编码,稳定生成 6 位短码
-- **异步持久化架构**:先写 Caffeine + Redis 缓存(< 1ms),再通过 RabbitMQ 异步持久化到 MySQL
-- **多级缓存架构**:本地缓存 + 分布式缓存 + 数据库,逐级回填
-- **缓存预热**:启动时加载 Top 1000 热点短链到本地缓存
-- **消息队列解耦**:
- - 短链生成:异步持久化到 MySQL,幂等性保证
- - 点击统计:异步处理,批量聚合刷库
-- **熔断降级**:Redis/MySQL/RabbitMQ 故障自动熔断,降级策略保障可用性
-- **全链路监控**:Prometheus 采集指标,Grafana 可视化,Zipkin 追踪
-
----
-
-## 功能特性
-
-- 现代化 UI 与交互:极简卡片化布局、响应式适配、细致的悬停与动效
-- 统一跳转端点:前端所有点击、复制、二维码均使用 `GET /api/redirect/{code}`
-- 历史记录:支持分页刷新、筛选、编辑别名与删除条目
-- 多语言支持:内置 i18n,可在导航栏切换语言
-- 开发体验:Vite 快速热更新、结构化代码与组件拆分
-
----
-
-## 🛠️ 技术栈与架构
-
-### 前端技术栈
-
-| 技术 | 版本 | 说明 |
-|------|------|------|
-| Vue 3 | 3.x | Composition API,响应式框架 |
-| Vite | 5.x | 极速构建工具 |
-| Tailwind CSS | 3.x | 原子化 CSS 框架 |
-| Axios | 1.x | HTTP 请求库 |
-| Vue Router | 4.x | 前端路由 |
-| Vue I18n | 9.x | 国际化支持 |
-| ECharts | 5.x | 数据可视化图表 |
-| QRCode.vue | 3.x | 二维码生成 |
-
-### 后端技术栈
-
-| 技术 | 版本 | 说明 |
-|------|------|------|
-| Spring Boot | 3.5.7 | 基础框架 |
-| Spring Data JPA | 3.x | 数据访问层 |
-| MySQL | 8.0 | 关系型数据库 |
-| Redis | 7.x | 分布式缓存 |
-| RabbitMQ | 3.x | 消息队列 |
-| Caffeine | 3.x | 本地缓存 |
-| Resilience4j | 2.x | 熔断器 |
-| Prometheus | - | 指标采集 |
-| Zipkin | - | 链路追踪 |
-| Grafana | - | 监控可视化 |
-
-### 核心组件
-
-- **短码生成**:号段模式 ID 生成器 + Hashids + Base58
-- **异步持久化**:RabbitMQ 消息队列 + 幂等性消费者 + 自动降级
-- **多级缓存**:Caffeine (L1) + Redis (L2) + MySQL (L3)
-- **消息队列**:
- - 短链持久化队列(tinyflow.shorturl.queue)
- - 点击统计队列(tinyflow.click.queue)
- - 死信队列(tinyflow.click.dlq)
-- **熔断降级**:Resilience4j 熔断器 + 降级策略
-- **可观测性**:Prometheus + Grafana + Zipkin
-
-### 运行拓扑
-
-**开发环境**:
-- 前端 dev server:`http://localhost:5173`(或 5174/5175)
-- 后端 API:`http://localhost:8080`
-- 前端通过 `vite.config.js` 代理 `/api` 到后端
-
-**生产环境**:
-- 前端:Nginx 静态托管 + 反向代理
-- 后端:Spring Boot JAR 部署
-- 数据库:MySQL 主从复制
-- 缓存:Redis 哨兵模式
-- 消息队列:RabbitMQ 集群
-
----
-
-## 🏗️ 系统架构
-
-### 整体架构图
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│ 用户端 (Web/Mobile) │
-└─────────────────────────────────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────────────┐
-│ Nginx (反向代理 + 负载均衡) │
-└─────────────────────────────────────────────────────────────────┘
- │
- ┌───────────────┴───────────────┐
- ▼ ▼
- ┌──────────────┐ ┌──────────────┐
- │ Vue 3 前端 │ │ Spring Boot │
- │ (静态资源) │ │ 后端服务 │
- └──────────────┘ └──────────────┘
- │
- ┌───────────────────────────────┼───────────────┐
- ▼ ▼ ▼
- ┌──────────────┐ ┌──────────────┐ ┌─────────┐
- │ Caffeine │ │ Redis │ │ RabbitMQ│
- │ (L1 本地缓存)│ │ (L2 分布式缓存)│ │ (消息队列)│
- └──────────────┘ └──────────────┘ └─────────┘
- │ │
- ▼ ▼
- ┌──────────────┐ ┌─────────┐
- │ MySQL │ │ 统计消费者│
- │ (持久化层) │ │ (批量写入)│
- └──────────────┘ └─────────┘
-```
-
-
-### 短码生成流程(异步持久化优化)
-
-```
-用户请求
- │
- ▼
-号段 ID 生成器 (SegmentIdGenerator)
- │
- ├─> 检查当前 Buffer 是否充足
- │
- ├─> 若 < 50%,异步加载下一 Buffer
- │
- ├─> 从内存获取 ID (无数据库 IO)
- │
- ▼
-Hashids 算法编码
- │
- ▼
-Base58 编码生成 6 位短码
- │
- ▼
-【异步持久化】
- │
- ├─> ① 立即写入 Caffeine 本地缓存 (< 0.1ms)
- │
- ├─> ② 写入 Redis 分布式缓存 (< 1ms)
- │
- ├─> ③ 更新布隆过滤器
- │
- ├─> ④ 发送 RabbitMQ 消息 (异步持久化)
- │
- ├─> ⑤ 立即返回短链接给用户 (< 1ms) ✅
- │
- └─> [后台] MQ 消费者异步持久化到 MySQL
- │
- ├─> 幂等性检查(避免重复插入)
- │
- └─> 写入数据库
-```
-
-
-### 多级缓存读取流程
-
-```
-用户访问短链
- │
- ▼
-L1 Caffeine 本地缓存
- │
- ├─> 命中 ──> 返回 (最快)
- │
- ├─> 未命中
- │ │
- │ ▼
- │ L2 Redis 分布式缓存
- │ │
- │ ├─> 命中 ──> 回填 L1 ──> 返回
- │ │
- │ ├─> 未命中
- │ │ │
- │ │ ▼
- │ │ L3 MySQL 数据库
- │ │ │
- │ │ ├─> 查询成功 ──> 回填 L2 ──> 回填 L1 ──> 返回
- │ │ │
- │ │ └─> 查询失败 ──> 返回 404
-```
-
-
-### 异步统计流程(点击计数)
-
-```
-用户点击短链
- │
- ▼
-记录点击事件
- │
- ▼
-发送到 RabbitMQ (tinyflow.click.queue)
- │
- ├─> 立即返回 302 重定向 (用户无感知)
- │
- ▼
-消息队列
- │
- ├─> 每 2 秒批量消费
- │
- ├─> 聚合统计数据 (Map
)
- │
- ├─> 批量 UPDATE clicks = clicks + delta
- │
- ├─> 手动 ACK 确认
- │
- └─> 失败重试 (指数退避 3 次) ──> 死信队列
-```
-
-### RabbitMQ 队列架构
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ RabbitMQ Server │
-│ │
-│ ┌────────────────────────────────────────────────┐ │
-│ │ 短链持久化队列 (tinyflow.shorturl.queue) │ │
-│ │ - 交换机: tinyflow.shorturl.exchange │ │
-│ │ - 路由键: shorturl.persist │ │
-│ │ - 消费者: ShortUrlPersistenceConsumer │ │
-│ │ - 功能: 异步持久化短链到 MySQL │ │
-│ └────────────────────────────────────────────────┘ │
-│ │
-│ ┌────────────────────────────────────────────────┐ │
-│ │ 点击统计队列 (tinyflow.click.queue) │ │
-│ │ - 交换机: tinyflow.click.exchange │ │
-│ │ - 路由键: click │ │
-│ │ - 消费者: ClickCountAggregator │ │
-│ │ - 功能: 批量聚合点击计数 │ │
-│ └────────────────────────────────────────────────┘ │
-│ │
-│ ┌────────────────────────────────────────────────┐ │
-│ │ 死信队列 (tinyflow.click.dlq) │ │
-│ │ - 交换机: tinyflow.click.dlx │ │
-│ │ - 路由键: click.dead │ │
-│ │ - 消费者: DeadLetterConsumer │ │
-│ │ - 功能: 处理消费失败的消息 │ │
-│ └────────────────────────────────────────────────┘ │
-└─────────────────────────────────────────────────────────┘
-```
-
-
----
-
-## 📊 性能指标
-
-### 压测环境
-
-- **服务器配置**:4 核 8GB
-- **压测工具**:K6
-- **并发数**:100 VU
-- **测试时长**:5 分钟
-
-### 性能数据
-
-| 指标 | 数值 | 说明 |
-|------|------|------|
-| QPS | 3000+ | 每秒处理请求数 |
-| P99 延迟 | < 100ms | 99% 请求响应时间 |
-| P95 延迟 | < 50ms | 95% 请求响应时间 |
-| 平均延迟 | ~30ms | 平均响应时间 |
-| 缓存命中率 | 85%+ | L1+L2 总命中率 |
-| 数据库 QPS | < 500 | 缓存后数据库压力 |
-| 消息吞吐 | 5000+/s | RabbitMQ 消息处理能力 |
-| 消息丢失率 | < 0.01% | 死信队列 + 手动 ACK |
-
-### 优化效果对比
-
-| 优化项 | 优化前 | 优化后 | 提升幅度 |
-|--------|--------|--------|----------|
-| 短码生成 TPS | 200 | 10000+ | **50 倍** |
-| 短码生成响应 | 10-50ms | < 1ms | **优化 95%+** |
-| 数据库压力 | 100% | 5% | **降低 95%** |
-| 缓存命中率 | 0% | 85% | **提升 85%** |
-| 列表接口响应 | 800ms | 50ms | **优化 94%** |
-| 系统 TPS | 600 | 3000+ | **5 倍** |
-
-
-
-
----
+
+
- 高性能短链接服务 -
+
-## 项目结构
+
-```
-前端/
-├── src/
-│ ├── components/ # 通用组件(加载、二维码、图标、统计卡片等)
-│ ├── pages/ # 页面(Dashboard、Stats)
-│ ├── router/ # 前端路由
-│ ├── composables/ # 组合式工具(API、统计等)
-│ ├── assets/ # 静态资源
-│ ├── i18n.js # 国际化配置
-│ ├── main.js # 入口文件
-│ └── style.css # 全局样式
-├── App.vue # 首页与顶栏
-├── vite.config.js # Dev 代理与构建配置
-├── package.json
-└── README.md
-```
+
-后端/
-- Controller
- - `ShortUrlController`:短链生成、维护与重定向相关接口
- - `StatsController`:统计相关查询接口
-- entity(含 DTO/VO)
- - `ShortUrl`、`ShortUrlDTO`、`ShortUrlOverviewDTO`
- - `ShortenRequest`、`PageResponseDTO`、`Result`
- - `DailyClick`、`DailyVisitTrendDTO`、`UrlClickStatsDTO`、`UrlListResponseDTO`
-- repository
- - `ShortUrlRepository`:短链数据访问
- - `DailyClickRepository`:点击统计数据访问
-- service
- - `ShortUrlService`:短链核心业务(生成、查询、重定向、更新别名、删除等)
-- 应用入口
- - `TinyFlowApplication`
+## 简介
+TinyFlow 是一个现代化的高性能短链接生成与统计系统,支持快速生成短链接并提供详细的访问数据分析功能。
----
+## 特性
-## 🚀 快速开始
+### 核心功能
+- 🚀 **极速生成**:毫秒级短链生成
+- 📊 **实时统计**:详细的访问数据分析
+- 🔗 **自定义短码**:支持自定义短链接后缀
+- 📱 **二维码生成**:自动生成短链接二维码
+- 🌐 **多语言支持**:完整的中英文国际化
-### 前置条件
+### 技术特点
+- 基于号段模式的ID生成算法
+- 多级缓存架构(本地 + Redis)
+- 异步消息队列处理
+- 响应式前端设计
-**环境要求**:
-- Java `>= 17`
-- Node.js `>= 18`
-- Maven `>= 3.6`
-- MySQL `>= 8.0`
-- Redis `>= 7.0`
-- RabbitMQ `>= 3.11`
+## 快速开始
-**推荐工具**:
-- IDE:IntelliJ IDEA / VS Code
-- 包管理器:npm / pnpm
-- 容器:Docker / Docker Compose
+### 环境要求
+- Java 17 或更高版本
+- Node.js 18 或更高版本
+- MySQL 8.0
+- Redis 7.0
-### 一键启动(Docker Compose)
+### 本地开发
+1. **克隆项目**
```bash
-# 1. 克隆项目
git clone https://github.com/Layau-code/TinyFlow.git
cd TinyFlow
-
-# 2. 启动基础服务 (MySQL + Redis + RabbitMQ + Prometheus + Grafana)
-docker-compose up -d
-
-# 3. 启动后端
-cd src/main
-mvn spring-boot:run
-
-# 4. 启动前端
-cd ../../web
-npm install
-npm run dev
-
-# 5. 访问应用
-# 前端:http://localhost:5173
-# 后端 API:http://localhost:8080
-# Grafana:http://localhost:3000 (admin/admin)
-# Prometheus:http://localhost:9090
```
-### 手动启动
-
-#### 后端启动
-
+2. **启动后端服务**
```bash
-# 1. 修改配置文件 src/main/resources/application.yml
-# 配置 MySQL、Redis、RabbitMQ 连接信息
-
-# 2. 初始化数据库
-mysql -u root -p < db/schema.sql
-
-# 3. 启动后端
-mvn clean package
-java -jar target/tinyflow-0.0.1-SNAPSHOT.jar
+mvn spring-boot:run
```
-#### 前端启动
-
+3. **启动前端服务**
```bash
-# 1. 进入前端目录
cd web
-
-# 2. 安装依赖
npm install
-
-# 3. 启动开发服务器
npm run dev
-
-# 4. 浏览器访问
-open http://localhost:5173
```
-### 构建生产包
-
-#### 前端构建
-
-```bash
-cd web
-npm run build
-# 产物输出到 web/dist 目录
-```
+4. **访问应用**
+- 前端界面:http://localhost:5173
+- 后端API:http://localhost:8080
-#### 后端构建
+## 技术栈
-```bash
-mvn clean package -DskipTests
-# 产物输出到 target/tinyflow-0.0.1-SNAPSHOT.jar
-```
+### 后端
+- **框架**:Spring Boot 3.5.7
+- **数据库**:MySQL + JPA
+- **缓存**:Redis + Caffeine
+- **消息队列**:RabbitMQ
+- **监控**:Spring Boot Actuator
-### 验证安装
+### 前端
+- **框架**:Vue 3 + Vite
+- **样式**:Tailwind CSS
+- **图表**:ECharts
+- **国际化**:Vue I18n
-```bash
-# 1. 检查后端健康状态
-curl http://localhost:8080/actuator/health
-
-# 2. 生成短链接
-curl -X POST http://localhost:8080/api/shorten \
- -H "Content-Type: application/json" \
- -d '{"longUrl": "https://www.example.com"}'
+## 项目结构
-# 3. 访问短链接
-curl -I http://localhost:8080/abc123
+```
+src/
+├── main/java/com/layor/tinyflow/ # 后端源代码
+│ ├── controller/ # API控制器
+│ ├── service/ # 业务逻辑
+│ ├── repository/ # 数据访问
+│ └── entity/ # 数据实体
+web/
+├── src/ # 前端源代码
+│ ├── components/ # Vue组件
+│ ├── pages/ # 页面组件
+│ └── composables/ # 组合式函数
```
----
-
-## 配置说明
-
-- API 地址:
- - 开发环境默认通过 `vite.config.js` 代理 `/api` 到 `http://localhost:8080`
- - 在 `App.vue` 中有常量 `API_BASE = 'http://localhost:8080'`,用于构造短链展示地址(如二维码、复制用)。生产环境请按实际域名修改。
-
-- 跳转端点:
- - 前端所有短链均指向 `GET /api/redirect/{code}`,由后端返回 `302` 到原始长链,并进行点击日志记录(推荐)。
-
----
-
-## 开发与调试
-
-- 常用脚本:
- - `npm run dev`:启动前端开发服务器
- - `npm run build`:构建生产包
-
-- 页面导航:
- - 首页生成短链与历史记录管理
- - `Dashboard`:集中管理与操作列表
- - `Stats`:查看某短码的访问统计(接口完善中)
+## 部署
-- 调试提示:
- - 首页 favicon 加载可能出现跨站错误(如 `net::ERR_BLOCKED_BY_ORB`),不影响核心功能;
- - 若点击短链仍走旧端点,请强制刷新缓存(Ctrl+F5)或检查 `App.vue` 的 `buildShortUrl` 与 `redirectViaApi` 实现是否已更新。
+### 生产环境构建
----
+```bash
+# 构建后端
+mvn clean package -DskipTests
-## 🚢 部署到生产环境
+# 构建前端
+cd web
+npm install
+npm run build
+```
-### Nginx 配置示例
+### Nginx配置示例
```nginx
server {
listen 80;
server_name yourdomain.com;
- # 前端静态资源
location / {
root /var/www/tinyflow/web/dist;
try_files $uri $uri/ /index.html;
}
- # 后端 API 代理
location /api {
proxy_pass http://localhost:8080;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- }
-
- # 短链接跳转
- location ~ ^/[a-zA-Z0-9]{6}$ {
- proxy_pass http://localhost:8080;
- proxy_set_header Host $host;
}
}
```
-### 环境变量配置
+## API使用示例
+### 生成短链接
```bash
-# application-prod.yml
-spring:
- datasource:
- url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/tinyflow
- username: ${MYSQL_USER:root}
- password: ${MYSQL_PASSWORD:}
- data:
- redis:
- host: ${REDIS_HOST:localhost}
- port: ${REDIS_PORT:6379}
- password: ${REDIS_PASSWORD:}
- rabbitmq:
- host: ${RABBITMQ_HOST:localhost}
- port: ${RABBITMQ_PORT:5672}
- username: ${RABBITMQ_USER:guest}
- password: ${RABBITMQ_PASSWORD:guest}
+curl -X POST http://localhost:8080/api/shorten \
+ -H "Content-Type: application/json" \
+ -d '{"longUrl": "https://example.com"}'
```
-### Docker 部署
-
+### 访问短链接
```bash
-# 1. 构建镜像
-docker build -t tinyflow:latest .
-
-# 2. 运行容器
-docker run -d \
- -p 8080:8080 \
- -e MYSQL_HOST=mysql \
- -e REDIS_HOST=redis \
- -e RABBITMQ_HOST=rabbitmq \
- --name tinyflow \
- tinyflow:latest
-```
-
-### 监控与告警
-
-- **Grafana Dashboard**:导入 `web/infra/observability/dashboards/shortener-overview.json`
-- **Prometheus 配置**:`web/infra/observability/prometheus.yml`
-- **告警规则**:配置 QPS、错误率、延迟阈值告警
-
-
-
-
----
-
-## ⚡ 性能优化
-
-### 已实现的优化
-
-#### 1. 短码生成优化(号段模式 + 异步持久化)
-
-**问题 1**:数据库自增 ID 在高并发下成为瓶颈
-
-**方案 1**:号段模式预分配
-- 号段模式预分配 ID 段(每次 1000 个)到内存
-- 双 Buffer 机制(当前 Buffer 用至 50% 时异步加载下一段)
-- Hashids 算法 + Base58 编码生成 6 位短码
-
-**效果**:单机 TPS 从 200 提升至 10000+,数据库压力降低 95%
-
----
-
-**问题 2**:同步写入 MySQL 阻塞短链生成响应(10-50ms)
-
-**方案 2**:异步持久化架构
-- **L1**: 立即写入 Caffeine 本地缓存(< 0.1ms)
-- **L2**: 写入 Redis 分布式缓存(< 1ms)
-- **L3**: 更新布隆过滤器(防缓存穿透)
-- **L4**: 发送 RabbitMQ 消息,异步持久化到 MySQL
-- **降级**: MQ 不可用时自动降级为同步写入
-
-**效果**:响应时间从 10-50ms 降至 < 1ms,性能提升 **10-50 倍**
-
-**关键代码**:
-```java
-// ShortUrlService.saveShortUrlAsync()
-private void saveShortUrlAsync(ShortUrl shortUrl) {
- // 1. 立即写入 Caffeine(极速)
- localCache.put(shortCode, longUrl);
-
- // 2. 写入 Redis(多实例共享)
- redisTemplate.opsForValue().set("short_url:" + shortCode, longUrl);
-
- // 3. 布隆过滤器(防穿透)
- shortCodeBloomFilter.put(shortCode);
-
- // 4. 发送 MQ 异步持久化(或降级为同步)
- if (rabbitTemplate != null) {
- rabbitTemplate.convertAndSend(SHORTURL_EXCHANGE, "shorturl.persist", message);
- } else {
- shortUrlRepository.save(shortUrl); // 降级
- }
-}
-```
-
-**幂等性保证**:
-```java
-// ShortUrlPersistenceConsumer
-@RabbitListener(queues = "tinyflow.shorturl.queue")
-public void consumePersistenceMessage(ShortUrlMessage message) {
- // 幂等性检查
- if (shortUrlRepository.existsByShortCode(message.getShortCode())) {
- return; // 已存在,跳过
- }
- shortUrlRepository.save(shortUrl);
-}
+curl -I http://localhost:8080/abc123
```
-#### 2. 多级缓存优化
-
-**问题**:热点短链高并发访问导致数据库压力大
-
-**方案**:
-- L1 Caffeine 本地缓存(10 万容量 + 1h TTL)
-- L2 Redis 分布式缓存
-- 缓存预热:启动时加载 Top 1000 热点短链
-- 逐级回填策略
-
-**效果**:缓存命中率 85%+,数据库 IO 降低 70%
-
-#### 3. 分页性能优化
-
-**问题**:前端内存分页 + 后端 N+1 查询
-
-**方案**:
-- 改为服务端分页(PageRequest + 稳定排序)
-- 批量 IN 查询消除 N+1 问题
-- Map 合并统计数据到 DTO
-
-**效果**:接口响应从 800ms 降至 50ms(优化 94%)
-
-#### 4. 消息队列异步解耦(双队列架构)
-
-**问题 1**:点击统计同步写入阻塞跳转请求
-
-**方案 1**:点击统计异步化
-- 跳转请求发送 RabbitMQ 消息后立即返回
-- 消费者每 2 秒批量聚合刷库
-- 死信队列 + 手动 ACK + 指数退避重试
-
-**效果**:系统 TPS 提升 5 倍,消息丢失率 < 0.01%
-
----
-
-**问题 2**:短链生成时同步写 MySQL 导致响应慢
-
-**方案 2**:短链持久化异步化
-- 短链生成时先写缓存,发送 MQ 消息
-- 独立消费者(ShortUrlPersistenceConsumer)异步持久化
-- 幂等性检查避免重复插入
-- MQ 不可用时自动降级为同步模式
-
-**效果**:短链生成响应从 10-50ms 降至 < 1ms
-
-**队列架构**:
-- `tinyflow.shorturl.queue`:短链持久化队列
-- `tinyflow.click.queue`:点击统计队列
-- `tinyflow.click.dlq`:死信队列(处理失败消息)
-
-### 可继续优化的点
-
-- [x] ~~**布隆过滤器**:防止缓存穿透~~ ✅ 已实现
-- [x] ~~**异步持久化**:短链生成先写缓存,MQ 异步持久化~~ ✅ 已实现
-- [ ] **限流策略**:Guava RateLimiter / Redis 滑动窗口
-- [ ] **读写分离**:MySQL 主从复制 + 读写路由
-- [ ] **分库分表**:ShardingSphere 水平拆分
-- [ ] **CDN 加速**:静态资源 + 短链跳转页 CDN 缓存
-- [ ] **消息队列集群**:RabbitMQ 集群部署,提升可靠性
-
----
-
-## 🗂️ 后续拓展计划
-
-- 安全与稳定性
- - 访问速率限制(Rate Limit)、防刷与机器人识别
- - 短码唯一性保障与冲突检测优化(含自定义别名策略)
- - 审计日志与操作留痕(更新/删除)
-
-- 统计与分析
- - 细化点击日志结构(UA、IP、Referer、UTM、地理位置等)
- - 趋势图与来源分布(按天、周、月、时间段)
- - 导出报表 CSV/JSON、分享统计页的只读链接
+## 贡献
-- 管理能力
- - 批量导入/导出短链
- - 标签/分组管理与筛选
- - 权限模型与多租户(个人/团队/企业)
- - 管理后台(Admin)与审核流程
+欢迎提交 Issue 和 Pull Request!请遵循以下流程:
-- 使用体验
- - 自定义域名与多域支持(如 `s.yourbrand.com`)
- - 二维码定制(颜色、LOGO、容错率)
- - 失效策略与到期提醒(TTL、软删除恢复)
- - 跳转前提示页(如合规警示/安全验证)
+1. Fork 本仓库
+2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
+3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
+4. 推送到分支 (`git push origin feature/AmazingFeature`)
+5. 创建 Pull Request
-- 开发运维
- - 缓存与热点优化(本地缓存/Redis/LRU)
- - 观察性与告警(Metrics/Tracing/Logging)
- - Webhook/事件订阅(点击通知、到期通知)
- - 完整测试体系(单测/集成/端到端)与 CI/CD 流程
+## 许可证
-> 如需优先推进其中某一项,请在 Issue 中告知场景与需求,我会按优先级完善。
+本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
-## 常见问题
+## 联系我们
-- 前端短链地址与后端域名不一致?
- - 请在 `App.vue` 中调整 `API_BASE` 为线上域名,或在构建时注入环境变量并使用 `import.meta.env`。
-
-- 后端未提供纯文本接口 `/shorten`?
- - 前端会回退到 `/api/shorten`,无需修改。你也可以移除文本接口的调用逻辑。
-
----
-
-## 贡献指南
-
-欢迎提交 Issue 或 PR 来改进项目:
-
-1. Fork 仓库并创建特性分支
-2. 保持代码风格一致,避免无关修改
-3. 编写必要的说明与测试(如适用)
-4. 提交 PR 并描述改动动机与效果
-
----
-
-## 📜 开源协议
-
-本项目采用 [MIT License](./LICENSE)。
-
----
-
-## 📮 联系方式
-
-- **项目地址**:https://github.com/Layau-code/TinyFlow
-- **问题反馈**:https://github.com/Layau-code/TinyFlow/issues
-- **邮箱**:18970931397@163.com
-
----
-
-## 🙏 致谢
-
-感谢以下开源项目:
-
-- [Spring Boot](https://spring.io/projects/spring-boot)
-- [Vue.js](https://vuejs.org/)
-- [Caffeine](https://github.com/ben-manes/caffeine)
-- [Hashids](https://hashids.org/)
-- [RabbitMQ](https://www.rabbitmq.com/)
-- [Resilience4j](https://resilience4j.readme.io/)
+- 项目地址:https://github.com/Layau-code/TinyFlow
+- 问题反馈:https://github.com/Layau-code/TinyFlow/issues
---
-
-**⭐ 如果这个项目对你有帮助,欢迎 Star 支持!**
-
-**💡 有任何建议或问题,欢迎提交 Issue 或 PR!**
-
-**🎓 适合作为学习高并发系统设计的实战项目!**
-
-Made with ❤️ by [Layau](https://github.com/Layau-code)
-
-
+如果这个项目对您有帮助,请给个 ⭐️ 支持一下!
+
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index 2dba29b..f789711 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -20,13 +20,13 @@
"vue-router": "^4.3.0"
},
"devDependencies": {
- "@vitejs/plugin-vue": "^6.0.3",
+ "@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.23",
"http-server": "^14.1.1",
"postcss": "^8.5.6",
"sass-embedded": "^1.97.1",
"tailwindcss": "^3.4.19",
- "vite": "^7.3.0"
+ "vite": "^5.3.1"
}
},
"node_modules/@alloc/quick-lru": {
@@ -382,9 +382,9 @@
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
- "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
@@ -395,13 +395,13 @@
"aix"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
- "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
@@ -412,13 +412,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
- "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
@@ -429,13 +429,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
- "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
@@ -446,13 +446,13 @@
"android"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
- "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
@@ -463,13 +463,13 @@
"darwin"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
- "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
@@ -480,13 +480,13 @@
"darwin"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
- "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
@@ -497,13 +497,13 @@
"freebsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
- "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
@@ -514,13 +514,13 @@
"freebsd"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
- "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
@@ -531,13 +531,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
- "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
@@ -548,13 +548,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
- "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
@@ -565,13 +565,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
- "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
@@ -582,13 +582,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
- "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
@@ -599,13 +599,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
- "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
@@ -616,13 +616,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
- "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
@@ -633,13 +633,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
- "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
@@ -650,13 +650,13 @@
"linux"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
- "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
@@ -667,30 +667,13 @@
"linux"
],
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
- "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
@@ -701,30 +684,13 @@
"netbsd"
],
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
- "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
@@ -735,30 +701,13 @@
"openbsd"
],
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
- "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
- "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
@@ -769,13 +718,13 @@
"sunos"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
- "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
@@ -786,13 +735,13 @@
"win32"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
- "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
@@ -803,13 +752,13 @@
"win32"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
- "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
@@ -820,7 +769,7 @@
"win32"
],
"engines": {
- "node": ">=18"
+ "node": ">=12"
}
},
"node_modules/@intlify/core-base": {
@@ -1253,13 +1202,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.53",
- "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
- "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.54.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
@@ -2318,19 +2260,16 @@
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
- "version": "6.0.3",
- "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz",
- "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@rolldown/pluginutils": "1.0.0-beta.53"
- },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0",
+ "vite": "^5.0.0 || ^6.0.0",
"vue": "^3.2.25"
}
},
@@ -3286,9 +3225,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.2",
- "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz",
- "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -3296,35 +3235,32 @@
"esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=18"
+ "node": ">=12"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.2",
- "@esbuild/android-arm": "0.27.2",
- "@esbuild/android-arm64": "0.27.2",
- "@esbuild/android-x64": "0.27.2",
- "@esbuild/darwin-arm64": "0.27.2",
- "@esbuild/darwin-x64": "0.27.2",
- "@esbuild/freebsd-arm64": "0.27.2",
- "@esbuild/freebsd-x64": "0.27.2",
- "@esbuild/linux-arm": "0.27.2",
- "@esbuild/linux-arm64": "0.27.2",
- "@esbuild/linux-ia32": "0.27.2",
- "@esbuild/linux-loong64": "0.27.2",
- "@esbuild/linux-mips64el": "0.27.2",
- "@esbuild/linux-ppc64": "0.27.2",
- "@esbuild/linux-riscv64": "0.27.2",
- "@esbuild/linux-s390x": "0.27.2",
- "@esbuild/linux-x64": "0.27.2",
- "@esbuild/netbsd-arm64": "0.27.2",
- "@esbuild/netbsd-x64": "0.27.2",
- "@esbuild/openbsd-arm64": "0.27.2",
- "@esbuild/openbsd-x64": "0.27.2",
- "@esbuild/openharmony-arm64": "0.27.2",
- "@esbuild/sunos-x64": "0.27.2",
- "@esbuild/win32-arm64": "0.27.2",
- "@esbuild/win32-ia32": "0.27.2",
- "@esbuild/win32-x64": "0.27.2"
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/escalade": {
@@ -5323,25 +5259,22 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "7.3.0",
- "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.0.tgz",
- "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
+ "version": "5.4.21",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -5350,25 +5283,19 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
"lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
- "jiti": {
- "optional": true
- },
"less": {
"optional": true
},
@@ -5389,47 +5316,9 @@
},
"terser": {
"optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vite/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
}
}
},
- "node_modules/vite/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/vue": {
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.22.tgz",
diff --git a/web/package.json b/web/package.json
index 4934a85..c3d473c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -18,13 +18,13 @@
"author": "TinyFlow Team",
"license": "MIT",
"devDependencies": {
- "@vitejs/plugin-vue": "^6.0.3",
+ "@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.23",
"http-server": "^14.1.1",
"postcss": "^8.5.6",
"sass-embedded": "^1.97.1",
"tailwindcss": "^3.4.19",
- "vite": "^7.3.0"
+ "vite": "^5.3.1"
},
"dependencies": {
"@antv/g2": "^5.4.7",