☰
SpringBoot+Android美容美发预约系统App开发部署全攻略
2026/10/7 21:43:22 网站建设 项目流程

做毕设和课程设计这些年,我接过不少类似的题,其中“美容美发服务系统App”这种“springboot + Android”的组合,真的属于长盛不衰的经典款。乍一看这题目好像没什么新意,但仔细拆一下:前端要跑安卓App、后端要撑一套预约下单流程、中间还要处理图片、订单状态、会员余额这些麻烦事,业务链路完整度其实相当高。很多同学拿到的源码、文档、讲解视频都是配套好的,但真到了自己部署和答辩的时候,要么卡在环境上,要么对代码一知半解,最后只能硬背PPT。

这篇文章我不打算照着项目文档再念一遍功能清单,而是从一个“拿到这套代码之后该怎么入手”的角度,把springboot后端、Android客户端、数据库设计、调试过程和常见坑都捋一遍。内容会覆盖整包部署、核心模块解析、接口对接思路、真机联调技巧,还有答辩时容易被追问的细节。不管你是打算二次开发,还是只想顺利跑通然后写论文,按这个顺序走一遍,心里基本就有底了。

1. 项目整体设计与思路拆解

1.1 这个选题“稳”在哪里

一个毕业设计或课程设计的好坏,不只看技术新不新,更要看业务逻辑能不能自圆其说。美容美发服务系统这个题目,天然把用户、技师、门店、订单、支付、评价这些要素串在了一起,几乎覆盖了一个小型交易系统的主干流程。对于需要快速出活的学生来说,它不像“电商秒杀系统”那样对并发有要求,也不像“推荐系统”那样需要跑算法,难度曲线平缓但完整度高,非常容易在答辩时把业务讲清楚。

更重要的是,这类题目能自然地划分出三种角色:普通用户、门店/技师端、平台管理员。有了角色划分,权限控制和接口设计就有话可讲,论文里的用例图、时序图、ER图也都好画。评委和导师见过的题目很多,对这种“麻雀虽小五脏俱全”的系统接受度很高,不会追问太多高深的底层原理,但会重视业务流程的闭合性。比如你预约了一个发型师,订单状态怎么流转、取消后库存时段怎么释放、充值余额有没有消费流水,这些比“我用到了Redis缓存”更能体现实际设计能力。

1.2 技术选型背后的真实逻辑

技术栈选的是springboot + Android原生,这个组合在众多选题里出现频率极高,原因很实在:springboot已经成为Java后端开发的事实标准,哪怕不写复杂微服务,单机就足够支撑这类预约系统;Android端用原生语言开发,不需要额外的跨端框架,对学生来说可控性强,打包也好演示。

后端部分,常规配置就是springboot 2.x + MyBatis-Plus + MySQL。MyBatis-Plus最近的几个版本对单表CRUD的简化非常明显,BaseMapper自带增删改查,省掉了一堆XML映射文件。这个项目如果是多表关联,比如订单关联服务和技师,还是需要用注解或XML写join。这里要提醒一下,拿到源码后先看pom.xml,确认数据库方言、分页插件、以及是否引入了druid连接池。很多同学启动失败,都是因为默认的HikariCP和MySQL驱动版本不兼容,或者时区配置没写。

Android端请求框架选型,常见的有三种:HttpURLConnection、OkHttp、Retrofit。正规一点的源码都会封装Retrofit,配合Gson解析统一返回体。如果不是很熟悉Retrofit,至少要把OkHttp的拦截器看一遍,因为登录token的添加就是在拦截器里完成的。图片加载十有八九是Glide,Glide的缓存策略、占位图设置都是面试和答辩的加分点。整体来看,这套技术选型没有一项是“炫技”,但每一项都是当前Android开发的主流答案,写成论文也不会被质疑。

1.3 核心模块与数据库表怎么拆

系统本质上围绕“预约”展开,我在看这类项目源码时,习惯先找订单表,从订单表出发反推所有关联表。一个合理的预约流程是:用户浏览门店和服务项目,选定技师,选择时间,生成带状态的订单,到店后由技师或管理员确认核销,最后用户评价。

对应到数据库,最少要有这几张表:

表名核心职责关键字段
user用户信息与身份id, phone, password, nickname, avatar, role, balance
store门店基本信息id, name, address, phone, cover, business_hours
service_item服务项目id, store_id, name, price, duration, cover, description
barber技师档案id, store_id, name, avatar, skill, level, intro
appointment预约订单id, user_id, barber_id, item_id, appoint_time, status, remark
comment评价记录id, appointment_id, user_id, content, score, create_time
recharge_record充值/消费流水id, user_id, amount, type, balance_after, create_time

拿到源码后,先打开数据库脚本,对照这套表结构理解一遍。如果发现缺表或者字段对不上,多半是版本不一致,要以最新sql文件为准。特别要注意status字段的取值,比如0待接单、1已确认、2已完成、3已取消、4已评价,这类状态定义一般写在代码常量类里,改动时前后端要同步。

2. 核心功能细节解析与实现要点

2.1 注册登录与Token会话保持

用户端登录是整个App的第一道门槛,很多源码在这块的处理方式都不一样。简单项目会用手机号加密码直接查表,登录成功后在内存或本地保存一个userId;规范一些的项目会引入JWT,后端签发token,Android端收到后存储到SharedPreferences,后续请求都带上这个token。后者的好处是后端接口无状态,适合答辩时讲“会话保持”和“接口安全”。

密码加密方面,明文存储是大忌,至少要使用MD5加盐或BCrypt。如果源码里直接compare(password, row.getPassword())且数据库存的是明文,你可以在论文里强调自己做了改进,改用Spring Security提供的BCryptPasswordEncoder加密。这个改进不需要改动太多代码,只需要加密工具类和注册登录两处调用,却能成为答辩亮点。如果要深挖,可以顺带说明BCrypt自动加盐、计算成本可调的原理,但点到为止就好。

接口层面的权限控制,往往通过一个HandlerInterceptor实现。preHandle里取出请求头中的token,调用JwtUtil解析,如果有效就放行,无效就返回401。这里需要注意,在线程内无法直接从拦截器取到用户对象,很多项目会通过ThreadLocal传递用户信息,拦截器解析完token后set进去,Controller里用UserHolder.get()获取。这是实际工程里非常常见的写法,如果你发现自己的源码没有这层设计,可以考虑补上,也顺带解决“订单记录里如何知道当前用户是谁”的问题。

2.2 预约下单与时段防冲突

预约是这个系统的核心业务,也是最容易被评委追问有没有“认真考虑”的地方。很多简单实现里,用户提交预约时只做一次“当前时间是否空闲”的判断,然后直接插入订单。问题在于,如果两个用户同时在选同一个技师的同一时段,服务端没有加锁或唯一索引,就可能出现超卖式的冲突。

处理方案并不复杂,核心是给预约增加“防重约束”。比如在appointment表上,为(barber_id, appoint_time, status)建立一个唯一逻辑,或者在创建订单前对指定技师和时段执行一次带条件的count查询,并配合事务来控制。更稳妥的做法是:先执行“UPDATE预约状态 SET status=已确认 WHERE id=? AND status=待确认”,通过affected rows数量判断当前订单是否被其他操作抢先,类似乐观锁的思路。把这条逻辑写在论文的“并发控制”小节里,说服力会强很多。

订单状态是另一种容易出问题的地方。我见过不少源码,用户取消订单只是改变本地状态,但没有处理后续的时段释放。严谨的流程里,取消操作要唤醒对应的时段状态,并且如果项目做了技师排班,还要释放排班占用。这部分要结合代码逐行看:状态机流转是否完整,取消订单时是否回调了releaseBarberSlot之类的方法,前端是否有对应的按钮置灰逻辑。如果能把这个闭环讲明白,答辩基本就稳了。

2.3 会员卡与余额流水

美容美发业务里,会员卡充值几乎是标配,也因此这类系统十有八九带余额支付和充值功能。余额字段放在user表里是很直接的设计,但只有余额没有流水,会在对账时说不清楚。合理做法是单独建一张recharge_record表,type字段区分充值、消费、退款、赠送,每次余额变动就插入一条流水,余额本身可以用balance字段冗余存储,查询时以流水表为准做对账。

这里有一个工程细节:扣减余额的接口必须是事务性的。如果先更新余额再插入流水,中途抛异常会导致两边不一致;反之先插流水再更新余额,也存在更新失败的风险。正确做法是使用@Transactional注解把余额扣减和流水记录放在同一方法内,并确保异常能触发回滚。如果源码里没有事务控制,这也是一个值得写进“系统改进”章节的点。

Android端支付通常不会接真实微信支付宝,而是模拟支付。大多数项目就是充值页输入金额后直接调用后端的recharge接口。注意讲解时要把这个说清楚:这是教学演示项目,不是商用的支付系统。答辩时如果被问“真的对接微信支付怎么做”,你可以简单说需要申请商户号、生成支付参数、回调验签,并补充说自己查阅过相关文档,比直接说“没做过”要好得多。

2.4 管理端该有的样子

管理端在不少项目里会被简化成一个藏在App里的管理员标签页,或者干脆用电脑浏览器访问一个简单的Web管理页面。从技术上看,springboot天然支持同时输出REST接口和页面模板,所以管理端做成Web方式并不难。如果是纯App内管理,那就要考虑登录角色区分,管理员登录后跳到一个不同的Fragment集,这部分代码通常写在MainActivity的role判断里。

管理功能主要有四块:门店管理、服务项目管理、技师管理、订单管理。门店和服务项目一般就是增删改查,配一个图片上传;订单管理重点关注按状态筛选和手动确认核销;技师管理里可能会包含排班表,排班表如果做得简单,就用日期字符串格式存储。拿到源码后,我建议先把管理端接口梳理一遍,画一张“接口-功能”对照表,方便论文里画系统功能结构图,也方便理解后端Controller层到底写了哪些接口。

3. 实操过程与核心环节实现

3.1 环境准备与项目结构速览

做这类项目最怕的就是环境不一致。建议先统一版本:JDK用1.8或11,Maven用3.6以上,MySQL用5.7或8.0,Android Studio建议用较新的稳定版,SDK版本根据项目minSdk和targetSdk配置。整个项目是前后端分离的,后端是springboot工程,前端是Android工程,两者要分开导入IDE。

后端导入时,用IDEA打开pom.xml所在目录,等待依赖下载,如果公司或学校网络慢,可以配置阿里云镜像。Android工程用Android Studio导入,这里要注意Gradle版本与JDK版本的匹配,Gradle 7.x需要JDK11+,如果你本机是JDK8,启动时会有报错。项目根目录下一般有README或者部署文档,先阅读这部分,确认数据库脚本位置和默认端口。

一个典型的springboot项目目录结构是这样的:

src/main/java ├─ com.example.beauty │ ├─ controller // 接口层 │ ├─ service // 业务层 │ ├─ mapper // 数据访问层 │ ├─ entity // 实体类 │ ├─ config // 配置类 │ ├─ interceptor // 拦截器 │ └─ utils // 工具类 src/main/resources ├─ application.yml ├─ mapper // MyBatis XML └─ db.sql

要注意的是,不同源码的包名不太一样,有的叫com.example.demo,有的叫com.beauty.app。不统一没关系,按功能模块去识别即可。Android端目录则按Activity、Adapter、Fragment、utils、api包区分,先看api包里的RetrofitService或OkHttpUtil,你就能找到所有后端接口的调用点。

3.2 后端接口落地示例

以“查询门店列表”为例,一个标准接口从下到上是这样的。Controller层:

@RestController @RequestMapping("/api/store") public class StoreController { @Autowired private StoreService storeService; @GetMapping("/list") public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return Result.success(storeService.pageStore(page, size)); } }

Service层:

@Service public class StoreService { @Autowired private StoreMapper storeMapper; public IPage<Store> pageStore(Integer page, Integer size) { Page<Store> p = new Page<>(page, size); return storeMapper.selectPage(p, null); } }

如果你用的是MyBatis-Plus,Mapper层几乎不用写任何SQL:

@Mapper public interface StoreMapper extends BaseMapper<Store> { }

这里的Result是统一返回体,一般包含code、message、data三个字段。建议你把Result类单独看一遍,因为Android端解析数据的格式完全取决于这个类。有的项目用code=200表示成功,有的用code=0,还有的直接返回boolean,解析前一定要对应好,否则会出现“列表加载不出来但后台日志没有报错”的情况。

3.3 Android端的请求封装

Android端请求封装的教科书写法是这样的:定义API接口,加Retrofit注解,然后通过单例获取服务对象。举个例子:

public interface ApiService { @GET("api/store/list") Call<Result<List<Store>>> getStoreList(@Query("page") int page, @Query("size") int size); @POST("api/user/login") Call<Result<LoginResponse>> login(@Body LoginRequest request); }

Retrofit单例的创建:

public class ApiClient { private static final String BASE_URL = "http://10.0.2.2:8080/"; private static ApiService apiService; public static ApiService getInstance() { if (apiService == null) { synchronized (ApiClient.class) { if (apiService == null) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); apiService = retrofit.create(ApiService.class); } } } return apiService; } }

注意BASE_URL这个地方,如果用的是模拟器,后端跑在本机,要用10.0.2.2指向宿主机;如果用的是真机,要改成电脑在局域网里的IP,比如192.168.1.100,并且要保证手机和电脑连接的是同一个Wi-Fi。很多同学第一次调试时,后端接口在浏览器里能打开,App里却总是超时,多半就是IP写错了。

Gson在解析时,如果实体类的字段名和后端返回的JSON不一致,可以使用@SerializedName注解做映射。这个细节在项目里很常见,因为后端习惯用下划线命名,而Android端习惯用驼峰,比如mobile和userMobile。当发现某个字段解析出来是null时,先检查是不是忘记加注解了。

3.4 图片上传与静态资源访问

美容美发系统的门店相册、服务项目图片、用户头像都涉及图片上传。后端的经典做法是:把文件保存到服务器某个目录,同时把访问路径存入数据库。在application.yml里配置静态资源映射,比如:

spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + System.getProperty("user.dir") + "/upload/"); } }

Android端用Glide加载图片时,要注意URL拼接。如果后端返回的是相对路径“/upload/1.jpg”,前端需要拼上完整的服务器地址。比较规范的做法是在统一Response解析时对图片字段做处理,或者干脆在后端直接返回完整URL。调试时常见的问题是图片加载404,原因要么是相对路径没拼对,要么是磁盘路径跟代码里的路径不一致。

3.5 后端与Android端的调试方法

调试是整个项目最耗时间的环节,但也是有套路可循的。后端调试优先使用断点:在IDEA里点击方法左侧行号打断点,以debug模式启动,请求进来后会停在断点处,可以逐行查看变量。如果项目里使用了MyBatis-Plus,可以在application.yml中打开SQL日志:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这样控制台会打印每一条执行的SQL,排查“查不到数据”“字段对不上”这类问题非常高效。

Android端调试首先看Logcat,在代码里使用Log.d(TAG, "message")打日志。数据解析失败时,把原始JSON字符串先打印出来,确认后端到底返回了什么。也可以用断点调试Android代码,比如在onResponse回调里打断点,直接看response.body()的原始内容。接口请求层面的问题,我习惯在电脑上用抓包工具或者直接在OkHttp中加日志拦截器。OkHttp的拦截器非常方便:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY);

加上以后,每个请求的URL、请求头、请求体、响应体都会完整打印在Logcat里,90%的接口问题靠它能定位到。

4. 常见问题与排查技巧实录

4.1 端口被占用与启动失败

springboot默认端口是8080,如果本机已经有服务占用了8080,就会启动失败。报错一般长这样:Port 8080 was already in use。解决办法有几种:直接改application.yml里的server.port,换成8081、8082等;或者在终端里查一下是哪个进程占用了端口。Windows下用:

netstat -ano | findstr 8080 taskkill /pid 对应PID /F

还有一类启动失败跟数据库连接有关,报错包含Communications link failure或者Access denied。前者检查MySQL服务是否启动、端口是否是默认的3306、地址是否写对;后者检查账号密码和授权,MySQL 8.0用户需要确认是否开启了mysql_native_password插件。很多macOS版本的MySQL默认只允许root在本地登录,连接时记得在URL里加上useSSL=false和serverTimezone=Asia/Shanghai,否则时区报错会让人一头雾水。

4.2 Android网络权限与明文流量问题

App调接口报错时,千万不要无脑怀疑代码,先检查AndroidManifest.xml里有没有加网络权限:

<uses-permission android:name="android.permission.INTERNET" />

新版Android对明文HTTP的限制非常严格,targetSdkVersion如果大于等于28,使用http://地址会被系统直接拦掉,报错一般是Cleartext HTTP traffic not permitted。最简单的处理方式是在AndroidManifest.xml的application节点上加上:

android:usesCleartextTraffic="true"

更优雅的方式是配置networkSecurityConfig,允许特定域名的明文流量。答辩时建议说后者,因为能体现你对Android 9网络安全的了解。另一个容易被忽略的点是,如果App里用到了定位或者读取相册,还要检查对应权限和动态权限申请的代码。如果源码里写的是targetSdkVersion 28以下的逻辑,在高版本手机上要单独适配运行时权限。

4.3 模拟器与真机访问后端差异

模拟器访问宿主机用10.0.2.2,但10.0.2.2只对Android Studio自带的模拟器有效。如果你用第三方模拟器,比如雷神模拟器或夜神模拟器,它们通常把宿主机地址映射成了10.0.3.2或者别的地址,配置方式都不一样,网上很多教程在10.0.2.2这点上说得很笼统,容易踩坑。

最省事的办法还是用真机。手机和电脑连同一个Wi-Fi,后端启动后把BASE_URL改成电脑的局域网IP,手机浏览器先访问一下,如果能看到JSON返回,说明网络通。但注意,笔记本或台式机如果开了防火墙,手机可能无法访问8080端口,需要在防火墙入站规则里放行对应端口。如果实在不方便真机,也可以不依赖模拟器地址映射,而是使用USB连接后执行adb reverse tcp:8080 tcp:8080,然后再把BASE_URL改为http://localhost:8080/,这样手机请求就会通过USB隧道转发到电脑的8080端口,速度比Wi-Fi还快。

4.4 数据库乱码与字段对不上

中文乱码绝大多数问题出在数据库编码。建库的时候要指定utf8mb4:

CREATE DATABASE beauty CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

同时检查connection URL里有没有characterEncoding=utf8。另外,MySQL 8.0与5.7的驱动类名也不一样,5.7用com.mysql.jdbc.Driver,8.0用com.mysql.cj.jdbc.Driver。如果项目里用的是旧驱动,换成新驱动后DataSource会自动识别,但最好手动确认一下。

“字段对不上”一般是实体类属性与表字段命名不一致导致的。用MyBatis-Plus时,默认开启驼峰转下划线,所以实体类里写storeName,对应数据库字段store_name才能正确映射。如果看到某个字段怎么查都是null,要么是resultMap没配,要么是表里没这个字段,先拿SQL在Navicat里跑一遍,再回代码里找问题。

4.5 启动后页面能打开但接口404

后端能启动,但Android端请求一直404,这种现象很常见。先分清是Controller路径问题还是请求方式问题。Postman或浏览器里直接访问接口,确认接口本身是好的。如果浏览器通了,App不通,检查Retrofit里的@GET、@POST注解与后端RequestMapping路径是否完全一致,大小写、斜杠都要严格对应。

另一个原因是前端请求带了特殊参数,比如Content-Type。如果Android端用@Body传对象,Retrofit默认是application/json;如果后端使用@RequestParam接收,就需要用@Query或form表单方式。接口签名对不上,后端甚至不会进入Controller方法就直接返回404和500。遇到这类问题,用OkHttp日志拦截器打印请求体,和后端接收的参数名逐一比对,一般几分钟就能定位。

5. 文档与讲解视频的使用思路

5.1 把项目文档改造成自己的

拿到配套的Word文档或PDF,不建议直接改个名字就提交。导师通常看得多,一眼就能认出是不是模板。但也没必要全盘重写,我的经验是:框架和图表可以借鉴,业务流程、数据库设计、代码说明、测试结果这四部分必须结合源码重新组织。先把系统划分为用户模块、门店模块、预约模块、支付模块、评论模块,然后逐块从源码中找到对应的Controller和Service,截图关键代码并配上自己的注释。

论文里的功能结构图不要全平台照搬,至少要把图表重画一遍,配色可以换掉,模块命名也能微调。需求分析部分要结合自己部署时遇到的问题补充非功能性需求,比如“系统应支持同一时段预约互斥处理”“图片上传后支持在线预览”。测试截图用自己运行起来后的项目跑一遍,把App里实际的效果图替换进文档,这样整体可信度会高很多。

5.2 讲解视频与答辩准备

讲解类的视频一般有两类:一类是项目演示,另一类是代码讲解。演示视频里会操作App完成注册、登录、预约、支付、查看订单等流程,这部分先看两遍,然后自己在模拟器或真机上重复做几遍,确保能顺手演示。代码讲解部分,视频里往往会跳到某个类或者某个方法去解释,建议按照视频的思路,把对应代码位置标注出来,形成一套自问自答的讲稿。

答辩时最常被问的几个问题,提前准备就不会慌:

常见问题推荐回答思路
为什么选择springboot?简化配置、内嵌Tomcat、微服务社区成熟,适合中小型项目快速开发。
用户登录怎么保证安全?密码BCrypt加密,登录后签发JWT,请求在拦截器中校验token。
如何防止同一技师同一时段重复预约?事务控制 + 预约状态乐观锁更新,同时保证更新后释放时段的原子性。
余额充值是怎么设计的?事务中同时完成余额更新和流水插入,查询时以流水表为对账依据。
Android端如何向后端传token?在OkHttp拦截器中添加请求头Authorization: Bearer {token}。

这里多说一句:回答问题不用讲得特别长,但要把原理和实现细节带到。比如问JWT时,顺便说说为什么不把用户信息全部放token里,因为token一旦泄露内容就能被解出来,所以只保存userId和过期时间,需要用户信息时再查库。这种细节会让人觉得你真的动手改过代码,而不是只会读PPT。

5.3 二次开发可以从哪下手

如果你想在原有基础上做一些差异化功能,我建议优先从以下三个方向入手。第一是在预约模块加上简单的时间段选择控件,后端生成当天可预约的时段列表,前端用RecyclerView展示,看起来就比固定时间输入高级很多。第二是数据可视化,后端增加统计接口,统计最近一周、一个月各门店的预约量,前端用MPAndroidChart画柱状图或折线图,这也是很多论文加分的点。第三是消息推送,用极光推送或Firebase Cloud Messaging实现预约成功后的通知,虽然接入成本稍高,但效果非常直观。

这三个方向都不需要推翻现有架构,只是在接口和页面层面做加法。改完了以后,配合Scrum的方式记录迭代过程,把每次提交后面的动机写清楚,放在论文“系统实现”章节里,这样整篇论文会有真实的努力痕迹,比堆砌技术名词扎实得多。

个人实操中的后话

这套项目我从拿到手到跑通,花了大概一个周末。第一次启动报错多半是MySQL版本和驱动不一致,Android端用第三方模拟器卡在IP地址上,调好以后整个流程跑下来也就半小时。如果你也是刚拿到这类源码,我的建议是别急着改代码,先把环境跑通,对照数据库表把核心流程的代码读一遍,再想想要加什么功能。项目本身不难,但它是一套能完整走起来的前后端工程,把每一层打通的感觉,比任何教程都直观得多。后面不管是继续加功能,还是拿去面试,这台地基都很值得花时间打牢。

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

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

立即咨询