三端婚恋相亲系统源码实战:统一用户身份与消息通道设计 简介这是一套面向婚恋交友行业创业者、PHP开发者及红娘运营团队的2024年最新相亲系统源码基于红娘金媒10.3版本支持PC网站、微信小程序与公众号三端接入可快速搭建覆盖多终端的婚恋平台。系统核心模块包括红娘服务、相亲活动、交友匹配与付费获取联系方式红娘可依据用户资料与偏好提供精准配对建议活动模块支持报名审核与通知推送匹配模块借助大数据与智能算法推荐潜在对象付费解锁联系方式兼顾隐私保护与平台收益。压缩包共2008个文件以895个PHP后端文件、321个JavaScript脚本、152个CSS样式及78个WXSS、77个WXML小程序页面文件为主另含GIF、PNG等图片素材与JSON配置整体约32.03MB目录结构完整。目前已有464人学习下载适合需要三端一体化婚恋解决方案的开发者参考与二次开发。1. 三端婚恋相亲系统源码到底在解决什么问题去年帮一个做本地相亲社群的朋友看系统他手里那套东西是典型的「网页能跑、小程序半残、公众号丢消息」——用户在小程序里划到心动对象点「打招呼」没反应切到公众号菜单进去消息列表是空的只有网页端勉强能用但网页端又没法做微信支付。这不是个例市面上大量所谓「三端婚恋相亲系统源码」都是这个状态一套后端硬撑三个前端接口没做端侧适配消息通道各走各的最后运营方只能让用户「去网页端操作」转化率直接腰斩。2024 年做婚恋相亲系统三端网页、小程序、公众号已经不是「要不要做」的问题而是「怎么让三端共用一套用户体系和消息通道」的问题。用户不会管你后端怎么设计他今天在公众号看到推送点进来明天在小程序里刷人后天用电脑网页填资料任何一端体验断裂他就流失。这套源码要解决的核心就三件事统一用户身份一个 uid 打通三端、统一消息通道站内信、订阅消息、公众号模板消息按端分发、统一资料与匹配逻辑三端读同一份画像数据。适合谁用适合有运营能力但缺技术团队的婚恋工作室、本地相亲社群、垂直行业交友平台不适合想直接上线就日活过万的幻想型选手。2. 三端身份打通从微信 unionid 到本地 uid 的映射设计2.1 为什么不能只用 openid 做用户主键新手最容易翻车的地方小程序拿 openid公众号拿 openid网页端用手机号注册三套用户表各存各的。结果同一个用户在三个端有三个账号匹配池被稀释运营发消息要发三遍。正确做法是微信生态内用 unionid 做跨应用唯一标识网页端用手机号 验证码最终在业务层统一映射到一个本地 uid。前提是你的小程序和公众号绑定在同一个微信开放平台账号下否则拿不到 unionid。很多源码包不提醒这一点买回来发现 unionid 全是空就是因为没绑开放平台。绑定之后小程序端wx.login换 code后端用 code appid secret 调jscode2session返回里就有 unionid公众号网页授权走snsapi_userinfo拿到的用户信息里也有 unionid。2.2 后端统一登录接口的实现下面是一个典型的统一登录接口用 Python FastAPI 写三端都调这一个入口靠platform字段区分来源from fastapi import FastAPI, HTTPException from pydantic import BaseModel import httpx app FastAPI() class LoginReq(BaseModel): platform: str # web / miniapp / official_account code: str None # 小程序或公众号授权 code phone: str None # 网页端手机号 sms_code: str None # 网页端短信验证码 app.post(/api/auth/unified_login) async def unified_login(req: LoginReq): if req.platform in (miniapp, official_account): # 微信侧用 code 换 unionid if not req.code: raise HTTPException(400, missing code) appid MINI_APPID if req.platform miniapp else OA_APPID secret MINI_SECRET if req.platform miniapp else OA_SECRET async with httpx.AsyncClient() as client: resp await client.get( https://api.weixin.qq.com/sns/jscode2session, params{appid: appid, secret: secret, js_code: req.code, grant_type: authorization_code} ) data resp.json() unionid data.get(unionid) if not unionid: raise HTTPException(401, unionid missing, check open platform binding) uid get_or_create_uid_by_unionid(unionid) else: # 网页端手机号 短信验证码 if not verify_sms(req.phone, req.sms_code): raise HTTPException(401, sms code invalid) uid get_or_create_uid_by_phone(req.phone) token issue_jwt(uid) return {uid: uid, token: token}逻辑说明platform决定走哪条认证链路微信侧只认 unionid网页侧只认手机号。get_or_create_uid_by_unionid内部先查映射表没有就插入新用户并返回自增 uid。参数上MINI_APPID、OA_APPID这些从环境变量读不要硬编码在代码里。issue_jwt签发的 token 里带 uid 和过期时间三端后续请求都带这个 token后端不再关心请求来自哪个端。2.3 数据库映射表的最小字段设计不要一上来就设计几十个字段的用户表先把映射关系跑通。核心就一张user_identity表字段类型说明uidbigint 自增业务主键三端共用unionidvarchar(64)微信跨应用标识可空phonevarchar(20)网页端手机号可空nicknamevarchar(64)昵称三端可各自覆盖avatarvarchar(255)头像 URLcreated_atdatetime注册时间unionid 和 phone 都建唯一索引但允许 null。这样微信用户先注册、后绑手机或者手机用户先注册、后绑微信都能合并到同一个 uid。合并逻辑写在get_or_create_uid_by_unionid里如果 unionid 查不到但 phone 能匹配上已有用户就把 unionid 补写到那条记录而不是新建。3. 消息通道分发站内信、订阅消息、模板消息怎么各走各的3.1 三端消息能力对比与选型这是最容易做错的部分。很多人以为「发消息」就是一个接口的事结果小程序端用户收不到公众号端用户被骚扰。先把三端的能力边界列清楚通道触发条件到达率限制站内信用户主动打开端100%打开才看到无小程序订阅消息用户授权一次中一次授权只能发一条需反复引导公众号模板消息用户关注公众号高需用户与公众号有交互2024 年规则收紧短信有手机号高成本高仅关键节点用常见做法是打招呼、新消息提醒走站内信 小程序订阅消息匹配成功、资料被查看走公众号模板消息验证码、付费成功走短信。不要所有消息都往公众号推用户会取关。3.2 消息分发服务的代码骨架下面是一个消息分发器根据用户当前活跃端和消息类型决定走哪些通道async def dispatch_message(uid: int, msg_type: str, payload: dict): # 1. 站内信永远写一份保证用户打开任意端都能看到 await save_inbox(uid, msg_type, payload) # 2. 查用户绑定的端 binds await get_user_binds(uid) # {miniapp: True, oa: True, phone: 138...} # 3. 按消息类型分发 if msg_type in (greeting, new_message): if binds.get(miniapp): await send_subscribe_msg(uid, payload) # 订阅消息 elif msg_type in (match_success, profile_viewed): if binds.get(oa): await send_template_msg(uid, payload) # 公众号模板消息 elif msg_type in (verify_code, pay_success): if binds.get(phone): await send_sms(binds[phone], payload)逻辑说明站内信是兜底先写库再分发。get_user_binds从user_identity表读该用户绑定了哪些端。send_subscribe_msg需要用户之前授权过对应模板 ID没授权就跳过不要报错中断。参数上msg_type用枚举字符串不要用数字方便排查日志。每个发送函数内部都要 try/except单个通道失败不影响其他通道。3.3 订阅消息的授权次数管理小程序订阅消息是「一次授权一条」用户点一次「允许」你只能发一条。很多源码包不处理这个发完一条后续全失败。正确做法是在user_identity表加一个subscribe_quota字段用户每次授权 1发送成功 -1发之前检查 quota 0。引导授权的时机也有讲究不要在用户刚注册就弹要在用户点击「打招呼」按钮时弹这时候他有明确预期通过率高。4. 资料与匹配逻辑三端读同一份画像数据4.1 用户画像字段的取舍婚恋系统的资料字段不是越多越好。字段太多用户填到一半就跑了字段太少匹配没依据。我一般会分三层必填层性别、年龄、所在城市、婚姻状况、学历、选填层身高、收入区间、房车情况、兴趣爱好、扩展层自我介绍、择偶要求、生活照。必填层决定能不能进匹配池选填层影响匹配权重扩展层影响用户点进主页后的转化。数据库设计上必填和选填放user_profile主表扩展层放user_profile_ext附表避免主表过宽。三端提交资料都调同一个POST /api/profile/update后端按字段白名单过滤防止小程序端传了网页端不该传的字段。4.2 匹配打分的可解释实现匹配算法不要一上来就上协同过滤冷启动阶段没数据跑出来全是噪声。先用规则打分可解释、可调参、运营能看懂def match_score(me: dict, other: dict) - float: score 0.0 # 硬性条件性别互补、年龄差在范围内 if me[gender] other[gender]: return 0.0 age_diff abs(me[age] - other[age]) if age_diff 10: return 0.0 score (10 - age_diff) * 2 # 年龄越近分越高最高 20 # 同城加分 if me[city] other[city]: score 15 # 学历匹配 if me[education] other[education]: score 10 # 择偶要求双向匹配 if meets_requirement(me, other) and meets_requirement(other, me): score 25 # 活跃度加权 score min(other[active_score], 10) return score逻辑说明先做硬过滤同性、年龄差过大直接 0 分再累加软性权重。meets_requirement检查对方的年龄、身高、收入是否落在我的择偶要求区间内双向都满足才加分避免单向匹配。active_score是用户近 7 天登录、发消息、完善资料的加权分防止推荐一堆僵尸号。参数上各项权重写在配置表里运营可以调不要硬编码。4.3 三端资料展示的差异处理同一份资料三端展示要适配。网页端可以展示完整信息小程序端受限于屏幕只展示核心字段 「展开更多」公众号端基本只做消息触达点进去跳小程序或网页。后端返回资料时带一个platform参数按端裁剪字段def get_profile(uid: int, viewer_platform: str): profile query_profile(uid) if viewer_platform miniapp: return {k: profile[k] for k in (nickname, age, city, avatar, tags)} elif viewer_platform web: return profile # 全量 else: return {nickname: profile[nickname], avatar: profile[avatar]}这样三端读的是同一份数据只是展示层做裁剪不会出现「小程序改了资料网页端没变」的问题。5. 避坑与排查三端联调最容易翻车的 5 个点5.1 坑一unionid 拿不到用户三端分裂现象小程序和公众号各自注册了账号同一个用户在两个端看到的是两套匹配池。原因小程序和公众号没有绑定在同一个微信开放平台账号下或者绑定了但jscode2session返回里没有 unionid 字段。解决先确认开放平台绑定关系再检查后端是否把 unionid 存进了user_identity表。如果历史数据已经分裂写一个合并脚本按手机号把两条记录合并unionid 补写到保留的那条上。5.2 坑二订阅消息发不出去日志显示 43101现象调用订阅消息接口返回errcode: 43101提示用户拒绝接收。原因用户没有授权对应模板或者授权次数已经用完。解决发送前查subscribe_quota为 0 就跳过并记录不要反复重试。同时在用户点击关键按钮时重新引导授权补充 quota。注意模板 ID 要和小程序后台配置的一致测试环境和正式环境容易搞混。5.3 坑三网页端登录态和小程序登录态互相覆盖现象用户在网页端登录后小程序端 token 失效被踢下线。原因后端用同一个 token 存储 key后登录的覆盖了先登录的。解决token 存储按uid platform做 key各端独立。JWT 里带 platform 字段校验时也按 platform 校验。如果业务要求单点登录那要明确告知用户不要默默踢人。5.4 坑四资料图片三端存储路径不一致现象小程序上传的头像网页端显示裂图。原因小程序上传到本地磁盘网页端上传到对象存储返回的 URL 前缀不同前端拼接逻辑各写各的。解决统一走一个上传接口后端根据配置决定存本地还是对象存储返回完整可访问 URL。前端不要自己拼路径直接用后端返回的 URL。5.5 坑五匹配结果分页在三端表现不一致现象网页端第一页和第二页有重复用户小程序端正常。原因网页端分页参数从 0 开始小程序端从 1 开始后端没统一。解决后端统一用offset limit前端传之前做转换或者在接口文档里写死从 0 开始。这种问题排查起来很费时间建议在接口层加参数校验offset 小于 0 直接报错。6. 上线前必做的三端一致性验证与灰度技巧系统跑通不等于能上线。上线前我会做一轮三端一致性验证核心是确认「同一个用户在三端看到的数据和能做的操作完全一致」。具体做法准备一个测试账号在三端分别登录依次执行注册、完善资料、浏览匹配、打招呼、发消息、查看站内信六个动作每步截图对比。任何一端出现数据不一致先查接口返回再查数据库最后查前端渲染。验证通过后不要全量放开先做灰度。灰度策略按 uid 取模比如uid % 100 10的用户走新系统其余走旧系统。这样出问题影响面可控。灰度期间重点看三个指标登录成功率三端分别统计、消息到达率站内信写入量 vs 订阅消息发送成功量、匹配点击率推荐列表的点击/曝光。任何一个指标比旧系统低 20% 以上先回滚再排查。还有一个血泪经验上线前一定要用真实微信账号测试不要只用开发者工具。开发者工具里订阅消息、模板消息、支付都能模拟成功真机上各种权限和网络问题才会暴露。我一般会找三台不同型号的手机分别测小程序和公众号网页端用 Chrome 和 Safari 各测一遍。测试用例不用多把核心链路走通就行但每端都要走。最后说一个我自己的习惯每次改完消息分发逻辑先在一个测试 uid 上把三种消息类型各触发一遍看日志里站内信、订阅消息、模板消息、短信四条记录是否按预期出现。这个习惯帮我省了很多次上线后才发现「某端收不到消息」的后悔药。希望帮到你。本文还有配套的精品资源点击获取