You are a wizard, Harry!
系列文章目录
1. 项目介绍及环境配置
2. 短信验证码登录
3. 用户信息
4. MongoDB
5.推荐好友列表/MongoDB集群/动态发布与查看
6. 圈子动态/圈子互动
7. 即时通讯(基于第三方API)
8. 附近的人(百度地图APi)
9. 小视频
10.网关配置
11.后台管理
文章目录
- 系列文章目录
- 一、推荐好友列表
- 1. 页面展示
- 2. 接口文档
- 3. 编码实现
- ⑴. 控制层 - 获取请求参数
- ⑵. Service层 - 调用API分页查询
- ⑶. API接口 - 分页查询推荐好友
- ⑷. API实现类 - 完成推荐用户id查询用户详情
- ⑸. 构造Dto对象
- ⑹. Postman测试
- 4. 代码优化 - 减少UserInfoApi频繁调用
- ⑴. API接口 - 获取请求参数
- ⑵. API接口实现类 - 批量查询用户详情
- ⑶. 测试类
- ⑷. Service层优化 - 减少UserInfoApi频繁调用
- ⑸. Postman测试
- 二、MongoDB集群
- 1. 单点问题分析
- 2. 解决方案
- 3. 副本集群
- ⑴. 执行原理
- ⑵. 特点
- 4. 分片集群
- ⑴. 分片服务(Shard Server)
- ⑵. 配置服务(Config Server)
- ⑶. 路由服务(Route Server)
- ⑷. 分片策略
- ⑸. 特点
- 三、发布动态
- 1. 页面展示
- 2. 表设计方案
- ⑴. 方案一
- ⑵. 方案二
- ⑶. 方案三
- ⑷. 表结构
- 3. 接口文档
- 4. 编码实现
- ⑴. 搭建提供者环境
- ①. 实体类
- ②. API接口
- ③. API实现类
- ⑵. 自增序列
- ①. 实体
- ②. 工具类
- ⑶. 测试
- ⑷. 接口文档
- ⑸. Controller获取请求参数
- ⑹. Service完成逻辑处理
- ⑺. API接口
- ⑻. API实现类
- ⑼. 测试类
- 5. 代码优化
- ⑴. 方法抽取
- ⑵. 引导类开启异步调用
- ⑶. 调用异步方法
- 6. 页面效果
- 四、查询我的动态
- 1. 页面展示
- 2. 接口文档
- 3. 编码实现
- ⑴. 构建vo对象
- ⑵. 控制层获取请求参数
- ⑶. Service层封装vo对象
- ⑷. API接口
- ⑸. API实现类查询动态数据
- 4. 测试
- ⑴. postman
- ⑵. 页面效果
一、推荐好友列表
1. 页面展示
2. 接口文档
3. 编码实现
⑴. 控制层 - 获取请求参数
编辑tanhua-app-server/src/main/java/com/tanhua/server/controller/TanhuaController.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26@RestController @RequestMapping("/tanhua") public class TanhuaController { @Autowired private TanhuaService tanhuaService; /** * 今日佳人 */ @GetMapping("/todayBest") public ResponseEntity todayBest() { TodayBest vo = tanhuaService.todayBest(); return ResponseEntity.ok(vo); } /** * 查询分页推荐好友列表 */ @GetMapping("recommendation") public ResponseEntity recommendation(RecommendUserDto dto) { PageResult pr = tanhuaService.recommendation(dto); return ResponseEntity.ok(pr); } }
⑵. Service层 - 调用API分页查询
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72@Service public class TanhuaService { @DubboReference private RecommendUserApi recommendUserApi; @DubboReference private UserInfoApi userInfoApi; // 查询今日佳人数据 public TodayBest todayBest() { // 1. 获取用户id Long userId = UserHolder.getUserId(); // 2. 调用api查询 RecommendUser recommendUser = recommendUserApi.queryWithMaxScore(userId); // 3. 设置默认值 if(recommendUser == null) { recommendUser = new RecommendUser(); recommendUser.setUserId(1l); recommendUser.setScore(99d); } // 4. 将recommendUser 转化成 todayBest对象 UserInfo userInfo = userInfoApi.findById(recommendUser.getUserId()); TodayBest vo = TodayBest.init(userInfo, recommendUser); // 5. 返回 return vo; } // 查询分页推荐好友列表 public PageResult recommendation(RecommendUserDto dto) { // 1. 获取用户id Long userId = UserHolder.getUserId(); // 2. 调用RecommendUserApi查询数据列表(PageResult -- RecommendUser) PageResult pr = recommendUserApi.queryRecommendUserList(dto.getPage(), dto.getPagesize(), userId); // 3. 获取分页中的RecommendUser数据列表 List<RecommendUser> items = (List<RecommendUser>) pr.getItems(); // 4. 判断列表数据是否为空 if (items == null) { return pr; } // 5. 循环RecommendUser推荐列表,根据推荐的用户id查询用户详情 List<TodayBest> list = new ArrayList<>(); for (RecommendUser item : items) { Long recommendUserId = item.getUserId(); UserInfo userInfo = userInfoApi.findById(recommendUserId); if(userInfo != null) { // 条件判断 if(!StringUtils.isEmpty(dto.getGender()) && !dto.getGender().equals(userInfo.getGender())) { continue; } if (dto.getAge() != null && dto.getAge() < userInfo.getAge()) { continue; } TodayBest vo = TodayBest.init(userInfo, item); list.add(vo); } } // 6. 构造返回值 pr.setItems(list); return pr; } }
⑶. API接口 - 分页查询推荐好友
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/RecommendUserApi.java
文件:
1
2
3
4
5
6
7
8
9public interface RecommendUserApi { // 查询今日佳人 RecommendUser queryWithMaxScore(Long toUserId); // 分页查询推荐好友列表 PageResult queryRecommendUserList(Integer page, Integer pagesize, Long toUserId); }
⑷. API实现类 - 完成推荐用户id查询用户详情
编辑tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/RecommendUserApiImpl.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41@DubboService public class RecommendUserApiImpl implements RecommendUserApi{ @Autowired private MongoTemplate mongoTemplate; // 查询今日佳人 public RecommendUser queryWithMaxScore(Long toUserId) { // 根据toUserId查询,根据评分score排序,获取第一条 // 1. 构建Criteria Criteria criteria = Criteria.where("toUserId").is(toUserId); // 2. 构建Query Query query = Query.query(criteria).with(Sort.by(Sort.Order.desc("score"))) .limit(1); // 查询第一条(第一页第一条) // 3. 调用mongoTemplate查询 return mongoTemplate.findOne(query, RecommendUser.class); } // 分页查询推荐好友列表 public PageResult queryRecommendUserList(Integer page, Integer pagesize, Long toUserId) { // 1. 构建Criteria对象 Criteria criteria = Criteria.where("toUserId").is(toUserId); // 2. 构造query对象 Query query = Query.query(criteria); // 3. 查询总数 long count = mongoTemplate.count(query, RecommendUser.class); // 4. 查询数据列表 query.with(Sort.by(Sort.Order.desc("score"))).limit(pagesize).skip((page - 1) * pagesize); List<RecommendUser> list = mongoTemplate.find(query, RecommendUser.class); // 5. 构造返回值 return new PageResult(page, pagesize, count, list); } }
⑸. 构造Dto对象
新建 tanhua-model/src/main/java/com/tanhua/model/dto/RecommendUserDto.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14@Data @NoArgsConstructor @AllArgsConstructor public class RecommendUserDto { private Integer page = 1; //当前页数 private Integer pagesize = 10; //页尺寸 private String gender; //性别 man woman private String lastLogin; //近期登陆时间 private Integer age; //年龄 private String city; //居住地 private String education; //学历 }
⑹. Postman测试
4. 代码优化 - 减少UserInfoApi频繁调用
- 在UserInfoApi中根据条件一次性获取所有用户列表
- 在Service层进行用户筛选
⑴. API接口 - 获取请求参数
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/UserInfoApi.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface UserInfoApi { public void save(UserInfo userInfo); public void update(UserInfo userInfo); // 根据id查询 UserInfo findById(Long id); /** * 批量查詢用戶詳情 * 返回值:Map<id, UserInfo> */ Map<Long, UserInfo> findByIds(List<Long> userIds, UserInfo info); }
⑵. API接口实现类 - 批量查询用户详情
编辑 tanhua-dubbo/tanhua-dubbo-db/src/main/java/com/tanhua/dubbo/api/UserInfoApiImpl.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51@DubboService public class UserInfoApiImpl implements UserInfoApi{ @Autowired private UserInfoMapper userInfoMapper; @Override public void save(UserInfo userInfo) { userInfoMapper.insert(userInfo); } @Override public void update(UserInfo userInfo) { userInfoMapper.updateById(userInfo); } @Override public UserInfo findById(Long id) { return userInfoMapper.selectById(id); } /** * 批量查询用戶详情 * @param userIds 用户id列表 * @param info 用户详情信息 * @return */ @Override public Map<Long, UserInfo> findByIds(List<Long> userIds, UserInfo info) { // 1. 用户id列表 QueryWrapper qw = new QueryWrapper<>(); qw.in("id", userIds); // 2. 添加筛选条件 if(info != null) { if(info.getAge() != null) { qw.lt("age", info.getAge()); } if(!StringUtils.isEmpty(info.getGender())) { qw.eq("gender", info.getGender()); } } // 3. 获取用户详情列表 转换成map集合 List<UserInfo> list = userInfoMapper.selectList(qw); Map<Long, UserInfo> map = CollUtil.fieldValueMap(list, "id"); // 4. 返回 return map; } }
⑶. 测试类
新建 tanhua-app-server/src/test/java/com/tanhua/test/UserInfoApiTest.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@RunWith(SpringRunner.class) @SpringBootTest(classes = AppServerApplication.class) public class UserInfoApiTest { @DubboReference private UserInfoApi userInfoApi; @Test public void testFindUsers() { List ids = new ArrayList<>(); ids.add(1l); ids.add(2l); ids.add(3l); ids.add(4l); // 添加判断条件 UserInfo userInfo = new UserInfo(); userInfo.setAge(23); // Map map = userInfoApi.findByIds(ids, null); Map map = userInfoApi.findByIds(ids, userInfo); map.forEach((k, v) -> System.out.println(k + "---" + v)); } }
⑷. Service层优化 - 减少UserInfoApi频繁调用
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93@Service public class TanhuaService { @DubboReference private RecommendUserApi recommendUserApi; @DubboReference private UserInfoApi userInfoApi; // 查询今日佳人数据 public TodayBest todayBest() { // 1. 获取用户id Long userId = UserHolder.getUserId(); // 2. 调用api查询 RecommendUser recommendUser = recommendUserApi.queryWithMaxScore(userId); // 3. 设置默认值 if(recommendUser == null) { recommendUser = new RecommendUser(); recommendUser.setUserId(1l); recommendUser.setScore(99d); } // 4. 将recommendUser 转化成 todayBest对象 UserInfo userInfo = userInfoApi.findById(recommendUser.getUserId()); TodayBest vo = TodayBest.init(userInfo, recommendUser); // 5. 返回 return vo; } // 查询分页推荐好友列表 public PageResult recommendation(RecommendUserDto dto) { // 1. 获取用户id Long userId = UserHolder.getUserId(); // 2. 调用RecommendUserApi查询数据列表(PageResult -- RecommendUser) PageResult pr = recommendUserApi.queryRecommendUserList(dto.getPage(), dto.getPagesize(), userId); // 3. 获取分页中的RecommendUser数据列表 List<RecommendUser> items = (List<RecommendUser>) pr.getItems(); // 4. 判断列表数据是否为空 if (items == null) { return pr; } // // 5. 循环RecommendUser推荐列表,根据推荐的用户id查询用户详情 // List<TodayBest> list = new ArrayList<>(); // for (RecommendUser item : items) { // Long recommendUserId = item.getUserId(); // UserInfo userInfo = userInfoApi.findById(recommendUserId); // if(userInfo != null) { // // 条件判断 // if(!StringUtils.isEmpty(dto.getGender()) && !dto.getGender().equals(userInfo.getGender())) { // continue; // } // if (dto.getAge() != null && dto.getAge() < userInfo.getAge()) { // continue; // } // TodayBest vo = TodayBest.init(userInfo, item); // list.add(vo); // } // } // // 5. 提取所有推荐的用户id列表 List<Long> ids = CollUtil.getFieldValues(items, "userId", Long.class); UserInfo userInfo = new UserInfo(); userInfo.setAge(dto.getAge()); userInfo.setGender(dto.getGender()); // 6. 构建查询条件,批量查询所有的用户详情 Map<Long, UserInfo> map = userInfoApi.findByIds(ids, userInfo); // 7. 循环推荐的数据列表,构建vo对象 List<TodayBest> list = new ArrayList<>(); for (RecommendUser item : items) { UserInfo info = map.get(item.getUserId()); if(info != null) { TodayBest vo = TodayBest.init(info, item); list.add(vo); } } // 6. 构造返回值 pr.setItems(list); return pr; } }
⑸. Postman测试
二、MongoDB集群
1. 单点问题分析
单机 MongoDB 并不适用于企业场景,存在两个问题亟需解决
- 单点故障: 单一MongoDB提供服务,在服务器宕机时造成整体应用崩溃
- 海量数据存储: 单一MongoDB,并不能支持海量数据存储
2. 解决方案
为了 解决单点故障 和 海量数据存储 问题,MongoDB提供了三种集群形式来支持:
- Master-Slaver(主从集群): 是一种主从副本的模式,目前已经 不推荐 使用
- Replica Set (副本集群): 模式取代了 Master-Slaver 模式,是一种互为主从的关系。可以解决 单点故障 问题
- Sharding (分片集群): 可以解决单点故障和 海量数据存储 问题
3. 副本集群
⑴. 执行原理
- 包括主节点和副本节点/从节点
- 主节点只能有一个,可以完成数据读写操作
- 副本节点可以有多个,只能完成读操作
- 多节点间有心跳检测,并进行数据同步
- 主节点宕机后,副本节点选举新的主节点
- 当主节点挂掉后,副本节点会进行选举,从中选举出一个新的主节点
⑵. 特点
单点故障、适用于中小型应用(数据量适中)、故障转移、读写分离
4. 分片集群
Sharding (分片集群)该模式适合处理大量数据,它将数据分开存储,不同服务器保存不同的数据,所有服务器数据的总和即为整个数据集。
⑴. 分片服务(Shard Server)
⑵. 配置服务(Config Server)
⑶. 路由服务(Route Server)
⑷. 分片策略
MongoDB 通过分片策略,决定数据存储的分片服务器。mongoDB 有两种分片策略,根据集合字段来指定。
- 范围指定: 将指定字段的数据按照范围进行划分,根据范围获取分片服务器
- 数据Hash: 将指定字段的数据进行Hash计算,获取存储的分片服务器
⑸. 特点
支持海量数据存储、分片集群内部结构、分片策略
三、发布动态
1. 页面展示
2. 表设计方案
需求:
- 查看个人发布的动态
- 查看好友发布的动态
- 指定好友可见/不可见
⑴. 方案一
最简单的设计思路,包含 好友表 与 动态表:
- 保存动态时,向动态表添加记录即可
- 查询好友动态时
- 先查询当前用户的所有好友
- 根据好友,查询好友发布的所有动态
优点: 开发难度较小、易于理解
缺点: 动态对特定好友可见/不可见,实现难度较大、效率较低
⑵. 方案二
基于设计1的改造版本,在动态表中添加可见人字段
- 保存动态
- 查询当前用户好友
- 保存动态,将 可见好友存入动态表
- 查询好友动态
优点: 开发难度较小、可以完成所有业务功能
缺点: 索引空间占用、效率较低
⑶. 方案三
时间线表保存好友发布的动态(ID)
- 发布动态
- 查询好友动态
优点: 可以完成所有业务功能、数据结构清晰
缺点: 开发难度较复杂
分片规则: 动态表(根据用户id)、分片时间线表(根据好友id分片)
⑷. 表结构
好友关系表(friends):
1
2
3
4
5
6
7
8{ "_id": ObjectId("6018bc055098b2230031e2da"), "created": NumberLong("1612233733056"), "userId": NumberLong("1"), "friendId": NumberLong("106"), "_class": "com.itheima.domain.mongo.Friend" }
动态表(movement):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{ "_id": ObjectId("5e82dc416401952928c211d8"), "pid": NumberLong("10064"), "userId": NumberLong("6"), "textContent": "最悲伤却又是最痛苦的谎言,就是我还好,没有关系。", "medias": [ "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1.jpg", "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567349498.jpg", "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567352977.jpg", "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567360406.jpg" ], "longitude": "121.588627", "latitude": "30.935781", "state": NumberInt("0"), "locationName": "中国上海市奉贤区人民路445弄", "created": NumberLong("1585634369493"), "_class": "com.tanhua.dubbo.server.pojo.Publish" }
时间线表(movement_timeline):
1
2
3
4
5
6
7
8
9{ "_id": ObjectId("609cf6538743d448c02c61f0"), "movementId": ObjectId("609cf6538743d448c02c61ef"), "userId": NumberLong("106"), "friendId": NumberLong("1"), "created": NumberLong("1620899411043"), "_class": "com.tanhua.model.mongo.MovementTimeLine" }
3. 接口文档
4. 编码实现
⑴. 搭建提供者环境
①. 实体类
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Friend.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/** * 好友表:好友关系表 */ @Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "friend") public class Friend implements java.io.Serializable{ private static final long serialVersionUID = 6003135946820874230L; private ObjectId id; private Long userId; //用户id private Long friendId; //好友id private Long created; //时间 }
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Movement.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//动态详情表 @Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "movement") public class Movement implements java.io.Serializable { private ObjectId id; //主键id // redis实现, 或者使用MongoDB实现 private Long pid; //Long类型,用于推荐系统的模型(自动增长) private Long created; //发布时间 private Long userId; private String textContent; //文字 private List<String> medias; //媒体数据,图片或小视频 url private String longitude; //经度 private String latitude; //纬度 private String locationName; //位置名称 private Integer state = 0;//状态 0:未审(默认),1:通过,2:驳回 }
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/MovementTimeLine.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/** * 好友时间线表,用于存储好友发布的数据 */ @Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "movement_timeLine") public class MovementTimeLine implements java.io.Serializable { private static final long serialVersionUID = 9096178416317502524L; private ObjectId id; private ObjectId movementId;//动态id private Long userId; //发布动态用户id private Long friendId; // 可见好友id private Long created; //发布的时间 }
②. API接口
新建 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java
文件:
1
2
3public interface MovementApi { }
③. API实现类
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java
文件:
1
2
3
4
5
6
7@DubboService public class MovementApiImpl implements MovementApi { @Autowired private MongoTemplate mongoTemplate; }
⑵. 自增序列
①. 实体
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Sequence.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13@Document(collection = "sequence") @Data @AllArgsConstructor @NoArgsConstructor public class Sequence { private ObjectId id; private long seqId; //自增序列 private String collName; //集合名称 }
②. 工具类
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/utils/IdWorker.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Component public class IdWorker { @Autowired private MongoTemplate mongoTemplate; public Long getNextId(String collName) { Query query = new Query(Criteria.where("collName").is(collName)); Update update = new Update(); update.inc("seqId", 1); FindAndModifyOptions options = new FindAndModifyOptions(); options.upsert(true); options.returnNew(true); Sequence sequence = mongoTemplate.findAndModify(query, update, options, Sequence.class); return sequence.getSeqId(); } }
⑶. 测试
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/test/java/com/tanhua/dubbo/IdWorkerTest.java
测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14@RunWith(SpringRunner.class) @SpringBootTest public class IdWorkerTest { @Autowired private IdWorker idWorker; @Test public void test() { Long id = idWorker.getNextId("test"); System.out.println(id); } }
⑷. 接口文档
⑸. Controller获取请求参数
新建 tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@RestController @RequestMapping("/movements") public class MovementController { @Autowired private MovementService movementService; /** * 发布动态 * @return */ @PostMapping public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException { movementService.publishMovement(movement, imageContent); return ResponseEntity.ok(null); } }
⑹. Service完成逻辑处理
新建 tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37@Service public class MovementService { @Autowired private OssTemplate ossTemplate; @DubboReference private MovementApi movementApi; /** * 发布动态 */ public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException { // 1. 判断发布动态的内容是否存在 if(StringUtils.isEmpty(movement.getTextContent())) { throw new BusinessException(ErrorResult.contentError()); } // 2. 获取当前登录的用户id Long userId = UserHolder.getUserId(); // 3. 将文件内容上传到阿里云OSS, 获取请求地址 List<String> medias = new ArrayList<>(); for (MultipartFile multipartFile : imageContent) { String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream()); medias.add(upload); } // 4. 将数据封装到movement对象 movement.setUserId(userId); movement.setMedias(medias); //5. 调用API完成动态发布 movementApi.publish(movement); } }
⑺. API接口
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java
文件:
1
2
3
4
5
6public interface MovementApi { // 发布动态 void publish(Movement movement); }
⑻. API实现类
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41@DubboService public class MovementApiImpl implements MovementApi { @Autowired private MongoTemplate mongoTemplate; @Autowired private IdWorker idWorker; // 发布动态 public void publish(Movement movement) { try { // 1. 保存动态详情 // 1.1 设置PID(同名时序列自增) movement.setPid(idWorker.getNextId("movement")); // 1.2 设置时间 movement.setCreated(System.currentTimeMillis()); // 1.3 保存动态 mongoTemplate.save(movement); // 2. 查询当前用户的好友数据 Criteria criteria = Criteria.where("userId").is(movement.getUserId()); Query query = Query.query(criteria); List<Friend> friends = mongoTemplate.find(query, Friend.class); // 3. 循环好友数据, 构建时间线数据存入数据库 for (Friend friend : friends) { MovementTimeLine timeLine = new MovementTimeLine(); timeLine.setMovementId(movement.getId()); timeLine.setUserId(friend.getUserId()); timeLine.setFriendId(friend.getFriendId()); timeLine.setCreated(System.currentTimeMillis()); mongoTemplate.save(timeLine); } } catch (Exception e) { // 忽略事物处理 e.printStackTrace(); } } }
⑼. 测试类
新建 tanhua-app-server/src/test/java/com/tanhua/test/MovementApiTest.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@RunWith(SpringRunner.class) @SpringBootTest(classes = AppServerApplication.class) public class MovementApiTest { @DubboReference private MovementApi movementApi; @Test public void testPublish() { Movement movement = new Movement(); movement.setUserId(106l); movement.setTextContent("你的酒窝没有酒,我却醉的像条狗"); List<String> list = new ArrayList<>(); list.add("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/tanhua/avatar_1.png"); list.add("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/tanhua/avatar_2.png"); movement.setMedias(list); movement.setLatitude("40.066355"); movement.setLongitude("116.350426"); movement.setLocationName("中国北京市昌平区建材城西路16号"); movementApi.publish(movement); } }
5. 代码优化
大量的时间线数据同步写入的问题如何解决 ?
@Async: Spring提供的异步处理注解,被此注解标注的方法会在新的线程中执行,其实就相当于我们自己new Thread。
⑴. 方法抽取
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/utils/TimeLineService.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32@Component public class TimeLineService { @Autowired private MongoTemplate mongoTemplate; @Async // 异步多线程调用 public void saveTimeLine(Long userId, ObjectId movementId) { // 2. 查询当前用户的好友数据 Criteria criteria = Criteria.where("userId").is(userId); Query query = Query.query(criteria); List<Friend> friends = mongoTemplate.find(query, Friend.class); // 睡眠时间 try { Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } // 3. 循环好友数据, 构建时间线数据存入数据库 for (Friend friend : friends) { MovementTimeLine timeLine = new MovementTimeLine(); timeLine.setMovementId(movementId); timeLine.setUserId(friend.getUserId()); timeLine.setFriendId(friend.getFriendId()); timeLine.setCreated(System.currentTimeMillis()); mongoTemplate.save(timeLine); } } }
⑵. 引导类开启异步调用
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/DubboMongoApplication.java
文件:
1
2
3
4
5
6
7
8
9@SpringBootApplication @EnableAsync // 开启Spring @Async支持 public class DubboMongoApplication { public static void main(String[] args) { SpringApplication.run(DubboMongoApplication.class,args); } }
⑶. 调用异步方法
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47@DubboService public class MovementApiImpl implements MovementApi { @Autowired private MongoTemplate mongoTemplate; @Autowired private IdWorker idWorker; @Autowired private TimeLineService timeLineService; // 发布动态 public void publish(Movement movement) { try { // 1. 保存动态详情 // 1.1 设置PID(同名时序列自增) movement.setPid(idWorker.getNextId("movement")); // 1.2 设置时间 movement.setCreated(System.currentTimeMillis()); // 1.3 保存动态 mongoTemplate.save(movement); // // 2. 查询当前用户的好友数据 // Criteria criteria = Criteria.where("userId").is(movement.getUserId()); // Query query = Query.query(criteria); // List<Friend> friends = mongoTemplate.find(query, Friend.class); // // // 3. 循环好友数据, 构建时间线数据存入数据库 // for (Friend friend : friends) { // MovementTimeLine timeLine = new MovementTimeLine(); // timeLine.setMovementId(movement.getId()); // timeLine.setUserId(friend.getUserId()); // timeLine.setFriendId(friend.getFriendId()); // timeLine.setCreated(System.currentTimeMillis()); // mongoTemplate.save(timeLine); // } // 异步多线程调用 timeLineService.saveTimeLine(movement.getUserId(), movement.getId()); } catch (Exception e) { // 忽略事物处理 e.printStackTrace(); } } }
6. 页面效果
四、查询我的动态
1. 页面展示
2. 接口文档
3. 编码实现
⑴. 构建vo对象
新建 tanhua-model/src/main/java/com/tanhua/model/vo/MovementsVo.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51@Data @NoArgsConstructor @AllArgsConstructor public class MovementsVo implements Serializable { private String id; //动态id private Long userId; //用户id private String avatar; //头像 private String nickname; //昵称 private String gender; //性别 man woman private Integer age; //年龄 private String[] tags; //标签 private String textContent; //文字动态 private String[] imageContent; //图片动态 private String distance; //距离 private String createDate; //发布时间 如: 10分钟前 private Integer likeCount; //点赞数 private Integer commentCount; //评论数 private Integer loveCount; //喜欢数 private Integer hasLiked; //是否点赞(1是,0否) private Integer hasLoved; //是否喜欢(1是,0否) public static MovementsVo init(UserInfo userInfo, Movement item) { MovementsVo vo = new MovementsVo(); //设置动态数据 BeanUtils.copyProperties(item, vo); vo.setId(item.getId().toHexString()); //设置用户数据 BeanUtils.copyProperties(userInfo, vo); if(!StringUtils.isEmpty(userInfo.getTags())) { vo.setTags(userInfo.getTags().split(",")); } //图片列表 vo.setImageContent(item.getMedias().toArray(new String[]{})); //距离 vo.setDistance("500米"); Date date = new Date(item.getCreated()); vo.setCreateDate(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date)); //设置是否点赞(后续处理) vo.setHasLoved(0); vo.setHasLiked(0); return vo; } }
⑵. 控制层获取请求参数
编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@RestController @RequestMapping("/movements") public class MovementController { @Autowired private MovementService movementService; /** * 发布动态 * @return */ @PostMapping public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException { movementService.publishMovement(movement, imageContent); return ResponseEntity.ok(null); } /** * 查询我的动态 */ @GetMapping("/all") public ResponseEntity findByUserId(Long userId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pagesize) { PageResult pr = movementService.findByUserId(userId, page, pagesize); return ResponseEntity.ok(pr); } }
⑶. Service层封装vo对象
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70@Service public class MovementService { @Autowired private OssTemplate ossTemplate; @DubboReference private MovementApi movementApi; @DubboReference private UserInfoApi userInfoApi; /** * 发布动态 */ public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException { // 1. 判断发布动态的内容是否存在 if(StringUtils.isEmpty(movement.getTextContent())) { throw new BusinessException(ErrorResult.contentError()); } // 2. 获取当前登录的用户id Long userId = UserHolder.getUserId(); // 3. 将文件内容上传到阿里云OSS, 获取请求地址 List<String> medias = new ArrayList<>(); for (MultipartFile multipartFile : imageContent) { // String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream()); // !!! 阿里云OSS收费, 这里暂时跳过 String upload = "https://img-blog.csdnimg.cn/bb419b491ec1445d84046aa1852946bd.jpeg"; medias.add(upload); } // 4. 将数据封装到movement对象 movement.setUserId(userId); movement.setMedias(medias); //5. 调用API完成动态发布 movementApi.publish(movement); } // 查询我的动态 public PageResult findByUserId(Long userId, Integer page, Integer pagesize) { // 1. 根据用户id, 调用API查询个人动态内容(PageResult -- Movement) PageResult pr = movementApi.findByUserId(userId, page, pagesize); // 2. 获取PageResult中item列表对象 List<Movement> items = (List<Movement>) pr.getItems(); // 3. 非空判断 if(items == null) { return pr; } // 4. 循环数据列表 UserInfo userInfo = userInfoApi.findById(userId); List<MovementsVo> vos = new ArrayList<>(); for (Movement item : items) { // 5. 一个Movement构建一个VO对象 MovementsVo vo = MovementsVo.init(userInfo, item); vos.add(vo); } // 6. 构建返回值 pr.setItems(vos); return pr; } }
⑷. API接口
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java
文件:
1
2
3
4
5
6
7
8
9public interface MovementApi { // 发布动态 void publish(Movement movement); // 查询我的动态 PageResult findByUserId(Long userId, Integer page, Integer pagesize); }
⑸. API实现类查询动态数据
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57@DubboService public class MovementApiImpl implements MovementApi { @Autowired private MongoTemplate mongoTemplate; @Autowired private IdWorker idWorker; @Autowired private TimeLineService timeLineService; // 发布动态 public void publish(Movement movement) { try { // 1. 保存动态详情 // 1.1 设置PID(同名时序列自增) movement.setPid(idWorker.getNextId("movement")); // 1.2 设置时间 movement.setCreated(System.currentTimeMillis()); // 1.3 保存动态 mongoTemplate.save(movement); // // 2. 查询当前用户的好友数据 // Criteria criteria = Criteria.where("userId").is(movement.getUserId()); // Query query = Query.query(criteria); // List<Friend> friends = mongoTemplate.find(query, Friend.class); // // // 3. 循环好友数据, 构建时间线数据存入数据库 // for (Friend friend : friends) { // MovementTimeLine timeLine = new MovementTimeLine(); // timeLine.setMovementId(movement.getId()); // timeLine.setUserId(friend.getUserId()); // timeLine.setFriendId(friend.getFriendId()); // timeLine.setCreated(System.currentTimeMillis()); // mongoTemplate.save(timeLine); // } // 异步多线程调用 timeLineService.saveTimeLine(movement.getUserId(), movement.getId()); } catch (Exception e) { // 忽略事物处理 e.printStackTrace(); } } // 查询我的动态 @Override public PageResult findByUserId(Long userId, Integer page, Integer pagesize) { Criteria criteria = Criteria.where("userId").is(userId); Query query = Query.query(criteria).skip((page - 1) * pagesize).limit(pagesize) .with(Sort.by(Sort.Order.desc("created"))); List<Movement> movements = mongoTemplate.find(query, Movement.class); return new PageResult(page, pagesize, 0l, movements); } }
4. 测试
⑴. postman
⑵. 页面效果
最后
以上就是暴躁舞蹈最近收集整理的关于Dobbo微服务项目实战(详细介绍+案例源码) - 5.推荐好友列表/MongoDB集群/动态发布与查看系列文章目录一、推荐好友列表二、MongoDB集群三、发布动态四、查询我的动态的全部内容,更多相关Dobbo微服务项目实战(详细介绍+案例源码)内容请搜索靠谱客的其他文章。
发表评论 取消回复