Files
cpcheck/app/main.py
T
dekun 32c8f4b156 Initial release: CPCHECK cloud port detection tool
Web-based TCP/UDP port checker with firewall/GFW diagnosis, PM2 deployment config, and Ubuntu one-click install script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 15:24:40 +08:00

102 lines
2.8 KiB
Python

"""CPCHECK - 云服务器端口检测工具 API"""
import os
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field, field_validator
from app.config import HOST, PORT
from app.detector import CheckResult, run_check
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_DIR = BASE_DIR / "static"
app = FastAPI(
title="CPCHECK",
description="云服务器端口检测工具 - 支持 TCP/UDP 端口检测与问题诊断",
version="1.0.0",
)
if STATIC_DIR.exists():
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
class CheckRequest(BaseModel):
host: str = Field(..., min_length=1, max_length=255, description="域名或 IP 地址")
port: int = Field(..., ge=1, le=65535, description="端口号")
protocol: str = Field(default="tcp", description="协议类型: tcp 或 udp")
@field_validator("protocol")
@classmethod
def validate_protocol(cls, v: str) -> str:
v = v.lower().strip()
if v not in ("tcp", "udp"):
raise ValueError("协议必须是 tcp 或 udp")
return v
@field_validator("host")
@classmethod
def validate_host(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("主机地址不能为空")
return v
class CheckResponse(BaseModel):
success: bool
data: dict | None = None
error: str | None = None
def result_to_dict(result: CheckResult) -> dict:
return {
"host": result.host,
"port": result.port,
"protocol": result.protocol,
"resolved_ip": result.resolved_ip,
"port_status": result.port_status.value,
"diagnosis": result.diagnosis.value,
"diagnosis_message": result.diagnosis_message,
"host_reachable": result.host_reachable,
"dns_ok": result.dns_ok,
"latency_ms": result.latency_ms,
"details": result.details,
"elapsed_ms": result.elapsed_ms,
}
@app.get("/")
async def index():
index_file = STATIC_DIR / "index.html"
if index_file.exists():
return FileResponse(str(index_file))
return {"message": "CPCHECK API is running. Visit /docs for API documentation."}
@app.get("/api/health")
async def health():
return {"status": "ok", "service": "cpcheck"}
@app.post("/api/check", response_model=CheckResponse)
async def check_port(req: CheckRequest):
try:
result = run_check(req.host, req.port, req.protocol)
return CheckResponse(success=True, data=result_to_dict(result))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
def start():
import uvicorn
uvicorn.run("app.main:app", host=HOST, port=PORT, reload=False)
if __name__ == "__main__":
start()