refactor(classes): actions 迁移至 invalidateFor 集中编排

This commit is contained in:
SpecialX
2026-07-05 18:38:58 +08:00
parent 220d702b44
commit 80dd67780c
6 changed files with 42 additions and 56 deletions

View File

@@ -1,6 +1,6 @@
"use server" "use server"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { getTranslations } from "next-intl/server" import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { handleActionError } from "@/shared/lib/action-utils" import { handleActionError } from "@/shared/lib/action-utils"
@@ -55,10 +55,7 @@ export async function createAdminClassAction(
homeroom: homeroom ?? null, homeroom: homeroom ?? null,
room: room ?? null, room: room ?? null,
}) })
revalidatePath("/admin/school/classes") await invalidateFor("classes.create")
revalidatePath("/teacher/classes/my")
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classCreated"), data: id } return { success: true, message: t("actions.classCreated"), data: id }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -114,10 +111,7 @@ export async function updateAdminClassAction(
}) })
} }
revalidatePath("/admin/school/classes") await invalidateFor("classes.update", { id: validatedClassId })
revalidatePath("/teacher/classes/my")
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classUpdated") } return { success: true, message: t("actions.classUpdated") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -139,10 +133,7 @@ export async function deleteAdminClassAction(classId: string): Promise<ActionSta
try { try {
await deleteAdminClass(parsed.data.classId) await deleteAdminClass(parsed.data.classId)
revalidatePath("/admin/school/classes") await invalidateFor("classes.delete", { id: parsed.data.classId })
revalidatePath("/teacher/classes/my")
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classDeleted") } return { success: true, message: t("actions.classDeleted") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)

View File

@@ -1,6 +1,6 @@
"use server" "use server"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { getTranslations } from "next-intl/server" import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { handleActionError } from "@/shared/lib/action-utils" import { handleActionError } from "@/shared/lib/action-utils"
@@ -63,7 +63,7 @@ export async function createGradeClassAction(
homeroom: homeroom ?? null, homeroom: homeroom ?? null,
room: room ?? null, room: room ?? null,
}) })
revalidatePath("/management/grade/classes") await invalidateFor("classes.create")
return { success: true, message: t("actions.classCreated"), data: id } return { success: true, message: t("actions.classCreated"), data: id }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -138,7 +138,7 @@ export async function updateGradeClassAction(
}) })
} }
revalidatePath("/management/grade/classes") await invalidateFor("classes.update", { id: validatedClassId })
return { success: true, message: t("actions.classUpdated") } return { success: true, message: t("actions.classUpdated") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -173,7 +173,7 @@ export async function deleteGradeClassAction(classId: string): Promise<ActionSta
try { try {
await deleteAdminClass(validatedClassId) await deleteAdminClass(validatedClassId)
revalidatePath("/management/grade/classes") await invalidateFor("classes.delete", { id: validatedClassId })
return { success: true, message: t("actions.classDeleted") } return { success: true, message: t("actions.classDeleted") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)

View File

@@ -1,6 +1,6 @@
"use server" "use server"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { getTranslations } from "next-intl/server" import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { handleActionError } from "@/shared/lib/action-utils" import { handleActionError } from "@/shared/lib/action-utils"
@@ -60,8 +60,7 @@ export async function enrollStudentByEmailAction(
try { try {
await enrollStudentByEmail(parsed.data.classId, parsed.data.email) await enrollStudentByEmail(parsed.data.classId, parsed.data.email)
revalidatePath("/teacher/classes/students") await invalidateFor("classes.students.update", { classId: parsed.data.classId })
revalidatePath("/teacher/classes/my")
return { success: true, message: t("actions.studentAdded") } return { success: true, message: t("actions.studentAdded") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -130,13 +129,7 @@ export async function joinClassByInvitationCodeAction(
}, },
}) })
if (hasStudentScope(ctx)) { await invalidateFor("classes.students.update", { classId })
revalidatePath("/student/learning/courses")
revalidatePath("/student/schedule")
} else {
revalidatePath("/teacher/classes/my")
}
revalidatePath("/profile")
return { success: true, message: t("actions.joinedClass"), data: { classId } } return { success: true, message: t("actions.joinedClass"), data: { classId } }
} catch (error) { } catch (error) {
// 审计日志:加入失败 // 审计日志:加入失败
@@ -170,8 +163,7 @@ export async function ensureClassInvitationCodeAction(classId: string): Promise<
try { try {
const code = await ensureClassInvitationCode(classId) const code = await ensureClassInvitationCode(classId)
revalidatePath("/teacher/classes/my") await invalidateFor("classes.invitation.create", { classId })
revalidatePath(`/teacher/classes/my/${encodeURIComponent(classId)}`)
return { success: true, message: t("actions.invitationCodeReady"), data: { code } } return { success: true, message: t("actions.invitationCodeReady"), data: { code } }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -192,8 +184,7 @@ export async function regenerateClassInvitationCodeAction(classId: string): Prom
try { try {
const code = await regenerateClassInvitationCode(classId) const code = await regenerateClassInvitationCode(classId)
revalidatePath("/teacher/classes/my") await invalidateFor("classes.invitation.create", { classId })
revalidatePath(`/teacher/classes/my/${encodeURIComponent(classId)}`)
return { success: true, message: t("actions.invitationCodeUpdated"), data: { code } } return { success: true, message: t("actions.invitationCodeUpdated"), data: { code } }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -267,9 +258,7 @@ export async function createClassInvitationCodeAction(
}, },
}) })
revalidatePath("/teacher/classes/my") await invalidateFor("classes.invitation.create", { classId })
revalidatePath(`/teacher/classes/my/${encodeURIComponent(classId)}`)
revalidatePath(`/admin/school/classes`)
return { return {
success: true, success: true,
message: t("actions.invitationCodeGenerated"), message: t("actions.invitationCodeGenerated"),
@@ -300,7 +289,8 @@ export async function revokeClassInvitationCodeAction(
} }
try { try {
const { revokeInvitationCode } = await import("./data-access-invitations") const { revokeInvitationCode, getClassIdByCodeId } = await import("./data-access-invitations")
const classId = await getClassIdByCodeId(codeId)
await revokeInvitationCode(codeId, ctx.userId) await revokeInvitationCode(codeId, ctx.userId)
const { logAudit } = await import("@/shared/lib/audit-logger") const { logAudit } = await import("@/shared/lib/audit-logger")
@@ -312,8 +302,9 @@ export async function revokeClassInvitationCodeAction(
detail: { revokedBy: ctx.userId }, detail: { revokedBy: ctx.userId },
}) })
revalidatePath("/teacher/classes/my") if (classId) {
revalidatePath(`/admin/school/classes`) await invalidateFor("classes.invitation.revoke", { classId })
}
return { success: true, message: t("actions.invitationCodeRevoked") } return { success: true, message: t("actions.invitationCodeRevoked") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -383,8 +374,7 @@ export async function setStudentEnrollmentStatusAction(
try { try {
await setStudentEnrollmentStatus(classId, studentId, status) await setStudentEnrollmentStatus(classId, studentId, status)
revalidatePath("/teacher/classes/students") await invalidateFor("classes.students.update", { classId })
revalidatePath("/teacher/classes/my")
return { success: true, message: t("actions.studentUpdated") } return { success: true, message: t("actions.studentUpdated") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -448,8 +438,7 @@ export async function bulkEnrollStudentsAction(
} }
} }
revalidatePath("/teacher/classes/students") await invalidateFor("classes.students.update", { classId })
revalidatePath("/admin/school/classes")
return { return {
success: true, success: true,
message: t("actions.bulkImportResult", { imported, failed }), message: t("actions.bulkImportResult", { imported, failed }),
@@ -510,7 +499,7 @@ export async function bulkAssignSubjectTeachersAction(
} }
} }
revalidatePath("/admin/school/classes") // 当前为 stub 实现(无 DB 写入),暂不触发缓存失效
return { return {
success: true, success: true,
message: t("actions.bulkAssignResult", { updated, failed }), message: t("actions.bulkAssignResult", { updated, failed }),

View File

@@ -1,6 +1,6 @@
"use server" "use server"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { getTranslations } from "next-intl/server" import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
@@ -72,7 +72,7 @@ export async function createClassScheduleItemAction(
course, course,
location: location ?? null, location: location ?? null,
}) })
revalidatePath("/teacher/classes/schedule") await invalidateFor("classes.schedule.update", { classId })
return { success: true, message: t("actions.scheduleItemCreated"), data: id } return { success: true, message: t("actions.scheduleItemCreated"), data: id }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -126,7 +126,7 @@ export async function updateClassScheduleItemAction(
course: course ?? undefined, course: course ?? undefined,
location: location ?? undefined, location: location ?? undefined,
}) })
revalidatePath("/teacher/classes/schedule") await invalidateFor("classes.schedule.update", { classId: targetClassId })
return { success: true, message: t("actions.scheduleItemUpdated") } return { success: true, message: t("actions.scheduleItemUpdated") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -156,7 +156,7 @@ export async function deleteClassScheduleItemAction(scheduleId: string): Promise
try { try {
await deleteClassScheduleItem(parsed.data.scheduleId) await deleteClassScheduleItem(parsed.data.scheduleId)
revalidatePath("/teacher/classes/schedule") await invalidateFor("classes.schedule.update", { classId: targetClassId })
return { success: true, message: t("actions.scheduleItemDeleted") } return { success: true, message: t("actions.scheduleItemDeleted") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)

View File

@@ -1,6 +1,6 @@
"use server" "use server"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { getTranslations } from "next-intl/server" import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
@@ -67,9 +67,7 @@ export async function createTeacherClassAction(
homeroom: homeroom ?? null, homeroom: homeroom ?? null,
room: room ?? null, room: room ?? null,
}) })
revalidatePath("/teacher/classes/my") await invalidateFor("classes.create")
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classCreated"), data: id } return { success: true, message: t("actions.classCreated"), data: id }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -122,9 +120,7 @@ export async function updateTeacherClassAction(
homeroom: homeroom ?? undefined, homeroom: homeroom ?? undefined,
room: room ?? undefined, room: room ?? undefined,
}) })
revalidatePath("/teacher/classes/my") await invalidateFor("classes.update", { id: validatedClassId })
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classUpdated") } return { success: true, message: t("actions.classUpdated") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)
@@ -154,9 +150,7 @@ export async function deleteTeacherClassAction(classId: string): Promise<ActionS
try { try {
await deleteTeacherClass(parsed.data.classId) await deleteTeacherClass(parsed.data.classId)
revalidatePath("/teacher/classes/my") await invalidateFor("classes.delete", { id: parsed.data.classId })
revalidatePath("/teacher/classes/students")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: t("actions.classDeleted") } return { success: true, message: t("actions.classDeleted") }
} catch (error) { } catch (error) {
return handleActionError(error) return handleActionError(error)

View File

@@ -307,6 +307,18 @@ export async function consumeInvitationCode(code: string): Promise<void> {
.where(eq(classInvitationCodes.code, normalized)) .where(eq(classInvitationCodes.code, normalized))
} }
/**
* 根据邀请码 ID 查询所属班级 ID用于缓存失效参数填充
*/
export async function getClassIdByCodeId(codeId: string): Promise<string | null> {
const [record] = await db
.select({ classId: classInvitationCodes.classId })
.from(classInvitationCodes)
.where(eq(classInvitationCodes.id, codeId))
.limit(1)
return record?.classId ?? null
}
/** /**
* 撤销邀请码(软删除)。 * 撤销邀请码(软删除)。
*/ */