欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > 通过rediss实现用户菜单智能推荐

通过rediss实现用户菜单智能推荐

2024/10/24 1:55:16 来源:https://blog.csdn.net/Javalearnibg/article/details/139989391  浏览:    关键词:通过rediss实现用户菜单智能推荐

本人用的框架 SpringCloud+ redis+Oauth2+Security

前言: 整体使用过滤器的思想,获取Request,然后从数据库查到菜单名称和路由以及计算点击次数,最后以list的形式存在redis,设计定时任务,在一定时间后,将redis的数据存在数据库(mysql或者oracle)中。 

设计时出现的问题(必看!!):

(一)、因为是微服务框架,所以在设计时想到的是在GateWay使用GlobalFilter对所有服务的请求进行拦截,但是有一个问题是,因为GateWay的pom文件依赖不允许有spring-web也就没办法使用fegin或者其他方式查询数据库,也就获取不到菜单的信息,所以舍弃了这种方法

(二)、那就使用基础模块,让每个服务都去依赖这个模块,就变相的达到了,控制每一个服务的方式。那又没办法想GateWay那样直接实现GlobalFilter拦截所有请求,但是又想到可以将拦截器加在security里,等每次认证结束后,经过过滤器,对请求进行处理,这样就达到了所有的目的

(三)、如果您只是单体的springBoot项目,那就更简单了,直接实现HandlerInterceptor,然后加到bean里让spring管理

一、先写拦截器内容

@Slf4j
public class UserFavoriteFunctionFilter extends OncePerRequestFilter {// 排除过滤的 uri 地址,nacos自行添加private final IgnoreWhiteProperties ignoreWhite;public UserFavoriteFunctionFilter(IgnoreWhiteProperties ignoreWhite) {this.ignoreWhite = ignoreWhite;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//  /gAcDeptController/listString urlPath = request.getRequestURI();log.info("Absolute path:{}", urlPath);// 跳过不需要统计的路径List<String> whites = ignoreWhite.getWhites();if (CollUtil.isEmpty(whites)){filterChain.doFilter(request, response);return;}if (StringUtils.matches(urlPath, whites)) {log.info("Skip path:{}", urlPath);filterChain.doFilter(request, response);return;}RemoteSystemService remoteSystemService = SpringUtils.getBean(RemoteSystemService.class);RedisService redisService = SpringUtils.getBean(RedisService.class);String prefixKey = "userFavorite:";BigDecimal userId = SecurityUtils.getUserId();// 获取uri的前半部分String[] split = urlPath.split("/");String ControllerPath = split[1]; // gAcDeptController//  从 G_AC_PERMISSION 查出当前菜单的 perm_noResponseData<String> data = remoteSystemService.getPermNo(ControllerPath);if (ObjectUtil.isNull(data)){filterChain.doFilter(request, response);return;}String permNo = data.getData();// 从redis查询当前的用户菜单点击量String key = prefixKey+userId;List<clickCountVo> clickCountVos = redisService.getCacheList(key);if (CollUtil.isNotEmpty(clickCountVos)){Map<String, clickCountVo> clickCountMap = clickCountVos.stream().collect(Collectors.toMap(clickCountVo::getName,  // 键映射函数vo -> vo            // 值映射函数,直接使用对象本身));clickCountVo clickCountVo = clickCountMap.get(permNo);if (ObjectUtil.isNotNull(clickCountVo)) {// 当前的点击量BigDecimal count = clickCountVo.getCount();AtomicLong atomicLong = new AtomicLong(count.longValue());long l = atomicLong.incrementAndGet();clickCountVo.setCount(new BigDecimal(l));clickCountVo.setTime(new Date());}else {clickCountVo clickVo = new clickCountVo();clickVo.setName(permNo);clickVo.setTime(new Date());clickVo.setCount(BigDecimal.ONE);clickCountVos.add(clickVo);}}else {clickCountVo countVo = new clickCountVo();countVo.setName(permNo);countVo.setTime(new Date());countVo.setCount(BigDecimal.ONE);clickCountVos.add(countVo);}redisService.deleteObject(key);redisService.setCacheList(key, clickCountVos);filterChain.doFilter(request, response);}}

二、创建一个Vo保存菜单信息和点击量

@Data
public class clickCountVo {private String name;private BigDecimal count;@JsonFormat(pattern = "yyyy-MM-dd")private Date time;
}

三、有一些路径我们不需要拦截的,可以在nacos配置一下

@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {private List<String> whites = new ArrayList<>();public List<String> getWhites(){return whites;}public void setWhites(List<String> whites){this.whites = whites;}
}

四、最重要的,将我们自定义的拦截器加到Scurity里,这里还搭配了Oauth2.0

    @Bean@Order(Ordered.HIGHEST_PRECEDENCE)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {AntPathRequestMatcher[] requestMatchers = permitAllUrl.getUrls().stream().map(AntPathRequestMatcher::new).toList().toArray(new AntPathRequestMatcher[] {});http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers).permitAll().anyRequest().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.authenticationEntryPoint(resourceAuthExceptionEntryPoint).bearerTokenResolver(starBearerTokenExtractor).jwt()).addFilterAfter(new UserFavoriteFunctionFilter(whiteProperties),BearerTokenAuthenticationFilter.class).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)).csrf(AbstractHttpConfigurer::disable);return http.build();}

.addFilterAfter(new UserFavoriteFunctionFilter(whiteProperties),BearerTokenAuthenticationFilter.class) 这个是添加我们自定义的过滤器的,熟悉Oauth2.0认证的都熟悉BearerTokenAuthenticationFilter,这个过滤器是当用户每一次想资源服务器请求时都会经过的过滤器,这个过滤器也负责处理token以及将用户认证信息存到SecurityContextHolder中,所以我们在这个过滤器后面加上我们自定义的过滤器UserFavoriteFunctionFilter(这个说起来都是泪,我一点一点debug后才发现addFilterAfter这个方法

然后你就找一个既又redis又有springWeb依赖的公共模块,将代码放进去就行了。

后面还有一个定时任务的功能,这个主要是为了防止redis数据太多,我们公司是TOB,基本没有什么用户量,也没有高并发什么的,暂时就没有写这个功能。

附带两个查询的方法,返回前端展示

@RestController
@RequestMapping("/userClickController")
@Tag(name = "获取用户常用菜单功能")
public class UserClickController {@Autowiredprivate RedisService redisService;@GetMapping("/getTop10Info")@Operation(summary = "获取点击量最多的前10个菜单信息")public List<clickCountVo> getTop10Info(){String  key = "userFavorite:";BigDecimal userId = SecurityUtils.getUserId();key = key+userId;List<clickCountVo> cacheList = redisService.getCacheList(key);// 按照点击量排序。如果点击量一样就按照时间排序 都是降序return cacheList.stream().sorted(Comparator.comparing(clickCountVo::getCount).reversed().thenComparing(Comparator.comparing(clickCountVo::getTime).reversed())).limit(10).collect(Collectors.toList());}@GetMapping("/getLastWeekInfo")@Operation(summary = "获取最近一周点击量的菜单信息")public List<clickCountVo> getLastWeekInfo(){String  key = "userFavorite:";BigDecimal userId = SecurityUtils.getUserId();key = key+userId;List<clickCountVo> cacheList = redisService.getCacheList(key);if (CollUtil.isNotEmpty(cacheList)){// 获取上一周的时间DateTime dateTime = DateUtil.lastWeek();// 按照点击量排序。如果点击量一样就按照时间排序 都是降序return cacheList.stream().filter(da -> da.getTime().toInstant().isAfter(dateTime.toInstant())).sorted(Comparator.comparing(clickCountVo::getCount).reversed().thenComparing(Comparator.comparing(clickCountVo::getTime).reversed())).collect(Collectors.toList());}return cacheList;}}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com