一、Postman检查API接口是否支持

        1.“HTTP Request” 来创建一个新的请求。——请求构建界面,这是你进行所有 API 调用的地方。
        2.设置请求方法和 URL:

        选择请求方法: 在 URL 输入框左侧,有一个下拉菜单。点击它,选择你想要测试的 HTTP 请求方法(例如:GET, POST, PUT, DELETE 等)。

GET:获取数据

        区别: GET 请求通常用于从服务器获取或读取数据。它不应该对服务器上的数据产生任何修改。因此,GET 被认为是“安全”和“幂等”的。安全(Safe): 发送 GET 请求不会改变服务器的状态或数据。幂等(Idempotent): 多次发送相同的 GET 请求,结果都将是相同的,不会产生副作用。

工作原理:

  1. 客户端(浏览器、Postman 等)将请求参数附加在 URL 的查询字符串中(.../users?id=123)。

  2. 请求头(Headers)和 URL 组成请求信息发送给服务器。

  3. 服务器根据 URL 和参数定位资源,然后返回资源的表示(通常是 JSON 或 XML 格式)给客户端。

  4. 因为参数在 URL 中,所以 GET 请求的长度有限制,并且不适合传输敏感数据。

POST:创建数据

        区别: POST 请求用于向服务器提交数据以创建新资源。它会改变服务器的状态,因此不安全非幂等不安全(Unsafe): 发送 POST 请求可能会改变服务器上的数据。非幂等(Non-Idempotent): 多次发送相同的 POST 请求,可能会导致创建多个重复的资源。例如,多次提交一个订单表单,可能会创建多份订单。

工作原理:

  1. 客户端将请求数据放在请求的**主体(Body)**中发送给服务器。请求体可以包含任何类型的数据,如表单数据、JSON、文件等,因此没有长度限制。

  2. 服务器接收到请求体中的数据后,处理并创建一个新资源。

  3. 通常,服务器在创建成功后会返回一个 201 Created 的状态码,并在响应头中包含新创建资源的 URL。

PUT:更新数据

        区别: PUT 请求用于更新或替换服务器上已存在的资源。它与 POST 的主要区别在于幂等性不安全(Unsafe): 会修改服务器上的数据。幂等(Idempotent): 多次发送相同的 PUT 请求,结果与发送一次请求是相同的。如果资源不存在,它通常会创建新资源。

工作原理:

  1. 客户端通过 URL 明确指定要更新的资源(例如:.../users/123)。

  2. 客户端将完整的、更新后的资源表示放在请求主体中发送给服务器。如果请求中缺少某个字段,服务器通常会将其删除或设为空。

  3. 服务器用请求主体中的数据完全替换 URL 指定的资源。

  4. 成功后,服务器通常返回 200 OK204 No Content 状态码。

DELETE:删除数据

        区别: DELETE 请求用于删除服务器上指定的资源不安全(Unsafe): 会改变服务器上的数据。幂等(Idempotent): 多次发送相同的 DELETE 请求,结果与发送一次是相同的。第一次请求会删除资源,后续请求会返回“资源未找到”或成功,但不会产生新的副作用。

工作原理:

  1. 客户端通过 URL 明确指定要删除的资源(例如:.../users/123)。

  2. 服务器收到请求后,会删除该资源。

  3. 成功后,服务器通常返回 200 OK204 No Content 状态码。

        输入请求 URL: 在请求方法右侧的输入框中,输入你要测试的 API 接口的完整 URL。例如:https://api.example.com/users

       3. API 接口通常需要一些参数来完成请求。Postman 提供了多种添加参数的方式,根据 API 的要求来选择。
  1. URL 参数(Params):

    • 如果你的 API 需要在 URL 中传递参数(例如:https://api.example.com/users?id=123),你可以在 URL 输入框下方找到 "Params" 选项卡。

    • 点击这个选项卡,Postman 会自动将你在 URL 中输入的参数拆分出来,或者你也可以在这里手动添加键值对(Key-Value pairs)。

  2. 请求头(Headers):

    • 许多 API 接口需要通过请求头来传递信息,比如 认证令牌(Authorization Token)内容类型(Content-Type)

    • 点击 "Headers" 选项卡,添加你需要的键值对。例如:

      • Key: Authorization

      • Value: Bearer your_token_here

二、Android中APIService调用

1.Retrofit 接口定义,用于调用 Spotify 的 RESTful API。通过接口interface调用

2.Retrofit的注释详解

        它们告诉 Retrofit 如何构建 HTTP 请求。例如下列

第一个示例

@GET("v1/me/top/{type}")

@GET:指的是HTTP请求方式为GET

其中的"v1/me/top/{type}":这是一个相对 URL 路径。Retrofit 会将其与您在构建 Retrofit 实例时提供的基本 URL(例如 https://api.spotify.com/)拼接起来,形成完整的请求地址。

object RetrofitClient {private const val BASE_URL = "https://api.spotify.com/"// 配置 OkHttpClientprivate val okHttpClient = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {level = HttpLoggingInterceptor.Level.BODY // 打印所有请求和响应的详细日志}).connectTimeout(30, TimeUnit.SECONDS) // 连接超时.readTimeout(30, TimeUnit.SECONDS)    // 读取超时.writeTimeout(30, TimeUnit.SECONDS)   // 写入超时.build()// 创建 Retrofit 实例val spotifyApiService: SpotifyApiService by lazy {Retrofit.Builder().baseUrl(BASE_URL).client(okHttpClient).addConverterFactory(GsonConverterFactory.create()).build().create(SpotifyApiService::class.java)}
}

 第二个示例

suspend fun getTopItems(@Header("Authorization") authorization: String, // Access Token@Path("type") type: String, // "artists" 或 "tracks"@Query("time_range") timeRange: String? = null, // long_term, medium_term, short_term@Query("limit") limit: Int? = null, // 数量@Query("offset") offset: Int? = null // 偏移量): TopItemsResponse<Item> // 使用泛型 <*> 因为 items 可以是 Artist 或 Track

        @Header:用于在 HTTP 请求头中添加键值对。"Authorization":这是 HTTP 请求头的键名,通常用于传递身份验证令牌。authorization: String:这个参数的值会通过 @Header 注解,被添加到请求头中。您需要在这里传入您的 Access Token,例如 "Bearer BQC..."

        @Path:用于将方法参数的值插入到 URL 路径中。在这里,它会将方法参数 type 的值(如 "artists")替换掉 URL 路径中的 {type} 占位符。

        @Query:用于向 URL 中添加查询参数(Query Parameters)。"time_range":这是查询参数的键名。Retrofit 会将其与方法参数的值组合成 ?time_range=... 这样的形式。

        suspend fun ... : TopItemsResponse<Item>

        TopItemsResponse:这是一个自定义的数据类(或接口),用于映射 API 返回的 JSON 响应。Retrofit 会使用一个 JSON 转换器(如 Moshi 或 Gson)将 JSON 数据自动解析成这个对象。

        <Item>:这是一个泛型(Generic)。在 getTopItems 方法中,使用了泛型 <Item>,因为 items 列表中的对象可以是 Artist,也可以是 Track。这种设计使得代码更加灵活,但需要您在调用时指定具体的类型,或者在运行时进行类型检查。

        在 getTopArtistItems 方法中,更具体地指定了 <Artist>,这意味着这个方法专门用于获取艺术家数据,返回的 items 列表将是 Artist 对象的列表。这是一种更类型安全的做法,通常更推荐。

、Android中API的数据模型创建

@Serializable 的作用是告诉 Kotlinx.serialization 库,这个数据类需要被序列化和反序列化

  • 序列化 (Serialization):将 Kotlin 对象(例如 TopItemsResponse 的实例)转换成另一种格式,比如 JSON 字符串。

  • 反序列化 (Deserialization):将 JSON 字符串转换回 Kotlin 对象。

        在 Android 开发中,当您从网络 API 获取数据时,通常会得到一个 JSON 格式的字符串。为了在您的应用中方便地使用这些数据,您需要将其转换为一个 Kotlin 对象,这就是反序列化。当您需要向服务器发送数据时(例如 POST 或 PUT 请求),您需要将 Kotlin 对象转换为 JSON 字符串,这就是序列化

@Serializable
data class TopItemsResponse<T>(val href: String,val limit: Int,val next: String?,val offset: Int,val previous: String?,val total: Int,val items: List<T> // 泛型 T 可以是 Artist 或 Track
)

@Serializable 解决了以下几个核心问题:

  1. 自动化数据转换

    • 在没有 @Serializable 的情况下,您需要手动编写代码来解析 JSON 字符串,并为每个字段赋值。这非常繁琐且容易出错。

    • 有了 @Serializable,您只需要定义好数据类,Kotlinx.serialization 库会为您自动完成 JSON 和 Kotlin 对象之间的转换。

  2. 与 Retrofit 的无缝集成

    • 当您使用 Retrofit 调用 API 时,您可以将 TopItemsResponse 作为返回类型。Retrofit 会检测到您使用了 Kotlinx.serialization 的 @Serializable 注解,并自动使用其 JSON 转换器来解析 API 返回的 JSON 数据,并将其填充到 TopItemsResponse 对象中。

    • 它使得您的网络请求代码非常简洁,您不需要关心底层的数据解析细节。

  3. 类型安全和泛型支持

    • 在您的 TopItemsResponse<T> 例子中,items: List<T> 是一个泛型列表。

    • Kotlinx.serialization 能够正确处理这个泛型,它会根据您在调用时提供的具体类型(例如 ArtistTrack)来自动解析 items 列表中的每个对象。

之后的对象参数比如Image,Item都用data class数据类来生成模型

四、Android中MediaSession数据调用

1.MediaItemUtils 工具类

        它主要用于将不同类型的数据模型(如 AlbumTrackArtist 等)转换为 androidx.media3.common.MediaItem 对象。这个工具类通常在媒体播放应用中用于构建和管理媒体库。

        MediaItem: MediaItem 是一个媒体库中条目的抽象表示。它可以代表一首歌曲、一个专辑、一个艺术家、一个播放列表或任何其他可播放或可浏览的内容。

  • 它包含一个唯一的 mediaId

  • 它包含一个 MediaMetadata 对象来描述该媒体条目。

  • 它还可以包含一个 sourceUri,指向实际的媒体文件或流。

        MediaMetadata: MediaMetadata 包含媒体条目的详细信息,如标题、艺术家、专辑、封面图片 URI (artworkUri)、媒体类型 (mediaType) 等。

  • MediaMetadata.Builder 用于创建和配置 MediaMetadata 对象。

fun buildMediaItem(title: String,mediaId: String,isPlayable: Boolean,isBrowsable: Boolean,mediaType: @MediaMetadata.MediaType Int,subtitleConfigurations: List<MediaItem.SubtitleConfiguration> = mutableListOf(),album: String? = null,artist: String? = null,genre: String? = null,sourceUri: Uri? = null,imageUri: Uri? = null,extras: Bundle? = null,subTitle: String = "",): MediaItem {val metadata =MediaMetadata.Builder().setAlbumTitle(album).setTitle(title).setSubtitle(subTitle).setArtist(artist).setGenre(genre).setIsBrowsable(isBrowsable).setIsPlayable(isPlayable).setArtworkUri(imageUri).setMediaType(mediaType).setExtras(extras).build()return MediaItem.Builder().setMediaId(mediaId).setSubtitleConfigurations(subtitleConfigurations).setMediaMetadata(metadata).setUri(sourceUri).build()}

之后是扩展函数来构建MediaItem的模式

        MediaItemUtils 中定义了多个扩展函数(如 Album.toMediaItem()Track.toMediaItem() 等)以及一个通用的 buildMediaItem() 函数。

buildMediaItem() 函数: 这是一个通用的辅助函数,它接受各种参数(标题、mediaIdisPlayable 等),然后使用 MediaMetadata.BuilderMediaItem.Builder 来构建一个 MediaItem

  • MediaMetadata.BuildersetTitle()setArtist()setArtworkUri() 等方法用于设置媒体的元数据。

  • MediaItem.BuildersetMediaId()setMediaMetadata()setUri() 等方法用于构建最终的 MediaItem

        扩展函数(Extension Functions): Kotlin 的扩展函数能力在这里得到了很好的应用。例如,Album.toMediaItem() 允许你在一个 Album 对象上直接调用 .toMediaItem() 方法,使其看起来像是 Album 类本身的一个成员函数。

  • 这些扩展函数的作用是将特定数据模型(如 AlbumTrack)转换为通用的 MediaItem 格式,以便媒体播放器(如 ExoPlayer)能够统一处理它们。

fun Album.toMediaItem(): MediaItem {return buildMediaItem(title = this.name,mediaId = this.id,isPlayable = true,isBrowsable = true,mediaType = MediaMetadata.MEDIA_TYPE_ALBUM,imageUri = getUri(images[1].url),sourceUri = this.uri.toUri(),extras = Bundle().apply {putString(MediaItemUtils.MEDIA_EXTTERNAL_URLS, this@toMediaItem.external_urls.spotify)}).setMediaItemType(MediaItemUtils.MEDIA_ITEM_TYPE_NORMAL)}

2.SpotifyDataRepository——Spotify数据仓库

        它封装了对 Spotify Web API 的网络请求,并将返回的数据转换为 MediaItem 对象,以便在媒体播放器应用中使用。

Retrofit 和 Spotify API 服务

suspend fun fetchTopTracks(timeRange: String = "medium_term", limit: Int = 10, offset: Int = 0):List<MediaItem>  {val result = RetrofitClient.spotifyApiService.getTopItems(authorization = "Bearer ${SPUtils.getString(SPUtils.ACCESS_TOKEN)}",type = "tracks",timeRange = timeRange,limit = limit,offset = offset)return result.items.map { it.album.toMediaItem() }}

这段代码的核心是与 Retrofit 的集成,它是一个用于 Android 和 Java 的类型安全的 HTTP 客户端。

  • RetrofitClient.spotifyApiService: 这是一个 Retrofit 接口的实例,它定义了所有与 Spotify Web API 交互的请求方法。每个方法都使用注解(如 @GET)来指定请求类型和 URL。

  • suspend fun: 所有的网络请求函数都使用了 suspend 关键字,这表示它们是 suspend 函数suspend 函数是 Kotlin Coroutines 的核心,它们可以暂停和恢复执行,使得异步操作(如网络请求)可以在不阻塞主线程的情况下进行。

之后的网络请求和数据转换流程

每个 fetch 函数都遵循一个标准的模式:

  1. 发起网络请求: 调用 RetrofitClient.spotifyApiService 接口中的相应方法,并传入必要的参数(如 authorization token、idlimit 等)。

  2. 等待响应: 由于这些函数是 suspend 的,它们会暂停执行直到 Retrofit 收到来自 Spotify API 的响应。

  3. 处理结果:

    • val result = RetrofitClient.spotifyApiService...: Retrofit 会自动将 JSON 响应解析成预定义的 Kotlin 数据类对象(例如,result.itemsresult.tracks.items)。

    • .map { ... }: 这是一个 Kotlin 集合的转换操作。它遍历 Retrofit 返回的数据列表(例如 result.items),并对每个元素应用一个转换函数。

    • .toMediaItem(): 这是上一个代码片段中定义的扩展函数。它负责将 Spotify 的数据模型(如 AlbumTrack)转换为 MediaItem 对象。这个步骤至关重要,因为它将网络获取的原始数据转换成媒体播放器能够理解和使用的格式。

  4. 返回 MediaItem 列表: 函数最终返回一个 List<MediaItem>,这个列表可以直接提供给 Media3 媒体会话或播放列表。

3.onGetChildren——媒体库服务(Media Library Service)处理客户端(如 Android Auto、Google Assistant 或其他媒体浏览器)请求其子项列表的入口点。

1. MediaLibraryServiceonGetChildren

  • MediaLibraryService: 这是 Media3 库中的一个服务,用于向客户端公开一个可浏览的媒体库树。客户端可以通过它来获取媒体内容,而不仅仅是播放。

  • onGetChildren(): 这个方法是 MediaLibraryService 的核心。当客户端(例如,汽车信息娱乐系统)想要浏览某个媒体项的子项时,它会调用此方法,并传入父项的 parentId

    • 你的任务是在这个方法中,根据 parentId 决定要返回哪些子媒体项。

@OptIn(UnstableApi::class)override fun onGetChildren(session: MediaLibraryService.MediaLibrarySession,browser: MediaSession.ControllerInfo,parentId: String,page: Int,pageSize: Int,params: MediaLibraryService.LibraryParams?,): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {logd(TAG) { "jidouauto cloud music onGetChildren beforeinit parentId: $parentId" }return scope.future {when(parentId){SPOTIFY_MUSIC_ROOT_ID -> {LibraryResult.ofItemList(dataRepository.getSpotifyMainTabs(), params)}MEDIA_TABS_RECOMMEND ->{val tracks = dataRepository.fetchTopTracks("short_term",10,0)val artists = dataRepository.fetchTopArtistTracks("short_term",10,0)val resultList= mutableListOf<MediaItem>()resultList.addAll(tracks)resultList.addAll(artists)LibraryResult.ofItemList(resultList,params)}MEDIA_TABS_MINE ->{val result = dataRepository.fetchPlaylistTracks(20,0)LibraryResult.ofItemList(result,params)}MEDIA_ALBUM_PLAY_LIST->{val extras = params?.extrasval playlistId = extras?.getString("playlistId") ?: "-1"val result = dataRepository.fetchAlbum(playlistId)LibraryResult.ofItemList(result,params)}MEDIA_ARTIST_PLAY_LIST->{val extras = params?.extrasval playlistId=extras?.getString("playlistId")?:"-1"val result =dataRepository.fetchArtistTopTracks(playlistId,"ES")LibraryResult.ofItemList(result,params)}MEDIA_PLAY_LIST->{val extras = params?.extrasval playlistId=extras?.getString("playlistId")?:"-1"val result =dataRepository.fetchDetailTracks(playlistId,"ES")LibraryResult.ofItemList(listOf(result) ,params)}else ->{LibraryResult.ofError(SessionError.ERROR_BAD_VALUE)}}}}

2. 构建媒体库层级结构

这段代码使用一个 when 表达式根据 parentId 来构建一个多层级的媒体库:

  • 根目录 (SPOTIFY_MUSIC_ROOT_ID): 当 parentId 是根 ID 时,它会返回一个包含主要选项卡(如“推荐”、“我的音乐”)的列表。这些选项卡本身是可浏览的文件夹。

    • dataRepository.getSpotifyMainTabs() 返回一个 List<MediaItem>,其 isBrowsable 属性被设置为 true

  • 子目录(MEDIA_TABS_RECOMMEND, MEDIA_TABS_MINE 等): 当 parentId 是某个选项卡的 ID 时,它会调用 dataRepository 中的相应方法来获取具体的内容列表。

    • MEDIA_TABS_RECOMMEND: 返回热门歌曲和热门艺术家的列表。

    • MEDIA_TABS_MINE: 返回用户的播放列表。

  • 动态内容(MEDIA_ALBUM_PLAY_LIST, MEDIA_ARTIST_PLAY_LIST 等): 这部分展示了如何处理更深层次的、依赖于特定 ID 的媒体内容。

    • params?.extras: params 参数中的 extras Bundle 可以用来传递额外的信息。例如,当客户端点击一个专辑项时,它可以在 extras 中传递专辑的 ID。

    • extras?.getString("playlistId"): 这段代码从 extras 中提取出 playlistId,然后用它来调用 dataRepository 的相应方法,从而获取该专辑或艺术家的具体曲目列表。

    • LibraryResult.ofItemList(...): 这是 Media3 库用来封装和返回结果的类。它将获取到的 MediaItem 列表包装在 LibraryResult 中。

五、总结

        整个工作流程是一个自底向上的过程,首先从 Postman 检查开始,确保 API 接口的功能、参数和请求方式(GET, POST, PUT, DELETE)是正确的。

        在 Android 端,这个流程由 Retrofit 库实现。你通过定义一个 Retrofit 接口来抽象 API,并使用 @GET@Header@Path@Query 等注解来构建请求。同时,你使用 Kotlinx.serialization@Serializable 注解来创建数据模型,让 Retrofit 自动将 JSON 响应解析为 Kotlin 对象,大大简化了数据处理。

        接着,一个名为 SpotifyDataRepository数据仓库封装了所有这些网络请求,并利用 Kotlin 扩展函数 (toMediaItem()) 将获取到的原始数据模型(如 Album, Track)统一转换为 Media3 库所需的 MediaItem 对象,这些对象包含了媒体的元数据(MediaMetadata)。

        最后,在 MediaLibraryServiceonGetChildren 方法中,你根据客户端请求的 parentId 来判断用户正在浏览的媒体库层级,然后调用 SpotifyDataRepository 中相应的方法获取数据,并将转换好的 MediaItem 列表封装到 LibraryResult 中返回给客户端,从而构建一个多层级的、可浏览的媒体库,供 Android Auto 等设备使用。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《计算机网络》实验报告一 常用网络命令

目 录 1、实验目的 2、实验环境 3、实验内容 3.1 ping基本用法 3.2 ifconfig/ipconfig基本用法 3.3 traceroute/tracert基本用法 3.4 arp基本用法 3.5 netstat基本用法 4、实验结果与分析 4.1 ping命令的基本用法 4.2 ifconfig/ipconfig命令的基本用法 4.3 tracer…

MySQL深度理解-深入理解MySQL索引底层数据结构与算法

1.引言在项目中会遇到各种各样的慢查询的问题&#xff0c;对于千万级的表&#xff0c;如果使用比较笨的查询方式&#xff0c;查询一条SQL可能需要几秒甚至几十秒&#xff0c;如果将索引设置的比较合理&#xff0c;可以将查询变得仍然非常快。2.索引的本质索引&#xff1a;帮助M…

Django母婴商城项目实践(九)- 商品列表页模块

9、商品列表页模块 1、业务逻辑 商品模块分为:商品列表页 和 商品详情页 商品列表页将所有商品按照一定的规则排序展示,用于可以从销量、价格、上架时间和收藏数量设置商品的排序方式,并且在商品左侧设置分类列表,选择某一个分类可以筛选出对应的商品信息。 商品列表页…

8、STM32每个系列的区别

1、F1和F4的系列的区别 F1采用Crotex M3内核&#xff0c;F4采用Crotex M4内核。F4比F1的主频高。F4具有浮点数运算单元&#xff0c;F1没有浮点单元。F4的具备增强的DSP指令集。F407的执行16位DSP指令的时间只有F1的30%~70%。F4执行32位DSP指令的时间只有F1的25% ~ 60%。F1内部S…

DeepSPV:一种从2D超声图像中估算3D脾脏体积的深度学习流程|文献速递-医学影像算法文献分享

Title题目DeepSPV: A deep learning pipeline for 3D spleen volume estimation from 2Dultrasound imagesDeepSPV&#xff1a;一种从2D超声图像中估算3D脾脏体积的深度学习流程01文献速递介绍1.1 临床背景 脾肿大指脾脏增大&#xff0c;是多种潜在疾病的重要临床指标&#x…

病历数智化3分钟:AI重构医院数据价值链

一、方案概述本方案针对某省医联体医院病例数据管理需求&#xff0c;通过AI技术实现病历数字化→信息结构化→数据应用化的全流程改造。系统采用双端协同架构&#xff1a; - 普通用户端&#xff1a;为一线医护人员提供病历拍摄、AI识别修正、安全上传功能 - 管理员后台&#…

CSS+JavaScript 禁用浏览器复制功能的几种方法

&#x1f6e1;️ 禁用浏览器复制功能完整指南 网页中禁用用户的复制功能&#xff0c;包括 CSS 方法、JavaScript 方法、综合解决方案以及实际应用场景。适用于需要保护内容版权、防止恶意爬取或提升用户体验的场景。 &#x1f4cb; 目录 &#x1f680; 快速开始&#x1f3a8…

Java 虚拟线程在高并发微服务中的实战经验分享

Java 虚拟线程在高并发微服务中的实战经验分享 虚拟线程&#xff08;Virtual Threads&#xff09;作为Java 19引入的预览特性&#xff0c;为我们在高并发微服务场景下提供了一种更轻量、易用的并发模型。本文结合真实生产环境&#xff0c;讲述在Spring Boot微服务中引入和使用虚…

《拆解WebRTC:NAT穿透的探测逻辑与中继方案》

WebRTC以其无需插件的便捷性&#xff0c;成为连接全球用户的隐形桥梁。但很少有人知晓&#xff0c;每一次流畅的视频对话背后&#xff0c;都藏着一场与网络边界的无声博弈——NAT&#xff0c;这个为缓解IPv4地址枯竭而生的技术&#xff0c;既是网络安全的屏障&#xff0c;也是端…

前端开发 React 组件优化

1. 使用 React.memo 进行组件优化问题&#xff1a;当父组件重新渲染时&#xff0c;子组件也会重新渲染&#xff0c;即使它的 props 没有变化。解决方案&#xff1a;使用 React.memo 包裹子组件&#xff0c;让其只在 props 变化时才重新渲染。示例场景&#xff1a;展示一个显示计…

变频器实习DAY12

目录变频器实习DAY12一、继续&#xff0c;柔性平台测试&#xff01;上午 王工Modbus新功能测试下午 柔性平台继续按照说明书再测一遍附加的小知识点中国狸花猫.git文件附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)变频器实习DAY12 一、继续&#xff0c;柔性平台测试&…

Redis--多路复用

&#x1f9e9; 一、什么是“客户端连接”&#xff1f;所谓 客户端连接 Redis&#xff0c;指的是&#xff1a;一个程序&#xff08;客户端&#xff09;通过网络连接到 Redis 服务端&#xff08;比如 127.0.0.1:6379&#xff09;&#xff0c;建立一个 TCP 连接&#xff0c;双方可…

数组——初识数据结构

一维数组数组的创建数组是一种相同类型元素的集合数组的创建方式C99 中引入了变长数组的概念&#xff0c;变长数组支持数组的大小使用变量来指定明显这里的vs2019不支持变长数组数组初始化和不完全初始化第二个数组就是典型的不完全初始化&#xff0c;开辟了10个空间&#xff0…

技术速递|使用 Semantic Kernel 与 A2A 协议构建多智能体解决方案

作者&#xff1a;卢建晖 - 微软高级云技术布道师 翻译/排版&#xff1a;Alan Wang 在快速发展的 AI 应用开发领域&#xff0c;能够协调多个智能体已成为构建复杂企业级解决方案的关键。虽然单个 AI 智能体擅长特定任务&#xff0c;但复杂的业务场景往往需要跨平台、跨框架甚至跨…

前端跨域请求原理及实践

在前端开发中&#xff0c;"跨域"是一个绕不开的话题。当我们的页面尝试从一个域名请求另一个域名的资源时&#xff0c;浏览器往往会抛出类似Access to fetch at xxx from origin xxx has been blocked by CORS policy的错误。下面将深入探讨跨域请求的底层原理&#…

SpringBoot07-数据层的解决方案:SQL

一、内置数据源 1-1、【回顾】Druid数据源的配置 druid的两种导入格式 1-2、springboot提供的3种内置数据源的配置 若是不配置Druid&#xff0c; springboot提供了3中默认的数据源配置&#xff0c;它们分别是&#xff1a; 1. HikariCP&#xff08;默认&#xff09; 从 Spring…

前端自动化埋点:页面模块级行为跟踪与问题定位系统​​的技术设计方案

一、核心设计目标​​精细化监控​​&#xff1a;定位到页面中​​单个模块​​的曝光、点击等行为。​​低侵入性​​&#xff1a;业务代码与埋点逻辑解耦&#xff0c;降低开发维护成本。​​链路可追踪​​&#xff1a;串联用户从曝光到操作的完整行为路径。​​实时性​​&a…

Node.js 与 Java 性能对比

一、核心架构与任务模型对比Node.js 单线程事件循环 非阻塞I/O 通过V8引擎执行JavaScript&#xff0c;采用事件驱动模型&#xff0c;所有I/O操作&#xff08;如网络请求、文件读写&#xff09;均为非阻塞。单线程处理所有请求&#xff0c;但通过事件循环&#xff08;Event Loo…

Python3常见接口函数

Python3常见接口函数一、基础内置函数 输入输出 print()&#xff1a;输出内容input()&#xff1a;读取用户输入 类型转换 int()、float()、str()、bool()&#xff1a;基础类型转换list()、tuple()、set()、dict()&#xff1a;容器类型转换bin()、hex()、oct()&#xff1a;进制转…

《P4092 [HEOI2016/TJOI2016] 树》

题目描述在 2016 年&#xff0c;佳媛姐姐刚刚学习了树&#xff0c;非常开心。现在他想解决这样一个问题&#xff1a;给定一颗有根树&#xff0c;根为 1 &#xff0c;有以下两种操作&#xff1a;标记操作&#xff1a;对某个结点打上标记。&#xff08;在最开始&#xff0c;只有结…