1. 项目背景与核心需求
101外卖点餐系统是一个典型的互联网餐饮服务平台,采用前后端分离架构实现。这类系统在2023年外卖市场规模突破1.2万亿的背景下,面临着高并发、实时性和用户体验的三重挑战。我们选择SpringBoot+Vue的技术组合,主要基于以下考量:
- SpringBoot的优势:
- 内嵌Tomcat容器,简化部署流程
- 自动配置机制减少XML配置
- 完善的生态体系(Spring Security, Spring Data JPA等)
- 与MySQL的天然亲和性
- Vue的选型理由:
- 渐进式框架易于集成
- 响应式数据绑定适合频繁更新的订单状态
- 组件化开发提升代码复用率
- 丰富的UI库(如Element UI)
实际开发中发现,Element UI的表格组件在订单列表展示时存在性能瓶颈,当单页数据超过500条时渲染延迟明显。后来我们通过虚拟滚动技术解决了这个问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 系统分层架构
code复制┌─────────────────────────────────────┐
│ 客户端 (Web/Mobile) │
└─────────────────────────────────────┘
↑↓ HTTPS
┌─────────────────────────────────────┐
│ Nginx (负载均衡 + 静态资源托管) │
└─────────────────────────────────────┘
↑↓ HTTP
┌─────────────────────────────────────┐
│ SpringBoot应用集群 │
│ ┌───────────┐ ┌───────────┐ │
│ │ API网关 │ │ 业务逻辑层 │ │
│ └───────────┘ └───────────┘ │
│ ┌───────────┐ ┌───────────┐ │
│ │ 安全认证 │ │ 数据访问层 │ │
│ └───────────┘ └───────────┘ │
└─────────────────────────────────────┘
↑↓ JDBC
┌─────────────────────────────────────┐
│ MySQL集群 (主从复制) │
└─────────────────────────────────────┘
↑↓ 同步
┌─────────────────────────────────────┐
│ Redis缓存集群 │
└─────────────────────────────────────┘
2.2 数据库关键表设计
用户表(users)
sql复制CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT '加密密码',
`phone` varchar(20) NOT NULL COMMENT '手机号',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0禁用1正常)',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
踩坑记录:最初没有对phone字段建立索引,在用户量达到10万+时,登录接口响应时间超过2秒。通过EXPLAIN分析发现全表扫描,添加索引后响应时间降至200ms以内。
3. 核心功能实现
3.1 订单状态机设计
采用状态模式实现订单生命周期管理:
java复制public interface OrderState {
void pay(Order order);
void cancel(Order order);
void deliver(Order order);
void receive(Order order);
}
@Component
@Scope("prototype")
public class UnpaidState implements OrderState {
@Override
public void pay(Order order) {
order.setState(OrderConstant.PAID);
// 扣减库存
inventoryService.reduce(order.getItems());
// 记录支付流水
paymentService.recordPayment(order);
}
@Override
public void cancel(Order order) {
if (order.getCreateTime().plusMinutes(30).isBefore(LocalDateTime.now())) {
throw new BusinessException("超过30分钟未支付,订单已自动取消");
}
order.setState(OrderConstant.CANCELLED);
}
// 其他方法默认抛出UnsupportedOperationException
}
状态转换规则:
code复制待支付 → [支付] → 已支付
待支付 → [超时/取消] → 已取消
已支付 → [接单] → 配送中
配送中 → [完成] → 已完成
任何状态 → [退款] → 已退款
3.2 实时通信方案
使用WebSocket实现订单状态实时推送:
前端实现:
javascript复制// 在vue组件中
created() {
this.socket = new WebSocket(`wss://${location.host}/ws/order/${this.orderId}`);
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'STATUS_UPDATE') {
this.order.status = data.payload.status;
this.$notify({
title: '订单状态更新',
message: `订单状态变更为: ${this.statusText[data.payload.status]}`,
type: 'success'
});
}
};
this.socket.onclose = () => {
console.log('WebSocket连接关闭');
};
}
后端SpringBoot配置:
java复制@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(orderWebSocketHandler(), "/ws/order/*")
.setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
@Bean
public WebSocketHandler orderWebSocketHandler() {
return new OrderTrackingHandler();
}
}
性能优化点:实际测试发现,当并发连接数超过5000时,原生WebSocket实现会出现性能瓶颈。后来我们引入了Netty+WebSocket的解决方案,单机支持连接数提升到3万+。
4. 安全与性能优化
4.1 安全防护体系
- 认证与授权:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/user/**").hasRole("USER")
.antMatchers("/api/merchant/**").hasRole("MERCHANT")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
- 敏感数据加密:
java复制public class AESUtil {
private static final String KEY = "your-256-bit-secret";
private static final String IV = "your-16-bit-IV";
public static String encrypt(String plainText) {
// 实现AES加密逻辑
}
public static String decrypt(String cipherText) {
// 实现AES解密逻辑
}
}
4.2 缓存策略设计
采用多级缓存架构:
- 本地缓存:Caffeine处理高频访问的店铺信息
java复制@Configuration
public class CacheConfig {
@Bean
public Cache<String, Shop> shopCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
}
- 分布式缓存:Redis缓存热门菜品和促销信息
java复制@Repository
public class DishCacheRepository {
private final RedisTemplate<String, Object> redisTemplate;
public void cacheHotDishes(List<Dish> dishes) {
String key = "hot:dishes:" + LocalDate.now();
redisTemplate.opsForValue().set(key, dishes, 2, TimeUnit.HOURS);
}
}
- 缓存一致性方案:
- 使用Redis的Pub/Sub实现缓存失效通知
- 对关键数据采用双删策略(先删缓存再更新DB,延迟后再删一次缓存)
5. 部署与监控
5.1 Docker Compose部署方案
yaml复制version: '3.8'
services:
mysql-master:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: food_order
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
redis:
image: redis:6
ports:
- "6379:6379"
command: redis-server --requirepass redis123
application:
build: .
ports:
- "8080:8080"
depends_on:
- mysql-master
- redis
environment:
- SPRING_PROFILES_ACTIVE=prod
5.2 Prometheus监控配置
应用端暴露指标:
java复制@Configuration
public class MetricsConfig {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "food-order-service",
"region", System.getenv().getOrDefault("REGION", "unknown"));
}
}
Grafana监控看板关键指标:
- 应用层:QPS、平均响应时间、错误率
- JVM:堆内存、GC次数、线程数
- 数据库:活跃连接数、慢查询数
- Redis:命中率、内存使用量
6. 典型问题解决方案
6.1 分布式事务问题
场景:用户支付成功后,需要同时:
- 更新订单状态
- 扣减库存
- 增加商家余额
采用本地消息表+定时任务补偿方案:
java复制@Service
@Transactional
public class PaymentServiceImpl implements PaymentService {
@Autowired
private TransactionLogMapper logMapper;
@Override
public void processPayment(PaymentDTO dto) {
// 1. 记录支付流水
paymentDao.create(dto);
// 2. 创建事务日志
TransactionLog log = new TransactionLog();
log.setBusinessId(dto.getOrderId());
log.setStatus(TransactionStatus.PROCESSING);
logMapper.insert(log);
try {
// 3. 执行本地事务
orderService.updateStatus(dto.getOrderId(), OrderStatus.PAID);
inventoryService.reduce(dto.getItems());
merchantService.addBalance(dto.getMerchantId(), dto.getAmount());
// 4. 更新事务状态
log.setStatus(TransactionStatus.SUCCESS);
logMapper.updateById(log);
} catch (Exception e) {
log.setStatus(TransactionStatus.FAILED);
logMapper.updateById(log);
throw e;
}
}
}
定时任务扫描失败的事务进行补偿:
java复制@Scheduled(fixedRate = 300000)
public void compensateFailedTransactions() {
List<TransactionLog> failedLogs = logMapper.selectByStatus(TransactionStatus.FAILED);
failedLogs.forEach(log -> {
try {
// 根据业务ID重试业务逻辑
retryService.retry(log.getBusinessId());
log.setStatus(TransactionStatus.SUCCESS);
} catch (Exception e) {
log.setRetryCount(log.getRetryCount() + 1);
if (log.getRetryCount() > 3) {
log.setStatus(TransactionStatus.EXCEPTION);
// 触发人工干预报警
alertService.notifyAdmin(log);
}
}
logMapper.updateById(log);
});
}
6.2 高并发下单优化
采用分级库存策略:
- 前端缓存:Vuex存储本地库存快照,减少无效请求
- Redis预扣减:
java复制public boolean reduceStock(Long dishId, int quantity) {
String key = "stock:" + dishId;
long value = redisTemplate.opsForValue().decrement(key, quantity);
if (value < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key, quantity);
return false;
}
return true;
}
- 数据库最终一致性:
sql复制UPDATE dish_stock
SET available = available - #{quantity},
locked = locked + #{quantity}
WHERE dish_id = #{dishId} AND available >= #{quantity}
压测数据:
- 优化前:500并发下单,成功率82%,平均响应时间1.2s
- 优化后:1000并发下单,成功率99.6%,平均响应时间380ms
