1. 项目背景与核心需求
酒水电商行业近年来呈现爆发式增长,2023年全球在线酒水销售额突破580亿美元。对于计算机专业毕业生而言,开发一个Java实现的智能酒水销售系统既能展示全栈开发能力,又能切入热门电商赛道。这个毕设项目的核心在于构建一个包含智能推荐、交易风控和库存管理的完整电商解决方案。
传统酒水销售系统普遍存在三个痛点:商品推荐精准度低、防欺诈机制薄弱、库存同步延迟。我们的系统需要实现:
- 基于用户画像的实时推荐算法
- 多维度交易风险识别(如频繁下单检测)
- 分布式库存的强一致性保证
提示:选择Java作为开发语言时,建议采用Spring Boot 3.x+JDK17组合,这是目前企业级开发的主流技术栈,既能满足毕设的技术先进性要求,又便于后续扩展为真实商用系统。
2. 系统架构设计
2.1 技术栈选型分析
后端采用分层架构:
- Web层:Spring Boot 3.1.5(内嵌Tomcat 10.1+)
- 服务层:Spring Cloud 2022.0.4(含OpenFeign、Hystrix)
- 数据层:MyBatis-Plus 3.5.3 + MySQL 8.0(读写分离)
- 缓存:Redis 7.0(集群模式)
前端技术组合:
- 基础框架:Vue 3.3 + Element Plus 2.3
- 构建工具:Vite 4.3
- 可视化:ECharts 5.4(销售数据展示)
2.2 微服务拆分方案
将系统拆分为六个微服务:
- 用户服务(含权限、会员体系)
- 商品服务(SKU管理、智能推荐)
- 订单服务(交易核心流程)
- 支付服务(对接第三方支付)
- 风控服务(规则引擎决策)
- 库存服务(分布式事务管理)
每个服务独立数据库,通过Nacos 2.2实现服务注册发现。这种设计在毕设演示时可以通过Docker Compose一键启动,也符合企业级应用的标准架构。
3. 核心功能实现细节
3.1 智能推荐系统实现
采用混合推荐策略:
// 基于内容的推荐算法示例 public List<Product> contentBasedRecommend(User user) { // 1. 提取用户历史行为特征 UserProfile profile = profileService.buildProfile(user.getId()); // 2. 计算商品特征相似度 return productService.findAll().stream() .sorted(Comparator.comparingDouble(p -> cosineSimilarity(profile.getPrefVector(), p.getFeatureVector()))) .limit(10) .collect(Collectors.toList()); } // 协同过滤补充 public List<Product> cfRecommend(Long userId) { // 使用Mahout库实现 DataModel model = new FileDataModel(new File("ratings.csv")); UserSimilarity similarity = new PearsonCorrelationSimilarity(model); return recommender.recommend(userId, 10); }实际部署时需要处理冷启动问题,我们的解决方案是:
- 新用户注册时强制填写口味偏好问卷
- 采用热销商品作为默认推荐
- 实时监控CTR调整算法权重
3.2 交易风控模块设计
风控规则引擎采用Drools 7.73实现,关键规则包括:
| 规则类型 | 触发条件 | 处置措施 |
|---|---|---|
| 高频下单 | 5分钟内>3笔同商品订单 | 强制人工审核 |
| 异地登录 | 登录IP与常用地不符 | 二次验证 |
| 价格异常 | 订单金额低于市场价30% | 冻结交易 |
规则配置界面采用可视化设计,便于毕设演示时动态调整阈值。核心检测逻辑:
public RiskCheckResult checkOrder(Order order) { KieSession session = kieContainer.newKieSession(); session.insert(order); session.insert(order.getUser()); session.fireAllRules(); return order.getRiskResult(); }4. 关键技术难点解决方案
4.1 库存超卖问题处理
采用Redis+Lua脚本实现分布式锁:
-- inventory.lua local key = KEYS[1] local change = tonumber(ARGV[1]) local current = tonumber(redis.call('GET', key)) if current >= change then redis.call('DECRBY', key, change) return 1 else return 0 endJava调用示例:
public boolean deductStock(String productCode, int num) { String script = ResourceUtils.getScript("inventory.lua"); Long result = redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList("stock:" + productCode), String.valueOf(num)); return result == 1; }4.2 分布式事务一致性
订单创建采用Saga模式:
- 订单服务:创建预订单(状态为PENDING)
- 库存服务:预扣减库存
- 支付服务:处理支付
- 最终确认(或补偿)
使用Seata 2.0实现事务协调:
@GlobalTransactional public Order createOrder(OrderDTO dto) { // 1. 创建订单记录 Order order = orderMapper.create(dto); // 2. 扣减库存 inventoryFeignClient.deduct(dto.getSku(), dto.getQuantity()); // 3. 发起支付 paymentFeignClient.create(order.getId(), dto.getAmount()); return order; }5. 毕设开发实践建议
5.1 版本控制策略
建议采用Git Flow工作流:
- master分支:仅存放稳定版本(用于答辩演示)
- develop分支:日常开发主干
- feature分支:每个功能独立开发
- hotfix分支:紧急问题修复
.gitignore需要特别配置:
# IDE相关 .idea/ *.iml # 构建输出 target/ dist/ # 本地配置文件 application-local.yml5.2 性能优化技巧
- 接口响应时间优化:
- 启用MyBatis二级缓存
- 使用@Cacheable注解缓存热点数据
- 添加Gzip压缩(spring.compression.enabled=true)
- 前端加载优化:
- 路由懒加载
- 图片使用WebP格式
- 启用HTTP/2服务器推送
- 压力测试建议:
- 使用JMeter模拟200并发用户
- 重点测试下单接口TPS
- 监控JVM内存使用情况(-Xmx建议设为512MB)
6. 答辩准备要点
6.1 演示场景设计
建议准备三个典型用户旅程:
- 普通用户:浏览->加购->下单->支付
- 管理员:商品上架->订单审核->数据统计
- 风控案例:触发规则->人工干预
每个场景应准备脚本数据:
-- 测试用户 INSERT INTO users VALUES(1,'normal@test.com','$2a$10$xV4...',0); -- 测试商品 INSERT INTO products VALUES(1001,'茅台飞天',1599.00,100);6.2 常见问题应对
技术类问题准备:
- 如何保证推荐算法的实时性?(答案:使用Redis实时更新用户特征向量)
- 系统最大支持多少并发?(答案:实测数据+水平扩展方案)
- 为什么选择Drools而不是自己写规则引擎?(答案:声明式编程效率高)
项目意义类问题:
- 与现有电商平台的区别?(突出智能推荐和风控特色)
- 商业落地的可能性?(从酒水行业特殊性切入)
我在实际开发中发现,使用Lombok插件可以大幅减少样板代码,但需要特别注意:
- 确保IDE安装了Lombok插件
- 编译参数添加-javaagent:lombok.jar
- 避免在实体类中使用@Builder导致JSON序列化问题
对于需要快速原型演示的功能,可以考虑使用Mock数据:
@Profile("dev") @RestController public class MockController { @GetMapping("/api/products") public List<Product> mockProducts() { return IntStream.range(1,10) .mapToObj(i -> new Product(i, "商品"+i, i*100, i*10)) .collect(Collectors.toList()); } }