From 5abb3b7aa48099830e3270467a178520448fb77d Mon Sep 17 00:00:00 2001
From: Table
Date: Sat, 29 Nov 2025 07:36:01 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=E7=AB=AF=E5=8F=A3?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=98=E9=87=8F=E5=8C=96=E5=B9=B6=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E7=AB=AF=E5=8F=A3=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在变量定义区添加 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 变量来统一管理端口配置
---
Makefile | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 295 insertions(+), 26 deletions(-)
diff --git a/Makefile b/Makefile
index 0ad7d11..ffe66f0 100644
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,9 @@ GOVET := go vet
DOCKER := docker
DOCKER_COMPOSE := $(DOCKER) compose
+# 应用端口
+PORT := 1234
+
# 默认目标
.PHONY: help
help: ## 显示帮助信息
@@ -24,19 +27,152 @@ help: ## 显示帮助信息
# 开发环境
.PHONY: dev
-dev: ## 启动开发环境
- @echo "启动开发环境..."
- $(GO) run src/main.go -env=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: ## 启动预发布环境
- @echo "启动预发布环境..."
- $(GO) run src/main.go -env=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: ## 启动生产环境
- @echo "启动生产环境..."
- $(GO) run src/main.go -env=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
@@ -133,36 +269,87 @@ docker-build: ## 构建Docker镜像
docker-compose-dev: ## 生成开发环境Docker Compose文件
@echo "生成开发环境Docker Compose文件..."
@mkdir -p $(DOCKER_DIR)
- @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 " - \"1234:1234\"" >> $(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 " network_mode: host" >> $(DOCKER_DIR)/docker-compose.dev.yml
- @echo "开发环境Docker Compose文件已生成: $(DOCKER_DIR)/docker-compose.dev.yml"
- @echo "注意: MySQL 和 Redis 需要单独部署,容器使用 network_mode: host 直接访问宿主机上的服务"
+ @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容器..."
- cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.dev.yml up -d
+ @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容器..."
- cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.stage.yml up -d
+ @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 环境变量"
- cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.prod.yml up -d
+ @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容器
@@ -251,6 +438,88 @@ 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: ## 清理构建文件