add
This commit is contained in:
449
API调用流程域设计.md
Normal file
449
API调用流程域设计.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# API 调用流程域设计分析
|
||||
|
||||
## 🎯 业务流程分析
|
||||
|
||||
根据你描述的场景,这是一个典型的**B2B 数据服务 API 调用流程**:
|
||||
|
||||
```
|
||||
用户企业 → 调用我们的产品API → 获取第三方大数据服务 → 计费扣款
|
||||
```
|
||||
|
||||
## 🏗️ 涉及的业务域分析
|
||||
|
||||
这个流程总共涉及 **6 个核心域**:
|
||||
|
||||
### 1. 🚪 **网关域 (Gateway Domain)**
|
||||
|
||||
- **职责**: 统一入口、基础路由、请求预处理
|
||||
- **功能**:
|
||||
- 接收所有外部请求
|
||||
- 基础的请求验证
|
||||
- 路由到具体的业务域
|
||||
|
||||
### 2. 🛡️ **安全域 (Security Domain)**
|
||||
|
||||
- **职责**: 认证授权、加密解密、白名单管理
|
||||
- **功能**:
|
||||
- IP 白名单验证
|
||||
- 企业 ID 验证
|
||||
- 密钥管理
|
||||
- 请求参数解密
|
||||
|
||||
### 3. 👤 **用户域 (User Domain)**
|
||||
|
||||
- **职责**: 用户和企业信息管理
|
||||
- **功能**:
|
||||
- 企业认证信息验证
|
||||
- 用户权限检查
|
||||
- 企业密钥获取
|
||||
|
||||
### 4. 📦 **产品域 (Product Domain)**
|
||||
|
||||
- **职责**: 产品访问控制、权限管理
|
||||
- **功能**:
|
||||
- 产品访问权限验证
|
||||
- API 限额检查
|
||||
- 产品配置管理
|
||||
|
||||
### 5. 📊 **数据服务域 (Data Service Domain)**
|
||||
|
||||
- **职责**: 核心业务逻辑、第三方 API 调用
|
||||
- **功能**:
|
||||
- 参数处理和转换
|
||||
- 调用上游数据公司 API
|
||||
- 数据格式转换和响应
|
||||
|
||||
### 6. 💰 **计费域 (Billing Domain)**
|
||||
|
||||
- **职责**: 计费扣款、账单管理
|
||||
- **功能**:
|
||||
- 钱包余额检查
|
||||
- 费用计算
|
||||
- 扣款操作
|
||||
- 计费记录
|
||||
|
||||
### 7. 📋 **审计域 (Audit Domain)**
|
||||
|
||||
- **职责**: 请求记录、日志管理
|
||||
- **功能**:
|
||||
- API 调用记录
|
||||
- 操作日志
|
||||
- 审计追踪
|
||||
|
||||
## 🔄 完整流程设计
|
||||
|
||||
### 架构图
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ 客户端 │
|
||||
└─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 网关域 (Gateway) │
|
||||
│ • 请求接收 • 基础验证 • 路由分发 • 响应聚合 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 数据服务域 (Data Service) │
|
||||
│ [主要协调者] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ │ │ │ │ │
|
||||
▼ ▼ ▼ ▼ ▼ ▼
|
||||
┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐
|
||||
│安全域 ││用户域 ││产品域 ││计费域 ││审计域 ││第三方 │
|
||||
│Security││User ││Product ││Billing ││Audit ││API │
|
||||
└────────┘└────────┘└────────┘└────────┘└────────┘└────────┘
|
||||
```
|
||||
|
||||
### 详细流程设计
|
||||
|
||||
```go
|
||||
// 完整的API调用流程
|
||||
func (h *DataServiceHandler) ProcessAPIRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) {
|
||||
// 1. 审计域 - 开始记录
|
||||
auditID := h.auditService.StartRequest(ctx, req)
|
||||
defer h.auditService.EndRequest(ctx, auditID)
|
||||
|
||||
// 2. 安全域 - IP白名单验证
|
||||
if err := h.securityService.ValidateIP(ctx, req.ClientIP); err != nil {
|
||||
return nil, errors.Wrap(err, "IP not in whitelist")
|
||||
}
|
||||
|
||||
// 3. 用户域 - 企业ID验证
|
||||
enterprise, err := h.userService.GetEnterpriseByID(ctx, req.EnterpriseID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid enterprise ID")
|
||||
}
|
||||
|
||||
// 4. 安全域 - 获取密钥并解密参数
|
||||
secretKey, err := h.securityService.GetEnterpriseSecret(ctx, req.EnterpriseID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get enterprise secret")
|
||||
}
|
||||
|
||||
decryptedParams, err := h.securityService.DecryptParams(ctx, req.EncryptedParams, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decrypt parameters")
|
||||
}
|
||||
|
||||
// 5. 产品域 - 产品权限验证
|
||||
if err := h.productService.ValidateAccess(ctx, enterprise.ID, req.ProductCode); err != nil {
|
||||
return nil, errors.Wrap(err, "no access to product")
|
||||
}
|
||||
|
||||
// 6. 计费域 - 检查余额
|
||||
cost, err := h.billingService.CalculateCost(ctx, req.ProductCode, decryptedParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to calculate cost")
|
||||
}
|
||||
|
||||
if err := h.billingService.CheckBalance(ctx, enterprise.ID, cost); err != nil {
|
||||
return nil, errors.Wrap(err, "insufficient balance")
|
||||
}
|
||||
|
||||
// 7. 数据服务域 - 调用第三方API
|
||||
upstreamResp, err := h.callUpstreamAPI(ctx, decryptedParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "upstream API call failed")
|
||||
}
|
||||
|
||||
// 8. 计费域 - 扣费
|
||||
if err := h.billingService.ChargeAccount(ctx, enterprise.ID, cost, auditID); err != nil {
|
||||
// 记录扣费失败,但不影响响应
|
||||
h.logger.Error("charge failed", zap.Error(err), zap.String("audit_id", auditID))
|
||||
}
|
||||
|
||||
// 9. 安全域 - 加密响应
|
||||
encryptedResp, err := h.securityService.EncryptResponse(ctx, upstreamResp, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to encrypt response")
|
||||
}
|
||||
|
||||
return &APIResponse{
|
||||
Data: encryptedResp,
|
||||
RequestID: auditID,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 🚪 路由入口设计
|
||||
|
||||
### 1. 网关层路由配置
|
||||
|
||||
```yaml
|
||||
# gateway.yaml
|
||||
routes:
|
||||
- path: /api/v1/data/*
|
||||
service: data-service
|
||||
middlewares:
|
||||
- request-id # 生成请求ID
|
||||
- rate-limit # 基础限流
|
||||
- audit-start # 开始审计
|
||||
timeout: 60s
|
||||
|
||||
- path: /api/v1/admin/*
|
||||
service: admin-service
|
||||
middlewares: [admin-auth, rate-limit]
|
||||
|
||||
- path: /api/v1/user/*
|
||||
service: user-service
|
||||
middlewares: [user-auth, rate-limit]
|
||||
```
|
||||
|
||||
### 2. 网关中间件
|
||||
|
||||
```go
|
||||
// 网关层的请求预处理中间件
|
||||
func DataAPIMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 1. 生成请求ID
|
||||
requestID := uuid.New().String()
|
||||
c.Set("request_id", requestID)
|
||||
c.Header("X-Request-ID", requestID)
|
||||
|
||||
// 2. 提取企业ID(从Header或路径参数)
|
||||
enterpriseID := extractEnterpriseID(c)
|
||||
if enterpriseID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing enterprise ID"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("enterprise_id", enterpriseID)
|
||||
|
||||
// 3. 提取客户端IP
|
||||
clientIP := c.ClientIP()
|
||||
c.Set("client_ip", clientIP)
|
||||
|
||||
// 4. 提取产品代码
|
||||
productCode := c.Param("product_code")
|
||||
c.Set("product_code", productCode)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func extractEnterpriseID(c *gin.Context) string {
|
||||
// 优先从Header获取
|
||||
if id := c.GetHeader("X-Enterprise-ID"); id != "" {
|
||||
return id
|
||||
}
|
||||
|
||||
// 从路径参数获取
|
||||
return c.Param("enterprise_id")
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据服务域作为主协调者
|
||||
|
||||
```go
|
||||
// 数据服务域的HTTP处理器
|
||||
type DataServiceHandler struct {
|
||||
securityService *SecurityService
|
||||
userService *UserService
|
||||
productService *ProductService
|
||||
billingService *BillingService
|
||||
auditService *AuditService
|
||||
upstreamClient *UpstreamAPIClient
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// API端点定义
|
||||
func (h *DataServiceHandler) RegisterRoutes(r *gin.RouterGroup) {
|
||||
// 具体的数据产品API
|
||||
r.POST("/financial-data", h.GetFinancialData)
|
||||
r.POST("/credit-check", h.GetCreditCheck)
|
||||
r.POST("/risk-assessment", h.GetRiskAssessment)
|
||||
r.POST("/company-info", h.GetCompanyInfo)
|
||||
}
|
||||
|
||||
// 具体业务处理器
|
||||
func (h *DataServiceHandler) GetFinancialData(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 构建请求对象
|
||||
req := &APIRequest{
|
||||
RequestID: c.GetString("request_id"),
|
||||
EnterpriseID: c.GetString("enterprise_id"),
|
||||
ProductCode: "FINANCIAL_DATA",
|
||||
ClientIP: c.GetString("client_ip"),
|
||||
EncryptedParams: c.PostForm("data"),
|
||||
}
|
||||
|
||||
// 调用业务逻辑
|
||||
resp, err := h.ProcessAPIRequest(ctx, req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
"request_id": req.RequestID,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 域间通信设计
|
||||
|
||||
### 1. 事件驱动架构
|
||||
|
||||
```go
|
||||
// 定义领域事件
|
||||
type APIRequestStarted struct {
|
||||
RequestID string `json:"request_id"`
|
||||
EnterpriseID string `json:"enterprise_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type APIRequestCompleted struct {
|
||||
RequestID string `json:"request_id"`
|
||||
EnterpriseID string `json:"enterprise_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
Success bool `json:"success"`
|
||||
Cost float64 `json:"cost"`
|
||||
Duration int64 `json:"duration_ms"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ChargeRequired struct {
|
||||
RequestID string `json:"request_id"`
|
||||
EnterpriseID string `json:"enterprise_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
ProductCode string `json:"product_code"`
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 异步处理
|
||||
|
||||
```go
|
||||
// 异步计费处理
|
||||
func (s *BillingService) HandleChargeRequired(event ChargeRequired) error {
|
||||
// 异步处理计费,避免阻塞主流程
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.ProcessCharge(ctx, event); err != nil {
|
||||
s.logger.Error("async charge failed",
|
||||
zap.Error(err),
|
||||
zap.String("request_id", event.RequestID))
|
||||
|
||||
// 发送补偿事件
|
||||
s.eventBus.Publish(ChargeFailed{
|
||||
RequestID: event.RequestID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 性能和可扩展性考虑
|
||||
|
||||
### 1. 缓存策略
|
||||
|
||||
```go
|
||||
// 多级缓存策略
|
||||
type CacheStrategy struct {
|
||||
// L1: 本地缓存 (企业信息、产品配置)
|
||||
localCache *cache.Cache
|
||||
|
||||
// L2: Redis缓存 (密钥、白名单)
|
||||
redisCache *redis.Client
|
||||
|
||||
// L3: 数据库
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (cs *CacheStrategy) GetEnterpriseSecret(enterpriseID string) (string, error) {
|
||||
// L1缓存查找
|
||||
if secret, found := cs.localCache.Get("secret:" + enterpriseID); found {
|
||||
return secret.(string), nil
|
||||
}
|
||||
|
||||
// L2缓存查找
|
||||
if secret, err := cs.redisCache.Get(context.Background(), "secret:"+enterpriseID).Result(); err == nil {
|
||||
cs.localCache.Set("secret:"+enterpriseID, secret, 5*time.Minute)
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// L3数据库查找
|
||||
var enterprise Enterprise
|
||||
if err := cs.db.Where("id = ?", enterpriseID).First(&enterprise).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 写入缓存
|
||||
cs.redisCache.Set(context.Background(), "secret:"+enterpriseID, enterprise.SecretKey, time.Hour)
|
||||
cs.localCache.Set("secret:"+enterpriseID, enterprise.SecretKey, 5*time.Minute)
|
||||
|
||||
return enterprise.SecretKey, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 熔断器模式
|
||||
|
||||
```go
|
||||
// 上游API调用熔断器
|
||||
func (s *DataService) callUpstreamAPI(ctx context.Context, params map[string]interface{}) (*UpstreamResponse, error) {
|
||||
return s.circuitBreaker.Execute(func() (interface{}, error) {
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// 构建请求
|
||||
reqBody, _ := json.Marshal(params)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", s.upstreamURL, bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+s.upstreamToken)
|
||||
|
||||
// 发送请求
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 解析响应
|
||||
var upstreamResp UpstreamResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&upstreamResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &upstreamResp, nil
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
### 涉及的域数量:**7 个域**
|
||||
|
||||
1. 网关域 - 统一入口
|
||||
2. 安全域 - 认证加密
|
||||
3. 用户域 - 企业验证
|
||||
4. 产品域 - 权限控制
|
||||
5. 数据服务域 - 核心业务
|
||||
6. 计费域 - 扣费计算
|
||||
7. 审计域 - 请求记录
|
||||
|
||||
### 路由入口设计:
|
||||
|
||||
- **主入口**: 网关域 (`/api/v1/data/`)
|
||||
- **业务协调**: 数据服务域作为主要协调者
|
||||
- **域间通信**: 通过 gRPC 调用和事件总线
|
||||
|
||||
### 设计优势:
|
||||
|
||||
1. **清晰的职责分离** - 每个域专注自己的业务
|
||||
2. **高可扩展性** - 可以独立扩展任何一个域
|
||||
3. **易于测试** - 每个域可以独立测试
|
||||
4. **容错性强** - 单个域故障不影响整体
|
||||
5. **性能优化** - 多级缓存和异步处理
|
||||
|
||||
这种设计既保证了业务逻辑的清晰性,又确保了系统的高性能和高可用性。
|
||||
398
Domain域详解.md
Normal file
398
Domain域详解.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Domain 域概念详解
|
||||
|
||||
## 🤔 什么是 Domain(域)?
|
||||
|
||||
**Domain(域)** 就像现实世界中的**部门**或**业务范围**。
|
||||
|
||||
### 📚 现实世界的类比
|
||||
|
||||
想象一个大公司:
|
||||
|
||||
```
|
||||
阿里巴巴集团
|
||||
├── 电商域 (淘宝、天猫)
|
||||
├── 支付域 (支付宝)
|
||||
├── 云计算域 (阿里云)
|
||||
├── 物流域 (菜鸟)
|
||||
└── 金融域 (蚂蚁金服)
|
||||
```
|
||||
|
||||
每个域都有:
|
||||
|
||||
- **专门的团队** - 不同的开发团队
|
||||
- **独立的业务** - 各自负责不同的业务功能
|
||||
- **清晰的边界** - 知道自己管什么,不管什么
|
||||
- **独立运作** - 可以独立决策和发展
|
||||
|
||||
## 🏗️ 在软件架构中的应用
|
||||
|
||||
### 传统方式 vs 域驱动方式
|
||||
|
||||
#### ❌ 传统方式(技术驱动)
|
||||
|
||||
```
|
||||
项目结构:
|
||||
├── controllers/ # 所有控制器
|
||||
├── services/ # 所有服务
|
||||
├── models/ # 所有数据模型
|
||||
└── utils/ # 工具类
|
||||
|
||||
问题:
|
||||
- 用户相关、产品相关、支付相关的代码混在一起
|
||||
- 不同业务逻辑耦合
|
||||
- 团队协作困难
|
||||
- 修改一个功能可能影响其他功能
|
||||
```
|
||||
|
||||
#### ✅ 域驱动方式(业务驱动)
|
||||
|
||||
```
|
||||
项目结构:
|
||||
├── user-domain/ # 用户域
|
||||
│ ├── user/ # 用户管理
|
||||
│ ├── auth/ # 认证授权
|
||||
│ └── profile/ # 用户资料
|
||||
├── product-domain/ # 产品域
|
||||
│ ├── catalog/ # 产品目录
|
||||
│ ├── inventory/ # 库存管理
|
||||
│ └── pricing/ # 价格管理
|
||||
├── payment-domain/ # 支付域
|
||||
│ ├── wallet/ # 钱包
|
||||
│ ├── order/ # 订单
|
||||
│ └── billing/ # 计费
|
||||
└── notification-domain/ # 通知域
|
||||
├── email/ # 邮件通知
|
||||
├── sms/ # 短信通知
|
||||
└── push/ # 推送通知
|
||||
|
||||
优势:
|
||||
- 按业务功能组织代码
|
||||
- 团队可以独立开发不同的域
|
||||
- 修改用户功能不会影响支付功能
|
||||
- 新人容易理解业务边界
|
||||
```
|
||||
|
||||
## 🎯 你的项目中的 Domain 重构
|
||||
|
||||
### 当前问题
|
||||
|
||||
你的项目中有这些编码式命名:
|
||||
|
||||
```
|
||||
IVYZ - 不知道是什么业务
|
||||
FLXG - 不知道是什么业务
|
||||
QYGL - 不知道是什么业务
|
||||
YYSY - 不知道是什么业务
|
||||
JRZQ - 不知道是什么业务
|
||||
```
|
||||
|
||||
### 重构后的 Domain 设计
|
||||
|
||||
```
|
||||
用户域 (User Domain)
|
||||
├── 用户注册登录
|
||||
├── 用户信息管理
|
||||
├── 企业认证
|
||||
└── 权限管理
|
||||
|
||||
金融域 (Financial Domain)
|
||||
├── 钱包管理
|
||||
├── 支付处理
|
||||
├── 账单生成
|
||||
└── 财务报表
|
||||
|
||||
数据服务域 (Data Service Domain)
|
||||
├── 风险评估服务 (原 FLXG)
|
||||
├── 征信查询服务 (原 JRZQ)
|
||||
├── 企业信息服务 (原 QYGL)
|
||||
├── 数据查询服务 (原 IVYZ)
|
||||
└── 应用系统服务 (原 YYSY)
|
||||
|
||||
产品域 (Product Domain)
|
||||
├── 产品目录管理
|
||||
├── 产品订阅
|
||||
├── 访问控制
|
||||
└── 白名单管理
|
||||
|
||||
平台域 (Platform Domain)
|
||||
├── 管理后台
|
||||
├── 系统监控
|
||||
├── 日志管理
|
||||
└── 配置管理
|
||||
```
|
||||
|
||||
## 💻 代码层面的 Domain 实现
|
||||
|
||||
### 1. Domain 层的职责
|
||||
|
||||
```go
|
||||
// domain/user/entity/user.go
|
||||
package entity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// User 用户实体
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Status UserStatus `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserStatus int
|
||||
|
||||
const (
|
||||
UserStatusActive UserStatus = 1
|
||||
UserStatusInactive UserStatus = 2
|
||||
UserStatusBanned UserStatus = 3
|
||||
)
|
||||
|
||||
// 业务规则:用户名必须是3-20个字符
|
||||
func (u *User) ValidateUsername() error {
|
||||
if len(u.Username) < 3 || len(u.Username) > 20 {
|
||||
return errors.New("用户名必须是3-20个字符")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 业务规则:检查用户是否可以登录
|
||||
func (u *User) CanLogin() bool {
|
||||
return u.Status == UserStatusActive
|
||||
}
|
||||
|
||||
// 业务规则:激活用户
|
||||
func (u *User) Activate() error {
|
||||
if u.Status == UserStatusBanned {
|
||||
return errors.New("被封禁的用户不能激活")
|
||||
}
|
||||
u.Status = UserStatusActive
|
||||
u.UpdatedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Domain Service (领域服务)
|
||||
|
||||
```go
|
||||
// domain/user/service/user_service.go
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"your-project/domain/user/entity"
|
||||
"your-project/domain/user/repository"
|
||||
)
|
||||
|
||||
// UserDomainService 用户领域服务
|
||||
type UserDomainService struct {
|
||||
userRepo repository.UserRepository
|
||||
}
|
||||
|
||||
func NewUserDomainService(userRepo repository.UserRepository) *UserDomainService {
|
||||
return &UserDomainService{
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// 业务规则:检查用户名是否唯一
|
||||
func (s *UserDomainService) IsUsernameUnique(ctx context.Context, username string) (bool, error) {
|
||||
existingUser, err := s.userRepo.FindByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return existingUser == nil, nil
|
||||
}
|
||||
|
||||
// 业务规则:用户注册
|
||||
func (s *UserDomainService) RegisterUser(ctx context.Context, username, email, phone string) (*entity.User, error) {
|
||||
// 1. 检查用户名唯一性
|
||||
unique, err := s.IsUsernameUnique(ctx, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !unique {
|
||||
return nil, errors.New("用户名已存在")
|
||||
}
|
||||
|
||||
// 2. 创建用户实体
|
||||
user := &entity.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Phone: phone,
|
||||
Status: entity.UserStatusActive,
|
||||
}
|
||||
|
||||
// 3. 验证业务规则
|
||||
if err := user.ValidateUsername(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 保存用户
|
||||
return s.userRepo.Save(ctx, user)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Repository 接口 (仓储模式)
|
||||
|
||||
```go
|
||||
// domain/user/repository/user_repository.go
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"your-project/domain/user/entity"
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储接口
|
||||
type UserRepository interface {
|
||||
Save(ctx context.Context, user *entity.User) (*entity.User, error)
|
||||
FindByID(ctx context.Context, id int64) (*entity.User, error)
|
||||
FindByUsername(ctx context.Context, username string) (*entity.User, error)
|
||||
FindByEmail(ctx context.Context, email string) (*entity.User, error)
|
||||
Update(ctx context.Context, user *entity.User) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, offset, limit int) ([]*entity.User, error)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 应用层 (Application Layer)
|
||||
|
||||
```go
|
||||
// application/user/service/user_app_service.go
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
userDomain "your-project/domain/user/service"
|
||||
"your-project/application/user/dto"
|
||||
)
|
||||
|
||||
// UserAppService 用户应用服务
|
||||
type UserAppService struct {
|
||||
userDomainService *userDomain.UserDomainService
|
||||
}
|
||||
|
||||
func NewUserAppService(userDomainService *userDomain.UserDomainService) *UserAppService {
|
||||
return &UserAppService{
|
||||
userDomainService: userDomainService,
|
||||
}
|
||||
}
|
||||
|
||||
// 用户注册用例
|
||||
func (s *UserAppService) RegisterUser(ctx context.Context, req *dto.RegisterUserRequest) (*dto.UserResponse, error) {
|
||||
// 1. 调用领域服务
|
||||
user, err := s.userDomainService.RegisterUser(ctx, req.Username, req.Email, req.Phone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 转换为 DTO
|
||||
return &dto.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Status: string(user.Status),
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 域与域之间的交互
|
||||
|
||||
### 事件驱动交互
|
||||
|
||||
```go
|
||||
// 用户注册成功后,通知其他域
|
||||
type UserRegisteredEvent struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// 金融域监听用户注册事件,自动创建钱包
|
||||
func (h *WalletEventHandler) HandleUserRegistered(event UserRegisteredEvent) error {
|
||||
// 为新用户创建钱包
|
||||
wallet := &entity.Wallet{
|
||||
UserID: event.UserID,
|
||||
Balance: 0.0,
|
||||
Currency: "CNY",
|
||||
}
|
||||
return h.walletRepo.Save(context.Background(), wallet)
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Domain 的好处
|
||||
|
||||
### 1. **团队协作**
|
||||
|
||||
```
|
||||
用户域团队:专注用户相关功能
|
||||
├── 张三:负责用户注册登录
|
||||
├── 李四:负责用户资料管理
|
||||
└── 王五:负责企业认证
|
||||
|
||||
支付域团队:专注支付相关功能
|
||||
├── 赵六:负责钱包功能
|
||||
├── 孙七:负责支付流程
|
||||
└── 周八:负责账单生成
|
||||
```
|
||||
|
||||
### 2. **独立部署**
|
||||
|
||||
```bash
|
||||
# 可以独立部署不同的域
|
||||
kubectl apply -f user-domain-deployment.yaml
|
||||
kubectl apply -f payment-domain-deployment.yaml
|
||||
kubectl apply -f product-domain-deployment.yaml
|
||||
```
|
||||
|
||||
### 3. **技术选择自由**
|
||||
|
||||
```
|
||||
用户域:使用 PostgreSQL (复杂查询)
|
||||
支付域:使用 MySQL (事务性强)
|
||||
数据域:使用 ClickHouse (分析查询)
|
||||
```
|
||||
|
||||
### 4. **测试隔离**
|
||||
|
||||
```go
|
||||
// 只测试用户域,不需要启动其他域
|
||||
func TestUserDomain(t *testing.T) {
|
||||
// 只需要用户域的依赖
|
||||
userRepo := mock.NewUserRepository()
|
||||
userService := service.NewUserDomainService(userRepo)
|
||||
|
||||
// 测试用户注册逻辑
|
||||
user, err := userService.RegisterUser(ctx, "testuser", "test@example.com", "13800138000")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "testuser", user.Username)
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
**Domain(域)就是按业务功能划分的代码组织方式**:
|
||||
|
||||
1. **用户域** - 管理用户相关的所有功能
|
||||
2. **支付域** - 管理支付相关的所有功能
|
||||
3. **产品域** - 管理产品相关的所有功能
|
||||
4. **数据域** - 管理数据查询相关的功能
|
||||
|
||||
**核心思想**:
|
||||
|
||||
- 按业务划分,不按技术划分
|
||||
- 每个域独立开发、测试、部署
|
||||
- 域之间通过事件或 API 通信
|
||||
- 团队可以专注于特定业务领域
|
||||
|
||||
这样组织代码后,你的项目会更容易理解、维护和扩展!
|
||||
@@ -2,10 +2,15 @@ package IVYZ
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"tianyuan-api/pkg/crypto"
|
||||
"tianyuan-api/pkg/errs"
|
||||
|
||||
"tianyuan-api/apps/api/internal/common"
|
||||
"tianyuan-api/apps/api/internal/svc"
|
||||
"tianyuan-api/apps/api/internal/types"
|
||||
"tianyuan-api/apps/api/internal/validator"
|
||||
"tianyuan-api/apps/api/internal/westmodel"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
@@ -25,89 +30,81 @@ func NewIVYZ0B03Logic(ctx context.Context, svcCtx *svc.ServiceContext) *IVYZ0B03
|
||||
}
|
||||
|
||||
func (l *IVYZ0B03Logic) IVYZ0B03(req *types.Request) (resp string, err *errs.AppError) {
|
||||
return
|
||||
//var status string
|
||||
//var charges bool
|
||||
//var remark = ""
|
||||
//secretKey, ok := l.ctx.Value("secretKey").(string)
|
||||
//if !ok {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//transactionID, ok := l.ctx.Value("transactionID").(string)
|
||||
//if !ok {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//userId, userIdOk := l.ctx.Value("userId").(int64)
|
||||
//if !userIdOk {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//productCode, productCodeOk := l.ctx.Value("productCode").(string)
|
||||
//if !productCodeOk || productCode == "" {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//defer func() {
|
||||
// if err != nil {
|
||||
// status = "failed"
|
||||
// charges = false
|
||||
// } else {
|
||||
// status = "success"
|
||||
// charges = true
|
||||
// }
|
||||
// sendApiRequestMessageErr := l.svcCtx.ApiRequestMqsService.SendApiRequestMessage(l.ctx, transactionID, userId, productCode, status, charges, remark)
|
||||
// if sendApiRequestMessageErr != nil {
|
||||
// logx.Errorf("发送 API 请求消息失败: %v", err)
|
||||
// }
|
||||
//}()
|
||||
//// 1、解密
|
||||
//key, decodeErr := hex.DecodeString(secretKey)
|
||||
//if decodeErr != nil {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//decryptData, aesDecryptErr := crypto.AesDecrypt(req.Data, key)
|
||||
//if aesDecryptErr != nil || len(decryptData) == 0 {
|
||||
// return "", errs.ErrParamDecryption
|
||||
//}
|
||||
//
|
||||
//// 2、校验
|
||||
//var data validator.FLXGDEC7Request
|
||||
//if validatorErr := validator.ValidateAndParse(decryptData, &data); validatorErr != nil {
|
||||
// return "", errs.ErrParamValidation
|
||||
//}
|
||||
//
|
||||
//// 3、西部加密
|
||||
//westConfig := l.svcCtx.Config.WestConfig
|
||||
//encryptedFields, encryptStructFieldsErr := common.EncryptStructFields(data, westConfig.Key)
|
||||
//if encryptStructFieldsErr != nil {
|
||||
// logx.Errorf("西部加密错误:%v", encryptStructFieldsErr)
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//
|
||||
//// 4、发送请求到西部
|
||||
//logx.Infof("交易号:%s", transactionID)
|
||||
//apiRequest := common.MapStructToAPIRequest(encryptedFields, westmodel.FLXGDEC7FieldMapping, "data")
|
||||
//
|
||||
//westResp, callAPIErr := l.svcCtx.WestDexService.CallAPI("G23BJ03", apiRequest, l.svcCtx.Config.WestConfig.SecretId)
|
||||
//if callAPIErr != nil {
|
||||
// return "", errs.ErrSystem
|
||||
//}
|
||||
//
|
||||
//// 5、响应解析
|
||||
////var respData westmodel.G32BJ05Response
|
||||
////unmarshalErr := json.Unmarshal(westResp, &respData)
|
||||
////if unmarshalErr != nil {
|
||||
//// return "", errs.ErrSystem
|
||||
////}
|
||||
////
|
||||
////if respData.Data.Code == "00" || respData.Data.Code == "100002" {
|
||||
//// l.ctx = context.WithValue(l.ctx, "Charges", true)
|
||||
////} else {
|
||||
//// return "", errs.ErrSystem
|
||||
////}
|
||||
////encryptData, aesEncrypt := crypto.AesEncrypt(westResp, key)
|
||||
////if aesEncrypt != nil {
|
||||
//// return "", errs.ErrSystem
|
||||
////}
|
||||
//return &types.Response{
|
||||
// Data: string(westResp),
|
||||
//}, nil
|
||||
}
|
||||
var status string
|
||||
var charges bool
|
||||
var remark = ""
|
||||
secretKey, ok := l.ctx.Value("secretKey").(string)
|
||||
if !ok {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
transactionID, ok := l.ctx.Value("transactionID").(string)
|
||||
if !ok {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
userId, userIdOk := l.ctx.Value("userId").(int64)
|
||||
if !userIdOk {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
productCode, productCodeOk := l.ctx.Value("productCode").(string)
|
||||
if !productCodeOk || productCode == "" {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
status = "failed"
|
||||
charges = false
|
||||
} else {
|
||||
status = "success"
|
||||
charges = true
|
||||
}
|
||||
sendApiRequestMessageErr := l.svcCtx.ApiRequestMqsService.SendApiRequestMessage(l.ctx, transactionID, userId, productCode, status, charges, remark)
|
||||
if sendApiRequestMessageErr != nil {
|
||||
logx.Errorf("发送 API 请求消息失败: %v", err)
|
||||
}
|
||||
}()
|
||||
// 1、解密
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
decryptData, aesDecryptErr := crypto.AesDecrypt(req.Data, key)
|
||||
if aesDecryptErr != nil || len(decryptData) == 0 {
|
||||
return "", errs.ErrParamDecryption
|
||||
}
|
||||
|
||||
// 2、校验
|
||||
var data validator.IVYZ0b03Request
|
||||
if validatorErr := validator.ValidateAndParse(decryptData, &data); validatorErr != nil {
|
||||
return "", errs.ErrParamValidation
|
||||
}
|
||||
|
||||
// 3、西部加密
|
||||
westConfig := l.svcCtx.Config.WestConfig
|
||||
encryptedFields, encryptStructFieldsErr := common.EncryptStructFields(data, westConfig.Key)
|
||||
if encryptStructFieldsErr != nil {
|
||||
logx.Errorf("西部加密错误:%v", encryptStructFieldsErr)
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
|
||||
// 4、发送请求到西部
|
||||
logx.Infof("交易号:%s", transactionID)
|
||||
apiRequest := common.MapStructToAPIRequest(encryptedFields, westmodel.IVYZ0b03FieldMapping, "data")
|
||||
|
||||
westResp, callAPIErr := l.svcCtx.WestDexService.CallAPI("G17BJ02", apiRequest, l.svcCtx.Config.WestConfig.SecretId)
|
||||
if callAPIErr != nil {
|
||||
if callAPIErr.Code == errs.ErrDataSource.Code {
|
||||
encryptData, aesEncrypt := crypto.AesEncrypt(westResp, key)
|
||||
if aesEncrypt != nil {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
return encryptData, callAPIErr
|
||||
}
|
||||
return "", callAPIErr
|
||||
}
|
||||
|
||||
encryptData, aesEncrypt := crypto.AesEncrypt(westResp, key)
|
||||
if aesEncrypt != nil {
|
||||
return "", errs.ErrSystem
|
||||
}
|
||||
return encryptData, nil
|
||||
}
|
||||
@@ -3,11 +3,13 @@ package YYSY
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"tianyuan-api/apps/api/internal/common"
|
||||
"tianyuan-api/apps/api/internal/validator"
|
||||
"tianyuan-api/apps/api/internal/westmodel"
|
||||
"tianyuan-api/pkg/crypto"
|
||||
"tianyuan-api/pkg/errs"
|
||||
"time"
|
||||
|
||||
"tianyuan-api/apps/api/internal/svc"
|
||||
"tianyuan-api/apps/api/internal/types"
|
||||
@@ -88,9 +90,10 @@ func (l *YYSYBE08Logic) YYSYBE08(req *types.Request) (resp string, err *errs.App
|
||||
|
||||
// 4、发送请求到西部
|
||||
logx.Infof("交易号:%s", transactionID)
|
||||
apiRequest := common.MapStructToAPIRequest(encryptedFields, westmodel.YYSYBE08FieldMapping, "data")
|
||||
|
||||
westResp, callAPIErr := l.svcCtx.WestDexService.CallAPI("G17BJ02", apiRequest, l.svcCtx.Config.WestConfig.SecretId)
|
||||
apiRequest := common.MapStructToAPIRequest(encryptedFields, westmodel.YYSYBE08FieldMapping, "")
|
||||
apiRequest["customerNumber"] = l.svcCtx.Config.WestConfig.SecretId
|
||||
apiRequest["timeStamp"] = fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
|
||||
westResp, callAPIErr := l.svcCtx.WestDexService.CallAPI("layoutIdcard", apiRequest, l.svcCtx.Config.WestConfig.SecretId)
|
||||
if callAPIErr != nil {
|
||||
if callAPIErr.Code == errs.ErrDataSource.Code {
|
||||
encryptData, aesEncrypt := crypto.AesEncrypt(westResp, key)
|
||||
|
||||
@@ -64,8 +64,6 @@ type FLXGDEC7Request struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZ0B03Request struct {
|
||||
}
|
||||
type IVYZ385ERequest struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -146,10 +144,14 @@ type YYSY09CDRequest struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type YYSYBE08Request struct {
|
||||
type IVYZ0b03Request struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type YYSYBE08Request struct{
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type YYSYD50FRequest struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
|
||||
@@ -135,10 +135,14 @@ var YYSY09CDFieldMapping = map[string]string{
|
||||
"MobileNo": "phone",
|
||||
"MobileType": "phoneType",
|
||||
}
|
||||
var YYSYBE08FieldMapping = map[string]string{
|
||||
var IVYZ0b03FieldMapping = map[string]string{
|
||||
"Name": "name",
|
||||
"MobileNo": "phone",
|
||||
}
|
||||
var YYSYBE08FieldMapping = map[string]string{
|
||||
"MobileNo": "xM",
|
||||
"IDCard": "gMSFZHM",
|
||||
}
|
||||
var YYSYD50FFieldMapping = map[string]string{
|
||||
"MobileNo": "phone",
|
||||
"IDCard": "idNo",
|
||||
|
||||
389
cmd目录详解.md
Normal file
389
cmd目录详解.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# Go 项目中的 cmd 目录详解
|
||||
|
||||
## 🎯 cmd 目录的作用
|
||||
|
||||
`cmd` 目录是 Go 项目的标准目录布局,专门用来存放**可执行程序的入口点**。每个子目录代表一个不同的应用程序。
|
||||
|
||||
## 📁 目录结构示例
|
||||
|
||||
```
|
||||
user-service/
|
||||
├── cmd/ # 应用程序入口目录
|
||||
│ ├── server/ # HTTP/gRPC 服务器
|
||||
│ │ └── main.go # 服务器启动入口
|
||||
│ ├── cli/ # 命令行工具
|
||||
│ │ └── main.go # CLI 工具入口
|
||||
│ ├── worker/ # 后台任务处理器
|
||||
│ │ └── main.go # Worker 入口
|
||||
│ └── migrator/ # 数据库迁移工具
|
||||
│ └── main.go # 迁移工具入口
|
||||
├── internal/ # 内部业务逻辑
|
||||
├── api/ # API 定义
|
||||
└── pkg/ # 可复用的包
|
||||
```
|
||||
|
||||
## 🔧 具体示例
|
||||
|
||||
### 1. 服务器入口 (cmd/server/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"user-service/internal/config"
|
||||
"user-service/internal/server"
|
||||
"user-service/internal/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 解析命令行参数
|
||||
var (
|
||||
configFile = flag.String("config", "configs/config.yaml", "配置文件路径")
|
||||
port = flag.Int("port", 8080, "服务端口")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
// 2. 加载配置
|
||||
cfg, err := config.Load(*configFile)
|
||||
if err != nil {
|
||||
log.Fatal("加载配置失败:", err)
|
||||
}
|
||||
|
||||
// 3. 初始化服务
|
||||
userService := service.NewUserService(cfg)
|
||||
|
||||
// 4. 创建 gRPC 服务器
|
||||
grpcServer := grpc.NewServer()
|
||||
server.RegisterUserServer(grpcServer, userService)
|
||||
|
||||
// 5. 启动服务器
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatal("监听端口失败:", err)
|
||||
}
|
||||
|
||||
// 6. 优雅关闭
|
||||
go func() {
|
||||
log.Printf("用户服务启动在端口 %d", *port)
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatal("服务启动失败:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 7. 等待中断信号
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Println("正在关闭服务...")
|
||||
grpcServer.GracefulStop()
|
||||
log.Println("服务已关闭")
|
||||
}
|
||||
```
|
||||
|
||||
### 2. CLI 工具入口 (cmd/cli/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"user-service/internal/config"
|
||||
"user-service/internal/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
action = flag.String("action", "", "执行的动作: create-user, list-users, reset-password")
|
||||
userID = flag.Int64("user-id", 0, "用户ID")
|
||||
username = flag.String("username", "", "用户名")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load("configs/config.yaml")
|
||||
if err != nil {
|
||||
fmt.Printf("加载配置失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
userService := service.NewUserService(cfg)
|
||||
|
||||
switch *action {
|
||||
case "create-user":
|
||||
if *username == "" {
|
||||
fmt.Println("用户名不能为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
user, err := userService.CreateUser(*username)
|
||||
if err != nil {
|
||||
fmt.Printf("创建用户失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("用户创建成功: ID=%d, Username=%s\n", user.ID, user.Username)
|
||||
|
||||
case "list-users":
|
||||
users, err := userService.ListUsers()
|
||||
if err != nil {
|
||||
fmt.Printf("获取用户列表失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, user := range users {
|
||||
fmt.Printf("ID: %d, Username: %s, Email: %s\n", user.ID, user.Username, user.Email)
|
||||
}
|
||||
|
||||
case "reset-password":
|
||||
if *userID == 0 {
|
||||
fmt.Println("用户ID不能为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
newPassword, err := userService.ResetPassword(*userID)
|
||||
if err != nil {
|
||||
fmt.Printf("重置密码失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("密码重置成功,新密码: %s\n", newPassword)
|
||||
|
||||
default:
|
||||
fmt.Println("不支持的操作,支持的操作: create-user, list-users, reset-password")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Worker 入口 (cmd/worker/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"user-service/internal/config"
|
||||
"user-service/internal/worker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := config.Load("configs/config.yaml")
|
||||
if err != nil {
|
||||
log.Fatal("加载配置失败:", err)
|
||||
}
|
||||
|
||||
// 创建上下文
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// 初始化 Worker
|
||||
emailWorker := worker.NewEmailWorker(cfg)
|
||||
notificationWorker := worker.NewNotificationWorker(cfg)
|
||||
|
||||
// 启动 Workers
|
||||
go func() {
|
||||
log.Println("启动邮件处理 Worker...")
|
||||
if err := emailWorker.Start(ctx); err != nil {
|
||||
log.Printf("邮件 Worker 错误: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
log.Println("启动通知处理 Worker...")
|
||||
if err := notificationWorker.Start(ctx); err != nil {
|
||||
log.Printf("通知 Worker 错误: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Println("正在关闭 Workers...")
|
||||
cancel()
|
||||
|
||||
// 等待 Workers 完成
|
||||
time.Sleep(5 * time.Second)
|
||||
log.Println("Workers 已关闭")
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 数据库迁移工具 (cmd/migrator/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"user-service/internal/config"
|
||||
"user-service/internal/migration"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
direction = flag.String("direction", "up", "迁移方向: up 或 down")
|
||||
steps = flag.Int("steps", 0, "迁移步数,0表示全部")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load("configs/config.yaml")
|
||||
if err != nil {
|
||||
log.Fatal("加载配置失败:", err)
|
||||
}
|
||||
|
||||
migrator := migration.NewMigrator(cfg.Database.URL)
|
||||
|
||||
switch *direction {
|
||||
case "up":
|
||||
log.Println("执行数据库迁移...")
|
||||
if err := migrator.Up(*steps); err != nil {
|
||||
log.Fatal("迁移失败:", err)
|
||||
}
|
||||
log.Println("迁移成功")
|
||||
|
||||
case "down":
|
||||
log.Println("回滚数据库迁移...")
|
||||
if err := migrator.Down(*steps); err != nil {
|
||||
log.Fatal("回滚失败:", err)
|
||||
}
|
||||
log.Println("回滚成功")
|
||||
|
||||
default:
|
||||
log.Fatal("不支持的迁移方向:", *direction)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 为什么这样设计?
|
||||
|
||||
### 1. **关注点分离**
|
||||
|
||||
- `cmd/` 只负责程序启动和配置解析
|
||||
- `internal/` 负责具体的业务逻辑
|
||||
- 每个应用程序有独立的入口点
|
||||
|
||||
### 2. **多种运行模式**
|
||||
|
||||
```bash
|
||||
# 启动 HTTP/gRPC 服务器
|
||||
./user-service-server -port=8080 -config=prod.yaml
|
||||
|
||||
# 使用 CLI 工具
|
||||
./user-service-cli -action=create-user -username=john
|
||||
|
||||
# 启动后台 Worker
|
||||
./user-service-worker
|
||||
|
||||
# 执行数据库迁移
|
||||
./user-service-migrator -direction=up
|
||||
```
|
||||
|
||||
### 3. **构建和部署灵活性**
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile 示例
|
||||
FROM golang:1.20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# 分别构建不同的应用程序
|
||||
RUN go build -o server ./cmd/server
|
||||
RUN go build -o cli ./cmd/cli
|
||||
RUN go build -o worker ./cmd/worker
|
||||
RUN go build -o migrator ./cmd/migrator
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
|
||||
# 根据需要复制不同的可执行文件
|
||||
COPY --from=builder /app/server .
|
||||
COPY --from=builder /app/migrator .
|
||||
|
||||
# 可以选择启动不同的程序
|
||||
CMD ["./server"]
|
||||
```
|
||||
|
||||
### 4. **Makefile 示例**
|
||||
|
||||
```makefile
|
||||
# Makefile
|
||||
.PHONY: build-server build-cli build-worker build-migrator
|
||||
|
||||
build-server:
|
||||
go build -o bin/server ./cmd/server
|
||||
|
||||
build-cli:
|
||||
go build -o bin/cli ./cmd/cli
|
||||
|
||||
build-worker:
|
||||
go build -o bin/worker ./cmd/worker
|
||||
|
||||
build-migrator:
|
||||
go build -o bin/migrator ./cmd/migrator
|
||||
|
||||
build-all: build-server build-cli build-worker build-migrator
|
||||
|
||||
run-server:
|
||||
./bin/server -port=8080
|
||||
|
||||
run-worker:
|
||||
./bin/worker
|
||||
|
||||
migrate-up:
|
||||
./bin/migrator -direction=up
|
||||
|
||||
migrate-down:
|
||||
./bin/migrator -direction=down -steps=1
|
||||
```
|
||||
|
||||
## 🚀 在你的项目中的应用
|
||||
|
||||
在你当前的项目中,每个服务都应该有这样的结构:
|
||||
|
||||
```
|
||||
apps/user/
|
||||
├── cmd/
|
||||
│ ├── server/main.go # gRPC 服务器
|
||||
│ ├── cli/main.go # 用户管理 CLI
|
||||
│ └── migrator/main.go # 数据库迁移
|
||||
├── internal/ # 业务逻辑
|
||||
├── user.proto # API 定义
|
||||
└── Dockerfile
|
||||
|
||||
apps/gateway/
|
||||
├── cmd/
|
||||
│ ├── server/main.go # HTTP 网关服务器
|
||||
│ └── cli/main.go # 网关管理 CLI
|
||||
├── internal/
|
||||
├── gateway.api
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
## 📋 总结
|
||||
|
||||
`cmd` 目录的核心作用:
|
||||
|
||||
1. **程序入口点** - 每个 main.go 是一个独立的应用程序
|
||||
2. **配置解析** - 处理命令行参数和配置文件
|
||||
3. **依赖注入** - 初始化和连接各个组件
|
||||
4. **生命周期管理** - 启动、运行、优雅关闭
|
||||
5. **多种运行模式** - 服务器、CLI、Worker 等不同形态
|
||||
744
go-zero服务实现详解.md
Normal file
744
go-zero服务实现详解.md
Normal file
@@ -0,0 +1,744 @@
|
||||
# go-zero 服务实现详解
|
||||
|
||||
## 🔥 核心服务实现
|
||||
|
||||
### 1. Gateway API 服务 (HTTP 入口)
|
||||
|
||||
#### API 定义 (gateway.api)
|
||||
|
||||
```go
|
||||
syntax = "v1"
|
||||
|
||||
info(
|
||||
title: "天远API网关"
|
||||
desc: "统一API入口"
|
||||
version: "v1.0"
|
||||
)
|
||||
|
||||
type (
|
||||
// 登录请求
|
||||
LoginReq {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
LoginResp {
|
||||
Token string `json:"token"`
|
||||
UserInfo UserInfo `json:"userInfo"`
|
||||
}
|
||||
|
||||
UserInfo {
|
||||
UserId int64 `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
EnterpriseId int64 `json:"enterpriseId"`
|
||||
}
|
||||
|
||||
// 数据查询请求 (你的核心业务)
|
||||
DataQueryReq {
|
||||
QueryType string `json:"queryType"` // risk/credit/company/data
|
||||
Parameters map[string]interface{} `json:"parameters"`
|
||||
}
|
||||
|
||||
DataQueryResp {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data"`
|
||||
TransactionId string `json:"transactionId"`
|
||||
RemainingBalance float64 `json:"remainingBalance"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
group: auth
|
||||
)
|
||||
service gateway-api {
|
||||
@handler LoginHandler
|
||||
post /api/v1/auth/login (LoginReq) returns (LoginResp)
|
||||
|
||||
@handler LogoutHandler
|
||||
post /api/v1/auth/logout returns ()
|
||||
}
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
group: data
|
||||
middleware: RateLimit,Audit
|
||||
)
|
||||
service gateway-api {
|
||||
@handler RiskAssessmentHandler
|
||||
post /api/v1/data/risk-assessment (DataQueryReq) returns (DataQueryResp)
|
||||
|
||||
@handler CreditCheckHandler
|
||||
post /api/v1/data/credit-check (DataQueryReq) returns (DataQueryResp)
|
||||
|
||||
@handler CompanyInfoHandler
|
||||
post /api/v1/data/company-info (DataQueryReq) returns (DataQueryResp)
|
||||
|
||||
@handler DataQueryHandler
|
||||
post /api/v1/data/query (DataQueryReq) returns (DataQueryResp)
|
||||
}
|
||||
```
|
||||
|
||||
#### 核心 Logic 实现 (处理复杂调用链)
|
||||
|
||||
```go
|
||||
// api/gateway/internal/logic/data/risklogic.go
|
||||
|
||||
type RiskLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func (l *RiskLogic) RiskAssessment(req *types.DataQueryReq) (resp *types.DataQueryResp, err error) {
|
||||
// 获取用户信息 (从JWT中解析)
|
||||
userId := ctxdata.GetUidFromCtx(l.ctx)
|
||||
|
||||
// 🔥 调用数据域RPC进行复杂业务处理
|
||||
dataResp, err := l.svcCtx.DataRpc.ProcessDataRequest(l.ctx, &data.ProcessDataRequestReq{
|
||||
UserId: userId,
|
||||
QueryType: "risk-assessment",
|
||||
Parameters: req.Parameters,
|
||||
ClientIp: httpx.GetClientIP(l.ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logx.Errorf("调用数据域RPC失败: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.DataQueryResp{
|
||||
Success: dataResp.Success,
|
||||
Data: dataResp.Data,
|
||||
TransactionId: dataResp.TransactionId,
|
||||
RemainingBalance: dataResp.RemainingBalance,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务上下文 (包含所有 RPC 客户端)
|
||||
|
||||
```go
|
||||
// api/gateway/internal/svc/servicecontext.go
|
||||
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
|
||||
// 🔗 RPC客户端连接
|
||||
UserRpc user.User
|
||||
DataRpc data.Data
|
||||
SecurityRpc security.Security
|
||||
BillingRpc billing.Billing
|
||||
ProductRpc product.Product
|
||||
AuditRpc audit.Audit
|
||||
|
||||
// 中间件
|
||||
RateLimit rest.Middleware
|
||||
AuditMiddleware rest.Middleware
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
|
||||
// 初始化RPC客户端
|
||||
UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
|
||||
DataRpc: data.NewData(zrpc.MustNewClient(c.DataRpc)),
|
||||
SecurityRpc: security.NewSecurity(zrpc.MustNewClient(c.SecurityRpc)),
|
||||
BillingRpc: billing.NewBilling(zrpc.MustNewClient(c.BillingRpc)),
|
||||
ProductRpc: product.NewProduct(zrpc.MustNewClient(c.ProductRpc)),
|
||||
AuditRpc: audit.NewAudit(zrpc.MustNewClient(c.AuditRpc)),
|
||||
|
||||
// 初始化中间件
|
||||
RateLimit: ratelimit.NewRateLimit(c.RateLimit),
|
||||
AuditMiddleware: auditrpc.NewAuditMiddleware(c.Audit),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Data RPC 服务 (核心协调者)
|
||||
|
||||
#### Proto 定义 (data.proto)
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package data;
|
||||
|
||||
option go_package = "./pb";
|
||||
|
||||
// 数据处理请求
|
||||
message ProcessDataRequestReq {
|
||||
int64 user_id = 1;
|
||||
string query_type = 2; // risk-assessment/credit-check/company-info/data-query
|
||||
map<string, string> parameters = 3;
|
||||
string client_ip = 4;
|
||||
}
|
||||
|
||||
message ProcessDataRequestResp {
|
||||
bool success = 1;
|
||||
string data = 2; // JSON格式的业务数据
|
||||
string transaction_id = 3;
|
||||
double remaining_balance = 4;
|
||||
string error_message = 5;
|
||||
}
|
||||
|
||||
service Data {
|
||||
rpc ProcessDataRequest(ProcessDataRequestReq) returns (ProcessDataRequestResp);
|
||||
}
|
||||
```
|
||||
|
||||
#### 核心协调逻辑 (你的复杂业务流程)
|
||||
|
||||
```go
|
||||
// rpc/data/internal/logic/orchestrator/dataorchestratorlogic.go
|
||||
|
||||
type DataOrchestratorLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func (l *DataOrchestratorLogic) ProcessDataRequest(in *pb.ProcessDataRequestReq) (*pb.ProcessDataRequestResp, error) {
|
||||
startTime := time.Now()
|
||||
|
||||
// === 第1步:安全验证 ===
|
||||
|
||||
// 1.1 获取用户企业信息
|
||||
userResp, err := l.svcCtx.UserRpc.GetUserInfo(l.ctx, &user.GetUserInfoReq{
|
||||
UserId: in.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 1.2 IP白名单验证
|
||||
_, err = l.svcCtx.SecurityRpc.CheckWhitelist(l.ctx, &security.CheckWhitelistReq{
|
||||
EnterpriseId: userResp.EnterpriseId,
|
||||
ClientIp: in.ClientIp,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("IP白名单验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 1.3 密钥解密
|
||||
decryptResp, err := l.svcCtx.SecurityRpc.DecryptSecret(l.ctx, &security.DecryptSecretReq{
|
||||
EnterpriseId: userResp.EnterpriseId,
|
||||
EncryptedKey: userResp.EncryptedSecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密钥解密失败: %w", err)
|
||||
}
|
||||
|
||||
// === 第2步:权限与产品验证 ===
|
||||
|
||||
// 2.1 产品权限检查
|
||||
productResp, err := l.svcCtx.ProductRpc.CheckProductAccess(l.ctx, &product.CheckProductAccessReq{
|
||||
UserId: in.UserId,
|
||||
QueryType: in.QueryType,
|
||||
SecretKey: decryptResp.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品权限检查失败: %w", err)
|
||||
}
|
||||
|
||||
// 2.2 余额检查
|
||||
balanceResp, err := l.svcCtx.BillingRpc.CheckBalance(l.ctx, &billing.CheckBalanceReq{
|
||||
UserId: in.UserId,
|
||||
ProductCode: productResp.ProductCode,
|
||||
QueryType: in.QueryType,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("余额不足: %w", err)
|
||||
}
|
||||
|
||||
// === 第3步:执行业务逻辑 ===
|
||||
|
||||
var businessResult *BusinessResult
|
||||
switch in.QueryType {
|
||||
case "risk-assessment":
|
||||
businessResult, err = l.processRiskAssessment(in.Parameters)
|
||||
case "credit-check":
|
||||
businessResult, err = l.processCreditCheck(in.Parameters)
|
||||
case "company-info":
|
||||
businessResult, err = l.processCompanyInfo(in.Parameters)
|
||||
case "data-query":
|
||||
businessResult, err = l.processDataQuery(in.Parameters)
|
||||
default:
|
||||
return nil, errors.New("不支持的查询类型")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("业务处理失败: %w", err)
|
||||
}
|
||||
|
||||
// === 第4步:计费和审计 ===
|
||||
|
||||
// 4.1 执行扣费
|
||||
chargeResp, err := l.svcCtx.BillingRpc.Charge(l.ctx, &billing.ChargeReq{
|
||||
UserId: in.UserId,
|
||||
ProductCode: productResp.ProductCode,
|
||||
Amount: balanceResp.RequiredAmount,
|
||||
TransactionType: in.QueryType,
|
||||
RequestId: generateRequestId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("扣费失败: %w", err)
|
||||
}
|
||||
|
||||
// 4.2 异步记录审计日志
|
||||
go func() {
|
||||
l.svcCtx.AuditRpc.RecordAPICall(context.Background(), &audit.RecordAPICallReq{
|
||||
UserId: in.UserId,
|
||||
EnterpriseId: userResp.EnterpriseId,
|
||||
QueryType: in.QueryType,
|
||||
ClientIp: in.ClientIp,
|
||||
TransactionId: chargeResp.TransactionId,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
Status: "success",
|
||||
})
|
||||
}()
|
||||
|
||||
return &pb.ProcessDataRequestResp{
|
||||
Success: true,
|
||||
Data: businessResult.ToJSON(),
|
||||
TransactionId: chargeResp.TransactionId,
|
||||
RemainingBalance: chargeResp.RemainingBalance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 🔥 原FLXG逻辑 - 风险评估
|
||||
func (l *DataOrchestratorLogic) processRiskAssessment(params map[string]string) (*BusinessResult, error) {
|
||||
// 调用西部数据源
|
||||
westData, err := l.svcCtx.WestDexClient.QueryRiskData(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 调用百度风控API
|
||||
baiduData, err := l.svcCtx.BaiduClient.RiskAssessment(params)
|
||||
if err != nil {
|
||||
logx.Errorf("百度API调用失败: %v", err)
|
||||
// 降级处理,只使用西部数据
|
||||
}
|
||||
|
||||
// 数据融合处理
|
||||
result := &BusinessResult{
|
||||
Code: "FLXG001",
|
||||
Data: mergeRiskData(westData, baiduData),
|
||||
Source: "west+baidu",
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 🔥 原JRZQ逻辑 - 征信查询
|
||||
func (l *DataOrchestratorLogic) processCreditCheck(params map[string]string) (*BusinessResult, error) {
|
||||
// 调用征信API
|
||||
creditData, err := l.svcCtx.CreditClient.QueryCredit(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BusinessResult{
|
||||
Code: "JRZQ001",
|
||||
Data: creditData,
|
||||
Source: "credit_bureau",
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据库操作 (go-zero Model)
|
||||
|
||||
#### 用户模型
|
||||
|
||||
```go
|
||||
// rpc/user/internal/model/usermodel.go
|
||||
|
||||
type User struct {
|
||||
Id int64 `db:"id"`
|
||||
Username string `db:"username"`
|
||||
Password string `db:"password"`
|
||||
Email string `db:"email"`
|
||||
EnterpriseId int64 `db:"enterprise_id"`
|
||||
Status int64 `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
type UserModel interface {
|
||||
Insert(ctx context.Context, data *User) (sql.Result, error)
|
||||
FindOne(ctx context.Context, id int64) (*User, error)
|
||||
FindOneByUsername(ctx context.Context, username string) (*User, error)
|
||||
Update(ctx context.Context, data *User) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type defaultUserModel struct {
|
||||
conn sqlx.SqlConn
|
||||
table string
|
||||
}
|
||||
|
||||
func NewUserModel(conn sqlx.SqlConn) UserModel {
|
||||
return &defaultUserModel{
|
||||
conn: conn,
|
||||
table: "`users`",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) FindOneByUsername(ctx context.Context, username string) (*User, error) {
|
||||
query := fmt.Sprintf("select %s from %s where `username` = ? limit 1", userRows, m.table)
|
||||
var resp User
|
||||
err := m.conn.QueryRowCtx(ctx, &resp, query, username)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 缓存处理
|
||||
|
||||
```go
|
||||
// rpc/user/internal/logic/user/getuserinfologic.go
|
||||
|
||||
func (l *GetUserInfoLogic) GetUserInfo(in *pb.GetUserInfoReq) (*pb.GetUserInfoResp, error) {
|
||||
// 1. 先查缓存
|
||||
cacheKey := fmt.Sprintf("user:info:%d", in.UserId)
|
||||
cached, err := l.svcCtx.RedisClient.Get(cacheKey)
|
||||
if err == nil && cached != "" {
|
||||
var userInfo pb.GetUserInfoResp
|
||||
if json.Unmarshal([]byte(cached), &userInfo) == nil {
|
||||
return &userInfo, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查数据库
|
||||
user, err := l.svcCtx.UserModel.FindOne(l.ctx, in.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enterprise, err := l.svcCtx.EnterpriseModel.FindOne(l.ctx, user.EnterpriseId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &pb.GetUserInfoResp{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
EnterpriseId: user.EnterpriseId,
|
||||
EnterpriseName: enterprise.Name,
|
||||
EncryptedSecretKey: enterprise.EncryptedSecretKey,
|
||||
}
|
||||
|
||||
// 3. 写入缓存 (5分钟过期)
|
||||
respJson, _ := json.Marshal(resp)
|
||||
l.svcCtx.RedisClient.Setex(cacheKey, string(respJson), 300)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 部署配置
|
||||
|
||||
### Docker 部署
|
||||
|
||||
#### 1. 服务 Dockerfile
|
||||
|
||||
```dockerfile
|
||||
# rpc/user/Dockerfile
|
||||
FROM golang:1.19-alpine AS builder
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
RUN go build -ldflags="-s -w" -o user rpc/user/user.go
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /app
|
||||
COPY --from=builder /build/user .
|
||||
COPY --from=builder /build/rpc/user/etc/user.yaml ./etc/
|
||||
|
||||
EXPOSE 8001
|
||||
CMD ["./user", "-f", "etc/user.yaml"]
|
||||
```
|
||||
|
||||
#### 2. Docker Compose (开发环境)
|
||||
|
||||
```yaml
|
||||
# deploy/docker/docker-compose.dev.yml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
# 基础设施
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root123
|
||||
MYSQL_DATABASE: tianyuan
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
etcd:
|
||||
image: quay.io/coreos/etcd:v3.5.0
|
||||
environment:
|
||||
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
|
||||
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
|
||||
ports:
|
||||
- "2379:2379"
|
||||
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:latest
|
||||
environment:
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
||||
ports:
|
||||
- "9092:9092"
|
||||
depends_on:
|
||||
- zookeeper
|
||||
|
||||
zookeeper:
|
||||
image: confluentinc/cp-zookeeper:latest
|
||||
environment:
|
||||
ZOOKEEPER_CLIENT_PORT: 2181
|
||||
|
||||
# 微服务
|
||||
user-rpc:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: rpc/user/Dockerfile
|
||||
ports:
|
||||
- "8001:8001"
|
||||
environment:
|
||||
- DB_HOST=mysql
|
||||
- REDIS_HOST=redis
|
||||
- ETCD_HOSTS=etcd:2379
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
- etcd
|
||||
|
||||
data-rpc:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: rpc/data/Dockerfile
|
||||
ports:
|
||||
- "8002:8002"
|
||||
environment:
|
||||
- DB_HOST=mysql
|
||||
- REDIS_HOST=redis
|
||||
- ETCD_HOSTS=etcd:2379
|
||||
- USER_RPC=user-rpc:8001
|
||||
depends_on:
|
||||
- user-rpc
|
||||
|
||||
gateway-api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: api/gateway/Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- USER_RPC=user-rpc:8001
|
||||
- DATA_RPC=data-rpc:8002
|
||||
- SECURITY_RPC=security-rpc:8003
|
||||
- BILLING_RPC=billing-rpc:8004
|
||||
depends_on:
|
||||
- user-rpc
|
||||
- data-rpc
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
```
|
||||
|
||||
#### 3. Kubernetes 部署
|
||||
|
||||
```yaml
|
||||
# deploy/k8s/user-rpc.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-rpc
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-rpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-rpc
|
||||
spec:
|
||||
containers:
|
||||
- name: user-rpc
|
||||
image: tianyuan/user-rpc:latest
|
||||
ports:
|
||||
- containerPort: 8001
|
||||
env:
|
||||
- name: DB_HOST
|
||||
value: "mysql-svc"
|
||||
- name: REDIS_HOST
|
||||
value: "redis-svc"
|
||||
- name: ETCD_HOSTS
|
||||
value: "etcd-svc:2379"
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8001
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-rpc-svc
|
||||
spec:
|
||||
selector:
|
||||
app: user-rpc
|
||||
ports:
|
||||
- port: 8001
|
||||
targetPort: 8001
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
### 4. Makefile (统一构建部署)
|
||||
|
||||
```makefile
|
||||
# Makefile
|
||||
.PHONY: build-all start-dev stop-dev deploy-k8s
|
||||
|
||||
# 构建所有服务
|
||||
build-all:
|
||||
@echo "构建所有微服务..."
|
||||
cd api/gateway && go build -o ../../bin/gateway gateway.go
|
||||
cd rpc/user && go build -o ../../bin/user-rpc user.go
|
||||
cd rpc/data && go build -o ../../bin/data-rpc data.go
|
||||
cd rpc/security && go build -o ../../bin/security-rpc security.go
|
||||
cd rpc/billing && go build -o ../../bin/billing-rpc billing.go
|
||||
|
||||
# 生成代码
|
||||
gen-api:
|
||||
cd api/gateway && goctl api go -api gateway.api -dir .
|
||||
|
||||
gen-rpc:
|
||||
cd rpc/user && goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
|
||||
|
||||
# 开发环境
|
||||
start-dev:
|
||||
docker-compose -f deploy/docker/docker-compose.dev.yml up -d
|
||||
|
||||
stop-dev:
|
||||
docker-compose -f deploy/docker/docker-compose.dev.yml down
|
||||
|
||||
# 数据库迁移
|
||||
migrate-up:
|
||||
cd tools/migrate && go run migrate.go up
|
||||
|
||||
migrate-down:
|
||||
cd tools/migrate && go run migrate.go down
|
||||
|
||||
# K8s部署
|
||||
deploy-k8s:
|
||||
kubectl apply -f deploy/k8s/namespace.yaml
|
||||
kubectl apply -f deploy/k8s/configmap.yaml
|
||||
kubectl apply -f deploy/k8s/mysql.yaml
|
||||
kubectl apply -f deploy/k8s/redis.yaml
|
||||
kubectl apply -f deploy/k8s/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/data-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/gateway-api.yaml
|
||||
|
||||
# 测试
|
||||
test-all:
|
||||
go test ./api/gateway/...
|
||||
go test ./rpc/user/...
|
||||
go test ./rpc/data/...
|
||||
|
||||
# 代码检查
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
# 清理
|
||||
clean:
|
||||
rm -rf bin/
|
||||
docker system prune -f
|
||||
```
|
||||
|
||||
## 🔄 CI/CD 配置
|
||||
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
name: Deploy Microservices
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Build services
|
||||
run: make build-all
|
||||
|
||||
- name: Run tests
|
||||
run: make test-all
|
||||
|
||||
- name: Build Docker images
|
||||
run: |
|
||||
docker build -t tianyuan/gateway:${{ github.sha }} api/gateway/
|
||||
docker build -t tianyuan/user-rpc:${{ github.sha }} rpc/user/
|
||||
docker build -t tianyuan/data-rpc:${{ github.sha }} rpc/data/
|
||||
|
||||
- name: Push to registry
|
||||
run: |
|
||||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
docker push tianyuan/gateway:${{ github.sha }}
|
||||
docker push tianyuan/user-rpc:${{ github.sha }}
|
||||
docker push tianyuan/data-rpc:${{ github.sha }}
|
||||
|
||||
- name: Deploy to K8s
|
||||
run: |
|
||||
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
|
||||
export KUBECONFIG=kubeconfig
|
||||
sed -i 's|latest|${{ github.sha }}|g' deploy/k8s/*.yaml
|
||||
kubectl apply -f deploy/k8s/
|
||||
```
|
||||
|
||||
这个架构设计完全基于 go-zero 框架,保持了你原有业务逻辑的同时,提供了清晰的服务边界和强大的扩展能力。每个服务都可以独立开发、测试、部署和扩容。
|
||||
739
go-zero错误分级与链路追踪设计.md
Normal file
739
go-zero错误分级与链路追踪设计.md
Normal file
@@ -0,0 +1,739 @@
|
||||
# go-zero 错误分级与链路追踪设计
|
||||
|
||||
## 1. 错误分级体系
|
||||
|
||||
### 1.1 错误等级定义
|
||||
|
||||
```go
|
||||
// shared/errcode/levels.go
|
||||
package errcode
|
||||
|
||||
// 错误等级枚举
|
||||
type ErrorLevel int
|
||||
|
||||
const (
|
||||
LevelDebug ErrorLevel = iota // 调试级别:开发调试信息
|
||||
LevelInfo // 信息级别:一般业务信息
|
||||
LevelWarn // 警告级别:需要关注但不影响业务
|
||||
LevelError // 错误级别:业务错误,需要处理
|
||||
LevelFatal // 致命级别:系统级错误,影响服务
|
||||
LevelPanic // 恐慌级别:严重错误,服务不可用
|
||||
)
|
||||
|
||||
// 错误等级字符串映射
|
||||
var LevelNames = map[ErrorLevel]string{
|
||||
LevelDebug: "DEBUG",
|
||||
LevelInfo: "INFO",
|
||||
LevelWarn: "WARN",
|
||||
LevelError: "ERROR",
|
||||
LevelFatal: "FATAL",
|
||||
LevelPanic: "PANIC",
|
||||
}
|
||||
|
||||
func (l ErrorLevel) String() string {
|
||||
if name, ok := LevelNames[l]; ok {
|
||||
return name
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 错误分类体系
|
||||
|
||||
```go
|
||||
// shared/errcode/types.go
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 错误类型
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// 系统级错误
|
||||
ErrorTypeSystem ErrorType = "SYSTEM" // 系统错误
|
||||
ErrorTypeNetwork ErrorType = "NETWORK" // 网络错误
|
||||
ErrorTypeDatabase ErrorType = "DATABASE" // 数据库错误
|
||||
ErrorTypeRedis ErrorType = "REDIS" // Redis错误
|
||||
ErrorTypeMQ ErrorType = "MQ" // 消息队列错误
|
||||
ErrorTypeRPC ErrorType = "RPC" // RPC调用错误
|
||||
|
||||
// 业务级错误
|
||||
ErrorTypeBusiness ErrorType = "BUSINESS" // 业务逻辑错误
|
||||
ErrorTypeValidation ErrorType = "VALIDATION" // 参数校验错误
|
||||
ErrorTypeAuth ErrorType = "AUTH" // 认证授权错误
|
||||
ErrorTypePermission ErrorType = "PERMISSION" // 权限错误
|
||||
|
||||
// 客户端错误
|
||||
ErrorTypeParam ErrorType = "PARAM" // 参数错误
|
||||
ErrorTypeRequest ErrorType = "REQUEST" // 请求错误
|
||||
ErrorTypeResponse ErrorType = "RESPONSE" // 响应错误
|
||||
)
|
||||
|
||||
// 统一错误结构
|
||||
type AppError struct {
|
||||
Code string `json:"code"` // 错误码
|
||||
Message string `json:"message"` // 错误消息
|
||||
Level ErrorLevel `json:"level"` // 错误等级
|
||||
Type ErrorType `json:"type"` // 错误类型
|
||||
TraceId string `json:"trace_id"` // 链路追踪ID
|
||||
SpanId string `json:"span_id"` // 跨度ID
|
||||
Service string `json:"service"` // 服务名称
|
||||
Method string `json:"method"` // 方法名称
|
||||
Timestamp time.Time `json:"timestamp"` // 时间戳
|
||||
Details interface{} `json:"details"` // 详细信息
|
||||
Stack string `json:"stack"` // 堆栈信息(仅错误级别以上)
|
||||
Cause error `json:"-"` // 原始错误(不序列化)
|
||||
}
|
||||
|
||||
// 实现error接口
|
||||
func (e *AppError) Error() string {
|
||||
return fmt.Sprintf("[%s][%s][%s] %s: %s",
|
||||
e.Level.String(), e.Type, e.Code, e.Service, e.Message)
|
||||
}
|
||||
|
||||
// 获取原始错误
|
||||
func (e *AppError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 错误构造器
|
||||
|
||||
```go
|
||||
// shared/errcode/builder.go
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
)
|
||||
|
||||
type ErrorBuilder struct {
|
||||
service string
|
||||
method string
|
||||
}
|
||||
|
||||
func NewErrorBuilder(service, method string) *ErrorBuilder {
|
||||
return &ErrorBuilder{
|
||||
service: service,
|
||||
method: method,
|
||||
}
|
||||
}
|
||||
|
||||
// Debug级别错误
|
||||
func (b *ErrorBuilder) Debug(code, message string) *AppError {
|
||||
return b.buildError(LevelDebug, ErrorTypeSystem, code, message, nil, nil)
|
||||
}
|
||||
|
||||
// Info级别错误
|
||||
func (b *ErrorBuilder) Info(code, message string) *AppError {
|
||||
return b.buildError(LevelInfo, ErrorTypeSystem, code, message, nil, nil)
|
||||
}
|
||||
|
||||
// Warn级别错误
|
||||
func (b *ErrorBuilder) Warn(errorType ErrorType, code, message string) *AppError {
|
||||
return b.buildError(LevelWarn, errorType, code, message, nil, nil)
|
||||
}
|
||||
|
||||
// Error级别错误
|
||||
func (b *ErrorBuilder) Error(errorType ErrorType, code, message string, cause error) *AppError {
|
||||
return b.buildError(LevelError, errorType, code, message, cause, nil)
|
||||
}
|
||||
|
||||
// Fatal级别错误
|
||||
func (b *ErrorBuilder) Fatal(errorType ErrorType, code, message string, cause error) *AppError {
|
||||
return b.buildError(LevelFatal, errorType, code, message, cause, nil)
|
||||
}
|
||||
|
||||
// Panic级别错误
|
||||
func (b *ErrorBuilder) Panic(errorType ErrorType, code, message string, cause error) *AppError {
|
||||
return b.buildError(LevelPanic, errorType, code, message, cause, nil)
|
||||
}
|
||||
|
||||
// 业务错误(常用)
|
||||
func (b *ErrorBuilder) BusinessError(code, message string) *AppError {
|
||||
return b.buildError(LevelError, ErrorTypeBusiness, code, message, nil, nil)
|
||||
}
|
||||
|
||||
// 参数校验错误(常用)
|
||||
func (b *ErrorBuilder) ValidationError(code, message string, details interface{}) *AppError {
|
||||
return b.buildError(LevelWarn, ErrorTypeValidation, code, message, nil, details)
|
||||
}
|
||||
|
||||
// 权限错误(常用)
|
||||
func (b *ErrorBuilder) PermissionError(code, message string) *AppError {
|
||||
return b.buildError(LevelWarn, ErrorTypePermission, code, message, nil, nil)
|
||||
}
|
||||
|
||||
// 系统错误(常用)
|
||||
func (b *ErrorBuilder) SystemError(code, message string, cause error) *AppError {
|
||||
return b.buildError(LevelFatal, ErrorTypeSystem, code, message, cause, nil)
|
||||
}
|
||||
|
||||
// 构建错误
|
||||
func (b *ErrorBuilder) buildError(level ErrorLevel, errorType ErrorType, code, message string, cause error, details interface{}) *AppError {
|
||||
appErr := &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Level: level,
|
||||
Type: errorType,
|
||||
Service: b.service,
|
||||
Method: b.method,
|
||||
Timestamp: time.Now(),
|
||||
Details: details,
|
||||
Cause: cause,
|
||||
}
|
||||
|
||||
// 获取链路追踪信息
|
||||
if traceId := trace.TraceIDFromContext(ctx); traceId != "" {
|
||||
appErr.TraceId = traceId
|
||||
}
|
||||
if spanId := trace.SpanIDFromContext(ctx); spanId != "" {
|
||||
appErr.SpanId = spanId
|
||||
}
|
||||
|
||||
// 错误级别以上记录堆栈信息
|
||||
if level >= LevelError {
|
||||
appErr.Stack = getStackTrace()
|
||||
}
|
||||
|
||||
return appErr
|
||||
}
|
||||
|
||||
// 获取堆栈信息
|
||||
func getStackTrace() string {
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
return string(buf[:n])
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 链路追踪集成
|
||||
|
||||
### 2.1 链路追踪配置
|
||||
|
||||
```yaml
|
||||
# etc/client-api.yaml
|
||||
Name: client-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8080
|
||||
|
||||
# 链路追踪配置
|
||||
Telemetry:
|
||||
Name: client-api
|
||||
Endpoint: http://jaeger:14268/api/traces
|
||||
Sampler: 1.0
|
||||
Batcher: jaeger
|
||||
|
||||
# 日志配置
|
||||
Log:
|
||||
ServiceName: client-api
|
||||
Mode: file
|
||||
Level: info
|
||||
Path: logs
|
||||
MaxSize: 100
|
||||
MaxAge: 7
|
||||
MaxBackups: 5
|
||||
Compress: true
|
||||
```
|
||||
|
||||
### 2.2 链路追踪中间件
|
||||
|
||||
```go
|
||||
// shared/middleware/trace_middleware.go
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
// HTTP链路追踪中间件
|
||||
func TraceMiddleware(serviceName string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tracer := otel.Tracer(serviceName)
|
||||
|
||||
// 开始span
|
||||
ctx, span := tracer.Start(r.Context(), r.URL.Path)
|
||||
defer span.End()
|
||||
|
||||
// 设置span属性
|
||||
span.SetAttributes(
|
||||
attribute.String("http.method", r.Method),
|
||||
attribute.String("http.url", r.URL.String()),
|
||||
attribute.String("http.user_agent", r.UserAgent()),
|
||||
attribute.String("service.name", serviceName),
|
||||
)
|
||||
|
||||
// 将链路信息注入上下文
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// 创建响应包装器
|
||||
wrapper := &responseWrapper{
|
||||
ResponseWriter: w,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
// 执行下一个处理器
|
||||
next.ServeHTTP(wrapper, r)
|
||||
|
||||
// 设置响应属性
|
||||
span.SetAttributes(
|
||||
attribute.Int("http.status_code", wrapper.statusCode),
|
||||
)
|
||||
|
||||
// 如果是错误状态码,设置span状态
|
||||
if wrapper.statusCode >= 400 {
|
||||
span.SetStatus(codes.Error, http.StatusText(wrapper.statusCode))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 响应包装器
|
||||
type responseWrapper struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (w *responseWrapper) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 RPC 链路追踪拦截器
|
||||
|
||||
```go
|
||||
// shared/interceptor/trace_interceptor.go
|
||||
package interceptor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
// RPC客户端链路追踪拦截器
|
||||
func TraceClientInterceptor(serviceName string) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string, req, reply interface{},
|
||||
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
|
||||
tracer := otel.Tracer(serviceName)
|
||||
ctx, span := tracer.Start(ctx, method)
|
||||
defer span.End()
|
||||
|
||||
// 设置span属性
|
||||
span.SetAttributes(
|
||||
attribute.String("rpc.method", method),
|
||||
attribute.String("rpc.service", serviceName),
|
||||
attribute.String("rpc.system", "grpc"),
|
||||
)
|
||||
|
||||
// 调用RPC
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
|
||||
// 处理错误
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
span.SetAttributes(
|
||||
attribute.String("rpc.grpc.status_code", status.Code(err).String()),
|
||||
)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// RPC服务端链路追踪拦截器
|
||||
func TraceServerInterceptor(serviceName string) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler) (interface{}, error) {
|
||||
|
||||
tracer := otel.Tracer(serviceName)
|
||||
ctx, span := tracer.Start(ctx, info.FullMethod)
|
||||
defer span.End()
|
||||
|
||||
// 设置span属性
|
||||
span.SetAttributes(
|
||||
attribute.String("rpc.method", info.FullMethod),
|
||||
attribute.String("rpc.service", serviceName),
|
||||
attribute.String("rpc.system", "grpc"),
|
||||
)
|
||||
|
||||
// 调用处理器
|
||||
resp, err := handler(ctx, req)
|
||||
|
||||
// 处理错误
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
|
||||
// 如果是自定义错误,记录更多信息
|
||||
if appErr, ok := err.(*errcode.AppError); ok {
|
||||
span.SetAttributes(
|
||||
attribute.String("error.type", string(appErr.Type)),
|
||||
attribute.String("error.code", appErr.Code),
|
||||
attribute.String("error.level", appErr.Level.String()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 日志集成
|
||||
|
||||
### 3.1 结构化日志
|
||||
|
||||
```go
|
||||
// shared/logger/logger.go
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/trace"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
// 日志字段
|
||||
type LogFields map[string]interface{}
|
||||
|
||||
// 结构化日志器
|
||||
type StructuredLogger struct {
|
||||
service string
|
||||
method string
|
||||
}
|
||||
|
||||
func NewStructuredLogger(service, method string) *StructuredLogger {
|
||||
return &StructuredLogger{
|
||||
service: service,
|
||||
method: method,
|
||||
}
|
||||
}
|
||||
|
||||
// 记录错误日志
|
||||
func (l *StructuredLogger) LogError(ctx context.Context, err error, fields LogFields) {
|
||||
logFields := l.buildBaseFields(ctx, fields)
|
||||
|
||||
if appErr, ok := err.(*errcode.AppError); ok {
|
||||
// 自定义错误
|
||||
logFields["error_code"] = appErr.Code
|
||||
logFields["error_type"] = appErr.Type
|
||||
logFields["error_level"] = appErr.Level.String()
|
||||
logFields["error_details"] = appErr.Details
|
||||
|
||||
// 根据错误级别选择日志方法
|
||||
switch appErr.Level {
|
||||
case errcode.LevelDebug:
|
||||
logx.WithContext(ctx).WithFields(logFields).Info(appErr.Message)
|
||||
case errcode.LevelInfo:
|
||||
logx.WithContext(ctx).WithFields(logFields).Info(appErr.Message)
|
||||
case errcode.LevelWarn:
|
||||
logx.WithContext(ctx).WithFields(logFields).Slow(appErr.Message)
|
||||
case errcode.LevelError:
|
||||
logx.WithContext(ctx).WithFields(logFields).Error(appErr.Message)
|
||||
case errcode.LevelFatal, errcode.LevelPanic:
|
||||
logx.WithContext(ctx).WithFields(logFields).Severe(appErr.Message)
|
||||
}
|
||||
} else {
|
||||
// 普通错误
|
||||
logFields["error"] = err.Error()
|
||||
logx.WithContext(ctx).WithFields(logFields).Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 记录业务日志
|
||||
func (l *StructuredLogger) LogInfo(ctx context.Context, message string, fields LogFields) {
|
||||
logFields := l.buildBaseFields(ctx, fields)
|
||||
logx.WithContext(ctx).WithFields(logFields).Info(message)
|
||||
}
|
||||
|
||||
// 记录警告日志
|
||||
func (l *StructuredLogger) LogWarn(ctx context.Context, message string, fields LogFields) {
|
||||
logFields := l.buildBaseFields(ctx, fields)
|
||||
logx.WithContext(ctx).WithFields(logFields).Slow(message)
|
||||
}
|
||||
|
||||
// 构建基础日志字段
|
||||
func (l *StructuredLogger) buildBaseFields(ctx context.Context, fields LogFields) logx.LogField {
|
||||
baseFields := logx.LogField{
|
||||
"service": l.service,
|
||||
"method": l.method,
|
||||
}
|
||||
|
||||
// 添加链路追踪信息
|
||||
if traceId := trace.TraceIDFromContext(ctx); traceId != "" {
|
||||
baseFields["trace_id"] = traceId
|
||||
}
|
||||
if spanId := trace.SpanIDFromContext(ctx); spanId != "" {
|
||||
baseFields["span_id"] = spanId
|
||||
}
|
||||
|
||||
// 合并自定义字段
|
||||
for k, v := range fields {
|
||||
baseFields[k] = v
|
||||
}
|
||||
|
||||
return baseFields
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 日志中间件
|
||||
|
||||
```go
|
||||
// shared/middleware/log_middleware.go
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"tianyuan/shared/logger"
|
||||
)
|
||||
|
||||
// HTTP日志中间件
|
||||
func LogMiddleware(serviceName string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
logger := logger.NewStructuredLogger(serviceName, r.URL.Path)
|
||||
|
||||
// 记录请求开始
|
||||
logger.LogInfo(r.Context(), "request_start", logger.LogFields{
|
||||
"method": r.Method,
|
||||
"path": r.URL.Path,
|
||||
"query": r.URL.RawQuery,
|
||||
"user_agent": r.UserAgent(),
|
||||
"remote_ip": httpx.GetRemoteAddr(r),
|
||||
})
|
||||
|
||||
// 创建响应包装器
|
||||
wrapper := &responseWrapper{
|
||||
ResponseWriter: w,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
// 执行请求
|
||||
next.ServeHTTP(wrapper, r)
|
||||
|
||||
// 记录请求结束
|
||||
duration := time.Since(start)
|
||||
fields := logger.LogFields{
|
||||
"status_code": wrapper.statusCode,
|
||||
"duration_ms": duration.Milliseconds(),
|
||||
}
|
||||
|
||||
if wrapper.statusCode >= 400 {
|
||||
logger.LogWarn(r.Context(), "request_error", fields)
|
||||
} else {
|
||||
logger.LogInfo(r.Context(), "request_success", fields)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 使用示例
|
||||
|
||||
### 4.1 在 Handler 中使用
|
||||
|
||||
```go
|
||||
// client/internal/handler/product/getproductlisthandler.go
|
||||
func (h *GetProductListHandler) GetProductList(w http.ResponseWriter, r *http.Request) {
|
||||
// 创建错误构造器
|
||||
errBuilder := errcode.NewErrorBuilder("client-api", "GetProductList")
|
||||
logger := logger.NewStructuredLogger("client-api", "GetProductList")
|
||||
|
||||
var req types.GetProductListReq
|
||||
|
||||
// 参数校验
|
||||
if err := validator.ValidateAndParse(r, &req); err != nil {
|
||||
appErr := errBuilder.ValidationError("PARAM_INVALID", "参数校验失败", err)
|
||||
logger.LogError(r.Context(), appErr, logger.LogFields{
|
||||
"request": req,
|
||||
})
|
||||
response.ErrorResponse(w, appErr)
|
||||
return
|
||||
}
|
||||
|
||||
// 调用Logic层
|
||||
resp, err := h.logic.GetProductList(r.Context(), &req)
|
||||
if err != nil {
|
||||
logger.LogError(r.Context(), err, logger.LogFields{
|
||||
"request": req,
|
||||
})
|
||||
response.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 记录成功日志
|
||||
logger.LogInfo(r.Context(), "get_product_list_success", logger.LogFields{
|
||||
"request": req,
|
||||
"result_count": len(resp.List),
|
||||
})
|
||||
|
||||
response.SuccessResponse(w, resp)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 在 RPC Logic 中使用
|
||||
|
||||
```go
|
||||
// domains/product/rpc/internal/logic/getproductlistlogic.go
|
||||
func (l *GetProductListLogic) GetProductList(ctx context.Context, req *product.GetProductListReq) (*product.GetProductListResp, error) {
|
||||
errBuilder := errcode.NewErrorBuilder("product-rpc", "GetProductList")
|
||||
logger := logger.NewStructuredLogger("product-rpc", "GetProductList")
|
||||
|
||||
// 业务校验
|
||||
validator := validator.NewProductValidator(ctx, l.svcCtx)
|
||||
if err := validator.ValidateGetProductListRequest(req); err != nil {
|
||||
appErr := errBuilder.BusinessError("VALIDATION_FAILED", err.Error())
|
||||
logger.LogError(ctx, appErr, logger.LogFields{
|
||||
"request": req,
|
||||
})
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
// 查询数据库
|
||||
products, err := l.svcCtx.ProductModel.FindList(ctx, req)
|
||||
if err != nil {
|
||||
appErr := errBuilder.SystemError("DB_QUERY_FAILED", "查询产品列表失败", err)
|
||||
logger.LogError(ctx, appErr, logger.LogFields{
|
||||
"request": req,
|
||||
"db_error": err.Error(),
|
||||
})
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
logger.LogInfo(ctx, "get_product_list_success", logger.LogFields{
|
||||
"request": req,
|
||||
"result_count": len(products),
|
||||
})
|
||||
|
||||
return &product.GetProductListResp{
|
||||
List: products,
|
||||
Total: int64(len(products)),
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 监控和告警
|
||||
|
||||
### 5.1 错误监控配置
|
||||
|
||||
```go
|
||||
// shared/monitor/error_monitor.go
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/zeromicro/go-zero/core/metric"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
var (
|
||||
// 错误计数器
|
||||
ErrorCounter = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||
Namespace: "tianyuan",
|
||||
Subsystem: "error",
|
||||
Name: "total",
|
||||
Help: "Total number of errors",
|
||||
Labels: []string{"service", "type", "level", "code"},
|
||||
})
|
||||
|
||||
// 错误率直方图
|
||||
ErrorRateHistogram = metric.NewHistogramVec(&metric.HistogramVecOpts{
|
||||
Namespace: "tianyuan",
|
||||
Subsystem: "error",
|
||||
Name: "rate",
|
||||
Help: "Error rate histogram",
|
||||
Labels: []string{"service", "method"},
|
||||
})
|
||||
)
|
||||
|
||||
// 记录错误指标
|
||||
func RecordError(appErr *errcode.AppError) {
|
||||
ErrorCounter.Inc(
|
||||
appErr.Service,
|
||||
string(appErr.Type),
|
||||
appErr.Level.String(),
|
||||
appErr.Code,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 告警规则
|
||||
|
||||
```yaml
|
||||
# prometheus告警规则
|
||||
groups:
|
||||
- name: tianyuan-errors
|
||||
rules:
|
||||
# 错误率告警
|
||||
- alert: HighErrorRate
|
||||
expr: rate(tianyuan_error_total[5m]) > 0.1
|
||||
for: 2m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High error rate detected"
|
||||
description: "Error rate is {{ $value }} for service {{ $labels.service }}"
|
||||
|
||||
# 致命错误告警
|
||||
- alert: FatalError
|
||||
expr: increase(tianyuan_error_total{level="FATAL"}[1m]) > 0
|
||||
for: 0m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Fatal error detected"
|
||||
description: "Fatal error in service {{ $labels.service }}: {{ $labels.code }}"
|
||||
```
|
||||
|
||||
## 6. 最佳实践总结
|
||||
|
||||
1. **错误分级原则**:
|
||||
|
||||
- DEBUG/INFO:开发调试信息
|
||||
- WARN:需要关注但不影响业务
|
||||
- ERROR:业务错误,需要处理
|
||||
- FATAL/PANIC:系统级错误,需要立即处理
|
||||
|
||||
2. **链路追踪要点**:
|
||||
|
||||
- 每个请求都有唯一的 TraceId
|
||||
- 跨服务调用保持链路连续性
|
||||
- 关键操作添加自定义 Span
|
||||
|
||||
3. **日志记录规范**:
|
||||
|
||||
- 结构化日志,便于查询分析
|
||||
- 包含链路追踪信息
|
||||
- 敏感信息脱敏处理
|
||||
|
||||
4. **监控告警策略**:
|
||||
- 错误率监控
|
||||
- 关键错误实时告警
|
||||
- 链路追踪性能监控
|
||||
544
validator封装设计.md
Normal file
544
validator封装设计.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# Validator 封装设计
|
||||
|
||||
## 1. 整体架构设计
|
||||
|
||||
```
|
||||
shared/
|
||||
├── validator/ # 格式校验器封装
|
||||
│ ├── validator.go # 校验器初始化和接口定义
|
||||
│ ├── format_validator.go # 格式校验实现
|
||||
│ ├── custom_rules.go # 自定义校验规则
|
||||
│ ├── messages.go # 错误消息配置
|
||||
│ └── middleware.go # 校验中间件
|
||||
├── errcode/
|
||||
│ ├── validator_errors.go # 校验相关错误码
|
||||
└── response/
|
||||
└── validator_response.go # 校验错误响应格式
|
||||
|
||||
domains/
|
||||
└── product/rpc/internal/logic/
|
||||
└── validator/ # 业务校验器封装
|
||||
├── base.go # 业务校验器基类
|
||||
├── product_validator.go
|
||||
└── category_validator.go
|
||||
```
|
||||
|
||||
## 2. 格式校验器封装(shared/validator)
|
||||
|
||||
### 2.1 校验器接口定义
|
||||
|
||||
```go
|
||||
// shared/validator/validator.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
// 校验器接口
|
||||
type IValidator interface {
|
||||
Validate(data interface{}) error
|
||||
ValidateStruct(data interface{}) error
|
||||
AddCustomRule(tag string, fn validator.Func) error
|
||||
}
|
||||
|
||||
// 全局校验器实例
|
||||
var GlobalValidator IValidator
|
||||
|
||||
// 初始化校验器
|
||||
func Init() error {
|
||||
v := &FormatValidator{
|
||||
validator: validator.New(),
|
||||
}
|
||||
|
||||
// 注册自定义规则
|
||||
if err := v.registerCustomRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 注册中文错误消息
|
||||
if err := v.registerMessages(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
GlobalValidator = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// 便捷函数
|
||||
func Validate(data interface{}) error {
|
||||
return GlobalValidator.Validate(data)
|
||||
}
|
||||
|
||||
func ValidateStruct(data interface{}) error {
|
||||
return GlobalValidator.ValidateStruct(data)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 格式校验器实现
|
||||
|
||||
```go
|
||||
// shared/validator/format_validator.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
type FormatValidator struct {
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
// 校验接口实现
|
||||
func (v *FormatValidator) Validate(data interface{}) error {
|
||||
if err := v.validator.Struct(data); err != nil {
|
||||
return v.formatError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *FormatValidator) ValidateStruct(data interface{}) error {
|
||||
return v.Validate(data)
|
||||
}
|
||||
|
||||
func (v *FormatValidator) AddCustomRule(tag string, fn validator.Func) error {
|
||||
return v.validator.RegisterValidation(tag, fn)
|
||||
}
|
||||
|
||||
// 错误格式化
|
||||
func (v *FormatValidator) formatError(err error) error {
|
||||
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||
var errMsgs []string
|
||||
|
||||
for _, fieldError := range validationErrors {
|
||||
errMsg := v.getErrorMessage(fieldError)
|
||||
errMsgs = append(errMsgs, errMsg)
|
||||
}
|
||||
|
||||
return errcode.NewValidationError(strings.Join(errMsgs, "; "))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取错误消息
|
||||
func (v *FormatValidator) getErrorMessage(fieldError validator.FieldError) string {
|
||||
// 获取字段的中文名称
|
||||
fieldName := v.getFieldName(fieldError)
|
||||
|
||||
// 根据校验标签获取错误消息
|
||||
switch fieldError.Tag() {
|
||||
case "required":
|
||||
return fmt.Sprintf("%s不能为空", fieldName)
|
||||
case "min":
|
||||
return fmt.Sprintf("%s最小值为%s", fieldName, fieldError.Param())
|
||||
case "max":
|
||||
return fmt.Sprintf("%s最大值为%s", fieldName, fieldError.Param())
|
||||
case "email":
|
||||
return fmt.Sprintf("%s格式不正确", fieldName)
|
||||
case "mobile":
|
||||
return fmt.Sprintf("%s格式不正确", fieldName)
|
||||
default:
|
||||
return fmt.Sprintf("%s校验失败", fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字段中文名称
|
||||
func (v *FormatValidator) getFieldName(fieldError validator.FieldError) string {
|
||||
// 可以通过反射获取struct tag中的中文名称
|
||||
// 或者维护一个字段名映射表
|
||||
fieldName := fieldError.Field()
|
||||
|
||||
// 简单示例,实际可以更复杂
|
||||
nameMap := map[string]string{
|
||||
"CategoryId": "分类ID",
|
||||
"PageNum": "页码",
|
||||
"PageSize": "每页数量",
|
||||
"Keyword": "关键词",
|
||||
"Mobile": "手机号",
|
||||
"Email": "邮箱",
|
||||
}
|
||||
|
||||
if chineseName, exists := nameMap[fieldName]; exists {
|
||||
return chineseName
|
||||
}
|
||||
|
||||
return fieldName
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 自定义校验规则
|
||||
|
||||
```go
|
||||
// shared/validator/custom_rules.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// 注册自定义校验规则
|
||||
func (v *FormatValidator) registerCustomRules() error {
|
||||
// 手机号校验
|
||||
if err := v.validator.RegisterValidation("mobile", validateMobile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 身份证号校验
|
||||
if err := v.validator.RegisterValidation("idcard", validateIDCard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 企业统一社会信用代码校验
|
||||
if err := v.validator.RegisterValidation("creditcode", validateCreditCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 手机号校验函数
|
||||
func validateMobile(fl validator.FieldLevel) bool {
|
||||
mobile := fl.Field().String()
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, _ := regexp.MatchString(pattern, mobile)
|
||||
return matched
|
||||
}
|
||||
|
||||
// 身份证号校验函数
|
||||
func validateIDCard(fl validator.FieldLevel) bool {
|
||||
idcard := fl.Field().String()
|
||||
pattern := `^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$`
|
||||
matched, _ := regexp.MatchString(pattern, idcard)
|
||||
return matched
|
||||
}
|
||||
|
||||
// 企业统一社会信用代码校验函数
|
||||
func validateCreditCode(fl validator.FieldLevel) bool {
|
||||
code := fl.Field().String()
|
||||
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`
|
||||
matched, _ := regexp.MatchString(pattern, code)
|
||||
return matched
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 校验中间件
|
||||
|
||||
```go
|
||||
// shared/validator/middleware.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"tianyuan/shared/response"
|
||||
)
|
||||
|
||||
// 校验中间件
|
||||
func ValidationMiddleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// 这里可以添加全局校验逻辑
|
||||
// 比如请求头校验、通用参数校验等
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Handler校验辅助函数
|
||||
func ValidateAndParse(r *http.Request, req interface{}) error {
|
||||
// 1. 参数绑定
|
||||
if err := httpx.Parse(r, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 格式校验
|
||||
if err := Validate(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 业务校验器封装(各域内部)
|
||||
|
||||
### 3.1 业务校验器基类
|
||||
|
||||
```go
|
||||
// domains/product/rpc/internal/logic/validator/base.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tianyuan/domains/product/rpc/internal/svc"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
// 业务校验器基类
|
||||
type BaseValidator struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewBaseValidator(ctx context.Context, svcCtx *svc.ServiceContext) *BaseValidator {
|
||||
return &BaseValidator{
|
||||
svcCtx: svcCtx,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// 业务校验接口
|
||||
type IBusinessValidator interface {
|
||||
ValidatePermission(userId int64, action string) error
|
||||
ValidateResourceExists(resourceType string, resourceId int64) error
|
||||
ValidateBusinessRules(data interface{}) error
|
||||
}
|
||||
|
||||
// 通用业务校验方法
|
||||
func (v *BaseValidator) ValidatePermission(userId int64, action string) error {
|
||||
// 调用用户域RPC检查权限
|
||||
resp, err := v.svcCtx.UserRpc.CheckPermission(v.ctx, &user.CheckPermissionReq{
|
||||
UserId: userId,
|
||||
Permission: action,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.HasPermission {
|
||||
return errcode.NewBusinessError("用户无权限执行此操作")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *BaseValidator) ValidateResourceExists(resourceType string, resourceId int64) error {
|
||||
// 根据资源类型检查资源是否存在
|
||||
switch resourceType {
|
||||
case "category":
|
||||
return v.validateCategoryExists(resourceId)
|
||||
case "product":
|
||||
return v.validateProductExists(resourceId)
|
||||
default:
|
||||
return errcode.NewBusinessError("未知的资源类型")
|
||||
}
|
||||
}
|
||||
|
||||
func (v *BaseValidator) validateCategoryExists(categoryId int64) error {
|
||||
category, err := v.svcCtx.CategoryModel.FindOne(v.ctx, categoryId)
|
||||
if err != nil {
|
||||
return errcode.NewBusinessError("分类不存在")
|
||||
}
|
||||
|
||||
if category.Status != 1 {
|
||||
return errcode.NewBusinessError("分类已禁用")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *BaseValidator) validateProductExists(productId int64) error {
|
||||
product, err := v.svcCtx.ProductModel.FindOne(v.ctx, productId)
|
||||
if err != nil {
|
||||
return errcode.NewBusinessError("产品不存在")
|
||||
}
|
||||
|
||||
if product.Status != 1 {
|
||||
return errcode.NewBusinessError("产品已下架")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 具体业务校验器
|
||||
|
||||
```go
|
||||
// domains/product/rpc/internal/logic/validator/product_validator.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tianyuan/domains/product/rpc/internal/svc"
|
||||
"tianyuan/domains/product/rpc/product"
|
||||
"tianyuan/shared/errcode"
|
||||
)
|
||||
|
||||
type ProductValidator struct {
|
||||
*BaseValidator
|
||||
}
|
||||
|
||||
func NewProductValidator(ctx context.Context, svcCtx *svc.ServiceContext) *ProductValidator {
|
||||
return &ProductValidator{
|
||||
BaseValidator: NewBaseValidator(ctx, svcCtx),
|
||||
}
|
||||
}
|
||||
|
||||
// 校验获取产品列表请求
|
||||
func (v *ProductValidator) ValidateGetProductListRequest(req *product.GetProductListReq) error {
|
||||
// 1. 校验用户权限
|
||||
if err := v.ValidatePermission(req.UserId, "product:list"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 校验分类存在性
|
||||
if req.CategoryId > 0 {
|
||||
if err := v.ValidateResourceExists("category", req.CategoryId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 校验用户是否有查看该分类的权限
|
||||
if req.CategoryId > 0 {
|
||||
if err := v.validateCategoryAccess(req.UserId, req.CategoryId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 校验创建产品请求
|
||||
func (v *ProductValidator) ValidateCreateProductRequest(req *product.CreateProductReq) error {
|
||||
// 1. 校验用户权限
|
||||
if err := v.ValidatePermission(req.UserId, "product:create"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 校验分类存在性
|
||||
if err := v.ValidateResourceExists("category", req.CategoryId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 校验产品名称是否重复
|
||||
if err := v.validateProductNameUnique(req.Name, req.CategoryId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 校验分类访问权限
|
||||
func (v *ProductValidator) validateCategoryAccess(userId, categoryId int64) error {
|
||||
// 检查用户是否有访问该分类的权限
|
||||
// 这里可能涉及到用户等级、VIP权限等业务逻辑
|
||||
userInfo, err := v.svcCtx.UserRpc.GetUserInfo(v.ctx, &user.GetUserInfoReq{
|
||||
UserId: userId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 示例:VIP用户可以访问所有分类,普通用户只能访问基础分类
|
||||
category, err := v.svcCtx.CategoryModel.FindOne(v.ctx, categoryId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if category.RequireVip && userInfo.UserType != "vip" {
|
||||
return errcode.NewBusinessError("该分类需要VIP权限")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 校验产品名称唯一性
|
||||
func (v *ProductValidator) validateProductNameUnique(name string, categoryId int64) error {
|
||||
exists, err := v.svcCtx.ProductModel.CheckNameExists(v.ctx, name, categoryId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return errcode.NewBusinessError("同分类下产品名称已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 使用示例
|
||||
|
||||
### 4.1 在 Handler 中使用格式校验
|
||||
|
||||
```go
|
||||
// client/internal/handler/product/getproductlisthandler.go
|
||||
func (h *GetProductListHandler) GetProductList(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.GetProductListReq
|
||||
|
||||
// 使用封装的校验函数
|
||||
if err := validator.ValidateAndParse(r, &req); err != nil {
|
||||
response.ParamErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 调用Logic层
|
||||
resp, err := h.logic.GetProductList(&req)
|
||||
if err != nil {
|
||||
response.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessResponse(w, resp)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 在 RPC Logic 中使用业务校验
|
||||
|
||||
```go
|
||||
// domains/product/rpc/internal/logic/getproductlistlogic.go
|
||||
func (l *GetProductListLogic) GetProductList(req *product.GetProductListReq) (*product.GetProductListResp, error) {
|
||||
// 创建业务校验器
|
||||
validator := validator.NewProductValidator(l.ctx, l.svcCtx)
|
||||
|
||||
// 执行业务校验
|
||||
if err := validator.ValidateGetProductListRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 执行业务逻辑
|
||||
products, total, err := l.svcCtx.ProductModel.FindList(l.ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &product.GetProductListResp{
|
||||
List: products,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 在 main.go 中初始化
|
||||
|
||||
```go
|
||||
// client/client.go
|
||||
func main() {
|
||||
// 初始化格式校验器
|
||||
if err := validator.Init(); err != nil {
|
||||
log.Fatalf("初始化校验器失败: %v", err)
|
||||
}
|
||||
|
||||
// 其他初始化代码...
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 封装的优势
|
||||
|
||||
1. **统一的接口**:所有校验都通过统一的接口调用
|
||||
2. **错误处理标准化**:统一的错误格式和消息
|
||||
3. **可扩展性**:容易添加新的校验规则
|
||||
4. **代码复用**:通用校验逻辑可以复用
|
||||
5. **测试友好**:校验器可以独立测试
|
||||
6. **配置化**:错误消息和校验规则可以配置化管理
|
||||
|
||||
这样的封装既保持了格式校验和业务校验的清晰边界,又提供了便捷的使用接口。
|
||||
405
域与服务的关系及目录架构.md
Normal file
405
域与服务的关系及目录架构.md
Normal file
@@ -0,0 +1,405 @@
|
||||
# 域与服务的关系及目录架构设计
|
||||
|
||||
## 🤔 域 vs 服务的关系
|
||||
|
||||
### 核心概念
|
||||
|
||||
- **域 (Domain)** = **业务边界**,是逻辑概念
|
||||
- **服务 (Service)** = **技术实现**,是物理部署单元
|
||||
|
||||
### 关系图解
|
||||
|
||||
```
|
||||
Domain (域)
|
||||
├── Service A (微服务A)
|
||||
├── Service B (微服务B)
|
||||
└── Service C (微服务C)
|
||||
```
|
||||
|
||||
**一个域可以包含多个微服务,也可以是一个微服务。**
|
||||
|
||||
## 🏗️ 具体的域-服务映射
|
||||
|
||||
### 方案一:一域一服务 (推荐给中小型项目)
|
||||
|
||||
```
|
||||
├── user-domain/ # 用户域
|
||||
│ └── user-service/ # 用户服务 (包含用户+企业+认证)
|
||||
├── security-domain/ # 安全域
|
||||
│ └── security-service/ # 安全服务 (加密+白名单+密钥)
|
||||
├── product-domain/ # 产品域
|
||||
│ └── product-service/ # 产品服务 (产品管理+权限控制)
|
||||
├── data-domain/ # 数据服务域
|
||||
│ └── data-service/ # 数据服务 (统一数据网关)
|
||||
├── billing-domain/ # 计费域
|
||||
│ └── billing-service/ # 计费服务 (钱包+扣费+账单)
|
||||
├── audit-domain/ # 审计域
|
||||
│ └── audit-service/ # 审计服务 (日志+记录)
|
||||
└── gateway-domain/ # 网关域
|
||||
└── gateway-service/ # 网关服务
|
||||
```
|
||||
|
||||
### 方案二:一域多服务 (适合大型项目)
|
||||
|
||||
```
|
||||
├── user-domain/ # 用户域
|
||||
│ ├── user-service/ # 用户管理服务
|
||||
│ ├── enterprise-service/ # 企业认证服务
|
||||
│ └── auth-service/ # 认证授权服务
|
||||
├── security-domain/ # 安全域
|
||||
│ ├── encryption-service/ # 加密解密服务
|
||||
│ ├── whitelist-service/ # 白名单服务
|
||||
│ └── key-management-service/ # 密钥管理服务
|
||||
├── data-domain/ # 数据服务域
|
||||
│ ├── data-gateway-service/ # 数据网关服务
|
||||
│ ├── risk-service/ # 风险评估服务 (原FLXG)
|
||||
│ ├── credit-service/ # 征信服务 (原JRZQ)
|
||||
│ ├── company-service/ # 企业信息服务 (原QYGL)
|
||||
│ └── query-service/ # 数据查询服务 (原IVYZ)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 📁 推荐的目录架构设计
|
||||
|
||||
### 整体项目结构 (基于你的项目)
|
||||
|
||||
```
|
||||
tianyuan-api-platform/
|
||||
├── domains/ # 业务域目录
|
||||
│ ├── user-domain/ # 用户域
|
||||
│ │ └── user-service/ # 用户服务
|
||||
│ │ ├── cmd/ # 应用入口
|
||||
│ │ │ ├── server/main.go # 服务器入口
|
||||
│ │ │ ├── cli/main.go # CLI工具
|
||||
│ │ │ └── migrator/main.go # 数据库迁移
|
||||
│ │ ├── api/ # API定义
|
||||
│ │ │ ├── grpc/ # gRPC定义
|
||||
│ │ │ │ └── user.proto
|
||||
│ │ │ └── http/ # HTTP API定义
|
||||
│ │ │ └── user.api
|
||||
│ │ ├── internal/ # 内部实现
|
||||
│ │ │ ├── domain/ # 领域层
|
||||
│ │ │ │ ├── entity/ # 实体
|
||||
│ │ │ │ │ ├── user.go
|
||||
│ │ │ │ │ └── enterprise.go
|
||||
│ │ │ │ ├── valueobject/ # 值对象
|
||||
│ │ │ │ ├── repository/ # 仓储接口
|
||||
│ │ │ │ │ └── user_repository.go
|
||||
│ │ │ │ └── service/ # 领域服务
|
||||
│ │ │ │ └── user_domain_service.go
|
||||
│ │ │ ├── application/ # 应用层
|
||||
│ │ │ │ ├── service/ # 应用服务
|
||||
│ │ │ │ │ └── user_app_service.go
|
||||
│ │ │ │ ├── dto/ # 数据传输对象
|
||||
│ │ │ │ └── command/ # 命令处理
|
||||
│ │ │ ├── infrastructure/ # 基础设施层
|
||||
│ │ │ │ ├── persistence/ # 持久化
|
||||
│ │ │ │ │ ├── mysql/
|
||||
│ │ │ │ │ │ └── user_repository_impl.go
|
||||
│ │ │ │ │ └── redis/
|
||||
│ │ │ │ ├── messaging/ # 消息队列
|
||||
│ │ │ │ └── external/ # 外部服务
|
||||
│ │ │ └── interfaces/ # 接口适配器
|
||||
│ │ │ ├── grpc/ # gRPC适配器
|
||||
│ │ │ │ └── user_grpc_handler.go
|
||||
│ │ │ ├── http/ # HTTP适配器
|
||||
│ │ │ │ └── user_http_handler.go
|
||||
│ │ │ └── event/ # 事件处理
|
||||
│ │ ├── configs/ # 配置文件
|
||||
│ │ │ ├── config.yaml
|
||||
│ │ │ └── config.dev.yaml
|
||||
│ │ ├── deployments/ # 部署配置
|
||||
│ │ │ ├── Dockerfile
|
||||
│ │ │ └── k8s/
|
||||
│ │ ├── docs/ # 文档
|
||||
│ │ ├── test/ # 测试
|
||||
│ │ └── go.mod
|
||||
│ │
|
||||
│ ├── data-domain/ # 数据服务域
|
||||
│ │ └── data-service/ # 数据服务 (主要协调者)
|
||||
│ │ ├── cmd/
|
||||
│ │ │ └── server/main.go
|
||||
│ │ ├── api/
|
||||
│ │ │ └── http/
|
||||
│ │ │ └── data.api
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── domain/
|
||||
│ │ │ │ └── service/
|
||||
│ │ │ │ ├── risk_assessment_service.go # 原FLXG逻辑
|
||||
│ │ │ │ ├── credit_check_service.go # 原JRZQ逻辑
|
||||
│ │ │ │ ├── company_info_service.go # 原QYGL逻辑
|
||||
│ │ │ │ ├── data_query_service.go # 原IVYZ逻辑
|
||||
│ │ │ │ └── app_system_service.go # 原YYSY逻辑
|
||||
│ │ │ ├── application/
|
||||
│ │ │ │ └── service/
|
||||
│ │ │ │ └── data_orchestrator_service.go # 主协调器
|
||||
│ │ │ ├── infrastructure/
|
||||
│ │ │ │ └── external/
|
||||
│ │ │ │ ├── westdex_client.go # 西部数据源客户端
|
||||
│ │ │ │ ├── baidu_client.go # 百度API客户端
|
||||
│ │ │ │ └── aliyun_client.go # 阿里云API客户端
|
||||
│ │ │ └── interfaces/
|
||||
│ │ │ └── http/
|
||||
│ │ │ ├── risk_handler.go # /api/v1/data/risk-assessment
|
||||
│ │ │ ├── credit_handler.go # /api/v1/data/credit-check
|
||||
│ │ │ ├── company_handler.go # /api/v1/data/company-info
|
||||
│ │ │ └── query_handler.go # /api/v1/data/query
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── security-domain/ # 安全域
|
||||
│ │ └── security-service/
|
||||
│ │ ├── cmd/
|
||||
│ │ ├── api/grpc/
|
||||
│ │ │ └── security.proto
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── domain/
|
||||
│ │ │ │ └── service/
|
||||
│ │ │ │ ├── encryption_service.go # 加密解密
|
||||
│ │ │ │ ├── whitelist_service.go # 白名单管理
|
||||
│ │ │ │ └── key_management_service.go # 密钥管理
|
||||
│ │ │ └── ...
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── billing-domain/ # 计费域
|
||||
│ │ └── billing-service/
|
||||
│ │ ├── cmd/
|
||||
│ │ ├── api/grpc/
|
||||
│ │ │ └── billing.proto
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── domain/
|
||||
│ │ │ │ ├── entity/
|
||||
│ │ │ │ │ ├── wallet.go
|
||||
│ │ │ │ │ ├── transaction.go
|
||||
│ │ │ │ │ └── billing_record.go
|
||||
│ │ │ │ └── service/
|
||||
│ │ │ │ ├── wallet_service.go
|
||||
│ │ │ │ ├── charging_service.go
|
||||
│ │ │ │ └── billing_service.go
|
||||
│ │ │ └── ...
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── product-domain/ # 产品域
|
||||
│ │ └── product-service/
|
||||
│ │ └── ... (类似结构)
|
||||
│ │
|
||||
│ ├── audit-domain/ # 审计域
|
||||
│ │ └── audit-service/
|
||||
│ │ └── ... (类似结构)
|
||||
│ │
|
||||
│ └── gateway-domain/ # 网关域
|
||||
│ └── gateway-service/
|
||||
│ ├── cmd/
|
||||
│ │ └── server/main.go
|
||||
│ ├── internal/
|
||||
│ │ ├── middleware/ # 中间件
|
||||
│ │ │ ├── auth.go
|
||||
│ │ │ ├── rate_limit.go
|
||||
│ │ │ └── audit.go
|
||||
│ │ ├── router/ # 路由器
|
||||
│ │ │ └── gateway_router.go
|
||||
│ │ └── proxy/ # 代理
|
||||
│ │ └── service_proxy.go
|
||||
│ └── configs/
|
||||
│ └── gateway.yaml
|
||||
│
|
||||
├── shared/ # 共享库
|
||||
│ ├── pkg/ # 通用包
|
||||
│ │ ├── crypto/ # 加密工具
|
||||
│ │ ├── jwt/ # JWT工具
|
||||
│ │ ├── response/ # 响应格式
|
||||
│ │ ├── errors/ # 错误定义
|
||||
│ │ ├── logger/ # 日志工具
|
||||
│ │ └── utils/ # 工具函数
|
||||
│ ├── events/ # 领域事件定义
|
||||
│ │ ├── user_events.go
|
||||
│ │ ├── billing_events.go
|
||||
│ │ └── audit_events.go
|
||||
│ └── proto/ # 公共protobuf定义
|
||||
│ └── common.proto
|
||||
│
|
||||
├── infrastructure/ # 基础设施
|
||||
│ ├── docker-compose.yaml # 本地开发环境
|
||||
│ ├── kubernetes/ # K8s部署文件
|
||||
│ │ ├── user-service.yaml
|
||||
│ │ ├── data-service.yaml
|
||||
│ │ └── gateway-service.yaml
|
||||
│ ├── terraform/ # 基础设施即代码
|
||||
│ └── monitoring/ # 监控配置
|
||||
│ ├── prometheus/
|
||||
│ └── grafana/
|
||||
│
|
||||
├── tools/ # 工具和脚本
|
||||
│ ├── generate/ # 代码生成器
|
||||
│ ├── migration/ # 数据迁移脚本
|
||||
│ └── deploy/ # 部署脚本
|
||||
│
|
||||
├── docs/ # 项目文档
|
||||
│ ├── architecture/ # 架构文档
|
||||
│ ├── api/ # API文档
|
||||
│ └── deployment/ # 部署文档
|
||||
│
|
||||
├── scripts/ # 构建和部署脚本
|
||||
│ ├── build.sh
|
||||
│ ├── deploy.sh
|
||||
│ └── test.sh
|
||||
│
|
||||
├── .github/ # GitHub Actions
|
||||
│ └── workflows/
|
||||
│ ├── user-service.yml
|
||||
│ ├── data-service.yml
|
||||
│ └── gateway-service.yml
|
||||
│
|
||||
├── Makefile # 统一构建脚本
|
||||
├── docker-compose.yml # 整体服务编排
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 🔄 从当前项目的迁移策略
|
||||
|
||||
### 当前项目映射关系
|
||||
|
||||
```
|
||||
当前结构 → 新结构
|
||||
|
||||
apps/api/ → domains/data-domain/data-service/
|
||||
├── FLXG/ → internal/domain/service/risk_assessment_service.go
|
||||
├── JRZQ/ → internal/domain/service/credit_check_service.go
|
||||
├── QYGL/ → internal/domain/service/company_info_service.go
|
||||
├── IVYZ/ → internal/domain/service/data_query_service.go
|
||||
└── YYSY/ → internal/domain/service/app_system_service.go
|
||||
|
||||
apps/user/ → domains/user-domain/user-service/
|
||||
apps/sentinel/ → domains/product-domain/product-service/
|
||||
apps/gateway/ → domains/gateway-domain/gateway-service/
|
||||
apps/admin/ → domains/admin-domain/admin-service/
|
||||
apps/mqs/ → domains/audit-domain/audit-service/
|
||||
|
||||
pkg/ → shared/pkg/
|
||||
westDex/ → domains/data-domain/data-service/internal/infrastructure/external/
|
||||
```
|
||||
|
||||
### 逐步迁移计划
|
||||
|
||||
#### 阶段 1: 创建新的目录结构 (1 周)
|
||||
|
||||
```bash
|
||||
# 创建新的目录结构
|
||||
mkdir -p domains/{user,data,security,billing,product,audit,gateway}-domain
|
||||
mkdir -p shared/{pkg,events,proto}
|
||||
mkdir -p infrastructure/{kubernetes,terraform,monitoring}
|
||||
```
|
||||
|
||||
#### 阶段 2: 迁移共享包 (1 周)
|
||||
|
||||
```bash
|
||||
# 迁移共享代码
|
||||
mv pkg/ shared/pkg/
|
||||
mv westDex/ domains/data-domain/data-service/internal/infrastructure/external/westdex/
|
||||
```
|
||||
|
||||
#### 阶段 3: 重构数据服务域 (2-3 周)
|
||||
|
||||
```go
|
||||
// 创建数据服务协调器
|
||||
// domains/data-domain/data-service/internal/application/service/data_orchestrator.go
|
||||
|
||||
type DataOrchestrator struct {
|
||||
riskService *RiskAssessmentService // 原FLXG
|
||||
creditService *CreditCheckService // 原JRZQ
|
||||
companyService *CompanyInfoService // 原QYGL
|
||||
queryService *DataQueryService // 原IVYZ
|
||||
appService *AppSystemService // 原YYSY
|
||||
|
||||
// 依赖的其他域服务
|
||||
securityService SecurityServiceClient // gRPC客户端
|
||||
userService UserServiceClient // gRPC客户端
|
||||
productService ProductServiceClient // gRPC客户端
|
||||
billingService BillingServiceClient // gRPC客户端
|
||||
auditService AuditServiceClient // gRPC客户端
|
||||
}
|
||||
|
||||
func (d *DataOrchestrator) ProcessAPIRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) {
|
||||
// 之前分析的完整流程代码
|
||||
}
|
||||
```
|
||||
|
||||
#### 阶段 4: 拆分其他域服务 (3-4 周)
|
||||
|
||||
逐个创建和迁移其他域服务
|
||||
|
||||
#### 阶段 5: 完善基础设施 (2 周)
|
||||
|
||||
添加监控、CI/CD、部署等
|
||||
|
||||
## 🚀 实际开发工作流
|
||||
|
||||
### 1. 单个服务开发
|
||||
|
||||
```bash
|
||||
cd domains/user-domain/user-service
|
||||
|
||||
# 开发
|
||||
go run cmd/server/main.go
|
||||
|
||||
# 测试
|
||||
go test ./...
|
||||
|
||||
# 构建
|
||||
go build -o bin/user-service cmd/server/main.go
|
||||
|
||||
# 部署
|
||||
docker build -t user-service .
|
||||
```
|
||||
|
||||
### 2. 跨域开发
|
||||
|
||||
```bash
|
||||
# 启动依赖服务
|
||||
docker-compose up mysql redis kafka
|
||||
|
||||
# 启动相关服务
|
||||
make start-user-service
|
||||
make start-security-service
|
||||
make start-data-service
|
||||
|
||||
# 集成测试
|
||||
make test-integration
|
||||
```
|
||||
|
||||
### 3. 统一管理
|
||||
|
||||
```makefile
|
||||
# Makefile
|
||||
.PHONY: build-all start-all test-all
|
||||
|
||||
build-all:
|
||||
cd domains/user-domain/user-service && go build -o ../../../bin/user-service cmd/server/main.go
|
||||
cd domains/data-domain/data-service && go build -o ../../../bin/data-service cmd/server/main.go
|
||||
cd domains/gateway-domain/gateway-service && go build -o ../../../bin/gateway-service cmd/server/main.go
|
||||
|
||||
start-all:
|
||||
docker-compose up -d
|
||||
|
||||
test-all:
|
||||
cd domains/user-domain/user-service && go test ./...
|
||||
cd domains/data-domain/data-service && go test ./...
|
||||
cd domains/gateway-domain/gateway-service && go test ./...
|
||||
```
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
### 域与服务的关系:
|
||||
|
||||
- **域 = 业务边界** (逻辑概念)
|
||||
- **服务 = 部署单元** (物理实现)
|
||||
- **推荐:一域一服务** (对于你的项目规模)
|
||||
|
||||
### 目录架构核心原则:
|
||||
|
||||
1. **按域组织** - 顶层按业务域划分
|
||||
2. **标准结构** - 每个服务内部结构一致
|
||||
3. **清晰分层** - 领域层、应用层、基础设施层、接口层
|
||||
4. **共享复用** - 公共代码放在 shared 目录
|
||||
5. **独立部署** - 每个服务都可以独立构建部署
|
||||
|
||||
这种架构既保持了逻辑上的清晰性,又确保了技术实现的可操作性!
|
||||
418
基于go-zero的完整微服务架构设计.md
Normal file
418
基于go-zero的完整微服务架构设计.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# 基于 go-zero 的完整微服务架构设计
|
||||
|
||||
## 🏗️ 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ External Access │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │Web App │ │Mobile │ │3rd API │ │Admin │ │
|
||||
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||
└───────│────────────│─────────────│─────────────│───────┘
|
||||
│ │ │ │
|
||||
└────────────┼─────────────┼─────────────┘
|
||||
│ │
|
||||
┌──────▼─────────────▼──────┐
|
||||
│ Gateway API (HTTP) │ 🌐 HTTP入口
|
||||
└──────┬────────────────────┘
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌───▼───┐ ┌─────▼─────┐
|
||||
│User RPC │ │Data │ │Admin HTTP │ 🔥 核心服务
|
||||
│ │ │RPC │ │ │
|
||||
└────┬────┘ └───┬───┘ └─────┬─────┘
|
||||
│ │ │
|
||||
┌────▼────┐ ┌───▼───┐ ┌─────▼─────┐
|
||||
│Security │ │Billing│ │Audit MQS │ 🛡️ 支撑服务
|
||||
│RPC │ │RPC │ │ │
|
||||
└─────────┘ └───────┘ └───────────┘
|
||||
```
|
||||
|
||||
## 📁 完整目录结构
|
||||
|
||||
```
|
||||
tianyuan-microservice/
|
||||
├── api/ # HTTP API服务
|
||||
│ ├── gateway/ # 🌐 API网关 (唯一HTTP入口)
|
||||
│ │ ├── gateway.api # API定义
|
||||
│ │ ├── gateway.go # 启动文件
|
||||
│ │ ├── etc/
|
||||
│ │ │ ├── gateway.yaml # 生产配置
|
||||
│ │ │ └── gateway-dev.yaml # 开发配置
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── config/config.go # 配置结构
|
||||
│ │ │ ├── handler/ # HTTP处理器
|
||||
│ │ │ │ ├── auth/
|
||||
│ │ │ │ │ ├── loginhandler.go
|
||||
│ │ │ │ │ └── logouthandler.go
|
||||
│ │ │ │ ├── data/
|
||||
│ │ │ │ │ ├── riskhandler.go # /api/v1/data/risk
|
||||
│ │ │ │ │ ├── credithandler.go # /api/v1/data/credit
|
||||
│ │ │ │ │ └── companyhandler.go # /api/v1/data/company
|
||||
│ │ │ │ ├── user/
|
||||
│ │ │ │ │ └── userinfohandler.go
|
||||
│ │ │ │ └── routes.go # 路由配置
|
||||
│ │ │ ├── logic/ # 业务逻辑 (协调RPC调用)
|
||||
│ │ │ │ ├── auth/
|
||||
│ │ │ │ │ └── loginlogic.go
|
||||
│ │ │ │ ├── data/
|
||||
│ │ │ │ │ ├── risklogic.go # 协调data-rpc
|
||||
│ │ │ │ │ ├── creditlogic.go
|
||||
│ │ │ │ │ └── companylogic.go
|
||||
│ │ │ │ └── user/
|
||||
│ │ │ │ └── userinfologic.go
|
||||
│ │ │ ├── middleware/ # 中间件
|
||||
│ │ │ │ ├── authinterceptor.go # JWT认证
|
||||
│ │ │ │ ├── ratelimit.go # 限流
|
||||
│ │ │ │ └── audit.go # 审计
|
||||
│ │ │ ├── svc/servicecontext.go # 服务上下文 (包含所有RPC客户端)
|
||||
│ │ │ └── types/types.go # 请求响应类型
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/
|
||||
│ │ └── deployment.yaml
|
||||
│ │
|
||||
│ └── admin/ # 🏢 管理后台 (独立HTTP服务)
|
||||
│ ├── admin.api
|
||||
│ ├── admin.go
|
||||
│ ├── etc/admin.yaml
|
||||
│ ├── internal/
|
||||
│ │ ├── handler/
|
||||
│ │ │ ├── product/
|
||||
│ │ │ │ ├── createproducthandler.go
|
||||
│ │ │ │ └── listproducthandler.go
|
||||
│ │ │ ├── review/
|
||||
│ │ │ │ └── reviewenterprisehandler.go
|
||||
│ │ │ └── routes.go
|
||||
│ │ ├── logic/
|
||||
│ │ ├── svc/servicecontext.go # 包含RPC客户端
|
||||
│ │ └── types/types.go
|
||||
│ ├── Dockerfile
|
||||
│ └── k8s/deployment.yaml
|
||||
│
|
||||
├── rpc/ # RPC微服务集群
|
||||
│ ├── user/ # 👥 用户域RPC服务
|
||||
│ │ ├── user.proto # gRPC定义
|
||||
│ │ ├── user.go # 启动文件
|
||||
│ │ ├── etc/user.yaml # 配置
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── config/config.go
|
||||
│ │ │ ├── logic/ # 业务逻辑
|
||||
│ │ │ │ ├── auth/
|
||||
│ │ │ │ │ ├── loginlogic.go
|
||||
│ │ │ │ │ └── validatetokenlogic.go
|
||||
│ │ │ │ ├── user/
|
||||
│ │ │ │ │ ├── getuserinfologic.go
|
||||
│ │ │ │ │ └── updateuserlogic.go
|
||||
│ │ │ │ └── enterprise/
|
||||
│ │ │ │ ├── validateenterpriselogic.go
|
||||
│ │ │ │ └── getenterpriselogic.go
|
||||
│ │ │ ├── server/userserver.go # gRPC服务实现
|
||||
│ │ │ ├── svc/servicecontext.go # 服务上下文
|
||||
│ │ │ └── model/ # 数据模型
|
||||
│ │ │ ├── usermodel.go # 用户表模型
|
||||
│ │ │ ├── enterprisemodel.go # 企业表模型
|
||||
│ │ │ └── vars.go # 常量定义
|
||||
│ │ ├── pb/ # 生成的protobuf文件
|
||||
│ │ │ ├── user.pb.go
|
||||
│ │ │ └── user_grpc.pb.go
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/deployment.yaml
|
||||
│ │
|
||||
│ ├── data/ # 📊 数据服务域RPC服务 (核心协调者)
|
||||
│ │ ├── data.proto
|
||||
│ │ ├── data.go
|
||||
│ │ ├── etc/data.yaml
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── logic/
|
||||
│ │ │ │ ├── orchestrator/
|
||||
│ │ │ │ │ └── dataorchestratorlogic.go # 🔥 核心协调逻辑
|
||||
│ │ │ │ ├── risk/ # 原FLXG业务
|
||||
│ │ │ │ │ ├── riskassessmentlogic.go
|
||||
│ │ │ │ │ └── riskreportlogic.go
|
||||
│ │ │ │ ├── credit/ # 原JRZQ业务
|
||||
│ │ │ │ │ ├── creditchecklogic.go
|
||||
│ │ │ │ │ └── credithistorylogic.go
|
||||
│ │ │ │ ├── company/ # 原QYGL业务
|
||||
│ │ │ │ │ ├── companyinfologic.go
|
||||
│ │ │ │ │ └── companysearchlogic.go
|
||||
│ │ │ │ └── query/ # 原IVYZ业务
|
||||
│ │ │ │ ├── dataquerylogic.go
|
||||
│ │ │ │ └── batchquerylogic.go
|
||||
│ │ │ ├── server/dataserver.go
|
||||
│ │ │ ├── svc/servicecontext.go # 包含其他RPC客户端
|
||||
│ │ │ └── external/ # 外部API客户端
|
||||
│ │ │ ├── westdex/westdexclient.go # 西部数据源
|
||||
│ │ │ ├── baidu/baiduclient.go # 百度API
|
||||
│ │ │ └── aliyun/aliyunclient.go # 阿里云API
|
||||
│ │ ├── pb/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/deployment.yaml
|
||||
│ │
|
||||
│ ├── security/ # 🔐 安全域RPC服务
|
||||
│ │ ├── security.proto
|
||||
│ │ ├── security.go
|
||||
│ │ ├── etc/security.yaml
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── logic/
|
||||
│ │ │ │ ├── encryption/
|
||||
│ │ │ │ │ ├── encryptlogic.go
|
||||
│ │ │ │ │ └── decryptlogic.go
|
||||
│ │ │ │ ├── whitelist/
|
||||
│ │ │ │ │ ├── checkwhitelistlogic.go
|
||||
│ │ │ │ │ └── addwhitelistlogic.go
|
||||
│ │ │ │ └── secret/
|
||||
│ │ │ │ ├── generatesecretlogic.go
|
||||
│ │ │ │ └── validatesecretlogic.go
|
||||
│ │ │ ├── server/securityserver.go
|
||||
│ │ │ ├── svc/servicecontext.go
|
||||
│ │ │ └── model/
|
||||
│ │ │ ├── whitelistmodel.go # IP白名单表
|
||||
│ │ │ └── secretkeymodel.go # 密钥表
|
||||
│ │ ├── pb/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/deployment.yaml
|
||||
│ │
|
||||
│ ├── billing/ # 💰 计费域RPC服务
|
||||
│ │ ├── billing.proto
|
||||
│ │ ├── billing.go
|
||||
│ │ ├── etc/billing.yaml
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── logic/
|
||||
│ │ │ │ ├── wallet/
|
||||
│ │ │ │ │ ├── getbalancelogic.go
|
||||
│ │ │ │ │ ├── rechargelogic.go
|
||||
│ │ │ │ │ └── freezelogic.go
|
||||
│ │ │ │ ├── charge/
|
||||
│ │ │ │ │ ├── chargelogic.go # 扣费逻辑
|
||||
│ │ │ │ │ ├── refundlogic.go # 退费逻辑
|
||||
│ │ │ │ │ └── calculatelogic.go # 计费计算
|
||||
│ │ │ │ └── order/
|
||||
│ │ │ │ ├── createorderlogic.go
|
||||
│ │ │ │ └── queryorderlogic.go
|
||||
│ │ │ ├── server/billingserver.go
|
||||
│ │ │ ├── svc/servicecontext.go
|
||||
│ │ │ └── model/
|
||||
│ │ │ ├── walletmodel.go # 钱包表
|
||||
│ │ │ ├── transactionmodel.go # 交易记录表
|
||||
│ │ │ └── ordermodel.go # 订单表
|
||||
│ │ ├── pb/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/deployment.yaml
|
||||
│ │
|
||||
│ ├── product/ # 🛍️ 产品域RPC服务
|
||||
│ │ ├── product.proto
|
||||
│ │ ├── product.go
|
||||
│ │ ├── etc/product.yaml
|
||||
│ │ ├── internal/
|
||||
│ │ │ ├── logic/
|
||||
│ │ │ │ ├── product/
|
||||
│ │ │ │ │ ├── createproductlogic.go
|
||||
│ │ │ │ │ ├── getproductlogic.go
|
||||
│ │ │ │ │ └── updateproductlogic.go
|
||||
│ │ │ │ ├── access/
|
||||
│ │ │ │ │ ├── checkproductaccesslogic.go
|
||||
│ │ │ │ │ └── grantaccesslogic.go
|
||||
│ │ │ │ └── subscription/
|
||||
│ │ │ │ ├── subscribelogic.go
|
||||
│ │ │ │ └── unsubscribelogic.go
|
||||
│ │ │ ├── server/productserver.go
|
||||
│ │ │ ├── svc/servicecontext.go
|
||||
│ │ │ └── model/
|
||||
│ │ │ ├── productmodel.go # 产品表
|
||||
│ │ │ ├── userproductmodel.go # 用户产品关系表
|
||||
│ │ │ └── productaccessmodel.go # 产品权限表
|
||||
│ │ ├── pb/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/deployment.yaml
|
||||
│ │
|
||||
│ └── audit/ # 📝 审计域RPC服务
|
||||
│ ├── audit.proto
|
||||
│ ├── audit.go
|
||||
│ ├── etc/audit.yaml
|
||||
│ ├── internal/
|
||||
│ │ ├── logic/
|
||||
│ │ │ ├── apilog/
|
||||
│ │ │ │ ├── recordapiloglogic.go
|
||||
│ │ │ │ └── queryapiloglogic.go
|
||||
│ │ │ ├── operation/
|
||||
│ │ │ │ ├── recordoperationlogic.go
|
||||
│ │ │ │ └── queryoperationlogic.go
|
||||
│ │ │ └── statistics/
|
||||
│ │ │ ├── apistatslogic.go
|
||||
│ │ │ └── usagestatslogic.go
|
||||
│ │ ├── server/auditserver.go
|
||||
│ │ ├── svc/servicecontext.go
|
||||
│ │ └── model/
|
||||
│ │ ├── apilogmodel.go # API调用日志表
|
||||
│ │ ├── operationlogmodel.go # 操作日志表
|
||||
│ │ └── statisticsmodel.go # 统计数据表
|
||||
│ ├── pb/
|
||||
│ ├── Dockerfile
|
||||
│ └── k8s/deployment.yaml
|
||||
│
|
||||
├── mqs/ # 📮 消息队列服务
|
||||
│ ├── consumer/ # 消息消费者
|
||||
│ │ ├── audit/ # 审计消息消费
|
||||
│ │ │ ├── auditconsumer.go
|
||||
│ │ │ └── etc/auditconsumer.yaml
|
||||
│ │ ├── billing/ # 计费消息消费
|
||||
│ │ │ ├── billingconsumer.go
|
||||
│ │ │ └── etc/billingconsumer.yaml
|
||||
│ │ └── notification/ # 通知消息消费
|
||||
│ │ ├── notificationconsumer.go
|
||||
│ │ └── etc/notificationconsumer.yaml
|
||||
│ └── producer/ # 消息生产者 (在各RPC服务中)
|
||||
│
|
||||
├── shared/ # 🔧 共享库
|
||||
│ ├── pkg/
|
||||
│ │ ├── crypto/ # 加密工具
|
||||
│ │ │ ├── aes.go
|
||||
│ │ │ └── rsa.go
|
||||
│ │ ├── jwt/ # JWT工具
|
||||
│ │ │ └── jwt.go
|
||||
│ │ ├── response/ # 统一响应格式
|
||||
│ │ │ └── response.go
|
||||
│ │ ├── errors/ # 错误定义
|
||||
│ │ │ └── errors.go
|
||||
│ │ ├── utils/ # 工具函数
|
||||
│ │ │ ├── strings.go
|
||||
│ │ │ └── time.go
|
||||
│ │ └── middleware/ # 公共中间件
|
||||
│ │ ├── recovery.go
|
||||
│ │ └── prometheus.go
|
||||
│ ├── model/ # 公共数据模型
|
||||
│ │ ├── common.go # 通用字段
|
||||
│ │ └── pagination.go # 分页模型
|
||||
│ └── proto/ # 公共protobuf定义
|
||||
│ └── common.proto
|
||||
│
|
||||
├── deploy/ # 🚀 部署相关
|
||||
│ ├── docker/
|
||||
│ │ ├── docker-compose.dev.yml # 开发环境
|
||||
│ │ ├── docker-compose.prod.yml # 生产环境
|
||||
│ │ └── nginx/ # Nginx配置
|
||||
│ │ └── nginx.conf
|
||||
│ ├── k8s/ # Kubernetes部署
|
||||
│ │ ├── namespace.yaml
|
||||
│ │ ├── configmap.yaml
|
||||
│ │ ├── mysql.yaml
|
||||
│ │ ├── redis.yaml
|
||||
│ │ ├── kafka.yaml
|
||||
│ │ ├── etcd.yaml
|
||||
│ │ └── ingress.yaml
|
||||
│ ├── terraform/ # 基础设施即代码
|
||||
│ │ ├── main.tf
|
||||
│ │ ├── variables.tf
|
||||
│ │ └── outputs.tf
|
||||
│ └── scripts/ # 部署脚本
|
||||
│ ├── build.sh # 构建脚本
|
||||
│ ├── deploy.sh # 部署脚本
|
||||
│ └── migrate.sh # 数据库迁移脚本
|
||||
│
|
||||
├── tools/ # 🛠️ 开发工具
|
||||
│ ├── goctl/ # go-zero代码生成工具配置
|
||||
│ │ ├── api.tpl # API模板
|
||||
│ │ └── rpc.tpl # RPC模板
|
||||
│ ├── migrate/ # 数据库迁移工具
|
||||
│ │ ├── migrations/
|
||||
│ │ │ ├── 001_create_users.sql
|
||||
│ │ │ ├── 002_create_enterprises.sql
|
||||
│ │ │ ├── 003_create_products.sql
|
||||
│ │ │ ├── 004_create_wallets.sql
|
||||
│ │ │ └── 005_create_audit_logs.sql
|
||||
│ │ └── migrate.go
|
||||
│ └── generate/ # 代码生成脚本
|
||||
│ ├── gen-api.sh # 生成API代码
|
||||
│ ├── gen-rpc.sh # 生成RPC代码
|
||||
│ └── gen-model.sh # 生成Model代码
|
||||
│
|
||||
├── docs/ # 📚 项目文档
|
||||
│ ├── api/ # API文档
|
||||
│ │ ├── gateway.md
|
||||
│ │ └── admin.md
|
||||
│ ├── rpc/ # RPC服务文档
|
||||
│ │ ├── user.md
|
||||
│ │ ├── data.md
|
||||
│ │ └── billing.md
|
||||
│ ├── database/ # 数据库设计文档
|
||||
│ │ ├── schema.md
|
||||
│ │ └── erd.md
|
||||
│ └── deployment/ # 部署文档
|
||||
│ ├── k8s.md
|
||||
│ └── docker.md
|
||||
│
|
||||
├── .github/ # 🔄 CI/CD
|
||||
│ └── workflows/
|
||||
│ ├── api-gateway.yml
|
||||
│ ├── user-rpc.yml
|
||||
│ ├── data-rpc.yml
|
||||
│ └── deploy.yml
|
||||
│
|
||||
├── go.work # Go工作区配置
|
||||
├── Makefile # 统一构建脚本
|
||||
├── README.md # 项目说明
|
||||
└── .gitignore # Git忽略文件
|
||||
```
|
||||
|
||||
## 🎯 服务分类与职责
|
||||
|
||||
### HTTP 服务 (对外接口)
|
||||
|
||||
- **gateway**: 唯一 HTTP 入口,负责路由和协调
|
||||
- **admin**: 管理后台,企业管理、产品管理、审核等
|
||||
|
||||
### RPC 服务 (内部通信)
|
||||
|
||||
- **user**: 用户、企业、认证管理
|
||||
- **data**: 数据服务协调器 + 原有业务逻辑
|
||||
- **security**: 加密解密、白名单、密钥管理
|
||||
- **billing**: 钱包、计费、订单管理
|
||||
- **product**: 产品、权限、订阅管理
|
||||
- **audit**: 日志记录、统计分析
|
||||
|
||||
### MQS 服务 (异步处理)
|
||||
|
||||
- **audit-consumer**: 异步处理审计日志
|
||||
- **billing-consumer**: 异步处理计费通知
|
||||
- **notification-consumer**: 异步处理消息通知
|
||||
|
||||
## 💾 数据库设计
|
||||
|
||||
### 按域分库
|
||||
|
||||
```yaml
|
||||
databases:
|
||||
user_db: # 用户域数据库
|
||||
tables:
|
||||
- users
|
||||
- enterprises
|
||||
- user_tokens
|
||||
|
||||
security_db: # 安全域数据库
|
||||
tables:
|
||||
- ip_whitelist
|
||||
- secret_keys
|
||||
- encrypt_records
|
||||
|
||||
billing_db: # 计费域数据库
|
||||
tables:
|
||||
- wallets
|
||||
- transactions
|
||||
- orders
|
||||
- billing_records
|
||||
|
||||
product_db: # 产品域数据库
|
||||
tables:
|
||||
- products
|
||||
- user_products
|
||||
- product_access
|
||||
|
||||
audit_db: # 审计域数据库
|
||||
tables:
|
||||
- api_logs
|
||||
- operation_logs
|
||||
- statistics
|
||||
```
|
||||
|
||||
接下来我会详细说明每个部分的具体实现...
|
||||
229
校验体系设计优化.md
Normal file
229
校验体系设计优化.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 校验体系设计优化
|
||||
|
||||
## 1. 校验层次重新设计
|
||||
|
||||
### Handler 层(client-api)
|
||||
|
||||
```go
|
||||
// types.go - 只做格式校验
|
||||
type GetProductListReq struct {
|
||||
CategoryId int64 `form:"category_id" validate:"min=1"` // 格式校验:必须大于0
|
||||
PageNum int64 `form:"page_num" validate:"min=1,max=1000"` // 格式校验:分页范围
|
||||
PageSize int64 `form:"page_size" validate:"min=1,max=100"` // 格式校验:每页数量
|
||||
Keyword string `form:"keyword" validate:"max=50"` // 格式校验:关键词长度
|
||||
}
|
||||
|
||||
// handler.go - 只做参数绑定和格式校验
|
||||
func (l *GetProductListHandler) GetProductList(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.GetProductListReq
|
||||
|
||||
// 1. 参数绑定
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
response.ParamErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 格式校验(只校验格式,不校验业务)
|
||||
if err := validator.Validate(&req); err != nil {
|
||||
response.ParamErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 调用Logic层
|
||||
resp, err := l.svcCtx.GetProductListLogic(l.ctx, &req)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### RPC 层(product-rpc)
|
||||
|
||||
```go
|
||||
// product.proto - RPC接口定义
|
||||
message GetProductListReq {
|
||||
int64 category_id = 1;
|
||||
int64 page_num = 2;
|
||||
int64 page_size = 3;
|
||||
string keyword = 4;
|
||||
int64 user_id = 5; // 业务相关:用户ID
|
||||
string user_role = 6; // 业务相关:用户角色
|
||||
}
|
||||
|
||||
// logic.go - 业务校验
|
||||
func (l *GetProductListLogic) GetProductList(req *product.GetProductListReq) (*product.GetProductListResp, error) {
|
||||
// 1. 业务校验(在相关业务域内)
|
||||
if err := l.validateBusinessRules(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 业务逻辑处理
|
||||
// ...
|
||||
}
|
||||
|
||||
func (l *GetProductListLogic) validateBusinessRules(req *product.GetProductListReq) error {
|
||||
// 业务规则校验:用户是否有权限查看该分类
|
||||
if !l.svcCtx.UserModel.HasCategoryPermission(req.UserId, req.CategoryId) {
|
||||
return errors.New("用户无权限查看该分类")
|
||||
}
|
||||
|
||||
// 业务规则校验:分类是否存在且启用
|
||||
category, err := l.svcCtx.CategoryModel.FindOne(req.CategoryId)
|
||||
if err != nil || category.Status != 1 {
|
||||
return errors.New("分类不存在或已禁用")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 参数传递和转换
|
||||
|
||||
### Logic 层的作用
|
||||
|
||||
```go
|
||||
// client/internal/logic/product/getproductlistlogic.go
|
||||
func (l *GetProductListLogic) GetProductList(req *types.GetProductListReq) (*types.GetProductListResp, error) {
|
||||
// 1. 获取用户信息(从JWT中解析)
|
||||
userInfo := l.ctx.Value("userInfo").(auth.UserInfo)
|
||||
|
||||
// 2. 转换为RPC请求(添加业务上下文)
|
||||
rpcReq := &product.GetProductListReq{
|
||||
CategoryId: req.CategoryId,
|
||||
PageNum: req.PageNum,
|
||||
PageSize: req.PageSize,
|
||||
Keyword: req.Keyword,
|
||||
UserId: userInfo.UserId, // 添加业务上下文
|
||||
UserRole: userInfo.Role, // 添加业务上下文
|
||||
}
|
||||
|
||||
// 3. 调用RPC服务
|
||||
rpcResp, err := l.svcCtx.ProductRpc.GetProductList(l.ctx, rpcReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 转换响应格式
|
||||
return &types.GetProductListResp{
|
||||
List: convertToApiFormat(rpcResp.List),
|
||||
Total: rpcResp.Total,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 校验规则组织优化
|
||||
|
||||
### 目录结构调整
|
||||
|
||||
```
|
||||
shared/
|
||||
├── validator/ # 通用格式校验器
|
||||
│ ├── validator.go # 校验器初始化
|
||||
│ ├── rules.go # 通用校验规则
|
||||
│ └── messages.go # 错误消息
|
||||
├── errcode/ # 错误码定义
|
||||
│ ├── api_errors.go # API层错误
|
||||
│ └── business_errors.go # 业务层错误
|
||||
└── utils/ # 工具函数
|
||||
└── converter.go # 数据转换
|
||||
|
||||
domains/
|
||||
├── product/
|
||||
│ ├── rpc/
|
||||
│ │ └── internal/
|
||||
│ │ └── logic/
|
||||
│ │ └── validator/ # 产品域业务校验
|
||||
│ │ ├── product_validator.go
|
||||
│ │ └── category_validator.go
|
||||
├── user/
|
||||
│ └── rpc/
|
||||
│ └── internal/
|
||||
│ └── logic/
|
||||
│ └── validator/ # 用户域业务校验
|
||||
│ ├── user_validator.go
|
||||
│ └── auth_validator.go
|
||||
```
|
||||
|
||||
### 业务校验器实现
|
||||
|
||||
```go
|
||||
// domains/product/rpc/internal/logic/validator/product_validator.go
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tianyuan/domains/product/rpc/internal/svc"
|
||||
"tianyuan/domains/product/rpc/product"
|
||||
)
|
||||
|
||||
type ProductValidator struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewProductValidator(svcCtx *svc.ServiceContext) *ProductValidator {
|
||||
return &ProductValidator{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// 校验用户是否有权限查看产品列表
|
||||
func (v *ProductValidator) ValidateGetProductListPermission(ctx context.Context, req *product.GetProductListReq) error {
|
||||
// 检查用户权限
|
||||
hasPermission, err := v.svcCtx.UserRpc.CheckPermission(ctx, &user.CheckPermissionReq{
|
||||
UserId: req.UserId,
|
||||
Permission: "product:list",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !hasPermission.HasPermission {
|
||||
return errors.New("用户无权限查看产品列表")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 校验分类相关业务规则
|
||||
func (v *ProductValidator) ValidateCategoryAccess(ctx context.Context, req *product.GetProductListReq) error {
|
||||
// 检查分类是否存在且用户有访问权限
|
||||
// ...
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 关于 httpx.Parse 的说明
|
||||
|
||||
`httpx.Parse` 是 go-zero 提供的参数绑定函数,它会:
|
||||
|
||||
1. **自动绑定参数**:根据 struct tag 从 HTTP 请求中解析参数
|
||||
2. **支持多种来源**:query 参数、form 数据、JSON body 等
|
||||
3. **类型转换**:自动进行基础类型转换
|
||||
|
||||
```go
|
||||
// 示例:httpx.Parse 的使用
|
||||
type GetProductListReq struct {
|
||||
CategoryId int64 `form:"category_id"` // 从form或query中获取
|
||||
PageNum int64 `form:"page_num"`
|
||||
PageSize int64 `form:"page_size"`
|
||||
Keyword string `form:"keyword"`
|
||||
}
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
var req GetProductListReq
|
||||
|
||||
// 这一行会自动从 r *http.Request 中解析参数到 req 结构体
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
// 参数解析失败
|
||||
return
|
||||
}
|
||||
|
||||
// 此时 req 已经包含了解析后的参数值
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 最佳实践总结
|
||||
|
||||
1. **Handler 层**:只做格式校验,使用 validator tag
|
||||
2. **Logic 层**:参数转换和聚合,添加业务上下文
|
||||
3. **RPC 层**:业务规则校验,放在对应的业务域内
|
||||
4. **校验器分离**:格式校验器放 shared,业务校验器放各自域内
|
||||
5. **参数绑定**:使用 go-zero 的 httpx.Parse 自动绑定
|
||||
414
网关域架构与跨域调用实现.md
Normal file
414
网关域架构与跨域调用实现.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# 网关域架构与跨域调用实现详解
|
||||
|
||||
## 🌐 网关域的职责与架构
|
||||
|
||||
### 网关域的核心职责
|
||||
|
||||
```
|
||||
网关域 (Gateway Domain) = API网关 + 路由管理 + 协议转换
|
||||
```
|
||||
|
||||
**主要功能:**
|
||||
|
||||
1. **统一入口** - 所有外部请求的唯一入口点
|
||||
2. **路由分发** - 根据路径将请求分发到对应的域服务
|
||||
3. **协议转换** - HTTP ↔ gRPC 转换
|
||||
4. **横切关注点** - 认证、限流、监控、日志
|
||||
5. **服务发现** - 动态发现后端服务
|
||||
|
||||
### 网关域架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Gateway Domain │
|
||||
│ ┌─────────────────────────────────────────────────────────┐│
|
||||
│ │ Gateway Service ││
|
||||
│ │ ││
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
|
||||
│ │ │ HTTP │ │ Router │ │ gRPC │ ││
|
||||
│ │ │ Handlers │ │ Engine │ │ Clients │ ││
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
|
||||
│ │ ││
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
|
||||
│ │ │ Auth │ │ Rate Limit │ │ Monitor │ ││
|
||||
│ │ │ Middleware │ │ Middleware │ │ Middleware │ ││
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
|
||||
│ └─────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ User Domain │ │ Data Domain │ │ Billing Domain │
|
||||
│ (User Service) │ │ (Data Service) │ │(Billing Service)│
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 🏗️ 网关域的目录结构
|
||||
|
||||
```
|
||||
domains/gateway-domain/
|
||||
└── gateway-service/
|
||||
├── cmd/
|
||||
│ └── server/
|
||||
│ └── main.go # 网关服务启动入口
|
||||
│
|
||||
├── api/
|
||||
│ └── http/
|
||||
│ └── gateway.api # 网关API定义
|
||||
│
|
||||
├── internal/
|
||||
│ ├── application/ # 应用层
|
||||
│ │ └── service/
|
||||
│ │ └── gateway_orchestrator.go # 🔥 核心协调器
|
||||
│ │
|
||||
│ ├── domain/ # 领域层
|
||||
│ │ ├── entity/
|
||||
│ │ │ ├── route.go # 路由实体
|
||||
│ │ │ └── service_endpoint.go # 服务端点
|
||||
│ │ └── service/
|
||||
│ │ ├── router_service.go # 路由服务
|
||||
│ │ └── discovery_service.go # 服务发现
|
||||
│ │
|
||||
│ ├── infrastructure/ # 基础设施层
|
||||
│ │ ├── client/ # gRPC客户端
|
||||
│ │ │ ├── user_client.go
|
||||
│ │ │ ├── data_client.go
|
||||
│ │ │ ├── security_client.go
|
||||
│ │ │ ├── billing_client.go
|
||||
│ │ │ └── product_client.go
|
||||
│ │ ├── discovery/ # 服务发现
|
||||
│ │ │ └── etcd_discovery.go
|
||||
│ │ └── config/
|
||||
│ │ └── service_config.go # 服务配置
|
||||
│ │
|
||||
│ └── interfaces/ # 接口适配器层
|
||||
│ ├── http/ # HTTP处理器
|
||||
│ │ ├── data_handler.go # 数据服务API处理
|
||||
│ │ ├── user_handler.go # 用户服务API处理
|
||||
│ │ └── billing_handler.go # 计费服务API处理
|
||||
│ ├── middleware/ # 中间件
|
||||
│ │ ├── auth_middleware.go # 认证中间件
|
||||
│ │ ├── rate_limit_middleware.go # 限流中间件
|
||||
│ │ ├── audit_middleware.go # 审计中间件
|
||||
│ │ └── cors_middleware.go # CORS中间件
|
||||
│ └── router/
|
||||
│ └── gateway_router.go # 路由配置
|
||||
│
|
||||
├── configs/
|
||||
│ ├── gateway.yaml # 网关配置
|
||||
│ └── routes.yaml # 路由配置
|
||||
│
|
||||
└── deployments/
|
||||
├── Dockerfile
|
||||
└── k8s/
|
||||
```
|
||||
|
||||
## 🎯 核心实现:跨域调用 Logic 在哪里
|
||||
|
||||
### 1. 网关层的协调逻辑 (Gateway Orchestrator)
|
||||
|
||||
```go
|
||||
// domains/gateway-domain/gateway-service/internal/application/service/gateway_orchestrator.go
|
||||
|
||||
type GatewayOrchestrator struct {
|
||||
// gRPC客户端 - 调用各个域服务
|
||||
userClient user.UserServiceClient
|
||||
dataClient data.DataServiceClient
|
||||
securityClient security.SecurityServiceClient
|
||||
billingClient billing.BillingServiceClient
|
||||
productClient product.ProductServiceClient
|
||||
auditClient audit.AuditServiceClient
|
||||
|
||||
// 网关自身的服务
|
||||
routerService *RouterService
|
||||
discoveryService *DiscoveryService
|
||||
}
|
||||
|
||||
// 🔥 这里实现跨域调用的核心逻辑
|
||||
func (g *GatewayOrchestrator) ProcessAPIRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) {
|
||||
// 1. 解析路由 - 确定目标域
|
||||
route, err := g.routerService.ResolveRoute(req.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 根据不同的业务场景,实现不同的协调逻辑
|
||||
switch route.Domain {
|
||||
case "data":
|
||||
return g.handleDataDomainRequest(ctx, req)
|
||||
case "user":
|
||||
return g.handleUserDomainRequest(ctx, req)
|
||||
case "billing":
|
||||
return g.handleBillingDomainRequest(ctx, req)
|
||||
default:
|
||||
return nil, errors.New("unknown domain")
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 数据域请求的协调逻辑 (你之前问的API调用流程)
|
||||
func (g *GatewayOrchestrator) handleDataDomainRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) {
|
||||
// 注意:这里只做最基础的协调,复杂的业务逻辑在Data Domain内部
|
||||
|
||||
// 1. 前置验证 (网关层职责)
|
||||
if err := g.validateBasicRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 认证检查 (网关层职责)
|
||||
userInfo, err := g.authenticateUser(ctx, req.Headers.Authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 调用数据域服务 (核心业务逻辑在数据域内部)
|
||||
dataReq := &data.ProcessAPIRequestRequest{
|
||||
UserId: userInfo.UserId,
|
||||
EnterpriseId: userInfo.EnterpriseId,
|
||||
ApiPath: req.Path,
|
||||
Parameters: req.Parameters,
|
||||
ClientIp: req.ClientIP,
|
||||
}
|
||||
|
||||
// 🚨 重点:复杂的跨域协调逻辑在数据域的DataOrchestrator中实现
|
||||
dataResp, err := g.dataClient.ProcessAPIRequest(ctx, dataReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 响应转换 (网关层职责)
|
||||
return g.convertToHTTPResponse(dataResp), nil
|
||||
}
|
||||
|
||||
// 简单的认证逻辑 (复杂认证在User Domain)
|
||||
func (g *GatewayOrchestrator) authenticateUser(ctx context.Context, authHeader string) (*UserInfo, error) {
|
||||
// 调用用户域进行认证
|
||||
authReq := &user.AuthenticateRequest{
|
||||
Token: authHeader,
|
||||
}
|
||||
|
||||
authResp, err := g.userClient.Authenticate(ctx, authReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UserInfo{
|
||||
UserId: authResp.UserId,
|
||||
EnterpriseId: authResp.EnterpriseId,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据域的协调逻辑 (Data Orchestrator)
|
||||
|
||||
```go
|
||||
// domains/data-domain/data-service/internal/application/service/data_orchestrator.go
|
||||
|
||||
type DataOrchestrator struct {
|
||||
// 数据域内部的业务服务
|
||||
riskService *RiskAssessmentService // 原FLXG
|
||||
creditService *CreditCheckService // 原JRZQ
|
||||
companyService *CompanyInfoService // 原QYGL
|
||||
queryService *DataQueryService // 原IVYZ
|
||||
|
||||
// 其他域的gRPC客户端
|
||||
securityClient SecurityServiceClient // 安全域
|
||||
userClient UserServiceClient // 用户域
|
||||
productClient ProductServiceClient // 产品域
|
||||
billingClient BillingServiceClient // 计费域
|
||||
auditClient AuditServiceClient // 审计域
|
||||
}
|
||||
|
||||
// 🔥 这里实现你问的那个复杂API调用流程的核心协调逻辑
|
||||
func (d *DataOrchestrator) ProcessAPIRequest(ctx context.Context, req *ProcessAPIRequestRequest) (*ProcessAPIResponseResponse, error) {
|
||||
|
||||
// === 第一阶段:安全验证 ===
|
||||
|
||||
// 1. IP白名单验证 (调用安全域)
|
||||
whitelistReq := &security.CheckWhitelistRequest{
|
||||
EnterpriseId: req.EnterpriseId,
|
||||
ClientIp: req.ClientIp,
|
||||
}
|
||||
if _, err := d.securityClient.CheckWhitelist(ctx, whitelistReq); err != nil {
|
||||
return nil, fmt.Errorf("IP白名单验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 企业ID验证 (调用用户域)
|
||||
enterpriseReq := &user.ValidateEnterpriseRequest{
|
||||
UserId: req.UserId,
|
||||
EnterpriseId: req.EnterpriseId,
|
||||
}
|
||||
enterpriseResp, err := d.userClient.ValidateEnterprise(ctx, enterpriseReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("企业验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 密钥解密 (调用安全域)
|
||||
decryptReq := &security.DecryptSecretRequest{
|
||||
EnterpriseId: req.EnterpriseId,
|
||||
EncryptedKey: enterpriseResp.EncryptedSecretKey,
|
||||
}
|
||||
decryptResp, err := d.securityClient.DecryptSecret(ctx, decryptReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密钥解密失败: %w", err)
|
||||
}
|
||||
|
||||
// === 第二阶段:权限与产品验证 ===
|
||||
|
||||
// 4. 产品权限检查 (调用产品域)
|
||||
productReq := &product.CheckProductAccessRequest{
|
||||
UserId: req.UserId,
|
||||
ApiPath: req.ApiPath,
|
||||
SecretKey: decryptResp.SecretKey,
|
||||
}
|
||||
productResp, err := d.productClient.CheckProductAccess(ctx, productReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品权限检查失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 余额检查 (调用计费域)
|
||||
balanceReq := &billing.CheckBalanceRequest{
|
||||
UserId: req.UserId,
|
||||
ProductCode: productResp.ProductCode,
|
||||
ApiPath: req.ApiPath,
|
||||
}
|
||||
balanceResp, err := d.billingClient.CheckBalance(ctx, balanceReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("余额检查失败: %w", err)
|
||||
}
|
||||
|
||||
// === 第三阶段:业务处理 ===
|
||||
|
||||
// 6. 根据API路径选择对应的业务服务 (数据域内部逻辑)
|
||||
var businessResult *BusinessResult
|
||||
switch {
|
||||
case strings.Contains(req.ApiPath, "risk-assessment"):
|
||||
businessResult, err = d.riskService.ProcessRequest(ctx, req.Parameters)
|
||||
case strings.Contains(req.ApiPath, "credit-check"):
|
||||
businessResult, err = d.creditService.ProcessRequest(ctx, req.Parameters)
|
||||
case strings.Contains(req.ApiPath, "company-info"):
|
||||
businessResult, err = d.companyService.ProcessRequest(ctx, req.Parameters)
|
||||
case strings.Contains(req.ApiPath, "data-query"):
|
||||
businessResult, err = d.queryService.ProcessRequest(ctx, req.Parameters)
|
||||
default:
|
||||
return nil, errors.New("不支持的API路径")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("业务处理失败: %w", err)
|
||||
}
|
||||
|
||||
// === 第四阶段:计费与审计 ===
|
||||
|
||||
// 7. 计费扣款 (调用计费域)
|
||||
chargeReq := &billing.ChargeRequest{
|
||||
UserId: req.UserId,
|
||||
ProductCode: productResp.ProductCode,
|
||||
ApiPath: req.ApiPath,
|
||||
Amount: balanceResp.RequiredAmount,
|
||||
RequestId: generateRequestId(),
|
||||
}
|
||||
chargeResp, err := d.billingClient.Charge(ctx, chargeReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("计费失败: %w", err)
|
||||
}
|
||||
|
||||
// 8. 记录审计日志 (调用审计域)
|
||||
auditReq := &audit.RecordAPICallRequest{
|
||||
UserId: req.UserId,
|
||||
EnterpriseId: req.EnterpriseId,
|
||||
ApiPath: req.ApiPath,
|
||||
ClientIp: req.ClientIp,
|
||||
RequestId: chargeResp.TransactionId,
|
||||
Result: "success",
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
}
|
||||
go d.auditClient.RecordAPICall(context.Background(), auditReq) // 异步记录
|
||||
|
||||
// 9. 返回最终结果
|
||||
return &ProcessAPIResponseResponse{
|
||||
Success: true,
|
||||
Data: businessResult.Data,
|
||||
TransactionId: chargeResp.TransactionId,
|
||||
RemainingBalance: chargeResp.RemainingBalance,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. HTTP 处理器 (接口适配器层)
|
||||
|
||||
```go
|
||||
// domains/gateway-domain/gateway-service/internal/interfaces/http/data_handler.go
|
||||
|
||||
type DataHandler struct {
|
||||
orchestrator *GatewayOrchestrator
|
||||
}
|
||||
|
||||
// HTTP入口点
|
||||
func (h *DataHandler) HandleDataAPI(c *gin.Context) {
|
||||
// 1. HTTP请求转换
|
||||
req := &APIRequest{
|
||||
Path: c.Request.URL.Path,
|
||||
Method: c.Request.Method,
|
||||
Parameters: extractParameters(c),
|
||||
Headers: extractHeaders(c),
|
||||
ClientIP: c.ClientIP(),
|
||||
}
|
||||
|
||||
// 2. 调用网关协调器
|
||||
resp, err := h.orchestrator.ProcessAPIRequest(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. HTTP响应
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
```
|
||||
|
||||
## 🌊 完整的调用流程
|
||||
|
||||
```
|
||||
1. 客户端 HTTP请求
|
||||
↓
|
||||
2. 网关域 (Gateway Domain)
|
||||
├── HTTP Handler (接口适配器层)
|
||||
├── Gateway Orchestrator (应用层)
|
||||
│ ├── 基础验证
|
||||
│ ├── 用户认证 → User Domain
|
||||
│ └── 路由到目标域
|
||||
└── 调用 Data Domain
|
||||
↓
|
||||
3. 数据域 (Data Domain)
|
||||
└── Data Orchestrator (应用层) 🔥 核心协调逻辑
|
||||
├── 安全验证 → Security Domain
|
||||
├── 企业验证 → User Domain
|
||||
├── 权限检查 → Product Domain
|
||||
├── 余额检查 → Billing Domain
|
||||
├── 业务处理 (内部服务)
|
||||
├── 计费扣款 → Billing Domain
|
||||
└── 审计记录 → Audit Domain
|
||||
```
|
||||
|
||||
## 🎯 总结:Logic 实现的分层
|
||||
|
||||
### 1. **网关域的职责** (简单协调)
|
||||
|
||||
- HTTP 协议处理
|
||||
- 路由分发
|
||||
- 基础认证
|
||||
- 中间件处理 (限流、CORS 等)
|
||||
|
||||
### 2. **数据域的职责** (复杂协调) 🔥
|
||||
|
||||
- **这里是你问的那个复杂 API 调用流程的主要实现位置**
|
||||
- 跨域服务协调
|
||||
- 业务流程编排
|
||||
- 事务一致性保证
|
||||
|
||||
### 3. **其他域的职责** (专业服务)
|
||||
|
||||
- 各自领域的专业逻辑
|
||||
- 对外提供 gRPC 接口
|
||||
|
||||
**关键点:网关域负责"谁来处理",数据域负责"怎么处理"!**
|
||||
766
网关管理详解.md
Normal file
766
网关管理详解.md
Normal file
@@ -0,0 +1,766 @@
|
||||
# 微服务网关管理详解
|
||||
|
||||
## 🚪 什么是 API 网关?
|
||||
|
||||
API 网关就像一个**智能门卫**,站在所有微服务的前面:
|
||||
|
||||
```
|
||||
客户端请求 → API网关 → 路由到具体的微服务
|
||||
↑ ↑ ↑
|
||||
用户App 统一入口 用户服务
|
||||
管理后台 ↓ 支付服务
|
||||
第三方系统 认证授权 产品服务
|
||||
限流控制 数据服务
|
||||
日志记录
|
||||
```
|
||||
|
||||
## 🎯 网关的核心功能
|
||||
|
||||
### 1. 请求路由 (Request Routing)
|
||||
|
||||
```go
|
||||
// 路由配置示例
|
||||
type Route struct {
|
||||
Path string `yaml:"path"`
|
||||
Method string `yaml:"method"`
|
||||
Service string `yaml:"service"`
|
||||
Middlewares []string `yaml:"middlewares"`
|
||||
Headers map[string]string `yaml:"headers"`
|
||||
}
|
||||
|
||||
var routes = []Route{
|
||||
{
|
||||
Path: "/api/v1/users/*",
|
||||
Method: "*",
|
||||
Service: "user-service",
|
||||
Middlewares: []string{"auth", "rate-limit"},
|
||||
},
|
||||
{
|
||||
Path: "/api/v1/payments/*",
|
||||
Method: "*",
|
||||
Service: "payment-service",
|
||||
Middlewares: []string{"auth", "encrypt"},
|
||||
},
|
||||
{
|
||||
Path: "/api/v1/products/*",
|
||||
Method: "*",
|
||||
Service: "product-service",
|
||||
Middlewares: []string{"auth", "cache"},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 负载均衡 (Load Balancing)
|
||||
|
||||
```go
|
||||
// 负载均衡器
|
||||
type LoadBalancer interface {
|
||||
Next() *ServiceInstance
|
||||
}
|
||||
|
||||
// 轮询负载均衡
|
||||
type RoundRobinBalancer struct {
|
||||
instances []*ServiceInstance
|
||||
current int
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (rb *RoundRobinBalancer) Next() *ServiceInstance {
|
||||
rb.mutex.Lock()
|
||||
defer rb.mutex.Unlock()
|
||||
|
||||
if len(rb.instances) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
instance := rb.instances[rb.current]
|
||||
rb.current = (rb.current + 1) % len(rb.instances)
|
||||
return instance
|
||||
}
|
||||
|
||||
// 加权轮询
|
||||
type WeightedRoundRobinBalancer struct {
|
||||
instances []*WeightedInstance
|
||||
total int
|
||||
current int
|
||||
}
|
||||
|
||||
type WeightedInstance struct {
|
||||
Instance *ServiceInstance
|
||||
Weight int
|
||||
Current int
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 服务发现 (Service Discovery)
|
||||
|
||||
```go
|
||||
// 服务发现接口
|
||||
type ServiceDiscovery interface {
|
||||
Register(service *ServiceInstance) error
|
||||
Deregister(serviceID string) error
|
||||
Discover(serviceName string) ([]*ServiceInstance, error)
|
||||
Watch(serviceName string) <-chan []*ServiceInstance
|
||||
}
|
||||
|
||||
// Consul 服务发现实现
|
||||
type ConsulDiscovery struct {
|
||||
client *consul.Client
|
||||
}
|
||||
|
||||
func (cd *ConsulDiscovery) Discover(serviceName string) ([]*ServiceInstance, error) {
|
||||
services, _, err := cd.client.Health().Service(serviceName, "", true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instances []*ServiceInstance
|
||||
for _, service := range services {
|
||||
instances = append(instances, &ServiceInstance{
|
||||
ID: service.Service.ID,
|
||||
Name: service.Service.Service,
|
||||
Address: service.Service.Address,
|
||||
Port: service.Service.Port,
|
||||
Meta: service.Service.Meta,
|
||||
})
|
||||
}
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ 网关中间件系统
|
||||
|
||||
### 1. 认证中间件
|
||||
|
||||
```go
|
||||
// 认证中间件
|
||||
func AuthMiddleware(jwtSecret string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 1. 获取 Token
|
||||
token := extractToken(c)
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少认证令牌"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 验证 Token
|
||||
claims, err := validateJWT(token, jwtSecret)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证令牌"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 设置用户上下文
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("roles", claims.Roles)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func extractToken(c *gin.Context) string {
|
||||
// 从 Header 获取
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return strings.TrimPrefix(authHeader, "Bearer ")
|
||||
}
|
||||
|
||||
// 从 Query 参数获取
|
||||
return c.Query("token")
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 限流中间件
|
||||
|
||||
```go
|
||||
// 限流中间件
|
||||
type RateLimiter struct {
|
||||
redis *redis.Client
|
||||
window time.Duration
|
||||
limit int
|
||||
}
|
||||
|
||||
func NewRateLimiter(redis *redis.Client, window time.Duration, limit int) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
redis: redis,
|
||||
window: window,
|
||||
limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 1. 获取客户端标识
|
||||
clientID := getClientID(c)
|
||||
|
||||
// 2. 检查限流
|
||||
allowed, err := rl.isAllowed(clientID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "限流检查失败"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "请求过于频繁,请稍后再试",
|
||||
"retry_after": int(rl.window.Seconds()),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) isAllowed(clientID string) (bool, error) {
|
||||
key := fmt.Sprintf("rate_limit:%s", clientID)
|
||||
|
||||
// 使用滑动窗口算法
|
||||
now := time.Now().Unix()
|
||||
window := now - int64(rl.window.Seconds())
|
||||
|
||||
pipe := rl.redis.Pipeline()
|
||||
|
||||
// 删除过期的记录
|
||||
pipe.ZRemRangeByScore(context.Background(), key, "0", fmt.Sprintf("%d", window))
|
||||
|
||||
// 获取当前窗口内的请求数
|
||||
countCmd := pipe.ZCard(context.Background(), key)
|
||||
|
||||
// 添加当前请求
|
||||
pipe.ZAdd(context.Background(), key, &redis.Z{
|
||||
Score: float64(now),
|
||||
Member: fmt.Sprintf("%d", now),
|
||||
})
|
||||
|
||||
// 设置过期时间
|
||||
pipe.Expire(context.Background(), key, rl.window)
|
||||
|
||||
_, err := pipe.Exec(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
count := countCmd.Val()
|
||||
return count < int64(rl.limit), nil
|
||||
}
|
||||
|
||||
func getClientID(c *gin.Context) string {
|
||||
// 优先使用用户ID
|
||||
if userID := c.GetString("user_id"); userID != "" {
|
||||
return "user:" + userID
|
||||
}
|
||||
|
||||
// 其次使用API Key
|
||||
if apiKey := c.GetHeader("X-API-Key"); apiKey != "" {
|
||||
return "api:" + apiKey
|
||||
}
|
||||
|
||||
// 最后使用IP地址
|
||||
return "ip:" + c.ClientIP()
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 缓存中间件
|
||||
|
||||
```go
|
||||
// 缓存中间件
|
||||
type CacheMiddleware struct {
|
||||
redis *redis.Client
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func NewCacheMiddleware(redis *redis.Client, ttl time.Duration) *CacheMiddleware {
|
||||
return &CacheMiddleware{
|
||||
redis: redis,
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *CacheMiddleware) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 只缓存 GET 请求
|
||||
if c.Request.Method != http.MethodGet {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 生成缓存键
|
||||
cacheKey := generateCacheKey(c)
|
||||
|
||||
// 尝试从缓存获取
|
||||
cached, err := cm.redis.Get(context.Background(), cacheKey).Result()
|
||||
if err == nil {
|
||||
var response CachedResponse
|
||||
if json.Unmarshal([]byte(cached), &response) == nil {
|
||||
// 设置响应头
|
||||
for key, value := range response.Headers {
|
||||
c.Header(key, value)
|
||||
}
|
||||
|
||||
c.Data(response.Status, response.ContentType, response.Body)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 包装 ResponseWriter 来捕获响应
|
||||
crw := &cachedResponseWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
body: &bytes.Buffer{},
|
||||
headers: make(map[string]string),
|
||||
}
|
||||
c.Writer = crw
|
||||
|
||||
c.Next()
|
||||
|
||||
// 缓存响应
|
||||
if crw.status >= 200 && crw.status < 300 {
|
||||
response := CachedResponse{
|
||||
Status: crw.status,
|
||||
Headers: crw.headers,
|
||||
ContentType: crw.Header().Get("Content-Type"),
|
||||
Body: crw.body.Bytes(),
|
||||
}
|
||||
|
||||
if data, err := json.Marshal(response); err == nil {
|
||||
cm.redis.Set(context.Background(), cacheKey, data, cm.ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CachedResponse struct {
|
||||
Status int `json:"status"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
ContentType string `json:"content_type"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
type cachedResponseWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
headers map[string]string
|
||||
status int
|
||||
}
|
||||
|
||||
func (crw *cachedResponseWriter) Write(data []byte) (int, error) {
|
||||
crw.body.Write(data)
|
||||
return crw.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func (crw *cachedResponseWriter) WriteHeader(status int) {
|
||||
crw.status = status
|
||||
crw.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 网关配置管理
|
||||
|
||||
### 1. 动态配置
|
||||
|
||||
```yaml
|
||||
# gateway.yaml
|
||||
gateway:
|
||||
port: 8080
|
||||
timeout: 30s
|
||||
|
||||
services:
|
||||
user-service:
|
||||
discovery: consul
|
||||
load_balancer: round_robin
|
||||
health_check:
|
||||
enabled: true
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
path: /health
|
||||
circuit_breaker:
|
||||
enabled: true
|
||||
failure_threshold: 5
|
||||
timeout: 60s
|
||||
|
||||
payment-service:
|
||||
discovery: consul
|
||||
load_balancer: weighted_round_robin
|
||||
weights:
|
||||
- instance: payment-service-1
|
||||
weight: 3
|
||||
- instance: payment-service-2
|
||||
weight: 1
|
||||
|
||||
middlewares:
|
||||
auth:
|
||||
jwt_secret: ${JWT_SECRET}
|
||||
excluded_paths:
|
||||
- /api/v1/auth/login
|
||||
- /api/v1/auth/register
|
||||
- /health
|
||||
|
||||
rate_limit:
|
||||
default:
|
||||
window: 60s
|
||||
limit: 100
|
||||
user_limits:
|
||||
premium_user:
|
||||
window: 60s
|
||||
limit: 1000
|
||||
basic_user:
|
||||
window: 60s
|
||||
limit: 100
|
||||
|
||||
cache:
|
||||
ttl: 300s
|
||||
excluded_paths:
|
||||
- /api/v1/users/me
|
||||
- /api/v1/payments/*
|
||||
|
||||
routes:
|
||||
- path: /api/v1/users/*
|
||||
service: user-service
|
||||
middlewares: [auth, rate_limit]
|
||||
|
||||
- path: /api/v1/payments/*
|
||||
service: payment-service
|
||||
middlewares: [auth, rate_limit]
|
||||
|
||||
- path: /api/v1/products/*
|
||||
service: product-service
|
||||
middlewares: [auth, rate_limit, cache]
|
||||
```
|
||||
|
||||
### 2. 配置热更新
|
||||
|
||||
```go
|
||||
// 配置管理器
|
||||
type ConfigManager struct {
|
||||
config *GatewayConfig
|
||||
mutex sync.RWMutex
|
||||
watchers []chan *GatewayConfig
|
||||
}
|
||||
|
||||
func NewConfigManager() *ConfigManager {
|
||||
return &ConfigManager{
|
||||
watchers: make([]chan *GatewayConfig, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConfigManager) LoadConfig(path string) error {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var config GatewayConfig
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm.config = &config
|
||||
|
||||
// 通知所有监听者
|
||||
for _, watcher := range cm.watchers {
|
||||
select {
|
||||
case watcher <- &config:
|
||||
default:
|
||||
// 非阻塞发送
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ConfigManager) Watch() <-chan *GatewayConfig {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
watcher := make(chan *GatewayConfig, 1)
|
||||
cm.watchers = append(cm.watchers, watcher)
|
||||
|
||||
// 立即发送当前配置
|
||||
if cm.config != nil {
|
||||
watcher <- cm.config
|
||||
}
|
||||
|
||||
return watcher
|
||||
}
|
||||
|
||||
func (cm *ConfigManager) WatchFile(path string) {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("配置文件已修改,重新加载...")
|
||||
time.Sleep(100 * time.Millisecond) // 避免多次触发
|
||||
cm.LoadConfig(path)
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
log.Println("配置文件监听错误:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 网关监控和可观测性
|
||||
|
||||
### 1. 指标收集
|
||||
|
||||
```go
|
||||
// 指标收集
|
||||
var (
|
||||
requestsTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "gateway_requests_total",
|
||||
Help: "Total number of requests",
|
||||
},
|
||||
[]string{"service", "method", "path", "status"},
|
||||
)
|
||||
|
||||
requestDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "gateway_request_duration_seconds",
|
||||
Help: "Request duration in seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"service", "method", "path"},
|
||||
)
|
||||
|
||||
activeConnections = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "gateway_active_connections",
|
||||
Help: "Number of active connections",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func MetricsMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
|
||||
activeConnections.Inc()
|
||||
defer activeConnections.Dec()
|
||||
|
||||
c.Next()
|
||||
|
||||
duration := time.Since(start).Seconds()
|
||||
status := strconv.Itoa(c.Writer.Status())
|
||||
service := c.GetString("target_service")
|
||||
|
||||
requestsTotal.WithLabelValues(
|
||||
service,
|
||||
c.Request.Method,
|
||||
c.Request.URL.Path,
|
||||
status,
|
||||
).Inc()
|
||||
|
||||
requestDuration.WithLabelValues(
|
||||
service,
|
||||
c.Request.Method,
|
||||
c.Request.URL.Path,
|
||||
).Observe(duration)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 链路追踪
|
||||
|
||||
```go
|
||||
// OpenTelemetry 追踪
|
||||
func TracingMiddleware(tracer trace.Tracer) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
spanName := fmt.Sprintf("%s %s", c.Request.Method, c.Request.URL.Path)
|
||||
|
||||
ctx, span := tracer.Start(c.Request.Context(), spanName)
|
||||
defer span.End()
|
||||
|
||||
// 设置 span 属性
|
||||
span.SetAttributes(
|
||||
attribute.String("http.method", c.Request.Method),
|
||||
attribute.String("http.url", c.Request.URL.String()),
|
||||
attribute.String("http.user_agent", c.Request.UserAgent()),
|
||||
attribute.String("client.ip", c.ClientIP()),
|
||||
)
|
||||
|
||||
// 传递上下文
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
||||
c.Next()
|
||||
|
||||
// 设置响应属性
|
||||
span.SetAttributes(
|
||||
attribute.Int("http.status_code", c.Writer.Status()),
|
||||
attribute.Int("http.response_size", c.Writer.Size()),
|
||||
)
|
||||
|
||||
if c.Writer.Status() >= 400 {
|
||||
span.SetStatus(codes.Error, fmt.Sprintf("HTTP %d", c.Writer.Status()))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🏗️ 网关架构设计
|
||||
|
||||
### 1. 高可用部署
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: "3.8"
|
||||
services:
|
||||
gateway-1:
|
||||
image: your-gateway:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- GATEWAY_ID=gateway-1
|
||||
- CONSUL_ADDRESS=consul:8500
|
||||
depends_on:
|
||||
- consul
|
||||
- redis
|
||||
|
||||
gateway-2:
|
||||
image: your-gateway:latest
|
||||
ports:
|
||||
- "8081:8080"
|
||||
environment:
|
||||
- GATEWAY_ID=gateway-2
|
||||
- CONSUL_ADDRESS=consul:8500
|
||||
depends_on:
|
||||
- consul
|
||||
- redis
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- gateway-1
|
||||
- gateway-2
|
||||
|
||||
consul:
|
||||
image: consul:latest
|
||||
ports:
|
||||
- "8500:8500"
|
||||
command: consul agent -dev -client=0.0.0.0
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
```
|
||||
|
||||
### 2. Nginx 负载均衡配置
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
upstream gateway {
|
||||
server gateway-1:8080 weight=1 max_fails=3 fail_timeout=30s;
|
||||
server gateway-2:8080 weight=1 max_fails=3 fail_timeout=30s;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://gateway;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# 健康检查
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# 健康检查端点
|
||||
location /health {
|
||||
access_log off;
|
||||
proxy_pass http://gateway;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 实施建议
|
||||
|
||||
### 1. 渐进式迁移
|
||||
|
||||
```
|
||||
阶段1: 搭建基础网关
|
||||
├── 基本的路由功能
|
||||
├── 健康检查
|
||||
└── 监控指标
|
||||
|
||||
阶段2: 添加安全功能
|
||||
├── JWT 认证
|
||||
├── API 限流
|
||||
└── 请求日志
|
||||
|
||||
阶段3: 性能优化
|
||||
├── 响应缓存
|
||||
├── 连接池
|
||||
└── 负载均衡
|
||||
|
||||
阶段4: 高级功能
|
||||
├── 熔断器
|
||||
├── 灰度发布
|
||||
└── API 版本管理
|
||||
```
|
||||
|
||||
### 2. 网关选型建议
|
||||
|
||||
**自研网关 (推荐给你)**:
|
||||
|
||||
```go
|
||||
// 基于 gin + consul + redis 的轻量级网关
|
||||
// 优势: 完全控制、定制化强、集成容易
|
||||
// 适合: 中小型项目、快速迭代
|
||||
```
|
||||
|
||||
**开源网关**:
|
||||
|
||||
- **Kong**: 功能丰富,插件生态好
|
||||
- **Traefik**: 配置简单,支持多种后端
|
||||
- **Envoy**: 性能极高,配置复杂
|
||||
|
||||
## 📋 总结
|
||||
|
||||
网关管理的核心要点:
|
||||
|
||||
1. **统一入口** - 所有外部请求通过网关
|
||||
2. **服务路由** - 将请求路由到正确的后端服务
|
||||
3. **安全认证** - 统一的身份验证和授权
|
||||
4. **流量控制** - 限流、熔断、负载均衡
|
||||
5. **可观测性** - 监控、日志、链路追踪
|
||||
6. **高可用** - 多实例部署、健康检查
|
||||
|
||||
对于你的项目,建议先实现基础的路由和认证功能,然后逐步添加高级特性。
|
||||
610
重构方案.md
Normal file
610
重构方案.md
Normal file
@@ -0,0 +1,610 @@
|
||||
# 天远 API 服务器重构方案
|
||||
|
||||
## 🎯 重构目标
|
||||
|
||||
1. **提高代码可维护性** - 清晰的业务边界和模块划分
|
||||
2. **增强系统扩展性** - 松耦合的微服务架构
|
||||
3. **改善开发效率** - 标准化的开发流程和工具链
|
||||
4. **提升系统可靠性** - 完善的监控、测试和部署体系
|
||||
|
||||
## 🏗️ 架构重构
|
||||
|
||||
### 1. 业务域重新设计 (DDD)
|
||||
|
||||
```
|
||||
新的业务域架构:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API Gateway │
|
||||
│ (统一入口网关) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌───────────────▼──┐ ┌─────────▼──────┐ ┌────▼──────────┐
|
||||
│ 用户域服务 │ │ 金融域服务 │ │ 数据服务域 │
|
||||
│ (User Domain) │ │(Financial Domain)│ │(Data Domain) │
|
||||
├─────────────────┤ ├─────────────────┤ ├──────────────┤
|
||||
│• 用户认证 │ │• 钱包管理 │ │• 风险评估 │
|
||||
│• 用户信息 │ │• 支付处理 │ │• 征信查询 │
|
||||
│• 企业认证 │ │• 计费结算 │ │• 企业信息 │
|
||||
│• 权限管理 │ │• 财务报表 │ │• 数据查询 │
|
||||
└─────────────────┘ └─────────────────┘ └──────────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌───────────────▼──┐ ┌─────────▼──────┐ ┌────▼──────────┐
|
||||
│ 产品域服务 │ │ 平台域服务 │ │ 基础设施域 │
|
||||
│(Product Domain) │ │(Platform Domain)│ │(Infra Domain) │
|
||||
├─────────────────┤ ├─────────────────┤ ├──────────────┤
|
||||
│• 产品目录 │ │• 管理后台 │ │• 消息队列 │
|
||||
│• 产品订阅 │ │• 系统监控 │ │• 缓存服务 │
|
||||
│• 访问控制 │ │• 日志管理 │ │• 配置中心 │
|
||||
│• 白名单管理 │ │• 运维工具 │ │• 服务发现 │
|
||||
└─────────────────┘ └─────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### 2. 微服务重新设计
|
||||
|
||||
#### 用户域 (User Domain)
|
||||
|
||||
```go
|
||||
// 用户认证服务
|
||||
user-auth-service/
|
||||
├── api/
|
||||
│ ├── auth.proto # gRPC 接口定义
|
||||
│ └── auth.api # HTTP 接口定义
|
||||
├── internal/
|
||||
│ ├── domain/ # 领域模型
|
||||
│ ├── application/ # 应用服务
|
||||
│ ├── infrastructure/ # 基础设施
|
||||
│ └── interfaces/ # 接口适配器
|
||||
└── cmd/
|
||||
└── main.go
|
||||
|
||||
// 企业认证服务
|
||||
enterprise-service/
|
||||
├── api/
|
||||
├── internal/
|
||||
└── cmd/
|
||||
```
|
||||
|
||||
#### 金融域 (Financial Domain)
|
||||
|
||||
```go
|
||||
// 钱包服务
|
||||
wallet-service/
|
||||
├── api/
|
||||
├── internal/
|
||||
│ ├── domain/
|
||||
│ │ ├── wallet/ # 钱包聚合根
|
||||
│ │ ├── transaction/ # 交易实体
|
||||
│ │ └── balance/ # 余额值对象
|
||||
│ ├── application/
|
||||
│ └── infrastructure/
|
||||
└── cmd/
|
||||
|
||||
// 支付服务
|
||||
payment-service/
|
||||
├── api/
|
||||
├── internal/
|
||||
└── cmd/
|
||||
```
|
||||
|
||||
#### 数据服务域 (Data Service Domain)
|
||||
|
||||
```go
|
||||
// 统一数据服务网关
|
||||
data-gateway-service/
|
||||
├── api/
|
||||
├── internal/
|
||||
│ ├── adapters/ # 第三方数据源适配器
|
||||
│ │ ├── westdex/ # 西部数据源
|
||||
│ │ ├── baidu/ # 百度接口
|
||||
│ │ └── aliyun/ # 阿里云接口
|
||||
│ ├── domain/
|
||||
│ │ ├── query/ # 查询服务
|
||||
│ │ ├── risk/ # 风险评估
|
||||
│ │ └── credit/ # 征信服务
|
||||
│ └── application/
|
||||
└── cmd/
|
||||
```
|
||||
|
||||
#### 产品域 (Product Domain)
|
||||
|
||||
```go
|
||||
// 产品管理服务
|
||||
product-service/
|
||||
├── api/
|
||||
├── internal/
|
||||
│ ├── domain/
|
||||
│ │ ├── product/ # 产品聚合根
|
||||
│ │ ├── subscription/ # 订阅实体
|
||||
│ │ └── access/ # 访问控制
|
||||
│ └── application/
|
||||
└── cmd/
|
||||
```
|
||||
|
||||
## 🛠️ 技术重构
|
||||
|
||||
### 1. 代码结构标准化
|
||||
|
||||
**采用六边形架构 (Hexagonal Architecture)**:
|
||||
|
||||
```go
|
||||
service/
|
||||
├── api/ # 接口定义
|
||||
│ ├── grpc/ # gRPC 协议
|
||||
│ ├── http/ # HTTP 协议
|
||||
│ └── event/ # 事件协议
|
||||
├── internal/
|
||||
│ ├── domain/ # 领域层
|
||||
│ │ ├── entity/ # 实体
|
||||
│ │ ├── valueobject/ # 值对象
|
||||
│ │ ├── aggregate/ # 聚合根
|
||||
│ │ ├── repository/ # 仓储接口
|
||||
│ │ └── service/ # 领域服务
|
||||
│ ├── application/ # 应用层
|
||||
│ │ ├── command/ # 命令处理
|
||||
│ │ ├── query/ # 查询处理
|
||||
│ │ ├── dto/ # 数据传输对象
|
||||
│ │ └── service/ # 应用服务
|
||||
│ ├── infrastructure/ # 基础设施层
|
||||
│ │ ├── persistence/ # 持久化
|
||||
│ │ ├── messaging/ # 消息传递
|
||||
│ │ ├── external/ # 外部服务
|
||||
│ │ └── config/ # 配置
|
||||
│ └── interfaces/ # 接口适配器层
|
||||
│ ├── grpc/ # gRPC 适配器
|
||||
│ ├── http/ # HTTP 适配器
|
||||
│ ├── event/ # 事件适配器
|
||||
│ └── cli/ # 命令行适配器
|
||||
├── pkg/ # 共享包
|
||||
├── test/ # 测试
|
||||
├── docs/ # 文档
|
||||
├── deployments/ # 部署配置
|
||||
├── Dockerfile
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
### 2. 技术栈升级
|
||||
|
||||
**推荐的技术栈**:
|
||||
|
||||
```yaml
|
||||
# 微服务框架
|
||||
framework: go-zero v1.6+ 或 go-kit v0.12+
|
||||
|
||||
# 数据库
|
||||
database:
|
||||
- PostgreSQL 15+ (替代MySQL, 更好的JSON支持)
|
||||
- Redis 7+ (缓存和会话)
|
||||
- ClickHouse (日志和分析)
|
||||
|
||||
# 消息队列
|
||||
messaging:
|
||||
- Apache Kafka 3.0+ (事件流)
|
||||
- RabbitMQ 3.11+ (任务队列)
|
||||
|
||||
# 服务治理
|
||||
service_mesh: Istio 1.17+
|
||||
service_discovery: Consul 1.15+
|
||||
configuration: Consul KV 或 etcd
|
||||
|
||||
# 监控和可观测性
|
||||
monitoring:
|
||||
- Prometheus + Grafana
|
||||
- Jaeger (分布式追踪)
|
||||
- ELK Stack (日志)
|
||||
- SkyWalking (APM)
|
||||
|
||||
# 安全
|
||||
security:
|
||||
- Vault (密钥管理)
|
||||
- OPA (策略引擎)
|
||||
- Keycloak (身份认证)
|
||||
```
|
||||
|
||||
## 📊 数据层重构
|
||||
|
||||
### 1. 数据库重新设计
|
||||
|
||||
**按业务域分离数据库**:
|
||||
|
||||
```sql
|
||||
-- 用户域数据库
|
||||
user_db:
|
||||
- users
|
||||
- user_profiles
|
||||
- user_sessions
|
||||
- enterprise_auth
|
||||
|
||||
-- 金融域数据库
|
||||
financial_db:
|
||||
- wallets
|
||||
- transactions
|
||||
- payment_orders
|
||||
- billing_records
|
||||
|
||||
-- 产品域数据库
|
||||
product_db:
|
||||
- products
|
||||
- product_subscriptions
|
||||
- access_policies
|
||||
- whitelists
|
||||
|
||||
-- 平台域数据库
|
||||
platform_db:
|
||||
- api_logs
|
||||
- system_configs
|
||||
- admin_users
|
||||
- audit_logs
|
||||
```
|
||||
|
||||
### 2. 数据一致性策略
|
||||
|
||||
**事件驱动架构**:
|
||||
|
||||
```go
|
||||
// 事件定义
|
||||
type UserRegistered struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type WalletCreated struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
WalletID string `json:"wallet_id"`
|
||||
Balance float64 `json:"balance"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
func (h *WalletEventHandler) HandleUserRegistered(event UserRegistered) error {
|
||||
// 为新用户创建钱包
|
||||
return h.walletService.CreateWallet(event.UserID)
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 开发流程重构
|
||||
|
||||
### 1. 代码生成和模板
|
||||
|
||||
**统一的代码生成器**:
|
||||
|
||||
```bash
|
||||
# 使用 protobuf 和 API 定义生成代码
|
||||
make generate-service name=user-auth
|
||||
make generate-api name=wallet
|
||||
|
||||
# 生成的目录结构
|
||||
generated/
|
||||
├── user-auth-service/
|
||||
│ ├── pb/ # protobuf 生成
|
||||
│ ├── client/ # 客户端代码
|
||||
│ ├── server/ # 服务端代码
|
||||
│ └── mock/ # 测试桩
|
||||
```
|
||||
|
||||
### 2. 测试策略
|
||||
|
||||
**多层次测试**:
|
||||
|
||||
```go
|
||||
// 单元测试
|
||||
func TestWalletService_Transfer(t *testing.T) {
|
||||
// 领域逻辑测试
|
||||
}
|
||||
|
||||
// 集成测试
|
||||
func TestWalletAPI_Transfer(t *testing.T) {
|
||||
// API 集成测试
|
||||
}
|
||||
|
||||
// 契约测试
|
||||
func TestWalletService_Contract(t *testing.T) {
|
||||
// 服务间契约测试
|
||||
}
|
||||
|
||||
// 端到端测试
|
||||
func TestTransferWorkflow(t *testing.T) {
|
||||
// 完整业务流程测试
|
||||
}
|
||||
```
|
||||
|
||||
### 3. CI/CD 流水线
|
||||
|
||||
**GitOps 工作流**:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/service.yml
|
||||
name: Service CI/CD
|
||||
on:
|
||||
push:
|
||||
paths: ["services/user-auth/**"]
|
||||
pull_request:
|
||||
paths: ["services/user-auth/**"]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run tests
|
||||
run: make test-user-auth
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build image
|
||||
run: make build-user-auth
|
||||
- name: Push to registry
|
||||
run: make push-user-auth
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Deploy to staging
|
||||
run: make deploy-user-auth-staging
|
||||
```
|
||||
|
||||
## 📱 API 设计重构
|
||||
|
||||
### 1. RESTful API 标准化
|
||||
|
||||
**统一的 API 设计规范**:
|
||||
|
||||
```go
|
||||
// 资源命名
|
||||
GET /api/v1/users # 获取用户列表
|
||||
GET /api/v1/users/{id} # 获取用户详情
|
||||
POST /api/v1/users # 创建用户
|
||||
PUT /api/v1/users/{id} # 更新用户
|
||||
DELETE /api/v1/users/{id} # 删除用户
|
||||
|
||||
// 子资源
|
||||
GET /api/v1/users/{id}/wallets # 获取用户钱包
|
||||
POST /api/v1/users/{id}/wallets # 创建用户钱包
|
||||
|
||||
// 统一响应格式
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Page int `json:"page,omitempty"`
|
||||
PerPage int `json:"per_page,omitempty"`
|
||||
Total int `json:"total,omitempty"`
|
||||
TotalPages int `json:"total_pages,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 2. GraphQL 支持
|
||||
|
||||
**为复杂查询提供 GraphQL**:
|
||||
|
||||
```graphql
|
||||
type User {
|
||||
id: ID!
|
||||
username: String!
|
||||
email: String!
|
||||
wallet: Wallet
|
||||
subscriptions: [ProductSubscription!]!
|
||||
}
|
||||
|
||||
type Query {
|
||||
user(id: ID!): User
|
||||
users(filter: UserFilter, pagination: Pagination): UserConnection
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createUser(input: CreateUserInput!): User!
|
||||
updateUser(id: ID!, input: UpdateUserInput!): User!
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 安全重构
|
||||
|
||||
### 1. 认证授权统一化
|
||||
|
||||
**OAuth 2.0 + RBAC**:
|
||||
|
||||
```go
|
||||
// 角色定义
|
||||
type Role struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Permissions []Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ID int64 `json:"id"`
|
||||
Resource string `json:"resource"` // users, wallets, products
|
||||
Action string `json:"action"` // read, write, delete
|
||||
Scope string `json:"scope"` // own, team, all
|
||||
}
|
||||
|
||||
// 中间件
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := extractToken(c)
|
||||
claims, err := validateToken(token)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := getUserFromClaims(claims)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user", user)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据安全
|
||||
|
||||
**端到端加密 + 数据脱敏**:
|
||||
|
||||
```go
|
||||
// 字段级加密
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email" encrypt:"true"`
|
||||
Phone string `json:"phone" encrypt:"true" mask:"***-****-####"`
|
||||
}
|
||||
|
||||
// 自动加密解密
|
||||
func (u *User) BeforeSave() error {
|
||||
return encrypt.EncryptStruct(u)
|
||||
}
|
||||
|
||||
func (u *User) AfterFind() error {
|
||||
return encrypt.DecryptStruct(u)
|
||||
}
|
||||
```
|
||||
|
||||
## 📈 监控和可观测性
|
||||
|
||||
### 1. 分布式追踪
|
||||
|
||||
**OpenTelemetry 集成**:
|
||||
|
||||
```go
|
||||
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
span.SetAttributes(
|
||||
attribute.String("user.username", req.Username),
|
||||
attribute.String("operation", "create_user"),
|
||||
)
|
||||
|
||||
// 业务逻辑
|
||||
user, err := s.userRepo.Create(ctx, req)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 发送事件
|
||||
event := UserCreatedEvent{UserID: user.ID, Username: user.Username}
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Error("failed to publish event", zap.Error(err))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 业务指标监控
|
||||
|
||||
**自定义业务指标**:
|
||||
|
||||
```go
|
||||
var (
|
||||
userRegistrations = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "user_registrations_total",
|
||||
Help: "Total number of user registrations",
|
||||
},
|
||||
[]string{"source", "status"},
|
||||
)
|
||||
|
||||
walletBalance = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wallet_balance",
|
||||
Help: "Current wallet balance",
|
||||
},
|
||||
[]string{"user_id", "currency"},
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## 🚀 迁移策略
|
||||
|
||||
### 阶段 1: 基础设施准备 (1-2 周)
|
||||
|
||||
1. 搭建新的开发环境
|
||||
2. 建立 CI/CD 流水线
|
||||
3. 配置监控和日志系统
|
||||
|
||||
### 阶段 2: 用户域重构 (2-3 周)
|
||||
|
||||
1. 重构用户认证服务
|
||||
2. 迁移用户数据
|
||||
3. 更新客户端集成
|
||||
|
||||
### 阶段 3: 金融域重构 (2-3 周)
|
||||
|
||||
1. 重构钱包服务
|
||||
2. 重构支付服务
|
||||
3. 数据一致性验证
|
||||
|
||||
### 阶段 4: 数据服务域重构 (3-4 周)
|
||||
|
||||
1. 重构数据网关
|
||||
2. 重新组织业务 API
|
||||
3. 性能优化
|
||||
|
||||
### 阶段 5: 产品域重构 (1-2 周)
|
||||
|
||||
1. 重构产品管理
|
||||
2. 重构访问控制
|
||||
3. 清理遗留代码
|
||||
|
||||
### 阶段 6: 平台域完善 (1-2 周)
|
||||
|
||||
1. 完善管理后台
|
||||
2. 优化监控系统
|
||||
3. 性能调优
|
||||
|
||||
## 📋 重构检查清单
|
||||
|
||||
### 代码质量
|
||||
|
||||
- [ ] 代码覆盖率 > 80%
|
||||
- [ ] 静态代码分析通过
|
||||
- [ ] API 文档完整
|
||||
- [ ] 性能基准测试
|
||||
|
||||
### 架构质量
|
||||
|
||||
- [ ] 服务间松耦合
|
||||
- [ ] 明确的业务边界
|
||||
- [ ] 数据一致性保证
|
||||
- [ ] 容错和恢复机制
|
||||
|
||||
### 运维质量
|
||||
|
||||
- [ ] 自动化部署
|
||||
- [ ] 监控告警完整
|
||||
- [ ] 日志可追溯
|
||||
- [ ] 备份恢复测试
|
||||
|
||||
### 安全质量
|
||||
|
||||
- [ ] 认证授权机制
|
||||
- [ ] 数据加密传输
|
||||
- [ ] 漏洞扫描通过
|
||||
- [ ] 安全策略文档
|
||||
|
||||
## 🎯 预期收益
|
||||
|
||||
1. **开发效率提升 40%** - 通过标准化和自动化
|
||||
2. **系统可靠性提升 60%** - 通过完善的测试和监控
|
||||
3. **维护成本降低 50%** - 通过清晰的架构和文档
|
||||
4. **新功能交付速度提升 3 倍** - 通过模块化设计
|
||||
|
||||
重构是一个渐进的过程,建议分阶段实施,确保每个阶段都有明确的交付物和验收标准。
|
||||
399
项目架构文档.md
Normal file
399
项目架构文档.md
Normal file
@@ -0,0 +1,399 @@
|
||||
# 天远 API 服务器 - 微服务架构文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
天远 API 服务器是一个基于 `go-zero` 框架构建的微服务架构系统,主要提供数据加密传输、用户管理、产品管理、支付等核心业务功能。整个系统采用微服务架构模式,每个服务独立部署,通过 gRPC 和 HTTP 进行通信。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **微服务框架**: go-zero
|
||||
- **编程语言**: Go
|
||||
- **数据库**: MySQL
|
||||
- **缓存**: Redis
|
||||
- **消息队列**: Kafka (通过 kq)
|
||||
- **服务注册发现**: Etcd
|
||||
- **API 协议**: HTTP/REST + gRPC
|
||||
- **加密**: AES, MD5
|
||||
- **第三方服务**: 阿里云短信、七牛云存储、百度 AI、支付宝支付
|
||||
|
||||
## 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ 客户端应用 │ │ 管理端应用 │ │ 第三方系统 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ API 网关层 │
|
||||
│ (gateway-api) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 微服务集群 │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ API │ │ User │ │ Sentinel │ │ Admin │ │
|
||||
│ │ Service │ │ Service │ │ Service │ │ Service │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ MQS │ │ Index │ │ WestDex │ │ 共享包 │ │
|
||||
│ │ Service │ │ Service │ │ Module │ │ (pkg) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 基础设施层 │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ MySQL │ │ Redis │ │ Kafka │ │ Etcd │ │
|
||||
│ │ Database │ │ Cache │ │MessageQueue │ │Service Disc │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 服务详细说明
|
||||
|
||||
### 1. Gateway Service (网关服务)
|
||||
|
||||
**路径**: `apps/gateway/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 统一入口网关,处理所有外部请求
|
||||
- 用户认证与授权
|
||||
- 企业认证管理
|
||||
- 产品信息查询
|
||||
- 用户产品管理
|
||||
- 充值管理
|
||||
- 白名单管理
|
||||
|
||||
**API 端点**:
|
||||
|
||||
- `/api/console/auth/*` - 认证相关(登录、注册、验证码)
|
||||
- `/api/console/user/*` - 用户管理(用户信息、企业认证)
|
||||
- `/api/console/product/*` - 产品管理
|
||||
- `/api/console/user-product/*` - 用户产品关联
|
||||
- `/api/console/topup/*` - 充值管理
|
||||
- `/api/console/whitelist/*` - 白名单管理
|
||||
|
||||
**依赖**:
|
||||
|
||||
- UserRpc: 用户服务的 gRPC 客户端
|
||||
- SentinelRpc: 哨兵服务的 gRPC 客户端
|
||||
|
||||
### 2. API Service (核心 API 服务)
|
||||
|
||||
**路径**: `apps/api/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 核心业务 API 处理
|
||||
- 数据加密解密
|
||||
- 第三方接口代理
|
||||
- 业务逻辑处理
|
||||
|
||||
**业务模块**:
|
||||
|
||||
- **IVYZ**: 数据查询服务 (7 个 API 端点)
|
||||
- **FLXG**: 风险评估服务 (12 个 API 端点)
|
||||
- **QYGL**: 企业管理服务 (6 个 API 端点)
|
||||
- **YYSY**: 应用系统服务 (7 个 API 端点)
|
||||
- **JRZQ**: 金融征信服务 (4 个 API 端点)
|
||||
- **COMB**: 组合业务服务 (1 个 API 端点)
|
||||
|
||||
**特点**:
|
||||
|
||||
- 所有 API 都需要通过 `ApiAuthInterceptor` 中间件认证
|
||||
- 数据采用 AES 加密传输
|
||||
- 集成了消息队列服务记录 API 调用
|
||||
|
||||
### 3. User Service (用户服务)
|
||||
|
||||
**路径**: `apps/user/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 用户注册、登录、认证
|
||||
- 企业认证管理
|
||||
- 钱包服务(余额、充值、扣款)
|
||||
- API 请求记录管理
|
||||
|
||||
**gRPC 服务**:
|
||||
|
||||
- `Auth`: 用户认证服务
|
||||
- `User`: 用户信息服务
|
||||
- `WalletService`: 钱包服务
|
||||
- `Enterprise`: 企业服务
|
||||
- `ApiRequestService`: API 请求记录服务
|
||||
|
||||
**数据模型**:
|
||||
|
||||
- 用户信息
|
||||
- 企业认证信息
|
||||
- 钱包余额
|
||||
- 扣款记录
|
||||
- 充值记录
|
||||
- API 请求记录
|
||||
|
||||
### 4. Sentinel Service (哨兵服务)
|
||||
|
||||
**路径**: `apps/sentinel/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 产品管理
|
||||
- 用户产品关联管理
|
||||
- 白名单管理
|
||||
- 密钥管理
|
||||
- 支付管理
|
||||
|
||||
**gRPC 服务**:
|
||||
|
||||
- `product`: 产品服务
|
||||
- `userProduct`: 用户产品服务
|
||||
- `whitelist`: 白名单服务
|
||||
- `secret`: 密钥服务
|
||||
- `topUp`: 充值服务
|
||||
|
||||
### 5. Admin Service (管理端服务)
|
||||
|
||||
**路径**: `apps/admin/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 管理员认证
|
||||
- 企业审核管理
|
||||
- 产品管理
|
||||
- 用户管理
|
||||
- 用户充值
|
||||
|
||||
**API 模块**:
|
||||
|
||||
- `auth`: 管理员登录
|
||||
- `review`: 企业审核
|
||||
- `product`: 产品 CRUD 操作
|
||||
- `user`: 用户管理和充值
|
||||
|
||||
### 6. MQS Service (消息队列服务)
|
||||
|
||||
**路径**: `apps/mqs/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 消息队列消费者服务
|
||||
- API 请求记录处理
|
||||
- 异步任务处理
|
||||
|
||||
**特点**:
|
||||
|
||||
- 监听 Kafka 消息队列
|
||||
- 处理 API 请求日志
|
||||
- 支持分布式消息处理
|
||||
|
||||
### 7. Index Service (索引服务)
|
||||
|
||||
**路径**: `apps/index/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 产品索引服务
|
||||
- 搜索功能支持
|
||||
|
||||
### 8. WestDex Module (西部对接模块)
|
||||
|
||||
**路径**: `westDex/`
|
||||
|
||||
**功能**:
|
||||
|
||||
- 第三方西部数据源对接
|
||||
- 数据加密解密
|
||||
- 独立运行的数据处理模块
|
||||
|
||||
## 共享包 (pkg/)
|
||||
|
||||
### 1. crypto
|
||||
|
||||
- AES 加密解密
|
||||
- MD5 哈希
|
||||
- 西部数据源专用加密
|
||||
|
||||
### 2. errs
|
||||
|
||||
- 统一错误处理
|
||||
- API 错误码定义
|
||||
|
||||
### 3. jwt
|
||||
|
||||
- JWT 令牌生成和验证
|
||||
|
||||
### 4. models
|
||||
|
||||
- 消息队列数据模型
|
||||
- 通用数据结构
|
||||
|
||||
### 5. response
|
||||
|
||||
- 统一响应格式
|
||||
|
||||
### 6. generate
|
||||
|
||||
- 支付相关生成器
|
||||
|
||||
### 7. sqlutil
|
||||
|
||||
- 数据库工具函数
|
||||
|
||||
## 中间件系统
|
||||
|
||||
### 1. AuthInterceptor
|
||||
|
||||
- 用户身份认证
|
||||
- JWT 令牌验证
|
||||
- 用户上下文注入
|
||||
|
||||
### 2. ApiAuthInterceptor
|
||||
|
||||
- API 调用认证
|
||||
- 签名验证
|
||||
- 限流控制
|
||||
|
||||
### 3. EntAuthInterceptor
|
||||
|
||||
- 企业认证检查
|
||||
- 企业用户权限验证
|
||||
|
||||
### 4. ApiMqsInterceptor
|
||||
|
||||
- API 调用记录
|
||||
- 消息队列发送
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 主要数据表
|
||||
|
||||
- `users`: 用户基础信息
|
||||
- `enterprises`: 企业认证信息
|
||||
- `products`: 产品信息
|
||||
- `user_products`: 用户产品关联
|
||||
- `wallets`: 钱包信息
|
||||
- `recharge_records`: 充值记录
|
||||
- `deduction_records`: 扣款记录
|
||||
- `api_requests`: API 请求记录
|
||||
- `pay_orders`: 支付订单
|
||||
- `whitelists`: 白名单
|
||||
- `secrets`: 密钥管理
|
||||
|
||||
## 配置管理
|
||||
|
||||
每个服务都支持多环境配置:
|
||||
|
||||
- `etc/*.yaml`: 生产环境配置
|
||||
- `etc/*.dev.yaml`: 开发环境配置
|
||||
|
||||
**主要配置项**:
|
||||
|
||||
- 数据库连接 (MySQL)
|
||||
- 缓存配置 (Redis)
|
||||
- 服务发现 (Etcd)
|
||||
- 第三方服务 (阿里云、七牛云、百度 AI)
|
||||
- JWT 配置
|
||||
- 消息队列配置
|
||||
|
||||
## 部署架构
|
||||
|
||||
### Docker 化部署
|
||||
|
||||
每个服务都有独立的 Dockerfile:
|
||||
|
||||
- `apps/*/Dockerfile`
|
||||
|
||||
### 服务发现
|
||||
|
||||
- 使用 Etcd 作为服务注册中心
|
||||
- gRPC 服务自动注册和发现
|
||||
|
||||
### 负载均衡
|
||||
|
||||
- 通过 go-zero 内置的负载均衡机制
|
||||
- 支持多种负载均衡策略
|
||||
|
||||
## 安全机制
|
||||
|
||||
### 1. 数据传输安全
|
||||
|
||||
- AES 加密传输
|
||||
- HTTPS 协议
|
||||
- 签名验证
|
||||
|
||||
### 2. 身份认证
|
||||
|
||||
- JWT 令牌认证
|
||||
- 多级权限控制
|
||||
- 白名单机制
|
||||
|
||||
### 3. 数据存储安全
|
||||
|
||||
- 数据库密码加密
|
||||
- 敏感信息脱敏
|
||||
- 访问日志记录
|
||||
|
||||
## 监控与日志
|
||||
|
||||
### 日志系统
|
||||
|
||||
- 结构化日志记录
|
||||
- 分级日志管理
|
||||
- 链路追踪支持
|
||||
|
||||
### 监控指标
|
||||
|
||||
- API 调用统计
|
||||
- 服务健康检查
|
||||
- 性能指标监控
|
||||
|
||||
## 扩展性设计
|
||||
|
||||
### 水平扩展
|
||||
|
||||
- 无状态服务设计
|
||||
- 数据库读写分离支持
|
||||
- 缓存集群支持
|
||||
|
||||
### 模块化设计
|
||||
|
||||
- 微服务独立部署
|
||||
- 插件化业务模块
|
||||
- 模板化代码生成
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码结构
|
||||
|
||||
每个微服务遵循 go-zero 标准结构:
|
||||
|
||||
```
|
||||
service/
|
||||
├── internal/
|
||||
│ ├── config/ # 配置定义
|
||||
│ ├── handler/ # HTTP处理器
|
||||
│ ├── logic/ # 业务逻辑
|
||||
│ ├── svc/ # 服务上下文
|
||||
│ ├── types/ # 类型定义
|
||||
│ └── middleware/ # 中间件
|
||||
├── etc/ # 配置文件
|
||||
├── *.api # API定义文件
|
||||
├── *.proto # protobuf定义
|
||||
└── Dockerfile # 容器化配置
|
||||
```
|
||||
|
||||
### API 设计规范
|
||||
|
||||
- RESTful API 设计
|
||||
- 统一错误码
|
||||
- 标准化响应格式
|
||||
- 版本控制支持
|
||||
|
||||
## 总结
|
||||
|
||||
天远 API 服务器采用了现代化的微服务架构,具备良好的可扩展性、可维护性和安全性。通过 go-zero 框架的支持,实现了高性能的服务间通信和统一的开发规范。整个系统支持多环境部署、监控告警、日志追踪等企业级特性,是一个完整的商业级 API 服务平台。
|
||||
Reference in New Issue
Block a user