feat(deployment): Add Docker support and deployment configuration

- Add Dockerfile for multi-stage build with Node.js and Nginx
- Create docker-compose.yml for simplified container deployment
- Add .dockerignore to optimize Docker build context
- Configure nginx.conf with performance and security optimizations
- Update README.md and README_EN.md with Docker deployment instructions
- Enhance database configuration to support optional Supabase integration
- Improve environment configuration handling for local and containerized environments
Enables easier deployment and provides a standardized container-based setup for the application.
This commit is contained in:
lintsinghua 2025-10-23 00:13:48 +08:00
parent 9b11e47b36
commit 93bac360f1
7 changed files with 322 additions and 9 deletions

46
.dockerignore Normal file
View File

@ -0,0 +1,46 @@
# 依赖
node_modules
# 构建产物
dist
.vite
# 环境变量(保留 .env 用于构建)
.env.local
.env.*.local
# 日志
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
# 编辑器
.vscode
.idea
*.swp
*.swo
*~
# 操作系统
.DS_Store
Thumbs.db
# Git
.git
.gitignore
.gitattributes
# 测试
coverage
.nyc_output
# 文档
*.md
docs
# 其他
.history
.cache

46
Dockerfile Normal file
View File

@ -0,0 +1,46 @@
# 多阶段构建 - 构建阶段
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 禁用代理并安装 pnpm
ENV HTTP_PROXY=""
ENV HTTPS_PROXY=""
ENV http_proxy=""
ENV https_proxy=""
ENV NO_PROXY="*"
ENV no_proxy="*"
RUN npm config set registry https://registry.npmjs.org/ && \
npm config delete proxy 2>/dev/null || true && \
npm config delete https-proxy 2>/dev/null || true && \
npm config delete http-proxy 2>/dev/null || true && \
npm install -g pnpm
# 复制依赖文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install --no-frozen-lockfile
# 复制项目文件(包括 .env
COPY . .
# 构建应用(环境变量会在构建时被读取)
RUN pnpm build
# 生产阶段 - 使用 nginx 提供静态文件服务
FROM nginx:alpine
# 复制自定义 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 暴露端口
EXPOSE 80
# 启动 nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@ -49,14 +49,43 @@
## 🚀 快速开始
### 环境要求
### 🐳 Docker 部署(推荐)
使用 Docker 可以快速部署应用,无需配置 Node.js 环境。
1. **克隆项目**
```bash
git clone https://github.com/lintsinghua/XCodeReviewer.git
cd XCodeReviewer
```
2. **配置环境变量**
```bash
cp .env.example .env
# 编辑 .env 文件,至少需要配置 VITE_GEMINI_API_KEY
```
3. **构建并启动**
```bash
docker-compose build
docker-compose up -d
```
4. **访问应用**
在浏览器中打开 `http://localhost:5174`
### 💻 本地开发部署
如果需要进行开发或自定义修改,可以使用本地部署方式。
#### 环境要求
- **Node.js**: `18+`
- **pnpm**: `8+` (推荐) 或 `npm` / `yarn`
- **Google Gemini API Key**: 用于 AI 代码分析
- **Supabase 项目**: 用于数据存储(可选,支持离线模式)
### 安装与启动
#### 安装与启动
1. **克隆项目**
```bash
@ -256,6 +285,7 @@ XCodeReviewer/
5. 查看详细的问题报告
### 构建和部署
```bash
# 开发模式
pnpm dev

View File

@ -49,14 +49,50 @@ In the fast-paced world of software development, ensuring code quality is crucia
## 🚀 Quick Start
### Requirements
### 🐳 Docker Deployment (Recommended)
Deploy quickly using Docker without Node.js environment setup.
1. **Clone the project**
```bash
git clone https://github.com/lintsinghua/XCodeReviewer.git
cd XCodeReviewer
```
2. **Configure environment variables**
```bash
cp .env.example .env
# Edit .env file and set at least VITE_GEMINI_API_KEY
```
3. **Build and start**
```bash
docker-compose build
docker-compose up -d
```
4. **Access the application**
Open `http://localhost:5174` in your browser
**Common commands:**
```bash
docker-compose logs -f # View logs
docker-compose restart # Restart service
docker-compose down # Stop service
```
### 💻 Local Development Deployment
For development or custom modifications, use local deployment.
#### Requirements
- **Node.js**: `18+`
- **pnpm**: `8+` (recommended) or `npm` / `yarn`
- **Google Gemini API Key**: For AI code analysis
- **Supabase Project**: For data storage (optional, supports offline mode)
### Installation & Setup
#### Installation & Setup
1. **Clone the project**
```bash
@ -111,7 +147,7 @@ In the fast-paced world of software development, ensuring code quality is crucia
```
5. **Access the application**
Open `http://localhost:5173` in your browser
Open `http://localhost:5174` in your browser
### 🔑 Getting API Keys

40
docker-compose.yml Normal file
View File

@ -0,0 +1,40 @@
services:
# XCodeReviewer 前端应用
xcodereviewer:
build:
context: .
dockerfile: Dockerfile
container_name: xcodereviewer-app
ports:
- "5174:80"
environment:
# Google Gemini AI 配置 (必需)
- VITE_GEMINI_API_KEY=${VITE_GEMINI_API_KEY}
- VITE_GEMINI_MODEL=${VITE_GEMINI_MODEL:-gemini-2.5-flash}
- VITE_GEMINI_TIMEOUT_MS=${VITE_GEMINI_TIMEOUT_MS:-25000}
# Supabase 配置 (可选)
- VITE_SUPABASE_URL=${VITE_SUPABASE_URL}
- VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
# GitHub 集成 (可选)
- VITE_GITHUB_TOKEN=${VITE_GITHUB_TOKEN}
# 应用配置
- VITE_APP_ID=${VITE_APP_ID:-xcodereviewer}
- VITE_MAX_ANALYZE_FILES=${VITE_MAX_ANALYZE_FILES:-40}
- VITE_LLM_CONCURRENCY=${VITE_LLM_CONCURRENCY:-2}
- VITE_LLM_GAP_MS=${VITE_LLM_GAP_MS:-500}
restart: unless-stopped
networks:
- xcodereviewer-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
xcodereviewer-network:
driver: bridge

38
nginx.conf Normal file
View File

@ -0,0 +1,38 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip 压缩配置
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# 安全头部
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# 处理 React Router 的客户端路由
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 代理(如果需要)
# location /api {
# proxy_pass http://backend:8000;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# }
}

View File

@ -19,19 +19,49 @@ const isValidUuid = (value?: string): boolean => {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
};
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
// 检查是否配置了 Supabase
const hasSupabaseConfig = supabaseUrl && supabaseAnonKey;
// 如果没有配置 Supabase使用虚拟配置避免错误
const finalSupabaseUrl = hasSupabaseConfig ? supabaseUrl : 'https://demo.supabase.co';
const finalSupabaseKey = hasSupabaseConfig ? supabaseAnonKey : 'demo-key';
export const supabase = hasSupabaseConfig ? createClient(finalSupabaseUrl, finalSupabaseKey, {
global: {
fetch: undefined
},
auth: {
storageKey: (import.meta.env.VITE_APP_ID || "sb") + "-auth-token"
}
});
}) : null;
// 演示模式标识
export const isDemoMode = !hasSupabaseConfig;
// 演示数据
const demoProfile: Profile = {
id: 'demo-user',
phone: null,
email: 'demo@xcodereviewer.com',
full_name: 'Demo User',
avatar_url: null,
role: 'admin',
github_username: 'demo-user',
gitlab_username: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
// 用户相关API
export const api = {
// Profile相关
async getProfilesById(id: string): Promise<Profile | null> {
if (isDemoMode) {
return demoProfile;
}
if (!supabase) return null;
const { data, error } = await supabase
.from('profiles')
.select('*')
@ -43,6 +73,12 @@ export const api = {
},
async getProfilesCount(): Promise<number> {
if (isDemoMode) {
return 1;
}
if (!supabase) return 0;
const { count, error } = await supabase
.from('profiles')
.select('*', { count: 'exact', head: true });
@ -95,6 +131,25 @@ export const api = {
// Project相关
async getProjects(): Promise<Project[]> {
if (isDemoMode) {
return [{
id: 'demo-project-1',
name: 'Demo Project',
description: '这是一个演示项目,展示 XCodeReviewer 的功能',
repository_url: 'https://github.com/demo/project',
repository_type: 'github',
default_branch: 'main',
programming_languages: JSON.stringify(['TypeScript', 'JavaScript', 'React']),
owner_id: 'demo-user',
owner: demoProfile,
is_active: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}];
}
if (!supabase) return [];
const { data, error } = await supabase
.from('projects')
.select(`
@ -382,6 +437,28 @@ export const api = {
// 统计相关
async getProjectStats(): Promise<any> {
if (isDemoMode) {
return {
total_projects: 1,
active_projects: 1,
total_tasks: 3,
completed_tasks: 2,
total_issues: 15,
resolved_issues: 12
};
}
if (!supabase) {
return {
total_projects: 0,
active_projects: 0,
total_tasks: 0,
completed_tasks: 0,
total_issues: 0,
resolved_issues: 0
};
}
const [projectsResult, tasksResult, issuesResult] = await Promise.all([
supabase.from('projects').select('id, is_active', { count: 'exact' }),
supabase.from('audit_tasks').select('id, status', { count: 'exact' }),