forked from 77media/video-flow
366 lines
11 KiB
JavaScript
366 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 最终验证脚本 - 测试修复后的Google OAuth完整流程
|
||
*/
|
||
|
||
const https = require('https');
|
||
const http = require('http');
|
||
|
||
// 颜色输出
|
||
const colors = {
|
||
reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m',
|
||
yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m'
|
||
};
|
||
|
||
function log(message, color = 'reset') {
|
||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||
}
|
||
|
||
function logSection(title) {
|
||
console.log('\n' + '='.repeat(70));
|
||
log(`🔍 ${title}`, 'cyan');
|
||
console.log('='.repeat(70));
|
||
}
|
||
|
||
function logSuccess(message) {
|
||
log(`✅ ${message}`, 'green');
|
||
}
|
||
|
||
function logError(message) {
|
||
log(`❌ ${message}`, 'red');
|
||
}
|
||
|
||
function logWarning(message) {
|
||
log(`⚠️ ${message}`, 'yellow');
|
||
}
|
||
|
||
function logInfo(message) {
|
||
log(`ℹ️ ${message}`, 'blue');
|
||
}
|
||
|
||
// 验证修复后的代码
|
||
async function verifyCodeFixes() {
|
||
logSection('验证代码修复情况');
|
||
|
||
const fs = require('fs');
|
||
const fixes = [];
|
||
|
||
try {
|
||
// 1. 检查lib/auth.ts中的redirect_uri修复
|
||
const authCode = fs.readFileSync('lib/auth.ts', 'utf8');
|
||
|
||
if (authCode.includes('https://www.movieflow.net/users/oauth/callback') &&
|
||
!authCode.includes('https://www.movieflow.net/api/auth/google/callback')) {
|
||
logSuccess('✓ lib/auth.ts - redirect_uri已修复');
|
||
fixes.push('redirect_uri_fixed');
|
||
} else {
|
||
logError('✗ lib/auth.ts - redirect_uri未正确修复');
|
||
}
|
||
|
||
// 2. 检查API路由是否存在
|
||
if (fs.existsSync('app/api/auth/google/callback/route.ts')) {
|
||
logSuccess('✓ API路由文件存在: /api/auth/google/callback/route.ts');
|
||
fixes.push('api_route_exists');
|
||
|
||
const apiCode = fs.readFileSync('app/api/auth/google/callback/route.ts', 'utf8');
|
||
if (apiCode.includes('oauth2.googleapis.com/token')) {
|
||
logSuccess('✓ API路由包含Google token交换逻辑');
|
||
fixes.push('token_exchange_logic');
|
||
}
|
||
} else {
|
||
logError('✗ API路由文件不存在');
|
||
}
|
||
|
||
// 3. 检查OAuth回调页面的跳转修复
|
||
const callbackCode = fs.readFileSync('app/users/oauth/callback/page.tsx', 'utf8');
|
||
|
||
if (callbackCode.includes('const returnUrl = \'/movies\';')) {
|
||
logSuccess('✓ OAuth回调页面跳转逻辑已修复');
|
||
fixes.push('callback_redirect_fixed');
|
||
} else if (callbackCode.includes('stateData.origin || \'/movies\'')) {
|
||
logWarning('⚠ OAuth回调页面仍使用原来的跳转逻辑');
|
||
logInfo('这会导致用户跳转回来源页面而不是/movies');
|
||
}
|
||
|
||
// 4. 检查localStorage存储键的一致性
|
||
if (callbackCode.includes('localStorage.setItem(\'currentUser\'')) {
|
||
logSuccess('✓ OAuth回调页面使用正确的localStorage键名');
|
||
fixes.push('localstorage_key_fixed');
|
||
} else if (callbackCode.includes('localStorage.setItem(\'user\'')) {
|
||
logError('✗ OAuth回调页面仍使用错误的localStorage键名');
|
||
}
|
||
|
||
} catch (error) {
|
||
logError(`代码验证失败: ${error.message}`);
|
||
}
|
||
|
||
return fixes;
|
||
}
|
||
|
||
// 模拟完整的用户操作流程
|
||
async function simulateUserJourney() {
|
||
logSection('模拟用户完整操作流程');
|
||
|
||
const journey = [];
|
||
|
||
// 步骤1: 用户访问注册页
|
||
logInfo('👤 用户访问注册页 /signup');
|
||
journey.push({
|
||
step: 1,
|
||
action: '访问注册页',
|
||
url: '/signup',
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤2: 点击Google登录按钮
|
||
logInfo('👤 用户点击Google登录按钮');
|
||
logInfo('🔄 执行 signInWithGoogle() 函数');
|
||
|
||
// 模拟环境检测
|
||
const hostname = 'www.movieflow.net';
|
||
const isDevEnv = hostname.includes('movieflow.net');
|
||
const redirectUri = isDevEnv ? 'https://www.movieflow.net/users/oauth/callback' : 'https://www.movieflow.ai/users/oauth/callback';
|
||
|
||
logSuccess(`🔄 构建授权URL,redirect_uri: ${redirectUri}`);
|
||
journey.push({
|
||
step: 2,
|
||
action: '点击Google登录',
|
||
redirectUri,
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤3: 跳转到Google授权页面
|
||
logInfo('🔄 浏览器跳转到Google授权页面');
|
||
logInfo('👤 用户在Google页面完成授权');
|
||
journey.push({
|
||
step: 3,
|
||
action: 'Google授权',
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤4: Google重定向回应用
|
||
logInfo('🔄 Google重定向到 /users/oauth/callback?code=xxx&state=xxx');
|
||
journey.push({
|
||
step: 4,
|
||
action: 'Google重定向回调',
|
||
callbackUrl: '/users/oauth/callback',
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤5: 回调页面处理
|
||
logInfo('🔄 OAuth回调页面开始处理');
|
||
logInfo('🔄 调用 /api/auth/google/callback API');
|
||
journey.push({
|
||
step: 5,
|
||
action: 'OAuth回调处理',
|
||
apiCall: '/api/auth/google/callback',
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤6: API处理流程
|
||
logInfo('🔄 API路由处理:');
|
||
logInfo(' 1. 使用code向Google换取id_token');
|
||
logInfo(' 2. 使用id_token调用Java后端');
|
||
logInfo(' 3. 返回用户信息和JWT token');
|
||
journey.push({
|
||
step: 6,
|
||
action: 'API处理流程',
|
||
subSteps: ['Google token交换', 'Java后端调用', '返回用户数据'],
|
||
status: 'success'
|
||
});
|
||
|
||
// 步骤7: 保存用户数据并跳转
|
||
logInfo('🔄 保存用户数据到localStorage');
|
||
logInfo('🔄 2秒后跳转到 /movies');
|
||
logSuccess('🎉 用户成功登录并跳转到主页面!');
|
||
journey.push({
|
||
step: 7,
|
||
action: '保存数据并跳转',
|
||
finalUrl: '/movies',
|
||
status: 'success'
|
||
});
|
||
|
||
return journey;
|
||
}
|
||
|
||
// 检查潜在问题
|
||
async function checkPotentialIssues() {
|
||
logSection('潜在问题检查');
|
||
|
||
const issues = [];
|
||
const recommendations = [];
|
||
|
||
// 1. 环境变量检查
|
||
if (!process.env.GOOGLE_CLIENT_SECRET) {
|
||
logWarning('环境变量 GOOGLE_CLIENT_SECRET 未设置');
|
||
issues.push('GOOGLE_CLIENT_SECRET未配置');
|
||
recommendations.push('在生产环境中设置GOOGLE_CLIENT_SECRET环境变量');
|
||
} else {
|
||
logSuccess('GOOGLE_CLIENT_SECRET 环境变量已设置');
|
||
}
|
||
|
||
// 2. Google Console配置检查
|
||
logInfo('需要在Google Console中配置以下redirect_uri:');
|
||
const redirectUris = [
|
||
'https://www.movieflow.net/users/oauth/callback',
|
||
'https://www.movieflow.ai/users/oauth/callback',
|
||
'http://localhost:3000/users/oauth/callback'
|
||
];
|
||
|
||
redirectUris.forEach(uri => {
|
||
logInfo(` • ${uri}`);
|
||
});
|
||
|
||
recommendations.push('验证Google Console中的OAuth配置');
|
||
|
||
// 3. 网络连通性检查
|
||
logInfo('检查关键服务的连通性...');
|
||
|
||
try {
|
||
// 检查Java后端
|
||
const javaBackendTest = await testConnection('77.app.java.auth.qikongjian.com', 443);
|
||
if (javaBackendTest) {
|
||
logSuccess('Java后端连通性正常');
|
||
} else {
|
||
logWarning('Java后端连通性可能有问题');
|
||
issues.push('Java后端连通性');
|
||
}
|
||
} catch (error) {
|
||
logWarning(`Java后端连通性测试失败: ${error.message}`);
|
||
}
|
||
|
||
try {
|
||
// 检查Google OAuth端点
|
||
const googleTest = await testConnection('accounts.google.com', 443);
|
||
if (googleTest) {
|
||
logSuccess('Google OAuth端点连通性正常');
|
||
} else {
|
||
logWarning('Google OAuth端点连通性可能有问题');
|
||
issues.push('Google OAuth连通性');
|
||
}
|
||
} catch (error) {
|
||
logWarning(`Google OAuth连通性测试失败: ${error.message}`);
|
||
}
|
||
|
||
return { issues, recommendations };
|
||
}
|
||
|
||
// 测试网络连通性
|
||
function testConnection(hostname, port) {
|
||
return new Promise((resolve) => {
|
||
const options = {
|
||
hostname,
|
||
port,
|
||
method: 'HEAD',
|
||
timeout: 5000
|
||
};
|
||
|
||
const req = https.request(options, (res) => {
|
||
resolve(true);
|
||
});
|
||
|
||
req.on('error', () => {
|
||
resolve(false);
|
||
});
|
||
|
||
req.on('timeout', () => {
|
||
resolve(false);
|
||
});
|
||
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// 生成最终报告
|
||
async function generateFinalReport(fixes, journey, issues) {
|
||
logSection('最终验证报告');
|
||
|
||
log('📊 修复情况统计:', 'cyan');
|
||
const totalFixes = 6; // 预期的修复项目数
|
||
const completedFixes = fixes.length;
|
||
|
||
log(`✅ 已完成修复: ${completedFixes}/${totalFixes}`, completedFixes === totalFixes ? 'green' : 'yellow');
|
||
|
||
if (fixes.includes('redirect_uri_fixed')) log(' ✓ redirect_uri配置已修复', 'green');
|
||
if (fixes.includes('api_route_exists')) log(' ✓ API路由已创建', 'green');
|
||
if (fixes.includes('token_exchange_logic')) log(' ✓ Token交换逻辑已实现', 'green');
|
||
if (fixes.includes('callback_redirect_fixed')) log(' ✓ 回调页面跳转已修复', 'green');
|
||
if (fixes.includes('localstorage_key_fixed')) log(' ✓ LocalStorage键名已统一', 'green');
|
||
|
||
log('\n📋 用户操作流程验证:', 'cyan');
|
||
const successfulSteps = journey.filter(j => j.status === 'success').length;
|
||
log(`✅ 流程步骤: ${successfulSteps}/${journey.length} 步骤验证通过`, 'green');
|
||
|
||
log('\n🚨 待解决问题:', 'cyan');
|
||
if (issues.issues.length === 0) {
|
||
log(' 🎉 没有发现严重问题!', 'green');
|
||
} else {
|
||
issues.issues.forEach((issue, index) => {
|
||
log(` ${index + 1}. ${issue}`, 'yellow');
|
||
});
|
||
}
|
||
|
||
log('\n💡 部署建议:', 'cyan');
|
||
issues.recommendations.forEach((rec, index) => {
|
||
log(` ${index + 1}. ${rec}`, 'blue');
|
||
});
|
||
|
||
log('\n🎯 总体评估:', 'cyan');
|
||
if (completedFixes >= totalFixes - 1 && issues.issues.length <= 1) {
|
||
logSuccess('🎉 OAuth流程修复完成,可以进行部署测试!');
|
||
return { status: 'ready_for_deployment', score: 95 };
|
||
} else if (completedFixes >= totalFixes - 2) {
|
||
logWarning('⚠️ 大部分问题已修复,建议解决剩余问题后部署');
|
||
return { status: 'mostly_ready', score: 80 };
|
||
} else {
|
||
logError('❌ 仍有重要问题需要解决');
|
||
return { status: 'needs_more_work', score: 60 };
|
||
}
|
||
}
|
||
|
||
// 主函数
|
||
async function runFinalVerification() {
|
||
log('🚀 启动最终验证测试', 'cyan');
|
||
log(`验证时间: ${new Date().toLocaleString()}`, 'blue');
|
||
|
||
try {
|
||
// 1. 验证代码修复
|
||
const fixes = await verifyCodeFixes();
|
||
|
||
// 2. 模拟用户流程
|
||
const journey = await simulateUserJourney();
|
||
|
||
// 3. 检查潜在问题
|
||
const issues = await checkPotentialIssues();
|
||
|
||
// 4. 生成最终报告
|
||
const report = await generateFinalReport(fixes, journey, issues);
|
||
|
||
// 5. 保存报告
|
||
const fs = require('fs');
|
||
const fullReport = {
|
||
timestamp: new Date().toISOString(),
|
||
fixes,
|
||
journey,
|
||
issues,
|
||
report
|
||
};
|
||
|
||
fs.writeFileSync('final-verification-report.json', JSON.stringify(fullReport, null, 2));
|
||
logSuccess('完整报告已保存到 final-verification-report.json');
|
||
|
||
process.exit(report.status === 'ready_for_deployment' ? 0 : 1);
|
||
|
||
} catch (error) {
|
||
logError(`验证失败: ${error.message}`);
|
||
console.error(error.stack);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// 运行验证
|
||
if (require.main === module) {
|
||
runFinalVerification();
|
||
}
|