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

Luckysheet 实现 excel 多人在线协同编辑(全功能实现增强版)

前言

        感谢大家对 Multi person online edit(多人在线编辑器) 项目的支持,mpoe 项目使用 quill、luckysheet、canvas-editor 实现的 md、excel、word 在线协同编辑,欢迎大家Fork 代码,多多 Start哦~

Multi person online edit 多人协同编辑器项目icon-default.png?t=O83Ahttps://gitee.com/wfeng0/mpoe        经过大家反馈和咨询,还是对 luckysheet 的协同更加感兴趣,但是原项目有些乱,有些功能也没有完善,因此,单独将Luckysheet 抽离成新项目,争取实现完整的协同功能。

        本项目仅实现luckysheet协同哈,使用 sequelize 作为ORM数据库连接,方便大家迁移,同时,也做了兼容,没有数据库的用户,只是不能持久化数据,协同功能不受任何影响。为了规范代码,使用 typescript 构建,没有使用任何前端框架,实现最简单的luckysheet协同增强版。

创建两个实例对象

        由于luckysheet是挂载在window上,因此,同一个页面不能直接创建两个实例对象,但是可以通过 iframe 实现:

 实现效果如下:

初始化协同

        配置Lucky sheet的协同非常简单:

  const options = {allowUpdate: true, // 配置协同功能loadUrl: "/api/loadLuckysheet", // 初始化 celldata 数据updateUrl: WS_SERVER_URL, // 协同服务转发服务// ...other option};

配置 allowUpdate

        是否允许操作表格后的后台更新,与updateUrl配合使用。如果要开启共享编辑,此参数必须设置为true.

配置 loadUrl      

   loadUrl是初始化 celldata 数据的一个http接口请求,底层实现是通过post发送请求,初始化sheet 数据:

$.post(loadurl, {"gridKey" : server.gridKey}, function (d) {})

        因此,需要在服务端创建一个 post 请求的接口,处理并返回数据:

配置 updateUrl

        操作表格后,实时保存数据的websocket地址,此接口也是共享编辑的接口地址,过共享编辑功能,可以实现Luckysheet实时保存数据和多人同步数据,每一次操作都会发送不同的参数到后台,具体的操作类型和参数参见表格操作。

/*** 创建 Web Socket 服务*/
export function createWebSocketServer(port: number) {const wsServer = new WebSocketServer({ port });logger.info(`ws server is running at: ws://localhost:${port}`);wsServer.on("connection", (client) => {console.log("==> user connected");client.on("error", console.error);client.on("close", () => {});client.on("message", (data) => {console.log("received: %s", data);});});
}

        进行数据解析:根据官网的描述,发送给后端的数据默认是经过pako压缩过后的,需要进行解析,转换为可识别对象操作

/*** Pako 数据解析*/
export function unzip(str: string) {const chartData = str.toString().split("").map((i) => i.charCodeAt(0));const binData = new Uint8Array(chartData);const data = pako.inflate(binData);return decodeURIComponent(String.fromCharCode(...Array.from(new Uint16Array(data))));
}

 解析数据如下:

 配置协同数据结构

        上面的讲述的都是 前台向后台发送数据,那么,协同服务应该返回什么数据结构给 luckysheet呢? 根据 luckysheet/src/controller/server.js 中的返回参数分析,协同服务需要按照下列数据返回:

/*** 处理广播给其他客户端事件,客户端接收服务端要求数据结构:* * data: 修改的命令* id: "7a"   websocket的id* username: 用户名(用于显示 xxx 正在编辑)* type: *  # message === '用户退出' 用户退出时,关闭协同编辑时其提示框*  # type == 1 send 成功或失败*  # type == 2 更新数据*  # type == 3 多人操作不同选区("t": "mv")(用不同颜色显示其他人所操作的选区)*  # type == 4 批量指令更新*  # type == 5 showloading*  # type == 6 hideloading*/if (data === "exit") return JSON.stringify({ message: "用户退出", id: userid });// 这里仅做 2 3 类型处理,其他类型自行拓展哈
const info = { data, id: userid, username, type: data.t === "mv" ? 3 : 2 }
return JSON.stringify(info);

 配置上诉后,即可实现初步协同,如下:

Sequelize 

        Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。本项目使用其构建,意在只需要书写表模型,即可完成复杂的 luckysheet数据结构存储。同时,还能检测连接状态,使得没有数据库的用户,也可以体验协同。

class DataBase {private _connected: boolean = false; // 连接状态private _sequelize: Sequelize | null = null; // 连接对象/*** 初始化数据库*/public init() {// 创建连接const URL = `mysql://${user}:${password}@${host}:${port}/${database}`;this._sequelize = new Sequelize(URL, { logging });// 测试连接this._sequelize.authenticate().then(...).catch(...)}
}

初始化模型

        Sequelize 是通过模型进行数据操作的,因此,我们需要提供对应的模型结构:

/*** Worker Books 工作簿模型表*/import { Model, Sequelize } from "sequelize";export class WorkerBookModel extends Model {// 通过 declare 定义模型类型declare gridKey: string;declare title: string;declare lang?: string;// 需要向外提供 注册模型的静态方法static registerModule(sequelize: Sequelize) {WorkerBookModel.init(....)}
}

同步模型

  • Model.sync() - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)
  • Model.sync({ force: true }) - 将创建表,如果表已经存在,则将其首先删除
  • Model.sync({ alter: true }) - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配.

        force: true 会导致表数据丢失,请谨慎使用!!!

在这里就不过多介绍 sequelize 相关知识了,大家自行查阅文档哈。

协同存储实现

         Luckysheet 每一次操作都会保存历史记录,用于撤销和重做,如果在表格初始化的时候开启了共享编辑功能,则会通过websocket将操作实时更新到后台。因此,我们根据传递到后台的操作类型,更新数据库状态,不就实现了协同存储了嘛。

单个单元格刷新 

async function v(data: string) {// 1. 解析 rc 单元格const { t, r, c, v, i } = <OperateData>JSON.parse(data);logger.info("[CRDT DATA]:", data);// 纠错判断if (t !== "v") return logger.error("t is not v.");if (isEmpty(i)) return logger.error("i is undefined.");if (isEmpty(r) || isEmpty(c)) return logger.error("r or c is undefined.");// 场景一:单个单元格插入值if (v && v.v && v.m) {// 判断表内是否存在当前记录const exist = await CellDataService.hasCellData(i, r, c);if(exist) CellDataService.updateCellData() else CellDataService.createCellData()}// 场景二:剪切/粘贴到某个单元格 - 会触发两次广播if (v === null) {// 删除该记录await CellDataService.deleteCellData(i, r, c);}// 场景三: 删除单元格内容if (v && !v.v && !v.m){// 删除记录await CellDataService.deleteCellData(i, r, c);}
}

范围单元格刷新 

        上诉是一个标准的范围单元格协同消息,我们需要根据 range row column 和 v 的数组,循环处理每一条数据项:

 // 循环列,取 v 的内容,然后创建记录for (let index = 0; index < v.length; index++) {// 这里面的每一项,都是一条记录for (let j = 0; j < v[index].length; j++) {// 解析内部的 r c 值const item = v[index][j];const r = range.row[0] + index;const c = range.column[0] + j;// 根据 r c 存储数据}}

 

隐藏行/列 行高/列宽处理

        行高列宽及隐藏行列,均触发在 t="cg" 中:

 

边框及合并单元格处理

// k borderInfo 边框处理// {"t":"cg","i":"e73f971d606...","v":[{"rangeType":"range","borderType":"border-all","color":"#000","style":"1","range":[{"row":[0,0],"column":[0,0],"row_focus":0,"column_focus":0,"left":0,"width":73,"top":0,"height":19,"left_move":0,"width_move":73,"top_move":0,"height_move":19}]}],"k":"borderInfo"}// {"t":"cg","i":"e73f971d......","v":[{"rangeType":"range","borderType":"border-all","color":"#000","style":"1","range":[{"row":[2,7],"column":[1,2],"row_focus":2,"column_focus":1,"left":74,"width":73,"top":40,"height":19,"left_move":74,"width_move":147,"top_move":40,"height_move":119,}]}],"k":"borderInfo"}// {"t":"cg","i":"e73f971d......","v":[{"rangeType":"range","borderType":"border-bottom","color":"#000","style":"1","range":[{"left":148,"width":73,"top":260,"height":19,"left_move":148,"width_move":73,"top_move":260,"height_move":19,"row":[13,13],"column":[2,2],"row_focus":13,"column_focus":2}]}],"k":"borderInfo"}if (k === "borderInfo") {// 处理 rangeTypefor (let idx = 0; idx < borderInfo.length; idx++) {const border = borderInfo[idx];const { rangeType, borderType, color, style, range } = border;// 这里能拿到 i range 判断是否存在// declare row_start?: number;// declare row_end?: number;// declare col_start?: number;// declare col_end?: number;const info: ConfigBorderModelType = {worker_sheet_id: i,rangeType,borderType,row_start: range[0].row[0],row_end: range[0].row[1],col_start: range[0].column[0],col_end: range[0].column[1],};const exist = await ConfigBorderService.hasConfigBorder(info);if (exist) {// 更新await ConfigBorderService.updateConfigBorder({config_border_id: exist.config_border_id,...info,color,style: Number(style),});} else {// 创建新的边框记录await ConfigBorderService.createConfigBorder({...info,style: Number(style),color,});}}}

         合并单元格的处理可能麻烦些:

    // 合并单元格 - 又是一个先删除后新增的操作,由luckysheet 前台设计决定的// {"t":"all","i":"e73f971....","v":{"merge":{"1_0":{"r":1,"c":0,"rs":3,"cs":3}},},"k":"config"}// {"t":"all","i":"e73f971....","v":{"merge":{"1_0":{"r":1,"c":0,"rs":3,"cs":3},"9_1":{"r":9,"c":1,"rs":5,"cs":3}},},"k":"config"}// {"t":"all","i":"e73f971....","v":{"merge":{"9_1":{"r":9,"c":1,"rs":5,"cs":3}},},"k":"config"}// 先删除await ConfigMergeService.deleteMerge(i);// 再新增for (const key in v.merge) {if (Object.prototype.hasOwnProperty.call(v.merge, key)) {const { r, c, rs, cs } = v.merge[key];await ConfigMergeService.createMerge({worker_sheet_id: i,r,c,rs,cs,});}}

获取数据的时候,需要处理两个地方: config 及 celldata

      /* eslint-disable */// 4. 查询 merge 数据 - 这里不仅要体现在 config 中,还要体现在 celldata.mc 中const merges = await ConfigMergeService.findAll(worker_sheet_id);merges?.forEach((merge) => {// 拼接 r_c 格式const { r, c } = merge.dataValues;// @ts-ignoretemp.config.merge[`${r}_${c}`] = merge.dataValues;// 配置 celldata mc 属性const currentMergeCell = temp.celldata.find(// @ts-ignore(i) => i.r == r && i.c == c);// @ts-ignoreif (currentMergeCell) currentMergeCell.v.mc = merge.dataValues;});

 

图片及统计图处理

        这块内容还有些前台的东西需要二开,后面会同步更新 git ,大家关注下仓库,start 下。

luckysheet-crdt: Luckysheet 协同增强版(全功能实现)icon-default.png?t=O83Ahttps://gitee.com/wfeng0/luckysheet-crdt

        图片上传,需要使用到两个新的 API:uploadImage、imageUrlHandle,默认情况下,插入的图片是以base64的形式放入sheet数据中,但是图片放入 sheet 中,进行协同传输,会导致node 解析数据堆栈溢出,因此,需要自定义图片上传方法:

    // 处理协同图片上传uploadImage: async (file: File) => {// 此处拿到的是上传的 file 对象,进行文件上传 ,配合 node 接口实现const formData = new FormData();formData.append("image", file);const { data } = await fetch({url: "/api/uploadImage",method: "POST",data: formData,});// *** 关键步骤:需要返回一个地址给 luckysheet ,用于显示图片if (data.code === 200) return Promise.resolve(data.url);else return Promise.resolve("image upload error");},

 

看大家的接口设计哈,如果直接返回能访问的服务器路径,其实不用第二个接口也能实现,这里就都简单介绍一下:

    // 处理上传图片的地址imageUrlHandle: (url: string) => {// 已经是 // http data 开头则不处理if (/^(?:\/\/|(?:http|https|data):)/i.test(url)) {return url;}// 不然拼接服务器路径return SERVER_URL + url;},

        在协同存储上处理如下:

 查询数据库,并处理为 luckysheet 初始化数据类型:

即可实现图片协同存储:

 

统计图的后面再更新哈,还在研究中~ 

总结

1. luckysheet 的协同并不难,很多东西源码底层已经封装好了,我们只需要按照官网说明,处理响应的操作即可;

2. 当然,库还有些没有完善的功能,需要大家自行拓展;

3. 后续会持续更新,关注大家的需求,也会考虑封装一个 npm 包,提供给大家,下载即用;

4. 大家多多start 支持呀~这样才有动力更新哦!

相关文章:

Luckysheet 实现 excel 多人在线协同编辑(全功能实现增强版)

前言 感谢大家对 Multi person online edit(多人在线编辑器) 项目的支持&#xff0c;mpoe 项目使用 quill、luckysheet、canvas-editor 实现的 md、excel、word 在线协同编辑&#xff0c;欢迎大家Fork 代码&#xff0c;多多 Start哦~ Multi person online edit 多人协同编辑器…...

最新VMware Workstation Pro领先的免费桌面虚拟化软件基于 x86 的 Windows 桌面虚拟化软件下载安装保姆级教程,直接下载,持续更新

目录 说明 安装程序下载 方法一&#xff1a;直接下载 方法二&#xff1a;官网下载 安装教程 说明 这几天重装电脑&#xff0c;想装VMware Workstation&#xff0c;搜了之后才发现它竟然对个人用户免费了&#xff0c;一个字&#xff1a;爽&#xff01;终于可以结束百度序列号…...

GitHub使用

太久不用GitHub发现自己又有些不会了&#xff0c;突发奇想为何不把每次看到的有指导意义的博客收录一下以便下次查阅呢 如何上传文件夹到GitHub上&#xff08;配图详解&#xff09;&#xff1f;_github上傳資料夾-CSDN博客 github上如何删除自己的仓库_github删除仓库-CSDN博…...

阿里云服务器Linux(centos)系统安装nginx1.20.2

阿里云服务器Linux(centos)系统安装nginx1.20.2 1.安装依赖包 一共要安装4种依赖&#xff08;基于c语言&#xff09; yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2.下载nginx安装包并解压安装包 nginx官网下载&#xff1a;http://nginx.org/en/do…...

linux 用户名密码设置

安装linux时默认的密码最小长度是5个字节&#xff0c;但这并不够&#xff0c;要把它设为8个字节。修改最短密码长度需要编辑login.defs文件#vi /etc/login.defs PASS_MAX_DAYS 99999 #&#xff03;密码设置最长有效期&#xff08;默认值) PASS_MIN_DAYS 0 ##密…...

MySQL是否可以配合Keepalived实现高可用

MySQL是否可以配合Keepalived实现高可用 是的&#xff0c;MySQL 可以配合 Keepalived 实现高可用性。通常&#xff0c;使用 Keepalived 与 MySQL 配合的方式主要是通过配置 虚拟IP&#xff08;VIP&#xff09; 来实现主从数据库的自动切换&#xff0c;从而保证在主数据库宕机时…...

windows下 mysql开启 binlog日志

一、查看是否开启 binlog -- 方式一 show binary logs;-- 方式二 show VARIABLES like log_bin 说明没有开启 方式一 &#xff1a;you are not using binary logging 方式二&#xff1a;log_bin off 二、编辑 my.ini 配置文件 默认安装地点位于&#xff1a;C:\ProgramDat…...

59. 螺旋矩阵 II

59. 螺旋矩阵 II class Solution { public:vector<vector<int>> generateMatrix(int n) {// for(int i0;i<n;i){ 这样的遍历方式不对// for(int j 0;j<n;j){// generateMatrix[i][j] // }// } //初始化矩阵vector<vector<int&…...

XML 查看器:深入理解与高效使用

XML 查看器&#xff1a;深入理解与高效使用 XML&#xff08;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言。它通过使用标签来定义数据结构&#xff0c;使得数据既易于人类阅读&#xff0c;也易于机器解析。在本文中&#xff0c;我们将探讨 XML 查看器的功能…...

JDBC学习

配置文件 application.yml spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/rbac?useUnicodetrue&characterEncodingutf-8&serverTimezoneUTC driver-class-name: com.mysql.jdbc.Driverrbac为我自己本地的数据库&…...

单元测试

junit5中五个方法为&#xff1a;Test、BeforeEach&#xff08;修饰实例方法&#xff0c;在测试方法开始之前执行&#xff09;、AfterEach&#xff08;修饰实例方法&#xff0c;在测试方法完成之后开始执行&#xff09;、BeforeAll&#xff08;修饰静态static方法&#xff0c;在…...

24/12/9 算法笔记<强化学习> PPO,DPPO

PPO是目前非常流行的增强学习算法&#xff0c;OpenAI把PPO作为目前baseline算法&#xff0c;首选PPO&#xff0c;可想而知&#xff0c;PPO可能不是最强的&#xff0c;但是是最广泛的。 PPO是基于AC架构&#xff0c;因为AC架构有一个好处&#xff0c;就是解决了连续动作空间的问…...

spring boot通过连接池的方式连接时序库IotDB

1、maven依赖 <dependency><groupId>org.apache.iotdb</groupId><artifactId>iotdb-session</artifactId><version>1.3.2</version></dependency>2、配置文件 iotdb:server:url: localhostport: 6667name: rootpwd: rootmax…...

maven多模块开发

目录 聚合 可选依赖 排除依赖 属性 打包 聚合 聚合就是将多个模块组成一个整体&#xff0c;进行项目构建。聚合工程&#xff08;也称为多模块项目&#xff09;是 Maven 的一种项目结构&#xff0c;它允许将一个大型项目拆分为多个较小的、更易于管理的子模块。每个子模块…...

Kubernetes Nginx-Ingress | 禁用HSTS/禁止重定向到https

目录 前言禁用HSTS禁止重定向到https关闭 HSTS 和设置 ssl-redirect 为 false 的区别 前言 客户请求经过ingress到服务后&#xff0c;默认加上了strict-transport-security&#xff0c;导致客户服务跨域请求失败&#xff0c;具体Response Headers信息如下&#xff1b; 分析 n…...

华为eNSP:VRRP

一、VRRP背景概述 在现代网络环境中&#xff0c;主机通常通过默认网关进行网络通信。当默认网关出现故障时&#xff0c;网络通信会中断&#xff0c;影响业务连续性和稳定性。为了提高网络的可靠性和冗余性&#xff0c;采用虚拟路由冗余协议&#xff08;VRRP&#xff09;是一种…...

linux 安装composer

下载composer curl -sS https://getcomposer.org/installer | php下载后设置环境变量&#xff0c;直接通过命令composer -v mv composer.phar /usr/local/bin/composer查看版本看是否安装成功 composer -v...

ESP32-S3模组上跑通ES8388(24)

接前一篇文章:ESP32-S3模组上跑通ES8388(23) 二、利用ESP-ADF操作ES8388 2. 详细解析 上一回解析完了es8388_init函数中的第8段代码,本回继续往下解析。为了便于理解和回顾,再次贴出es8388_init函数源码,在components\audio_hal\driver\es8388\es8388.c中,如下: ​ …...

类文件具有错误的版本 61.0, 应为 55.0 请删除该文件或确保该文件位于正确的类路径子目录中。

类文件具有错误的版本 61.0, 应为 55.0 请删除该文件或确保该文件位于正确的类路径子目录中。 这个错误表明你的项目尝试加载的 .class 文件&#xff08;编译好的 Java 类&#xff09;是用比你的运行环境支持更高版本的 Java 编译的。具体来说&#xff1a; 版本 61.0 对应 Ja…...

AI 的时代,新科技和新技术如何推动跨学科的整合?

在当前AI的发展中&#xff0c;我们面临的一个主要挑战就是融合的问题&#xff0c;这实际上不仅是技术上的融合&#xff0c;还有更深层次的哲学层面的思考。 或许在中国这方面的讨论较少&#xff0c;但在西方哲学和神学的语境中&#xff0c;探讨万物的根本和不同学科之间的联系…...

12.9-12.16学习周报

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract一、SNN与传统ANN的比较1.SNN概述2.SNN神经元和ANN的比较结构3.spikingjelly1.数据格式2.状态的保存和重置3.传播模式4.神经元 二、图数据库1.图数据库…...

CanFestival移植到STM32 F4芯片(基于HAL库)

本文讲述如何通过简单操作就可以把CanFestival库移植到STM32 F4芯片上&#xff0c;作为Slave设备。使用启明欣欣的工控板来做实验。 一 硬件连接 观察CAN报文需要专门的设备&#xff0c;本人从某宝上买了一个兼容PCAN的开源小板子&#xff0c;二十几块钱&#xff0c;通过USB接…...

构建安全的数据库环境:群晖NAS安装MySQL和phpMyAdmin详细步骤

文章目录 前言1. 安装MySQL2. 安装phpMyAdmin3. 修改User表4. 本地测试连接MySQL5. 安装cpolar内网穿透6. 配置MySQL公网访问地址7. 配置MySQL固定公网地址8. 配置phpMyAdmin公网地址9. 配置phpmyadmin固定公网地址 前言 本文将详细讲解如何在群晖NAS上安装MySQL及其数据库管理…...

相机(Camera)硬件组成详解

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 写在前面&#xff1a;可以去B站观看一些相机原理的视频来配合学习&#xff0c;这里推荐&#xff1a;推荐1&#xff0c;推荐2&#xff0c;推荐3 相机&#xff08;Camera&#xff09;是一种复杂的光…...

【Linux———基础IO】

每一滴眼泪&#xff0c;每一次心碎&#xff0c;什么爱能无疚无悔.......................................................................... 文章目录 前言 一、【认识文件I/O】 1.1、【回顾C语言文件I/O】 1.2、【操作系统文件I/O】 1.2.1、【open函数】 1、【open函数的三…...

WebRTC服务质量(03)- RTCP协议

一、前言&#xff1a; RTCP&#xff08;RTP Control Protocol&#xff09;是一种控制协议&#xff0c;与RTP&#xff08;Real-time Transport Protocol&#xff09;一起用于实时通信中的控制和反馈。RTCP负责监控和调节实时媒体流。通过不断交换RTCP信息&#xff0c;WebRTC应用…...

[python SQLAlchemy数据库操作入门]-10.性能优化:提升 SQLAlchemy 在股票数据处理中的速度

哈喽,大家好,我是木头左! 当处理大量数据时,如股票数据,默认的ORM操作可能会显得效率低下。本文将探讨如何通过一些技巧和策略来优化SQLAlchemy ORM的性能,从而提升其在股票数据处理中的速度。 1. 选择合适的数据类型 在定义模型时,选择合适的数据类型对于性能至关重要…...

23种设计模式之中介者模式

目录 1. 简介2. 代码2.1 Mediator &#xff08;中介者接口&#xff09;2.2 ChatRoom &#xff08;具体中介者类&#xff09;2.3 User &#xff08;同事接口&#xff09;2.4 ChatUser &#xff08;具体同事类&#xff09;2.5 Test &#xff08;测试&#xff09;2.6 运行结果 3. …...

MySQL:GROUP_CONCAT分组合并

目录 一、概述二、用法三、分组去重 一、概述 GROUP_CONCAT函数要配合group by才能发挥作用&#xff0c;主要用于分组之后合并某一字段。 二、用法 SELECT GROUP_CONCAT(age) FROM tb_user GROUP BY banner;三、分组去重 SELECT GROUP_CONCAT(DISTINCT age) FROM tb_user GRO…...

递归方式渲染嵌套的菜单项

1. 递归组件 递归方式渲染嵌套的菜单项&#xff0c;这个是非常好的做法。为了避免在每个子菜单上都渲染一个新的 <ul> 标签&#xff0c;可以使用 v-if 来判断是否有子菜单&#xff0c;再决定是否渲染子菜单的部分。 2. 提高性能 对于递归渲染组件&#xff0c;Vue 可能…...

常用Vim操作

vimrc配置 ctags -R * 生成tags文件 set number set ts4 set sw4 set autoindent set cindent set tag~/tmp/log/help/tags 自动补全&#xff1a; ctrln&#xff1a;自动补全 输入&#xff1a; a&#xff1a;从当前文字后插入i&#xff1a;从当前文字前插入s: 删除当前字…...

spark3 sql优化:同一个表关联多次,优化方案

目录 1.合并查询2.使用 JOIN 条件的过滤优化3.使用 Map-side Join 或 Broadcast Join4.使用 Partitioning 和 Bucketing5.利用 DataFrame API 进行优化假设 A 和 B 已经加载为 DataFramePerform left joins with specific conditions6.使用缓存或持久化7.避免笛卡尔积总结 1.合…...

XML 在线格式化 - 加菲工具

XML 在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果...

陪玩系统小程序源码/游戏陪玩APP系统用户端有哪些功能?游戏陪玩小程序APP源码开发

多客陪玩系统-游戏陪玩线下预约上门服务等陪玩圈子陪玩社区系统源码 陪玩系统源码&#xff0c;高质量的陪玩系统源码&#xff0c;游戏陪玩APP源码开发&#xff0c;语音陪玩源码搭建: 线上陪玩活动组局与线下家政服务系统的部署需要综合考虑技术选型、开发流程、部署流程、功能实…...

力扣-图论-9【算法学习day.59】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…...

ViT学习笔记(三) RepViT和TransNext简介

标准ViT的其他模块的功能以及源码解读&#xff0c;在CSDN上有很多优秀文章&#xff0c;参考文章将代码大致过一遍。像我这种只做工程不写论文的&#xff0c;个人认为大致明白就好&#xff0c;用不着特别细究。下面跟踪两个ViT比较新的变种继续深入学习一下&#xff1a;RepViT和…...

大华DSS数字监控系统 attachment_downloadAtt.action 任意文件下载漏洞复现

0x01 产品描述: 大华 DSS 数字监控系统是大华开发的一款安防视频监控系统,拥有实时监视、云台操作、录像回放、报警处理、设备管理等功能。0x02 漏洞描述: 大华DSS数字监控系统 attachment_downloadAtt.action接口存在任意文件读取漏洞,未经身份验证攻击者可通过该漏洞读取…...

Dockerfile容器镜像构建技术

文章目录 1、容器回顾1_容器与容器镜像之间的关系2_容器镜像分类3_容器镜像获取的方法 2、其他容器镜像获取方法演示1_在DockerHub直接下载2_把操作系统的文件系统打包为容器镜像3_把正在运行的容器打包为容器镜像 3、Dockerfile介绍4、Dockerfile指令1_FROM2_RUN3_CMD4_EXPOSE…...

LabVIEW实现MQTT通信

目录 1、MQTT通信原理 2、硬件环境部署 3、云端环境部署 4、程序架构 5、前面板设计 6、程序框图设计 7、测试验证 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利用LabVIEW和常用…...

分布式事物XA、BASE、TCC、SAGA、AT

分布式事务——Seata 一、Seata的架构&#xff1a; 1、什么是Seata&#xff1a; 它是一款分布式事务解决方案。官网查看&#xff1a;Seata 2.执行过程 在分布式事务中&#xff0c;会有一个入口方法去调用各个微服务&#xff0c;每一个微服务都有一个分支事务&#xff0c;因…...

[创业之路-182]:《华为战略管理法-DSTE实战体系》-1-华为的发展历程和战略管理演变

目录 前言、华为在战略管理上做对了什么&#xff1f; 1、前瞻性的战略眼光 2、有效的战略解码 3、灵活的战略调整 4、注重创新和研发 5、以客户为中心的战略导向 6、完善的内部管理体系 一、华为不同时期的战略选择 1.1 华为不同时期的战略选择 1、创业初期&#xff…...

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…...

web 自动化 selenium

1、下载Chrome对应的driver版本 我的chrome是 131.0.6778.109&#xff0c;我下载的driver是131.0.6778.69&#xff08;想找一模一样的&#xff0c;但是没有&#xff09; https://googlechromelabs.github.io/chrome-for-testing/ chromedriver下载 2、配置Chrome deriver及 …...

【OpenCV】Canny边缘检测

理论 Canny 边缘检测是一种流行的边缘检测算法。它是由 John F. Canny 在 1986 年提出。 这是一个多阶段算法&#xff0c;我们将介绍算法的每一个步骤。 降噪 由于边缘检测易受图像中的噪声影响&#xff0c;因此第一步是使用 5x5 高斯滤波器去除图像中的噪声。我们在前面的章…...

D94【python 接口自动化学习】- pytest进阶之fixture用法

day94 pytest的fixture详解 学习日期&#xff1a;20241210 学习目标&#xff1a;pytest基础用法 -- pytest的fixture详解 学习笔记&#xff1a; fixture的介绍 fixture是 pytest 用于将测试前后进行预备、清理工作的代码处理机制。 fixture相对于setup和teardown来说有以…...

关于idea-Java-servlet-Tomcat-Web开发中出现404NOT FOUND问题的解决

在做web项目时&#xff0c;第一次使用servlet开发链接前端和后端的操作&#xff0c;果不其然&#xff0c;遇到了诸多问题&#xff0c;而遇到最多的就是运行项目打开页面时出现404NOT FOUND的情况。因为这个问题我也是鼓捣了好久&#xff0c;上网查了许多资料才最终解决&#xf…...

SpringBoot Maven快速上手

文章目录 一、Maven 1.1 Maven 简介&#xff1a;1.2 Maven 的核心功能&#xff1a; 1.2.1 项目构建&#xff1a;1.2.2 依赖管理&#xff1a; 1.3 Maven 仓库&#xff1a; 1.3.1 本地仓库&#xff1a;1.3.2 中央仓库&#xff1a;1.3.3 私服&#xff1a; 二、第一个 SpringBoot…...

全面解析MySQL底层概念

mysql的概念 &#xff08;1&#xff09;mysql的架构 客户端请求--->连接器&#xff08;验证用户身份&#xff0c;给予权限&#xff09;--->查询缓存&#xff08;存在则直接返回&#xff0c;不存在则执行后续操作 --->分析器&#xff08;对SQL进行词法分析和语法分析…...

【C++】string类

一、C 标准库中的string类 1、string类 string类的文档介绍 首先string并不是STL中的&#xff0c;而是一个类string比STL出现的早 从上面可以看出string是从一个类模板中实例化出来的一个对象 在使用string类是必须要包头文件#include< string > 又因为是std中的类…...

《Python制作动态爱心粒子特效》

一、实现思路 粒子效果&#xff1a; – 使用Pygame模拟粒子运动&#xff0c;粒子会以爱心的轨迹分布并运动。爱心公式&#xff1a; 爱心的数学公式&#xff1a; x16sin 3 (t),y13cos(t)−5cos(2t)−2cos(3t)−cos(4t) 参数 t t 的范围决定爱心形状。 动态效果&#xff1a; 粒子…...