Java 项目实战: 交友项目 - 回顾和今日内容介绍

概述

今天要解决两件事:把用户控制面板剩下的功能(通用设置、陌生人问题、通知设置、黑名单)补齐,同时在项目里引入第二个存储引擎 MongoDB,用于「今日佳人」这类关系型数据库不好存的推荐数据。

纲要

  • 回顾:完善用户信息的关键产出
  • 本次主线:按讲义顺序拆解本章结构
  • 通用设置:需求与表结构、查询设置、陌生人问题、通知设置、黑名单管理(分页查询 / 移除)
  • MongoDB 简介:特点、Docker 安装、体系结构、数据类型
  • MongoDB 入门:库表操作、增删改查、索引、执行计划
  • SpringData-Mongo:环境搭建与基本操作
  • 今日佳人:recommend_user 表结构、mf-dubbo-mongo 服务提供者、接口实现
  • 推荐好友列表
  • 依赖关系:组件套路与鉴权体系是今天的地基,MongoDB 是新引擎
  • 产出预告:本章结束时工程的模块变化

回顾

之前围绕「完善用户信息」展开,产出直接服务今天的开发:

产出 一句话回顾
OSS 对象存储 头像上传走阿里云 OSS,不落本地磁盘
人脸识别组件 对接阿里云人脸检测,校验头像是否含人脸
资料 CRUD UserInfo 的查询与更新,走 mfController → mfService → Dubbo 的链路
拦截器 + ThreadLocal Token 校验统一收口在拦截器,用户 Id 放 ThreadLocal 透传,业务层随取随用
统一异常处理 @ControllerAdvice 兜底,异常转标准响应结构

这套「Controller 瘦身、Service 编排、Dubbo 调后端、ThreadLocal 传身份」的套路,今天写的每一个接口都会照搬。写代码前先把这个模板在脑子里过一遍,能省不少时间。

现在任务书

本章讲义结构如下,任务按这个顺序推进:

板块 做什么 产出物 验收标准
通用设置 需求分析、设计 Settings/Question/BlackList 三张表与实体类,实现查询通用设置接口 SettingsController + SettingsService + SettingsApi 全链路 GET 请求能返回当前用户的通知开关与陌生人问题
陌生人问题 保存 / 更新用户的陌生人问题 QuestionApi 及实现 更新后再次查询返回新问题
通知设置 保存通知开关状态 SettingsApi 的通知更新逻辑 数据库中 like_comment 等字段随请求变化
黑名单管理 分页查询黑名单、移除黑名单 BlackListApi + BlackListMapper + 分页插件配置 分页返回用户信息列表,移除后列表减少一条
MongoDB 简介 理解文档型数据库定位,Docker 安装,掌握体系结构与数据类型 可运行的 mongodb 容器 docker ps 能看到容器,mongo 客户端可连接
MongoDB 入门 库与集合操作、insert/save/update/remove、find 查询、索引创建、explain 执行计划 一组 mongosh 命令实操记录 能说出 COLLSCAN 与 IXSCAN 的区别
SpringData-Mongo 在 mf-dubbo-mongo 模块中集成 Spring Data MongoDB MongoTemplate 的 CRUD 代码 单元测试完成增删改查
今日佳人 设计 recommend_user 表,实现 RecommendUserApi,查询缘分值最高的用户 mf-dubbo-mongo 服务提供者 + mfService 调用 首页返回今日佳人卡片数据
推荐好友列表 基于推荐数据实现好友推荐列表接口 RecommendUserApi 扩展 列表分页返回推荐用户

前四个板块都是「用户设置面板」的一部分,走 MySQL + MyBatis-Plus 的老路子;后面五个板块转向 MongoDB,是本章真正的重头戏。

通用设置:本章的第一块硬骨头

不要被「通用设置」这个名字骗了,它不是一个接口,而是四个接口的组合:查询设置、设置陌生人问题、设置通知、黑名单管理(查询 + 移除)。表结构先行:
#mermaid-svg-LuUMI7xFWcwcOztk{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-LuUMI7xFWcwcOztk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LuUMI7xFWcwcOztk .error-icon{fill:#552222;}#mermaid-svg-LuUMI7xFWcwcOztk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LuUMI7xFWcwcOztk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LuUMI7xFWcwcOztk .marker.cross{stroke:#333333;}#mermaid-svg-LuUMI7xFWcwcOztk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LuUMI7xFWcwcOztk p{margin:0;}#mermaid-svg-LuUMI7xFWcwcOztk .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-LuUMI7xFWcwcOztk .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-LuUMI7xFWcwcOztk .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-LuUMI7xFWcwcOztk .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-LuUMI7xFWcwcOztk .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-LuUMI7xFWcwcOztk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LuUMI7xFWcwcOztk .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-LuUMI7xFWcwcOztk .node rect,#mermaid-svg-LuUMI7xFWcwcOztk .node circle,#mermaid-svg-LuUMI7xFWcwcOztk .node ellipse,#mermaid-svg-LuUMI7xFWcwcOztk .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LuUMI7xFWcwcOztk .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-LuUMI7xFWcwcOztk .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LuUMI7xFWcwcOztk .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LuUMI7xFWcwcOztk .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-LuUMI7xFWcwcOztk .edgeLabel .label text{fill:#333;}#mermaid-svg-LuUMI7xFWcwcOztk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一对多(user_id 关联)
黑名单记录
settings
Long
id
PK
Long
user_id
Integer
like_notification
Integer
comment_notification
Integer
pinglun_notification
question
Long
id
PK
Long
user_id
String
txt
black_list
Long
id
PK
Long
user_id
Long
black_user_id

黑名单分页查询有个细节:黑名单表只存 user_id,页面要展示的是对方的头像和昵称,所以实现里要拿 BlackListApi 查出 id 列表后再调 UserInfoApi 补全用户信息,还要给 mf-dubbo-db 配上 MyBatis-Plus 的分页插件。这是本节最容易漏的一步。

MongoDB:为什么要引入第二存储引擎

「今日佳人」要存的是推荐关系:用户 A 与候选 B 的缘分值,每天刷新,量级是百万级用户 × 候选集合。这种数据用 MySQL 存,要么建一张巨宽的关联表,要么频繁 join,都不划算。MongoDB 的文档模型恰好适合:

维度 MySQL MongoDB
数据模型 关系表,结构固定 BSON 文档,字段可扩展
查询语言 SQL MQL(find / update 等方法)
事务 强事务 4.0 起支持副本集内多文档事务
适用场景 设置、用户资料等强结构数据 推荐结果、访客记录、动态等海量半结构数据

本章只做入门三件事:Docker 起容器、mongosh 把增删改查和索引过一遍、用 explain 看执行计划。体系结构上记住一条对应关系就够:MySQL 的 database / table / row,在 MongoDB 里是 database / collection / document。

SpringData-Mongo 与今日佳人

Spring Data 家族的风格是统一的模板 + Repository 抽象。工程里新建 mf-dubbo-mongo 模块作为服务提供者,用 MongoTemplate 操作文档:

java 复制代码
// 查询缘分值最高的推荐用户(节选自 RecommendUserApiImpl 思路)
Query query = Query.query(Criteria.where("userId").is(userId))
        .with(Sort.by(Sort.Direction.DESC, "score"))
        .skip(0).limit(1);
RecommendUser ru = mongoTemplate.findOne(query, RecommendUser.class);

对应的工程结构变化,本章结束时的快照:

tree 复制代码
mf
├── mf-app-server          # 应用服务:SettingsController、mfController
├── mf-autoconfig          # 自动配置:OSS、人脸组件
├── mf-commons             # 公共工具
├── mf-model               # 实体:domain(Settings/Question/BlackList)、mongo(RecommendUser)
└── mf-dubbo
    ├── mf-dubbo-interface # API 接口:SettingsApi、QuestionApi、BlackListApi、RecommendUserApi
    ├── mf-dubbo-db        # MySQL 服务提供者:SettingsApiImpl、BlackListApiImpl 等
    └── mf-dubbo-mongo     # MongoDB 服务提供者:DubboMongoApplication、RecommendUserApiImpl

注意 mf-model 里出现了两个包:domain 放 MySQL 实体,mongo 放 MongoDB 文档实体,两套注解体系(@TableName 对 @Document)不要混用。

依赖关系

#mermaid-svg-q0y6GxEPpq61cjsE{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-q0y6GxEPpq61cjsE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-q0y6GxEPpq61cjsE .error-icon{fill:#552222;}#mermaid-svg-q0y6GxEPpq61cjsE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-q0y6GxEPpq61cjsE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-q0y6GxEPpq61cjsE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-q0y6GxEPpq61cjsE .marker.cross{stroke:#333333;}#mermaid-svg-q0y6GxEPpq61cjsE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-q0y6GxEPpq61cjsE p{margin:0;}#mermaid-svg-q0y6GxEPpq61cjsE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-q0y6GxEPpq61cjsE .cluster-label text{fill:#333;}#mermaid-svg-q0y6GxEPpq61cjsE .cluster-label span{color:#333;}#mermaid-svg-q0y6GxEPpq61cjsE .cluster-label span p{background-color:transparent;}#mermaid-svg-q0y6GxEPpq61cjsE .label text,#mermaid-svg-q0y6GxEPpq61cjsE span{fill:#333;color:#333;}#mermaid-svg-q0y6GxEPpq61cjsE .node rect,#mermaid-svg-q0y6GxEPpq61cjsE .node circle,#mermaid-svg-q0y6GxEPpq61cjsE .node ellipse,#mermaid-svg-q0y6GxEPpq61cjsE .node polygon,#mermaid-svg-q0y6GxEPpq61cjsE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-q0y6GxEPpq61cjsE .rough-node .label text,#mermaid-svg-q0y6GxEPpq61cjsE .node .label text,#mermaid-svg-q0y6GxEPpq61cjsE .image-shape .label,#mermaid-svg-q0y6GxEPpq61cjsE .icon-shape .label{text-anchor:middle;}#mermaid-svg-q0y6GxEPpq61cjsE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-q0y6GxEPpq61cjsE .rough-node .label,#mermaid-svg-q0y6GxEPpq61cjsE .node .label,#mermaid-svg-q0y6GxEPpq61cjsE .image-shape .label,#mermaid-svg-q0y6GxEPpq61cjsE .icon-shape .label{text-align:center;}#mermaid-svg-q0y6GxEPpq61cjsE .node.clickable{cursor:pointer;}#mermaid-svg-q0y6GxEPpq61cjsE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-q0y6GxEPpq61cjsE .arrowheadPath{fill:#333333;}#mermaid-svg-q0y6GxEPpq61cjsE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-q0y6GxEPpq61cjsE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-q0y6GxEPpq61cjsE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-q0y6GxEPpq61cjsE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-q0y6GxEPpq61cjsE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-q0y6GxEPpq61cjsE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-q0y6GxEPpq61cjsE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-q0y6GxEPpq61cjsE .cluster text{fill:#333;}#mermaid-svg-q0y6GxEPpq61cjsE .cluster span{color:#333;}#mermaid-svg-q0y6GxEPpq61cjsE 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-q0y6GxEPpq61cjsE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-q0y6GxEPpq61cjsE rect.text{fill:none;stroke-width:0;}#mermaid-svg-q0y6GxEPpq61cjsE .icon-shape,#mermaid-svg-q0y6GxEPpq61cjsE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-q0y6GxEPpq61cjsE .icon-shape p,#mermaid-svg-q0y6GxEPpq61cjsE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-q0y6GxEPpq61cjsE .icon-shape .label rect,#mermaid-svg-q0y6GxEPpq61cjsE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-q0y6GxEPpq61cjsE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-q0y6GxEPpq61cjsE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-q0y6GxEPpq61cjsE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 完善用户信息

拦截器+ThreadLocal / 统一异常 / OSS
通用设置四大接口
今日佳人
MySQL + MyBatis-Plus

含分页插件
MongoDB Docker 部署
SpringData-Mongo 集成
黑名单分页查询

跨 MySQL 用户信息补全
day04+ 圈子 / 推荐

继续消费 MongoDB 数据

两条线在这里交汇:设置面板继续吃 MySQL,推荐数据开始吃 MongoDB,之后的圈子、访客、小视频会持续使用 MongoDB,所以本章的环境搭建和 MongoTemplate 用法必须练熟。

误解澄清

常见误解 实际情况
MongoDB 可以完全替换 MySQL 不替换。两者并存,各管一类数据,项目里通过两个 dubbo 模块隔离
save 和 insert 效果一样 insert 遇到相同 _id 会报错;save 存在则更新、不存在则插入,语义不同
黑名单分页只查一张表 只查 black_list 得到 id,还要调 UserInfoApi 补头像昵称,是两步操作
分页插件全局自动生效 需要在 mf-dubbo-db 显式配置 MybatisPlusInterceptor,否则 Page 查询不生效
MongoDB 没有索引概念 有 createIndex,且 explain 能看到 COLLSCAN(全表扫)与 IXSCAN(索引扫)的差异

下一站

结构铺完了,从这里开始逐个击破:先做通用设置的需求分析与表结构设计。

相关推荐
用户77712985091243 分钟前
Stream流式使用
java
旋律41444 分钟前
List、Set、Map:三个装数据的容器,到底啥区别?怎么遍历?
java·后端
工具派1 小时前
不重写一行老代码:给 Spring Boot 系统加一层 AI 能力
java·人工智能·spring boot
大雄很用心1 小时前
Java 数据结构基础:栈、堆与 Deque
java
牛战士coding1 小时前
用户点了停止生成,上游还在烧 token 吗?取消传播链路拆解
java·人工智能
初心未改HD1 小时前
DSH Skill开发实战:SKILL.md是给LLM看的操作手册,不是给人看的README,踩了4个坑
java·大数据·开发语言
aibo8603081 小时前
基于 Spring Boot 的茶铺管理系统设计与实现
java·spring boot·后端
Wang's Blog2 小时前
Java 项目实战: 交友项目 - SpringData-Mongo 环境搭建和 CRUD
java·开发语言·交友
SunnyDays10112 小时前
Java 操作 PPT 超链接:添加、修改和删除
java·powerpoint·ppt·超链接