news 2026/10/10 9:38:39

校园商铺管理系统完整开发指南:SpringBoot+Vue+MySQL从设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
校园商铺管理系统完整开发指南:SpringBoot+Vue+MySQL从设计到部署

做毕设辅导这几年,我经手过最多的题目类型,就是“某某系统管理平台”。表面看,这类题目就是标准的增删改查,很多同学拿到题目的第一反应是“稳了”,结果真正动手才发现:功能好写,但数据库设计容易乱,论文难凑,部署难搞,答辩难讲。今天我把这套“校园商铺管理系统”完整拆一遍,从业务设计、数据库建模,到核心代码、部署排错,再到论文怎么对应着写,一条线全给你捋清楚。

这套系统用的是 SpringBoot + Vue + MySQL,是目前毕设生态里最稳的组合。为什么说“最稳”?答辩老师都认识这三样,不会追着问你冷门框架;前后端分离结构能撑起论文里的系统设计章节;单机部署就能跑完整套流程,不需要额外买服务器,也不用配一堆中间件。适合谁看?准备选这类题目但还没定技术方案的同学、代码已经写完但论文憋不出来的同学、以及想从头理解一套前后端分离项目是怎么落地的后台开发新人。

1. 项目定位与方案取舍:为什么偏偏是这一套组合

1.1 先把业务场景说清楚

商铺管理系统,管的东西说白了就是:校园里的一排商铺,哪些铺位租出去了、租给了谁、租金什么时候交、合同是几号到期、最近三个月总共收了多少租金。它和网上商城完全是两回事——商城管的是商品和订单,商铺系统管的是“铺位—合同—账单”这条资产线。

我手头这个项目对应的场景是某高校实训楼底商,一共三层。底层是奶茶店、便利店、打印店,人流集中;楼上因为人流量问题大部分空置,所以系统里必须有“空置铺位”的概念。很多同学做这类系统时最容易犯的错,就是把商铺表当订单表设计,上来就给每个商铺挂一堆字段,结果合同、缴费这些核心业务反而没地方放。后面改表结构改到想哭。

这个系统最终服务三类人:系统管理员负责整个铺位资源的管理和收费统计,商户可以登录查看自己的合同和缴费记录,普通学生或访客则查看铺位公示、提交入驻意向。三条角色线分开之后,功能边界会非常清楚。

1.2 技术选型不是拍脑袋

先列一下我最终定的技术栈,再逐个说为什么:

  • 后端采用 SpringBoot 2.x。相比 SSH/SSM 时代的 XML 配置,SpringBoot 内嵌 Tomcat,打包成 jar 直接java -jar就能跑,现场演示时最快十分钟能起一个后端环境,这是答辩环节的巨大加分项。
  • 前端采用 Vue 2 + Element UI,或者 Vue 3 + Element Plus 都行。如果你手里已经有一堆 Vue 2 的组件代码,就不要强行上 Vue 3,毕设评分不会因为你用了新版本就多给分,稳定跑通比什么都重要。
  • 数据库用 MySQL 5.7 或 8.0。除非论文里专门安排了 MongoDB 相关内容,否则别给自己找麻烦。
  • ORM 选 MyBatis-Plus。相比原生 MyBatis,不用写大量 XML,BaseMapper 自带单表 CRUD,配合 LambdaQueryWrapper 写条件查询非常顺手,也符合目前招聘市场的主流用法。

我还见过同题目的毕业生用 JSP + Servlet + JDBC 做的,功能一样能跑,但论文里的“技术选型”章节就得拼命解释为什么不用主流框架,答辩时容易被追问到无话可说。所以选型这件事,不只是为了跑通代码,更是为了论文和答辩有底气。

1.3 前后端分离到底意味着什么

前后端分离不是炫技。它的实际好处是:前端可以用 mock 数据先画页面,后端用 Postman 或 Apifox 先测接口,两边并行推进,最后联调。部署也灵活——前端构建出来是一堆静态文件,交给 Nginx 托管;后端就是一个 jar 包,只要有 JDK 和 MySQL 就能跑。

但前提是接口规范想清楚。我习惯把所有接口统一挂在/api前缀下,比如/api/admin/login、/api/shop/list,后端 Global prefix 或 Controller 里加 RequestMapping 前缀,前端 axios 的 baseURL 也统一写/api。这样联调时少踩很多路径不一致的坑。

2. 功能模块拆解与角色设计

2.1 三种角色三条业务线

系统拆成三个端,每个端的功能集合不要混:

角色核心功能典型页面
系统管理员商铺资源管理、合同管理、账单管理、商户管理、统计看板仪表盘、商铺列表、合同列表、账单列表
商户查看名下合同、查看账单、模拟缴费、维护店铺资料我的合同、我的账单、店铺资料
学生/访客查询铺位公示、提交入驻意向铺位公示、意向表单

做角色拆分时有个特别重要的原则:页面可以共用,接口权限必须隔离。比如“商铺列表”这个页面,管理员看的是全部铺位,学生看的是“可租”铺位,你不能因为页面长得像就共用一个接口,否则权限会漏成筛子。我在这个项目里用拦截器判断 token 里的 role 字段来控制接口访问,简单场景完全够用;要是你想在论文里多写点内容,可以升级成 Spring Security + JWT,但别为了“高级”把自己搞晕。

2.2 合同模块才是整个系统的灵魂

如果只看“商铺管理”这四个字,很多人的第一版表结构是这样的:商铺表里直接放商户名、租金、到期时间。这么做在小规模演示里能跑,但一旦涉及续租、退租、租金调整,你就发现所有东西都写死在一条记录里,改都没法改。

正确做法是把合同独立成表。一份合同记录:商铺 id、商户 id、起租日期、到期日期、月租金、押金、状态(生效中/已到期/已退租)。账单则按照合同自动生成,比如每月 1 号生成一笔当月租金账单,商户缴费后账单状态变为“已缴”。这样商铺、合同、账单形成一条清晰的数据链:商铺被哪份合同占用,合同又产生了哪些账单,全部可追溯。这套抽象在论文里写“合同全生命周期管理”,专业感立刻就出来了。

2.3 前端页面规划

页面按角色拆,每组页面控制在 4 到 6 个以内,不至于做完:

  • 登录页:管理员和商户共用一个登录入口,登录后根据角色跳转不同首页。
  • 管理员端:仪表盘(当月营收、可租铺位数量、到期合同提醒)、商铺管理、合同管理、账单管理、商户管理。
  • 商户端:我的合同、我的账单、账单缴费(模拟支付)、店铺资料维护。
  • 公共端:铺位公示列表、入驻意向提交。

提醒一句:演示时老师喜欢看“提醒类”功能,比如“最近 7 天有 3 份合同即将到期”,这种一眼能看到业务逻辑的统计,远比花哨的图表加分。实现也不难,后端写个接口查到期时间在 7 天内的合同,前端仪表盘放个数字卡片就行。

3. 数据库设计:表结构、类型选择与关系取舍

3.1 核心表清单

我最终落地的库一共 7 张表,先看总览:

表名核心字段说明
sys_userid, username, password, real_name, phone, role管理员和商户统一放一张表,用 role 区分
shopid, shop_no, name, location, area, status, monthly_rent铺位基础信息,status 区分空置/出租/维护
contractid, shop_id, user_id, start_date, end_date, monthly_rent, deposit, status合同主表,一条合同关联一个商铺和一个商户
billid, contract_id, period, amount, status, create_time, pay_time周期账单,period 存按月字符串
applyid, shop_id, applicant_name, phone, reason, status, create_time入驻意向表
noticeid, title, content, create_time公告/通知
system_logid, operator, action, create_time操作日志,论文测试章节可以引用

这张表结构看起来平平无奇,但支撑了整个系统的核心流程。其中 sys_user 表合并管理员和商户是个刻意的选择:两者本质上都是“账号 + 身份”,拆分只会增加无意义的代码量,一个 role 字段就能区分权限。如果商户需要存营业执照、经营范围这类扩展信息,可以再加一张 merchant_info 扩展表,一对一关联 sys_user.id。

3.2 金额字段为什么必须用 decimal(10,2)

经典问题。用 double 存金额,0.1 + 0.2 这种精度问题迟早会在累计统计时暴露。比如某账单单价是 1200.60,另一个是 899.90,相加后 Python 或者 Java 的 double 运算会给你一个诡异的 2100.4999999999995,展示出去非常难看。

MySQL 里金额一律用decimal(10,2),Java 实体类用 BigDecimal,前端展示时用toFixed(2)或直接展示后端传来的字符串。不要用 float,更不要用 int 存“分”——毕设阶段没必要自己增加换算复杂度。押金、租金、滞纳金等所有涉及钱的字段,统一规格,写代码时省心很多。

3.3 外键、索引与逻辑外键的取舍

很多课程教外键约束,但真实项目里物理外键用得越来越少,因为删除时容易连环报错。这个项目我建议用逻辑外键:表之间有关联字段(shop_id、contract_id),但不加 FOREIGN KEY 约束,删除由代码控制。

好处是测试数据好造、删除商铺时不会突然被数据库拦截。你删除一份错误合同,最好只删 contract 表里的记录,而不是被外键牵着走。但论文里一定要写清楚“采用逻辑外键设计,由应用层保证数据一致性”,这反而是一个可以被老师夸的加分点。

索引方面,别一上来就给所有字段加索引。只需要在查询频率高的字段上加:shop 的 status、contract 的 shop_id 和 end_date、bill 的 contract_id 和 period。尤其注意 bill.period 不要存 Date 类型,直接存字符串 ‘2024-05’,这样统计 SQL 写where period = '2024-05'比用date_format函数快得多,逻辑也更直白。

4. 核心功能实现:从登录认证到租金统计

4.1 登录与认证

不建议在毕设里自己写 session 管理,直接用 JWT。流程是:登录接口校验用户名密码(密码存 BCrypt 哈希,不要明文),校验成功签发 token,前端把 token 存到 localStorage,axios 请求拦截器在请求头带上Authorization: Bearer xxx,后端用拦截器校验 token 并解析出 userId 和 role。

这里有个坑值得单独说:使用 io.jsonwebtoken 的 jjwt 库时,网上大量教程写的是 0.9.1 版本,但 JDK 9 以后运行会直接报ClassNotFoundError: javax/xml/bind/DatatypeConverter。解决办法是换 jjwt 0.11.5,API 略有调整但文档清晰。这个坑几乎每年都能在答辩前夜困住一批人,先把依赖版本核对好。

4.2 用 MyBatis-Plus 把 CRUD 减到最少

实体类建好后,Mapper 接口继承 BaseMapper,Service 继承 IService,Controller 里直接调用 list、save、updateById、removeById,单表的增删改查基本不用写 SQL。复杂点的条件查询用 LambdaQueryWrapper,比如查所有生效中的合同:

LambdaQueryWrapper<Contract> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Contract::getStatus, 1) .ge(Contract::getEndDate, LocalDate.now()); List<Contract> contracts = contractService.list(wrapper);

分页查询配合 MyBatis-Plus 的分页插件,前端传 page、pageSize,后端返回 total 和 records,这是前后端列表页的标准姿势,代码量比原生 MyBatis 少一半以上。Controller 层返回统一数据结构,比如Result.success(data),前端处理起来非常统一。

4.3 按月统计租金的 SQL 写法

管理员首页最常看的数字是“本月应收租金”和“近六月租金趋势”。如果 bill 表每笔账单都记录了周期(例如 period=‘2024-05’)和状态,那么月度应收就能写成:

SELECT period, SUM(amount) AS total FROM bill WHERE status = 0 GROUP BY period ORDER BY period DESC LIMIT 6;

注意 SUM 的字段类型是 decimal,返回的也是 decimal,Java 里用 BigDecimal 承接就不会丢精度。如果是“实收”口径,只需要再加一个WHERE status = 1(已缴费)。应收和实收的区别建议在代码注释里写清楚,论文的需求分析里也最好定义清楚,答辩被问到“应收和实收怎么区分”时你不会慌。

4.4 Vue 端的关键封装

axios 封装三件事一定要做:baseURL 统一、请求拦截器带 token、响应拦截器统一处理业务码。响应统一结构我习惯用{ code: 200, message: 'success', data: ... },前端拦截器判断 code 不是 200 时就弹 Message 提示,后端异常时 code 为 500 并带上友好提示。

路由守卫也非常重要,没有登录时访问任何需要权限的页面,都重定向到 /login,并根据 role 做页面访问控制。不然老师演示时随手改一下 URL 就能跳进管理页,这就不仅是扣分的问题了,而是系统安全性的硬伤。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else if (token && to.path === '/login') { next('/') } else { next() } })

5. 部署全流程:从 Windows 本机到 Linux 服务器

5.1 环境清单

部署之前先确认环境,版本不匹配是最大的坑:

  • JDK 1.8:毕设项目用 8 就够了,不要为了追新去 JDK 17,除非你确认所有依赖都兼容。
  • Maven 3.6+、Node.js 14 或 16。注意 Node 17+ 跑 vue-cli 4 会报 OpenSSL 错误,后面排错部分细讲。
  • MySQL 5.7 或 8.0、Nginx(部署前端用,本机调试可以不用)。
  • 开发工具:IDEA 或 VS Code,数据库工具 Navicat 或 DataGrip,接口测试 Postman 或 Apifox。

我在部署时踩过最痛的坑是 Node 版本从 14 升到 18 后,npm run build直接报错,吓得以为是代码问题。后来发现就是 Node 版本太新导致的,降回 16 秒过。所以环境清单一定写在部署文档第一页。

5.2 数据库初始化

项目里的 SQL 文件通常分两种角色:建库建表脚本和初始数据脚本。导入步骤:

  1. 先建空库:create database shop_system default character set utf8mb4;
  2. 再运行 SQL 文件,把表结构和初始数据导入。
  3. 验证:连接数据库,执行show tables;能看到 7 张表,再执行select * from sys_user;能看到初始账号。

注意字符集统一用 utf8mb4,别用 utf8。否则商户名称里一旦出现特殊字符或生僻字,保存就报错或者变成乱码,答辩现场输入一个特殊字符直接翻车。这个细节部署文档里必须写。

5.3 后端打包与启动

后端在 IDEA 里点 package,或者命令行执行:

mvn clean package -DskipTests

产物是 target 目录下的 jar。启动命令很简单:

java -jar shop-system-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

生产环境配置里最关键的几个项:数据库地址从 localhost:3306 改成服务器 IP,账号密码不要用初始弱口令,端口注意不要和服务器上其他服务冲突。我习惯把生产配置单独抽成 application-prod.yml,和本地开发配置分开,避免本地调试时误连生产库。

5.4 前端构建与刷新 404 问题

前端打包:

npm run build

产物在 dist 目录,把 dist 部署到 Nginx:

server { listen 80; server_name localhost; location / { root /usr/local/shop/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; } }

很多同学部署完发现刷新页面就 404,原因就是 SPA 的路由在 Nginx 层没有 fallback。上面配置里try_files $uri $uri/ /index.html;这一行就是解决这个问题的。这么一小行代码,能帮你避免部署阶段最绝望的一次事故。

5.5 部署文档怎么写到能照着抄

部署文档不要写成“步骤一:下载 JDK”这种天书,要按环境、角色、顺序一步步来,每步给出验证方式。比如:

  • 操作:修改 application-prod.yml 的数据库密码。
  • 验证:重启后看日志有没有 “Tomcat started” 和数据库连接成功的输出。

按这个颗粒度写,答辩现场照着手册操作就能跑通,老师问“你能不能现场演示一遍”时你也能从容应对。部署文档从来不是给老师看的,是给你答辩那天紧张到大脑空白时候的救命稻草。

6. 常见问题与排错实录

6.1 前端访问后端跨域报错

前后端分离后,本机前端跑在 8081,后端跑在 8080,浏览器会拦截跨域请求。最简单的方案是后端加一个全局 CORS 配置:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("*") .allowedHeaders("*") .allowCredentials(true); } }

如果你走的是 Nginx 反向代理方式,跨域在 Nginx 层就解决了,后端就不需要加这个配置。二选一,不要两头同时搞,反而容易出玄学问题。

6.2 MySQL 版本导致的驱动差异

导入别人项目时最经典的报错:Cannot load class driver: com.mysql.jdbc.Driver。MySQL 5.7 时代常用连接器 5.x,类名是com.mysql.jdbc.Driver;MySQL 8.0 需要 8.x 驱动,类名是com.mysql.cj.jdbc.Driver,并且要在连接串上额外加serverTimezone=Asia/Shanghai。

排错顺序建议:先看本机 MySQL 版本,再看 pom.xml 里的 connector 版本,最后看 application.yml 里的 driver-class-name 是否匹配。这三个环节任何一个对不上,启动都会报错。

6.3 Node 版本与 vue-cli 的 OpenSSL 报错

报错内容类似error:0308010C:digital envelope routines::unsupported时,基本可以断定是 Node 17 以上跑 vue-cli 4。两个解决办法:一是把 Node 降到 16;二是在 build 脚本里加NODE_OPTIONS=--openssl-legacy-provider。我推荐第一种,因为改脚本方式在跨平台部署时经常有各种兼容问题。

6.4 端口被占用

启动时报Port 8080 was already in use,Windows 上执行:

netstat -ano | findstr 8080

找到 PID 后:

taskkill /PID xxx /F

Linux 上则是:

lsof -i:8080

顺便说一句,把后端的 server.port 改成一个不太常用的端口比如 9090,也能减少冲突概率。我就吃过前端页面里写死 8080 的亏,前后端联调时经常被迫杀进程。

6.5 时间字段差了 8 小时

MySQL 和 Java 之间出现时间字段相差 8 小时,通常是连接串没带 serverTimezone,或者 MySQL 时区设置成了 UTC。解决办法:JDBC URL 里加serverTimezone=Asia/Shanghai,并在配置里统一时区。这个坑很小但很烦,而且出现在论文的“系统测试”截图里尤其尴尬,老师一眼就能看出时区没处理。

7. 论文结构:把代码翻译成能过的文档

7.1 七个章节的骨架

毕设论文通常按这个结构走:摘要、绪论(背景与意义、国内外研究现状)、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望。对应的逻辑就是:先告诉老师你要做什么,再说你用什么做,然后说你设计了什么,最后说你做成了什么并且验证了它是好的。

写代码时留下的每个注释、每个类名、每个数据库字段,在写论文时都是素材。Controller 层的每个接口,对应系统实现章节的一个小标题;数据库设计的每张表,对应系统设计章节的表结构描述。强烈建议做项目时就按这个对应关系整理文档,而不是代码做完以后熬夜补论文。

7.2 需求分析怎么写不像抄模板

需求分析是老师最常怀疑你“抄模板”的部分。与其写一堆“系统具有易用性、安全性、可扩展性”这种废话,不如写成具体的场景描述,比如“商户登录后应能查看名下所有合同及对应账单,账单逾期时系统进行醒目提示”。

这类描述可以直接从代码里反推出来:你有哪张表、哪个字段、哪个状态值,就能写哪些需求。再配一页功能结构图和一页用例图,这两张图在论文和答辩 PPT 里都很能撑场面。切记不要贴一堆从别处复制的通用需求条款,老师翻到那一页基本心里就有数了。

7.3 系统测试章节怎么填

系统测试章节最忌讳只写“功能正常”。我建议按模块列黑盒测试用例表,每个用例包含:用例编号、测试步骤、预期结果、实际结果、是否通过。不用多,每个模块写 3 到 5 条就够。比如“管理员登录——输入错误密码——提示用户名或密码错误——实际结果一致——通过”。这类内容非常真实,也可以顺手把前面排错过程遇到的典型问题写进“测试中发现的问题及修复”小节,反而显得项目经得起折腾。

最后说点我在实际带这类项目时的体会:这套系统的真正难点从来不在代码,而在“你把一个真实业务抽象成数据模型”的那一步。合同和账单这两张表想明白了,整个系统就立住了;想不明白,后面每一步都是补丁摞补丁。如果你正准备做类似的毕设,我给的建议是:先画模型,再写代码;先跑通主流程,再做花哨功能;部署文档从第一天就开始写,不要拖到答辩前最后一晚。

再分享一个小技巧:系统做完之后,花半小时把每个核心接口用 Apifox 导出一份带真实响应的接口文档。答辩时老师问“这个查询用了什么接口”,你直接打开文档指给他看,比现场翻代码快得多,这个印象分会非常可观。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 9:38:06

Python单元测试最佳实践:unittest框架核心用法与工程管理指南

1. 为什么你的项目需要单元测试——先想清楚再动手1.1 单元测试到底在解决什么问题我在一线写了十来年代码&#xff0c;见过太多项目死在"改一处代码&#xff0c;崩三个功能"的泥潭里。最常见的场景是&#xff1a;产品经理说"帮我把价格计算里加个折扣"&am…

作者头像 李华
网站建设 2026/10/10 9:37:48

基于SpringBoot+Vue的酒店管理系统设计与实现全攻略

每年到三四月份&#xff0c;总有不少同学私信问我&#xff1a;毕设到底选什么题&#xff1f;系统做到什么程度答辩才稳&#xff1f;有没有一个项目是“功能够全、技术栈够主流、工作量看起来也够足”的&#xff1f;如果你正在为选题挠头&#xff0c;那我非常建议看看“基于Spri…

作者头像 李华
网站建设 2026/10/10 9:37:04

英语报警口语速成:5W框架与六大紧急场景应对

1. 报警电话的5W框架&#xff1a;先搞清楚接警员想听什么很多人学英语报了十多年培训班&#xff0c;雅思也考过&#xff0c;真到了国外碰上抢劫、车祸或者朋友突然倒地不起的那一刻&#xff0c;大脑直接一片空白&#xff0c;嘴里只剩下“Hello”和“Help”。这不是个别现象&…

作者头像 李华
网站建设 2026/10/10 9:35:46

银行排队系统:从数据结构到事件驱动仿真全解析

简介&#xff1a;一份面向数据结构课程期末作业的银行排队系统实现资源&#xff0c;重点演示队列先进先出结构以及VIP与普通用户的多队列优先级调度。压缩包共九个文件&#xff0c;大小约1.03MB&#xff0c;包含C源码、用户信息文本、可执行程序及Code::Blocks工程文件&#xf…

作者头像 李华
网站建设 2026/10/10 9:35:05

高校社区生鲜配送系统实战:从Spring Boot部署到订单状态机设计

简介&#xff1a;面向高校社区场景的生鲜配送系统项目&#xff0c;是一份基于Java技术的Web前后端完整工程&#xff0c;适合计算机专业学生用于毕业设计、课程设计或项目实训。系统覆盖用户管理、商品管理、订单处理、库存控制、配送调度、支付接口、数据分析、客服和移动端适配…

作者头像 李华