Files
NextEdu/src/modules/elective/data-access-settings.ts

108 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "server-only"
import { eq, and } from "drizzle-orm"
import { db } from "@/shared/db"
import { systemSettings } from "@/shared/db/schema"
import { cacheFn } from "@/shared/lib/cache"
/**
* 选课模块配置化设置P2-4 新增)。
*
* 设计原则:
* - 复用全局 `system_settings` 表category="elective"),避免新增独立表
* - 支持按年级覆盖key=`creditLimit:grade:<gradeId>`fallback 到全局key=`creditLimit:default`
* - 用 React `cache()` 包装,单次请求内去重
* - 配置缺失时使用默认值(向后兼容)
*
* 配置项:
* - `creditLimit:default` / `creditLimit:grade:<gradeId>`:学期学分上限(默认 10
* - `capacityNotifyThreshold`:容量阈值通知比例 0-1默认 0.9
*/
const SETTINGS_CATEGORY = "elective"
/** 默认学期学分上限K12 选修课) */
const DEFAULT_MAX_CREDIT_PER_TERM = 10
/** 默认容量阈值通知比例90% */
const DEFAULT_CAPACITY_NOTIFY_THRESHOLD = 0.9
/**
* 读取 system_settings 中指定 key 的值。
* 失败或未配置时返回 null不抛错保证向后兼容
*/
async function readSettingValue(key: string): Promise<string | null> {
const [row] = await db
.select({ value: systemSettings.value, valueType: systemSettings.valueType })
.from(systemSettings)
.where(
and(
eq(systemSettings.category, SETTINGS_CATEGORY),
eq(systemSettings.key, key)
)
)
.limit(1)
return row?.value ?? null
}
/**
* 获取学期学分上限P2-4 新增)。
*
* 查询顺序:
* 1. 若传入 gradeId先查 `creditLimit:grade:<gradeId>`
* 2. 若未配置或未传入 gradeIdfallback 到 `creditLimit:default`
* 3. 都未配置则返回默认值 10
*
* @param gradeId 学生所在年级 ID可选
*/
export const getElectiveCreditLimitRaw = async (
gradeId?: string | null,
): Promise<number> => {
if (gradeId) {
const gradeValue = await readSettingValue(`creditLimit:grade:${gradeId}`)
if (gradeValue !== null) {
const parsed = Number(gradeValue)
if (!Number.isNaN(parsed) && parsed > 0) return parsed
}
}
const defaultValue = await readSettingValue("creditLimit:default")
if (defaultValue !== null) {
const parsed = Number(defaultValue)
if (!Number.isNaN(parsed) && parsed > 0) return parsed
}
return DEFAULT_MAX_CREDIT_PER_TERM
}
export const getElectiveCreditLimit = cacheFn(getElectiveCreditLimitRaw, {
tags: ["elective"],
ttl: 300,
keyParts: ["elective", "getElectiveCreditLimit"],
})
/**
* 获取容量阈值通知比例P2-4 新增)。
*
* 当课程 `enrolledCount >= capacity * threshold` 时触发管理员通知。
* 默认 0.990%)。
*/
export const getCapacityNotifyThresholdRaw = async (): Promise<number> => {
const value = await readSettingValue("capacityNotifyThreshold")
if (value !== null) {
const parsed = Number(value)
if (!Number.isNaN(parsed) && parsed > 0 && parsed <= 1) return parsed
}
return DEFAULT_CAPACITY_NOTIFY_THRESHOLD
}
export const getCapacityNotifyThreshold = cacheFn(getCapacityNotifyThresholdRaw, {
tags: ["elective"],
ttl: 300,
keyParts: ["elective", "getCapacityNotifyThreshold"],
})
/** 导出默认值常量(供测试与文档引用) */
export const ELECTIVE_DEFAULTS = {
MAX_CREDIT_PER_TERM: DEFAULT_MAX_CREDIT_PER_TERM,
CAPACITY_NOTIFY_THRESHOLD: DEFAULT_CAPACITY_NOTIFY_THRESHOLD,
} as const