electron-vue 台称串口对接 SerialPort

news2024/11/25 21:31:37

大致流程

1.首先找一个串口工具(sscom5.12.1)试试读取串口是否成功连上;

2.创建electron-vue的项目;

3.安装依赖,调整版本,启动项目;(在electron中使用串口_electron 串口_Jack_Kee的博客-CSDN博客)

4.学习SerialPort Usage | Node SerialPort工具;

5.开发入口文件,测试是否成功获取串口信息

6.成功获取串口后,接收台称数据

7.根据台称说明书与协议,解析台称数据(数值,单位,正负,精度...)

8.完成称重需求

9.完成打印需求

10.完成自动更新

串口工具大概样子

通过切换端口号和波特率,可以查看哪个是台称串口及波特率,我本机的端口号COM3 波特率9600是台秤的串口信息。

创建electron项目(起步 · electron-vue)

跟着官方步骤 创建electron-vue项目,成功启动项目(启动,打包)

 "electron": "^17.4.11",

引入serialport

npm install serialport --save

然后,安装electron-rebuild(用来重新编译serialport包):

npm install --save-dev electron-rebuild

重新编译serialport包

.\node_modules\.bin\electron-rebuild.cmd
 

提示Rebuild Complete,表示编译完成

按照该文章,实现最终效果注意安装依赖版本,

此注明我项目使用的各个版本

"dependencies": {
		"@serialport/parser-byte-length": "^11.0.0",
		"axios": "^0.18.0",
		"electron-updater": "^5.3.0",
		"element-ui": "^2.8.2",
		"lodash": "^4.17.21",
		"multispinner": "^0.2.1",
		"qrcodejs2": "^0.0.2",
		"serialport": "^11.0.0",
		"vue": "^2.5.16",
		"vue-electron": "^1.0.6",
		"vue-router": "^3.0.1",
		"vuex": "^3.0.1",
		"vuex-electron": "^1.0.0",
		"vuex-persistedstate": "^4.1.0"
	},
	"devDependencies": {
		"ajv": "^6.5.0",
		"babel-core": "^6.26.3",
		"babel-loader": "^7.1.4",
		"babel-minify-webpack-plugin": "^0.3.1",
		"babel-plugin-component": "^1.1.1",
		"babel-plugin-transform-runtime": "^6.23.0",
		"babel-preset-env": "^1.7.0",
		"babel-preset-stage-0": "^6.24.1",
		"babel-register": "^6.26.0",
		"cfonts": "^2.1.2",
		"chalk": "^2.4.1",
		"copy-webpack-plugin": "^4.5.1",
		"cross-env": "^5.1.6",
		"css-loader": "^0.28.11",
		"del": "^3.0.0",
		"devtron": "^1.4.0",
		"electron": "^17.4.11",
		"electron-builder": "^23.6.0",
		"electron-debug": "^1.5.0",
		"electron-devtools-installer": "^2.2.4",
		"electron-rebuild": "^3.2.9",
		"file-loader": "^1.1.11",
		"html-webpack-plugin": "^3.2.0",
		"listr": "^0.14.3",
		"mini-css-extract-plugin": "0.4.0",
		"node-loader": "^0.6.0",
		"node-sass": "^4.9.2",
		"sass-loader": "^7.0.3",
		"style-loader": "^0.21.0",
		"url-loader": "^1.0.1",
		"vue-html-loader": "^1.2.4",
		"vue-loader": "^15.2.4",
		"vue-style-loader": "^4.1.0",
		"vue-template-compiler": "^2.5.16",
		"webpack": "^4.15.1",
		"webpack-cli": "^3.0.8",
		"webpack-dev-server": "^3.1.4",
		"webpack-hot-middleware": "^2.22.2",
		"webpack-merge": "^4.1.3"
	}
 

 SerialPort (注意不同版本使用方式不同)

serialport:10.x.x

import { SerialPort } from 'serialport'

serialport:7.x.x

const SerialPort = require('serialport')

项目操作案例:

const { ByteLengthParser } = require("@serialport/parser-byte-length");
let ports = []; // 串口list
let mainWindow = null;  // electron 窗口

function registerIpcEvent() {
  // 发送可选串口
  SerialPort.list().then((ports) => {
    setTimeout(() => {
      mainWindow.webContents.send("send-port-info", ports);
    }, 1000);
  });
  //  关闭窗口
  app.on("window-all-closed", function () {
    if (process.platform !== "darwin") {
      ports.forEach((e) => {
        e && e.close();
      });
      app.quit();
    }
  });
  ipcMain.on("closeApp", () => {
    ports.forEach((e) => {
      e && e.close();
    });
    app.quit();
  });

  // 重新初始化串口
  ipcMain.on("init-port", (event, args) => {
    initPort(args);
  });
  // 关闭串口
  ipcMain.on("close-serialport", (event, args) => {
    const currentPort = ports.find((i) => i.path === args.name);
    currentPort && currentPort.close();
    if (currentPort && !currentPort.isOpen) {
      event.reply("close-serialport", { name: args.name });
    }
    ports = ports.filter((e) => e !== currentPort);
  });

  // 打开某个串口
  ipcMain.on("open-serialport", (event, args) => {
    const serialport = new SerialPort(
      {
        path: args.name,
        baudRate: +args.baudRate,
      },
      (err) => {
        console.log(err);
        if (err) {
           event.reply("open-serialport", {
             hasError: true,
             ...args,
             message: err,
           });
        } else {
          event.reply("open-serialport", args);
          ports.push(serialport);
        }
      }
    );
    // 根据说明书数据量字节17一次传输 也可以通过其他方式截取
    const parser = serialport.pipe(new ByteLengthParser({ length: 17  }));
    parser.on("data", (data) => {
     // xxxxxxx 
    });
    serialport.on("open", () => {});
  });
}

function initPort(bool) {
  if (bool) {
    SerialPort.list().then((ports) => {
      setTimeout(() => {
        mainWindow.webContents.send("send-port-info", ports);
      }, 1000);
    });
  }
}

还可以通过下面的形式截断串口数据 

正则匹配:parser-regex  

 一种转换流,它使用正则表达式来拆分传入的文本。

要使用Regex解析器,请提供一个正则表达式来拆分传入的文本。数据作为可由编码选项控制的字符串发出(默认为utf8)

const { SerialPort } = require('serialport')
const { ReadlineParser } = require('@serialport/parser-readline')
const port = new SerialPort({ path: '/dev/ROBOT', baudRate: 14400 })

const parser = port.pipe(new ReadlineParser({ delimiter: '\r\n' }))
parser.on('data', console.log)

分隔符截取:parser-readline 

在接收到换行符后发出数据的转换流。若要使用Readline分析器,请提供分隔符(默认为\n)。数据以可由编码选项控制的字符串形式发出(默认为utf8)。

const { SerialPort } = require('serialport')
const { RegexParser } = require('@serialport/parser-regex')
const port = new SerialPort({ path: '/dev/ROBOT', baudRate: 14400 })

const parser = port.pipe(new RegexParser({ regex: /[\r\n]+/ }))
parser.on('data', console.log)

开发入口文件

const { app, BrowserWindow, ipcMain, Menu, dialog } = require("electron");
const { SerialPort } = require("serialport");
const _ = require("lodash");
const { ByteLengthParser } = require("@serialport/parser-byte-length");
import { updateHandle } from "../renderer/utils/update.js";

let ports = [];
const path = require("path");
const fs = require("fs");


//读取本地更新配置json文件 包含是否调试 请求地址 更新地址...
const File_path = path
  .join(path.resolve("./../"), "/config.json")
  .replace(/\\/g, "/");
const config_res = JSON.parse(fs.readFileSync(File_path, "utf-8"));
let mainWindow = null;
const winURL =
  process.env.NODE_ENV === "development"
    ? `http://localhost:9080`
    : `file://${__dirname}/index.html`;


app.whenReady().then(() => {
  createWindow();

  registerIpcEvent();
  // getPrinterList(); 注释打印
  config_res.IS_DEBUG && createMenu();
  app.on("activate", function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: config_res.IS_DEBUG, // 是否有边框窗口
    fullscreen: !config_res.IS_DEBUG, // 全屏
    webPreferences: {
      // preload: path.join(__dirname, "preload.js"),
      nodeIntegration: true,
      contextIsolation: false,
      enableRemoteModule: true,
      webviewTag: true,
      webSecurity: false,
    },
  });
  config_res.IS_DEBUG ? mainWindow.webContents.openDevTools() : ""; // 生产模式调试开关

  setTimeout(() => {
    mainWindow.loadURL(winURL);
  }, 1000);
  // 监听崩溃
  mainWindow.webContents.on("crashed", () => {
    const options = {
      type: "error",
      title: "系统意外终止",
      message: "可点击以下按钮",
      buttons: ["点击重启", "直接退出"],
    };
    dialog.showMessageBox(options, (index) => {
      if (index === 0) reloadWindow(mainWindow);
      else app.quit();
    });
  });

}

function createMenu() {
  // main
  const template = [
    {
      label: "调试",
      click: function (item, focusedWindow) {
        if (focusedWindow) {
          config_res.IS_DEBUG && focusedWindow.toggleDevTools();
        }
      },
    },

  ];
  const menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
}
function initPort(bool) {
  if (bool) {
    SerialPort.list().then((ports) => {
      setTimeout(() => {
        mainWindow.webContents.send("send-port-info", ports);
      }, 1000);
    });
  }
}
 
function registerIpcEvent() {
  // 发送可选串口
  SerialPort.list().then((ports) => {
    setTimeout(() => {
      mainWindow.webContents.send("send-port-info", ports);
    }, 1000);
  });
  //  关闭窗口
  app.on("window-all-closed", function () {
    if (process.platform !== "darwin") {
      ports.forEach((e) => {
        e && e.close();
      });
      app.quit();
    }
  });
  ipcMain.on("closeApp", () => {
    ports.forEach((e) => {
      e && e.close();
    });
    app.quit();
  });

  // 重新初始化串口
  ipcMain.on("init-port", (event, args) => {
    initPort(args);
  });
  // 关闭串口
  ipcMain.on("close-serialport", (event, args) => {
    const currentPort = ports.find((i) => i.path === args.name);
    currentPort && currentPort.close();
    if (currentPort && !currentPort.isOpen) {
      event.reply("close-serialport", { name: args.name });
    }
    ports = ports.filter((e) => e !== currentPort);
  });

  // 打开某个串口
  ipcMain.on("open-serialport", (event, args) => {
    const serialport = new SerialPort(
      {
        path: args.name,
        baudRate: +args.baudRate,
      },
      (err) => {
        console.log(err);
        if (err) {
           event.reply("open-serialport", {
             hasError: true,
             ...args,
             message: err,
           });
        } else {
          event.reply("open-serialport", args);
          ports.push(serialport);
        }
      }
    );
    // 数据量字节截取拉大 获取大区间集合
    const parser = serialport.pipe(new ByteLengthParser({ length: 17 * 10 }));
    parser.on("data", (data) => {
      let dataString = data.toString("hex");
 
      sendData(event, {
       // xxxxx 需要发送给页面的信息
      });
    });
    serialport.on("open", () => {});
  });
}
function sendData(event, value) {
  event.sender.send("send-data", value);
}
 
 

需要展示串口信息的页面

1. 发送init-port 初始化请求

const { ipcRenderer } = require("electron");

ipcRenderer.send("init-port", true);

 2.页面接收解析后的串口信息函数

  data() {
    return {
      portNameList: [], // 多称选择
      openedPort: [], // 已经打开的称信息
      currPortData: 0.0, // 显示重量
      currName: "", // 称名称
      currRate: "9600", // 波特率
      sign: "", //秤 正 负
      rateOption: rateOption,// 波特率选择
    };
  },  
  watch: {
    currName(val, oldval) {
      if (val !== "" && oldval !== "") {
        this.openOrclose("open", oldval);
      }
    },
  },  
 methods: {
  listSerialPorts() {
      ipcRenderer.on("open-serialport", (event, args) => {
        console.log("open-serialport", args);
        if (args.hasError) {
          alert(args.message);
        } else {
          this.openedPort.push(args.name);
          console.log("open", this.openedPort);
        }
      });

      ipcRenderer.on("close-serialport", (event, args) => {
        const index = this.openedPort.findIndex((i) => i === args.name);
        this.openedPort.splice(index, 1);
        console.log("close", this.openedPort);
      });

      ipcRenderer.on("send-port-info", (event, args) => {
        for (let port of args) {
          this.portNameList.push({
            portName: `称台${this.portNameList.length + 1}`,
            name: port.path,
            path: port.path,
          });
        }

        this.currName = this.portNameList && this.portNameList[0].name;
        console.log("send-port-info获取的串口", this.portNameList);
        this.openOrclose("open");
      });

      ipcRenderer.on("send-data", (event, args) => {
        console.log("send-data", args, this.resData);
        this.currPortData = args.value; // 接收传入的信息 用于展示
       });
    },

    openOrclose(type, oldname = "") {
      if (this.portNameList.length <= 0) {
        this.$message.error("台称读取失败,请重新进入页面或重新进入系统!");
        return;
      }
      const name = this.currName;
      const baudRate = this.currRate;
      if (oldname !== "" && this.openedPort.includes(oldname)) {
        this.sendMessageToMain("close-serialport", { name: oldname });
      }
      if (type === "close") {
        if (this.openedPort.includes(name)) {
          this.sendMessageToMain("close-serialport", { name });
        }
      } else {
        this.sendMessageToMain("open-serialport", {
          name: this.currName,
          baudRate: this.currRate,
        });
      }
    },
}

根据台称说明书与协议,解析台称数据(数值,单位,正负,精度...)

先看说明书梅特勒托利多METTLER TOLEDO

  

 1.从说明书的是数据含有17/18字节 我通过走数据查到是17个字节

 const bufferArray = dataString.split("0d")[1]; 
// 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d

 2.连续输出格式 5-10字节代表的称指示重量,11-16字节表示皮重重量

 

3.数据是没有小数点的要通过解析算出小数点的位置和正负号

4.状态A,状态B,状态C 需要解析分别表示一些内容

 状态A说明,main.js里面具体操作

      let dataString = data.toString("hex");
      const bufferArray = dataString.split("0d")[1]; 
      // 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d
      const bufferfloat = bufferArray.slice(2, 4); 
      // 小数点位置 截取byte第二位 根据说明书转为二进制查看0,1,2位的值判断小数点位置
      let float = parseInt(bufferfloat, 16).toString(2).substr(-3); 
      //16进制转为2进制 截取后三位;

      let de = 1;

      if (float === "100") {
        de = 100;
      } else if (float === "101") {
        de = 1000;
      } else if (float === "110") {
        de = 10000;
      } else if (float === "111") {
        de = 100000;
      }
      
      var value = Number(weight) / de; 

  状态B说明,main.js里面具体操作

 

      let dataString = data.toString("hex");
      const bufferArray = dataString.split("0d")[1]; 
        // 02 34 30 20 20 20 20 32 30 34 20 20 20 30 30 30 0d
      const buffersign = bufferArray.slice(4, 6); 
        // 单位值位置 截取byte第2位 共2位
      let signCode = parseInt(buffersign, 16).toString(2).substr(-2, 1); 
        //16进制转为2进制 截取倒数第2位; 从倒数第2位开始截取1个长度的字符
        // signCode 0=正1=负

   状态C说明,main.js里面具体操作

 


      let dataString = data.toString("hex");
      const bufferArray = dataString.split("0d")[1]; 
        // 02 34 30 20 /20 20 20 32 30 34/ 20 20 20 30 30 30 0d
      const bufferunit = bufferArray.slice(6, 8); // 单位值位置 截取byte第3位 共2位
      let unitCode = parseInt(bufferunit, 16).toString(2).substr(-3);
      let unit = null;
      
if (unitCode === "000") {
        unit = "kg";
      } else {
        unit = "g";
      }

main.js 文件处理

  // 打开某个串口
  ipcMain.on("open-serialport", (event, args) => {
    const serialport = new SerialPort(
      {
        path: args.name,
        baudRate: +args.baudRate,
      },
      (err) => {
        console.log(err);
        if (err) {
           event.reply("open-serialport", {
             hasError: true,
             ...args,
             message: err,
           });
        } else {
          event.reply("open-serialport", args);
          ports.push(serialport);
        }
      }
    );
    // 数据量字节17为一组 但发送太过频繁 所以增大10倍 减少页面渲染压力
    const parser = serialport.pipe(new ByteLengthParser({ length: 17 * 10 }));
    parser.on("data", (data) => {
      let dataString = data.toString("hex");
      const bufferArray = dataString.split("0d")[1]; 
        // 02 34 30 20 /20 20 20 32 30 34/ 20 20 20 30 30 30 0d
      const bufferfloat = bufferArray.slice(2, 4); 
        // 小数点位置 截取byte第二位 根据说明书转为二进制查看0,1,2位的值判断小数点位置
      const buffersign = bufferArray.slice(4, 6); 
        // 正负号位置 截取byte第2位 共2位
      const bufferunit = bufferArray.slice(6, 8); 
        // 单位值位置 截取byte第3位 共2位
      const bufferweight = bufferArray.slice(8, 20);
         // 重量值位置 截取byte第5-10位 共6位
      const bufferpeel = bufferArray.slice(20, 32); 
        // 皮重值位置 截取byte第11-16位 共6位

      let float = parseInt(bufferfloat, 16).toString(2).substr(-3); 
        //16进制转为2进制 截取后三位;
      let signCode = parseInt(buffersign, 16).toString(2).substr(-2, 1); 
        //16进制转为2进制 截取倒数第2位; 从倒数第2位开始截取1个长度的字符。
      let unitCode = parseInt(bufferunit, 16).toString(2).substr(-3);

      let weight = hexToString(bufferweight.toString("hex"));
      let peel = hexToString(bufferpeel.toString("hex"));

      let de = 1;
      let unit = null;
      let sign =  signCode;  // sign 0=正1=负
      if (float === "100") {
        de = 100;
      } else if (float === "101") {
        de = 1000;
      } else if (float === "110") {
        de = 10000;
      } else if (float === "111") {
        de = 100000;
      }
      
      if (unitCode === "000") {
        unit = "kg";
      } else {
        unit = "g";
      }
      var value = Number(weight) / de;
      console.log("weights",value,float,weight,unit,peel,bufferArray,dataString,sign);
      sendData(event, {
        value,
        float,
        weight,
        unit,
        de,
        peel,
        bufferArray,
        dataString,
        sign
      });
    });
    serialport.on("open", () => {});
  });

function sendData(event, value) {
  event.sender.send("send-data", value);
}

/**
 * 将16进制字符串进行分组,每两个一组
 * @param  {[String]} str [16进制字符串]
 * @return {[Array]}     [16进制数组]
 */
//处理中文乱码问题
function utf8to16(str) {
  var out, i, len, c;
  var char2, char3;
  out = "";
  len = str.length;
  i = 0;
  while (i < len) {
    c = str.charCodeAt(i++);
    switch (c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        out += str.charAt(i - 1);
        break;
      case 12:
      case 13:
        char2 = str.charCodeAt(i++);
        out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
        break;
      case 14:
        char2 = str.charCodeAt(i++);
        char3 = str.charCodeAt(i++);
        out += String.fromCharCode(
          ((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
        );
        break;
    }
  }
  return out;
}

function hexToString(str) {
  // 30 空格 0d跨行
  var val = "",
    len = str.length / 2;
  for (var i = 0; i < len; i++) {
    val += String.fromCharCode(parseInt(str.substr(i * 2, 2), 16));
  }
  return utf8to16(val);
}

detail.vue 页面组装秤重量

console.log("weights",value,float,weight,unit,peel,bufferArray,dataString,sign);
      // 接收到数据 处理数据展示样子 带上小数点,正负号,显示称设置的皮重
      ipcRenderer.on("send-data", (event, args) => {
        if (
          this.resData &&
          ["kg", "千克", "公斤"].includes(this.resData.unit)
        ) {
          if (args.unit === "kg") {
            this.currPortData = args.value;
          } else {
            this.currPortData = args.value / 1000;
          }
        } else if (
          ["g", "克", "斤", "ml", "毫升"].includes(this.resData.unit)
        ) {
          if (args.unit === "g") {
            this.currPortData = args.value;
          } else {
            this.currPortData = args.value * 1000;
          }
        }
        if (args.sign !== "") {
          this.sign = args.sign === "0" ? "" : "-";
        }
      });

9.完成打印需求

待更新..

10.完成自动更新

待更新..

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

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

相关文章

Eclipse控制台输出log4j日志乱码解决

1. 出现乱码可能是编码格式对应不起来&#xff0c;主要是Eclipse控制台编码和log4j编码的匹配 log4j.properties 主要是查看这两个地方编码一致不一致&#xff0c;如果不一致则手动更改成一致编码&#xff0c;GBK或UTF-8

Ribbon 负载均衡服务调用

文章目录 1 SpringCloud Load Balance2 总结:3 Ribbon工作流程&#xff1a;4 自定义Ribbon 负载均衡算法&#xff1a;4.1 iRule接口&#xff1a;4.2 Ribbon自带的负载均衡算法&#xff1a;4.3 负载均衡算法替代&#xff1a;4.3.1、在非启动类包及子包下创建配置类4.3.2、定义4.…

LayUI动态选项卡的使用

目录 一、Tab选项卡 1.什么是Tab选项卡 2.Tab选项卡的作用 二、Tab选项卡的详细使用步骤 1.参考官网&#xff0c;选择自己喜欢的选项卡 ​ 2.将静态选项卡转换成动态选项卡 3.将选项卡的标签名变成实际菜单名 4.重名选项卡不能二次打开 5.切换到指定选项卡 6.iframe的…

Python示例解释观察者模式

观察者模式是一种常用的设计模式&#xff0c;用于在对象之间建立一种一对多的依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;所有依赖于它的对象都会得到通知并自动更新。下面通过一个简单的例子来解释观察者模式的概念。 假设我们有一个名为"主题"&a…

神经网络万能近似定理探索与实验

神经网络万能近似定理探索与实验 今天&#xff0c;我们来做神经网络万能近似定理的探索与实验。关于万能近似定理呢&#xff0c;就是说&#xff0c;对这个神经元的输出进行非线性激活函数处理&#xff0c;单层的神经网络就可以拟合任何一个函数。 下面先看我们搭建的第一个网…

docker部署达梦数据库

一、下载安装包 安装步骤&#xff1a; 先跟着网页走 注&#xff1a;第二步导入安装包&#xff0c;如果是在自己电脑上&#xff0c;就不一定要拷贝到/opt目录下&#xff0c;在安装包所在目录地址栏输入cmd&#xff0c;进入终端进行操作即可 操作到正常打印日志&#xff08;如…

java项目之房屋租赁系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的房屋租赁系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&…

尚硅谷Docker实战教程-笔记14【高级篇,Docker容器监控之CAdvisor+InfluxDB+Granfana、Docker终章总结】

尚硅谷大数据技术-教程-学习路线-笔记汇总表【课程资料下载】视频地址&#xff1a;尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 尚硅谷Docker实战教程-笔记01【基础篇&#xff0c;Docker理念简介、官网介绍、平台入门图解、平台架构图解】…

centos7.9 连续登录失败处理

如果有人恶意破解你的服务器&#xff0c;下面的操作可以起到一定的作用&#xff0c;连续登录失败锁定账户一段时间&#xff0c;让攻击者的成本增加&#xff0c;从而降低服务器被恶意破解的风险。 参考博客 https://blog.csdn.net/hjxloveqsx/article/details/129004832 https…

MyBatis 中如何使用分页

MyBatis 中如何使用分页 在实际的项目开发中&#xff0c;我们经常需要对数据库中的数据进行分页查询&#xff0c;以提高数据查询的效率和用户体验。MyBatis 是一种流行的 Java 持久层框架&#xff0c;它提供了多种分页查询的方式&#xff0c;本文将介绍其中常用的两种方式&…

MySQL数据库详解

文章目录 引言1. SQL1.1 SQL通用语法1.2 SQL分类1.3 DDL1.3.1 数据库操作1.3.2 表操作1.3.2.1 表操作-查询创建1.3.2.2 表操作-数据类型1.3.2.3 表操作-修改1.3.2.3 表操作-删除 1.4 DML1.4.1 添加数据1.4.2 修改数据1.4.3 删除数据 1.5 DQL1.5.1 基本语法1.5.2 基础查询1.5.3 …

车载以太网之SOME/IP-SD专题篇

前言 首先,请问大家几个小小问题,你清楚: 你知道什么是SOME/IP SD吗?SOME/IP-SD报文是如何发送与接收的呢?SOME/IP-SD 存在哪几种Entry Type呢?SOME/IP-SD内部状态机转换又是如何?今天,我们就来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲: 目录…

Sentinel 规则详解

Sentinel 规则 流控规则 flow1、QPS流控2、并发线程数流控3、流控模式4、流控效果 熔断&#xff08;降级&#xff09;规则 degrade1、慢调用比例2、异常比例3、异常数 热点规则 param-flow授权规则 authority1、应用场景2、自定义来源3、授权规则配置 系统规则 前言&#xff1a…

代码随想录day1 | 704.二分查找 27.移除元素

一、二分 1、二分易错点 1、循环变量 while(left < right) 还是while(left <right)2、判断条件 if(num[mid] > tar) mid right 还是 mid right -12、循环不变量 [left, right] [left, right) (left, right]3、左闭右闭写法 当时左闭右闭时&#xff0c;while循…

2、基于kubeadm搭建K8S环境

目录 一、环境说明 二、初始化所有节点 三、修改三台服务器主机名&#xff0c;并写入host文件 四、调整内核参数 五、所有节点安装Docker 六、所有节点配置K8S源 七、所有节点安装kubeadm&#xff0c;kubelet和kubectl 八、部署 kubernetes Master 节点 九、k8s-node …

基于docker的keepalived+MySQL主从实现MySQL高可用

因生产需要对MySQL做高可用&#xff0c;同时&#xff0c;资源有限&#xff0c;因此采用双节点主从keepalived方式实现高勇。另外因需要大批量部署MySQL集群&#xff0c;需要采用模板化部署&#xff0c;本方案采用将MySQL容器化&#xff0c;实现MySQL模板化配置部署。 部署环境及…

SpringMVC学习笔记--上篇

SpringMVC学习笔记 1、SpringMVC 1.1、什么是SpringMVC Spring MVC是Spring Framework的一部分&#xff0c;是基于Java实现MVC的轻量级Web框架。 1.2、SpringMVC的特点 Spring MVC的特点&#xff1a; 轻量级&#xff0c;简单易学高效 , 基于请求响应的MVC框架与Spring兼容…

Python深度学习-有向图合并、排序、最长路径计算

一、有向图方向、权重表示方法 Python通常使用有向图中边的起点和终点来表示边的方向。例如&#xff0c;如果有一条从节点A到节点B的边&#xff0c;则可以使用以下方式表示该有向边&#xff1a; graph {A: {B: 1} }在这个例子中&#xff0c;节点A和节点B之间存在一条权重为1…

谷歌插件下载Redux DevTools管理Redux数据

我们在做 react-redux开发时 很多时候可能无法确定自己的数据有没有成功导进来 这里就有个不错的谷歌插件推荐给大家 大家可以下载我的资源 谷歌插件Redux DevTools 这里 我们打开 Google Chrome浏览器 然后 直接在谷歌浏览器上访问 chrome://extensions/ 如果你的第一次进入 …