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实现客户端负载均衡。

相关推荐
Tadas-Gao1 小时前
微服务注册中心选型深度分析:Eureka、Nacos与新一代替代方案
java·分布式·微服务·云原生·eureka·架构·系统架构
m0_488913011 小时前
新手小白也能学会的Dify本地部署教程(超详细)
人工智能·搜索引擎·云原生·eureka·开源·大模型·产品经理
AI开发架构师1 小时前
大数据领域Eureka的服务注册中心搭建
大数据·ai·云原生·eureka
fyakm1 小时前
Spring Cloud Eureka:服务注册与发现(附服务端和客户端代码)
spring·spring cloud·eureka
希忘auto1 小时前
Spring Cloud之注册中心之Eureka
java·spring cloud·eureka
v***Y891 小时前
SpringCloud 整合 Dubbo
spring·spring cloud·dubbo
S***q3771 小时前
SpringCloud 整合 Dubbo
spring·spring cloud·dubbo
百锦再1 小时前
HashMap、Hashtable、TreeMap异同深度详解
jvm·spring boot·struts·spring cloud·缓存·kafka·tomcat
RFG20121 小时前
18、Dubbo实例注入:简化微服务架构中的依赖管理【面向初学者】
人工智能·后端·微服务·云原生·架构·tomcat·dubbo