JAVA-实战8 Redis实战项目—雷神点评(8)网红探店

切なくて時をまきもどしてみるかい?

网红探店

探店齁币多,真假雷神说

发布探店笔记

实现代码如下:

// 控制层方法
@RestController
@RequestMapping("/blog")
public class BlogController {
    @Autowired
    private BlogService blogService;

    @PostMapping
    public ResultData UploadBlog(@RequestBody BlogData NewBlog) {
        return blogService.UploadBlog(NewBlog);
    }
}

// 服务层方法
@Service
public class BlogServiceImpl implements BlogService {
    @Autowired
    private BlogMapper blogMapper;

    @Override
    public ResultData UploadBlog(BlogData NewBlog) {
        Long UserId = Long.valueOf(CurrentHolder.getCurrent().getId());
        NewBlog.setUserId(UserId);
        blogMapper.insert(NewBlog);
        return ResultData.success(NewBlog.getId());
    }
}

测试效果如下:
image
image

查看探店笔记

实现代码如下:

// 控制层方法
@GetMapping("/{id}")
public ResultData QueryBlogById(@PathVariable("id") Long BlogId) {
    return blogService.QueryBlogById(BlogId);
}

// 服务层方法
@Override
public ResultData QueryBlogById(Long BlogId){

    BlogData Result = blogMapper.selectById(BlogId);
    if(Result==null) {
        return ResultData.error("Blog not exist!");
    }

    return ResultData.success(Result);
}

测试效果如下:
image

探店笔记点赞

实现点赞操作,并且每次查询探店笔记返回该笔记是否被当前用户点赞。现实中同一个用户点赞一次会点赞次数+1,再次点赞会取消

数据库实现

考虑数据库维护每个用户-探店笔记组合的点赞次数,根据奇偶性判断

实现代码如下:

// 控制层方法
@PutMapping("/like/{id}")
    public ResultData LikeBlog(@PathVariable("id") Long BlogId) {
        System.out.println("Now is "+BlogId);
        return blogService.LikeBlog(BlogId);
    }

// 服务层方法
@Override
@Transactional
public ResultData LikeBlog(Long BlogId) {
    Long UserId = Long.valueOf(CurrentHolder.getCurrent().getId());

    LambdaQueryWrapper<LikeData> lwp = new LambdaQueryWrapper<LikeData>();
    // 查询当前用户对于点赞的探店笔记的点赞记录
    lwp.eq(LikeData::getBlogId,BlogId).eq(LikeData::getUserId, UserId).select(LikeData::getLikedCount);
    LikeData LikeRecord = likeMapper.selectOne(lwp);
    
    if(LikeRecord==null) {
        // 没有记录代表没有点赞过
        LikeData NewLikeRecord = new LikeData();
        NewLikeRecord.setBlogId(BlogId);
        NewLikeRecord.setUserId(UserId);
        NewLikeRecord.setLikedCount(1L);
        likeMapper.insert(NewLikeRecord);

        LambdaUpdateWrapper<BlogData> BlogLwp = new LambdaUpdateWrapper<BlogData>();
        BlogLwp.eq(BlogData::getId,BlogId);
        blogMapper.UpdateBlogLike(BlogLwp,1);
        return ResultData.success("You Like Successfully!");
    }else {
        // 有点赞记录,根据点赞次数奇偶性判断点赞次数 +1/-1
        Long LikeCount = LikeRecord.getLikedCount();

        LambdaUpdateWrapper<LikeData> LikeLwp = new LambdaUpdateWrapper<LikeData>();
        LikeLwp.eq(LikeData::getBlogId,BlogId).eq(LikeData::getUserId,UserId);
        likeMapper.UpdateLikeRecord(LikeLwp,1);

        LambdaUpdateWrapper<BlogData> BlogLwp = new LambdaUpdateWrapper<BlogData>();
        BlogLwp.eq(BlogData::getId,BlogId);
        if(LikeCount%2==0) {
            blogMapper.UpdateBlogLike(BlogLwp,1);
            return ResultData.success("You Like Successfully!");
        }else {
            blogMapper.UpdateBlogLike(BlogLwp,-1);
            return ResultData.success("You Cancel Like Successfully!");
        }
    }
}

// XML配置文件实现SQL
// LikeMapper.xml
<mapper namespace="org.example.mapper.LikeMapper">
    <update id="UpdateLikeRecord">
        update tb_like set liked_count = liked_count + #{amount} ${ew.customSqlSegment}
    </update>
</mapper>
// BlogMapper.xml
<mapper namespace="org.example.mapper.BlogMapper">
    <update id="UpdateBlogLike">
        update tb_blog set liked = liked+#{amount} ${ew.customSqlSegment}
    </update>
</mapper>

测试效果如下:

第一次点赞
image
image
image
image

第二次点赞
image
image
image
image

第三次点赞
image
image
image
image

IsLiked字段判断+Redis优化

实际业务中点赞往往是百万千万级别的,如果全部执行数据库操作执行效率过度。采取如下优化手段:

前端每次传递IsLiked字段表示当前探店笔记是否被点赞过,如果为true表示已点赞过那么点赞次数-1,为fasle表示未点赞过那么点赞次数+1。

使用Redis替代数据库,Redis维护set结构,以探店笔记idBlogId作为key,点赞用户集合作为value,以此保证一人一赞

实现步骤如下:

①获取当前登录用户信息
②判断用户是否已经点赞
③根据是否点赞执行相应操作

实现代码如下:

@Override
@Transactional
public ResultData LikeBlog(Long BlogId) {
    Long UserId = Long.valueOf(CurrentHolder.getCurrent().getId());
    // 判断点赞集合中是否有当前用户
    String BlogKey = BLOG_LIKED_KEY + BlogId;
    Boolean UserLiked = stringRedisTemplate.opsForSet().isMember(BlogKey,UserId.toString());
    
    LambdaUpdateWrapper<BlogData> BlogLwp = new LambdaUpdateWrapper<BlogData>();
    BlogLwp.eq(BlogData::getId,BlogId);

    if(BooleanUtil.isFalse(UserLiked)){
        // 点赞次数+1 并且加入点赞集合
        blogMapper.UpdateBlogLike(BlogLwp,1);
        stringRedisTemplate.opsForSet().add(BlogKey,UserId.toString());
        return ResultData.success("You Like Successfully!");
    } else {
        // 点赞次数-1 并且移出点赞集合
        blogMapper.UpdateBlogLike(BlogLwp,-1);
        stringRedisTemplate.opsForSet().remove(BlogKey,UserId.toString());
        return ResultData.success("You Cancel Like Successfully!");
    }

}

每次查询判断是否被点赞过

@Override
public ResultData QueryBlogById(Long BlogId){

    BlogData Result = blogMapper.selectById(BlogId);
    if(Result==null) {
        return ResultData.error("Blog not exist!");
    }

    // 判断是否被点赞过
    boolean UserLikedBlog = UserIsLikedBlog(Result);
    Result.setIsLike(UserLikedBlog);

    return ResultData.success(Result);
}

private boolean UserIsLikedBlog(BlogData Result) {
    Long UserId = Long.valueOf(CurrentHolder.getCurrent().getId());

    String BlogKey = BLOG_LIKED_KEY + Result.getId();
    Boolean UserLiked = stringRedisTemplate.opsForSet().isMember(BlogKey,UserId.toString());
    // 使用isTrue方法
    return BooleanUtil.isTrue(UserLiked);
}

常量封装工具类:

public class RedisConstants {
    public static final String BLOG_LIKED_KEY = "blog:liked:";
}

查看探店笔记的完善

在添加了IsLiked字段之后,完善探店笔记查看功能,对于返回结果添加作者的头像、昵称以及当前用户是否点赞

@Override
public ResultData QueryBlogById(Long BlogId){
    BlogData Result = blogMapper.selectById(BlogId);
    if(Result==null) {
        return ResultData.error("Blog not exist!");
    }
    // 添加作者头像和昵称
    Long AuthorId = Result.getUserId();
    UserData AuthorData = userMapper.selectById(AuthorId);
    Result.setName(AuthorData.getNickName());
    Result.setIcon(AuthorData.getIcon());

    // 判断是否被点赞过
    boolean UserLikedBlog = UserIsLikedBlog(Result);
    Result.setIsLike(UserLikedBlog);

    return ResultData.success(Result);
}

完善后效果如下:
image

点赞排行榜

按照点赞时间先后排序,返回top5的用户,可选用以下数据结构:
image

选用ScoreSet数据结构,其在SpringRedis中使用StringRedisTemplate.opsForZSet()方法调用

修改点赞方法:

@Override
@Transactional
public ResultData LikeBlog(Long BlogId) {
    Long UserId = Long.valueOf(CurrentHolder.getCurrent().getId());
    // 判断点赞集合中是否有当前用户
    String BlogKey = BLOG_LIKED_KEY + BlogId;
    Double UserScore = stringRedisTemplate.opsForZSet().score(BlogKey,UserId.toString());

    LambdaUpdateWrapper<BlogData> BlogLwp = new LambdaUpdateWrapper<BlogData>();
    BlogLwp.eq(BlogData::getId,BlogId);

    if(UserScore==null){
        // 点赞次数+1 并且加入点赞集合
        blogMapper.UpdateBlogLike(BlogLwp,1);
        stringRedisTemplate.opsForZSet().add(BlogKey,UserId.toString(),System.currentTimeMillis());
        return ResultData.success("You Like Successfully!");
    } else {
        // 点赞次数-1 并且移出点赞集合
        blogMapper.UpdateBlogLike(BlogLwp,-1);
        stringRedisTemplate.opsForZSet().remove(BlogKey,UserId.toString());
        return ResultData.success("You Cancel Like Successfully!");
    }

}

测试效果如下:

点赞成功后,查看点赞用户集合
image
image

取消点赞,查看点赞用户集合
image
image

实现点赞排行榜:

@Override
public ResultData QueryBlogLikes(Long BlogId) {
    String BlogKey = BLOG_LIKED_KEY + BlogId;

    Set<String> Top5Set = stringRedisTemplate.opsForZSet().range(BlogKey,0,4);

    if(Top5Set==null||Top5Set.isEmpty()) {
        return ResultData.error("No One Liked This Blog!");
    }

    List<Long> Top5Ids = Top5Set.stream().map(Long::valueOf).toList();

    String IdStr = Top5Ids.stream().map(String::valueOf).collect(Collectors.joining(","));
    QueryWrapper<UserData> ew = new QueryWrapper<UserData>();
    ew.in("id",Top5Ids).orderByAsc("FIELD(id,"+IdStr+")");

    List<UserData> UserList = userMapper.selectList(ew);

    return ResultData.success(UserList);
}

Q:为什么已有用户Id列表,还要使用条件查询器而且后面要跟一个奇奇怪怪的orderByAsc?

A:如果我们不添加条件查询器直接使用selectByIds()方法查询,那么底层生成的SQL语句通常使用IN查询(例如WHERE id IN (1, 3, 2)),并不保证‌返回结果的顺序与IN子句中 ID 的顺序一致,通常默认按主键索引排序或无序返回,即返回的用户列表会照默认的用户id升序排序进行排列,这不符合我们预期的保持输入顺序从而实现按照点赞时间戳返回结果。因此,在查询条件中利用orderByAsc配合FIELD()函数来强制指定排序规则。FIELD(id, 1, 3, 2)会返回id在列表中的位置索引,从而实现按指定顺序排序。

使用stream()流将用户Id列表拼接成,分割的字符串,然后在orderByAsc()中拼接成为FIELD(id,...,)函数表达式的格式传递给条件查询器然后执行查询。

查看IDEA执行日志:

不添加条件查询器
image

添加条件查询器
image


测试效果如下:
image
image

posted @ 2026-05-07 20:09  tcswuzb  阅读(17)  评论(0)    收藏  举报