news 2026/7/6 1:35:52

守望康:我用 FastAPI + Vue 3 做了一个社区养老互助平台,完整技术复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
守望康:我用 FastAPI + Vue 3 做了一个社区养老互助平台,完整技术复盘

守望康:我用 FastAPI + Vue 3 做了一个社区养老互助平台,完整技术复盘

我是风吹夏回。这是我的第二个全栈项目"守望康"——一个面向社区老人的互助+健康管理平台。本文复盘从架构设计到核心实现,附带 6 个真实踩坑记录。


为什么做这个项目?

国家统计局数据显示,中国 60 岁以上人口已突破2.9 亿,占总人口的 21.1%,预计 2035 年将突破 4 亿。在"9073"养老格局下,90% 的老人选择居家养老,但社区养老服务数字化程度严重不足——很多独居老人遇到困难只能打电话,甚至不知道找谁帮忙。

与此同时,低龄健康老人和社区志愿者的服务潜力远未被激活:他们有时间和意愿提供帮助,却缺少一个高效的信息匹配平台。

基于这个背景,我开发了守望康,Slogan:邻里守望,健康常在

技术栈:Vue 3 + TypeScript + Pinia(前端) / FastAPI + SQLAlchemy 2.0 + Pydantic v2(后端) / MySQL + Redis + RabbitMQ + Elasticsearch / DeepSeek AI / 阿里云 NLS 语音识别 / Docker Compose 部署。


一、用户认证:登录注册 + JWT 双 Token + 三端分流

1.1 两种登录方式

登录页支持密码登录短信验证码登录切换,内置三个测试账号(老人/志愿者/管理员各一个),一键填充方便演示:

consttestAccounts=[{label:'老人端',phone:'13900000001',password:'elder123'},{label:'志愿者端',phone:'13700000001',password:'vol123'},{label:'管理员端',phone:'13800000000',password:'admin123'}]

1.2 注册流程

注册时选择角色(老人/志愿者)、填写手机号、获取短信验证码、设置密码:

// 注册表单校验constrole=ref<'elder'|'volunteer'>('elder')// 手机号正则校验 → 6 位短信码 → 密码 ≥ 6 位 → 确认密码一致 → 真实姓名

1.3 后端 JWT 鉴权 + RBAC

后端采用JWT 双 Token机制:access_token(24h,日常鉴权)+refresh_token(30d,无感刷新)。Token payload 包含type字段区分类型,鉴权时强制校验type == "access",防止 refresh token 被用于接口请求。

# core/security.pydefcreate_access_token(subject,role,expires_minutes=1440):payload={"sub":str(subject),"role":role,"exp":datetime.now(timezone.utc)+timedelta(minutes=expires_minutes),"type":"access"}returnjwt.encode(payload,settings.secret_key,algorithm="HS256")

权限控制使用 FastAPIDepends依赖链:HTTPBearerget_current_user(解码 JWT)→get_current_user_obj(查 DB)→require_roles(角色校验),每个环节只做一件事:

defrequire_roles(*allowed_roles:str):asyncdef_checker(user:User=Depends(get_current_user_obj))->User:ifuser.rolenotinallowed_roles:raiseHTTPException(status_code=403)returnuserreturn_checker# 使用示例:只有老人能创建求助@router.post("/orders")asyncdefcreate_order(body:OrderCreate,user:User=Depends(require_roles("elder"))):...

密码用bcrypt哈希,直接调原生库而非passlib(避免兼容性问题),密码截断到 72 字节(bcrypt 上限)。登录失败累计 5 次自动锁定账号。

1.4 前端路由守卫 + 三端分流

登录成功后,路由守卫根据role字段自动跳转到对应首页:

// router/index.tsconstROLE_HOME={elder:'/elder/home',volunteer:'/volunteer/home',admin:'/admin/dashboard',}router.beforeEach(async(to,_from,next)=>{consttoken=localStorage.getItem('sk-access-token')// 有 token → fetchUser 验证 → 有效就跳角色首页// 无 token → 公开页面放行,其他重定向到 /login})

Axios 请求拦截器自动注入 Bearer Token,401 时清除 token 并跳转登录页。踩坑点:401 拦截器用动态import('@/router')而非静态import,避免request.ts → stores/auth.ts → router/index.ts的循环依赖。


二、老人端:适老化不是放大字体那么简单

老人端是整个项目的核心。做养老产品,UI 是第一个要过的坎——60 岁以上的用户,老花眼、手指不灵活、对复杂操作有畏难心理。我从四个维度做了系统性的适老化设计。

2.1 三套 CSS 主题变量体系

不是简单改几行样式,而是为三个角色各建了一套完整的 CSS 变量体系,通过html的 className 切换:

// stores/theme.tsconstrole=ref<'elder'|'volunteer'|'admin'>('elder')constfontSize=ref<'base'|'xl'|'xxl'>('base')// 老人端专属functionapply(){document.documentElement.className=`theme-${role.value}font-${fontSize.value}`}

三端对比:

维度老人端志愿者端管理后台
正文字号20px(最大 28px)16px14px
最小字号16px12px12px
按钮最小高度56px40px32px
背景色#FFF8EB 暖白护眼#F8FAFC#FFFFFF
容器最大宽720px1200px1200px

配色刻意避开纯白背景(刺眼),老人端用#FFF8EB暖白,对比度 ≥ 4.5:1 符合 WCAG 标准。对 Element Plus 做了全局覆写:

.theme-elder .el-button{min-height:56px;font-size:var(--text-body);padding:12px 24px;}.theme-elder .el-input__wrapper{min-height:56px;font-size:var(--text-body);}

2.2 首页设计:五层信息分层

首页从上到下分五层,核心操作一眼可见:

<!-- 1. Hero 区:日期 + 个性化问候 --> <TodayHero :name="userName" /> <!-- 2. SOS 按钮:120px 红色脉冲大按钮,3 圈金色光环动画 --> <SOSButton @trigger="onSOS" /> <!-- 3. 6 格服务入口:每种服务独立配色,不靠文字也能区分 --> <ServiceCard icon={HandHeart} title="发起求助" color="primary" /> <ServiceCard icon={Heart} title="健康档案" color="emerald" /> <ServiceCard icon={ShoppingCart} title="健康商城" color="warm" badge="新人有礼" /> <ServiceCard icon={User} title="我的" color="lavender" /> <ServiceCard icon={ShieldAlert} title="反诈查验" color="rose" /> <ServiceCard icon={Sparkles} title="AI健康助手" color="indigo" /> <!-- 4. 今日用药提醒 --> <!-- 5. 最近服务 -->

6 个 ServiceCard 各有独立配色(深海蓝/翡翠绿/暖金黄/紫罗兰/玫瑰红/靛蓝),老人即使不认字也能靠颜色找到功能。

2.3 SOS 紧急呼叫

点击 SOS 进入三个阶段:

确认 → 全屏红色倒计时(5s,Web Audio 警笛声)→ 完成/失败

前端:SOSButton 组件 120px 高度,3 圈金色脉冲光环每 2.4 秒循环,72px 金色电话图标每 3 秒抖动:

<button class="sos-button"> <span class="sos-pulse-ring" v-for="i in 3" :key="i" :style="{ animationDelay: `${(i - 1) * 0.8}s` }" /> <span class="sos-icon"><Phone :size="40" /></span> 一键 SOS 紧急求助 </button>

警报音用 Web Audio API 实时合成——两个锯齿波振荡器 5Hz 失谐产生"警笛"质感,频率在 600-1200Hz 之间正弦扫描:

// composables/useAlertSound.tsconstosc1=ctx.createOscillator()// 600Hzconstosc2=ctx.createOscillator()// 605Hz,5Hz 失谐 = 警笛质感setInterval(()=>{constfreq=600+Math.sin(Date.now()/200)*300osc1.frequency.value=freq},60)

后端:收到 SOS 后通过 RabbitMQ 广播 → 消费者查询所有认证志愿者 + 管理员 → 批量创建通知 + WebSocket 推送 + 家属短信,三重保障。

2.4 发起求助 + 语音输入

老人可发起四种求助:陪诊、代购、探访、家政。流程三步:选类型 → 填描述 → 确认。描述支持语音输入,不需要打字。

语音链路:浏览器 AudioWorklet 录音(16kHz) → 手写 WAV 编码(44 字节 RIFF 头) → 上传后端 → 阿里云 NLS 识别 → 返回文字

// composables/useVoiceRecorder.tsfunctionencodeWAV(samples:Float32Array,sampleRate:number):ArrayBuffer{constbuffer=newArrayBuffer(44+samples.length*2)constview=newDataView(buffer)// RIFF 头:'RIFF' + 文件大小 + 'WAVE'// fmt chunk:PCM, 1 channel, sampleRate, 16-bit// data chunk:Float32 → Int16 转换writeString(view,0,'RIFF')view.setUint32(4,36+samples.length*2,true)writeString(view,8,'WAVE')// ...}

2.5 健康管理

三个子模块:

用药提醒:老人添加药品名、剂量、时间槽(如["08:00", "12:00", "18:00"]),后端每分钟定时生成打卡任务,超时 30 分钟自动标记"未完成"并短信通知紧急联系人。打卡使用tuple_复合键批量查询,避免 N+1。

健康指标:血压、血糖、血氧、心率、体重、体温六项录入,异常值自动标红。

健康档案:慢病标签、过敏史、用药史、紧急联系人。

2.6 健康商城

老人端内置了一个完整的健康商品商城,商品按慢病标签智能推荐。

商城首页:五大分类(慢病食品/康复辅具/监测设备/营养保健/适老家居),分类切换 + 智能推荐双区展示。商品卡片标注适配人群(如"适配高血压"“糖尿病推荐”),让老人一眼看懂适不适合自己。

<!-- Mall.vue — 分类 + 商品网格 --> <div class="cat-grid"> <button v-for="c in categories" @click="selectCategory(c.code)"> <Apple :size="28" /> 慢病食品 </button> <!-- 康复辅具 / 监测设备 / 营养保健 / 适老家居 --> </div> <div class="goods-grid"> <BaseCard v-for="g in categoryProducts" @click="goProduct(g.id)"> <img :src="g.cover" /> <span>{{ g.name }}</span> <span>¥{{ g.price }}</span> <span class="tag" :class="tagColor(g)">{{ tagText(g) }}</span> </BaseCard> </div>

商品搜索使用Elasticsearch而非 MySQL LIKE,支持 IK 中文分词。ES 只存搜索字段(名称、描述、分类、标签),完整数据仍在 MySQL,搜索结果用 ES 命中 ID 回 MySQLIN查询,避免双写一致性问题。

# es_client.py — 商品搜索body={"query":{"multi_match":{"query":keyword,"fields":["name^3","description"]# 名称权重 3 倍}}}

商品详情:展示价格、库存、慢病适配标签,支持数量选择 + 加入购物车 + 立即购买。

购物车:全选/单选切换、数量加减、实时计算总价、一键结算。

支付:对接支付宝沙箱环境。个人开发者无营业执照,沙箱可完整模拟支付流程,上线前替换密钥和网关即可切换正式环境。踩坑:沙箱要求 RSA2 密钥为 PKCS#1 格式(openssl genrsa),不能用 PKCS#8。

# 支付宝电脑网站支付alipay=AliPay(appid=settings.alipay_app_id,app_private_key_string=settings.alipay_private_key,alipay_public_key_string=settings.alipay_public_key,sign_type="RSA2",debug=settings.alipay_sandbox# 沙箱模式)

2.7 AI 健康助手 + 反诈查验

两个 AI 功能都基于 DeepSeek API。

健康助手:携带老人健康档案上下文(脱敏后:年龄、慢病、过敏史、用药)进行问答。System Prompt 严格限制——绝不给出医疗诊断、绝不建议停药换药,回复末尾强制加免责声明。

反诈查验:老人收到可疑短信,口述内容 → 语音转文字 → AI 分析风险等级(高/中/低)+ 结论 + 提醒。全程零打字,结果自动 TTS 语音播报。

# 反诈 System Prompt 核心约束""" 你是反诈分析专家。分析用户输入,判断是否为诈骗。 输出 JSON:{ risk_level: "高风险"|"中风险"|"低风险", conclusion, reminder } 常见特征:冒充公检法、要求转账、中奖通知、陌生链接 """


三、志愿者端:抢单 + 信用分游戏化激励

志愿者端的设计核心是让接单有成就感——我设计了一套类似游戏段位的信用分等级体系。

3.1 信用分勋章卡

首页顶部是一张金色渐变勋章卡,视觉上就给人"这是我的荣誉"的感觉:

<!-- 金色渐变背景 + 装饰光晕 + 大号分数 --> <div class="medal-card" style="background: linear-gradient(135deg, #FBBF24, #D97706, #B45309)"> <Crown :size="18" /> 我的成就 <span class="score-num">245</span>分 <!-- 等级徽章 --> <TierBadge tier="silver" size="xl" /> <!-- 升级进度条 --> <div class="progress-bar"> <div class="fill" :style="{ width: '48%' }" /> 距 💎 黄金 还差 55 分 </div> <!-- 底部三栏统计 --> 32h 服务时长 | 94% 好评率 | 本月 +35 </div>

3.2 四级信用分体系

# services/credit_service.pyTIER_NAMES={"bronze":"青铜","silver":"白银","gold":"黄金","diamond":"钻石"}defget_tier_by_score(score:int)->str:ifscore<=99:return"bronze"elifscore<=299:return"silver"elifscore<=599:return"gold"return"diamond"

积分来源:完成订单(+10)、五星好评(+3)、响应 SOS(+20)、连续接单(+5)、完成培训(+8)。扣分项:抢单后取消(-10)、差评(-5)。

3.3 抢单流程

待接单列表 → 点击"立即抢单" → confirm('确认接单?') → 确认 → grabOrder(id) → 刷新列表 → 跳转任务详情

抢单是并发冲突最激烈的场景——多个志愿者可能同时抢同一订单。我用双层锁方案:Redis 分布式锁(优先)+ MySQLSELECT FOR UPDATE行锁(回退)。

# services/order_service.pyasyncdefgrab_order(db,order_id,volunteer_id):try:# 第一层:Redis 锁(SET NX EX + Lua 原子释放)asyncwithredis_client.lock(f'order:grab:{order_id}',ttl=10):returnawaitself._do_grab(db,order_id,volunteer_id)exceptException:# 第二层:Redis 挂了回退到 DB 行锁returnawaitself._do_grab_db_lock(db,order_id,volunteer_id)

3.4 Tab 切换 + 实时同步

三个 Tab(待接单/进行中/已完成),每个带计数徽章。页面通过 WebSocket 订阅ordersoscredit三个 key,订单状态变更自动刷新:

onMounted(async()=>{awaitloadAllData()register('order',loadAllData)// 订单变化 → 刷新register('sos',loadAllData)// SOS 变化 → 刷新register('credit',loadAllData)// 信用变化 → 刷新})


四、管理后台:数据看板 + 工单审核 + 全模块管理

管理员端面向社区工作人员,核心是数据驱动决策全流程管理

4.1 数据看板

页面顶部 6 个 StatCard 指标卡片(老人数/志愿者数/今日求助/待处理预警/商城GMV/平均信用),待处理预警卡带红色呼吸光晕动画。数据通过 4 个 API 并行加载:

const[statsRes,healthRes,trendRes,eventsRes]=awaitPromise.all([getDashboardStats(),// 核心指标getHealthDistribution(),// 健康画像getServiceTrend(30),// 30 天趋势getRecentEvents(20),// 实时事件])

纯 CSS 柱状图(30 天服务趋势):30 列网格,每列两个柱子(求助数/完成数),高度按比例计算,无第三方图表库依赖。

SVG 环形图(慢病分布):用stroke-dasharraystroke-dashoffset实现环形图,中心显示老人总数,外围按慢病类型分段着色。

实时事件流:SOS(红)/健康(黄)/订单(绿)/审核(灰)/信用(蓝) 五种类型,带已读/未读状态,通过 WebSocket 实时推送,管理员不需要手动刷新。

4.2 工单审核

老人提交的普通求助进入PENDING_REVIEW状态,管理员在工单列表页逐条审核:

<!-- 状态筛选栏:待审核 / 待接单 / 已接单 / 已完成 / 已取消 --> <div class="status-bar"> <button v-for="s in statusList" :class="{ active: activeStatus === s.value }"> {{ s.label }} </button> </div> <!-- 审核弹窗:展示工单号、类型、老人、描述,通过/拒绝 --> <BaseDialog v-model="reviewDialog" title="工单审核"> <BaseButton @click="review(true)">✅ 审核通过</BaseButton> <BaseButton @click="review(false)" variant="danger">❌ 拒绝</BaseButton> </BaseDialog>

审核通过 →PENDING(待抢单),通知志愿者可接单;审核拒绝 →CANCELLED

4.3 用户管理

老人管理志愿者管理各一个列表页,支持手机号/姓名搜索。列表展示关键字段:手机号、姓名、社区、状态(正常/禁用/锁定)、注册时间。管理员可启用/禁用账号。

4.4 商品管理

管理员可对商城商品做完整 CRUD:

// 商品表单字段constformData=ref({sku:'',name:'',category:'',brand:'',cover:'',description:'',tags:[],// 通用标签for_diseases:[],// 慢病适配(高血压/糖尿病/冠心病等)price:0,stock:0,status:'on_sale',is_hot:false,is_new:false,is_recommend:false})

商品分类支持 5 类(慢病食品/康复辅具/监测设备/营养保健/适老家居),慢病标签可选 6 种(高血压/糖尿病/冠心病/高血脂/关节炎/骨质疏松),标签决定商城首页的推荐逻辑。商品状态支持上架/下架切换。

4.5 信用管理 + 系统设置

信用管理:查看每个志愿者的当前信用分、等级、流水记录,管理员可手动调整信用分(上限 500 分/次),调整原因必填。

系统设置:字典表管理(如服务类型、慢病标签等枚举值可在线配置,无需改代码),为后续多社区 SaaS 化预留扩展点。


五、项目踩坑记录(6 个真实问题)

问题一:Spug 短信发不出去

现象:注册时短信验证码始终发送失败。

根因:Spug 推送服务要求调用方 IP 必须在后台白名单中,开发机出口 IP 未被信任。

解决:登录 Spug 控制台 → 添加服务器公网 IP 到白名单 → 即时生效。

教训:用第三方 SaaS 服务先读安全策略文档,IP 白名单是最常见的坑。


问题二:三端订单不互通

现象:老人发了 SOS,志愿者端看不到;志愿者抢了单,老人端不知道。

根因:初期各端靠轮询数据库获取状态,延迟 5-10 秒,且跨角色通知缺失。

解决:引入RabbitMQ 消息队列 + WebSocket 实时推送

老人发起 SOS → RabbitMQ 发布到 sos.dispatch 队列 → 消费者查询所有志愿者 + 管理员 → 批量创建站内通知(落 DB) → WebSocket 实时推送到在线用户 → 短信通知家属

改造后通知延迟从 5-10 秒降到 1 秒以内。MQ 连接使用aio_pika.connect_robust自带断线重连,所有订阅记录在案,连接重建后自动重新注册。


问题三:虚拟机部署导致页面加载 3-5 秒

现象:MySQL、Redis、RabbitMQ、ES 都跑在虚拟机上,每次请求多次跨网络通信,延迟叠加后首页加载 3-5 秒。

解决:开发阶段把所有中间件迁到本地127.0.0.1

指标虚拟机本地提升
首页加载3-5s< 500ms6-10x
商品搜索2-3s< 300ms7-10x
SOS 广播2-5s< 500ms4-10x

问题四:抢单并发冲突

现象:两个志愿者同时抢同一订单,后者覆盖前者,一单被两人抢到。

解决Redis 分布式锁(优先)+ MySQL 行锁(回退)双层保障。

asyncdefgrab_order(db,order_id,volunteer_id):try:asyncwithredis_client.lock(f'order:grab:{order_id}',ttl=10):returnawaitself._do_grab(db,order_id,volunteer_id)exceptException:returnawaitself._do_grab_db_lock(db,order_id,volunteer_id)# SELECT FOR UPDATE

关键设计:Lua 脚本释放锁(只有持有者才能删锁),10 秒 TTL 防死锁,自旋等待最多 5 秒。


问题五:无支付资质 → 支付宝沙箱

现象:个人开发者无营业执照,无法申请正式支付宝商户。

解决:支付宝沙箱环境,个人账号即可开通,完整模拟支付流程。上线前替换密钥和网关地址即可切换正式环境。

额外踩坑:沙箱要求 RSA2 密钥必须是 PKCS#1 格式(openssl genrsa),不能用 PKCS#8。


问题六:浏览器语音识别不准

现象:初期用浏览器 Web Speech API,中文准确率 70-85%,老年人发音稍含糊就识别错误,且 Firefox/Safari 支持差。

解决:改为浏览器只负责录音,上传到后端由阿里云 NLS 识别:

浏览器 AudioWorklet 录音(16kHz) → 手写 WAV 编码(44 字节 RIFF 头) → POST /speech/recognize(FormData 上传) → 三级格式检测(Content-Type → 扩展名 → Magic Bytes) → 非 WAV 格式 ffmpeg 转码 → 阿里云 NLS 一句话识别(HMAC-SHA1 签名) → 返回文本(准确率 95%+,支持标点预测和数字转写)

额外踩坑:阿里云 NLS 与 httpx 有 TLS 兼容问题,改用subprocess调 curl 绕过。


六、项目复盘

做得好的

  • 适老化是系统工程:字号、对比度、触控尺寸、语音输入,是完整的设计语言而非简单放大
  • 抢单双层锁:Redis 优先 + MySQL 回退,兼顾性能和可靠性
  • MQ 订阅自动恢复:连接断开重连后自动重注册,运维省心
  • 三端主题 CSS 变量体系:通过 className 切换,零运行时开销
  • AI Prompt 打磨:健康助手严格限制不越界,反诈分析结构化输出

待改进

  • 缺单元测试(核心业务逻辑需要覆盖)
  • Token 过期直接跳登录,应该静默刷新
  • 没有 LBS 附近匹配(志愿者目前全局匹配)
  • ES 和 MySQL 数据一致性靠应用层保证,理想方案是 Canal 监听 binlog

项目代码完全开源,欢迎 Star 和交流。

本文由 风吹夏回 原创,发布于 CSDN,转载请注明出处。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 1:31:46

《论三生原理》虚实连续统数学细节深度解析?

AI辅助创作&#xff1a;结合给出的核心约束、集合论矛盾、实证进展三部分信息&#xff0c;从量子类比、集合论根基、有限域实证、理论局限四个维度完整拆解虚实连续统体系&#xff1a;一、虚实叠加态&#xff1a;复数归一化动力学结构1. 归一化约束 α^2β^21 的底层含义这里α…

作者头像 李华
网站建设 2026/7/6 1:29:28

【多智能体】多智能体编队一致性研究matlab仿真

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e;个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知&#xff0c;求助可私信。&#x1f525; …

作者头像 李华
网站建设 2026/7/6 1:21:18

Gensim 4.3.3 Word2Vec 参数调优实战:5个关键参数对藏文词向量质量的影响

Gensim 4.3.3 Word2Vec 参数调优实战&#xff1a;5个关键参数对藏文词向量质量的影响藏文作为一门形态丰富的语言&#xff0c;其词向量训练面临着独特的挑战。本文将深入探讨Gensim 4.3.3中Word2Vec的五个核心参数对藏文词向量质量的量化影响&#xff0c;并提供可复现的实验方案…

作者头像 李华
网站建设 2026/7/6 1:15:51

国家中小学智慧教育平台电子课本下载神器:三步搞定教材PDF的终极方案

国家中小学智慧教育平台电子课本下载神器&#xff1a;三步搞定教材PDF的终极方案 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取课本内容…

作者头像 李华