【业务功能篇90】微服务-springcloud-检索服务-ElasticSearch实战运用-DSL语句
商城检索服务
1.检索页面的搭建
商品检索页面我们放在search服务中处理,首页我们需要在mall-search服务中支持Thymeleaf。添加对应的依赖
<!-- 添加Thymeleaf的依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
然后我们拷贝模板文件到template目录下,然后不要忘记添加Thymeleaf的名称空间
需要把相关的静态资源文件拷贝到Nginx服务中。目录结构是:/mydata/nginx/html/static/search/
我们需要修改index.html页面中的资源的路径
然后我们要通过 msb.search.com
来访问我们的检索服务,那么就需要设置对应的host文件
然后我们就需要修改Nginx的配置
这时我需要在修改网关的服务,根据我们的域名访问,那么需要网关路由到我们的检索服务中
然后我们就可以重启相关的服务 ,来测试了
2.检索服务
2.1 创建对应VO
我们需要检索数据库中的相关的商品信息,那么我们就需要提交相关的检索条件,为了统一的管理提交的数据,我们需要创建一个VO来封装信息。
/*** 封装页面所有可能提交的查询条件*/
@Data
public class SearchParam {private String keyword; // 页面传递的查询全文匹配的关键字private Long catalog3Id;// 需要根据分类查询的编号/*** sort=salaCount_asc/desc* sort=skuPrice_asc/desc* sort=hotScore_asc/desc*/private String sort; // 排序条件// 查询的筛选条件 hasStock=0/1;private Integer hasStock ; // 是否只显示有货// brandId=1&brandId=2private List<Long> brandId; // 按照品牌来查询,可以多选// skuPrice=200_300// skuPrice=_300// skuPrice=200_private String skuPrice; // 价格区间查询// 不同的属性 attrs:1_苹果:6.5寸private List<String> attrs; // 按照属性信息进行筛选private Integer pageNum; // 页码}
然后就是检索后的数据我们需要封装的VO对象,定义如下:
package com.msb.mall.mallsearch.vo;import com.msb.common.dto.es.SkuESModel;
import lombok.Data;import java.util.List;/*** 封装检索后的响应信息*/
@Data
public class SearchResult {private List<SkuESModel> products; // 查询到的所有的商品信息 满足条件// 分页信息private Integer pageNum; // 当前页private Long total; // 总的记录数private Integer totalPages; // 总页数// 当前查询的所有的商品涉及到的所有的品牌信息private List<BrandVO> brands;// 当前查询的所有的商品涉及到的所有的属性信息private List<AttrVo> attrs;// 当前查询的所有商品涉及到的所有的类别信息private List<CatalogVO> catalogs;@Datapublic static class CatalogVO{private Long catalogId;private String catalogName;}/*** 品牌的相关信息*/@Datapublic static class BrandVO{private Long brandId; // 品牌的编号private String brandName; // 品牌的名称private String brandImg; // 品牌的图片}@Datapublic static class AttrVo{private Long attrId; // 属性的编号private String attrName; // 属性的名称private List<String> attrValue; // 属性的值}}
2.2 构建查询DSL语句
我们需要根据基本的检索条件来封装对应的DSL语句
- 查询关键字 模糊匹配
- 过滤(分类,品牌,属性,价格区间,库存…)
- 排序
- 分页
- 高亮
GET /product/_search
{"query": {"bool": {"must": [{"match": {"subTitle": "华为"}}],"filter": [{"term": {"catalogId": "225"}},{"terms": {"brandId": ["13","16","14"]}},{"range": {"skuPrice": {"gte": 10,"lte": 12000}}},{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "9"}}},{"terms": {"attrs.attrValue": ["12","08","11"]}}]}}}}]}},"sort": [{"skuPrice": {"order": "desc"}}],"from": 0,"size": 20,"highlight": {"fields": {"subTitle": {}},"pre_tags": "<b style='color:red'>","post_tags": "<b>"}
}
2.3 构建SearchRequest对象
根据客户端提交的检索的信息,我们需要封装为对应的SearchRequest对象,然后通过ES的API来检索数据。
/*** 构建检索的请求* 模糊匹配,关键字匹配* 过滤(类别,品牌,属性,价格区间,库存)* 排序* 分页* 高亮* 聚合分析* @param param* @return*/private SearchRequest buildSearchRequest(SearchParam param) {SearchRequest searchRequest = new SearchRequest();searchRequest.indices(ESConstant.PRODUCT_INDEX);SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 构建具体的检索的条件// 1.构建bool查询BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 1.1 关键字的条件if(!StringUtils.isEmpty(param.getKeyword())){boolQuery.must(QueryBuilders.matchQuery("subTitle",param.getKeyword()));}// 1.2 类别的检索条件if(param.getCatalog3Id() != null){boolQuery.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));}// 1.3 品牌的检索条件if(param.getBrandId() != null && param.getBrandId().size() > 0){boolQuery.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));}// 1.4 是否有库存if(param.getHasStock() != null){boolQuery.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));}// 1.5 根据价格区间来检索if(!StringUtils.isEmpty(param.getSkuPrice())){String[] msg = param.getSkuPrice().split("_");RangeQueryBuilder skuPrice = QueryBuilders.rangeQuery("skuPrice");if(msg.length == 2){// 说明是 200_300skuPrice.gte(msg[0]);skuPrice.lte(msg[1]);}else if(msg.length == 1){// 说明是 _300 200_if(param.getSkuPrice().endsWith("_")){// 说明是 200_skuPrice.gte(msg[0]);}if(param.getSkuPrice().startsWith("_")){// 说明是 _300skuPrice.lte(msg[0]);}}boolQuery.filter(skuPrice);}// 1.6 属性的检索条件 attrs=20_8英寸:10英寸&attrs=19_64GB:32GBif(param.getAttrs() != null && param.getAttrs().size() > 0){for (String attrStr : param.getAttrs()) {BoolQueryBuilder boolNestedQuery = QueryBuilders.boolQuery();// attrs=19_64GB:32GB 我们首先需要根据 _ 做分割String[] attrStrArray = attrStr.split("_");// 属性的编号String attrId = attrStrArray[0];// 64GB:32GB 获取属性的值String[] values = attrStrArray[1].split(":");// 拼接组合条件boolNestedQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));boolNestedQuery.must(QueryBuilders.termsQuery("attrs.attrValue",values));NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolNestedQuery, ScoreMode.None);boolQuery.filter(nestedQuery);}}sourceBuilder.query(boolQuery);// 2.排序if(!StringUtils.isEmpty(param.getSort())){// sort=salaCount_asc/descString[] s = param.getSort().split("_");SortOrder order = s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;sourceBuilder.sort(s[0], order);}// 3.处理分页// Integer pageNum; // 页码if(param.getPageNum() != null){// 需要做分页处理 pageSize = 5// pageNum:1 from:0 [0,1,2,3,4]// pageNum:2 from:5 [5,6,7,8,9]// from = ( pageNum - 1 ) * pageSizesourceBuilder.from( (param.getPageNum() - 1 ) * ESConstant.PRODUCT_PAGESIZE);sourceBuilder.size(ESConstant.PRODUCT_PAGESIZE);}// 4. 设置高亮if(!StringUtils.isEmpty(param.getKeyword())){// 如果有根据关键字查询那么我们才需要高亮设置HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("subTitle");highlightBuilder.preTags("<b style='color:red'>");highlightBuilder.postTags("</b>");sourceBuilder.highlighter(highlightBuilder);}// 5.聚合运算// 5.1 品牌的聚合TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");brand_agg.field("brandId");brand_agg.size(50);// 品牌的子聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(10));brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(10));sourceBuilder.aggregation(brand_agg);// 5.2 类别的聚合TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");catalog_agg.field("catalogId");catalog_agg.size(10);// 类别的子聚合catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(10));sourceBuilder.aggregation(catalog_agg);// 5.3 属性的聚合NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");// 属性id聚合TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg");attr_id_agg.field("attrs.attrId");attr_id_agg.size(10);// 属性id下的子聚合 属性名称和属性值attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));attr_agg.subAggregation(attr_id_agg);sourceBuilder.aggregation(attr_agg);System.out.println(sourceBuilder.toString());searchRequest.source(sourceBuilder);return searchRequest;}
2.4 构建SearchResult对象
当我们通过封装的SearchRequest对象从ES中检索出了相关的信息后,我们需要将返回的SearchResponse对象封装为前端接收的SearchResult对象。
- 所有的满足条件的商品
- 分页相关的信息
- 当前商品涉及的品牌信息
- 当前商品涉及的类别信息
- 当前商品涉及的属性信息
/*** 根据检索的结果解析封装为SearchResult对象* @param response* @return*/private SearchResult buildSearchResult(SearchResponse response,SearchParam param){SearchResult result = new SearchResult();SearchHits hits = response.getHits();// 1.检索的所有商品信息SearchHit[] products = hits.getHits();List<SkuESModel> esModels = new ArrayList<>();if(products != null && products.length > 0){for (SearchHit product : products) {String sourceAsString = product.getSourceAsString();// 把json格式的字符串通过fastjson转换为SkuESModel对象SkuESModel model = JSON.parseObject(sourceAsString, SkuESModel.class);if(!StringUtils.isEmpty(param.getKeyword())){// 我们需要设置高亮HighlightField subTitle = product.getHighlightFields().get("subTitle");String subTitleHighlight = subTitle.getFragments()[0].string();model.setSubTitle(subTitleHighlight); // 设置高亮}esModels.add(model);}}result.setProducts(esModels);Aggregations aggregations = response.getAggregations();// 2.当前商品所涉及到的所有的品牌ParsedLongTerms brand_agg = aggregations.get("brand_agg");List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();// 存储所有品牌的容器List<SearchResult.BrandVO> brandVOS = new ArrayList<>();if(buckets!=null && buckets.size() > 0){for (Terms.Bucket bucket : buckets) {SearchResult.BrandVO brandVO = new SearchResult.BrandVO();// 获取品牌的keyString keyAsString = bucket.getKeyAsString();brandVO.setBrandId(Long.parseLong(keyAsString)); // 设置品牌的编号// 然后我们需要获取品牌的名称和图片的地址ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");List<? extends Terms.Bucket> bucketsImg = brand_img_agg.getBuckets();if(bucketsImg != null && bucketsImg.size() > 0){String img = bucketsImg.get(0).getKeyAsString();brandVO.setBrandImg(img);}// 获取品牌名称的信息ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");String breadName = brand_name_agg.getBuckets().get(0).getKeyAsString();brandVO.setBrandName(breadName);brandVOS.add(brandVO);}}result.setBrands(brandVOS);// 3.当前商品涉及到的所有的类别信息ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");List<? extends Terms.Bucket> bucketsCatalogs = catalog_agg.getBuckets();// 创建一个保存所有类别的容器List<SearchResult.CatalogVO> catalogVOS = new ArrayList<>();if(bucketsCatalogs != null && bucketsCatalogs.size() > 0){for (Terms.Bucket bucket : bucketsCatalogs) {SearchResult.CatalogVO catalogVO = new SearchResult.CatalogVO();String keyAsString = bucket.getKeyAsString(); // 获取类别的编号catalogVO.setCatalogId(Long.parseLong(keyAsString));// 获取类别的名称ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");String catalogName = catalog_name_agg.getBuckets().get(0).getKeyAsString();catalogVO.setCatalogName(catalogName);catalogVOS.add(catalogVO);}}result.setCatalogs(catalogVOS);// 4.当前商品涉及到的所有的属性信息ParsedNested attr_agg = aggregations.get("attr_agg");ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");List<? extends Terms.Bucket> bucketsAttr = attr_id_agg.getBuckets();List<SearchResult.AttrVo > attrVos = new ArrayList<>();if(bucketsAttr != null && bucketsAttr.size() > 0){for (Terms.Bucket bucket : bucketsAttr) {SearchResult.AttrVo attrVo = new SearchResult.AttrVo();// 获取属性的编号String keyAsString = bucket.getKeyAsString();attrVo.setAttrId(Long.parseLong(keyAsString));// 又得分别获取 属性的名称 和 属性的值ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString(); // 属性的名称attrVo.setAttrName(attrName);ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");if(attr_value_agg.getBuckets() != null && attr_value_agg.getBuckets().size() > 0 ){List<String> values = attr_value_agg.getBuckets().stream().map(item -> {String keyAsString1 = item.getKeyAsString();return keyAsString1;}).collect(Collectors.toList());attrVo.setAttrValue(values);}attrVos.add(attrVo);}}result.setAttrs(attrVos);// 5. 分页信息 当前页 总的记录数 总页数long total = hits.getTotalHits().value;result.setTotal(total);// 设置总记录数 6 /5 1+1result.setPageNum(param.getPageNum()); // 设置当前页long totalPage = total % ESConstant.PRODUCT_PAGESIZE == 0 ? total / ESConstant.PRODUCT_PAGESIZE : (total / ESConstant.PRODUCT_PAGESIZE + 1);result.setTotalPages((int)totalPage); // 设置总的页数return result;}
相关文章:

【业务功能篇90】微服务-springcloud-检索服务-ElasticSearch实战运用-DSL语句
商城检索服务 1.检索页面的搭建 商品检索页面我们放在search服务中处理,首页我们需要在mall-search服务中支持Thymeleaf。添加对应的依赖 <!-- 添加Thymeleaf的依赖 --><dependency><groupId>org.springframework.boot</groupId><artifa…...

QTday4
实现闹钟功能 1》 头文件 #ifndef BURGER_H #define BURGER_H#include <QWidget> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QTextEdit> #include <QTimerEvent> //定时器事件类 #include <QDateTim…...

设计模式之命令模式(Command)的C++实现
1、命令模式的提出 在软件开发过程中,“行为请求者”和“行为实现者”通常呈现一种“紧耦合”,如果行为的实现经常变化,则不利于代码的维护。命令模式可以将行为的请求者和行为的实现者进行解耦。具体流程是将行为请求者封装成一个对象&…...

取证工具prodiscover的基本操作
前言提醒 取证工具ProDiscover在网上讲解操作的文章实在太少,一是prodiscover是用于磁盘取证的工具,本身比较小众比不上其他的编程软件能用到的地方多,二是这个工具是用来恢复提取磁盘中被删除的文件,是比较隐晦的软件。 需要注…...

flutter plugins插件【二】【FlutterAssetsGenerator】
2、FlutterAssetsGenerator 介绍地址:https://juejin.cn/post/6898542896274735117 配置assets目录 插件会从pubspec.yaml文件下读取assets目录,因此要使用本插件,你需要在pubspec.yaml下配置资源目录 flutter:# The following line ens…...

看懂UML类图
UML 统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。 类的表示 首先看那个…...

keras深度学习框架通过简单神经网络实现手写数字识别
背景 keras深度学习框架,并不是一个独立的深度学习框架,它后台依赖tensorflow或者theano。大部分开发者应该使用的是tensorflow。keras可以很方便的像搭积木一样根据模型搭出我们需要的神经网络,然后进行编译,训练,测试…...

React 中的 ref 如何操作 dom节点,使输入框获取焦点
聚焦文字输入框 .focus() 获取焦点 当用户点击按钮时,handleClick 函数会被调用,从而将焦点聚焦到文本输入框上。 // 焦文字输入框 import { useRef } from "react";const FocusForm () > {const inputRef useRef<any>(null);func…...

最短路Dijkstra,spfa,图论二分图算法AYIT---ACM训练(模板版)
文章目录 前言A - Dijkstra Algorithm0x00 算法题目0x01 算法思路0x02 代码实现 B - 最长路0x00 算法题目0x01 算法思路0x02 代码实现 C - 二分图最大匹配0x00 算法题目0x01 算法思路0x02 代码实现 D - 搭配飞行员0x00 算法题目0x01 算法思路0x02 代码实现 E - The Perfect Sta…...
AK 微众银行 9.3 笔试 Java后端方向
T1(模拟,二分) (没看清买的糖果只有前缀,一开始用二分写了,后来意识到也没改了,简单写的话,直接模拟就好了) #include <bits/stdc.h>#define endl \nusing namespace std;const int N 50010;int n; int a[N];bool check(…...

了解java中的通配符“?“
目录 通配符的作用 先看一段代码 用通配符"?"后,代码变化 结论 通配符上界 通配符下界 对通配符上下界的注释理解及其练习代码 简记: ? 用于在泛型的使用,即为通配符. 在Java中,通配符(wildcard)主要用于泛型…...
浙大陈越何钦铭数据结构07-图6 旅游规划【最小堆实现】
题目: 题目和浙大陈越何钦铭数据结构07-图6 旅游规划是一样的,不同的是用最小堆实现函数【FindMinDist】。 时间复杂度对比: 浙大陈越何钦铭数据结构07-图6 旅游规划: 创建图(CreateGraph):时…...

OpenShift 4 - 用 Prometheus 和 Grafana 监视用户应用定制的观测指标(视频)
《OpenShift / RHEL / DevSecOps 汇总目录》 说明:本文已经在 OpenShift 4.13 的环境中验证 文章目录 OpenShift 的监控功能构成部署被监控应用用 OpenShift 内置功能监控应用用 Grafana 监控应用安装 Grafana 运行环境配置 Grafana 数据源定制监控 Dashboard 演示视…...

【LeetCode】剑指 Offer <二刷>(3)
目录 题目:剑指 Offer 06. 从尾到头打印链表 - 力扣(LeetCode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 07. 重建二叉树 - 力扣…...

Ceph IO流程及数据分布
1. Ceph IO流程及数据分布 1.1 正常IO流程图 步骤: client 创建cluster handler。client 读取配置文件。client 连接上monitor,获取集群map信息。client 读写io 根据crshmap 算法请求对应的主osd数据节点。主osd数据节点同时写入另外两个副本节点数据。…...
Netty-NIO
文章目录 一、NIO-Selector1.处理accept2.cancel3.处理read4.处理客户端断开5. 处理消息的边界6. 写入内容过多的问题7. 处理可写事件 一、NIO-Selector 1.处理accept //1.创建selector,管理多个channel Selector selector Selector.open(); ByteBuffer buffer ByteBuffer.…...
红外物理学习笔记 ——第三章
第三章 基尔霍夫定律:就是说物体热平衡条件下,发射的辐射功率要等于吸收的辐射功率 M α E M\alpha E MαE α \alpha α 是吸收率, M M M 是幅出度(发射出去的), E E E是辐照度(外面照过来的…...
使用 htmx 构建交互式 Web 应用
学习目标:了解htmx的基本概念、特点和用法,并能够运用htmx来创建交互式的Web应用程序。 学习内容: 1. 什么是htmx? - htmx是一种用于构建交互式Web应用程序的JavaScript库。 - 它通过将HTML扩展为一种声明性的交互式语言&a…...

S32K324芯片学习笔记
文章目录 Core and architectureDMASystem and power managementMemory and memory interfacesClocksSecurity and integrity安全与完整性Safety ISO26262Analog、Timers功能框图内存mapflash Signal MultiplexingPort和MSCR寄存器的mapping Core and architecture 两个Arm Co…...

htmx-使HTML更强大
本文作者是360奇舞团开发工程师 htmx 让我们先来看一段俳句: javascript fatigue: longing for a hypertext already in hand 这个俳句很有意思,是开源项目htmx文档中写的,意思是说,我们已经有了超文本,为什么还要去使用javascr…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...