推荐好友列表
需求分析
推荐好友:分页形式查询推荐的用户列表,根据评分排序显示
代码实现:
tanhuaController:
/**
* 查询分页推荐好友列表
*/
@GetMapping("/recommendation")
public ResponseEntity recommendation(RecommendUserDto dto) {
PageResult pr = tanhuaService.recommendation(dto);
return ResponseEntity.ok(pr);
}
tanhuaService:
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、提取所有推荐的用户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对象 Long count=0L; 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); count++; } } pr.setCounts(count); //8、构造返回值 pr.setItems(list); log.info("最终推荐表:{}",pr); return pr; }
RecommendUserApiImpl
//分页查询
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).with(Sort.by(Sort.Order.desc("score"))).limit(pagesize)
.skip((page - 1) * pagesize);
//3、调用mongoTemplate查询
List<RecommendUser> list = mongoTemplate.find(query, RecommendUser.class);
long count = mongoTemplate.count(query, RecommendUser.class);
//4、构建返回值PageResult
return new PageResult(page,pagesize,count,list);
}
请求dto对象
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; //性别 man woman
private String lastLogin; //近期登陆时间
private Integer age; //年龄
private String city; //居住地
private String education; //学历
}
我们定义了一个userInfoApi的查询多个userinfo的功能,这样在查询出推荐用户表的时候,我们根据多个id在userInfoApi直接一次性查询到,而且可以增加年龄性别等条件,这样查询出再在controller层进行筛选
MongoDB集群
问题分析:
集群概述:
副本集群:多个服务器存储相同的数据保证可靠性。但是不能解决海量数据问题
分片集群
为了保证每个服务的高可用,需要服务配置副本集群,这里仅以单节点为例
路由服务如何获取文档的分片服务器位置呢?
分片集群:分片策略
圈子功能
需求分析
探花交友项目中的圈子功能,类似微信的朋友圈
发布动态
浏览好友、个人、推荐动态
动态互动如:点赞、评论、喜欢等
表结构设计
优点 :开发难度较小 易于理解
缺点 :动态对特定好友可见/不可见,实现难度较大 效率较低
优点 开发难度较小 可以完成所有业务功能
缺点 效率较低 索引空间占用
也就是,我们另外创建个时间线表,来存放动态表的id ,用户 及其好友
环境搭建
Mongodb中实现字段的自增:两种解决方法(1、使用redis保证自动增长,2、使用mongodb自定义表) redis在用户端,我们使用第二种
mongo主键自增
1.创建实体类
@Document(collection = "sequence")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Sequence {
private ObjectId id;
private long seqId; //自增序列
private String collName; //集合名称
}
2.编写service
@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();
}
}
实体类
Movement:发布信息表(总记录表数据)
@Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "movement") public class Movement implements java.io.Serializable { private ObjectId id; //主键id 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:驳回 }
MovementTimeLine:好友时间线表,用于存储好友发布(或推荐)的数据,每一个用户一张表进行存储
/**
* 好友时间线表,用于存储好友发布的数据
*/
@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; //发布的时间
}
Friend 好友关系表
/**
* 好友表:好友关系表
*/
@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; //时间
}
MovementApiImpl:
@DubboService public class MovementApiImpl implements MovementApi{ @Autowired private MongoTemplate mongoTemplate; @Autowired private IdWorker idWorker; @Override public void publish(Movement movement) { //1.保存动态详情 //设置PID和发布时间 movement.setPid(idWorker.getNextId("movement")); movement.setCreated(System.currentTimeMillis()); //id会自动生成 我们定义的时候用了ObjectId 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); log.info("friends:{}",friends); //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()); log.info("timeline:{}",timeLine); mongoTemplate.save(timeLine); } } }
MovementController
@RestController @RequestMapping("/movements") public class MovementController { @Autowired private MovementService movementService; /** * 发布动态 */ @PostMapping public ResponseEntity movements(Movement movement, MultipartFile imageContent[] ) throws IOException { movementService.publishMovement(movement,imageContent); return ResponseEntity.ok(null); } }
MovementService
@Service public class MovementService { @Autowired private OssTemplate ossTemplate; @DubboReference private MovementApi movementApi; /** * 发布动态 * @param movement * @param imageContent */ 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.将文件内容上传到阿里云,获取请求地址 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); } }
大量的时间线数据同步写入的问题如何解决?
我们在保存时间线数据的时候,用的循环保存,如果好友量特别大,那么进程就会特别慢
发布动态-异步处理
@Async: Spring提供的异步处理注解,被此注解标注的方法会在新的线程中执行,其实就相当于我们自己new Thread。
解决:
我们将需要新开线程的内容提出成一个类:
@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) {
e.printStackTrace();
}
//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);
}
}
}
我的动态