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

【Unity】通用GM QA工具 运行时数值修改 命令行 测试工具

GM工具使用:

 

GM工具通常用于游戏运行时修改数值(加钱/血量)、解锁关卡等,用于快速无死角测试游戏。一个通用型GM工具对于游戏项目是非常实用且必要的,但通用不能向易用妥协,纯命令行GM门槛太高,对QA不友好。

这类运行时命令行工具实现原理很简单,主要是通过给变量或方法添加Attribute标识,然后通过反射获取被标记的变量或方法,命令触发通过反射为变量赋值或Invoke方法。

此类工具免费或付费的已经泛滥了,不推荐浪费时间重复造轮子。

1. 免费开源的Log显示工具,也嵌入了命令行功能。由于GF有更好用的Debuger窗口了,所以没选择它:https://github.com/yasirkula/UnityIngameDebugConsole

2.  Quantum Console, 收费,AssetStore上好评最多,但强行绑定了一个UGUI界面,无解耦。这里我是想直接扩展进GF Debuger窗口,方便使用,因此需要修改插件源码:Quantum Console | Utilities Tools | Unity Asset Store

 感兴趣的话直接AssetStore搜“command console”,免费的也有很多。

我就不浪费时间筛选,直接选择购买好评较多的Quantum Console进行整改。

Quantum Console用法:

Quantum C默认只对继承MonoBehavior的脚本有效,应该是因为需要反射获取所有类型速度太慢,初始化时会卡顿。

对于继承自MonoBehavior的脚本直接通过以下Attribute标记命令即可:

1. 命令行前缀,CommandPrefix("GM."):

相当于给命令行分组,比如把所有命令行标记个前缀叫“GM.”, 那么输入"GM"时所有GM开头的命令都会在列表中显示出来。

[CommandPrefix("GM.玩家.")]
public class PlayerEntity
{
}

2. 把变量或方法作为命令行,Command("命令名字", "命令用法说明"):

[Command("移动速度", "float类型,默认值10")]
private float moveSpeed = 10f;
[Command("添加敌人", "参数int,创建敌人个数")]
internal void AddEnemies(int v)

 对于非MonoBehavior脚本需要手动调用注册命令接口,将该类型添加到需要反射扫描的名单里:

1. QuantumRegistry.RegisterObject()和QuantumRegistry.DeregisterObject()注册或取消注册,然后通过Command("命令名字", "命令描述", MonoTargetType.Registry)添加命令:

public class PlayerDataModel : DataModelBase
{protected override void OnCreate(RefParams userdata){QuantumRegistry.RegisterObject(this);}protected override void OnRelease(){QuantumRegistry.DeregisterObject(this);}[Command("金币", "玩家金币数量", MonoTargetType.Registry)]public int Coins;
}

将Quantum C扩展进GF:

由于GF解耦做得非常好了,我们只需要自定义类实现GameFramework.Debugger.IDebuggerWindow接口就可以写自己的GUI界面和功能了。

1. 扩展Debuger菜单栏,编写GM工具交互界面:

using System.Collections.Generic;
using UnityEngine;
using GameFramework;
using GameFramework.Debugger;
using System;
using Cysharp.Threading.Tasks;
using GM.Utilities;
using System.Threading.Tasks;
using System.Reflection;
using System.Linq;
namespace GM
{public class GMConsoleWindow : IDebuggerWindow{const string LogCommand = "{0}";const string LogSuccess = "<color=#2BD988>{0}</color>";const string LogFailed = "<color=#F22E2E>{0}</color>";const string InputFieldCtrlID = "Input";private int m_MaxLine = 100;private int m_MaxRecordInputHistory = 30;private Queue<GMLogNode> m_LogNodes;private LinkedList<string> m_InputHistoryList;private LinkedListNode<string> m_CurrentHistory = null;string m_InputText;string m_PreInputText;bool m_InputFocused;bool m_InputChanged;Vector2 m_ScrollPosition = Vector2.zero;Vector2 m_FilterScrollPosition = Vector2.zero;SuggestionStack m_CommandsFilter;SuggestorOptions m_FilterOptions;Rect inputRect = default;bool m_LogAppend;bool m_MoveCursorToEnd;GUIStyle m_CommandsFilterBtStyle;private readonly Type m_VoidTaskType = typeof(Task<>).MakeGenericType(Type.GetType("System.Threading.Tasks.VoidTaskResult"));private List<System.Threading.Tasks.Task> m_CurrentTasks;private List<IEnumerator<ICommandAction>> m_CurrentActions;public void Initialize(params object[] args){if (!QuantumConsoleProcessor.TableGenerated){QuantumConsoleProcessor.GenerateCommandTable(true);}m_InputHistoryList = new LinkedList<string>();m_LogNodes = new Queue<GMLogNode>();m_CurrentTasks = new List<System.Threading.Tasks.Task>();m_CurrentActions = new List<IEnumerator<ICommandAction>>();m_CommandsFilter = new SuggestionStack();m_FilterOptions = new SuggestorOptions(){CaseSensitive = false,CollapseOverloads = true,Fuzzy = true,};}public void OnDraw(){if (m_CommandsFilterBtStyle == null){m_CommandsFilterBtStyle = new GUIStyle(GUI.skin.button){alignment = TextAnchor.MiddleLeft};}GUILayout.BeginVertical();{m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, "box");{foreach (var logNode in m_LogNodes){GUILayout.Label(logNode.LogMessage);}GUILayout.EndScrollView();}if (m_LogAppend){m_LogAppend = false;m_ScrollPosition = new Vector2(0, float.MaxValue);}GUILayout.BeginHorizontal();{GUI.enabled = QuantumConsoleProcessor.TableGenerated;GUI.SetNextControlName(InputFieldCtrlID);m_InputText = GUILayout.TextField(m_InputText);if (Event.current.type == EventType.Repaint){inputRect = GUILayoutUtility.GetLastRect();if (m_MoveCursorToEnd){m_MoveCursorToEnd = false;MoveInputCursorToEnd();}}m_InputFocused = (GUI.GetNameOfFocusedControl() == InputFieldCtrlID);m_InputChanged = m_InputText != m_PreInputText;if (m_InputChanged){m_PreInputText = m_InputText;m_CommandsFilter.UpdateStack(m_InputText, m_FilterOptions);}if (GUILayout.Button("Execute", GUILayout.Width(60))){ExecuteCommand(m_InputText);}if (GUILayout.Button("Clear", GUILayout.Width(60))){ClearLogs();}GUILayout.EndHorizontal();}GUILayout.EndVertical();if (m_InputFocused && m_CommandsFilter.TopmostSuggestionSet != null){if (Event.current.type == EventType.Repaint){float maxHeight = GUILayoutUtility.GetLastRect().height - inputRect.height - 5f;inputRect.height = Mathf.Clamp(m_CommandsFilter.TopmostSuggestionSet.Suggestions.Count * 30, maxHeight * 0.5f, maxHeight);inputRect.position -= Vector2.up * (inputRect.height + 5f);}if (m_InputChanged){m_FilterScrollPosition = Vector2.zero;}GUILayout.BeginArea(inputRect);m_FilterScrollPosition = GUILayout.BeginScrollView(m_FilterScrollPosition, "box", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));{GUILayout.BeginVertical(GUILayout.ExpandHeight(true));{foreach (var item in m_CommandsFilter.TopmostSuggestionSet.Suggestions){if (GUILayout.Button(item.FullSignature, m_CommandsFilterBtStyle)){m_MoveCursorToEnd = true;var fragments = m_InputText.Split(' ');if (fragments.Length >= 2){m_InputText = string.Empty;for (int i = 0; i < fragments.Length - 1; i++){m_InputText = Utility.Text.Format("{0}{1}{2}", m_InputText, i == 0 ? string.Empty : " ", fragments[i]);}m_InputText = Utility.Text.Format("{0} {1}", m_InputText, item.PrimarySignature);}else{m_InputText = item.PrimarySignature;}}}GUILayout.EndVertical();}GUILayout.EndScrollView();}GUILayout.EndArea();}}}/// <summary>/// 输入框游标移动到尾部/// </summary>private void MoveInputCursorToEnd(){GUI.FocusControl(InputFieldCtrlID);// 获取当前TextEditorTextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);if (editor != null){editor.cursorIndex = m_InputText.Length;editor.selectIndex = m_InputText.Length;}}public void OnEnter(){QuantumRegistry.RegisterObject<GMConsoleWindow>(this);}public void OnLeave(){QuantumRegistry.DeregisterObject<GMConsoleWindow>(this);}public void OnUpdate(float elapseSeconds, float realElapseSeconds){if (m_InputFocused){if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))ExecuteCommand(m_InputText);if (Input.GetKeyDown(KeyCode.DownArrow)){SelectInputHistory(false);}else if (Input.GetKeyDown(KeyCode.UpArrow)){SelectInputHistory(true);}}TasksUpdate();ActionsUpdate();}public void Shutdown(){}private void SelectInputHistory(bool upOrdown){if (m_InputHistoryList.Count == 0) return;m_MoveCursorToEnd = true;if (upOrdown){if (m_CurrentHistory == null || m_CurrentHistory.Previous == null){m_InputText = m_InputHistoryList.Last.Value;m_CurrentHistory = m_InputHistoryList.Last;return;}m_InputText = m_CurrentHistory.Previous.Value;m_CurrentHistory = m_CurrentHistory.Previous;}else{if (m_CurrentHistory == null || m_CurrentHistory.Next == null){m_InputText = m_InputHistoryList.First.Value;m_CurrentHistory = m_InputHistoryList.First;return;}m_InputText = m_CurrentHistory.Next.Value;m_CurrentHistory = m_CurrentHistory.Next;}}private void AppendLog(GMLogType logType, string logMessage){m_LogNodes.Enqueue(GMLogNode.Create(logType, logMessage));while (m_LogNodes.Count > m_MaxLine){ReferencePool.Release(m_LogNodes.Dequeue());}m_LogAppend = true;}[Command("clear", "清空GM日志", MonoTargetType.Registry)]private void ClearLogs(){m_LogNodes.Clear();m_ScrollPosition = Vector2.zero;}private void ExecuteCommand(string cmd, bool recordHistory = true){if (string.IsNullOrWhiteSpace(cmd)) return;if (recordHistory) RecordInputHistory(cmd);AppendLog(GMLogType.Command, cmd);m_InputText = string.Empty;try{var commandResult = QuantumConsoleProcessor.InvokeCommand(cmd);if (commandResult != null){if (commandResult is IEnumerator<ICommandAction> enumeratorTp){m_CurrentActions.Add(enumeratorTp);ActionsUpdate();}else if (commandResult is IEnumerable<ICommandAction> enumerableTp){m_CurrentActions.Add(enumerableTp.GetEnumerator());ActionsUpdate();}else if (commandResult is UniTask task){m_CurrentTasks.Add(task.AsTask());}else if (commandResult.GetType().Name == "UniTask`1"){var asTaskGenericMethod = typeof(UniTaskExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(item => item.Name == "AsTask" && item.IsGenericMethod);Type uniTaskType = commandResult.GetType();Type genericArgument = uniTaskType.GetGenericArguments()[0];MethodInfo genericMethod = asTaskGenericMethod.MakeGenericMethod(genericArgument);Task taskT = (Task)genericMethod.Invoke(null, new object[] { commandResult });m_CurrentTasks.Add(taskT);}else{var resultType = commandResult.GetType();if (resultType == typeof(string) || resultType.IsPrimitive){AppendLog(GMLogType.Success, commandResult.ToString());}else{AppendLog(GMLogType.Success, Utility.Json.ToJson(commandResult));}}}}catch (System.Reflection.TargetInvocationException e){AppendLog(GMLogType.Failed, e.Message);}catch (Exception e){AppendLog(GMLogType.Failed, e.Message);}}private void RecordInputHistory(string cmd){if (m_InputHistoryList.Count > 0 && m_InputHistoryList.First.Value == cmd) return;m_InputHistoryList.AddFirst(cmd);m_CurrentHistory = m_InputHistoryList.Last;while (m_InputHistoryList.Count > m_MaxRecordInputHistory){m_InputHistoryList.RemoveLast();}}private void TasksUpdate(){for (int i = m_CurrentTasks.Count - 1; i >= 0; i--){if (m_CurrentTasks[i].IsCompleted){if (m_CurrentTasks[i].IsFaulted){foreach (Exception e in m_CurrentTasks[i].Exception.InnerExceptions){AppendLog(GMLogType.Failed, e.Message);}}else{Type taskType = m_CurrentTasks[i].GetType();if (taskType.IsGenericTypeOf(typeof(Task<>)) && !m_VoidTaskType.IsAssignableFrom(taskType)){System.Reflection.PropertyInfo resultProperty = m_CurrentTasks[i].GetType().GetProperty("Result");object result = resultProperty.GetValue(m_CurrentTasks[i]);string log = Utility.Json.ToJson(result);AppendLog(GMLogType.Success, log);}}m_CurrentTasks.RemoveAt(i);}}}private void ActionsUpdate(){for (int i = m_CurrentActions.Count - 1; i >= 0; i--){IEnumerator<ICommandAction> action = m_CurrentActions[i];try{if (action.Execute() != ActionState.Running){m_CurrentActions.RemoveAt(i);}}catch (Exception e){m_CurrentActions.RemoveAt(i);AppendLog(GMLogType.Failed, e.Message);break;}}}private enum GMLogType{Command,Success,Failed}/// <summary>/// 日志记录结点。/// </summary>private sealed class GMLogNode : IReference{private GMLogType m_LogType;private string m_LogMessage;/// <summary>/// 初始化日志记录结点的新实例。/// </summary>public GMLogNode(){m_LogType = GMLogType.Failed;m_LogMessage = null;}/// <summary>/// 获取日志类型。/// </summary>public GMLogType LogType{get{return m_LogType;}}/// <summary>/// 获取日志内容。/// </summary>public string LogMessage{get{return m_LogMessage;}}/// <summary>/// 创建日志记录结点。/// </summary>/// <param name="logType">日志类型。</param>/// <param name="logMessage">日志内容。</param>/// <returns>创建的日志记录结点。</returns>public static GMLogNode Create(GMLogType logType, string logMessage){GMLogNode logNode = ReferencePool.Acquire<GMLogNode>();logNode.m_LogType = logType;switch (logType){case GMLogType.Success:logNode.m_LogMessage = Utility.Text.Format(LogSuccess, logMessage);break;case GMLogType.Failed:logNode.m_LogMessage = Utility.Text.Format(LogFailed, logMessage);break;default:logNode.m_LogMessage = Utility.Text.Format(LogCommand, logMessage);break;}return logNode;}/// <summary>/// 清理日志记录结点。/// </summary>public void Clear(){m_LogType = GMLogType.Failed;m_LogMessage = null;}}}
}

2. 将自定义的GM工具界面注册进GF Debuger窗口:

GF.Debugger.RegisterDebuggerWindow("GM", new GM.GMConsoleWindow());

效果:

相关文章:

【Unity】通用GM QA工具 运行时数值修改 命令行 测试工具

GM工具使用: GM工具通常用于游戏运行时修改数值(加钱/血量)、解锁关卡等&#xff0c;用于快速无死角测试游戏。一个通用型GM工具对于游戏项目是非常实用且必要的&#xff0c;但通用不能向易用妥协&#xff0c;纯命令行GM门槛太高&#xff0c;对QA不友好。 这类运行时命令行工具…...

[Spring] Spring原理(SpringBoot完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

python | rq,一个无敌的 关于Redis 的Python 库!

本文来源公众号“python”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;rq&#xff0c;一个无敌的 Python 库&#xff01; 大家好&#xff0c;今天为大家分享一个无敌的 Python 库 - rq。 Github地址&#xff1a;https://githu…...

Redis的缓存淘汰策略

1. 查看Redis 最大的占用内存 打开redis配置文件, 设置maxmemory参数&#xff0c;maxmemory 是bytes字节类型, 注意转换 2. Redis默认内存多少可以用 注意: 在64bit系统下&#xff0c; maxmemory 设置为 0 表示不限制Redis内存使用 3. 一般生产上如何配置 一般推荐Redis 设置内…...

【C++】深度解析:用 C++ 模拟实现 priority_queue类,探索其底层实现细节(仿函数、容器适配器)

目录 ⭐前言 ✨堆 ✨容器适配器 ✨仿函数 ⭐priority_queue介绍 ⭐priority_queue参数介绍 ⭐priority_queue使用 ⭐priority_queue实现 ✨仿函数实现 ✨堆的向上调整和向下调整 ✨完整代码 ⭐前言 ✨堆 堆是一种特殊的树形数据结构&#xff0c;通常以二叉树的…...

1个人躲,5个人抓!《极限竞速:地平线5》全新游戏模式“捉迷藏”即将推出

风靡全球的赛车竞速游戏《极限竞速&#xff1a;地平线5》即将推出全新游戏模式——捉迷藏(Hide & Seek)。 《极限竞速&#xff1a;地平线5》日前发布了全新预告&#xff0c;展示了即将于 9 月 10 日推出的捉迷藏模式游戏玩法。该预告是日前举办的2024 年科隆国际游戏展 Xb…...

ARCGIS XY坐标excel转要素面

1、准备好excel 坐标 excel文件转为csv才能识别&#xff0c;CSV只能保留第一个工作表并且&#xff0c;不会保留格式。 2、在ArcGis中导入XY事件图层 创建XY事件图层 图层要素赋对象ID 将导入的图层导出为先新的图层&#xff0c;这样就给每个要素附加了唯一的值 选择点集转线…...

MyBatis源码系列3(解析配置文件,创建SqlSessionFactory对象)

创建SqlSessionFactory&#xff1b; 首先读取配置文件&#xff0c;使用构造者模式创建SqlSessionFactory对象。 InputStream inputStream Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder…...

企业级web应用服务器tomcat

目录 一、Web技术 1.1 HTTP协议和B/S 结构 1.2 前端三大核心技术 1.2.1 HTML 1.2.2 CSS&#xff08;Cascading Style Sheets&#xff09;层叠样式表 1.2.3 JavaScript 二、tomcat的功能介绍 2.1 安装 tomcat 环境准备 2.1.1 安装java环境 2.1.2 安装并启动tomcat …...

深入浅出,探讨IM(即时通讯-聊天工具)技术架构及用户界面设计

在数字化时代的浪潮中&#xff0c;即时通讯&#xff08;IM&#xff09;工具已然成为人们日常沟通的重要方式。从微信、QQ到飞信钉、喧喧IM、企业微信、钉钉、Slack&#xff0c;这些IM工具不仅为我们提供了便捷的沟通方式&#xff0c;更在技术架构和用户界面设计上展现了独特的魅…...

小米、友邦带领恒指大反攻!

港股三大指数反弹止步2连跌&#xff0c;恒生科技指数一度冲高至2%&#xff0c;恒指收涨1.44%。盘面上&#xff0c;大型科技股多数表现活跃&#xff0c;业绩超预期&#xff0c;小米大涨超8%表现尤其抢眼&#xff0c;京东涨约4%&#xff0c;百度涨1.71%&#xff0c;网易涨2.14%&a…...

中国植物性状数据库

中国植物性状的研究主要集中在植物的生理结构和功能&#xff0c;‌以及它们对环境的适应性上。‌中国植物性状的多样性体现在多个方面&#xff0c;‌包括植物的生理结构、‌生长习性、‌以及对环境的适应性等。 中国植物性状数据库&#xff0c;包含了来自140个样点的1529种植物…...

[数据集][目标检测]街灯路灯检测数据集VOC+YOLO格式1893张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1893 标注数量(xml文件个数)&#xff1a;1893 标注数量(txt文件个数)&#xff1a;1893 标注…...

C++位运算

C位运算 运算符 & 按位与 如果两个相应的二进制位都为1&#xff0c;则该位的结果值为1&#xff0c;否则为0 | 按位或 两个相应的二进制位中只要有一个为1&#xff0c;该位的结果值为1 ^ 按位异或 若参加运算的两个二进制位值相同则为0&#xff0c;否则为1 ~ 取反 ~是一元…...

Day97:云上攻防-云原生篇KubernetesK8s安全APIKubelet未授权访问容器执行

知识点&#xff1a; 1、云原生-K8s安全-名词架构&各攻击点 2、云原生-K8s安全-Kubelet未授权访问 3、云原生-K8s安全-API Server未授权访问 K8S集群 Kubernetes是一个开源的&#xff0c;用于编排云平台中多个主机上的容器化的应用&#xff0c;目标是让部署容器化的应用…...

招聘|头部云厂商招 PG 核心骨干 DBA【上海】

我们的招聘专区又回来了&#xff01;&#x1f3c3; Bytebase 作为先进的数据库 DevOps 团队协同工具 &#x1f527;&#xff0c;用户群里汇聚了 &#x1f497; 业界优秀的 DBA&#xff0c;SRE&#xff0c;运维的同学们 &#x1f31f;。 上周用户群里有小伙伴发招聘信息 &…...

继承(下)【C++】

文章目录 子类继承父类之后&#xff0c;子类的默认成员函数的变化构造函数编译器自动生成的构造函数程序员手动写的构造函数 拷贝构造编译器自动生成的拷贝构造函数程序员手动写的拷贝构造函数 赋值重载编译器自动生成的赋值重载程序员手动写的赋值重载 析构函数 继承与友元菱形…...

AI模拟器

一、介绍 基于鸿蒙Next模拟一个ai对话过程二、场景需求 客户服务、数据分析、个性化推荐、图像和视频处理、智能家居、交通管理、教育行业、制造等等。 三、业务步骤 第一步&#xff1a;输入框提出问题&#xff0c;发送问题&#xff0c; 第二部&#xff1a;下次发送&#xff0…...

【C++二分查找 前缀和】1658. 将 x 减到 0 的最小操作数

本文涉及的基础知识点 C二分查找 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode1658. 将 x 减到 0 的最小操作数 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&am…...

验证实战知识点--(2)

1.seq中的pre_start pre_start 是 uvm_sequence 类的一个虚拟方法&#xff0c;用于在序列开始执行之前进行初始化和设置。这个方法在调用 start 方法前立即执行&#xff0c;提供了一个执行自定义初始化代码的机会。 start 方法用于启动序列的执行&#xff0c;而 pre_start 可以…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...