diff --git a/README.md b/README.md index d9bb880..5b57d71 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,15 @@ docker-compose up -d 🎉 **搞定!** 打开 http://localhost:5173 开始体验吧! +### 演示账户 + +系统内置演示账户,包含示例项目和审计数据,可直接体验完整功能: + +- 📧 邮箱:`demo@example.com` +- 🔑 密码:`demo123` + +> ⚠️ **生产环境请删除演示账户或修改密码!** + > 📖 更多部署方式请查看 [部署指南](docs/DEPLOYMENT.md) ## ✨ 核心能力 diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py new file mode 100644 index 0000000..aa26f87 --- /dev/null +++ b/backend/app/db/init_db.py @@ -0,0 +1,276 @@ +""" +数据库初始化模块 +在应用启动时创建默认演示账户和演示数据 +""" +import json +import logging +from datetime import datetime, timedelta +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select + +from app.core.security import get_password_hash +from app.models.user import User +from app.models.project import Project, ProjectMember +from app.models.audit import AuditTask, AuditIssue +from app.models.analysis import InstantAnalysis + +logger = logging.getLogger(__name__) + +# 默认演示账户配置 +DEFAULT_DEMO_EMAIL = "demo@example.com" +DEFAULT_DEMO_PASSWORD = "demo123" +DEFAULT_DEMO_NAME = "演示用户" + + +async def create_demo_user(db: AsyncSession) -> User | None: + """ + 创建演示用户账户 + - demo@example.com / demo123 + """ + result = await db.execute(select(User).where(User.email == DEFAULT_DEMO_EMAIL)) + demo_user = result.scalars().first() + + if not demo_user: + demo_user = User( + email=DEFAULT_DEMO_EMAIL, + hashed_password=get_password_hash(DEFAULT_DEMO_PASSWORD), + full_name=DEFAULT_DEMO_NAME, + is_active=True, + is_superuser=True, # 演示用户拥有管理员权限以便体验所有功能 + role="admin", + ) + db.add(demo_user) + await db.flush() + logger.info(f"✓ 创建演示账户: {DEFAULT_DEMO_EMAIL}") + return demo_user + else: + logger.info(f"演示账户已存在: {DEFAULT_DEMO_EMAIL}") + return demo_user + + +async def create_demo_data(db: AsyncSession, user: User) -> None: + """ + 为演示用户创建演示数据,用于仪表盘展示 + """ + # 检查是否已有演示数据 + result = await db.execute(select(Project).where(Project.owner_id == user.id)) + existing_projects = result.scalars().all() + if existing_projects: + logger.info("演示数据已存在,跳过创建") + return + + logger.info("开始创建演示数据...") + now = datetime.utcnow() + + # ==================== 创建演示项目 ==================== + projects_data = [ + { + "name": "电商平台后端", + "description": "基于 Spring Boot 的电商平台后端服务,包含用户管理、商品管理、订单处理等模块", + "source_type": "repository", + "repository_url": "https://github.com/example/ecommerce-backend", + "repository_type": "github", + "default_branch": "main", + "programming_languages": json.dumps(["Java", "SQL"]), + }, + { + "name": "移动端 App", + "description": "React Native 跨平台移动应用,支持 iOS 和 Android", + "source_type": "repository", + "repository_url": "https://github.com/example/mobile-app", + "repository_type": "github", + "default_branch": "develop", + "programming_languages": json.dumps(["TypeScript", "JavaScript"]), + }, + { + "name": "数据分析平台", + "description": "Python 数据分析和可视化平台,集成机器学习模型", + "source_type": "zip", + "repository_url": None, + "repository_type": "other", + "default_branch": "main", + "programming_languages": json.dumps(["Python"]), + }, + { + "name": "微服务网关", + "description": "基于 Go 的高性能 API 网关,支持限流、熔断、负载均衡", + "source_type": "repository", + "repository_url": "https://gitlab.com/example/api-gateway", + "repository_type": "gitlab", + "default_branch": "master", + "programming_languages": json.dumps(["Go"]), + }, + { + "name": "智能客服系统", + "description": "基于 NLP 的智能客服系统,支持多轮对话、意图识别和知识库问答", + "source_type": "repository", + "repository_url": "https://github.com/example/smart-customer-service", + "repository_type": "github", + "default_branch": "main", + "programming_languages": json.dumps(["Python", "JavaScript"]), + }, + { + "name": "区块链钱包", + "description": "多链加密货币钱包,支持 ETH、BTC 等主流币种的存储和转账", + "source_type": "zip", + "repository_url": None, + "repository_type": "other", + "default_branch": "main", + "programming_languages": json.dumps(["Rust", "TypeScript"]), + }, + ] + + projects = [] + for i, pdata in enumerate(projects_data): + project = Project( + owner_id=user.id, + is_active=True, + created_at=now - timedelta(days=30 - i * 5), + **pdata + ) + db.add(project) + projects.append(project) + + await db.flush() + logger.info(f"✓ 创建了 {len(projects)} 个演示项目") + + # ==================== 创建审计任务和问题 ==================== + tasks_data = [ + # 项目1: 电商平台后端 + {"project_idx": 0, "status": "completed", "days_ago": 25, "files": 156, "lines": 12500, "issues": 23, "score": 72.5}, + {"project_idx": 0, "status": "completed", "days_ago": 15, "files": 162, "lines": 13200, "issues": 18, "score": 78.3}, + {"project_idx": 0, "status": "completed", "days_ago": 5, "files": 168, "lines": 14100, "issues": 12, "score": 85.2}, + # 项目2: 移动端 App + {"project_idx": 1, "status": "completed", "days_ago": 20, "files": 89, "lines": 8900, "issues": 15, "score": 68.7}, + {"project_idx": 1, "status": "completed", "days_ago": 8, "files": 95, "lines": 9500, "issues": 8, "score": 82.1}, + {"project_idx": 1, "status": "running", "days_ago": 0, "files": 98, "lines": 9800, "issues": 0, "score": 0}, + # 项目3: 数据分析平台 + {"project_idx": 2, "status": "completed", "days_ago": 12, "files": 45, "lines": 5600, "issues": 9, "score": 76.4}, + {"project_idx": 2, "status": "completed", "days_ago": 2, "files": 52, "lines": 6200, "issues": 5, "score": 88.9}, + # 项目4: 微服务网关 + {"project_idx": 3, "status": "completed", "days_ago": 18, "files": 78, "lines": 9200, "issues": 11, "score": 74.8}, + {"project_idx": 3, "status": "failed", "days_ago": 3, "files": 0, "lines": 0, "issues": 0, "score": 0}, + # 项目5: 智能客服系统 + {"project_idx": 4, "status": "completed", "days_ago": 22, "files": 134, "lines": 15800, "issues": 19, "score": 71.2}, + {"project_idx": 4, "status": "completed", "days_ago": 10, "files": 142, "lines": 16500, "issues": 14, "score": 79.6}, + {"project_idx": 4, "status": "completed", "days_ago": 1, "files": 148, "lines": 17200, "issues": 7, "score": 86.8}, + # 项目6: 区块链钱包 + {"project_idx": 5, "status": "completed", "days_ago": 16, "files": 67, "lines": 8400, "issues": 16, "score": 65.3}, + {"project_idx": 5, "status": "completed", "days_ago": 6, "files": 72, "lines": 9100, "issues": 9, "score": 77.5}, + ] + + tasks = [] + for tdata in tasks_data: + task_time = now - timedelta(days=tdata["days_ago"]) + task = AuditTask( + project_id=projects[tdata["project_idx"]].id, + created_by=user.id, + task_type="full_scan", + status=tdata["status"], + branch_name="main", + total_files=tdata["files"], + scanned_files=tdata["files"] if tdata["status"] == "completed" else 0, + total_lines=tdata["lines"], + issues_count=tdata["issues"], + quality_score=tdata["score"], + started_at=task_time, + completed_at=task_time + timedelta(minutes=5) if tdata["status"] == "completed" else None, + created_at=task_time, + ) + db.add(task) + tasks.append(task) + + await db.flush() + logger.info(f"✓ 创建了 {len(tasks)} 个审计任务") + + # ==================== 创建审计问题 ==================== + issue_templates = [ + {"type": "security", "severity": "critical", "title": "SQL 注入漏洞", "file": "UserService.java", "line": 45}, + {"type": "security", "severity": "high", "title": "硬编码密钥", "file": "config/secrets.py", "line": 12}, + {"type": "security", "severity": "high", "title": "XSS 跨站脚本攻击风险", "file": "components/Comment.tsx", "line": 78}, + {"type": "security", "severity": "medium", "title": "不安全的随机数生成", "file": "utils/token.go", "line": 23}, + {"type": "bug", "severity": "high", "title": "空指针异常风险", "file": "OrderController.java", "line": 156}, + {"type": "bug", "severity": "medium", "title": "数组越界访问", "file": "DataProcessor.py", "line": 89}, + {"type": "bug", "severity": "low", "title": "未处理的 Promise 拒绝", "file": "api/client.ts", "line": 34}, + {"type": "performance", "severity": "medium", "title": "N+1 查询问题", "file": "ProductRepository.java", "line": 67}, + {"type": "performance", "severity": "low", "title": "不必要的重复渲染", "file": "pages/Dashboard.tsx", "line": 112}, + {"type": "style", "severity": "low", "title": "函数过长,建议拆分", "file": "services/payment.go", "line": 45}, + {"type": "maintainability", "severity": "medium", "title": "重复代码块", "file": "handlers/auth.go", "line": 78}, + {"type": "maintainability", "severity": "low", "title": "缺少错误处理", "file": "utils/http.py", "line": 56}, + ] + + issue_count = 0 + for task in tasks: + if task.status != "completed" or task.issues_count == 0: + continue + + # 为每个完成的任务创建问题 + num_issues = min(task.issues_count, len(issue_templates)) + for i in range(num_issues): + template = issue_templates[i % len(issue_templates)] + issue = AuditIssue( + task_id=task.id, + file_path=f"src/{template['file']}", + line_number=template["line"] + i * 10, + issue_type=template["type"], + severity=template["severity"], + title=template["title"], + message=template["title"], + description=f"在文件 {template['file']} 第 {template['line'] + i * 10} 行发现 {template['title']},这可能导致安全风险或程序异常。", + suggestion="建议进行代码审查并修复此问题。详细修复方案请参考相关安全规范。", + status="open" if i % 3 != 0 else "resolved", + resolved_by=user.id if i % 3 == 0 else None, + resolved_at=now - timedelta(days=i) if i % 3 == 0 else None, + created_at=task.created_at, + ) + db.add(issue) + issue_count += 1 + + await db.flush() + logger.info(f"✓ 创建了 {issue_count} 个审计问题") + + # ==================== 创建即时分析记录 ==================== + analyses_data = [ + {"lang": "Python", "issues": 3, "score": 75.5, "days_ago": 10}, + {"lang": "JavaScript", "issues": 5, "score": 68.2, "days_ago": 8}, + {"lang": "Java", "issues": 2, "score": 82.1, "days_ago": 6}, + {"lang": "Go", "issues": 1, "score": 91.3, "days_ago": 4}, + {"lang": "TypeScript", "issues": 4, "score": 72.8, "days_ago": 2}, + {"lang": "Python", "issues": 0, "score": 95.0, "days_ago": 1}, + ] + + for adata in analyses_data: + analysis = InstantAnalysis( + user_id=user.id, + language=adata["lang"], + code_content="# 演示代码\nprint('Hello, World!')", + analysis_result=json.dumps({"issues": [], "summary": "演示分析结果"}), + issues_count=adata["issues"], + quality_score=adata["score"], + analysis_time=2.5, + created_at=now - timedelta(days=adata["days_ago"]), + ) + db.add(analysis) + + await db.flush() + logger.info(f"✓ 创建了 {len(analyses_data)} 条即时分析记录") + + await db.commit() + logger.info("✓ 演示数据创建完成") + + +async def init_db(db: AsyncSession) -> None: + """ + 初始化数据库 + """ + logger.info("开始初始化数据库...") + + # 创建演示用户 + demo_user = await create_demo_user(db) + + # 创建演示数据 + if demo_user: + await create_demo_data(db, demo_user) + + await db.commit() + logger.info("数据库初始化完成") diff --git a/backend/app/main.py b/backend/app/main.py index faf5029..ed2c174 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,11 +1,50 @@ +import logging +from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware + from app.core.config import settings from app.api.v1.api import api_router +from app.db.session import AsyncSessionLocal +from app.db.init_db import init_db + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + 应用生命周期管理 + 启动时初始化数据库(创建默认账户等) + """ + logger.info("XCodeReviewer 后端服务启动中...") + + # 初始化数据库(创建默认账户) + try: + async with AsyncSessionLocal() as db: + await init_db(db) + logger.info("✓ 数据库初始化完成") + except Exception as e: + logger.warning(f"数据库初始化跳过(可能数据库未就绪): {e}") + + logger.info("=" * 50) + logger.info("XCodeReviewer 后端服务已启动") + logger.info(f"API 文档: http://localhost:8000/docs") + logger.info("=" * 50) + logger.info("演示账户: demo@example.com / demo123") + logger.info("=" * 50) + + yield + + logger.info("XCodeReviewer 后端服务已关闭") + app = FastAPI( title=settings.PROJECT_NAME, - openapi_url=f"{settings.API_V1_STR}/openapi.json" + openapi_url=f"{settings.API_V1_STR}/openapi.json", + lifespan=lifespan ) # Configure CORS - Allow all origins in development @@ -19,10 +58,19 @@ app.add_middleware( app.include_router(api_router, prefix=settings.API_V1_STR) + @app.get("/health") async def health_check(): return {"status": "ok"} + @app.get("/") async def root(): - return {"message": "Welcome to XCodeReviewer API", "docs": "/docs"} + return { + "message": "Welcome to XCodeReviewer API", + "docs": "/docs", + "demo_account": { + "email": "demo@example.com", + "password": "demo123" + } + } diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index df851fc..655b363 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -33,6 +33,15 @@ docker-compose up -d # 后端 API: http://localhost:8000/docs ``` +### 演示账户 + +系统启动时会自动创建演示账户,包含示例项目和审计数据,可直接体验完整功能: + +- 📧 邮箱:`demo@example.com` +- 🔑 密码:`demo123` + +> ⚠️ **安全提示**: 生产环境部署后,请删除演示账户或修改密码。 + --- ## Docker Compose 部署(推荐) diff --git a/docs/FAQ.md b/docs/FAQ.md index 4439347..7d85c8b 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -34,6 +34,15 @@ docker-compose up -d # 4. 访问 http://localhost:5173 ``` +### Q: 演示账户是什么? + +系统启动时会自动创建演示账户,包含示例项目和审计数据: + +- 📧 邮箱:`demo@example.com` +- 🔑 密码:`demo123` + +演示账户拥有管理员权限,可体验所有功能。生产环境请删除或修改密码。 + ### Q: 不想用 Docker,如何本地运行? 参见 [部署指南 - 本地开发部署](DEPLOYMENT.md#本地开发部署)。 diff --git a/frontend/package.json b/frontend/package.json index e48bf1b..f77f003 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "xcode-reviewer", - "version": "1.3.4", + "version": "2.0.0-beta.3", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 4c3e68d..fc05694 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -61,7 +61,7 @@ export default function Dashboard() { // 项目列表 - 使用真实数据 if (results[1].status === 'fulfilled') { - setRecentProjects(Array.isArray(results[1].value) ? results[1].value.slice(0, 5) : []); + setRecentProjects(Array.isArray(results[1].value) ? results[1].value.slice(0, 6) : []); } else { console.error('获取项目列表失败:', results[1].reason); setRecentProjects([]); diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 4beeb0e..44570a8 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -8,6 +8,7 @@ import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { toast } from "sonner"; import { Lock, Mail, Terminal, Shield, Fingerprint } from "lucide-react"; +import { version } from "../../package.json"; export default function Login() { const [email, setEmail] = useState(""); @@ -226,7 +227,7 @@ export default function Login() { {/* Version Info */}

- Version 1.3.4 · Secure Connection + Version {version} · Secure Connection