162 lines
4.4 KiB
TypeScript
162 lines
4.4 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) {
|
|
throw new Error('必须设置 DATABASE_URL');
|
|
}
|
|
|
|
const prisma = new PrismaClient({
|
|
datasources: {
|
|
db: {
|
|
url: databaseUrl,
|
|
},
|
|
},
|
|
});
|
|
|
|
async function main() {
|
|
console.log('=== 正式系统数据统计 ===\n');
|
|
|
|
// 用户统计
|
|
const totalUsers = await prisma.user.count({
|
|
where: {
|
|
archivedAt: null,
|
|
},
|
|
});
|
|
|
|
const activeUsers = await prisma.user.count({
|
|
where: {
|
|
archivedAt: null,
|
|
updatedAt: {
|
|
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
|
},
|
|
},
|
|
});
|
|
|
|
// 素材统计
|
|
const totalMaterials = await prisma.material.count({
|
|
where: {
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
|
|
// 素材类型分布
|
|
const materialTypeStats = await prisma.material.groupBy({
|
|
by: ['materialType'],
|
|
where: {
|
|
deletedAt: null,
|
|
},
|
|
_count: {
|
|
id: true,
|
|
},
|
|
orderBy: {
|
|
_count: {
|
|
id: 'desc',
|
|
},
|
|
},
|
|
});
|
|
|
|
// 视频/音频素材数量
|
|
const videoAudioCount = await prisma.material.count({
|
|
where: {
|
|
deletedAt: null,
|
|
materialType: {
|
|
in: ['video', 'audio', '主图视频', '种草视频', '安装视频', '音乐'],
|
|
},
|
|
},
|
|
});
|
|
|
|
// 素材资产数量
|
|
const assetCount = await prisma.materialAsset.count();
|
|
|
|
// 最近30天上传统计
|
|
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
const recentUploads = await prisma.material.count({
|
|
where: {
|
|
deletedAt: null,
|
|
createdAt: {
|
|
gte: thirtyDaysAgo,
|
|
},
|
|
},
|
|
});
|
|
|
|
// 下载统计
|
|
const downloadStats = await prisma.material.aggregate({
|
|
where: {
|
|
deletedAt: null,
|
|
},
|
|
_sum: {
|
|
downloadCount: true,
|
|
},
|
|
_avg: {
|
|
downloadCount: true,
|
|
},
|
|
});
|
|
|
|
// 最近7天活跃用户
|
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
const recentActiveUsers = await prisma.material.groupBy({
|
|
by: ['userId'],
|
|
where: {
|
|
deletedAt: null,
|
|
createdAt: {
|
|
gte: sevenDaysAgo,
|
|
},
|
|
},
|
|
_count: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
console.log('📊 用户统计:');
|
|
console.log(` 总用户数: ${totalUsers}`);
|
|
console.log(` 最近7天活跃用户: ${activeUsers}`);
|
|
console.log(` 最近7天上传素材的用户: ${recentActiveUsers.length}`);
|
|
console.log('');
|
|
|
|
console.log('📦 素材统计:');
|
|
console.log(` 总素材数: ${totalMaterials}`);
|
|
console.log(` 视频/音频素材: ${videoAudioCount}`);
|
|
console.log(` 素材资产数: ${assetCount}`);
|
|
console.log(` 最近30天上传: ${recentUploads}`);
|
|
console.log(` 总下载次数: ${downloadStats._sum.downloadCount || 0}`);
|
|
console.log(` 平均下载次数: ${Math.round(downloadStats._avg.downloadCount || 0)}`);
|
|
console.log('');
|
|
|
|
console.log('📋 素材类型分布:');
|
|
materialTypeStats.forEach((stat) => {
|
|
const percentage = ((stat._count.id / totalMaterials) * 100).toFixed(1);
|
|
console.log(` ${stat.materialType}: ${stat._count.id} (${percentage}%)`);
|
|
});
|
|
console.log('');
|
|
|
|
// 计算并发需求
|
|
const estimatedConcurrentUsers = Math.max(10, Math.ceil(totalUsers * 0.1)); // 假设10%用户同时在线
|
|
const estimatedDailyRequests = Math.max(1000, recentUploads * 10 + (downloadStats._sum.downloadCount || 0) * 0.1);
|
|
const peakConcurrentRequests = Math.ceil(estimatedDailyRequests / (24 * 60 * 60) * 10); // 峰值是平均的10倍
|
|
|
|
console.log('🔧 并发需求估算:');
|
|
console.log(` 估算并发用户: ${estimatedConcurrentUsers}`);
|
|
console.log(` 估算日请求量: ${estimatedDailyRequests}`);
|
|
console.log(` 峰值并发请求: ${peakConcurrentRequests}`);
|
|
console.log('');
|
|
|
|
// 推荐配置
|
|
console.log('💡 FRP配置建议:');
|
|
const recommendedPoolCount = Math.max(30, Math.min(100, peakConcurrentRequests * 2));
|
|
console.log(` 推荐连接池大小 (poolCount): ${recommendedPoolCount}`);
|
|
console.log(` 推荐心跳间隔 (heartbeatInterval): 20秒`);
|
|
console.log(` 推荐心跳超时 (heartbeatTimeout): 60秒`);
|
|
console.log(` 推荐文件描述符限制 (LimitNOFILE): ${Math.max(8192, recommendedPoolCount * 100)}`);
|
|
console.log(` 推荐内存限制 (MemoryMax): 1G`);
|
|
console.log(` 推荐CPU限制 (CPUQuota): 100% (不限制)`);
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('Error:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|