vue项目改造服务端渲染

vue项目改造服务端渲染

概述

【定义】

  服务器渲染的Vue应用程序被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行

【优点】

  与传统SPA相比,服务器端渲染(SSR)的优势主要在于:

  1、更好的 SEO,搜索引擎爬虫抓取工具可以直接查看完全渲染的页面

  截至目前,Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。但如果应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容

  2、更快的内容到达时间,特别是对于缓慢的网络情况或运行缓慢的设备

  无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以用户将会更快速地看到完整渲染的页面,通常可以产生更好的用户体验

思路

  下面以官方的SSR服务器端渲染流程图为例,进行概要说明

ssr

  1、universal Application Code是服务器端和浏览器端通用的代码

  2、app.js是应用程序的入口entry,对应vue cli生成的项目的main.js文件

  3、entry-client.js是客户端入口,仅运行于浏览器,entry-server.js是服务器端入口,仅运行于服务器

  4、entry-client和entry-server这两个文件都需要通过webpack构建,其中entry-client需要通过webpack.server.config.js文件打包,entry-server需要通过webpack.server.config.js文件打包

  5、entry-client构建后的client Bundle打包文件是vue-ssr-client-manifest.json,entry-server构建后的server Bundle打包文件是vue-ssr-server-bundle.json

  6、server.js文件将客户端打包文件vue-ssr-client-manifest.json、服务器端打包文件vue-ssr-server-bundle.json和HTML模板混合,渲染成HTML

webpack配置

  基于vue-cli生成的项目的build目录结构如下

复制代码

复制代码

build- build.js- check-versions.js- utils.js- vue-loader.conf.js- webpack.base.conf.js- webpack.dev.conf.js- webpack.prod.conf.js

复制代码

复制代码

  前面3个文件无需修改,只需修改*.*.conf.js文件

  1、修改vue-loader.conf.js,将extract的值设置为false,因为服务器端渲染会自动将CSS内置。如果使用该extract,则会引入link标签载入CSS,从而导致相同的CSS资源重复加载

-    extract: isProduction
+    extract: false

  2、修改webpack.base.conf.js

  只需修改entry入门配置即可

复制代码

复制代码

...
module.exports = {context: path.resolve(__dirname, '../'),entry: {- app: './src/main.js'+ app: './src/entry-client.js'},
...

复制代码

复制代码

  3、修改webpack.prod.conf.js

  包括应用vue-server-renderer、去除HtmlWebpackPlugin、增加client环境变量

复制代码

复制代码

'use strict'
...
+ const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const webpackConfig = merge(baseWebpackConfig, {...plugins: [// http://vuejs.github.io/vue-loader/en/workflow/production.htmlnew webpack.DefinePlugin({'process.env': env,
+     'process.env.VUE_ENV': '"client"'}),...// generate dist index.html with correct asset hash for caching.// you can customize output by editing /index.html// see https://github.com/ampedandwired/html-webpack-plugin
-    new HtmlWebpackPlugin({
-      filename: config.build.index,
-      template: 'index.html',
-      inject: true,
-      minify: {
-        removeComments: true,
-        collapseWhitespace: true,
-        removeAttributeQuotes: true
-        // more options:
-        // https://github.com/kangax/html-minifier#options-quick-reference
-      },
-      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
-      chunksSortMode: 'dependency'
-    }),...// copy custom static assetsnew CopyWebpackPlugin([{from: path.resolve(__dirname, '../static'),to: config.build.assetsSubDirectory,ignore: ['.*']}]),
+    new VueSSRClientPlugin()]
})
...
module.exports = webpackConfig

复制代码

复制代码

  4、新增webpack.server.conf.js

复制代码

复制代码

const webpack = require('webpack')
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.conf.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')module.exports = merge(baseConfig, {entry: './src/entry-server.js',target: 'node',devtool: 'source-map',output: {libraryTarget: 'commonjs2'},externals: nodeExternals({whitelist: /\.css$/}),plugins: [new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),'process.env.VUE_ENV': '"server"'}),new VueSSRServerPlugin()]
})

复制代码

复制代码

入口配置

  在浏览器端渲染中,入口文件是main.js,而到了服务器端渲染,除了基础的main.js,还需要配置entry-client.js和entry-server.js

  1、修改main.js

复制代码

复制代码

import Vue from 'vue'
import Vuex from 'vuex'
-  import '@/assets/style.css'
import App from './App'
-  import router from './router'
+ import createRouter from './router'
-  import store from './store'
+ import createStore from './store'
import async from './utils/async'
Vue.use(async)
- new Vue({
+ export default function createApp() {
+  const router = createRouter()
+  const store = createStore()
+  const app = new Vue({
-   el: '#app',router,store,
-   components: { App },
-   template: '<App/>'
+   render: h => h(App)})
+ return { app, router, store }
+}

复制代码

复制代码

  2、新增entry-client.js

  后面会介绍到asyncData方法,但是asyncData方法只能用于路由绑定的组件,如果是初始数据则可以直接在entry-client.js中获取

复制代码

复制代码

/* eslint-disable */
import Vue from 'vue'
import createApp from './main'Vue.mixin({beforeRouteUpdate (to, from, next) {const { asyncData } = this.$optionsif (asyncData) {asyncData({store: this.$store,route: to}).then(next).catch(next)} else {next()}}
})const { app, router, store } = createApp()/* 获得初始数据 */
import { LOAD_CATEGORIES_ASYNC } from '@/components/Category/module'
import { LOAD_POSTS_ASYNC } from '@/components/Post/module'
import { LOAD_LIKES_ASYNC } from '@/components/Like/module'
import { LOAD_COMMENTS_ASYNC } from '@/components/Comment/module'
import { LOAD_USERS_ASYNC } from '@/components/User/module'
(function getInitialData() {const { postCount, categoryCount, userCount, likeCount, commentCount } = store.gettersconst { dispatch } = store// 获取类别信息!categoryCount && dispatch(LOAD_CATEGORIES_ASYNC),// 获取文章信息!postCount && dispatch(LOAD_POSTS_ASYNC),// 获取点赞信息!likeCount && dispatch(LOAD_LIKES_ASYNC),// 获取评论信息!commentCount && dispatch(LOAD_COMMENTS_ASYNC),// 获取用户信息!userCount && dispatch(LOAD_USERS_ASYNC)
})()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 = falseconst 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('#root')
})

复制代码

复制代码

  3、新增entry-sever.js

复制代码

复制代码

/* eslint-disable */
import createApp from './main'export default context => 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 })}Promise.all(matchedComponents.map(Component => {if (Component.asyncData) {return Component.asyncData({store,route: router.currentRoute})}})).then(() => {context.state = store.stateresolve(app)}).catch(reject)}, reject)
})

复制代码

复制代码

组件修改

  由于代码需要在服务器端和浏览器端共用,所以需要修改组件,使之在服务器端运行时不会报错

  1、修改router路由文件,给每个请求一个新的路由router实例

复制代码

复制代码

import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)
+ export default function createRouter() {
- export default new Router({
+   return new Router({mode: 'history',routes: [{path: '/',component: () => import(/* webpackChunkName:'home' */ '@/components/Home/Home'),name: 'home',meta: { index: 0 }},...]})
+}

复制代码

复制代码

  2、修改状态管理vuex文件,给每个请求一个新的vuex实例

复制代码

复制代码

import Vue from 'vue'
import Vuex from 'vuex'
import auth from '@/components/User/module'
...Vue.use(Vuex)
+ export default function createStore() {
- export default new Vuex.Store({
+   return new Vuex.Store({modules: {auth,...}})
+}

复制代码

复制代码

  3、使用asyncData方法来获取异步数据

  要特别注意的是,由于asyncData只能通过路由发生作用,使用是非路由组件的异步数据获取最好移动到路由组件中

  如果要通过asyncData获取多个数据,可以使用Promise.all()方法

复制代码

复制代码

asyncData({ store }) {const { dispatch } = storereturn Promise.all([dispatch(LOAD_CATEGORIES_ASYNC),dispatch(LOAD_POSTS_ASYNC)])
}

复制代码

复制代码

  如果该异步数据是全局通用的,可以在entry-client.js方法中直接获取

  将TheHeader.vue通用头部组件获取异步数据的代码移动到entry-client.js方法中进行获取

复制代码

复制代码

// TheHeader.vuecomputed: {...
-    ...mapGetters([
-      'postCount',
-      'categoryCount',
-      'likeCount',
-      'commentCount',
-      'userCount'
-    ])},
-  mounted() {// 获取异步信息
-    this.loadAsync()...
-  },
...methods: {
-    loadAsync() {
-      const { postCount, categoryCount, userCount, likeCount, commentCount } = this
-      const { dispatch } = this.$store
-      // 获取类别信息
-      !categoryCount && dispatch(LOAD_CATEGORIES_ASYNC)
-      // 获取文章信息
-      !postCount && dispatch(LOAD_POSTS_ASYNC)
-      // 获取点赞信息
-      !likeCount && dispatch(LOAD_LIKES_ASYNC)
-      // 获取评论信息
-      !commentCount && dispatch(LOAD_COMMENTS_ASYNC)
-     // 获取用户信息
-      !userCount && dispatch(LOAD_USERS_ASYNC)
-    },

复制代码

复制代码

  将Post.vue中的异步数据通过asyncData进行获取

复制代码

复制代码

// post.vue
...
export default {
+  asyncData({ store, route }) {
+    return store.dispatch(LOAD_POST_ASYNC, { id: route.params.postid })
+  },
...
-  mounted() {
-    this.$store.dispatch(LOAD_POST_ASYNC, { id: this.postId })
-  },
...

复制代码

复制代码

  4、将全局css从main.js移动到App.vue中的内联style样式中,因为main.js中未设置css文件解析

复制代码

复制代码

// main.js
- import '@/assets/style.css'
// App.vue
...
<style module lang="postcss">
...
</style>

复制代码

复制代码

  5、由于post组件的模块module.js中需要对数据通过window.atob()方法进行base64解析,而nodeJS环境下无window对象,会报错。于是,代码修改如下

// components/Post/module
- text: decodeURIComponent(escape(window.atob(doc.content))) 
+ text: typeof window === 'object' ? decodeURIComponent(escape(window.atob(doc.content))) : ''

服务器配置

  1、在根目录下,新建server.js文件

  由于在webpack中去掉了HTMLWebpackPlugin插件,而是通过nodejs来处理模板,同时也就缺少了该插件设置的HTML文件压缩功能

  需要在server.js文件中安装html-minifier来实现HTML文件压缩

复制代码

复制代码

const express = require('express')
const fs = require('fs')
const path = require('path')
const { createBundleRenderer } = require('vue-server-renderer')
const { minify } = require('html-minifier')
const app = express()
const resolve = file => path.resolve(__dirname, file)const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {runInNewContext: false,template: fs.readFileSync(resolve('./index.html'), 'utf-8'),clientManifest: require('./dist/vue-ssr-client-manifest.json'),basedir: resolve('./dist')
})
app.use(express.static(path.join(__dirname, 'dist')))
app.get('*', (req, res) => {res.setHeader('Content-Type', 'text/html')const handleError = err => {if (err.url) {res.redirect(err.url)} else if (err.code === 404) {res.status(404).send('404 | Page Not Found')} else {res.status(500).send('500 | Internal Server Error')console.error(`error during render : ${req.url}`)console.error(err.stack)}}const context = {title: '小火柴的前端小站',url: req.url}renderer.renderToString(context, (err, html) => {console.log(err)if (err) {return handleError(err)}res.send(minify(html, { collapseWhitespace: true, minifyCSS: true}))})
})app.on('error', err => console.log(err))
app.listen(8080, () => {console.log(`vue ssr started at localhost: 8080`)
})

复制代码

复制代码

  2、修改package.json文件

-     "build": "node build/build.js",
+    "build:client": "node build/build.js",
+    "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules",
+    "build": "rimraf dist && npm run build:client && npm run build:server",

  3、修改index.html文件

复制代码

复制代码

<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"><link rel="shortcut icon" href="/static/favicon.ico"><title>小火柴的蓝色理想</title></head><body><!--vue-ssr-outlet--></body>
</html>

复制代码

复制代码

  4、取消代理

  如果继续使用代理如/api代理到后端接口,则可能会报如下错误

error:connect ECONNREFUSED 127.0.0.1:80

  直接写带有http的后端接口地址即可

const API_HOSTNAME = 'http://192.168.1.103:4000'

测试

  1、安装依赖包

cnpm install --save-dev vue-server-renderer

  2、构建

npm run build

  3、运行

node server.js

  点击右键,查看网页源代码。结果如下,说明网站已经实现了服务器端渲染

部署

【pm2】

  由于该网站需要守护nodejs程序,使用pm2部署较为合适

  在项目根目录下,新建一个ecosystem.json文件,内容如下

复制代码

复制代码

{"apps" : [{"name"      : "blog-www","script"    : "./index.js","env": {"COMMON_VARIABLE": "true"},"env_production" : {"NODE_ENV": "production"}}],"deploy" : {"production" : {"user" : "xxx","host" : ["1.2.3.4"],"port" : "22","ref"  : "origin/master","repo" : "git@github.com:littlematch0123/blog-client.git","path" : "/home/xxx/www/mall","post-deploy" : "source ~/.nvm/nvm.sh && cnpm install && pm2 startOrRestart ecosystem.json --env production","ssh_options": "StrictHostKeyChecking=no","env"  : {"NODE_ENV": "production"}}}
}

复制代码

复制代码

【CDN】

  由于项目实际上既有静态资源,也有nodeJS程序。因此,最好把静态资源上传到七牛CDN上

  自行选择服务器的一个目录,新建upload.js文件

复制代码

复制代码

var fs = require('fs');
var qiniu = require('qiniu');
var accessKey = 'xxx';
var secretKey = 'xxx';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var staticPath = '/home/www/blog/client/source/';
var prefix = 'client/static';
var bucket = 'static';var config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z1;
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
putExtra = null; // 一定要将putExtra设置为null,否则会出现所有文件类别都被识别为第一个文件的类型的情况
// 文件上传方法
function uploadFile (localFile) {// 配置上传到七牛云的完整路径const key = localFile.replace(staticPath, prefix)const options = {scope: bucket + ":" + key,}const putPolicy = new qiniu.rs.PutPolicy(options)// 生成上传凭证const uploadToken = putPolicy.uploadToken(mac)// 上传文件formUploader.putFile(uploadToken, key, localFile, putExtra, function(respErr, respBody, respInfo) {if (respErr) throw respErrif (respInfo.statusCode == 200) {console.log(respBody);} else {console.log(respInfo.statusCode);console.log(respBody);}  
})
}
// 目录上传方法
function uploadDirectory (dirPath) {fs.readdir(dirPath, function (err, files) {if (err) throw err// 遍历目录下的内容files.forEach(item => {let path = `${dirPath}/${item}`fs.stat(path, function (err, stats) {if (err) throw err// 是目录就接着遍历 否则上传if (stats.isDirectory())  uploadDirectory(path)else  uploadFile(path, item)})})})
}
fs.exists(staticPath, function (exists) {if (!exists) {console.log('目录不存在!')}else {console.log('开始上传...')uploadDirectory(staticPath)}
})

复制代码

复制代码

【post-deploy】

  然后,修改ecosystem.json文件中的post-deploy项

"source ~/.nvm/nvm.sh && cnpm install && npm run build && node /home/xiaohuochai/blog/client/upload.js&& pm2 startOrRestart ecosystem.json --env production",

  但是,经过实际测试,在服务器端进行构建build,极其容易造成服务器死机。于是,还是在本地构建完成后,上传dist文件到服务器再进行相关操作

"source ~/.nvm/nvm.sh && cnpm install && node /home/xiaohuochai/blog/client/upload.js&& pm2 startOrRestart ecosystem.json --env production"

  修改项目的静态资源地址为CDN地址,API地址为服务器API地址

// config/index.js
assetsPublicPath: 'https://static.xiaohuochai.site/client/'// src/constants/API.js
const API_HOSTNAME = 'https://api.xiaohuochai.cc'

【nginx】

  如果要使用域名对项目进行访问,还需要进行nginx配置

复制代码

复制代码

upstream client {server 127.0.0.1:3002;
}
server{listen 80;server_name www.xiaohuochai.cc xiaohuochai.cc;return 301 https://www.xiaohuochai.cc$request_uri;
}
server{listen 443 http2;server_name www.xiaohuochai.cc xiaohuochai.cc;ssl on;ssl_certificate /home/blog/client/crt/www.xiaohuochai.cc.crt;ssl_certificate_key /home/blog/client/crt/www.xiaohuochai.cc.key;ssl_session_timeout 5m;ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;ssl_protocols TLSv1 TLSv1.1 TLSv1.2;ssl_prefer_server_ciphers on;if ($host = 'xiaohuochai.cc'){rewrite ^/(.*)$ http://www.xiaohuochai.cc/$1 permanent;}location / {expires 7d;add_header Content-Security-Policy "default-src 'self' https://static.xiaohuochai.site; connect-src https://api.xiaohuochai.cc; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.xiaohuochai.site ; img-src 'self' data: https://pic.xiaohuochai.site https://static.xiaohuochai.site; style-src 'self' 'unsafe-inline' https://static.xiaohuochai.site; frame-src https://demo.xiaohuochai.site https://xiaohuochai.site https://www.xiaohuochai.site;";proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;proxy_set_header Host $http_host;proxy_set_header X-Nginx-Proxy true;proxy_pass http://client;proxy_redirect off;}
} 

复制代码

复制代码

浏览器渲染

  官网的代码中,如果使用开发环境development,则需要进行相当复杂的配置

  能否应用当前的webpack.dev.conf.js来进行开发呢?完全可以,开发环境中使用浏览器端渲染,生产环境中使用服务器端渲染

  需要做出如下三点更改:

  1、更改API地址,开发环境使用webpack代理,生产环境使用上线地址

复制代码

复制代码

// src/constants/API
let API_HOSTNAME
if (process.env.NODE_ENV === 'production') {API_HOSTNAME = 'https://api.xiaohuochai.cc'
} else {API_HOSTNAME = '/api'
}

复制代码

复制代码

  2、在index.html同级目录下,新建一个index.template.html文件,index.html是开发环境的模板文件,index.template.html是生产环境的模板文件

复制代码

复制代码

// index.html<body><div id="root"></div></body>// index.template.html<body><!--vue-ssr-outlet--></body>

复制代码

复制代码

  3、更改服务器端入口文件server.js的模板文件为index.template.html

复制代码

复制代码

// server.js
const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {runInNewContext: false,template: fs.readFileSync(resolve('./index.template.html'), 'utf-8'),clientManifest: require('./dist/vue-ssr-client-manifest.json'),basedir: resolve('./dist')
})https://www.cnblogs.com/lcosima/p/9613349.html

https://www.cnblogs.com/lcosima/p/9613349.html

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

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

相关文章

二叉树-堆应用(1)

目录 堆排序 整体思路 代码实现 Q1建大堆/小堆 Q2数据个数和下标 TopK问题 整体思路 代码实现 Q1造数据CreateData Q2建大堆/小堆 建堆的两种方法这里会用到前面的向上/向下调整/交换函数。向上调整&向下调整算法-CSDN博客 堆排序 整体思路 建堆&#xff08;直…

Qt 5.9.4 转 Qt 6.6.1 遇到的问题总结(三)

1.QSet: toList 中的toList 函数已不存在&#xff0c;遇到xx->toList改成直接用&#xff0c;如下&#xff1a; 2.开源QWT 图形库中QwtDial中的 setPenWidth 变成 setPenWidthF函数。 3.QDateTime 中无setTime_t 改为了setSecsSinceEpoch函数。 4.QRegExp 类已不存在 可以用Q…

【定位·HTML】

定位布局可以分为以下四种&#xff1a; 静态定位&#xff08;inherit&#xff09; 相对定位&#xff08;relative&#xff09; 绝对定位&#xff08;absolute&#xff09; 固定定位&#xff08;fixed&#xff09; 一般的标签元素不加任何定位属性时&#xff0c;默认都属于静态…

百川终入海 ,一站式海量数据迁移工具 X2Doris 正式发布

在大数据分析领域&#xff0c;Apache Doris 作为广受认可的开源实时数据仓库&#xff0c;已经在越来越多行业用户的真实业务场景中得到广泛应用&#xff0c;成为许多企业数据分析基础设施的重要基座。尤其在过去一年多的时间里&#xff0c;越来越多企业选择基于 Apache Doris 进…

node.js(nest.js控制器)学习笔记

nest.js控制器&#xff1a; 控制器负责处理传入请求并向客户端返回响应。 为了创建基本控制器&#xff0c;我们使用类和装饰器。装饰器将类与所需的元数据相关联&#xff0c;并使 Nest 能够创建路由映射&#xff08;将请求绑定到相应的控制器&#xff09;。 1.获取get请求传参…

牛客——中位数图(连续子数组和二维前缀和)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 给出1~n的一个排列&#xff0c;统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后&#xff0c;位于中间的数。 输入描述: 第一行为两个正…

CentOS gui 图形界面显示文字乱码

一、现象 CentOS&#xff08;CentOS 7.5&#xff09;控制台下显示中文乱码&#xff1a; 或者通过X11 Forwarding远程显示CentOS的图形化程序文字乱码&#xff1a; 二、解决方法 安装中文语言包&#xff1a; yum install kde-l10n-Chinese 注&#xff1a;网上有些文章会推荐安…

Linux文本三剑客---grep经典案例

grep&#xff08;从文本或字符串种过滤特定内容。&#xff09; 格式&#xff1a;Usage: grep [OPTION]... PATTERNS [FILE]... 常用选项&#xff1a; -E 等价于 egrep 扩展正则 -i 忽略大小写 -w 匹配单词 -o 仅显示匹配内容 -r 递归匹配 -c 统计匹配的行数 -v 取反 -n 行号 -A…

如何采集京东的商品-简数采集器

如何使用简数采集器批量采集京东的商品及相关描述呢&#xff1f; 简数采集器目前不支持采集京东的商品及相关信息&#xff0c;且不建议采集&#xff0c;请换个采集源采集。 简数采集器采集网页文章特别简单方便&#xff0c;不需要懂代码写采集规则的&#xff0c;只需输入要采…

【解刊】审稿人极其友好!中科院2区SCI,3个月录用,论文质量要求宽松!

计算机类 • 高分快刊 今天带来Springer旗下计算机领域高分快刊&#xff0c;有投稿经验作者表示期刊审稿人非常友好&#xff0c;具体情况一起来看看下文解析。如有投稿意向可重点关注&#xff1a; 01 期刊简介 Complex & Intelligent Systems ✅出版社&#xff1a;Sprin…

上岸秘籍来啦!TOGAF认证考试全攻略

上岸秘籍来啦&#xff01;手把手教你如何顺利通过TOGAF认证考试&#xff01; &#x1f31f;考试内容 TOGAF 9.2认证分为两个级别&#xff1a; ✅ TOGAF基础级&#xff1a;掌握标准术语、结构和基本概念&#xff0c;理解企业架构和核心标准。 ✅ TOGAF鉴定级&#xff1a;深入分析…

【知识点】设计模式

创建型 单例模式 Singleton&#xff1a;确保一个类只有一个实例&#xff0c;并提供该实例的全局访问点 使用一个私有构造方法、一个私有静态变量以及一个公有静态方法来实现。私有构造方法确保了不能通过构造方法来创建对象实例&#xff0c;只能通过公有静态方法返回唯一的私…

qt学习:Table widget控件

目录 头文件 实战 重新配置ui界面 添加头文件 在构造函数中添加初始化 显示方法 该实例是在sqlite项目上添加qt学习&#xff1a;QTSQL连接sqlite数据库增删改查-CSDN博客 头文件 #include <QTableWidgetItem> 实战 重新配置ui界面 用法介绍&#xff0c;可以双击…

UE5/UE4中3D汉字字体文字的创建与实现

本案例工程下载位置&#xff1a;https://mbd.pub/o/bread/ZZqVmJ9v 在虚幻引擎5&#xff08;UE5&#xff09;和虚幻引擎4&#xff08;UE4&#xff09;中&#xff0c;实现3D汉字字体的创建是一项常见的需求。 本文将详细介绍两种有效的方法&#xff1a; 1.通过TextRender配合Of…

《区块链简易速速上手小册》第9章:区块链的法律与监管(2024 最新版)

文章目录 9.1 法律框架和挑战9.1.1 基础知识9.1.2 主要案例&#xff1a;加密货币的监管9.1.3 拓展案例 1&#xff1a;跨国数据隐私和合规性9.1.4 拓展案例 2&#xff1a;智能合约的法律挑战 9.2 区块链的合规性问题9.2.1 基础知识9.2.2 主要案例&#xff1a;加密货币交易所的合…

DevOps落地笔记-03|用户故事:对用户需求达成共识的关键

上一讲主要跟你介绍了如何使用影响地图这个工具来进行产品定义、里程碑规划和用户需求分析。影响地图让我们始终以达到目标为核心&#xff0c;并让功能和需求不偏离该目标&#xff0c;从而让交付更有重点。可视化、结构化的思维导图为技术和业务人员创建了共享的整体视图&#…

769933-15-5,Biotin aniline,可以合成多种有机化合物和聚合物

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;769933-15-5&#xff0c;Biotin aniline&#xff0c;生物素苯胺&#xff0c;生物素-苯胺 一、基本信息 产品简介&#xff1a;Biotin Aniline&#xff0c;一种具有重要生物学功能的化合物&#xff0c;不仅参与了维生…

Docker多节点部署Minio分布式文件系统并测试

文章目录 一、前提准备二、文件配置1. .env2. env/minio.env3. docker-compose-minio.yml 三、测试四、Java测试1. 引入依赖2. 增删改 一、前提准备 准备如下文件夹和文件 ./ ├── docker-compose-minio.yml ├── .env ├── env │ ├── minio.env ├── minio │…

C#网络爬虫之TianyaCrawler实战经验分享

互联网时代的到来带来了大量的数据&#xff0c;而网络爬虫技术成为了获取这些数据的重要途径之一。如果你是一名C#开发者&#xff0c;那么你可能会对TianyaCrawler这个强大的网络爬虫框架感兴趣。本文将带你深入了解TianyaCrawler&#xff0c;分享它的技术概况、使用场景&#…

显示企业信息的SSL证书

随着互联网的发展&#xff0c;以及《网络安全法》、《数据安全法》、《个人信息保护法》等法律法规的出台&#xff0c;越来越多的企业意识到保护用户信息的重要性&#xff0c;尤其是对于涉及商业交易和个人数据的网站。为了保护网站安全和用户隐私&#xff0c;给网站部署SSL证书…