ユーザー機能実装ガイド:いいね、設定、ブラックリスト管理

機能概要

  • いいね機能の実装
  • ユーザー共通設定機能
  • ブラックリスト機能
  • 電話番号変更機能

1. いいね統計表示

1.1 基本概念

  • いいね:他のユーザーに対する評価(例:AがBにいいねしても、BがAにいいねしたとは限らない)
  • フォロワー:自分をいいねしたユーザー
  • 相互フォロー:お互いにいいねした状態

1.2 Dubboサービス

//UserLikeApi.java
public interface UserLikeApi {
    /**
     * 相互いいね数の取得
     */
    Long getMutualFollowCount(Long userId);
    
    /**
     * いいね数の取得
     */
    Long getFollowCount(Long userId);
    
    /**
     * フォロワー数の取得
     */
    Long getFollowerCount(Long userId);
}
//UserLikeApiImpl.java
@Override
public Long getMutualFollowCount(Long userId) {
    List<Long> followingList = queryFollowingList(userId);
    Long mutualCount = 0L;
    
    for (Long targetId : followingList) {
        String redisKey = buildFollowKey(targetId);
        if (redisTemplate.opsForHash().hasKey(redisKey, String.valueOf(userId))) {
            mutualCount++;
        }
    }
    return mutualCount;
}

@Override
public Long getFollowCount(Long userId) {
    String redisKey = buildFollowKey(userId);
    return redisTemplate.opsForHash().size(redisKey);
}

@Override
public Long getFollowerCount(Long userId) {
    Query query = Query.query(Criteria.where("targetUserId").is(userId));
    return mongoTemplate.count(query, FollowRelation.class);
}

1.3 API実装

//FollowStatsVo.java
@Data
public class FollowStatsVo {
    private Long mutualCount;
    private Long followingCount;
    private Long followerCount;
}
//UserController.java
@GetMapping("/stats")
public ResponseEntity<FollowStatsVo> getFollowStats() {
    try {
        FollowStatsVo stats = userService.getFollowStats();
        return ResponseEntity.ok(stats);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

2. フォローリスト表示

2.1 フォロー機能のDubbo実装

//UserLikeApi.java
public interface UserLikeApi {
    /**
     * 相互フォローリスト取得
     */
    PageInfo<FollowRelation> getMutualFollows(Long userId, Integer page, Integer size);
    
    /**
     * フォローリスト取得
     */
    PageInfo<FollowRelation> getFollowingList(Long userId, Integer page, Integer size);
    
    /**
     * フォロワーリスト取得
     */
    PageInfo<FollowRelation> getFollowerList(Long userId, Integer page, Integer size);
}
//UserLikeApiImpl.java
@Override
public PageInfo<FollowRelation> getMutualFollows(Long userId, Integer page, Integer size) {
    List<Long> followingIds = queryFollowingList(userId);
    Query query = Query.query(
        Criteria.where("userId").in(followingIds)
        .and("targetUserId").is(userId)
    );
    return executePaginatedQuery(query, page, size);
}

@Override
public PageInfo<FollowRelation> getFollowingList(Long userId, Integer page, Integer size) {
    Query query = Query.query(Criteria.where("userId").is(userId));
    return executePaginatedQuery(query, page, size);
}

@Override
public PageInfo<FollowRelation> getFollowerList(Long userId, Integer page, Integer size) {
    Query query = Query.query(Criteria.where("targetUserId").is(userId));
    return executePaginatedQuery(query, page, size);
}

2.2 訪問者リスト機能

//VisitorApi.java
public interface VisitorApi {
    /**
     * 最新訪問者リスト取得
     */
    PageInfo<Visitor> getRecentVisitors(Long userId, Integer page, Integer size);
}
//VisitorApiImpl.java
@Override
public PageInfo<Visitor> getRecentVisitors(Long userId, Integer page, Integer size) {
    PageRequest pagination = PageRequest.of(page - 1, size, 
        Sort.by(Sort.Direction.DESC, "visitTime"));
    
    Query query = Query.query(Criteria.where("profileOwnerId").is(userId))
        .with(pagination);
    
    List<Visitor> visitors = mongoTemplate.find(query, Visitor.class);
    enrichWithMatchScore(visitors, userId);
    
    PageInfo<Visitor> result = new PageInfo<>();
    result.setRecords(visitors);
    result.setPageNum(page);
    result.setPageSize(size);
    
    updateLastViewTime(userId);
    return result;
}

2.3 APIエンドポイント実装

//FollowListVo.java
@Data
public class FollowListVo {
    private Long userId;
    private String avatar;
    private String nickname;
    private String gender;
    private Integer age;
    private String location;
    private String education;
    private String maritalStatus;
    private Integer matchScore;
    private Boolean isFollowing;
}
//FollowController.java
@GetMapping("/list/{type}")
public ResponseEntity<PageResult> getFollowList(
    @PathVariable Integer type,
    @RequestParam(defaultValue = "1") Integer page,
    @RequestParam(defaultValue = "10") Integer size,
    @RequestParam(required = false) String keyword) {
    
    try {
        PageResult result = followService.getFollowList(type, page, size, keyword);
        return ResponseEntity.ok(result);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

2.4 フォロー解除機能

//FollowController.java
@DeleteMapping("/unfollow/{targetId}")
public ResponseEntity<Void> unfollowUser(@PathVariable Long targetId) {
    try {
        followService.unfollowUser(targetId);
        return ResponseEntity.ok().build();
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}
//FollowService.java
public void unfollowUser(Long targetId) {
    Long currentUserId = getCurrentUserId();
    boolean wasMutual = followApi.isMutualFollow(currentUserId, targetId);
    
    followApi.removeFollow(currentUserId, targetId);
    
    if (wasMutual) {
        chatService.removeFriend(targetId);
    }
}

3. ユーザー設定機能

3.1 データベース構造

CREATE TABLE `user_preferences` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT NOT NULL,
  `like_notifications` TINYINT(1) DEFAULT 1,
  `comment_notifications` TINYINT(1) DEFAULT 1,
  `announcement_notifications` TINYINT(1) DEFAULT 1,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX `idx_user_id` (`user_id`)
);

3.2 エンティティクラス

//UserPreferences.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserPreferences {
    private Long id;
    private Long userId;
    private Boolean likeNotifications = true;
    private Boolean commentNotifications = true;
    private Boolean announcementNotifications = true;
}

3.3 設定取得機能

//SettingsController.java
@GetMapping("/preferences")
public ResponseEntity<UserSettingsVo> getUserSettings() {
    try {
        UserSettingsVo settings = settingsService.getUserSettings();
        return ResponseEntity.ok(settings);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}
//UserSettingsVo.java
@Data
public class UserSettingsVo {
    private Long userId;
    private String phoneNumber;
    private String questionForStrangers = "";
    private Boolean likeNotification = true;
    private Boolean commentNotification = true;
    private Boolean announcementNotification = true;
}

4. ブラックリスト機能

4.1 テーブル設計

CREATE TABLE `blocked_users` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT NOT NULL,
  `blocked_user_id` BIGINT NOT NULL,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX `idx_user_id` (`user_id`),
  INDEX `idx_blocked_user_id` (`blocked_user_id`)
);

4.2 ブラックリスト管理

//BlockListController.java
@GetMapping("/blocked")
public ResponseEntity<PageResult> getBlockedList(
    @RequestParam(defaultValue = "1") Integer page,
    @RequestParam(defaultValue = "10") Integer size) {
    
    try {
        PageResult result = blockService.getBlockedList(page, size);
        return ResponseEntity.ok(result);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

@DeleteMapping("/blocked/{userId}")
public ResponseEntity<Void> unblockUser(@PathVariable Long userId) {
    try {
        blockService.unblockUser(userId);
        return ResponseEntity.ok().build();
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

5. 電話番号変更機能

5.1 認証コード送信

//PhoneUpdateController.java
@PostMapping("/verify-code/send")
public ResponseEntity<Void> sendVerifyCode(
    @RequestHeader("Authorization") String token) {
    
    try {
        phoneUpdateService.sendVerificationCode(token);
        return ResponseEntity.ok().build();
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

5.2 認証コード検証

//PhoneUpdateController.java
@PostMapping("/verify-code/validate")
public ResponseEntity<Map<String, Object>> validateCode(
    @RequestBody Map<String, String> request,
    @RequestHeader("Authorization") String token) {
    
    try {
        String code = request.get("code");
        boolean isValid = phoneUpdateService.validateVerificationCode(code, token);
        Map<String, Object> response = Map.of("valid", isValid);
        return ResponseEntity.ok(response);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

5.3 電話番号更新

//PhoneUpdateController.java
@PutMapping("/phone-number")
public ResponseEntity<Void> updatePhoneNumber(
    @RequestBody Map<String, String> request,
    @RequestHeader("Authorization") String token) {
    
    try {
        String newPhone = request.get("phoneNumber");
        phoneUpdateService.updatePhoneNumber(newPhone, token);
        return ResponseEntity.ok().build();
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_ERROR).build();
    }
}

タグ: Java SpringBoot Dubbo MongoDB redis

6月13日 00:46 投稿