需求分析
和微信朋友圈类似,用户可以查看自己好友发布的动态。查询过程我们在表设计的时候就已经说过了,这里不再赘述。需要注意的是,这里也需要返回动态的VO对象。下面是接口:
api
根据用户id到时间线表中查询所有符合条件的动态id,注意这里查询的条件是friendId = 传递的用户id 好友的朋友是你
根据动态id查询到所有的动态
根据动态中的作者的用户id,到MySQL的UserInfo表中查询用户详情
最终将两部分封装成VO返回
查询好友动态时,我们可以根据好友动态时间线表查询
因为好友发表动态时都会在时间表存储数据,把好友信息,和发布的动态id存储
我们就可以根据自己的userID对应friendId查询出好友动态id
再根据动态id查询好友动态,封装数据返回
MovementsController
/**
* 查询好友动态
* GET /movements
*/
@GetMapping
public ResponseEntity findFriendMovement(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pageResult = movementsService.findFriendMovement(page, pagesize);
return ResponseEntity.ok(pageResult);
}
MovementsService
public PageResult findFriendMovement(Integer page, Integer pagesize) {
//根据用户id到时间线表中查询所有符合条件的动态id,注意这里查询的条件是friendId = 传递的用户id 好友的朋友是你
//查询当前用户id
Long userId = UserHolder.getUserId();
List<Movement> movementList = movementApi.findByFriendId(userId, page, pagesize);
// 3. 从集合中获取到发送动态的所有的好友id
//查看动态表中的数据是否为空,空的话返回一个空的集合
if (CollUtil.isEmpty(movementList)) {
return new PageResult();
}
//不为空通过动态表,去完成用户详情信息的封装
List<MovementsVo> voList = createMovementVo(movementList);
return getPageResult(page, pagesize, voList);
}
MovementApiImpl
// 根据时间线查询好友动态
@Override
public List<Movement> findByFriendId(Long userId, Integer page, Integer pagesize) {
Query query=Query.query(Criteria.where("friendId").is(userId))//好友的朋友是你
.skip((page-1)*pagesize)
.limit(pagesize)
.with(Sort.by(Sort.Order.desc("created")));//分页排序,mongodb
//根据时间线表中的friendid ==userId ,来查询动态id
List<MovementTimeLine> timeLineList = mongoTemplate.find(query, MovementTimeLine.class);
//根据时间线集合中的movementId来查询集合中的动态id 注意类型的转化
List<ObjectId> movementIds = CollUtil.getFieldValues(timeLineList, "movementId", ObjectId.class);
//通过动态id,查询动态表中的数据
Query qw =Query.query(Criteria.where("id").in(movementIds));
List<Movement> movementList = mongoTemplate.find(qw, Movement.class);
return movementList;
}
createMovementVo,查询出用户的详情信息,通过封装vo
private List<MovementsVo> createMovementVo(List<Movement> movementList) {
if (CollUtil.isEmpty(movementList)) {
// 如果为动态为空,那么说明已经到底了 那么直接返回一个空的vo集合即可
List<MovementsVo> list = new ArrayList<>();
return list;
} else {
List<Long> friendIds = CollUtil.getFieldValues(movementList, "userId", Long.class);
// 4. 根据id查询好友的详细信息,得到一个集合
//getUserInfoByIds写过此方法
Map<Long, UserInfo> friendUserInfo = this.userInfoApi.getUserInfoByIds(friendIds, null);
// 5. 遍历动态集合,封装vo
List<MovementsVo> voList = new ArrayList<>();
for (Movement movement : movementList) {
// 获取到动态发布者的id
Long friendId = movement.getUserId();
UserInfo userInfo = friendUserInfo.get(friendId);
if (userInfo != null) {
//内部方法进行封装,转换数据类型
MovementsVo init = MovementsVo.init(userInfo, movement);
voList.add(init);
}
}
return voList;
}
}
查询推荐动态
需求分析
基于大数据,探花交友APP会根据用户的喜欢等信息推荐出用户可能感兴趣的动态。具体推荐由大数据系统负责,我们不需要关心。我们只需要知道,推荐信息保存在Redis中,保存的形式是如下:
采用String类型保存数据
Key为MOVEMENTS_RECOMMEND_用户id
Value为动态的pid,多个pid之间用逗号隔开
因此我们的业务流程如下:
前端传来请求,携带分页参数
后端获取到用户id,根据用户id到Redis中查询推荐信息。如果在Redis中查询不到推荐信息,则从数据库中随机生成pageSize个动态。
然后根据pid进行分页,获取当期页的所有pid
根据pid获取到动态集合,并且根据动态中的作者信息,获取到动态的作者详情
封装VO
api参数
分析:
查询推荐动态时.我们要先在redis中查询推荐动态的pid
如果redis有数据我们就要解析数据("16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067")
利用stream流把数据分成一段段的列表
如果redis中没有数据就在数据库构造十条(随机构造)
构造十条数据时我们要用到TypedAggregation对象
代码实现
问题1:需要掌握以下两个问题,第一个是我们从Redis中获取到的推荐的pid是一个字符串,如何实现分页?
我们可以split方法先转化成数组,然后利用Stream流中的skip()和limit()方法实现分页 java8中的新特性
如何从MongoDB中随机获取指定数量的动态
利用MongoDB中的聚合函数。Aggregation.smple()设置随机采样数
MovementsController
/**
* 推荐动态
* GET /movements/recommend
*/
@GetMapping("/recommend")
public ResponseEntity findRecommendMovement(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pageResult = movementsService.findRecommendMovement(page, pagesize);
return ResponseEntity.ok(pageResult);
}
MovementsService
public PageResult findRecommendMovement(Integer page, Integer pagesize) {
//从redis中获取数据
//String redisKey="MOVEMENTS_RECOMMEND_"+UserHolder.getUserId();
//根据用户Id 到redis中查询数据 判断推荐信息是否存在
String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
//判断数据是否存在
List<Movement> list = Collections.EMPTY_LIST;//构建空集合
if (StringUtils.isEmpty(redisValue)) {
//如果不存在调用api构造十条数据,则随机生成记录
list = movementApi.randomMonements(pagesize);
} else {
//如果存在处理pid数据
String[] pids = redisValue.split(",");
if ((page - 1) * pagesize < pids.length) {
List<Long> pids = Arrays.stream(pids)
.skip((page - 1) * pagesize)
.limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
list = movementApi.findMovementByPid(pids);
}
}
// 4. 封装vo
List<MovementsVo> movementsVoList = createMovementVo(movementList);
// // 5. 封装返回值
return new PageResult(page, pagesize, 0, movementsVoList)
}
MovementApiImpl
@Override
public List<Movement> randomMonements(Integer pagesize) {
//创建通缉对象
TypedAggregation aggregation= Aggregation.newAggregation(Movement.class,Aggregation.sample(pagesize));
//调用mongoTemepalte统计
AggregationResults<Movement> results = mongoTemplate.aggregate(aggregation, Movement.class);
return results.getMappedResults();
}
@Override
public List<Movement> findMovementByPid(List<Long> pids) {
Criteria criteria=Criteria.where("pid").in(pids);
Query query=Query.query(criteria);
List<Movement> list = mongoTemplate.find(query, Movement.class);
return list;
}
import cn.hutool.core.collection.CollUtil;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import com.tanhua.autoconfig.template.OssTemplate;
import com.tanhua.dubbo.api.CommentApi;
import com.tanhua.dubbo.api.MovementApi;
import com.tanhua.dubbo.api.UserInfoApi;
import com.tanhua.dubbo.api.VisitorsApi;
import com.tanhua.model.domain.UserInfo;
import com.tanhua.model.enums.CommentType;
import com.tanhua.model.mongo.Comment;
import com.tanhua.model.mongo.Movement;
import com.tanhua.model.mongo.Visitors;
import com.tanhua.model.vo.ErrorResult;
import com.tanhua.model.vo.MovementsVo;
import com.tanhua.model.vo.PageResult;
import com.tanhua.model.vo.VisitorsVo;
import com.tanhua.server.exception.BusinessException;
import com.tanhua.server.interceptor.UserHolder;
import com.tanhua.utils.Constants;
import org.apache.dubbo.config.annotation.DubboReference;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class MovementService {
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private MovementApi movementApi;
@DubboReference
private UserInfoApi userInfoApi;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@DubboReference
private CommentApi commentApi;
@DubboReference
private VisitorsApi visitorsApi;
/**
* 当前用户发布动态
*
* @param movement
* @param imageContent
*/
public void movements(Movement movement, MultipartFile[] imageContent) throws IOException {
//判断用户是否输入文本信息
if (StringUtils.isEmpty(movement.getTextContent())) {
throw new BusinessException(ErrorResult.disloveError());
}
//获取当前用户id
Long userId = UserHolder.getUserId();
//将图片存入oss中
List<String> medias = new ArrayList<>();
for (MultipartFile multipartFile : imageContent) {
String url = ossTemplate.uploadMovement(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), userId);
medias.add(url);
}
movement.setMedias(medias);
movement.setUserId(userId);
//调用mongoDbApi存入数据并存入时间线表
movementApi.publishMovements(movement);
}
/**
* 查询当前用户id的所有动态
*
* @param userId
* @param page
* @param pagesize
* @return
*/
public PageResult findByIdMovements(Long userId, int page, int pagesize) {
//1.调用mongoApi查询当前用户的所有动态信息
PageResult pageResult = movementApi.findById(userId, page, pagesize);
List<Movement> items = (List<Movement>) pageResult.getItems();
//如果没有数据直接返回pageResult对象
if (items == null || items.size() <= 0) {
return pageResult;
}
//根据id查询用户详情信息
UserInfo userInfo = userInfoApi.findById(userId);
List<MovementsVo> list = new ArrayList<>();
for (Movement item : items) {
MovementsVo movementsVo = MovementsVo.init(userInfo, item);
list.add(movementsVo);
}
pageResult.setItems(list);
return pageResult;
}
/**
* 查询当前用户所有好友动态
* @param page
* @param pagesize
* @return
*/
public PageResult findByFriendMovementAll(int page, int pagesize) {
//获取当前用户id
Long userId = UserHolder.getUserId();
//调用mongoApi接口获取所有好友的动态信息
List<Movement> movementList = movementApi.findByIdFriendMovementAll(userId, page, pagesize);
return getPageResult(page,pagesize,movementList);
}
/**
* 查询当前用户推荐好友的所有动态
* @param page
* @param pagesize
* @return
*/
public PageResult findByRecommendM(int page, int pagesize) {
Long userId = UserHolder.getUserId();
//- 从redis获取当前用户的推荐PID列表
String redisValue = redisTemplate.opsForValue().get(Constants.MOVEMENTS_RECOMMEND + userId);
List<Movement> movementList = Collections.EMPTY_LIST;
if (StringUtils.isEmpty(redisValue)){
//- 如果不存在,调用API随机获取10条动态数据
movementList = movementApi.randomRecommendM(pagesize);
}else {
//- 如果存在,调用API根据PID列表查询动态数据
//- 构造VO对象
String[] split = redisValue.split(",");
if ((page - 1) * pagesize < split.length ){
List<Long> pidList = Arrays.stream(split).skip((page - 1) * pagesize).limit(pagesize)
.map(item->Long.valueOf(item))
.collect(Collectors.toList());
//调用Api查询推荐好友的所有动态
movementList = movementApi.findMovementByPid(pidList);
}
}
return getPageResult(page,pagesize,movementList);
}
//获取PageResult对象
private PageResult getPageResult(int page, int pagesize, List<Movement> movementList) {
if (CollUtil.isEmpty(movementList)){
return new PageResult();
}
Set<Long> userIds = movementList.stream().map(item -> item.getUserId()).collect(Collectors.toSet());
Map<Long, UserInfo> map = userInfoApi.findByIds(new UserInfo(), new ArrayList<>(userIds));
List<MovementsVo> vos = new ArrayList<>();
for (Movement movement : movementList) {
UserInfo userInfo = map.get(movement.getUserId());
if (userInfo != null){
MovementsVo vo = MovementsVo.init(userInfo, movement);
//从Redis中判断是否点赞过该评论
String Key = Constants.MOVEMENTS_INTERACT_KEY + movement.getId().toHexString();
String likeHashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
if (redisTemplate.opsForHash().get(Key, likeHashKey) != null){
vo.setHasLiked(1);
}
//从Redis中判断是否喜欢过该评论
String loveHashKey = Constants.MOVEMENT_LOVE_HASHKEY + movement.getId().toHexString();
if (redisTemplate.opsForHash().get(Key, loveHashKey) != null){
vo.setHasLoved(1);
}
vos.add(vo);
}
}
return new PageResult(page,pagesize,0,vos);
}
/**
* 根据动态id获取单条动态
* @param movementId
* @return
*/
public MovementsVo findMovement(String movementId) {
//调用API获取当前动态id的所有动态
Movement movement = movementApi.findMovement(movementId);
if (movement == null){
return null;
}
UserInfo userInfo = userInfoApi.findById(movement.getUserId());
return MovementsVo.init(userInfo, movement);
}
/**
* 动态-点赞
* @param movementId
* @return
*/
public Integer saveLike(String movementId) {
//判断是否点赞过该评论
boolean exists = commentApi.findComment(movementId, CommentType.LIKE,UserHolder.getUserId());
if (exists){
throw new BusinessException(ErrorResult.likeError());
}
//调用API获取点赞总数
Comment comment = new Comment();
comment.setPublishId(new ObjectId(movementId));
comment.setCommentType(CommentType.LIKE.getType());
comment.setUserId(UserHolder.getUserId());
comment.setCreated(System.currentTimeMillis());
Integer countLikes = commentApi.saveComments(comment);
//4、拼接redis的key,将用户的点赞状态存入redis
String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
redisTemplate.opsForHash().put(key,hashKey,"1");
return countLikes;
}
/**
* 动态取消点赞
* @param movementId
* @return
*/
public Integer disLike(String movementId) {
boolean exists = commentApi.findComment(movementId, CommentType.LIKE, UserHolder.getUserId());
if (!exists){
throw new BusinessException(ErrorResult.disLikeError());
}
//调用API获取点赞总数
Comment comment = new Comment();
comment.setPublishId(new ObjectId(movementId));
comment.setCommentType(CommentType.LIKE.getType());
comment.setUserId(UserHolder.getUserId());
Integer countLikes = commentApi.delete(comment);
//删除redis中的点赞标记
String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
redisTemplate.opsForHash().delete(key,hashKey);
return countLikes;
}
/**
* 动态-喜欢
* @param movementId
* @return
*/
public Integer saveLove(String movementId) {
//判断是否喜欢过该评论
boolean exists = commentApi.findComment(movementId,CommentType.LOVE,UserHolder.getUserId());
if (exists){
throw new BusinessException(ErrorResult.likeError());
}
//调用API获取喜欢总数
Comment comment = new Comment();
comment.setPublishId(new ObjectId(movementId));
comment.setCommentType(CommentType.LOVE.getType());
comment.setUserId(UserHolder.getUserId());
comment.setCreated(System.currentTimeMillis());
Integer countLikes = commentApi.saveComments(comment);
//4、拼接redis的key,将用户的喜欢状态存入redis
String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
String hashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
redisTemplate.opsForHash().put(key,hashKey,"1");
return countLikes;
}
/**
* 动态-取消喜欢
* @param movementId
* @return
*/
public Integer unlove(String movementId) {
boolean exists = commentApi.findComment(movementId, CommentType.LOVE, UserHolder.getUserId());
if (!exists){
throw new BusinessException(ErrorResult.disLikeError());
}
//调用API获取喜欢总数
Comment comment = new Comment();
comment.setPublishId(new ObjectId(movementId));
comment.setCommentType(CommentType.LOVE.getType());
comment.setUserId(UserHolder.getUserId());
Integer countLikes = commentApi.delete(comment);
//删除redis中的喜欢标记
String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
String hashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
redisTemplate.opsForHash().delete(key,hashKey);
return countLikes;
}
/**
* 谁看过我
* @return
*/
public List<VisitorsVo> visitors() {
//查询访问的时间
String key = Constants.VISITORS_USER;
String hashKey = UserHolder.getUserId().toString();
String value = (String) redisTemplate.opsForHash().get(key, hashKey);
Long data = StringUtils.isEmpty(value)?null:Long.valueOf(value);
//调用API判断今天看过我的用户
List<Visitors> list = visitorsApi.lookVisitors(data,UserHolder.getUserId());
if (CollUtil.isEmpty(list)){
return new ArrayList<>();
}
//提取用户id
List<Long> ids = list.stream().map(item -> item.getVisitorUserId()).collect(Collectors.toList());
//调用APi获取用户信息
Map<Long, UserInfo> map = userInfoApi.findByIds(null, ids);
List<VisitorsVo> vos = new ArrayList<>();
for (Visitors visitors : list) {
UserInfo userInfo = map.get(visitors.getVisitorUserId());
if (userInfo != null){
VisitorsVo vo = VisitorsVo.init(userInfo, visitors);
vos.add(vo);
}
}
return vos;
}
}