#!/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 } # 数据库运行时配置和参数脱敏不能被重构删除。 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" "后台接单分页上限" # 已发布迁移只允许新增,不允许修改或删除历史文件。 if changed_migrations="$(git -C "${ROOT_DIR}" diff --name-status HEAD -- 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'