Files
sub2apipay/src/lib/system-config.ts
erio eafb7e49fa feat: 渠道展示、订阅套餐、系统配置全功能
- 新增 Channel / SubscriptionPlan / SystemConfig 三个数据模型
- Order 模型扩展支持订阅订单(order_type, plan_id, subscription_group_id)
- Sub2API client 新增分组查询、订阅分配/续期、用户订阅查询
- 订单服务支持订阅履约流程(CAS 锁 + 分组消失安全处理)
- 管理后台:渠道管理、订阅套餐管理、系统配置、Sub2API 分组同步
- 用户页面:双 Tab UI(按量付费/包月订阅)、渠道卡片、充值弹窗、订阅确认
- PaymentForm 支持 fixedAmount 固定金额模式
- 订单状态 API 返回 failedReason 用于订阅异常展示
- 数据库迁移脚本
2026-03-13 19:06:25 +08:00

120 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 { prisma } from '@/lib/db';
// 内存缓存key → { value, expiresAt }
const cache = new Map<string, { value: string; expiresAt: number }>();
const CACHE_TTL_MS = 30_000; // 30 秒
function getCached(key: string): string | undefined {
const entry = cache.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiresAt) {
cache.delete(key);
return undefined;
}
return entry.value;
}
function setCache(key: string, value: string): void {
cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
}
export function invalidateConfigCache(key?: string): void {
if (key) {
cache.delete(key);
} else {
cache.clear();
}
}
export async function getSystemConfig(key: string): Promise<string | undefined> {
const cached = getCached(key);
if (cached !== undefined) return cached;
const row = await prisma.systemConfig.findUnique({ where: { key } });
if (row) {
setCache(key, row.value);
return row.value;
}
// 回退到环境变量
const envVal = process.env[key];
if (envVal !== undefined) {
setCache(key, envVal);
}
return envVal;
}
export async function getSystemConfigs(keys: string[]): Promise<Record<string, string>> {
const result: Record<string, string> = {};
const missing: string[] = [];
for (const key of keys) {
const cached = getCached(key);
if (cached !== undefined) {
result[key] = cached;
} else {
missing.push(key);
}
}
if (missing.length > 0) {
const rows = await prisma.systemConfig.findMany({
where: { key: { in: missing } },
});
const dbMap = new Map(rows.map((r) => [r.key, r.value]));
for (const key of missing) {
const val = dbMap.get(key) ?? process.env[key];
if (val !== undefined) {
result[key] = val;
setCache(key, val);
}
}
}
return result;
}
export async function setSystemConfig(key: string, value: string, group?: string, label?: string): Promise<void> {
await prisma.systemConfig.upsert({
where: { key },
update: { value, ...(group !== undefined && { group }), ...(label !== undefined && { label }) },
create: { key, value, group: group ?? 'general', label },
});
invalidateConfigCache(key);
}
export async function setSystemConfigs(configs: { key: string; value: string; group?: string; label?: string }[]): Promise<void> {
await prisma.$transaction(
configs.map((c) =>
prisma.systemConfig.upsert({
where: { key: c.key },
update: { value: c.value, ...(c.group !== undefined && { group: c.group }), ...(c.label !== undefined && { label: c.label }) },
create: { key: c.key, value: c.value, group: c.group ?? 'general', label: c.label },
}),
),
);
invalidateConfigCache();
}
export async function getSystemConfigsByGroup(group: string): Promise<{ key: string; value: string; label: string | null }[]> {
return prisma.systemConfig.findMany({
where: { group },
select: { key: true, value: true, label: true },
orderBy: { key: 'asc' },
});
}
export async function getAllSystemConfigs(): Promise<{ key: string; value: string; group: string; label: string | null }[]> {
return prisma.systemConfig.findMany({
select: { key: true, value: true, group: true, label: true },
orderBy: [{ group: 'asc' }, { key: 'asc' }],
});
}
export async function deleteSystemConfig(key: string): Promise<void> {
await prisma.systemConfig.delete({ where: { key } }).catch(() => {});
invalidateConfigCache(key);
}