fix: code compliance audit and fix across all services
NestJS (6 services): implement @RequirePermission decorator with SetMetadata+Reflector, register APP_GUARD globally, fix as assertions to type guards, add explicit return types, fix import type for express, fix /metrics implicit any, replace native Error with ApplicationError, remove typeorm remnants, register LifecycleService. teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward real userId to downstream, log downstream failures, migrate health controller to shared/health. Go (2 services): interface to any, doc comments, CORS dev whitelist, JWT secret fail-fast, push-gateway internal API auth, metrics and readyz endpoints, remove dead code. Python (2 services): lifespan return type, dev_mode to bool, data-ana APIRouter, ai POST body model, ClickHouse async wrapping.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
保证服务在 ClickHouse 不可用时仍可启动并响应骨架数据。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
@@ -68,7 +69,7 @@ async def close_client() -> None:
|
||||
global _client, _client_initialized
|
||||
if _client is not None:
|
||||
try:
|
||||
_client.close()
|
||||
await asyncio.to_thread(_client.close)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("clickhouse_client_close_failed", error=str(exc))
|
||||
finally:
|
||||
@@ -86,7 +87,8 @@ async def query_dashboard(student_id: str) -> dict | None:
|
||||
return None
|
||||
|
||||
try:
|
||||
rows = client.query(
|
||||
result = await asyncio.to_thread(
|
||||
client.query,
|
||||
"SELECT student_id, class_id, exam_id, subject_id, score, "
|
||||
"rank_in_class, knowledge_point_id, mastery_level, error_count, "
|
||||
"last_updated "
|
||||
@@ -95,7 +97,8 @@ async def query_dashboard(student_id: str) -> dict | None:
|
||||
"ORDER BY last_updated DESC "
|
||||
"LIMIT 50",
|
||||
parameters={"sid": student_id},
|
||||
).result_rows
|
||||
)
|
||||
rows = result.result_rows
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("query_dashboard_failed_degraded", error=str(exc), student_id=student_id)
|
||||
return None
|
||||
@@ -131,7 +134,8 @@ async def query_class_performance(class_id: str) -> dict | None:
|
||||
|
||||
try:
|
||||
# 平均分、参考人数、及格率(>=60)
|
||||
agg_rows = client.query(
|
||||
result = await asyncio.to_thread(
|
||||
client.query,
|
||||
"SELECT "
|
||||
" count() AS total_students, "
|
||||
" avg(score) AS average_score, "
|
||||
@@ -139,7 +143,8 @@ async def query_class_performance(class_id: str) -> dict | None:
|
||||
"FROM student_dashboard_view "
|
||||
"WHERE class_id = {cid:String}",
|
||||
parameters={"cid": class_id},
|
||||
).result_rows
|
||||
)
|
||||
agg_rows = result.result_rows
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning(
|
||||
"query_class_performance_failed_degraded",
|
||||
@@ -175,7 +180,8 @@ async def query_student_errors(student_id: str) -> list[dict] | None:
|
||||
return None
|
||||
|
||||
try:
|
||||
rows = client.query(
|
||||
result = await asyncio.to_thread(
|
||||
client.query,
|
||||
"SELECT student_id, question_id, knowledge_point_id, error_count, "
|
||||
"last_error_time, content "
|
||||
"FROM student_errors "
|
||||
@@ -183,7 +189,8 @@ async def query_student_errors(student_id: str) -> list[dict] | None:
|
||||
"ORDER BY last_error_time DESC "
|
||||
"LIMIT 100",
|
||||
parameters={"sid": student_id},
|
||||
).result_rows
|
||||
)
|
||||
rows = result.result_rows
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning(
|
||||
"query_student_errors_failed_degraded",
|
||||
@@ -212,7 +219,7 @@ async def ping() -> bool:
|
||||
if client is None:
|
||||
return False
|
||||
try:
|
||||
client.query("SELECT 1")
|
||||
await asyncio.to_thread(client.query, "SELECT 1")
|
||||
return True
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("clickhouse_ping_failed", error=str(exc))
|
||||
@@ -241,7 +248,8 @@ async def upsert_student_dashboard(
|
||||
return False
|
||||
|
||||
try:
|
||||
client.insert(
|
||||
await asyncio.to_thread(
|
||||
client.insert,
|
||||
"student_dashboard_view",
|
||||
[
|
||||
[
|
||||
@@ -305,7 +313,8 @@ async def upsert_student_error(
|
||||
return False
|
||||
|
||||
try:
|
||||
client.insert(
|
||||
await asyncio.to_thread(
|
||||
client.insert,
|
||||
"student_errors",
|
||||
[
|
||||
[
|
||||
|
||||
@@ -23,8 +23,8 @@ class Settings(BaseSettings):
|
||||
# 可观测性
|
||||
otel_endpoint: str = "http://localhost:4318"
|
||||
log_level: str = "info"
|
||||
# 开发模式开关("true"/"false")
|
||||
dev_mode: str = "false"
|
||||
# 开发模式开关
|
||||
dev_mode: bool = False
|
||||
# Kafka brokers(CDC 消费;留空则不启动消费者)
|
||||
# 主机访问用 localhost:9092,容器内访问用 kafka:29092
|
||||
kafka_brokers: str = ""
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import structlog
|
||||
from fastapi import FastAPI
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
||||
@@ -89,7 +90,7 @@ def init_tracer() -> None:
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""应用生命周期.
|
||||
|
||||
1. 初始化 logger(structlog)
|
||||
@@ -133,6 +134,9 @@ FastAPIInstrumentor.instrument_app(app)
|
||||
# Prometheus 指标
|
||||
app.mount("/metrics", make_asgi_app())
|
||||
|
||||
# 业务路由
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
async def healthz() -> dict:
|
||||
@@ -189,7 +193,7 @@ async def readyz() -> dict:
|
||||
}
|
||||
|
||||
|
||||
@app.get("/analytics/class/{class_id}/performance")
|
||||
@router.get("/analytics/class/{class_id}/performance")
|
||||
async def class_performance(class_id: str) -> dict:
|
||||
"""班级成绩分析.
|
||||
|
||||
@@ -215,7 +219,7 @@ async def class_performance(class_id: str) -> dict:
|
||||
return {"success": True, "data": {**result, "degraded": False}}
|
||||
|
||||
|
||||
@app.get("/analytics/student/{student_id}/weakness")
|
||||
@router.get("/analytics/student/{student_id}/weakness")
|
||||
async def student_weakness(student_id: str) -> dict:
|
||||
"""学生薄弱知识点分析.
|
||||
|
||||
@@ -259,7 +263,7 @@ async def student_weakness(student_id: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@app.get("/analytics/student/{student_id}/errorbook")
|
||||
@router.get("/analytics/student/{student_id}/errorbook")
|
||||
async def student_errorbook(student_id: str) -> dict:
|
||||
"""学生错题本.
|
||||
|
||||
@@ -290,3 +294,6 @@ async def student_errorbook(student_id: str) -> dict:
|
||||
"degraded": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
Reference in New Issue
Block a user