文档概述

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


一、层级对照表

层级Go 命名Java 命名职责
入口层handler / controllerController接收请求、参数校验、调用服务
业务层service / usecaseService业务逻辑、事务编排
数据层repository / storeMapper / DAO / Repository数据持久化
实体层entity / model / domainEntity / PO / DO数据结构定义
传输层dto / request / responseDTO / VO / BO数据传输对象
中间件层middlewareFilter / Interceptor / Aspect认证、鉴权、日志、限流
配置层configConfiguration / Properties配置管理

二、企业级目录结构对比

Go 典型结构

code
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)

code
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 请求处理流程图

3.2 完整请求时序图

Go 请求时序图

Java 请求时序图

3.3 认证授权流程图

3.4 权限校验流程图

3.5 缓存处理流程图

3.6 异常处理流程图

3.7 事务处理流程图

3.8 完整系统架构图

3.9 数据流转图

3.10 并发处理模型对比

四、参数校验

Go 实现

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()
}
go
// 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"
    }
}
go
// 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 实现

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));
    }
}

校验对比表

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

五、身份认证

Go 实现(JWT)

go
// 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
}
go
// 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)

java
// 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();
    }
}

认证对比表

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

六、权限控制

Go 实现(RBAC)

go
// 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
}
go
// 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()
    }
}
go
// 路由注册示例
// 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 + 自定义注解)

java
// 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) {
        // ...
    }
}

权限对比表

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

七、缓存策略

Go 实现

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")
go
// 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))
}
go
// 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
}
go
// 防缓存穿透 - 布隆过滤器 + 空值缓存
// 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)

java
// 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();
    }
}

缓存对比表

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

八、日志系统

Go 实现(Zap)

go
// 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...)
}
go
// 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))
}
go
// 操作审计日志
// 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)

java
// 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>
java
// 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();
    }
}
java
// 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) {
        // ...
    }
}

日志对比表

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

九、异常处理与统一响应

Go 实现

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)
)
go
// 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),
    })
}
go
// 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()
    }
}
go
// 使用示例 - 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 实现

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);
    }
}
java
// 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());
    }
}
java
// 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));
    }
}
java
// 使用示例 - 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);
    }
}

异常处理对比表

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

十、事务管理

Go 实现

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)
    })
}
go
// 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
}
go
// 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 实现

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);
    }
}
java
// 编程式事务管理
@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);
        }
    }
}
java
// 分布式事务 - 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, "订单创建失败");
        }
    }
}

事务对比表

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

十一、限流与熔断

Go 实现

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
}
go
// 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()
    }
}
go
// 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)

java
// 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
*/
java
// 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();
    }
}
java
// 自定义 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();
    }
}

限流熔断对比表

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

十二、完整分层代码对比

12.1 Handler / Controller 层

Go 实现

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 实现

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 对比表

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

12.2 Service 层

Go 实现

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 实现

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 层对比表

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

12.3 Repository / Mapper 层

Go 实现

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)

java
// 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);
}
java
// 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
// 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 + 注解/XMLinterface 继承 JpaRepository
简单查询db.First/Find@Select 注解方法名派生查询
复杂查询链式调用 / Raw SQLXML 动态 SQL@Query / Specification
分页Offset/LimitPageHelper / 手动Pageable 参数
事务支持Context 传递 tx自动参与事务自动参与事务
悲观锁clause.LockingFOR UPDATE@Lock 注解
批量操作Where IN + Updatesforeach 标签@Modifying + @Query
关联查询Preload / Joins / RawresultMap / 嵌套查询FETCH JOIN

12.4 Entity / Model 层

Go 实现

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 实现

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
自增autoIncrementIdType.AUTOGenerationType.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 实现

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 实现

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);
    }
}
java
// 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 层对比表

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

十三、关键特性总结对比

13.1 核心差异总表

13.2 设计理念对比

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

13.3 性能与资源对比

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

13.4 开发效率对比

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

13.5 适用场景建议


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

Go 项目结构

code
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 项目结构

code
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:生态丰富、工具链成熟、企业级特性完善,适合大型复杂业务系统

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

作者

望兮

全栈开发者