1. 项目概述:黔醉酒业白酒销售系统技术架构解析
黔醉酒业白酒销售系统是一个典型的Java Web全栈项目,采用当前主流的技术栈组合:SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0。这个系统最核心的价值在于实现了基于协同过滤算法的白酒个性化推荐功能,这在酒类垂直电商领域具有创新意义。
我在实际开发中发现,酒类电商与传统电商的最大区别在于用户决策链路更长、产品专业度要求更高。系统通过协同过滤算法分析用户的浏览、购买行为,建立"用户-商品"关联矩阵,能够有效提升高单价白酒的转化率。整个技术栈的选择也体现了现代Java Web开发的典型特征:后端采用SpringBoot2快速构建微服务架构,前端使用Vue3实现响应式界面,ORM层通过MyBatis-Plus简化数据库操作,MySQL8.0则提供了完善的JSON支持和窗口函数等高级特性。
提示:选择SpringBoot2而非SpringBoot3主要是考虑到企业现有系统的兼容性需求,SpringBoot2的社区支持更成熟稳定
2. 核心技术点深度解析
2.1 协同过滤算法实现
系统采用基于用户的协同过滤(UserCF)算法,核心代码位于RecommendService.java中。算法实现主要分为三个步骤:
- 用户行为数据收集:通过埋点收集用户浏览、收藏、购买等行为
// 用户行为权重定义 public enum BehaviorType { BROWSE(1), COLLECT(3), PURCHASE(5); // ... }- 相似度矩阵计算:使用改进的余弦相似度算法,加入时间衰减因子
public double similarity(User a, User b) { // 时间衰减因子:λ=0.5 double timeDecay = Math.pow(0.5, (now - lastTime)/86400); return cosineSimilarity(a.getBehaviorVector(), b.getBehaviorVector()) * timeDecay; }- 推荐结果生成:取相似度最高的K个邻居用户的行为进行加权推荐
在实际应用中,我们发现白酒品类具有明显的场景化特征(如商务宴请、婚庆、收藏等),因此在基础算法上增加了场景维度,使推荐结果更符合用户真实需求。
2.2 SpringBoot2后端架构设计
后端采用典型的三层架构,但有几个值得注意的设计亮点:
- 多数据源配置:主库用于交易,从库用于报表和分析
@Configuration @MapperScan(basePackages = "com.qianzui.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate") public class PrimaryDataSourceConfig { // 主数据源配置 }- 分布式锁实现:使用Redisson解决秒杀场景下的超卖问题
public boolean seckill(Long productId) { RLock lock = redissonClient.getLock("seckill:" + productId); try { if(lock.tryLock(1, 10, TimeUnit.SECONDS)) { // 扣减库存逻辑 } } finally { lock.unlock(); } }- 接口幂等设计:通过token机制防止重复提交
@PostMapping("/order/create") public Result createOrder(@RequestBody OrderDTO dto, @RequestHeader("Idempotent-Token") String token) { if(!redisTemplate.opsForValue().setIfAbsent(token, "1", 5, TimeUnit.MINUTES)) { return Result.fail("请勿重复提交"); } // 创建订单逻辑 }2.3 Vue3前端工程化实践
前端采用Vue3+TypeScript技术栈,主要技术特点包括:
- 组件化开发:基于Composition API重构业务组件
// 使用setup语法糖 <script setup lang="ts"> const recommendList = ref<Product[]>([]) const loading = ref(false) const loadRecommend = async (userId: string) => { loading.value = true recommendList.value = await getRecommendations(userId) loading.value = false } </script>- 状态管理:Pinia替代Vuex实现更简洁的状态管理
export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: null as UserInfo | null }), actions: { async login(params: LoginParams) { const res = await api.login(params) this.token = res.token this.userInfo = res.userInfo } } })- 性能优化:基于v-memo的列表渲染优化
<template> <div v-for="item in list" v-memo="[item.id]"> <!-- 复杂列表项内容 --> </div> </template>3. 数据库设计与优化
3.1 MySQL8.0特性应用
系统充分利用了MySQL8.0的新特性:
- 窗口函数实现销售排行榜
SELECT product_id, sales_volume, RANK() OVER(ORDER BY sales_volume DESC) as rank FROM product_sales WHERE category = '白酒'- JSON字段存储商品扩展属性
CREATE TABLE products ( id BIGINT PRIMARY KEY, base_info JSON, spec_info JSON, INDEX idx_category ((CAST(base_info->'$.category' AS CHAR(20)))) );- 公用表表达式(CTE)优化复杂查询
WITH user_behavior AS ( SELECT user_id, product_id, behavior_type, behavior_time FROM user_behaviors WHERE behavior_time > DATE_SUB(NOW(), INTERVAL 30 DAY) ) SELECT product_id, COUNT(*) as hot_score FROM user_behavior GROUP BY product_id ORDER BY hot_score DESC LIMIT 10;3.2 分库分表策略
针对订单表的大数据量问题,我们采用了按月分表的策略:
- 动态表名拦截器实现
public class OrderTableInterceptor implements InnerInterceptor { @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 根据时间参数动态修改表名 if(parameter instanceof OrderQuery && ((OrderQuery)parameter).getMonth() != null) { String newTable = "orders_" + ((OrderQuery)parameter).getMonth(); String newSql = boundSql.getSql().replace("orders", newTable); resetSql(ms, boundSql, newSql); } } }- 历史订单归档策略
@Scheduled(cron = "0 0 3 * * ?") public void archiveOrders() { LocalDate archiveDate = LocalDate.now().minusMonths(6); String sourceTable = "orders_" + archiveDate.format(DateTimeFormatter.ofPattern("yyyyMM")); String archiveTable = "orders_archive_" + archiveDate.format(DateTimeFormatter.ofPattern("yyyyMM")); jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + archiveTable + " LIKE " + sourceTable); jdbcTemplate.execute("INSERT INTO " + archiveTable + " SELECT * FROM " + sourceTable); jdbcTemplate.execute("DROP TABLE " + sourceTable); }4. 系统部署与性能调优
4.1 容器化部署方案
系统采用Docker Compose进行容器编排:
version: '3' services: app: image: openjdk:11-jre ports: - "8080:8080" volumes: - ./app.jar:/app.jar command: java -jar /app.jar depends_on: - mysql - redis mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: qianzui ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d redis: image: redis:6 ports: - "6379:6379" volumes: - ./redis/data:/data4.2 JVM参数调优
针对白酒促销期间的高并发场景,我们对JVM参数进行了特别优化:
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xms2g -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/heapdump.hprof4.3 缓存策略设计
采用多级缓存架构提升系统响应速度:
- 本地缓存(Caffeine) + Redis分布式缓存
@Cacheable(value = "products", key = "#id", cacheManager = "caffeineCacheManager") public Product getProductById(Long id) { // 数据库查询 } @Cacheable(value = "products", key = "#id", cacheManager = "redisCacheManager") public Product getProductDetail(Long id) { // 数据库查询 }- 热点数据预加载
@Scheduled(fixedRate = 3600000) public void preloadHotProducts() { List<Long> hotIds = productMapper.selectHotProductIds(); hotIds.forEach(id -> { productService.getProductById(id); productService.getProductDetail(id); }); }5. 典型问题排查实录
5.1 MyBatis-Plus分页查询性能问题
现象:当查询结果集超过10万条时,分页查询变慢
解决方案:
- 使用基于游标的分页替代传统LIMIT分页
public IPage<User> selectUsersByCursor(Long lastId, int size) { return lambdaQuery() .gt(lastId != null, User::getId, lastId) .orderByAsc(User::getId) .page(new Page<>(1, size, false)); }- 添加覆盖索引优化查询
ALTER TABLE users ADD INDEX idx_status_create_time (status, create_time, id);5.2 Vue3组件重复渲染问题
现象:推荐列表组件在数据更新时出现闪烁
解决方案:
- 使用v-memo优化渲染性能
<template> <div v-for="item in list" v-memo="[item.id, item.isRecommended]"> <ProductCard :product="item" /> </div> </template>- 合理使用watchEffect替代多个watch
watchEffect(() => { if (userStore.isLoggedIn) { loadRecommendations(userStore.userId) } })5.3 MySQL8.0连接池耗尽问题
现象:高峰时段出现"Too many connections"错误
解决方案:
- 优化连接池配置
spring: datasource: hikari: maximum-pool-size: 50 idle-timeout: 60000 max-lifetime: 1800000 connection-timeout: 30000- 启用连接泄漏检测
@Bean public HikariDataSource dataSource() { HikariConfig config = new HikariConfig(); config.setLeakDetectionThreshold(30000); return new HikariDataSource(config); }6. 项目文档要点解析
系统提供的文档包含以下几个关键部分:
- 架构设计文档
- 系统上下文图
- 部署架构图
- 微服务划分方案
- 数据库设计文档
- ER图
- 表结构说明
- 索引设计规范
- API接口文档
- Swagger UI集成
- 接口签名算法
- 错误码规范
- 运维手册
- 健康检查端点
- 日志收集方案
- 监控指标说明
在白酒推荐算法部分,文档特别强调了AB测试框架的使用方法,包括:
- 如何配置实验分组
- 如何埋点收集效果数据
- 如何进行统计学显著性检验
注意:实际部署时需要根据服务器配置调整JVM参数和连接池大小,文档中的配置值仅供参考