简介:这是一套面向Java开发者与美容行业信息化从业者的美容管理系统源码,采用多端架构,包含后台管理端、商家端与微信端,适合用于二次开发、毕业设计或商业项目参考。系统以消费者关注服务号为入口,支持在线预约项目、到店消费与服务,并集成威富通第三方支付,覆盖微信、支付宝、扫码枪扫码及微信H5支付等场景。技术层面基于IMS框架,后台与商家端采用EasyUI及美化主题包,微信端使用AUI框架并集成微信JSSDK,实现分享、地图定位、模板推送,同时接入阿里大于短信功能。压缩包共约2000个文件,以png、js、css、java、jsp、gif、html、jar等为主,涵盖前端页面、后端逻辑、样式资源与依赖库,整体约85.05MB。目前已有303人学习下载,可帮助读者快速理解多端美容预约系统的目录结构与支付、消息推送等模块的实现思路。
1. 三端美容管理系统的源码拆解:后台、商家端、微信端到底怎么分工
拿到「Java美容管理系统源码,主要分为三个端,后台,商家端,微信端.zip」这类资源,第一反应不该是急着解压跑起来,而是先想清楚这三个端各自解决什么问题。美容行业的业务链条其实很特殊:门店有多个,每个门店有店长、美容师、前台,客户通过微信预约到店,商家端负责排班和订单核销,后台负责连锁级别的权限、财务和商品配置。三端不是简单的「PC 版 + 移动版」,而是三种完全不同的角色视角。后台是运营总控,商家端是门店执行,微信端是客户自助。搞混了这三者的边界,后面改代码会非常痛苦。这篇笔记就按「先理清架构,再动手跑通,最后讲踩坑」的顺序,把这份 Java 美容管理系统源码的落地路径讲透,适合想拿它做二次开发、课程设计或者接私活的 Java 工程师。
2. 三端架构怎么拆:从 Maven 模块到权限模型
2.1 先看目录结构,判断是单体还是多模块
拿到源码压缩包,解压后第一件事是看根目录有没有pom.xml,以及它下面有几个<module>。美容管理系统常见的组织方式有两种:一种是单 Spring Boot 工程,用不同包名区分admin、merchant、wx;另一种是 Maven 多模块,xxx-admin、xxx-merchant、xxx-wx各自独立。两种都能用,但多模块在权限隔离和部署上更清晰。
# 解压后先看顶层结构,不要急着导入 IDE unzip Java美容管理系统源码.zip -d beauty-system cd beauty-system find . -maxdepth 2 -name "pom.xml" -o -maxdepth 2 -name "build.gradle" # 看模块划分 grep -A 20 "<modules>" pom.xml这段命令的作用是先确认工程形态。如果只有一个pom.xml,说明是单体,三端靠包名或不同 Controller 前缀区分;如果有多个模块,通常common放工具类和实体,admin、merchant、wx各自依赖common。参数上重点看<modules>里列了几个,以及每个模块的artifactId命名,这直接决定你后面改一个功能要动几个地方。
2.2 权限模型:三端共用一个用户表还是分开
美容系统的权限是核心难点。后台管理员、商家端店长、微信端客户,这三类主体的认证方式完全不同。常见做法是:后台和商家端共用一套sys_user+sys_role+sys_menu的 RBAC 模型,微信端单独用member表走 openid 登录。判断源码质量,就看它有没有把这两套体系混在一起。
-- 典型的三端权限表结构,重点看 role 和 user 的关联 -- 后台/商家端:基于角色的菜单权限 SELECT u.username, r.role_name, m.menu_name FROM sys_user u JOIN sys_user_role ur ON u.id = ur.user_id JOIN sys_role r ON ur.role_id = r.id JOIN sys_role_menu rm ON r.id = rm.role_id JOIN sys_menu m ON rm.menu_id = m.id WHERE u.user_type = 'MERCHANT'; -- 区分后台和商家端 -- 微信端:基于 openid 的会员体系,通常独立 SELECT m.nickname, m.openid, m.phone FROM member m WHERE m.store_id = 1001;这里的关键参数是user_type字段。如果源码里后台和商家端用同一个sys_user表但靠user_type区分,那商家端登录后必须做数据过滤,否则 A 店长能看到 B 店的数据,这是美容连锁系统最常见的越权漏洞。我一般会检查所有商家端的查询有没有带store_id条件,没有的话就是坑。
2.3 微信端登录:网页端同步小程序微信登陆的常见实现
热词里提到「网页端同步小程序微信登陆」,这在美容系统里很实际:客户可能在公众号网页预约,也可能在小程序预约,两边要能识别是同一个人。常见做法是用 UnionID 打通。微信端登录流程一般是:前端拿 code → 后端调微信接口换 openid 和 unionid → 查 member 表,有就登录,没有就注册。
// 微信端登录核心逻辑,注意 unionid 的存储 public Member wxLogin(String code) { // 1. 用 code 换 access_token 和 openid String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code"; JSONObject result = HttpUtil.get(url); String openid = result.getString("openid"); String unionid = result.getString("unionid"); // 关键:跨端识别靠它 // 2. 优先用 unionid 查,没有再用 openid Member member = memberMapper.selectByUnionId(unionid); if (member == null) { member = new Member(); member.setOpenid(openid); member.setUnionid(unionid); memberMapper.insert(member); } return member; }参数说明:appId和appSecret必须配在配置文件里,不要硬编码。unionid只有在微信开放平台绑定过公众号和小程序才会返回,如果源码里只存了 openid,那网页端和小程序端就是两个账号,这是很多免费源码的通病。排查时看member表有没有unionid字段,没有的话就得自己加。
3. 本地跑通的最小路径:数据库、依赖、启动顺序
3.1 数据库导入与配置修改的三个必改项
美容管理系统源码通常带一个.sql文件,导入 MySQL 后要改配置文件。必改的三项是:数据库连接、微信 appid、文件上传路径。少改一个都启动不了或者功能残缺。
# application.yml 里必须改的配置 spring: datasource: url: jdbc:mysql://localhost:3306/beauty_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379 # 美容系统常用 Redis 存 token 和预约锁,没有 Redis 会报错 wx: appid: 你的小程序appid secret: 你的小程序secret file: upload-path: /data/beauty/upload/ # Linux 下注意目录权限逻辑说明:serverTimezone不设会导致预约时间差 8 小时,这是血泪经验。Redis 如果源码里用来做登录 token 缓存,那必须启动 Redis,否则登录接口直接 500。upload-path在 Windows 下写D:/upload/,Linux 下要确保运行用户有写权限,否则上传头像和门店图片会失败。
3.2 启动顺序与端口冲突排查
三端如果是多模块,启动顺序一般是先common装到本地仓库,再启动admin,最后merchant和wx。端口默认都是 8080 的话会冲突,要在各自application.yml里改成 8081、8082、8083。
# 先安装 common 模块到本地 Maven 仓库 mvn clean install -pl common -am -DskipTests # 分别启动三个端,指定端口 java -jar admin/target/admin.jar --server.port=8081 & java -jar merchant/target/merchant.jar --server.port=8082 & java -jar wx/target/wx.jar --server.port=8083 & # 检查端口占用 netstat -tlnp | grep -E "8081|8082|8083"参数说明:-pl common -am表示只构建 common 模块及其依赖,-DskipTests跳过测试加快速度。如果启动报Table 'beauty_system.xxx' doesn't exist,说明 SQL 没导全,检查.sql文件里有没有CREATE DATABASE和USE语句。后台默认账号密码一般在 SQL 文件末尾的INSERT INTO sys_user里,常见是admin/123456。
3.3 微信端本地调试:reqable 抓包与域名配置
微信端本地调试最麻烦的是小程序要求 HTTPS 域名。开发阶段可以在微信开发者工具里勾选「不校验合法域名」,但真机调试就不行。热词里提到「reqable 能 pc 端微信小程序抓包吗」,答案是能,但要注意小程序走的是微信自己的网络通道,需要配置代理并安装证书。
# 本地起一个内网穿透把 8083 暴露成 HTTPS,方便真机调试 # 常见做法是用 frp 或 natapp,这里只讲配置思路 # 1. 微信开发者工具 -> 详情 -> 本地设置 -> 不校验合法域名 # 2. 真机调试时,把 request 合法域名配成你的穿透域名 # 3. reqable 抓包:设置系统代理 -> 安装根证书 -> 信任证书注意:抓包只用于调试自己的系统,不要用于任何非法用途。小程序端如果登录一直失败,先看wx.login拿到的 code 有没有传给后端,再看后端换 openid 的接口有没有报errcode: 40029,这个错误码表示 code 无效,通常是 appid 和 secret 不匹配。
4. 商家端与后台的功能边界:哪些功能该放哪端
4.1 商家端只做门店级操作,后台做连锁级配置
这是最容易做错的地方。商家端登录后,只能看到自己门店的预约、订单、美容师、排班。后台则能看到所有门店,并且能配置商品、优惠券、会员等级这些全局数据。如果源码里商家端能改商品价格,那就是设计缺陷。
| 功能 | 后台 | 商家端 | 微信端 |
|---|---|---|---|
| 门店管理 | 增删改查所有门店 | 只看本店 | 无 |
| 商品/服务项目 | 全局配置 | 只看本店可售 | 浏览下单 |
| 预约管理 | 查看所有 | 本店预约核销 | 发起预约 |
| 美容师排班 | 无 | 本店排班 | 查看可约 |
| 会员管理 | 全局会员 | 本店会员 | 个人中心 |
| 财务统计 | 连锁报表 | 本店流水 | 无 |
这张表的作用是帮你判断源码的功能划分是否合理。如果商家端有「商品管理」的增删改,那要么是源码把后台功能错放了,要么是权限没做细。我一般会先跑一遍商家端,看菜单里有没有不该出现的项。
4.2 预约核销的并发问题:Redis 锁怎么用
美容系统的预约是核心场景,多个客户同时约同一个美容师的同一时段,必须防超卖。常见做法是用 Redis 分布式锁或者数据库唯一索引。
// 预约防重复:Redis 锁 + 数据库唯一索引双保险 public Result bookAppointment(Long storeId, Long beauticianId, String timeSlot) { String lockKey = "book:" + storeId + ":" + beauticianId + ":" + timeSlot; // 1. Redis 锁,过期时间 10 秒防止死锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if (Boolean.FALSE.equals(locked)) { return Result.fail("该时段已被预约,请换一个时间"); } try { // 2. 再查一次数据库,防止锁失效后的并发 int count = appointmentMapper.countBySlot(beauticianId, timeSlot); if (count > 0) { return Result.fail("该时段已被预约"); } // 3. 插入预约记录,数据库唯一索引兜底 appointmentMapper.insert(new Appointment(storeId, beauticianId, timeSlot)); return Result.success(); } finally { redisTemplate.delete(lockKey); // 释放锁 } }参数说明:setIfAbsent的第三个参数是过期时间,必须设,否则一个请求挂了锁永远不释放。数据库唯一索引建在(beautician_id, time_slot)上,这样即使 Redis 锁失效,数据库也会拒绝重复插入。如果源码里没有这个唯一索引,高并发下一定会出现同一时段两个预约,这是必须自己补的。
5. 避坑与排查:这份源码最容易翻车的五个地方
5.1 启动报错 Unknown database:SQL 文件没建库
现象:启动时抛Unknown database 'beauty_system'。原因:.sql文件里只有建表语句,没有CREATE DATABASE。解决:手动建库CREATE DATABASE beauty_system DEFAULT CHARSET utf8mb4;再导入表结构。注意字符集要用utf8mb4,否则微信昵称里的 emoji 存不进去。
5.2 商家端登录后看到其他门店数据:store_id 过滤缺失
现象:用 A 店账号登录,预约列表里出现 B 店的记录。原因:Mapper 查询没带store_id条件,或者带了但前端传的store_id可以被篡改。解决:在 Service 层从 token 里取store_id,不要信前端传的。所有商家端查询强制加AND store_id = #{currentStoreId}。
5.3 微信端登录一直转圈:code 被重复使用
现象:小程序点登录没反应,后端日志显示invalid code。原因:wx.login拿到的 code 只能用一次,如果前端在onLoad和按钮点击里各调了一次,第二次就失效。解决:确保wx.login只在需要时调一次,拿到 code 后立即传给后端,不要缓存 code。
5.4 上传图片失败:目录权限和大小限制
现象:后台上传门店图片报 500,日志显示FileNotFoundException。原因:upload-path目录不存在或 Spring Boot 运行用户没写权限。解决:mkdir -p /data/beauty/upload && chmod 755 /data/beauty/upload,同时检查spring.servlet.multipart.max-file-size是否够大,默认 1MB 传不了高清图。
5.5 定时任务不执行:多实例重复跑
现象:预约提醒短信发了两次。原因:商家端部署了两个实例,@Scheduled任务在每个实例都跑。解决:用 Redis 锁或者 Quartz 集群模式,确保同一时刻只有一个实例执行。简单做法是在任务开头抢一个 Redis 锁,抢不到就跳过。
6. 二次开发进阶:把这份源码改成能接私活的版本
6.1 先做数据隔离,再做功能扩展
拿到源码想接私活,第一件事不是加功能,而是把多门店数据隔离做扎实。我一般会写一个 MyBatis 拦截器,自动给所有商家端 SQL 加上store_id条件,这样就不用每个 Mapper 手写。
// MyBatis 拦截器:自动给商家端查询加 store_id @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class StoreInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; // 只拦截商家端的 Mapper,后台和微信端不处理 if (ms.getId().contains("merchant")) { Object param = invocation.getArgs()[1]; if (param instanceof Map) { Map<String, Object> map = (Map<String, Object>) param; // 从 ThreadLocal 取当前登录用户的 store_id map.put("storeId", UserContext.getStoreId()); } } return invocation.proceed(); } }逻辑说明:UserContext用 ThreadLocal 存当前请求的store_id,在登录拦截器里塞进去。这样所有商家端查询自动带上storeId参数,Mapper XML 里写AND store_id = #{storeId}即可。参数上注意ms.getId().contains("merchant")这个判断,要和你项目的包名对应,写错了会拦截到后台查询导致后台看不到数据。
6.2 验证方法:用两个门店账号交叉测试
改完隔离逻辑,必须验证。开两个浏览器无痕窗口,分别登录 A 店和 B 店账号,在 A 店创建预约,然后去 B 店看能不能查到。查不到才算通过。再直接调接口,把请求里的storeId改成对方门店的,看后端是否忽略前端传值、只用 token 里的。这一步能挡住 90% 的越权问题。
| 测试项 | 预期结果 | 失败原因 |
|---|---|---|
| A 店查预约 | 只返回 A 店数据 | 拦截器没生效 |
| 篡改 storeId 参数 | 仍返回本店数据 | 用了前端传值 |
| 后台查预约 | 返回所有门店 | 拦截器误拦后台 |
| 微信端查预约 | 只返回自己的 | 会员体系没隔离 |
6.3 一个具体技巧:用枚举管理预约状态
美容系统的预约状态很多:待确认、已确认、已到店、已完成、已取消、爽约。用魔法数字 0/1/2 后期维护会疯。我一般会定义一个枚举,并在数据库存字符串。
public enum AppointmentStatus { PENDING("待确认"), CONFIRMED("已确认"), ARRIVED("已到店"), COMPLETED("已完成"), CANCELLED("已取消"), NO_SHOW("爽约"); private final String desc; AppointmentStatus(String desc) { this.desc = desc; } public String getDesc() { return desc; } }这样前端传CONFIRMED,后端存CONFIRMED,日志里一眼能看懂。改状态时用AppointmentStatus.valueOf(status)校验,传了非法值直接抛异常,比if (status == 1)可靠得多。
这份源码我前后改过三版,最大的教训是:不要一上来就加功能,先把权限和数据隔离跑通,否则后面每加一个功能都要回头补隔离,越补越乱。希望帮到你。
本文还有配套的精品资源,点击获取