【owt】WebrtcNode, subscribe流程

news2024/12/23 19:55:28

subscribe流程

1. AmqpClient - New message received

2023-04-26T21:54:18.415  - DEBUG: AmqpClient - RpcServer New message received {
  method: 'subscribe',
  args: [
    'b149e44bb10d4e91bd162a8c6806ae7b',
    'webrtc',
    {
      transportId: 'b149e44bb10d4e91bd162a8c6806ae7b',
      tracks: [Array],
      controller: 'conference-aed26ef945c09ddf89b3@192.168.221.62_0',
      owner: 'xG6DXLHdXwky_E8eAAAD'
    }
  ],
  corrID: 28,
  replyTo: 'amq.gen-WtoELIbC4gJ1GfdYgkvSFA'
}

2. WebrtcNode - subscribe

webrtc_agent/webrtc/index.js

2023-04-26T21:54:18.416  - DEBUG: WebrtcNode - subscribe, operationId: b149e44bb10d4e91bd162a8c6806ae7b connectionType: webrtc options: {
  transportId: 'b149e44bb10d4e91bd162a8c6806ae7b',
  tracks: [
    {
      from: 'cb8fcfb93f174c24862feaa38915111a',
      mid: '0',
      type: 'audio',
      formatPreference: [Object]
    },
    {
      from: 'cb8fcfb93f174c24862feaa38915111a',
      mid: '1',
      parameters: {},
      type: 'video',
      formatPreference: [Object]
    }
  ],
  controller: 'conference-aed26ef945c09ddf89b3@192.168.221.62_0',
  owner: 'xG6DXLHdXwky_E8eAAAD'
}

from----参数的意义???,来自哪个transportId

 /*
     * For operations on type webrtc, publicTrackId is connectionId.
     * For operations on type internal, operationId is connectionId.
     */
    // functions: publish, unpublish, subscribe, unsubscribe, linkup, cutoff
    // options = {
    //   transportId,
    //   tracks = [{mid, type, formatPreference, scalabilityMode}],
    //   controller, owner, enableBWE
    // }
    // formatPreference = {preferred: MediaFormat, optional: [MediaFormat]}
    that.subscribe = function (operationId, connectionType, options, callback) {
        log.debug('subscribe, operationId:', operationId, 'connectionType:', connectionType, 'options:', options);
        if (mappingTransports.has(operationId)) {
            return callback('callback', {type: 'failed', reason: 'Connection already exists:'+operationId});
        }

        var conn = null;
        if (connectionType === 'webrtc') {
            if (!options.transportId) {
                // Generate a transportId

            }
            // 1. 创建 WebRTCConnection
              // options.transportId, connecid 类似
              // options.controller, 消息来自哪个confrence-agent,回传消息的时候用
              // options.owner, 用户id
            conn = createWebRTCConnection(options.transportId, options.controller, options.owner);
              // 2. addTrackOperation
            options.tracks.forEach(function trackOp(t) {
                conn.addTrackOperation(operationId, 'recvonly', t);
            });
            mappingTransports.set(operationId, options.transportId);
            if (options.enableBWE) {
                conn.enableBWE = true;
            }
            callback('callback', 'ok');
        } else {
            log.error('Connection type invalid:' + connectionType);
        }

        if (!conn) {
            log.error('Create connection failed', operationId, connectionType);
            callback('callback', {type: 'failed', reason: 'Create Connection failed'});
        }
    };

???callback 是哪里来的

2.1 createWebRTCConnection

创建WebRtcConnection

小节 3

2.2 addTrackOperation

小节 4

3.WebrtcNode - createWebRTCConnection——返回WrtcConnection

webrtc_agent/webrtc/index.js

 var createWebRTCConnection = function (transportId, controller, owner) {
        if (peerConnections.has(transportId)) {
            log.debug('PeerConnection already created:', transportId);
            return peerConnections.get(transportId);
        }
        var connection = new WrtcConnection({
            connectionId: transportId,
            threadPool: threadPool,
            ioThreadPool: ioThreadPool,
            network_interfaces: global.config.webrtc.network_interfaces,
            owner,
        }, function onTransportStatus(status) {
            notifyTransportStatus(controller, transportId, status);
        }, function onTrack(trackInfo) {
          // track的相关信息
            handleTrackInfo(transportId, trackInfo, controller);
        });

        // map 存放WebRtcconneciton
        peerConnections.set(transportId, connection);
        mappingPublicId.set(transportId, new Map());
        connection.controller = controller;
        return connection;
    };

3.1 peerConnections 成员

存放 WrtcConnection,transportId与connection一一对应

// Map { transportId => WrtcConnection }
var peerConnections = new Map();
----------------------------------       
peerConnections.set(transportId, connection);

3.2 mappingPublicId成员

// Map { transportId => Map { trackId => publicTrackId } }
var mappingPublicId = new Map();
--------------------------------------------
mappingPublicId.set(transportId, new Map());

createWebRTCConnection 的时候,只是创建了空的Map,而Map中存放的{ trackId => publicTrackId } },是在 handleTrackInfotrackInfo.type === 'track-added' 中存入的。

dist-debug/webrtc_agent/webrtc/index.js

 var handleTrackInfo = function (transportId, trackInfo, controller) {
        var publicTrackId;
        var updateInfo;
        if (trackInfo.type === 'track-added') {
            // Generate public track ID
            const track = trackInfo.track;
            publicTrackId = transportId + '-' + track.id;
            if (mediaTracks.has(publicTrackId)) {
                log.error('Conflict public track id:', publicTrackId, transportId, track.id);
                return;
            }
 ...
            mappingPublicId.get(transportId).set(track.id, publicTrackId);

 ...
 }

3.3 new WrtcConnection——创建rtc连接,并初始化

详细见小节4

3.4 handleTrackInfo

4. new WrtcConnection——创建rtc连接,并初始化

webrtc_agent/webrtc/wrtcConnection.js

 module.exports = function (spec, on_status, on_track) {
     ...


  wrtc = new Connection(wrtcId, threadPool, ioThreadPool, { ipAddresses });
  wrtc.callBase = new CallBase();
  // wrtc.addMediaStream(wrtcId, {label: ''}, direction === 'in');

  initWebRtcConnection(wrtc);
  return that;
};

4.1 new Connection——创建c++的WebrtcConnection

webrtc_agent/webrtc/connection.js

2023-04-26T21:54:18.416  - INFO: Connection - message: Connection, id: b149e44bb10d4e91bd162a8c6806ae7bd
class Connection extends EventEmitter {
  constructor (id, threadPool, ioThreadPool, options = {}) {
    super();
    log.info(`message: Connection, id: ${id}`);
    this.id = id;
    this.threadPool = threadPool;
    this.ioThreadPool = ioThreadPool;
    this.mediaConfiguration = 'default';
    this.mediaStreams = new Map();
    this.initialized = false;
    this.options = options;
    this.ipAddresses = options.ipAddresses || '';
    this.trickleIce = options.trickleIce || false;
    this.metadata = this.options.metadata || {};
    this.isProcessingRemoteSdp = false;
    this.ready = false;
// native 的WebRtcConnection
    this.wrtc = this._createWrtc();
  }
...
}

4.1.1 ---------Connection._createWrtc——创建webrtc connection

 _createWrtc() {
    var wrtc = new addon.WebRtcConnection(
      this.threadPool, this.ioThreadPool, this.id,
      global.config.webrtc.stunserver,
      global.config.webrtc.stunport,
      global.config.webrtc.minport,
      global.config.webrtc.maxport,
      false, //this.trickleIce,
      this._getMediaConfiguration(this.mediaConfiguration),
      false,
      '', // turnserver,
      '', // turnport,
      '', //turnusername,
      '', //turnpass,
      '', //networkinterface
      this.ipAddresses
    );

    return wrtc;
  }

4.1.2 NAN_METHOD(WebRtcConnection::New)

source/agent/webrtc/rtcConn/WebRtcConnection.cc

NAN_METHOD(WebRtcConnection::New) {
    ...
    WebRtcConnection* obj = new WebRtcConnection();
    obj->me = std::make_shared<erizo::WebRtcConnection>(worker, io_worker, wrtcId, iceConfig,
                                                        rtp_mappings, ext_mappings, obj);

    uv_async_init(uv_default_loop(), &obj->async_, &WebRtcConnection::eventsCallback);
    obj->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
    obj->asyncResource_ = new Nan::AsyncResource("WebRtcConnectionCallback");
    ...
}

4.1.3 erizo::WebRtcConnection::WebRtcConnection

source/agent/webrtc/rtcConn/erizo/src/erizo/WebRtcConnection.cpp

2023-04-26 21:54:18,417  - INFO: WebRtcConnection - 
  id: b149e44bb10d4e91bd162a8c6806ae7b,  
  message: constructor,
  stunserver: , 
    stunPort: 0, 
    minPort: 0, 
    maxPort: 0
WebRtcConnection::WebRtcConnection(std::shared_ptr<Worker> worker, std::shared_ptr<IOWorker> io_worker,
    const std::string& connection_id, const IceConfig& ice_config, const std::vector<RtpMap> rtp_mappings,
    const std::vector<erizo::ExtMap> ext_mappings, WebRtcConnectionEventListener* listener) :
    connection_id_{connection_id},
    audio_enabled_{false}, video_enabled_{false}, bundle_{false}, conn_event_listener_{listener},
    ice_config_{ice_config}, rtp_mappings_{rtp_mappings}, extension_processor_{ext_mappings},
    worker_{worker}, io_worker_{io_worker},
    remote_sdp_{std::make_shared<SdpInfo>(rtp_mappings)}, local_sdp_{std::make_shared<SdpInfo>(rtp_mappings)},
    audio_muted_{false}, video_muted_{false}, first_remote_sdp_processed_{false}
    {
  ELOG_INFO("%s message: constructor, stunserver: %s, stunPort: %d, minPort: %d, maxPort: %d",
      toLog(), ice_config.stun_server.c_str(), ice_config.stun_port, ice_config.min_port, ice_config.max_port);
  stats_ = std::make_shared<Stats>();
  // distributor_ = std::unique_ptr<BandwidthDistributionAlgorithm>(new TargetVideoBWDistributor());
  global_state_ = CONN_INITIAL;

  trickle_enabled_ = ice_config_.should_trickle;
  slide_show_mode_ = false;

  sending_ = true;
}

4.2 WrtcConnection - initWebRtcConnection

webrtc_agent/webrtc/wrtcConnection.js

/*
   * Given a WebRtcConnection waits for the state CANDIDATES_GATHERED for set remote SDP.
   */
    // wrtc 是Connection,Connection 继承于EventEmitter
  var initWebRtcConnection = function (wrtc) {
    // EventEmitter.on()用于监听事件
    // 在c++从回调到js中,就是在Connection.init中触发
    // Connection wrtc
    wrtc.on('status_event', (evt, status) => {
      if (evt.type === 'answer') {
        processAnswer(evt.sdp);

        const message = localSdp.toString();
        log.debug('Answer SDP', message);
        on_status({type: 'answer', sdp: message});

      } else if (evt.type === 'candidate') {
        let message = evt.candidate;
        networkInterfaces.forEach((i) => {
          if (i.ip_address && i.replaced_ip_address) {
            message = message.replace(new RegExp(i.ip_address, 'g'), i.replaced_ip_address);
          }
        });
        on_status({type: 'candidate', candidate: message});

      } else if (evt.type === 'failed') {
        log.warn('ICE failed, ', status, wrtc.id);
        on_status({type: 'failed', reason: 'Ice procedure failed.'});

      } else if (evt.type === 'ready') {
        log.debug('Connection ready, ', wrtc.wrtcId);
        on_status({
          type: 'ready'
        });
      }
    });

    // Connection wrtc
    wrtc.init(wrtcId);
  };

4.2.1 Connection/EventEmitter.on——监听事件

4.2.2 Connection.init——初始化c++的WebRtcConnection

webrtc_agent/webrtc/connection.js

2023-04-26T21:54:18.417  - DEBUG: Connection - message: 
Init Connection, 
connectionId: b149e44bb10d4e91bd162a8c6806ae7b 
{"ipAddresses":[]}
init(streamId) {
    if (this.initialized) {
      return false;
    }
    const firstStreamId = streamId;
    this.initialized = true;
    log.debug(`message: Init Connection, connectionId: ${this.id} `+
              `${logger.objectToLog(this.options)}`);
    this.sessionVersion = 0;

    // WebRtcConnection c++ wrapper, 调用c++
    this.wrtc.init((newStatus, mess, streamId) => {
      log.debug('message: WebRtcConnection status update, ' +
               'id: ' + this.id + ', status: ' + newStatus +
                ', ' + logger.objectToLog(this.metadata) + mess);
      switch(newStatus) {
        case CONN_INITIAL:
           // 触发4.2.1
          this.emit('status_event', {type: 'started'}, newStatus);
          break;

        case CONN_SDP_PROCESSED:
          this.isProcessingRemoteSdp = false;
          // this.latestSdp = mess;
          // this._maybeSendAnswer(newStatus, streamId);
          break;

        case CONN_SDP:
          this.latestSdp = mess;
          this._maybeSendAnswer(newStatus, streamId);
          break;

        case CONN_GATHERED:
          this.alreadyGathered = true;
          this.latestSdp = mess;
          this._maybeSendAnswer(newStatus, firstStreamId);
          break;

        case CONN_CANDIDATE:
          mess = mess.replace(this.options.privateRegexp, this.options.publicIP);
          this.emit('status_event', {type: 'candidate', candidate: mess}, newStatus);
          break;

        case CONN_FAILED:
          log.warn('message: failed the ICE process, ' + 'code: ' + WARN_BAD_CONNECTION +
                   ', id: ' + this.id);
          this.emit('status_event', {type: 'failed', sdp: mess}, newStatus);
          break;

        case CONN_READY:
          log.debug('message: connection ready, ' + 'id: ' + this.id +
                    ', ' + 'status: ' + newStatus + ' ' + mess + ',' + streamId);
          if (!this.ready) {
            this.ready = true;
            this.emit('status_event', {type: 'ready'}, newStatus);
          }
          break;
      }
    });
    if (this.options.createOffer) {
      log.debug('message: create offer requested, id:', this.id);
      const audioEnabled = this.options.createOffer.audio;
      const videoEnabled = this.options.createOffer.video;
      const bundle = this.options.createOffer.bundle;
        // WebRtcConnection c++ wrapper, 调用c++
      this.wrtc.createOffer(videoEnabled, audioEnabled, bundle);
    }
    // 触发4.2.1
    this.emit('status_event', {type: 'initializing'});
    return true;
  }

4.2.3 NAN_METHOD(WebRtcConnection::init)

source/agent/webrtc/rtcConn/WebRtcConnection.cc

NAN_METHOD(WebRtcConnection::init) {
  WebRtcConnection* obj = Nan::ObjectWrap::Unwrap<WebRtcConnection>(info.Holder());
  std::shared_ptr<erizo::WebRtcConnection> me = obj->me;

  obj->eventCallback_ = new Nan::Callback(info[0].As<Function>());
  bool r = me->init();

  info.GetReturnValue().Set(Nan::New(r));
}

4.2.4 erizo::WebRtcConnection::init()

source/agent/webrtc/rtcConn/erizo/src/erizo/WebRtcConnection.cpp

bool WebRtcConnection::init() {
    maybeNotifyWebRtcConnectionEvent(global_state_, "");
    return true;
}

状态通知给js层,

4.2.5 erizo::WebRtcConnection::maybeNotifyWebRtcConnectionEvent

void WebRtcConnection::maybeNotifyWebRtcConnectionEvent(const WebRTCEvent& event, const std::string& message,
    const std::string& stream_id) {
  boost::mutex::scoped_lock lock(event_listener_mutex_);
  if (!conn_event_listener_) {
      return;
  }
  conn_event_listener_->notifyEvent(event, message, stream_id);
}

—> 4.2.2

this.wrtc.init((newStatus, mess, streamId) => {
      log.debug('message: WebRtcConnection status update, ' +
               'id: ' + this.id + ', status: ' + newStatus +
                ', ' + logger.objectToLog(this.metadata) + mess);
      switch(newStatus) {
        case CONN_INITIAL:
          this.emit('status_event', {type: 'started'}, newStatus);
          break;
          ....
      });

—> 4.2.1

wrtc.on('status_event', (evt, status) => {
  ...
});

4.2.6 log

2023-04-26T21:54:18.419  - DEBUG: Connection - message: 
WebRtcConnection status update, 
id: b149e44bb10d4e91bd162a8c6806ae7b, 
status: 101, 
{}

CONN_INITIAL = 101

4.2.7 status

const CONN_INITIAL        = 101;
const CONN_STARTED        = 102;
const CONN_GATHERED       = 103;
const CONN_READY          = 104;
const CONN_FINISHED       = 105;
const CONN_CANDIDATE      = 201;
const CONN_SDP            = 202;
const CONN_SDP_PROCESSED  = 203;
const CONN_FAILED         = 500;
const WARN_BAD_CONNECTION = 502;

5. WrtcConnection.addTrackOperation

// option = {mid, type, formatPreference, scalabilityMode}
  that.addTrackOperation = function (operationId, sdpDirection, option) {
    var ret = false;
    var {mid, type, formatPreference, scalabilityMode} = option;
    if (!operationMap.has(mid)) {
      log.debug(`MID ${mid} for operation ${operationId} add`);
      const enabled = true;
    // map
      operationMap.set(mid, {operationId, type, sdpDirection, formatPreference, enabled});
      if (scalabilityMode) {
        operationMap.get(mid).scalabilityMode = scalabilityMode;
      }
      ret = true;
    } else {
      log.warn(`MID ${mid} has mapped operation ${operationMap.get(mid).operationId}`);
    }
    return ret;
  };

5.1 WrtcConnection.operationMap

  // mid => { operationId, sdpDirection, type, formatPreference, rids, enabled, finalFormat }
  var operationMap = new Map();

6. 流程图

在这里插入图片描述

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

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

相关文章

C4网络技术挑战赛 智慧园区方案分析

添加链接描述 智慧园区网络 市场现状: 网络与应用系统多厂商、多平台、多系统,导致IT部门管理运维复杂&#xff0c;效率低下. 统一运维管理&#xff1f; 无线网络与物联网的双网合一&#xff1f; ps&#xff1a; 无线网络(英语:Wireless network)指的是任何型式的无线电计…

【Mysql数据库从0到1】-入门基础篇--mysql基本使用

【Mysql数据库从0到1】-入门基础篇--mysql基本使用 &#x1f53b;一、Mysql5.7 VS Mysql8.0 详解1.1 ⛳字符编码1.2 ⛳用户的创建与授权1.3 ⛳ 认证插件1.4 ⛳ 隐藏索引1.5 ⛳ 持久化设置1.6 ⛳ 通用表表达式&#xff08;Common Table Expressions&#xff09;1.7 ⛳ 性能提升1…

redis缓存单体服务测试本地锁失效问题

测试1&#xff1a;锁释放之后向redis缓存存入数据 //TODO 产生堆外内存溢出 OutOfDirectMemoryError//gulimall.com查询分类Overridepublic Map<String, List<CategoryLevel2Vo>> getCatelogJson() {/*** 问题 &#xff1a;解决办法* 1.缓存穿透 高并发情况下查询缓…

Java学习方式分享

哈喽&#xff0c;大家好呀&#xff0c;好久不见&#xff01;咱依然是那个腼腆害羞内向社恐文静、唱跳rap篮球都不大行的【三婶er】 坦白地说&#xff0c;今天是偶然看到C站这个活动的&#xff0c;这个主题我颇有感触&#xff0c;刚学java时的场景&#xff0c;历历在目。所以今天…

ChatGPT常见的报错解决方法(全网最全解决方法)

因为最近在使用ChatGPT的过程中&#xff0c;时常会出现一些错误提示&#xff0c;为了方便自己快速解决问题&#xff0c;所以也搜集了一些其他博主的解决方法&#xff0c;以下是整理的内容。 目录 1、拒绝访问 2、Access denied错误 3、We have detected suspicious 错误 4…

leetcode_19_相同的树

bool isSameTree(struct TreeNode* p, struct TreeNode* q){if(pNULL && qNULL)return true;//其中一个为空if(pNULL || qNULL)return false;//都不为空,且首节点的值不相等if(p->val ! q->val)return false;//p和q的值相等&#xff0c;分别比较左子树和右子树re…

如何使用debugHunter发现隐藏调试参数和Web应用程序敏感信息

关于debugHunter debugHunter是一款针对Web应用程序隐藏调试参数和敏感信息的识别扫描工具,该工具本质上是一个Chrome扩展,可以帮助广大研究人员扫描目标Web应用程序/网站以查找调试参数,并在发现了包含修改响应的URL时发送通知。该扩展利用了二分查找算法来有效地确定导致…

《基于Linux物联网综合项目》常见问题汇总fae

关于该课程说明 1&#xff09;本课程目标 通过web浏览器访问服务器&#xff0c;实现登录、注册、数据库操作、远程操控硬件、采集环境信息、远程监控、拍照、图片显示等功能。 将单片机、linux、html、摄像头、数据库等知识点融入到一个项目中。 2&#xff09;什么群体适合学…

JVM 虚拟机栈介绍

一、虚拟机栈&#xff08;VM Stack&#xff09; 1.1&#xff09;什么是虚拟机栈 虚拟机栈是用于描述java方法执行的内存模型。 每个java方法在执行时&#xff0c;会创建一个“栈帧&#xff08;stack frame&#xff09;”&#xff0c;栈帧的结构分为“局部变量表、操作数栈、动态…

JavaScript实现以数组方式输入数值,输出最大的数的代码

以下为实现以数组方式输入数值&#xff0c;输出最大的数的程序代码和运行截图 目录 前言 一、以数组方式输入数值&#xff0c;输出最大的数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找…

基于html+css的图展示107

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

职业规划-论软件迭代变化和个人知识更新

职业规划-论软件迭代变化和个人知识更新 目录概述需求&#xff1a; 设计思路实现思路分析1.历程2.第一份工作3.后来4.BK毕业5.实习 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardnes…

冈萨雷斯DIP第6章知识点

6.1 彩色基础 区分不同颜色的特性&#xff1f; 区分不同颜色的特性通常是亮度、色调和饱和度。 亮度&#xff1a;亮度体现的是发光强度的消色概念&#xff08;不包含颜色的概念&#xff09;色调&#xff1a;表示被观察者感知的主导色&#xff0c;通常是混合光波中与主波长相关的…

深入理解设计原则之里氏替换原则(LSP)【软件架构设计】

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 深入理解设计模式系列 高级C并发线程编程 LSP&#xff1a;里氏替换原则 系列文章目录1、里氏替换原则的定义和解读2、里氏替换原则可以用于哪些设计模式中&#xff1f;3、如何使用里氏替换原则来降低代码耦合度&a…

《微服务架构设计模式》第一章 逃离单体地狱

内容总结自《微服务架构设计模式》 逃离单体地狱 一、单体架构1、好处2、弊端 二、微服务架构1、定义2、好处3、弊端 三、模式的概念1、定义2、构成3、引申微服务 一、单体架构 1、好处 易于对应用程序进行大规模的更改&#xff1a;可以更改代码和数据库模式&#xff0c;然后…

华为OD机试真题 Java 实现【单词倒序】【2023Q1 100分】,附详细解题思路

一、题目描述 输入单行英文句子&#xff0c;里面包含英文字母&#xff0c;空格以及.? 三种标点符号&#xff0c;请将句子内每个单词进行倒序&#xff0c;并输出倒序后的语句。 二、输入描述 输入字符串S&#xff0c;S的长度1≤N≤100。 三、输出描述 输出逆序后的字符串 …

有道云笔记也挺速度,也搞了个AI助手,能抗衡Notion AI?

前言 小编平时做技术笔记的时候&#xff0c;经常使用到的软件就是有道云笔记&#xff0c;最近无意间发现&#xff0c;笔记编写的页面中&#xff0c;竟然集成了AI助手&#xff01;网易有道可真是低调&#xff01;毕竟最近AI圈大火&#xff0c;竟然没有蹭一波热度&#xff0c;直…

Spring Security 核心解读(二)自定义认证授权体系

自定义认证授权体系 概述自定义认证定义登录接口配置 Security 放行策略定义通用登录过滤器并将其配置到 Security 过滤器链上定义资源接口在 Security 授权设置中放行启动项目 结尾 概述 以前使用Spring Security 时&#xff0c;基本都是按部就班参考文档开发。 基本是从 Use…

【Python开发】FastAPI 06:处理错误

某些情况下&#xff0c;有必要向客户端&#xff08;包括前端浏览器、其他应用程序、物联网设备等&#xff09;返回错误提示&#xff0c;以便客户端能够了解错误的类型&#xff0c;从而做出应对。 目录 1 默认处理 1.1 错误介绍 1.2 使用 HTTPException 2 自定义处理 2.1 自…

论旅行之收获2

论旅行之收获2 概况站点第一站&#xff1a;北京市大兴区大兴机场基本情况吐槽小小趣事 第二站&#xff1a;云南省昆明长水机场云南省昆明市五华区基本概况经济分析 第三站&#xff1a;昆明站大理站云南省大理白族自治州大理市下关基本情况 第四站&#xff1a;云南省大理白族自治…