用ES搜索关键字并且返回模糊字段高亮

这篇具有很好参考价值的文章主要介绍了用ES搜索关键字并且返回模糊字段高亮。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

   一般来说,各个网站,首页的搜索,都会有进行全文搜索的示例,并且把模糊匹配的多个数据进行标记(高亮),这样便于全局检索关键的数据,便于客户进行浏览。基于此,本文简单介绍这种功能基本java 的 实现

   由于公司页面此功能隐藏了,本文就以接口调用返回看具体实现了

   下面是最终的想要的结果,搜索内容代码 1, type 是 类型  
    类型ID: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同,

   每一种类型代表一张表的数据
es高亮查询,java,maven,spring boot

控制层 controller 

@ApiOperation("关键字搜索结果页聚合")
@PostMapping("/pageListAgg")
public R pageListAgg(@RequestBody @Valid GlobalSearchDataReqDTO globalSearchDataReqDTO) {
 return success(globalSearchService.pageListAgg(globalSearchDataReqDTO));
}
逻辑层 service
public Map<String, Object> pageListAgg(GlobalSearchDataReqDTO globalSearchDataReqDTO) {
        Map<String, Object> resultMap = new HashMap<>();

        if (Objects.isNull(globalSearchDataReqDTO.getType())) {
            globalSearchDataReqDTO.setType(1);
        }
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 构建查询条件 & 高亮
        this.searchConditionBuild(globalSearchDataReqDTO, nativeSearchQueryBuilder);
        // 排序
        nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort());
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("type").order(SortOrder.ASC));
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        // terms 指定分组的别名, field 指定要分组的字段名, size 指定查询结果的数量 默认是10个
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("typeGroup").field("type").size(10);
        // 分页
        if (Objects.isNull(globalSearchDataReqDTO.getType())) {
            globalSearchDataReqDTO.setType(1);
        }
        TopHitsAggregationBuilder topHitsAggregationBuilder = AggregationBuilders.topHits(globalSearchDataReqDTO.getType().toString());
        if (globalSearchDataReqDTO.getPageNum() >= 1) {
            topHitsAggregationBuilder.from((globalSearchDataReqDTO.getPageNum() - 1) * globalSearchDataReqDTO.getPageSize());
        }
        topHitsAggregationBuilder.size(globalSearchDataReqDTO.getPageSize());
        aggregation.subAggregation(topHitsAggregationBuilder);
        nativeSearchQueryBuilder.addAggregation(aggregation);
        // 构建并查询
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
        SearchHits<GlobalSearchData> search = elasticsearchTemplate.search(nativeSearchQuery, GlobalSearchData.class);

        // 聚合结果
        ParsedStringTerms typeAgg = Objects.requireNonNull(search.getAggregations()).get("typeGroup");
        Map<String, Object> typeMap = getStringsTypeList(typeAgg, globalSearchDataReqDTO.getType());

        List<GlobalSearchData> globalSearchDataList = JSONObject.parseArray(JSON.toJSONString(typeMap.get("dataList")), GlobalSearchData.class);
        List<GlobalSearchResultDTO> resultList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(globalSearchDataList)) {
            // 高亮
            List<SearchHit<GlobalSearchData>> searchHits = search.getSearchHits();
            List<GlobalSearchData> dataList = getHighLightData(searchHits, globalSearchDataList);
            // 返回数据处理
            resultList = globalSyncDataService.resultDataHandle(dataList, globalSearchDataReqDTO);
        }
        //resultMap.put("total", Objects.nonNull(typeMap.get("typeTotal")) ? typeMap.get("typeTotal") : 0); //总记录数
        resultMap.put("total", resultList.size()); //记录数
        resultMap.put("resultList", resultList); //结果数据
        typeMap.remove("typeTotal");
        typeMap.remove("dataList");
        resultMap.put("typeMap", typeMap); //聚合数据
        return resultMap;
    }

// 构建查询条件 & 高亮


private void searchConditionBuild(GlobalSearchDataReqDTO globalSearchDataReqDTO, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        String searchContent = globalSearchDataReqDTO.getSearchContent();
        builder.must(QueryBuilders.matchQuery("accountId", globalSearchDataReqDTO.getAccountId()));

        // 其他条件 模糊搜索 OR
        BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery();
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("customerName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("contactName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("companyName", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("title", searchContent).boost(5).slop(25));
        shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("code", searchContent).boost(5).slop(25));

        if (!judgeContainsStr(searchContent) && searchContent.length() < 13) {
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("mobile", searchContent).boost(5).slop(25));
            shouldBuilder.should(QueryBuilders.termQuery("phone", searchContent).boost(5));
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("phone", searchContent).boost(5).slop(25));
            shouldBuilder.should(QueryBuilders.matchPhrasePrefixQuery("companyTelephone", searchContent).boost(5).slop(25));
        }
//        BoolQueryBuilder shouldBuilder1 = QueryBuilders.boolQuery();
//        shouldBuilder1.should(QueryBuilders.matchPhraseQuery("title", searchContent).slop(25));
//        builder.must(shouldBuilder1);
        builder.must(shouldBuilder);


        if (!tokenManager.getBase().isMain()) {
            // 当前登录人userId在 canSeeUserId中 或者 canSeeUserId = 1
            BoolQueryBuilder userIdShouldBuild = QueryBuilders.boolQuery();
            userIdShouldBuild.should(QueryBuilders.termQuery("canSeeUserId", globalSearchDataReqDTO.getUserId()));
            userIdShouldBuild.should(QueryBuilders.termQuery("canSeeUserId", -1));
            builder.must(userIdShouldBuild);
        }
        nativeSearchQueryBuilder.withFilter(builder);
        nativeSearchQueryBuilder.withQuery(builder);

        //设置高亮的字段
        nativeSearchQueryBuilder.withHighlightFields(
                new HighlightBuilder.Field("customerName"),
                new HighlightBuilder.Field("contactName"),
                new HighlightBuilder.Field("companyName"),
                new HighlightBuilder.Field("title"),
                new HighlightBuilder.Field("code"),
                new HighlightBuilder.Field("mobile"),
                new HighlightBuilder.Field("phone"),
                new HighlightBuilder.Field("companyTelephone")
        );

        nativeSearchQueryBuilder.withHighlightBuilder(new HighlightBuilder()
                .preTags("<em style=\"color:#165DFF\">").postTags("</em>")
                .fragmentSize(800000) //最大高亮分片数
                .numOfFragments(0)); //从第一个分片获取高亮片段
    }
获取分组结果

  private Map<String, Object> getStringsTypeList(ParsedStringTerms typeAgg, Integer type) {
        Map<String, Object> retMap = new HashMap<>();
        if (Objects.nonNull(typeAgg)) {
            List<? extends Terms.Bucket> buckets = typeAgg.getBuckets();
            buckets.forEach(bucket -> {
                if (type.toString().equals(bucket.getKeyAsString())) {
                    retMap.put("typeTotal", bucket.getDocCount());
                }
                retMap.put(bucket.getKeyAsString(), bucket.getDocCount() > 2000 ? 2000 : bucket.getDocCount()); //记录数 最大限制2000条

                if (type.toString().equals(bucket.getKeyAsString())) {
                    ParsedTopHits topHits = bucket.getAggregations().get(bucket.getKeyAsString());
                    if (Objects.nonNull(topHits.getHits())) {
                        org.elasticsearch.search.SearchHit[] hits = topHits.getHits().getHits();
                        List<GlobalSearchData> dataList = Lists.newArrayList();
                        for (org.elasticsearch.search.SearchHit hit : hits) {
                            dataList.add(JSONObject.parseObject(JSONObject.toJSONString(hit.getSourceAsMap()), GlobalSearchData.class));
                        }
                        retMap.put("dataList", dataList);
                    }
                }
            });
        }

        return retMap;
    }

处理结果返回字段
 

public List<GlobalSearchResultDTO> resultDataHandle(List<GlobalSearchData> globalSearchDataList, GlobalSearchDataReqDTO globalSearchDataReqDTO) {
        Long accountId = globalSearchDataReqDTO.getAccountId();
        List<GlobalSearchResultDTO> list = Lists.newArrayList();
        if (CollectionUtils.isEmpty(globalSearchDataList)) {
            return list;
        }
        List<Long> ids = globalSearchDataList.stream().map(GlobalSearchData::getSystemId).collect(Collectors.toList());

        switch (globalSearchDataReqDTO.getType()) {
            case ESGlobalTypeConstant.LEAD:
                list = leadsMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(Objects.isNull(item.getStatus()) ? ConstantsEnum.LeadsStatus.FOLLOW_UP.title : ConstantsEnum.LeadsStatus.getTitle(item.getStatus())));
                break;
            case ESGlobalTypeConstant.CUSTOMER:
                list = customerMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setLevel(dictionaryService.getTitleByTypeAndCode(DictTypeEnum.CUSTOMER_LEVEL, Optional.ofNullable(item.getLevel()).map(Object::toString).orElse(""))));
                break;
            case ESGlobalTypeConstant.BUSINESS:
                list = businessMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> {
                    item.setStatusName(String.valueOf(BusinessStatusEnum.getDesc(item.getStatus().byteValue())));
                });
                break;
            case ESGlobalTypeConstant.CONTACT:
                list = contactMapper.selectGlobalResultData(accountId, ids);
                break;
            case ESGlobalTypeConstant.PRESALE:
                list = presaleMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> {
                    item.setStatusName(PresaleStatusEnum.getByCode(item.getStatus()).getDesc());
                    if (StringUtils.equals(item.getStatusName(), PresaleStatusEnum.PENDING_APPROVAL.getDesc())) {
                        item.setPresaleStage("--");
                        return;
                    }
                    if (StringUtils.isBlank(item.getPresaleStage())) {
                        item.setPresaleStage(presaleStageService.getFirstStageName(accountId, item.getSystemId()));
                    }
                });
                break;
            case ESGlobalTypeConstant.ORDER:
                list = orderMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(String.valueOf(OrderStatusEnum.getDesc(item.getStatus().byteValue()))));
                break;
            case ESGlobalTypeConstant.CONTRACT:
                list = contractMapper.selectGlobalResultData(accountId, ids);
                list.forEach(item -> item.setStatusName(ConstantsEnum.ContractStatus.getTitle(item.getStatus())));
                break;
        }
        return listCover(globalSearchDataList, list, globalSearchDataReqDTO.getSearchContent());
    }
   private List<GlobalSearchData> getHighLightData(List<SearchHit<GlobalSearchData>> searchHits, List<GlobalSearchData> dataList) {
        List<GlobalSearchData> globalSearchDataList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(dataList)) {
            List<Long> ids = dataList.stream().map(GlobalSearchData::getId).collect(Collectors.toList());
            for (SearchHit<GlobalSearchData> searchHit : searchHits) {
                if (ids.contains(searchHit.getContent().getId())) {
                    highLightHandle(searchHit, globalSearchDataList);
                }
            }
        } else {
            for (SearchHit<GlobalSearchData> searchHit : searchHits) {
                highLightHandle(searchHit, globalSearchDataList);
            }
        }
        return globalSearchDataList;
    }
 private void highLightHandle(SearchHit<GlobalSearchData> searchHit, List<GlobalSearchData> globalSearchDataList) {
        //高亮的内容
        Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
        //将高亮的内容填充到content中
        searchHit.getContent().setCustomerName(highlightFields.get("customerName") == null ? searchHit.getContent().getCustomerName() : highlightFields.get("customerName").get(0));
        searchHit.getContent().setContactName(highlightFields.get("contactName") == null ? searchHit.getContent().getContactName() : highlightFields.get("contactName").get(0));
        searchHit.getContent().setCompanyName(highlightFields.get("companyName") == null ? searchHit.getContent().getCompanyName() : highlightFields.get("companyName").get(0));
        searchHit.getContent().setTitle(highlightFields.get("title") == null ? searchHit.getContent().getTitle() : highlightFields.get("title").get(0));
        searchHit.getContent().setCode(highlightFields.get("code") == null ? searchHit.getContent().getCode() : highlightFields.get("code").get(0));
        searchHit.getContent().setMobile(highlightFields.get("mobile") == null ? searchHit.getContent().getMobile() : highlightFields.get("mobile").get(0));
        searchHit.getContent().setPhone(highlightFields.get("phone") == null ? searchHit.getContent().getPhone() : highlightFields.get("phone").get(0));
        searchHit.getContent().setCompanyTelephone(highlightFields.get("companyTelephone") == null ? searchHit.getContent().getCompanyTelephone() : highlightFields.get("companyTelephone").get(0));
        globalSearchDataList.add(searchHit.getContent());
    }

入参
 

@Data
public class GlobalSearchDataReqDTO extends BasePage {

    /**
     * 搜索内容
     */
    //@NotNull(message = "搜索内容不能为空")
    private String searchContent;

    /**
     * 类型ID: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同
     */
    private Integer type;

    @ApiModelProperty(value = "归属人", hidden = true)
    private List<Long> ownerIds;

    @ApiModelProperty(value = "池归属", hidden = true)
    private List<Long> pools;

    @ApiModelProperty(value = "当前用户的部门", hidden = true)
    private List<Long> deptIds;

    @ApiModelProperty(value = "跟随客户的归属人", hidden = true)
    private List<Long> customerOwnerIds;

    @ApiModelProperty(value = "池管理员的池", hidden = true)
    private List<Long> adminPool;

    @ApiModelProperty(value = "池成员的池", hidden = true)
    private List<Long> partPool;
}


返回值参数
 文章来源地址https://www.toymoban.com/news/detail-631569.html

@Data
public class GlobalSearchResultDTO {

    @ApiModelProperty(value = "对应主键ID")
    private Long systemId;

    /**
     * 类型: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同
     */
    @ApiModelProperty(value = "类型")
    private Integer type;

    @ApiModelProperty(value = "类型名称")
    private String typeName;

    @ApiModelProperty(value = "标题")
    private String title;

    @ApiModelProperty(value = "编号")
    private String code;

    @ApiModelProperty(value = "客户id")
    private Long customerId;

    @ApiModelProperty(value = "客户名称")
    private String customerName;

    @ApiModelProperty(value = "联系人姓名")
    private String contactName;

    @ApiModelProperty(value = "公司名称")
    private String companyName;

    @ApiModelProperty(value = "联系人手机")
    private String mobile;

    @ApiModelProperty(value = "联系人电话")
    private String phone;

    @ApiModelProperty(value = "公司电话")
    private String companyTelephone;

    @ApiModelProperty(value = "池id")
    private Long poolId;

    @ApiModelProperty(value = "池名称")
    private String poolName;

    @ApiModelProperty(value = "状态")
    private Integer status;

    @ApiModelProperty(value = "状态名")
    private String statusName;

    @ApiModelProperty(value = "关联数据id")
    private Long relationId;

    @ApiModelProperty(value = "来源")
    private String source;

    /* 客户 */
//    @ApiModelProperty(value = "所在区域")
//    private String location;

    @ApiModelProperty("客户等级")
    private String level;

    /* 商机 */
//    @ApiModelProperty(value = "协销人员")
//    private Long assistUserId;
//
//    @ApiModelProperty(value = "协助人员,逗号分隔")
//    private String assistUser;

    @ApiModelProperty(value = "商机意向度")
    private String stageDesc;

    /* 售前 */
    @ApiModelProperty("售前负责人id")
    private Long presaleSalesmanId;

    @ApiModelProperty(value = "售前负责人")
    private String presaleSalesman;

    @ApiModelProperty(value = "售前阶段")
    private String presaleStage;

    /* 订单 */
//    @ApiModelProperty(value = "商品名称")
//    private String productName;

    @ApiModelProperty(value = "金额")
    private BigDecimal money;

    @ApiModelProperty("签订日期")
    private LocalDate signDate;

    @ApiModelProperty(value = "归属人姓名")
    private String ownerName;

    @ApiModelProperty(value = "数据创建人姓名")
    private String createByName;

    @ApiModelProperty(value = "创建时间")
    private String createTime;

    @ApiModelProperty(value = "联系人手机数组")
    private List<ContactReturn> mobileList;

    public void convertEmptyValue() {
        this.setTitle(StringUtils.originalOrEmpty(this.getTitle()));
        this.setCode(StringUtils.originalOrEmpty(this.getCode()));
        this.setCustomerName(StringUtils.originalOrEmpty(this.getCustomerName()));
        this.setContactName(StringUtils.originalOrEmpty(this.getContactName()));
        this.setCompanyName(StringUtils.originalOrEmpty(this.getCompanyName()));
        this.setPhone(StringUtils.originalOrEmpty(this.getPhone()));
        this.setMobile(StringUtils.originalOrEmpty(this.getMobile()));
        this.setCompanyTelephone(StringUtils.originalOrEmpty(this.getCompanyTelephone()));
        this.setPoolName(StringUtils.originalOrEmpty(this.getPoolName()));
        this.setStatusName(StringUtils.originalOrEmpty(this.getStatusName()));
        this.setSource(StringUtils.originalOrEmpty(this.getSource()));
        this.setLevel(StringUtils.originalOrEmpty(this.getLevel()));
        this.setStageDesc(StringUtils.originalOrEmpty(this.getStageDesc()));
        this.setPresaleSalesman(StringUtils.originalOrEmpty(this.getPresaleSalesman()));
        this.setPresaleStage(StringUtils.originalOrEmpty(this.getPresaleStage()));
        this.setOwnerName(StringUtils.originalOrEmpty(this.getOwnerName()));
        this.setCreateByName(StringUtils.originalOrEmpty(this.getCreateByName()));
    }


}

到了这里,关于用ES搜索关键字并且返回模糊字段高亮的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 拼多多关键字搜索API-通过关键字获取拼多多商品列表

    pinduoduo.item_search 公共参数 请求地址: pinduoduo/item_search 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) secret String 是 调用密钥 api_name String 是 API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等] cache String 否 [yes,no]默认yes,将调用缓存的数据

    2024年02月22日
    浏览(52)
  • 怎样优雅地增删查改(六):按任意字段关键字查询

    定义按任意字段查询过滤器(IKeywordOrientedFilter)接口,查询实体列表Dto若实现该接口,将筛选指定的目标字段(TargetFields)包含指定的(Keyword)的实体。 创建应用过滤条件方法:ApplySearchFiltered,代码如下: 请注意,可应用过滤的条件为: input需实现IKeywordOr

    2024年02月15日
    浏览(29)
  • elasticsearch搜索关键字高亮显示

    使用 elasticsearch 时,有一个很常见的需求是,能在页面上将搜索出的结果中属于的文字,进行高亮显示。 elasticsearch 对这个做了一定的支持,它能查询结果的基础上,额外返回需要高亮显示的整个文本,至于具体你想怎么用它,需要根据业务自行实现。 使用 k

    2023年04月08日
    浏览(39)
  • vue实现搜索高亮关键字

    模仿浏览器中ctrl+f实现搜索 高亮显示 思路: 通过ref获取dom元素 删除当前高亮色; 设置本次搜索的高亮; 进行内容替换; 为首个进行高亮,设置为当前; 关键代码: 定义一个正则 key就代表要高亮的字符串,i 代表匹配大小写 g代表全局匹

    2024年02月10日
    浏览(47)
  • 【JavaScript】函数 ④ ( 函数返回值 | 函数返回值语法 return 关键字 | 函数默认返回值 undefined )

    JavaScript 函数 可以 实现某种特定的功能 , 执行完毕后 , 可以返回一个 \\\" 返回值 \\\" ; 当 函数 被调用执行任务完毕时 , \\\" 返回值 \\\" 会被返回给调用者 ; 如果 函数 中没有明确 使用 return 返回 \\\" 返回值 \\\" , 那么函数会默认返回undefined 值 ; 在 JavaScript 中 , 函数 返回值是 通过

    2024年04月10日
    浏览(43)
  • ES6中let和const关键字与var关键字之间的区别?

    前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而又亲切的学习平台。在这个

    2024年02月09日
    浏览(40)
  • Vue关键字搜索功能(小案例)

    这里介绍两种方法:1、使用watch侦听方法 2、computed计算属性方法 页面结果: 第一种 第二种 相较于watch写法,computed写法看上去更加简洁,比如: 1、computed自身就是一种计算属性,不必再去data中新建一个属性。 2、计算属性实时更新,不用像watch方法,新建的filPerson初始值为

    2024年02月06日
    浏览(47)
  • 【Python】函数进阶 ① ( 函数返回多个返回值 | 函数参数传递类型简介 | 位置参数 | 关键字参数 )

    在函数中 , 如果要 返回 多个返回值 , 可以 在 return 语句中 , 设置多个返回值 , 这些返回值之间使用 逗号 隔开 , 这些返回值的类型是 元组 tuple 类型的 ; 在下面的代码中 , 返回了 3 个返回值 , 其 本质上是返回了一个包含 3 个元素的 元组 数据容器 , 可以使用多重赋值将返回的

    2024年02月11日
    浏览(39)
  • js将搜索的关键字加颜色

    使用正则匹配并加入span标签,页面渲染时使用v-html渲染即可

    2024年02月11日
    浏览(28)
  • alibaba按关键字搜索商品 API

    为了进行电商平台 的API开发,首先我们需要做下面几件事情。 1)开发者注册一个账号 2)然后为每个alibaba应用注册一个应用程序键(App Key) 。 3)下载alibaba API的SDK并掌握基本的API基础知识和调用 4)利用SDK接口和对象,传入AppKey或者必要的时候获取并传入SessionKey来进行程序

    2024年02月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包