尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

基于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张核心表严格遵循第三范式同时做了适度的冗余方便查询。下面逐张开讲。用户表userid主键自增username用户名唯一索引passwordBCrypt加密后的密码nickname昵称avatar头像地址phone手机号role角色0表示普通用户1表示管理员status状态1正常0禁用create_time / update_time创建时间和更新时间角色字段我放在了用户表里而不是单独建角色表原因很简单系统只有两种角色没有细粒度的权限模型需求单独建表反而增加关联查询的复杂度。只要在登录接口里把role返回给前端前端根据role控制菜单和路由显示即可。商品分类表categoryidname分类名称sort排序字段iconstatus商品表cakeidcategory_id外键关联分类表namedescriptionpriceDECIMAL(10,2)imagetaste口味标签size规格如6寸/8寸/10寸stock库存sales销量冗余字段status上下架状态create_time购物车表cartiduser_idcake_idquantitychecked是否选中create_time一个用户对应多条购物车记录用user_id cake_id作为唯一索引加购时如果已存在则累加数量。订单表ordersidorder_no订单编号唯一用时间戳随机数生成user_idtotal_amount总金额status0待支付1待发货2配送中/待自提3已完成4已取消receiver_name收货人receiver_phonereceiver_addressremark定制备注create_timepay_timedelivery_timefinish_time这里把收货信息冗余在订单表里而不是单独建地址表是考虑到蛋糕店的实际场景——用户下单时临时填写收货信息即可不需要维护一个常用地址簿。做毕业设计讲究的是够用就好不要被复杂需求绑架。订单详情表order_itemidorder_id外键关联订单表cake_idcake_name快照冗余cake_imageprice下单时的单价quantitysubtotal订单详情表非常重要它存的是一个历史快照。假设商品改价了或者商品被删除了订单里依然能查到用户购买时的名称、单价和图片。这种冗余设计是从电商系统里学来的成熟经验答辩时如果被问到为什么冗余这是个标准的加分回答。留言反馈表messageiduser_idcontent留言内容reply管理员回复create_time轮播图表banneridimageurlsortstatus关于字段类型有几个地方容易忽视我单独列出来提醒金额一律用DECIMAL(10,2)绝对不要用float或double否则算总价时会出现1.999999这种经典精度问题。时间字段统一用datetimeJava实体类对应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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 ResultT { private Integer code; // 200成功500失败401未登录 private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT 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因为引入整套之后默认的过滤链、拦截规则反而会给纯接口项目添乱。登录流程前端把用户名和密码发送到/api/user/login。后端用BCrypt匹配密码匹配成功则用JWT工具生成token返回。前端把token存到localStorage并在后续每次请求的请求头里带上Authorization: Bearer ${token}。后端定义一个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或其他云存储。原因很现实国内云服务商的对象存储虽然有几元钱的免费额度但需要实名认证有的还需要绑定支付方式而毕业设计的演示环境是本地或者一台学生服务器上传到本地最简单、不依赖外部服务。实现方式后端接收MultipartFile文件。校验文件大小和类型只允许jpg/png大小限制5MB以内。生成唯一文件名规则是System.currentTimeMillis() 随机数 原始后缀。保存到D:/cake/upload/目录。返回可访问的URL路径比如/images/xxx.jpg。为了让前端能访问到上传的图片配置一个WebMvcConfigurerOverride public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceHandler(file:D:/cake/upload/); }这个配置有个坑本地和Linux服务器上绝对路径不同上传目录最好放到配置文件中用Value注解读取打包部署时改配置文件即可不用改代码重新编译。分页查询用的是MyBatis-Plus的分页插件。配置类里注入MybatisPlusInterceptorBean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }配置之后Mapper接口只需要这么写public interface CakeMapper extends BaseMapperCake { // 不需要写任何方法BaseMapper里已有 }Service层调用PageCake page new Page(current, size); LambdaQueryWrapperCake wrapper new LambdaQueryWrapper(); wrapper.eq(Cake::getStatus, 1); if (StringUtils.isNotBlank(categoryId)) { wrapper.eq(Cake::getCategoryId, categoryId); } wrapper.orderByDesc(Cake::getCreateTime); PageCake result cakeMapper.selectPage(page, wrapper);使用LambdaQueryWrapper而不是QueryWrapper的原因是它有编译期类型检查表字段改名后写错的地方会直接编译报错而不是运行期报SQL异常。这个习惯建议从一开始就养成。3.3 订单流程事务、状态控制和定时任务订单模块是整个系统里逻辑最复杂的部分也是最值得在论文里详细写的地方。创建订单的流程前端把购物车选中的商品列表、收货人信息、备注一起传给后端。后端接收后先校验每个商品的状态和库存。计算总金额——注意总金额必须在后端算前端传来的totalAmount不能直接信任。生成订单主表记录和订单详情表记录。扣减库存。从购物车中移除已下单的商品。返回订单编号。这中间涉及多次数据修改操作必须加事务。在Service方法上加Transactional(rollbackFor Exception.class)注解任何一步抛出异常前面的操作全部回滚。如果没有事务可能会出现订单创建成功但库存没扣这种致命bug。库存扣减的SQL用MyBatis-Plus写的话要注意一点不要先查询库存再判断再更新而应该用条件更新的方式保证原子性boolean success cakeService.update( new LambdaUpdateWrapperCake() .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里自定义SQLMapper 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) ListSalesTrendVO 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) ListHotCakeVO selectHotCakes();前端用ECharts折线图和柱状图渲染数据视觉效果好代码量也不大。这块建议单独留一个接口把统计数据聚合成一个大的Map返回给前端减少请求次数。4. 开发中踩过的坑每条都是真实翻车记录做毕设最大的痛苦不在写不出代码而在代码明明对着教程写的怎么就是跑不通。我在这里把踩过的坑集中盘点一下这部分内容建议收藏遇到同类问题时直接照着排查。4.1 跨域问题前后端联调的第一道坎前端跑在localhost:8080后端跑在localhost:9090SpringBoot默认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.00000:00的字符串在页面上直接显示会带一个恼人的T而且时区还是UTC跟我们东八区差8个小时。解决方案是在application.yml里统一配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8遗憾的是这个配置对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的保留字比如order、desc、rank那么用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操作 width200 template slot-scopescope el-button typeprimary sizemini clickhandleEdit(scope.row)编辑/el-button el-button typedanger sizemini clickhandleDelete(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就要换成#defaultscope了。做毕设建议直接确认版本Vue 2配Element UIVue 3配Element Plus别混着来。5. 打包部署、论文撰写与演示准备最后一公里的细节功能和代码都搞定之后还有三件容易被忽略但决定成败的事打包部署流程是否顺畅、论文结构能否讲清楚、演示环节有没有提前排练。这里分别展开说。5.1 后端打包与前端构建后端打包非常简单在pom.xml里配置了spring-boot-maven-plugin之后执行mvn clean package -DskipTests在target/目录下生成一个可执行jar包运行java -jar cake-system.jar --server.port9090一个很小的坑如果直接在application.yml里配置了数据库密码打包后配置文件是拿不到源码了但这在毕业设计里无伤大雅。如果想让自己的工程水平显得更高一点可以用application-prod.yml和application-dev.yml做环境区分打包时通过--spring.profiles.activeprod指定。这个操作在论文里也能作为系统部署文档的一部分来写。前端构建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不要硬扛把控制台日志认认真真读一遍再按我上面列的方向去排查大部分问题半小时之内都能解决。
返回列表