feat(data-ana): v2 P6 硬化完成 + 6 新 RPC + Prometheus 监控
P6 硬化(5 项全部完成):
- CDC 多实例水平扩展: _INSTANCE_ID + get_lag() 真实 lag 计算
- ExamCache Redis 化: key data_ana:exam:{exam_id}, TTL 30 天 + 内存 LRU fallback
- ClickHouse TTL 归档: 5 表均加 TTL(1-3 年),分区级删除
- Prometheus 监控: 18 个指标(CDC/CH/ExamCache/DataScope/gRPC/业务)
- readyz 深度硬化: 4 依赖超时检查(CH 1s/Redis 200ms/iam 2s/CDC lag<1000)
v2 新增 6 个 RPC(analytics.proto 扩展为 18 RPC):
- GetStudentGrowth / GetAssignmentAnalysis / GetMasterySummary
- ListDiagnosticReports(占位,待 ai 服务)/ ListErrorBookItems / GetErrorBookStats
监控与可观测性: lifespan 预热 + gRPC ServerInterceptor + CDC 消费者指标
Docker 本地测试 19 项全部通过(healthz/readyz/metrics + 11 HTTP + 10 gRPC + ruff)
nextstep-v2.md: 上游需求对齐 + 下游要求(iam/core-edu/content/ai/SRE)
This commit is contained in:
@@ -111,6 +111,42 @@ class AnalyticsServiceStub:
|
||||
response_deserializer=analytics__pb2.MasteryUpdateEvent.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.GetStudentGrowth = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentGrowth",
|
||||
request_serializer=analytics__pb2.GetStudentGrowthRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.StudentGrowth.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.GetAssignmentAnalysis = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetAssignmentAnalysis",
|
||||
request_serializer=analytics__pb2.GetAssignmentAnalysisRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.AssignmentAnalysis.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.GetMasterySummary = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetMasterySummary",
|
||||
request_serializer=analytics__pb2.GetMasterySummaryRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.MasterySummary.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.ListDiagnosticReports = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/ListDiagnosticReports",
|
||||
request_serializer=analytics__pb2.ListDiagnosticReportsRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.DiagnosticReportList.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.ListErrorBookItems = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/ListErrorBookItems",
|
||||
request_serializer=analytics__pb2.ListErrorBookItemsRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.ErrorBookList.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
self.GetErrorBookStats = channel.unary_unary(
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetErrorBookStats",
|
||||
request_serializer=analytics__pb2.GetErrorBookStatsRequest.SerializeToString,
|
||||
response_deserializer=analytics__pb2.ErrorBookStats.FromString,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
|
||||
class AnalyticsServiceServicer:
|
||||
@@ -191,6 +227,44 @@ class AnalyticsServiceServicer:
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def GetStudentGrowth(self, request, context):
|
||||
"""===== P6+ v2 扩展 RPC(响应上游 parent-bff / student-bff v2 请求) =====
|
||||
学生成长档案(综合成绩趋势 + 掌握度变化 + 考勤统计,供 parent-bff GrowthArchiveService).
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def GetAssignmentAnalysis(self, request, context):
|
||||
"""作业/考试分析(单次作业/考试维度统计,供 student-bff / parent-bff)."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def GetMasterySummary(self, request, context):
|
||||
"""学生掌握度汇总(轻量级,仅返回总体掌握度 + 三档分布,供 parent-bff MasteryService.GetSummary)."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def ListDiagnosticReports(self, request, context):
|
||||
"""诊断报告列表(占位实现,真实数据需 ai 服务集成,供 parent-bff / student-bff)."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def ListErrorBookItems(self, request, context):
|
||||
"""错题本列表(gRPC 版本,供 parent-bff ErrorBookService.List)."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
def GetErrorBookStats(self, request, context):
|
||||
"""错题本统计(按知识点聚合,供 parent-bff ErrorBookService.GetStats)."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details("Method not implemented!")
|
||||
raise NotImplementedError("Method not implemented!")
|
||||
|
||||
|
||||
def add_AnalyticsServiceServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
@@ -254,6 +328,36 @@ def add_AnalyticsServiceServicer_to_server(servicer, server):
|
||||
request_deserializer=analytics__pb2.SubscribeMasteryUpdateRequest.FromString,
|
||||
response_serializer=analytics__pb2.MasteryUpdateEvent.SerializeToString,
|
||||
),
|
||||
"GetStudentGrowth": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetStudentGrowth,
|
||||
request_deserializer=analytics__pb2.GetStudentGrowthRequest.FromString,
|
||||
response_serializer=analytics__pb2.StudentGrowth.SerializeToString,
|
||||
),
|
||||
"GetAssignmentAnalysis": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetAssignmentAnalysis,
|
||||
request_deserializer=analytics__pb2.GetAssignmentAnalysisRequest.FromString,
|
||||
response_serializer=analytics__pb2.AssignmentAnalysis.SerializeToString,
|
||||
),
|
||||
"GetMasterySummary": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetMasterySummary,
|
||||
request_deserializer=analytics__pb2.GetMasterySummaryRequest.FromString,
|
||||
response_serializer=analytics__pb2.MasterySummary.SerializeToString,
|
||||
),
|
||||
"ListDiagnosticReports": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.ListDiagnosticReports,
|
||||
request_deserializer=analytics__pb2.ListDiagnosticReportsRequest.FromString,
|
||||
response_serializer=analytics__pb2.DiagnosticReportList.SerializeToString,
|
||||
),
|
||||
"ListErrorBookItems": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.ListErrorBookItems,
|
||||
request_deserializer=analytics__pb2.ListErrorBookItemsRequest.FromString,
|
||||
response_serializer=analytics__pb2.ErrorBookList.SerializeToString,
|
||||
),
|
||||
"GetErrorBookStats": grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetErrorBookStats,
|
||||
request_deserializer=analytics__pb2.GetErrorBookStatsRequest.FromString,
|
||||
response_serializer=analytics__pb2.ErrorBookStats.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
"next_edu_cloud.analytics.v1.AnalyticsService", rpc_method_handlers
|
||||
@@ -630,3 +734,183 @@ class AnalyticsService:
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def GetStudentGrowth(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentGrowth",
|
||||
analytics__pb2.GetStudentGrowthRequest.SerializeToString,
|
||||
analytics__pb2.StudentGrowth.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def GetAssignmentAnalysis(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetAssignmentAnalysis",
|
||||
analytics__pb2.GetAssignmentAnalysisRequest.SerializeToString,
|
||||
analytics__pb2.AssignmentAnalysis.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def GetMasterySummary(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetMasterySummary",
|
||||
analytics__pb2.GetMasterySummaryRequest.SerializeToString,
|
||||
analytics__pb2.MasterySummary.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ListDiagnosticReports(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/ListDiagnosticReports",
|
||||
analytics__pb2.ListDiagnosticReportsRequest.SerializeToString,
|
||||
analytics__pb2.DiagnosticReportList.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ListErrorBookItems(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/ListErrorBookItems",
|
||||
analytics__pb2.ListErrorBookItemsRequest.SerializeToString,
|
||||
analytics__pb2.ErrorBookList.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def GetErrorBookStats(
|
||||
request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None,
|
||||
):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
"/next_edu_cloud.analytics.v1.AnalyticsService/GetErrorBookStats",
|
||||
analytics__pb2.GetErrorBookStatsRequest.SerializeToString,
|
||||
analytics__pb2.ErrorBookStats.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user