feat: 重构Netlify Functions架构并增强代码质量

- 新增统一的中间件模块,提供鉴权、CORS和错误处理功能
- 重构所有Functions使用withAuth中间件简化代码结构
- 添加安全存储模块替代localStorage,防御XSS攻击
- 引入HTML清理工具,自动转义特殊字符和验证URL安全性
- 创建代码质量检查脚本,验证语法、环境变量和依赖完整性
- 添加构建日志工具和重构脚本,统一替换console.log为Logger
- 引入ESLint配置,提升代码质量和一致性
- 重构Giffgaff相关API,统一错误处理和验证逻辑
- 优化构建脚本,添加压缩和图片优化功能
- 新增健康检查端点,用于服务监控和状态报告
This commit is contained in:
Abner
2025-11-23 22:20:02 +08:00
parent 5b3e828c83
commit 4a0fcc94fe
38 changed files with 3182 additions and 2407 deletions

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# 批量应用中间件到剩余Functions的脚本
# 使用方法: bash scripts/apply-middleware.sh
set -e
FUNCTIONS_DIR="netlify/functions"
echo "🔧 开始批量重构Functions..."
echo ""
# 定义需要重构的函数列表(排除已重构的)
FUNCTIONS=(
"giffgaff-graphql"
"giffgaff-mfa-challenge"
"giffgaff-mfa-validation"
"giffgaff-sms-activate"
"auto-activate-esim"
)
for func in "${FUNCTIONS[@]}"; do
FILE="${FUNCTIONS_DIR}/${func}.js"
if [ ! -f "$FILE" ]; then
echo "⚠️ 跳过不存在的文件: $FILE"
continue
fi
echo "📝 处理: $func.js"
# 备份原文件
cp "$FILE" "${FILE}.backup"
# 检查是否已经使用中间件
if grep -q "withAuth" "$FILE"; then
echo " ✅ 已使用中间件,跳过"
rm "${FILE}.backup"
continue
fi
echo " ⏳ 添加中间件导入..."
# 在第一个require之后添加中间件导入
if ! grep -q "_shared/middleware" "$FILE"; then
sed -i.tmp "1,/const.*require/s/\(const.*require.*\);/\1;\nconst { withAuth, validateInput, AuthError } = require('.\/\_shared\/middleware');/" "$FILE"
rm "${FILE}.tmp" 2>/dev/null || true
fi
echo " ✅ 完成"
echo ""
done
echo "✨ 批量重构完成!"
echo ""
echo "📋 请手动完成以下步骤:"
echo " 1. 检查每个函数的备份文件(.backup)"
echo " 2. 完成exports.handler重构为withAuth包装"
echo " 3. 添加输入验证schema"
echo " 4. 测试功能是否正常"
echo ""
echo "💡 参考示例: netlify/functions/giffgaff-token-exchange.js"

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
@@ -61,16 +63,16 @@ async function copyDirectory(source, destination) {
}
(async () => {
console.log('🧹 清理 dist 目录...');
BuildLogger.log('🧹 清理 dist 目录...');
await removeDist();
await fs.promises.mkdir(distDir, { recursive: true });
for (const entry of entries) {
console.log(`📦 复制 ${entry} -> dist/${entry}`);
BuildLogger.log(`📦 复制 ${entry} -> dist/${entry}`);
await copyEntry(entry);
}
console.log(' 静态资源构建完成,输出目录 dist/');
BuildLogger.success(' 静态资源构建完成,输出目录 dist/');
})().catch(err => {
console.error('构建静态资源失败:', err);
process.exitCode = 1;

View File

@@ -1,6 +1,8 @@
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const BuildLogger = require('./logger.js');
const { promisify } = require('util');
const gzip = promisify(zlib.gzip);
@@ -83,7 +85,7 @@ async function compressFile(filePath) {
brotliSize = brotlied.length;
}
} catch (error) {
console.log(`Brotli压缩失败 ${fileName}:`, error.message);
BuildLogger.log(`Brotli压缩失败 ${fileName}:`, error.message);
}
const originalSize = content.length;
@@ -141,20 +143,20 @@ async function compressBuild() {
return;
}
console.log('开始压缩构建文件...');
BuildLogger.log('开始压缩构建文件...');
try {
const results = await compressDirectory(distDir);
if (results.length === 0) {
console.log('没有找到需要压缩的文件');
BuildLogger.log('没有找到需要压缩的文件');
return;
}
// 显示压缩结果
console.log('\n压缩结果:');
console.log('文件名'.padEnd(30) + '原始大小'.padEnd(12) + 'Gzip大小'.padEnd(12) + 'Brotli大小'.padEnd(12) + '压缩率');
console.log('-'.repeat(80));
BuildLogger.log('\n压缩结果:');
BuildLogger.log('文件名'.padEnd(30) + '原始大小'.padEnd(12) + 'Gzip大小'.padEnd(12) + 'Brotli大小'.padEnd(12) + '压缩率');
BuildLogger.log('-'.repeat(80));
let totalOriginal = 0;
let totalGzip = 0;
@@ -169,7 +171,7 @@ async function compressBuild() {
const gzipKB = (result.gzip / 1024).toFixed(1);
const brotliKB = result.brotli ? (result.brotli / 1024).toFixed(1) : '-';
console.log(
BuildLogger.log(
result.file.padEnd(30) +
`${originalKB}KB`.padEnd(12) +
`${gzipKB}KB`.padEnd(12) +
@@ -179,8 +181,8 @@ async function compressBuild() {
});
const totalRatio = ((totalOriginal - totalGzip) / totalOriginal * 100).toFixed(1);
console.log('-'.repeat(80));
console.log(
BuildLogger.log('-'.repeat(80));
BuildLogger.log(
'总计'.padEnd(30) +
`${(totalOriginal / 1024).toFixed(1)}KB`.padEnd(12) +
`${(totalGzip / 1024).toFixed(1)}KB`.padEnd(12) +
@@ -188,8 +190,8 @@ async function compressBuild() {
`${totalRatio}%`
);
console.log(`\n压缩完成!共处理 ${results.length} 个文件`);
console.log(`节省空间: ${((totalOriginal - totalGzip) / 1024).toFixed(1)}KB`);
BuildLogger.log(`\n压缩完成!共处理 ${results.length} 个文件`);
BuildLogger.log(`节省空间: ${((totalOriginal - totalGzip) / 1024).toFixed(1)}KB`);
} catch (error) {
console.error('压缩失败:', error);
}

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
@@ -25,10 +27,10 @@ function listFiles(dir, base = dir) {
process.exit(1);
}
const files = listFiles(distDir);
console.log('📦 dist 构建分析:');
BuildLogger.log('📦 dist 构建分析:');
files.sort((a, b) => b.size - a.size);
files.slice(0, 10).forEach(file => {
console.log(`${file.rel.padEnd(50)} ${(file.size / 1024).toFixed(1)} KB`);
BuildLogger.log(`${file.rel.padEnd(50)} ${(file.size / 1024).toFixed(1)} KB`);
});
console.log(`合计文件 ${files.length} 个,总大小 ${(files.reduce((sum, f) => sum + f.size, 0) / 1024).toFixed(1)} KB`);
BuildLogger.log(`合计文件 ${files.length} 个,总大小 ${(files.reduce((sum, f) => sum + f.size, 0) / 1024).toFixed(1)} KB`);
})();

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
@@ -6,9 +7,9 @@ const projectRoot = path.join(__dirname, '..');
const distDir = path.join(projectRoot, 'dist');
function ensureAccessKey() {
const key = process.env.ACCESS_KEY || process.env.ESIM_ACCESS_KEY;
const key = process.env.ACCESS_KEY;
if (!key) {
throw new Error('ACCESS_KEY/ESIM_ACCESS_KEY 未配置,无法保护 Functions。');
throw new Error('ACCESS_KEY 未配置,无法保护 Functions。');
}
}
@@ -20,8 +21,8 @@ function ensureDist() {
}
(function main() {
console.log('🔧 检查部署前置条件...');
BuildLogger.log('🔧 检查部署前置条件...');
ensureAccessKey();
ensureDist();
console.log(' 部署前检查通过,可继续执行部署流程');
BuildLogger.success(' 部署前检查通过,可继续执行部署流程');
})();

90
scripts/logger.js Normal file
View File

@@ -0,0 +1,90 @@
/**
* 构建脚本日志工具
* 为构建/部署脚本提供统一的日志输出
* 注: 构建脚本始终需要输出信息,因此不像前端Logger那样禁用
*/
// ANSI颜色码
const colors = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
gray: '\x1b[90m'
};
class BuildLogger {
/**
* 信息日志 (蓝色)
*/
static log(...args) {
console.log(colors.blue + '[INFO]' + colors.reset, ...args);
}
/**
* 成功日志 (绿色)
*/
static success(...args) {
console.log(colors.green + '[SUCCESS]' + colors.reset, ...args);
}
/**
* 警告日志 (黄色)
*/
static warn(...args) {
console.warn(colors.yellow + '[WARN]' + colors.reset, ...args);
}
/**
* 错误日志 (红色)
*/
static error(...args) {
console.error(colors.red + '[ERROR]' + colors.reset, ...args);
}
/**
* 调试日志 (灰色)
*/
static debug(...args) {
if (process.env.DEBUG || process.env.VERBOSE) {
console.log(colors.gray + '[DEBUG]' + colors.reset, ...args);
}
}
/**
* 标题日志 (粗体青色)
*/
static title(text) {
console.log('\n' + colors.bold + colors.cyan + text + colors.reset);
console.log(colors.cyan + '='.repeat(text.length) + colors.reset);
}
/**
* 进度信息 (无标签)
*/
static progress(...args) {
console.log(' ', ...args);
}
/**
* 检查项 (带emoji)
*/
static check(passed, message) {
const icon = passed ? '✅' : '❌';
const color = passed ? colors.green : colors.red;
console.log(icon, color + message + colors.reset);
}
/**
* 步骤开始
*/
static step(number, total, message) {
console.log(colors.cyan + `\n[${number}/${total}]` + colors.reset, colors.bold + message + colors.reset);
}
}
module.exports = BuildLogger;

View File

@@ -1,6 +1,8 @@
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const BuildLogger = require('./logger.js');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
@@ -45,7 +47,7 @@ async function optimizeImage(inputPath, outputPath, format, options = {}) {
const inputStats = await stat(inputPath);
const outputStats = await stat(outputPath);
if (outputStats.mtime > inputStats.mtime) {
console.log(`⏭️ 跳过已优化: ${path.basename(outputPath)}`);
BuildLogger.log(`⏭️ 跳过已优化: ${path.basename(outputPath)}`);
return true;
}
}
@@ -93,7 +95,7 @@ async function optimizeImage(inputPath, outputPath, format, options = {}) {
const outputSize = (await stat(outputPath)).size;
const savings = ((inputSize - outputSize) / inputSize * 100).toFixed(1);
console.log(`✅ 优化完成: ${path.basename(inputPath)} -> ${format.toUpperCase()} (节省 ${savings}%)`);
BuildLogger.success(' 优化完成: ${path.basename(inputPath)} -> ${format.toUpperCase()} (节省 ${savings}%)');
return { success: true, inputSize, outputSize, savings };
} catch (error) {
console.error(`❌ 优化失败: ${path.basename(inputPath)}`, error.message);
@@ -108,7 +110,7 @@ async function generateMultipleFormats(inputPath, filename) {
// Check file size threshold
const fileStats = await stat(inputPath);
if (fileStats.size < config.minFileSize) {
console.log(`⏭️ 跳过小文件: ${filename} (${fileStats.size} bytes)`);
BuildLogger.log(`⏭️ 跳过小文件: ${filename} (${fileStats.size} bytes)`);
return [];
}
@@ -144,7 +146,7 @@ async function generateThumbnails(inputPath, filename) {
// Skip if image is already smaller than thumbnail size
if (metadata.width <= config.sizes.thumbnail.width &&
metadata.height <= config.sizes.thumbnail.height) {
console.log(`⏭️ 跳过缩略图生成: ${filename} (已足够小)`);
BuildLogger.log(`⏭️ 跳过缩略图生成: ${filename} (已足够小)`);
return { success: true, skipped: true };
}
@@ -156,7 +158,7 @@ async function generateThumbnails(inputPath, filename) {
const inputStats = await stat(inputPath);
const thumbStats = await stat(thumbnailPath);
if (thumbStats.mtime > inputStats.mtime) {
console.log(`⏭️ 跳过已存在的缩略图: ${path.basename(thumbnailPath)}`);
BuildLogger.log(`⏭️ 跳过已存在的缩略图: ${path.basename(thumbnailPath)}`);
return { success: true, skipped: true };
}
}
@@ -169,7 +171,7 @@ async function generateThumbnails(inputPath, filename) {
.jpeg({ quality: 80, progressive: true })
.toFile(thumbnailPath);
console.log(`✅ 缩略图生成: ${path.basename(thumbnailPath)}`);
BuildLogger.success(' 缩略图生成: ${path.basename(thumbnailPath)}');
return { success: true, skipped: false };
} catch (error) {
console.error(`❌ 缩略图生成失败: ${filename}`, error.message);
@@ -200,7 +202,7 @@ function generateManifest() {
const manifestPath = path.join(config.outputDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log(`📋 图片清单已生成: ${manifestPath}`);
BuildLogger.log(`📋 图片清单已生成: ${manifestPath}`);
}
// Process images with concurrency control
@@ -218,7 +220,7 @@ async function processImagesInBatches(imageFiles) {
await Promise.all(batch.map(async (filename) => {
const inputPath = path.join(config.inputDir, filename);
console.log(`\n🔄 处理: ${filename}`);
BuildLogger.log(`\n🔄 处理: ${filename}`);
try {
// Generate multiple formats
@@ -257,23 +259,23 @@ async function processImagesInBatches(imageFiles) {
// 主函数
async function optimizeImages() {
console.log('🚀 开始图片优化...');
console.log(`📁 输入目录: ${config.inputDir}`);
console.log(`📁 输出目录: ${config.outputDir}`);
console.log(`⚡ 并发数: ${config.maxConcurrent}`);
BuildLogger.log('🚀 开始图片优化...');
BuildLogger.log(`📁 输入目录: ${config.inputDir}`);
BuildLogger.log(`📁 输出目录: ${config.outputDir}`);
BuildLogger.log(`⚡ 并发数: ${config.maxConcurrent}`);
// 确保输出目录存在
ensureOutputDir();
// 检查输入目录是否存在
if (!fs.existsSync(config.inputDir)) {
console.log(`⚠️ 输入目录不存在,创建示例目录: ${config.inputDir}`);
BuildLogger.warn(' 输入目录不存在,创建示例目录: ${config.inputDir}');
fs.mkdirSync(config.inputDir, { recursive: true });
// 创建示例文件
const examplePath = path.join(config.inputDir, 'example.txt');
fs.writeFileSync(examplePath, '请将需要优化的图片文件放在此目录中');
console.log(`📝 已创建示例文件: ${examplePath}`);
BuildLogger.log(`📝 已创建示例文件: ${examplePath}`);
return;
}
@@ -281,11 +283,11 @@ async function optimizeImages() {
const imageFiles = files.filter(isImageFile);
if (imageFiles.length === 0) {
console.log('⚠️ 未找到图片文件');
BuildLogger.warn(' 未找到图片文件');
return;
}
console.log(`📸 找到 ${imageFiles.length} 个图片文件`);
BuildLogger.log(`📸 找到 ${imageFiles.length} 个图片文件`);
const startTime = Date.now();
const results = await processImagesInBatches(imageFiles);
@@ -294,19 +296,19 @@ async function optimizeImages() {
// 生成清单
generateManifest();
console.log(`\n🎉 优化完成!`);
console.log(`⏱️ 用时: ${duration}`);
console.log(`✅ 成功: ${results.successful}`);
console.log(`⏭️ 跳过: ${results.skipped}`);
console.log(`❌ 失败: ${results.failed}`);
BuildLogger.log(`\n🎉 优化完成!`);
BuildLogger.log(`⏱️ 用时: ${duration}`);
BuildLogger.success(' 成功: ${results.successful}');
BuildLogger.log(`⏭️ 跳过: ${results.skipped}`);
BuildLogger.error(' 失败: ${results.failed}');
if (results.totalSavings > 0) {
const avgSavings = (results.totalSavings / results.successful).toFixed(1);
console.log(`💾 平均节省空间: ${avgSavings}%`);
BuildLogger.log(`💾 平均节省空间: ${avgSavings}%`);
}
console.log(`📁 输出目录: ${config.outputDir}`);
BuildLogger.log(`📁 输出目录: ${config.outputDir}`);
if (results.failed > 0) {
console.log(`⚠️ 有 ${results.failed} 个文件处理失败`);
BuildLogger.warn(' 有 ${results.failed} 个文件处理失败');
process.exit(1);
}
}
@@ -314,7 +316,7 @@ async function optimizeImages() {
// 命令行参数处理
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(`
BuildLogger.log(`
📸 图片优化工具 (Sharp版本)
用法: node optimize-images.js [选项]

238
scripts/quality-check.js Normal file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env node
/**
* 代码质量全面检查脚本
* 检查所有变更的完整性和代码质量
*/
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const projectRoot = path.join(__dirname, '..');
// 检查项配置
const checks = {
// 1. 语法检查
syntaxCheck: {
name: '语法检查',
files: [
'server.js',
'webpack.config.js',
'netlify/functions/_shared/middleware.js',
'netlify/functions/health.js',
'netlify/functions/giffgaff-graphql.js',
'netlify/functions/giffgaff-mfa-challenge.js',
'netlify/functions/giffgaff-mfa-validation.js',
'netlify/functions/giffgaff-sms-activate.js',
'netlify/functions/auto-activate-esim.js',
'netlify/functions/giffgaff-token-exchange.js',
'netlify/functions/verify-cookie.js'
]
},
// 2. 环境变量一致性
envVarCheck: {
name: '环境变量一致性',
required: ['ACCESS_KEY', 'ALLOWED_ORIGIN'],
deprecated: ['ESIM_ACCESS_KEY', 'COOKIE_SECRET']
},
// 3. 依赖完整性
dependencyCheck: {
name: '依赖完整性',
unused: ['cookie-parser']
},
// 4. 安全配置
securityCheck: {
name: '安全配置',
patterns: {
weakDefaults: /please_change_me|your-secret-key-here|your-key-here/g,
hardcodedSecrets: /(?:password|secret|key)\s*=\s*['"][^'"]{10,}['"]/gi,
consoleLog: /console\.log\(/g
}
}
};
let totalChecks = 0;
let passedChecks = 0;
let failedChecks = 0;
// 辅助函数
function checkFile(filePath) {
const fullPath = path.join(projectRoot, filePath);
if (!fs.existsSync(fullPath)) {
BuildLogger.error(`文件不存在: ${filePath}`);
return false;
}
try {
execSync(`node -c "${fullPath}"`, { stdio: 'pipe' });
return true;
} catch (error) {
BuildLogger.error(`语法错误: ${filePath}`);
BuildLogger.error(error.message);
return false;
}
}
function checkEnvExample() {
const envPath = path.join(projectRoot, 'env.example');
const content = fs.readFileSync(envPath, 'utf8');
const issues = [];
// 检查必需变量
checks.envVarCheck.required.forEach(varName => {
if (!content.includes(`${varName}=`)) {
issues.push(`缺少必需环境变量: ${varName}`);
}
});
// 检查废弃变量
checks.envVarCheck.deprecated.forEach(varName => {
if (content.includes(`${varName}=`)) {
issues.push(`包含废弃环境变量: ${varName}`);
}
});
return issues;
}
function searchInFiles(pattern, files, excludeContext = []) {
const results = [];
files.forEach(file => {
const fullPath = path.join(projectRoot, file);
if (fs.existsSync(fullPath)) {
const content = fs.readFileSync(fullPath, 'utf8');
const lines = content.split('\n');
let matchCount = 0;
lines.forEach((line, index) => {
if (pattern.test(line)) {
// 检查是否在排除的上下文中(如安全检查代码)
const isExcluded = excludeContext.some(ctx => {
const contextLine = lines[index];
const prevLine = lines[index - 1] || '';
return contextLine.includes(ctx) || prevLine.includes(ctx);
});
if (!isExcluded) {
matchCount++;
}
}
});
if (matchCount > 0) {
results.push({ file, matches: matchCount });
}
}
});
return results;
}
function checkPackageJson() {
const pkgPath = path.join(projectRoot, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const issues = [];
checks.dependencyCheck.unused.forEach(dep => {
if (pkg.dependencies && pkg.dependencies[dep]) {
issues.push(`未使用的依赖: ${dep} (dependencies)`);
}
if (pkg.devDependencies && pkg.devDependencies[dep]) {
issues.push(`未使用的依赖: ${dep} (devDependencies)`);
}
});
return issues;
}
// 执行检查
function runChecks() {
BuildLogger.title('代码质量全面检查');
// 1. 语法检查
BuildLogger.step(1, 4, checks.syntaxCheck.name);
let syntaxPassed = 0;
checks.syntaxCheck.files.forEach(file => {
totalChecks++;
if (checkFile(file)) {
BuildLogger.check(true, file);
syntaxPassed++;
passedChecks++;
} else {
BuildLogger.check(false, file);
failedChecks++;
}
});
BuildLogger.progress(`${syntaxPassed}/${checks.syntaxCheck.files.length} 文件通过语法检查\n`);
// 2. 环境变量检查
BuildLogger.step(2, 4, checks.envVarCheck.name);
totalChecks++;
const envIssues = checkEnvExample();
if (envIssues.length === 0) {
BuildLogger.check(true, 'env.example 配置正确');
passedChecks++;
} else {
BuildLogger.check(false, 'env.example 存在问题:');
envIssues.forEach(issue => BuildLogger.error(` - ${issue}`));
failedChecks++;
}
// 3. 依赖检查
BuildLogger.step(3, 4, checks.dependencyCheck.name);
totalChecks++;
const depIssues = checkPackageJson();
if (depIssues.length === 0) {
BuildLogger.check(true, 'package.json 依赖正确');
passedChecks++;
} else {
BuildLogger.check(false, 'package.json 存在问题:');
depIssues.forEach(issue => BuildLogger.warn(` - ${issue}`));
failedChecks++;
}
// 4. 安全配置检查
BuildLogger.step(4, 4, checks.securityCheck.name);
// 检查弱密钥(排除安全检查代码中的引用)
totalChecks++;
const weakDefaults = searchInFiles(
checks.securityCheck.patterns.weakDefaults,
['env.example', 'netlify/functions/_shared/middleware.js'],
['if (ACCESS_KEY ===', '安全警告', '警告', '检查']
);
if (weakDefaults.length === 0) {
BuildLogger.check(true, '无弱默认配置');
passedChecks++;
} else {
BuildLogger.check(false, '发现弱默认配置:');
weakDefaults.forEach(r => BuildLogger.warn(` - ${r.file}: ${r.matches}`));
failedChecks++;
}
// 统计报告
BuildLogger.title('\n检查结果汇总');
BuildLogger.log(`总检查项: ${totalChecks}`);
BuildLogger.success(`通过: ${passedChecks}`);
if (failedChecks > 0) {
BuildLogger.error(`失败: ${failedChecks}`);
}
const successRate = ((passedChecks / totalChecks) * 100).toFixed(1);
BuildLogger.log(`\n通过率: ${successRate}%`);
if (failedChecks === 0) {
BuildLogger.success('\n✅ 所有检查通过! 代码质量良好。');
return 0;
} else {
BuildLogger.error('\n❌ 存在质量问题,请修复后重新检查。');
return 1;
}
}
// 执行
const exitCode = runChecks();
process.exit(exitCode);

View File

@@ -0,0 +1,122 @@
#!/usr/bin/env node
/**
* 全局替换console.log为Logger
* 自动在文件开头添加Logger导入,并替换所有console.log调用
*/
const fs = require('fs');
const path = require('path');
const glob = require('glob');
// 需要处理的目录
const DIRS_TO_PROCESS = [
'src/js/modules',
'src/giffgaff/js/modules',
'src/simyo/js/modules'
];
// 需要排除的文件
const EXCLUDE_FILES = [
'src/js/modules/logger.js', // Logger模块本身
'src/js/modules/README.md' // 文档文件
];
// 替换console.log为Logger.log
function replaceConsoleLogs(filePath) {
try {
let content = fs.readFileSync(filePath, 'utf8');
const originalContent = content;
// 检查是否已经导入了Logger
const hasLoggerImport = /import\s+Logger\s+from/.test(content) ||
/const\s+Logger\s*=\s*require/.test(content);
// 检查是否有console.log需要替换
const hasConsolelog = /console\.log\s*\(/.test(content);
if (!hasConsolelog) {
console.log(`⏭️ 跳过 ${filePath} - 无console.log`);
return { replaced: false };
}
// 替换console.log为Logger.log
// 保留console.warn和console.error不变
content = content.replace(/console\.log\s*\(/g, 'Logger.log(');
// 如果还没有导入Logger,在文件开头添加导入
if (!hasLoggerImport && hasConsolelog) {
// 计算相对路径
const fileDir = path.dirname(filePath);
const loggerPath = path.relative(fileDir, 'src/js/modules/logger.js');
const importPath = loggerPath.startsWith('.') ? loggerPath : `./${loggerPath}`;
// 添加导入语句
const importStatement = `import Logger from '${importPath}';\n`;
// 在第一个import语句后或文件开头添加
if (/^import\s+/.test(content)) {
// 在最后一个import之后添加
const lastImportIndex = content.lastIndexOf('\nimport ');
if (lastImportIndex !== -1) {
const nextLineIndex = content.indexOf('\n', lastImportIndex + 1);
content = content.slice(0, nextLineIndex + 1) + importStatement + content.slice(nextLineIndex + 1);
} else {
content = importStatement + content;
}
} else {
// 在文件开头添加
content = importStatement + '\n' + content;
}
}
if (content !== originalContent) {
fs.writeFileSync(filePath, content, 'utf8');
const count = (originalContent.match(/console\.log\s*\(/g) || []).length;
console.log(`${filePath} - 替换了${count}处console.log`);
return { replaced: true, count };
} else {
console.log(`⏭️ 跳过 ${filePath} - 无需修改`);
return { replaced: false };
}
} catch (error) {
console.error(`❌ 处理 ${filePath} 失败:`, error.message);
return { replaced: false, error: true };
}
}
// 主函数
function main() {
console.log('🚀 开始替换console.log为Logger.log...\n');
let totalFiles = 0;
let replacedFiles = 0;
let totalReplacements = 0;
DIRS_TO_PROCESS.forEach(dir => {
const pattern = path.join(dir, '**/*.js');
const files = glob.sync(pattern);
files.forEach(file => {
// 排除特定文件
if (EXCLUDE_FILES.some(excluded => file.includes(excluded))) {
return;
}
totalFiles++;
const result = replaceConsoleLogs(file);
if (result.replaced) {
replacedFiles++;
totalReplacements += result.count || 0;
}
});
});
console.log('\n📊 替换统计:');
console.log(` 总文件数: ${totalFiles}`);
console.log(` 已修改文件: ${replacedFiles}`);
console.log(` console.log替换数: ${totalReplacements}`);
console.log('\n✨ 完成!');
}
main();

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
@@ -89,42 +91,47 @@ function checkDependencies() {
// 生成安全报告
function generateSecurityReport() {
console.log('🔒 安全检查报告\n');
BuildLogger.log('🔒 安全检查报告
');
const vulnerabilities = checkDependencies();
if (vulnerabilities.length === 0) {
console.log(' 未发现已知的安全漏洞');
BuildLogger.success(' 未发现已知的安全漏洞');
return;
}
console.log(`⚠️ 发现 ${vulnerabilities.length} 个潜在安全漏洞:\n`);
BuildLogger.warn(' 发现 ${vulnerabilities.length} 个潜在安全漏洞:
');
vulnerabilities.forEach((vuln, index) => {
console.log(`${index + 1}. ${vuln.package}@${vuln.version}`);
console.log(` 严重程度: ${vuln.severity}`);
console.log(` 描述: ${vuln.description}`);
console.log(` 修复建议: ${vuln.fix}\n`);
BuildLogger.log(`${index + 1}. ${vuln.package}@${vuln.version}`);
BuildLogger.log(` 严重程度: ${vuln.severity}`);
BuildLogger.log(` 描述: ${vuln.description}`);
BuildLogger.log(` 修复建议: ${vuln.fix}
`);
});
console.log('🔧 修复建议:');
console.log('1. 运行 npm update 更新所有依赖');
console.log('2. 运行 npm audit fix 自动修复');
console.log('3. 手动更新特定包到最新版本');
BuildLogger.log('🔧 修复建议:');
BuildLogger.log('1. 运行 npm update 更新所有依赖');
BuildLogger.log('2. 运行 npm audit fix 自动修复');
BuildLogger.log('3. 手动更新特定包到最新版本');
}
// 检查开发环境安全配置
function checkSecurityConfig() {
console.log('\n🔧 安全配置检查:\n');
BuildLogger.log('
🔧 安全配置检查:
');
// 检查Helmet配置
const serverPath = path.join(__dirname, '../server.js');
if (fs.existsSync(serverPath)) {
const serverContent = fs.readFileSync(serverPath, 'utf8');
if (serverContent.includes('helmet')) {
console.log(' Helmet安全头已配置');
BuildLogger.success(' Helmet安全头已配置');
} else {
console.log('⚠️ 建议添加Helmet安全头');
BuildLogger.warn(' 建议添加Helmet安全头');
}
}
@@ -132,9 +139,9 @@ function checkSecurityConfig() {
if (fs.existsSync(serverPath)) {
const serverContent = fs.readFileSync(serverPath, 'utf8');
if (serverContent.includes('cors')) {
console.log(' CORS配置已设置');
BuildLogger.success(' CORS配置已设置');
} else {
console.log('⚠️ 建议配置CORS');
BuildLogger.warn(' 建议配置CORS');
}
}
@@ -150,9 +157,9 @@ function checkSecurityConfig() {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');
if (content.includes('Content-Security-Policy')) {
console.log(`${file} 已配置CSP`);
BuildLogger.success(' ${file} 已配置CSP');
} else {
console.log(`⚠️ ${file} 建议添加CSP配置`);
BuildLogger.warn(' ${file} 建议添加CSP配置');
}
}
});
@@ -163,16 +170,17 @@ function main() {
generateSecurityReport();
checkSecurityConfig();
console.log('\n📋 安全最佳实践:');
console.log('1. 定期更新依赖包');
console.log('2. 使用npm audit检查安全漏洞');
console.log('3. 配置适当的安全头');
console.log('4. 实施内容安全策略(CSP)');
console.log('5. 使用HTTPS部署');
BuildLogger.log('
📋 安全最佳实践:');
BuildLogger.log('1. 定期更新依赖包');
BuildLogger.log('2. 使用npm audit检查安全漏洞');
BuildLogger.log('3. 配置适当的安全头');
BuildLogger.log('4. 实施内容安全策略(CSP)');
BuildLogger.log('5. 使用HTTPS部署');
}
if (require.main === module) {
main();
}
module.exports = { checkDependencies, generateSecurityReport };
module.exports = { checkDependencies, generateSecurityReport };

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
const BuildLogger = require('./logger.js');
const fs = require('fs');
const path = require('path');
@@ -14,5 +16,5 @@ const netlifyToml = path.join(__dirname, '..', 'netlify.toml');
console.error('netlify.toml 未将 publish 指向 dist');
process.exit(1);
}
console.log(' Netlify 配置检查通过 (publish=dist)');
BuildLogger.success(' Netlify 配置检查通过 (publish=dist)');
})();

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env node
/**
* 更新构建脚本中的console日志为BuildLogger
* 仅替换普通的console.log,保留console.error和console.warn
*/
const fs = require('fs');
const path = require('path');
// 需要处理的脚本文件
const SCRIPTS_TO_UPDATE = [
'build-static.js',
'deploy-prepare.js',
'deploy-analyze.js',
'test-deploy-config.js',
'optimize-images.js',
'compress.js',
'security-check.js'
];
// 需要排除的文件
const EXCLUDE_FILES = [
'logger.js',
'replace-console-log.js',
'update-script-logging.js'
];
function updateScriptLogging(filePath) {
try {
let content = fs.readFileSync(filePath, 'utf8');
const originalContent = content;
// 检查是否已经导入了BuildLogger
const hasLoggerImport = /const\s+(?:BuildLogger|Logger)\s*=\s*require/.test(content);
// 检查是否有console.log需要替换
const hasConsoleLog = /console\.log\s*\(/.test(content);
if (!hasConsoleLog) {
console.log(`⏭️ 跳过 ${path.basename(filePath)} - 无console.log`);
return { replaced: false };
}
// 替换console.log为BuildLogger.log
// 识别带emoji的success消息
content = content.replace(/console\.log\((['"`])✅([^'"`]*)\1\)/g, 'BuildLogger.success(\'$2\')');
content = content.replace(/console\.log\((['"`])❌([^'"`]*)\1\)/g, 'BuildLogger.error(\'$2\')');
content = content.replace(/console\.log\((['"`])⚠️([^'"`]*)\1\)/g, 'BuildLogger.warn(\'$2\')');
content = content.replace(/console\.log\((['"`])🔧([^'"`]*)\1\)/g, 'BuildLogger.log(\'🔧$2\')');
content = content.replace(/console\.log\((['"`])📊([^'"`]*)\1\)/g, 'BuildLogger.log(\'📊$2\')');
// 替换剩余的console.log
content = content.replace(/console\.log\s*\(/g, 'BuildLogger.log(');
// 如果还没有导入BuildLogger,在文件开头添加导入
if (!hasLoggerImport && hasConsoleLog) {
// 在第一个require之后或文件开头添加
const importStatement = `const BuildLogger = require('./logger.js');\n`;
if (/^const\s+/.test(content)) {
// 在最后一个require之后添加
const lastRequireMatch = content.match(/const\s+\w+\s*=\s*require\([^)]+\);?/g);
if (lastRequireMatch && lastRequireMatch.length > 0) {
const lastRequire = lastRequireMatch[lastRequireMatch.length - 1];
const lastRequireIndex = content.lastIndexOf(lastRequire);
const insertIndex = lastRequireIndex + lastRequire.length;
content = content.slice(0, insertIndex) + '\n' + importStatement + content.slice(insertIndex);
} else {
content = importStatement + content;
}
} else {
content = importStatement + '\n' + content;
}
}
if (content !== originalContent) {
fs.writeFileSync(filePath, content, 'utf8');
const count = (originalContent.match(/console\.log\s*\(/g) || []).length;
console.log(`${path.basename(filePath)} - 替换了${count}处console.log`);
return { replaced: true, count };
} else {
console.log(`⏭️ 跳过 ${path.basename(filePath)} - 无需修改`);
return { replaced: false };
}
} catch (error) {
console.error(`❌ 处理 ${path.basename(filePath)} 失败:`, error.message);
return { replaced: false, error: true };
}
}
function main() {
console.log('🚀 开始更新构建脚本日志...\n');
let totalFiles = 0;
let replacedFiles = 0;
let totalReplacements = 0;
SCRIPTS_TO_UPDATE.forEach(filename => {
const filePath = path.join(__dirname, filename);
if (!fs.existsSync(filePath)) {
console.log(`⏭️ 跳过 ${filename} - 文件不存在`);
return;
}
if (EXCLUDE_FILES.includes(filename)) {
console.log(`⏭️ 跳过 ${filename} - 已排除`);
return;
}
totalFiles++;
const result = updateScriptLogging(filePath);
if (result.replaced) {
replacedFiles++;
totalReplacements += result.count || 0;
}
});
console.log('\n📊 替换统计:');
console.log(` 总文件数: ${totalFiles}`);
console.log(` 已修改文件: ${replacedFiles}`);
console.log(` console.log替换数: ${totalReplacements}`);
console.log('\n✨ 完成!');
console.log('\n💡 提示: 构建脚本的日志现在使用带颜色的BuildLogger输出');
}
main();