一、为什么电商系统是毕设的最佳选择
每年计算机专业毕业设计中,选择电商系统作为课题的同学占比超过三分之一。这个现象背后有其必然性:电商系统的业务流程清晰完整,从用户注册登录到商品浏览下单再到支付配送,每个环节都有明确的技术实现点;业务逻辑既不过于简单(否则缺乏技术深度),也不过于复杂(否则毕设时间内无法完成);电商系统的技术栈覆盖全面,能够充分展示开发者的前后端开发能力、数据库设计能力和系统架构能力。
但与此同时,电商系统毕设也存在着几个普遍痛点:架构设计缺乏整体规划,很多同学直接照搬开源项目,导致模块边界混乱;数据表设计不规范,没有考虑实际业务场景的扩展性;部署能力薄弱,毕设答辩时程序只能在本地运行,无法演示云上部署效果。
本文将从一个完整的电商系统出发,详细讲解模块化架构设计、核心功能实现和阿里云产品部署的全流程。通过本文的学习,你不仅能够掌握一个符合生产环境标准的电商系统开发方法,还能获得一套可以直接复用于毕设的完整项目模板。
二、电商系统整体架构设计
2.1 架构演进与选型背景
传统的单体电商系统架构简单但存在明显的扩展瓶颈。当业务量增长时,单体应用的所有模块都需要整体扩容,无法针对热点模块单独优化。而且代码高度耦合,任何一个模块的修改都可能影响整个系统。
微服务架构通过将业务拆分为独立的服务单元,解决了扩展性问题,但同时带来了运维复杂度。对于毕设项目,建议采用"适度微服务"方案:将系统拆分为用户服务、商品服务、订单服务等核心模块,使用阿里云托管产品降低运维负担。
┌────────────────────────────────────────────────────────────────────┐
│ 电商系统阿里云部署架构图 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 阿里云CDN内容分发 │ │
│ │ (静态资源加速:图片/CSS/JS) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 阿里云弹性负载均衡SLB │ │
│ │ (请求分发:HTTP/HTTPS) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 云服务器ECS集群 │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 用户服务 │ │ 商品服务 │ │ 订单服务 │ │ │
│ │ │ (2台ECS) │ │ (2台ECS) │ │ (2台ECS) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ RDS MySQL│ │ Redis缓存 │ │ OSS对象 │ │
│ │ (主从架构) │ │ (集群版) │ │ 存储 │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
2.2 技术栈选型依据
| 层级 | 技术选型 | 阿里云产品 | 选型理由 |
|---|---|---|---|
| 前端框架 | Vue.js 3 | 无 | 轻量级、组件化生态完善 |
| 后端框架 | Spring Boot 2.7 | 无 | Java系主流、性能稳定 |
| 服务网关 | Spring Cloud Gateway | API网关 | 统一入口、流量管控 |
| 数据库 | MySQL 8.0 | RDS MySQL | 托管运维、自动备份 |
| 缓存 | Redis 7.0 | Redis企业版 | 高性能、支持集群 |
| 文件存储 | OSS | OSS对象存储 | 海量存储、CDN加速 |
| 负载均衡 | SLB | 传统型负载均衡 | 免费使用、稳定可靠 |
| 日志服务 | SLS | 日志服务 | 集中管理、实时分析 |
三、用户模块:从登录认证到权限管理
3.1 模块职责与技术方案
用户模块是电商系统的入口模块,负责用户身份认证、权限管理和个人信息维护。在技术实现上,采用Spring Security+JWT的组合方案,实现无状态的分布式认证。
// 用户认证核心配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth
.antMatchers("/api/auth/**", "/api/product/list").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
3.2 JWT Token生成与验证
@Component
public class JwtTokenProvider {
@Value("${jwt.secret:your-secret-key}")
private String jwtSecret;
@Value("${jwt.expiration:604800000}")
private long jwtExpiration;
// 生成Token
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
// 从Token获取用户名
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
// 验证Token
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
3.3 用户数据表设计
-- 用户信息表
CREATE TABLE `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(128) NOT NULL COMMENT '密码(BCrypt加密)',
`nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
`email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL(存储OSS地址)',
`status` TINYINT DEFAULT 1 COMMENT '状态:0禁用 1正常',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE `sys_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`role_name` VARCHAR(50) NOT NULL COMMENT '角色名称',
`role_key` VARCHAR(50) NOT NULL COMMENT '角色标识',
`description` VARCHAR(200) DEFAULT NULL COMMENT '角色描述',
`status` TINYINT DEFAULT 1,
`create_time` DATETIME,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 用户角色关联表
CREATE TABLE `sys_user_role` (
`user_id` BIGINT NOT NULL,
`role_id` BIGINT NOT NULL,
PRIMARY KEY (`user_id`, `role_id`)
) ENGINE=InnoDB COMMENT='用户角色关联表';
-- 权限表(RBAC)
CREATE TABLE `sys_permission` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`parent_id` BIGINT DEFAULT 0 COMMENT '父权限ID',
`name` VARCHAR(50) NOT NULL COMMENT '权限名称',
`permission_key` VARCHAR(100) NOT NULL COMMENT '权限标识',
`url` VARCHAR(255) DEFAULT NULL COMMENT '权限URL',
`type` TINYINT DEFAULT 1 COMMENT '类型:1菜单 2按钮',
`sort` INT DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
四、商品模块:高并发场景下的缓存策略
4.1 模块职责边界
商品模块是电商系统中数据量最大、访问最频繁的模块。单个电商平台的商品数量可能达到百万级别,每天的访问PV可能过亿。因此,商品模块的架构设计必须充分考虑性能问题。
┌──────────────────────────────────────────────────────────────────┐
│ 商品模块数据访问架构 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 用户请求 → CDN边缘节点 → Nginx本地缓存 → Redis分布式缓存 │
│ ↓ │
│ MySQL主数据库 │
│ │
│ 热点数据(热门商品):CDN缓存(1小时) + Redis缓存(10分钟) │
│ 普通数据(普通商品):Redis缓存(10分钟) + MySQL │
│ 冷门数据(长尾商品):直接查询MySQL │
│ │
└──────────────────────────────────────────────────────────────────┘
4.2 商品数据表设计
-- 商品分类表(支持三级分类)
CREATE TABLE `product_category` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`parent_id` BIGINT DEFAULT 0 COMMENT '父分类ID',
`name` VARCHAR(50) NOT NULL COMMENT '分类名称',
`icon` VARCHAR(255) DEFAULT NULL COMMENT '分类图标(OSS)',
`sort` INT DEFAULT 0 COMMENT '排序',
`status` TINYINT DEFAULT 1,
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';
-- 商品SPU表
CREATE TABLE `product_spu` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`category_id` BIGINT NOT NULL COMMENT '所属分类',
`name` VARCHAR(200) NOT NULL COMMENT '商品名称',
`subtitle` VARCHAR(500) DEFAULT NULL COMMENT '商品副标题',
`brand` VARCHAR(100) DEFAULT NULL COMMENT '品牌',
`keywords` VARCHAR(500) DEFAULT NULL COMMENT '搜索关键词',
`description` TEXT COMMENT '商品详情(富文本)',
`status` TINYINT DEFAULT 1 COMMENT '状态:0下架 1上架',
`sales_count` INT DEFAULT 0 COMMENT '销量',
`view_count` INT DEFAULT 0 COMMENT '浏览量',
`create_time` DATETIME,
`update_time` DATETIME,
PRIMARY KEY (`id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_brand` (`brand`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SPU表';
-- 商品SKU表
CREATE TABLE `product_sku` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`spu_id` BIGINT NOT NULL COMMENT '所属SPU',
`specs` VARCHAR(500) NOT NULL COMMENT '规格JSON',
`price` DECIMAL(10,2) NOT NULL COMMENT '售价',
`original_price` DECIMAL(10,2) DEFAULT NULL COMMENT '原价',
`stock` INT DEFAULT 0 COMMENT '库存',
`sku_code` VARCHAR(50) NOT NULL COMMENT 'SKU编码',
`image` VARCHAR(255) DEFAULT NULL COMMENT 'SKU主图(OSS)',
`status` TINYINT DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sku_code` (`sku_code`),
KEY `idx_spu_id` (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU表';
-- 商品图片表
CREATE TABLE `product_image` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`sku_id` BIGINT NOT NULL,
`image_url` VARCHAR(255) NOT NULL COMMENT '图片URL(OSS)',
`sort` INT DEFAULT 0,
`is_main` TINYINT DEFAULT 0 COMMENT '是否主图',
PRIMARY KEY (`id`),
KEY `idx_sku_id` (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品图片表';
4.3 商品查询服务实现
@Service
public class ProductService {
@Autowired
private ProductSpuMapper spuMapper;
@Autowired
private ProductSkuMapper skuMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private AlibabaCloudOSSService ossService;
private static final String PRODUCT_DETAIL_CACHE = "product:detail:";
private static final String PRODUCT_LIST_CACHE = "product:list:";
/**
* 获取商品详情(多级缓存)
*/
public ProductDetailVO getProductDetail(Long spuId) {
String cacheKey = PRODUCT_DETAIL_CACHE + spuId;
// 1. 查询Redis缓存
ProductDetailVO cached = (ProductDetailVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
// 异步增加浏览量
incrementViewCountAsync(spuId);
return cached;
}
// 2. 查询数据库
ProductSpu spu = spuMapper.selectById(spuId);
if (spu == null || spu.getStatus() != 1) {
throw new BusinessException("商品不存在或已下架");
}
// 3. 构建详情对象
ProductDetailVO vo = buildProductDetailVO(spu);
// 4. 写入Redis缓存(10分钟)
redisTemplate.opsForValue().set(cacheKey, vo, 10, TimeUnit.MINUTES);
// 5. 异步增加浏览量
incrementViewCountAsync(spuId);
return vo;
}
/**
* 商品分页列表查询
*/
public PageResult<ProductListVO> getProductList(ProductQueryDTO dto) {
String cacheKey = PRODUCT_LIST_CACHE + dto.getCategoryId() + ":"
+ dto.getPage() + ":" + dto.getSize();
// 1. 尝试从缓存获取
PageResult cached = (PageResult) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 2. 构建查询条件
Example example = new Example(ProductSpu.class);
Example.Criteria criteria = example.createCriteria()
.andEqualTo("status", 1);
if (dto.getCategoryId() != null) {
// 查询当前分类及子分类
List<Long> categoryIds = getCategoryAndChildrenIds(dto.getCategoryId());
criteria.andIn("categoryId", categoryIds);
}
if (StringUtils.hasText(dto.getKeyword())) {
criteria.andLike("name", "%" + dto.getKeyword() + "%");
}
// 3. 价格区间筛选
if (dto.getMinPrice() != null) {
// 需要联查SKU表获取最低价,此处简化处理
}
// 4. 排序
if ("price_asc".equals(dto.getSort())) {
example.setOrderByClause("price asc");
} else if ("sales".equals(dto.getSort())) {
example.setOrderByClause("sales_count desc");
} else {
example.setOrderByClause("create_time desc");
}
// 5. 分页查询
PageHelper.startPage(dto.getPage(), dto.getSize());
List<ProductSpu> list = spuMapper.selectByExample(example);
Page<ProductSpu> page = (Page<ProductSpu>) list;
// 6. 转换为VO
List<ProductListVO> voList = list.stream()
.map(this::convertToListVO)
.collect(Collectors.toList());
PageResult result = new PageResult<>(voList, page.getTotal(), page.getPages());
// 7. 缓存结果
redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
return result;
}
/**
* 异步增加浏览量
*/
@Async
public void incrementViewCountAsync(Long spuId) {
spuMapper.incrementViewCount(spuId);
}
}
4.4 阿里云OSS文件上传
@Service
public class AlibabaCloudOSSService {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
/**
* 上传商品图片到OSS
*/
public String uploadProductImage(MultipartFile file, Long skuId) {
// 1. 验证文件
validateImageFile(file);
// 2. 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = "product/" + skuId + "/"
+ UUID.randomUUID().toString().replace("-", "")
+ extension;
// 3. 创建OSSClient
OSS ossClient = new OSSClientBuilder()
.build(endpoint, accessKeyId, accessKeySecret);
try {
// 4. 上传文件
ossClient.putObject(bucketName, newFileName, file.getInputStream());
// 5. 返回访问URL
return "https://" + bucketName + "." + endpoint + "/" + newFileName;
} finally {
ossClient.shutdown();
}
}
/**
* 生成带签名的访问URL(用于私有读权限的Bucket)
*/
public String generateSignedUrl(String objectKey, int expireSeconds) {
OSS ossClient = new OSSClientBuilder()
.build(endpoint, accessKeyId, accessKeySecret);
try {
Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000L);
URL url = ossClient.generatePresignedUrl(bucketName, objectKey, expiration);
return url.toString();
} finally {
ossClient.shutdown();
}
}
}
五、订单模块:高并发下的库存扣减方案
5.1 订单状态机设计
订单模块是电商系统中并发压力最大的模块。双十一等大促期间,订单系统的TPS可能达到数万级别。如何在这种压力下保证库存不超卖、订单不重复,是订单模块设计的核心挑战。
┌──────────────────────────────────────────────────────────────────┐
│ 订单全生命周期状态流转 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 待支付 │ ←←←←←←← (订单超时自动取消) │
│ └───┬─────┘ │
│ │ 支付成功 │
│ ▼ │
│ ┌─────────┐ │
│ │ 已支付 │ ─────────────────→ ┌─────────┐ │
│ └───┬─────┘ │ 退款中 │ │
│ │ 超时关闭 └────┬────┘ │
│ ▼ │ │
│ ┌─────────┐ │ │
│ │ 已取消 │ ───────────────────────────┴────→ 已退款 │
│ └─────────┘ │
│ │
│ │ 商家发货 │
│ ▼ │
│ ┌─────────┐ │
│ │ 待发货 │ │
│ └───┬─────┘ │
│ │ 物流揽收 │
│ ▼ │
│ ┌─────────┐ │
│ │ 已发货 │ │
│ └───┬─────┘ │
│ │ 确认收货 │
│ ▼ │
│ ┌─────────┐ │
│ │ 已完成 │ ←←←←←←← (7天后自动完成) │
│ └─────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
5.2 订单数据表设计
-- 订单主表
CREATE TABLE `order_info` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
`pay_amount` DECIMAL(10,2) NOT NULL COMMENT '实付金额',
`freight_amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '运费',
`discount_amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '优惠金额',
`pay_type` TINYINT DEFAULT NULL COMMENT '支付方式:1微信 2支付宝',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态',
`receiver_name` VARCHAR(50) NOT NULL COMMENT '收货人姓名',
`receiver_phone` VARCHAR(20) NOT NULL COMMENT '收货人电话',
`receiver_address` VARCHAR(255) NOT NULL COMMENT '收货地址',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '订单备注',
`create_time` DATETIME,
`pay_time` DATETIME COMMENT '支付时间',
`ship_time` DATETIME COMMENT '发货时间',
`receive_time` DATETIME COMMENT '收货时间',
`finish_time` DATETIME COMMENT '完成时间',
`cancel_time` DATETIME 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_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_id` BIGINT NOT NULL,
`sku_id` BIGINT NOT NULL,
`sku_name` VARCHAR(200) NOT NULL COMMENT '商品名称(快照)',
`sku_specs` VARCHAR(500) DEFAULT NULL COMMENT '规格(快照)',
`sku_image` VARCHAR(255) DEFAULT NULL COMMENT '商品图片(快照)',
`price` DECIMAL(10,2) NOT NULL COMMENT '购买价格',
`quantity` INT NOT NULL COMMENT '购买数量',
`sub_total` DECIMAL(10,2) NOT NULL COMMENT '小计金额',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品表';
-- 物流信息表
CREATE TABLE `order_shipping` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_id` BIGINT NOT NULL,
`express_company` VARCHAR(50) DEFAULT NULL COMMENT '快递公司',
`express_no` VARCHAR(50) DEFAULT NULL COMMENT '快递单号',
`ship_time` DATETIME COMMENT '发货时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流信息表';
5.3 订单创建与库存扣减实现
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private ProductSkuMapper skuMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private AlibabaCloudMQService mqService;
@Transactional(rollbackFor = Exception.class)
public Order createOrder(CreateOrderRequest request) {
// 1. 分布式锁防止重复下单
String lockKey = "lock:order:create:" + request.getUserId();
String lockValue = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("请勿重复提交订单");
}
try {
// 2. 校验并扣减库存(Redis预扣减)
List<OrderItemDTO> items = request.getItems();
Map<Long, Integer> stockDeductMap = new HashMap<>();
for (OrderItemDTO item : items) {
// 校验库存
String stockKey = "stock:" + item.getSkuId();
String currentStock = redisTemplate.opsForValue().get(stockKey);
if (currentStock == null) {
// 缓存未命中,从数据库加载
ProductSku sku = skuMapper.selectById(item.getSkuId());
currentStock = String.valueOf(sku.getStock());
redisTemplate.opsForValue().set(stockKey, currentStock, 1, TimeUnit.HOURS);
}
int stock = Integer.parseInt(currentStock);
if (stock < item.getQuantity()) {
throw new BusinessException("商品库存不足:" + item.getSkuName());
}
// Redis扣减库存
Long newStock = redisTemplate.opsForValue()
.decrement(stockKey, item.getQuantity());
if (newStock < 0) {
// 扣减失败,回补并报错
redisTemplate.opsForValue().increment(stockKey, item.getQuantity());
throw new BusinessException("商品库存不足:" + item.getSkuName());
}
stockDeductMap.put(item.getSkuId(), item.getQuantity());
}
// 3. 计算订单金额
BigDecimal totalAmount = calculateTotalAmount(items);
BigDecimal discountAmount = calculateDiscount(request);
BigDecimal payAmount = totalAmount.subtract(discountAmount);
// 4. 生成订单号
String orderNo = generateOrderNo();
// 5. 创建订单
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(request.getUserId());
order.setTotalAmount(totalAmount);
order.setDiscountAmount(discountAmount);
order.setPayAmount(payAmount);
order.setStatus(OrderStatus.PENDING_PAY.getCode());
order.setReceiverName(request.getReceiverName());
order.setReceiverPhone(request.getReceiverPhone());
order.setReceiverAddress(request.getReceiverAddress());
order.setCreateTime(new Date());
orderMapper.insert(order);
// 6. 保存订单商品
for (OrderItemDTO item : items) {
ProductSku sku = skuMapper.selectById(item.getSkuId());
ProductSpu spu = spuMapper.selectById(sku.getSpuId());
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setSkuId(item.getSkuId());
orderItem.setSkuName(spu.getName());
orderItem.setSkuSpecs(sku.getSpecs());
orderItem.setSkuImage(getMainImage(item.getSkuId()));
orderItem.setPrice(sku.getPrice());
orderItem.setQuantity(item.getQuantity());
orderItem.setSubTotal(sku.getPrice().multiply(new BigDecimal(item.getQuantity())));
orderItemMapper.insert(orderItem);
}
// 7. 发送订单创建消息(用于后续库存数据库同步)
mqService.sendOrderCreatedMessage(order.getId(), stockDeductMap);
// 8. 设置订单超时(30分钟)
redisTemplate.opsForValue().set(
"order:timeout:" + orderNo,
orderNo,
30,
TimeUnit.MINUTES
);
log.info("订单创建成功:{}", orderNo);
return order;
} finally {
// 释放分布式锁
String currentLock = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(currentLock)) {
redisTemplate.delete(lockKey);
}
}
}
private String generateOrderNo() {
return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
+ RandomStringUtils.randomNumeric(6);
}
}
5.4 库存数据库同步与超卖防护
@Service
public class StockSyncService {
@Autowired
private ProductSkuMapper skuMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 消费订单创建消息,同步扣减数据库库存
*/
@RabbitListener(queues = "stock:deduct:queue")
@Transactional
public void handleStockDeduct(StockDeductMessage message) {
for (Map.Entry<Long, Integer> entry : message.getStockMap().entrySet()) {
Long skuId = entry.getKey();
Integer quantity = entry.getValue();
// 使用乐观锁更新数据库
int rows = skuMapper.deductStockWithVersion(skuId, quantity);
if (rows == 0) {
// 数据库库存不足,需要回滚Redis
redisTemplate.opsForValue()
.increment("stock:" + skuId, quantity);
log.error("数据库库存扣减失败,SKU: {}, 数量: {}", skuId, quantity);
}
}
}
}
<!-- StockMapper.xml -->
<update id="deductStockWithVersion">
UPDATE product_sku
SET stock = stock - #{quantity},
version = version + 1,
update_time = NOW()
WHERE id = #{skuId}
AND stock >= #{quantity}
AND version = #{version}
</update>
六、支付模块:阿里云聚合支付集成
6.1 支付流程概述
┌──────────────────────────────────────────────────────────────────┐
│ 支付流程时序图 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 用户 前端服务 订单服务 支付服务 支付宝/微信 │
│ │ │ │ │ │ │
│ │──下单──▶│ │ │ │ │
│ │ │──创建订单─▶│ │ │ │
│ │◀─订单信息─┤ │ │ │ │
│ │ │ │ │ │ │
│ │──发起支付─▶│ │ │ │ │
│ │ │───────────▶│ │ │ │
│ │ │ │───支付请求──▶│ │ │
│ │ │ │◀───支付链接──┤ │ │
│ │◀─支付链接─┤ │ │ │ │
│ │ │ │ │ │ │
│ │───────────支付宝/微信扫码支付────────▶│ │ │
│ │ │ │ │◀──异步回调──┤ │
│ │ │ │◀──更新订单状态─┤ │ │
│ │◀─支付成功─┤ │ │ │ │
│ │
└──────────────────────────────────────────────────────────────────┘
6.2 支付服务实现
@Service
@Slf4j
public class PaymentService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AlipayService alipayService;
@Autowired
private WxPayService wxPayService;
/**
* 创建支付订单
*/
public PaymentResponse createPayment(PaymentRequest request) {
// 1. 查询订单
Order order = orderMapper.selectByOrderNo(request.getOrderNo());
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getStatus() != OrderStatus.PENDING_PAY.getCode()) {
throw new BusinessException("订单状态不允许支付");
}
// 2. 根据支付方式调用对应服务
PaymentResponse response = new PaymentResponse();
response.setOrderNo(order.getOrderNo());
response.setAmount(order.getPayAmount());
if (PaymentType.ALIPAY.getCode().equals(request.getPayType())) {
// 支付宝支付
String payUrl = alipayService.createPayOrder(order);
response.setPayUrl(payUrl);
response.setPayType(PaymentType.ALIPAY);
} else if (PaymentType.WXPAY.getCode().equals(request.getPayType())) {
// 微信支付
String codeUrl = wxPayService.createNativeOrder(order);
response.setCodeUrl(codeUrl);
response.setPayType(PaymentType.WXPAY);
}
return response;
}
/**
* 支付宝异步回调处理
*/
public void handleAlipayNotify(String notifyData) {
try {
// 验签
boolean signVerified = alipayService.verifyNotify(notifyData);
if (!signVerified) {
log.error("支付宝回调验签失败");
return;
}
// 解析回调数据
AlipayTradeNotifyResponse response = alipayService.parseNotifyResponse(notifyData);
String orderNo = response.getOutTradeNo();
String tradeStatus = response.getTradeStatus();
// 处理支付结果
if ("TRADE_SUCCESS".equals(tradeStatus)) {
updateOrderPaid(orderNo, PaymentType.ALIPAY, response.getTradeNo());
}
} catch (Exception e) {
log.error("处理支付宝回调异常", e);
}
}
@Transactional
public void updateOrderPaid(String orderNo, PaymentType payType, String transactionId) {
Order order = orderMapper.selectByOrderNo(orderNo);
if (order == null || order.getStatus() != OrderStatus.PENDING_PAY.getCode()) {
return;
}
order.setStatus(OrderStatus.PAID.getCode());
order.setPayType(payType.getCode());
order.setPayTime(new Date());
order.setTransactionId(transactionId);
orderMapper.updateById(order);
log.info("订单支付成功:{}", orderNo);
}
}
七、阿里云云上部署实战
7.1 服务器与数据库准备
阿里云产品清单:
| 产品 | 规格 | 用途 | 成本估算 |
|---|---|---|---|
| ECS实例 | 2核4G * 2台 | 应用服务 | 约200元/月 |
| RDS MySQL | 2核4G高可用版 | 数据库 | 约300元/月 |
| Redis | 2G集群版 | 缓存 | 约150元/月 |
| OSS | 40G标准存储 | 文件存储 | 约10元/月 |
| SLB | 公网类型 | 负载均衡 | 约50元/月 |
| 合计 | - | - | 约710元/月 |
7.2 应用服务部署配置
第一步:购买并配置ECS实例
在阿里云ECS控制台创建两台实例,选择CentOS 7.x系统盘,配置安全组规则开放80/443/8080端口。
# 连接ECS服务器
ssh root@your-ecs-ip
# 安装Java运行环境
yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel
# 验证Java安装
java -version
# 安装Maven
yum install -y maven
# 安装Docker
yum install -y docker
systemctl start docker
systemctl enable docker
第二步:配置RDS MySQL
在阿里云RDS控制台创建MySQL 8.0高可用实例,配置白名单允许ECS内网访问。
# 从应用服务器连接RDS
mysql -h your-rds-endpoint -P 3306 -u your-username -p
# 创建数据库
CREATE DATABASE ecommerce DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 执行建表脚本(使用前述SQL)
source /path/to/init.sql;
第三步:构建并部署应用
# 在开发机器上构建
mvn clean package -DskipTests
# 上传JAR包到ECS
scp target/ecommerce-1.0.0.jar root@ecs1:/app/
scp target/ecommerce-1.0.0.jar root@ecs2:/app/
# 在ECS上创建启动脚本
cat > /app/start.sh << EOF
#!/bin/bash
export JAVA_OPTS="-Xms512m -Xmx1024m"
export SPRING_PROFILES_ACTIVE=prod
export MYSQL_HOST=your-rds-endpoint
export REDIS_HOST=your-redis-host
export OSS_ENDPOINT=your-oss-endpoint
java \$JAVA_OPTS -jar /app/ecommerce-1.0.0.jar
EOF
chmod +x /app/start.sh
# 启动应用
cd /app && nohup ./start.sh > app.log 2>&1 &
7.3 负载均衡配置
在阿里云SLB控制台创建传统型负载均衡实例:
- 添加后端服务器组,绑定两台ECS实例
- 配置健康检查(HTTP协议,检查路径:/actuator/health)
- 配置监听端口:80端口(HTTP)
# 验证部署
curl http://slb-public-ip/api/product/list
7.4 Nginx反向代理配置
# /etc/nginx/conf.d/ecommerce.conf
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name your-domain.com;
# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 静态资源代理到OSS
location /oss/ {
proxy_pass https://your-bucket.oss-cn-beijing.aliyuncs.com/;
}
}
7.5 SSL证书配置(HTTPS)
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/your-cert.pem;
ssl_certificate_key /etc/nginx/ssl/your-key.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_protocols TLSv1.2 TLSv1.3;
# 安全头配置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# 其他配置同上...
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
八、竞品对比分析
| 对比维度 | 阿里云部署方案 | 自建服务器方案 | 其他云平台 |
|---|---|---|---|
| 部署效率 | 图形化控制台,操作简单 | 需要手动配置全部组件 | 界面和阿里云类似 |
| 运维成本 | 托管服务,免维护 | 需要专职运维 | 介于两者之间 |
| 成本投入 | 按需付费,弹性计费 | 一次性投入高,但无持续费用 | 定价策略各异 |
| 产品生态 | 阿里云系产品深度集成 | 需自行集成 | 产品线丰富 |
| 数据库托管 | RDS一键创建,自动备份 | 需自行搭建主从 | 提供托管服务 |
| 安全防护 | 免费DDoS基础防护 | 需自行配置 | 类似阿里云 |
| 适用场景 | 快速上线、弹性业务 | 有预算限制的场景 | 企业级应用 |
九、总结与延伸
本文系统讲解了电商系统的完整开发与阿里云部署方案,覆盖了从架构设计到云上部署的全流程。核心要点回顾:
- 模块化架构:采用DDD思想划分用户、商品、订单、支付等核心模块
- 高性能设计:通过多级缓存(CDN→Nginx→Redis)应对高并发场景
- 分布式锁:使用Redis分布式锁防止库存超卖和重复下单
- 消息队列:通过异步消息实现系统解耦和最终一致性
- 云原生部署:基于阿里云ECS+RDS+Redis+OSS构建完整的云上基础设施
对于正在准备毕设的同学,建议从本文的架构设计中汲取思路,不必完全照搬。核心是理解为什么这样设计,以及如何在自己的项目中应用这些技术方案。
进阶学习路径建议:
- 初级阶段:完成单体电商系统的本地开发
- 中级阶段:引入Redis缓存、异步消息等技术优化性能
- 高级阶段:学习Docker容器化,使用阿里云ACK部署微服务架构