代码审查API

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
        }
    }
posted @ 2025-10-29 21:18  神也忧伤  阅读(3)  评论(0)    收藏  举报