import "server-only" /** * 监控埋点接口(预留) * * 在关键 Server Action 中调用 trackEvent 记录业务事件,用于: * - 公告阅读率、消息回复率等关键指标统计 * - 通知发送失败告警 * - 用户行为漏斗分析 * * 当前实现:输出到 console.info,不阻塞主流程。 * 后续扩展:可接入外部监控服务(如 Sentry / PostHog / 自建埋点系统), * 只需在 trackEventToSink 中替换实现即可,调用方无需改动。 */ /** 事件名称(使用点号分隔的命名空间,如 "announcement.published") */ export type EventName = | "announcement.created" | "announcement.updated" | "announcement.published" | "announcement.archived" | "announcement.deleted" | "announcement.pin_toggled" | "announcement.marked_read" | "message.sent" | "message.deleted" | "message.marked_read" | "message.star_toggled" | "notification.marked_read" | "notification.marked_all_read" | "notification.sent" | "notification.send_failed" | "notification.archived" | "attendance.recorded" | "attendance.batch_recorded" | "attendance.updated" | "attendance.deleted" | "attendance.rules_saved" | "elective.course_created" | "elective.course_updated" | "elective.course_deleted" | "elective.selection_opened" | "elective.selection_closed" | "elective.course_selected" | "elective.course_dropped" | "elective.lottery_completed" // 6.7: 考试/作业模块监控事件 | "exam.created" | "exam.updated" | "exam.published" | "exam.archived" | "exam.deleted" | "exam.duplicated" | "exam.ai_generated" | "exam.submitted" | "exam.graded" | "homework.created" | "homework.updated" | "homework.published" | "homework.archived" | "homework.deleted" | "homework.submitted" | "homework.graded" | "homework.auto_save_failed" // AI 模块监控事件 | "ai.chat" | "ai.chat_stream" | "ai.similar_question" | "ai.grading_assist" | "ai.lesson_content" | "ai.question_variant" | "ai.weakness_analysis" | "ai.child_summary" | "ai.study_path" /** 埋点事件负载 */ export interface TrackEventPayload { /** 事件名称 */ event: EventName /** 当前用户 ID(可选,未登录场景为 undefined) */ userId?: string /** 目标对象 ID(如公告 ID、消息 ID) */ targetId?: string /** 目标对象类型(如 "announcement"、"message") */ targetType?: string /** 附加属性(如受众人数、渠道类型) */ properties?: Record } /** * 将事件发送到外部监控服务。 * * 当前为占位实现:仅输出到 console.info。 * 接入真实服务时替换此函数体即可。 */ function trackEventToSink(payload: TrackEventPayload): void { console.info( `[TrackEvent] ${payload.event} userId=${payload.userId ?? "-"} targetId=${payload.targetId ?? "-"}${payload.properties ? ` properties=${JSON.stringify(payload.properties)}` : ""}` ) } /** * 记录一个监控事件。 * * 非阻塞:任何异常都被吞掉,确保不影响主业务流程。 * * @example * ```ts * await trackEvent({ * event: "announcement.published", * userId: ctx.userId, * targetId: id, * targetType: "announcement", * properties: { audienceSize: userIds.length }, * }) * ``` */ export async function trackEvent(payload: TrackEventPayload): Promise { try { trackEventToSink(payload) } catch { // 埋点失败不影响主流程 } } /** * 6.7: 考试/作业模块专用埋点函数 * * 封装 trackEvent,自动设置 targetType,简化调用方代码。 * * @example * ```ts * await trackExamEvent("exam.published", { userId: ctx.userId, targetId: examId }) * await trackExamEvent("homework.submitted", { * userId: ctx.userId, * targetId: submissionId, * properties: { questionCount, duration: 1200 } * }) * ``` */ export async function trackExamEvent( event: Extract, params: { userId?: string targetId?: string properties?: Record } ): Promise { const targetType = event.startsWith("exam.") ? "exam" : "homework" await trackEvent({ event, userId: params.userId, targetId: params.targetId, targetType, properties: params.properties, }) }