【工具】C#游戏防沉迷小工具

news2025/3/15 23:01:03

背景介绍

嘿,各位小伙伴!今天想跟大家唠唠我为啥要搞这么个防沉迷小工具。
咱都清楚,现在这游戏啊,玩起来那叫一个带劲,但时间一长,不仅眼睛累,心也跟着累。有些游戏,规则定得挺有意思:要是玩超过 15 分钟,你就可以秒退了,系统不会给你什么惩罚。这不,我就寻思着,要是有个东西能帮咱盯着游戏开始的时间,到时候提醒咱一声,那该多好啊!我可以根据当前局势判断要不要退出,要是碰到开挂的、花钱的,那果断推出,不能苦了自己舒服了别人。
于是,我就琢磨着做这么个防沉迷小工具。它的功能说白了就是:在后台悄悄看着游戏进程,一旦快到那个关键的 15 分钟(或者你可以自己定别的时长),就给你来个提醒,比如播放个声音,或者用其他方式。这样,咱就能根据当时的游戏情况,决定是赶紧退出 “保全自己”,还是再坚持一会儿。
而且啊,这小工具还很灵活,支持各种配置。你可以根据自己的喜好,调整提醒的时间、方式啥的,想怎么来就怎么来。不管你是想避开那些开挂的,还是不想被诱导花冤枉钱,有了它,都能帮你更果断地做出选择,不至于让自己玩得太累,让别人占了便宜。
总之,这防沉迷小助手就是咱在游戏世界里的一位贴心 “小管家”,帮咱把握好分寸,让咱玩得开心又不 “上头”。

一、需求设计目标

在游戏场景中,许多防沉迷系统采用强制退出机制。本工具旨在实现柔性提醒机制,通过声音、弹窗、窗口抖动等方式提醒用户,同时满足以下工程目标:

  1. 可配置化:支持配置进程匹配规则、提醒策略等参数
  2. 扩展性:可灵活添加新的提醒方式
  3. 健壮性:避免资源泄漏,处理进程访问异常
  4. 低侵入性:不修改目标进程内存或行为

效果如下:
效果图

二、架构设计

采用分层架构:

  • 配置层:处理XML配置加载
  • 监控层:实现进程状态检测
  • 策略层:多种提醒策略实现
  • UI层:提供配置界面和状态显示

三、关键模块实现

3.1 配置管理模块

使用.NET ConfigurationSection实现自定义配置:

public class ProcessMonitorSection : ConfigurationSection
{
    [ConfigurationProperty("ProcessItems")]
    public ProcessItemCollection ProcessItems => 
        (ProcessItemCollection)this["ProcessItems"];
}

优势:

  1. 配置热加载能力
  2. 强类型配置访问
  3. 配置验证机制

3.2 进程监控引擎

核心监控流程:

启动定时器
遍历配置规则
枚举系统进程
匹配进程名?
计算运行时间
超时且需提醒?
执行提醒策略

关键技术点:

  • 通配符转正则表达式
private Regex ConvertWildcardToRegex(string pattern)
{
    return new Regex("^" + 
        Regex.Escape(pattern)
            .Replace("\\*", ".*")
            .Replace("\\?", ".") + "$", 
        RegexOptions.IgnoreCase);
}
  • 进程生命周期管理
  • 异常处理机制

3.3 策略模式实现提醒

策略接口定义:

public interface IAlertStrategy
{
    Task ExecuteAsync(AlertContext context);
}

具体策略示例(窗口抖动):

public class WindowShakeStrategy : IAlertStrategy
{
    public async Task ExecuteAsync(AlertContext context)
    {
        await Task.Run(() => 
        {
            for (int i = 0; i < 3; i++) {
                NativeMethods.ShakeWindow(_windowHandle);
                Thread.Sleep(200);
            }
        });
    }
}

策略组合配置示例:

<add processNamePattern="notepad++" 
     addictionTime="300"
     soundAlert="Alarm01.wav"
     messageBoxText="该休息了!"
     showMessageBox="true"/>

四、工程实践亮点

4.1 资源管理

  • 实现IDisposable接口
  • 使用using语句确保资源释放
  • 定时器生命周期控制

4.2 并发控制

  • 锁机制保护共享资源
lock (_lock)
{
    // 访问_monitorItems
}
  • 异步策略执行避免UI阻塞

4.3 可观测性

  • 日志跟踪系统状态
  • 异常捕获与处理
catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException)
{
    // 处理进程访问异常
}

五、使用与扩展

5.1 配置示例

<ProcessItems>
    <add processNamePattern="game*.exe" 
         addictionTime="900"
         soundAlert="alert.wav"
         messageBoxText="游戏时间已达15分钟"
         showMessageBox="true"/>
</ProcessItems>

5.2 扩展新策略

  1. 实现IAlertStrategy接口
  2. 在配置中添加新策略参数
  3. 在工厂方法中创建策略实例

示例(邮件提醒策略):

public class EmailAlertStrategy : IAlertStrategy
{
    public Task ExecuteAsync(AlertContext context)
    {
        return Task.Run(() => 
        {
            // 发送邮件逻辑
        });
    }
}

六、完整代码

  • AppForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AntiAddictionAides
{
    public partial class AppForm : Form
    {
        #region 函数
        public AppForm()
        {
            InitializeComponent();

            #region 初始化
            Inst = this;

            var headers = new string[] { "序号", "进程信息", "防沉迷时间(秒)", "声音提示", "弹框提示", "窗口抖动" };
            foreach(var v in headers)
            {
                this.listViewProcess.Columns.Add(v).TextAlign = HorizontalAlignment.Center;
            }
            this.listViewProcess.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
            this.listViewProcess.Columns[1].Width = 150;
            this.listViewProcess.Columns[3].Width = 150;
            this.listViewProcess.Columns[4].Width = 400;
            this.listViewProcess.Columns[5].Width = 96;

            // 读取配置项
            _processMonitor = new ProcessMonitor();
            var configItems = LoadProcessConfig();
            foreach(var v in configItems)
            {
                var lv = this.listViewProcess.Items.Add($"{this.listViewProcess.Items.Count + 1}");
                lv.SubItems.AddRange(new string[] {
                    v.ProcessNamePattern,
                    v.AddictionTime.ToString(),
                    v.SoundAlert,
                    v.MessageBoxText,
                    (v.ShowMessageBox ? "是" : "否")
                });
                var monitor = _processMonitor.SetMonitor(v.ProcessNamePattern, TimeSpan.FromSeconds(v.AddictionTime));
                if(!string.IsNullOrWhiteSpace(v.SoundAlert))
                {
                    monitor.AddAlert(new SoundAlertStrategy(v.SoundAlert));
                }
                if (!string.IsNullOrWhiteSpace(v.MessageBoxText))
                {
                    monitor.AddAlert(new MessageBoxAlertStrategy(v.MessageBoxText));
                }
                if(v.ShowMessageBox)
                {
                    monitor.AddAlert(new WindowShakeStrategy(this.Handle));
                }
            }
            #endregion
        }

        public static void Log(string info)
        {
            lock(Inst)
            {
                Console.WriteLine($"[{DateTime.Now}]{info}");
            }
        }
        #endregion

        #region 内部函数
        static List<ProcessItem> LoadProcessConfig()
        {
            var config = new List<ProcessItem>();
            try
            {
                var section = ConfigurationManager.GetSection("ProcessMonitor") as ProcessMonitorSection;
                foreach (ProcessItemElement element in section.ProcessItems)
                {
                    config.Add(new ProcessItem
                    {
                        ProcessNamePattern = element.ProcessNamePattern,
                        AddictionTime = element.AddictionTime,
                        SoundAlert = element.SoundAlert,
                        MessageBoxText = element.MessageBoxText,
                        ShowMessageBox = element.ShowMessageBox
                    });
                }
            }
            catch (Exception ex)
            {
                AppForm.Log($"配置加载失败: {ex.Message}");
            }
            return config;
        }

        class ProcessItem
        {
            public string ProcessNamePattern { get; set; }
            public int AddictionTime { get; set; }
            public string SoundAlert { get; set; }
            public string MessageBoxText { get; set; }
            public bool ShowMessageBox { get; set; }
        }
        #endregion

        #region 属性变量
        ProcessMonitor _processMonitor;
        public static AppForm Inst { get; set; }
        #endregion

        #region 事件
        private void buttonStart_Click(object sender, EventArgs e)
        {
            if(buttonStart.Text == "启动")
            {
                _processMonitor.Start();
                buttonStart.Text = "停止";
            }
            else
            {
                _processMonitor.Stop();
                buttonStart.Text = "启动";
            }
        }
        #endregion
    }

    #region 配置
    public class ProcessMonitorSection : ConfigurationSection
    {
        [ConfigurationProperty("ProcessItems")]
        public ProcessItemCollection ProcessItems =>
            (ProcessItemCollection)this["ProcessItems"];
    }

    public class ProcessItemCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement() =>
            new ProcessItemElement();

        protected override object GetElementKey(ConfigurationElement element) =>
            ((ProcessItemElement)element).ProcessNamePattern;
    }

    public class ProcessItemElement : ConfigurationElement
    {
        [ConfigurationProperty("processNamePattern", IsRequired = true)]
        public string ProcessNamePattern =>
            (string)this["processNamePattern"];

        [ConfigurationProperty("addictionTime", IsRequired = true)]
        public int AddictionTime =>
            (int)this["addictionTime"];

        [ConfigurationProperty("soundAlert", IsRequired = false)]
        public string SoundAlert =>
            (string)this["soundAlert"];

        [ConfigurationProperty("messageBoxText", IsRequired = false)]
        public string MessageBoxText =>
            (string)this["messageBoxText"];

        [ConfigurationProperty("showMessageBox", IsRequired = true)]
        public bool ShowMessageBox =>
            (bool)this["showMessageBox"];
    }
    #endregion
}
  • ProcessMonitor.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Media;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace AntiAddictionAides
{
    public class ProcessMonitor : IDisposable
    {
        private readonly Timer _timer;
        private readonly Dictionary<string, MonitorItem> _monitorItems = new Dictionary<string, MonitorItem>();
        private readonly object _lock = new object();
        private bool _disposed;

        public ProcessMonitor()
        {
            _timer = new Timer(CheckProcesses, null, Timeout.Infinite, Timeout.Infinite);
        }

        public void Start()
        {
            // 每30秒检查一次
            _timer.Change(0, 5000);
        }

        public void Stop()
        {
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        public MonitorItem SetMonitor(string processNamePattern, TimeSpan threshold)
        {
            var regex = ConvertWildcardToRegex(processNamePattern);
            var monitor = new MonitorItem(regex, threshold);
            lock (_lock)
            {
                _monitorItems[processNamePattern] = monitor;
            }
            AppForm.Log($"添加监控,进程:{processNamePattern} 沉迷时间:{threshold.TotalSeconds} 秒");
            return monitor;
        }

        public void RemoveMonitor(string processNamePattern)
        {
            lock (_lock)
            {
                if (_monitorItems.TryGetValue(processNamePattern, out var item))
                {
                    item.Dispose();
                    _monitorItems.Remove(processNamePattern);
                }
            }
            AppForm.Log($"删除监控,进程:{processNamePattern}");
        }

        private void CheckProcesses(object state)
        {
            lock (_lock)
            {
                foreach (var item in _monitorItems.Values)
                {
                    AppForm.Log($"任务检查,进程:{item.ProcessNameRegex}");
                    foreach (var process in Process.GetProcesses())
                    {
                        try
                        {
                            if (!item.ProcessNameRegex.IsMatch(process.ProcessName))
                            {
                                continue;
                            }
                            item.Alert(process);
                        }
                        catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException)
                        {
                            // 处理进程访问异常
                        }
                        finally
                        {
                            process.Dispose();
                        }
                    }
                }
            }
        }

        private Regex ConvertWildcardToRegex(string pattern)
        {
            return new Regex("^" +
                Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") +
                "$", RegexOptions.IgnoreCase);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    Stop();
                    foreach (var item in _monitorItems.Values)
                    {
                        item.Dispose();
                    }
                    _timer.Dispose();
                }
                _disposed = true;
            }
        }

        ~ProcessMonitor()
        {
            Dispose(false);
        }

        public class MonitorItem
        {
            public Dictionary<int, AlertContext> ProcessAlert { get; set; }
            public Regex ProcessNameRegex { get; }
            public TimeSpan Threshold { get; }
            public List<IAlertStrategy> Alerts { get; private set; }

            public MonitorItem(Regex regex, TimeSpan threshold, IAlertStrategy[] alerts = null)
            {
                ProcessNameRegex = regex;
                Threshold = threshold;
                Alerts = new List<IAlertStrategy>();
                if (alerts != null)
                {
                    Alerts.AddRange(alerts);
                }
                ProcessAlert = new Dictionary<int, AlertContext>();
            }

            AlertContext GetAlertContext(Process process, int pid=0, string pname=null)
            {
                AlertContext context;
                lock (ProcessAlert)
                {
                    if (!ProcessAlert.TryGetValue(process.Id, out context) || process.ProcessName != context.ProcessName)
                    {
                        if(pid > 0 && !string.IsNullOrWhiteSpace(pname))
                        {
                            context = new AlertContext()
                            {
                                ProcessId = pid,
                                ProcessName = pname,
                                LastPlayed = DateTime.Now
                            };
                            ProcessAlert[pid] = context;
                        }
                    }
                }
                return context;
            }


            public void Alert(Process process)
            {
                var pinfo = $"[{process.Id}-{process.ProcessName}]";
                AlertContext context = GetAlertContext(process, process.Id, process.ProcessName);
                if (context == null)
                {
                    AppForm.Log($"获取告警上下文失败:{pinfo}");
                    return;
                }
                AppForm.Log($"获取{pinfo}上下文信息,告警数:{context.AlertNum} 最近时间:{context.LastPlayed}");
                if (context.AlertNum > 3)
                {
                    AppForm.Log($"进程{pinfo}告警数{context.AlertNum}超限,已忽略");
                    return;
                }
                context.RunTime = DateTime.Now - process.StartTime;
                if (context.RunTime < Threshold)
                {
                    AppForm.Log($"进程{pinfo}未达沉迷时限,已忽略");
                    return;
                }
                if (DateTime.Now - context.LastPlayed < TimeSpan.FromSeconds(10))
                {
                    AppForm.Log($"进程{pinfo}仍在提醒期限内,已忽略");
                    return;
                }

                context.AlertNum++;
                context.LastPlayed = DateTime.Now;
                AppForm.Log($"进程{pinfo}在{context.LastPlayed}触发提醒{context.AlertNum}");

                // 默认提示音
                if (Alerts.Count == 0)
                {
                    new SoundAlertStrategy("Alarm01.wav").ExecuteAsync(context);
                }
                else
                {
                    foreach(var v in Alerts)
                    {
                        v.ExecuteAsync(context);
                    }
                }
            }

            public int AddAlert(IAlertStrategy alert)
            {
                int idx = 0;
                lock(Alerts)
                {
                    Alerts.Add(alert);
                    idx = Alerts.Count;
                }
                return idx;
            }

            public void RemoveAlert(int idx)
            {
                if(idx < 0 || idx >= Alerts.Count)
                {
                    return;
                }

                lock (Alerts)
                {
                    Alerts.RemoveAt(idx);
                }
            }

            public void Dispose()
            {
                foreach(var v in Alerts)
                {
                    if(v is SoundAlertStrategy sa)
                    {
                        sa.Dispose();
                    }
                }
            }
        }
    }

    public class WinMediaWav
    {
        public static readonly string MediaPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Media");
        public static string[] Wavs
        {
            get
            {
                return null;
            }
        }

        public static string Wav(string name)
        {
            var file = Path.Combine(MediaPath, name);
            var files = new string[] { name, file, file + ".wav" };
            foreach(var v in files)
            {
                if (File.Exists(v))
                {
                    return v;
                }
            }
            return null;
        }
    }


    public interface IAlertStrategy
    {
        Task ExecuteAsync(AlertContext context);
    }

    public class SoundAlertStrategy : IAlertStrategy
    {
        private readonly SoundPlayer _player;

        public SoundAlertStrategy(string soundFilePath)
        {
            _player = new SoundPlayer(WinMediaWav.Wav(soundFilePath));
        }

        public Task ExecuteAsync(AlertContext context)
        {
            AppForm.Log($"执行提示音{context.RunTime} {context.ProcessId}-{context.ProcessName}");
            return Task.Run(() => _player.PlaySync());
        }

        public void Dispose()
        {
            _player.Dispose();
        }
    }

    public class MessageBoxAlertStrategy : IAlertStrategy
    {
        private string Text { get; set; }
        public MessageBoxAlertStrategy(string text)
        {
            Text = text;
        }

        public Task ExecuteAsync(AlertContext context)
        {
            return Task.Run(() =>
            {
                AppForm.Log($"执行弹框提醒{context.RunTime} {context.ProcessId}-{context.ProcessName}");
                var result = System.Windows.Forms.MessageBox.Show(
                    Text ?? $"老铁,醒醒,该休息啦!",
                    $"防沉迷提醒-{context.ProcessName} 沉迷时间:{context.RunTime}",
                    System.Windows.Forms.MessageBoxButtons.OK,
                    System.Windows.Forms.MessageBoxIcon.Information);
            });
        }
    }

    public class WindowShakeStrategy : IAlertStrategy
    {
        private readonly IntPtr _windowHandle;

        public WindowShakeStrategy(IntPtr hWnd)
        {
            _windowHandle = hWnd;
        }

        public async Task ExecuteAsync(AlertContext context)
        {
            await Task.Run(() =>
            {
                AppForm.Log($"执行窗口抖动提醒{context.RunTime} {context.ProcessId}-{context.ProcessName}");
                for (int i = 0; i < 3; i++)
                {
                    NativeMethods.ShakeWindow(_windowHandle);
                    Thread.Sleep(200);
                }
            });
        }
    }

    public class AlertContext
    {
        public int ProcessId { get; set; }
        public string ProcessName { get; set; }
        public TimeSpan RunTime { get; set; }
        public DateTime LastPlayed { get; set; } = DateTime.MinValue;
        public int AlertNum { get; set; }
    }

    internal static class NativeMethods
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        public static void ShakeWindow(IntPtr hWnd)
        {
            const uint SWP_NOSIZE = 0x0001;
            const uint SWP_NOZORDER = 0x0004;
            const int SHAKE_DISTANCE = 10; // 抖动幅度
            const int SHAKE_SPEED = 20;    // 抖动速度(毫秒)

            // 获取原始窗口位置
            if (!GetWindowRect(hWnd, out RECT originalRect))
                return;

            int originalX = originalRect.Left;
            int originalY = originalRect.Top;

            // 抖动动画
            for (int i = 0; i < 3; i++)
            {
                // 右移
                SetWindowPos(hWnd, IntPtr.Zero,
                    originalX + SHAKE_DISTANCE, originalY,
                    0, 0, SWP_NOSIZE | SWP_NOZORDER);
                System.Threading.Thread.Sleep(SHAKE_SPEED);

                // 左移
                SetWindowPos(hWnd, IntPtr.Zero,
                    originalX - SHAKE_DISTANCE, originalY,
                    0, 0, SWP_NOSIZE | SWP_NOZORDER);
                System.Threading.Thread.Sleep(SHAKE_SPEED);
            }

            // 恢复原始位置
            SetWindowPos(hWnd, IntPtr.Zero,
                originalX, originalY,
                0, 0, SWP_NOSIZE | SWP_NOZORDER);
        }
    }
}
  • App.Config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <!-- 必须放在最前面 -->
    <configSections>
        <section name="ProcessMonitor" type="AntiAddictionAides.ProcessMonitorSection, AntiAddictionAides" />
    </configSections>
    
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

    <ProcessMonitor>
        <ProcessItems>
            <add processNamePattern="notepad++" addictionTime="300" soundAlert="Alarm01.wav" messageBoxText="该休息了!" showMessageBox="true" />
            <add processNamePattern="chrome*" addictionTime="3600" soundAlert="Alarm02.wav" messageBoxText="注意用眼健康" showMessageBox="false" />
        </ProcessItems>
    </ProcessMonitor>
</configuration>

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

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

相关文章

深圳南柯电子|净水器EMC测试整改:水质安全与电磁兼容性的双赢

在当今注重健康生活的时代&#xff0c;净水器作为家庭用水安全的第一道防线&#xff0c;其性能与安全性备受关注。其中&#xff0c;电磁兼容性&#xff08;EMC&#xff09;测试是净水器产品上市前不可或缺的一环&#xff0c;它直接关系到产品在复杂电磁环境中的稳定运行及不对其…

SpeechCraf论文学习

Abstract 核心问题 挑战 语音风格包含细微的多样化信息&#xff08;如情感、语调、节奏&#xff09;&#xff0c;传统基于标签/模板的标注方法难以充分捕捉&#xff0c;制约了语音-语言多模态模型的性能。 数据瓶颈&#xff1a; 大规模数据收集与高质量标注之间存在矛盾&…

Work【2】:PGP-SAM —— 无需额外提示的自动化 SAM!

文章目录 前言AbstractIntroductionMethodsContextual Feature ModulationProgressive Prototype RefinementPrototype-based Prompt Generator ExperimentDatasetsImplementation DetailsResults and AnalysisAblation Study 总结 前言 和大家分享一下我们发表在 ISBI 2025 上…

数据安全之策:备份文件的重要性与自动化实践

在信息化高速发展的今天&#xff0c;数据已成为企业运营和个人生活中不可或缺的重要资源。无论是企业的财务报表、客户资料&#xff0c;还是个人的家庭照片、学习笔记&#xff0c;数据的丢失或损坏都可能带来无法挽回的损失。因此&#xff0c;备份文件的重要性日益凸显&#xf…

uniapp+Vue3 组件之间的传值方法

一、父子传值&#xff08;props / $emit 、ref / $refs&#xff09; 1、props / $emit 父组件通过 props 向子组件传递数据&#xff0c;子组件通过 $emit 触发事件向父组件传递数据。 父组件&#xff1a; // 父组件中<template><view class"container">…

blender使用初体验(甜甜圈教程)

使用blender 建模了甜甜圈&#xff0c;时间空闲了&#xff0c;但愿能创建点好玩的吸引人的东西

大模型学习笔记------Llama 3模型架构之旋转编码(RoPE)

大模型学习笔记------Llama 3模型架构之旋转编码&#xff08;RoPE&#xff09; 1、位置编码简介1.1 绝对位置编码1.2 相对位置编码 2、旋转编码&#xff08;RoPE&#xff09;2.1 基本概念---旋转矩阵2.2 RoPE计算原理2.2.1 绝对位置编码2.2.2 相对位置编码 3、旋转编码&#xf…

04 1个路由器配置一个子网的dhcp服务

前言 这是最近一个朋友的 ensp 相关的问题, 这里来大致了解一下 ensp, 计算机网络拓扑 相关基础知识 这里一系列文章, 主要是参照了这位博主的 ensp 专栏 这里 我只是做了一个记录, 自己实际操作了一遍, 增强了一些 自己的理解 当然 这里仅仅是一个 简单的示例, 实际场景…

安装open-webui

open-webui是一个开源的大语言模型交互界面 前提&#xff1a;Ollama已安装&#xff0c;并下载了deepseek-r1:1.5b模型 拉取镜像 docker pull ghcr.io/open-webui/open-webui:main 配置docker-compose.yml services:open-webui:image: ghcr.io/open-webui/open-webui:mainv…

HCIA-11.以太网链路聚合与交换机堆叠、集群

链路聚合背景 拓扑组网时为了高可用&#xff0c;需要网络的冗余备份。但增加冗余容易后会出现环路&#xff0c;所以我们部署了STP协议来破除环路。 但是&#xff0c;根据实际业务的需要&#xff0c;为网络不停的增加冗余是现实需要的一部分。 那么&#xff0c;为了让网络冗余…

Amazon RDS ProxySQL 探索(一)

:::info &#x1f4a1; 在日常开发中&#xff0c;开发者们会涉及到数据库的连接&#xff0c;在使用Amazon RDS数据库时&#xff0c;若使用集群模式或者多数据库时&#xff0c;会出现一写多读多个Endpoint&#xff0c;在实际开发中&#xff0c; 开发者们配置数据库连接通常希望走…

200多种算法应用于二维和三维无线传感器网络(WSN)覆盖场景

2.1 二元感知模型 在当前无线传感器网络&#xff08;WSN&#xff09;覆盖场景中&#xff0c;最常见且理想的感知模型是二元感知模型[27]。如图2所示&#xff0c; Q 1 Q_1 Q1​和 Q 2 Q_2 Q2​代表平面区域内的两个随机点。 Q 1 Q_1 Q1​位于传感器的检测区域内&#xff0c;其感…

模拟类似 DeepSeek 的对话

以下是一个完整的 JavaScript 数据流式获取实现方案&#xff0c;模拟类似 DeepSeek 的对话式逐段返回效果。包含前端实现、后端模拟和详细注释&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><titl…

Flutter PopScope对于iOS设置canPop为false无效问题

这个问题应该出现很久了&#xff0c;之前的组件WillPopScope用的好好的&#xff0c;flutter做优化打算“软性”处理禁用返回手势&#xff0c;出了PopScope&#xff0c;这个组件也能处理在安卓设备上的左滑返回事件。但是iOS上面左滑返回手势禁用&#xff0c;一直无效。 当然之…

SpringBoot + ResponseBodyEmitter 实时异步流式推送,优雅!

ChatGPT 的火爆&#xff0c;让流式输出技术迅速走进大众视野。在那段时间里&#xff0c;许多热爱钻研技术的小伙伴纷纷开始学习和实践 SSE 异步处理。 我当时也写过相关文章&#xff0c;今天&#xff0c;咱们换一种更为简便的方式来实现流式输出&#xff0c;那就是 ​​Respon…

网络运维学习笔记(DeepSeek优化版) 016 HCIA-Datacom综合实验01

文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置&#xff08;IP二层VLAN链路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…

02 windows qt配置ffmpeg开发环境搭建

版本说明 首先我使用ffmpeg版本是4.2.1 QT使用版本5.14.2 我选择是c编译 在02Day.pro⾥⾯添加ffmpeg头⽂件和库⽂件路径 win32 { INCLUDEPATH $$PWD/ffmpeg-4.2.1-win32-dev/include LIBS $$PWD/ffmpeg-4.2.1-win32-dev/lib/avformat.lib \$$PWD/ffmpeg-4.2.1-win32-dev/l…

vue-treeselect 【单选/多选】的时候只选择最后一层(绑定的值只绑定最后一层)

欢迎访问我的个人博客 &#xff5c;snows_ls BLOGhttp://snows-l.site 一、单选 1、问题&#xff1a; vue-treeselect 单选的时候只选择最后一层&#xff08;绑定的值只绑定最后一层&#xff09; 2、方法 1、只需要加上 :disable-branch-nodes"true" 就行&#xff0…

微信小程序实现根据不同的用户角色显示不同的tabbar并且可以完整的切换tabbar

直接上图上代码吧 // login/login.js const app getApp() Page({/*** 页面的初始数据*/data: {},/*** 生命周期函数--监听页面加载*/onLoad(options) {},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函…

Elastic Stack 8.16.0 日志收集平台的搭建

简介 1.1 ELK 介绍 ELK 是 ‌Elasticsearch‌、‌Logstash‌、‌Kibana‌ 三款开源工具的首字母缩写&#xff0c;构成了一套完整的日志管理解决方案&#xff0c;主要用于日志的采集、存储、分析与可视化‌。 1&#xff09;Logstash&#xff1a;数据管道工具&#xff0c;负责从…