上周刚帮一个学弟把一套体检预约App项目整理成可以完整交付的状态:源码、讲解视频、设计文档(也就是常说的LW)全齐了,前后捏了大概两周时间。这个项目表面上看是个典型的前后端分离管理系统,但真正做下来你会发现,它最难的地方不在CRUD,而在“App端怎么跟管理后台配合、预约流程怎么做才不冲突、原型设计怎么落到代码里”。今天就把这套基于Java+SpringBoot的体检预约App和管理后台交互原型设计,从业务拆解、数据模型、关键流程到源码结构,全部掰开聊一遍。
这套东西适合三类人:一是正在做毕设的学生,直接抄作业;二是想拿SpringBoot做完整项目练手的开发者,可以学到预约类业务的完整处理手法;三是想了解“交互原型+真实系统”如何相互补充的产品型开发。不管你是刚学完JavaWeb还是已经工作一两年,看完应该都能对这类系统有个整体把握。
1. 项目整体设计:体检预约系统到底在做什么
1.1 业务场景与用户角色拆解
体检预约系统不是一个新鲜概念,但很多院校的毕设题里一直有它的位置,原因很简单:业务链路完整、角色分明、有并发场景、有前端App又有后台管理,几乎能把软件开发课程里的核心知识点全部覆盖。
先理清业务场景。某个体检中心过去主要靠线下排队、电话预约,高峰期窗口拥挤,客户体验差,检前登记要手动填表,检后报告也要现场领取。线上化之后,用户通过App完成注册登录、选择体检套餐、预约分院和时间段、查看体检报告;运营人员在管理后台维护套餐、设置每天的号源数量、管理订单、录入报告。
系统涉及三类角色:用户(C端,使用App)、管理员(B端,使用后台)、还有潜在的体检中心业务人员(比如护士/医生,通过后台或子账号操作)。在原型设计阶段,这三类角色的操作路径必须清晰分开。App端重流程引导,后台重数据管理和操作效率。两者的交互关系则是通过一套RESTful API打通。
我在给学弟整理的时候,特别跟他强调了一个点:不要把“后台”做成只有管理员自己看的玩具。真正的管理后台必须能支撑“配置-查看-操作-反馈”的完整闭环,比如管理员改了一个套餐价格,App端立刻能看到变化;管理员把某个时间段号源调低,用户端预约时就要显示“剩余1个”。这种闭环,才是这个项目区别于普通增删改查作业的灵魂。
1.2 技术选型:为什么是Java+SpringBoot组合
技术选型是这套项目最容易被答辩老师追问的地方,你需要能说清楚“为什么”。
后端用Java+SpringBoot,理由很实在。SpringBoot把Spring MVC、自动配置、内嵌Tomcat这些东西全部打包好了,一个main方法就能跑起来,开发效率极高。Java本身生态成熟,招人好招,资料好查,对毕设项目来说,遇到问题基本都能在搜索引擎里找到现成答案。SpringBoot自带的starter机制让集成MyBatis、Redis、JWT这些组件变得非常轻量。
App端为什么不做原生而用Uniapp?很简单,原生Android开发周期长,而且毕设通常要求“App”能装到手机上看效果。Uniapp一套代码可以打包成Android/iOS/H5,同时支持微信小程序,演示的时候可以直接跑在浏览器里,也可以真机预览,灵活度很高。更重要的是,Uniapp用vue语法开发,对前端基础要求不算高,学起来比原生快得多。
管理后台用Vue+ElementUI,这是目前最经典的后台管理组合。ElementUI的表单、表格、日期选择器、弹窗组件可以直接用,做后台管理页面能省一大半工作量。整个项目前后端分离:SpringBoot只提供JSON接口,App和后台分别调用,结构清晰,也方便分工协作。
1.3 App端与管理后台的交互链路
理解了角色,我们得再画一遍交互链路,这在设计文档里也是核心章节。用户打开App,首先看到的是首页推荐体检套餐,点击某个套餐进入详情页,选择“预约”,此时会跳到预约页面:选择分院、体检日期、时间段。提交预约后,后端生成订单,同时扣掉该时段的号源。用户可以在“我的预约”里看到订单状态:待支付、已确认、已完成、已取消。体检完成后,后台录入报告,用户端收到“报告已出”的状态提醒,点开就能查看报告摘要。
管理后台的交互链路是反过来的。管理员登录后看到数据看板:今日预约量、本周流量、套餐销量。然后进入“排班管理”为未来N天配置号源,比如周一上午50个号,下午30个号。接着在“订单管理”中查看用户下的订单,支持核销(用户到店后扫码/输入订单号)、改期、取消。最后在“报告管理”中为已完成的订单录入体检结果,用户端刷新后就能看到。
这里有一个很容易被忽略的交互设计点:状态流转。所有页面上的按钮,都应该根据当前状态动态变化。比如订单状态是“待支付”时,App端显示“去支付”按钮;状态是“已确认”时,显示“去体检”的提示;状态是“已完成”且报告未出时,显示“报告生成中”。原型设计阶段就要把这些状态流转画清楚,否则开发的时候前后端很容易因为流程对不上而返工。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能模块与数据模型设计
2.1 用户端App的功能边界
用户端App的功能不要贪多,但每个功能都要完整。我建议把App端划分成四大模块,正好对应底部Tab栏:首页、预约、订单、我的。
首页:展示推荐套餐、轮播图、公告,搜索框支持按套餐名称/分类搜索。套餐卡片上要显示价格、适用人群、已预约人数,营造真实感。点击进入套餐详情,展示体检项目列表,比如一般检查、血常规、肝功能、彩超等,以及原价、优惠价、适合人群说明。
预约:这是核心模块。用户选择套餐后进入预约流程,流程一般包含四步——选分院、选日期、选时段、确认订单并提交。时间选择上,后端只返回“可用时段”,比如某个分院某天上午还有多少个号,如果满了就直接置灰不可点击。提交订单时,后端做二次校验,防止前端绕过限制。
订单:列出用户所有订单,按状态分Tab:全部/待支付/待体检/已完成/已取消。订单详情页展示套餐信息、预约分院、体检日期、订单状态、取消/改期按钮。这里需要注意,改成和取消都受时间限制,比如体检前一天18:00后不能取消,这个规则要在前后端同时校验。
我的:用户信息、登录密码修改、体检报告列表、常见问题反馈。报告列表展示每次体检的结果摘要,点击查看各指标详情。
2.2 管理后台的核心功能
管理后台面向运营人员,功能要覆盖业务完整闭环。我通常把后台拆成六个页面:数据看板、套餐管理、排班管理、订单管理、用户管理、报告管理。
数据看板使用ECharts展示统计图:预约趋势折线图、套餐销量饼图、分院预约量柱状图。这些数据接口聚合查询SQL不算复杂,但必须有,因为这是答辩时最直观的加分项。
套餐管理:套餐列表、添加/编辑套餐、设置套餐状态(上架/下架)、查看套餐下的体检项目。套餐删除通常做逻辑删除或下架处理,毕竟历史订单里关联着套餐快照。
排班管理:以日历形式展示日期,点击某一天设置各分院的号源总数和时段分配。比如将上午拆成09:00-10:00、10:00-11:00两个时段,每个时段单独设置号源数。排班的逻辑直接影响预约时的余号计算,是后台最重要的功能之一。
订单管理:支持按订单号、手机号、状态搜索订单,对订单执行核销、改期、取消操作。核销操作要记录操作时间和操作人,方便溯源。
用户管理:查看注册用户列表、用户体检次数,支持禁用账号。这个模块比较简单,但能体现权限意识。
报告管理:选择已完成订单,录入体检报告;支持上传PDF报告或逐项录入指标;录入完成后用户端才能看到报告状态。
2.3 数据库设计:从套餐、订单到体检报告
数据库设计决定这个项目能走多远。我给你列一份核心表结构,可以直接拿去建库。
用户表(t_user):id、username、password(BCrypt加密存储)、phone、gender、age、create_time、status。
体检套餐表(t_package):id、name、category、price、original_price、description、suitable_people、status、cover_image、create_time。
套餐项目表(t_package_item):id、package_id、item_name、item_desc、参考价格。这是一对多关系,一个套餐包含多个体检项目。
分院表(t_branch):id、name、address、phone、business_hours。
排班表(t_schedule):id、branch_id、work_date、time_slot、total_quota、used_quota、status。其中work_date和time_slot联合起来能唯一确定某个分院某个时段。每天可以有多条记录,每个时段一条。
订单表(t_order):id、order_no、user_id、package_id、package_snapshot(JSON或文本,保存下单时的套餐名称和价格)、branch_id、schedule_id、examine_date、time_slot、status、pay_status、create_time、update_time、remark。这里之所以要保存package_snapshot,是因为套餐价格以后可能调整,但历史订单必须按下单时价格展示,这是很经典的冗余设计。
体检报告表(t_report):id、order_id、user_id、report_data(JSON格式,存放指标)、conclusion、doctor_advice、create_time。
订单改期记录表(t_order_operation_log):id、order_id、operation_type(改期/取消/核销)、operator_id、operator_name、create_time、detail。
外键关联不要物理外键,逻辑外键即可。理由很实际:物理外键在高并发插入和删除时容易造成锁竞争,而且后期分库分表或迁移数据时非常痛苦,字段索引查询已经足够。
3. 关键流程的工程化实现
3.1 体检预约的并发冲突处理
预约功能是这套项目的技术难点,也是最容易被问到的点。想象一下,某个热门的周一上午时段放了50个号,很多人同时抢,数据库如果只是简单扣减库存,就可能导致超卖——也就是最终生成订单的数量超过50个。
推荐的处理方式是“乐观锁 + 逻辑扣减”组合。在排班表加一个version字段,或者直接在SQL里带条件更新。核心语句类似:
sql复制update t_schedule set used_quota = used_quota + 1, version = version + 1
where id = #{scheduleId} and used_quota < total_quota;
执行这条SQL时,数据库会锁定对应的行,并且只有used_quota小于total_quota时更新才成功。如果影响行数为0,说明号源已经被抢完,直接抛出业务异常返回“该时段已约满”。这个方案不依赖Redis也能保证不错卖,实现简单,适合毕设和中小型项目。
同时要在订单表上做防重约束:同一个用户、同一天、同一个分院,只能有一条有效订单。可以用唯一索引或提前查询判断。实际开发中,我在下单接口上用了一个简单的synchronized锁(按userId维度),再加上数据库唯一索引兜底,双保险。虽然分布式环境下这样不够严谨,但单机部署完全够用。
3.2 基于JWT的登录状态管理
App端和管理后台都需要登录认证。我用的是JWT(JSON Web Token),它的好处是服务端无状态,App端只要在请求头带上token,后端拦截器校验通过就放行。
登录流程:用户提交手机号/用户名和密码,后端校验通过后生成JWT,将userId、角色等信息放入token,设置过期时间(比如2小时)。返回给前端后,前端把它存在本地存储,每次请求时放进Authorization头。SpringBoot端写一个拦截器,排除登录和注册接口,其他接口统一校验token。
有个细节要特别提醒:不要把密码直接放token里,也不要放敏感信息,token一旦泄露有风险。另外要给拦截器配置白名单,比如套餐列表、轮播图这些不登录也能看的接口,让用户先浏览再引导登录。
拦截器实现大概长这样:
java复制public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
throw new BusinessException("未登录或登录已过期");
}
// 校验并解析token,将userId放入request
Claims claims = JwtUtil.parseToken(token);
request.setAttribute("userId", claims.get("userId"));
return true;
}
}
注册到WebMvcConfigurer时,注意放行路径的写法和静态资源的处理,不要把所有路径都拦截了导致管理后台登录后刷新又跳转。
3.3 管理后台与App端的API设计规范
前后端分离项目,接口规范很重要。所有接口统一返回一个Result对象,结构如下:
json复制{
"code": 200,
"message": "success",
"data": {}
}
code为200表示成功,非200表示失败。前端根据code做统一提示,不再需要每个接口单独处理错误。异常处理使用全局异常处理器@RestControllerAdvice,业务异常、参数校验异常、未知异常分别返回不同的错误码和提示信息。
接口路径按模块划分,App端以/app开头,后台以/admin开头。比如:
- POST /app/auth/login —— App端登录
- GET /app/package/list —— 套餐列表
- POST /app/order/create —— 创建订单
- GET /admin/statistics/overview —— 后台数据看板
- PUT /admin/schedule/update —— 修改排班
如果有分页,统一用pageNum和pageSize,返回数据类型也统一。前后端联调时最好用Swagger生成接口文档,减少沟通成本。我给学弟整理项目时,会在Swagger里给每个接口写好说明和示例,这样讲解视频里演示API时非常方便。
4. 原型设计与源码工程结构
4.1 App交互原型:从页面流到操作反馈
交互原型设计是这个项目的重要部分。很多人以为原型就是画几张图,其实不是。原型要解决的是“用户点这里之后会发生什么”的预期问题。
我在项目里用Axure画了两套原型:一套是App端的高保真原型,另一套是管理后台的低保真线框图。App端原型包含12个主要页面:启动页、登录注册页、首页、套餐列表、套餐详情、预约填单页、订单确认页、支付结果页、预约记录、订单详情、报告列表、我的页面。每两个页面之间用Axure的交互连线做好跳转,并且标注出每个操作的前置条件和后置反馈。
比如“预约填单页”的交互说明,我会这样写:页面加载时调用排班接口,若某个日期没有排班数据或余号为0,则在日历上置灰不可点击;选择日期后,下方时段列表自动刷新;点击“提交订单”,若用户未登录,弹窗提示并跳转到登录页。
这些说明虽然看着繁琐,但恰恰是原型设计师和开发之间最重要的磨合点。没有这些注释,开发就会凭感觉做,做出来的交互很可能跟需求完全不一样。
4.2 管理后台原型:数据看板与操作闭环
管理后台的原型设计重点在“业务流程完整”。不要只画好看的静态页面,要把每个操作的交互状态都表达出来。
后台原型我建议至少覆盖这几个场景:管理员登录后进入首页看板,点击“排班管理”进入日历,点击某个日期弹出排班编辑弹窗,修改号源数后保存,列表数据立即刷新。在“订单管理”中,点击某条订单的“核销”按钮,弹出确认框,确认后按钮变为“已核销”且不可再次操作。如果订单状态允许改期,则弹出改期窗口供选择新的日期时段。
后台交互设计有个原则:所有危险操作必须有二次确认。删除套餐、取消订单、禁用用户,都必须弹窗确认,而且提示文案要写清楚:该操作是否可逆、会产生什么影响。这也是答辩时老师会关注的设计细节。
4.3 源码目录结构与讲解视频使用方法
当你拿到这套项目的源码包,第一时间不要盲目打开各种文件,先看目录结构。我整理源码的习惯是分四个目录:
- backend:SpringBoot后端工程
- app:Uniapp前端工程
- admin:Vue后台管理端工程
- docs:设计文档(LW)、数据库脚本、讲解视频、原型文件
后端工程采用标准Maven结构:
text复制backend/
├── src/main/java/com/example/health/
│ ├── controller/
│ ├── service/
│ ├── mapper/
│ ├── entity/
│ ├── config/
│ ├── common/
│ └── HealthApplication.java
├── src/main/resources/
│ ├── application.yml
│ └── mapper/*.xml
└── pom.xml
讲解视频一般会分段录制,第一段讲环境准备,第二段讲项目启动和效果演示,第三段讲代码走读,第四段讲设计文档和答辩准备。我建议你拿到视频后先完整看一遍启动流程,把环境配好,把项目跑起来,再看代码讲解。千万不要反着来,不然代码没跑起来,看讲解也一头雾水。
LW设计文档一般包含需求分析、可行性分析、系统设计、数据库设计、接口设计、系统测试、总结与展望。这部分是毕设答辩的核心材料,要跟源码对应着看。比如文档里写“预约模块使用乐观锁控制并发”,你就要能指出代码中具体是哪一行实现了这个逻辑,这样答辩时才不会被问倒。
5. 常见问题与排查实录
5.1 SpringBoot版本过高导致的启动失败
这个项目里我用的SpringBoot 2.7.18,对应JDK8。很多人拿到高版本的SpringBoot 3.x项目,本地JDK还是8,一启动就报错,提示class file version错误,这是因为SpringBoot 3最低要求JDK17。
如果你是JDK8环境,老老实实把pom.xml里的parent版本改成2.7.x,再把项目里一些javax包替换成jakarta包的问题处理掉。另外,SpringBoot 3把javax.servlet换成了jakarta.servlet,很多代码如果不改就会启动失败。最稳妥的做法是:新建项目时直接选Spring Initializr,Java版本选8,SpringBoot版本选2.7.x,然后对照源码逐个把依赖补进来。
5.2 跨域与接口联调问题
App端运行在浏览器H5模式,或者后台Vue开发服务器(localhost:8080)时,访问后端SpringBoot(localhost:8081)必定碰到跨域问题。现象是浏览器控制台报CORS错误,接口状态码是200但前端拿不到数据。
解决办法在后端配置全局跨域:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
注意allowedOriginPatterns和allowCredentials要配合使用,不能用allowedOrigins("*")加allowCredentials(true),某些浏览器版本会直接拒绝。配置好之后,前端通过代理或者直接请求都能正常访问。如果还不行,优先排查是不是接口路径写错了、端口是不是被占用。
5.3 App端时间格式与后端不一致
预约功能绕不开时间。后端用LocalDateTime返回数据时,默认序列化后是一串数组或者带T的字符串,App端直接显示会非常难看。解决办法是在实体类的LocalDateTime字段上统一加注解:
java复制@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
或者在application.yml里配置全局的时间格式。另外,前端提交日期参数时,一定要统一传字符串,后端用@DateTimeFormat解析。我曾经遇到过联调时App端传的时间少8小时的问题,就是因为时区没指定为GMT+8。
5.4 常见问题速查表
我把整理源码过程中遇到的高频问题列成了一张表,方便你快速定位。
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| SpringBoot无法启动,提示class file version | JDK版本过低 | 将SpringBoot版本降低到2.7.x,或安装JDK17 |
| 管理后台接口报404 | 后端接口路径与前端请求路径不一致 | 检查前端请求的baseURL和实际Controller映射 |
| 登录成功但访问别的接口报401 | Token未正确设置到请求头 | 检查前端请求拦截器是否携带Authorization |
| 预约时提示“已约满”但实际库存还有 | SQL中used_quota判断条件写反 | 检查update语句是used_quota < total_quota |
| 中文乱码 | 数据库连接没有指定字符集 | 在application.yml中给URL加characterEncoding=utf8 |
| 前端修改套餐状态后App端没变化 | Redis缓存未清理 | 改了数据后调用缓存清理,或者设置短缓存时间 |
| 图片上传失败 | 上传文件大小受限 | 在SpringBoot配置文件中调整max-file-size和max-request-size |
6. 这套项目能怎么扩展
6.1 从原型到产品化的改造点
如果你不止想应付毕设,而是想把这套系统当作实习作品或求职项目,我建议你在现有基础上做三个改造。
第一,引入Redis缓存。现在套餐列表和首页数据每次查询都直接打数据库,以后数据量大了性能会越来越差。可以把高频读接口的返回值缓存到Redis,设置10分钟过期,能明显提升响应速度。在项目中加上Redis之后,“为什么用Redis”就会成为你面试时的一个亮点。
第二,接入真正意义的支付功能。项目里目前是模拟支付,点击支付直接改成已支付。如果你有条件,可以申请一个支付宝沙箱环境,在沙箱里完成真实的支付流程。这样不仅体验更真实,简历上也能写“接入支付宝沙箱支付”。如果没有条件,模拟支付做一个异步通知回调的处理,也能体现你对支付流程的理解。
第三,把用户端App打包成Android安装包。Uniapp可以直接通过云打包生成apk文件,装到安卓手机上运行。演示的时候比单纯跑浏览器H5版本震撼得多,尤其是老师问到“这个能装到手机吗”,你说能当场展示,这就是加分项。
6.2 给毕设和简历的加分项
做这种全栈项目,最怕的是“全是CRUD”,没有任何技术亮点。如果你想在答辩或面试中脱颖而出,建议往这几个方向加东西。
第一个是单元测试。我在项目里给预约下单和订单取消写了几个JUnit测试用例,用Mockito模拟数据源,覆盖了超卖、重复预约等异常场景。虽然代码量不大,但能体现出工程素养。现在很多应届生的项目里根本没有一个测试用例,你写了就比大多数人强。
第二个是Docker部署。写一个Dockerfile,把后端打成镜像,再配合docker-compose把MySQL和Redis一起编排起来。能完整讲清楚Docker部署过程,也是实打实的亮点。
第三个是项目文档的规范性。LW设计文档里如果能有清晰的用例图、时序图(用绘图工具导出图片,不要用代码画)、数据库ER图,老师会认为你的设计能力过关。我整理的文档里,会重点描述“预约时间冲突如何解决”“排班数据如何动态更新”,这些是业务逻辑的核心,老师也最喜欢问。
回到这套项目本身,我做下来最大的感受是:源码和视频都只是辅助,真正有价值的是你对整个业务链路的理解。你跟别人讲“我做了体检预约系统”,不如讲“我设计的预约并发控制方案能扛住抢号场景”,后一种表达才真正体现你的项目思考。
如果你现在正在调试这套代码,我给一个最直接的建议:先把数据库脚本跑通,再后端跑通,再后台,再App,不要同时打开三个窗口调试。踩过几次坑之后你会发现,这类全栈项目本质上就是一个流程管理的问题——把每一步都走稳,整个系统自然就立起来了。最后再分享一个小技巧:数据库设计文档和接口文档一定要随着代码同步更新,别等最后补,不然后期你看着自己写的代码都会一脸懵。
