# SpringCloud **Repository Path**: yunlin909/spring-cloud ## Basic Information - **Project Name**: SpringCloud - **Description**: SpringCloud案例库 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2026-06-12 - **Last Updated**: 2026-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Cloud 项目综合实践 ## 以电商系统为例 ---![img.png](frontend/src/static/img.png) ## 项目架构一览 ``` ┌─────────────────┐ │ Vue 3 前端 │ :3000 (Vite 开发服务器) │ Element Plus │ └───────┬─────────┘ │ HTTP (Vite Proxy → :9090) ▼ ┌─────────────────┐ │ Gateway 网关 │ :9090 │ Spring Cloud │ │ Gateway │ │ + Knife4j 聚合 │ └───────┬─────────┘ │ Nacos 服务发现 + LoadBalancer ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ user-service │ │ goods-service│ │ order-service│ │ :9091 │ │ :9093 │ │ :9092 │ │ 用户 CRUD │ │ 商品/分类CRUD│ │ 订单 CRUD │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌──────────┴──────────┐ │ OpenFeign 远程调用 │ │ UserClient │ │ GoodsClient │ │ + LoadBalancer │ │ (随机/轮询策略) │ └─────────────────────┘ │ │ │ └───────────────┴───────────────┘ │ ▼ ┌─────────────────┐ │ MySQL (Nacos) │ │ ecommerce 库 │ │ localhost:3306 │ └─────────────────┘ ``` --- ## 前置任务:数据库设计 ### 电商业务场景 一个小型电商平台,包含用户、商品分类、商品、订单四大核心领域。支持用户注册登录、商品多级分类浏览、下单购买、订单管理等功能。 ### 任务描述 根据电商业务场景,设计并创建以下四张核心数据库表,为后续微服务开发奠定数据基础。 ### 数据库信息 | 配置项 | 值 | |---|---| | 数据库名 | `ecommerce` | | 字符集 | `utf8mb4` | | SQL 脚本 | `sql/电商系统数据库设计.sql` | | MySQL 地址 | `localhost:3306` | ### 表结构设计 #### tb_user — 用户表 | 字段 | 类型 | 说明 | |---|---|---| | id | bigint(20) PK | 主键,自增 | | username | varchar(50) | 用户名 | | password | varchar(255) | 密码(MD5 加密) | | phone | varchar(20) | 手机号 | | email | varchar(100) | 邮箱 | | avatar | varchar(255) | 头像 URL | | status | tinyint(1) | 状态:0-禁用,1-启用 | | create_time | datetime | 创建时间 | | update_time | datetime | 更新时间 | #### tb_category — 商品分类表(支持多级树形结构) | 字段 | 类型 | 说明 | |---|---|---| | id | bigint(20) PK | 主键,自增 | | name | varchar(50) | 分类名称 | | parent_id | bigint(20) | 父分类 ID(0=顶级分类) | | sort_order | int(11) | 排序序号 | | status | tinyint(1) | 状态 | | create_time | datetime | 创建时间 | | update_time | datetime | 更新时间 | #### tb_goods — 商品表 | 字段 | 类型 | 说明 | |---|---|---| | id | bigint(20) PK | 主键,自增 | | name | varchar(100) | 商品名称 | | category_id | bigint(20) | 所属分类 ID | | price | decimal(10,2) | 商品价格 | | stock | int(11) | 库存数量 | | sold_count | int(11) | 已售数量 | | description | text | 商品描述 | | image | varchar(255) | 商品图片 URL | | status | tinyint(1) | 状态:0-下架,1-上架 | | create_time | datetime | 创建时间 | | update_time | datetime | 更新时间 | #### tb_order — 订单表 | 字段 | 类型 | 说明 | |---|---|---| | id | bigint(20) PK | 主键,自增 | | order_no | varchar(64) | 订单编号(UUID 生成) | | user_id | bigint(20) | 下单用户 ID | | goods_id | bigint(20) | 商品 ID | | goods_name | varchar(100) | 商品名称(冗余存储,防止商品修改后历史订单显示异常) | | goods_price | decimal(10,2) | 下单时商品单价 | | quantity | int(11) | 购买数量 | | total_price | decimal(10,2) | 订单总价 | | status | tinyint(1) | 订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消,5-已退款 | | remark | varchar(255) | 备注 | | create_time | datetime | 创建时间 | | update_time | datetime | 更新时间 | ### 测试数据 | 表 | 数据量 | 说明 | |---|---|---| | tb_user | 8 条 | 含管理员、普通用户、VIP 用户 | | tb_category | 21 条 | 三层分类树(电子、服装、食品等) | | tb_goods | 20 条 | 各分类下 2-3 个商品 | | tb_order | 15 条 | 覆盖所有订单状态 | ### 验收标准 - [ ] 执行 `sql/电商系统数据库设计.sql` 脚本后,四张表全部创建成功 - [ ] 表中包含预设的测试数据 - [ ] 通过 `SELECT COUNT(*)` 验证各表数据量与预期一致 --- ## 任务一:单模块后端开发 ### 电商业务场景 作为开发人员,先以传统单体开发方式实现各业务模块的 CRUD 功能,跑通基础业务流程后再进行微服务拆分。 ### 任务描述 使用 **SpringBoot + MyBatis-Plus + MySQL** 技术栈,实现各业务模块的独立后端开发,包括实体类、Mapper、Service、Controller 层的完整 CRUD 功能。 ### 实现步骤 #### 1.1 创建 user-service 模块 **目录结构**(位于 `user-service/src/main/java/cn/hniu/userservice/`): ``` ├── UserServiceApplication.java # 启动类 ├── entity/ │ └── User.java # 用户实体,映射 tb_user 表 ├── mapper/ │ └── UserMapper.java # 继承 BaseMapper ├── service/ │ ├── UserService.java # 继承 IService │ └── impl/ │ └── UserServiceImpl.java # 继承 ServiceImpl └── controller/ └── userController.java # REST API 控制器 ``` **API 接口清单**: | 方法 | 路径 | 说明 | 电商场景 | |---|---|---|---| | GET | `/user` | 查询用户列表 | 后台管理-用户列表 | | GET | `/user/{id}` | 根据 ID 查询用户 | 查看用户详情 | | POST | `/user` | 新增用户 | 用户注册 | | PUT | `/user` | 修改用户信息 | 个人中心-编辑资料 | | DELETE | `/user/{id}` | 删除用户 | 后台管理-注销用户 | | GET | `/user/port` | 返回当前服务端口 | 负载均衡验证用 | **关键代码**: ```java // User 实体 — 使用 MyBatis-Plus 注解 @Data @TableName("tb_user") public class User { @TableId(type = IdType.AUTO) private Long id; private String username; private String password; private String phone; private String email; private String avatar; private Integer status; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; } // Mapper — 一行代码搞定 @Mapper public interface UserMapper extends BaseMapper {} // Controller — 标准 RESTful CRUD @RestController @RequestMapping("/user") public class userController { @Autowired private UserService userService; @GetMapping public List list() { return userService.list(); } @GetMapping("/{id}") public User getById(@PathVariable Long id) { return userService.getById(id); } @PostMapping public boolean save(@RequestBody User user) { return userService.save(user); } @PutMapping public boolean update(@RequestBody User user) { return userService.updateById(user); } @DeleteMapping("/{id}") public boolean delete(@PathVariable Long id) { return userService.removeById(id); } // 负载均衡验证:返回当前服务端口 @Value("${server.port}") private String port; @GetMapping("/port") public String getPort() { return "user-service 端口: " + port; } } ``` #### 1.2 创建 goods-service 模块 **目录结构**(位于 `goods-service/src/main/java/cn/hniu/goodsservice/`): ``` ├── GoodsServiceApplication.java ├── entity/ │ ├── Goods.java # 商品实体,映射 tb_goods 表 │ └── Category.java # 分类实体,映射 tb_category 表,含 children 字段(非表字段,用于树形结构) ├── mapper/ │ ├── GoodsMapper.java # 继承 BaseMapper │ └── CategoryMapper.java # 继承 BaseMapper ├── service/ │ ├── GoodsService.java │ ├── CategoryService.java │ └── impl/ │ ├── GoodsServiceImpl.java │ └── CategoryServiceImpl.java # 实现 getCategoryTree() 递归构建分类树 └── controller/ ├── GoodsController.java └── CategoryController.java ``` **API 接口清单**: | 方法 | 路径 | 说明 | 电商场景 | |---|---|---|---| | GET | `/goods/page` | 分页查询商品 | 首页-商品列表,支持分页和分类筛选 | | GET | `/goods/{id}` | 根据 ID 查询商品 | 商品详情页 | | POST | `/goods` | 新增商品 | 后台管理-上架新商品 | | PUT | `/goods` | 修改商品信息 | 后台管理-编辑商品 | | DELETE | `/goods/{id}` | 删除商品 | 后台管理-下架商品 | | PUT | `/goods/{id}/stock/reduce` | 扣减库存(quantity 参数) | **下单时库存扣减,分布式事务的关键接口** | | GET | `/goods/port` | 返回当前服务端口 | 负载均衡验证用 | | GET | `/category/tree` | 获取分类树 | **首页-多级分类导航** | | GET | `/category` | 查询分类列表 | 后台管理-分类列表 | | POST | `/category` | 新增分类 | 后台管理-添加分类 | | PUT | `/category` | 修改分类 | 后台管理-编辑分类 | | DELETE | `/category/{id}` | 删除分类 | 后台管理-删除分类 | **分类树构建核心代码**: ```java // CategoryServiceImpl @Override public List getCategoryTree() { // 1. 查询所有分类 List allCategories = categoryMapper.selectList(null); // 2. 筛选顶级分类(parentId = 0) List rootCategories = allCategories.stream() .filter(c -> c.getParentId() == 0) .collect(Collectors.toList()); // 3. 递归设置 children for (Category root : rootCategories) { setChildren(root, allCategories); } return rootCategories; } private void setChildren(Category parent, List allCategories) { List children = allCategories.stream() .filter(c -> c.getParentId().equals(parent.getId())) .collect(Collectors.toList()); for (Category child : children) { setChildren(child, allCategories); } parent.setChildren(children); } ``` #### 1.3 创建 order-service 模块 **目录结构**(位于 `order-service/src/main/java/cn/hniu/orderservice/`): ``` ├── OrderServiceApplication.java ├── entity/ │ └── Order.java # 订单实体,映射 tb_order 表 ├── mapper/ │ └── OrderMapper.java ├── service/ │ ├── OrderService.java │ └── impl/ │ └── OrderServiceImpl.java # 订单创建逻辑(含 Feign 调用、库存扣减) ├── controller/ │ └── OrderController.java ├── feign/ │ ├── UserClient.java # Feign 调用 user-service │ └── GoodsClient.java # Feign 调用 goods-service └── config/ └── LoadBalancerConfig.java # 自定义负载均衡策略 ``` **API 接口清单**: | 方法 | 路径 | 说明 | 电商场景 | |---|---|---|---| | GET | `/order/page` | 分页查询订单 | 我的订单-列表 | | GET | `/order/{id}` | 根据 ID 查询订单 | 订单详情 | | GET | `/order/user/{userId}` | 查询用户的所有订单(Feign 调用 user-service 获取用户信息) | 我的订单-按用户筛选 | | POST | `/order` | 创建订单(**核心:Feign 调用 goods-service 扣减库存**) | 下单结算 | | PUT | `/order/{id}/pay` | 支付订单 | 我的订单-去支付 | | PUT | `/order/{id}/cancel` | 取消订单 | 我的订单-取消 | | DELETE | `/order/{id}` | 删除订单 | 我的订单-删除 | | GET | `/order/port` | 返回当前服务端口 | 负载均衡验证用 | **订单创建核心逻辑**(后续分布式事务的重点改造对象): ```java // OrderServiceImpl.create() 核心流程: @Override @Transactional public Order create(Order order) { // 1. 生成订单编号 order.setOrderNo(UUID.randomUUID().toString().replace("-", "")); // 2. 设置订单状态为"待支付" order.setStatus(0); // 3. 通过 Feign 查询商品信息(获取商品名称、价格) Goods goods = goodsClient.getById(order.getGoodsId()).getData(); order.setGoodsName(goods.getName()); order.setGoodsPrice(goods.getPrice()); // 4. 计算总价 order.setTotalPrice(goods.getPrice().multiply(new BigDecimal(order.getQuantity()))); // 5. 通过 Feign 扣减库存 goodsClient.reduceStock(order.getGoodsId(), order.getQuantity()); // 6. 保存订单 orderMapper.insert(order); return order; } ``` #### 1.4 创建 gateway-service 模块 Gateway 网关作为整个电商系统的统一入口,所有前端请求都通过 Gateway 转发到后端微服务。 **目录结构**(位于 `gateway-service/src/main/java/cn/hniu/gatewayservice/`): ``` ├── GatewayServiceApplication.java # 启动类,@EnableDiscoveryClient └── config/ └── CorsConfig.java # CORS 跨域配置 ``` **路由规则**(`application.yml`): ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** - id: goods-service uri: lb://goods-service predicates: - Path=/goods/**,/category/** - id: order-service uri: lb://order-service predicates: - Path=/order/** ``` ### 各模块 pom.xml 依赖说明 | 模块 | 特有依赖 | |---|---| | 所有模块(父 POM 继承) | `spring-boot-starter`, `lombok`, `mysql-connector-j`, `devtools`, `test` | | user-service | `spring-boot-starter-web`, `nacos-discovery`, `mybatis-plus`, `knife4j` | | goods-service | `spring-boot-starter-web`, `nacos-discovery`, `mybatis-plus`, `knife4j` | | order-service | `spring-boot-starter-web`, `nacos-discovery`, `mybatis-plus`, `knife4j`, **`openfeign`**, **`loadbalancer`** | | gateway-service | **`spring-cloud-starter-gateway`**, `nacos-discovery`, **`loadbalancer`**, `knife4j-gateway` | ### 验收标准 - [ ] 各服务独立启动无报错,正确注册到 Nacos - [ ] 访问 `http://localhost:9090/user` 能返回用户列表 JSON - [ ] 访问 `http://localhost:9090/goods/page?page=1&size=5` 能分页返回商品 - [ ] 访问 `http://localhost:9090/category/tree` 能返回三级分类树 JSON - [ ] 访问 `http://localhost:9090/order/page` 能返回订单列表 - [ ] POST `/order` 创建订单后,对应商品的 stock 字段自动减 1 --- ## 任务二:改造成微服务 ### 电商业务场景 将商品服务拆分为独立的 `goods-service` 模块,并通过 Nacos 注册中心和 Gateway 统一网关实现微服务架构。 ### 任务描述 使用 **Spring Cloud Alibaba** 微服务体系,将单体模块拆分为独立的微服务,并通过 **Nacos** 实现服务注册与发现,通过 **Spring Cloud Gateway** 实现统一网关入口。 ### 服务拓扑 | 服务名 | 端口 | 职责 | Nacos 服务名 | |---|---|---|---| | `gateway-service` | 9090 | 统一网关,路由转发 + CORS 跨域处理 | `gateway-service` | | `user-service` | 9091 | 用户服务,提供用户 CRUD | `user-service` | | `goods-service` | 9093 | 商品服务,提供商品分类 & 商品 CRUD + 库存扣减 | `goods-service` | | `order-service` | 9092 | 订单服务,提供订单 CRUD,通过 Feign 调用 user/goods | `order-service` | ### 电商请求流转示例 用户下单的完整调用链: ``` 用户点击"立即购买" → 前端 POST /order → Gateway (:9090) → order-service (:9092) → Feign → goods-service (:9093) 查询商品信息 → Feign → goods-service (:9093) 扣减库存 → 保存订单到 MySQL → 返回订单号给前端 ``` ### 实现步骤 1. **创建 goods-service 模块** - 从父 POM 中抽离商品相关代码到独立模块 - `pom.xml` 引入 `spring-cloud-starter-alibaba-nacos-discovery` - `application.yml` 配置 Nacos 地址和服务名 2. **创建 gateway-service 模块** - `pom.xml` 引入 `spring-cloud-starter-gateway` + `nacos-discovery` + `loadbalancer` - 配置路由规则(见下方 YAML 示例) - 实现 CorsConfig 跨域配置(允许前端 `localhost:3000` 访问) 3. **配置路由规则** ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** - id: goods-service uri: lb://goods-service predicates: - Path=/goods/**,/category/** - id: order-service uri: lb://order-service predicates: - Path=/order/** ``` 4. **使用 OpenFeign 实现服务间调用**(详见任务六) 5. **使用 Spring Cloud LoadBalancer 实现客户端负载均衡**(详见任务五) ### 验收标准 - [ ] 启动 Nacos Server(`localhost:8848`),登录控制台 - [ ] 依次启动 goods-service、user-service、order-service、gateway-service - [ ] Nacos 控制台"服务管理→服务列表"中能看到 4 个服务均为"健康"状态 - [ ] 通过 Gateway 统一入口(`:9090`)可访问所有服务接口 - [ ] 创建订单成功,商品库存正确扣减 --- ## 任务三:整合 Knife4j 查看微服务 API ### 电商业务场景 前后端联调时,前端开发需要知道每个接口的请求参数、返回格式。Knife4j 提供在线 API 文档和调试功能,无需反复沟通。 ### 任务描述 在 **gateway-service** 中整合 **Knife4j**(Swagger 的增强 UI 方案),实现微服务 API 文档的集中聚合与可视化展示,方便开发调试时查看所有微服务接口。 ### 实现步骤 #### 3.1 子服务引入 Knife4j 依赖 在 `pom.xml`(父 POM 或各子模块)中添加: ```xml com.github.xiaoymin knife4j-openapi2-spring-boot-starter 4.3.0 ``` #### 3.2 子服务配置 Swagger + 添加注解 每个子服务 `application.yml` 中添加: ```yaml knife4j: enable: true setting: language: zh_cn springdoc: swagger-ui: path: /swagger-ui.html api-docs: path: /v3/api-docs group-configs: - group: 'default' paths-to-match: '/**' packages-to-scan: cn.hniu ``` 在每个 Controller 上添加注解(以订单服务为例): ```java @Api(tags = "订单管理") @RestController @RequestMapping("/order") public class OrderController { @ApiOperation("分页查询订单") @ApiImplicitParams({ @ApiImplicitParam(name = "page", value = "页码", defaultValue = "1"), @ApiImplicitParam(name = "size", value = "每页条数", defaultValue = "10") }) @GetMapping("/page") public Result> page(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { ... } @ApiOperation("创建订单(会扣减库存)") @PostMapping public Result create(@RequestBody Order order) { ... } @ApiOperation("支付订单(模拟暗箱支付)") @PutMapping("/{id}/pay") public Result pay(@ApiParam("订单ID") @PathVariable Long id) { ... } } ``` #### 3.3 Gateway 聚合配置 gateway-service 引入聚合依赖: ```xml com.github.xiaoymin knife4j-gateway-spring-boot-starter 4.3.0 ``` gateway-service `application.yml` 中添加(手动模式): ```yaml knife4j: gateway: enabled: true routes: - name: 用户服务 service-name: user-service url: /user/v2/api-docs context-path: /user - name: 商品服务 service-name: goods-service url: /goods/v2/api-docs context-path: /goods - name: 订单服务 service-name: order-service url: /order/v2/api-docs context-path: /order ``` ### 验收标准 - [ ] 访问 `http://localhost:9090/doc.html` 可看到"用户服务""商品服务""订单服务"三个分组 - [ ] 每个分组下列出该服务的所有 API 接口,含参数说明和返回示例 - [ ] 在 Knife4j UI 中直接点击"发送"可在线调试接口(如 POST `/order` 创建订单) - [ ] 每个接口都有中文说明和参数描述 --- ## 任务四:前端开发 ### 电商业务场景 普通用户在首页浏览商品、按分类筛选、加入购物车、下单结算;管理员在后台管理分类和商品。 ### 任务描述 使用 **Vue 3 + Element Plus + Vite + Axios** 开发电商系统前端页面,实现商品浏览、购物车、订单等核心业务流程。 ### 技术栈 | 技术 | 版本 | 用途 | |---|---|---| | Vue 3 | 3.x | 前端框架(Composition API) | | Element Plus | 2.x | UI 组件库 | | Vite | 4.x | 构建工具 + 开发服务器 (端口 3000) | | Vue Router | 4.x | 前端路由 | | Axios | 1.x | HTTP 请求库 | ### 前端目录结构 ``` frontend/src/ ├── main.js # 入口:挂载 ElementPlus、Router、Axios 全局配置 ├── App.vue # 根组件:顶部导航菜单(首页/分类管理/商品管理/购物车/我的订单) ├── api/ │ └── index.js # 统一 API 封装(userApi, categoryApi, goodsApi, orderApi) ├── router/ │ └── index.js # 路由配置 └── views/ ├── Home.vue # 首页:商品搜索 + 分类筛选 + 商品网格展示 + 加入购物车 ├── CategoryManage.vue# 分类管理:树形表格 + 增删改 ├── GoodsManage.vue # 商品管理:分页表格 + 增删改 ├── Cart.vue # 购物车:localStorage 存储 + 数量修改 + 下单结算 └── OrderList.vue # 我的订单:状态筛选(待支付/已支付/已取消)+ 支付/取消/删除 ``` ### 页面路由 | 路径 | 组件 | 电商功能 | |---|---|---| | `/` | Home.vue | **用户端首页**:搜索商品、按分类筛选、商品卡片展示、点击加入购物车 | | `/category` | CategoryManage.vue | **管理端**:分类树形展示、新增/编辑/删除分类 | | `/goods` | GoodsManage.vue | **管理端**:商品分页表格、上架/编辑/下架商品 | | `/cart` | Cart.vue | **用户端购物车**:展示已选商品、修改数量、删除、勾选结算下单 | | `/order` | OrderList.vue | **用户端我的订单**:按状态筛选、去支付、取消订单、删除订单 | ### Vite 代理配置 ```js // vite.config.js export default defineConfig({ server: { port: 3000, proxy: { '/user': 'http://localhost:9090', '/goods': 'http://localhost:9090', '/category': 'http://localhost:9090', '/order': 'http://localhost:9090' } } }) ``` ### API 封装示例 ```js // src/api/index.js — 统一管理所有后端接口 import axios from 'axios' // 用户 API export const userApi = { list: () => axios.get('/user'), getById: (id) => axios.get(`/user/${id}`), save: (data) => axios.post('/user', data), update: (data) => axios.put('/user', data), delete: (id) => axios.delete(`/user/${id}`) } // 商品 API export const goodsApi = { page: (params) => axios.get('/goods/page', { params }), getById: (id) => axios.get(`/goods/${id}`), save: (data) => axios.post('/goods', data), update: (data) => axios.put('/goods', data), delete: (id) => axios.delete(`/goods/${id}`) } // 分类 API export const categoryApi = { tree: () => axios.get('/category/tree'), list: () => axios.get('/category'), save: (data) => axios.post('/category', data), update: (data) => axios.put('/category', data), delete: (id) => axios.delete(`/category/${id}`) } // 订单 API export const orderApi = { page: (params) => axios.get('/order/page', { params }), getById: (id) => axios.get(`/order/${id}`), create: (data) => axios.post('/order', data), pay: (id) => axios.put(`/order/${id}/pay`), cancel: (id) => axios.put(`/order/${id}/cancel`), delete: (id) => axios.delete(`/order/${id}`) } ``` ### 关键电商流程 **用户下单完整流程**: ``` 1. 用户在 Home.vue 浏览商品,点击"加入购物车" → 商品信息存入 localStorage(购物车数据在本地存储) 2. 用户进入 Cart.vue 购物车页面 → 读取 localStorage 展示购物车列表 → 可修改数量、删除商品 3. 用户勾选商品,点击"结算下单" → 前端调用 POST /order { goodsId, quantity } → 后端 order-service 通过 Feign 调用 goods-service 扣减库存 → 返回订单号,前端提示"下单成功" → 跳转到 OrderList.vue 我的订单页 4. 用户在 OrderList.vue 点击"去支付" → 调用 PUT /order/{id}/pay(模拟支付,直接修改状态) → 订单状态从"待支付"变为"已支付" ``` ### 验收标准 - [ ] `npm run dev` 启动前端(端口 3000),页面正常渲染 - [ ] 首页正确显示商品列表和左侧分类树 - [ ] 点击分类树节点,商品列表按分类筛选 - [ ] 购物车:添加商品后 localStorage 有数据,刷新不丢失 - [ ] 下单:点击结算后订单创建成功,商品库存减 1 - [ ] 我的订单:能查看到刚创建的订单,点击支付/取消功能正常 - [ ] 分类管理和商品管理的增删改功能正常 --- ## 任务五:实现负载均衡 ### 电商业务场景 双十一期间,用户服务需启动多个实例来分担高并发流量。通过负载均衡将请求均匀分发到不同实例,提升系统吞吐量。 ### 任务描述 验证并展示微服务架构下的负载均衡能力,确保高并发场景下请求能被均匀分发到多个服务实例。 ### 实现步骤 #### 5.1 多实例启动 ```bash # 启动 user-service 实例1 (默认端口 9091) java -jar user-service-1.0.1-SNAPSHOT.jar # 启动 user-service 实例2 (指定端口 9095) java -jar user-service-1.0.1-SNAPSHOT.jar --server.port=9095 ``` #### 5.2 验证负载均衡 ```bash # 连续多次请求,观察端口号变化 curl http://localhost:9090/user/port # 返回: user-service 端口: 9091 curl http://localhost:9090/user/port # 返回: user-service 端口: 9095 curl http://localhost:9090/user/port # 返回: user-service 端口: 9091 ``` #### 5.3 自定义负载均衡策略 已在 `order-service/src/main/java/cn/hniu/orderservice/config/LoadBalancerConfig.java` 中实现: ```java @Configuration public class LoadBalancerConfig { // user-service 使用随机策略 @Bean @LoadBalancerClient(name = "user-service", configuration = RandomLoadBalancerConfig.class) public ServiceInstanceListSupplier userServiceInstanceSupplier(ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder() .withBlockingDiscoveryClient() .withCaching() .build(context); } // goods-service 使用轮询策略(默认) @Bean @LoadBalancerClient(name = "goods-service", configuration = RoundRobinLoadBalancerConfig.class) public ServiceInstanceListSupplier goodsServiceInstanceSupplier(ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder() .withBlockingDiscoveryClient() .withCaching() .build(context); } } ``` ### 验收标准 - [ ] 启动 2 个 user-service 实例(端口 9091 和 9095),Nacos 中可见 2 个实例 - [ ] 连续访问 `/user/port` 10 次,两个端口交替出现 - [ ] 通过 Gateway 入口(`:9090/user/port`)访问,同样能验证负载均衡 - [ ] 停掉一个实例后,请求全部路由到另一实例,业务不受影响 --- ## 任务六:声明式服务调用组件OpenFeign ### 电商业务场景 创建订单时,order-service 需要调用 user-service 获取用户信息(校验用户是否存在),调用 goods-service 获取商品信息和扣减库存。通过 OpenFeign 实现声明式的远程调用,代码简洁优雅。 ### 任务描述 深入掌握 **Spring Cloud OpenFeign** 声明式服务调用组件,实现微服务间的优雅远程调用,包括基础配置、超时重试、日志输出、拦截器等功能。 ### 当前项目已实现的 Feign 接口 **`order-service/src/main/java/cn/hniu/orderservice/feign/UserClient.java`**: ```java @FeignClient(name = "user-service") // 通过 Nacos 服务名发现 public interface UserClient { @GetMapping("/user/{id}") Result getUserById(@PathVariable("id") Long id); } ``` **`order-service/src/main/java/cn/hniu/orderservice/feign/GoodsClient.java`**: ```java @FeignClient(name = "goods-service") public interface GoodsClient { // 查询商品信息(用于创建订单时获取名称、价格) @GetMapping("/goods/{id}") Result getById(@PathVariable("id") Long id); // 扣减库存(分布式事务的关键节点) @PutMapping("/goods/{id}/stock/reduce") Result reduceStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity); } ``` ### 任务要求 1. **引入依赖 + 启用 Feign** ```java // order-service 启动类 @EnableFeignClients // 扫描 @FeignClient 接口 @EnableDiscoveryClient @SpringBootApplication public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } ``` 2. **配置 Feign 超时时间**(防止调用阻塞) ```yaml spring: cloud: openfeign: client: config: default: connectTimeout: 3000 # 连接超时 3 秒 readTimeout: 5000 # 读取超时 5 秒 loggerLevel: FULL # 日志级别 goods-service: connectTimeout: 5000 # 商品服务单独配置更长超时(涉及库存操作) readTimeout: 8000 ``` 3. **配置 Feign 重试机制** ```java @Bean public Retryer feignRetryer() { // 初始间隔 1 秒,最大间隔 2 秒,最多重试 3 次 return new Retryer.Default(1000, 2000, 3); } ``` 4. **配置日志输出**(调试时输出完整请求/响应) ```yaml logging: level: cn.hniu.orderservice.feign: DEBUG # Feign 接口包的日志级别 ``` 5. **编写请求拦截器**(统一传递认证 Token) ```java @Component public class FeignAuthInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 从当前请求上下文获取 Token 并传递到下游服务 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { String token = attributes.getRequest().getHeader("Authorization"); if (token != null) { template.header("Authorization", token); } } } } ``` 6. **集成 Nacos + LoadBalancer**:Feign 默认集成负载均衡,`@FeignClient(name = "user-service")` 会自动从 Nacos 获取实例列表并通过 LoadBalancer 选择实例。 ### 验收标准 - [ ] order-service 通过 Feign 成功调用 user-service 获取用户信息 - [ ] order-service 通过 Feign 成功调用 goods-service 扣减库存 - [ ] 创建订单接口(POST `/order`)执行后,商品库存正确减少 - [ ] 控制台 DEBUG 日志中可看到 Feign 请求的完整 URL、请求体、响应体 - [ ] 启动 2 个 goods-service 实例,创建订单时 Feign 调用轮流命中不同实例(负载均衡生效) --- ## 任务七:实现限流 ### 电商业务场景 秒杀活动期间,大量用户同时创建订单。为防止系统被瞬间流量冲垮,对下单接口进行限流(如每秒最多 100 个请求),超出的请求排队或拒绝。 ### 任务描述 使用 **Sentinel** 实现服务限流,保障系统在高并发下的稳定性。 ### 实现步骤 #### 7.1 添加依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` #### 7.2 配置 Sentinel ```yaml spring: cloud: sentinel: transport: dashboard: localhost:8080 # Sentinel Dashboard 地址 port: 8719 # 各服务与控制台通信端口 eager: true # 启动时立即注册到 Dashboard ``` #### 7.3 在 Gateway 层配置限流(推荐) ```yaml spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path=/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 # 每秒允许 10 个请求 redis-rate-limiter.burstCapacity: 20 # 突发容量 20 key-resolver: "#{@ipKeyResolver}" # 按 IP 限流 ``` #### 7.4 代码中使用 @SentinelResource ```java @RestController @RequestMapping("/order") public class OrderController { @SentinelResource( value = "createOrder", blockHandler = "createOrderBlockHandler" ) @PostMapping public Result create(@RequestBody Order order) { return Result.success(orderService.create(order)); } // 限流后的降级方法 public Result createOrderBlockHandler(Order order, BlockException e) { return Result.error(429, "当前下单人数过多,请稍后再试"); } } ``` #### 7.5 启动 Sentinel Dashboard ```bash # 下载 sentinel-dashboard.jar 后启动 java -Dserver.port=8080 -jar sentinel-dashboard-1.8.6.jar # 访问 http://localhost:8080,默认账号密码 sentinel/sentinel ``` ### 验收标准 - [ ] Sentinel Dashboard 中能看到所有微服务注册 - [ ] 在 Dashboard 中为 `/order` 设置 QPS=5 的限流规则 - [ ] 用 JMeter 或 curl 快速连续请求 10 次,第 6 次开始返回 429 "当前下单人数过多" - [ ] Dashboard 中"实时监控"能看到通过的 QPS 和拒绝的 QPS --- ## 任务八:服务容错组件Sentinel ### 电商业务场景 除了限流,电商系统还需要多种容错能力: - **熔断**:库存服务响应变慢时,快速熔断返回兜底数据(如"库存查询中,请稍后"),避免级联故障 - **热点限流**:某个爆款商品(如 iPhone)被抢购时,对此商品 ID 单独限流 - **系统保护**:当 CPU 或负载过高时自动限流,防止系统崩溃 ### 任务描述 深入掌握 **Sentinel** 服务容错组件,通过流量控制、熔断降级、热点参数限流和系统自适应保护等机制,全方位保障电商系统的稳定性与高可用性。 ### 电商容错场景 | 场景 | Sentinel 规则 | 电商效果 | |---|---|---| | 秒杀流量控制 | QPS 限流 `/order/create` = 100/秒 | 秒杀时排队等待,不超系统容量 | | 库存服务慢响应 | 慢调用比例熔断:RT>1s 比例>50% 时熔断 | 库存查询超时自动降级,返回兜底信息 | | 爆款商品限流 | 热点参数限流:特定 goodsId 额外限制 | iPhone 抢购限流,普通商品不受影响 | | 系统高负载保护 | 系统规则:CPU>80% 自动限流 | 保证核心下单链路可用 | ### 实现步骤 #### 8.1 流量控制(Flow Control) **QPS 限流 — 下单接口**: ``` 资源名: POST:/order 阈值类型: QPS QPS 阈值: 100 流控模式: 直接 流控效果: 快速失败 ``` → 超过 100 QPS 的请求直接返回 429 **线程数限流 — 库存操作**: ``` 资源名: /goods/{id}/stock/reduce 阈值类型: 线程数 线程数阈值: 20 ``` → 并发处理库存扣减的线程不超过 20 个 #### 8.2 熔断降级(Circuit Breaking) **慢调用比例 — 商品查询接口**: ``` 资源名: /goods/{id} 熔断策略: 慢调用比例 最大 RT: 1000ms 比例阈值: 50% 熔断时长: 10s 最小请求数: 5 统计时长: 1000ms ``` → 5 个请求中超过 50% 响应时间 > 1 秒,自动熔断 10 秒 **异常比例 — 创建订单接口**: ``` 资源名: createOrder 熔断策略: 异常比例 比例阈值: 20% # 20% 的请求抛异常则熔断 熔断时长: 30s 最小请求数: 10 ``` → 10 个请求中 20% 抛出异常(如库存不足),熔断 30 秒快速失败 #### 8.3 热点参数限流 **爆款商品限流示例**: ```java @SentinelResource( value = "getGoodsById", blockHandler = "getGoodsBlockHandler" ) @GetMapping("/{id}") public Result getById(@PathVariable("id") @SentinelResource(value = "goodsHotParam") Long id) { return goodsService.getById(id); } ``` 热点规则配置(在 Dashboard 或代码中): ``` 资源名: goodsHotParam 参数索引: 0(第 1 个参数) 单机阈值: 5000 QPS 参数例外项: - 参数值 1001(iPhone): 500 QPS ← 爆款商品单独限制 - 参数值 1002(华为手机): 1000 QPS ``` → 普通商品 5000 QPS,iPhone 只有 500 QPS #### 8.4 熔断降级代码示例 ```java @RestController @RequestMapping("/order") public class OrderController { @SentinelResource( value = "createOrder", blockHandler = "createOrderBlockHandler", // Sentinel 规则触发 fallback = "createOrderFallback" // 业务异常触发 ) @PostMapping("/create") public Result createOrder(@RequestBody OrderDTO orderDTO) { // 1. 查询商品信息(可能触发熔断) Goods goods = goodsClient.getById(orderDTO.getGoodsId()).getData(); // 2. 扣减库存 goodsClient.reduceStock(orderDTO.getGoodsId(), orderDTO.getQuantity()); // 3. 创建订单 Order order = orderService.create(orderDTO); return Result.success(order); } // 限流/熔断触发时调用 public Result createOrderBlockHandler(OrderDTO dto, BlockException e) { log.warn("下单被限流/熔断: {}", e.getMessage()); return Result.error(429, "当前下单人数较多,请稍后再试"); } // 业务异常时调用(如商品不存在、库存不足) public Result createOrderFallback(OrderDTO dto, Throwable e) { log.error("下单业务异常: {}", e.getMessage(), e); return Result.error(500, "下单失败:" + e.getMessage()); } } ``` #### 8.5 规则持久化到 Nacos 默认 Sentinel 规则存储在内存,服务重启后丢失。生产环境需持久化到 Nacos: ```yaml spring: cloud: sentinel: transport: dashboard: localhost:8080 port: 8719 datasource: # 流量控制规则 flow: nacos: server-addr: localhost:8848 data-id: ${spring.application.name}-flow-rules group-id: SENTINEL_GROUP rule-type: flow # 熔断降级规则 degrade: nacos: server-addr: localhost:8848 data-id: ${spring.application.name}-degrade-rules group-id: SENTINEL_GROUP rule-type: degrade ``` 在 Nacos 中创建配置 `order-service-flow-rules`: ```json [ { "resource": "createOrder", "grade": 1, "count": 100, "controlBehavior": 0, "strategy": 0 } ] ``` ### 验收标准 - [ ] 配置 QPS=5 的限流规则后,第 6 个请求返回 429 - [ ] 模拟慢调用(接口 sleep 2 秒),触发熔断后快速失败 - [ ] 为 goodsId=1001 配置热点限流 500 QPS,普通商品 5000 QPS,验证差异化限流 - [ ] Dashboard 中实时监控能看到 QPS、RT、线程数等指标 - [ ] Nacos 中配置规则后重启服务,规则自动加载生效 --- ## 任务九:消息驱动框架Spring Cloud Stream ### 电商业务场景 - **订单通知**:用户下单后,异步发送短信/邮件通知,不阻塞下单主流程 - **操作日志**:所有业务操作(下单、支付、取消)异步记录到日志服务 - **订单超时取消**:下单 30 分钟未支付,自动取消订单并释放库存 - **库存同步**:商品库存变化时,异步同步到搜索服务和推荐服务 ### 任务描述 使用 **Spring Cloud Stream** 消息驱动框架,通过统一的消息编程模型实现微服务间的异步解耦通信,支持 **RabbitMQ** 作为消息中间件。 ### 消息架构 ``` order-service (生产者) notification-service (消费者) │ │ │ 发布 "订单已创建" 事件 │ 消费事件,模拟发送短信通知 ├─────────────────────────────────────────►│ │ │ │ log-service (消费者) │ │ │ 发布 "订单已创建/已支付/已取消" 事件 │ 消费事件,记录操作日志 ├─────────────────────────────────────────►│ │ │ │ order-service (消费者,延迟消息) │ │ │ 发布 "订单超时检查" 延迟消息 │ 消费延迟消息,超时未支付则自动取消 └─────────────────────────────────────────►│ ``` ### 实现步骤 #### 9.1 环境搭建 ```bash # 安装 RabbitMQ(推荐 Docker 方式) docker run -d --name rabbitmq \ -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=admin \ rabbitmq:3-management # 访问管理控制台: http://localhost:15672 (admin/admin) ``` #### 9.2 添加依赖 ```xml com.alibaba.cloud spring-cloud-starter-stream-rabbit ``` #### 9.3 定义消息事件 ```java // 订单事件 — 统一的消息体 @Data @Builder public class OrderEvent implements Serializable { private Long orderId; // 订单 ID private String orderNo; // 订单编号 private Long userId; // 用户 ID private Long goodsId; // 商品 ID private String goodsName; // 商品名称 private Integer quantity; // 购买数量 private BigDecimal amount; // 订单金额 private Integer status; // 订单状态 private String action; // 操作类型:CREATED, PAID, CANCELLED private LocalDateTime timestamp; // 事件时间 } ``` #### 9.4 消息生产者(order-service 中发送事件) ```java @Component @Slf4j public class OrderEventProducer { private final StreamBridge streamBridge; public OrderEventProducer(StreamBridge streamBridge) { this.streamBridge = streamBridge; } /** * 发送订单创建事件(用于通知服务发送短信、日志服务记录日志) */ public void sendOrderCreatedEvent(Order order) { OrderEvent event = OrderEvent.builder() .orderId(order.getId()) .orderNo(order.getOrderNo()) .userId(order.getUserId()) .goodsId(order.getGoodsId()) .goodsName(order.getGoodsName()) .quantity(order.getQuantity()) .amount(order.getTotalPrice()) .action("CREATED") .timestamp(LocalDateTime.now()) .build(); streamBridge.send("orderEvent-out-0", event); log.info("📤 订单创建事件已发送: orderId={}", order.getId()); } /** * 发送订单支付事件 */ public void sendOrderPaidEvent(Order order) { OrderEvent event = buildEvent(order, "PAID"); streamBridge.send("orderEvent-out-0", event); log.info("📤 订单支付事件已发送: orderId={}", order.getId()); } } ``` #### 9.5 在 OrderServiceImpl 中集成消息发送 ```java @Service @Slf4j public class OrderServiceImpl extends ServiceImpl implements OrderService { @Autowired private OrderEventProducer eventProducer; // 注入消息生产者 @Override @Transactional public Order create(Order order) { // ... 原有逻辑:生成订单号、查询商品、扣减库存、保存订单 ... // 🆕 异步发送订单创建事件(不阻塞下单主流程) try { eventProducer.sendOrderCreatedEvent(order); } catch (Exception e) { log.error("发送订单事件失败(不影响主流程)", e); } return order; } } ``` #### 9.6 消息消费者 — 通知服务 ```java @Configuration @Slf4j public class NotificationConsumer { @Bean public Consumer orderNotification() { return event -> { log.info("📩 收到订单事件: orderId={}, action={}", event.getOrderId(), event.getAction()); switch (event.getAction()) { case "CREATED": log.info("📧 模拟发送下单成功短信: 用户ID={}, 订单编号={}", event.getUserId(), event.getOrderNo()); break; case "PAID": log.info("📧 模拟发送支付成功短信: 订单编号={}", event.getOrderNo()); break; case "CANCELLED": log.info("📧 模拟发送订单取消通知: 订单编号={}", event.getOrderNo()); break; } }; } } ``` #### 9.7 消息消费者 — 日志服务 ```java @Configuration @Slf4j public class LogConsumer { @Bean public Consumer orderLog() { return event -> { // 记录操作日志到数据库或日志文件 log.info("📝 [操作日志] 用户={}, 订单={}, 操作={}, 金额={}, 时间={}", event.getUserId(), event.getOrderNo(), event.getAction(), event.getAmount(), event.getTimestamp() ); }; } } ``` #### 9.8 Stream 配置文件 ```yaml spring: rabbitmq: host: localhost port: 5672 username: admin password: admin cloud: stream: # 消息通道绑定 bindings: # 生产者:订单事件输出通道 orderEvent-out-0: destination: order-exchange # RabbitMQ 交换机名称 content-type: application/json producer: required-groups: notification-group, log-group # 确保队列被创建 # 消费者:通知服务输入通道 orderNotification-in-0: destination: order-exchange group: notification-group # 消费者分组(竞争消费) consumer: max-attempts: 3 # 最大重试次数 back-off-initial-interval: 1000 # 首次重试间隔 # 消费者:日志服务输入通道 orderLog-in-0: destination: order-exchange group: log-group # 不同分组,广播消费 consumer: max-attempts: 3 back-off-initial-interval: 1000 # RabbitMQ 专属配置 rabbit: bindings: orderNotification-in-0: consumer: auto-bind-dlq: true # 自动绑定死信队列 dlq-ttl: 5000 orderLog-in-0: consumer: auto-bind-dlq: true dlq-ttl: 5000 # 函数式编程声明 cloud.function.definition: orderNotification;orderLog ``` #### 9.9 订单超时自动取消(延迟消息) ```java /** * 下单后发送延迟消息,30 分钟后检查订单是否已支付 * 未支付则自动取消订单并恢复库存 */ public void sendOrderTimeoutCheck(Order order) { OrderEvent event = buildEvent(order, "TIMEOUT_CHECK"); // 发送延迟消息:30 分钟后消费 streamBridge.send("orderTimeout-out-0", MessageBuilder.withPayload(event) .setHeader("x-delay", 30 * 60 * 1000) // RabbitMQ 延迟插件 .build() ); } // 消费延迟消息 @Bean public Consumer orderTimeoutHandler() { return event -> { Order order = orderMapper.selectById(event.getOrderId()); if (order != null && order.getStatus() == 0) { // 订单仍未支付,自动取消 order.setStatus(4); orderMapper.updateById(order); // 恢复库存 goodsClient.restoreStock(order.getGoodsId(), order.getQuantity()); log.info("⏰ 订单 {} 超时未支付,已自动取消", order.getOrderNo()); } }; } ``` ### 验收标准 - [ ] RabbitMQ 管理控制台(`:15672`)中能看到 `order-exchange` 交换机 - [ ] 创建订单后,控制台日志显示"📤 订单创建事件已发送" - [ ] 通知和日志消费者的控制台分别打印"📩 收到订单事件"和"📝 [操作日志]" - [ ] 消息消费失败后自动重试 3 次,仍失败则进入死信队列 - [ ] RabbitMQ 控制台能看到 `notification-group` 和 `log-group` 两个队列 - [ ] 取消一个消费者服务,消息被同组的另一个实例接管(竞争消费验证) --- ## 任务十:分布式事务 ### 电商业务场景 用户下单时,需要同时完成两个操作: 1. **order-service**:创建订单记录 2. **goods-service**:扣减商品库存 这两个操作跨两个微服务。如果库存扣减成功但订单保存失败(或反过来),就会产生数据不一致——这正是分布式事务要解决的问题。 ### 任务描述 使用 **Seata** 解决跨服务调用场景下的分布式事务问题(如创建订单时需要同时扣减商品库存)。 ### 当前代码的分布式事务风险 ```java // OrderServiceImpl.create() — 当前实现存在数据不一致风险 @Override @Transactional // ⚠️ 只能回滚本服务的本地事务! public Order create(Order order) { // ... 生成订单号、查询商品信息 ... // Step 1: Feign 调用 goods-service 扣减库存(远程调用,不在同一个事务中) goodsClient.reduceStock(order.getGoodsId(), order.getQuantity()); // Step 2: 保存订单到本地数据库 orderMapper.insert(order); // ⚠️ 如果 Step 1 成功但 Step 2 失败,库存已扣减但订单未创建! return order; } ``` ### 实现步骤 #### 10.1 搭建 Seata Server ```bash # 1. 创建 seata 数据库(存储全局事务信息) # 2. 配置 seata-server 连接到 Nacos(服务注册 + 配置中心) # 3. 启动 Seata Server seata-server.bat -p 8091 -m file ``` #### 10.2 在 order-service 和 goods-service 中添加依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-seata ``` #### 10.3 配置 Seata ```yaml seata: enabled: true application-id: ${spring.application.name} tx-service-group: ecommerce-tx-group # 事务分组 config: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP namespace: "" registry: type: nacos nacos: application: seata-server server-addr: localhost:8848 group: SEATA_GROUP ``` #### 10.4 改造订单创建方法 ```java @Service @Slf4j public class OrderServiceImpl extends ServiceImpl implements OrderService { @Autowired private GoodsClient goodsClient; @Override @GlobalTransactional(name = "create-order", rollbackFor = Exception.class) // 🆕 Seata 全局事务 @Transactional // 本地事务 public Order create(Order order) { // 1. 生成订单编号 order.setOrderNo(UUID.randomUUID().toString().replace("-", "")); order.setStatus(0); // 2. 查询商品信息 Goods goods = goodsClient.getById(order.getGoodsId()).getData(); order.setGoodsName(goods.getName()); order.setGoodsPrice(goods.getPrice()); // 3. 计算总价 order.setTotalPrice(goods.getPrice().multiply(BigDecimal.valueOf(order.getQuantity()))); // 4. 扣减库存(远程调用,Seata 保证原子性) goodsClient.reduceStock(order.getGoodsId(), order.getQuantity()); // 5. 保存订单 orderMapper.insert(order); log.info("✅ 订单创建成功: orderNo={}, goods={}, qty={}, total={}", order.getOrderNo(), goods.getName(), order.getQuantity(), order.getTotalPrice()); return order; } } ``` #### 10.5 Seata AT 模式原理 ``` ┌────────────────┐ │ Seata Server │ ← 事务协调器(TC) │ (端口 8091) │ └───────┬────────┘ ┌─────────────┴─────────────┐ │ │ ┌─────────┴──────────┐ ┌──────────┴─────────┐ │ order-service │ │ goods-service │ │ (TM + RM) │ │ (RM) │ │ │ │ │ │ 1.开启全局事务 │ │ │ │ 2.执行业务SQL │ │ 3.Feign调用扣减库存 │ │ 4.注册分支事务 │◄──►│ 5.执行业务SQL │ │ 6.提交/回滚 │ │ 6.注册分支事务 │ │ │ │ 7.提交/回滚 │ └────────────────────┘ └─────────────────────┘ AT 模式核心:Seata 自动代理数据源,拦截 SQL 并生成前置镜像(before image) 和后置镜像(after image),回滚时使用前置镜像恢复数据。 ``` ### 验证分布式事务回滚 **场景 1:库存不足时回滚** ```java // 测试:购买 9999 件商品(库存只有 100 件) // 期望:goods-service 抛出库存不足异常 // 结果:Seata 回滚 order-service 的订单记录 @PostMapping("/order/test/rollback") public Result testRollback() { Order order = new Order(); order.setUserId(1L); order.setGoodsId(1L); order.setQuantity(9999); // 库存不足 try { orderService.create(order); } catch (Exception e) { // 验证:tb_order 表中没有该订单 // 验证:tb_goods 表中库存没有减少 return Result.success("回滚验证:订单未创建,库存未减少"); } return Result.error("应该回滚但没有"); } ``` ### 验收标准 - [ ] Seata Server 正常运行,Nacos 中能看到 seata-server 服务 - [ ] 正常创建订单:订单保存成功,库存正确减少(分布式事务正常提交) - [ ] 库存不足场景:goods-service 抛出异常后,order-service 的订单也回滚 - [ ] 查看 `undo_log` 表(Seata AT 模式自动创建),确认回滚日志已生成 - [ ] 模拟 goods-service 宕机:订单创建失败,无脏数据残留 --- ## 任务十一:实现链路追踪 ### 电商业务场景 用户反馈下单很慢,但不知道瓶颈在哪个环节。通过链路追踪可以看到请求经过的所有微服务节点及其耗时: ``` Gateway(:9090) ──50ms──► order-service(:9092) ──30ms──► goods-service(:9093) └──10ms──► user-service(:9091) ``` 发现 goods-service 的库存查询耗时 30ms,可以针对性优化。 ### 任务描述 使用 **Sleuth + Zipkin** 实现分布式链路追踪,能够在一次请求中追踪经过的所有微服务节点及其耗时。 ### 实现步骤 #### 11.1 启动 Zipkin Server ```bash # Docker 启动 Zipkin docker run -d --name zipkin \ -p 9411:9411 \ openzipkin/zipkin # 访问 http://localhost:9411 ``` #### 11.2 添加依赖(所有微服务) ```xml org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin ``` #### 11.3 配置 Zipkin 上报 ```yaml spring: zipkin: base-url: http://localhost:9411 # Zipkin Server 地址 sender: type: web # HTTP 方式上报 sleuth: sampler: probability: 1.0 # 100% 采样(生产环境建议 0.1) ``` #### 11.4 电商系统的完整调用链 发起一次创建订单请求(POST `/order`),Sleuth 自动为整个调用链生成唯一 TraceId: ``` ┌─ TraceId: 5a7b3c2d1e4f6a8b ─────────────────────────────────┐ │ │ │ Span: Gateway │ │ ├─ ws://localhost:9090 POST /order │ │ │ 耗时: 5ms │ │ └─ Span: order-service │ │ ├─ http://order-service:9092/order │ │ │ 耗时: 50ms │ │ ├─ Span: goods-service (Feign 查商品) │ │ │ ├─ http://goods-service:9093/goods/1 │ │ │ │ 耗时: 20ms │ │ │ └─ Span: MySQL (SELECT * FROM tb_goods WHERE id=1) │ │ │ 耗时: 15ms │ │ └─ Span: goods-service (Feign 扣库存) │ │ └─ http://goods-service:9093/goods/1/stock/reduce │ │ 耗时: 15ms │ │ │ │ 总耗时: 200ms │ └───────────────────────────────────────────────────────────────┘ ``` ### 验证步骤 1. 发起一次跨服务请求(POST `/order` 创建订单) 2. 打开 Zipkin UI(`http://localhost:9411`) 3. 点击"Run Query"查看最近的调用链 4. 点击某条调用链,查看服务间的依赖关系和耗时分布 5. 点击"依赖分析"查看微服务拓扑图 ### 验收标准 - [ ] Zipkin Server 正常启动(`:9411`) - [ ] 创建订单后,Zipkin UI 中出现完整的调用链(Gateway → order-service → goods-service) - [ ] 能够看到每个 Span 的耗时(如 Feign 调用 goods-service 耗时多少 ms) - [ ] 日志中自动输出 TraceId(格式如 `[order-service,5a7b3c2d1e4f6a8b,abc123,true]`) - [ ] 依赖关系图中能看出 order-service 依赖 goods-service --- ## 技术栈总览 | 组件 | 版本 | 说明 | 电商项目中的角色 | |---|---|---|---| | Java | 17 | 运行环境 | — | | Spring Boot | 2.7.14 | 基础框架 | 各微服务基础脚手架 | | Spring Cloud | 2021.0.3 | 微服务治理 | 服务注册、配置、网关、远程调用 | | Spring Cloud Alibaba | 2021.0.5.0 | 微服务生态 | Nacos + Sentinel + Seata 三件套 | | Nacos | 2.x | 服务注册 & 配置中心 | 所有微服务注册到 Nacos,Sentinel 规则持久化 | | Sentinel | 1.8.6 | 限流熔断 | 保护下单接口,防止秒杀流量冲垮系统 | | Seata | 1.6.1 | 分布式事务 | 保证"下单+扣库存"的原子性 | | Gateway | 3.1.4 | API 网关 | 前端统一入口,路由转发 + CORS | | OpenFeign | 3.1.3 | 远程调用 | order→user、order→goods 的声明式调用 | | Spring Cloud LoadBalancer | 3.1.3 | 负载均衡 | 多实例流量分发(随机/轮询策略) | | Spring Cloud Stream | 3.2.6 | 消息驱动 | 订单事件异步通知 + 操作日志 + 超时取消 | | RabbitMQ | 3.x | 消息中间件 | Stream 的消息代理 | | Sleuth + Zipkin | 3.1.3 | 链路追踪 | 追踪下单请求在各服务间的耗时分布 | | MyBatis-Plus | 3.5.3.1 | ORM 框架 | 简化 CRUD,BaseMapper 一行代码搞定 | | MySQL | 8.0 | 数据库 | ecommerce 库存储用户、商品、订单数据 | | Knife4j | 4.3.0 | API 文档聚合 | 在线查看和调试所有微服务接口 | | Vue 3 | 3.x | 前端框架 | Composition API 开发管理后台和用户端 | | Element Plus | 2.x | UI 组件库 | 表格、表单、弹窗等企业级 UI | | Vite | 4.x | 构建工具 | 前端开发服务器 + 代理到 Gateway | --- ## 快速启动指南 ### 1. 环境准备 - JDK 17+ - MySQL 8.0+ - Maven 3.6+ - Node.js 16+(前端) ### 2. 启动基础设施 ```bash # 1. 启动 MySQL 并执行初始化 SQL mysql -u root -p < sql/电商系统数据库设计.sql # 2. 启动 Nacos(端口 8848) # 下载 nacos-server-2.x.zip,解压后运行: bin/startup.cmd -m standalone # 3. 启动 RabbitMQ(Docker) docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin \ rabbitmq:3-management ``` ### 3. 启动微服务(按顺序) ```bash # 1. goods-service(:9093)— 商品和分类数据必须最先可用 cd goods-service && mvn spring-boot:run # 2. user-service(:9091) cd user-service && mvn spring-boot:run # 3. order-service(:9092)— 依赖 goods-service 和 user-service cd order-service && mvn spring-boot:run # 4. gateway-service(:9090)— 最后启动,作为统一入口 cd gateway-service && mvn spring-boot:run ``` ### 4. 启动前端 ```bash cd frontend npm install npm run dev # 访问 http://localhost:3000 ``` ### 5. 启动可选基础设施 ```bash # Sentinel Dashboard(端口 8080) java -jar sentinel-dashboard-1.8.6.jar # Seata Server(端口 8091) seata-server.bat -p 8091 # Zipkin Server(端口 9411) docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin ``` ### 6. 验证系统 - Nacos 控制台: `http://localhost:8848/nacos`(账号密码 nacos/nacos) - Knife4j 文档: `http://localhost:9090/doc.html` - RabbitMQ 控制台: `http://localhost:15672`(admin/admin) - Sentinel Dashboard: `http://localhost:8080`(sentinel/sentinel) - Zipkin UI: `http://localhost:9411` - 前端页面: `http://localhost:3000`