主要改动: 1. Docker Compose 配置优化(dev/stage/prod): - 移除 mysql 和 redis 服务定义,改为使用宿主机服务 - 更新 extra_hosts 使用宿主机实际 IP (192.168.1.11) 替代 host-gateway - 解决 rootless Docker 网络连接问题 2. Config 配置文件更新(dev/stage/prod): - 更新 database.host 和 redis.host 为 host.docker.internal - 支持容器通过 host.docker.internal 访问宿主机服务 3. README.md 文档完善: - 添加 Docker 容器连接宿主机 MySQL/Redis 的完整排查指南 - 添加 MySQL 容器无法正常停止的解决方案 - 更新 Docker Compose 环境说明 - 添加常见错误信息和排查步骤 问题修复: - 修复容器无法连接宿主机 MySQL(bind-address 和用户权限问题) - 修复容器无法连接宿主机 Redis(bind 和 protected-mode 问题) - 修复 rootless Docker 的 host-gateway 问题(使用宿主机实际 IP) 相关配置要求: - MySQL: bind-address = 0.0.0.0, root@'%' 用户权限 - Redis: bind = 0.0.0.0, protected-mode = no
290 lines
9.7 KiB
Makefile
290 lines
9.7 KiB
Makefile
# 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
|
||
|
||
# 默认目标
|
||
.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: ## 启动开发环境
|
||
@echo "启动开发环境..."
|
||
$(GO) run src/main.go -env=dev
|
||
|
||
.PHONY: stage
|
||
stage: ## 启动预发布环境
|
||
@echo "启动预发布环境..."
|
||
$(GO) run src/main.go -env=stage
|
||
|
||
.PHONY: prod
|
||
prod: ## 启动生产环境
|
||
@echo "启动生产环境..."
|
||
$(GO) run src/main.go -env=prod
|
||
|
||
# 构建相关
|
||
.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)
|
||
@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 直接访问宿主机上的服务"
|
||
|
||
.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
|
||
|
||
.PHONY: docker-up-stage
|
||
docker-up-stage: ## 启动预发布环境Docker容器
|
||
@echo "启动预发布环境Docker容器..."
|
||
cd $(DOCKER_DIR) && $(DOCKER_COMPOSE) -f docker-compose.stage.yml up -d
|
||
|
||
.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
|
||
|
||
.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: 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
|