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 - 高性能短链接系统 -
-![TinyFlow Logo](./docs/images/logo.png) - - -**世界上没有两个相同的雨滴,就像每一个短链接都独一无二** - -**雨滴汇聚成河流 (Flow),链接汇聚成数据洪流** - -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.7-brightgreen.svg)](https://spring.io/projects/spring-boot) -[![Vue 3](https://img.shields.io/badge/Vue.js-3.x-4FC08D.svg)](https://vuejs.org/) -[![Java](https://img.shields.io/badge/Java-17-orange.svg)](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 倍** | - - - - ---- +
+

- 高性能短链接服务 -

+
-## 项目结构 +
+ + + License: MIT + + + + + Java 17 + + + + + Vue 3 + + + + + Spring Boot 3.5.7 + + +
-``` -前端/ -├── 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",