fix: code compliance audit and fix across all services
Some checks failed
CI / quality-ts (push) Failing after 48s
CI / quality-go (push) Failing after 4s
CI / quality-proto (push) Failing after 2s
CI / deploy (push) Has been skipped

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:
SpecialX
2026-07-09 17:28:27 +08:00
parent b53a486c6e
commit 0a71b02e04
93 changed files with 5775 additions and 608 deletions

View File

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

View File

@@ -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 brokersCDC 消费;留空则不启动消费者)
# 主机访问用 localhost:9092容器内访问用 kafka:29092
kafka_brokers: str = ""

View File

@@ -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. 初始化 loggerstructlog
@@ -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)