Unity扩展 Text支持超链接文本

重点提示:当前的文本扩展支持多个超链接,支持修改超链接规则和支持修改超链接颜色。

近期在邮件文本中用到了超链接。最初是在邮件窗口中新加一个按钮用来超链接跳转,之后发现效果表现不如直接在文本中添加,后经过几个小时的资料查询将遇到的解决方法和问题贴出来。

方案一:换用TMP组件

问题:需要制作字体库等额外操作,改动较大不太适合。

方案二:网上找相关Text组件扩展

问题:在大多数扩展中,仅支持一个超链接文本。当文本中出现多个超链接文本时,只会响应第一个匹配的超链接点击事件。

最后在GitHub中找到了一个比较适合的Text扩展,支持多个正则超链接规则:GitHub - setchi/uGUI-Hypertext: Hypertext for uGUI

这里的解决方案,就是在这个脚本上进行修改而来的。原脚本中,每个超链接对应独立的点击事件,以及超链接颜色修改。更详细可直接看支持库中的例子。

根据需要,绑定超链接唯一点击事件,添加颜色开关等等,具体可直接查看代码:

///
/// 《超链接文本》支持多个链接 支持正则表达式
/// 当前版本修改于 uGUI-Hypertext GitHub:https://github.com/setchi/uGUI-Hypertext/tree/master
/// 新增超链接颜色修改控制。
/// 统一事件点击回调
/// 默认支持href匹配
/// 版本:0.10.0
/// 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Hyperlink
{
    /// <summary>
    /// 顶点池子
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class ObjectPool<T> where T : new()
    {
        private readonly Stack<T> _stack = new Stack<T>();
        private readonly Action<T> _getAction;
        private readonly Action<T> _releaseAction;
        
        /// <summary>
        /// 总数
        /// </summary>
        public int Count { get; set; }
        /// <summary>
        /// 没有被使用的数量
        /// </summary>
        public int UnusedCount => _stack.Count;
        /// <summary>
        /// 已经使用的数量
        /// </summary>
        public int UsedCount => Count - UnusedCount;

        public ObjectPool(Action<T> onGetAction, Action<T> onRelease)
        {
            _getAction = onGetAction;
            _releaseAction = onRelease;
        }
        
        public T Get()
        {
            T element;
            if (_stack.Count == 0)
            {
                element = new T();
                Count++;
            }
            else
            {
                element = _stack.Pop();
            }

            _getAction?.Invoke(element);

            return element;
        }

        public void Release(T element)
        {
            if (_stack.Count > 0 && ReferenceEquals(_stack.Peek(), element))
            {
                UnityEngine.Debug.LogError("试图归还已经归还的对象。");
            }

            _releaseAction?.Invoke(element);

            _stack.Push(element);
        }
    }

    /// <summary>
    /// 超链接信息块
    /// </summary>
    internal class LinkInfo
    {
        public readonly int StartIndex;
        public readonly int Length;
        public readonly string Link = null;
        public readonly string Text;
        public readonly Color Color;
        public  readonly bool OverwriteColor = false;
        public readonly ClickLinkEvent Callback;
        public List<Rect> Boxes;

        public LinkInfo(int startIndex, int length, Color? color, ClickLinkEvent callback)
        {
            StartIndex = startIndex;
            Length = length;
            Link = null;
            Text = null;
            OverwriteColor = color.HasValue;
            if (color.HasValue)
            {
                Color = color.Value;
            }
            Callback = callback;
            Boxes = new List<Rect>();
        }

        public LinkInfo(int startIndex, int length, string link, string text, Color? color, ClickLinkEvent callback)
        {
            StartIndex = startIndex;
            Length = length;
            Link = link;
            Text = text;
            OverwriteColor = color.HasValue;
            if (color.HasValue)
            {
                Color = color.Value;
            }
            Callback = callback;
            Boxes = new List<Rect>();
        }

        public LinkInfo(int startIndex, string link, string text, Color? color, ClickLinkEvent callback) : this(startIndex, text.Length, link, text, color,
            callback)
        {
        }

        public LinkInfo(int startIndex, string link, string text, ClickLinkEvent callback) : this(startIndex, link, text, Color.blue, 
            callback)
        {
        }
    }
    
    /// <summary>
    /// 超链接点击事件
    /// </summary>
    [Serializable]
    public class ClickLinkEvent : UnityEvent<string,string>
    {
    }

    /// <summary>
    /// 超链接正则表达式
    /// </summary>
    [Serializable]
    public class RegexPattern
    {
        public string pattern;
        public Color color;
        public bool overwriteColor = false;
        
        public RegexPattern(string regexPattern, Color color,bool overwriteColor = true)
        {
            this.pattern = regexPattern;
            this.overwriteColor = overwriteColor;
            this.color = color;
        }
        
        public RegexPattern(string regexPattern,bool overwriteColor = true):this(regexPattern,Color.blue,overwriteColor)
        {
        }
    }
    
    public class TextHyperlink : Text, IPointerClickHandler
    {
        private const int CharVertex = 6;
        private const char Tab = '\t', LineFeed = '\n', Space = ' ', LesserThan = '<', GreaterThan = '>';
        /// <summary>
        /// 看不见顶点的字符
        /// </summary>
        private readonly char[] _invisibleChars =
        {
            Space,
            Tab,
            LineFeed
        };
        /// <summary>
        /// 超链接信息块
        /// </summary>
        private readonly List<LinkInfo> _links = new List<LinkInfo>();
        /// <summary>
        /// 字符顶点池
        /// </summary>
        private static readonly ObjectPool<List<UIVertex>> UIVerticesPool = new ObjectPool<List<UIVertex>>(null, l => l.Clear());
        /// <summary>
        /// 字符索引映射
        /// </summary>
        private int[] _charIndexMap;
        
        private Canvas _root;
        private Canvas RootCanvas => _root ? _root : (_root = GetComponentInParent<Canvas>());

        /// <summary>
        /// 超链接匹配规则
        /// </summary>
        public List<RegexPattern> linkRegexPattern = new List<RegexPattern>()
        {
            new(@"<a href=([^>\n\s]+)>(.*?)(</a>)"),
        };

        [SerializeField]
        private ClickLinkEvent _onClickLink = new ClickLinkEvent();

        /// <summary>
        /// 超链接点击事件
        /// </summary>
        public ClickLinkEvent onClickLink
        {
            get => _onClickLink;
            set => _onClickLink = value;
        }

        #region PopulateMesh

        private readonly UIVertex[] _tempVerts = new UIVertex[4];
        
        protected override void OnPopulateMesh(VertexHelper toFill)
        {
            if (font == null)
            {
                return;
            }

            m_DisableFontTextureRebuiltCallback = true;

            var extents = rectTransform.rect.size;

            var settings = GetGenerationSettings(extents);
            settings.generateOutOfBounds = true;
            cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);

            var verts = cachedTextGenerator.verts;
            var unitsPerPixel = 1 / pixelsPerUnit;
            var vertCount = verts.Count;

            if (vertCount <= 0)
            {
                toFill.Clear();
                return;
            }

            var roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
            roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
            toFill.Clear();

            if (roundingOffset != Vector2.zero)
            {
                for (var i = 0; i < vertCount; ++i)
                {
                    var tempVertsIndex = i & 3;
                    _tempVerts[tempVertsIndex] = verts[i];
                    _tempVerts[tempVertsIndex].position *= unitsPerPixel;
                    _tempVerts[tempVertsIndex].position.x += roundingOffset.x;
                    _tempVerts[tempVertsIndex].position.y += roundingOffset.y;

                    if (tempVertsIndex == 3)
                    {
                        toFill.AddUIVertexQuad(_tempVerts);
                    }
                }
            }
            else
            {
                for (var i = 0; i < vertCount; ++i)
                {
                    var tempVertsIndex = i & 3;
                    _tempVerts[tempVertsIndex] = verts[i];
                    _tempVerts[tempVertsIndex].position *= unitsPerPixel;

                    if (tempVertsIndex == 3)
                    {
                        toFill.AddUIVertexQuad(_tempVerts);
                    }
                }
            }

            var vertices = UIVerticesPool.Get();
            toFill.GetUIVertexStream(vertices);

            GenerateCharIndexMap(vertices.Count < text.Length * CharVertex);

            _links.Clear();
            TryAddMatchLink();
            GenerateHrefBoxes(ref vertices);

            toFill.Clear();
            toFill.AddUIVertexTriangleStream(vertices);
            UIVerticesPool.Release(vertices);

            m_DisableFontTextureRebuiltCallback = false;
        }

        /// <summary>
        /// 生成超链接包围框
        /// </summary>
        /// <param name="vertices"></param>
        private void GenerateHrefBoxes(ref List<UIVertex> vertices)
        {
            var verticesCount = vertices.Count;

            for (var i = 0; i < _links.Count; i++)
            {
                var linkInfo = _links[i];

                var startIndex = _charIndexMap[linkInfo.StartIndex];
                var endIndex = _charIndexMap[linkInfo.StartIndex + linkInfo.Length - 1];

                for (var textIndex = startIndex; textIndex <= endIndex; textIndex++)
                {
                    var vertexStartIndex = textIndex * CharVertex;
                    if (vertexStartIndex + CharVertex > verticesCount)
                    {
                        break;
                    }

                    var min = Vector2.one * float.MaxValue;
                    var max = Vector2.one * float.MinValue;

                    for (var vertexIndex = 0; vertexIndex < CharVertex; vertexIndex++)
                    {
                        var vertex = vertices[vertexStartIndex + vertexIndex];
                        if (linkInfo.OverwriteColor)
                        {
                            vertex.color = linkInfo.Color;
                        }
                        vertices[vertexStartIndex + vertexIndex] = vertex;

                        var pos = vertices[vertexStartIndex + vertexIndex].position;

                        if (pos.y < min.y)
                        {
                            min.y = pos.y;
                        }

                        if (pos.x < min.x)
                        {
                            min.x = pos.x;
                        }

                        if (pos.y > max.y)
                        {
                            max.y = pos.y;
                        }

                        if (pos.x > max.x)
                        {
                            max.x = pos.x;
                        }
                    }

                    linkInfo.Boxes.Add(new Rect {min = min, max = max});
                }

                linkInfo.Boxes = CalculateLineBoxes(linkInfo.Boxes);
            }
        }

        /// <summary>
        /// 计算行包围框
        /// </summary>
        /// <param name="boxes"></param>
        /// <returns></returns>
        private static List<Rect> CalculateLineBoxes(List<Rect> boxes)
        {
            var lineBoxes = new List<Rect>();
            var lineStartIndex = 0;

            for (var i = 1; i < boxes.Count; i++)
            {
                if (boxes[i].xMin >= boxes[i - 1].xMin)
                {
                    continue;
                }

                lineBoxes.Add(CalculateAABB(boxes.GetRange(lineStartIndex, i - lineStartIndex)));
                lineStartIndex = i;
            }

            if (lineStartIndex < boxes.Count)
            {
                lineBoxes.Add(CalculateAABB(boxes.GetRange(lineStartIndex, boxes.Count - lineStartIndex)));
            }

            return lineBoxes;
        }

        private static Rect CalculateAABB(IReadOnlyList<Rect> rects)
        {
            var min = Vector2.one * float.MaxValue;
            var max = Vector2.one * float.MinValue;

            for (var i = 0; i < rects.Count; i++)
            {
                if (rects[i].xMin < min.x)
                {
                    min.x = rects[i].xMin;
                }

                if (rects[i].yMin < min.y)
                {
                    min.y = rects[i].yMin;
                }

                if (rects[i].xMax > max.x)
                {
                    max.x = rects[i].xMax;
                }

                if (rects[i].yMax > max.y)
                {
                    max.y = rects[i].yMax;
                }
            }

            return new Rect {min = min, max = max};
        }

        /// <summary>
        /// 生成字节索引映射
        /// </summary>
        /// <param name="verticesReduced"></param>
        private void GenerateCharIndexMap(bool verticesReduced)
        {
            if (_charIndexMap == null || _charIndexMap.Length < text.Length)
            {
                Array.Resize(ref _charIndexMap, text.Length);
            }

            if (!verticesReduced)
            {
                for (var i = 0; i < _charIndexMap.Length; i++)
                {
                    _charIndexMap[i] = i;
                }
                return;
            }

            var offset = 0;
            var inTag = false;

            for (var i = 0; i < text.Length; i++)
            {
                var character = text[i];

                if (inTag)
                {
                    offset--;

                    if (character == GreaterThan)
                    {
                        inTag = false;
                    }
                }
                else if (supportRichText && character == LesserThan)
                {
                    offset--;
                    inTag = true;
                }
                else if (_invisibleChars.Contains(character))
                {
                    offset--;
                }

                _charIndexMap[i] = Mathf.Max(0, i + offset);
            }
        }

        #endregion

        private Vector3 CalculateLocalPosition(Vector3 position, Camera pressEventCamera)
        {
            if (!RootCanvas)
            {
                return Vector3.zero;
            }

            if (RootCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
            {
                return transform.InverseTransformPoint(position);
            }

            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                rectTransform,
                position,
                pressEventCamera,
                out var localPosition
            );

            return localPosition;
        }
        
        void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
        {
            var localPosition = CalculateLocalPosition(eventData.position, eventData.pressEventCamera);

            foreach (var linkInfo in _links)
            {
                if (!linkInfo.Boxes.Any(t => t.Contains(localPosition))) continue;
                var subText = text.Substring(linkInfo.StartIndex, linkInfo.Length);
                var link = linkInfo.Link ?? subText;
                var content = linkInfo.Text ?? subText;
                linkInfo.Callback?.Invoke(link,content);
            }
        }

        #region Add Text Link
        
        /// <summary>
        /// 尝试添加超链接
        /// </summary>
        private void TryAddMatchLink()
        {
            foreach (var entry in linkRegexPattern)
            {
                var matches = Regex.Matches(text, entry.pattern, RegexOptions.Singleline);
                foreach (Match match in matches)
                {
                    var regex = new Regex(entry.pattern, RegexOptions.Singleline);
                    var regexMatch = regex.Match(match.Value);
                    var overwriteColor = entry.overwriteColor == true ? entry.color : (Color?)null;
                    if (regexMatch.Success)
                    {
                        var group = match.Groups[1];
                        AddLink(match.Index, group.Value,match.Value, overwriteColor, _onClickLink);
                    }
                    else
                    {
                        AddLink(match.Index, match.Value.Length, overwriteColor, _onClickLink);
                    }
                }
            }
        }

        private void CheckLinkException(int startIndex, int length, ClickLinkEvent onClick)
        {
            if (onClick == null)
            {
                throw new ArgumentNullException(nameof(onClick));
            }

            if (startIndex < 0 || startIndex > text.Length - 1)
            {
                throw new ArgumentOutOfRangeException(nameof(startIndex));
            }

            if (length < 1 || startIndex + length > text.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length));
            }
        }
        
        private void AddLink(int startIndex, int length, Color? linkColor, ClickLinkEvent onClick)
        {
            CheckLinkException(startIndex, length, onClick);

            _links.Add(new LinkInfo(startIndex, length, linkColor, onClick));
        }
        
        private void AddLink(int startIndex, string link, string content, Color? linkColor, ClickLinkEvent onClick)
        {
            CheckLinkException(startIndex, content.Length, onClick);

            _links.Add(new LinkInfo(startIndex, link, content, linkColor, onClick));
        }

        protected void AddLink(int startIndex, string link, string content, ClickLinkEvent onClick)
        {
            CheckLinkException(startIndex, content.Length, onClick);

            _links.Add(new LinkInfo(startIndex, link, content, onClick));
        }
        
        protected void CleanLink()
        {
            _links.Clear();
            linkRegexPattern.Clear();
        }

        #endregion
        
        #region Hyperlink_Test

        #if Hyperlink_Test
        protected override void OnEnable()
        {
            base.OnEnable();
            onClickLink.AddListener(OnClickLinkText);
        }

        protected override void OnDisable()
        {
            base.OnDisable();
            onClickLink.RemoveListener(OnClickLinkText);
        }

        /// <summary>
        /// 当前点击超链接回调
        /// </summary>
        private void OnClickLinkText(string link,string content)
        {
            Debug.Log($"超链接信息:{link}\n{content}");
            Application.OpenURL(link);
        }
        #endif
        #endregion
    }
}

编辑器面板扩展:

继承自UGUI-Text面板,在原有数据显示上,添加可编辑的扩展属性。

using UnityEditor;
using UnityEditor.UI;

namespace Hyperlink.Editor
{
    [CustomEditor(typeof(TextHyperlink), true)]
    [CanEditMultipleObjects]
    public class TextHyperlinkEditor : TextEditor
    {
        private SerializedProperty _linkRegexPattern;
        private SerializedProperty _onClickLink;
        protected override void OnEnable()
        {
            base.OnEnable();
            _linkRegexPattern = serializedObject.FindProperty("linkRegexPattern");
            _onClickLink = serializedObject.FindProperty("_onClickLink");
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            
            serializedObject.Update();
            EditorGUILayout.PropertyField(_linkRegexPattern);
            EditorGUILayout.Space();
            EditorGUILayout.PropertyField(_onClickLink);
            serializedObject.ApplyModifiedProperties();
        }
    }
}

效果图:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

两步解决Hugging Face下载模型速度慢/连接超时/无法下载问题

博主使用的配置是 x86_64 Linux服务器 第一步设置代理镜像: export HF_ENDPOINT=https://hf-mirror.com 第二步(使用代码时,删除引号): --token参数表示下载的模型是否需要登录验证(部分模型需要token) huggingface-cli download --token "获取的hf_*******token…

如何选择优质模型?SD3性能究竟如何?

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答12 厚德提问大佬答第十二期 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德…

vivado联合modelsim仿真

一. 编译Vivado仿真库 打开Vivado&#xff0c;Tools -> Compile Simulation Libraries 二. 设置仿真工具和库路径 因为新建工程的默认仿真工具是Vivado Simulator&#xff0c;所以要使用Modelsim仿真&#xff0c;每个新工程都要设置一次&#xff0c;方法如下&#xff1a; …

2024年【安全生产监管人员】考试题及安全生产监管人员试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产监管人员考试题根据新安全生产监管人员考试大纲要求&#xff0c;安全生产模拟考试一点通将安全生产监管人员模拟考试试题进行汇编&#xff0c;组成一套安全生产监管人员全真模拟考试试题&#xff0c;学员可通…

现代工作场所中的睡岗检测算法应用

在现代职场环境中&#xff0c;员工的工作状态直接影响到公司的整体效益。睡岗现象&#xff0c;即员工在工作时间内打瞌睡或睡觉&#xff0c;不仅降低了生产力&#xff0c;还可能带来安全隐患。因此&#xff0c;如何有效地检测和预防睡岗行为成为了企业管理中的一个重要课题。随…

真的,今年我劝各位真的别轻易离职

前言 在当前的职业浪潮中&#xff0c;把握时机深入学习大模型技术无疑是明智之举。随着行业对这类专业人才的渴求日益增长&#xff0c;无论是巨头企业还是新兴初创&#xff0c;都在加速推进大模型的应用实践&#xff0c;而真正能将技术有效落地并转化为业务价值的专家却供不应…

详细解释下flutter初始示例的代码

详细解释下flutter初始示例的代码 main 首句导入需要的包 类似于其他语言的import main函数为入口函数 包裹MyApp类 MyApp 这个类继承自无状态类 可见myapp不管理任何状态 build方法是所有widget内必须实现的方法 此处返回一个 ChangeNotferiProvider 可以看到它用于管理应…

理解 REST API 和 GraphQL 的区别

你可能听说过 GraphQL&#xff0c;但对它与 REST 的区别还不完全确定。今天我们将介绍 REST 和 GraphQL 的一些基本原理&#xff0c;以及它们的不同使用场景。 GraphQL 作为 REST API 的替代品越来越受欢迎&#xff0c;不过它不一定是完全的“替代品”。 根据你的使用情景&am…

LLM笔记:训练大模型之并行化

1 数据并行 最常见的并行化手段主要是把数据分成多个块&#xff0c;然后每个节点就可以在本地独立的跑各自的数据任务&#xff0c;最后再和其他节点通信&#xff0c;进而汇总最后的结果好处就是计算效率高&#xff0c;每个节点可以独自计算自己的任务且这种方法易于实现缺点就…

【Python基础篇】一篇文章入门Python,进入Python的世界

文章目录 0.前言1.打印&#xff08;Hello&#xff0c;World&#xff09;2.创建变量3.打印升级3.1 打印一句话中间加变量3.2 sep设置分隔符3.3 end和换行 4. 注释 0.前言 大家好&#xff0c;我是小辰&#xff0c;前几天做了个重大的决定&#xff0c;学习python。 首先&#xff0…

博途S7-1500PLC“虚轴“编程应用

1、CODESYS如何添加虚轴 如何添加虚轴(AM400PLC)-CSDN博客文章浏览阅读164次。EtherCAT运动控制总线启用的时候,选择EtherCAT总线任务周期。选择好后,选择点击添加。https://rxxw-control.blog.csdn.net/article/details/139898985虚轴是利用软件算法实现的运动控制轨迹规划…

外挂级OCR神器:免费文档解析、表格识别、手写识别、古籍识别、PDF转Word

智能文档解析&#xff1a;大模型友好的文档解析工具 PDF转Markdown 支持将任意格式的文件&#xff08;图片、PDF、Doc&#xff0f;Docx、网页等&#xff09;解析为Markdown或Json格式&#xff0c;以对LLM友好的方式呈现。 更高速度&#xff1a;100页PDF最快1.5s完成解析 更大…

DEBOPIE框架:打造最好的ChatGPT交易机器人

本文介绍了如何利用 DEBOPIE 框架并基于 ChatGPT 创建高效交易机器人&#xff0c;并强调了在使用 AI 辅助交易时需要注意的限制以及操作步骤。原文: Build the Best ChatGPT Trading Bots with my “DEBOPIE” Framework 如今有大量文章介绍如何通过 ChatGPT 帮助决定如何以及在…

Hi3861 OpenHarmony嵌入式应用入门--TCP Server

本篇使用的是lwip编写tcp服务端。需要提前准备好一个PARAM_HOTSPOT_SSID宏定义的热点&#xff0c;并且密码为PARAM_HOTSPOT_PSK LwIP简介 LwIP是什么&#xff1f; A Lightweight TCP/IP stack 一个轻量级的TCP/IP协议栈 详细介绍请参考LwIP项目官网&#xff1a;lwIP - A Li…

6.7、函数的分文件编写

mian函数部分代码 #include <iostream> using namespace std; #include <string> #include "swap.h"//函数的分文件编写 //实现两个数字进行交换的函数//函数的声明 //void swap(int a,int b); //函数的定义 //void swap(int a, int b) //{ // int temp…

9. Revit API UI: UIView、UIDocument、框选聚焦

9. Revit API UI: UIView、UIDocument、框选聚焦 UI命名空间下的API&#xff0c;到这里差不多就要讲完了&#xff0c;同Application那篇所讲的几个类与接口&#xff0c;都是带UI的对应了一个不带UI的&#xff0c;如UIApplication和Application&#xff0c;作用呢&#xff0c;也…

Python基于PyQt5和卷积神经网络分类模型(CNN分类算法)实现时装类别识别系统GUI界面项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 随着深度学习技术的发展&#xff0c;计算机视觉领域取得了显著的进步&#xff0c;特别是在图像分类、目…

Java8环境安装(jdk1.8安装)详细教程

Java 8环境安装&#xff08;jdk1.8安装&#xff09;详细教程 Java 8&#xff08;也称为JDK 1.8&#xff09;&#xff0c;是Oracle公司于2014年3月发布的一个重要的Java语言版本。这个版本自发布以来&#xff0c;因其众多的新特性和改进&#xff0c;被认为是Java语言发展历程中…

渗透测试之注入

命令注入 命令注入相关分隔符&#xff1a; 字符说明;仅限Linux环境&#xff0c;用于隔开命令&#xff0c;按顺序执行|前面命令的输出结果作为后面命令的输入内容||前提是前面的命令执行失败&#xff0c;和&&号相反&前后两条命令依次执行&&前提是前面的命…

虚拟机的网络配置

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️ 每一步都向着梦想靠近&#xff0c;坚持就是胜利的序曲 一 …