1. 项目概述:一个从零到一的开源商城系统

最近在Github上开源了一个自己从零开始搭建的商城系统,这算是我个人在Web全栈开发领域的一次阶段性总结。这个项目不是简单的增删改查堆砌,而是试图构建一个结构清晰、易于二次开发、且具备一定生产可用性的电商解决方案。我把它开源出来,一方面是希望自己的代码能接受更多同行的审视,获得改进建议;另一方面,也是想为那些希望学习如何构建一个完整商业系统的开发者,提供一个可参考、可运行的“活”案例。如果你正想了解一个现代Web应用如何从设计走向实现,或者你手头有个小项目需要一套电商逻辑,那么这个项目或许能给你带来一些启发。

这个商城系统涵盖了用户端、管理后台和一套相对完整的后端服务。用户端提供了商品浏览、搜索、购物车、订单、支付(模拟)等核心购物流程;管理后台则负责商品、订单、用户、促销等内容的管理。技术栈上,我选择了当前业界比较主流且我个人认为在开发效率和性能上取得较好平衡的组合:Spring Boot作为后端框架,Vue 3作为前端框架,数据库则使用了MySQL和Redis。整个项目的代码结构、命名规范、API设计都力求清晰,注释也比较详尽,目标是让任何一个有相关技术背景的开发者都能快速上手,理解业务逻辑并进行定制开发。

2. 核心架构设计与技术选型考量

2.1 为什么是前后端分离与微服务雏形?

我选择了经典的前后端分离架构。这不是一个单体应用把所有东西揉在一起,而是将用户界面(前端)和业务逻辑、数据存取(后端)彻底解耦,通过定义良好的RESTful API进行通信。这样做的好处非常明显:前后端可以并行开发,互不干扰;前端可以独立部署,灵活选用技术栈(比如未来可以轻松替换成React或其它框架);后端API可以同时为Web、移动App甚至第三方应用提供服务,扩展性更强。

在服务划分上,我并没有一开始就引入复杂的Spring Cloud生态,而是采用了“模块化单体”的思路。具体来说,我在一个Spring Boot工程内,按照业务域(Domain)清晰地划分了多个模块(Module),例如 user-service product-service order-service payment-service 等。每个模块在代码层面是独立的,拥有自己的控制器、服务层、数据访问层和领域模型。它们共同依赖一个核心的 common 模块,里面放着工具类、通用配置、DTO和异常定义等。

注意:对于个人项目或中小型创业公司初期,过早引入完整的微服务会带来巨大的运维和部署复杂度。这种“模块化单体”是向微服务平滑过渡的绝佳起点。它强迫你在编码阶段就思考服务的边界,未来当业务量真的上来,需要拆分成独立服务部署时,迁移成本会低很多。

2.2 技术栈的深度解析与选型理由

后端:Spring Boot 3 + MyBatis-Plus Spring Boot是Java生态中构建生产级应用的事实标准,它提供了“约定大于配置”的极速开发体验和强大的企业级特性。选择Spring Boot 3是为了拥抱最新的Java生态,比如对GraalVM原生镜像的更好支持(虽然本项目暂未使用),以及性能上的提升。MyBatis-Plus是在原生MyBatis基础上的强力增强,它提供了强大的单表CRUD操作能力,通过简单的继承和注解,就能实现绝大部分数据操作,极大减少了样板代码。同时,它保留了MyBatis灵活编写复杂SQL的能力,在需要多表关联查询或复杂业务逻辑时游刃有余。

数据库:MySQL 8 + Redis 7 关系型数据库是业务系统的基石。MySQL 8在性能、安全性和JSON支持上都有显著提升,完全能满足一个商城系统初期的数据存储需求。表结构设计上,我遵循了基本的数据库范式,但也没有过度设计,在读写性能和一致性之间做了权衡。例如,商品信息、用户信息这类强一致性要求的,放在MySQL;而像首页商品列表、分类信息这类读多写少、允许短暂延迟的数据,则用Redis做缓存。

Redis在这里扮演了多重角色:首先是缓存,减轻数据库压力;其次是会话存储(Spring Session),实现分布式环境下的用户状态共享;再者是作为简单消息队列(使用List结构),处理一些异步任务,比如记录用户操作日志;最后,它还用于实现一些简单的并发控制,比如防止用户重复提交订单、秒杀场景的库存预扣等。

前端:Vue 3 + TypeScript + Pinia + Element Plus Vue 3的Composition API让逻辑组织和复用变得前所未有的清晰,配合TypeScript,可以在开发阶段就捕获大量潜在的类型错误,提升代码健壮性和可维护性。Pinia作为Vue官方的状态管理库,比Vuex更简洁、对TypeScript支持更友好,非常适合管理跨组件的用户状态、购物车数据等。UI框架选择了Element Plus,它组件丰富、文档齐全、社区活跃,能快速搭建出美观且交互一致的管理后台。对于用户端,为了更灵活的样式定制,我主要使用了原生CSS和部分Element Plus组件。

基础设施与工具链

  • 项目管理与构建: Maven用于后端依赖管理和构建,npm/Yarn用于前端。
  • API文档: 集成SpringDoc OpenAPI 3(Swagger UI),后端接口变更后,前端开发者能实时查看最新的API文档和在线调试,极大提升联调效率。
  • 代码风格与质量: 后端使用Spotless自动格式化代码,确保团队代码风格统一;集成Jacoco生成单元测试覆盖率报告。前端使用ESLint和Prettier。
  • 部署: 项目提供了Dockerfile和docker-compose.yml,可以一键将整个系统(后端应用、MySQL、Redis、Nginx)在容器环境中运行起来,简化了部署流程。

3. 核心业务模块实现细节

3.1 用户、商品与购物车:电商的基石

用户系统 的设计除了常规的注册、登录、个人信息管理,我特别注重了安全性。密码存储使用了BCrypt强哈希算法,绝对明文存储。登录成功后颁发的JWT(JSON Web Token)包含了用户ID和角色信息,并设置了合理的过期时间。为了防止JWT被盗用,我在服务端维护了一个简单的Token黑名单(存于Redis),用于处理用户主动登出或修改密码后使旧Token失效的场景。

商品系统 是商城的核心。数据库表设计上,除了商品基本信息表( spu ,标准化产品单元),还有商品SKU表( sku ,库存量单位,如不同颜色、尺码)、商品分类表(支持多级分类)、商品属性表、商品图片表等。这里的一个关键设计是SPU和SKU的分离。一个SPU代表一个商品品类(比如“小米14手机”),而SKU则代表具体的规格(比如“黑色 12GB+256GB”)。这样设计便于库存管理和前端展示。

在商品列表查询,特别是涉及复杂筛选和排序时,直接查询MySQL可能会成为性能瓶颈。我的做法是:

  1. 构建商品搜索索引: 当后台管理员上架新商品或修改商品信息时,除了写入数据库,还会异步地将商品的关键信息(ID、标题、价格、分类、主要属性等)同步到Elasticsearch(本项目为了简化,用了一个模拟的“内存索引服务”,但架构上预留了接入ES的接口)。这个索引专门用于复杂搜索。
  2. 列表查询流程: 前端请求商品列表,带有一堆筛选条件。后端首先访问这个搜索服务,快速拿到符合条件的商品ID列表和排序结果。然后,再用这些ID去MySQL里批量查询( WHERE id IN (…) )获取商品的完整详情信息。这就是经典的“搜索引擎+数据库”的查询模式,能很好地平衡查询的灵活性和性能。

购物车 的实现需要考虑用户登录和未登录两种状态。对于未登录用户,购物车数据直接存储在浏览器的 localStorage 中,结构是一个商品SKU ID和数量的键值对。用户登录后,在发起任何购物车操作(如添加商品)时,前端会尝试将 localStorage 中的购物车数据合并到服务端的用户购物车中(基于Redis Hash结构存储)。这个合并逻辑需要处理商品去重和数量累加。这样能保证用户体验的无缝衔接。

3.2 订单与支付:交易的核心闭环

订单流程是电商系统中最复杂、对一致性要求最高的部分。

1. 下单流程(创建订单): 这是一个典型的分布式事务场景。用户点击“提交订单”,后端需要原子性地完成以下操作:

  • 校验商品信息、库存、用户地址。
  • 计算订单总金额(商品金额、运费、优惠券折扣等)。
  • 预扣库存 :这是防止超卖的关键。我采用了Redis分布式锁 + 数据库乐观锁的方案。先用Redis锁确保同一SKU同时只有一个请求进入扣减库存逻辑,然后在数据库中执行类似 UPDATE sku SET stock = stock - ? WHERE id = ? AND stock >= ? 的语句。如果影响行数为0,说明库存不足,回滚并提示用户。
  • 生成订单号(使用“时间戳+随机数+用户ID片段”的规则,确保唯一且粗略有序)。
  • 将订单主信息、订单商品快照、收货地址快照等写入订单表( order )和订单商品表( order_item )。这里写入的是商品快照,而非直接关联商品表,目的是冻结下单时刻的商品信息,即使后续商品价格、标题修改了,也不影响已产生的订单。
  • 清理用户购物车中对应的商品。
  • 如果使用了优惠券,标记优惠券为已使用。

这个过程必须在一个数据库事务中完成,确保任何一步失败,整个操作都能回滚。库存预扣成功后,会有一个“支付超时”的定时任务(通过Redis的过期键监听或定时扫描未支付订单实现),如果订单在指定时间(如30分钟)内未支付,则自动取消订单,并恢复预扣的库存。

2. 支付流程(模拟): 真实的支付需要对接微信支付、支付宝等第三方渠道。在本开源项目中,我实现了一个完整的模拟流程来阐述核心逻辑。

  • 用户选择支付方式,前端调用后端“创建支付”接口。
  • 后端根据支付方式,生成一个包含订单号、金额等信息的支付参数(如果是真实环境,这里就是调用支付平台API获取支付链接或二维码信息)。
  • 在本项目中,我模拟生成了一个“支付页面”URL。用户跳转后,点击“模拟支付成功”按钮。
  • 前端通知后端“支付回调”。后端需要做几件重要的事:
    • 验证回调的合法性 (真实场景需验证签名,防止伪造请求)。
    • 查询订单状态 ,确保订单是“待支付”状态,并且金额匹配(防重放攻击)。
    • 更新订单状态 为“已支付”。
    • 触发后续动作 :记录支付流水、更新商品销量、通知库存系统进行真实扣减(如果之前只是预扣)、发送订单支付成功通知(站内信、邮件、短信)等。
    • 这些后续动作,我大多通过发布“领域事件”(Domain Event)来异步处理。例如,订单支付成功后,发布一个 OrderPaidEvent ,由独立的“消息监听器”来异步处理更新销量、发送通知等非核心链路的任务,保证支付回调接口能快速响应。

3.3 后台管理系统:高效运营的保障

一个功能强大的后台是商城运营的神经中枢。我使用Vue 3 + Element Plus搭建了一个单页面应用(SPA),通过路由和动态菜单控制权限。主要功能模块包括:

  • 仪表盘: 展示关键运营数据,如销售额、订单量、用户增长趋势图(需要接入数据统计服务)。
  • 商品管理: 商品的增删改查、上下架、批量操作。这里实现了富文本编辑器(用于商品详情)和图片上传(对接对象存储服务或本地存储)。
  • 订单管理: 订单列表、详情查看、订单状态手动变更(如发货、备注)、导出订单数据。
  • 用户管理: 用户列表、禁用/启用账户、查看用户订单历史。
  • 促销管理: 优惠券的创建(满减、折扣)、发放、核销记录;未来可扩展秒杀、团购等活动管理。
  • 系统管理: 管理员账号、角色权限(RBAC模型)的配置。

后台的所有操作都通过调用后端的API完成,确保了数据逻辑的统一。权限控制上,后端接口通过Spring Security的 @PreAuthorize 注解进行方法级权限校验,前端则根据用户角色动态渲染菜单和操作按钮。

4. 开发、部署与运维实践

4.1 本地开发环境快速搭建

为了让其他开发者能最快地跑起来项目,我在项目根目录的 README.md 中提供了详细的步骤。核心就是利用Docker Compose。

# 1. 克隆项目
git clone https://github.com/your-username/mall-project.git
cd mall-project

# 2. 使用Docker Compose启动基础设施(MySQL, Redis)
docker-compose up -d mysql redis

# 3. 导入初始SQL脚本(创建数据库和表结构)
mysql -h127.0.0.1 -uroot -p123456 < sql/mall_init.sql

# 4. 启动后端Spring Boot应用
# 可以在IDE中直接运行 MallApplication.java,或者用Maven打包后运行。

# 5. 启动前端管理后台
cd mall-admin
npm install
npm run dev

# 6. 启动前端用户端(如果独立)
cd mall-app
npm install
npm run dev

启动后,访问 http://localhost:8080/swagger-ui.html 可以看到所有后端API文档;访问 http://localhost:8081 可以进入管理后台(默认账号admin/123456);用户端则运行在 http://localhost:5173

实操心得:在 docker-compose.yml 中,我不仅定义了MySQL和Redis服务,还为他们配置了数据卷(volumes),将容器内的数据持久化到宿主机。这样即使容器删除重建,数据也不会丢失。对于初学者,理解Docker数据卷的概念是避免“数据蒸发”的关键。

4.2 线上部署与持续集成考量

对于生产环境部署,我提供了基于Docker的部署方案。将后端Spring Boot应用打包成Jar文件,然后编写Dockerfile将其制作成镜像。前端项目通过 npm run build 生成静态文件,由Nginx提供访问服务。

一个完整的 docker-compose.prod.yml 示例如下:

version: '3.8'
services:
  mysql:
    image: mysql:8.0
    container_name: mall-mysql
    environment:
      MYSQL_ROOT_PASSWORD: your_strong_password
      MYSQL_DATABASE: mall
    volumes:
      - ./mysql_data:/var/lib/mysql
      - ./sql/mall_init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - mall-network

  redis:
    image: redis:7-alpine
    container_name: mall-redis
    command: redis-server --appendonly yes
    volumes:
      - ./redis_data:/data
    networks:
      - mall-network

  backend:
    build: ./backend  # 指向包含Dockerfile的后端目录
    container_name: mall-backend
    depends_on:
      - mysql
      - redis
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=mysql
      - REDIS_HOST=redis
    ports:
      - "8080:8080"
    networks:
      - mall-network

  nginx:
    image: nginx:alpine
    container_name: mall-nginx
    volumes:
      - ./frontend/dist:/usr/share/nginx/html  # 挂载前端构建产物
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf # 自定义Nginx配置
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - backend
    networks:
      - mall-network

networks:
  mall-network:
    driver: bridge

在这个配置中,所有服务通过一个自定义的Docker网络 mall-network 互联,使用服务名(如 mysql )即可相互访问,无需关心IP地址变化。

对于更正式的持续集成/持续部署(CI/CD),可以在代码仓库(如Github)中配置Actions。一个简单的CI流水线可以包括:代码拉取 -> 运行单元测试 -> 构建后端Jar包和前端静态文件 -> 构建Docker镜像 -> 推送到私有镜像仓库。CD部分则可以通过SSH连接到服务器,拉取新镜像并重启容器服务。这部分脚本我也在项目 ./github/workflows 目录下提供了示例。

4.3 性能优化与监控要点

当系统有一定用户量后,性能优化就提上日程。除了前面提到的Redis缓存和搜索分离,还有几点可以考虑:

  1. 数据库层面:

    • 索引优化: 为订单表的 user_id create_time ,商品SKU表的 spu_id category_id 等高频查询字段建立索引。使用 EXPLAIN 命令分析慢查询。
    • 读写分离: 当读压力很大时,可以考虑引入MySQL主从复制,将读请求路由到从库,写请求走主库。这可以通过ShardingSphere-JDBC等中间件透明地实现。
    • 分库分表: 对于订单这种可能海量增长的数据,提前规划分表策略,比如按用户ID哈希或按创建月份分表。
  2. 应用层面:

    • 连接池调优: 合理配置HikariCP(Spring Boot默认连接池)的参数,如 maximumPoolSize connectionTimeout
    • 线程池隔离: 将不同的业务操作(如支付回调、发送邮件)放到不同的线程池中,避免一个慢操作拖垮整个应用。
    • JVM调优: 根据服务器内存设置合理的堆大小( -Xms , -Xmx )和垃圾回收器参数。
  3. 监控与告警:

    • 应用监控: 集成Spring Boot Actuator暴露健康检查、指标等信息。再通过Prometheus采集这些指标,用Grafana制作可视化仪表盘,监控QPS、响应时间、错误率、JVM内存等。
    • 日志聚合: 使用ELK(Elasticsearch, Logstash, Kibana)或Loki套件,将分散在各个容器/服务器上的应用日志集中收集、索引和展示,便于问题排查。
    • 链路追踪: 在微服务架构或复杂调用链中,集成SkyWalking或Zipkin,可以清晰看到一个用户请求经过了哪些服务,每个环节耗时多少,是定位性能瓶颈的利器。

5. 常见问题与排查技巧实录

在实际开发和部署过程中,我踩过不少坑,也总结了一些排查问题的经验。

5.1 开发环境常见问题

问题1:本地启动后端服务,连接MySQL失败,报错“Public Key Retrieval is not allowed”。

  • 原因: 较新版本的MySQL驱动出于安全考虑,默认禁止客户端自动从服务器获取公钥。
  • 解决: 在Spring Boot的数据库连接配置( application.yml )中,添加连接参数: allowPublicKeyRetrieval=true 。但请注意,这仅用于开发环境,生产环境应通过其他方式确保连接安全。
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

问题2:前端Vue项目 npm install 时网络超时或速度极慢。

  • 原因: npm默认源在国内访问可能不稳定。
  • 解决: 切换为国内镜像源。可以使用 nrm 工具快速切换。
# 安装nrm
npm install -g nrm
# 列出可用源
nrm ls
# 切换为淘宝源
nrm use taobao
# 然后再执行 npm install

问题3:Redis连接失败,报“Connection refused”。

  • 检查步骤:
    1. 确认Redis容器是否正常运行: docker ps | grep redis
    2. 确认Spring Boot配置中的Redis主机和端口是否正确。在Docker Compose网络中,主机名应为服务名 redis ,而不是 localhost
    3. 检查Redis是否设置了密码,并在Spring Boot配置中正确配置了 spring.data.redis.password
    4. 进入Redis容器内部测试: docker exec -it mall-redis redis-cli ping ,应该返回 PONG

5.2 线上部署与运行时问题

问题4:应用运行一段时间后,响应变慢,数据库连接池报超时。

  • 排查思路:
    1. 查看应用日志: 搜索 Timeout Connection is not available 等关键字。
    2. 检查数据库连接数: 登录MySQL,执行 SHOW PROCESSLIST; ,查看当前连接数和活跃查询。如果连接数接近或达到 max_connections 上限,或者有大量Sleep状态的连接,可能是连接泄漏。
    3. 检查应用连接池配置: 确认 maximumPoolSize 设置是否合理,是否远小于数据库的 max_connections 。检查代码中是否每次操作后都正确关闭了数据库连接(MyBatis-Plus通常会自动处理,但手动获取 Connection 时需注意)。
    4. 检查是否有慢查询: 在MySQL中开启慢查询日志,分析耗时长的SQL语句并进行优化。
  • 临时缓解: 重启应用可以释放所有泄漏的连接,但这是治标不治本。

问题5:用户反馈“商品已下单成功,但库存没扣减”,导致超卖。

  • 这是最严重的问题之一,必须彻底排查。
    1. 核对库存扣减逻辑: 回顾“下单流程”章节,确认是否严格使用了“Redis分布式锁 + 数据库乐观锁”的方案。检查扣减库存的SQL语句是否为 UPDATE ... SET stock = stock - ? WHERE id = ? AND stock >= ?
    2. 检查并发场景: 在高并发下单(如秒杀)时,仅靠数据库乐观锁可能压力巨大,大量请求会失败。此时需要结合更高级的方案,如将库存提前加载到Redis中,在Redis中用 DECR 原子操作进行预扣,再将扣减结果异步同步回数据库。
    3. 检查支付超时回滚逻辑: 确认支付超时后,取消订单和恢复库存的代码是否可靠执行。检查定时任务的调度是否正常,以及恢复库存的SQL是否正确。
    4. 查看日志: 搜索订单ID和SKU ID,查看下单和支付回调时的完整日志链,定位是在哪个环节库存数据出现了不一致。

问题6:管理后台图片上传失败。

  • 排查步骤:
    1. 前端检查: 打开浏览器开发者工具的“网络(Network)”标签,查看上传请求的响应状态码和返回信息。常见错误是 413 Request Entity Too Large ,表示文件大小超过Nginx或后端服务器限制。
    2. 后端检查: 查看Spring Boot应用日志。Spring Boot默认对上传文件大小也有限制,需要在 application.yml 中配置:
      spring:
        servlet:
          multipart:
            max-file-size: 10MB
            max-request-size: 20MB
      
    3. 存储服务检查: 如果上传到云存储(如OSS、COS),检查SDK配置的AccessKey、Bucket名称、地域(Region)是否正确,以及Bucket的权限策略是否允许上传。
    4. 磁盘空间检查: 如果上传到服务器本地,检查目标目录的磁盘空间是否已满。

5.3 项目扩展与二次开发建议

如果你基于这个项目进行二次开发,这里有一些方向和建议:

  1. 引入消息队列(如RabbitMQ/RocketMQ/Kafka): 将订单创建成功、支付成功等事件,通过消息队列进行异步解耦。可以专门开发一个“消息消费者”服务来处理发送邮件、短信、更新排行榜等非实时性任务,提升主流程的响应速度和解耦程度。
  2. 构建独立的搜索服务: 将项目中模拟的搜索逻辑,替换为真正的Elasticsearch或Solr。这能极大提升商品搜索、筛选和排序的性能与灵活性。
  3. 实现分布式事务: 如果未来将 user-service order-service 等模块拆分为独立部署的微服务,那么跨服务的下单(涉及库存、订单、优惠券)就需要引入分布式事务解决方案,如Seata的AT模式,或者采用最终一致性的方案(如基于消息队列)。
  4. 丰富促销体系: 当前主要实现了优惠券。可以扩展秒杀、拼团、积分商城、会员折扣等更复杂的营销玩法。这些功能对系统的并发处理能力、数据一致性要求更高,是很好的技术挑战。
  5. 完善监控与运维: 如前所述,集成APM、日志中心、告警系统,让系统在线上运行时更加透明、可控。

这个开源项目就像一块“毛坯房”,提供了坚固的主体结构和水电管线(核心架构与基础模块)。你可以根据自己的业务需求和技术偏好,对它进行“精装修”和“扩建”。无论是学习Spring Boot、Vue的全栈开发,还是研究电商业务的高并发、一致性难题,希望这个项目都能成为一个有价值的起点。代码已经放在Github上,欢迎Star、Fork,更期待你的Issue和Pull Request,让我们一起把它变得更好。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐