
1. 先拆清楚需求这个商城平台到底要管什么业务很多人在拿到超市线上购物商城销售管理平台这个题目时第一反应是找一套现成的电商开源项目改巴改巴。我见过不少同学这么干最后往往卡在改不动或者跑起来全是Bug的尴尬境地。问题出在哪儿出在没把需求拆明白。超市购物商城和淘宝、京东这类大平台有个本质区别它有一套自己的商品组织方式和价格策略。我在实际接到这类项目时第一步从来不是写代码而是先把超市这个场景下的核心业务链路捋清楚。先说人。超市里至少有三类角色要进系统顾客在线上挑货、加购、下单、结算运营人员要管理商品上下架、库存、价格、促销活动管理员要看销售报表、统计利润、管理用户和权限。很多毕设只做了顾客端和管理员端把运营和销售分析砍掉了结果是答辩时被老师问一句你对销售的统计分析怎么体现的就接不上话了。再说货。线上超市的商品不是一张表就能扛住的。同一个商品可能有多规格比如农夫山泉550ml*12瓶整箱和农夫山泉550ml单瓶虽然属于同一个品牌SKU下的不同规格但价格、库存、条形码都不同。在我做的方案里会拆成**SPU标准商品单元和SKU库存量单位**两层SPU存储商品的公共属性SKU存储规格、价格、库存。这个设计对后续的事情影响特别大后面讲数据库设计时会展开说。然后是交易链路。线上超市最常用的流程是浏览商品 → 加入购物车 → 提交订单 → 在线支付或货到付款 → 管理员发货自提则免 → 顾客确认收货 → 售后处理。要注意的是超市场景里还有**门店自提和同城配送**两种履约方式这是区别于普通电商的地方。我在做订单状态机设计时把状态定为了待支付、已支付、已发货、已送达/待自提、已完成、已取消、售后中总共七个状态。最后是管理闭环。商品卖得好不好、毛利是多少、哪个时段单量最高、哪个品类滞销这些不是上线之后才想的而是在项目设计阶段就要留好数据出口。我一般会预留一张销售明细表和一个统计查询模块后端直接用聚合查询算出日销售额、周同比、品类占比。这个模块写起来不复杂但对整个项目的完整度提升非常大面试或答辩讲起来也很有说服力。所以动工之前建议先把需求文档列出来哪怕只列功能点清单也行。用表格给自己列一个比较理想的功能全景端侧功能模块具体内容用户端商品中心分类筛选、关键字搜索、商品详情、规格选择用户端购物车加入、修改数量、删除、批量结算用户端订单中心提交订单、支付/货到付款、取消订单、确认收货用户端个人中心注册登录、收货地址管理、订单查询、售后申请运营端商品管理商品维护、上下架、库存调整、促销设置运营端订单管理订单列表、发货/自提核销、退款审核运营端分类管理商品分类、品牌管理管理端销售统计每日销售额、订单量、Top10商品、品类占比管理端用户管理用户列表、角色分配、禁用/启用管理端系统管理轮播图配置、公告管理、敏感词过滤这十行表格对应着后端的十几个模块、几十张表。还不算复杂业务逻辑。如果你现在脑子里只有能删改查的概念那建议先把上面这张表对照着你自己的项目核一遍缺什么补什么。2. 技术选型和工程搭建为什么是SpringBoot Vue以及前后端分离的落地细节技术选型这件事说到底是围绕开发效率、生态成熟度、学习成本三个维度。SpringBoot Vue这套组合能成为目前Java方向Web项目的绝对主流不是因为它技术含量最高而是因为它最均衡对单人或小团队最友好。SpringBoot提供的能力对一个商城项目来说是恰到好的内嵌Tomcat不用单独部署Web容器Starter机制引入依赖像拼积木一样简单自动配置省掉大量XML配置生态完善整合MyBatis、Redis、Shiro/JWT都有一站式方案。Vue这边组件化开发让前端页面模块化响应式数据绑定让交互逻辑简单直接Vue Router Pinia/Vuex把路由和状态管理也规范化了。我在搭这个项目时选择的是前后端完全分离的架构后端跑在8080端口提供纯JSON接口前端跑在5173端口Vite默认通过Axios调用后端API。中间跨域问题用后端的CORS配置解决登录态用JWT管理。工程结构上我建议先定好后端的包结构因为一旦代码写多了再调整很痛苦。我的习惯结构如下com.supermarket.sales ├── controller // 接口层只做参数接收和结果返回 ├── service // 业务逻辑层核心业务判断都在这层 │ └── impl // 接口实现 ├── mapper // MyBatis的Mapper接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接收前端请求参数 ├── vo // 视图对象返回给前端的结构 ├── config // 配置类跨域、拦截器、静态资源映射 ├── common // 公共类统一返回结果、异常处理、工具类 └── utils // JWT工具、加密工具等前端工程我选择的目录结构同样清晰明了src ├── api // 按模块封装的接口请求文件 │ ├── goods.js │ ├── cart.js │ ├── order.js │ └── statistics.js ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // 状态管理登录状态、购物车数量等 ├── views // 页面视图 │ ├── home // 商城首页 │ ├── goods // 商品列表、详情 │ ├── cart // 购物车 │ ├── order // 订单 │ ├── user // 个人中心 │ └── admin // 后台管理端页面 ├── utils // Axios实例封装、路由守卫等工具 └── App.vue这套结构的好处是前后端职责边界非常清晰后端每个包职责单一前端每个views目录对应一个业务模块。后期Debug时问题出在哪个层一眼就能定位。再提一个新手容易忽略的点后端统一返回结构。我在项目中定义了一个Result类包含code、msg、data三个字段所有接口都返回这个结构配合一个全局异常处理器。这样前端Axios拦截器只需要判断code的值来决定是正常处理还是弹错误提示。这个设计可以极度简化联调成本。前端所有API导出都是request({ url, method, params, data })形式拦截器统一注入token、统一处理HTTP状态码和业务状态码后续扩展新模块时只需要在api目录下加文件就行。3. 数据库设计从SPU/SKU到订单快照超市场景的建模要点数据库设计是整个项目的底座也最能体现一个人对业务的理解深度。我的顺序是从商品域开始然后交易域最后是统计与用户域。逐一说明关键设计思路。3.1 商品域的SPU/SKU建模前面提到的农夫山泉整箱vs单瓶例子就是SPU/SKU拆分的典型应用场景。我在product_spu表中存的是统一的商品信息比如商品名称、品牌、分类ID、封面图、商品详情描述在product_sku表中存的是具体可售卖的规格规格名称如550ml*12瓶、条码、售价、原价、库存。两张表用spu_id关联。为什么必须拆分因为很多业务动作发生在SKU粒度上购物车选的是什么规格直接决定价格和库存订单明细里记录的是SKU信息运营调价调库存也是针对SKU。如果不拆分所有字段堆一张表里会出现大量冗余而且后续促销功能无法做——比如满两件打八折只对某几个SKU生效不拆根本没法设计。3.2 订单域订单主表 订单明细 订单快照订单是商城系统的核心也是并发和一致性要求最高的部分。我的设计分为三张表order_info存订单主表信息订单号、用户ID、订单总金额、支付金额、优惠金额、订单状态、收货人信息、下单时间、支付时间、order_item存订单明细每笔订单包含哪些SKU、数量、单价、商品名称、order_status_log存状态变更日志哪个状态从哪变到哪、谁操作的、时间点。一个必须养成的习惯是下单时要做商品快照。什么意思就是把下单那一刻的商品名称、单价、规格、图片链接复制一份存到order_item里而不是通过sku_id再去关联查实时商品表。原因很现实用户下单后过几天去看订单详情商品可能已经改价、改名、下架甚至删除如果还去查商品表得到的一定是变了的数据甚至查不到。快照能保证订单历史永远稳定显示下单时的商品面貌。这个细节很多简历上写着商城项目的人答不上来但其实在实际业务中极其重要。3.3 库存与并发超卖的预防策略超市场景比普通电商更容易出现高频秒杀式购买比如某款饮料搞特价用户会短时间涌入。库存扣减的并发控制如果没做好就会出现超卖——库存只剩10件却卖出20单的问题。我在项目中采用了乐观锁 前置库存判断的方式。核心SQL就是在扣减库存时加一个条件判断同时把库存扣减和销量增加绑定在一个事务里完成UPDATE product_sku SET stock stock - #{quantity}, sales_count sales_count #{quantity} WHERE id #{skuId} AND stock #{quantity}如果更新的影响行数为0说明库存不足就直接抛出库存不足的业务异常。这是目前实战中最简单的防超卖方案不用引入Redis分布式锁也能扛住大部分场景。如果要进一步优化性能可以加一层Redis预热库存但毕设或中小型项目做到SQL层面的控制已经足够稳定了。3.4 完整的核心表清单参考我把项目中必须的核心表列出来方便你对照自己的设计查漏补缺聚合域表名关键字段说明用户域sys_user用户名、密码BCrypt加密、手机号、角色、状态用户域user_address收货人名称、电话、省市区、详细地址、默认地址标记商品域product_category分类名称、父分类ID、排序、是否显示商品域product_spu商品标题、副标题、分类ID、品牌、主图路径、详情富文本商品域product_sku规格名、条码、售价、原价、库存、销量、状态交易域cart_item用户ID、SKU ID、数量、勾选状态、加入时间交易域order_info订单号、用户ID、状态、总金额、实际支付、收货快照信息交易域order_item订单ID、商品快照、SKU ID、数量、单价交易域order_status_log订单ID、旧状态、新状态、操作人、时间营销域promotion活动名、类型、满减门槛、折扣率/减免金额、起止时间营销域promotion_sku活动关联的SKU集合统计域sales_daily统计日期、商品ID、销量、销售额、分类维度归档这张表不是让你照抄而是检查你的设计有没有遗漏。我见过很多项目把收货地址直接塞进用户表里把购物车明细做成一个JSON字段的短期跑demo没问题一旦做订单生成、地址管理、报表统计这些功能时就开始到处别扭。我的经验是宁可最开始多画半小时表也不要在写代码时反复改数据库。4. 后端核心实现从统一返回封装到订单提交的事务控制后端是整个平台的逻辑中枢。这里我不会罗列全部代码而是把几个真正有分量、并且能给你带来实际提升的模块拆开讲。4.1 统一返回体、全局异常与JWT鉴权先说统一返回体。我在common/Result里定义了code、msg、data三个字段成功时code200业务失败时比如库存不足未登录则返回不同的业务码。配合RestControllerAdvice全局异常处理器Controller层可以做到非常干净——专心写业务不用每写一个接口都把成功还是失败手动包一遍。JWT鉴权这一部分我给前后端分离项目定的标准方案是用户登录成功后后端生成一个Token返回前端前端存在localStorage里。之后每次请求Axios拦截器都会在请求头注入Authorization: Bearer token。后端通过一个JwtInterceptor拦截器统一校验把所有需要登录的接口如购物车、下单、个人中心保护起来。这里有个实战经验值得说拦截器只解决你登录了没的问题不解决你有没有权限的问题。超市平台里顾客、运营、管理员能访问的接口是不同的。所以我在JWT的claim里存了role字段在需要权限的接口上用了自定义注解和权限拦截器或者直接在拦截器内判断请求路径前缀——/admin/**的接口只允许ADMIN角色访问。这样权限体系简单实用对小型项目足够了。4.2 微信风格的商品搜索关键字 分类 排序需要的SQL组装商品列表页是一个高频访问页面接口设计直接影响前端页面加载体验。我先做了一个支持categoryId、keyword、sortField销量/价格/时间、pageNum、pageSize的查询接口。用MyBatis的动态SQL来组装查询条件select idselectSkuPage resultTypecom.supermarket.sales.vo.SkuVO SELECT s.id, s.sku_name, s.price, s.stock, s.sales_count, p.title, p.main_image FROM product_sku s LEFT JOIN product_spu p ON s.spu_id p.id where if testkeyword ! null and keyword ! AND (p.title LIKE CONCAT(%, #{keyword}, %) OR s.sku_name LIKE CONCAT(%, #{keyword}, %)) /if if testcategoryId ! null AND p.category_id #{categoryId} /if AND s.status 1 /where ORDER BY choose when testsortField saless.sales_count DESC/when when testsortField priceAscs.price ASC/when when testsortField priceDescs.price DESC/when otherwises.create_time DESC/otherwise /choose /select这里有个细节注意事项LIKE查询字段必须用CONCAT拼通配符而不是直接写LIKE %${keyword}%前者是预编译参数能防止SQL注入后者是字符串拼接虽然写法省事但极其危险。对于接口参数尽量使用PageHelper或手动分页分页还要单独查一个COUNT(*)总数返回给前端前端分页组件才拿得到总页数。4.3 订单提交一个接口看完整套事务思维下单接口是整个后端逻辑密度最高的点之一非常适合展示一个开发者的综合能力。我把submitOrder的操作顺序梳理如下从Token中解析出用户ID。校验收货地址是否属于当前用户。校验购物车中勾选的商品是否全部在架上且库存充足。计算订单总金额遍历SKU同时根据当前商品是否关联促销活动计算优惠金额。生成订单号用时间戳用户ID随机数保证高并发下不重复。批量扣减每个SKU的库存使用前面的乐观锁SQL逐个判断影响行数。批量生成order_item明细快照。生成order_info主订单记录状态待支付。清除购物车中已提交的商品。记录订单状态变更日志。这一整个行为必须在一个数据库事务里完成否则就会出现扣了库存但订单没生成或者生成了订单但购物车没清空的脏数据。在SpringBoot里用Transactional注解标记这个Service方法即可。操作顺序的原则先锁资源校验扣库存再创建主数据订单最后做清理清购物车、记日志。新手最容易犯的错误是把订单主表和订单明细两个INSERT分开写、库存扣减和订单创建不在同一个事务方法里结果就是线上数据千奇百怪。第二步、第三步做了校验之后要重新查库存或者依赖数据库条件更新不要只用Java代码if判断一次就往下走因为并发情况下你拿到的库存值可能是过期的。4.4 销售统计模块用聚合查询高效实现经营看板销售统计这个功能很多项目把它当锦上添花但我觉得它是销售管理平台的一个重头戏。后端实现主要用三个查询思路日销售汇总GROUP BY DATE(create_time)统计每天的订单金额、订单数、客单价。商品销量排行关联order_item表和product_spu表按SKU或SPU维度聚合销量和销售额取Top10。品类占比关联商品分类表按分类维度统计销售额占比。实际写MyBatis时就是几个带GROUP BY和HAVING的聚合SQL。要注意日期字段的时区问题数据库连接串里显式指定serverTimezoneAsia/Shanghai否则统计结果会差八小时。另外为了让看板数据不卡顿我通常会做一个物化层——定时任务每天凌晨把前一天的数据汇总到sales_daily表里前端看板直接查这个表。这个空间换时间的思路在企业项目里非常常见写到简历上也是一个不错的加分项。5. 前端Vue实现路由守卫、购物车状态管理与后台布局经验Vue这边的重点在于页面结构怎么组织、公共状态怎么维护、路由怎么守卫、接口怎么调用。我按照用户端和后台管理端两条线分别说。5.1 用户端从首页到下单的完整流程拆解用户端页面我用Vue3 Vue Router Pinia来搭建。首页Home.vue主要组件是搜索栏、轮播图、分类导航、今日热销通过调用商品接口把数据渲染出来。商品列表页GoodsList.vue从URL的query参数里读取categoryId和keyword调用列表接口。商品详情页GoodsDetail.vue通过route.params.id读取SPU ID然后调用详情接口把SKU列表展示出来用户切换规格时组件动态绑定当前SKU的价格和库存。购物车状态我放在了Pinia的cartStore里。用户加购时调用addToCart接口成功后更新本地状态同时刷新顶部导航栏的购物车角标数量。这里的关键点是本地状态和服务端数据要保持同步。因为用户可能在不同设备登录购物车数据最终以服务端为准。所以我的方案是进入购物车页时总是重新拉取接口数据本地Store只做缓存和展示优化。路由守卫这块用户端和管理端的逻辑不一样。用户端我设置了meta.requiresAuth: true的路由未登录访问时跳转登录页登录完成后通过redirect参数带回原来的页面。管理端的所有路由都包在带meta.role: ADMIN的父路由下守卫里如果检测到当前用户角色不是管理员直接重定向到首页并提示无权限。基于角色的路由控制写清楚通常能省掉很多不必要的分端判断。5.2 管理后台的布局与组件复用管理后台我用了经典的后台布局左侧导航菜单、右侧内容区、顶栏面包屑。Vue生态中可以直接基于Element Plus的el-container组件拼装这套布局配合动态侧边栏菜单。后台页面重点不是展示效果多炫而是表格、表单、分页操作流程的完整性。商品管理页是一个典型的增删改查 上下架 库存调整页面。我实现时先用el-table渲染商品列表用el-pagination做分页点击编辑弹出一个el-dialog表单表单里字段与后端DTO对应。上传商品主图时用el-upload组件本身支持action地址配置设置好后端上传接口就好上传接口我会单独处理返回文件路径并回填到表单的mainImage字段。订单管理页需要把前面后端设计的订单状态机在前端呈现出来。我的做法是订单列表按状态做Tab筛选在待发货Tab里显示发货按钮点击弹窗选择物流公司和单号在售后中Tab里显示退款审核面板审核通过后调用退款接口。这些按钮的显隐逻辑用一个函数判断当前行的状态即可不要让前端强依赖后端返回的操作权限码。5.3 Axios自动携带Token与统一错误提示前端网络层是联调效率的关键。我在utils/request.js里创建Axios实例统一做三件事请求拦截器从localStorage读取token并注入请求头。响应拦截器判断HTTP状态码如果是401则清除本地token并跳转登录页如果业务返回code非200则用Element Plus的ElMessage弹出后端返回的msg。超时设置timeout: 10000避免网络差时接口一直pending。这样开发效率会显著提升——后端异常信息能直接显示在页面上前端不需要每个页面都手动写错误处理。我在实际开发里基本所有异步函数只写try/catch承载核心业务逻辑全局拦截器已经解决了通用错误场景。6. 联调与部署实战跨域、CORS、宝塔部署jar和前端静态资源的完整路径开发归开发真正要交付一个能跑起来的项目联调和部署才是见水平的地方。6.1 跨域问题的本质与后端正确解法前后端分离项目最经典的坑就是跨域。你前端跑5173端口后端跑8080端口浏览器发现两个端口不同直接拦截。这里要先理解一个底层判定逻辑浏览器的同源策略针对Ajax请求要求协议、域名、端口三者全部一致否则就属于跨域请求但它不拦截CSS、JS、图片的加载也不限制页面跳转所以跨域问题通常只出在XHR或Fetch调用上。后端的正规解法是在配置类里启用CORS而不是让前端改代理。我习惯写一个CorsConfig类实现WebMvcConfigurer接口重写addCorsMappings方法允许的来源设为具体的域名或者在开发阶段用allowedOriginPatterns(*)允许的请求方法包括GET、POST、PUT、DELETE、OPTIONS允许携带凭证。开发环境的另一个备选方案是前端Vite代理在vite.config.js里配置server.proxy把/api路径转发到8080。但我建议后端CORS也同时配置好因为生产环境往往是前后端分离部署线上跨域会更复杂后端支持CORS能提前消除隐患。6.2 生产环境构建前端打包成静态资源后端打成jar前端部署前需要执行一次生产构建npm run build构建完成后dist目录就是纯静态文件可以用任何Web服务器托管。后端执行mvn clean package在target目录下得到一个可运行的jar文件。这样整个系统就变成了两部分前端静态资源 后端服务。部署路径上有两种主流做法选哪种取决于你的服务器条件方案A单机省事把dist目录直接扔进Nginx的站点根目录Nginx配置好反向代理凡是/api开头的请求都转发给http://localhost:8080其余请求直接返回前端静态文件。这是最符合前后端分离精神的做法用一个Nginx同时处理动静分离。方案B非Java环境推荐如果服务器上不方便装JDK和Maven环境可以先把jar和dist分别放到服务器对应目录后端直接java -jar sales-platform.jar启动再单独配Nginx处理前端。很多云服务器教程会推荐这种但方案A更主流因为它让整个系统入口统一为80端口用户访问体验更顺。6.3 配置文件分离和环境切换SpringBoot的多环境配置是个必会技能我却见过不少项目把这部分完全略过。开发环境连本机数据库、生产环境连服务器数据库是同一套配置这会有两个问题一是代码提交时容易把本机密码带上去二是部署时改配置又容易改错。我的做法是在application.yml里用spring.profiles.active控制当前环境开发环境用application-dev.yml生产环境用application-prod.yml。启动时指定环境java -jar sales-platform.jar --spring.profiles.activeprod数据库连接配置里我建议显式加characterEncodingutf8和serverTimezoneAsia/Shanghai否则中文乱码和日期差8小时会一直困扰你。6.4 部署后的自检清单项目上线前最后做一遍冒烟测试我通常按这个顺序走前端页面能否正常打开静态资源是否全部加载。用户注册、登录、找回密码流程是否通顺。商品列表加载和库存显示是否正常。完整走一遍加购→下单→支付→发货→确认收货全链路。后台统计看板的日期、金额数据是否和订单明细能对上。权限是否生效普通用户访问不了管理端接口未登录用户访问不了个人中心。刷新页面后登录态是否保持token过期后是跳转登录页且不出现白屏。这一轮走完之后这个项目才能真正算做得能交付。7. 项目亮点提炼与答辩/面试加分思路代码写完后最后一步是把手里的项目说出去。相同的功能不同的人讲述给人的印象完全不同。我梳理几个适合在答辩或面试中突出讲的内容点。第一SPU/SKU的商品建模。只说我设计了商品表和说我通过SPU/SKU拆分解决了多规格商品的库存价格一致性是两种层次的表达。后者说明你真正处理过真实业务里的商品形态问题。第二乐观锁防超卖。面试官问多人在线购物怎么保证不超卖直接在纸上写出那条带WHERE stock #{quantity}的UPDATE语句然后讲清楚事务边界和影响行数判断的逻辑。这比背一堆Redis理论有用得多。第三事务一致性设计。下单接口涉及的十步操作全部在一个Transactional里你能说清楚为什么购物车清理必须和库存扣减在同一个事务为什么不把清购物车放在前端提前做就证明你有事务思维。第四统计报表的物化表设计。日汇总提前算好前端查的是结果表而不是查原始订单明细这种以空间换时间、用冗余换性能的思路在真实项目中非常实用。第五权限控制的落地细节。接口层面的角色控制、前端路由守卫、菜单按角色生成三个层面统一起来讲也能体现你的全局把控能力。这个项目做到上述程度不管是作为课程设计、毕业设计还是自己练手的综合项目它的完成度和说服力都已经足够。整个过程没有哪个环节是花哨的炫技每一步解决的都是实际业务里真实会碰到的问题。按这个路径走下来你收获的一定不只是能运行三个字。