一个给婚纱摄影工作室做的预约系统,后端Spring Boot、前端Vue,前后端分离,项目代号叫dbx29。这类系统看似就是一个预约,实际上订单流转、档期排期、角色权限,每块都有不少细节,这篇文章我把整个项目从设计到实现捋一遍,把踩过的坑和值得参考的做法都写出来。
我是去年接的某摄影工作室的项目,老板原来靠微信接单,一个客服天天对着表格排档期,客户问一句答一句,忙起来乱得不行。后来提的需求很简单:让客户自己在网页上看套餐、选摄影师、挑档期、下单付款,后台能管订单、管排期、管相册。听着不复杂,真做起来涉及的东西比想象中多得多。如果你也准备做类似的项目,或者正在做但卡在某个环节,这篇文章应该能帮你省不少时间。
1. 项目概述与核心需求拆解
1.1 婚纱摄影预约系统到底要管什么
先把这个系统的核心理清楚。表面上它解决的是"客户预约"这件事,但往深了看,它管的是三件事:客户资源、摄影资源、时间资源。
- 客户资源:联系方式、意向套餐、消费记录、照片交付状态
- 摄影资源:摄影师、化妆师、服装、场地,这些是婚纱摄影工作室的核心生产力
- 时间资源:某天某位摄影师是否被约满,某个场地是否空闲
这三块互相牵扯,是整个系统最需要花心思设计的地方。我接手的时候老板还掰着指头跟我算,说一个摄影师一天最多接三单,外景的话一天两单就极限了,化妆师跟场地也得配套,这事光靠Excel真的会漏。所以系统的核心不在于"加个按钮、存条数据",而是要把这些资源在时间轴上的占用情况变成程序能管理的状态。
1.2 为什么技术栈选了Spring Boot + Vue
现在做这类业务系统,主流方案其实就那么几个方向,我选Spring Boot + Vue并不是跟风,而是这套组合在业务系统开发里是公认稳的搭配,前后端分离,开发协作效率高,部署运维也省心。
后端用Spring Boot,主要是它的生态太成熟了。做预约系统一定绕不开权限控制、数据校验、事务管理、文件上传这些通用能力,Spring Boot都有现成的starter,配合MyBatis Plus这种持久层框架,订单和排期这类复杂查询写起来很顺手。再加上Spring Security或者更轻量的JWT方案,登录鉴权基本上不用从零造轮子。
前端用Vue,老实说,这个项目用Vue 2还是Vue 3都可以,我这次用的Vue 2,因为团队里同事更熟,而且Element UI的组件库对后台管理系统来说非常友好。婚纱摄影预约系统有两个端的界面——客户端的预约页面和后台的管理页面,用Vue组件化来写,一套代码维护起来也不乱。
有人可能会问,为什么不用现成的SaaS预约平台,非要自己开发?我跟老板也聊过这个问题。市面上的平台确实便宜,但是数据不掌握在自己手里,而且婚纱摄影的流程很个性化,比如有的工作室有独家外景地、有的有特色婚礼跟妆套餐,定制化需求到了平台里就很受限。自己开发,前期成本高一些,但后面改需求、加功能、甚至把系统卖给同行,都是一笔长远账。
1.3 角色与权限拆解
做这类系统,第一件事就是把角色定清楚,角色没定好,后面的接口设计全都是乱的。
我这个系统把用户分成三类:
- 客户:前台浏览套餐、选择摄影师、查看档期、提交预约、支付定金、查看自己的订单和相册
- 摄影师/化妆师:查看自己被安排的档期,上传拍摄完成的照片素材
- 管理员:管理套餐、管理人员、管理档期、处理订单、上传精修照片、查看营业数据
这里有几个容易漏的地方。比如摄影师能不能改自己的档期?老板说不能,档期只能管理员来排,不然摄影师自己把档期改了,客户那边看到的排期就乱了。这个规则在代码里就得锁死,前端隐藏按钮还不够,后端接口也要做权限校验,不然绕过前端直接调接口就出问题了。
权限这块我用的是JWT + 拦截器的方式,没有上Spring Security那套重型的Authorization Server,原因是这个系统角色固定、权限规则简单,用拦截器判断角色字符串反而更好理解、更好维护。
2. 数据库设计与业务表结构
2.1 建了这些表,业务流程才走得通
数据库设计我放在最先做,因为表结构定了,后面写接口就是往里面填数据的事。这个项目我一共建了九张核心表,列出来给大家参考。
- 用户表(user):客户、摄影师、管理员都放这一张表,用role字段区分。摄影师和化妆师都是员工,统一叫staff也行,但为了拓展我设计成一张user表统管
- 套餐表(package):套餐名称、封面图、包含服务内容、价格、原价、状态
- 摄影师表(photographer):姓名、简介、擅长风格、个人作品图集、接单状态
- 档期表(schedule):日期、时间段、关联摄影师、状态(可约/锁定/已约满)
- 订单表(order):订单编号、客户ID、套餐ID、摄影师ID、拍摄日期、状态、支付金额
- 订单详情表(order_detail):订单的扩展信息,比如选了几套服装、是否需要外景、精修张数等
- 相册表(album):关联订单,拍摄完成后存放精修照片
- 评价表(review):客户对拍摄服务的评价和打分
- 支付流水表(payment):记录支付、退款流水
这里说一个设计心得:订单表不要单独只存一套套餐快照。客户下单之后,套餐的价钱、内容都是可能变的,所以订单里要有"下单时的套餐名称、价格、内容说明"这些冗余字段,不能只用套餐外键关联,否则以后套餐改价了,历史订单的展示数据就全是错的。很多新手设计师容易忽略这一点,我一般把下单时能固化的快照字段都固化下来。
2.2 字段设计上几个容易踩的坑
先说金额字段。数据库里的金额我统一用decimal(10, 2),Java侧用BigDecimal,前端展示用字符串或toFixed处理。千万千万别用float或者double存钱,精度问题一旦出现,对账对不上,客户投诉,这个锅谁都背不起。支付金额、退款金额、定金金额,我都单独区分字段,别混着存。
再说时间和日期。档期表里的日期我用的是date类型,时间段用time类型,后端接收的时候做好格式校验。有一个细节要注意:客户选择的拍摄日期可能跨月、跨年,前端日期选择器可以往后开放三个月或半年,后端也相应做限制,防止有人约到一年后导致排期表无限膨胀。
还有一个是订单编号。单号我采用的是"日期+随机数"的方式,比如20250615001,不搞太复杂。之前我见过有人用UUID当订单号给客户看,一串乱七八糟的字符,客户记都记不住,体验很差。业务上对外展示的单号要短、要能念得出来。
2.3 预约冲突怎么处理才不出问题
这是预约系统的核心难点,必须单独说一下。你想,客户约了6月12号拍外景,选了某摄影师,如果这个摄影师6月12号已经被别人约走了,那这单就得在提交的时候被拦下来。
我处理的方式是:在档期表(schedule)里把每天的每个时间段(上午场、下午场、全天场)定义成独立的可预约单元,每个单元绑定一名摄影师。客户提交订单的时候,后端先开启一个事务,把这个时间段对应的档期记录锁定(select ... for update,也就是行锁),然后检查状态是不是"可约"。如果可约,就更新为"已锁定",同时创建订单。一旦两个客户同时抢同一个档期,后到的那一个会在这个锁上等待,前一个事务提交之后,后一个再查状态,发现已经被占了,直接返回"该档期已被预约"。
这种方案比在前端做限制靠谱得多。前端再花哨,防不住两个用户同时提交。行锁加事务是这个场景最简单可靠的方案,我实测下来在中小型工作室的并发量下完全没压力。
还要注意服务端的重试机制。行锁等待可能抛死锁或等待超时,我给接口加了重试逻辑,第一次失败提示用户稍等再试,同时返回一个明确的错误码。这个体验问题很关键,客户一旦遇到莫名其妙的下单失败,对系统的信任度就降低一大截。
3. 后端核心功能实现
3.1 预约接口怎么设计才优雅
预约接口是这个系统里业务最复杂的接口,我把它单独拎出来说。它的入参大概是:客户ID、套餐ID、摄影师ID、拍摄日期、时间段、联系人信息、备注。
处理流程是这样的:
1. 校验客户登录状态、套餐是否存在、摄影师是否存在且可接单 2. 计算订单金额,定金和尾款分开算 3. 开启事务,锁定档期,检查冲突 4. 创建订单,状态设为待支付 5. 生成支付流水,返回支付二维码或支付链接这个流程里有一个细节:步骤2计算金额的时候,一定要从后端重新取套餐价格,不能完全信任前端传来的价格,前端限价是不可靠的,接口被人拿工具直接调了就乱套了。
订单状态我用的是状态机模式,定义了这几个状态:
- 待支付(0):刚创建,还没付定金
- 已付定金(1):定金支付完成,档期已锁定
- 拍摄中(2):当天正在拍摄
- 已完成(3):照片已交付,订单完成
- 已取消(4):客户或管理员取消
- 退款中(5):部分退、全退的中间态
从待支付只能到已付定金或已取消,已付定金可以到拍摄中或已取消,已取消再走退款流程。状态流转我写在一个service方法里统一处理,状态不合法就直接抛异常。这么做的原因是状态字段如果放任谁都能直接改,几天之后数据就脏得你怀疑人生。
3.2 日历排期接口的思路
排期页面是客户端的核心页面之一,要展示某位摄影师在未来三个月里哪几天还能约。我后端的实现方式是:一次性返回选定月份每一天的可约状态,前端拿到数据以后在日历上渲染不同的颜色,绿色可约、灰色已满、橙色休息。
接口响应格式大致是:
{ "code": 200, "data": [ {"date": "2025-06-01", "status": "available", "timeSlots": ["morning", "afternoon", "full"]}, {"date": "2025-06-02", "status": "full", "timeSlots": []}, {"date": "2025-06-03", "status": "rest", "timeSlots": []} ] }这里有个优化点:默认只查询当前月份的数据,前后端都要做懒加载。我之前见过有人把一整年的排期一次查出来,数据量一大前端就卡,也没这必要。一个月一个月地加载,交互体验反而更流畅。
休息日怎么来的?管理后台有排班设置,管理员提前把摄影师的休息日、事假、外出学习这些时间标成不可约。不要想着程序自动算每周几休息,每个工作室的排班规则都不一样,做成人工维护的最灵活。
3.3 订单从创建到交付,后端要撑住哪些接口
一个订单在生命周期里要用到的接口并不只是创建那一个。我盘点了一下,至少包括:
- 订单列表:客户端查自己的订单,后台端可按状态筛选所有订单
- 订单详情:查套餐内容、摄影师信息、支付记录、相册进度
- 取消订单:未支付的直接关闭,已付定金的走取消+退款流程
- 支付回调:微信/支付宝支付成功后的异步通知,更新订单和支付流水
- 上传照片:摄影师拍完当天可以上传原片,后台可以上传精修片
- 确认交付:管理员确认照片交付完成,订单进入已完成状态
支付回调是这里面的技术重点,因为它是异步的,而且可能会重复通知。代码里要先校验回调签名,然后判断支付流水当前状态,如果已经是"支付成功"就直接忽略,防止重复更新订单状态。
这世上没有靠switch(mode)就能通吃的异步接口,支付回调一定要做成幂等的。我当时跟同伴反复强调这个点,后来上线后果然收到了微信的重复回调,里面状态判断成功拦住了重复更新,大家才松一口气。
4. 前端页面实现与交互细节
4.1 前端页面结构:客户看到的和后台看到的
前端分为两部分,我分别说一下。
客户端的页面包括:首页(介绍展示)、套餐列表页、套餐详情页、摄影师展示页、预约页、我的订单页、相册页。整个设计风格走的是简约白+金的大气路线,图和图之间的距离要留够,婚纱摄影本身就靠视觉冲击力吃饭,页面花里胡哨反而会压过作品的质感。
后台用的是一个典型的管理布局:左侧菜单栏,顶部操作栏,中间内容区,用Vue Router来做视图切换。页面有:仪表盘(今日预约、营业收入、待处理订单数量)、订单管理、套餐管理、摄影师管理、档期排布、照片管理。
我特别想提醒的一点是:后台的表格页一定要做筛选条件,比如订单管理页要能按日期范围、订单状态、摄影师姓名来筛选,这是必然的需求。老板不会告诉你他要什么,但他用两天就会来问"我能不能只看上周拍完还没交片的单子"。
4.2 日期选择与档期联动的实现细节
预约页的日期选择是交互上最繁琐的一块。客户先选套餐,再选摄影师,然后才跳到日历选日期。这里的数据联动逻辑是:
选套餐 -> 过滤出提供这个套餐的摄影师列表 -> 选摄影师 -> 拉取该摄影师的排期 -> 渲染日历
前端有个细节:切换摄影师以后,已经选的日期要重置掉,不然可能出现客户先选了A摄影师的6月12号,再换B摄影师,日期还留在6月12号,但B摄影师那天约满了,后面提交的时候才报错,体验很差。
日历组件我没有用第三方日历库,是用Element UI的date-picker加自定义class来标记可约不可约状态的,配合天粒度周粒度切换。实测下来,移动端和PC端的表现都还行,而且省了一个额外引入的库。
另外还有个细节是接口请求的节流。用户快速切换摄影师或月份的时候,会连续发起好几个请求,后面的响应可能先回来,把前面的覆盖掉,页面显示的排期就张冠李戴了。我用了请求序号,每次请求带上自增的序号,响应返回时判断序号是不是最新的,不是就不更新页面。这个坑很隐蔽,但遇上了就很烦。
4.3 相册模块:照片上传与展示的体验优化
相册模块是客户体验的加分项,也是后台管理的隐藏工作量,我单独写一段。
照片上传我用的是前端直传云存储的方案,后端只负责生成上传凭证和保存照片URL,不经过应用服务器中转。为什么这么做?因为婚纱摄影一张原片少则5MB、多则20MB,一套婚纱照几百张原片,如果都走后端转存,服务器带宽分分钟被打满,而且应用服务器挂了照片还没了,风险很大。
前端上传组件可以断点续传、进度展示,这是标配。上传完成以后,前端把URL和照片信息提交给后端接口,后端统一记录到相册表。
这里有一个权限要点:相册照片的访问不能是纯公开的URL,否则客户把链接发给朋友,朋友也能看。我在照片URL后面拼接一个带签名的token参数,后端校验通过才能访问,有效期可以设置长一些,比如七天内有效。这样的话,私密性有保障,实现成本也不高。
5. 系统部署与常见问题排查
5.1 本地开发环境搭建
开发环境的搭建其实很简单,前后端分离,前端一个NPM服务,后端一个Spring Boot应用,各自独立跑。
前端:
npm install npm run serve后端,确保本机有JDK 8+和Maven,然后:
mvn clean package -DskipTests java -jar target/dbx29.jar本地跑的时候要特别注意跨域问题。前端端口是8080,后端端口是9090,前端通过axios请求后端必须开启CORS。在Spring Boot里加一个配置类,允许指定来源访问即可。这里不要用*通配符,因为涉及携带Cookie的请求,指定来源更安全。
数据库我用的是MySQL,本地装一个就好。表结构我建议用启动初始化或Flyway管理,不要手动导SQL,不然团队成员各自导各自的,改几次表结构就乱了。我是用的Flyway,每次变更写一个版本化的SQL脚本,应用启动自动执行,团队协作非常稳。
5.2 上线部署的几个建议
部署方案我推荐最简单可靠的:前端打包成静态文件交给Nginx托管,后端用Java执行一个Jar包,数据库单独一台MySQL或者云数据库。
Nginx配置关键点在于前端路由的history模式要配置try_files,否则刷新页面就404。这一段相信踩过坑的朋友都懂:
location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }反向代理部分,把/api开头的请求转发到后端服务:
location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }后端Jar包建议用systemd或supervisor守护,别裸跑一个nohup java -jar,服务挂了都没人知道。我自己的习惯是systemd管理,写一个Unit文件,支持开机自启、崩溃自动重启、日志收集,非常省心。
5.3 常见问题速查表
我把这个项目开发和上线过程中遇到过的典型问题和排查思路整理成一个表格,方便大家对照参考。
| 问题现象 | 可能原因 | 排查方式与解决办法 |
|---|---|---|
| 前端请求接口提示跨域 | 后端未开启CORS或配置不对 | 检查后端CORS配置,确认允许的来源和请求头 |
| 用户提交订单时提示“档期已被预约” | 档期被其他客户抢先锁定 | 属于正常冲突,可在前端提示更换时间段或摄影师 |
| 支付回调后订单状态没更新 | 回调验签失败或幂等逻辑没写好 | 查看后端日志中的回调记录,检查验签逻辑 |
| 上传照片后页面展示不出来 | 云存储域名未加白或签名过期 | 检查存储桶的访问权限配置,检查签名过期时间 |
| 客户端的日期选择器无法选中某天 | 后端返回的排期状态字段与前端预期不一致 | 用浏览器开发工具看接口返回,对比状态枚举 |
| 刷新后台页面出现404 | Nginx未配置try_files | 按上文Nginx配置补上路由回退 |
| 订单金额显示多了小数位 | 前端展示未做格式化处理 | 前端用toFixed(2)统一格式,后端返回BigDecimal字符串 |
| 后台无法筛选某天的订单 | 查询条件传的日期格式不对 | 统一日期格式为yyyy-MM-dd,前后端保持一致 |
这个表格是我整理过程中删减过的,实际项目里还遇到过更多恶心问题,比如数据库连接池爆掉、服务器时区不对导致时间差八小时,等等。每个问题记下来的时候,我都会顺手把解决方案写进团队的Wiki,后面再遇到就是查文档的事,不用重复踩坑。
5.4 说几个容易忽略的技术细节
做个这种系统,你以为写完功能就完了,其实还藏了一些不显眼但很重要的事。
一是时区问题。服务器默认时区是UTC的话,数据库里存的日期会跟中国时间差八个小时,客户预约的日期直接错一天,这是灾难级事故。我统一在数据库连接串上加serverTimezone=Asia/Shanghai,同时把系统时区也设置对,双保险。
二是系统操作日志。谁在什么时候改了什么档期、处理了哪笔订单,最好都记录下来。刚开始我做的时候嫌麻烦不想加,后来老板几次问"这个订单为什么被取消了",没有日志就只能干瞪眼。后来补了一个简易操作日志表,管理员的关键操作都记录,纠纷处理起来效率高了很多。
三是数据备份。这种系统数据量就算不大,但那也是客户的预约数据和照片信息,丢了没法交代。我建议至少每天自动备份一次数据库,备份文件保留七天,用云服务器的定时任务加上简单的脚本就够了。别觉得麻烦,真要丢一次数据,后悔都来不及。
四是照片的URL安全。前面提到的签名token,一定要记得加过期时间,同时注意后台对相册的删除操作要同步失效对应的访问链接,不能让删除的照片还能被访问。这个小细节看起来小,但涉及客户隐私,不能疏忽。
6. 项目收尾的几句实在话
系统上线以后,老板最大的感受是客服压力小了很多,客户自己能在网上看套餐、选档期,不用一遍又一遍打电话问了。客户那边的体验也好了,预约流程透明,什么时候拍、什么时候拿到底片,状态都能看到。这就是做这类业务系统最直接的成就感。
回到技术上,这类预约系统的骨架是相通的:表设计围绕“人、服务、时间”三个维度展开,后端核心是把状态机和事务处理好,前端重点是交互的流畅和细节的兜底。把这些想清楚了,哪怕换个行业,比如美容预约、家政预约、宠物店预约,逻辑都大差不差。
最后分享一个小经验:开发过程中一定要让懂业务的人全程参与,而不是调研完就闷头写代码。我这个项目里,管理员提出“休息日人工维护”“摄影师不能改档期”这些实际需求,靠着这种及时反馈避免了后面大改。系统做出来是给人用的,业务跑不顺,技术再好也白搭。
如果你也在做一个类似的系统,希望这篇文章能帮你少踩几个坑。预约系统的技术难度不算大,但要把它做好、做得让客户和商家都觉得好用,细节真的不少。