自定义Unity组件——AudioManager(音频管理器)

需求描述

        在游戏开发中,音频资源是不可或缺的,通常情况下音频资源随机分布,各个音频的操作和管理都是各自负责,同时对于音频的很多操作逻辑都是大同小异的,这就造成了许多冗余代码的堆叠,除此之外在获取各类音频资源的时候也会造成不必要的开销。所以解决资源分散的问题最直接的方式就是集中管理和分配,通过统一的渠道和特有标识即可获取或操作对应的音频资源。所以本篇文章将围绕这个方案进行尝试。

功能描述

        在Unity中我们导入的音频资源都会转换为AudioClip,音频的设置和管理则由AudioSource负责,AudioListener负责监听音频。我们可以在此基础上去封装,从而打造一个音频管理器。

        音频管理器负责管理音频信息以及操作音频,比如音频信息的增加和删除,音频的播放和暂停等;

        音频信息可以使用一个单独的实体类来记录,用于记录AudioSource组件中的信息,之所以单独用一个实体类来记录音频信息而不直接采用AudioSource,主要是可以通过业务需求去动态调整所要记录的音频信息,我们的实际开发中并非需要AudioSource中所有的信息,有时候仅仅需要其中的一部分,同时音频信息可能涉及存储,直接采用AudioSource可能无法与已经开发好的存储系统相互兼容,而实体类可以为其添加接口或继承来兼容存储系统;

        AudioSource组件的管理可以通过一个组件池进行管理,我们知道AudioSource也是等同于一个音频实体类,但是其同时也是一种组件,组件同样作为一种资源,通常情况下相比简单的实体类而言会带来更大的开销,例如一个场景中有十个游戏对象有播放音频的需求,那么按照传统情况就需要每个游戏对象挂载一个AudioSource组件,但是实际运行中十个游戏对象并不一定需要同时播放音频,它们或许都存在各自触发音频播放的条件,我们只需要在游戏对象需要播放音频时为其分配一个AudioSource组件即可,组件池则负责维护AudioSource组件的生产、获取和归还,进而减少资源开销。

        本质上,AudioSource组件承担的工作就是记录音频信息和操作音频,我们现在将记录工作分担给音频信息实体类,而操作音频的工作则分担给音频管理器,例如音频管理器的播放依旧是调用AudioSource的播放方法,在播放之前由音频管理器去获取AudioSource组件并且为之配置音频信息,其它的音频操作逻辑同理。

代码展示(C#)

AudioManager.cs

using System.Linq;
using System;
using System.Collections.Generic;
using UnityEngine;namespace Tools.AudioManagerAPI
{/// <summary>/// 音频管理器/// </summary>[DisallowMultipleComponent]public class AudioManager : MonoBehaviour{[Header("必要属性")][Tooltip("音频总音量,默认为1(值属于[0,1]),该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值")][Range(0, 1), SerializeField] private float TotalVolume = 1;/// <summary>/// 音频总音量,默认为1(值属于[0,1])/// <para>声明:该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值</para>/// </summary>public float mTotalVolume{get { return TotalVolume; }set{if (value >= 0 && value <= 1 && TotalVolume != value){TotalVolume = value;mTotalVolumeChangedEvents?.Invoke(value);}}}/// <summary>/// 是否启用音频信息覆盖,默认为true/// </summary>public bool mIsOverWrite { get => isOverWrite; set => isOverWrite = value; }/// <summary>/// 音频管理器中所存储的音频数量/// </summary>public int mCount { get => audioInfos.Count; }/// <summary>/// 总音量更改事件回调/// </summary>public event Action<float> mTotalVolumeChangedEvents;/// <summary>/// 音频信息名称合集/// </summary>public string[] mAudioInfoNames { get => audioInfos.Keys.ToArray(); }private Dictionary<string, AudioInfo> audioInfos;//音频信息集合private bool isInit;//是否完成了初始化private bool isOverWrite;//是否启用音频信息覆盖private static AudioSourcePool audioSourcePool = AudioSourcePool.GetInstance();//AudioSource组件池/// <summary>/// 播放指定名称的音频/// <para>p_audioName:音频名称</para>/// </summary>public void Play(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;ai.Play();}}}/// <summary>/// 播放指定名称的音频并开启立体声过渡/// <para>p_audioName:音频名称</para>/// <para>声明:该方法要求已启用立体声过渡且已设置好立体声过渡的相关属性</para>/// </summary>public void PlayWithStereoTransition(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;StartCoroutine(ai.mStereoPanTransitionCoroutine);ai.Play();}}}/// <summary>/// 播放指定名称的音频并开启立体声过渡/// <para>p_audioName:音频名称</para>/// <para>p_stereoTransitionValues:立体声过渡值集合</para>/// <para>p_stereoTimeSpan:立体声过渡每帧时间间隔</para>/// </summary>public void PlayWithStereoTransition(string p_audioName, float[] p_stereoTransitionValues, float p_stereoTimeSpan){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;ai.mStereoTransition = true;ai.mStereoTransitionValues = p_stereoTransitionValues;ai.mStereoTransitionTimeSpan = p_stereoTimeSpan;StartCoroutine(ai.mStereoPanTransitionCoroutine);ai.Play();}}}/// <summary>/// 暂停播放指定名称的音频/// <para>p_audioName:音频名称</para>/// </summary>public void Pause(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];StopCoroutine(ai.mStereoPanTransitionCoroutine);ai.Pause();audioSourcePool.Return(ai.mAudioSource);}}}/// <summary>/// 添加音频信息/// <para>p_audioInfo:音频信息</para>/// <para>声明1:若启用了音频信息覆盖,当存在相同名称的音频时,新的音频信息将覆盖旧的音频信息</para>/// <para>声明2:默认启用了音频信息覆盖,可通过mIsOverWrite属性设置禁用</para>/// </summary>public void AddAudioInfo(AudioInfo p_audioInfo){if (isInit) DoAddAudioInfo(p_audioInfo);}/// <summary>/// 删除音频信息/// <para>p_audioName:音频名称</para>/// <para>返回值:若删除成功则返回true,否则返回false</para>/// </summary>public bool DeleteAudioInfo(string p_audioName){if (isInit) return DoDeleteAudioInfo(p_audioName);return false;}private void Awake(){isInit = false;if (InitParameters()) isInit = true;}//添加音频信息的执行逻辑private void DoAddAudioInfo(AudioInfo p_audioInfo){if (p_audioInfo != null && !String.IsNullOrEmpty(p_audioInfo.mAudioName)){string v_audioName = p_audioInfo.mAudioName;p_audioInfo.BindAudioManager(this);if (isOverWrite) audioInfos[v_audioName] = p_audioInfo;else if (!audioInfos.ContainsKey(v_audioName)) audioInfos.Add(v_audioName, p_audioInfo);}}//删除音频信息的执行逻辑private bool DoDeleteAudioInfo(string p_audioName){if (!String.IsNullOrEmpty(p_audioName) && audioInfos.ContainsKey(p_audioName)){audioInfos[p_audioName].BindAudioManager(null);return audioInfos.Remove(p_audioName);}return false;}//对音频管理器相关参数进行初始化private bool InitParameters(){audioInfos = new Dictionary<string, AudioInfo>();isOverWrite = true;audioSourcePool.BindAudioManager(this);
#if UNITY_EDITORforeach (AudioInfo ai in AudioInfos)DoAddAudioInfo(ai);
#endifreturn true;}#if UNITY_EDITOR/// <summary>/// 在当前Inspector面板中的AudioInfos中的元素数量/// </summary>public int mAudioInfoCount { get => AudioInfos?.Length > 0 ? AudioInfos.Length : 0; }[SerializeField] private AudioInfo[] AudioInfos;//存储Inspector面板中/// <summary>/// 在当前Inspector面板中的AudioInfos中添加一个元素/// </summary>public void Add(){AudioInfo v_audioInfo = new AudioInfo();if (AudioInfos?.Length > 0){AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length + 1];AudioInfos.CopyTo(v_audioInfos, 0);v_audioInfos[v_audioInfos.Length - 1] = v_audioInfo;AudioInfos = new AudioInfo[v_audioInfos.Length];v_audioInfos.CopyTo(AudioInfos, 0);}else AudioInfos = new AudioInfo[] { v_audioInfo };v_audioInfo.ValidateCheck();}/// <summary>/// 在当前Inspector面板中的AudioInfos中删除一个元素/// </summary>public void Delete(int p_index){if (AudioInfos?.Length == 1) AudioInfos = Array.Empty<AudioInfo>();else if (AudioInfos?.Length > 1){AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length - 1];int v_index = 0;for (int i = 0; i < AudioInfos.Length; i++){if (i != p_index) v_audioInfos[v_index++] = AudioInfos[i];}AudioInfos = new AudioInfo[v_audioInfos.Length];v_audioInfos.CopyTo(AudioInfos, 0);}}private void OnValidate(){foreach (AudioInfo audioInfo in AudioInfos){audioInfo?.ValidateCheck();}}
#endif}
}

AudioInfo.cs

using UnityEngine;
using System.Collections;
using System.Linq;
using System;namespace Tools.AudioManagerAPI
{/// <summary>/// 音频信息/// </summary>[System.Serializable]public class AudioInfo{[Header("必要组件")][Tooltip("AudioClip组件"), SerializeField] private AudioClip TheAudioClip;[Header("必要属性")][Tooltip("音频名称"), SerializeField] private string AudioName;[Tooltip("音频音量,默认为1(值属于[0,1])"), Range(0, 1), SerializeField] private float Volume = 1;[Tooltip("音频播放速度,默认为1(值属于[-3,3])"), Range(-3, 3), SerializeField] private float Pitch = 1;[Tooltip("立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道"), Range(-1, 1), SerializeField] private float StereoPan = 0;[Tooltip("音频优先级,默认为128(值属于[0,256])"), Range(0, 256), SerializeField] private int Priority = 128;[Tooltip("是否在场景启动时进行播放,默认为true"), SerializeField] private bool PlayOnAwake = true;[Tooltip("是否循环播放,默认为false"), SerializeField] private bool Loop;[Tooltip("是否忽略总音量影响,默认为false"), SerializeField] private bool IgnoreTotalVolume;[Header("立体声过渡属性")][Tooltip("是否启用立体声过渡,默认为false"), SerializeField] private bool StereoTransition;[Tooltip("立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])"), Range(.1f, 5), SerializeField] private float StereoTransitionTimeSpan = 0.5f;[Tooltip("立体声过渡值集合"), SerializeField] private float[] StereoTransitionValues;/// <summary>/// 音频名称/// </summary>public string mAudioName { get => AudioName; set => AudioName = value; }/// <summary>/// 音频音量,默认为1(值属于[0,1])/// </summary>public float mVolume{get { return Volume; }set { if (value >= 0 && value <= 1) Volume = value; }}/// <summary>/// 音频播放速度,默认为1(值属于[-3,3])/// </summary>public float mPitch{get { return Pitch; }set { if (value >= -3 && value <= 3) Pitch = value; }}/// <summary>/// 立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道/// </summary>public float mStereoPan{get { return StereoPan; }set { if (value >= -1 && value <= 1) StereoPan = value; }}/// <summary>/// 音频优先级,默认为128(值属于[0,256])/// </summary>public int mPriority{get { return Priority; }set { if (value >= 0 && value <= 256) Priority = value; }}/// <summary>/// 是否在场景启动时进行播放,默认为true/// </summary>public bool mPlayOnAwake { get => PlayOnAwake; set => PlayOnAwake = value; }/// <summary>/// 是否循环播放,默认为false/// </summary>public bool mLoop { get => Loop; set => Loop = value; }/// <summary>/// 是否启用立体声过渡,默认为false/// </summary>public bool mStereoTransition { get => StereoTransition; set => StereoTransition = value; }/// <summary>/// 立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])/// </summary>public float mStereoTransitionTimeSpan{get { return StereoTransitionTimeSpan; }set { if (value >= 0.1f && value <= 5) StereoTransitionTimeSpan = value; }}/// <summary>/// 立体声过渡值集合/// </summary>public float[] mStereoTransitionValues{get => StereoTransitionValues;set => StereoTransitionValues = value;}/// <summary>/// AudioSource组件/// </summary>public AudioSource mAudioSource { get => audioSource; set => audioSource = value; }/// <summary>/// 立体声过渡协程/// </summary>public IEnumerator mStereoPanTransitionCoroutine { get => stereoPanTransitionCoroutine; }/// <summary>/// 是否忽略总音量影响,默认为false/// </summary>public bool mIgnoreTotalVolume { get => IgnoreTotalVolume; set => IgnoreTotalVolume = value; }private AudioSource audioSource;//AudioSource组件private AudioManager audioManager;//音频管理器private bool isInit;//是否完成初始化private IEnumerator stereoPanTransitionCoroutine;//立体声过渡协程private float actualVolume;//实际音量private Action<float> totalVolumeChangedEvent;//总音量更改事件对象/// <summary>/// 将指定的AudioSource组件信息记录在新的AudioInfo实例中并返回它/// <para>p_audioSource:指定的AudioSource组件</para>/// <para>返回值:新的AudioInfo实例</para>/// </summary>public static AudioInfo Record(AudioSource p_audioSource){AudioInfo v_audioInfo = new AudioInfo();if (p_audioSource != null){v_audioInfo.TheAudioClip = p_audioSource.clip;v_audioInfo.Volume = p_audioSource.volume;v_audioInfo.Pitch = p_audioSource.pitch;v_audioInfo.StereoPan = p_audioSource.panStereo;v_audioInfo.Priority = p_audioSource.priority;v_audioInfo.PlayOnAwake = p_audioSource.playOnAwake;v_audioInfo.Loop = p_audioSource.loop;v_audioInfo.audioSource = p_audioSource;}return v_audioInfo;}public AudioInfo(){isInit = false;InitToDefault();}/// <summary>/// 播放音频/// </summary>public void Play(){if (audioSource != null && !audioSource.isPlaying){if (!IgnoreTotalVolume){if (actualVolume < 0 || actualVolume > 1) actualVolume = Volume;audioSource.volume = actualVolume;}else actualVolume = -1;audioSource.Play();}}/// <summary>/// 暂停音频播放/// </summary>public void Pause(){if (audioSource != null && audioSource.isPlaying){audioSource.Pause();audioSource.volume = Volume;}}/// <summary>/// 绑定音频管理器/// <para>p_audioManager:音频管理器</para>/// <para>声明1:若有需要可通过该方法将当前的AudioInfo与指定的音频管理器进行绑定</para>/// <para>声明2:绑定后将自动向指定的音频管理器添加当前的AudioInfo</para>/// </summary>public void BindAudioManager(AudioManager p_audioManager){audioManager = p_audioManager;if (audioManager != null){audioManager.mTotalVolumeChangedEvents -= totalVolumeChangedEvent;audioManager.mTotalVolumeChangedEvents += totalVolumeChangedEvent;}}/// <summary>/// 初始化为默认值/// </summary>public void InitToDefault(){if (!isInit){TheAudioClip = null;AudioName = "Audio";Volume = 1;Pitch = 1;StereoPan = 0;StereoTransitionValues = null;StereoTransitionTimeSpan = 0.5f;Priority = 128;StereoTransition = false;PlayOnAwake = true;Loop = false;audioSource = null;stereoPanTransitionCoroutine = StereoPanTransition();totalVolumeChangedEvent = (val) => TotalVolumeChangedEvent(val);actualVolume = -1;}}/// <summary>/// 将当前AudioInfo实例中的信息配置给指定的AudioSource组件/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void ShareTo(AudioSource p_audioSource){if (p_audioSource != null){p_audioSource.clip = TheAudioClip;p_audioSource.volume = Volume;p_audioSource.pitch = Pitch;p_audioSource.panStereo = StereoPan;p_audioSource.priority = Priority;p_audioSource.playOnAwake = PlayOnAwake;p_audioSource.loop = Loop;}}/// <summary>/// 将指定的AudioSource组件信息存储在当前AudioInfo实例中/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void SelfRecord(AudioSource p_audioSource){if (p_audioSource != null){TheAudioClip = p_audioSource.clip;Volume = p_audioSource.volume;Pitch = p_audioSource.pitch;StereoPan = p_audioSource.panStereo;Priority = p_audioSource.priority;PlayOnAwake = p_audioSource.playOnAwake;Loop = p_audioSource.loop;audioSource = p_audioSource;}}// 总音量更改事件// p_totalVolume:总音量// 若不忽略总音量影响,通过调用该事件将基于总音量和当前音量换算实际音量数值// 当TotleVolume为0时,实际音量为0;// 当TotleVolume为1或不属于[0,1)时,实际音量为Volume;// 当TotleVolume属于(0,1)时,实际音量为Volume * TotalVolumeprivate void TotalVolumeChangedEvent(float p_totalVolume){if (!IgnoreTotalVolume){if (p_totalVolume == 0) actualVolume = 0;else if (p_totalVolume > 0 && p_totalVolume < 1) actualVolume = Volume * p_totalVolume;else actualVolume = Volume;//运行时修改AudioSource音量if (audioSource != null) audioSource.volume = actualVolume;}else actualVolume = -1;}//立体声过渡协程private IEnumerator StereoPanTransition(){int currentIndex = 0;while (true){if (audioSource == null || !StereoTransition || StereoTransitionValues == null || StereoTransitionValues.Length == 0)yield break;audioSource.panStereo = StereoTransitionValues[currentIndex];yield return new WaitForSeconds(StereoTransitionTimeSpan);currentIndex = (currentIndex + 1) % StereoTransitionValues.Length;if (currentIndex == 0) StereoTransitionValues = StereoTransitionValues.Reverse().ToArray<float>();}}#if UNITY_EDITOR[NonSerialized] private bool isAudioClipLog;private bool isAudioNameLog;/// <summary>/// Inspector面板的数据更改检测/// </summary>public void ValidateCheck(){AudioClipCheck();AudioNameCheck();}//AudioClip检测private void AudioClipCheck(){if (TheAudioClip == null){if (!isAudioClipLog){Debug.LogWarning("Component: <b><color=orange>TheAudioClip</color></b> is null.");isAudioClipLog = true;}}else isAudioClipLog = false;}//AudioName检测private void AudioNameCheck(){if (String.IsNullOrEmpty(AudioName)){if (!isAudioNameLog){Debug.LogWarning("Property: <b><color=orange>AudioName</color></b> is empty.");isAudioNameLog = true;}}else isAudioNameLog = false;}
#endif}
}

AudioSourcePool.cs 

using System.Collections.Generic;
using UnityEngine;namespace Tools.AudioManagerAPI
{/// <summary>/// AudioSource组件池/// </summary>public class AudioSourcePool{/// <summary>/// 空闲的AudioSource数量/// </summary>public int mFreeCount { get => audioSources.Count; }private Stack<AudioSource> audioSources;//AudioSource组件集合private AudioManager audioManager;//AudioManager组件private AudioInfo defaultAudioInfo;//默认的AudioInfo/// <summary>/// 获取实例(单例模式)/// </summary>public static AudioSourcePool GetInstance(){return Handler.instance;}/// <summary>/// 绑定音频管理器/// <para>p_audioManager:音频管理器</para>/// </summary>public void BindAudioManager(AudioManager p_audioManager){if (p_audioManager != null) audioManager = p_audioManager;}/// <summary>/// 获取AudioSource组件/// <para>返回值:AudioSource组件</para>/// </summary>public AudioSource Get(){return DoGet();}/// <summary>/// 获取AudioSource组件并按照指定的AudioInfo为之配置属性/// <para>p_audioInfo:指定的AudioInfo</para>/// <para>返回值:AudioSource组件</para>/// </summary>public AudioSource Get(AudioInfo p_audioInfo){AudioSource v_audioSource = DoGet();p_audioInfo?.ShareTo(v_audioSource);return v_audioSource;}/// <summary>/// 归还指定的AudioSource组件/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void Return(AudioSource p_audioSource){if (p_audioSource != null){CleanAudioSource(p_audioSource);audioSources.Push(p_audioSource);}}class Handler{public static AudioSourcePool instance = new AudioSourcePool();}private AudioSourcePool(){audioSources = new Stack<AudioSource>();defaultAudioInfo = new AudioInfo();}//获取AudioSource组件的执行逻辑private AudioSource DoGet(){if (audioManager == null) return null;AudioSource v_audioSource = null;while (v_audioSource == null){if (audioSources.Count == 0) GenerateAudioSource();v_audioSource = audioSources.Pop();}return v_audioSource;}//生成AudioSource组件private void GenerateAudioSource(){if (audioManager?.gameObject != null){AudioSource v_audioSource = audioManager.gameObject.AddComponent<AudioSource>();audioSources.Push(v_audioSource);}}//清洗AudioSource组件private void CleanAudioSource(AudioSource p_audioSource){defaultAudioInfo.ShareTo(p_audioSource);}}
}

NumberRange.cs

using System.Collections.Generic;
using System.Linq;namespace Tools.AudioManagerAPI
{/// <summary>/// 数值范围数组工具类/// </summary>public static class NumberRange{/// <summary>/// 获取指定范围内指定步长的Float数值数组/// <para>p_start:起始值</para>/// <para>p_end:终点值</para>/// <para>p_step:步长值</para>/// <para>[ContainsEnd]:是否包括终点值,默认为false</para>/// <para>返回值:Float[]</para>/// </summary>public static float[] FloatRange(float p_start, float p_end, float p_step, bool ContainsEnd = false){if (!ContainsEnd) return DoFloatRange(p_start, p_end, p_step).ToArray();else{List<float> result = DoFloatRange(p_start, p_end, p_step).ToList();result.Add(p_end);return result.ToArray();}}//获取指定范围内指定步长的Float数值数组的执行逻辑static IEnumerable<float> DoFloatRange(float p_start, float p_end, float p_step){for (float i = p_start; i <= p_end; i += p_step){yield return i;}}}
}

界面展示

演示效果

自定义Unity组件AudioManager

资源下载

GitHub_AudioManager    百度网盘

如果这篇文章对你有帮助,请给作者点个赞吧! 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Axure RP9 引入eCharts图表

一、 ECharts 地址&#xff1a;https://echarts.apache.org/zh/index.html 概述&#xff1a;一个基于 JavaScript 的开源可视化图表库 提供了很多图标样式案例 二、 Axure引入eCharts图表步骤 步骤一&#xff1a;打开Axure&#xff0c;添加矩形元素&#xff0c;调整矩形所…

WorkPlus私有化部署IM即时通讯平台,构建高效安全的局域网办公环境

随着数字化转型的加速&#xff0c;政府机构与企业对高效、安全的即时通讯和协作工具的需求日益增长。企业微信和钉钉作为当前市场上较为常见的通讯工具&#xff0c;虽然在一定程度上满足了企业内部协作的需求&#xff0c;但仍存在一些问题&#xff0c;如数据安全性、私有化部署…

静态路由+BFD实例

项目拓扑与项目需求 项目需求 ① 主链路为电信&#xff0c;电信链路出故障时&#xff0c;业务数据流量切换到联通链路 实验步骤 步骤1&#xff1a;设备重命名以及IP地址的配置 设备 接口编号 IP地址 AR1 G0/0/0 10.0.13.1/24 G0/0/1 10.0.14.1/24 AR2 G0/0/0 10.0…

IntelliJ IDEA快速查询maven依赖关系

1.在Maven窗口中点击Dependencies->show Dependencies 2.得到依赖关系图 此时原有快捷键Ctrlf可以查询jar包&#xff0c;如果没有查询菜单出来则设置快捷键方式为 File->Settings->Keymap->搜索栏输入find->在Main Menu下Edit下Find下Find双击算则Add keyboard…

项目任务管理上的一些总结

1. 开发任务管理现状&#xff1a; 1&#xff1a;基于禅道进行任务派发&#xff0c;缺少任务统计&#xff0c;进度上只能以“来不及”、“进度正常”、“进度延后”等模糊字眼。 2&#xff1a;“感觉”工作效率不高了&#xff0c;工作量是否饱和&#xff0c;任务投入产出偏差多…

vue3中$refs使用调整

前言&#xff1a; vue3环境 在vue2环境中&#xff0c;可以直接通过this.$refs获取模块&#xff1b;在vue3环境中&#xff0c;通用以下两种方式获取&#xff1a; 1、通过声明ref进行获取&#xff1b; import { ref} from vue; const logoForm ref(); console.log(logoForm.va…

NSDT孪生场景编辑器系统介绍

一、产品背景 数字孪生的建设流程涉及建模、美术、程序、仿真等多种人才的协同作业&#xff0c;人力要求高&#xff0c;实施成本高&#xff0c;建设周期长。如何让小型团队甚至一个人就可以完成数字孪生的开发&#xff0c;是数字孪生工具链要解决的重要问题。考虑到数字孪生复杂…

go语言unsafe.Pointer与uintptr

以下内容来源go语言圣经 1、unsafe.Pointer&#xff0c;相当于c语言中的void *类型的指针&#xff0c;如果需要运算需要转成uintptr类型的指针 2. uintptr uintptr是一个无符号的整型&#xff0c;它可以保存一个指针地址。 它可以进行指针运算。 uintptr无法持有对象, GC不把…

急救车工业路由器应用提升急救效率:车联网、数据采集与远程诊疗

急救车作为医院里医疗急救过程中的重要组成部分&#xff0c;在智慧医疗物联网领域中急救车应用4G工业路由器实现网络部署与数据采集&#xff0c;通过工业4G路由器能够实时采集到病患的生理数据、救护现场音频与视频、GPS定位以及车辆运行状态等重要信息。这些数据将被传输到医疗…

分布式任务调度平台——XXL-JOB

目录 1.概述1.1.什么是任务调度&#xff1f;1.2.为什么需要分布式调度1.3 XXL-JOB 介绍1.3.1.概述1.3.2.✨系统架构图1.3.3.✨设计思想1.3.4.✨执行流程 2.快速入门2.1.下载源码2.2.初始化调度数据库2.3.编译源码2.4.配置部署调度中心2.4.1.调度中心配置2.4.2.部署项目 2.5.配置…

Java 并发编程面试题——BlockingQueue

目录 1.什么是阻塞队列 (BlockingQueue)&#xff1f;2.BlockingQueue 有哪些核心方法&#xff1f;3.BlockingQueue 有哪些常用的实现类&#xff1f;3.1.ArrayBlockingQueue3.2.DelayQueue3.3.LinkedBlockingQueue3.4.PriorityBlockingQueue3.5.SynchronousQueue 4.✨BlockingQu…

java项目值水果销售管理网站(ssm源码+文档)

项目简介 水果销售管理网站实现了以下功能&#xff1a; 管理员&#xff1a;主页、个人中心、用户管理、水果信息管理、水果类别管理、销售单位管理、水果销售分析管理、会员分析管理、系统管理、订单管理。用户前台&#xff1a;首页、水果信息、新闻资讯、我的、跳转到后台、…

我用PYQT5做的第一个实用的上位机项目(二)

从这篇开始&#xff0c;复盘整个的过程&#xff0c;做一个记录。 首先&#xff0c;制作一些自定义的常用部件&#xff0c;原生的部件很粗糙。 一、按钮的图片资源&#xff1a;用绘图软件&#xff08;例如AI、coreldraw、PS等&#xff0c;看自己的熟悉程度&#xff09;制作按钮…

2009-2018年各省涉农贷款数据(wind)

2009-2018年各省涉农贷款数据&#xff08;wind&#xff09; 1、时间&#xff1a;:209-2018年 2、范围&#xff1a;31省 3、来源&#xff1a;wind 4、指标&#xff1a;涉农贷款 指标解释 &#xff1a;在涉农贷款的分类上&#xff0c;按照城乡地域将涉农贷款分为农村贷款和城…

新手程序员怎么接单?

程序员如何在自己年富力强的时候&#xff0c;最大化发挥自己的能力&#xff1f;将超能力转化为“钞能力”&#xff1f; 有人还在苦哈哈当老黄牛&#xff0c;一身使不完的牛劲&#xff0c;有人已经另辟蹊径&#xff0c;开创了自己的一片致富小天地。 接单找兼职&#xff0c;就…

springcloud之微服务简介

写在前面 本文看下微服务的基础内容&#xff0c;并对springcloud做一个简单的介绍。 1&#xff1a;为什么需要微服务 记得工作的前五六年&#xff0c;项目基本上都是一个大的单体应用&#xff0c;大家都是在同一个分支开发以及提交代码&#xff0c;如下图是之前一个单体应用…

【小余送书第一期】《数据要素安全流通》参与活动,即有机会中奖哦!!

目录 1、背景介绍 2、本书编撰背景 3、本书亮点 4、本书主要内容 5、活动须知 1、背景介绍 随着大数据、云计算、人工智能等新兴技术的迅猛发展&#xff0c;数据已经成为我国经济社会发展的五大生产要素之一&#xff0c;《网络安全法》《个人信息保护法》《数据安全法》的…

Jmeter+Ant+Git+Jenkins持续集成介绍

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 一 简介 1.什么是ant? ant是构建工具 2.什么是构建 概念到处可查到&#xff0c;形象来说&#xff…

【NLP的Python库(04/4)】:Flair

一、说明 Flair是一个现代的NLP库。从文本处理到文档语义&#xff0c;支持所有核心 NLP 任务。Flair使用现代转换器神经网络模型来完成多项任务&#xff0c;并结合了其他Python库&#xff0c;可以选择特定的模型。其清晰的API和注释文本的数据结构&#xff0c;以及多语言支持&a…

[C]精炼分析状态机FSM

FSM&#xff1a;finite state machine 【有限状态机】&#xff0c;用通俗的语言来表达就是逻辑流程图。 当前状态满足触发条件时&#xff0c;就会切换到下一个状态&#xff0c;并执行对应的任务操作。传统代码做法是用if-else 或者 switch-case来处理。若要做到可扩展性良好的…