1)初步环境搭建
1.搭建行为模块
2.模块配置文件,nacos配置文件 (该服务无网关,直接写api即可)
1.1)yml
server:
port: 51805
spring:
application:
name: leadnews-behavior
cloud:
nacos:
discovery:
server-addr: 192.168.233.136:8848
config:
server-addr: 192.168.233.136:8848
file-extension: yml
1.2)nacos
1.2.1)behavior
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.233.136:3306/leadnews_behavior?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.heima.model.behavior.pojos
1.2.2)app-gateway
新增如下
# 行为微服务
- id: leadnews-behavior
uri: lb://leadnews-behavior
predicates:
- Path=/behavior/**
filters:
- StripPrefix= 1
注意事项
- 点赞,阅读(次数,时长,比例,加载时长),踩,需要专门的微服务
- 所有数据放到redis
- 关注功能在user服务完成
- 收藏与点赞过的记录的回显 需要在article服务实现
不需要额外数据库!,不过这里有数据库依赖,还是写着吧
1.3)中途问题
1.1.1)likes_behavior503
口头描述一下吧,就是minio的静态页面会发请求到window也就是宿主机本机的nginx的服务器,然后代理转发到 java程序的behavior服务端口, 但是我们之前写错了写成了虚拟机的ip
不过虚拟机里没有部署nginx,因此出现503错误
玛德,他后面一集原来有讲啊,我自己琢磨半天,还翻评论区,死活想不到他那个请求怎么来的,我一开始以为前端写死的呢,差点放弃了byd泪目了
解决
删除原来的js文件
重新编辑后上传
这个js文件在一开始的资料里,也就是我们minio初始化传的js和css文件
清除浏览器数据再发起请求
预检请求成功
1.1.2)精度丢失问题
00结尾,因为后端存为long类型的,网络传输时精度丢失
解决思路
使用jackson进行序列化和反序列化
Jackson配置
后端发前端,id转string,前端发后端,转Long和integer
前端字符串方式展示和返回后端
拷贝到common模块下,配置到spring容器,
自定义注解
拷贝到model下的annotation包
精度恢复
在想要序列化的属性加该注解也能生效
2)点赞/取消点赞
2.1)概述

点赞,保存行为,也可以取消点赞
2.2)思路分析
1.判断是否登录。(从别的服务拷一个拦截器,线程里保存了user对象)
public class AppTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if(userId != null){
//存入到当前线程中
ApUser apUser = new ApUser();
apUser.setId(Integer.valueOf(userId));
AppThreadLocalUtil.setUser(apUser);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AppThreadLocalUtil.clear();
}
}
当然还要注册该拦截器才能生效
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
}
}
2.参数空值校验
3.拼接key值,为文章id和type 值为operation。
4.点赞查redis,有就返回已点赞,(防止同时发送两个请求)没有就set,
(这里可能有多个用户对一篇文章进行点赞,我们将点赞行为做为一个最外的键值,然后第二层键值为用户id,key,hkey,value ,value随便填一个就行,因为主要判断有无key值)
取消点赞查redis,有就删掉,返回已取消,没有也返回已取消
2.3)dto
@Data
public class LikesBehaviorDto {
// 文章id
private Long articleId;
// 操作类型 0 点赞 1 取消点赞
private Short operation;
// 对哪个类型的行为 0 文章 1动态 2评论
private Short type;
}
2.4)业务层
2.4.1)常量
为什么以:结尾呢? 在redis客户端或者是 navicat识别:进行分层展示
例如 点赞行为, LIKE BEHAVIOR 这个行为 可对应N多篇文章, 在分为是哪个user点的, 也就是将key值划分更加细粒化了
(多层级,使用多个:最后一层不需要)
设置:
前
设置:
后
public class BeHaviorConstants {
public static final String LIKE_ARTICLE ="LIKE-ARTICLE:";
public static final String LIKE_COMMENT ="LIKE-COMMENT:";
public static final String LIKE_DYNAMIC ="LIKE-DYNAMIC:";
public static final String READ_ARTICLE ="READ-ARTICLE:";
public static final String UNLIKE_ARTICLE ="UNLIKE-ARTICLE:";
public static final String FOLLOW_AUTHOR ="FOLLOW-AUTHOR:";
public static final String COLLECTION_ARTICLE ="COLLECTION-ARTICLE:";
public static final String COLLECTION_DYNAMIC ="COLLECTION-DYNAMIC:";
}
service
/**
* 用户点赞
*/
ResponseResult userLikesArticle (LikesBehaviorDto dto);
impl
@Service
public class LikeServiceImpl implements LikeService {
@Autowired
private CacheService cacheService;
/**
* 用户点赞
*
* @param dto
*/
@Override
public ResponseResult userLikesArticle(LikesBehaviorDto dto) {
// 1.登录状态判断
String userId = AppThreadLocalUtil.getUser().getId().toString();
if (userId.isEmpty()) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 2.参数校验
if (dto == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 3.拼接key,后续查找
String key = "";// LIKE-ARTICLE/COMMENT-123451
if (dto.getType() == 0) {
key = BeHaviorConstants.LIKE_ARTICLE + dto.getArticleId()+":";
}
if (dto.getType() == 1) {
key = BeHaviorConstants.LIKE_DYNAMIC + dto.getArticleId()+":";
}
if (dto.getType() == 2) {
key = BeHaviorConstants.LIKE_COMMENT + dto.getArticleId()+":";
}
// 3.判断是点赞还是取消
if (dto.getOperation() == 0) {
if (cacheService.hGet(key,userId)!=null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"已点赞,请勿重复操作");
}
cacheService.hPut(key,userId, JSON.toJSONString(dto));
} else {
cacheService.hDelete(key,userId);
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
2.5)controller
@RestController
@RequestMapping("/api/v1/likes_behavior")
public class LikeController {
@Autowired
private LikeService likeService;
@PostMapping
public ResponseResult LikeBehavior(@RequestBody LikesBehaviorDto dto){
return likeService.userLikesArticle(dto);
}
}
3)浏览阅读次数
3.1)概述
进入一次文章就发一次请求,count数量加1存redis
一股子crud的味道扑面而来
3.2)思路分析
1.登录校验
2.参数校验
3.查出来,转dto,根据查出来的count和前端传来的count相加,再put (由于redis里全是字符串,我们想要让字符串和对象之间转换怎么办??? JSON parse Stringfy咯,那我们查的时候也要转)
这里前端传来的articleId为字符串,接口写着long,离谱
不过Long也能接收其string,算了还是Long吧,匹配一下数据库
好像经过了@RequestBody 前端转来的json字符串作为请求体,会反序列化为java对象,articleId字符串如果反序列化为long会经过 Long.valueOf(str);应该,如果是String接收就不用 Long.valueOf(str);
3.3)dto
@Data
public class ReadBehaviorDto {
private Long articleId;
private int count;
}
3.4)业务层
service
/**
* 用户点赞
*/
ResponseResult userLikesArticle (LikesBehaviorDto dto);
impl
这里我们把最特别也就是 从redis查出来的dto与前端的count进行求和,我们提到前面去用过五关斩六将思维 可以少写一个hPUT
@Service
public class ReadServiceImpl implements ReadService {
@Autowired
private CacheService cacheServicel;
/**
* 用户访问,记录+1
*
* @param dto
*/
@Override
public ResponseResult userReadArticle(ReadBehaviorDto dto) {
// 1.登录校验
ApUser user = AppThreadLocalUtil.getUser();
if (user == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 2.参数校验
if (dto == null || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 2.1拼串
String key = BeHaviorConstants.READ_ARTICLE + dto.getArticleId()+":";
// 3.查出来,转dto,根据查出来的count和前端传来的count相加,再put
// 这一步不能直接查出来转对象,因为有可能为空,后续再转对象
// 在redis里,转对象,给dto加值,然后再Hput到redis
// 不在,那就直接拿前端那个dto 塞进去
// 注意这里Object不能.toString 因为有可能为空,应cast转为string
String redisString = (String) cacheServicel.hGet(key, user.getId().toString());
if (redisString != null) {
ReadBehaviorDto readBehaviorDto = JSON.parseObject(redisString, ReadBehaviorDto.class);
dto.setCount(readBehaviorDto.getCount() + dto.getCount());
}
cacheServicel.hPut(BeHaviorConstants.READ_ARTICLE + dto.getArticleId().toString(), user.getId().toString(), JSON.toJSONString(dto));
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
3.5)controller
@RestController
@RequestMapping("/api/v1/read_behavior")
public class ReadController {
@Autowired
private ReadService readService;
@PostMapping
ResponseResult ReadBehavior(@RequestBody ReadBehaviorDto dto) {
return readService.userReadArticle(dto);
}
}
4)不喜欢
4.1)思路
1.对应行为和文章id为key和用户id为hkey ,然后value值任意即可
4.2)dto
@Data
public class UnLikeBehaviorDto {
Long articleId;
Short type;
}
4.3)业务层
service
public interface UnLikeService {
/**
* 用户取消点赞
* @param dto
* @return
*/
ResponseResult unlike(UnLikeBehaviorDto dto);
}
impl
@Service
public class UnLikeServiceImpl implements UnLikeService {
@Autowired
private CacheService cacheService;
/**
* 用户取消点赞
*
* @param dto
* @return
*/
@Override
public ResponseResult unlike(UnLikeBehaviorDto dto) {
if (dto == null || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 1.根据对应行为和文章id和用户id 组成一个大key,然后值任意即可
String key = BeHaviorConstants.UNLIKE_ARTICLE + dto.getArticleId()+":";
String userId = AppThreadLocalUtil.getUser().getId().toString();
Object hGet = cacheService.hGet(key, userId);
if (hGet != null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"已不喜欢,请勿重复操作");
}
cacheService.hPut(key, userId, JSON.toJSONString(dto));
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
4.4)controller
@RestController
@RequestMapping("/api/v1/un_likes_behavior")
public class UnLikeController {
@Autowired
private UnLikeService unLikeService;
@PostMapping
ResponseResult UnLikeArticle(@RequestBody UnLikeBehaviorDto dto) {
return unLikeService.unlike(dto);
}
}
5)关注
5.1)dto
@Data
public class UserRelationDto {
Long articleId;
Integer authorId;
Short operation;
}
5.2)业务层
service
public interface ApUserFollowService {
/**
* 用户关注/取消关注作者
*/
ResponseResult follow(UserRelationDto dto);
}
impl
@Service
public class ApUserFollowServiceImpl implements ApUserFollowService {
@Autowired
private CacheService cacheService;
/**
* 用户关注/取消关注作者
*
* @param dto
*/
@Override
public ResponseResult follow(UserRelationDto dto) {
String userId = AppThreadLocalUtil.getUser().getId().toString();
if (dto == null || dto.getArticleId() == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
String key = BeHaviorConstants.FOLLOW_AUTHOR + dto.getAuthorId()+":";
if (dto.getOperation() == 0) {
cacheService.hPut(key, userId, JSON.toJSONString(dto));
} else {
cacheService.hDelete(key, userId);
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
5.3)controller
@RestController
@RequestMapping("/api/v1/user/user_follow")
public class ApUserFollowController {
@Autowired
private ApUserFollowService userFollowService;
@PostMapping
public ResponseResult follow(@RequestBody UserRelationDto dto){
return userFollowService.follow(dto);
}
}
6)文章收藏
6.1)dto
@Data
public class CollectionBehaviorDto {
Long entryId;
Short operation;
Date publishedTime;
Short type;
}
6.2)业务层
service
public interface ApUserCollectionService {
/**
* 用户收藏文章
*/
ResponseResult collect (CollectionBehaviorDto dto);
}
impl
@Service
public class ApUserCollectionServiceImpl implements ApUserCollectionService {
@Autowired
private CacheService cacheService;
/**
* 用户收藏文章
*
* @param dto
*/
@Override
public ResponseResult collect(CollectionBehaviorDto dto) {
// 1.登录校验
String userId = AppThreadLocalUtil.getUser().getId().toString();
if (userId == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 2.参数校验
if (dto == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 3.判断是文章还是动态的收藏,拼字符串
String key = dto.getType() == 0 ? BeHaviorConstants.COLLECTION_ARTICLE + dto.getEntryId() + ":" :
BeHaviorConstants.COLLECTION_DYNAMIC + dto.getEntryId() + ":";
// 4.判断是收藏还是取消收藏
if (dto.getOperation() == 0) {
cacheService.hPut(key, userId, JSON.toJSONString(dto));
} else {
cacheService.hDelete(key, userId);
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
6.3)controller
@RestController
@RequestMapping("/api/v1/collection_behavior")
public class ApCollectionController {
@Autowired
private ApCollectionService apCollectionService;
@PostMapping
public ResponseResult collection(@RequestBody CollectionBehaviorDto dto) {
return apCollectionService.collection(dto);
}
}
7)详情数据回显
7.1)dto
@Data
public class ArticleInfoDto {
Long articleId;
Integer authorId;
}
7.2)业务层
service
public interface ApArticleBehaviorDetailService {
/**
* 返回用户对文章做了什么行为的详细记录
*/
ResponseResult behaviorDetails(ArticleInfoDto info);
}
impl
@Service
public class ApArticleBehaviorDetailServiceImpl implements ApArticleBehaviorDetailService {
@Autowired
private CacheService cacheService;
/**
* 返回用户对文章做了什么行为的详细记录
*
* @param info
*/
@Override
public ResponseResult behaviorDetails(ArticleInfoDto dto) {
// 1.登录校验
String userId = AppThreadLocalUtil.getUser().getId().toString();
if (userId == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
// 2.参数校验
if (dto == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// 查询点赞、收藏、不喜欢记录,是否关注该作者
String likeKey = BeHaviorConstants.LIKE_ARTICLE + dto.getArticleId();
String unlikeKey = BeHaviorConstants.UNLIKE_ARTICLE + dto.getArticleId();
String collectKey = BeHaviorConstants.COLLECTION_ARTICLE + dto.getArticleId();
String followKey = BeHaviorConstants.FOLLOW_AUTHOR + dto.getAuthorId();
boolean islike = cacheService.hGet(likeKey, userId) != null ? true : false;
boolean isunlike = cacheService.hGet(unlikeKey, userId) != null ? true : false;
boolean iscollect = cacheService.hGet(collectKey, userId) != null ? true : false;
boolean isfollow = cacheService.hGet(followKey, userId) != null ? true : false;
return ResponseResult.okResult(new ArticleInfoVo(islike,isunlike,iscollect,isfollow));
}
}
7.3)controller
@RestController
@RequestMapping("/api/v1/article/load_article_behavior")
public class ApArticleBehaviorDetailController {
@Autowired
private ApArticleBehaviorDetailService articleBehaviorDetailService;
@PostMapping
ResponseResult articleBehaviorDetail(@RequestBody ArticleInfoDto dto){
return articleBehaviorDetailService.behaviorDetails(dto);
}
}
8)总体效果
全部通过,效果图就不截图了,下一关了哥几个
算了还是截一下吧
