Springboot集成ElasticSearch实现minio文件内容全文检索

一、docker安装Elasticsearch

(1)springboot和Elasticsearch的版本对应关系如下,请看版本对应:

 注意安装对应版本,否则可能会出现一些未知的错误。

(2)拉取镜像

docker pull elasticsearch:7.17.6

(3)运行容器

docker run  -it  -d  --name elasticsearch -e "discovery.type=single-node"  -e "ES_JAVA_OPTS=-Xms512m -Xmx1024m"   -p 9200:9200 -p 9300:9300  elasticsearch:7.17.6

 访问http://localhost:9200/,出现如下内容表示安装成功。

(4)安装中文分词器

进入容器:

docker exec -it elasticsearch bash

然后进入bin目录执行下载安装ik分词器命令:

elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.6/elasticsearch-analysis-ik-7.17.6.zip

退出bash并重启容器:

docker restart elasticsearch

二、安装kibana

Kibana 是为 Elasticsearch设计的开源分析和可视化平台。你可以使用 Kibana 来搜索,查看存储在 Elasticsearch 索引中的数据并与之交互。你可以很容易实现高级的数据分析和可视化,以图表的形式展现出来。

(1)拉取镜像

docker pull kibana:7.17.6

(2)运行容器 

docker run --name kibana -p 5601:5601 --link elasticsearch:es  -e "elasticsearch.hosts=http://es:9200"  -d kibana:7.17.6

--link elasticsearch:es表示容器互联,即容器kibana连接到elasticsearch。

(3)使用kibana dev_tools发送http请求操作Elasticsearch

 三、后端代码

(1)引入maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

(2)application.yml配置

spring:
  elasticsearch:
    uris: http://localhost:9200

(3)实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;


/**
 * @author yangfeng
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "file")
public class File {

    @Id
    private String id;

    /**
     * 文件名称
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String fileName;

    /**
     * 文件分类
     */
    @Field(type = FieldType.Keyword)
    private String fileCategory;

    /**
     * 文件内容
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String fileContent;

    /**
     * 文件存储路径
     */
    @Field(type = FieldType.Keyword, index = false)
    private String filePath;

    /**
     * 文件大小
     */
    @Field(type = FieldType.Keyword, index = false)
    private Long fileSize;

    /**
     * 文件类型
     */
    @Field(type = FieldType.Keyword, index = false)
    private String fileType;

    /**
     * 创建人
     */
    @Field(type = FieldType.Keyword, index = false)
    private String createBy;

    /**
     * 创建日期
     */
    @Field(type = FieldType.Keyword, index = false)
    private Date createTime;

    /**
     * 更新人
     */
    @Field(type = FieldType.Keyword, index = false)
    private String updateBy;

    /**
     * 更新日期
     */
    @Field(type = FieldType.Keyword, index = false)
    private Date updateTime;

}

(4)repository接口,继承ElasticsearchRepository

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author yangfeng
 * @date: 2024年11月9日 15:29
 */
@Repository
public interface FileRepository extends ElasticsearchRepository<File, String> {

    /**
     * 关键字查询
     *
     * @return
     */
    @Highlight(fields = {@HighlightField(name = "fileName"), @HighlightField(name = "fileContent")},
            parameters = @HighlightParameters(preTags = {"<span style='color:red'>"}, postTags = {"</span>"}, numberOfFragments = 0))
    List<SearchHit<File>> findByFileNameOrFileContent(String fileName, String fileContent, Pageable pageable);
}

(5)service接口

import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;

import java.util.List;

/**
 * description: ES文件服务
 *
 * @author yangfeng
 * @version V1.0
 * @date 2023-02-21
 */
public interface IFileService {

    /**
     * 保存文件
     */
    void saveFile(String filePath, String fileCategory) throws Exception;

    /**
     * 关键字查询
     *
     * @return
     */
    List<SearchHit<File>> search(FileDTO dto);

    /**
     * 关键字查询
     *
     * @return
     */
    SearchHits<File> searchPage(FileDTO dto);
}

(6)service实现类

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.MinioUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * description: ES文件服务
 *
 * @author yangfeng
 * @version V1.0
 * @date 2023-02-21
 */
@Slf4j
@Service
public class FileServiceImpl implements IFileService {

    @Autowired
    private FileRepository fileRepository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 保存文件
     */
    @Override
    public void saveFile(String filePath, String fileCategory) throws Exception {
        if (Objects.isNull(filePath)) {
            throw new JeecgBootException("文件不存在");
        }

        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        String fileName = CommonUtils.getFileNameByUrl(filePath);
        String fileType = StringUtils.isNotBlank(fileName) ? fileName.substring(fileName.lastIndexOf(".") + 1) : null;

        InputStream inputStream = MinioUtil.getMinioFile(filePath);

        // 读取文件内容,上传到es,方便后续的检索
        String fileContent = FileUtils.readFileContent(inputStream, fileType);
        File file = new File();
        file.setId(IdUtil.getSnowflake(1, 1).nextIdStr());
        file.setFileContent(fileContent);
        file.setFileName(fileName);
        file.setFilePath(filePath);
        file.setFileType(fileType);
        file.setFileCategory(fileCategory);
        file.setCreateBy(user.getUsername());
        file.setCreateTime(new Date());
        fileRepository.save(file);
    }


    /**
     * 关键字查询
     *
     * @return
     */
    @Override
    public List<SearchHit<File>> search(FileDTO dto) {
        Pageable pageable = PageRequest.of(dto.getPageNo() - 1, dto.getPageSize(), Sort.Direction.DESC, "createTime");
        return fileRepository.findByFileNameOrFileContent(dto.getKeyword(), dto.getKeyword(), pageable);
    }


    @Override
    public SearchHits<File> searchPage(FileDTO dto) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(QueryBuilders.multiMatchQuery(dto.getKeyword(), "fileName", "fileContent"));
        // 设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        String[] fieldNames = {"fileName", "fileContent"};
        for (String fieldName : fieldNames) {
            highlightBuilder.field(fieldName);
        }
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        highlightBuilder.order();
        queryBuilder.withHighlightBuilder(highlightBuilder);

        // 也可以添加分页和排序
        queryBuilder.withSorts(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .withPageable(PageRequest.of(dto.getPageNo() - 1, dto.getPageSize()));

        NativeSearchQuery nativeSearchQuery = queryBuilder.build();

        return elasticsearchRestTemplate.search(nativeSearchQuery, File.class);
    }

}

(7)controller

import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 文件es操作
 *
 * @author yangfeng
 * @since 2024-11-09
 */
@Slf4j
@RestController
@RequestMapping("/elasticsearch/file")
public class FileController {

    @Autowired
    private IFileService fileService;


    /**
     * 保存文件
     *
     * @return
     */
    @PostMapping(value = "/saveFile")
    public Result<?> saveFile(@RequestBody File file) throws Exception {
        fileService.saveFile(file.getFilePath(), file.getFileCategory());
        return Result.OK();
    }


    /**
     * 关键字查询-repository
     *
     * @throws Exception
     */
    @PostMapping(value = "/search")
    public Result<?> search(@RequestBody FileDTO dto) {
        return Result.OK(fileService.search(dto));
    }

    /**
     * 关键字查询-原生方法
     *
     * @throws Exception
     */
    @PostMapping(value = "/searchPage")
    public Result<?> searchPage(@RequestBody FileDTO dto) {
        return Result.OK(fileService.searchPage(dto));
    }

}

(8)工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class FileUtils {

    private static final List<String> FILE_TYPE;

    static {
        FILE_TYPE = Arrays.asList("pdf", "doc", "docx", "text");
    }

    public static String readFileContent(InputStream inputStream, String fileType) throws Exception{
        if (!FILE_TYPE.contains(fileType)) {
            return null;
        }
        // 使用PdfBox读取pdf文件内容
        if ("pdf".equalsIgnoreCase(fileType)) {
            return readPdfContent(inputStream);
        } else if ("doc".equalsIgnoreCase(fileType) || "docx".equalsIgnoreCase(fileType)) {
            return readDocOrDocxContent(inputStream);
        } else if ("text".equalsIgnoreCase(fileType)) {
            return readTextContent(inputStream);
        }

        return null;
    }


    private static String readPdfContent(InputStream inputStream) throws Exception {
        // 加载PDF文档
        PDDocument pdDocument = PDDocument.load(inputStream);

        // 创建PDFTextStripper对象, 提取文本
        PDFTextStripper textStripper = new PDFTextStripper();

        // 提取文本
        String content = textStripper.getText(pdDocument);
        // 关闭PDF文档
        pdDocument.close();
        return content;
    }


    private static String readDocOrDocxContent(InputStream inputStream) {
        try {
            // 加载DOC文档
            XWPFDocument document = new XWPFDocument(inputStream);

            // 2. 提取文本内容
            XWPFWordExtractor extractor = new XWPFWordExtractor(document);
            return extractor.getText();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }


    private static String readTextContent(InputStream inputStream) {
        StringBuilder content = new StringBuilder();
        try (InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
            int ch;
            while ((ch = isr.read()) != -1) {
                content.append((char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return content.toString();
    }

}

(9)dto

import lombok.Data;

@Data
public class FileDTO {

    private String keyword;

    private Integer pageNo;

    private Integer pageSize;

}

四、前端代码

(1)查询组件封装

<template>
    <a-input-search
        v-model:value="pageInfo.keyword"
        placeholder="全文检索"
        @search="handleSearch"
        style="width: 220px;margin-left:30px"
    />
    <a-modal v-model:visible="showSearch" title="全文检索" width="900px" :footer="null"
             destroy-on-close>
      <SearchContent :items="searchItems" :loading="loading"/>
      <div style="padding: 10px;display: flex;justify-content: flex-end">
        <Pagination v-if="pageInfo.total" :pageSize="pageInfo.pageSize" :pageNo="pageInfo.pageNo"
                    :total="pageInfo.total" @pageChange="changePage" :show-total="total => `共 ${total} 条`"/>
      </div>
    </a-modal>
</template>

<script lang="ts" setup>
import {ref} from 'vue'
import {Pagination} from "ant-design-vue";
import SearchContent from "@/components/ElasticSearch/SearchContent.vue"
import {searchPage} from "@/api/sys/elasticsearch"

const loading = ref<boolean>(false)
const showSearch = ref<any>(false)
const searchItems = ref<any>();

const pageInfo = ref<{
  pageNo: number;
  pageSize: number;
  keyword: string;
  total: number;
}>({
  // 当前页码
  pageNo: 1,
  // 当前每页显示多少条数据
  pageSize: 10,
  keyword: '',
  total: 0,
});

async function handleSearch() {
  if (!pageInfo.value.keyword) {
    return;
  }
  pageInfo.value.pageNo = 1
  showSearch.value = true
  await getSearchItems();
}

function changePage(pageNo) {
  pageInfo.value.pageNo = pageNo
  getSearchItems();
}

async function getSearchItems() {
  loading.value = true
  try {
    const res: any = await searchPage(pageInfo.value);
    searchItems.value = res?.searchHits;
    debugger
    pageInfo.value.total = res?.totalHits
  } finally {
    loading.value = false
  }
}
</script>

<style scoped></style>

(2)接口elasticsearch.ts

import {defHttp} from '/@/utils/http/axios';

enum Api {
    saveFile = '/elasticsearch/file/saveFile',
    searchPage = '/elasticsearch/file/searchPage',
}


/**
 * 保存文件到es
 * @param params
 */
export const saveFile = (params) => defHttp.post({
    url: Api.saveFile,
    params
});

/**
 * 关键字查询-原生方法
 * @param params
 */
export const searchPage = (params) => defHttp.post({
    url: Api.searchPage,
    params
},);

(3)搜索内容组件SearchContent.vue

<template>
  <a-spin :spinning="loading">
    <div class="searchContent">
      <div v-for="(item,index) in items" :key="index" v-if="!!items.length > 0">
        <a-card class="contentCard">
          <template #title>
            <a @click="detailSearch(item.content)">
              <div class="flex" style="align-items: center">
                <div>
                  <img src="../../assets/images/pdf.png" v-if="item?.content?.fileType=='pdf'" style="width: 20px"/>
                  <img src="../../assets/images/word.png" v-if="item?.content?.fileType=='word'" style="width: 20px"/>
                  <img src="../../assets/images/excel.png" v-if="item?.content?.fileType=='excel'" style="width: 20px"/>
                </div>
                <div style="margin-left:10px">
                  <article class="article" v-html="item.highlightFields.fileName"
                           v-if="item?.highlightFields?.fileName"></article>
                  <span v-else>{{ item?.content?.fileName }}</span>
                </div>
              </div>
            </a>
          </template>
          <div class="item">
            <article class="article" v-html="item.highlightFields.fileContent"
                     v-if="item?.highlightFields?.fileContent"></article>
            <span v-else>{{
                item?.content?.fileContent?.length > 150 ? item.content.fileContent.substring(0, 150) + '......' : item.content.fileContent
              }}</span>
          </div>
        </a-card>
      </div>
      <EmptyData v-else/>
    </div>
  </a-spin>
</template>
<script lang="ts" setup>
import {useGlobSetting} from "@/hooks/setting";
import EmptyData from "/@/components/ElasticSearch/EmptyData.vue";
import {ref} from "vue";

const glob = useGlobSetting();

const props = defineProps({
  loading: {
    type: Boolean,
    default: false
  },
  items: {
    type: Array,
    default: []
  },
})

function detailSearch(searchItem) {
  const url = ref(`${glob.domainUrl}/sys/common/pdf/preview/`);
  window.open(url.value + searchItem.filePath + '#scrollbars=0&toolbar=0&statusbar=0', '_blank');
}

</script>
<style lang="less" scoped>
.searchContent {
  min-height: 500px;
  overflow-y: auto;
}

.contentCard {
  margin: 10px 20px;
}

a {
  color: black;
}

a:hover {
  color: #3370ff;
}

:deep(.ant-card-body) {
  padding: 13px;
}
</style>

五、效果展示

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前后端请求响应

引入 在之前的例子中&#xff0c;我们编写了一个简单的web类&#xff0c;我们运行启动类&#xff0c;启动内嵌的tomcat后就可以在浏览器通过特定的路径访问tomcat中的应用程序。 但之前编写的程序仅仅是个简单的java类&#xff0c;其并未实现某个接口或继承某个类&…

VS2022编译32位OpenCV

使用环境 Visual Studio 2022 OpenCV: 4.7.0 cmake: 3.30.2一、使用CMake工具生成vs2022的openCV工程解决方案 打开cmake&#xff0c;选择opencv的源代码目录&#xff0c;创建一个文件夹&#xff0c;作为VS工程文件的生成目录 点击configure构建项目&#xff0c;弹出构建设置…

电子应用设计方案-12:智能窗帘系统方案设计

一、系统概述 本设计方案旨在打造便捷、高效的全自动智能窗帘系统。 二、硬件选择 1. 电机&#xff1a;选用低噪音、扭矩合适的智能电机&#xff0c;根据窗帘尺寸和重量确定电机功率&#xff0c;确保能平稳拉动窗帘。 2. 轨道&#xff1a;选择坚固、顺滑的铝合金轨道&…

MySQL系统优化

文章目录 MySQL系统优化第一章&#xff1a;引言第二章&#xff1a;MySQL服务架构优化1. 读写分离2. 水平分区与垂直分区3. 缓存策略 第三章&#xff1a;MySQL配置优化1. 内存分配优化Buffer Pool 的优化查询缓存与表缓存Key Buffer 2. 连接优化最大连接数会话超时连接池 3. 日志…

LLM - 计算 多模态大语言模型 的参数量(Qwen2-VL、Llama-3.1) 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/143749468 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 影响 (…

光耦选型指南

一、指南说明 针对光偶选型和实际使用过程中出现因光偶特性变化引起的产品失效问题&#xff0c;提供指导。光耦属于易失效器件&#xff0c;选型和使用过程中要特别的小心。目前发现&#xff0c;因光偶的选型&#xff0c;光偶工作电流&#xff0c;CTR参数选型不合适&#xff0c…

git创建远程仓库,以gitee码云为例GitHub同理

git远程Remote服务端仓库构建的视频教程在这 Git建立服务端Remote远程仓库&#xff0c;gitee码云例&#xff0c;Github_哔哩哔哩_bilibili 1、登gitee码云/Github 登录 - Gitee.com https://github.com/ &#xff08;没账号的注册一下就行&#xff09; 点击如下图位置的创…

19.UE5道具掉落

2-21 道具掉落&#xff0c;回血、回蓝、升级提升伤害_哔哩哔哩_bilibili 目录 1.道具的创建&#xff0c;道具功能的实现 2.随机掉落 1.道具的创建&#xff0c;道具功能的实现 新建Actor蓝图&#xff0c;并命名为道具总类&#xff0c;添加一个Niagara粒子组件和一个碰撞箱bo…

排序算法(基础)大全

一、排序算法的作用&#xff1a; 排序算法的主要作用是将一组数据按照特定的顺序进行排列&#xff0c;使得数据更加有序和有组织。 1. 查找效率&#xff1a;通过将数据进行排序&#xff0c;可以提高查找算法的效率。在有序的数据中&#xff0c;可以使用更加高效的查找算法&…

《Java核心技术 卷I》用户界面AWT事件继承层次

AWT事件继承层次 EventObject类有一个子类AWTEvent&#xff0c;它是所有AWT事件类的父类。 Swing组件会生成更多其他事件对象&#xff0c;都直接拓展自EventObject而不是AWTEvent。 AWT将事件分为底层(low-level)事件和语义事件。 语义事件&#xff1a;表示用户的动作事件&…

24-Ingest Pipeline Painless Script

将文档中的tags字段按照逗号&#xff08;,&#xff09;分隔符进行分割。 同时为文档&#xff0c;增加一个字段。blog查看量 DELETE tech_blogs#Blog数据&#xff0c;包含3个字段&#xff0c;tags用逗号间隔 PUT tech_blogs/_doc/1 {"title":"Introducing big …

node.js下载安装步骤整理

>> 进入node.js下载页面下载 | Node.js 中文网 >>点击 全部安装包 >>删除网址node后面部分&#xff0c;只保留如图所示部分&#xff0c;回车 >>点击进入v11.0.0/版本 >>点击下载node-v11.0.0-win-x64.zip(电脑是windows 64位操作系统适用) >…

【HAProxy09】企业级反向代理HAProxy高级功能之压缩功能与后端服务器健康性监测

HAProxy 高级功能 介绍 HAProxy 高级配置及实用案例 压缩功能 对响应给客户端的报文进行压缩&#xff0c;以节省网络带宽&#xff0c;但是会占用部分CPU性能 建议在后端服务器开启压缩功能&#xff0c;而非在HAProxy上开启压缩 注意&#xff1a;默认Ubuntu的包安装nginx开…

右键添加获取可供WSL使用的路径,对windows文件夹也适用,即获取符合Linux规范的路径内容给WSL

文章目录 1. 功能展示1.1. 对 WSL 文件/文件夹/目录空白位置 使用1.2. 对 Windows 文件/文件夹/目录空白位置 使用1.3. Fin 2. 方法3. 文件内容3.1. AddWSLPath.reg3.2. CopyPath.vbs 4. 念念碎 1. 功能展示 1.1. 对 WSL 文件/文件夹/目录空白位置 使用 输出 /etc 1.2. 对 Wi…

数据分析——Python绘制实时的动态折线图

最近在做视觉应用开发&#xff0c;有个需求需要实时获取当前识别到的位姿点位是否有突变&#xff0c;从而确认是否是视觉算法的问题&#xff0c;发现Python的Matplotlib进行绘制比较方便。 目录 1.数据绘制2.绘制实时的动态折线图3.保存实时数据到CSV文件中 import matplotlib.…

Chrome 浏览器开启打印模式

打开开发者工具ctrl shift p输入print 找到 Emulate CSS print media type

小米运动健康与华为运动健康在苹手机ios系统中无法识别蓝牙状态 (如何在ios系统中开启 蓝牙 相册 定位 通知 相机等功能权限,保你有用)

小米运动健康与华为运动健康在苹手机ios系统中无法识别蓝牙状态 &#xff08;解决方案在最下面&#xff0c;参考蓝牙权限设置方式举一反三开启其它模块的权限&#xff09; 最近买了一台小米手表s4&#xff0c;但是苹手机ios系统中的 “小米运动健康” app 始终无法识别我手机…

不用来回切换,一个界面管理多个微信

你是不是也有多个微信号需要管理&#xff1f; 是不是也觉得频繁切换账号很麻烦&#xff1f; 是不是也想提升多账号管理的效率&#xff1f; 在工作中&#xff0c;好的辅助工具&#xff0c;能让我们的效率加倍增长&#xff01; 今天&#xff0c; 就给大家分享一个多微管理工具…

爬虫——数据解析与提取

第二节&#xff1a;数据解析与提取 在网络爬虫开发中&#xff0c;获取网页内容&#xff08;HTML&#xff09;是第一步&#xff0c;但从这些内容中提取有用的数据&#xff0c;才是爬虫的核心部分。HTML文档通常结构复杂且充满冗余信息&#xff0c;因此我们需要使用高效的解析工…

3D Gaussian Splatting 代码层理解之Part1

2023 年初,来自法国蔚蓝海岸大学和 德国马克斯普朗克学会的作者发表了一篇题为“用于实时现场渲染的 3D 高斯泼溅”的论文。该论文提出了实时神经渲染的重大进步,超越了NeRF等以往方法的实用性。高斯泼溅不仅减少了延迟,而且达到或超过了 NeRF 的渲染质量,在神经渲染领域掀…