【Unity AI】基于 WebSocket 和 讯飞星火大模型

news2024/11/28 8:07:41

文章目录

    • 整体
      • AIManager
      • DialogueManager
      • UIManager
      • ModelManager
      • AudioManager
      • SaveManager
    • 详细部分
      • AI
      • UI
      • 动画
      • 音频

在这里插入图片描述
在这里插入图片描述

整体

AIManager

负责配置讯飞的appId,生成鉴权URL,通过WebSocket向服务器请求并返回数据(分为最终返回和流式返回)

 public async void RequestAnswer(List<Content> dialogueContext, Action<string> completeCallback, Action<string> streamingCallback = null)
 {
     //获取鉴权URL
     string authUrl = GetAuthURL();
     string url = authUrl.Replace("http://", "ws://").Replace("https://", "wss://");

     using(webSocket = new ClientWebSocket())
     {
         try
         {
             //向服务器发起连接请求
             await webSocket.ConnectAsync(new Uri(url), cancellation);

             Debug.Log("成功连接服务器");

             //改变参数的上下文对话(这一块外包给了dialogueManager进行控制,因为要控制上下文长度)
             request.payload.message.text = dialogueContext;

             //将Json序列化为byte流
             string jsonString = JsonConvert.SerializeObject(request);
             byte[] binaryJsonData = Encoding.UTF8.GetBytes(jsonString.ToString());

             //向服务器发送数据
             _=webSocket.SendAsync(new ArraySegment<byte>(binaryJsonData), WebSocketMessageType.Text, true, cancellation);

             Debug.Log("已向服务器发送数据,正在等待消息返回...");

             //循环等待服务器返回内容
             byte[] receiveBuffer = new byte[1024];
             WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
             String resp = "";
             while (!result.CloseStatus.HasValue)
             {
                 if (result.MessageType == WebSocketMessageType.Text)
                 {
                     string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count);

                     //将结果解释为Json(没有指定具体类型)
                     JObject jsonObj = JObject.Parse(receivedMessage);
                     int code = (int)jsonObj["header"]["code"];

                     if (0 == code)
                     {
                         int status = (int)jsonObj["payload"]["choices"]["status"];

                         JArray textArray = (JArray)jsonObj["payload"]["choices"]["text"];
                         string content = (string)textArray[0]["content"];
                         resp += content;

                         if (status == 2)
                         {
                             Debug.Log($"最后一帧: {receivedMessage}");
                             int totalTokens = (int)jsonObj["payload"]["usage"]["text"]["total_tokens"];
                             Debug.Log($"整体返回结果: {resp}");
                             Debug.Log($"本次消耗token数: {totalTokens}");

                             completeCallback(resp.TrimStart('\n'));
                             return;
                         }
                         else
                         {
                             streamingCallback?.Invoke(resp.TrimStart('\n'));
                         }
                     }
                     else
                     {
                         Debug.Log($"请求报错: {receivedMessage}");
                         if (code == 10013)
                             OnRequestBan?.Invoke();
                         break;
                     }
                 }
                 else if (result.MessageType == WebSocketMessageType.Close)
                 {
                     Debug.Log("已关闭WebSocket连接");
                     break;
                 }

                 result = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
             }
         }
         catch (Exception e)
         {
             Debug.LogError(e.ToString());
         }

         completeCallback(null);
     }
 }


 string GetAuthURL()
 {
     //date参数生成
     string date = DateTime.UtcNow.ToString("r");

     //authorization参数生成
     StringBuilder stringBuilder = new StringBuilder("host: spark-api.xf-yun.com\n");
     stringBuilder.Append("date: ").Append(date).Append("\n");
     stringBuilder.Append("GET /").Append(llmVersion).Append(" HTTP/1.1");

     //利用hmac-sha256算法结合APISecret对上一步的tmp签名,获得签名后的摘要tmp_sha。
     //将上方的tmp_sha进行base64编码生成signature
     string signature = HMACsha256(apiSecret, stringBuilder.ToString());

     //利用上面生成的签名,拼接下方的字符串生成authorization_origin
     string authorization_origin = string.Format("api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", apiKey, "hmac-sha256", "host date request-line", signature);

     //最后再将上方的authorization_origin进行base64编码,生成最终的authorization
     string authorization = Convert.ToBase64String(Encoding.UTF8.GetBytes(authorization_origin));

     //将鉴权参数组合成最终的键值对,并urlencode生成最终的握手URL。
     string path1 = "authorization=" + authorization;
     string path2 = "date=" + WebUtility.UrlEncode(date);
     string path3 = "host=" + "spark-api.xf-yun.com";

     return "wss://spark-api.xf-yun.com/" + llmVersion + "?" + path1 + "&" + path2 + "&" + path3;
 }


 public string HMACsha256(string apiSecretIsKey, string buider)
 {
     byte[] bytes = Encoding.UTF8.GetBytes(apiSecretIsKey);
     System.Security.Cryptography.HMACSHA256 hMACSHA256 = new System.Security.Cryptography.HMACSHA256(bytes);
     byte[] date = Encoding.UTF8.GetBytes(buider);
     date = hMACSHA256.ComputeHash(date);
     hMACSHA256.Clear();

     //将上方的tmp_sha进行base64编码生成signature
     return Convert.ToBase64String(date);
 }

DialogueManager

作为UI和AI的中间层,存储历史的用户和AI对话,对历史对话进行裁切,保存到本地。

//说话
public void Talk(string contentText, Action<string> completeCallback, Action<string> streamingCallback)
{
    //添加到历史记录
    AddHistory(Role.User, contentText);
    
    //用于发送到AIManager,拼接上玩家的角色设定
    List<Content> dialogue = new List<Content>() { CharacterSettingContent };
    dialogue.AddRange(historyDialogue);

    //日志打印
    StringBuilder sb = new StringBuilder();
    foreach(Content content in dialogue)
    {
        sb.Append(content.content).Append("\n");
    }
    Debug.LogWarning(sb.ToString());

    //发送给AI
    AIManager.Instance.RequestAnswer(dialogue, (answer) =>
    {
        if(answer != null)
            AddHistory(Role.AI, answer);

        completeCallback(answer);
    }, streamingCallback);
}

public void AddHistory(Content content)
{
    Role role = Role.User;
    switch(content.role)
    {
    case "system":
        role = Role.System;
        break;
    case "user":
        role = Role.User;
        break;
    case "assistant":
        role = Role.AI;
        break;
    }

    AddHistory(role, content.content);
}

public void AddHistory(Role role, string contentText)
{
    //确定当前加入历史记录的Role
    string roleStr = "";
    switch(role)
    {
    case Role.System:
        roleStr = "system";
        break;
    case Role.User:
        roleStr = "user";
        break;
    case Role.AI:
        roleStr = "assistant";
        break;
    default:
        break;
    }

    //添加到历史记录
    historyDialogue.AddLast(new Content() { role = roleStr, content = contentText });
    currentTokens += contentText.Length;

    //如果是用户发送的,就进行裁切
    if(role == Role.User)
    {
        //裁切历史文本
        while (currentTokens >= MaxHistoryTokens)
        {
            //发送删除委托
            OnDialogueRemoved?.Invoke(historyDialogue.First());

            currentTokens -= historyDialogue.First.Value.content.Length;
            historyDialogue.RemoveFirst();
        }
    }

    //序列化后保存到本地
    SaveManager.Instance.data.dialogues = historyDialogue;
    SaveManager.Instance.Save();

    StringBuilder sb = new StringBuilder();
    foreach (Content content in historyDialogue)
    {
        sb.Append(content.content).Append("\n");
    }
    Debug.Log(sb.ToString());

    //发送委托
    OnDialogueAdded?.Invoke(historyDialogue.Last());
}

UIManager

负责跨UI的交互,如输入发送用户文本,需要触发接收显示对话气泡文本,涉及到两个UI类。配合DOTween实现打字机效果,无代码实现随字体伸展的对话框效果。

下面是对话气泡的显示效果

using DG.Tweening;
using DG.Tweening.Core;
using DG.Tweening.Plugins.Options;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class DialogueBubbleUI : MonoBehaviour
{
    //UI引用
    public TMP_Text shortTextUI, longTextUI;
    public GameObject shortUI, longUI;
    private ScrollRect longUIScrollRect;

    //分割属性
    public int splitLength = 360;
    public int limitShortTextUIHeigt = 171;

    //打字机效果
    TweenerCore<string, string, StringOptions> tweener;
    public float typeSpeed = .01f;

    //临时存储
    private string lastTextContent = "";

    private void Awake()
    {
        longUIScrollRect = longUI.GetComponent<ScrollRect>();
    }

    //隐藏和复位所有对话框
    public void ResetUI()
    {
        shortUI.SetActive(false);
        shortTextUI.text = string.Empty;
        longUI.SetActive(false);
        longTextUI.text = string.Empty;
    }

    public void PlayFromStart(string endText)
    {
        ResetUI();

        Play(endText);
    }

    public void Play(string endText)
    {
        string startText;
        if (longTextUI.text != string.Empty)
            startText = longTextUI.text;
        else
            startText = shortTextUI.text;

        //DoTween实现打字机效果
        tweener?.Kill();
        tweener = DOTween.To(() => startText, value =>
        {
            //中间判断说的话的长度,确定是否需要转到大的对话框(可下拉的那种)
            //达到阈值后转为显示大对话框
            if (shortTextUI.rectTransform.rect.height > limitShortTextUIHeigt && shortTextUI.text.Length > 0)
            {
                if (!longUI.activeSelf)
                {
                    shortUI.SetActive(false);
                    longUI.SetActive(true);
                }

                longTextUI.text = value;
                longUIScrollRect.verticalNormalizedPosition = 0f;
            }
            else
            {
                if (!shortUI.activeSelf)
                {
                    shortUI.SetActive(true);
                    longUI.SetActive(false);
                }

                shortTextUI.text = value;
            }

            //播放UI音效
            if (lastTextContent != value)
                AudioManager.Instance.PlayUIAudio(UIAudioType.DialoguePopText);
            lastTextContent = value;

        }, endText, endText.Length * typeSpeed).SetUpdate(true).SetEase(Ease.Linear);
    }
}

ModelManager

负责触发模型的动画,使用枚举值选择动画,枚举名称对应了animator中的对应名称的参数。

负责定时随机播放模型的待机动画,问问题时的思考动画播放

AudioManager

负责播放UI音效(文字冒泡音效,),内部使用池化的思想,构建了一个大小固定的AudioSource池,对外暴露的Play接口会从池中获取闲置的或者最早使用的AudioSource,避免每次都重新创建该组件。

使用AudioMixer配合暴露的参数来统一控制播放的音量。

负责背景音乐的播放,开始时随机选择一首,播放完一首后播放下一首。对外暴露接口供播放器UI调用。

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

public class AudioManager : UnitySingleton<AudioManager>
{
    [Header("UI Audio")]
    //UI音效对象池
    public int AudioPoolSize = 10;
    private GameObject audioSourceParent;
    private Queue<AudioSource> audioSources;

    //UI音量组
    public AudioMixerGroup uiAudioMixerGroup;
    private float initUIAudioVolume;

    //配置UI音频列表
    [System.Serializable]
    public class UIAudioClipConfig
    {
        public UIAudioType type;
        public AudioClip clip;
    }
    public List<UIAudioClipConfig> uiAudioClips;
    private Dictionary<UIAudioType, AudioClip> uiAudioClipsDict = new Dictionary<UIAudioType, AudioClip>();


    [Header("BGM")]
    //背景音量组(背景使用单独的AudioSource)
    public AudioMixerGroup bgmAudioMixerGroup;
    private float initBGMAudioVolume;
    private AudioSource bgmAudioSource;

    //配置背景音频列表
    [System.Serializable]
    public class BGAudioClipConfig
    {
        public string SongTitle, Singer;
        public AudioClip song;
    }
    public List<BGAudioClipConfig> BGMs;
    private int currentBgmIndex;
    private bool isMusicPlay = true;
    

    //委托
    public event System.Action<BGAudioClipConfig> OnBGMSelected;
    public event System.Action OnBGMPlay, OnBGMPause;


    protected override void Awake()
    {
        base.Awake();

        //初始化默认参数
        uiAudioMixerGroup.audioMixer.GetFloat("UIVolume", out initUIAudioVolume);
        bgmAudioMixerGroup.audioMixer.GetFloat("BGMVolume", out initBGMAudioVolume);

        //编辑器面板配置的UI参数数组转为字典
        foreach(var config in  uiAudioClips)
        {
            uiAudioClipsDict.Add(config.type, config.clip);
        }
        uiAudioClips.Clear();

        //初始化一个AudioSources子级对象
        audioSourceParent = transform.Find("AudioSources")?.gameObject;
        if (audioSourceParent == null)
        {
            GameObject obj = new GameObject("AudioSources");
            audioSourceParent = obj;
            audioSourceParent.transform.parent = transform;
        }

        //在子级对象上添加UIAudioSource
        audioSources = new Queue<AudioSource>(AudioPoolSize);
        for (int i = 0; i < AudioPoolSize; i++)
        {
            audioSources.Enqueue(audioSourceParent.AddComponent<AudioSource>());
        }

        //在子级上添加背景的AudioSource
        bgmAudioSource = audioSourceParent.AddComponent<AudioSource>();
        bgmAudioSource.outputAudioMixerGroup = bgmAudioMixerGroup;
    }

    private void Start()
    {
        //读取UI音频设置
        SetUIAudioVolume(SaveManager.Instance.data.uiVolumeMultiper);
        //读取背景音频设置
        SetBGMAudioVolume(SaveManager.Instance.data.bgmVolumeMultiper);

        //读取播放设置
        isMusicPlay = SaveManager.Instance.data.isMusicPlay;

        //随机选择背景音乐
        SelectBGM(Random.Range(0, BGMs.Count));
    }

    //设置UI音量
    public void SetUIAudioVolume(float multiper)
    {
        //修改AudioMixerGroup
        uiAudioMixerGroup.audioMixer.SetFloat("UIVolume", multiper < 0.05 ? -1000 : Mathf.LerpUnclamped(initUIAudioVolume * 2, initUIAudioVolume, multiper));

        //存储到本地
        SaveManager.Instance.data.uiVolumeMultiper = multiper;
        SaveManager.Instance.Save();
    }

    //设置BGM音量
    public void SetBGMAudioVolume(float multiper)
    {
        //修改AudioMixerGroup
        bgmAudioMixerGroup.audioMixer.SetFloat("BGMVolume", multiper < 0.05 ? -1000 : Mathf.LerpUnclamped(initBGMAudioVolume * 2, initBGMAudioVolume, multiper));

        //存储到本地
        SaveManager.Instance.data.bgmVolumeMultiper = multiper;
        SaveManager.Instance.Save();
    }


    public void PlayAudio(AudioClip audioClip, AudioSource audioSource)
    {
        audioSource.PlayOneShot(audioClip);
        audioSources.Enqueue(audioSource);
    }

    public void PlayUIAudio(UIAudioType audioType)
    {
        AudioSource audioSource = audioSources.Dequeue();
        audioSource.outputAudioMixerGroup = uiAudioMixerGroup;
        PlayAudio(uiAudioClipsDict[audioType], audioSource);
    }

    private void Update()
    {
        if(isMusicPlay)
        {
            if(!bgmAudioSource.isPlaying)
            {
                SelectNextBGM();
            }
        }
    }

    //BGM
    public void PlayBGM()
    {
        isMusicPlay = true;

        bgmAudioSource.clip = BGMs[currentBgmIndex].song;
        bgmAudioSource.Play();

        SaveManager.Instance.data.isMusicPlay = true;
        SaveManager.Instance.Save();

        OnBGMPlay?.Invoke();
    }

    public void PauseBGM()
    {
        isMusicPlay = false;

        bgmAudioSource.Pause();

        SaveManager.Instance.data.isMusicPlay = false;
        SaveManager.Instance.Save();

        OnBGMPause?.Invoke();
    }

    public void SelectBGM(int index)
    {
        if (index >= BGMs.Count)
            return;

        currentBgmIndex = index;
        OnBGMSelected?.Invoke(BGMs[index]);

        if (isMusicPlay)
            PlayBGM();
        else
            PauseBGM();
    }

    public void SelectLastBGM() => SelectBGM((currentBgmIndex - 1) % BGMs.Count);
    public void SelectNextBGM() => SelectBGM((currentBgmIndex + 1) % BGMs.Count);
}

public enum UIAudioType
{
    DialoguePopText,
    SendBtn,
    ButtonPress
}

SaveManager

内部有一个Data可序列化类。外部可修改该类内部数据,并调用Save方法将其序列化到本地。

当前序列化的内容有历史对话,音量调整,大模型设置,光线调整。

protected override void Awake()
{
    base.Awake();

    //获取文件,读取所需的Json文件并序列化为Data
    dataPath = Application.persistentDataPath + "/" + dataName;
    if(File.Exists(dataPath))
    {
        using(StreamReader sr = new StreamReader(dataPath))
        {
            string json = sr.ReadToEnd();
            data = JsonConvert.DeserializeObject<Data>(json);
        }
    }
    else
    {
        data = new Data();
    }
}

public void Save()
{
    if (File.Exists(dataPath))
        File.Delete(dataPath);

    string json = JsonConvert.SerializeObject(data);
    File.WriteAllText(dataPath, json);
}

详细部分

AI

对外暴露最大回复数,模型的切换,内部根据官方文档实现了鉴权和websocket连接服务器。以及流式返回和最终返回的委托。

UI

通过Content Size Filter和Vertical Layout Group 和 Horizontal Layout Group 的多层嵌套,以及两种版本的气泡UI,通过同一个类对其进行显隐和更新的控制,实现了较少字体时气泡随字体更新改变自身大小,较多字体时气泡固定并允许内容滚动显示的功能。对外只需要调用Play并传入文字即可。

动画

使用Unity的Avatar动画重定向功能,将Mixamo骨骼动画映射到MMD骨骼上。

音频

使用枚举和字典来让外部确认要播放什么音频,通过在AudioManager下配置枚举值对应音频,再向外提供通过枚举值进行播放的接口

背景音乐通过将音频和音乐名,歌手名整合成一个类,更改时使用委托将组合类发送出去。UI可以根据接受到的类显示歌和歌手名

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

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

相关文章

C++基础(6)——模板初阶

目录 1.泛型编程 2.函数模板 2.1函数模板的概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.4.1隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 2.4.2显式实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 2.5 模板…

C++ WebDriver扩展

概述 WebDriver协议基于HTTP&#xff0c;使用JSON进行数据传输&#xff0c;定义了client与driver之间的通信标准。无论client的实现语言&#xff08;如Java或C#&#xff09;&#xff0c;都能通过协议中的endpoints准确指示driver执行各种操作&#xff0c;覆盖了Selenium的所有功…

Redis入门第五步:Redis持久化

欢迎继续跟随《Redis新手指南&#xff1a;从入门到精通》专栏的步伐&#xff01;在本文中&#xff0c;我们将深入探讨Redis的持久化机制&#xff0c;这是确保数据在服务器重启后不会丢失的关键功能。了解如何配置和使用不同的持久化方法&#xff0c;对于构建可靠的应用程序至关…

数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(二)

前言 在上一节中&#xff0c;主要介绍了 Navicat Premium 17 的使用以及创建一个基础的表格。当时只设置了给数据表补充字段&#xff0c;没有设置给数据表删除字段。现在补充一下。 ALTER TABLE student ADD test int(4); 给名为 student 的数据表添加 test 列&#xf…

CI/CD中的自动化测试:在持续集成/持续部署流程中引入网页自动化测试

目录 引言 一、CI/CD流程概述 1.1 什么是CI/CD 1.2 CI/CD流程的主要阶段 1.3 CI/CD的优点 二、自动化测试基础 2.1 自动化测试概述 2.2 自动化测试的作用 2.3 自动化测试的主要类型 三、Web自动化测试工具 3.1 Selenium 3.1.1 Selenium WebDriver常用API 3.1.2 示例…

ChatGPT+R语言助力生态环境数据统计分析!回归与混合效应模型、多元统计分析、结构方程模型(SEM)(lavaan)、Meta分析、贝叶斯回归等

从生态环境领域数据特点及统计方法介绍、GPT入门到GPT辅助R语言基础&#xff1b;数据准备及ggplot 绘图基础&#xff1b;回归和混合效应模型&#xff08;包含方差分析、协方差分析&#xff09;&#xff1b;多元统计分析&#xff08;排序、聚类和分组差异检验&#xff09;&#…

AI 搜索引擎工具集合

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏AI_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 前言 AI 搜索引擎 前言 在信息爆炸的时代&#xff0c;A 搜索引擎应运而生。它以强大的人工智能技术为支撑&#xff0…

攻防世界--->

做题笔记。 下载 查壳。 64ida打开。 先运行一下程序&#xff1a; 这里可以得到 输入为16个字符。超过会退出。 ida看&#xff1a; 查找字符&#xff1a; 最开始&#xff0c;做的时候&#xff0c;很懵&#xff0c;因为太多函数了。 静下心&#xff0c;只追踪我们需要的函数。…

FreeRTOS篇13:延时函数

一.什么是延时函数&#xff1f; 二.延时函数分类 相对延时&#xff1a;vTaskDelay 绝对延时&#xff1a;vTaskDelayUntil 三.vTaskDelay 与 HAL_Delay 的区别 vTaskDelay 作用是让任务阻塞&#xff0c;任务阻塞后&#xff0c;RTOS系统调用其它处于就绪状态的优先级最高的…

4S店4S店客户管理系统小程序(lw+演示+源码+运行)

社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。手机具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要求的操作系统是非…

C++设计模式之观察者模式

一、观察者模式概念 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式通常用于实现分布式事件处理系统,当一个对象(称为“主题”或“发布者”)改变状…

CMU 10423 Generative AI:lec14(Vision Language Model:CLIP、VQ-VAE)

文章目录 1 概述2 CLIP (Used in GPT-V)3 VQ-VAE (Used in Gemini)**VQ-VAE 详细笔记****VQ-VAE 的模块组成与数据流** **1. 输入数据****2. 编码器&#xff08;Encoder&#xff09;****2.1 编码器的作用****2.2 数据流与维度变化****2.3 编码器输出** **3. 量化器&#xff08;…

北京湃生艾瑞金助力实用临床树脂微创修复实战合肥站圆满结束

随着微创理念的普及以及口腔医学生物修复材料的运用&#xff0c;人们对于牙齿治疗舒适度和美观性有了更高的要求。口腔软硬组织修复、龋洞树脂修复、缺牙种植修复等系列材料在临床上的应用越来越广。而这些医学材料的运用&#xff0c;亦能有效帮助口腔治疗实现精准和微创的目标…

零信任如何增强网络物理系统 (CPS) 安全性

远程访问对于管理关键基础设施至关重要&#xff0c;因为它允许企业优化和扩展运营并保持效率。然而&#xff0c;它也带来了许多安全漏洞&#xff0c;而且随着连接设备数量的增加&#xff0c;这些漏洞只会越来越多。 到 2025 年&#xff0c;企业和消费者环境中的物联网设备数量…

数据架构图:从数据源到数据消费的全面展示

在这篇文章中&#xff0c;我们将探讨如何通过架构图来展示数据的整个生命周期&#xff0c;从数据源到数据消费。下面是一个使用Mermaid格式的示例数据架构图&#xff0c;展示了数据从源到消费的流动、处理和存储过程。 数据架构图示例 说明 数据源&#xff1a;分为内部数据源&…

Bean,看到P188没看了与maven

通过IOC容器获得Bean对象

职业技术学校开设无人机培训技术详解

职业技术学校开设无人机培训技术&#xff0c;是一个涉及多个方面的综合性教学过程。以下是对该培训技术的详细解析&#xff1a; 一、培训目标 无人机培训技术的目标在于培养学员掌握无人机的基本原理、组装调试、飞行操作、安全规范及维修保养等技能&#xff0c;使其成为具备…

周期信号的傅里叶级数表示

一、特征函数&#xff1a; 一个信号&#xff0c;若系统对该信号的输出响应仅是一个常数乘以输入&#xff0c;则为特征函数&#xff0c;幅度因子称为系统的特征值。 复指数是线性时不变系统的特征函数 复指数序列是离散时间线性时不变系统的特征函数 二、连续时间周期信号的…

macOS终端配置自动补全功能

如何在macOS终端中配置自动补全功能 终端是一个非常强大的工具&#xff0c;它可以用来完成很多任务&#xff0c;比如创建、复制、移动、删除文件&#xff0c;执行脚本和运行程序。不过它的默认设置对用户不太友好&#xff0c;作为开发者&#xff0c;我们通常习惯代码编辑器的辅…

Kubernetes-环境篇-01-mac开发环境搭建

1、brew安装 参考知乎文章&#xff1a;https://zhuanlan.zhihu.com/p/111014448 苹果电脑 常规安装脚本&#xff08;推荐 完全体 几分钟安装完成&#xff09; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"苹果电脑 极…