手动创建 vue2 ssr 开发环境
本文和个人博客同步发表
更多优质文章查看个人博客
前言
手动搭建 vue ssr 一直是一些前端开发者的噩梦,因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说,很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致这样的原因之一无外乎学习成本高,软件复杂等。这也是有些前端开发者直接拥抱nuxt.js的部分原因。这篇博客使用vue2,以步骤为主,来展示如何创建完整ssr开发环境。
主要参考文章
- vue2 ssr 中文官网
- webpack 中文官网
- webpack-dev-middleware
- webpack-hot-middleware
- webpack tapable
- memory-fs
- express
- 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思路已经很清晰了。在这里简单梳理一下。
根据上面的图可知,想要实现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 一直是一些前端开发者的噩梦,因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说,很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致…...
RHCE-操作系统刻录工具
Windows 1.准备材料。 一个可用的windows操作系统(下载的时候用迅雷比较快) MSDN, 我告诉你 - 做一个安静的工具站 大于等于8G的U盘 想要安装的系统光盘镜像 U盘烧录工具(软碟通) UltraISO软碟通中文官方网站 - 光盘映像文件制作/编辑/转换工具 …...

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

《爆肝整理》保姆级系列教程python接口自动化(十八)--重定向(Location)(详解)
简介 在实际工作中,有些接口请求完以后会重定向到别的url,而你却需要重定向前的url。URL主要是针对虚拟空间而言,因为不是自己独立管理的服务器,所以无法正常进行常规的操作。但是自己又不希望通过主域名的二级目录进行访问&…...
MySQL的索引、视图
什么是索引模式(schema)中的一个数据库对象 在数据库中用来加速对表的查询 通过使用快速路径访问方法快速定位数据,减少了磁盘的I/O 与表独立存放,但不能独立存在,必须属于某个表 由数据库自动维护,表被删除时,该表上的索引自动被…...

【JavaWeb】网络层协议——IP协议
目录 IP协议结构 IP地址管理 特殊IP 解决IP地址不够用 动态分配IP地址 NAT网络地址转换 IPV6 IP协议结构 版本:就是IP协议的版本号。目前只有 4 和 6。这里介绍的是IPV4 首部长度:单位是4字节。于TCP首部长度完全一致,也是可变的&…...
【Python学习笔记】41.Python3 多线程
前言 本章介绍Python的多线程。 Python3 多线程 多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序中的任务放到后台去处理。用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理…...

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

NETCore下CI/CD之自动化测试 (详解篇)
NETCore下CI/CD之自动化测试 (详解篇) 目录:导读 前言 安装JDK 安装 Tomcat 首先,我们需要指定 Tomcat.PID 进程文件,进入 /usr/local/tomcat/bin,编辑文件 增加 tomcat 账户并赋予权限 防止Jeknins…...
Hoeffding不等式剪枝方法
在基于物品的协通过滤算法中,当用户历史行为数据有很多时,对计算会有很大挑战,对此可以使用剪枝对数据进行化简来达到减少计算量。 不是每个物品对都需要进行增量计算。对于两个物品的相似度,每次更新都能够得到一个新的相…...
【算法】数组中的重复数字问题
数组中的重复数据 数组中重复的数字 错误的集合 以第三题,错误的集合为例 对于这样的问题,有很简单的解决方式,先遍历一次数组,用一个哈希表记录每个数字出现的次数,然后遍历一次 [1…N],看看那个元素重…...

数值方法笔记2:解决非线性方程
1. 不动点定理及其条件验证2. 收敛阶、收敛检测与收敛加速2.1 如何估计不动点迭代的收敛阶xk1g(xk){x}_{{k}1}{g}\left({x}_{{k}}\right)xk1g(xk)2.2 给定精度的情况下,如何预测不动点迭代需要迭代的次数2.3 如何加快收敛的速度2.4 停止不定点迭代的条件2.5 不动…...

基于SpringBoot的在线文档管理系统
文末获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏…...

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

[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…...
进程和线程详解
在计算机领域中,进程和线程是非常重要的概念。了解进程和线程是软件开发的基础,也是计算机科学教育中的一部分。本文将介绍进程和线程的概念、区别和应用。 一、什么是进程 在计算机科学中,进程是正在执行的程序实例。一个进程可以由一个或…...
《刀锋》读书笔记
刀锋(毛姆长篇作品精选)毛姆50个笔记点评认为好看的确是完美的结局。《刀锋》里面的人每个人都以自己的方式生活着。艾略特的势利,拉里的自由,伊莎贝尔的现实,苏珊的清醒,索菲的堕落,至于“我”…...
nginx中的ngx_modules
ngx_modules和ngx_module_names是configure脚本生成的,是在objs/ngx_modules.c文件中与其生成的相关的脚本文件相关的变量在options脚本中定义了objs目录的变量NGX_OBJSobjs在init脚本中定义的最终存放ngx_modules的文件 NGX_MODULES_C$NGX_OBJS/ngx_modules.c2. 处…...

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

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

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...