黑马头条第九天实战全部测试通过,附代码和实现思路

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文件

image-20240918182728082

重新编辑后上传

image-20240918182753317
这个js文件在一开始的资料里,也就是我们minio初始化传的js和css文件

image-20240918182841636

清除浏览器数据再发起请求

image-20240918182917831
预检请求成功

1.1.2)精度丢失问题

image-20240913133715695

00结尾,因为后端存为long类型的,网络传输时精度丢失

解决思路

使用jackson进行序列化和反序列化

Jackson配置

image-20240913133942007

后端发前端,id转string,前端发后端,转Long和integer

前端字符串方式展示和返回后端

拷贝到common模块下,配置到spring容器,

image-20240913134316063

自定义注解

拷贝到model下的annotation包

image-20240913134716611

精度恢复

image-20240913134856719

在想要序列化的属性加该注解也能生效

image-20240913134937620

2)点赞/取消点赞

2.1)概述

image-20240913121253597

点赞,保存行为,也可以取消点赞

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值划分更加细粒化了

(多层级,使用多个:最后一层不需要)

设置:

设置:

image-20240919161531611

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,离谱

image-20240918222620361
image-20240918222721598

不过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)总体效果

全部通过,效果图就不截图了,下一关了哥几个

算了还是截一下吧

image-20240919163316144
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值