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

目录

一. 前言

二. 思路

三. 实践

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来实现快速回滚。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode每日一题】——566.重塑矩阵

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 简单 三【题目编号】 566.重塑矩阵 四【题目描述】 在 MATLAB 中&…

HTML5中的data-*属性

介绍&#xff1a; data-*全局属性是一类被称为自定义数据属性的属性&#xff0c;它赋予我们在所有 HTML 元素上嵌入自定义数据属性的能力。 data-*的使用 <div class"child" data-name"小红" data-age"18"></div> 在js里有两种获…

Vue2 第二节 ----初识Vue(简单示例,模板语法,数据绑定)

知识点&#xff1a; 1.Vue的简单示例 2.模板语法 3.数据绑定 4.el和data的两种写法 5.MVVM模型 一. Vue的简单实例 <div id"root"><h1>hello, {{name.toUpperCase()}}, {{address}}</h1></div><script type"text/javascript&q…

ChatGPT 是如何工作的:从预训练到 RLHF

欢迎来到人工智能的未来&#xff1a;生成式人工智能&#xff01;您是否想知道机器如何学习理解人类语言并做出相应的反应&#xff1f;让我们来看看ChatGPT ——OpenAI 开发的革命性语言模型。凭借其突破性的 GPT-3.5 架构&#xff0c;ChatGPT 席卷了世界&#xff0c;改变了我们…

[Java] 单例设计模式详解

模式定义&#xff1a;保证一个类只有一个实例&#xff0c;并且提供一个全局访问点&#xff0c;时一种创建型模式 使用场景&#xff1a;重量级的对象&#xff0c;不需要多个实例&#xff0c;如线程池&#xff0c;数据库连接池 单例设计模式的实现 1.懒汉模式&#xff1a;延迟…

066、故障处理之热点问题

为什么要解决热点 分布式架构中各个组件的理想状态&#xff1a;资源利用率相对均衡 形成写热点的原因 高频访问的小表SQL执行计划不合理具有顺序增长属性的索引扫描 数据组织模型 例如数据是序列递增&#xff0c;则有可能数据全部都集中一个region上 &#xff0c;或者集中…

6、用restful风格写controller方法接口,单元测试依赖

编写单元测试&#xff0c;用restful风格写controller方法 单元测试依赖 实际项目开发中&#xff0c;单元测试与业务代码通常都会要求同步进行 TDD测试驱动开发&#xff1a;先编写单元测试&#xff0c;然后努力去开发业务代码去满足所有的单元测试用例。 添加SpringBoot的测试…

Springer独立出版 | 2023年触觉与虚拟现实国际会议(ICHVR 2023)

会议简介 Brief Introduction 2023年触觉与虚拟现实国际会议(ICHVR 2023) 会议时间&#xff1a;2023年12月15日-17日 召开地点&#xff1a;中国北海 大会官网&#xff1a;www.ichvr.org 2023年触觉与虚拟现实国际会议(ICHVR 2023)由东南大学、上海交通大学联合主办&#xff1b;…

Python-ElasticSearch客户端的封装(聚合查询、统计查询、全量数据)

目录 ES Python客户端介绍封装代码测试代码参考 ES Python客户端介绍 官方提供了两个客户端elasticsearch、elasticsearch-dsl pip install elasticsearchpip install elasticsearch-dsl第二个是对第一个的封装&#xff0c;类似ORM操作数据库&#xff0c;可以.filter、.group…

com.android.ide.common.signing.KeytoolException:

签名没问题但是提示Execution failed for task :app:packageDebug. > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable > com.android.ide.common.signing.KeytoolException: Failed to read ke…

github gitlab 多用户多平台切换

一、背景 我需要用账号1 来登录并管理github 账号 我需要用账号2 来登录并管理gitlab 账号 二、设置账号 邮箱 设置账号1用户名与邮箱 git config --global user.name "miaojiang" git config --global user.email "187133163.com" 三、生成本地密钥…

pycharm配置arcpy环境

目录 1、安装ArcGIS软件2、安装PyCharm3、创建PyCharm项目4、验证ArcPy环境 在GIS开发中&#xff0c; ArcPy是不可或缺的重要组件&#xff0c;而PyCharm作为一款功能强大的Python IDE&#xff0c;为我们提供了更便捷、高效的开发环境。在本文中&#xff0c;我们将详细介绍如何…

sublime配置less的一些坑(1)

仅在sublime的Install Package安装保存less报错 在sublime的Install Package安装less 打开sublime软件,按住CtrlShiftP组合键,弹出的界面中选择Install Package 选中后enter或者回车。等会弹出一个弹窗,大致意思是说你已经成功安装了package control。如果你在此之前已经安装了…

【力扣每日一题】2023.7.31 重排链表

目录 题目&#xff1a; 示例: 分析: 代码: 题目&#xff1a; 示例: 分析: 给我们一个链表&#xff0c;让我们按照题目要求原地修改重排链表。 那么具体怎么个重排法呢&#xff0c;题目给出了一串式子&#xff0c;其实就是把链表分为前后两段&#xff0c;然后在前半段的节…

企业工程管理系统源码之提高工程项目管理软件的效率

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保…

UML/SysML建模工具更新(2023.7)(1-5)有国产工具

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 最近一段时间更新的工具有&#xff1a; 工具最新版本&#xff1a;Visual Paradigm 17.1 更新时间&#xff1a;2023年7月11日 工具简介 很用心的建模工具。支持编写用例规约。支持文本分析和C…

小目标检测(1)——大恒(DaHeng)相机操作与控制编程

文章目录 引言正文相关开发库的介绍编程准备配置引用头文件GalaxyIncludes.h配置lib文件 具体编程过程初始化和反初始化枚举设备开关设备 属性控制属性控制器种类 图像采集控制和图像处理采单帧回调采集图像处理流对象属性控制 获取设备事件获取掉线事件通知 样例程序分析补充&…

重生之我要学C++第五天

这篇文章主要内容是构造函数的初始化列表以及运算符重载在顺序表中的简单应用&#xff0c;运算符重载实现自定义类型的流插入流提取。希望对大家有所帮助&#xff0c;点赞收藏评论&#xff0c;支持一下吧&#xff01; 目录 构造函数进阶理解 1.内置类型成员在参数列表中的定义 …

2023.07.29 驱动开发DAY6

通过epoll实现一个并发服务器 服务器 #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h…

脑电信号处理与特征提取——6.运用机器学习技术和脑电进行大脑解码(涂毅恒)

目录 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分析 6.4 代码解读 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分…