ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理

ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理

环境介绍

ElasticSearch 版本号: 6.7.0

需求说明

用户会传入多个关键字去ES查询ElasticSearch nested 字段 的多个字段,要求在返回的结果中被搜索的字段需要高亮所有匹配的关键字。例如同时通过上海策划关键字,再 工作经历的列表中的工作内容公司名称中搜索。如果有人员的工作经历中这两个关键字上海策划都可以匹配到,那么返回的结果中同时高亮这上海策划关键字

分析调研

基础的ElasticSearch nested 字段的高亮实现搜可以参考https://blog.csdn.net/weixin_48990070/article/details/120342597 这篇笔记。

问题点1

对于同一个nested 字段支持在一个nested Query 用不同的关键字来搜索,但对于should 查询只会高亮其中匹配的一个关键字,而不是全部。引入如果多关键字直接是任意满足的关系,则之后高亮匹配的其中的一个关键字,这个与不满足需求。

问题点2

那就把关键字拆分为多个nested Query ,一个关键字对应一个nested Query 。但这个方法一样可以搜索,但对于同一个nested字段的nested Query 默认的inner_hits 属性只能出现在一个nested Query中,不允许同一个nested字段的不同nested Query 都指定inner_hits ,如果一定要这么做,那么就会得到一个查询错误的提示,提示如下:

        "reason": {"type": "illegal_argument_exception","reason": "[inner_hits] already contains an entry for key [trackRecordList]"}

如果只在一个关键字的nested Query指定inner_hits,那么最终的高亮结果只会有该nested Query的高亮,还是不满足要求。

问题点3

通过AI询问得知inner_hits 有个name 属性可以解决问题点2的情况,可以通过设置inner_hits不同的name 属性值来达到对同一个nested字段用不同nested Query 来做多关键字的高亮效果,但是这样里又出现了两个新的问题。
1、inner_hits 有个name 属性值不能重复,否则一样出问题点2的错误提示。
2、高亮 结果是按照inner_hits 有个name 属性值分组展示的,不像非nested会给一个最终多个关键字都高亮的结果。

转换下问题就是:
1、要根据关键字自动生成不重复inner_hits 有个name 属性值
2、对于同字段的高亮结果,要做高亮内容的合并。

因此只要解决了上面两个问题,就可以完成业务的需求了。

最终解决方案

问题1解决方案

在将查询参数转换为ES Query语句的处理中,用Map来缓存每个nested字段的当前有几个nested Query,通过累计数量,来自动生成每个nested Query中的inner_hits 有个name 属性名,例如名称为 nested字段名+“-”+自增序号

因此就不能再使用静态方法来构建查询语句了,得用构建器了,下面就是构建器的部分实现

public class EsQueryBuilder {// 存储嵌套字段及其累计值的映射private Map<String, IntAccumulator> accNestedFieldMap = new HashMap<>();// 无需嵌套高亮的字段集合private Set<String> noNestedHighlightFields = new HashSet<>();// 关键词分组列表private List<PageSearchKeywordGroupParameter> keywordGroupList ;// 是否开启高亮显示private boolean isHighlight = false;// 主查询构建器private BoolQueryBuilder mainQueryBuilder;//存储嵌套字段及其高亮构建器private Map<NestedQueryBuilder,InnerHitBuilder> nestedQueryBuilderHighlightMap = new HashMap<>();/*** 构造方法* @param keywordGroupList 搜索关键字组* @param isHighlight 是否高亮*/public EsQueryBuilder(List<PageSearchKeywordGroupParameter> keywordGroupList,boolean isHighlight) {this.keywordGroupList = keywordGroupList;this.isHighlight = isHighlight;this.mainQueryBuilder = new BoolQueryBuilder();//补充嵌套字段初始累加器EsQueryFieldEnum.getNestedFieldList().forEach(item->{accNestedFieldMap.put(item.getFieldConfig().getMainField(),new IntAccumulator(0));});}/*** 向当前的查询构建器中添加条件。这个方法会遍历关键字组列表(keywordGroupList)中的每一个项目,* 并根据是否标记为排除条件,将关键字添加到查询的必须条件(must)或者必须不条件(mustNot)中。* @return EsQueryBuilder 返回当前的查询构建器实例,允许链式调用。*/public EsQueryBuilder addCondition() {keywordGroupList.forEach(item->{// 只处理非空关键字的项目if(StringUtils.isNotBlank(item.getKeyword())){// 根据是否为排除条件,选择添加到must或mustNot中if(BooleanUtils.isTrue(item.getIsExclude())){mainQueryBuilder.mustNot(buildQueryBuilder(item));}else{mainQueryBuilder.must(buildQueryBuilder(item));}}});return this;}/*** 为所有内容添加高亮显示条件的查询构建器。* 该方法遍历关键字组列表,对非排除条件的关键字进行全文搜索设置,并根据关键字是否为排除条件,添加相应的查询条件。* @return EsQueryBuilder 当前查询构建器实例,支持链式调用。*/public EsQueryBuilder addConditionForAllContentHighlight() {// 遍历关键字组列表,过滤掉设置为排除条件的关键字,对剩余的关键字进行全文搜索设置keywordGroupList.stream()// 过滤掉设置为排除条件的关键字.filter(item->BooleanUtils.isNotTrue(item.getIsExclude())).peek(item->{// 设置搜索类型为全文搜索,清空子类型设置item.setSearchType(EsQueryTypeEnum.ALL.value());item.setSearchSubType(null);}).forEach(item->{// 根据关键字是否为排除条件,添加相应的查询条件if(StringUtils.isNotBlank(item.getKeyword())){if(BooleanUtils.isTrue(item.getIsExclude())){// 如果是排除条件,则添加到must not查询条件中mainQueryBuilder.mustNot(buildQueryBuilder(item));}else{// 如果不是排除条件,则添加到must查询条件中mainQueryBuilder.must(buildQueryBuilder(item));}}});return this;}/*** 嵌套字段高亮处理**/private void highlightNestedQuery() {if(!nestedQueryBuilderHighlightMap.isEmpty()){nestedQueryBuilderHighlightMap.forEach(NestedQueryBuilder::innerHit);}}/*** 为查询添加过滤条件。* 这个方法允许用户指定一个过滤条件,并将其应用到当前的查询构建器中。* @param queryBuilder 过滤条件的查询构建器。这是一个已经构建好的查询条件,将作为过滤条件添加到主查询中。* @return 返回当前的EsQueryBuilder实例,允许链式调用。*/public EsQueryBuilder filterCondition(QueryBuilder queryBuilder) {// 为主查询添加过滤条件mainQueryBuilder.filter(queryBuilder);return this;}/*** 构建查询条件* @return org.elasticsearch.search.builder.SearchSourceBuilder*/public SearchSourceBuilder build() {SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//查询条件searchSourceBuilder.query(mainQueryBuilder);return searchSourceBuilder;}/*** 构建查询条件(支持高亮)* @return org.elasticsearch.search.builder.SearchSourceBuilder*/public SearchSourceBuilder buildWithHighlight() {SearchSourceBuilder searchSourceBuilder = build();if(isHighlight){//补充非嵌套字段的高亮highlightNestedQuery();//非嵌套高亮searchSourceBuilder.highlighter(EsHighlightUtils.buildNotNestHighlightBuilder(noNestedHighlightFields));}return searchSourceBuilder;}/*** 构建查询构建器。* 该方法根据传入的参数生成一个对应的查询条件构建器,主要用于处理专家页面的搜索关键词分组参数。* @param parameter 搜索参数,包含需要搜索的关键词和其他搜索条件。* @return 返回构建好的查询条件构建器对象。*/private QueryBuilder buildQueryBuilder(PageSearchKeywordGroupParameter parameter){// 初始化一个布尔类型的查询条件构建器,用于后续添加各种查询条件BoolQueryBuilder keywordQueryBuilder = new BoolQueryBuilder();// 根据参数生成对应的查询类型枚举,用于确定如何构建查询条件EsQueryTypeEnum queryTypeEnum = generateQueryTypeEnum(parameter);// 调用查询类型枚举中定义的添加条件处理器,处理当前搜索参数,并将其添加到查询条件构建器中queryTypeEnum.getAddConditionHandler().handle(this,keywordQueryBuilder,parameter.getKeyword());return keywordQueryBuilder;}/*** 根据关键词和字段枚举生成查询条件。* @param keyword 关键词,用于构建查询条件。* @param fieldEnum 字段枚举,包含字段配置信息,用于指定要查询的字段。* @param highlight 是否高亮处理。* @return org.elasticsearch.index.query.QueryBuilder 查询构建器,用于构建Elasticsearch的查询语句。*/private QueryBuilder generateCondition(String keyword, EsQueryFieldEnum fieldEnum, boolean highlight){EsQueryFieldConfigDTO fieldConfigDTO = fieldEnum.getFieldConfig();// 构建基于关键词的基本查询条件BoolQueryBuilder boolQueryBuilder = EsQueryBuilderUtils.generateFieldQueryBuilder(keyword,true, fieldConfigDTO.getSearchFieldList());if(BooleanUtils.isTrue(fieldConfigDTO.getIsNested())){// 如果是嵌套类型字段,则使用NestedQueryBuilder来处理NestedQueryBuildernestedQueryBuilder = new NestedQueryBuilder(fieldConfigDTO.getMainField(), boolQueryBuilder, ScoreMode.Avg);if(highlight && isHighlight){// 如果需要高亮显示,则为嵌套类型字段设置高亮处理String innerHitName = generateInnerHitName(fieldEnum);InnerHitBuilder innerHitBuilder = EsHighlightUtils.buildNestHighlightBuilder(innerHitName,fieldConfigDTO.getSearchFieldList());nestedQueryBuilderHighlightMap.put(nestedQueryBuilder,innerHitBuilder);}return nestedQueryBuilder;}else{// 对于非嵌套类型字段,处理高亮显示的逻辑if(highlight && isHighlight){// 收集非嵌套类型的高亮字段noNestedHighlightFields.addAll(fieldConfigDTO.getSearchFieldList());}return boolQueryBuilder;}}/*** 生成嵌套查询的innerHit名称* @param fieldEnum* @return java.lang.String**/private String generateInnerHitName(EsQueryFieldEnum fieldEnum){IntAccumulator accumulator = accNestedFieldMap.get(fieldEnum.getFieldConfig().getMainField());accumulator.accumulate(1);return fieldEnum.getFieldConfig().getMainField()+"-"+accumulator.getValue();}/*** 向查询构建器中添加公司名称条件。* @param esQueryBuilder ES查询构建器,用于生成特定的ES查询条件。* @param keywordQueryBuilder 关键词查询构建器,用于组合不同的查询条件。* @param keyword 用户输入的关键词,用于匹配公司名称。*/public static void addCompanyNameCondition(EsQueryBuilder esQueryBuilder,BoolQueryBuilder keywordQueryBuilder,String keyword) {// 根据关键词和字段类型(当前公司名称),生成查询条件,并添加到关键词查询构建器中keywordQueryBuilder.should(esQueryBuilder.generateCondition(keyword,EsQueryFieldEnum.CURRENT_COMPANY,true));// 根据关键词和字段类型(履历中的公司名称),生成查询条件,并添加到关键词查询构建器中keywordQueryBuilder.should(esQueryBuilder.generateCondition(keyword,EsQueryFieldEnum.TRACK_RECORD_COMPANY,true));}}

其他相关代码:
定义一个适用Lambda表达式的接口

/*** Es 搜索条件处理器*/
@FunctionalInterface
public interface IEsQueryConditionHandler {/*** 处理Es搜索条件* @param esQueryBuilder* @param keywordQueryBuilder* @param keyword* @return void*/void handle(EsQueryBuilder esQueryBuilder, BoolQueryBuilder keywordQueryBuilder,String keyword);
}

定义搜索字段的枚举

/*** 专家库ES查询字段枚举*/
public enum EsQueryFieldEnum {/*** 当前公司*/CURRENT_COMPANY(10,"当前公司", EsQueryFieldConfigDTO.builder().mainField("companyInfo").isNested(false).searchFieldList(List.of("companyInfo.companyName")).build()),/*** 工作经历公司*/TRACK_RECORD_COMPANY(20,"工作经历公司", EsQueryFieldConfigDTO.builder().mainField("trackRecordList").isNested(true).searchFieldList(List.of("trackRecordList.companyName","trackRecordList.companyOtherName")).build()),;/*** 嵌套字段列表*/private static final List<EsQueryFieldEnum> NESTED_FIELD_LIST = Stream.of(EsQueryFieldEnum.values()).filter(item->item.fieldConfig.getIsNested()).collect(Collectors.toList());EsQueryFieldEnum(Integer value, String description,EsQueryFieldConfigDTO fieldConfig){this.value =value;this.description = description;this.fieldConfig = fieldConfig;}private final Integer value;private final String description;private final EsQueryFieldConfigDTO fieldConfig;public Integer value() {return this.value;}public String getDescription() {return this.description;}public EsQueryFieldConfigDTO getFieldConfig() {return fieldConfig;}/*** 获取嵌套字段列表*/public static List<EsQueryFieldEnum> getNestedFieldList() {return NESTED_FIELD_LIST;}}

定义搜索类型的枚举

/*** ES查询类型枚举**/
public enum EsQueryTypeEnum {/*** 公司*/COMPANY(20,"公司", EsQueryBuilder::addCompanyNameCondition),;EsQueryTypeEnum(Integer value, String description,IEsQueryConditionHandler addConditionHandler){this.value =value;this.description = description;this.addConditionHandler = addConditionHandler;}private final Integer value;private final String description;private final IEsQueryConditionHandler addConditionHandler;public Integer value() {return this.value;}public String getDescription() {return this.description;}public IEsQueryConditionHandler getAddConditionHandler() {return addConditionHandler;}public static EsQueryTypeEnum resolve(Integer statusCode) {for (EsQueryTypeEnum status : values()) {if (status.value.equals(statusCode)) {return status;}}return null;}}

使用方法

SearchSourceBuilder searchSourceBuilder = queryBuilder// 增加关键字查询条件条件.addCondition()// 组合条件过滤.filterCondition(EsQueryHandler.getAdvancedSearchQueryBuilder(searchParameter))//生成查询语句.build();
// 获取总条数
Integer total = EsService.countBySearch(searchSourceBuilder);//重新生成高亮查询语句
searchSourceBuilder = queryBuilder.buildWithHighlight();
//补充排序规则
EsQueryHandler.setSearchSortRule(searchSourceBuilder,searchParameter.getSortType());
// 从第几页开始
searchSourceBuilder.from(searchParameter.getOffset());
// 每页显示多少条
searchSourceBuilder.size(searchParameter.getLimit());
//分页搜索
List<EsAllInfoDTO> allInfoList = EsService.listByPageSearch(searchSourceBuilder);

问题2解决方案

合并高亮的处理,这个问题实际就是:对于一个字符串a,存在多个字符串a1,a2,a3,并且a1,a2,a3再过滤掉<em></em> 字符后是相同的字符串。现在需要将字符串a,a1,a2,a3 合并为一个字符串fa。合并后的字符串需要满足:
1、fa过滤掉<em></em> 字符后同a相同
2、所有在a1,a2,a3<em></em>包围的子字符串,在fa同样被<em></em>包围

另外要保证一个点是原始的字符串a不能本身就有<em></em> 这些字符串,这个可以通过对数据源头进行过滤就可以了。比如使用Jsonp 过滤。

合并高亮字符串的具体的实现算法如下:

/*** Es高亮工具类*/
public class EsHighlightUtils {public static final String emBegin = "<em>";public static final String emEnd = "</em>";private static final String emRegex = "(?i)<em>|</em>";private static final int emBeginLen = emBegin.length();private static final int emEndLen = emEnd.length();/*** 将字符串数组中的字符串合并,并在特定位置添加增强标签(<em></em>)。* @param stringList 字符串数组,数组中所有字符串如果去除"<em>" 和"</em>"后必定是相同的字符串。* @return 合并后的字符串,增强了指定的字符串片段。*/public static String mergeStrWithEmTags(List<String> stringList) {// 移除原始字符串中的所有em标签,获取干净的源字符串String sourceStr = stringList.get(0).replaceAll(emRegex, "");// 使用StringBuilder来操作源字符串,以便高效地添加em标签StringBuilder sourceBuilder = new StringBuilder(sourceStr);// 初始化一个布尔数组,用于标记哪些字符需要增强boolean[] emFlags = new boolean[sourceStr.length()];// 填充布尔数组,标记需要增强的字符位置fillEmFlags(stringList, emFlags);// 根据标记,在相应位置添加em标签addEmFlags(sourceBuilder, emFlags);return sourceBuilder.toString();}/*** 为给定的字符串数组中的每个字符串设置强调标志数组。* 该方法会查找每个字符串中所有"<em>"开头和"</em>"结尾的包围结构,* 并将这些包围结构在原字符串中的对应部分在标志数组中设置为true。*  @param stringList 字符串数组,包含需要处理的字符串。* @param emFlags 增强标志数组,与字符串数组对应,用于标记特定部分。*/private static void fillEmFlags(List<String> stringList, boolean[] emFlags) {// 遍历字符串数组,为每个字符串设置强调标志for(int j = 0; j< stringList.size(); j++){String str = stringList.get(j);// 查找每个字符串中"<em>"的起始位置int beginIndex = str.indexOf(emBegin);int cumulativeOffset = 0;int noEmLen = 0;int endIndex = 0;while(beginIndex != -1){//计算没有增强的字符串长度noEmLen = endIndex>0?Math.max(beginIndex - (endIndex + emEndLen),0):beginIndex;// 查找"<em>"后的"</em>"位置endIndex = str.indexOf(emEnd,beginIndex+emBeginLen);if(endIndex==-1){// 如果找不到结束标签,则跳出循环break;}// 计算被包围的子字符串长度int emSubLength = endIndex - beginIndex - emBeginLen;// 更新累计偏移量,跳过未增强的字符串cumulativeOffset = cumulativeOffset+ noEmLen;// 将被包围的子字符串在标志数组中对应的元素设置为truefor(int i=0;i<emSubLength;i++){emFlags[cumulativeOffset + i] = true;}// 更新累计偏移量,为处理下一个"<em>"做准备cumulativeOffset = cumulativeOffset + emSubLength;// 计算下一个"<em>"标签的起始位置beginIndex = endIndex + emEndLen;// 继续查找下一个"<em>"beginIndex = str.indexOf(emBegin,beginIndex);}}}/*** 向源字符串中插入增强标签。* 根据给定的增强标志数组(emFlags),在源字符串(sourceBuilder)中插入开始(emBegin)和结束(emEnd)标签。* 当emFlags中的元素为true时,表示字符串的这个位置需要被增强* @param sourceBuilder 被插入标签的源字符串的StringBuilder对象。* @param emFlags 增强标志数组,true表示字符串的这个位置需要被增强。*/private static void addEmFlags(StringBuilder sourceBuilder, boolean[] emFlags) {// 初始化是否开始插入标签的标志和累计偏移量boolean startEm = false;int cumulativeOffset = 0 ;// 遍历增强标志数组,根据标志插入相应的标签for (boolean emFlag : emFlags) {if (emFlag) {// 当前位置需要插入开始标签if (!startEm) {// 第一次需要插入开始标签,进行插入操作并更新累计偏移量startEm = true;sourceBuilder.insert(cumulativeOffset, emBegin);cumulativeOffset += emBeginLen;}// 无论是否第一次,只要需要插入开始标签,累计偏移量就需要增加cumulativeOffset++;} else {// 当前位置需要插入结束标签if (startEm) {// 已经开始插入标签,进行插入操作并更新累计偏移量sourceBuilder.insert(cumulativeOffset, emEnd);cumulativeOffset += emEndLen;}// 标记不再插入开始标签startEm = false;// 累计偏移量增加cumulativeOffset++;}}// 如果遍历结束时正在插入开始标签,插入结束标签if(startEm){sourceBuilder.insert(cumulativeOffset,emEnd);}}/*** 构建嵌套的高亮 InnerHitBuilder* @param name* @param fields* @return org.elasticsearch.index.query.InnerHitBuilder*/public static InnerHitBuilder buildNestHighlightBuilder(String name, Collection<String> fields) {if(CollectionUtils.isEmpty(fields)){return null;}InnerHitBuilder innerHitBuilder = StringUtils.isBlank(name)?new InnerHitBuilder():new InnerHitBuilder(name);HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.preTags(emBegin).postTags(emEnd);//设置高亮的方法highlightBuilder.highlighterType("plain");//设置分段的数量不做限制highlightBuilder.numOfFragments(0);for(String field:fields){highlightBuilder.field(field);}innerHitBuilder.setHighlightBuilder(highlightBuilder);return innerHitBuilder;}/*** 构建非嵌套的高亮 HighlightBuilder* @param fields* @return org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder*/public static HighlightBuilder buildNotNestHighlightBuilder(Collection<String> fields) {HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.preTags(emBegin).postTags(emEnd);//设置高亮的方法highlightBuilder.highlighterType("plain");//设置分段的数量不做限制highlightBuilder.numOfFragments(0);for(String field:fields){highlightBuilder.field(field);}return highlightBuilder;}
}

修改https://blog.csdn.net/weixin_48990070/article/details/120342597 这篇笔记中的替换高亮处理的代码,思路为每次只合并找到的第一个高亮内容,将它和当前的原始内容合并,并将合并后的内容替换掉原始内容。重复这个动作知道所有高亮的内容都被合并到当前的原始内容中。

	/*** 替换嵌套高亮的值* @param sourceObj* @param nestedEle* @param highlightEle* @return void*/private void replaceInnerHighlightValue(JsonObject sourceObj, JsonElement nestedEle, JsonElement highlightEle){if(nestedEle==null || highlightEle==null){return ;}//获取源对象中的嵌套字段名称JsonObject nestedObj= nestedEle.getAsJsonObject();String innerFieldName = nestedObj.get("field").getAsString();//获取当前对象匹配的源对象中的偏移位置int innerFieldOffset = nestedObj.get("offset").getAsInt();//获取源对象JsonObject findSourceObj = GsonUtils.getJsonObjectForArray(sourceObj,innerFieldName,innerFieldOffset);if(findSourceObj==null){return ;}//替换高亮的部分log.debug("高亮的部分:{}",highlightEle);JsonObject highlightObj = highlightEle.getAsJsonObject();highlightObj.entrySet().forEach((h)->{//合并高亮字段对应的原值String highlightValue = h.getValue().getAsString();JsonObject currentSourceObj = findSourceObj;String[] keyNames = StringUtils.split(h.getKey(),".");//循环到倒数第二层,获取待替换字段值对象for(int i=0;i<keyNames.length-2;i++){String keyName = keyNames[i+1];currentSourceObj = currentSourceObj.get(keyName).getAsJsonObject();}//获取最后一层的字段名称String lastFieldName = keyNames[keyNames.length-1];//获取高亮字段对应的原值String sourceValue = currentSourceObj.get(lastFieldName).getAsString();//合并原值和高亮增强的值String mergedValue = EsHighlightUtils.mergeStrWithEmTags(List.of(sourceValue, highlightValue));//替换最后一层对象的指定字段的值GsonUtils.replaceFieldValue(currentSourceObj,lastFieldName, mergedValue);});log.debug("替换后的高亮的部分{}",findSourceObj);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/817850.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

银河麒麟高级服务器操作系统adb读写缓慢问题分析

1.问题环境 处理器&#xff1a; HUAWEI Kunpeng 920 5251K 内存&#xff1a; 512 GiB 整机类型/架构&#xff1a; TaiShan 200K (Model 2280K) BIOS版本&#xff1a; Byosoft Corp. 1.81.K 内核版本 4.19.90-23.15.v2101.ky10.aarch64 第三方应用 数据库 2.问题…

【脚本】JAVA 执行 阿里QLExpress 动态脚本 demo 进阶版 增加项目灵活性

【脚本】JAVA 执行 阿里QLExpress 脚本 demo 进阶版 测试demo import com.ql.util.express.DefaultContext;public class QlExpressTest {public static void main(String[] args) throws Exception {QLExpressManager qlExpressManager new QLExpressManager();DefaultConte…

苹果个人证书管理

根据近日工业和信息化部发布的《工业和信息化部关于开展移动互联网应用程序备案工作的通知》&#xff0c;相信不少要进行IOS平台App备案的朋友遇到了一个问题&#xff0c;就是apple不提供云管理式证书的下载&#xff0c;也就无法获取公钥及证书SHA-1指纹。 已经上架的应用不想重…

瑞芯微RK3568/RK3588+鸿蒙,矿鸿工控屏、矿鸿工控板、矿鸿网关,推动矿业数智化变革

4月10日至12日&#xff0c;以“绿色智能创新&#xff0c;携手共赢未来”为主题的第二届中国国际矿业装备与技术展览会在西安举行。信迈科技携矿鸿解决方案及产品亮相&#xff0c;赋能矿山行业数智化升级和国产化改造进程全面提速。 作为华为矿山军团矿鸿生态使能合作伙伴&#…

【位运算】3097. 或值至少为 K 的最短子数组 II

本文涉及知识点 位运算 LeetCode3097. 或值至少为 K 的最短子数组 II 给你一个 非负 整数数组 nums 和一个整数 k 。 如果一个数组中所有元素的按位或运算 OR 的值 至少 为 k &#xff0c;那么我们称这个数组是 特别的 。 请你返回 nums 中 最短特别非空 子数组 的长度&…

数据可视化-ECharts Html项目实战(10)

在之前的文章中&#xff0c;我们学习了如何在ECharts中编写雷达图&#xff0c;实现特殊效果的插入运用&#xff0c;函数的插入&#xff0c;以及多图表雷达图。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&…

国内免费中文版ChatGPT网站入口(2024/4/15)

首先向大家介绍一些基本信息&#xff1a;ChatGPT3.5在官方网站上是可以免费使用的&#xff0c;而ChatGPT4.0则采取按月收费的模式&#xff0c;价格为20美元&#xff08;约合140元人民币&#xff09;。因此&#xff0c;所谓的免费使用主要指的是GPT3.5。如果有人声称GPT4.0也是免…

vite+vue3+antDesignVue 记录-持续记录

记录学习过程 持续补充 每天的学习点滴 开始时间2024-04-12 1&#xff0c;报错记录 &#xff08;1&#xff09;env.d.ts文件 解决方法&#xff1a; 在env.d.ts文件中添加以下代码&#xff08;可以看一下B站尚硅谷的讲解视频&#xff09; declare module *.vue {import { Defi…

关于centos8自带的apache2.4开启https后,XP系统的IE8无法显示网页的问题

经检验&#xff0c;是因为系统的apache和openssl版本太高导致的。 禁用系统默认的apache2.4&#xff0c;自己重新源码编译安装一套openssl-1.0.1fapache2.2.23php7.1.2即可。跟update-crypto-policies没有关系&#xff0c;可保持默认的DEFAULT状态。 关于centos8自带的apache2…

数据结构从入门到实战——顺序表

目录 前言 一、顺序表的概念及结构 1.1 线性表 二、顺序表分类 三、动态顺序表的实现 3.1 顺序表结构的创建以及初始化 3.2 顺序表的销毁 3.3 顺序表的打印 3.4 尾插数据 ——最困难的 3.5 头插数据 3.6 尾删数据 3.7 头部删除数据 前言 在计算机科学和数据结…

不饱和脂肪酸的综述

1.概述 不饱和脂肪酸是一类脂肪酸&#xff0c;其碳链上存在双键&#xff08;不饱和键&#xff09;。根据双键的数量和位置&#xff0c;不饱和脂肪酸可以进一步分为以下两类&#xff1a; 单不饱和脂肪酸&#xff08;Monounsaturated fatty acids&#xff0c;简称MUFA&#xff0…

excel添加折线图,如何将日期作为横坐标?

就这么两列数据&#xff0c;想添加一个以日期为横坐标的折线图&#xff0c;但是出来的折线是这个样子&#xff0c;切换行/列也不行&#xff0c;怎么办呢&#xff1f; 实际上这个折线图中包括两条折线&#xff0c;蓝色的是日期的折线&#xff0c;橙色的是时间的折线&#xff0c;…

夜月一帘幽梦,春风十里“三指针法“ (链表面试题篇2)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能接…

第 6 章 URDF、Gazebo与Rviz综合应用(自学二刷笔记)

重要参考&#xff1a; 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 6.7 URDF、Gazebo与Rviz综合应用 关于URDF(Xacro)、Rviz 和 Gazebo 三者的关系&#xff0c;前面已有阐述&…

无人机三维建模对光伏测绘的影响有多大?

在光伏电站建设前期阶段&#xff0c;需要对选址地点进行测绘&#xff0c;人工测绘容易出现数据不准确、信息共享不畅等缺陷。随着科技的快速发展以及无人机技术的不断提升&#xff0c;许多光伏企业选择借助无人机进行测绘工作&#xff0c;快速生成三维建模&#xff0c;提高测绘…

虚拟内存映像

最下面是固定的内容&#xff0c; bss放未初始化的变量、data是初始化的、text是代码内容&#xff0c; stack放寄存器放不下的局部变量&#xff0c; heap就是用户控制的地方&#xff0c;栈由编译器控制。 为什么寄存器也不能很多&#xff1f; 因为寄存器寻址也要编码&#…

ATFX港股:长周期看,恒生指数报价已经回到2008年以来的底部区域

消息面&#xff1a; 1、 4月12日&#xff0c;官方发布《推动资本市场高质量发展的若干意见》文件&#xff0c;其中提到九条意见&#xff0c;被称为“国九条”&#xff0c;重要内容有&#xff1a;将上市前突击“清仓式”分红等情形纳入发行上市负面清单&#xff1b;推动一年多次…

less+rem+媒体查询布局(主流)

rem适配布局 一.rem基础二.媒体查询1.概念2.语法&#xff08;1&#xff09;.mediatype查询类型&#xff08;2&#xff09;.关键字&#xff08;3&#xff09;.媒体特性&#xff08;4&#xff09;.应用 3.媒体查询rem实现元素动态大小变化4.引入资源&#xff08;针对不同媒体查询…

完全免费、私有且本地运行的搜索聚合器FreeAskInternet原理解读

我们在百度搜索,会出现如下回答方式,如何实现的呢?让我们看看一个完全免费、私有且本地运行的搜索聚合器FreeAskInternet的实现原理吧。 一.功能介绍 FreeAskInternet 是一个完全免费、私有且本地运行的搜索聚合器,并使用 LLM 生成答案,无需 GPU。用户可以提出问题,系统…

自动化测试selenium(2)

目录 WebDriver介绍 WebDriver使用 使用WebDriver驱动操作浏览器(打开一个百度) WebDriver 相关API 定位元素 操作元素 上一篇主要介绍了自动化测试的概念以及selenium的基本原理, 这里我们来讲一下如何利用selenium来写测试用的脚本. WebDriver介绍 Selenium是一个用于…