| 
					
				 | 
			
			
				@@ -0,0 +1,285 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+package com.tzld.piaoquan.recommend.server.service.filter.strategy; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.google.common.cache.CacheBuilder; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.google.common.cache.CacheLoader; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.google.common.cache.LoadingCache; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.tzld.piaoquan.recommend.server.repository.WxVideoTagRel; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.tzld.piaoquan.recommend.server.repository.WxVideoTagRelRepository; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import lombok.Data; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.apache.commons.collections4.CollectionUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.apache.commons.collections4.MapUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.apache.commons.lang3.StringUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.slf4j.Logger; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.slf4j.LoggerFactory; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.beans.factory.annotation.Autowired; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.beans.factory.annotation.Qualifier; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.data.redis.core.RedisTemplate; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.scheduling.annotation.Scheduled; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.stereotype.Component; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import javax.annotation.Nonnull; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import javax.annotation.PostConstruct; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import javax.annotation.Resource; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.*; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.ConcurrentHashMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.TimeUnit; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.stream.Collectors; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 黑名单列表相关的容器。主要实现以下几个功能 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * <ul> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     <ol>1. 判断用户属于哪个类型的黑名单</ol> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     <ol>2. 根据用户类型判断视频对于该用户是否有风险</ol> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     <ol>3. 根据用户类型过滤掉视频列表中对该用户有风险的视频</ol> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * </ul> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+@Component 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+public class BlacklistContainer { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static final Logger LOG = LoggerFactory.getLogger(BlacklistContainer.class); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public static final String USER_TYPE_SUB_TYPE_CONNECTOR = ":"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static final int USER_REDIS_KEY_PARTITION_COUNT = 100; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 用户访问黑名单 Redis Key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static final String USER_VISIO_BLACKLIST_HASH_KEY = "visio:blacklist:user:"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * IP访问黑名单 Redis Key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static final String IP_VISIO_BLACKLIST_HASH_KEY = "visio:blacklist:ip"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Autowired 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Qualifier("longVideoRedisTemplate") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private RedisTemplate<String, String> longVideoRedisTemplate; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @ApolloJsonValue("${content.security.generalization.user.condition.config:}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private Map<String, GeneralizationUserConfig> generalizationUserConditionConfig; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Resource 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private WxVideoTagRelRepository wxVideoTagRelRepository; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 不同类型的用户要过滤掉的标签列表配置 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <p> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * Key的格式为: {userType}:{userSubType} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * userType枚举值: 1-竞品用户, 2-微信人员, 3-网安 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * userSubType枚举值: 1-精准用户, 2-泛化用户 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @ApolloJsonValue("${content.security.filter.config:{}}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private Map<String, TagFilterConfig> tagFilterConfigMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @ApolloJsonValue("${content.security.user.type.priority.config:{}}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private Map<String, List<String>> userTypePriorityConfigMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 保存Tag标签与视频列表的映射 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * Key为TagId,  Value为对应的视频ID列表 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static Map<Long, Set<Long>> videoTagCache = new ConcurrentHashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 黑名单本地二级缓存,一级缓存为Redis缓存。此处直接读取即可 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * Redis缓存由longvideo服务写入 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * com.weiqu.video.service.filter.impl.BlacklistFilterImpl#refreshUidCache 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * <br> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * com.weiqu.video.service.filter.impl.BlacklistFilterImpl#refreshIPCache 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private final LoadingCache<String, Map<String, String>> blacklistCache = CacheBuilder.newBuilder() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            .expireAfterWrite(10, TimeUnit.MINUTES) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            .build(new CacheLoader<String, Map<String, String>>() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                public Map<String, String> load(@Nonnull String key) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    Map<Object, Object> map = longVideoRedisTemplate.opsForHash().entries(key); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (MapUtils.isEmpty(map)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return new HashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return map.entrySet().stream().collect( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            Collectors.toMap( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                    entry -> entry.getKey().toString(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                    entry -> entry.getValue().toString(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                    (v1, v2) -> v1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            )); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @PostConstruct 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void init() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        refreshVideoTagCache(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Scheduled(cron = "0 0/5 0/1 * * ? ") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void cronSync() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        refreshVideoTagCache(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void refreshVideoTagCache() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LOG.info("同步本地标签ID与视频列表的缓存任务开始"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Map<Long, Set<Long>> tmpMap = new ConcurrentHashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (MapUtils.isNotEmpty(tagFilterConfigMap)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // 获取所有的标签ID列表 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Set<Long> tagIdSet = new HashSet<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for (Map.Entry<String, TagFilterConfig> entry : tagFilterConfigMap.entrySet()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                TagFilterConfig tagFilterConfig = entry.getValue(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (Objects.isNull(tagFilterConfig)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (CollectionUtils.isNotEmpty(tagFilterConfig.getRecommendExcludeTag())) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    tagIdSet.addAll(tagFilterConfig.getRecommendExcludeTag()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (CollectionUtils.isNotEmpty(tagFilterConfig.getDetailExcludeTag())) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    tagIdSet.addAll(tagFilterConfig.getDetailExcludeTag()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // 获取标签ID对应的视频ID列表 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for (Long tagId : tagIdSet) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                List<WxVideoTagRel> wxVideoTagRels = wxVideoTagRelRepository.findAllByTagId(tagId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Set<Long> videoIdSet = wxVideoTagRels.stream().map(WxVideoTagRel::getVideoId).collect(Collectors.toSet()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                tmpMap.put(tagId, videoIdSet); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        videoTagCache = tmpMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LOG.info("同步本地标签ID与视频列表的缓存任务结束"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public List<Long> filterUnsafeVideoByUser(List<Long> videoIds, String uid, Long hotSceneType, String cityCode, String clientIP) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (CollectionUtils.isEmpty(videoIds)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return videoIds; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        String userType = this.matchUserBlacklistTypeEnum(uid, hotSceneType, cityCode, clientIP); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Collection<Long> tagIdSet = this.findExcludeTagIds(userType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (CollectionUtils.isEmpty(tagIdSet)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return videoIds; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return videoIds.stream().filter(videoId -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (videoTagAnyMatch(videoId, tagIdSet)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                LOG.error("用户 {} 在因命中 {} 移除对应的视频ID {}: 请求参数为: hotSceneType={}, cityCode={}, clientIP={}", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        uid, userType, videoId, hotSceneType, cityCode, clientIP); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }).collect(Collectors.toList()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private String matchUserBlacklistTypeEnum(String uid, Long hotSceneType, String cityCode, String clientIP) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            LOG.info("计算用户黑名单类型,判断参数: uid={}, hotSceneType={}, cityCode={}, clientIP={}", uid, hotSceneType, cityCode, clientIP); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            String key = this.calcUserRedisKey(uid); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Map<String, String> uidBlacklistMap = blacklistCache.get(key); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (uidBlacklistMap.containsKey(uid)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                String userType = uidBlacklistMap.get(uid); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                LOG.info("用户 {} 在UID黑名单中命中 {}", uid, userType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return userType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Map<String, String> ipBlacklistMap = blacklistCache.get(IP_VISIO_BLACKLIST_HASH_KEY); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (ipBlacklistMap.containsKey(clientIP)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                String userType = ipBlacklistMap.get(clientIP); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                LOG.info("用户 {} 在IP黑名单中命中 {}, 参数为: clientIP为: {}", uid, userType, clientIP); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return userType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return this.matchGeneralizationUserType(uid, cityCode, hotSceneType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } catch ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Exception e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            LOG.error("blacklist filter isSafeVideoByUid error: ", e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private String matchGeneralizationUserType(String uid, String cityCode, Long hotSceneType) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (MapUtils.isNotEmpty(generalizationUserConditionConfig) && generalizationUserConditionConfig.containsKey(cityCode)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            GeneralizationUserConfig userConfig = generalizationUserConditionConfig.get(cityCode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (CollectionUtils.isNotEmpty(userConfig.getExcludeHotSceneType())) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!userConfig.getExcludeHotSceneType().contains(hotSceneType)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    String userType = userConfig.getUserType() + USER_TYPE_SUB_TYPE_CONNECTOR + userConfig.getUserSubType(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    LOG.info("用户 {} 在泛化用户规则中命中: {}, 参数为: cityCode={}, hotSceneType={}", uid, userType, cityCode, hotSceneType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return userType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 匹配videoId的标签包含tagIds中的任意一个 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * @param videoId 视频ID 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * @param tagIds  标签ID列表 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * @return true-匹配,false-不匹配 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private boolean videoTagAnyMatch(Long videoId, Collection<Long> tagIds) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (MapUtils.isEmpty(videoTagCache) || CollectionUtils.isEmpty(tagIds)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (Long tagId : tagIds) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Set<Long> videoIds = videoTagCache.get(tagId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (CollectionUtils.isNotEmpty(videoIds) && videoIds.contains(videoId)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private String calcUserRedisKey(String uidStr) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        long uid = 0L; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            uid = Long.parseLong(uidStr); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } catch ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Exception e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            LOG.error("calcUserRedisKey error: ", e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return USER_VISIO_BLACKLIST_HASH_KEY + (uid % USER_REDIS_KEY_PARTITION_COUNT); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private Collection<Long> findExcludeTagIds(String userType) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (StringUtils.isBlank(userType) || MapUtils.isEmpty(tagFilterConfigMap)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return Collections.emptySet(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        TagFilterConfig tagFilterConfig = tagFilterConfigMap.get(userType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (Objects.isNull(tagFilterConfig)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return Collections.emptySet(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return tagFilterConfig.getRecommendExcludeTag(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static class GeneralizationUserConfig { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Set<Long> excludeHotSceneType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        String userType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        String userSubType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static class TagFilterConfig { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         * 推荐场景下要过滤掉的标签 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Set<Long> recommendExcludeTag; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         * 详情场景下要过滤掉的标签 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Set<Long> detailExcludeTag; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |