尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
基于SpringBoot+Vue+MySQL的高校商铺管理系统设计与实现
很多同学在毕业设计选题时会挑类似“某高校XX管理系统”一类的题目这次要聊的“高校商铺管理系统”就是其中典型。它基于 SpringBoot Vue MySQL 这套成熟框架组合表面上是普通的增删改查后台实际上却包含角色权限、商铺租赁、合同管理、账单结算、通知公告等多个业务闭环。说白了就是把校园里几十家门店的入驻、租金、合同、续约、退租管理全部塞进一个 Web 系统里让管理员、运营人员和商户各司其职。这类题目适合两类人一是想用“全栈项目”体现综合能力、但又不想碰高并发和分布式复杂架构的本科生二是刚入门 Java 后端、想找一个能完整落地、也能顺畅答辩的练手项目的开发者。它不会让你在技术上显得“炫技”但能把基本功和业务逻辑展示得很扎实。下面我就按实际做毕设的流程把这个项目从需求到部署、再到答辩避坑完整拆一遍。1. 动手前必须理清的事把题目翻译成需求1.1 角色划分与核心业务路径很多同学拿到题目第一反应是“赶紧建表写代码”但商铺管理系统不是单纯管“商铺”两个字它真正管的是“经营关系”。我习惯先画一条业务主线商铺建档 - 合同签订 - 账单生成 - 费用缴纳 - 状态变更。围绕这条主线系统至少要有三类角色系统管理员管理后台配置负责审核商铺入驻、管理运营人员账号、查看全局营收统计、处理异常数据。运营专员日常操盘手录入商铺信息、发起租赁合同、录入抄表数据、生成账单、标记付款状态。商户租户商铺实际使用者登录后查看自己的商铺详情、租赁合同、历史账单线上缴纳租金并申请续租或退租。生活化的类比是这套系统就像一个“校园物业中心”管理员是物业负责人运营专员是前台接待和收费员商户就是各个门面房的租户。物业不能把合同和账单混在一张表里瞎记系统也一样角色和业务模块必须清晰分开。1.2 技术选型为什么是 SpringBoot Vue MySQL而不是更复杂的东西先看一组对比对比维度SpringBoot 单体SSM 传统项目SpringCloud 微服务配置成本自动配置极低XML 配置多繁琐服务治理组件多复杂部署方式内置 Tomcat直接跑 jar外置 Tomcat麻烦多服务部署环境要求高学习成本低适合毕设偏高过时很高明显过度设计毕设性价比高中低选 SpringBoot 不是因为它是“新技术”而是因为它把开发效率拉满。它内置了 TomcatMaven 引入几个 starter 就能跑起来配合 MyBatis-Plus 后连基础 SQL 都不用自己写。同样道理前端选 Vue 是因为组件化和响应式数据模型非常适合后台管理系统Element Plus 提供现成的表格、表单、弹窗、树形控件能让界面在短时间内达到可演示的状态。MySQL 则有成熟的关系型事务支持合同和账单这种强一致性数据恰恰需要事务兜底。另外一个态度问题我也想说一下。现在网上动不动就建议在毕设里堆 Redis、RabbitMQ、Spring Cloud Alibaba看起来“技术栈全”实际上答辩时评委问一句“你为什么要用它”就很容易卡壳。商铺管理系统的并发量撑不到微服务的层级事务放在单体里处理反而简单可靠。毕业设计的核心是展示你对业务的理解和代码的规范度不是展示你堆了多少中间件。2. 数据库设计整个项目好不好做的分水岭2.1 核心表结构与字段规划这个项目至少要设计这么几张表表名作用关键字段sys_user统一保存管理员、运营、商户账号user_id, username, password, real_name, role, phone, statusshop_type商铺分类如餐饮、打印店type_id, type_name, sort_ordershop_info商铺档案shop_id, shop_name, type_id, owner_user_id, floor_no, position, area, rent, statusrental_contract租赁合同contract_id, shop_id, renter_id, start_date, end_date, rent_amount, deposit, statuspayment_order账单流水payment_id, contract_id, period, amount, status, pay_timenotice_info通知公告notice_id, title, content, publish_time字段设计上有两个细节特别容易被忽视我逐一说明。第一owner_user_id 到底关联谁商铺在“在租”状态下属于某个商户但在“空置”状态下没有 owner。所以不能用 owner_user_id 作为商铺的唯一归属而是在合同表里维护“当前有效合同的商户”。查询商铺时通过“合同状态为生效中”去关联租户而不是直接取商铺表的某个字段。第二金额字段不要用 double 或 float。这类项目里你会涉及房租、物业费、保证金浮点数在累计计算时会丢失精度。建议用 decimal(10,2)在 Java 里对应 BigDecimal。这些细节虽然小但答辩时拿出来讲会显得你很有工程经验。2.2 状态字段、软删除与“为什么不用外键”的道理商铺状态我建议用整数枚举存储0 待审核1 空置2 在租3 停用4 到期未续。数据库里存 tinyintJava 里用枚举做映射。不要直接存中文状态名否则以后想改文案就会面临全表数据更新。再说一个毕设常见的坑不要建物理外键。物理外键会让数据插入顺序变得僵化比如你先插入合同、后补商铺资料外键直接就拦截你了。项目里逻辑外键就够了关联关系在 Service 层控制或者用 MyBatis-Plus 的关联查询来做。这样删除数据时也更可控不会因为一条外键约束导致整串数据删不掉。软删除也建议做上。给所有核心业务表增加 deleted 字段默认 0删除时改成 1。这么做不是为了炫技是防止答辩现场演示“删除商铺”时把历史合同和账单一起物理删掉了那数据就真的找不回来了。尤其在演示过程中误操作也是常见事故软删除是低成本高收益的保险。最后别忘了加 created_time、updated_time 两个公共字段。如果用 MyBatis-Plus可以让公共字段自动填充。跑批查询或者分析时时间线清晰会舒服很多。3. 后端实现要点认证、权限与账单逻辑3.1 工程结构与统一返回体创建 SpringBoot 工程时勾选 Spring Web、MySQL Driver、MyBatis-Plus、Lombok 就能起步。建议再引入 spring-boot-starter-validation 做参数校验蛮实用的。目录结构我习惯按这种分层组织controller/ # 对接前端接口 service/ # 业务逻辑 mapper/ # 数据库访问层 entity/ # 数据表实体 dto/ # 接收前端参数的传输对象 vo/ # 返回给前端的视图对象 config/ # 跨域、拦截器、全局异常处理很多同学写完一个项目controller 里直接塞一大坨业务代码返回给前端的数据又是 Map 又是 String答辩时一翻代码很容易被扣印象分。建议先做一个统一响应体Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT ok(T data) { ... } public static T ResultT error(String msg) { ... } }另外每个实体最好都有对应的 VO不要直接把数据库实体暴露给前端。比如商铺实体里有 deleted 字段就不应该传给前端。VO 可以做字段裁剪和组合这也是答辩时可以讲的规范点。3.2 登录认证用 JWT 还是 Session商铺管理系统属于前后端分离项目我推荐用 JWT 做登录态管理。流程是用户提交账号密码后端校验通过后生成 token 返回前端存到 localStorage 或 Pinia 里随后每次请求都在 header 里带上 token。实现上不必引入 Spring Security 全家桶自己写一个拦截器足够。核心逻辑如下PostMapping(/login) public ResultString login(RequestBody LoginDTO dto) { User user userService.login(dto.getUsername(), dto.getPassword()); // 比对 BCrypt 密码通过后生成 token String token JwtUtil.generate(user.getId(), user.getRole()); return Result.ok(token); }密码存储不要用明文后端使用 BCrypt 加密。很多学生图省事直接把密码以明文字符串存进数据库一旦代码被评审展示这就是严重隐患。BCrypt 加盐 hash 的效果非常可靠Spring Security 里自带的 BCryptPasswordEncoder 可以直接用。拦截器负责三件事解析 token 确认用户存在、判断接口角色权限、设置当前用户上下文。代码很短但这是整个后台系统的安全骨架。管理员接口和商户接口要分开控制比如“删除商铺”只允许管理员“查看我的合同”只允许商户本人这种权限颗粒度在答辩中是加分点。3.3 账单生成最容易踩坑的业务核心租赁合同生效后每个月要生成一张账单。业务逻辑看起来简单但有三个坑必须处理。坑一账单重复生成。如果运营人员在月结时连续点击了两次“生成账单”系统不应产生两条记录。解决办法是给 payment_order 表加唯一索引组合键是 (contract_id, period)。在代码里也要先做存在性检查写成一个幂等操作。坑二金额计算的精度和拆分。一张账单通常包含月租、管理费、水电分摊等。建议在 Service 层分别计算最后用 BigDecimal 相加。不要在前端做金额合计后端必须重新计算一遍防止接口被篡改。坑三缴费状态的变更。商户付款后后端要更新 payment_order 的状态同时可能推迟合同到期时间。这里要放到一个事务里否则就会出现“钱已付但合同没延期”的不一致状态。伪代码逻辑大概是Transactional public void generateBill(Long contractId, String period) { // 1. 查询生效中的合同 // 2. 校验该周期是否已存在账单 // 3. 计算租金和管理费 // 4. 插入 payment_order 记录 }这部分写扎实后整个系统的可信度就上来了。评委最喜欢问的就是“你这个账单是怎么保证不重复生成、数据不错乱的”能答清楚这一层基本等于稳了。4. 前端 Vue 实现从接口到可用页面4.1 工程初始化与目录规划前端我建议直接用 Vue 3 Vite Element Plus Pinia。为什么不用 Vue 2一是 Vue 3 已经是主流新项目再用旧版没有意义二是 Element Plus 对 Vue 3 的支持很好表格和表单组件完善做这种管理系统效率很高。初始化工程后目录大概这样src/ api/ # 后端接口封装 assets/ # 静态资源 components/ # 通用组件 layout/ # 后台整体布局侧边栏、顶栏 router/ # 路由配置 stores/ # Pinia 状态 views/ # 页面 login/ dashboard/ shop/ contract/ payment/页面划分建议跟着后端模块走每个模块包含列表页、表单页、详情页三种常见形态。4.2 axios 拦截器与路由守卫前后端分离后所有接口请求都建议封装在 api 目录下不要在每个页面里直接写 axios。统一封装的好处是拦截器只写一次// axios 请求拦截 service.interceptors.request.use(config { config.headers.Authorization Bearer ${localStorage.getItem(token)} return config }) // axios 响应拦截 service.interceptors.response.use(res { if (res.data.code 401) { router.push(/login) } return res.data })路由守卫也要按角色过滤。管理员可以进入系统管理页面商户只能看到自己的合同和账单。实现方式是路由 meta 里标记 roles在 beforeEach 里判断当前登录用户的角色不匹配就直接跳转首页并给出提示。这种体验会让系统显得“有权限意识”远胜于一个所有人都能看所有页面的 demo。4.3 列表页和表单页的通用实现套路商铺管理列表是整个系统的门面。Element Plus 里 el-table 绑定列表数据上方放搜索栏底部放分页组件。一个标准列表页的骨架是这样el-table :datashopList el-table-column propshopName label商铺名称 / el-table-column proptypeName label类型 / el-table-column proprent label月租金 / el-table-column label状态 template #default{ row } el-tag :typestatusTag(row.status){{ statusText(row.status) }}/el-tag /template /el-table-column el-table-column label操作 el-button link typeprimary clickopenDetail(row)详情/el-button /el-table-column /el-table表单页我建议用弹窗形式承载使用 el-dialog 包裹 el-form配合 rules 做校验。宽度控制在 600px 左右标题就取“新增商铺”“编辑合同”“登记账单”这类直白文案。不要做得太复杂小程序能用、好看、不啰嗦才最合适。记住一个重要原则页面里的数据不要用回调地狱方式一层层嵌套拿而是通过 Pinia 或页面级的 onMounted 调用统一的 API 函数。比如进入合同列表页就调用 contractApi.list(params)拿到数据直接赋值给响应式变量状态管理和页面渲染都会干净很多。5. 联调、部署与演示环境搭建5.1 开发环境的跨域处理本地开发时Vite 默认端口是 5173SpringBoot 默认 8080浏览器直接请求会产生跨域。最优雅的解决办法是配置 Vite 代理server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求 /api/shop/list 在开发环境下会自动转发到后端 8080 端口浏览器层面不感知跨域。后端也可以配置 CORS 规则但开发时以 Vite 代理为主即可。我碰到过很多项目明明后端 CORS 配好了部署时还是突然 401 或 404原因大都是没有区分“开发模式代理”和“生产模式反向代理”。5.2 前后端两种打包部署方式方式一前后端合并部署。前端执行 npm run build生成 dist 目录把整个 dist 文件夹复制到 SpringBoot 的 src/main/resources/static 下然后用 Maven 打包mvn clean package -DskipTests java -jar target/shop-manage.jar这样访问 http://localhost:8080 就能直接打开页面后端接口也在同一个端口上。适合演示环境流程短。方式二前后端分离部署。前端 dist 部署到 Nginx后端 jar 单独跑在 8080 端口。Nginx 配置反向代理把 /api 前缀的请求转发到后端location /api/ { proxy_pass http://127.0.0.1:8080/; }注意 proxy_pass 最后面的斜杠少了它路径拼接时会出现 /api/api 的尴尬问题。这个细节我见过不下三次导致前端页面接口大面积 404。生产或答辩环境建议用方式二因为更像真实项目部署。如果你担心答辩现场网络不稳定就在笔记本本地用方式一跑通再把后端 jar 和前端 dist 都准备好备用。5.3 数据库连接配置与初始化数据库连接配置不要硬编码在 application.yml 里。虽然本地开发硬编码最省事但答辩演示往往要连另一台机器的数据库。更好的做法是使用环境变量spring: datasource: url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:shop_db}?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: ${DB_USER:root} password: ${DB_PASSWORD:root}另外一定要准备一份 db/init.sql里面包含建库、建表、插入初始账号和模拟数据。答辩前只要执行一遍 init.sql 就能还原一个可演示的环境而不是依赖之前的测试数据。把初始化脚本当作交付物之一是很多评审老师会看重的好习惯。6. 答辩与避坑这类毕设最容易翻车的四个地方6.1 高频问题速查表现象可能原因解决办法登录后接口 401token 过期或拦截器放行路径没配好检查拦截器放行 /login、/register 等路径MySQL 连接报时区错误连接串缺少 serverTimezone在 JDBC URL 加 Asia/Shanghai账单金额对不上使用 double 累加全部改为 BigDecimal数据库用 decimal删除商铺却连带账单没了物理删除导致关联数据丢失改用软删除保留历史数据前端页面接口 404Nginx 代理路径配置错误检查 proxy_pass 末尾斜杠和 /api 前缀分页数据紊乱每页数量前端和后端不一致统一 pageSize 参数后端做兜底 maxPageSize我把这些情况整理成表格是因为它们几乎覆盖了我见过的大部分毕设事故现场。尤其“物理删除导致数据丢失”这条很多同学答辩前满怀信心结果演示到“删除一间没签合同的商铺”时把关联的历史提示记录全部删掉页面直接崩溃十分可惜。6.2 让项目更有“灵魂”的加分点如果时间充裕我建议再做三件锦上添花的事。第一加一张 operation_log 操作日志表。管理员审核入驻、运营录入合同、商户提交退租请求全部记录在案。答辩时你可以演示“查看操作日志”瞬间有一个可追溯的审计链路这在管理系统中非常加分。第二把“新建商铺 - 签订合同 - 按周期生成账单 - 模拟缴费 - 合同到期预警”做成一条完整演示路径。不要只介绍一个个孤立页面。让评委跟着你走完一个业务流程他们对项目的理解深度会完全不同。第三做一个简单的租期预警。在合同列表页增加筛选条件查询“距到期不足30天”的合同然后推送给运营端提醒。这其实就是一个普通查询但业务价值感很强。6.3 心态与技术之外的提醒最后聊点心态。答辩时不要背稿子也不要把每一个接口都念一遍。评委更想听到的是你作为开发者如何拆解业务、如何取舍技术方案、如何解决实际问题。哪怕你说“这里我考虑了软删除避免误操作导致数据丢失”都比“这里我用了一个 for 循环”得分高得多。我个人带项目时的经验是把代码读一遍把数据库设计过一遍把核心事务方法讲一遍这三点比界面多漂亮都重要。界面整洁即可真正决定成绩下限的是数据正确性和业务闭环上限则看你的表达思路是否清楚。这里也分享一个我自己的独门小技巧答辩前一天把数据库重新初始化一次用 init.sql 跑一遍然后完整操作一遍“合同到期后自动停用商铺”的流程。这个操作基本能查出来你是否漏配了定时任务、是否有人在正式环境依赖了手工清理的数据。我见过不止一个团队在演示前一晚发现自己漏了一条 SQL提前跑底prepared是唯一能兜底的办法。
RELATED

相关推荐

空间域统计排序滤波全解析:中值、极值滤波的Python实现与去噪实战

空间域统计排序滤波全解析:中值、极值滤波的Python实现与去噪实战

噪声是数字图像处理中最让人头疼的问题之一。我在项目里处理工业相机采集的零件表面图像时,椒盐噪声、脉冲噪声经常混在细节纹理里,普通的均值滤波一上,噪声是轻了,但边缘和细小划痕也被抹得一塌糊涂。后来我把空间域统计排序滤波…

📅 2026/10/13 22:04:08
Flink DataStream核心机制拆解:状态、水位线与反压实战

Flink DataStream核心机制拆解:状态、水位线与反压实战

很多人学 Flink 的时候都有过这么一段经历:照着官方文档把 WordCount 跑通了,觉得 DataStream API 不过如此,不就是map一下、keyBy一下、再sum一下嘛。可一旦把手里的业务逻辑真正往 DataStream 上迁,就开始到处碰壁——窗口算出来…

📅 2026/10/13 22:04:08
LiteLLM:统一多模型API调用的开源网关与实践指南

LiteLLM:统一多模型API调用的开源网关与实践指南

1. 项目概述:LiteLLM 是什么,它解决了哪些真实痛点? LiteLLM 这个名字乍一听像某个轻量级编程语言或小众框架,但其实它是一个在大模型应用开发圈子里迅速走红的开源工具库——它的核心定位非常明确: 统一所有主流大模…

📅 2026/10/13 22:04:08
MORE NEWS

更多资讯

📰

多阶段鲁棒调度模型:含可再生能源与储能的微电网最优运行的Matlab实现

微电网这个领域,大家一开始做调度优化,十有八九都是从“确定性模型”入门的:把光伏、负荷预测数据固定下来,丢进一个规划模型,出一组漂亮的机组组合和储能充放电计划。可真正对着实际系统跑一圈就会发现,预…

📰

2026年开源机器人遥操作设备:从选型到部署的工程实践指南

1. 从“能遥控”到“好用”的鸿沟:遥操作设备到底卡在哪聊机器人遥操作,很多人第一反应是“不就是个遥控器吗”。真上手做过的人都知道,让机械臂动起来不难,难的是让操作者觉得“这玩意儿跟我的手是一体的”。2026年这个时间节点上…

📰

FreeSQL:Oracle多版本SQL测试的云端解决方案

1. FreeSQL:Oracle多版本特性验证的革命性工具 作为一名长期与Oracle打交道的数据库工程师,我深知多版本环境管理带来的痛苦。每次需要验证不同Oracle版本特性时,要么得在本地部署多个实例,要么就得申请不同的测试服务器——硬件…

📰

MoE稀疏激活架构解析:552B参数如何只花8B推理成本

1. 稀疏激活这件事,到底省在哪1.1 从"全量调用"到"按需唤醒"的账本逻辑先把标题里的数字翻译成人话。552B 参数指的是模型总容量,8B 指的是每次前向推理真正参与计算的参数量。这两个数字差了几十倍,但模型效果却要往 55…

📰

华为OD三轮面试复盘:Java高频真题与核心考点全解析

明白,以下是按照您的要求生成的完整博文内容。这是一篇围绕“华为OD三轮面试复盘:Java开发高频真题核心考点全解析”这一标题展开的深度技术复盘文章,包含面试流程梳理、高频真题拆解、核心考点原理分析、实操心得与避坑指南,全文…

📰

Two-Pass HZB遮挡剔除技术:原理与性能优化实践

1. 技术背景与核心价值在实时渲染领域,遮挡剔除(Occlusion Culling)一直是提升性能的关键技术。传统方案如视锥剔除只能处理相机可见范围内的物体,而Hierarchical Z-Buffer(HZB)技术通过构建深度层级结构&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬