做电子产品销售管理系统很多新手一上来就纠结“用什么框架”“要不要前后端分离”“数据库表怎么建”。老实说我前后带过不少实习生做这类课程设计也帮人改过不少毕设代码最后发现真正能把一套SpringBoot Vue MyBatis MySQL的销售系统从零跑起来、并且跑得稳的反而不是那些纠结技术的人而是先把业务逻辑理清楚的人。这篇文章就用一个实际可复现的电子产品销售系统为例带你完整走一遍需求拆解、技术选型、后端模块实现、前端页面落地、数据库设计、联调部署的整个流程顺带把那些文档里不会写、但实操中一定会踩的坑全部交代清楚。不管你是准备做毕设、课程设计还是单纯想练手一套完整的前后端分离项目这套系统的思路和源码细节都能直接拿来用。文章会涉及到具体代码和SQL但不会堆一整屏代码重点放在“为什么要这么写”和“哪些环节容易翻车”所以哪怕你Java基础一般跟着思路走一遍也能理解核心逻辑。1. 这个销售系统到底要解决什么问题1.1 业务场景与核心需求拆解很多人在拿到“电子产品销售管理系统”这个题目时第一反应是“做一个商品列表加一个购物车能下单就行”。这种理解不能说错但做完之后十有八九会被老师或面试官问住订单状态怎么流转库存超卖怎么处理用户权限怎么区分一台手机被两个用户同时下单怎么办我习惯在写第一行代码之前先把业务角色和核心流程画出来。这套系统面向的角色非常明确游客、注册用户、管理员。游客只能浏览商品和搜索注册用户可以加入购物车、下单、查看自己的订单、模拟支付管理员负责商品上下架、库存管理、订单发货处理。核心业务流则是用户浏览商品 → 加入购物车 → 提交订单 → 扣减库存 → 模拟支付 → 管理员发货 → 用户确认收货。听起来简单但每个环节都牵连着数据一致性和状态同步问题。比如提交订单时库存已经不足了怎么办用户下了单不付款库存应该什么时候释放这些都是在需求阶段就要想清楚的不然写到订单模块就卡壳了。1.2 技术选型背后的思考这套系统选用了SpringBoot Vue MyBatis MySQL这个组合在今天依然是Java全栈项目最稳妥的方案之一原因很简单。SpringBoot解决了传统SSM项目里一堆XML配置的痛点内嵌Tomcat打成一个jar包就能跑极大地降低了部署门槛。Vue作为前端框架组件化开发让页面维护起来比传统JSP、Freemarker那套舒服太多尤其是购物车、商品筛选这种交互较多的模块响应式数据绑定简直省事。MyBatis则是半自动ORMSQL还是自己写这对销售系统这种查询条件多变、需要精细控制SQL的场景非常合适。最后MySQL不用多说中小企业级应用最成熟的数据库选型配合Navicat或命令行都能快速上手。有人可能会问为什么不直接用MyBatis-Plus或者JPA我的看法是作为学习和演示项目MyBatis能让你更清楚地理解SQL执行过程和映射原理。等你把MyBatis的原生写法吃透了再去用MyBatis-Plus那种把BaseMapper继承一下就能CRUD的工具会更快上手出了问题也知道底层发生了什么。1.3 全景架构与数据流整套系统采用经典的前后端分离结构。前端Vue项目通过Axios发起HTTP请求后端SpringBoot以RESTful接口的形式暴露业务能力MyBatis负责ORM映射MySQL负责数据持久化。前端默认端口8080Vue CLI常见配置后端默认端口8081或9090通过代理转发解决跨域问题生产环境则用Nginx托管前端静态资源并反向代理到后端服务。数据流向是这样的Vue组件 → Vuex或Pinia管理状态 → Axios请求 → 后端Controller → Service → Mapper → MyBatis → MySQL。每一层的职责都严格分离Controller只做参数接收和结果封装Service专注业务逻辑Mapper只管数据库操作。这样做最大的好处是出问题时能快速定位在哪一层而不是一个人Debug都要翻遍所有代码。2. SpringBoot后端核心模块设计与实现2.1 项目初始化与分层结构后端项目我建议直接使用Spring Initializr生成基础骨架选择Java 8或Java 11、SpringBoot 2.7.x版本因为这套版本组合对MyBatis和MySQL的兼容性最好。SpringBoot 3.x虽然也出来了但底层是Jakarta EE部分老资料和插件还没完全跟上新手学习阶段没必要在版本兼容上耗费精力。依赖方面核心需要引入spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok以及一个JWT或拦截器用的工具类。Lombok一定记得在IDEA中安装插件不然编译会报找不到getter/setter。包结构我推荐这样划分controller接收请求处理参数service业务逻辑层接口和实现分离mapperMyBatis的Mapper接口对应XML文件entity数据库实体类vo / dto视图对象和传输对象避免实体直接暴露给前端config配置类比如跨域、拦截器、全局异常处理common统一返回结果、状态码、工具类分层结构看起来繁琐但当你需要给某个接口加一个字段校验时你会发现清晰的分层能帮你省下大量时间。我见过很多新手把业务逻辑全写在Controller里一个方法几百行最后连自己都看不下去了。2.2 用户与权限体系销售系统必须有登录注册功能用户表和权限表是基础。用户表设计不宜过于复杂核心字段包括id、username、password、nickname、avatar、role、status、create_time、update_time。密码绝对不能明文存储这一点无论何时强调都不为过。使用BCryptPasswordEncoder对密码加密每次登录时通过matches方法比对。这样做的好处是即使用户表数据泄露攻击者拿到的也是哈希值无法直接还原出原密码。权限控制用拦截器或者Spring AOP都能实现。最简单的做法是自定义一个HandlerInterceptor在preHandle方法中校验请求头里的Token然后判断用户角色。管理员接口和管理员角色绑定普通用户接口只需要登录即可访问。我在实际项目里会在Token中放入userId和role解析Token时直接取角色信息避免每次拦截都查一次数据库。2.3 商品、库存与订单模块的业务逻辑商品模块的核心是商品表product和库存表stock考虑到电子产品有品牌、分类、参数等属性我一般会再加一个分类表category用外键或者逻辑关联。商品上下架状态用status字段控制0表示下架1表示上架。查询接口默认只展示上架商品管理员后台则可以看到全部状态。订单模块是整套系统的重点也是最容易出错的地方。一个订单对应多个商品所以需要订单主表orders和订单明细表order_item。订单主表存用户id、订单编号、总金额、状态、收货信息明细表存商品id、商品名称、单价、数量、小计。订单状态我设计了几个枚举值待付款、待发货、待收货、已完成、已取消。用户下单后状态为待付款模拟支付成功后变为待发货管理员发货后变为待收货用户确认收货后变为已完成。这个流转逻辑可以在Service层用状态机的方式写也可以用简单的if/else判断核心是不要在Controller里随意修改状态所有状态变更必须经过Service层的状态校验。2.4 MyBatis映射与SQL编写要点MyBatis是这套系统的数据访问支柱映射文件通常放在resources/mapper目录下命名与Mapper接口保持一致。编写XML时有几个细节我吃了不少亏这里直接告诉你。第一个是resultMap的配置。如果实体类的驼峰字段和数据库下划线字段没有正确映射查询结果会全是null。最简单的解决办法是在application.yml中开启驼峰映射mybatis: configuration: map-underscore-to-camel-case: true开启之后数据库字段create_time会自动映射到实体属性createTime省去大量手写resultMap的麻烦。第二个是动态SQL。商品列表需要按分类、品牌、关键词筛选如果写死SQL每个条件都要单独写一种查询代码会非常冗余。使用MyBatis的where和if标签组合可以优雅地解决这个问题select idlistProducts resultTypeProduct SELECT * FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR brand LIKE CONCAT(%, #{keyword}, %)) /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /select第三个是批量操作。批量添加购物车或批量更新库存时不要用for循环挨个执行那样会发起大量数据库连接请求效率极低。使用MyBatis的foreach标签批量插入或批量更新一次SQL搞定。3. Vue前端从零搭建到功能落地3.1 Vue环境搭建与项目初始化前端这块很多新手卡在最开始的环境配置环节。Node.js推荐使用LTS版本Vue CLI或者Vite都可以。如果做课程设计Vue CLI用起来更省心webpack配置都是现成的如果是新项目想尝鲜Vite启动速度快很多但部分老插件的兼容性需要留意。创建项目的基本流程是npm install -g vue/cli然后vue create web-frontend选择需要预设例如Vue 2或Vue 3。需要注意的是Element UI只支持Vue 2Element Plus只支持Vue 3不要搞混。当前资料最全的是Vue 2 Element UI组合但如果想在这个基础上做一点新东西完全可以选择Vue 3 Element Plus。安装依赖也是一个容易出问题的环节。npm install经常会遇到网络慢的问题建议设置淘宝镜像源npm config set registry https://registry.npmmirror.com如果项目初始化后跑不起来多半是Node版本和依赖版本不匹配。可以把node_modules目录删掉package-lock.json删掉重新npm install试试。3.2 页面路由与组件拆分前端页面按角色拆分为游客和用户端商品列表、商品详情、购物车、订单列表、结算页、管理员端后台布局、商品管理、订单管理、用户管理。路由配置使用Vue Router注意懒加载写法。不要一次性import所有页面组件会导致首屏加载很慢。webpackChunkName可以给每个路由对应的组件分包{ path: /product/:id, name: ProductDetail, component: () import(/* webpackChunkName: product */ ../views/ProductDetail.vue) }组件拆分的核心原则是“一个页面一个文件一个功能块一个组件”。比如商品列表页可以拆出SearchBar、ProductCard、Pagination三个子组件父组件负责数据获取和状态管理子组件只做展示和事件抛出。数据流单向子组件通过props接收数据通过$emit触发事件逻辑清晰不容易乱。3.3 Axios请求封装与跨域处理Axios请求必须封装不能在每个页面都直接写axios.get。封装的时候做几件事统一配置baseURL、请求超时时间、请求拦截器、响应拦截器。请求拦截器自动携带Token响应拦截器统一处理后端返回的数据结构和错误码。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config })跨域问题在开发阶段是最常见的报错之一控制台一串“Access-Control-Allow-Origin”的红色报错让人以为是后端配置错了。其实开发阶段最简单的解决方法不是在后端写CrossOrigin注解而是在Vue的vue.config.js里配置代理devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } }这样前端请求/api开头的接口会被代理转发到后端8081端口浏览器看到的是同源请求自然不会触发跨域拦截。后端接口统一以/api开头前端代理也匹配/api两者配合得严丝合缝。3.4 购物车与结算流程的前端实现购物车是销售系统用户体验的关键模块。购物车数据有两种管理方式存本地或存后端数据库。如果只是做课程设计存本地localStorage完全可以简单省事但换浏览器或清缓存就没了。稍微做得正式一点就建一张购物车表用户未登录时可以存本地登录后合并到后端。这块逻辑虽然多一点但对系统完整度提升明显。结算页涉及金额计算务必注意浮点精度问题。JS中的0.1 0.2并不等于0.3直接用浮点数相加做金额计算会在某些临界值出现微小误差。解决方案是先将元转为分用整数运算最后再转回元。例如单价99.9元乘以100变成9990分计算清楚后再除以100展示给用户。4. MySQL数据库设计与优化细节4.1 表结构设计要点数据库设计决定了系统能走多远表结构没设计好后面写多少SQL都别扭。销售系统的核心表我梳理下来至少要包括用户表user、分类表category、商品表product、库存表stock、购物车表cart、订单主表orders、订单明细表order_item以及一张用于存储验证码或Token的黑名单之类的辅助表。商品表和分类表通过category_id关联商品表和库存表有两种设计选择一是把库存字段直接放在商品表里二是单独建库存表。如果是简单项目合并就可以查询也方便。但如果考虑到电商后续要按仓库、按SKU规格管理库存单独建表更科学。电子产品的SKU通常涉及颜色、存储容量等规格建议至少给商品表预留sku_code字段和spec字段JSON格式存储规格信息。订单表的金额字段建议使用decimal(10,2)类型而不是double或float因为金额计算必须精确。int和bigint虽然可以用“分”为单位存储但两次类型转换容易引入精度问题直接用decimal在SQL层面就精确处理就好。4.2 索引与查询优化的几个细节数据量小的时候大家都不关心索引一旦商品数据上千条购物车关联查询就开始变慢。索引设计有几个原则首先主键索引是必须的InnoDB默认会建聚簇索引其次经常作为查询条件的字段例如商品表的category_id、status订单表的user_id、status需要建立普通索引最后不要滥用索引每个索引都会增加写入开销一张表索引数量控制在5个以内比较合理。分页查询在大数据量下的优化也要提一下。LIMIT 100000, 20这种深分页查询即使有索引也可能慢因为MySQL要读取前10万条数据再丢弃。可以改用延迟关联或者基于上一页最大id的查询条件来优化。不过对于课程设计和初版销售系统普通LIMIT分页已经足够这里先了解有这个坑就好。4.3 事务与并发控制库存扣减和订单创建是典型的需要事务保护的操作。使用Spring的Transactional注解将创建订单、扣减库存、清空购物车这几个操作放入同一个事务任何一个环节失败所有操作一起回滚避免出现“订单创建了但库存没扣”或者“库存扣了但订单没创建成功”这种数据不一致问题。在高并发场景下单纯依赖事务还不够因为两个请求同时读到库存为1然后同时扣减会出现超卖。解决超卖最直接的办法是在SQL层面加条件限制UPDATE stock SET quantity quantity - #{num} WHERE product_id #{productId} AND quantity #{num}这样即使两个请求并发执行也只有一个能成功扣减另一个受影响行数为0在Service层判断更新结果即可。这是最朴素有效的防超卖手段不需要引入Redis分布式锁在演示项目中足够了。5. 联调、部署与常见问题排查实录5.1 前后端联调的关键节点前后端联调是整个项目过程中最容易让人崩溃的阶段因为问题往往是“前后端都没错但接口就是调不通”。我建议联调前先定好接口文档哪怕是一个简单的Markdown表也要明确每个接口的路径、请求方式、参数类型、返回格式。用Postman先把所有后端接口测一遍保证后端独立可用再进入前端联调。一个常见的联调问题是参数类型不匹配。后端用Integer接收的字段前端传了一个字符串“100”在非严格模式下虽然会自动转换但有时候会收到null值。另外日期时间字段建议统一使用String类型传输格式为yyyy-MM-dd HH:mm:ss前端解析展示都方便不要直接用Date类型和前端互传。5.2 常见报错与解决方案速查表我把实操中遇到的典型问题和解决方案整理成一张表方便你按图索骥问题现象可能原因解决方案前端访问接口报跨域未配置代理或后端未配置CORS开发环境优先配置Vue devServer代理生产环境用Nginx反向代理MyBatis查询结果全是null未开启驼峰映射application.yml设置map-underscore-to-camel-case: trueMySQL连接报SSL错误MySQL 8.x默认使用SSL连接JDBC URL加useSSLfalse以及allowPublicKeyRetrievaltrue中文乱码数据库/表/连接三方字符集不一致统一使用utf8mb4连接URL加characterEncodingutf-8密码明文字段显示64位乱码密码加密但无解密逻辑正常现象用BCrypt的matches方法校验即可npm install卡住或失败网络问题或npm镜像源不稳定切换淘宝镜像源重新安装端口被占用Tomcat或devServer端口被其他进程占用修改application.yml和vue.config.js中的端口配置这里重点说一下MySQL的SSL报错这是一个非常典型的环境问题。MySQL 8.x默认开启SSL本地开发环境没有配置SSL证书时JDBC连接会报类似“SSL connection error”的错。解决方案很简单连接URL后面加上useSSLfalse或configureServerCertificatefalse。还有allowPublicKeyRetrievaltrue也是MySQL 8.x常见的连接参数不加可能报Public Key Retrieval不允许的错。5.3 部署上线流程与注意事项课程设计如果只是本机演示把后端jar包跑起来、前端npm run serve起来就够了但真正要做到能给别人访问需要走一遍部署流程。后端打包很简单在项目根目录执行mvn clean package然后java -jar target/xxx.jar启动。确保application.yml里的数据库连接指向你的云服务器MySQL不要写localhost否则部署到服务器上会连不上数据库。前端打包需要执行npm run build会生成dist目录把dist目录放到Nginx的html目录下然后配置Nginx将/api前缀的请求反向代理到后端端口server { listen 80; server_name your-server-ip; location / { root /usr/share/nginx/html; index index.html; } location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时有一个非常容易忽视的点前端打包后访问刷新页面会报404。这是因为Vue Router使用history模式时刷新页面会向服务器发送实际路径的请求而服务器没有对应的路由配置。解决方案是在Nginx配置中添加try_files指令location / { try_files $uri $uri/ /index.html; }6. 这套源码的可扩展方向与个人体会6.1 从课程设计到生产系统的差距每次看到有人把课程设计当成生产系统看待我都要提醒一句它们之间还差着十万八千里。课程设计关注的是功能流程是否完整、代码结构是否清晰、能否演示出来生产系统关注的则是并发能力、数据安全、业务复杂度和可维护性。拿这套销售系统来说做课程设计时库存扣减用一条UPDATE语句加事务就足够了但生产上至少需要考虑分布式事务、分布式锁、消息队列削峰、订单超时自动关闭等功能。所以如果你以后进公司写代码发现真实项目的复杂度远超这里的内容千万别惊讶这才是正常行业水平。6.2 后续可以做的功能延伸如果你做完这套系统后想继续优化和扩展我建议从以下几个方向入手。第一引入Redis。把商品热门数据、验证码、登录Token存到Redis里减轻数据库压力这是最常规的升级路径。第二引入Spring Security或Shiro做更细粒度的权限控制尤其适合后台管理需要多角色超级管理员、运营、仓库管理员的场景。第三增加支付模拟模块对接支付宝沙箱或微信支付沙箱环境将下单后的支付流程真实串起来系统完整度会大幅提升。第四增加订单超时自动取消功能利用延迟任务或定时任务扫描超时未支付订单并释放库存这个功能在实际电商系统中是标配。6.3 我对做这类全栈项目的一点建议做了这么多年的项目从SSM到SpringBoot从JSP到Vue最大的感受是技术栈会过时但解决问题的思路永远比用到哪个框架更重要。一个销售系统的核心是让用户流畅地“选商品、下订单、付钱、收货”围绕这条主线的数据一致性、状态流转、权限控制才是真正的技术含量所在。框架随时可以换业务逻辑的打磨才是越做越有感觉的部分。回到这套SpringBoot Vue的销售系统如果你正在拿它做毕业设计或者学习项目请一定不要把完成当成终点。试着去改一改需求、加一个模块、换一种实现方式比如把普通的分页查询改造成带筛选条件的动态分页把单管理员改成多角色权限控制把本地存储的购物车改成数据库持久化。这些改造过程中遇到的问题才是你能写在简历上和讲给面试官听的真正的项目经验。
阅读完成 · 觉得有帮助?