将 000064-000078 合并为 064/065/066,默认规则改为收号 35%、卖出 40%、服务费 25%。 货架调价改为增加分站 1 元兑换哈夫币比例。sql-guard 仅保护 main 已发布迁移。
102 lines
5.9 KiB
Bash
Executable File
102 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 检查关键 SQL 防线是否仍存在;真实数据量下的执行计划需要在 MySQL 上单独验证。
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
MIGRATIONS_DIR="${ROOT_DIR}/backend/migrations"
|
|
failures=()
|
|
|
|
require_pattern() {
|
|
local file="$1" pattern="$2" name="$3"
|
|
if [[ ! -f "${ROOT_DIR}/${file}" ]]; then
|
|
failures+=("${name}: 文件不存在 ${file}")
|
|
elif ! grep -Fq -- "${pattern}" "${ROOT_DIR}/${file}"; then
|
|
failures+=("${name}: 缺少 ${pattern} (${file})")
|
|
fi
|
|
}
|
|
|
|
forbid_pattern() {
|
|
local file="$1" pattern="$2" name="$3"
|
|
if [[ -f "${ROOT_DIR}/${file}" ]] && grep -Fq -- "${pattern}" "${ROOT_DIR}/${file}"; then
|
|
failures+=("${name}: 存在被禁止的跨表全扫描反模式 ${pattern} (${file})")
|
|
fi
|
|
}
|
|
|
|
# 数据库运行时配置和参数脱敏不能被重构删除。
|
|
require_pattern "backend/internal/config/config.go" "DATABASE_SLOW_QUERY_THRESHOLD_MS" "慢查询阈值配置"
|
|
require_pattern "backend/internal/config/config.go" "DATABASE_MAX_OPEN_CONNS" "连接池上限配置"
|
|
require_pattern "backend/internal/database/gorm_logger.go" "ParamsFilter" "SQL 参数脱敏"
|
|
require_pattern "backend/internal/database/mysql.go" "SetMaxOpenConns" "连接池应用"
|
|
|
|
# 当前财务仪表盘的时间范围聚合和索引是高频查询防线。
|
|
require_pattern "backend/internal/modules/adminfinance/subquery.go" "settled_at >= ? AND scoped_order.settled_at <= ?" "结算聚合时间范围"
|
|
require_pattern "backend/internal/modules/adminfinance/dashboard.go" "po.created_at >= ? AND po.created_at <= ?" "支付聚合时间范围"
|
|
require_pattern "backend/migrations/000060_finance_dashboard_indexes.sql" "idx_admin_pickups_status_completed_at" "财务完成索引"
|
|
require_pattern "backend/migrations/000060_finance_dashboard_indexes.sql" "idx_admin_pickups_status_cancelled_at" "财务取消索引"
|
|
|
|
# 分页核心路径必须保留上限;配置型全量列表在运维文档中登记为白名单。
|
|
require_pattern "backend/internal/modules/listing/public_query.go" "if pageSize > 50" "公开商品分页上限"
|
|
require_pattern "backend/internal/modules/pickup/repository.go" "if pageSize > 100" "后台接单分页上限"
|
|
|
|
# 智能意图路由与单表唯一索引直达防线(防止多表跨表 OR/LIKE 导致 MySQL 放弃索引而退化为全表扫描)
|
|
require_pattern "backend/internal/modules/order/queries.go" "o.order_no = ?" "订单列表订单号唯一索引直达"
|
|
require_pattern "backend/internal/modules/order/queries.go" "l.listing_no = ?" "订单列表商品号唯一索引直达"
|
|
require_pattern "backend/internal/modules/order/queries.go" "owner.phone = ? OR renter.phone = ?" "订单列表手机号索引直达"
|
|
require_pattern "backend/internal/modules/order/queries.go" "page == 1 && len(rows) < pageSize" "订单列表首页面单扫描短路"
|
|
|
|
require_pattern "backend/internal/modules/pickup/repository.go" "p.pickup_no = ?" "提号列表提号单号唯一索引直达"
|
|
require_pattern "backend/internal/modules/pickup/repository.go" "owner.phone = ?" "提号列表号主手机号索引直达"
|
|
require_pattern "backend/internal/modules/pickup/repository.go" "page == 1 && len(rows) < pageSize" "提号列表首页面单扫描短路"
|
|
|
|
require_pattern "backend/internal/modules/dispute/query.go" "o.order_no = ?" "纠纷列表订单号唯一索引直达"
|
|
require_pattern "backend/internal/modules/dispute/query.go" "page == 1 && len(rows) < pageSize" "纠纷列表首页面单扫描短路"
|
|
|
|
require_pattern "backend/internal/modules/adminuser/repository.go" "u.phone = ?" "用户管理手机号唯一索引直达"
|
|
require_pattern "backend/internal/modules/adminuser/repository.go" "page == 1 && len(rows) < pageSize" "用户管理首页面单扫描短路"
|
|
|
|
require_pattern "backend/internal/modules/listing/query.go" "l.listing_no = ?" "商品列表商品号唯一索引直达"
|
|
require_pattern "backend/internal/modules/listing/query.go" "page == 1 && len(rows) < pageSize" "商品列表首页面单扫描短路"
|
|
|
|
require_pattern "backend/internal/modules/chat/support.go" "l.listing_no = ?" "客服会话商品号唯一索引直达"
|
|
require_pattern "backend/internal/modules/chat/support.go" "renter.phone = ? OR owner.phone = ?" "客服会话手机号唯一索引直达"
|
|
|
|
# 禁止核心列表重新引入跨表 6~10 路全扫描反模式
|
|
forbid_pattern "backend/internal/modules/dispute/query.go" "o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ? OR d.id = ?" "纠纷列表禁止6表无意图全表扫描"
|
|
|
|
# 已合入 main 的迁移只允许新增,不允许修改或删除。
|
|
# 当前分支尚未合入 main 的迁移可以整段重写。
|
|
published_ref=""
|
|
if git -C "${ROOT_DIR}" rev-parse --verify main >/dev/null 2>&1; then
|
|
published_ref="main"
|
|
elif git -C "${ROOT_DIR}" rev-parse --verify origin/main >/dev/null 2>&1; then
|
|
published_ref="origin/main"
|
|
fi
|
|
if [[ -z "${published_ref}" ]]; then
|
|
failures+=("无法确定 main 分支,无法检查已发布迁移")
|
|
elif changed_migrations="$(git -C "${ROOT_DIR}" diff --name-status "${published_ref}" -- backend/migrations)"; then
|
|
while read -r status file _; do
|
|
[[ -z "${status:-}" ]] && continue
|
|
if [[ "${status}" != "A" ]]; then
|
|
failures+=("已发布迁移文件不可修改或删除: ${status} ${file}")
|
|
fi
|
|
done <<< "${changed_migrations}"
|
|
else
|
|
failures+=("无法检查迁移文件变更")
|
|
fi
|
|
|
|
# 初始合并迁移没有 Down,其余迁移必须提供可回滚段。
|
|
while IFS= read -r file; do
|
|
[[ "$(basename "${file}")" == "000001_init.sql" ]] && continue
|
|
if ! grep -Fq -- '-- +goose Up' "${file}" || ! grep -Fq -- '-- +goose Down' "${file}"; then
|
|
failures+=("迁移缺少 Goose Up/Down: ${file#"${ROOT_DIR}/"}")
|
|
fi
|
|
done < <(find "${MIGRATIONS_DIR}" -maxdepth 1 -type f -name '*.sql' | sort)
|
|
|
|
if (( ${#failures[@]} > 0 )); then
|
|
printf '[sql-guard] SQL 约束检查失败\n' >&2
|
|
printf '[sql-guard] %s\n' "${failures[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf '[sql-guard] SQL 约束检查通过\n'
|