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:
lintsinghua 2025-12-09 17:47:34 +08:00
parent 4f95f65645
commit 14b7c8cccc
14 changed files with 74 additions and 59 deletions

View File

@ -46,7 +46,7 @@
| 项目管理 | 审计报告 |
|:---:|:---:|
| ![项目管理](frontend/public/images/example3.png) | ![审计报告](frontend/public/images/审计报告示例.png) |
| <img src="frontend/public/images/example3.png" alt="项目管理" width="400"> | <img src="frontend/public/images/审计报告示例.png" alt="审计报告" width="400"> |
| *GitHub/GitLab 无缝集成* | *专业报告,一键导出* |
## ✨ 为什么选择我们?

View File

@ -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,

View File

@ -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": "项目已恢复"}

View File

@ -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

View File

@ -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",

View File

@ -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)}

View File

@ -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 = [

View File

@ -215,14 +215,16 @@ class LLMService:
2. JSON字符串值中的特殊字符必须正确转义换行用\\n双引号用\\",反斜杠用\\\\
3. code_snippet字段必须使用\\n表示换行
请从以下维度全面分析代码
- 编码规范和代码风格
请从以下维度全面彻底地分析代码找出所有问题
- 安全漏洞SQL注入XSS命令注入路径遍历SSRFXXE反序列化硬编码密钥等
- 潜在的 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
IMPORTANTFind 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}"

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

After

Width:  |  Height:  |  Size: 404 KiB

View File

@ -159,15 +159,15 @@ export default function InstantExportDialog({
</div>
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<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 className="flex items-center justify-between border-b border-gray-200 pb-1">
<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 className="flex items-center justify-between border-b border-gray-200 pb-1">
<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>

View File

@ -633,7 +633,7 @@ class UserManager {
'bg-red-100 text-red-800'
}`}
>
: {record.quality_score.toFixed(1)}
: {(record.quality_score ?? 0).toFixed(1)}
</Badge>
<Button
variant="ghost"
@ -653,7 +653,7 @@ class UserManager {
</span>
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{record.analysis_time.toFixed(2)}s
{(record.analysis_time ?? 0).toFixed(2)}s
</span>
</div>
</div>
@ -827,7 +827,7 @@ class UserManager {
<div className="flex items-center gap-2">
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
<Clock className="w-3 h-3 mr-1" />
{analysisTime.toFixed(2)}s
{(analysisTime ?? 0).toFixed(2)}s
</Badge>
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
{language}
@ -852,10 +852,10 @@ class UserManager {
<Target className="w-6 h-6" />
</div>
<div className="text-3xl font-bold text-primary mb-1">
{result.quality_score.toFixed(1)}
{(result.quality_score ?? 0).toFixed(1)}
</div>
<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 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" />
</div>
<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>
<p className="text-xs font-bold text-red-700 uppercase mb-1"></p>
<div className="text-xs text-red-600 font-bold uppercase"></div>
@ -874,7 +874,7 @@ class UserManager {
<Info className="w-6 h-6" />
</div>
<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>
<p className="text-xs font-bold text-yellow-700 uppercase mb-1"></p>
<div className="text-xs text-yellow-600 font-bold uppercase"></div>
@ -900,24 +900,24 @@ class UserManager {
</h3>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
<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>
<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 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>
<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 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>
<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 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>
<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>