ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

跑通SpringBoot+Vue+MyBatis美容院管理系统:从源码到部署的全解析

跑通SpringBoot+Vue+MyBatis美容院管理系统:从源码到部署的全解析 把一套基于 SpringBoot Vue MyBatis 架构的美容院管理系统从源码跑通到什么程度才算真正吃透了我最近刚好完整地把这套带 MySQL 数据库的企业级源码翻了一遍不只是启动起来看看页面而是把后端接口、前端交互、数据库表结构和线上部署全都走了一遍。从实际体验来说它不是一个只停留在空壳的模板而是把会员档案、预约排班、订单收银、套餐卡次、库存预警、员工提成和营收报表这些美容院日常经营里绕不开的模块都串起来了。如果你是做 Java 课程设计、毕业设计或者正准备接手一个中小门店的信息化项目这篇文章应该能帮你省掉读源码和踩坑的不少时间。下面我就按照实际开发中会遇到的顺序把这套系统的设计思路、核心代码、数据库细节、部署问题和二次开发方向拆开讲一遍。文章里所有配置和代码示例都是我在跑通这套源码时真正用到的写法你照着操作基本不会卡壳。1. 项目定位与技术选型为什么是这套组合1.1 这套系统到底解决了什么问题美容院的线下业务链条其实比表面看起来要长。顾客进门不是简单买一次服务而是要经历建档、预约、到店、划卡、购买产品、续充、评价这么一串过程。很多小门店还在用 Excel 手记不仅容易漏也根本没法统计哪个项目卖得好、哪个员工做了多少业绩。这套系统做的事情就是把这些散落的业务节点全部结构化。从功能上看会员模块要能管理档案、余额、等级、累计消费预约模块要能按项目和员工排班并且自动挡住时间冲突订单模块要能把服务项目、产品、卡次消耗都记清楚报表模块要能按日、按周、按月统计营收和客单价。这些不是独立功能而是靠一条完整的数据流串起来。源码里最大的优点就是预约、订单、会员余额这三块不是各写各的而是通过外键和状态字段形成闭环。很多学生在做类似题目时容易把重心放在“能增删改查”就行。但真正拿这套系统去用你会发现事务、时区、并发扣款、分页查询、登录失效这些细节才是决定项目能不能落地的关键。这套源码好就好在它把工程化的套路带进来了不是纯 CRUD 堆砌。1.2 为什么选 SpringBoot Vue MyBatis MySQL 这套技术组合有人会问我做这种管理系统用 Python 或者 PHP 不是更快吗但放在国内大部分课程设计、毕业设计和中小企业的实际情况里SpringBoot 就是 Java 后端最稳妥的选择。它把 Tomcat、Spring 容器、自动配置全打包好了写一个 Application 类就能启动部署时一个 jar 文件搞定不用像传统 SSM 那样配置一堆 XML。Vue 的好处是组件化清晰页面上的会员表格、预约日历、订单弹窗都能拆成独立组件维护。美容院系统的交互不算复杂但信息量大一个页面要同时展示很多状态Vue 的数据绑定比 jQuery 手工操作 DOM 省心太多。Element UI 组件库也能直接弥补后台管理系统对表格、表单、弹窗、表格树的需求开发速度非常快。MyBatis 在这一点上有不可替代的优势多条件动态查询。美容院的报表和筛选页经常出现“会员等级 服务项目 消费日期”这种任意组合MyBatis 的if标签可以很优雅地拼 SQL。如果你用 JPA/Hibernate反而需要花很多精力处理动态 Query 逻辑。MySQL 更不用说它稳定、轻量、社区资料丰富足够支撑这套系统未来很长一段时间的业务规模。1.3 前后端分离的工程结构与模块划分拿到源码后不要急着扔到 IDEA 里跑。先看目录这套源码大概率是分成 backend 和 frontend 两个独立工程。后端是标准的 Spring Boot Maven 结构controller、service、mapper、entity、common 分得很清楚前端是 Vue CLI 创建的工程views 下面按业务模块建文件夹api 目录里放所有接口请求方法。开发环境时前端 dev server 默认跑在 8081 端口后端跑在 8080 端口。这里有一个容易踩坑的地方如果前端直接用 axios 请求localhost:8080浏览器会因为端口不同产生跨域。解决方式是在 vue.config.js 里配置代理把/api前缀的请求转发到后端。很多同学以为跨域就得在后端写CrossOrigin其实开发阶段用 devServer.proxy 更干净后端也不容易被全局跨域配置污染。2. 数据库设计与核心业务模型2.1 核心表结构逐张拆解数据库是这套系统的地基我建议你花一天时间把所有建表 SQL 看一遍。下面这张表把核心业务表的功能列出来对应关系一目了然表名核心作用关键字段member会员档案与余额phone 唯一、balance、level、total_consumeemployee员工基础信息name、position、status、commission_rateservice_item服务项目定义name、price、duration、commission_rateappointment预约记录member_id、employee_id、service_item_id、start_time、end_time、statusorders订单主表order_no、member_id、amount、pay_type、appointment_idorder_item订单明细order_id、type、item_id、priceaccount_log余额流水member_id、change_amount、balance_after、reasoninventory产品库存product_name、stock、warning_line这些表不是随随便便设计的。比如 member 表里手机号必须加唯一索引因为它是会员卡号和登录账号balance 用 decimal(10,2) 而不是 float金额计算最怕精度丢失。service_item 表里的 duration 一定要存分钟数这个字段后面在预约冲突判断里会用到。建表语句里表名和字段名尽量都用下划线风格并且写 COMMENT。不要小看注释后面自己维护时会非常感激当时写了这些的人。下面这段我稍微精简过的 member 建表语句可以作为新建项目的参考模板CREATE TABLE member ( id bigint NOT NULL AUTO_INCREMENT, phone varchar(20) NOT NULL COMMENT 手机号登录凭证, name varchar(50) DEFAULT COMMENT 会员姓名, level tinyint DEFAULT 0 COMMENT 等级0普通 1银卡 2金卡 3钻石, balance decimal(10,2) DEFAULT 0.00 COMMENT 账户余额, total_consume decimal(10,2) DEFAULT 0.00 COMMENT 累计消费金额, created_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT会员表;注意字符集一定要用 utf8mb4。很多人只写 utf8结果用户输入一个生僻字或者 emoji 表情存储就直接报错或者乱码。数据库编码问题是最容易在后期爆雷的。2.2 表关联关系与数据一致性要点表与表之间的关系并不复杂。member 是主数据orders 通过 member_id 关联orders 通过 order_item 关联具体服务或产品appointment 同时关联 member、employee、service_item 三方是典型的多对一关系。逻辑上一次到店消费的完整链路是先建预约到店后核销预约并创建订单订单完成后再扣减会员卡次或余额。这里最重要的设计思想是预约和订单是两个环节千万别混在一张表里。预约解决的是“谁、什么时间、找哪位员工、做什么项目”的排班问题订单解决的是“消费了什么、应收多少、怎么支付”的交易问题。把这两个概念拆开后续做日程表、排班表、员工业绩统计都会轻松很多。数据一致性方面我强烈建议你在二次开发时补上 account_log 流水表。因为每次充值、消费、退款都会改变 member.balance如果只更新余额字段以后查账会发现对不上。只要余额变动就插入一条流水记录变动前后的值这样出问题可以直接回溯。源码里如果本身已经有这张表那更好如果没有自己加一张也很简单只是需要让所有修改余额的地方都走同一个 service 方法不要到处直接 update member。2.3 初始化数据与索引设计实战初始化脚本也是项目的一部分。管理员的密码不能以明文形式写在 SQL 里必须是 BCrypt 加密后的字符串。哪怕是演示项目我也建议你从第一天开始保持这个习惯不然代码泄露出去后台分分钟被登录。索引不用建太多但要建在刀刃上。手机号字段建唯一索引用于登录和防重复建档预约表的 appointment_time 建普通索引因为日报表每天都要查当天预约orders 的 create_time 建索引营业额统计按天分组时才不会全表扫描order_item 的 order_id 建索引查订单明细会非常快。用 MySQL 的EXPLAIN SELECT ...可以直观看到查询是否走了索引这是调优最基础的手段。3. SpringBoot 后端实现关键点3.1 依赖清单与代码分层后端 pom.xml 里应该包含这些核心依赖spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、pagehelper-spring-boot-starter、lombok、java-jwt 或 jjwt。有些项目还会引入 spring-boot-starter-validation 来做参数校验。这里我建议你核实一下版本号尽量用 Spring Boot 2.7.x 和对应的 mybatis starter不要盲目上新版本否则插件配置写法和源码里的演示代码不一致。代码分层上这套源码是标准的 Controller-Service-Mapper 三层。Controller 只负责接收参数和返回统一 Result 对象Service 写真正的业务逻辑Mapper 只做数据库访问。我见过不少项目把业务逻辑写在 Controller 里改动一次就要动接口层非常痛苦。分层最核心的意义是让每个类只做一类事后面无论是改 SQL 还是换前端页面影响范围都能控制住。业务逻辑里凡是涉及多张表写入的方法必须在 Service 方法上加Transactional。举个例子会员充值时要做两件事更新 member 表的 balance插入 account_log 流水。如果第二步失败第一步的余额变更就得回滚否则账就平不了。事务默认只回滚 RuntimeException所以不要在业务方法里随意 catch 异常后吞掉这样事务会失效。3.2 MyBatis 动态 SQL 与 PageHelper 分页配置MyBatis 在这里最常被用到的玩法是动态 SQL。会员列表页一般有搜索条件手机号、等级、注册时间。如果用字符串拼接 SQL 很容易碰到引号问题而用 mapper XML 的if标签就非常安全。下面这段 SQL 就是典型的写法select idselectMemberByCondition resultTypecom.example.entity.Member SELECT * FROM member where if testphone ! null and phone ! AND phone LIKE CONCAT(%, #{phone}, %) /if if testlevel ! null AND level #{level} /if /where ORDER BY id DESC /selectwhere标签会自动去掉第一个多出来的 AND这个细节比手写WHERE 11要好一截。分页推荐使用 PageHelper。在 Spring Boot 中如果已经引用了 pagehelper-spring-boot-starteryml 里可以这么配pagehelper: helper-dialect: mysql reasonable: truereasonable: true的作用是当页码小于 1 时自动显示第一页大于总页数时自动显示最后一页避免出现空白列表。代码里使用分页时要注意一个硬性规则PageHelper.startPage 后必须紧跟着下一条查询语句才会被拦截生效。如果中间插入了其他查询分页会错乱。这是新手最常见的使用错误。3.3 JWT 登录鉴权与全局异常处理前后端分离项目的登录状态不能靠 Session因为 Vue 页面和后端接口通常不在一块。源码里一般用 JWT 生成 token后端通过拦截器校验。我实际配置的时候会写一个 JwtInterceptor 类实现 HandlerInterceptor 的 preHandle 方法从请求头 Authorization 里取 token解析成功后把 userId 放到 request attribute 里供后续使用。没带 token 或者 token 过期就返回 401 和一段 JSON。登录接口本身和静态资源要放行所以拦截器注册时需要排除/api/auth/login、/error等路径。这个如果漏掉会出现自己人却被自己人拦截的尴尬情况。我建议把放行路径集中写在配置类里方便后面维护。全局异常处理用RestControllerAdvice。推荐拆三个方法一个处理自定义业务异常比如“余额不足”“预约时间冲突”一个处理参数校验异常一个处理兜底的 Exception。返回体统一是 Result 对象包含 code、message、data。这样前端就能统一识别错误并弹出提示。千万不要让系统默认的 500 异常页直接暴露在用户面前那样既丑又泄露内部细节。3.4 预约冲突判断与会员充值事务预约模块是整个系统的难点也是最容易被做成“假功能”的地方。判断员工是否空闲不能只看有没有同一个时间点的记录而要看时间段是否重叠。假设预约从 start_time 开始持续 duration 分钟那么 end_time 等于 start_time 加 duration。新预约与已有预约冲突的条件是新开始的 time 小于已有预约的结束时间并且新结束的 time 大于已有预约的开始时间。对应 SQL 写法SELECT COUNT(*) FROM appointment WHERE employee_id #{employeeId} AND status IN (0, 1) AND #{endTime} start_time AND start_time #{endTime}这段 SQL 如果查出来 count 大于 0就不能插入新预约。注意这里 status 要过滤因为已取消、已完成的预约不应该参与冲突判断。Time 字段建议用 datetime不要只存日期否则做不了精确的排班判断。会员充值接口要展示事务的典型用法。我先用原子的 SQL 更新余额再插入流水Transactional(rollbackFor Exception.class) public void recharge(Long memberId, BigDecimal amount) { int count memberMapper.increaseBalance(memberId, amount); if (count 0) { throw new BusinessException(会员不存在); } accountLogMapper.insert(memberId, amount, BalanceChangeType.RECHARGE); }increaseBalance 对应的 SQL 是UPDATE member SET balance balance #{amount} WHERE id #{memberId}这种写法天然避免“先查后改”的并发问题。如果不写事务第二步插入流水失败钱就凭空多了。4. Vue 前端实现与交互细节4.1 路由守卫与页面权限控制前端第一个要处理的是登录跳转逻辑。vue-router 的全局前置守卫里写一段判断没有 token 就强制跳到登录页。这是后台管理系统最基础的拦截。路由定义时可以在 meta 里存放角色信息比如管理员才能访问员工管理页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })这段代码非常简单但已经能解决大部分页面白屏问题。真正企业级项目会把角色、菜单权限都做成动态渲染就是从后端接口拿当前用户能访问的菜单列表再动态生成路由。这套源码如果只做了静态路由你可以在二次开发时慢慢改成动态的不用一步到位。4.2 Axios 拦截器与统一错误处理前端请求层直接决定体验。我建议所有接口请求都走同一个 axios 实例而不是每个页面单独 import axios。原因是能在拦截器里统一做三件事加 token、解析业务错误码、处理 401 跳登录。service.interceptors.response.use(response { const res response.data if (res.code ! 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.clear() router.push(/login) } Message.error(error.message) return Promise.reject(error) })有人会问为什么 code 不等于 200 还要 reject而不是直接把数据返回因为绝大多数页面只需要成功的数据失败情况统一抛到调用方再由调用方决定是否需要 special 处理。这套写法能减少很多if (data.code 200) ... else ...的重复代码。4.3 ECharts 报表展示与表单校验实战管理系统里的报表页一定要做不然到答辩时没有亮点。ECharts 图标库接入很简单后端接口返回一个数组前端 foreach 推进 option 里即可。比较常见的是年度营业额折线图和项目消费排行条形图。写报表组件时要注意一个真实存在的坑容器 div 必须显式设置宽度和高度否则 ECharts 初始化时拿不到尺寸图表不显示。如果图表是在弹窗里渲染必须用this.$nextTick等弹窗内容挂载后再 init。表单校验方面Element UI 的 rules 很好用。手机号校验用正则/^1[3-9]\d{9}$/金额校验要限制两位小数。同时提交按钮要绑定this.$refs.form.validate()否则即便填错了也能提交成功。还有一个容易忽略的点前端精度和金额不要用 JavaScript 浮点数直接计算例如 0.1 0.2 会得到 0.30000000000000004。前端只是展示真正的金额计算与合法校验必须在后端完成。5. 部署上线与常见问题排查5.1 从源码到生产环境的完整打包步骤本地跑通后离上线就差打包这一步。后端正向操作很简单在 pom.xml 所在目录执行mvn clean package -DskipTests然后把 target 目录下生成的 jar 传到服务器准备 application-prod.yml 配置好生产数据库地址启动时指定 profilejava -jar beauty-admin.jar --spring.profiles.activeprod前端先修改.env.production文件中的VUE_APP_BASE_API比如设为/api再执行npm run build构建完成后会生成 dist 目录把这个目录放到 Nginx 配置的 web 根路径下再配置反向代理将/api的请求转发给后端的 8080 端口。Nginx 配置片段如下location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这里最核心的思路是尽量减少跨域前端请求的地址看起来和页面域名一致Nginx 在服务端转发。生产环境尽量不要让后端暴露 CORS 全局允许风险太大。5.2 上线后最容易踩的五个坑第一个坑是时区。MySQL 连接串里必须带上serverTimezoneAsia/Shanghai否则新版驱动会报链路错误。如果部署后发现所有时间都差了 8 小时先查 JVM 时区启动命令加-Duser.timezoneAsia/Shanghai。第二个坑是数据库编码。建库时就用utf8mb4否则汉字没问题用户输入 emoji 就会报错。第三个坑是前端路由 history 模式刷新 404。Nginx 需要追加一行try_files $uri $uri/ /index.html;不加这一行用户只要按一下 F5页面就是 404。第四个坑是数据库连接池空闲超时。如果系统隔了一夜没人访问第二天第一次请求可能报“Communications link failure”。解决办法是在 Hikari 配置里加connection-test-query: SELECT 1并合理设置空闲超时时间。第五个坑是账号密码等敏感信息硬编码在 yml 里。虽然这个项目是教学用途但你一旦上线就要把密码挪到环境变量或配置中心。安全习惯越早养成越好。5.3 数据库备份与系统安全加固上线后的数据是无价的每天至少要自动备份一次。可以用 crontab 执行定时任务0 2 * * * mysqldump -u beauty_user -p密码 beauty /backup/beauty_$(date \%Y\%m\%d).sql注意这里%在 crontab 里要写成\%否则不会按天生成文件名。建议保留最近七天加一行清理命令删除过期备份。安全方面我一直坚持几个底线后台登录必须加图形验证码防止暴力破解数据库账号不要用 root新建一个只授权业务库的账号密码不存明文统一用 BCrypt 加密文件上传接口必须校验文件类型和文件头防止有人传恶意脚本。这套系统涉及客户信息和资金交易安全不是可选项是必须做的事。6. 二次开发与扩展方向6.1 小程序端与移动端能力扩展这套系统的后端接口已经按 JSON 格式提供天然可以给微信小程序复用。你只需要在小程序里配置合法的域名使用wx.request调用后端/api接口登录时把后端返回的 token 存入wx.setStorageSync。小程序端可以做一个顾客入口让会员查看余额、卡次、预约记录并在线提交预约申请。改造点在于小程序没有浏览器 localStorage接口请求需要重新封装一个 request 函数但逻辑和前端拦截器完全一致。如果要做微信登录还需要增加一个根据 openid 查找或创建会员的接口并在 member 表新增 openid 字段。这个扩展不难工作量主要集中在小程序的界面适配。6.2 营销活动与消息通知集成美容院最关心的其实是复购和到店率。可以在这套系统上增加两个增量功能一是短信提醒预约前一天自动给会员发提醒消息二是储值赠送规则比如“充 1000 送 100”在充值接口里判断规则表后追加赠送金额。这两个功能都离不开定时任务。Spring Boot 可以引入 spring-boot-starter-quartz每天固定时间扫描 appointment 表把 start_time 在第二天的预约查出来批量调用短信服务商接口。营销活动千万不要在原有代码里到处写 if else推荐单独建一张 promotion_rule 表配置好满减条件只改配置不动代码这样以后非技术人员也能在后台维护活动。我个人在实际操作里最大的体会是拿到这套源码别急着改界面一定要先把数据库表关系画出来再顺着一次“预约→到店→下单→扣款”的流程把代码走一遍。三次下来你对 SpringBoot、Vue、MyBatis、MySQL 这整套组合的把握比单独看十个教程都强。遇到问题优先看启动日志和控制台报错不要靠猜。这套项目的下限是能跑通上限完全取决于你在表结构和接口规范上愿意投入多少心思。
返回列表