当前位置: 首页 > news >正文

通过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;}}

相关文章:

  • 基于YOLOv9+pyside的安检仪x光危险物物品检测(有ui)
  • 慧哥Saas充电桩开源平台 V2.5.5
  • SQL经典面试题
  • PHP pwn 学习 (1)
  • 开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(六)
  • 用MySQL+node+vue做一个学生信息管理系统(一):配置项目
  • 【 木兰宽松许可证】
  • win10下Python的安装和卸载
  • 【Python】.py和.pyc文件的区别
  • 【深度学习】注意力机制
  • Unity | Shader基础知识(第十七集:学习Stencil并做出透视效果)
  • 华为SRv6 policy EVPN配置案例
  • CS中的局部性原理
  • vite项目如何在本地启动https协议
  • 【SpringBoot3学习 | 第1篇】SpringBoot3介绍与配置文件
  • [deviceone开发]-do_Webview的基本示例
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • co模块的前端实现
  • DOM的那些事
  • express如何解决request entity too large问题
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • IndexedDB
  • Javascript编码规范
  • Linux gpio口使用方法
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • SwizzleMethod 黑魔法
  • uni-app项目数字滚动
  • 类orAPI - 收藏集 - 掘金
  • 区块链共识机制优缺点对比都是什么
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • k8s使用glusterfs实现动态持久化存储
  • 积累各种好的链接
  • 新海诚画集[秒速5センチメートル:樱花抄·春]
  • ​iOS安全加固方法及实现
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ######## golang各章节终篇索引 ########
  • #mysql 8.0 踩坑日记
  • #QT(一种朴素的计算器实现方法)
  • $GOPATH/go.mod exists but should not goland
  • (PADS学习)第二章:原理图绘制 第一部分
  • (二)Eureka服务搭建,服务注册,服务发现
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (四)事件系统
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (转)负载均衡,回话保持,cookie
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .NET 读取 JSON格式的数据
  • .NET 设计一套高性能的弱事件机制
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件