vue+springboot读取git的markdown文件并展示

前言

最近,在研究一个如何将我们git项目的MARKDOWN文档获取到,并且可以展示到界面通过检索查到,于是经过几天的摸索,成功的研究了出来

本次前端vue使用的是Markdown-it

Markdown-it 是一个用于解析和渲染 Markdown 标记语言的 JavaScript 库。
它采用模块化的设计,提供了灵活的配置选项和丰富的插件系统,使开发者可以根据自己的需要定制 Markdown 的解析和渲染过程。

使用 Markdown-it,你可以将 Markdown 文本解析为 HTML 输出,并且可以根据需要添加功能、扩展语法或修改解析行为

后端springboot使用JGit

JGit 是一个开源的 Java 实现的 Git 客户端库,它允许开发者在 Java 程序中直接操作 Git 仓库。

JGit 提供了一些核心的 API,使开发者可以使用 Java 代码来访问和操作 Git 仓库,例如创建仓库、提交变更、分支管理、标签管理、克隆远程仓库等。它提供了对 Git 分布式版本控制系统的完整支持,能够满足日常的代码版本管理需求。

但是我们这里单纯只是将其获取git的文件进行展示markdown,因此并用不上

准备工作

前端

在前端,
我使用了element-ui前端框架写页面
使用Markdown-it 进行解析markdown
使用axios连接了前后端
因此,需要安装如上依赖,指令如下:

npm i element-ui
npm i markdown-it
npm i axios

后端

因后端为springboot项目,需要安装springboot的依赖,这里不多赘述,主要是需要安装JGit的依赖

<dependency>
    <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
    <version>5.9.0.202009080501-r</version>
</dependency>

效果演示

那么,在说如何做之前,先介绍一下我做出来的效果吧

首先,我建立了一个git仓库 ,专门用于获取到markdown文件

在这里插入图片描述

为了程序能够获取到文件,我在data文件夹下放了四个markdown文件,并且在程序指定只获取data下的markdown文件

  • 无数据时,前端界面显示如下
    在这里插入图片描述

当什么关键字都不输入,检索全部markdown文件

在这里插入图片描述
此时展示文件

此时随便点击列表的一个文件查看

在这里插入图片描述

切换另一个
在这里插入图片描述
在这里插入图片描述

以上,我们能够发现,它能够把我们的markdown的表格,图片以及表情正确的显示出来,并且样式排版也过得去,当然,这个是可以自己调整的

多提一句,我有做一个简单的检索关键字的逻辑,逻辑如下:

  1. 什么关键字不输入的时候,检索指定文件夹下所有markdown
  2. 当输入关键字,检索文件名,如果包含关键字,则把文件路径加入集合列表
  3. 当文件名不包含关键字,判断文件内容是否包含关键字,包含也把对应文件路径加入列表

前端代码逻辑

界面部分

<template>
    <div>
        <el-page-header content="MarkDown展示"/>
        <el-input v-model="searchKey" placeholder="检索问题" style="position: relative;;width: 70%;left: 0%"></el-input>
        <el-button @click="searchProblem" type="primary" plain style="margin: 10px;">检索</el-button>
        <el-card>
            <el-table :data="searchData" style="width: 100%;font-size: 20px; max-height: 500px; overflow-y: auto;background-color: white;">
                    <el-table-column type="index" label="序号">
                    </el-table-column>
                    <el-table-column label="列表项">
                        <template slot-scope="scope">
                        <span>{{ changePathName(scope.row )}}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="操作">
                        <template slot-scope="scope">
                            <div>
                                <el-button type="primary" size="medium" @click="findMarkDown(scope.row)" style="font-size: 24px;">查看</el-button>
                            </div>
                        
                        </template>
                    </el-table-column>
                </el-table>
        </el-card>
        <!-- 展示区 -->
        <el-card style="position: relative;width: 100%;overflow-x: auto;" header="MarkDown处理文档">
            <div style="position: relative;">
              <el-card style="background-color:rgb(255, 253, 245);padding: 32px;" v-if="htmlContent">
                <div v-html="htmlContent" class="v-md-header"></div>
              </el-card>
              <el-card style="background-color:rgb(255, 253, 245);padding: 32px;" v-else>
                <div style="position: absolute;left:46.5%;text-align: center;line-height: 0px;font-weight: bold;">请检索并查看文档</div>
              </el-card>
            </div>
        </el-card>
    </div>
  </template>

JavaScript逻辑

<script>
// 封装的axios调用后端的方法,如需要则按照自己的项目调用修改即可
  import {searchProblem,findMarkDownBypath} from "../ajax/api"
  import MarkdownIt from 'markdown-it';
  export default {
    name: "MarkDown",
    data() {
      return {
        searchKey: "",
        searchData: [], // 检索到的问题列表
        markdownText: '', // 加载好图片的Markdown文本
        markdownRenderer: null, // 解析markdown渲染器定义
        htmlContent: '', // 解析为html
      }
    },
    mounted() {
    this.markdownRenderer = new MarkdownIt();
  },
    methods: {
    // 检索文件
      searchProblem() {
        searchProblem(this.searchKey).then(res => {
            console.log("检索数据:",res);
            this.searchData = res.data.data; // 赋值检索数据,注意这里的res.data.data请根据自己实际回参更改获取参数
            this.markdownText = ""; // 每次检索清空markdown显示文档内容
            this.htmlContent = ""; // 每次检索清空markdown显示文档内容
        })
      },
      // 根据文件路径查找markdown文件
      findMarkDown(path) {
        console.log("path:",path);
        findMarkDownBypath(path).then(res => {
            console.log("markdown内容:",res);
            this.markdownText = res.data.data;
            this.htmlContent = this.markdownRenderer.render(this.markdownText);
            console.log(this.htmlContent);
        })
      },
     // 处理字符串,回传的参数实际为:data/学生成绩系统前端.md,将字符串进行截取
     changePathName(str) {
        if (str) {
            var lastIndex = str.lastIndexOf('/');
            var result = str.substring(lastIndex + 1);
            return result.replace('.md','');
        }
        return str;
     }
    }
  }
  </script>

在以上,后端传递的路径实际为:

[
    "data/README.en.md",
    "data/README.md",
    "data/学生成绩系统前端.md",
    "data/网上购药商城.md"
]

因此为了美观和直观展示,我是有做字符处理的,详情参考如何代码

此外,我后端获取到的markdown的内容实际数据为:

# search_markdown_data

#### Description
用于检索markdown的数据来源

#### Software Architecture
Software architecture description

#### Installation

1.  xxxx
2.  xxxx
3.  xxxx

#### Instructions

1.  xxxx
2.  xxxx
3.  xxxx

#### Contribution

1.  Fork the repository
2.  Create Feat_xxx branch
3.  Commit your code
4.  Create Pull Request


#### Gitee Feature

1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4.  The most valuable open source project [GVP](https://gitee.com/gvp)
5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

我的代码中,使用markdown-it创建渲染器,将如上数据转换为:

<h1>search_markdown_data</h1>
<h4>Description</h4>
<p>用于检索markdown的数据来源</p>
<h4>Software Architecture</h4>
<p>Software architecture description</p>
<h4>Installation</h4>
<ol>
<li>xxxx</li>
<li>xxxx</li>
<li>xxxx</li>
</ol>
<h4>Instructions</h4>
<ol>
<li>xxxx</li>
<li>xxxx</li>
<li>xxxx</li>
</ol>
<h4>Contribution</h4>
<ol>
<li>Fork the repository</li>
<li>Create Feat_xxx branch</li>
<li>Commit your code</li>
<li>Create Pull Request</li>
</ol>
<h4>Gitee Feature</h4>
<ol>
<li>You can use Readme_XXX.md to support different languages, such as Readme_en.md, Readme_zh.md</li>
<li>Gitee blog <a href="https://blog.gitee.com">blog.gitee.com</a></li>
<li>Explore open source project <a href="https://gitee.com/explore">https://gitee.com/explore</a></li>
<li>The most valuable open source project <a href="https://gitee.com/gvp">GVP</a></li>
<li>The manual of Gitee <a href="https://gitee.com/help">https://gitee.com/help</a></li>
<li>The most popular members  <a href="https://gitee.com/gitee-stars/">https://gitee.com/gitee-stars/</a></li>
</ol>

实际为html的数据,因此,我们就可以在界面使用vue的v-html展示markdown的内容

css样式

以上我们知道它会将数据转为html的数据,因此,就可以使用css样式调整,以下为我的css样式,供参考:

h1 {
    color: #ff0000;
  }
  
  p {
    font-size: 16px;
    line-height: 1.5;
  }
  
  .v-md-header {
    text-align: left !important;
  }

  table {
    border-collapse: collapse;
    width: 100%;
  }
  
  th, td {
    border: 1px solid black;
    padding: 8px;
  }
  
  th {
    background-color: #f2f2f2; /* 设置表头的背景颜色 */
  }
  
  tr:nth-child(even) {
    background-color: #dddddd; /* 设置偶数行的背景颜色 */
  }
  
  tr:hover {
    background-color: #f5f5f5; /* 设置鼠标悬停时的背景颜色 */
  }
  h1,h2,h3,h4,h5{
    border-bottom: 1px #d8d6d6 solid;
  }
  img{
    width: 80%;
  }

后端代码逻辑

先说下我的查询的方法,JGIT我尝试了很久,都只能通过先克隆到本地,再读取的方式,于是放弃了研究如何使用JGIT在线读取文件的方式,也许后面我可能研究的出来。

同样,为了保证每次都是最新的文档,我采用了判断是否已经克隆下来了,如果克隆了则更新代码,没有克隆则克隆,来保证每次都是最新代码

在正式执行前,我们需要先定义好需要的参数

// 解析markdown图片正则
    private static final String MARKDOWN_IMAGE_PATTERN = "(!\\[[^\\]]*\\])\\(([^\\)]+)\\)";
// 需要克隆git到本机的路径
    private final String LOCAL_PATH = "E:\\git\\markdown";
// 需要获取markdown的git链接
    private final String GIT_PATH = "https://gitee.com/spring-in-huangxian-county/search_markdown_data.git";
// 需要获取的git分支
    private final String GIT_BRANCH = "master";
// 需要抓取的Git内指定文件夹的markdown
    private final String MARK_DOWN_PATH = "data";
// 当前后端项目的位置,该目的是为了能够找到正确的文件路径
    private final String PROJECT_PATH = "F:\\gitee\\search_markdown_end";

查询

controller层

    @GetMapping("/searchProblem")
    public ResultVO<List<String>> searchMarkdown(@RequestParam("searchKey") String searchKey)
        throws Exception {
        // 获取Git仓库中的Markdown文档列表
        try {
            List<String> markdownFiles = new MarkDownService().getGitDataFilePath();
            List<String> results = new ArrayList<>();
            if (StringUtils.isEmpty(searchKey)) {
                results.addAll(markdownFiles);
            } else {
                for (String path:markdownFiles) {
                    // 如果标题包含检索关键字加入列表
                    if (path.contains(searchKey)) {
                        results.add(path);
                    } else {
                        // 判断具体内容是否包含关键字,是则加入列表
                        if (new MarkDownService().isContainSearchKeyForContent(searchKey,path)) {
                            results.add(path);
                        }
                    }
                }
            }
            return new ResultVO<>(0,"OK",results);
        }catch (Exception e) {
            return new ResultVO<>(1,e.getMessage());
        }
    }

ResultVO为封装的响应体,如有兴趣可参考我之前文章
MarkDownService为service层文件名

service层

克隆和拉取git的方式读取文件,获取文件路径

    // 克隆和拉取git的方式读取文件
    public List<String> getGitDataFilePath() throws Exception {
        File localPath = new File(LOCAL_PATH);
        String remoteUrl = GIT_PATH;
        String branchName =GIT_BRANCH; // 或者其他分支名称
        String folderPath = MARK_DOWN_PATH; // data文件夹的路径
        List<String> markDownFilePathList = new ArrayList<>();

        Repository repository;
        if (localPath.exists()) {
            repository = openLocalRepository(localPath);
            pullLatestChanges(repository);
        } else {
            repository = cloneRepository(localPath, remoteUrl);
        }

        try (Git git = new Git(repository)) {

            Iterable<RevCommit> commits = git.log().add(repository.resolve(branchName)).call();
            RevCommit commit = commits.iterator().next();
            try (RevWalk revWalk = new RevWalk(repository)) {
                RevTree tree = revWalk.parseTree(commit.getTree());
                try (TreeWalk treeWalk = new TreeWalk(repository)) {
                    treeWalk.addTree(tree);
                    treeWalk.setRecursive(true);
                    while (treeWalk.next()) {
                        if (treeWalk.getPathString().startsWith(folderPath) && treeWalk.getPathString().endsWith(".md")) {
                            System.out.println("Found markdown file: " + treeWalk.getPathString());
                            // 这里可以根据需要进行具体的处理,比如读取文件内容等
                            markDownFilePathList.add(treeWalk.getPathString());
                        }
                    }
                }
            }
        } catch (IOException | GitAPIException e) {
            e.printStackTrace();
        }
        return markDownFilePathList;
    }

打开本地git

    // 打开本地git项目
    private Repository openLocalRepository(File localPath) throws IOException {
        System.out.println("Opening existing repository...");
        Git git = Git.open(localPath);
        return git.getRepository();
    }

克隆代码

    // 克隆git
    private Repository cloneRepository(File localPath, String remoteUrl) throws GitAPIException {
        System.out.println("Cloning repository...");
        Git git = Git.cloneRepository()
            .setURI(remoteUrl)
            .setDirectory(localPath)
            .call();
        return git.getRepository();
    }

拉取最新代码

    //拉取git最新代码
    private void pullLatestChanges(Repository repository) throws GitAPIException {
        System.out.println("Pulling latest changes...");
        Git git = new Git(repository);
        PullCommand pull = git.pull().setTimeout(30);
        pull.call();
    }

检查文件内容是否包含关键字

    /**
     * @param searchKey 检索关键字
     * @param path markdown文本路径
     * @desc 通过关键字和路径找到指定markdown文件是否内容包含关键字
     * */
    public Boolean isContainSearchKeyForContent(String searchKey,String path) {
        Boolean containFlag = false;
        String content ="";
        try {
            content =  findMarkDownBypathNoWithImage(path);
        }catch (Exception e) {
            System.out.println("获取markdown文本失败:"+e.getMessage());
        }
        if (content.contains(searchKey)) {
            containFlag = true;
        }
        return containFlag;
    }

要判断文件内容是否包含关键字,不需要将文件图片进行解析,直接获取文件内容

    public String findMarkDownBypathNoWithImage(String filePath) throws Exception{
        String localPath = LOCAL_PATH;
        String markDownContent = "";
        if (filePath.endsWith(".md")) {
            File markdownFile = new File(localPath, filePath);
            try (Scanner scanner = new Scanner(markdownFile)) {
                StringBuilder contentBuilder = new StringBuilder();
                while (scanner.hasNextLine()) {
                    contentBuilder.append(scanner.nextLine()).append("\n");
                }
                markDownContent = contentBuilder.toString();
                System.out.println("Markdown file content:\n" + markDownContent);
            } catch (IOException e) {
                throw new Exception(e.getMessage());
            }
        }
        return markDownContent;
    }

根据路径获取文件

以下为会解析图片的方式进行获取文件

controller层

    @GetMapping("findMarkDownBypath")
    public ResultVO<String> findMarkDownBypath(@RequestParam("path")String path) throws Exception {
        try {
            return new ResultVO<>(new MarkDownService().findMarkDownBypathWithImage(path));
        }catch (Exception e) {
            return new ResultVO<>(1,e.getMessage());
        }
    }

service层

    public String findMarkDownBypathWithImage(String filePath) throws Exception{
        String localPath = LOCAL_PATH;
        String markDownContent = "";
        if (filePath.endsWith(".md")) {
            File markdownFile = new File(localPath, filePath);
            try (Scanner scanner = new Scanner(markdownFile)) {
                StringBuilder contentBuilder = new StringBuilder();
                while (scanner.hasNextLine()) {
                    contentBuilder.append(scanner.nextLine()).append("\n");
                }
                String markdownContent = contentBuilder.toString();
                markDownContent = loadImages(markdownContent,filePath);
                // 在这里得到了具体的markdown文件内容
                System.out.println("Markdown file content:\n" + markdownContent);
            } catch (IOException e) {
                throw new Exception(e.getMessage());
            }
        }
        return markDownContent;
    }

解析图片

    public  String loadImages(String markdownContent, String markdownFilePath) {
        Pattern pattern = Pattern.compile(MARKDOWN_IMAGE_PATTERN);
        Matcher matcher = pattern.matcher(markdownContent);
        String localPath = LOCAL_PATH;
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String originalImageTag = matcher.group(0);
            String altText = matcher.group(1);
            String imagePath = matcher.group(2);
            try {
                String absoluteImagePath = getAbsoluteImagePath(imagePath, markdownFilePath);
                absoluteImagePath = absoluteImagePath.replace(PROJECT_PATH,localPath);
                String imageData = loadImage(absoluteImagePath);
                String transformedImageTag = "![Image](" + imageData + ")";
                matcher.appendReplacement(sb, transformedImageTag);
            } catch (IOException e) {
                // 图像加载出错,可以根据实际需求进行处理
                e.printStackTrace();
            }
        }
        matcher.appendTail(sb);

        return sb.toString();
    }

    public static String loadImage(String imagePath) throws IOException {
        File imageFile = new File(imagePath);

        // 读取图像文件的字节数组
        byte[] imageData = FileUtils.readFileToByteArray(imageFile);

        // 将字节数组转换为Base64编码字符串
        String base64ImageData = java.util.Base64.getEncoder().encodeToString(imageData);

        return "data:image/png;base64," + base64ImageData;
    }
    public static String getAbsoluteImagePath(String imagePath, String markdownFilePath) {
        File markdownFile = new File(markdownFilePath);
        String markdownDirectory = markdownFile.getParent();
        String absoluteImagePath = new File(markdownDirectory, imagePath).getAbsolutePath();
        return absoluteImagePath;
    }

依赖

为了防止出现依赖可能缺失的情况,可参考我的项目的maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.hxc.common</groupId>
    <artifactId>CommonBack</artifactId>
    <version>1.0</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <log4j2.version>2.17.0</log4j2.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.8</version>
        </dependency>
<!--        swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-annotations</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>
        <!--        swagger的ui-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.15.0</version>
        </dependency>
        <!--    文件处理-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!--   POI excel处理依赖     -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <!--   工具类     -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.8</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.eclipse.jgit</groupId>-->
<!--            <artifactId>org.eclipse.jgit</artifactId>-->
<!--            <version>4.4.1.201607150455-r</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>5.9.0.202009080501-r</version>
        </dependency>
    </dependencies>


    <build>
        <finalName>common_end</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>com.hxc.common.MarkDownApplication</mainClass>
                            <classpathPrefix>libs/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!--跳过junit-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

git

以下为我实现读取git的markdown的项目,可供参考

前端

后端

结语

以上为我实现vue+springboot读取git的markdown文件并展示的过程

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

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

相关文章

Vue 2.0源码分析-Virtual DOM

Virtual DOM 这个概念相信大部分人都不会陌生&#xff0c;它产生的前提是浏览器中的 DOM 是很“昂贵"的&#xff0c;为了更直观的感受&#xff0c;我们可以简单的把一个简单的 div 元素的属性都打印出来&#xff0c;如图所示&#xff1a; 可以看到&#xff0c;真正的 DOM …

C语言-指针讲解(3)

文章目录 1.字符指针变量1.1 字符指针变量类型是什么1.2字符指针变量的两种使用方法&#xff1a;1.3字符指针笔试题讲解1.3.1 代码解剖 2.数组指针变量2.1 什么是数组指针2.2 数组指针变量是什么&#xff1f;2.2.3 数组指针变量的举例 2.3数组指针和指针数组的区别是什么&#…

javascript判断是否是json格式

文章目录 一、问题二、解决三、总结3.1、定义 一、问题 工作中有用到JSON.parse这个来解析JSON字符串&#xff0c;这个时候突然有一次遇到JSON字符串是长串数字或数字字符串&#xff0c;主要是自己也没兼容好&#xff0c;就导致了一长串数字JSON.parse之后变成了e24等数字。主…

中低压MOSFET 2N7002W 60V 300mA 双N通道 SOT-323封装

2N7002W小电流双N通道MOSFET&#xff0c;电压60V电流300mA&#xff0c;采用SOT-323封装形式。超高密度电池设计&#xff0c;适用于极低的ros (on)&#xff0c;具有导通电阻和最大直流电流能力&#xff0c;ESD保护。可应用于笔记本中的电源管理&#xff0c;电池供电系统等产品应…

Selenium实现多页面切换

当使用 Selenium 进行自动化测试或爬取数据时&#xff0c;有时需要处理多个页面之间的切换。以下是一些可能需要多页面切换的情况&#xff1a; 1、打开新窗口/页面&#xff1a; 在当前页面上点击链接、按钮或执行某些操作时&#xff0c;可能会打开一个新的窗口或页面。此时&a…

Appium+Python+pytest自动化测试框架的实战

本文主要介绍了AppiumPythonpytest自动化测试框架的实战&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下 先简单介绍一下目录&#xff0c;再贴一些代码&#xff0c;代码里有注释 Basic目录下写的是一些公…

【C++】泛型编程 ⑮ ( 类模板示例 - 数组类模板 | 自定义类中持有指针成员变量 )

文章目录 一、支持 数组类模板 存储的 自定义类1、可拷贝和可打印的自定义类2、改进方向3、改进方向 - 构造函数4、改进方向 - 析构函数5、改进方向 - 重载左移运算符6、改进方向 - 重载拷贝构造函数 和 等号运算符 二、代码示例1、Array.h 头文件2、Array.cpp 代码文件3、Test…

io.lettuce.core.RedisCommandExecutionException

io.lettuce.core.RedisCommandExecutionException: ERR invalid password ERR invalid password-CSDN博客 io.lettuce.core.RedisCommandExecutionException /** Copyright 2011-2022 the original author or authors.** Licensed under the Apache License, Version 2.0 (the…

RabbitMQ基础教程

1.什么是消息队列 消息队列&#xff08;Message Queue&#xff09;&#xff0c;我们一般简称为MQ。消息队列中间件是分布式系统中重要的组件&#xff0c;具有异步性、松耦合、分布式、可靠性等特点。用于实现高性能、高可用、可伸缩和最终一致性架构。是大型分布式系统不可缺少…

JVM类加载的过程和JVM垃圾回收机制

文章目录 一、JVM类加载的过程1.1类加载的基本流程1.1.1加载1.1.2验证1.1.3准备1.1.4解析1.1.5初始化 1.2双亲委派模型 二、JVM垃圾回收机制2.1找到垃圾2.1.1引用计数(比如Python&#xff0c;PHP中用到)2.1.2可达性分析(比如Java中用到) 2.2释放垃圾2.2.1标记清除2.2.2复制算法…

RAM模型从数据准备到pretrain、finetune与推理全过程详细说明

提示&#xff1a;RAM模型&#xff1a;环境安装、数据准备与说明、模型推理、模型finetune、模型pretrain等 文章目录 前言一、环境安装二、数据准备与解读1.数据下载2.数据标签内容解读3.标签map内容解读 三、finetune训练1.微调训练命令2.load载入参数问题3.权重载入4.数据加载…

大数据技术之数据安全与网络安全——CMS靶场实训

大数据技术之数据安全与网络安全——CMS靶场实训 在当今数字化时代&#xff0c;大数据技术的迅猛发展带来了前所未有的数据增长&#xff0c;同时也催生了对数据安全和网络安全的更为迫切的需求。本篇博客将聚焦于大数据技术背景下的数据安全与网络安全&#xff0c;并通过CMS&a…

4.操作系统常见面试题(2)

3.4 虚拟内存 直接使⽤物理内存会产⽣⼀些问题 1. 内存空间利⽤率的问题&#xff1a;各个进程对内存的使⽤会导致内存碎⽚化&#xff0c;当要⽤ malloc 分配⼀块很⼤的内存空间时&#xff0c;可能会出现虽然有⾜够多的空闲物理内存&#xff0c;却没有⾜够⼤的连续空闲内存这种…

点大商城V2.5.3分包小程序端+小程序上传提示限制分包制作教程

这几天很多播播资源会员反馈点大商城V2.5.3小程序端上传时提示大小超限&#xff0c;官方默认单个包都不能超过2M&#xff0c;总分包不能超20M。如下图提示超了93KB&#xff0c;如果出现超的不多情况下可采用手动删除一些images目录下不使用的图片&#xff0c;只要删除超过100KB…

82基于matlab GUI的图像处理

基于matlab GUI的图像处理&#xff0c;功能包括图像一般处理&#xff08;灰度图像、二值图&#xff09;&#xff1b;图像几何变换&#xff08;旋转可输入旋转角度、平移、镜像&#xff09;、图像边缘检测&#xff08;拉普拉斯算子、sobel算子、wallis算子、roberts算子&#xf…

unordered_map 与 unordered_set 的模拟实现

unordered_map 与 unordred_set 的模拟实现与 map 与 set 的模拟实现差不多。map 与 set 的模拟实现中&#xff0c;底层的数据结构是红黑树。unordered_map 与 unordered_set 的底层数据结构是哈希表。因此&#xff0c;在模拟实现 unordered_map 与 unordred_set 之前你必须确保…

nodejs微信小程序+python+PHP-青云商场管理系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder

密码&#xff0c;加密&#xff0c;解密 spring-security-crypto-5.7.3.jar /** Copyright 2002-2011 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with t…

HTML新特性【缩放图像、图像切片、平移、旋转、缩放、变形、裁切路径、时钟、运动的小球】(二)-全面详解(学习总结---从入门到深化)

目录 绘制图像_缩放图像 绘制图像_图像切片 Canvas状态的保存和恢复 图形变形_平移 图形变形_旋转 图形变形_缩放 图形变形_变形 裁切路径 动画_时钟 动画_运动的小球 引入外部SVG 绘制图像_缩放图像 ctx.drawImage(img, x, y, width, height) img &#xf…

开源与闭源

我的观点&#xff1a; 开源与闭源软件都有各自的优势和劣势&#xff0c;没有绝对的对错之分。.. 一、开源和闭源的优劣势比较 开源的好处与劣处 优势&#xff1a; 创新与合作&#xff1a;开源软件能够吸引更多的开发者参与到项目中来&#xff0c;促进创新和合作。开放的源代码…
最新文章