5.6服务拆分实战

分类: 微服务架构理论与实践

服务拆分实战

本节将通过实战演示,将单体商城应用拆分为微服务。我们将从创建父项目开始,逐步拆分出用户服务、商品服务和订单服务。

本节将学习:创建父项目、拆分用户服务、拆分商品服务、拆分订单服务,以及服务间通信问题。

步骤1:创建父项目

父项目结构

首先创建一个 Maven 父项目,用于管理所有微服务的依赖版本。

项目结构:

mall-microservices/
├── pom.xml                    # 父 POM
├── user-service/              # 用户服务模块
├── product-service/           # 商品服务模块
├── order-service/             # 订单服务模块
├── payment-service/           # 支付服务模块
├── inventory-service/         # 库存服务模块
└── gateway-service/           # 网关服务模块

父 POM 配置

文件路径: mall-microservices/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mall</groupId> <artifactId>mall-microservices</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <name>Mall Microservices Parent</name> <description>微服务商城父项目</description> <modules> <module>user-service</module> <module>product-service</module> <module>order-service</module> <module>payment-service</module> <module>inventory-service</module> <module>gateway-service</module> </modules> <properties> <java.version>21</java.version> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Spring Boot 版本 --> <spring-boot.version>3.2.0</spring-boot.version> <!-- Spring Cloud 版本 --> <spring-cloud.version>2023.0.0</spring-cloud.version> <!-- Spring Cloud Alibaba 版本 --> <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version> <!-- 其他依赖版本 --> <mybatis-plus.version>3.5.5</mybatis-plus.version> <mysql.version>8.0.33</mysql.version> <lombok.version>1.18.30</lombok.version> <hutool.version>5.8.23</hutool.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> <!-- MySQL --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql.version}</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!-- Hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.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> </plugin> </plugins> </pluginManagement> </build> </project>

步骤2:拆分用户服务

创建用户服务模块

文件路径: mall-microservices/user-service/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.mall</groupId> <artifactId>mall-microservices</artifactId> <version>1.0.0</version> </parent> <artifactId>user-service</artifactId> <name>User Service</name> <description>用户服务</description> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Spring Boot Actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Spring Security (用于密码加密) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

用户服务配置文件

文件路径: mall-microservices/user-service/src/main/resources/application.yml

server: port: 8081 spring: application: name: user-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root sql: init: mode: never # MyBatis Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 # Actuator 配置 management: endpoints: web: exposure: include: health,info,metrics

用户实体类

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/entity/User.java

package com.mall.userservice.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String username; private String email; private String phone; private String password; private String nickname; private String avatar; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }

用户 Mapper

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/mapper/UserMapper.java

package com.mall.userservice.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mall.userservice.entity.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<User> { }

用户 Service

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/service/UserService.java

package com.mall.userservice.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mall.userservice.entity.User; public interface UserService extends IService<User> { User register(User user); User login(String username, String password); User getByUsername(String username); User getByEmail(String email); }

用户 Service 实现

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/service/impl/UserServiceImpl.java

package com.mall.userservice.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.userservice.entity.User; import com.mall.userservice.mapper.UserMapper; import com.mall.userservice.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Autowired private PasswordEncoder passwordEncoder; @Override @Transactional(rollbackFor = Exception.class) public User register(User user) { // 检查用户名是否已存在 User existUser = baseMapper.selectOne( new LambdaQueryWrapper<User>() .eq(User::getUsername, user.getUsername()) ); if (existUser != null) { throw new RuntimeException("Username already exists"); } // 检查邮箱是否已存在 existUser = baseMapper.selectOne( new LambdaQueryWrapper<User>() .eq(User::getEmail, user.getEmail()) ); if (existUser != null) { throw new RuntimeException("Email already exists"); } // 加密密码 user.setPassword(passwordEncoder.encode(user.getPassword())); // 设置默认状态 user.setStatus(1); // 保存用户 save(user); return user; } @Override public User login(String username, String password) { User user = getByUsername(username); if (user == null) { throw new RuntimeException("User not found"); } if (!passwordEncoder.matches(password, user.getPassword())) { throw new RuntimeException("Invalid password"); } if (user.getStatus() != 1) { throw new RuntimeException("User is disabled"); } // 清除密码 user.setPassword(null); return user; } @Override public User getByUsername(String username) { return baseMapper.selectOne( new LambdaQueryWrapper<User>() .eq(User::getUsername, username) ); } @Override public User getByEmail(String email) { return baseMapper.selectOne( new LambdaQueryWrapper<User>() .eq(User::getEmail, email) ); } }

用户 Controller

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/controller/UserController.java

package com.mall.userservice.controller; import com.mall.userservice.entity.User; import com.mall.userservice.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping("/register") public Result<User> register(@RequestBody User user) { User registered = userService.register(user); return Result.success("User registered successfully", registered); } @PostMapping("/login") public Result<User> login(@RequestParam String username, @RequestParam String password) { User user = userService.login(username, password); return Result.success("Login successful", user); } @GetMapping("/{id}") public Result<User> getUser(@PathVariable Long id) { User user = userService.getById(id); if (user == null) { return Result.error("User not found"); } user.setPassword(null); return Result.success(user); } @GetMapping("/username/{username}") public Result<User> getUserByUsername(@PathVariable String username) { User user = userService.getByUsername(username); if (user == null) { return Result.error("User not found"); } user.setPassword(null); return Result.success(user); } }

统一响应结果类

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/common/Result.java

package com.mall.userservice.common; import lombok.Data; @Data public class Result<T> { private Integer code; private String message; private T data; private Long timestamp; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); result.setTimestamp(System.currentTimeMillis()); return result; } public static <T> Result<T> success(String message, T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage(message); result.setData(data); result.setTimestamp(System.currentTimeMillis()); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); result.setTimestamp(System.currentTimeMillis()); return result; } }

密码加密配置

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/config/SecurityConfig.java

package com.mall.userservice.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth .anyRequest().permitAll() ); return http.build(); } }

启动类

文件路径: mall-microservices/user-service/src/main/java/com/mall/userservice/UserServiceApplication.java

package com.mall.userservice; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.mall.userservice.mapper") public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }

数据库初始化

文件路径: mall-microservices/user-service/src/main/resources/db/user_db.sql

CREATE DATABASE IF NOT EXISTS user_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE user_db; CREATE TABLE `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名', `email` VARCHAR(100) UNIQUE NOT NULL COMMENT '邮箱', `phone` VARCHAR(20) UNIQUE COMMENT '手机号', `password` VARCHAR(255) NOT NULL COMMENT '密码(加密)', `nickname` VARCHAR(50) COMMENT '昵称', `avatar` VARCHAR(255) COMMENT '头像URL', `status` TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX `idx_username` (`username`), INDEX `idx_email` (`email`), INDEX `idx_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

步骤3:拆分商品服务

创建商品服务模块

文件路径: mall-microservices/product-service/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.mall</groupId> <artifactId>mall-microservices</artifactId> <version>1.0.0</version> </parent> <artifactId>product-service</artifactId> <name>Product Service</name> <description>商品服务</description> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Spring Boot Actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

商品服务配置文件

文件路径: mall-microservices/product-service/src/main/resources/application.yml

server: port: 8082 spring: application: name: product-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/product_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root # MyBatis Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto # Actuator 配置 management: endpoints: web: exposure: include: health,info,metrics

商品实体类

文件路径: mall-microservices/product-service/src/main/java/com/mall/productservice/entity/Product.java

package com.mall.productservice.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @TableName("product") public class Product { @TableId(type = IdType.AUTO) private Long id; private String productName; private String productCode; private Long categoryId; private String description; private BigDecimal price; private String imageUrl; private String images; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }

商品 Controller

文件路径: mall-microservices/product-service/src/main/java/com/mall/productservice/controller/ProductController.java

package com.mall.productservice.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.mall.productservice.entity.Product; import com.mall.productservice.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; @GetMapping public Result<Page<Product>> getProducts( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Long categoryId, @RequestParam(required = false) String keyword) { Page<Product> productPage = productService.getProducts(page, size, categoryId, keyword); return Result.success(productPage); } @GetMapping("/{id}") public Result<Product> getProduct(@PathVariable Long id) { Product product = productService.getById(id); if (product == null) { return Result.error("Product not found"); } return Result.success(product); } @PostMapping public Result<Product> createProduct(@RequestBody Product product) { productService.save(product); return Result.success("Product created successfully", product); } }

数据库初始化

文件路径: mall-microservices/product-service/src/main/resources/db/product_db.sql

CREATE DATABASE IF NOT EXISTS product_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE product_db; CREATE TABLE `category` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `parent_id` BIGINT DEFAULT 0 COMMENT '父分类ID', `category_name` VARCHAR(100) NOT NULL COMMENT '分类名称', `category_code` VARCHAR(50) UNIQUE NOT NULL COMMENT '分类代码', `level` TINYINT DEFAULT 1 COMMENT '分类层级', `sort_order` INT DEFAULT 0 COMMENT '排序', `status` TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX `idx_parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'; CREATE TABLE `product` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `product_name` VARCHAR(200) NOT NULL COMMENT '商品名称', `product_code` VARCHAR(50) UNIQUE NOT NULL COMMENT '商品编码', `category_id` BIGINT NOT NULL COMMENT '分类ID', `description` TEXT COMMENT '商品描述', `price` DECIMAL(10,2) NOT NULL COMMENT '商品价格', `image_url` VARCHAR(500) COMMENT '商品主图', `images` TEXT COMMENT '商品图片列表(JSON)', `status` TINYINT DEFAULT 1 COMMENT '状态:1-上架,0-下架', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX `idx_category_id` (`category_id`), INDEX `idx_product_code` (`product_code`), INDEX `idx_status` (`status`), FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

步骤4:拆分订单服务

创建订单服务模块

文件路径: mall-microservices/order-service/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.mall</groupId> <artifactId>mall-microservices</artifactId> <version>1.0.0</version> </parent> <artifactId>order-service</artifactId> <name>Order Service</name> <description>订单服务</description> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Spring Boot Actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

订单服务配置文件

文件路径: mall-microservices/order-service/src/main/resources/application.yml

server: port: 8083 spring: application: name: order-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root # MyBatis Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto # Actuator 配置 management: endpoints: web: exposure: include: health,info,metrics # 服务调用配置(暂时使用硬编码,后续会使用服务发现) service: user: url: http://localhost:8081 product: url: http://localhost:8082

订单实体类

文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/entity/Order.java

package com.mall.orderservice.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @TableName("order") public class Order { @TableId(type = IdType.AUTO) private Long id; private String orderNo; private Long userId; private BigDecimal totalAmount; private BigDecimal payAmount; private Integer status; private Integer paymentStatus; private LocalDateTime paymentTime; private LocalDateTime deliveryTime; private LocalDateTime completeTime; private LocalDateTime cancelTime; private String cancelReason; private String remark; private LocalDateTime createTime; private LocalDateTime updateTime; }

订单 Service(包含服务间调用)

文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/service/impl/OrderServiceImpl.java

package com.mall.orderservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.orderservice.entity.Order; import com.mall.orderservice.mapper.OrderMapper; import com.mall.orderservice.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.util.UUID; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private RestTemplate restTemplate; @Value("${service.user.url}") private String userServiceUrl; @Value("${service.product.url}") private String productServiceUrl; @Override @Transactional(rollbackFor = Exception.class) public Order createOrder(Order order) { // 1. 验证用户是否存在(调用用户服务) String userUrl = userServiceUrl + "/api/users/" + order.getUserId(); try { restTemplate.getForObject(userUrl, Object.class); } catch (Exception e) { throw new RuntimeException("User not found: " + order.getUserId()); } // 2. 生成订单号 order.setOrderNo(generateOrderNo()); // 3. 设置订单状态 order.setStatus(0); // 待支付 order.setPaymentStatus(0); // 未支付 // 4. 保存订单 save(order); return order; } private String generateOrderNo() { return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }

RestTemplate 配置

文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/config/RestTemplateConfig.java

package com.mall.orderservice.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }

数据库初始化

文件路径: mall-microservices/order-service/src/main/resources/db/order_db.sql

CREATE DATABASE IF NOT EXISTS order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE order_db; CREATE TABLE `order` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号', `user_id` BIGINT NOT NULL COMMENT '用户ID', `total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额', `pay_amount` DECIMAL(10,2) NOT NULL COMMENT '实付金额', `status` TINYINT DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消', `payment_status` TINYINT DEFAULT 0 COMMENT '支付状态:0-未支付,1-已支付,2-已退款', `payment_time` DATETIME COMMENT '支付时间', `delivery_time` DATETIME COMMENT '发货时间', `complete_time` DATETIME COMMENT '完成时间', `cancel_time` DATETIME COMMENT '取消时间', `cancel_reason` VARCHAR(255) COMMENT '取消原因', `remark` VARCHAR(500) COMMENT '订单备注', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX `idx_order_no` (`order_no`), INDEX `idx_user_id` (`user_id`), INDEX `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; CREATE TABLE `order_item` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_id` BIGINT NOT NULL COMMENT '订单ID', `product_id` BIGINT NOT NULL COMMENT '商品ID', `product_name` VARCHAR(200) NOT NULL COMMENT '商品名称', `product_image` VARCHAR(500) COMMENT '商品图片', `product_price` DECIMAL(10,2) NOT NULL COMMENT '商品单价', `quantity` INT NOT NULL COMMENT '购买数量', `subtotal` DECIMAL(10,2) NOT NULL COMMENT '小计金额', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX `idx_order_id` (`order_id`), FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';

服务间通信问题

当前问题

当前服务间通信的问题:

  1. 硬编码 URL:订单服务中硬编码了用户服务和商品服务的 URL
  2. 无法负载均衡:如果服务有多个实例,无法自动负载均衡
  3. 服务发现缺失:无法自动发现服务实例
  4. 故障处理缺失:服务不可用时没有降级处理

当前实现示例

订单服务调用用户服务(硬编码方式):

// 当前实现:硬编码 URL @Value("${service.user.url}") private String userServiceUrl; public void validateUser(Long userId) { String url = userServiceUrl + "/api/users/" + userId; restTemplate.getForObject(url, User.class); }

后续改进方向

后续章节将解决以下问题:

  1. 第7章:使用 Nacos 实现服务注册与发现
  2. 第8章:使用 LoadBalancer 实现负载均衡
  3. 第9章:使用 OpenFeign 简化服务调用
  4. 第11章:使用 Sentinel 实现熔断降级

测试验证

启动服务

启动顺序:

  1. 启动 MySQL 数据库
  2. 执行数据库初始化脚本
  3. 启动用户服务(端口 8081)
  4. 启动商品服务(端口 8082)
  5. 启动订单服务(端口 8083)

测试用户服务

# 注册用户 curl -X POST http://localhost:8081/api/users/register \ -H "Content-Type: application/json" \ -d '{ "username": "testuser", "email": "test@example.com", "password": "123456" }' # 查询用户 curl http://localhost:8081/api/users/1

测试商品服务

# 查询商品列表 curl http://localhost:8082/api/products?page=1&size=10 # 查询商品详情 curl http://localhost:8082/api/products/1

测试订单服务

# 创建订单 curl -X POST http://localhost:8083/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "totalAmount": 100.00, "payAmount": 100.00 }'

官方资源

本节小结

在本节中,我们完成了服务拆分实战:

第一个是创建父项目。 创建了 Maven 父项目,统一管理所有微服务的依赖版本。

第二个是拆分用户服务。 创建了独立的用户服务,包含完整的代码实现和数据库设计。

第三个是拆分商品服务。 创建了独立的商品服务,包含商品管理和分类功能。

第四个是拆分订单服务。 创建了订单服务,并实现了服务间调用(当前使用硬编码 URL)。

第五个是服务间通信问题。 识别了当前服务间通信的问题,为后续章节的改进打下基础。

这就是服务拆分实战。通过实际拆分,我们创建了三个独立的微服务,每个服务都有独立的数据库和完整的代码实现。在下一章,我们将学习 Spring Cloud Alibaba,并使用 Nacos 解决服务发现的问题。