微前端 Spa qiankun

简介

首先什么是微前端?

他是一个软件架构模式。借鉴了后端的为服务架构思想,是将复杂单一的前端进行拆分成多个可以独立开发、部署、维护的小型应用。不同的应用关注不同的业务。最终将其集成到一个主框架里面。简单来说就是先分后合。

传统前端开发的痛点

有这么一个场景,你现在在实际工作当中维护了一个vue项目。这个项目里面现在有了业务A、业务B、业务C…,然后还有系统登录权限、公共配置等模块,就导致这个vue项目很大,尤其是对于vue2+webpack来说打包速度堪忧。这个时候微前端就有用武之地了。将每一个业务模块都拆分出来独立进行开发之后统一合并部署,大大的提升了维护,同时如果需要将其整体从vue2迁移到vue3同样也是如此逐渐迁移。

微前端实现方案

iframe

这个就比较简单,开发一个菜单然后主题内容就根据菜单的切换更换这个主体iframe包的url地址就好了。

为什么不用ifream

web Components

web组件:也就是基于原生来创建组件,像vue当中的组件也是这样实现的。

  • 自定义元素:定义元素及其行为
  • 影子Dom:将封装的“影子”DOM 树附加到元素并控制其关联的功能。使用这种方式保持元素的功能私有,不用担心与文档的其他部分发生冲突。
  • html 模板:<template><slot> 元素可以作为标记模板

无界微前端组件实现案例:

假设我有一个叫child.html的文件,里面对h1元素的样式进行了改变,现在我有一个父页面需要加载child.html当中的代码进来并且要实现样式隔离。

  • 创建class类继承HTMLElement。通过getAttribute可以获取到wu-jie标签当中的所有属性
  • 通过loadFile方法获取child的html代码,通过attachShadow创建一个影子DOM
  • 最后将这个影子DOM和child的代码合到一起加到主页面当中。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PCM Player</title>
</head>
<body>
<h1>我是一级标题</h1>
<wu-jie src="http://localhost:63342/CimH5/src/views/home/child.html"/>
<script>
  const loadFile = async (src) => {
    return await fetch(src).then(res => res.text());
  };
  const injectHTML = (shadowRoot, html) => {
    const wrapper = document.createElement('div');
    wrapper.innerHTML = html;
    shadowRoot.appendChild(wrapper);
  };

  class WUJIE extends HTMLElement {
    constructor() {
      super();
      this.src = this.getAttribute('src');
    }

    async connectedCallback() {
      const html = await loadFile(this.src);
      const shadowRoot = this.attachShadow({mode: 'open'});
      injectHTML(shadowRoot, html);
      console.log('得到子应用的代码 =====', html);
    }
  }

  window.customElements.define('wu-jie', WUJIE);
</script>
</body>
</html>

这样也就完成了组件之间的样式隔离(也就是经常听到的沙箱)

沙箱:是一种用于隔离正在运行程序的安全机制,通常用于执行未经测试或者不受信任的程序或代码,它会为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响到外部程序的运行。也就是不同应用之间虽然同时运行在同一个html当中但是js、css不会混淆到一起,互不影响。

在这里插入图片描述

single-spa(单页面)

通过路由劫持实现应用加载(采用systemjs)

  • 优:基于props主子应用通信
  • 缺:无沙箱

module federation

通过模块联邦将组件打包导出使用

  • 优:共享模块的方式进行通信
  • 缺:无沙箱,需要使用webpack5

single-spa

简介

spa(single page application)是一个将多个单页面应用聚合为一个整体应用的 js 微前端框架

single-spa会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面

实战

安装spa&初始化应用

全局安装脚手架

npm install --global create-single-spa

创建一个基座应用,在通过脚手架创建应用的时候会有这几个选项,可以看一下,分别就是用来创建子应用、基座、公共模块三个大类的

'single-spa-application / parcel':微前端架构中的微应用,可以使用 vue、react、angular 等框架。
'single-spa root config':创建微前端容器应用。
'utility modules':公共模块应用,非渲染组件,用于跨应用共享 javascript 逻辑的微应用
C:\Users\modify>create-single-spa base
? Select type to generate single-spa root config
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Would you like to use single-spa Layout Engine No
? Organization name (can use letters, numbers, dash or underscore) QmQ
Initialized empty Git repository in C:/Users/modify/base/.git/

创建子应用,这里创建一个vue和react的项目。下面演示一下创建react项目的,vue项目也是一样

D:\mygit\spa>create-single-spa react-app
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Organization name (can use letters, numbers, dash or underscore) modify
? Project name (can use letters, numbers, dash or underscore) react-app
Initialized empty Git repository in D:/mygit/spa/react-app/.git/

spa代码说明

base基座

在src下的index.ejs当中可以直接找到下面这个代码,这个代码就是用来给基座和子应用进行关联的。

    <% if (isLocal) { %>
        <script type="systemjs-importmap">
            {
              "imports": {
                "@modify/root-config": "//localhost:9000/modify-root-config.js",
              }
            }
        </script>
    <% } %>

然后看同样的src下的modify-root-config.js这个名字适合创建的时候设置的Organization name有关的,所以文件名是有区别的

import {registerApplication, start} from 'single-spa';

// 服务注册
// 这里用到的name属性也就一个标识,然后通过SystemJs导入一个外部js
registerApplication({
  name: '@single-spa/welcome',
  app: () =>
    System.import(
      'https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js'
    ),
  // 这里表示路由,因为生成的路由是["/"]这个表示以/开头的都会走这个规则也就是访问上面那个js,所以这里进行了调整
  activeWhen: location => location.pathname === '/'
});

// 启动
start({
  urlRerouteOnly: true
});
vue项目

这里的vue3项目当中会有两个启动命令,一个serve一个serve:standalone。区别在于是否serve是基于微前端spa启动的,而standalone是独立启动,和平常我们使用的vue项目启动是一样的。

在使用命令serve:standalone进行启动报错:

single-spa.dev.js:155 Uncaught TypeError: application '@modify/vue-app' died in status LOADING_SOURCE_CODE: Cannot read properties of undefined (reading 'meta')
	xxxx

解决方案:修改vue.config.js文件,GitHub issues

const {defineConfig} = require('@vue/cli-service');
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 3000
  },
  configureWebpack: {
    output: {
      libraryTarget: 'system'
    }
  }
});

项目关联

关联vue项目

看了这些,那怎么把vue项目和基座项目关联起来呢?首先这里我的vue项目名是@modify/vue-app(看package.json的name属性)之后再base基座项目当中的modify-root-config.js添加配置,表示将vue项目注册到基座当中

registerApplication({
  // vue项目名称
  name: '@modify/vue-app',
  app: () =>
    // 导入模块
    System.import(
      '@modify/vue-app'
    ),
  // 指定访问路径
  activeWhen: location => location.pathname === '/vue'
});

在index.ejs代码当中添加导入关联配置

    <% if (isLocal) { %>
        <script type="systemjs-importmap">
            {
              "imports": {
                "@modify/root-config": "//localhost:9000/modify-root-config.js",
                // 和上面System.import导入的名称一致。
                "@modify/vue-app": "//localhost:3000/js/app.js"
              }
            }
        </script>
    <% } %>

这里就可能会有问题了,你指定了vue项目的端口是3000我知道,那你是怎么知道后面加/js/app.js这个路径呢?这个时候我们通过serve去启动vue项目,访问3000可以看到下面这个样子,简单翻译一下就懂了。最后访问基座项目加到对应路由也就是:localhost:9000/vue也是能访问这个vue项目的,就说明配置关联成功了。

在这里插入图片描述

关联react项目

这个和vue项目是一样的,还是先添加注册的规则,然后启动react项目看微前端的react指向的地址,然后添加配置即可,也就是这样

    <% if (isLocal) { %>
        <script type="systemjs-importmap">
            {
              "imports": {
                "@modify/root-config": "//localhost:9000/modify-root-config.js",
                "@modify/vue-app": "//localhost:3000/js/app.js",
                "@modify/react-app": "//localhost:4000/modify-react-app.js"
              }
            }
        </script>
    <% } %>
基座与vue项目传参

还是在基座路由规则这添加一个customProps表示传递过去的数据,传的是一个对象。

registerApplication({
  name: '@modify/vue-app',
  app: () =>
    System.import(
      '@modify/vue-app'
    ),
  activeWhen: location => location.pathname === '/vue',
  customProps: {
    a: 1
  },
});

在vue项目当中的main.js文件当中可以进行接收,在这里可以通过this拿到a的值,之后在vue文件当中就可以通过defineProps拿到这个a了。

const vueLifecycles = singleSpaVue({
  createApp,
  appOptions: {
    render() {
      return h(App, {
        a: this.a
      });
    }
  }
});

乾坤

乾坤demo地址:https://github.com/lizuoqun/spa/tree/master/qiankun-base

基座(主应用)

先构建一个vue3的前端项目,之后安装一下qiankun。用vue3项目当做一个主应用基座

npm create vite@latest qiankun-base
cd qiankun-base
npm install
npm i qiankun -S
npm run dev

入口改造

在这里简单说明一下,注册了一个react应用,react应用访问的地址是3000端口+react-app,这个在下面创建react主应用可以看到,然后配置了qiankun的生命周期。

import {createApp} from 'vue';
import './style.css';
import App from './App.vue';
import {registerMicroApps, start} from 'qiankun';

// 注册的所有子应用
const QIANKUN_APPS = [
  {
    name: 'react app', // 子应用名称
    entry: '//localhost:3000', // 子应用的IP端口
    container: '#reactAppContainer', // 子应用渲染到哪个容器当中
    activeRule: '/react-app' // 子应用访问的路径
  }
];
// qiankun生命周期
const QIANKUN_LIFECYCLE = {
  beforeLoad: [async (app: any) => {
    console.log('before load =====', app.name);
  }],
  beforeMount: [async (app: any) => {
    console.log('before mount =====', app.name);
  }],
  afterUnmount: [async (app: any) => {
    console.log('after unmount =====', app.name);
  }]
};
registerMicroApps(QIANKUN_APPS, QIANKUN_LIFECYCLE);

start();
createApp(App).mount('#app');

React子应用

项目初始化

通过命令创建一个react项目

npm install -g create-react-app
create-react-app react-app

这里有可能创建之后启动项目会出现报错,直接全局搜索把引入这个web-vitals的代码注释掉即可。

Module not found: Error: Can't resolve 'web-vitals'

入口改造

修改index.js入口文件。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {createRoot} from 'react-dom/client';
// 安装下 npm i react-router-dom
import {BrowserRouter} from 'react-router-dom';
import './public-path.js'

let root;

function render(props) {
  // 判断子应用加载的时机(是嵌套在主应用下的还是直接单独运行加载的)
  const {container} = props;
  const dom = container ? container.querySelector('#root') : document.querySelector('#root');
  root = createRoot(dom);
  root.render(
    // 默认路由
    <BrowserRouter basename="/react-app">
      <App/>
    </BrowserRouter>
  );
}

reportWebVitals();

// 判断是否在 qiankun 环境下运行
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}


// 生命周期。
// 1. bootstrap: 在主应用加载子应用之前,子应用的生命周期将被调用。
// 2. mount: 在主应用加载子应用之后,子应用的生命周期将被调用。
// 3. unmount: 当子应用被卸载时,子应用的生命周期将被调用。

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const {container} = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

添加public-path.js

在qiankun文档上面是没有eslint注释的代码,会导致项目运行出错,可以看 Github上看到的解决方案

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

修改webpack配置

安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired

npm i -D @rescripts/cli

根目录新增 .rescriptsrc.js

const {name} = require('./package');

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*'
    };
    config.historyApiFallback = true;
    config.hot = false;
    // webpack5里面也没有这个watchContentBase配置了,索性也直接注释好了
    // config.watchContentBase = false;
    config.liveReload = false;

    return config;
  }
};

到这一步就完成了子应用的改造,启动项目访问 http://localhost:3000/react-app

在这里插入图片描述

Vue3子应用

项目初始化

npm create vite@latest vue-app
cd vue-app
npm i
npm install vue-plugin-qiankun

修改vite配置

关于 vite-plugin-qiankun 插件

import {defineConfig} from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';

// https://vitejs.dev/config/
export default defineConfig({
  base: './vue-app',
  plugins: [vue(), qiankun('vue-app', {
    useDevMode: true
  })],
  server: {
    port: 3001,
    cors: true,
    origin: 'http://localhost:3001'
  }
});

入口改造

这里要注意:在引入了其他的库之后在if判断和else当中的createApp当中都要use一下对应的库。完成了这一步然后再基座应用当中和加react子应用一样把vue子应用加进去这就完成了微前端

import {createApp} from 'vue';
import './style.css';
import App from './App.vue';
import router from './router';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import {renderWithQiankun, qiankunWindow} from 'vite-plugin-qiankun/dist/helper';

let app: any;
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  createApp(App).use(router).use(ElementPlus).mount('#app');
} else {
  renderWithQiankun({
    mount(props) {
      app = createApp(App).use(router).use(ElementPlus);
      app.mount(props.container?.querySelector('#app'));
    },
    bootstrap() {
      console.log('vue app bootstrap');
    },
    update() {
      console.log('vue app update');
    },
    unmount(props) {
      console.log('vue app unmount', props);
      app.unmount();
    }
  });
}

子应用间跳转

vue跳react

这里安装vue-router和配置router路由就不说明了,主要看一下跳转代码。这里跳转到vue子应用当中的路由都是可以的,但是react子应用是跳转不过去的。

<script setup lang="ts">
import {ref} from 'vue';
import router from '@/router/index';

const activeName = ref('list');

const handleClick = (tab: any) => {
  router.push({name: tab.paneName});
};
</script>

<template>
  <div class="vue__app__title">这是一个vue3子应用</div>
  <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
    <el-tab-pane label="List" name="list"></el-tab-pane>
    <el-tab-pane label="Detail" name="detail"></el-tab-pane>
    <el-tab-pane label="Go React" name="react-app"></el-tab-pane>
  </el-tabs>
  <router-view/>
</template>
方案一:直接重定向
window.location.href = '/react-app';
方案二:pushState

语法

history.pushState(stateObject, title, url);

参数

参数描述
stateObject传入的状态对象。当前进(后退)到某一新的状态时,会触发popstate事件。此事件对象event.state存储的就是这个stateObject的值。
title新状态的标题。(目前,大多数浏览器并不支持该参数,建议传null值)
url状态对应的历史记录的地址。

调整一下vue当中跳转的代码,重新匹配一下路由通过window.history.pushState跳到react子应用当中

const handleClick = (tab: any) => {
  if (tab.paneName === 'react-app') {
    window.history.pushState({}, '', '/react-app');
  } else {
    router.push({name: tab.paneName});
  }
};

同样的在主应用当中我们可以拦截到这个pushState,并且在主应用里面进行一些相对应的处理:改一下基座的入口,添加以下代码

注意一下咯:这里通过apply重新设置值的时候使用的普通函数,要是使用箭头函数的话会改变this指向导致报错

// 重写history的pushState方法,触发自定义事件,并返回原方法的返回值
const write = (type: string) => {
  const orig = (window as any).history[type];
  return function () {
    const rv = orig.apply(this, arguments);
    const e: any = new Event(type);
    e.arguments = arguments;
    window.dispatchEvent(e);
    return rv;
  };
};

window.history.pushState = write('pushState');

window.addEventListener('pushState', () => {
  console.log('base 基座监听到了pushState事件=====');
});

问题

react卸载报错

在从vue项目跳转到react当中,再从react返回到vue,会出现以下报错,就是react入口文件当中配置了卸载unmountComponentAtNode的时候出现了问题。

application 'react app' died in status UNMOUNTING: react_dom_client__WEBPACK_IMPORTED_MODULE_1__.unmountComponentAtNode is not a function
TypeError: react_dom_client__WEBPACK_IMPORTED_MODULE_1__.unmountComponentAtNode is not a function

这是因为在 React 18, unmountComponentAtNode 已被 root.unmount() 取代。那么在react项目当中改一下入口当中的卸载钩子

// 删掉这个
// export async function unmount(props) {
//   const {container} = props;
//   ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
// }

export async function unmount(props) {
  root.unmount();
}

公共依赖加载

在开发过程中,不同的子应用还是会使用到很多相同的第三方库的依赖,比方说:axios、dayjs、lodash等。而微前端是统一加载的,这个时候我们可以将这些公共依赖只加载一次,而不是切换个子应用又加载一次。

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

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

相关文章

【Unity - 屏幕截图】技术要点

在Unity中想要实现全屏截图或者截取某个对象区域的图片都是可以通过下面的函数进行截取 Texture2D/// <summary>/// <para>Reads the pixels from the current render target (the screen, or a RenderTexture), and writes them to the texture.</para>/…

【氮化镓】低温对p-GaN HEMT迁移率、阈值电压和亚阈值摆幅的影响

本期分享一篇低温对p-GaN HEMT 迁移率、阈值电压和亚阈值摆幅影响进行表征和建模的研究论文。文章作者Shivendra Kumar Singh、Thien Sao Ngo、Tian-Li Wu(通讯作者)和Yogesh Singh Chauhan,分别来资源中国台湾阳明交通大学国际半导体技术学院、印度理工学院坎普尔分校电气工…

(二)Python输入输出函数

一、输入函数 input函数&#xff1a;用户输入的数据&#xff0c;以字符串形式返回&#xff1b;若需数值类型&#xff0c;则进行类型转换。 xinput("请入你喜欢的蔬菜&#xff1a;") print(x) 二、输出函数 print函数 输出单一数值 x666 print(x) 输出混合类型…

专利开放许可与知识产权保护的关系是什么?

专利开放许可在一定程度上是对知识产权保护制度的补充和拓展。知识产权保护的核心目标是鼓励创新&#xff0c;通过赋予专利所有者一定期限内的独占权&#xff0c;使其能够从创新成果中获得经济回报&#xff0c;从而激励更多的创新投入。 专利开放许可则为专利的应用和传播提供了…

如何制作一个宠物店小程序

产品介绍&#xff1a; 出门在外随时都可以看到不少的居民养的有宠物&#xff0c;大多数都是以阿猫阿狗为主&#xff0c;可以所得宠物市场比较大&#xff0c;宠物数量已经统计到的就有上亿只。这么多的宠物肯定需要宠物粮食&#xff0c;宠物服务市场就出来了。 家里有宠…

Git的基本使用入门

参考&#xff1a;Git速查 git的基本概念 git常用命令大部分是基于三大分区来执行的。先来了解一些专有名词吧。 工作区&#xff0c;也叫 Working Directory暂存区&#xff0c;也叫 stage&#xff0c;index版本库&#xff0c;也叫本地仓库&#xff0c;commit History 将代码推…

从混乱到可控:非结构化数据在远程监造中的作用

一、背景远程数字监造&#xff0c;工业制造的新趋势 在光伏组件的生产过程中&#xff0c;其质量和安全性&#xff0c;对产品的整体效益来说至关重要。为保证最终效益&#xff0c;必须要有对生产过程的监造和生产完成的验收。 然而&#xff0c;传统的线下监造模式效率较低&…

facebook受众选择设置策略的最佳方式

在进行Facebookguanggao投放时&#xff0c;受众的选择是一个至关重要的步骤。正确的受众选择不仅能够帮助我们更好地定位目标用户&#xff0c;还能显著提高guanggao的转化率和投资回报率&#xff08;ROI&#xff09;。然而&#xff0c;受众选择的数量和范围同样是需要认真考虑的…

Mybatis全局配置介绍

【mybatis全局配置介绍】 mybatis-config.xml&#xff0c;是MyBatis的全局配置文件&#xff0c;包含全局配置信息&#xff0c;如数据库连接参数、插件等。整个框架中只需要一个即可。 1、mybatis全局配置文件是mybatis框架的核心配置&#xff0c;整个框架只需一个&#xff1b…

物联网IoT平台 | 物联网IoT平台的定义

物联网IoT平台&#xff1a;定义、发展与应用在当今信息化时代&#xff0c;物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;已经成为推动社会进步和产业升级的重要力量。物联网IoT平台&#xff0c;作为连接物理世界与数字世界的桥梁&#xff0c;正逐步改变…

【多线程】线程池(上)

文章目录 线程池基本概念线程池的优点线程池的特点 创建线程池自定义线程池线程池的工作原理线程池源码分析内置线程池newFixedThreadPoolSingleThreadExecutornewCachedThreadPoolScheduledThreadPool 线程池的核心线程是否会被回收?拒绝策略ThreadPoolExecutor.AbortPolicyT…

撸猫变梳毛?怎么解决猫咪掉毛问题?好用的宠物空气净化器推荐

秋风一吹&#xff0c;新一轮的猫咪换毛季又到了&#xff0c;这也意味着我失去了撸猫自由。我每天的治愈方式就是下班撸猫&#xff0c;抚摸着柔软的毛发&#xff0c;好像一天的烦恼都消除了。可是一到换毛季&#xff0c;猫还没撸两下&#xff0c;先从猫咪身上带下一手毛&#xf…

ASP.NET Core8.0学习笔记(二十一)——EFCore关系配置API

一、关系配置API概述 当我们需要指定一个字段作为外键&#xff0c;而这个外键又不符合以上四种约定时&#xff0c;就需要在IEntityTypeConfiguration实现类&#xff08;对应的配置类&#xff09;中使用Fluent API直接配置外键。理论上可以通过API直接指定一个属性&#xff0c;…

关于Qt音乐播放器进度条拖拽无用的问题解决方案

在使用Qt编写音乐播放器的时候&#xff0c;进度条关联播放音乐基本是必须的。那么在设计的过程中你可能会碰到一个奇怪的问题就是拖拽进度条的时候&#xff0c;可能会报错如下&#xff1a; 然后音乐就卡着不动了。。。 connect(ui->volume_toolButton,&VolumeToolBtn::…

LLM - 配置 ModelScope SWIFT 测试 Qwen2-VL 视频微调(LoRA) 教程(3)

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

挖掘空间数据要素典型领域应用场景

空间数据要素作为数字经济的基石&#xff0c;正在多个领域发挥着重要作用。随着技术的发展&#xff0c;空间数据的应用场景不断拓展&#xff0c;为各行各业带来了深刻的变革。以下是几个典型的空间数据要素应用领域&#xff1a; 1. 城市规划与管理 空间数据在城市规划和管理中…

在线培训知识库+帮助中心:教育行业智慧学习的创新桥梁

在数字化转型的浪潮中&#xff0c;教育行业正经历着前所未有的变革。为了应对日益增长的学习需求&#xff0c;提升教育质量&#xff0c;构建一个集在线培训知识库与帮助中心于一体的智慧学习环境&#xff0c;已成为教育行业转型升级的重要方向。这一创新模式不仅优化了学习资源…

雷池社区版配置遇到问题不要慌,查看本文解决

很多新人不太熟悉反向代理&#xff0c;所以导致配置站点出现问题 配置问题 记录常见的配置问题 配置后攻击测试没有拦截记录 检查访问请求有没有真实经过雷池 有很多新人配置站点后&#xff0c;真实的网站流量还是走的源站&#xff0c;导致雷池这边什么数据都没有 配置后…

【CTF Web】Pikachu 不安全的url跳转 Writeup(URL重定向+代码审计)

不安全的url跳转 不安全的url跳转问题可能发生在一切执行了url地址跳转的地方。 如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话 就可能发生"跳错对象"的问题。 url跳转比较直接的危害是:…

springboot 整合 rabbitMQ(2)

springboot 整合 rabbitMQ&#xff08;1&#xff09;-CSDN博客 上期说了rabbitMQ的基础用法&#xff08;普通队列模式&#xff09; 这期学习一下如何防止消息重复消费和进阶用法&#xff08;订阅者模式&#xff09; 目录 重复消费问题 导致 RabbitMQ 重复消费问题的原因&a…