基于Android的信息化医疗服务系统:架构设计与弱网优化实战
简介这是一套面向高校安卓课程设计与移动医疗开发学习者的完整项目资料源自大三学期课程作业由两人协作约两个月完成涵盖Android客户端、后端数据接口与简易Web管理后台适合想了解SpringBoot、jFinal与安卓端联调流程的初学者参考。压缩包共1106个文件约74.32MB包含52个java源码、157个xml布局与配置、235个json数据文件、67张png界面截图、11个jar依赖及apk安装包等另附一份约13000字、60页Word格式课程报告按论文结构书写便于对照理解系统设计与实现思路。目前已有1017人学习下载。读者可从中获取App端与后台源码、数据库脚本、接口设计参考及完整项目文档用于课程设计模仿、答辩准备或后续功能完善与参赛改造对移动医疗类项目实践有一定借鉴价值。1. 从一张挂号单说起Android 信息化医疗服务系统到底在解决什么早上七点半医院门诊大厅已经排起长队。窗口工作人员在纸质表格上核对身份证、翻找病历本、手写挂号单患者攥着单子跑上跑下。这个场景在很多基层医疗机构仍然每天上演。基于 Android 的信息化医疗服务系统要做的就是把这条链路从纸上搬到手持终端和自助机里患者用手机或现场平板完成建档、挂号、缴费、查报告医护用 Android 平板查房、开医嘱、扫码核对管理者在后台看实时数据。它适合两类人一类是想做医疗信息化毕设或课程设计的学生需要一套能跑通、能演示、能讲清架构的完整方案另一类是中小医院或诊所的 IT 负责人预算有限、没有大型 HIS 厂商支持想用 Android 终端加自建服务端把核心流程先数字化。这篇文章不讲空泛概念只讲这套系统怎么分层设计、Android 端和服务端怎么对接、哪些参数必须提前定死、哪些坑我踩过之后再也不碰。2. 系统分层与选型为什么 Android 端不能直接连数据库2.1 四层架构的职责边界一套能落地的信息化医疗服务系统常见做法是拆成四层接入层Android 手机、平板、自助一体机、网关层统一鉴权、限流、路由、业务服务层挂号、缴费、报告、医嘱等微服务或单体模块、数据层MySQL 存业务数据、Redis 存会话与号源缓存、对象存储放影像和 PDF 报告。Android 端只跟网关层说话绝不直连数据库。原因很直接移动端一旦被反编译数据库连接串就是裸奔而且号源扣减、缴费状态这类操作必须放在服务端做事务客户端只负责展示和提交。提示如果只是毕设演示业务服务层可以先做成单体 Spring Boot 应用但网关层和 Android 端的接口契约要按最终形态设计后面拆微服务时不用改客户端。2.2 Android 端技术栈选型理由Android 端我一般会选Kotlin JetpackViewModel LiveData/Flow Room Retrofit OkHttp。Kotlin 的空安全和协程能大幅减少回调地狱Room 做本地缓存让患者在弱网环境下也能看到最近一次的就诊记录Retrofit 负责 REST 接口OkHttp 加拦截器统一塞 Token 和打印日志。UI 层如果要做复杂表单和列表用RecyclerView ViewBinding就够了不必上 Compose除非团队已经熟悉。最低 API 等级建议定在API 26Android 8.0覆盖绝大多数在用设备同时能用到通知渠道和后台限制等新特性。2.3 服务端接口契约的三个硬约束第一所有写操作必须带幂等键。挂号、缴费这类接口客户端生成一个 UUID 作为requestId服务端用它做去重防止用户连点或网络重试导致重复扣号。第二时间统一用服务端时间。号源开放、预约截止都依赖服务端时钟客户端时间不可信。第三分页参数固定为page和sizesize上限设 50防止有人拉全量数据。下面是一个挂号接口的请求体示例{ requestId: a1b2c3d4-e5f6-7890-abcd-ef1234567890, patientId: P20240001, deptId: D001, doctorId: DOC1001, scheduleId: SCH20240520001, visitDate: 2024-05-20 }requestId由客户端生成服务端在 Redis 里以它为 key 做SETNX过期时间设 10 分钟。scheduleId对应排班表主键服务端据此扣减号源。visitDate只传日期具体时段由排班决定避免客户端传错时间格式。3. 从零跑通 Android 端环境、网络层与本地缓存3.1 开发环境与项目骨架先装Android Studio建议用较新的稳定版SDK 里勾选 API 26 到 API 34。新建项目选 Empty Views Activity语言选 Kotlin包名用com.example.medcare。在build.gradle.kts里加依赖dependencies { implementation(androidx.core:core-ktx:1.12.0) implementation(androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0) implementation(androidx.lifecycle:lifecycle-livedata-ktx:2.7.0) implementation(androidx.room:room-runtime:2.6.1) implementation(androidx.room:room-ktx:2.6.1) kapt(androidx.room:room-compiler:2.6.1) implementation(com.squareup.retrofit2:retrofit:2.9.0) implementation(com.squareup.retrofit2:converter-gson:2.9.0) implementation(com.squareup.okhttp3:logging-interceptor:4.12.0) }room-ktx提供协程支持kapt是注解处理器Retrofit 的 Gson 转换器负责 JSON 解析。版本号写死避免构建时解析最新版导致意外升级。3.2 Retrofit 网络层与 Token 拦截器网络层用一个单例ApiClient暴露 Retrofit 实例。关键在 OkHttp 的拦截器里统一加Authorization头并在 401 时触发重新登录object ApiClient { private const val BASE_URL https://your-server.com/api/ private var token: String? null fun setToken(t: String?) { token t } private val client OkHttpClient.Builder() .addInterceptor { chain - val req chain.request().newBuilder() .addHeader(Authorization, Bearer ${token ?: }) .addHeader(Content-Type, application/json) .build() chain.proceed(req) } .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build() val service: MedApi by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(MedApi::class.java) } }connectTimeout设 10 秒readTimeout设 15 秒医院内网 Wi-Fi 偶尔抖动太短会频繁超时太长用户等不及。Token 存在内存里App 重启后从 EncryptedSharedPreferences 恢复不要明文写进 SharedPreferences。3.3 Room 本地缓存与号源列表展示号源列表变化不频繁但用户可能反复查看。用 Room 建一张schedule表缓存最近查询的排班Entity(tableName schedule) data class ScheduleEntity( PrimaryKey val scheduleId: String, val deptName: String, val doctorName: String, val visitDate: String, val remainCount: Int, val updatedAt: Long ) Dao interface ScheduleDao { Query(SELECT * FROM schedule WHERE visitDate :date ORDER BY deptName) fun observeByDate(date: String): FlowListScheduleEntity Insert(onConflict OnConflictStrategy.REPLACE) suspend fun upsertAll(list: ListScheduleEntity) }observeByDate返回 FlowUI 层订阅后数据一变自动刷新。upsertAll在每次网络请求成功后调用把最新号源写进本地。remainCount为 0 时 UI 置灰按钮但最终扣减以服务端为准本地只做展示。4. 服务端核心模块号源扣减、缴费状态与报告推送4.1 号源扣减的防超卖设计号源扣减是整套系统最容易翻车的地方。常见做法是在 MySQL 里用UPDATE schedule SET remain_count remain_count - 1 WHERE schedule_id ? AND remain_count 0靠行锁保证原子性。但高并发下数据库压力大我一般会在 Redis 里先做预扣-- KEYS[1] schedule:stock:{scheduleId} -- ARGV[1] 扣减数量 local stock tonumber(redis.call(GET, KEYS[1]) or -1) if stock tonumber(ARGV[1]) then return -1 end return redis.call(DECRBY, KEYS[1], ARGV[1])Lua 脚本保证判断和扣减原子执行。返回 -1 表示库存不足返回其他值表示扣减后的余量。Redis 扣成功后再异步写 MySQL 并落一条挂号记录。如果 MySQL 写失败要有补偿任务把 Redis 库存加回去。这个补偿逻辑必须做否则 Redis 和 MySQL 会不一致。4.2 缴费状态机与回调幂等缴费状态只有四个待支付、支付中、已支付、已退款。状态流转必须单向不允许从已支付直接跳回待支付。第三方支付回调可能重复发送所以回调接口要用requestId做幂等PostMapping(/pay/callback) public String payCallback(RequestBody PayCallbackDto dto) { String key pay:callback: dto.getRequestId(); Boolean first redisTemplate.opsForValue().setIfAbsent(key, 1, 24, TimeUnit.HOURS); if (Boolean.FALSE.equals(first)) { return SUCCESS; // 重复回调直接返回成功 } orderService.markPaid(dto.getOrderId(), dto.getTradeNo()); return SUCCESS; }setIfAbsent返回 true 表示第一次处理false 表示重复。重复时直接返回成功避免第三方一直重试。markPaid里再校验订单当前状态只有支付中才能改成已支付。4.3 报告推送与 Android 端通知检查报告出来后服务端通过 WebSocket 或轮询通知 Android 端。WebSocket 心跳机制建议客户端每 30 秒发一次 ping服务端 60 秒没收到就断开。Android 端用OkHttp的 WebSocket 监听收到消息后发本地通知val ws OkHttpClient().newWebSocket( Request.Builder().url(wss://your-server.com/ws/report).build(), object : WebSocketListener() { override fun onMessage(webSocket: WebSocket, text: String) { val report Gson().fromJson(text, ReportNotify::class.java) NotificationHelper.show(context, 报告已出, report.title) } } )通知渠道在 Android 8.0 以上必须创建渠道 ID 固定为report_channel重要性设为IMPORTANCE_HIGH否则通知不弹横幅。5. 避坑与排查那些让我加班到凌晨的细节5.1 现象挂号成功但号源没减原因Redis 预扣成功但 MySQL 更新失败后没有补偿或者补偿任务没跑。解决在挂号记录表加status字段标记预扣成功、落库成功、已补偿。定时任务扫描预扣成功超过 5 分钟的记录回滚 Redis 库存并把记录置为已补偿。5.2 现象Android 端列表重复加载原因LiveData在配置变更如旋转屏幕后重新观察触发第二次请求。解决用ViewModel持有数据observe只在onCreate里调一次或者用Flow加stateIn做去重。不要直接在onResume里发请求。5.3 现象缴费回调偶尔丢失原因第三方回调超时时间短服务端处理慢导致对方重试但重试时requestId变了。解决回调接口的幂等键不能用第三方传的requestId要用自己订单号orderId。第三方每次重试订单号不变才能正确去重。5.4 现象报告 PDF 在 Android 端打不开原因服务端返回的是content://或file://路径Android 7.0 以上禁止直接暴露file://。解决服务端返回 HTTPS 下载链接Android 端用DownloadManager或 OkHttp 下载到应用私有目录再用FileProvider生成content://URI 交给系统阅读器。5.5 现象弱网下重复提交挂号原因用户点击后请求超时但服务端实际已处理用户又点一次。解决客户端在请求发出后禁用按钮直到收到响应或超时同时服务端用requestId幂等。双保险。6. 进阶技巧用接口幂等键和本地队列把弱网体验拉回来弱网是医院场景的常态电梯里、地下室、老楼 Wi-Fi 死角都会断。我的习惯是在 Android 端加一个本地待提交队列用户提交挂号或缴费时先把请求写进 Room 的pending_request表再尝试发网络。成功就删记录失败就留在表里由WorkManager在网络恢复后重试。重试时requestId不变服务端幂等键保证不会重复扣号。Entity(tableName pending_request) data class PendingRequest( PrimaryKey val requestId: String, val url: String, val method: String, val body: String, val createdAt: Long )WorkManager的约束设NetworkType.CONNECTED重试策略用BackoffPolicy.EXPONENTIAL初始延迟 10 秒最大延迟 5 分钟。这样用户在地下车库点了挂号走到地面网络恢复后自动提交不用重新填一遍。另一个技巧是接口版本号放在 URL 里比如/api/v1/register。后面服务端升级接口老版本 App 还能继续用/v1不至于一升级就全量用户闪退。这个习惯我从做第一个医疗项目就养成了后来每次迭代都庆幸留了这条路。验证方法很简单用 Android Studio 的 Network Profiler 看请求耗时用 Charles 或 Fiddler 模拟弱网延迟 2000ms、丢包 30%观察待提交队列是否按预期重试。如果重试三次后仍失败给用户一个明确的「已保存网络恢复后自动提交」提示而不是一个红色报错。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Java自动拆箱的坑让我加班到凌晨三点

Java自动拆箱的坑让我加班到凌晨三点

凌晨三点的办公室,咖啡已经喝到第三杯,我盯着屏幕上那个诡异的 NullPointerException,心里一万个问号:一个简单的整数比较,怎么就能抛NPE? 这次生产环境的账单结算功能突然崩溃,问题就出在自动拆…

2026/10/11 14:03:16 阅读更多 →
Oracle HCM Cloud落地前必须读懂的PPT业务契约

Oracle HCM Cloud落地前必须读懂的PPT业务契约

简介:本资源为Oracle人力资源管理方案的完整PPT课件,面向HR信息化建设从业者、企业IT系统规划人员及ERP实施顾问,聚焦于将传统人事管理升级为战略型人力资源管理的核心路径。课件系统阐述了组织结构与岗位编制的灵活建模(支持无限…

2026/10/11 14:03:16 阅读更多 →
Vue的v-for里用index当key,结果删数据时bug了!

Vue的v-for里用index当key,结果删数据时bug了!

"明明只是删了个列表项,怎么旁边的复选框状态自己跳了?"——上周排查生产环境bug时,我盯着屏幕上诡异的UI表现,立刻意识到又是一个经典的v-forindex连环坑。这场景你可能不陌生:中后台管理系统中&#xff0c…

2026/10/11 14:03:16 阅读更多 →

最新新闻

进程地址空间深度剖析:虚拟地址转换、堆栈增长与内存问题定位

进程地址空间深度剖析:虚拟地址转换、堆栈增长与内存问题定位

写进程地址空间第一篇文章的时候,我把虚拟内存的整体框架拆开讲了一遍:从代码段到栈,从堆到内存映射段,把一张内存布局图硬生生画了半小时。文章发出后,有同学私信问我:既然地址空间只是个“虚拟”的概念&a…

2026/10/11 14:45:43 阅读更多 →
Excel多表合并三大方案:Power Query/VBA/Python实战指南

Excel多表合并三大方案:Power Query/VBA/Python实战指南

简介:本资源是一份面向Excel初学者与办公人员的实用VBA自动化教程,聚焦解决多工作表批量合并这一高频痛点问题。文档详细讲解了如何通过一段可直接运行的VBA代码,将多个结构一致的Excel工作簿(如各部门销售表、各门店日报等&#…

2026/10/11 14:45:43 阅读更多 →
Python协同过滤旅游推荐系统:从矩阵构建到线上验证的完整文档指南

Python协同过滤旅游推荐系统:从矩阵构建到线上验证的完整文档指南

简介:这份文档面向计算机相关专业学生与旅游推荐系统开发者,围绕“信息过载”下如何满足用户个性化出行需求展开,可作为毕业设计、课程设计或推荐算法入门项目的参考模板。压缩包内仅含1个docx文件,约8.64MB,内容为完整…

2026/10/11 14:45:43 阅读更多 →
深度学习100道选择题:知识图谱的压力测试仪

深度学习100道选择题:知识图谱的压力测试仪

简介:本资源是一套系统性的深度学习基础理论自测题集,面向人工智能初学者、高校学生及备考求职者,旨在帮助读者快速检验监督学习、无监督学习、模型评估、正则化、降维、目标检测等核心概念的掌握程度。题库共100道高质量单选题,覆…

2026/10/11 14:45:43 阅读更多 →
DSSS抗窄带干扰MATLAB仿真:从扩频增益到频域置零与半解析BER

DSSS抗窄带干扰MATLAB仿真:从扩频增益到频域置零与半解析BER

简介:这是一份面向无线通信学习者与MATLAB仿真实践者的DSSS扩频通信抗窄带干扰仿真代码包,聚焦直接序列扩频系统在窄带干扰环境下的建模与性能分析,适合通信工程、电子信息类专业学生及需要理解扩频抗干扰机制的开发者参考。包内共3个文件&am…

2026/10/11 14:45:43 阅读更多 →
单链表基础三题详解:删除节点、反转链表与找中间节点

单链表基础三题详解:删除节点、反转链表与找中间节点

链表这块内容,大学里第一次接触的时候觉得简单,无非是节点加指针。可真到动手写题的时候,删除节点能删丢一半,反转链表能绕成环,找中间节点还会因为奇偶数量吵半天。单链表综合练习里的“删除指定值节点”“反转链表”…

2026/10/11 14:44:43 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →