1、首页推荐
1.1、接口分析
地址:http://192.168.136.160:3000/project/19/interface/api/118
响应:
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 { "counts" : 4698 , "pagesize" : 20 , "pages" : 58 , "page" : 16 , "items" : [ { "id" : 1011 , "avatar" : "assets/images/avatar_2.png" , "nickname" : "黑马小妹" , "gender" : "woman" , "age" : 23 , "tags" : [ "本科" , "年龄相仿" , "单身" ], "fateValue" : 96 }, { "id" : 2495 , "avatar" : "assets/images/avatar_1.png" , "nickname" : "米朵妹妹" , "gender" : "man" , "age" : 28 , "tags" : [ "年龄相仿" , "本科" , "单身" ], "fateValue" : 87 }, { "id" : 5708 , "avatar" : "assets/images/avatar_4.png" , "nickname" : "黑马小妹" , "gender" : "man" , "age" : 24 , "tags" : [ "单身" , "本科" , "年龄相仿" ], "fateValue" : 94 }, { "id" : 4768 , "avatar" : "assets/images/avatar_3.png" , "nickname" : "黑马小妹" , "gender" : "man" , "age" : 24 , "tags" : [ "年龄相仿" , "单身" , "本科" ], "fateValue" : 80 } ] }
1.2、功能实现 1.2.1 controller TanhuaController
编写推荐列表方法
1 2 3 4 5 6 7 8 @GetMapping("/recommendation") public ResponseEntity recommendation (RecommendUserDto dto) { PageResult pr = tanhuaService.recommendation(dto); return ResponseEntity.ok(pr); }
1.2.2 service TanhuaService
编写推荐列表方法
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 public PageResult recommendation (RecommendUserDto dto) { Long userId = UserHolder.getUserId(); PageResult pr = recommendUserApi.queryRecommendUserList(dto.getPage(),dto.getPagesize(),userId); List<RecommendUser> items = (List<RecommendUser>) pr.getItems(); if (items == null || items.size() <=0 ) { return pr; } List<Long> ids = CollUtil.getFieldValues(items, "userId" , Long.class); UserInfo userInfo = new UserInfo(); userInfo.setAge(dto.getAge()); userInfo.setGender(dto.getGender()); Map<Long, UserInfo> map = userInfoApi.findByIds(ids, userInfo); 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); } } pr.setItems(list); return pr; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public Map<Long, UserInfo> findByIds (List<Long> userIds, UserInfo info) { QueryWrapper qw = new QueryWrapper(); qw.in("id" , userIds); if (info != null ) { if (info.getAge() != null ) { qw.lt("age" , info.getAge()); } if (!StringUtils.isEmpty(info.getGender())) { qw.eq("gender" , info.getGender()); } } List<UserInfo> list = userInfoMapper.selectList(qw); Map<Long, UserInfo> map = CollUtil.fieldValueMap(list, "id" ); return map; }
1.2.3 API接口 在RecommendUserApi
接口和RecommendUserApiImpl
实现类中添加方法查询
1 2 3 4 5 6 7 8 9 10 11 12 13 public PageResult queryRecommendUserList (Integer page, Integer pagesize, Long toUserId) { Criteria criteria = Criteria.where("toUserId" ).is(toUserId); Query query = Query.query(criteria).with(Sort.by(Sort.Order.desc("score" ))).limit(pagesize) .skip((page - 1 ) * pagesize); List<RecommendUser> list = mongoTemplate.find(query, RecommendUser.class); long count = mongoTemplate.count(query, RecommendUser.class); return new PageResult(page,pagesize,count,list); }
1.2.4 请求dto对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class RecommendUserDto { private Integer page = 1 ; private Integer pagesize = 10 ; private String gender; private String lastLogin; private Integer age; private String city; private String education; }
2、MongoDB集群 3、圈子功能 2.1、功能说明 探花交友项目中的圈子功能,类似微信的朋友圈,基本的功能为:发布动态、浏览好友动态、浏览推荐动态、点赞、评论、喜欢等功能。
发布:
1.2、实现方案分析 单点问题分析: 单机Mongodb并不适用于企业场景,存在两个问题:
单一MongoDB提供服务,在服务器宕机时造成整体应用崩溃海量数据存储
海量数据存储
单一MongoDB,并不能支持海量数据存储
集群概述:
为了解决单点故障和海量数据存储问题,MongoDB提供了三种集群形式来支持
Master-Slaver(主从集群)︰是一种主从副本的模式,目前已经不推荐使用
Replica Set (副本集群)︰模式取代了Master-Slaver模式,是一种互为主从的关系。可以解决单点故障
Sharding(分片集群)︰可以解决单点故障和海量数据存储问题
副本集群:
Replica Set(副本集群)︰该模式取代了主从模式,数据复制多份保存在不同服务器中,在出现故障时自动切换,实现故障转移.
包括主节点和副本节点/从节点 主节点只能有一个,可以完成数据读写操作
副本节点可以有多个,只能完成读操作
多节点间有心跳检测,并进行数据同步
主节点宕机后,副本节点选举新的主节点
对于圈子功能的实现,我们需要对它的功能特点做分析:
互动数据,动态数据规模极大
数据价值低
指定好友不可见/不可见
非好友内容不可见
针对以上特点,我们来分析一下:
对于数据量大而言,显然不能够使用关系型数据库进行存储,我们需要通过MongoDB进行存储
对于读多写少的应用,需要减少读取的成本
比如说,一条SQL语句,单张表查询一定比多张表查询要快
对于每个人数据在存储层面最好做到相互隔离,这样的话就不会有影响
所以对于存储而言,主要是核心的4张表:
发布表:记录了所有用户的发布的东西信息,如图片、视频等。
自己时间线:相册是每个用户独立的,记录了该用户所发布的所有内容。
好友时间线:所谓“刷朋友圈”,就是刷时间线,就是一个用户所有的朋友的发布内容。
好友表:记录好友关系
1.3、技术方案(重点) 表结构设计一: 最简单的设计思路,包含好友表与动态表。
保存动态,向动态表中添加记录即可
查询好友动态时
先查询当前用户的所有好友
根据好友,查询好友发布的所有动态
优点:
开发难度小
易于理解
缺点:
动态对特定好友不可见/可见,实现难度较大
效率较低
表结构设计-2: 基于设计1的改造版本,在动态表中添加可见人字段
保存动态
查询当前用户好友·
保存动态,将可见好友存入动态表
查询好友动态
优点:
缺点
效率较低
索引空间占用(如果给可见人创建索引,如果粉丝量较大,那么开销就会很大)
表结构设计-3:
时间线表保存好友发布的动态:
增加一个时间线表如图所示:
当用户发布动态时,首先查询好友,写入动态表,再将关系保存至时间数据表中。
首先查询时间线表,再查询动态表中的数据。
需要指定分片规则:
动态表:根据用户id分片
时间线表:根据好友id分片
将指定字段的数据按照范围进行划分,根据范围获取分片服务器
根据之前我们的分析,对于技术方案而言,将采用MongoDB+Redis来实现,其中MongoDB负责存储,Redis负责缓存数据。
1.4、表结构设计
发布表:动态总记录表(记录每个人发送的动态详情)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #表名:movement { "_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" }
好友时间线表:记录当前好友发布的动态数据
1 2 3 4 5 6 7 8 9 10 #表名:movement_timeline { "_id" : ObjectId("609cf6538743d448c02c61f0" ), "movementId" : ObjectId("609cf6538743d448c02c61ef" ), "userId" : NumberLong("106" ), "friendId" : NumberLong("1" ), "created" : NumberLong("1620899411043" ), "_class" : "com.tanhua.model.mongo.MovementTimeLine" }
好友关系表:记录好友的双向关系(双向)
1 2 3 4 5 6 7 8 #表名:friend { "_id" : ObjectId("6018bc055098b2230031e2da" ), "created" : NumberLong("1612233733056" ), "userId" : NumberLong("1" ), "friendId" : NumberLong("106" ), "_class" : "com.itheima.domain.mongo.Friend" }
4、圈子实现 3.1、环境搭建 Mongodb中实现字段的自增:两种解决方法(1、使用redis保证自动增长,2、使用mongodb自定义表)
3.1.1、mongo主键自增
第一步:创建实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.annotation.Id;import org.springframework.data.mongodb.core.mapping.Document;import org.springframework.data.mongodb.core.mapping.Field;@Document(collection = "sequence") @Data @AllArgsConstructor @NoArgsConstructor public class Sequence { private ObjectId id; private long seqId; private String collName; }
第二步:编写service
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 package com.tanhua.dubbo.utils;import com.tanhua.domain.mongo.Sequence;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.mongodb.core.FindAndModifyOptions;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import org.springframework.data.mongodb.core.query.Update;import org.springframework.stereotype.Component;@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(); } }
3.1.2、实体类 写到tanhua-domain工程中:
Movement Movement:发布信息表(总记录表数据)
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 package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.mongodb.core.mapping.Document;import java.util.List;@Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "movement") public class Movement implements java .io .Serializable { private ObjectId id; private Long pid; private Long created; private Long userId; private String textContent; private List<String> medias; private String longitude; private String latitude; private String locationName; private Integer state = 0 ; }
MovementTimeLine MovementTimeLine:好友时间线表,用于存储好友发布(或推荐)的数据,每一个用户一张表进行存储
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 package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.mongodb.core.mapping.Document;@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; private Long userId; private Long friendId; private Long created; }
Friend Friend 好友关系表
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 package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.mongodb.core.mapping.Document;@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; private Long friendId; private Long created; }
3.1.3、API接口 1 2 3 4 5 6 7 8 package com.tanhua.dubbo.api.mongo;import com.tanhua.domain.mongo.Publish;import com.tanhua.domain.vo.PageResult;public interface MovementApi {}
3.1.4、API实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.tanhua.dubbo.api.mongo;import com.tanhua.domain.mongo.*;import com.tanhua.domain.vo.PageResult;import com.tanhua.dubbo.utils.IdService;import org.apache.dubbo.config.annotation.Service;import org.bson.types.ObjectId;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import java.util.ArrayList;import java.util.List;@Service public class MovementApiImpl implements PublishApi {}
3.1.5、MovementsController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.tanhua.server.controller;import com.tanhua.domain.mongo.Publish;import com.tanhua.server.service.MovementsService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;@RestController @RequestMapping("/movements") public class MovementsController { @Autowired private MovementsService movementsService; }
3.1.6、MovementsService 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 package com.tanhua.server.service;import com.tanhua.autoconfig.templates.OssTemplate;import com.tanhua.domain.db.UserInfo;import com.tanhua.domain.mongo.Publish;import com.tanhua.domain.vo.Movements;import com.tanhua.domain.vo.PageResult;import com.tanhua.dubbo.api.UserInfoApi;import com.tanhua.dubbo.api.mongo.PublishApi;import com.tanhua.server.interceptor.UserHolder;import io.jsonwebtoken.lang.Collections;import org.apache.dubbo.config.annotation.Reference;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.*;@Service public class MovementsService { @Autowired private OssTemplate ossTemplate; @DubboReference private MovementApi movementApi; @DubboReference private UserInfoApi userInfoApi; @Autowired private RedisTemplate<String,String> redisTemplate; }
3.2、发布动态 后续的测试:使用106(13800138000)和1号用户(13500000000)
3.2.0、思路步骤
3.2.1、MovementsController tanhua-server
工程编写MovementsController,完成发布动态功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @RestController @RequestMapping("/movements") public class MovementController { @Autowired private MovementService movementService; @Autowired private CommentsService commentsService; @PostMapping public ResponseEntity movements (Movement movement, MultipartFile imageContent[]) throws IOException { movementService.publishMovement(movement,imageContent); return ResponseEntity.ok(null ); } }
3.2.2、编写service tanhua-server
工程编写MovementsService,完成发布动态功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void publishMovement (Movement movement, MultipartFile[] imageContent) throws IOException { if (StringUtils.isEmpty(movement.getTextContent())) { throw new BusinessException(ErrorResult.contentError()); } Long userId = UserHolder.getUserId(); List<String> medias = new ArrayList<>(); for (MultipartFile multipartFile : imageContent) { String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream()); medias.add(upload); } movement.setUserId(userId); movement.setMedias(medias); movementApi.publish(movement); }
3.2.3、API层 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 package com.tanhua.dubbo.api;import com.tanhua.dubbo.utils.IdWorker;import com.tanhua.dubbo.utils.TimeLineService;import com.tanhua.model.mongo.Movement;import com.tanhua.model.vo.PageResult;import org.apache.dubbo.config.annotation.DubboService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Sort;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import java.util.List;@DubboService public class MovementApiImpl implements MovementApi { @Autowired private MongoTemplate mongoTemplate; @Autowired private IdWorker idWorker; @Autowired private TimeLineService timeLineService; public void publish (Movement movement) { try { movement.setPid(idWorker.getNextId("movement" )); movement.setCreated(System.currentTimeMillis()); mongoTemplate.save(movement); timeLineService.saveTimeLine(movement.getUserId(),movement.getId()); } catch (Exception e) { e.printStackTrace(); } } }
3.2.4、异步处理工具类 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 @Component public class TimeLineService { @Autowired private MongoTemplate mongoTemplate; @Async public void saveTimeLine (Long userId, ObjectId movementId) { 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) { e.printStackTrace(); } 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); } } }
3.2.5、整合测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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); } }
3.3、查询个人动态 查询好友动态其实就是查询自己的时间线表,好友在发动态时已经将动态信息写入到了自己的时间线表中。
3.3.0、思路分析
3.3.1、vo对象 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 package com.tanhua.model.vo;import com.tanhua.model.domain.UserInfo;import com.tanhua.model.mongo.Movement;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.beans.BeanUtils;import org.springframework.util.StringUtils;import java.io.Serializable;import java.text.SimpleDateFormat;import java.util.Date;@Data @NoArgsConstructor @AllArgsConstructor public class MovementsVo implements Serializable { private String id; private Long userId; private String avatar; private String nickname; private String gender; private Integer age; private String[] tags; private String textContent; private String[] imageContent; private String distance; private String createDate; private Integer likeCount; private Integer commentCount; private Integer loveCount; private Integer hasLiked; private Integer hasLoved; 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; } }
3.3.2、controller 修改MovementsController
完成查询好友动态功能
1 2 3 4 5 6 7 8 9 10 @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); }
3.3.3、service 修改MovementsService
完成查询好友动态功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public PageResult findByUserId (Long userId, Integer page, Integer pagesize) { PageResult pr = movementApi.findByUserId(userId,page,pagesize); List<Movement> items = (List<Movement>) pr.getItems(); if (items == null ) { return pr; } UserInfo userInfo = userInfoApi.findById(userId); List<MovementsVo> vos = new ArrayList<>(); for (Movement item : items) { MovementsVo vo = MovementsVo.init(userInfo, item); vos.add(vo); } pr.setItems(vos); return pr; }
3.3.4、API层 修改PublishApi
和PublishApiImpl
完成查询好友动态功能
1 2 3 4 5 6 7 8 @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); }
3.3.5、测试