vue-微信H5-拍照和视频,加人像框

news2024/9/9 5:30:27

在这里插入图片描述
图片拍照:

<template>
  <div>
    <v-easy-camera
      :fullscreen="true"
      ref="easyCamera"
      v-model="pictureData.picture"
      class="main-camera"
    >
      <template #header>
        <div class="top">
          <van-image
            class="mask_control_close"
            width="20px"
            height="20px"
            fit="cover"
            @click="cameraStop"
            :src="require('@/assets/img/camera/叉叉.png')"
          />
        </div>
      </template>
    </v-easy-camera>
    <van-image
      class="mask"
      :src="require('@/assets/img/camera/333.png')"
    />
    <van-image
      class="mask_control"
      width="80px"
      height="80px"
      fit="cover"
      @click="cameraSnap"
      :src="require('@/assets/img/camera/拍照.png')"
    />
    <van-image
      v-show="confirmIsShow"
      class="mask_control_confirm"
      width="40px"
      height="40px"
      fit="cover"
      @click="confirm"
      :src="require('@/assets/img/camera/确定.png')"
    />
    <van-image
      v-show="confirmIsShow"
      class="mask_control_return"
      width="40px"
      height="40px"
      fit="cover"
      @click="cancel"
      :src="require('@/assets/img/camera/撤销.png')"
    />
    <van-image
      v-show="!confirmIsShow && isAndroid"
      class="mask_control_confirm"
      width="40px"
      height="40px"
      fit="cover"
      @click="flip"
      :src="require('@/assets/img/camera/翻转镜头.png')"
    />
  </div>
</template>

  <script>
import EasyCamera from "easy-vue-camera";

export default {
  name: "cameraCustom",
  components: {
    "v-easy-camera": EasyCamera,
  },
  data() {
    return {
      pictureData: {
        picture: "",
        pictureSize: "",
      },
      isEnable: true,
      myEasyCamera: null,
      confirmIsShow: false,
      isFlip: false,
      isAndroid: false,
      currentFacingMode: "",
    };
  },

  mounted() {
    // 判断用户的操作系统
    const isAndroid = /android/i.test(navigator.userAgent);
    const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);

    //获取原始控件区并隐藏(处理为移除子元素)
    let aaa = document.getElementsByClassName("camera-stack")[0];
    let div = document.createElement("div");
    div.className = "bott";
    div.style.height = "180px";
    div.style.widht = "100%";

    aaa.appendChild(div);
    let controlModel = document.getElementsByClassName(
      "camera-stack-controls"
    )[0];
    controlModel.removeChild(controlModel.firstChild);
    //获取拍照组件实例
    this.myEasyCamera = this.$refs.easyCamera;

    if (isAndroid) {
      this.isAndroid = false;
      console.log("This device is using Android.");
      // 在 Android 上执行的逻辑
    } else if (isIOS) {
      this.isAndroid = true;
      setInterval(() => {
        if (this.isFlip == false) {
          this.flip();
        }
      }, 500);
      // 在 iOS 上执行的逻辑
    } else {
      console.log("This device is using a different operating system.");
      // 在其他操作系统上执行的逻辑
    }
  },
  methods: {
    // 点击关闭
    cameraStop() {
      // console.log("关闭");
      this.myEasyCamera.close();
      this.$emit("close");
    },
    // 点击拍照
    cameraSnap() {
      if (!this.isEnable) {
        return;
      }
      // console.log("拍照");
      this.myEasyCamera.snap();
      document.getElementsByClassName(
        "camera-stack-controls"
      )[0].style.display = "none";
      this.confirmIsShow = true;
      // this.pictureData.pictureSize =
      // this.getImageSize(this.pictureData.picture) / 1024 / 1024;
      this.isEnable = false;
    },
    // 点击对号完成
    confirm() {
      // console.log("完成");
      this.isEnable = true;
      this.confirmIsShow = false;
      this.$emit("setPictureUrl", this.pictureData);
      this.$emit("close");
    },
    // 点击返回重拍
    cancel() {
      // console.log("重拍");
      this.isEnable = true;
      this.confirmIsShow = false;
      this.myEasyCamera.start();
    },
    // 获取照片大小
    getImageSize(base64Str) {
      const indexBase64 = base64Str.indexOf("base64,");
      if (indexBase64 < 0) return false;
      const str = base64Str.substr(indexBase64 + 6);
      return (str.length * 0.75).toFixed(2);
    },
    flip() {
      this.myEasyCamera.switchCamera();
      this.isFlip = true;
    },
  },
};
</script>

  <style   scoped>
.top {
  background-color: black;
  height: 50px;
  width: 100%;
}
.bottom {
  background-color: black;
  height: 60px;
  width: 100%;
}
.bott {
  position: absolute;
  background-color: black;
  width: 100%;
  z-index: 18;
}
.mask {
  position: absolute;
  top: 50px;
  left: 0;
  z-index: 18;
  height: calc(100% - 200px);
  width: 100%;
}
.mask /deep/ img {
  height: auto;
}
.mask_control {
  position: absolute;
  z-index: 18;
  left: calc(50% - 40px);
  bottom: 40px;
}
.mask_control_close {
  position: absolute;
  z-index: 18;
  right: 20px;
  top: 20px;
}
.mask_control_confirm {
  position: absolute;
  z-index: 18;
  right: 40px;
  bottom: 60px;
}
.mask_control_return {
  position: absolute;
  z-index: 18;
  left: 40px;
  bottom: 60px;
}
.main-camera {
  background-color: #000;
}
.fullscreen-camera /deep/ .camera-stack{
  transform: scaleX(-1);
}
</style>


视频录制:

<template>
  <div class="publish">
    <div class="top">
      <van-image
        class="mask_control_close"
        width="20px"
        height="20px"
        fit="cover"
        @click="CloseCamera"
        :src="require('@/assets/img/camera/叉叉.png')"
      />
    </div>
    <div id="myElement" class="videomain">
      <video
        ref="video"
        class="video_class"
        autoplay
        playsinline
        webkit-playsinline="true"
      ></video>
<!--      <img class="mask" :src="require('@/assets/img/camera/333.png')" alt="" />-->
      <!-- <div class="mask_div">
        <div class="mask_mb"></div>
        <img
          class="mask"
          :src="require('@/assets/img/camera/333.png')"
          alt=""
        />
      </div> -->
      <van-image
        class="mask"
        :src="require('@/assets/img/camera/333.png')"
      />
    </div>
    <div class="bottom">
      <div class="time" id="timer">
        {{ padNumber(hours) }} : {{ padNumber(minutes) }} :
        {{ padNumber(seconds) }}
      </div>

      <div class="bottom02">
        <div
          style="width: 40px; height: 40px"
          v-show="isRecording && isSure"
        ></div>

        <img
          v-show="confirmIsShow"
          class="mask_control_return"
          width="40px"
          height="40px"
          fit="cover"
          @click="stopCancel"
          :src="require('@/assets/img/camera/撤销.png')"
        />

        <img
          v-if="isRecording && isSure"
          class="mask_control"
          width="80px"
          height="80px"
          fit="cover"
          @click="startRecording"
          :src="require('@/assets/img/camera/拍照.png')"
        />

        <img
          v-if="!isRecording && isSure"
          class="mask_control"
          width="80px"
          height="80px"
          fit="cover"
          @click="stopRecording"
          :src="require('@/assets/img/camera/拍照 (1).png')"
        />

        <van-image
          v-show="confirmIsShow"
          class="mask_control_confirm"
          width="40px"
          height="40px"
          fit="cover"
          @click="downloadVideo"
          :src="require('@/assets/img/camera/确定.png')"
        />

        <van-image
          v-show="isRecording && isAndroid"
          class="mask_control_confirm"
          width="40px"
          height="40px"
          fit="cover"
          @click="toggleCamera"
          :src="require('@/assets/img/camera/翻转镜头.png')"
        />

        <div
          style="width: 40px; height: 40px"
          v-show="!isAndroid && isRecording"
        ></div>
      </div>
    </div>
  </div>
</template>

    <script>
    import { Toast } from 'vant';

export default {
  data() {
    return {
      mediaRecorder: null,
      recordedChunks: [],
      recording: false,
      isSure: true,
      isRecording: true,
      isAndroid: false,
      confirmIsShow: false,
      hours: 0,
      minutes: 0,
      seconds: 0,
      intervalId: null,
      currentFacingMode: "environment", // 'user' for front camera, 'environment' for back camera
    };
  },
  computed: {
    elapsedTime() {
      return this.startTime
        ? Math.floor((Date.now() - this.startTime) / 1000)
        : 0;
    },
    hours() {
      return Math.floor(this.elapsedTime / 3600);
    },
    minutes() {
      return Math.floor((this.elapsedTime % 3600) / 60);
    },
    seconds() {
      return this.elapsedTime % 60;
    },
    formattedTime() {
      return `${this.pad(this.hours)}:${this.pad(this.minutes)}:${this.pad(
        this.seconds
      )}`;
    },
  },
  mounted() {
    // 判断用户的操作系统
    const isAndroid = /android/i.test(navigator.userAgent);
    const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);

    if (isAndroid) {
      this.isAndroid = true;
      this.currentFacingMode = "user";
      console.log("This device is using Android.");
      // 在 Android 上执行的逻辑
    } else if (isIOS) {
      this.isAndroid = true;
      console.log("This device is using iOS.");
      // 在 iOS 上执行的逻辑
    } else {
      console.log("This device is using a different operating system.");
      // 在其他操作系统上执行的逻辑
    }

    const screenHeight =
      window.innerHeight ||
      document.documentElement.clientHeight ||
      document.body.clientHeight;
    this.screenHeight = screenHeight - 50 - 180;
    console.log("🚀 ~ mounted ~  this.screenHeight:", this.screenHeight);

    const myElement = document.getElementById("myElement"); // 替换 'myElement' 为你实际元素的ID
    // 设置元素高度
    if (myElement) {
      myElement.style.height = this.screenHeight + "px"; // 替换 '100px' 为你想要设置的高度
    }
    this.canvas = document.createElement('canvas')
    this.canvas.width = this.$refs.video.videoWidth
    this.canvas.height = this.$refs.video.videoHeight
    const ctx = this.canvas.getContext('2d')
    const self = this
    function drawCanvas() {
      ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)
      ctx.drawImage(self.$refs.video, 0, 0, self.canvas.width, self.canvas.height)
      window.requestAnimationFrame(drawCanvas)
    }
    // 获取视频流并将其绑定到视频元素
    navigator.mediaDevices
      .getUserMedia({
        video: {
          facingMode: this.currentFacingMode,
        },
      })
      .then((stream) => {
        this.$refs.video.srcObject = stream;
        this.$refs.video.play();
        drawCanvas()
      })
      .catch((error) => {
        console.error("获取摄像头失败:", error);
      });
  },
  methods: {
    startRecording() {
      this.recordedChunks = [];
      this.isRecording = false;
      this.recording = true;
      this.startTimer(); //开始计时
      // 创建 MediaRecorder 对象,将视频流传入
      const isIOS = /iphone|ipad|ipod/.test(navigator.userAgent)
      if (isIOS) {
        try {
          this.mediaRecorder = new MediaRecorder(this.$refs.video.srcObject, { mimeType:  'video/mp4;codecs=avc1,mp4a' });
        } catch (e) {
          try {
            this.mediaRecorder = new MediaRecorder(this.canvas.captureStream(15), { mimeType:  'video/mp4;codecs=avc1,mp4a' });
          } catch (e) {
            console.error(e)
            this.$toast.fail("当前浏览器不支持录屏,请直接上传文件");
          }
        }
      } else {
        try {
          this.mediaRecorder = new MediaRecorder(this.$refs.video.srcObject, { mimeType:  'video/webm;codecs=h264' });
        } catch(e) {
          console.error(e)
          this.$toast.fail("当前浏览器不支持录屏,请直接上传文件");
        }
      }


      // 监听数据可用事件,将数据块存储到数组
      this.mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0) {
          this.recordedChunks.push(event.data);
        }
      };

      this.mediaRecorder.onerror = (e) => {
        console.error('error:', e)
      }

      // 监听录制结束事件
      this.mediaRecorder.onstop = () => {
        this.recording = false;
      };

      // 开始录制
      this.mediaRecorder.start();
    },
    stopRecording() {
      this.stopTimer();
      this.isRecording = true;
      this.isSure = false;

      if (this.mediaRecorder && this.recording) {
        // 停止录制
        this.mediaRecorder.stop();
        this.confirmIsShow = true;
      }
    },
    stopCancel() {
      this.resetTimer();
      this.isSure = true;
      this.confirmIsShow = false;
    },
    downloadVideo() {
      console.log("seconds", this.seconds);
      if (this.minutes == 0 && this.seconds < 5) {
        Toast.fail('录制时长最少5秒钟');
        return
      }

      // 将录制的数据块合并为 Blob
      const blob = new Blob(this.recordedChunks, { type: "video/webm" });
      // 假设 videoBlob 是一个录制的视频的 Blob 对象
      const videoFile = new File(this.recordedChunks, "recorded-video.webm", {
        type: "video/webm",
      });
      console.log(
        "🚀 ~ file: videoCamera.vue:148 ~ downloadVideo ~ videoFile:",
        videoFile
      );

      // 现在,videoFile 就是一个 File 对象,可以像处理文件一样使用它

      console.log("🚀 ~ file: chatbb.vue:66 ~ downloadVideo ~ blob:", blob);

      this.$emit("setPictureUrl2", videoFile);
      this.$emit("close");

      this.$refs.video.srcObject.getTracks().forEach((track) => {
        track.stop()
      })

      // 创建下载链接并触发下载
      //   const url = URL.createObjectURL(blob);
      //   const a = document.createElement("a");
      //   a.href = url;
      //   a.download = "recorded-video.webm";
      //   a.click();

      // 释放资源
      //   URL.revokeObjectURL(url);
    },
    CloseCamera() {
      this.stopRecording();
      this.$emit("close");
      this.$refs.video.srcObject.getTracks().forEach((track) => {
        track.stop()
      })
      // 停止录制
    },
    // 切换摄像头
    toggleCamera() {
      console.log(1);
      this.currentFacingMode =
        this.currentFacingMode === "user" ? "environment" : "user";
      this.restartVideo();
    },
    async restartVideo() {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          facingMode: this.currentFacingMode,
        },
      });
      this.$refs.video.srcObject = stream;
    },
    padNumber(num) {
      return String(num).padStart(2, "0");
    },
    startTimer() {
      this.intervalId = setInterval(() => {
        this.seconds++;
        if (this.seconds === 60) {
          this.seconds = 0;
          this.minutes++;
          if (this.minutes === 60) {
            this.minutes = 0;
            this.hours++;
          }
        }
      }, 1000);
    },
    stopTimer() {
      clearInterval(this.intervalId);
      this.intervalId = null;
    },
    resetTimer() {
      this.hours = 0;
      this.minutes = 0;
      this.seconds = 0;
      this.stopTimer();
    },
  },
  beforeDestroy() {
    // 清理定时器
    this.stopTimer();
  },
};
</script>


    <style scoped>
.publish {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  /* align-items: center; */
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  background: #000;
}

.top {
  background-color: black;
  min-height: 50px;
  width: 100%;
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.video_class {
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 100;
}
.bottom {
  position: relative;
  min-height: 180px;
  width: 100%;
  background-color: #000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 999;
}
.bottom02 {
  width: 100%;
  background-color: #000;
  display: flex;
  justify-content: space-around;
  align-items: center;
  z-index: 999;
}
.mask_div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.mask_mb {
  position: absolute;
  /* background-color: rgba(0.129, 0.129, 0.125, 0.5); */
  width: 100%;
  height: 100%;
  z-index: 333;
}
.mask {
  /* background-color: #fff; */
  position: absolute;
  top: 0;
  left: 0%;
  display: flex;
  justify-content: center;
  max-width: 100%;
  width: 100%;
  height: 100%;
  background-color: transparent; /* 中间区域透明,保持正常 */
}
.mask /deep/ img {
  height: 140vw;
  width: 100vw;

}
.mask_control_return {
  /* position: absolute; */
  z-index: 18;
  left: 40px;
  bottom: 60px;
}
.mask_control_close {
  margin-right: 10px;
}
.flip {
  background-color: #000;
}
.videomain {
  position: relative;
  background-color: #000;
  flex-shrink: 0;
  /* flex: 1; */
  /* max-width: 500px; */
}
.time {
  color: #fff;
  position: absolute;
  top: 5px;
  left: 50%;
  transform: translate(-50%);
  z-index: 9999;
}
</style>


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

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

相关文章

【跳槽面试】Redis中分布式锁的实现

分布式锁常见的三种实现方式&#xff1a; 数据库乐观锁&#xff1b;基于Redis的分布式锁&#xff1b;基于ZooKeeper的分布式锁。 本地面试考点是&#xff0c;你对Redis使用熟悉吗&#xff1f;Redis中是如何实现分布式锁的。 在Redis中&#xff0c;分布式锁的实现主要依赖于R…

【JavaEE Spring】SpringBoot 配置文件

SpringBoot 配置文件 1. 配置文件的作用1.1 配置文件的说明1.2 SpringBoot 配置文件 2. 配置文件的格式特殊说明 3. properties 配置文件说明3.1 properties 基本语法3.2 读取配置文件3.3 properties 缺点分析 4. yml 配置文件说明4.1 yml 的基本语法4.2 yml 使⽤进阶4.2.1 yml…

Java基础 - 07 Set之Set,AbstractSet

上边几篇&#xff0c;我们对java的List集合进行相关介绍&#xff0c;了解了关于List集合下的相关实现类的方法或者接口。 自本篇开始&#xff0c;将围绕java的Set进行介绍&#xff0c;也是对我java知识的巩固吧&#xff0c;处理业务越多&#xff0c;发现自己对基础知识的薄弱&…

数据结构小项目----通讯录的实现(这里用链表实现) 超详细~~~~૮(˶ᵔ ᵕ ᵔ˶)ა

目录 Contact.h说明&#xff1a; 结构体与头文件的包含&#xff1a; ​编辑 函数在头文件的声明与定义&#xff1a; Contact.c中各个函数的实现&#xff1a; 1.检查链表中的数据是否满了&#xff0c;满了就扩容 2.链表的尾插 3.链表的删除 4.查找名字是否匹配 5.初始化通讯…

Vagrant安装Oracle Data Guard环境示例

在Windows 11下&#xff0c;通过Vagrant安装标准的Data Guard环境&#xff08;默认为non-CDB模式&#xff09;&#xff0c;耗时约26分钟&#xff0c;共生成2台虚机。以下为安装日志&#xff1a; ...host2: Welcome to DGMGRL, type "help" for information.host2: C…

b+树的理解

二叉树&#xff1a; 每个节点支持两个分支的树结构&#xff0c;相比于单向链表&#xff0c;多了一个分支。 二叉查找树&#xff1a; 在二叉树的基础上增加了一个规则&#xff0c;左子树的所有节点都小于它的根节点&#xff0c;右子树的所有节点都大于他的根节点。 二叉查找树…

Beego之Beego快速入门

1、beego快速入门 1.1 新建项目 新建一个项目&#xff1a; [rootzsx src]# bee new quickstart 2023/02/19 15:55:50.370 [D] init global config instance failed. If you do not use this, just ignore it. open conf/app.conf: no such file or directory 2023/02/19 1…

易优demo网站测试结果

易优demo网站测试结果-06 1、信息收集 网站账号&#xff1a;admin 密码&#xff1a;Aa123456 2、存在的漏洞 2.1 后台弱口令漏洞 http://eyoucms-s347fqn.gxalabs.com/login.php?sAdmin/login网站账号&#xff1a;admin 密码&#xff1a;Aa123456 成功登陆 2.2 代码远程…

递归、搜索与回溯算法(专题二:深搜)

往期文章&#xff08;希望小伙伴们在看这篇文章之前&#xff0c;看一下往期文章&#xff09; &#xff08;1&#xff09;递归、搜索与回溯算法&#xff08;专题零&#xff1a;解释回溯算法中涉及到的名词&#xff09;【回溯算法入门必看】-CSDN博客 &#xff08;2&#xff09…

linux基础学习(5):yum

yum是为了解决rpm包安装依赖性而产生的一种安装工具 1.yum源 1.1配置文件位置 yum源的配置文件在/etc/yum.repos.d/中 *Base源是网络yum源&#xff0c;也就是需要联网才能使用的yum源。默认情况下&#xff0c;系统会使用Base源 *Media源是光盘yum源&#xff0c;是本地yum源…

openjdk源码了解

openjdk给出debug配置选项&#xff0c;common/autoconf/jdk-options.m4 AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_LEVEL], [################################################################################# Set the debug level# release: no debug information, all opti…

Jenkins实现CICD(1)_Windows10 安装Jenkins

文章目录 一、打开Jenkins官网&#xff0c;下载安装包二、安装Jenkins三、JAVA环境_JDK17下载安装&#xff08;Windows版&#xff09;四、将jdk-17添加到系统环境变量五、jenkins关联jdk-17六、安装常用插件(例如&#xff1a;git、gitlab、钉钉) 一、打开Jenkins官网&#xff0…

PDshell16逆向PostgreSQL 工程显示字段comment备注

现状&#xff1a;当刚逆向成功的表结构是没有原来表结构中的&#xff0c;comment备注如下 然后pd逆向工程的sql已经返回了这个备注的含义 解决方案&#xff1a; 1、设置显示注释列 tools——Display Preferences…如下 勾选-按照下面得方式勾选这三个 复制这里的VBS脚本&a…

Python数据分析案例37——基于分位数神经网络(QRNN)的汇率预测

案例背景 我导师的研究方向是少有的做"分位数回归"方向&#xff0c;作为研究机器学习深度学习方向的我自然就继承了这个特色&#xff0c;改进出了很多特殊结合方法&#xff0c;我会结合各种机器学习方法和各种分位数回归的方法。 之前写过分位数随机森林&#xff0…

哈希表 -- 刷题(查找算法)

目录 &#x1f4bb;哈希 -- 知识点 &#x1f40d;刷题 &#x1f33c;1&#xff0c;雪花 AC -- vector AC -- 链式前向星 &#x1f33c;2&#xff0c;公式 &#x1f4bb;哈希 -- 知识点 线性表 和 树表&#xff0c;通过比较关键字进行查找 而 散列表&#xff0c;基于…

【MATLAB源码-第119期】基于matlab的GMSK系统1bit差分解调误码率曲线仿真,输出各个节点的波形以及功率谱。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 GMSK&#xff08;高斯最小频移键控&#xff09;是一种数字调制技术&#xff0c;广泛应用于移动通信&#xff0c;例如GSM网络。它是一种连续相位调频制式&#xff0c;通过改变载波的相位来传输数据。GMSK的关键特点是其频谱的…

【接上篇】二、Flask学习之CSS(下篇)

上篇&#xff1a;二、Flask学习之CSS 3.8hover hover是用来美化鼠标悬停的效果的&#xff0c;当鼠标停放在某个区域&#xff0c;就会执行对应的hover操作。可以操作本标签的内容&#xff0c;也可以操作本标签下某一个标签的内容 3.9after <!DOCTYPE html> <html l…

Navicat平替工具,一款免费开源的通用数据库工具

前言 前段时间有小伙伴在群里提问说&#xff1a;因为公司不允许使用破解版的Navicat&#xff0c;有好用的Navicat平替工具推荐吗&#xff1f;今天分享一款免费开源的通用数据库工具&#xff1a;DBeaver。 DBeaver工具介绍 DBeaver是一款免费的跨平台数据库工具&#xff0c;适…

灵活扩展:深入理解MyBatis插件机制

第1章&#xff1a;MyBatis插件的重要性 大家好&#xff0c;我是小黑&#xff0c;咱们今天要聊的是MyBatis插件&#xff0c;MyBatis&#xff0c;大家都不陌生&#xff0c;它是一个ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;让咱们在操作数据库时能更加优雅。但今…

“深入理解 Docker 和 Nacos 的单个部署与集成部署“

目录 引言&#xff1a;Docker Nacos 单个部署1.1 什么是 Docker&#xff1f;Docker 的概念和工作原理Docker 为什么受到广泛应用和认可 1.2 什么是 Nacos&#xff1f;Nacos 的核心功能和特点Nacos 在微服务架构中的作用 1.3 Docker 单个部署 Nacos Docker Nacos 集成部署总结&a…