一键上传,无限容量!打造高效图床工具,利用Electron和Gitee搭建自己的私人云存储空间

说在前面

平时写文章或写代码的时候,都少不了需要将本地图片转成在线图片链接,大家都是使用什么工具进行转换的呢?相信很多人都有自己的图床工具,今天来给大家介绍一下,怎么基于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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

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

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

相关文章

Docker本地部署Firefox火狐浏览器并远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;…

上市公司数字化转型及同群效应数据集合(四种测算方法)

数据简介&#xff1a;当今世界处于高速发展的信息时代中&#xff0c;数字革命的产生催生出大量数字技术和数字信息。在数字经济时代&#xff0c;数字化转型赋予了企业新的发展动能&#xff0c;数字化转型已经成为诸多企业高质量发展的重要路径。是否需要进行数字化转型、能否及…

windows启动后直接进入指定程序并且不显示欢迎界面和windows桌面

windows启动后直接进入指定程序并且不显示欢迎界面和windows桌面 前言开机进入指定程序方法问题 浅尝GINA和Credential Providers关闭欢迎屏幕 前言 由于系统需求需要做到电脑开机后显示完windows加载页面就直接进入自己系统的界面&#xff0c;并且不显示登录欢迎页面&#xf…

IDE1007:当前上下文中不存在名称“xxx“

这种在Halcon中直接导出的代码不能直接放程序中&#xff0c;应该在控件中比如一个按钮中&#xff0c;就不会出错了。

Docker安装可视化工具Portainer

目录 Portainer简介 Portainer安装 Portainer简介 Portainer是一款开源的容器管理平台&#xff0c;支持多种容器技术&#xff0c;如Docker、Kubernetes和Swarm等。它提供了一个易于使用的Web UI界面&#xff0c;可用于管理和监控容器和集群。Portainer旨在使容器管理更加简单…

【celery踩坑】celery定时和周期任务全部不执行

一、背景 有一天&#xff0c;突然发现线上系统上的任务没有执行&#xff0c;状态一直是未完成。 看了一下celery的beat日志&#xff0c;发现周期任务和定时任务都不执行了。 重启项目&#xff0c;发现django_celery_beat_periodictask中&#xff0c;也只是执行前面几个周期或者…

C语言之“可变参数<stdarg.h>”

目录 前言 stdarg.h头文件 实例&#xff1a;遍历并求和所有传递给sum函数的额外实际参数 前言 有时我们会希望函数带有可变数量的参数就像printf&#xff08;cosnt char* format ...&#xff09;和scanf&#xff08;cosnt char* format ...&#xff09;那样除了有一个参数 …

【用unity实现100个游戏之17】从零开始制作一个类幸存者肉鸽(Roguelike)游戏6(附项目源码)

文章目录 本节最终效果前言开始游戏主角扣血和死亡游戏结束清屏效果赢得比赛角色选择界面每个角色有自己的特点&#xff0c;及初始属性不一样参考源码完结 本节最终效果 前言 本节紧跟着上一篇&#xff0c;主要实现不同游戏界面和不同角色选择。 开始游戏 简单绘制UI 修改…

Vue中的组件和插件

一、组件 组件是Vue中最核心的概念之一&#xff0c;它可以把一个页面拆分成多个独立的、可复用的部分。组件通常包含了自己的模板、样式和逻辑&#xff0c;用于封装一个特定的功能或界面。Vue的组件有单文件组件和普通组件两种类型&#xff0c;可以通过Vue.component或Vue.ext…

基于javaweb的宠物服务商城系统设计与开发

摘 要 最近几年以来&#xff0c;宠物在人们的日常生活中所占的地位越来越重要了&#xff0c;它们不仅仅是我们的朋友&#xff0c;也成为了我们家庭中的一份子。21世纪&#xff0c;信息技术飞速发展&#xff0c;计算机行业日新月异&#xff0c;极大地带动了信息的流动&#xff…

DS1302时钟保持芯片,让你很快读懂它

概述&#xff1a; DS1302是DALLAS公司制作的涓流充电时钟芯片。芯片有实时时钟日历功能&#xff0c;有31*8bits静态RAM&#xff0c;可以通过串行接口方式和处理器(stm32&#xff0c;ARM等)进行通信来读写RAM&#xff0c;有两种传送方式&#xff0c;单字节传送和多字节传送。内部…

OSG编程指南<十六>:OSG渲染到纹理RTT及三维纹理体渲染技术简介

1、渲染到纹理&#xff08;RTT&#xff09; 1.1 RTT介绍 RTT&#xff08;Render to Texture&#xff09;即渲染到纹理。在普通的图形渲染流程中&#xff0c;最终结果是渲染到帧缓存中&#xff0c;然后才会显示到屏幕上。而RTT则是将场景渲染到一张纹理上&#xff0c;并且在之后…

三.排序与分页

目录 一.排序数据二.分页 一.排序数据 1.排序规则 使用ORDER BY 子句排序 ASC&#xff08;ascend&#xff09;升序DESC&#xff08;descend&#xff09;降序 ORDER BY 子句在SELECT语句的结尾 2.单列排序 SELECT last_name, job_id, department_id, hire_date FROM e…

.net core 连接数据库,通过数据库生成Modell

1、安装EF Core Power Tools&#xff1a;打开Vs开发工具→扩展→管理扩展 2、(切记执行这步之前确保自己的代码不存在编写或者编译错误&#xff01;)安装完成后在你需要创建数据库实体的项目文件夹上面单击右键&#xff0c;找到EF Core 工具&#xff08;必须安装扩展之和才会有…

Git修改远程仓库名称

1、先直接在远程点仓库名&#xff0c;然后左侧菜单栏找settings-general&#xff0c;然后直接修改工程名&#xff0c;保存即可。 2、还是在settings-general下&#xff0c;下拉找到Advanced点击Expand展开&#xff0c;然后下拉到最底部 在Change path里填入新的项目名称&#x…

C语言猜素数(ZZULIOJ1292:猜素数)

题目描述 Lx给Xp出了一道难题&#xff0c;随便在0和1000000之间抽出两个数&#xff0c;估计在这两个数之间的素数的个数&#xff0c;如果猜测的结果和正确结果一样&#xff0c;Xp就可以得到Lx的一件礼物&#xff0c;你能猜对吗&#xff1f;编程实现一下吧&#xff01; 输入&…

App测试中iOS和Android的差异

1、系统版本&#xff1a; iOS和Android系统版本的更新速度、使用人数比例以及功能的不同都可能导致应用程序在不同操作系统版本上的表现和兼容性存在区别。 例如&#xff0c;在iOS平台上&#xff0c;很多用户会更快地升级到最新版本的iOS系统&#xff0c;而在Android平台上&a…

Spring-事务支持

目录 一、事务概述 二、引入事务场景 三、Spring对事务的支持 Spring实现事务的两种方式 声明式事务之注解实现方式 1.在Spring配置文件中配置事务管理器 2. 在Spring配置文件引入tx命名空间 3. 在Spring配置文件中配置“事务注解驱动器”&#xff0c;通过注解的方式控…

Node.js【文件系统模块、路径模块 、连接 MySQL、nodemon、操作 MySQL】(三)-全面详解(学习总结---从入门到深化)

目录 Node.js 文件系统模块&#xff08;二&#xff09; Node.js 文件系统模块&#xff08;三&#xff09; Node.js 文件系统模块&#xff08;四&#xff09; Node.js 路径模块 Node.js 连接 MySQL Node.js nodemon Node.js 操作 MySQL Node.js 应用 Node.js 文件系统模块…

子类拷贝构造函数会调用父类拷贝构造函数吗?

一. 编译器提供的默认子类拷贝构造函数会调用父类拷贝构造函数。 #include <iostream> #include <string> using namespace std;class Parent { public:Parent(string home_address "中国") : m_home_address(home_address) {cout << "调用…