Files
AIclinicalresearch/backend/scripts/verify-activity-tracking.ts
HaHafeng 097e7920ab feat(admin): add user-level direct permission system and enhance activity tracking
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
2026-03-10 09:02:35 +08:00

138 lines
4.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.
/**
* 埋点验证脚本
*
* 检查 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);
});