简介:这是一套面向高校计算机相关专业毕业设计的智慧医疗医院预约挂号App完整项目,基于AndroidStudio与原生安卓技术开发,配套SQLite数据库,包含安卓客户端与服务器端源码及项目文档,适合正在准备毕设或需要安卓实战案例的学生与开发者参考。资源包共115个文件,约4.87MB,涵盖40个xml布局、14个java业务代码、30张jpg与16张png界面素材,以及gradle构建脚本、项目报告docx和说明文档等,结构完整、便于二次开发。功能覆盖病人注册登录、流行病学调查表填写、核酸检查预约与记录查询、新冠疫苗预约与记录查询、门诊预约与记录查询,以及医保卡绑定、就诊卡创建管理等模块,基本还原了医院线上服务的主要流程。目前已有503人学习下载,可帮助读者快速理解安卓端与本地数据库的交互方式、页面组织与业务逻辑拆分,为毕设选题、功能扩展和答辩准备提供可落地的参考方案。
1. 智慧医疗预约挂号 App:从 AndroidStudio 工程到能跑通的服务端联调
很多同学做毕业设计时,选题定了「智慧医疗医院预约挂号 App」,打开 AndroidStudio 新建一个 Empty Activity,然后就开始发愁:客户端界面能画出来,但号源从哪来?医生排班怎么存?挂号成功之后状态怎么同步?这套东西到底要写几个模块、服务端用什么、数据库几张表、接口怎么定,才是真正卡住进度的地方。这个标题对应的不是单一页面,而是一套「安卓客户端 + 服务端 + 数据库」的最小闭环系统,核心业务是科室浏览、医生排班查询、号源锁定、预约下单、订单状态流转。它适合正在做安卓方向毕设、需要一套能演示、能答辩、能继续扩展的完整工程的人。下面按我实际搭这类项目的顺序,把选型、建表、接口、联调和踩坑一次讲清楚,你照着做就能在本地跑通一条完整的挂号链路。
2. 先定架构再写代码:AndroidStudio 客户端与服务端怎么分工
2.1 为什么选 AndroidStudio 原生 + 轻量服务端,而不是全塞进 App
毕设里最常见的翻车方式,是把所有数据用本地 SQLite 存在手机里,挂号记录只在自己手机上可见,答辩老师一问「两台手机怎么看到同一个号源」就答不上来。预约挂号本质是一个多端共享状态的业务,号源必须放在服务端统一扣减,客户端只负责展示和提交请求。所以架构上我一般会拆成三层:AndroidStudio 客户端负责 UI 和网络请求,服务端负责业务逻辑和号源并发控制,数据库负责持久化。
客户端用 AndroidStudio 原生开发,语言选 Java 或 Kotlin 都行,网络层用 Retrofit + OkHttp,JSON 解析用 Gson,图片加载用 Glide。这套组合在毕设里资料最多,出问题好查。服务端如果不想引入太重的东西,用 Spring Boot 起一个单体服务最省事,接口全部走 RESTful 风格,返回统一 JSON 结构。数据库用 MySQL,本地装一个或者用 Docker 起一个都行。
提示:不要一上来就上微服务、网关、注册中心,毕设的评分点在于业务闭环和代码规范,不在于架构复杂度。单体服务 + 清晰分层足够拿高分。
2.2 数据库表设计:五张表撑起整个挂号流程
表结构是这类项目的骨架,设计不好后面接口会越写越乱。我一般会落这五张核心表,字段按最小可用原则给:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 患者账号 | id, phone, password, real_name, id_card |
| department | 科室 | id, name, description, parent_id |
| doctor | 医生 | id, name, title, department_id, avatar, intro |
| schedule | 排班号源 | id, doctor_id, work_date, time_slot, total_num, left_num, fee |
| appointment | 预约订单 | id, user_id, schedule_id, status, create_time, order_no |
其中 schedule 表的 left_num 是并发扣减的核心字段,appointment 表的 status 用 0 待就诊、1 已完成、2 已取消三个状态就够演示。建表时给 schedule 的 doctor_id + work_date 加联合索引,给 appointment 的 user_id 加索引,查询性能在毕设数据量下完全够用。
CREATE TABLE schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doctor_id BIGINT NOT NULL, work_date DATE NOT NULL, time_slot VARCHAR(20) NOT NULL COMMENT '上午/下午', total_num INT NOT NULL DEFAULT 0, left_num INT NOT NULL DEFAULT 0, fee DECIMAL(10,2) NOT NULL DEFAULT 0, INDEX idx_doctor_date (doctor_id, work_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段建表语句里,left_num 和 total_num 分开存是为了方便前端展示「剩余几个号」,fee 单独存是因为同一个医生不同时段挂号费可能不同。索引建在 doctor_id 和 work_date 上,是因为患者查号源时永远是「某医生某天」这个维度。
2.3 接口约定:客户端和服务端先对齐再动手
接口没定清楚就开始写,后面改字段会改到崩溃。我一般先写一份接口文档,至少把路径、方法、请求参数、返回结构定死。核心接口有这几个:
- POST /api/user/login 登录,返回 token
- GET /api/department/list 科室列表
- GET /api/doctor/list?departmentId=1 按科室查医生
- GET /api/schedule/list?doctorId=1&date=2025-06-01 查号源
- POST /api/appointment/create 提交预约
- GET /api/appointment/list?userId=1 我的预约
返回结构统一成{ "code": 200, "msg": "success", "data": {...} },客户端只判断 code 是否为 200,业务错误用不同 code 区分,比如 401 未登录、409 号源不足。这样客户端解析逻辑只写一次,后面加接口不用改解析代码。
3. 客户端关键页面实现:从登录到挂号成功的完整链路
3.1 用 Retrofit 封装网络层,避免每个 Activity 重复写请求
AndroidStudio 里最容易写乱的就是网络请求,每个页面各写一套 HttpURLConnection,最后 token 传递、错误处理全不一致。正确做法是先封装一个单例的 Retrofit 客户端,统一加请求头和超时配置。
public class ApiClient { private static Retrofit retrofit; public static Retrofit getInstance() { if (retrofit == null) { OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .addInterceptor(chain -> { Request original = chain.request(); Request request = original.newBuilder() .header("token", TokenManager.getToken()) .build(); return chain.proceed(request); }) .build(); retrofit = new Retrofit.Builder() .baseUrl("http://10.0.2.2:8080/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }这里 baseUrl 用10.0.2.2是安卓模拟器访问宿主机 localhost 的固定地址,真机调试要换成电脑局域网 IP。拦截器统一注入 token,后面所有接口都不用再手动传。超时设 10 秒,毕设本地环境足够,设太长反而卡 UI。
3.2 号源列表页:RecyclerView 绑定排班数据
号源列表是挂号流程的核心页面,用 RecyclerView 展示某医生某天的排班。每个 item 显示时段、剩余号数、挂号费和一个「预约」按钮。剩余号为 0 时按钮置灰,这个判断必须在客户端和服务端都做一遍,客户端做是为了体验,服务端做是为了数据正确。
public void onBindViewHolder(ScheduleHolder holder, int position) { Schedule item = list.get(position); holder.tvSlot.setText(item.getTimeSlot()); holder.tvLeft.setText("剩余 " + item.getLeftNum() + " 个"); holder.tvFee.setText("¥" + item.getFee()); if (item.getLeftNum() <= 0) { holder.btnBook.setEnabled(false); holder.btnBook.setText("已约满"); } else { holder.btnBook.setEnabled(true); holder.btnBook.setOnClickListener(v -> { Intent intent = new Intent(context, ConfirmActivity.class); intent.putExtra("scheduleId", item.getId()); context.startActivity(intent); }); } }绑定逻辑里,left_num 直接决定按钮状态,点击后把 scheduleId 传到确认页。注意不要在列表页直接下单,因为用户可能误触,确认页是必要的中间步骤,也方便答辩时演示业务流程。
3.3 提交预约:客户端防重复点击,服务端防超卖
提交预约这个动作,客户端要做的是防止用户连点按钮产生多条请求,服务端要做的是防止号源被扣成负数。客户端侧在点击后立刻禁用按钮,请求返回后再恢复。
btnSubmit.setOnClickListener(v -> { btnSubmit.setEnabled(false); api.createAppointment(scheduleId, userId).enqueue(new Callback<ApiResponse>() { @Override public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) { btnSubmit.setEnabled(true); if (response.body() != null && response.body().getCode() == 200) { Toast.makeText(this, "预约成功", Toast.LENGTH_SHORT).show(); finish(); } else { Toast.makeText(this, "号源不足,请刷新", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<ApiResponse> call, Throwable t) { btnSubmit.setEnabled(true); Toast.makeText(this, "网络异常", Toast.LENGTH_SHORT).show(); } }); });服务端对应的扣减逻辑必须用数据库行锁或者乐观锁,不能先查再改。常见做法是UPDATE schedule SET left_num = left_num - 1 WHERE id = ? AND left_num > 0,根据 affected rows 判断是否扣减成功,返回 0 就说明号源已满。这一条是这类项目最核心的正确性保障,答辩时能讲清楚这一点很加分。
4. 服务端联调与排错:号源扣减、跨域、模拟器网络三个高频坑
4.1 号源扣减的并发问题:为什么你的项目一压测就超卖
现象是两个人同时点预约,号源只剩 1 个,结果两条订单都创建成功,left_num 变成 -1。原因是服务端用了「先 select 查剩余,再 update 扣减」的写法,两个请求都查到 left_num=1,都判断可以预约,然后各自扣减。解决办法是把判断和扣减合并成一条 SQL,利用数据库的行锁保证原子性。
UPDATE schedule SET left_num = left_num - 1 WHERE id = #{scheduleId} AND left_num > 0;执行后判断返回的影响行数,等于 1 才继续插入 appointment 记录,等于 0 直接返回「号源不足」。如果业务要求更严格,可以在事务里先SELECT ... FOR UPDATE锁住这一行再操作,但毕设场景下上面这条 SQL 已经够用。
4.2 模拟器访问本地服务端失败:10.0.2.2 和局域网 IP 的区别
现象是浏览器能打开服务端接口,但 App 里请求一直失败。原因通常是 baseUrl 写成了localhost或127.0.0.1,这两个地址在模拟器里指向模拟器自己,不是你的电脑。安卓模拟器访问宿主机要用10.0.2.2,真机则要用电脑的局域网 IP,比如192.168.1.100,并且手机和电脑要在同一个网络下。
另外 Android 9 以后默认禁止明文 HTTP 请求,需要在 AndroidManifest.xml 的 application 标签加android:usesCleartextTraffic="true",否则请求会被系统直接拦截,日志里报 CLEARTEXT communication not permitted。
4.3 登录 token 丢失:拦截器顺序和存储位置
现象是登录成功后,后续接口返回 401。原因一般是 token 存到了某个 Activity 的成员变量里,页面切换就丢了。正确做法是用 SharedPreferences 持久化,登录成功后写入,拦截器每次从 SharedPreferences 读。注意拦截器里读 SharedPreferences 要用 ApplicationContext,不能用 Activity 的 context,否则可能内存泄漏。
还有一个容易忽略的点:如果同时加了日志拦截器和 token 拦截器,顺序要对,token 拦截器要在日志拦截器之前添加,这样日志里能看到带 token 的完整请求,方便排查。
5. 毕设加分项:把预约挂号做成可演示、可扩展的完整系统
5.1 用状态机管理订单,让业务逻辑经得起追问
很多同学的订单状态就是随便改,取消和完成混在一起,答辩时被问「已取消的订单能不能再改成已完成」就卡住了。正确做法是把订单状态定义成明确的状态机:待就诊只能流转到已完成或已取消,已完成和已取消是终态,不能再变。服务端每次改状态前先校验当前状态是否允许这次流转,不允许就返回错误。这样代码里多一个校验方法,但业务严谨性直接上一个档次。
public boolean canTransfer(int from, int to) { if (from == 0 && (to == 1 || to == 2)) return true; return false; }5.2 加一个号源定时释放,演示「取消后号源回流」
预约取消后,号源要加回去,否则号会越来越少。在取消接口里,把 appointment 状态改成 2 的同时,执行UPDATE schedule SET left_num = left_num + 1 WHERE id = ?。这两步要放在同一个事务里,避免取消成功但号源没加回去。更进一步,可以加一个定时任务,把超过就诊时间还没完成的订单自动标记为已完成,这样系统看起来更像真实产品。
5.3 客户端列表刷新:下拉刷新和返回刷新别漏
号源列表和我的预约列表,用户操作后要能刷新。号源列表加 SwipeRefreshLayout 支持下拉刷新,我的预约列表在 onResume 里重新请求一次,保证从确认页返回后能看到新订单。这两个细节不做,演示时会出现「预约成功了但列表里没有」的尴尬情况。
| 刷新场景 | 实现方式 | 注意点 |
|---|---|---|
| 号源列表 | SwipeRefreshLayout | 刷新时清空旧数据再填充 |
| 我的预约 | onResume 重新请求 | 避免频繁请求可加时间间隔判断 |
| 科室医生列表 | 首次加载 + 下拉刷新 | 数据变动少,可不做自动刷新 |
5.4 我踩过的教训:先把一条链路跑通,再铺页面
我带过几届毕设,最常见的失败模式是页面画了十几个,但一条完整的「登录 → 选科室 → 选医生 → 选号源 → 下单 → 查看订单」链路都没跑通。正确的顺序是先用最丑的界面把这条链路打通,确认数据能存能查能改,再去美化 UI、加科室图标、加医生头像。功能闭环永远优先于界面好看,答辩老师看的是你能不能讲清楚数据怎么流动,不是你的按钮圆角多大。希望帮到你。
本文还有配套的精品资源,点击获取