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

【声网】实现web端与uniapp微信小程序端音视频互动

实现web端与uniapp微信小程序端音视频互动

利用声网实现音视频互动

开通声网服务

  1. 注册声网账号

  2. 进入Console

  3. 成功登录控制台后,按照以下步骤创建一个声网项目:

    1. 展开控制台左上角下拉框,点击创建项目按钮。

      在这里插入图片描述

    2. 在弹出的对话框内,依次选择项目类型,输入项目名称,选择场景标签鉴权机制。其中鉴权机制推荐选择安全模式(APPID + Token)调试模式的安全性较低。
      在这里插入图片描述

    3. 创建好项目之后可以获取APP ID,Token,和Channel。临时Token有效期为24小时
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

web端

技术栈:vite+vue3+vue-router+pinia

组件库:element plus

开发环境准备:

  • Windows 或 macOS 计算机,需满足以下要求:
    • 下载声网 Web SDK 支持的浏览器。声网强烈推荐使用最新稳定版 Google Chrome 浏览器。
    • 具备物理音视频采集设备。
    • 可连接到互联网。如果你的网络环境部署了防火墙,请参考应用企业防火墙限制以正常使用声网服务。
    • 搭载 2.2 GHz Intel 第二代 i3/i5/i7 处理器或同等性能的其他处理器。
  • 安装 Node.js 及 npm。
  • 有效的声网账户和声网项目,并且从声网控制台获取以下信息:
    • App ID:声网随机生成的字符串,用于识别你的 App。
    • 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
    • 频道名称:用于标识频道的字符串。

项目开发

  1. 项目初始化

    使用vite创建vue3项目,npm create vite@latest

    集成webSDK,npm install agora-rtc-sdk-ng@latest

    安装vue-router,npm install vue-router@4在src下创建router.js;

    //router.js
    import { createRouter, createWebHistory } from 'vue-router';const routes = [{path: '/',name: 'Home',component: () => import("@/views/home/home.vue")},{path: '/video',name: 'Video',component: () => import("@/views/video/video.vue")},];const router = createRouter({history: createWebHistory(),routes
    });export default router;
    

    安装Element Plus ,npm install element-plus --save,并且设置组件自动导入,具体可以看官方文档

    安装Pinia,npm install pinia

    在main.js中初始化pinia,并在主目录创建store目录,创建options.js文件

    //mian.js
    import { createSSRApp } from 'vue'
    import * as Pinia from 'pinia';
    export function createApp() {const app = createSSRApp(App)app.use(Pinia.createPinia());return {app,Pinia}
    }
    
    //options.js
    import { defineStore } from "pinia";
    import {reactive} from "vue";export const useOptionsStore = defineStore('options',() => {const options = reactive({appId: "Your APPID",token: "Your token",})return {options}
    })
    

    在main.js中引入所有安装的内容:

    import { createApp } from 'vue';
    import router from "./router";
    import { createPinia } from 'pinia';
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import App from './App.vue';const app = createApp(App)
    app.use(router)
    app.use(ElementPlus)app.use(createPinia())
    app.mount('#app')
    
  2. 编写home.vue

​ 使用el-form el-form-item el-input 对用户的输入进行校验,校验通过后携带用户输入的参数进入到视频通话页面

<template><div class="content"><el-form:model="form"label-width="auto"style="max-width: 600px":rules="rules"ref="ruleFormRef"><el-form-item label="房间号" prop="channel"><el-input v-model="form.channel" placeholder="请输入房间号"></el-input></el-form-item><el-form-item label="用户名" prop="uid"><el-input v-model="form.uid" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-button type="primary" @click="joinChannel" style="width: 100%">加入房间</el-button></el-form-item></el-form></div>
</template>
<script setup>
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";const router = useRouter();const form = reactive({channel: "",uid: "",
});const ruleFormRef = ref(null);const rules = reactive({channel: [{ required: true, message: "请输入房间号", trigger: "blur" }],uid: [{ required: true, message: "请输入用户名", trigger: "blur" }],
});const joinChannel = async () => {await ruleFormRef.value.validate();router.push({name: "Video",query: { channel: form.channel, uid: form.uid },});
};
</script>
<style scoped>
.content {display: flex;flex-direction: column;
}
</style>
  1. 编写video.vue

    • 实现视频通话的步骤大致为:初始化客户端对象并创建本地客户端 -> 订阅远端流 -> 加入频道 -> 创建并发布本地音视频轨道-> (通话) ->退出关闭本地流
    1. 初始化客户端对象并创建本地客户端

      导入声网组件,import AgoraRTC from "agora-rtc-sdk-ng";

      初始化客户端对象,let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" }); 注意:与微信小程序通信codec为h264。与其他端使用的是vp8。并且此client不能写成响应式数据,要保证全局使用的本地客户端对象为唯一的。

      要保证客户端对象可以在最一开始就可以进行初始化,并且可以全局使用,所以都写入setup中。

import AgoraRTC from "agora-rtc-sdk-ng";
let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });
  1. 订阅远端流

    当远端流发布到频道时,会触发 user-published 事件,需要通过 client.on 监听该事件并在回调中订阅新加入的远端流。当远端用户取消发布流或退出频道时,触发user-unpublished事件,关闭及移除对应的流。为了用户能够在进入页面的时候及时监听到远端流,所以将监听事件放入onMounted中。

    handleUserPublished函数中编写远端用户加入事件,远端用户加入事件中需要做的事情是通过调用client.subscribe(远端加入的用户user对象,连接方式)方法获取到远端用户对象和连接方式(分为video,audio),区别不同的连接方式,调用包含在user中不同的方法。如果是video,就先需要在先创建dom,将user的视频放入到dom中,并且该dom要有宽高。如果是audio则不用。

    handleUserUnpublished函数中编写远端用户退出事件,就将remoteUserRef的值置为null,使其不再显示页面上。

<template><!-- 使用ref获取dom -->远程用户:<divclass="remote-user"ref="remoteUserRef"></div>
</template>
      import AgoraRTC from "agora-rtc-sdk-ng";import {ref} from "vue";let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });const remoteUserRef = ref(null)const handleUserPublished = async (user, mediaType) => {await client.subscribe(user, mediaType);// 如果是视频轨道,则添加到远程用户列表if (mediaType === "video") {user.videoTrack.play(remoteUserRef.value);}// 如果是音频轨道,直接播放if (mediaType === "audio") {user.audioTrack.play();}};const handleUserUnpublished = () => {// 移除远程用户remoteUserRef.value = null;};onMounted(async () => {// 监听远程用户发布事件client.on("user-published", handleUserPublished);client.on("user-unpublished", handleUserUnpublished);});
 .remote-user {width: 640px;height: 480px;}
  1. 加入频道

    调用 client.join(appId, channel, token, uid) 方法加入一个 RTC 频道,需要在该方法中传入 app ID 、用户 ID、Token、频道名称。

    appId,token都可以通过pinia取出,channel和uid通过路由参数取出。client.join是一个异步方法,返回一个promise,使用async,await。

    import { useOptionsStore } from "../store/options";
    import {  useRoute } from "vue-router";
    const route = useRoute();
    const store = useOptionsStore();const {options} = store;const { uid , channel} = route.queryconst joinChannel = async () => {await client.join(options.appId,channel,options.token,uid);
    };onMounted(async () => {// 监听远程用户发布事件client.on("user-published", handleUserPublished);client.on("user-unpublished", handleUserUnpublished);await joinChannel();
    });
    
  2. 创建并发布本地音视频轨道

调用 AgoraRTC.createMicrophoneAudioTrack() 通过麦克风采集的音频创建本地音频轨道对象,调用 AgoraRTC.createCameraVideoTrack()通过摄像头采集的视频创建本地视频轨道对象;然后调用 client.publish 方法,将这些本地音视频轨道对象当作参数即可将音视频发布到频道中。并且创建一个容器用于播放本地视频轨道。

<template><divclass="local-player"ref="localPlayerRef"></div>
...
</template>
 import { ref , reactive} from "vue";...const localUser = reactive({videoTrack: null,audioTrack: null,});const localPlayerRef = ref(null);const createTrack = async () => {localUser.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();localUser.videoTrack = await AgoraRTC.createCameraVideoTrack();await client.publish([localUser.audioTrack, localUser.videoTrack]);localUser.videoTrack.play(localPlayerRef.value);}onMounted(async () => {// 监听远程用户发布事件client.on("user-published", handleUserPublished);client.on("user-unpublished", handleUserUnpublished);await joinChannel();await createTrack();});
 .local-player {width: 640px;height: 480px;}</style>
  1. 离开频道
const leaveChannel = async () => {localUser.audioTrack && localUser.audioTrack.close();localUser.videoTrack && localUser.videoTrack.close();// 离开频道if (client) {await client.leave();client = null;}// 返回首页localPlayerRef.value = null;remoteUserRef.value = null;router.push({ name: "Home" });
};onBeforeUnmount(() => {leaveChannel();
});

整体代码:

 <template><!-- 创建本地视频容器 start--><div>本地用户: <divclass="local-player"ref="localPlayerRef"></div></div><!-- 创建本地视频容器 end --><!-- 使用 v-for 循环遍历所有远程用户并为每个用户创建一个视频容器 --><div>远程用户:<divclass="remote-user"ref="remoteUserRef"></div></div></div><button @click="leaveChannel" style="margin-top: 20px; display: block">退出</button></template>
 import {ref,onMounted,onBeforeUnmount,} from "vue";import AgoraRTC from "agora-rtc-sdk-ng";import { useOptionsStore } from "@/store/options";import { useRouter, useRoute } from "vue-router";const router = useRouter();const route = useRoute();const localUser = reactive({videoTrack: null,audioTrack: null,});let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });const localPlayerRef = ref(null);const remoteUserRef = ref(null);const store = useOptionsStore();const { options } = store;onBeforeUnmount(() => {leaveChannel();});const joinChannel = async () => {await client.join(options.appId,channel,options.token,uid);};const createTrack = () => {// 创建并发布本地音频和视频轨道localUser.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();localUser.videoTrack = await AgoraRTC.createCameraVideoTrack();await client.publish([localUser.audioTrack, localUser.videoTrack]);localUser.videoTrack.play(localPlayerRef.value);}const leaveChannel = async () => {localUser.audioTrack && localUser.audioTrack.close();localUser.videoTrack && localUser.videoTrack.close();// 离开频道if (client) {await client.leave();client = null;}// 返回首页localPlayerRef.value = null;remoteUserRef.value = null;router.push({ name: "Home" });};const handleUserPublished = async (user, mediaType) => {await client.subscribe(user, mediaType);if (user.uid !== localUser.uid &&!remoteUsers.value.some((remoteUser) => remoteUser.uid === user.uid)) {remoteUsers.value.push(user);}// 如果是视频轨道,则添加到远程用户列表if (mediaType === "video") {nextTick(() => {const remoteUserRef = remoteUserRefMap.value[`remote_Ref_${user.uid}`];if (remoteUserRef) {user.videoTrack.play(remoteUserRef);}});}// 如果是音频轨道,直接播放if (mediaType === "audio") {user.audioTrack.play();}};const handleUserUnpublished = (user) => {// 移除远程用户remoteUsers.value = remoteUsers.value.filter((remoteUser) => remoteUser.uid !== user.uid);};onMounted(async () => {// 监听远程用户发布事件client.on("user-published", handleUserPublished);client.on("user-unpublished", handleUserUnpublished);await joinChannel();await createTrack();});
 .local-player,.remote-user {width: 640px;height: 480px;}

uniapp小程序端

技术栈:uniapp+vue3+pinia

组件库:uni ui

开发环境准备:

  • 下载并安装最新版的微信开发者工具。
  • 一个经过企业认证的微信小程序账号。在调试小程序 Demo 过程中,需要使用 live-pusher 和 live-player 组件。只有特定行业的认证企业账号才可以使用这两个组件。具体组件见微信官方文档
  • 微信小程序账号需要在微信公众平台进行配置。
  • 参考开通服务在控制台创建项目、获取 App ID 和临时 Token,并开启小程序服务。
  • 一台安装有微信 App 的移动设备。

Console开启小程序服务

第一次使用微信小程序时,需要参考如下步骤开通服务:

  1. 登录声网控制台,点击左侧导航栏的全部产品,选择实时互动 RTC,进入实时互动 RTC 页面。
  2. 在实时互动 RTC 页面,切换到功能配置页签。

在这里插入图片描述

微信小程序配置

  1. 获取小程序组件权限

​ 在微信公众平台的小程序开发选项中,切换到接口设置页签,打开实时播放音视频流实时录制音视频流的开关。

在这里插入图片描述

  1. 配置服务器域名

在小程序的开发设置里,将如下域名配到服务器域名里。

request 合法域名区域填入以 https 开头的域名;

   https://uap-ap-web-1.agora.iohttps://uap-ap-web-2.agoraio.cnhttps://uap-ap-web-3.agora.iohttps://uap-ap-web-4.agoraio.cnhttps://report-ad.agoralab.cohttps://rest-argus-ad.agoralab.cohttps://uni-webcollector.agora.io

socket 合法域名区域点入以 wss 开头的域名。

wss://miniapp.agoraio.cn

在这里插入图片描述

项目开发

  1. 初始化项目

    HBuilder X 可视化(推荐)或者cli脚手架创建uniapp项目。 具体创建方式

    创建项目后,需要集成小程序SDK。有两种方式:

    • 第一种:下载小程序SDK 并解压。

      将解压出来的JS文件复制到项目中主目录的static下,在主目录下创建一个utils,创建一个sdk.js,引入并导出该SDK,用于vue文件可以引入并使用。

      const AgoraMiniappSDK = require('../static/Agora_Miniapp_SDK_for_WeChat.js');export default AgoraMiniappSDK
      
    • 第二种方式:npm 安装

      npm install agora-miniapp-sdk
      
    1. 使用pinia存入音视频所需要的APP ID 和临时token

      如果使用的是HBuilder X 可视化创建的项目,可以直接使用pinia,使用cli脚手架创建的需要手动通过npm install pinia安装

      • 在main.js中初始化pinia,并在主目录创建store目录,创建options.js文件

        //mian.js
        import { createSSRApp } from 'vue'
        import * as Pinia from 'pinia';
        export function createApp() {const app = createSSRApp(App)app.use(Pinia.createPinia());return {app,Pinia}
        }
        
        //options.js
        import { defineStore } from "pinia";
        import {reactive} from "vue";export const useOptionsStore = defineStore('options',() => {const options = reactive({appId: "Your APPID",token: "Your token",})return {options}
        })
        
    2. 引入uni ui组件库

      如果使用HBuilder X ,可以直接通过uni ui组件库导入到项目中

    3. 编写路由文件Pages.json

      暂定只有两个页面,index页面用于输入房间号和用户id,video文件用于视频通话

      {"pages": [{"path": "pages/index/index","style": {"navigationBarTitleText": "主页"}},{"path": "pages/video/video","style": {"navigationStyle":"custom"}}],"globalStyle": {"navigationBarTextStyle": "black","navigationBarTitleText": "uni-app","navigationBarBackgroundColor": "#F8F8F8","backgroundColor": "#F8F8F8"},"uniIdRouter": {}
      }
      
    4. 编写index页面

      ​ 使用uni-forms``````uni-forms-item``````uni-easyinput对用户的输入进行校验,校验通过后携带用户输入的参数进入到视频通话页面

      <template><view class="contain"><uni-forms :modelValue="formData" ref="form" :rules="rules"><uni-forms-item name="channle"><uni-easyinputtype="text"v-model="formData.channle"placeholder="请输入房间号"/></uni-forms-item><uni-forms-item name="uid"><uni-easyinputtype="text"v-model="formData.uid"placeholder="请输入用户uid"/></uni-forms-item><button @click="joinChannle">进入房间</button></uni-forms></view>
      </template><script setup>
      import { ref } from "vue";const formData = ref({channle: "",uid: "",
      });const form = ref(null);const rules = {channle: {rules: [{ required: true, errorMessage: "请输入房间号", trigger: "blur" }],},uid: {rules: [{ required: true, errorMessage: "请输入用户uid", trigger: "blur" }],},
      };const joinChannle = async () => {await form.value.validate();uni.navigateTo({url: `/pages/video/video? channle=${formData.value.channle}&uid=${formData.value.uid}`,});
      };
      </script><style scoped lang="scss">
      ::v-deep(.contain) {display: flex;flex-direction: column;align-items: center;height: 100vh;.uni-forms {width: 80%;margin-top: 40%;.uni-easyinput {.uni-easyinput__content {height: 90rpx;}}button {width: 100%;height: 80rpx;line-height: 80rpx;background-color: #409eff;color: #fff;border: none;border-radius: 10rpx;margin-top: 40rpx;}}
      }
      </style>
      
    5. 编写video.vue

      • 实现视频通话的步骤大致为:初始化客户端对象 -> 订阅远端流 -> 加入频道 -> 发布本地流 -> (通话) ->退出关闭本地流

      • 可以用以下的图片概括

        在这里插入图片描述

        一、代码实现步骤

        1. 初始化客户端对象

          ​ 引入SDK对象 import AgoraMiniappSDK from "../../utils/sdk"

          ​ 在setup中实例化客户端对象let client = new AgoraMiniappSDK.Client();在setup中创建是因为可以最一开始就创建好对象,并且在全局可以访问到同一个客户端对象。实例化好客户端对象后,需要进行初始化,调用的是client.init(appId)方法,appId通过pinia取出,此方法是一个异步函数,返回一个promise对象,可以使用.then(),也可以使用async、await方法。但在setup语法糖中不能直接使用async修饰,所以需要将方法包裹在函数中。

          ​ 在这里会使用微信小程序的onLoad生命周期函数,正好也可以接收从index页面传过来的路由参数。在vue3中,onLoad函数需要引入。import { onLoad } from "@dcloudio/uni-app";

          ​ 到这里,代码就如同:

          <template>
          </template><script setup>
          import AgoraMiniappSDK from "../../utils/sdk";
          import { useOptionsStore } from "../../store/options";
          import { onLoad } from "@dcloudio/uni-app;const store = useOptionsStore();
          const { appId, token } = store.options;let client = new AgoraMiniappSDK.Client();
          let channel,uid;onLoad(async (options) => {if (options) {channel = options.channle;uid = options.uid;}await client.init(appId);
          });</script>
          
        2. 订阅远端流

          ​ 当远端流发布到频道时,会触发 stream-added 事件,需要通过 client.on 监听该事件并在回调中订阅新加入的远端流。当远端用户取消发布流或退出频道时,触发stream-remove事件,关闭及移除对应的流。为了用户能够在进入页面的时候及时监听到远端流,所以将监听事件放入onLoad中。

          onLoad(async (options) => {if (options) {channel = options.channle;uid = options.uid;}client.on("stream-added", handleAdd);client.on("stream-removed", handleRemove);await client.init(appId);
          });
          

          ​ 在handleAdd函数中编写远端用户加入事件,远端用户加入事件中需要做的事情是通过调用client.subscribe(远端加入的用户uid)方法获取到远端用户的远端音视频地址,并将地址绑定在微信的<live-player>的src上,就可以在页面上显示远端用户的视频。所以需要在<template><live-player>组件,也需要定义一个变量livePlayer存入地址。

          ​ 在handleRemove函数中编写远端用户退出事件,就将livePlayer的值置为null,使其不再显示页面上。

          <template><view><!-- 远端拉流 start --><live-playerclass="live-view"id="player":src="livePlayer"mode="RTC":autoplay="true"/><!-- mode为模式,RTC为实时通话,autoplay一定要设置了true才会自动播放 -->      <!-- 远端拉流 end --></view>
          </template><script setup>
          import {ref} from "vue"
          import AgoraMiniappSDK from "../../utils/sdk";
          import { useOptionsStore } from "../../store/options";
          import { onLoad } from "@dcloudio/uni-app;const store = useOptionsStore();
          const { appId, token } = store.options;let client = new AgoraMiniappSDK.Client();
          let channel,uid;const livePlayer = ref(null);const handleAdd = async (e) => {const { url } = await client.subscribe(e.uid);//远端用户uidlivePlayer.value = url;
          };const handleRemove = async (e) => {livePlayer.value = null;
          };onLoad(async (options) => {if (options) {channel = options.channle;uid = options.uid;}await client.init(appId);
          });
          </script>
          
        3. 加入频道

          ​ 调用 client.join(yourToken, channel, uid) 方法加入频道。此方法是异步函数

          ​ 在 client.join 中你需要将 yourToken 替换成你自己生成的 Token。并填入想要加入的频道名以及用户 uid。为了能够及时加入频道,并且使得整个流程清晰,所以将join方法写在onLoad中。

          <template><view>...</view>
          </template><script setup>
          ...const joinChannle = async () => {await client.join(token, channel, uid);
          };...onLoad(async (options) => {if (options) {channel = options.channle;uid = options.uid;}await client.init(appId);await joinChannle();
          });
          </script>
          
        4. 发布本地流

          ​ 成功加入频道后,就能调用 client.publish 方法将本地音视频流发布到频道中。成功发布后,SDK 会返回该路音视频流的 URL。URL用于微信小程序的组件<live-pusher>。同理,需要设置变量pusherPlayer存入地址。需要调用client.setRole(“broadcaster”)方法先将推流的角色设置为主播(broadcaster),才能推流,并且<live-pusher>的autopush需要设置为true

        const livePlayer = ref(null);
        const joinChannle = async () => {await client.setRole("broadcaster");await client.join(token, channel, uid);pusherPlayer.value = await client.publish();
        };
        
        1. 退出关闭本地流

          调用client.cleave()方法退出频道。

        整体代码

      <template><view><!-- 本地推流 start --><live-pusher:url="pusherPlayer"mode="RTC":autopush="true"class="pusher"/><!-- 本地推流 end --><!-- 远端拉流 start --><live-playerclass="live-view":src="livePlayer"mode="RTC"style="width: 100vw; height: 100vh":autoplay="true"/><!-- 远端拉流 end --><!-- 功能按钮 start --><view> <i class="iconfont hang-up return-icon" @click="goBack"></i></view><!-- 功能按钮 end --></view>
      </template><script setup>
      import { ref,  computed, watch } from "vue";
      import AgoraMiniappSDK from "../../utils/sdk";
      import { useOptionsStore } from "../../store/options";
      import { onLoad } from "@dcloudio/uni-app";const store = useOptionsStore();const { appId, token } = store.options;let channel, uid;let client = new AgoraMiniappSDK.Client();const pusherPlayer = ref(null);
      const livePlayer = ref(null);const joinChannle = async () => {await client.init(appId);await client.setRole("broadcaster");await client.join(token, channel, uid);pusherPlayer.value = await client.publish();
      };const handleAdd = async (e) => {const { url } = await client.subscribe(e.uid);livePlayer.value = url;
      };const handleRemove = async (e) => {livePlayer.value = null;
      };const goBack = () => {uni.showModal({title: "提示",content: "确定要退出直播吗?",success: (res) => {if (res.confirm) {client.leave();uni.navigateBack();}},});
      };onLoad(async (options) => {if (options) {channel = options.channle;uid = options.uid;}client.on("stream-added", handleAdd);client.on("stream-removed", handleRemove);await joinChannle();
      });</script>

相关文章:

【声网】实现web端与uniapp微信小程序端音视频互动

实现web端与uniapp微信小程序端音视频互动 利用声网实现音视频互动 开通声网服务 注册声网账号 进入Console 成功登录控制台后&#xff0c;按照以下步骤创建一个声网项目&#xff1a; 展开控制台左上角下拉框&#xff0c;点击创建项目按钮。 在弹出的对话框内&#xff0c;依…...

Spark 为什么比 Hive 快

文章目录 数据处理方式不同并行方式不同稳定性不同Shuffle 方式不同 数据处理方式不同 Spark 是基于内存计算的分布式计算框架&#xff0c;可以在内存中高效地执行数据操作&#xff0c;因此通常比 Hive 更快。Spark 会尽可能将数据加载到内存中&#xff0c;并在内存中执行多个…...

vue使用外部的模板

在 Vue 2 中&#xff0c;如果你希望使用外部的 HTML 文件内容作为模板&#xff0c;有几种方法可以实现&#xff0c;但每种方法都有其局限性或需要注意的事项。下面是一些可能的方法&#xff1a; 1. 使用 AJAX 加载外部 HTML 你可以使用 AJAX 来异步加载外部的 HTML 文件&…...

Python+Django网站指纹信息侦测探查

程序示例精选 PythonDjango网站指纹信息侦测探查 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango网站指纹信息侦测探查》编写代码&#xff0c;代码整洁&#xff0c;规则&#…...

【算法基础实验】图论-UnionFind连通性检测之quick-find

Union-Find连通性检测之quick-find 理论基础 在图论和计算机科学中&#xff0c;Union-Find 或并查集是一种用于处理一组元素分成的多个不相交集合&#xff08;即连通分量&#xff09;的情况&#xff0c;并能快速回答这组元素中任意两个元素是否在同一集合中的问题。Union-Fin…...

JetBrains CLion v2023.3.4 激活版 (C/C++ 集成开发IDE)

前言 JetBrains CLion是一款跨平台的C/C集成开发环境&#xff0c;由JetBrains公司推出。其最新版本支持C14几乎完全&#xff0c;并初步支持C17&#xff0c;使得编写代码更加便捷。CLion还提供了Disassembly view&#xff08;反汇编视图&#xff09;&#xff0c;即使没有源代码…...

一文解析golang中的协程与GMP模型

文章目录 前言1、线程实现模型1.1、用户级线程与内核级线程1.2、内核级线程模型1.3、用户级线程模型1.3、两级线程模型 2、GMP模型2.1、GMP模型概述2.1、GMP v1版本 - GM模型2.2、GMP v2版本 - GMP模型2.3、GMP相关源码2.4 调度流程2.5 设计思想 3.总结 前言 并发(并行&#x…...

深入浅出地探讨Python中字符串拼接的原理及效率优化方法

文章目录 1. 字符串的不可变性与内存模型2. 字符串拼接的底层实现3. 字符串拼接的效率问题及其优化3.1 使用 join() 方法3.2 字符串格式化3.3 实践中的选择和建议 Python中的字符串是不可变的数据类型&#xff0c;这意味着一旦一个字符串被创建&#xff0c;它的内容就无法被改变…...

C语言--基础面试真题

1、局部变量和静态变量的区别 普通局部变量和静态局部变量区别 存储位置&#xff1a; 普通局部变量存储在栈上 静态局部变量存储在静态存储区 生命周期&#xff1a; 当函数执行完毕时&#xff0c;普通局部变量会被销毁 静态局部变量的生命周期则是整个程序运行期间&#…...

C# 异步编程模型(APM)的深入理解

在现代软件开发中&#xff0c;异步编程已经成为了一种不可或缺的技能。特别是在需要处理I/O密集型任务或长时间运行的操作时&#xff0c;异步编程能够显著提高应用程序的性能和用户体验。C# 作为一种流行的编程语言&#xff0c;提供了多种异步编程模型&#xff0c;其中最经典的…...

janus模块介绍-SIP Gateway

模块启动 默认的SIP GateWay也是https协议&#xff0c;端口为8088或者8089 如果需要在自己搭建的测试服务上测试SIP GateWay模块&#xff0c;则也需要修改为wss 具体改动如下: 找到/opt/janus/share/janus/demos/siptest.js var server "wss://" window.location…...

wstunnel (websocket模式ssh)

接上一篇 修改客户端运行参数 ssh -o ProxyCommand"./wstunnel client -L stdio://%h:%p ws://192.168.254.131:8080" 127.0.0.1 其中127.0.0.1为服务端的本地ssh访问&#xff0c;可以修改为通过服务端访问其他设备的ssh服务。例如&#xff1a; ssh -o ProxyComma…...

linux文件相关命令

一、linux文件相关命令 1、cd cd 是 “change directory” 的缩写&#xff0c;用于改变当前工作目录。 cd &#xff1a;不带任何参数时&#xff0c;会切换到用户的主目录&#xff08;通常是 /home/用户名&#xff09;。 cd ~&#xff1a;也会切换到用户的主目录。 cd .&…...

锂电池寿命预测 | Matlab基于GRU门控循环单元的锂电池寿命预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 锂电池寿命预测 | Matlab基于GRU门控循环单元的锂电池寿命预测 Matlab基于GRU的锂电池剩余寿命预测 基于GRU的锂电池剩余寿命预测&#xff08;单变量&#xff09; 运行环境Matlab2020及以上 锂电池的剩余寿命预测是…...

通往AGI路上,DPU将如何构建生成式AI时代的坚实算力基石?

4月19日&#xff0c;在以“重构世界 奔赴未来”为主题的2024中国生成式AI大会上&#xff0c;中科驭数作为DPU新型算力基础设施代表&#xff0c;受邀出席了中国智算中心创新论坛&#xff0c;发表了题为《以网络为中心的AI算力底座构建之路》主题演讲&#xff0c;勾勒出在通往AGI…...

无人驾驶 自动驾驶汽车 环境感知 精准定位 决策与规划 控制与执行 高精地图与车联网V2X 深度神经网络学习 深度强化学习 Apollo

无人驾驶 百度apollo课程 1-5 百度apollo课程 6-8 七月在线 无人驾驶系列知识入门到提高 当今,自动驾驶技术已经成为整个汽车产业的最新发展方向。应用自动驾驶技术可以全面提升汽车驾驶的安全性、舒适性,满足更高层次的市场需求等。自动驾驶技术得益于人工智能技术的应用…...

【QEMU系统分析之启动篇(十九)】

系列文章目录 第十九章 QEMU系统仿真的加速器上电后设置分析 文章目录 系列文章目录第十九章 QEMU系统仿真的加速器上电后设置分析 前言一、QEMU是什么&#xff1f;二、QEMU系统仿真的启动分析1.系统仿真的初始化代码2.主循环数据初始化3. os_setup_post()Windows 系统 os_set…...

Jmeter redis连接测试

Jmeter连接redis获取数据&#xff0c;一直连不上报错。最后只能通过java代码连接测试&#xff0c;最后只能自己动手。 import redis.clients.jedis.*;import java.io.IOException; import java.util.HashSet; import java.util.Set;/*** 单机版的Jedis连接池的用法*/ public c…...

使用Uiautomotorviewer无法获取手机页面元素+解决办法

在进行 Android 应用程序开发或测试时&#xff0c;有时会遇到以下错误&#xff1a; Error while obtaining UI hierarchy XML file: com.android.ddmlib.SyncException这个错误可能会导致开发或测试过程中的一些困扰&#xff0c;但有一个简单的解决方法&#xff1a; 解决方法…...

深度相机(3D相机)

传统的RGB彩色相机称为2D相机&#xff0c; 只能得到2D的图像信息&#xff0c; 无法得到物体与相机的距离信息&#xff0c;也就是深度信息。 顾名思义&#xff0c; 深度相机除了获取2D信息&#xff0c;还能得到深度信息&#xff0c;也叫RGBD相机&#xff0c; 或3D相机。 顺便提…...

了解时间复杂度和空间复杂度

在学习数据结构前&#xff0c;我们需要了解时间复杂度和空间复杂度的概念&#xff0c;这能够帮助我们了解数据结构。 算法效率分为时间效率和空间效率 时间复杂度 一个算法的复杂度与其执行的次数成正比。算法中执行基础操作的次数&#xff0c;为算法的时间复杂度。 我们采…...

[笔试强训]day2

1.牛牛的快递 题目链接&#xff1a;牛牛的快递_牛客题霸_牛客网 思路&#xff1a;分小于1.0kg和大于1.0kg&#xff0c;其中大于1.0kg的要“向上取整” &#xff0c;eg&#xff1a;1.7->2&#xff0c;2.0->2。注意一个点&#xff1a;第二个输入的参数是字符&#xff0c;要…...

android脱壳第二发:grpc-dumpdex加修复

上一篇我写的dex脱壳&#xff0c;写到银行类型的app的dex修复问题&#xff0c;因为dex中被抽取出来的函数的code_item_off 的偏移所在的内存&#xff0c;不在dex文件范围内&#xff0c;所以需要进行一定的修复&#xff0c;然后就停止了。本来不打算接着搞得&#xff0c;但是写了…...

在 TypeScript 中declare module 关键字用法

在 TypeScript 中&#xff0c;declare module 关键字用于声明模块的类型信息&#xff0c;这种声明通常出现在声明文件&#xff08;通常是 .d.ts 文件&#xff09;中。这对于当你需要为现有的 JavaScript 库或模块提供类型信息时非常有用&#xff0c;尤其是对于没有提供自己的类…...

NLP step by step -- 了解Transformer

Transformer模型 Transformer相关历史 首先我们先看一下有关Transformer模型的发展历史&#xff0c;下面的图是基于Transformer架构的一些关键模型节点&#xff1a; 图片来源于Hugging Face 图片来源于Hugging Face Transformer 架构 于 2017 年 6 月推出。原本研究的重点是…...

【Leetcode】vector刷题

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;Leetcode刷题 目录 1.只出现一次的数字2.杨辉三角3.删除有序数组中的重复项4.只出现一次的数字II5.只出现一次的数字III6.电话号码的字母组合 1.只出现一次的数字 题目链接&#xff1a;136.只出现一…...

【图解计算机网络】从浏览器地址输入到网页显示的整个过程

从浏览器地址输入到网页显示的整个过程 整体流程DHCPhttp协议报文组装DNSTCP协议封装与TCP三次握手IP协议封装与路由表MAC地址与ARP协议交换机路由器 整体流程 从往浏览器输入一个地址到网页的显示&#xff0c;要经过很长的一个流程&#xff0c;中间涉及到计算机网络的许多知识…...

liqo学习及安装,k8s,kubernetes多集群互联

先按照官方的教程在虚拟机安装学习 在开始以下教程之前&#xff0c;您应该确保您的系统上安装了以下软件&#xff1a; Docker&#xff0c;容器运行时。Kubectl&#xff0c;Kubernetes 的命令行工具。 curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.…...

五种主流数据库:集合运算

关系型数据库中的表与集合理论中的集合类似&#xff0c;表是由行&#xff08;记录&#xff09;组成的集合。因此&#xff0c;SQL 支持基于数据行的各种集合运算&#xff0c;包括并集运算&#xff08;Union&#xff09;、交集运算&#xff08;Intersect&#xff09;和差集运算&a…...

java-springmvc 01

springmvc也是在spring framework中的&#xff0c;不是一个单独的项目 MVC就是和Tomcat有关。 01.MVC启动的第一步&#xff0c;启动Tomcat&#xff08;这个和springboot的run方法启动Tomcat有关&#xff09; 02.SpringMVC中&#xff0c;最为核心的就是DispatcherServlet&…...

SecuPress Pro 专业级WordPress网站安全防护插件优化版

下载地址&#xff1a;SecuPress Pro 专业版.zip SecuPress Pro&#xff1a;专业的WordPress安全解决方案 如果您没有时间进行每周扫描&#xff0c;SecuPress Pro将是您的理想选择。SecuPress Pro提供了所有SecuPress Free的功能&#xff0c;同时还增加了一些高级选项&#xff…...

linux 查看nginx日志

在 Linux 系统中&#xff0c;查看 Nginx 日志通常涉及以下几个步骤&#xff1a; 确定日志文件位置&#xff1a;Nginx 的日志文件通常位于 /etc/nginx/logs 或 /var/log/nginx。具体位置取决于您在安装 Nginx 时的配置。 查看访问日志&#xff1a;Nginx 的访问日志默认命名为 a…...

iOS - 多线程-GCD-队列组

文章目录 iOS - 多线程-GCD-队列组1. 队列组1.1 基本使用步骤 iOS - 多线程-GCD-队列组 开发过程中&#xff0c;有时候想实现这样的效果 多个任务并发执行所有任务执行完成后&#xff0c;进行下一步处理&#xff08;比如回到主线程刷新UI&#xff09; 1. 队列组 可以使用GC…...

1052. 【NOIP2016备赛】方阵操作(square)

1052. 【NOIP2016备赛】方阵操作(square) (Input: square.in, Output: square.out) 时间限制: 1 s 空间限制: 256 MB 题目描述 小 Z 给你一个 n n 的方阵&#xff0c;要求你完成 Q 次操作&#xff1a; 1. 1 i j k&#xff0c;将 ai,j 修改为 k。 2. 2 i j&#xff0c;交…...

Python打怪升级(4)

在计算机领域常常有说"合法"和"非法"指的是:是否合理&#xff0c;是否有效&#xff0c;并不是指触犯了法律。 random.randint(begin,end) 详细讲解一下这个random是指模板&#xff0c;也就是别人写好的代码直接来用&#xff0c;在Python当中&#xff0c;…...

2024年【危险化学品生产单位安全生产管理人员】考试题库及危险化学品生产单位安全生产管理人员考试报名

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品生产单位安全生产管理人员考试题库参考答案及危险化学品生产单位安全生产管理人员考试试题解析是安全生产模拟考试一点通题库老师及危险化学品生产单位安全生产管理人员操作证已考过的学员汇总&#xff0c;…...

动态IP与静态IP的区别,你选对了吗?

在互联网世界中&#xff0c;IP地址是每台设备在网络上的唯一标识。这些地址可以是动态的&#xff0c;也可以是静态的。对于非专业人士来说&#xff0c;理解这两者之间的区别可能会有些困难。本文旨在深入探讨动态IP和静态IP的主要差异&#xff0c;帮助读者根据自己的需求做出明…...

jasypt组件死锁bug案例分享

事故描述 1、上午9.55发布了一个Apollo动态配置参数&#xff1b; 2、片刻后&#xff0c;服务器接口开始出现大量的超时告警&#xff0c;似乎是某资源被耗尽不足分配&#xff1b; 3、正值业务请求高峰的上午十点&#xff08;平台上午10点会有一些活动会拉一波用户流量&#x…...

golang上传文件到ftp服务器

之前有个业务需要把文件上传到ftp服务器&#xff0c;写了一个上传ftp的功能 package ftpimport "context"type Client interface {// UploadFile 上传文件UploadFile(ctx context.Context, opt *UploadFileOpt) error }type UploadFileOpt struct {Data […...

数据治理和数据管理 傻傻分不清楚?

互联网时代&#xff0c;数据&#xff0c;这一无形资产&#xff0c;已成为现代企业的核心竞争力。如何高效地管理和利用数据&#xff0c;成为企业关注的焦点。在这个过程中&#xff0c;数据治理&#xff08;Data Governance&#xff09;和数据管理&#xff08;Data Management&a…...

linux磁盘原理

在linux系统中&#xff0c;对磁盘进行管理与windows系统类似&#xff0c;都要先分区&#xff0c;格式化&#xff0c;创建文件系统&#xff0c;挂载目录&#xff0c;数据写入...

react 使用WEB3.0控件开发包 V3.0接入海康威视摄像头

1、下载官方安装包&#xff1a; 2、安装官方插件 3、引入文件 在public/index 中引入监控依赖&#xff0c;这三个文件可以在下载的官方demo中找到 4、react 中使用 useEffect(() > { const ipInfo :[192.168.xxxx];//初始化摄像头const WebVideoCtrl window.WebVideoCtrl…...

数据可视化———Tableau

基本认识&#xff1a; 维度&#xff1a;定性—字符串文本&#xff0c;日期和日期时间等等 度量&#xff1a;定量—连续值&#xff0c;一般属于数值 数据类型&#xff1a; 数值 日期/日期时间 字符串 布尔值 地理值 运算符 算数运算符&#xff1a;加减乘除,%取余&#xff0c;…...

【黑马头条】-day12项目部署和发布-jenkins

文章目录 1 持续集成2 软件开发模式2.1 瀑布模式2.2 敏捷开发2.2.1 迭代开发2.2.2 增量开发 3 Jenkins3.1 Jenkins安装3.1.1 导入镜像3.1.2 配置3.1.3 初始化设置 3.2 插件安装3.3 服务器环境准备3.3.1 Docker安装配置3.3.2 Git安装配置3.3.3 Maven安装配置 3.4 Jenkins工具配置…...

学习操作系统路线

操作系统 简介 本课程为计算机专业学生量身定制&#xff0c;补足计算机操作系统相关知识&#xff0c;查漏补缺&#xff0c;也可用于考研复习。内容包括&#xff1a;操作统概述、进程管理、内存管理、文件管理、输入/输出管理等章节。内容力求精炼、重点突出、条理清晰、深入浅…...

uniapp微信小程序(商城项目)

最近&#xff0c;闲来无事&#xff0c;打算学一下uniapp小程序 于是在跟着某站上学着做了一个小程序&#xff0c;主要是为了学uniapp和vue。某站黑马优购 完成的功能主要有&#xff1a;首页、搜索、分类和购物车。 有人问了为什么没有登录、和添加订单呢&#xff1f;问的很好…...

Linux的自动化脚本:使用crul命令下载文件,实现断点续传

目录 一、要求 二、解决思路 &#xff08;一&#xff09;curl工具可以进行文件传输&#xff0c;可以实现手动断点续传 1、使用 --range 选项&#xff1a; 2. 使用 --continue-at 选项&#xff1a; &#xff08;二&#xff09;编写shell脚本调用curl命令&#xff0c;实现自…...

Golang | Leetcode Golang题解之第46题全排列

题目&#xff1a; 题解&#xff1a; func permute(nums []int) [][]int {var (n len(nums)dfs func(vals []int) // 已选择数 排列为vals 后续回溯继续选择 直至选完ans [][]int)dfs func(vals []int) {//边界if len(vals) n {ans append(ans, vals)}//转移 枚举选哪个f…...

MySQL数据表记录删操作

删除操作 作用删除表里的记录行&#xff08;都是整行整行的删除的&#xff09; 1.单表的删除 语法&#xff1a; delete from 表名 where 要删除的记录筛选条件; 案例&#xff1a;删除员工编号大于203的员工信息 delete from employees where employee_id>203; 2.多表…...

Python浅谈清朝秋海棠叶版图

1、清朝疆域概述&#xff1a; 清朝是我国最后一个封建王朝&#xff0c;其始于1616年建州女真部努尔哈赤建立后金&#xff0c;此后统一女真各部、东北地区。后又降服漠南蒙古&#xff0c;1644年入关打败农民起义军、灭南明&#xff0c;削三藩&#xff0c;复台湾。后又收外蒙&am…...

Python项目开发实战:俄罗斯方块游戏(案例教程)

一、项目概述 俄罗斯方块(Tetris)是一款经典的益智游戏,它以其简单易懂的游戏规则和丰富的变化吸引了无数玩家。本项目旨在使用Python编程语言及其图形库Pygame来开发一个基础的俄罗斯方块游戏。通过本项目,我们将了解游戏开发的基本流程,掌握Python和Pygame的基本用法,并…...

UE5介绍及安装

**虚幻引擎5&#xff08;Unreal Engine 5&#xff0c;简称UE5&#xff09;**是由Epic Games公司开发的一款游戏引擎&#xff0c;它提供了强大的游戏开发功能和工具&#xff0c;帮助游戏开发者更加高效、灵活地创建出高品质的游戏。UE5在虚幻4的基础上进行了大量的升级和改进&am…...

关于 c++的模板库中的数组模板 is_array_v的测试

&#xff08;1&#xff09;该模板的源代码如下&#xff1a; template <class> // determine whether type argument is an array bool is_array_v false;template <class _Ty, size_t _Nx> bool is_array_v<_Ty[_Nx]> true;template <class _Ty>…...

JVM六种常见的垃圾回收器-重点含CMS过程详解

一、jdk 1.8 及其之前的分代模型: 堆内存结构必须熟悉&#xff1a;&#xff08;垃圾回收&#xff0c;性能调优常用&#xff09; 过程简单分析&#xff1a; ①年轻代和老年代在堆内存中的占比默认 1 &#xff1a;2 ②年轻代又分为两个区&#xff0c;伊甸园区(Eden)和幸存区(S0和…...

扫雷(升级版)附全代码

上次我写了简单版本的扫雷&#xff08;建议看一下扫雷【C语言】-CSDN博客&#xff09;&#xff0c;但是有些功能并没有实现。比如&#xff1a; 没有如果排查位置不是雷&#xff0c;可以展开周围的功能。没有标记的功能。 在这篇中这些功能都会被实现。 文章目录 展开 标记 …...

mac查看Linux服务器的性能

mac上安装 linux系统 如果有 linux服务器账号密码&#xff0c;那么上一步可忽略&#xff1b; 比如&#xff1a;直接连接阿里云或腾讯云账号 1. 安装termius 链接: https://pan.baidu.com/s/1iYsZPZThPizxqtkLPT89-Q?pwdbw6j 提取码: bw6j 官网 Termius - SSH platform for …...

公钥私钥?一文搞懂非对称加密

非对称加密 非对称加密&#xff1a; 通信双方分别创建公钥和私钥&#xff0c;并且保证公钥所加密的信息&#xff0c;只有配对的私钥可以解密&#xff0c;接下来&#xff0c;双方公开交换公钥&#xff0c;通信时&#xff0c;使用对方的公钥进行加密&#xff0c;如此&#xff0…...

pandas入门

pandas入门 一、pandas简介1.1 pandas介绍1.2 pandas的基本功能 二、pandas快速入门2.1 读取数据2.2 验证数据2.3 建立索引2.4 数据抽取2.4.1 选择列2.4.2 选择行2.4.3 指定行和列 2.5 排序2.6 分组聚合2.7 数据转置2.8 增加列2.9 统计分析 一、pandas简介 1.1 pandas介绍 pa…...

922. 按奇偶排序数组 II

922. 按奇偶排序数组 II 题目链接&#xff1a;922. 按奇偶排序数组 II 代码如下&#xff1a; class Solution { public:vector<int> sortArrayByParityII(vector<int>& nums) {vector<int> evenNums,oddNums;vector<int> res(nums.size());for(…...

UE 将UObject写到Package,加载Package获取UObject

1&#xff0c;将UObject写到Package中 //CachedMesh 就是要写的UObject对象 UStaticMesh* CachedMesh CachedFontData.GetGlyphMesh(ShapedGlyph.GlyphIndex, TextParamValue.bOutline, TextParamValue.Extrude, TextParamValue.Bevel, TextParamValue.BevelType, TextParam…...

ComfyUI 基础教程(十四):ComfyUI中4种实现局部重绘方法

在ComfyUI中有多种方式可以实现局部重绘,简单的方式是使用VAE内补编码器进行局部重绘,也可以用Fooocus inpaint进行局部重绘,还可以用controlNet的inpaint模型进行局部重绘,以及使用Clip seg蒙版插件! 本篇介绍使用VAE內补编码器进行局部重绘的方法。 1、VAE内补编码器 局…...

代码随想录第51天 | 309.最佳买卖股票时机含冷冻期

309.最佳买卖股票时机含冷冻期 309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 动态规划来决定最佳时机&#xff0c;这次有冷冻期&#xff01;| LeetCode&#xff1a;309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bi…...