Spring Cloud微服务项目完整搭建指南
好的,这是一个非常经典和完整的微服务实践问题。我将为您提供一个从零开始、保姆级的教程,一步步搭建一个包含用户服务(user-service)和问答服务(qa-service)的 Spring Cloud 项目。
我们将使用以下技术栈:
构建工具: Maven
开发环境: JDK 17+ (Spring Boot 3.x 要求)
Spring Boot: 3.2.5
Spring Cloud: 2023.0.1
服务注册与发现: Eureka
配置中心: Spring Cloud Config
数据库: MySQL (使用 Spring Data JPA)
服务间通信: OpenFeign
整个教程分为以下几个大步骤,请严格按照顺序执行:
环境准备
项目整体结构与父工程搭建
搭建服务注册中心 (Eureka Server)
搭建配置中心 (Config Server)
创建配置中心的 Git 仓库
搭建用户服务 (user-service)
搭建问答服务 (qa-service)
启动与测试整个系统
第 1 步:环境准备
安装 JDK 17 或更高版本。
安装 Maven。
安装 Git。
安装 Docker (推荐,用于快速启动 MySQL 数据库)。
安装一个 IDE,如 IntelliJ IDEA 或 VS Code。
启动 MySQL 数据库 打开终端,运行以下 Docker 命令来启动一个 MySQL 8.0 实例。
Bash
docker run -d --name mysql-for-sc -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=user_db -e MYSQL_DATABASE=qa_db mysql:8.0
这会创建一个名为 mysql-for-sc
的容器,暴露 3306 端口,并创建两个我们稍后会用到的数据库:user_db
和 qa_db
。
第 2 步:项目整体结构与父工程搭建
我们将创建一个父项目 spring-cloud-tutorial
来管理所有子模块的依赖和版本。
2.1 最终目录结构
spring-cloud-tutorial/
├── .gitignore
├── pom.xml # 父POM,管理所有依赖版本
├── eureka-server/ # 服务注册中心
│ └── ...
├── config-server/ # 配置中心
│ └── ...
├── user-service/ # 用户服务
│ └── ...
└── qa-service/ # 问答服务└── ...
2.2 创建父项目
创建一个名为
spring-cloud-tutorial
的空文件夹。在文件夹中创建一个
pom.xml
文件,内容如下。这个文件至关重要,它定义了 Spring Boot 和 Spring Cloud 的版本,并统一管理所有子模块的依赖。
spring-cloud-tutorial/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><groupId>com.example</groupId><artifactId>spring-cloud-tutorial</artifactId><version>1.0.0</version><packaging>pom</packaging> <modules><module>eureka-server</module><module>config-server</module><module>user-service</module><module>qa-service</module></modules><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-boot.version>3.2.5</spring-boot.version><spring-cloud.version>2023.0.1</spring-cloud.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>
第 3 步:搭建服务注册中心 (Eureka Server)
Eureka 是服务“管理员”,所有服务启动后都来这里“报到”(注册),其他服务可以从这里找到它们。
3.1 创建 eureka-server
模块
在 spring-cloud-tutorial
目录下创建一个 eureka-server
子模块(一个标准的 Spring Boot 项目)。
3.2 目录结构
eureka-server/
├── pom.xml
└── src/└── main/├── java/│ └── com/│ └── example/│ └── eurekaserver/│ └── EurekaServerApplication.java└── resources/└── application.yml
3.3 pom.xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project ...><modelVersion>4.0.0</modelVersion><parent><groupId>com.example</groupId><artifactId>spring-cloud-tutorial</artifactId><version>1.0.0</version><relativePath>../pom.xml</relativePath></parent><artifactId>eureka-server</artifactId><name>eureka-server</name><description>Service Registry Center</description><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency></dependencies>
</project>
3.4 启动类
@EnableEurekaServer
注解声明这是一个 Eureka 服务注册中心。
EurekaServerApplication.java
Java
package com.example.eurekaserver;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}
}
3.5 配置文件
application.yml
YAML
server:port: 8761 # Eureka 默认端口eureka:instance:hostname: localhostclient:# Eureka Server 也是一个客户端,但我们不希望它注册自己register-with-eureka: false# 也不需要从其他注册中心获取服务列表fetch-registry: falseservice-url:# 单机模式下,指向自己defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
第 4 步:搭建配置中心 (Config Server)
Config Server 用于集中管理所有微服务的配置文件。
4.1 创建 config-server
模块
在 spring-cloud-tutorial
目录下创建 config-server
子模块。
4.2 目录结构
config-server/
├── pom.xml
└── src/└── main/├── java/│ └── com/│ └── example/│ └── configserver/│ └── ConfigServerApplication.java└── resources/└── application.yml
4.3 pom.xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project ...><modelVersion>4.0.0</modelVersion><parent><groupId>com.example</groupId><artifactId>spring-cloud-tutorial</artifactId><version>1.0.0</version><relativePath>../pom.xml</relativePath></parent><artifactId>config-server</artifactId><name>config-server</name><description>Centralized Configuration Server</description><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-config-server</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency></dependencies>
</project>
4.4 启动类
@EnableConfigServer
注解声明这是一个配置中心。
ConfigServerApplication.java
Java
package com.example.configserver;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {public static void main(String[] args) {SpringApplication.run(ConfigServerApplication.class, args);}
}
4.5 配置文件
application.yml
YAML
server:port: 8888 # Config Server 默认端口spring:application:name: config-server # 服务名称cloud:config:server:git:# IMPORTANT: 替换成你自己的 Git 仓库地址uri: https://github.com/YOUR_USERNAME/spring-cloud-configs.git# 如果是本地仓库,可以使用 file:///path/to/your/repo# uri: file:///Users/yourname/path/to/spring-cloud-configsdefault-label: main # Git 分支# 如果仓库是私有的,需要配置 username 和 password# username: your-git-username# password: your-git-password# 将自己注册到 Eureka
eureka:client:service-url:defaultZone: http://localhost:8761/eureka/
第 5 步:创建配置中心的 Git 仓库
这是所有微服务配置文件的存放地。
在 GitHub, GitLab 或本地创建一个 Git 仓库 (例如
spring-cloud-configs
)。在仓库中创建以下几个文件:
user-service.yml
YAML
server:port: 8081 # 用户服务端口# 数据库配置
spring:datasource:url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: update # 自动更新表结构show-sql: true # 显示 SQL# Eureka 配置
eureka:instance:prefer-ip-address: true # 优先使用 IP 地址注册client:service-url:defaultZone: http://localhost:8761/eureka/# 暴露所有 actuator 端点以供监控
management:endpoints:web:exposure:include: "*"
qa-service.yml
YAML
server:port: 8082 # 问答服务端口# 数据库配置
spring:datasource:url: jdbc:mysql://localhost:3306/qa_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: true# Eureka 配置
eureka:instance:prefer-ip-address: trueclient:service-url:defaultZone: http://localhost:8761/eureka/# 暴露所有 actuator 端点
management:endpoints:web:exposure:include: "*"# Feign 配置
feign:client:config:default:connect-timeout: 5000 # 连接超时时间read-timeout: 5000 # 读取超时时间
将这些文件 commit 并 push 到你的 Git 仓库。确保
config-server
的application.yml
中的uri
指向这个仓库。
第 6 步:搭建用户服务 (user-service)
6.1 创建 user-service
模块
6.2 目录结构
user-service/
├── pom.xml
└── src/└── main/├── java/│ └── com/│ └── example/│ └── userservice/│ ├── controller/│ │ └── UserController.java│ ├── entity/│ │ └── User.java│ ├── repository/│ │ └── UserRepository.java│ └── UserSerivceApplication.java└── resources/├── bootstrap.yml # 关键:用于连接 Config Server└── application.yml # 可以为空,或放一些不常变的本地配置
6.3 pom.xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project ...><modelVersion>4.0.0</modelVersion><parent><groupId>com.example</groupId><artifactId>spring-cloud-tutorial</artifactId><version>1.0.0</version><relativePath>../pom.xml</relativePath></parent><artifactId>user-service</artifactId><name>user-service</name><description>User Service</description><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies>
</project>
6.4 bootstrap.yml
这个文件是关键。它的加载优先级高于 application.yml
,用于告诉服务去哪里找配置中心。
src/main/resources/bootstrap.yml
YAML
spring:application:# 这个名字必须和 Git 仓库里的配置文件名 (user-service.yml) 对应name: user-servicecloud:config:# 配置中心地址uri: http://localhost:8888# 使用的分支label: main
application.yml
可以留空,因为所有配置都将从 Config Server 加载。
6.5 Java 代码
Entity (User.java
) 注意:Spring Boot 3 使用 jakarta.persistence
而不是 javax.persistence
。
Java
package com.example.userservice.entity;import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;@Entity
@Table(name = "users") // 'user' is a reserved keyword in some DBs
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String email;// Getters and Setters...
}
Repository (UserRepository.java
)
Java
package com.example.userservice.repository;import com.example.userservice.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Controller (UserController.java
)
Java
package com.example.userservice.controller;import com.example.userservice.entity.User;
import com.example.userservice.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserRepository userRepository;@PostMappingpublic User createUser(@RequestBody User user) {return userRepository.save(user);}@GetMapping("/{id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {return userRepository.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());}@GetMappingpublic List<User> getAllUsers() {return userRepository.findAll();}
}
Application (UserServiceApplication.java
)
Java
package com.example.userservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class UserServiceApplication {public static void main(String[] args) {SpringApplication.run(UserServiceApplication.class, args);}
}
第 7 步:搭建问答服务 (qa-service)
此服务将通过 Feign 调用 user-service
。
7.1 创建 qa-service
模块
7.2 目录结构
qa-service/
├── pom.xml
└── src/└── main/├── java/│ └── com/│ └── example/│ └── qaservice/│ ├── client/ # Feign Client 接口│ │ └── UserClient.java│ ├── controller/│ │ └── QuestionController.java│ ├── dto/ # 数据传输对象│ │ ├── QuestionDTO.java│ │ └── UserDTO.java│ ├── entity/│ │ └── Question.java│ ├── repository/│ │ └── QuestionRepository.java│ └── QaServiceApplication.java└── resources/└── bootstrap.yml
7.3 pom.xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project ...><modelVersion>4.0.0</modelVersion><parent><groupId>com.example</groupId><artifactId>spring-cloud-tutorial</artifactId><version>1.0.0</version><relativePath>../pom.xml</relativePath></parent><artifactId>qa-service</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>
</project>
7.4 bootstrap.yml
src/main/resources/bootstrap.yml
YAML
spring:application:name: qa-service # 对应 qa-service.ymlcloud:config:uri: http://localhost:8888label: main
7.5 Java 代码
Entity (Question.java
)
Java
package com.example.qaservice.entity;import jakarta.persistence.*;@Entity
@Table(name = "questions")
public class Question {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String title;private String content;private Long authorId; // 提问者的用户ID// Getters and Setters...
}
Repository (QuestionRepository.java
)
Java
package com.example.qaservice.repository;import com.example.qaservice.entity.Question;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {
}
DTOs (数据传输对象) 创建一个 UserDTO
来接收来自 user-service
的数据。
Java
// package com.example.qaservice.dto;
public class UserDTO {private Long id;private String username;private String email;// Getters and Setters...
}
创建一个 QuestionDTO
来组合问题和作者信息。
Java
// package com.example.qaservice.dto;
public class QuestionDTO {private Long id;private String title;private String content;private UserDTO author; // 作者信息// Getters and Setters...
}
Feign Client (UserClient.java
) 这是 Feign 的核心,定义一个接口来声明式地调用远程服务。
Java
package com.example.qaservice.client;import com.example.qaservice.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;// name = "user-service" 必须是用户服务在 Eureka 上注册的服务名
@FeignClient(name = "user-service")
public interface UserClient {// 路径和方法签名必须和 user-service 的 Controller 完全匹配@GetMapping("/users/{id}")UserDTO getUserById(@PathVariable("id") Long id);
}
Controller (QuestionController.java
)
Java
package com.example.qaservice.controller;import com.example.qaservice.client.UserClient;
import com.example.qaservice.dto.QuestionDTO;
import com.example.qaservice.dto.UserDTO;
import com.example.qaservice.entity.Question;
import com.example.qaservice.repository.QuestionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/questions")
public class QuestionController {@Autowiredprivate QuestionRepository questionRepository;@Autowiredprivate UserClient userClient; // 注入 Feign Client@PostMappingpublic Question createQuestion(@RequestBody Question question) {return questionRepository.save(question);}@GetMapping("/{id}")public ResponseEntity<QuestionDTO> getQuestionWithAuthor(@PathVariable Long id) {return questionRepository.findById(id).map(question -> {// 通过 Feign 调用 user-service 获取作者信息UserDTO author = userClient.getUserById(question.getAuthorId());// 组装 DTOQuestionDTO dto = new QuestionDTO();dto.setId(question.getId());dto.setTitle(question.getTitle());dto.setContent(question.getContent());dto.setAuthor(author);return ResponseEntity.ok(dto);}).orElse(ResponseEntity.notFound().build());}
}
Application (QaServiceApplication.java
) 使用 @EnableFeignClients
开启 Feign 功能。
Java
package com.example.qaservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients // 开启 Feign
public class QaServiceApplication {public static void main(String[] args) {SpringApplication.run(QaServiceApplication.class, args);}
}
第 8 步:启动与测试整个系统
8.1 启动顺序
必须严格按照以下顺序启动,否则会因依赖服务未就绪而失败!
启动 MySQL:
docker start mysql-for-sc
(如果已在第一步启动,则跳过)启动
eureka-server
: 运行EurekaServerApplication
。启动后,访问
http://localhost:8761
。你应该能看到 Eureka 的管理界面,目前 "Instances currently registered with Eureka" 下是空的。
启动
config-server
: 运行ConfigServerApplication
。启动后,刷新 Eureka 界面 (
http://localhost:8761
),你应该能看到CONFIG-SERVER
已经注册上来了。你也可以测试 Config Server 是否正常工作,访问
http://localhost:8888/user-service/main
,应该能看到user-service.yml
的配置内容。
启动
user-service
: 运行UserServiceApplication
。启动后,刷新 Eureka 界面,你应该能看到
USER-SERVICE
也注册上来了。
启动
qa-service
: 运行QaServiceApplication
。启动后,刷新 Eureka 界面,你应该能看到
QA-SERVICE
也注册上来了。
8.2 功能测试 (使用 curl 或 Postman)
创建一个用户 (调用 user-service)
Bashcurl -X POST http://localhost:8081/users \ -H "Content-Type: application/json" \ -d '{"username": "Alice", "email": "alice@example.com"}'
你会得到类似
{"id":1,"username":"Alice","email":"alice@example.com"}
的返回。记下这个id
(这里是 1)。验证用户创建成功 (调用 user-service)
Bashcurl http://localhost:8081/users/1
创建一个问题 (调用 qa-service) 这个问题的作者是刚才创建的用户 (id=1)。
Bashcurl -X POST http://localhost:8082/questions \ -H "Content-Type: application/json" \ -d '{"title": "What is Spring Cloud?", "content": "Can someone explain Spring Cloud in simple terms?", "authorId": 1}'
你会得到问题创建成功的返回,记下问题的
id
(这里假设也是 1)。测试 Feign 调用 (关键步骤) 现在,我们请求
Bashqa-service
获取问题详情。qa-service
内部会自动调用user-service
来获取作者信息。curl http://localhost:8082/questions/1
如果一切正常,你会得到一个包含了用户信息的完整响应:
JSON{"id": 1,"title": "What is Spring Cloud?","content": "Can someone explain Spring Cloud in simple terms?","author": {"id": 1,"username": "Alice","email": "alice@example.com"} }
看到这个结果,恭喜你,整个系统已经成功跑起来了!你已经搭建了一个包含服务注册、配置中心、数据库交互、服务间通信的完整 Spring Cloud 微服务项目。