Redisを用いたブログのいいね・フォロー機能の実装

ブログ投稿と閲覧機能の実装

ブログ投稿処理

@PostMapping
public Result createBlogPost(@RequestBody BlogEntry blogEntry) {
    UserDTO currentUser = UserContext.getCurrentUser();
    blogEntry.setAuthorId(currentUser.getId());
    blogService.save(blogEntry);
    return Result.success(blogEntry.getId());
}

画像アップロード処理

@PostMapping("upload")
public Result uploadImageFile(@RequestParam("file") MultipartFile file) {
    try {
        String originalName = file.getOriginalFilename();
        String newFileName = generateUniqueFileName(originalName);
        file.transferTo(new File(Constants.IMAGE_DIRECTORY, newFileName));
        return Result.success(newFileName);
    } catch (IOException ex) {
        throw new RuntimeException("画像アップロード失敗", ex);
    }
}

ブログ詳細取得処理

@GetMapping("/{blogId}")
public Result getBlogDetails(@PathVariable Integer blogId) {
    return blogService.getBlogDetails(blogId);
}

@Override
public Result getBlogDetails(Integer blogId) {
    BlogEntry blog = getById(blogId);
    if (blog == null) {
        return Result.failure("ブログが存在しません");
    }
    populateAuthorInfo(blog);
    return Result.success(blog);
}

private void populateAuthorInfo(BlogEntry blog) {
    User author = userService.getById(blog.getAuthorId());
    blog.setAuthorName(author.getDisplayName());
    blog.setAuthorAvatar(author.getAvatarUrl());
}

いいね機能の実装

いいね状態管理

@PutMapping("/like/{blogId}")
public Result toggleLike(@PathVariable("blogId") Long blogId) {
    return blogService.handleLikeAction(blogId);
}

@Override
public Result handleLikeAction(Long blogId) {
    Long currentUserId = UserContext.getCurrentUser().getId();
    String redisKey = Constants.BLOG_LIKES_PREFIX + blogId;
    
    Boolean alreadyLiked = redisTemplate.opsForSet()
        .isMember(redisKey, currentUserId.toString());
    
    if (Boolean.FALSE.equals(alreadyLiked)) {
        boolean updated = update().setSql("like_count = like_count + 1")
            .eq("id", blogId).update();
        if (updated) {
            redisTemplate.opsForSet().add(redisKey, currentUserId.toString());
        }
    } else {
        boolean updated = update().setSql("like_count = like_count - 1")
            .eq("id", blogId).update();
        if (updated) {
            redisTemplate.opsForSet().remove(redisKey, currentUserId.toString());
        }
    }
    return Result.success();
}

いいね状態の判定

private void checkLikeStatus(BlogEntry blog) {
    UserDTO currentUser = UserContext.getCurrentUser();
    if (currentUser == null) return;
    
    String redisKey = Constants.BLOG_LIKES_PREFIX + blog.getId();
    Boolean isLiked = redisTemplate.opsForSet()
        .isMember(redisKey, currentUser.getId().toString());
    blog.setLiked(Boolean.TRUE.equals(isLiked));
}

いいねランキング機能

トップいいねユーザー取得

@GetMapping("/likes/{blogId}")
public Result getTopLikers(@PathVariable Integer blogId) {
    return blogService.getTopLikers(blogId);
}

@Override
public Result getTopLikers(Integer blogId) {
    String redisKey = Constants.BLOG_LIKES_PREFIX + blogId;
    Set<String> topLikerIds = redisTemplate.opsForZSet()
        .range(redisKey, 0, 4);
    
    if (topLikerIds == null || topLikerIds.isEmpty()) {
        return Result.success(Collections.emptyList());
    }
    
    List<Long> userIds = topLikerIds.stream()
        .map(Long::valueOf).collect(Collectors.toList());
    
    List<UserDTO> topUsers = userService.listByIds(userIds).stream()
        .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
        .collect(Collectors.toList());
    
    return Result.success(topUsers);
}

フォロー機能の実装

フォロー状態管理

@RestController
@RequestMapping("/follow")
public class FollowController {
    
    @PostMapping("/{targetUserId}/{follow}")
    public Result updateFollowStatus(@PathVariable Long targetUserId, 
                                   @PathVariable Boolean follow) {
        return followService.updateFollowStatus(targetUserId, follow);
    }
}

@Service
public class FollowService {
    
    public Result updateFollowStatus(Long targetUserId, Boolean shouldFollow) {
        Long currentUserId = UserContext.getCurrentUser().getId();
        String redisKey = Constants.FOLLOWS_PREFIX + currentUserId;
        
        if (shouldFollow) {
            FollowRelation relation = new FollowRelation();
            relation.setFollowerId(currentUserId);
            relation.setTargetUserId(targetUserId);
            if (save(relation)) {
                redisTemplate.opsForSet().add(redisKey, targetUserId.toString());
            }
        } else {
            remove(new QueryWrapper<FollowRelation>()
                .eq("follower_id", currentUserId)
                .eq("target_user_id", targetUserId));
            redisTemplate.opsForSet().remove(redisKey, targetUserId.toString());
        }
        return Result.success();
    }
}

共通フォロー機能

共通フォローユーザー取得

@GetMapping("/common/{otherUserId}")
public Result getCommonFollows(@PathVariable Long otherUserId) {
    return followService.getCommonFollows(otherUserId);
}

@Override
public Result getCommonFollows(Long otherUserId) {
    Long currentUserId = UserContext.getCurrentUser().getId();
    String currentUserKey = Constants.FOLLOWS_PREFIX + currentUserId;
    String otherUserKey = Constants.FOLLOWS_PREFIX + otherUserId;
    
    Set<String> commonFollowIds = redisTemplate.opsForSet()
        .intersect(currentUserKey, otherUserKey);
    
    if (commonFollowIds == null || commonFollowIds.isEmpty()) {
        return Result.success(Collections.emptyList());
    }
    
    List<UserDTO> commonUsers = userService.listByIds(
        commonFollowIds.stream().map(Long::valueOf).collect(Collectors.toList()))
        .stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))
        .collect(Collectors.toList());
    
    return Result.success(commonUsers);
}

タグ: redis SpringBoot Java データベース キャッシュ

7月24日 04:39 投稿