做门店管理系统的这些年,我总结出一个规律:凡是老板能坚持用超过三个月的系统,往往不是功能最多的那套,而是把“钱”和“账”算得最清楚的那套。美发门店尤其明显——会员储值、划卡、员工提成,每一笔都牵动着老板的神经。最近整理了一套美发门店管理系统源码,后端SpringBoot、前端Vue、数据库MySQL,环境配置好就能直接运行。
名字里虽然有两个“管理”,听起来有点啰嗦,但内容很务实:会员储值、预约排班、员工提成、商品库存、营业报表全都覆盖。它适合两类人:一类是学完前后端分离、想拿完整项目练手的开发者;另一类是准备给中小型美发店做信息化改造、需要一套能快速交付的私有化方案的朋友。这篇文章我打算从这套系统的业务设计、数据模型、前后端实现、直接运行步骤到二次开发建议,一条龙讲清楚。
1. 项目整体设计与技术选型思路
1.1 美发门店的业务痛点,往往比技术难点更棘手
美发店的日常运营和互联网公司的“业务”完全是两个世界。前台小妹用本子记预约,老板用Excel记会员余额,烫染技师凭感觉算提成——这种场景在中小型美发店里太常见了。我见过一家开了五年的店,会员充值记录全靠微信聊天记录翻,月底对账能对到凌晨。做美发门店管理系统,本质上是把“人、卡、单、货、账”这五条线全部数字化:谁是谁的会员、卡里还剩多少钱、今天谁做了什么项目、这个月每个发型师做了多少业绩、店里还有多少染膏库存,这些信息不再依赖人脑记忆,而是落在数据库里,随时可查。
这套系统建议把自己代入三个角色来看需求:老板想看今天的营收、实收和欠账;收银员要快速完成开卡、充值、划卡、卖产品;发型师要在自己的账号下看到预约列表和提成明细。如果这三个角色用起来都顺手,系统就算成功了一半。所以不管源码结构怎么变化,业务模块一定要围绕这三个角色来铺开。
1.2 为什么这套技术栈最“稳”
市面上门店管理系统其实有很多现成的SaaS产品,但如果你要的是“可定制、可二次开发、数据自己掌握”的私有化部署版本,那用开源技术栈自己搭几乎是唯一选择。
SpringBoot的好处有不少:自动配置、内置Tomcat、生态成熟,配合Maven管理依赖,把原来Spring那些繁琐的XML配置全干掉了。Vue在前端领域占有率极高,组件化开发让页面复用变得很舒服,Element UI一套组件库就能把后台管理界面的质感拉起来。MySQL就更不用说了,开源免费、普及度高,中小型美发门店一天几千条流水,MySQL稳稳扛住,完全没必要一上来就上Oracle或者分布式数据库。
我见过有人用Python Flask写这种系统,也有人用PHP写,写都能写,但落到“客户现场改需求、后续找人维护、代码交接”这些现实问题时,SpringBoot+Spring家族在国内的招聘市场和使用习惯上是最省事的。Vue同理,会Vue的前端工程师比会React的多得多,在三四线城市也容易找到人接手。技术选型有时候不是选最先进的,而是选最不担心没人接盘的。
1.3 功能模块拆解:一套好用的美发系统该覆盖什么
从功能模块上看,这套系统大致可以分为六个大块:
- 系统管理:登录认证、用户管理、角色权限分配,给老板、店长、收银员、发型师等不同角色分配不同菜单和按钮权限。
- 会员管理:会员信息登记、会员卡开卡/续卡、储值充值、通过卡号/手机号快速查找、余额变动明细。
- 预约与排队管理:顾客提前预约某个发型师,选择服务项目和时间段,发型师端可以看到当天预约列表。
- 服务与订单管理:开单、选服务项目、选负责发型师、计算消费金额、收款或划卡,生成消费明细。
- 商品与库存管理:洗发水、护发素、染膏、烫发剂等商品的进销存,以及美发套餐的售卖与次数核销。
- 营业报表:日营收、月营收、会员储值余额汇总、员工业绩排名、商品销售统计。
这六个模块基本覆盖了一家美发店从开门到打烊的所有业务流程。很多刚入行的同学容易把系统设计成“只有会员管理”或者“只有收银”,做出来老板用两天就觉得不够。源码里把预约、提成、库存这些周边能力配齐,才算是能真正落地的门店管理工具。
2. 核心功能与数据模型设计解析
2.1 会员管理与储值卡:钱的流向要清清楚楚
美发店的会员玩法其实蛮有行业特色的。常见的储值方式有三种:一是充值金额赠送金额,比如充1000送200;二是充值享折扣,充2000以后剪发打7折;三是纯次数卡,比如办一张“剪发年卡”,一年内剪10次,按次数消卡。这三种玩法对应的字段设计完全不同,如果只用一个balance字段,很容易在送金额和次数卡上打架。
一般这套源码里的会员表会包含:会员卡号、姓名、手机号、性别、生日、会员等级、卡内余额、累计消费、开卡时间和状态。充值记录表则需要记录充值金额、赠送金额、支付方式、操作员工、操作时间。我特别想提醒的是:储值余额的变动一定要留流水,不能用触发器硬改余额了事。因为门店纠纷最多的就是“我卡里到底还有多少钱”,只要余额变更都有对应的流水记录,客户投诉时查明细一查一个准,账目也经得起对账。另外会员到期时间、锁定状态这两个字段也建议保留,别等出现“过期卡还能继续划”这种问题再去补逻辑。
会员卡号的生成策略也值得看一眼。有些版本用数据库自增ID,有些用时间戳加随机数,实际使用中我更喜欢用规则化卡号:前缀+日期+序号,比如VIP20250618001。这样前台在系统里直接输卡号,或者用键盘扫码枪扫一下,就能快速定位会员,比输手机号快得多,尤其在高峰期排队的时候,体验差异很明显。
2.2 预约、排班与员工提成:门店运转的齿轮
预约功能是美发店的刚需,尤其是周末,好的发型师档期能排满一整天。这里的核心数据结构是预约单:顾客名称/会员卡号、预约项目、预约技师、预约日期、时间段、备注,再到状态。状态字段一定要够细,比如待确认、已确认、已完成、已取消、已到店。前台确认之后,发型师在个人工作台就能看到今天的日程,到了时间点提醒自己。
预约模块还有一个容易被忽视的并发问题:同一个发型师在同一天同一个时间段,不能被两个顾客同时预约。如果只是前端做了选择时间控件,后端接口却不去校验,就可能出现“双重预约”。所以后端在写入预约时,最好对employee_id、appoint_date、time_slot做一个唯一约束,或者在Service层加锁判断。这个点源码里可能处理得比较基础,但二次开发时务必要注意。
员工提成则是另一个容易出bug的地方。美发店的提成规则很碎:洗剪吹可能按固定金额提成,烫染可能按业绩百分比提成,套餐不同提成比例也不同,甚至办卡充值也有销售提成。如果只在业务代码里写死提成比例,后期换一个提成方案就要改代码。更合理的做法是把提成规则做成配置表:服务项目、员工等级、提成类型(固定金额/百分比)、提成比例/金额、生效时间。订单完成后,系统自动根据配置表计算当笔提成,月底按员工汇总。源码里是不是完全按配置表设计的不好说,但如果你拿到的版本还是硬编码,二次开发时优先把这个模块重构掉,后续会省心很多。
2.3 商品库存与套餐:别让“卖着卖着就没了”
美发店不只是卖服务,还卖产品,染膏烫发剂更是消耗大头。商品库存模块基础是商品档案:编码、名称、类别、进价、售价、库存单位、最低库存量、当前库存。库存变动要记录入库单和出库单,例如采购入库、服务耗用出库、零售出库、盘点调整,每一条都要有来源,不然月底库存和实际对不上。
我见过不少做门店系统的团队,把库存做得像财务软件一样复杂——出入库单、审批流、调拨单,全搞出来。实际上中小型美发店根本没有专职库管,你要给他那么复杂的库存管理流程,他根本不用。这里比较务实的做法是:前台卖出商品或服务配方里包含耗材时,系统自动扣减库存;低于最低库存量时,在首页给个提醒就好。把入口做得越少越好,老板才愿意用。
套餐的处理在美发行业也有点特殊。套餐本身可以看作一个“服务包”,比如“新客体验套餐:98元含剪发一次+护理一次”。套餐订单生成后,关联多条服务明细,每次消费时按次数核销其中一项。为了支持“套餐剩余次数”这个显示,订单明细里要有类型区分:单次服务、套餐、套餐子项,以及每次核销的记录表。不少开发者在设计时容易忽略套餐子项的核销表,导致套餐做完一次后状态就乱了。这个模块建议大家在阅读源码时重点看。
2.4 数据库表结构:一张图看懂核心表
我不贴完整建表语句,但把核心表列出来,大家对照源码看会容易得多。
| 表名 | 主要字段 | 作用 |
|---|---|---|
| sys_user | id, username, password, real_name, role_id, store_id | 系统用户与登录 |
| sys_role / sys_menu | 角色菜单关联表 | 权限控制 |
| member | id, card_no, name, phone, balance, total_consume, status | 会员基础档案 |
| member_card | id, member_id, card_type, card_balance, expired_time | 会员卡(可多卡) |
| recharge_record | id, member_id, amount, gift_amount, pay_method, operator_id | 储值/充值流水 |
| appointment | id, member_id, employee_id, service_id, appoint_time, status | 预约记录 |
| service_item | id, name, category_id, price, commission_type, commission_value | 服务项目与提成规则 |
| sale_order / order_item | 订单主表 + 订单明细 | 服务/商品销售记录 |
| product | id, name, category_id, purchase_price, sale_price, stock | 商品档案 |
| stock_in_out | id, product_id, type, quantity, order_id | 库存出入库流水 |
| consume_record | id, member_id, card_id, amount, order_id | 会员卡扣费流水 |
这些表设计出来之后,整个系统的数据结构就有了骨架。我习惯先把ER关系理清楚再写代码,特别是member、member_card、recharge_record、consume_record这四张表的关系,是会员模块的核心闭环。建议打开数据库客户端把这几张表的关系画一遍,看得比代码快。
3. 前后端分离的实操落地
3.1 后端SpringBoot工程:三层结构怎么搭
拿到源码后,先别急着点启动,花半小时扫一遍包结构。常规的SpringBoot后端是Controller-Service-Mapper三层:
- Controller层:接收HTTP请求,做参数校验和基础封装,不写业务逻辑。
- Service层:业务核心,处理事务、业务规则判断、跨表操作。
- Mapper层:与数据库交互,通常是MyBatis或MyBatis-Plus的Mapper接口。
以会员充值为例,Controller接收/member/recharge请求,Service层里要做几件事:校验会员状态、生成充值记录、更新会员卡余额、如果涉及赠送还要更新赠送金额、记录操作日志。这四个动作必须在同一个事务里,要么全成功要么全回滚,否则就会出“钱扣了余额没涨”的事故。源码里一般会在Service方法上加@Transactional注解,大家读代码时可以留意一下。
另外,统一返回体是前后端对暗号的基础。后端每个接口都应该返回固定的JSON结构,一般是这样:
{ "code": 200, "message": "success", "data": {} }如果返回的格式五花八门,前端axios就很难统一处理错误。这套源码里通常会封装一个Result类,包括success()、error()等静态方法,用起来比较方便。登录模块一般用JWT令牌,拦截器里校验token,后端接口的/login路径要放行,其他路径统一拦截。密码存储记得用BCrypt加密,千万别明文存在数据库里——我确实见过有项目这么做,老板自己都能进数据库把所有人的密码看得清清楚楚,完全是安全隐患。
3.2 前端Vue页面:从登录到报表的完整链路
Vue这边一般是标准的管理后台结构。路由配置里,登录页、工作台、会员管理、预约管理、商品管理、报表中心是独立页面。使用Vue Router时建议配置路由守卫:判断本地token是否存在,不存在就跳转到登录页。这一步很关键,很多新手写前端时只想着展示页面,忘了登录态控制,结果页面能直接打开、接口全报401。
axios封装也是一个值得学习的点。一般会创建一个request.js,在里面设置baseURL、请求拦截器(把token加到header里)、响应拦截器(判断code是否为200,不是就统一弹提示并跳转登录)。这么做的目的是让每个业务页面不用重复写错误处理代码。比如说会员管理页里点“充值”,弹窗提交后只需要拿到成功结果,至于401、500、余额不足这类失败场景,拦截器已经统一弹了提示,页面里不需要到处写try catch。
页面交互上,会员管理页就是典型的CRUD:表格展示会员列表,搜索框按手机号或卡号查询,新增/编辑用弹窗表单,充值操作单独一个弹窗。预约页则多了时间线视图,前端拉取当天预约列表按时间段排列。报表页用Element UI的表格配合ECharts画折线和柱状图。这套组合做管理后台非常顺手,也是Vue在国内企业应用里最常见的形态。如果源码里还做了动态菜单——根据登录用户的角色,从后端拿菜单树然后动态生成路由,那这部分代码值得重点学,以后做任何后台系统都用得上。
3.3 接口返回与状态码:前后端怎么对暗号
一个容易被忽略的细节是HTTP状态码和业务状态码的区分。有的团队习惯直接用HTTP 200表示成功,500表示失败,这样前端拦截起来很省事,但业务层面的错误(比如余额不足、会员已锁定)也会被HTTP 500吞掉,变得不好区分。更推荐的做法是:HTTP层只表达网络和服务是否可用,业务结果统一放JSON里的code字段。例如:
| code | 含义 |
|---|---|
| 200 | 业务成功 |
| 400 | 参数错误 |
| 401 | 未登录或登录过期 |
| 403 | 无权限 |
| 500 | 服务器内部错误 |
| 1001 | 会员余额不足 |
| 1002 | 会员状态异常 |
前端axios响应拦截器里先判断HTTP状态,再判断业务code,两个维度分层处理。这套源码如果你拿到,可能不是严格这样做的,但建议在二次开发时统一规范。特别是“会员余额不足”这类业务错误,如果用HTTP 500返回,前端就没办法针对性地提示“余额不足,请先充值”,只能显示一句“系统错误”,用户体验就很差。
4. 直接把项目跑起来的完整步骤
4.1 环境准备:装对版本能省一半时间
网上关于“为什么我启动失败”的问题,十个里有七个是环境版本不对。以这套技术栈的常见版本组合为例,我建议这样配:
- JDK 1.8(不要用JDK 17直接跑老项目,除非源码明确升级过)
- Maven 3.6.x 或 3.8.x
- Node.js 14 LTS 或 16 LTS(Vue 2项目不要用最新的Node 20+,npm install容易报错)
- MySQL 5.7 或 8.0(8.0需要留意驱动版本和时区设置)
- IDE:后端用IntelliJ IDEA,前端可以用VSCode或IDEA都行
安装顺序上先把MySQL装好并跑起来,然后创建数据库执行SQL脚本。MySQL 8.0默认加密方式如果用的caching_sha2_password,老版本JDBC驱动连不上,建议在创建用户时指定mysql_native_password,或者在pom.xml里把mysql-connector-java升到8.0.x以上。这一步属于经典坑,先预防住。
4.2 后端启动:配置文件里的几个坑
后端启动前先看application.yml或application.properties。最关键的三个配置都在数据源上:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hair_salon?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver注意几个细节:serverTimezone=Asia/Shanghai不加的话,MySQL 8.0会报时区错误;characterEncoding=utf8最好改成utf8mb4,因为会员昵称、备注里真有人会存emoji,utf8存不下。改完配置后,在IDEA里直接运行主类HairSalonApplication,控制台看到Started就说明后端起来了。
如果启动时报数据库连接失败,先别怀疑代码,用Navicat或命令行试一下能不能连上同一个库,八成是密码、端口或驱动版本的问题。还有一个很容易踩的坑:项目里可能配了spring.profiles.active=dev,而dev环境的配置跟本地不一致,这时候要检查是不是多环境配置文件放错了位置。
4.3 前端启动:npm那点事
前端项目目录下执行:
npm install npm run servenpm install慢是正常的,可以换镜像源:
npm config set registry https://registry.npmmirror.com如果项目里有package-lock.json,安装时可能会锁定旧版本依赖,遇到node和依赖不兼容的问题,可以删掉node_modules和package-lock.json重新安装。启动后访问前端页面,但前端默认端口也是8080的话会跟后端冲突。一般会在vue.config.js里把devServer端口改成8081或8090,并配置代理:
devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这样前端请求/api/xxx就会自动转发到后端,避开跨域问题。这也是前后端分离联调时最常用的方式,比在每台机器上单独设置CORS头更省心。我特别建议前端启动后先打开浏览器控制台的Network面板,随便点一个接口看请求有没有转发成功,不要只看页面有没有出来。
4.4 常见启动报错速查表
我把自己跑这种项目踩过的坑整理成表格,遇到报错可以对照排查。
| 报错现象 | 常见原因 | 解决办法 |
|---|---|---|
| 后端启动时报数据库连接失败 | 密码错了、MySQL没启动、端口不对 | 用客户端工具先测连接,确认库名密码 |
| Access denied for user 'root' | 用户权限问题 | 检查MySQL用户授权,或改配置文件账号 |
| 前端npm install报ERESOLVE依赖冲突 | Node版本过高或依赖锁冲突 | 换Node 16,删除lock和node_modules重装 |
| 前端访问接口跨域 | 前端和后端端口不一致 | 配置vue.config.js里的proxy代理 |
| 登录后页面空白 | 路由守卫或token处理异常 | 打开浏览器控制台看Network,定位是401还是JS报错 |
| 中文乱码 | 数据库字符集或连接字符集不对 | 库表统一utf8mb4,连接串加上characterEncoding |
| 端口被占用 | 上一次启动没关干净 | netstat -ano找端口进程并结束 |
这些坑单独看都不难,但卡住的时候特别耗时间。建议按顺序来:先确认数据库,再启动后端,最后启动前端,逐步排查缩小范围。别一上来就全项目跑,那样出问题都分不清是哪一层的锅。
5. 源码学习与二次开发建议
5.1 从这套源码里能学到什么
对想练手的人来说,这套美发门店管理系统其实是很好的“完整工程”示例,因为它不是demo,而是把很多真实业务场景串起来了。你能学到的东西包括:
- 一个完整的SpringBoot项目结构长什么样,包名怎么划分、配置怎么分离。
- 后端如何把业务规则落到Service层,而不是全堆在Controller里。
- 前端Vue Router和axios拦截器在真实项目里的配合方式。
- 数据库表之间如何通过逻辑关联组织,而不只是理论上的ER图。
- 一套管理后台从登录到报表的完整链路。
我特别建议初学者拿到源码后先跑通,再试着改一个简单功能,比如“给会员列表加一个按会员等级筛选的下拉框”。不要一上来就想看懂每一行代码,先把数据流走通,再去啃细节,效率会高很多。技术面试时聊项目,能讲清楚“为什么要设计充值流水表”“提成怎么算”“预约怎么防冲突”,比背一百道八股文都管用。
5.2 二次开发往哪个方向扩
这套系统如果要做私有化交付,一般有几个高频的扩展方向:
- 小程序端:很多美发店希望顾客能自己在微信小程序上预约、查余额。后端接口可以复用,前端单独写一个uni-app或原生小程序项目,通过openid关联会员卡号。
- 短信通知:预约成功、到店提醒、余额不足提醒,接入云短信服务,需要加一个短信模板配置表和发送日志表。
- 连锁门店:给表结构加store_id字段,把库存、员工、订单全部按门店隔离,还要加跨店查询报表的权限。
- 营销活动:拼团、秒杀、到期提醒、沉睡会员召回,这些运营功能本质上是基于会员数据和消费记录再加工。
- 数据大屏:把营业额、客单量、活跃会员数用大屏方式投到门店电视上,前端用ECharts或DataV就能做。
如果目标客户是连锁店,还有一个必须考虑的点:多租户权限和数据隔离怎么做。是每个门店单独一套库,还是共库分店ID?两种方案各有取舍,共库分店ID维护容易、但跨店报表要小心写错查询条件;分库则隔离彻底、但代码维护成本高。大多数中小型项目选择共库分店ID,这是我在实际交付中比较推荐起步的方式。
5.3 上线部署:从开发机到服务器
最后聊一下如何把项目从本地搬到服务器。后端打包执行:
mvn clean package -DskipTests拿到target目录下的jar包后,放到服务器上运行:
nohup java -jar hair-salon.jar --spring.profiles.active=prod > app.log 2>&1 &前端打包执行:
npm run build生成dist目录,里面是纯静态文件,交给Nginx托管。Nginx同时还要承担反向代理的角色,把/api请求转发到SpringBoot端口:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/hair-salon; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }数据库上线前不用全量导入演示数据,但要保证建表脚本执行完整,并修改默认密码、创建专门的业务账号。别忘了定期备份MySQL,门店一天的数据损失老板都受不了。我见过有团队上线前不设备份,结果一次误操作把会员表清掉的真实案例,最后只能靠现金流水和回忆补录,惊心动魄。
上线后还有一件事很容易被忽略:日志。后端日志至少要有info级别,并做按天滚动。出了问题没有日志等于没有眼睛。我之前接手过一套没有日志输出的老系统,线上出问题只能靠猜,后来花了三天时间把所有关键业务节点补上日志,再排查问题时效率完全不一样。
我个人在实际操作中的体会是:美发店老板不一定懂技术,但他们一定懂自己的店该怎么管。系统做得再花哨,如果会员余额对不上账、月底给员工算错提成,老板下个月就不会再用。反过来,只要数据准、操作快、报表一眼能看到今天赚多少,哪怕界面朴素一点,他们也愿意天天用。如果你手上刚好也有一份类似的源码,别急着追求高大上的技术,先把会员、订单、流水这三条数据链路理清楚,这比任何炫技都管用。
最后再分享一个小习惯:拿到任何一套可运行的源码,我建议先往数据库里塞一套模拟数据,把一个月的营业流水造出来,再在界面里一个模块一个模块地过。这样既能快速暴露设计缺陷,也是给客户演示时最有力的说服方式。等到系统逻辑没什么问题了,再考虑加新功能、换新界面,那才是水到渠成的事。