引言:微服务架构的现状与挑战
在当今互联网技术快速迭代的背景下,单体应用架构已难以满足业务快速发展的需求。微服务架构通过将应用拆分为一系列小型、自治的服务,实现了技术栈多样化、团队独立开发和部署等优势,但同时也带来了服务治理、分布式协调、链路追踪等新的挑战。
Spring Cloud Alibaba 作为国内最流行的微服务解决方案之一,整合了阿里巴巴中间件生态与 Spring Cloud 标准,为开发者提供了一套完整的微服务治理方案。本文将带您从搭建基础框架开始,逐步实现服务注册发现、配置中心、熔断限流、网关路由等核心功能,并最终构建一套完善的监控告警体系,全方位掌握微服务从构建到运维的全链路实践。
一、环境准备与技术选型
1.1 开发环境
- JDK: 17.0.10
- Maven: 3.9.6
- MySQL: 8.0.36
- Docker: 26.1.4 (用于部署中间件)
- IDE: IntelliJ IDEA 2024.1
1.2 技术栈选型
组件 | 功能 | 版本 |
---|---|---|
Spring Boot | 应用开发基础框架 | 3.2.6 |
Spring Cloud | 微服务标准规范 | 2023.0.2 |
Spring Cloud Alibaba | 微服务组件套件 | 2023.0.1.0 |
Nacos | 服务注册与配置中心 | 2.3.2 |
Sentinel | 熔断与限流 | 1.8.7 |
Spring Cloud Gateway | 网关服务 | 4.1.2 |
OpenFeign | 服务调用 | 4.1.2 |
MyBatis-Plus | ORM 框架 | 3.5.6 |
Seata | 分布式事务 | 2.0.0 |
SkyWalking | 分布式链路追踪 | 9.7.0 |
Prometheus | metrics 收集 | 2.45.0 |
Grafana | 监控可视化 | 10.4.2 |
Lombok | 简化 Java 代码 | 1.18.30 |
Fastjson2 | JSON 处理 | 2.0.47 |
Knife4j | Swagger3 增强 | 4.4.0 |
1.3 整体架构设计
我们将构建一个包含以下服务的微服务系统:
二、基础环境搭建
2.1 安装与配置 Nacos
Nacos 是 Spring Cloud Alibaba 生态中的核心组件,提供服务注册发现和配置管理功能。我们使用 Docker 快速部署:
# 拉取镜像
docker pull nacos/nacos-server:v2.3.2
# 启动容器
docker run -d \
--name nacos \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
nacos/nacos-server:v2.3.2
访问 http://localhost:8848/nacos ,默认用户名密码均为 nacos,登录成功则表示 Nacos 安装成功。
2.2 安装与配置 Sentinel Dashboard
Sentinel 用于实现服务熔断与限流功能:
# 拉取镜像
docker pull bladex/sentinel-dashboard:1.8.7
# 启动容器
docker run -d \
--name sentinel \
-p 8080:8080 \
bladex/sentinel-dashboard:1.8.7
访问 http://localhost:8080 ,默认用户名密码均为 sentinel。
2.3 安装 SkyWalking
SkyWalking 用于分布式链路追踪和性能分析:
# 拉取OAP服务镜像
docker pull apache/skywalking-oap-server:9.7.0
# 拉取UI镜像
docker pull apache/skywalking-ui:9.7.0
# 启动OAP服务
docker run -d \
--name skywalking-oap \
-p 11800:11800 \
-p 12800:12800 \
-e SW_STORAGE=h2 \
apache/skywalking-oap-server:9.7.0
# 启动UI
docker run -d \
--name skywalking-ui \
-p 8081:8080 \
-e SW_OAP_ADDRESS=http://skywalking-oap:12800 \
--link skywalking-oap \
apache/skywalking-ui:9.7.0
访问 http://localhost:8081 即可打开 SkyWalking 控制台。
2.4 安装 Prometheus 和 Grafana
# 拉取Prometheus镜像
docker pull prom/prometheus:v2.45.0
# 创建配置文件目录
mkdir -p /tmp/prometheus
# 创建配置文件
cat > /tmp/prometheus/prometheus.yml << EOF
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'skywalking'
static_configs:
- targets: ['skywalking-oap:1234']
EOF
# 启动Prometheus
docker run -d \
--name prometheus \
-p 9090:9090 \
-v /tmp/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
--link skywalking-oap \
prom/prometheus:v2.45.0
# 拉取Grafana镜像
docker pull grafana/grafana:10.4.2
# 启动Grafana
docker run -d \
--name grafana \
-p 3000:3000 \
--link prometheus \
grafana/grafana:10.4.2
访问 http://localhost:3000 ,默认用户名密码为 admin/admin。
三、构建基础微服务框架
3.1 创建父工程
首先创建一个 Maven 父工程,统一管理依赖版本:
<?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.ken.microservice</groupId>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Spring Cloud Alibaba 实战示例</name>
<description>Spring Cloud Alibaba 微服务从搭建到监控全链路示例</description>
<!-- 子模块 -->
<modules>
<module>common</module>
<module>user-service</module>
<module>order-service</module>
<module>product-service</module>
<module>gateway-service</module>
</modules>
<!-- 版本管理 -->
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<!-- Spring 版本 -->
<spring.boot.version>3.2.6</spring.boot.version>
<spring.cloud.version>2023.0.2</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version>
<!-- 其他组件版本 -->
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<seata.version>2.0.0</seata.version>
<lombok.version>1.18.30</lombok.version>
<fastjson2.version>2.0.47</fastjson2.version>
<knife4j.version>4.4.0</knife4j.version>
<mysql.version>8.0.36</mysql.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-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Knife4j (Swagger3) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
3.2 创建公共模块
创建 common 模块,存放公共实体类、工具类等:
<?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">
<parent>
<groupId>com.ken.microservice</groupId>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<name>公共模块</name>
<description>微服务公共组件</description>
<dependencies>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- Spring 工具类 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
<!-- Google Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<!-- Swagger 注解 -->
<dependency>
<groupId>io.swagger.v3.oas</groupId>
<artifactId>swagger-models</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
创建公共响应类:
package com.ken.microservice.common.result;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.util.ObjectUtils;
/**
* 统一响应结果
*
* @param <T> 响应数据类型
*/
@Data
@Schema(description = "统一响应结果")
public class Result<T> {
@Schema(description = "状态码", example = "200")
private int code;
@Schema(description = "消息", example = "操作成功")
private String msg;
@Schema(description = "响应数据")
private T data;
@JSONField(serialize = false)
private boolean success;
/**
* 私有构造方法,防止直接实例化
*/
private Result() {
}
/**
* 成功响应
*
* @param <T> 数据类型
* @return 成功响应结果
*/
public static <T> Result<T> success() {
return success(null);
}
/**
* 成功响应
*
* @param data 响应数据
* @param <T> 数据类型
* @return 成功响应结果
*/
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMsg(ResultCode.SUCCESS.getMsg());
result.setData(data);
result.setSuccess(true);
return result;
}
/**
* 失败响应
*
* @param code 错误码
* @param msg 错误消息
* @param <T> 数据类型
* @return 失败响应结果
*/
public static <T> Result<T> fail(int code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
result.setData(null);
result.setSuccess(false);
return result;
}
/**
* 失败响应
*
* @param resultCode 错误码枚举
* @param <T> 数据类型
* @return 失败响应结果
*/
public static <T> Result<T> fail(ResultCode resultCode) {
return fail(resultCode.getCode(), resultCode.getMsg());
}
/**
* 失败响应
*
* @param resultCode 错误码枚举
* @param msg 错误消息
* @param <T> 数据类型
* @return 失败响应结果
*/
public static <T> Result<T> fail(ResultCode resultCode, String msg) {
return fail(resultCode.getCode(), msg);
}
/**
* 转为JSON字符串
*
* @return JSON字符串
*/
@Override
public String toString() {
return JSONObject.toJSONString(this);
}
}
创建响应状态码枚举:
package com.ken.microservice.common.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 响应状态码枚举
*/
@Getter
@AllArgsConstructor
@Schema(description = "响应状态码枚举")
public enum ResultCode {
/**
* 成功
*/
SUCCESS(200, "操作成功"),
/**
* 服务器内部错误
*/
INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
/**
* 请求参数错误
*/
PARAM_ERROR(400, "请求参数错误"),
/**
* 未授权
*/
UNAUTHORIZED(401, "未授权"),
/**
* 资源不存在
*/
RESOURCE_NOT_FOUND(404, "资源不存在"),
/**
* 服务调用失败
*/
SERVICE_CALL_FAILED(503, "服务调用失败");
/**
* 状态码
*/
private final int code;
/**
* 状态描述
*/
private final String msg;
}
四、用户服务实现
4.1 创建用户服务模块
<?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">
<parent>
<groupId>com.ken.microservice</groupId>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<name>用户服务</name>
<description>用户管理微服务</description>
<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.ken.microservice</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Knife4j (Swagger3) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- Seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 配置文件
创建 bootstrap.yml 配置文件:
spring:
application:
name: user-service
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
config:
server-addr: localhost:8848
file-extension: yaml
namespace: public
group: DEFAULT_GROUP
创建 application-dev.yml 配置文件:
server:
port: 8082
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ken.microservice.user.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
feign:
sentinel:
enabled: true
sentinel:
transport:
dashboard: localhost:8080
port: 8719
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
server-addr: localhost:8848
namespace:
group: SEATA_GROUP
application: seata-server
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace:
group: SEATA_GROUP
logging:
level:
com.ken.microservice.user: debug
# SkyWalking 配置
skywalking:
agent:
service_name: ${spring.application.name}
collector:
backend_service: localhost:11800
# Knife4j 配置
knife4j:
enable: true
openapi:
title: 用户服务API
description: 用户服务接口文档
version: 1.0.0
group:
default:
api-rule: package
api-rule-resources:
- com.ken.microservice.user.controller
4.3 数据库设计
创建用户数据库和表:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE user_db;
-- 创建用户表
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 插入测试数据
INSERT INTO `user` (`username`, `password`, `nickname`, `phone`, `email`, `status`)
VALUES
('zhangsan', '123456', '张三', '13800138000', 'zhangsan@example.com', 1),
('lisi', '123456', '李四', '13900139000', 'lisi@example.com', 1);
4.4 实体类
package com.ken.microservice.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
@TableName("user")
@Schema(description = "用户实体")
public class User {
@TableId(type = IdType.AUTO)
@Schema(description = "用户ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "zhangsan")
private String username;
@Schema(description = "密码", example = "123456")
private String password;
@Schema(description = "昵称", example = "张三")
private String nickname;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "邮箱", example = "zhangsan@example.com")
private String email;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
4.5 Mapper 接口
package com.ken.microservice.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ken.microservice.user.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
4.6 Service 层
package com.ken.microservice.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;
/**
* 用户服务接口
*/
public interface UserService extends IService<User> {
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
Result<User> getUserById(Long id);
/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户信息
*/
Result<User> getUserByUsername(String username);
/**
* 创建用户
*
* @param user 用户信息
* @return 创建结果
*/
Result<Long> createUser(User user);
/**
* 更新用户
*
* @param user 用户信息
* @return 更新结果
*/
Result<Boolean> updateUser(User user);
/**
* 删除用户
*
* @param id 用户ID
* @return 删除结果
*/
Result<Boolean> deleteUser(Long id);
}
package com.ken.microservice.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.user.entity.User;
import com.ken.microservice.user.mapper.UserMapper;
import com.ken.microservice.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* 用户服务实现类
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public Result<User> getUserById(Long id) {
log.info("查询用户信息,用户ID:{}", id);
if (id == null || id <= 0) {
log.warn("查询用户信息失败,用户ID不合法:{}", id);
return Result.fail(ResultCode.PARAM_ERROR, "用户ID不合法");
}
User user = baseMapper.selectById(id);
if (user == null) {
log.warn("查询用户信息失败,用户不存在,用户ID:{}", id);
return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");
}
log.info("查询用户信息成功,用户ID:{}", id);
return Result.success(user);
}
@Override
public Result<User> getUserByUsername(String username) {
log.info("查询用户信息,用户名:{}", username);
if (!StringUtils.hasText(username)) {
log.warn("查询用户信息失败,用户名为空");
return Result.fail(ResultCode.PARAM_ERROR, "用户名不能为空");
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
User user = baseMapper.selectOne(queryWrapper);
if (user == null) {
log.warn("查询用户信息失败,用户不存在,用户名:{}", username);
return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");
}
log.info("查询用户信息成功,用户名:{}", username);
return Result.success(user);
}
@Override
public Result<Long> createUser(User user) {
log.info("创建用户,用户信息:{}", user);
if (user == null) {
log.warn("创建用户失败,用户信息为空");
return Result.fail(ResultCode.PARAM_ERROR, "用户信息不能为空");
}
if (!StringUtils.hasText(user.getUsername())) {
log.warn("创建用户失败,用户名为空");
return Result.fail(ResultCode.PARAM_ERROR, "用户名不能为空");
}
if (!StringUtils.hasText(user.getPassword())) {
log.warn("创建用户失败,密码为空");
return Result.fail(ResultCode.PARAM_ERROR, "密码不能为空");
}
// 检查用户名是否已存在
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, user.getUsername());
long count = baseMapper.selectCount(queryWrapper);
if (count > 0) {
log.warn("创建用户失败,用户名已存在:{}", user.getUsername());
return Result.fail(ResultCode.PARAM_ERROR, "用户名已存在");
}
int rows = baseMapper.insert(user);
if (rows <= 0) {
log.error("创建用户失败,数据库操作失败");
return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "创建用户失败");
}
log.info("创建用户成功,用户ID:{}", user.getId());
return Result.success(user.getId());
}
@Override
public Result<Boolean> updateUser(User user) {
log.info("更新用户,用户信息:{}", user);
if (user == null || user.getId() == null) {
log.warn("更新用户失败,用户ID为空");
return Result.fail(ResultCode.PARAM_ERROR, "用户ID不能为空");
}
// 检查用户是否存在
User existingUser = baseMapper.selectById(user.getId());
if (existingUser == null) {
log.warn("更新用户失败,用户不存在,用户ID:{}", user.getId());
return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");
}
int rows = baseMapper.updateById(user);
if (rows <= 0) {
log.error("更新用户失败,数据库操作失败,用户ID:{}", user.getId());
return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "更新用户失败");
}
log.info("更新用户成功,用户ID:{}", user.getId());
return Result.success(true);
}
@Override
public Result<Boolean> deleteUser(Long id) {
log.info("删除用户,用户ID:{}", id);
if (id == null || id <= 0) {
log.warn("删除用户失败,用户ID不合法:{}", id);
return Result.fail(ResultCode.PARAM_ERROR, "用户ID不合法");
}
// 检查用户是否存在
User user = baseMapper.selectById(id);
if (user == null) {
log.warn("删除用户失败,用户不存在,用户ID:{}", id);
return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");
}
int rows = baseMapper.deleteById(id);
if (rows <= 0) {
log.error("删除用户失败,数据库操作失败,用户ID:{}", id);
return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "删除用户失败");
}
log.info("删除用户成功,用户ID:{}", id);
return Result.success(true);
}
}
4.7 Controller 层
package com.ken.microservice.user.controller;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;
import com.ken.microservice.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户CRUD接口")
public class UserController {
private final UserService userService;
@Operation(summary = "根据ID查询用户", description = "通过用户ID获取用户详细信息")
@GetMapping("/{id}")
public Result<User> getUserById(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable Long id) {
return userService.getUserById(id);
}
@Operation(summary = "根据用户名查询用户", description = "通过用户名获取用户详细信息")
@GetMapping("/username/{username}")
public Result<User> getUserByUsername(
@Parameter(description = "用户名", required = true, example = "zhangsan")
@PathVariable String username) {
return userService.getUserByUsername(username);
}
@Operation(summary = "创建用户", description = "新增用户信息")
@PostMapping
public Result<Long> createUser(
@Parameter(description = "用户信息", required = true)
@RequestBody User user) {
return userService.createUser(user);
}
@Operation(summary = "更新用户", description = "修改用户信息")
@PutMapping
public Result<Boolean> updateUser(
@Parameter(description = "用户信息", required = true)
@RequestBody User user) {
return userService.updateUser(user);
}
@Operation(summary = "删除用户", description = "根据ID删除用户")
@DeleteMapping("/{id}")
public Result<Boolean> deleteUser(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable Long id) {
return userService.deleteUser(id);
}
}
4.8 启动类
package com.ken.microservice.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 用户服务启动类
*/
@SpringBootApplication(scanBasePackages = "com.ken.microservice")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.ken.microservice")
@MapperScan("com.ken.microservice.user.mapper")
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
五、商品服务实现
商品服务的实现与用户服务类似,这里重点展示核心代码:
5.1 数据库设计
-- 创建数据库
CREATE DATABASE IF NOT EXISTS product_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE product_db;
-- 创建商品表
CREATE TABLE IF NOT EXISTS `product` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '商品价格',
`stock` int NOT NULL DEFAULT 0 COMMENT '商品库存',
`description` varchar(500) DEFAULT NULL COMMENT '商品描述',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0-下架,1-上架',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';
-- 插入测试数据
INSERT INTO `product` (`name`, `price`, `stock`, `description`, `status`)
VALUES
('iPhone 15', 7999.00, 100, '苹果手机iPhone 15', 1),
('华为Mate 60', 6999.00, 50, '华为手机Mate 60', 1),
('小米14', 4999.00, 200, '小米手机14', 1);
5.2 核心业务代码
实体类:
package com.ken.microservice.product.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 商品实体类
*/
@Data
@TableName("product")
@Schema(description = "商品实体")
public class Product {
@TableId(type = IdType.AUTO)
@Schema(description = "商品ID", example = "1")
private Long id;
@Schema(description = "商品名称", example = "iPhone 15")
private String name;
@Schema(description = "商品价格", example = "7999.00")
private BigDecimal price;
@Schema(description = "商品库存", example = "100")
private Integer stock;
@Schema(description = "商品描述", example = "苹果手机iPhone 15")
private String description;
@Schema(description = "状态:0-下架,1-上架", example = "1")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
服务实现类中扣减库存的方法:
/**
* 扣减商品库存
*
* @param productId 商品ID
* @param quantity 扣减数量
* @return 扣减结果
*/
@Override
public Result<Boolean> decreaseStock(Long productId, Integer quantity) {
log.info("扣减商品库存,商品ID:{},扣减数量:{}", productId, quantity);
if (productId == null || productId <= 0) {
log.warn("扣减商品库存失败,商品ID不合法:{}", productId);
return Result.fail(ResultCode.PARAM_ERROR, "商品ID不合法");
}
if (quantity == null || quantity <= 0) {
log.warn("扣减商品库存失败,扣减数量不合法:{}", quantity);
return Result.fail(ResultCode.PARAM_ERROR, "扣减数量不合法");
}
// 查询商品信息
Product product = baseMapper.selectById(productId);
if (product == null) {
log.warn("扣减商品库存失败,商品不存在,商品ID:{}", productId);
return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "商品不存在");
}
// 检查库存是否充足
if (product.getStock() < quantity) {
log.warn("扣减商品库存失败,库存不足,商品ID:{},当前库存:{},需要数量:{}",
productId, product.getStock(), quantity);
return Result.fail(ResultCode.PARAM_ERROR, "商品库存不足");
}
// 扣减库存
Product updateProduct = new Product();
updateProduct.setId(productId);
updateProduct.setStock(product.getStock() - quantity);
int rows = baseMapper.updateById(updateProduct);
if (rows <= 0) {
log.error("扣减商品库存失败,数据库操作失败,商品ID:{}", productId);
return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "扣减库存失败");
}
log.info("扣减商品库存成功,商品ID:{},扣减数量:{},剩余库存:{}",
productId, quantity, updateProduct.getStock());
return Result.success(true);
}
六、订单服务实现
订单服务需要调用用户服务和商品服务,这里重点展示服务调用和分布式事务相关代码:
6.1 数据库设计
-- 创建数据库
CREATE DATABASE IF NOT EXISTS order_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE order_db;
-- 创建订单表
CREATE TABLE IF NOT EXISTS `order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` varchar(50) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已取消,3-已完成',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';
-- 创建订单项表
CREATE TABLE IF NOT EXISTS `order_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单项ID',
`order_id` bigint NOT NULL COMMENT '订单ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`product_name` varchar(100) NOT NULL COMMENT '商品名称',
`product_price` decimal(10,2) NOT NULL COMMENT '商品单价',
`quantity` int NOT NULL COMMENT '购买数量',
`total_price` decimal(10,2) NOT NULL COMMENT '商品总价',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单项表';
-- 创建undo_log表(Seata分布式事务需要)
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AT transaction mode undo table';
6.2 Feign 客户端
创建用户服务 Feign 客户端:
package com.ken.microservice.order.feign;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 用户服务Feign客户端
*/
@FeignClient(name = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/api/v1/users/{id}")
Result<User> getUserById(@PathVariable("id") Long id);
}
创建商品服务 Feign 客户端:
package com.ken.microservice.order.feign;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.product.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 商品服务Feign客户端
*/
@FeignClient(name = "product-service", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
/**
* 根据ID查询商品
*
* @param id 商品ID
* @return 商品信息
*/
@GetMapping("/api/v1/products/{id}")
Result<Product> getProductById(@PathVariable("id") Long id);
/**
* 扣减商品库存
*
* @param productId 商品ID
* @param quantity 扣减数量
* @return 扣减结果
*/
@PostMapping("/api/v1/products/decrease-stock")
Result<Boolean> decreaseStock(
@RequestParam("productId") Long productId,
@RequestParam("quantity") Integer quantity);
}
创建 Feign 客户端降级类:
package com.ken.microservice.order.feign;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.user.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 用户服务Feign客户端降级类
*/
@Slf4j
@Component
public class UserFeignClientFallback implements UserFeignClient {
@Override
public Result<User> getUserById(Long id) {
log.error("调用用户服务失败,用户ID:{},执行降级处理", id);
return Result.fail(ResultCode.SERVICE_CALL_FAILED, "调用用户服务失败,请稍后重试");
}
}
6.3 订单服务核心代码
订单实体类:
package com.ken.microservice.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
*/
@Data
@TableName("order")
@Schema(description = "订单实体")
public class Order {
@TableId(type = IdType.AUTO)
@Schema(description = "订单ID", example = "1")
private Long id;
@Schema(description = "订单编号", example = "202306010001")
private String orderNo;
@Schema(description = "用户ID", example = "1")
private Long userId;
@Schema(description = "订单总金额", example = "7999.00")
private BigDecimal totalAmount;
@Schema(description = "订单状态:0-待支付,1-已支付,2-已取消,3-已完成", example = "0")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
订单项实体
package com.ken.microservice.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单项实体类
*/
@Data
@TableName("order_item")
@Schema(description = "订单项实体")
public class OrderItem {
@TableId(type = IdType.AUTO)
@Schema(description = "订单项ID", example = "1")
private Long id;
@Schema(description = "订单ID", example = "1")
private Long orderId;
@Schema(description = "商品ID", example = "1")
private Long productId;
@Schema(description = "商品名称", example = "iPhone 15")
private String productName;
@Schema(description = "商品单价", example = "7999.00")
private BigDecimal productPrice;
@Schema(description = "购买数量", example = "1")
private Integer quantity;
@Schema(description = "商品总价", example = "7999.00")
private BigDecimal totalPrice;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
创建订单请求 DTO:
package com.ken.microservice.order.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 创建订单请求DTO
*/
@Data
@Schema(description = "创建订单请求DTO")
public class CreateOrderDTO {
@Schema(description = "用户ID", required = true, example = "1")
private Long userId;
@Schema(description = "订单项列表", required = true)
private List<OrderItemDTO> orderItems;
/**
* 校验请求参数
*
* @return 校验结果
*/
public String validate() {
if (userId == null || userId <= 0) {
return "用户ID不合法";
}
if (CollectionUtils.isEmpty(orderItems)) {
return "订单项不能为空";
}
for (OrderItemDTO item : orderItems) {
if (item.getProductId() == null || item.getProductId() <= 0) {
return "商品ID不合法";
}
if (item.getQuantity() == null || item.getQuantity() <= 0) {
return "购买数量不合法";
}
}
return null;
}
}
/**
* 订单项DTO
*/
@Data
@Schema(description = "订单项DTO")
class OrderItemDTO {
@Schema(description = "商品ID", required = true, example = "1")
private Long productId;
@Schema(description = "购买数量", required = true, example = "1")
private Integer quantity;
}
订单服务实现类(包含分布式事务):
package com.ken.microservice.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.order.dto.CreateOrderDTO;
import com.ken.microservice.order.entity.Order;
import com.ken.microservice.order.entity.OrderItem;
import com.ken.microservice.order.feign.ProductFeignClient;
import com.ken.microservice.order.feign.UserFeignClient;
import com.ken.microservice.order.mapper.OrderItemMapper;
import com.ken.microservice.order.mapper.OrderMapper;
import com.ken.microservice.order.service.OrderService;
import com.ken.microservice.product.entity.Product;
import com.ken.microservice.user.entity.User;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 订单服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
private final OrderMapper orderMapper;
private final OrderItemMapper orderItemMapper;
private final UserFeignClient userFeignClient;
private final ProductFeignClient productFeignClient;
/**
* 创建订单
* 使用Seata的全局事务注解保证分布式事务一致性
*/
@Override
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
public Result<String> createOrder(CreateOrderDTO createOrderDTO) {
log.info("开始创建订单,请求参数:{}", createOrderDTO);
// 校验请求参数
String validateResult = createOrderDTO.validate();
if (validateResult != null) {
log.warn("创建订单失败,参数校验失败:{}", validateResult);
return Result.fail(ResultCode.PARAM_ERROR, validateResult);
}
Long userId = createOrderDTO.getUserId();
// 1. 查询用户信息
Result<User> userResult = userFeignClient.getUserById(userId);
if (!userResult.isSuccess()) {
log.error("创建订单失败,查询用户信息失败:{}", userResult.getMsg());
return Result.fail(ResultCode.SERVICE_CALL_FAILED, "查询用户信息失败:" + userResult.getMsg());
}
// 2. 查询商品信息并计算总金额
List<OrderItem> orderItems = new ArrayList<>();
BigDecimal totalAmount = BigDecimal.ZERO;
for (CreateOrderDTO.OrderItemDTO itemDTO : createOrderDTO.getOrderItems()) {
Long productId = itemDTO.getProductId();
Integer quantity = itemDTO.getQuantity();
// 查询商品信息
Result<Product> productResult = productFeignClient.getProductById(productId);
if (!productResult.isSuccess()) {
log.error("创建订单失败,查询商品信息失败,商品ID:{},错误信息:{}",
productId, productResult.getMsg());
throw new RuntimeException("查询商品信息失败:" + productResult.getMsg());
}
Product product = productResult.getData();
// 检查商品状态
if (product.getStatus() != 1) {
log.error("创建订单失败,商品未上架,商品ID:{},商品名称:{}",
productId, product.getName());
throw new RuntimeException("商品[" + product.getName() + "]未上架");
}
// 检查库存
if (product.getStock() < quantity) {
log.error("创建订单失败,商品库存不足,商品ID:{},商品名称:{},当前库存:{},需要数量:{}",
productId, product.getName(), product.getStock(), quantity);
throw new RuntimeException("商品[" + product.getName() + "]库存不足");
}
// 扣减库存
Result<Boolean> decreaseResult = productFeignClient.decreaseStock(productId, quantity);
if (!decreaseResult.isSuccess()) {
log.error("创建订单失败,扣减商品库存失败,商品ID:{},错误信息:{}",
productId, decreaseResult.getMsg());
throw new RuntimeException("扣减商品[" + product.getName() + "]库存失败:" + decreaseResult.getMsg());
}
// 计算商品总价
BigDecimal itemTotalPrice = product.getPrice().multiply(new BigDecimal(quantity));
totalAmount = totalAmount.add(itemTotalPrice);
// 创建订单项
OrderItem orderItem = new OrderItem();
orderItem.setProductId(productId);
orderItem.setProductName(product.getName());
orderItem.setProductPrice(product.getPrice());
orderItem.setQuantity(quantity);
orderItem.setTotalPrice(itemTotalPrice);
orderItem.setCreateTime(LocalDateTime.now());
orderItem.setUpdateTime(LocalDateTime.now());
orderItems.add(orderItem);
}
// 3. 创建订单
Order order = new Order();
// 生成订单编号:年月日时分秒 + 3位随机数
String orderNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ (100 + new Random().nextInt(900));
order.setOrderNo(orderNo);
order.setUserId(userId);
order.setTotalAmount(totalAmount);
order.setStatus(0); // 待支付
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
int orderRows = orderMapper.insert(order);
if (orderRows <= 0) {
log.error("创建订单失败,保存订单信息到数据库失败");
throw new RuntimeException("创建订单失败");
}
// 4. 保存订单项
for (OrderItem item : orderItems) {
item.setOrderId(order.getId());
orderItemMapper.insert(item);
}
log.info("创建订单成功,订单编号:{},订单ID:{}", orderNo, order.getId());
return Result.success(orderNo);
}
// 其他方法省略...
}
七、网关服务实现
Spring Cloud Gateway 作为微服务网关,负责路由转发、负载均衡、认证授权等功能。
7.1 网关模块配置
<?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">
<parent>
<groupId>com.ken.microservice</groupId>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway-service</artifactId>
<name>网关服务</name>
<description>微服务网关</description>
<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.ken.microservice</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Spring Cloud Gateway Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- Knife4j Gateway -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-gateway-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
7.2 网关配置文件
spring:
application:
name: gateway-service
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
config:
server-addr: localhost:8848
file-extension: yaml
namespace: public
group: DEFAULT_GROUP
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/v1/users/**filters:
- name: Sentinel
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/v1/products/**
filters:
- name: Sentinel
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/v1/orders/**
filters:
- name: Sentinel
# Swagger路由
- id: swagger-route
uri: lb://user-service
predicates:
- Path=/swagger-ui.html,/swagger-ui/**,/v3/api-docs/**,/doc.html/**
filters:
- name: RewritePath
args:
regexp: /doc.html/(?<segment>.*)
replacement: /$\{segment}
server:
port: 8080
sentinel:
transport:
dashboard: localhost:8080
port: 8720
scg:
fallback:
mode: response
response-status: 429
response-body: '{"code":429,"msg":"请求过于频繁,请稍后再试","data":null,"success":false}'
# Knife4j 配置
knife4j:
gateway:
enabled: true
routes:
- name: 用户服务
url: /user-service/v3/api-docs
service-name: user-service
- name: 商品服务
url: /product-service/v3/api-docs
service-name: product-service
- name: 订单服务
url: /order-service/v3/api-docs
service-name: order-service
logging:
level:
org.springframework.cloud.gateway: debug
com.alibaba.cloud.sentinel: debug
7.3 网关启动类
package com.ken.microservice.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 网关服务启动类
*/
@SpringBootApplication(scanBasePackages = "com.ken.microservice")
@EnableDiscoveryClient
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
八、服务监控与可观测性
8.1 集成 SkyWalking 实现链路追踪
在各服务的启动参数中添加 SkyWalking agent 配置:
-javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=user-service
-Dskywalking.collector.backend_service=localhost:11800
通过 SkyWalking 控制台,我们可以查看完整的服务调用链路:
8.2 集成 Prometheus 和 Grafana 实现指标监控
在各服务中添加 Prometheus 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
添加配置:
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
export:
prometheus:
enabled: true
endpoint:
health:
show-details: always
prometheus:
enabled: true
在 Prometheus 配置文件中添加各服务的监控端点:
scrape_configs:
- job_name: 'user-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['user-service:8082']
- job_name: 'product-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['product-service:8083']
- job_name: 'order-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['order-service:8084']
- job_name: 'gateway-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['gateway-service:8080']
在 Grafana 中导入 Spring Boot 应用的监控面板(ID:12856),可以直观地查看各服务的 JVM、请求量、响应时间等指标。
8.3 集成 Sentinel 实现熔断与限流
在 Sentinel 控制台中,可以为各服务设置限流规则:
为用户服务的查询接口设置 QPS 限流为 10:
- 资源名:GET:/api/v1/users/{id}
- 限流类型:QPS
- 阈值:10
为订单服务的创建订单接口设置线程数限流为 5:
- 资源名:POST:/api/v1/orders
- 限流类型:线程数
- 阈值:5
为商品服务的扣减库存接口设置熔断规则:
- 资源名:POST:/api/v1/products/decrease-stock
- 熔断策略:异常比例
- 阈值:0.5
- 熔断时长:10 秒
- 最小请求数:5
九、微服务高级特性
9.1 配置中心动态配置
在 Nacos 控制台中创建配置:
- 数据 ID:user-service-dev.yaml
- 配置内容:
user:
config:
version: 1.0.0
enableCache: true
cacheExpireSeconds: 300
在用户服务中添加配置类:
package com.ken.microservice.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* 用户服务配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "user.config")
@RefreshScope // 支持配置动态刷新
public class UserConfig {
private String version;
private boolean enableCache;
private Integer cacheExpireSeconds;
}
9.2 服务优雅降级与容错
使用 Resilience4j 实现服务容错:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
在服务调用处添加熔断注解:
@CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
public Result<Product> getProduct(Long productId) {
return productFeignClient.getProductById(productId);
}
public Result<Product> getProductFallback(Long productId, Exception e) {
log.error("调用商品服务失败,执行降级处理,商品ID:{}", productId, e);
return Result.fail(ResultCode.SERVICE_CALL_FAILED, "获取商品信息失败,请稍后重试");
}
十、总结与展望
本文详细介绍了基于 Spring Cloud Alibaba 的微服务从搭建到监控的全链路实践,涵盖了服务注册发现、配置中心、服务调用、熔断限流、网关路由、分布式事务、链路追踪、指标监控等核心功能。通过实际代码示例,展示了如何构建一个完整的微服务体系。
附录:常见问题与解决方案
Nacos 服务注册失败
- 检查 Nacos 服务器是否正常运行
- 检查服务配置的 Nacos 地址是否正确
- 检查网络是否通畅,防火墙是否开放对应端口
Feign 调用失败
- 检查服务是否已注册到注册中心
- 检查 Feign 接口定义是否与服务端接口一致
- 检查熔断降级配置是否正确
分布式事务不生效
- 检查 Seata 服务器是否正常运行
- 检查各服务是否正确配置了 Seata
- 检查数据库是否创建了 undo_log 表
- 确保 @GlobalTransactional 注解正确添加到事务发起方
SkyWalking 链路追踪不显示
- 检查 SkyWalking agent 是否正确配置
- 检查服务启动参数是否正确
- 检查 SkyWalking OAP 服务是否正常运行
Sentinel 规则不生效
- 检查 Sentinel Dashboard 是否正常运行
- 检查服务是否正确配置了 Sentinel
- 确保服务已被调用过,Sentinel 已收集到服务信息