22 KiB
22 KiB
好的!我理解了,你需要:
- 前端:现代化框架(React/Vue)
- 后端:独立的轻量后端(不用 Next.js API Routes)
- 部署:都要简单方便
让我重新设计一个最佳方案!🎯 最终推荐架构
技术选型
前端 (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 全栈,类型安全
需要我提供:
- ✅ 完整的项目初始化脚本?
- ✅ Prisma Schema 数据库设计?
- ✅ 前端 Dashboard 组件代码?
- ✅ 详细的部署文档?
选择任意方向深入!🚀