
1. 这个平台解决的核心问题与选题价值前阵子帮一位准备毕业设计的学弟梳理方案他最初的选题是婚纱摄影预约系统我一看就知道这个题目有问题——太宽泛了。市面上这类管理系统遍地都是答辩评委一眼就能看出工作量不足。后来我们把它收敛到民族婚纱预定这个细分方向整个项目的差异化立刻出来了。民族婚纱预定系统管理平台从名字就能看出它不是一个简单的CRUD增删改查。它把婚纱礼服租赁/预定这种典型O2O业务的完整管理链路搬到了Web端用户可以在线浏览民族风格的婚纱苗绣、彝绣、藏式、汉服改良等查看礼服详情、档期、价格提交预定申请管理员在后台维护服装库存、审核预定订单、管理用户信息、查看经营统计。如果用于课设这是一个能拿高分的工作量如果用于毕设它的问题是结构完整、有深度、可扩展。为什么我推荐这个题目而不是烂大街的图书管理系统或学生选课系统原因有三点第一业务逻辑有复杂度但不至于失控。预定系统涉及服装SKU管理、时间档期冲突、订单状态流转、用户权限分层这些业务规则比图书增删改查复杂得多但又不像电商秒杀那样需要处理高并发非常适合在毕设周期内完成。第二技术栈覆盖面全。前端Vue负责交互体验后端SpringBoot提供REST APIMySQL存持久化数据还要处理图片上传、日期选择、状态流转这些实际项目常见的问题。一套下来面试官常见的技术点基本都涉及了。第三展示效果好。民族风格的主题在视觉上天然有优势无论是前端界面的色彩搭配、服装展示图还是整个系统的功能气质都容易在答辩时给评委留下印象。相比千篇一律的白底表格管理系统这算是天然加分项。如果你正在纠结毕设/课设选题把这个方向作为参考是非常合适的。接下来我把这个系统从技术选型、数据库设计到前后端实现的关键环节全部拆开讲包括我在实际开发中踩过的坑。2. 技术选型与项目脚手架搭建的取舍思路2.1 后端框架和版本选择的依据SpringBoot Java MySQL这个组合在当前的毕业设计生态里属于黄金组合。它不需要像SSHStrutsSpringHibernate那样写大量XML配置也不需要像Spring Cloud微服务体系那样维护复杂的注册中心正处在有技术含量但门槛适中的最佳区间。实际搭建时我建议使用下面的版本组合组件版本说明JDK1.8 或 11大部分学校机房和老项目兼容性最好SpringBoot2.7.x不要追最新的3.x部分依赖兼容性问题多MyBatis-Plus3.5.x单表CRUD不用写SQL大幅提高开发效率MySQL5.7 或 8.05.7经典稳定8.0注意驱动和时区配置Vue2.6.x Element-UI生态成熟资料多毕设足够用Node14~16对应Vue2的构建工具链我特别强调SpringBoot版本的问题这是最近很多人踩坑的地方。现在SpringBoot已经出到3.x了但它基于Jakarta EE规范很多老的包名、配置类都变了网上搜到的很多教程都是基于2.x时代的照着做容易报错。如果找不到合适的JDK版本可以用java -version先确认环境再决定SpringBoot版本。我这里归纳一个快速对照规则JDK 8对应SpringBoot 2.xJDK 17对应SpringBoot 3.x。毕设老老实实用JDK 8 SpringBoot 2.7.x是最省心的。2.2 前端技术栈Vue2 Element-UI还是Vue3 Element-Plus前端选型是一个容易被忽视的决策点。标题里明确了Vue但Vue本身有Vue2和Vue3两大版本对应的UI库也不同。我在实际教学中看到很多学生无脑选Vue3 Vite Element-Plus结果遇到浏览器兼容问题、组件文档看不懂白白浪费大量时间。如果你的目标是在一两个月内拿出一个能演示、能答辩的完整系统我更推荐Vue2 Vue Router Vuex Axios Element-UI这套组合。原因很实在Element-UI的组件文档是中文的示例代码直接复制粘贴就能跑Vue2的生态经过了多年沉淀任何报错都能在搜索引擎找到答案网上关于Vue2 SpringBoot前后端分离的教程数量是Vue3的好几倍大多数高校的软件工程课程老师自己可能都还是用Vue2举例。当然如果你本身已经熟悉Vue3的组合式APIComposition API用Vue3也完全可行。毕竟它现在是趋势招聘市场上Vue3的岗位也更多。前提是你已经具备独立排查问题的能力而不是边学边写。2.3 前后端分离的目录结构与IDEA配置要点前后端分离是本项目的默认架构前后端各自维护自己的工程目录。我习惯这么组织national-wedding-system/ ├── backend/ # SpringBoot后端工程 │ ├── src/main/java/com/wedding/ │ │ ├── config/ # 配置类跨域、拦截器、文件上传等 │ │ ├── controller/ # 控制层 │ │ ├── service/ # 业务层接口 实现 │ │ ├── mapper/ # MyBatis-Plus的数据访问层 │ │ ├── entity/ # 数据库实体类 │ │ ├── common/ # 通用返回结果、异常处理 │ │ └── util/ # 工具类JWT、日期处理等 │ └── src/main/resources/ │ ├── application.yml # 主配置 │ └── mapper/ # XML文件复杂SQL时使用 ├── frontend/ # Vue前端工程 │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── router/ # 路由配置 │ │ ├── store/ # Vuex状态管理 │ │ ├── views/ # 页面组件 │ │ ├── components/ # 公共组件 │ │ └── utils/ # axios实例、工具方法 │ └── package.json └── sql/ # 数据库初始化脚本后端用IDEA创建SpringBoot项目时建议直接用Spring Initializrstart.spring.io生成勾选依赖Spring Web、MySQL Driver、MyBatis-Plus如果用的阿里镜像不支持搜索就先建普通项目再手动加依赖。前端用命令npm install -g vue/cli装好脚手架后执行vue create frontend创建项目。一个容易卡住新手的点是IDEA中target/generated-sources的注解处理问题。如果MyBatis-Plus的TableField、TableName注解不生效通常需要在IDEA的Settings里搜索Annotation Processors勾选Enable annotation processing。3. 数据库建模婚纱SKU、档期冲突与订单状态流转数据库设计是这类管理系统最见功力的地方。很多课程设计的数据库就是简单的用户表婚纱表订单表看似能用实际逻辑一跑就漏洞百出。我在这个项目里把核心表拆得稍微细一些既能保证数据一致性又能在答辩时展示数据库设计能力。3.1 核心表结构设计与字段说明整个系统我按模块拆分为以下核心表表名用途关键字段sys_user系统用户管理员/普通用户username, password, role, phone, emailuser_detail用户扩展信息user_id, real_name, id_card, addresswedding_dress婚纱礼服表name, style, color, size, price, deposit, cover_image, statusdress_inventory婚纱库存/尺码表dress_id, size, total_count, available_countdress_booking预定订单表order_no, user_id, dress_id, booking_date, status, contact_name, contact_phonedress_comment用户评价表order_id, user_id, content, ratingsys_notice公告表title, content, create_time这里有一个和普通商品系统的关键区别婚纱不是标准商品它带尺码、带风格、有档期限制。比如一套苗绣嫁衣可能只有M码和L码各一套用户在5月20日预定了那5月20日当天这套衣服就不能再被别人预定。所以dress_inventory表和dress_booking表需要配合工作库存不仅要管总量还要管某一天是否有档期。3.2 预定冲突检测的SQL与业务实现预定冲突检测是这个项目业务逻辑中最容易出错也最能体现水平的部分。我先说需求场景用户A选择了某套婚纱预定日期为6月1日用户B也想订这套婚纱日期同样是6月1日系统应该提示该婚纱在所选日期已被预定或者库存不足。实现思路有两种方案一预定即扣减库存在用户提交订单时检查available_count如果大于0则扣减。但这套逻辑在未支付订单占用档期的场景下比较难处理比如用户提交了订单但没付款库存已经被扣了别的用户就看不到了。毕设答辩时如果被问到如何防止恶意占用库存这个方案容易暴露问题。方案二基于日期维度做冲突校验这是我在项目里实际采用的方式。dress_booking表中记录每次预定的booking_date预定使用的日期提交预定请求时执行下面这条核心SQLSELECT COUNT(*) FROM dress_booking WHERE dress_id #{dressId} AND booking_date #{bookingDate} AND status IN (PENDING, APPROVED)如果查询结果大于等于该尺码的库存数量则不再接受预定。注意这里的状态过滤很重要已取消的订单CANCELLED不占用档期所以不能计入。这个方案的优点是把库存抽象成档期的概念更贴合婚纱租赁业务而且实现简单答辩解释起来也清楚。缺点是并发极端情况下可能产生超卖但对毕设场景完全够用。如果想让方案更严谨可以在这里加SELECT ... FOR UPDATE锁行或者用乐观锁版本号方式。我在项目里用了乐观锁的改进版Transactional public synchronized Result createBooking(BookingDTO dto) { // 1. 查询该婚纱在指定日期的已有预定数 LambdaQueryWrapperDressBooking wrapper new LambdaQueryWrapper(); wrapper.eq(DressBooking::getDressId, dto.getDressId()) .eq(DressBooking::getBookingDate, dto.getBookingDate()) .in(DressBooking::getStatus, PENDING, APPROVED); Long count dressBookingMapper.selectCount(wrapper); // 2. 获取该婚纱对应尺码的库存总数 Integer total dressInventoryMapper.getCountByDressIdAndSize( dto.getDressId(), dto.getSize()); // 3. 比较 if (count total) { return Result.error(该婚纱在所选日期已无档期); } // 4. 生成订单号保存订单 String orderNo BK System.currentTimeMillis() RandomUtil.randomNumbers(4); // ... 省略 }synchronized关键字在这个场景下能保证单个JVM实例内的方法串行执行对于单机部署的课设系统已经足够稳妥。3.3 民族服饰属性建模的细节考量既然标题强调民族婚纱而不能把它做成一个通用的服装租赁系统数据库字段设计上就必须体现民族特色。我在wedding_dress表中给每件婚纱加了几个特定属性style风格苗绣嫁衣、彝绣婚服、藏族氆氇礼服、满族旗袍改良、汉服凤冠霞帔等decoration工艺装饰手工刺绣、银饰镶嵌、盘扣、流苏、珠片等color主色调大红、藏青、墨绿、素白等region民族/地区属性苗族、彝族、藏族、满族、汉族等。这些字段不只是为了展示更重要的是前端可以做组合筛选。比如用户想看苗族红色手工刺绣的婚纱后台API就能通过条件构造器直接查出来。从答辩的角度这体现了你对业务领域的理解深度而不只是我学会了增删改查。4. 后端核心链路从JWT鉴权到订单状态的精细化控制4.1 基于JWT 拦截器的权限控制实现任何管理平台都必须有登录和权限控制但很多毕设只是简单地把用户角色存在Session里页面用v-if判断一下就完了。这样虽然能跑通但API层面毫无防御能力——只要知道URL任何人都可以直接调后端接口。我在这个项目里用JWTJSON Web Token做了一套轻量级的前后端分离认证方案原理不复杂但效果扎实。整体流程是用户输入账号密码后端校验通过后生成一个JWT令牌返回给前端前端把Token存在localStorage中每次请求在Headers里带上Authorization: Bearer token后端写一个拦截器对需要认证的请求统一校验Token解析出用户ID和角色涉及管理员操作的接口再判断角色是否为ADMIN不是则拒绝访问。生成Token的核心代码基于io.jsonwebtoken:jjwtpublic String generateToken(Long userId, String role) { Date now new Date(); Date expireDate new Date(now.getTime() 1000 * 60 * 60 * 24); // 24小时过期 return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }拦截器解析请求头Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/auth/login)) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { try { Claims claims Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token.substring(7)) .getBody(); request.setAttribute(userId, Long.valueOf(claims.getSubject())); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; } }注意一个细节密码存储不能明文。虽然毕设一般不会被真正攻击但答辩老师大概率会问密码安全性怎么保证。最基础的做法是用MD5加盐或BCrypt加密。我推荐BCryptPasswordEncoder它是Spring Security自带的用法简单且安全性远超MD5// 注册时加密 String encodedPassword new BCryptPasswordEncoder().encode(rawPassword); // 登录时校验 boolean matches new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);4.2 婚纱信息管理模块与图片上传的两种方案婚纱管理模块围绕wedding_dress表做增删改查这本身不难难的是图片上传与回显。前端需要把婚纱照片传上来后端要存文件前端列表页需要展示图片。我在实际项目里遇到过上传图片后刷新页面就404的问题根源是SpringBoot的静态资源映射没有指向上传目录。解决方案是在application.yml里配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB web: resources: static-locations: classpath:/static/,file:${upload.path} upload: path: ./uploads/然后在后端写一个上传接口PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件不能为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName System.currentTimeMillis() RandomUtil.randomNumbers(6) ext; File dest new File(uploadPath fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); return Result.success(/uploads/ fileName); }这里有个容易踩的坑Windows和Linux的路径分隔符不同。用File.separator或者直接/拼接更安全。还要注意如果后端部署在服务器上上传目录需要配置为绝对路径否则项目重启后文件可能丢失。如果不想把图片存在本地可以在云服务商开通对象存储把图片传到OSS桶里返回公网URL。但毕设阶段为了省事省钱本地存储完全够用。4.3 订单状态机待付款、已审核、已使用、已完成、已取消订单模块的状态设计决定了整个业务的严谨性。我在系统里设计了一套标准状态机状态编码状态名称含义可流转方向PENDING待审核用户提交预定等待管理员确认APPROVED / REJECTED / CANCELLEDAPPROVED已通过管理员确认有档期允许使用CHECKED_IN / CANCELLEDCHECKED_IN已使用用户到店取衣/试穿完成COMPLETEDCOMPLETED已完成订单结束无REJECTED已拒绝档期冲突或管理员驳回无CANCELLED已取消用户主动取消无为什么需要一个待审核中间态而不是提交即成功因为婚纱预定不是纯系统自动处理管理员可能需要在后台确认实际档期和门店排期。这个状态让系统显得更真实同时也在答辩时提供了一个可展开讨论的人工审核环节。后端每个状态的变更我都封装成独立方法不允许前端直接改状态字段public Result updateStatus(Long bookingId, String targetStatus) { DressBooking booking dressBookingMapper.selectById(bookingId); if (booking null) { return Result.error(订单不存在); } SetString allowedNext STATUS_MACHINE.get(booking.getStatus()); if (allowedNext null || !allowedNext.contains(targetStatus)) { return Result.error(非法状态流转: booking.getStatus() - targetStatus); } booking.setStatus(targetStatus); dressBookingMapper.updateById(booking); return Result.success(); }这种方式是我实际开发中最推荐的做法既避免了代码里到处都是if (status.equals(...))的冗长判断又集中管理了状态流转规则后期维护也方便。另外提醒一点订单号要全局唯一。用System.currentTimeMillis()加随机数在并发下仍然可能重复更稳的方式是日期前缀 雪花算法比如2025052010123456789。MyBatis-Plus内置的IdWorker.getId()就是雪花算法实现直接用就行。4.4 统计报表接口按民族风格、近七日订单趋势毕设系统里大多数人都做统计报表功能但做法差别很大。最低级的做法是把数据库里所有订单查出来在Java里for循环统计好一点的做法是直接用SQL的GROUP BY聚合。我选择了后者。近七日订单趋势的SQLSELECT DATE(create_time) AS day, COUNT(*) AS orderCount FROM dress_booking WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time) ORDER BY day按民族风格统计top5SELECT d.style, COUNT(b.id) AS bookingCount FROM dress_booking b LEFT JOIN wedding_dress d ON b.dress_id d.id GROUP BY d.style ORDER BY bookingCount DESC LIMIT 5前端ECharts画折线图和柱状图把接口返回的day数组作为X轴orderCount数组作为Y轴即可。这个模块虽然简单但展示效果好答辩时评委看一眼大屏图表工作量感知瞬间提升一个档次。5. 前端Vue实现角色路由、预定流程与交互细节5.1 登录鉴权与动态路由的方案对比前端首先要处理登录后不同角色看到不同菜单的需求。管理员能看到用户管理、订单审核、库存管理普通用户只能看到婚纱浏览、个人订单、个人资料。实现方式有两种方式一前端根据角色用v-if控制菜单显隐最简单在路由组件模板里写el-menu-item v-ifuserInfo.role ADMIN index/admin/user用户管理/el-menu-item el-menu-item index/booking我的预定/el-menu-item优点是代码量少理解成本低缺点是菜单只是看不见并没有在路由层面拦截。如果用户直接在浏览器地址栏输入/admin/user由于前端路由存在且后端接口没校验页面依然能打开请求数据时会403。方式二前端路由守卫 动态权限控制这是我在项目里用的方案。在router/index.js中配置meta.roles字段{ path: /admin/user, component: () import(/views/admin/UserManage.vue), meta: { roles: [ADMIN] } }然后写全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); return; } const role localStorage.getItem(role); if (to.meta.roles !to.meta.roles.includes(role)) { ElMessage.error(无权访问该页面); next(/); return; } next(); });配合后端接口的角色校验形成双重防线。答辩时提到前端做了路由级权限控制后端接口也做了角色校验这个表述就很完整。5.2 管理后台的核心页面婚纱管理、预定审核、用户管理管理后台我拆了几个核心页面每页对应一个业务操作闭环。婚纱管理页面用表格展示所有婚纱列包含封面缩略图、名称、风格、颜色、尺码、日租价格、押金和状态。提供的新增/编辑对话框里有上传图片控件、风格下拉框、描述文本域。核心交互点在于选择风格时联动显示对应的民族属性字段比如选苗族时自动填入默认装饰工艺银饰、刺绣。这个联动逻辑用watch监听即可。预定审核页面专门给管理员查看待审核订单列表要展示的关键信息包括订单号、用户昵称、婚纱名称、预定日期、联系手机、下单时间。操作按钮就是通过和拒绝拒绝时必须填原因。我踩过的坑是审核通过后如果不主动刷新订单列表前端展示的订单状态还是旧的需要在操作成功后重新拉取列表接口。用户管理页面相对简单主要是展示用户列表、禁用/启用账号、重置密码。这里要注意一个细节不能把密码明文展示在列表里。后台列表只显示用户名、手机号、注册时间、状态即可即使管理员也不能看到用户明文密码。5.3 用户端预定流程日期选择、档期提示与订单提交用户端预定流程是另一个考察交互细节的重点。核心页面是婚纱详情页用户需要选择使用日期和尺码系统实时提示该日期是否有档期。这一步我用的是Element-UI的DatePicker组件同时在change事件里调用后端查询接口async handleDateChange(date) { const params { dressId: this.dressId, bookingDate: date }; const res await checkDressAvailable(params); if (res.data.available) { this.availableTip 该日期可预约; this.canSubmit true; } else { this.availableTip 该日期已被预约请选择其他日期; this.canSubmit false; } }前后端交互上还有一个重点日期格式统一。DatePicker返回的默认值是JavaScript的Date对象通过qs或JSON.stringify序列化后可能变成带T和时区的字符串而后端Java侧用LocalDate接收时如果不处理JsonFormat就可能报类型转换错误。我统一在前端用dayjs格式化import dayjs from dayjs; const formattedDate dayjs(date).format(YYYY-MM-DD);然后在后端的实体类字段上标注格式JsonFormat(pattern yyyy-MM-dd, timezone GMT8) private LocalDate bookingDate;只要保住了这层格式就能避免绝大多数日期错乱的bug。5.4 Axios拦截器的统一错误处理前端所有请求依赖Axios。我会在utils/request.js里配置一个统一的axios实例设置基础URL、请求拦截器和响应拦截器const service axios.create({ baseURL: /api, // 配合Vue开发服务器代理 timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); service.interceptors.response.use( response { // 后端约定的返回结构 { code, message, data } if (response.data.code 401) { localStorage.clear(); router.push(/login); ElMessage.error(登录已过期请重新登录); } return response.data; }, error { ElMessage.error(error.message || 网络异常); return Promise.reject(error); } );这样做的好处是每一个页面组件里不需要重复写错误处理逻辑代码瘦身效果非常明显。前端调用接口时只需要关注res.code 200其他状态码统一在拦截器层面兜住。6. 本地联调与部署中踩过的高频坑6.1 跨域问题的标准解决方案前后端分离项目最常见的第一个问题就是跨域。前端跑在localhost:8080后端跑在localhost:9090浏览器会因为同源策略拦截请求。解决方案我在后端写了一个全局CORS配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意这里不要用addAllowedOrigin(*)因为设置了allowCredentials(true)后这种方式在部分浏览器版本下会失效。用addAllowedOriginPattern(*)是兼容性最好的做法。同时如果前端配置了Vite或webpack的代理也可以用代理方式避免跨域但后端配置CORS是更通用、更直接的手段。6.2 MySQL 8.x和驱动的兼容性问题如果你用的MySQL是8.0以上版本JDBC驱动类名和5.7时代的写法不一样。老教程里常写jdbc.drivercom.mysql.jdbc.Driver8.x正确的驱动类名是com.mysql.cj.jdbc.DriverURL中还要配置时区和SSL否则启动会报错。我在application.yml中的稳定配置如下spring: datasource: url: jdbc:mysql://localhost:3306/wedding_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue这个参数很关键MySQL 8.x在首次连接时会要求客户端从服务器获取公钥加密密码不加这个参数经常报Public Key Retrieval is not allowed的异常。这个问题曾经折磨了我一个下午网上老教程基本不会提到。6.3 前端npm install卡死与依赖版本冲突前端构建时最恼人的问题就是npm install卡住或报各种版本冲突错误。我推荐用cnpm或淘宝镜像源替代默认的npm源npm config set registry https://registry.npmmirror.com npm config get registry如果再遇到ERESOLVE unable to resolve dependency tree这类依赖树冲突可以在命令后加--legacy-peer-depsnpm install --legacy-peer-deps这个参数在安装老项目依赖时几乎是必备技能。因为很多课程设计项目是开箱拿来的package.json里的依赖版本很老和最新Node.js的依赖解析规则不兼容只有放松peer依赖检查才能顺利装上。6.4 前端build后部署静态文件与后端打包合并上线毕设的最终展示有两种常见方式一是直接用开发环境运行本地启动npm run serve 启动SpringBoot二是打成生产包部署到服务器。第二种方式更专业也更能应对远程演示的场景。如果要将前端打包后的静态文件和后端合并成一个端口运行做法是npm run build生成dist目录把dist下的文件复制到SpringBoot的src/main/resources/static/目录mvn package打成JAR包部署后直接用后端端口访问前端页面。此时要注意前端baseURL不能再代理到/api而是直接写后端相对路径/api。因为静态文件和API同源后浏览器访问http://localhost:9090/就是首页调用/api接口时正好命中后端Controller。我在项目中采用的前端vue.config.js开发代理配置如下module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true, pathRewrite: { ^/api: } } } } };后端Controller统一在RequestMapping里加/api前缀这样前端代码里请求路径一直是/api/xxx开发环境走代理生产环境走同源无缝切换。7. 从能跑到优秀答辩准备与功能扩展思路7.1 演示前必须准备的功能清单每次答辩前我都会建议学生给自己列一个演示功能清单按顺序走一遍避免临时找不到入口或操作失误。我推荐的演示顺序是用户注册登录展示手机号注册、登录成功后跳转首页婚纱浏览与筛选按民族风格筛选点进详情页查看参数和婚纱图片预定流程选择日期和尺码系统提示档期提交订单管理员审核退出登录切管理员账号在订单管理里审核刚才的订单数据统计看板展示近七日订单趋势和民族风格占比解释数据怎么来用户管理演示禁用账号再尝试登录确认被禁。这套流程覆盖了系统全部核心功能时长控制在10分钟内节奏紧凑评委能看到完整的业务闭环。7.2 答辩评委会追问的五个问题根据我带毕设的经验答辩现场评委最常问的就是下面这么几个问题提前准备好答案基本不会冷场Q1预定冲突是怎么防止的答后端查询该婚纱在指定日期的有效订单数量和库存总量比较提交逻辑放在synchronized方法中单机场景下保证并发安全。Q2密码存数据库为什么不安全为什么用BCrypt答BCrypt是自适应哈希算法自带随机盐相同密码每次加密的结果不同且计算速度慢能有效抵御彩虹表攻击和暴力破解。Q3订单状态为什么不能由前端随便改答前端只是展示层后端定义状态机并校验流转合法性任何非法状态变更都会被拒绝。即使攻击者绕过前端直接调API也无法让已取消的订单变成已完成。Q4如果量大了数据库查询变慢怎么办答目前核心查询都有MySQL索引支撑如dress_id booking_date复合索引。如果未来数据量增大可以通过分库分表、加入Redis缓存热点数据等方式优化。毕设阶段不追求极致的性能但要有优化思路。Q5这个系统还有什么可以扩展的功能答可以接入在线支付模拟沙箱、短信通知用户预定结果、增加礼服试穿VR展示、增加后台操作日志审计等。说出两三个合理的扩展点就能展示你对系统的整体把控力。7.3 三个低成本加分的锦上添花功能如果时间充裕我建议在系统里增加以下三个低成本高感知的小功能它们不会占用大量开发时间但会让系统完整度更高。功能一管理员操作日志用SpringBoot的HandlerInterceptor配合Log注解拦截管理员对关键接口的写操作把操作人、操作内容、时间、IP记录到一张日志表。这个功能代码量不大但答辩时提一句系统记录了所有管理员的关键操作方便事后回溯和审计展示的是安全意识。功能二首页公告栏在首页展示店铺公告或上新通知。后端一篇文章表前端一个接口读取最新一条。这个功能虽然简单但能撑起首页的内容丰富度。功能三订单取消后的档期自动释放我想了想这其实涉及订单状态机的逆向流转——用户取消订单后该婚纱在对应日期的档期立刻就空出来其他用户马上能预定。代码上只需要在取消时修改状态为CANCELLED而查询档期的SQL会自动忽略这个状态这个功能本质上是现成的。但由于实现太简单很多学生容易忘记展示其实可以向评委解释取消订单后档期自动释放的机制是完全由状态过滤实现的。这一句话就能体现数据库设计时的深度。7.4 后续基于这个项目可以做的技术演进整个项目做完后如果你想进一步丰富简历或深入学习可以考虑做这些演进演进方向一引入Redis缓存热门婚纱数据。首页和列表页的SELECT * FROM wedding_dress查询频率最高可以用Redis为热点数据加缓存进一步理解缓存穿透、击穿、雪崩这些经典概念。演进方向二引入消息队列处理订单通知。订单审核通过后通过RabbitMQ或Kafka发消息给用户通知模块模拟真实系统里事件驱动的架构模式。虽然对于小型系统是过度设计但作为学习项目能体现出你对消息中间件的理解。演进方向三用Docker部署整套环境。写一份docker-compose.yml把MySQL、后端JAR包、Nginx静态文件各跑一个容器一键启动。这在实际工作中是最常见的基础能力简历里写熟悉Docker部署含金量远高于会启动Tomcat。8. 写在最后我的一些实际体会完整做完这个SpringBootVue民族婚纱预定系统最大的感受是用一套完整的技术栈落地一个垂直领域的业务系统和上完一门编程课是完全不同的两件事。课程教的是语法和框架而项目逼你去思考用户的预定流程怎么设计才合理订单状态为什么需要流转校验前端日期格式怎么和后端对齐这些真实问题。哪怕是不起眼的一个日期字段背后都牵扯着系统设计的严谨性。这一路开发下来我对我的方法和流程也有了更深刻的理解。比如先设计数据库再写代码这件事以前觉得是教科书里的废话真正动手时才发现如果数据库表关系没想清楚后端接口写十次可能要推翻九次。再比如前后端联调要趁早如果等到前端页面全部写完再对接接口一旦契约对不上排查成本会高得离谱。如果你正准备拿这个项目模板去完成自己的毕设或课设我给你三个实操层面的建议第一先跑通完整项目再改造成你自己的版本。不要从零开始一页一页写先把登录、列表、订单这条主线跑通然后替换成自己设计的页面布局、字段和交互这样做效率最高。第二务必自己动手把核心逻辑重新写一遍。预定冲突检测、状态机流转、JWT校验这三个模块即使代码已经现成也要亲手敲一遍并跑通测试。因为答辩时评委不关心代码是不是你写的但会通过你的表述判断你是否真的理解系统。第三把开发记录留好。开发过程中遇到什么bug、怎么排查解决的随手记录下来。这些内容放在答辩PPT的遇到的困难与解决方案部分是最有说服力的素材比单纯罗列我会SpringBoot靠谱得多。这个系统的定位本身就是适合毕设/课设/学习项目和标题的匹配度很高你在学习过程中遇到的问题越多、踩的坑越深最后展现出来的成长就越扎实。希望这篇拆解能帮你理清思路少走一些我当年走过的弯路。最后再提一句项目中任何模块如果你在看的时候出现了疑惑可以顺着我上面的章节逐一对照源码和数据库表结构基本上就都能串起来了。