GoTest/Makefile
Table 5abb3b7aa4 refactor: 将端口配置变量化并优化端口检查逻辑
- 在变量定义区添加 PORT := 1234,统一管理端口配置
- 将所有硬编码的 1234 端口替换为 $(PORT) 变量
  - dev/stage/prod 命令的端口检查逻辑
  - Docker Compose 文件生成中的端口配置
  - kill-$(PORT) 快捷命令
  - kill-port 和 kill-port-force 的帮助信息
- 优化 dev 命令的端口检查逻辑
  - 支持检测 Docker 容器占用端口
  - 提供自动停止 Docker 容器的选项
  - 停止容器后自动清理残留的端口占用
- 改进端口占用检测的准确性
  - 使用独立的命令检查(lsof/netstat/ss)
  - 验证 PID 有效性,过滤无效值(如 '-')
  - 提供更清晰的错误提示和解决方案

现在可以通过修改 Makefile 顶部的 PORT 变量来统一管理端口配置
2025-11-29 07:36:01 +08:00

559 lines
23 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Makefile for Yinli API
# 变量定义
APP_NAME := yinli-api
VERSION := $(shell git tag --sort=-version:refname | head -n 1 2>/dev/null || echo "1.0.0")
BUILD_DIR := build
DOCKER_DIR := docker
DOC_DIR := doc
# Go 相关变量
GO := go
GOFMT := gofmt
GOVET := go vet
# Docker 相关变量
DOCKER := docker
DOCKER_COMPOSE := $(DOCKER) compose
# 应用端口
PORT := 1234
# 默认目标
.PHONY: help
help: ## 显示帮助信息
@echo "可用的命令:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
# 开发环境
.PHONY: dev
dev: ## 启动开发环境(本地模式,使用 localhost
@echo "启动开发环境(本地模式)..."
@bash -c '\
PORT=$(PORT); \
echo "检查端口 $(PORT) 是否被占用..."; \
PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$$PORT 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$$PORT " | awk "{print \$$7}" | grep -E "^[0-9]+" | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$$PORT " | grep -oP "pid=\K[0-9]+" | head -1); \
fi; \
CONTAINER=""; \
if command -v docker >/dev/null 2>&1; then \
CONTAINER=$$(docker ps --format "{{.Names}}" --filter "publish=$$PORT" 2>/dev/null | head -1); \
fi; \
if [ -n "$$PID" ] && [ "$$PID" != "-" ] && [ "$$PID" != "0" ]; then \
PROCESS_NAME=$$(ps -p $$PID -o comm= 2>/dev/null || echo "未知"); \
echo "⚠️ 警告: 端口 $$PORT 已被进程 $$PID 占用"; \
echo " 进程名称: $$PROCESS_NAME"; \
if [ -n "$$CONTAINER" ]; then \
echo " Docker 容器: $$CONTAINER"; \
echo " 提示: 使用 \"docker stop $$CONTAINER\" 或 \"make docker-down-dev\" 停止容器"; \
echo ""; \
echo -n " 是否自动停止 Docker 容器 $$CONTAINER? (y/N): "; \
read -t 5 CONFIRM || CONFIRM=""; \
if [ "$$CONFIRM" = "y" ] || [ "$$CONFIRM" = "Y" ]; then \
docker stop $$CONTAINER 2>/dev/null && echo "✅ 已停止容器 $$CONTAINER" || echo "❌ 停止容器失败"; \
sleep 2; \
PID=$$(lsof -ti:$$PORT 2>/dev/null | head -1 || echo ""); \
if [ -n "$$PID" ] && [ "$$PID" != "-" ] && [ "$$PID" != "0" ]; then \
echo "⚠️ 端口仍被占用,尝试终止进程 $$PID..."; \
kill -9 $$PID 2>/dev/null && echo "✅ 已终止进程 $$PID" || echo "❌ 终止进程失败"; \
sleep 1; \
fi; \
else \
echo " 继续启动(可能会失败)..."; \
fi; \
else \
echo " 提示: 使用 \"make kill-$$PORT\" 或 \"make kill-port-force PORT=$$PORT\" 终止该进程"; \
echo ""; \
echo -n " 是否自动终止进程 $$PID? (y/N): "; \
read -t 5 CONFIRM || CONFIRM=""; \
if [ "$$CONFIRM" = "y" ] || [ "$$CONFIRM" = "Y" ]; then \
kill -9 $$PID 2>/dev/null && echo "✅ 已终止进程 $$PID" || echo "❌ 终止进程失败,可能需要 sudo 权限"; \
sleep 1; \
else \
echo " 继续启动(可能会失败)..."; \
fi; \
fi; \
echo ""; \
elif [ -n "$$CONTAINER" ]; then \
echo "⚠️ 警告: 端口 $$PORT 被 Docker 容器 $$CONTAINER 占用"; \
echo " 提示: 使用 \"docker stop $$CONTAINER\" 或 \"make docker-down-dev\" 停止容器"; \
echo ""; \
echo -n " 是否自动停止 Docker 容器 $$CONTAINER? (y/N): "; \
read -t 5 CONFIRM || CONFIRM=""; \
if [ "$$CONFIRM" = "y" ] || [ "$$CONFIRM" = "Y" ]; then \
docker stop $$CONTAINER 2>/dev/null && echo "✅ 已停止容器 $$CONTAINER" || echo "❌ 停止容器失败"; \
sleep 2; \
else \
echo " 继续启动(可能会失败)..."; \
fi; \
echo ""; \
fi; \
echo "配置本地模式(使用 localhost..."; \
cp config/dev.yaml config/dev.yaml.bak 2>/dev/null || true; \
sed -i.tmp "s|host: host.docker.internal|host: localhost|g" config/dev.yaml; \
trap "mv config/dev.yaml.bak config/dev.yaml 2>/dev/null; rm -f config/dev.yaml.tmp 2>/dev/null" INT TERM EXIT; \
$(GO) run src/main.go -env=dev; \
EXIT_CODE=$$?; \
mv config/dev.yaml.bak config/dev.yaml 2>/dev/null || true; \
rm -f config/dev.yaml.tmp 2>/dev/null || true; \
exit $$EXIT_CODE'
@bash -c '\
cp config/dev.yaml config/dev.yaml.bak 2>/dev/null || true; \
sed -i.tmp "s|host: host.docker.internal|host: localhost|g" config/dev.yaml; \
trap "mv config/dev.yaml.bak config/dev.yaml 2>/dev/null; rm -f config/dev.yaml.tmp 2>/dev/null" INT TERM EXIT; \
$(GO) run src/main.go -env=dev; \
EXIT_CODE=$$?; \
mv config/dev.yaml.bak config/dev.yaml 2>/dev/null || true; \
rm -f config/dev.yaml.tmp 2>/dev/null || true; \
exit $$EXIT_CODE'
.PHONY: stage
stage: ## 启动预发布环境(本地模式,使用 localhost
@echo "启动预发布环境(本地模式)..."
@echo "检查端口 $(PORT) 是否被占用..."
@PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$(PORT) 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$(PORT) " | awk '{print $$7}' | grep -E '^[0-9]+' | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$(PORT) " | grep -oP 'pid=\K[0-9]+' | head -1); \
fi; \
if [ -n "$$PID" ] && [ "$$PID" != "-" ] && [ "$$PID" != "0" ]; then \
echo "⚠️ 警告: 端口 $(PORT) 已被进程 $$PID 占用"; \
echo " 提示: 使用 'make kill-$(PORT)' 或 'make kill-port-force PORT=$(PORT)' 终止该进程"; \
echo ""; \
fi; \
echo "配置本地模式(使用 localhost..."
@bash -c '\
cp config/stage.yaml config/stage.yaml.bak 2>/dev/null || true; \
sed -i.tmp "s|host: host.docker.internal|host: localhost|g" config/stage.yaml; \
trap "mv config/stage.yaml.bak config/stage.yaml 2>/dev/null; rm -f config/stage.yaml.tmp 2>/dev/null" INT TERM EXIT; \
$(GO) run src/main.go -env=stage; \
EXIT_CODE=$$?; \
mv config/stage.yaml.bak config/stage.yaml 2>/dev/null || true; \
rm -f config/stage.yaml.tmp 2>/dev/null || true; \
exit $$EXIT_CODE'
.PHONY: prod
prod: ## 启动生产环境(本地模式,使用 localhost
@echo "启动生产环境(本地模式)..."
@echo "检查端口 $(PORT) 是否被占用..."
@PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$(PORT) 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$(PORT) " | awk '{print $$7}' | grep -E '^[0-9]+' | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$(PORT) " | grep -oP 'pid=\K[0-9]+' | head -1); \
fi; \
if [ -n "$$PID" ] && [ "$$PID" != "-" ] && [ "$$PID" != "0" ]; then \
echo "⚠️ 警告: 端口 $(PORT) 已被进程 $$PID 占用"; \
echo " 提示: 使用 'make kill-$(PORT)' 或 'make kill-port-force PORT=$(PORT)' 终止该进程"; \
echo ""; \
fi; \
echo "配置本地模式(使用 localhost..."
@bash -c '\
cp config/prod.yaml config/prod.yaml.bak 2>/dev/null || true; \
sed -i.tmp "s|host: host.docker.internal|host: localhost|g" config/prod.yaml; \
trap "mv config/prod.yaml.bak config/prod.yaml 2>/dev/null; rm -f config/prod.yaml.tmp 2>/dev/null" INT TERM EXIT; \
$(GO) run src/main.go -env=prod; \
EXIT_CODE=$$?; \
mv config/prod.yaml.bak config/prod.yaml 2>/dev/null || true; \
rm -f config/prod.yaml.tmp 2>/dev/null || true; \
exit $$EXIT_CODE'
# 构建相关
.PHONY: build
build: ## 构建应用程序
@echo "构建应用程序..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -ldflags="-w -s" -o $(BUILD_DIR)/$(APP_NAME) src/main.go
.PHONY: build-windows
build-windows: ## 构建Windows版本
@echo "构建Windows版本..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build -ldflags="-w -s" -o $(BUILD_DIR)/$(APP_NAME).exe src/main.go
.PHONY: build-mac
build-mac: ## 构建macOS版本
@echo "构建macOS版本..."
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build -ldflags="-w -s" -o $(BUILD_DIR)/$(APP_NAME)-mac src/main.go
.PHONY: build-all
build-all: build build-windows build-mac ## 构建所有平台版本
# 依赖管理
.PHONY: deps
deps: ## 下载依赖
@echo "下载依赖..."
$(GO) mod download
.PHONY: deps-update
deps-update: ## 更新依赖
@echo "更新依赖..."
$(GO) mod tidy
$(GO) get -u ./...
# 代码质量
.PHONY: fmt
fmt: ## 格式化代码
@echo "格式化代码..."
$(GOFMT) -s -w .
.PHONY: vet
vet: ## 代码静态检查
@echo "代码静态检查..."
$(GOVET) ./...
.PHONY: check
check: fmt vet ## 执行所有代码检查
# 测试相关
.PHONY: test
test: ## 运行测试
@echo "运行测试..."
$(GO) test -v ./...
.PHONY: test-coverage
test-coverage: ## 运行测试并生成覆盖率报告
@echo "运行测试并生成覆盖率报告..."
@mkdir -p $(BUILD_DIR)
$(GO) test -v -coverprofile=$(BUILD_DIR)/coverage.out ./...
$(GO) tool cover -html=$(BUILD_DIR)/coverage.out -o $(BUILD_DIR)/coverage.html
@echo "覆盖率报告已生成: $(BUILD_DIR)/coverage.html"
.PHONY: test-race
test-race: ## 运行竞态检测测试
@echo "运行竞态检测测试..."
$(GO) test -race -v ./...
.PHONY: benchmark
benchmark: ## 运行基准测试
@echo "运行基准测试..."
$(GO) test -bench=. -benchmem ./...
# 文档生成
.PHONY: docs
docs: ## 生成API文档
@echo "生成API文档..."
@mkdir -p $(DOC_DIR)/dev $(DOC_DIR)/stage $(DOC_DIR)/prod
@echo "请先安装 swag: go install github.com/swaggo/swag/cmd/swag@latest"
@echo "然后运行: swag init -g src/main.go -o doc/dev --parseDependency --parseInternal"
.PHONY: docs-serve
docs-serve: docs ## 启动文档服务器
@echo "启动文档服务器..."
@cd $(DOC_DIR) && python3 -m http.server 8081
# Docker 相关
.PHONY: docker-build
docker-build: ## 构建Docker镜像
@echo "构建Docker镜像..."
$(DOCKER) build -t $(APP_NAME):$(VERSION) -t $(APP_NAME):latest .
.PHONY: docker-compose-dev
docker-compose-dev: ## 生成开发环境Docker Compose文件
@echo "生成开发环境Docker Compose文件..."
@mkdir -p $(DOCKER_DIR)
@HOST_IP=$$(hostname -I | awk '{print $$1}' 2>/dev/null || ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $$2}' | cut -d/ -f1 || echo "192.168.1.11"); \
echo "services:" > $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " yinli-api:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " build:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " context: .." >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " dockerfile: Dockerfile" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " ports:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " - \"$(PORT):$(PORT)\"" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " environment:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " - APP_ENV=dev" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " volumes:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " - ../config:/app/config:ro" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " extra_hosts:" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo " - \"host.docker.internal:$$HOST_IP\"" >> $(DOCKER_DIR)/docker-compose.dev.yml; \
echo "开发环境Docker Compose文件已生成: $(DOCKER_DIR)/docker-compose.dev.yml (使用宿主机 IP: $$HOST_IP)"; \
echo "注意: MySQL 和 Redis 需要单独部署,容器通过 host.docker.internal 访问宿主机上的服务"
.PHONY: docker-up-dev
docker-up-dev: docker-compose-dev ## 启动开发环境Docker容器
@echo "启动开发环境Docker容器..."
@echo "配置 Docker 模式(使用 host.docker.internal..."
@cp config/dev.yaml config/dev.yaml.bak 2>/dev/null || true; \
sed -i.tmp 's|host: localhost|host: host.docker.internal|g' config/dev.yaml; \
HOST_IP=$$(hostname -I | awk '{print $$1}' 2>/dev/null || ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $$2}' | cut -d/ -f1); \
if [ -z "$$HOST_IP" ]; then \
mv config/dev.yaml.bak config/dev.yaml 2>/dev/null || true; \
rm -f config/dev.yaml.tmp 2>/dev/null || true; \
echo "❌ 无法获取宿主机 IP 地址"; \
exit 1; \
fi; \
echo "使用宿主机 IP: $$HOST_IP"; \
cd $(DOCKER_DIR) && \
sed -i.tmp2 "s|host.docker.internal:[0-9.]*|host.docker.internal:$$HOST_IP|g" docker-compose.dev.yml && \
$(DOCKER_COMPOSE) -f docker-compose.dev.yml up -d && \
rm -f docker-compose.dev.yml.tmp2; \
mv config/dev.yaml.bak config/dev.yaml 2>/dev/null || true; \
rm -f config/dev.yaml.tmp 2>/dev/null || true
.PHONY: docker-up-stage
docker-up-stage: ## 启动预发布环境Docker容器
@echo "启动预发布环境Docker容器..."
@echo "配置 Docker 模式(使用 host.docker.internal..."
@cp config/stage.yaml config/stage.yaml.bak 2>/dev/null || true; \
sed -i.tmp 's|host: localhost|host: host.docker.internal|g' config/stage.yaml; \
HOST_IP=$$(hostname -I | awk '{print $$1}' 2>/dev/null || ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $$2}' | cut -d/ -f1); \
if [ -z "$$HOST_IP" ]; then \
mv config/stage.yaml.bak config/stage.yaml 2>/dev/null || true; \
rm -f config/stage.yaml.tmp 2>/dev/null || true; \
echo "❌ 无法获取宿主机 IP 地址"; \
exit 1; \
fi; \
echo "使用宿主机 IP: $$HOST_IP"; \
cd $(DOCKER_DIR) && \
sed -i.tmp2 "s|host.docker.internal:[0-9.]*|host.docker.internal:$$HOST_IP|g" docker-compose.stage.yml && \
$(DOCKER_COMPOSE) -f docker-compose.stage.yml up -d && \
rm -f docker-compose.stage.yml.tmp2; \
mv config/stage.yaml.bak config/stage.yaml 2>/dev/null || true; \
rm -f config/stage.yaml.tmp 2>/dev/null || true
.PHONY: docker-up-prod
docker-up-prod: ## 启动生产环境Docker容器
@echo "启动生产环境Docker容器..."
@echo "警告: 请确保已设置 MYSQL_ROOT_PASSWORD 和 REDIS_PASSWORD 环境变量"
@echo "配置 Docker 模式(使用 host.docker.internal..."
@cp config/prod.yaml config/prod.yaml.bak 2>/dev/null || true; \
sed -i.tmp 's|host: localhost|host: host.docker.internal|g' config/prod.yaml; \
HOST_IP=$$(hostname -I | awk '{print $$1}' 2>/dev/null || ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $$2}' | cut -d/ -f1); \
if [ -z "$$HOST_IP" ]; then \
mv config/prod.yaml.bak config/prod.yaml 2>/dev/null || true; \
rm -f config/prod.yaml.tmp 2>/dev/null || true; \
echo "❌ 无法获取宿主机 IP 地址"; \
exit 1; \
fi; \
echo "使用宿主机 IP: $$HOST_IP"; \
cd $(DOCKER_DIR) && \
sed -i.tmp2 "s|host.docker.internal:[0-9.]*|host.docker.internal:$$HOST_IP|g" docker-compose.prod.yml && \
$(DOCKER_COMPOSE) -f docker-compose.prod.yml up -d && \
rm -f docker-compose.prod.yml.tmp2; \
mv config/prod.yaml.bak config/prod.yaml 2>/dev/null || true; \
rm -f config/prod.yaml.tmp 2>/dev/null || true
.PHONY: docker-down
docker-down: ## 停止并移除所有Docker容器
@echo "停止并移除Docker容器..."
@cd $(DOCKER_DIR) && \
OUTPUT=$$($(DOCKER_COMPOSE) -f docker-compose.dev.yml -f docker-compose.stage.yml -f docker-compose.prod.yml down --remove-orphans 2>&1); \
echo "$$OUTPUT"; \
if echo "$$OUTPUT" | grep -q "permission denied"; then \
echo ""; \
echo "⚠️ 检测到权限错误,自动重启 rootless Docker 服务并重试..."; \
systemctl --user restart docker >/dev/null 2>&1 || true; \
sleep 3; \
echo "重试停止容器..."; \
$(DOCKER_COMPOSE) -f docker-compose.dev.yml -f docker-compose.stage.yml -f docker-compose.prod.yml down --remove-orphans 2>&1 || { \
echo ""; \
echo "❌ 仍然失败,请手动执行: systemctl --user restart docker && make docker-down"; \
exit 0; \
}; \
fi
.PHONY: docker-down-dev
docker-down-dev: ## 停止并移除开发环境Docker容器
@echo "停止并移除开发环境Docker容器..."
@cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.dev.yml down --remove-orphans 2>&1 || { \
echo ""; \
echo "⚠️ 如果遇到权限错误permission denied容器可能由 root 用户创建。"; \
echo " 请手动执行: sudo docker compose -f docker/docker-compose.dev.yml down"; \
exit 0; \
}
.PHONY: docker-down-stage
docker-down-stage: ## 停止并移除预发布环境Docker容器
@echo "停止并移除预发布环境Docker容器..."
@cd $(DOCKER_DIR) && \
OUTPUT=$$($(DOCKER_COMPOSE) -f docker-compose.stage.yml down --remove-orphans 2>&1); \
echo "$$OUTPUT"; \
if echo "$$OUTPUT" | grep -q "permission denied"; then \
echo ""; \
echo "⚠️ 检测到权限错误,自动重启 rootless Docker 服务并重试..."; \
systemctl --user restart docker >/dev/null 2>&1 || true; \
sleep 3; \
echo "重试停止容器..."; \
$(DOCKER_COMPOSE) -f docker-compose.stage.yml down --remove-orphans 2>&1 || { \
echo ""; \
echo "❌ 仍然失败,请手动执行: systemctl --user restart docker && make docker-down-stage"; \
exit 0; \
}; \
fi
.PHONY: docker-down-prod
docker-down-prod: ## 停止并移除生产环境Docker容器
@echo "停止并移除生产环境Docker容器..."
@cd $(DOCKER_DIR) && \
if ! $(DOCKER_COMPOSE) -f docker-compose.prod.yml down --remove-orphans 2>&1 | tee /dev/stderr | grep -q "permission denied"; then \
:; \
else \
echo ""; \
echo "⚠️ 检测到权限错误,自动重启 rootless Docker 服务并重试..."; \
systemctl --user restart docker >/dev/null 2>&1 || true; \
sleep 3; \
echo "重试停止容器..."; \
$(DOCKER_COMPOSE) -f docker-compose.prod.yml down --remove-orphans 2>&1 || { \
echo ""; \
echo "❌ 仍然失败,请手动执行: systemctl --user restart docker && make docker-down-prod"; \
exit 0; \
}; \
fi
.PHONY: docker-logs
docker-logs: ## 查看开发环境Docker容器日志
@echo "查看开发环境Docker容器日志..."
cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.dev.yml logs -f
.PHONY: docker-logs-stage
docker-logs-stage: ## 查看预发布环境Docker容器日志
@echo "查看预发布环境Docker容器日志..."
cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.stage.yml logs -f
.PHONY: docker-logs-prod
docker-logs-prod: ## 查看生产环境Docker容器日志
@echo "查看生产环境Docker容器日志..."
cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.prod.yml logs -f
.PHONY: docker-ps
docker-ps: ## 查看所有Docker容器状态
@echo "查看所有Docker容器状态..."
cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.dev.yml -f docker-compose.stage.yml -f docker-compose.prod.yml ps
# 端口管理
.PHONY: kill-port
kill-port: ## 终止指定端口的进程 (用法: make kill-port PORT=$(PORT))
@if [ -z "$(PORT)" ]; then \
echo "❌ 错误: 请指定端口号"; \
echo "用法: make kill-port PORT=$(PORT)"; \
exit 1; \
fi; \
echo "查找端口 $(PORT) 的进程..."; \
PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$(PORT) 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$(PORT) " | awk '{print $$7}' | grep -E '^[0-9]+' | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$(PORT) " | grep -oP 'pid=\K[0-9]+' | head -1); \
fi; \
if [ -z "$$PID" ] || [ "$$PID" = "-" ] || [ "$$PID" = "0" ]; then \
echo "✅ 端口 $(PORT) 未被占用"; \
exit 0; \
fi; \
echo "找到进程 PID: $$PID"; \
PROCESS_INFO=$$(ps -p $$PID -o pid,cmd --no-headers 2>/dev/null || echo "无法获取进程信息"); \
echo "进程信息: $$PROCESS_INFO"; \
read -p "是否终止进程 $$PID? (y/N): " CONFIRM; \
if [ "$$CONFIRM" = "y" ] || [ "$$CONFIRM" = "Y" ]; then \
kill -9 $$PID 2>/dev/null && echo "✅ 已终止进程 $$PID" || echo "❌ 终止进程失败"; \
else \
echo "已取消"; \
fi
.PHONY: kill-port-force
kill-port-force: ## 强制终止指定端口的进程,无需确认 (用法: make kill-port-force PORT=$(PORT))
@if [ -z "$(PORT)" ]; then \
echo "❌ 错误: 请指定端口号"; \
echo "用法: make kill-port-force PORT=$(PORT)"; \
exit 1; \
fi; \
echo "查找端口 $(PORT) 的进程..."; \
PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$(PORT) 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$(PORT) " | awk '{print $$7}' | grep -E '^[0-9]+' | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$(PORT) " | grep -oP 'pid=\K[0-9]+' | head -1); \
fi; \
if [ -z "$$PID" ] || [ "$$PID" = "-" ] || [ "$$PID" = "0" ]; then \
echo "✅ 端口 $(PORT) 未被占用"; \
exit 0; \
fi; \
echo "找到进程 PID: $$PID"; \
PROCESS_INFO=$$(ps -p $$PID -o pid,cmd --no-headers 2>/dev/null || echo "无法获取进程信息"); \
echo "进程信息: $$PROCESS_INFO"; \
kill -9 $$PID 2>/dev/null && echo "✅ 已强制终止进程 $$PID" || echo "❌ 终止进程失败"
.PHONY: kill-$(PORT)
kill-$(PORT): ## 终止端口 $(PORT) 的进程(应用默认端口)
@echo "终止端口 $(PORT) 的进程..."
@PID=""; \
if command -v lsof >/dev/null 2>&1; then \
PID=$$(lsof -ti:$(PORT) 2>/dev/null | head -1); \
fi; \
if [ -z "$$PID" ] && command -v netstat >/dev/null 2>&1; then \
PID=$$(netstat -tlnp 2>/dev/null | grep ":$(PORT) " | awk '{print $$7}' | grep -E '^[0-9]+' | cut -d/ -f1 | head -1); \
fi; \
if [ -z "$$PID" ] && command -v ss >/dev/null 2>&1; then \
PID=$$(ss -tlnp 2>/dev/null | grep ":$(PORT) " | grep -oP 'pid=\K[0-9]+' | head -1); \
fi; \
if [ -z "$$PID" ] || [ "$$PID" = "-" ] || [ "$$PID" = "0" ]; then \
echo "✅ 端口 $(PORT) 未被占用"; \
exit 0; \
fi; \
echo "找到进程 PID: $$PID"; \
PROCESS_INFO=$$(ps -p $$PID -o pid,cmd --no-headers 2>/dev/null || echo "无法获取进程信息"); \
echo "进程信息: $$PROCESS_INFO"; \
kill -9 $$PID 2>/dev/null && echo "✅ 已终止进程 $$PID" || echo "❌ 终止进程失败"
# 清理
.PHONY: clean
clean: ## 清理构建文件
@echo "清理构建文件..."
rm -rf $(BUILD_DIR)
rm -rf $(DOC_DIR)
$(GO) clean
.PHONY: clean-docker
clean-docker: ## 清理Docker资源
@echo "清理Docker资源..."
$(DOCKER) system prune -f
$(DOCKER) volume prune -f
# 安装工具
.PHONY: install-tools
install-tools: ## 安装开发工具
@echo "安装开发工具..."
$(GO) install github.com/swaggo/swag/cmd/swag@latest
$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# 全流程
.PHONY: all
all: clean deps check test build docs ## 执行完整的构建流程
.PHONY: ci
ci: deps check test-coverage ## CI流程
# 版本管理
.PHONY: version
version: ## 显示版本信息
@echo "应用名称: $(APP_NAME)"
@echo "版本: $(VERSION)"
@echo "Go版本: $(shell $(GO) version)"
# 默认目标
.DEFAULT_GOAL := help