Pārlūkot izejas kodu

feature:add follower and follewee

ggbocoder 2 gadi atpakaļ
vecāks
revīzija
ac12fd75be

+ 2 - 0
README.md

@@ -28,7 +28,9 @@
 - 使用布隆过滤器屏蔽拉黑的ip下载应用
 - 使用Redis实现一致性哈希算法,使用Redis的ZSet实现用户综合评分
 - 使用kafka收集用户下载app量,使用kafka解耦构建流水线,当流水线构建完毕时通过kafka将消息通知到邮箱
+- 使用kafka+themeleaf实现订阅app更新功能
 - 加快下载速度,断点
+- 
 
 ## 评论点赞设计
 - 用Redis的set结构存储点赞情况,key:postId,value:set<userId>

+ 30 - 0
src/main/java/cn/seecoder/fdroidrepository/Controller/FollowController.java

@@ -0,0 +1,30 @@
+package cn.seecoder.fdroidrepository.Controller;
+
+
+import cn.seecoder.fdroidrepository.Service.ServiceImpl.FollowService;
+import cn.seecoder.fdroidrepository.result.SingleResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/fans")
+public class FollowController {
+    private Logger logger = LoggerFactory.getLogger(FollowController.class);
+    @Autowired
+    FollowService followService;
+
+    @PostMapping("/follow")
+    public SingleResult<?> follow(int followerId,int followeeId){
+        try {
+            followService.follow(followerId,followeeId);
+            return SingleResult.success("ok!");
+        } catch (InterruptedException e) {
+            logger.error("关注失败:" + e.getMessage());
+        }
+        return SingleResult.failure("400","failed");
+    }
+}

+ 0 - 1
src/main/java/cn/seecoder/fdroidrepository/Mapper/UserMapper.java

@@ -9,7 +9,6 @@ import org.apache.ibatis.annotations.Options;
 public interface UserMapper {
     User findByUsername(String username);
 
-
     long save(User user);
 
     User selectByUserId(int userId);

+ 119 - 0
src/main/java/cn/seecoder/fdroidrepository/Service/ServiceImpl/FollowService.java

@@ -0,0 +1,119 @@
+package cn.seecoder.fdroidrepository.Service.ServiceImpl;
+
+
+import cn.seecoder.fdroidrepository.DataObject.User;
+import cn.seecoder.fdroidrepository.utils.RedisUtil;
+import cn.seecoder.fdroidrepository.utils.Tools;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class FollowService {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private UserServiceImpl userService;
+
+    public void follow(int followerId, int followeeId) throws InterruptedException {
+        String followeeKey = RedisUtil.getFolloweeKey(String.valueOf(followerId));
+        String followerKey = RedisUtil.getFollowerKey(String.valueOf(followeeId));
+        String followLock = RedisUtil.getLockByName(followeeKey);
+        String uuid = Tools.generateUUID();
+        Boolean store = redisTemplate.opsForValue().setIfAbsent(followLock, uuid, 3, TimeUnit.SECONDS);
+        if (Boolean.TRUE.equals(store)) {
+            redisTemplate.opsForZSet().add(followerKey,followerId,System.currentTimeMillis());
+            redisTemplate.opsForZSet().add(followeeKey, followeeId, System.currentTimeMillis());
+            String uuidLock = (String) redisTemplate.opsForValue().get(followLock);
+            if (uuid.equals(uuidLock)) {
+                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+                redisScript.setScriptText(script);
+                redisScript.setResultType(Long.class);
+                Long execute = (Long) redisTemplate.execute(redisScript, Arrays.asList(followLock), uuidLock);
+            }
+        }else{
+            Thread.sleep(100);
+            follow(followerId,followeeId);
+        }
+    }
+
+    private void unFollow(int followerId,int followeeId) throws InterruptedException {
+        String followeeKey = RedisUtil.getFolloweeKey(String.valueOf(followerId));
+        String followerKey = RedisUtil.getFollowerKey(String.valueOf(followeeId));
+        String followLock =RedisUtil.getLockByName(followeeKey);
+        String uuid = Tools.generateUUID();
+        Boolean store = redisTemplate.opsForValue().setIfAbsent(followLock,uuid,3,TimeUnit.SECONDS);
+        if(store) {
+            redisTemplate.opsForZSet().remove(followeeKey,followeeId);
+            redisTemplate.opsForZSet().remove(followerKey,followerId);
+            String uuidLock = (String) redisTemplate.opsForValue().get(followLock);
+            if(uuid.equals(uuidLock)){
+                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+                redisScript.setScriptText(script);
+                redisScript.setResultType(Long.class);
+                Long execute = (Long) redisTemplate.execute(redisScript, Arrays.asList(followLock), uuidLock);
+            }
+        }else{
+            Thread.sleep(100);
+            unFollow(followerId,followeeId);
+        }
+    }
+
+
+    public long getFolloweeCount(int userId){
+        String followKey =RedisUtil.getFolloweeKey(String.valueOf(userId));
+        return redisTemplate.opsForZSet().zCard(followKey);
+    }
+
+    public long getFollowerCount(int userId){
+        String followKey = RedisUtil.getFollowerKey(String.valueOf(userId));
+        return redisTemplate.opsForZSet().zCard(followKey);
+    }
+
+    public List<Map<String, Object>> findFollowees(int userId){
+        String followeeKey = RedisUtil.getFolloweeKey(String.valueOf(userId));
+        Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey,0,-1);
+        if(targetIds==null||targetIds.isEmpty()){
+            return null;
+        }
+        List<Map<String,Object>> list=new ArrayList<>();
+        for(Integer targetId: targetIds){
+            Map<String,Object> map = new HashMap<>();
+            User user=userService.loadUserByUserId(targetId);
+            map.put("username",user.getUsername());
+            Double score =redisTemplate.opsForZSet().score(followeeKey,targetId);
+            map.put("followTime",new Date(score.longValue()));
+            list.add(map);
+        }
+        return list;
+    }
+
+
+    public List<Map<String, Object>> findFollowers(int userId){
+        String followerKey = RedisUtil.getFollowerKey(String.valueOf(userId));
+        Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey,0,-1);
+        if(targetIds==null||targetIds.isEmpty()){
+            return null;
+        }
+        List<Map<String,Object>> list=new ArrayList<>();
+        for(Integer targetId: targetIds){
+            Map<String,Object> map = new HashMap<>();
+            User user=userService.loadUserByUserId(targetId);
+            map.put("username",user.getUsername());
+            Double score =redisTemplate.opsForZSet().score(followerKey,targetId);
+            map.put("followTime",new Date(score.longValue()));
+            list.add(map);
+        }
+        return list;
+    }
+
+
+}

+ 4 - 0
src/main/java/cn/seecoder/fdroidrepository/Service/ServiceImpl/UserServiceImpl.java

@@ -83,6 +83,10 @@ public class UserServiceImpl implements UserService, UserDetailsService {
         return existingUser == null;
     }
 
+    public User loadUserByUserId(int userId) {
+        return userMapper.selectByUserId(userId);
+    }
+
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

+ 17 - 0
src/main/java/cn/seecoder/fdroidrepository/utils/RedisUtil.java

@@ -0,0 +1,17 @@
+package cn.seecoder.fdroidrepository.utils;
+
+public class RedisUtil {
+    private final static String REDIS_SPLIT_KEY = ":";
+
+    public static String getFolloweeKey(String userId){
+        return "followees"+REDIS_SPLIT_KEY+userId;
+    }
+
+    public static String getLockByName(String lockName){
+        return "lock"+REDIS_SPLIT_KEY+lockName;
+    }
+
+    public static String getFollowerKey(String userId){
+        return "followers"+REDIS_SPLIT_KEY+userId;
+    }
+}

+ 2 - 0
src/main/resources/mapper/UserMapper.xml

@@ -25,6 +25,8 @@
         SELECT * FROM users WHERE username = #{username}
     </select>
 
+
+
     <select id="selectByUserId" resultType="cn.seecoder.fdroidrepository.DataObject.User">
         SELECT
             user_id as userId,