ARTICLE DETAIL

资讯详情

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

Python与uniapp微信小程序心理自测咨询系统开发实战

Python与uniapp微信小程序心理自测咨询系统开发实战 这一轮接手的项目明显和上一个大不一样。上一轮交付的是一个2048游戏的微信小程序源码工程那种纯前端单机小游戏包体小、没有后端、没有用户体系难点基本集中在页面交互和动画手感上。但这次是Python_uniapp——微信小程序的心理自测咨询它要解决的是另外一整套问题心理量表怎么出题、怎么算分、结果怎么解读用户身份怎么建立手机号怎么合规获取咨询预约怎么从表单走到服务闭环以及上线前那些绕不开的体积、导航栏和域名配置的坑。说白了这不是做个页面而是做一个轻量级的心理健康服务SaaS工作量完全不在一个量级。这篇文章我就以实际交付这套系统的过程为主线把从技术选型、量表计分逻辑、登录鉴权、咨询流程到打包上线遇到的真实问题一个一个摊开来讲。适合正在做微信小程序开发、想了解uniapp落地细节或者打算做心理健康、在线咨询类小程序的朋友参考。1. 从游戏小程序到心理服务小程序本质差异与架构选型1.1 两类小程序的基因完全不同先聊一个很直接的问题2048这种游戏小程序和当前要做的心理自测咨询小程序差别到底在哪2048那种项目技术上完全是前端单机。页面渲染、用户交互、本地存储全在微信小程序里完成不需要服务器不需要数据库不需要用户登录甚至不需要考虑用户隐私——所有人玩同一个游戏玩完关掉就行。它不能像网页那样随便在浏览器里跑但本质上仍然是纯前端的活儿。心理自测咨询小程序就完全不一样了。第一它必须有后端。因为量表题库、测评记录、咨询订单都要存到服务器上数据要跨设备、跨时间保留。第二它必须有自己的用户体系。用户测过一次之后过两周想再测一次看变化趋势如果没有账号体系这个复测对比功能根本没法做。第三它涉及敏感个人信息。心理测评结果属于健康类敏感数据存储和处理都要格外小心。第四它要接入微信的开放能力——登录、手机号获取、订阅消息通知这些都是游戏小程序基本用不上的。所以我在动手之前先把需求拆成了四块量表测评、用户中心、咨询预约、个人中心。每一块再往下拆就变成了技术方案选型的问题。1.2 为什么是Python做后端、uniapp做前端后端选Python不是因为Python写起来快这么简单而是这个项目的后续扩展空间决定的。心理健康类产品的后端除了常规的增删改查接口后面大概率会加一些分析类功能比如根据量表历史数据画趋势曲线、给用户做简单的风险分层、甚至接入自然语言处理做情绪分析。Python在这块的生态是碾压级的pandas做数据处理、scikit-learn做分类模型、transformers做文本情绪识别基本是现成轮子直接装。如果选Node.js或者Java这些功能也能做但生态成熟度和上手成本确实不如Python。框架层面我用的是FastAPI不是Flask。原因有两个一是FastAPI原生支持异步高并发场景下扛得住二是它自动生成OpenAPI接口文档前端联调的时候直接打开/docs就能看到所有接口的参数和返回结构省掉一大半沟通成本。对于一个人全栈开发的项目来说这个体验太重要了。前端选uniapp核心原因是一套代码多端复用。这个项目虽然现在目标是微信小程序但心理服务类产品大概率会想要H5端做分享传播或者打包成Android App。uniapp的模型是你写一套Vue代码通过不同平台的编译器产出微信小程序、H5、App各自的原生工程。api层也做了统一封装比如发请求用uni.request取登录状态用uni.login而不是直接调wx.request和wx.login——这就是它和原生微信小程序开发最直观的区别。代码里不直接依赖微信的API将来要发H5版或App版业务代码几乎不用动。1.3 整体技术栈与前端目录设计这里放一张我在实际项目里的技术选型表供参考层级技术方案说明前端框架uniapp Vue3一套代码编译到微信小程序后端框架Python FastAPIRESTful API 自动接口文档数据库MySQL 5.7用户表、量表表、测评记录表、咨询表缓存Redis可选登录态缓存、热点数据微信生态wx.login / getPhoneNumber / subscribeMessage身份登录、手机号、服务通知前端目录我按业务模块来划分不按页面类型堆在一起pages/ ├── index/ # 首页量表推荐、咨询师入口 ├── survey/ # 量表列表、答题页、结果页 ├── consult/ # 咨询预约表单、订单列表 ├── profile/ # 个人中心、历史测评记录 ├── webview/ # 用户协议、隐私政策等静态页面后端目录则按领域拆routerapp/ ├── routers/ │ ├── auth.py # 登录、手机号、token刷新 │ ├── survey.py # 量表列表、答卷提交、结果查询 │ ├── consult.py # 预约单创建、状态流转 │ └── user.py # 用户信息、历史记录这样设计的思路很简单前后端都按业务能力而不是技术类型组织代码以后加功能的时候知道往哪个文件夹塞文件就够了。2. 心理自测不只是出题算分量表、计分与结果解读的实现2.1 量表选型先上PHQ-9和SDSSCL-90放后面心理自测模块是整个小程序的核心但如果一上来就上SCL-90这种90题的深度量表用户大概率做一半就放弃了。实测数据也支持这一点长量表的完成率明显低于短量表。我最终的选型策略是分梯队第一梯队PHQ-9患者健康问卷9题和SDS抑郁自评量表20题。题目少1-2分钟能完成适合作为小程序的主流量表。第二梯队SAS焦虑自评量表20题覆盖焦虑场景。第三梯队SCL-90症状自评量表90题作为深度自测工具引导用户在PC端或空闲时段使用。SDS和SAS有个特点不直接拿原始分给用户看而是换算成标准分再按常模分级。这个换算逻辑必须后端算前端只负责展示。一是因为算法集中、好维护二是因为前端一旦被篡改或缓存错乱结果就不准了对心理产品来说结果出错是严重事故。2.2 后端计分接口反向题与风险分级怎么写才不出错以SDS为例它采用1-4级评分20个条目中第2、5、6、11、12、14、16、17、18、20题是反向计分题。什么叫反向计分就是说这道题描述的是积极状态比如我觉得一天中早晨最好如果你勾了没有或很少时间1分实际上反映的是负面情绪状态所以要反向换算——选1得4分选4得1分公式是5 - 原始分。这个地方是心理量表程序最容易写错的点。很多没接触过量表的人会把20个题目的得分直接加起来结果算出的分数完全不对。我写了一个专门的处理函数def calculate_sds(raw_answers: list[int]) - dict: if len(raw_answers) ! 20: raise ValueError(SDS量表必须有20个题目的答案) reverse_items {2, 5, 6, 11, 12, 14, 16, 17, 18, 20} raw_score 0 for idx, score in enumerate(raw_answers, start1): if idx in reverse_items: raw_score 5 - score else: raw_score score standard_score int(raw_score * 1.25) # 标准分 粗分 × 1.25后取整 severity_index raw_score / 80 # 抑郁严重度指数 粗分 / 80 return { raw_score: raw_score, standard_score: standard_score, severity_index: round(severity_index, 3) }算完分之后还要做风险分级。按国内常模SDS标准分在53-62为轻度抑郁63-72为中度72以上为重度。但这套分级只能作为筛查参考不能作为临床诊断。所以我在结果页明确写了一句本测评结果仅作为情绪状态的自我筛查参考不构成医疗诊断如有需要请及时寻求专业心理医生帮助。这个免责声明不是走形式而是这类产品必须守住的合规底线。后续如果要接入新的量表我也会要求业务方提供参考文献和分级标准绝不自己拍脑袋定阈值。2.3 前端答题流程与中途流失的兜底方案答题页的交互设计我经过一轮真实的用户测试后调整过。最初是一页展示全部20题用户抱怨看到一堆题就有压力后来改成一道题一页用户又觉得点得太累。最终方案是每页展示5题左右滑动切换底部有一个清晰的进度条。这个方案的完成率最高也符合主流量表类产品的交互习惯。中途退出是最容易丢数据的地方。用户在答题过程中收到一条微信消息切出去回完再进来如果题目答案全丢了大概率直接放弃整个测评。解决方法是每答完一题立即用uni.setStorageSync把当前答案存到本地同时存一个current_survey_id页面加载时优先读取本地缓存onLoad() { const saved uni.getStorageSync(sds_draft); if (saved saved.surveyId this.surveyId) { this.answers saved.answers; this.currentPage saved.currentPage; } }, saveDraft() { uni.setStorageSync(sds_draft, { surveyId: this.surveyId, answers: this.answers, currentPage: this.currentPage }); },提交成功后立刻清除这个草稿缓存避免下次打开时数据串了。结果页除了展示分数和分级我还在底部放了一个预约咨询师聊聊的按钮。这一步转化的价值非常大——用户刚做完自测、情绪关注度最高的时候是引导预约的最佳时机。实际操作下来从结果页进入预约表单的转化率比从首页直接点预约高了大概3倍。3. 手机号快捷登录与用户体系微信生态鉴权链路全拆解3.1 静默登录code换openid的完整链路微信小程序没有传统的用户名密码登录。它的标准做法是小程序端调用uni.login拿到一个临时code把code发给后端后端拿着code去微信的接口换取openid。这个openid是用户在你这套小程序体系内的唯一身份标识同一个用户在不同小程序里openid不同但在你这一个小程序里终身不变。链路长这样小程序端 uni.login() - 获取 code - 传给后端 /api/auth/login - 后端用 code 调微信接口( jscode2session ) - 拿到 openid session_key - 后端生成自定义 token 返回给前端后端的FastAPI实现大概是这样的import httpx from fastapi import APIRouter, HTTPException router APIRouter(prefix/api/auth, tags[auth]) APPID 你的微信小程序appid SECRET 你的微信小程序secret router.post(/login) async def login(code: str): url https://api.weixin.qq.com/sns/jscode2session params { appid: APPID, secret: SECRET, js_code: code, grant_type: authorization_code, } async with httpx.AsyncClient() as client: resp await client.get(url, paramsparams) data resp.json() if openid not in data: raise HTTPException(status_code400, detail微信登录失败) openid data[openid] user get_or_create_user(openid) token create_token(user[id]) return {token: token, user: user}这个流程是静默的用户无感知。要点在于token不能直接用openid当token必须自己生成一个有有效期的token比如JWT后端在处理请求时校验token再通过token找到用户。否则openid泄露就等于用户身份泄露了。前端uni.login的调用在每次小程序启动时做一次拿到token后存到uni.storage后续所有请求头里带Authorization: Bearer token。3.2 手机号授权组件的正确用法很多第一次做小程序的人以为手机号也能像openid一样静默获取这是错的。微信对手机号的管控非常严格从2022年起小程序只能通过官方的getPhoneNumber组件来获取用户手机号而且拿到的不是明文手机号是一个加密code需要后端再调一次微信的接口才能换到真实手机号。前端用法button open-typegetPhoneNumber getphonenumberonGetPhoneNumber 绑定手机号 /buttonasync onGetPhoneNumber(e) { if (e.detail.code) { const res await uni.request({ url: /api/auth/bind-phone, data: { code: e.detail.code }, method: POST }); if (res.data.success) { uni.showToast({ title: 绑定成功, icon: success }); } } else { // 用户点击了拒绝授权 uni.showToast({ title: 需要手机号才能预约咨询, icon: none }); } }后端拿到code后调用微信的phonenumber.getPhoneNumber接口换取手机号router.post(/bind-phone) async def bind_phone(code: str, user_id: int): url https://api.weixin.qq.com/wxa/business/getuserphonenumber # 需要一个access_token用appidsecret换取 data { code: code } resp httpx.post(url, params{access_token: access_token}, jsondata) phone_info resp.json()[phone_info] phone_number phone_info[phoneNumber] update_user_phone(user_id, phone_number) return {success: True}有几个坑要提醒一下换手机号的code是一次性的只能使用一次不能重复提交。这个接口有调用频率限制不要在小程序每次启动时都在后台调它只有用户主动点击授权时才触发。用测试号开发时getPhoneNumber拿到的code在真机上才能换到真实手机号。微信开发者工具模拟器里的手机号授权是模拟数据联调时很容易在这里被误导我在这个上面吃过亏。3.3 心理数据的隐私合规边界心理测评数据不是普通的用户数据按照现行规定它属于敏感个人信息处理不当是会有麻烦的。我在项目里做了这么几层处理微信后台的用户隐私保护指引里明确声明收集的信息类型包括手机号、测评记录并说明用途。后端数据库存储时手机号做脱敏测评原始答案只保留必要的汇总指标原始逐题答案定期清理。小程序本地不缓存量表原始答案答题草稿只保留24小时。用户协议和隐私政策里写明测评结果仅用于为用户提供情绪状态参考不向任何第三方共享。这一块不能觉得我是小项目不需要。心理服务涉及用户情绪状态一旦发生数据泄露对用户的伤害比其他类型的项目严重得多。合规上线才能走远。4. 咨询预约与接单闭环从一个表单到一段服务关系4.1 预约表单设计宁可少收集也要收集对咨询预约是这个项目的变现路径也是服务闭环的起点。表单字段我经过几次删改最终只留了四个字段类型为什么保留咨询方向下拉选择让用户明确诉求如情绪压力、亲密关系、学业职场期望时间日期时间段减少后续沟通排期成本问题概述textarea200字内让咨询师提前了解情况紧急程度单选区分普通预约与紧急情况其他字段像性别年龄职业等第一版全部不做。原因很直接表单每多一个字段就多一个流失率。用户来预约是带着情绪来的填太多无关信息会烦躁。等用户完成第一次咨询、建立了信任关系后再在个人中心引导完善资料效果比在表单里硬要信息好得多。紧急程度这个字段我要特别说一下。我在表单下方加了一行醒目的提示如果你正处于极度痛苦或有自伤想法请立即拨打当地心理援助热线不要等待预约。这不是套话而是真实必要的设计——心理咨询类产品必须为极端情况留出紧急出口。产品可以慢但用户生命不能等。4.2 订阅消息通知与咨询师接单流程预约单提交之后怎么通知咨询师最实用的方案就是微信订阅消息。微信的订阅消息规则要搞清楚小程序不能随便给用户推消息必须由用户主动订阅过某类模板才能发一次。所以我在提交预约表单的逻辑里加了一个授权弹窗让用户勾选预约状态变动时通过微信通知我这样用户就能收到已接单已完成等状态变更的提醒。咨询师端我做了个简单的待办列表预约单的状态机是这样流转的待接单 - 已接单 - 服务中 - 已完成 - 已取消用户或咨询师均可触发状态机的实现不复杂但要注意操作权限用户只能取消待接单和已接单状态的单子咨询师只能接单、开始服务和完成服务。后端在每次状态变更接口里都做了当前用户身份校验防止越权操作。这个对咨询类产品来说不只是数据问题还是服务安全的问题——没做身份校验的话任何人拿到一个预约单ID就能篡改状态。4.3 在线沟通第一版建议先别做IM很多人做咨询类小程序第一反应是我要做在线聊天功能。我在这个项目里做了一版WebSocket在线IM的原型但最后被我自己砍掉了。原因很简单第一IM的内容安全是难点。心理咨询聊天的内容非常敏感一旦涉及极端自伤等文本系统必须做到及时识别和预警这不是第一版能做完的事情。第二消息审核成本高。微信对小程序内的UGC内容有监管要求聊天记录需要过滤违规内容需要接入内容安全接口。第三商业上也不是必须——早期咨询师完全可以通过微信通话或线下完成服务平台要做的是撮合而不一定是承载。最终第一版我用的是表单预约线下服务引导。用户提交预约单后咨询师接单双方可以通过系统内置的留言板简单沟通也就是互相留一条短消息不做实时对话然后约定时间进行电话或视频通话。等用户量和咨询师数量都上来了再迭代IM也不迟。这个取舍值得摆到台面上说。做产品不是把功能做得多而是把当下最核心的动作做透。5. 上线前最容易卡住的三个技术细节5.1 主包超过2MB分包的切割思路微信小程序主包体积限制是2MB超过就报错source size 2612kb exceed max limit 2mb。uniapp打包后很容易超限因为框架本身会带上vue运行时和公共组件代码。解决思路是路由分包。微信小程序允许把一部分页面放到分包里用户访问到分包页面时才下载对应代码主包只保留启动必须的内容。我在pages.json里这样配置{ pages: [ pages/index/index, pages/survey/survey, pages/profile/profile ], subPackages: [ { root: pages/consult, pages: [ consult-form, consult-list, consult-detail ] }, { root: pages/result, pages: [ result-detail ] } ] }切分的依据是用户从首页出发最短路径上需要的页面放主包。首页、量表列表、个人中心是必须的咨询预约、测评结果详情页是次要路径全部放到分包。经过这样切分主包体积从2.6MB降到了1.7MB左右。还有一个经常被忽略的优化点图片资源。design稿里的大图不要直接扔进static目录先压缩一遍能转成WebP就转成WebP。图标类图片尽量用字体图标库一张100KB的png图标换成iconfont可能只占几KB。5.2 顶部导航栏的高度适配问题微信小程序的顶部导航栏高度在不同机型上不是定值。比如iPhone X以上机型有顶部刘海区状态栏高度是44px左右而普通机型是20px。胶囊按钮的位置也不同。如果直接用一个写死的导航栏高度很容易在刘海屏上出现标题被挤掉、按钮错位的问题。我项目里用了一个自定义导航栏适配代码是通用的const systemInfo uni.getSystemInfoSync(); const menuButton uni.getMenuButtonBoundingClientRect(); const statusBarHeight systemInfo.statusBarHeight; // 顶部状态栏高度 const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height; // 自定义导航栏高度这个menuButton.top是胶囊按钮距离屏幕顶部的距离menuButton.height是胶囊高度。用这两个值算出来的导航栏总高度在各种机型上都能贴合胶囊按钮的位置。另外要注意自定义导航栏后页面顶部会被导航栏遮住需要在页面根元素上加padding-top值就是算出来的statusBarHeight navBarHeight。这个细节不处理的话页面内容会顶到状态栏下面观感非常差。5.3 manifest配置、合法域名与一个常见的签名误区manifest.json在uniapp项目里是核心配置文件微信小程序相关的三件事都在这里配appid微信公众平台里的小程序AppID不填的话工具里会报错。permission如果需要位置权限、相册权限等在manifest里声明。mp-weixin节点下的setting比如urlCheck: false可以在开发调试时跳过合法域名校验但上线前必须关闭这个选项并配置真实的HTTPS request合法域名。说到合法域名这里有一个新手高频踩坑点在微信开发者工具里本地调试时默认勾选不校验合法域名此时可以请求任意http地址。但上线后如果后端域名没有配置到微信公众平台的request合法域名列表里所有请求都会失败报错信息是url not in domain list。自查方法很简单在微信公众平台的开发管理-开发设置-服务器域名里把线上后端域名加进去并且必须是备案过的HTTPS域名。最后说一个热搜词的误解小程序签名uniapp。小程序本身是不需要签名的需要签名的是uniapp打包出来的Android App也就是APK。上架安卓应用市场时需要用jks或keystore文件对APK进行签名市场才会认。如果你只是发布微信小程序完全不涉及Android签名不用在这一步上纠结。如果你后续要把uniapp工程打成App上架再去看Android证书和签名配置那才是一套独立的流程。结尾的几句真话把这套心理自测咨询小程序从0到1做下来我最有感触的不是代码而是业务认知对技术方案的约束。游戏小程序的核心是好玩用户可以随时关掉重开心理服务小程序的核心是信任用户填量表时交出来的是真实的情绪状态。这决定了从技术选型到交互设计再到合规边界的每一个取舍——为什么要用Python、为什么要做风险分级、为什么要设计紧急出口、为什么第一版不上IM背后都是信任两个字在起作用。如果你打算复刻一个类似的项目我最后想分享的小建议是上线前找三五个真实用户从首页开始把做测评-看结果-预约咨询的完整链路走一遍看他们在哪一步卡住、在哪一步犹豫、在哪一步退出。这一遍真实场景测试比你自己多写两千行代码都值钱得多。
返回列表