引言:面试中的商城系统设计挑战

在技术面试中,商城系统设计是一个经典且高频的考题。它不仅考察候选人对分布式系统、数据库设计、缓存策略等基础知识的掌握,更能通过细节追问检验其解决实际业务问题的能力和创新思维。一个优秀的商城系统设计方案应该像一座精心设计的建筑,既有宏伟的蓝图(架构设计),又有精致的细节(实现优化)。

本文将从架构设计、核心模块实现、性能优化、高可用保障等多个维度,详细解析如何在面试中展现专业深度和创新思维。我们将结合具体的代码示例和设计模式,帮助你构建一个既实用又有亮点的商城系统设计方案。

一、整体架构设计:展现系统性思维

1.1 分层架构设计

一个专业的商城系统应该采用清晰的分层架构,这不仅便于维护,也体现了设计者的系统性思维。

┌─────────────────────────────────────────────────────┐
│                    用户层 (Web/App)                   │
├─────────────────────────────────────────────────────┤
│                  网关层 (API Gateway)                 │
├─────────────────────────────────────────────────────┤
│                  服务层 (Microservices)               │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │
│  │  用户服务    │ │  商品服务    │ │  订单服务    │    │
│  └─────────────┘ └─────────────┘ └─────────────┘    │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │
│  │  支付服务    │ │  库存服务    │ │  促销服务    │    │
│  └─────────────┘ └─────────────┘ └─────────────┘    │
├─────────────────────────────────────────────────────┤
│                  基础设施层 (Infrastructure)          │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │
│  │   数据库     │ │   缓存      │ │   消息队列   │    │
│  └─────────────┘ └─────────────┘ └─────────────┘    │
└─────────────────────────────────────────────────────┘

设计亮点:

  • 服务拆分合理:按照业务领域进行服务划分,避免单体架构的臃肿
  • 网关统一管理:所有请求通过网关,实现统一的认证、限流、日志
  • 基础设施解耦:数据库、缓存、消息队列等基础设施独立维护,便于扩展

1.2 创新的架构模式

在基础分层之上,可以引入一些创新的架构模式来展现深度:

1. CQRS(命令查询职责分离)模式

// 命令端(写操作)
@Service
public class ProductCommandService {
    @Autowired
    private ProductRepository repository;
    
    public void createProduct(ProductCreateCommand command) {
        // 复杂的业务校验
        validateProduct(command);
        // 写入主库
        Product product = new Product(command);
        repository.save(product);
        // 发布领域事件
        eventPublisher.publish(new ProductCreatedEvent(product.getId()));
    }
}

// 查询端(读操作)
@Service
public class ProductQueryService {
    @Autowired
    private ProductReadRepository readRepository;
    
    public ProductDTO getProduct(Long id) {
        // 从只读库或缓存查询
        return readRepository.findById(id);
    }
}

2. 事件驱动架构

// 使用Spring Event实现事件驱动
@Component
public class OrderEventListener {
    
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 异步处理库存扣减
        inventoryService.deductStock(event.getOrderId());
        // 异步发送通知
        notificationService.sendOrderNotification(event.getOrderId());
        // 异步更新统计
        statisticsService.updateSalesData(event.getOrderId());
    }
}

二、核心模块设计:展现专业深度

2.1 商品模块设计

2.1.1 多规格商品设计

商城系统中,商品规格(如颜色、尺码)是一个复杂的设计点。专业方案应该支持灵活的规格组合。

-- 商品主表
CREATE TABLE product (
    id BIGINT PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    category_id BIGINT,
    base_price DECIMAL(10,2),
    status TINYINT,
    created_at TIMESTAMP
);

-- 规格项表(颜色、尺码等)
CREATE TABLE product_spec (
    id BIGINT PRIMARY KEY,
    product_id BIGINT,
    spec_name VARCHAR(50),  -- 颜色
    spec_value VARCHAR(50), -- 红色
    price DECIMAL(10,2),    -- 规格价格
    stock INT,
    sku_code VARCHAR(50),
    INDEX idx_product (product_id),
    INDEX idx_sku (sku_code)
);

-- 规格模板表
CREATE TABLE spec_template (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50),
    spec_items JSON  -- {"颜色":["红","蓝"], "尺码":["S","M","L"]}
);

设计亮点:

  • SKU独立管理:每个规格组合生成独立的SKU,便于库存和价格管理
  • JSON存储规格:使用JSON存储规格模板,灵活支持各种商品类型
  • 索引优化:为SKU编码建立索引,支持快速查询

2.1.2 商品详情页缓存策略

@Component
public class ProductCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String PRODUCT_DETAIL_KEY = "product:detail:%s";
    private static final String PRODUCT_SALE_KEY = "product:sale:%s";
    
    public ProductDTO getProductDetail(Long productId) {
        String key = String.format(PRODUCT_DETAIL_KEY, productId);
        
        // 1. 先查本地缓存(Caffeine)
        ProductDTO product = localCache.getIfPresent(key);
        if (product != null) {
            return product;
        }
        
        // 2. 再查Redis
        product = (ProductDTO) redisTemplate.opsForValue().get(key);
        if (product != null) {
            localCache.put(key, product);
            return product;
        }
        
        // 3. 最后查数据库
        product = productRepository.findDetailById(productId);
        if (product != null) {
            // 填充缓存
            redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
            localCache.put(key, product);
        }
        
        return product;
    }
    
    // 缓存预热和更新
    public void warmupCache(Long productId) {
        ProductDTO product = productRepository.findDetailById(productId);
        if (product != null) {
            String key = String.format(PRODUCT_DETAIL_KEY, productId);
            redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
        }
    }
}

设计亮点:

  • 多级缓存:本地缓存 + Redis缓存,减少网络开销
  • 缓存预热:后台任务提前加载热点数据
  • 缓存更新:通过消息队列监听商品变更事件,主动更新缓存

2.2 购物车模块设计

购物车需要支持未登录用户的临时购物车和登录用户的持久化购物车。

@Service
public class ShoppingCartService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CartRepository cartRepository;
    
    private static final String TEMP_CART_KEY = "temp_cart:%s"; // 未登录用户
    private static final String USER_CART_KEY = "user_cart:%s"; // 登录用户
    
    /**
     * 添加商品到购物车
     */
    public CartResult addToCart(CartItemRequest request, Long userId, String sessionId) {
        // 1. 确定购物车标识
        String cartKey = userId != null ? 
            String.format(USER_CART_KEY, userId) : 
            String.format(TEMP_CART_KEY, sessionId);
        
        // 2. 获取当前购物车
        Cart cart = getCart(cartKey);
        
        // 3. 添加商品
        cart.addItem(request.getSkuId(), request.getQuantity());
        
        // 4. 保存购物车
        saveCart(cartKey, cart);
        
        // 5. 返回结果(包含促销信息)
        return buildCartResult(cart);
    }
    
    /**
     * 合并购物车(登录时)
     */
    public void mergeCart(Long userId, String sessionId) {
        String tempKey = String.format(TEMP_CART_KEY, sessionId);
        String userKey = String.format(USER_CART_KEY, userId);
        
        Cart tempCart = getCart(tempKey);
        Cart userCart = getCart(userKey);
        
        // 合并逻辑
        userCart.merge(tempCart);
        saveCart(userKey, userCart);
        
        // 删除临时购物车
        redisTemplate.delete(tempKey);
    }
    
    private Cart getCart(String key) {
        Cart cart = (Cart) redisTemplate.opsForValue().get(key);
        return cart != null ? cart : new Cart();
    }
    
    private void saveCart(String key, Cart cart) {
        // 设置过期时间7天
        redisTemplate.opsForValue().set(key, cart, 7, TimeUnit.DAYS);
    }
}

设计亮点:

  • 双模式设计:支持未登录和登录两种状态
  • 自动合并:登录时自动合并临时购物车
  • Redis存储:高性能存储,支持过期时间

2.3 订单模块设计

订单系统是商城的核心,涉及事务一致性、分布式锁等复杂问题。

2.3.1 订单创建流程

@Service
@Transactional
public class OrderCreateService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PromotionService promotionService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 创建订单(包含完整的业务流程)
     */
    public OrderCreateResult createOrder(OrderCreateCommand command) {
        // 1. 参数校验
        validateCommand(command);
        
        // 2. 分布式锁防止重复提交
        String lockKey = "order:create:" + command.getUserId();
        Boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS);
        
        if (Boolean.FALSE.equals(locked)) {
            throw new BusinessException("订单正在创建中,请勿重复提交");
        }
        
        try {
            // 3. 计算订单金额(包含促销)
            OrderAmount amount = calculateAmount(command.getItems());
            
            // 4. 预扣库存(分布式锁)
            for (ItemRequest item : command.getItems()) {
                boolean lockedStock = inventoryService.preDeductStock(
                    item.getSkuId(), item.getQuantity(), command.getUserId());
                if (!lockedStock) {
                    throw new BusinessException("商品库存不足:" + item.getSkuId());
                }
            }
            
            // 5. 创建订单
            Order order = Order.builder()
                .orderNo(generateOrderNo())
                .userId(command.getUserId())
                .totalAmount(amount.getTotal())
                .payAmount(amount.getPay())
                .discountAmount(amount.getDiscount())
                .status(OrderStatus.WAIT_PAY)
                .items(buildOrderItems(command.getItems(), amount))
                .build();
            
            orderRepository.save(order);
            
            // 6. 发送订单创建事件(异步处理后续流程)
            eventPublisher.publish(new OrderCreatedEvent(order.getId()));
            
            // 7. 返回结果
            return OrderCreateResult.success(order.getId(), order.getPayAmount());
            
        } finally {
            // 8. 释放锁
            redisTemplate.delete(lockKey);
        }
    }
    
    /**
     * 订单号生成(时间戳 + 随机数 + 业务号)
     */
    private String generateOrderNo() {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String random = String.format("%04d", ThreadLocalRandom.current().nextInt(10000));
        String sequence = String.format("%04d", getAndIncrementSequence());
        return timestamp + random + sequence;
    }
    
    private int getAndIncrementSequence() {
        // 使用Redis原子递增
        return redisTemplate.opsForValue()
            .increment("order:sequence:" + LocalDate.now().toString())
            .intValue();
    }
}

设计亮点:

  • 分布式锁:防止订单重复提交
  • 预扣库存:避免超卖,支持回滚
  • 事件驱动:创建后异步处理,提升响应速度
  • 订单号生成:避免重复,包含时间序列信息

2.3.2 订单状态机设计

/**
 * 订单状态机
 */
public enum OrderStatus {
    WAIT_PAY("待支付"),
    PAID("已支付"),
    SHIPPED("已发货"),
    COMPLETED("已完成"),
    CANCELLED("已取消"),
    REFUNDING("退款中"),
    REFUNDED("已退款");
    
    private final String desc;
    OrderStatus(String desc) { this.desc = desc; }
}

/**
 * 状态机转换器
 */
@Component
public class OrderStatusMachine {
    
    private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED_TRANSITIONS = new HashMap<>();
    
    static {
        // 定义合法的状态转换
        ALLOWED_TRANSITIONS.put(OrderStatus.WAIT_PAY, 
            Set.of(OrderStatus.PAID, OrderStatus.CANCELLED));
        ALLOWED_TRANSITIONS.put(OrderStatus.PAID, 
            Set.of(OrderStatus.SHIPPED, OrderStatus.REFUNDING));
        ALLOWED_TRANSITIONS.put(OrderStatus.SHIPPED, 
            Set.of(OrderStatus.COMPLETED, OrderStatus.REFUNDING));
    }
    
    public void transition(Order order, OrderStatus targetStatus) {
        // 1. 检查转换是否合法
        Set<OrderStatus> allowed = ALLOWED_TRANSITIONS.get(order.getStatus());
        if (allowed == null || !allowed.contains(targetStatus)) {
            throw new BusinessException(
                String.format("订单状态转换非法: %s -> %s", order.getStatus(), targetStatus));
        }
        
        // 2. 执行状态转换
        OrderStatus oldStatus = order.getStatus();
        order.setStatus(targetStatus);
        orderRepository.save(order);
        
        // 3. 发布状态变更事件
        eventPublisher.publish(new OrderStatusChangedEvent(
            order.getId(), oldStatus, targetStatus));
    }
}

设计亮点:

  • 状态转换约束:通过Map定义合法转换,避免非法状态变更
  • 事件通知:状态变更后发布事件,触发关联业务
  • 幂等性:支持重复调用,不会产生副作用

三、性能优化:展现技术深度

3.1 数据库优化

3.1.1 分库分表策略

/**
 * 订单分表策略(按用户ID取模)
 */
@Component
public class OrderShardingStrategy {
    
    private static final int TABLE_COUNT = 16; // 16张分表
    
    /**
     * 计算分表索引
     */
    public int calculateShardIndex(Long userId) {
        return (int) (userId % TABLE_COUNT);
    }
    
    /**
     * 动态数据源路由
     */
    @Aspect
    public static class OrderDataSourceAspect {
        
        @Around("@annotation(sharding)")
        public Object routeDataSource(ProceedingJoinPoint pjp, Sharding sharding) throws Throwable {
            // 获取用户ID参数
            Object[] args = pjp.getArgs();
            Long userId = extractUserId(args, sharding.paramIndex());
            
            // 计算分表索引
            int shardIndex = calculateShardIndex(userId);
            
            // 设置数据源和表名
            DynamicDataSourceContext.set(
                String.format("order_db_%d", shardIndex % 4), // 4个库
                String.format("order_%d", shardIndex)        // 16张表
            );
            
            try {
                return pjp.proceed();
            } finally {
                DynamicDataSourceContext.clear();
            }
        }
    }
}

// 使用示例
@Mapper
public interface OrderMapper {
    
    @Sharding(paramIndex = 0)
    @Insert("INSERT INTO ${tableName} (order_no, user_id, amount) " +
            "VALUES (#{order.orderNo}, #{order.userId}, #{order.amount})")
    void insert(@Param("order") Order order);
    
    @Sharding(paramIndex = 0)
    @Select("SELECT * FROM ${tableName} WHERE user_id = #{userId} " +
            "ORDER BY id DESC LIMIT #{offset}, #{limit}")
    List<Order> selectByUserId(@Param("userId") Long userId, 
                               @Param("offset") int offset, 
                               @Param("limit") int limit);
}

设计亮点:

  • 水平分表:按用户ID取模,数据均匀分布
  • 动态数据源:支持读写分离,多个数据库实例
  • 注解驱动:通过注解自动路由,业务代码无侵入

3.1.2 慢查询优化

-- 问题:查询用户订单列表,包含商品信息
SELECT o.*, p.name, p.price 
FROM order o 
JOIN order_item oi ON o.id = oi.order_id 
JOIN product p ON oi.sku_id = p.id 
WHERE o.user_id = 12345 
ORDER BY o.create_time DESC 
LIMIT 0, 20;

-- 优化1:添加覆盖索引
ALTER TABLE order ADD INDEX idx_user_time (user_id, create_time DESC);

-- 优化2:使用延迟关联(减少回表)
SELECT o.* 
FROM order o 
INNER JOIN (
    SELECT id 
    FROM order 
    WHERE user_id = 12345 
    ORDER BY create_time DESC 
    LIMIT 0, 20
) AS tmp ON o.id = tmp.id;

-- 优化3:反范式设计,冗余商品信息
-- 在order_item表中冗余商品名称和价格,避免JOIN
ALTER TABLE order_item ADD COLUMN product_name VARCHAR(200);
ALTER TABLE order_item ADD COLUMN product_price DECIMAL(10,2);

设计亮点:

  • 索引优化:建立合适的联合索引
  • 延迟关联:减少不必要的回表操作
  • 反范式设计:用空间换时间,减少JOIN

3.2 缓存优化

3.2.1 缓存穿透、击穿、雪崩防护

@Component
public class CacheProtectionService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 防止缓存穿透(查询不存在的数据)
     */
    public <T> T getWithPenetrationProtection(String key, Callable<T> dbQuery) {
        // 1. 先查缓存
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            // 注意:空值也需要缓存
            if (cached instanceof NullValue) {
                return null;
            }
            return (T) cached;
        }
        
        // 2. 查询数据库
        try {
            T result = dbQuery.call();
            
            // 3. 缓存结果(包括空值)
            if (result != null) {
                redisTemplate.opsForValue().set(key, result, 30, TimeUnit.MINUTES);
            } else {
                // 缓存空值,设置较短过期时间
                redisTemplate.opsForValue().set(key, NullValue.INSTANCE, 5, TimeUnit.MINUTES);
            }
            
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 防止缓存击穿(热点key过期时大量请求打到DB)
     */
    public <T> T getWithBreakdownProtection(String key, Callable<T> dbQuery) {
        // 1. 尝试获取缓存
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return (T) cached;
        }
        
        // 2. 获取分布式锁
        String lockKey = "lock:" + key;
        Boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (Boolean.TRUE.equals(locked)) {
            try {
                // 双重检查
                cached = redisTemplate.opsForValue().get(key);
                if (cached != null) {
                    return (T) cached;
                }
                
                // 查询数据库
                T result = dbQuery.call();
                
                // 缓存结果(随机过期时间,防止雪崩)
                int randomExpire = 30 + new Random().nextInt(30); // 30-60分钟
                redisTemplate.opsForValue().set(key, result, randomExpire, TimeUnit.MINUTES);
                
                return result;
            } finally {
                redisTemplate.delete(lockKey);
            }
        } else {
            // 等待并重试
            try {
                Thread.sleep(100);
                return getWithBreakdownProtection(key, dbQuery);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
    }
    
    /**
     * 防止缓存雪崩(大量key同时过期)
     */
    public void setWithAvalancheProtection(String key, Object value, int expireSeconds) {
        // 添加随机值(0-30%的随机偏移)
        int randomOffset = (int) (expireSeconds * 0.3 * Math.random());
        int finalExpire = expireSeconds + randomOffset;
        
        redisTemplate.opsForValue().set(key, value, finalExpire, TimeUnit.SECONDS);
    }
}

// 空值标记类
class NullValue implements Serializable {
    static final NullValue INSTANCE = new NullValue();
    private static final long serialVersionUID = 1L;
}

设计亮点:

  • 穿透防护:缓存空值,避免无效查询
  • 击穿防护:分布式锁控制并发DB查询
  • 雪崩防护:随机过期时间,避免同时失效

四、高可用与容灾:展现工程能力

4.1 限流与降级

/**
 * 限流器(基于Redis的令牌桶)
 */
@Component
public class RateLimiter {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 尝试获取令牌
     * @param key 限流key(如:user:123:order_create)
     * @param permits 令牌数量
     * @param interval 时间窗口(秒)
     * @return 是否获取成功
     */
    public boolean tryAcquire(String key, int permits, int interval) {
        String script = 
            "local current = redis.call('GET', KEYS[1]) " +
            "if current and tonumber(current) >= tonumber(ARGV[1]) then " +
            "  return 0 " +
            "end " +
            "current = redis.call('INCRBY', KEYS[1], ARGV[1]) " +
            "if current == tonumber(ARGV[1]) then " +
            "  redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
            "end " +
            "return 1";
        
        Long result = (Long) redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(key),
            permits,
            interval
        );
        
        return result == 1;
    }
}

/**
 * 降级处理器
 */
@Component
public class DegradationHandler {
    
    private final Map<String, DegradationConfig> configs = new ConcurrentHashMap<>();
    
    /**
     * 执行业务逻辑,支持降级
     */
    public <T> T execute(String key, Callable<T> businessLogic, Supplier<T> fallback) {
        DegradationConfig config = configs.get(key);
        if (config == null) {
            config = loadConfig(key);
        }
        
        // 检查是否触发降级
        if (shouldDegrade(config)) {
            return fallback.get();
        }
        
        try {
            long start = System.currentTimeMillis();
            T result = businessLogic.call();
            long cost = System.currentTimeMillis() - start;
            
            // 更新统计
            updateStats(key, cost, true);
            
            return result;
        } catch (Exception e) {
            updateStats(key, 0, false);
            
            // 错误次数超过阈值,触发降级
            if (config.getErrorCount() > config.getErrorThreshold()) {
                config.setDegrade(true);
                config.setDegradeEndTime(System.currentTimeMillis() + config.getDegradeDuration());
            }
            
            return fallback.get();
        }
    }
    
    private boolean shouldDegrade(DegradationConfig config) {
        if (config.isDegrade()) {
            if (System.currentTimeMillis() > config.getDegradeEndTime()) {
                config.setDegrade(false);
                config.setErrorCount(0);
                return false;
            }
            return true;
        }
        return false;
    }
}

设计亮点:

  • 令牌桶限流:支持突发流量,平滑请求
  • 动态降级:基于错误率和响应时间自动触发
  • 配置热更新:支持运行时调整阈值

4.2 熔断器模式

/**
 * 简单的熔断器实现
 */
@Component
public class CircuitBreaker {
    
    private final Map<String, CircuitBreakerState> states = new ConcurrentHashMap<>();
    
    public enum State {
        CLOSED,      // 正常状态
        OPEN,        // 熔断状态
        HALF_OPEN    // 半开状态(试探性放行)
    }
    
    static class CircuitBreakerState {
        private State state = State.CLOSED;
        private int failureCount = 0;
        private long lastFailureTime = 0;
        private long openTime = 0;
    }
    
    public <T> T execute(String key, Callable<T> businessLogic, Supplier<T> fallback) {
        CircuitBreakerState state = states.computeIfAbsent(key, k -> new CircuitBreakerState());
        
        // 熔断状态检查
        if (state.state == State.OPEN) {
            // 检查是否进入半开状态
            if (System.currentTimeMillis() - state.openTime > 5000) { // 5秒后尝试恢复
                state.state = State.HALF_OPEN;
                state.failureCount = 0;
            } else {
                return fallback.get();
            }
        }
        
        try {
            T result = businessLogic.call();
            
            // 成功:重置状态
            if (state.state == State.HALF_OPEN) {
                state.state = State.CLOSED;
                state.failureCount = 0;
            }
            
            return result;
        } catch (Exception e) {
            // 失败:增加失败计数
            state.failureCount++;
            state.lastFailureTime = System.currentTimeMillis();
            
            // 达到阈值:打开熔断器
            if (state.failureCount >= 5) {
                state.state = State.OPEN;
                state.openTime = System.currentTimeMillis();
            }
            
            return fallback.get();
        }
    }
}

设计亮点:

  • 状态机:清晰的三态转换
  • 自动恢复:半开状态试探性放行
  • 快速失败:熔断期间直接走降级逻辑

五、创新思维:展现差异化优势

5.1 智能推荐与个性化

/**
 * 基于用户行为的个性化推荐
 */
@Service
public class PersonalizedRecommendationService {
    
    @Autowired
    private UserBehaviorRepository behaviorRepository;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 基于协同过滤的推荐
     */
    public List<ProductDTO> recommendForUser(Long userId, int limit) {
        String cacheKey = "recommend:user:" + userId;
        
        // 1. 尝试从缓存获取
        List<ProductDTO> cached = (List<ProductDTO>) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null && !cached.isEmpty()) {
            return cached;
        }
        
        // 2. 获取用户行为特征
        UserBehaviorFeatures features = behaviorRepository.getUserFeatures(userId);
        
        // 3. 基于内容的推荐(商品标签匹配)
        List<Long> contentBased = recommendByContent(features.getPreferenceTags());
        
        // 4. 基于协同过滤(相似用户喜欢的商品)
        List<Long> collaborative = recommendByCollaborative(userId);
        
        // 5. 混合推荐策略
        List<Long> combined = mergeRecommendations(contentBased, collaborative);
        
        // 6. 获取商品详情
        List<ProductDTO> result = productRepository.findByIds(combined.stream()
            .limit(limit).collect(Collectors.toList()));
        
        // 7. 缓存结果(5分钟)
        redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
        
        return result;
    }
    
    /**
     * 实时推荐更新(监听用户行为事件)
     */
    @EventListener
    public void onUserBehavior(UserBehaviorEvent event) {
        // 异步更新推荐缓存
        CompletableFuture.runAsync(() -> {
            String cacheKey = "recommend:user:" + event.getUserId();
            redisTemplate.delete(cacheKey);
            
            // 触发推荐计算
            List<ProductDTO> newRecs = recommendForUser(event.getUserId(), 10);
            redisTemplate.opsForValue().set(cacheKey, newRecs, 5, TimeUnit.MINUTES);
        });
    }
}

设计亮点:

  • 混合推荐:结合多种推荐算法
  • 实时更新:监听用户行为事件,动态调整
  • 缓存策略:平衡实时性和性能

5.2 营销系统设计

/**
 * 灵活的促销规则引擎
 */
@Component
public class PromotionEngine {
    
    /**
     * 计算订单促销优惠
     */
    public PromotionResult calculate(Order order, List<Promotion> promotions) {
        PromotionResult result = new PromotionResult();
        
        // 按优先级排序
        promotions.sort(Comparator.comparingInt(Promotion::getPriority).reversed());
        
        for (Promotion promotion : promotions) {
            if (promotion.match(order)) {
                Discount discount = promotion.apply(order);
                result.addDiscount(discount);
                
                // 标记已使用(互斥规则)
                order.addUsedPromotion(promotion.getId());
            }
        }
        
        return result;
    }
}

/**
 * 促销规则接口
 */
public interface Promotion {
    int getPriority(); // 优先级
    boolean match(Order order); // 匹配条件
    Discount apply(Order order); // 计算优惠
}

/**
 * 满减促销实现
 */
@Component
public class FullReductionPromotion implements Promotion {
    
    @Override
    public int getPriority() {
        return 100;
    }
    
    @Override
    public boolean match(Order order) {
        // 满200减30
        return order.getTotalAmount().compareTo(new BigDecimal("200")) >= 0;
    }
    
    @Override
    public Discount apply(Order order) {
        return Discount.fixed(new BigDecimal("30"), "满200减30");
    }
}

/**
 * 优惠券促销实现
 */
@Component
public class CouponPromotion implements Promotion {
    
    @Override
    public int getPriority() {
        return 200; // 优惠券优先级更高
    }
    
    @Override
    public boolean match(Order order) {
        return order.getCouponId() != null;
    }
    
    @Override
    public Discount apply(Order order) {
        Coupon coupon = couponRepository.findById(order.getCouponId());
        return Discount.percentage(coupon.getDiscount(), coupon.getName());
    }
}

设计亮点:

  • 策略模式:促销规则可插拔
  • 优先级控制:支持复杂的优惠叠加规则
  • 扩展性:新增促销类型无需修改核心逻辑

六、面试表达技巧:如何展现专业度

6.1 架构设计表达

错误示范:

“我会用Spring Boot做后端,MySQL做数据库,Redis做缓存…”

专业表达:

“我会采用分层微服务架构,网关层使用Spring Cloud Gateway实现统一入口,服务层按业务领域拆分为用户、商品、订单等独立服务,基础设施层使用MySQL分库分表存储,Redis集群做缓存和分布式锁,RabbitMQ做异步解耦。这种架构的优势是…”

6.2 技术选型理由

错误示范:

“用Redis因为快…”

专业表达:

“选择Redis主要基于三点考虑:第一,Redis支持丰富的数据结构,String适合缓存商品详情,Hash适合购物车,Sorted Set适合排行榜;第二,Redis单线程模型避免并发问题,配合Lua脚本可以实现原子操作;第三,Redis集群模式支持水平扩展,满足未来业务增长需求…”

6.3 细节追问应对

面试官: “如果Redis挂了怎么办?”

专业回答:

“这是一个很好的问题,我会从三个层面应对:

  1. 缓存穿透保护:即使Redis不可用,系统也能直接查询数据库,虽然性能下降但保证可用性
  2. 降级策略:Redis故障时,对非核心功能(如推荐、统计)直接降级,核心功能(下单、支付)走DB
  3. 容灾方案:Redis采用主从+哨兵模式,故障自动切换;同时数据库有读写分离,保证极端情况下查询能力”

七、总结:打造你的亮点方案

在面试中展现专业深度和创新思维,关键在于:

  1. 架构设计要有高度:展示系统性思维,不只是CRUD
  2. 核心模块要有深度:深入细节,展现解决复杂问题的能力
  3. 性能优化要有数据:用具体指标说明优化效果
  4. 高可用要有方案:展示工程化思维和容灾能力
  5. 创新点要有价值:结合业务场景,提出有实际意义的创新

记住,面试官更看重的是你思考问题的方式解决复杂问题的能力,而不仅仅是知识点的堆砌。通过本文的方案,你应该能够构建一个既有技术深度又有业务价值的商城系统设计,在面试中脱颖而出。