155 lines
4.4 KiB
TypeScript
155 lines
4.4 KiB
TypeScript
/**
|
|
* 数据完整性定期检查 Edge Function
|
|
*
|
|
* 功能:
|
|
* 1. 定期运行数据一致性检查
|
|
* 2. 检测错误率是否超过阈值(万分之一)
|
|
* 3. 发送告警通知
|
|
* 4. 生成检查报告
|
|
*
|
|
* 触发方式:
|
|
* - 定时任务(建议每小时执行一次)
|
|
* - 手动调用
|
|
*/
|
|
|
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
|
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
};
|
|
|
|
interface CheckResult {
|
|
check_type: string;
|
|
total_records: number;
|
|
inconsistent_records: number;
|
|
error_rate: number;
|
|
status: 'PASS' | 'FAIL';
|
|
}
|
|
|
|
interface IntegrityReport {
|
|
timestamp: string;
|
|
checks: CheckResult[];
|
|
overall_error_rate: number;
|
|
overall_status: 'PASS' | 'FAIL';
|
|
target_threshold: number; // 万分比阈值
|
|
alerts: string[];
|
|
}
|
|
|
|
serve(async (req) => {
|
|
// 处理 CORS 预检请求
|
|
if (req.method === 'OPTIONS') {
|
|
return new Response('ok', { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
// 创建 Supabase 客户端
|
|
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
|
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
|
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
|
|
|
console.log('开始数据完整性检查...');
|
|
|
|
// 运行数据完整性检查
|
|
const { data: checkResults, error: checkError } = await supabase.rpc(
|
|
'run_data_integrity_check'
|
|
);
|
|
|
|
if (checkError) {
|
|
throw new Error(`数据检查失败: ${checkError.message}`);
|
|
}
|
|
|
|
// 构建报告
|
|
const report: IntegrityReport = {
|
|
timestamp: new Date().toISOString(),
|
|
checks: checkResults || [],
|
|
overall_error_rate: 0,
|
|
overall_status: 'PASS',
|
|
target_threshold: 1, // 万分之一
|
|
alerts: [],
|
|
};
|
|
|
|
// 计算总体错误率
|
|
let totalRecords = 0;
|
|
let totalInconsistent = 0;
|
|
|
|
for (const check of report.checks) {
|
|
totalRecords += check.total_records;
|
|
totalInconsistent += check.inconsistent_records;
|
|
|
|
// 检查单项是否超标
|
|
if (check.error_rate > report.target_threshold) {
|
|
report.alerts.push(
|
|
`⚠️ ${check.check_type} 错误率 ${check.error_rate.toFixed(2)}/万 超过阈值 ${report.target_threshold}/万`
|
|
);
|
|
}
|
|
}
|
|
|
|
report.overall_error_rate = totalRecords > 0
|
|
? (totalInconsistent / totalRecords) * 10000
|
|
: 0;
|
|
|
|
report.overall_status = report.overall_error_rate <= report.target_threshold
|
|
? 'PASS'
|
|
: 'FAIL';
|
|
|
|
// 如果总体错误率超标,添加告警
|
|
if (report.overall_status === 'FAIL') {
|
|
report.alerts.unshift(
|
|
`🚨 系统错误率 ${report.overall_error_rate.toFixed(2)}/万 超过目标阈值 ${report.target_threshold}/万`
|
|
);
|
|
}
|
|
|
|
// 记录检查结果到审计日志
|
|
await supabase.from('data_audit_logs').insert({
|
|
table_name: 'system_integrity_check',
|
|
record_id: '00000000-0000-0000-0000-000000000000', // 系统检查使用固定ID
|
|
operation: 'INSERT',
|
|
new_values: report,
|
|
validation_passed: report.overall_status === 'PASS',
|
|
error_message: report.overall_status === 'FAIL'
|
|
? report.alerts.join('; ')
|
|
: null,
|
|
});
|
|
|
|
// 如果有告警,可以发送通知(这里预留接口)
|
|
if (report.alerts.length > 0) {
|
|
console.warn('数据完整性告警:', report.alerts);
|
|
|
|
// TODO: 集成邮件/短信/钉钉等告警通知
|
|
// await sendAlertNotification(report);
|
|
}
|
|
|
|
// 返回报告
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: true,
|
|
report,
|
|
message: report.overall_status === 'PASS'
|
|
? '✓ 数据完整性检查通过'
|
|
: '✗ 数据完整性检查发现异常',
|
|
}),
|
|
{
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
status: report.overall_status === 'PASS' ? 200 : 500,
|
|
}
|
|
);
|
|
|
|
} catch (error) {
|
|
console.error('数据完整性检查异常:', error);
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
error: errorMessage,
|
|
}),
|
|
{
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
status: 500,
|
|
}
|
|
);
|
|
}
|
|
});
|