fix: 修复时间显示问题,使用带时区的UTC时间
- 将所有 datetime.utcnow() 替换为 datetime.now(timezone.utc) - 修复 completed_at, started_at, updated_at, resolved_at 等时间字段 - 修复 JWT token 过期时间计算 - 修复数据导出和ZIP上传时间戳 - 调整README中项目管理和审计报告图片显示比例
This commit is contained in:
parent
4f95f65645
commit
14b7c8cccc
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
| 项目管理 | 审计报告 |
|
| 项目管理 | 审计报告 |
|
||||||
|:---:|:---:|
|
|:---:|:---:|
|
||||||
|  |  |
|
| <img src="frontend/public/images/example3.png" alt="项目管理" width="400"> | <img src="frontend/public/images/审计报告示例.png" alt="审计报告" width="400"> |
|
||||||
| *GitHub/GitLab 无缝集成* | *专业报告,一键导出* |
|
| *GitHub/GitLab 无缝集成* | *专业报告,一键导出* |
|
||||||
|
|
||||||
## ✨ 为什么选择我们?
|
## ✨ 为什么选择我们?
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from sqlalchemy.future import select
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from app.api import deps
|
from app.api import deps
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
|
|
@ -94,7 +94,7 @@ async def export_database(
|
||||||
# 7. 构建导出数据
|
# 7. 构建导出数据
|
||||||
export_data = {
|
export_data = {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"export_date": datetime.utcnow().isoformat(),
|
"export_date": datetime.now(timezone.utc).isoformat(),
|
||||||
"user": {
|
"user": {
|
||||||
"id": current_user.id,
|
"id": current_user.id,
|
||||||
"email": current_user.email,
|
"email": current_user.email,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -236,7 +236,7 @@ async def update_project(
|
||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
setattr(project, field, value)
|
setattr(project, field, value)
|
||||||
|
|
||||||
project.updated_at = datetime.utcnow()
|
project.updated_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(project)
|
await db.refresh(project)
|
||||||
return project
|
return project
|
||||||
|
|
@ -260,7 +260,7 @@ async def delete_project(
|
||||||
raise HTTPException(status_code=403, detail="无权删除此项目")
|
raise HTTPException(status_code=403, detail="无权删除此项目")
|
||||||
|
|
||||||
project.is_active = False
|
project.is_active = False
|
||||||
project.updated_at = datetime.utcnow()
|
project.updated_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
return {"message": "项目已删除"}
|
return {"message": "项目已删除"}
|
||||||
|
|
||||||
|
|
@ -283,7 +283,7 @@ async def restore_project(
|
||||||
raise HTTPException(status_code=403, detail="无权恢复此项目")
|
raise HTTPException(status_code=403, detail="无权恢复此项目")
|
||||||
|
|
||||||
project.is_active = True
|
project.is_active = True
|
||||||
project.updated_at = datetime.utcnow()
|
project.updated_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
return {"message": "项目已恢复"}
|
return {"message": "项目已恢复"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
import uuid
|
import uuid
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
|
@ -53,7 +53,7 @@ async def process_zip_task(task_id: str, file_path: str, db_session_factory, use
|
||||||
|
|
||||||
try:
|
try:
|
||||||
task.status = "running"
|
task.status = "running"
|
||||||
task.started_at = datetime.utcnow()
|
task.started_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
# 创建使用用户配置的LLM服务实例
|
# 创建使用用户配置的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):
|
if task_control.is_cancelled(task_id):
|
||||||
print(f"🛑 ZIP任务 {task_id} 已被取消")
|
print(f"🛑 ZIP任务 {task_id} 已被取消")
|
||||||
task.status = "cancelled"
|
task.status = "cancelled"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
task_control.cleanup_task(task_id)
|
task_control.cleanup_task(task_id)
|
||||||
return
|
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:
|
if len(files_to_scan) > 0 and scanned_files == 0:
|
||||||
task.status = "failed"
|
task.status = "failed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
task.scanned_files = 0
|
task.scanned_files = 0
|
||||||
task.total_lines = total_lines
|
task.total_lines = total_lines
|
||||||
task.issues_count = 0
|
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 配置")
|
print(f"❌ ZIP任务 {task_id} 失败: 所有 {len(files_to_scan)} 个文件分析均失败,请检查 LLM API 配置")
|
||||||
else:
|
else:
|
||||||
task.status = "completed"
|
task.status = "completed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
task.scanned_files = scanned_files
|
task.scanned_files = scanned_files
|
||||||
task.total_lines = total_lines
|
task.total_lines = total_lines
|
||||||
task.issues_count = total_issues
|
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:
|
except Exception as e:
|
||||||
print(f"❌ ZIP扫描失败: {e}")
|
print(f"❌ ZIP扫描失败: {e}")
|
||||||
task.status = "failed"
|
task.status = "failed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
task_control.cleanup_task(task_id)
|
task_control.cleanup_task(task_id)
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -408,7 +408,7 @@ async def instant_analysis(
|
||||||
# 创建使用用户配置的LLM服务实例
|
# 创建使用用户配置的LLM服务实例
|
||||||
llm_service = LLMService(user_config=user_config)
|
llm_service = LLMService(user_config=user_config)
|
||||||
|
|
||||||
start_time = datetime.utcnow()
|
start_time = datetime.now(timezone.utc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await llm_service.analyze_code(req.code, req.language)
|
result = await llm_service.analyze_code(req.code, req.language)
|
||||||
|
|
@ -421,7 +421,7 @@ async def instant_analysis(
|
||||||
detail=f"代码分析失败: {error_msg}"
|
detail=f"代码分析失败: {error_msg}"
|
||||||
)
|
)
|
||||||
|
|
||||||
end_time = datetime.utcnow()
|
end_time = datetime.now(timezone.utc)
|
||||||
duration = (end_time - start_time).total_seconds()
|
duration = (end_time - start_time).total_seconds()
|
||||||
|
|
||||||
# Save record
|
# Save record
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from app.api import deps
|
from app.api import deps
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
|
|
@ -161,7 +161,7 @@ async def cancel_task(
|
||||||
|
|
||||||
# 更新数据库状态
|
# 更新数据库状态
|
||||||
task.status = "cancelled"
|
task.status = "cancelled"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"message": "任务已取消", "task_id": id}
|
return {"message": "任务已取消", "task_id": id}
|
||||||
|
|
@ -223,7 +223,7 @@ async def update_issue(
|
||||||
issue.status = issue_update.status
|
issue.status = issue_update.status
|
||||||
if issue_update.status == "resolved":
|
if issue_update.status == "resolved":
|
||||||
issue.resolved_by = current_user.id
|
issue.resolved_by = current_user.id
|
||||||
issue.resolved_at = datetime.utcnow()
|
issue.resolved_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(issue)
|
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_bytes = ReportGenerator.generate_task_report(task_dict, issues_list, project_name)
|
||||||
|
|
||||||
# 返回 PDF 文件
|
# 返回 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(
|
return Response(
|
||||||
content=pdf_bytes,
|
content=pdf_bytes,
|
||||||
media_type="application/pdf",
|
media_type="application/pdf",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
|
|
@ -12,9 +12,9 @@ def create_access_token(
|
||||||
subject: Union[str, Any], expires_delta: timedelta = None
|
subject: Union[str, Any], expires_delta: timedelta = None
|
||||||
) -> str:
|
) -> str:
|
||||||
if expires_delta:
|
if expires_delta:
|
||||||
expire = datetime.utcnow() + expires_delta
|
expire = datetime.now(timezone.utc) + expires_delta
|
||||||
else:
|
else:
|
||||||
expire = datetime.utcnow() + timedelta(
|
expire = datetime.now(timezone.utc) + timedelta(
|
||||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
)
|
)
|
||||||
to_encode = {"exp": expire, "sub": str(subject)}
|
to_encode = {"exp": expire, "sub": str(subject)}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ async def create_demo_data(db: AsyncSession, user: User) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("开始创建演示数据...")
|
logger.info("开始创建演示数据...")
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# ==================== 创建演示项目 ====================
|
# ==================== 创建演示项目 ====================
|
||||||
projects_data = [
|
projects_data = [
|
||||||
|
|
|
||||||
|
|
@ -215,14 +215,16 @@ class LLMService:
|
||||||
2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\)
|
2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\)
|
||||||
3. code_snippet字段必须使用\\n表示换行
|
3. code_snippet字段必须使用\\n表示换行
|
||||||
|
|
||||||
请从以下维度全面分析代码:
|
请从以下维度全面、彻底地分析代码,找出所有问题:
|
||||||
- 编码规范和代码风格
|
- 安全漏洞(SQL注入、XSS、命令注入、路径遍历、SSRF、XXE、反序列化、硬编码密钥等)
|
||||||
- 潜在的 Bug 和逻辑错误
|
- 潜在的 Bug 和逻辑错误
|
||||||
- 性能问题和优化建议
|
- 性能问题和优化建议
|
||||||
- 安全漏洞和风险
|
- 编码规范和代码风格
|
||||||
- 可维护性和可读性
|
- 可维护性和可读性
|
||||||
- 最佳实践和设计模式
|
- 最佳实践和设计模式
|
||||||
|
|
||||||
|
【重要】请尽可能多地找出代码中的所有问题,不要遗漏任何安全漏洞或潜在风险!
|
||||||
|
|
||||||
输出格式必须严格符合以下 JSON Schema:
|
输出格式必须严格符合以下 JSON Schema:
|
||||||
|
|
||||||
{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)
|
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
|
3. code_snippet field MUST use \\n for newlines
|
||||||
|
|
||||||
Please comprehensively analyze the code from the following dimensions:
|
Please comprehensively and thoroughly analyze the code, finding ALL issues from the following dimensions:
|
||||||
- Coding standards and code style
|
- Security vulnerabilities (SQL injection, XSS, command injection, path traversal, SSRF, XXE, deserialization, hardcoded secrets, etc.)
|
||||||
- Potential bugs and logical errors
|
- Potential bugs and logical errors
|
||||||
- Performance issues and optimization suggestions
|
- Performance issues and optimization suggestions
|
||||||
- Security vulnerabilities and risks
|
- Coding standards and code style
|
||||||
- Maintainability and readability
|
- Maintainability and readability
|
||||||
- Best practices and design patterns
|
- 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:
|
The output format MUST strictly conform to the following JSON Schema:
|
||||||
|
|
||||||
{schema}
|
{schema}
|
||||||
|
|
@ -358,6 +362,10 @@ Please analyze the following code:
|
||||||
response = await adapter.complete(request)
|
response = await adapter.complete(request)
|
||||||
content = response.content
|
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():
|
if not content or not content.strip():
|
||||||
error_msg = f"LLM返回空响应 - Provider: {self.config.provider.value}, Model: {self.config.model}"
|
error_msg = f"LLM返回空响应 - Provider: {self.config.provider.value}, Model: {self.config.model}"
|
||||||
|
|
@ -368,6 +376,10 @@ Please analyze the following code:
|
||||||
# 尝试从响应中提取JSON
|
# 尝试从响应中提取JSON
|
||||||
result = self._parse_json(content)
|
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():
|
if result == self._get_default_response():
|
||||||
error_msg = f"无法解析LLM响应为有效的分析结果 - Provider: {self.config.provider.value}"
|
error_msg = f"无法解析LLM响应为有效的分析结果 - Provider: {self.config.provider.value}"
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ class ReportGenerator:
|
||||||
.issue-item {
|
.issue-item {
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
padding: 10px 0; /* Reduced padding */
|
padding: 10px 0; /* Reduced padding */
|
||||||
break-inside: avoid;
|
/* 移除 break-inside: avoid,允许问题块跨页 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-item:last-child {
|
.issue-item:last-child {
|
||||||
|
|
@ -220,6 +220,8 @@ class ReportGenerator:
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 6px; /* Reduced margin */
|
margin-bottom: 6px; /* Reduced margin */
|
||||||
|
break-inside: avoid; /* 标题行不断开 */
|
||||||
|
break-after: avoid; /* 标题后不断页 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-title {
|
.issue-title {
|
||||||
|
|
@ -247,6 +249,7 @@ class ReportGenerator:
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
break-after: avoid; /* 元信息后不断页 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-desc {
|
.issue-desc {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from urllib.parse import urlparse, quote
|
from urllib.parse import urlparse, quote
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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:
|
try:
|
||||||
# 1. 更新状态为运行中
|
# 1. 更新状态为运行中
|
||||||
task.status = "running"
|
task.status = "running"
|
||||||
task.started_at = datetime.utcnow()
|
task.started_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
# 创建使用用户配置的LLM服务实例
|
# 创建使用用户配置的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):
|
if task_control.is_cancelled(task_id):
|
||||||
print(f"🛑 任务 {task_id} 已被用户取消")
|
print(f"🛑 任务 {task_id} 已被用户取消")
|
||||||
task.status = "cancelled"
|
task.status = "cancelled"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
task_control.cleanup_task(task_id)
|
task_control.cleanup_task(task_id)
|
||||||
return
|
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):
|
if task_control.is_cancelled(task_id):
|
||||||
print(f"🛑 任务 {task_id} 在LLM分析后被取消")
|
print(f"🛑 任务 {task_id} 在LLM分析后被取消")
|
||||||
task.status = "cancelled"
|
task.status = "cancelled"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
task_control.cleanup_task(task_id)
|
task_control.cleanup_task(task_id)
|
||||||
return
|
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):
|
if len(files) > 0 and scanned_files == 0 and skipped_files == len(files):
|
||||||
task.status = "completed"
|
task.status = "completed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
task.scanned_files = 0
|
task.scanned_files = 0
|
||||||
task.total_lines = 0
|
task.total_lines = 0
|
||||||
task.issues_count = 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调用失败),标记为失败
|
# 如果有文件需要分析但全部失败(LLM调用失败),标记为失败
|
||||||
elif len(files) > 0 and scanned_files == 0 and failed_files > 0:
|
elif len(files) > 0 and scanned_files == 0 and failed_files > 0:
|
||||||
task.status = "failed"
|
task.status = "failed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
task.scanned_files = 0
|
task.scanned_files = 0
|
||||||
task.total_lines = total_lines
|
task.total_lines = total_lines
|
||||||
task.issues_count = 0
|
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 配置")
|
print(f"❌ 任务 {task_id} 失败: {failed_files} 个文件分析失败,请检查 LLM API 配置")
|
||||||
else:
|
else:
|
||||||
task.status = "completed"
|
task.status = "completed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
task.scanned_files = scanned_files
|
task.scanned_files = scanned_files
|
||||||
task.total_lines = total_lines
|
task.total_lines = total_lines
|
||||||
task.issues_count = total_issues
|
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:
|
except Exception as e:
|
||||||
print(f"❌ 扫描失败: {e}")
|
print(f"❌ 扫描失败: {e}")
|
||||||
task.status = "failed"
|
task.status = "failed"
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
task_control.cleanup_task(task_id)
|
task_control.cleanup_task(task_id)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from app.core.config import settings
|
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 = {
|
meta = {
|
||||||
"original_filename": original_filename,
|
"original_filename": original_filename,
|
||||||
"file_size": file_size,
|
"file_size": file_size,
|
||||||
"uploaded_at": datetime.utcnow().isoformat(),
|
"uploaded_at": datetime.now(timezone.utc).isoformat(),
|
||||||
"project_id": project_id
|
"project_id": project_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 404 KiB |
|
|
@ -159,15 +159,15 @@ export default function InstantExportDialog({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600 font-bold">质量评分:</span>
|
<span className="text-gray-600 font-bold">质量评分:</span>
|
||||||
<span className="font-bold text-black">{analysisResult.quality_score.toFixed(1)}/100</span>
|
<span className="font-bold text-black">{(analysisResult.quality_score ?? 0).toFixed(1)}/100</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600 font-bold">发现问题:</span>
|
<span className="text-gray-600 font-bold">发现问题:</span>
|
||||||
<span className="font-bold text-orange-600">{analysisResult.issues.length}</span>
|
<span className="font-bold text-orange-600">{analysisResult.issues?.length ?? 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600 font-bold">分析耗时:</span>
|
<span className="text-gray-600 font-bold">分析耗时:</span>
|
||||||
<span className="font-bold text-black">{analysisTime.toFixed(2)}s</span>
|
<span className="font-bold text-black">{(analysisTime ?? 0).toFixed(2)}s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -633,7 +633,7 @@ class UserManager {
|
||||||
'bg-red-100 text-red-800'
|
'bg-red-100 text-red-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
评分: {record.quality_score.toFixed(1)}
|
评分: {(record.quality_score ?? 0).toFixed(1)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -653,7 +653,7 @@ class UserManager {
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Clock className="w-3 h-3" />
|
<Clock className="w-3 h-3" />
|
||||||
{record.analysis_time.toFixed(2)}s
|
{(record.analysis_time ?? 0).toFixed(2)}s
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -827,7 +827,7 @@ class UserManager {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
|
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
|
||||||
<Clock className="w-3 h-3 mr-1" />
|
<Clock className="w-3 h-3 mr-1" />
|
||||||
{analysisTime.toFixed(2)}s
|
{(analysisTime ?? 0).toFixed(2)}s
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
|
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
|
||||||
{language}
|
{language}
|
||||||
|
|
@ -852,10 +852,10 @@ class UserManager {
|
||||||
<Target className="w-6 h-6" />
|
<Target className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold text-primary mb-1">
|
<div className="text-3xl font-bold text-primary mb-1">
|
||||||
{result.quality_score.toFixed(1)}
|
{(result.quality_score ?? 0).toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">质量评分</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-2">质量评分</p>
|
||||||
<Progress value={result.quality_score} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
<Progress value={result.quality_score ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
|
@ -863,7 +863,7 @@ class UserManager {
|
||||||
<AlertTriangle className="w-6 h-6" />
|
<AlertTriangle className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold text-red-600 mb-1">
|
<div className="text-3xl font-bold text-red-600 mb-1">
|
||||||
{result.summary.critical_issues + result.summary.high_issues}
|
{(result.summary?.critical_issues ?? 0) + (result.summary?.high_issues ?? 0)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-bold text-red-700 uppercase mb-1">严重问题</p>
|
<p className="text-xs font-bold text-red-700 uppercase mb-1">严重问题</p>
|
||||||
<div className="text-xs text-red-600 font-bold uppercase">需要立即处理</div>
|
<div className="text-xs text-red-600 font-bold uppercase">需要立即处理</div>
|
||||||
|
|
@ -874,7 +874,7 @@ class UserManager {
|
||||||
<Info className="w-6 h-6" />
|
<Info className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold text-yellow-600 mb-1">
|
<div className="text-3xl font-bold text-yellow-600 mb-1">
|
||||||
{result.summary.medium_issues + result.summary.low_issues}
|
{(result.summary?.medium_issues ?? 0) + (result.summary?.low_issues ?? 0)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-bold text-yellow-700 uppercase mb-1">一般问题</p>
|
<p className="text-xs font-bold text-yellow-700 uppercase mb-1">一般问题</p>
|
||||||
<div className="text-xs text-yellow-600 font-bold uppercase">建议优化</div>
|
<div className="text-xs text-yellow-600 font-bold uppercase">建议优化</div>
|
||||||
|
|
@ -900,24 +900,24 @@ class UserManager {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.complexity}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics?.complexity ?? 0}</div>
|
||||||
<p className="text-xs text-gray-600 uppercase mb-2">复杂度</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">复杂度</p>
|
||||||
<Progress value={result.metrics.complexity} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
<Progress value={result.metrics?.complexity ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.maintainability}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics?.maintainability ?? 0}</div>
|
||||||
<p className="text-xs text-gray-600 uppercase mb-2">可维护性</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">可维护性</p>
|
||||||
<Progress value={result.metrics.maintainability} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
<Progress value={result.metrics?.maintainability ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.security}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics?.security ?? 0}</div>
|
||||||
<p className="text-xs text-gray-600 uppercase mb-2">安全性</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">安全性</p>
|
||||||
<Progress value={result.metrics.security} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
<Progress value={result.metrics?.security ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.performance}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics?.performance ?? 0}</div>
|
||||||
<p className="text-xs text-gray-600 uppercase mb-2">性能</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">性能</p>
|
||||||
<Progress value={result.metrics.performance} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
<Progress value={result.metrics?.performance ?? 0} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue