民族婚纱预定系统开发实战:SpringBoot+Vue前后端分离架构
跟婚纱预定系统这种业务项目打交道多了,你会发现一个很有意思的现象:单纯写CRUD的教程遍地都是,但把一个项目从零到一完整落地的过程,尤其是那些藏在代码背后的设计决策和部署坑点,反而没人愿意讲透。我自己做过几个前后端分离的业务系统之后,深感这种“看起来简单、做起来全是细节”的项目,才是最考验功力的。
这次借着一个民族婚纱预定系统的完整开发过程,我把整套技术方案、代码结构和部署思路都梳理一遍。这个项目用的是SpringBoot+Vue+MyBatis+MySQL这套经典组合,该踩的坑我都踩过了,该填的坑我也都填好了,你拿到源码之后能直接跑、能照着改造,也能从里面学到不少前后端分离项目的实战套路。不管你是准备做毕业设计,还是刚进公司要接手类似的管理系统,这篇内容都值得你花点时间看完。
1. 项目定位与技术选型思路
1.1 这套系统到底解决什么问题
民族婚纱预定系统,本质上是一个面向特定人群(少数民族婚礼场景、传统服饰租赁/定制商家)的预约管理系统。它要管的不是“婚纱好看不好看”这种审美问题,而是实实在在的业务流:用户浏览服饰、查看档期、提交预定、商家确认订单、后台管理服饰库存和订单状态。
所以核心需求拆下来就是三块。第一,前台展示与预定,用户能按民族风格、适用场景、价格区间筛选服饰,查看详情和可预约时间;第二,订单全流程管理,包括提交预定、支付占位(或者线下支付占位)、商家审核、订单状态流转;第三,后台管理,服饰信息维护、分类管理、轮播图配置、订单处理、用户管理。这三块合在一起,就是一个典型的“C端展示+B端管理”双端系统。
这种系统的难点不在于某个功能单点有多难写,而在于前后端如何分工、状态如何流转、数据如何组织。
1.2 为什么选SpringBoot+Vue+MyBatis这套组合
很多人问我,现在新项目为什么不上Spring Cloud微服务?为什么不用MyBatis-Plus或者JPA?我的回答很简单:看业务复杂度。
这个项目的数据量级、并发规模和团队协作方式,完全不需要微服务那一套。SpringBoot单应用就能搞定所有后端接口,部署运维成本低,本地跑起来也快。Vue做前端SPA,前后端通过JSON交互,天然契合这种管理类系统的开发节奏。至于MyBatis,它的优势是SQL可控,复杂查询可以手动优化,特别适合这种业务表结构相对固定、查询逻辑五花八门的系统。MySQL则是久经考验的关系型数据库,存储订单、用户、服饰这类结构化数据非常合适。
说实话,这个组合现在看起来“传统”,但它最大的价值是:技术栈足够通用,网上资料多,招人也好招。你在牛客上搜SpringBoot面试题,搜出来的高频考点就是这套东西。把这个项目吃透了,面试聊项目经验的时候非常有料。
2. 核心功能模块与设计思路拆解
2.1 用户端的三个关键页面
用户端最核心的页面是服饰列表页、服饰详情页和预定下单页。
列表页要解决的核心问题是“筛选和分页”。民族服饰有很多维度——民族(藏族、苗族、蒙古族等)、场景(婚礼、写真、仪式)、价格区间。如果只用MySQL的
LIKE
查询,一旦数据量上来,接口响应会很慢。所以我在设计时采用了动态SQL拼接,根据前端传来的筛选条件组合查询语句,同时用PageHelper做物理分页。
这里有个细节值得说:服饰列表的封面图,不要用数据库的
BLOB
字段直接存图片二进制,而是存图片URL路径。图片文件走独立的上传接口,落盘到服务器本地,数据库只记路径。这样做的好处是前端
<img>
标签直接绑定路径就能显示,不需要额外写二进制流转换接口,而且后续迁移到OSS也方便。
详情页要处理的则是“图片展示+档期查询”。一个民族婚纱套系往往有五六张展示图,数据表设计成主表(服饰基本信息)+子表(图片列表)的方式,查询时用一条SQL把主表和子表数据联合查出来,组装成带图片列表的VO对象返回给前端。
下单页的核心是“防重复预定”。用户选好服饰、填了婚期、提交预定,后端必须校验这条档期是否已被别人占用。我的做法是在数据库层面加唯一约束,用
occasion_date
(档期日期)+
dress_id
(服饰ID)作为联合唯一索引。这样就算两个用户同时提交,数据库也会拒绝其中一条,比单靠代码判断靠谱得多。
2.2 管理后台的功能划分
管理后台我按角色权限拆成两块:管理员和运营人员。
管理员负责用户管理、角色分配、系统配置。运营人员负责服饰管理、订单审核、轮播图维护。权限控制这块,SpringBoot集成Spring Security或者Sa-Token都可以。我这个项目用的是基于Token的认证方案——前端登录后拿到Token,之后每个请求都在Header里带上Token,后端通过拦截器统一校验身份和权限。
后台界面我用的是Vue+Element UI。Element UI的表格组件、表单组件、对话框组件都是现成的,写后台管理页面效率非常高。比如服饰管理页面,一个
el-table
绑定列表数据,
el-dialog
里放表单,提交后刷新列表,这一套组合拳能覆盖80%的后台页面场景。
2.3 为什么强调“前端分离”这个关键词
“前后端分离”这个关键词现在被说烂了,但很多人其实没搞明白它到底分离了什么。
分离的不仅仅是代码仓库和开发人员,更重要的是 交互协议 。前端不再通过服务端模板(比如JSP、Thymeleaf)拿数据,而是通过HTTP接口拿JSON。这意味着前端可以独立部署(Nginx或Node服务器),后端可以独立部署(Tomcat或Docker容器),两边只要约定好接口文档,就可以并行开发。
这个模式有个必须处理的痛点就是跨域。前端跑在8080端口,后端跑在9090端口,浏览器默认会拦截跨域的Ajax请求。解决办法有两个:后端配置
@CrossOrigin
注解或CORS全局配置;或者通过Nginx反向代理,把
/api
路径转发到后端服务。生产环境我推荐用Nginx方案,因为前后端同源之后,连Cookie、Token传递都不用额外处理。
3. 数据库设计与后端核心实现
3.1 四张核心表的结构设计
整个系统的数据库我用了一个很克制的设计,核心业务表就四张:用户表、服饰表、订单表、图片表。
用户表(
sys_user
)字段包括
id
、
username
、
password
、
nickname
、
avatar
、
role
、
create_time
。密码用BCrypt加密存储,绝对不允许明文入库。角色字段用字符串标识,比如
ADMIN
、
OPERATOR
、
CUSTOMER
,简单直白。
服饰表(
dress
)字段包括
id
、
name
、
ethnic_type
、
scene_type
、
price
、
description
、
status
、
cover_image
。
ethnic_type
存民族分类,比如“藏族”“苗族”,
scene_type
存适用场景,比如“婚礼”“写真”。这两个字段在列表页的筛选中起着关键作用。
订单表(
order_info
)字段包括
id
、
order_no
、
user_id
、
dress_id
、
occasion_date
、
contact_name
、
contact_phone
、
status
、
remark
、
create_time
。
order_no
用时间戳+随机数生成,保证唯一,方便后续对账。
status
用整数表示,0待审核、1已确认、2已取消、3已完成。
图片表(
dress_image
)字段包括
id
、
dress_id
、
image_url
、
sort_order
。这个表主要解决一件服饰多张展示图的问题,
sort_order
用来控制前端展示顺序。
这四张表的关联关系很清晰:用户1对多订单,服饰1对多订单,服饰1对多图片。不需要复杂的中间表,也不需要过度设计,够用且好理解。
3.2 后端接口设计规范
后端接口我统一采用
/api
前缀。比如
/api/user/login
、
/api/dress/list
、
/api/order/create
、
/api/order/list
、
/api/admin/dress/add
。统一前缀的好处是前端封装的Axios实例可以统一配置
baseURL
,Nginx反向代理也只需要转发一个路径。
返回格式也做了统一封装。写了一个
Result
类,结构是
{code: 200, message: "success", data: {...}}
。成功时
code
为200,业务异常时
code
为500或自定义错误码,前端Axios拦截器统一判断,不是200就弹错误提示。
Token认证这块我用的是JWT。用户登录成功后,后端生成一个有效期为24小时的JWT返回给前端。前端把它存在
localStorage
里,每次请求在Axios请求拦截器中添加:
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
})
后端收到请求后,拦截器先解析Token,如果Token无效或过期,直接返回401状态码。前端Axios响应拦截器看到401,自动跳转到登录页。这套流程就是热词里说的“vue前后端分离请求token处理”,实际跑起来非常丝滑。
3.3 MyBatis动态SQL的几个关键写法
MyBatis是这个项目的数据访问层核心,动态SQL用得好不好,直接决定代码优雅不优雅。
服饰列表查询是我重点展示的部分。因为前端传来的是不确定的筛选条件——可能只传了民族,可能只传了场景,可能什么都没传。如果为每种情况写一个SQL方法,代码会膨胀到没法维护。动态SQL的
<where>
标签完美解决了这个问题:
<select id="selectDressList" resultType="com.example.entity.Dress">
SELECT * FROM dress
<where>
<if test="ethnicType != null and ethnicType != ''">
AND ethnic_type = #{ethnicType}
</if>
<if test="sceneType != null and sceneType != ''">
AND scene_type = #{sceneType}
</if>
<if test="keyword != null and keyword != ''">
AND name LIKE CONCAT('%', #{keyword}, '%')
</if>
AND status = 1
</where>
ORDER BY create_time DESC
</select>
<where>
标签会自动处理掉第一个
AND
,不会出现
WHERE AND
这种SQL语法错误。这也是MyBatis的一个高频考点,面试时经常被问到。
订单创建接口就必须加事务控制。创建订单时要做两件事:插入订单记录、更新服饰状态。如果只插入订单而更新状态失败,数据就不一致了。所以我在Service层加了
@Transactional
注解,任何一步异常都能回滚。这里插一句,
@Transactional
有几个容易踩的坑:默认只回滚
RuntimeException
,如果是自定义异常需要指定
rollbackFor
;同类内部调用会让代理失效,事务不生效。这些都值得单独写一篇细说,但在这个项目里,我直接用最简单的方式——把事务方法放在Controller调用的Service实现类里。
我在这个项目的数据库脚本中,还专门为订单表加了一个状态索引:
CREATE INDEX idx_order_status ON order_info(status);
订单状态流转查询是高频操作,加了这个索引之后,后台订单列表页的响应速度会明显提升。
4. 前端实现与项目部署全流程
4.1 Vue工程结构与路由配置
前端工程我基于Vue CLI(Webpack)搭建,目前Vue 2.6依然是我在这个项目中偏稳妥的选择——组件生态丰富,Element UI适配度高,相关的坑早就被填平了。工程结构如下:
src/
api/ // 封装所有接口请求
assets/ // 静态资源
components/ // 公共组件
router/ // 路由配置
store/ // Vuex状态管理
views/ // 页面组件
admin/ // 后台管理页面
user/ // 用户端页面
App.vue // 根组件
main.js // 入口文件
路由配置里需要区分用户端和管理后台。用户端页面放在
/
路径下,比如
/
、
/dress/:id
、
/order/create
。管理后台统一挂在
/admin
路径下,并且配置路由守卫,未登录或非管理员角色访问时跳转到登录页。
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path.startsWith('/admin') && !token) {
next('/login')
} else {
next()
}
})
这里有个细节想提醒:用户端和管理后台最好用不同的Layout组件。用户端是导航栏+内容的布局,管理后台是侧边栏+顶部栏+内容区的布局。不要混用一套,后期调整起来非常痛苦。
4.2 Axios封装与接口调用
Axios封装是前端工程的基础设施。我的做法是建一个
request.js
文件,导出配置好的Axios实例,所有API请求都基于这个实例发出。
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_URL || '/api',
timeout: 10000
})
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
this.$message.error(res.message)
return Promise.reject(new Error(res.message))
}
return res.data
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
这样封装之后,业务代码里调用接口就非常清爽。比如服饰详情页:
getDressDetail(id).then(data => {
this.dress = data
this.imageList = data.imageList
})
不用在每个页面重复处理错误提示和登录失效问题,一套拦截器全部搞定。
关于Vue项目中播放m3u8视频的需求,我在这个项目的服饰展示模块里也有涉及。因为部分高端婚纱套系会提供试穿视频或动态展示视频,视频源格式是m3u8。处理方案是前端引入
hls.js
库,在Vue组件里动态创建
<video>
标签并加载m3u8流。这个方案在PC端兼容性很好,移动端的话可以优先用原生
<video>
标签配合
x5-playsinline
属性处理。
4.3 完整部署流程
部署是我要详细展开的部分。很多人项目写完了,部署却卡住,我能理解那种感觉。所以我整理了一套相对完整的流程,从环境准备到正式上线。
第一步,环境准备。
需要准备一台Linux服务器(我这边测试用的是CentOS 7.9)。安装JDK 1.8、MySQL 5.7、Nginx,这些直接用包管理器安装即可。Node.js环境用于前端打包,版本要注意,Vue CLI项目用Node 14或16都比较稳。
第二步,初始化数据库。
把项目自带的
dress_booking.sql
脚本上传到服务器,执行:
mysql -u root -p < dress_booking.sql
脚本里建好了库和表,还有一个默认管理员账号
admin/admin123
。用Navicat或命令行检查一下表是否创建成功。
第三步,修改后端配置并打包。
找到
application.yml
,修改数据库连接信息:
spring:
datasource:
url: jdbc:mysql://localhost:3306/dress_booking?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 你的数据库密码
然后执行Maven打包命令:
mvn clean package -DskipTests
打包完成后,
target
目录下会生成一个
dressbooking-0.0.1-SNAPSHOT.jar
。
第四步,启动后端服务。
直接使用
java -jar
命令启动,搭配
nohup
:
nohup java -jar dressbooking-0.0.1-SNAPSHOT.jar > app.log 2>&1 &
启动之后看日志确认没有报错。测试接口是否可以访问:
curl http://localhost:9090/api/dress/list
能返回JSON数据就说明后端没问题。
第五步,构建前端并配置Nginx。
在前端工程目录执行:
npm install
npm run build
构建完成后,
dist
目录就是前端静态文件。把它上传到服务器的
/usr/share/nginx/html/dress
目录,然后修改Nginx配置:
server {
listen 80;
server_name your_domain.com;
location / {
root /usr/share/nginx/html/dress;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:9090/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
注意
try_files $uri $uri/ /index.html
这行,它的作用是处理Vue Router的history模式。如果不加这行,刷新某个子页面路由时,Nginx会返回404。
/api
反向代理配置则是把前端的API请求转发到后端Java服务,解决跨域问题。
第六步,重启Nginx并验证。
nginx -t
nginx -s reload
浏览器访问服务器IP,能看到系统首页,登录、查询、下单流程都跑通,部署就算完成了。
5. 常见问题与排查技巧实录
5.1 数据库自动断开导致的“服务不可用”
这个问题我在这个项目里遇到过。MySQL的
wait_timeout
默认是8小时,如果长时间没有请求,MySQL会自动断开空闲连接。数据库连接池里的连接还在被使用,但MySQL已经断开了,就会出现连接异常。
排查思路其实很直接:启动项目后一切正常,但过了一晚上再访问,接口报错。看后端日志,关键报错是
Communications link failure
或者
Connection is not available, request timed out
。
解决办法有两个方向。一是给连接池配置合适的参数,比如HikariCP(SpringBoot默认连接池)可以设置:
spring:
datasource:
hikari:
max-lifetime: 1800000
idle-timeout: 600000
二是调整MySQL的
wait_timeout
,把它改大为28800秒(8小时)以上。我两个都改了,目前运行稳定。
5.2 “端口被占用”引发的部署失败
第一次部署时,后端启动失败,报
Port 9090 was already in use
。排查方法是:
lsof -i :9090
kill -9 PID
然后重新启动SpringBoot服务。这个操作很基础,但新手经常被卡在这里,值得单独提一下。如果是多次部署,建议用脚本统一管理启动、停止、重启操作,避免每次都手动找PID。
5.3 Vue打包后布局异常
“vue 打包后布局异常”也是热词里出现频率很高的一个问题。开发环境跑得好好的,
npm run build
之后,CSS错乱、图片不显示。这个大概率是静态资源路径问题。需要在
vue.config.js
里设置
publicPath
为相对路径:
module.exports = {
publicPath: './'
}
或者干脆用绝对路径
/dress/
,配合Nginx的root配置来使用。这个坑很容易踩,尤其是部署到子目录时更明显。
5.4 MyBatis字符串比较的“隐形坑”
热词里有个“mybatis 单个数字字符比较”,这个坑我在项目中实际遇到过。在XML里写条件判断时,比如判断订单状态是否为某个值:
<if test="status == 1">
AND status = 1
</if>
此时
status
是前端传过来的整数
1
,没问题。但如果是字符串类型的参数,比如
'1'
,
status == '1'
这种写法在某些MyBatis版本中会触发数字开头的字符串比较问题。解决方法是统一用
status == '1'.toString()
,或者干脆在入参时就转为Integer类型,避免使用单引号包裹数字比较。
5.5 SpringBoot版本选择引发的连锁问题
热词里有“springboot版本太高”这个表述,说明很多人在版本选择上吃过亏。SpringBoot 3.x对JDK版本有要求,必须JDK 17以上,而且很多第三方组件的兼容性还没完全跟上。如果你本地是JDK 8,就不要强行用SpringBoot 3.x,老老实实用SpringBoot 2.7.x。这个项目我选的就是2.7.6版本,稳定且兼容面广。
这里给大家一张简单的版本对照表:
| JDK版本 | 推荐SpringBoot版本 | 说明 |
|---|---|---|
| JDK 8 | 2.5.x ~ 2.7.x | 主流稳定版,资料多 |
| JDK 11 | 2.7.x | 兼容性好 |
| JDK 17 | 3.0.x+ | 新特性多,但需确认生态兼容 |
5.6 前端请求BaseURL如何按环境切换
开发时前端走
http://localhost:9090
,部署时走Nginx的
/api
,实现方式是
.env.development
和
.env.production
两个环境变量文件:
// .env.development
VUE_APP_BASE_URL = 'http://localhost:9090'
// .env.production
VUE_APP_BASE_URL = '/api'
打包时Vue CLI会自动读取对应的环境变量。这样代码里只需要写
axios.create({ baseURL: process.env.VUE_APP_BASE_URL })
,不同环境下自动切换。
6. 项目后续扩展方向与个人经验总结
6.1 这个项目还能怎么玩
如果想把项目做得更完整,或者说想在面试时有更多亮点可说,我建议在下面几个方向选一个做延伸。
支付模块是当前项目的一个明显短板。目前订单只是预约占位,没有真正接入支付。可以接微信支付H5+Native支付,在用户提交订单后生成支付二维码,支付完成后通过回调更新订单状态。这个功能一加上,整个系统的商业闭环就完整了。集成微信支付官方SDK,文档写得很详细,照着流程走一遍基本能通。
消息通知也可以做一下。用户提交预定后,给管理员发送通知;管理员审核后,给用户发送通知。如果不想引第三方推送服务,最简单的做法是在系统内部做个站内信表,用户在消息中心查看通知,不用搞复杂了,但能体现你对业务流程的思考。
还有权限细化。现在只有Admin和User两种角色,可以扩展成RBAC模型,引入角色表、菜单表、权限表,用Spring Security的注解控制接口权限。
6.2 写代码之外我说句实在话
这个项目让我最满意的不是代码本身,而是整套方案是可复制、可迁移的。技术选型不花哨,但每一块都能讲清楚为什么这么选;业务流程不复杂,但每个环节的前后端交互都考虑到了;部署方式很简单,但生产环境下遇到的问题也都覆盖到了。
我在实际开发中体会很深的一点是:很多人学SpringBoot,天天背面试题,什么自动配置原理、Bean生命周期,倒背如流,但真让他从零搭一个能跑的前后端分离项目,他会卡在跨域上,卡在Token校验上,卡在Nginx配置上。这个项目就是用来填平“理论”和“实践”之间的鸿沟的。
最后再分享一个小技巧:拿到任何一套前后端分离项目源码,不要急着跑起来,先看数据库脚本,把表结构理清楚;再看后端接口文档或Controller层,搞清楚有哪些接口、每个接口做什么;然后看前端路由和API封装,理解页面和接口的对应关系;最后再启动项目,把核心流程走一遍。按这个顺序走下来,一套陌生的系统你一天之内就能上手。做项目,慢就是快,理顺了再动手,比盲目改代码高效得多。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)