news 2026/9/9 9:34:13

基于SpringBoot+Vue的蛋糕店管理系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot+Vue的蛋糕店管理系统设计与实现

1. 毕设选题没头绪?蛋糕店这套业务逻辑为什么值得做

每年到毕业设计环节,大部分同学最先卡住的问题不是"怎么做",而是"做什么"。数据库课程设计也好、软件工程综合项目也罢,选一个既能体现工作量、又能讲清楚业务闭环、还不至于把自己困死在技术细节里的题目,其实比想象中难。我当时换过好几个方向,比如二手交易平台、校园失物招领、在线考试系统,最后定下来做蛋糕店管理系统,理由很实际:蛋糕店这个场景足够生活化,业务链条完整,从商品展示、购物车、下单支付到库存扣减、订单状态流转、后台数据统计,每一步都有东西可写、可演示、可被答辩老师追问。

这套基于SpringBoot + Vue的蛋糕店管理系统,采用的是经典的前后端分离架构。后端以SpringBoot为核心,配合MyBatis-Plus操作MySQL数据库,前端用Vue 2 + Element UI搭页面,通过Axios调用RESTful接口。功能上分成用户端和管理端:用户端负责蛋糕浏览、按分类筛选、加入购物车、下单结算、查看订单状态;管理端负责商品管理、分类管理、订单处理、客户留言管理和数据看板。听起来不算多,但覆盖面已经足够广,数据库建模、接口设计、状态管理、权限控制、图表可视化全都有了。

这里想先给正在选题的人一个建议:毕设项目的核心价值不在于用了多冷门多高级的技术,而在于能不能自圆其说地解释清楚"这个系统解决什么问题、架构怎么设计、每张表为什么这么建、接口为什么这么定义"。蛋糕店管理系统恰恰满足这些。它不像电商平台那么庞大,不需要秒杀、支付网关对接、分布式事务,但又有足够的业务纵深可以挖掘,作为计算机毕业设计来说,性价比非常高。

1.1 蛋糕店管理系统到底管什么

如果你去问一家蛋糕店老板,他最头疼的是什么,答案大概率不是"怎么把蛋糕做好吃",而是"订单乱、库存对不上、客户信息丢三落四"。尤其是定制蛋糕这种业务,客户往往提前几天预订,指定款式、口味、取货时间,门店需要提前备料。一旦订单靠纸笔记录,漏单、错单、取货时间记错,都是家常便饭。

这套系统就围绕这些真实痛点展开:

  • 商品展示与检索:蛋糕按品类分类(生日蛋糕、慕斯、小甜点、面包西点等),前端按分类展示,支持分页、搜索。
  • 购物车与下单:用户选择蛋糕规格(尺寸、口味),加入购物车,统一结算。
  • 订单全生命周期管理:订单从待支付到待发货,再到配送中/待自提,最后到已完成或已取消。管理员可以对订单进行状态推进,用户端实时可见。
  • 定制留言与反馈:蛋糕定制常有特殊需求(比如刻字、指定色素),用户下单时可填写备注,也可以单独留言,管理员在后台查看。
  • 后台数据看板:展示今日订单量、今日销售额、热销商品排行、近7天销售趋势,方便店铺做决策。

这些功能点提炼成需求文档之后,你会发现它其实就是一个"简化版电商系统"。数据库表设计、接口设计、页面流转,全部有成熟套路可以借鉴,同时又不需要处理支付回调、退款、库存锁定这些复杂逻辑,非常适合作为毕业设计的选题。

1.2 技术选型的比较过程:为什么是SpringBoot + Vue

我评估过几套方案,各有优劣,这里列出来给正在纠结的同学做参考。

方案一:JSP + Servlet + MySQL

这是很多学校教学过程里讲的方案,也是所谓的"最稳"选择。优点是你对底层原理门儿清,Servlet怎么处理请求、Session怎么管理、JSP怎么渲染页面,老师问起来你全都答得上来。但缺点也很明显:前端页面和服务端代码耦合在一起,页面交互体验比较差,而且这个技术栈本身已经偏向"教学化",答辩时如果评委问一句"为什么不用前后端分离",你很难给出有说服力的理由。

方案二:SpringBoot + Thymeleaf(服务端渲染)

比方案一好一些,SpringBoot确实大幅简化了后端开发,模板引擎也让你不用写一坨Java代码输出的HTML。但问题在于,Thymeleaf的页面交互还是要靠JQuery + Ajax去拼HTML,复杂一点的购物车、多条件下单,前端代码会写得非常痛苦。而且从毕设的"技术展示面"来看,缺了Vue/React这类现代前端框架的加持,整体观感会低一个档次。

方案三:SpringBoot + Vue前后端分离(最终选择)

选这套方案,最直接的原因是它能同时覆盖后端和前端两条技术线。SpringBoot代表的是Java生态里最主流的微服务开发框架,Vue是当前国内中小型企业项目里使用率很高的前端框架,两者结合,答辩时技术亮点足够多——RESTful API设计、跨域处理、JWT身份认证、前端路由守卫、状态管理,随便提一个都能展开聊上几分钟。

另外,Vue本身的学习曲线在主流前端框架里相对平缓,模板语法直观,组件化开发的思想也比JQuery时代先进太多。就算前期没怎么接触过前端,花几天时间过一遍Vue核心概念就能上手写页面。

对比总结如下表:

维度JSP + ServletSpringBoot + ThymeleafSpringBoot + Vue
前后端分离
开发效率
技术展示面较窄
答辩亮点一般多,可深入提问
踩坑风险中高,但可控

我承认前后端分离的开发方式在初期确实会多一些工作量,比如跨域处理、接口联调、环境搭建都要额外花时间。但这些东西恰恰是毕业后进企业会遇到的真实开发场景,提前走一遍,收益远大于成本。

1.3 这个选题在答辩时的"隐藏加分项"

很多人忽视了一点:答辩老师看一个毕设好不好,不仅看功能齐不齐,还看你有没有"设计感"和"工程意识"。蛋糕店管理系统在这方面有天然的叙述优势。

业务闭环完整。用户可以逛店、加购、下单、查看订单,管理员可以处理订单、管理商品、查看统计。这形成了一个完整的故事线,演示起来非常流畅,不会出现"这个按钮做了但没接数据"的尴尬。

可扩展空间大。答辩老师通常最后会问一句"后续还能加什么功能"。蛋糕店系统的扩展方向太多了:引入会员积分、接入微信支付、做小程序端、加消息推送通知用户取货、用Redis缓存热销商品……你甚至不用去写这些功能,只需要在论文的"展望"章节里把这些方向以清晰的技术语言描述出来,老师就会觉得你有全局思考能力。

数据可视化为答辩撑场。后台的数据看板(销售趋势折线图、热销商品饼图)是整个系统的视觉亮点,演示时一打开就是满满一屏图表,视觉冲击力很强,答辩开头五分钟就能把评委的目光抓住。

2. 数据库设计与项目结构:地基打好了,后面不返工

很多同学做毕设容易犯一个错误:上来就写代码,表结构一边写一边改,结果项目做到一半发现订单表和商品表对不上,购物车查不到数据,又回去重构数据库。这种返工极其耗费时间。我个人的经验是:先花一天时间把数据库表全部建好,实体类、Mapper、接口设计全部跟着表结构走,后面开发会顺很多。

2.1 数据库表设计:从业务反推每个字段

蛋糕店管理系统我设计了7张核心表,严格遵循第三范式,同时做了适度的冗余方便查询。下面逐张开讲。

用户表(user)

  • id:主键,自增
  • username:用户名,唯一索引
  • password:BCrypt加密后的密码
  • nickname:昵称
  • avatar:头像地址
  • phone:手机号
  • role:角色,0表示普通用户,1表示管理员
  • status:状态,1正常,0禁用
  • create_time / update_time:创建时间和更新时间

角色字段我放在了用户表里,而不是单独建角色表,原因很简单:系统只有两种角色,没有细粒度的权限模型需求,单独建表反而增加关联查询的复杂度。只要在登录接口里把role返回给前端,前端根据role控制菜单和路由显示即可。

商品分类表(category)

  • id,name(分类名称),sort(排序字段),icon,status

商品表(cake)

  • id,category_id(外键关联分类表),name,description,price(DECIMAL(10,2)),image,taste(口味标签),size(规格,如6寸/8寸/10寸),stock(库存),sales(销量,冗余字段),status(上下架状态),create_time

购物车表(cart)

  • id,user_id,cake_id,quantity,checked(是否选中),create_time

一个用户对应多条购物车记录,用user_id + cake_id作为唯一索引,加购时如果已存在则累加数量。

订单表(orders)

  • id,order_no(订单编号,唯一,用时间戳+随机数生成),user_id,total_amount(总金额),status(0待支付,1待发货,2配送中/待自提,3已完成,4已取消),receiver_name(收货人),receiver_phone,receiver_address,remark(定制备注),create_time,pay_time,delivery_time,finish_time

这里把收货信息冗余在订单表里而不是单独建地址表,是考虑到蛋糕店的实际场景——用户下单时临时填写收货信息即可,不需要维护一个常用地址簿。做毕业设计讲究的是"够用就好",不要被复杂需求绑架。

订单详情表(order_item)

  • id,order_id(外键关联订单表),cake_id,cake_name(快照冗余),cake_image,price(下单时的单价),quantity,subtotal

订单详情表非常重要,它存的是一个"历史快照"。假设商品改价了或者商品被删除了,订单里依然能查到用户购买时的名称、单价和图片。这种"冗余设计"是从电商系统里学来的成熟经验,答辩时如果被问到为什么冗余,这是个标准的加分回答。

留言反馈表(message)

  • id,user_id,content(留言内容),reply(管理员回复),create_time

轮播图表(banner)

  • id,image,url,sort,status

关于字段类型,有几个地方容易忽视,我单独列出来提醒:

  • 金额一律用DECIMAL(10,2)绝对不要用float或double,否则算总价时会出现1.999999这种经典精度问题。
  • 时间字段统一用datetime,Java实体类对应LocalDateTime,配合MyBatis-Plus的自动填充功能,插入更新时不用手动set时间。
  • 状态字段用tinyint,存数字,扩展性比varchar存"待支付""已支付"这种中文要好得多,查询也快。

建表语句示例:

CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单编号', `user_id` int(11) NOT NULL COMMENT '下单用户id', `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0待支付 1待发货 2配送中 3已完成 4已取消', `receiver_name` varchar(50) DEFAULT NULL, `receiver_phone` varchar(20) DEFAULT NULL, `receiver_address` varchar(200) DEFAULT NULL, `remark` varchar(500) DEFAULT NULL COMMENT '定制需求备注', `create_time` datetime NOT NULL, `pay_time` datetime DEFAULT NULL, `finish_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

2.2 SpringBoot项目分层与包结构

后端项目我采用了最标准的五层结构,这不只是为了好看,更重要的是让答辩时的代码讲解有逻辑可循。

com.cake ├── controller // 入参接收、参数校验、调用service ├── service // 业务逻辑层,接口 + impl ├── mapper // MyBatis-Plus的Mapper接口,继承BaseMapper ├── entity // 数据库实体类,与表字段一一对应 ├── common // 统一返回结果Result、异常处理、常量 ├── config // 配置类:跨域、MyBatis-Plus分页插件、静态资源映射 ├── util // 工具类:JWT工具、订单编号生成 └── CakeApplication.java

每个类的职责边界务必清晰。Controller里不要写业务逻辑,Service里不要直接拼SQL。很多同学为了省事,把业务代码直接堆在Controller里,答辩时老师一旦深追某个方法,讲着讲着自己就乱了。分层清晰还有一个好处:论文里的"系统设计"章节可以直接复用这些描述,省得再编一套说法。

统一返回结果类是我特别想强调的一点。

前后端分离的项目里,后端接口返回的JSON格式必须统一,否则前端处理数据时得为每个接口单独写判断逻辑,极其痛苦。我定义了一个泛型Result类:

@Data public class Result<T> { private Integer code; // 200成功,500失败,401未登录 private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMsg("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String msg) { Result<T> result = new Result<>(); result.setCode(500); result.setMsg(msg); return result; } }

这样前端Axios的响应拦截器里只需要判断一次code === 200,就统一处理成功,否则弹出msg即可。

2.3 Vue项目结构与页面拆解

前端项目基于Vue 2 + Vue Router + Vuex + Element UI + Axios + ECharts。项目结构如下:

src ├── api // 接口请求封装,按模块拆分 │ ├── user.js │ ├── cake.js │ ├── order.js │ └── stats.js ├── assets // 静态资源 ├── components // 公共组件(图片上传、分页等) ├── router // 路由配置 ├── store // Vuex状态管理 ├── views // 页面组件 │ ├── front // 用户端页面 │ │ ├── home // 首页/商品列表 │ │ ├── cart // 购物车 │ │ ├── order // 订单列表/下单页 │ │ ├── login // 登录注册 │ │ └── profile // 个人中心 │ └── admin // 管理端页面 │ ├── Dashboard.vue // 数据看板 │ ├── CakeManage.vue │ ├── CategoryManage.vue │ ├── OrderManage.vue │ ├── MessageManage.vue │ └── BannerManage.vue ├── utils │ └── request.js // Axios封装,含请求/响应拦截器 ├── App.vue └── main.js

路由配置上做了一个很实用的设计:根据用户角色动态生成路由。普通用户登录后只能看到用户端页面,管理员登录后才能看到管理端菜单。实现方式是在路由配置里加meta信息标记哪些路由需要admin角色,然后在Vue Router的beforeEach守卫里判断当前用户角色是否匹配。这套机制也是答辩时可以重点讲的亮点之一。

3. 核心功能实现思路与关键代码解读

功能实现是正文的重头戏。我挑几个最有代表性的模块展开讲,包含关键代码、设计思路和当时踩过的坑。这些代码片段都是可以直接复用的,你拿到手改改包名就能跑。

3.1 登录认证:JWT + 拦截器 + 前端路由守卫

登录是整个系统的第一道关卡。密码存储用的是BCryptPasswordEncoder,这是Spring Security里的加密工具,同样明文密码每次加密结果都不同,数据库里即使被脱库也无法反推原始密码。我单独引了spring-security-crypto依赖,并没有引入整套Spring Security,因为引入整套之后默认的过滤链、拦截规则反而会给纯接口项目添乱。

登录流程:

  1. 前端把用户名和密码发送到/api/user/login
  2. 后端用BCrypt匹配密码,匹配成功则用JWT工具生成token返回。
  3. 前端把token存到localStorage,并在后续每次请求的请求头里带上Authorization: Bearer ${token}
  4. 后端定义一个WebMvcConfigurer拦截器,拦截所有/api/**请求(放行登录、注册、商品列表等白名单接口),从请求头解析token,解析成功才放行,失败返回401。

JWT工具类的核心代码:

public class JwtUtil { private static final String SECRET = "cake-secret-key"; private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000; // 7天 public static String generateToken(Integer userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }

这里有个经验:signWith方法在jjwt 0.9.x和0.11.x版本之间API差异很大,0.11之后建议用Keys.hmacShaKeyFor()传入字节数组。如果你在引入依赖时不小心用了高版本却照着老教程写代码,一启动就会报错。我建议直接锁版本,用io.jsonwebtoken:jjwt:0.9.1,踩坑最少。

拦截器里校验token时,我从Claims里取出userId和role,存入ThreadLocal或HttpServletRequest的attribute里,后续Service层需要当前登录用户时直接从request中取,避免了每次查询数据库带用户ID的麻烦。

前端配合的后端返回结构是:

{ "code": 200, "msg": "操作成功", "data": { "token": "eyJhbGciOiJIUzI1NiJ9...", "userInfo": { "id": 1, "username": "admin", "nickname": "店长", "role": 1 } } }

前端路由守卫的写法:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() return } if (!token) { next('/login') return } if (to.meta.requiresAdmin) { const role = store.state.user.role if (role !== 1) { next('/') return } } next() })

这套流程麻雀虽小五脏俱全,Session、Cookie、Token三种方案在答辩时的对比其实是高频考点,建议你把"为什么用JWT而不是Session"这个问题提前准备好答案。核心就两点:前后端分离跨域场景下Cookie处理麻烦;JWT无状态,服务端不存会话信息,天然适配水平扩展。

3.2 商品管理与图片上传:本地存储还是OSS

商品管理模块涉及两个有意思的点:图片上传和分页查询。

图片上传,我选择了上传到本地服务器指定目录,然后通过静态资源映射对外访问的方案,而不是阿里云OSS或其他云存储。原因很现实:国内云服务商的对象存储虽然有几元钱的免费额度,但需要实名认证,有的还需要绑定支付方式;而毕业设计的演示环境是本地或者一台学生服务器,上传到本地最简单、不依赖外部服务。实现方式:

  1. 后端接收MultipartFile文件。
  2. 校验文件大小和类型(只允许jpg/png,大小限制5MB以内)。
  3. 生成唯一文件名,规则是System.currentTimeMillis() + 随机数 + 原始后缀
  4. 保存到D:/cake/upload/目录。
  5. 返回可访问的URL路径,比如/images/xxx.jpg

为了让前端能访问到上传的图片,配置一个WebMvcConfigurer:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceHandler("file:D:/cake/upload/"); }

这个配置有个坑:本地和Linux服务器上绝对路径不同,上传目录最好放到配置文件中,用@Value注解读取,打包部署时改配置文件即可,不用改代码重新编译。

分页查询用的是MyBatis-Plus的分页插件。配置类里注入MybatisPlusInterceptor

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }

配置之后,Mapper接口只需要这么写:

public interface CakeMapper extends BaseMapper<Cake> { // 不需要写任何方法,BaseMapper里已有 }

Service层调用:

Page<Cake> page = new Page<>(current, size); LambdaQueryWrapper<Cake> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Cake::getStatus, 1); if (StringUtils.isNotBlank(categoryId)) { wrapper.eq(Cake::getCategoryId, categoryId); } wrapper.orderByDesc(Cake::getCreateTime); Page<Cake> result = cakeMapper.selectPage(page, wrapper);

使用LambdaQueryWrapper而不是QueryWrapper的原因,是它有编译期类型检查,表字段改名后写错的地方会直接编译报错而不是运行期报SQL异常。这个习惯建议从一开始就养成。

3.3 订单流程:事务、状态控制和定时任务

订单模块是整个系统里逻辑最复杂的部分,也是最值得在论文里详细写的地方。

创建订单的流程:

  1. 前端把购物车选中的商品列表、收货人信息、备注一起传给后端。
  2. 后端接收后,先校验每个商品的状态和库存。
  3. 计算总金额——注意,总金额必须在后端算,前端传来的totalAmount不能直接信任。
  4. 生成订单主表记录和订单详情表记录。
  5. 扣减库存。
  6. 从购物车中移除已下单的商品。
  7. 返回订单编号。

这中间涉及多次数据修改操作,必须加事务。在Service方法上加@Transactional(rollbackFor = Exception.class)注解,任何一步抛出异常,前面的操作全部回滚。如果没有事务,可能会出现"订单创建成功但库存没扣"这种致命bug。

库存扣减的SQL,用MyBatis-Plus写的话要注意一点:不要先查询库存再判断再更新,而应该用条件更新的方式,保证原子性:

boolean success = cakeService.update( new LambdaUpdateWrapper<Cake>() .eq(Cake::getId, cakeId) .gt(Cake::getStock, quantity) // 库存必须大于购买数量 .setSql("stock = stock - " + quantity) ); if (!success) { throw new RuntimeException("库存不足"); }

setSql("stock = stock - " + quantity)这种方式是直接在SQL层让数据库执行原子减操作,而不是先查出来减完再update,避免了并发下的超卖问题。考虑到毕设数据量不大,并发超卖未必会实际发生,但答辩时把这个设计讲出来,绝对是加分项。

订单状态转换我用一个简单的状态图就能讲清楚:

  • 0 待支付 → 用户点击支付(模拟)→ 1 待发货
  • 1 待发货 → 管理员点击发货 → 2 配送中/待自提
  • 2 配送中/待自提 → 用户确认收货或管理员标记完成 → 3 已完成
  • 0/1 → 用户取消 → 4 已取消

状态流转的校验逻辑放在Service层,每一层只允许从规定的上一个状态迁移到下一个状态,防止前端通过恶意请求乱跳状态。

超时未支付自动取消用的是Spring Boot自带的@Scheduled定时任务:

@Scheduled(fixedRate = 60 * 1000) // 每分钟执行一次 public void cancelExpiredOrders() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(30); // 查出所有创建时间早于deadline且状态为0的订单 // 将状态改为4已取消,同时恢复库存 }

@EnableScheduling别忘了加在启动类上。这个功能建议做一个,因为它在论文里能单开一个小节讲,属于非常典型的"非功能需求设计"。

3.4 数据看板:SQL聚合与ECharts可视化

管理端首页的数据看板是整个系统最强的视觉组件。统计内容包括:今日订单数、今日销售额、总用户数、总商品数、近7天销售趋势折线图、热销商品Top5柱状图。

最关键的是一个聚合查询。由于MyBatis-Plus的BaseMapper不提供复杂的聚合查询,我直接在Mapper里自定义SQL:

@Mapper public interface StatsMapper { @Select("SELECT DATE(create_time) as date, COUNT(*) as orderCount, " + "SUM(total_amount) as totalAmount FROM orders " + "WHERE create_time >= #{startTime} AND status != 4 " + "GROUP BY DATE(create_time) ORDER BY date") List<SalesTrendVO> selectSalesTrend(@Param("startTime") LocalDateTime startTime); }

热销商品则要关联订单详情表:

@Select("SELECT cake_name as name, SUM(quantity) as total FROM order_item " + "GROUP BY cake_name ORDER BY total DESC LIMIT 5") List<HotCakeVO> selectHotCakes();

前端用ECharts折线图和柱状图渲染数据,视觉效果好,代码量也不大。这块建议单独留一个接口把统计数据聚合成一个大的Map返回给前端,减少请求次数。

4. 开发中踩过的坑:每条都是真实翻车记录

做毕设最大的痛苦不在"写不出代码",而在"代码明明对着教程写的,怎么就是跑不通"。我在这里把踩过的坑集中盘点一下,这部分内容建议收藏,遇到同类问题时直接照着排查。

4.1 跨域问题:前后端联调的第一道坎

前端跑在localhost:8080,后端跑在localhost:9090(SpringBoot默认8080,我改成了9090避免冲突),前端发请求时浏览器会报No 'Access-Control-Allow-Origin' header is present

这个问题的本质是浏览器的同源策略:协议、域名、端口三者任一不同,浏览器就会拦截跨域请求。解决方案也不难,在SpringBoot里配置一个全局跨域过滤器:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意allowedOriginPatterns是SpringBoot 2.4+之后的写法,老版本用的是allowedOrigins。两者区别在于:allowedOrigins("*")配合allowCredentials(true)会报错,必须用allowedOriginPatterns。我因为网上教程版本混杂,在这个点折腾了近两个小时。

另外,前端Axios发起跨域请求时,如果请求头带了Authorization,后端allowedHeaders("*")才能放行,否则预检请求(OPTIONS请求)都过不去,更别提实际业务请求了。

4.2 时间格式化问题:前端显示UTC时间字符串

这是MyBatis-Plus搭配Jackson时非常经典的一个坑。后端返回LocalDateTime,默认序列化后前端拿到的是一个类似2024-05-20T12:30:00.000+00:00的字符串,在页面上直接显示会带一个恼人的T,而且时区还是UTC,跟我们东八区差8个小时。

解决方案是在application.yml里统一配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

遗憾的是,这个配置对LocalDateTime类型并不生效。正确做法是在实体类的时间字段上加@JsonFormat,或者全局配置一个Jackson的自定义序列化器。更省事的方案是直接引入jackson-datatype-jsr310并配置:

@Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; }

配置完所有接口返回的时间格式就统一变成了2024-05-20 12:30:00,清爽多了。

4.3 实体类字段名为驼峰,数据库字段名为下划线

MyBatis-Plus默认开启了map-underscore-to-camel-casecake_name会自动映射到cakeName,大部分情况下没问题。但如果你有特殊命名的字段,比如数据库字段叫user_id,实体类属性叫userId,这没问题;如果实体类属性叫userID,映射就乱了。建议从一开始就让实体类属性严格使用小写驼峰命名,避免这种边缘问题。

还有个大坑:如果数据表里某个字段是MySQL的保留字,比如orderdescrank,那么用MyBatis-Plus生成SQL时会直接拼接字段名导致语法错误。我在订单表上就吃过这个亏,表名用了orders才规避。建表时给表名、字段名都加反引号确实能解决,但更稳妥的做法是尽量避免保留字作为表名和字段名。

4.4 Vue项目刷新页面404

当时做前端路由时用了history模式,本地开发一切正常,但打包部署到服务器后,一刷新/admin/dashboard这个路径就报404。原因很简单:history模式依赖服务器的路由重写,不管前端路由是啥,服务器都应该返回index.html。本地开发时是Vue DevServer自己处理的,所以没问题;部署到Nginx后,Nginx发现没有/admin/dashboard这个文件,就返回404了。

Nginx配置解决:

location / { try_files $uri $uri/ /index.html; }

这行配置的意思是:先尝试找真实文件,找不到就一律返回index.html,前端路由拿到页面后再根据URL渲染对应组件。这个坑几乎每个做前后端分离项目的人都会踩到,提前写在这里帮你避免。

顺带一提,如果你在毕业设计答辩现场用的演示环境是本地运行的,这个坑不一定会暴露,但把Nginx部署流程走一遍并把这些配置文件写进论文附录里,会显得你的工程能力更扎实。

4.5 Element UI表格里操作列的"作用域插槽"写法

Element UI的表格自定义列要用作用域插槽,这是Vue 2特有的写法,和Vue 3里的v-slot语法有区别。当时在这个地方卡了很久,因为教程里用的Element Plus和Vue 3的写法跟我用的Vue 2 + Element UI不一样。简单记一下:

<el-table-column label="操作" width="200"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="handleEdit(scope.row)">编辑</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column>

在Vue 2的Element UI里,scope.row就是当前行的数据对象,可以直接取到scope.row.id等字段。注意slot-scope是Vue 2.6及之前的旧语法,在Vue 2.6+实际也能用,但如果装了@vue/composition-api或升级到Vue 3,就要换成#default="scope"了。做毕设建议直接确认版本:Vue 2配Element UI,Vue 3配Element Plus,别混着来。

5. 打包部署、论文撰写与演示准备:最后一公里的细节

功能和代码都搞定之后,还有三件容易被忽略但决定成败的事:打包部署流程是否顺畅、论文结构能否讲清楚、演示环节有没有提前排练。这里分别展开说。

5.1 后端打包与前端构建

后端打包非常简单,在pom.xml里配置了spring-boot-maven-plugin之后,执行:

mvn clean package -DskipTests

target/目录下生成一个可执行jar包,运行:

java -jar cake-system.jar --server.port=9090

一个很小的坑:如果直接在application.yml里配置了数据库密码,打包后配置文件是拿不到源码了,但这在毕业设计里无伤大雅。如果想让自己的工程水平显得更高一点,可以用application-prod.ymlapplication-dev.yml做环境区分,打包时通过--spring.profiles.active=prod指定。这个操作在论文里也能作为"系统部署文档"的一部分来写。

前端构建:

npm run build

构建完生成dist/目录,里面是纯静态文件。我把它直接放到了Nginx的html目录下管理。

5.2 演示数据的准备

这一点被无数人低估。**演示前准备好一套像样的数据,比多写两个功能还重要。**说白了,答辩老师看到你打开系统,首页是"测试蛋糕1""测试蛋糕2"、订单列表全是"张三""李四"的测试数据,第一印象就打了折扣。

我当时花了大概一小时,精心造了20多个蛋糕商品的数据,分了5个分类,每个商品配上真实风格的图片(从免费图库下载),价格、口味、销量、库存都设置得合理,比如经典的"水果缤纷"定价138元、月销260;"黑森林"定价168元、月销320。订单数据也造了一周的,这样数据看板打开后,折线图、柱状图直接就有漂亮的曲线和对比效果,演示时非常有说服力。

数据库初始化脚本建议写一个init.sql,包含建表语句和INSERT数据,在论文附录里完整给出。这会让老师觉得你的系统可以直接跑起来,而不是一个只有空壳的表结构。

5.3 论文结构建议与答辩讲解主线

如果你的学校要求写设计说明书或毕业论文,下面这套结构非常通用,可以直接参考调整:

  • 第一章 绪论:背景意义(蛋糕店管理痛点)、国内外研究现状、系统目标、技术选型
  • 第二章 相关技术介绍:SpringBoot、MyBatis-Plus、Vue、MySQL、JWT、ECharts,每样一页左右
  • 第三章 需求分析:可行性分析(经济、技术、操作)、功能性需求(用户端、管理端分别用用例图)、非功能需求(性能、安全性、易用性)
  • 第四章 系统设计:总体架构图、功能模块设计、数据库设计(ER图 + 每张表的字段说明)、接口设计
  • 第五章 系统实现:核心功能截图 + 关键代码 + 实现思路,按前面说的登录、商品、订单、统计四个模块展开
  • 第六章 系统测试:功能测试用例表、异常场景测试、兼容性测试
  • 第七章 总结与展望:总结完成的工作,展望后续方向

整个系统跑下来加上数据库表初始化导入,大概30分钟就能把全部功能过一遍。建议演示前自己完整走两遍流程,重点练好几个场景:注册登录→浏览商品→加入购物车→下单→后台发货→数据看板变化。

最后碎碎念

做这个系统前前后后大概花了一个半月,大部分时间其实不是花在写代码上,而是花在"想清楚再动手"和"踩坑后排查"上。数据库表设计提前想明白了,后面没返过工;跨域和时间格式化这种问题,第一次遇到觉得天都要塌了,查明白之后发现也就几分钟的事。如果你现在正在为毕业设计发愁,相信我,按"选题→数据库→后端接口→前端页面→部署→文档"的顺序一步步来,每天固定推进一点,这套SpringBoot + Vue的蛋糕店管理系统真的不难。遇到跑不通的bug不要硬扛,把控制台日志认认真真读一遍,再按我上面列的方向去排查,大部分问题半小时之内都能解决。

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

ECC内存纠错与MBIST测试:从uncorr. ecc错误计数到硬件排查指南

提到“ECC”这仨字母&#xff0c;干服务器、存储或嵌入式的人大概率会想到内存纠错码&#xff08;Error Correction Code&#xff09;。最近遇到一台设备日志里冒出“uncorr. ecc 显示2”&#xff0c;旁边还跟了一条MBIST ECC相关的告警&#xff0c;排查了一圈才把事情理顺。这…

作者头像 李华
网站建设 2026/9/9 9:33:08

Python+微信小程序打造考研资料共享平台:架构设计与实战避坑指南

去年帮朋友找考研专业课真题&#xff0c;翻了三个旧群、点了十几个失效网盘链接之后&#xff0c;我决定用 Python 做后端、微信小程序做前端&#xff0c;自己搞一个考研资料共享平台。这年头考研资料不是没有&#xff0c;而是碎得离谱&#xff1a;网盘链接说挂就挂&#xff0c;…

作者头像 李华
网站建设 2026/9/9 9:32:09

光伏并网仿真模型:扰动观察法MPPT与储能协调控制

做了大半年光伏并网仿真&#xff0c;这个模型算是把之前零散踩过的坑一次性填平了。项目标题里提到的“扰动观察法MPPT储能模块”其实是一个很典型的组合方案&#xff0c;但真正跑通、跑稳、跑出平滑曲线&#xff0c;中间涉及到的东西远不止一个MPPT函数那么简单。这篇就把我实…

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

五颗芯片如何构建真实可用的边缘智能全栈系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 9:31:26

文件同步与版本控制的冲突测试实战指南

去年我接到一个文件同步类App的测试任务&#xff0c;中间产品经理丢过来一个很魔幻的Bug单&#xff1a;小米平板删除文件之后&#xff0c;存储空间居然没有变化。我一开始以为是系统缓存刷新慢&#xff0c;结果一查牵扯出一整条链路的问题——文件存储、版本控制、冲突处理&…

作者头像 李华
网站建设 2026/9/9 9:31:06

Opencode本地AI开发工具链:离线CLI、编辑器集成与环境适配指南

1. 项目概述&#xff1a;Opencode 是什么&#xff0c;它解决的到底是什么问题&#xff1f; Opencode 不是一个传统意义上的开源项目、框架或编程语言&#xff0c;而是一个正在快速演化的 AI 原生开发工具链品牌 ——更准确地说&#xff0c;它是面向开发者、尤其是前端与全栈工…

作者头像 李华