简介这是一套面向高校计算机相关专业毕业设计的智慧医疗医院预约挂号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 OkHttpJSON 解析用 Gson图片加载用 Glide。这套组合在毕设里资料最多出问题好查。服务端如果不想引入太重的东西用 Spring Boot 起一个单体服务最省事接口全部走 RESTful 风格返回统一 JSON 结构。数据库用 MySQL本地装一个或者用 Docker 起一个都行。提示不要一上来就上微服务、网关、注册中心毕设的评分点在于业务闭环和代码规范不在于架构复杂度。单体服务 清晰分层足够拿高分。2.2 数据库表设计五张表撑起整个挂号流程表结构是这类项目的骨架设计不好后面接口会越写越乱。我一般会落这五张核心表字段按最小可用原则给表名作用关键字段user患者账号id, phone, password, real_name, id_carddepartment科室id, name, description, parent_iddoctor医生id, name, title, department_id, avatar, introschedule排班号源id, doctor_id, work_date, time_slot, total_num, left_num, feeappointment预约订单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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句里left_num 和 total_num 分开存是为了方便前端展示「剩余几个号」fee 单独存是因为同一个医生不同时段挂号费可能不同。索引建在 doctor_id 和 work_date 上是因为患者查号源时永远是「某医生某天」这个维度。2.3 接口约定客户端和服务端先对齐再动手接口没定清楚就开始写后面改字段会改到崩溃。我一般先写一份接口文档至少把路径、方法、请求参数、返回结构定死。核心接口有这几个POST /api/user/login 登录返回 tokenGET /api/department/list 科室列表GET /api/doctor/list?departmentId1 按科室查医生GET /api/schedule/list?doctorId1date2025-06-01 查号源POST /api/appointment/create 提交预约GET /api/appointment/list?userId1 我的预约返回结构统一成{ 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 CallbackApiResponse() { Override public void onResponse(CallApiResponse call, ResponseApiResponse 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(CallApiResponse 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_num1都判断可以预约然后各自扣减。解决办法是把判断和扣减合并成一条 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:usesCleartextTraffictrue否则请求会被系统直接拦截日志里报 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、加科室图标、加医生头像。功能闭环永远优先于界面好看答辩老师看的是你能不能讲清楚数据怎么流动不是你的按钮圆角多大。希望帮到你。本文还有配套的精品资源点击获取