本文目录导读:

Git 钩子脚本可以通过在代码提交前或提交后运行性能检查工具,来实施“性能门禁”,以下是一些常见的方法和示例脚本,涵盖前端(JS/TS)和后端(Java/Python/Go)等场景。
性能门禁的核心思路
性能门禁通常检查以下维度:
- 代码体积/资源体积:避免提交过大的文件或打包产物
- 执行时间:防止引入明显变慢的函数或算法
- 内存/CPU 使用:检测内存泄漏或高消耗操作
- 网络请求:检查 API 调用次数、payload 大小
- 构建速度:防止退化导致构建过慢
常用工具与集成方式
| 检查类型 | 推荐工具 | 适用场景 |
|---|---|---|
| 代码复杂度 | ESLint (复杂度规则)、Plato、Radon | JS/TS/Python |
| 文件体积 | du、size-limit、webpack-bundle-analyzer |
前端资源 |
| 打包体积 | size-limit、bundlesize |
前端构建 |
| 函数执行时间 | 自定义 Benchmark 脚本 | 后端关键逻辑 |
| 内存泄漏 | Node.js heap snapshot、Valgrind | 后端/服务端 |
| API 性能 | Lighthouse CI (lighthouse-ci) | 前端性能评分 |
| 构建时间 | 计时脚本 | CI/CD 环境 |
Git 钩子检查性能门禁示例
检查文件大小上限(pre-commit 钩子)
#!/bin/bash
# .git/hooks/pre-commit
# 设置最大文件大小(单位:字节,这里设为 1MB)
MAX_SIZE=$((1 * 1024 * 1024))
# 获取本次提交的改动文件(仅暂存区)
changed_files=$(git diff --cached --name-only --diff-filter=ACM)
threshold_exceeded=false
for file in $changed_files; do
if [ -f "$file" ]; then
file_size=$(stat --format=%s "$file")
if [ "$file_size" -gt "$MAX_SIZE" ]; then
echo "❌ 性能门禁失败: $file 大小为 $(numfmt --to=iec $file_size),超出限制 $(numfmt --to=iec $MAX_SIZE)"
threshold_exceeded=true
fi
fi
done
if [ "$threshold_exceeded" = true ]; then
echo "请优化大文件后再提交。"
exit 1
fi
exit 0
检查前端打包体积(pre-push 钩子,需要 Node.js)
// .git/hooks/pre-push (先设置可执行权限)
// 需要 Node.js 环境
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
// 配置:最大允许的打包体积(字节)
const MAX_BUNDLE_SIZE = 200 * 1024; // 200KB
try {
// 运行构建(仅用于检查,不覆盖dist)
execSync('npm run build -- --profile', {
cwd: process.cwd(),
stdio: 'pipe',
timeout: 120000 // 2分钟超时
});
// 找到构建产物(以 dist 为例)
const distDir = path.join(process.cwd(), 'dist');
if (!fs.existsSync(distDir)) {
console.log('⚠️ 未找到 dist 目录,跳过打包体积检查');
process.exit(0);
}
const files = fs.readdirSync(distDir, { recursive: true });
let totalSize = 0;
for (const file of files) {
const fullPath = path.join(distDir, file);
if (fs.statSync(fullPath).isFile()) {
totalSize += fs.statSync(fullPath).size;
}
}
if (totalSize > MAX_BUNDLE_SIZE) {
console.error(`❌ 性能门禁失败: 打包总大小 ${(totalSize / 1024).toFixed(2)}KB,超过限制 ${(MAX_BUNDLE_SIZE / 1024).toFixed(2)}KB`);
process.exit(1);
} else {
console.log(`✅ 打包体积检查通过: ${(totalSize / 1024).toFixed(2)}KB`);
}
} catch (error) {
console.error('❌ 构建或体积检查出错:', error.message);
process.exit(1);
}
检查代码复杂度(pre-commit,ESLint)
#!/bin/bash
# .git/hooks/pre-commit (需要已安装 eslint)
# 检查是否有 ESLint 配置
if [ ! -f ".eslintrc.js" ] && [ ! -f ".eslintrc.json" ]; then
echo "⚠️ 未找到 ESLint 配置,跳过复杂度检查"
exit 0
fi
# 获取被修改的 JS/TS 文件
changed_js_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx)$')
if [ -z "$changed_js_files" ]; then
exit 0
fi
# 运行 ESLint 复杂度规则(必须配置了 complexity 规则)
npx eslint --rule 'complexity: ["error", 10]' $changed_js_files 2>&1
if [ $? -ne 0 ]; then
echo "❌ 性能门禁失败: 存在高复杂度函数,请重构后提交"
exit 1
fi
exit 0
检查数据库查询/API 性能(后端 pre-commit,自定义脚本)
#!/bin/bash
# .git/hooks/pre-commit
# 检查是否有 SQL 查询或 API 调用的改动
changed_sql_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sql|prisma|entity|repository)\.(ts|js|py|java)$')
if [ -z "$changed_sql_files" ]; then
exit 0
fi
# 演示:检查是否包含 N+1 查询模式(关键字:forEach 内调用 find/findOne)
for file in $changed_sql_files; do
if grep -qP 'await.*find(One)?\(.*\)\s*\)' "$file" 2>/dev/null; then
echo "❌ 性能门禁失败: $file 中可能存在 N+1 查询模式(循环内逐个查找)"
echo "建议使用批量查询(IN 子句)或 JOIN 代替"
exit 1
fi
done
echo "✅ SQL 查询模式检查通过"
exit 0
集成 Lighthouse CI 性能评分(pre-push)
# .github/workflows/performance-gate.yml (作为 GitHub Actions,但逻辑可移植到钩子)
# 实际钩子脚本:调用 lighthouse-ci 并检查分数
#!/bin/bash
# .git/hooks/pre-push
# 需要先安装 lighthouse-ci
# npm install -g @lhci/cli
# 假设有一个暂存或预览地址
URL="https://your-staging-url.com"
# 运行 Lighthouse 并输出 JSON 结果
lhci collect --url=$URL --numberOfRuns=2
lhci assert --preset=lighthouse:recommended 2>&1 | grep -q "All assertions passed"
if [ $? -ne 0 ]; then
echo "❌ 性能门禁失败: Lighthouse 分数未达到阈值"
exit 1
fi
echo "✅ 性能评分通过"
exit 0
钩子脚本放置与权限
# 将脚本放到 .git/hooks/ 下,并赋予可执行权限 chmod +x .git/hooks/pre-commit chmod +x .git/hooks/pre-push # 若要全局分发至团队,可使用 git 模板或 husky 管理
使用 Husky(前端项目推荐)
package.json 中添加命令:
npx husky add .husky/pre-commit "npx eslint --rule 'complexity: [\"error\", 10]' --ext .js,.ts --diff" npx husky add .husky/pre-push "npm run build && node scripts/check-bundle-size.js"
性能门禁的常见阈值参考
| 检查项 | 合理阈值 |
|---|---|
| 单个文件大小 | 500KB ~ 2MB(视项目类型) |
| 打包 JS 体积 | 200KB ~ 1MB(gzip 后更严格) |
| 函数圈复杂度 | 10 ~ 15 |
| 构建时间 | 2 ~ 5 分钟 |
| Lighthouse 性能评分 | ≥ 80 分 |
| 单个 API 响应时间 | < 200ms |
注意事项
- 性能门禁不要过于严格,避免阻碍正常开发,可以设置“警告”和“拒绝”两个级别。
- 只在特定分支(如 main/release)强制执行,开发分支可以只提示。
- 本地钩子可被绕过,CI/CD 管道应保留强制的性能检查。
- 避免在钩子中运行耗时过长的检查(如完整构建),可放到
pre-push或 CI 阶段。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播