GoTest/README.md
Table 7e5572344b fix: 修复 MySQL 容器在 rootless Docker 环境下无法正常停止的问题
主要改动:
- 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 服务
2025-11-29 06:18:17 +08:00

770 lines
21 KiB
Markdown
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.

# Yinli API
基于 Golang Gin 框架构建的高性能 RESTful API 服务,集成了 JWT 认证、Redis 缓存、频率限制、CORS 等安全特性。
## 🚀 技术架构
### 核心技术栈
- **Web 框架**: [Gin](https://gin-gonic.com/) - 高性能的 HTTP Web 框架
- **数据库**: [MySQL 8.0](https://dev.mysql.com/doc/) - 关系型数据库
- **缓存**: [Redis](https://redis.io/) - 内存数据库,用于缓存和频率限制
- **ORM**: [GORM](https://gorm.io/) - Go 语言 ORM 库
- **配置管理**: [Viper](https://github.com/spf13/viper) - 配置文件管理
- **认证**: [JWT](https://jwt.io/) - JSON Web Token 认证
- **文档**: [Swagger](https://swagger.io/) - API 文档自动生成
- **测试**: [Testify](https://github.com/stretchr/testify) - 测试框架
- **容器化**: [Docker](https://www.docker.com/) - 容器化部署
### 安全特性
- **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 (可选)
### 本地开发
1. **克隆项目**
```bash
git clone <repository-url>
cd yinli-api
```
2. **安装依赖**
```bash
make deps
```
3. **配置数据库**
```bash
# 创建数据库
mysql -u root -p < sql/init.sql
```
4. **启动 Redis**
```bash
redis-server
```
5. **启动开发环境**
```bash
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` 文件:
```bash
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` 文件:
```bash
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**重要:配置后必须重启才能生效**
```bash
# 方法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
```
验证配置:
```bash
docker info | grep -A 10 "Registry Mirrors"
```
如果看到配置的镜像源列表,说明配置成功。
**方法2使用环境变量临时**
```bash
# 设置 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`),无需额外配置
#### 开发环境部署
1. **生成 Docker Compose 文件(可选,文件已存在)**
```bash
make docker-compose-dev
```
2. **启动开发环境服务**
```bash
make docker-up-dev
```
3. **查看日志**
```bash
make docker-logs
```
4. **停止服务**
```bash
make docker-down-dev
```
#### 预发布环境部署
1. **启动预发布环境服务**
```bash
make docker-up-stage
```
2. **查看日志**
```bash
make docker-logs-stage
```
3. **停止服务**
```bash
make docker-down-stage
```
**注意:** 预发布环境使用不同的端口映射MySQL: 3307, Redis: 6380避免与开发环境冲突。
#### 生产环境部署
1. **设置环境变量(重要!)**
```bash
export MYSQL_ROOT_PASSWORD=your_secure_password
export REDIS_PASSWORD=your_redis_password
```
2. **启动生产环境服务**
```bash
make docker-up-prod
```
3. **查看日志**
```bash
make docker-logs-prod
```
4. **停止服务**
```bash
make docker-down-prod
```
**注意:**
- 生产环境使用不同的端口映射MySQL: 3308, Redis: 6381
- 生产环境配置了健康检查和自动重启策略
- 请务必在生产环境部署前修改配置文件中的敏感信息JWT密钥、数据库密码等
## 📋 可用命令
### 开发命令
```bash
make dev # 启动开发环境
make stage # 启动预发布环境
make prod # 启动生产环境
```
### 构建命令
```bash
make build # 构建 Linux 版本
make build-all # 构建所有平台版本
make clean # 清理构建文件
```
### 测试命令
```bash
make test # 运行测试
make test-coverage # 运行测试并生成覆盖率报告
make benchmark # 运行基准测试
```
### 代码质量
```bash
make fmt # 格式化代码
make vet # 静态检查
make lint # 代码规范检查
make check # 执行所有检查
```
### 文档生成
```bash
make docs # 生成 API 文档
make docs-serve # 启动文档服务器
```
### Docker 操作
```bash
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` 指定。
### 主要配置项
```yaml
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 测试
1. **用户注册**
```bash
curl -X POST http://localhost:1234/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "testuser",
"password": "password123"
}'
```
2. **用户登录**
```bash
curl -X POST http://localhost:1234/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"name": "testuser",
"password": "password123"
}'
```
3. **获取用户资料**
```bash
curl -X GET http://localhost:1234/api/user/profile \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
### 使用 Postman
1. 导入 Postman 集合文件(如果有)
2. 设置环境变量 `base_url``http://localhost:1234`
3. 在认证接口获取 JWT token
4. 在需要认证的接口中添加 `Authorization: Bearer {token}`
## 🔍 开发调试
### 日志查看
```bash
# 查看应用日志
tail -f logs/app.log
# 查看 Docker 容器日志
make docker-logs
```
### 数据库调试
```bash
# 连接数据库
mysql -h localhost -u root -p yinli
# 查看用户表
SELECT * FROM user;
```
### Redis 调试
```bash
# 连接 Redis
redis-cli
# 查看所有键
KEYS *
# 查看频率限制
KEYS rate_limit:*
```
### 性能监控
```bash
# 查看应用性能
go tool pprof http://localhost:1234/debug/pprof/profile
# 内存使用情况
go tool pprof http://localhost:1234/debug/pprof/heap
```
### 端口进程管理
在 Ubuntu 环境中,如果端口被占用,可以使用以下命令查找并终止进程:
```bash
# 方法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
```
**示例:**
```bash
# 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
```
## 📊 测试报告
运行测试并生成报告:
```bash
make test-coverage
```
测试报告将生成在 `build/coverage.html`,可在浏览器中查看详细的覆盖率信息。
## 🚀 部署指南
### 生产环境部署
1. **构建生产版本**
```bash
make build
```
2. **配置生产环境**
```bash
# 修改 config/prod.yaml
# 设置正确的数据库和 Redis 连接信息
# 更改 JWT 密钥
```
3. **使用 Docker 部署**
```bash
# 设置环境变量
export MYSQL_ROOT_PASSWORD=your_secure_password
export REDIS_PASSWORD=your_redis_password
# 启动生产环境
make docker-up-prod
# 查看日志
make docker-logs-prod
```
### 环境变量
生产环境建议使用环境变量覆盖敏感配置:
```bash
export YINLI_DATABASE_PASSWORD=your_db_password
export YINLI_JWT_SECRET=your_jwt_secret
export YINLI_REDIS_PASSWORD=your_redis_password
```
## 📚 主要依赖库
### 核心依赖
- [Gin Web Framework](https://github.com/gin-gonic/gin) - HTTP Web 框架
- [GORM](https://github.com/go-gorm/gorm) - ORM 库
- [Viper](https://github.com/spf13/viper) - 配置管理
- [JWT-Go](https://github.com/golang-jwt/jwt) - JWT 实现
- [Go-Redis](https://github.com/go-redis/redis) - Redis 客户端
### 中间件
- [Gin-CORS](https://github.com/gin-contrib/cors) - CORS 中间件
- [Gin-Swagger](https://github.com/swaggo/gin-swagger) - Swagger 文档
### 测试工具
- [Testify](https://github.com/stretchr/testify) - 测试断言库
- [HTTP Test](https://golang.org/pkg/net/http/httptest/) - HTTP 测试工具
### 开发工具
- [Air](https://github.com/cosmtrek/air) - 热重载工具
- [GolangCI-Lint](https://github.com/golangci/golangci-lint) - 代码检查工具
## 🤝 贡献指南
1. Fork 项目
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](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:
1. 设置环境变量:
```bash
export MYSQL_ROOT_PASSWORD=your_secure_password
export REDIS_PASSWORD=your_redis_password
```
2. 修改 `config/prod.yaml` 中的敏感配置JWT密钥等
3. 启动生产环境:
```bash
make docker-up-prod
```
4. 查看日志:
```bash
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: 如果在中国大陆遇到镜像拉取超时或失败,可以:
1. **配置 Docker 镜像加速器**(推荐):
```bash
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 服务才能生效!**
验证配置
```bash
docker info | grep -A 10 "Registry Mirrors"
```
如果看到镜像源列表说明配置成功
2. **Rootless Docker 配置**
- 如果使用 rootless Docker`docker version` 显示 `Context: rootless`配置文件位置为 `~/.config/docker/daemon.json`
- 配置后重启`systemctl --user restart docker`
- 验证`docker info | grep -A 10 "Registry Mirrors"`
3. **使用官方镜像名称**项目使用官方镜像名称 `mysql:8.0`、`redis:7-alpine`通过配置的 Docker 镜像加速器自动加速拉取
4. **Go 模块下载失败**
- Dockerfile 中已配置使用国内 Go 代理`GOPROXY=https://goproxy.cn,direct`
- 如果仍然失败可以在 Dockerfile 中修改 GOPROXY 环境变量
```dockerfile
ENV GOPROXY=https://goproxy.cn,https://goproxy.io,direct
```
- 常用 Go 代理`https://goproxy.cn`、`https://goproxy.io`、`https://mirrors.aliyun.com/goproxy/`
5. **检查网络连接**确保能够访问镜像仓库
- 测试镜像源`curl -I https://docker.m.daocloud.io/v2/`
- 如果镜像加速器配置已生效但仍失败可能是网络问题可以稍后重试
6. **如果镜像加速器仍未生效**
- 检查配置文件格式是否正确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` 错误可能的原因和解决方法
1. **Docker 上下文不匹配**常见原因
如果系统同时安装了标准 Docker rootless Docker容器可能由不同的上下文创建
```bash
# 查看当前 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` 上下文
2. **临时解决方案(使用 sudo**
```bash
# 停止并删除容器
sudo docker stop docker-mysql-1
sudo docker rm docker-mysql-1
# 或使用 docker compose
sudo docker compose -f docker/docker-compose.stage.yml down
```
3. **永久解决方案(推荐)**将用户添加到 `docker`
```bash
# 将当前用户添加到 docker
sudo usermod -aG docker $USER
# 重新登录或执行以下命令使组权限生效
newgrp docker
# 验证是否成功
groups | grep docker
```
**注意**添加到 docker 组后需要
- 重新登录系统
- 执行 `newgrp docker` 命令
- 重新打开终端
之后就可以不使用 `sudo` 直接操作 Docker
4. **检查 Docker socket 权限**
```bash
ls -la /var/run/docker.sock
```
应该显示类似`srw-rw---- 1 root docker`表示 `docker` 组有读写权限
5. **如果使用 rootless Docker**
- Rootless Docker 不需要 sudo但需要确保 Docker 服务正在运行
- 检查服务状态`systemctl --user status docker`
- 启动服务`systemctl --user start docker`
- 注意rootless Docker 和标准 Docker 创建的容器是隔离的不能互相管理
- **如果遇到权限错误可以尝试**
```bash
# 方法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 ...
```