diff --git a/API调用流程域设计.md b/API调用流程域设计.md new file mode 100644 index 0000000..10ee471 --- /dev/null +++ b/API调用流程域设计.md @@ -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. **性能优化** - 多级缓存和异步处理 + +这种设计既保证了业务逻辑的清晰性,又确保了系统的高性能和高可用性。 diff --git a/Domain域详解.md b/Domain域详解.md new file mode 100644 index 0000000..30af588 --- /dev/null +++ b/Domain域详解.md @@ -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 通信 +- 团队可以专注于特定业务领域 + +这样组织代码后,你的项目会更容易理解、维护和扩展! diff --git a/apps/api/internal/logic/IVYZ/ivyz0b03logic.go b/apps/api/internal/logic/IVYZ/ivyz0b03logic.go index b46cab9..879444e 100644 --- a/apps/api/internal/logic/IVYZ/ivyz0b03logic.go +++ b/apps/api/internal/logic/IVYZ/ivyz0b03logic.go @@ -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 +} \ No newline at end of file diff --git a/apps/api/internal/logic/YYSY/yysybe08logic.go b/apps/api/internal/logic/YYSY/yysybe08logic.go index 4352b6f..473479e 100644 --- a/apps/api/internal/logic/YYSY/yysybe08logic.go +++ b/apps/api/internal/logic/YYSY/yysybe08logic.go @@ -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) diff --git a/apps/api/internal/validator/structs.go b/apps/api/internal/validator/structs.go index e5d8aa5..e76c9e4 100644 --- a/apps/api/internal/validator/structs.go +++ b/apps/api/internal/validator/structs.go @@ -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"` diff --git a/apps/api/internal/westmodel/fieldMapping.go b/apps/api/internal/westmodel/fieldMapping.go index 5622ef9..2c2edfb 100644 --- a/apps/api/internal/westmodel/fieldMapping.go +++ b/apps/api/internal/westmodel/fieldMapping.go @@ -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", diff --git a/cmd目录详解.md b/cmd目录详解.md new file mode 100644 index 0000000..d1486ac --- /dev/null +++ b/cmd目录详解.md @@ -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 等不同形态 diff --git a/go-zero服务实现详解.md b/go-zero服务实现详解.md new file mode 100644 index 0000000..ef4f63a --- /dev/null +++ b/go-zero服务实现详解.md @@ -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 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 框架,保持了你原有业务逻辑的同时,提供了清晰的服务边界和强大的扩展能力。每个服务都可以独立开发、测试、部署和扩容。 diff --git a/go-zero错误分级与链路追踪设计.md b/go-zero错误分级与链路追踪设计.md new file mode 100644 index 0000000..eaaf151 --- /dev/null +++ b/go-zero错误分级与链路追踪设计.md @@ -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. **监控告警策略**: + - 错误率监控 + - 关键错误实时告警 + - 链路追踪性能监控 diff --git a/validator封装设计.md b/validator封装设计.md new file mode 100644 index 0000000..2ea557c --- /dev/null +++ b/validator封装设计.md @@ -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. **配置化**:错误消息和校验规则可以配置化管理 + +这样的封装既保持了格式校验和业务校验的清晰边界,又提供了便捷的使用接口。 diff --git a/域与服务的关系及目录架构.md b/域与服务的关系及目录架构.md new file mode 100644 index 0000000..aa1668e --- /dev/null +++ b/域与服务的关系及目录架构.md @@ -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. **独立部署** - 每个服务都可以独立构建部署 + +这种架构既保持了逻辑上的清晰性,又确保了技术实现的可操作性! diff --git a/基于go-zero的完整微服务架构设计.md b/基于go-zero的完整微服务架构设计.md new file mode 100644 index 0000000..a9446be --- /dev/null +++ b/基于go-zero的完整微服务架构设计.md @@ -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 +``` + +接下来我会详细说明每个部分的具体实现... diff --git a/校验体系设计优化.md b/校验体系设计优化.md new file mode 100644 index 0000000..303d9c7 --- /dev/null +++ b/校验体系设计优化.md @@ -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 自动绑定 diff --git a/网关域架构与跨域调用实现.md b/网关域架构与跨域调用实现.md new file mode 100644 index 0000000..b5fa91f --- /dev/null +++ b/网关域架构与跨域调用实现.md @@ -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 接口 + +**关键点:网关域负责"谁来处理",数据域负责"怎么处理"!** diff --git a/网关管理详解.md b/网关管理详解.md new file mode 100644 index 0000000..9354bac --- /dev/null +++ b/网关管理详解.md @@ -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. **高可用** - 多实例部署、健康检查 + +对于你的项目,建议先实现基础的路由和认证功能,然后逐步添加高级特性。 diff --git a/重构方案.md b/重构方案.md new file mode 100644 index 0000000..28912ae --- /dev/null +++ b/重构方案.md @@ -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 倍** - 通过模块化设计 + +重构是一个渐进的过程,建议分阶段实施,确保每个阶段都有明确的交付物和验收标准。 diff --git a/项目架构文档.md b/项目架构文档.md new file mode 100644 index 0000000..13841db --- /dev/null +++ b/项目架构文档.md @@ -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 服务平台。