from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
from sqlmodel import SQLModel, Field as SQLField, Session, create_engine, select
from typing import List, Optional
import time
from datetime import datetime
import json
# 数据库模型 - 定义数据表结构
class CodeReviewRecord(SQLModel, table=True):
__tablename__ = "code_reviews"
id: Optional[int] = SQLField(default=None, primary_key=True)
code_snippet: str = SQLField(max_length=10000)
language: str = SQLField(default="python")
issues_found: str # 存储JSON格式的问题列表
score: int
review_time: float # 审查耗时(秒)
created_at: datetime = SQLField(default_factory=datetime.now)
# 数据库连接配置
DATABASE_URL = "sqlite:///./code_reviews.db"
engine = create_engine(DATABASE_URL, echo=True) # echo=True 用于调试,显示SQL语句
# 创建表
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
# 依赖项:获取数据库会话
def get_session():
with Session(engine) as session:
yield session
app = FastAPI(
title="代码审查助手 API",
description="一个智能的代码质量审查服务,支持多种编程语言",
version="1.3.0" # 更新版本号
)
# 启动时创建数据库表
@app.on_event("startup")
def on_startup():
create_db_and_tables()
# 安全解析issues_found的函数
def safe_parse_issues(issues_str: str) -> List[str]:
"""安全解析存储的问题列表"""
try:
return json.loads(issues_str)
except json.JSONDecodeError:
# 如果JSON解析失败,返回原始字符串作为列表
return [issues_str]
# 请求模型保持不变
class CodeReviewRequest(BaseModel):
code: str = Field(
...,
min_length=1,
max_length=10000,
description="要审查的源代码",
example="print('Hello World')\npassword = '123456'"
)
language: str = Field(
default="python",
description="编程语言",
example="python"
)
# 增强的模拟审查函数
def mock_ai_review(code: str, language: str = "python") -> dict:
start_time = time.time()
issues = []
if language == "python":
if "print(" in code:
issues.append("第1行:在生产环境中建议使用logging库替代print语句")
if "password" in code.lower() and "=" in code.lower():
issues.append("检测到密码硬编码,存在安全风险")
if "import os" in code and "input()" in code:
issues.append("发现os导入与用户输入结合,可能存在命令注入风险")
elif language == "javascript":
if "eval(" in code:
issues.append("使用eval()函数存在安全风险,建议避免使用")
if "innerHTML" in code:
issues.append("直接操作innerHTML可能导致XSS漏洞")
# 通用代码质量检查
lines = code.split('\n')
if len(lines) > 50:
issues.append(f"代码过长({len(lines)}行),建议拆分成更小的函数")
if "TODO" in code or "FIXME" in code:
issues.append("代码中包含未完成的TODO/FIXME注释")
processing_time = time.time() - start_time
return {
"review_status": "完成",
"language": language,
"issues_found": issues if issues else ["代码结构良好,未发现明显问题"],
"suggestions": [
"建议添加详细的代码注释",
"考虑添加单元测试"
],
"score": max(60, 100 - len(issues) * 10),
"metrics": {
"lines_of_code": len(lines),
"issue_count": len(issues),
"analysis_time": f"{processing_time:.2f}s"
},
"processing_time": processing_time
}
@app.post(
"/review",
summary="提交代码审查",
description="接收源代码并进行智能审查,返回代码质量问题和建议,并保存审查记录到数据库",
response_description="代码审查结果"
)
def create_review(request: CodeReviewRequest, session: Session = Depends(get_session)):
"""
提交代码进行审查,结果将保存到数据库:
"""
try:
start_time = time.time()
# 调用模拟审查函数
review_result = mock_ai_review(request.code, request.language.lower())
# 使用JSON安全序列化issues_found
issues_json = json.dumps(review_result["issues_found"], ensure_ascii=False)
# 创建数据库记录
db_record = CodeReviewRecord(
code_snippet=request.code[:500] + ("..." if len(request.code) > 500 else ""), # 只保存前500字符
language=request.language,
issues_found=issues_json, # 使用JSON而不是str()
score=review_result["score"],
review_time=review_result["processing_time"]
)
# 保存到数据库
session.add(db_record)
session.commit()
session.refresh(db_record)
processing_time = time.time() - start_time
return {
"status": "success",
"request_info": {
"language": request.language,
"code_length": len(request.code),
"timestamp": time.time(),
"record_id": db_record.id # 返回数据库记录ID
},
"review": review_result,
"database_info": {
"record_saved": True,
"record_id": db_record.id,
"total_processing_time": f"{processing_time:.2f}s"
}
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"审查过程中发生错误: {str(e)}"
)
# 增强的获取审查历史记录接口 - 支持高级查询
@app.get(
"/reviews",
summary="获取审查历史",
description="支持按语言、分数范围过滤和分页查询审查记录"
)
def get_review_history(
session: Session = Depends(get_session),
language: Optional[str] = None,
min_score: Optional[int] = None,
max_score: Optional[int] = None,
limit: int = 10,
offset: int = 0
):
# 构建基础查询
statement = select(CodeReviewRecord)
# 添加过滤条件
if language:
statement = statement.where(CodeReviewRecord.language == language)
if min_score is not None:
statement = statement.where(CodeReviewRecord.score >= min_score)
if max_score is not None:
statement = statement.where(CodeReviewRecord.score <= max_score)
# 排序和分页
statement = statement.order_by(CodeReviewRecord.created_at.desc())
statement = statement.offset(offset).limit(limit)
reviews = session.exec(statement).all()
return {
"status": "success",
"filters": {
"language": language,
"min_score": min_score,
"max_score": max_score
},
"pagination": {
"limit": limit,
"offset": offset,
"total": len(reviews)
},
"reviews": [
{
"id": review.id,
"language": review.language,
"code_snippet_preview": review.code_snippet[:100] + ("..." if len(review.code_snippet) > 100 else ""),
"score": review.score,
"issues_count": len(safe_parse_issues(review.issues_found)),
"review_time": f"{review.review_time:.2f}s",
"created_at": review.created_at.isoformat()
}
for review in reviews
]
}
# 获取单条审查记录详情
@app.get(
"/reviews/{review_id}",
summary="获取审查记录详情",
description="根据ID获取特定的代码审查记录"
)
def get_review_detail(review_id: int, session: Session = Depends(get_session)):
review = session.get(CodeReviewRecord, review_id)
if not review:
raise HTTPException(status_code=404, detail="审查记录未找到")
return {
"status": "success",
"review": {
"id": review.id,
"code_snippet": review.code_snippet,
"language": review.language,
"issues_found": safe_parse_issues(review.issues_found),
"score": review.score,
"review_time": f"{review.review_time:.2f}s",
"created_at": review.created_at.isoformat()
}
}
# 新增:统计接口
@app.get(
"/stats",
summary="审查统计信息",
description="获取代码审查的统计数据和洞察"
)
def get_review_stats(session: Session = Depends(get_session)):
# 获取所有记录
all_reviews = session.exec(select(CodeReviewRecord)).all()
if not all_reviews:
return {
"status": "success",
"message": "暂无审查数据",
"stats": {}
}
# 计算统计信息
total_reviews = len(all_reviews)
scores = [review.score for review in all_reviews]
review_times = [review.review_time for review in all_reviews]
# 按语言分组统计
language_stats = {}
for review in all_reviews:
lang = review.language
if lang not in language_stats:
language_stats[lang] = {
"count": 0,
"avg_score": 0,
"scores": []
}
language_stats[lang]["count"] += 1
language_stats[lang]["scores"].append(review.score)
# 计算平均值
for lang in language_stats:
lang_scores = language_stats[lang]["scores"]
language_stats[lang]["avg_score"] = round(sum(lang_scores) / len(lang_scores), 2)
del language_stats[lang]["scores"] # 清理临时数据
return {
"status": "success",
"stats": {
"total_reviews": total_reviews,
"average_score": round(sum(scores) / len(scores), 2),
"score_distribution": {
"min": min(scores),
"max": max(scores),
"avg": round(sum(scores) / len(scores), 2)
},
"performance": {
"avg_review_time": round(sum(review_times) / len(review_times), 2),
"total_review_time": round(sum(review_times), 2)
},
"by_language": language_stats,
"first_review": min([r.created_at for r in all_reviews]).isoformat(),
"last_review": max([r.created_at for r in all_reviews]).isoformat()
}
}
@app.get(
"/stats",
summary="审查统计信息",
description="获取代码审查的统计数据和洞察"
)
def get_review_stats(session:Session=Depends(get_session)):
#获取所有审查记录
all_reviews=session.exec(select(CodeReviewRecord)).all()
if not all_reviews: #审查记录为空
return{
"status":"success",
"message":"暂无审查数据",
"stats":{}
}
#计算基础统计信息
total_reviews=len(all_reviews)
scores=[review.score for review in all_reviews]
review_times=[review.review_time for review in all_reviews]
#按语言分组统计
language_stats={}
for review in all_reviews:
lang=review.language
if lang not in language_stats:
language_stats[lang]={
"count":0,
"avg_score":0,
"scores":[]
}
language_stats[lang]["count"]+=1
language_stats[lang]["scores"].append(review.score)
#计算各语言的平均分数
for lang in language_stats:
lang_scores=language_stats[lang]["scores"]
language_stats[lang]["avg_score"]=round(sum(lang_scores) / len(lang_scores),2)
del language_stats[lang]["scores"]
return{
"status":"success",
"stats":{
"total_reviews":total_reviews,
"average_score":round(sum(lang_scores) / len(lang_scores),2),
"score_distribution":{
"min":min(scores),
"max":max(scores),
"avg":round(sum(lang_scores) / len(lang_scores),2)
},
"performance":{
"avg_review_time":round(sum(review_times) / len(review_times),2),
"total_review_time":round(sum(review_times),2)
},
"by_language":language_stats,
"first_review":min([r.created_at for r in all_reviews]).isoformat(),
"last_review":max([r.created_at for r in all_reviews]).isoformat()
}
}
@app.get("/", summary="服务状态", description="检查API服务状态")
def read_root():
return {
"message": "代码审查服务已启动",
"version": "1.3.0",
"features": [
"代码质量审查",
"多语言支持",
"审查历史记录",
"数据库持久化",
"高级查询过滤",
"数据统计分析",
"记录管理功能"
],
"endpoints": {
"POST /review": "提交代码审查",
"GET /reviews": "获取审查历史(支持过滤)",
"GET /reviews/{id}": "获取详细记录",
"GET /stats": "查看统计信息",
"DELETE /reviews/{id}": "删除记录",
"GET /health": "健康检查"
}
}
@app.get("/health", summary="健康检查", description="检查服务健康状态")
def health_check():
return {
"status": "healthy", # 修复拼写错误
"service": "code-review-api",
"timestamp": time.time(),
"version": "1.3.0",
"database": "connected"
}
#添加数据清理功能
@app.delete(
"/reviews/{review_id}",
summary="删除审查记录",
description="删除指定的审查记录"
)
def delete_review(review_id:int,session:Session=Depends(get_session)):
review=session.get(CodeReviewRecord,review_id)
if not review:
raise HTTPException(status_code=404,detail="审查记录未找到")
session.delete(review)
session.commit()
return{
"status":"success",
"message":f"记录{review_id}已删除",
"delete_record":{
"id":review.id,
"language":review.language,
"score":review.score
}
}