简介:这是一套面向高校计算机相关专业毕业设计的智慧医疗医院预约挂号App完整项目,基于AndroidStudio与原生安卓技术开发,配套SQLite数据库,包含安卓客户端与服务器端源码及项目文档,适合正在准备毕设或需要安卓实战案例的学生与开发者参考。压缩包共115个文件,约4.87MB,以40个xml布局、14个java源码、30张jpg与16张png界面素材为主,另含gradle构建脚本、项目报告docx及说明文档,结构完整便于直接导入运行。功能覆盖病人注册登录、流行病学调查表填写、核酸检查预约与记录查询、新冠疫苗预约与记录查询、门诊预约与记录查询,以及医保卡绑定、就诊卡创建管理等模块,基本还原了医院线上服务的主要业务链路。目前已有503人学习下载,可帮助读者快速理解安卓端与本地数据库的交互方式、页面跳转逻辑与数据表设计思路,为毕设选题、功能扩展和答辩准备提供可复用的参考方案。
1. 智慧医疗预约挂号 App:从 AndroidStudio 工程到可答辩的完整交付
很多同学做毕业设计时,选题定了「智慧医疗」,结果卡在第一步:AndroidStudio 新建工程之后,不知道一个能跑通、能演示、能写进论文的预约挂号 App 到底该长什么样。这个标题讲的不是某个开源仓库,而是一套完整的交付物——安卓客户端 + 安卓服务器端 + 源代码 + 项目文档,核心场景就是患者注册登录、选科室、选医生、选时段、提交挂号、查看记录,后台负责排班和订单管理。它适合两类人:一是需要一套能演示、能讲清架构的毕设方案;二是想借这个场景把 Android 网络请求、本地数据库、服务端接口串起来练一遍的开发者。下面我按实际做项目的顺序,把选型、实现、参数和踩坑一次讲透。
2. 先定架构再写代码:客户端、服务端、数据库怎么切
2.1 为什么推荐「Android 原生 + Java/Kotlin 服务端」而不是纯本地
毕设最常见的翻车方式是:所有数据塞进手机本地 SQLite,答辩老师一问「多用户怎么共享号源」就答不上来。预约挂号的核心矛盾是号源是共享资源,必须有一个中心服务端来扣减库存、防止超卖。所以架构上我一般会切成三层:Android 客户端负责界面和交互,服务端负责业务逻辑和号源管理,数据库负责持久化。
服务端有两种常见做法。第一种是用 Java/Kotlin 写一个轻量 HTTP 服务(Spring Boot 或纯 Servlet),第二种是用 Android 本身起一个本地服务端(比如 NanoHTTPD 或 Ktor)跑在同一台设备或局域网内。毕设演示场景下,第二种更省事——不用买服务器、不用配域名,手机连同一个 WiFi 就能跑通。但如果论文里要写「高并发」「分布式」,就得用第一种,哪怕只是本地跑。
我的建议是:演示优先选轻量方案,论文架构图按标准三层画。两者不冲突,客户端代码几乎不用改,只换 baseUrl。
2.2 数据库表设计:四张表撑起整个挂号流程
不管服务端用什么语言,表结构是通用的。下面这套是我实际用过、能覆盖挂号全流程的最小集合:
| 表名 | 关键字段 | 作用 |
|---|---|---|
| user | id, phone, password, name, id_card | 患者账号 |
| department | id, name, description | 科室 |
| doctor | id, name, department_id, title, intro | 医生,关联科室 |
| schedule | id, doctor_id, date, time_slot, total, remaining | 排班与号源余量 |
| appointment | id, user_id, schedule_id, status, create_time | 挂号订单 |
这里最关键的是schedule表的remaining字段。它是防超卖的核心——每次挂号先判断remaining > 0,再执行扣减。appointment表的status用整数表示:0 待就诊、1 已完成、2 已取消,方便客户端做状态筛选。
注意:
remaining的扣减必须放在服务端的同一个事务里,客户端传上来的余量永远不可信。
2.3 客户端页面清单与跳转关系
一个能演示的客户端至少需要这些页面,按用户操作顺序排:
- 启动页 / 登录注册页
- 首页(科室列表)
- 医生列表页(按科室筛选)
- 医生详情 + 排班选择页
- 确认挂号页
- 我的挂号记录页
- 个人中心(修改资料、退出登录)
跳转关系用 Intent 串联,登录成功后把userId存进 SharedPreferences,后续每个请求都带上它。这一步看着简单,但很多同学忘了在「我的挂号记录」里按userId过滤,导致 A 用户能看到 B 用户的订单——这是答辩时最容易被抓的逻辑漏洞。
3. 用 AndroidStudio 把客户端跑起来:网络层与核心页面
3.1 网络请求封装:OkHttp + Gson 的最小可用模板
客户端和服务端通信,我一般用 OkHttp 做请求、Gson 做解析。先加依赖(build.gradle的dependencies里):
implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.google.code.gson:gson:2.10.1'然后封装一个单例工具类,统一处理 baseUrl、超时和 JSON 转换:
public class HttpUtil { // 演示环境用局域网 IP,真机调试时改成电脑的局域网地址 private static final String BASE_URL = "http://192.168.1.100:8080"; private static final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 .readTimeout(15, TimeUnit.SECONDS) // 读取超时 .build(); // 通用 POST,body 为 JSON 字符串 public static void post(String path, String json, Callback callback) { RequestBody body = RequestBody.create( json, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder() .url(BASE_URL + path) .post(body) .build(); client.newCall(request).enqueue(callback); } }逻辑说明:BASE_URL是服务端地址,真机调试时不能写 localhost,因为 localhost 指向手机自己,必须写电脑在局域网里的 IP。connectTimeout设 10 秒,readTimeout设 15 秒,是因为挂号接口涉及数据库事务,偶尔会慢一点,设太短会误报失败。回调统一用enqueue异步执行,避免在主线程发请求导致 ANR。
参数怎么改:如果服务端换了端口,只改BASE_URL;如果接口需要 token,在Request.Builder里加.addHeader("token", xxx)。
3.2 登录与挂号两个核心接口的调用
登录接口客户端这样调:
JSONObject json = new JSONObject(); json.put("phone", phoneInput.getText().toString()); json.put("password", pwdInput.getText().toString()); HttpUtil.post("/user/login", json.toString(), new Callback() { @Override public void onFailure(Call call, IOException e) { runOnUiThread(() -> Toast.makeText(LoginActivity.this, "网络异常,请检查服务端是否启动", Toast.LENGTH_SHORT).show()); } @Override public void onResponse(Call call, Response response) throws IOException { String result = response.body().string(); // 解析服务端返回的 {code, msg, data:{userId,...}} LoginResp resp = new Gson().fromJson(result, LoginResp.class); if (resp.code == 200) { // 保存 userId,后续请求都要用 getSharedPreferences("user", MODE_PRIVATE) .edit().putInt("userId", resp.data.userId).apply(); startActivity(new Intent(LoginActivity.this, MainActivity.class)); } else { runOnUiThread(() -> Toast.makeText(LoginActivity.this, resp.msg, Toast.LENGTH_SHORT).show()); } } });挂号接口的关键是先查余量再提交,但真正的扣减在服务端:
JSONObject json = new JSONObject(); json.put("userId", userId); json.put("scheduleId", selectedScheduleId); HttpUtil.post("/appointment/create", json.toString(), callback);逻辑说明:客户端只传userId和scheduleId,绝不传余量数字。服务端收到后先SELECT remaining FROM schedule WHERE id=? FOR UPDATE锁行,判断大于 0 再UPDATE schedule SET remaining = remaining - 1,同时插入appointment记录。这样即使两个人同时点,也不会超卖。
参数说明:scheduleId是排班记录的唯一标识,由医生详情页的排班列表返回。userId从 SharedPreferences 取,不要从输入框拿。
3.3 服务端接口清单与返回格式约定
服务端不管用什么框架,接口和返回格式要统一。我一般约定返回体固定为{code, msg, data}:
| 接口路径 | 方法 | 入参 | 返回 data |
|---|---|---|---|
| /user/register | POST | phone, password, name | userId |
| /user/login | POST | phone, password | userId, name |
| /department/list | GET | 无 | 科室数组 |
| /doctor/list | GET | departmentId | 医生数组 |
| /schedule/list | GET | doctorId, date | 排班数组 |
| /appointment/create | POST | userId, scheduleId | appointmentId |
| /appointment/list | GET | userId | 订单数组 |
code用 200 表示成功,400 表示参数错误,500 表示服务端异常。客户端只判断code == 200才走成功逻辑,其余弹msg。这套约定写进项目文档,答辩时能直接讲「接口规范」。
提示:
/appointment/create一定要做幂等或至少做重复提交拦截,否则用户连点两次会生成两条订单。
4. 号源扣减与并发:预约挂号最容易翻车的地方
4.1 超卖是怎么发生的:一个真实的时间线
假设某医生上午 9:00 时段只剩 1 个号。用户 A 和用户 B 几乎同时点「确认挂号」。如果服务端逻辑是「先查余量,再扣减」,两条请求可能都查到remaining = 1,都判断通过,然后各自扣减,最终remaining变成 -1,两个人都挂号成功。这就是超卖。
根因是查询和扣减之间有时间窗口,两个请求的读操作都发生在写操作之前。解决思路只有一条:让「判断 + 扣减」变成一个原子操作。
4.2 三种防超卖方案与选型
| 方案 | 做法 | 适用场景 | 缺点 |
|---|---|---|---|
| 数据库行锁 | SELECT ... FOR UPDATE锁住排班行 | 单机数据库,毕设首选 | 并发高时锁等待 |
| 乐观锁 | 更新时带WHERE remaining = 旧值 | 冲突少的场景 | 失败要重试 |
| 唯一索引 | 对 (userId, scheduleId) 建唯一索引 | 防同一人重复挂号 | 不防不同人抢同一号 |
毕设场景我推荐行锁 + 唯一索引组合:行锁防超卖,唯一索引防同一用户重复提交。下面是服务端扣减的核心 SQL:
-- 开启事务 START TRANSACTION; -- 锁住这一行,其他事务必须等待 SELECT remaining FROM schedule WHERE id = ? FOR UPDATE; -- 应用层判断 remaining > 0 后执行 UPDATE schedule SET remaining = remaining - 1 WHERE id = ?; -- 插入订单,唯一索引 uk_user_schedule 防止重复 INSERT INTO appointment (user_id, schedule_id, status, create_time) VALUES (?, ?, 0, NOW()); COMMIT;逻辑说明:FOR UPDATE让这一行在事务提交前被锁住,第二个请求必须等第一个提交后才能读到最新余量,从而看到remaining = 0并拒绝。uk_user_schedule是appointment表上的唯一索引,保证同一用户对同一排班只能有一条记录。
参数说明:?依次是scheduleId、scheduleId、userId、scheduleId。事务里任何一步失败都要ROLLBACK,否则会留下脏数据。
4.3 客户端如何配合:按钮防抖与结果反馈
服务端做了防护,客户端也不能拖后腿。用户连点「确认挂号」按钮,会发出多条请求。最简单的做法是点击后立刻禁用按钮:
confirmBtn.setOnClickListener(v -> { confirmBtn.setEnabled(false); // 立即禁用,防止连点 confirmBtn.setText("提交中..."); HttpUtil.post("/appointment/create", json.toString(), new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { // 无论成功失败,都要恢复按钮,否则用户卡死在这一页 runOnUiThread(() -> { confirmBtn.setEnabled(true); confirmBtn.setText("确认挂号"); }); } @Override public void onFailure(Call call, IOException e) { runOnUiThread(() -> confirmBtn.setEnabled(true)); } }); });逻辑说明:setEnabled(false)是前端最廉价的防抖手段。关键是在成功和失败回调里都要恢复按钮,很多同学只在成功里恢复,一旦网络失败按钮就永久变灰,演示时直接卡住。
参数说明:如果要做更严格的防抖,可以记录lastClickTime,两次点击间隔小于 800ms 直接忽略。
5. 避坑与排查:从「跑不起来」到「能演示」的五个血泪经验
5.1 真机连不上服务端,模拟器却正常
现象:模拟器里登录成功,换真机就提示网络异常。原因:模拟器访问宿主机用10.0.2.2,真机必须用电脑的局域网 IP,而且手机和电脑要在同一个 WiFi 下。解决:把BASE_URL改成电脑的局域网地址(命令行ipconfig查),并确认电脑防火墙放行了服务端端口。这是最高频的翻车点,没有之一。
5.2 AndroidStudio 报 could not resolve dependencies
现象:build.gradle里加了依赖,同步时报could not resolve all task dependencies。原因:网络访问 Maven 仓库不稳定,或者依赖版本号写错。解决:先检查版本号是否存在,再在settings.gradle里确认仓库地址包含mavenCentral()和google()。如果公司网络有限制,换成能访问的镜像仓库。不要盲目删缓存,先看报错里具体是哪个坐标解析失败。
5.3 挂号成功但「我的记录」是空的
现象:下单返回成功,进记录页却查不到。原因:查询接口没带userId,或者带了但服务端没按它过滤。解决:在/appointment/list的 SQL 里加WHERE user_id = ?,客户端确认从 SharedPreferences 取的是登录时存的同一个userId。这个 bug 在答辩演示时非常致命,因为老师一定会点「我的记录」。
5.4 数据库时间字段对不上,排班显示错乱
现象:排班日期显示成前一天或乱码。原因:客户端和服务端对日期的格式约定不一致,或者时区没统一。解决:统一用yyyy-MM-dd字符串传日期,time_slot用09:00-09:30这种固定格式,服务端存字符串而不是时间戳。毕设不需要处理跨时区,统一格式最省心。
5.5 服务端一重启,之前挂的号全没了
现象:演示中途重启服务端,订单数据丢失。原因:用了内存数据库(比如 H2 的默认模式)或数据存在临时目录。解决:换成文件模式的 SQLite 或 MySQL,确认数据文件路径固定。如果用的是内存存储,至少要在项目文档里说明「演示数据不持久化」,别让老师以为你做了个假系统。
6. 让这套毕设更耐问:文档、演示脚本与可扩展点
答辩能不能过,代码只占一半,另一半是你能不能把「为什么这么做」讲清楚。我的习惯是准备一份演示脚本,按固定顺序走一遍:注册新用户 → 登录 → 选科室 → 选医生 → 选排班 → 挂号 → 查看记录 → 取消挂号。每一步都提前想好老师可能追问的点,比如「两个人同时抢最后一个号会怎样」,这时候你就把第 4 章的行锁逻辑讲出来,比背概念有说服力。
项目文档里我一般会放三样东西:架构图(三层,标注数据流向)、接口表(就是第 3.3 节那张)、表结构说明(第 2.2 节那张)。这三样能让老师快速判断你是不是真的理解系统,而不是抄了个模板。
如果想再加分,有两个低成本的扩展方向。第一是加一个简单的排班管理端,哪怕只是一个网页或一个隐藏的 Android 页面,能让管理员新增排班,系统就从「只能看」变成「能运营」。第二是给挂号接口加一个简单的限流,比如同一userId每秒最多请求一次,用内存里的 Map 记录时间戳就能实现,代码不超过 20 行,但论文里可以写「防刷号机制」。
最后说个我自己的教训:第一次做这类项目时,我把所有逻辑都堆在 Activity 里,一个文件两千行,改一个 bug 要翻半天。后来学乖了,网络请求、数据解析、界面更新分开写,哪怕只是多建两个类,调试效率完全不一样。毕设代码不要求多优雅,但至少要让你自己在答辩前一周还能看懂。希望帮到你。
本文还有配套的精品资源,点击获取