概述
交友项目是一个陌生人社交 App,后端要用一套服务同时支撑移动端 App 和运营后台。这篇不看代码怎么写,先把产品本身讲清楚:它卖给谁、有哪几块功能、每块功能的界面长什么样,以及每个界面背后都躺着一个必须用后端技术解决的问题。
纲要
- 陌生人社交的产品命题:怎么让两个互不认识的人建立连接
- 目标用户画像与竞品对照(花田 / SOUL / 陌陌)
- 产品形态:C 端移动 App + B 端运营后台,共用一套后端
- 六大功能模块清单(按 Tab 结构组织,与原型页一一对应)
- 客户端原型线框:登录页、交友页、圈子页、我的页
- 从界面反推后端:功能 → 技术 → 对应章节的映射表
- 后端工程形态:Maven 聚合工程的五个顶层模块
- 三个最容易搞错的认知偏差
陌生人社交的产品命题
熟人社交(微信这一类)的连接是已有关系的线上化------你加了对方,因为你们本来就认识。陌生人社交反过来,连接关系在打开 App 之前根本不存在,所以产品的全部功夫都花在一件事上:
怎么在用户零关系、零内容的情况下,把一个可能愿意跟他聊天的陌生人推到他面前。
这个问题的答案在不同产品里长得不一样,落到交友项目上是三条路径:
- 推荐流:左滑不喜欢、右滑喜欢,双方都右滑即配对成功,进入可聊天状态。冷启动靠算法和用户填的兴趣标签,不靠社交关系链。
- 地理位置:搜附近的人,按距离倒序。陌生人社交里"离得近"天然比"画像像"更容易促成线下见面,这是一个很直接的驱动力。
- 弱压力破冰:语音匹配(发匿名语音、收匿名语音,听完选喜欢 / 不喜欢)、测灵魂(答题给用户打标签分类)、今日佳人(每天推一个高缘分值的对象)。
注意最后这条被很多初学者忽略,但它恰恰是陌生人社交的难点------直白的搭讪压力太大了。匿名语音、答题匹配这些机制的本质是给用户一个"不用直接开口"的接口,把破冰成本降到最低。讲义里"沟通弱势群体"这个目标人群说的就是这批人。
目标用户画像与竞品
讲义给的用户数据很具体:整体以 30 岁以下为主,其中 18---25 岁是核心受众。进一步分了四类:
| 人群 | 主要诉求 | 在产品里的落点 |
|---|---|---|
| 上班群体 | 在陌生人面前建立人设,扩大交际圈 | 圈子动态、个人资料页 |
| 学生群体 | 找有共同话题的长期关系,乐于表达 | 兴趣标签、测灵魂、推荐 |
| 文艺群体 | 找同好,在小圈子里交流 | 圈子推荐频道、兴趣社群 |
| 沟通弱势群体 | 现有线上沟通方式无效,需要更有效的破冰手段 | 语音匹配、今日佳人、缘分值 |
竞品选了三个,都有明确的对标点,不是随便挑的:
| 产品 | 定位 | 对项目的参考价值 |
|---|---|---|
| 花田 | 兴趣匹配 + 线下活动结合 | 内容运营与归属感的做法 |
| SOUL | 弱化颜值、强调"灵魂"匹配 | 测灵魂、标签分类、算法匹配 |
| 陌陌 | 基于地理位置的移动社交 | 搜附近、附近的人、距离排序 |
三个竞品有一个共同点:都在弱化肤浅的目的,把"探索自我"当成娱乐性卖点。做技术的时候也要记住这一点------匹配不只是算距离和标签,它承担的是产品体验。
产品形态:一套后端,两个前端
交友项目不是只有 App。它同时有两端:
| 端 | 形态 | 使用者 | 主要做什么 |
|---|---|---|---|
| C 端 | 移动 App(Flutter + Android) | 普通用户 | 交友、圈子、消息、小视频、个人中心 |
| B 端 | 后台管理系统(Web) | 运营 / 客服 / 审核 | 用户管理、动态与小视频审核、举报处理、数据统计 |
两端调的是同一套后端服务。这是全篇唯一需要点一句的架构动因:如果为 App 和后台各写一套逻辑,"同一份用户数据、同一套审核规则、同一份内容"就会被复制两份,后面必然走样。把业务能力抽到独立的服务里、两个前端通过 HTTP 接口复用,这就是课程后面要讲的微服务 + 分层架构的起点------具体拆分方式属于「技术架构」那一章的活。
#mermaid-svg-p7gpNmtvs5kp8tCZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-p7gpNmtvs5kp8tCZ .error-icon{fill:#552222;}#mermaid-svg-p7gpNmtvs5kp8tCZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-p7gpNmtvs5kp8tCZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .marker.cross{stroke:#333333;}#mermaid-svg-p7gpNmtvs5kp8tCZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-p7gpNmtvs5kp8tCZ p{margin:0;}#mermaid-svg-p7gpNmtvs5kp8tCZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster-label text{fill:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster-label span{color:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster-label span p{background-color:transparent;}#mermaid-svg-p7gpNmtvs5kp8tCZ .label text,#mermaid-svg-p7gpNmtvs5kp8tCZ span{fill:#333;color:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .node rect,#mermaid-svg-p7gpNmtvs5kp8tCZ .node circle,#mermaid-svg-p7gpNmtvs5kp8tCZ .node ellipse,#mermaid-svg-p7gpNmtvs5kp8tCZ .node polygon,#mermaid-svg-p7gpNmtvs5kp8tCZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .rough-node .label text,#mermaid-svg-p7gpNmtvs5kp8tCZ .node .label text,#mermaid-svg-p7gpNmtvs5kp8tCZ .image-shape .label,#mermaid-svg-p7gpNmtvs5kp8tCZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-p7gpNmtvs5kp8tCZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .rough-node .label,#mermaid-svg-p7gpNmtvs5kp8tCZ .node .label,#mermaid-svg-p7gpNmtvs5kp8tCZ .image-shape .label,#mermaid-svg-p7gpNmtvs5kp8tCZ .icon-shape .label{text-align:center;}#mermaid-svg-p7gpNmtvs5kp8tCZ .node.clickable{cursor:pointer;}#mermaid-svg-p7gpNmtvs5kp8tCZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .arrowheadPath{fill:#333333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p7gpNmtvs5kp8tCZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-p7gpNmtvs5kp8tCZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p7gpNmtvs5kp8tCZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster text{fill:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ .cluster span{color:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-p7gpNmtvs5kp8tCZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-p7gpNmtvs5kp8tCZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-p7gpNmtvs5kp8tCZ .icon-shape,#mermaid-svg-p7gpNmtvs5kp8tCZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p7gpNmtvs5kp8tCZ .icon-shape p,#mermaid-svg-p7gpNmtvs5kp8tCZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-p7gpNmtvs5kp8tCZ .icon-shape .label rect,#mermaid-svg-p7gpNmtvs5kp8tCZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p7gpNmtvs5kp8tCZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-p7gpNmtvs5kp8tCZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-p7gpNmtvs5kp8tCZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} C 端 App
Flutter + Android
后端服务集群
app-server
B 端运营后台
Web
用户 / 资料服务
动态 / 圈子服务
消息 / 推荐服务
MySQL
MongoDB
Redis
六大功能模块清单
按客户端底部 Tab 的顺序把功能拆开。讲义和原始讲解给的口径一致:注册登录、交友、圈子、消息、小视频、我的。
| 模块 | 核心能力 | 对应原型页 | 后续章节要解决的关键技术 |
|---|---|---|---|
| 注册登录 | 手机号 + 短信验证码登录,首次登录完善资料 | 登录页、首次登录信息完善 | 阿里云短信、验证码 Redis 缓存、JWT 无状态鉴权 |
| 交友 | 项目(左滑右滑)、搜附近、今日佳人、桃花传音、测灵魂、推荐好友 | 交友页 | MongoDB geo 地理位置查询、推荐算法、缘分值计算 |
| 圈子 | 发布动态(文字 + 最多 6 图)、好友动态、推荐动态、点赞 / 评论 / 转发 | 圈子页 | MongoDB 存动态、MQ 异步削峰、Redis 计数 |
| 消息 | 好友列表、即时聊天、互动通知(喜欢 / 点赞 / 评论 / 系统) | 消息页 | 环信 IM 集成、消息落库、会话列表排序 |
| 小视频 | 推荐视频流、发布视频、评论、关注 | 小视频页 | FastDFS 分布式文件存储、SpringCache 缓存 |
| 我的 | 个人资料编辑、访客记录(谁看过我)、通用设置、黑名单、钱包 | 我的页 | 阿里云 OSS 存头像、百度人脸识别审核、MongoDB 存访客 |
两张表里已经埋了整门课的引子。记住一个判断标准:凡是界面上要"跨用户聚合数据"的地方(动态流、访客、推荐),后端都不会简单------它要处理海量写入、去重和排序。凡是要"上传文件"的地方(头像、小视频、动态配图),一定绕不开对象存储或分布式文件系统。凡是"要实时"的地方(聊天、通知),一定绕不开第三方 IM 或消息中间件。
客户端原型解读
原型文件是 交友项目移动端原型V1.2,一共 4 个主要页面加若干子页。下面挑四个最能代表产品形态的画成线框,文案全部取自原型,没有编造。
屏 1 登录页
登录页只有一条主路径:手机号 + 短信验证码。原型里连"重新发送"的倒计时和次数限制都标了出来,说明这不是个占位页。
doc
+----------------------------------------+
| |
| 交友项目 |
| |
| 项目 slogans |
| |
| [ 请输入手机号 ] |
| [ 请输入验证码 获取验证码 ] |
| |
| [ 手机号登录注册 ] |
| |
| 已发送至 138****8888 |
| 重新发送 (50s) |
+----------------------------------------+
要点:
- 文案
项目 slogans是原型里的品牌标语占位(原型文件里实际拼作项目slogn,属笔误,正式产品应为 slogan)。 获取验证码不是点击即发:发送后按钮变为重新发送 (50s)带倒计时,倒计时结束才可重发;超过 3 次需要等 1 小时。这条约束后端必须校验------前端按钮只是提示,防刷要靠服务端记录发送次数和冷却时间。- 校验规则两条:手机号位数错误时输入框标红并提示
请输入正确的手机号;验证码错误时输入框抖动后清空、标红,并弹出验证码错误。 - 登录成功且是首次登录时,不直接进首页,而是跳「首次登录信息完善」;这个状态后端要能判断(用户资料是否已完善),不能只靠前端记。
- 这个页面在本篇只需要知道"有验证码 + 有倒计时 + 有次数限制"三件事。验证码真正的实现(阿里云短信 SDK、自动装配封装、Redis 缓存键设计)在后面的短信验证码与登录章节。
屏 2 交友页
交友页是项目的核心页,主体是一张可左右滑动的用户卡片,顶部是几个入口。
doc
+----------------------------------------+
| 项目 搜附近 桃花传音 测灵魂 |
+----------------------------------------+
| ------------------------------------ |
| |
| [ 用户头像 ] |
| |
| 黑马小美 |
| 单身 · 本科 · 年龄相仿 |
| 缘分值 89 |
| |
| ------------------------------------ |
| |
| [ 不喜欢 ] [ 喜欢 ] |
| |
| 今日还能发送 9 次 |
+----------------------------------------+
| 交友 圈子 消息 小视频 我的 |
+----------------------------------------+
要点:
- 顶部四个入口:项目 (左滑右滑卡片)、搜附近 、桃花传音 (语音匹配 / 漂流瓶式匿名语音)、测灵魂(答题分类)。
- 卡片信息结构固定:头像、昵称、性别、学历、年龄描述(原型写作
单身 本科 年龄相仿)、缘分值。缘分值是项目自己的匹配度指标,不是简单距离。 - 左右滑的约束写在原型注释里:
左划喜欢,右划不喜欢,每天限量不超过 100 个,开通会员可增加限额。页面上显示的是今日还能发送 9 次/今日还能接受 9 次------限额是按天计数的状态,必须服务端持久化,不能用本地缓存糊弄。 双方互相喜欢则配对成功。这是典型的双向写场景:A 右滑 B 的时候,如果 B 之前右滑过 A,就要立刻建立好友关系。并发下同一对用户互相右滑,做不好会插入两条好友记录,这类幂等性问题在后面写业务代码时会反复遇到。- 搜附近页面的约束:
附近的人根据用户距离倒序显示,展示头像、昵称、性别、标签、年龄、距离,点击进用户主页;原型给的示例文案是您附近有 25 个好友,选择跟他们聊聊吧!。距离倒序 = MongoDB 的地理位置索引 +$near查询。 - 测灵魂是分级的:初级 / 中级 / 高级灵魂题,
回答完初级题解锁下一级问题,点锁定题提示请先回答上一级问题;测试完有结果页可分享。题目在后台维护,每次提交更新用户属性------也就是说这个模块同时是内容管理 和用户画像写入口。
屏 3 圈子页
圈子是"朋友圈的陌生人版",顶部两个频道切换。
doc
+----------------------------------------+
| 圈子 推荐 | 最新 发动态 |
+----------------------------------------+
| 黑马小美 单身 · 本科 · 年龄相仿 |
| 黑马程序员 1024 程序员节快乐 |
| [ 图1 ] [ 图2 ] [ 图3 ] |
| 10 分钟前 20km 赞 评 转 |
| ------------------------------------ |
| 黑马小美 单身 · 本科 · 年龄相仿 |
| 黑马程序员 今天也要好好写代码 |
| [ 图1 ] [ 图2 ] |
| 1 小时前 3km 赞 评 转 |
| ------------------------------------ |
| 共 15 条评论 |
| [ 添加评论 ] |
+----------------------------------------+
| 交友 圈子 消息 小视频 我的 |
+----------------------------------------+
要点:
- 频道分 推荐 和 最新 。推荐频道是
根据问卷及喜好推荐相似用户动态,最新频道按时间倒序------两者数据源不同,前者走推荐服务,后者直查库。 - 动态可带图或视频:
图片最多不超过 6 张或发布一个小视频。发布页的约束是 140 字以内、最多选 6 张照片、超过 6 张不能再选。140 字和 6 张这两个数字就是接口的校验参数和字段上限,写实体类时直接照着来。 - 每条动态下方两类信息:相对时间(
10 分钟前、1 小时前,超过一天显示具体年月日,且要做取整)和距离(20km,发布地到本地的距离)。注意距离字段意味着发布动态时要记录位置,这又回到 geo 索引。 - 计数项:浏览量、点赞数、评论数、转发数。这四个数字是高频更新字段,直接对 MongoDB 主表做
inc在高并发下会打满写热点------所以课程里会用 MQ 把点赞 / 评论这类操作异步化,这也是「RabbitMQ」那一章要引入的东西。 - 评论可以回复,原型里显示
共 15 条评论与添加评论输入框。
屏 4 我的页
个人中心,功能密度最高的一页。
doc
+----------------------------------------+
| 我的 [ 设置 ] |
+----------------------------------------+
| [ 头像 ] 黑马小美 北京 |
| 互相关注 12 喜欢 34 粉丝 56 |
| ------------------------------------ |
| 我的动态 谁看过我 通用设置 |
| 客服在线 钱包 安全设置 |
| 黑名单 退出登录 |
| ------------------------------------ |
| 最近 4 人来访, 2 个缘分值很高 |
| 黑马小美 单身 · 本科 缘分值 89 |
+----------------------------------------+
| 交友 圈子 消息 小视频 我的 |
+----------------------------------------+
要点:
- 顶部三个关注类数字:互相关注 / 喜欢 / 粉丝。三者口径不同(互关是双向关系,喜欢是单向行为,粉丝是被关注数),后端是三份不同的数据。
- 功能入口拆开看:
我的动态(自己发的动态列表,时间倒序)、谁看过我(访客记录,原型示例最近 4 人来访,其中有 2 个缘分值很高)、通用设置、安全设置、黑名单、客服在线、钱包。 - 谁看过我这一项值得单独说:它要求每次有人访问你的主页就写一条访客记录,是典型的"读少写多"数据。用 MySQL 存很快就会顶不住,所以课程选了 MongoDB。同类的还有动态和评论。
- 编辑资料页:头像、昵称、性别、现居城市、生日。昵称要
进行敏感词的过滤和限制,支持汉字、字母、数字、---和_;命中敏感词时提示输入内容包含敏感词(原型文案写成"铭感词",是笔误)。 - 头像流程不是上传完就完事:选择拍照或相册 → 剪裁 → 人脸审核 。审核过程中前端显示扫描效果,结果以弹窗提示(成功即
审核成功,失败给重新上传)。这里会用到百度 AI 的人脸识别接口,头像本身走阿里云 OSS。 - 安全设置里有修改密码、手机号(原型马赛克为
186****7674)、手势锁(创建后每次从后台切回前台要解锁)。手势锁是纯客户端能力,后端不用参与,别做多余设计。
从界面反推后端:功能和技术是配对的
这张表是整篇的落点。前面每一个界面元素,背后都能对上一个具体的技术方案和课程章节。
| 界面功能 | 要解决的技术问题 | 技术方案 | 课程章节 |
|---|---|---|---|
| 手机号 + 验证码登录 | 验证码怎么发、怎么存、怎么防刷 | 阿里云短信服务 + 自动装配封装组件 + Redis 缓存验证码 | 短信验证码 |
| 登录态保持 | 无状态鉴权,避免服务端存 Session | JWT 签发 Token + 拦截器校验 | 用户登录 |
| 完善资料(头像) | 文件存哪儿、怎么访问 | 阿里云 OSS 对象存储 | 用户资料 |
| 头像人脸审核 | 内容合规 | 百度 AI 人脸识别接口 | 用户资料 |
| 搜附近 / 距离倒序 | 地理位置查询与排序 | MongoDB geo 索引 + $near 查询 |
交友(搜附近) |
| 今日佳人 / 缘分值 | 匹配度计算 | 基于用户标签与行为的打分 + 推荐服务 | 推荐系统 |
| 圈子动态列表 | 海量动态存储与分页 | MongoDB 文档存储 | 圈子 |
| 点赞 / 评论 / 转发 | 高频写、计数热点 | RabbitMQ 异步处理 + Redis 计数 | 消息中间件 |
| 动态与视频推荐流 | 个性化排序 | Spark + MLlib 协同过滤,独立推荐服务 | 推荐系统 |
| 即时聊天 | 长连接、离线消息 | 集成环信 IM(第三方 SDK) | 即时通信 |
| 小视频播放与上传 | 大文件存储与分发 | FastDFS 分布式文件系统 + SpringCache 缓存 | 小视频 |
| 运营后台 | 内容审核、用户管理、数据统计 | 前后端分离 + 同一套 app / dubbo 服务 | 后台管理 |
看这张表的正确方式不是"我要学 12 样东西",而是:每个功能都逼出一个技术选型。这也是项目作为练手项目的价值------它不演示某个知识点,它展示一套完整业务是怎么被拆成技术方案的。
举个具体的判断过程。为什么附近的人用 MongoDB 而不是 MySQL?因为要按距离倒序取前 N 条,MySQL 里算球面距离等于对全表做函数运算,索引直接失效;MongoDB 自带 2dsphere 地理索引,$near 能走索引。为什么动态用 MongoDB?因为动态字段不固定(图片数量、标签、位置、点赞数各不相同),关系型表要么拆一堆关联表要么留一堆空列,文档型天然合适。为什么点赞不能直接写主表?因为热门动态的点赞会集中打击同一行文档,用 MQ 攒批、幂等消费,再统一回写计数,写压力就摊平了。
后端工程形态
工程用 Maven 聚合工程组织,mf 是父工程,下面五个顶层模块,Dubbo 相关再拆一层。这是后续所有章节的落脚点,先把结构记住。
tree
mf
├── pom.xml # 父工程,统一依赖版本(Spring Boot 2.3.9.RELEASE)
├── mf-model # 实体类 / VO,前后端与 Dubbo 共享的数据模型
├── mf-commons # 通用工具、常量、异常、拦截器
├── mf-autoconfig # 自动装配组件(短信服务、人脸识别等第三方封装)
├── mf-dubbo # Dubbo 服务聚合层
│ ├── mf-dubbo-interface # 服务接口(消费者与提供者共用)
│ ├── mf-dubbo-db # 提供者:MySQL 数据访问
│ └── mf-dubbo-mongo # 提供者:MongoDB 数据访问
└── mf-app-server # 消费者:面向前端的服务入口
└── src/main/java/com/mf/server
├── AppServerApplication.java # 启动类
├── controller # 对外 REST 接口
└── service # 业务编排,调用 Dubbo 服务
各模块职责:
| 模块 | 职责 | 关键点 |
|---|---|---|
| mf-model | 实体、DTO、VO | 被多方依赖,不能引入 Web 相关依赖 |
| mf-commons | 通用能力 | 常量、业务异常、统一响应、拦截器 |
| mf-autoconfig | 第三方组件封装 | 短信、人脸识别,配置项走 @ConfigurationProperties |
| mf-dubbo-interface | Dubbo 接口定义 | 只有接口,不含实现,消费者靠它远程调用 |
| mf-dubbo-db | MySQL 服务提供者 | 面向关系型数据的写读 |
| mf-dubbo-mongo | MongoDB 服务提供者 | 动态、评论、访客、地理位置 |
| mf-app-server | 面向 App / 后台的入口 | 只有 controller 和 service,不直接连数据库 |
有两个设计点值得先记住,后面章节会反复出现:
mf-dubbo 被拆成 interface / db / mongo 三块,不是为了好看。Dubbo 的提供者和消费者处在不同进程,接口定义必须能被两边同时依赖,所以接口单独成一个模块,实现各自独立。DB 和 Mongo 分开则是因为它们的数据来源、连接池、事务语义完全不同,混在一个提供者里会让依赖变得不可控。
mf-app-server 里只有 controller 和 service 两个包,没有 mapper。这不是还没写完,而是刻意为之------入口服务不碰数据库,所有数据访问都通过 Dubbo 走远程调用。这条约束一旦破坏,微服务拆分就名存实亡了。
三个容易搞错的认知偏差
以为 App 要自己写。 客户端(Flutter + Android)和原型都由前端团队提供,课程给的是 APK 和 Android 模拟器,后端同学只需要按接口实现服务端。要装的是网易 MuMu 模拟器(兼容性较好),不是 Android Studio 那一整套。原型图也不是让你画的,它是接口契约的另一种形式------界面上每个字段、每个约束,最后都要在后端有对应实现。
以为功能简单。 "左滑右滑"看起来是个动画效果,实际要处理每日限额持久化、互相喜欢时的并发写、配对后的会话创建。"发一条动态"看起来是个 insert,实际要处理图片上传、位置存储、距离计算、相对时间格式化、计数异步化。真正的工作量从来不在界面上。
以为学完就能上线。 这个项目覆盖的是研发视角。一个社交产品真正上线还需要内容审核体系(动态、头像、视频、昵称全都要过审)、举报处理流程、运营活动、数据合规与用户隐私保护。项目里出现人脸审核、敏感词过滤这些点,是因为它们属于"不做就不能上架"的硬性要求,但完整的运营与合规体系不在课程范围内,也别指望它在这里。
官方文档
总结
交友项目是个陌生人社交 App,核心命题是"在零关系的前提下让两个陌生人建立连接",推荐、附近的人、语音匹配、测灵魂都是这个命题的不同解法。目标用户以 18---25 岁为核心,四类人群的诉求分别落在动态、标签、圈子和小压力破冰上。
产品是 C 端 App 加 B 端运营后台的双端形态,共用一套后端服务,这是后面必须拆微服务的根本原因。
六大模块(注册登录、交友、圈子、消息、小视频、我的)每一个都能对上一组具体技术:验证码对短信服务和 Redis,搜附近对 MongoDB geo,动态对 MongoDB 加 MQ,聊天对环信,小视频对 FastDFS,后台对前后端分离。这张映射表才是这个项目真正的导学价值------功能不是凭空来的,技术也不是凭空学的。
工程形态上,mf 父工程下分 model、commons、autoconfig、dubbo(interface / db / mongo)、app-server 五块,app-server 只做入口编排、不碰数据库,这条边界是微服务拆分能不能立住的关键。
下一篇进入「技术架构」,看这套功能是怎么被拆成服务、用什么中间件串起来的。