lijiayang050403 57f964744f feat: up
2025-10-24 04:55:08 +00:00

553 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

好的!我理解了,你需要:
1. **前端**现代化框架React/Vue
2. **后端**:独立的轻量后端(不用 Next.js API Routes
3. **部署**:都要简单方便
让我重新设计一个最佳方案!🎯 **最终推荐架构**
* * *
### **技术选型**
前端 (SPA):
- Framework: Vite + React 18 + TypeScript
- UI: Tailwind CSS + shadcn/ui
- State: Zustand + TanStack Query
- Charts: Recharts + D3.js
- 部署: Cloudflare Pages / Vercel (静态托管)
后端 (API):
- Framework: Fastify + TypeScript (比Express快2倍)
- GitHub SDK: Octokit
- Database: PostgreSQL (Supabase免费托管)
- Cache: Upstash Redis (Serverless)
- 部署: Railway.app ⭐ (一键部署,免费额度够用)
认证:
- GitHub OAuth 2.0
- JWT Token
实时通信:
- Server-Sent Events (SSE) - 比WebSocket简单
- 或 Pusher (免费版)
* * *
🏗️ **系统架构图**
┌─────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ React SPA (Vite) │ │
│ │ │ │
│ │ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │
│ │ │Dashboard │ │Repository │ │Analytics │ │ │
│ │ │ Page │ │Detail Page│ │ Page │ │ │
│ │ └────┬─────┘ └─────┬─────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ └──────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────▼──────────┐ │ │
│ │ │ TanStack Query │ (数据获取层) │ │
│ │ │ + Axios/Fetch │ │ │
│ │ └─────────┬──────────┘ │ │
│ └──────────────────────┼───────────────────────────────┘ │
└─────────────────────────┼──────────────────────────────────┘
HTTPS (REST API)
┌─────────────────────────▼──────────────────────────────────┐
│ 后端服务器 (Railway) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Fastify API Server (Node.js) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Auth │ │ Repos │ │ Webhooks │ │ │
│ │ │ /login │ │ /repos/* │ │ /webhooks/* │ │ │
│ │ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────▼──────────┐ │ │
│ │ │ Service Layer │ │ │
│ │ │ • GitHubService │ │ │
│ │ │ • CacheService │ │ │
│ │ │ • WebhookService │ │ │
│ │ └──────────┬──────────┘ │ │
│ └─────────────────────┼────────────────────────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ ┌────▼─────┐ ┌────▼────┐ ┌──────▼──────┐ │
│ │ Octokit │ │ Prisma │ │ Redis Cache │ │
│ │ GitHub │ │ ORM │ │ (Upstash) │ │
│ │ API │ └────┬────┘ └─────────────┘ │
│ └──────────┘ │ │
└───────────────────────┼─────────────────────────────────────┘
┌──────▼──────┐
│ PostgreSQL │
│ (Supabase) │
└─────────────┘
┌────────────────────────────────────────────┐
│ 外部服务 │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ GitHub API │ │ GitHub Webhooks │ │
│ │ (公开接口) │ │ (实时事件推送) │ │
│ └──────────────┘ └──────────────────┘ │
└────────────────────────────────────────────┘
* * *
📂 **项目结构**
github-dashboard/
├── frontend/ # 前端项目 (Vite)
│ ├── src/
│ │ ├── components/
│ │ │ ├── dashboard/
│ │ │ │ ├── StatsCard.tsx
│ │ │ │ ├── ActivityChart.tsx
│ │ │ │ └── RepoList.tsx
│ │ │ ├── charts/
│ │ │ └── ui/ # shadcn/ui 组件
│ │ ├── pages/
│ │ │ ├── Dashboard.tsx
│ │ │ ├── RepoDetail.tsx
│ │ │ └── Analytics.tsx
│ │ ├── hooks/
│ │ │ ├── useRepos.ts
│ │ │ └── useRepoStats.ts
│ │ ├── lib/
│ │ │ ├── api.ts # API 请求封装
│ │ │ └── auth.ts # 认证逻辑
│ │ ├── stores/
│ │ │ └── authStore.ts # Zustand store
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── package.json
│ ├── vite.config.ts
│ └── tailwind.config.js
├── backend/ # 后端项目 (Fastify)
│ ├── src/
│ │ ├── routes/
│ │ │ ├── auth.ts # POST /auth/login
│ │ │ ├── repos.ts # GET /repos, /repos/:id
│ │ │ ├── webhooks.ts # POST /webhooks/github
│ │ │ └── stats.ts # GET /stats/*
│ │ ├── services/
│ │ │ ├── github.service.ts
│ │ │ ├── cache.service.ts
│ │ │ └── webhook.service.ts
│ │ ├── middlewares/
│ │ │ ├── auth.middleware.ts
│ │ │ └── error.middleware.ts
│ │ ├── prisma/
│ │ │ └── schema.prisma
│ │ ├── types/
│ │ │ └── index.ts
│ │ ├── utils/
│ │ │ └── logger.ts
│ │ ├── app.ts # Fastify 实例
│ │ └── server.ts # 启动文件
│ ├── package.json
│ ├── tsconfig.json
│ └── .env.example
└── README.md
* * *
🚀 **部署方案**
### **前端部署Cloudflare Pages** ⭐
# 1. 构建
cd frontend
npm run build
# 2. Cloudflare Pages 自动部署
# - 连接 GitHub 仓库
# - 构建命令: npm run build
# - 输出目录: dist
# - 自动部署: 推送到 main 分支即触发
**优势:**
* ✅ 完全免费
* ✅ 全球 CDN超快
* ✅ 自动 HTTPS
* ✅ 无限带宽
* ✅ 支持自定义域名
* * *
### **后端部署Railway.app** ⭐⭐⭐
# 1. 安装 Railway CLI
npm install -g @railway/cli
# 2. 登录
railway login
# 3. 初始化项目
cd backend
railway init
# 4. 部署
railway up
# 5. 添加数据库
railway add postgresql
# 6. 设置环境变量
railway variables set GITHUB_CLIENT_ID=xxx
railway variables set GITHUB_CLIENT_SECRET=xxx
**Railway 配置文件 (`railway.json`)**: { "$schema": "https://railway.app/railway.schema.json", "build": { "builder": "NIXPACKS" }, "deploy": { "startCommand": "npm run start", "restartPolicyType": "ON_FAILURE", "restartPolicyMaxRetries": 10 } }
**优势:**
* ✅ 免费额度:$5/月(足够小项目)
* ✅ 一键部署,零配置
* ✅ 自动 HTTPS
* ✅ 内置 PostgreSQL
* ✅ 环境变量管理
* ✅ 自动 Git 同步部署
* * *
### **替代方案对比**
| 平台 | 免费额度 | 部署难度 | 启动速度 | 推荐度 |
| --- | --- | --- | --- | --- |
| **Railway** | $5/月 | ⭐⭐⭐⭐⭐ | 快 | ⭐⭐⭐⭐⭐ |
| Render | 750h/月 | ⭐⭐⭐⭐ | 慢(冷启动) | ⭐⭐⭐⭐ |
| Fly.io | 3个免费实例 | ⭐⭐⭐ | 快 | ⭐⭐⭐⭐ |
| Heroku | 无免费版 | ⭐⭐⭐⭐ | 中等 | ⭐⭐ |
* * *
💻 **核心代码示例**
### **后端 - Fastify 入口 (`backend/src/app.ts`)**
import Fastify from 'fastify';
import cors from '@fastify/cors';
import jwt from '@fastify/jwt';
import { authRoutes } from './routes/auth';
import { repoRoutes } from './routes/repos';
import { webhookRoutes } from './routes/webhooks';
export function createApp() {
const app = Fastify({
logger: true,
});
// 插件
app.register(cors, {
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true,
});
app.register(jwt, {
secret: process.env.JWT_SECRET!,
});
// 路由
app.register(authRoutes, { prefix: '/api/auth' });
app.register(repoRoutes, { prefix: '/api/repos' });
app.register(webhookRoutes, { prefix: '/api/webhooks' });
// 健康检查
app.get('/health', async () => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
return app;
}
### **后端 - GitHub Service (`backend/src/services/github.service.ts`)**
import { Octokit } from '@octokit/rest';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});
export class GitHubService {
private octokit: Octokit;
constructor(accessToken: string) {
this.octokit = new Octokit({ auth: accessToken });
}
async getRepositories() {
const cacheKey = 'repos:list';
// 尝试从缓存读取
const cached = await redis.get(cacheKey);
if (cached) return cached;
// 从 GitHub API 获取
const { data } = await this.octokit.repos.listForAuthenticatedUser({
sort: 'updated',
per_page: 100,
});
// 缓存5分钟
await redis.set(cacheKey, data, { ex: 300 });
return data;
}
async getRepositoryStats(owner: string, repo: string) {
const cacheKey = `repo:${owner}/${repo}:stats`;
const cached = await redis.get(cacheKey);
if (cached) return cached;
const [repoData, issues, pulls, commits] = await Promise.all([
this.octokit.repos.get({ owner, repo }),
this.octokit.issues.listForRepo({ owner, repo, state: 'open' }),
this.octokit.pulls.list({ owner, repo, state: 'open' }),
this.octokit.repos.listCommits({
owner,
repo,
per_page: 100,
since: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
}),
]);
const stats = {
name: repoData.data.name,
stars: repoData.data.stargazers_count,
forks: repoData.data.forks_count,
openIssues: issues.data.length,
openPRs: pulls.data.length,
commitsLastMonth: commits.data.length,
language: repoData.data.language,
updatedAt: repoData.data.updated_at,
};
await redis.set(cacheKey, stats, { ex: 300 });
return stats;
}
}
### **前端 - API 客户端 (`frontend/src/lib/api.ts`)**
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
timeout: 10000,
});
// 请求拦截器:添加 token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器:处理错误
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default api;
### **前端 - 数据获取 Hook (`frontend/src/hooks/useRepos.ts`)**
import { useQuery } from '@tanstack/react-query';
import api from '@/lib/api';
interface Repository {
id: number;
name: string;
full_name: string;
description: string;
stargazers_count: number;
open_issues_count: number;
language: string;
}
export function useRepositories() {
return useQuery({
queryKey: ['repositories'],
queryFn: async () => {
const { data } = await api.get<Repository[]>('/repos');
return data;
},
staleTime: 5 * 60 * 1000, // 5分钟
refetchOnWindowFocus: false,
});
}
export function useRepositoryStats(owner: string, repo: string) {
return useQuery({
queryKey: ['repo-stats', owner, repo],
queryFn: async () => {
const { data } = await api.get(`/repos/${owner}/${repo}/stats`);
return data;
},
enabled: !!owner && !!repo,
staleTime: 5 * 60 * 1000,
});
}
* * *
🔐 **认证流程**
┌────────┐ ┌──────────┐ ┌─────────┐
│ 前端 │ │ 后端 │ │ GitHub │
└───┬────┘ └────┬─────┘ └────┬────┘
│ │ │
│ 1. 点击 "Login with GitHub" │ │
├──────────────────────────>│ │
│ │ │
│ 2. 重定向到 GitHub OAuth │ │
├───────────────────────────┼─────────────────────────>│
│ │ │
│ 3. 用户授权 │ │
│ │<──────────────────────────┤
│ │ │
│ 4. GitHub 回调 + code │ │
<───────────────────────────┤ │
│ │ │
│ 5. 发送 code 到后端 │ │
├──────────────────────────>│ │
│ │ │
│ │ 6. 用 code 换 access_token│
│ ├─────────────────────────>│
│ │ │
│ │<──────────────────────────┤
│ │ │
│ 7. 返回 JWT token │ │
<───────────────────────────┤ │
│ │ │
│ 8. 保存 token 到 localStorage │
│ │ │
* * *
📊 **数据流**
前端 Dashboard 页面加载:
1. 检查 localStorage 是否有 token
├─ 无 → 跳转登录页
└─ 有 → 继续
2. 发起并行请求TanStack Query:
├─ GET /api/repos (获取仓库列表)
├─ GET /api/stats/overview (总体统计)
└─ GET /api/stats/activity (活动数据)
3. 后端处理:
├─ 验证 JWT token
├─ 检查 Redis 缓存
│ ├─ 命中 → 直接返回
│ └─ 未命中 → 调用 GitHub API + 写入缓存
└─ 返回数据
4. 前端渲染:
├─ TanStack Query 自动管理加载/错误状态
├─ 渲染图表Recharts
└─ 显示数据卡片
* * *
🎯 **开发步骤**
### **1. 初始化项目**
# 创建项目目录
mkdir github-dashboard
cd github-dashboard
# 初始化前端
npm create vite@latest frontend -- --template react-ts
cd frontend
npm install
npm install @tanstack/react-query axios zustand
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# 初始化后端
cd ..
mkdir backend && cd backend
npm init -y
npm install fastify @fastify/cors @fastify/jwt
npm install @octokit/rest @upstash/redis @prisma/client
npm install -D typescript @types/node tsx prisma
npx prisma init
### **2. 配置 Railway 部署**
cd backend
# 创建 railway.json
cat > railway.json << EOF
{
"build": {
"builder": "NIXPACKS"
},
"deploy": {
"startCommand": "npm run start"
}
}
EOF
# 部署
railway login
railway init
railway up
### **3. 配置 Cloudflare Pages**
cd frontend
# 构建测试
npm run build
# Cloudflare Pages Dashboard:
# 1. 连接 GitHub 仓库
# 2. 设置构建命令: npm run build
# 3. 设置输出目录: dist
# 4. 添加环境变量: VITE_API_URL=你的Railway后端URL
* * *
🎉 **总结**
这个架构的优势
**前后端分离** - 各自独立部署和扩展 **轻量高效** - Fastify 性能优秀Vite 开发体验好 **部署简单** - Railway + Cloudflare Pages 都是一键部署 **成本低廉** - 免费额度足够个人/小团队使用 **可扩展性** - 需要时可轻松升级到付费版 **现代化** - TypeScript 全栈类型安全
需要我提供
1. 完整的项目初始化脚本
2. Prisma Schema 数据库设计
3. 前端 Dashboard 组件代码
4. 详细的部署文档
选择任意方向深入!🚀