
说起毕业设计选题医疗预约挂号系统算是Android方向里被选得最多的题目之一。原因很清楚业务场景够完整、技术栈覆盖面广、前后端都有大量可写的内容而且这个题目跟日常生活贴合评委一看就知道你做的是什么答辩时不需要费劲解释业务背景。我这两年帮人看过不少类似的系统自己也完整做过一个移动端医疗挂号预约平台有一些体会和踩坑经验可以分享。这篇文章写给准备做这个课题的同学也写给想拿一个完整业务系统练手的开发者。我会把整个项目的思路拆开讲从需求分析、数据库设计、接口约定到Android客户端的关键实现、并发预约的坑、常见问题的排查方式全程按我实际做的流程来。你不用全部照抄但照着这个思路走至少能少走一半弯路。1. 项目定位与整体设计思路1.1 医疗预约系统到底在解决什么问题做系统之前先要搞清楚这个系统不是给医院做的完整医疗信息化平台它解决的核心问题只有三个——查得到、约得上、不冲突。所谓查得到就是患者能通过手机看到医院有哪些科室、哪些医生、什么时间出诊约得上是说患者可以在线选择时间段并成功挂号不冲突则要求同一时间同一个号源不能被两个患者拿到医生排班不能被重复预约。如果你去看很多现成的毕业设计代码会发现大部分系统的业务逻辑非常单薄一个用户表一个医生表预约就是往表里插一条数据完事根本没有考虑业务约束。这类系统答辩时被问到“两个用户同时预约最后一个号怎么办”就直接卡住。所以我在设计时把业务链路拉完整科室维护、医生管理、排班生成、号源扣减、预约状态流转、取消预约、历史记录查询每个环节都要闭环。这个系统适合的人群也很明确计算机相关专业做毕业设计的学生想练全栈Android Java后端 MySQL的开发者以及需要一个通用业务模板在自己电脑上跑通的初学者。难度上属于中等偏上比单纯的学生管理系统高一个档次但又没到企业级中间件的复杂度刚好适合在几周内完成。1.2 技术选型为什么是Java Android 后端接口这套组合技术栈的选择要回答一个核心问题为什么移动端用Android原生Java后端也用Java而不去用Flutter或小程序从毕业设计角度讲课题名称通常就限定了方向如果题目写的是“基于Android的XX系统”你用小程序做客户端流程上就说不通。Android原生Java开发的好处是结构清晰、资料海量、真机调试方便而且Android Studio自带布局预览和模拟器做完能直接往手机上一装演示效果比纯网页端直观得多。后端选Java生态是最稳妥的因为跟Android同语言很多同学不需要额外学一门新语言。具体框架我用的是Spring Boot MyBatis-Plus MySQL这套组合。Spring Boot负责接口发布MyBatis-Plus简化数据库操作MySQL做数据持久化。这套组合在毕业设计里属于标配技术成熟、网上案例多遇到问题基本都能搜到解法。如果你自己比较熟悉PHP或Python也不是不能用但答辩时“为什么用Java做后端”这个问题会好回答得多。前后端通信用RESTful风格的JSON接口这部分要注意Android网络请求必须放到子线程我用的是OkHttp Retrofit的组合。Retrofit的优势在于接口定义清晰直接在接口方法上加注解就能完成请求声明配合Gson解析JSON开发效率比手写HttpClient高很多。1.3 功能模块拆解患者端、医生端、管理端各自做什么我把整个系统拆成三个端来设计。患者端Android手机侧是核心交付部分包含注册登录、首页科室列表、医生排班查询、在线预约、我的预约、取消预约这几个页面。注册登录用手机号加验证码本地模拟即可不必接真实短信服务登录后保存token到SharedPreferences。医生端做在同一个App里的一个次级入口医生登录后可以查看自己的排班记录和当日预约患者列表必要时可以对预约记录进行标记操作如标记已就诊。这一块如果单独做一个后台管理App工作量太大而且答辩时也很难展示完整我选择将它设计成同App内的功能通过角色字段区分打开的是哪个界面。管理端做成Web管理页面或干脆用接口模拟。做Web页面会额外增加前端工作量我的做法是管理员功能用一组独立接口实现再在Android端藏一个管理入口管理员登录后能看到号源统计、用户列表、医生排班维护。这样做的好处是全部代码在一个工程内提交代码和答辩展示都方便缺点是App体积稍大但对于毕业设计来说完全无所谓。三个端共享同一套后端接口但权限控制上需要做区分普通患者接口、医生接口、管理员接口各用各的鉴权逻辑。这里不推荐搞复杂的权限框架用拦截器判断登录用户的role字段即可简单够用。2. 数据库与核心业务流程设计2.1 核心表结构设计用户、医生、科室、排班、预约数据库是整个系统的地基表建不好后面写代码全是补丁。我设计的是六张核心表用户表、科室表、医生表、排班表、预约表、操作日志表。用户表t_user包含id、手机号、密码、姓名、角色0患者/1医生/2管理员、创建时间。这里要注意手机号要做唯一索引因为登录依赖手机号重复会导致查询歧义。科室表t_department字段比较简单id、科室名称、科室介绍、创建时间。一般医院的科室有几十个不需要做分页全量查询没问题。医生表t_doctor包含id、姓名、职称、所属科室id、个人简介、头像地址、是否出诊状态。医生跟科室是多对一关系一个科室多个医生所以医生表存department_id作为外键。实际做的时候外键约束看情况加我习惯在业务层维护关联数据库层面只建普通索引因为MyBatis-Plus联表查询时如果强外键反而容易报错。排班表t_schedule是设计的重头戏字段为id、doctor_id、work_date出诊日期、period上午/下午/晚班、total_num号源总数、booked_num已预约数、status排班状态。为什么不把号源跟预约合并成一张表因为排班是医生出诊的宏观计划预约是患者的具体行为分开设计后统计一天有多少医生出诊、某个医生某个时段还剩多少号查询逻辑会清晰很多。预约表t_appointment字段为id、patient_id患者id、doctor_id、schedule_id关联排班、appointment_date、period、status0待就诊/1已就诊/2已取消/3爽约、create_time。预约表是整个业务里数据增长最快的表也是并发压力最大的表status字段的更新逻辑必须严格按状态机来。操作日志表t_log用来记录敏感操作比如用户注册、取消预约、管理员修改排班。这个表在答辩时可以拿来说“系统具备审计能力”虽然实际项目中日志一般用框架自带的日志但毕业设计里有一张独立的日志表更容易说明问题。2.2 预约状态的流转设计状态机是业务不出错的关键预约状态是整个系统最容易出bug的地方我可以用一个状态机把它约束清楚。刚创建时状态为“待就诊”0。患者在预约时间之前可以主动取消此时状态变更为“已取消”2。如果患者没有取消到医生出诊当天系统或医生操作将状态更新为“已就诊”1。如果过了出诊日期状态仍然为“待就诊”则自动改成“爽约”3。这个状态机里最关键的规则是已取消和爽约状态不能再次变更为待就诊已就诊状态不能被修改。代码层面我用一个状态字段加一组前置条件来控制比如取消预约的方法里第一行先判断status是否为0不是就直接抛业务异常。我的建议是在Java后端不要用魔法数字来写状态直接定义一个枚举类AppointmentStatus包含待就诊、已就诊、已取消、爽约四个常量配合一个枚举方法判断是否允许状态迁移。这样写的好处是代码可读性大幅提高答辩时讲状态流转逻辑也更有条理。2.3 排班与号源逻辑先有排班才有号这一步的细节直接影响后续排班设计直接决定你后面写预约接口时痛不痛苦我在这上面返工过两次总结出一套固定做法。排班不是一个抽象概念而是要具体到某个医生某一天某个时段上午/下午/晚班出诊这个时段可预约的号源总数是多少。一个医生一天可以有多个时段的排班比如上午和下午各一个排班记录。号源总数按科室或医生的实际情况设定展示时实时计算剩余号源 total_num - booked_num。生成排班的方式有两种手动一条条添加或者按模板批量生成。我的做法是后台提供一个接口传入doctor_id、开始日期、结束日期、周期周几出诊、时段、号源数服务端循环生成N条排班记录。这个功能很加分因为管理员不用每天手动点添加答辩时提一句“支持按模板批量生成排班”就很自然。号源扣减是整个系统并发风险最高的操作后面第4部分我会专门讲这里先记住一个原则扣减号源和创建预约必须放在同一个数据库事务里要么都成功要么都失败绝对不允许出现预约记录创建成功但号源没扣掉的情况。3. Android客户端与后端接口的实现要点3.1 Android端架构选择与关键页面实现Android端的架构我用的是MVVM LiveData ViewModel这套组合比纯MVP写起来省事又比Architecture Components全家桶容易理解。项目结构按功能分包ui存放Activity和Fragment、adapter列表适配器、model数据实体、network网络层、utils工具类。首页做科室列表用RecyclerView展示每个科室点击进入医生列表页。医生列表页上方放科室名称下面按医生姓名、职称、简介展示卡片点击进入医生详情页。医生详情页包含医生信息、排班日历和预约按钮三块排班日历我用的是横向滚动的日期标签栏选完日期后下方展示当天该医生的时段和剩余号源点击某个时段触发预约。我的预约页用TabLayout ViewPager2做三个分类页签待就诊、已完成、已取消。每个分类对应一个Fragment列表数据根据登录用户的id和状态字段去拉。这里要注意Fragment里不能用Activity的上下文做网络请求回调否则页面销毁后可能崩溃我当时踩过这个坑后来统一用getApplicationContext()来保证安全。登录注册页面用约束布局做比较规范手机号输入框、密码输入框、登录按钮、跳转注册链接。注册页加一个确认密码字段再加上简单的正则校验手机号11位、密码6-20位不含空格。校验放在Android端做一层后端接口还要再校验一层双重校验是基本要求。3.2 接口设计与数据格式约定前后端接口必须有一个统一的数据返回格式否则Android端解析时每个接口都要单独处理异常代码会非常碎。我定义的统一响应体结构如下{ code: 200, message: 成功, data: {} }code为200表示成功400表示参数错误401表示未登录或token失效500表示服务器异常。Android端封装一个ResponseCallback类在onResponse里先判断code不是200就直接Toast提示message是200再进入回调解析data。这样所有接口的异常处理逻辑都是同一套新增接口时只需要关心data部分的解析。核心接口列表我先列出来按功能分组一、用户模块POST /api/user/register注册、POST /api/user/login登录、GET /api/user/info获取个人信息二、科室与医生模块GET /api/department/list科室列表、GET /api/doctor/list?departmentIdxx按科室查医生、GET /api/doctor/detail?doctorIdxx医生详情含排班三、排班与预约模块GET /api/schedule/daily?doctorIdxxdatexx某医生某天的排班、POST /api/appointment/create创建预约、POST /api/appointment/cancel取消预约、GET /api/appointment/mine?status0按状态查我的预约、GET /api/schedule/remaining?scheduleIdxx查剩余号源接口命名上我坚持小写单词加斜杠分隔参数统一用queryString传递POST的请求体用Map或明确的实体对象。Android端Retrofit定义接口时方法名跟后端路径保持一致方便对照排错。3.3 预约流程的并发控制与数据一致性预约接口是整个系统里最需要小心的地方原因很简单数据库在并发场景下会出现竞态条件。假设排班表里total_num是50booked_num已经到了49此时两个患者同时提交预约如果两个请求都先查到booked_num49然后都执行booked_num50的更新再各自插入一条预约记录结果就是号源被超发。解决这个问题的核心思路是不要把“判断号源是否足够”和“扣减号源”拆开执行要将扣减动作设计成一个原子操作。我采用的方案是SQL层面的条件更新代码可以简化如下// Service层 Transactional(rollbackFor Exception.class) public Long createAppointment(CreateAppointmentDTO dto) { // 核心扣减语句只在有余号的情况下才更新成功 int updated scheduleMapper.decreaseBookedNum(dto.getScheduleId(), 1); if (updated 0) { throw new BizException(号源已约满请选择其他时段); } // 插入预约记录 Appointment appointment new Appointment(); // ... 赋值 appointmentMapper.insert(appointment); return appointment.getId(); }对应mapper里的SQLUPDATE t_schedule SET booked_num booked_num 1 WHERE id #{scheduleId} AND booked_num total_num这条UPDATE语句只有booked_num严格小于total_num时才会真正执行MySQL默认行锁会锁住这一行同一时间只有一个请求能成功更新。如果更新影响行数为0说明号源已经被约完直接返回“已约满”。这里有个容易被忽略的细节事务里先执行UPDATE拿锁再执行INSERT顺序不能反。如果你先insert预约记录再去更新排班表万一更新失败insert的数据就变成了一条无效预约还得靠事务回滚清除虽然最终结果一致但会多占用数据库的锁资源影响并发性能。另外建议在数据库层面对t_appointment表加唯一约束——联合(patient_id, schedule_id)的唯一索引。这两个字段唯一了同一个患者不可能对同一个排班创建两条预约记录这是兜底逻辑比代码层判断更可靠。4. 实操过程与环境搭建4.1 从零搭建项目环境清单与工程结构环境清单我直接给你按顺序装就行。开发机需要安装JDK 1.8以上、Android Studio版本不必追新稳定版就行、IntelliJ IDEA或Eclipse后端用、MySQL 5.7或8.0、Navicat或SQLyog数据库可视化工具、Postman或Apifox接口调试。手机或模拟器一台Android Studio自带的模拟器够用但预约相关的通知和页面跳转还是真机调试更直观。工程结构分两端。后端父工程命名随意下面按controller、service、mapper、entity、config分包Android工程按前面说的ui、adapter、network等分包。两个工程用Git分开管理时要注意.gitignore文件后端忽略/targetAndroid忽略/build和本地配置文件。4.2 核心接口的开发顺序建议先做哪一个接口开发顺序直接影响联调效率我的建议按这个顺序推进第一步做用户模块的注册和登录把token生成和校验逻辑先跑通。理由很简单几乎所有Android页面的请求头都要带token鉴权机制不先立起来后面每个接口都要返工。第二步做科室和医生的查询接口。这些是纯查询接口逻辑简单适合先跑通一条“数据从MySQL到后端经过JSON到Android展示”的完整链路。第三步做排班表的生成接口这是核心业务前置条件。先把批量生成排班的逻辑写好数据库中准备好测试数据后面预约接口才有东西可约。第四步做预约创建和取消接口这是整个系统的难点。先把并发问题处理好再处理状态迁移的校验。第五步做我的预约查询和操作日志记录属于收尾类功能逻辑简单但页面多需要耐心。4.3 真机调试与自测模拟预约全流程开发完不要急着提交我习惯把所有流程在真机上完整过一遍注册一个患者账号注册一个医生账号用管理员接口给医生批量生成三天的排班然后患者登录、选科室、选医生、选日期时段、提交预约预约成功后到“我的预约”查看再取消预约最后查看号源是否恢复。这里有一个很实用的自测方法用两个模拟器或两部手机同时操作同一时间点提交预约同一个排班检验并发控制是否生效。如果你发现两个请求都成功了说明你的扣减逻辑有问题回去检查事务和UPDATE语句的执行情况。还有一个容易忽略的测试场景日期边界。比如排班是今天的号你现在预约成功明天再来查询这条记录时它的状态是否自动变成了“爽约”或“已就诊”如果还是“待就诊”说明你的状态变更逻辑没考虑时间维度。我的处理方案是查询我的预约列表时在SQL里对status0的记录做一层时间判断如果当前时间已晚于排班的出诊日期则先批量更新为爽约状态再返回结果。5. 常见问题与排查技巧实录5.1 号源超发并发测试下的时间戳陷阱之前帮一个同学排查他的项目发现预约接口在并发100次请求时超发了将近10个号。排查后发现把扣减逻辑写成了先查remainNum再判断大于0然后更新整个过程没有事务也没有锁。这就是典型的竞态条件。排查这类问题的正确思路是三步走第一步看日志里是否出现多条重复的scheduleId预约成功记录第二步检查更新排班表的SQL是条件更新还是先查后改第三步看Service层方法是否有Transactional注解以及是否捕获了异常导致事务未生效。顺便提醒一个Spring Boot的经典坑Transactional默认只在RuntimeException和Error时回滚如果你在方法里catch了Exception并吞掉即使操作失败数据库也不会回滚。所以事务方法里尽量让异常自然抛出不要自己catch。5.2 科室排班展示慢数据库索引相关的坑开发阶段数据量少感觉不到慢查询等排班数据上了几百条预约记录上了几千条某些页面的响应速度会明显下降。我遇到过一起按科室查医生排班时SQL只根据doctor_id和work_date筛选但这两个字段都没有索引导致每次查询全表扫描。解决方法是给高频查询字段加索引。具体来说t_schedule表加联合索引(doctor_id, work_date, period)t_appointment表加联合索引(patient_id, status)t_doctor表其实不需要额外加索引主键查询够用。加完索引之后查询耗时能降一到两个数量级。如果你用的MySQL版本支持EXPLAIN命令可以通过它查看SQL执行计划确认是否走了索引。5.3 日期切换后状态不更新预约状态的时间判断逻辑很多初学者会在“如何判断预约是否过期”这个问题上犯错。如果每到页面加载时才去判断当前时间那么只要用户不打开App状态就一直停留在“待就诊”。我跟你们讲一个更稳妥的思路在查询接口里做“按需过期”处理。意思是说当患者查询“我的预约”列表时后端sql先做两步先批量更新所有已过排班日期且状态为待就诊的记录把它们置为爽约再进行查询。这在数据量不大的毕业设计场景里完全够用不影响性能也不依赖定时任务逻辑清晰。5.4 网络请求超时与Android端请求报错Android开发里最常见的报错有两类一类是主线程网络请求NetworkOnMainThreadException一类是接口无法连接ConnectException。前者一定会遇到解决方法是所有网络请求都放到子线程用Retrofit的enqueue异步回调即可后者通常是IP或端口配置问题检查后端接口地址模拟器用10.0.2.2访问宿主机真机用电脑的局域网IP这个点容易忽略很多同学在模拟器上调试通了换真机就报错多半就是这个原因。另外Android 9及以上默认禁止明文HTTP流量如果你接口用的是http://而不是https://需要在AndroidManifest.xml里配置android:usesCleartextTraffictrue否则请求直接报错拦都拦不住。这个也是开发时最容易一头雾水的问题现象是访问所有接口都失败但后端控制台又看不到任何请求日志。5.5 常见问题速查表问题现象可能原因解决方案登录成功但列表页返回401token未保存或请求头未携带token检查登录后是否保存tokenRetrofit拦截器是否统一添加Header创建预约提示约满但数据库有余号扣减SQL没加booked_num total_num条件改为条件UPDATE并检查事务边界Android真机无法访问后端用的是localhost或127.0.0.1改成电脑局域网IP确保同一WiFi网络Android页面请求接口全部失败未配置明文流量许可AndroidManifest中配置usesCleartextTraffic我的预约列表不更新状态缺少按日期更新状态的逻辑查询前先执行批量过期更新数据库中文乱码数据库编码或连接串未指定utf8连接URL加useUnicodetruecharacterEncodingutf86. 一些实操补充与设计取舍6.1 图片与个人信息维护的几个实用细节医生头像如果支持上传需要处理文件存储。毕业设计不建议搭独立的文件服务器直接存到后端项目的static/upload目录访问时通过后端接口路径映射即可。Android端头像显示用Glide框架加载URLGlide缓存机制会自动处理好重复加载问题。个人信息修改功能不要图省事把用户整张表的所有字段都暴露到修改接口里。正确做法是定义一个UpdateProfileDTO只允许客户端提交姓名、密码、头像这些可修改字段手机号在登录后是不允许前端自由修改的。这样设计在答辩时讲“安全的接口设计”是一个加分点。6.2 测试数据准备让演示效果更真实很多同学演示时用的是“张三”“李四”这类测试数据看起来特别假。我建议在数据库里准备一套贴近真实的模拟数据科室用自然循环、心血管内科、消化内科、骨科、儿科这类常规科室医生名字用普通姓氏加名字职称范围覆盖主任医师、副主任医师、主治医师排班覆盖今天、明天、后天上午下午晚间都有号。这套数据准备好之后演示效果立刻就不一样了评审老师会下意识觉得“这系统像回事”。需要特别说明的是生成测试数据前要检查一遍“医生是否停诊”“号源是否为0”这类状态避免演示时点开一个医生发现全是约满的状态那样观感很差。抽出两三个有号源的医生排班作为重点演示路径操作时一路顺畅。6.3 与第三方服务的边界短信验证码与实际支付在真实生产环境里预约系统通常会接短信服务商下发验证码、接支付平台做挂号费支付。但毕业设计阶段不建议去对接真实第三方支付和短信通道一方面是审核流程繁琐涉及资质另一方面是第三方的SDK版本更新频繁跟你的代码可能产生兼容问题。我的做法是短信验证码用后端模拟生成并直接打印在控制台和日志中App端提示“验证码已生成测试环境”挂号费模块用“订单金额”字段替代真实支付在预约记录里保存一个虚拟费用状态。答辩时如实说明“真实场景下可对接支付服务本项目通过虚拟支付流程验证业务逻辑”比硬接一个第三方SDK且跑不通要体面得多。7. 个人经验总结与后续扩展建议这个项目从头到尾做完我最深刻的体会是医疗预约系统的难点根本不在于Android页面写得有多花哨而在于业务数据的一致性。在数据库设计阶段多花时间把状态机和排班逻辑想清楚后面写代码的幸福感会高很多。如果上来就急着写Activity等做到预约并发控制这一步你会发现自己需要回头改表结构、改接口参数返工成本非常高。后续如果想让这个项目更上一个档次可以从三个方向扩展第一个方向是加入消息推送用Socket实现“预约成功通知”“医生停诊提醒”后端在排班状态变化时主动推给客户端第二个方向是加入统计报表在管理端按科室汇总每天的预约量、爽约率用柱状图展示第三个方向是把后端内存式token改成Redis存储将单机版并发优化成分布式会话。这些方向不用全部实现挑一个做深做透就能让整个毕业设计的深度和中上部拉开差距。最后分享一个答辩时的小技巧提前准备好一组测试账号演示时用固定的手机号和密码登录不要现场输入。现场手机号千奇百怪密码也容易按错万一登录失败特别尴尬。把管理员的账号、医生的账号、患者的账号分别准备好演示时切换角色展示三个端的不同页面整套流程走下来基本就稳了。希望这篇拆解能帮到正在做类似项目的同学。上面提到的表结构、接口设计、并发方案和踩坑记录都是我自己实际跑过的你可以放心照着改。项目代码好不好关键看逻辑严谨不严谨、演示流畅不流畅把这两点抓住了这个课题就能做出亮点。