探花交友_第10章_实现推荐功能

文章目录

  • 探花交友_第10章_实现推荐功能
  • 1、了解推荐系统
    • 1.1、什么是推荐系统?
    • 1.2、电商是推荐系统的先行者
    • 1.3、推荐系统业务流程
    • 1.4、协同过滤推荐算法
      • 1.4.1、基于用户的推荐 UserCF
      • 1.4.2、基于商品的推荐 ItemCF
    • 1.5、ALS算法
  • 2、好友推荐
    • 2.1、流程
    • 2.2、部署好友推荐服务
  • 3、圈子推荐
    • 3.1、功能说明
    • 3.2、流程说明
    • 3.3、动态计分规则
    • 3.4、发送消息
      • 3.4.1、QuanziMQService
      • 3.4.2、修改QuanZiService
    • 3.5、接收消息
      • 3.5.1、创建my-tanhua-recommend工程
      • 3.5.2、配置文件
      • 3.5.3、启动类
      • 3.5.4、RecommendQuanZi
      • 3.5.5、QuanZiMsgConsumer
    • 3.7、测试
  • 4、部署推荐系统
  • 5、小视频推荐
    • 5.1、动态计分规则
    • 5.2、发送消息
      • 5.2.1、VideoMQService
      • 4.3.2、VideoService
    • 5.3、接收消息
      • 5.3.1、RecommendVideo
      • 5.3.2、VideoMsgConsumer
      • 5.3.3、测试
    • 5.4、部署推荐服务
  • 了解推荐系统
  • 实现好友的推荐
  • 圈子推荐功能说明
  • 圈子推荐功能流程
  • 圈子推荐功能的实现
  • 小视频推荐功能的实现

1、了解推荐系统

1.1、什么是推荐系统?

为了解决信息过载和用户无明确需求的问题,找到用户感兴趣的物品,才有了个性化推荐系统。

其实,解决信息过载的问题,代表性的解决方案是分类目录和搜索引擎,如hao123,电商首页的分类目录以及百度,360搜索等。

不过分类目录和搜索引擎只能解决用户主动查找信息的需求,即用户知道自己想要什么,并不能解决用户没用明确需求很随便的问题。

经典语录是:你想吃什么,随便!面对这种很随便又得罪不起的用户(女友和上帝),只能通过分析用户的历史行为给用户的兴趣建模,从而主动给用户推荐能够满足他们兴趣和需求的信息。比如问问女友的闺蜜,她一般什么时候喜欢吃什么。

1.2、电商是推荐系统的先行者

  • 电子商务网站是个性化推荐系统重要地应用的领域之一,亚马逊就是个性化推荐系统的积极应用者和推广者,亚马逊的推荐系统深入到网站的各类商品,为亚马逊带来了至少30%的销售额。
  • 不光是电商类,推荐系统无处不在。QQ,微信的好友推荐;新浪微博的你可能感兴趣的人;优酷,土豆的电影推荐;豆瓣的图书推荐;大从点评的餐饮推荐;脉脉的同事推荐等。
  • 推荐引擎的鼻祖思想源泉:http://portal.acm.org/citation.cfm?id=1070751
  • 亚马逊最早提出基亍物品的协同过滤推荐算法:http://portal.acm.org/citation.cfm?id=372071

京东的推荐:

1.3、推荐系统业务流程

推荐系统广泛存在于各类网站中,作为一个应用为用户提供个性化的推荐。它需要一些用户的历史数据,一般由三个部分组成:基础数据、推荐算法系统、前台展示。

  • 基础数据包括很多维度,包括用户的访问、浏览、下单、收藏,用户的历史订单信息,评价信息等很多信息;
  • 推荐算法系统主要是根据不同的推荐诉求由多个算法组成的推荐模型;
  • 前台展示主要是对客户端系统进行响应,返回相关的推荐信息以供展示。

1.4、协同过滤推荐算法

迄今为止,在个性化推荐系统中,协同过滤技术是应用最成功的技术。目前国内外有许多大型网站应用这项技术为用户更加智能(个性化、千人千面)的推荐内容。

核心思想:

协同过滤一般是在海量的用户中发掘出一小部分和你品位比较类似的,在协同过滤中,这些用户成为邻居,然后根据他们喜欢的其他东西组织成一个排序的目彔作为推荐给你。

1.4.1、基于用户的推荐 UserCF


对于用户A,根据用户的历史偏好,这里只计算得到一个邻居–用户C,然后将用户C 喜欢的物品D 推荐给用户A。

基于用户的协同过滤算法先计算的是用户与用户的相似度(兴趣相投,物以类聚人以群分),然后将相似度比较接近的用户A购买的物品推荐给用户B,专业的说法是该算法用最近邻居(nearest-neighbor)算法找出一个用户的邻居集合,该集合的用户和该用户有相似的喜好,算法根据邻居的偏好对该用户进行预测。

1.4.2、基于商品的推荐 ItemCF

  • 基于ItemCF的原理和基于UserCF类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。
  • 从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。
  • 解释:对于物品A,根据所有用户的历史偏好,喜欢物品A 的用户都喜欢物品C,得出物品A 和物品C 比较相似,而用户C 喜欢物品A,那么可以推断出用户C 可能也喜欢物品C。

1.5、ALS算法

ALS 是交替最小二乘 (alternating least squares)的简称。在机器学习的上下文中,ALS 特指使用交替最小二乘求解的一个协同推荐算法。它通过观察到的所有用户给产品的打分,来推断每个用户的喜好并向用户推荐适合的产品。从协同过滤的分类来说,ALS算法属于User-Item CF,也叫做混合CF。它同时考虑了User和Item两个方面。

用户和商品的关系,可以抽象为如下的三元组:<User,Item,Rating>。其中,Rating是用户对商品的评分,表征用户对该商品的喜好程度。如下:

196  242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
166 346 1   886397596
298 474 4   884182806
115 265 2   881171488
253 465 5   891628467
305 451 3   886324817
6   86  3   883603013
62  257 2   879372434
286 1014    5   879781125
200 222 5   876042340
210 40  3   891035994
................

2、好友推荐

对于好友的推荐,需要找出每个用户之间的相似性,具体规则如下:

字段 权重分
年龄差 0-2岁 30分 3-5 20分 5-10岁 10分 10岁以上 0分
性别 异性 30分 同性 0分
位置 同城 20分 不同 0分
学历 相同 20分 不同 0分

2.1、流程

2.2、部署好友推荐服务

#拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-recommend-user:1.0.1#创建容器
docker create --name tanhua-spark-recommend-user \
--env MONGODB_HOST=192.168.31.81 \
--env MONGODB_PORT=27017 \
--env MONGODB_USERNAME=tanhua \
--env MONGODB_PASSWORD=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV \
--env MONGODB_DATABASE=tanhua \
--env MONGODB_COLLECTION=recommend_user \
--env JDBC_URL="jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false" \
--env JDBC_DRIVER=com.mysql.jdbc.Driver \
--env JDBC_USER=root \
--env JDBC_PASSWORD=root \
--env JDBC_TABLE=tb_user_info \
--env SCHEDULE_PERIOD=30 \
registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-recommend-user:1.0.1#参数说明
#MONGODB_HOST mongodb服务的地址
#MONGODB_PORT mongodb服务的端口
#MONGODB_USERNAME mongodb服务的认证用户名
#MONGODB_PASSWORD mongodb服务的认证密码
#MONGODB_DATABASE mongodb连接的数据库
#MONGODB_COLLECTION 操作表
#JDBC_URL  mysql数据库连接地址
#JDBC_DRIVER  jdbc驱动
#JDBC_USER  数据库连接用户名
#JDBC_PASSWORD  数据库连接密码
#JDBC_TABLE  数据库表名
#SCHEDULE_PERIOD  下次执行时间间隔,但是为分,默认为10分钟#启动服务
docker start tanhua-spark-recommend-user
#查看日志
docker logs -f tanhua-spark-recommend-user

执行完成后,可以看到MongoDB中的recommend_user表中数据已经重新生成了。

3、圈子推荐

3.1、功能说明

在圈子功能中,针对于用户发布的动态信息,系统可以根据用户的发布、浏览、点赞等操作,对动态信息做计算,然后对每个用户进行不同的推荐。

3.2、流程说明

流程说明:

  • 用户对圈子的动态操作,如:发布、浏览、点赞、喜欢等,就会给RocketMQ进行发送消息;
  • 推荐系统接收消息,并且处理消息数据,处理之后将结果数据写入到MongoDB中;
  • Spark系统拉取数据,然后进行推荐计算;
  • 计算之后的结果数据写入到Redis中,为每个用户都进行个性化推荐;

3.3、动态计分规则

  • 浏览 +1
  • 点赞 +5
  • 喜欢 +8
  • 评论 + 10
  • 发布动态
    • 文字长度:50以内1分,50~100之间2分,100以上3分
    • 图片个数:每个图片一分

核心推荐逻辑:

  • 推荐模型:用户 | 动态 | 评分
  • 其中,评分是用户对动态操作的得分合计
  • 为什么自己发布动态还要计分? 是因为,自己发布就相当于自己对此动态也感兴趣,这样就可以在相似的人之间进行推荐了。

3.4、发送消息

3.4.1、QuanziMQService

my-tanhua-server增加依赖:

<!--RocketMQ相关-->
<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId>
</dependency>

配置文件:

# RocketMQ相关配置
rocketmq.name-server=192.168.31.81:9876
rocketmq.producer.group=tanhua
package com.tanhua.server.service;import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.common.pojo.User;
import com.tanhua.common.utils.UserThreadLocal;
import com.tanhua.dubbo.server.api.QuanZiApi;
import com.tanhua.dubbo.server.pojo.Publish;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
@Slf4j
public class QuanziMQService {@Autowiredprivate RocketMQTemplate rocketMQTemplate;@Reference(version = "1.0.0")private QuanZiApi quanZiApi;/*** 发布动态消息** @param publishId* @return*/public Boolean publishMsg(String publishId) {return this.sendMsg(publishId, 1);}/*** 浏览动态消息** @param publishId* @return*/public Boolean queryPublishMsg(String publishId) {return this.sendMsg(publishId, 2);}/*** 点赞动态消息** @param publishId* @return*/public Boolean likePublishMsg(String publishId) {return this.sendMsg(publishId, 3);}/*** 取消点赞动态消息** @param publishId* @return*/public Boolean disLikePublishMsg(String publishId) {return this.sendMsg(publishId, 6);}/*** 喜欢动态消息** @param publishId* @return*/public Boolean lovePublishMsg(String publishId) {return this.sendMsg(publishId, 4);}/*** 取消喜欢动态消息** @param publishId* @return*/public Boolean disLovePublishMsg(String publishId) {return this.sendMsg(publishId, 7);}/*** 评论动态消息** @param publishId* @return*/public Boolean commentPublishMsg(String publishId) {return this.sendMsg(publishId, 5);}/*** 发送圈子操作相关的消息** @param publishId* @param type      1-发动态,2-浏览动态, 3-点赞, 4-喜欢, 5-评论,6-取消点赞,7-取消喜欢* @return*/private Boolean sendMsg(String publishId, Integer type) {try {User user = UserThreadLocal.get();Publish publish = this.quanZiApi.queryPublishById(publishId);//构建消息Map<String, Object> msg = new HashMap<>();msg.put("userId", user.getId());msg.put("date", System.currentTimeMillis());msg.put("publishId", publishId);msg.put("pid", publish.getPid());msg.put("type", type);this.rocketMQTemplate.convertAndSend("tanhua-quanzi", msg);} catch (Exception e) {log.error("发送消息失败! publishId = " + publishId + ", type = " + type, e);return false;}return true;}
}

3.4.2、修改QuanZiService

在QuanZiService完成发送消息方法调用。

package com.tanhua.server.service;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.common.pojo.User;
import com.tanhua.common.pojo.UserInfo;
import com.tanhua.common.service.PicUploadService;
import com.tanhua.common.utils.RelativeDateFormat;
import com.tanhua.common.utils.UserThreadLocal;
import com.tanhua.common.vo.PicUploadResult;
import com.tanhua.dubbo.server.api.QuanZiApi;
import com.tanhua.dubbo.server.api.VisitorsApi;
import com.tanhua.dubbo.server.pojo.Comment;
import com.tanhua.dubbo.server.pojo.Publish;
import com.tanhua.dubbo.server.pojo.Visitors;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.vo.CommentVo;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.QuanZiVo;
import com.tanhua.server.vo.VisitorsVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.util.*;@Service
public class QuanZiService {@Reference(version = "1.0.0")private QuanZiApi quanZiApi;@Reference(version = "1.0.0")private VisitorsApi visitorsApi;@Autowiredprivate UserService userService;@Autowiredprivate UserInfoService userInfoService;@Autowiredprivate PicUploadService picUploadService;@Autowiredprivate QuanziMQService quanziMQService;public PageResult queryPublishList(Integer page, Integer pageSize) {//分析:通过dubbo中的服务查询用户的好友动态//通过mysql查询用户的信息,回写到结果对象中(QuanZiVo)PageResult pageResult = new PageResult();pageResult.setPage(page);pageResult.setPagesize(pageSize);//直接从ThreadLocal中获取对象User user = UserThreadLocal.get();//通过dubbo查询数据PageInfo<Publish> pageInfo = this.quanZiApi.queryPublishList(user.getId(), page, pageSize);List<Publish> records = pageInfo.getRecords();if (CollUtil.isEmpty(records)) {return pageResult;}pageResult.setItems(this.fillQuanZiVo(records));return pageResult;}/*** 填充用户信息** @param userInfo* @param quanZiVo*/private void fillUserInfoToQuanZiVo(UserInfo userInfo, QuanZiVo quanZiVo) {BeanUtil.copyProperties(userInfo, quanZiVo, "id");quanZiVo.setGender(userInfo.getSex().name().toLowerCase());quanZiVo.setTags(StringUtils.split(userInfo.getTags(), ','));//当前用户User user = UserThreadLocal.get();quanZiVo.setCommentCount(0); //TODO 评论数quanZiVo.setDistance("1.2公里"); //TODO 距离quanZiVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), quanZiVo.getId()) ? 1 : 0); //是否点赞(1是,0否)quanZiVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(quanZiVo.getId()))); //点赞数quanZiVo.setHasLoved(this.quanZiApi.queryUserIsLove(user.getId(), quanZiVo.getId()) ? 1 : 0); //是否喜欢(1是,0否)quanZiVo.setLoveCount(Convert.toInt(this.quanZiApi.queryLoveCount(quanZiVo.getId()))); //喜欢数}/*** 根据查询到的publish集合填充QuanZiVo对象** @param records* @return*/private List<QuanZiVo> fillQuanZiVo(List<Publish> records) {List<QuanZiVo> quanZiVoList = new ArrayList<>();records.forEach(publish -> {QuanZiVo quanZiVo = new QuanZiVo();quanZiVo.setId(publish.getId().toHexString());quanZiVo.setTextContent(publish.getText());quanZiVo.setImageContent(publish.getMedias().toArray(new String[]{}));quanZiVo.setUserId(publish.getUserId());quanZiVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));quanZiVoList.add(quanZiVo);});//查询用户信息List<Object> userIds = CollUtil.getFieldValues(records, "userId");List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);for (QuanZiVo quanZiVo : quanZiVoList) {//找到对应的用户信息for (UserInfo userInfo : userInfoList) {if (quanZiVo.getUserId().longValue() == userInfo.getUserId().longValue()) {this.fillUserInfoToQuanZiVo(userInfo, quanZiVo);break;}}}return quanZiVoList;}/*** 发布动态** @param textContent* @param location* @param latitude* @param longitude* @param multipartFile* @return*/public String savePublish(String textContent,String location,String latitude,String longitude,MultipartFile[] multipartFile) {//查询当前的登录信息User user = UserThreadLocal.get();Publish publish = new Publish();publish.setUserId(user.getId());publish.setText(textContent);publish.setLocationName(location);publish.setLatitude(latitude);publish.setLongitude(longitude);publish.setSeeType(1);List<String> picUrls = new ArrayList<>();//图片上传for (MultipartFile file : multipartFile) {PicUploadResult picUploadResult = this.picUploadService.upload(file);picUrls.add(picUploadResult.getName());}publish.setMedias(picUrls);String publishId = this.quanZiApi.savePublish(publish);if(StrUtil.isNotEmpty(publishId)){//发送消息this.quanziMQService.publishMsg(publishId);}return publishId;}public PageResult queryRecommendPublishList(Integer page, Integer pageSize) {//分析:通过dubbo中的服务查询系统推荐动态//通过mysql查询用户的信息,回写到结果对象中(QuanZiVo)PageResult pageResult = new PageResult();pageResult.setPage(page);pageResult.setPagesize(pageSize);//直接从ThreadLocal中获取对象User user = UserThreadLocal.get();//通过dubbo查询数据PageInfo<Publish> pageInfo = this.quanZiApi.queryRecommendPublishList(user.getId(), page, pageSize);List<Publish> records = pageInfo.getRecords();if (CollUtil.isEmpty(records)) {return pageResult;}pageResult.setItems(this.fillQuanZiVo(records));return pageResult;}/*** 动态点赞* @param publishId* @return*/public Long likeComment(String publishId) {User user = UserThreadLocal.get();Boolean result = this.quanZiApi.likeComment(user.getId(), publishId);if (result) {//发消息this.quanziMQService.likePublishMsg(publishId);//查询点赞数return this.quanZiApi.queryLikeCount(publishId);}return null;}/*** 动态的取消点赞** @param publishId* @return*/public Long disLikeComment(String publishId) {User user = UserThreadLocal.get();Boolean result = this.quanZiApi.disLikeComment(user.getId(), publishId);if (result) {//发消息this.quanziMQService.disLikePublishMsg(publishId);//查询点赞数return this.quanZiApi.queryLikeCount(publishId);}return null;}public Long loveComment(String publishId) {User user = UserThreadLocal.get();//喜欢Boolean result = this.quanZiApi.loveComment(user.getId(), publishId);if(result){//发消息this.quanziMQService.lovePublishMsg(publishId);//查询喜欢数return this.quanZiApi.queryLoveCount(publishId);}return null;}public Long disLoveComment(String publishId) {User user = UserThreadLocal.get();//取消喜欢Boolean result = this.quanZiApi.disLoveComment(user.getId(), publishId);if(result){//发消息this.quanziMQService.disLovePublishMsg(publishId);//查询喜欢数return this.quanZiApi.queryLoveCount(publishId);}return null;}public QuanZiVo queryById(String publishId) {Publish publish = this.quanZiApi.queryPublishById(publishId);if (publish == null) {return null;}//发消息this.quanziMQService.queryPublishMsg(publishId);return this.fillQuanZiVo(Arrays.asList(publish)).get(0);}/*** 查询评论列表** @param publishId* @param page* @param pageSize* @return*/public PageResult queryCommentList(String publishId, Integer page, Integer pageSize) {PageResult pageResult = new PageResult();pageResult.setPage(page);pageResult.setPagesize(pageSize);User user = UserThreadLocal.get();//查询评论列表数据PageInfo<Comment> pageInfo = this.quanZiApi.queryCommentList(publishId, page, pageSize);List<Comment> records = pageInfo.getRecords();if(CollUtil.isEmpty(records)){return pageResult;}//查询用户信息List<Object> userIdList = CollUtil.getFieldValues(records, "userId");List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIdList);List<CommentVo> result = new ArrayList<>();for (Comment record : records) {CommentVo commentVo = new CommentVo();commentVo.setContent(record.getContent());commentVo.setId(record.getId().toHexString());commentVo.setCreateDate(DateUtil.format(new Date(record.getCreated()), "HH:mm"));//是否点赞commentVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), commentVo.getId()) ? 1 : 0);//点赞数commentVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(commentVo.getId())));for (UserInfo userInfo : userInfoList) {if(ObjectUtil.equals(record.getUserId(), userInfo.getUserId())){commentVo.setAvatar(userInfo.getLogo());commentVo.setNickname(userInfo.getNickName());break;}}result.add(commentVo);}pageResult.setItems(result);return pageResult;}/*** 发表评论* @param publishId* @param content* @return*/public Boolean saveComments(String publishId, String content) {User user = UserThreadLocal.get();Boolean result = this.quanZiApi.saveComment(user.getId(), publishId, content);if(result){//发消息this.quanziMQService.commentPublishMsg(publishId);}return result;}public PageResult queryAlbumList(Long userId, Integer page, Integer pageSize) {PageResult pageResult = new PageResult();pageResult.setPage(page);pageResult.setPagesize(pageSize);//查询数据PageInfo<Publish> pageInfo = this.quanZiApi.queryAlbumList(userId, page, pageSize);if(CollUtil.isEmpty(pageInfo.getRecords())){return pageResult;}//填充数据pageResult.setItems(this.fillQuanZiVo(pageInfo.getRecords()));return pageResult;}public List<VisitorsVo> queryVisitorsList() {User user = UserThreadLocal.get();List<Visitors> visitorsList = this.visitorsApi.queryMyVisitor(user.getId());if (CollUtil.isEmpty(visitorsList)) {return Collections.emptyList();}List<Object> userIds = CollUtil.getFieldValues(visitorsList, "visitorUserId");List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);List<VisitorsVo> visitorsVoList = new ArrayList<>();for (Visitors visitor : visitorsList) {for (UserInfo userInfo : userInfoList) {if (ObjectUtil.equals(visitor.getVisitorUserId(), userInfo.getUserId())) {VisitorsVo visitorsVo = new VisitorsVo();visitorsVo.setAge(userInfo.getAge());visitorsVo.setAvatar(userInfo.getLogo());visitorsVo.setGender(userInfo.getSex().name().toLowerCase());visitorsVo.setId(userInfo.getUserId());visitorsVo.setNickname(userInfo.getNickName());visitorsVo.setTags(StringUtils.split(userInfo.getTags(), ','));visitorsVo.setFateValue(visitor.getScore().intValue());visitorsVoList.add(visitorsVo);break;}}}return visitorsVoList;}
}

3.5、接收消息

接收消息的工作需要新创建my-tanhua-recommend工程,在此工程中完成相关的操作。

3.5.1、创建my-tanhua-recommend工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>my-tanhua</artifactId><groupId>cn.itcast.tanhua</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>my-tanhua-recommend</artifactId><dependencies><!--引入interface依赖--><dependency><groupId>cn.itcast.tanhua</groupId><artifactId>my-tanhua-dubbo-interface</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!--RocketMQ相关--><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId></dependency><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency></dependencies></project>

3.5.2、配置文件

application.properties

spring.application.name = itcast-rocketmq
server.port = 18082# RocketMQ相关配置
rocketmq.name-server=192.168.31.81:9876
rocketmq.producer.group=tanhua# mongodb相关配置
spring.data.mongodb.username=tanhua
spring.data.mongodb.password=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=tanhua
spring.data.mongodb.port=27017
spring.data.mongodb.host=192.168.31.81

3.5.3、启动类

package com.tanhua.recommend;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RecommendApplication {public static void main(String[] args) {SpringApplication.run(RecommendApplication.class, args);}
}

3.5.4、RecommendQuanZi

存储到MongoDB的中的实体结构。

package com.tanhua.recommend.pojo;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 = "recommend_quanzi")
public class RecommendQuanZi {private ObjectId id;private Long userId;// 用户idprivate Long publishId; //动态id,需要转化为Long类型private Double score; //得分private Long date; //时间戳
}

3.5.5、QuanZiMsgConsumer

package com.tanhua.recommend.msg;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.tanhua.dubbo.server.pojo.Publish;
import com.tanhua.recommend.pojo.RecommendQuanZi;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;@Component
@RocketMQMessageListener(topic = "tanhua-quanzi",consumerGroup = "tanhua-quanzi-consumer")
@Slf4j
public class QuanZiMsgConsumer implements RocketMQListener<String> {@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic void onMessage(String msg) {try {JSONObject jsonObject = JSONUtil.parseObj(msg);Long userId = jsonObject.getLong("userId");Long date = jsonObject.getLong("date");String publishId = jsonObject.getStr("publishId");Long pid = jsonObject.getLong("pid");Integer type = jsonObject.getInt("type");RecommendQuanZi recommendQuanZi = new RecommendQuanZi();recommendQuanZi.setUserId(userId);recommendQuanZi.setId(ObjectId.get());recommendQuanZi.setDate(date);recommendQuanZi.setPublishId(pid);//1-发动态,2-浏览动态, 3-点赞, 4-喜欢, 5-评论,6-取消点赞,7-取消喜欢switch (type) {case 1: {Publish publish = this.mongoTemplate.findById(new ObjectId(publishId), Publish.class);if (ObjectUtil.isNotEmpty(publish)) {double score = 0d;//获取图片数score += CollUtil.size(publish.getMedias());//获取文本的长度//文字长度:50以内1分,50~100之间2分,100以上3分int length = StrUtil.length(publish.getText());if (length >= 0 && length < 50) {score += 1;} else if (length < 100) {score += 2;} else {score += 3;}recommendQuanZi.setScore(score);}break;}case 2: {recommendQuanZi.setScore(1d);break;}case 3: {recommendQuanZi.setScore(5d);break;}case 4: {recommendQuanZi.setScore(8d);break;}case 5: {recommendQuanZi.setScore(10d);break;}case 6: {recommendQuanZi.setScore(-5d);break;}case 7: {recommendQuanZi.setScore(-8d);break;}default: {recommendQuanZi.setScore(0d);break;}}//数据保存到MongoDB中this.mongoTemplate.save(recommendQuanZi);} catch (Exception e) {log.error("处理消息出错!msg = " + msg, e);}}
}

3.7、测试

测试方法:使用APP进行操作,可以看到在MongoDB中已经有数据写入。

4、部署推荐系统

在推荐系统中,我们将基于前面写入到推荐表中的数据通过Spark进行计算,在Spark计算完成后将结果写入到Redis中,以供在业务系统中进行查询。

推荐服务我们将基于docker的形式进行部署:

#拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-quanzi:1.0#创建容器
docker create --name tanhua-spark-quanzi \
--env MONGODB_HOST=192.168.31.81 \
--env MONGODB_PORT=27017 \
--env MONGODB_USERNAME=tanhua \
--env MONGODB_PASSWORD=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV \
--env MONGODB_DATABASE=tanhua \
--env MONGODB_COLLECTION=recommend_quanzi \
--env SCHEDULE_PERIOD=10 \
--env REDIS_NODES="192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381" \
registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-quanzi:1.0#参数说明
#MONGODB_HOST mongodb服务的地址
#MONGODB_PORT mongodb服务的端口
#MONGODB_USERNAME mongodb服务的认证用户名
#MONGODB_PASSWORD mongodb服务的认证密码
#MONGODB_DATABASE mongodb连接的数据库
#MONGODB_COLLECTION 操作表
#SCHEDULE_PERIOD  下次执行时间间隔,但是为分,默认为10分钟
#REDIS_NODES  redis集群地址,也可以使用单节点#mongodb开启认证服务
#docker create --name mongodb --restart=always -p 27017:27017 -v mongodb:/data/db mongo:4.0.3 --auth#启动服务,启动之后就会进行执行,在SCHEDULE_PERIOD时间后再次执行
docker start tanhua-spark-quanzi#查看日志
docker logs -f tanhua-spark-quanzi#执行完成后会将数据写入到redis中

进入redis查看是否已经有数据:

5、小视频推荐

小视频的推荐和动态推荐的实现逻辑非常的类似。

5.1、动态计分规则

  • 发布+2
  • 点赞 +5
  • 评论 + 10

5.2、发送消息

5.2.1、VideoMQService

package com.tanhua.server.service;import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.common.pojo.User;
import com.tanhua.common.utils.UserThreadLocal;
import com.tanhua.dubbo.server.api.VideoApi;
import com.tanhua.dubbo.server.pojo.Video;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
@Slf4j
public class VideoMQService {@Autowiredprivate RocketMQTemplate rocketMQTemplate;@Reference(version = "1.0.0")private VideoApi videoApi;/*** 发布小视频消息** @return*/public Boolean videoMsg(String videoId) {return this.sendMsg(videoId, 1);}/*** 点赞小视频** @return*/public Boolean likeVideoMsg(String videoId) {return this.sendMsg(videoId, 2);}/*** 取消点赞小视频** @return*/public Boolean disLikeVideoMsg(String videoId) {return this.sendMsg(videoId, 3);}/*** 评论小视频** @return*/public Boolean commentVideoMsg(String videoId) {return this.sendMsg(videoId, 4);}/*** 发送小视频操作相关的消息** @param videoId* @param type     1-发动态,2-点赞, 3-取消点赞,4-评论* @return*/private Boolean sendMsg(String videoId, Integer type) {try {User user = UserThreadLocal.get();Video video = this.videoApi.queryVideoById(videoId);//构建消息Map<String, Object> msg = new HashMap<>();msg.put("userId", user.getId());msg.put("date", System.currentTimeMillis());msg.put("videoId", videoId);msg.put("vid", video.getVid());msg.put("type", type);this.rocketMQTemplate.convertAndSend("tanhua-video", msg);} catch (Exception e) {log.error("发送消息失败! videoId = " + videoId + ", type = " + type, e);return false;}return true;}
}

4.3.2、VideoService

package com.tanhua.server.service;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.tanhua.common.pojo.User;
import com.tanhua.common.pojo.UserInfo;
import com.tanhua.common.service.PicUploadService;
import com.tanhua.common.utils.UserThreadLocal;
import com.tanhua.common.vo.PicUploadResult;
import com.tanhua.dubbo.server.api.QuanZiApi;
import com.tanhua.dubbo.server.api.VideoApi;
import com.tanhua.dubbo.server.pojo.Video;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.VideoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@Service
@Slf4j
public class VideoService {@Reference(version = "1.0.0")private VideoApi videoApi;@Autowiredprivate PicUploadService picUploadService;@Autowiredprotected FastFileStorageClient storageClient;@Autowiredprivate FdfsWebServer fdfsWebServer;@Autowiredprivate UserInfoService userInfoService;@Reference(version = "1.0.0")private QuanZiApi quanZiApi;@Autowiredprivate QuanZiService quanZiService;@Autowiredprivate VideoMQService videoMQService;/*** 小视频上传** @param picFile   封面图片* @param videoFile 视频文件* @return*/public Boolean saveVideo(MultipartFile picFile, MultipartFile videoFile) {User user = UserThreadLocal.get();Video video = new Video();video.setUserId(user.getId());video.setSeeType(1);try {//上传图片到阿里云ossPicUploadResult uploadResult = this.picUploadService.upload(picFile);video.setPicUrl(uploadResult.getName());//上传视频到FastDFS中StorePath storePath = this.storageClient.uploadFile(videoFile.getInputStream(),videoFile.getSize(),StrUtil.subAfter(videoFile.getOriginalFilename(), '.', true),null);video.setVideoUrl(fdfsWebServer.getWebServerUrl() + storePath.getFullPath());String videoId = this.videoApi.saveVideo(video);if(StrUtil.isNotEmpty(videoId)){//发送消息this.videoMQService.videoMsg(videoId);}return StrUtil.isNotEmpty(videoId);} catch (IOException e) {log.error("上传小视频出错~ userId = " + user.getId() + ", file = " + videoFile.getOriginalFilename(), e);}return null;}public PageResult queryVideoList(Integer page, Integer pageSize) {User user = UserThreadLocal.get();PageResult pageResult = new PageResult();pageResult.setPage(page);pageResult.setPagesize(pageSize);PageInfo<Video> pageInfo = this.videoApi.queryVideoList(user.getId(), page, pageSize);List<Video> records = pageInfo.getRecords();if (CollUtil.isEmpty(records)) {return pageResult;}//查询用户信息List<Object> userIds = CollUtil.getFieldValues(records, "userId");List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);List<VideoVo> videoVoList = new ArrayList<>();for (Video record : records) {VideoVo videoVo = new VideoVo();videoVo.setUserId(record.getUserId());videoVo.setCover(record.getPicUrl());videoVo.setVideoUrl(record.getVideoUrl());videoVo.setId(record.getId().toHexString());videoVo.setSignature("我就是我~"); //TODO 签名videoVo.setCommentCount(Convert.toInt(this.quanZiApi.queryCommentCount(videoVo.getId()))); //评论数videoVo.setHasFocus(this.videoApi.isFollowUser(user.getId(), videoVo.getUserId()) ? 1 : 0); //是否关注videoVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), videoVo.getId()) ? 1 : 0); //是否点赞(1是,0否)videoVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(videoVo.getId())));//点赞数//填充用户信息for (UserInfo userInfo : userInfoList) {if (ObjectUtil.equals(videoVo.getUserId(), userInfo.getUserId())) {videoVo.setNickname(userInfo.getNickName());videoVo.setAvatar(userInfo.getLogo());break;}}videoVoList.add(videoVo);}pageResult.setItems(videoVoList);return pageResult;}/*** 点赞** @param videoId* @return*/public Long likeComment(String videoId) {User user = UserThreadLocal.get();Boolean result = this.quanZiApi.likeComment(user.getId(), videoId);if (result) {//发送消息this.videoMQService.likeVideoMsg(videoId);return this.quanZiApi.queryLikeCount(videoId);}return null;}/*** 取消点赞** @param videoId* @return*/public Long disLikeComment(String videoId) {User user = UserThreadLocal.get();Boolean result = this.quanZiApi.disLikeComment(user.getId(), videoId);if (result) {//发送消息this.videoMQService.disLikeVideoMsg(videoId);return this.quanZiApi.queryLikeCount(videoId);}return null;}public Boolean saveComment(String videoId, String content) {Boolean result = this.quanZiService.saveComments(videoId, content);if(result){//发送消息this.videoMQService.commentVideoMsg(videoId);}return result;}public PageResult queryCommentList(String videoId, Integer page, Integer pageSize) {return this.quanZiService.queryCommentList(videoId, page, pageSize);}/*** 关注用户** @param userId* @return*/public Boolean followUser(Long userId) {User user = UserThreadLocal.get();return this.videoApi.followUser(user.getId(), userId);}/*** 取消关注** @param userId* @return*/public Boolean disFollowUser(Long userId) {User user = UserThreadLocal.get();return this.videoApi.disFollowUser(user.getId(), userId);}
}

5.3、接收消息

5.3.1、RecommendVideo

package com.tanhua.recommend.pojo;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 = "recommend_video")
public class RecommendVideo {private ObjectId id;private Long userId;// 用户idprivate Long videoId; //视频id,需要转化为Long类型private Double score; //得分private Long date; //时间戳
}

5.3.2、VideoMsgConsumer

package com.tanhua.recommend.msg;import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.tanhua.recommend.pojo.RecommendVideo;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;@Component
@RocketMQMessageListener(topic = "tanhua-video",consumerGroup = "tanhua-video-consumer")
@Slf4j
public class VideoMsgConsumer implements RocketMQListener<String> {@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic void onMessage(String msg) {try {JSONObject jsonObject = JSONUtil.parseObj(msg);Long userId = jsonObject.getLong("userId");Long vid = jsonObject.getLong("vid");Integer type = jsonObject.getInt("type");//1-发动态,2-点赞, 3-取消点赞,4-评论RecommendVideo recommendVideo = new RecommendVideo();recommendVideo.setUserId(userId);recommendVideo.setId(ObjectId.get());recommendVideo.setDate(System.currentTimeMillis());recommendVideo.setVideoId(vid);switch (type) {case 1: {recommendVideo.setScore(2d);break;}case 2: {recommendVideo.setScore(5d);break;}case 3: {recommendVideo.setScore(-5d);break;}case 4: {recommendVideo.setScore(10d);break;}default: {recommendVideo.setScore(0d);break;}}this.mongoTemplate.save(recommendVideo);} catch (Exception e) {log.error("处理小视频消息失败~" + msg, e);}}
}

5.3.3、测试

可以看到,用户1对于视频有点赞、取消点赞、评论等操作。

5.4、部署推荐服务

#拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-video:1.0#创建容器
docker create --name tanhua-spark-video \
--env MONGODB_HOST=192.168.31.81 \
--env MONGODB_PORT=27017 \
--env MONGODB_USERNAME=tanhua \
--env MONGODB_PASSWORD=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV \
--env MONGODB_DATABASE=tanhua \
--env MONGODB_COLLECTION=recommend_video \
--env SCHEDULE_PERIOD=10 \
--env REDIS_NODES="192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381" \
registry.cn-hangzhou.aliyuncs.com/itcast/tanhua-spark-video:1.0#启动服务
docker start tanhua-spark-video#查看日志
docker logs -f tanhua-spark-video

测试:

探花交友_第10章_实现推荐功能相关推荐

  1. 探花交友_第10章_搭建后台系统(新版)

    探花交友_第10章_搭建后台系统(新版) 文章目录 探花交友_第10章_搭建后台系统(新版) 1.1 概述 1.2 API网关 1.2.1 搭建网关 依赖 引导类 跨域问题配置类 配置文件 测试 1. ...

  2. 明解C语言入门篇_第10章_指针

    前言 本文为业余学习<明解C语言入门篇>的记录,包含代码清单和练习题. 开始学习时间:2022年8月21日 +++++++++++++++++++++++++++++++ 第1章 初识C语 ...

  3. Linux系统配置及服务管理_第10章_计划任务日志管理

    一.计划任务 简介: 作用:计划任务主要是做一些周期性的任务,目前最主要的用途是定期备份数据. 分类: 一次性调度执行 at 循环调度执行 cron 1.一次性调度执行 at (1)安装 yum -y ...

  4. linux crontab 每5分钟执行一次_Linux系统配置及服务管理_第10章_计划任务

    一·简介 作用: 计划任务主要是做一些周期性的任务, 目前最主要的用途是定期备份数据. 分类 一次性调度执行 at Schedule one-time tasks with at. 循环调度执行 cr ...

  5. 链表_第10章_基本数据结构_算法导论

    双向链表(double linked list)中每个元素都是一个对象,每个对象有一个关键字key与两个指针:next and prev.next指向链表的后一个元素,prev指向前驱元素. 单链接的 ...

  6. 探花交友_第2章_环境搭建(新版)

    探花交友_第2章_环境搭建(新版) 文章目录 探花交友_第2章_环境搭建(新版) 课程介绍 <探花交友> 1.项目介绍 1.1.项目背景 1.2.市场分析 1.3.目标用户群体 1.4.使 ...

  7. 探花交友_第9章_小视频方案(新版)

    探花交友_第9章_小视频方案(新版) 文章目录 探花交友_第9章_小视频方案(新版) 1. 我的访客 1.1 需求分析 1.1.1 功能说明 1.1.2 数据库表 1.2 记录访客数据 tanhua- ...

  8. 探花交友_第12章_实现推荐系统(新版)

    探花交友_第12章_实现推荐系统(新版) 文章目录 探花交友_第12章_实现推荐系统(新版) 1.了解推荐系统 1.1.什么是推荐系统? 1.2.电商是推荐系统的先行者 1.3.推荐系统业务流程 1. ...

  9. 探花交友_第11章_数据统计与内容审核(新版)

    探花交友_第11章_数据统计与内容审核(新版) 文章目录 探花交友_第11章_数据统计与内容审核(新版) 1.用户冻结解冻 1.1 用户冻结 ManageController ManageServic ...

最新文章

  1. 基于Hash算法的高维数据的最近邻检索
  2. 一生都要Debug,我们最需要掌握哪些硬技能?
  3. 老黄历接口(免注册)
  4. neo4j java label_Neo4j:在具有相同Label的两个或多个节点之间创建关系
  5. ASP.NET MVC案例教程(基于ASP.NET MVC beta)——第三篇
  6. shell入门(二)——面试题实例
  7. C#网络编程之---TCP协议的同步通信(相互发送接收数据)
  8. Qt将GeoJson文件转为mif文件的示例
  9. python遍历文件夹内文件并检索文件中的中文内容
  10. Java对象转Map,Map转对象
  11. Android应用程序启动时出现白色背景问题
  12. 第一个用python实现的数据化运营分析实例——销售预测
  13. c++求一个数的因子
  14. 小白如何利用自媒体做引流推广?
  15. pythonapp自动化_基于python的App UI自动化环境搭建
  16. 基于30多万条招聘信息的热门城市、地域 、薪资、人才要求的R语言数据可视化分析
  17. 用excel中数据分析功能做线性回归练习。分别选取20、200、2000(或20000)组数据,进行练习。记录回归方程式、相关系数R2并用jupyter编程
  18. 《开源软件开发导论》作业1
  19. [渝粤教育] 首都师范大学 走进舞蹈艺术 参考 资料
  20. Android备份到电脑,用 TWRP「一键 Ghost」你的 Android 手机,还能备份到电脑

热门文章

  1. OpenGL总结9-万向锁
  2. 众筹网站项目第七天之zTree树形结构实现(2)
  3. 同步屏障Barrier
  4. layui表单离焦验证
  5. 推荐这款,SpringBoot 开源商城系统,挣钱太轻松了
  6. 微软研究院分享:计算机专业求职的正确姿势
  7. 附件上传的插件介绍--- plupload
  8. 【目标检测-YOLO】YOLOv5-v6.0-网络架构详解(第二篇)
  9. brpc源码学习(六)- brpc server 端整体流程
  10. 配镜注意这三点,轻松解决上海配眼镜去哪里好难题