Spring Cloud微服务案例

wen java案例 1

本文目录导读:

Spring Cloud微服务案例

  1. 项目架构总览
  2. 代码实现
  3. 启动顺序和测试
  4. 核心功能演示

我为您精心设计一个完整的Spring Cloud微服务电商案例,包含服务注册、配置中心、网关、熔断等核心组件。

项目架构总览

springcloud-demo/
├── pom.xml (父POM)
├── eureka-server/          # 服务注册中心 (端口:8761)
├── config-server/          # 配置中心 (端口:8888)
├── api-gateway/            # API网关 (端口:8080)
├── user-service/           # 用户服务 (端口:8081)
├── order-service/          # 订单服务 (端口:8082)
├── product-service/        # 商品服务 (端口:8083)
└── common/                 # 公共模块

代码实现

父POM配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>springcloud-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.5</spring-cloud.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <modules>
        <module>common</module>
        <module>eureka-server</module>
        <module>config-server</module>
        <module>api-gateway</module>
        <module>user-service</module>
        <module>order-service</module>
        <module>product-service</module>
    </modules>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 公共模块依赖 -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>common</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

公共模块 (common)

// Result.java - 统一响应结果
package com.example.common;
import lombok.Data;
@Data
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("success");
        result.setData(data);
        return result;
    }
    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
}
// User.java - 用户实体
package com.example.common;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
    private Long id;
    private String username;
    private String email;
    private String phone;
}
// Order.java - 订单实体
package com.example.common;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class Order implements Serializable {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer quantity;
    private BigDecimal totalAmount;
    private String status;
    private LocalDateTime createTime;
}
// Product.java - 商品实体
package com.example.common;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class Product implements Serializable {
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Integer stock;
}

Eureka服务注册中心

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>springcloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>eureka-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>
# application.yml
server:
  port: 8761
spring:
  application:
    name: eureka-server
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka/
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

配置中心 (Config Server)

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>springcloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>config-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>
</project>
# application.yml
server:
  port: 8888
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-config-repo  # 配置仓库地址
          search-paths: config-repo
          default-label: main
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
package com.example.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

API网关 (Spring Cloud Gateway)

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>springcloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>api-gateway</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
</project>
# bootstrap.yml
spring:
  application:
    name: api-gateway
  cloud:
    config:
      uri: http://localhost:8888
      fail-fast: true
---
# application.yml
server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/product/**
          filters:
            - StripPrefix=1
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
management:
  endpoints:
    web:
      exposure:
        include: '*' 
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
    // 编程式路由配置 (可选)
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r
                .path("/api/user/**")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://user-service"))
            .build();
    }
}
// JWT过滤器 (网关认证)
package com.example.gateway.filter;
import io.jsonwebtoken.Jwts;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class JwtAuthFilter implements GatewayFilter, Ordered {
    private static final String SECRET_KEY = "your-secret-key";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 白名单路径放行
        String path = request.getURI().getPath();
        if (path.contains("/api/user/login") || path.contains("/api/user/register")) {
            return chain.filter(exchange);
        }
        // 获取token
        String token = request.getHeaders().getFirst("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
            try {
                // 验证token
                Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
                return chain.filter(exchange);
            } catch (Exception e) {
                // token无效
                return unauthorized(exchange);
            }
        }
        return unauthorized(exchange);
    }
    private Mono<Void> unauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }
    @Override
    public int getOrder() {
        return -100;
    }
}

用户服务 (User Service)

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>springcloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>user-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>common</artifactId>
        </dependency>
    </dependencies>
</project>
# bootstrap.yml
spring:
  application:
    name: user-service
  cloud:
    config:
      uri: http://localhost:8888
      fail-fast: true
---
# application.yml
server:
  port: 8081
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:h2:mem:userdb;DB_CLOSE_DELAY=-1
    username: sa
    password: 
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
management:
  endpoints:
    web:
      exposure:
        include: '*'
package com.example.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
// UserRepository
package com.example.user.repository;
import com.example.user.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    UserEntity findByUsername(String username);
}
// UserEntity
package com.example.user.entity;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String username;
    private String password;
    private String email;
    private String phone;
}
// UserController
package com.example.user.controller;
import com.example.common.Result;
import com.example.user.entity.UserEntity;
import com.example.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserRepository userRepository;
    @GetMapping
    public Result<List<UserEntity>> getAllUsers() {
        return Result.success(userRepository.findAll());
    }
    @GetMapping("/{id}")
    public Result<UserEntity> getUserById(@PathVariable Long id) {
        Optional<UserEntity> user = userRepository.findById(id);
        return user.map(Result::success)
                  .orElse(Result.error(404, "用户不存在"));
    }
    @PostMapping
    public Result<UserEntity> createUser(@RequestBody UserEntity user) {
        return Result.success(userRepository.save(user));
    }
    @PutMapping("/{id}")
    public Result<UserEntity> updateUser(@PathVariable Long id, @RequestBody UserEntity user) {
        user.setId(id);
        return Result.success(userRepository.save(user));
    }
    @DeleteMapping("/{id}")
    public Result<Void> deleteUser(@PathVariable Long id) {
        userRepository.deleteById(id);
        return Result.success(null);
    }
}

订单服务 (Order Service)

package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
// Feign客户端接口
package com.example.order.client;
import com.example.common.Product;
import com.example.common.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "product-service", fallback = ProductClientFallback.class)
public interface ProductClient {
    @GetMapping("/products/{id}")
    Result<Product> getProductById(@PathVariable("id") Long id);
    @GetMapping("/products/stock/{id}")
    Result<Boolean> checkStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity);
}
// Feign容错实现
package com.example.order.client;
import com.example.common.Product;
import com.example.common.Result;
import org.springframework.stereotype.Component;
@Component
public class ProductClientFallback implements ProductClient {
    @Override
    public Result<Product> getProductById(Long id) {
        return Result.error(500, "Product service is unavailable");
    }
    @Override
    public Result<Boolean> checkStock(Long id, Integer quantity) {
        return Result.error(500, "Stock check failed");
    }
}
// OrderController
package com.example.order.controller;
import com.example.common.Order;
import com.example.common.Product;
import com.example.common.Result;
import com.example.order.client.ProductClient;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/orders")
public class OrderController {
    private final ConcurrentHashMap<Long, Order> orders = new ConcurrentHashMap<>();
    @Autowired
    private ProductClient productClient;
    @Autowired
    private RestTemplate restTemplate;
    @PostMapping
    @CircuitBreaker(name = "createOrder", fallbackMethod = "createOrderFallback")
    public Result<Order> createOrder(@RequestParam("userId") Long userId,
                                     @RequestParam("productId") Long productId,
                                     @RequestParam("quantity") Integer quantity) {
        // 调用商品服务获取商品信息
        Result<Product> productResult = productClient.getProductById(productId);
        if (productResult.getCode() != 200) {
            return Result.error(500, "商品服务调用失败");
        }
        Product product = productResult.getData();
        // 创建订单
        Order order = new Order();
        order.setId(System.currentTimeMillis());
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
        order.setStatus("CREATED");
        order.setCreateTime(LocalDateTime.now());
        orders.put(order.getId(), order);
        return Result.success(order);
    }
    public Result<Order> createOrderFallback(Long userId, Long productId, Integer quantity, Throwable throwable) {
        // 降级处理
        Order fallbackOrder = new Order();
        fallbackOrder.setId(System.currentTimeMillis());
        fallbackOrder.setUserId(userId);
        fallbackOrder.setProductId(productId);
        fallbackOrder.setQuantity(quantity);
        fallbackOrder.setStatus("FALLBACK");
        fallbackOrder.setCreateTime(LocalDateTime.now());
        return Result.success(fallbackOrder);
    }
    @GetMapping("/{id}")
    public Result<Order> getOrderById(@PathVariable Long id) {
        Order order = orders.get(id);
        if (order != null) {
            return Result.success(order);
        }
        return Result.error(404, "订单不存在");
    }
}

商品服务 (Product Service)

package com.example.product;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ProductServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }
}
// ProductController
package com.example.product.controller;
import com.example.common.Product;
import com.example.common.Result;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/products")
public class ProductController {
    private final Map<Long, Product> products = new ConcurrentHashMap<>();
    @PostConstruct
    public void init() {
        // 初始化商品数据
        Product p1 = new Product();
        p1.setId(1L);
        p1.setName("iPhone 15");
        p1.setDescription("苹果最新款手机");
        p1.setPrice(new BigDecimal("6999"));
        p1.setStock(100);
        products.put(p1.getId(), p1);
        Product p2 = new Product();
        p2.setId(2L);
        p2.setName("MacBook Pro");
        p2.setDescription("苹果笔记本");
        p2.setPrice(new BigDecimal("14999"));
        p2.setStock(50);
        products.put(p2.getId(), p2);
    }
    @GetMapping
    public Result<List<Product>> getAllProducts() {
        return Result.success(products.values().stream().toList());
    }
    @GetMapping("/{id}")
    public Result<Product> getProductById(@PathVariable Long id) {
        Product product = products.get(id);
        if (product != null) {
            return Result.success(product);
        }
        return Result.error(404, "商品不存在");
    }
    @GetMapping("/stock/{id}")
    public Result<Boolean> checkStock(@PathVariable Long id, @RequestParam("quantity") Integer quantity) {
        Product product = products.get(id);
        if (product != null && product.getStock() >= quantity) {
            return Result.success(true);
        }
        return Result.success(false);
    }
    @PutMapping("/stock/{id}")
    public Result<Product> updateStock(@PathVariable Long id, @RequestParam("quantity") Integer quantity) {
        Product product = products.get(id);
        if (product != null) {
            product.setStock(product.getStock() - quantity);
            return Result.success(product);
        }
        return Result.error(404, "商品不存在");
    }
}

启动顺序和测试

启动顺序:

  1. Eureka Server (8761)
  2. Config Server (8888)
  3. User Service (8081)
  4. Product Service (8083)
  5. Order Service (8082)
  6. API Gateway (8080)

测试接口:

# 1. 通过网关访问用户服务
curl http://localhost:8080/api/user/users
# 2. 创建用户
curl -X POST http://localhost:8080/api/user/users \
  -H "Content-Type: application/json" \
  -d '{"username":"test", "password":"123456", "email":"test@example.com"}'
# 3. 查看商品(通过网关)
curl http://localhost:8080/api/product/products
# 4. 创建订单(通过网关)
curl -X POST "http://localhost:8080/api/order/orders?userId=1&productId=1&quantity=2"
# 5. 查看Eureka注册中心
open http://localhost:8761

核心功能演示

服务注册发现

  • 所有服务自动注册到Eureka
  • 通过服务名进行负载均衡调用

负载均衡

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

熔断降级

# application.yml
resilience4j:
  circuitbreaker:
    instances:
      createOrder:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10000

分布式配置

# bootstrap.yml
spring:
  cloud:
    config:
      name: user-service
      profile: dev
      label: main
      uri: http://localhost:8888

这就是一个完整的Spring Cloud微服务电商案例,包含了微服务架构的核心组件:服务注册、配置中心、API网关、熔断降级、负载均衡等,您可以根据实际需求进行扩展和修改。

抱歉,评论功能暂时关闭!