feat: 添加演示账户和演示数据,完善项目文档

- 添加演示账户 demo@example.com / demo123
- 创建 6 个演示项目和 15 个审计任务
- 完善 DEPLOYMENT.md、CONFIGURATION.md、LLM_PROVIDERS.md、FAQ.md 文档
- 更新 CONTRIBUTING.md、SECURITY.md、DISCLAIMER.md
- 前端版本号自动从 package.json 读取
- 仪表盘项目概览显示 6 个项目
- 版本号更新为 2.0.0-beta.3
This commit is contained in:
lintsinghua 2025-12-05 15:09:39 +08:00
parent 1550e51662
commit f982d6a5e6
8 changed files with 357 additions and 5 deletions

View File

@ -97,6 +97,15 @@ docker-compose up -d
🎉 **搞定!** 打开 http://localhost:5173 开始体验吧! 🎉 **搞定!** 打开 http://localhost:5173 开始体验吧!
### 演示账户
系统内置演示账户,包含示例项目和审计数据,可直接体验完整功能:
- 📧 邮箱:`demo@example.com`
- 🔑 密码:`demo123`
> ⚠️ **生产环境请删除演示账户或修改密码!**
> 📖 更多部署方式请查看 [部署指南](docs/DEPLOYMENT.md) > 📖 更多部署方式请查看 [部署指南](docs/DEPLOYMENT.md)
## ✨ 核心能力 ## ✨ 核心能力

276
backend/app/db/init_db.py Normal file
View File

@ -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("数据库初始化完成")

View File

@ -1,11 +1,50 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.core.config import settings from app.core.config import settings
from app.api.v1.api import api_router 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( app = FastAPI(
title=settings.PROJECT_NAME, 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 # 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.include_router(api_router, prefix=settings.API_V1_STR)
@app.get("/health") @app.get("/health")
async def health_check(): async def health_check():
return {"status": "ok"} return {"status": "ok"}
@app.get("/") @app.get("/")
async def root(): 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"
}
}

View File

@ -33,6 +33,15 @@ docker-compose up -d
# 后端 API: http://localhost:8000/docs # 后端 API: http://localhost:8000/docs
``` ```
### 演示账户
系统启动时会自动创建演示账户,包含示例项目和审计数据,可直接体验完整功能:
- 📧 邮箱:`demo@example.com`
- 🔑 密码:`demo123`
> ⚠️ **安全提示**: 生产环境部署后,请删除演示账户或修改密码。
--- ---
## Docker Compose 部署(推荐) ## Docker Compose 部署(推荐)

View File

@ -34,6 +34,15 @@ docker-compose up -d
# 4. 访问 http://localhost:5173 # 4. 访问 http://localhost:5173
``` ```
### Q: 演示账户是什么?
系统启动时会自动创建演示账户,包含示例项目和审计数据:
- 📧 邮箱:`demo@example.com`
- 🔑 密码:`demo123`
演示账户拥有管理员权限,可体验所有功能。生产环境请删除或修改密码。
### Q: 不想用 Docker如何本地运行 ### Q: 不想用 Docker如何本地运行
参见 [部署指南 - 本地开发部署](DEPLOYMENT.md#本地开发部署)。 参见 [部署指南 - 本地开发部署](DEPLOYMENT.md#本地开发部署)。

View File

@ -1,6 +1,6 @@
{ {
"name": "xcode-reviewer", "name": "xcode-reviewer",
"version": "1.3.4", "version": "2.0.0-beta.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -61,7 +61,7 @@ export default function Dashboard() {
// 项目列表 - 使用真实数据 // 项目列表 - 使用真实数据
if (results[1].status === 'fulfilled') { 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 { } else {
console.error('获取项目列表失败:', results[1].reason); console.error('获取项目列表失败:', results[1].reason);
setRecentProjects([]); setRecentProjects([]);

View File

@ -8,6 +8,7 @@ import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { toast } from "sonner"; import { toast } from "sonner";
import { Lock, Mail, Terminal, Shield, Fingerprint } from "lucide-react"; import { Lock, Mail, Terminal, Shield, Fingerprint } from "lucide-react";
import { version } from "../../package.json";
export default function Login() { export default function Login() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@ -226,7 +227,7 @@ export default function Login() {
{/* Version Info */} {/* Version Info */}
<div className="mt-6 text-center"> <div className="mt-6 text-center">
<p className="font-mono text-[10px] text-gray-400 uppercase"> <p className="font-mono text-[10px] text-gray-400 uppercase">
Version 1.3.4 · Secure Connection Version {version} · Secure Connection
</p> </p>
</div> </div>
</div> </div>