Jetpack Compose 的简单 MVI 框架

Jetpack Compose 的简单 MVI 框架

在 Jetpack Compose 应用程序中管理状态的一种简单方法

选择正确的架构是至关重要的,因为架构变更后期代价高昂。MVP已被MVVM和MVI取代,而MVI更受欢迎。MVI通过强制实施结构化的状态管理方法,只在reducer中更新状态,并通过管道处理所有意图。相比之下,MVVM更新状态的位置更加自由,但理解和调试流程更加困难。状态管理对于应用程序的发展和扩展至关重要。

MVI简介

在MVI中有三个架构组件:

  • Model:表示应用程序或特定屏幕的状态以及生成该状态的业务逻辑。
  • View:表示用户交互的 UI。
  • Intent:这些是触发新模型的操作,可以是来自用户或外部的操作。
    需要注意的是,在MVI中状态是不可变的,并且MVI遵循单向数据流。这可以用以下图表表示:

基本流程如下:

生成初始模型,并将其推送到视图进行渲染。
用户或外部因素(例如网络加载完成)触发一个操作(即意图)。
意图在业务逻辑中进行处理,生成一个新的模型,并将其发布到视图。
循环无限重复。这种架构提供了明确的责任分离:视图负责渲染 UI,意图负责承载操作,而模型负责业务逻辑。

创建MVI框架的基本脚手架

我们将从为MVI框架创建基本脚手架开始。这里描述的解决方案是针对Android并专为Jetpack Compose应用程序量身定制的,但原则可以应用于任何移动应用或其他类型的应用。

我们将基于Android的ViewModel来构建模型,因为这个解决方案与Android框架很好地集成,并且具备生命周期感知能力,但需要注意的是,这并非必需,其他解决方案同样可行。

为了创建脚手架,我们需要以下组件:

  • 一个不可变的状态(模型),供视图观察。
  • 一个管道来推送意图(我将其称为操作,以避免与Android的Intent混淆)。
  • 一个减速器,用于根据现有状态和当前操作生成新的状态。
    由于这个解决方案专为Jetpack Compose而设计,我们将使用MutableState作为模型。对于管道,我们将使用MutableSharedFlow来提供给减速器。虽然不是必需的,但我还喜欢为状态和操作定义标记接口。让我们看看MVI脚手架的代码:
// 1
interface State// 2
interface Action// 3
interface Reducer<S : State, A : Action> {/*** Generates a new instance of the [State] based on the [Action]** @param state the current [State]* @param action the [Action] to reduce the [State] with* @return the reduced [State]*/fun reduce(state: S, action: A): S
}private const val BufferSize = 64// 4
open class MviViewModel<S : State, A : Action>(private val reducer: Reducer<S, A>,initialState: S,
) : ViewModel() {// 5private val actions = MutableSharedFlow<A>(extraBufferCapacity = BufferSize)// 6var state: S by mutableStateOf(initialState)private setinit {// 7viewModelScope.launch {actions.collect { action ->state = reducer.reduce(state, action)}}}// 8fun dispatch(action: A) {val success = actions.tryEmit(action)if (!success) error("MVI action buffer overflow")}
}
  1. 我们为状态定义一个标记接口。

  2. 对于操作,我们也是如此做。

  3. 减速器将负责更新状态,它有一个单一的函数,该函数接受当前状态和操作,并生成一个新的状态。需要注意的是,减速器的reduce方法必须是纯函数——生成的状态只能依赖于输入的状态和操作,并且状态必须同步生成。

  4. MviViewModel是MVI框架的基础。 MviViewModel接收一个减速器和初始状态以启动MVI流程。

  5. 对于操作的管道,我们使用MutableSharedFlow,并设置了一个特定的缓冲容量。

  6. 状态保存在MutableState对象中,并作为只读属性公开。它在MviViewModel构造函数中初始化为提供的初始状态。

  7. 当ViewModel启动时,我们启动一个协程来收集来自MutableSharedFlow的操作,每次发出操作时,我们运行减速器并相应地更新状态。需要注意的是,对于这个简单的示例,我使用viewModelScope作为协程范围,但建议为了更好的可测试性提供一个专用的范围。本文末尾链接的完整示例展示了实现方式。

  8. 最后,我们需要一种方法将操作推送到管道中,我们使用dispatch方法来实现,它接受一个操作并将其推送到MutableSharedFlow中。如果缓冲区已满,这可能表示某种问题,因此我们选择在这种情况下抛出异常。

有了这个脚手架,我们现在可以创建一个简单的应用程序。我们将创建您选择的典型架构的示例应用程序,一个带有两个按钮的计数器,一个用于增加计数,一个用于减少计数。

基本示例应用程序
对于我们的示例应用程序,我们需要以下几个组件:

  • State
  • Actions
  • Reducer
  • ViewModel
  • UI

让我们从定义我们的状态开始。对于这个非常简单的示例,我们的状态只需保存一个属性,即当前计数值:

// 1
data class CounterState(// 2val counter: Int,// 3
) : State {companion object {// 4val initial: CounterState = CounterState(counter = 0,)}
}
  1. 我们使用数据类作为状态,这样我们可以利用生成的函数,比如我们将用它来从现有状态创建新状态的复制函数。

  2. 我们的状态只有一个属性,即计数器的值。

  3. 该状态继承了我们的标记接口。

  4. 最后,我们提供一个默认值,作为起点使用。

接下来,我们将定义操作。对于这个示例,我们只有两个操作,一个用于增加状态,一个用于减少状态:

// 1
sealed interface CounterAction : Action {// 2data object Increment : CounterAction// 3data object Decrement : CounterAction
}
  1. 我们为计数器操作使用了一个密封接口,该接口继承自我们的标记接口。

  2. 我们所需的操作不携带任何有效负载,因此我们为增加操作创建了一个数据对象。在大多数应用程序中,当需要有效负载时,我们会同时使用数据对象和数据类。

  3. 对于减少操作,我们也是同样的做法。

现在我们有了状态和操作,我们可以构建我们的减速器,它负责根据当前状态和操作生成一个新状态。让我们来看看代码:

// 1
class CounterReducer : Reducer<CounterState, CounterAction> {// 2override fun reduce(state: CounterState, action: CounterAction): CounterState {// 3return when (action) {CounterAction.Decrement -> state.copy(counter = state.counter - 1)CounterAction.Increment -> state.copy(counter = state.counter + 1)}}
}
  1. CounterReducer实现了Reducer接口。

  2. 我们重写了reduce函数,它负责生成状态。

  3. 我们对操作进行了全面的when操作,在每个操作中,根据当前状态生成一个新状态。

我们只剩下两个部分了,ViewModel和UI。让我们首先创建我们的viewModel:

class CounterViewModel(// 1reducer: CounterReducer = CounterReducer(),// 2
) : MviViewModel<CounterState, CounterAction>(reducer = reducer,initialState = CounterState.initial,
) {// 3fun onDecrement() {dispatch(CounterAction.Decrement)}// 4fun onIncrement() {dispatch(CounterAction.Increment)}
}
  1. CounterviewModelCounterReducer作为构造函数参数接收。在这个示例中,我们在构造函数中实例化了减速器,但在真实的应用程序中,我们将使用依赖注入。

  2. CounterviewModel继承自我们的基本MviViewModel,提供了减速器和初始状态。

  3. 我们定义了一个名为onDecrement的方法,它将向MVI管道推送减少操作。

  4. 我们对增加操作也是同样处理,定义了相应的onIncrement方法。

我们所剩下的就是UI,我会简要介绍一下,因为当涉及到MVI框架时,我们实际上如何将状态呈现到UI中的细节并不重要。这里有一个简单的UI显示计数器和两个按钮来增加/减少它:

@Composable
fun CounterScreen(viewModel: CounterViewModel,modifier: Modifier = Modifier,
) {CounterScreen(state = viewModel.state,onDecreaseClick = viewModel::onDecrement,onIncreaseClick = viewModel::onIncrement,modifier = modifier,)
}@Composable
fun CounterScreen(state: CounterState,onDecreaseClick: () -> Unit,onIncreaseClick: () -> Unit,modifier: Modifier = Modifier,
) {Row(modifier = modifier,verticalAlignment = Alignment.CenterVertically,) {IconButton(onClick = onDecreaseClick) {Icon(imageVector = Icons.Default.RemoveCircleOutline,contentDescription = "decrement")}Text(text = state.counter.toString(),style = MaterialTheme.typography.displaySmall,modifier = Modifier.padding(horizontal = 16.dp),)IconButton(onClick = onIncreaseClick) {Icon(imageVector = Icons.Default.AddCircleOutline,contentDescription = "increment")}}
}

通过这个基本的MVI框架和一个样例应用程序,我们已经搭建好了。但是我们的解决方案缺少处理异步(或长时间运行的)操作的能力,因为我们的减速器正在同步地更新状态。接下来,我们将看到如何增强我们的MVI框架以支持异步工作。

处理异步工作

为了在MVI框架中处理异步工作,我们将添加一个新概念,即Middleware。Middleware是一个组件,它被插入到MVI管道中,并可以异步执行操作。Middleware通常会在其工作开始、期间和结束时发出自己的操作(例如,如果我们有一个需要进行网络调用的操作,则Middleware可能会发出一个操作来指示网络加载已开始,可能会发出其他操作来更新网络加载中的进度指示器,并在网络加载完成时发出最终加载完成的操作)。

与其他组件一样,我们将为Middleware创建一个基类:

// 1
interface Dispatcher<A : Action> {fun dispatch(action: A)
}// 2
abstract class Middleware<S : State, A : Action>() {// 3private lateinit var dispatcher: Dispatcher<A>// 4abstract suspend fun process(state: S, action: A)// 5protected fun dispatch(action: A) = dispatcher.dispatch(action)// 6internal fun setDispatcher(dispatcher: Dispatcher<A>,) {this.dispatcher = dispatcher}
}
  1. Middleware需要分发自己的Actions,因此我们定义了一个Dispatcher接口(稍后我们将看到如何使用它)。

  2. Middleware类是在State和Action上进行参数化的,与减速器类似。

  3. Middleware将接收Dispacher以将操作推送到MVI管道。

  4. 挂起的process方法是异步工作将要进行的地方。

  5. 这是一个将操作推送到MVI框架的实用方法,以便我们可以将Dispatcher保持私有。

  6. 最后,我们有一个方法用于初始化Middleware中使用的Dispatcher。

接下来,让我们看看如何更新我们的MviViewModel以在MVI流程中插入Middleware:

open class MviViewModel<S : State, A : Action>(private val reducer: Reducer<S, A>,// 1private val middlewares: List<Middleware<S, A>> = emptyList(),initialState: S,
) : ViewModel(), Dispatcher<A> {// 2private data class ActionImpl<S : State, A : Action>(val state: S,val action: A,)private val actions = MutableSharedFlow<ActionImpl<S, A>>(extraBufferCapacity = BufferSize)var state: S by mutableStateOf(initialState)private setinit {// 3middlewares.forEach { middleware -> middleware.setDispatcher(this) }// 4viewModelScope.launch {actions.onEach { actionImpl ->// 5middlewares.forEach { middleware ->// 6middleware.process(actionImpl.state, actionImpl.action)}}.collect()}viewModelScope.launch {actions.collect {state = reducer.reduce(state, it.action)}}}override fun dispatch(action: A) {val success = actions.tryEmit(action)if (!success) error("MVI action buffer overflow")}
}
  1. 现在,MviViewModel接收一个中间件列表,默认为空列表,因为并不是所有屏幕都有异步工作。ViewModel还实现了Dispatcher接口。

  2. 我们定义了一个包装器类,它包装了当前状态和操作,我们将其推送到管道上,以便在接收操作时拥有状态的副本。

  3. 在init块中,我们循环遍历中间件,并为每个中间件设置调度器,即ViewModel本身。

  4. 接下来,我们启动一个协程来观察MutableSharedFlow的Action和State的发射。

  5. 对于每个发射,我们遍历所有中间件。

  6. 对于每个中间件,我们调用其process方法来处理操作。

这种方法的思想是,我们将拥有一组中间件,每个中间件负责应用程序的一部分业务逻辑;每个中间件将观察来自MVI管道的操作,当它负责的操作被发射时,它将启动异步操作。在一个大型应用程序中,我们可以将屏幕分成几个部分,由各自独立的中间件处理,或者我们可以根据它们执行的业务逻辑来分离中间件。思想是拥有小而精细的中间件,每个中间件只执行一个或一小组操作,而不是一个处理所有异步工作的大型中间件。

更新计数器应用程序

通过中间件和更新的MviViewModel,MVI框架已经完成,但是通过一个示例可以更容易理解,因此我们将在计数器屏幕上添加一个按钮,用于为计数器生成一个随机值。我们假设生成这个随机值是一个需要在后台线程上运行的长时间运行过程,因此我们将为此操作创建一个中间件。由于这是一个长时间运行的操作,我们将在执行工作时显示进度指示器。

我们将首先更新计数器状态,以包括加载指示器:

data class CounterState(// 1val loading: Boolean,val counter: Int,
) : State {companion object {val initial: CounterState = CounterState(// 2loading = false,counter = 0,)}
}
  1. 我们给状态添加了一个加载标志。
  2. 并将初始值更新为将该标志设置为false。
    接下来,我们需要一个新的操作来生成随机计数器值,因此我们将其添加到封闭的层次结构中。同样地,当数字准备好时,我们需要更新状态,所以我们需要另一个操作来触发更新。对于这个第二个操作,我们有一个有效载荷,即随机生成的计数器值,因此我们将使用一个数据类。最后,我们希望在后台任务运行时显示加载指示器,所以我们将添加第三个操作来显示进度指示器:
sealed interface CounterAction : Action {data object Loading : CounterActiondata object Increment : CounterActiondata object Decrement : CounterActiondata object GenerateRandom : CounterActiondata class CounterUpdated(val value: Int) : CounterAction
}

接下来,我们将创建中间件,负责生成随机数。当我们开始时,我们将发出一个加载操作,并在结束时发出CounterUpdated操作。我们将使用延迟来模拟长时间操作:

// 1
class CounterMiddleware : Middleware<CounterState, CounterAction>() {// 2override suspend fun process(state: CounterState, action: CounterAction) {// 3when (action) {CounterAction.GenerateRandom -> generateRandom()else -> { /* no-op */ }}}private suspend fun generateRandom() {// 4dispatch(CounterAction.Loading)// 5delay(500L + Random.nextLong(2000L))val counterValue = Random.nextInt(100)// 6dispatch(CounterAction.CounterUpdated(counterValue))}
}
  1. CounterMiddleware扩展了我们的中间件基类。
  2. 我们重写了负责异步工作的process方法。
  3. 我们检查操作,并只处理GenerateRandom操作。
  4. 当我们收到正确的操作时,我们发出Loading操作,这将触发状态更新以显示进度指示器。
  5. 接下来,我们开始工作,在这里通过延迟操作进行模拟。
  6. 而当工作完成时,我们通过一个新的操作发出结果。
    这就是CounterMiddleware的全部内容。接下来,我们需要更新reducer以处理我们之前定义的额外操作。reducer不必处理所有的操作,GenerateRandom操作仅在中间件处处理,因此它将不执行任何操作。让我们看一下代码:
class CounterReducer : Reducer<CounterState, CounterAction> {override fun reduce(state: CounterState, action: CounterAction): CounterState {return when (action) {// 1CounterAction.Loading -> state.copy(loading = true)CounterAction.Decrement -> state.copy(counter = state.counter - 1)CounterAction.Increment -> state.copy(counter = state.counter + 1)// 2is CounterAction.CounterUpdated -> state.copy(loading = false,counter = action.value,)// 3CounterAction.GenerateRandom -> state}}
}
  1. 当接收到Loading操作时,状态会更新以指示正在进行的长时间运行操作。
  2. 当接收到CounterUpdated操作时,我们清除加载标志,并使用操作有效载荷更新计数器值。
  3. GenerateRandom不在reducer中处理,因此返回现有状态。
    接下来,我们需要更新viewmodel,将中间件提供给基类,并添加一个新方法来处理生成随机数的操作。让我们看一下更新:
class CounterViewModel(// 1middleware: CounterMiddleware = CounterMiddleware(),reducer: CounterReducer = CounterReducer(),
) : MviViewModel<CounterState, CounterAction>(reducer = reducer,// 2middlewares = listOf(middleware),initialState = CounterState.initial,
) {fun onDecrement() {dispatch(CounterAction.Decrement)}fun onIncrement() {dispatch(CounterAction.Increment)}// 3fun onGenerateRandom() {dispatch(CounterAction.GenerateRandom)}
}
  1. 我们在构造函数中提供CounterMiddleware。像reducer一样,这通常是注入的,但为了简单起见,我们在此处实例化。
  2. 我们将中间件(在我们的情况下只有一个)提供给基类,以插入到MVI流中。
  3. 最后,我们有一个新的方法来处理生成随机计数器值。
    这基本上结束了示例。最后一步是更新UI,以提供一个触发器来生成随机数字,并在应用程序忙于长时间运行的操作时显示进度指示器。以下代码展示了一个可能的实现方式:
@Composable
fun CounterScreen(state: CounterState,onDecreaseClick: () -> Unit,onIncreaseClick: () -> Unit,onGenerateRandom: () -> Unit,modifier: Modifier = Modifier,
) {Box(contentAlignment = Alignment.Center,modifier = modifier,) {Column(horizontalAlignment = Alignment.CenterHorizontally,) {Row(verticalAlignment = Alignment.CenterVertically,) {IconButton(onClick = onDecreaseClick,enabled = !state.loading,) {Icon(imageVector = Icons.Default.RemoveCircleOutline,contentDescription = "decrement")}Text(text = state.counter.toString(),style = MaterialTheme.typography.displaySmall,modifier = Modifier.padding(horizontal = 16.dp),)IconButton(onClick = onIncreaseClick,enabled = !state.loading,) {Icon(imageVector = Icons.Default.AddCircleOutline,contentDescription = "increment")}}Button(onClick = onGenerateRandom,enabled = !state.loading,modifier = Modifier.padding(top = 16.dp),) {Text(text = "Generate Random")}}if (state.loading) {CircularProgressIndicator()}}
}

下面是一个MVI架构的示例代码,给感兴趣的读者作为参考。

GitHub

https://github.com/fvilarino/Weather-Sample

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

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

相关文章

控制台日志打印console的封装,加入美化、行显示与打印开关,支持node.js环境

控制台日志打印console的封装&#xff0c;加入美化、行显示与打印开关&#xff0c;支持node.js环境 为什么要写这个&#xff1f; 封装这个控制台日志打印工具&#xff0c;主要是在项目中自己做的SDK需要提供给其他开发人员使用&#xff0c;加入了日志美化和打印打开&#xff…

【数据结构】顺序表与ArrayList

作者主页&#xff1a;paper jie 的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精…

【云计算】虚拟私有云 VPC

虚拟私有云 VPC 1.前言1.1 基本介绍1.2 VPC 的作用1.3 VPC 的适用人群 2.VPC 基本概念2.1 VPC 相关基本概念2.2 其他相关基本概念 3.VPC 通信场景3.1 VPC 内部互通3.2 VPC 间互通3.2.1 对等连接3.2.2 Transit Gateway 或者云联网 3.3 访问 Internet3.3.1 Internet 网关3.3.2 NA…

【HCIE】04.网络安全技术

端口隔离 在同一VLAN中可以隔离二层与三层通信&#xff0c;让同VLAN内的设备可以通信或者不可以通信。 定义一个端口隔离组&#xff0c;在一个组内无法互访&#xff0c;不在一个组里面可以进行互访 port-isolate enable group1 //使能端口隔离功能 port-isolate mdoe all //全…

B : DS顺序表--连续操作

Description 建立顺序表的类&#xff0c;属性包括&#xff1a;数组、实际长度、最大长度&#xff08;设定为1000&#xff09; 该类具有以下成员函数&#xff1a; 构造函数&#xff1a;实现顺序表的初始化。 插入多个数据的multiinsert(int i, int n, int item[])函数&#…

Unity 开发人员转CGE(castle Game engine)城堡游戏引擎指导手册

Unity 开发人员的城堡游戏引擎概述 一、简介2. Unity相当于什么GameObject&#xff1f;3. 如何设计一个由多种资产、生物等组成的关卡&#xff1f;4. 在哪里放置特定角色的代码&#xff08;例如生物、物品&#xff09;&#xff1f;Unity 中“向 GameObject 添加 MonoBehaviour”…

Vue3大屏项目实现数字跳动的效果

一、vue-count-to组件&#xff1a; 1、安装&#xff1a; npm install vue3-count-to --save 2、使用&#xff1a; <template><BaseCountTo:startVal"startVal":endVal"endVal":duration"duration":decimals"decimals":pr…

基于复旦微的FMQL45T900全国产化ARM核心模块(100%国产化)

TES745D是一款基于上海复旦微电子FMQL45T900的全国产化ARM核心板。该核心板将复旦微的FMQL45T900&#xff08;与XILINX的XC7Z045-2FFG900I兼容&#xff09;的最小系统集成在了一个87*117mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;能…

Redis 五大类型源码及底层实现

面试题&#xff1a; 谈谈Redis数据类型的底层数据结构&#xff1a; SDS动态字符串双向链表玉缩列表ziplist哈希表hashtable跳表kiplist整数集合intset快速列表quicklist紧凑列表listpack Redis源代码的核心部分 官网&#xff1a;GitHub - redis/redis: Redis is an in-memory…

在已知的二维坐标里找到最接近的点

一、业务场景 最近在研发的项目&#xff0c;在做可视化层&#xff0c;在全球地图上&#xff0c;对我们的国家的陆地地图经纬度按照步长为1的间隔做了二维处理。在得到一组整数的点位信息后&#xff0c;需要将我们已有的数据库数据(业务项目)按照地址的经纬度&#xff0c;映射到…

大数据Flink(八十三):SQL语法的DML:With、SELECT WHERE、SELECT DISTINCT 子句

文章目录 SQL语法的DML:With、SELECT & WHERE、SELECT DISTINCT 子句 一、DML:With 子句

本地Docker Registry远程连接,为你带来高效便捷的镜像管理体验!

Linux 本地 Docker Registry本地镜像仓库远程连接 文章目录 Linux 本地 Docker Registry本地镜像仓库远程连接1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址…

jmeterbeanshell调用jsonpath获取对应值

1.jmeter 新建线程组、Java Request、BeanShell Assertion、View Results Tree 2、在BeanShell Assertion中贴入代码&#xff1a; import org.apache.jmeter.extractor.json.jsonpath.JSONManager; import java.util.List; JSONManager js new JSONManager(); String jsonStr…

电商项目高级篇-01 elasticsearch

电商项目高级篇-01 elasticsearch 1、linux下安装elasticsearch和可视化工具2、docker设置虚拟机开机启动和容器开机启动3、elasticsearch的curd3.1、新增、更新3.2、查询 1、linux下安装elasticsearch和可视化工具 将安装好jdk1.8和tomcat的centos7下安装elasticsearch dock…

贝叶斯神经网络 BBB 学习中遇到的一些问题

这里写目录标题 贝叶斯公式模型概率的公式1/n 形式的贝叶斯公式 全概率公式全概率公式的积分形式 后验推理后验预测分布 posterior predictive distributionKL 散度 平均场 VIBayes by Backprop 代码重新参数化 贝叶斯公式 模型概率的公式 一开始看了这个 https://zhuanlan.z…

Unity 2021.x及以下全版本Crack

前言 最近Unity那档子事不出来了吗&#xff0c;搞得所有人都挺烦的&#xff0c;顺便在公司内网需要我完成一个游戏的项目&#xff0c;就研究了一下如何将Unity给Crack掉。 注意所有操作应有连接外网的权限 以我选择的版本为例&#xff0c;我使用的是Unity 2021.3.5f1与Unity…

企业商标信息API:品牌管理的秘密武器

引言 当今数字时代&#xff0c;品牌管理变得比以往任何时候都更具挑战性。企业需要不断创新、保护知识产权、实时监测市场动态以及应对竞争压力。在这个竞争激烈的环境中&#xff0c;企业商标信息API已经成为品牌管理的秘密武器&#xff0c;为企业提供了无可估量的价值。 企业…

CFCA企业版通配符SSL证书

CFCA是中国CFCA企业版通配符SSL证书金融认证中心的缩写&#xff0c;即China Financial Certification Authority。它是一家经过中国人民银行和国家信息安全机构批准成立的国家级权威安全认证机构&#xff0c;也是国际CA浏览器联盟组织&#xff08;CA/Browser Forum&#xff09;…

基础算法--位运算

位运算理解&#xff1a; n >> k&#xff1a;代表n右移k位 比如 000011 >> 1 000001 前面会补零&#xff08;所以第几位是从0开始计算&#xff09; n & 1&#xff1a;表示最后一位是否为1 比如&#xff1a;n 3 0011 而 1 0001 则3 & 1 0011 & 000…

红日靶场五(vulnstack5)渗透分析

环境搭建 win7 192.168.111.132&#xff08;仅主机&#xff09; 192.168.123.212&#xff08;桥接&#xff09; .\heart p-0p-0p-0win2008 ip: 192.168.111.131&#xff08;仅主机&#xff09; sun\admin 2020.comkali ip: 192.168.10.131&#xff08;nat&#xff09;vps&…