从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享

news2024/11/29 20:50:49

说在前面

平时写文章或写代码的时候,都少不了需要将本地图片转成在线图片链接,大家都是使用什么工具进行转换的呢?相信很多人都有自己的图床工具,今天来给大家介绍一下,怎么基于Gitee和Electron来开发一个便捷的图床工具,支持图片的上传、删除、复制和快速生成markdown链接、快捷键唤起和隐藏面板,粘贴剪切板图片上传等……

框架选型

原本只是想写一个Chrome插件来实现简单功能,后面发现Chrome插件的局限性太大了,所以最后还是选择使用Electron来制作一个桌面程序。

存储方面我们可以直接使用gitee来用做图库存储,不需要额外去购买存储服务器。

准备工作

一、Gitee创建图床仓库目录

1、Gitee注册

直接到Gitee官网: Gitee - 基于 Git 的代码托管和研发协作平台 ,点击注册即可。

image.png

2、仓库创建

注册完账号后直接登录,在首页点击右上角的加号可以新建仓库

image.png

仓库信息自行填写即可

image.png

创建完仓库后我们可以新建一个文件夹用来存储图片:

image.png

3、生成授权码

打开设置里的私人令牌页面

image.png

点击生成新令牌,根据提示填写信息即可,注意保存好生成的令牌。

image.png

二、搭建electron项目

我们可以先搭建一个简单的electron项目:

  1. 安装 Node.js:确保你的电脑上已经安装了 Node.js。你可以从 Node.js 官方网站(https://nodejs.org)下载并安装最新版本的 Node.js。

  2. 创建项目目录:在你想要创建项目的位置,创建一个新的文件夹作为项目目录。

  3. 初始化项目:打开命令行终端,进入到项目目录,并执行以下命令初始化一个新的 npm 项目:

npm init -y
  1. 安装 Electron:在命令行终端中执行以下命令来安装 Electron:
npm install electron
  1. 创建主文件:在项目目录中创建一个名为 main.js 的文件,作为 Electron 应用的主文件。

  2. 编写主文件代码:在 main.js 文件中编写 Electron 应用的主要逻辑。例如,下面是一个简单的示例:

const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
}

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

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit();
});
  1. 创建 HTML 文件:在项目目录中创建一个名为 index.html 的文件,作为 Electron 应用的初始页面。

  2. 编写 HTML 文件代码:在 index.html 文件中编写你的应用界面的 HTML 代码。

  3. package.json 文件中添加启动命令:打开 package.json 文件,在 "scripts" 部分添加以下内容:

"scripts": {
  "start": "electron ."
}
  1. 启动 Electron 应用:在命令行终端中执行以下命令来启动 Electron 应用:
npm start

功能实现

前面准备工作全都完成后,现在我们就有了一个简单electron项目架子和一个gitee仓库,可以开始来实现相关的功能了。

一、git操作

gitee提供了api文档,我们可以通过gitee的api文档来对我们的仓库进行上传图片和获取图片的操作。

gitee API文档地址:https://gitee.com/api/v5/swagger#/getV5ReposOwnerRepoStargazers?ex=no

这里我将需要使用到的功能写成了一个类:

1、初始化,获取配置信息
  • accessToken

用户授权码,也就是我们前面生成的私人令牌。

image.png

  • username

仓库所属空间地址(企业、组织或个人的地址path),如下图:

image.png

  • repo

仓库路径(path),如下图:

image.png

  • dirPath

图片存放目录地址,如下图:

image.png

  • branchName

分支名,默认为master,我们可以修改成指定分支:

image.png

  init(config = {}) {
    // 设置 Gitee 仓库信息和目录路径
    this.username = config.username;
    this.repo = config.repo;
    this.accessToken = config.accessToken;
    this.branchName = config.branchName || "master";
    this.apiUrl = "https://gitee.com/api/v5/repos/";
    this.dirPath = config.dirPath;
  }

将以上配置信息在程序的配置里设置好即可:

image.png

2、上传图片到gitee图床目录下

根据API文档进行请求即可:

  async uploadToGitee(base64Data) {
    try {
      const formData = new FormData();
      formData.append("content", base64Data);
      formData.append("access_token", this.accessToken);
      formData.append("message", "上传图片");

      const timeStamp = new Date().getTime();
      Toast.showLoading("正在上传");
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}${timeStamp}.jpg`,
        {
          method: "POST",
          body: formData,
        }
      );
      Toast.hide();
      if (!response.ok) {
        throw new Error("上传图片失败");
      }

      const data = await response.json();
      Toast.showToast("图片上传成功!");
      return data.content.download_url;
    } catch (error) {
      console.error(error);
      Toast.showToast("图片上传失败!");
      throw error;
    }
  }
3、获取gitee图床目录下的所有图片

根据API文档进行请求即可:

async getImg() {
    try {
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}`,
        {
          headers: {
            Authorization: `token ${this.accessToken}`,
          },
        }
      );

      if (!response.ok) {
        throw new Error("获取图片列表失败");
      }

      const data = await response.json();

      // 筛选出图片文件
      const imageFiles = data.filter(
        (file) =>
          file.type === "file" && file.name.match(/\.(jpg|jpeg|png|gif)$/i)
      );

      return imageFiles;
    } catch (error) {
      console.error(error);
      throw error;
    }
  }
4、删除gitee图床目录下指定图片

根据API文档进行请求即可:

  async deleteImg(fileName, sha, cb) {
    try {
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}/${fileName}?access_token=${this.accessToken}&ref=${this.branchName}`,
        {
          method: "DELETE",
          headers: {
            "Content-Type": "application/json;charset=utf-8",
          },
          body: JSON.stringify({
            message: "删除图片",
            sha,
            prune: true,
          }),
        }
      );

      if (!response.ok) {
        throw new Error("删除图片失败");
      }

      Toast.showToast("删除成功");
      cb && cb();
    } catch (error) {
      console.error(error);
      Toast.showToast("删除失败");
      throw error;
    }
  }

二、拖拽点击、粘贴、选择文件夹上传图片

我们可以通过三种方式来上传我们本地的图片

image.png

image.png

1、拖拽或点击上传图片

前面有写了一篇实现拖拽或点击上传图片的文章,这里就不详细再赘述了,有兴趣的可以去看看:《文件拖拽上传功能已经烂大街了,你还不会吗?》

2、粘贴上传

平时我们经常会使用到截图,所以我们希望可以直接将截图粘贴到工具中进行上传,这里我们可以通过监听页面上的paste事件,直接读取剪切板的图片展示到页面上并进行上传。

document.addEventListener("paste", function (e) {
  const items = e.clipboardData.items;

  for (const item of items) {
    if (item.type.indexOf("image") !== -1) {
      const blob = item.getAsFile();
      showPreview(blob);
    }
  }
});
3、选择文件夹上传文件夹中的所有图片

一张图片一张图片上传太慢了,所以我们也支持直接选择一个文件夹,将文件夹里的所有图片一次性上传到gitee上,

    1. 创建一个 HTML 文件,包含一个用于选择文件夹的 <input> 元素和一个按钮用于触发上传操作。代码如下:
<input
    type="file"
    style="display: none"
    id="folderInput"
    onchange="uploadImages()"
    webkitdirectory
    multiple
  />
  <button class="upload-btn" onclick="selectFolder()" style="background: #FFD04C;">
    选择文件夹上传
  </button>

webkitdirectory:告诉浏览器文件选择器应该允许选择文件夹(目录),而不仅仅是单个文件。

multiple:告诉浏览器文件选择器应该允许选择多个文件或文件夹。

  • 2、获取到选择的文件并做相关处理
function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => {
      const base64String = reader.result.split(",")[1];
      resolve(base64String);
    };
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}
function selectFolder() {
  const folderInput = document.getElementById("folderInput");
  folderInput.click();
}

async function uploadImages() {
  const folderInput = document.getElementById("folderInput");
  let files = folderInput.files || [];
  files = [...files].filter((file) => file.type.startsWith("image/"));

  for (let i = 0; i < files.length; i++) {
    Toast.showLoading(`上传中,${i}/${files.length}`);
    const file = files[i];
    const base64Data = await blobToBase64(file);
    await gitOperate.uploadToGitee(base64Data,false);
  }
  Toast.hide();
  Toast.showToast(`已全部上传`);
  waterfall.init();
}

三、瀑布流展示图片

image.png

上传完图片后,我们还希望可以看到之前上传的图片,这里我们需要对图片列表做一个瀑布流展示。

image.png

之前我也有写过一个瀑布流组件详细的实现步骤,有兴趣的同学可以看看:《Vue封装一个瀑布流图片容器组件》。

这里我们可以通过原生JavaScrip来快速实现一个,具体代码如下:

class WaterfallContent {
  constructor(config = {}) {
    this.init(config);
  }
  init(config = {}) {
    this.imgList = config.imgList || [];
    this.column = config.column || 8;
    this.imgMargin = config.imgMargin || 0.5;
    this.domId = config.domId || "waterfall-container";
    this.minHeight = [];
    this.arr = [];
    const ul = document.getElementById(this.domId);
    ul.innerHTML = "";
  }
  async create(imgList = this.imgList, cb) {
    this.init();
    this.imgList = imgList;
    const ul = document.getElementById(this.domId);
    ul.innerHTML = "";
    let trueWidth = Math.floor(
      (100 - this.column * this.imgMargin * 2) / this.column
    );
    let trueWidthPx = 0;
    for (let i = 0; i < this.column; i++) {
      let li = document.createElement("li");
      li.style.listStyle = "none";
      li.style.float = "left";
      li.style.width = `${trueWidth}%`;
      li.style.margin = `0 ${this.imgMargin}%`;
      li.classList.add("git-img");

      ul.appendChild(li);
      this.arr.push(li);
      this.minHeight.push(0);
      trueWidthPx = li.offsetWidth;
    }
    this.loadHandler(trueWidthPx, cb);
  }
  getBase64(file) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    return new Promise((resolve) => {
      reader.onload = () => {
        resolve(reader.result);
      };
    });
  }
  getImgPx(img, maxWidth) {
    const image = new Image();
    image.src = img;
    return new Promise((resolve) => {
      image.onload = () => {
        const width = image.width;
        const height = image.height;
        image.width = maxWidth;
        image.height = image.height * (maxWidth / width);
        resolve({ width, height, image });
      };
    });
  }
  async loadHandler(trueWidth, cb) {
    for (let i = 0; i < this.imgList.length; i++) {
      const imgItem = this.imgList[i];
      const src = imgItem.download_url;
      const res = await this.getImgPx(src, trueWidth);
      const { image } = res;
      const minHeight = this.minHeight;
      const arr = this.arr;
      // 高度数组的最小值
      const min = Math.min.apply(null, minHeight);
      // 高度数组的最小值索引
      const minIndex = minHeight.indexOf(min);
      // 克隆一份图片
      const im = image.cloneNode(true);
      im.setAttribute("data-sha", imgItem.sha);
      im.onclick = this.imgClick;
      // 将图片假如对应最小值索引的容器中
      arr[minIndex].appendChild(im);
      // 更新最小值索引的容器的高度
      minHeight[minIndex] += im.height;
      if (i === 0 && cb) {
        cb();
      }
    }
  }
}

四、自定义鼠标右键菜单栏

image.png

查看图片的时候我们希望可以对图片进行操作,这里的操作我们通过鼠标右键点击弹出,所以我们可以实现一个自定义鼠标右键菜单栏。

具体代码如下:

class MouseMenu {
  constructor(config) {
    this.menuClass = config.menuClass || "j-mouse-menu";
    this.menuId = config.menuId || "JMouseMenu";
    this.contentId = config.contentId || "j-mouse-content";
    this.init();
  }
  init() {
    const dom = document.getElementById(this.contentId);
    dom.oncontextmenu = (e) => {
      const clickItem = e.path[0];
      if (clickItem.localName !== "img") return;
      const menu = document.getElementById(this.menuId);
      this.clickItem = clickItem;
      this.hideAllMenu();
      // 自定义body元素的鼠标事件处理函数
      e = e || window.event;
      e.preventDefault();
      let scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop; // 获取垂直滚动条位置
      let scrollLeft =
        document.documentElement.scrollLeft || document.body.scrollLeft; // 获取水平滚动条位置
      menu.style.display = "block";
      let left = e.clientX + scrollLeft;
      let top = e.clientY + scrollTop;
      if (menu.offsetHeight + top > window.innerHeight) {
        top = window.innerHeight - menu.offsetHeight - 5;
      }
      if (menu.offsetWidth + left > window.innerWidth) {
        left = window.innerWidth - menu.offsetWidth - 5;
      }
      menu.style.left = left + "px";
      menu.style.top = top + "px";
      document.onclick = () => {
        this.hideAllMenu();
      };
    };
  }
  hideAllMenu() {
    const jMenu = document.getElementsByClassName("j-mouse-menu");
    for (const item of jMenu) {
      item.style.display = "none";
    }
  }
}

五、Toast提示功能

image.png

交互少不了Toast弹窗提示,这里我们使用JavaScrip简单实现一个,具体代码如下:

class ToastC {
  constructor(config) {
    this.config = config;
    this.init();
  }

  init() {
    const body = document.body;
    const toastContainer = document.createElement("div");
    toastContainer.id = "toastContainer";
    const styleObj = {
      position: "fixed",
      top: "50%",
      left: "50%",
      transform: "translate(-50%, -50%)",
      background: "rgba(0, 0, 0, 0.8)",
      color: "#ffffff",
      fontSize: "16px",
      opacity: 0.7,
      transition: "opacity 0.3s ease-in-out",
      padding: "10px",
      "border-radius": "5px",
      display: "none",
      textAlign: "center",
    };
    for (const key in styleObj) toastContainer.style[key] = styleObj[key];
    body.appendChild(toastContainer);

    const loader = document.createElement("div");
    loader.id = "toastLoader";
    const loaderStyleObj = {
      border: "4px solid #f3f3f3",
      borderTop: "4px solid #3498db",
      borderRadius: "50%",
      width: "20px",
      height: "20px",
      animation: "spin 1s linear infinite",
      margin: "0 auto 10px",
      display: "none",
    };
    for (const key in loaderStyleObj) loader.style[key] = loaderStyleObj[key];
    toastContainer.appendChild(loader);

    const text = document.createElement("div");
    text.id = "toastText";
    const textStyleObj = {
      marginTop: "5px",
    };
    for (const key in textStyleObj) text.style[key] = textStyleObj[key];
    toastContainer.appendChild(text);

    const keyframes = `
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
      `;
    const style = document.createElement("style");
    style.innerHTML = keyframes;
    document.head.appendChild(style);
  }

  showToast(text) {
    const textElem = document.getElementById("toastText");

    // 设置Toast提示文本
    textElem.innerText = text;

    // 显示Toast提示
    toastContainer.style.display = "block";

    // 3秒后隐藏Toast提示
    setTimeout(() => this.hide(), 3000);
  }

  showLoading(text = "加载中...") {
    const loader = document.getElementById("toastLoader");
    const textElem = document.getElementById("toastText");

    // 设置Toast提示文本为加载中
    textElem.innerText = text;

    // 显示Toast提示和加载动画
    loader.style.display = "block";
    this.show();
  }

  show() {
    const toastContainer = document.getElementById("toastContainer");

    // 显示Toast提示
    toastContainer.style.display = "block";
  }

  hide() {
    try {
      const toastContainer = document.getElementById("toastContainer");
      const loader = document.getElementById("toastLoader");

      // 隐藏Toast提示和加载动画
      toastContainer.style.display = "none";
      loader.style.display = "none";
    } catch (err) {}
  }
}

六、快捷键打开隐藏窗口

我们可以设置快捷键快速唤起和隐藏窗口,在根目录下的main.js文件中注册快捷键,这里我设置的是alt + x,大家也可以改成自己喜欢的快捷键,具体代码如下:

  // 注册快捷键
  globalShortcut.register("Alt+X", () => {
    if (mainWindow.isVisible()) {
      mainWindow.hide();
    } else {
      mainWindow.show();
    }
  });

工具使用

一、源码下载

直接到 gitee 上下载即可。

image.png

二、依赖安装

下载完源码之后,我们到gitImgBed目录下运行npm i安装依赖,等待依赖安装完成。

image.png

三、程序打包

依赖安装完成之后,我们可以在gitImgBed目录下运行npm run build进行打包

image.png

打包完成后我们可以在当前目录下看到一个叫jyeontuGitImgBed-win32-x64文件夹,打开文件夹,找到里面一个叫jyeontuGitImgBed的应用程序,双击启动即可

image.png

四、配置填写

将之前准备工作时间的gitee仓库相关信息填写到配置中。

image.png

输入正确信息后保存,便可以上传和查看gitee图床中的图片了。

image.png

image.png

image.png

源码

一、gitee

gitee 地址:https://gitee.com/zheng_yongtao/electron_program

二、公众号

关注公众号『前端也能这么有趣』发送 图床即可获取源码。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

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

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

相关文章

22、Python -- 创建对象和使用对象

目录 构造方法类的作用创建对象操作对象的实例变量操作对象的实例方法增加方法调用方法添加方法删除方法 方法与实例变量总结 构造方法 创建对象 操作实例变量 操作方法 构造方法 构造方法是一个特殊的实例方法&#xff0c;方法名为__init__ 创建对象时&#xff0c;自动调用构…

【手把手教vue会员管理系统】篇四之登录界面

一、登录界面路由配置 1.将view目录下的about、home组件删除&#xff0c;再新建login文件夹&#xff0c;在login文件夹下新建index.vue组件&#xff0c;并添加如下图所示的代码&#xff1a; 2.将App.vue更改成下图所示&#xff0c;删掉不必要的代码。 3.在router的index.js文件…

哈希算法:如何防止数据库中的用户信息被脱库?

文章来源于极客时间前google工程师−王争专栏。 2011年CSDN“脱库”事件&#xff0c;CSDN网站被黑客攻击&#xff0c;超过600万用户的注册邮箱和密码明文被泄露&#xff0c;很多网友对CSDN明文保存用户密码行为产生了不满。如果你是CSDN的一名工程师&#xff0c;你会如何存储用…

项目资源管理案例题

本文来自江山老师高项文档 规划资源管理 没有规划资源管理 资源管理计划项目经理一人制定&#xff0c;没有全员参与 资源管理计划内容不全面&#xff0c;有遗漏 质量工程师编写资源管理计划是不对的 资源管理计划应该各干系人参与&#xff0c;而且还需要经过评审 估算活…

基于元模型优化算法的主从博弈多虚拟电厂动态定价和能量管理MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 参考文献&#xff1a; 基于元模型优化算法的主从博弈多虚拟电厂动态定价和能量管理_董雷 仿真平台&#xff1a; MATLABcplex 主要内容&#xff1a; 主要为多虚拟电厂/微网的优化调度策略&#xff0c;模型…

实用篇-Nacos配置管理

Nacos不仅具有注册中心的功能&#xff0c;还具有注册管理的功能 一、Nacos实现配置管理 可以使用统一配置管理&#xff0c;来配置更改热更新&#xff0c;整体结构如下 前提条件: 你已经把上面的 实用篇-Nacos注册中心 学完了&#xff0c;并且项目也跟着做了。我们下面会使用到…

Python 接口自动化 —— requests框架

1.前言 Python内置的urllib模块&#xff0c;也可以用于访问网络资源。但是&#xff0c;它用起来比较麻烦&#xff0c;而且&#xff0c;缺少很多实用的高级功能。因此我们使用 requests 模块进行进行接口测试。 requests官方文档资料地址&#xff1a;http://cn.python-request…

操作系统:进程与线程(二)同步与互斥B

一战成硕 2.3.5 管程2.3.6 经典同步问题 2.3.5 管程 管程的特性保证了进程互斥&#xff0c;无需程序员自己实现互斥&#xff0c;降低了死锁发生的可能。 管程的定义 管程是一种高级的同步机制。 定义描述举例 条件变量 2.3.6 经典同步问题 生产者消费者问题 缓冲区没满&a…

会说话,得天下!演讲与口才训练必修课

会说话&#xff0c;得天下&#xff01;演讲与口才训练必修课 口才是现代职场上一项非常重要的素质。有了好的口才&#xff0c;不仅可以很好地展示自己的思想和见解&#xff0c;还可以影响他人并获得支持&#xff0c;这对个人职业发展十分重要。所以&#xff0c;学习如何进行出…

详解 DES加密技术 | 凯撒密码 | 栅栏密码

目录 密码学 恺撒密码 栅栏密码 消息和加密 密码的三个特性 算法和密钥 对称算法 公开密钥算法 DES对称加密技术 DES算法的安全性 DES算法的原理 DES算法的实现步骤 IP置换表和IP-1逆置换表 函数f 子密钥ki S盒的工作原理 DES算法的应用误区 密码学 密码学是一门…

Oracle (7)Online Redo Log Files

目录 一、Oracle Online Redo Log Files及其相关内容介绍 1、Online Redo Log Files简介 2、Online Redo Log Files特点 3、Online Redo Log Files文件组 4、多路复用文件 5、联机重做日志文件工作方式 6、LGWR什么时候写重做 7、LS和LSN 8、删除Redo文件成员 9、删除…

【黑马程序员】mysql基础篇笔记

2023年10月26日12:21:09过一下 1.01.MySQL课程介绍(Av765670802,P1) 2.02. 基础-课程内容&数据库相关概念(Av765670802,P2) 3.03. 基础-概述-MySQL安装及启动(Av765670802,P3) 4.04. 基础-概述-数据模型(Av765670802,P4) 通过表来存储数据的数据库就叫做关系型数据库 …

centos7安装配置以及Linux常用命令

⭐⭐ linux专栏&#xff1a;linux专栏 ⭐⭐ 个人主页&#xff1a;个人主页 目录 一.CentOS的安装 使用vi编辑ifcfg-ens33 二. 下载MobaXterm软件 2.1MobaXterm的用途 2.2 MobaXterm的使用 2.3下载插件vim 三.Linux常用命令 3.4 vi或vim编辑器 3.4.1 命令模式 3.4.2.…

面试官:给你40亿个数,你应该如何快速判断一个数是否在这40亿个数中?—— 位图 [ C++入门 ]

阅读导航 引言一、位图的概念1. 官方文档2. 基本概念 二、位图的实现1. 插入2. 删除3. 查找C模拟实现&#x1f534;完整代码 总结 引言 在C编程中&#xff0c;位图是一种常用的数据结构&#xff0c;用于高效地表示大量的布尔值。它通过使用一个二进制位来表示每个元素的存在与…

集成电路自动化测试的优势是什么?

集成电路自动化测试的优势主要包括&#xff1a; 提高效率&#xff1a;自动化测试可以显著提高测试效率&#xff0c;因为可以24小时不间断地进行测试&#xff0c;同时避免了人工测试中的误差和疏漏&#xff0c;节省了大量时间和人力成本。 提升精度&#xff1a;自动化测试的精…

MySQL|基础操作+8大查询方式汇总

MySQL操作 一、操作数据库 MySQL中可以创建多个数据库用于存储不同场景的表结构&#xff0c;学习MySQL之前&#xff0c;我们要先理清如下的关系&#xff1a; 数据库 --> 数据表 --> 字段 抛开数据库存储上限去考虑&#xff0c;每个数据库中可以包含无数个数据表&#xf…

Apriori介绍及代码批注

一、Apriori原理解析 1. 概述 关联规则分析是数据挖掘中最活跃的研究方法之一&#xff0c;目的是在一个数据集中找到各项之间的关联关系&#xff0c;而这种关系并没有在数据中直接体现出来。以超市的销售数据为例&#xff0c;当存在很多商品时&#xff0c;可能的商品组合数量…

MySQL的数据库操作、数据类型、表操作

目录 一、数据库操作 &#xff08;1&#xff09;、显示数据库 &#xff08;2&#xff09;、创建数据库 &#xff08;3&#xff09;、删除数据库 &#xff08;4&#xff09;、使用数据库 二、常用数据类型 &#xff08;1&#xff09;、数值类型 &#xff08;2&#xff0…

用Jmeter做微信小程序项目接口测试【案例】

公司新项目组开发一款微信小程序电商平台&#xff0c;为了更好保证产品质量&#xff0c;因此提出了需要进行接口测试。 从接口本身来讲&#xff0c;对其测试与其他项目应该是一样的。所以不难理解&#xff0c;我们要对小程序的接口测试需要准备的 材料有&#xff1a; 1、完备…

LCR 158. 库存管理 II 哈希 / 摩尔投票法

LCR 158. 库存管理 II - 力扣&#xff08;LeetCode&#xff09; 仓库管理员以数组 stock 形式记录商品库存表。stock[i] 表示商品 id&#xff0c;可能存在重复。请返回库存表中数量大于 stock.length / 2 的商品 id。 &#xff08;1&#xff09;方法一&#xff1a;先排序 题目…