diff --git a/README.md b/README.md
index c30eac0..71edca4 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@
| 项目管理 | 审计报告 |
|:---:|:---:|
-|  |  |
+|
|
|
| *GitHub/GitLab 无缝集成* | *专业报告,一键导出* |
## ✨ 为什么选择我们?
diff --git a/backend/app/api/v1/endpoints/database.py b/backend/app/api/v1/endpoints/database.py
index 0cdfca8..079273c 100644
--- a/backend/app/api/v1/endpoints/database.py
+++ b/backend/app/api/v1/endpoints/database.py
@@ -10,7 +10,7 @@ from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from pydantic import BaseModel
import json
-from datetime import datetime
+from datetime import datetime, timezone
from app.api import deps
from app.db.session import get_db
@@ -94,7 +94,7 @@ async def export_database(
# 7. 构建导出数据
export_data = {
"version": "1.0.0",
- "export_date": datetime.utcnow().isoformat(),
+ "export_date": datetime.now(timezone.utc).isoformat(),
"user": {
"id": current_user.id,
"email": current_user.email,
diff --git a/backend/app/api/v1/endpoints/projects.py b/backend/app/api/v1/endpoints/projects.py
index 8fdc3a3..b5dbe36 100644
--- a/backend/app/api/v1/endpoints/projects.py
+++ b/backend/app/api/v1/endpoints/projects.py
@@ -5,7 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from pydantic import BaseModel
-from datetime import datetime
+from datetime import datetime, timezone
import shutil
import os
import uuid
@@ -236,7 +236,7 @@ async def update_project(
for field, value in update_data.items():
setattr(project, field, value)
- project.updated_at = datetime.utcnow()
+ project.updated_at = datetime.now(timezone.utc)
await db.commit()
await db.refresh(project)
return project
@@ -260,7 +260,7 @@ async def delete_project(
raise HTTPException(status_code=403, detail="无权删除此项目")
project.is_active = False
- project.updated_at = datetime.utcnow()
+ project.updated_at = datetime.now(timezone.utc)
await db.commit()
return {"message": "项目已删除"}
@@ -283,7 +283,7 @@ async def restore_project(
raise HTTPException(status_code=403, detail="无权恢复此项目")
project.is_active = True
- project.updated_at = datetime.utcnow()
+ project.updated_at = datetime.now(timezone.utc)
await db.commit()
return {"message": "项目已恢复"}
diff --git a/backend/app/api/v1/endpoints/scan.py b/backend/app/api/v1/endpoints/scan.py
index 75b0aa4..661fa7f 100644
--- a/backend/app/api/v1/endpoints/scan.py
+++ b/backend/app/api/v1/endpoints/scan.py
@@ -3,7 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from typing import Any, List, Optional
from pydantic import BaseModel
-from datetime import datetime
+from datetime import datetime, timezone
import uuid
import shutil
import os
@@ -53,7 +53,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
try:
task.status = "running"
- task.started_at = datetime.utcnow()
+ task.started_at = datetime.now(timezone.utc)
await db.commit()
# 创建使用用户配置的LLM服务实例
@@ -116,7 +116,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
if task_control.is_cancelled(task_id):
print(f"🛑 ZIP任务 {task_id} 已被取消")
task.status = "cancelled"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
task_control.cleanup_task(task_id)
return
@@ -173,7 +173,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
# 如果有文件需要分析但全部失败,标记为失败
if len(files_to_scan) > 0 and scanned_files == 0:
task.status = "failed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
task.scanned_files = 0
task.total_lines = total_lines
task.issues_count = 0
@@ -182,7 +182,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
print(f"❌ ZIP任务 {task_id} 失败: 所有 {len(files_to_scan)} 个文件分析均失败,请检查 LLM API 配置")
else:
task.status = "completed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
task.scanned_files = scanned_files
task.total_lines = total_lines
task.issues_count = total_issues
@@ -194,7 +194,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
except Exception as e:
print(f"❌ ZIP扫描失败: {e}")
task.status = "failed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
task_control.cleanup_task(task_id)
finally:
@@ -408,7 +408,7 @@ async def instant_analysis(
# 创建使用用户配置的LLM服务实例
llm_service = LLMService(user_config=user_config)
- start_time = datetime.utcnow()
+ start_time = datetime.now(timezone.utc)
try:
result = await llm_service.analyze_code(req.code, req.language)
@@ -421,7 +421,7 @@ async def instant_analysis(
detail=f"代码分析失败: {error_msg}"
)
- end_time = datetime.utcnow()
+ end_time = datetime.now(timezone.utc)
duration = (end_time - start_time).total_seconds()
# Save record
diff --git a/backend/app/api/v1/endpoints/tasks.py b/backend/app/api/v1/endpoints/tasks.py
index b7d9941..16a52c6 100644
--- a/backend/app/api/v1/endpoints/tasks.py
+++ b/backend/app/api/v1/endpoints/tasks.py
@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from pydantic import BaseModel
-from datetime import datetime
+from datetime import datetime, timezone
from app.api import deps
from app.db.session import get_db
@@ -161,7 +161,7 @@ async def cancel_task(
# 更新数据库状态
task.status = "cancelled"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
return {"message": "任务已取消", "task_id": id}
@@ -223,7 +223,7 @@ async def update_issue(
issue.status = issue_update.status
if issue_update.status == "resolved":
issue.resolved_by = current_user.id
- issue.resolved_at = datetime.utcnow()
+ issue.resolved_at = datetime.now(timezone.utc)
await db.commit()
await db.refresh(issue)
@@ -299,7 +299,7 @@ async def export_task_report_pdf(
pdf_bytes = ReportGenerator.generate_task_report(task_dict, issues_list, project_name)
# 返回 PDF 文件
- filename = f"audit-report-{task.id[:8]}-{datetime.utcnow().strftime('%Y%m%d')}.pdf"
+ filename = f"audit-report-{task.id[:8]}-{datetime.now(timezone.utc).strftime('%Y%m%d')}.pdf"
return Response(
content=pdf_bytes,
media_type="application/pdf",
diff --git a/backend/app/core/security.py b/backend/app/core/security.py
index 063000d..a71f44a 100644
--- a/backend/app/core/security.py
+++ b/backend/app/core/security.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from typing import Any, Union
from jose import jwt
from passlib.context import CryptContext
@@ -12,9 +12,9 @@ def create_access_token(
subject: Union[str, Any], expires_delta: timedelta = None
) -> str:
if expires_delta:
- expire = datetime.utcnow() + expires_delta
+ expire = datetime.now(timezone.utc) + expires_delta
else:
- expire = datetime.utcnow() + timedelta(
+ expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode = {"exp": expire, "sub": str(subject)}
diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py
index aa26f87..cb70bb4 100644
--- a/backend/app/db/init_db.py
+++ b/backend/app/db/init_db.py
@@ -4,7 +4,7 @@
"""
import json
import logging
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@@ -60,7 +60,7 @@ async def create_demo_data(db: AsyncSession, user: User) -> None:
return
logger.info("开始创建演示数据...")
- now = datetime.utcnow()
+ now = datetime.now(timezone.utc)
# ==================== 创建演示项目 ====================
projects_data = [
diff --git a/backend/app/services/llm/service.py b/backend/app/services/llm/service.py
index ff05d56..d5c21cf 100644
--- a/backend/app/services/llm/service.py
+++ b/backend/app/services/llm/service.py
@@ -215,14 +215,16 @@ class LLMService:
2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\)
3. code_snippet字段必须使用\\n表示换行
-请从以下维度全面分析代码:
-- 编码规范和代码风格
+请从以下维度全面、彻底地分析代码,找出所有问题:
+- 安全漏洞(SQL注入、XSS、命令注入、路径遍历、SSRF、XXE、反序列化、硬编码密钥等)
- 潜在的 Bug 和逻辑错误
- 性能问题和优化建议
-- 安全漏洞和风险
+- 编码规范和代码风格
- 可维护性和可读性
- 最佳实践和设计模式
+【重要】请尽可能多地找出代码中的所有问题,不要遗漏任何安全漏洞或潜在风险!
+
输出格式必须严格符合以下 JSON Schema:
{schema}
@@ -268,14 +270,16 @@ You are a professional code auditing assistant. Your task is to analyze code and
2. Special characters in JSON strings must be properly escaped (\\n for newlines, \\" for quotes, \\\\ for backslashes)
3. code_snippet field MUST use \\n for newlines
-Please comprehensively analyze the code from the following dimensions:
-- Coding standards and code style
+Please comprehensively and thoroughly analyze the code, finding ALL issues from the following dimensions:
+- Security vulnerabilities (SQL injection, XSS, command injection, path traversal, SSRF, XXE, deserialization, hardcoded secrets, etc.)
- Potential bugs and logical errors
- Performance issues and optimization suggestions
-- Security vulnerabilities and risks
+- Coding standards and code style
- Maintainability and readability
- Best practices and design patterns
+【IMPORTANT】Find as many issues as possible! Do NOT miss any security vulnerabilities or potential risks!
+
The output format MUST strictly conform to the following JSON Schema:
{schema}
@@ -358,6 +362,10 @@ Please analyze the following code:
response = await adapter.complete(request)
content = response.content
+ # 记录 LLM 原始响应(用于调试)
+ logger.info(f"📥 LLM 原始响应长度: {len(content) if content else 0} 字符")
+ logger.info(f"📥 LLM 原始响应内容:\n{content}")
+
# 检查响应内容是否为空
if not content or not content.strip():
error_msg = f"LLM返回空响应 - Provider: {self.config.provider.value}, Model: {self.config.model}"
@@ -368,6 +376,10 @@ Please analyze the following code:
# 尝试从响应中提取JSON
result = self._parse_json(content)
+ # 记录解析后的问题数量
+ issues_count = len(result.get("issues", []))
+ logger.info(f"📊 LLM 分析结果: 发现 {issues_count} 个问题, 质量评分: {result.get('quality_score', 'N/A')}")
+
# 检查解析结果是否有效(不是默认响应)
if result == self._get_default_response():
error_msg = f"无法解析LLM响应为有效的分析结果 - Provider: {self.config.provider.value}"
diff --git a/backend/app/services/report_generator.py b/backend/app/services/report_generator.py
index a440fcf..e282dff 100644
--- a/backend/app/services/report_generator.py
+++ b/backend/app/services/report_generator.py
@@ -208,7 +208,7 @@ class ReportGenerator:
.issue-item {
border-bottom: 1px solid #e5e7eb;
padding: 10px 0; /* Reduced padding */
- break-inside: avoid;
+ /* 移除 break-inside: avoid,允许问题块跨页 */
}
.issue-item:last-child {
@@ -220,6 +220,8 @@ class ReportGenerator:
justify-content: space-between;
align-items: flex-start;
margin-bottom: 6px; /* Reduced margin */
+ break-inside: avoid; /* 标题行不断开 */
+ break-after: avoid; /* 标题后不断页 */
}
.issue-title {
@@ -247,6 +249,7 @@ class ReportGenerator:
display: inline-block;
border-radius: 2px;
font-family: monospace;
+ break-after: avoid; /* 元信息后不断页 */
}
.issue-desc {
diff --git a/backend/app/services/scanner.py b/backend/app/services/scanner.py
index 78919c3..cc7af98 100644
--- a/backend/app/services/scanner.py
+++ b/backend/app/services/scanner.py
@@ -5,7 +5,7 @@
import asyncio
import httpx
from typing import List, Dict, Any, Optional
-from datetime import datetime
+from datetime import datetime, timezone
from urllib.parse import urlparse, quote
from sqlalchemy.ext.asyncio import AsyncSession
@@ -252,7 +252,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
try:
# 1. 更新状态为运行中
task.status = "running"
- task.started_at = datetime.utcnow()
+ task.started_at = datetime.now(timezone.utc)
await db.commit()
# 创建使用用户配置的LLM服务实例
@@ -336,7 +336,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
if task_control.is_cancelled(task_id):
print(f"🛑 任务 {task_id} 已被用户取消")
task.status = "cancelled"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
task_control.cleanup_task(task_id)
return
@@ -379,7 +379,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
if task_control.is_cancelled(task_id):
print(f"🛑 任务 {task_id} 在LLM分析后被取消")
task.status = "cancelled"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
task_control.cleanup_task(task_id)
return
@@ -454,7 +454,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
# 如果所有文件都被跳过(空文件等),标记为完成但给出提示
if len(files) > 0 and scanned_files == 0 and skipped_files == len(files):
task.status = "completed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
task.scanned_files = 0
task.total_lines = 0
task.issues_count = 0
@@ -464,7 +464,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
# 如果有文件需要分析但全部失败(LLM调用失败),标记为失败
elif len(files) > 0 and scanned_files == 0 and failed_files > 0:
task.status = "failed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
task.scanned_files = 0
task.total_lines = total_lines
task.issues_count = 0
@@ -473,7 +473,7 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
print(f"❌ 任务 {task_id} 失败: {failed_files} 个文件分析失败,请检查 LLM API 配置")
else:
task.status = "completed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
task.scanned_files = scanned_files
task.total_lines = total_lines
task.issues_count = total_issues
@@ -485,6 +485,6 @@ async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = N
except Exception as e:
print(f"❌ 扫描失败: {e}")
task.status = "failed"
- task.completed_at = datetime.utcnow()
+ task.completed_at = datetime.now(timezone.utc)
await db.commit()
task_control.cleanup_task(task_id)
diff --git a/backend/app/services/zip_storage.py b/backend/app/services/zip_storage.py
index a1c0839..37eb119 100644
--- a/backend/app/services/zip_storage.py
+++ b/backend/app/services/zip_storage.py
@@ -7,7 +7,7 @@ import os
import shutil
from pathlib import Path
from typing import Optional
-from datetime import datetime
+from datetime import datetime, timezone
from app.core.config import settings
@@ -54,7 +54,7 @@ async def save_project_zip(project_id: str, file_path: str, original_filename: s
meta = {
"original_filename": original_filename,
"file_size": file_size,
- "uploaded_at": datetime.utcnow().isoformat(),
+ "uploaded_at": datetime.now(timezone.utc).isoformat(),
"project_id": project_id
}
diff --git a/frontend/public/images/审计报告示例.png b/frontend/public/images/审计报告示例.png
index e31f199..78ad92d 100644
Binary files a/frontend/public/images/审计报告示例.png and b/frontend/public/images/审计报告示例.png differ
diff --git a/frontend/src/components/reports/InstantExportDialog.tsx b/frontend/src/components/reports/InstantExportDialog.tsx
index 51082c8..bc8a335 100644
--- a/frontend/src/components/reports/InstantExportDialog.tsx
+++ b/frontend/src/components/reports/InstantExportDialog.tsx
@@ -159,15 +159,15 @@ export default function InstantExportDialog({