第一章:项目架构设计与技术选型
1.1 电商系统核心业务分析 传统单体电商系统的痛点:
java // 传统单体电商架构的问题 单点故障:商品服务崩溃 → 整个网站不可用 技术栈固化:所有模块必须使用相同技术 扩展困难:促销时只能整体扩容 发布风险:小改动需要全站发布 团队协作:代码冲突、沟通成本高
// 电商系统核心业务流程: 用户浏览 → 加入购物车 → 下单 → 支付 → 发货 → 收货 → 评价 ↓ ↓ ↓ ↓ ↓ ↓ ↓ 商品服务 购物车服务 订单服务 支付服务 库存服务 物流服务 评价服务 微服务拆分策略:
text 电商微服务拆分原则:
核心服务划分: 用户域:用户服务、认证服务、会员服务 商品域:商品服务、类目服务、搜索服务 交易域:购物车服务、订单服务、支付服务 库存域:库存服务、仓库服务 物流域:物流服务、地址服务 营销域:优惠券服务、活动服务 运营域:统计服务、通知服务 1.2 技术栈全景图 完整技术选型:
xml
核心技术:Spring Boot 3.1 + Spring Cloud 2022 + JDK 17 注册中心:Nacos 2.2(服务发现+配置中心) API网关:Spring Cloud Gateway + Sentinel 服务通信:OpenFeign + LoadBalancer 熔断降级:Resilience4j + Sentinel 链路追踪:Sleuth + Zipkin + SkyWalking 配置中心:Nacos Config 分布式事务:Seata 消息队列:RocketMQ 5.0 缓存:Redis 7 + Redisson 数据库:MySQL 8.0 + MyBatis-Plus 搜索引擎:Elasticsearch 8.0 对象存储:MinIO 监控告警:Prometheus + Grafana + AlertManager 容器化:Docker + Kubernetes CI/CD:Jenkins + GitLab
第二章:项目初始化与基础架构
2.1 创建父工程与模块划分 项目整体结构:
text e-commerce-microservices/ ├── README.md ├── pom.xml # 父工程 ├── docker-compose.yml ├── sql/ # 数据库脚本 ├── config/ # 配置文件 ├── scripts/ # 部署脚本 ├── docs/ # 文档 ├── e-commerce-parent/ # 父模块 │ ├── e-commerce-common/ # 公共模块 │ │ ├── common-core/ # 核心工具 │ │ ├── common-dto/ # 通用DTO │ │ ├── common-feign/ # Feign客户端 │ │ ├── common-redis/ # Redis配置 │ │ ├── common-mybatis/ # MyBatis配置 │ │ └── common-security/ # 安全配置 │ │ │ ├── e-commerce-gateway/ # API网关 │ ├── e-commerce-auth/ # 认证中心 │ ├── e-commerce-user/ # 用户服务 │ ├── e-commerce-product/ # 商品服务 │ ├── e-commerce-cart/ # 购物车服务 │ ├── e-commerce-order/ # 订单服务 │ ├── e-commerce-payment/ # 支付服务 │ ├── e-commerce-inventory/ # 库存服务 │ ├── e-commerce-coupon/ # 优惠券服务 │ ├── e-commerce-search/ # 搜索服务 │ ├── e-commerce-notification/ # 通知服务 │ └── e-commerce-monitor/ # 监控服务 └── infrastructure/ # 基础设施 ├── nacos/ # Nacos配置 ├── seata/ # Seata配置 ├── sentinel/ # Sentinel配置 └── prometheus/ # Prometheus配置 父工程pom.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion>
<groupId>com.ecommerce</groupId>
<artifactId>e-commerce-microservices</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>e-commerce-microservices</name>
<description>电商微服务系统</description>
<!– 模块定义 –>
<modules>
<module>e-commerce-parent/e-commerce-common/common-core</module>
<module>e-commerce-parent/e-commerce-common/common-dto</module>
<module>e-commerce-parent/e-commerce-common/common-feign</module>
<module>e-commerce-parent/e-commerce-common/common-redis</module>
<module>e-commerce-parent/e-commerce-common/common-mybatis</module>
<module>e-commerce-parent/e-commerce-common/common-security</module>
<module>e-commerce-parent/e-commerce-gateway</module>
<module>e-commerce-parent/e-commerce-auth</module>
<module>e-commerce-parent/e-commerce-user</module>
<module>e-commerce-parent/e-commerce-product</module>
<module>e-commerce-parent/e-commerce-cart</module>
<module>e-commerce-parent/e-commerce-order</module>
<module>e-commerce-parent/e-commerce-payment</module>
<module>e-commerce-parent/e-commerce-inventory</module>
<module>e-commerce-parent/e-commerce-coupon</module>
<module>e-commerce-parent/e-commerce-search</module>
<module>e-commerce-parent/e-commerce-notification</module>
<module>e-commerce-parent/e-commerce-monitor</module>
</modules>
<!– 统一版本管理 –>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<properties>
<!– 基础版本 –>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!– Spring Cloud –>
<spring-cloud.version>2022.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<!– 持久层 –>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<druid.version>1.2.18</druid.version>
<!– 工具 –>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.28</lombok.version>
<hutool.version>5.8.20</hutool.version>
<!– 缓存 –>
<redisson.version>3.20.0</redisson.version>
<!– 分布式事务 –>
<seata.version>1.7.1</seata.version>
<!– 消息队列 –>
<rocketmq.version>2.2.3</rocketmq.version>
<!– 服务端口 –>
<gateway.port>8080</gateway.port>
<auth.port>8081</auth.port>
<user.port>8082</user.port>
<product.port>8083</product.port>
<cart.port>8084</cart.port>
<order.port>8085</order.port>
<payment.port>8086</payment.port>
<inventory.port>8087</inventory.port>
</properties>
<!– 依赖管理 –>
<dependencyManagement>
<dependencies>
<!– 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>
<!– Druid –>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!– MapStruct –>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!– Redisson –>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<!– Hutool –>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!– Seata –>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!– RocketMQ –>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!– 通用依赖 –>
<dependencies>
<!– Lombok –>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!– 测试 –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!– 构建配置 –>
<build>
<plugins>
<!– 编译器插件 –>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!– Spring Boot Maven插件 –>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2.2 公共模块设计 common-core 核心工具模块:
java // 1. 统一响应类 package com.ecommerce.common.core.response;
import lombok.Data; import lombok.experimental.Accessors;
import java.io.Serializable;
@Data @Accessors(chain = true) public class ApiResponse implements Serializable {
private Integer code;
private String message;
private T data;
private Long timestamp;
public static <T> ApiResponse<T> success() {
return success(null);
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<T>()
.setCode(200)
.setMessage("成功")
.setData(data)
.setTimestamp(System.currentTimeMillis());
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<T>()
.setCode(200)
.setMessage(message)
.setData(data)
.setTimestamp(System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(Integer code, String message) {
return new ApiResponse<T>()
.setCode(code)
.setMessage(message)
.setTimestamp(System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(String message) {
return error(500, message);
}
}
// 2. 业务异常类 package com.ecommerce.common.core.exception;
import lombok.Getter;
@Getter public class BusinessException extends RuntimeException {
private final Integer code;
private final String message;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
}
// 3. 错误码枚举 package com.ecommerce.common.core.exception;
import lombok.Getter;
@Getter public enum ErrorCode {
// 通用错误码
SUCCESS(200, "成功"),
PARAM_ERROR(400, "参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
METHOD_NOT_ALLOWED(405, "方法不允许"),
SYSTEM_ERROR(500, "系统错误"),
SERVICE_UNAVAILABLE(503, "服务不可用"),
// 业务错误码 1000-1999
USER_NOT_EXIST(1001, "用户不存在"),
USER_DISABLED(1002, "用户已被禁用"),
USER_PASSWORD_ERROR(1003, "密码错误"),
USER_EXISTS(1004, "用户已存在"),
// 商品错误码 2000-2999
PRODUCT_NOT_EXIST(2001, "商品不存在"),
PRODUCT_OFF_SHELF(2002, "商品已下架"),
PRODUCT_INSUFFICIENT_STOCK(2003, "商品库存不足"),
// 订单错误码 3000-3999
ORDER_NOT_EXIST(3001, "订单不存在"),
ORDER_STATUS_ERROR(3002, "订单状态错误"),
ORDER_CANCEL_FAILED(3003, "订单取消失败"),
// 支付错误码 4000-4999
PAYMENT_FAILED(4001, "支付失败"),
PAYMENT_NOT_EXIST(4002, "支付记录不存在"),
// 库存错误码 5000-5999
INVENTORY_LOCK_FAILED(5001, "库存锁定失败"),
INVENTORY_RELEASE_FAILED(5002, "库存释放失败"),
// 优惠券错误码 6000-6999
COUPON_NOT_EXIST(6001, "优惠券不存在"),
COUPON_EXPIRED(6002, "优惠券已过期"),
COUPON_USED(6003, "优惠券已使用");
private final Integer code;
private final String message;
ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
// 4. 统一异常处理器 package com.ecommerce.common.core.handler;
import com.ecommerce.common.core.response.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap; import java.util.Map;
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusinessException(BusinessException e) {
log.error("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ApiResponse.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<?> handleValidationException(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
log.error("参数验证失败: {}", errors);
return ApiResponse.error(400, "参数验证失败").setData(errors);
}
@ExceptionHandler(Exception.class)
public ApiResponse<?> handleGlobalException(Exception e) {
log.error("系统异常: ", e);
return ApiResponse.error(500, "系统异常,请稍后重试");
}
} common-dto 数据传输对象:
java // 1. 分页请求DTO package com.ecommerce.common.dto;
import lombok.Data;
@Data public class PageRequest {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String orderBy;
private Boolean asc = true;
public Integer getOffset() {
return (pageNum – 1) * pageSize;
}
}
// 2. 分页响应DTO package com.ecommerce.common.dto;
import lombok.Data; import lombok.NoArgsConstructor;
import java.util.List;
@Data @NoArgsConstructor public class PageResult {
private Integer pageNum;
private Integer pageSize;
private Long total;
private Integer pages;
private List<T> list;
public PageResult(Integer pageNum, Integer pageSize, Long total, List<T> list) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
this.pages = (int) Math.ceil((double) total / pageSize);
this.list = list;
}
public static <T> PageResult<T> of(Integer pageNum, Integer pageSize, Long total, List<T> list) {
return new PageResult<>(pageNum, pageSize, total, list);
}
}
// 3. 用户上下文DTO package com.ecommerce.common.dto;
import lombok.Data;
@Data public class UserContext {
private Long userId;
private String username;
private String nickname;
private String phone;
private String email;
private Integer userType;
private String token;
public static UserContext empty() {
return new UserContext();
}
public boolean isLogin() {
return userId != null;
}
} common-feign Feign客户端配置:
java // 1. Feign配置类 package com.ecommerce.common.feign.config;
import com.ecommerce.common.feign.interceptor.FeignAuthInterceptor; import feign.Logger; import feign.Request; import feign.Retryer; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public Request.Options options() {
return new Request.Options(
5, TimeUnit.SECONDS, // 连接超时
30, TimeUnit.SECONDS, // 读取超时
true // 跟随重定向
);
}
@Bean
public Retryer feignRetryer() {
// 最大重试3次,初始间隔100ms
return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);
}
@Bean
public FeignAuthInterceptor feignAuthInterceptor() {
return new FeignAuthInterceptor();
}
@Bean
public ErrorDecoder errorDecoder() {
return new FeignErrorDecoder();
}
}
// 2. Feign认证拦截器 package com.ecommerce.common.feign.interceptor;
import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration;
@Slf4j public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 传递认证信息
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (headerName.startsWith("x-") ||
headerName.equalsIgnoreCase("authorization")) {
String headerValue = request.getHeader(headerName);
template.header(headerName, headerValue);
}
}
}
// 添加Feign调用标识
template.header("X-Feign-Request", "true");
template.header("X-Request-ID", generateRequestId());
log.debug("Feign请求: {} {}, Headers: {}",
template.method(), template.url(), template.headers());
}
private String generateRequestId() {
return java.util.UUID.randomUUID().toString();
}
}
// 3. Feign降级工厂 package com.ecommerce.common.feign.fallback;
import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j;
@Slf4j public class CommonFallbackFactory implements FallbackFactory {
private final Class<T> targetType;
public CommonFallbackFactory(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T create(Throwable cause) {
log.error("Feign调用失败,服务: {}, 错误: {}",
targetType.getSimpleName(), cause.getMessage());
return new FallbackProxy<>(targetType, cause).createProxy();
}
} 2.3 数据库设计与初始化 数据库设计原则:
sql – 1. 用户数据库 (user_db) CREATE DATABASE IF NOT EXISTS user_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
– 用户表 CREATE TABLE user_db.tb_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘用户ID’, username VARCHAR(50) NOT NULL COMMENT ‘用户名’, password VARCHAR(100) NOT NULL COMMENT ‘密码’, nickname VARCHAR(50) COMMENT ‘昵称’, phone VARCHAR(20) COMMENT ‘手机号’, email VARCHAR(100) COMMENT ‘邮箱’, avatar VARCHAR(500) COMMENT ‘头像’, gender TINYINT DEFAULT 0 COMMENT ‘性别 0-未知 1-男 2-女’, birthday DATE COMMENT ‘生日’, status TINYINT DEFAULT 1 COMMENT ‘状态 0-禁用 1-正常’, user_type TINYINT DEFAULT 1 COMMENT ‘用户类型 1-普通用户 2-会员 3-管理员’, last_login_time DATETIME COMMENT ‘最后登录时间’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), UNIQUE KEY uk_username (username), UNIQUE KEY uk_phone (phone), UNIQUE KEY uk_email (email), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户表’;
– 用户地址表 CREATE TABLE user_db.tb_user_address ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘地址ID’, user_id BIGINT NOT NULL COMMENT ‘用户ID’, receiver_name VARCHAR(50) NOT NULL COMMENT ‘收货人姓名’, receiver_phone VARCHAR(20) NOT NULL COMMENT ‘收货人电话’, province VARCHAR(50) COMMENT ‘省’, city VARCHAR(50) COMMENT ‘市’, district VARCHAR(50) COMMENT ‘区’, detail VARCHAR(200) COMMENT ‘详细地址’, postal_code VARCHAR(10) COMMENT ‘邮政编码’, is_default TINYINT DEFAULT 0 COMMENT ‘是否默认地址 0-否 1-是’, status TINYINT DEFAULT 1 COMMENT ‘状态 0-删除 1-正常’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_is_default (is_default), CONSTRAINT fk_user_address_user FOREIGN KEY (user_id) REFERENCES tb_user (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户地址表’;
– 2. 商品数据库 (product_db) CREATE DATABASE IF NOT EXISTS product_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
– 商品表 CREATE TABLE product_db.tb_product ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘商品ID’, spu_code VARCHAR(50) NOT NULL COMMENT ‘SPU编码’, name VARCHAR(200) NOT NULL COMMENT ‘商品名称’, sub_title VARCHAR(500) COMMENT ‘副标题’, category_id BIGINT NOT NULL COMMENT ‘分类ID’, brand_id BIGINT COMMENT ‘品牌ID’, main_image VARCHAR(500) COMMENT ‘主图’, images TEXT COMMENT ‘商品图片JSON数组’, detail TEXT COMMENT ‘商品详情’, specifications TEXT COMMENT ‘规格参数JSON’, price DECIMAL(10,2) NOT NULL COMMENT ‘价格’, market_price DECIMAL(10,2) COMMENT ‘市场价格’, cost_price DECIMAL(10,2) COMMENT ‘成本价’, stock INT NOT NULL DEFAULT 0 COMMENT ‘库存’, sales INT DEFAULT 0 COMMENT ‘销量’, status TINYINT DEFAULT 1 COMMENT ‘状态 0-下架 1-上架 2-待审核’, weight DECIMAL(10,3) COMMENT ‘重量(kg)’, volume DECIMAL(10,3) COMMENT ‘体积(m³)’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), UNIQUE KEY uk_spu_code (spu_code), KEY idx_category_id (category_id), KEY idx_brand_id (brand_id), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘商品表’;
– 商品SKU表 CREATE TABLE product_db.tb_product_sku ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘SKU ID’, product_id BIGINT NOT NULL COMMENT ‘商品ID’, sku_code VARCHAR(50) NOT NULL COMMENT ‘SKU编码’, specifications VARCHAR(500) COMMENT ‘规格JSON’, price DECIMAL(10,2) NOT NULL COMMENT ‘价格’, market_price DECIMAL(10,2) COMMENT ‘市场价格’, stock INT NOT NULL DEFAULT 0 COMMENT ‘库存’, sales INT DEFAULT 0 COMMENT ‘销量’, image VARCHAR(500) COMMENT ‘SKU图片’, status TINYINT DEFAULT 1 COMMENT ‘状态 0-禁用 1-正常’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), UNIQUE KEY uk_sku_code (sku_code), KEY idx_product_id (product_id), KEY idx_status (status), CONSTRAINT fk_product_sku_product FOREIGN KEY (product_id) REFERENCES tb_product (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘商品SKU表’;
– 3. 订单数据库 (order_db) CREATE DATABASE IF NOT EXISTS order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
– 订单表 CREATE TABLE order_db.tb_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘订单ID’, order_no VARCHAR(32) NOT NULL COMMENT ‘订单号’, user_id BIGINT NOT NULL COMMENT ‘用户ID’, total_amount DECIMAL(10,2) NOT NULL COMMENT ‘订单总金额’, discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT ‘优惠金额’, shipping_amount DECIMAL(10,2) DEFAULT 0 COMMENT ‘运费’, pay_amount DECIMAL(10,2) NOT NULL COMMENT ‘实付金额’, payment_type TINYINT COMMENT ‘支付方式 1-微信 2-支付宝’, status TINYINT NOT NULL COMMENT ‘订单状态 1-待付款 2-待发货 3-已发货 4-已完成 5-已取消’, payment_time DATETIME COMMENT ‘支付时间’, shipping_time DATETIME COMMENT ‘发货时间’, receive_time DATETIME COMMENT ‘收货时间’, cancel_time DATETIME COMMENT ‘取消时间’, cancel_reason VARCHAR(200) COMMENT ‘取消原因’, remark VARCHAR(500) COMMENT ‘订单备注’, receiver_name VARCHAR(50) NOT NULL COMMENT ‘收货人姓名’, receiver_phone VARCHAR(20) NOT NULL COMMENT ‘收货人电话’, receiver_address VARCHAR(500) NOT NULL COMMENT ‘收货地址’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘订单表’;
– 订单商品表 CREATE TABLE order_db.tb_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(200) NOT NULL COMMENT ‘商品名称’, product_image VARCHAR(500) COMMENT ‘商品图片’, sku_id BIGINT COMMENT ‘SKU ID’, sku_code VARCHAR(50) COMMENT ‘SKU编码’, specifications VARCHAR(500) COMMENT ‘规格’, price DECIMAL(10,2) NOT NULL COMMENT ‘单价’, quantity INT NOT NULL COMMENT ‘数量’, total_amount DECIMAL(10,2) NOT NULL COMMENT ‘总金额’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, PRIMARY KEY (id), KEY idx_order_id (order_id), KEY idx_product_id (product_id), CONSTRAINT fk_order_item_order FOREIGN KEY (order_id) REFERENCES tb_order (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘订单商品表’;
– 4. 库存数据库 (inventory_db) CREATE DATABASE IF NOT EXISTS inventory_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
– 库存表 CREATE TABLE inventory_db.tb_inventory ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘库存ID’, product_id BIGINT NOT NULL COMMENT ‘商品ID’, sku_id BIGINT COMMENT ‘SKU ID’, warehouse_id BIGINT NOT NULL COMMENT ‘仓库ID’, total_stock INT NOT NULL DEFAULT 0 COMMENT ‘总库存’, available_stock INT NOT NULL DEFAULT 0 COMMENT ‘可用库存’, locked_stock INT DEFAULT 0 COMMENT ‘锁定库存’, occupied_stock INT DEFAULT 0 COMMENT ‘占用库存’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id), UNIQUE KEY uk_product_warehouse (product_id, warehouse_id), KEY idx_sku_id (sku_id), KEY idx_warehouse_id (warehouse_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘库存表’;
– 库存操作记录表 CREATE TABLE inventory_db.tb_inventory_log ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT ‘日志ID’, product_id BIGINT NOT NULL COMMENT ‘商品ID’, sku_id BIGINT COMMENT ‘SKU ID’, warehouse_id BIGINT NOT NULL COMMENT ‘仓库ID’, order_no VARCHAR(32) COMMENT ‘订单号’, operation_type TINYINT NOT NULL COMMENT ‘操作类型 1-入库 2-出库 3-锁定 4-解锁 5-占用 6-释放’, quantity INT NOT NULL COMMENT ‘操作数量’, before_stock INT NOT NULL COMMENT ‘操作前库存’, after_stock INT NOT NULL COMMENT ‘操作后库存’, remark VARCHAR(200) COMMENT ‘备注’, operator VARCHAR(50) COMMENT ‘操作人’, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, PRIMARY KEY (id), KEY idx_product_id (product_id), KEY idx_order_no (order_no), KEY idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘库存操作记录表’;
第三章:用户服务实现
3.1 用户服务核心功能 用户服务pom.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ecommerce</groupId>
<artifactId>e-commerce-microservices</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>e-commerce-user</artifactId>
<name>e-commerce-user</name>
<description>用户服务</description>
<properties>
<service.port>8082</service.port>
</properties>
<dependencies>
<!– 公共模块 –>
<dependency>
<groupId>com.ecommerce</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.ecommerce</groupId>
<artifactId>common-dto</artifactId>
<version>${project.version}</version>
</dependency>
<!– Spring Boot –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!– Spring Cloud –>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!– 持久层 –>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!– 数据库驱动 –>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!– Redis –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!– JWT –>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!– 监控 –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!– 测试 –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
用户服务配置文件:
yaml
bootstrap.yml
spring: application: name: user-service
profiles: active: dev
cloud: nacos: discovery: server-addr:
N
A
C
O
S
H
O
S
T
:
l
o
c
a
l
h
o
s
t
:
{NACOS_HOST:localhost}:
NACOSHOST:localhost:{NACOS_PORT:8848} namespace: ${NACOS_NAMESPACE:public} group: ${NACOS_GROUP:DEFAULT_GROUP} metadata: version: 1.0.0
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
group: ${spring.cloud.nacos.discovery.group}
file-extension: yaml
shared-configs:
– data-id: common.yaml
group: ${spring.cloud.nacos.discovery.group}
refresh: true
– data-id: datasource.yaml
group: ${spring.cloud.nacos.discovery.group}
refresh: true
– data-id: redis.yaml
group: ${spring.cloud.nacos.discovery.group}
refresh: true
application-dev.yml
server: port: 8082 servlet: context-path: /user
spring: 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: 123456 druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20
redis: host: localhost port: 6379 password: database: 0 lettuce: pool: max-active: 20 max-wait: -1ms max-idle: 10 min-idle: 0 shutdown-timeout: 100ms
mybatis-plus: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.ecommerce.user.entity 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
JWT配置
jwt: secret: ecommerce-user-secret-key-2023-spring-cloud-microservices expiration: 86400000 # 24小时 header: Authorization token-prefix: Bearer
用户服务配置
user: password: salt: ecommerce2023 max-retry-count: 5 lock-duration: 30 # 分钟
日志配置
logging: level: com.ecommerce.user: DEBUG org.springframework.cloud: INFO pattern: console: “%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} – %msg%n” file: name: logs/user-service.log max-size: 10MB max-history: 30
管理端点
management: endpoints: web: exposure: include: “health,info,metrics,prometheus” endpoint: health: show-details: always probes: enabled: true 用户服务核心代码:
java // 用户实体 package com.ecommerce.user.entity;
import com.baomidou.mybatisplus.annotation.*; import lombok.Data;
import java.time.LocalDateTime;
@Data @TableName(“tb_user”) public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String nickname;
private String phone;
private String email;
private String avatar;
private Integer gender;
private LocalDateTime birthday;
private Integer status;
private Integer userType;
private LocalDateTime lastLoginTime;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
// 用户地址实体 package com.ecommerce.user.entity;
import com.baomidou.mybatisplus.annotation.*; import lombok.Data;
import java.time.LocalDateTime;
@Data @TableName(“tb_user_address”) public class UserAddress {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String receiverName;
private String receiverPhone;
private String province;
private String city;
private String district;
private String detail;
private String postalCode;
private Integer isDefault;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
} 2. Mapper接口:
java // 用户Mapper package com.ecommerce.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.user.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;
@Mapper public interface UserMapper extends BaseMapper {
@Select("SELECT * FROM tb_user WHERE username = #{username} AND deleted = 0")
User selectByUsername(String username);
@Select("SELECT * FROM tb_user WHERE phone = #{phone} AND deleted = 0")
User selectByPhone(String phone);
@Select("SELECT * FROM tb_user WHERE email = #{email} AND deleted = 0")
User selectByEmail(String email);
}
// 用户地址Mapper package com.ecommerce.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.user.entity.UserAddress; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper public interface UserAddressMapper extends BaseMapper {
@Select("SELECT * FROM tb_user_address WHERE user_id = #{userId} AND status = 1 ORDER BY is_default DESC, create_time DESC")
List<UserAddress> selectByUserId(Long userId);
@Select("SELECT * FROM tb_user_address WHERE user_id = #{userId} AND is_default = 1 AND status = 1")
UserAddress selectDefaultByUserId(Long userId);
} 3. Service层:
java // 用户服务接口 package com.ecommerce.user.service;
import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.*;
public interface UserService {
// 用户注册
UserDTO register(UserRegisterDTO registerDTO);
// 用户登录
UserLoginResult login(UserLoginDTO loginDTO);
// 获取用户信息
UserDTO getUserInfo(Long userId);
// 更新用户信息
UserDTO updateUserInfo(Long userId, UserUpdateDTO updateDTO);
// 修改密码
void changePassword(Long userId, ChangePasswordDTO changePasswordDTO);
// 分页查询用户
PageResult<UserDTO> listUsers(PageRequest pageRequest, UserQueryDTO queryDTO);
// 检查用户名是否存在
boolean checkUsername(String username);
// 检查手机号是否存在
boolean checkPhone(String phone);
// 检查邮箱是否存在
boolean checkEmail(String email);
}
// 用户服务实现 package com.ecommerce.user.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ecommerce.common.core.exception.BusinessException; import com.ecommerce.common.core.exception.ErrorCode; import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.*; import com.ecommerce.user.entity.User; import com.ecommerce.user.mapper.UserMapper; import com.ecommerce.user.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors;
@Slf4j @Service @RequiredArgsConstructor @Transactional(rollbackFor = Exception.class) public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final RedisTemplate<String, Object> redisTemplate;
private final UserMapper userMapper;
@Override
public UserDTO register(UserRegisterDTO registerDTO) {
log.info("用户注册: username={}, phone={}",
registerDTO.getUsername(), registerDTO.getPhone());
// 1. 验证参数
validateRegisterDTO(registerDTO);
// 2. 检查用户名、手机号、邮箱是否已存在
if (checkUsername(registerDTO.getUsername())) {
throw new BusinessException(ErrorCode.USER_EXISTS);
}
if (StrUtil.isNotBlank(registerDTO.getPhone()) &&
checkPhone(registerDTO.getPhone())) {
throw new BusinessException("手机号已存在");
}
if (StrUtil.isNotBlank(registerDTO.getEmail()) &&
checkEmail(registerDTO.getEmail())) {
throw new BusinessException("邮箱已存在");
}
// 3. 创建用户
User user = new User();
BeanUtil.copyProperties(registerDTO, user);
// 密码加密
user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
// 设置默认值
user.setStatus(1); // 正常状态
user.setUserType(1); // 普通用户
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
// 4. 保存用户
userMapper.insert(user);
log.info("用户注册成功: userId={}, username={}", user.getId(), user.getUsername());
// 5. 清除相关缓存
clearUserCache(user.getId(), user.getUsername());
return convertToDTO(user);
}
@Override
public UserLoginResult login(UserLoginDTO loginDTO) {
log.info("用户登录: identifier={}", loginDTO.getIdentifier());
// 1. 获取用户
User user = getUserByIdentifier(loginDTO.getIdentifier());
if (user == null) {
throw new BusinessException(ErrorCode.USER_NOT_EXIST);
}
// 2. 检查用户状态
if (user.getStatus() != 1) {
throw new BusinessException(ErrorCode.USER_DISABLED);
}
// 3. 验证密码
if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
// 记录登录失败次数
recordLoginFailure(user.getId());
throw new BusinessException(ErrorCode.USER_PASSWORD_ERROR);
}
// 4. 清除登录失败记录
clearLoginFailure(user.getId());
// 5. 更新最后登录时间
user.setLastLoginTime(LocalDateTime.now());
userMapper.updateById(user);
// 6. 生成token
String token = generateToken(user);
// 7. 返回结果
UserLoginResult result = new UserLoginResult();
result.setUser(convertToDTO(user));
result.setToken(token);
result.setExpireTime(System.currentTimeMillis() + 86400000); // 24小时
log.info("用户登录成功: userId={}, username={}", user.getId(), user.getUsername());
return result;
}
@Override
@Transactional(readOnly = true)
public UserDTO getUserInfo(Long userId) {
log.debug("获取用户信息: userId={}", userId);
// 先从缓存获取
String cacheKey = "user:info:" + userId;
UserDTO cachedUser = (UserDTO) redisTemplate.opsForValue().get(cacheKey);
if (cachedUser != null) {
return cachedUser;
}
// 从数据库获取
User user = userMapper.selectById(userId);
if (user == null || user.getDeleted() == 1) {
throw new BusinessException(ErrorCode.USER_NOT_EXIST);
}
UserDTO userDTO = convertToDTO(user);
// 存入缓存
redisTemplate.opsForValue().set(cacheKey, userDTO, 30, TimeUnit.MINUTES);
return userDTO;
}
@Override
public PageResult<UserDTO> listUsers(PageRequest pageRequest, UserQueryDTO queryDTO) {
log.debug("分页查询用户: pageNum={}, pageSize={}",
pageRequest.getPageNum(), pageRequest.getPageSize());
// 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getDeleted, 0);
if (StrUtil.isNotBlank(queryDTO.getUsername())) {
wrapper.like(User::getUsername, queryDTO.getUsername());
}
if (StrUtil.isNotBlank(queryDTO.getPhone())) {
wrapper.like(User::getPhone, queryDTO.getPhone());
}
if (queryDTO.getStatus() != null) {
wrapper.eq(User::getStatus, queryDTO.getStatus());
}
if (queryDTO.getUserType() != null) {
wrapper.eq(User::getUserType, queryDTO.getUserType());
}
// 排序
if (StrUtil.isNotBlank(pageRequest.getOrderBy())) {
if (pageRequest.getAsc()) {
wrapper.orderByAsc(User::getCreateTime);
} else {
wrapper.orderByDesc(User::getCreateTime);
}
} else {
wrapper.orderByDesc(User::getCreateTime);
}
// 分页查询
Page<User> page = new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize());
Page<User> userPage = userMapper.selectPage(page, wrapper);
// 转换为DTO
List<UserDTO> userDTOs = userPage.getRecords().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return PageResult.of(
(int) userPage.getCurrent(),
(int) userPage.getSize(),
userPage.getTotal(),
userDTOs
);
}
private User getUserByIdentifier(String identifier) {
// 根据用户名、手机号、邮箱获取用户
User user = userMapper.selectByUsername(identifier);
if (user == null) {
user = userMapper.selectByPhone(identifier);
}
if (user == null) {
user = userMapper.selectByEmail(identifier);
}
return user;
}
private void validateRegisterDTO(UserRegisterDTO registerDTO) {
if (StrUtil.isBlank(registerDTO.getUsername())) {
throw new BusinessException("用户名不能为空");
}
if (StrUtil.isBlank(registerDTO.getPassword())) {
throw new BusinessException("密码不能为空");
}
if (StrUtil.isBlank(registerDTO.getPhone())) {
throw new BusinessException("手机号不能为空");
}
// 验证手机号格式
if (!registerDTO.getPhone().matches("^1[3-9]\\\\d{9}$")) {
throw new BusinessException("手机号格式不正确");
}
// 验证密码强度
if (registerDTO.getPassword().length() < 6) {
throw new BusinessException("密码长度不能少于6位");
}
}
private void recordLoginFailure(Long userId) {
String key = "user:login:fail:" + userId;
Long failCount = redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 30, TimeUnit.MINUTES);
// 如果失败次数超过5次,锁定账户30分钟
if (failCount != null && failCount >= 5) {
String lockKey = "user:lock:" + userId;
redisTemplate.opsForValue().set(lockKey, "locked", 30, TimeUnit.MINUTES);
log.warn("用户登录失败次数过多,账户锁定: userId={}, failCount={}", userId, failCount);
}
}
private void clearLoginFailure(Long userId) {
String key = "user:login:fail:" + userId;
redisTemplate.delete(key);
String lockKey = "user:lock:" + userId;
redisTemplate.delete(lockKey);
}
private String generateToken(User user) {
// 使用JWT生成token
Map<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
claims.put("username", user.getUsername());
claims.put("userType", user.getUserType());
return JwtUtil.generateToken(claims, user.getUsername());
}
private UserDTO convertToDTO(User user) {
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(user, userDTO);
return userDTO;
}
private void clearUserCache(Long userId, String username) {
// 清除用户信息缓存
redisTemplate.delete("user:info:" + userId);
redisTemplate.delete("user:info:username:" + username);
}
}
// 用户地址服务 package com.ecommerce.user.service;
import com.ecommerce.user.dto.UserAddressDTO;
import java.util.List;
public interface UserAddressService {
// 添加地址
UserAddressDTO addAddress(Long userId, UserAddressDTO addressDTO);
// 更新地址
UserAddressDTO updateAddress(Long userId, Long addressId, UserAddressDTO addressDTO);
// 删除地址
void deleteAddress(Long userId, Long addressId);
// 获取地址列表
List<UserAddressDTO> listAddresses(Long userId);
// 获取默认地址
UserAddressDTO getDefaultAddress(Long userId);
// 设置默认地址
void setDefaultAddress(Long userId, Long addressId);
} 4. 控制器层:
java // 用户控制器 package com.ecommerce.user.controller;
import com.ecommerce.common.core.response.ApiResponse; import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.; import com.ecommerce.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.;
@Slf4j @RestController @RequestMapping(“/api/user”) @RequiredArgsConstructor @Tag(name = “用户管理”, description = “用户管理相关接口”) public class UserController {
private final UserService userService;
@PostMapping("/register")
@Operation(summary = "用户注册", description = "新用户注册接口")
public ApiResponse<UserDTO> register(@Valid @RequestBody UserRegisterDTO registerDTO) {
UserDTO userDTO = userService.register(registerDTO);
return ApiResponse.success("注册成功", userDTO);
}
@PostMapping("/login")
@Operation(summary = "用户登录", description = "用户登录接口")
public ApiResponse<UserLoginResult> login(@Valid @RequestBody UserLoginDTO loginDTO) {
UserLoginResult result = userService.login(loginDTO);
return ApiResponse.success("登录成功", result);
}
@GetMapping("/info")
@Operation(summary = "获取用户信息", description = "获取当前登录用户信息")
public ApiResponse<UserDTO> getUserInfo(@RequestHeader("X-User-Id") Long userId) {
UserDTO userDTO = userService.getUserInfo(userId);
return ApiResponse.success(userDTO);
}
@PutMapping("/info")
@Operation(summary = "更新用户信息", description = "更新用户基本信息")
public ApiResponse<UserDTO> updateUserInfo(
@RequestHeader("X-User-Id") Long userId,
@Valid @RequestBody UserUpdateDTO updateDTO) {
UserDTO userDTO = userService.updateUserInfo(userId, updateDTO);
return ApiResponse.success("更新成功", userDTO);
}
@PutMapping("/password")
@Operation(summary = "修改密码", description = "修改登录密码")
public ApiResponse<Void> changePassword(
@RequestHeader("X-User-Id") Long userId,
@Valid @RequestBody ChangePasswordDTO changePasswordDTO) {
userService.changePassword(userId, changePasswordDTO);
return ApiResponse.success("密码修改成功");
}
@GetMapping("/list")
@Operation(summary = "分页查询用户", description = "管理员分页查询用户列表")
public ApiResponse<PageResult<UserDTO>> listUsers(
@Valid PageRequest pageRequest,
UserQueryDTO queryDTO) {
PageResult<UserDTO> result = userService.listUsers(pageRequest, queryDTO);
return ApiResponse.success(result);
}
@GetMapping("/check/username")
@Operation(summary = "检查用户名", description = "检查用户名是否已存在")
public ApiResponse<Boolean> checkUsername(@RequestParam String username) {
boolean exists = userService.checkUsername(username);
return ApiResponse.success(!exists);
}
@GetMapping("/check/phone")
@Operation(summary = "检查手机号", description = "检查手机号是否已存在")
public ApiResponse<Boolean> checkPhone(@RequestParam String phone) {
boolean exists = userService.checkPhone(phone);
return ApiResponse.success(!exists);
}
} 5. JWT工具类:
java package com.ecommerce.user.util;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
import java.security.Key; import java.util.Date; import java.util.Map;
@Slf4j @Component public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
// 生成token
public static String generateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// 从token中获取用户名
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
// 从token中获取过期时间
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
// 从token中获取指定claim
public <T> T getClaimFromToken(String token, java.util.function.Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
// 从token中获取所有claims
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证token是否过期
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
// 验证token
public Boolean validateToken(String token, String username) {
final String usernameFromToken = getUsernameFromToken(token);
return (username.equals(usernameFromToken) && !isTokenExpired(token));
}
}
第四章:商品服务实现
4.1 商品服务核心功能 商品服务pom.xml:
xml
com.ecommerce common-core ${project.version}
<!– Elasticsearch –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!– 缓存 –>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!– 消息队列 –>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<!– Seata分布式事务 –>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
商品服务核心代码:
java // 商品实体 package com.ecommerce.product.entity;
import com.baomidou.mybatisplus.annotation.*; import lombok.Data;
import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List;
@Data @TableName(“tb_product”) public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String spuCode;
private String name;
private String subTitle;
private Long categoryId;
private Long brandId;
private String mainImage;
@TableField(typeHandler = JsonTypeHandler.class)
private List<String> images;
private String detail;
@TableField(typeHandler = JsonTypeHandler.class)
private List<ProductSpec> specifications;
private BigDecimal price;
private BigDecimal marketPrice;
private BigDecimal costPrice;
private Integer stock;
private Integer sales;
private Integer status;
private BigDecimal weight;
private BigDecimal volume;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
// 商品规格 @Data public class ProductSpec { private String name; private String value; private String unit; }
// JSON类型处理器 public class JsonTypeHandler extends BaseTypeHandler {
private final Class<T> type;
private final ObjectMapper objectMapper;
public JsonTypeHandler(Class<T> type) {
this.type = type;
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
try {
String json = objectMapper.writeValueAsString(parameter);
ps.setString(i, json);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON序列化失败", e);
}
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private T parseJson(String json) {
if (StringUtils.isBlank(json)) {
return null;
}
try {
return objectMapper.readValue(json, type);
} catch (IOException e) {
throw new RuntimeException("JSON反序列化失败", e);
}
}
}
// 商品Mapper package com.ecommerce.product.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.product.entity.Product; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update;
@Mapper public interface ProductMapper extends BaseMapper {
@Select("SELECT * FROM tb_product WHERE id = #{id} AND status = 1 AND deleted = 0")
Product selectAvailableById(Long id);
@Update("UPDATE tb_product SET stock = stock – #{quantity}, sales = sales + #{quantity} WHERE id = #{productId} AND stock >= #{quantity}")
int deductStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
@Update("UPDATE tb_product SET stock = stock + #{quantity} WHERE id = #{productId}")
int addStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
} 2. 商品搜索(Elasticsearch集成):
java // Elasticsearch商品文档 package com.ecommerce.product.document;
import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType;
import java.math.BigDecimal; import java.util.List; import java.util.Map;
@Data @Document(indexName = “products”) public class ProductDocument {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String subTitle;
@Field(type = FieldType.Long)
private Long categoryId;
@Field(type = FieldType.Long)
private Long brandId;
@Field(type = FieldType.Keyword)
private String spuCode;
@Field(type = FieldType.Text)
private String mainImage;
@Field(type = FieldType.Double)
private BigDecimal price;
@Field(type = FieldType.Integer)
private Integer stock;
@Field(type = FieldType.Integer)
private Integer sales;
@Field(type = FieldType.Integer)
private Integer status;
@Field(type = FieldType.Nested)
private List<ProductSpecDocument> specifications;
@Field(type = FieldType.Object)
private Map<String, Object> attributes;
@Field(type = FieldType.Date)
private Long createTime;
}
// 商品规格文档 @Data public class ProductSpecDocument {
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String value;
@Field(type = FieldType.Keyword)
private String unit;
}
// 商品搜索Repository package com.ecommerce.product.repository;
import com.ecommerce.product.document.ProductDocument; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ProductSearchRepository extends ElasticsearchRepository<ProductDocument, Long> {
// 根据名称搜索
Page<ProductDocument> findByName(String name, Pageable pageable);
// 根据分类ID搜索
Page<ProductDocument> findByCategoryId(Long categoryId, Pageable pageable);
// 价格范围搜索
Page<ProductDocument> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable);
// 复合条件搜索
@Query("{\\"bool\\": {\\"must\\": [" +
"{\\"match\\": {\\"name\\": \\"?0\\"}}," +
"{\\"range\\": {\\"price\\": {\\"gte\\": ?1, \\"lte\\": ?2}}}" +
"]}}")
Page<ProductDocument> searchByNameAndPriceRange(String name, BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable);
}
// 商品搜索服务 package com.ecommerce.product.service;
import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.product.dto.ProductSearchDTO; import com.ecommerce.product.document.ProductDocument;
public interface ProductSearchService {
// 创建索引
boolean createIndex();
// 添加商品到索引
void addProductToIndex(ProductDocument product);
// 从索引中删除商品
void removeProductFromIndex(Long productId);
// 搜索商品
PageResult<ProductDocument> searchProducts(ProductSearchDTO searchDTO, PageRequest pageRequest);
// 根据ID批量查询
List<ProductDocument> findByIds(List<Long> ids);
// 更新商品索引
void updateProductIndex(ProductDocument product);
} 3. 商品缓存策略(Redisson分布式锁):
java // 商品缓存服务 package com.ecommerce.product.service;
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Slf4j @Service @RequiredArgsConstructor public class ProductCacheService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedissonClient redissonClient;
// 商品缓存key
private static final String PRODUCT_KEY_PREFIX = "product:";
private static final String PRODUCT_STOCK_KEY_PREFIX = "product:stock:";
private static final String PRODUCT_LOCK_KEY_PREFIX = "product:lock:";
// 获取商品信息(带缓存)
public ProductDTO getProductWithCache(Long productId) {
String cacheKey = PRODUCT_KEY_PREFIX + productId;
// 1. 从缓存获取
ProductDTO product = (ProductDTO) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 2. 获取分布式锁
String lockKey = PRODUCT_LOCK_KEY_PREFIX + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待5秒,锁超时时间10秒
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("获取商品信息超时");
}
// 3. 双重检查缓存
product = (ProductDTO) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 4. 从数据库获取
product = productService.getProductById(productId);
if (product == null) {
// 缓存空值,防止缓存穿透
redisTemplate.opsForValue().set(cacheKey, "", 5, TimeUnit.MINUTES);
return null;
}
// 5. 放入缓存
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
// 6. 缓存库存到Redis(用于秒杀等场景)
cacheStock(productId, product.getStock());
return product;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取商品信息中断", e);
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 缓存库存
private void cacheStock(Long productId, Integer stock) {
String stockKey = PRODUCT_STOCK_KEY_PREFIX + productId;
redisTemplate.opsForValue().set(stockKey, stock, 1, TimeUnit.HOURS);
}
// 扣减库存(Redis预减库存)
public boolean deductStock(Long productId, Integer quantity) {
String stockKey = PRODUCT_STOCK_KEY_PREFIX + productId;
String lockKey = PRODUCT_LOCK_KEY_PREFIX + productId + ":stock";
RLock lock = redissonClient.getLock(lockKey);
try {
// 加锁
boolean locked = lock.tryLock(3, 5, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 获取当前库存
Object stockObj = redisTemplate.opsForValue().get(stockKey);
if (stockObj == null) {
// 库存未缓存,从数据库获取
Integer dbStock = productService.getProductStock(productId);
redisTemplate.opsForValue().set(stockKey, dbStock, 1, TimeUnit.HOURS);
stockObj = dbStock;
}
Integer currentStock = (Integer) stockObj;
if (currentStock < quantity) {
return false; // 库存不足
}
// 预减库存
Long newStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
// 异步更新数据库
asyncUpdateDbStock(productId, quantity);
return newStock != null && newStock >= 0;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 异步更新数据库库存
private void asyncUpdateDbStock(Long productId, Integer quantity) {
CompletableFuture.runAsync(() -> {
try {
productMapper.deductStock(productId, quantity);
} catch (Exception e) {
log.error("异步更新库存失败: productId={}, quantity={}", productId, quantity, e);
// 失败时回滚Redis库存
rollbackStock(productId, quantity);
}
});
}
// 回滚库存
private void rollbackStock(Long productId, Integer quantity) {
String stockKey = PRODUCT_STOCK_KEY_PREFIX + productId;
redisTemplate.opsForValue().increment(stockKey, quantity);
}
}
第五章:订单服务与分布式事务
5.1 订单服务核心功能 订单状态机设计:
java // 订单状态枚举 package com.ecommerce.order.enums;
import lombok.Getter;
@Getter public enum OrderStatus {
PENDING_PAYMENT(1, "待付款") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return targetStatus == PAID || targetStatus == CANCELLED;
}
},
PAID(2, "已付款") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return targetStatus == SHIPPED || targetStatus == REFUNDING;
}
},
SHIPPED(3, "已发货") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return targetStatus == RECEIVED || targetStatus == REFUNDING;
}
},
RECEIVED(4, "已完成") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return targetStatus == REFUNDED;
}
},
CANCELLED(5, "已取消") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return false;
}
},
REFUNDING(6, "退款中") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return targetStatus == REFUNDED || targetStatus == PAID;
}
},
REFUNDED(7, "已退款") {
@Override
public boolean canChangeTo(OrderStatus targetStatus) {
return false;
}
};
private final Integer code;
private final String description;
OrderStatus(Integer code, String description) {
this.code = code;
this.description = description;
}
// 检查是否可以转换到目标状态
public abstract boolean canChangeTo(OrderStatus targetStatus);
// 根据code获取枚举
public static OrderStatus getByCode(Integer code) {
for (OrderStatus status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("无效的订单状态码: " + code);
}
}
// 订单状态机 package com.ecommerce.order.service.state;
import com.ecommerce.order.enums.OrderStatus; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;
@Slf4j @Component public class OrderStateMachine {
/**
* 检查订单状态是否可以转换
*/
public boolean canChangeStatus(OrderStatus currentStatus, OrderStatus targetStatus) {
if (currentStatus == null || targetStatus == null) {
return false;
}
return currentStatus.canChangeTo(targetStatus);
}
/**
* 执行状态转换
*/
public void changeStatus(Order order, OrderStatus targetStatus, String operator, String remark) {
OrderStatus currentStatus = OrderStatus.getByCode(order.getStatus());
if (!canChangeStatus(currentStatus, targetStatus)) {
throw new BusinessException(
String.format("订单状态不能从[%s]转换到[%s]",
currentStatus.getDescription(),
targetStatus.getDescription()));
}
// 更新订单状态
order.setStatus(targetStatus.getCode());
// 记录状态变更日志
OrderStatusLog statusLog = new OrderStatusLog();
statusLog.setOrderId(order.getId());
statusLog.setFromStatus(currentStatus.getCode());
statusLog.setToStatus(targetStatus.getCode());
statusLog.setOperator(operator);
statusLog.setRemark(remark);
statusLog.setCreateTime(LocalDateTime.now());
orderStatusLogMapper.insert(statusLog);
log.info("订单状态变更: orderId={}, from={}, to={}, operator={}",
order.getId(),
currentStatus.getDescription(),
targetStatus.getDescription(),
operator);
// 发布状态变更事件
publishStatusChangeEvent(order, currentStatus, targetStatus);
}
/**
* 发布状态变更事件
*/
private void publishStatusChangeEvent(Order order, OrderStatus fromStatus, OrderStatus toStatus) {
OrderStatusChangeEvent event = new OrderStatusChangeEvent();
event.setOrderId(order.getId());
event.setOrderNo(order.getOrderNo());
event.setUserId(order.getUserId());
event.setFromStatus(fromStatus);
event.setToStatus(toStatus);
event.setChangeTime(LocalDateTime.now());
applicationEventPublisher.publishEvent(event);
}
} 5.2 Seata分布式事务集成 Seata配置:
yaml
seata配置
seata: enabled: true application-id: ${spring.application.name} tx-service-group: default_tx_group enable-auto-data-source-proxy: true config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: ${spring.cloud.nacos.discovery.namespace} group: SEATA_GROUP username: nacos password: nacos registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: ${spring.cloud.nacos.discovery.namespace} group: SEATA_GROUP username: nacos password: nacos service: vgroup-mapping: default_tx_group: default disable-global-transaction: false 创建订单(分布式事务):
java // 订单服务 package com.ecommerce.order.service;
import com.ecommerce.order.dto.CreateOrderDTO; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;
@Slf4j @Service @RequiredArgsConstructor public class OrderService {
private final OrderMapper orderMapper;
private final OrderItemMapper orderItemMapper;
private final InventoryService inventoryService;
private final CouponService couponService;
private final ProductService productService;
/**
* 创建订单(分布式事务)
*/
@GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
public OrderDTO createOrder(CreateOrderDTO createOrderDTO) {
log.info("开始创建订单: userId={}, items={}",
createOrderDTO.getUserId(),
createOrderDTO.getItems().size());
// 1. 验证商品信息
validateProducts(createOrderDTO.getItems());
// 2. 生成订单号
String orderNo = generateOrderNo();
// 3. 计算订单金额
OrderAmount orderAmount = calculateOrderAmount(createOrderDTO);
// 4. 创建订单
Order order = createOrderEntity(createOrderDTO, orderNo, orderAmount);
orderMapper.insert(order);
// 5. 创建订单商品
List<OrderItem> orderItems = createOrderItems(order.getId(), createOrderDTO.getItems());
orderItemMapper.insertBatch(orderItems);
// 6. 扣减库存(调用库存服务)
inventoryService.deductStock(createOrderDTO.getItems());
// 7. 使用优惠券(调用优惠券服务)
if (createOrderDTO.getCouponId() != null) {
couponService.useCoupon(createOrderDTO.getUserId(), createOrderDTO.getCouponId());
}
// 8. 发送创建订单消息
sendOrderCreatedMessage(order);
log.info("订单创建成功: orderId={}, orderNo={}", order.getId(), order.getOrderNo());
return convertToDTO(order);
}
/**
* 验证商品信息
*/
private void validateProducts(List<CreateOrderItemDTO> items) {
for (CreateOrderItemDTO item : items) {
ProductDTO product = productService.getProductById(item.getProductId());
if (product == null) {
throw new BusinessException("商品不存在: " + item.getProductId());
}
if (product.getStatus() != 1) {
throw new BusinessException("商品已下架: " + product.getName());
}
if (product.getStock() < item.getQuantity()) {
throw new BusinessException("商品库存不足: " + product.getName());
}
}
}
/**
* 生成订单号
*/
private String generateOrderNo() {
// 时间戳 + 随机数
return "O" + System.currentTimeMillis() +
RandomUtil.randomNumbers(6);
}
/**
* 计算订单金额
*/
private OrderAmount calculateOrderAmount(CreateOrderDTO createOrderDTO) {
BigDecimal totalAmount = BigDecimal.ZERO;
for (CreateOrderItemDTO item : createOrderDTO.getItems()) {
ProductDTO product = productService.getProductById(item.getProductId());
BigDecimal itemAmount = product.getPrice().multiply(
BigDecimal.valueOf(item.getQuantity()));
totalAmount = totalAmount.add(itemAmount);
}
// 计算运费
BigDecimal shippingAmount = calculateShipping(createOrderDTO);
// 计算优惠金额
BigDecimal discountAmount = calculateDiscount(createOrderDTO);
// 实付金额 = 商品总额 + 运费 – 优惠
BigDecimal payAmount = totalAmount.add(shippingAmount).subtract(discountAmount);
OrderAmount orderAmount = new OrderAmount();
orderAmount.setTotalAmount(totalAmount);
orderAmount.setShippingAmount(shippingAmount);
orderAmount.setDiscountAmount(discountAmount);
orderAmount.setPayAmount(payAmount);
return orderAmount;
}
}
// 库存服务(扣减库存) package com.ecommerce.inventory.service;
import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;
@Slf4j @Service @LocalTCC public class InventoryService {
/**
* TCC模式扣减库存
*/
@TwoPhaseBusinessAction(name = "deductStock", commitMethod = "commitDeductStock",
rollbackMethod = "rollbackDeductStock")
public boolean deductStockPrepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "items")
List<CreateOrderItemDTO> items) {
log.info("TCC第一阶段:预扣库存, xid={}", actionContext.getXid());
Map<Long, Integer> lockMap = new HashMap<>();
try {
for (CreateOrderItemDTO item : items) {
// 获取分布式锁
String lockKey = "inventory:lock:" + item.getProductId();
RLock lock = redissonClient.getLock(lockKey);
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("锁定库存失败");
}
// 预扣库存
int affected = inventoryMapper.lockStock(
item.getProductId(),
item.getQuantity(),
actionContext.getXid());
if (affected == 0) {
throw new BusinessException("库存不足");
}
lockMap.put(item.getProductId(), item.getQuantity());
// 记录库存锁定日志
InventoryLockLog lockLog = new InventoryLockLog();
lockLog.setProductId(item.getProductId());
lockLog.setOrderXid(actionContext.getXid());
lockLog.setQuantity(item.getQuantity());
lockLog.setStatus(0); // 锁定状态
lockLog.setCreateTime(LocalDateTime.now());
inventoryLockLogMapper.insert(lockLog);
}
// 将锁定信息保存到上下文
actionContext.setActionContext("lockMap", lockMap);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("锁定库存中断", e);
}
}
/**
* TCC第二阶段提交
*/
public boolean commitDeductStock(BusinessActionContext actionContext) {
log.info("TCC第二阶段提交:确认扣减库存, xid={}", actionContext.getXid());
// 更新库存锁定状态为已确认
inventoryLockLogMapper.updateStatusByXid(actionContext.getXid(), 1);
// 实际扣减库存
Map<Long, Integer> lockMap = (Map<Long, Integer>)
actionContext.getActionContext("lockMap");
for (Map.Entry<Long, Integer> entry : lockMap.entrySet()) {
inventoryMapper.confirmDeductStock(entry.getKey(), entry.getValue());
}
return true;
}
/**
* TCC第二阶段回滚
*/
public boolean rollbackDeductStock(BusinessActionContext actionContext) {
log.info("TCC第二阶段回滚:释放库存, xid={}", actionContext.getXid());
// 更新库存锁定状态为已回滚
inventoryLockLogMapper.updateStatusByXid(actionContext.getXid(), 2);
// 释放锁定的库存
Map<Long, Integer> lockMap = (Map<Long, Integer>)
actionContext.getActionContext("lockMap");
for (Map.Entry<Long, Integer> entry : lockMap.entrySet()) {
inventoryMapper.releaseLockedStock(entry.getKey(), entry.getValue());
}
return true;
}
}
// 库存Mapper @Mapper public interface InventoryMapper {
// 锁定库存
@Update("UPDATE tb_inventory SET locked_stock = locked_stock + #{quantity}, " +
"available_stock = available_stock – #{quantity} " +
"WHERE product_id = #{productId} AND available_stock >= #{quantity}")
int lockStock(@Param("productId") Long productId,
@Param("quantity") Integer quantity,
@Param("xid") String xid);
// 确认扣减库存
@Update("UPDATE tb_inventory SET locked_stock = locked_stock – #{quantity}, " +
"total_stock = total_stock – #{quantity} " +
"WHERE product_id = #{productId} AND locked_stock >= #{quantity}")
int confirmDeductStock(@Param("productId") Long productId,
@Param("quantity") Integer quantity);
// 释放锁定的库存
@Update("UPDATE tb_inventory SET locked_stock = locked_stock – #{quantity}, " +
"available_stock = available_stock + #{quantity} " +
"WHERE product_id = #{productId} AND locked_stock >= #{quantity}")
int releaseLockedStock(@Param("productId") Long productId,
@Param("quantity") Integer quantity);
} 5.3 订单超时取消(RocketMQ延迟消息) 订单超时取消实现:
java // 订单超时服务 package com.ecommerce.order.service;
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit;
@Slf4j @Service @RequiredArgsConstructor public class OrderTimeoutService {
private final RocketMQTemplate rocketMQTemplate;
private final OrderMapper orderMapper;
private final DelayQueue<OrderTimeoutTask> timeoutQueue = new DelayQueue<>();
/**
* 启动订单超时检查
*/
@PostConstruct
public void init() {
// 启动后台线程处理超时订单
new Thread(this::processTimeoutOrders).start();
// 从数据库加载待支付的超时订单
loadPendingOrders();
}
/**
* 添加订单超时任务
*/
public void addOrderTimeoutTask(Long orderId, LocalDateTime createTime) {
// 计算延迟时间(30分钟)
long delay = TimeUnit.MINUTES.toMillis(30) –
System.currentTimeMillis() –
createTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
if (delay > 0) {
OrderTimeoutTask task = new OrderTimeoutTask(orderId, delay);
timeoutQueue.put(task);
log.info("添加订单超时任务: orderId={}, delay={}ms", orderId, delay);
}
}
/**
* 处理超时订单
*/
private void processTimeoutOrders() {
while (!Thread.currentThread().isInterrupted()) {
try {
OrderTimeoutTask task = timeoutQueue.take();
cancelTimeoutOrder(task.getOrderId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("订单超时处理线程中断", e);
}
}
}
/**
* 取消超时订单
*/
private void cancelTimeoutOrder(Long orderId) {
try {
Order order = orderMapper.selectById(orderId);
if (order == null || order.getStatus() != OrderStatus.PENDING_PAYMENT.getCode()) {
return;
}
// 检查是否真的超时(防止重复处理)
LocalDateTime now = LocalDateTime.now();
LocalDateTime createTime = order.getCreateTime();
long minutes = Duration.between(createTime, now).toMinutes();
if (minutes >= 30) {
log.info("取消超时订单: orderId={}, createTime={}", orderId, createTime);
// 更新订单状态为已取消
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setCancelTime(now);
order.setCancelReason("超时未支付");
orderMapper.updateById(order);
// 释放库存
releaseOrderStock(orderId);
// 发送订单取消消息
sendOrderCanceledMessage(order);
}
} catch (Exception e) {
log.error("取消超时订单失败: orderId={}", orderId, e);
}
}
/**
* 发送延迟消息(RocketMQ方式)
*/
public void sendOrderTimeoutMessage(Long orderId) {
// 发送30分钟后的延迟消息
rocketMQTemplate.syncSend("ORDER_TIMEOUT_TOPIC",
MessageBuilder.withPayload(orderId).build(),
TimeUnit.MINUTES.toMillis(30),
TimeUnit.MILLISECONDS);
log.info("发送订单超时延迟消息: orderId={}", orderId);
}
/**
* 监听订单超时消息
*/
@RocketMQMessageListener(
topic = "ORDER_TIMEOUT_TOPIC",
consumerGroup = "order-timeout-consumer"
)
@Slf4j
@Component
public class OrderTimeoutConsumer implements RocketMQListener<Long> {
@Override
public void onMessage(Long orderId) {
log.info("收到订单超时消息: orderId={}", orderId);
cancelTimeoutOrder(orderId);
}
}
}
// 订单超时任务 @Data class OrderTimeoutTask implements Delayed {
private final Long orderId;
private final long expireTime;
public OrderTimeoutTask(Long orderId, long delay) {
this.orderId = orderId;
this.expireTime = System.currentTimeMillis() + delay;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expireTime – System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.expireTime, ((OrderTimeoutTask) o).expireTime);
}
}
第六章:支付服务与消息队列
6.1 支付服务集成 支付服务配置:
java // 支付服务接口 package com.ecommerce.payment.service;
import com.ecommerce.payment.dto.*;
public interface PaymentService {
// 创建支付
PaymentDTO createPayment(CreatePaymentDTO createPaymentDTO);
// 查询支付状态
PaymentDTO queryPayment(String paymentNo);
// 支付回调处理
PaymentCallbackResult handlePaymentCallback(PaymentCallbackDTO callbackDTO);
// 退款
RefundDTO refund(RefundRequest refundRequest);
// 查询退款状态
RefundDTO queryRefund(String refundNo);
}
// 支付策略接口 package com.ecommerce.payment.strategy;
public interface PaymentStrategy {
// 支付类型
PaymentType getPaymentType();
// 创建支付
PaymentDTO createPayment(CreatePaymentDTO createPaymentDTO);
// 查询支付
PaymentDTO queryPayment(String paymentNo);
// 处理回调
PaymentCallbackResult handleCallback(PaymentCallbackDTO callbackDTO);
// 退款
RefundDTO refund(RefundRequest refundRequest);
}
// 微信支付实现 @Service @Slf4j public class WechatPaymentStrategy implements PaymentStrategy {
@Override
public PaymentType getPaymentType() {
return PaymentType.WECHAT;
}
@Override
public PaymentDTO createPayment(CreatePaymentDTO createPaymentDTO) {
log.info("微信支付创建: orderNo={}, amount={}",
createPaymentDTO.getOrderNo(),
createPaymentDTO.getAmount());
// 调用微信支付API
WechatPaymentRequest request = buildWechatRequest(createPaymentDTO);
WechatPaymentResponse response = wechatClient.createPayment(request);
// 保存支付记录
Payment payment = savePaymentRecord(createPaymentDTO, response);
PaymentDTO paymentDTO = convertToDTO(payment);
paymentDTO.setPayData(response.getPayData()); // 支付参数(用于前端调起支付)
return paymentDTO;
}
@Override
public PaymentCallbackResult handleCallback(PaymentCallbackDTO callbackDTO) {
log.info("处理微信支付回调: {}", callbackDTO);
// 验证签名
if (!verifySignature(callbackDTO)) {
throw new BusinessException("签名验证失败");
}
// 解析回调数据
WechatCallbackData callbackData = parseCallbackData(callbackDTO);
// 更新支付状态
Payment payment = updatePaymentStatus(callbackData);
// 发送支付成功消息
if (payment.getStatus() == PaymentStatus.SUCCESS.getCode()) {
sendPaymentSuccessMessage(payment);
}
return buildCallbackResult(callbackData);
}
private WechatPaymentRequest buildWechatRequest(CreatePaymentDTO createPaymentDTO) {
WechatPaymentRequest request = new WechatPaymentRequest();
request.setAppId(wechatConfig.getAppId());
request.setMchId(wechatConfig.getMchId());
request.setNonceStr(RandomUtil.randomString(32));
request.setBody("商品购买-" + createPaymentDTO.getOrderNo());
request.setOutTradeNo(createPaymentDTO.getPaymentNo());
request.setTotalFee(createPaymentDTO.getAmount().multiply(new BigDecimal(100)).intValue());
request.setSpbillCreateIp(createPaymentDTO.getClientIp());
request.setNotifyUrl(wechatConfig.getNotifyUrl());
request.setTradeType("JSAPI");
request.setOpenid(createPaymentDTO.getOpenId());
// 生成签名
String sign = generateSignature(request);
request.setSign(sign);
return request;
}
} 支付状态同步(消息队列):
java // 支付成功消息生产者 @Service @Slf4j @RequiredArgsConstructor public class PaymentMessageProducer {
private final RocketMQTemplate rocketMQTemplate;
/**
* 发送支付成功消息
*/
public void sendPaymentSuccessMessage(Payment payment) {
PaymentSuccessMessage message = new PaymentSuccessMessage();
message.setPaymentId(payment.getId());
message.setOrderNo(payment.getOrderNo());
message.setPaymentNo(payment.getPaymentNo());
message.setAmount(payment.getAmount());
message.setPaymentTime(payment.getPaymentTime());
message.setPaymentType(payment.getPaymentType());
// 发送顺序消息,确保同一个订单的消息有序处理
rocketMQTemplate.syncSendOrderly(
"PAYMENT_SUCCESS_TOPIC",
message,
payment.getOrderNo()
);
log.info("发送支付成功消息: paymentId={}, orderNo={}",
payment.getId(), payment.getOrderNo());
}
/**
* 发送支付失败消息
*/
public void sendPaymentFailedMessage(Payment payment, String failReason) {
PaymentFailedMessage message = new PaymentFailedMessage();
message.setPaymentId(payment.getId());
message.setOrderNo(payment.getOrderNo());
message.setPaymentNo(payment.getPaymentNo());
message.setAmount(payment.getAmount());
message.setFailReason(failReason);
message.setFailTime(LocalDateTime.now());
rocketMQTemplate.syncSend("PAYMENT_FAILED_TOPIC", message);
log.info("发送支付失败消息: paymentId={}, orderNo={}, reason={}",
payment.getId(), payment.getOrderNo(), failReason);
}
}
// 订单服务消费者 @Slf4j @Component @RocketMQMessageListener( topic = “PAYMENT_SUCCESS_TOPIC”, consumerGroup = “order-payment-consumer”, consumeMode = ConsumeMode.ORDERLY, // 顺序消费 messageModel = MessageModel.CLUSTERING ) public class PaymentSuccessConsumer implements RocketMQListener {
private final OrderService orderService;
@Override
public void onMessage(PaymentSuccessMessage message) {
log.info("收到支付成功消息: orderNo={}, paymentNo={}",
message.getOrderNo(), message.getPaymentNo());
try {
// 更新订单状态为已支付
orderService.updateOrderStatus(
message.getOrderNo(),
OrderStatus.PAID,
"系统",
"支付成功"
);
// 发送订单支付成功通知
sendOrderPaidNotification(message);
} catch (Exception e) {
log.error("处理支付成功消息失败: orderNo={}", message.getOrderNo(), e);
// 记录失败,人工干预或重试
}
}
}
// 库存服务消费者 @Slf4j @Component @RocketMQMessageListener( topic = “PAYMENT_SUCCESS_TOPIC”, consumerGroup = “inventory-payment-consumer”, consumeMode = ConsumeMode.ORDERLY, messageModel = MessageModel.CLUSTERING ) public class InventoryPaymentConsumer implements RocketMQListener {
private final InventoryService inventoryService;
@Override
public void onMessage(PaymentSuccessMessage message) {
log.info("库存服务收到支付成功消息: orderNo={}", message.getOrderNo());
try {
// 确认扣减库存(从锁定状态转为已售出)
inventoryService.confirmStock(message.getOrderNo());
} catch (Exception e) {
log.error("库存服务处理支付消息失败: orderNo={}", message.getOrderNo(), e);
// 发送库存处理失败消息,触发补偿机制
sendInventoryProcessFailedMessage(message, e.getMessage());
}
}
}
第七章:网关与安全认证
7.1 Spring Cloud Gateway网关配置 网关路由配置:
yaml spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true
routes:
# 用户服务
– id: user-service
uri: lb://user-service
predicates:
– Path=/api/user/**
filters:
– StripPrefix=1
– name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"
# 商品服务
– id: product-service
uri: lb://product-service
predicates:
– Path=/api/product/**
filters:
– StripPrefix=1
– name: CircuitBreaker
args:
name: productService
fallbackUri: forward:/fallback/product
# 订单服务
– id: order-service
uri: lb://order-service
predicates:
– Path=/api/order/**
– Method=POST,PUT,GET
filters:
– StripPrefix=1
– name: AuthFilter
# 支付服务
– id: payment-service
uri: lb://payment-service
predicates:
– Path=/api/payment/**
filters:
– StripPrefix=1
– name: AuthFilter
# 认证服务
– id: auth-service
uri: lb://auth-service
predicates:
– Path=/api/auth/**
filters:
– StripPrefix=1
# 静态资源
– id: static-resource
uri: lb://user-service
predicates:
– Path=/static/**
# 管理端点
– id: actuator
uri: lb://${spring.application.name}
predicates:
– Path=/actuator/**
filters:
– name: AuthFilter
args:
require-role: ADMIN
网关限流配置
redis: rate-limiter: enabled: true replenish-rate: 10 # 每秒令牌数 burst-capacity: 20 # 令牌桶容量
网关断路器配置
resilience4j: circuitbreaker: instances: productService: sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 wait-duration-in-open-state: 5s failure-rate-threshold: 50 网关过滤器:
java // 认证过滤器 @Component @Slf4j public class AuthFilter implements GlobalFilter, Ordered {
private final JwtUtil jwtUtil;
private final RedisTemplate<String, Object> redisTemplate;
// 白名单路径
private static final List<String> WHITE_LIST = Arrays.asList(
"/api/auth/login",
"/api/auth/register",
"/api/product/list",
"/api/product/detail",
"/static/",
"/actuator/health"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
String method = request.getMethod().name();
// 检查是否在白名单
if (isWhiteList(path, method)) {
return chain.filter(exchange);
}
// 获取token
String token = getToken(request);
if (StringUtils.isBlank(token)) {
return unauthorized(exchange, "缺少认证令牌");
}
// 验证token
if (!jwtUtil.validateToken(token)) {
return unauthorized(exchange, "令牌无效或已过期");
}
// 检查token是否在黑名单中
if (isTokenBlacklisted(token)) {
return unauthorized(exchange, "令牌已失效");
}
// 获取用户信息
UserInfo userInfo = jwtUtil.getUserInfo(token);
// 检查权限
if (!hasPermission(userInfo, path, method)) {
return forbidden(exchange, "没有访问权限");
}
// 将用户信息添加到请求头
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", String.valueOf(userInfo.getUserId()))
.header("X-User-Name", userInfo.getUsername())
.header("X-User-Roles", String.join(",", userInfo.getRoles()))
.build();
// 记录访问日志
logAccess(request, userInfo);
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
private boolean isWhiteList(String path, String method) {
return WHITE_LIST.stream().anyMatch(path::startsWith) ||
(path.startsWith("/api/product/") && "GET".equals(method));
}
private String getToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return request.getQueryParams().getFirst("token");
}
private boolean isTokenBlacklisted(String token) {
String key = "blacklist:token:" + DigestUtils.md5Hex(token);
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
private boolean hasPermission(UserInfo userInfo, String path, String method) {
// 简单的权限检查,实际项目中可以使用RBAC模型
if (path.startsWith("/api/admin/") && !userInfo.getRoles().contains("ADMIN")) {
return false;
}
if (path.startsWith("/api/order/") && method.equals("POST") &&
!userInfo.getRoles().contains("USER")) {
return false;
}
return true;
}
private void logAccess(ServerHttpRequest request, UserInfo userInfo) {
String logMsg = String.format("网关访问: user=%s, method=%s, path=%s, ip=%s",
userInfo.getUsername(),
request.getMethod(),
request.getPath(),
request.getRemoteAddress());
if (log.isDebugEnabled()) {
log.debug(logMsg);
}
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
ApiResponse<?> apiResponse = ApiResponse.error(401, message);
byte[] bytes = JsonUtil.toJson(apiResponse).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
ApiResponse<?> apiResponse = ApiResponse.error(403, message);
byte[] bytes = JsonUtil.toJson(apiResponse).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
// 限流Key解析器 @Component public class UserKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
// 根据用户ID限流
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
if (StringUtils.isNotBlank(userId)) {
return Mono.just(userId);
}
// 根据IP限流
String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
.getAddress().getHostAddress();
return Mono.just(ip);
}
}
第八章:监控与部署
8.1 完整的监控体系 Prometheus配置:
yaml
prometheus.yml
global: scrape_interval: 15s evaluation_interval: 15s
alerting: alertmanagers: – static_configs: – targets: [‘alertmanager:9093’]
rule_files:
- “alert_rules.yml”
scrape_configs:
-
job_name: ‘spring-boot-apps’ metrics_path: ‘/actuator/prometheus’ scrape_interval: 10s static_configs:
- targets:
- ‘gateway-service:8080’
- ‘user-service:8082’
- ‘product-service:8083’
- ‘order-service:8085’
- ‘payment-service:8086’ labels: group: ‘e-commerce-services’
- targets:
-
job_name: ‘nacos’ static_configs:
- targets: [‘nacos:8848’]
-
job_name: ‘redis’ static_configs:
- targets: [‘redis-exporter:9121’]
-
job_name: ‘mysql’ static_configs:
- targets: [‘mysqld-exporter:9104’]
-
job_name: ‘rocketmq’ static_configs:
- targets: [‘rocketmq-exporter:5557’]
告警规则
alert_rules.yml
groups:
- name: e-commerce-alerts rules:
-
alert: HighErrorRate expr: rate(http_server_requests_seconds_count{status!~“2…”}[5m]) > 0.1 for: 1m labels: severity: warning annotations: summary: “高错误率报警” description: “{{ $labels.instance }} 错误率超过10%”
-
alert: HighLatency expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1 for: 2m labels: severity: warning annotations: summary: “高延迟报警” description: “{{ $labels.instance }} 95分位延迟超过1秒”
-
alert: ServiceDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: “服务下线报警” description: “{{ $labels.instance }} 服务已下线”
-
alert: HighMemoryUsage expr: (sum(jvm_memory_used_bytes) by (instance) / sum(jvm_memory_max_bytes) by (instance)) * 100 > 80 for: 5m labels: severity: warning annotations: summary: “高内存使用率” description: “{{ $labels.instance }} 内存使用率超过80%”
-
alert: HighCPUUsage expr: process_cpu_usage * 100 > 80 for: 5m labels: severity: warning annotations: summary: “高CPU使用率” description: “{{ $labels.instance }} CPU使用率超过80%” Spring Boot Actuator指标:
-
java // 自定义业务指标 @Component public class BusinessMetrics {
private final MeterRegistry meterRegistry;
// 订单相关指标
private final Counter orderCreatedCounter;
private final Counter orderPaidCounter;
private final Counter orderCanceledCounter;
private final Timer orderProcessTimer;
// 支付相关指标
private final Counter paymentSuccessCounter;
private final Counter paymentFailedCounter;
private final DistributionSummary paymentAmountSummary;
// 用户相关指标
private final Gauge activeUsersGauge;
private final AtomicInteger activeUsers = new AtomicInteger(0);
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 初始化计数器
this.orderCreatedCounter = Counter.builder("ecommerce.orders.created")
.description("创建订单总数")
.tag("application", "order-service")
.register(meterRegistry);
this.orderPaidCounter = Counter.builder("ecommerce.orders.paid")
.description("支付成功订单数")
.tag("application", "order-service")
.register(meterRegistry);
this.orderCanceledCounter = Counter.builder("ecommerce.orders.canceled")
.description("取消订单数")
.tag("application", "order-service")
.register(meterRegistry);
// 初始化计时器
this.orderProcessTimer = Timer.builder("ecommerce.orders.process.time")
.description("订单处理时间")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
// 初始化支付指标
this.paymentSuccessCounter = Counter.builder("ecommerce.payments.success")
.description("支付成功数")
.tag("application", "payment-service")
.register(meterRegistry);
this.paymentFailedCounter = Counter.builder("ecommerce.payments.failed")
.description("支付失败数")
.tag("application", "payment-service")
.register(meterRegistry);
this.paymentAmountSummary = DistributionSummary.builder("ecommerce.payments.amount")
.description("支付金额分布")
.baseUnit("CNY")
.register(meterRegistry);
// 初始化计量器
this.activeUsersGauge = Gauge.builder("ecommerce.users.active", activeUsers, AtomicInteger::get)
.description("活跃用户数")
.register(meterRegistry);
}
public void recordOrderCreated(OrderDTO order) {
orderCreatedCounter.increment();
// 记录订单金额
meterRegistry.summary("ecommerce.orders.amount")
.tag("status", "created")
.record(order.getPayAmount().doubleValue());
}
public void recordOrderPaid(OrderDTO order, long processTime) {
orderPaidCounter.increment();
orderProcessTimer.record(processTime, TimeUnit.MILLISECONDS);
meterRegistry.summary("ecommerce.orders.amount")
.tag("status", "paid")
.record(order.getPayAmount().doubleValue());
}
public void recordPaymentSuccess(PaymentDTO payment) {
paymentSuccessCounter.increment();
paymentAmountSummary.record(payment.getAmount().doubleValue());
}
public void recordPaymentFailed(PaymentDTO payment, String reason) {
paymentFailedCounter.increment();
meterRegistry.counter("ecommerce.payments.failed.reason",
"reason", reason).increment();
}
public void incrementActiveUsers() {
activeUsers.incrementAndGet();
}
public void decrementActiveUsers() {
activeUsers.decrementAndGet();
}
}
// 在业务代码中使用 @Service @RequiredArgsConstructor public class OrderService {
private final BusinessMetrics businessMetrics;
public OrderDTO createOrder(CreateOrderDTO createOrderDTO) {
long startTime = System.currentTimeMillis();
try {
// 创建订单逻辑…
OrderDTO order = createOrderInternal(createOrderDTO);
// 记录指标
businessMetrics.recordOrderCreated(order);
return order;
} finally {
long processTime = System.currentTimeMillis() – startTime;
businessMetrics.recordOrderProcessTime(processTime);
}
}
} 8.2 Docker与Kubernetes部署 Docker Compose完整部署:
yaml version: ‘3.8’
services:
基础服务
mysql: image: mysql:8.0 container_name: ecommerce-mysql environment: MYSQL_ROOT_PASSWORD: ecommerce123 MYSQL_DATABASE: ecommerce ports: – “3306:3306” volumes: – mysql_data:/var/lib/mysql – ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql networks: – ecommerce-network healthcheck: test: [“CMD”, “mysqladmin”, “ping”, “-h”, “localhost”] timeout: 20s retries: 10
redis: image: redis:7-alpine container_name: ecommerce-redis ports: – “6379:6379” volumes: – redis_data:/data command: redis-server –appendonly yes networks: – ecommerce-network healthcheck: test: [“CMD”, “redis-cli”, “ping”] interval: 10s timeout: 5s retries: 5
nacos: image: nacos/nacos-server:v2.2.0 container_name: ecommerce-nacos environment: – MODE=standalone – SPRING_DATASOURCE_PLATFORM=mysql – MYSQL_SERVICE_HOST=mysql – MYSQL_SERVICE_DB_NAME=nacos – MYSQL_SERVICE_PORT=3306 – MYSQL_SERVICE_USER=root – MYSQL_SERVICE_PASSWORD=ecommerce123 – NACOS_AUTH_ENABLE=true ports: – “8848:8848” – “9848:9848” – “9849:9849” volumes: – nacos_logs:/home/nacos/logs depends_on: mysql: condition: service_healthy networks: – ecommerce-network
seata: image: seataio/seata-server:1.7.1 container_name: ecommerce-seata environment: – SEATA_PORT=8091 – STORE_MODE=db – SEATA_IP=seata ports: – “8091:8091” volumes: – ./config/seata/registry.conf:/seata-server/resources/registry.conf depends_on: – mysql – nacos networks: – ecommerce-network
rocketmq: image: apache/rocketmq:5.1.0 container_name: ecommerce-rocketmq ports: – “9876:9876” – “10909:10909” – “10911:10911” volumes: – rocketmq_logs:/home/rocketmq/logs – rocketmq_store:/home/rocketmq/store networks: – ecommerce-network
监控服务
prometheus: image: prom/prometheus:v2.44.0 container_name: ecommerce-prometheus ports: – “9090:9090” volumes: – ./config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml – prometheus_data:/prometheus command: – ‘–config.file=/etc/prometheus/prometheus.yml’ – ‘–storage.tsdb.path=/prometheus’ – ‘–web.console.libraries=/etc/prometheus/console_libraries’ – ‘–web.console.templates=/etc/prometheus/consoles’ – ‘–storage.tsdb.retention.time=200h’ – ‘–web.enable-lifecycle’ networks: – ecommerce-network
grafana: image: grafana/grafana:9.5.2 container_name: ecommerce-grafana ports: – “3000:3000” environment: – GF_SECURITY_ADMIN_PASSWORD=admin123 volumes: – grafana_data:/var/lib/grafana – ./config/grafana/dashboards:/etc/grafana/provisioning/dashboards – ./config/grafana/datasources:/etc/grafana/provisioning/datasources depends_on: – prometheus networks: – ecommerce-network
应用服务
gateway: build: context: ./e-commerce-parent/e-commerce-gateway dockerfile: Dockerfile container_name: ecommerce-gateway ports: – “8080:8080” environment: – SPRING_PROFILES_ACTIVE=docker – SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848 – SPRING_CLOUD_NACOS_USERNAME=nacos – SPRING_CLOUD_NACOS_PASSWORD=nacos – SPRING_REDIS_HOST=redis – SPRING_REDIS_PORT=6379 depends_on: nacos: condition: service_healthy redis: condition: service_healthy networks: – ecommerce-network deploy: replicas: 2
user-service: build: context: ./e-commerce-parent/e-commerce-user dockerfile: Dockerfile container_name: ecommerce-user ports: – “8082:8082” environment: – SPRING_PROFILES_ACTIVE=docker – SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848 – SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db – SPRING_DATASOURCE_USERNAME=root – SPRING_DATASOURCE_PASSWORD=ecommerce123 – SPRING_REDIS_HOST=redis – SPRING_REDIS_PORT=6379 depends_on: – nacos – mysql – redis networks: – ecommerce-network deploy: replicas: 3
product-service: build: context: ./e-commerce-parent/e-commerce-product dockerfile: Dockerfile container_name: ecommerce-product ports: – “8083:8083” environment: – SPRING_PROFILES_ACTIVE=docker – SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848 – SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/product_db – SPRING_DATASOURCE_USERNAME=root – SPRING_DATASOURCE_PASSWORD=ecommerce123 – SPRING_REDIS_HOST=redis – SPRING_REDIS_PORT=6379 – SPRING_ELASTICSEARCH_HOST=elasticsearch – SPRING_ELASTICSEARCH_PORT=9200 depends_on: – nacos – mysql – redis networks: – ecommerce-network deploy: replicas: 3
order-service: build: context: ./e-commerce-parent/e-commerce-order dockerfile: Dockerfile container_name: ecommerce-order ports: – “8085:8085” environment: – SPRING_PROFILES_ACTIVE=docker – SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848 – SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/order_db – SPRING_DATASOURCE_USERNAME=root – SPRING_DATASOURCE_PASSWORD=ecommerce123 – SPRING_REDIS_HOST=redis – SPRING_REDIS_PORT=6379 – SEATA_SERVER_HOST=seata – ROCKETMQ_NAME_SERVER=rocketmq:9876 depends_on: – nacos – mysql – redis – seata – rocketmq networks: – ecommerce-network deploy: replicas: 3
networks: ecommerce-network: driver: bridge
volumes: mysql_data: redis_data: nacos_logs: rocketmq_logs: rocketmq_store: prometheus_data: grafana_data: Kubernetes部署文件:
yaml
namespace.yaml
apiVersion: v1 kind: Namespace metadata: name: ecommerce
configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: ecommerce-config namespace: ecommerce data: application.yml: | spring: cloud: nacos: discovery: server-addr: nacos:8848 config: server-addr: nacos:8848 redis: host: redis port: 6379
secret.yaml
apiVersion: v1 kind: Secret metadata: name: ecommerce-secrets namespace: ecommerce type: Opaque data: mysql-password: ZWNvbW1lcmNlMTIz # ecommerce123 base64编码 nacos-password: bmFjb3M= # nacos base64编码
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: ecommerce labels: app: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: – name: user-service image: ecommerce/user-service:1.0.0 ports: – containerPort: 8082 env: – name: SPRING_PROFILES_ACTIVE value: “k8s” – name: SPRING_DATASOURCE_URL value: “jdbc:mysql://mysql:3306/user_db” – name: SPRING_DATASOURCE_USERNAME value: “root” – name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: name: ecommerce-secrets key: mysql-password resources: requests: memory: “512Mi” cpu: “250m” limits: memory: “1Gi” cpu: “500m” livenessProbe: httpGet: path: /actuator/health/liveness port: 8082 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8082 initialDelaySeconds: 30 periodSeconds: 5
service.yaml
apiVersion: v1 kind: Service metadata: name: user-service namespace: ecommerce spec: selector: app: user-service ports:
- port: 8082 targetPort: 8082 type: ClusterIP
ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ecommerce-ingress namespace: ecommerce annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/ssl-redirect: “false” spec: rules:
- host: api.ecommerce.com http: paths:
- path: /user pathType: Prefix backend: service: name: user-service port: number: 8082
- path: /product pathType: Prefix backend: service: name: product-service port: number: 8083
- path: /order pathType: Prefix backend: service: name: order-service port: number: 8085
hpa.yaml
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: user-service-hpa namespace: ecommerce spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: user-service minReplicas: 2 maxReplicas: 10 metrics:
- type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
- type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
第九章:项目总结与扩展
9.1 项目架构总结 已实现的核心功能:
text ✅ 用户服务:注册、登录、用户管理 ✅ 商品服务:商品管理、搜索、缓存 ✅ 订单服务:创建订单、状态管理、分布式事务 ✅ 支付服务:支付集成、回调处理 ✅ 库存服务:库存管理、分布式锁 ✅ 优惠券服务:优惠券管理、使用 ✅ 网关服务:路由、认证、限流、熔断 ✅ 配置中心:统一配置管理 ✅ 服务注册发现:Nacos服务治理 ✅ 监控告警:Prometheus + Grafana ✅ 链路追踪:Sleuth + Zipkin ✅ 消息队列:RocketMQ异步处理 ✅ 分布式事务:Seata TCC模式 ✅ 容器化部署:Docker + Kubernetes 性能优化点:
text
java // 秒杀服务设计 @Service public class SeckillService {
// 1. 库存预热到Redis
public void preheatStock(Long productId, Integer stock) {
redisTemplate.opsForValue().set("seckill:stock:" + productId, stock);
}
// 2. 用户请求排队
public SeckillResult seckill(Long userId, Long productId) {
// 验证用户资格
if (!checkUserQualification(userId)) {
return SeckillResult.fail("用户资格不符");
}
// 验证秒杀时间
if (!checkSeckillTime(productId)) {
return SeckillResult.fail("秒杀未开始或已结束");
}
// 获取Redis分布式锁
RLock lock = redissonClient.getLock("seckill:lock:" + productId);
try {
boolean acquired = lock.tryLock(100, 10, TimeUnit.MILLISECONDS);
if (!acquired) {
return SeckillResult.fail("系统繁忙");
}
// 预减库存
Long stock = redisTemplate.opsForValue().decrement("seckill:stock:" + productId);
if (stock == null || stock < 0) {
// 库存不足,恢复库存
redisTemplate.opsForValue().increment("seckill:stock:" + productId);
return SeckillResult.fail("库存不足");
}
// 生成秒杀订单号
String seckillOrderNo = generateSeckillOrderNo();
// 发送消息到MQ,异步创建订单
sendSeckillOrderMessage(userId, productId, seckillOrderNo);
return SeckillResult.success(seckillOrderNo);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return SeckillResult.fail("系统异常");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
} 2. 推荐系统:
java // 基于用户行为的推荐 @Service public class RecommendationService {
// 协同过滤推荐
public List<ProductDTO> recommendByCF(Long userId) {
// 1. 获取用户历史行为
List<UserBehavior> behaviors = getUserBehaviors(userId);
// 2. 计算用户相似度
Map<Long, Double> userSimilarities = calculateUserSimilarities(userId);
// 3. 获取相似用户喜欢的商品
Set<Long> recommendedProductIds = getProductsFromSimilarUsers(userSimilarities);
// 4. 过滤已购买商品
recommendedProductIds.removeAll(getPurchasedProducts(userId));
// 5. 获取商品详情
return productService.getProductsByIds(new ArrayList<>(recommendedProductIds));
}
// 实时推荐(基于Redis)
public List<ProductDTO> getRealTimeRecommendations(Long userId) {
String key = "recommend:realtime:" + userId;
// 从Redis获取实时推荐
List<Long> productIds = (List<Long>) redisTemplate.opsForValue().get(key);
if (productIds == null || productIds.isEmpty()) {
// 重新计算推荐
productIds = calculateRealTimeRecommendations(userId);
redisTemplate.opsForValue().set(key, productIds, 10, TimeUnit.MINUTES);
}
return productService.getProductsByIds(productIds);
}
} 3. 数据统计分析:
java // 实时数据统计 @Service public class DataAnalysisService {
// 使用Flink进行实时数据分析
public void realTimeAnalysis() {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 从Kafka读取订单数据
DataStream<OrderEvent> orderStream = env
.addSource(new FlinkKafkaConsumer<>(
"order-topic",
new OrderEventSchema(),
properties))
.name("订单数据源");
// 实时统计
orderStream
.keyBy(OrderEvent::getProductId)
.timeWindow(Time.minutes(5))
.aggregate(new ProductSalesAggregator())
.addSink(new RedisSink<>());
env.execute("实时数据分析");
}
// 商品销售聚合器
public static class ProductSalesAggregator
implements AggregateFunction<OrderEvent, ProductSales, ProductSales> {
@Override
public ProductSales createAccumulator() {
return new ProductSales();
}
@Override
public ProductSales add(OrderEvent event, ProductSales accumulator) {
accumulator.setProductId(event.getProductId());
accumulator.setSalesCount(accumulator.getSalesCount() + event.getQuantity());
accumulator.setSalesAmount(accumulator.getSalesAmount() +
event.getPrice() * event.getQuantity());
return accumulator;
}
@Override
public ProductSales getResult(ProductSales accumulator) {
return accumulator;
}
@Override
public ProductSales merge(ProductSales a, ProductSales b) {
a.setSalesCount(a.getSalesCount() + b.getSalesCount());
a.setSalesAmount(a.getSalesAmount() + b.getSalesAmount());
return a;
}
}
} 9.3 学习收获与职业发展 通过本项目你学会了:
✅ 微服务架构设计与拆分
✅ Spring Cloud全家桶实战
✅ 分布式系统核心问题解决方案
✅ 高并发场景下的性能优化
✅ 容器化与云原生部署
✅ 全链路监控与故障排查
✅ 团队协作与项目管理
面试加分项:
text
text 初级 → 中级 → 高级 → 架构师 → 技术专家
学习路线:
text https://github.com/yourusername/e-commerce-microservices
包含内容: ├── 完整源代码 ├── 数据库脚本 ├── Docker配置文件 ├── Kubernetes部署文件 ├── 接口文档 ├── 架构设计文档 └── 学习路线图 快速启动命令:
bash
1. 克隆项目
git clone https://github.com/yourusername/e-commerce-microservices.git
2. 启动基础设施
cd e-commerce-microservices docker-compose up -d mysql redis nacos seata rocketmq
3. 导入数据库
mysql -uroot -p < sql/init.sql
4. 启动服务
方式1:IDEA中逐个启动
方式2:使用Maven
mvn spring-boot:run -pl e-commerce-parent/e-commerce-user mvn spring-boot:run -pl e-commerce-parent/e-commerce-product
…
5. 访问系统
网关:http://localhost:8080
Nacos:http://localhost:8848/nacos (nacos/nacos)
Grafana:http://localhost:3000 (admin/admin123)
10.2 学习路线图 30天学习计划:
text 第1周:Spring Cloud基础 Day1-2:Nacos服务注册与发现 Day3-4:OpenFeign服务调用 Day5-6:Spring Cloud Gateway Day7:项目搭建与调试
第2周:核心业务开发 Day8-9:用户服务实现 Day10-11:商品服务实现 Day12-13:订单服务实现 Day14:支付服务实现
第3周:高级特性 Day15:分布式事务Seata Day16:消息队列RocketMQ Day17:缓存与分布式锁 Day18:监控与链路追踪 Day19:安全与认证 Day20:性能优化
第4周:部署与运维 Day21-22:Docker容器化 Day23-24:Kubernetes部署 Day25-26:CI/CD流水线 Day27-28:压力测试与调优 Day29-30:项目总结与面试准备 10.3 常见问题解答 Q1:项目启动报错怎么办?
text A:常见问题排查:
text A:配置修改位置:
text A:扩展步骤:
关键收获:
✅ 掌握了企业级微服务架构设计
✅ 理解了分布式系统核心概念
✅ 积累了完整的项目开发经验
✅ 具备了解决复杂问题的能力
✅ 建立了技术自信和学习方法
记住:
技术的学习永无止境,但每个完整的项目都是你职业生涯的坚实基石。 从今天起,你不再只是技术的使用者,而是架构的设计者。
下一步行动:
运行项目:确保所有服务正常运行
深入研究:选择感兴趣的部分深入源码
分享经验:写博客、做分享、帮助他人
参与开源:贡献代码,加入技术社区
持续学习:关注新技术,不断更新知识体系
特别福利: 关注我并私信"电商微服务",获取:
本文完整Markdown版本
项目PPT演示文稿
面试常见问题及答案
扩展功能实现代码
学习社群邀请
今日打卡任务:
👍 点赞本文,支持原创
💾 收藏本文,方便查阅
🚀 运行项目,在评论区分享截图
👨💻 关注我,获取更多实战教程
在评论区告诉我:
你在实现过程中最大的收获是什么?
还希望学习什么类型的项目?
对电商系统有什么改进建议?
让我们一起在技术的道路上不断前行! 🚀🔥
下期预告: 《亿级流量电商系统架构设计与实践》
高并发场景下的架构优化
数据库分库分表实战
缓存穿透、雪崩、击穿解决方案
全链路压测与性能优化
容灾与多活架构设计
关注我,不错过每一篇精品教程! 💪
网硕互联帮助中心




评论前必须登录!
注册