最近帮朋友捣鼓了一套游泳用品专卖店管理系统从需求梳理到最终部署上线前后折腾了小一个月。这个项目用 Spring Boot 做后端前端选了 Vue3 配合 Element Plus典型的电商后台加商城前台的双端结构。如果你正在找基于 Spring Boot 的毕设题目或者想给自家小店做个进销存加线上商城这篇内容应该能帮你省不少踩坑的时间。先说这套系统到底解决什么问题游泳用品店不像普通服装店货品有很强的季节性泳镜、泳衣、泳帽、耳塞鼻夹这些配件规格多、有效期和材质区别大而且临近夏天销量会突然暴增。纯靠人工记台账库存对不上、订单漏发、客户复购没法跟踪这些问题在小店里太常见了。所以系统核心就盯住三件事商品管理要细、库存要实时、订单要跑得顺。1. 整体设计思路与技术选型拆解1.1 为什么是 Spring Boot 而不是 SSH 或 SSM老一代做 Java Web 的开发者都经历过 SSHStruts2 Spring Hibernate和 SSMSpring SpringMVC MyBatis时代。那个年代配置 XML 能把人配吐一个数据源要写十几行配置部署一次 Tomcat 要小心翼翼。Spring Boot 最大的价值是约定大于配置内嵌 Tomcat、自动配置、起步依赖让项目从创建到跑起来只要几分钟。选 Spring Boot 有几个非常实际的理由。第一自动配置机制带来的效率提升对一个人开发的小项目太重要了。第二生态成熟无论是做权限控制的 Spring Security还是做持久层的 MyBatis Plus都能无缝整合。第三部署灵活一个 jar 包搞定服务器上只要有 JDK 就能跑。我这次用的是 Spring Boot 2.7.18这里多说一句如果不是特别需要 JDK17 的新特性别急着上 Spring Boot 3.x后面我会专门讲版本选择的坑。1.2 技术栈选型的完整清单这套系统最终用的技术栈是经过对比后敲定的每一项都有明确理由。后端用 Spring Boot 2.7.18 搭配 MyBatis Plus 3.5.xMyBatis Plus 比原生 MyBatis 好的地方在于内置了通用 CRUD 方法、分页插件和代码生成器单表操作基本不需要手写 SQL能省下大量时间用于核心业务逻辑。前端这边选了 Vue3 Element Plus Vite 的组合。Vue3 的 Composition API 比 Vue2 的 Options API 更灵活Element Plus 组件库做后台管理界面非常顺手表格、表单、弹窗、分页这些都是现成的。Vite 做构建工具比 Webpack 快非常多尤其在开发模式下热更新体验是质的飞跃。前台商城页面为了兼顾 SEO 选用了服务端渲染方案不过如果你只是做毕设直接用 Vue3 做单页应用也完全可以。数据库用 MySQL 8.0Redis 做缓存和会话保持。选 Redis 而不是简单地用 Session是因为考虑到多个模块之间要共享登录状态而且商品分类、轮播图这些热点数据需要缓存加速。1.3 模块划分与工程结构设计项目采用标准的 Maven 多模块结构还是单模块我这次用了单模块坦率讲单模块在中小型项目中维护起来更方便打包部署也更简单。如果你是想深入学习工程化可以从一开始就拆成多模块但说实话复杂度和收益不成正比。包结构用经典的分层架构controller控制层、service业务层、mapper持久层、entity实体类、config配置类、common公共工具类。控制层只做参数接收和结果封装业务逻辑全部下沉到 service这样做的好处是后期如果要把业务逻辑抽出来做微服务迁移成本最低。2. 核心业务模块设计与实现要点2.1 用户与权限模块JWT 单点登录方案会员端和管理端是两套完全不同的用户体系这一点在设计之初就要想清楚。会员用户面向 C 端消费者关注的是下单、支付、查看订单这些操作。管理端用户面向店长和店员关注的是商品上架、库存管理、订单处理、销售统计。这里我选择了 JWTJSON Web Token方案流程大概是用户登录成功后后端生成 token 返回给前端前端把 token 存到 localStorage每次请求在 header 里带上 token后端通过拦截器校验 token 有效性。对比传统 Session 方案JWT 的好处是天然适合前后端分离不需要考虑 Session 共享问题。不过 JWT 也有坑最典型的就是 token 无法主动失效。用户修改密码、账号被禁用这些场景已经发出的 token 还是有效的。我的解决办法是在 Redis 里维护一个黑名单把需要失效的 token 加进去拦截器验证时先查一下黑名单。当然还要给 token 设置合理的过期时间管理员端设 2 小时会员端设 7 天维护登录体验和安全性的平衡。2.2 商品管理模块从尺码到季节标签的细节游泳用品商品的属性比普通商品复杂泳衣有尺码、泳镜有度数近视泳镜还有 -1.5、-2.0 这种光学度数、泳帽有成人儿童之分耳塞鼻夹这些配件还要区分材质。如果把所有属性都硬塞到商品表里表结构会非常臃肿而且扩展性差。我的设计思路是采用商品表和属性表分离的方式。SPUStandard Product Unit标准产品单元表存储商品基本信息SKUStock Keeping Unit库存量单位表存储具体规格信息。打个比方一件竞速系列男士连体泳衣是 SPU而红色 M 码和蓝色 L 码就是不同的 SKU每个 SKU 有自己的库存和价格。用户在商城前台选择具体规格时实际上是选中了一个 SKU下单时结算数量和价格才不会乱。还有一个细节是季节标签。泳具品类淡旺季非常明显6 到 9 月是销售高峰秋冬季基本走量的是泳镜防雾剂、游泳耳塞这类配件。系统给每个商品加了一个季节属性字段管理员可以按标签筛选省得旺季前手工翻商品列表。2.3 库存管理与预警机制做游泳用品生意最怕的就是库存对不上。泳衣卖出去一件仓库里明明还有但系统显示没有了或者反过来显示有库存实际找不到了。这些问题的根源通常不是系统 bug而是没有做库存流水记录。这套系统的库存方案是引入库存变更流水表每次入库、出库、销售、退货都写一条流水。商品库存表只存储最终结果流水表记录每一次变更的前值、后值和操作类型。实现起来其实不复杂。service 层里写一个统一的库存变更方法入参是商品 SKU ID、变更数量、操作类型和操作人方法内部先锁库存再更新商品表最后写入流水表。用数据库事务保证这三个操作是原子的任何一步失败都能回滚。库存预警这块我也做了很简单但很实用。每个商品可以设置库存下限后台定时任务每小时跑一次扫描低于下限的 SKU把预警信息写入通知表管理员登录时在首页就能看到警示。实测下来这个功能帮朋友避免了好几次断货事故。2.4 订单模块与购物车实现购物车和订单这两块是前后端配合最密集的地方。购物车功能前端把数据存到浏览器 localstorage 还是后端数据库我推荐后端存储方案。原因很简单用户换了设备购物车还在而且后端存储可以和优惠活动、会员等级联动为后续做营销留了扩展空间。实现上用 Redis 哈希结构存储key 是用户 IDfield 是 SKU IDvalue 是数量性能很好读写都是毫秒级。订单流程按经典电商模型设计核心流程包括这几个步骤提交订单、锁定库存、生成订单记录、模拟支付、支付成功后扣减库存。这里最需要用心思的是锁库存的时机。我的方案是用户提交订单时锁定库存 15 分钟15 分钟内未支付自动释放这样能避免恶意下单占库存也能防止用户下单后迟迟不付款导致商品卖超。订单状态的管理也花了不少心思。我定义了一组状态枚举包括待支付、已支付待发货、已发货、已完成、已取消和售后处理中。每个状态之间的流转在代码里做了严格校验不允许从已取消直接跳到已发货这种非法操作。这项设计在后续测试阶段省了非常多的事状态机模型值得推荐。2.5 数据统计与运营辅助功能很多毕设项目做到订单模块就结束了但实际店铺运营中数据统计的重要性一点不比买卖功能低。我给这套系统加了一个简易的统计模块包括每日销售额趋势、Top10 热销商品、库存周转率、会员复购率这几个核心指标。实现上没有引入复杂的报表工具就是几个定时任务配合 SQL 聚合查询。每天凌晨统计前一天的数据写入汇总表页面展示时直接查汇总表查询速度非常快。如果要实时统计就在订单模块里做埋点订单状态变更时异步更新统计信息效果也不错。3. 数据库设计与关键数据表详解3.1 数据表整体规划数据库设计是整个系统的基础表结构设计不合理后面写代码会处处别扭。我最终设计了 14 张核心表覆盖用户、商品、订单、营销、系统配置五大类。这里把主要的表列出来可以直接参考这个结构去建库。管理员相关的表有管理员表和管理员角色表。会员侧的表包括会员用户表、会员收货地址表、购物车表。商品相关的有商品分类表、商品 SPU 表、商品 SKU 表和商品评价表。订单相关的有关单主表、订单明细表和售后申请表。支撑功能的还有轮播图表和系统通知表。这里讲一下为什么把管理员和会员拆成两张表而不是统一做成一张用户表加区分字段。表面上看统一表更简洁但实际业务中管理员和会员的字段几乎没有交集。管理员关心的是账号、角色权限、最后登录 IP会员关心的是手机号、积分、会员等级、收货信息。强行合并会制造大量冗余字段所以从设计上就分开各管各的逻辑更清楚。3.2 核心表结构详细设计商品 SPU 表和 SKU 表是商品体系的核心。SPU 表主要字段包括商品 ID、商品名称、副标题、所属分类 ID、品牌、主图地址、详情描述、上下架状态和季节标签。SKU 表的字段包括 SKU ID、所属 SPU ID、规格名称比如红色/M 码、价格、成本价、库存数量、SKU 图片地址和销量。这里要注意价格字段我统一用整数类型存储分单位的数值避免浮点数精度问题。用元做单位容易踩浮点数计算的坑这一条是很多老开发都会强调的细节。订单相关的三张表也要仔细设计。订单主表字段包括订单号、用户 ID、订单总金额、实付金额、优惠金额、订单状态、收货人姓名、手机号、收货地址、下单时间和支付时间。订单明细表存储每个商品的 SKU ID、单价、数量、小计金额并且冗余了商品名称和主图快照。这里做冗余是很有必要的因为商品信息后续可能修改但历史订单必须保持当时的下单信息否则用户查看历史订单时显示的商品和你现在店铺里的商品不一致会很奇怪。购物车表结构相对简单核心字段是用户 ID、SKU ID、数量、选中状态和创建时间。我加了一个选种状态字段这个字段看着不起眼但在做批量结算功能时非常好用不用前端去维护复杂的选中逻辑。3.3 索引设计与查询优化索引设计是数据库性能的关键。我对订单表按照用户 ID 和状态字段建了联合索引查询某个用户的所有订单这类高频操作走索引非常快。商品表则对分类 ID 和上下架状态建了联合索引商城前台的商品列表查询是最高频的接口性能必须保证。这里有一个实操技巧在写 SQL 前先用 explain 看执行计划确认查询是否走索引。我遇到过一个问题订单查询慢得离谱排查后发现是商品名称字段做了模糊匹配没有走索引。后来改成了只在商品 SPU 表上做名称搜索订单查询全部走精确 ID 关联速度一下子就上来了。查询优化这件事越早做越好别等数据量大了再回头改代价完全不是一个量级。4. 前端实现与前后端联调4.1 管理后台与商城前台的页面结构管理后台用的是 Vue3 Element Plus布局是经典的侧边栏加顶栏结构。侧边栏按功能模块分组包括仪表盘、商品管理、订单管理、会员管理、营销管理和系统设置。商品管理里面有商品列表、商品发布、分类管理三个子页面订单管理里主要是订单列表和售后处理。这种结构的优点是员工上手成本极低有过任意后台管理系统使用经验的人基本不用教。商城前台这边为了给用户更好的浏览体验页面风格偏清新运动风主色调用了深蓝色带浅蓝渐变符合游泳场景的视觉印象。首页有轮播图、分类快捷入口、热销商品推荐。商品列表页支持按分类筛选、价格排序、销量排序和关键词搜索。商品详情页展示多张轮播图、规格选择、库存余量显示加入购物车和立即购买按钮都在首屏能点击到的位置。这些细节的设计老实讲对比头部电商平台还有很多差距但作为一个中小型游泳用品店的需求完全够用。4.2 API 接口设计与响应格式统一前后端分离的项目接口设计的好坏直接影响联调效率。我的做法是定义一套统一的响应格式无论成功失败后端都返回标准的 JSON 结构。格式包括状态码、消息和数据三个字段。状态码 200 表示成功400 表示参数错误401 表示未登录或 token 失效403 表示无权限500 表示服务器内部错误。前端拿到响应后首先检查状态码再做相应处理逻辑非常统一。接口路径的设计也遵循了 RESTful 风格。商品相关的接口用 /api/product 开头订单相关的用 /api/order 开头用户相关的用 /api/user 开头。HTTP 动词对应具体操作GET 查询、POST 新增、PUT 修改、DELETE 删除。不过实际开发中我做了适度妥协比如登录的接口用了 POST /api/admin/login并没有严格遵循纯 RESTful 风格里登录应该用 POST 语义但是没有资源路径的死板规范。接口设计这件事不能生搬硬套合适的才是最好的。4.3 跨域问题与代理配置实战前后端分离开发中跨域问题几乎必然会遇到。前端开发服务器跑在 5173 端口后端 Spring Boot 跑在 8080 端口从 5173 发请求到 8080 就存在跨域。解决的方式有两种生产环境用 Nginx 反向代理解决开发环境用 Vite 的 proxy 配置解决。我先说开发环境的方案。在 Vue3 项目的 vite.config.js 里配置 proxy 代理将 /api 前缀的请求转发到后端服务器。关键配置项是 target 指向后端地址changeOrigin 设为 true 让请求来源伪装成目标域名。这样前端请求的路径写成 /api/product/list实际会被转发到后端对应的接口地址上浏览器端感知不到跨域非常清爽。生产环境我在 Nginx 里也做了代理配置。location /api/ 块下配 proxy_pass 指向 Spring Boot 服务地址。这里有一个容易踩的坑proxy_pass 后面带不带斜杠行为会不同。带斜杠表示替换 /api 前缀不带斜杠表示保留原始 URI 路径传给后端。如果你后端的 Controller 定义路径时带了 /api 前缀那 Nginx 里就不要带斜杠否则 404。这个细节我当初折腾了半小时才反应过来。4.4 文件上传商品图片与富文本处理游泳用品的商品展示图对销售转化率影响很大一套泳衣好不好看很大程度上看图片质量。这个功能涉及文件上传实现。Spring Boot 处理文件上传相对简单使用 MultipartFile 直接接收文件然后将文件保存到服务器的指定目录。存储路径我选择存储在本地磁盘用日期分目录管理避免一个目录下文件太多。同时把图片访问路径存到数据库字段这样前端展示时直接拼 URL 就能访问。还有一个细节是图片的类型和大小的控制。我在后端设置了白名单校验只允许 jpg、png、webp 格式单张图片最大 5MB。前端的 Element Plus 上传组件也有对应的校验双重校验保证安全性。商品详情页的富文本编辑我用了 wangEditor 这个开源组件支持图片插入配置起来比较轻量。5. 实测过程中踩过的坑与排查技巧5.1 Spring Boot 版本选择的血泪教训开头提到版本选择的问题这里展开细说。当时我看到 Spring Boot 3.2 已经发布想着新版本肯定更好直接用了 3.x结果一连串问题找上门。首先是 javax 包名改成 jakarta 了许多老代码和依赖需要调整。然后是 Spring Security 6.x 的配置方式和 5.x 不兼容网上大部分教程都是基于 5.x 写的照着抄全是坑。最难受的是部分第三方 starter 还不支持 Spring Boot 3.x比如一些老牌的开源组件在 Maven 仓库里的依赖坐标都变了。那个项目卡了我一个周末才爬出来。后来学乖了果断退回 Spring Boot 2.7.18。这个版本是 2.x 系列的最后一个版本官方长期维护生态兼容性最好几乎所有第三方库都能无缝集成。如果你不是特别需要 JDK17 以下的虚拟线程等新特性2.7.18 是最稳妥的选择。这一点对于毕设和实际项目都很重要项目的目标是把业务做出来跑起来而不是追新版本。版本选得稳开发体验会无比顺畅。5.2 MyBatis Plus 和 LambdaQueryWrapper 的坑MyBatis Plus 的 LambdaQueryWrapper 是一个非常好用的工具它让 Java 代码写查询条件不用再拼 SQL 字符串而且由于是类型安全引用编译期就能发现字段错误不容易出现 SQL 拼错导致的运行时问题。示例代码里写条件的时候直接 Product::getStatus非常清晰。但用好 LambdaQueryWrapper 也有一些注意点。第一个坑是对实体类字段做逻辑删除时MP 默认会在所有查询中自动拼接逻辑删除条件。如果你的表没有逻辑删除字段却配置了全局逻辑删除所有查询自动带上 is_deleted 0 条件结果查出来都是空这个坑很隐蔽。第二个坑是 MyBatis Plus 的分页插件需要单独配置否则 Page 对象查出来的数据只有 total 值records 是空的。我这边是配置了一个 MybatisPlusInterceptor Bean 并添加了 PaginationInnerInterceptor正则配置后分页就正常了。这类问题要养成习惯从控制台日志里看打印出来的 SQL一下子就定位了不用瞎猜。5.3 阿里云服务器部署与 Docker 实践系统开发完成后要上线部署本来想用宝塔面板节省时间但考虑到项目后续可能要打包成镜像做快速扩容最终选择了 Docker 部署。Docker 部署 Spring Boot 项目的核心工作是编写 Dockerfile 文件。这里贴一个精简可用的 Dockerfile 示例它复制打包好的 jar 包到容器里设置时区为东八区暴露 8080 端口执行启动命令。FROM openjdk:8-jre-alpine VOLUME /tmp COPY target/swim-shop.jar app.jar RUN sh -c touch /app.jar ENV JAVA_OPTS-XX:UseG1GC -Xms256m -Xmx512m ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar /app.jar]这里有一个优化点值得强调JVM 参数不要直接写在 ENTRYPOINT 里写死而是通过环境变量传递。这样不同配置的服务器不需要改 Dockerfile只要在 docker run 命令里指定不同环境变量值即可。比如内存较小的服务器可以加 -e JAVA_OPTS-Xms128m -Xmx256m 覆盖默认值。数据库我用了一个 MySQL 容器但是这里有一个重要的建议生产环境不要把数据文件放在容器里。容器一旦删除数据就全没了。要给 MySQL 容器挂载宿主机目录通过 -v 参数把数据目录映射到宿主机磁盘上。这一点我相信很多初学者都会忽略我之前也吃过亏Docker 用起来方便但数据安全这根弦要时刻绷紧。5.4 常见问题速查表总结一下这段时间遇到的典型问题整理成一张速查表方便大家对照排查。数据库连接报错 Public Key Retrieval is not allowed这个问题的原因是在 MySQL 8.0 的驱动配置里useSSL 参数或 allowPublicKeyRetrieval 参数没有正确设置。解决办法是数据库连接 URL 加上 allowPublicKeyRetrievaltrueuseSSLfalse 参数。前端请求接口返回 401常见原因有几个token 过期、token 没有放到请求头里、后端 JWT 校验密钥不一致。排查顺序一般是先看浏览器控制台里的请求详情确认 Authorization 头有没有带。如果没有带重点检查前端 axios 拦截器如果带了还是 401重点检查后端密钥配置和 token 生成逻辑。中文乱码问题这种问题通常出在数据库连接参数里 characterEncoding 没有配置就用了。解决办法是连接 URL 显式加上 characterEncodingutf8 参数。如果改了还是乱码下一步检查 MySQL 数据库的默认编码是否建库时就指定好了 utf8mb4。文件上传成功但图片访问 404这种情况一般是上传文件的保存路径和静态资源映射路径对不上。Spring Boot 配置了很久的静态资源默认只映射 classpath 下的目录你上传到磁盘的文件要额外加一个 WebMvcConfigurer 里重写 addResourceHandlers 方法把外部目录映射成 URL 路径来访问。最后一个高频问题是本地开发没问题但部署到 Linux 服务器后图片上传失败。这个经常是目录权限的问题保存图片的目录 Tomcat 或 Java 进程没有写得权限。解决办法是给目录执行 chmod 755 命令打开权限或者把存储路径权限设置为专用目录并确保用户对它有读写权限。推荐专门建一个目录用于图片存储不让应用直接往系统根目录或 home 目录写文件。6. 项目落地效果与可扩展方向系统上线运行到现在最明显的感受是库存准确性大幅提升了。以前朋友店里每个月盘点都要对账对半天现在库存流水一目了然哪个环节出了问题顺着流水查就行。订单处理效率提升更是立竿见影以前晚上回家还要靠记事本记录当天订单现在管理员在手机上打开后台就能看到所有待发货订单处理完一键标记发货即可。如果后续想把系统做成真正面向市场的产品有几个可以扩展的方向。全面接入真正的在线支付目前用的模拟支付上线正式环境需要对接微信支付或支付宝毕竟是真正面向消费者的游泳用品店铺线上支付是刚需。消息推送功能值得加订单状态变更通知可以接入短信或微信模板消息提高用户感知。还有一个值得做的方向是会员营销体系引入积分和优惠券功能通过促销工具拉动复购率让系统从单纯的记录工具变成促进销售的工具。移动端适配也要顺手做一下现在用的 Element Plus 桌面端体验不错但在手机上浏览体验略差可以用移动端组件库或者做一套 H5 商城。实际上做到这个程度这套系统已经能支撑一家小型游泳用品连锁店的日常运营了再往下扩展就是基于这个骨架增加新的业务模块了。我在实际开发中最深的一点体会是不要为了炫技而引入过多新潮技术先把核心业务跑通再逐步优化细节。一个稳定可用的系统永远比一个花里胡哨但动不动就出 bug 的系统更有价值。如果你正准备动手做类似的项目建议按我说的流程来先把需求梳理清楚再把数据库表设计好最后才是写代码。好的数据库设计能让后端开发事半功倍反之则会让你不断返工改代码这是我做完这个项目最想说的一句话。