Go vs Java 企业级分层架构全面对比

文档概述

本文档系统对比 Go 与 Java(Spring Boot)在分层架构中的设计差异,涵盖参数校验、身份认证、权限控制、缓存策略、日志系统、异常处理、事务管理等核心模块。


一、层级对照表

层级 Go 命名 Java 命名 职责
入口层 handler / controller Controller 接收请求、参数校验、调用服务
业务层 service / usecase Service 业务逻辑、事务编排
数据层 repository / store Mapper / DAO / Repository 数据持久化
实体层 entity / model / domain Entity / PO / DO 数据结构定义
传输层 dto / request / response DTO / VO / BO 数据传输对象
中间件层 middleware Filter / Interceptor / Aspect 认证、鉴权、日志、限流
配置层 config Configuration / Properties 配置管理

二、企业级目录结构对比

Go 典型结构

project/
├── cmd/
│   └── server/
│       └── main.go                  # 程序入口
├── internal/
│   ├── handler/                     # HTTP 处理器
│   │   ├── user_handler.go
│   │   ├── order_handler.go
│   │   └── middleware/              # 中间件
│   │       ├── auth.go              # 认证中间件
│   │       ├── permission.go        # 权限中间件
│   │       ├── logger.go            # 日志中间件
│   │       ├── ratelimit.go         # 限流中间件
│   │       └── recovery.go          # 异常恢复
│   ├── service/                     # 业务逻辑
│   │   ├── user_service.go
│   │   └── order_service.go
│   ├── repository/                  # 数据访问
│   │   ├── user_repository.go
│   │   ├── order_repository.go
│   │   └── cache/                   # 缓存实现
│   │       └── user_cache.go
│   ├── entity/                      # 实体定义
│   │   ├── user.go
│   │   └── order.go
│   ├── dto/                         # 传输对象
│   │   ├── request/
│   │   │   ├── user_request.go
│   │   │   └── order_request.go
│   │   └── response/
│   │       ├── user_response.go
│   │       └── common.go            # 统一响应结构
│   └── errors/                      # 错误定义
│       └── errors.go
├── pkg/                             # 可复用包
│   ├── auth/                        # JWT 工具
│   │   └── jwt.go
│   ├── cache/                       # 缓存抽象
│   │   └── redis.go
│   ├── logger/                      # 日志工具
│   │   └── logger.go
│   ├── validator/                   # 校验器
│   │   └── validator.go
│   └── utils/
│       └── crypto.go
├── config/                          # 配置
│   ├── config.go
│   └── config.yaml
├── migrations/                      # 数据库迁移
└── go.mod

Java 典型结构(Spring Boot)

project/
├── src/main/java/com/example/
│   ├── Application.java             # 程序入口
│   ├── controller/                  # 控制器
│   │   ├── UserController.java
│   │   └── OrderController.java
│   ├── service/                     # 服务接口
│   │   ├── UserService.java
│   │   └── OrderService.java
│   ├── service/impl/                # 服务实现
│   │   ├── UserServiceImpl.java
│   │   └── OrderServiceImpl.java
│   ├── mapper/                      # MyBatis Mapper
│   │   ├── UserMapper.java
│   │   └── OrderMapper.java
│   ├── entity/                      # 实体类
│   │   ├── User.java
│   │   └── Order.java
│   ├── dto/                         # 传输对象
│   │   ├── request/
│   │   │   ├── CreateUserRequest.java
│   │   │   └── UpdateUserRequest.java
│   │   └── response/
│   │       ├── UserResponse.java
│   │       └── CommonResponse.java
│   ├── config/                      # 配置类
│   │   ├── SecurityConfig.java      # 安全配置
│   │   ├── RedisConfig.java         # Redis 配置
│   │   └── WebMvcConfig.java        # MVC 配置
│   ├── filter/                      # 过滤器
│   │   └── JwtAuthenticationFilter.java
│   ├── interceptor/                 # 拦截器
│   │   ├── AuthInterceptor.java
│   │   └── PermissionInterceptor.java
│   ├── aspect/                      # 切面
│   │   ├── LogAspect.java
│   │   └── PermissionAspect.java
│   ├── exception/                   # 异常处理
│   │   ├── BusinessException.java
│   │   └── GlobalExceptionHandler.java
│   ├── enums/                       # 枚举
│   │   └── ErrorCode.java
│   └── util/                        # 工具类
│       ├── JwtUtil.java
│       └── RedisUtil.java
├── src/main/resources/
│   ├── mapper/                      # MyBatis XML
│   │   └── UserMapper.xml
│   ├── application.yml
│   └── application-dev.yml
└── pom.xml

三、核心架构流程图

3.1 请求处理流程图

graph TB
    subgraph "Go 请求处理流程"
        A1[🌐 HTTP Request] 
        A1 --> B1[Recovery Middleware<br/>异常恢复]
        B1 --> C1[Logger Middleware<br/>请求日志]
        C1 --> D1[CORS Middleware<br/>跨域处理]
        D1 --> E1[RateLimit Middleware<br/>限流控制]
        E1 --> F1[Auth Middleware<br/>JWT 认证]
        F1 --> G1[Permission Middleware<br/>权限校验]
        G1 --> H1[Handler<br/>参数绑定与校验]
        H1 --> I1[Service<br/>业务逻辑处理]
        I1 --> J1{Cache Check<br/>缓存检查}
        J1 -->|Hit| K1[返回缓存数据]
        J1 -->|Miss| L1[Repository<br/>数据访问]
        L1 --> M1[(Database)]
        L1 --> N1[更新缓存]
        N1 --> O1[Response<br/>统一响应]
        K1 --> O1
        
        H1 -.->|DTO| P1[Request DTO]
        L1 -.->|Entity| Q1[Entity Model]
        O1 -.->|DTO| R1[Response DTO]
    end
graph TB
    subgraph "Java 请求处理流程"
        A2[🌐 HTTP Request]
        A2 --> B2[Filter Chain<br/>过滤器链]
        B2 --> C2[CorsFilter<br/>跨域处理]
        C2 --> D2[RequestLoggingFilter<br/>请求日志]
        D2 --> E2[JwtAuthenticationFilter<br/>JWT 认证]
        E2 --> F2[RateLimitInterceptor<br/>限流拦截器]
        F2 --> G2[Controller<br/>@Valid 参数校验]
        G2 --> H2[PermissionAspect<br/>权限切面]
        H2 --> I2[LogAspect<br/>日志切面]
        I2 --> J2[Service<br/>业务逻辑]
        J2 --> K2{@Cacheable<br/>缓存检查}
        K2 -->|Hit| L2[返回缓存数据]
        K2 -->|Miss| M2[Mapper/Repository<br/>数据访问]
        M2 --> N2[(Database)]
        M2 --> O2[更新缓存]
        O2 --> P2[Response<br/>统一响应]
        L2 --> P2
        
        G2 -.->|DTO| Q2[Request DTO]
        M2 -.->|Entity| R2[Entity/PO]
        P2 -.->|VO| S2[Response VO]
    end

3.2 完整请求时序图

Go 请求时序图

sequenceDiagram
    autonumber
    participant Client as 客户端
    participant MW as 中间件链
    participant Handler as Handler
    participant Validator as Validator
    participant Service as Service
    participant Cache as Redis Cache
    participant Repo as Repository
    participant DB as Database
    
    Client->>+MW: HTTP Request
    
    Note over MW: Recovery Middleware
    Note over MW: Logger Middleware
    Note over MW: RateLimit Check
    
    MW->>MW: 检查限流
    alt 超出限流
        MW-->>Client: 429 Too Many Requests
    end
    
    Note over MW: Auth Middleware
    MW->>MW: 解析 JWT Token
    alt Token 无效
        MW-->>Client: 401 Unauthorized
    end
    
    Note over MW: Permission Middleware
    MW->>Cache: 获取角色权限
    Cache-->>MW: 权限列表
    MW->>MW: 校验权限
    alt 权限不足
        MW-->>Client: 403 Forbidden
    end
    
    MW->>+Handler: 转发请求
    Handler->>+Validator: 参数校验
    Validator-->>-Handler: 校验结果
    alt 校验失败
        Handler-->>Client: 400 Bad Request
    end
    
    Handler->>+Service: 调用业务方法
    
    Service->>+Cache: 查询缓存
    alt 缓存命中
        Cache-->>Service: 缓存数据
        Service-->>Handler: 返回结果
    else 缓存未命中
        Cache-->>-Service: nil
        Service->>+Repo: 查询数据
        Repo->>+DB: SQL Query
        DB-->>-Repo: 查询结果
        Repo-->>-Service: Entity
        
        Service->>Cache: 异步写入缓存
        Service-->>Handler: 返回结果
    end
    
    Handler->>Handler: 转换为 Response DTO
    Handler-->>-MW: Response
    
    Note over MW: Logger 记录响应
    MW-->>-Client: HTTP Response

Java 请求时序图

sequenceDiagram
    autonumber
    participant Client as 客户端
    participant Filter as Filter Chain
    participant Interceptor as Interceptor
    participant Controller as Controller
    participant AOP as AOP Aspects
    participant Service as Service
    participant Cache as Redis/@Cacheable
    participant Mapper as Mapper/Repository
    participant DB as Database
    
    Client->>+Filter: HTTP Request
    
    Note over Filter: CorsFilter
    Note over Filter: RequestLoggingFilter
    Filter->>Filter: 记录请求开始
    
    Note over Filter: JwtAuthenticationFilter
    Filter->>Filter: 解析 JWT
    alt Token 无效
        Filter-->>Client: 401 Unauthorized
    end
    Filter->>Filter: 设置 SecurityContext
    
    Filter->>+Interceptor: 转发请求
    
    Note over Interceptor: RateLimitInterceptor
    Interceptor->>Cache: 检查限流
    alt 超出限流
        Interceptor-->>Client: 429 Too Many Requests
    end
    
    Interceptor->>+Controller: 转发请求
    
    Note over Controller: @Valid 参数校验
    Controller->>Controller: Bean Validation
    alt 校验失败
        Controller-->>Client: 400 Bad Request
    end
    
    Controller->>+AOP: 调用 Service 方法
    
    Note over AOP: @Before PermissionAspect
    AOP->>Cache: 获取用户权限
    AOP->>AOP: 校验 @RequirePermission
    alt 权限不足
        AOP-->>Client: 403 Forbidden
    end
    
    Note over AOP: @Around LogAspect
    AOP->>AOP: 记录方法调用
    
    AOP->>+Service: 执行业务方法
    
    Note over Service: @Transactional
    Service->>Service: 开启事务
    
    Service->>+Cache: @Cacheable 检查缓存
    alt 缓存命中
        Cache-->>Service: 缓存数据
    else 缓存未命中
        Cache-->>-Service: null
        Service->>+Mapper: 查询数据
        Mapper->>+DB: SQL Query
        DB-->>-Mapper: ResultSet
        Mapper-->>-Service: Entity
        
        Note over Service: @CachePut 更新缓存
        Service->>Cache: 写入缓存
    end
    
    Service->>Service: 提交事务
    Service-->>-AOP: 返回结果
    
    Note over AOP: @After LogAspect
    AOP->>AOP: 记录返回值
    
    AOP-->>-Controller: 返回结果
    Controller->>Controller: 封装 CommonResponse
    Controller-->>-Interceptor: Response
    Interceptor-->>-Filter: Response
    
    Note over Filter: 记录响应日志
    Filter-->>-Client: HTTP Response

3.3 认证授权流程图

graph TB
    subgraph "登录认证流程"
        A[用户提交登录请求] --> B{参数校验}
        B -->|失败| C[返回参数错误]
        B -->|成功| D[查询用户信息]
        D --> E{用户存在?}
        E -->|否| F[返回用户不存在]
        E -->|是| G{密码正确?}
        G -->|否| H[返回密码错误]
        G -->|是| I{账号状态正常?}
        I -->|否| J[返回账号已禁用]
        I -->|是| K[生成 JWT Token]
        K --> L[生成 Refresh Token]
        L --> M[更新最后登录时间]
        M --> N[返回 Token 信息]
    end
    
    subgraph "Token 刷新流程"
        O[提交 Refresh Token] --> P{Token 有效?}
        P -->|否| Q[返回 Token 无效]
        P -->|是| R{Token 在黑名单?}
        R -->|是| S[返回 Token 已失效]
        R -->|否| T[生成新 Access Token]
        T --> U[返回新 Token]
    end
    
    subgraph "登出流程"
        V[用户请求登出] --> W[将 Token 加入黑名单]
        W --> X[设置黑名单过期时间]
        X --> Y[返回登出成功]
    end

3.4 权限校验流程图

graph TB
    subgraph "RBAC 权限校验"
        A[收到请求] --> B[从 Token 获取用户信息]
        B --> C[获取用户角色 ID]
        C --> D{角色权限在缓存中?}
        D -->|是| E[从缓存获取权限列表]
        D -->|否| F[从数据库查询权限]
        F --> G[写入缓存]
        G --> E
        E --> H{检查所需权限}
        H --> I{是否有通配符权限 *?}
        I -->|是| J[权限校验通过]
        I -->|否| K{是否有资源通配符?<br/>如 user:*}
        K -->|是| L{请求权限匹配前缀?}
        L -->|是| J
        L -->|否| M{是否精确匹配?}
        K -->|否| M
        M -->|是| J
        M -->|否| N{是否还有其他权限需检查?}
        N -->|是| H
        N -->|否| O[权限校验失败]
        O --> P[返回 403 Forbidden]
        J --> Q[继续处理请求]
    end

3.5 缓存处理流程图

graph TB
    subgraph "缓存读取策略 - Cache Aside"
        A[业务请求] --> B{查询缓存}
        B -->|命中| C[返回缓存数据]
        B -->|未命中| D{布隆过滤器检查<br/>可选}
        D -->|不存在| E[返回空/默认值]
        D -->|可能存在| F{获取分布式锁}
        F -->|获取失败| G[等待后重试]
        G --> B
        F -->|获取成功| H[再次检查缓存<br/>双重检查]
        H -->|命中| I[释放锁]
        I --> C
        H -->|未命中| J[查询数据库]
        J --> K{数据存在?}
        K -->|是| L[写入缓存]
        K -->|否| M[写入空值缓存<br/>防止穿透]
        L --> I
        M --> I
    end
    
    subgraph "缓存更新策略"
        N[数据变更] --> O[更新数据库]
        O --> P{更新成功?}
        P -->|否| Q[返回错误]
        P -->|是| R[删除缓存]
        R --> S{删除成功?}
        S -->|否| T[记录日志<br/>异步重试]
        S -->|是| U[返回成功]
        T --> U
    end

3.6 异常处理流程图

graph TB
    subgraph "Go 异常处理流程"
        A1[Handler 接收请求] --> B1{参数绑定}
        B1 -->|失败| C1[返回 400 + 校验错误]
        B1 -->|成功| D1[调用 Service]
        D1 --> E1{Service 返回 error?}
        E1 -->|是 AppError| F1[提取错误码和消息]
        E1 -->|是 其他 error| G1[包装为 500 错误]
        E1 -->|无错误| H1[转换响应 DTO]
        F1 --> I1[根据错误码映射 HTTP 状态]
        G1 --> I1
        I1 --> J1[返回统一响应结构]
        H1 --> K1[返回成功响应]
        
        L1[Panic 发生] --> M1[Recovery 中间件捕获]
        M1 --> N1[记录堆栈日志]
        N1 --> O1[返回 500 错误]
    end
    
    subgraph "Java 异常处理流程"
        A2[Controller 接收请求] --> B2{@Valid 校验}
        B2 -->|失败| C2[抛出 MethodArgumentNotValidException]
        B2 -->|成功| D2[调用 Service]
        D2 --> E2{抛出异常?}
        E2 -->|BusinessException| F2[GlobalExceptionHandler 捕获]
        E2 -->|其他 Exception| G2[GlobalExceptionHandler 捕获]
        E2 -->|无异常| H2[返回 CommonResponse]
        F2 --> I2[提取 ErrorCode]
        G2 --> J2[记录异常日志]
        J2 --> K2[返回通用 500 错误]
        I2 --> L2[映射 HTTP 状态码]
        L2 --> M2[返回 CommonResponse]
        C2 --> N2[GlobalExceptionHandler 捕获]
        N2 --> O2[提取校验错误信息]
        O2 --> P2[返回 400 + 错误详情]
    end

3.7 事务处理流程图

graph TB
    subgraph "Go 事务处理"
        A1[Service 方法开始] --> B1[txManager.WithTransaction]
        B1 --> C1[开启事务 BeginTx]
        C1 --> D1[将 tx 放入 Context]
        D1 --> E1[执行业务逻辑]
        E1 --> F1[Repository 从 Context 获取 tx]
        F1 --> G1[执行 SQL]
        G1 --> H1{发生错误?}
        H1 -->|是| I1[Rollback]
        H1 -->|否| J1{还有其他操作?}
        J1 -->|是| E1
        J1 -->|否| K1[Commit]
        I1 --> L1[返回错误]
        K1 --> M1[返回成功]
        
        N1[发生 Panic] --> O1[defer 中 Rollback]
        O1 --> P1[重新 Panic]
    end
    
    subgraph "Java 事务处理"
        A2[@Transactional 方法调用] --> B2[TransactionInterceptor 拦截]
        B2 --> C2[获取 TransactionManager]
        C2 --> D2[根据传播行为处理]
        D2 --> E2{REQUIRED?}
        E2 -->|是| F2{已有事务?}
        F2 -->|是| G2[加入现有事务]
        F2 -->|否| H2[创建新事务]
        E2 -->|REQUIRES_NEW| I2[挂起现有事务<br/>创建新事务]
        G2 --> J2[执行业务方法]
        H2 --> J2
        I2 --> J2
        J2 --> K2{抛出异常?}
        K2 -->|是| L2{匹配 rollbackFor?}
        L2 -->|是| M2[Rollback]
        L2 -->|否| N2[Commit]
        K2 -->|否| N2
        M2 --> O2[抛出异常]
        N2 --> P2[返回结果]
    end

3.8 完整系统架构图

graph TB
    subgraph "客户端层"
        Web[Web 浏览器]
        Mobile[移动 App]
        API[第三方 API]
    end
    
    subgraph "网关层"
        LB[负载均衡<br/>Nginx/Cloud LB]
        Gateway[API Gateway<br/>限流/认证/路由]
    end
    
    subgraph "应用层"
        subgraph "Go 服务"
            G_MW[中间件链]
            G_Handler[Handler]
            G_Service[Service]
            G_Repo[Repository]
        end
        
        subgraph "Java 服务"
            J_Filter[Filter Chain]
            J_Controller[Controller]
            J_Service[Service]
            J_Mapper[Mapper/Repository]
        end
    end
    
    subgraph "缓存层"
        Redis[(Redis Cluster)]
        LocalCache[本地缓存<br/>Caffeine/BigCache]
    end
    
    subgraph "数据层"
        MySQL[(MySQL 主从)]
        ES[(Elasticsearch)]
        MongoDB[(MongoDB)]
    end
    
    subgraph "消息队列"
        Kafka[Kafka/RabbitMQ]
    end
    
    subgraph "基础设施"
        Config[配置中心<br/>Nacos/Consul]
        Registry[服务注册<br/>Nacos/Etcd]
        Trace[链路追踪<br/>Jaeger/Zipkin]
        Monitor[监控告警<br/>Prometheus/Grafana]
        Log[日志中心<br/>ELK/Loki]
    end
    
    Web --> LB
    Mobile --> LB
    API --> LB
    LB --> Gateway
    
    Gateway --> G_MW
    Gateway --> J_Filter
    
    G_MW --> G_Handler --> G_Service --> G_Repo
    J_Filter --> J_Controller --> J_Service --> J_Mapper
    
    G_Service --> Redis
    G_Service --> LocalCache
    J_Service --> Redis
    J_Service --> LocalCache
    
    G_Repo --> MySQL
    G_Repo --> ES
    J_Mapper --> MySQL
    J_Mapper --> MongoDB
    
    G_Service --> Kafka
    J_Service --> Kafka
    
    G_MW -.-> Trace
    J_Filter -.-> Trace
    G_Service -.-> Monitor
    J_Service -.-> Monitor
    G_Handler -.-> Log
    J_Controller -.-> Log

3.9 数据流转图

graph LR
    subgraph "Go 数据流转"
        A1[HTTP Request<br/>JSON] -->|Bind| B1[Request DTO<br/>struct]
        B1 -->|Validate| C1[校验后 DTO]
        C1 -->|Transform| D1[Entity/Model]
        D1 -->|GORM| E1[SQL 执行]
        E1 -->|Scan| F1[Entity 结果]
        F1 -->|Convert| G1[Response DTO]
        G1 -->|Marshal| H1[HTTP Response<br/>JSON]
    end
    
    subgraph "Java 数据流转"
        A2[HTTP Request<br/>JSON] -->|Jackson| B2[Request DTO]
        B2 -->|@Valid| C2[校验后 DTO]
        C2 -->|BeanUtils/MapStruct| D2[Entity/PO]
        D2 -->|MyBatis/JPA| E2[SQL 执行]
        E2 -->|ResultMap/@Entity| F2[Entity 结果]
        F2 -->|Converter| G2[Response VO]
        G2 -->|Jackson| H2[HTTP Response<br/>JSON]
    end

3.10 并发处理模型对比

graph TB
    subgraph "Go 并发模型"
        A1[HTTP 请求] --> B1[Goroutine 处理]
        B1 --> C1{需要并发操作?}
        C1 -->|是| D1[启动多个 Goroutine]
        D1 --> E1[通过 Channel 通信]
        E1 --> F1[WaitGroup 等待完成]
        F1 --> G1[合并结果]
        C1 -->|否| G1
        G1 --> H1[返回响应]
        
        I1[Context 传递] -.-> B1
        I1 -.-> D1
        J1[超时控制<br/>context.WithTimeout] -.-> I1
    end
    
    subgraph "Java 并发模型"
        A2[HTTP 请求] --> B2[线程池处理<br/>Tomcat Thread]
        B2 --> C2{需要并发操作?}
        C2 -->|是| D2[CompletableFuture]
        D2 --> E2[异步任务编排]
        E2 --> F2[allOf/join 等待]
        F2 --> G2[合并结果]
        C2 -->|否| G2
        G2 --> H2[返回响应]
        
        I2[@Async 异步方法] -.-> D2
        J2[线程池配置<br/>ThreadPoolExecutor] -.-> I2
    end

四、参数校验

Go 实现

// pkg/validator/validator.go
package validator

import (
    "regexp"
    "github.com/go-playground/validator/v10"
)

var validate *validator.Validate

func init() {
    validate = validator.New()
    // 注册自定义校验器
    validate.RegisterValidation("mobile", validateMobile)
    validate.RegisterValidation("idcard", validateIDCard)
}

func validateMobile(fl validator.FieldLevel) bool {
    mobile := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, mobile)
    return matched
}

func validateIDCard(fl validator.FieldLevel) bool {
    idcard := fl.Field().String()
    matched, _ := regexp.MatchString(`^\d{17}[\dXx]$`, idcard)
    return matched
}

func Validate(s interface{}) error {
    return validate.Struct(s)
}

// 获取第一个校验错误的友好提示
func GetValidationError(err error) string {
    if errs, ok := err.(validator.ValidationErrors); ok {
        for _, e := range errs {
            return formatFieldError(e)
        }
    }
    return err.Error()
}

func formatFieldError(e validator.FieldError) string {
    fieldMessages := map[string]map[string]string{
        "Username": {
            "required": "用户名不能为空",
            "min":      "用户名长度不能少于3个字符",
            "max":      "用户名长度不能超过32个字符",
        },
        "Mobile": {
            "required": "手机号不能为空",
            "mobile":   "手机号格式不正确",
        },
        "Email": {
            "required": "邮箱不能为空",
            "email":    "邮箱格式不正确",
        },
    }
    
    if messages, ok := fieldMessages[e.Field()]; ok {
        if msg, ok := messages[e.Tag()]; ok {
            return msg
        }
    }
    return e.Error()
}
// internal/dto/request/user_request.go
package request

type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=32"`
    Password string `json:"password" binding:"required,min=8,max=64"`
    Email    string `json:"email" binding:"required,email"`
    Mobile   string `json:"mobile" binding:"required,mobile"`
    IDCard   string `json:"id_card" binding:"omitempty,idcard"`
    Age      int    `json:"age" binding:"omitempty,gte=0,lte=150"`
    RoleID   int64  `json:"role_id" binding:"required,gt=0"`
}

type UpdateUserRequest struct {
    Username *string `json:"username" binding:"omitempty,min=3,max=32"`
    Email    *string `json:"email" binding:"omitempty,email"`
    Mobile   *string `json:"mobile" binding:"omitempty,mobile"`
    Status   *int    `json:"status" binding:"omitempty,oneof=0 1 2"`
}

type ListUserRequest struct {
    Page     int    `form:"page" binding:"omitempty,gte=1"`
    PageSize int    `form:"page_size" binding:"omitempty,gte=1,lte=100"`
    Keyword  string `form:"keyword" binding:"omitempty,max=64"`
    Status   *int   `form:"status" binding:"omitempty,oneof=0 1 2"`
    OrderBy  string `form:"order_by" binding:"omitempty,oneof=created_at updated_at"`
    Order    string `form:"order" binding:"omitempty,oneof=asc desc"`
}

func (r *ListUserRequest) SetDefaults() {
    if r.Page <= 0 {
        r.Page = 1
    }
    if r.PageSize <= 0 {
        r.PageSize = 20
    }
    if r.OrderBy == "" {
        r.OrderBy = "created_at"
    }
    if r.Order == "" {
        r.Order = "desc"
    }
}
// internal/handler/user_handler.go
func (h *UserHandler) Create(c *gin.Context) {
    var req request.CreateUserRequest
    
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, validator.GetValidationError(err))
        return
    }
    
    // 业务层额外校验(如唯一性)
    user, err := h.userService.Create(c.Request.Context(), &req)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, dto.ToUserResponse(user))
}

Java 实现

// 自定义校验注解
// validator/Mobile.java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// validator/MobileValidator.java
public class MobileValidator implements ConstraintValidator<Mobile, String> {
    private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isEmpty()) {
            return true; // 由 @NotBlank 处理空值
        }
        return MOBILE_PATTERN.matcher(value).matches();
    }
}

// dto/request/CreateUserRequest.java
@Data
public class CreateUserRequest {
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 32, message = "用户名长度需在3-32个字符之间")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, max = 64, message = "密码长度需在8-64个字符之间")
    private String password;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @NotBlank(message = "手机号不能为空")
    @Mobile
    private String mobile;
    
    @Pattern(regexp = "^\\d{17}[\\dXx]$", message = "身份证号格式不正确")
    private String idCard;
    
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age;
    
    @NotNull(message = "角色ID不能为空")
    @Positive(message = "角色ID必须为正数")
    private Long roleId;
}

// dto/request/UpdateUserRequest.java
@Data
public class UpdateUserRequest {
    
    @Size(min = 3, max = 32, message = "用户名长度需在3-32个字符之间")
    private String username;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Mobile
    private String mobile;
    
    @ValidStatus  // 自定义校验:值必须在 0, 1, 2 中
    private Integer status;
}

// dto/request/ListUserRequest.java
@Data
public class ListUserRequest {
    
    @Min(value = 1, message = "页码最小为1")
    private Integer page = 1;
    
    @Min(value = 1, message = "每页条数最小为1")
    @Max(value = 100, message = "每页条数最大为100")
    private Integer pageSize = 20;
    
    @Size(max = 64, message = "关键词最大64个字符")
    private String keyword;
    
    private Integer status;
    
    @Pattern(regexp = "^(created_at|updated_at)$", message = "排序字段不合法")
    private String orderBy = "created_at";
    
    @Pattern(regexp = "^(asc|desc)$", message = "排序方向不合法")
    private String order = "desc";
}

// controller/UserController.java
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping
    public CommonResponse<UserResponse> create(
            @Valid @RequestBody CreateUserRequest request) {
        User user = userService.create(request);
        return CommonResponse.success(UserResponse.from(user));
    }
    
    @GetMapping
    public CommonResponse<PageResult<UserResponse>> list(
            @Valid ListUserRequest request) {
        return CommonResponse.success(userService.list(request));
    }
}

校验对比表

方面 Go Java
校验库 go-playground/validator Hibernate Validator (JSR-380)
校验位置 Handler 层显式调用 @Valid 注解自动触发
自定义校验 RegisterValidation 函数 @Constraint 注解 + Validator 类
错误消息 手动映射 message 属性声明
分组校验 通过标签组合 groups 分组

五、身份认证

Go 实现(JWT)

// pkg/auth/jwt.go
package auth

import (
    "errors"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

var (
    ErrTokenExpired     = errors.New("token已过期")
    ErrTokenInvalid     = errors.New("token无效")
    ErrTokenMalformed   = errors.New("token格式错误")
)

type Claims struct {
    UserID   int64  `json:"user_id"`
    Username string `json:"username"`
    RoleID   int64  `json:"role_id"`
    jwt.RegisteredClaims
}

type JWTManager struct {
    secretKey     []byte
    accessExpiry  time.Duration
    refreshExpiry time.Duration
    issuer        string
}

func NewJWTManager(secret string, accessExpiry, refreshExpiry time.Duration) *JWTManager {
    return &JWTManager{
        secretKey:     []byte(secret),
        accessExpiry:  accessExpiry,
        refreshExpiry: refreshExpiry,
        issuer:        "myapp",
    }
}

type TokenPair struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    ExpiresIn    int64  `json:"expires_in"`
}

func (m *JWTManager) GenerateTokenPair(userID int64, username string, roleID int64) (*TokenPair, error) {
    now := time.Now()
    
    // Access Token
    accessClaims := Claims{
        UserID:   userID,
        Username: username,
        RoleID:   roleID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(now.Add(m.accessExpiry)),
            IssuedAt:  jwt.NewNumericDate(now),
            NotBefore: jwt.NewNumericDate(now),
            Issuer:    m.issuer,
            Subject:   "access",
        },
    }
    accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
    accessTokenString, err := accessToken.SignedString(m.secretKey)
    if err != nil {
        return nil, err
    }
    
    // Refresh Token
    refreshClaims := Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(now.Add(m.refreshExpiry)),
            IssuedAt:  jwt.NewNumericDate(now),
            Subject:   "refresh",
        },
    }
    refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
    refreshTokenString, err := refreshToken.SignedString(m.secretKey)
    if err != nil {
        return nil, err
    }
    
    return &TokenPair{
        AccessToken:  accessTokenString,
        RefreshToken: refreshTokenString,
        ExpiresIn:    int64(m.accessExpiry.Seconds()),
    }, nil
}

func (m *JWTManager) ParseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return m.secretKey, nil
    })
    
    if err != nil {
        if errors.Is(err, jwt.ErrTokenExpired) {
            return nil, ErrTokenExpired
        }
        if errors.Is(err, jwt.ErrTokenMalformed) {
            return nil, ErrTokenMalformed
        }
        return nil, ErrTokenInvalid
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, ErrTokenInvalid
}
// internal/handler/middleware/auth.go
package middleware

import (
    "strings"
    "github.com/gin-gonic/gin"
)

type contextKey string

const (
    UserIDKey   contextKey = "user_id"
    UsernameKey contextKey = "username"
    RoleIDKey   contextKey = "role_id"
)

func AuthMiddleware(jwtManager *auth.JWTManager, cache cache.TokenCache) gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            response.Unauthorized(c, "缺少认证信息")
            c.Abort()
            return
        }
        
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) != 2 || parts[0] != "Bearer" {
            response.Unauthorized(c, "认证格式错误")
            c.Abort()
            return
        }
        
        tokenString := parts[1]
        
        // 检查 Token 是否在黑名单中(用于登出)
        if cache.IsBlacklisted(c.Request.Context(), tokenString) {
            response.Unauthorized(c, "Token已失效")
            c.Abort()
            return
        }
        
        claims, err := jwtManager.ParseToken(tokenString)
        if err != nil {
            switch err {
            case auth.ErrTokenExpired:
                response.Unauthorized(c, "Token已过期")
            default:
                response.Unauthorized(c, "Token无效")
            }
            c.Abort()
            return
        }
        
        // 存入上下文
        c.Set(string(UserIDKey), claims.UserID)
        c.Set(string(UsernameKey), claims.Username)
        c.Set(string(RoleIDKey), claims.RoleID)
        
        c.Next()
    }
}

// 获取当前用户ID
func GetCurrentUserID(c *gin.Context) int64 {
    if v, exists := c.Get(string(UserIDKey)); exists {
        return v.(int64)
    }
    return 0
}

// 获取当前用户角色
func GetCurrentRoleID(c *gin.Context) int64 {
    if v, exists := c.Get(string(RoleIDKey)); exists {
        return v.(int64)
    }
    return 0
}

Java 实现(Spring Security + JWT)

// util/JwtUtil.java
@Component
public class JwtUtil {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.access-expiry:7200}")
    private long accessExpiry; // 秒
    
    @Value("${jwt.refresh-expiry:604800}")
    private long refreshExpiry; // 秒
    
    @Data
    @AllArgsConstructor
    public static class TokenPair {
        private String accessToken;
        private String refreshToken;
        private Long expiresIn;
    }
    
    public TokenPair generateTokenPair(Long userId, String username, Long roleId) {
        Date now = new Date();
        
        String accessToken = Jwts.builder()
                .setSubject(userId.toString())
                .claim("username", username)
                .claim("role_id", roleId)
                .claim("type", "access")
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + accessExpiry * 1000))
                .signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS256)
                .compact();
        
        String refreshToken = Jwts.builder()
                .setSubject(userId.toString())
                .claim("type", "refresh")
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + refreshExpiry * 1000))
                .signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS256)
                .compact();
        
        return new TokenPair(accessToken, refreshToken, accessExpiry);
    }
    
    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    public Long getUserIdFromToken(String token) {
        return Long.parseLong(parseToken(token).getSubject());
    }
    
    public boolean isTokenExpired(String token) {
        try {
            return parseToken(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }
}

// filter/JwtAuthenticationFilter.java
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;
    private final RedisTemplate<String, Object> redisTemplate;
    
    private static final String TOKEN_BLACKLIST_PREFIX = "token:blacklist:";
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
        
        String token = authHeader.substring(7);
        
        try {
            // 检查黑名单
            if (Boolean.TRUE.equals(redisTemplate.hasKey(TOKEN_BLACKLIST_PREFIX + token))) {
                sendErrorResponse(response, HttpStatus.UNAUTHORIZED, "Token已失效");
                return;
            }
            
            Long userId = jwtUtil.getUserIdFromToken(token);
            
            if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(userId.toString());
                
                if (!jwtUtil.isTokenExpired(token)) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(
                                    userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        } catch (ExpiredJwtException e) {
            sendErrorResponse(response, HttpStatus.UNAUTHORIZED, "Token已过期");
            return;
        } catch (JwtException e) {
            sendErrorResponse(response, HttpStatus.UNAUTHORIZED, "Token无效");
            return;
        }
        
        filterChain.doFilter(request, response);
    }
    
    private void sendErrorResponse(HttpServletResponse response, HttpStatus status, String message)
            throws IOException {
        response.setStatus(status.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(
                String.format("{\"code\":%d,\"message\":\"%s\"}", status.value(), message));
    }
}

// config/SecurityConfig.java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()
                .requestMatchers("/api/v1/public/**").permitAll()
                .requestMatchers("/actuator/health").permitAll()
                .anyRequest().authenticated())
            .addFilterBefore(jwtAuthenticationFilter, 
                UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

认证对比表

方面 Go Java
JWT 库 golang-jwt/jwt jjwt / spring-security-jwt
认证实现 中间件(Middleware) Filter + Spring Security
Token 存储 Context(gin.Context) SecurityContext
黑名单 Redis 手动实现 Redis 手动实现
密码加密 bcrypt 手动调用 PasswordEncoder Bean

六、权限控制

Go 实现(RBAC)

// internal/service/permission_service.go
package service

type PermissionService interface {
    GetRolePermissions(ctx context.Context, roleID int64) ([]string, error)
    HasPermission(ctx context.Context, roleID int64, permCode string) (bool, error)
    CheckPermissions(ctx context.Context, roleID int64, permCodes ...string) (bool, error)
}

type permissionService struct {
    roleRepo       repository.RoleRepository
    permissionRepo repository.PermissionRepository
    cache          cache.Cache
}

func NewPermissionService(
    roleRepo repository.RoleRepository,
    permRepo repository.PermissionRepository,
    cache cache.Cache,
) PermissionService {
    return &permissionService{
        roleRepo:       roleRepo,
        permissionRepo: permRepo,
        cache:          cache,
    }
}

func (s *permissionService) GetRolePermissions(ctx context.Context, roleID int64) ([]string, error) {
    // 先查缓存
    cacheKey := fmt.Sprintf("role:permissions:%d", roleID)
    var permissions []string
    
    if err := s.cache.Get(ctx, cacheKey, &permissions); err == nil {
        return permissions, nil
    }
    
    // 查数据库
    permissions, err := s.permissionRepo.FindCodesByRoleID(ctx, roleID)
    if err != nil {
        return nil, err
    }
    
    // 写入缓存,过期时间 30 分钟
    _ = s.cache.Set(ctx, cacheKey, permissions, 30*time.Minute)
    
    return permissions, nil
}

func (s *permissionService) HasPermission(ctx context.Context, roleID int64, permCode string) (bool, error) {
    permissions, err := s.GetRolePermissions(ctx, roleID)
    if err != nil {
        return false, err
    }
    
    for _, p := range permissions {
        if p == permCode || p == "*" { // * 表示超级权限
            return true, nil
        }
        // 支持通配符匹配:user:* 匹配 user:create, user:read 等
        if strings.HasSuffix(p, ":*") {
            prefix := strings.TrimSuffix(p, "*")
            if strings.HasPrefix(permCode, prefix) {
                return true, nil
            }
        }
    }
    
    return false, nil
}

func (s *permissionService) CheckPermissions(ctx context.Context, roleID int64, permCodes ...string) (bool, error) {
    for _, code := range permCodes {
        has, err := s.HasPermission(ctx, roleID, code)
        if err != nil {
            return false, err
        }
        if !has {
            return false, nil
        }
    }
    return true, nil
}
// internal/handler/middleware/permission.go
package middleware

import (
    "github.com/gin-gonic/gin"
)

// RequirePermission 创建权限校验中间件
func RequirePermission(permService service.PermissionService, permissions ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        roleID := GetCurrentRoleID(c)
        if roleID == 0 {
            response.Forbidden(c, "未获取到角色信息")
            c.Abort()
            return
        }
        
        has, err := permService.CheckPermissions(c.Request.Context(), roleID, permissions...)
        if err != nil {
            response.InternalError(c, "权限校验失败")
            c.Abort()
            return
        }
        
        if !has {
            response.Forbidden(c, "权限不足")
            c.Abort()
            return
        }
        
        c.Next()
    }
}

// RequireAnyPermission 满足任一权限即可
func RequireAnyPermission(permService service.PermissionService, permissions ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        roleID := GetCurrentRoleID(c)
        if roleID == 0 {
            response.Forbidden(c, "未获取到角色信息")
            c.Abort()
            return
        }
        
        for _, perm := range permissions {
            has, err := permService.HasPermission(c.Request.Context(), roleID, perm)
            if err != nil {
                continue
            }
            if has {
                c.Next()
                return
            }
        }
        
        response.Forbidden(c, "权限不足")
        c.Abort()
    }
}

// RequireRole 角色校验
func RequireRole(roles ...string) gin.HandlerFunc {
    roleSet := make(map[string]struct{})
    for _, r := range roles {
        roleSet[r] = struct{}{}
    }
    
    return func(c *gin.Context) {
        roleCode := GetCurrentRoleCode(c)
        if _, ok := roleSet[roleCode]; !ok {
            response.Forbidden(c, "角色权限不足")
            c.Abort()
            return
        }
        c.Next()
    }
}
// 路由注册示例
// cmd/server/router.go
func SetupRouter(
    userHandler *handler.UserHandler,
    orderHandler *handler.OrderHandler,
    permService service.PermissionService,
    jwtManager *auth.JWTManager,
    tokenCache cache.TokenCache,
) *gin.Engine {
    r := gin.New()
    
    // 全局中间件
    r.Use(middleware.Recovery())
    r.Use(middleware.Logger())
    r.Use(middleware.RateLimit(100, time.Minute))
    r.Use(middleware.CORS())
    
    // 公开路由
    public := r.Group("/api/v1")
    {
        public.POST("/auth/login", userHandler.Login)
        public.POST("/auth/register", userHandler.Register)
        public.POST("/auth/refresh", userHandler.RefreshToken)
    }
    
    // 需要认证的路由
    authorized := r.Group("/api/v1")
    authorized.Use(middleware.AuthMiddleware(jwtManager, tokenCache))
    {
        // 用户模块
        users := authorized.Group("/users")
        {
            users.GET("", 
                middleware.RequirePermission(permService, "user:list"),
                userHandler.List)
            users.GET("/:id", 
                middleware.RequirePermission(permService, "user:read"),
                userHandler.GetByID)
            users.POST("", 
                middleware.RequirePermission(permService, "user:create"),
                userHandler.Create)
            users.PUT("/:id", 
                middleware.RequirePermission(permService, "user:update"),
                userHandler.Update)
            users.DELETE("/:id", 
                middleware.RequirePermission(permService, "user:delete"),
                userHandler.Delete)
        }
        
        // 订单模块
        orders := authorized.Group("/orders")
        {
            orders.GET("", 
                middleware.RequireAnyPermission(permService, "order:list", "order:*"),
                orderHandler.List)
            orders.POST("/:id/cancel",
                middleware.RequirePermission(permService, "order:cancel"),
                orderHandler.Cancel)
        }
        
        // 管理员专用
        admin := authorized.Group("/admin")
        admin.Use(middleware.RequireRole("admin", "super_admin"))
        {
            admin.GET("/dashboard", userHandler.Dashboard)
            admin.POST("/users/:id/ban", userHandler.BanUser)
        }
    }
    
    return r
}

Java 实现(Spring Security + 自定义注解)

// annotation/RequirePermission.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String[] value();           // 所需权限码
    Logical logical() default Logical.AND;  // 权限间逻辑关系
}

// annotation/RequireRole.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
    String[] value();
}

// enums/Logical.java
public enum Logical {
    AND,  // 所有权限都需要
    OR    // 满足任一即可
}

// service/PermissionService.java
public interface PermissionService {
    List<String> getRolePermissions(Long roleId);
    boolean hasPermission(Long roleId, String permCode);
    boolean checkPermissions(Long roleId, List<String> permCodes, Logical logical);
}

// service/impl/PermissionServiceImpl.java
@Service
@RequiredArgsConstructor
public class PermissionServiceImpl implements PermissionService {
    
    private final RoleMapper roleMapper;
    private final PermissionMapper permissionMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    
    private static final String ROLE_PERM_KEY = "role:permissions:";
    private static final long CACHE_EXPIRE = 30; // 分钟
    
    @Override
    @SuppressWarnings("unchecked")
    public List<String> getRolePermissions(Long roleId) {
        String cacheKey = ROLE_PERM_KEY + roleId;
        
        // 查缓存
        Object cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return (List<String>) cached;
        }
        
        // 查数据库
        List<String> permissions = permissionMapper.selectCodesByRoleId(roleId);
        
        // 写缓存
        redisTemplate.opsForValue().set(cacheKey, permissions, CACHE_EXPIRE, TimeUnit.MINUTES);
        
        return permissions;
    }
    
    @Override
    public boolean hasPermission(Long roleId, String permCode) {
        List<String> permissions = getRolePermissions(roleId);
        
        for (String p : permissions) {
            if (p.equals(permCode) || p.equals("*")) {
                return true;
            }
            // 通配符支持
            if (p.endsWith(":*")) {
                String prefix = p.substring(0, p.length() - 1);
                if (permCode.startsWith(prefix)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    @Override
    public boolean checkPermissions(Long roleId, List<String> permCodes, Logical logical) {
        if (logical == Logical.AND) {
            return permCodes.stream().allMatch(code -> hasPermission(roleId, code));
        } else {
            return permCodes.stream().anyMatch(code -> hasPermission(roleId, code));
        }
    }
}

// aspect/PermissionAspect.java
@Aspect
@Component
@RequiredArgsConstructor
@Order(2) // 在认证之后执行
public class PermissionAspect {
    
    private final PermissionService permissionService;
    
    @Before("@annotation(requirePermission)")
    public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
        Long roleId = getCurrentRoleId();
        if (roleId == null) {
            throw new ForbiddenException("未获取到角色信息");
        }
        
        List<String> required = Arrays.asList(requirePermission.value());
        boolean hasPermission = permissionService.checkPermissions(
                roleId, required, requirePermission.logical());
        
        if (!hasPermission) {
            throw new ForbiddenException("权限不足");
        }
    }
    
    @Before("@annotation(requireRole)")
    public void checkRole(JoinPoint joinPoint, RequireRole requireRole) {
        String currentRole = getCurrentRoleCode();
        if (currentRole == null) {
            throw new ForbiddenException("未获取到角色信息");
        }
        
        Set<String> allowedRoles = Set.of(requireRole.value());
        if (!allowedRoles.contains(currentRole)) {
            throw new ForbiddenException("角色权限不足");
        }
    }
    
    private Long getCurrentRoleId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() instanceof CustomUserDetails) {
            return ((CustomUserDetails) auth.getPrincipal()).getRoleId();
        }
        return null;
    }
    
    private String getCurrentRoleCode() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() instanceof CustomUserDetails) {
            return ((CustomUserDetails) auth.getPrincipal()).getRoleCode();
        }
        return null;
    }
}

// controller/UserController.java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @GetMapping
    @RequirePermission("user:list")
    public CommonResponse<PageResult<UserResponse>> list(@Valid ListUserRequest request) {
        return CommonResponse.success(userService.list(request));
    }
    
    @GetMapping("/{id}")
    @RequirePermission("user:read")
    public CommonResponse<UserResponse> getById(@PathVariable Long id) {
        return CommonResponse.success(userService.getById(id));
    }
    
    @PostMapping
    @RequirePermission("user:create")
    public CommonResponse<UserResponse> create(@Valid @RequestBody CreateUserRequest request) {
        return CommonResponse.success(userService.create(request));
    }
    
    @PutMapping("/{id}")
    @RequirePermission("user:update")
    public CommonResponse<Void> update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        userService.update(id, request);
        return CommonResponse.success();
    }
    
    @DeleteMapping("/{id}")
    @RequirePermission("user:delete")
    public CommonResponse<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return CommonResponse.success();
    }
    
    // 需要多个权限(AND 逻辑)
    @PostMapping("/{id}/reset-password")
    @RequirePermission(value = {"user:update", "user:sensitive"}, logical = Logical.AND)
    public CommonResponse<Void> resetPassword(@PathVariable Long id) {
        userService.resetPassword(id);
        return CommonResponse.success();
    }
    
    // 满足任一权限即可(OR 逻辑)
    @GetMapping("/{id}/detail")
    @RequirePermission(value = {"user:read", "user:admin"}, logical = Logical.OR)
    public CommonResponse<UserDetailResponse> getDetail(@PathVariable Long id) {
        return CommonResponse.success(userService.getDetail(id));
    }
}

// controller/AdminController.java
@RestController
@RequestMapping("/api/v1/admin")
@RequireRole({"admin", "super_admin"})  // 类级别角色限制
public class AdminController {
    
    @GetMapping("/dashboard")
    public CommonResponse<DashboardVO> dashboard() {
        // ...
    }
    
    @PostMapping("/users/{id}/ban")
    @RequirePermission("user:ban")  // 方法级别额外权限
    public CommonResponse<Void> banUser(@PathVariable Long id) {
        // ...
    }
}

权限对比表

方面 Go Java
权限检查 中间件函数 AOP 切面 + 注解
权限声明 路由注册时指定 方法/类注解
权限缓存 手动实现 手动实现
通配符 手动解析 手动解析
逻辑组合 不同中间件函数 注解属性

七、缓存策略

Go 实现

// pkg/cache/cache.go
package cache

import (
    "context"
    "encoding/json"
    "time"
    "github.com/redis/go-redis/v9"
)

type Cache interface {
    Get(ctx context.Context, key string, dest interface{}) error
    Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
    Delete(ctx context.Context, keys ...string) error
    Exists(ctx context.Context, key string) (bool, error)
    SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) (bool, error)
    Incr(ctx context.Context, key string) (int64, error)
    Expire(ctx context.Context, key string, expiration time.Duration) error
}

type redisCache struct {
    client *redis.Client
}

func NewRedisCache(client *redis.Client) Cache {
    return &redisCache{client: client}
}

func (c *redisCache) Get(ctx context.Context, key string, dest interface{}) error {
    data, err := c.client.Get(ctx, key).Bytes()
    if err != nil {
        if err == redis.Nil {
            return ErrCacheMiss
        }
        return err
    }
    return json.Unmarshal(data, dest)
}

func (c *redisCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
    data, err := json.Marshal(value)
    if err != nil {
        return err
    }
    return c.client.Set(ctx, key, data, expiration).Err()
}

func (c *redisCache) Delete(ctx context.Context, keys ...string) error {
    return c.client.Del(ctx, keys...).Err()
}

var ErrCacheMiss = errors.New("cache miss")
// internal/repository/cache/user_cache.go
package cache

import (
    "context"
    "fmt"
    "time"
)

type UserCache interface {
    GetUser(ctx context.Context, id int64) (*entity.User, error)
    SetUser(ctx context.Context, user *entity.User) error
    DeleteUser(ctx context.Context, id int64) error
    GetUserByEmail(ctx context.Context, email string) (*entity.User, error)
    SetUserByEmail(ctx context.Context, user *entity.User) error
}

type userCache struct {
    cache      cache.Cache
    expiration time.Duration
}

func NewUserCache(c cache.Cache) UserCache {
    return &userCache{
        cache:      c,
        expiration: 30 * time.Minute,
    }
}

func (c *userCache) userKey(id int64) string {
    return fmt.Sprintf("user:id:%d", id)
}

func (c *userCache) userEmailKey(email string) string {
    return fmt.Sprintf("user:email:%s", email)
}

func (c *userCache) GetUser(ctx context.Context, id int64) (*entity.User, error) {
    var user entity.User
    err := c.cache.Get(ctx, c.userKey(id), &user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func (c *userCache) SetUser(ctx context.Context, user *entity.User) error {
    return c.cache.Set(ctx, c.userKey(user.ID), user, c.expiration)
}

func (c *userCache) DeleteUser(ctx context.Context, id int64) error {
    return c.cache.Delete(ctx, c.userKey(id))
}
// internal/service/user_service.go - 带缓存的服务层
package service

type userService struct {
    userRepo  repository.UserRepository
    userCache cache.UserCache
    logger    *zap.Logger
}

func (s *userService) GetByID(ctx context.Context, id int64) (*entity.User, error) {
    // 1. 查缓存
    user, err := s.userCache.GetUser(ctx, id)
    if err == nil {
        s.logger.Debug("cache hit", zap.Int64("user_id", id))
        return user, nil
    }
    
    if err != cache.ErrCacheMiss {
        s.logger.Warn("cache error", zap.Error(err))
    }
    
    // 2. 查数据库
    user, err = s.userRepo.FindByID(ctx, id)
    if err != nil {
        return nil, err
    }
    if user == nil {
        return nil, ErrUserNotFound
    }
    
    // 3. 写入缓存(异步)
    go func() {
        if err := s.userCache.SetUser(context.Background(), user); err != nil {
            s.logger.Warn("failed to set cache", zap.Error(err))
        }
    }()
    
    return user, nil
}

func (s *userService) Update(ctx context.Context, id int64, req *dto.UpdateUserRequest) error {
    // 1. 更新数据库
    if err := s.userRepo.Update(ctx, id, req); err != nil {
        return err
    }
    
    // 2. 删除缓存(保证一致性)
    if err := s.userCache.DeleteUser(ctx, id); err != nil {
        s.logger.Warn("failed to delete cache", zap.Error(err))
    }
    
    return nil
}

func (s *userService) Delete(ctx context.Context, id int64) error {
    // 1. 删除数据库
    if err := s.userRepo.Delete(ctx, id); err != nil {
        return err
    }
    
    // 2. 删除缓存
    if err := s.userCache.DeleteUser(ctx, id); err != nil {
        s.logger.Warn("failed to delete cache", zap.Error(err))
    }
    
    return nil
}
// 防缓存穿透 - 布隆过滤器 + 空值缓存
// internal/service/user_service.go

func (s *userService) GetByIDWithProtection(ctx context.Context, id int64) (*entity.User, error) {
    // 1. 布隆过滤器检查(可选)
    if s.bloomFilter != nil && !s.bloomFilter.Test([]byte(fmt.Sprintf("user:%d", id))) {
        return nil, ErrUserNotFound
    }
    
    // 2. 查缓存
    user, err := s.userCache.GetUser(ctx, id)
    if err == nil {
        // 检查是否为空值缓存
        if user.ID == 0 {
            return nil, ErrUserNotFound
        }
        return user, nil
    }
    
    // 3. 分布式锁防止缓存击穿
    lockKey := fmt.Sprintf("lock:user:%d", id)
    locked, err := s.cache.SetNX(ctx, lockKey, "1", 5*time.Second)
    if err != nil {
        return nil, err
    }
    
    if !locked {
        // 未获取到锁,等待后重试
        time.Sleep(100 * time.Millisecond)
        return s.GetByIDWithProtection(ctx, id)
    }
    defer s.cache.Delete(ctx, lockKey)
    
    // 4. 再次检查缓存(双重检查)
    user, err = s.userCache.GetUser(ctx, id)
    if err == nil {
        return user, nil
    }
    
    // 5. 查数据库
    user, err = s.userRepo.FindByID(ctx, id)
    if err != nil {
        return nil, err
    }
    
    if user == nil {
        // 缓存空值,防止缓存穿透
        _ = s.userCache.SetUser(ctx, &entity.User{})
        return nil, ErrUserNotFound
    }
    
    // 6. 写入缓存
    _ = s.userCache.SetUser(ctx, user)
    
    return user, nil
}

Java 实现(Spring Cache + Redis)

// config/RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // Key 序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        // Value 序列化
        Jackson2JsonRedisSerializer<Object> jsonSerializer = 
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL);
        jsonSerializer.setObjectMapper(om);
        
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeKeysWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        new StringRedisSerializer()))
                .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
        
        // 不同缓存空间配置不同过期时间
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("user", config.entryTtl(Duration.ofMinutes(30)));
        configMap.put("order", config.entryTtl(Duration.ofMinutes(10)));
        configMap.put("permission", config.entryTtl(Duration.ofHours(1)));
        
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(configMap)
                .build();
    }
}

// service/impl/UserServiceImpl.java
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
    
    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    
    private static final String USER_CACHE_KEY = "user:id:";
    private static final long CACHE_EXPIRE = 30; // 分钟
    
    @Override
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public User getById(Long id) {
        log.debug("Cache miss, querying database for user: {}", id);
        return userMapper.selectById(id);
    }
    
    @Override
    @CachePut(value = "user", key = "#result.id")
    @Transactional
    public User create(CreateUserRequest request) {
        User user = new User();
        BeanUtils.copyProperties(request, user);
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setCreatedAt(LocalDateTime.now());
        userMapper.insert(user);
        return user;
    }
    
    @Override
    @CacheEvict(value = "user", key = "#id")
    @Transactional
    public void update(Long id, UpdateUserRequest request) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new NotFoundException("用户不存在");
        }
        
        if (request.getUsername() != null) {
            user.setUsername(request.getUsername());
        }
        if (request.getEmail() != null) {
            user.setEmail(request.getEmail());
        }
        user.setUpdatedAt(LocalDateTime.now());
        
        userMapper.updateById(user);
    }
    
    @Override
    @CacheEvict(value = "user", key = "#id")
    @Transactional
    public void delete(Long id) {
        userMapper.deleteById(id);
    }
    
    // 批量删除缓存
    @Override
    @CacheEvict(value = "user", allEntries = true)
    @Transactional
    public void batchDelete(List<Long> ids) {
        userMapper.deleteBatchIds(ids);
    }
    
    // 手动缓存控制 - 带穿透保护
    @Override
    public User getByIdWithProtection(Long id) {
        String cacheKey = USER_CACHE_KEY + id;
        
        // 1. 查缓存
        Object cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            if (cached instanceof NullValue) {
                return null; // 空值缓存
            }
            return (User) cached;
        }
        
        // 2. 分布式锁
        String lockKey = "lock:user:" + id;
        Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, "1", Duration.ofSeconds(5));
        
        if (Boolean.FALSE.equals(locked)) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return getByIdWithProtection(id);
        }
        
        try {
            // 3. 双重检查
            cached = redisTemplate.opsForValue().get(cacheKey);
            if (cached != null) {
                return cached instanceof NullValue ? null : (User) cached;
            }
            
            // 4. 查数据库
            User user = userMapper.selectById(id);
            
            // 5. 写缓存
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, user, 
                        CACHE_EXPIRE, TimeUnit.MINUTES);
            } else {
                // 缓存空值,防止穿透
                redisTemplate.opsForValue().set(cacheKey, NullValue.INSTANCE, 
                        5, TimeUnit.MINUTES);
            }
            
            return user;
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    // 空值标记
    private static class NullValue implements Serializable {
        static final NullValue INSTANCE = new NullValue();
        private static final long serialVersionUID = 1L;
    }
}

// 自定义缓存注解 - 支持更复杂的缓存策略
// annotation/CacheableWithLock.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheableWithLock {
    String cacheName();
    String key();
    long expire() default 30;  // 分钟
    long lockTimeout() default 5;  // 秒
    boolean cacheNull() default true;
}

// aspect/CacheAspect.java
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class CacheAspect {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final SpelExpressionParser parser = new SpelExpressionParser();
    
    @Around("@annotation(cacheableWithLock)")
    public Object around(ProceedingJoinPoint pjp, CacheableWithLock cacheableWithLock) 
            throws Throwable {
        
        String cacheKey = buildCacheKey(pjp, cacheableWithLock);
        
        // 1. 查缓存
        Object cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            if (cached instanceof NullValue) {
                return null;
            }
            log.debug("Cache hit: {}", cacheKey);
            return cached;
        }
        
        // 2. 加锁
        String lockKey = "lock:" + cacheKey;
        Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, "1", 
                    Duration.ofSeconds(cacheableWithLock.lockTimeout()));
        
        if (Boolean.FALSE.equals(locked)) {
            Thread.sleep(100);
            return around(pjp, cacheableWithLock); // 重试
        }
        
        try {
            // 3. 双重检查
            cached = redisTemplate.opsForValue().get(cacheKey);
            if (cached != null) {
                return cached instanceof NullValue ? null : cached;
            }
            
            // 4. 执行方法
            Object result = pjp.proceed();
            
            // 5. 写缓存
            if (result != null) {
                redisTemplate.opsForValue().set(cacheKey, result,
                        Duration.ofMinutes(cacheableWithLock.expire()));
            } else if (cacheableWithLock.cacheNull()) {
                redisTemplate.opsForValue().set(cacheKey, NullValue.INSTANCE,
                        Duration.ofMinutes(5));
            }
            
            return result;
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    private String buildCacheKey(ProceedingJoinPoint pjp, CacheableWithLock annotation) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        EvaluationContext context = new StandardEvaluationContext();
        
        String[] paramNames = signature.getParameterNames();
        Object[] args = pjp.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        
        String keyExpression = annotation.key();
        String key = parser.parseExpression(keyExpression)
                .getValue(context, String.class);
        
        return annotation.cacheName() + ":" + key;
    }
    
    private static class NullValue implements Serializable {
        static final NullValue INSTANCE = new NullValue();
    }
}

缓存对比表

方面 Go Java
缓存框架 go-redis + 手动封装 Spring Cache + Redis
缓存注解 无(手动实现) @Cacheable/@CachePut/@CacheEvict
序列化 json.Marshal Jackson/GenericJackson2Json
穿透保护 手动实现空值缓存 + 分布式锁 手动实现或自定义切面
缓存预热 启动时手动加载 @PostConstruct / ApplicationRunner
多级缓存 手动实现本地 + Redis Caffeine + Redis 组合

八、日志系统

Go 实现(Zap)

// pkg/logger/logger.go
package logger

import (
    "os"
    "time"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
)

var Logger *zap.Logger

type Config struct {
    Level      string
    Filename   string
    MaxSize    int  // MB
    MaxBackups int
    MaxAge     int  // days
    Compress   bool
    Console    bool
}

func Init(cfg *Config) error {
    level := parseLevel(cfg.Level)
    
    // 编码器配置
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "timestamp",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        FunctionKey:    zapcore.OmitKey,
        MessageKey:     "message",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }
    
    var cores []zapcore.Core
    
    // 文件输出
    if cfg.Filename != "" {
        fileWriter := &lumberjack.Logger{
            Filename:   cfg.Filename,
            MaxSize:    cfg.MaxSize,
            MaxBackups: cfg.MaxBackups,
            MaxAge:     cfg.MaxAge,
            Compress:   cfg.Compress,
        }
        fileCore := zapcore.NewCore(
            zapcore.NewJSONEncoder(encoderConfig),
            zapcore.AddSync(fileWriter),
            level,
        )
        cores = append(cores, fileCore)
    }
    
    // 控制台输出
    if cfg.Console {
        consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
        consoleCore := zapcore.NewCore(
            consoleEncoder,
            zapcore.AddSync(os.Stdout),
            level,
        )
        cores = append(cores, consoleCore)
    }
    
    core := zapcore.NewTee(cores...)
    Logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
    
    return nil
}

func parseLevel(level string) zapcore.Level {
    switch level {
    case "debug":
        return zapcore.DebugLevel
    case "info":
        return zapcore.InfoLevel
    case "warn":
        return zapcore.WarnLevel
    case "error":
        return zapcore.ErrorLevel
    default:
        return zapcore.InfoLevel
    }
}

// 便捷方法
func Debug(msg string, fields ...zap.Field) { Logger.Debug(msg, fields...) }
func Info(msg string, fields ...zap.Field)  { Logger.Info(msg, fields...) }
func Warn(msg string, fields ...zap.Field)  { Logger.Warn(msg, fields...) }
func Error(msg string, fields ...zap.Field) { Logger.Error(msg, fields...) }

func With(fields ...zap.Field) *zap.Logger {
    return Logger.With(fields...)
}
// internal/handler/middleware/logger.go
package middleware

import (
    "bytes"
    "io"
    "time"
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
)

type responseWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w *responseWriter) Write(b []byte) (int, error) {
    w.body.Write(b)
    return w.ResponseWriter.Write(b)
}

func Logger(log *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        query := c.Request.URL.RawQuery
        
        // 读取请求体
        var requestBody []byte
        if c.Request.Body != nil {
            requestBody, _ = io.ReadAll(c.Request.Body)
            c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
        }
        
        // 包装 ResponseWriter 以捕获响应
        blw := &responseWriter{
            ResponseWriter: c.Writer,
            body:           bytes.NewBuffer(nil),
        }
        c.Writer = blw
        
        // 生成请求 ID
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = generateRequestID()
        }
        c.Set("request_id", requestID)
        c.Header("X-Request-ID", requestID)
        
        // 执行请求
        c.Next()
        
        // 计算耗时
        latency := time.Since(start)
        
        // 构建日志字段
        fields := []zap.Field{
            zap.String("request_id", requestID),
            zap.String("method", c.Request.Method),
            zap.String("path", path),
            zap.String("query", query),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("latency", latency),
            zap.String("client_ip", c.ClientIP()),
            zap.String("user_agent", c.Request.UserAgent()),
        }
        
        // 添加用户信息(如果已认证)
        if userID, exists := c.Get("user_id"); exists {
            fields = append(fields, zap.Any("user_id", userID))
        }
        
        // 记录请求体(敏感信息脱敏)
        if len(requestBody) > 0 && len(requestBody) < 10240 {
            sanitized := sanitizeBody(requestBody)
            fields = append(fields, zap.String("request_body", sanitized))
        }
        
        // 错误请求记录响应体
        if c.Writer.Status() >= 400 {
            fields = append(fields, zap.String("response_body", blw.body.String()))
        }
        
        // 根据状态码选择日志级别
        switch {
        case c.Writer.Status() >= 500:
            log.Error("Server error", fields...)
        case c.Writer.Status() >= 400:
            log.Warn("Client error", fields...)
        default:
            log.Info("Request completed", fields...)
        }
    }
}

// 敏感字段脱敏
func sanitizeBody(body []byte) string {
    var data map[string]interface{}
    if err := json.Unmarshal(body, &data); err != nil {
        return "[non-json body]"
    }
    
    sensitiveFields := []string{"password", "token", "secret", "card_number"}
    for _, field := range sensitiveFields {
        if _, ok := data[field]; ok {
            data[field] = "***"
        }
    }
    
    sanitized, _ := json.Marshal(data)
    return string(sanitized)
}

func generateRequestID() string {
    return fmt.Sprintf("%d-%s", time.Now().UnixNano(), randomString(8))
}
// 操作审计日志
// internal/service/audit_service.go
package service

type AuditLog struct {
    ID         int64
    UserID     int64
    Username   string
    Action     string    // create, update, delete, login, logout
    Resource   string    // user, order, product
    ResourceID string
    OldValue   string    // JSON
    NewValue   string    // JSON
    IP         string
    UserAgent  string
    RequestID  string
    CreatedAt  time.Time
}

type AuditService interface {
    Log(ctx context.Context, log *AuditLog) error
    Query(ctx context.Context, query *AuditQuery) ([]*AuditLog, int64, error)
}

type auditService struct {
    repo   repository.AuditRepository
    logger *zap.Logger
}

func (s *auditService) Log(ctx context.Context, log *AuditLog) error {
    log.CreatedAt = time.Now()
    
    // 异步写入数据库
    go func() {
        if err := s.repo.Create(context.Background(), log); err != nil {
            s.logger.Error("failed to save audit log",
                zap.Error(err),
                zap.Any("audit", log))
        }
    }()
    
    // 同步写入日志文件(确保不丢失)
    s.logger.Info("audit",
        zap.Int64("user_id", log.UserID),
        zap.String("action", log.Action),
        zap.String("resource", log.Resource),
        zap.String("resource_id", log.ResourceID),
        zap.String("request_id", log.RequestID))
    
    return nil
}

Java 实现(Logback + AOP)

// resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
    
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- JSON 格式文件输出(用于 ELK) -->
    <appender name="FILE_JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>request_id</includeMdcKeyName>
            <includeMdcKeyName>user_id</includeMdcKeyName>
        </encoder>
    </appender>
    
    <!-- 审计日志单独文件 -->
    <appender name="AUDIT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/audit.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/audit.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
    </appender>
    
    <!-- 审计日志 Logger -->
    <logger name="AUDIT" level="INFO" additivity="false">
        <appender-ref ref="AUDIT_FILE"/>
    </logger>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE_JSON"/>
    </root>
</configuration>
// filter/RequestLoggingFilter.java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class RequestLoggingFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        
        long startTime = System.currentTimeMillis();
        
        // 生成 Request ID
        String requestId = request.getHeader("X-Request-ID");
        if (requestId == null || requestId.isEmpty()) {
            requestId = UUID.randomUUID().toString();
        }
        
        // 放入 MDC(日志上下文)
        MDC.put("request_id", requestId);
        response.setHeader("X-Request-ID", requestId);
        
        // 包装请求以支持多次读取
        ContentCachingRequestWrapper wrappedRequest = 
                new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = 
                new ContentCachingResponseWrapper(response);
        
        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            logRequest(wrappedRequest, wrappedResponse, duration);
            wrappedResponse.copyBodyToResponse();
            MDC.clear();
        }
    }
    
    private void logRequest(ContentCachingRequestWrapper request,
                           ContentCachingResponseWrapper response,
                           long duration) {
        
        int status = response.getStatus();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String query = request.getQueryString();
        String clientIp = getClientIp(request);
        
        // 构建日志消息
        Map<String, Object> logData = new LinkedHashMap<>();
        logData.put("method", method);
        logData.put("uri", uri);
        logData.put("query", query);
        logData.put("status", status);
        logData.put("duration_ms", duration);
        logData.put("client_ip", clientIp);
        logData.put("user_agent", request.getHeader("User-Agent"));
        
        // 请求体(敏感信息脱敏)
        String requestBody = getRequestBody(request);
        if (requestBody != null && !requestBody.isEmpty()) {
            logData.put("request_body", sanitize(requestBody));
        }
        
        // 错误响应记录响应体
        if (status >= 400) {
            String responseBody = getResponseBody(response);
            logData.put("response_body", responseBody);
        }
        
        if (status >= 500) {
            log.error("Request completed: {}", logData);
        } else if (status >= 400) {
            log.warn("Request completed: {}", logData);
        } else {
            log.info("Request completed: {}", logData);
        }
    }
    
    private String sanitize(String body) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> data = mapper.readValue(body, Map.class);
            
            List<String> sensitiveFields = Arrays.asList(
                    "password", "token", "secret", "card_number", "cvv");
            
            for (String field : sensitiveFields) {
                if (data.containsKey(field)) {
                    data.put(field, "***");
                }
            }
            
            return mapper.writeValueAsString(data);
        } catch (Exception e) {
            return body;
        }
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip.split(",")[0].trim();
    }
}
// aspect/AuditLogAspect.java
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class AuditLogAspect {
    
    private final AuditLogService auditLogService;
    private final ObjectMapper objectMapper;
    
    private static final Logger AUDIT_LOGGER = LoggerFactory.getLogger("AUDIT");
    
    @Around("@annotation(auditLog)")
    public Object around(ProceedingJoinPoint pjp, AuditLog auditLog) throws Throwable {
        // 获取旧值(更新/删除操作)
        String oldValue = null;
        if (auditLog.action() == AuditAction.UPDATE || 
            auditLog.action() == AuditAction.DELETE) {
            oldValue = getOldValue(pjp, auditLog);
        }
        
        // 执行方法
        Object result = pjp.proceed();
        
        // 记录审计日志
        try {
            AuditLogEntity entity = buildAuditLog(pjp, auditLog, oldValue, result);
            
            // 异步保存到数据库
            auditLogService.saveAsync(entity);
            
            // 同步写入日志文件
            AUDIT_LOGGER.info(objectMapper.writeValueAsString(entity));
        } catch (Exception e) {
            log.error("Failed to save audit log", e);
        }
        
        return result;
    }
    
    private AuditLogEntity buildAuditLog(ProceedingJoinPoint pjp, 
                                          AuditLog annotation,
                                          String oldValue, 
                                          Object result) {
        
        HttpServletRequest request = getCurrentRequest();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        AuditLogEntity entity = new AuditLogEntity();
        entity.setAction(annotation.action().name());
        entity.setResource(annotation.resource());
        entity.setResourceId(extractResourceId(pjp, annotation));
        entity.setOldValue(oldValue);
        entity.setNewValue(result != null ? toJson(result) : null);
        entity.setRequestId(MDC.get("request_id"));
        entity.setCreatedAt(LocalDateTime.now());
        
        if (auth != null && auth.getPrincipal() instanceof CustomUserDetails) {
            CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
            entity.setUserId(user.getId());
            entity.setUsername(user.getUsername());
        }
        
        if (request != null) {
            entity.setIp(getClientIp(request));
            entity.setUserAgent(request.getHeader("User-Agent"));
        }
        
        return entity;
    }
}

// annotation/AuditLog.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
    AuditAction action();
    String resource();
    String resourceIdExpr() default "";  // SpEL 表达式
}

// enums/AuditAction.java
public enum AuditAction {
    CREATE, UPDATE, DELETE, LOGIN, LOGOUT, EXPORT, IMPORT
}

// 使用示例
@Service
public class UserServiceImpl implements UserService {
    
    @Override
    @AuditLog(action = AuditAction.CREATE, resource = "user")
    @Transactional
    public User create(CreateUserRequest request) {
        // ...
    }
    
    @Override
    @AuditLog(action = AuditAction.UPDATE, resource = "user", resourceIdExpr = "#id")
    @Transactional
    public void update(Long id, UpdateUserRequest request) {
        // ...
    }
    
    @Override
    @AuditLog(action = AuditAction.DELETE, resource = "user", resourceIdExpr = "#id")
    @Transactional
    public void delete(Long id) {
        // ...
    }
}

日志对比表

方面 Go Java
日志库 Zap / Zerolog Logback / Log4j2
结构化日志 原生支持 Logstash Encoder
日志上下文 Context 传递 MDC(Mapped Diagnostic Context)
请求日志 中间件实现 Filter / Interceptor
审计日志 手动调用服务 AOP + 注解
日志轮转 lumberjack Logback RollingPolicy
敏感信息 手动脱敏 手动脱敏

九、异常处理与统一响应

Go 实现

// internal/errors/errors.go
package errors

import (
    "fmt"
    "net/http"
)

type ErrorCode int

const (
    CodeSuccess          ErrorCode = 0
    CodeBadRequest       ErrorCode = 400
    CodeUnauthorized     ErrorCode = 401
    CodeForbidden        ErrorCode = 403
    CodeNotFound         ErrorCode = 404
    CodeConflict         ErrorCode = 409
    CodeTooManyRequests  ErrorCode = 429
    CodeInternalError    ErrorCode = 500
    CodeServiceUnavailable ErrorCode = 503
    
    // 业务错误码 (10000+)
    CodeUserNotFound     ErrorCode = 10001
    CodeUserExists       ErrorCode = 10002
    CodePasswordWrong    ErrorCode = 10003
    CodeTokenExpired     ErrorCode = 10004
    CodeTokenInvalid     ErrorCode = 10005
    CodePermissionDenied ErrorCode = 10006
    CodeOrderNotFound    ErrorCode = 20001
    CodeOrderCancelled   ErrorCode = 20002
    CodeInsufficientStock ErrorCode = 20003
)

var codeMessages = map[ErrorCode]string{
    CodeSuccess:           "成功",
    CodeBadRequest:        "请求参数错误",
    CodeUnauthorized:      "未授权",
    CodeForbidden:         "禁止访问",
    CodeNotFound:          "资源不存在",
    CodeConflict:          "资源冲突",
    CodeTooManyRequests:   "请求过于频繁",
    CodeInternalError:     "服务器内部错误",
    CodeServiceUnavailable: "服务暂不可用",
    CodeUserNotFound:      "用户不存在",
    CodeUserExists:        "用户已存在",
    CodePasswordWrong:     "密码错误",
    CodeTokenExpired:      "Token已过期",
    CodeTokenInvalid:      "Token无效",
    CodePermissionDenied:  "权限不足",
    CodeOrderNotFound:     "订单不存在",
    CodeOrderCancelled:    "订单已取消",
    CodeInsufficientStock: "库存不足",
}

type AppError struct {
    Code     ErrorCode
    Message  string
    Err      error  // 原始错误(不对外暴露)
    Data     interface{}
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

func (e *AppError) Unwrap() error {
    return e.Err
}

func New(code ErrorCode) *AppError {
    return &AppError{
        Code:    code,
        Message: codeMessages[code],
    }
}

func NewWithMessage(code ErrorCode, message string) *AppError {
    return &AppError{
        Code:    code,
        Message: message,
    }
}

func Wrap(code ErrorCode, err error) *AppError {
    return &AppError{
        Code:    code,
        Message: codeMessages[code],
        Err:     err,
    }
}

func WrapWithMessage(code ErrorCode, message string, err error) *AppError {
    return &AppError{
        Code:    code,
        Message: message,
        Err:     err,
    }
}

// 判断错误类型
func IsAppError(err error) bool {
    _, ok := err.(*AppError)
    return ok
}

func GetCode(err error) ErrorCode {
    if appErr, ok := err.(*AppError); ok {
        return appErr.Code
    }
    return CodeInternalError
}

func GetHTTPStatus(code ErrorCode) int {
    switch {
    case code == CodeSuccess:
        return http.StatusOK
    case code == CodeBadRequest:
        return http.StatusBadRequest
    case code == CodeUnauthorized, code == CodeTokenExpired, code == CodeTokenInvalid:
        return http.StatusUnauthorized
    case code == CodeForbidden, code == CodePermissionDenied:
        return http.StatusForbidden
    case code == CodeNotFound, code == CodeUserNotFound, code == CodeOrderNotFound:
        return http.StatusNotFound
    case code == CodeConflict, code == CodeUserExists:
        return http.StatusConflict
    case code == CodeTooManyRequests:
        return http.StatusTooManyRequests
    case code >= 10000:
        return http.StatusBadRequest  // 业务错误返回 400
    default:
        return http.StatusInternalServerError
    }
}

// 预定义错误
var (
    ErrUserNotFound     = New(CodeUserNotFound)
    ErrUserExists       = New(CodeUserExists)
    ErrPasswordWrong    = New(CodePasswordWrong)
    ErrTokenExpired     = New(CodeTokenExpired)
    ErrTokenInvalid     = New(CodeTokenInvalid)
    ErrPermissionDenied = New(CodePermissionDenied)
    ErrOrderNotFound    = New(CodeOrderNotFound)
    ErrInsufficientStock = New(CodeInsufficientStock)
)
// internal/dto/response/response.go
package response

type PageData struct {
    List     interface{} `json:"list"`
    Total    int64       `json:"total"`
    Page     int         `json:"page"`
    PageSize int         `json:"page_size"`
    Pages    int         `json:"pages"`
}

func NewPageData(list interface{}, total int64, page, pageSize int) *PageData {
    pages := int(total) / pageSize
    if int(total)%pageSize > 0 {
        pages++
    }
    return &PageData{
        List:     list,
        Total:    total,
        Page:     page,
        PageSize: pageSize,
        Pages:    pages,
    }
}

func Success(c *gin.Context, data interface{}) {
    requestID, _ := c.Get("request_id")
    c.JSON(200, Response{
        Code:      0,
        Message:   "success",
        Data:      data,
        RequestID: requestID.(string),
    })
}

func SuccessWithMessage(c *gin.Context, message string, data interface{}) {
    requestID, _ := c.Get("request_id")
    c.JSON(200, Response{
        Code:      0,
        Message:   message,
        Data:      data,
        RequestID: requestID.(string),
    })
}

func Error(c *gin.Context, err error) {
    requestID, _ := c.Get("request_id")
    
    var appErr *errors.AppError
    if e, ok := err.(*errors.AppError); ok {
        appErr = e
    } else {
        appErr = errors.Wrap(errors.CodeInternalError, err)
    }
    
    httpStatus := errors.GetHTTPStatus(appErr.Code)
    
    c.JSON(httpStatus, Response{
        Code:      int(appErr.Code),
        Message:   appErr.Message,
        RequestID: requestID.(string),
    })
}

func BadRequest(c *gin.Context, message string) {
    requestID, _ := c.Get("request_id")
    c.JSON(400, Response{
        Code:      int(errors.CodeBadRequest),
        Message:   message,
        RequestID: requestID.(string),
    })
}

func Unauthorized(c *gin.Context, message string) {
    requestID, _ := c.Get("request_id")
    c.JSON(401, Response{
        Code:      int(errors.CodeUnauthorized),
        Message:   message,
        RequestID: requestID.(string),
    })
}

func Forbidden(c *gin.Context, message string) {
    requestID, _ := c.Get("request_id")
    c.JSON(403, Response{
        Code:      int(errors.CodeForbidden),
        Message:   message,
        RequestID: requestID.(string),
    })
}

func NotFound(c *gin.Context, message string) {
    requestID, _ := c.Get("request_id")
    c.JSON(404, Response{
        Code:      int(errors.CodeNotFound),
        Message:   message,
        RequestID: requestID.(string),
    })
}

func InternalError(c *gin.Context, message string) {
    requestID, _ := c.Get("request_id")
    c.JSON(500, Response{
        Code:      int(errors.CodeInternalError),
        Message:   message,
        RequestID: requestID.(string),
    })
}
// internal/handler/middleware/recovery.go
package middleware

import (
    "fmt"
    "runtime/debug"
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
)

func Recovery(logger *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if r := recover(); r != nil {
                // 记录堆栈
                stack := string(debug.Stack())
                
                requestID, _ := c.Get("request_id")
                
                logger.Error("Panic recovered",
                    zap.Any("error", r),
                    zap.String("stack", stack),
                    zap.String("request_id", requestID.(string)),
                    zap.String("path", c.Request.URL.Path),
                    zap.String("method", c.Request.Method),
                )
                
                response.InternalError(c, "服务器内部错误")
                c.Abort()
            }
        }()
        c.Next()
    }
}
// 使用示例 - internal/service/user_service.go
func (s *userService) GetByID(ctx context.Context, id int64) (*entity.User, error) {
    user, err := s.userRepo.FindByID(ctx, id)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return nil, errors.ErrUserNotFound
    }
    return user, nil
}

func (s *userService) Create(ctx context.Context, req *dto.CreateUserRequest) (*entity.User, error) {
    // 检查用户是否存在
    existing, err := s.userRepo.FindByEmail(ctx, req.Email)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if existing != nil {
        return nil, errors.ErrUserExists
    }
    
    // 业务规则校验
    if req.Age < 18 {
        return nil, errors.NewWithMessage(errors.CodeBadRequest, "用户年龄不能小于18岁")
    }
    
    user := &entity.User{
        Username: req.Username,
        Email:    req.Email,
        Password: hashPassword(req.Password),
    }
    
    if err := s.userRepo.Create(ctx, user); err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    
    return user, nil
}

// internal/handler/user_handler.go
func (h *UserHandler) GetByID(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        response.BadRequest(c, "无效的用户ID")
        return
    }
    
    user, err := h.userService.GetByID(c.Request.Context(), id)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, dto.ToUserResponse(user))
}

Java 实现

// enums/ErrorCode.java
@Getter
@AllArgsConstructor
public enum ErrorCode {
    
    // 通用错误
    SUCCESS(0, "成功"),
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "未授权"),
    FORBIDDEN(403, "禁止访问"),
    NOT_FOUND(404, "资源不存在"),
    CONFLICT(409, "资源冲突"),
    TOO_MANY_REQUESTS(429, "请求过于频繁"),
    INTERNAL_ERROR(500, "服务器内部错误"),
    SERVICE_UNAVAILABLE(503, "服务暂不可用"),
    
    // 用户模块 (10000+)
    USER_NOT_FOUND(10001, "用户不存在"),
    USER_EXISTS(10002, "用户已存在"),
    PASSWORD_WRONG(10003, "密码错误"),
    TOKEN_EXPIRED(10004, "Token已过期"),
    TOKEN_INVALID(10005, "Token无效"),
    PERMISSION_DENIED(10006, "权限不足"),
    
    // 订单模块 (20000+)
    ORDER_NOT_FOUND(20001, "订单不存在"),
    ORDER_CANCELLED(20002, "订单已取消"),
    INSUFFICIENT_STOCK(20003, "库存不足"),
    ORDER_STATUS_ERROR(20004, "订单状态不允许此操作");
    
    private final int code;
    private final String message;
    
    public HttpStatus toHttpStatus() {
        if (code == 0) return HttpStatus.OK;
        if (code == 400 || code >= 10000) return HttpStatus.BAD_REQUEST;
        if (code == 401) return HttpStatus.UNAUTHORIZED;
        if (code == 403) return HttpStatus.FORBIDDEN;
        if (code == 404) return HttpStatus.NOT_FOUND;
        if (code == 409) return HttpStatus.CONFLICT;
        if (code == 429) return HttpStatus.TOO_MANY_REQUESTS;
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }
}

// exception/BusinessException.java
@Getter
public class BusinessException extends RuntimeException {
    
    private final ErrorCode errorCode;
    private final Object data;
    
    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
        this.data = null;
    }
    
    public BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
        this.data = null;
    }
    
    public BusinessException(ErrorCode errorCode, Throwable cause) {
        super(errorCode.getMessage(), cause);
        this.errorCode = errorCode;
        this.data = null;
    }
    
    public BusinessException(ErrorCode errorCode, String message, Object data) {
        super(message);
        this.errorCode = errorCode;
        this.data = data;
    }
}

// exception/NotFoundException.java
public class NotFoundException extends BusinessException {
    public NotFoundException(String message) {
        super(ErrorCode.NOT_FOUND, message);
    }
}

// exception/ForbiddenException.java
public class ForbiddenException extends BusinessException {
    public ForbiddenException(String message) {
        super(ErrorCode.FORBIDDEN, message);
    }
}
// dto/response/CommonResponse.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> {
    
    private int code;
    private String message;
    private T data;
    private String requestId;
    private long timestamp;
    
    public CommonResponse(int code, String message) {
        this.code = code;
        this.message = message;
        this.timestamp = System.currentTimeMillis();
        this.requestId = MDC.get("request_id");
    }
    
    public CommonResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.timestamp = System.currentTimeMillis();
        this.requestId = MDC.get("request_id");
    }
    
    public static <T> CommonResponse<T> success() {
        return new CommonResponse<>(0, "success", null);
    }
    
    public static <T> CommonResponse<T> success(T data) {
        return new CommonResponse<>(0, "success", data);
    }
    
    public static <T> CommonResponse<T> success(String message, T data) {
        return new CommonResponse<>(0, message, data);
    }
    
    public static <T> CommonResponse<T> error(ErrorCode errorCode) {
        return new CommonResponse<>(errorCode.getCode(), errorCode.getMessage());
    }
    
    public static <T> CommonResponse<T> error(ErrorCode errorCode, String message) {
        return new CommonResponse<>(errorCode.getCode(), message);
    }
    
    public static <T> CommonResponse<T> error(int code, String message) {
        return new CommonResponse<>(code, message);
    }
}

// dto/response/PageResult.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
    
    private List<T> list;
    private long total;
    private int page;
    private int pageSize;
    private int pages;
    
    public static <T> PageResult<T> of(List<T> list, long total, int page, int pageSize) {
        PageResult<T> result = new PageResult<>();
        result.setList(list);
        result.setTotal(total);
        result.setPage(page);
        result.setPageSize(pageSize);
        result.setPages((int) Math.ceil((double) total / pageSize));
        return result;
    }
    
    public static <T> PageResult<T> of(IPage<T> page) {
        return of(page.getRecords(), page.getTotal(), 
                  (int) page.getCurrent(), (int) page.getSize());
    }
}
// exception/GlobalExceptionHandler.java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /**
     * 业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<CommonResponse<Object>> handleBusinessException(
            BusinessException e, HttpServletRequest request) {
        
        log.warn("Business exception: {} - {}", e.getErrorCode(), e.getMessage());
        
        CommonResponse<Object> response = CommonResponse.error(
                e.getErrorCode().getCode(), e.getMessage());
        
        if (e.getData() != null) {
            response.setData(e.getData());
        }
        
        return ResponseEntity
                .status(e.getErrorCode().toHttpStatus())
                .body(response);
    }
    
    /**
     * 参数校验异常 - @Valid 校验失败
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<CommonResponse<Map<String, String>>> handleValidationException(
            MethodArgumentNotValidException e) {
        
        Map<String, String> errors = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage()));
        
        // 取第一个错误作为主要消息
        String message = e.getBindingResult().getFieldErrors().stream()
                .findFirst()
                .map(FieldError::getDefaultMessage)
                .orElse("参数校验失败");
        
        log.warn("Validation failed: {}", errors);
        
        CommonResponse<Map<String, String>> response = 
                CommonResponse.error(ErrorCode.BAD_REQUEST, message);
        response.setData(errors);
        
        return ResponseEntity.badRequest().body(response);
    }
    
    /**
     * 参数校验异常 - @Validated 校验失败(方法参数)
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<CommonResponse<Void>> handleConstraintViolation(
            ConstraintViolationException e) {
        
        String message = e.getConstraintViolations().stream()
                .findFirst()
                .map(ConstraintViolation::getMessage)
                .orElse("参数校验失败");
        
        return ResponseEntity.badRequest()
                .body(CommonResponse.error(ErrorCode.BAD_REQUEST, message));
    }
    
    /**
     * 参数绑定异常
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<CommonResponse<Void>> handleBindException(BindException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
                .findFirst()
                .map(FieldError::getDefaultMessage)
                .orElse("参数绑定失败");
        
        return ResponseEntity.badRequest()
                .body(CommonResponse.error(ErrorCode.BAD_REQUEST, message));
    }
    
    /**
     * 请求方法不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<CommonResponse<Void>> handleMethodNotSupported(
            HttpRequestMethodNotSupportedException e) {
        
        return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
                .body(CommonResponse.error(405, "请求方法不支持: " + e.getMethod()));
    }
    
    /**
     * 请求体缺失
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<CommonResponse<Void>> handleMessageNotReadable(
            HttpMessageNotReadableException e) {
        
        return ResponseEntity.badRequest()
                .body(CommonResponse.error(ErrorCode.BAD_REQUEST, "请求体格式错误"));
    }
    
    /**
     * JWT 相关异常
     */
    @ExceptionHandler(ExpiredJwtException.class)
    public ResponseEntity<CommonResponse<Void>> handleExpiredJwt(ExpiredJwtException e) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(CommonResponse.error(ErrorCode.TOKEN_EXPIRED));
    }
    
    @ExceptionHandler(JwtException.class)
    public ResponseEntity<CommonResponse<Void>> handleJwtException(JwtException e) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(CommonResponse.error(ErrorCode.TOKEN_INVALID));
    }
    
    /**
     * 访问拒绝
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<CommonResponse<Void>> handleAccessDenied(AccessDeniedException e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(CommonResponse.error(ErrorCode.FORBIDDEN));
    }
    
    /**
     * 限流异常
     */
    @ExceptionHandler(RateLimitExceededException.class)
    public ResponseEntity<CommonResponse<Void>> handleRateLimit(RateLimitExceededException e) {
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                .body(CommonResponse.error(ErrorCode.TOO_MANY_REQUESTS));
    }
    
    /**
     * 兜底异常处理
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<CommonResponse<Void>> handleException(
            Exception e, HttpServletRequest request) {
        
        log.error("Unhandled exception: {} - {}", 
                request.getRequestURI(), e.getMessage(), e);
        
        // 生产环境不暴露具体错误信息
        String message = "服务器内部错误";
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(CommonResponse.error(ErrorCode.INTERNAL_ERROR, message));
    }
}
// 使用示例 - service/impl/UserServiceImpl.java
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
    
    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    
    @Override
    public User getById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        return user;
    }
    
    @Override
    @Transactional
    public User create(CreateUserRequest request) {
        // 检查用户是否存在
        User existing = userMapper.selectByEmail(request.getEmail());
        if (existing != null) {
            throw new BusinessException(ErrorCode.USER_EXISTS);
        }
        
        // 业务规则校验
        if (request.getAge() != null && request.getAge() < 18) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "用户年龄不能小于18岁");
        }
        
        User user = new User();
        BeanUtils.copyProperties(request, user);
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setCreatedAt(LocalDateTime.now());
        
        userMapper.insert(user);
        return user;
    }
    
    @Override
    @Transactional
    public void updateStatus(Long id, Integer status) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 状态流转校验
        if (user.getStatus().equals(2) && status.equals(1)) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "已禁用的用户不能直接激活");
        }
        
        user.setStatus(status);
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
    }
}

// controller/UserController.java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @GetMapping("/{id}")
    public CommonResponse<UserResponse> getById(@PathVariable Long id) {
        User user = userService.getById(id);
        return CommonResponse.success(UserResponse.from(user));
    }
    
    @PostMapping
    public CommonResponse<UserResponse> create(
            @Valid @RequestBody CreateUserRequest request) {
        User user = userService.create(request);
        return CommonResponse.success(UserResponse.from(user));
    }
    
    @GetMapping
    public CommonResponse<PageResult<UserResponse>> list(@Valid ListUserRequest request) {
        PageResult<UserResponse> result = userService.list(request);
        return CommonResponse.success(result);
    }
}

异常处理对比表

方面 Go Java
错误类型 自定义 error 结构体 自定义 Exception 类
错误传播 返回值传递 throw 抛出
全局处理 Recovery 中间件 @RestControllerAdvice
错误码 常量定义 枚举定义
堆栈跟踪 debug.Stack() Exception.getStackTrace()
错误包装 errors.Wrap new Exception(msg, cause)

十、事务管理

Go 实现

// pkg/database/transaction.go
package database

import (
    "context"
    "database/sql"
)

type TxKey struct{}

// 从 Context 获取事务
func GetTx(ctx context.Context) *sql.Tx {
    if tx, ok := ctx.Value(TxKey{}).(*sql.Tx); ok {
        return tx
    }
    return nil
}

// 事务管理器
type TxManager interface {
    WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}

type txManager struct {
    db *sql.DB
}

func NewTxManager(db *sql.DB) TxManager {
    return &txManager{db: db}
}

func (m *txManager) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
    // 检查是否已在事务中
    if GetTx(ctx) != nil {
        return fn(ctx)
    }
    
    tx, err := m.db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    
    // 将事务放入 Context
    txCtx := context.WithValue(ctx, TxKey{}, tx)
    
    defer func() {
        if r := recover(); r != nil {
            _ = tx.Rollback()
            panic(r)
        }
    }()
    
    if err := fn(txCtx); err != nil {
        if rbErr := tx.Rollback(); rbErr != nil {
            return fmt.Errorf("rollback error: %v (original: %v)", rbErr, err)
        }
        return err
    }
    
    return tx.Commit()
}

// GORM 版本
type GormTxManager struct {
    db *gorm.DB
}

func NewGormTxManager(db *gorm.DB) *GormTxManager {
    return &GormTxManager{db: db}
}

func (m *GormTxManager) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
    return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        txCtx := context.WithValue(ctx, TxKey{}, tx)
        return fn(txCtx)
    })
}
// internal/repository/user_repository.go
type userRepository struct {
    db *gorm.DB
}

// 自动获取事务或普通连接
func (r *userRepository) getDB(ctx context.Context) *gorm.DB {
    if tx, ok := ctx.Value(database.TxKey{}).(*gorm.DB); ok {
        return tx
    }
    return r.db.WithContext(ctx)
}

func (r *userRepository) Create(ctx context.Context, user *entity.User) error {
    return r.getDB(ctx).Create(user).Error
}

func (r *userRepository) Update(ctx context.Context, user *entity.User) error {
    return r.getDB(ctx).Save(user).Error
}

func (r *userRepository) Delete(ctx context.Context, id int64) error {
    return r.getDB(ctx).Delete(&entity.User{}, id).Error
}
// internal/service/order_service.go
type orderService struct {
    txManager   database.TxManager
    orderRepo   repository.OrderRepository
    productRepo repository.ProductRepository
    userRepo    repository.UserRepository
    logger      *zap.Logger
}

func (s *orderService) CreateOrder(ctx context.Context, req *dto.CreateOrderRequest) (*entity.Order, error) {
    var order *entity.Order
    
    err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
        // 1. 检查用户
        user, err := s.userRepo.FindByID(txCtx, req.UserID)
        if err != nil {
            return err
        }
        if user == nil {
            return errors.ErrUserNotFound
        }
        
        // 2. 检查库存并扣减
        product, err := s.productRepo.FindByIDForUpdate(txCtx, req.ProductID)
        if err != nil {
            return err
        }
        if product == nil {
            return errors.New(errors.CodeNotFound, "商品不存在")
        }
        if product.Stock < req.Quantity {
            return errors.ErrInsufficientStock
        }
        
        product.Stock -= req.Quantity
        if err := s.productRepo.Update(txCtx, product); err != nil {
            return err
        }
        
        // 3. 创建订单
        order = &entity.Order{
            UserID:     req.UserID,
            ProductID:  req.ProductID,
            Quantity:   req.Quantity,
            TotalPrice: product.Price * float64(req.Quantity),
            Status:     entity.OrderStatusPending,
            CreatedAt:  time.Now(),
        }
        
        if err := s.orderRepo.Create(txCtx, order); err != nil {
            return err
        }
        
        // 4. 创建订单明细
        detail := &entity.OrderDetail{
            OrderID:   order.ID,
            ProductID: req.ProductID,
            Quantity:  req.Quantity,
            Price:     product.Price,
        }
        
        if err := s.orderRepo.CreateDetail(txCtx, detail); err != nil {
            return err
        }
        
        return nil
    })
    
    if err != nil {
        s.logger.Error("create order failed",
            zap.Error(err),
            zap.Int64("user_id", req.UserID))
        return nil, err
    }
    
    return order, nil
}

// 带 Saga 补偿的分布式事务示例
func (s *orderService) CreateOrderWithSaga(ctx context.Context, req *dto.CreateOrderRequest) (*entity.Order, error) {
    var order *entity.Order
    var compensations []func() error
    
    // 定义补偿函数
    defer func() {
        if r := recover(); r != nil {
            s.executeCompensations(compensations)
            panic(r)
        }
    }()
    
    // Step 1: 扣减库存
    if err := s.productRepo.DeductStock(ctx, req.ProductID, req.Quantity); err != nil {
        return nil, err
    }
    compensations = append(compensations, func() error {
        return s.productRepo.AddStock(ctx, req.ProductID, req.Quantity)
    })
    
    // Step 2: 创建订单
    order = &entity.Order{
        UserID:    req.UserID,
        ProductID: req.ProductID,
        Quantity:  req.Quantity,
        Status:    entity.OrderStatusPending,
    }
    if err := s.orderRepo.Create(ctx, order); err != nil {
        s.executeCompensations(compensations)
        return nil, err
    }
    compensations = append(compensations, func() error {
        return s.orderRepo.Delete(ctx, order.ID)
    })
    
    // Step 3: 调用支付服务(外部服务)
    if err := s.paymentClient.CreatePayment(ctx, order.ID, order.TotalPrice); err != nil {
        s.executeCompensations(compensations)
        return nil, err
    }
    
    return order, nil
}

func (s *orderService) executeCompensations(compensations []func() error) {
    // 逆序执行补偿
    for i := len(compensations) - 1; i >= 0; i-- {
        if err := compensations[i](); err != nil {
            s.logger.Error("compensation failed", zap.Error(err))
        }
    }
}

Java 实现

// service/impl/OrderServiceImpl.java
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    private final OrderMapper orderMapper;
    private final ProductMapper productMapper;
    private final UserMapper userMapper;
    private final OrderDetailMapper orderDetailMapper;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(CreateOrderRequest request) {
        // 1. 检查用户
        User user = userMapper.selectById(request.getUserId());
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 2. 检查库存并扣减(悲观锁)
        Product product = productMapper.selectByIdForUpdate(request.getProductId());
        if (product == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND, "商品不存在");
        }
        if (product.getStock() < request.getQuantity()) {
            throw new BusinessException(ErrorCode.INSUFFICIENT_STOCK);
        }
        
        product.setStock(product.getStock() - request.getQuantity());
        product.setUpdatedAt(LocalDateTime.now());
        productMapper.updateById(product);
        
        // 3. 创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(request.getQuantity())));
        order.setStatus(OrderStatus.PENDING.getCode());
        order.setCreatedAt(LocalDateTime.now());
        orderMapper.insert(order);
        
        // 4. 创建订单明细
        OrderDetail detail = new OrderDetail();
        detail.setOrderId(order.getId());
        detail.setProductId(request.getProductId());
        detail.setQuantity(request.getQuantity());
        detail.setPrice(product.getPrice());
        orderDetailMapper.insert(detail);
        
        return order;
    }
    
    /**
     * 带传播行为的嵌套事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order createOrderWithNotification(CreateOrderRequest request) {
        // 创建订单(主事务)
        Order order = createOrder(request);
        
        // 发送通知(独立事务,失败不影响主事务)
        try {
            notificationService.sendOrderCreatedNotification(order);
        } catch (Exception e) {
            log.warn("Failed to send notification for order: {}", order.getId(), e);
        }
        
        return order;
    }
    
    /**
     * 取消订单(状态校验 + 库存回滚)
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrder(Long orderId, String reason) {
        Order order = orderMapper.selectByIdForUpdate(orderId);
        if (order == null) {
            throw new BusinessException(ErrorCode.ORDER_NOT_FOUND);
        }
        
        // 状态校验
        if (!OrderStatus.canCancel(order.getStatus())) {
            throw new BusinessException(ErrorCode.ORDER_STATUS_ERROR, 
                    "当前订单状态不允许取消");
        }
        
        // 更新订单状态
        order.setStatus(OrderStatus.CANCELLED.getCode());
        order.setCancelReason(reason);
        order.setCancelledAt(LocalDateTime.now());
        order.setUpdatedAt(LocalDateTime.now());
        orderMapper.updateById(order);
        
        // 恢复库存
        productMapper.addStock(order.getProductId(), order.getQuantity());
        
        log.info("Order cancelled: orderId={}, reason={}", orderId, reason);
    }
}

// 通知服务 - 使用 REQUIRES_NEW 传播行为
@Service
@RequiredArgsConstructor
@Slf4j
public class NotificationServiceImpl implements NotificationService {
    
    private final NotificationMapper notificationMapper;
    private final EmailClient emailClient;
    
    /**
     * 独立事务:即使失败也不影响主事务
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void sendOrderCreatedNotification(Order order) {
        // 记录通知
        Notification notification = new Notification();
        notification.setUserId(order.getUserId());
        notification.setType("ORDER_CREATED");
        notification.setContent("您的订单 " + order.getId() + " 已创建成功");
        notification.setCreatedAt(LocalDateTime.now());
        notificationMapper.insert(notification);
        
        // 发送邮件(可能失败)
        emailClient.sendOrderConfirmation(order);
    }
    
    /**
     * 异步发送,不影响主流程
     */
    @Override
    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendOrderCreatedNotificationAsync(Order order) {
        sendOrderCreatedNotification(order);
    }
}
// 编程式事务管理
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderServiceWithProgrammaticTx implements OrderService {
    
    private final TransactionTemplate transactionTemplate;
    private final PlatformTransactionManager transactionManager;
    private final OrderMapper orderMapper;
    private final ProductMapper productMapper;
    
    /**
     * 使用 TransactionTemplate
     */
    public Order createOrderWithTemplate(CreateOrderRequest request) {
        return transactionTemplate.execute(status -> {
            try {
                // 业务逻辑
                Product product = productMapper.selectByIdForUpdate(request.getProductId());
                if (product.getStock() < request.getQuantity()) {
                    throw new BusinessException(ErrorCode.INSUFFICIENT_STOCK);
                }
                
                product.setStock(product.getStock() - request.getQuantity());
                productMapper.updateById(product);
                
                Order order = new Order();
                order.setProductId(request.getProductId());
                order.setQuantity(request.getQuantity());
                order.setStatus(OrderStatus.PENDING.getCode());
                orderMapper.insert(order);
                
                return order;
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }
    
    /**
     * 手动控制事务
     */
    public Order createOrderManual(CreateOrderRequest request) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setTimeout(30);
        
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            // 业务逻辑
            Product product = productMapper.selectByIdForUpdate(request.getProductId());
            if (product.getStock() < request.getQuantity()) {
                throw new BusinessException(ErrorCode.INSUFFICIENT_STOCK);
            }
            
            product.setStock(product.getStock() - request.getQuantity());
            productMapper.updateById(product);
            
            Order order = new Order();
            order.setProductId(request.getProductId());
            order.setQuantity(request.getQuantity());
            orderMapper.insert(order);
            
            transactionManager.commit(status);
            return order;
            
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    /**
     * 分段提交(大批量处理)
     */
    @Override
    public void batchUpdateOrderStatus(List<Long> orderIds, Integer newStatus) {
        int batchSize = 100;
        
        for (int i = 0; i < orderIds.size(); i += batchSize) {
            int end = Math.min(i + batchSize, orderIds.size());
            List<Long> batch = orderIds.subList(i, end);
            
            transactionTemplate.execute(status -> {
                orderMapper.batchUpdateStatus(batch, newStatus, LocalDateTime.now());
                return null;
            });
            
            log.info("Batch updated orders: {}-{}", i, end);
        }
    }
}
// 分布式事务 - Seata AT 模式示例
@Service
@RequiredArgsConstructor
@Slf4j
public class DistributedOrderService {
    
    private final OrderMapper orderMapper;
    private final ProductFeignClient productClient;
    private final PaymentFeignClient paymentClient;
    
    /**
     * Seata 分布式事务
     */
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public Order createDistributedOrder(CreateOrderRequest request) {
        // 1. 扣减库存(库存服务)
        productClient.deductStock(request.getProductId(), request.getQuantity());
        
        // 2. 创建订单(订单服务 - 本地)
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setStatus(OrderStatus.PENDING.getCode());
        orderMapper.insert(order);
        
        // 3. 创建支付单(支付服务)
        paymentClient.createPayment(order.getId(), order.getTotalPrice());
        
        return order;
    }
    
    /**
     * TCC 模式示例
     */
    @GlobalTransactional
    public Order createOrderWithTcc(CreateOrderRequest request) {
        // Try: 预留资源
        productClient.tryDeductStock(request.getProductId(), request.getQuantity());
        
        Order order = new Order();
        order.setStatus(OrderStatus.TRYING.getCode());
        orderMapper.insert(order);
        
        paymentClient.tryCreatePayment(order.getId(), order.getTotalPrice());
        
        // Confirm 和 Cancel 由 Seata 框架自动调用
        return order;
    }
}

// Saga 模式 - 手动补偿
@Service
@RequiredArgsConstructor
@Slf4j
public class SagaOrderService {
    
    private final OrderMapper orderMapper;
    private final ProductClient productClient;
    private final PaymentClient paymentClient;
    
    public Order createOrderWithSaga(CreateOrderRequest request) {
        List<Runnable> compensations = new ArrayList<>();
        
        try {
            // Step 1: 扣减库存
            productClient.deductStock(request.getProductId(), request.getQuantity());
            compensations.add(() -> 
                productClient.addStock(request.getProductId(), request.getQuantity()));
            
            // Step 2: 创建订单
            Order order = new Order();
            order.setProductId(request.getProductId());
            order.setQuantity(request.getQuantity());
            order.setStatus(OrderStatus.PENDING.getCode());
            orderMapper.insert(order);
            compensations.add(() -> orderMapper.deleteById(order.getId()));
            
            // Step 3: 创建支付
            paymentClient.createPayment(order.getId(), order.getTotalPrice());
            compensations.add(() -> paymentClient.cancelPayment(order.getId()));
            
            return order;
            
        } catch (Exception e) {
            // 执行补偿(逆序)
            log.error("Saga failed, executing compensations", e);
            Collections.reverse(compensations);
            for (Runnable compensation : compensations) {
                try {
                    compensation.run();
                } catch (Exception compEx) {
                    log.error("Compensation failed", compEx);
                    // 记录补偿失败,人工处理
                }
            }
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "订单创建失败");
        }
    }
}

事务对比表

方面 Go Java
事务声明 手动 Begin/Commit/Rollback @Transactional 注解
事务传播 Context 传递手动处理 propagation 属性
隔离级别 BeginTx 参数 isolation 属性
只读事务 需手动实现 readOnly = true
超时控制 Context WithTimeout timeout 属性
嵌套事务 需手动保存点 NESTED 传播行为
分布式事务 手动 Saga/消息队列 Seata/手动 Saga

十一、限流与熔断

Go 实现

// pkg/ratelimit/ratelimit.go
package ratelimit

import (
    "context"
    "time"
    "github.com/redis/go-redis/v9"
    "golang.org/x/time/rate"
)

// 本地限流器(单机)
type LocalRateLimiter struct {
    limiter *rate.Limiter
}

func NewLocalRateLimiter(r rate.Limit, b int) *LocalRateLimiter {
    return &LocalRateLimiter{
        limiter: rate.NewLimiter(r, b),
    }
}

func (l *LocalRateLimiter) Allow() bool {
    return l.limiter.Allow()
}

func (l *LocalRateLimiter) Wait(ctx context.Context) error {
    return l.limiter.Wait(ctx)
}

// 分布式限流器(基于 Redis)
type RedisRateLimiter struct {
    client     *redis.Client
    keyPrefix  string
    limit      int
    window     time.Duration
}

func NewRedisRateLimiter(client *redis.Client, keyPrefix string, limit int, window time.Duration) *RedisRateLimiter {
    return &RedisRateLimiter{
        client:    client,
        keyPrefix: keyPrefix,
        limit:     limit,
        window:    window,
    }
}

// 滑动窗口限流
func (l *RedisRateLimiter) Allow(ctx context.Context, key string) (bool, error) {
    now := time.Now().UnixMilli()
    windowStart := now - l.window.Milliseconds()
    fullKey := l.keyPrefix + key
    
    pipe := l.client.Pipeline()
    
    // 移除窗口外的请求
    pipe.ZRemRangeByScore(ctx, fullKey, "0", fmt.Sprintf("%d", windowStart))
    
    // 获取当前窗口内的请求数
    countCmd := pipe.ZCard(ctx, fullKey)
    
    // 添加当前请求
    pipe.ZAdd(ctx, fullKey, redis.Z{Score: float64(now), Member: now})
    
    // 设置过期时间
    pipe.Expire(ctx, fullKey, l.window)
    
    _, err := pipe.Exec(ctx)
    if err != nil {
        return false, err
    }
    
    count := countCmd.Val()
    return count < int64(l.limit), nil
}

// 令牌桶限流(Redis 实现)
const tokenBucketScript = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local bucket = redis.call('HMGET', key, 'tokens', 'last_time')
local tokens = tonumber(bucket[1]) or capacity
local last_time = tonumber(bucket[2]) or now

local elapsed = now - last_time
local new_tokens = math.min(capacity, tokens + elapsed * rate / 1000)

if new_tokens >= requested then
    new_tokens = new_tokens - requested
    redis.call('HMSET', key, 'tokens', new_tokens, 'last_time', now)
    redis.call('EXPIRE', key, math.ceil(capacity / rate) + 1)
    return 1
else
    return 0
end
`

type TokenBucketLimiter struct {
    client   *redis.Client
    script   *redis.Script
    capacity int
    rate     int // tokens per second
}

func NewTokenBucketLimiter(client *redis.Client, capacity, rate int) *TokenBucketLimiter {
    return &TokenBucketLimiter{
        client:   client,
        script:   redis.NewScript(tokenBucketScript),
        capacity: capacity,
        rate:     rate,
    }
}

func (l *TokenBucketLimiter) Allow(ctx context.Context, key string) (bool, error) {
    result, err := l.script.Run(ctx, l.client, []string{key},
        l.capacity, l.rate, time.Now().UnixMilli(), 1).Int()
    if err != nil {
        return false, err
    }
    return result == 1, nil
}
// internal/handler/middleware/ratelimit.go
package middleware

import (
    "github.com/gin-gonic/gin"
)

// IP 限流中间件
func RateLimitByIP(limiter *ratelimit.RedisRateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP()
        
        allowed, err := limiter.Allow(c.Request.Context(), "ip:"+ip)
        if err != nil {
            // 限流器故障时放行(降级策略)
            c.Next()
            return
        }
        
        if !allowed {
            response.TooManyRequests(c, "请求过于频繁,请稍后再试")
            c.Abort()
            return
        }
        
        c.Next()
    }
}

// 用户限流中间件
func RateLimitByUser(limiter *ratelimit.RedisRateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := GetCurrentUserID(c)
        if userID == 0 {
            c.Next()
            return
        }
        
        allowed, err := limiter.Allow(c.Request.Context(), fmt.Sprintf("user:%d", userID))
        if err != nil {
            c.Next()
            return
        }
        
        if !allowed {
            response.TooManyRequests(c, "请求过于频繁,请稍后再试")
            c.Abort()
            return
        }
        
        c.Next()
    }
}

// API 限流中间件(按接口)
func RateLimitByAPI(limiter *ratelimit.TokenBucketLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        key := fmt.Sprintf("api:%s:%s", c.Request.Method, c.FullPath())
        
        allowed, err := limiter.Allow(c.Request.Context(), key)
        if err != nil {
            c.Next()
            return
        }
        
        if !allowed {
            c.Header("Retry-After", "1")
            response.TooManyRequests(c, "接口请求过于频繁")
            c.Abort()
            return
        }
        
        c.Next()
    }
}
// pkg/circuitbreaker/circuitbreaker.go
package circuitbreaker

import (
    "context"
    "errors"
    "sync"
    "time"
)

var (
    ErrCircuitOpen    = errors.New("circuit breaker is open")
    ErrTooManyRequests = errors.New("too many requests")
)

type State int

const (
    StateClosed State = iota
    StateOpen
    StateHalfOpen
)

type CircuitBreaker struct {
    name          string
    maxFailures   int
    timeout       time.Duration
    halfOpenMax   int
    
    mu            sync.RWMutex
    state         State
    failures      int
    successes     int
    lastFailure   time.Time
    halfOpenCount int
}

type Config struct {
    Name        string
    MaxFailures int           // 触发熔断的失败次数
    Timeout     time.Duration // 熔断持续时间
    HalfOpenMax int           // 半开状态允许的请求数
}

func New(cfg Config) *CircuitBreaker {
    return &CircuitBreaker{
        name:        cfg.Name,
        maxFailures: cfg.MaxFailures,
        timeout:     cfg.Timeout,
        halfOpenMax: cfg.HalfOpenMax,
        state:       StateClosed,
    }
}

func (cb *CircuitBreaker) Execute(ctx context.Context, fn func() error) error {
    if err := cb.beforeRequest(); err != nil {
        return err
    }
    
    err := fn()
    
    cb.afterRequest(err)
    return err
}

func (cb *CircuitBreaker) beforeRequest() error {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    
    switch cb.state {
    case StateClosed:
        return nil
        
    case StateOpen:
        if time.Since(cb.lastFailure) > cb.timeout {
            cb.state = StateHalfOpen
            cb.halfOpenCount = 0
            return nil
        }
        return ErrCircuitOpen
        
    case StateHalfOpen:
        if cb.halfOpenCount >= cb.halfOpenMax {
            return ErrTooManyRequests
        }
        cb.halfOpenCount++
        return nil
    }
    
    return nil
}

func (cb *CircuitBreaker) afterRequest(err error) {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    
    if err != nil {
        cb.onFailure()
    } else {
        cb.onSuccess()
    }
}

func (cb *CircuitBreaker) onFailure() {
    cb.failures++
    cb.lastFailure = time.Now()
    
    switch cb.state {
    case StateClosed:
        if cb.failures >= cb.maxFailures {
            cb.state = StateOpen
        }
    case StateHalfOpen:
        cb.state = StateOpen
    }
}

func (cb *CircuitBreaker) onSuccess() {
    switch cb.state {
    case StateClosed:
        cb.failures = 0
    case StateHalfOpen:
        cb.successes++
        if cb.successes >= cb.halfOpenMax {
            cb.state = StateClosed
            cb.failures = 0
            cb.successes = 0
        }
    }
}

func (cb *CircuitBreaker) State() State {
    cb.mu.RLock()
    defer cb.mu.RUnlock()
    return cb.state
}

// 使用示例
type PaymentClient struct {
    cb      *CircuitBreaker
    httpCli *http.Client
}

func (c *PaymentClient) CreatePayment(ctx context.Context, orderID int64, amount float64) error {
    return c.cb.Execute(ctx, func() error {
        resp, err := c.httpCli.Post(...)
        if err != nil {
            return err
        }
        if resp.StatusCode >= 500 {
            return errors.New("payment service error")
        }
        return nil
    })
}

Java 实现(Resilience4j)

// config/Resilience4jConfig.java
@Configuration
public class Resilience4jConfig {
    
    @Bean
    public RateLimiterConfig rateLimiterConfig() {
        return RateLimiterConfig.custom()
                .limitRefreshPeriod(Duration.ofSeconds(1))
                .limitForPeriod(100)  // 每秒 100 个请求
                .timeoutDuration(Duration.ofMillis(500))
                .build();
    }
    
    @Bean
    public RateLimiterRegistry rateLimiterRegistry(RateLimiterConfig config) {
        return RateLimiterRegistry.of(config);
    }
    
    @Bean
    public CircuitBreakerConfig circuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
                .failureRateThreshold(50)  // 失败率阈值 50%
                .waitDurationInOpenState(Duration.ofSeconds(30))  // 熔断持续时间
                .permittedNumberOfCallsInHalfOpenState(10)  // 半开状态请求数
                .slidingWindowSize(100)  // 滑动窗口大小
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                .build();
    }
    
    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry(CircuitBreakerConfig config) {
        return CircuitBreakerRegistry.of(config);
    }
    
    @Bean
    public RetryConfig retryConfig() {
        return RetryConfig.custom()
                .maxAttempts(3)
                .waitDuration(Duration.ofMillis(500))
                .retryExceptions(IOException.class, TimeoutException.class)
                .ignoreExceptions(BusinessException.class)
                .build();
    }
    
    @Bean
    public RetryRegistry retryRegistry(RetryConfig config) {
        return RetryRegistry.of(config);
    }
}

// application.yml 配置方式
/*
resilience4j:
  ratelimiter:
    instances:
      default:
        limitForPeriod: 100
        limitRefreshPeriod: 1s
        timeoutDuration: 500ms
      userApi:
        limitForPeriod: 50
        limitRefreshPeriod: 1s
        
  circuitbreaker:
    instances:
      paymentService:
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 10
        slidingWindowSize: 100
        
  retry:
    instances:
      paymentService:
        maxAttempts: 3
        waitDuration: 500ms
*/
// service/impl/PaymentServiceImpl.java
@Service
@RequiredArgsConstructor
@Slf4j
public class PaymentServiceImpl implements PaymentService {
    
    private final PaymentClient paymentClient;
    private final CircuitBreakerRegistry circuitBreakerRegistry;
    private final RetryRegistry retryRegistry;
    private final RateLimiterRegistry rateLimiterRegistry;
    
    @Override
    @CircuitBreaker(name = "paymentService", fallbackMethod = "createPaymentFallback")
    @Retry(name = "paymentService")
    @RateLimiter(name = "paymentService")
    public PaymentResult createPayment(Long orderId, BigDecimal amount) {
        log.info("Creating payment for order: {}", orderId);
        return paymentClient.create(orderId, amount);
    }
    
    // 熔断降级方法
    public PaymentResult createPaymentFallback(Long orderId, BigDecimal amount, Throwable t) {
        log.warn("Payment service fallback triggered for order: {}, error: {}", 
                orderId, t.getMessage());
        
        // 返回降级响应
        PaymentResult result = new PaymentResult();
        result.setSuccess(false);
        result.setMessage("支付服务暂时不可用,请稍后重试");
        result.setPending(true);
        return result;
    }
    
    // 编程式使用
    public PaymentResult createPaymentProgrammatic(Long orderId, BigDecimal amount) {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService");
        Retry retry = retryRegistry.retry("paymentService");
        RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("paymentService");
        
        Supplier<PaymentResult> supplier = () -> paymentClient.create(orderId, amount);
        
        Supplier<PaymentResult> decoratedSupplier = Decorators.ofSupplier(supplier)
                .withCircuitBreaker(circuitBreaker)
                .withRetry(retry)
                .withRateLimiter(rateLimiter)
                .withFallback(List.of(CallNotPermittedException.class, RequestNotPermitted.class),
                        t -> createPaymentFallback(orderId, amount, t))
                .decorate();
        
        return decoratedSupplier.get();
    }
}
// 自定义 Redis 分布式限流
// ratelimit/RedisRateLimiter.java
@Component
@RequiredArgsConstructor
public class RedisRateLimiter {
    
    private final StringRedisTemplate redisTemplate;
    
    private static final String SLIDING_WINDOW_SCRIPT = """
        local key = KEYS[1]
        local window = tonumber(ARGV[1])
        local limit = tonumber(ARGV[2])
        local now = tonumber(ARGV[3])
        
        redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
        local count = redis.call('ZCARD', key)
        
        if count < limit then
            redis.call('ZADD', key, now, now .. '-' .. math.random())
            redis.call('EXPIRE', key, math.ceil(window / 1000))
            return 1
        else
            return 0
        end
        """;
    
    private final DefaultRedisScript<Long> slidingWindowScript;
    
    @PostConstruct
    public void init() {
        slidingWindowScript = new DefaultRedisScript<>();
        slidingWindowScript.setScriptText(SLIDING_WINDOW_SCRIPT);
        slidingWindowScript.setResultType(Long.class);
    }
    
    /**
     * 滑动窗口限流
     * @param key 限流 key
     * @param windowMs 窗口大小(毫秒)
     * @param limit 限制次数
     * @return 是否允许
     */
    public boolean isAllowed(String key, long windowMs, int limit) {
        Long result = redisTemplate.execute(
                slidingWindowScript,
                List.of(key),
                String.valueOf(windowMs),
                String.valueOf(limit),
                String.valueOf(System.currentTimeMillis())
        );
        return result != null && result == 1L;
    }
    
    /**
     * IP 限流
     */
    public boolean isAllowedByIp(String ip, int limit, Duration window) {
        return isAllowed("ratelimit:ip:" + ip, window.toMillis(), limit);
    }
    
    /**
     * 用户限流
     */
    public boolean isAllowedByUser(Long userId, int limit, Duration window) {
        return isAllowed("ratelimit:user:" + userId, window.toMillis(), limit);
    }
    
    /**
     * 接口限流
     */
    public boolean isAllowedByApi(String method, String path, int limit, Duration window) {
        return isAllowed("ratelimit:api:" + method + ":" + path, window.toMillis(), limit);
    }
}

// interceptor/RateLimitInterceptor.java
@Component
@RequiredArgsConstructor
public class RateLimitInterceptor implements HandlerInterceptor {
    
    private final RedisRateLimiter rateLimiter;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                            Object handler) throws Exception {
        
        String ip = getClientIp(request);
        
        // IP 限流:每分钟 100 次
        if (!rateLimiter.isAllowedByIp(ip, 100, Duration.ofMinutes(1))) {
            sendRateLimitResponse(response);
            return false;
        }
        
        // 用户限流(如果已登录)
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() instanceof CustomUserDetails) {
            Long userId = ((CustomUserDetails) auth.getPrincipal()).getId();
            if (!rateLimiter.isAllowedByUser(userId, 200, Duration.ofMinutes(1))) {
                sendRateLimitResponse(response);
                return false;
            }
        }
        
        return true;
    }
    
    private void sendRateLimitResponse(HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
        response.setContentType("application/json;charset=UTF-8");
        response.setHeader("Retry-After", "60");
        response.getWriter().write(
            "{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip.split(",")[0].trim();
    }
}

限流熔断对比表

方面 Go Java
本地限流 golang.org/x/time/rate Guava RateLimiter / Bucket4j
分布式限流 Redis Lua 脚本手动实现 Redis Lua / Redisson
熔断器 手动实现 / sony/gobreaker Resilience4j / Sentinel
配置方式 代码配置 注解 + YAML 配置
降级处理 回调函数 fallbackMethod
监控指标 Prometheus 手动埋点 Actuator 自动暴露

十二、完整分层代码对比

12.1 Handler / Controller 层

Go 实现

// internal/handler/user_handler.go
package handler

import (
    "strconv"
    "github.com/gin-gonic/gin"
    "myapp/internal/dto/request"
    "myapp/internal/dto/response"
    "myapp/internal/service"
    "myapp/pkg/validator"
)

type UserHandler struct {
    userService service.UserService
}

func NewUserHandler(userService service.UserService) *UserHandler {
    return &UserHandler{userService: userService}
}

// Create 创建用户
// @Summary 创建用户
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param request body request.CreateUserRequest true "创建用户请求"
// @Success 200 {object} response.Response{data=response.UserResponse}
// @Failure 400 {object} response.Response
// @Router /api/v1/users [post]
func (h *UserHandler) Create(c *gin.Context) {
    var req request.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, validator.GetValidationError(err))
        return
    }
    
    user, err := h.userService.Create(c.Request.Context(), &req)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, response.ToUserResponse(user))
}

// GetByID 根据ID获取用户
func (h *UserHandler) GetByID(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        response.BadRequest(c, "无效的用户ID")
        return
    }
    
    user, err := h.userService.GetByID(c.Request.Context(), id)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, response.ToUserResponse(user))
}

// List 获取用户列表
func (h *UserHandler) List(c *gin.Context) {
    var req request.ListUserRequest
    if err := c.ShouldBindQuery(&req); err != nil {
        response.BadRequest(c, validator.GetValidationError(err))
        return
    }
    req.SetDefaults()
    
    users, total, err := h.userService.List(c.Request.Context(), &req)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    list := make([]*response.UserResponse, len(users))
    for i, u := range users {
        list[i] = response.ToUserResponse(u)
    }
    
    response.Success(c, response.NewPageData(list, total, req.Page, req.PageSize))
}

// Update 更新用户
func (h *UserHandler) Update(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        response.BadRequest(c, "无效的用户ID")
        return
    }
    
    var req request.UpdateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, validator.GetValidationError(err))
        return
    }
    
    if err := h.userService.Update(c.Request.Context(), id, &req); err != nil {
        response.Error(c, err)
        return
    }
    
    response.SuccessWithMessage(c, "更新成功", nil)
}

// Delete 删除用户
func (h *UserHandler) Delete(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        response.BadRequest(c, "无效的用户ID")
        return
    }
    
    if err := h.userService.Delete(c.Request.Context(), id); err != nil {
        response.Error(c, err)
        return
    }
    
    response.SuccessWithMessage(c, "删除成功", nil)
}

// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
    var req request.LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, validator.GetValidationError(err))
        return
    }
    
    tokenPair, err := h.userService.Login(c.Request.Context(), &req)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, tokenPair)
}

// GetCurrentUser 获取当前登录用户
func (h *UserHandler) GetCurrentUser(c *gin.Context) {
    userID := middleware.GetCurrentUserID(c)
    if userID == 0 {
        response.Unauthorized(c, "未登录")
        return
    }
    
    user, err := h.userService.GetByID(c.Request.Context(), userID)
    if err != nil {
        response.Error(c, err)
        return
    }
    
    response.Success(c, response.ToUserDetailResponse(user))
}

Java 实现

// controller/UserController.java
package com.example.controller;

import com.example.dto.request.*;
import com.example.dto.response.*;
import com.example.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "用户管理")
@Validated
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    @Operation(summary = "创建用户")
    @RequirePermission("user:create")
    public CommonResponse<UserResponse> create(
            @Valid @RequestBody CreateUserRequest request) {
        return CommonResponse.success(userService.create(request));
    }
    
    @GetMapping("/{id}")
    @Operation(summary = "根据ID获取用户")
    @RequirePermission("user:read")
    public CommonResponse<UserResponse> getById(@PathVariable Long id) {
        return CommonResponse.success(userService.getById(id));
    }
    
    @GetMapping
    @Operation(summary = "获取用户列表")
    @RequirePermission("user:list")
    public CommonResponse<PageResult<UserResponse>> list(@Valid ListUserRequest request) {
        return CommonResponse.success(userService.list(request));
    }
    
    @PutMapping("/{id}")
    @Operation(summary = "更新用户")
    @RequirePermission("user:update")
    public CommonResponse<Void> update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        userService.update(id, request);
        return CommonResponse.success("更新成功");
    }
    
    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户")
    @RequirePermission("user:delete")
    public CommonResponse<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return CommonResponse.success("删除成功");
    }
    
    @PostMapping("/login")
    @Operation(summary = "用户登录")
    public CommonResponse<TokenPairResponse> login(
            @Valid @RequestBody LoginRequest request) {
        return CommonResponse.success(userService.login(request));
    }
    
    @GetMapping("/me")
    @Operation(summary = "获取当前登录用户")
    public CommonResponse<UserDetailResponse> getCurrentUser() {
        Long userId = SecurityUtils.getCurrentUserId();
        return CommonResponse.success(userService.getDetail(userId));
    }
    
    @PutMapping("/me/password")
    @Operation(summary = "修改密码")
    public CommonResponse<Void> changePassword(
            @Valid @RequestBody ChangePasswordRequest request) {
        Long userId = SecurityUtils.getCurrentUserId();
        userService.changePassword(userId, request);
        return CommonResponse.success("密码修改成功");
    }
    
    @PostMapping("/{id}/disable")
    @Operation(summary = "禁用用户")
    @RequirePermission("user:disable")
    @AuditLog(action = AuditAction.UPDATE, resource = "user", resourceIdExpr = "#id")
    public CommonResponse<Void> disable(@PathVariable Long id) {
        userService.updateStatus(id, UserStatus.DISABLED.getCode());
        return CommonResponse.success("用户已禁用");
    }
}

Handler/Controller 对比表

方面 Go Java
路由绑定 构造函数注入 + 手动注册 @RequestMapping 注解
参数校验 ShouldBindJSON + 手动调用 @Valid 自动触发
路径参数 c.Param("id") + 手动转换 @PathVariable 自动转换
查询参数 c.ShouldBindQuery 自动绑定到对象
响应封装 调用 response.Success/Error 返回 CommonResponse
API 文档 swaggo 注释生成 SpringDoc 注解
权限控制 中间件链 @RequirePermission 注解

12.2 Service 层

Go 实现

// internal/service/user_service.go
package service

import (
    "context"
    "time"
    
    "myapp/internal/dto/request"
    "myapp/internal/entity"
    "myapp/internal/errors"
    "myapp/internal/repository"
    "myapp/internal/repository/cache"
    "myapp/pkg/auth"
    "myapp/pkg/database"
    "go.uber.org/zap"
    "golang.org/x/crypto/bcrypt"
)

type UserService interface {
    Create(ctx context.Context, req *request.CreateUserRequest) (*entity.User, error)
    GetByID(ctx context.Context, id int64) (*entity.User, error)
    List(ctx context.Context, req *request.ListUserRequest) ([]*entity.User, int64, error)
    Update(ctx context.Context, id int64, req *request.UpdateUserRequest) error
    Delete(ctx context.Context, id int64) error
    Login(ctx context.Context, req *request.LoginRequest) (*auth.TokenPair, error)
    ChangePassword(ctx context.Context, userID int64, req *request.ChangePasswordRequest) error
}

type userService struct {
    userRepo   repository.UserRepository
    userCache  cache.UserCache
    txManager  database.TxManager
    jwtManager *auth.JWTManager
    logger     *zap.Logger
}

func NewUserService(
    userRepo repository.UserRepository,
    userCache cache.UserCache,
    txManager database.TxManager,
    jwtManager *auth.JWTManager,
    logger *zap.Logger,
) UserService {
    return &userService{
        userRepo:   userRepo,
        userCache:  userCache,
        txManager:  txManager,
        jwtManager: jwtManager,
        logger:     logger,
    }
}

func (s *userService) Create(ctx context.Context, req *request.CreateUserRequest) (*entity.User, error) {
    // 检查邮箱是否已存在
    existing, err := s.userRepo.FindByEmail(ctx, req.Email)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if existing != nil {
        return nil, errors.ErrUserExists
    }
    
    // 检查用户名是否已存在
    existing, err = s.userRepo.FindByUsername(ctx, req.Username)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if existing != nil {
        return nil, errors.NewWithMessage(errors.CodeConflict, "用户名已存在")
    }
    
    // 密码加密
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    
    user := &entity.User{
        Username:  req.Username,
        Email:     req.Email,
        Mobile:    req.Mobile,
        Password:  string(hashedPassword),
        RoleID:    req.RoleID,
        Status:    entity.UserStatusActive,
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }
    
    if err := s.userRepo.Create(ctx, user); err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    
    s.logger.Info("user created",
        zap.Int64("user_id", user.ID),
        zap.String("username", user.Username))
    
    return user, nil
}

func (s *userService) GetByID(ctx context.Context, id int64) (*entity.User, error) {
    // 先查缓存
    user, err := s.userCache.GetUser(ctx, id)
    if err == nil {
        return user, nil
    }
    
    // 查数据库
    user, err = s.userRepo.FindByID(ctx, id)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return nil, errors.ErrUserNotFound
    }
    
    // 异步写入缓存
    go func() {
        if err := s.userCache.SetUser(context.Background(), user); err != nil {
            s.logger.Warn("failed to cache user", zap.Error(err))
        }
    }()
    
    return user, nil
}

func (s *userService) List(ctx context.Context, req *request.ListUserRequest) ([]*entity.User, int64, error) {
    users, total, err := s.userRepo.FindByCondition(ctx, &repository.UserQuery{
        Keyword:  req.Keyword,
        Status:   req.Status,
        Page:     req.Page,
        PageSize: req.PageSize,
        OrderBy:  req.OrderBy,
        Order:    req.Order,
    })
    if err != nil {
        return nil, 0, errors.Wrap(errors.CodeInternalError, err)
    }
    
    return users, total, nil
}

func (s *userService) Update(ctx context.Context, id int64, req *request.UpdateUserRequest) error {
    user, err := s.userRepo.FindByID(ctx, id)
    if err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return errors.ErrUserNotFound
    }
    
    // 检查用户名唯一性
    if req.Username != nil && *req.Username != user.Username {
        existing, err := s.userRepo.FindByUsername(ctx, *req.Username)
        if err != nil {
            return errors.Wrap(errors.CodeInternalError, err)
        }
        if existing != nil {
            return errors.NewWithMessage(errors.CodeConflict, "用户名已存在")
        }
        user.Username = *req.Username
    }
    
    if req.Email != nil {
        user.Email = *req.Email
    }
    if req.Mobile != nil {
        user.Mobile = *req.Mobile
    }
    if req.Status != nil {
        user.Status = *req.Status
    }
    
    user.UpdatedAt = time.Now()
    
    if err := s.userRepo.Update(ctx, user); err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    
    // 删除缓存
    _ = s.userCache.DeleteUser(ctx, id)
    
    return nil
}

func (s *userService) Delete(ctx context.Context, id int64) error {
    user, err := s.userRepo.FindByID(ctx, id)
    if err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return errors.ErrUserNotFound
    }
    
    // 软删除
    if err := s.userRepo.Delete(ctx, id); err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    
    // 删除缓存
    _ = s.userCache.DeleteUser(ctx, id)
    
    s.logger.Info("user deleted", zap.Int64("user_id", id))
    
    return nil
}

func (s *userService) Login(ctx context.Context, req *request.LoginRequest) (*auth.TokenPair, error) {
    user, err := s.userRepo.FindByUsername(ctx, req.Username)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return nil, errors.ErrUserNotFound
    }
    
    // 验证密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
        return nil, errors.ErrPasswordWrong
    }
    
    // 检查用户状态
    if user.Status != entity.UserStatusActive {
        return nil, errors.NewWithMessage(errors.CodeForbidden, "用户已被禁用")
    }
    
    // 生成 Token
    tokenPair, err := s.jwtManager.GenerateTokenPair(user.ID, user.Username, user.RoleID)
    if err != nil {
        return nil, errors.Wrap(errors.CodeInternalError, err)
    }
    
    // 更新最后登录时间
    go func() {
        _ = s.userRepo.UpdateLastLogin(context.Background(), user.ID, time.Now())
    }()
    
    s.logger.Info("user logged in",
        zap.Int64("user_id", user.ID),
        zap.String("username", user.Username))
    
    return tokenPair, nil
}

func (s *userService) ChangePassword(ctx context.Context, userID int64, req *request.ChangePasswordRequest) error {
    user, err := s.userRepo.FindByID(ctx, userID)
    if err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    if user == nil {
        return errors.ErrUserNotFound
    }
    
    // 验证旧密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
        return errors.NewWithMessage(errors.CodeBadRequest, "原密码错误")
    }
    
    // 加密新密码
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
    if err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    
    user.Password = string(hashedPassword)
    user.UpdatedAt = time.Now()
    
    if err := s.userRepo.Update(ctx, user); err != nil {
        return errors.Wrap(errors.CodeInternalError, err)
    }
    
    return nil
}

Java 实现

// service/UserService.java
package com.example.service;

import com.example.dto.request.*;
import com.example.dto.response.*;
import com.example.entity.User;

public interface UserService {
    UserResponse create(CreateUserRequest request);
    UserResponse getById(Long id);
    UserDetailResponse getDetail(Long id);
    PageResult<UserResponse> list(ListUserRequest request);
    void update(Long id, UpdateUserRequest request);
    void delete(Long id);
    TokenPairResponse login(LoginRequest request);
    void changePassword(Long userId, ChangePasswordRequest request);
    void updateStatus(Long id, Integer status);
}

// service/impl/UserServiceImpl.java
package com.example.service.impl;

import com.example.dto.request.*;
import com.example.dto.response.*;
import com.example.entity.User;
import com.example.enums.ErrorCode;
import com.example.enums.UserStatus;
import com.example.exception.BusinessException;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import com.example.util.JwtUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.*;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
    
    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserResponse create(CreateUserRequest request) {
        // 检查邮箱是否已存在
        if (userMapper.selectByEmail(request.getEmail()) != null) {
            throw new BusinessException(ErrorCode.USER_EXISTS, "邮箱已被使用");
        }
        
        // 检查用户名是否已存在
        if (userMapper.selectByUsername(request.getUsername()) != null) {
            throw new BusinessException(ErrorCode.CONFLICT, "用户名已存在");
        }
        
        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setMobile(request.getMobile());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setRoleId(request.getRoleId());
        user.setStatus(UserStatus.ACTIVE.getCode());
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());
        
        userMapper.insert(user);
        
        log.info("User created: userId={}, username={}", user.getId(), user.getUsername());
        
        return UserResponse.from(user);
    }
    
    @Override
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public UserResponse getById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        return UserResponse.from(user);
    }
    
    @Override
    @Cacheable(value = "user:detail", key = "#id", unless = "#result == null")
    public UserDetailResponse getDetail(Long id) {
        User user = userMapper.selectWithRoleById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        return UserDetailResponse.from(user);
    }
    
    @Override
    public PageResult<UserResponse> list(ListUserRequest request) {
        Page<User> page = new Page<>(request.getPage(), request.getPageSize());
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 关键词搜索
        if (StringUtils.hasText(request.getKeyword())) {
            wrapper.and(w -> w
                .like(User::getUsername, request.getKeyword())
                .or()
                .like(User::getEmail, request.getKeyword())
                .or()
                .like(User::getMobile, request.getKeyword())
            );
        }
        
        // 状态过滤
        if (request.getStatus() != null) {
            wrapper.eq(User::getStatus, request.getStatus());
        }
        
        // 排序
        if ("created_at".equals(request.getOrderBy())) {
            wrapper.orderBy(true, "asc".equals(request.getOrder()), User::getCreatedAt);
        } else if ("updated_at".equals(request.getOrderBy())) {
            wrapper.orderBy(true, "asc".equals(request.getOrder()), User::getUpdatedAt);
        }
        
        Page<User> result = userMapper.selectPage(page, wrapper);
        
        return PageResult.of(
            result.getRecords().stream().map(UserResponse::from).toList(),
            result.getTotal(),
            request.getPage(),
            request.getPageSize()
        );
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"user", "user:detail"}, key = "#id")
    public void update(Long id, UpdateUserRequest request) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 检查用户名唯一性
        if (StringUtils.hasText(request.getUsername()) 
                && !request.getUsername().equals(user.getUsername())) {
            if (userMapper.selectByUsername(request.getUsername()) != null) {
                throw new BusinessException(ErrorCode.CONFLICT, "用户名已存在");
            }
            user.setUsername(request.getUsername());
        }
        
        if (StringUtils.hasText(request.getEmail())) {
            user.setEmail(request.getEmail());
        }
        if (StringUtils.hasText(request.getMobile())) {
            user.setMobile(request.getMobile());
        }
        if (request.getStatus() != null) {
            user.setStatus(request.getStatus());
        }
        
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        
        log.info("User updated: userId={}", id);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
        @CacheEvict(value = "user", key = "#id"),
        @CacheEvict(value = "user:detail", key = "#id")
    })
    public void delete(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 软删除
        userMapper.deleteById(id);
        
        log.info("User deleted: userId={}", id);
    }
    
    @Override
    public TokenPairResponse login(LoginRequest request) {
        User user = userMapper.selectByUsername(request.getUsername());
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 验证密码
        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new BusinessException(ErrorCode.PASSWORD_WRONG);
        }
        
        // 检查用户状态
        if (!UserStatus.ACTIVE.getCode().equals(user.getStatus())) {
            throw new BusinessException(ErrorCode.FORBIDDEN, "用户已被禁用");
        }
        
        // 生成 Token
        JwtUtil.TokenPair tokenPair = jwtUtil.generateTokenPair(
            user.getId(), user.getUsername(), user.getRoleId());
        
        // 异步更新最后登录时间
        updateLastLoginAsync(user.getId());
        
        log.info("User logged in: userId={}, username={}", user.getId(), user.getUsername());
        
        return TokenPairResponse.from(tokenPair);
    }
    
    @Async
    protected void updateLastLoginAsync(Long userId) {
        try {
            userMapper.updateLastLogin(userId, LocalDateTime.now());
        } catch (Exception e) {
            log.warn("Failed to update last login time: userId={}", userId, e);
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changePassword(Long userId, ChangePasswordRequest request) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 验证旧密码
        if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "原密码错误");
        }
        
        // 更新密码
        user.setPassword(passwordEncoder.encode(request.getNewPassword()));
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        
        log.info("User password changed: userId={}", userId);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"user", "user:detail"}, key = "#id")
    public void updateStatus(Long id, Integer status) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        
        // 状态流转校验
        if (UserStatus.DELETED.getCode().equals(user.getStatus())) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "已删除的用户不能修改状态");
        }
        
        user.setStatus(status);
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        
        log.info("User status updated: userId={}, status={}", id, status);
    }
}

Service 层对比表

方面 Go Java
接口定义 interface 类型 interface 关键字
依赖注入 构造函数手动注入 @Autowired / @RequiredArgsConstructor
事务管理 txManager.WithTransaction @Transactional 注解
缓存操作 手动调用 cache 方法 @Cacheable/@CacheEvict 注解
密码加密 bcrypt 包手动调用 PasswordEncoder Bean
日志记录 zap.Logger 字段日志 @Slf4j + log.info()
异步处理 go func() 协程 @Async 注解
错误返回 return nil, error throw Exception

12.3 Repository / Mapper 层

Go 实现

// internal/repository/user_repository.go
package repository

import (
    "context"
    "time"
    
    "myapp/internal/entity"
    "gorm.io/gorm"
)

type UserQuery struct {
    Keyword  string
    Status   *int
    RoleID   *int64
    Page     int
    PageSize int
    OrderBy  string
    Order    string
}

type UserRepository interface {
    Create(ctx context.Context, user *entity.User) error
    Update(ctx context.Context, user *entity.User) error
    Delete(ctx context.Context, id int64) error
    FindByID(ctx context.Context, id int64) (*entity.User, error)
    FindByEmail(ctx context.Context, email string) (*entity.User, error)
    FindByUsername(ctx context.Context, username string) (*entity.User, error)
    FindByCondition(ctx context.Context, query *UserQuery) ([]*entity.User, int64, error)
    UpdateLastLogin(ctx context.Context, id int64, loginTime time.Time) error
    BatchUpdateStatus(ctx context.Context, ids []int64, status int) error
}

type userRepository struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
    return &userRepository{db: db}
}

// getDB 获取数据库连接(支持事务)
func (r *userRepository) getDB(ctx context.Context) *gorm.DB {
    if tx, ok := ctx.Value(database.TxKey{}).(*gorm.DB); ok {
        return tx
    }
    return r.db.WithContext(ctx)
}

func (r *userRepository) Create(ctx context.Context, user *entity.User) error {
    return r.getDB(ctx).Create(user).Error
}

func (r *userRepository) Update(ctx context.Context, user *entity.User) error {
    return r.getDB(ctx).Save(user).Error
}

func (r *userRepository) Delete(ctx context.Context, id int64) error {
    // 软删除
    return r.getDB(ctx).Delete(&entity.User{}, id).Error
}

func (r *userRepository) FindByID(ctx context.Context, id int64) (*entity.User, error) {
    var user entity.User
    err := r.getDB(ctx).First(&user, id).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}

func (r *userRepository) FindByEmail(ctx context.Context, email string) (*entity.User, error) {
    var user entity.User
    err := r.getDB(ctx).Where("email = ?", email).First(&user).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}

func (r *userRepository) FindByUsername(ctx context.Context, username string) (*entity.User, error) {
    var user entity.User
    err := r.getDB(ctx).Where("username = ?", username).First(&user).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}

func (r *userRepository) FindByCondition(ctx context.Context, query *UserQuery) ([]*entity.User, int64, error) {
    db := r.getDB(ctx).Model(&entity.User{})
    
    // 关键词搜索
    if query.Keyword != "" {
        keyword := "%" + query.Keyword + "%"
        db = db.Where("username LIKE ? OR email LIKE ? OR mobile LIKE ?", 
            keyword, keyword, keyword)
    }
    
    // 状态过滤
    if query.Status != nil {
        db = db.Where("status = ?", *query.Status)
    }
    
    // 角色过滤
    if query.RoleID != nil {
        db = db.Where("role_id = ?", *query.RoleID)
    }
    
    // 统计总数
    var total int64
    if err := db.Count(&total).Error; err != nil {
        return nil, 0, err
    }
    
    // 排序
    orderClause := "created_at DESC"
    if query.OrderBy != "" {
        order := "DESC"
        if query.Order == "asc" {
            order = "ASC"
        }
        orderClause = query.OrderBy + " " + order
    }
    db = db.Order(orderClause)
    
    // 分页
    offset := (query.Page - 1) * query.PageSize
    db = db.Offset(offset).Limit(query.PageSize)
    
    var users []*entity.User
    if err := db.Find(&users).Error; err != nil {
        return nil, 0, err
    }
    
    return users, total, nil
}

func (r *userRepository) UpdateLastLogin(ctx context.Context, id int64, loginTime time.Time) error {
    return r.getDB(ctx).Model(&entity.User{}).
        Where("id = ?", id).
        Update("last_login_at", loginTime).Error
}

func (r *userRepository) BatchUpdateStatus(ctx context.Context, ids []int64, status int) error {
    return r.getDB(ctx).Model(&entity.User{}).
        Where("id IN ?", ids).
        Updates(map[string]interface{}{
            "status":     status,
            "updated_at": time.Now(),
        }).Error
}

// 使用原生 SQL 的复杂查询示例
func (r *userRepository) FindWithRoleByID(ctx context.Context, id int64) (*entity.UserWithRole, error) {
    var result entity.UserWithRole
    
    err := r.getDB(ctx).Raw(`
        SELECT u.*, r.name as role_name, r.code as role_code
        FROM users u
        LEFT JOIN roles r ON u.role_id = r.id
        WHERE u.id = ? AND u.deleted_at IS NULL
    `, id).Scan(&result).Error
    
    if err != nil {
        return nil, err
    }
    if result.ID == 0 {
        return nil, nil
    }
    
    return &result, nil
}

// 悲观锁查询
func (r *userRepository) FindByIDForUpdate(ctx context.Context, id int64) (*entity.User, error) {
    var user entity.User
    err := r.getDB(ctx).
        Clauses(clause.Locking{Strength: "UPDATE"}).
        First(&user, id).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}

Java 实现(MyBatis-Plus)

// mapper/UserMapper.java
package com.example.mapper;

import com.example.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.*;

import java.time.LocalDateTime;
import java.util.List;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    @Select("SELECT * FROM users WHERE email = #{email} AND deleted_at IS NULL")
    User selectByEmail(@Param("email") String email);
    
    @Select("SELECT * FROM users WHERE username = #{username} AND deleted_at IS NULL")
    User selectByUsername(@Param("username") String username);
    
    @Update("UPDATE users SET last_login_at = #{loginTime} WHERE id = #{id}")
    void updateLastLogin(@Param("id") Long id, @Param("loginTime") LocalDateTime loginTime);
    
    @Update("<script>" +
            "UPDATE users SET status = #{status}, updated_at = NOW() " +
            "WHERE id IN " +
            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
            "#{id}" +
            "</foreach>" +
            "</script>")
    void batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status);
    
    // 关联查询
    @Select("SELECT u.*, r.name as role_name, r.code as role_code " +
            "FROM users u " +
            "LEFT JOIN roles r ON u.role_id = r.id " +
            "WHERE u.id = #{id} AND u.deleted_at IS NULL")
    @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "username", column = "username"),
        @Result(property = "email", column = "email"),
        @Result(property = "role.name", column = "role_name"),
        @Result(property = "role.code", column = "role_code")
    })
    User selectWithRoleById(@Param("id") Long id);
    
    // 悲观锁查询
    @Select("SELECT * FROM users WHERE id = #{id} FOR UPDATE")
    User selectByIdForUpdate(@Param("id") Long id);
    
    // 复杂统计查询
    @Select("SELECT DATE(created_at) as date, COUNT(*) as count " +
            "FROM users " +
            "WHERE created_at >= #{startDate} AND created_at < #{endDate} " +
            "GROUP BY DATE(created_at) " +
            "ORDER BY date")
    List<UserDailyStats> selectDailyStats(@Param("startDate") LocalDateTime startDate,
                                          @Param("endDate") LocalDateTime endDate);
}
// mapper/UserMapper.xml(复杂查询用 XML)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <resultMap id="UserWithRoleMap" type="com.example.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="mobile" column="mobile"/>
        <result property="status" column="status"/>
        <result property="createdAt" column="created_at"/>
        <association property="role" javaType="com.example.entity.Role">
            <id property="id" column="role_id"/>
            <result property="name" column="role_name"/>
            <result property="code" column="role_code"/>
        </association>
    </resultMap>
    
    <select id="selectUsersWithRole" resultMap="UserWithRoleMap">
        SELECT 
            u.id, u.username, u.email, u.mobile, u.status, u.created_at,
            r.id as role_id, r.name as role_name, r.code as role_code
        FROM users u
        LEFT JOIN roles r ON u.role_id = r.id
        WHERE u.deleted_at IS NULL
        <if test="query.keyword != null and query.keyword != ''">
            AND (u.username LIKE CONCAT('%', #{query.keyword}, '%')
                 OR u.email LIKE CONCAT('%', #{query.keyword}, '%')
                 OR u.mobile LIKE CONCAT('%', #{query.keyword}, '%'))
        </if>
        <if test="query.status != null">
            AND u.status = #{query.status}
        </if>
        <if test="query.roleId != null">
            AND u.role_id = #{query.roleId}
        </if>
        ORDER BY 
        <choose>
            <when test="query.orderBy == 'created_at'">
                u.created_at
            </when>
            <when test="query.orderBy == 'updated_at'">
                u.updated_at
            </when>
            <otherwise>
                u.created_at
            </otherwise>
        </choose>
        <if test="query.order == 'asc'">ASC</if>
        <if test="query.order != 'asc'">DESC</if>
        LIMIT #{query.pageSize} OFFSET #{query.offset}
    </select>
    
    <select id="countUsersWithCondition" resultType="long">
        SELECT COUNT(*)
        FROM users u
        WHERE u.deleted_at IS NULL
        <if test="query.keyword != null and query.keyword != ''">
            AND (u.username LIKE CONCAT('%', #{query.keyword}, '%')
                 OR u.email LIKE CONCAT('%', #{query.keyword}, '%'))
        </if>
        <if test="query.status != null">
            AND u.status = #{query.status}
        </if>
    </select>
    
</mapper>
// Java JPA Repository 实现(对比)
// repository/UserRepository.java
package com.example.repository;

import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import javax.persistence.LockModeType;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long>, 
                                        JpaSpecificationExecutor<User> {
    
    Optional<User> findByEmail(String email);
    
    Optional<User> findByUsername(String username);
    
    @Modifying
    @Query("UPDATE User u SET u.lastLoginAt = :loginTime WHERE u.id = :id")
    void updateLastLogin(@Param("id") Long id, @Param("loginTime") LocalDateTime loginTime);
    
    @Modifying
    @Query("UPDATE User u SET u.status = :status, u.updatedAt = CURRENT_TIMESTAMP WHERE u.id IN :ids")
    void batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status);
    
    // 悲观锁
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT u FROM User u WHERE u.id = :id")
    Optional<User> findByIdForUpdate(@Param("id") Long id);
    
    // 自定义查询
    @Query("SELECT u FROM User u LEFT JOIN FETCH u.role WHERE u.id = :id")
    Optional<User> findWithRoleById(@Param("id") Long id);
    
    // 统计查询
    @Query(value = "SELECT DATE(created_at) as date, COUNT(*) as count " +
                   "FROM users WHERE created_at >= :start AND created_at < :end " +
                   "GROUP BY DATE(created_at)", nativeQuery = true)
    List<Object[]> findDailyStats(@Param("start") LocalDateTime start,
                                  @Param("end") LocalDateTime end);
    
    // 存在性检查
    boolean existsByEmail(String email);
    
    boolean existsByUsername(String username);
    
    // 软删除
    @Modifying
    @Query("UPDATE User u SET u.deletedAt = CURRENT_TIMESTAMP WHERE u.id = :id")
    void softDelete(@Param("id") Long id);
}

Repository/Mapper 层对比表

方面 Go (GORM) Java (MyBatis) Java (JPA)
定义方式 interface + struct 实现 interface + 注解/XML interface 继承 JpaRepository
简单查询 db.First/Find @Select 注解 方法名派生查询
复杂查询 链式调用 / Raw SQL XML 动态 SQL @Query / Specification
分页 Offset/Limit PageHelper / 手动 Pageable 参数
事务支持 Context 传递 tx 自动参与事务 自动参与事务
悲观锁 clause.Locking FOR UPDATE @Lock 注解
批量操作 Where IN + Updates foreach 标签 @Modifying + @Query
关联查询 Preload / Joins / Raw resultMap / 嵌套查询 FETCH JOIN

12.4 Entity / Model 层

Go 实现

// internal/entity/user.go
package entity

import (
    "time"
    "gorm.io/gorm"
)

// 用户状态
const (
    UserStatusActive   = 1
    UserStatusDisabled = 2
    UserStatusDeleted  = 3
)

type User struct {
    ID          int64          `gorm:"primaryKey;autoIncrement" json:"id"`
    Username    string         `gorm:"size:32;uniqueIndex;not null" json:"username"`
    Email       string         `gorm:"size:128;uniqueIndex;not null" json:"email"`
    Mobile      string         `gorm:"size:20;index" json:"mobile"`
    Password    string         `gorm:"size:128;not null" json:"-"` // json:"-" 不序列化
    RoleID      int64          `gorm:"index;not null" json:"role_id"`
    Status      int            `gorm:"default:1;not null" json:"status"`
    Avatar      string         `gorm:"size:256" json:"avatar"`
    LastLoginAt *time.Time     `json:"last_login_at"`
    CreatedAt   time.Time      `gorm:"autoCreateTime" json:"created_at"`
    UpdatedAt   time.Time      `gorm:"autoUpdateTime" json:"updated_at"`
    DeletedAt   gorm.DeletedAt `gorm:"index" json:"-"` // 软删除
    
    // 关联(不映射到数据库)
    Role *Role `gorm:"foreignKey:RoleID" json:"role,omitempty"`
}

func (User) TableName() string {
    return "users"
}

// IsActive 判断用户是否激活
func (u *User) IsActive() bool {
    return u.Status == UserStatusActive
}

// 带角色信息的用户(用于关联查询结果)
type UserWithRole struct {
    User
    RoleName string `json:"role_name"`
    RoleCode string `json:"role_code"`
}

// internal/entity/role.go
type Role struct {
    ID          int64     `gorm:"primaryKey" json:"id"`
    Name        string    `gorm:"size:32;not null" json:"name"`
    Code        string    `gorm:"size:32;uniqueIndex;not null" json:"code"`
    Description string    `gorm:"size:256" json:"description"`
    Status      int       `gorm:"default:1" json:"status"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
    
    Permissions []Permission `gorm:"many2many:role_permissions" json:"permissions,omitempty"`
}

// internal/entity/permission.go
type Permission struct {
    ID        int64     `gorm:"primaryKey" json:"id"`
    Name      string    `gorm:"size:64;not null" json:"name"`
    Code      string    `gorm:"size:64;uniqueIndex;not null" json:"code"`
    Resource  string    `gorm:"size:32" json:"resource"`
    Action    string    `gorm:"size:32" json:"action"`
    CreatedAt time.Time `json:"created_at"`
}

// internal/entity/order.go
type Order struct {
    ID          int64          `gorm:"primaryKey" json:"id"`
    OrderNo     string         `gorm:"size:32;uniqueIndex;not null" json:"order_no"`
    UserID      int64          `gorm:"index;not null" json:"user_id"`
    ProductID   int64          `gorm:"index;not null" json:"product_id"`
    Quantity    int            `gorm:"not null" json:"quantity"`
    TotalPrice  float64        `gorm:"type:decimal(10,2);not null" json:"total_price"`
    Status      int            `gorm:"default:1;not null" json:"status"`
    PaymentTime *time.Time     `json:"payment_time"`
    CreatedAt   time.Time      `json:"created_at"`
    UpdatedAt   time.Time      `json:"updated_at"`
    DeletedAt   gorm.DeletedAt `gorm:"index" json:"-"`
    
    User    *User    `gorm:"foreignKey:UserID" json:"user,omitempty"`
    Product *Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
}

// 订单状态
const (
    OrderStatusPending   = 1 // 待支付
    OrderStatusPaid      = 2 // 已支付
    OrderStatusShipped   = 3 // 已发货
    OrderStatusCompleted = 4 // 已完成
    OrderStatusCancelled = 5 // 已取消
)

// CanCancel 判断订单是否可取消
func (o *Order) CanCancel() bool {
    return o.Status == OrderStatusPending || o.Status == OrderStatusPaid
}

Java 实现

// entity/User.java
package com.example.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("users")
public class User {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    private String email;
    
    private String mobile;
    
    private String password;
    
    private Long roleId;
    
    private Integer status;
    
    private String avatar;
    
    private LocalDateTime lastLoginAt;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedAt;
    
    @TableLogic  // 逻辑删除
    private LocalDateTime deletedAt;
    
    // 非数据库字段
    @TableField(exist = false)
    private Role role;
    
    public boolean isActive() {
        return UserStatus.ACTIVE.getCode().equals(this.status);
    }
}

// entity/User.java(JPA 版本)
package com.example.entity;

import lombok.Data;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import javax.persistence.*;
import java.time.LocalDateTime;

@Data
@Entity
@Table(name = "users", indexes = {
    @Index(name = "idx_email", columnList = "email"),
    @Index(name = "idx_username", columnList = "username"),
    @Index(name = "idx_role_id", columnList = "role_id")
})
@SQLDelete(sql = "UPDATE users SET deleted_at = NOW() WHERE id = ?")
@Where(clause = "deleted_at IS NULL")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(length = 32, nullable = false, unique = true)
    private String username;
    
    @Column(length = 128, nullable = false, unique = true)
    private String email;
    
    @Column(length = 20)
    private String mobile;
    
    @Column(length = 128, nullable = false)
    private String password;
    
    @Column(name = "role_id", nullable = false)
    private Long roleId;
    
    @Column(nullable = false)
    private Integer status = 1;
    
    @Column(length = 256)
    private String avatar;
    
    @Column(name = "last_login_at")
    private LocalDateTime lastLoginAt;
    
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @Column(name = "deleted_at")
    private LocalDateTime deletedAt;
    
    // 关联关系
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "role_id", insertable = false, updatable = false)
    private Role role;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    public boolean isActive() {
        return UserStatus.ACTIVE.getCode().equals(this.status);
    }
}

// enums/UserStatus.java
package com.example.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum UserStatus {
    ACTIVE(1, "正常"),
    DISABLED(2, "禁用"),
    DELETED(3, "删除");
    
    private final Integer code;
    private final String description;
    
    public static UserStatus fromCode(Integer code) {
        for (UserStatus status : values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        return null;
    }
}

// entity/Role.java
@Data
@Entity
@Table(name = "roles")
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(length = 32, nullable = false)
    private String name;
    
    @Column(length = 32, nullable = false, unique = true)
    private String code;
    
    @Column(length = 256)
    private String description;
    
    private Integer status = 1;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private List<Permission> permissions;
}

// entity/Order.java
@Data
@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_no", length = 32, nullable = false, unique = true)
    private String orderNo;
    
    @Column(name = "user_id", nullable = false)
    private Long userId;
    
    @Column(name = "product_id", nullable = false)
    private Long productId;
    
    @Column(nullable = false)
    private Integer quantity;
    
    @Column(name = "total_price", precision = 10, scale = 2, nullable = false)
    private BigDecimal totalPrice;
    
    @Column(nullable = false)
    private Integer status = 1;
    
    @Column(name = "payment_time")
    private LocalDateTime paymentTime;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", insertable = false, updatable = false)
    private User user;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", insertable = false, updatable = false)
    private Product product;
    
    public boolean canCancel() {
        return OrderStatus.PENDING.getCode().equals(status) 
            || OrderStatus.PAID.getCode().equals(status);
    }
}

Entity 层对比表

方面 Go (GORM) Java (MyBatis-Plus) Java (JPA)
主键定义 gorm:"primaryKey" @TableId @Id + @GeneratedValue
自增 autoIncrement IdType.AUTO GenerationType.IDENTITY
表名 TableName() 方法 @TableName @Table(name=)
字段映射 自动驼峰转下划线 自动转换 @Column(name=)
唯一索引 uniqueIndex 数据库层定义 unique = true
软删除 gorm.DeletedAt @TableLogic @SQLDelete + @Where
自动时间 autoCreateTime @TableField(fill=) @PrePersist
关联关系 gorm:"foreignKey" @TableField(exist=false) @ManyToOne
忽略字段 gorm:"-" @TableField(exist=false) @Transient
JSON 忽略 json:"-" @JsonIgnore @JsonIgnore

12.5 DTO 层

Go 实现

// internal/dto/request/user_request.go
package request

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=32"`
    Email    string `json:"email" binding:"required,email"`
    Mobile   string `json:"mobile" binding:"required,mobile"`
    Password string `json:"password" binding:"required,min=8,max=64"`
    RoleID   int64  `json:"role_id" binding:"required,gt=0"`
}

// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
    Username *string `json:"username" binding:"omitempty,min=3,max=32"`
    Email    *string `json:"email" binding:"omitempty,email"`
    Mobile   *string `json:"mobile" binding:"omitempty,mobile"`
    Status   *int    `json:"status" binding:"omitempty,oneof=1 2"`
    Avatar   *string `json:"avatar" binding:"omitempty,url"`
}

// ListUserRequest 用户列表请求
type ListUserRequest struct {
    Page     int    `form:"page" binding:"omitempty,gte=1"`
    PageSize int    `form:"page_size" binding:"omitempty,gte=1,lte=100"`
    Keyword  string `form:"keyword" binding:"omitempty,max=64"`
    Status   *int   `form:"status" binding:"omitempty,oneof=1 2 3"`
    RoleID   *int64 `form:"role_id" binding:"omitempty,gt=0"`
    OrderBy  string `form:"order_by" binding:"omitempty,oneof=created_at updated_at"`
    Order    string `form:"order" binding:"omitempty,oneof=asc desc"`
}

func (r *ListUserRequest) SetDefaults() {
    if r.Page <= 0 {
        r.Page = 1
    }
    if r.PageSize <= 0 {
        r.PageSize = 20
    }
    if r.OrderBy == "" {
        r.OrderBy = "created_at"
    }
    if r.Order == "" {
        r.Order = "desc"
    }
}

func (r *ListUserRequest) Offset() int {
    return (r.Page - 1) * r.PageSize
}

// LoginRequest 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
    OldPassword string `json:"old_password" binding:"required"`
    NewPassword string `json:"new_password" binding:"required,min=8,max=64,nefield=OldPassword"`
}

// internal/dto/response/user_response.go
package response

import (
    "myapp/internal/entity"
    "time"
)

// UserResponse 用户响应
type UserResponse struct {
    ID        int64     `json:"id"`
    Username  string    `json:"username"`
    Email     string    `json:"email"`
    Mobile    string    `json:"mobile"`
    RoleID    int64     `json:"role_id"`
    Status    int       `json:"status"`
    Avatar    string    `json:"avatar"`
    CreatedAt time.Time `json:"created_at"`
}

// ToUserResponse 转换实体到响应
func ToUserResponse(user *entity.User) *UserResponse {
    if user == nil {
        return nil
    }
    return &UserResponse{
        ID:        user.ID,
        Username:  user.Username,
        Email:     user.Email,
        Mobile:    maskMobile(user.Mobile),
        RoleID:    user.RoleID,
        Status:    user.Status,
        Avatar:    user.Avatar,
        CreatedAt: user.CreatedAt,
    }
}

// UserDetailResponse 用户详情响应
type UserDetailResponse struct {
    ID          int64      `json:"id"`
    Username    string     `json:"username"`
    Email       string     `json:"email"`
    Mobile      string     `json:"mobile"`
    RoleID      int64      `json:"role_id"`
    RoleName    string     `json:"role_name"`
    Status      int        `json:"status"`
    StatusText  string     `json:"status_text"`
    Avatar      string     `json:"avatar"`
    LastLoginAt *time.Time `json:"last_login_at"`
    CreatedAt   time.Time  `json:"created_at"`
    UpdatedAt   time.Time  `json:"updated_at"`
}

func ToUserDetailResponse(user *entity.User) *UserDetailResponse {
    if user == nil {
        return nil
    }
    resp := &UserDetailResponse{
        ID:          user.ID,
        Username:    user.Username,
        Email:       user.Email,
        Mobile:      maskMobile(user.Mobile),
        RoleID:      user.RoleID,
        Status:      user.Status,
        StatusText:  getStatusText(user.Status),
        Avatar:      user.Avatar,
        LastLoginAt: user.LastLoginAt,
        CreatedAt:   user.CreatedAt,
        UpdatedAt:   user.UpdatedAt,
    }
    if user.Role != nil {
        resp.RoleName = user.Role.Name
    }
    return resp
}

// 手机号脱敏
func maskMobile(mobile string) string {
    if len(mobile) != 11 {
        return mobile
    }
    return mobile[:3] + "****" + mobile[7:]
}

func getStatusText(status int) string {
    switch status {
    case 1:
        return "正常"
    case 2:
        return "禁用"
    case 3:
        return "删除"
    default:
        return "未知"
    }
}

// TokenPairResponse Token 响应
type TokenPairResponse struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    ExpiresIn    int64  `json:"expires_in"`
    TokenType    string `json:"token_type"`
}

func ToTokenPairResponse(pair *auth.TokenPair) *TokenPairResponse {
    return &TokenPairResponse{
        AccessToken:  pair.AccessToken,
        RefreshToken: pair.RefreshToken,
        ExpiresIn:    pair.ExpiresIn,
        TokenType:    "Bearer",
    }
}

// internal/dto/response/common.go
package response

// Response 统一响应结构
type Response struct {
    Code      int         `json:"code"`
    Message   string      `json:"message"`
    Data      interface{} `json:"data,omitempty"`
    RequestID string      `json:"request_id,omitempty"`
    Timestamp int64       `json:"timestamp"`
}

// PageData 分页数据
type PageData struct {
    List     interface{} `json:"list"`
    Total    int64       `json:"total"`
    Page     int         `json:"page"`
    PageSize int         `json:"page_size"`
    Pages    int         `json:"pages"`
}

func NewPageData(list interface{}, total int64, page, pageSize int) *PageData {
    pages := int(total) / pageSize
    if int(total)%pageSize > 0 {
        pages++
    }
    return &PageData{
        List:     list,
        Total:    total,
        Page:     page,
        PageSize: pageSize,
        Pages:    pages,
    }
}

Java 实现

// dto/request/CreateUserRequest.java
package com.example.dto.request;

import com.example.validator.Mobile;
import lombok.Data;

import javax.validation.constraints.*;

@Data
public class CreateUserRequest {
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 32, message = "用户名长度需在3-32个字符之间")
    private String username;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @NotBlank(message = "手机号不能为空")
    @Mobile
    private String mobile;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, max = 64, message = "密码长度需在8-64个字符之间")
    private String password;
    
    @NotNull(message = "角色ID不能为空")
    @Positive(message = "角色ID必须为正数")
    private Long roleId;
}

// dto/request/UpdateUserRequest.java
@Data
public class UpdateUserRequest {
    
    @Size(min = 3, max = 32, message = "用户名长度需在3-32个字符之间")
    private String username;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Mobile
    private String mobile;
    
    @Min(value = 1)
    @Max(value = 2)
    private Integer status;
    
    @Pattern(regexp = "^https?://.*", message = "头像必须是有效的URL")
    private String avatar;
}

// dto/request/ListUserRequest.java
@Data
public class ListUserRequest {
    
    @Min(value = 1, message = "页码最小为1")
    private Integer page = 1;
    
    @Min(value = 1, message = "每页条数最小为1")
    @Max(value = 100, message = "每页条数最大为100")
    private Integer pageSize = 20;
    
    @Size(max = 64, message = "关键词最大64个字符")
    private String keyword;
    
    private Integer status;
    
    private Long roleId;
    
    @Pattern(regexp = "^(created_at|updated_at)$", message = "排序字段不合法")
    private String orderBy = "created_at";
    
    @Pattern(regexp = "^(asc|desc)$", message = "排序方向不合法")
    private String order = "desc";
    
    public int getOffset() {
        return (page - 1) * pageSize;
    }
}

// dto/request/LoginRequest.java
@Data
public class LoginRequest {
    
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    private String password;
}

// dto/request/ChangePasswordRequest.java
@Data
public class ChangePasswordRequest {
    
    @NotBlank(message = "原密码不能为空")
    private String oldPassword;
    
    @NotBlank(message = "新密码不能为空")
    @Size(min = 8, max = 64, message = "密码长度需在8-64个字符之间")
    private String newPassword;
    
    @AssertTrue(message = "新密码不能与原密码相同")
    private boolean isPasswordDifferent() {
        return newPassword == null || !newPassword.equals(oldPassword);
    }
}
// dto/response/UserResponse.java
package com.example.dto.response;

import com.example.entity.User;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class UserResponse {
    
    private Long id;
    private String username;
    private String email;
    private String mobile;
    private Long roleId;
    private Integer status;
    private String avatar;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
    
    public static UserResponse from(User user) {
        if (user == null) {
            return null;
        }
        UserResponse response = new UserResponse();
        response.setId(user.getId());
        response.setUsername(user.getUsername());
        response.setEmail(user.getEmail());
        response.setMobile(maskMobile(user.getMobile()));
        response.setRoleId(user.getRoleId());
        response.setStatus(user.getStatus());
        response.setAvatar(user.getAvatar());
        response.setCreatedAt(user.getCreatedAt());
        return response;
    }
    
    private static String maskMobile(String mobile) {
        if (mobile == null || mobile.length() != 11) {
            return mobile;
        }
        return mobile.substring(0, 3) + "****" + mobile.substring(7);
    }
}

// dto/response/UserDetailResponse.java
@Data
public class UserDetailResponse {
    
    private Long id;
    private String username;
    private String email;
    private String mobile;
    private Long roleId;
    private String roleName;
    private Integer status;
    private String statusText;
    private String avatar;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime lastLoginAt;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updatedAt;
    
    public static UserDetailResponse from(User user) {
        if (user == null) {
            return null;
        }
        UserDetailResponse response = new UserDetailResponse();
        response.setId(user.getId());
        response.setUsername(user.getUsername());
        response.setEmail(user.getEmail());
        response.setMobile(maskMobile(user.getMobile()));
        response.setRoleId(user.getRoleId());
        response.setStatus(user.getStatus());
        response.setStatusText(UserStatus.fromCode(user.getStatus()).getDescription());
        response.setAvatar(user.getAvatar());
        response.setLastLoginAt(user.getLastLoginAt());
        response.setCreatedAt(user.getCreatedAt());
        response.setUpdatedAt(user.getUpdatedAt());
        
        if (user.getRole() != null) {
            response.setRoleName(user.getRole().getName());
        }
        return response;
    }
}

// dto/response/TokenPairResponse.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenPairResponse {
    private String accessToken;
    private String refreshToken;
    private Long expiresIn;
    private String tokenType = "Bearer";
    
    public static TokenPairResponse from(JwtUtil.TokenPair pair) {
        return new TokenPairResponse(
            pair.getAccessToken(),
            pair.getRefreshToken(),
            pair.getExpiresIn(),
            "Bearer"
        );
    }
}

// dto/response/CommonResponse.java
@Data
@NoArgsConstructor
public class CommonResponse<T> {
    
    private int code;
    private String message;
    private T data;
    private String requestId;
    private long timestamp;
    
    public CommonResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.timestamp = System.currentTimeMillis();
        this.requestId = MDC.get("request_id");
    }
    
    public static <T> CommonResponse<T> success() {
        return new CommonResponse<>(0, "success", null);
    }
    
    public static <T> CommonResponse<T> success(T data) {
        return new CommonResponse<>(0, "success", data);
    }
    
    public static <T> CommonResponse<T> success(String message) {
        return new CommonResponse<>(0, message, null);
    }
    
    public static <T> CommonResponse<T> error(ErrorCode errorCode) {
        return new CommonResponse<>(errorCode.getCode(), errorCode.getMessage(), null);
    }
    
    public static <T> CommonResponse<T> error(ErrorCode errorCode, String message) {
        return new CommonResponse<>(errorCode.getCode(), message, null);
    }
}

// dto/response/PageResult.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
    
    private List<T> list;
    private long total;
    private int page;
    private int pageSize;
    private int pages;
    
    public static <T> PageResult<T> of(List<T> list, long total, int page, int pageSize) {
        PageResult<T> result = new PageResult<>();
        result.setList(list);
        result.setTotal(total);
        result.setPage(page);
        result.setPageSize(pageSize);
        result.setPages((int) Math.ceil((double) total / pageSize));
        return result;
    }
    
    public static <T, E> PageResult<T> of(IPage<E> page, Function<E, T> converter) {
        List<T> list = page.getRecords().stream()
                .map(converter)
                .collect(Collectors.toList());
        return of(list, page.getTotal(), (int) page.getCurrent(), (int) page.getSize());
    }
}

DTO 层对比表

方面 Go Java
请求绑定 struct tags (binding) @Valid + JSR-380 注解
可选字段 指针类型 (*string) 包装类型 (String)
默认值 SetDefaults() 方法 字段初始化
响应转换 ToXxxResponse 函数 static from() 方法
时间格式 json tag 或序列化器 @JsonFormat
数据脱敏 手动处理 手动处理
分页封装 PageData 结构体 PageResult 泛型类
统一响应 Response 结构体 CommonResponse 泛型类

十三、关键特性总结对比

13.1 核心差异总表

graph LR
    subgraph "Go 技术栈"
        G1[Gin/Echo] --> G2[手动 DI]
        G2 --> G3[GORM/sqlx]
        G3 --> G4[go-redis]
        G4 --> G5[Zap Logger]
    end
    
    subgraph "Java 技术栈"
        J1[Spring MVC] --> J2[Spring IoC]
        J2 --> J3[MyBatis/JPA]
        J3 --> J4[Spring Cache]
        J4 --> J5[Logback/Log4j2]
    end
    
    subgraph "共同关注点"
        C1[分层架构]
        C2[参数校验]
        C3[认证鉴权]
        C4[异常处理]
        C5[日志审计]
    end

13.2 设计理念对比

方面 Go Java
哲学 显式优于隐式 约定优于配置
依赖注入 构造函数手动组装 IoC 容器自动注入
AOP 实现 中间件 + 装饰器模式 动态代理 + 注解
错误处理 返回值 (value, error) 异常抛出 try-catch
并发模型 Goroutine + Channel Thread + CompletableFuture
配置管理 Viper + 结构体 @Value + Properties
代码生成 较少依赖 Lombok / MapStruct
框架侵入 低侵入 较高侵入

13.3 性能与资源对比

指标 Go Java (Spring Boot)
启动时间 ~100ms ~3-10s
内存占用 ~10-30MB ~200-500MB
编译产物 单二进制文件 JAR + JRE
冷启动 慢(适合预热)
GC 停顿 低延迟 G1/ZGC 可调优
并发处理 Goroutine 轻量 线程池管理

13.4 开发效率对比

方面 Go Java
学习曲线 语法简单,生态需学习 语法复杂,生态成熟
代码量 相对多(显式代码) 相对少(注解魔法)
IDE 支持 GoLand / VSCode IntelliJ IDEA
调试体验 Delve 调试器 成熟的 Debug 工具
重构支持 一般 强大
文档生成 swaggo SpringDoc/Swagger

13.5 适用场景建议

graph TB
    subgraph "Go 更适合"
        GA[高性能微服务]
        GB[云原生应用]
        GC[DevOps 工具]
        GD[网络密集型服务]
        GE[容器化部署]
    end
    
    subgraph "Java 更适合"
        JA[大型企业应用]
        JB[复杂业务系统]
        JC[遗留系统集成]
        JD[团队规模大]
        JE[生态依赖多]
    end
    
    subgraph "均可胜任"
        CA[REST API 服务]
        CB[CRUD 应用]
        CC[消息队列消费者]
        CD[定时任务服务]
    end

十四、附录:完整项目结构参考

Go 项目结构

myapp/
├── cmd/
│   └── server/
│       └── main.go                     # 程序入口
├── internal/
│   ├── handler/                        # HTTP 处理器
│   │   ├── user_handler.go
│   │   ├── order_handler.go
│   │   └── middleware/
│   │       ├── auth.go
│   │       ├── permission.go
│   │       ├── logger.go
│   │       ├── ratelimit.go
│   │       ├── recovery.go
│   │       └── cors.go
│   ├── service/                        # 业务逻辑
│   │   ├── user_service.go
│   │   ├── order_service.go
│   │   └── permission_service.go
│   ├── repository/                     # 数据访问
│   │   ├── user_repository.go
│   │   ├── order_repository.go
│   │   └── cache/
│   │       └── user_cache.go
│   ├── entity/                         # 实体定义
│   │   ├── user.go
│   │   ├── order.go
│   │   └── role.go
│   ├── dto/
│   │   ├── request/
│   │   │   └── user_request.go
│   │   └── response/
│   │       ├── user_response.go
│   │       └── common.go
│   └── errors/
│       └── errors.go
├── pkg/
│   ├── auth/
│   │   └── jwt.go
│   ├── cache/
│   │   └── redis.go
│   ├── logger/
│   │   └── logger.go
│   ├── validator/
│   │   └── validator.go
│   ├── database/
│   │   └── transaction.go
│   └── ratelimit/
│       └── ratelimit.go
├── config/
│   ├── config.go
│   └── config.yaml
├── docs/                               # Swagger 文档
├── migrations/                         # 数据库迁移
├── scripts/                            # 构建脚本
├── Dockerfile
├── docker-compose.yaml
├── Makefile
└── go.mod

Java 项目结构

myapp/
├── src/main/java/com/example/
│   ├── Application.java
│   ├── controller/
│   │   ├── UserController.java
│   │   └── OrderController.java
│   ├── service/
│   │   ├── UserService.java
│   │   ├── OrderService.java
│   │   └── impl/
│   │       ├── UserServiceImpl.java
│   │       └── OrderServiceImpl.java
│   ├── mapper/                         # MyBatis Mapper
│   │   ├── UserMapper.java
│   │   └── OrderMapper.java
│   ├── entity/
│   │   ├── User.java
│   │   ├── Order.java
│   │   └── Role.java
│   ├── dto/
│   │   ├── request/
│   │   │   ├── CreateUserRequest.java
│   │   │   └── ListUserRequest.java
│   │   └── response/
│   │       ├── UserResponse.java
│   │       ├── CommonResponse.java
│   │       └── PageResult.java
│   ├── config/
│   │   ├── SecurityConfig.java
│   │   ├── RedisConfig.java
│   │   ├── WebMvcConfig.java
│   │   └── Resilience4jConfig.java
│   ├── filter/
│   │   └── JwtAuthenticationFilter.java
│   ├── interceptor/
│   │   ├── AuthInterceptor.java
│   │   └── RateLimitInterceptor.java
│   ├── aspect/
│   │   ├── LogAspect.java
│   │   ├── PermissionAspect.java
│   │   └── AuditLogAspect.java
│   ├── exception/
│   │   ├── BusinessException.java
│   │   └── GlobalExceptionHandler.java
│   ├── enums/
│   │   ├── ErrorCode.java
│   │   └── UserStatus.java
│   ├── annotation/
│   │   ├── RequirePermission.java
│   │   └── AuditLog.java
│   ├── validator/
│   │   ├── Mobile.java
│   │   └── MobileValidator.java
│   └── util/
│       ├── JwtUtil.java
│       └── SecurityUtils.java
├── src/main/resources/
│   ├── mapper/
│   │   └── UserMapper.xml
│   ├── application.yml
│   ├── application-dev.yml
│   ├── application-prod.yml
│   └── logback-spring.xml
├── src/test/
├── Dockerfile
├── docker-compose.yaml
└── pom.xml

结语

本文档从实际企业项目出发,系统对比了 Go 与 Java 在分层架构各层的实现差异。两种语言/框架各有优势:

  • Go:简洁、高性能、低资源占用,适合云原生和高并发场景
  • Java:生态丰富、工具链成熟、企业级特性完善,适合大型复杂业务系统

选择时应综合考虑团队技术栈、项目规模、性能需求和长期维护成本。核心的架构思想(分层、解耦、抽象)是通用的,具体实现可根据语言特性灵活调整。