feat: 添加关键词词云数据访问层函数
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import type { Quarter, Keyword, VisualStyleRule } from '@prisma/client';
|
||||||
|
|
||||||
|
// 类型定义
|
||||||
|
export type KeywordWithVisual = Keyword & {
|
||||||
|
visualConfig: {
|
||||||
|
color: string;
|
||||||
|
size: string;
|
||||||
|
border: string;
|
||||||
|
rotation?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QuarterWithKeywords = Quarter & {
|
||||||
|
keywords: KeywordWithVisual[];
|
||||||
|
_count?: { keywords: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QuarterWithCount = Quarter & {
|
||||||
|
_count?: { keywords: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有季度列表
|
||||||
|
*/
|
||||||
|
export async function getAllQuarters(
|
||||||
|
options?: { isActive?: boolean }
|
||||||
|
): Promise<Quarter[]> {
|
||||||
|
const where = options?.isActive !== undefined
|
||||||
|
? { isActive: options.isActive }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return prisma.quarter.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { displayOrder: 'asc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个季度的详情(包含关键词计数)
|
||||||
|
*/
|
||||||
|
export async function getQuarterByQuarter(
|
||||||
|
quarter: string
|
||||||
|
): Promise<QuarterWithCount | null> {
|
||||||
|
const quarterData = await prisma.quarter.findUnique({
|
||||||
|
where: { quarter },
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: { keywords: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return quarterData as QuarterWithCount | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定季度的所有关键词
|
||||||
|
*/
|
||||||
|
export async function getKeywordsByQuarter(
|
||||||
|
quarter: string
|
||||||
|
): Promise<QuarterWithKeywords | null> {
|
||||||
|
const quarterData = await prisma.quarter.findUnique({
|
||||||
|
where: { quarter },
|
||||||
|
include: {
|
||||||
|
keywords: {
|
||||||
|
orderBy: { trendScore: 'desc' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!quarterData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换 visualConfig 从 JSON 到对象
|
||||||
|
const keywords: KeywordWithVisual[] = quarterData.keywords.map(kw => ({
|
||||||
|
...kw,
|
||||||
|
visualConfig: (typeof kw.visualConfig === 'string'
|
||||||
|
? JSON.parse(kw.visualConfig)
|
||||||
|
: kw.visualConfig) as KeywordWithVisual['visualConfig'],
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...quarterData,
|
||||||
|
keywords,
|
||||||
|
} as QuarterWithKeywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有启用的视觉规则
|
||||||
|
*/
|
||||||
|
export async function getVisualStyleRules(
|
||||||
|
options?: { enabled?: boolean }
|
||||||
|
): Promise<VisualStyleRule[]> {
|
||||||
|
const where = options?.enabled !== undefined
|
||||||
|
? { enabled: options.enabled }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return prisma.visualStyleRule.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: [
|
||||||
|
{ priority: 'asc' },
|
||||||
|
{ minScore: 'desc' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建或更新季度
|
||||||
|
*/
|
||||||
|
export async function upsertQuarter(
|
||||||
|
quarter: string,
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
titleEn?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
subtitleEn?: string;
|
||||||
|
displayOrder?: number;
|
||||||
|
}
|
||||||
|
): Promise<Quarter> {
|
||||||
|
return prisma.quarter.upsert({
|
||||||
|
where: { quarter },
|
||||||
|
update: data,
|
||||||
|
create: {
|
||||||
|
quarter,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量创建关键词
|
||||||
|
*/
|
||||||
|
export async function createKeywords(
|
||||||
|
quarterId: number,
|
||||||
|
keywords: Array<{
|
||||||
|
word: string;
|
||||||
|
trendScore: number;
|
||||||
|
description: string;
|
||||||
|
descriptionEn?: string;
|
||||||
|
detailPoints: string[];
|
||||||
|
detailPointsEn?: string[];
|
||||||
|
visualConfig: Record<string, any>;
|
||||||
|
}>
|
||||||
|
): Promise<{ created: number; failed: number; errors: Array<{ word: string; error: string }> }> {
|
||||||
|
const errors: Array<{ word: string; error: string }> = [];
|
||||||
|
let created = 0;
|
||||||
|
|
||||||
|
for (const kw of keywords) {
|
||||||
|
try {
|
||||||
|
await prisma.keyword.create({
|
||||||
|
data: {
|
||||||
|
quarterId,
|
||||||
|
word: kw.word,
|
||||||
|
trendScore: kw.trendScore,
|
||||||
|
description: kw.description,
|
||||||
|
descriptionEn: kw.descriptionEn,
|
||||||
|
detailPoints: kw.detailPoints as any, // Prisma Json 类型
|
||||||
|
detailPointsEn: kw.detailPointsEn as any,
|
||||||
|
visualConfig: kw.visualConfig as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
created++;
|
||||||
|
} catch (error) {
|
||||||
|
errors.push({
|
||||||
|
word: kw.word,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { created, failed: errors.length, errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录错误日志
|
||||||
|
*/
|
||||||
|
export async function logKeywordCloudError(
|
||||||
|
data: {
|
||||||
|
quarter: string;
|
||||||
|
keyword?: string;
|
||||||
|
errorType: string;
|
||||||
|
errorMessage: string;
|
||||||
|
rawData?: any;
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
await prisma.keywordCloudErrorLog.create({
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user