Spring Boot 3 微服务完全搭建指南
从零开始搭建企业级微服务架构,包含完整代码和详细注释
目录
1. 项目概述
1.1 技术栈
1.2 架构图
┌─────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ (Web/App/小程序) │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────────┐
│ 网关层 (Gateway) │
│ • 路由转发 • 鉴权认证 • 限流熔断 │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────────┐
│ 服务注册中心 (Nacos) │
│ • 服务注册 • 配置管理 • 服务发现 │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌───────▼──────┐ ┌───▼────┐ ┌────▼──────┐
│ 业务服务A │ │业务服务B│ │ 业务服务C │
│ • MyBatis-Flex│ │ ... │ │ ... │
│ • Redis │ │ │ │ │
│ • RabbitMQ │ │ │ │ │
│ • MinIO │ │ │ │ │
└──────────────┘ └────────┘ └───────────┘2. 环境准备
2.1 安装Docker和Docker Compose
# 安装Docker(以CentOS/Ubuntu为例)
# CentOS
curl -fsSL https://get.docker.com | sh
# Ubuntu
curl -fsSL https://get.docker.com | sudo sh
# 启动Docker
sudo systemctl start docker
sudo systemctl enable docker
# 安装Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose2.2 创建基础设施Docker Compose文件
创建 infrastructure/docker-compose.yml:
# infrastructure/docker-compose.yml
# 基础设施服务编排文件
# 包含:MySQL、Redis、RabbitMQ、MinIO、Nacos
version: '3.8'
services:
# ============================================
# MySQL 8.0 - 关系型数据库
# ============================================
mysql:
image: mysql:8.0
container_name: mysql
restart: always
environment:
# root用户密码
MYSQL_ROOT_PASSWORD: root123456
# 创建默认数据库
MYSQL_DATABASE: micro_service
# 时区设置
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
# 数据持久化
- ./mysql/data:/var/lib/mysql
# 自定义配置文件
- ./mysql/conf:/etc/mysql/conf.d
# 初始化SQL脚本
- ./mysql/init:/docker-entrypoint-initdb.d
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
networks:
- micro-service-network
# ============================================
# Redis 7.x - 缓存和分布式锁
# ============================================
redis:
image: redis:7-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
# 数据持久化
- ./redis/data:/data
# 配置文件
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- micro-service-network
# ============================================
# RabbitMQ 3.12 - 消息队列
# ============================================
rabbitmq:
image: rabbitmq:3.12-management-alpine
container_name: rabbitmq
restart: always
environment:
# 默认用户
RABBITMQ_DEFAULT_USER: admin
# 默认密码
RABBITMQ_DEFAULT_PASS: admin123456
TZ: Asia/Shanghai
ports:
# AMQP协议端口
- "5672:5672"
# 管理界面端口
- "15672:15672"
volumes:
- ./rabbitmq/data:/var/lib/rabbitmq
networks:
- micro-service-network
# ============================================
# MinIO - 对象存储
# ============================================
minio:
image: minio/minio:latest
container_name: minio
restart: always
environment:
# 根用户
MINIO_ROOT_USER: minioadmin
# 根密码
MINIO_ROOT_PASSWORD: minioadmin123
TZ: Asia/Shanghai
ports:
# API端口
- "9000:9000"
# 控制台端口
- "9001:9001"
volumes:
- ./minio/data:/data
# 启动命令:server /data 指定数据目录,--console-address 指定控制台地址
command: server /data --console-address ":9001"
networks:
- micro-service-network
# ============================================
# Nacos 2.2 - 注册中心和配置中心
# ============================================
nacos:
image: nacos/nacos-server:v2.2.3
container_name: nacos
restart: always
environment:
# standalone: 单机模式
MODE: standalone
# 使用MySQL作为数据源
SPRING_DATASOURCE_PLATFORM: mysql
# MySQL服务地址
MYSQL_SERVICE_HOST: mysql
# MySQL端口
MYSQL_SERVICE_PORT: 3306
# MySQL数据库名
MYSQL_SERVICE_DB_NAME: nacos
# MySQL用户名
MYSQL_SERVICE_USER: root
# MySQL密码
MYSQL_SERVICE_PASSWORD: root123456
TZ: Asia/Shanghai
ports:
# 主端口
- "8848:8848"
# gRPC端口(Nacos 2.x新增)
- "9848:9848"
# gRPC端口
- "9849:9849"
volumes:
- ./nacos/logs:/home/nacos/logs
- ./nacos/data:/home/nacos/data
depends_on:
- mysql
networks:
- micro-service-network
# 定义网络,所有服务在同一网络下可以互相通信
networks:
micro-service-network:
driver: bridge2.3 创建配置文件
创建 infrastructure/redis/redis.conf:
# Redis配置文件
# 开启持久化
appendonly yes
# 持久化文件目录
appendfsync everysec
# 设置密码(生产环境必须)
requirepass redis123456
# 最大内存限制
maxmemory 512mb
# 内存淘汰策略
maxmemory-policy allkeys-lru
# 绑定所有IP
bind 0.0.0.0
# 关闭保护模式(Docker环境需要)
protected-mode no创建 infrastructure/mysql/init/01-schema.sql:
-- MySQL初始化脚本
-- 创建Nacos所需数据库
-- 创建nacos数据库
CREATE DATABASE IF NOT EXISTS nacos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建业务数据库
CREATE DATABASE IF NOT EXISTS micro_service CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用业务数据库
USE micro_service;
-- 创建示例用户表
CREATE TABLE IF NOT EXISTS sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
avatar VARCHAR(500) COMMENT '头像URL',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_username (username),
INDEX idx_email (email),
INDEX idx_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
-- 插入测试数据
INSERT INTO sys_user (username, password, email, phone, status) VALUES
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5E', 'admin@example.com', '13800138000', 1),
('test', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5E', 'test@example.com', '13800138001', 1);2.4 启动基础设施
# 创建目录结构
mkdir -p infrastructure/{mysql/{data,conf,init},redis/data,rabbitmq/data,minio/data,nacos/{logs,data}}
# 启动所有服务
cd infrastructure
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f nacos2.5 验证服务
3. 基础架构搭建
3.1 父工程 POM 配置
创建 pom.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>micro-service-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Micro Service Parent</name>
<description>微服务父工程</description>
<!-- 子模块列表 -->
<modules>
<module>micro-gateway</module>
<module>micro-common</module>
<module>micro-service-user</module>
</modules>
<properties>
<!-- Java版本 -->
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Spring Boot版本 -->
<spring-boot.version>3.2.0</spring-boot.version>
<!-- Spring Cloud版本 -->
<spring-cloud.version>2023.0.0</spring-cloud.version>
<!-- Spring Cloud Alibaba版本 -->
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<!-- 组件版本 -->
<mybatis-flex.version>1.8.0</mybatis-flex.version>
<druid.version>1.2.20</druid.version>
<minio.version>8.5.7</minio.version>
<hutool.version>5.8.23</hutool.version>
<lombok.version>1.18.30</lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot依赖管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba依赖管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MyBatis-Flex -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MinIO客户端 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- Hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- MapStruct对象映射 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- Maven编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<!-- Lombok处理器 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- MapStruct处理器 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>3.2 网关服务 (micro-gateway)
创建 micro-gateway/pom.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.example</groupId>
<artifactId>micro-service-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>micro-gateway</artifactId>
<name>Micro Gateway</name>
<description>网关服务</description>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Boot Actuator监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>创建 micro-gateway/src/main/java/com/example/gateway/GatewayApplication.java:
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 网关服务启动类
*
* @SpringBootApplication: 标记为Spring Boot应用
* @EnableDiscoveryClient: 启用服务发现,注册到Nacos
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
System.out.println("=== 网关服务启动成功 ===");
}
}创建 micro-gateway/src/main/resources/application.yml:
# 网关服务主配置文件
# 注意:实际配置从Nacos配置中心读取
server:
port: 8080 # 网关服务端口
spring:
application:
name: micro-gateway # 服务名称,注册到Nacos时使用
profiles:
active: dev # 激活的环境配置
cloud:
nacos:
# 配置中心
config:
server-addr: 127.0.0.1:8848 # Nacos地址
namespace: dev # 命名空间
group: DEFAULT_GROUP # 配置分组
file-extension: yaml # 配置文件格式
# 共享配置
shared-configs:
- data-id: common.yaml
group: DEFAULT_GROUP
refresh: true
# 服务发现
discovery:
server-addr: 127.0.0.1:8848
namespace: dev
group: DEFAULT_GROUP创建 micro-gateway/src/main/resources/bootstrap.yml:
# 引导配置文件
# 在应用启动时最先加载,用于配置Nacos连接
spring:
application:
name: micro-gateway
profiles:
active: dev
cloud:
nacos:
username: nacos
password: nacos在Nacos配置中心创建 micro-gateway-dev.yaml:
# Nacos配置中心的网关路由配置
server:
port: 8080
spring:
cloud:
gateway:
# 全局默认过滤器
default-filters:
# 添加响应头
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
# 重试过滤器
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
# 路由配置
routes:
# 用户服务路由
- id: user-service-route
# lb:// 表示使用负载均衡,从Nacos获取服务实例
uri: lb://micro-service-user
predicates:
# 路径匹配:/api/user/** 转发到用户服务
- Path=/api/user/**
filters:
# 去掉路径前缀:/api/user/login -> /login
- StripPrefix=2
# 添加前缀:最终变成 /user/login
- PrefixPath=/user
# 请求限流
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒补充10个令牌
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量20
key-resolver: "#{@ipKeyResolver}" # 使用IP限流
# 文件服务路由
- id: file-service-route
uri: lb://micro-service-file
predicates:
- Path=/api/file/**
filters:
- StripPrefix=2
- PrefixPath=/file
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]': # 匹配所有路径
allowedOrigins: "*" # 允许所有来源(生产环境应限制)
allowedMethods: "*" # 允许所有方法
allowedHeaders: "*" # 允许所有请求头
allowCredentials: true # 允许携带cookie
maxAge: 3600 # 预检请求缓存时间
# 日志配置
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty: INFO3.3 公共模块 (micro-common)
创建 micro-common/pom.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.example</groupId>
<artifactId>micro-service-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>micro-common</artifactId>
<name>Micro Common</name>
<description>公共模块:工具类、配置类、常量等</description>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Spring AMQP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- MyBatis-Flex -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MinIO -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MapStruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>4. 核心组件集成
4.1 MyBatis-Flex 集成
创建 micro-common/src/main/java/com/example/common/config/MybatisFlexConfig.java:
package com.example.common.config;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.audit.ConsoleMessageCollector;
import com.mybatisflex.core.logicdelete.LogicDeleteManager;
import com.mybatisflex.core.logicdelete.impl.BooleanLogicDeleteProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* MyBatis-Flex 配置类
*
* MyBatis-Flex 是一个优雅的MyBatis增强框架,特点:
* 1. 零配置,开箱即用
* 2. 支持ActiveRecord模式
* 3. 强大的代码生成器
* 4. 完善的逻辑删除支持
*/
@Slf4j
@Configuration
public class MybatisFlexConfig {
/**
* 初始化配置
*
* @PostConstruct: 在依赖注入完成后执行
*/
@PostConstruct
public void init() {
// 开启SQL审计,打印执行的SQL(开发环境建议开启,生产环境关闭)
AuditManager.setAuditEnable(true);
// 设置SQL收集器,输出到控制台
AuditManager.setMessageCollector(new ConsoleMessageCollector());
// 设置逻辑删除处理器
// BooleanLogicDeleteProcessor: 使用布尔字段,删除时设置为true
LogicDeleteManager.setProcessor(new BooleanLogicDeleteProcessor());
log.info("=== MyBatis-Flex 配置初始化完成 ===");
}
}创建 micro-common/src/main/java/com/example/common/entity/BaseEntity.java:
package com.example.common.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.LogicDelete;
import com.mybatisflex.core.activerecord.Model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
*
* 所有业务实体都应该继承此类
* 提供了通用的字段:ID、创建时间、更新时间、逻辑删除标志
*
* @param <T> 实体类型,用于ActiveRecord模式
*/
@Data
@EqualsAndHashCode(callSuper = false)
public abstract class BaseEntity<T extends Model<T>> extends Model<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
* @Id: 标记为主键
* @KeyType: 主键生成策略,Auto表示数据库自增
*/
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 创建时间
* @Column: 映射数据库字段
* onInsertValue: 插入时自动填充当前时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
/**
* 更新时间
* onInsertValue: 插入时填充
* onUpdateValue: 更新时自动填充当前时间
*/
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
/**
* 逻辑删除标志
* @LogicDelete: 标记为逻辑删除字段
* true: 已删除, false: 未删除
*/
@LogicDelete
private Boolean deleted;
}4.2 Redis 集成
创建 micro-common/src/main/java/com/example/common/config/RedisConfig.java:
package com.example.common.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 配置类
*
* 配置RedisTemplate,用于操作Redis
* 配置了Key和Value的序列化方式
*/
@Configuration
@EnableCaching // 开启Spring Cache注解支持
public class RedisConfig {
/**
* 配置RedisTemplate
*
* RedisTemplate是Spring提供的Redis操作模板
* 需要配置序列化器,否则存储的数据会乱码
*
* @param connectionFactory Redis连接工厂
* @return 配置好的RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key序列化器:使用StringRedisSerializer
// 将Key序列化为字符串,便于阅读
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// Value序列化器:使用GenericJackson2JsonRedisSerializer
// 将Value序列化为JSON格式,支持复杂对象
ObjectMapper mapper = new ObjectMapper();
// 启用类型信息,反序列化时能正确还原对象类型
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer(mapper);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
// 初始化模板
template.afterPropertiesSet();
return template;
}
}创建 micro-common/src/main/java/com/example/common/util/RedisUtil.java:
package com.example.common.util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*
* 封装了常用的Redis操作,简化使用
* 包含:String、Hash、List、Set、ZSet、分布式锁等操作
*/
@Slf4j
@Component
@RequiredArgsConstructor // Lombok生成包含final字段的构造器
public class RedisUtil {
private final RedisTemplate<String, Object> redisTemplate;
// ==================== String操作 ====================
/**
* 设置String值
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置String值,带过期时间
*
* @param key 键
* @param value 值
* @param timeout 过期时间
* @param unit 时间单位
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取String值
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除Key
*
* @param key 键
* @return 是否成功
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 判断Key是否存在
*
* @param key 键
* @return 是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*
* @param key 键
* @param timeout 过期时间
* @param unit 时间单位
* @return 是否成功
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
// ==================== 分布式锁 ====================
/**
* 获取分布式锁(简单版)
*
* 使用SETNX命令实现
* 可能存在死锁问题,生产环境建议使用Redisson
*
* @param lockKey 锁的Key
* @param value 锁的值(通常使用UUID)
* @param expireTime 过期时间(秒)
* @return 是否获取成功
*/
public Boolean tryLock(String lockKey, String value, long expireTime) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
/**
* 释放分布式锁(Lua脚本保证原子性)
*
* @param lockKey 锁的Key
* @param value 锁的值(必须与获取时一致)
* @return 是否释放成功
*/
public Boolean unlock(String lockKey, String value) {
// Lua脚本:判断值是否匹配,匹配则删除
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(lockKey), value);
return result != null && result == 1;
}
}4.3 RabbitMQ 集成
创建 micro-common/src/main/java/com/example/common/config/RabbitMQConfig.java:
package com.example.common.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ 配置类
*
* 配置交换机、队列、绑定关系
* 配置消息转换器
*/
@Slf4j
@Configuration
public class RabbitMQConfig {
// ==================== 交换机定义 ====================
/**
* 直连交换机(Direct Exchange)
*
* 特点:精确匹配Routing Key
* 适用场景:点对点消息发送
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange("direct.exchange");
}
/**
* 主题交换机(Topic Exchange)
*
* 特点:支持通配符匹配Routing Key
* 适用场景:发布/订阅模式,如日志分类
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topic.exchange");
}
/**
* 扇形交换机(Fanout Exchange)
*
* 特点:广播到所有绑定的队列,忽略Routing Key
* 适用场景:广播消息
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanout.exchange");
}
// ==================== 队列定义 ====================
/**
* 邮件发送队列
*/
@Bean
public Queue emailQueue() {
return QueueBuilder.durable("email.queue") // 持久化队列
.withArgument("x-dead-letter-exchange", "") // 死信交换机
.withArgument("x-dead-letter-routing-key", "email.queue.dlq") // 死信路由键
.build();
}
/**
* 短信发送队列
*/
@Bean
public Queue smsQueue() {
return QueueBuilder.durable("sms.queue")
.build();
}
/**
* 订单处理队列(演示延迟队列)
*/
@Bean
public Queue orderDelayQueue() {
return QueueBuilder.durable("order.delay.queue")
.withArgument("x-dead-letter-exchange", "direct.exchange") // 死信交换机
.withArgument("x-dead-letter-routing-key", "order.process") // 死信路由键
.withArgument("x-message-ttl", 300000) // 消息TTL:5分钟
.build();
}
/**
* 订单处理队列
*/
@Bean
public Queue orderProcessQueue() {
return QueueBuilder.durable("order.process.queue")
.build();
}
// ==================== 绑定关系 ====================
/**
* 绑定邮件队列到直连交换机
*/
@Bean
public Binding emailBinding() {
return BindingBuilder
.bind(emailQueue())
.to(directExchange())
.with("email.send"); // Routing Key
}
/**
* 绑定短信队列到直连交换机
*/
@Bean
public Binding smsBinding() {
return BindingBuilder
.bind(smsQueue())
.to(directExchange())
.with("sms.send");
}
/**
* 绑定订单处理队列
*/
@Bean
public Binding orderProcessBinding() {
return BindingBuilder
.bind(orderProcessQueue())
.to(directExchange())
.with("order.process");
}
// ==================== 消息转换器 ====================
/**
* 配置JSON消息转换器
*
* 将Java对象序列化为JSON格式发送
* 接收时自动反序列化为Java对象
*/
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 配置RabbitTemplate
*
* RabbitTemplate是Spring提供的RabbitMQ操作模板
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(messageConverter());
// 设置确认回调(消息是否成功发送到交换机)
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.debug("消息成功发送到交换机: {}", correlationData);
} else {
log.error("消息发送到交换机失败: {}, 原因: {}", correlationData, cause);
}
});
// 设置返回回调(消息是否成功路由到队列)
template.setReturnsCallback(returned -> {
log.error("消息路由到队列失败: {}, 原因: {}",
returned.getMessage(), returned.getReplyText());
});
return template;
}
}创建 micro-common/src/main/java/com/example/common/util/RabbitMQUtil.java:
package com.example.common.util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
/**
* RabbitMQ工具类
*
* 封装了常用的消息发送操作
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RabbitMQUtil {
private final RabbitTemplate rabbitTemplate;
/**
* 发送消息到指定交换机
*
* @param exchange 交换机名称
* @param routingKey 路由键
* @param message 消息内容
*/
public void send(String exchange, String routingKey, Object message) {
try {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
log.debug("消息发送成功: exchange={}, routingKey={}", exchange, routingKey);
} catch (Exception e) {
log.error("消息发送失败: exchange={}, routingKey={}, error={}",
exchange, routingKey, e.getMessage());
throw e;
}
}
/**
* 发送延迟消息
*
* @param exchange 交换机名称
* @param routingKey 路由键
* @param message 消息内容
* @param delayMillis 延迟时间(毫秒)
*/
public void sendDelay(String exchange, String routingKey, Object message, long delayMillis) {
try {
rabbitTemplate.convertAndSend(exchange, routingKey, message, msg -> {
// 设置消息过期时间(TTL)
msg.getMessageProperties().setExpiration(String.valueOf(delayMillis));
return msg;
});
log.debug("延迟消息发送成功: exchange={}, routingKey={}, delay={}ms",
exchange, routingKey, delayMillis);
} catch (Exception e) {
log.error("延迟消息发送失败: {}", e.getMessage());
throw e;
}
}
/**
* 发送直连消息(发送到默认直连交换机)
*
* @param routingKey 路由键
* @param message 消息内容
*/
public void sendDirect(String routingKey, Object message) {
send("direct.exchange", routingKey, message);
}
/**
* 发送广播消息
*
* @param exchange 扇形交换机名称
* @param message 消息内容
*/
public void sendFanout(String exchange, Object message) {
// 扇形交换机忽略routingKey
send(exchange, "", message);
}
}4.4 MinIO 集成
创建 micro-common/src/main/java/com/example/common/config/MinioConfig.java:
package com.example.common.config;
import io.minio.MinioClient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinIO 配置类
*
* MinIO是一个高性能的对象存储系统,兼容Amazon S3 API
* 适用于:文件存储、图片存储、视频存储等
*/
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "minio") // 从配置文件读取minio.*属性
@Data
public class MinioConfig {
/**
* MinIO服务地址
* 示例:http://localhost:9000
*/
private String endpoint;
/**
* 访问密钥(Access Key)
*/
private String accessKey;
/**
* 秘密密钥(Secret Key)
*/
private String secretKey;
/**
* 默认存储桶名称
*/
private String bucketName = "default";
/**
* 创建MinIO客户端
*
* MinioClient是操作MinIO的核心类
*/
@Bean
public MinioClient minioClient() {
log.info("=== 初始化MinIO客户端: {} ===", endpoint);
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}创建 micro-common/src/main/java/com/example/common/util/MinioUtil.java:
package com.example.common.util;
import io.minio.*;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* MinIO工具类
*
* 封装了常用的对象存储操作
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {
private final MinioClient minioClient;
/**
* 创建存储桶
*
* @param bucketName 存储桶名称
*/
public void createBucket(String bucketName) {
try {
// 检查存储桶是否存在
boolean exists = minioClient.bucketExists(
BucketExistsArgs.builder().bucket(bucketName).build()
);
if (!exists) {
// 创建存储桶
minioClient.makeBucket(
MakeBucketArgs.builder().bucket(bucketName).build()
);
log.info("存储桶创建成功: {}", bucketName);
}
} catch (Exception e) {
log.error("存储桶创建失败: {}, error: {}", bucketName, e.getMessage());
throw new RuntimeException("存储桶创建失败", e);
}
}
/**
* 上传文件
*
* @param bucketName 存储桶名称
* @param objectName 对象名称(文件路径)
* @param file 上传的文件
* @return 文件访问URL
*/
public String uploadFile(String bucketName, String objectName, MultipartFile file) {
try {
// 确保存储桶存在
createBucket(bucketName);
// 上传文件
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), -1)
.build()
);
log.info("文件上传成功: {}/{}", bucketName, objectName);
// 返回文件URL
return getFileUrl(bucketName, objectName);
} catch (Exception e) {
log.error("文件上传失败: {}, error: {}", objectName, e.getMessage());
throw new RuntimeException("文件上传失败", e);
}
}
/**
* 上传文件(使用流)
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param inputStream 输入流
* @param contentType 内容类型
* @param size 文件大小
* @return 文件访问URL
*/
public String uploadFile(String bucketName, String objectName,
InputStream inputStream, String contentType, long size) {
try {
createBucket(bucketName);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(inputStream, size, -1)
.build()
);
return getFileUrl(bucketName, objectName);
} catch (Exception e) {
log.error("文件上传失败: {}", e.getMessage());
throw new RuntimeException("文件上传失败", e);
}
}
/**
* 获取文件URL
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 文件URL
*/
public String getFileUrl(String bucketName, String objectName) {
try {
// 生成预签名URL,有效期7天
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(7, TimeUnit.DAYS)
.build()
);
} catch (Exception e) {
log.error("获取文件URL失败: {}", e.getMessage());
throw new RuntimeException("获取文件URL失败", e);
}
}
/**
* 下载文件
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 输入流
*/
public InputStream downloadFile(String bucketName, String objectName) {
try {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
log.error("文件下载失败: {}", e.getMessage());
throw new RuntimeException("文件下载失败", e);
}
}
/**
* 删除文件
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
*/
public void deleteFile(String bucketName, String objectName) {
try {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
log.info("文件删除成功: {}/{}", bucketName, objectName);
} catch (Exception e) {
log.error("文件删除失败: {}", e.getMessage());
throw new RuntimeException("文件删除失败", e);
}
}
/**
* 生成上传URL(前端直传)
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 预签名上传URL
*/
public String getUploadUrl(String bucketName, String objectName) {
try {
createBucket(bucketName);
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(bucketName)
.object(objectName)
.expiry(1, TimeUnit.HOURS) // URL有效期1小时
.build()
);
} catch (Exception e) {
log.error("生成上传URL失败: {}", e.getMessage());
throw new RuntimeException("生成上传URL失败", e);
}
}
}5. 工具类和配置类
5.1 统一响应结果
创建 micro-common/src/main/java/com/example/common/result/Result.java:
package com.example.common.result;
import lombok.Data;
import java.io.Serializable;
/**
* 统一响应结果封装
*
* 所有API接口都返回此对象,保证响应格式统一
*
* @param <T> 数据类型
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态码
* 200: 成功
* 其他: 错误码
*/
private Integer code;
/**
* 提示消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
// ==================== 成功响应 ====================
/**
* 成功响应(无数据)
*/
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMessage());
return result;
}
/**
* 成功响应(有数据)
*/
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMessage());
result.setData(data);
return result;
}
/**
* 成功响应(自定义消息)
*/
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(message);
result.setData(data);
return result;
}
// ==================== 错误响应 ====================
/**
* 错误响应
*/
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(ResultCode.ERROR.getCode());
result.setMessage(message);
return result;
}
/**
* 错误响应(带状态码)
*/
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
/**
* 错误响应(使用ResultCode)
*/
public static <T> Result<T> error(ResultCode resultCode) {
Result<T> result = new Result<>();
result.setCode(resultCode.getCode());
result.setMessage(resultCode.getMessage());
return result;
}
}创建 micro-common/src/main/java/com/example/common/result/ResultCode.java:
package com.example.common.result;
import lombok.Getter;
/**
* 响应状态码枚举
*/
@Getter
public enum ResultCode {
// 成功
SUCCESS(200, "操作成功"),
// 客户端错误 1xxx
PARAM_ERROR(1001, "参数错误"),
PARAM_EMPTY(1002, "参数为空"),
PARAM_TYPE_ERROR(1003, "参数类型错误"),
PARAM_FORMAT_ERROR(1004, "参数格式错误"),
// 认证授权 2xxx
UNAUTHORIZED(2001, "未登录或登录已过期"),
FORBIDDEN(2002, "没有操作权限"),
TOKEN_INVALID(2003, "Token无效"),
TOKEN_EXPIRED(2004, "Token已过期"),
// 业务错误 3xxx
USER_NOT_EXIST(3001, "用户不存在"),
USER_ALREADY_EXIST(3002, "用户已存在"),
PASSWORD_ERROR(3003, "密码错误"),
ACCOUNT_DISABLED(3004, "账号已被禁用"),
// 系统错误 5xxx
ERROR(5000, "系统错误"),
SERVER_BUSY(5001, "服务器繁忙"),
DATABASE_ERROR(5002, "数据库操作失败"),
CACHE_ERROR(5003, "缓存操作失败"),
MQ_ERROR(5004, "消息队列操作失败"),
FILE_ERROR(5005, "文件操作失败");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}5.2 分页结果封装
创建 micro-common/src/main/java/com/example/common/result/PageResult.java:
package com.example.common.result;
import com.mybatisflex.core.paginate.Page;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页结果封装
*
* @param <T> 数据类型
*/
@Data
public class PageResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前页码
*/
private Long pageNum;
/**
* 每页大小
*/
private Long pageSize;
/**
* 总记录数
*/
private Long total;
/**
* 总页数
*/
private Long pages;
/**
* 数据列表
*/
private List<T> list;
/**
* 是否有下一页
*/
private Boolean hasNext;
/**
* 是否有上一页
*/
private Boolean hasPrevious;
/**
* 从MyBatis-Flex Page转换
*
* @param page MyBatis-Flex分页对象
* @return 分页结果
*/
public static <T> PageResult<T> of(Page<T> page) {
PageResult<T> result = new PageResult<>();
result.setPageNum(page.getPageNumber());
result.setPageSize(page.getPageSize());
result.setTotal(page.getTotalRow());
result.setPages(page.getTotalPage());
result.setList(page.getRecords());
result.setHasNext(page.getPageNumber() < page.getTotalPage());
result.setHasPrevious(page.getPageNumber() > 1);
return result;
}
}5.3 全局异常处理
创建 micro-common/src/main/java/com/example/common/exception/GlobalExceptionHandler.java:
package com.example.common.exception;
import com.example.common.result.Result;
import com.example.common.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
/**
* 全局异常处理器
*
* @RestControllerAdvice: 标记为全局异常处理类
* 作用:捕获Controller层抛出的所有异常,统一处理并返回标准响应
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*
* @param e 业务异常
* @return 错误响应
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常(@Valid)
*
* @param e 参数校验异常
* @return 错误响应
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
// 获取所有校验错误信息
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", message);
return Result.error(ResultCode.PARAM_ERROR.getCode(), message);
}
/**
* 处理参数绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数绑定失败: {}", message);
return Result.error(ResultCode.PARAM_ERROR.getCode(), message);
}
/**
* 处理其他所有异常
*
* @param e 异常
* @param request 请求对象
* @return 错误响应
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e, HttpServletRequest request) {
log.error("系统异常: {}, URL: {}", e.getMessage(), request.getRequestURI(), e);
return Result.error(ResultCode.ERROR);
}
}创建 micro-common/src/main/java/com/example/common/exception/BusinessException.java:
package com.example.common.exception;
import com.example.common.result.ResultCode;
import lombok.Getter;
/**
* 业务异常
*
* 用于抛出业务逻辑错误,会被GlobalExceptionHandler捕获处理
*/
@Getter
public class BusinessException extends RuntimeException {
private final Integer code;
public BusinessException(String message) {
super(message);
this.code = ResultCode.ERROR.getCode();
}
public BusinessException(ResultCode resultCode) {
super(resultCode.getMessage());
this.code = resultCode.getCode();
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
}5.4 JWT工具类
创建 micro-common/src/main/java/com/example/common/util/JwtUtil.java:
package com.example.common.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*
* JWT(JSON Web Token)用于用户认证和信息传递
* 结构:Header.Payload.Signature
*/
@Slf4j
@Component
public class JwtUtil {
/**
* 密钥,从配置文件读取
*/
@Value("${jwt.secret:your-256-bit-secret-your-256-bit-secret}")
private String secret;
/**
* 过期时间(毫秒),默认7天
*/
@Value("${jwt.expiration:604800000}")
private Long expiration;
/**
* 生成密钥
*/
private SecretKey getKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
/**
* 生成JWT Token
*
* @param userId 用户ID
* @param username 用户名
* @return JWT字符串
*/
public String generateToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims) // 自定义声明
.setSubject(String.valueOf(userId)) // 主题(用户ID)
.setIssuedAt(now) // 签发时间
.setExpiration(expiryDate) // 过期时间
.signWith(getKey(), SignatureAlgorithm.HS256) // 签名算法
.compact();
}
/**
* 从Token解析Claims
*
* @param token JWT字符串
* @return Claims对象
*/
public Claims parseToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
log.warn("Token已过期");
throw e;
} catch (JwtException e) {
log.warn("Token解析失败: {}", e.getMessage());
throw e;
}
}
/**
* 验证Token是否有效
*
* @param token JWT字符串
* @return 是否有效
*/
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 从Token获取用户ID
*
* @param token JWT字符串
* @return 用户ID
*/
public Long getUserIdFromToken(String token) {
Claims claims = parseToken(token);
return Long.valueOf(claims.get("userId").toString());
}
/**
* 从Token获取用户名
*
* @param token JWT字符串
* @return 用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("username").toString();
}
/**
* 判断Token是否过期
*
* @param token JWT字符串
* @return 是否过期
*/
public boolean isTokenExpired(String token) {
try {
Claims claims = parseToken(token);
return claims.getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
}6. 部署和运维
6.1 多环境配置文件
创建 micro-service-user/src/main/resources/application.yml:
# 主配置文件
# 用于指定激活的环境和加载Nacos配置
spring:
application:
name: micro-service-user
profiles:
active: dev # 激活dev环境
config:
import:
# 从Nacos加载配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
- optional:nacos:common.yaml
cloud:
nacos:
server-addr: ${NACOS_SERVER:127.0.0.1:8848}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
config:
namespace: ${NACOS_NAMESPACE:dev}
group: DEFAULT_GROUP
file-extension: yaml
discovery:
namespace: ${NACOS_NAMESPACE:dev}
group: DEFAULT_GROUP
metadata:
version: 1.0.0
region: default创建 micro-service-user/src/main/resources/application-dev.yml:
# 开发环境配置
# 本地开发使用,连接本地或Docker部署的服务
server:
port: 9001
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123456
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
redis:
host: localhost
port: 6379
password: redis123456
database: 0
timeout: 10s
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin123456
virtual-host: /
listener:
simple:
acknowledge-mode: manual
concurrency: 5
max-concurrency: 10
minio:
endpoint: http://localhost:9000
access-key: minioadmin
secret-key: minioadmin123
bucket-name: micro-service
# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
com.mybatisflex: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"创建 micro-service-user/src/main/resources/application-prod.yml:
# 生产环境配置
# 生产环境使用,使用环境变量注入敏感信息
server:
port: ${SERVER_PORT:9001}
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${MYSQL_URL:jdbc:mysql://mysql:3306/micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai}
username: ${MYSQL_USERNAME:root}
password: ${MYSQL_PASSWORD}
druid:
initial-size: 10
min-idle: 10
max-active: 100
max-wait: 60000
test-on-borrow: false
test-while-idle: true
redis:
host: ${REDIS_HOST:redis}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD}
database: 0
timeout: 30s
lettuce:
pool:
max-active: 50
max-idle: 50
min-idle: 10
rabbitmq:
host: ${RABBITMQ_HOST:rabbitmq}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:admin}
password: ${RABBITMQ_PASSWORD}
virtual-host: /
listener:
simple:
acknowledge-mode: manual
concurrency: 10
max-concurrency: 50
minio:
endpoint: ${MINIO_ENDPOINT:http://minio:9000}
access-key: ${MINIO_ACCESS_KEY}
secret-key: ${MINIO_SECRET_KEY}
bucket-name: ${MINIO_BUCKET:micro-service}
# 生产环境日志
logging:
level:
root: WARN
com.example: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: /var/log/micro-service/app.log6.2 Dockerfile
创建 micro-service-user/Dockerfile:
# ============================================
# Spring Boot 服务 Dockerfile
# 使用多阶段构建,减小镜像体积
# ============================================
# 第一阶段:构建阶段
FROM eclipse-temurin:17-jdk-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制Maven配置
COPY pom.xml .
COPY micro-common/pom.xml micro-common/
COPY micro-service-user/pom.xml micro-service-user/
# 下载依赖(利用Docker缓存层)
RUN mkdir -p micro-common/src micro-service-user/src
RUN --mount=type=cache,target=/root/.m2 \
mvn dependency:go-offline -B
# 复制源代码
COPY micro-common/src micro-common/src
COPY micro-service-user/src micro-service-user/src
# 构建项目
RUN --mount=type=cache,target=/root/.m2 \
mvn clean package -DskipTests -pl micro-service-user -am
# 第二阶段:运行阶段
FROM eclipse-temurin:17-jre-alpine
# 安装常用工具
RUN apk add --no-cache curl tzdata
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建非root用户(安全最佳实践)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 设置工作目录
WORKDIR /app
# 从构建阶段复制JAR文件
COPY --from=builder /app/micro-service-user/target/*.jar app.jar
# 创建日志目录
RUN mkdir -p /var/log/micro-service && chown -R appuser:appgroup /var/log/micro-service
# 切换到非root用户
USER appuser
# 暴露端口
EXPOSE 9001
# JVM参数(可通过环境变量覆盖)
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError"
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:9001/actuator/health || exit 1
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]创建 micro-gateway/Dockerfile:
# 网关服务 Dockerfile
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY micro-gateway/pom.xml micro-gateway/
RUN mkdir -p micro-gateway/src
RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline -B
COPY micro-gateway/src micro-gateway/src
RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests -pl micro-gateway
FROM eclipse-temurin:17-jre-alpine
RUN apk add --no-cache curl tzdata
ENV TZ=Asia/Shanghai
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/micro-gateway/target/*.jar app.jar
USER appuser
EXPOSE 8080
ENV JAVA_OPTS="-Xms256m -Xmx256m"
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]6.3 一键启动脚本
创建 start.sh:
#!/bin/bash
# ============================================
# 微服务一键启动脚本
# 支持:本地启动、Docker启动、生产部署
# ============================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 打印带颜色的信息
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 帮助信息
show_help() {
cat << EOF
微服务一键启动脚本
用法: ./start.sh [选项] [命令]
选项:
-h, --help 显示帮助信息
-e, --env 指定环境 (dev|test|prod),默认: dev
-m, --mode 启动模式 (local|docker|build),默认: local
命令:
start 启动服务
stop 停止服务
restart 重启服务
status 查看服务状态
logs 查看日志
clean 清理环境
示例:
./start.sh start # 本地开发环境启动
./start.sh -e prod -m docker start # Docker生产环境启动
./start.sh stop # 停止服务
./start.sh logs gateway # 查看网关日志
EOF
}
# 默认参数
ENV="dev"
MODE="local"
COMMAND=""
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-e|--env)
ENV="$2"
shift 2
;;
-m|--mode)
MODE="$2"
shift 2
;;
start|stop|restart|status|logs|clean)
COMMAND="$1"
shift
;;
*)
print_error "未知参数: $1"
show_help
exit 1
;;
esac
done
# 检查命令
if [ -z "$COMMAND" ]; then
print_error "请指定命令"
show_help
exit 1
fi
print_info "环境: $ENV, 模式: $MODE, 命令: $COMMAND"
# ============================================
# 本地模式
# ============================================
start_local() {
print_info "启动基础设施服务..."
# 检查Docker是否运行
if ! docker info > /dev/null 2>&1; then
print_error "Docker未运行,请先启动Docker"
exit 1
fi
# 启动基础设施
cd infrastructure
docker-compose up -d
cd ..
print_info "等待服务启动..."
sleep 10
# 检查服务健康状态
check_services
print_info "启动微服务..."
# 编译并启动网关
print_info "启动网关服务..."
cd micro-gateway
mvn spring-boot:run -Dspring-boot.run.profiles=$ENV &
cd ..
# 编译并启动用户服务
print_info "启动用户服务..."
cd micro-service-user
mvn spring-boot:run -Dspring-boot.run.profiles=$ENV &
cd ..
print_info "所有服务启动完成!"
print_info "网关地址: http://localhost:8080"
print_info "用户服务: http://localhost:9001"
}
stop_local() {
print_info "停止微服务..."
# 停止Java进程
pkill -f "micro-gateway" || true
pkill -f "micro-service-user" || true
# 停止基础设施
cd infrastructure
docker-compose down
cd ..
print_info "服务已停止"
}
# ============================================
# Docker模式
# ============================================
start_docker() {
print_info "Docker模式启动..."
# 构建镜像
print_info "构建Docker镜像..."
docker build -t micro-gateway:latest -f micro-gateway/Dockerfile .
docker build -t micro-service-user:latest -f micro-service-user/Dockerfile .
# 启动所有服务
docker-compose -f docker-compose.yml -f docker-compose.$ENV.yml up -d
print_info "服务启动完成!"
docker-compose ps
}
stop_docker() {
print_info "停止Docker服务..."
docker-compose down
}
# ============================================
# 通用函数
# ============================================
check_services() {
print_info "检查服务健康状态..."
services=("http://localhost:8848/nacos" "http://localhost:15672" "http://localhost:9001")
names=("Nacos" "RabbitMQ" "MinIO")
for i in "${!services[@]}"; do
if curl -s "${services[$i]}" > /dev/null; then
print_info "${names[$i]} 运行正常"
else
print_warn "${names[$i]} 可能未就绪,继续等待..."
fi
done
}
show_status() {
print_info "服务状态:"
if [ "$MODE" = "docker" ]; then
docker-compose ps
else
# 检查Java进程
pgrep -f "micro-gateway" > /dev/null && print_info "网关服务: 运行中" || print_warn "网关服务: 未运行"
pgrep -f "micro-service-user" > /dev/null && print_info "用户服务: 运行中" || print_warn "用户服务: 未运行"
# 检查Docker容器
cd infrastructure
docker-compose ps
cd ..
fi
}
show_logs() {
SERVICE=${1:-all}
if [ "$MODE" = "docker" ]; then
if [ "$SERVICE" = "all" ]; then
docker-compose logs -f
else
docker-compose logs -f $SERVICE
fi
else
print_warn "本地模式请查看各服务的控制台输出"
fi
}
clean_environment() {
print_warn "清理所有数据,此操作不可恢复!"
read -p "确认继续? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
stop_local
rm -rf infrastructure/mysql/data
rm -rf infrastructure/redis/data
rm -rf infrastructure/rabbitmq/data
rm -rf infrastructure/minio/data
print_info "环境已清理"
else
print_info "取消清理"
fi
}
# ============================================
# 主逻辑
# ============================================
case $COMMAND in
start)
if [ "$MODE" = "docker" ]; then
start_docker
else
start_local
fi
;;
stop)
if [ "$MODE" = "docker" ]; then
stop_docker
else
stop_local
fi
;;
restart)
$0 stop
sleep 2
$0 start
;;
status)
show_status
;;
logs)
show_logs $2
;;
clean)
clean_environment
;;
*)
print_error "未知命令: $COMMAND"
show_help
exit 1
;;
esac赋予执行权限:
chmod +x start.sh6.4 Docker Compose 生产配置
创建 docker-compose.yml:
version: '3.8'
services:
# 网关服务
gateway:
image: micro-gateway:latest
container_name: micro-gateway
restart: always
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_SERVER=nacos:8848
- JAVA_OPTS=-Xms256m -Xmx256m
depends_on:
- nacos
networks:
- micro-service-network
# 用户服务
user-service:
image: micro-service-user:latest
container_name: micro-service-user
restart: always
ports:
- "9001:9001"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_SERVER=nacos:8848
- MYSQL_URL=jdbc:mysql://mysql:3306/micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=false
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- RABBITMQ_HOST=rabbitmq
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
- MINIO_ENDPOINT=http://minio:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- JAVA_OPTS=-Xms512m -Xmx512m
depends_on:
- mysql
- redis
- rabbitmq
- minio
- nacos
networks:
- micro-service-network
networks:
micro-service-network:
external: true # 使用外部网络,与基础设施共享7. 完整项目结构
micro-service-parent/ # 父工程
├── pom.xml # 父POM,统一管理依赖版本
├── start.sh # 一键启动脚本
├── docker-compose.yml # Docker Compose生产配置
├── README.md # 项目说明文档
│
├── infrastructure/ # 基础设施Docker Compose
│ ├── docker-compose.yml # MySQL、Redis、RabbitMQ、MinIO、Nacos
│ ├── mysql/
│ │ ├── init/ # 初始化SQL脚本
│ │ └── conf/ # MySQL配置文件
│ └── redis/
│ └── redis.conf # Redis配置文件
│
├── micro-common/ # 公共模块
│ ├── pom.xml
│ └── src/main/java/com/example/common/
│ ├── config/ # 配置类
│ │ ├── MybatisFlexConfig.java
│ │ ├── RedisConfig.java
│ │ ├── RabbitMQConfig.java
│ │ └── MinioConfig.java
│ ├── entity/ # 基础实体
│ │ └── BaseEntity.java
│ ├── result/ # 响应结果封装
│ │ ├── Result.java
│ │ ├── ResultCode.java
│ │ └── PageResult.java
│ ├── exception/ # 异常处理
│ │ ├── GlobalExceptionHandler.java
│ │ └── BusinessException.java
│ └── util/ # 工具类
│ ├── RedisUtil.java
│ ├── RabbitMQUtil.java
│ ├── MinioUtil.java
│ └── JwtUtil.java
│
├── micro-gateway/ # 网关服务
│ ├── pom.xml
│ ├── Dockerfile
│ └── src/main/java/com/example/gateway/
│ └── GatewayApplication.java
│ └── src/main/resources/
│ ├── application.yml
│ └── bootstrap.yml
│
└── micro-service-user/ # 用户服务(示例业务服务)
├── pom.xml
├── Dockerfile
└── src/main/java/com/example/user/
├── UserServiceApplication.java
├── controller/
├── service/
├── mapper/
└── entity/
└── src/main/resources/
├── application.yml
├── application-dev.yml
└── application-prod.yml快速开始指南
1. 克隆项目并启动
# 克隆项目
git clone http://localhost:9090/git/student_study.git
cd student_study
# 启动所有服务(开发环境)
./start.sh start
# 或启动所有服务(Docker模式)
./start.sh -m docker start2. 验证服务
3. 开发新服务
复制
micro-service-user模块修改
pom.xml中的 artifactId 和 name修改
application.yml中的服务名称实现业务代码
在父POM中添加新模块
总结
本文档详细介绍了从零搭建Spring Boot 3微服务架构的完整过程:
核心技术点
Spring Boot 3 - 使用Java 17,最新特性
Spring Cloud Alibaba - Nacos注册中心/配置中心
Spring Cloud Gateway - 高性能网关
MyBatis-Flex - 新一代ORM框架
Redis - 缓存、分布式锁
RabbitMQ - 消息队列,支持延迟消息
MinIO - 对象存储
Docker - 容器化部署
最佳实践
✅ 多环境配置(dev/test/prod)
✅ 统一响应格式和异常处理
✅ 完善的工具类封装
✅ 一键启动脚本
✅ Docker多阶段构建
✅ 健康检查和日志配置
按照本文档,任何小白都可以搭建出可运行的微服务架构!