首页 / 资讯中心 / 文章详情

基于 Spring Boot + Vue3 的汽车美容洗车预约会员系统设计与实现

基于 Spring Boot + Vue3 的汽车美容洗车预约会员系统设计与实现 ★ FEATURED ARTICLE
温馨提示本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片一、项目背景与需求分析随着汽车保有量持续增长洗车与汽车美容已经成为高频、刚需的消费场景。传统门店普遍依赖电话预约、纸质会员卡和人工排班容易出现预约冲突、会员余额对不上、服务记录缺失等问题。基于 Spring Boot Vue3 构建一套汽车美容洗车预约会员系统可以把预约排期、会员储值、服务核销和经营数据统计统一到一个平台中既降低门店的运营成本也提升车主的消费体验。本系统的核心目标可以归纳为三点预约在线化车主可自助选择服务项目、门店、时间段系统自动校验工位与技师占用情况避免重复预约。会员数字化支持会员等级、储值余额、积分、优惠券与消费流水所有资金变动可追溯。经营可视化为门店管理者提供预约量、营业额、会员增长、热门服务等统计报表。二、系统整体架构系统采用前后端分离架构前端使用 Vue3 Vite Element Plus 构建管理端与用户端页面后端使用 Spring Boot 提供 RESTful 接口MySQL 负责业务数据持久化Redis 用于缓存时段库存与登录令牌文件资源车辆照片、门店图片存储在对象存储中。flowchart TD A[Vue3 用户端 / 管理端] --|HTTP JWT| B[Spring Boot 应用] B -- C[预约服务模块] B -- D[会员与储值模块] B -- E[订单与支付模块] B -- F[统计报表模块] C -- G[(MySQL)] D -- G E -- G F -- G C -- H[(Redis 时段库存)] B -- I[对象存储]分层上后端遵循 Controller → Service → Mapper 的经典结构使用 MyBatis-Plus 简化单表 CRUD复杂统计查询通过自定义 XML 或注解 SQL 实现。接口统一返回ResultT包装体异常由全局异常处理器统一转换为业务错误码。三、技术选型层次技术说明前端框架Vue3 Vite组合式 API构建速度快UI 组件Element Plus管理端表格、表单、弹窗状态管理Pinia登录态、购物车式预约单后端框架Spring Boot 3.x自动装配、内嵌 Tomcat持久层MyBatis-Plus分页插件、逻辑删除数据库MySQL 8.0事务与索引支持完善缓存Redis时段库存、验证码、Token鉴权Spring Security JWT无状态认证区分角色四、数据库设计核心表围绕「用户—会员—服务—预约—订单」展开主要表结构如下sys_user账号、密码BCrypt、手机号、角色车主 / 店员 / 管理员。member会员卡号、等级、余额、积分、累计消费、状态。service_item服务名称、类型洗车 / 打蜡 / 内饰清洁 / 镀晶、价格、时长、适用车型。store与workstation门店与洗车工位工位决定同一时段的并发容量。appointment预约单号、会员、服务项目、门店、工位、开始时间、结束时间、状态。orders与order_item订单金额、支付方式、优惠券抵扣、核销状态。balance_log储值变动流水记录变动前后余额与业务来源。预约表需要在(workstation_id, start_time)上建立唯一索引作为并发场景下的最后一道防线防止两个请求同时通过校验后写入重复时段。五、后端核心实现5.1 预约时段校验预约创建的关键是「先校验、再落库」并且校验与落库必须在同一事务中完成。下面是一个简化的服务实现Service RequiredArgsConstructor public class AppointmentServiceImpl implements AppointmentService { private final AppointmentMapper appointmentMapper; private final WorkstationMapper workstationMapper; private final ServiceItemMapper serviceItemMapper; Override Transactional(rollbackFor Exception.class) public Long create(AppointmentCreateDTO dto) { ServiceItem item serviceItemMapper.selectById(dto.getServiceItemId()); if (item null) { throw new BizException(服务项目不存在); } LocalDateTime start dto.getStartTime(); LocalDateTime end start.plusMinutes(item.getDuration()); // 校验工位在该时段是否已被占用 int conflict appointmentMapper.countConflict( dto.getWorkstationId(), start, end); if (conflict amp;gt; 0) { throw new BizException(该时段已被预约请选择其他时间); } Appointment entity new Appointment(); entity.setMemberId(dto.getMemberId()); entity.setWorkstationId(dto.getWorkstationId()); entity.setServiceItemId(item.getId()); entity.setStartTime(start); entity.setEndTime(end); entity.setStatus(AppointmentStatus.PENDING); appointmentMapper.insert(entity); return entity.getId(); } }对应的冲突统计 SQL 使用半开区间判断重叠SELECT COUNT(1) FROM appointment WHERE workstation_id #{workstationId} AND status IN (PENDING, CONFIRMED) AND start_time #{end} AND end_time #{start};5.2 会员储值扣款储值扣款涉及余额与流水两张表必须保证原子性。推荐使用「条件更新 影响行数判断」的方式避免先查后改带来的并发超扣Transactional(rollbackFor Exception.class) public void deduct(Long memberId, BigDecimal amount, String bizNo) { int rows memberMapper.deductBalance(memberId, amount); if (rows 0) { throw new BizException(余额不足或会员状态异常); } BalanceLog log new BalanceLog(); log.setMemberId(memberId); log.setAmount(amount.negate()); log.setBizNo(bizNo); log.setType(BalanceType.CONSUME); balanceLogMapper.insert(log); }UPDATE member SET balance balance - #{amount}, update_time NOW() WHERE id #{memberId} AND balance #{amount} AND status 1;5.3 JWT 鉴权与角色控制登录成功后签发 JWT前端在请求头携带Authorization: Bearer token。后端通过过滤器解析令牌并写入SecurityContext接口上使用PreAuthorize区分角色权限PreAuthorize(hasRole(ADMIN)) GetMapping(/stats/overview) public ResultOverviewVO overview(RequestParam String month) { return Result.ok(reportService.overview(month)); }六、前端实现要点前端按角色拆分为两个入口车主端侧重预约流程与会员中心管理端侧重排班、核销与报表。路由使用vue-router的懒加载登录态与用户信息存放在 Pinia 中并通过路由守卫拦截未登录访问。import { defineStore } from pinia import { login, getProfile } from /api/auth export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , profile: null }), actions: { async login(form) { const { data } await login(form) this.token data.token localStorage.setItem(token, data.token) this.profile await getProfile() }, logout() { this.token this.profile null localStorage.removeItem(token) } } })预约页面采用「服务项目 → 门店与工位 → 日期时段 → 确认下单」的分步表单。时段列表由后端按工位容量实时计算返回前端只负责渲染可选与已满状态避免把排期逻辑复制到浏览器端造成不一致。七、关键业务难点与解决方案并发预约冲突数据库唯一索引兜底 事务内校验捕获唯一键冲突后返回友好提示。时段库存热点热门时段用 Redis 做预扣减落库失败时回补降低数据库瞬时压力。储值资金一致性所有余额变动只允许通过统一的服务方法执行禁止在其它业务中直接 update 余额字段。订单与预约状态联动预约状态机为「待确认 → 已确认 → 服务中 → 已完成 / 已取消」状态流转集中在服务层校验避免非法跳转。报表性能按月预聚合到统计表前端查询走聚合结果避免每次实时扫描大表。八、部署与运行后端使用 Maven 打包为可执行 jar前端执行npm run build产出静态资源交由 Nginx 托管并反向代理/api到后端服务。数据库与 Redis 建议使用 Docker Compose 一键启动便于本地开发与测试环境保持一致。services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: car_wash ports: - 3306:3306 redis: image: redis:7 ports: - 6379:6379九、总结本文围绕 Spring Boot Vue3 的汽车美容洗车预约会员系统梳理了需求边界、整体架构、数据库设计以及预约校验、储值扣款、鉴权等核心实现。实际落地时建议先把预约与会员两条主链路跑通再逐步补充优惠券、营销活动与多门店管理能力避免一次性铺开导致复杂度失控。
阅读完成 · 觉得有帮助?
咨询建站