gongxiangp2/scripts/query-system-stats.ts

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();
});