Spring Cloud微服务课程设计 第二章:Eureka注册中心

第二章:Eureka注册中心

理论讲解

什么是Eureka注册中心?

Eureka就像是微服务世界的"电话簿"或"服务黄页"。

在一个复杂的微服务系统中,有很多服务需要互相通信,但每个服务的地址可能会变化(比如扩容、故障转移等)。

Eureka注册中心就是让所有服务都来这里"登记"自己的联系方式,其他服务需要调用时先来Eureka查询地址。

应用场景举例:

想象一下外卖平台:

  • 餐厅服务、配送服务、支付服务、用户服务都在Eureka注册
  • 当用户下单时,订单服务不需要知道其他服务的具体地址,只需要问Eureka:"支付服务在哪里?配送服务在哪里?"
  • 即使某个服务换了服务器地址,只需要在Eureka更新,其他服务自动就能发现新地址


项目结构

复制代码
chapter-02-eureka/
├── eureka-server/          # 注册中心服务端
├── commons-service/        # 公共基础服务
├── user-service/           # 用户服务(服务提供者)
├── product-service/        # 商品服务(服务提供者)
└── order-service/          # 订单服务(服务消费者)

完整代码实现

1. Eureka Server注册中心

pom.xml
xml 复制代码
<?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.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>eureka-server</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 替换为你的启动类全路径 -->
    <start-class>com.lihaozhe.eurekaserver.EurekaServerApplication</start-class>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.inject</groupId>
      <artifactId>guice</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 替换为你的启动类全路径 -->
          <mainClass>com.lihaozhe.eurekaserver.EurekaServerApplication</mainClass>
        </configuration>
        <!-- 可选:如果需要打包为可执行jar,添加此配置 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
应用启动类
java 复制代码
package com.lihaozhe.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Eureka注册中心服务器
 * 使用@EnableEurekaServer注解启用Eureka服务器功能
 * 这是微服务架构中的"服务注册中心",所有服务都要在这里注册和发现
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
  public static void main(String[] args) {
    // 启动Eureka注册中心服务器
    SpringApplication.run(EurekaServerApplication.class, args);
  }
}
配置文件
properties 复制代码
# application.yml
#=============================================
# 服务器配置
#=============================================
# 应用服务端口
server:
  port: 8761

#=============================================
# Spring 相关配置
#=============================================
# 当前微服务在注册中心中的名称
spring:
  application:
    name: eureka-server
  security:
    user:
      # 开启 Spring Security 时的登录用户名
      name: admin
      # 对应的登录密码(生产环境请使用更复杂的密码)
      password: lihaozhe

#=============================================
# Eureka 服务端/客户端通用配置
#=============================================
# 当前节点的主机名(会在 Eureka 控制台显示)
eureka:
  instance:
    hostname: phoenix
    # 注册到 Eureka 时优先使用 IP 而非 hostname
    prefer-ip-address: true
    # 实例在注册表中的唯一标识(IP:端口)
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    # 是否把自己注册到 Eureka(单节点 Eureka 也要设为 true)
    register-with-eureka: true
    # 是否从 Eureka 拉取注册表(单节点也要设为 true)
    fetch-registry: true
    service-url:
      # 注册中心地址(带安全认证)
      defaultZone: http://admin:lihaozhe@localhost:8761/eureka
      # 如果有多个注册中心,使用逗号间隔
      # defaultZone: http://admin:lihaozhe@localhost:8761/eureka,http://admin:lihaozhe@localhost:8762/eureka/
  server:
    # 开启自我保护模式(防止网络抖动时误删服务)
    enable-self-preservation: true
    # 清理无效服务实例的间隔时间(毫秒)
    eviction-interval-timer-in-ms: 10000
    # 自我保护阈值比例
    # 当上一分钟实际收到的心跳数 < 理论上应收到的心跳数 * 0.85 时,
    # 如果开启了自我保护模式(enable-self-preservation: true),
    # Eureka 会立即进入自我保护状态,不再剔除任何失效实例,防止因网络抖动大面积"误杀"服务。
    renewal-percent-threshold: 0.85

#=============================================
# Actuator 监控端点配置
#=============================================
# 暴露的 web 端点列表
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      # 访问 /actuator/health 时始终展示详细信息
      show-details: always

#=============================================
# 日志级别配置
#=============================================
# 把 Netflix 包下所有日志级别设为 warn,减少控制台输出
logging:
  level:
    com.netflix: warn

2. Commons Service公共基础服务

pom.xml
xml 复制代码
<?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.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>commons-service</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

</project>

3. User Service用户服务

pom.xml
xml 复制代码
<?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.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>user-service</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 替换为你的启动类全路径 -->
    <start-class>com.lihaozhe.userservice.UserServiceApplication</start-class>
  </properties>

  <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.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 引出 commons-service 公共基础服务 -->
    <dependency>
      <groupId>com.lihaozhe</groupId>
      <artifactId>commons-service</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 替换为你的启动类全路径 -->
          <mainClass>com.lihaozhe.userservice.UserServiceApplication</mainClass>
        </configuration>
        <!-- 可选:如果需要打包为可执行jar,添加此配置 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
应用启动类
java 复制代码
package com.lihaozhe.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 用户服务应用启动类
 * 使用@EnableDiscoveryClient注解启用服务发现功能
 * 这个服务会向Eureka注册中心注册自己,并能够发现其他服务
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(UserServiceApplication.class, args);
  }
}
用户实体类

注意:Commons Service公共基础服务内编写

java 复制代码
package com.lihaozhe.userservice.dto;

/**
 * 用户数据传输对象
 * 用于在服务间传递用户信息
 *
 * @author 李昊哲
 * @version 1.0.0
 */
public class UserDTO {
  /**
   * 用户ID
   */
  private Long id;
  /**
   * 用户名
   */
  private String username;
  /**
   * 邮箱
   */
  private String email;
  /**
   * 电话
   */
  private String phone;
  /**
   * 地址
   */
  private String address;

  // 构造函数
  public UserDTO() {
  }

  public UserDTO(String username, String email, String phone, String address) {
    this.username = username;
    this.email = email;
    this.phone = phone;
    this.address = address;
  }

  public UserDTO(Long id, String username, String email, String phone, String address) {
    this.id = id;
    this.username = username;
    this.email = email;
    this.phone = phone;
    this.address = address;
  }

  // Getter和Setter方法
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  public String getAddress() {
    return address;
  }

  public void setAddress(String address) {
    this.address = address;
  }

  @Override
  public String toString() {
    return String.format("User{id=%d, username='%s',phone=%s, email='%s'}", id, username, phone, email);
  }
}
用户控制器
java 复制代码
package com.lihaozhe.userservice.controller;

import com.lihaozhe.userservice.dto.UserDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 用户服务控制器
 * 提供用户相关的REST API
 * 这个服务会注册到Eureka,供其他服务调用
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/users")
public class UserController {
  // 模拟用户数据
  private final List<UserDTO> users = new ArrayList<>();
  private final Logger logger = LoggerFactory.getLogger(UserController.class);

  /**
   * 构造函数,初始化模拟数据
   */
  public UserController() {
    // 初始化一些测试用户
    users.add(new UserDTO(1L, "张三", "zhangsan@lihaozhe.com", "13800138001", "北京市朝阳区"));
    users.add(new UserDTO(2L, "李四", "lisi@lihaozhe.com", "13800138002", "上海市浦东新区"));
    users.add(new UserDTO(3L, "王五", "wangwu@lihaozhe.com", "13800138003", "广州市天河区"));
  }

  /**
   * 根据用户ID获取用户信息
   * GET /api/users/{id}
   */
  @GetMapping("/{id}")
  public UserDTO getUserById(@PathVariable("id") Long id) {
    logger.info("查询用户ID: {}", id);

    // 查找用户
    Optional<UserDTO> user = users.stream()
        .filter(u -> u.getId().equals(id))
        .findFirst();

    return user.orElse(null);
  }

  /**
   * 获取所有用户列表
   * GET /api/users
   */
  @GetMapping()
  public List<UserDTO> getAllUsers() {
    logger.info("获取所有用户列表");
    return users;
  }

  /**
   * 创建新用户
   * POST /api/users
   */
  @PostMapping()
  public UserDTO createUser(@RequestBody UserDTO user) {
    logger.info("创建用户: {}", user.getUsername());

    // 生成新用户ID
    Long newId = users.stream()
        .mapToLong(UserDTO::getId)
        .max()
        .orElse(0L) + 1;

    user.setId(newId);
    users.add(user);

    return user;
  }

  /**
   * 健康检查端点
   * GET /api/users/health
   */
  @GetMapping("/health")
  public Map<String, Object> health() {
    return Map.of(
        "status", "UP",
        "service", "user-service",
        "timestamp", System.currentTimeMillis()
    );
  }

  /**
   * 服务信息端点
   * GET /api/users/info
   */
  @GetMapping("/info")
  public Map<String, Object> info() {
    return Map.of(
        "name", "user-service",
        "description", "用户管理服务",
        "version", "1.0.0",
        "totalUsers", users.size()
    );
  }
}
用户服务配置
properties 复制代码
# application.properties
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 用户服务实例端口,集群部署时切忌重复
server.port=8081

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring.application.name=user-service

#=============================================
# 2. Eureka 实例级配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka.instance.hostname=user-service

# 注册时优先使用 IP,防止 hostname 解析失败
eureka.instance.prefer-ip-address=true

# 唯一标识,格式 IP:端口
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}

# 心跳间隔 5s,缩短感知时间
eureka.instance.lease-renewal-interval-in-seconds=5

# 10s 内收不到心跳即剔除,开发调试可设短
eureka.instance.lease-expiration-duration-in-seconds=10

#=============================================
# 3. Eureka 客户端行为配置
#=============================================

# 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
eureka.client.register-with-eureka=true

# 是否拉取注册表(默认 true,同上)
eureka.client.fetch-registry=true

# 注册中心地址(带安全认证)
eureka.client.service-url.defaultZone=http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management.endpoints.web.exposure.include=health,info,metrics

# 默认 never,always 方便排查
management.endpoint.health.show-details=always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging.level.com.lihaozhe.userservice=DEBUG
yaml 复制代码
# application.yml
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 用户服务实例端口,集群部署时切忌重复
server:
  port: 8081

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring:
  application:
    name: user-service

#=============================================
# 2. Eureka 客户端实例配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka:
  instance:
    hostname: user-service

    # 注册时优先使用 IP,防止 hostname 解析失败
    prefer-ip-address: true

    # 唯一标识,格式 IP:端口
    instance-id: ${spring.cloud.client.ip-address}:${server.port}

    # 心跳间隔 5s,缩短感知时间
    lease-renewal-interval-in-seconds: 5

    # 10s 内收不到心跳即剔除,开发调试可设短
    lease-expiration-duration-in-seconds: 10

  #=============================================
  # 3. Eureka 客户端行为配置
  #=============================================

  # 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
  client:
    register-with-eureka: true

    # 是否拉取注册表(默认 true,同上)
    fetch-registry: true

    # 注册中心地址(带安全认证)
    service-url:
      defaultZone: http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

  # 默认 never,always 方便排查
  endpoint:
    health:
      show-details: always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging:
  level:
    com.lihaozhe.userservice: DEBUG

4. Product Service商品服务

pom.xml
xml 复制代码
<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.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>product-service</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 替换为你的启动类全路径 -->
    <start-class>com.lihaozhe.productservice.ProductServiceApplication</start-class>
  </properties>

  <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.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 引出 commons-service 公共基础服务 -->
    <dependency>
      <groupId>com.lihaozhe</groupId>
      <artifactId>commons-service</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 替换为你的启动类全路径 -->
          <mainClass>com.lihaozhe.productservice.ProductServiceApplication</mainClass>
        </configuration>
        <!-- 可选:如果需要打包为可执行jar,添加此配置 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
应用启动类
java 复制代码
package com.lihaozhe.productservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 商品服务应用启动类
 * 商品服务会向Eureka注册自己,提供商品管理功能
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ProductServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(ProductServiceApplication.class, args);
  }
}
商品实体类

注意:Commons Service公共基础服务内编写

java 复制代码
package com.lihaozhe.productservice.dto;

/**
 * 商品数据传输对象
 *
 * @author 李昊哲
 * @version 1.0.0
 */
public class ProductDTO {
  /**
   * 商品ID
   */
  private Long id;
  /**
   * 商品名称
   */
  private String name;
  /**
   * 商品分类
   */
  private String category;
  /**
   * 商品价格
   */
  private Double price;
  /**
   * 商品库存
   */
  private Integer stock;
  /**
   * 商品描述
   */
  private String description;

  // 构造函数
  public ProductDTO() {
  }

  public ProductDTO(String name, String category, Double price, Integer stock, String description) {
    this.name = name;
    this.category = category;
    this.price = price;
    this.stock = stock;
    this.description = description;
  }

  public ProductDTO(Long id, String name, String category, Double price, Integer stock, String description) {
    this.id = id;
    this.name = name;
    this.category = category;
    this.price = price;
    this.stock = stock;
    this.description = description;
  }

  // Getter和Setter方法
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getCategory() {
    return category;
  }

  public void setCategory(String category) {
    this.category = category;
  }

  public Double getPrice() {
    return price;
  }

  public void setPrice(Double price) {
    this.price = price;
  }

  public Integer getStock() {
    return stock;
  }

  public void setStock(Integer stock) {
    this.stock = stock;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  @Override
  public String toString() {
    return String.format("Product{id=%d, name='%s',category=%s price=%.2f, stock=%d}", id, name, category, price, stock);
  }

}
商品控制器
java 复制代码
package com.lihaozhe.productservice.controller;

import com.lihaozhe.productservice.dto.ProductDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 商品服务控制器
 * 提供商品管理的REST API
 * @author 李昊哲
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/products")
public class ProductController {
  // 模拟商品数据
  private final List<ProductDTO> products = new ArrayList<>();
  private final Logger logger = LoggerFactory.getLogger(this.getClass());

  /**
   * 构造函数,初始化模拟数据
   */
  public ProductController() {
    // 初始化测试商品
    products.add(new ProductDTO(1L, "iPhone 15", "手机", 5999.00, 100, "最新款iPhone"));
    products.add(new ProductDTO(2L, "MacBook Pro", "电脑", 12999.00, 50, "专业级笔记本电脑"));
    products.add(new ProductDTO(3L, "AirPods Pro", "耳机", 1899.00, 200, "无线降噪耳机"));
    products.add(new ProductDTO(4L, "iPad Air", "平板", 4399.00, 80, "轻薄便携平板"));
  }

  /**
   * 根据ID获取商品
   * GET /api/products/{id}
   */
  @GetMapping("/{id}")
  public ProductDTO getProduct(@PathVariable("id") Long id) {
    logger.info("查询商品ID: {}", id);

    return products.stream()
        .filter(p -> p.getId().equals(id))
        .findFirst()
        .orElse(null);
  }

  /**
   * 获取所有商品
   * GET /api/products
   */
  @GetMapping
  public List<ProductDTO> getAllProducts() {
    logger.info("获取所有商品列表");
    return products;
  }

  /**
   * 根据分类获取商品
   * GET /api/products/category/{category}
   */
  @GetMapping("/category/{category}")
  public List<ProductDTO> getProductsByCategory(@PathVariable("category") String category) {
    logger.info("根据分类查询商品: {}", category);

    return products.stream()
        .filter(p -> p.getCategory().equalsIgnoreCase(category))
        .toList();
  }

  /**
   * 更新商品库存
   * PUT /api/products/{id}/stock
   */
  @PutMapping("/{id}/stock")
  public Map<String, Object> updateStock(@PathVariable("id") Long id, @RequestBody Map<String, Integer> request) {
    Integer quantity = request.get("quantity");
    logger.info("更新商品 {} 库存,变化数量: {}", id, quantity);

    // 查找商品并更新库存
    Optional<ProductDTO> productOpt = products.stream()
        .filter(p -> p.getId().equals(id))
        .findFirst();

    if (productOpt.isPresent()) {
      ProductDTO product = productOpt.get();
      int newStock = product.getStock() + quantity;
      product.setStock(Math.max(newStock, 0)); // 库存不能为负数

      return Map.of(
          "success", true,
          "message", "库存更新成功",
          "newStock", product.getStock()
      );
    }

    return Map.of(
        "success", false,
        "message", "商品不存在"
    );
  }

  /**
   * 健康检查端点
   * GET /api/products/health
   */
  @GetMapping("/health")
  public Map<String, Object> health() {
    return Map.of(
        "status", "UP",
        "service", "product-service",
        "timestamp", System.currentTimeMillis(),
        "totalProducts", products.size()
    );
  }
}
商品服务配置
properties 复制代码
# application.properties
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 商品服务实例端口,集群部署时切忌重复
server.port=8082

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring.application.name=product-service

#=============================================
# 2. Eureka 实例级配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka.instance.hostname=product-service

# 注册时优先使用 IP,防止 hostname 解析失败
eureka.instance.prefer-ip-address=true

# 唯一标识,格式 IP:端口
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}

# 心跳间隔 5s,缩短感知时间
eureka.instance.lease-renewal-interval-in-seconds=5

# 10s 内收不到心跳即剔除,开发调试可设短
eureka.instance.lease-expiration-duration-in-seconds=10

#=============================================
# 3. Eureka 客户端行为配置
#=============================================

# 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
eureka.client.register-with-eureka=true

# 是否拉取注册表(默认 true,同上)
eureka.client.fetch-registry=true

# 注册中心地址(带安全认证)
eureka.client.service-url.defaultZone=http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management.endpoints.web.exposure.include=health,info,metrics

# 默认 never,always 方便排查
management.endpoint.health.show-details=always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging.level.com.lihaozhe.productservice=DEBUG
yaml 复制代码
# application.yml
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 商品服务实例端口,集群部署时切忌重复
server:
  port: 8082

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring:
  application:
    name: product-service

#=============================================
# 2. Eureka 客户端实例配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka:
  instance:
    hostname: product-service

    # 注册时优先使用 IP,防止 hostname 解析失败
    prefer-ip-address: true

    # 唯一标识,格式 IP:端口
    instance-id: ${spring.cloud.client.ip-address}:${server.port}

    # 心跳间隔 5s,缩短感知时间
    lease-renewal-interval-in-seconds: 5

    # 10s 内收不到心跳即剔除,开发调试可设短
    lease-expiration-duration-in-seconds: 10

  #=============================================
  # 3. Eureka 客户端行为配置
  #=============================================

  # 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
  client:
    register-with-eureka: true

    # 是否拉取注册表(默认 true,同上)
    fetch-registry: true

    # 注册中心地址(带安全认证)
    service-url:
      defaultZone: http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

  # 默认 never,always 方便排查
  endpoint:
    health:
      show-details: always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging:
  level:
    com.lihaozhe.productservice: DEBUG

5. Order Service订单服务

pom.xml
xml 复制代码
<?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.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>order-service</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 替换为你的启动类全路径 -->
    <start-class>com.lihaozhe.orderservice.OrderServiceApplication</start-class>
  </properties>

  <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.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 引出 commons-service 公共基础服务 -->
    <dependency>
      <groupId>com.lihaozhe</groupId>
      <artifactId>commons-service</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 替换为你的启动类全路径 -->
          <mainClass>com.lihaozhe.orderservice.OrderServiceApplication</mainClass>
        </configuration>
        <!-- 可选:如果需要打包为可执行jar,添加此配置 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>
应用启动类
java 复制代码
package com.lihaozhe.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestClient;

/**
 * 订单服务应用启动类
 * 订单服务是服务消费者,会调用用户服务和商品服务
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(OrderServiceApplication.class, args);
  }

  /**
   * 配置RestClient Bean
   * 用于调用其他微服务
   */
  @Bean
  public RestClient restClient() {
    return RestClient.create();
  }
}
订单实体类

注意:Commons Service公共基础服务内编写

java 复制代码
package com.lihaozhe.orderservice.dto;

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

/**
 * 订单数据传输对象
 *
 * @author 李昊哲
 * @version 1.0.0
 */
public class OrderDTO {
  /**
   * 订单ID
   */
  private Long id;
  /**
   * 用户ID
   */
  private Long userId;
  /**
   * 订单项列表
   */
  private List<OrderItemDTO> items;
  /**
   * 订单总金额
   */
  private Double totalAmount;
  /**
   * 订单状态
   */
  private String status;
  /**
   * 创建时间
   */
  private LocalDateTime createTime;

  // 构造函数
  public OrderDTO() {
  }

  public OrderDTO(Long userId, List<OrderItemDTO> items, Double totalAmount, String status) {
    this.userId = userId;
    this.items = items;
    this.totalAmount = totalAmount;
    this.status = status;
    this.createTime = LocalDateTime.now();
  }

  public OrderDTO(Long id, Long userId, List<OrderItemDTO> items, Double totalAmount, String status) {
    this.id = id;
    this.userId = userId;
    this.items = items;
    this.totalAmount = totalAmount;
    this.status = status;
    this.createTime = LocalDateTime.now();
  }

  // Getter和Setter方法
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public Long getUserId() {
    return userId;
  }

  public void setUserId(Long userId) {
    this.userId = userId;
  }

  public List<OrderItemDTO> getItems() {
    return items;
  }

  public void setItems(List<OrderItemDTO> items) {
    this.items = items;
  }

  public Double getTotalAmount() {
    return totalAmount;
  }

  public void setTotalAmount(Double totalAmount) {
    this.totalAmount = totalAmount;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public LocalDateTime getCreateTime() {
    return createTime;
  }

  public void setCreateTime(LocalDateTime createTime) {
    this.createTime = createTime;
  }
}
订单详情类

注意:Commons Service公共基础服务内编写

java 复制代码
package com.lihaozhe.orderservice.dto;

/**
 * 订单项数据传输对象
 *
 * @author 李昊哲
 * @version 1.0.0
 */

public class OrderItemDTO {
  /**
   * 商品ID
   */
  private Long productId;
  /**
   * 商品名称
   */
  private String productName;
  /**
   * 数量
   */
  private Integer quantity;
  /**
   * 单价
   */
  private Double price;

  // 构造函数

  public OrderItemDTO() {
  }

  public OrderItemDTO(String productName, Integer quantity, Double price) {
    this.productName = productName;
    this.quantity = quantity;
    this.price = price;
  }

  public OrderItemDTO(Long productId, String productName, Integer quantity, Double price) {
    this.productId = productId;
    this.productName = productName;
    this.quantity = quantity;
    this.price = price;
  }

  // Getter和Setter方法

  public Long getProductId() {
    return productId;
  }

  public void setProductId(Long productId) {
    this.productId = productId;
  }

  public String getProductName() {
    return productName;
  }

  public void setProductName(String productName) {
    this.productName = productName;
  }

  public Integer getQuantity() {
    return quantity;
  }

  public void setQuantity(Integer quantity) {
    this.quantity = quantity;
  }

  public Double getPrice() {
    return price;
  }

  public void setPrice(Double price) {
    this.price = price;
  }
}
服务调用工具类
java 复制代码
package com.lihaozhe.orderservice.service;

import com.lihaozhe.productservice.dto.ProductDTO;
import com.lihaozhe.userservice.dto.UserDTO;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 服务发现工具类
 * 演示如何通过Eureka发现和调用其他服务
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@Service
public class ServiceDiscoveryService {
  private final RestClient restClient;
  private final DiscoveryClient discoveryClient;

  /**
   * 构造函数,注入依赖
   */
  public ServiceDiscoveryService(RestClient restClient, DiscoveryClient discoveryClient) {
    this.restClient = restClient;
    this.discoveryClient = discoveryClient;
  }

  /**
   * 获取所有注册的服务名称
   */
  public List<String> getServices() {
    return this.discoveryClient.getServices();
  }

  /**
   * 根据服务名称获取服务实例
   */
  public List<ServiceInstance> getServiceInstances(String serviceName) {
    return discoveryClient.getInstances(serviceName);
  }

  /**
   * 获取用户服务的基础URL
   * 从Eureka获取用户服务的地址
   */
  public String getUserServiceUrl() {
    List<ServiceInstance> instances = getServiceInstances("user-service");
    if (instances.isEmpty()) {
      throw new RuntimeException("用户服务未找到");
    }
    // 获取第一个可用的实例(实际生产环境应该实现负载均衡)
    ServiceInstance instance = instances.getFirst();
    return instance.getUri().toString();
  }

  /**
   * 获取商品服务的基础URL
   */
  public String getProductServiceUrl() {
    List<ServiceInstance> instances = getServiceInstances("product-service");
    if (instances.isEmpty()) {
      throw new RuntimeException("商品服务未找到");
    }
    ServiceInstance instance = instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
    return instance.getUri().toString();
  }

  /**
   * 通过服务发现调用用户服务获取用户信息
   */
  public UserDTO getUserById(Long userId) {
    String userServiceUrl = getUserServiceUrl();
    String url = userServiceUrl + "/api/users/" + userId;

    try {
      return restClient.get()
          .uri(url)
          .retrieve()
          .body(UserDTO.class);
    } catch (Exception e) {
      throw new RuntimeException("调用用户服务失败: " + e.getMessage());
    }
  }

  /**
   * 通过服务发现调用商品服务获取商品信息
   */
  public ProductDTO getProductById(Long productId) {
    String productServiceUrl = getProductServiceUrl();
    String url = productServiceUrl + "/api/products/" + productId;
    IO.println("获取商品信息-商品服务:URL: " + url);
    try {
      return restClient.get()
          .uri(url)
          .retrieve()
          .body(ProductDTO.class);
    } catch (Exception e) {
      throw new RuntimeException("调用商品服务失败: " + e.getMessage());
    }
  }

  /**
   * 通过服务发现调用商品服务更新库存
   */
  public boolean updateProductStock(Long productId, Integer quantity) {
    String productServiceUrl = getProductServiceUrl();
    String url = productServiceUrl + "/api/products/" + productId + "/stock";
    IO.println("更新库存-商品服务:URL: " + url);

    try {
      String requestBody = String.format("{\"quantity\": %d}", quantity);

      String response = restClient.put()
          .uri(url)
          .header("Content-Type", "application/json")
          .body(requestBody)
          .retrieve()
          .body(String.class);

      return response != null && response.contains("success");
    } catch (Exception e) {
      throw new RuntimeException("更新商品库存失败: " + e.getMessage());
    }
  }
}
订单控制器
java 复制代码
package com.lihaozhe.orderservice.controller;

import com.lihaozhe.orderservice.dto.OrderDTO;
import com.lihaozhe.orderservice.dto.OrderItemDTO;
import com.lihaozhe.orderservice.service.ServiceDiscoveryService;
import com.lihaozhe.productservice.dto.ProductDTO;
import com.lihaozhe.userservice.dto.UserDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 订单服务控制器
 * 演示如何通过Eureka发现和调用其他服务
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/orders")
public class OrderController {

  private final ServiceDiscoveryService serviceDiscoveryService;
  private final List<OrderDTO> orders = new ArrayList<>();
  private Long orderIdCounter = 1L;
  private final Logger logger = LoggerFactory.getLogger(this.getClass());

  /**
   * 构造函数,注入服务发现工具类
   */
  public OrderController(ServiceDiscoveryService serviceDiscoveryService) {
    this.serviceDiscoveryService = serviceDiscoveryService;
  }

  /**
   * 创建订单
   * POST /api/orders
   * 演示调用用户服务和商品服务
   */
  @PostMapping
  public Map<String, Object> createOrder(@RequestBody Map<String, Object> orderRequest) {
    Long userId = Long.valueOf(orderRequest.get("userId").toString());
    Long productId = Long.valueOf(orderRequest.get("productId").toString());
    Integer quantity = Integer.valueOf(orderRequest.get("quantity").toString());

    logger.info("创建订单 - 用户ID: {}, 商品ID: {}, 数量: {}", userId, productId, quantity);

    try {
      // 1. 调用用户服务验证用户是否存在
      UserDTO user = serviceDiscoveryService.getUserById(userId);
      if (user == null) {
        return Map.of("success", false, "message", "用户不存在");
      }

      // 2. 调用商品服务获取商品信息
      ProductDTO product = serviceDiscoveryService.getProductById(productId);
      if (product == null) {
        return Map.of("success", false, "message", "商品不存在");
      }

      // 3. 检查库存
      if (product.getStock() < quantity) {
        return Map.of("success", false, "message", "库存不足");
      }

      // 4. 创建订单
      OrderItemDTO orderItem = new OrderItemDTO(productId, product.getName(), quantity, product.getPrice());
      List<OrderItemDTO> items = List.of(orderItem);
      Double totalAmount = product.getPrice() * quantity;

      OrderDTO order = new OrderDTO(orderIdCounter++, userId, items, totalAmount, "CREATED");
      orders.add(order);

      // 5. 调用商品服务更新库存
      boolean stockUpdated = serviceDiscoveryService.updateProductStock(productId, -quantity);
      if (!stockUpdated) {
        return Map.of("success", false, "message", "库存更新失败");
      }

      return Map.of(
          "success", true,
          "message", "订单创建成功",
          "orderId", order.getId(),
          "totalAmount", totalAmount,
          "user", user.getUsername(),
          "product", product.getName()
      );

    } catch (Exception e) {
      return Map.of("success", false, "message", "创建订单失败: " + e.getMessage());
    }
  }

  /**
   * 根据订单ID获取订单详情
   * GET /api/orders/{orderId}
   */
  @GetMapping("/{orderId}")
  public OrderDTO getOrder(@PathVariable("orderId") Long orderId) {
    return orders.stream()
        .filter(o -> o.getId().equals(orderId))
        .findFirst()
        .orElse(null);
  }

  /**
   * 获取用户的所有订单
   * GET /api/orders/user/{userId}
   */
  @GetMapping("/user/{userId}")
  public List<OrderDTO> getUserOrders(@PathVariable("userId") Long userId) {
    // 先验证用户是否存在
    UserDTO user = null;
    try {
      user = serviceDiscoveryService.getUserById(userId);
      if (user == null) {
        return List.of();
      } else {
        return orders.stream().filter(o -> o.getUserId().equals(userId)).toList();
      }
    } catch (Exception e) {
      System.err.println("验证用户失败: " + e.getMessage());
      return List.of();
    }
  }

  /**
   * 获取所有注册的服务
   * GET /api/orders/services
   */
  @GetMapping("/services")
  public List<String> getServices() {
    return serviceDiscoveryService.getServices();
  }

  /**
   * 健康检查端点
   * GET /api/orders/health
   */
  @GetMapping("/health")
  public Map<String, Object> health() {
    return Map.of(
        "status", "UP",
        "service", "order-service",
        "timestamp", System.currentTimeMillis(),
        "totalOrders", orders.size()
    );
  }

  /**
   * 服务信息端点
   * GET /api/orders/info
   */
  @GetMapping("/info")
  public Map<String, Object> info() {
    return Map.of(
        "name", "order-service",
        "description", "订单管理服务",
        "version", "1.0.0",
        "dependencies", List.of("user-service", "product-service")
    );
  }
}
订单服务配置
properties 复制代码
# application.properties
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 订单服务实例端口,集群部署时切忌重复
server.port=8083

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring.application.name=order-service

#=============================================
# 2. Eureka 实例级配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka.instance.hostname=order-service

# 注册时优先使用 IP,防止 hostname 解析失败
eureka.instance.prefer-ip-address=true

# 唯一标识,格式 IP:端口
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}

# 心跳间隔 5s,缩短感知时间
eureka.instance.lease-renewal-interval-in-seconds=5

# 10s 内收不到心跳即剔除,开发调试可设短
eureka.instance.lease-expiration-duration-in-seconds=10

#=============================================
# 3. Eureka 客户端行为配置
#=============================================

# 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
eureka.client.register-with-eureka=true

# 是否拉取注册表(默认 true,同上)
eureka.client.fetch-registry=true

# 注册中心地址(带安全认证)
eureka.client.service-url.defaultZone=http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management.endpoints.web.exposure.include=health,info,metrics

# 默认 never,always 方便排查
management.endpoint.health.show-details=always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging.level.com.lihaozhe.orderservice=DEBUG
yaml 复制代码
# application.yml
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================

# 订单服务实例端口,集群部署时切忌重复
server:
  port: 8083

#=============================================
# 1. Spring 基础配置
#=============================================

# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring:
  application:
    name: order-service

#=============================================
# 2. Eureka 客户端实例配置
#=============================================

# 在 Eureka 控制台显示的主机名,可读性优先
eureka:
  instance:
    hostname: order-service

    # 注册时优先使用 IP,防止 hostname 解析失败
    prefer-ip-address: true

    # 唯一标识,格式 IP:端口
    instance-id: ${spring.cloud.client.ip-address}:${server.port}

    # 心跳间隔 5s,缩短感知时间
    lease-renewal-interval-in-seconds: 5

    # 10s 内收不到心跳即剔除,开发调试可设短
    lease-expiration-duration-in-seconds: 10

  #=============================================
  # 3. Eureka 客户端行为配置
  #=============================================

  # 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
  client:
    register-with-eureka: true

    # 是否拉取注册表(默认 true,同上)
    fetch-registry: true

    # 注册中心地址(带安全认证)
    service-url:
      defaultZone: http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 4. Actuator 监控配置
#=============================================

# 按需暴露,生产勿暴露 env/beans
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

  # 默认 never,always 方便排查
  endpoint:
    health:
      show-details: always

#=============================================
# 5. 日志级别
#=============================================

# 业务包日志级别,上线后改为 INFO 或 WARN
logging:
  level:
    com.lihaozhe.orderservice: DEBUG

开发思路和过程

  1. 架构设计

    • Eureka Server作为注册中心,管理所有服务的注册和发现
    • 用户服务、商品服务作为服务提供者,向Eureka注册
    • 订单服务作为服务消费者,通过Eureka发现其他服务
  2. 服务注册

    • 每个服务使用@EnableDiscoveryClient启用服务发现
    • 配置Eureka服务器地址,服务自动注册
  3. 服务发现

    • 使用DiscoveryClient获取服务实例列表
    • 根据服务名称获取具体的服务地址
    • 通过RestClient调用发现的服务
  4. 服务治理

    • 配置心跳间隔和过期时间
    • 启用自我保护模式防止网络波动误删服务

运行测试

启动顺序:

  1. 启动Eureka Server (端口8761)
  2. 启动User Service (端口8081)
  3. 启动Product Service (端口8082)
  4. 启动Order Service (端口8083)

测试步骤:

bash 复制代码
# 1. 查看Eureka注册中心
打开浏览器访问: http://localhost:8761

# 2. 测试用户服务
curl http://localhost:8081/api/users/1
curl http://localhost:8081/api/users

# 3. 测试商品服务
curl http://localhost:8082/api/products/1
curl http://localhost:8082/api/products

# 4. 测试订单服务(通过服务发现调用其他服务)
curl http://localhost:8083/api/orders/services
curl -X POST http://localhost:8083/api/orders \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "productId": 1, "quantity": 2}'

# 5. 查看用户订单
curl http://localhost:8083/api/orders/user/1

这一章我们学习了Eureka注册中心的使用,实现了服务的自动注册和发现。

在下一章中,我们将学习如何使用LoadBalancer实现客户端负载均衡。

相关推荐
翘着二郎腿的程序猿11 小时前
SpringBoot集成Knife4j/Swagger:接口文档自动生成,告别手写API文档
java·spring boot·后端
小鸡脚来咯11 小时前
Spring Boot 常见面试题汇总
java·spring boot·后端
李白的粉11 小时前
基于springboot的阿博图书馆管理系统
java·spring boot·后端·毕业设计·课程设计·源代码·图书馆管理系统
ren0491811 小时前
Spring Framework、SpringBoot、Mybatis、Freemarker
spring boot·spring·mybatis
absunique11 小时前
Spring boot 3.3.1 官方文档 中文
java·数据库·spring boot
召田最帅boy11 小时前
Spring Boot博客系统集成AI智能摘要功能实战
人工智能·spring boot·后端
967711 小时前
spring boot 终端运行指令以及这个查询端口是否被占用,以及释放端口的命令
java·spring boot·后端
拾贰_C11 小时前
【idea | knife4j | springboot2/3|接上篇|终篇】knife4j版本号与spring boot版本不兼容问题(细节问题)
java·spring boot·intellij-idea
韩立学长11 小时前
基于Springboot医疗健康管理系统6sp2oz07(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
ab15151711 小时前
3.11二刷基础78、79,完成进阶
spring cloud