【前端工程化】未使用docker时,前端项目实现线上秒级回滚

news2024/9/30 9:18:51

目录

一. 前言

二. 思路

三. 实践

3.1 准备单页应用项目

3.2 保存历史构建index.html内容

3.3 模拟服务端托管前端应用

3.4 快速回滚node服务端代码开发

3.5 快速回滚前端可视化页面开发

 3.6 快速回滚测试

四. 总结


一. 前言

        项目快速回滚前端工程化中很重要的一环,项目部署到线上后如果报错打不开或者其他原因需要回滚到上一个版本,这个时候回滚的速度就会显得尤为重要。

        正常的回滚步骤:需要git reset回退代码或者git rervet撤销上个版本的代码,然后重新打包上线,撤回代码和重新打包都需要时间,会影响线上几分钟的时间,需要一种更快的方案来实现秒级回滚

        docker是一个很好的实现方案,但不是所有公司都会用docker来部署前端应用,大部分都是通过nginx在本地托管,或者上传到oss上面通过cdn的方式来访问静态资源。

        本文将带你一步一步实现一个前端项目秒级回滚的demo,适合跟着文章手动敲一遍,可以更好的理解整体流程的实现,快速应用到自己公司项目里面。

本文示例完整代码已上传:github.com/guojiongwei…

二. 思路

        单页应用打包后都有一个index.html入口文件,每一次打包后的index.html里面都会引入本版本所需要的静态资源,如果我们能不删除过往版本的静态资源(或者上传到oss上面),并且每次项目打包都把本次打包的信息和index.html内容保存起来,保存一个数组列表数据。

        在需要回滚版本时,通过前端可视化界面可以选择项目中某个分支中的构建记录进行快速回滚。具体实现原理就是用回滚版本存的index.html内容替换当前项目正在使用的index.html内容,替换index.html内容后,引入的静态资源都会变成该版本打包出来的静态资源路径,从而实现快速回滚。

        静态资源一般可以放到oss上面,也方便cdn加速,本文为了演示功能,把每一次打包出来的静态资源都放到了项目本地的dist里面,重新构建不会删除原有的资源。

        这种方案适用于所有的单页应用项目,不限制react还是vue,webpack还是vite,整体思路就是保留历史构建的css,js,图片等静态资源,保存每一次构建的index.html内容,回滚时用对应版本的index.html内容替换当前的内容,实现真正的秒级回滚。

三. 实践

3.1 准备单页应用项目

先准备一个单页应用项目,以react + vite为例,在命令行执行命令创建项目:

# npm 6.x 
npm create vite@latest quick_rollback --template react-ts 

# npm 7+, 需要加双-- 
npm create vite@latest quick_rollback -- --template react-ts

项目创建好,进入到项目里面安装依赖:

cd quick_rollback 
npm install

        vite默认在构建的时候会把原先的dist文件夹删除调,需要修改一下配置,让vite在构建的时候不删除原先的dist文件,这样dist里面就会保留每次构建的静态资源了(webpack也有类似的配置)。

修改vite.config.ts配置,添加build配置:

build: { 
    emptyOutDir: false, // 每次打包后不删除输出目录 
}

        注意:真实的项目一般都会把前端静态资源上传到oss上面,采用cdn访问的方式,就不需要配置该项,该项主要是方便本文的demo演示才配置的。

        如果公司前端项目静态资源没有上传到oss上面,而是最基础的放在当前服务器的项目dist文件里面,用nginx托管了一下,也还是需要配置这一项,只有保留了历史构建资源才能更好的实现秒级回滚。

来测试一下,先执行第一次打包:

npm run build

在项目中生成了dist文件夹

1.png

修改一下src/App.tsx里面的代码,替换:

<h1>Vite + React</h1> 
// 替换为 
<h1>Vite + React + 秒级回滚</h1>

替换完成后,再次执行npm run build进行打包。

2.png

可以看到重新打包时没有清空dist文件夹,保留了上一次构建的index.js

        接下来要做的就是每次构建成功后,都记录下本次index.html的值,保存起来,等待回滚的时候使用进行替换。

3.2 保存历史构建index.html内容

在项目根目录新增脚本文件build.mjs(使用.mjs是为了方便使用ES Module语法),添加代码:

// build.mjs 
console.log('打包记录历史构建index.html内容')

并且在npm run build后执行该文件,修改package.json

"build": "tsc && vite build && node build.mjs",

        现在每次打包都会执行node build.mjs文件了,下面就要获取打包后的index.html内容并且保存下来了。

        保存的构建记录内容需要存起来,可以存在数据库里面,本文为了简单模拟就存在了项目根目录的history.json文件里面了。

修改build.jms:

// build.js
import path from 'path'
import fs from 'fs'

function start() {
  // 设置存储构建的history.json文件路径
  const historyPath = path.resolve('history.json')
  // 如果json文不存在就创建一个,初始值为 { list: [] }
  if(!fs.existsSync(historyPath)) {
    fs.writeFileSync(historyPath, JSON.stringify({ list: [] }))
  }
  // 读取本次打包后的dist/index.html内容
  const html = fs.readFileSync(path.resolve('./dist/index.html'), 'utf-8')
  // 获取到当前histyory.json的内容
  const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'))
  // 将当前打包的信息push到history的list中,包含构建时间和index.html内容还有id
  // 实际应用中还可以添加其他的很多信息
  history.list.push({
    time: new Date().toLocaleString('zh-cn'),
    html,
    // 模拟生成一个随机的id
    id: Math.random().toString(16).substr(2),
    // ... 分支信息,commit信息,构建时间,构建人,构建环境等字段
  })

  // 将最新的构建记录内容写入到history.json中
  fs.writeFileSync(historyPath, JSON.stringify(history, null, 2))
}

start()

具体逻辑:

  1. 先设置了一下存储构建的history.json文件路径。
  2. 如果json文不存在就创建一个,初始值为 { list: [] }
  3. 读取本次打包后的dist/index.html内容。
  4. 获取一下当前的history.json文件内容。
  5. 将当前打包的信息pushlist中,包含构建时间和index.html内容还有id(以及其他信息)。
  6. 把最新的构建记录数据写入到history.json文件中。

修改完成后先执行一次打包:

npm run build

然后修改src/App.tsx,把刚才的改动改回来:

<h1>Vite + React + 秒级回滚</h1> 
// 替换为 
<h1>Vite + React</h1>

替换完成后再打包一次:

npm run build

打包完成后再查看history.json文件,会看到里面保留了两次构建的index.html信息。

3.png

        历史构建记录保存好后,需要创建一个node服务和一个前端可视化回滚页面来实现回滚逻辑,实现步骤:

  1. 在前端可视化页面选择某一次构建记录后,把id传给node服务器。
  2. 服务器根据id找到对应的html内容,用html内容替换dist/index.html的内容。
  3. 替换完成后用户访问页面就可以访问到对应版本的内容了,实现了秒级回滚。

3.3 模拟服务端托管前端应用

        前端项目打包成htmlcssjs静态资源后,一般会由nginx等进行托管,实现外网访问,本文使用前端的一个静态资源服务器serve来托管我们打包后的资源。

先全局安装(mac需要加sudo):

npm i serve -g

        安装完成后进入到我们的快速回滚quick_rollback项目下面,执行serve的命令,托管dist文件夹静态资源:

serve -s dist

        启动成功后,终端会显示托管后的访问地址,浏览器打开该地址就可以看到项目已经可以访问到了。

4.png

3.4 快速回滚node服务端代码开发

先创建一个server.mjs来写服务端的代码,服务端要做的事情:

  1. 启动一个3001端口的服务。
  2. 访问根路径的时候返回一个前端可视化回滚的页面。
  3. 提供 /history接口给前端页面提供历史构建记录数据。
  4. 提供 /rollback接口给前端页面提供回滚到哪一版本的接口。
  5. 处理 /rollback回滚逻辑,找到对应版本的html内容,去替换dist/index.html文件内容。

1. 在项目根目录新建一个server.mjs

先用http创建一个基础的服务器:

import http from 'http';
import url from 'url';
import fs from 'fs';
import path from 'path';

const server = http.createServer((req, res) => {
  // 获取请求的路径
  const { pathname } = url.parse(req.url, true);
  // 获取请求的方法
  const method = req.method.toLowerCase();
	
  // ... 后续的代码都会写在这里
})

server.listen(3001, () => {
  console.log('server is running on http://localhost:3001')
});

2. 添加根路径接口,返回rollback.html可视化回滚页面

        回滚在前端可视化页面操作会更方便,在项目根目录创建一个rollback.html文件,添加简单代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>rollback</title>
  </head>
  <body>
  </body>
</html>

然后修改server.mjs文件,添加根路径接口:

// server.mjs

// 如果请求的是根路径,就返回rollback.html
if(pathname === '/' && method === 'get') {
	res.writeHead(200, { 'Content-Type': 'text/html' }, 'utf-8')
	res.end(fs.readFileSync(path.resolve('./rollback.html'), 'utf-8'))
}

        这样当node server.mjs启动服务器访问http://localhost:3001地址的时候就会访问到rollback.html页面。

3.添加获取构建记录接口

        在rollback.html可视化页面我们要获取到历史构建记录,才方便进行回滚操作,需要在服务端提供接口把history.json文件内容返回回来,修改server.mjs文件,添加代码:

// 如果请求的是history,就返回history.json的内容
if(pathname === '/history' && method === 'get') {
  res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8')
  res.end(JSON.stringify({
  	code: 200,
  	mssage: '操作成功',
  	data: JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'))
  }))
}

4.添加快速回滚到接口

        可视化页面访问到历史构建记录后,就可以选择一个历史版本来进行回滚,所以服务端要提供回滚接口,修改server.mjs,添加代码:

// 如果请求的是rollback,就将对应版本的html内容写入到dist/index.html中
if(pathname === '/rollback' && method === 'get') {
  res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8')
  const { query } = url.parse(req.url, true);
  const { id } = query;
  const history = JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'));
  const html = history.list.find(item => item.id === id).html;
  fs.writeFileSync(path.resolve('./dist/index.html'), html);
  res.end(JSON.stringify({
    code: 200,
    mssage: '操作成功',
    data: {}
  }));
}

代码逻辑比较简单:

  1. 提供了get请求 /rollback接口。
  2. 接收query参数id
  3. 获取到history的历史构建记录数据。
  4. id和历史构建记录数据做对比,查找到对应的构建记录。
  5. 拿到对应的index.html内容,去修改 ./dist/index.html,实现快速回滚操作。
  6. 然后给前端响应。

到这里服务端的基础逻辑就写好了,开始写快速回滚可视化页面rollback.html的代码了。

完整server.mjs代码:

import http from 'http';
import url from 'url';
import fs from 'fs';
import path from 'path';

const server = http.createServer((req, res) => {
  // 获取请求的路径
  const { pathname } = url.parse(req.url, true);
  // 获取请求的方法
  const method = req.method.toLowerCase();

  // 如果请求的是根路径,就返回rollback.html
  if(pathname === '/' && method === 'get') {
    res.writeHead(200, { 'Content-Type': 'text/html' }, 'utf-8')
    res.end(fs.readFileSync(path.resolve('./rollback.html'), 'utf-8'))
  }

  // 如果请求的是history,就返回history.json的内容
  if(pathname === '/history' && method === 'get') {
    res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8')
    res.end(JSON.stringify({
      code: 200,
      mssage: '操作成功',
      data: JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'))
    }))
  }

  // 如果请求的是rollback,就将对应版本的html内容写入到dist/index.html中
  if(pathname === '/rollback' && method === 'get') {
    res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8')
    const { query } = url.parse(req.url, true);
    const { id } = query;
    const history = JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'));
    const html = history.list.find(item => item.id === id).html;
    fs.writeFileSync(path.resolve('./dist/index.html'), html);
    res.end(JSON.stringify({
      code: 200,
      mssage: '操作成功',
      data: {}
    }));
  }
})

server.listen(3001, () => {
  console.log('server is running on http://localhost:3001')
});

3.5 快速回滚前端可视化页面开发

        前端页面也比较简单,界面准备一个select选择框选择回滚的版本,和一个确定按钮来确定回滚操作。

为了从dom操作中解放出来,这里会用尤大大开发的petite-vue来开发前端页面。

        petite-vue 是 Vue 的可替代发行版,针对渐进式增强进行了优化。它提供了与标准 Vue 相同的模板语法和响应式模型:

  • 大小只有5.8kb
  • Vue 兼容模版语法
  • 基于DOM,就地转换
  • 响应式驱动

修改rollback.html,添加selectbutton按钮元素,再引入petite-vuecdn文件进行实例化:

  1. 在实例初始化后立即请求获取构建记录列表,赋值给this.historyList
  2. 页面上构建记录遍历生成了select选择项optionv-model绑定值为this.currentItem
  3. button按钮是回滚确定按钮,点击后会先判断有没有选择回滚版本。
  4. 没有选择就提示,选择了借助confirm来二次确认。
  5. 确认后调用服务端回滚接口把id传过去,根据响应内容获取执行结果。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>rollback</title>
  </head>
  <body>
    <script src="https://cdn.bootcdn.net/ajax/libs/petite-vue/0.4.1/petite-vue.umd.min.js"></script>
    <div id="app" @vue:mounted="onMounted">
      <select v-model="currentItem">
        <option value="">请选择回滚版本</option>
        <option v-for="item in historyList" :key="item.id" :value="item">发版时间:{{ item.time }}</option>
      </select>
      <button @click="onRollback">回滚</button>
    </div>
  </body>
  <script>
    /** vue实例 */
    PetiteVue.createApp({
      historyList: [], // 构建记录列表
      currentItem: undefined, // 当前选中的项目
      onMounted() {
        this.getHistory();
      },
      /** 获取构建记录列表 */
      getHistory() {
        fetch("/history").then(res => res.json()).then(res => {
          if (res.code === 200) {
            this.historyList = res.data.list;
          }
        });
      },
      /** 代码回滚 */
      onRollback() {
        if (!this.currentItem) return alert("请选择回滚目标版本!");
        const isRollback = confirm(`确认项目回滚到${this.currentItem.time}版本!`);
        if (isRollback) {
          fetch(`/rollback?id=${this.currentItem.id}`).then(res => res.json()).then(res => {
            if (res.code === 200) {
              alert("快速回滚成功!");
            }
          });
        }
      },
    }).mount("#app");
  </script>
</html>

 3.6 快速回滚测试

        上面打包生成了两个构建版本页面,两个版本页面h1标签展示的分别是Vite + ReactVite + React + 快速回滚,先用serve把当前dist文件夹静态资源托管运行起来:

serve -s dist

打开项目地址浏览器看到现在的内容:

4.png

再新开一个终端,启动我们的服务端代码:

node server.mjs

然后打开http://localhost:3001页面

5.png

        我们选择发版时间较早的那个版本,那个版本对应的页面展示是Vite + React + 快速回滚,选择完成后,点击回滚,进行二次确认,看到下面的提示,代表回滚成功了:

6.png

        这个时候再返回前端react项目页面,刷新一下浏览器,可以看到页面内容变成了我们回滚的版本:

7.png

        到了这里,秒级回滚的核心功能就已经完成了。

四. 总结

这种保留历史构建代码的方式还可以规避两个常见的问题:

  1. 前端构建时dist文件被清空,此时前端访问项目会访问不到。
  2. 用了路由懒加载,新版本发布后,原文件消失,用户跳转页面请求资源会404,造成页面异常。

        可谓是一举多得,但只到现在这步,还会有一个问题,因为没有删除历史构建文件,会越积越多,造成存储资源浪费。

        解决方案是修改build.mjs文件,只保留最近5次的构建结果,5次之外的构建资源去进行删除,这样既能实现秒级回滚,又不会造成太多的资源浪费。

这里提供一下实现思路:

  1. 每一次构建时都获取一下本次构建生成的所有静态资源文件。
  2. 获取到后保存在当前的构建记录里面。
  3. 同时把文件名称都保存一个files.json文件里面,文件路径作为key
  4. 每一次构建结束后判断当前构建的总次数,如果超过5次
  5. 就获取到最早一次构建生成的静态资源文件列表。
  6. 用最早生成的静态资源文件在整体的文件files.json里面做对比。
  7. 如果后面五次构建都没用到最早一次构建产生的文件,那该文件就可以做删除操作了。

        一般回滚都是回滚到上一个版本,很少会出现回滚到超过5个版本的情况,如果真的有该情况,就重新打包构建。

        本文只是提供了一种前端线上项目秒级回滚的思路,适合所有的单页应用项目,我现在所在的公司就是采用的这种方案,但实际使用起来要复杂一些,需要封装一个公共的项目,和项目之间解耦,方便每一个项目使用。

        构建记录要以项目为单位存在数据库或者ossjson文件里面,可视化管理平台可以控制所有的项目,可以进行单独项目各自分支的快速回滚。

        除了这种方式还有其他很多的方式实现秒级回滚,本方式相对来说实现简单成本也低,应用也广泛。

        后面会再出一篇使用docker实现项目快速回滚的方案,万金油方案,前端单页,多页,还有服务端项目都可以用docker来实现快速回滚。

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

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

相关文章

【项目】轻量级HTTP服务器

文章目录 一、项目介绍二、前置知识2.1 URI、URL、URN2.2 CGI2.2.1 CGI的概念2.2.2 CGI模式的实现2.2.3 CGI的意义 三、项目设计3.1 日志的编写3.2 套接字编写3.3 HTTP服务器实现3.4 HTTP请求与响应结构3.5 EndPoint类的实现3.5.1 EndPoint的基本逻辑3.5.2 读取请求3.5.3 构建响…

yolov5 onnx模型 转为 rknn模型

1、转换为rknn模型环境搭建 onnx模型需要转换为rknn模型才能在rv1126开发板上运行&#xff0c;所以需要先搭建转换环境 模型转换工具 模型转换相关文件下载&#xff1a; 网盘下载链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;teuc 将其移动到虚拟机中&#xf…

用于提取数据的三个开源NLP工具

开发人员和数据科学家使用生成式AI和大语言模型&#xff08;LLM&#xff09;来查询大量文档和非结构化数据。开源LLM包括Dolly 2.0、EleutherAI Pythia、Meta AI LLaMa和StabilityLM等&#xff0c;它们都是尝试人工智能的起点&#xff0c;可以接受自然语言提示&#xff0c;生成…

3d动画用云渲染靠谱吗?有什么不同?

3d动画是一种利用计算机技术制作的动画形式&#xff0c;它可以模拟真实世界的物体和场景&#xff0c;创造出各种惊人的效果和视觉体验。3d动画广泛应用于影视、游戏、广告、教育等领域&#xff0c;成为当今最流行的艺术表现形式之一。据统计&#xff0c;2019年全球3d动画市场规…

[STL]list使用介绍

[STL]list使用 注&#xff1a;本文测试环境是visual studio2019。 文章目录 [STL]list使用1. list介绍2. 构造函数3. 迭代器相关函数begin函数和end函数rbegin函数和rend函数 4. 容量相关函数empty函数size函数 5. 数据修改函数push_back函数和pop_back函数push_front函数和pop…

软件兼容性测试的重要性以及一些常用的测试方法

随着软件应用的不断发展&#xff0c;不同操作系统、浏览器、设备和平台的广泛应用&#xff0c;软件兼容性变得越来越重要。在开发和发布软件之前进行兼容性测试是确保软件在多个环境下正常运行的关键步骤。本文将介绍软件兼容性测试的重要性以及一些常用的测试方法。 首先&…

JMeter常用内置对象:vars、ctx、prev

在前文 Beanshell Sampler 与 Beanshell 断言 中&#xff0c;初步阐述了JMeter beanshell的使用&#xff0c;接下来归集整理了JMeter beanshell 中常用的内置对象及其使用。 注&#xff1a;示例使用JMeter版本为5.1 1. vars 如 API 文档 所言&#xff0c;这是定义变量的类&a…

SpringBoot版本升级引起的FileNotFoundException——WebMvcConfigurerAdapter.class

缘起 最近公司项目要求JDK从8升到17&#xff0c;SpringBoot版本从2.x升级到3.x&#xff0c;期间遇到了一个诡异的FileNotFoundException异常&#xff0c;日志如下&#xff08;敏感信息使用xxx脱敏&#xff09; org.springframework.beans.factory.BeanDefinitionStoreExcepti…

安科瑞智能型BA系列电流传感器

安科瑞虞佳豪 壹捌柒陆壹伍玖玖零玖叁 选型

微信小程序——同一控件的点击与长按事件共存的解决方案

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

一份 GitHub star 过万的 1121 页图解算法让“他”成功杀进字节跳动

前两天收到读者喜报&#xff0c;说是进字节了&#xff0c;和他交流了一下他的学习心得&#xff0c;发现他看的资料也是我之前推荐过的算法进阶指南&#xff0c;这里推荐给大家&#xff0c;github star 可是过万哦&#xff01;质量非常高&#xff01; 这份算法笔记与其他的不同&…

使用andlua+写一个获取VSCode最新版本号的安卓软件

点击加号 选择Defalut模板 名称改为vscv 包名改为com.b.vscv 编辑main.lua require "import" import "android.app.*" import "android.os.*" import "android.widget.*" import "android.view.*" import "layout&qu…

微信小程序开发总结

架构分析 软件应用架构包括&#xff1a; 数据层、业务逻辑层、服务处、控制层、展示层、用户&#xff0c;小程序属于展示层&#xff0c;通常还需要其他层次提供支持 主体文件&#xff1a; app.js,app.json,app.wxss&#xff0c;前两者是必须存在再根目录下&#xff0c;app.wxs…

【网络云盘客户端】——上传文件的功能的实现

目录 上传文件功能的实现 uploadtask的设计 设置上传的槽函数 uploadFileAction接口 uploadFile接口 定时上传文件 进度条的设计 上传文件功能的实现 上传文件功能实现 1.双击 ”上传文件 “的 QListWidgetItem 或者 点击 “上传” 菜单项 都会弹出一个文件对话框 2.在文…

关于Java中的Lambda变量捕获

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 目录 一、Lambda表达式语法 二、Lambda中变量捕获 一、Lambda表达式语法 基本语法: (parameters) -> expression 或 (parameters) ->{ statements; } Lambda表达式由三部分组成&a…

嵌入式:QT Day4

一、手动完成服务器的实现&#xff0c;并注释具体步骤 源码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> //服务器类 #include <QTcpSocket> //客户端类 #include <…

vue-echarts配置项详解

起因 最近接手了一个vue3项目&#xff0c;echarts用的是"vue-echarts": “^6.0.0”&#xff0c;每次查看文档的时候痛苦不已&#xff0c;找一个配置要花费大量时间&#xff0c;所以这篇文章&#xff0c;主要就是为了记录比较常见的一些配置。 主要会写三种图的配置…

libcomposite: Unknown symbol config_group_init (err 0)

加载libcomposite.ko 失败 问题描述 如图&#xff0c;在做USB OTG 设备模式的时候需要用到libcomposite.ko驱动&#xff0c;加载失败了。 原因&解决方法 有一个依赖叫configfs.ko的驱动没有安装。可以从内核代码的fs/configfs/configfs.ko中找到这个驱动。先加载confi…

Linux学习之自定义函数

函数是把一些重复使用的命令封装成一个集合&#xff0c;之后可以使用函数名调用。 定义函数的格式如下&#xff1a; function 函数名() {指令集&#xff08;若干条语句&#xff09; return n }要是直接在Shell中直接定义函数&#xff0c;那么直接在Shell中直接使用函数名 参数…

建筑工地为什么要做人员定位?解读技术背后的安全与效益

建筑工地是一个复杂而危险的环境&#xff0c;人员安全一直是行业亟待解决的难题。为了确保工人的安全&#xff0c;并提高工地的管理效率&#xff0c;越来越多的建筑工地开始采用人员定位技术。 对此&#xff0c;华安联大便和各位朋友一起深入探讨人员定位技术的优势和功能&…