微前端vue项目实战 -- 乾坤qiankun框架 (一)

news2025/2/24 7:38:40

背景:

公司研发的项目主要是 GIS地图为基础的管理系统,由主体项目(管理业务模块)+ GIS地图应用,由于 GIS地图应用模块 会在多个地方使用,所以单独构建一个项目,分别在不同的业务场景中引用、交互,也在app中通过webview通信使用,之前一直使用iframe的通信方式,iframe基本也可以满足业务需求,应用分割、独立运行、分别部署,iframe方案已经满足并且不断优化已经满足需求,但是作为程序猿还是想尝试一下新技术,或许实战它可能不是最佳方案,那也只有试了才知道,那就研究一下吧。

站在巨人的肩膀,看一下天空,用了阿里的乾坤qiankun框架,尝试一下踩坑,说干就干。

首先贴一下qiankun官网,官网介绍也很详细,也在网上借鉴了很多大神的操作,下面记录一下自己学习、码代码的步骤,就当记个笔记了

一、iframe方案的实现

// 主应用 引入子应用
// 通过 iframe

<iframe webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen="true" id="iframe" :src="url" width="100%" height="100%" scrolling="No" frameborder="0"></iframe>


// 主应用 接口 iframe 子应用页面数据

window.onmessage = params => {

    console.log("子应用传来的数据",params )

}


// 主应用 向iframe 子应用发送数据

 let ele = document.getElementById("iframe").contentWindow;
 ele.postMessage(params, "*");




// 子应用接收主应用传来的数据

  window.addEventListener("message", async params => {
      console.log('接收主应用消息', params)
       
  });

//子应用给主应用发送数据

  let obj = {xxx:xxx}
  window.parent.postMessage(obj, "*");


// 子应用移除监听

 window.removeEventListener("message",()=>{});

二、微前端qiankun实现方案

什么是微前端(取阿里qiankun的介绍)

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级

    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

将原本把所有功能集中于一个项目中的方式转变为把功能按业务划分成一个主项目和多个子项目,每个子项目负责自身功能,同时具备和其它子项目和主项目进行通信的能力,达到更细化更易于管理的目的。

1、微前端框架搭建--qiankun

主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可,微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用

可以看到官网介绍微应用需要使用到webpack,所以在构建应用时,我们使用vue在选择构建方式时候,主应用可以使用webpack 或vite,子应用必须使用webpack 构建,所以实验学习使用的 

主应用:vue3 使用cli的构建方式;子应用:vue2 使用cli方式

构建的应用大概长这样:

为了方便,贴上学习实践的demo:

主项目vue3:vue-qiankun-master: 微前端qiankun,vue3.x项目,微前端主应用

子项目vue2:vue-qiankun-child: 微前端qiankun,vue2.x项目,微前端子应用

 上面demo只需要下载下来运行就可以查看效果了

2、开始构建项目

主、子项目都具备 登录、侧边菜单、主内容;页面如下

 

 2.1、构建主应用

vue3项目构建过程可以看我的其他文章,此处省略

构建完主应用项目后,在主应用安装qiankun

yarn add qiankun # 或者 npm i qiankun -S

使用qiankun的registerMicroApps注册微应用

在主应用项目中src创建micros,在micros创建index.js、apps.js

应用切换时用到nprogress,先安装nprogress

npm install --save nprogress
# 或者
yarn add nprogress

index.js

// 子应用切换加载进度条
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import Store from "../store"

// 注册微应用主应用
import { registerMicroApps, start, addGlobalUncaughtErrorHandler, initGlobalState } from 'qiankun';
import apps from "./apps";

// 微应用通信 定义全局状态,并返回通信方法
const state = {}
const actions = initGlobalState(state);
actions.setGlobalState({
    globalToken: ''
})

registerMicroApps(apps, {
    beforeLoad: (app) => {
        // 加载微应用前,加载进度条    
        NProgress.start();
        console.log("before load", app.name);

        if (Store.state.token) {
            //  微应用加载检查登录 已登录 子应用直接传参登录
            actions.setGlobalState({ globalToken: Store.state.token })
        }

        return Promise.resolve();
    },
    afterMount: (app) => {
        // 加载微应用前,进度条加载完成    
        NProgress.done();
        console.log("after mount", app.name);

        return Promise.resolve();
    },
});

addGlobalUncaughtErrorHandler((event) => {
    console.error(event);
    const { message: msg } = event
    if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
        console.error("微应用加载失败,请检查应用是否可运行");
    }
});

export default start;
export {
    actions
}

 这里用到qiankun框架的几个api,简单介绍一下

registerMicroApps(apps, lifeCycles?):

  • apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
  • lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子

微应用注册,包含两个参数,第一个参数是微应用的一些注册信息,第二个参数是全局的微应用生命周期钩子。

start(opts?)

  • opts - Options 可选

我们用来启动qiankun的方法,包含一个参数

addGlobalUncaughtErrorHandler(handler)

  • handler - (...args: any[]) => void - 必选

全局的未捕获异常处理器。

qiankun API具体使用方式请查看官网api

apps.js

const apps = [
  /**
  * name: 微应用名称 - 具有唯一性
  * entry: 微应用入口 - 通过该地址加载微应用
  * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
  * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
  */

  {
    name: "vue-micro-app",
    entry: "//localhost:8082",
    container: "#micro-container",
    activeRule: "#/vue2-micro-app",
    props: {
      router: router
    }
  },
];
export default apps;

apps导出的参数是registerMicroApps的第一个参数apps

  * name: 微应用名称 - 具有唯一性
  * entry: 微应用入口 - 通过该地址加载微应用
  * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
  * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用

参数具体规则可以查看官网api介绍

mian.js

在main.js中引用,运行一下 start函数 开启微应用,sandbox: { experimentalStyleIsolation: true }开启沙箱隔离样式

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import store from './store'
import start from '@/micros'

import vueDragging from "./directives/vue-dragging.es5"

const Vue = createApp(App)


Vue.use(store)
Vue.use(router)
Vue.use(ElementPlus, { size: 'small', zIndex: 3000 })
Vue.use(vueDragging)
Vue.mount('#app')

start({ sandbox: { experimentalStyleIsolation: true } })

启动应用,我们会发现和未安装qiankun前没什么区别。

2.2、主应用中构建微应用容器和微应用菜单

主应用App.vue中添加微应用容器

  <div class="app-container">
        <router-view></router-view>
        <!-- 新添加,微应用的容器 -->
        <div id="micro-container"></div>
  </div>

主应用菜单结构如下:

 <div class="app-nav" v-show="appToken">
 
      <router-link class="nav-a-btn" to="/" >主-home</router-link>
      <router-link class="nav-a-btn" to="/master-about" >主-about</router-link>

      <router-link class="nav-a-btn" to="/vue2-micro-app/home" >子-home</router-link>
      <router-link class="nav-a-btn" to="/vue2-micro-app/about" >子-about</router-link>

    </div>

我们可以看到 to中多了上面app.jsactiveRule字段中对应的值(去掉了#号),因为#/vue2-micro-app正是触发启动微应用的条件

改造后的app.vue

<template>
  <div class="app-main">
    <div class="app-nav" v-show="appToken">
      <template v-for="menu in menuList" :key="menu.name">
        <div class="nav-a-btn" :class="{'router-active':menuActive==menu.path}"
          @click="menuChangeRouter(menu)">{{menu.btnName}}</div>
      </template>
    </div>
    <div class="app-content">
      <div class="app-header-content" v-show="appToken">
        <div> {{crumbsRouter}}</div>
        <div>Token: {{appToken}}</div>
        <el-button type="primary" round @click="loginOut">退出登录</el-button>
      </div>
      <div class="app-container">
        <router-view></router-view>
        <!-- 新添加,微应用的容器 -->
        <div id="micro-container"></div>
      </div>

    </div>
  </div>

</template>

<script setup>
import { reactive, toRefs, ref } from "@vue/reactivity";
import { computed, watch, onMounted } from "@vue/runtime-core";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import { actions } from "@/micros"


const Router = useRouter()
const Route = useRoute()
const Store = useStore()

let state = reactive({
  menuList: [
    {
      name: 'home',
      path: '/master-home',
      btnName: '主-home'
    },
    {
      name: 'master-about',
      path: '/master-about',
      btnName: '主-about'
    },
    {
      name: 'micro-home',
      path: '/vue2-micro-app/home',
      btnName: '子-home'
    },
    {
      name: 'micro-about',
      path: '/vue2-micro-app/about',
      btnName: '子-about'
    }
  ],
  menuActive: computed(() => Route.path),
  crumbsRouter: computed(() => {
    let name = ""
    state.menuList.forEach(item => {
      if (state.menuActive == item.path) {
        name = item.btnName
      }
    })
    return name

  }),
  // 从环境变量中取参数
  appId: process.env.VUE_APP_MICRO_ENTRY,
  appToken: computed(() => Store.state.token)
})

watch(() => Route.path, (val, oval) => {
  console.log("监听路由变化", val, oval)
})

onMounted(() => {
  actions.onGlobalStateChange((state) => {
    // state: 变更后的状态; prevState: 变更前的状态
    console.log("主应用观察者:状态改变", state);
    let token = state.globalToken
    Store.commit("setToken", token)
    console.log("kkkkkkkkkkk")

    console.log("jjjjj", Store.state.token)
  })
})

let menuChangeRouter = (row) => {
  state.menuActive = row.name
  // 路由跳转方式
  Router.push({ path: row.path })
  // 跳转方法二 
  //  window.history.pushState({}, '', '/#'+row.path)
}

let loginOut = () => {
  Store.commit("loginOut")
  Router.push('/login')
}

let { menuList, menuActive, crumbsRouter, appId, appToken } = toRefs(state)


</script>


<style lang="scss">
html,
body {
  margin: 0;
  padding: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
$leftWidth: 200px;
.app-main {
  display: flex;
  justify-content: space-between;
  width: 100%;
  height: 100vh;
  background: #f6f7fc;
}
.app-nav {
  width: $leftWidth;
  height: 100%;
  display: flex;
  flex-direction: column;
  box-shadow: 2px 0px 10px 0px rgb(0, 47, 60, 0.2);
  padding: 20px;
  box-sizing: border-box;
  background: #fff;
  z-index: 9;

  .nav-a-btn {
    width: 100%;
    height: 40px;
    line-height: 40px;
    background: #f3f4f5;
    margin-bottom: 10px;
    font-weight: bold;
    cursor: pointer;
  }

  .router-active {
    color: #42b983;
    background: #deeefdde;
  }
}
.app-content {
  width: calc(100% - $leftWidth);
  height: 100%;
  .app-header-content {
    padding: 0 20px;
    width: 100%;
    height: 50px;
    background: #ffffff;
    box-shadow: 0px 0px 8px 0px rgb(0 0 0 / 8%);
    display: flex;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
    border-bottom: 1px solid #ccc;
  }
  .app-container {
    width: 100%;
    height: calc(100% - 50px);
    overflow: auto;
    padding: 20px;
    box-sizing: border-box;
  }
}

/* S 修改滚动条默认样式 */

::-webkit-scrollbar {
  width: 8px;
  background: white;
}

::-webkit-scrollbar-corner,
   /* 滚动条角落 */
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-track {
  /*滚动条的轨道*/
  border-radius: 4px;
}

::-webkit-scrollbar-corner,
::-webkit-scrollbar-track {
  /* 滚动条轨道 */
  background-color: rgba(180, 160, 120, 0.1);
  box-shadow: inset 0 0 1px rgba(180, 160, 120, 0.5);
}

::-webkit-scrollbar-thumb {
  /* 滚动条手柄 */
  background-color: #00adb5;
}

/* E 修改滚动条默认样式 */
</style>

2、微应用子应用搭建

微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用

开始改造子应用

创建qiankun/public-path.js

// 新增:动态设置 webpack publicPath,防止资源加载出错
if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef  
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

在main.js 引入 public-path.js  并改造main.js  如下:

import "./qiankun/public-path"
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './router'
import store from './store'
import MicroActions from './qiankun/qiankun-actions'

Vue.config.productionTip = false

Vue.use(VueRouter)


// 新增:用于保存vue实例
let instance = null;
let router = null;
let microPath = ''


if (window.__POWERED_BY_QIANKUN__) {
  microPath = '/vue2-micro-app'
}



/** * 新增: * 渲染函数 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行 */
function render(props) {

  console.log("子应用render的参数", props)

  // 新增判断,如果是独立运行不执行onGlobalStateChange
  if (window.__POWERED_BY_QIANKUN__) {
    if (props) {
      // 注入 actions 实例
      MicroActions.setActions(props);
    }
    
    // 挂载主应用传入路由实例 用于子应用跳转主应用
    Vue.prototype.$microRouter = props.router

    props.onGlobalStateChange((state, prevState) => {
      // state: 变更后的状态; prev 变更前的状态
      console.log("通信状态发生改变:", state, prevState);
      store.commit('setToken', state.globalToken)
    }, true);
  }

  // router不再是同一个实例,而是每次mount的时候都会新获取一个实例
  router = new VueRouter({
    routes
  })
  // 路由守卫
  router.beforeEach((to, from, next) => {
    if (to.path !== (microPath + '/login')) {
      if (store.state.token) {
        console.log("已经登录 token=", store.state.token)
        if (window.__POWERED_BY_QIANKUN__ && !to.path.includes('vue2-micro-app')) {
          next(microPath + to.path)
        } else {
          next()
        }
      } else {
        console.log("子应用 - 未登录 请登录")
        next(microPath + '/login')
      }
    } else {
      next()
    }
  })


  // 挂载应用  
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount("#micro-app");
}


/** 
* 新增: 
* bootstrap 只会在微应用初始化的时候调用一次,
  下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 
*/
export async function bootstrap() {
  console.log("VueMicroApp bootstraped");
}

/** 
* 新增: 
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 
*/
export async function mount(props) {
  console.log("VueMicroApp mount", props);
  render(props);
}
/** 
* 新增: 
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 
*/
export async function unmount() {
  console.log("VueMicroApp unmount");
  instance.$destroy();
  instance = null;
}

// 新增:独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}


// 原本启动代码
// new Vue({
//   router,
//   store,
//   render: h => h(App)
// }).$mount('#app')

注意:$mount("#micro-app")  挂在的时候修改了根id为了区别主应用和子应用,避免出现问题,对应的入口index.html 的 id="micro-app"

接下来就是改造路由,加入判断是否微应用打开,是就走子应用路径逻辑,否就单独运行

// import Vue from 'vue'
// import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// import store from '../store'

// Vue.use(VueRouter)


// 判断环境是否是微应用打开
let microPath = ''
if (window.__POWERED_BY_QIANKUN__) {
  microPath = '/vue2-micro-app'
}

const routes = [
  {
    path: microPath + '/',
    redirect: microPath + '/home'
  },
  {
    name: 'Home',
    path: microPath + '/home',
    component: Home
  },
  {
    name: 'About',
    path: microPath + '/about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    name: 'login',
    path: microPath + '/login',
    component: () => import(/* webpackChunkName: "about" */ '../views/login.vue')
  }
]

// const router = new VueRouter({
//   routes
// })

// // 路由守卫  移动到main.js中
// router.beforeEach((to, from, next) => {
//   if (to.path !== (microPath + '/login')) {
//     if (store.state.token) {
//       console.log("已经登录 token=",store.state.token)
//       if (window.__POWERED_BY_QIANKUN__ && !to.path.includes('vue2-micro-app')) {
//         next(microPath + to.path)
//       } else {
//         next()
//       }
//     } else {
//       console.log("子应用 - 未登录 请登录")
//       next(microPath + '/login')
//     }
//   } else {
//     next()
//   }
// })

export default routes

路由主要的改动就是每个path都添加了一个microPath变量,检测是否微应用打开

相应的路由守卫也要添加microPath变量,另外微应用的login跳转的时候也要加上microPath判断(在main.js中查看)

设置webpack,在vue.config.js设置打开配置

const path = require("path");

module.exports = {
  devServer: {
    // 监听端口
    port: 8066,
    // 关闭主机检查,使微应用可以被 fetch
    disableHostCheck: true,
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    output: {
      // 微应用的包名,这里与主应用中注册的微应用名称一致
      library: "vue-micro-app",
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: "umd",
      // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
      jsonpFunction: `webpackJsonp_vue-micro-app`,
    },
  },
};

最后启动主应用和子应用项目到浏览器上可以看到,如下页面:

 

好了此时微应用已经正常启动,上面页面已经做了,主应用登录子应用可以同步登录状态,主应用与子应用通信

下一章继续探索微前端的主应用如何与子应用通信,如何使用initGlobalState进行通信,敬请期待 

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

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

相关文章

JavaScript鼠标移动事件及案例

一、鼠标点击事件 1.onclick单击事件 鼠标单击时事件处理函数 <input type"button" id"bt" value"点击"> <script> //找到按钮并设置点击事件 document.getElementById("bt").onclick function (){ //被点击…

kkFileView部署及使用

Windows:kkFileView部署及使用前言部署Web引用普通webVue &#xff1a;需要引入base64.js前言 kkFileView支持主流文档的在线预览服务。官网地址 部署 kkFileView不需要集成&#xff0c;只需要部署到服务器即可。 从 Gitee 上下载最新的压缩包&#xff0c;或者从代码仓库中下…

【Vue+element-ui搭建前端页面】适用于初学者学习

作者有话说&#xff1a; 学习编程我们必不可少的就是做项目&#xff0c;在学习后端的同时&#xff0c;前端知识也是要学习滴&#xff0c;这篇文章将展示如何利用Vue和element-ui搭建前端界面。话不多说&#xff0c;进入主题&#xff01;&#xff01;&#xff01; 目录 首先是…

网页报错“Form elements must have labels”的处理

网页报错“Form elements must have labels”的处理 先给出错误现象源码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width&quo…

uniapp中单选按钮的实现

标签说明&#xff1a; radio-group&#xff1a;单项选择器&#xff0c;内部由多个 <radio> 组成。通过把多个radio包裹在一个radio-group下&#xff0c;实现这些radio的单选。 radio&#xff1a;单选项目 属性说明&#xff1a; change&#xff1a;<radio-group>…

Vue中使用element-ui 给按钮绑定一个单击事件,实现点击按钮就弹出一个dialog对话框

1.需求描述 想要实现点击一个按钮就弹出一个对话框&#xff0c;在对话框中可输入数据进行提交&#xff0c;在点击取消时对话框关闭 2.功能实现 1.创建按钮 在element中把找到按钮的代码放到div里 <el-row><el-button type"primary" plain>新增</el…

Java支付宝沙箱环境支付,SDK接口远程调试【内网穿透】

文章目录1.测试环境2.本地配置3. 内网穿透3.1 下载安装cpolar内网穿透3.2 创建隧道4. 测试公网访问5. 配置固定二级子域名5.1 保留一个二级子域名5.2 配置二级子域名6. 使用固定二级子域名进行访问1.测试环境 MavenSpring bootJdk 1.8 2.本地配置 获取支付宝支付Java SDK,ma…

Vue--》过滤器介绍及其使用方法

目录 过滤器 过滤器的兼容性 私有过滤器和全局过滤器 过滤器的连续调用 过滤器进行传参 过滤器 过滤器的兼容性 注意&#xff1a;Vue3中明确取消了过滤器这个功能&#xff0c;如果想使用只能在Vue2中进行&#xff0c;如果所做的项目是Vue2的话&#xff0c;可以了解一下这…

uni-app开发微信小程序,H5 关于压缩上传图片的问题

文章目录 前言 一、为什么要压缩图片 二、图片压缩方式 1. 微信小程序​​​​​​​ 2. H5 总结 前言 关于微信小程序、H5兼容性问题&#xff0c;今天就压缩以及上传图片做一个可实现方法的简要阐述。 一、为什么要压缩图片​​​​​​​ 在使用uni-app开发小程序及H…

深入了解-微信开发者工具

主要介绍微信开发者工具如何编译小程序代码&#xff0c;如何实现小程序模拟器以及如何调试小程序。 1 简介 虽然在开发语言层面小程序与传统的网页差别不大&#xff1a;是使用JavaScript 脚本语言编写逻辑代码、使用类似于HTML的WXML来描述页面的结构、使用类似于CSS的WXSS来…

B/S架构

目录 一、什么是B/S架构 二、三层架构 三、Active技术 四、网络节点 五、分布式网络计算 六、JavaScript 一、什么是B/S架构 1.B/S架构是软件系统体系结构&#xff0c;是指浏览器-Web服务器(Broswer-Server)&#xff0c;采用三层架构&#xff0c;即表现层、业务逻辑层、数据访问…

十分钟带你入门Chrome插件开发

一、简述 我们所说的chrome插件一般都是指chrome扩展程序&#xff08;Chrome Extension&#xff09;。chrome插件是一个用Web技术开发、用来增强浏览器功能的软件&#xff0c;它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的文件。chrome插件除了Chrome浏览器…

【TFS-CLUB社区 第4期赠书活动】〖Flask Web全栈开发实战〗等你来拿,参与评论,即可有机获得

文章目录❤️‍&#x1f525; 赠书活动 - 《Flask Web全栈开发实战》❤️‍&#x1f525; 编辑推荐❤️‍&#x1f525; 内容提要❤️‍&#x1f525; 赠书活动 → 获奖名单❤️‍&#x1f525; 赠书活动 - 《Flask Web全栈开发实战》 内容简介&#xff1a; 《Flask Web全栈开发…

flex布局 多种方法让两个盒子分布在左右两边

方法一&#xff1a; 一个父盒子里面包含了两个子盒子的&#xff0c;可以用justify-content:space-between属性 <div classparent> <div class"left"></div> <div class"right"></div> </div> .parent { disp…

FreeRTOS(教程非常详细)

概述&#xff1a; 之前写了关于FreeRTOS的部分内容&#xff0c;为了方便阅读&#xff0c;现在给汇总到一起了。全部学习完后&#xff0c;恭喜你对FreeRTOS有了更深的认知。 第一章 FreeRTOS移植到STM32 第二章 FreeRTOS创建任务 第三章 FreeRTOS任务管理 第四章 FreeRTOS消…

【springcloud 微服务】Spring Cloud Ribbon 负载均衡使用策略详解

目录 一、前言 二、什么是Ribbon 2.1 ribbon简介 2.1.1 ribbon在负载均衡中的角色 2.2 客户端负载均衡 2.3 服务端负载均衡 2.4 常用负载均衡算法 2.4.1 随机算法 2.4.2 轮询算法 2.4.3 加权轮询算法 2.4.4 IP地址hash 2.4.5 最小链接数 三、Ribbon中负载均衡策略…

Unity 实现A* 寻路算法

前言 A* 寻路算法是什么 游戏开发中往往有这样的需求&#xff0c;让玩家控制的角色自动寻路到目标地点&#xff0c;或是让 AI 角色移动到目标位置&#xff0c;实际的情况可能很复杂&#xff0c;比如地图上有无法通过的障碍或者需要付出代价&#xff08;时间或其他资源&#x…

XShell免费版的安装配置教程以及使用教程(超级详细、保姆级)

目录 一、 XShell的作用 二、 下载免费版XShell 三、 安装XShell 四、使用XShell连接Linux服务器 一、 XShell的作用 XShell 是一种流行且简单的网络程序&#xff0c;旨在模拟虚拟终端。XShell可以在Windows界面下来访问远端不同系统下的服务器&#xff0c;从而比较好的达到…

11.落地:微服务架构灰度发布方案

前置知识 1.nacos 服务注册与发现 2.本地负载均衡器算法 3.gateway 网关 4.ThreadLocal 1.什么是灰度发布&#xff1f; 2.什么是灰度策略? 3.灰度发布落地方案有哪些 4.灰度发布架构设计原理 nginxlua&#xff1f; 5.如何基于GateWayNacos构建灰度环境 6.GateWay负载均衡…

【云原生 • Kubernetes】认识 k8s、k8s 架构、核心概念点介绍

目录 一、Kubernetes 简介 二、Kubernetes 架构 三、Kunbernetes 有哪些核心概念&#xff1f; 1. 集群 Cluster 2. 容器 Container 3. POD 4. 副本集 ReplicaSet 5. 服务 service 6. 发布 Deployment 7. ConfigMap/Secret 8. DaemonSet 9. 核心概念总结 一、Kubern…