使用MicroApp重构旧项目

news2024/9/20 20:27:42

前言

        随着技术的飞速发展,我们公司内部一个基于“上古神器” jQuery + PHP 构建的十年历史老项目已显力不从心,技术非常老旧且维护成本高昂,其实已经无数次想要重构,但是苦于历史遗留原因以及业务的稳定性而一直难以下手,而且一时半会又不能全部重构。本次新页面较多且后续将持续迭代新模块,而老页面的改动较少且代码库错综复杂,牵一发而动全身。

        经过几番思考,我们发现微前端是一种非常实用的去实施渐进式重构的架构,很适合用微前端技术来完成本次需求,最终决定利用 Vue3 + Vite 搭建一个全新的基座(主应用),作为新旧系统融合的桥梁,将原来的老项目接入到基座,后面的新需求都在新项目里面开发就行,不用再动老项目。此举不仅实现了新页面用 Vue3 开发,而且老项目也能和新项目融合在一起,既保持了旧系统的稳定运行,又引入了新技术栈的活力。

        同时,鉴于我们另一个 Vue2 + webpack 项目也同样面临技术过时和项目规模庞大的问题,每次开发时运行起来非常卡顿,打包很慢,后期难以维护,也需要用微前端来进行一些拆分,不可能一直往该项目上堆代码。

        所以,我们决定一步到位,设计了一套微前端项目模板,将微前端的核心配置抽象为可复用的插件,并结合自研组件库、HTTP请求、权限控制等插件,构建了一个全面的项目脚手架,旨在简化未来项目的搭建流程,提升开发效率,确保技术栈的先进性与可持续性。

微前端框架选型(MicroApp)

        从对⽐图可以看出,⽬前开源的微前端框架中有的功能 Micro App都有,并提供了⼀些它们不具备的功能,⽐如静态资源地址补全,元素隔离,插件系统等。

        我们本次项目使用的是 Vue3+Vite+TypeScript 的技术栈,在综合对比了各个框架之后,认为MicroApp是最适合我们当前现实情况的。原因有下:

  1. 使用简单,将所有功能都封装到一个类WebComponent组件内,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。

  2. 不需要像 single-spa 和 qiankun 一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案

  3. 功能丰富,提供了 js沙箱、样式隔离、元素隔离、预加载、数据通信、静态资源补全等一系列完善的功能。

  4. 零依赖,这赋予它小巧的体积和更高的扩展性。

  5. 兼容所有框架,为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何技术框架中都可以正常运行。

  6. 侵入性低:对原代码几乎没有影响

  7. 组件化:基于webComponents思想实现微前端。

微前端架构设计(pnpm+monorepo)

        使用 pnpm 和 monorepo 管理项目依赖和代码结构,确保所有子应用和基座应用都位于同一仓库的不同目录下,便于集中管理和版本控制。

/root  
|-- /packages  
    |-- /main-app        # 基座应用  
    |-- /old-app-wrapper # 老项目接入容器  
    |-- /new-module-a    # 新应用A  
    |-- /new-module-b    # 新应用B  
    ...  
|-- /pnpm-workspace.yaml  
|-- /package.json
  • 基座应用(Main App)

    • 使用 Vue 3 + Vite 搭建的基座应用将成为整个系统的核心,负责路由管理、权限验证、资源加载等基础设施功能。基座应用应保持轻量级,避免过度耦合,并提供必要的API和事件系统供子应用使用。

  • 子应用(Micro Apps)

    • 老项目接入(旧应用容器):将老项目(PHP+jQuery)作为一个子应用,确保与基座应用的隔离和独立运行。新建一个项目,用于专门展示老项目页面,先在路由表中给所有路由都添加一个 iframeUrl参数(存的是旧页面的地址),并封装一个 iframe 组件,在组件中监听路由变化,动态更新Iframe的src,每一次切换路由,就将页面的地址传入 iframe 组件,从而加载出对应的老页面。

    • 新模块开发(新应用容器)新页面和模块直接在Vue 3项目中开发,利用Vue 3的Composition API、响应式系统等优势,提高开发效率和代码质量。当旧系统中有某个部分要重构时,则将旧项目中的路由下线,并将重构后的模块进行上线,实现无缝替换。

  • 通信机制

    • 建立基座应用与子应用之间的有效通信机制,如使用自定义事件、全局状态管理(如Vuex或Zustand)或专门的通信库。

    • 自定义事件:基座应用可以监听来自子应用的自定义事件,并作出相应处理。子应用同样可以监听基座应用的事件。

    • 全局状态管理:使用Vuex或Zustand等状态管理库,在基座应用中维护全局状态,子应用可以通过API访问或修改这些状态(如果允许)。

    • 专门的通信库:如使用single-spa、qiankun等微前端框架提供的API进行通信。

项目模板与脚手架

  • 模板设计:

    • 核心配置插件化:将微前端的核心配置(如子应用注册、加载策略、生命周期管理等)封装成可复用的插件,便于在不同项目中快速集成。

    • 自研组件库:整合并封装常用的UI组件,提高开发效率和界面一致性。

    • HTTP请求插件:封装统一的HTTP请求处理逻辑,包括请求拦截、响应处理、错误重试等,简化API调用。

    • 权限控制插件:基于角色或权限的动态路由控制,确保系统的安全性。

  • 脚手架构建:

    • 使用Vite作为构建工具,利用其快速冷启动和热模块替换特性,提升开发体验。

    • 集成ESLint、Prettier等代码质量工具,确保代码风格统一和减少错误。

    • 提供一键生成项目结构的脚本,包括基础目录、配置文件、基础路由和页面模板等。

  • CICD:

    • 统一的CICD流程为各个子应用和主应用提供统一的构建/部署流程

微前端设计思路

  1. 拆分功能模块:首先,我们需要将整个后台管理系统拆分为多个独立的功能模块,如用户管理模块、专项管理模块、订单管理模块等。每个模块都可以作为一个独立的微应用进行开发和维护。

  2. 设计通信协议:为了实现各个微应用之间的通信和资源共享,我们需要设计一套统一的通信协议和API。例如,我们可以定义一个emit方法来触发自定义事件,以及一个on方法来监听自定义事件;我们还可以使用Webpack的CommonsChunkPlugin插件来实现公共资源的提取和共享。

  3. 开发主应用:主应用是整个后台管理系统的入口,它负责加载和管理各个微应用。主应用需要提供一个容器元素来承载各个微应用的内容,并提供一些基础设施服务,如路由管理、状态管理等。此外,主应用还需要实现与各个微应用的通信和资源共享。

  4. 开发微应用:每个微应用都是一个独立的功能模块,它可以独立开发、部署和运行。每个微应用都需要提供一个容器元素来承载该应用的内容,并提供一些与主应用交互的接口,如共享资源、通信等。此外,微应用还需要实现自身的业务逻辑和界面展示。

  5. 集成测试:在完成各个微应用的开发后,我们需要对整个系统进行集成测试,确保各个微应用之间的通信和资源共享正常工作。此外,我们还需要对整个系统的性能、稳定性等进行测试和优化。

v-micro-app-plugin

        本文中的微前端项目,使用的是 v-micro-app-plugin插件 ,它是一款基于京东MicroApp框架的微前端插件,旨在帮助开发者快速地将微应用集成到不同的系统中,实现高效、灵活的前端模块化开发。以下是详细的使用指南,希望能够帮助你快速上手。

        本文中的案例-资源地址

  • 微前端插件 v-micro-app-plugin 源码地址:GitHub - yoguoer/v-micro-app-plugin: v-micro-app-plugin是一款基于MicroApp的微前端插件,实现快速安装使用。
  • 用该插件搭建的的示例项目 vMicroVerseHub 源码地址:GitHub - yoguoer/vMicroVerseHub: vMicroversehub 是一个使用 v-micro-app-plugin搭建的 monorepo 微前端项目框架,主要用于给出案例,更好地展示插件的功能和用法。

        该插件暂时放在私有 npm 包中,外部无法获取,但是可以直接将项目中打包后生成的dist文件夹复制下来,放到需要使用的项目中的 node_modules 文件夹下即可。(虽然这并不是一个推荐的做法,因为它绕过了 npm 的包管理和版本控制功能,可能会导致一系列问题,包括版本冲突、难以维护和更新等。)当然,也可以私信我,我直接把包分享给你啦!~~

项目实践

技术栈:

  • 主应用:Vue3+Vite+TypeScript

  • 子应用1(老项目):用 iframe 挨个嵌入

  • 子应用2(新模块):react / Vue3 ...

        本次重构的是一个后台管理系统,最外层是基座,基座不仅是微前端应用集成的一个关键平台,还承载着维护公共资源、管理依赖项以及确立开发规范的重要使命。具体而言,其职责可概括为以下几点:

  1. 子应用集成,给子应用提供渲染容器

  2. 权限管理

  3. 会话管理

  4. 路由、菜单管理

  5. 主题管理

  6. 共享依赖

  7. 多语言管理(important)

        因为micro-app对主应用和子应用的技术栈没有任何要求,所以,我们新建三个项目,my-app(Vue3)、my-app1(React)、my-app2(Vue2)。my-app是整体项目的主应用,也就是基座,my-app1和my-app2都是平级的子应用。

搭建微前端基座

1、创建一个项目作为主应用,这个步骤就不赘述了。

笔者创建了一个主应用叫 main-app,提供一个框架给子应用。

2、安装 v-micro-app-plugin 微前端插件

pnpm i v-micro-app-plugin --save

3、配置并使用

        为了便于后续复用该配置信息(配置路由、菜单、名称等等),我们将 options 参数独立出来,放在 settings 文件夹下的 microAppSetting.ts 文件中。

  • microAppSetting.ts:

const env = import.meta.env.MODE
​
const microAppUrl = {  
    appFirst: {  
      development: 'http://localhost:3000/#/',  
      test: 'https://test.example.com/vivien/appFirst/',  
      production: 'https://www.example.com/vivien/appFirst/'  
    },  
    appSecond: {  
      development: 'http://localhost:4000/#/',  
      test: 'https://test.example.com/vivien/appSecond/',  
      production: 'https://www.example.com/vivien/appSecond/'  
    },  
  };  
​
const microAppSetting = {
    projectName: 'mainApp',
    subAppConfigs: {
        'appFirst': {
            name: 'appFirst',
            url: microAppUrl['appFirst'][env]
        },
        'appSecond': {
            name: 'appSecond',
            url: microAppUrl['appSecond'][env]
        }
    },
    isBaseApp: true, // 标记当前应用为主应用
    basePath: '/', // 打包路径或其他基础路径 
    disableSandbox: false, // 是否禁用沙箱
    iframe: true, // 是否使用 iframe
}
​
export default microAppSetting
export { microAppUrl }
  • main.js:

import microAppSetting from '@/settings/microAppSetting'
​
const options = microAppSetting
​
// 初始化微前端插件  
await initMyMicroApp(app, options, router, store);

⚠注意:一定要在 router 和 store 初始化后,才可以使用 initMyMicroApp 进行初始化!!!举个简单的例子:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'y
import { createPinia } from 'pinia'
import router from './router/index'
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'
import initMyMicroApp from 'v-micro-app-plugin'
import microAppSetting from '@/settings/microAppSetting'
​
const app = createApp(App)
const store = createPinia()
app.use(router).use(ElementPlus).use(store)
​
// 初始化微前端插件  
const options = microAppSetting
await initMyMicroApp(app, options, router, store);
​
app.mount('#app')

构建子应用

1、创建任意多个项目作为子应用,这个步骤就不赘述了。

笔者创建了两个子应用,一个叫 sub-app-first,一个叫 sub-app-second。

2、安装 v-micro-app-plugin 微前端插件

pnpm i v-micro-app-plugin --save

3、配置并使用

  • sub-app-first:

const options = {
  projectName: 'appFirst',
  subAppConfigs: {},
  isBaseApp: false, // 标记当前应用不为主应用
  basePath: '/', // 打包路径或其他基础路径 
  disableSandbox: false, // 是否禁用沙箱
  iframe: true, // 是否使用 iframe
}
​
// 初始化微前端插件  
await initMyMicroApp(app, options, router, store);
  • sub-app-second:

const options = {
  projectName: 'appSecond',
  subAppConfigs: {},
  isBaseApp: false, // 标记当前应用不为主应用
  basePath: '/', // 打包路径或其他基础路径 
  disableSandbox: false, // 是否禁用沙箱
  iframe: true, // 是否使用 iframe
}
​
// 初始化微前端插件  
await initMyMicroApp(app, options, router, store);

配置路由信息

        有了主子应用之后,我们就需要在主应用中给子应用配置路由信息,这里一共有 2 个子应用,我们为它们分别进行配置。

  • appFirst:

import microAppSetting from '@/settings/microAppSetting'

export default {
  path: '/appFirst',
  name: 'appFirst',
  component: Layout,
  order: 1,
  hidden: false,
  meta: {
    title: 'appFirst',
    hideBreadcrumb: false,
    icon: Document,
    microAppOptions: microAppSetting.subAppConfigs!['appFirst']
  }
}
  • appSecond:

import microAppSetting from '@/settings/microAppSetting'

export default {
  path: '/appSecond',
  name: 'appSecond',
  component: Layout,
  order: 2,
  hidden: false,
  
  meta: {
    title: 'appSecond',
    hideBreadcrumb: false,
    icon: Document,
    microAppOptions: microAppSetting.subAppConfigs!['appSecond'],
  }
}

封装 MicroAppContainer

        众所周知,路由切换时,可以给<router-view />填充上对应路径的内容,同理,microApp中的<micro-app></micro-app>也有同样的功能。我们可以对其进行二次封装,结合 v-if,以便于根据是路由指向的是子应用,还是本系统自由模块,来判断究竟是渲染微应用视图,还是渲染普通视图。

        为了达到这个目的,我们可以新建一个 MicroAppContainer 文件夹,在其中创建一个index.vue,然后键入以下内容:

<template>
  <div :class="[`${prefixCls}-container`]">
    <!-- name:应用名称, url:应用地址 -->
    <micro-app v-bind="options" :name="options.name" keep-alive></micro-app>
  </div>
</template>
<script setup lang="ts">
import { watch } from "vue";

const props = defineProps<{
  options: {
    [key: string]: any;
  };
}>();

let prefixCls = props.options.name

watch(
  () => props.options,
  (newValue) => {
    prefixCls = newValue.name
  },
  { immediate: true, deep: true }
);
</script>
<style></style>

⚠注意:

keep-alive 属性可根据需要决定是否设置。

区分是否微应用视图

  • 在你需要加载子应用页面的地方:

        <div :class="[`${prefixCls}-viewer-microapp`]" v-if="isMicroAppView">
          <MicroAppContainer :options="microAppOptions" />
        </div>
        <div v-else>
          <router-view />
        </div>
  • 一些必要的逻辑语句:

import { watchEffect, ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()

let isMicroAppView: Ref = ref(false)
let microAppOptions: Ref = ref({})
watchEffect(async () => {
  microAppOptions.value = route.meta.microAppOptions
  isMicroAppView.value = !isNullOrUnDef(microAppOptions.value) && !isEmpty(microAppOptions.value)
})

运行项目

        我们可以看到,sub-app-first 和 sub-app-second 均能独立作为一个系统去运行,并且在 main-app 下也能作为一个模块存在。

  • sub-app-first:

  • sub-app-second:

  • main-app:

  • 控制台输出信息:

封装 Iframe 组件

        前文已经提到,老页面不需要做任何修改,且牵一发而动全身,只适合直接用 iframe 搬过来,相当于换个皮肤展示就好。但又因页面数量庞大,所以我们选择直接封装一个 iframe 组件,配合路由动态设置其 src 值,实现页面的动态切换。

        在这里,我们专门创建了一个子应用,用于独立地展示该老系统,起到新旧隔离的作用,具体操作步骤如下:

1、首先,在 views 中新建一个 iframeViews 文件夹,然后创建 index.vue

<template>
  <div ref="iframeContainers" v-if="loading">
    <iframe
      :key="name"
      :src="url"
      height="100%"
      width="100%"
      sandbox="allow-scripts allow-same-origin"
      frameborder="0"
    ></iframe>
  </div>
</template>

<script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'

let route = useRoute() // 获取当前路由信息

const iframeContainers = ref(null)
let url = ref(null)
let name =  ref(null)
let loading = ref(false)

onMounted(() => {
})

onUnmounted(() => {
})

watch(
  () => route,
  (newRoute) => {
    loading.value = true
    url.value = newRoute.meta.iframeUrl
    name.value = newRoute.name
    // console.log('🚀 ~ watch ~ newPath:', newRoute, url.value, name.value)
  },
  { immediate: true, deep: true }
)
</script>

<style scoped>
</style>

2、配置路由表

// 用户管理
import { Layout } from '@/router/layout'
import { $t } from "@/plugins/locales/setupLocale";
import { User } from "@element-plus/icons-vue";
import { getIframeUrl } from "@/settings/iframeUrlSetting";

export default {
    path: "/user",
    name: 'user',
    component: Layout,
    order: 1,
    hidden: false,
    redirect: "userList",
    meta: {
        title: $t('用户管理'),
        hideBreadcrumb: false,
        icon: User
    },
    children: [
        {
            path: '/userList',
            component: () => import("@/views/iframeViews/index.vue"),
            name: 'userList',
            hidden: false,
            meta: {
                title: $t('用户列表'),
                iframeUrl: getIframeUrl('userList'),
            }
        },
        {
            path: '/auth',
            component: () => import("@/views/iframeViews/index.vue"),
            name: 'auth',
            hidden: false,
            meta: {
                title: $t('权限列表'),
                iframeUrl: getIframeUrl('auth'),
            }
        }
    ]
}

3、为了能够在开发、测试、部署环境下都能正常运行,避免跨域问题,我们还需要通过灵活的方式来动态获取 iframeUrl

const env = import.meta.env.VITE_NODE_ENV
const url = {
    development: "https://example.com/vivien_test/",
    production: "https://example.com/vivien_prod/",
    test: "https://example.tcl.com/vivien_test/",
}
const iframeUrl = {
    development: {
    	userList: '/vivien/user/index.html',
        auth: '/vivien/auth/index.html'
	},
    production: {
        userList: '/prod/user/index.html',
        auth: '/prod/auth/index.html'
    },
    test: {
        userList: '/test/user/index.html',
        auth: '/test/auth/index.html'
    }
}
// 获取iframeUrl
export function getIframeUrl(name: string): string {
    return url[env] + iframeUrl[env][name]
}

完成基本功能

        经过这番操作,我们的旧系统就全部都嵌入进来啦!至于新系统,我们就和平常的开发一样,常规操作就可以了。主应用打开的视图如下:

        不管我们拆分成了多少个项目来开发然后拼接成一个页面,对于用户来说,这完完全全就是一个系统,只是对于开发者来说有区别而已。

通信功能

        完成了基础功能之后,我们还需要确保应用之间能够相互通信,由于主应用和子应用的通信 API 有一点差别,用的时候容易混淆,不够简便,所以我们对其进行了二次封装,提供了统一的通信 API。

对于具体的使用方法,我们通过几个简单的例子来说明:

准备工作

        首先,要引入我们的 getMicroAppMessage() 方法,获取一个通信对象

import { getMicroAppMessage } from "v-micro-app-plugin";

const microAppMessage = getMicroAppMessage();
  • 发出全局信息:用法一致

  microAppMessage.sendGlobal({
    data: { fun: "sendGlobal", text: "给全局发送数据~sendGlobal" },
    callback: () => {
      console.log("使用sendGlobal发送数据成功,执行回调!");
    },
  });
  • 子应用给主应用发出信息:无需 appName 参数

  microAppMessage.sendMessage({
    data: { app: "appSecond", value: "子应用给主应用发送数据~sendMessage" },
    callback: () => {
      console.log("子应用使用sendMessage发送数据成功,执行回调!");
    },
  });
  • 主应用给子应用发出信息:需要 appName 参数

  microAppMessage.sendMessage({
    data: { app: "mainApp", value: "主应用给appFirst发送数据~sendMessage" },
    appName: "appFirst",
    callback: () => {
      console.log("主应用使用sendMessage发送数据成功,执行回调!");
    },
  });
  • 接收全局信息: 用法一致

  setTimeout(() => {
    console.log("接收到的全局信息getGlobalMessage:", microAppMessage.getGlobalMessage());
  }, 1000);
  • 子应用接收主应用发来的信息:无需 appName 参数

  setTimeout(() => {
    console.log(
      "子应用接收到主应用发来的非全局信息getMessage:",
      microAppMessage.getMessage()
    );
  }, 1000);
  • 主应用接收子应用发来的信息:需要 appName 参数

  setTimeout(() => {
    console.log(
      "主应用收到appFirst发来的信息getMessage:", microAppMessage.getMessage('appFirst'),
      "主应用收到appSecond发来的信息getMessage:", microAppMessage.getMessage('appSecond')
    );
  }, 1000);
  • 控制台信息:

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

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

相关文章

TortoiseGit处理文件夹名、文件名大小写变更问题

windows环境下使用TortoiseGit&#xff0c;经常会碰到把源码中某个文件或文件夹仅修改大小写进行重命名的场景&#xff0c;但是git默认大小写不敏感&#xff0c;可以使用以下方式处理&#xff1a; 一、文件大小写重命名 右键要重命名的文件→TortoiseGit→Rename 二、文件夹大…

猫头虎 分享:Python库 Scrapy 的简介、安装、用法详解入门教程

猫头虎 分享&#xff1a;Python库 Scrapy 的简介、安装、用法详解入门教程 &#x1f405; 今天猫头虎带您探索Python中的强大爬虫库——Scrapy&#xff0c;从简介到安装&#xff0c;再到用法详解&#xff0c;带您一步步掌握这门技术&#xff01; &#x1f40d; &#x1f4e2; …

Mafia

目录 一、题目 二、思考 三、payload 3.1 方案一 3.2 方案二 3.3 方案三 3.4 方案四 四、思考与总结 一、题目 /* Challenge */ mafia (new URL(location).searchParams.get(mafia) || 11) mafia mafia.slice(0, 50) mafia mafia.replace(/[\\\"\\-\!\\\[\]]/gi…

Linux快捷方式创建、输出重定向(正确输出和错误输出)

一.正确输出 创建一个1.txt文件&#xff0c;然后用vim打开这个文件&#xff0c;然后再开一个窗口 进程号是5602 通过proc可以看到5602这个进程 进入5602里面这里记录了程序的信息&#xff0c;找到fd 进入fd目录下面有0124快捷方式&#xff1a;快捷方式对应的真正的文件是 /de…

计算机毕业设计 医院问诊系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

2024年人工智能SSD采购容量估计将超过45 EB

市场报告摘要 市场背景&#xff1a;根据TrendForce公司的报告&#xff0c;由于人工智能&#xff08;AI&#xff09;服务器客户对AI应用的企业级固态硬盘&#xff08;SSD&#xff09;需求激增&#xff0c;过去两个季度中企业级SSD订单显著增加。上游供应商动态&#xff1a;上游…

mysql 一些知识点 面试用

mysql 1、4个隔离级别与3个现象2、快照读与当前读2.1 可重复读的情况下出现幻读问题的两种情况 3 数据库 常用引擎4、InnoDB存储引擎对MVCC的实现5、索引(重点)5.1 什么是索引5.2 索引的创建与删除5.2.1 查看表中有哪些索引5.2.2 添加索引5.2.3 删除索引 5.3 索引的分类5.4 树数…

web技术1——jdk目录结构(重要),tomcat服务器

jdk文件夹结构(重要) bin目录&#xff1a; 里面都是.exe可执行文件。java&#xff0c;javac&#xff0c;javadoc&#xff0c;java编译工具&#xff0c;java监测工具等.exe文件都在这里。 include目录: 底层有用c写的东西&#xff0c;这里面包含很多c语言的文件&#xff0c…

Ok, Boomer

目录 一、题目 二、思路 三、payload 3.1 方案一 3.2 方案二 四、思考与总结 一、题目 <!-- Challenge --> <h2 id"boomer">Ok, Boomer.</h2> <script>boomer.innerHTML DOMPurify.sanitize(new URL(location).searchParams.get(boome…

DAM-E3505N以太网口三相400V 100A全参数交流电量采集模块Modbus-TCP协议

品牌&#xff1a;阿尔泰科技 型号&#xff1a;DAM-E3505 简介&#xff1a; DAM-E3505N为三相全参数交流电量采集模块&#xff0c;以太网通讯接口&#xff0c;支持标准Modbus-TCP协议。配备良好的人机交互界面&#xff0c;使用方便&#xff0c;性能稳定。 指标参数&#xff1…

破解 Google 账户注册难题丨0到1学习谷歌广告(1)

立了个flag&#xff0c;连载系列&#xff0c;把主流渠道的0到1细节都写一遍。 做跨境电商&#xff0c;要投谷歌广告&#xff0c;拥有一个Google账户已成为我们不可或缺的一部分。今天&#xff0c;就让我们一起来聊聊如何轻松注册一个属于你自己的Google账户。 为什么需要Googl…

【深度学习入门】深度学习概述

一、什么是人工智能 机器人能够通过“眼睛”看到这个世界&#xff0c;并对这个世界加以理解&#xff0c;最后做出一些决策。因此人工智能具有 感知理解决策 的能力。 人类能够站在食物链顶端的关键是具有学习的能力&#xff0c;这是智能的本质。 简而言之&#xff0c;人工智能就…

【数据结构篇顺序表】算法题

1. 移除元素​https://leetcode.cn/problems/remove-element/description/​sorted-array/description/​ 1.思路 这个题要删除数组中等于 val的元素&#xff0c;然后返回数组中剩余的元素个数 那么肯定是要用到循环的&#xff0c;先给两个指针 l1,l2&#xff0c;开始时l1和…

学网安兴趣最重要

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

Matplotlib-绘图基础

文章目录 一、安装Matplotlib二、基本绘图流程1.创建画布与创建子图2.基本绘图1&#xff09;.绘制线条2).设置线型、线宽格式化字符颜色的缩写 3).设置坐标轴范围4).设置坐标刻度5).设置坐标轴6&#xff09;备注 三、高级绘图1&#xff09;plt对象支持的图类型2&#xff09;散点…

【Linux】——进程概念(万字解读)

一 冯诺依曼体系结构 在此之前&#xff0c;我们先要理解我们计算机的冯诺依曼体系结构&#xff0c;因为是进程的基础 我们所有的操作其实都是基于这样一个模型&#xff0c;比如你在qq上&#xff0c;和别人发送消息&#xff0c;这个消息肯定是先通过输入设备进行输入&#xf…

Vue3学习 Day03

标签的ref属性 用ref标记dom元素 取代了用id来标识标签&#xff0c;因为用id来标识标签&#xff0c;如果父组件和子组件中有id一样的&#xff0c;会引起混淆。 用ref标记组件 子组件 向父亲暴露name&#xff0c;age&#xff0c;classroom&#xff08;非响应式&#xff09; …

make/Makefile -基本使用

文章目录 一、make/Makefile 的认识makeMakefile 二、make/Makefile 基本使用创建项目清理项目make 指令的使用 三、makefile 的几个语法关键字 PHONY :$ :变量 : 四、makefile的语法推导过程 一、make/Makefile 的认识 我们一般使用 Visual Studio&#xff08;下面简称 VS&am…

【秋招笔试题】米小游的植树工

解法&#xff1a;若区间覆盖的最小值大于等于2&#xff0c;则有他没他没影响&#xff0c;反之则不能算。所以一开始差分数组预处理区间加&#xff0c;然后ST表查最小值即可。 package com.sky;import java.util.Scanner; import java.util.Arrays;public class Test1 {static …

代码随想录——合并区间(Leetcode hot14)

题目链接 思路&#xff1a; 合并区间分为两种情况&#xff1a; 前一个数组右边界 > 后一个数组左边界 eg:[1,3],[2,6] > 合并为[1,6]前一个数组右边界 > 后一个数组右边界 eg:[1,6],[2,4] > 合并为[1,6] class Solution {public int[][] merge(int[][] intervals)…