当前位置: 首页 > news >正文

手动创建 vue2 ssr 开发环境

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

前言

手动搭建 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结合,并返回rendererrenderer = 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.$optionsif (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.stateresolve(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 folderplugins: [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文件 changechokidar.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

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

相关文章:

手动创建 vue2 ssr 开发环境

本文和个人博客同步发表 更多优质文章查看个人博客 前言 手动搭建 vue ssr 一直是一些前端开发者的噩梦&#xff0c;因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说&#xff0c;很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致…...

RHCE-操作系统刻录工具

Windows 1.准备材料。 一个可用的windows操作系统(下载的时候用迅雷比较快) MSDN, 我告诉你 - 做一个安静的工具站 大于等于8G的U盘 想要安装的系统光盘镜像 U盘烧录工具&#xff08;软碟通&#xff09; UltraISO软碟通中文官方网站 - 光盘映像文件制作/编辑/转换工具 …...

PHP面向对象01:面向对象基础

PHP面向对象01&#xff1a;面向对象基础一、关键字说明二、技术实现1. 定义类2. 类成员三、 访问修饰限定符1. public2. protected3. private4. 空修饰限定符四、类内部对象五、构造和析构1. 构造方法2. 析构方法六、范围解析操作符1. 访问类常量2. 静态成员3. self关键字七、类…...

《爆肝整理》保姆级系列教程python接口自动化(十八)--重定向(Location)(详解)

简介   在实际工作中&#xff0c;有些接口请求完以后会重定向到别的url&#xff0c;而你却需要重定向前的url。URL主要是针对虚拟空间而言&#xff0c;因为不是自己独立管理的服务器&#xff0c;所以无法正常进行常规的操作。但是自己又不希望通过主域名的二级目录进行访问&…...

MySQL的索引、视图

什么是索引模式(schema)中的一个数据库对象 在数据库中用来加速对表的查询 通过使用快速路径访问方法快速定位数据,减少了磁盘的I/O 与表独立存放&#xff0c;但不能独立存在&#xff0c;必须属于某个表 由数据库自动维护&#xff0c;表被删除时&#xff0c;该表上的索引自动被…...

【JavaWeb】网络层协议——IP协议

目录 IP协议结构 IP地址管理 特殊IP 解决IP地址不够用 动态分配IP地址 NAT网络地址转换 IPV6 IP协议结构 版本&#xff1a;就是IP协议的版本号。目前只有 4 和 6。这里介绍的是IPV4 首部长度&#xff1a;单位是4字节。于TCP首部长度完全一致&#xff0c;也是可变的&…...

【Python学习笔记】41.Python3 多线程

前言 本章介绍Python的多线程。 Python3 多线程 多线程类似于同时执行多个不同程序&#xff0c;多线程运行有如下优点&#xff1a; 使用线程可以把占据长时间的程序中的任务放到后台去处理。用户界面可以更加吸引人&#xff0c;比如用户点击了一个按钮去触发某些事件的处理…...

Windows 版本ffmpeg编译概述

在使用ffmpeg过程当中&#xff0c;ffmpeg在Linux(包括mac,android)编译非常容易,直接configure,make即可&#xff0c;Android需要交叉编译,在windows就比较麻烦&#xff0c;庆幸的是ffmpeg官方提供已编译好Windows版本的二进制库&#xff08;http://ffmpeg.org/download.html#b…...

NETCore下CI/CD之自动化测试 (详解篇)

NETCore下CI/CD之自动化测试 &#xff08;详解篇&#xff09; 目录&#xff1a;导读 前言 安装JDK 安装 Tomcat 首先&#xff0c;我们需要指定 Tomcat.PID 进程文件&#xff0c;进入 /usr/local/tomcat/bin&#xff0c;编辑文件 增加 tomcat 账户并赋予权限 防止Jeknins…...

Hoeffding不等式剪枝方法

在基于物品的协通过滤算法中&#xff0c;当用户历史行为数据有很多时&#xff0c;对计算会有很大挑战&#xff0c;对此可以使用剪枝对数据进行化简来达到减少计算量。     不是每个物品对都需要进行增量计算。对于两个物品的相似度&#xff0c;每次更新都能够得到一个新的相…...

【算法】数组中的重复数字问题

数组中的重复数据 数组中重复的数字 错误的集合 以第三题&#xff0c;错误的集合为例 对于这样的问题&#xff0c;有很简单的解决方式&#xff0c;先遍历一次数组&#xff0c;用一个哈希表记录每个数字出现的次数&#xff0c;然后遍历一次 [1…N]&#xff0c;看看那个元素重…...

数值方法笔记2:解决非线性方程

1. 不动点定理及其条件验证2. 收敛阶、收敛检测与收敛加速2.1 如何估计不动点迭代的收敛阶xk1g(xk){x}_{{k}1}{g}\left({x}_{{k}}\right)xk1​g(xk​)2.2 给定精度的情况下&#xff0c;如何预测不动点迭代需要迭代的次数2.3 如何加快收敛的速度2.4 停止不定点迭代的条件2.5 不动…...

基于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 浏…...

软件体系结构(期末复习)

文章目录软件体系结构软件体系结构概论软件体系结构建模软件体系结构风格统一建模语言基于体系结构的软件开发软件体系结构 软件体系结构概论 软件危机是指计算机软件的开发和维护过程中遇到的一系列严重问题。 软件危机的表现: 软件危机的原因: 软件工程的基本要素&#xf…...

[vue3] pinia的基本使用

使用Pinia npm install piniastore文件里index.js import { createPinia } from piniaconst pinia createPinia()export default piniamain.js导入并引用 import { createApp } from vue import App from ./App.vue import pinia from ./storescreateApp(App).use(pinia).m…...

进程和线程详解

在计算机领域中&#xff0c;进程和线程是非常重要的概念。了解进程和线程是软件开发的基础&#xff0c;也是计算机科学教育中的一部分。本文将介绍进程和线程的概念、区别和应用。 一、什么是进程 在计算机科学中&#xff0c;进程是正在执行的程序实例。一个进程可以由一个或…...

《刀锋》读书笔记

刀锋&#xff08;毛姆长篇作品精选&#xff09;毛姆50个笔记点评认为好看的确是完美的结局。《刀锋》里面的人每个人都以自己的方式生活着。艾略特的势利&#xff0c;拉里的自由&#xff0c;伊莎贝尔的现实&#xff0c;苏珊的清醒&#xff0c;索菲的堕落&#xff0c;至于“我”…...

nginx中的ngx_modules

ngx_modules和ngx_module_names是configure脚本生成的&#xff0c;是在objs/ngx_modules.c文件中与其生成的相关的脚本文件相关的变量在options脚本中定义了objs目录的变量NGX_OBJSobjs在init脚本中定义的最终存放ngx_modules的文件 NGX_MODULES_C$NGX_OBJS/ngx_modules.c2. 处…...

设计模式之访问者模式

什么是访问者模式 访问者模式提供了一个作用于某对象结构中的各元素的操作表示&#xff0c;他使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。     访问者模式主要包含以下几个角色&#xff1a;         Vistor(抽象访问者)&#xff1a;为对象结…...

Go项目(三)

文章目录用户微服务表结构查表web 服务跨域问题图形验证码短信用户注册服务中心注册 grpc 服务动态获取端口负载均衡配置中心启动项目小结用户微服务 作为系统的第一个微服务&#xff0c;开发的技术点前面已经了解了一遍&#xff0c;虽有待补充&#xff0c;但急需实战这里主要…...

CTK学习:(一)编译CTK

CTK插件框架简介 CTK Plugin Framework是用于C++的动态组件系统,以OSGi规范为模型。在此框架下,应用程序由不同的组件组成,遵循面向服务的方法。 ctk是一个开源项目,Github 地址:https://github.com/commontk。 源码地址commontk/CTK: A set of common support code for…...

15种NLP数据增强方法总结与对比

数据增强的方法 数据增强&#xff08;Data Augmentation&#xff0c;简称DA&#xff09;&#xff0c;是指根据现有数据&#xff0c;合成新数据的一类方法。毕竟数据才是真正的效果天花板&#xff0c;有了更多数据后可以提升效果、增强模型泛化能力、提高鲁棒性等。然而由于NLP…...

Python每日一练(20230219)

目录 1. 循环随机取数组直到得出指定数字&#xff1f; 2. 旋转链表 3. 区间和的个数 1. 循环随机取数组直到得出指定数字&#xff1f; 举个例子&#xff1a; 随机数字范围&#xff1a;0~100 每组数字量&#xff1a;6&#xff08;s1,s2,s3,s4,s5,s6&#xff09; 第二轮开始随…...

vTESTstudio - VT System CAPL Functions - VT7001

vtsSerialClose - 关闭VT系统通道的串行端口功能&#xff1a;关闭由系统变量命名空间指定的VT系统通道的串行端口。Target&#xff1a;目标通道变量空间名称&#xff0c;例如&#xff1a;VTS::ECUPowerSupply返回值&#xff1a;0&#xff1a;成功重置目标通道最大和最小值-1&am…...

「可信计算」论文初步解读

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…...

CSDN 算法技能树 蓝桥杯-基础 刷题+思考总结

切面条-蓝桥杯-基础-CSDN算法技能树https://edu.csdn.net/skill/algorithm/algorithm-530255df51be437b967cbc4524fe66ea?category188 目录 切面条 大衍数列 门牌制作 方阵转置 微生物增殖 成绩统计 星系炸弹 判断闰年的依据: 特别数的和 *日志统计*&#xff08;双指…...

信小程序点击按钮绘制定制转发分享图

1. 说明 先上代码片断分享链接&#xff1a; https://developers.weixin.qq.com/s/vl3ws9mA72GG 使用 painter 画图 按钮传递定制化信息 效果如下&#xff1a; 2. 关键代码说明 文件列表如下&#xff1a; {"usingComponents": {"painter": "/com…...

Python自动化测试-使用Pandas来高效处理测试数据

Python自动化测试-使用Pandas来高效处理测试数据 目录&#xff1a;导读 一、思考 二、使用pandas来操作Excel文件 三、使用pandas来操作csv文件 四、总结 一、思考 1.Pandas是什么&#xff1f; 功能极其强大的数据分析库可以高效地操作各种数据集 csv格式的文件Excel文件H…...

语音增强学习路线图Roadmap

语音增强算是比较难的研究领域&#xff0c;从入门到精通有很多台阶&#xff0c;本文介绍一些有价值的书籍&#xff0c;值得反复阅读。主要分为基础类和进阶类书籍&#xff0c;大多都是理论和实践相结合的书籍&#xff0c;编程实践是抓手,让知识和基础理论变扎实。基础书籍《信号…...

nginx配置ssl实现https访问

文章目录一、介绍二、创建证书1、OpenSSL创建自签名密钥和证书三、nginx配置四、开放端口一、介绍 nginx配置ssl证书&#xff0c;实现https访问&#xff0c;可以使用自签名SSL证书或者购买机构颁发的证书两种方式参考链接 https://blog.csdn.net/weixin_39198406/article/deta…...

网站广告做的好的企业案例分析/google关键词优化排名

在互联网业务蒸蒸日上的今时今日&#xff0c;系统架构日渐复杂&#xff0c;随着软件产品和工程团队的变革&#xff0c;许多开源的监控工具应运而生&#xff0c;其中有一些相当出名&#xff0c;比如 Zabbix、Nagios 还有 StatsD。也有一些问题被大家不断讨论&#xff0c;例如&am…...

小电影网站怎么做的/电商培训视频教程

论文&#xff1a;Group Sampling for Scale Invariant Face Detection 论文链接&#xff1a;http://openaccess.thecvf.com/content_CVPR_2019/papers/Ming_Group_Sampling_for_Scale_Invariant_Face_Detection_CVPR_2019_paper.pdf 这篇是发表在CVPR2019的关于人脸检测的文章…...

建设集团有限公司网站/热搜榜排名今日事件

我有一个问题,我不确定如何在假定DDD并使用C#/ EF Core时解决.简化情况&#xff1a;我们有2个聚合 – 项目和仓库.它们中的每一个都具有ExternalId(Guid)的身份以在外部(FE等)识别它,其也被视为其域身份.它还有数据库Id taht在数据库模型中表示它 – 实体模型和Db模型是同一类,…...

做网站步骤/优化网站链接的方法

好的&#xff0c;我会用中文回答你的问题。请问你想知道什么&#xff1f;...

企业网站备案在哪个部门/高端网站建设专业公司

文章目录 finallshell 推荐指数 &#xff1a; 五颗星xshell 推荐指数&#xff1a; 四颗星Putty &#xff0c;secureCRT 推荐指数&#xff1a; 三颗星MobaXterm 推荐指数&#xff1a; 四颗星Mosh 推荐指数&#xff1a; 四颗星 总结&#xff1a; 1. finallshell 推荐指数 &…...

求一个用脚做asmr的网站/厦门seo代理商

Instruments 是应用程序用来动态跟踪和分析 Mac OS X 和 iOS 代码的实用工具。 这是一个灵活而强大的工具,它让你可以跟踪一个或多个进程,并检查收集的数据。 这样,Instruments可以帮你更好的理解应用程序和操作系统的行为。 使用 Instruments 应用,你可以使用特殊的工具(即 in…...