自定义 Gradle 插件进行统一的静态代码分析

静态代码分析是一项了不起的技术, 它能让代码库更易于维护. 但是, 如果你在不同的版本库中拥有多个服务(可能由不同的团队开发), 如何才能让每个人都遵循既定的代码风格呢? 一个好办法是将所有规则封装在一个插件中, 该插件会在每个项目构建时自动执行所需的验证.

因此, 在本文中我将向你展示:

  1. 如何创建带有自定义 PMD 和 Checkstyle 规则的 Gradle 插件.
  2. 如何发布到 [plugins.gradle.org]
  3. 如何使用 GitHub Actions 自动执行发布流程.

你可以查看[本仓库]中的代码示例.

PMD, Checkstyle 和多仓库的难点

[PMD] 是静态分析工具, 可在每次项目构建时检查代码. 通过 [Gradle]“https://medium.com/javarevisited/why-java-developer-should-learn-maven-or-gradle-aefe7ea20a83”), 可以轻松应用它们.

plugins {
    id 'java'
    id 'pmd'
    id 'checkstyle'
}

现在, 你可以按照自己的方式调整每个插件.

checkstyle {
    toolVersion = '10.5.0'
    ignoreFailures = false
    maxWarnings = 0
    configFile = file(pathToCheckstyleConfig)
}

pmd {
    consoleOutput = true
    toolVersion = '6.52.0'
    ignoreFailures = false
    ruleSetFiles = file(pathToPmdConfig)
}

如果你的整个项目(甚至是公司)都是[单仓库, 那么这样的设置绝对没问题. 你只需将这些配置放入根build.gradle文件中, 就能将这些插件应用到现有的每个模块中. 但如果你选择的是[多仓库]呢?

如果你想在公司内开发人员正在开发的所有项目(以及程序员将来创建的所有项目)中共享相同的代码风格, 该怎么办?

那么, 你可以告诉他们只需[复制并粘贴]插件的配置即可. 无论如何, 这种方法容易出错. 总有人可能会配置错误.

事实上, 我们需要在每个可行的项目中以某种方式重复使用已定义的代码样式配置. 答案很简单. 我们需要一个定制的 Gradle 插件来封装 PMD 和 Checkstyle 规则.

自定义 Gradle 插件

构建配置

请看下面的 build.gradle 声明. 这是 Gradle 插件项目的基本设置.

plugins {
    id 'java-gradle-plugin'
    id 'com.gradle.plugin-publish' version '1.1.0'
}

group = 'io.github.simonharmonicminor.code.style'
sourceCompatibility = '8'

repositories {
    mavenCentral()
}

ext {
    set('lombokVersion', '1.18.24')
}

dependencies {
    compileOnly "org.projectlombok:lombok:${lombokVersion}"
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
}

gradlePlugin {
    website = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'
    vcsUrl = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'
    plugins {
        gradleCodeStylePluginExample {
            id = 'io.github.simonharmonicminor.code.style'
            displayName = 'Gradle Plugin Code Style Example'
            description = 'Predefined Checkstyle and PMD rules'
            implementationClass = 'io.github.simonharmonicminor.code.style.CodingRulesGradlePluginPlugin'
            tags.set(['codestyle', 'checkstyle', 'pmd'])
        }
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

现在让我们从 plugins 块开始, 一步步解构配置. 请看下面的代码片段.

plugins {
    id 'java-gradle-plugin'
    id 'com.gradle.plugin-publish' version '1.1.0'
}

java-gradle-plugin命令会启用常规 Gradle 插件项目的任务. com.gradle.plugin-publish命令允许打包插件并发布到plugins.gradle.org.

我最近正在向你展示整个发布过程.

然后是基本的项目配置.

group = 'io.github.simonharmonicminor.code.style'
sourceCompatibility = '8'

repositories {
    mavenCentral()
}

group定义了groupId, 以符合[Apache Maven 命名规范] sourceCompatibility是目标 Java 二进制文件的版本. 虽然 Java 8 现在已经过时, 但我还是建议你使用公司开发人员使用的最早 JDK 版本构建 Gradle 插件. 否则, 你会阻碍他们遵循你的代码风格指南.

然后是 dependencies 范围.

ext {
    set('lombokVersion', '1.18.24')
}

dependencies {
    compileOnly "org.projectlombok:lombok:${lombokVersion}"
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
}

这里没什么特别的. 接下来是发布配置.

website = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'
    vcsUrl = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'
    plugins {
        gradleCodeStylePluginExample {
            id = 'io.github.simonharmonicminor.code.style'
            displayName = 'Gradle Plugin Code Style Example'
            description = 'Predefined Checkstyle and PMD rules'
            implementationClass = 'io.github.simonharmonicminor.code.style.CodingRulesGradlePluginPlugin'
            tags.set(['codestyle', 'checkstyle', 'pmd'])
        }
    }
}

websitevcsUrl应指向包含插件源代码的公共 Git 仓库. plugins块定义了项目中Plugin接口的每个实现. 最后,tags只是在注册表中搜索插件的hash标签.

当你将 Gradle 插件发布到 [plugins.gradle.org] 时, 包的名称至关重要. 你的插件代码应该可以在 GitHub 上找到. 如果不是开源的, 发布时可能会遇到问题. 那么, 你可以将软件包名称声明为io.github.your_github_login.any.package.you.like.

但是, 如果你想使用其他名称, 如com.mycompany.my.plugin, 请确保域名mycompany.com. 否则, Gradle 工程师可能会拒绝发布.

注意 Gradle 禁止plugingradle作为标签值. 在gradle publishPlugins任务执行过程中, 这样的构建会失败.

tasks.named('test') {
    useJUnitPlatform()
}

插件代码

我想向大家展示整个插件的代码. 然后我将向你解释每个细节. 请看下面的代码片段.

public class CodingRulesGradlePluginPlugin implements Plugin<Project> {

  @Override
  public void apply(Project project) {
    project.getPluginManager().apply("checkstyle");
    project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {
      checkstyleExtension.setToolVersion("10.5.0");
      checkstyleExtension.setIgnoreFailures(false);
      checkstyleExtension.setMaxWarnings(0);
      checkstyleExtension.setConfigFile(
          FileUtil.copyContentToTempFile("style/checkstyle.xml", ".checkstyle.xml")
      );
    });

    project.getPluginManager().apply("pmd");
    project.getExtensions().configure(PmdExtension.class, pmdExtension -> {
      pmdExtension.setConsoleOutput(true);
      pmdExtension.setToolVersion("6.52.0");
      pmdExtension.setIgnoreFailures(false);
      pmdExtension.setRuleSets(emptyList());
      pmdExtension.setRuleSetFiles(project.files(
          FileUtil.copyContentToTempFile("style/pmd.xml", ".pmd.xml")
      ));
    });

    final SortedSet<String> checkstyleTaskNames = project.getTasks()
        .withType(Checkstyle.class)
        .getNames();

    final SortedSet<String> pmdTaskNames = project.getTasks()
        .withType(Pmd.class)
        .getNames();

    project.task(
        "runStaticAnalysis",
        task -> task.setDependsOn(
            Stream.concat(
                checkstyleTaskNames.stream(),
                pmdTaskNames.stream()
            ).collect(Collectors.toList())
        )
    );
  }
}

最明显也是最重要的细节是, 每个插件任务都必须实现 Gradle Plugin 接口.

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class CodingRulesGradlePluginPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) { ... }
}

然后我在配置 Checkstyle 任务. 我只需应用 checkstyle 插件, 获取 CheckstyleConfiguration 并覆盖我想要的属性. 请看下面的代码块.

project.getPluginManager().apply("checkstyle");
project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {
  checkstyleExtension.setToolVersion("10.5.0");
  checkstyleExtension.setIgnoreFailures(false);
  checkstyleExtension.setMaxWarnings(0);
  checkstyleExtension.setConfigFile(
      FileUtil.copyContentToTempFile("style/checkstyle.xml", ".checkstyle.xml")
  );
});

FileUtil.copyContentToTempFile函数需要解释一下. 我把 Checkstyle 配置放到了 src/main/resources/style/checkstyle.xml 文件中. 但是, 如果你直接指向它, 那么人们在他们的项目中应用你的 Gradle 时就会得到奇怪的错误信息. 有一些变通方法, 但最简单的方法是将内容复制到临时文件中.

看看下面的 PMD 配置. 与 Checkstyle 类似.

project.getPluginManager().apply("pmd");
project.getExtensions().configure(PmdExtension.class, pmdExtension -> {
  pmdExtension.setConsoleOutput(true);
  pmdExtension.setToolVersion("6.52.0");
  pmdExtension.setIgnoreFailures(false);
  pmdExtension.setRuleSets(emptyList());
  pmdExtension.setRuleSetFiles(project.files(
      FileUtil.copyContentToTempFile("style/pmd.xml", ".pmd.xml")
  ));
});

现在我们准备就绪. 我们可以将其应用到实际项目中. 虽然也有一点改进. 请看下面的代码片段.

final SortedSet<String> checkstyleTaskNames = project.getTasks()
    .withType(Checkstyle.class)
    .getNames();

final SortedSet<String> pmdTaskNames = project.getTasks()
    .withType(Pmd.class)
    .getNames();

project.task(
    "runStaticAnalysis",
    task -> task.setDependsOn(
        Stream.concat(
            checkstyleTaskNames.stream(),
            pmdTaskNames.stream()
        ).collect(Collectors.toList())
    )
);

runStaticAnalysis任务会触发所有 Checkstyle 和 PMD 任务按顺序运行. 当你想在创建拉取请求前验证整个项目时, 它就派上用场了. 如果直接在build.gradle中添加runStaticAnalysis任务, 它将看起来像这样:

task runStaticAnalysis {
    dependsOn checkstyleMain, checkstyleTest, pmdMain, pmdTest
}

同样, 我将一次性展示整段代码, 然后指出重要的细节.

class CodingRulesGradlePluginPluginTest {

  @Test
  void shouldApplyPluginSuccessfully() {
    final Project project = ProjectBuilder.builder().build();
    project.getPluginManager().apply("java");

    assertDoesNotThrow(
        () -> new CodingRulesGradlePluginPlugin().apply(project)
    );

    final Task task = project.getTasks().getByName("runStaticAnalysis");
    assertNotNull(task, "runStaticAnalysis task should be registered");
    final Set<String> codeStyleTasks =
        Stream.of("checkstyleMain", "checkstyleTest", "pmdTest", "pmdMain").collect(toSet());
    assertTrue(
        task.getDependsOn().containsAll(codeStyleTasks),
        format(
            "Task runStaticAnalysis should contain '%s' tasks, but actually: %s",
            codeStyleTasks,
            task.getDependsOn()
        )
    );
  }
}

首先是 Gradle 项目实例化测试. 请看下面的代码片段.

import org.gradle.testfixtures.ProjectBuilder;
import org.gradle.api.Project;

final Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply("java");

Gradle 为单元测试提供了一些固定装置. ProjectBuilder创建了一个与 API 兼容的Project接口实现. 因此, 你可以放心地将它传递给 YourPluginClass.apply 方法.

在调用业务逻辑之前, 我们还要手动应用 java 插件. 我们的插件针对 Java 应用程序. 因此, 传递 Java 配置的 Project 实现是很自然的.

然后, 我们只需调用自定义插件方法并传递配置的 Project 实现.

assertDoesNotThrow(
    () -> new CodingRulesGradlePluginPlugin().apply(project)
);

之后是断言. 我们需要确保 runStaticAnalysis 任务注册成功.

final Task task = project.getTasks().getByName("runStaticAnalysis");
assertNotNull(task, "runStaticAnalysis task should be registered");

如果存在, 我们将根据现有的 Checkstyle 和 PMD 任务验证该任务.

final Set<String> codeStyleTasks =
    Stream.of("checkstyleMain", "checkstyleTest", "pmdTest", "pmdMain").collect(toSet());
assertTrue(
    task.getDependsOn().containsAll(codeStyleTasks),
    format(
        "Task runStaticAnalysis should contain '%s' tasks, but actually: %s",
        codeStyleTasks,
        task.getDependsOn()
    )
);

这是我们在将插件推送到 [plugins.gradle.org/]之前应该测试的最基本情况.

使用 GitHub Actions 发布插件

当你在 [plugins.gradle.org/]上注册一个新账户时, 进入你的页面并打开 API Keys 选项卡. 你应该生成新的密钥. 会有两个.

gradle.publish.key=...
gradle.publish.secret=...

然后, 打开版本库的Settings, 转到Secrets and Variables -> Actions项. 你必须把获得的密钥存储为版本库秘密.

最后是 GitHub Actions 的构建配置.

我把自己的文件放在了.github/workflow/build.yml.

请看下面的整个设置. 然后, 我将告诉你特定区块的含义.

name: Java CI with Gradle

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 8
        uses: actions/setup-java@v3
        with:
          java-version: '8'
          distribution: 'temurin'
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: build
  publish:
    needs:
      - build
    if: github.ref == 'refs/heads/master'
    runs-on: ubuntu-latest
    steps:
      - name: Auto Increment Semver Action
        uses: MCKanpolat/auto-semver-action@1.0.5
        id: versioning
        with:
          releaseType: minor
          incrementPerCommit: false
          github_token: ${{ secrets.GITHUB_TOKEN }}
      - name: Next Release Number
        run: echo ${{ steps.versioning.outputs.version }}
      - uses: actions/checkout@v3
      - name: Set up JDK 8
        uses: actions/setup-java@v3
        with:
          java-version: '8'
          distribution: 'temurin'
      - name: Publish Gradle plugin
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: build publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pversion=${{ steps.versioning.outputs.version }}

文件顶部的声明说明了管道触发的规则.

name: Java CI with Gradle

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

管道会在每次向master分支提出拉取请求和每次构建master分支时运行.

构建由两项工作组成. 第一个工作很简单. 它只是运行 Gradle build 任务. 请看下面的配置.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 8
        uses: actions/setup-java@v3
        with:
          java-version: '8'
          distribution: 'temurin'
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: build

然后是发布的任务本身. 它也包含几个步骤. 第一个步骤是自动增加版本并保存到环境变量中. 这很方便, 因为 Gradle 插件不能以快照的形式发布.

publish:
    needs:
      - build
    if: github.ref == 'refs/heads/master'
    runs-on: ubuntu-latest
    steps:
      - name: Auto Increment Semver Action
        uses: MCKanpolat/auto-semver-action@1.0.5
        id: versioning
        with:
          releaseType: minor
          incrementPerCommit: false
          github_token: ${{ secrets.GITHUB_TOKEN }}
      - name: Next Release Number
        run: echo ${{ steps.versioning.outputs.version }}

if: github.ref == 'refs/heads/master'告知 GitHub Actions 只有在master分支在构建的时候才能运行管道线中的任务. 因此, 在拉取请求构建过程中, GitHub Actions 不会触发publish进程.

现在, 我们需要发布打包的插件本身. 请看下面的代码片段.

- uses: actions/checkout@v3
- name: Set up JDK 8
  uses: actions/setup-java@v3
  with:
      java-version: '8'
      distribution: 'temurin'
- name: Publish Gradle plugin
  uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
  with:
      arguments: build publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pversion=${{ steps.versioning.outputs.version }}

如你所见, GitHub Actions 通过secrets传递了gradle.publish.keygradle.publish.secret属性, 并将新项目版本作为环境变量.

总结一下

正如你所看到的, 在 Gradle 中自动检查代码样式规则并不复杂. 顺便说一句, 你可以通过包含 id 'io.github.simonharmonicminor.code.style' version '0.1.0' 来应用项目中描述的插件.

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

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

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

相关文章

2024年第十届中西部外语翻译大赛(1)

2024年第十届中西部外语翻译大赛 竞赛信息 “由中西部翻译协会共同体指导发起&#xff0c;各省市译协共建学术指导委员会&#xff0c;2024年第十届中西部外语翻译大赛由中西部翻译协会共同体秘书处&#xff08;武汉公仪网络科技有限公司&#xff09;承办。” - 获奖证书样图 -…

怎么3d立面有些模型不能删除是什么原因怎么解决?---模大狮模型网

在进行3D建模和设计过程中&#xff0c;有时会遇到一些模型无法删除的情况&#xff0c;这可能会导致设计流程受阻&#xff0c;影响工作效率。本文将介绍在3D立面中遇到无法删除模型的原因以及解决方法&#xff0c;帮助您顺利解决这一问题&#xff0c;提高设计效率。 一、模型未正…

能自动化视频剪辑的开源工具来了 剪辑师、自媒体作者狂喜

项目简介 Funclip 是阿里巴巴通义实验室开源的一款视频剪辑工具&#xff0c;专门用于精准、便捷的视频切片。 它能够自动识别视频中的中文语音并允许用户根据语音内容来裁剪视频。该工具使用了阿里巴巴语音识别模型FunASR Paraformer-Large确保了剪辑的精准性。 你可以根据识…

七人拼团策略:深度解析奖励体系与互助合作机制

在七人拼团策略中&#xff0c;其精心设计的奖励体系是吸引众多参与者的核心动力。接下来&#xff0c;我们将详细解析这一模式中三种关键的奖励类型&#xff1a;直推奖、滑落奖和团队奖&#xff0c;并探讨它们背后的互助合作机制。 奖励体系解析 在七人拼团中&#xff0c;每一件…

内网环境ubuntu设置静态ip、DNS、路由,不影响网络访问

内网环境通常是有线的&#xff0c;通过服务器的ip、mac、dns地址访问网络才生效的&#xff0c;如果ip地址变了&#xff0c;就不能访问网络了。 如果你的ip地址变了&#xff0c;或者要防止ip变更影响网络访问&#xff0c;就要设置 1、依次点击右上角的电源-设置&#xff0c;在打…

【深度学习】SDXL中的Offset Noise,Diffusion with Offset Noise,带偏移噪声的扩散

https://www.crosslabs.org//blog/diffusion-with-offset-noise 带有偏移噪声的扩散 针对修改后的噪声进行微调&#xff0c;使得稳定扩散能够轻松生成非常暗或非常亮的图像。 作者&#xff1a;尼古拉斯古藤伯格 | 2023年1月30日 马里奥兄弟使用稳定扩散挖掘隧道。左图显示了未…

EasyExcel进阶教程

EasyExcel进阶教程 EasyExcel进阶教程概述一、关于表头1.1 多级表头1.2 ExcelProperties注解的index字段和order字段的第一个区别1.3 ExcelProperties注解的index字段和order字段的第二个区别1.4 表头单元格的合并二、动态表头三、样式设置3.1 表头样式设置3.2 数据样式设置Eas…

OpenAI DALL·E 3

本文翻译整理自&#xff1a;https://openai.com/index/dall-e-3/ 文章目录 一、关于 DALLE 3二、注重安全防止有害生成内部测试 三、创意掌控 一、关于 DALLE 3 1、DALLE 3 比我们以前的系统了解更多的细微差别和细节&#xff0c;使您可以轻松地将您的想法转化为极其准确的图像…

ubuntu quota配置磁盘配额

安装quota工具:sudo apt-get install quota这条命令会安装quota工具&#xff0c;它用于在Linux系统中管理和强制执行磁盘配额。编辑用户quota:sudo edquota -u <username> /data这条命令会打开默认的文本编辑器&#xff0c;允许你为用户liushenshen在/data文件系统上设置…

SDL系列(三)—— SDL2.0 扩展库:SDL_image与SDL_mixer

SDL_image SDL 默认支持的&#xff0c;只能打开 BMP 格式的图片 。 然而我们常见的是 Png jpg 格式的图片&#xff0c;于是我们这节完成 SDL 借用 自带的三方库 &#xff0c;来 完成加载渲染 png 等其他图片格式。 SDL_image 简介 使用 SDL_image &#xff0c;您…

大学生须知~~毕业季行李轻松寄,怎么邮寄行李省钱!

毕业季即将到来&#xff0c;告别母校告别这座城市&#xff0c;肯定恋恋不舍&#xff0c;这几年的学生生涯也留下了不少行李。怎么邮寄才便宜呢&#xff1f;&#xff1f; 记得找惠发快递呀&#xff01;因为我们平台是跟快递总部合作的&#xff0c;不管你寄大件还是快递都很便宜…

文件系统,磁盘的物理存储结构和逻辑存储结构

问题导入 在计算机中不是所有的文件都是被打开的&#xff0c;只有我们要用的文件的被打开&#xff0c;大部分文件&#xff08;当前不需要访问的文件&#xff09;&#xff0c;都在磁盘中保存着。问题是没有被打开的文件是否需要管理&#xff1f; 这就好比我有十套衣服&#xff…

YOLO损失函数——SIoU和Focal Lossr损失函数解析

1. 概述 YOLO&#xff08;You Only Look Once&#xff09; 系列模型以其实时目标检测能力而闻名&#xff0c;其有效性在很大程度上归功于其专门设计的损失函数。在本文中&#xff0c;这里将深入探讨YOLO演进中不可或缺的各种YOLO损失函数&#xff0c;并重点介绍它们在PyTorch中…

JWT生成token工具类实现

JWT简介 JWT定义 JWT全称为Json web token&#xff0c;也就是 Json 格式的 web token JWT数据结构 1.JWT由三段字符串组成&#xff0c;中间用.分隔 Project_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzE2MzcwMTM0LCJpYXQiOjE3MTU3NjUzMzQsImp0aSI6IjllO…

价格战开卷!字节发布豆包大模型,比行业便宜99.3%

豆包大模型正式亮相 5月15日&#xff0c;在2024春季火山引擎Force原动力大会上&#xff0c;字节跳动自研豆包大模型正式亮相。 &#xff08;图源&#xff1a;证券时报&#xff09; 火山引擎是字节跳动旗下云服务平台&#xff0c;据火山引擎总裁谭待介绍&#xff0c;豆包大模型…

线上网页点击菜单没有反应 报错ChunkLoadError:Loading chunk chunk-***** failed

现象 点击菜单无反应并且控制台报错Loading chunk chunk-***** failed 具体错误现象截图如下 分析 在线上页面已经打开的情况下&#xff0c;重新打包部署了前端项目。每次打包&#xff0c;js文件的hash值都会发生改变&#xff0c;因为我们的路由采用了懒加载&#xff0c;未…

第二届视觉语音识别挑战赛 CNVSRC 2024 启动

由 NCMMSC 2024 组委会发起&#xff0c;清华大学、北京邮电大学、海天瑞声、语音之家共同主办的第二届中文连续视觉语音识别挑战赛 CNVSRC 2024 即日启动&#xff0c;诚邀参与报名。 视觉语音识别&#xff0c;也称唇语识别&#xff0c;是一项通过口唇动作来推断发音内容的技术。…

【Linux线程(三)】生产者消费者模型

目录 前言&#xff1a; 一、什么是生产者消费者模型 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;321原则 1.三个关系 2. 两种角色 3.一个场所 &#xff08;三&#xff09;生产者消费者模型的优缺点 二、基于阻塞队列实现生产者消费者模型 &#xff0…

算法练习第22天|39. 组合总和、40.组合总和II

39. 组合总和 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/combination-sum/description/ 题目描述&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数…

安卓、iOS、iPad三端搞定,不再剧荒!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 之前给大家推荐过各种看剧姿势&#xff0c;但很多苹果、平板端的小伙伴还是存在更好的需求体验&#xff0c;今天给大家推荐这款可以在安卓、iOS和平板上都能安装使用&#xff0c;不再剧…