手动创建 vue2 ssr 开发环境

news2024/9/22 10:06:46

本文和个人博客同步发表
更多优质文章查看个人博客

前言

手动搭建 vue ssr 一直是一些前端开发者的噩梦,因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说,很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致这样的原因之一无外乎学习成本高,软件复杂等。这也是有些前端开发者直接拥抱nuxt.js的部分原因。这篇博客使用vue2,以步骤为主,来展示如何创建完整ssr开发环境。

主要参考文章

  1. vue2 ssr 中文官网
  2. webpack 中文官网
  3. webpack-dev-middleware
  4. webpack-hot-middleware
  5. webpack tapable
  6. memory-fs
  7. express
  8. vue2 ssr 官方参考案例

构建ssr所需依赖包

{
  "devDependencies": {
    "chokidar": "^3.5.3",
    "css-loader": "^6.7.3",
    "memory-fs": "^0.5.0",
    "vue-loader": "^15.9.8",
    "vue-style-loader": "^4.1.3",
    "webpack": "^5.54.0",
    "webpack-dev-middleware": "^5.2.1",
    "webpack-hot-middleware": "^2.25.1",
    "webpack-node-externals": "^3.0.0"
  },
  "scripts": {
    "dev": "node ./server/index.cjs"
  },
  "dependencies": {
    "express": "^4.18.2",
    "vue": "^2.6.14",
    "vue-router": "^3.5.2",
    "vue-server-renderer": "^2.6.14",
    "vue-template-compiler": "^2.6.14",
    "vuex": "^3.6.2",
    "vuex-router-sync": "^5.0.0"
  }
}

目录结构

需要创建如下图的目录结构,方能进行后面的代码编写
请添加图片描述

ssr是如何生成的?

了解完目录架构之后,首先需要知道ssr是如何生成的是至关重要的,只有这样我们才了解后续通过什么样的操作来构建ssr。首先看一张官方给出的构建图。
请添加图片描述

从图中可以看出,想要实现ssr,必选通过webpack构建生成的服务端bundle文件和客户端bundle文件,服务端bundle在bundle renderer的作用下生成html字符串,并发送给浏览器端并且和客户端bundle一起作用下激活,最终实现ssr。代码中创建html字符串和发送到浏览器的实现如下所示

let devServerPromise = devServer((serverBundle, options) => {
    // 服务端生成的bundele和客户端生成的clientManifest结合,并返回renderer
    renderer = createBundleRenderer(serverBundle, Object.assign(options, {
        runInNewContext: false,
    }))
});


ROUTER.get('*', (req, res) => {
    const context = {
        url: req.url
    }
    devServerPromise.then((random) => {

        // 将 Vue 实例渲染为字符串,发送给客户端
        renderer.renderToString(context).then(html => {
            res.send(html)
        }).catch(err => {
            console.log('err',req.url,err)
        })
    })
})

其实在构建图中还少一个关键点,如下所示
请添加图片描述

webpack在编译客户端时会生成客户端构建清单(clientManifest),清单里面的内容其实是当html字符串在浏览器中解析时要获取的资源内容(也可简单理解为client bundle),当解析html时根据内容去请求client bundle。

好!到目前为止,实现vue ssr思路已经很清晰了。在这里简单梳理一下。

需要
需要
依赖
依赖
生成
生成
生成
生成html字符串
webapck
clientBundle
renderToString函数
serverBundle
clientManifest

根据上面的图可知,想要实现ssr,就是需要serverBundle、clientBundle、clientManifest文件。而这三种文件又是通过webpack生成的。所以现在的要面临的问题就是配置webpack,生成这三种文件,然后通过renderToString函数实现ssr。话不多说开始配置。

配置 webpack

在配置webpack之前,需要先创建要打包的代码、app.js、entry-client.js和entry-server.js。创建这些内容的作用是为后续webpack打包做准备。

1. 创建UAC(Universal Application Code)相关文件

请添加图片描述

在ssr-demo文件夹下的client文件夹中,分别创建router、store和views文件夹和相关内容。

1.1 router文件夹和相关内容


// 路径 client/router/index.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router);

export function createRouter(){
    return new Router({
        mode:'history',
        routes:[
            {path:'/',component: () => import('../views/index.vue')},
        ]
    })
}

1.2 store文件夹和相关内容


// 路径 client/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export function createStore(){
    return new Vuex.Store({
        state:{},
        actions:{},
        mutations:{}
    })
}

1.3 view文件夹和相关内容


<!-- 路径 client/views/index.vue -->
<template>
  <div>
    kinghiee ssr test
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

2. 创建app.js文件

请添加图片描述


// 路径 client/app.js
import Vue from 'vue';
import App from './App.vue';
import { createRouter } from './router/index';
import { createStore } from './store';
import { sync } from 'vuex-router-sync';

// 简单工厂模式创建vue实例
export function createApp() {
    const router = createRouter();
    const store = createStore();

    sync(store, router);

    const app =  new Vue({
        router,
        store,
        render: h => h(App),
    })

    return {
        app,
        router,
        store
    }
};

app.js文件的作用: 使用简单工厂模式,创建vue实例,为每个请求创建新的应用程序实例,避免状态单例。更加详细解释请查看官网;

3. 创建entry-client.js文件

请添加图片描述


// 路径 client/entry-client.js
import { createApp } from './app';
import Vue from 'vue';

const { app, router, store } = createApp();

Vue.mixin({
    /**
     * 路由更新触发组件内异步获取数据方法
     * @param {*} to
     * @param {*} from
     * @param {*} next
     */
    beforeRouteUpdate(to, from, next) {
        const { asyncData } = this.$options
        if (asyncData) {
            asyncData({
                store: this.$store,
                route: to
            }).then(next).catch(next)
        } else {
            next()
        }
    }
});

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

router.onReady(() => {
    router.beforeResolve((to, from, next) => {
        const matched = router.getMatchedComponents(to);
        const prevMatched = router.getMatchedComponents(from);

        let diffed = false;
        const activated = matched.filter((c, i) => {
            return diffed || (diffed = (prevMatched[i] !== c))
        })
        if (!activated.length) {
            return next();
        }
        Promise.all(activated.map(c => {
            if (c.asyncData) {
                return c.asyncData({ store, route: to })
            }
        })).then(() => {
            next();
        }).catch(next)
    })
    app.$mount('#app')
})

4. 创建entry-server.js文件

请添加图片描述

// 路径 client/entry-server.js
import { createApp } from './app'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()

    router.push(context.url)

    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      // 对所有匹配的路由组件调用 `asyncData()`
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 在所有预取钩子(preFetch hook) resolve 后,
        // 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
        context.state = store.state

        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

注:entry-client.js和entry-server.js文件以及文件中为什么这么写在官网中都能找的到,这里只说明搭建步骤 官网

相关代码创建完毕之后,client文件夹内容大致如下

请添加图片描述

到此为止,webpack打包前期的准备工作已经结束。接下来开始配置webpack打包

5. webpack配置

请添加图片描述
在ssr webpack打包配置中分为客户端和服务端配置。客户端打包配置主要生成后面用到的客户端bundle和clientManifest,而服务端打包配置主要生成后面用到的服务端bundle,这三种文件正是ssr所需的关键文件。

5.1 客户端打包配置

// 路径 build/webpack.client.dev.js
const { resolve: RESOLVE } = require('path');
const WEBPACK = require('webpack');
const { VueLoaderPlugin: VUELOADERPLUGIN } = require('vue-loader');
const VUESSRCLIENTPLUGIN = require('vue-server-renderer/client-plugin')

module.exports = {
    mode: 'development',
    entry: { app: RESOLVE(__dirname, '../client/entry-client.js') },
    output: {
        path: RESOLVE(__dirname, '../dist'),
        filename: 'src/[name].[contenthash:6].js',
        publicPath: '/dist/'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.css$/i,
                use: ["vue-style-loader", "css-loader"],
            },
        ]
    },
    resolve: {
        extensions: ['.js', '.ts', '.vue', '.json'],
        alias: {
            '@client':RESOLVE(__dirname,'../client')
        }
    },
    plugins: [
        new VUELOADERPLUGIN(),
        new VUESSRCLIENTPLUGIN({
            filename: 'src/vue-ssr-client-manifest.json'
        })
    ]
}

5.2 服务端打包配置


// 路径 build/webpack.server.dev.js
const { resolve: RESOLVE } = require('path');
const { VueLoaderPlugin: VUELOADERPLUGIN } = require('vue-loader');
const VUESERVERPlUGINSSR = require('vue-server-renderer/server-plugin')
const NODEEETERNALS = require('webpack-node-externals');

module.exports = {
    target: 'node',
    devtool: 'eval-cheap-source-map',
    entry: RESOLVE(__dirname, '../client/entry-server.js'),
    output: {
        path: RESOLVE(__dirname, '../dist'),
        filename: 'server-bundle.js',
        libraryTarget: 'commonjs2'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.css$/i,
                use: ["vue-style-loader", "css-loader"],
            },
        ]
    },
    externalsPresets: { node: true }, // in order to ignore built-in modules like path, fs, etc.
    externals: [NODEEETERNALS()], // in order to ignore all modules in node_modules folder
    plugins: [
        new VUELOADERPLUGIN(),
        new VUESERVERPlUGINSSR({
            filename: 'src/vue-ssr-server-bundle.json'
        })
    ]
}

webpack 客户端和服务端都已配置好了,那如何生成相应的三种文件呐?其实生成这三种文件需要在webpack编译阶段,而对于配置开发环境来说,一般还用到热更新和webpack dev中间件,所以webpack编译和热更新常在一起出现。目前为止该准备的都已经到位了,现在就可以开始webpack编译和配置热更新操作了。

webpack编译和配置热更新

在server文件夹内创建如下文件夹和文件
请添加图片描述
请添加图片描述

1. webpack编译

本博客给出的代码示例和官方的示例组织上有不同的地方,但功能上一样。本博客按照功能的不同对代码进行了合理的拆分和封装,而不是把全部功能写到一个函数下面。这样做的目的是关注点单一、功能单一、便于开发和维护。

1.1 编译客户端

// 路径 dev/clientCompile.cjs
let webpack = require('webpack');
let path = require('path');

module.exports = function clientCompile(clientConfig, clientManifestCb) {

    // 向客户端 webpack 修改配置
    clientConfig.entry.app = [ 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=2000&reload=true', clientConfig.entry.app ];
    clientConfig.output.filename = '[name].[contenthash:6].js';
    clientConfig.plugins.push(
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoEmitOnErrorsPlugin(),
    ); 

    // 编译客户端 webpack 配置
    let clientCompiler = webpack(clientConfig); // 获取 compiler 实例

    let devMiddleware = require('webpack-dev-middleware')(clientCompiler, {
        publicPath: clientConfig.output.publicPath,
        serverSideRender: true,
        stats: {//可选
            colors: true,
            modules: true,
        },
    });

    // done是AsyncSeriesHook类型钩子
    clientCompiler.hooks.done.tap('done', stats => {
        stats = stats.toJson({stats:'errors-warnings'});

        // 如果客户端编译完毕,有错误或者警告会打印到控制台
        stats.errors.forEach(err => console.error(err));
        stats.warnings.forEach(err => console.warn(err));
        
        // 有错误后续不生成 manifest 文件
        if (stats.errors.length) return;
        console.log('\n客户端更新...\n');
        let manifestContent = devMiddleware.context.outputFileSystem.readFileSync(
            path.resolve(clientConfig.output.path, 'src/vue-ssr-client-manifest.json'),
            'utf-8'
        );
        
        clientManifestCb(JSON.parse(manifestContent));
    });
        
    let hotMiddleware = require('webpack-hot-middleware')(clientCompiler); 
    
    return {
        devMiddleware,
        hotMiddleware
    }
}

如上代码所示,把编译客户端的代码写到一个文件内,并导处客户端编译函数,在其他地方用。

1.2 编译服务端

// 路径 dev/serverCompile.cjs
let webpack = require('webpack');
let path = require('path');
const MFS = require('memory-fs');

module.exports = function serverCompile(
    serverConfig,
    serverBundleCb
) {
    let serverCompiler = webpack(serverConfig); 
    let mfs = new MFS();
    serverCompiler.outputFileSystem = mfs; // 把 webpack 默认的普通文件系统更换为内存文件系统

    serverCompiler.watch({ ignored: /node_modules/, }, (err, stats) => {
        if (err) throw err;
        stats = stats.toJson();
    
        // 有错误后续不执行
        if (stats.errors.length) return;
    
        console.log('\n服务端更新...\n');
    
        // 获取服务端bundle文件路径
        let bundlePath = path.resolve(
            serverConfig.output.path,
            'src/vue-ssr-server-bundle.json'
        );

        serverBundleCb(JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')))
    });
}

写完客户端和服务端编译后,需要把函数导出来在后面的地方使用。代码如下

2. webpack编译和热更新配置

dev.cjs文件如下

// 路由 router/dev.cjs
const SERVER = require('express');
const ROUTER = SERVER.Router();
const FS = require('fs');
const PATH = require('path');
let clientConfig = require('../../build/webpack.client.dev');
let serverConfig = require('../../build/webpack.server.dev'); 
let templatePath =  PATH.resolve(__dirname, '../server.template.html');
let { createBundleRenderer } = require('vue-server-renderer')
let serverCompile = require('../dev/serverCompile.cjs');
let clientCompile = require('../dev/clientCompile.cjs');
let tempWatch = require('../dev/tempWatch.cjs');

let renderer;

const devServer = (cb) => {
    let clientManifest, serverBundle, readyResolve, templateContent;

    templateContent = FS.readFileSync(templatePath, 'utf-8');

    let readyPromise = new Promise(resolve => readyResolve = resolve );

    // 更新客户端和服务端内容
    let updateClientAndServer = () => {
        
        // 只有构建清单文件都存在时,执行更新操作
        if(clientManifest && serverBundle) {
            readyResolve(); // 把promise resolve掉

            cb(serverBundle, {
                template: templateContent,
                clientManifest
            })
        }
    };

    // 监听模板文件
    tempWatch(templatePath, () => {
          updateClientAndServer();
    });

    // 客户端 编译
    let { devMiddleware, hotMiddleware} = clientCompile(
        clientConfig, (clientManifestContent) => {
        clientManifest = clientManifestContent;
        updateClientAndServer();
    })

    ROUTER.use(devMiddleware);
    ROUTER.use(hotMiddleware); 

    // 服务端 编译
    serverCompile(serverConfig, (serverBundleContent) => {
        serverBundle = serverBundleContent;
        updateClientAndServer();
    })

    return readyPromise;
}

let devServerPromise = devServer((serverBundle, options) => {
    renderer = createBundleRenderer(serverBundle, Object.assign(options, {
        runInNewContext: false,
    }))
});

在devServer函数中,分别使用clientCompile函数,编译客户端。在回调函数把生成的clientManifestContent内容赋值给clientManifest,然后通知updateClientAndServer函数完成后续内容。同时clientCompile函数也返回了两个中间件并放入use函数中,完成后续的热更新和webpack dev server. 和clientCompile函数类似,serverCompile函数,它也是在回调函数中把生成的serverBundleContent赋值给serverBundle,并通知updateClientAndServer函数完成其他内容。在updateClientAndServer函数中,当clientManifest和serverBundle内容都有时,就可以把promise resolve掉,进而可以调用renderToString函数生成html字符串,发送给浏览器,最后实现ssr。

在devServer函数中还是用了tempWatch,该函数的作用是当模板文件发生变化时,更新相关内容。代码如下

// 路径 dev/tempWatch.cjs
let fs = require('fs');
let chokidar = require('chokidar');

module.exports = function tempWatch(templatePath, watchCb) {
    
    // 监听模板html文件 change
    chokidar.watch(templatePath).on('change', () => {
        console.log('模板更新中...');
        templateContent = fs.readFileSync(templatePath, 'utf-8');
        console.log('模板更新成功!');

        // 更新模块
        watchCb();
    });
}

到此为止,vue ssr的配置和生成基本结束。但是现在通过浏览器还是访问不了,还需要最后一步配置服务器

配置服务器提供访问

在dev.cjs中添加路由配置,然后导处路由,在程序入口处使用。

// 路径 router/dev.cjs
const SERVER = require('express');
const ROUTER = SERVER.Router();
const FS = require('fs');
const PATH = require('path');
let clientConfig = require('../../build/webpack.client.dev');
let serverConfig = require('../../build/webpack.server.dev'); 
let templatePath =  PATH.resolve(__dirname, '../server.template.html');
let { createBundleRenderer } = require('vue-server-renderer')
let serverCompile = require('../dev/serverCompile.cjs');
let clientCompile = require('../dev/clientCompile.cjs');
let tempWatch = require('../dev/tempWatch.cjs');

let renderer;

const devServer = (cb) => {
    let clientManifest, serverBundle, readyResolve, templateContent;

    templateContent = FS.readFileSync(templatePath, 'utf-8');

    let readyPromise = new Promise(resolve => readyResolve = resolve );

    // 更新客户端和服务端内容
    let updateClientAndServer = () => {
        
        // 只有构建清单文件都存在时,执行更新操作
        if(clientManifest && serverBundle) {
            readyResolve(); // 把promise resolve掉

            cb(serverBundle, {
                template: templateContent,
                clientManifest
            })
        }
    };

    // 监听模板文件
    tempWatch(templatePath, () => {
        updateClientAndServer();
    });

    // 客户端 编译
    let { devMiddleware, hotMiddleware} = clientCompile(
        clientConfig, (clientManifestContent) => {
        clientManifest = clientManifestContent;
        updateClientAndServer();
    })

    ROUTER.use(devMiddleware);
    ROUTER.use(hotMiddleware); 

    // 服务端 编译
    serverCompile(serverConfig, (serverBundleContent) => {
        serverBundle = serverBundleContent;
        updateClientAndServer();
    })

    return readyPromise;
}

let devServerPromise = devServer((serverBundle, options) => {
    renderer = createBundleRenderer(serverBundle, Object.assign(options, {
        runInNewContext: false,
    }))
});


ROUTER.get('*', (req, res) => {
    const context = {
        url: req.url
    }
    devServerPromise.then(() => {
        renderer.renderToString(context).then(html => {
            res.send(html)
        }).catch(err => {
            console.log('err',req.url,err)
        })
    })
})

module.exports = ROUTER;

在程序入口处使用该路由


// 路径 server/index.cjs
const SERVER = require('express')();
const SSRROUTER = require('./router/dev.cjs');
const PORT = 8000;

SERVER.use(SSRROUTER);

SERVER.listen(PORT,() => {
    console.log(`app listening at port ${PORT}`);
});

最后输入npm run dev启动项目,结果如下

请添加图片描述

注: 配置ssr的过程有点繁琐,如果途中有配置错的地方可以查看我的github ssr demo

如果博客中有什么不理解的或者错误内容,欢迎指出,及时更正

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

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

相关文章

NCHW - NHWC - CHWN 排列

TensorFlow有两种数据格式NHWC和NCHW,默认的数据格式是NHWC,可以通过参数data_format指定数据格式。这个参数规定了 input Tensor 和 output Tensor 的排列方式。 1、data_format 设置为 “NHWC” 时,排列顺序为 [batch, height, width, channels] 设置为 “NCHW” 时,排…

Linux中常用命令汇总三

Linux中常用命令汇总二地址&#xff1a;https://blog.csdn.net/u011837804/article/details/1289972501、用户组管理类每个用户都有一个用户组&#xff0c;系统可以对一个用户组中的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同&#xff0c;如Linux下的用户属…

MIPI CSI 进一步理解

CSI&#xff08;Camera Serial Interface&#xff09;定义了摄像头外设与主机控制器之间的接口&#xff0c;旨在确定摄像头与主机控制器在移动应用中的标准。 CSI关键词描述 缩写 解释 CCI Camera Control Interface&#xff08;物理层组件&#xff0c;通常使用I2C或I3C进行通…

显示技术之器件---LED、Mini LED、Micro LED、OLED、LCD、SMD、SMT

显示技术之基础—半导体 1、LED 1.1 LED(Light Emitting Diode) 即发光二极管&#xff0c;是一种半导体固体发光器件&#xff0c;它是利用固体半导体芯片作为发光材料&#xff0c;当两端加上正向电压&#xff0c;半导体中的载流子发生复合引起光子发射而产生光。LED由含镓&a…

基于SpringBoot的卓越导师双选系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

九龙证券|机构密集调研银行业!这些上市银行尤其受捧,关注两大变量

2023年开年以来&#xff0c;组织连续了上一年对上市银行的调研热情。 Wind数据显现&#xff0c;到现在&#xff0c;年内已有9家银行承受637家组织调研&#xff0c;参加调研的组织数较上一年同期添加近一成。 其间&#xff0c;7家被调研银行均处江浙区域。上一年以来&#xff0…

Docker 学习笔记

概述 1. 什么是 Docker&#xff1f; Docker 是一个应用容器平台&#xff0c;管理项目中用到的所有环境&#xff08;MySQL、Redis…&#xff09; 2. Docker 和虚拟机的区别 虚拟机是携带操作系统的&#xff0c;本身很小的应用程序因为携带了操作系统而变得十分笨重&#xff0…

WebRTC paced sender

文章目录4.1 pacer创建4.2 音视频数据包发送4.3 webrtc::PacketRouter4.4 Pacer 媒体数据发送控制4.5 pacer 中的码率探测paced sender通常简称为pacer&#xff0c;其是WebRTC RTP栈的一部分&#xff0c;用于平滑发送到网络上的数据流包&#xff0c;考虑一个帧率为60fps带宽为1…

linux高级命令之线程的注意点

线程的注意点学习目标能够说出线程的注意点1. 线程的注意点介绍线程之间执行是无序的主线程会等待所有的子线程执行结束再结束线程之间共享全局变量线程之间共享全局变量数据出现错误问题2. 线程之间执行是无序的import threading import timedeftask():time.sleep(1)print(&qu…

一句话解读《持续交付》核心能力

​DevOps 是基于持续交付的软件工程。DevOps的核心知识体系在DevOps 四书。持续交付主要是指应用软件集成交付环节&#xff0c;通过配置管理、构建与持续集成、测试管理、部署与发布管理、环境管理、数据管理和度量管理领域的能力建设和工程实践保证软件持续顺畅高质量的对用户…

JavaWeb9-volatile解决内存可见性和指令重排序问题

目录 1.解决内存可见性问题 2.解决指令重排序问题 3.volatile缺点 4.特使使用场景 volatile&#xff08;易变的&#xff0c;易挥发的&#xff0c;不稳定的&#xff09;可以解决内存可见性和指令重排序的问题。 1.解决内存可见性问题 代码在写入 volatile 修饰的变量时&am…

【Linux】自定义生成Kickstart(system-config-kickstart)

文章目录前言一、安装二、运行三、配置3.1 基本配置3.2 安装方法3.3 引导装载程序选项3.4 分区信息3.5 网络配置3.6 验证3.7 防火墙配置3.8 显示配置3.9 软件包选择3.10 预安装脚本3.11 安装后脚本3.12 保存与查看四、总结前言 本文简单介绍下system-config-kickstart的使用&a…

AMQP协议介绍

这篇文章主要介绍AMQP 0-9-1 协议&#xff0c;是RabbitMQ支持的协议之一&#xff0c;理解AQMP对于使用和理解RabbitMQ也很有帮助。 AMQP 0-9-1&#xff08;高级消息队列协议&#xff09;是一种消息传递协议&#xff0c;它使客户端应用程序能与消息中间件进行通信。消息中间件接…

Alibaba Arthas

Alibaba Arthas 基于arthas 3.4.6 Arthas是Alibaba开源的Java诊断工具 可以用来解决 查看class 的加载路径&#xff0c;排除ClassLoader 双向委派存在的问题 程序在线反编译&#xff0c;与热更新 监控到JVM的实时运行状态&#xff08;线程状态&#xff0c;程序热点&#x…

敏感词之 DFA 算法

敏感词之 DFA 算法 常用算法 遍历匹配 将输入的词语&#xff0c;与词库中的敏感词逐个字符遍历&#xff0c;对比是否包含 优点&#xff1a;思路简单&#xff0c;易于实现&#xff08;KMP 算法&#xff0c;Brute-Force 算法&#xff09; 缺点&#xff1a;当词库数目非常大时…

uniapp自定义验证码输入框,隐藏光标

一. 前言 先看下使用场景效果图&#xff1a; 点击输入框唤起键盘&#xff0c;蓝框就相当于input的光标&#xff0c;验证码输入错误或者不符合格式要求会将字体以及边框改成红色提示&#xff0c;持续1s&#xff0c;然后清空数据&#xff0c;恢复原边框样式&#xff1b;5位验证…

【Kubernetes】【十二】Pod详解 Pod调度

Pod调度 ​ 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达…

Prometheus监控案例-tomcat、mysql、redis、haproxy、nginx

监控tomcat tomcat自身并不能提供监控指标数据&#xff0c;需要借助第三方exporter实现&#xff1a;https://github.com/nlighten/tomcat_exporter 构建镜像 基于tomcat官方镜像&#xff0c;重新制作一个镜像&#xff0c;将tomcat-exporter和tomcat整合到一起。Ddockerfile如…

【安全知识】——如何绕过cdn获取真实ip

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a; 现在的样子是你想要的吗&#xff1f;cdn简单来说就是…

商标侵权行为的种类有哪些

商标侵权行为的种类有哪些 1、商标侵权行为的种类有以下七种&#xff1a; (1)未经商标注册人的许可&#xff0c;在同一种商品上使用与其注册商标相同的商标的; (2)未经商标注册人的许可&#xff0c;在同一种商品上使用与其注册商标近似的商标&#xff0c;或者在类似商品上使…