主要改动: - Docker Compose 配置优化: * 为所有环境的 MySQL 服务添加 stop_grace_period: 60s(增加优雅关闭时间) * 添加 stop_signal: SIGTERM(使用 SIGTERM 信号优雅停止) * 添加 init: true(使用 init 进程管理子进程,避免僵尸进程) - Makefile 改进: * 所有 docker-down-* 命令添加自动重试机制 * 自动检测权限错误并重启 rootless Docker 服务 * 显示完整的 Docker Compose 进度信息(包括容器状态变化) - README.md 更新: * 添加 Docker 权限问题的详细解决方案 * 包括 rootless Docker 的特殊处理方法和自动重试机制说明 问题原因: MySQL 容器在 rootless Docker 环境下停止时遇到权限问题,需要更长的优雅关闭时间来处理 InnoDB 数据文件。 解决方案: 1. 增加 stop_grace_period 到 60 秒,给 MySQL 足够时间优雅关闭 2. 使用 init 进程管理子进程,避免权限问题 3. 在 Makefile 中添加自动检测和重试机制,无需手动重启 Docker 服务
21 KiB
Yinli API
基于 Golang Gin 框架构建的高性能 RESTful API 服务,集成了 JWT 认证、Redis 缓存、频率限制、CORS 等安全特性。
🚀 技术架构
核心技术栈
- Web 框架: Gin - 高性能的 HTTP Web 框架
- 数据库: MySQL 8.0 - 关系型数据库
- 缓存: Redis - 内存数据库,用于缓存和频率限制
- ORM: GORM - Go 语言 ORM 库
- 配置管理: Viper - 配置文件管理
- 认证: JWT - JSON Web Token 认证
- 文档: Swagger - API 文档自动生成
- 测试: Testify - 测试框架
- 容器化: Docker - 容器化部署
安全特性
- JWT 认证: 基于 JSON Web Token 的用户认证
- CORS 支持: 跨域资源共享配置
- 频率限制: 基于 Redis 的 API 频率限制
- 密码加密: 使用 bcrypt 加密用户密码
- 中间件保护: 多层中间件安全防护
项目结构
yinli-api/
├── src/ # 源代码目录
│ ├── main.go # 应用程序入口
│ ├── handler/ # HTTP 处理器
│ ├── middleware/ # 中间件
│ ├── model/ # 数据模型
│ ├── repository/ # 数据访问层
│ ├── service/ # 业务逻辑层
│ └── pkg/ # 可复用的包
│ ├── auth/ # 认证相关
│ ├── cache/ # 缓存操作
│ ├── config/ # 配置管理
│ └── database/ # 数据库连接
├── config/ # 配置文件
│ ├── dev.yaml # 开发环境配置
│ ├── stage.yaml # 预发布环境配置
│ └── prod.yaml # 生产环境配置
├── docker/ # Docker 相关文件
├── doc/ # API 文档
├── sql/ # 数据库脚本
├── test/ # 测试文件
├── build/ # 构建输出
├── Dockerfile # Docker 镜像构建文件
├── Makefile # 构建脚本
└── README.md # 项目说明
🛠️ 快速开始
环境要求
- Go 1.21+
- MySQL 8.0+
- Redis 6.0+
- Docker & Docker Compose (可选)
本地开发
- 克隆项目
git clone <repository-url>
cd yinli-api
- 安装依赖
make deps
- 配置数据库
# 创建数据库
mysql -u root -p < sql/init.sql
- 启动 Redis
redis-server
- 启动开发环境
make dev
Docker 部署
项目支持三种环境的 Docker Compose 部署:dev(开发)、stage(预发布)、prod(生产)。
Docker 镜像配置(中国大陆用户)
如果在中国大陆使用 Docker,建议配置镜像加速器以提高镜像拉取速度。项目使用官方镜像名称(如 mysql:8.0、redis:7-alpine),通过配置的 Docker 镜像加速器自动加速拉取。
方法1:配置 Docker 镜像加速器(推荐)
根据 Docker 运行模式选择配置位置:
标准 Docker(需要 root 权限):
编辑或创建 /etc/docker/daemon.json 文件:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.nju.edu.cn"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
Rootless Docker(无需 root 权限):
编辑或创建 ~/.config/docker/daemon.json 文件:
mkdir -p ~/.config/docker
cat > ~/.config/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.nju.edu.cn"
]
}
EOF
然后重启 rootless Docker(重要:配置后必须重启才能生效):
# 方法1:重启 rootless Docker 服务
systemctl --user restart docker
# 方法2:如果使用 dockerd-rootless,需要重启用户服务
pkill -HUP dockerd
# 或者重启整个 rootless Docker
systemctl --user stop docker.socket
systemctl --user start docker.socket
验证配置:
docker info | grep -A 10 "Registry Mirrors"
如果看到配置的镜像源列表,说明配置成功。
方法2:使用环境变量(临时)
# 设置 Docker 镜像代理
export DOCKER_REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
常用国内镜像源(按推荐顺序):
- DaoCloud:
https://docker.m.daocloud.io⭐ 推荐 - Docker 代理:
https://dockerproxy.com - 南京大学:
https://docker.nju.edu.cn - 上海交大:
https://docker.mirrors.sjtug.sjtu.edu.cn - 阿里云:
https://registry.cn-hangzhou.aliyuncs.com(需要登录) - 中科大:
https://docker.mirrors.ustc.edu.cn(可能不稳定) - 网易:
https://hub-mirror.c.163.com(可能不稳定)
重要提示:
- 项目使用官方镜像名称(如
mysql:8.0、redis:7-alpine),通过配置的 Docker 镜像加速器自动加速 - 如果镜像加速器配置正确,Docker 会自动从配置的镜像源拉取镜像
- 如果遇到镜像拉取失败,请确保已重启 Docker 服务使配置生效:
sudo systemctl restart docker - Go 模块下载:Dockerfile 中已配置使用国内 Go 代理(
GOPROXY=https://goproxy.cn,direct),无需额外配置
开发环境部署
- 生成 Docker Compose 文件(可选,文件已存在)
make docker-compose-dev
- 启动开发环境服务
make docker-up-dev
- 查看日志
make docker-logs
- 停止服务
make docker-down-dev
预发布环境部署
- 启动预发布环境服务
make docker-up-stage
- 查看日志
make docker-logs-stage
- 停止服务
make docker-down-stage
注意: 预发布环境使用不同的端口映射(MySQL: 3307, Redis: 6380),避免与开发环境冲突。
生产环境部署
- 设置环境变量(重要!)
export MYSQL_ROOT_PASSWORD=your_secure_password
export REDIS_PASSWORD=your_redis_password
- 启动生产环境服务
make docker-up-prod
- 查看日志
make docker-logs-prod
- 停止服务
make docker-down-prod
注意:
- 生产环境使用不同的端口映射(MySQL: 3308, Redis: 6381)
- 生产环境配置了健康检查和自动重启策略
- 请务必在生产环境部署前修改配置文件中的敏感信息(JWT密钥、数据库密码等)
📋 可用命令
开发命令
make dev # 启动开发环境
make stage # 启动预发布环境
make prod # 启动生产环境
构建命令
make build # 构建 Linux 版本
make build-all # 构建所有平台版本
make clean # 清理构建文件
测试命令
make test # 运行测试
make test-coverage # 运行测试并生成覆盖率报告
make benchmark # 运行基准测试
代码质量
make fmt # 格式化代码
make vet # 静态检查
make lint # 代码规范检查
make check # 执行所有检查
文档生成
make docs # 生成 API 文档
make docs-serve # 启动文档服务器
Docker 操作
make docker-build # 构建 Docker 镜像
make docker-compose-dev # 生成开发环境 Docker Compose 文件
make docker-up-dev # 启动开发环境容器
make docker-up-stage # 启动预发布环境容器
make docker-up-prod # 启动生产环境容器
make docker-down # 停止所有容器
make docker-down-dev # 停止开发环境容器
make docker-down-stage # 停止预发布环境容器
make docker-down-prod # 停止生产环境容器
make docker-logs # 查看开发环境容器日志
make docker-logs-stage # 查看预发布环境容器日志
make docker-logs-prod # 查看生产环境容器日志
make docker-ps # 查看所有容器状态
🔧 配置说明
环境配置
项目支持三种环境配置:
dev- 开发环境stage- 预发布环境prod- 生产环境
配置文件位于 config/ 目录下,可通过环境变量 APP_ENV 或命令行参数 -env 指定。
主要配置项
server:
port: 1234 # 服务端口
mode: debug # 运行模式 (debug/release)
database:
host: localhost # 数据库地址
port: 3306 # 数据库端口
username: root # 数据库用户名
password: sasasasa # 数据库密码
dbname: yinli # 数据库名称
redis:
host: localhost # Redis 地址
port: 6379 # Redis 端口
password: "" # Redis 密码
db: 0 # Redis 数据库
jwt:
secret: your-secret-key # JWT 密钥
expireHours: 24 # 令牌过期时间(小时)
rateLimit:
enabled: true # 是否启用频率限制
requests: 100 # 每个时间窗口的请求数
window: 60 # 时间窗口(秒)
📡 API 接口
认证接口
| 方法 | 路径 | 描述 | 认证 |
|---|---|---|---|
| POST | /api/auth/register |
用户注册 | ❌ |
| POST | /api/auth/login |
用户登录 | ❌ |
用户接口
| 方法 | 路径 | 描述 | 认证 |
|---|---|---|---|
| GET | /api/user/profile |
获取用户资料 | ✅ |
| PUT | /api/user/profile |
更新用户资料 | ✅ |
| PUT | /api/user/password |
修改密码 | ✅ |
管理员接口
| 方法 | 路径 | 描述 | 认证 |
|---|---|---|---|
| GET | /api/admin/users |
获取用户列表 | ✅ (管理员) |
| DELETE | /api/admin/users/{id} |
删除用户 | ✅ (管理员) |
| PUT | /api/admin/users/{id}/status |
更新用户状态 | ✅ (管理员) |
系统接口
| 方法 | 路径 | 描述 | 认证 |
|---|---|---|---|
| GET | /health |
健康检查 | ❌ |
| GET | /swagger/* |
API 文档 | ❌ |
🧪 接口测试
使用 curl 测试
- 用户注册
curl -X POST http://localhost:1234/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "testuser",
"password": "password123"
}'
- 用户登录
curl -X POST http://localhost:1234/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"name": "testuser",
"password": "password123"
}'
- 获取用户资料
curl -X GET http://localhost:1234/api/user/profile \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
使用 Postman
- 导入 Postman 集合文件(如果有)
- 设置环境变量
base_url为http://localhost:1234 - 在认证接口获取 JWT token
- 在需要认证的接口中添加
Authorization: Bearer {token}头
🔍 开发调试
日志查看
# 查看应用日志
tail -f logs/app.log
# 查看 Docker 容器日志
make docker-logs
数据库调试
# 连接数据库
mysql -h localhost -u root -p yinli
# 查看用户表
SELECT * FROM user;
Redis 调试
# 连接 Redis
redis-cli
# 查看所有键
KEYS *
# 查看频率限制
KEYS rate_limit:*
性能监控
# 查看应用性能
go tool pprof http://localhost:1234/debug/pprof/profile
# 内存使用情况
go tool pprof http://localhost:1234/debug/pprof/heap
端口进程管理
在 Ubuntu 环境中,如果端口被占用,可以使用以下命令查找并终止进程:
# 方法1: 使用 netstat 查找端口对应的进程
netstat -tuln | grep :1234
# 方法2: 使用 lsof 查找端口对应的进程(需要安装: sudo apt install lsof)
lsof -i :1234
# 方法3: 使用 ss 查找端口对应的进程
ss -tulnp | grep :1234
# 获取进程 ID (PID) 后,使用以下命令终止进程
kill <PID>
# 如果进程无法正常终止,可以使用强制终止
kill -9 <PID>
# 或者使用一行命令直接终止占用端口的进程
kill $(lsof -t -i:1234)
# 如果 lsof 不可用,可以使用 fuser(需要安装: sudo apt install psmisc)
sudo fuser -k 1234/tcp
示例:
# 1. 查找端口 1234 对应的进程
$ netstat -tulnp | grep :1234
tcp6 0 0 :::1234 :::* LISTEN 449736/main
# 2. 或者使用 lsof 获取更详细信息
$ lsof -i :1234
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
main 449736 table 9u IPv6 903989 0t0 TCP *:1234 (LISTEN)
# 3. 终止进程(PID 为 449736)
$ kill 449736
# 4. 如果进程无法正常终止,使用强制终止
$ kill -9 449736
📊 测试报告
运行测试并生成报告:
make test-coverage
测试报告将生成在 build/coverage.html,可在浏览器中查看详细的覆盖率信息。
🚀 部署指南
生产环境部署
- 构建生产版本
make build
- 配置生产环境
# 修改 config/prod.yaml
# 设置正确的数据库和 Redis 连接信息
# 更改 JWT 密钥
- 使用 Docker 部署
# 设置环境变量
export MYSQL_ROOT_PASSWORD=your_secure_password
export REDIS_PASSWORD=your_redis_password
# 启动生产环境
make docker-up-prod
# 查看日志
make docker-logs-prod
环境变量
生产环境建议使用环境变量覆盖敏感配置:
export YINLI_DATABASE_PASSWORD=your_db_password
export YINLI_JWT_SECRET=your_jwt_secret
export YINLI_REDIS_PASSWORD=your_redis_password
📚 主要依赖库
核心依赖
- Gin Web Framework - HTTP Web 框架
- GORM - ORM 库
- Viper - 配置管理
- JWT-Go - JWT 实现
- Go-Redis - Redis 客户端
中间件
- Gin-CORS - CORS 中间件
- Gin-Swagger - Swagger 文档
测试工具
开发工具
- Air - 热重载工具
- GolangCI-Lint - 代码检查工具
🤝 贡献指南
- Fork 项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
📄 许可证
本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情。
🆘 常见问题
Q: 如何修改数据库连接?
A: 修改 config/{env}.yaml 文件中的 database 配置项。
Q: 如何添加新的 API 接口?
A: 1. 在 src/handler 中添加处理函数
2. 在 src/handler/router.go 中注册路由
3. 添加相应的测试用例
Q: 如何自定义中间件?
A: 在 src/middleware 目录下创建新的中间件文件,参考现有中间件的实现。
Q: 如何部署到生产环境?
A:
- 设置环境变量:
export MYSQL_ROOT_PASSWORD=your_secure_password export REDIS_PASSWORD=your_redis_password - 修改
config/prod.yaml中的敏感配置(JWT密钥等) - 启动生产环境:
make docker-up-prod - 查看日志:
make docker-logs-prod
Q: Docker Compose 支持哪些环境?
A: 项目支持三种环境的 Docker Compose 部署:
- dev: 开发环境,端口映射 MySQL:3306, Redis:6379
- stage: 预发布环境,端口映射 MySQL:3307, Redis:6380
- prod: 生产环境,端口映射 MySQL:3308, Redis:6381,包含健康检查和自动重启
Q: Docker 镜像拉取失败怎么办?
A: 如果在中国大陆遇到镜像拉取超时或失败,可以:
-
配置 Docker 镜像加速器(推荐):
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.nju.edu.cn" ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker重要:配置后必须重启 Docker 服务才能生效!
验证配置:
docker info | grep -A 10 "Registry Mirrors"如果看到镜像源列表,说明配置成功。
-
Rootless Docker 配置:
- 如果使用 rootless Docker(
docker version显示Context: rootless),配置文件位置为~/.config/docker/daemon.json - 配置后重启:
systemctl --user restart docker - 验证:
docker info | grep -A 10 "Registry Mirrors"
- 如果使用 rootless Docker(
-
使用官方镜像名称:项目使用官方镜像名称(如
mysql:8.0、redis:7-alpine),通过配置的 Docker 镜像加速器自动加速拉取 -
Go 模块下载失败:
- Dockerfile 中已配置使用国内 Go 代理(
GOPROXY=https://goproxy.cn,direct) - 如果仍然失败,可以在 Dockerfile 中修改 GOPROXY 环境变量:
ENV GOPROXY=https://goproxy.cn,https://goproxy.io,direct - 常用 Go 代理:
https://goproxy.cn、https://goproxy.io、https://mirrors.aliyun.com/goproxy/
- Dockerfile 中已配置使用国内 Go 代理(
-
检查网络连接:确保能够访问镜像仓库
- 测试镜像源:
curl -I https://docker.m.daocloud.io/v2/ - 如果镜像加速器配置已生效但仍失败,可能是网络问题,可以稍后重试
- 测试镜像源:
-
如果镜像加速器仍未生效:
- 检查配置文件格式是否正确(JSON 格式)
- 标准 Docker:检查
/etc/docker/daemon.json - Rootless Docker:检查
~/.config/docker/daemon.json - 确认已重启 Docker 服务
- 检查 Docker 服务状态:
sudo systemctl status docker或systemctl --user status docker
Q: Docker 权限错误(permission denied)怎么办?
A: 如果遇到 Error response from daemon: cannot stop container: permission denied 错误,可能的原因和解决方法:
-
Docker 上下文不匹配(常见原因): 如果系统同时安装了标准 Docker 和 rootless Docker,容器可能由不同的上下文创建。
# 查看当前 Docker 上下文 docker context show # 查看所有可用的上下文 docker context ls # 如果容器是由标准 Docker 创建的,切换到 default 上下文 docker context use default # 然后尝试停止容器 docker stop docker-mysql-1 # 如果需要切换回 rootless docker context use rootless提示:如果容器是通过
sudo docker compose创建的,通常需要使用default上下文;如果通过普通用户创建的,可能使用rootless上下文。 -
临时解决方案(使用 sudo):
# 停止并删除容器 sudo docker stop docker-mysql-1 sudo docker rm docker-mysql-1 # 或使用 docker compose sudo docker compose -f docker/docker-compose.stage.yml down -
永久解决方案(推荐):将用户添加到
docker组# 将当前用户添加到 docker 组 sudo usermod -aG docker $USER # 重新登录或执行以下命令使组权限生效 newgrp docker # 验证是否成功 groups | grep docker注意:添加到 docker 组后,需要:
- 重新登录系统,或
- 执行
newgrp docker命令,或 - 重新打开终端
之后就可以不使用
sudo直接操作 Docker 了。 -
检查 Docker socket 权限:
ls -la /var/run/docker.sock应该显示类似:
srw-rw---- 1 root docker,表示docker组有读写权限。 -
如果使用 rootless Docker:
- Rootless Docker 不需要 sudo,但需要确保 Docker 服务正在运行
- 检查服务状态:
systemctl --user status docker - 启动服务:
systemctl --user start docker - 注意:rootless Docker 和标准 Docker 创建的容器是隔离的,不能互相管理
- 如果遇到权限错误,可以尝试:
# 方法1:重启 rootless Docker 服务(推荐) systemctl --user restart docker # 等待几秒后,再尝试停止容器 docker stop docker-mysql-1 # 方法2:使用 docker kill 强制停止 docker kill docker-mysql-1 docker rm docker-mysql-1 # 方法3:检查并修复 rootless Docker socket 权限 ls -la /run/user/$(id -u)/docker.sock # 应该显示类似:srw-rw---T 1 table ...