☰
Spring Boot美发店会员管理系统:从数据库设计到业务逻辑实战
2026/10/7 10:51:23 网站建设 项目流程

做美发店会员管理系统这个活儿,我想先聊聊最容易被代码带偏的一件事:很多人拿到“Springboot美发店会员管理系统”这样的题目,第一反应就是赶紧建表、写接口、撸页面,结果做到一半发现理发店老板真正要的东西,和网上那些“通用会员系统Demo”根本不是一回事。我这套系统就是在这个坑里摸爬滚打出来的,用的是Springboot全家桶那一套,自己动手写源码、调数据库、反复改需求,直到跑通整个理发店的业务流程。这篇博文不打算给你贴一个完整的毕业设计级代码清单,那样太长了,也对你没帮助。我更想把从零搭一个“真能用起来”的美发店会员系统过程中最核心的思路、关键数据表设计、几个容易翻车的业务逻辑,还有从开发环境到最终调试部署的完整链路,原原本本讲清楚。你可以把它当作一个参考底稿,也可以直接照着我这个思路去改造成自己的项目。

1. 美发店要的“会员系统”到底是个什么东西

1.1 别把“会员管理”理解成简单的增删改查

我接过不少类似需求,发现一个共性现象:很多开发者一听到会员管理系统,脑子里浮现的是“会员表 + 充值表 + 消费记录”三个表就完事了。但真正到美发店这种场景里,业务规则要比这复杂得多。首先是储值余额,顾客充值1000送200,这个赠款怎么算?有些店赠款是只能在烫染项目上用,洗剪吹得用本金;有些店则是月底清零赠款,剩下本金继续能用。其次是次卡,比如顾客买了一张“剪发10次卡”或者“头皮护理5次卡”,这个次卡是否实名制?能不能多人共用一张卡?过期时间怎么算?还有积分,现在几乎每家美发店都有积分体系,消费1块钱积1分,积分能兑换洗护产品或者抵现金,那积分抵现金的比例是多少?有效期多长?

我做的这套系统,最开始需求清单上只写了“会员开卡、充值、消费、查询余额”,但实际和店长聊下来,需求至少翻了一倍。这里我给你一个建议:做这种行业小系统,前期花半天时间蹲在店里观察实际收银场景,比看一百篇技术博客都管用。你会发现收银员其实很忙,根本没有时间输一堆复杂的表单,她们就想要一个大大的会员手机号输入框,回车一下,会员信息和余额立刻就弹出来,然后直接选项目扣款。你要是给她们做一个跳来跳去的复杂前端,这系统上线那天就是它被弃用那天。

1.2 我从真实场景里拆出来的核心业务清单

我这里列一下我做这个系统时,最终确定的业务功能清单,你可以对着看看自己是不是漏了什么:

  • 会员档案:姓名、手机号、性别、生日、发型师偏好、备注、开卡门店、开卡时间、会员等级。
  • 储值账户:本金余额、赠送余额、累计充值、累计消费、最后充值时间、状态(正常/冻结/挂失)。
  • 次卡账户:一个会员可以有多张次卡,每张次卡有总次数、剩余次数、有效期、适用项目范围。
  • 积分账户:累计积分、已用积分、可用积分、积分有效期规则。
  • 消费流水:每一笔消费都要记录扣的是本金、赠款、次卡次数、积分抵用中的哪一种,金额要拆分清楚。
  • 充值流水:充值多少、赠送多少、支付方式、经手人、充值时间。
  • 项目管理:项目分类(剪发、烫发、染发、护理)、项目价格、是否参与会员折扣、是否可以使用赠款。
  • 员工管理:发型师信息,以及每个发型师的服务提成记录,虽然很多小店不要求这个,但做出来会专业很多。
  • 系统设置:店铺名称、储值规则、积分规则、次卡过期策略、小票打印开关。

有了这个清单你就能看出来,所谓会员管理系统,本质上是“账户系统 + 计费引擎 + 流水审计”的组合,而不是一个简单的通讯录。理解了这一点,你后面写代码时候的表结构设计和事务控制思路都会清晰很多。

2. 技术选型背后的真实考量

2.1 为什么是Springboot而不是别的

用Springboot几乎是这个场景下最稳妥的选择。原因很简单,一个是生态成熟,网上关于Springboot的资料多到你看不完,遇到问题一搜就有答案;另一个是它内置了Tomcat,打成一个jar包就能跑,对美发店这种没有专业运维的环境极其友好,店主自己有一台Windows电脑就能部署。相比之下,你要是用SSH那种老古董结构,配置一堆XML,光是环境折腾就能劝退一半人。用Spring Cloud这种微服务框架就更没必要了,一个小店系统搞微服务纯属给自己找麻烦。技术选型一定要跟业务场景匹配,大炮打蚊子除了显得唬人没有任何好处。

2.2 数据库和ORM怎么定的

数据库我选的是MySQL 8.0,原因很朴素:免费、稳定、装机量大,美发店的数据量撑破天也就是几千个会员加几十万条流水,MySQL绰绰有余。如果你电脑上还没装MySQL,建议直接用安装包装一个,服务端字符集选utf8mb4,这样才能正常存表情符号。你想想现在的顾客昵称里什么emoji都有,用utf8存会直接报错。

ORM我用的是Spring Data JPA。说实话,在MyBatis和JPA之间我也纠结过一阵。如果我的团队里都是老MyBatis选手,那肯定选MyBatis;但考虑到这个系统里有很多“会员及其名下所有账户”这种聚合查询,用JPA的实体关系映射写起来确实省事,一对多、多对一直接通过注解搞定,而且JPA自带根据方法名派生查询,比如findByPhoneAndStatus,开发速度非常快。不过我也得提醒你,JPA玩不熟的人容易踩“N+1查询”的坑,后面我会详细讲我怎么用@EntityGraph和JOIN FETCH解决的。

2.3 前端方案:Thymeleaf还是前后端分离

这里我踩过一次弯。一开始我打算做前后端分离,Vue写个管理后台,但后来发现这种小系统搞前后端分离需要额外处理跨域、Token鉴权、打包部署,复杂度直接上升一个量级。后来我冷静下来想了想使用场景:美发店就那一两台电脑,内网访问,根本没有跨域需求;收银界面要的是简洁快速。最终我选了Thymeleaf服务端渲染,Springboot单片应用搞定一切,刷新页面就能看到最新数据,开发效率高出一大截。页面样式就用的Bootstrap,这玩意儿虽然不新潮,但胜在稳定,而且自带响应式,放到平板上也能用。

如果你确实想练前后端分离,那是另一套玩法,但如果是交作业或者快速落地一个真实系统,我强烈建议你选Thymeleaf这一套。你记住,架构是为业务服务的,不是拿来炫技的。

3. 数据库表设计:地基打不好,后面全是坑

3.1 核心表结构到底怎么建

我见过很多人的会员表就一个字段记余额,这其实埋着很大的隐患。正确的做法是“会员主表 + 账户流水表”分离,余额永远不直接update,而是通过流水汇总或者通过程序严格计算后更新。我这样设计的好处是每一分钱的变动都有据可查,出了问题可以对账。

下面是会员主表的核心字段,我直接给你看一下我当时建表的关键部分:

CREATE TABLE member ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '会员ID', phone VARCHAR(20) NOT NULL COMMENT '手机号,登录和检索用', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT COMMENT '性别 1男 2女 0未知', birthday DATE COMMENT '生日', level INT DEFAULT 1 COMMENT '会员等级 1普通 2银卡 3金卡', main_balance DECIMAL(10,2) DEFAULT 0.00 COMMENT '本金余额', gift_balance DECIMAL(10,2) DEFAULT 0.00 COMMENT '赠送余额', total_points INT DEFAULT 0 COMMENT '累计积分', used_points INT DEFAULT 0 COMMENT '已使用积分', status TINYINT DEFAULT 1 COMMENT '状态 1正常 2冻结 3挂失', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';

这里有一个我反复强调的细节:金额字段一律用DECIMAL(10,2),绝对不要用FLOAT或者DOUBLE。FLOAT在计算0.1+0.2这种场景下会出现精度丢失的问题,虽然平时看着差距不大,但做账的时候一分钱对不上都会被老板拉去喝茶。这话不是我危言耸听,你去看看所有金融、电商系统的数据库规范,金额都是定点数。

次卡表我也单独建了一张,因为一个会员可能同时持有多种次卡,如果只给会员表加一个“剩余次数”字段,那你没法区分是哪张卡剩的次数:

CREATE TABLE member_card ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL COMMENT '会员ID', card_name VARCHAR(50) NOT NULL COMMENT '次卡名称,如剪发10次卡', total_count INT NOT NULL COMMENT '总次数', used_count INT DEFAULT 0 COMMENT '已用次数', expire_date DATE COMMENT '有效期截止', applicable_items VARCHAR(255) COMMENT '适用项目ID,逗号分隔', status TINYINT DEFAULT 1 COMMENT '1有效 2已用完 3已过期 4已挂失' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='次卡表';

消费流水表是另一个重点,我管它叫“账本”,这个表记录了每一笔钱的来龙去脉。

CREATE TABLE consume_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL, item_name VARCHAR(50) NOT NULL COMMENT '消费项目名', item_price DECIMAL(10,2) NOT NULL COMMENT '项目原价', discount_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '折扣优惠金额', use_main_balance DECIMAL(10,2) DEFAULT 0.00 COMMENT '动用本金', use_gift_balance DECIMAL(10,2) DEFAULT 0.00 COMMENT '动用赠款', use_card_id BIGINT COMMENT '用掉的次卡ID', use_points INT DEFAULT 0 COMMENT '用掉的积分', actual_amount DECIMAL(10,2) NOT NULL COMMENT '实际支付金额', operator VARCHAR(50) COMMENT '操作人', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消费流水表';

这样设计之后你会发现一个好处:消费的时候,程序只需要在事务里同时更新会员表的余额、次卡表的剩余次数、积分账户,然后插入一条消费流水。不管中间网断了还是程序崩了,事务回滚之后数据都不会错。

3.2 索引和查询优化:别让你的表变成全表扫描

你可能觉得几千条数据的表随便查都很快,但到后面流水的累积速度会超出你的想象。尤其是消费记录表,一年的数据就上万条。如果每次收银员输个手机号,你都要去全表扫一遍,那响应速度就会肉眼可见地变慢,到时候客人排队等着结账,程序却卡了三秒,这体验就很差劲了。

我给会员表的phone字段建了唯一索引,这个是基础中的基础。消费流水表则针对member_id和created_at建了联合索引,这样“查某个会员最近三个月的所有消费”这种高频查询就能命中索引。次卡表对member_id和status建索引,方便快速加载某个会员所有可用的次卡。线上系统如果数据量到百万级可能要考虑分库分表,但美发店这个量级,索引优化到位就完全够用。我给索引的建议是:不要盲目给每个字段都加索引,索引也是要占空间的,写操作也会变慢。高频查询条件走索引,这种思路放哪个行业都通用。

3.3 数据字典和JPA实体映射的对应关系

数据库表建好了,下一步就是写Java实体类。JPA的实体映射有几个地方要注意,一个是枚举类型和TINYINT之间的转换,你可以用@Column配合@Convert实现,也可以干脆在实体里用Integer类型存状态字段,在Service层定义常量。我更倾向后者,简单直接,少一层转换逻辑。另一个是日期的映射,LocalDateTime对应MySQL的DATETIME,LocalDate对应DATE,千万别搞混。

实体关系方面,会员和消费流水是一对多,会员和次卡是一对多。但JPA里我建议不要在会员实体上直接配@OneToMany集合,否则你在查询会员时它会自动把你的流水全部加载出来,一次查询N条流水就变成N+1条SQL,性能惨不忍睹。你需要查流水的时候,直接在消费流水Repository里面写findByMemberIdOrderByCreatedAtDesc就好了,这样控制力更强。

4. 核心业务代码:充值、消费、次卡扣减的逻辑陷阱

4.1 开卡和充值的实现思路

会员开卡是第一步,逻辑其实不复杂:创建一条会员记录,再插入一条充值流水。这里有一个关键点:首充赠送规则怎么算。很多店做活动是“充300送30,充500送80,充1000送200”,这种阶梯赠送规则直接写死在前端不行,万一活动变了又要改代码重新部署。我的做法是在数据库建一张recharge_rule表,存充值阈值和赠送金额,后台可以随时调整,甚至允许设置不同会员等级的不同赠送比例。前端拿用户输入的充值金额去匹配规则,显示“本次充值可获得赠送XX元”,消费者决定之前都看得明明白白。

核心Service代码大概长这样:

@Service public class MemberServiceImpl implements MemberService { @Transactional(rollbackFor = Exception.class) public RechargeResult recharge(Long memberId, BigDecimal amount) { // 1.根据充值金额找到对应的赠送规则 RechargeRule rule = rechargeRuleMapper.findByThresholdLessThanEqualOrderByThresholdDesc(amount); BigDecimal giftAmount = rule == null ? BigDecimal.ZERO : rule.getGiftAmount(); // 2.更新会员余额 Member member = memberMapper.selectByIdForUpdate(memberId); // 注意行锁! member.setMainBalance(member.getMainBalance().add(amount)); member.setGiftBalance(member.getGiftBalance().add(giftAmount)); memberMapper.updateById(member); // 3.写充值流水 RechargeRecord record = new RechargeRecord(); record.setMemberId(memberId); record.setRechargeAmount(amount); record.setGiftAmount(giftAmount); record.setPayType("CASH"); record.setOperator(operator); rechargeRecordMapper.insert(record); return new RechargeResult(amount, giftAmount); } }

注意我在第2步用了selectByIdForUpdate,这个就是SELECT ... FOR UPDATE,目的是把这条会员记录锁住,避免两个人同时给同一个会员充值导致余额算错。对于美发店这种低并发场景,行锁足够用了,不需要引入分布式锁那么重的方案。

4.2 消费扣款:余额、次卡、积分到底先扣哪个

这是整个系统里最容易出问题的地方,也是我调得最久的一块的逻辑。先说结论,我最终定下来的扣款顺序是:先用赠款,再用本金,次卡如果是匹配项目那就优先使用次卡次数,最后再用积分抵一部分现金。这个顺序是根据美发店的实际运营习惯调整的,因为赠款通常都是限期使用的,老板希望顾客先把赠款用掉,本金留着以后再用。而次卡其实是预付费用买断的服务次数,它和储值余额是两个不同的账户体系,所以要先判断这个消费项目在不在次卡的适用范围内,在的话就先扣次卡次数,不在就扣余额。

这里送上扣款判断的核心伪代码,字段名都是我省略了其他业务之后的简化版,你感受一下思路:

public void consume(Long memberId, Long itemId) { // 查会员、查项目 Member member = memberMapper.selectByIdForUpdate(memberId); Item item = itemMapper.selectById(itemId); // 1.先看有没有可用的次卡 List<MemberCard> cards = cardMapper.findByMemberIdAndStatus(memberId, 1); MemberCard matchedCard = cards.stream() .filter(card -> card.getApplicableItemIds().contains(itemId) && card.getRemainCount() > 0) .findFirst().orElse(null); if (matchedCard != null) { // 扣次卡次数 matchedCard.setUsedCount(matchedCard.getUsedCount() + 1); cardMapper.updateById(matchedCard); insertConsumeRecord(member, item, "次卡消费"); return; } // 2.如果没有次卡,走储值账户扣款 BigDecimal need = item.getPrice(); BigDecimal useGift = Math.min(member.getGiftBalance(), need); BigDecimal useMain = need.subtract(useGift); member.setGiftBalance(member.getGiftBalance().subtract(useGift)); member.setMainBalance(member.getMainBalance().subtract(useMain)); // 3.再根据消费金额算积分,1元1分,向下取整 int points = need.intValue(); member.setTotalPoints(member.getTotalPoints() + points); memberMapper.updateById(member); insertConsumeRecordWithDetail(member, item, useMain, useGift, points); }

这里有个细节我要重点提醒:本金余额不够的时候怎么办?比如会员余额还剩20块,项目要38块,实际门店里顾客经常会说“先扣20,剩下的我现金补给你”。那这个“混合支付”场景要不要支持?我当时咬咬牙做了支持。做法是消费流水中加一个cash_amount字段,记录顾客额外补的现金。这样对账的时候才能算清楚一天收了多少钱。你要是省这个功能,后面财务对不上账一定会回来找你。

4.3 并发扣款和幂等性,没你想的那么难

美发店收银并发量很低,但不代表不会出现重复请求。比如收银员手滑点了两次“确认扣款”,没有幂等处理的话就会扣两次钱。我的方案是给前端按钮加loading状态,同时后端在扣款接口里做一个简单的防重:以会员ID加上当前时间戳为维度,在Redis里设一个几秒钟的锁。如果用了Redis,这样最方便;如果不想依赖Redis,那就在业务表里加一个“请求唯一编号”字段,插入流水前先查重。这个思路适用于市面上所有类似的小系统,一做进去就能避免很多扯皮。

4.4 Spring Data JPA查询的N+1问题

刚才提到N+1问题,这里展开说下。假如Controller里查到10个会员,你设置了fetch = FetchType.EAGER,那查询会员时会额外查10次关联表,这就是N+1。大多数时候应该明确用@Query配合JOIN FETCH一次性把关联对象查出来。举个例子:

@Query("SELECT m FROM Member m LEFT JOIN FETCH m.accounts WHERE m.phone = :phone") Member findByPhoneWithAccounts(@Param("phone") String phone);

这样只发一条SQL就把会员和他名下的所有账户都查出来了。JPA本身不背性能的锅,关键是使用者得清楚每个查询最终生成什么SQL。实在搞不明白的时候,把spring.jpa.show-sql=true打开,在控制台看看每个接口打印出来的SQL,这是排查性能问题最直接的笨办法,但这个笨办法能解决90%的问题。

5. 从开发环境到部署上线的实操全流程

5.1 环境准备:JDK、Maven、MySQL、IDEA一个都不能少

开发环境这块,我直接给你说我的版本组合:JDK 1.8或者11都行,我用的是JDK 8,稳定不折腾;Maven 3.6.3以上;MySQL 8.0;IDEA社区版或者旗舰版都可以。Springboot版本用的2.7.x,这个版本对应Spring Framework 5.3.x,足够支撑这套系统。千万别一上来就追最新版Springboot 3.x,因为3.x要求JDK 17起步,而且很多老版本的依赖会不兼容,你自己调试的时候会被各种莫名其妙的报错折磨到崩溃。做一个这种体量的系统,稳定压倒一切。

IDEA里导入项目后,记得先配置Maven仓库,用阿里云镜像,不然下载依赖能等一上午。设置方法是在settings.xml里加一个镜像节点,网上搜一下就能找到,我这里不贴了,属于基础不能再基础的操作。

5.2 application.yml配置:端口、数据库连接、日志级别

配置文件是最容易踩坑的地方,我给你看一个我实际使用的配置骨架:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hair_salon?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect

serverTimezone=Asia/Shanghai这个参数特别重要,不设的话,如果你的电脑和数据库时区不一致,时间字段会差8个小时,查出来总觉得“时间不对”,排查半天下不了手。ddl-auto: update的意思是Hibernate会根据实体自动更新表结构,适合开发阶段;但上线前我建议改成none,直接用SQL脚本建表,免得Hibernate自作主张改出问题。

5.3 本地调试的心法:什么样的Bug需要看日志

很多新手遇到报错就慌,然后在代码里到处打印System.out.println,这是最低效的排查方式。我的习惯是一上来先看控制台日志,Springboot的默认日志已经能告诉你大部分信息。比如数据库连不上,日志里一定有Cannot create PoolableConnectionFactory这种字样;表不存在,会报Table 'xxx' doesn't exist。看清楚异常栈的顶部三行,基本就定位到问题方向了,完全没有必要瞎猜。

调试过程中,我还习惯把logging.level.com.example=DEBUG这种配置加上,只针对自己的包名打DEBUG日志,别全局DEBUG,否则日志量会淹没你的屏幕。在Service层、事务边界、扣款逻辑三处打关键日志,出问题时能快速定位是哪一段逻辑出的问题。

5.4 打包部署:把项目变成一个小jar包

开发调试没问题之后,部署就很简单了,Springboot最爽的一点就是打jar包直接交给使用者。执行mvn clean package -DskipTests,然后target目录下就会生成一个xxx.jar。你可以用java -jar xxx.jar在本地试运行,确认没问题后,把这个jar包拷到美发店收银电脑上。想让它在后台运行而不是一直占着终端窗口,Windows下可以用javaw -jar xxx.jar,或者写一个简单的.bat启动脚本:

@echo off start javaw -jar C:\hair_salon\hair-salon-system.jar

Linux服务器上部署就更简单了,用nohup java -jar xxx.jar > app.log 2>&1 &启动,日志输出到文件里。这台机器只要能跑JRE就能运行你的系统,不需要额外装Tomcat。我在部署的时候还会特意在jar包旁边放一个外置的application.yml,Springboot会自动读取当前目录下的配置文件覆盖jar包内置的配置,这样以后改数据库密码或者改端口,直接改文本重启就行,不用重新打包。

5.5 部署完的重点检查清单

部署上线以后不能直接撒手不管,我建议你做这些检查。

第一,数据库备份策略。美发店的数据量不大,但会员储值余额是实打实的钱,绝不能丢。我给店铺装的是MySQL Workbench的定时导出,每天凌晨自动把数据库dump成一个SQL文件,保留最近15天。这个小动作成本极低,但关键时刻能救命。

第二,浏览器访问测试。至少要在Chrome、Edge、还有店里那台老电脑自带的IE兼容模式(如果真的还有的话)下都点一遍主要功能,确保页面样式不崩。

第三,日常使用演练。我会让收银员在测试环境里模拟一整天的流程:开卡、充值、消费、退款、查报表。这一步能发现很多你坐在电脑前面根本想不到的问题。比如我就遇到过,收银员输入手机号时习惯带个空格,结果会员查不到;解决方式是在保存会员信息的时候统一trim掉手机号两边的空白。

6. 上线之后最容易踩到的真实坑位

6.1 “余额不对”十有八九是并发或者精度问题

这个我前面讲了一半,这里复盘一个真实案例。系统上线第二周,店长跑过来跟我说,有个顾客充值了500块,第二天来消费时余额少了十几块。排查之后发现,当天这个顾客消费时,另一个店员同时在后台给这个会员补录了一笔充值的操作,两个请求同时读到余额都是500,分别做加减之后写回,其中一个人的更新就覆盖了另一个人的结果。解决办法就是我前面说的SELECT ... FOR UPDATE行锁,把这个场景修掉了。你必须意识到,涉及钱的系统,并发安全永远是第一优先级。

6.2 次卡过期判定和过期后的余额处理逻辑

次卡常见的一个坑是:过期了顾客没消费完,但是理发店老板可能觉得“卡过期了就作废”或者“还能再宽限一个月”。这个规则每个店都不一样,我做成一个可配置参数,后台设置过期后“仍然可以消费”还是“立即锁定”。你猜怎么着,我最后给这个系统做得最精细的地方,不是代码多漂亮,而是这种业务规则的灵活配置,因为每个店的运营策略都不一样,你要是一开始写死,后面就会陷入无穷无尽的改需求循环。

6.3 报表功能:店长真正天天看的页面

说起来有点意思,这个系统里开发工作量最大的不是收银操作页面,而是报表页。店长每天看是今天现金收了多少钱、刷卡多少钱、会员卡扣了多少钱、充值收入多少钱、每个发型师的业绩排行。这些东西看似简单,但它需要把消费流水、充值流水、退款记录全部聚合在一起。我建议你开发的时候不要把这个功能留到最后,因为它是老板直观感受你系统价值的窗口。早期就把统计SQL写好,长这样:

SELECT DATE(created_at) AS biz_date, SUM(CASE WHEN pay_type='CASH' THEN actual_amount ELSE 0 END) AS cash_income, SUM(CASE WHEN pay_type='CARD' THEN actual_amount ELSE 0 END) AS card_income, SUM(CASE WHEN pay_type='MEMBER_BALANCE' THEN actual_amount ELSE 0 END) AS member_balance_income FROM consume_record WHERE created_at >= ? AND created_at < ? GROUP BY DATE(created_at);

你要是有精力,还可以做一个简单的柱状图展示最近7天的营业额趋势,用ECharts的CDN版就行。这种页面一展示出来,基本没人会怀疑你这套系统的实用性。

6.4 数据库备份和恢复演练要真正做一次

很多开发者部署完就完事了,嘴上说做了备份,但实际上从来没试过恢复。我吃过一次亏,硬盘坏了,备份文件是有的,但是导入的时候一直报错,最后发现是备份SQL文件的字符集和新建库的字符集不一致,折腾了两个小时才导回来。所以我的建议是:备份脚本写完之后,至少要在测试环境做一次“恢复演练”,把dump出来的SQL导入一个全新的空库,确认所有表和数据都在,才算是备份真到位。

7. 给后来者的一点个人体会

这套系统前前后后我花了大概两个多星期的业余时间,从建表、写接口、做页面、调试、部署到给店员培训,整个链路走下来,我最大的感受是:做一个行业小系统,技术只占一半,对业务的理解占另一半。Springboot、JPA、MySQL这些都是工具,信手拈来即可,真正花时间的是想清楚理发店的钱是怎么流动的、次卡和储值卡的区别是什么、过期退款这些规则怎么设定才合理。

如果你是想找一套现成的源码学习,我想提醒你,拿到任何一套Springboot美发店会员系统的源码,第一件事不是跑起来,而是先打开数据库设计文档看看它的表结构,再看核心Service层的扣款逻辑,这两处看得懂,这套代码你就吃透了。网上很多所谓的“完整源码”其实跑不起来,缺依赖、缺数据库脚本、缺配置文件,你要有自己补全这些环境的能力,这也是我为什么在这一篇里花了大量篇幅讲环境搭建和配置细节的原因。

最后再分享一个小技巧。开发这类系统,尽量把“业务规则”和“业务代码”分开。所谓业务规则就是哪些地方是可变的,比如充值赠送比例、次卡过期策略、积分抵扣比例,都放配置表,别写死在if else里面。这个习惯帮我省掉的改需求时间,比我写整个系统的时间还多。希望这篇内容能帮你少走点弯路,如果你照着做出来了,你会发现美发店会员系统这套东西,没那么神秘,但确实值得认真做。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询