Features: - Add user_permissions table for direct user-to-permission grants (ops:user-ops) - Merge role_permissions + user_permissions in auth chain (login, middleware, getCurrentUser) - Add getUserQueryScope support for USER role with ops:user-ops (cross-tenant access) - Unify cross-tenant operation checks via getUserQueryScope (remove hardcoded SUPER_ADMIN checks) - Add 3 new API endpoints: GET/PUT /:id/permissions, GET /options/permissions - Support ops:user-ops as alternative permission on all user/tenant management routes - Frontend: add user-ops permission toggle on UserFormPage and UserDetailPage - Enhance DC module activity tracking (StreamAIController, SessionController, QuickActionController) - Fix DC AIController user ID extraction and feature name consistency - Add verify-activity-tracking.ts validation script - Update deployment checklist and admin module documentation DB Migration: 20260309_add_user_permissions_table Made-with: Cursor
138 lines
4.4 KiB
TypeScript
138 lines
4.4 KiB
TypeScript
/**
|
||
* 埋点验证脚本
|
||
*
|
||
* 检查 simple_logs 表中是否存在各关键埋点,
|
||
* 并汇总每个模块/功能的记录数量。
|
||
*
|
||
* 用法: npx tsx scripts/verify-activity-tracking.ts [--days N]
|
||
*/
|
||
|
||
import { PrismaClient } from '@prisma/client';
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
interface TrackingPoint {
|
||
module: string;
|
||
feature: string;
|
||
label: string;
|
||
}
|
||
|
||
const EXPECTED_TRACKING_POINTS: TrackingPoint[] = [
|
||
// 系统级
|
||
{ module: 'SYSTEM', feature: '用户登录', label: '用户登录' },
|
||
{ module: 'SYSTEM', feature: '顶部导航点击', label: '顶部导航点击' },
|
||
// ASL
|
||
{ module: 'ASL', feature: '意图识别', label: 'ASL 意图识别(需求扩写)' },
|
||
{ module: 'ASL', feature: 'Deep Research', label: 'ASL 启动 Deep Research' },
|
||
// AIA (各智能体名称作为 feature)
|
||
{ module: 'AIA', feature: '科学问题梳理', label: 'AIA 智能体 - 科学问题梳理(示例)' },
|
||
// PKB
|
||
{ module: 'PKB', feature: '创建知识库', label: 'PKB 创建知识库' },
|
||
// DC
|
||
{ module: 'DC', feature: '智能数据清洗', label: 'DC 智能数据清洗' },
|
||
// IIT/CRA
|
||
{ module: 'IIT', feature: 'CRA质控', label: 'IIT CRA质控' },
|
||
// RVW
|
||
{ module: 'RVW', feature: '稿', label: 'RVW 稿件审查相关' },
|
||
];
|
||
|
||
async function main() {
|
||
const daysArg = process.argv.findIndex(a => a === '--days');
|
||
const days = daysArg >= 0 ? parseInt(process.argv[daysArg + 1], 10) || 7 : 7;
|
||
|
||
const since = new Date();
|
||
since.setDate(since.getDate() - days);
|
||
|
||
console.log(`\n📊 埋点验证报告 (最近 ${days} 天,since ${since.toISOString().slice(0, 10)})\n`);
|
||
console.log('='.repeat(80));
|
||
|
||
// 1. 总记录数
|
||
const totalCount = await prisma.simple_logs.count({
|
||
where: { created_at: { gte: since } },
|
||
});
|
||
console.log(`\n📈 总记录数: ${totalCount}\n`);
|
||
|
||
// 2. 按模块统计
|
||
const moduleStats = await prisma.$queryRaw`
|
||
SELECT module, COUNT(*) as count
|
||
FROM admin_schema.simple_logs
|
||
WHERE created_at >= ${since}
|
||
GROUP BY module
|
||
ORDER BY count DESC
|
||
` as Array<{ module: string; count: bigint }>;
|
||
|
||
console.log('📦 模块统计:');
|
||
console.log('-'.repeat(40));
|
||
for (const row of moduleStats) {
|
||
console.log(` ${row.module.padEnd(12)} ${Number(row.count).toString().padStart(6)} 条`);
|
||
}
|
||
|
||
// 3. 检查每个关键埋点是否存在
|
||
console.log('\n🔍 关键埋点覆盖检查:');
|
||
console.log('-'.repeat(80));
|
||
|
||
let coveredCount = 0;
|
||
let missingCount = 0;
|
||
|
||
for (const tp of EXPECTED_TRACKING_POINTS) {
|
||
const count = await prisma.simple_logs.count({
|
||
where: {
|
||
module: tp.module,
|
||
feature: { contains: tp.feature },
|
||
created_at: { gte: since },
|
||
},
|
||
});
|
||
|
||
const status = count > 0 ? '✅' : '❌';
|
||
if (count > 0) coveredCount++;
|
||
else missingCount++;
|
||
|
||
console.log(` ${status} ${tp.label.padEnd(35)} ${count > 0 ? `${count} 条` : '缺失'}`);
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(80));
|
||
console.log(`\n📋 结果: ${coveredCount}/${EXPECTED_TRACKING_POINTS.length} 已覆盖, ${missingCount} 缺失\n`);
|
||
|
||
// 4. 按 feature 统计 Top 20
|
||
console.log('🏆 Top 20 Feature (按记录数):');
|
||
console.log('-'.repeat(80));
|
||
const topFeatures = await prisma.$queryRaw`
|
||
SELECT module, feature, action, COUNT(*) as count
|
||
FROM admin_schema.simple_logs
|
||
WHERE created_at >= ${since}
|
||
GROUP BY module, feature, action
|
||
ORDER BY count DESC
|
||
LIMIT 20
|
||
` as Array<{ module: string; feature: string; action: string; count: bigint }>;
|
||
|
||
for (const row of topFeatures) {
|
||
console.log(` ${row.module.padEnd(10)} ${row.feature.padEnd(25)} ${row.action.padEnd(10)} ${Number(row.count).toString().padStart(6)} 条`);
|
||
}
|
||
|
||
// 5. DAU/MAU
|
||
const dauResult = await prisma.$queryRaw`
|
||
SELECT COUNT(DISTINCT user_id) as dau
|
||
FROM admin_schema.simple_logs
|
||
WHERE created_at >= CURRENT_DATE
|
||
` as Array<{ dau: bigint }>;
|
||
|
||
const mauResult = await prisma.$queryRaw`
|
||
SELECT COUNT(DISTINCT user_id) as mau
|
||
FROM admin_schema.simple_logs
|
||
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
|
||
` as Array<{ mau: bigint }>;
|
||
|
||
console.log(`\n👤 DAU (今日活跃用户): ${Number(dauResult[0]?.dau || 0)}`);
|
||
console.log(`👥 MAU (30日活跃用户): ${Number(mauResult[0]?.mau || 0)}`);
|
||
|
||
console.log('\n✅ 验证完成\n');
|
||
}
|
||
|
||
main()
|
||
.then(() => prisma.$disconnect())
|
||
.catch(async (e) => {
|
||
console.error('❌ 验证失败:', e);
|
||
await prisma.$disconnect();
|
||
process.exit(1);
|
||
});
|