目录

Spring-Cloud-Alibaba-实战从-0-到-1-构建可监控的微服务体系

Spring Cloud Alibaba 实战:从 0 到 1 构建可监控的微服务体系

[https://csdnimg.cn/release/blogv2/dist/pc/img/activeVector.png VibeCoding·九月创作之星挑战赛 10w+人浏览 1k人参与

https://csdnimg.cn/release/blogv2/dist/pc/img/arrowright-line-White.png]( )

引言:微服务架构的现状与挑战

在当今互联网技术快速迭代的背景下,单体应用架构已难以满足业务快速发展的需求。微服务架构通过将应用拆分为一系列小型、自治的服务,实现了技术栈多样化、团队独立开发和部署等优势,但同时也带来了服务治理、分布式协调、链路追踪等新的挑战。

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-PlusORM 框架3.5.6
Seata分布式事务2.0.0
SkyWalking分布式链路追踪9.7.0
Prometheusmetrics 收集2.45.0
Grafana监控可视化10.4.2
Lombok简化 Java 代码1.18.30
Fastjson2JSON 处理2.0.47
Knife4jSwagger3 增强4.4.0

1.3 整体架构设计

我们将构建一个包含以下服务的微服务系统:

https://i-blog.csdnimg.cn/direct/8b51ed7d34fb4c248f5e1fc4ce5ffc84.png

二、基础环境搭建

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

访问   ,默认用户名密码均为 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

访问   ,默认用户名密码均为 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

访问   即可打开 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

访问   ,默认用户名密码为 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 控制台,我们可以查看完整的服务调用链路:

https://i-blog.csdnimg.cn/direct/a1134b5505d7412eb0d6bcdb25099557.png

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 控制台中,可以为各服务设置限流规则:

  1. 为用户服务的查询接口设置 QPS 限流为 10:

    • 资源名:GET:/api/v1/users/{id}
    • 限流类型:QPS
    • 阈值:10
  2. 为订单服务的创建订单接口设置线程数限流为 5:

    • 资源名:POST:/api/v1/orders
    • 限流类型:线程数
    • 阈值:5
  3. 为商品服务的扣减库存接口设置熔断规则:

    • 资源名: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 的微服务从搭建到监控的全链路实践,涵盖了服务注册发现、配置中心、服务调用、熔断限流、网关路由、分布式事务、链路追踪、指标监控等核心功能。通过实际代码示例,展示了如何构建一个完整的微服务体系。

附录:常见问题与解决方案

  1. Nacos 服务注册失败

    • 检查 Nacos 服务器是否正常运行
    • 检查服务配置的 Nacos 地址是否正确
    • 检查网络是否通畅,防火墙是否开放对应端口
  2. Feign 调用失败

    • 检查服务是否已注册到注册中心
    • 检查 Feign 接口定义是否与服务端接口一致
    • 检查熔断降级配置是否正确
  3. 分布式事务不生效

    • 检查 Seata 服务器是否正常运行
    • 检查各服务是否正确配置了 Seata
    • 检查数据库是否创建了 undo_log 表
    • 确保 @GlobalTransactional 注解正确添加到事务发起方
  4. SkyWalking 链路追踪不显示

    • 检查 SkyWalking agent 是否正确配置
    • 检查服务启动参数是否正确
    • 检查 SkyWalking OAP 服务是否正常运行
  5. Sentinel 规则不生效

    • 检查 Sentinel Dashboard 是否正常运行
    • 检查服务是否正确配置了 Sentinel
    • 确保服务已被调用过,Sentinel 已收集到服务信息