Files
tyapi-server/.cursor/rules/api.mdc

2528 lines
73 KiB
Plaintext
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.

---
description:
globs:
alwaysApply: false
---
# TYAPI Server API 开发规范
## 🏗️ 项目架构概览
本项目采用 **DDD领域驱动设计** + **Clean Architecture** + **事件驱动架构**,基于 Gin 框架构建的企业级后端 API 服务。
## 📋 目录结构规范
```
internal/
├── domains/ # 领域层
│ └── user/ # 用户领域
│ ├── dto/ # 数据传输对象
│ ├── entities/ # 实体
│ ├── events/ # 领域事件
│ ├── handlers/ # HTTP处理器
│ ├── repositories/ # 仓储接口实现
│ ├── routes/ # 路由配置
│ ├── services/ # 领域服务
│ └── validators/ # 验证器
├── shared/ # 共享基础设施
│ ├── interfaces/ # 接口定义
│ ├── middleware/ # 中间件
│ ├── http/ # HTTP基础组件
│ └── ...
└── config/ # 配置管理
```
## 🎯 业务分层架构
### 1. 控制器层 (Handlers)
```go
// internal/domains/user/handlers/user_handler.go
type UserHandler struct {
userService *services.UserService // 注入领域服务
response interfaces.ResponseBuilder // 统一响应构建器
validator interfaces.RequestValidator // 请求验证器
logger *zap.Logger // 结构化日志
jwtAuth *middleware.JWTAuthMiddleware // JWT认证
}
// 标准CRUD处理器方法
func (h *UserHandler) Create(c *gin.Context) {
var req dto.CreateUserRequest
// 1. 请求验证
if err := h.validator.BindAndValidate(c, &req); err != nil {
return // 验证器已处理响应
}
// 2. 调用领域服务
user, err := h.userService.Create(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create user", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
// 3. 统一响应格式
response := dto.FromEntity(user)
h.response.Created(c, response, "User created successfully")
}
```
### 2. 服务层 (Services)
```go
// internal/domains/user/services/user_service.go
type UserService struct {
repo *repositories.UserRepository // 数据访问
eventBus interfaces.EventBus // 事件总线
logger *zap.Logger // 日志
}
func (s *UserService) Create(ctx context.Context, req *dto.CreateUserRequest) (*entities.User, error) {
// 1. 业务规则验证
if err := s.validateCreateUser(req); err != nil {
return nil, err
}
// 2. 实体创建
user := entities.NewUser(req.Username, req.Email, req.Password)
// 3. 数据持久化
if err := s.repo.Create(ctx, user); err != nil {
return nil, err
}
// 4. 发布领域事件
event := events.NewUserCreatedEvent(user.ID, user.Username, user.Email)
s.eventBus.PublishAsync(ctx, event)
return user, nil
}
```
### 3. 仓储层 (Repositories)
```go
// internal/domains/user/repositories/user_repository.go
type UserRepository struct {
db *gorm.DB // 数据库连接
cache interfaces.CacheService // 缓存服务
logger *zap.Logger // 日志
}
func (r *UserRepository) Create(ctx context.Context, user *entities.User) error {
// 使用事务确保数据一致性
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(user).Error; err != nil {
return err
}
// 清除相关缓存
r.cache.Delete(ctx, fmt.Sprintf("user:count"))
return nil
})
}
```
### 4. DTO 层 (数据传输对象)
```go
// internal/domains/user/dto/user_dto.go
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=50" validate:"username"`
Email string `json:"email" binding:"required,email" validate:"email"`
Password string `json:"password" binding:"required,min=8" validate:"password"`
DisplayName string `json:"display_name" binding:"max=100"`
}
type UserResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
DisplayName string `json:"display_name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 实体转换函数
func FromEntity(user *entities.User) *UserResponse {
return &UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
DisplayName: user.DisplayName,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
```
## 🛣️ 路由配置规范
### 1. DDD 领域路由设计模式
```go
// internal/domains/user/routes/user_routes.go
type UserRoutes struct {
handler *handlers.UserHandler
jwtAuth *middleware.JWTAuthMiddleware
}
func (r *UserRoutes) RegisterRoutes(router *gin.Engine) {
// API版本组
v1 := router.Group("/api/v1")
// 🏢 用户域路由组 - 按域名组织
users := v1.Group("/users")
{
// 🌍 公开路由(不需要认证)
users.POST("/send-code", r.handler.SendCode) // 发送验证码
users.POST("/register", r.handler.Register) // 用户注册
users.POST("/login", r.handler.Login) // 用户登录
// 🔐 需要认证的路由
authenticated := users.Group("")
authenticated.Use(r.jwtAuth.Handle())
{
authenticated.GET("/me", r.handler.GetProfile) // 获取当前用户信息
authenticated.PUT("/me/password", r.handler.ChangePassword) // 修改密码
// 未来扩展示例:
// authenticated.PUT("/me", r.handler.UpdateProfile) // 更新用户信息
// authenticated.DELETE("/me", r.handler.DeleteAccount) // 删除账户
// authenticated.GET("/me/sessions", r.handler.GetSessions) // 获取登录会话
}
}
// 📱 SMS验证码域路由组如果需要单独管理SMS
sms := v1.Group("/sms")
{
sms.POST("/send", r.handler.SendCode) // 发送验证码
// 未来可以添加:
// sms.POST("/verify", r.handler.VerifyCode) // 验证验证码
}
}
```
### 2. DDD 多域路由架构
```go
// 按域组织路由,支持横向扩展
func (r *UserRoutes) RegisterRoutes(router *gin.Engine) {
v1 := router.Group("/api/v1")
// 👥 用户域
users := v1.Group("/users")
// 📦 订单域
orders := v1.Group("/orders")
// 🛍️ 商品域
products := v1.Group("/products")
// 💰 支付域
payments := v1.Group("/payments")
}
// 多级权限路由分层
users := v1.Group("/users")
{
// Level 1: 公开路由
users.POST("/register", r.handler.Register)
users.POST("/login", r.handler.Login)
// Level 2: 用户认证路由
authenticated := users.Group("")
authenticated.Use(r.jwtAuth.Handle())
{
authenticated.GET("/me", r.handler.GetProfile)
}
// Level 3: 管理员路由
admin := users.Group("/admin")
admin.Use(r.jwtAuth.Handle(), r.adminAuth.Handle())
{
admin.GET("", r.handler.AdminList)
admin.DELETE("/:id", r.handler.AdminDelete)
}
}
```
### 3. DDD 路由命名最佳实践
#### ✅ **推荐做法** - 领域导向设计:
```go
// 🏢 按业务域划分路由
/api/v1/users/* # 用户域的所有操作
/api/v1/orders/* # 订单域的所有操作
/api/v1/products/* # 商品域的所有操作
/api/v1/payments/* # 支付域的所有操作
// 📋 资源操作使用名词复数
POST /api/v1/users/register # 用户注册
POST /api/v1/users/login # 用户登录
GET /api/v1/users/me # 获取当前用户
PUT /api/v1/users/me/password # 修改当前用户密码
// 🔗 体现资源关系的嵌套路径
GET /api/v1/users/me/orders # 获取当前用户的订单
GET /api/v1/orders/123/items # 获取订单的商品项目
POST /api/v1/products/456/reviews # 为商品添加评论
```
#### ❌ **避免的做法** - 技术导向设计:
```go
// ❌ 技术导向路径
/api/v1/auth/* # 混合了多个域的认证操作
/api/v1/service/* # 不明确的服务路径
/api/v1/api/* # 冗余的api前缀
// ❌ 动词路径
/api/v1/getUserInfo # 应该用 GET /users/me
/api/v1/changeUserPassword # 应该用 PUT /users/me/password
/api/v1/deleteUserAccount # 应该用 DELETE /users/me
// ❌ 混合域概念
/api/v1/userorders # 应该分离为 /users/me/orders
/api/v1/authprofile # 应该分离为 /users/me
```
## 🔐 权限控制体系
### 1. JWT 认证中间件
```go
// 强制认证中间件
type JWTAuthMiddleware struct {
config *config.Config
logger *zap.Logger
}
// 可选认证中间件(支持游客访问)
type OptionalAuthMiddleware struct {
jwtAuth *JWTAuthMiddleware
}
// 使用方式
protected.Use(r.jwtAuth.Handle()) // 强制认证
public.Use(r.optionalAuth.Handle()) // 可选认证
```
### 2. 权限验证模式
```go
// 在Handler中获取当前用户
func (h *UserHandler) getCurrentUserID(c *gin.Context) string {
userID, exists := c.Get("user_id")
if !exists {
return ""
}
return userID.(string)
}
// 权限检查示例
func (h *UserHandler) UpdateProfile(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "User not authenticated")
return
}
// 业务逻辑...
}
```
### 3. 权限级别定义
- **Public**: 公开接口,无需认证
- **User**: 需要用户登录
- **Admin**: 需要管理员权限
- **Owner**: 需要资源所有者权限
## 📝 API 响应规范
### 1. 统一响应格式 (APIResponse 结构)
```go
// 标准API响应结构
type APIResponse struct {
Success bool `json:"success"` // 操作是否成功
Message string `json:"message"` // 响应消息(中文)
Data interface{} `json:"data,omitempty"` // 响应数据
Errors interface{} `json:"errors,omitempty"` // 错误详情
Pagination *PaginationMeta `json:"pagination,omitempty"` // 分页信息
Meta map[string]interface{} `json:"meta,omitempty"` // 元数据
RequestID string `json:"request_id"` // 请求追踪ID
Timestamp int64 `json:"timestamp"` // Unix时间戳
}
// 分页元数据结构
type PaginationMeta struct {
Page int `json:"page"` // 当前页码
PageSize int `json:"page_size"` // 每页大小
Total int64 `json:"total"` // 总记录数
TotalPages int `json:"total_pages"` // 总页数
HasNext bool `json:"has_next"` // 是否有下一页
HasPrev bool `json:"has_prev"` // 是否有上一页
}
```
### 2. 成功响应格式示例
```json
// 查询成功响应 (200 OK)
{
"success": true,
"message": "获取成功",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 创建成功响应 (201 Created)
{
"success": true,
"message": "用户注册成功",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 登录成功响应 (200 OK)
{
"success": true,
"message": "登录成功",
"data": {
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"login_method": "password"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 分页响应 (200 OK)
{
"success": true,
"message": "获取成功",
"data": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 10,
"total": 100,
"total_pages": 10,
"has_next": true,
"has_prev": false
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
### 3. 错误响应格式示例
```json
// 参数验证错误 (400 Bad Request)
{
"success": false,
"message": "请求参数错误",
"errors": {
"phone": ["手机号必须为11位数字"],
"password": ["密码长度至少6位"],
"confirm_password": ["确认密码必须与密码一致"]
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 验证码错误 (422 Unprocessable Entity)
{
"success": false,
"message": "验证失败",
"errors": {
"phone": ["手机号必须为11位数字"],
"code": ["验证码必须为6位数字"]
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 业务逻辑错误 (400 Bad Request)
{
"success": false,
"message": "手机号已存在",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 认证错误 (401 Unauthorized)
{
"success": false,
"message": "用户未登录或token已过期",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 权限错误 (403 Forbidden)
{
"success": false,
"message": "权限不足,无法访问此资源",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 资源不存在 (404 Not Found)
{
"success": false,
"message": "请求的资源不存在",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 资源冲突 (409 Conflict)
{
"success": false,
"message": "手机号已被注册",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 限流错误 (429 Too Many Requests)
{
"success": false,
"message": "请求过于频繁,请稍后再试",
"meta": {
"retry_after": "60s"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 服务器错误 (500 Internal Server Error)
{
"success": false,
"message": "服务器内部错误",
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
### 🚦 限流中间件和 TooManyRequests 详解
#### 限流配置
```yaml
# config.yaml 限流配置
ratelimit:
requests: 1000 # 每个时间窗口允许的请求数
window: 60s # 时间窗口大小
burst: 200 # 突发请求允许数
```
#### 限流中间件实现
```go
// RateLimitMiddleware 限流中间件(修复后的版本)
type RateLimitMiddleware struct {
config *config.Config
response interfaces.ResponseBuilder // ✅ 使用统一响应格式
limiters map[string]*rate.Limiter
mutex sync.RWMutex
}
// Handle 限流处理逻辑
func (m *RateLimitMiddleware) Handle() gin.HandlerFunc {
return func(c *gin.Context) {
clientID := m.getClientID(c) // 获取客户端ID通常是IP地址
limiter := m.getLimiter(clientID)
if !limiter.Allow() {
// 添加限流头部信息
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", m.config.RateLimit.Requests))
c.Header("X-RateLimit-Window", m.config.RateLimit.Window.String())
c.Header("Retry-After", "60")
// ✅ 使用统一的TooManyRequests响应格式修复前是c.JSON
m.response.TooManyRequests(c, "请求过于频繁,请稍后再试")
c.Abort()
return
}
c.Next()
}
}
```
#### 多层限流保护
```go
// 🔹 1. 全局IP限流中间件层
// 通过RateLimitMiddleware自动处理返回429状态码
// 🔹 2. 短信发送限流(业务层)
func (s *SMSCodeService) checkRateLimit(ctx context.Context, phone string) error {
// 最小发送间隔检查
lastSentKey := fmt.Sprintf("sms:last_sent:%s", phone)
if lastSent exists && now.Sub(lastSent) < s.config.RateLimit.MinInterval {
return fmt.Errorf("请等待 %v 后再试", s.config.RateLimit.MinInterval)
}
// 每小时发送限制
hourlyKey := fmt.Sprintf("sms:hourly:%s:%s", phone, now.Format("2006010215"))
if hourlyCount >= s.config.RateLimit.HourlyLimit {
return fmt.Errorf("每小时最多发送 %d 条短信", s.config.RateLimit.HourlyLimit)
}
return nil
}
// 🔹 3. Handler层限流错误处理
func (h *UserHandler) SendSMSCode(c *gin.Context) {
err := h.smsCodeService.SendCode(ctx, &req)
if err != nil {
// 检查是否是限流错误
if strings.Contains(err.Error(), "请等待") ||
strings.Contains(err.Error(), "最多发送") {
// ✅ 使用TooManyRequests响应
h.response.TooManyRequests(c, err.Error())
return
}
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, nil, "验证码发送成功")
}
```
#### 限流响应示例
**中间件层限流**(全局 IP 限流):
```json
{
"success": false,
"message": "请求过于频繁,请稍后再试",
"meta": {
"retry_after": "60s"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
**业务层限流**(短信发送限流):
```json
{
"success": false,
"message": "请等待 60 秒后再试",
"meta": {
"retry_after": "60s"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### TooManyRequests 使用场景
- 🚫 **全局限流**: IP 请求频率限制
- 📱 **短信限流**: 验证码发送频率限制
- 🔐 **登录限流**: 防止暴力破解
- 📧 **邮件限流**: 邮件发送频率限制
- <20><> **搜索限流**: 防止恶意搜索
### 4. ResponseBuilder 响应构建器使用
```go
// 成功响应
h.response.Success(c, data, "获取成功")
h.response.Created(c, data, "创建成功")
// 客户端错误响应
h.response.BadRequest(c, "请求参数错误", validationErrors)
h.response.Unauthorized(c, "用户未登录或token已过期")
h.response.Forbidden(c, "权限不足,无法访问此资源")
h.response.NotFound(c, "请求的资源不存在")
h.response.Conflict(c, "手机号已被注册")
h.response.ValidationError(c, validationErrors)
h.response.TooManyRequests(c, "请求过于频繁,请稍后再试")
// 服务器错误响应
h.response.InternalError(c, "服务器内部错误")
// 分页响应
h.response.Paginated(c, data, pagination)
// 自定义响应
h.response.CustomResponse(c, statusCode, data)
```
### 5. 错误处理分层架构
```go
// 1. Handler层 - HTTP错误处理
func (h *UserHandler) Register(c *gin.Context) {
var req dto.RegisterRequest
// 验证请求参数
if err := h.validator.BindAndValidate(c, &req); err != nil {
return // 验证器已处理响应,直接返回
}
// 调用业务服务
user, err := h.userService.Register(c.Request.Context(), &req)
if err != nil {
h.logger.Error("用户注册失败", zap.Error(err))
// 根据错误类型返回相应响应
switch {
case strings.Contains(err.Error(), "手机号已存在"):
h.response.Conflict(c, "手机号已被注册")
case strings.Contains(err.Error(), "验证码错误"):
h.response.BadRequest(c, "验证码错误或已过期")
default:
h.response.InternalError(c, "注册失败,请稍后重试")
}
return
}
// 成功响应
response := dto.FromEntity(user)
h.response.Created(c, response, "用户注册成功")
}
// 2. 验证器层 - 参数验证错误
func (v *RequestValidator) BindAndValidate(c *gin.Context, dto interface{}) error {
// 绑定请求体
if err := c.ShouldBindJSON(dto); err != nil {
v.response.BadRequest(c, "请求体格式错误", err.Error())
return err
}
// 验证数据
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrors(err)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
}
// 3. 业务服务层 - 业务逻辑错误
func (s *UserService) Register(ctx context.Context, req *dto.RegisterRequest) (*entities.User, error) {
// 验证手机号格式
if !s.isValidPhone(req.Phone) {
return nil, fmt.Errorf("手机号格式不正确")
}
// 检查手机号是否已存在
if err := s.checkPhoneDuplicate(ctx, req.Phone); err != nil {
return nil, fmt.Errorf("手机号已存在")
}
// 验证验证码
if err := s.smsCodeService.VerifyCode(ctx, req.Phone, req.Code, entities.SMSSceneRegister); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
// 创建用户...
return user, nil
}
```
## 🔄 RESTful API 设计规范
### 1. DDD 架构下的 URL 设计规范
```bash
# 🏢 领域驱动的资源设计
GET /api/v1/users/me # 获取当前用户信息
PUT /api/v1/users/me # 更新当前用户信息
DELETE /api/v1/users/me # 删除当前用户账户
# 🔐 认证相关操作(仍在用户域内)
POST /api/v1/users/register # 用户注册
POST /api/v1/users/login # 用户登录
POST /api/v1/users/logout # 用户登出
POST /api/v1/users/send-code # 发送验证码
# 📱 SMS验证码域操作
POST /api/v1/sms/send # 发送验证码
POST /api/v1/sms/verify # 验证验证码
# 🔗 子资源嵌套(当前用户的资源)
GET /api/v1/users/me/orders # 获取当前用户的订单
GET /api/v1/users/me/favorites # 获取当前用户的收藏
POST /api/v1/users/me/favorites # 添加收藏
DELETE /api/v1/users/me/favorites/:id # 删除收藏
# 🛍️ 跨域资源关系
GET /api/v1/orders/123 # 获取订单详情
GET /api/v1/orders/123/items # 获取订单商品
POST /api/v1/products/456/reviews # 为商品添加评论
# 🎯 特殊操作使用动词(在对应域内)
PUT /api/v1/users/me/password # 修改密码
POST /api/v1/orders/123/cancel # 取消订单
POST /api/v1/payments/123/refund # 退款操作
```
### 2. DDD 多域 API 路径设计示例
```bash
# 👥 用户域 (User Domain)
POST /api/v1/users/register # 用户注册
POST /api/v1/users/login # 用户登录
GET /api/v1/users/me # 获取当前用户
PUT /api/v1/users/me # 更新用户信息
PUT /api/v1/users/me/password # 修改密码
GET /api/v1/users/me/sessions # 获取登录会话
# 📦 订单域 (Order Domain)
GET /api/v1/orders # 获取订单列表
POST /api/v1/orders # 创建订单
GET /api/v1/orders/:id # 获取订单详情
PUT /api/v1/orders/:id # 更新订单
POST /api/v1/orders/:id/cancel # 取消订单
GET /api/v1/orders/:id/items # 获取订单商品
# 🛍️ 商品域 (Product Domain)
GET /api/v1/products # 获取商品列表
POST /api/v1/products # 创建商品
GET /api/v1/products/:id # 获取商品详情
PUT /api/v1/products/:id # 更新商品
GET /api/v1/products/:id/reviews # 获取商品评论
POST /api/v1/products/:id/reviews # 添加商品评论
# 💰 支付域 (Payment Domain)
POST /api/v1/payments # 创建支付
GET /api/v1/payments/:id # 获取支付状态
POST /api/v1/payments/:id/refund # 申请退款
# 📱 通知域 (Notification Domain)
GET /api/v1/notifications # 获取通知列表
PUT /api/v1/notifications/:id/read # 标记通知为已读
POST /api/v1/sms/send # 发送短信验证码
```
### 3. HTTP 状态码规范
```bash
# ✅ 成功响应 (2xx)
200 OK # 查询成功 (GET /api/v1/users/me)
201 Created # 创建成功 (POST /api/v1/users/register)
204 No Content # 删除成功 (DELETE /api/v1/users/me)
# ❌ 客户端错误 (4xx)
400 Bad Request # 请求参数错误
401 Unauthorized # 未认证 (需要登录)
403 Forbidden # 无权限 (登录但权限不足)
404 Not Found # 资源不存在
422 Unprocessable Entity # 业务验证失败
429 Too Many Requests # 请求频率限制
# ⚠️ 服务器错误 (5xx)
500 Internal Server Error # 服务器内部错误
502 Bad Gateway # 网关错误
503 Service Unavailable # 服务不可用
```
### 4. 状态码在 DDD 架构中的应用
```go
// 用户域状态码示例
func (h *UserHandler) Login(c *gin.Context) {
// 参数验证失败
if err := h.validator.BindAndValidate(c, &req); err != nil {
// 422 Unprocessable Entity
return
}
user, err := h.userService.Login(ctx, &req)
if err != nil {
switch {
case errors.Is(err, domain.ErrUserNotFound):
h.response.NotFound(c, "用户不存在") // 404
case errors.Is(err, domain.ErrInvalidPassword):
h.response.Unauthorized(c, "密码错误") // 401
case errors.Is(err, domain.ErrUserBlocked):
h.response.Forbidden(c, "账户已被禁用") // 403
default:
h.response.InternalError(c, "登录失败") // 500
}
return
}
h.response.Success(c, user, "登录成功") // 200
}
func (h *UserHandler) Register(c *gin.Context) {
user, err := h.userService.Register(ctx, &req)
if err != nil {
switch {
case errors.Is(err, domain.ErrPhoneExists):
h.response.Conflict(c, "手机号已存在") // 409
case errors.Is(err, domain.ErrInvalidCode):
h.response.BadRequest(c, "验证码错误") // 400
default:
h.response.InternalError(c, "注册失败") // 500
}
return
}
h.response.Created(c, user, "注册成功") // 201
}
```
## ✅ 数据验证规范
### 1. 结构体标签验证 (中文提示)
```go
// 用户注册请求验证
type RegisterRequest struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Password string `json:"password" binding:"required,min=6,max=128" example:"password123"`
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password" example:"password123"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
}
// 用户登录请求验证
type LoginWithPasswordRequest struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Password string `json:"password" binding:"required" example:"password123"`
}
// 修改密码请求验证
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required" example:"oldpassword123"`
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
}
```
### 2. 官方中文翻译包集成
项目集成了 `github.com/go-playground/validator/v10/translations/zh` 官方中文翻译包,自动提供专业的中文验证错误消息。
**集成优势:**
- ✅ **官方支持**: 使用 validator 官方维护的中文翻译
- ✅ **专业翻译**: 所有标准验证规则都有准确的中文翻译
- ✅ **自动更新**: 跟随 validator 版本自动获得新功能的中文支持
- ✅ **智能结合**: 官方翻译 + 自定义字段名映射,提供最佳用户体验
- ✅ **兼容性好**: 保持与现有 API 接口的完全兼容
```go
// 创建支持中文翻译的验证器
func NewRequestValidatorZh(response interfaces.ResponseBuilder) interfaces.RequestValidator {
// 创建验证器实例
validate := validator.New()
// 创建中文locale
zhLocale := zh.New()
uni := ut.New(zhLocale, zhLocale)
// 获取中文翻译器
trans, _ := uni.GetTranslator("zh")
// 注册官方中文翻译
zh_translations.RegisterDefaultTranslations(validate, trans)
// 注册自定义验证器和翻译
registerCustomValidatorsZh(validate, trans)
return &RequestValidatorZh{
validator: validate,
translator: trans,
response: response,
}
}
// 手机号验证器
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
if phone == "" {
return true // 空值由required标签处理
}
// 中国手机号验证11位以1开头
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}
// 用户名验证器
func validateUsername(fl validator.FieldLevel) bool {
username := fl.Field().String()
if username == "" {
return true // 空值由required标签处理
}
// 用户名规则3-30字符字母数字下划线不能数字开头
if len(username) < 3 || len(username) > 30 {
return false
}
matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]*$`, username)
return matched
}
// 强密码验证器
func validateStrongPassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
if password == "" {
return true // 空值由required标签处理
}
// 密码强度至少8位包含大小写字母和数字
if len(password) < 8 {
return false
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
return hasUpper && hasLower && hasDigit
}
```
### 3. 自定义验证器和翻译注册
```go
// 注册自定义验证器和中文翻译
func registerCustomValidatorsZh(v *validator.Validate, trans ut.Translator) {
// 注册手机号验证器
v.RegisterValidation("phone", validatePhoneZh)
v.RegisterTranslation("phone", trans, func(ut ut.Translator) error {
return ut.Add("phone", "{0}必须是有效的手机号", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("phone", fe.Field())
return t
})
// 注册用户名验证器
v.RegisterValidation("username", validateUsernameZh)
v.RegisterTranslation("username", trans, func(ut ut.Translator) error {
return ut.Add("username", "{0}格式不正确,只能包含字母、数字、下划线,且不能以数字开头", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("username", fe.Field())
return t
})
// 注册密码强度验证器
v.RegisterValidation("strong_password", validateStrongPasswordZh)
v.RegisterTranslation("strong_password", trans, func(ut ut.Translator) error {
return ut.Add("strong_password", "{0}强度不足必须包含大小写字母和数字且不少于8位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("strong_password", fe.Field())
return t
})
}
// 智能错误格式化(官方翻译 + 自定义字段名)
func (v *RequestValidatorZh) formatValidationErrorsZh(err error) map[string][]string {
errors := make(map[string][]string)
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, fieldError := range validationErrors {
fieldName := v.getFieldNameZh(fieldError)
// 使用官方翻译器获取中文错误消息
errorMessage := fieldError.Translate(v.translator)
// 替换字段名为中文显示名称
fieldDisplayName := v.getFieldDisplayName(fieldError.Field())
if fieldDisplayName != fieldError.Field() {
errorMessage = strings.ReplaceAll(errorMessage, fieldError.Field(), fieldDisplayName)
}
if _, exists := errors[fieldName]; !exists {
errors[fieldName] = []string{}
}
errors[fieldName] = append(errors[fieldName], errorMessage)
}
}
return errors
}
```
### 4. 中文翻译效果对比
**标准验证规则** (官方翻译)
```json
{
"success": false,
"message": "验证失败",
"errors": {
"phone": ["手机号必须是有效的手机号"],
"email": ["email必须是一个有效的邮箱"],
"password": ["password长度必须至少为8个字符"],
"confirm_password": ["ConfirmPassword必须等于Password"],
"age": ["age必须大于或等于18"]
}
}
```
**自定义验证规则** (自定义翻译)
```json
{
"success": false,
"message": "验证失败",
"errors": {
"username": [
"用户名格式不正确,只能包含字母、数字、下划线,且不能以数字开头"
],
"password": ["密码强度不足必须包含大小写字母和数字且不少于8位"]
}
}
```
**优化后的用户体验**
通过字段名映射,最终用户看到的是:
```json
{
"success": false,
"message": "验证失败",
"errors": {
"phone": ["手机号必须是有效的手机号"],
"email": ["邮箱必须是一个有效的邮箱"],
"password": ["密码长度必须至少为8个字符"],
"confirm_password": ["确认密码必须等于密码"],
"age": ["年龄必须大于或等于18"]
}
}
```
### 4. 验证器使用示例
```go
func (h *UserHandler) Register(c *gin.Context) {
var req dto.RegisterRequest
// 验证器会自动处理错误响应,返回中文错误信息
if err := h.validator.BindAndValidate(c, &req); err != nil {
return // 验证失败,已返回带中文提示的错误响应
}
// 继续业务逻辑...
}
// 验证失败时的响应示例
{
"success": false,
"message": "验证失败",
"errors": {
"phone": ["手机号 长度必须为 11 位"],
"password": ["密码 长度不能少于 6 位"],
"confirm_password": ["确认密码 必须与 密码 一致"],
"code": ["验证码 长度必须为 6 位"]
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
## 📊 分页和查询规范
### 1. 分页参数
```go
type UserListRequest struct {
Page int `form:"page" binding:"min=1"`
PageSize int `form:"page_size" binding:"min=1,max=100"`
Sort string `form:"sort"` // 排序字段
Order string `form:"order"` // asc/desc
Search string `form:"search"` // 搜索关键词
Filters map[string]interface{} `form:"filters"` // 过滤条件
}
```
### 2. 查询接口设计
```
GET /api/v1/users?page=1&page_size=20&sort=created_at&order=desc&search=john
```
## 🔧 中间件使用规范
### 1. 全局中间件(按优先级)
```go
// internal/container/container.go - RegisterMiddlewares
router.RegisterMiddleware(requestID) // 95 - 请求ID
router.RegisterMiddleware(security) // 85 - 安全头部
router.RegisterMiddleware(responseTime) // 75 - 响应时间
router.RegisterMiddleware(cors) // 70 - CORS
router.RegisterMiddleware(rateLimit) // 65 - 限流
router.RegisterMiddleware(requestLogger) // 80 - 请求日志
```
### 2. 路由级中间件
```go
// 认证中间件
protected.Use(r.jwtAuth.Handle())
// 可选认证中间件
public.Use(r.optionalAuth.Handle())
// 自定义中间件
adminRoutes.Use(r.adminAuth.Handle())
```
## 🎯 错误处理规范
### 1. 业务错误分类 (中文错误码和消息)
```go
// 业务错误结构
type BusinessError struct {
Code string `json:"code"` // 错误码
Message string `json:"message"` // 中文错误消息
Details interface{} `json:"details,omitempty"` // 错误详情
}
// 用户域错误码定义
const (
// 用户相关错误
ErrUserNotFound = "USER_NOT_FOUND" // 用户不存在
ErrUserExists = "USER_EXISTS" // 用户已存在
ErrPhoneExists = "PHONE_EXISTS" // 手机号已存在
ErrInvalidCredentials = "INVALID_CREDENTIALS" // 登录凭据无效
ErrInvalidPassword = "INVALID_PASSWORD" // 密码错误
ErrUserBlocked = "USER_BLOCKED" // 用户被禁用
// 验证码相关错误
ErrInvalidCode = "INVALID_CODE" // 验证码错误
ErrCodeExpired = "CODE_EXPIRED" // 验证码已过期
ErrCodeUsed = "CODE_USED" // 验证码已使用
ErrCodeSendTooFrequent = "CODE_SEND_TOO_FREQUENT" // 验证码发送过于频繁
// 请求相关错误
ErrValidationFailed = "VALIDATION_FAILED" // 参数验证失败
ErrInvalidRequest = "INVALID_REQUEST" // 请求格式错误
ErrMissingParam = "MISSING_PARAM" // 缺少必需参数
// 权限相关错误
ErrUnauthorized = "UNAUTHORIZED" // 未认证
ErrForbidden = "FORBIDDEN" // 权限不足
ErrTokenExpired = "TOKEN_EXPIRED" // Token已过期
ErrTokenInvalid = "TOKEN_INVALID" // Token无效
// 系统相关错误
ErrInternalServer = "INTERNAL_SERVER_ERROR" // 服务器内部错误
ErrServiceUnavailable = "SERVICE_UNAVAILABLE" // 服务不可用
ErrRateLimitExceeded = "RATE_LIMIT_EXCEEDED" // 请求频率超限
)
// 错误消息映射(中文)
var ErrorMessages = map[string]string{
// 用户相关
ErrUserNotFound: "用户不存在",
ErrUserExists: "用户已存在",
ErrPhoneExists: "手机号已被注册",
ErrInvalidCredentials: "用户名或密码错误",
ErrInvalidPassword: "密码错误",
ErrUserBlocked: "账户已被禁用,请联系客服",
// 验证码相关
ErrInvalidCode: "验证码错误",
ErrCodeExpired: "验证码已过期,请重新获取",
ErrCodeUsed: "验证码已使用,请重新获取",
ErrCodeSendTooFrequent: "验证码发送过于频繁,请稍后再试",
// 请求相关
ErrValidationFailed: "请求参数验证失败",
ErrInvalidRequest: "请求格式错误",
ErrMissingParam: "缺少必需参数",
// 权限相关
ErrUnauthorized: "用户未登录或登录已过期",
ErrForbidden: "权限不足,无法访问此资源",
ErrTokenExpired: "登录已过期,请重新登录",
ErrTokenInvalid: "登录信息无效,请重新登录",
// 系统相关
ErrInternalServer: "服务器内部错误,请稍后重试",
ErrServiceUnavailable: "服务暂时不可用,请稍后重试",
ErrRateLimitExceeded: "请求过于频繁,请稍后再试",
}
// 创建业务错误
func NewBusinessError(code string, details ...interface{}) *BusinessError {
message := ErrorMessages[code]
if message == "" {
message = "未知错误"
}
err := &BusinessError{
Code: code,
Message: message,
}
if len(details) > 0 {
err.Details = details[0]
}
return err
}
// 实现error接口
func (e *BusinessError) Error() string {
return e.Message
}
```
### 2. 错误处理模式示例
```go
// 服务层错误处理
func (s *UserService) GetByID(ctx context.Context, id string) (*entities.User, error) {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, NewBusinessError(ErrUserNotFound)
}
s.logger.Error("获取用户失败", zap.Error(err), zap.String("user_id", id))
return nil, NewBusinessError(ErrInternalServer)
}
return user, nil
}
func (s *UserService) Register(ctx context.Context, req *dto.RegisterRequest) (*entities.User, error) {
// 检查手机号是否已存在
existingUser, err := s.repo.FindByPhone(ctx, req.Phone)
if err == nil && existingUser != nil {
return nil, NewBusinessError(ErrPhoneExists)
}
// 验证验证码
if err := s.smsCodeService.VerifyCode(ctx, req.Phone, req.Code, entities.SMSSceneRegister); err != nil {
if strings.Contains(err.Error(), "expired") {
return nil, NewBusinessError(ErrCodeExpired)
}
return nil, NewBusinessError(ErrInvalidCode)
}
// 创建用户...
return user, nil
}
// Handler层错误处理
func (h *UserHandler) GetProfile(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, ErrorMessages[ErrUnauthorized])
return
}
user, err := h.userService.GetByID(c.Request.Context(), userID)
if err != nil {
if bizErr, ok := err.(*BusinessError); ok {
switch bizErr.Code {
case ErrUserNotFound:
h.response.NotFound(c, bizErr.Message)
case ErrUnauthorized:
h.response.Unauthorized(c, bizErr.Message)
case ErrForbidden:
h.response.Forbidden(c, bizErr.Message)
default:
h.response.InternalError(c, bizErr.Message)
}
} else {
h.logger.Error("获取用户信息失败", zap.Error(err))
h.response.InternalError(c, ErrorMessages[ErrInternalServer])
}
return
}
response := dto.FromEntity(user)
h.response.Success(c, response, "获取用户信息成功")
}
// 登录错误处理示例
func (h *UserHandler) LoginWithPassword(c *gin.Context) {
var req dto.LoginWithPasswordRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
return // 验证器已处理响应
}
user, err := h.userService.LoginWithPassword(c.Request.Context(), &req)
if err != nil {
h.logger.Error("用户登录失败", zap.Error(err), zap.String("phone", req.Phone))
if bizErr, ok := err.(*BusinessError); ok {
switch bizErr.Code {
case ErrUserNotFound:
h.response.NotFound(c, "手机号未注册")
case ErrInvalidPassword:
h.response.Unauthorized(c, "密码错误")
case ErrUserBlocked:
h.response.Forbidden(c, bizErr.Message)
default:
h.response.BadRequest(c, bizErr.Message)
}
} else {
h.response.InternalError(c, "登录失败,请稍后重试")
}
return
}
// 生成JWT token...
h.response.Success(c, loginResponse, "登录成功")
}
```
### 3. 统一错误响应格式
```go
// 错误响应中间件
func ErrorHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// 检查是否有未处理的错误
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
if bizErr, ok := err.(*BusinessError); ok {
// 业务错误
c.JSON(getHTTPStatus(bizErr.Code), gin.H{
"success": false,
"message": bizErr.Message,
"error_code": bizErr.Code,
"details": bizErr.Details,
"request_id": c.GetString("request_id"),
"timestamp": time.Now().Unix(),
})
} else {
// 系统错误
c.JSON(500, gin.H{
"success": false,
"message": ErrorMessages[ErrInternalServer],
"error_code": ErrInternalServer,
"request_id": c.GetString("request_id"),
"timestamp": time.Now().Unix(),
})
}
}
}
}
// 根据错误码获取HTTP状态码
func getHTTPStatus(errorCode string) int {
statusMap := map[string]int{
ErrValidationFailed: 400, // Bad Request
ErrInvalidRequest: 400,
ErrMissingParam: 400,
ErrInvalidCode: 400,
ErrPhoneExists: 409, // Conflict
ErrUserExists: 409,
ErrUnauthorized: 401, // Unauthorized
ErrTokenExpired: 401,
ErrTokenInvalid: 401,
ErrInvalidCredentials: 401,
ErrForbidden: 403, // Forbidden
ErrUserBlocked: 403,
ErrUserNotFound: 404, // Not Found
ErrCodeSendTooFrequent: 429, // Too Many Requests
ErrRateLimitExceeded: 429,
ErrInternalServer: 500, // Internal Server Error
ErrServiceUnavailable: 503, // Service Unavailable
}
if status, exists := statusMap[errorCode]; exists {
return status
}
return 500 // 默认服务器错误
}
```
## 📈 日志记录规范
### 1. 结构化日志 (中文日志消息)
```go
// 成功日志
h.logger.Info("用户注册成功",
zap.String("user_id", user.ID),
zap.String("phone", user.Phone),
zap.String("request_id", c.GetString("request_id")))
h.logger.Info("用户登录成功",
zap.String("user_id", user.ID),
zap.String("phone", user.Phone),
zap.String("login_method", "password"),
zap.String("ip_address", c.ClientIP()),
zap.String("request_id", c.GetString("request_id")))
h.logger.Info("验证码发送成功",
zap.String("phone", req.Phone),
zap.String("scene", string(req.Scene)),
zap.String("request_id", c.GetString("request_id")))
// 错误日志
h.logger.Error("用户注册失败",
zap.Error(err),
zap.String("phone", req.Phone),
zap.String("error_type", "business_logic"),
zap.String("request_id", c.GetString("request_id")))
h.logger.Error("数据库操作失败",
zap.Error(err),
zap.String("operation", "create_user"),
zap.String("table", "users"),
zap.String("request_id", c.GetString("request_id")))
h.logger.Error("外部服务调用失败",
zap.Error(err),
zap.String("service", "sms_service"),
zap.String("action", "send_code"),
zap.String("phone", req.Phone),
zap.String("request_id", c.GetString("request_id")))
// 警告日志
h.logger.Warn("验证码重复发送",
zap.String("phone", req.Phone),
zap.String("scene", string(req.Scene)),
zap.Int("retry_count", retryCount),
zap.String("request_id", c.GetString("request_id")))
h.logger.Warn("异常登录尝试",
zap.String("phone", req.Phone),
zap.String("ip_address", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
zap.Int("attempt_count", attemptCount),
zap.String("request_id", c.GetString("request_id")))
// 调试日志
h.logger.Debug("开始处理用户注册请求",
zap.String("phone", req.Phone),
zap.String("request_id", c.GetString("request_id")))
```
### 2. 日志级别使用规范
- **Debug**: 详细的调试信息(开发环境)
- 请求参数详情
- 中间步骤状态
- 性能指标数据
- **Info**: 重要的业务信息(生产环境)
- 用户操作成功记录
- 系统状态变更
- 业务流程关键节点
- **Warn**: 需要关注但不影响主功能的问题
- 重试操作
- 降级处理
- 资源使用超预期
- **Error**: 影响功能的错误信息
- 业务逻辑错误
- 数据库操作失败
- 外部服务调用失败
### 3. 日志上下文信息规范
```go
// 必需字段
- request_id: 请求追踪ID
- user_id: 用户ID如果已认证
- action: 操作类型
- timestamp: 时间戳(自动添加)
// 可选字段
- phone: 手机号(敏感信息需脱敏)
- ip_address: 客户端IP
- user_agent: 用户代理
- error_type: 错误类型分类
- duration: 操作耗时
- service: 服务名称
- method: 请求方法
- path: 请求路径
// 脱敏处理示例
func maskPhone(phone string) string {
if len(phone) != 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}
h.logger.Info("用户登录成功",
zap.String("phone", maskPhone(user.Phone)), // 138****8000
zap.String("user_id", user.ID),
zap.String("request_id", c.GetString("request_id")))
```
## 🧪 测试规范
### 1. 单元测试
```go
func TestUserService_Create(t *testing.T) {
// 使用testify进行测试
assert := assert.New(t)
// Mock依赖
mockRepo := &mocks.UserRepository{}
mockEventBus := &mocks.EventBus{}
service := services.NewUserService(mockRepo, mockEventBus, logger)
// 测试用例...
user, err := service.Create(ctx, req)
assert.NoError(err)
assert.NotNil(user)
}
```
### 2. 集成测试
```go
func TestUserHandler_Create(t *testing.T) {
// 设置测试环境
router := setupTestRouter()
// 发送测试请求
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(jsonData))
router.ServeHTTP(w, req)
// 验证响应
assert.Equal(t, 201, w.Code)
}
```
## 🚀 新增业务领域开发指南
### 1. 创建新领域
```bash
# 1. 创建领域目录结构
mkdir -p internal/domains/product/{dto,entities,events,handlers,repositories,routes,services,validators}
# 2. 复制用户领域作为模板
cp -r internal/domains/user/* internal/domains/product/
# 3. 修改包名和结构体名称
```
### 2. 注册到依赖注入容器
```go
// internal/container/container.go
fx.Provide(
// Product domain
NewProductRepository,
NewProductService,
NewProductHandler,
NewProductRoutes,
),
fx.Invoke(
RegisterProductRoutes,
),
```
### 3. 添加路由注册
```go
func RegisterProductRoutes(
router *http.GinRouter,
productRoutes *routes.ProductRoutes,
) {
productRoutes.RegisterRoutes(router.GetEngine())
productRoutes.RegisterPublicRoutes(router.GetEngine())
productRoutes.RegisterAdminRoutes(router.GetEngine())
}
```
## 🚀 DDD 新域开发指南
### 1. 创建新业务域
```bash
# 1. 创建领域目录结构(以订单域为例)
mkdir -p internal/domains/order/{dto,entities,events,handlers,repositories,routes,services}
# 2. 复制用户域作为模板
cp -r internal/domains/user/* internal/domains/order/
# 3. 批量替换包名和结构体名称
```
### 2. 定义领域实体和 DTO
```go
// internal/domains/order/entities/order.go
type Order struct {
ID string `json:"id" gorm:"primaryKey"`
UserID string `json:"user_id" gorm:"not null"`
TotalAmount float64 `json:"total_amount"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// internal/domains/order/dto/order_dto.go
type CreateOrderRequest struct {
Items []OrderItem `json:"items" binding:"required,dive"`
}
type OrderResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
UserID string `json:"user_id" example:"user-123"`
TotalAmount float64 `json:"total_amount" example:"99.99"`
Status string `json:"status" example:"pending"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
}
```
### 3. 配置领域路由
```go
// internal/domains/order/routes/order_routes.go
func (r *OrderRoutes) RegisterRoutes(router *gin.Engine) {
v1 := router.Group("/api/v1")
// 📦 订单域路由组
orders := v1.Group("/orders")
{
// 公开查询(可选认证)
orders.GET("/:id/public", r.handler.GetPublicOrder)
// 需要认证的路由
authenticated := orders.Group("")
authenticated.Use(r.jwtAuth.Handle())
{
authenticated.GET("", r.handler.List) // GET /api/v1/orders
authenticated.POST("", r.handler.Create) // POST /api/v1/orders
authenticated.GET("/:id", r.handler.GetByID) // GET /api/v1/orders/:id
authenticated.PUT("/:id", r.handler.Update) // PUT /api/v1/orders/:id
authenticated.POST("/:id/cancel", r.handler.Cancel) // POST /api/v1/orders/:id/cancel
authenticated.GET("/:id/items", r.handler.GetItems) // GET /api/v1/orders/:id/items
}
}
}
```
### 4. 注册到依赖注入容器
```go
// internal/container/container.go
fx.Provide(
// User domain
repositories.NewUserRepository,
services.NewUserService,
handlers.NewUserHandler,
routes.NewUserRoutes,
// Order domain - 新增
order_repositories.NewOrderRepository,
order_services.NewOrderService,
order_handlers.NewOrderHandler,
order_routes.NewOrderRoutes,
),
fx.Invoke(
RegisterUserRoutes,
RegisterOrderRoutes, // 新增
),
// 添加路由注册函数
func RegisterOrderRoutes(
router *http.GinRouter,
orderRoutes *order_routes.OrderRoutes,
) {
orderRoutes.RegisterRoutes(router.GetEngine())
}
```
### 5. 跨域关系处理
```go
// 用户订单关系 - 在用户域添加
func (r *UserRoutes) RegisterRoutes(router *gin.Engine) {
users := v1.Group("/users")
authenticated := users.Group("")
authenticated.Use(r.jwtAuth.Handle())
{
authenticated.GET("/me", r.handler.GetProfile)
// 添加用户相关的订单操作
authenticated.GET("/me/orders", r.handler.GetUserOrders) // 获取用户订单
authenticated.GET("/me/orders/stats", r.handler.GetOrderStats) // 订单统计
}
}
// 或者在订单域处理用户关系
func (h *OrderHandler) List(c *gin.Context) {
userID := h.getCurrentUserID(c) // 从JWT中获取用户ID
orders, err := h.orderService.GetUserOrders(ctx, userID)
// ... 业务逻辑
}
```
## 📖 Swagger/OpenAPI 文档集成指南
### 1. 新增接口 Swagger 文档支持
#### 为 Handler 方法添加 Swagger 注释
```go
// @Summary 接口简短描述(必需)
// @Description 接口详细描述(可选)
// @Tags 标签分组(推荐)
// @Accept json
// @Produce json
// @Security Bearer # 如果需要JWT认证
// @Param request body dto.RequestStruct true "请求参数描述"
// @Param id path string true "路径参数描述"
// @Param page query int false "查询参数描述"
// @Success 200 {object} dto.ResponseStruct "成功响应描述"
// @Failure 400 {object} map[string]interface{} "错误响应描述"
// @Router /api/v1/your-endpoint [post]
func (h *YourHandler) YourMethod(c *gin.Context) {
// Handler实现
}
```
#### Swagger 注释语法详解
```go
// 基础注释
// @Summary 接口摘要(在文档列表中显示)
// @Description 详细描述(支持多行)
// @Tags 标签分组用于在UI中分组显示
// 请求/响应格式
// @Accept 接受的内容类型json, xml, plain, html, mpfd, x-www-form-urlencoded
// @Produce 响应的内容类型json, xml, plain, html
// 安全认证
// @Security Bearer # JWT认证
// @Security ApiKeyAuth # API Key认证
// @Security BasicAuth # 基础认证
// 参数定义
// @Param name location type required "description" Enums(A,B,C) default(A)
// location: query, path, header, body, formData
// type: string, number, integer, boolean, array, object
// required: true, false
// 响应定义
// @Success code {type} model "description"
// @Failure code {type} model "description"
// code: HTTP状态码
// type: object, array, string, number, boolean
// model: 响应模型如dto.UserResponse
// 路由定义
// @Router path [method]
// method: get, post, put, delete, patch, head, options
```
### 2. 完整示例:订单域接口文档
```go
// CreateOrder 创建订单
// @Summary 创建新订单
// @Description 根据购物车内容创建新的订单,支持多商品下单
// @Tags 订单管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body dto.CreateOrderRequest true "创建订单请求"
// @Success 201 {object} dto.OrderResponse "订单创建成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 422 {object} map[string]interface{} "业务验证失败"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/orders [post]
func (h *OrderHandler) CreateOrder(c *gin.Context) {
// 实现代码
}
// GetOrderList 获取订单列表
// @Summary 获取当前用户的订单列表
// @Description 分页获取当前用户的订单列表,支持按状态筛选和关键词搜索
// @Tags 订单管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int false "页码" default(1) minimum(1)
// @Param page_size query int false "每页数量" default(20) minimum(1) maximum(100)
// @Param status query string false "订单状态" Enums(pending,paid,shipped,delivered,cancelled)
// @Param search query string false "搜索关键词"
// @Success 200 {object} dto.OrderListResponse "订单列表"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/orders [get]
func (h *OrderHandler) GetOrderList(c *gin.Context) {
// 实现代码
}
// UpdateOrder 更新订单
// @Summary 更新订单信息
// @Description 更新指定订单的部分信息,如收货地址、备注等
// @Tags 订单管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path string true "订单ID" Format(uuid)
// @Param request body dto.UpdateOrderRequest true "更新订单请求"
// @Success 200 {object} dto.OrderResponse "订单更新成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 403 {object} map[string]interface{} "无权限操作此订单"
// @Failure 404 {object} map[string]interface{} "订单不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/orders/{id} [put]
func (h *OrderHandler) UpdateOrder(c *gin.Context) {
// 实现代码
}
```
### 3. DTO 结构体文档化
#### 为请求/响应结构体添加文档标签
```go
// CreateOrderRequest 创建订单请求
type CreateOrderRequest struct {
Items []OrderItem `json:"items" binding:"required,dive" example:"[{\"product_id\":\"123\",\"quantity\":2}]"`
DeliveryAddress string `json:"delivery_address" binding:"required,max=200" example:"北京市朝阳区xxx街道xxx号"`
PaymentMethod string `json:"payment_method" binding:"required,oneof=alipay wechat" example:"alipay"`
Remark string `json:"remark" binding:"max=500" example:"请尽快发货"`
} // @name CreateOrderRequest
// OrderResponse 订单响应
type OrderResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
UserID string `json:"user_id" example:"user-123"`
OrderNo string `json:"order_no" example:"ORD20240101001"`
Status OrderStatus `json:"status" example:"pending"`
TotalAmount float64 `json:"total_amount" example:"299.99"`
PaymentMethod string `json:"payment_method" example:"alipay"`
DeliveryAddress string `json:"delivery_address" example:"北京市朝阳区xxx街道xxx号"`
Items []OrderItem `json:"items"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
} // @name OrderResponse
// OrderItem 订单商品项
type OrderItem struct {
ProductID string `json:"product_id" example:"prod-123"`
ProductName string `json:"product_name" example:"iPhone 15 Pro"`
Quantity int `json:"quantity" example:"1"`
Price float64 `json:"price" example:"999.99"`
Subtotal float64 `json:"subtotal" example:"999.99"`
} // @name OrderItem
// OrderListResponse 订单列表响应
type OrderListResponse struct {
Orders []OrderResponse `json:"orders"`
Pagination Pagination `json:"pagination"`
} // @name OrderListResponse
// Pagination 分页信息
type Pagination struct {
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int `json:"total" example:"150"`
TotalPages int `json:"total_pages" example:"8"`
} // @name Pagination
```
#### 枚举类型文档化
```go
// OrderStatus 订单状态
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending" // 待支付
OrderStatusPaid OrderStatus = "paid" // 已支付
OrderStatusShipped OrderStatus = "shipped" // 已发货
OrderStatusDelivered OrderStatus = "delivered" // 已送达
OrderStatusCancelled OrderStatus = "cancelled" // 已取消
)
// 为枚举添加Swagger文档
// @Description 订单状态
// @Enum pending,paid,shipped,delivered,cancelled
```
### 4. 文档生成和更新流程
#### 标准工作流程
```bash
# 1. 编写/修改Handler方法添加Swagger注释
vim internal/domains/order/handlers/order_handler.go
# 2. 编写/修改DTO结构体添加example标签
vim internal/domains/order/dto/order_dto.go
# 3. 重新生成Swagger文档
make docs
# 或直接使用命令
swag init -g cmd/api/main.go -o docs/swagger
# 4. 重启项目
go run cmd/api/main.go
# 5. 访问文档查看效果
open http://localhost:8080/swagger/index.html
```
#### 快速开发脚本
```bash
# 创建docs脚本scripts/update-docs.sh
#!/bin/bash
echo "🔄 Updating Swagger documentation..."
# 生成文档
make docs
if [ $? -eq 0 ]; then
echo "✅ Swagger documentation updated successfully!"
echo "📖 View at: http://localhost:8080/swagger/index.html"
else
echo "❌ Failed to update documentation"
exit 1
fi
# 重启开发服务器(可选)
if [ "$1" = "--restart" ]; then
echo "🔄 Restarting development server..."
pkill -f "go run cmd/api/main.go"
nohup go run cmd/api/main.go > /dev/null 2>&1 &
echo "🚀 Development server restarted!"
fi
```
### 5. 文档质量检查清单
#### 必需元素检查
- [ ] **@Summary**: 简洁明了的接口描述
- [ ] **@Description**: 详细的功能说明
- [ ] **@Tags**: 正确的分组标签
- [ ] **@Router**: 正确的路径和 HTTP 方法
- [ ] **@Accept/@Produce**: 正确的内容类型
- [ ] **@Security**: 认证要求(如需要)
#### 参数文档检查
- [ ] **路径参数**: 所有{id}等路径参数都有@Param
- [ ] **查询参数**: 分页、筛选等参数都有@Param
- [ ] **请求体**: 复杂请求有@Param body 定义
- [ ] **示例值**: 所有参数都有 realistic 的 example
#### 响应文档检查
- [ ] **成功响应**: @Success 定义了正确的状态码和模型
- [ ] **错误响应**: @Failure 覆盖了主要的错误场景
- [ ] **响应模型**: DTO 结构体有完整的 json 标签和 example
- [ ] **状态码**: 符合 RESTful 规范
### 6. 高级文档特性
#### 自定义响应模型
```go
// 为复杂响应创建专门的文档模型
type APIResponse struct {
Success bool `json:"success" example:"true"`
Data interface{} `json:"data"`
Message string `json:"message" example:"操作成功"`
RequestID string `json:"request_id" example:"req-123"`
Timestamp int64 `json:"timestamp" example:"1640995200"`
} // @name APIResponse
// 在Handler中使用
// @Success 200 {object} APIResponse{data=dto.OrderResponse} "成功响应"
```
#### 分组和版本管理
```go
// 使用一致的标签分组
// @Tags 用户认证 # 认证相关接口
// @Tags 用户管理 # 用户CRUD接口
// @Tags 订单管理 # 订单相关接口
// @Tags 商品管理 # 商品相关接口
// @Tags 系统管理 # 系统功能接口
// 版本控制
// @Router /api/v1/users [post] # V1版本
// @Router /api/v2/users [post] # V2版本向后兼容
```
### 7. 常见问题和解决方案
#### 问题 1文档生成失败
```bash
# 检查Swagger注释语法
swag init -g cmd/api/main.go -o docs/swagger --parseDependency
# 常见错误:
# - 缺少@Router注释
# - HTTP方法写错必须小写
# - 路径格式不正确
# - 缺少必需的包导入
```
#### 问题 2模型没有正确显示
```bash
# 确保结构体有正确的标签
type UserRequest struct {
Name string `json:"name" example:"张三"` # json标签必需
} // @name UserRequest # 显式命名(可选)
# 确保包被正确解析
swag init -g cmd/api/main.go -o docs/swagger --parseDependency --parseInternal
```
#### 问题 3认证测试失败
```go
// 确保安全定义正确
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
// 在接口中正确使用
// @Security Bearer
```
### 8. 持续集成中的文档检查
```bash
# CI脚本示例.github/workflows/docs.yml
name: API Documentation Check
on: [push, pull_request]
jobs:
docs-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.23
- name: Install swag
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Generate docs
run: make docs
- name: Check docs are up to date
run: |
if [[ `git status --porcelain docs/` ]]; then
echo "Documentation is out of date. Please run 'make docs'"
exit 1
fi
```
## 📚 最佳实践总结
### 🏗️ 架构设计原则
1. **领域驱动设计**: 按业务域组织代码和 API 路径,避免技术导向设计
2. **单一职责原则**: 每个层只负责自己的职责,保持清晰的边界分离
3. **依赖注入管理**: 使用 Uber FX 进行依赖管理,支持模块化扩展
4. **接口隔离原则**: 定义清晰的接口边界,便于测试和扩展
### 📋 API 设计规范
5. **统一响应格式**: 标准化的 API 响应结构和中文错误提示
6. **RESTful 路径设计**: 语义化路径清晰表达业务意图
7. **多层数据验证**: 从 DTO 到业务规则的完整验证链
8. **中文化用户体验**: 所有面向用户的消息都使用中文
### 🔧 技术实现规范
9. **结构化日志记录**: 使用 Zap 记录中文结构化日志,便于监控和调试
10. **智能缓存策略**: 合理使用 Redis 缓存提升系统性能
11. **事件驱动架构**: 使用领域事件解耦业务逻辑,支持异步处理
12. **错误处理分层**: 统一的业务错误码和 HTTP 状态码映射
### 📖 开发协作规范
13. **文档优先开发**: 编写接口时同步维护 Swagger 文档,确保文档和代码一致性
14. **完整测试覆盖**: 单元测试、集成测试和端到端测试
15. **代码审查机制**: 确保代码质量和规范一致性
16. **持续集成部署**: 自动化构建、测试和部署流程
### 🚀 性能和扩展性
17. **数据库事务管理**: 合理使用数据库事务确保数据一致性
18. **请求限流保护**: 防止恶意请求和系统过载
19. **监控和告警**: 完整的应用性能监控和业务指标收集
20. **水平扩展支持**: 微服务架构支持横向扩展
## 🔄 配置管理
### 1. 环境配置
```yaml
# config.yaml (开发环境)
server:
port: "8080"
mode: "debug"
# config.prod.yaml (生产环境)
server:
port: "8080"
mode: "release"
```
### 2. 环境变量覆盖
```bash
# 优先级: 环境变量 > 配置文件 > 默认值
export ENV=production
export DB_HOST=prod-database
export JWT_SECRET=secure-jwt-secret
```
## 📋 当前项目 API 接口清单
### 👥 用户域 (User Domain)
```bash
# 🌍 公开接口(无需认证)
POST /api/v1/users/send-code # 发送验证码
POST /api/v1/users/register # 用户注册
POST /api/v1/users/login # 用户登录
# 🔐 认证接口需要JWT Token
GET /api/v1/users/me # 获取当前用户信息
PUT /api/v1/users/me/password # 修改密码
```
### 📱 SMS 验证码域
```bash
# 🌍 公开接口
POST /api/v1/sms/send # 发送验证码与users/send-code相同
```
### 🔧 系统接口
```bash
# 🌍 健康检查
GET /health # 系统健康状态
GET /health/detailed # 详细健康状态
```
### 📊 请求示例
#### 发送验证码
```bash
curl -X POST http://localhost:8080/api/v1/users/send-code \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"scene": "register"
}'
# 响应示例
{
"success": true,
"message": "验证码发送成功",
"data": {
"message": "验证码已发送到您的手机",
"expires_at": "2024-01-01T00:05:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 用户注册
```bash
curl -X POST http://localhost:8080/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"password": "password123",
"confirm_password": "password123",
"code": "123456"
}'
# 响应示例
{
"success": true,
"message": "用户注册成功",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 密码登录
```bash
curl -X POST http://localhost:8080/api/v1/users/login-password \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"password": "password123"
}'
# 响应示例
{
"success": true,
"message": "登录成功",
"data": {
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"login_method": "password"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 短信验证码登录
```bash
curl -X POST http://localhost:8080/api/v1/users/login-sms \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"code": "123456"
}'
# 响应示例同密码登录login_method为"sms"
```
#### 获取当前用户信息
```bash
curl -X GET http://localhost:8080/api/v1/users/me \
-H "Authorization: Bearer <your-jwt-token>"
# 响应示例
{
"success": true,
"message": "获取用户信息成功",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 修改密码
```bash
curl -X PUT http://localhost:8080/api/v1/users/me/password \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"old_password": "oldpassword123",
"new_password": "newpassword123",
"confirm_new_password": "newpassword123",
"code": "123456"
}'
# 响应示例
{
"success": true,
"message": "密码修改成功",
"data": null,
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 错误响应示例
```bash
# 参数验证失败
{
"success": false,
"message": "请求参数验证失败",
"errors": {
"phone": ["手机号 长度必须为 11 位"],
"password": ["密码 长度不能少于 6 位"]
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
# 业务逻辑错误
{
"success": false,
"message": "手机号已被注册",
"request_id": "req_123456789",
"timestamp": 1704067200
}
# 认证失败
{
"success": false,
"message": "用户未登录或登录已过期",
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
### 🔄 响应格式示例
#### 成功响应
```json
// 用户注册成功
{
"success": true,
"message": "用户注册成功",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 用户登录成功
{
"success": true,
"message": "登录成功",
"data": {
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"phone": "13800138000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"login_method": "password"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 发送验证码成功
{
"success": true,
"message": "验证码发送成功",
"data": {
"message": "验证码已发送",
"expires_at": "2024-01-01T00:05:00Z"
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
#### 错误响应
```json
// 参数验证失败
{
"success": false,
"message": "请求参数验证失败",
"errors": {
"phone": ["手机号 长度必须为 11 位"],
"password": ["密码 长度不能少于 6 位"],
"code": ["验证码 长度必须为 6 位"]
},
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 业务逻辑错误
{
"success": false,
"message": "手机号已被注册",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 认证失败
{
"success": false,
"message": "用户未登录或登录已过期",
"request_id": "req_123456789",
"timestamp": 1704067200
}
// 验证码错误
{
"success": false,
"message": "验证码错误或已过期",
"request_id": "req_123456789",
"timestamp": 1704067200
}
```
---
遵循以上规范,可以确保 API 开发的一致性、可维护性和扩展性。