Android使用声网SDK实现音视频互动(RTC)功能

news2025/4/18 19:24:44

一、前期准备

1、注册声网账号

声网官网

2、创建项目

拿到AppID,主要证书

二、代码部分

先上一下官方提供的demo地址:

Agora-RTC-QuickStart: 此仓库包含 Agora RTC Native SDK 的QuickStart示例项目。 - Gitee.comhttps://gitee.com/agoraio-community/Agora-RTC-QuickStart/tree/main/Android/Agora-RTC-QuickStart-Android可以在声网的帮助文档中看下图的教程很详细,或者无脑跑上面的demo,只需要填入声网控制台上获取到的appid,证书,和生成的临时token,以及生成临时token时填入的渠道号,但是控制台生成的临时token只有一天的有效期,下面会给出服务端生成临时token的代码,自己部署到服务器上,用客户端去调用接口

服务端:

提供一个获取token的接口

//还没要到代码,后续会补充上来,或者自行去帮助文档中查看,注意是rtc_token

客户端:

1、配置仓库

在settings.gradle中配置,主要是配置镜像

pluginManagement {
    repositories {

        maven { url "https://maven.aliyun.com/repository/public" }
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        maven { url "https://maven.aliyun.com/repository/public" }
        google()
        mavenCentral()
    }
}

rootProject.name = "你的项目名称"
include ':app'

2、导入声网的sdk

在app模块下的build.gradle的dependencies中加入下面这行,注意下面这个是轻量级的库,详细的库在声网自行搜索

 implementation 'io.agora.rtc:lite-sdk:4.5.1' //替换为最新的

 3、添加防混淆规则

在app模块下的proguard-rules.pro文件中加入下面代码

-keep class io.agora.**{*;}
-dontwarn io.agora.**

4、 静态声明权限

在AndroidManifest.XML文件中声明如下权限

  <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

    <!--必要权限-->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!--可选权限-->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下 SDK 的设备,还需要添加以下权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
    <!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

5、具体代码部分

注:如果需要两个客户端互相传输音视频的话,直接用上面给的官方demo的代码就行,下面介绍的是对官方代码的一些封装,可以满足单西向传输,动态申请权限,调用临时token的接口,token本地存储及校验和不同页面的使用方法

(1)创建bean对象:
import java.io.Serializable

/*
* name :相机的名称
* channelName :频道名称 必须唯一
* uid :用户唯一id
* token : 临时token
* lastPostTime :上次成功获取到数据的时间
*/
data class Camera(
    val name: String,
    val channelName: String = "",
    var uid:Int = 0,
    var token: String? = null,
    var lastPostTime: Long = 0
) : Serializable
(2)token的本地存储工具类
object SpUtil {
    private val context = App.app!!

    val sharedPreferences: SharedPreferences =
        context.getSharedPreferences("camera", Context.MODE_PRIVATE)

    //获取绑定的摄像头列表
    fun getCameraData(): List<Camera> {
        val listStr = sharedPreferences.getString("list", "")
        if (listStr == "") {
            return listOf<Camera>()
        } else {
            val typeToken = object : TypeToken<List<Camera>>() {}.type
            return Gson().fromJson(listStr, typeToken)
        }
    }
    //保存绑定的摄像头列表
    fun saveCameraListData(list: List<Camera>) {
        sharedPreferences.edit().apply {
            putString("list", Gson().toJson(list))
            apply()
        }
    }
    //更新绑定的摄像头列表
    fun updateCameraData(channelName: String, token: String, uid: Int) {
        val list = getCameraData().toMutableList()
        val localCameraList = list.filter { it.channelName == channelName }

        if (localCameraList.isNotEmpty()) {
            val localCamera = localCameraList.first()
            localCameraList.forEach {
                list.remove(it)
            }
            localCamera.token = token
            localCamera.lastPostTime = System.currentTimeMillis()
            localCamera.uid = uid
            list.add(localCamera)
        }
        saveCameraListData(list)
    }

    //检查绑定摄像头的token是否过期
    fun checkToken(camera: Camera): Boolean {
        val list = getCameraData()
        val localCameraList = list.filter { it.channelName == camera.channelName }
        if (localCameraList.isNotEmpty()) {
            val localCamera = localCameraList.first()
            // 判断token是否过期
            val checkTime = System.currentTimeMillis() - localCamera.lastPostTime <  43200000
            if (localCamera.token != null && checkTime) {
                return true
            }
        }
        return false
    }

    //获取本地摄像头的数据
    fun getLocalCameraData(): Camera {
        val str = sharedPreferences.getString("localCamera", "")
        if (str == "") {
            val localCamera = Camera("本机", PlatformApp.getInstance().oaid)
            saveLocalCameraData(localCamera)
            return localCamera
        } else {
            val typeToken = object : TypeToken<Camera>() {}.type
            return Gson().fromJson(str, typeToken)
        }
    }

    //保存本地摄像头的数据
    fun saveLocalCameraData(camera: Camera) {
        sharedPreferences.edit().apply {
            putString("localCamera", Gson().toJson(camera))
            apply()
        }
    }

    //检查本地摄像头的token是否过期
    fun checkLocalCameraData(): Boolean {
        val localCamera = getLocalCameraData()
        // 判断token是否过期
        val checkTime = System.currentTimeMillis() - localCamera.lastPostTime < 43200000
        return localCamera.token != null && checkTime
    }
}
(3)RTC的管理类

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.SurfaceView
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.kwad.sdk.utils.bt.runOnUiThread
import fczs.colorscol.rrjj.base.App
import fczs.colorscol.rrjj.beans.Camera
import io.agora.rtc2.ChannelMediaOptions
import io.agora.rtc2.Constants
import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtc2.RtcEngine
import io.agora.rtc2.RtcEngineConfig
import io.agora.rtc2.video.VideoCanvas
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
import kotlin.coroutines.resume

class RtcManger {

    private val baseContext = App.app!!

    // 填写项目的 App ID,可在声网控制台中生成
    private val appId = "你的AppId"
    
    //临时token
    var token = ""
    
    //uid每个渠道应保持唯一性,为0的话,sdk会自动分配一个,但如果临时token是自己服务器生成的,那就应该保持和服务给的一致,否则token鉴权不过,无法加入渠道
    var uid = 0

    //要加入的渠道
    var channelName = "";

    private var mRtcEngine: RtcEngine? = null

    //权限回调码
    val PERMISSION_REQ_ID: Int = 22

    //远程视频视图容器
    var remoteVideoViewContainer: FrameLayout? = null

    //本地视图容器
    var localVideoViewContainer: FrameLayout? = null

    private val mRtcEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {
        // 监听频道内的远端用户,获取用户的 uid 信息
        override fun onUserJoined(uid: Int, elapsed: Int) {
            runOnUiThread { // 获取 uid 后,设置远端视频视图
                setupRemoteVideo(uid)
            }
        }

        override fun onUserOffline(uid: Int, reason: Int) {
            super.onUserOffline(uid, reason)
            runOnUiThread {
                remoteVideoViewContainer?.removeAllViews()
            }
        }
    }

    fun init(camera: Camera) {
        this.channelName = camera.channelName
        this.token = camera.token ?: ""
        this.uid = camera.uid
    }

    /*
    * clientRoleType: 用户角色类型,默认为 Constants.BROADCASTER (主播) 发送方 ,还可以是Constants.CLIENT_ROLE_AUDIENCE(观众) 接收方
    * localVideoViewContainer: 本地视频视图容器
    * remoteVideoViewContainer: 远端视频视图容器
    */
    fun initializeAndJoinChannel(
        clientRoleType: Int = Constants.CLIENT_ROLE_BROADCASTER,
        localVideoViewContainer: FrameLayout? = null,
        remoteVideoViewContainer: FrameLayout? = null
    ) {
        this.remoteVideoViewContainer = remoteVideoViewContainer
        this.localVideoViewContainer = localVideoViewContainer
        try {
            // 创建 RtcEngineConfig 对象,并进行配置
            val config = RtcEngineConfig()
            config.mContext = baseContext
            config.mAppId = appId

            //添加远端视频视图handler
            if (remoteVideoViewContainer != null) {
                config.mEventHandler = mRtcEventHandler
            }
            // 创建并初始化 RtcEngine
            mRtcEngine = RtcEngine.create(config)

        } catch (e: Exception) {
            throw RuntimeException("Check the error.")
        }
        // 启用视频模块
        mRtcEngine!!.enableVideo()

        //本地视图显示
        if (localVideoViewContainer != null) {
            // 开启本地预览
            mRtcEngine!!.startPreview()
            // 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象
            val container = localVideoViewContainer
            val surfaceView = SurfaceView(baseContext)
            container.addView(surfaceView)
            // 将 SurfaceView 对象传入声网实时互动 SDK,设置本地视图
            mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
        }

        // 创建 ChannelMediaOptions 对象,并进行配置
        val options = ChannelMediaOptions()
        // 根据场景将用户角色设置为 BROADCASTER (主播) 或 AUDIENCE (观众)
        options.clientRoleType = clientRoleType
        // 直播场景下,设置频道场景为 BROADCASTING (直播场景)
        options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING

        // 使用临时 Token 加入频道,自行指定用户 ID 并确保其在频道内的唯一性
        mRtcEngine!!.joinChannel(token, channelName, uid, options)
    }


    // 获取体验实时音视频互动所需的录音、摄像头等权限
    fun getRequiredPermissions(): Array<String> {
        // 判断 targetSDKVersion 31 及以上时所需的权限
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,  // 录音权限
                Manifest.permission.CAMERA,  // 摄像头权限
                Manifest.permission.READ_PHONE_STATE,  // 读取电话状态权限
                Manifest.permission.BLUETOOTH_CONNECT, // 蓝牙连接权限
            )
        } else {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.CAMERA
            )
        }
    }

    private fun setupRemoteVideo(uid: Int) {
        if (remoteVideoViewContainer != null) {
            val container = remoteVideoViewContainer!!
            val surfaceView = SurfaceView(baseContext)
            surfaceView.setZOrderMediaOverlay(true)
            container.addView(surfaceView)
            // 将 SurfaceView 对象传入声网实时互动 SDK,设置远端视图
            mRtcEngine!!.setupRemoteVideo(
                VideoCanvas(
                    surfaceView,
                    VideoCanvas.RENDER_MODE_FIT,
                    uid
                )
            )
        }
    }

    fun checkPermissions(context: Context): Boolean {
        for (permission in getRequiredPermissions()) {
            val permissionCheck = ContextCompat.checkSelfPermission(context, permission)
            if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
                return false
            }
        }
        return true
    }

    fun close() {
        // 停止本地视频预览
        mRtcEngine?.stopPreview()
        // 离开频道
        mRtcEngine?.leaveChannel()

        localVideoViewContainer?.removeAllViews()
    }

    //获取后台数据  channelName:渠道名称  expire: 获取到的临时token的有效时间  localCamera: 是否是本地摄像头(摄像)
    suspend fun requestData(channelName: String, localCamera: Boolean = false): String {
        return suspendCancellableCoroutine { continuation ->

            val fromBody = FormBody.Builder()
                .add("channelName",channelName)
                .add("uid","能保持唯一性的字符串,如设备的oaid")
                .build()
            val request = Request.Builder()
                .url("自己服务器接口的url")
                .post(fromBody)
                .build()

            // 发起异步请求
            OkHttpClient().newCall(request).enqueue(object : Callback {
                override fun onResponse(call: Call, response: Response) {
                    if (response.isSuccessful) {
                        try {
                            val responseBody = response.body?.string() ?: ""
                            val jsonObject = JSONObject(responseBody)
                            val code = jsonObject.getInt("code")
                            if (code == 200) {

                                var data = jsonObject.getJSONObject("data")
                                val token = data.getString("token")
                                val uid = data.getInt("uid")
                                this@RtcManger.channelName = channelName
                                this@RtcManger.token = token
                                this@RtcManger.uid = uid
                                if (localCamera) {
                                    //更新本机摄像头发送视图时的数据
                                    SpUtil.saveLocalCameraData(Camera("本机", channelName, uid, token, System.currentTimeMillis()))
                                } else {
                                    //更新本地存储的绑定摄像头的数据
                                    SpUtil.updateCameraData(channelName, token, uid)
                                }
                                continuation.resume("true")
                            } else {
                                continuation.resume("false")
                            }
                        } catch (e: Exception) {
                            // JSON 解析失败
                            continuation.resume("false")
                            Log.e("Request failed", "Json解析失败:" + e.message.toString())
                        }
                    }
                }

                override fun onFailure(call: Call, e: IOException) {
                    continuation.resume("false")
                    Log.e("Request failed", "请求失败:" + e.message.toString())
                }
            })
        }
    }
}
(4)发送音视频界面
界面:

确报有一个下面的布局就行

  <FrameLayout
        android:id="@+id/local_video_view_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
   />
代码:

在activity/fragment中声明如下变量和方法,示例中发送端是fragment,后面接收端是activity的示例代码

private val rtcManger = RtcManger()
private val localCamera by lazy { SpUtil.getLocalCameraData() }

//正常动态申请权限应该在activity的结果回调方法中写,但如果在onResume中执行判断就可以不用写结果回调方法
override fun onResume() {
        super.onResume()

        // 如果已经授权,则初始化 RtcEngine 并加入频道
        if (rtcManger.checkPermissions(requireContext())) {
            binding.permissionLL.container.visibility = View.GONE
            checkToken()
        } else {
            //显示无权限布局
            binding.permissionLL.apply {
                container.visibility = View.VISIBLE
                permissionBt.setOnClickListener {
                    ActivityCompat.requestPermissions(
                        requireActivity(),
                        rtcManger.getRequiredPermissions(),
                        rtcManger.PERMISSION_REQ_ID
                    )

                }
            }
        }
    }

private fun checkToken() {
        binding.permissionLL.container.visibility = View.GONE
        if (SpUtil.checkLocalCameraData()) {
            rtcManger.init(localCamera)
            start()
        } else {
             
            CoroutineScope(Dispatchers.Main).launch {
                connectDialog.show()
                val result = withContext(Dispatchers.IO) {
                    rtcManger.requestData(localCamera.channelName, true)
                }.toBoolean()
                connectDialog.dismiss()
                if (result) {
                    start()
                } else {
                    Toast.makeText(requireContext(), "服务器异常,请稍后重试", Toast.LENGTH_SHORT)
                        .show()
                }
            }
        }
    }

 private fun start() {
        rtcManger.initializeAndJoinChannel(
            Constants.CLIENT_ROLE_BROADCASTER,
            binding.localVideoViewContainer
        )
    }

    private fun stop() {
        rtcManger.close()
    }

    override fun onPause() {
        super.onPause()
        stop()
    }
 (5)接收音视频界面
界面:
    <FrameLayout
        android:id="@+id/remote_video_view_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
代码: 

和上面发送的大差不差,只是进入acticity时传递了要发送端的Camera对象,里面有渠道号,token等信息,这些发送端在拉token的时候已经通过SpUtil存到本地了,自己读取一下需要的

private lateinit var camera: Camera

public override fun onCreate(savedInstanceState: Bundle?) {

 camera = intent.getSerializableExtra("camera") as Camera

}

 override fun onResume() {
        super.onResume()
        // 如果已经授权,则初始化 RtcEngine 并加入频道
        if (rtcManger.checkPermissions(this)) {
            binding.permissionLL.container.visibility = View.GONE
            checkToken()
        } else {
            binding.permissionLL.apply {
                container.visibility = View.VISIBLE
                permissionBt.setOnClickListener {
                    ActivityCompat.requestPermissions(
                        this@CameraActivity,
                        rtcManger.getRequiredPermissions(),
                        rtcManger.PERMISSION_REQ_ID
                    )
                }
            }
        }
}

    @Deprecated("Deprecated in Java")
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        // 系统权限申请回调
        if (rtcManger.checkPermissions(this)) {
            checkToken()
        }
    }

 private fun checkToken() {
        binding.permissionLL.container.visibility = View.GONE

        if (SpUtil.checkToken(camera)) {
            rtcManger.init(camera)
            start()
        } else {
           
            CoroutineScope(Dispatchers.Main).launch {
                connectDialog.show()
                val result = withContext(Dispatchers.IO) {
                    rtcManger.requestData(camera.channelName)
                }.toBoolean()
                if (result) {
                    connectDialog.dismiss()
                    start()
                } else {
                    connectDialog.showFailView {
                        finish()
                    }
                }
            }
        }
    }

  fun start() {
        rtcManger.initializeAndJoinChannel(
            Constants.CLIENT_ROLE_AUDIENCE,
            null,
            binding.remoteVideoViewContainer
        )
    }

    override fun onPause() {
        super.onPause()
        binding.remoteVideoViewContainer.removeAllViews() 
        rtcManger.close()
    }

ok,就是这样,总体来说是很简单的,发送端和接收端只是start方法不一样,其余的都差不多,希望上面的经验能帮到你

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

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

相关文章

FPGA_modelsim错误总结

1&#xff0c; 使用modelsim仿真DDR3报错Module ‘SIP_PHY_CONTROL‘ is not defined 在配置ddr3的时候vivado 速度太慢了&#xff0c;所以选用modelsim。我的是2018.3vivado&#xff0c;modelsim用了10.4 但是不行报错 然后看了帖子说 questasim可以下载了还是报错。 然后又…

了解 DeFi:去中心化金融的入门指南与未来展望

去中心化金融&#xff0c;或 DeFi&#xff0c;代表着全球金融体系运作方式的革命性转变。它是一个总称&#xff0c;指的是一个不断增长的去中心化应用程序&#xff08;dapp&#xff09;、协议和平台生态系统&#xff0c;这些生态系统构建在公共区块链网络上&#xff0c;无需传统…

Python爬虫第10节-lxml解析库用 XPath 解析网页

目录 引言 一、XPath简介 二、XPath常用规则 三、实例讲解 四、节点的选取 4.1 所有节点的选取 4.2 子节点的选取 4.3 父节点选取 五、属性匹配获取及文本获取 5.1 属性匹配 5.2 文本获取 5.3 属性获取 5.4 属性多值匹配 5.5 多属性匹配 六、按序选择 七、节点…

【C语言】预处理(预编译)(C语言完结篇)

一、预定义符号 前面我们学习了C语言的编译和链接。 在C语言中设置了一些预定义符号&#xff0c;其可以直接使用&#xff0c;预定义符号也是在预处理期间处理的。 如下&#xff1a; 可以看到上面的预定义符号&#xff0c;其都有两个短下划线&#xff0c;要注意的是&#xff…

关于聊天室数据库建表

首先了解一下外键 ​​一、外键的本质​​ ​​定义​​&#xff1a;外键是某个表中的字段&#xff08;或字段组合&#xff09;&#xff0c;其值必须与另一张表的主键值相匹配。 ​​核心作用​​&#xff1a;强制数据一致性&#xff0c;维护表间关系。 二、外键的核心用途…

基于 OpenHarmony 5.0 的星闪轻量型设备应用开发-Ch1 开发环境搭建

写在前面&#xff1a; 文本所写的工程创建均是基于 HH-SPARK-WS63 星闪无线模组。 此篇是系列文章《基于 OpenHarmony5.0 的星闪轻量型设备应用开发》的第 1 章。 1.1 介绍 HH-SPARK-WS63 星闪无线模组&#xff08;以下简称 WS63&#xff09;是由润和软件推出的基于海思 WS63V…

离线安装 nvidia-docker2(nvidia-container-toolkit)

很多时候大家都有用docker使用gpu的需求&#xff0c;但是因为网络等原因不是那么好用&#xff0c;这里留了一个给ubuntu的安装包&#xff0c;网络好的话也提供了在线安装方式 安装 nvidia-docker2 1 离线安装 &#xff08;推荐&#xff09; unzip解压后进入目录 dpkg -i *.d…

第7篇:Linux程序访问控制FPGA端LEDR<五>

Q&#xff1a;如何设计.c程序代码实现FPGA端外设LEDR流水灯&#xff1f; A&#xff1a;在DE1-SoC开发板上实现的流水灯效果&#xff1a;一次只点亮一个红色LED&#xff0c;初始状态为向左移动直至点亮LEDR9&#xff0c;然后改变移动的方向为向右直至点亮LEDR0&#xff0c;以此…

Unity 实现伤害跳字

核心组件&#xff1a; Dotween TextMeshPro 过程轨迹如下图&#xff1a; 代码如下&#xff1a; using System.Collections; using System.Collections.Generic; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.Pool;public class …

008二分答案+贪心判断——算法备赛

二分答案贪心判断 有些问题&#xff0c;从已知信息推出答案&#xff0c;细节太多&#xff0c;过程繁杂&#xff0c;不易解答。 从猜答案出发&#xff0c;贪心地判断该答案是否合法是个不错的思路&#xff0c;这要求所有可能的答案是单调的&#xff08;例&#xff1a;x满足条件…

衣橱管理助手系统(衣服推荐系统)(springboot+ssm+vue+mysql)含运行文档

衣橱管理助手系统(衣服推荐系统)(springbootssmvuemysql)含运行文档 该系统名为衣橱管理助手&#xff0c;是一个衣物搭配管理系统&#xff0c;主要功能包括衣物档案管理、衣物搭配推荐、搭配收藏以及套装智能推荐。用户可以通过系统进行衣物的搭配和收藏管理&#xff0c;系统提…

文件上传做题记录

1&#xff0c;[SWPUCTF 2021 新生赛]easyupload2.0 直接上传php 再试一下phtml 用蚁剑连发现连不上 那就只要命令执行了 2&#xff0c;[SWPUCTF 2021 新生赛]easyupload1.0 当然&#xff0c;直接上传一个php是不行的 phtml也不行&#xff0c;看下是不是前端验证&#xff0c;…

Vue环境搭建:vue+idea

目录 第一章、Vue环境搭建&#xff1a;安装node2.1&#xff09;node的下载2.2&#xff09;配置node的环境变量2.3&#xff09;常见的npm命令 第二章、使用idea创建vue工程2.1&#xff09;在IDEA中设置国内镜像2.2&#xff09;在IDEA中进行脚手架安装2.3&#xff09;在IDEA中创建…

银河麒麟v10(arm架构)部署Embedding模型bge-m3【简单版本】

硬件 服务器配置&#xff1a;鲲鹏2 * 920&#xff08;32c&#xff09; 4 * Atlas300I duo卡 参考文章 https://www.hiascend.com/developer/ascendhub/detail/07a016975cc341f3a5ae131f2b52399d 鲲鹏昇腾Atlas300Iduo部署Embedding模型和Rerank模型并连接Dify&#xff08;自…

轻量级碎片化笔记memos本地NAS部署与跨平台跨网络同步笔记实战

文章目录 前言1. 使用Docker部署memos2. 注册账号与简单操作演示3. 安装cpolar内网穿透4. 创建公网地址5. 创建固定公网地址 推荐 ​ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站 前言…

【C++算法】54.链表_合并 K 个升序链表

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a; 题目链接&#xff1a; 23. 合并 K 个升序链表 题目描述&#xff1a; 解法 解法一&#xff1a;暴力解法 每个链表的平均长度为n&#xff0c;有k个链表&#xff0c;时间复杂度O(nk^2) 合并两个有序…

EG8200Mini-104边缘计算网关!聚焦IEC104协议的工业数据转换与远程运维平台

在工业自动化和信息化融合不断深化的背景下&#xff0c;现场设备的数据采集与协议转换能力对系统集成效率与运维成本产生着直接影响。EG8200Mini-104边缘计算网关正是基于此需求场景设计&#xff0c;具备IEC104主从站双向支持能力&#xff0c;并配套远程运维与多网络接入方案&a…

python多线程+异步编程让你的程序运行更快

多线程简介 多线程是Python中实现并发编程的重要方式之一&#xff0c;它允许程序在同一时间内执行多个任务。在某些环境中使用多线程可以加快我们代码的执行速度&#xff0c;例如我们通过爬虫获得了一个图片的url数组&#xff0c;但是如果我们一个一个存储很明显会非常缓慢&…

各种场景的ARP攻击描述笔记(超详细)

1、ARP报文限速 上一章我们说过ARP报文也是需要上送CPU进行处理的协议报文,如果设备对收到的大量ARP报文全部进行处理,可能导致CPU负荷过重而无法处理其他业务。因此,在处理之前需要对ARP报文进行限速,以保护CPU资源。 1.根据源MAC地址或源IP地址进行ARP限速 当设备检测到某一…

庙算兵推:使用Streamlit框架构建了一个智能作战推演系统。

这段代码是一个完整的军事模拟应用&#xff0c;使用Streamlit框架构建了一个智能作战推演系统。该系统包括了三维地图显示、作战单位管理、应急事件处理等功能。用户可以通过界面控制推演的开始和暂停&#xff0c;调整时间加速倍率&#xff0c;并查看实时的战斗情况和系统状态。…