
简介这份源码面向美容院、美业门店的经营者与PHP开发者提供一套可直接部署的会员管理解决方案覆盖会员档案、消费记录与日常店铺运营等场景适合需要二次开发或快速搭建门店后台的技术人员参考使用。压缩包共1601个文件约23.4MB以662个php业务逻辑文件与272个html页面模板为主体辅以148个js交互脚本、35个css样式及大量gif、png、jpg图片素材另含config配置、functions函数库、sql建表脚本与ttf字体等资源前后台结构完整。系统权限可细分到每个功能点增删改查一应俱全并整合了Excel导出能力微信端部分可支撑移动端会员触达。目前已有3259人学习下载读者可从中获取完整的会员管理业务逻辑、权限控制思路与数据导出实现便于对照研究或按需裁剪改造。1. 美容院会员管理系统一套带微信端的门店源码能解决什么开一家美容院最头疼的不是没客人而是客人来了记不住、走了唤不回。前台用 Excel 记卡项微信上靠人工回消息预约月底对账发现次卡核销对不上——这套「美容院会员管理系统源码含微信端」就是冲着这个场景来的。它把会员档案、卡项套餐、预约排班、消费核销、储值余额和微信端自助查询串成一条线前台在后台开卡顾客在微信端看余额、约项目店长在报表里看流水。适合谁一是想给自家门店做数字化但预算有限的小型连锁二是接私活需要一套能改的门店管理底座的开发者。关键词里的美容、会员、源码、店铺对应的正是这套系统的四个落点行业场景、核心数据模型、可二次开发的代码、单店到多店的部署形态。这套源码的技术选型按同类门店管理项目的常见做法后端多半是 Spring Boot 或 ThinkPHP 这类上手快、部署轻的框架前端后台用 Vue 或 Layui 搭管理界面微信端走公众号网页授权或小程序数据库 MySQL 存会员、卡项、订单、预约四类主表。下面不空谈概念直接按「能干什么 → 怎么跑 → 怎么改」拆开讲把目录结构、核心表、关键接口和踩过的坑都摆出来。2. 系统架构与目录结构从会员开卡到微信端查询的链路2.1 整体分层与请求走向这类门店系统的架构不复杂但分层必须清楚否则二次开发时改一个核销逻辑能牵出五处报错。典型分层是四层微信端/管理后台作为表现层Controller 接请求做参数校验Service 承载开卡、核销、预约冲突判断这些业务规则Mapper/DAO 落库。一次「顾客在微信端查余额」的请求走向是这样的微信网页授权拿到 openid → 用 openid 查绑定关系找到 member_id → Service 汇总会员主表余额、次卡剩余次数、储值流水 → 返回给前端渲染。这条链路里最容易翻车的是 openid 与 member_id 的绑定关系很多源码把它塞在会员表的一个字段里一人多店就会串号合格做法是单独一张绑定表。管理后台的请求则多一层权限登录后拿 token拦截器校验角色店长/前台/美容师不同角色能看的菜单和能做的操作不同。前台只能开卡核销店长才能改价格和看报表。这个权限粒度如果源码里写死在前端菜单后端不校验那就是个黑匣子改的时候必须补上后端注解或中间件。2.2 目录结构长什么样按常见工程组织源码解压后大致是这样一个结构不同框架名字有差异但职责对应beauty-member/ ├── admin-web/ # 管理后台前端Vue/Layui │ ├── src/views/member/ # 会员档案页 │ ├── src/views/card/ # 卡项与套餐 │ └── src/api/ # 接口封装 ├── server/ # 后端服务 │ ├── controller/ # 会员、卡项、预约、订单入口 │ ├── service/ # 开卡、核销、余额计算 │ ├── mapper/ # 数据库访问 │ ├── entity/ # 会员/卡项/订单实体 │ └── config/ # 数据源、微信参数、拦截器 ├── wx-web/ # 微信端页面H5 或小程序 │ ├── pages/member/ # 我的余额、我的卡 │ └── pages/booking/ # 预约项目 ├── sql/ # 建表脚本与初始数据 └── README.md拿到源码先看sql/和config/这两处决定了能不能跑起来建表脚本里有没有初始管理员账号配置文件里的数据库地址和微信 appid/secret 是不是占位符。我一般先跑建表脚本再改配置最后才动业务代码顺序反了会浪费大量时间在环境问题上。2.3 核心数据模型四张主表撑起业务会员系统的骨架就是四张表理解它们的关系后面改功能才有地图。会员表存基础档案和储值余额卡项表存次卡/储值卡/套餐的定义会员卡表是「谁买了哪张卡、剩多少次」订单/流水表记录每一笔消费和充值。预约表单独一张关联会员和美容师、时间段。-- 会员主表储值余额放这里方便查询 CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, phone VARCHAR(20) UNIQUE NOT NULL, balance DECIMAL(10,2) DEFAULT 0, -- 储值余额 openid VARCHAR(64), -- 微信绑定单店可放这 store_id BIGINT, -- 多店时区分门店 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 会员卡次卡剩余次数是核销的核心字段 CREATE TABLE member_card ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, card_id BIGINT NOT NULL, -- 关联卡项定义 remain_times INT DEFAULT 0, -- 剩余次数 expire_at DATE, -- 有效期 status TINYINT DEFAULT 1 -- 1正常 0失效 );参数说明balance用 DECIMAL 不用 FLOAT金额计算别用浮点这是血泪经验remain_times核销时要做乐观锁或行锁两个前台同时核销同一张卡会扣成负数expire_at过期判断放在 Service 层别只靠前端提示。多店场景下store_id必须贯穿所有查询否则 A 店能看到 B 店会员这是这类源码最常见的越权问题。3. 本地跑起来环境准备、建库与微信端联调3.1 环境与依赖清单跑这套源码前先把环境对齐。后端按 Spring Boot 系准备 JDK 8 或 11、Maven、MySQL 5.7/8.0前端准备 Node 14 以上微信端如果是 H5 就只需浏览器如果是小程序要装开发者工具。下面这张表是我一般会先核对的清单组件版本建议用途常见坑JDK8 / 11后端运行版本过高注解报错MySQL5.7 / 8.0数据存储8.0 驱动类名不同Maven3.6依赖构建私服缺失拉不到包Node14后台前端版本过高构建失败微信开发者工具最新小程序调试未配合法域名依赖拉不下来是第一个坎源码里如果配了内部私服地址把settings.xml或pom.xml里的仓库改成公共源即可。数据库驱动注意 MySQL 8 的com.mysql.cj.jdbc.Driver和时区参数serverTimezoneAsia/Shanghai少了时区参数时间会差 8 小时预约时间全乱。3.2 建库、改配置、启动后端第一步建库导数据第二步改配置第三步启动。命令不复杂但每步都有讲究# 1. 建库并导入初始脚本 mysql -uroot -p -e CREATE DATABASE beauty_member DEFAULT CHARSET utf8mb4; mysql -uroot -p beauty_member sql/init.sql # 2. 改后端配置application.yml 或 config 文件 # datasource.url: jdbc:mysql://127.0.0.1:3306/beauty_member?serverTimezoneAsia/Shanghai # datasource.username / password 改成自己的 # wx.appid / wx.secret 先留空本地调试可跳过授权 # 3. 启动 mvn spring-boot:run # 或打包后 java -jar target/xxx.jar逻辑说明init.sql里通常带一个默认管理员账号登录后台第一件事就是改密码微信 appid/secret 留空时微信端授权会失败本地调试可以加一个「测试模式」开关用固定 openid 模拟登录这个开关上线前必须关掉。启动后先访问后台登录页能进说明数据库通了再调一个会员列表接口能返回数据说明 Mapper 层没问题。3.3 微信端联调的关键三步微信端是这套源码的卖点也是联调最容易卡住的地方。核心是三件事网页授权拿 openid、绑定会员、合法域名配置。H5 走公众号网页授权流程是引导用户跳转授权页回调里拿 code 换 openid小程序则用wx.login拿 code 换 openid。拿到 openid 后查绑定表没绑定就引导输入手机号验证后绑定。// 微信 H5 授权回调示例伪代码按实际框架调整 const code getQuery(code); const res await request(/api/wx/login, { code }); // 后端用 code appid secret 换 openid再查绑定关系 if (!res.memberId) { // 未绑定跳转手机号验证页 location.href /bind?openid res.openid; } else { // 已绑定进入我的卡包 location.href /my-card?memberId res.memberId; }参数说明code只能用一次且几分钟内有效别缓存appid和secret放后端绝不能出现在前端代码里这是安全底线。本地联调时微信回调域名必须是备案过的合法域名本地 IP 不行常见做法是用内网穿透工具映射一个临时域名配到公众号后台或者干脆先用测试模式跳过授权把绑定和卡包逻辑调通再上真机。4. 核心模块拆解开卡、核销、预约冲突判断4.1 开卡与储值一次事务里做完三件事开卡不是插一条记录那么简单它要同时做三件事生成会员卡记录、扣减或记录收款、写一条流水。这三步必须在一个事务里否则会出现「钱收了卡没开」的对账灾难。核心 Service 大致长这样Transactional(rollbackFor Exception.class) public void openCard(Long memberId, Long cardId, BigDecimal payAmount) { CardDef def cardMapper.selectById(cardId); // 1. 生成会员卡 MemberCard mc new MemberCard(); mc.setMemberId(memberId); mc.setCardId(cardId); mc.setRemainTimes(def.getTimes()); // 次卡次数 mc.setExpireAt(LocalDate.now().plusMonths(def.getValidMonths())); memberCardMapper.insert(mc); // 2. 储值卡则加余额次卡则记收款 if (def.getType() 1) { memberMapper.addBalance(memberId, payAmount); } // 3. 写流水金额、卡项、操作人一个不能少 flowMapper.insert(buildFlow(memberId, cardId, payAmount)); }逻辑说明Transactional的rollbackFor要显式写 Exception默认只回滚运行时异常检查型异常不回滚是经典坑。def.getType()区分储值卡和次卡储值卡加余额、次卡只记次数别混着写。流水表要记操作人出了问题能追到是谁开的卡。参数上payAmount和卡项定价可能不一致有折扣所以金额以实收为准别用卡项定价覆盖。4.2 核销剩余次数扣减要防并发核销是门店每天用得最多的操作也是最容易出并发问题的地方。两个前台同时给同一张次卡核销如果先查后改就会扣成负数。正确做法是用带条件的更新语句把判断和扣减合成一步-- 原子扣减剩余次数大于 0 才扣返回影响行数判断是否成功 UPDATE member_card SET remain_times remain_times - 1 WHERE id #{cardId} AND remain_times 0 AND status 1;Service 层拿到影响行数等于 0 就抛「次数不足或卡已失效」等于 1 再写核销流水。这样即使并发数据库行锁也保证了不会超扣。参数上status 1不能漏失效卡不能核销如果业务允许负数先消费后补卡那要另做欠费记录别直接放开条件。核销流水里记美容师 id 和项目 id后面算提成和项目消耗都靠它。4.3 预约冲突判断时间段重叠的边界预约模块的难点不是存数据是判断冲突同一个美容师同一时间段不能被约两次。判断逻辑是「新预约开始时间 已有预约结束时间 且 新预约结束时间 已有预约开始时间」两个条件同时成立才算重叠。很多人只写一个条件导致首尾相接的预约被误判冲突。public boolean hasConflict(Long beauticianId, LocalDateTime start, LocalDateTime end) { // 重叠判断start existing.end AND end existing.start int count bookingMapper.countConflict(beauticianId, start, end); return count 0; }对应的 SQL 用start_time #{end} AND end_time #{start}注意是严格小于和大于这样 10:00-11:00 和 11:00-12:00 不算冲突。参数上时间统一用 DATETIME 存别用字符串跨天预约要单独处理别指望一个条件覆盖。预约状态要区分「已预约/已到店/已取消」取消的预约不参与冲突判断查询时加status ! 2。5. 避坑与排查这类门店源码最容易翻车的五处5.1 微信 openid 串号一人多店看到别人余额现象同一顾客在两家店都注册微信端登录后看到的是另一家店的卡包。原因openid 直接存在会员表而 openid 是相对公众号唯一的多店共用一个公众号时一个 openid 对应多条会员记录查询没带门店条件就取错了。解决单独建member_wx_bind表字段是 openid member_id store_id查询时三者一起定位或者每个门店独立公众号从根上隔离。5.2 金额用浮点月底对账差几分钱现象储值余额和流水汇总对不上差几毛几分。原因余额字段用了 FLOAT/DOUBLE累加产生精度误差。解决金额字段一律 DECIMAL(10,2)Java 侧用 BigDecimal比较用compareTo不用equals除法指定精度和舍入模式。已经上线的库改字段类型要写迁移脚本先把历史数据四舍五入再改。5.3 核销没加锁次卡被扣成负数现象顾客反映次数没用完却显示负数或者一次消费扣了两次。原因先查剩余次数再更新两个请求并发时都读到旧值。解决用 4.2 里的条件更新语句靠数据库行锁保证原子性或者用乐观锁版本号更新失败重试。核销接口还要做幂等同一笔订单重复提交只扣一次用订单号做唯一约束。5.4 微信授权域名没配真机白屏现象开发者工具里正常真机打开微信端白屏或提示「redirect_uri 参数错误」。原因公众号后台的网页授权域名没配或者配了但没带协议、带了端口。解决公众号后台配置的域名不带 http/https、不带端口、必须是备案域名本地调试用测试模式或内网穿透临时域名小程序还要在后台配 request 合法域名否则接口请求被拦。5.5 权限只做前端前台能改价格现象前台账号登录后通过改请求参数能修改卡项价格或看全店报表。原因菜单按角色隐藏了但后端接口没校验角色直接调接口就绕过了。解决后端每个敏感接口加角色注解或拦截器校验价格修改、报表查询、会员删除这类操作只允许店长角色前端隐藏只是体验不是安全边界。6. 二次开发从单店到多店、从手动到自动的改造思路拿到这套源码多数人第一件事是想加功能。我的建议是先别急着加先把「多店隔离」和「数据可追溯」两条底座打牢再谈扩展。多店隔离的做法是把store_id提升为一等公民所有业务表加这个字段所有查询强制带门店条件最好在 Mapper 层用拦截器自动拼store_id避免开发时漏写。这一步做完系统才敢给第二家店用。第二个改造点是核销与提成的联动。原始源码核销只扣次数但门店真正关心的是美容师提成。可以在核销流水表加beautician_id和commission_amount核销时按项目提成比例算好写入月底按美容师汇总。改造时注意提成比例要可配置放卡项或项目表里别写死在代码里。第三个是微信端从「查询」升级到「自助预约」。原始微信端多半只能看余额和卡加预约功能要复用后台的冲突判断逻辑把 Service 抽成公共模块微信端和后台都调同一个方法避免两套判断规则不一致。预约成功后发模板消息提醒这个要申请模板消息权限本地调试先用日志代替。验证改造是否成功我一般跑三条回归开一张次卡核销到 0 次再核销一次看是否被拦两个线程同时核销同一张卡看剩余次数是否准确A 店账号查 B 店会员看是否返回空。这三条过了底座基本稳。说个自己的教训早些年接一个门店项目图快把 openid 直接塞会员表上线后第二家店接入顾客微信端互相看到对方卡包排查了一整天才定位到绑定关系。从那以后我每次接这类会员系统都先把「绑定表 store_id 贯穿」这两件事强制走一遍再动任何业务代码。希望帮到你。本文还有配套的精品资源点击获取