diff --git a/README.md b/README.md index 03a0986..2fde578 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,16 @@ npm install npm run dev ``` +## 代码门禁 + +提交前或部署前在本地执行全量检查,覆盖后端 `go vet` / `go build` / `go test` 与前端 `prettier` / `eslint` / `typecheck` / `vite build`: + +```bash +./scripts/check.sh +``` + +`./scripts/deploy-prod.sh` 在构建镜像前会自动调用 `check.sh`(本地有 `go` 和 `npm` 时)。检查未通过会中止部署;确需跳过用 `--skip-check`(不推荐)。`check.sh` 也支持 `--skip-backend`、`--skip-frontend`、`--skip-tests` 做局部调试。 + ## 开发态短信与实名 - 后端日志默认同时输出到控制台和 `backend/logs/app-YYYY-MM-DD.log`,按北京时间自动按天切分;可通过 `LOG_LEVEL=debug|info|warn|error` 调整级别。 diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..08eb7fd --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# 全量代码门禁:部署前在本地开发机执行,确保后端 + 前端检查全绿。 +# +# 用途: +# - 部署门禁:scripts/deploy-prod.sh 在 build 镜像前自动调用本脚本(--skip-check 可跳过) +# - 手动校验:开发时手动跑 ./scripts/check.sh +# +# 包含: +# 后端:go vet + go build + go test +# 前端:npm run check (prettier + eslint + typecheck) + npm run build +# +# 任一步骤失败即退出非零,阻止部署。 +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BACKEND_DIR="${ROOT_DIR}/backend" +FRONTEND_DIR="${ROOT_DIR}/frontend" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +NC='\033[0m' + +log() { printf "${BLUE}[check]${NC} %s\n" "$*"; } +log_ok() { printf "${GREEN}[check]${NC} %s\n" "$*"; } +log_warn() { printf "${YELLOW}[check]${NC} %s\n" "$*"; } +log_error() { printf "${RED}[check]${NC} %s\n" "$*" >&2; } + +SKIP_BACKEND=0 +SKIP_FRONTEND=0 +SKIP_TESTS=0 + +show_help() { + cat << EOF +用法: ./scripts/check.sh [选项] + +全量代码门禁:后端 go vet/build/test + 前端 check/build。 + +选项: + --skip-backend 跳过后端检查 + --skip-frontend 跳过前端检查 + --skip-tests 跳过测试(仅做 vet/build + lint/typecheck/build) + -h, --help 显示帮助 + +示例: + ./scripts/check.sh + ./scripts/check.sh --skip-frontend +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-backend) SKIP_BACKEND=1; shift ;; + --skip-frontend) SKIP_FRONTEND=1; shift ;; + --skip-tests) SKIP_TESTS=1; shift ;; + -h|--help) show_help; exit 0 ;; + *) + log_error "未知参数: $1" + show_help + exit 1 + ;; + esac +done + +step_start() { printf "\n${BLUE}[check]${NC} \033[1m▶ %s${NC}\n" "$1"; } +step_ok() { log_ok "✓ $1 ($2)"; } +step_fail() { log_error "✗ $1 失败"; } + +run_step() { + # run_step "步骤名" 命令... + local name="$1"; shift + local start + start=$(date +%s) + step_start "$name" + if "$@"; then + local elapsed=$(( $(date +%s) - start )) + step_ok "$name" "${elapsed}s" + else + step_fail "$name" + return 1 + fi +} + +FAILED=0 + +# --------------------------------------------------------------------------- +# 后端检查 +# --------------------------------------------------------------------------- +if [[ "${SKIP_BACKEND}" == "0" ]]; then + log "后端检查目录: ${BACKEND_DIR}" + cd "${BACKEND_DIR}" + + run_step "go vet ./..." go vet ./... || FAILED=1 + run_step "go build ./..." go build ./... || FAILED=1 + if [[ "${SKIP_TESTS}" == "0" ]]; then + # -p 1 串行避免 SQLite 内存库并发; 真实 MySQL e2e 默认 skip + run_step "go test ./..." go test -p 1 -count=1 ./... || FAILED=1 + else + log_warn "跳过后端测试 (--skip-tests)" + fi +else + log_warn "跳过后端检查 (--skip-backend)" +fi + +# --------------------------------------------------------------------------- +# 前端检查 +# --------------------------------------------------------------------------- +if [[ "${SKIP_FRONTEND}" == "0" ]]; then + log "前端检查目录: ${FRONTEND_DIR}" + cd "${FRONTEND_DIR}" + + if [[ ! -d node_modules ]]; then + run_step "npm ci (首次安装依赖)" npm ci || FAILED=1 + fi + + # npm run check = prettier --check + eslint --max-warnings=0 + vue-tsc + run_step "npm run check (prettier + eslint + typecheck)" npm run check || FAILED=1 + run_step "npm run build (vite 生产构建)" npm run build || FAILED=1 +else + log_warn "跳过前端检查 (--skip-frontend)" +fi + +# --------------------------------------------------------------------------- +# 汇总 +# --------------------------------------------------------------------------- +printf "\n" +if [[ "${FAILED}" == "0" ]]; then + log_ok "全部检查通过 ✓" + exit 0 +else + log_error "检查未通过,已阻止本次部署。可用 --skip-check 强制跳过(不推荐)。" + exit 1 +fi diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index 105c1a7..d8ad823 100755 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -63,6 +63,7 @@ show_help() { --no-migrate 不执行数据库迁移 --reset-db 重置数据库(删除并重建) --logs 部署完成后跟随查看后端日志 + --skip-check 跳过部署前代码门禁(不推荐) -h, --help 显示帮助信息 环境变量: @@ -85,6 +86,7 @@ NO_BUILD=0 NO_MIGRATE=0 RESET_DB=0 FOLLOW_LOGS=0 +SKIP_CHECK=0 while [[ $# -gt 0 ]]; do case "$1" in @@ -104,6 +106,10 @@ while [[ $# -gt 0 ]]; do FOLLOW_LOGS=1 shift ;; + --skip-check) + SKIP_CHECK=1 + shift + ;; -h|--help) show_help exit 0 @@ -324,6 +330,20 @@ main() { log "检查 Docker Compose 配置..." compose config >/dev/null + # 部署前代码门禁:本地有 go + node 时跑全量检查,挡住未通过的提交进镜像。 + # 部署机无 go/node 或 --skip-check 时跳过并告警,不阻断(避免环境耦合)。 + if [[ "${NO_BUILD}" == "0" && "${SKIP_CHECK}" == "0" ]]; then + if command -v go >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then + log "执行部署前代码门禁 (scripts/check.sh)..." + if ! bash "${ROOT_DIR}/scripts/check.sh"; then + log_error "代码门禁未通过,已中止部署。确认修复后重试,或用 --skip-check 强制跳过。" + exit 1 + fi + else + log_warn "本地缺少 go 或 npm,跳过代码门禁。建议在开发机执行 ./scripts/check.sh 后再部署。" + fi + fi + if [[ "${NO_BUILD}" == "1" ]]; then log "启动生产服务..." compose up -d