ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Flask的智慧养老系统实战:从数据库设计到部署上线

基于Flask的智慧养老系统实战:从数据库设计到部署上线 做这种“业务管理系统”最有意思的一个问题永远是功能清单都差不多为什么有的系统能真正被用起来有的做完就丢在抽屉里吃灰这套基于 Flask 的智慧养老系统我前前后后改过三版最大的体会是真正让“智慧养老”落地不是堆多少个模块而是把养老院、社区服务站里最容易被漏掉的那些事用一条清晰的流程链给串起来。老人电子档案、健康数据记录、服务工单派发、异常告警通知加上家属端查看这几个核心闭环保住系统基本上就立住了。如果你是拿来做课程设计、毕业设计或者想快速跑一个带完整前后端的项目参考这套 Flask 架构值得认真走一遍如果你是刚入行的开发者想搞清楚一个真实业务系统从数据库设计到部署上线是怎么拆出来的这篇更对胃口。废话不多说我直接按项目的真实开发顺序来讲。1. 项目定位与核心需求拆解1.1 智慧养老系统到底在解决什么问题先泼一盆冷水养老行业里的“智慧”很多项目只是装个平板、放个大屏本质上还是人工操作。真正有意义的智慧养老系统瞄准的是这几个痛点老人纸质档案散落各处一个老人住进来健康情况、既往病史、紧急联系人全凭记忆服务过程靠微信群里吼护工今天去没去、服务做没做管理员只能晚上翻聊天记录老人突发血压异常、心率异常第一发现人往往是老人自己或护工等通知到家属耳朵里已经过了很久家属想了解老人近况没有统一入口只能频繁打电话“查岗”。所以这个系统的核心不是做一个花哨的仪表盘而是把“建档—监测—服务—异常响应—家属反馈”这条业务闭环跑通。落实到功能模块上也就清晰了老人基础档案管理、家属账号绑定、健康数据记录与查询、服务类别维护、服务工单流转、护工派单、异常告警处理、管理端统计。这个范围定下来之后后端选型就比较好判断了。1.2 技术选型为什么这次用 Flask 而不是 Django 或 FastAPI老实说市面上类似课程设计项目用 Django 的也不少Django 自带 admin、ORM、迁移甚至权限框架开发效率听起来很高。但我还是选了 Flask原因很直接Flask 足够轻路由、视图、模板、ORM 全部可以按需集成对一个小型系统来说不会产生“框架压过业务”的感觉源码思路清晰所有东西都在你能看到的位置排查逻辑的时候不用翻框架源码对部署环境要求低SQLite 起步、MySQL 无缝切换放在一台 2G 内存的服务器上也能跑得很顺网上 Flask 的教程、源码、面试题覆盖量极大后面无论扩展还是换人维护门槛都更低。有人会提 FastAPI性能更好、自带异步、自动生成接口文档听起来很香。但对于这个项目来说核心是模板页面 少量 JSON 接口没有高并发实时推送的需求FastAPI 的优势发挥不出来反而会让很多人卡在 async/await 的理解上。Flask 在这里更像一把趁手的小刀不需要它就是不需要这个选择不丢人。2. 整体架构与数据模型设计2.1 模块划分与核心业务主流程整个系统按使用角色分成三个端管理端系统管理员负责老人档案审核、服务类别管理、护工管理、工单派发、异常告警查看与处理护工端护工查看自己的待办工单开始服务、提交完成回执补充老人健康数据家属端家属登录后可查看老人健康记录、服务记录、异常提醒也可以代老人提交服务申请。看一遍主流程就很清楚家属或管理员前台录入老人信息 → 管理员开通家属账号 → 护工/管理员在服务过程中记录健康指标 → 系统检测到指标异常自动生成告警 → 管理员确认消息可通知家属 → 家属登录查看详情。这一整条链路每个环节都能对应到一张数据表和一组视图函数这也是这个项目能作为“案例”教别人怎么写的原因——它没有把功能做成孤岛。2.2 数据表怎么设计少即是多我给项目设计数据表时坚持一个原则能用 8 张表讲清的绝不放 20 张。表多了关联关系复杂表少了业务流转踹不开。最终核心表结构如下表名作用关键字段user登录用户管理员/护工/家属id, username, password_hash, role, elder_id, staff_idelder老人档案id, name, gender, birth_date, id_card, address, contact_name, contact_phone, health_notestaff护工档案id, name, phone, skills, statushealth_record健康数据记录id, elder_id, recorder_id, type, value, record_timeservice_type服务类别id, name, price, enabledservice_order服务工单id, order_no, elder_id, staff_id, service_type_id, status, appoint_time, finish_timealert异常告警id, elder_id, alert_type, level, content, status, created_atlogin_log登录日志id, user_id, ip, login_time每个关系我都尽量保持简单一个老人可以有多个健康记录一个老人可以有多个服务订单一个护工可以被派多个订单多个家属账号可以挂在同一个老人下面。这种看似“朴素”的设计恰恰让后续写统计查询时不用做复杂的嵌套子查询性能也稳。2.3 项目目录与蓝图划分Flask 项目最怕把所有路由堆在一个 app.py 里几百行还能忍上千行直接劝退。我采用蓝图Blueprint拆分目录结构如下app/ |-- app.py # 应用入口注册蓝图 |-- config.py # 配置类 |-- requirements.txt |-- models.py # 数据库模型统一放一个文件或拆到 models/ 包 |-- extends.py # db、login_manager 等扩展实例 |-- views/ | |-- auth.py # 登录/登出 | |-- elder.py # 老人档案与健康数据 | |-- service.py # 服务工单与派单 | |-- alert.py # 异常告警 | |-- dashboard.py # 管理端首页与统计 |-- templates/ | |-- auth/ | |-- elder/ | |-- service/ | |-- alert/ |-- static/ | |-- css/ | |-- js/这样拆的好处是每个蓝图只负责一个业务域新增功能不影响旧路由更重要的是团队协作时每个人改一个文件合代码不会天天冲突。第一次做项目的人建议直接养成这个习惯。3. 核心功能模块实现3.1 登录认证与角色权限从“能登录”到“能防越权”用户体系里我设计了三种角色管理员admin、护工staff、家属family。登录后把用户 ID 和角色写进 session前端页面根据角色渲染不同菜单后端通过装饰器控制访问权限。这里有一个特别容易被忽略的点很多初学项目只做了“登录后才能看”的判断没做“不同角色各看各的”。比如家属登录后理论上只能看到自己绑定老人相关的数据物理上都应该在后端做过滤不能只靠前端按钮隐藏。我用了一个角色装饰器在视图函数的入口做拦截from functools import wraps from flask import session, redirect, url_for, flash def role_required(*roles): def decorator(f): wraps(f) def wrapper(*args, **kwargs): if user_id not in session: flash(请先登录, warning) return redirect(url_for(auth.login)) if session.get(role) not in roles: flash(没有权限访问该页面, danger) return redirect(url_for(dashboard.index)) return f(*args, **kwargs) return wrapper return decorator密码存储一定不要用明文这个项目我用的werkzeug.security里的generate_password_hash和check_password_hash。别嫌麻烦哪怕只是校内项目用户名密码泄露也是事故。登录成功的同时我会往login_log表插入一条记录管理员在后台能看到登录 IP 和时间对排查“账号被异地登录”非常有用。3.2 老人档案与健康数据把最普通的 CRUD 做出细节老人档案就是典型的增删改查但细节坑不少。身份证号格式、手机号格式、出生日期范围这些校验不能省更重要的是“档案创建后怎么和家属账号绑定”这一步很多项目会漏。我的做法是创建老人档案后生成一个初始密码和邀请码由管理员线下发给家属家属首次登录时强制修改密码。这样就不需要预置一堆弱密码账号。健康数据模块做得比较实在支持血压、心率、血糖、血氧等指标类型记录时带上录入人。老人健康指标记录数据足够多之后页面上用 ECharts 画一段趋势图。这里展示一个简化版的路由elder_bp.route(/health/add/int:elder_id, methods[POST]) role_required(admin, staff) def add_health_record(elder_id): record_type request.form.get(type) value request.form.get(value) elder Elder.query.get_or_404(elder_id) record HealthRecord( elder_idelder.id, recorder_idcurrent_user.id, typerecord_type, valuevalue, record_timedatetime.now() ) db.session.add(record) db.session.commit() flash(健康数据已记录, success) return redirect(url_for(elder.detail, elder_idelder.id))有的开发者会把“当前登录用户”用g.user或 session 里手动取值我建议统一用 Flask-Login 的current_user它既能在模板里直接用也能在后端做权限判断省掉不少重复代码。3.3 服务工单与护工派单状态机要清晰服务工单是这个系统里业务逻辑最重的部分我把状态定义成待分配pending→ 已分配assigned→ 已完成done→ 已取消cancelled。为什么不用“进行中”因为实际养老场景里服务开始和结束的边界很难卡如果你加了太多状态管理员和护工都搞不清当前到底该点哪个按钮。四个状态正好每个状态的流转路径都明确管理员创建工单后状态为 pending管理员选择护工后状态改为 assigned护工完成服务提交回执后状态改为 done管理员在分配前可以取消工单状态为 cancelled。派单逻辑我第一版做的是“自动派给空闲护工”后来发现实际业务里家属可能有指定偏好于是改成默认支持人工指派 后台显示当前在线护工和忙碌护工。这个改动说明一个道理需求永远要跟着真实场景走不是越智能越好而是越匹配越好。工单完成时我会记录完成时间并和预约时间对比后续统计“及时完成率”。“及时”这类指标如果没有系统记录时间光靠人嘴说是说不清的有了数据之后管理养老院就变成了看报表而不是听汇报。3.4 异常告警与提醒处理我最喜欢的模块告警模块写起来不复杂但效果最像“智慧系统”。逻辑很简单健康数据录入后系统根据指标类型和预设阈值判断是否异常。比如血压值超过 160/95或者心率低于 50就自动生成一条告警记录同时给绑定该老人的家属账号生成一条站内通知。阈值早期我是写死在代码里的ALERT_RULES { blood_pressure_high: 160, heart_rate_low: 50, heart_rate_high: 120, blood_sugar_high: 11.1, }后来管理员反馈希望改阈值不重新上线代码我改成把阈值放进单独配置表里。这个改动成本不大却非常提升维护体验。告警生成后管理员在告警列表能看到状态“未处理/已确认/已通知”确认后再调用短信平台接口发短信。这一步在开发环境我会留一个 mock 接口避免天天发真短信也方便离线调试。4. 关键代码与核心环节解读4.1 应用初始化和配置一个 Flask 项目能不能顺利跑起来配置和扩展初始化是第一道关。我习惯把 db 实例单独放在extends.py避免 models、views 和 app 之间循环导入# extends.py from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() login_manager LoginManager()# app.py from flask import Flask from config import Config from extends import db, login_manager def create_app(): app Flask(__name__) app.config.from_object(Config) db.init_app(app) login_manager.init_app(app) login_manager.login_view auth.login from views.auth import auth_bp from views.elder import elder_bp from views.service import service_bp from views.alert import alert_bp from views.dashboard import dashboard_bp app.register_blueprint(auth_bp, url_prefix/auth) app.register_blueprint(elder_bp, url_prefix/elder) app.register_blueprint(service_bp, url_prefix/service) app.register_blueprint(alert_bp, url_prefix/alert) app.register_blueprint(dashboard_bp, url_prefix/) return app这种“应用工厂模式”看着多了一层好处是测试环境下可以创建一个临时 app生产环境也可以多实例加载同一个工厂函数后面部署的时候会发现这个设计非常顺手。4.2 数据模型示例用 SQLAlchemy 定义表关系我模型文件里最核心的几个类这样写class Elder(db.Model): __tablename__ elder id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(50), nullableFalse) gender db.Column(db.String(10)) birth_date db.Column(db.Date) id_card db.Column(db.String(18), uniqueTrue) address db.Column(db.String(255)) contact_name db.Column(db.String(50)) contact_phone db.Column(db.String(20)) health_note db.Column(db.Text) create_time db.Column(db.DateTime, defaultdatetime.now)重点解释两个细节一是uniqueTrue加在身份证上防止重复建档二是defaultdatetime.now必须在函数调用位置而不是datetime.now()否则模型创建时会把服务器启动时间固化成默认值这个坑在测试环境不易发现上线后日期全部错乱排查成本很高。服务工单模型我会单独把状态列出来便于查询不同状态的工单数class ServiceOrder(db.Model): __tablename__ service_order id db.Column(db.Integer, primary_keyTrue) order_no db.Column(db.String(32), uniqueTrue) elder_id db.Column(db.Integer, db.ForeignKey(elder.id)) staff_id db.Column(db.Integer, db.ForeignKey(staff.id), nullableTrue) service_type_id db.Column(db.Integer, db.ForeignKey(service_type.id)) status db.Column(db.String(20), defaultpending) appoint_time db.Column(db.DateTime) finish_time db.Column(db.DateTime) create_time db.Column(db.DateTime, defaultdatetime.now)order_no我生成规则是date(Ymd) 四位随机数防止前端刷新重复提交时产生同一条工单。这里如果再严谨一点还应该加数据库唯一索引兜底。4.3 登录路由与视图函数会话管理的关键登录视图我单独讲一下因为不少初学者在这写出来的代码缩进有问题或者校验顺序不对。正确的顺序是先查用户再校验密码最后判断角色是否可用任何一个环节失败都不要继续auth_bp.route(/login, methods[GET, POST]) def login(): if request.method POST: username request.form.get(username) password request.form.get(password) user User.query.filter_by(usernameusername).first() if user and check_password_hash(user.password_hash, password): session[user_id] user.id session[role] user.role login_log LoginLog(user_iduser.id, iprequest.remote_addr) db.session.add(login_log) db.session.commit() return redirect(url_for(dashboard.index)) flash(用户名或密码错误, danger) return render_template(auth/login.html)这里有个容易被忽略的问题用户表里如果有“已停用”状态登录成功不代表账号可用。我后来给 User 模型加了is_active字段登录条件里同时判断user.is_active True否则离职护工还能登录系统看到老人隐私这不是小事。4.4 前端页面与接口之间的配合项目的前端我用了 Jinja2 模板 Bootstrap 布局没有做前后端分离。为什么这类系统的页面数量不多交互也不重模板渲染成本低、开发快直接改一个地方全局生效。家属端的数据趋势图用的是后端把一个列表 JSON 传进模板再用 ECharts 渲染family_bp.route(/elder/int:elder_id/chart) role_required(family, admin) def elder_chart(elder_id): records HealthRecord.query.filter_by(elder_idelder_id).order_by(HealthRecord.record_time.asc()).all() data [{time: r.record_time.strftime(%m-%d), value: r.value} for r in records] return render_template(family/chart.html, data_jsonjson.dumps(data))模板里就一步到位script var chartData {{ data_json|safe }}; // 初始化 echarts 并绘制折线图 /script|safe过滤器使用要谨慎这里 data_json 是我们自己序列化的数字和日期不会出现不可信文本所以可以用。如果你让用户输入了内容还直接|safe渲染那就是 XSS 漏洞的温床这条红线一定要守住。5. 部署上线与运维要点5.1 本地怎么快速跑起来拿到源码第一步永远是先看依赖列表而不是直接python app.py。我的 requirements.txt 保持精简flask flask-sqlalchemy flask-login flask-wtf werkzeug pymysql本地开发环境建议用虚拟环境加 SQLite零配置启动python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt python init_db.py # 创建数据库表和初始管理员账号 python app.py启动后访问http://127.0.0.1:5000默认管理员账号密码我会在初始化脚本里写清楚第一次登录后强制改密。init_db.py 里的建表逻辑用的是db.create_all()这个对小型项目够用如果后续频繁改表结构建议直接上 Flask-Migrate否则你会经历手工删表重建的尴尬。5.2 生产部署Gunicorn Nginx 的组合开发用的flask run自带了一个开发服务器性能差、稳定性也差绝不能直接扔到线上。生产环境我用的组合是 Gunicorn 跑后端进程Nginx 做反向代理和静态文件服务。安装和启动命令如下pip install gunicorn gunicorn -w 2 -b 127.0.0.1:8000 app:app-w 2是启动两个 worker 进程对这台低配服务器足够。为什么绑127.0.0.1而不是0.0.0.0因为不打算让 Gunicorn 直接对外暴露端口请求统一走 Nginx安全策略更清晰。Nginx 虚拟主机配置核心段server { listen 80; server_name your_domain_or_ip; location /static { alias /home/www/smart-elder/static; expires 7d; } 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; } }把 /static 从 Flask 里剥离出去交给 Nginx 这步很重要否则静态资源请求都要穿一层 Python性能白白浪费而且 Flask 默认静态文件服务在高并发下很容易成为瓶颈。5.3 安全加固和常见性能陷阱先列安全清单三件事必须做app.secret_key换成环境变量里的随机值app.debug False不能忘登录接口加限流或验证码防止有人暴力破解后台。还有一点容易被忽略Nginx 代理后Flask 拿到的request.remote_addr全是127.0.0.1必须在 Nginx 里设置X-Forwarded-For头并在 Flask 里配合 ProxyFix 中间件登录日志里的 IP 才有意义。性能层面这类系统瓶颈通常不在 Flask 本身而在数据库查询。列表页我坚决不写裸的Model.query.all()至少加.paginate(page, per_page)数据量到几千条之后分页对响应速度的提升是肉眼可见的。健康数据查询也尽量按elder_id record_time加复合索引索引不是概念是真能救命的东西。6. 常见问题与排错速查表6.1 启动和初始化阶段的高频问题现象原因排查与解决运行后提示No module named flask_sqlalchemy依赖没装全检查 requirements.txt 并pip install -r requirements.txt数据库表创建成功但页面报关系不存在表结构修改后没重新建表开发阶段直接db.drop_all()再create_all()生产用 Flask-Migrate登录后刷新又变游客SECRET_KEY 未配置或每次重启变化在配置文件中设定固定的随机字符串不要用默认值中文数据写入数据库变乱码数据库连接没有指定 utf8mb4SQLAlchemy 连接串追加?charsetutf8mb4Nginx 默认也该加 charset utf-8这张表里最值得说的是第一条很多人一上来就运行缺这个包提示缺那个包其实项目 README 里如果没有写清楚环境准备步骤环境问题能浪费一小时。我的源码配套会带一个README.md从 Python 版本到依赖安装到初始化脚本能少踩一半坑。6.2 业务逻辑运行中的隐藏隐患时区问题服务器默认 UTC如果数据库存datetime.now()用的是本地时间页面显示又有可能出现 8 小时偏差。项目里我直接统一用本地时间记录并在初始化配置里写清楚避免在生产环境再改。工单取消后护工工作量统计统计“护工已完成订单数”时一定注意只统计 status 为 done 的订单不要用总数减去 cancelled 这种间接算法语义不清的逻辑越到后面越难改。多个家属绑定同一个老人如果一个老人有三个家属账号家属端页面都要能正确显示。这里的坑在查询时如果用User.elder_id关联那要给该字段建索引否则每次页面加载都要做全表扫描。告警重复触发血压异常是一个记录值就可能触发但家属会连续收到好几条短信。我的解决方式是同一老人同类告警在 10 分钟内只触发一次用 Redis 计数器或者数据库时间查询都能实现千万别忘了做这是家属投诉率最高的点。6.3 前端联调与模板渲染的几个教训页面渲染不出来先看页面返回的 HTTP 状态码。如果是 500别盯着浏览器直接看后端日志。被验证过最多次的坑是模板文件命名或路径写错render_template找不到文件会报 TemplateNotFound而这个错误在开发服务器下往往被日志吞掉一半。所以我会在 view 函数入口加上一段临时的print(request.path)确认蓝图前缀拼接正确再继续调。前端和后端参数名不一致这类问题也很多我的建议是表单 name、变量名、模板里的循环变量从设计表结构开始就统一叫法elder_id就是elder_id不要一会儿elderId一会儿old_id。统一命名规范带来的整洁感到后期维护时会翻倍回报你。模板里如果出现未定义的变量Jinja2 默认是静默处理而不是报错页面看上去正常其实数据是空的。排查这种问题可以用{{ debug or }}加一个调试输出但更推荐在本地开启TEMPLATES_AUTO_RELOAD True并且把EXPLAIN_TEMPLATE_LOADING True打开看加载路径。别问我是怎么知道的我第一次遇到图表空白就是靠这个查出来的当时调了整整一个下午。最后分享一点个人经验这个项目做完我最大的感受不是技术上的而是需求边界上的。做一个养老系统容易做一个真正能用起来的养老系统难难在你要理解管理员、护工、老人、家属四个角色各自的诉求。管理员要省事护工要操作简单老人要稳定家属要放心四个诉求往系统里一放很多设计决策自动就清晰了。比如派单为什么我坚持“人工指派优先于自动派单”因为家属对护工的信任度不是算法算出来的而是长期服务养出来的。如果你手上正好拿到这套源码我建议不要急着跑通全部功能先花半小时把数据库关系画出来再顺着服务工单状态流转走一遍代码这种“读源码”的方法比任何一个模块都更有价值。等技术细节消化得差不多再往里面加你的想法——比如对接智能手环数据、增加语音提醒、给管理员出周报方向都在路也通剩下的就是动手了。
返回列表