【unity进阶知识3】封装一个事件管理系统

前言

框架的事件系统主要负责高效的方法调用与数据传递,实现各功能之间的解耦,通常在调用某个实例的方法时,必须先获得这个实例的引用或者新实例化一个对象,低耦合度的框架结构希望程序本身不去关注被调用的方法所依托的实例对象是否存在,通过事件系统做中转将功能的调用封装成事件,使用事件监听注册、移除和事件触发完成模块间的功能调用管理。常用在UI事件、跨模块事件上。

一、作用

访问其它脚本时,不直接访问,而是通过发送一条类似“命令”,让监听了这条“命令”的脚本自动执行对应的逻辑。

二、原理

1、让脚本向事件中心添加事件,监听对应的“命令”。
2、发送“命令”,事件中心就会通知监听了这条“命令”的脚本,让它们自动执行对应的逻辑。
在这里插入图片描述

三、不使用事件管理器

在这里插入图片描述

新增3个测试脚本

public class Player : MonoBehaviour {public void Log(){Debug.Log("我是玩家");}
}
public class Player1 : MonoBehaviour {public void Log(){Debug.Log("我是玩家1");}
}
public class Player2 : MonoBehaviour {public void Log(){Debug.Log("我是玩家2");}
}

调用各个脚本的log方法

public class EventManagerTest: MonoBehaviour
{private void Start(){GameObject go = GameObject.Find("Player");go.GetComponent<Player>().Log();    GameObject go1 = GameObject.Find("Player1");go1.GetComponent<Player1>().Log();GameObject go2 = GameObject.Find("Player2");go2.GetComponent<Player2>().Log();}
}

效果
在这里插入图片描述

四、使用事件管理器

1、事件管理器

新增EventManager,事件管理器

/// <summary>
/// 事件管理器
/// </summary>
public class EventManager : Singleton<EventManager>
{Dictionary<string, UnityAction> eventsDictionary = new Dictionary<string, UnityAction>();/// <summary>/// 事件监听/// </summary>/// <param name="eventName">事件名称</param>/// <param name="action">监听方法</param>public void AddEventListener(string eventName, UnityAction action){if (eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] += action;}else{eventsDictionary.Add(eventName, action);}}/// <summary>/// 触发事件/// </summary>/// <param name="eventName">事件名称</param>public void Dispatch(string eventName){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName]?.Invoke();}}
}

2、添加事件监听

在这里插入图片描述

分别在Player、Player1、Player2新增如下代码,添加事件监听

private void Start() {EventManager.Instance.AddEventListener("打印日志", Log);    
}

3、触发事件

在这里插入图片描述

在EventManagerTest中触发事件

public class EventManagerTest : MonoBehaviour
{private void Start(){// GameObject go = GameObject.Find("Player");// go.GetComponent<Player>().Log();    // GameObject go1 = GameObject.Find("Player1");// go1.GetComponent<Player1>().Log();// GameObject go2 = GameObject.Find("Player2");// go2.GetComponent<Player2>().Log();EventManager.Instance.Dispatch("打印日志");}
}

4、结果

在这里插入图片描述

五、移除事件

比如有几个小怪,都添加了事件监听,杀死后会被销毁,如果不把事件移除,直接再次执行命令则会报错:
MissingReferenceException:The object of type 'Capsule'has been destroyed but you are still trying to access it.
在这里插入图片描述
修改EventManager,添加移除事件方法

/// <summary>
/// 移除事件某个监听方法
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="action">监听方法</param>
public void RemoveEventListener(string eventName, UnityAction action){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] -= action;}
}/// <summary>
/// 移除整个事件
/// </summary>
/// <param name="eventName">名称</param>
public void RemoveEvent(string eventName){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] = null;}
}

测试调用

public class EventManagerTest : MonoBehaviour
{ private void OnGUI(){if (GUI.Button(new Rect(0, 0, 150, 50), "触发事件")){EventManager.Instance.Dispatch("打印日志");}if (GUI.Button(new Rect(0, 50, 150, 50), "移除Player事件监听")){GameObject go = GameObject.Find("Player");EventManager.Instance.RemoveEventListener("打印日志", go.GetComponent<Player>().Log); }if (GUI.Button(new Rect(0, 100, 150, 50), "移除整个事件")){EventManager.Instance.RemoveEvent("打印日志");}}
}

效果
在这里插入图片描述

六、自定义枚举事件名称

目前事件名称是字符串,手打容易出错,我们可以选择使用枚举的方式

/// <summary>
/// 事件名称枚举
/// </summary>
public enum EventNameEnum{Log,    //打印AddHealth   //群体回血
}

修改EventManager,新增获取事件名称方法

/// <summary>
/// 获取事件名称
/// </summary>
/// <param name="eventNameEnum">事件枚举</param>
/// <returns>事件名称</returns>
private string GetEnventName(object EventNameEnum){return EventNameEnum.GetType().Name + "_" + EventNameEnum.ToString();
}

修改测试调用

public class EventManagerTest : MonoBehaviour
{ private void OnGUI(){if (GUI.Button(new Rect(0, 0, 150, 50), "触发事件")){EventManager.Instance.Dispatch(EventNameEnum.Log);}if (GUI.Button(new Rect(0, 50, 150, 50), "移除Player事件监听")){GameObject go = GameObject.Find("Player");EventManager.Instance.RemoveEventListener(EventNameEnum.Log, go.GetComponent<Player>().Log); }if (GUI.Button(new Rect(0, 100, 150, 50), "移除整个事件")){EventManager.Instance.RemoveEvent(EventNameEnum.Log);}}
}

结果,和之前一样
在这里插入图片描述

七、传递带有一个参数的事件

如果我们想要传递带有一个参数的事件,可以遵循里氏替换原则(Liskov Substitution Principle),即子类可以替换父类而不会影响程序的正确性。

  • 里氏替换原则
    通过使用 IEventInfo 接口,可以确保 EventInfo<T>EventInfo 类可以在需要 IEventInfo 的上下文中被替换而不影响程序的功能。这使得事件管理器能够处理不同类型的事件回调。

  • 单一职责原则
    每个 EventInfo 类都有自己的职责:EventInfo<T> 处理带参数的回调,而 EventInfo 处理不带参数的回调。这增强了代码的清晰性和可维护性。

这种设计提供了灵活性,使得事件管理系统能够处理多种类型的事件,同时也遵循了面向对象设计的原则。你可以根据需要扩展或修改 IEventInfoEventInfo 类,以支持更多的事件类型和逻辑。

1、接口 IEventInfo

定义一个标记接口 IEventInfo,用于标识事件信息的类型。这样可以在系统中使用多态性,确保遵循里氏替换原则。

public interface IEventInfo { }

2、泛型类 EventInfo

EventInfo 类实现了 IEventInfo 接口。这个类用于处理带有参数的事件回调(UnityAction),允许在事件触发时传递参数。action 字段用于保存事件回调。

private class EventInfo<T> : IEventInfo
{public UnityAction<T> action;public EventInfo(UnityAction<T> call){action += call; // 将传入的回调添加到 action 上}
}

3、非泛型类 EventInfo

另一个 EventInfo 类用于处理没有参数的事件回调(UnityAction)。这种设计使得可以处理不同类型的事件。

private class EventInfo : IEventInfo
{public UnityAction action;public EventInfo(UnityAction call){action += call; // 将传入的回调添加到 action 上}
}

4、修改EventManager

事件名称记得修改一下,不然我们可能很难分出哪个是带传参的,我们可以选择把这个参数的类型的名字也传进去

Dictionary<string, IEventInfo> eventsDictionary = new Dictionary<string, IEventInfo>();/// <summary>
/// 无参数的事件监听
/// </summary>
/// <param name="EventNameEnum">事件枚举</param>
/// <param name="action">监听方法</param>
public void AddEventListener(object EventNameEnum, UnityAction call)
{string eventName = GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action += call;}else{eventsDictionary.Add(eventName, new EventInfo(call));}
}/// <summary>
/// 带1个参数的事件监听
/// </summary>
public void AddEventListener<T>(object EventNameEnum, UnityAction<T> call)
{string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo<T>).action += call;}else{eventsDictionary.Add(eventName, new EventInfo<T>(call));}
}//其他类似

IEventInfo是我们人为制造出来的一个副接口,这样的话就可以成功把有参数的事件和无参数的事件都存到字典里面去了

5、事件监听

Player、Player1、Player2都添加带一个参数的事件监听

public class Player : MonoBehaviour
{private void Start(){EventManager.Instance.AddEventListener(EventNameEnum.Log, Log);EventManager.Instance.AddEventListener<int>(EventNameEnum.AddHealth, AddHealth);}public void Log(){Debug.Log("我是玩家");}public void AddHealth(int health){Debug.Log($"玩家恢复+{health + 1}血");}
}

6、触发事件

测试触发事件

public class EventManagerTest : MonoBehaviour
{private void OnGUI(){if (GUI.Button(new Rect(150, 0, 150, 50), "触发带1个参数事件")){EventManager.Instance.Dispatch<int>(EventNameEnum.AddHealth, 1);}if (GUI.Button(new Rect(150, 50, 150, 50), "移除Player带1个参数事件监听")){GameObject go = GameObject.Find("Player");EventManager.Instance.RemoveEventListener<int>(EventNameEnum.AddHealth, go.GetComponent<Player>().AddHealth); }if (GUI.Button(new Rect(150, 100, 150, 50), "移除整个带1个参数事件")){EventManager.Instance.RemoveEvent<int>(EventNameEnum.AddHealth);}}
}

7、效果

在这里插入图片描述

八、传递带有多个参数的事件

方法一、自定义类

相当于将多个参数合并到一个类里,在传递进去

比如

public class MyInfo
{public int a;public float b;public double c;
}

调用
在这里插入图片描述

方法二、元组

相当于通过元组把多个参数合并,传递进去

方法三、添加带不同数量参数的方法(推荐)

这种办法虽然最麻烦,但是不会有性能问题,可以避免下面的问题

1、GC(垃圾回收)

创建元组或自定义类实例会导致额外的内存分配,从而增加垃圾回收的压力。在高频率调用的场景下,频繁分配和回收内存会导致性能下降,影响游戏的帧率。

2、装箱问题

对于值类型(如 int、struct 等),使用元组或对象时可能会导致装箱和拆箱,增加内存开销和降低性能。这在使用泛型时尤为明显,因为值类型会被包装为对象。

3、开销和复杂性

封装多个参数在一个元组或自定义类中,虽然提高了代码的可读性,但也增加了开销,特别是在事件频繁触发的情况下,开销可能会显著。

九、最终代码

这里我添加最多支持添加4个参数的事件,一般都够了,如果觉得还是不够,可以模仿我的方式继续添加即可

using System.Collections.Generic;
using UnityEngine.Events;/// <summary>
/// 事件管理器,之所以这么多函数,主要是出于性能考虑,避免产生GC、装箱问题
/// </summary>
public class EventManager : Singleton<EventManager>
{Dictionary<string, IEventInfo> eventsDictionary = new Dictionary<string, IEventInfo>();/// <summary>/// 获取事件名称/// </summary>/// <param name="eventNameEnum">事件枚举</param>/// <returns>事件名称</returns>private string GetEnventName(object EventNameEnum){return EventNameEnum.GetType().Name + "_" + EventNameEnum.ToString();}#region 事件监听/// <summary>/// 无参数的事件监听/// </summary>/// <param name="EventNameEnum">事件枚举</param>/// <param name="action">监听方法</param>public void AddEventListener(object EventNameEnum, UnityAction call){string eventName = GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action += call;}else{eventsDictionary.Add(eventName, new EventInfo(call));}}/// <summary>/// 带1个参数的事件监听/// </summary>public void AddEventListener<T>(object EventNameEnum, UnityAction<T> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo<T>).action += call;}else{eventsDictionary.Add(eventName, new EventInfo<T>(call));}}/// <summary>/// 带2个参数的事件监听/// </summary>public void AddEventListener<T0, T1>(object EventNameEnum, UnityAction<T0, T1> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo<T0, T1>).action += call;}else{eventsDictionary.Add(eventName, new EventInfo<T0, T1>(call));}}/// <summary>/// 带3个参数的事件监听/// </summary>public void AddEventListener<T0, T1, T2>(object EventNameEnum, UnityAction<T0, T1, T2> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo<T0, T1, T2>).action += call;}else{eventsDictionary.Add(eventName, new EventInfo<T0, T1, T2>(call));}}/// <summary>/// 带4个参数的事件监听/// </summary>public void AddEventListener<T0, T1, T2, T3>(object EventNameEnum, UnityAction<T0, T1, T2, T3> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name + "_" + typeof(T3).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo<T0, T1, T2, T3>).action += call;}else{eventsDictionary.Add(eventName, new EventInfo<T0, T1, T2, T3>(call));}}#endregion#region 触发事件/// <summary>/// 触发事件/// </summary>/// <param name="EventNameEnum">事件枚举</param>public void Dispatch(object EventNameEnum){string eventName = GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action?.Invoke();}}/// <summary>/// 触发带1个参数事件/// </summary>public void Dispatch<T>(object EventNameEnum, T parameter){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T).Name;//如果字典中该事件的名字存在,且该事件不为空,则执行该事件,不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T>).action?.Invoke(parameter);}/// <summary>/// 触发带2个参数事件/// </summary>public void Dispatch<T0, T1>(object EventNameEnum, T0 parameter0, T1 parameter1){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name;//如果字典中该事件的名字存在,且该事件不为空,则执行该事件,不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1>).action?.Invoke(parameter0, parameter1);}/// <summary>/// 触发带3个参数事件/// </summary>public void Dispatch<T0, T1, T2>(object EventNameEnum, T0 parameter0, T1 parameter1, T2 parameter2){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name;//如果字典中该事件的名字存在,且该事件不为空,则执行该事件,不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2>).action?.Invoke(parameter0, parameter1, parameter2);}/// <summary>/// 触发带4个参数事件/// </summary>public void Dispatch<T0, T1, T2, T3>(object EventNameEnum, T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3){string eventName = GetEnventName(EventNameEnum) +  "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name + "_" + typeof(T3).Name;//如果字典中该事件的名字存在,且该事件不为空,则执行该事件,不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2, T3>).action?.Invoke(parameter0, parameter1, parameter2, parameter3);}#endregion#region 移除事件某个监听方法/// <summary>/// 移除无参数事件某个监听方法/// </summary>/// <param name="EventNameEnum">事件枚举</param>/// <param name="call">监听方法</param>public void RemoveEventListener(object EventNameEnum, UnityAction call){string eventName = GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action -= call;}}/// <summary>/// 移除带1个参数事件某个监听方法/// </summary>public void RemoveEventListener<T>(object EventNameEnum, UnityAction<T> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T>).action -= call;}/// <summary>/// 移除带2个参数事件某个监听方法/// </summary>public void RemoveEventListener<T0, T1>(object EventNameEnum, UnityAction<T0, T1> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1>).action -= call;}/// <summary>/// 移除带3个参数事件某个监听方法/// </summary>public void RemoveEventListener<T0, T1, T2>(object EventNameEnum, UnityAction<T0, T1, T2> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2>).action -= call;}/// <summary>/// 移除带4个参数事件某个监听方法/// </summary>public void RemoveEventListener<T0, T1, T2, T3>(object EventNameEnum, UnityAction<T0, T1, T2, T3> call){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name + "_" + typeof(T3).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2, T3>).action -= call;}#endregion#region 移除整个事件/// <summary>/// 移除整个不带参数的事件/// </summary>/// <param name="EventNameEnum">事件枚举</param>public void RemoveEvent(object EventNameEnum){string eventName = GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action = null;}}/// <summary>/// 移除整个带1个参数的事件/// </summary>public void RemoveEvent<T>(object EventNameEnum){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T).Name;//如果字典中存在要移除的命令,则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T>).action = null;}/// <summary>/// 移除整个带2个参数的事件/// </summary>public void RemoveEvent<T0, T1>(object EventNameEnum){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name;//如果字典中存在要移除的命令,则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1>).action = null;}/// <summary>/// 移除整个带3个参数的事件/// </summary>public void RemoveEvent<T0, T1, T2>(object EventNameEnum){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name;//如果字典中存在要移除的命令,则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2>).action = null;}/// <summary>/// 移除整个带4个参数的事件/// </summary>public void RemoveEvent<T0, T1, T2, T3>(object EventNameEnum){string eventName = GetEnventName(EventNameEnum) + "_" + typeof(T0).Name + "_" + typeof(T1).Name + "_" + typeof(T2).Name + "_" + typeof(T3).Name;//如果字典中存在要移除的命令,则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfo<T0, T1, T2, T3>).action = null;}#endregion/// <summary>/// 移除事件中心的所有事件。可以考虑在切换场景时调用。/// </summary>public void RemoveAllEvent(){eventsDictionary.Clear();}#region 里氏替换原则private interface IEventInfo { }private class EventInfo : IEventInfo{public UnityAction action;public EventInfo(UnityAction call){action += call;}}private class EventInfo<T> : IEventInfo{public UnityAction<T> action;public EventInfo(UnityAction<T> call){action += call;}}private class EventInfo<T0, T1> : IEventInfo{public UnityAction<T0, T1> action;public EventInfo(UnityAction<T0, T1> call){action += call;}}private class EventInfo<T0, T1, T2> : IEventInfo{public UnityAction<T0, T1, T2> action;public EventInfo(UnityAction<T0, T1, T2> call){action += call;}}private class EventInfo<T0, T1, T2, T3> : IEventInfo{public UnityAction<T0, T1, T2, T3> action;public EventInfo(UnityAction<T0, T1, T2, T3> call){action += call;}}#endregion
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

收银系统源码-ERP进销存解决方案

收银系统目前已经成为门店日常经营的必备软件工具&#xff0c;功能一般需涵盖线下门店收银&#xff0c;ERP进销存、线上商城等。一套好的ERP进销存模块也能很大程度帮助门店经营管理门店。 ERP进销存功能涵盖了商品的采购、销售、调拨、盘点、库存管理、资金管理等全链路管理&…

对话总结:Scale AI的创始人兼CEO Alex Wang

AI的三大支柱 计算:主要由大公司如NVIDIA推动。算法:顶尖实验室如OpenAI主导。数据:Scale致力于推动数据进展。前沿数据的重要性 与人类智能相比较,前沿数据是AI发展的关键。互联网数据是机器与人类合作的结果。语言模型的发展 第一阶段:原始的Transformer论文和GPT的小规…

初识ZYNQ——FPGA学习笔记15

一、ZYNQ简介 ZYNQ&#xff1a;Zynq-7000 All Programmable SoC&#xff08;APSoC&#xff09;&#xff0c;赛灵思公司&#xff08;AMD Xilinx&#xff09;推出的新一代全可编程片上系统 PS&#xff1a;Processing System&#xff0c;处理系统 PL&#xff1a;Program Logic&…

html TAB切换按钮变色、自动生成table--使用函数优化结构

<!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 1px solid #ccc; padding: 1px; marg…

RTA-OS Port Guide学习(三)-基于S32K324 OS

文章目录 前言HardwareSupported DevicesRegister UsageInitializationModificationRequired OS resourcesInterruptsInterrupt Priority LevelsAllocation of ISRs to Interrupt VectorsVector TableWriting Category 1 Interrupt HandlersWriting Category 2 Interrupt Handl…

Qualitor processVariavel.php 未授权命令注入漏洞复现(CVE-2023-47253)

0x01 漏洞描述&#xff1a; Qualitor 8.20及之前版本存在命令注入漏洞&#xff0c;远程攻击者可利用该漏洞通过PHP代码执行任意代码&#xff0c;利用难度较低危害较大。 0x02 影响版本&#xff1a; Qualitor < 8.20 0x03 搜索语句&#xff1a; Fofa:app"Qualitor-…

服务运营 | 运营前沿:生成式AI改变医疗保健的运作方式

编者按 人工智能正在重塑医疗保健的运作方式&#xff0c;减少医生负担并优化病人的就医体验。从解答患者疑问到开发新药&#xff0c;人工智能正在快速革新医疗保健这一行业。编者团队此次将关注《哈佛商业评论》于2023年11月27日发布的文章《GenAI Could Transform How Health …

数字乡村解决方案-3

1. 国家大数据战略与数字乡村 中国第十三个五年规划纲要强调实施国家大数据战略&#xff0c;加快建设数字中国&#xff0c;推进数据资源整合和开放共享&#xff0c;保障数据安全&#xff0c;以大数据助力产业转型升级和提高社会治理的精准性与有效性。 2. 大数据与数字经济 …

【韩顺平Java笔记】第2章:Java概述

按视频的标号来对应小标题&#xff0c;自用学习笔记 文章目录 5. 内容梳理6. 程序举例6.1 什么是程序 7. Java故事7.1 Java诞生小故事7.2 Java技术体系平台 8. Java特性8.1 Java重要特点 9. sublime10. jdk介绍10.1 Java运行机制及运行过程10.1.1 Java虚拟机&#xff08;JVM&a…

无人机飞手入伍当兵技术优势分析

随着现代战争形态的不断演变&#xff0c;无人机技术在军事领域的应用日益广泛&#xff0c;成为提升军队作战能力的重要手段。对于无人机飞手而言&#xff0c;其专业技能和实战经验在入伍当兵后能够转化为显著的技术优势&#xff0c;为国防事业贡献重要力量。以下是从专业技能优…

【学习笔记】TLS/SSL握手之Records

TLS / SSL会话是由记录&#xff08;Records&#xff09;所组成&#xff0c;有4种records HandshakeAlertChange Cipher SpecApplication DataHandshake和Alert Records被分为子类型&#xff08;Subtypes&#xff09;&#xff1a; Handshake&#xff1a;Client HelloHandshake&a…

Miniforge详细安装教程(macOs和Windows)

(注&#xff1a;主要是解决商业应用anaconda收费问题&#xff0c;这是轻量级的代替&#xff0c;个人完全可以使用anaconda和miniconda) Miniforge 是一个轻量级的包管理器&#xff0c;类似于 Anaconda 和 Miniconda。它主要用于安装基于 conda 的 Python 环境&#xff0c;专注于…

Java新手指南:从菜鸟到编程大师的趣味之路-多态

这里是Themberfue 本章讲的是Java三大特性之一的多态&#xff0c;也是最后一个特性 多态概念 既然我们要学习多态&#xff0c;首先得知道这玩意儿到底是个什么东西&#xff1f; 通俗地说&#xff0c;多态其实就是多种形态。具体来说就是去完成某个行为&#xff0c;当不同的对象…

Secret Configmap

应用启动过程中可能需要一些敏感信息&#xff0c;比如访问数据库的用户名&#xff0c;密码或者秘钥&#xff0c;讲这些信息直接保存在容器镜像中显然不合适&#xff0c;kubernetes提供的解决方案就是Secret Secret会以密文的方式存储数据&#xff0c;避免了直接在配置文件中保…

tauri开发配置文件和文件夹访问路径问题

文件夹没权限&#xff1a;Unhandled Promise Rejection: path not allowed on the configured scope: /Users/song/Library/Application Support/com.pakeplus.app/assets/default.png 没有文件夹&#xff0c;需要先创建&#xff1a;Unhandled Promise Rejection: path: /Users…

数据结构与算法——Java实现 24.中缀表达式转后缀

目录 中缀表达式转后缀表达式 引言 思路 代码 正因为我有能力跨越&#xff0c;考验才会降临 —— 24.9.28 中缀表达式转后缀表达式 引言 Java中的编译器会将我们编写代码中的中缀表达式转化为后缀表达式&#xff0c;然后编译好输出程序 思路 遍历中缀表达式&#xff0c;如果遇…

javaweb之会话管理

Cookie&#xff1a; 1. Cookie 的定义 Cookie 是存储在用户浏览器中的小块数据&#xff0c;通常由服务器发送并存储&#xff0c;以便在用户浏览器和服务器之间保持会话状态。每次用户发送请求时&#xff0c;浏览器都会自动附带相应的 Cookie&#xff0c;允许服务器辨识用户。…

JSR 303学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

低代码可视化-UniApp二维码可视化-代码生成器

市面上提供了各种各样的二维码组件&#xff0c;做了一简单的uniapp二维码组件&#xff0c;二维码实现依赖davidshimjs/qrcodejs。 组件特点 跨浏览器支持&#xff1a;利用Canvas元素实现二维码的跨浏览器兼容性&#xff0c;兼容微信小程序、h5、app。 无依赖性&#xff1a;QR…

基于SpringBoot的新冠检测信息管理系统的设计与实现

文未可获取一份本项目的java源码和数据库参考。 国内外在该方向的研究现状及分析 新型冠状病毒肺炎疫情发生以来&#xff0c;中国政府采取积极的防控策略和措施&#xff0c;经过两个多月的不懈努力&#xff0c;有效控制了新发病例的増长&#xff0c;本地传播已经趋于完全控制…