拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python智慧养老系统实战:Django与Flask组合开发完整指南

最近我把一个“智慧养老服务系统”从零到一跑通了后端用的是Python生态里最常见的两个框架Django和Flask。说实话Django加Flask这种组合不少人觉得多余但真正做进项目里你会发现Django负责繁重的业务模块和后台管理Flask去扛轻量的推送接口和硬件设备对接反而特别顺手。这套系统面向社区养老服务中心和中小型养老院解决的是老人健康数据不上云、紧急呼叫响应慢、家属沟通靠电话这些老问题。如果你正在用Python做管理系统或者想给老人照护场景做一套数字化中台这篇文章可以当一份带坑的实战笔记来看。先说结论智慧养老系统不是一个智能硬件项目而是一个“流程管理数据采集消息触达”的综合业务系统。硬件只负责产生数据关键价值在于数据进来之后怎么被处理、告警怎么被响应、消息怎么被闭环。Django和Flask在其中各司其职一副“大小王”的搭配。1. 项目背景与需求拆解1.1 智慧养老到底要解决什么问题我们和本地的社区服务中心聊过收纳的老人大多是高龄、独居和慢性病患者家属白天上班根本顾不过来。养老服务的痛点不是缺床位而是信息不透明、响应不及时、照护记录靠手写。比如老人突然按铃值班室阿姨要先翻纸质档案才知道老人有没有基础疾病家属想了解老人今天的血压血糖只能打电话问护理员。这些问题听起来不大但日积月累会消耗大量人力也容易出责任事故。智慧养老服务系统要解决的就是三件事把老人的健康信息电子化、把紧急事件响应流程线上化、把家属和护工之间的沟通固化下来。说白了就是一个面向养老场景的管理平台或者叫“养老院的中台”。它能覆盖老人档案管理、健康数据采集、设备告警、工单处理等一整套流程让家属、护工、管理员都能在一个系统里看到各自关心的内容。1.2 为什么选PythonDjango和Flask各管一段我们团队在选型时没纠结太久语言直接锁定Python。原因很简单养老场景会对接大量智能硬件设备像血压计、血氧仪、睡眠雷达这些设备的SDK和协议大多有Python示例后端采集和处理逻辑用Python写最省事。而且团队里几个同学都熟悉Python学框架成本低。Django的优势是“全家桶”非常完整自带ORM、Admin后台、用户认证、表单、分页、中间件做一个业务系统几乎不用找第三方库。Flask则完全没有这些强制约束只有路由、请求上下文和模板渲染写一个几KB的小服务非常轻。实际项目中我们把Django作为主业务平台负责养老机构管理、人员档案、权限、报表等重量级功能把Flask作为边缘服务单独部署在另一台内网机器上专门接收智能硬件设备的上报数据以及对外提供站外通知接口。这么拆分不是因为Django跑不动硬件接口而是不想因为频繁升级主业务代码影响到设备数据链路两个服务独立部署、独立维护更安全。这个决策其实很多人不理解但我的看法是框架只是一种工具项目是单体还是多服务取决于团队分工和故障隔离的需求而不是为了“微服务”三个字。2. 系统整体架构与功能模块2.1 整体架构Django主服务加Flask边缘服务我们画过一张架构图这里用文字描述。用户通过浏览器访问NginxNginx把静态资源直接返回把动态请求反向代理到DjangoDjango使用MySQL存业务数据使用Redis做缓存和会话存储。另一条链路是设备通过HTTP上报数据到FlaskFlask先做格式校验把数据写入Redis队列随后Django的定时任务从Redis里读取数据入库Flask同时会根据规则触发告警。为什么加Redis队列因为设备上报频率不稳定高峰时段可能几百条记录同时上来直接写MySQL会把主库拖慢。加一层Redis缓冲之后即便Django短暂重启数据也不会丢。前端没有单独做前后端分离页面直接使用Django模板渲染。主要考虑到团队没有专门前端服务端渲染可以把页面逻辑和后端绑定在一起开发效率高也能减少接口联调成本。部分展示型页面用了Vue的CDN做局部交互比如大屏的图表数据这样不用引入全套Node构建链。2.2 核心功能模块拆解系统的功能模块我列一张表方便你直接对应开发范围模块名称主要功能技术实现要点老人档案基本信息、家属关系、既往病史、用药记录Django Model加Form加Admin健康监测设备数据展示、趋势图、异常标红MySQL加ECharts紧急呼叫一键求助、响应记录、超时提醒Flask加WebSocket加短信接口工单管理护工任务派发、完成状态跟踪Django ORM加状态流转消息通知事件通知家属、站内信、公众号模板消息Redis加Celery加Flask回调权限管理管理员、护工、家属角色和菜单权限Django Group加Permission数据大屏实时入住率、告警数、平均响应时长聚合查询加Chart.js这里单独说一下数据大屏。很多智慧养老项目都爱把“大屏”做成演示屏幕其实真正有用的是“值班室实时看板”。我们的大屏默认显示在养老院值班室大电视上按级别显示未处理告警、今日工单、异常设备数量超过两分钟未响应的告警会置顶闪烁。这个功能看上去简单但实际价值很高因为值班人员不可能一直盯着每个老人后台的九宫格。2.3 环境准备与项目初始化环境依然是老一套。本机用WindowsPython版本3.10虚拟环境用venv创建PyCharm或VSCode都行。VSCode强烈建议先装Python扩展和Pylance否则代码提示弱得让人抓狂。创建项目我习惯先建一个虚拟环境然后再安装依赖python -m venv venv venv\Scripts\activate pip install django4.2 flask3.0 waitress gunicorn mysqlclient redis celery django-cors-headers这里提一句Python版本别太老Python 3.8以下不建议因为新版Django和依赖库会放弃旧版支持。装mysqlclient之前Windows上一般要确保VC运行库已经装好否则会报“Unable to find vcvarsall.bat”这个坑当年卡了我一下午。创建Django项目和appdjango-admin startproject carehome cd carehome python manage.py startapp elder python manage.py startapp health python manage.py startapp alert python manage.py startapp system每个app按职责拆分elder管档案health管健康数据alert管告警和紧急呼叫system管权限和系统配置。Flask服务单独放在一个device_gateway目录不和Django项目目录搅在一起。这是目录结构上的一个经验不要把所有代码放在一个文件夹下面至少按“业务平台”和“设备接入”分开后续部署和找问题都清爽。3. 数据库模型与关键业务实现3.1 数据模型设计要点数据模型是这类系统的核心。我们花了不少时间设计几个关键表如下表名核心字段说明usersusername, password, role, phone登录用户统一用Django AbstractUser扩展elder_infoname, id_card, birthday, bed_no, emergency_contact老人基本信息health_recordelder, heart_rate, blood_pressure, blood_oxygen, create_time健康指标记录device_infodevice_id, type, binding_elder, status硬件设备绑定关系alert_recorddevice_id, elder, alert_type, status, handler告警与响应记录work_ordertitle, assignee, status, priority, created_at护工工单设计时的几个注意点一是老人档案不要直接用用户表老人不一定有账号需要单独建表通过create_user关联护理员账号二是健康指标不要和老人表做成一个大宽表指标会持续增长每天几千条宽表查询和存储都会很难受三是一张设备表要预留设备状态字段因为设备掉线本身也是告警来源。Django的ORM映射这些表很简单比如ElderInfo的Model大概是class ElderInfo(models.Model): name models.CharField(max_length50) id_card models.CharField(max_length18, uniqueTrue) birthday models.DateField() bed_no models.CharField(max_length20) emergency_contact models.CharField(max_length50) emergency_phone models.CharField(max_length20) status models.CharField(max_length1, choices[(0,正常),(1,已出院)], default0) create_time models.DateTimeField(auto_now_addTrue)注意auto_now_add用于创建时间auto_now更新时间。如果你用created_at models.DateTimeField()不在save时自动更新每次手动维护会漏。我做项目时吃过这个亏后来统一用auto_now_add和auto_now不再手动赋值时间。3.2 用户认证与RBAC权限控制Django自带用户认证但直接改内置User表并不够。我们用AbstractUser扩展出CustomUser增加phone和user_type字段。user_type区分管理员、护工、家属三种角色。RBAC的落地我选用Django自带的Group加Permission没有自己再造权限引擎。管理员在Admin后台给不同角色组分配权限代码里通过装饰器判断from django.contrib.auth.decorators import login_required, permission_required login_required permission_required(elder.view_elderinfo, raise_exceptionTrue) def elder_list(request): elders ElderInfo.objects.filter(status0) return render(request, elder/list.html, {elders: elders})为什么不用自己设计菜单权限表因为Django Group和Permission已经覆盖了大部分场景尤其是“护工只能看不能改、管理员全权限”这种需求通过Admin后台就能配置根本不用改代码。真到了需要自定义“数据级权限”的时候再通过中间件重写queryset也不晚。在这个项目里我们只在工单模块加了自定义装饰器用来限制护工只能看到自己负责的工单其他模块全部用自带方案。3.3 健康数据上传与设备对接这部分是踩坑比较多的。设备上报到Flask的接口我们约定统一JSON格式{ device_id: SN001, type: blood_pressure, value: {high: 138, low: 92}, timestamp: 2025-01-15 08:30:00 }Flask侧处理逻辑非常简单app.route(/api/upload, methods[POST]) def upload(): data request.get_json() if not data or device_id not in data: return {code: 400, msg: invalid data}, 400 device_id data[device_id] device get_device(device_id) if not device: return {code: 404, msg: device not found}, 404 r.rpush(health:queue, json.dumps(data)) if data[type] blood_pressure and data[value][high] 160: create_alert(device.elder, 血压偏高) return {code: 0, msg: ok}Flask本身不直接写MySQL而是把数据推入Redis队列。Django这边用Celery的周期任务每隔几秒从Redis里取数据批量写入app.task def sync_health_data(): pipe r.lrange(health:queue, 0, 99) if not pipe: return for item in pipe: data json.loads(item) HealthRecord.objects.create( elderdata[elder_id], heart_ratedata.get(heart_rate), blood_pressuref{data[value][high]}/{data[value][low]}, create_timedata[timestamp] ) r.ltrim(health:queue, 100, -1)这里要注意时间字段用字符串传给DjangoORM会自动转成datetime但如果设备上报的格式不是严格的YYYY-MM-DD HH:MM:SS会报ValidationError。所以我建议在Flask校验层先做一次datetime.strptime转换失败直接丢弃或标记脏数据别让它污染数据库。3.4 紧急呼叫与消息通知紧急呼叫是系统里最不能出错的模块。我们的实现是老人按下床头呼叫器呼叫器通过网络把信号发给平台我们用Flask接收并落一条AlertRecord然后立刻通过WebSocket给值班室屏幕推送一条告警同时调用第三方短信API给紧急联系人发短信。值班人员点击“响应”后系统记录响应时间超过5分钟未响应自动升级提醒。def create_alert(elder, alert_type): record AlertRecord.objects.create( elderelder, alert_typealert_type, statuspending ) send_websocket({alert_id: record.id, elder_name: elder.name}) send_sms(elder.emergency_phone, f{elder.name}发出{alert_type}告警) return record消息通知模块我们用了Celery异步发短信避免接口超时。如果对接的是第三方短信服务一定要做重试和幂等。重试保证消息不丢幂等防止重复扣费。最简单的幂等方案是在数据库里存一个message_id每条消息生成唯一ID发短信前先查是否存在存在就直接返回。4. Django与Flask的协作实战4.1 Django侧MTV模式到底怎么用再说说Django的MTV模式。很多初学者分不清MTV和MVC其实Django里的M就是Model对应数据库表T是Template负责页面长什么样V是View负责业务逻辑怎么跑。和传统MVC的区别在于Controller的角色被Django的URL路由和View一起承担了。我们的项目里View层只做分发和组合数据业务逻辑尽量下沉到Model或Service否则一个函数写几百行后期根本没法维护。一个简单示例展示健康趋势页面的Viewdef health_trend(request, elder_id): records HealthRecord.objects.filter( elder_idelder_id, create_time__gtetimezone.now() - timedelta(days7) ).order_by(create_time) data { labels: [r.create_time.strftime(%m-%d %H:%M) for r in records], heart_rate: [r.heart_rate for r in records], } return JsonResponse(data)很多教程会直接把查询写在View里没问题但一旦查询逻辑要复用比如大屏也要用同样的趋势数据我就建议抽成一个service函数接口和模板共同调用。这样代码看起来清爽调试也方便。4.2 Flask侧轻量接口怎么设计和调试Flask侧我习惯用蓝图把接口区分开device_api.py、notify_api.py、wechat_api.py。每个蓝图内部先做参数校验再调用业务函数。Flask开发时默认开启debug模式会暴露一些环境变量实际部署必须关掉。另外Flask的request.get_json()返回的是dict如果你要判断一个字段的类型用type(x).__name__打印一下不要凭印象。比如设备上报的high有可能是字符串138不是int判断if data[value][high] 160就会报字符串和整数比较错误所以校验方法要写成def to_int(value): try: return int(value) except (TypeError, ValueError): return 0Flask如何绑定到网页元素这个问题其实是理解方向错了。Flask本身不直接绑定网页元素需要使用模板引擎渲染出HTML标签然后由浏览器展示。如果你要在按钮点击后调用Flask接口常用方式是在模板里写form methodpost action/api/upload或者用AJAX请求对应的URL。所谓“绑定”本质上就是URL路由和前端元素的关系。开发时可以先在浏览器F12的Network面板看请求URL是否命中路由再排查后端逻辑。4.3 服务间通信与接口联调Django和Flask之间的通信我们采用了两种方式同步的REST API和异步的Redis队列。主流程中需要实时确认的场景用REST例如Django需要知道某设备是否在线调Flask的/api/device/status接口设备数据上报这种高频、允许延迟的场景用Redis队列削峰填谷。联调阶段最大的坑是接口地址写死。开发环境Django跑在8000端口Flask跑在5001端口代码里直接用http://127.0.0.1:5001没有问题但部署后服务器域名和端口会变。我们专门用一个common/config.py统一管理服务地址而且环境变量优先import os FLASK_BASE_URL os.getenv(FLASK_BASE_URL, http://127.0.0.1:5001)另外CORS跨域问题也要注意。如果前端页面通过AJAX请求Flask接口需要在Flask里设置Access-Control-Allow-Origin否则浏览器会跨域拦截。简单方案是使用flask-cors库pip install flask-corsfrom flask_cors import CORS CORS(app, resources{r/api/*: {origins: *}}, supports_credentialsTrue)只开/api/路径的跨域不要全开能减少安全隐患。我遇到过Flask侧已经加了CORS但还是报跨域最后发现是浏览器的同源策略把非简单请求拦截了需要先处理预检请求OPTIONS。加CORS后要重启Flask进程不能光改代码不重启。5. 部署上线与疑难杂症排查5.1 Windows环境使用Waitress加Nginx部署很多人部署Python Web项目分不清环境。开发环境用python manage.py runserver和flask run就够了生产环境必须换一个能扛并发、足够稳定的WSGI服务器。我们内网机器是Windows Server没用gunicorn选了Waitress因为它是纯Python实现安装简单Windows上跑得很稳。Django启动命令waitress-serve --listen0.0.0.0:8000 carehome.wsgi:applicationFlask服务类似waitress-serve --listen0.0.0.0:5001 app:appNginx在Windows上作为反向代理静态文件直接由Nginx返回动态请求转发给Waitress。Nginx的关键配置片段server { listen 80; server_name care.example.com; location /static/ { alias D:/project/carehome/static/; } location /media/ { alias D:/project/carehome/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里的坑是路径分隔符。Windows路径要用正斜杠或双反斜杠alias D:/project/...这样可以但如果在配置文件里写反斜杠Nginx会把它当转义字符。还有Windows下的Nginx不要放在有空格的目录里否则启动服务可能会失败。另外Waitress默认是单进程多核CPU可以开多个进程配合负载均衡不过内网小并发不太需要。5.2 静态文件加载失败一个老生常谈的坑Django项目开发时很常见的问题VSCode里写了img src{% static images/logo.png %}但浏览器就是404。原因通常有三类第一忘记在settings.py里配置STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static ]第二模板顶部没有加载{% load static %}导致static标签解析失败。第三生产环境DEBUGFalse时Django默认不再自动提供静态文件服务必须执行python manage.py collectstatic把静态文件集中到STATIC_ROOT目录并且由Nginx指向STATIC_ROOT而不是开发时的STATICFILES_DIRS。我们测试时踩过最狠的一回是collectstatic执行了但Nginx的root写错结果整个页面裸奔所有样式和图片都丢了。排查这类问题有个顺序先看浏览器Network里失败资源的具体URL再对照Nginx配置里的alias路径是否真的存在最后检查文件权限。5.3 部署后附件路径错误老人档案里需要上传身份证照片和体检报告Django的FileField上传路径是相对路径。我们一开始在settings里写MEDIA_URL /media/ MEDIA_ROOT media/本机运行没问题部署到Windows服务器后问题来了上传的文件被存到启动目录下的media里但Nginx alias写的是D:/project/carehome/media/路径不一致导致图片展示不出来。解决方式是把MEDIA_ROOT改成绝对路径import os BASE_DIR os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MEDIA_ROOT os.path.join(BASE_DIR, media)上传下载都用Django提供的File对象不要自己拼文件路径防止Windows和Linux路径分隔符差异。另外上传接口一定要校验文件扩展名使用str.endswith()判断不能只靠Content-Type因为浏览器可以伪造Content-Type。我见过有人上传了.html文件然后被浏览器直接渲染非常危险。5.4 常见问题速查表问题现象解决思路数据库迁移失败makemigrations正常migrate报错检查MySQL版本Django 4.2需要MySQL 8.0检查表名是否和关键字冲突查询不到数据get()抛DoesNotExist用filter().first()或get_object_or_404删除数据不生效有外键报ProtectedError理清外键on_delete参数批量删除用filter().delete()上传图片404Nginx返回404检查MEDIA_ROOT和Nginx alias路径是否一致Flask返回中文乱码API返回中文变成\uXXXX设置app.config[JSON_AS_ASCII] False浏览器跨域AJAX请求被拦截Flask配置CORS仅开放需要的路径Celery任务不执行周期任务没启动确认beat和worker都启动redis连接正常大屏图表没数据AJAX拿到空数组查看浏览器Network和后台日志确认时间范围过滤条件是否错误这里单独说一下“Django执行查询删除对象”这道送分题。删除单个对象时obj.delete()返回一个元组表示删了多少对象但前提是你已经拿到了对象。如果只想批量删除符合条件的数据用QuerySet.delete()HealthRecord.objects.filter(create_time__lt2024-01-01).delete()注意QuerySet.delete()不会触发Model里的delete()方法也就是不走自定义逻辑所以如果有级联清理要手动处理。还有一个更隐蔽的坑如果你调用filter().first().delete()一定先判断first()是不是None否则会报AttributeError。5.5 个人实战体会这个项目从需求梳理到部署上线大概花了三周。我个人最深的体会是智慧养老系统能不能落地不在于用了多新的算法而在于流程是否闭环。设备测到血压异常系统推给谁推了有没有人看看了能不能及时联系家属每一步都要有明确的责任人和状态流转否则就是花了钱买一个大屏摆设。最后再分享一个开发顺序的小技巧。先做紧急呼叫和告警闭环再做健康数据大屏最后补权限和后台管理。因为告警闭环是养老院的刚需这个跑通之后客户才会信任你后面加功能才有空间。先解决有没有再解决好不好这个顺序我试过确实能减少很多返工。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门