用户行为数据的记录包括了关注、点赞、不喜欢、收藏、阅读等行为
这些行为与当前app端的功能实现没有任何关系,即使没有行为数据,功能也不耽误实现,那为什么要做行为数据的保存呢?
黑马头条项目整个项目开发涉及web展示和大数据分析来给用户推荐文章,如何找出哪些文章是热点文章进行针对性的推荐呢?这个时候需要进行大数据分析的准备工作,埋点。
所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、阅读文章的时长,观看视频的时长等等。
黑马头条课程里主要涉及到了关注行为,点赞行为,阅读行为的保存。其他类似于不喜欢、收藏功能可根据这些实现的功能自行实现。
处理行为是一个量比较大的操作,所以专门创建一个微服务来处理行为相关操作,参考别的微服务进行搭建即可
创建api:
行为服务中pom.xml:
<?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>itheima-leadnews-service</artifactId> <groupId>com.itheima</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>itheima-leadnews-service-behaviour</artifactId> <dependencies> <dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-behaviour-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-common-db</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-core-controller</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
spring: profiles: active: dev --- server: port: 9006 spring: profiles: dev application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo logging: level.com: debug --- server: port: 9006 spring: profiles: test application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo --- server: port: 9006 spring: profiles: pro application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo
@SpringBootApplication @EnableDiscoveryClient @MapperScan(basePackages = "com.itheima.behaviour.mapper") public class BehaviourApplication { public static void main(String[] args) { SpringApplication.run(BehaviourApplication.class,args); } @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
通过代码生成器生成pojo,controller,service ,mapper,和xml
生成之后copy到如下目录:
在文章详情中,当用户点击了关注作者按钮,需要记录当前行为到表中,目前只需要存储数据即可,后期会做实时的流式处理,根据这些基础数据做热点文章的计算。
(1)ap_follow_behavior APP关注行为表
(2)ap_behavior_entry 行为实体表
行为实体指的是使用的终端设备或者是登录的用户,统称为行为实体。
type :0终端设备 1用户
行为实体与APP关注行为表是一对多的关系,关注行为需要知道是谁(设备或用户)关注了该文章信息
关注与取消关注的功能已经实现,当用户点击了关注保存关注行为,取消关注不保存数据。
因为只做保存操作,只需要在【关注业务操作】的时候发送消息给行为微服务 【行为微服务】获取消息进行数据保存即可。
实现步骤:
1 用户微服务中关注操作发送消息,保存用户行为
2 行为微服务接收消息
2.1 获取行为实体
2.2 保存数据
(1)用户微服务添加依赖
<!-- kafka依赖 begin --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka-test</artifactId> <scope>test</scope> </dependency>
(2)在用户微服务中搭建kafka的环境
spring: profiles: active: dev --- server: port: 9002 spring: application: name: leadnews-user profiles: dev datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 producer: batch-size: 16384 buffer-memory: 33554432 key-serializer: org.apache.kafka.common.serialization.StringSerializer retries: 10 value-serializer: org.apache.kafka.common.serialization.StringSerializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.user.pojo logging: level.com: debug --- server: port: 9002 spring: application: name: leadnews-user profiles: pro datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 producer: batch-size: 16384 buffer-memory: 33554432 key-serializer: org.apache.kafka.common.serialization.StringSerializer retries: 10 value-serializer: org.apache.kafka.common.serialization.StringSerializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.user.pojo --- server: port: 9002 spring: application: name: leadnews-user profiles: test datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 producer: batch-size: 16384 buffer-memory: 33554432 key-serializer: org.apache.kafka.common.serialization.StringSerializer retries: 10 value-serializer: org.apache.kafka.common.serialization.StringSerializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.user.pojo
(3)添加依赖到行为服务
<!-- kafka依赖 begin --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka-test</artifactId> <scope>test</scope> </dependency>
(4)修改yaml文件:添加kafka消费者:
spring: profiles: active: dev --- server: port: 9006 spring: profiles: dev application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 consumer: auto-offset-reset: earliest group-id: behaviour-consumer-group # 默认值即为字符串 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 默认值即为字符串 value-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo logging: level.com: debug --- server: port: 9006 spring: profiles: test application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 consumer: auto-offset-reset: earliest group-id: behaviour-consumer-group # 默认值即为字符串 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 默认值即为字符串 value-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo --- server: port: 9006 spring: profiles: pro application: name: leadnews-behaviour cloud: nacos: discovery: server-addr: 192.168.211.136:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.211.136:3306/leadnews_behaviour?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=&serverTimezone=Asia/Shanghai username: root password: 123456 kafka: # 配置连接到服务端集群的配置项 ip:port,ip:port bootstrap-servers: 192.168.211.136:9092 consumer: auto-offset-reset: earliest group-id: behaviour-consumer-group # 默认值即为字符串 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 默认值即为字符串 value-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置 mybatis-plus: mapper-locations: classpath*:mapper/*.xml # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 type-aliases-package: com.itheima.behaviour.pojo
发送消息需要准备一个FollowBehaviorDto,进行数据的传递
(1)创建dto
package com.itheima.behaviour.dto; import lombok.Data; import lombok.Getter; import lombok.Setter; @Data @Setter @Getter public class FollowBehaviorDto { //文章id Long articleId; //被关注者 用户ID Integer followId; //关注者 用户id Integer userId; //设备ID Integer equipmentId; }
(2)新建常量,固定当前消息的topic
public static final String FOLLOW_BEHAVIOR_TOPIC="follow.behavior.topic";
(3)用户微服务中添加依赖:
<dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-behaviour-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(4)注入KafkaTemplate
并在实现类中发送消息
FollowBehaviorDto dto = new FollowBehaviorDto(); //被关注者 用户的ID dto.setFollowId(apAuthor.getUserId()); //文章 dto.setArticleId(relationDto.getArticleId()); //关注者 用户的ID dto.setUserId(currentUserId); kafkaTemplate.send(BusinessConstants.MqConstants.FOLLOW_BEHAVIOR_TOPIC, JSON.toJSONString(dto));
整体代码如下:
package com.itheima.user.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.article.feign.ApAuthorFeign; import com.itheima.article.pojo.ApAuthor; import com.itheima.behaviour.dto.FollowBehaviorDto; import com.itheima.common.constants.BusinessConstants; import com.itheima.common.exception.LeadNewsException; import com.itheima.common.pojo.Result; import com.itheima.common.pojo.StatusCode; import com.itheima.common.util.RequestContextUtil; import com.itheima.user.dto.UserRelationDto; import com.itheima.user.mapper.ApUserFanMapper; import com.itheima.user.mapper.ApUserFollowMapper; import com.itheima.user.pojo.ApUser; import com.itheima.user.pojo.ApUserFan; import com.itheima.user.pojo.ApUserFollow; import com.itheima.user.service.ApUserFollowService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import java.time.LocalDateTime; /** * <p> * APP用户关注信息表 服务实现类 * </p> * * @author ljh * @since 2021-11-28 */ @Service public class ApUserFollowServiceImpl extends ServiceImpl<ApUserFollowMapper, ApUserFollow> implements ApUserFollowService { @Autowired private ApUserFollowMapper apUserFollowMapper; @Autowired private ApUserFanMapper apUserFanMapper; @Autowired private ApAuthorFeign apAuthorFeign; @Autowired private KafkaTemplate kafkaTemplate; @Override public void follow(UserRelationDto relationDto) throws LeadNewsException { //判断当前用户是否是匿名用户 if (RequestContextUtil.isAnonymous()) { throw new LeadNewsException(StatusCode.NEED_LOGIN.code(), StatusCode.NEED_LOGIN.message()); } Integer currentUserId = RequestContextUtil.getUserId(); //先根据页面传递过来的作者的ID 获取到作者的信息(user_id:作者对应的app_user_id,name,id) ApAuthor apAuthor = apAuthorFeign.findById(relationDto.getAuthorId()).getData(); if (apAuthor == null) { throw new LeadNewsException("错误"); } if (relationDto.getOperation() == 1) { //如果是关注 就添加关注记录 // 需要先查询 是否有记录 有记录不能添加记录 QueryWrapper<ApUserFollow> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", currentUserId); queryWrapper.eq("follow_id", apAuthor.getUserId()); ApUserFollow apUserFollow1 = apUserFollowMapper.selectOne(queryWrapper); if (apUserFollow1 != null) { throw new LeadNewsException("已经关注过了"); } // 张三 关注了 李四 ApUserFollow apUserFollow = new ApUserFollow(); //张三 apUserFollow.setUserId(currentUserId); //李四 apUserFollow.setFollowId(apAuthor.getUserId()); apUserFollow.setFollowName(apAuthor.getName()); apUserFollow.setLevel(1);//普通的关注 apUserFollow.setIsNotice(1); apUserFollow.setCreatedTime(LocalDateTime.now()); apUserFollowMapper.insert(apUserFollow); //添加粉丝记录 ApUserFan apUserFan = new ApUserFan(); //张三 apUserFan.setFansId(currentUserId); apUserFan.setFansName(RequestContextUtil.getRequestUserTokenInfo().getName()); //李四 apUserFan.setUserId(apAuthor.getUserId()); apUserFan.setLevel(1); apUserFan.setIsDisplay(0); apUserFan.setIsShieldLetter(0); apUserFan.setIsShieldComment(0); apUserFan.setCreatedTime(LocalDateTime.now()); apUserFanMapper.insert(apUserFan); //发送消息 消息本身:包含 文章的ID 当前的用户的ID(这个是只有用户才能关注,设备是不允许关注的),作者对应的app_user的ID //todo FollowBehaviorDto dto = new FollowBehaviorDto(); //被关注者 用户的ID dto.setFollowId(apAuthor.getUserId()); //文章 dto.setArticleId(relationDto.getArticleId()); //关注者 用户的ID dto.setUserId(currentUserId); kafkaTemplate.send(BusinessConstants.MqConstants.FOLLOW_BEHAVIOR_TOPIC, JSON.toJSONString(dto)); } else { //取消关注 删除记录 //删除 取消关注 apUserFollowMapper.deleteFollow(currentUserId, apAuthor.getUserId()); //删除 粉丝记录 apUserFanMapper.deleteFan(apAuthor.getUserId(), currentUserId); } } }
步骤:
(1)在行为微服务中实现查询行为实体业务 (2)在行为微服务中实现保存关注行为数据 (3)在行为微服务中创建监听类监听消息 实现业务
(1)在行为微服务中实现查询行为实体业务
@Service public class ApBehaviorEntryServiceImpl extends ServiceImpl<ApBehaviorEntryMapper, ApBehaviorEntry> implements ApBehaviorEntryService { @Override public ApBehaviorEntry findByUserIdOrEquipmentId(Integer userId, Integer type) { QueryWrapper<ApBehaviorEntry> queryWrapper = new QueryWrapper<ApBehaviorEntry>(); queryWrapper.eq("entry_id",userId); queryWrapper.eq("type",type);//标识用户 ApBehaviorEntry entry = getOne(queryWrapper); return entry; } }
(2)在行为微服务中实现保存关注行为数据
public static final Integer TYPE_USER = 1;//用户 public static final Integer TYPE_E = 0;//设备
@Service public class ApFollowBehaviorServiceImpl extends ServiceImpl<ApFollowBehaviorMapper, ApFollowBehavior> implements ApFollowBehaviorService { @Autowired private ApBehaviorEntryService apBehaviorEntryService; @Override public void saveFollowBehavior(FollowBehaviorDto dto) { //1.查询行为实体 ApBehaviorEntry apBehaviorEntry = apBehaviorEntryService.findByUserIdOrEquipmentId(userId, SystemConstants.TYPE_USER); if (entry != null) { //2.判断 如果行为实体为空 则不做处理 //3.如果 行为实体有值 则保存关注行为数据 ApFollowBehavior alb = new ApFollowBehavior(); //设置实体ID alb.setEntryId(entry.getId()); //设置创建时间 alb.setCreatedTime(LocalDateTime.now()); //设置文章的ID alb.setArticleId(dto.getArticleId()); //设置被关注者 用户的ID alb.setFollowId(dto.getFollowId()); save(alb); } } }
(3)在行为微服务中创建监听类监听消息 实现业务
@Component public class FollowBehaviorListener { @Autowired private ApFollowBehaviorService apFollowBehaviorService; //监听消息 @KafkaListener(topics = BusinessConstants.MqConstants.FOLLOW_BEHAVIOR_TOPIC) public void receiverMessage(ConsumerRecord<?,?> record){ if(record!=null){ FollowBehaviorDto dto = JSON.parseObject(record.value().toString(), FollowBehaviorDto.class); //保存关注行为数据 apFollowBehaviorService.saveFollowBehavior(dto); } } }
(4) 测试
先登录
再关注:
查看:
当前登录的用户点击了”赞“,就要保存当前行为数据
当前用户点赞以后保存数据,取消点赞则不删除数据
保存也是根据当前行为实体和文章id进行保存
(1)创建dto 用于接收页面传递的参数进行保存点赞行为
@Data @Setter @Getter public class LikesBehaviourDto { // 设备ID Integer equipmentId; // 文章、动态、评论等ID Long articleId; /** * 喜欢内容类型 * 0文章 * 1动态 * 2评论 */ Integer type; /** * 喜欢操作方式 * 1 点赞 * 0 取消点赞 */ Integer operation; }
(2)controller
@PostMapping("/like") public Result like(@RequestBody LikesBehaviourDto likesBehaviourDto) throws Exception{ apLikesBehaviorService.like(likesBehaviourDto); return Result.ok(); }
(3)业务层
@Service public class ApLikesBehaviorServiceImpl extends ServiceImpl<ApLikesBehaviorMapper, ApLikesBehavior> implements ApLikesBehaviorService { @Autowired private ApBehaviorEntryService apBehaviorEntryService; @Override public void like(LikesBehaviourDto likesBehaviourDto) throws Exception { //1.检查 参数值 if(likesBehaviourDto == null || likesBehaviourDto.getArticleId() == null || likesBehaviourDto.getType() > 2 || likesBehaviourDto.getType() < 0 || likesBehaviourDto.getOperation() < 0 || likesBehaviourDto.getOperation() > 1){ throw new LeadNewsException("错误的参数"); } //2.先获取当前用户的ID 如果是0 标识匿名用户 如果不是0 就是真实的用户 Integer currentUserId = RequestContextUtil.getUserId(); ApBehaviorEntry entry = null; if (RequestContextUtil.isAnonymous()) { entry = apBehaviorEntryService.findByUserIdOrEquipmentId(likesBehaviourDto.getEquipmentId() , SystemConstants.TYPE_E); } else { entry = apBehaviorEntryService.findByUserIdOrEquipmentId(currentUserId , SystemConstants.TYPE_USER); } if (entry == null) { throw new LeadNewsException("用户实体不存在"); } //2.添加数据到表中(添加 也有可能是更新) if(likesBehaviourDto.getOperation()==1) { //点赞 //查询是否存在点赞记录 如果有 则不用点赞 //select * from xxx where entry_id=? and article_id=? QueryWrapper<ApLikesBehavior> queryWrapper =new QueryWrapper<ApLikesBehavior>(); queryWrapper.eq("entry_id",entry.getId()); queryWrapper.eq("article_id",likesBehaviourDto.getArticleId()); ApLikesBehavior apLikesBehavior = apLikesBehaviorMapper.selectOne(queryWrapper); if(apLikesBehavior!=null){//说明有记录(有可能是取消点赞的) if(apLikesBehavior.getOperation()==0){ apLikesBehavior.setOperation(1);// apLikesBehaviorMapper.updateById(apLikesBehavior); } return; } ApLikesBehavior entity = new ApLikesBehavior(); entity.setOperation(likesBehaviourDto.getOperation()); entity.setArticleId(likesBehaviourDto.getArticleId()); entity.setEntryId(entry.getId());//根据用户ID 或者设备ID 从 实体表中获取实体对象 再获取到主键 设置到这里 entity.setCreatedTime(LocalDateTime.now()); entity.setType(0); apLikesBehaviorMapper.insert(entity); }else{ //取消点赞 //更新 update xxx set operation=0 where entry_id=? and article_id=? and operation=1 QueryWrapper<ApLikesBehavior> queryWrapper =new QueryWrapper<ApLikesBehavior>(); queryWrapper.eq("entry_id",entry.getId()); queryWrapper.eq("article_id",likesBehaviourDto.getArticleId()); queryWrapper.eq("operation",1); ApLikesBehavior apLikesBehavior = apLikesBehaviorMapper.selectOne(queryWrapper); if(apLikesBehavior!=null){ apLikesBehavior.setOperation(0); apLikesBehaviorMapper.updateById(apLikesBehavior); } } } }
添加异常构造函数:
(4)在app网关中配置行为微服务的路由
spring: profiles: active: dev --- server: port: 6003 spring: application: name: leadnews-app-gateway profiles: dev cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedHeaders: "*" allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: # 文章微服务 - id: article uri: lb://leadnews-article predicates: - Path=/article/** filters: - StripPrefix= 1 # app用户微服务 - id: user uri: lb://leadnews-user predicates: - Path=/user/** filters: - StripPrefix= 1 - id: behaviour uri: lb://leadnews-behaviour predicates: - Path=/behaviour/** filters: - StripPrefix= 1 --- server: port: 6003 spring: application: name: leadnews-app-gateway profiles: test cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedHeaders: "*" allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: # 文章微服务 - id: article uri: lb://leadnews-article predicates: - Path=/article/** filters: - StripPrefix= 1 # app用户微服务 - id: user uri: lb://leadnews-user predicates: - Path=/user/** filters: - StripPrefix= 1 - id: behaviour uri: lb://leadnews-behaviour predicates: - Path=/behaviour/** filters: - StripPrefix= 1 --- server: port: 6003 spring: application: name: leadnews-app-gateway profiles: pro cloud: nacos: server-addr: 192.168.211.136:8848 discovery: server-addr: ${spring.cloud.nacos.server-addr} gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedHeaders: "*" allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: # 文章微服务 - id: article uri: lb://leadnews-article predicates: - Path=/article/** filters: - StripPrefix= 1 # app用户微服务 - id: user uri: lb://leadnews-user predicates: - Path=/user/** filters: - StripPrefix= 1 - id: behaviour uri: lb://leadnews-behaviour predicates: - Path=/behaviour/** filters: - StripPrefix= 1
(5)由于点赞 主键没有自增,我们使用雪花算法来生成主键
添加如下代码:
修改如下配置:
(6)测试
启动项目:
先app登录:
点赞:
校验成功:
当用户查看了某一篇文章,需要记录当前用户查看的次数,阅读时长,阅读文章的比例,加载的时长(非必要)
这些数据保存到一个表中。当用户查看了一篇文章的详情,点击返回重新加入文章列表发送请求,记录当前用户阅读此文章的行为。
ap_read_behavior APP阅读行为表
当点击一篇文章之后,查看了之后,点击返回重新回到文章列表的时候,由前端发送请求给到后台即可。至于次数,时长由前端来进行计算即可。
(1)创建dto来接收请求数据
@Data public class ReadBehaviorDto { // 设备ID Integer equipmentId; // 文章、动态、评论等ID Long articleId; /** * 阅读次数 */ Integer count; /** * 阅读时长(S) */ Integer readDuration; /** * 阅读百分比 不带小数 */ Integer percentage; /** * 加载时间 */ Integer loadDuration; }
(2) controller
@PostMapping("/read") public Result read(@RequestBody ReadBehaviorDto readBehaviorDto) throws Exception{ apReadBehaviorService.readBehavior(readBehaviorDto); return Result.ok(); }
(3) 业务层
@Service public class ApReadBehaviorServiceImpl extends ServiceImpl<ApReadBehaviorMapper, ApReadBehavior> implements ApReadBehaviorService { @Autowired private ApBehaviorEntryService apBehaviorEntryService; @Override public void readBehavior(ReadBehaviorDto readBehaviorDto) throws Exception { //1.参数校验 if(readBehaviorDto == null || readBehaviorDto.getArticleId() == null){ throw new LeadNewsException("文章不存在"); } Integer currentUserId = RequestContextUtil.getUserId(); ApBehaviorEntry entry = null; if (RequestContextUtil.isAnonymous()) { entry = apBehaviorEntryService.findByUserIdOrEquipmentId(readBehaviorDto.getEquipmentId() , SystemConstants.TYPE_E); } else { entry = apBehaviorEntryService.findByUserIdOrEquipmentId(currentUserId , SystemConstants.TYPE_USER); } if (entry == null) { throw new LeadNewsException("用户实体不存在"); } //3.保存或更新阅读的行为 QueryWrapper<ApReadBehavior> queryWrapper = new QueryWrapper<ApReadBehavior>(); queryWrapper.eq("entry_id",apBehaviorEntry.getId()); queryWrapper.eq("article_id",readBehaviorDto.getArticleId()); ApReadBehavior apReadBehavior = getOne(queryWrapper); if(apReadBehavior == null){ apReadBehavior = new ApReadBehavior(); apReadBehavior.setCount(readBehaviorDto.getCount()); apReadBehavior.setArticleId(readBehaviorDto.getArticleId()); apReadBehavior.setPercentage(readBehaviorDto.getPercentage()); apReadBehavior.setEntryId(entry.getId()); apReadBehavior.setLoadDuration(readBehaviorDto.getLoadDuration()); apReadBehavior.setReadDuration(readBehaviorDto.getReadDuration()); apReadBehavior.setCreatedTime(LocalDateTime.now()); save(apReadBehavior); }else{ apReadBehavior.setUpdatedTime(LocalDateTime.now()); apReadBehavior.setCount((apReadBehavior.getCount()+1)); updateById(apReadBehavior); } } }
同样设置主键采用雪花算法实现:
(4)测试
当用户查看了一篇文章的详情,点击返回重新加入文章列表发送请求,记录当前用户阅读此文章的行为.该行为通过POSTMAN来进行测试。
先登录:
阅读行为保存:
{ "articleId":1368752420570234881, "count":1, "readDuration":1, "percentage":99, "loadDuration":1, "operation":1 }
点击三次之后:
为什么会有不喜欢?
一旦用户点击了不喜欢,不再给当前用户推荐这一类型的文章信息
1.定义DTO 用于接收请求传递过来的参数 2.编写controller service 在service 中首先需要获取当前用户是否为 用户 或者设备。判断是否有行为实体,如果有则进行保存,保存的时候先校验是否已经存在,如果存在,则为更新即可 3.设置POJO主键生成策略为 雪花算法生成。
ap_unlikes_behavior APP不喜欢行为表
dto:
@Data public class UnLikesBehaviorDto { Integer equipmentId; Long articleId; /** * 不喜欢操作方式 * 0 不喜欢 * 1 取消不喜欢 */ Integer type; }
收藏表在文章库中,为什么不设计在行为库?
因为app端用户可以个人中心找到自己收藏的文章列表,这样设计更方便。便于关联表查询,如果是夸库,则不能关联表查询。
1.定义DTO 用于接收请求传递过来的参数 2.编写controller service 在service 中首先需要获取当前用户是否为 用户 或者设备。判断是否有行为实体,如果有则进行保存,保存的时候先校验是否已经存在,如果存在,则为更新即可 3.设置POJO主键生成策略为 雪花算法生成。
ap_collection APP收藏信息表
对象dto
@Data public class CollectionBehaviorDto { // 设备ID Integer equipmentId; // 文章、动态ID Long entryId; /** * 收藏内容类型 * 0文章 * 1动态 */ Integer type; /** * 操作类型 * 0收藏 * 1取消收藏 */ Integer operation; /** 发布时间 冗余字段 接收页面文章的发布时间值 */ LocalDateTime publishedTime; }
主要是用来展示文章的关系,app端用户必须登录,判断当前用户是否已经关注该文章的作者、是否收藏了此文章、是否点赞了文章、是否不喜欢该文章等
例:如果当前用户点赞了该文章,点赞按钮进行高亮,其他功能类似。
1 用户查看文章详情,展示文章信息(功能已实现),同时需要展示当前文章的行为(点赞,收藏等)
2 根据用户id(已登录)或设备id(未登录)去查询当前实体id
3 通过实体id和前端传递过来的文章id去查询收藏表、点赞表、不喜欢表;其中点赞和不喜欢需要远程调用behavior微服务获取数据。
4 在文章详情展示是否关注此作者,需要通过当前用户和作者关系表进行查询,有数据则关注,无数据则没有关注
返回的格式如下:
{"isfollow":true,"islike":true,"isunlike":false,"iscollection":true}
通过图来说明思路如下:
简单总结下就是 点击查询文章详情时,再发送一个请求 通过文章微服务获取其他相关的譬如:收藏,关注,点赞,等数据 判断是否关注,点赞 ,收藏之后返回一个JSON给前端 前端根据JSON的值进行展示即可
页面请求 过来后台controller 接收 调用service service内部业务逻辑如下
//1.定义变量 //2.通过feign调用查询 行为实体 //3.查询是否关注 查询是否喜欢 查询是否收藏 查询是否点赞 //3.1 通过feign调用查询 是否喜欢 //3.2 通过feign调用查询 是否点赞 //3.3 通过feign调用查询 是否关注 //3.4 查询是否收藏 //4.获取之后组合map 返回即可,返回的数据格式如下: {"isfollow":true,"islike":true,"isunlike":false,"iscollection":true}
(1) com.itheima.article.dto下创建dto
@Data @Getter @Setter public class ArticleBehaviourDtoQuery { // 设备ID Integer equipmentId; // 文章ID Long articleId; // 作者ID Integer authorId; }
(2)创建controller
@PostMapping("/load/article/behavior") public Result<Map<String,Object>> loadArticleBehaviour(@RequestBody ArticleBehaviourDtoQuery articleBehaviourDtoQuery){ Map<String,Object> resultMap = apArticleService.loadArticleBehaviour(articleBehaviourDtoQuery); return Result.ok(resultMap); }
(3)业务实现类
@Override public Map<String, Object> loadArticleBehaviour(ArticleBehaviourDtoQuery articleBehaviourDtoQuery) { //1.定义变量 //是否喜欢 默认是false boolean isunlike=false; //是否点赞 默认是false boolean islike = false; //是否收藏 boolean isCollection = false; //是否关注 boolean isFollow = false; Map<String,Object> resultMap = new HashMap<String,Object>(); //{"isfollow":true,"islike":true,"isunlike":false,"iscollection":true} resultMap.put("isfollow",isFollow); resultMap.put("islike",islike); resultMap.put("isunlike",isunlike); resultMap.put("iscollection",isCollection); //2.通过feign调用查询 行为实体 //3.查询是否关注 查询是否喜欢 查询是否收藏 查询是否点赞 //3.1 通过feign调用查询 是否喜欢 //3.2 通过feign调用查询 是否点赞 //3.3 通过feign调用查询 是否关注 //3.3.1 首先根据表结构获取相关的思路(根据页面传递的作者的ID 获取到作者表信息 获取到对应的appuser的ID) //3.3.2 再根据该appuserId 和当前的用户的appUserId 从关注表中获取到信息 如果不为空即可 //3.4 查询是否收藏 //4.获取之后组合map 返回即可 return resultMap; }
启动类中开启feignclient支持:
(1)创建feign
@FeignClient(name="leadnews-behaviour",path = "/apBehaviorEntry",contextId ="apBehaviorEntry" ) public interface ApBehaviorEntryFeign extends CoreFeign<ApBehaviorEntry> { @GetMapping("/entryOne") public ApBehaviorEntry findByUserIdOrEquipmentId( @RequestParam(name="userId",required = true) Integer userId, @RequestParam(name="type",required = true)Integer type); }
(2)在文章微服务中添加依赖:
<dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-behaviour-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(3)在行为微服务中实现feign
controller:
/** * 根据设备ID 或者用户的ID 获取实体对象 * @param userId * @param type * @return */ @GetMapping("/entryOne") public ApBehaviorEntry findByUserIdOrEquipmentId( @RequestParam(name="userId",required = true) Integer userId, @RequestParam(name="type",required = true)Integer type){ return apBehaviorEntryService.findByUserIdOrEquipmentId(userId,type); }
(4)在文章微服务中调用:
@Autowired private ApBehaviorEntryFeign apBehaviorEntryFeign; //略。。。。。 Integer userId = RequestContextUtil.getUserId();//张三 ApBehaviorEntry entry=null; if(RequestContextUtil.isAnonymous()){ entry = apBehaviorEntryFeign.findByUserIdOrEquipmentId(articleBehaviourDtoQuery.getEquipmentId(), SystemConstants.TYPE_E); }else{ entry = apBehaviorEntryFeign.findByUserIdOrEquipmentId(userId, SystemConstants.TYPE_USER); } if(entry==null){ return resultMap; } //略......
(1)创建feign接口:
@FeignClient(name="leadnews-behaviour",path = "/apUnlikesBehavior",contextId = "apUnlikesBehavior") public interface ApUnlikesBehaviorFeign extends CoreFeign<ApUnlikesBehavior> { /** * 根据文章的ID 和entryId获取数据 * @param articleId * @param entryId * @return */ @GetMapping("/getUnlikesBehavior") public ApUnlikesBehavior getUnlikesBehavior(@RequestParam(name="articleId") Long articleId,@RequestParam(name="entryId")Integer entryId); }
(2)行为微服务实现feign接口
@GetMapping("/getUnlikesBehavior") public ApUnlikesBehavior getUnlikesBehavior(@RequestParam(name="articleId") Long articleId, @RequestParam(name="entryId")Integer entryId){ QueryWrapper<ApUnlikesBehavior> querywraper = new QueryWrapper<ApUnlikesBehavior>(); querywraper.eq("entry_id",entryId); querywraper.eq("article_id",articleId); ApUnlikesBehavior apUnlikesBehavior = apUnlikesBehaviorService.getOne(querywraper); return apUnlikesBehavior; }
(3)文章微服务中调用:
@Autowired private ApUnlikesBehaviorFeign apUnlikesBehaviorFeign; //略...... //3.查询是否关注 查询是否喜欢 查询是否收藏 查询是否点赞 //3.1 通过feign调用查询 是否喜欢 ApUnlikesBehavior unlikesBehavior = apUnlikesBehaviorFeign.getUnlikesBehavior(articleBehaviourDtoQuery.getArticleId(), entry.getId()); if (unlikesBehavior!=null && "1".equals(unlikesBehavior.getType().toString())) { isunlike=true; resultMap.put("isunlike",isunlike); } //略......
(1)创建feign
@FeignClient(name="leadnews-behaviour",path = "/apLikesBehavior",contextId = "apLikesBehavior") public interface ApLikesBehaviorFeign extends CoreFeign<ApLikesBehavior> { /** * 根据文章的ID 和 entryId获取 是否点赞 * @param articleId * @param entryId * @return */ @GetMapping("/getLikesBehavior") public ApLikesBehavior getLikesBehavior(@RequestParam(name="articleId") Long articleId, @RequestParam(name="entryId")Integer entryId); }
(2)行为微服务实现feign接口
@GetMapping("/getLikesBehavior") public ApLikesBehavior getLikesBehavior(@RequestParam(name="articleId") Long articleId, @RequestParam(name="entryId")Integer entryId){ QueryWrapper<ApLikesBehavior> queryWrapper = new QueryWrapper<ApLikesBehavior>(); queryWrapper.eq("article_id",articleId); queryWrapper.eq("entry_id",entryId); return apLikesBehaviorService.getOne(queryWrapper); }
(3)文章微服务调用
@Autowired private ApLikesBehaviorFeign apLikesBehaviorFeign; //略...... ApLikesBehavior likesBehavior = apLikesBehaviorFeign.getLikesBehavior(articleBehaviourDtoQuery.getArticleId(), appEntry.getId()); //1标识点赞 if(likesBehavior!=null && ("1").equals(likesBehavior.getOperation().toString())){ islike=true;//点赞了 resultMap.put("islike",islike); } //略....
这个需要注意:
关注表是在user微服务中,并且 表中的关注者 和被关注者 对应的存储都是appUser中的ID ,使用时需要用到当前的作者信息。 思路: 先根据作者ID 查询作者信息 在根据作者信息中的userId 和当前的用户的ID 通过feign调用获取到关注表信息。判断即可。
(1)在user-api中创建feign接口
@FeignClient(name="leadnews-user",path = "/apUserFollow",contextId = "apUserFollow") public interface ApUserFollowFeign extends CoreFeign<ApUserFollow> { //获取关注信息记录 某一个关注者 和被关注者的ID 获取信息 @GetMapping("/getApUserFollow") ApUserFollow getApUserFollow(@RequestParam(name="followId")Integer followId,@RequestParam(name="userId")Integer userId); }
(2)在用户微服务中实现接口:
@GetMapping("/getApUserFollow") ApUserFollow getApUserFollow(@RequestParam(name="followId")Integer followId,@RequestParam(name="userId")Integer userId){ QueryWrapper<ApUserFollow> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("follow_id",followId); queryWrapper.eq("user_id",userId); return apUserFollowService.getOne(queryWrapper); }
(3)在文章微服务中添加依赖:
<dependency> <groupId>com.itheima</groupId> <artifactId>itheima-leadnews-user-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(4)调用:
@Autowired private ApAuthorMapper apAuthorMapper; @Autowired private ApUserFollowFeign apUserFollowFeign; //略.... ApAuthor apAuthor = apAuthorMapper.selectById(articleBehaviourDtoQuery.getAuthorId()); if(apAuthor!=null){ Integer followId = apAuthor.getUserId(); ApUserFollow apUserFollow = apUserFollowFeign.getApUserFollow(followId, userId); //3.3.2 再根据该appuserId 和当前的用户的appUserId 从关注表中获取到信息 如果不为空即可 if(apUserFollow!=null){ isFollow=true; resultMap.put("isfollow",isFollow); } } //略...
查询是否收藏 由于 收藏表在文章微服务直接调用即可
//3.4 查询是否收藏 QueryWrapper<ApCollection> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("entry_id", appEntry.getId()); queryWrapper.eq("article_id", articleBehaviourDtoQuery.getArticleId()); queryWrapper.eq("type", 0); ApCollection apCollection = apCollectionMapper.selectOne(queryWrapper); if (apCollection != null) { isCollection = true; resultMap.put("iscollection", isCollection); }
@Autowired private ApBehaviorEntryFeign apBehaviorEntryFeign; @Autowired private ApUnlikesBehaviorFeign apUnlikesBehaviorFeign; @Autowired private ApLikesBehaviorFeign apLikesBehaviorFeign; @Autowired private ApUserFollowFeign apUserFollowFeign; @Autowired private ApAuthorMapper apAuthorMapper; @Autowired private ApCollectionMapper apCollectionMapper; @Override public Map<String, Object> loadArticleBehaviour(ArticleBehaviourDtoQuery articleBehaviourDtoQuery) { //1.定义变量 //是否喜欢 默认是false boolean isunlike = false; //是否点赞 默认是false boolean islike = false; //是否收藏 boolean isCollection = false; //是否关注 boolean isFollow = false; Map<String, Object> resultMap = new HashMap<String, Object>(); //{"isfollow":true,"islike":true,"isunlike":false,"iscollection":true} resultMap.put("isfollow", isFollow); resultMap.put("islike", islike); resultMap.put("isunlike", isunlike); resultMap.put("iscollection", isCollection); //2.通过feign调用查询 行为实体 Integer userId = RequestContextUtil.getUserId();//张三 ApBehaviorEntry entry=null; if(RequestContextUtil.isAnonymous()){ entry = apBehaviorEntryFeign.findByUserIdOrEquipmentId(articleBehaviourDtoQuery.getEquipmentId(), SystemConstants.TYPE_E); }else{ entry = apBehaviorEntryFeign.findByUserIdOrEquipmentId(userId, SystemConstants.TYPE_USER); } if(entry==null){ return resultMap; } //3.查询是否关注 查询是否喜欢 查询是否收藏 查询是否点赞 //3.1 通过feign调用查询 是否喜欢 ApUnlikesBehavior unlikesBehavior = apUnlikesBehaviorFeign.getUnlikesBehavior(articleBehaviourDtoQuery.getArticleId(), entry.getId()); if (unlikesBehavior!=null && "1".equals(unlikesBehavior.getType().toString())) { isunlike=true; resultMap.put("isunlike",isunlike); } //3.2 通过feign调用查询 是否点赞 ApLikesBehavior likesBehavior = apLikesBehaviorFeign.getLikesBehavior(articleBehaviourDtoQuery.getArticleId(), entry.getId()); //1标识点赞 if(likesBehavior!=null && ("1").equals(likesBehavior.getOperation().toString())){ islike=true;//点赞了 resultMap.put("islike",islike); } //3.3 通过feign调用查询 是否关注 ApAuthor apAuthor = apAuthorMapper.selectById(articleBehaviourDtoQuery.getAuthorId()); if(apAuthor!=null){ //作者对应的ap_user_id的值 Integer followId = apAuthor.getUserId(); ApUserFollow apUserFollow = apUserFollowFeign.getApUserFollow(followId, userId); //3.3.2 再根据该appuserId 和当前的用户的appUserId 从关注表中获取到信息 如果不为空即可 if(apUserFollow!=null){ isFollow=true; resultMap.put("isfollow",isFollow); } } //3.3.1 首先根据表结构获取相关的思路(根据页面传递的作者的ID 获取到作者表信息 获取到对应的appuser的ID) //3.3.2 再根据该appuserId 和当前的用户的appUserId 从关注表中获取到信息 如果不为空即可 //3.4 查询是否收藏 //3.4 查询是否收藏 QueryWrapper<ApCollection> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("entry_id", entry.getId()); queryWrapper.eq("article_id", articleBehaviourDtoQuery.getArticleId()); queryWrapper.eq("type", 0); ApCollection apCollection = apCollectionMapper.selectOne(queryWrapper); if (apCollection != null) { isCollection = true; resultMap.put("iscollection", isCollection); } //4.获取之后组合map 返回即可 return resultMap; }