redux中间件原理-讲义

1、redux中间件简介

1.1、什么是redux中间件

redux 提供了类似后端 Express 的中间件概念,本质的目的是提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。

通俗来说,redux中间件就是对dispatch的功能做了扩展。

先来看一下传统的redux执行流程:

在这里插入图片描述

图1 redux传统执行流程

代码示例:
import { createStore } from 'redux';/*** 这是一个 reducer,形式为 (state, action) => state 的纯函数。* 描述了 action 如何把 state 转变成下一个 state。*/
function counter(state = 0, action) {switch (action.type) {case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;}
}// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>console.log(store.getState())
);// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree 。创建store的时候需要传入reducer,真正能改变store中数据的是store.dispatch API。

对dispatch改造后,效果如下:

在这里插入图片描述

图2 dispatch改造后的执行流程

如上图所示,dispatch派发给 redux Store 的 action 对象,到达reducer之前,进行一些额外的操作,会被 Store 上的多个中间件依次处理。例如可以利用中间件来进行日志记录、创建崩溃报告、调用异步接口或者路由等等,那么其实所有的对 action 的处理都可以有中间件组成的。 简单来说,中间件就是对store.dispatch()的增强。

1.2、使用redux中间件

redux有很多中间件,我们这里以 redux-thunk 为例。

代码示例:

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';const store = createStore(reducers, applyMiddleware(thunk)
);

直接将thunk中间件引入,放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作。

Redux middleware 提供了一个分类处理 action 的机会。在 middleware 中,我们可以检阅每一个流过的 action,并挑选出特定类型的 action 进行相应操作,以此来改变 action。其实applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
中间件多了可以当做参数依次传进去。

代码示例:

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';const logger = createLogger();const store = createStore(reducers, applyMiddleware(thunk, logger) //会按顺序执行
);

2、中间件的运行机制

2.1、createStore源码分析

源码:

// 摘至createStore
export function createStore(reducer, rootState, enhance) {//...if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}/*若使用中间件,这里 enhancer 即为 applyMiddleware()若有enhance,直接返回一个增强的createStore方法,可以类比成react的高阶函数*/return enhancer(createStore)(reducer, preloadedState)}//...
}

对于createStore的源码我们只需要关注和applyMiddleware有关的地方, 通过源码得知在调用createStore时传入的参数进行一个判断,并对参数做矫正。 据此可以得出createStore有多种使用方法,根据第一段参数判断规则,我们可以得出createStore的两种使用方式:

const store = createStore(reducer, {a: 1, b: 2}, applyMiddleware(...));

或:

const store = createStore(reducer, applyMiddleware(...));

经过createStore中的第一个参数判断规则后,对参数进行了校正,得到了新的enhancer得值,如果新的enhancer的值不为undeifined,便将createStore传入enhancer(即applyMiddleware调用后返回的函数)内,让enhancer执行创建store的过程。也就时说这里的:

enhancer(createStore)(reducer, preloadedState);

实际上等同于:

applyMiddleware(mdw1, mdw2, mdw3)(createStore)(reducer, preloadedState);

applyMiddleware会有两层柯里化,同时表明它还有一种很函数式编程的用法,即 :

const store = applyMiddleware(mdw1, mdw2, mdw3)(createStore);

这种方式将创建store的步骤完全放在了applyMiddleware内部,并在其内第二层柯里化的函数内执行创建store的过程即调用createStore,调用后程序将跳转至createStore走参数判断流程最后再创建store。

无论哪一种执行createStore的方式,我们都终将得到store,也就是在creaeStore内部最后返回的那个包含dispatch、subscribe、getState等方法的对象。

2.2、applyMiddleware源码分析

源码:

export default function applyMiddleware(...middlewares) {return createStore => (...args) => {// 利用传入的createStore和reducer和创建一个storeconst store = createStore(...args)let dispatch = () => {throw new Error()}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)}// 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍const chain = middlewares.map(middleware => middleware(middlewareAPI))// 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatchdispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
}

为方便阅读和理解,部分ES6箭头函数已修改为ES5的普通函数形式,如下:

function applyMiddleware (...middlewares){return function (createStore){return function (reducer, preloadedState, enhancer){const store = createStore(reducer, preloadedState, enhancer);let dispatch = function (){throw new Error()};const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)};//一下两行代码是所有中间件被串联起来的核心部分实现// 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍const chain = middlewares.map(middleware => middleware(middlewareAPI));// 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatchdispatch = compose(...chain)(store.dispatch);return {...store,dispatch};}}
}

从上面的代码我们不难看出,applyMiddleware 这个函数的核心就在于在于组合 compose,通过将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,然后对 middleware 的设计采用柯里化的方式,以便于compose ,从而可以动态产生 next 方法以及保持 store 的一致性。

在函数式编程(Functional Programming)相关的文章中,经常能看到 柯里化(Currying)这个名词。它是数学家柯里(Haskell Curry)提出的。

柯里化,用一句话解释就是,把一个多参数的函数转化为单参数函数的方法。

根据源码,我们可以将其主要功能按步骤划分如下:

1、依次执行middleware

middleware执行后返回的函数合并到一个chain数组,这里我们有必要看看标准middleware的定义格式,如下:

const chain = middlewares.map(middleware => middleware(middlewareAPI));

遍历所有的中间件,并调用它们,传入那个类似于store的对象middlewareAPI,这会导致中间件中第一层柯里化函数被调用,并返回一个接收next(即dispatch)方法作为参数的新函数。

export default store => next => action => {}// 即
function (store) {return function(next) {return function (action) {return {}}}
}

那么此时合并的chain结构如下:

[    ...,function(next) {return function (action) {return {}}}
]

2、改变dispatch指向

dispatch = compose(...chain)(store.dispatch);

我们展开了这个数组,并将其内部的元素(函数)传给了compose函数,compose函数又返回了我们一个新函数。然后我们再调用这个新函数并传入了原始的未经任何修改的dispatch方法,最后返回一个经过了修改的新的dispatch方法。

什么是compose?在函数式编程中,compose指接收多个函数作为参数,并返回一个新的函数的方式。调用新函数后传入一个初始的值作为参数,该参数经最后一个函数调用,将结果返回并作为倒数第二个函数的入参,倒数第二个函数调用完后,将其结果返回并作为倒数第三个函数的入参,依次调用,知道最后调用完传入compose的所有的函数后,返回一个最后的结果。

compose函数如下:
[...chain].reduce((a, b) => (...args) => a(b(...args)))
实际就是一个柯里化函数,即将所有的middleware合并成一个middleware,并在最后一个middleware中传入当前的dispatch

// 假设chain如下:
chain = [a: next => action => { console.log('第1层中间件') return next(action) }b: next => action => { console.log('第2层中间件') return next(action) }c: next => action => { console.log('根dispatch') return next(action) }
]

调用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))
可以发现已经将所有middleware串联起来了,并同时修改了dispatch的指向。
最后看一下这时候compose执行返回,如下:

dispatch = a(b(c(dispatch)))// 调用dispatch(action)
// 执行循序
/*1. 调用 a(b(c(dispatch)))(action) __print__: 第1层中间件2. 返回 a: next(action) 即b(c(dispatch))(action)3. 调用 b(c(dispatch))(action) __print__: 第2层中间件4. 返回 b: next(action) 即c(dispatch)(action)5. 调用 c(dispatch)(action) __print__: 根dispatch6. 返回 c: next(action) 即dispatch(action)7. 调用 dispatch(action)
*/

总结来说就是:

在中间件串联的时候,middleware1-3的串联顺序是从右至左的,也就是middleware3被包裹在了最里面,它内部含有对原始的store.dispatch的调用,middleware1被包裹在了最外边。

当我们在业务代码中dispatch一个action时,也就是中间件执行的时候,middleware1-3的执行顺序是从左至右的,因为最后被包裹的中间件,将被最先执行。

如图所示:

在这里插入图片描述

3、常见的redux中间件

3.1、logger日志中间件

源码:

function createLogger(options = {}) {/*** 传入 applyMiddleWare 的函数* @param  {Function} { getState      }) [description]* @return {[type]}      [description]*/return ({ getState }) => (next) => (action) => {let returnedValue;const logEntry = {};logEntry.prevState = stateTransformer(getState());logEntry.action = action;// .... returnedValue = next(action);// ....logEntry.nextState = stateTransformer(getState());// ....return returnedValue;};
}export default createLogger;

为了方便查看,将代码修改为ES5之后,如下:

/*** getState 可以返回最新的应用 store 数据*/
function ({getState}) {/*** next 表示执行后续的中间件,中间件有可能有多个*/return function (next) {/*** 中间件处理函数,参数为当前执行的 action */return function (action) {...}}
}

这样的结构本质上就是为了将 middleware 串联起来执行。

3.2、redux异步管理中间件

在多种中间件中,处理 redux 异步事件的中间件,绝对占有举足轻重的地位。从简单的 react-thunk 到 redux-promise 再到 redux-saga等等,都代表这各自解决redux异步流管理问题的方案。

3.2.1、redux-thunk

redux-thunk的使用:

function getWeather(url, params) {return (dispatch, getState) => {fetch(url, params).then(result => {dispatch({type: 'GET_WEATHER_SUCCESS', payload: result,});}).catch(err => {dispatch({type: 'GET_WEATHER_ERROR', error: err,});});};
}

在上述使用实例中,我们应用thunk中间到redux后,可以dispatch一个方法,在方法内部我们想要真正dispatch一个action对象的时候再执行dispatch即可,特别是异步操作时非常方便。

源码:

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;

为了方便阅读,源码中的箭头函数在这里换成了普通函数,如下:

function createThunkMiddleware (extraArgument){return function ({dispatch, getState}){return function (next){return function (action){if (typeof action === 'function'){return action(dispatch, getState, extraArgument);}return next(action);};}}
}let thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;

thunk是一个很常用的redux中间件,应用它之后,我们可以dispatch一个方法,而不仅限于一个纯的action对象。它的源码也很简单,如上所示,除去语法固定格式也就区区几行。

下面我们就来看看源码(为了方便阅读,源码中的箭头函数在这里换成了普通函数),首先是这三层柯里化:

// 外层
function createThunkMiddleware (extraArgument){// 第一层return function ({dispatch, getState}){// 第二层return function (next){// 第三层return function (action){if (typeof action === 'function'){return action(dispatch, getState, extraArgument);}return next(action);};}}
}

首先是外层,上面的源码可知,这一层存在的主要目的是支持在调用applyMiddleware并传入thunk的时候时候可以不直接传入thunk本身,而是先调用包裹了thunk的函数(第一层柯里化的父函数)并传入需要的额外参数,再将该函数调用的后返回的值(也就是真正的thunk)传给applyMiddleware,从而实现对额外参数传入的支持,使用方式如下:

const store = createStore(reducer, applyMiddleware(thunk.withExtraArgument({api, whatever})));

如果无需额外参数则用法如下:

const store = createStore(reducer, applyMiddleware(thunk));

接下来来看第一层,这一层是真正applyMiddleware能够调用的一层,从形参来看,这个函数接收了一个类似于store的对象,因为这个对象被结构以后获取了它的dispatch和getState这两个方法,巧的是store也有这两方法,但这个对象到底是不是store,还是只借用了store的这两方法合成的一个新对象?这个问题在我们后面分析applyMiddleware源码时,自会有分晓。

再来看第二层,在第二层这个函数中,我们接收的一个名为next的参数,并在第三层函数内的最后一行代码中用它去调用了一个action对象,感觉有点 dispatch({type: ‘XX_ACTION’, data: {}}) 的意思,因为我们可以怀疑它就是一个dispatch方法,或者说是其他中间件处理过的dispatch方法,似乎能通过这行代码链接上所有的中间件,并在所有只能中间件自身逻辑处理完成后,最终调用真实的store.dispath去dispatch一个action对象,再走到下一步,也就是reducer内。

最后我们看看第三层,在这一层函数的内部源码中首先判断了action的类型,如果action是一个方法,我们就调用它,并传入dispatch、getState、extraArgument三个参数,因为在这个方法内部,我们可能需要调用到这些参数,至少dispatch是必须的。**这三行源码才是真正的thunk核心所在。所有中间件的自身功能逻辑也是在这里实现的。**如果action不是一个函数,就走之前解析第二层时提到的步骤。

3.2.2、redux-promise

不同的中间件都有着自己的适用场景,react-thunk 比较适合于简单的API请求的场景,而 Promise 则更适合于输入输出操作,比较fetch函数返回的结果就是一个Promise对象,下面就让我们来看下最简单的 Promise 对象是怎么实现的:

import { isFSA } from 'flux-standard-action';function isPromise(val) {return val && typeof val.then === 'function';
}export default function promiseMiddleware({ dispatch }) {return next => action => {if (!isFSA(action)) {return isPromise(action)? action.then(dispatch): next(action);}return isPromise(action.payload)? action.payload.then(result => dispatch({ ...action, payload: result }),error => {dispatch({ ...action, payload: error, error: true });return Promise.reject(error);}): next(action);};
}

它的逻辑也很简单主要是下面两部分:

  1. 先判断是不是标准的 flux action。如果不是,那么判断是否是 promise, 是的话就执行 action.then(dispatch),否则执行 next(action)。
  2. 如果是, 就先判断 payload 是否是 promise,如果是的话 payload.then 获取数据,然后把数据作为 payload 重新 dispatch({ …action, payload: result}) ;不是的话就执行 next(action)

结合 redux-promise 我们就可以利用 es7 的 async 和 await 语法,来简化异步操作了,比如这样:

const fetchData = (url, params) => fetch(url, params)
async function getWeather(url, params) {const result = await fetchData(url, params)if (result.error) {return {type: 'GET_WEATHER_ERROR', error: result.error,}}return {type: 'GET_WEATHER_SUCCESS', payload: result,}}

3.2.3、redux-saga

redux-saga是一个管理redux应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。对于Saga,我们可简单定义如下:

Saga = Worker + Watcher

redux-saga相当于在Redux原有数据流中多了一层,通过对Action进行监听,从而捕获到监听的Action,然后可以派生一个新的任务对state进行维护(这个看项目本身的需求),通过更改的state驱动View的变更。如下图所示:

在这里插入图片描述

saga特点:

  1. saga 的应用场景是复杂异步。
  2. 可以使用 takeEvery 打印 logger(logger大法好),便于测试。
  3. 提供 takeLatest/takeEvery/throttle 方法,可以便利的实现对事件的仅关注最近实践还是关注每一次实践的时间限频。
  4. 提供 cancel/delay 方法,可以便利的取消或延迟异步请求。
  5. 提供 race(effects),[…effects] 方法来支持竞态和并行场景。
  6. 提供 channel 机制支持外部事件。
function *getCurrCity(ip) {const data = yield call('/api/getCurrCity.json', { ip })yield put({type: 'GET_CITY_SUCCESS', payload: data,})
}
function * getWeather(cityId) {const data = yield call('/api/getWeatherInfo.json', { cityId })yield put({type: 'GET_WEATHER_SUCCESS', payload: data,})
}
function loadInitData(ip) {yield getCurrCity(ip)yield getWeather(getCityIdWithState(state))yield put({type: 'GET_DATA_SUCCESS',})
}

场景。
6. 提供 channel 机制支持外部事件。

function *getCurrCity(ip) {const data = yield call('/api/getCurrCity.json', { ip })yield put({type: 'GET_CITY_SUCCESS', payload: data,})
}
function * getWeather(cityId) {const data = yield call('/api/getWeatherInfo.json', { cityId })yield put({type: 'GET_WEATHER_SUCCESS', payload: data,})
}
function loadInitData(ip) {yield getCurrCity(ip)yield getWeather(getCityIdWithState(state))yield put({type: 'GET_DATA_SUCCESS',})
}

总的来讲Redux Saga适用于对事件操作有细粒度需求的场景,同时它也提供了更好的可测试性,与可维护性,比较适合对异步处理要求高的大型项目,而小而简单的项目完全可以使用redux-thunk就足以满足自身需求了。毕竟react-thunk对于一个项目本身而言,毫无侵入,使用极其简单,只需引入这个中间件就行了。而react-saga则要求较高,难度较大。

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

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

相关文章

MySQL8.0.26 开启bin_log日志 linux

binlog日志,即binary log,是二进制日志文件。它有两个作用,一是增量备份,即只备份新增的内容;二是用于主从复制等,即主节点维护了一个binlog日志文件,从节点从binlog中同步数据。我们可以通过bi…

基于阿里云 MaxCompute 构建企业云数据仓库CDW

在本文中阿里云资深产品专家云郎分享了基于阿里云 MaxCompute 构建企业云数据仓库CDW的最佳实践建议。 本文内容根据演讲视频以及PPT整理而成。 大家下午好,我是云郎,之前在甲骨文做企业架构师8年,目前是MaxCompute产品经理。 在这么长的客户…

大咖说中台 | 建设数据中台系列(五)——中台架构详解(下)

作者 | 耿立超来源 | 《大数据平台架构与原型实现:数据中台建设实战》本质上,中台是一种中心化、平台化的企业组织架构和业务形态,当这样的组织和业务架构投射到IT 系统上时会自然地形成我们今天讨论的IT 意义上的“中台”。笔者曾经参与过不…

阿里风控大脑如何应用大数据来构建风控体系?

简介: 2019年双11阿里风控保护了约388亿消费者的操作行为,同时挡住了约22亿次恶意攻击。在首席技术官大数据专享会,阿里巴巴新零售技术事业群高级数据技术专家丁明峰为大家介绍了阿里风控大脑关于大数据应用的探索与实践,即风控领…

DataWorks 如何撑起阿里99%的数据开发?

阿里妹导读: DataWorks是阿里巴巴自主研发,支撑阿里巴巴经济体99%数据业务建设和治理,每天数万名数据开发和算法开发工程师在使用。从2010年起步到目前的版本,经历了多次技术变革和架构升级,也遗留了大量的历史包袱。…

实用小技能 | 用 Word 和 Excel 自制一个题库自判断答题系统!

作者 | 杨秀璋责编 | 夕颜来源 | CSDN博客 如果你需要做题库,并且喜欢电子答题的方法,这篇文章或许会对你有所帮助。反正李老师班级的平均成绩高出其他班级的14分,这就是它的好处,希望这篇文章对我今后的学生有所帮助吧&#xff0…

使用canal实现MySQL 8 全量同步数据到 ElasticSearch 7.15.2中 linux

文章目录一、软件安装1. jdk 安装2. ES7.15.2 安装3. Mysql 8.0 安装4. canal下载二、Mysql 配置2.1. 开启binlog2.2. 验证binlog状态2.3. 创建账号2.4. 权限赋予2.5. 刷新权限2.6. 创建数据库2.7. 初始化表结构2.8. 初始化数据三、canal-deployer的配置与使用3.1. 解压deploye…

“数据驱动、智能引领”,打造未来智能小镇“样板间”

云栖号案例库:【点击查看更多上云案例】 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 云栖小镇位于浙江杭州西湖区南部,属杭州之江国家旅游度假区核心区块&#xf…

探索中国广电“智慧城市”未来转型路径

云栖号案例库:【点击查看更多上云案例】 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 引言 东方明珠是上海的传媒旗舰,在文娱领域拓展的同时,也被广电…

使用canal实现MySQL 8 增量同步数据到 ElasticSearch 7.15.2中 linux

文章目录一、清空控制台1. 清空控制台2. 修改数据3. 监控数据4. 数据变化5. 索引查询6. 预期性能评估7. 增量同步分析二、验证方案2.1. 把shop索引删除2.2. 重建shop索引2.3. 修改id取得数据2.4. 查看shop索引数据2.4. 修改分类表数据2.6. 监控2.7. 查看shop索引数据2.8. 总结2…

30岁,真的是程序员迈不过去的坎吗?

经常听到有人在说:“现在技术迭代越来越快,程序员的压力更大了,30岁之后的程序员就不好找工作了......”,这不乏很多人在故意贩卖焦虑,但是也会引起我们对自己发展的思考。尤其刚入行的程序员会迷茫,会思考…

全球加速产品 方便更多学生获得申请名校的机会

云栖号案例库:【点击查看更多上云案例】 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 公司介绍 青岛XX教育科技股份有限公司是英国爱德思考试局(Edexcel&#xff…

如何构建一个可持续的企业级数据赋能体系?

简介: 对于所有企业来说,数据决定了基于算力、算法等能做出哪些场景和应用。在本次首席技术官大数据专享会上,友盟首席产品官林鸣晖围绕业务数据化,数据资产化、资产应用化、应用价值化构建属于企业的可闭环、可沉淀、可持续的数据…

ElasticSearch 7.15.2 使用java canal 接入实现灵活化增量数据准实时同步

前言: ①canal.adapter-1.1.5 支持一对一单表的增量数据同步ElasticSearch 7; ②对于多表聚合场景的SQL满足不了我们的业务需求。 ③采用java canal 接入,可以实现灵活化增量数据准实时同步 文章目录一、java canal 接入1. 依赖导入2. 增加配…

2020 年最厉害的 10 门编程语言

作者 | 沉默王二来源 | 沉默王二对于很多初学编程的人来说,尤其是马上要入学的大一新生来说,选择哪门子编程语言实在是个痛苦的事。选择对了,毕业后顺利找到工作,完美走向职场;选择错了,毕业后受伤的才华无…

MaxCompute客户端在windows命令行下查询中文乱码怎么办?

MaxCompute客户端工具是阿里云大数据计算服务MaxCompue产品官方客户端工具,通过客户端工具可以连接MaxCompute项目,完成包括数据管理、数据上下传、作业执行、用户及授权管理等各项操作。 但有用户会碰到在Windows下的命令行中执行odpscmd后,…

SLS多云日志采集、处理及分析

场景描述 本文根据用户业务场景不同分别采用Logtail和Log producer写入阿里云日志服务,针对未使用其他日志采集服务的用户,推荐在第三方云平台或线下IDC服务器安装logtail采集并使用DCDN安全传输;针对已使用其他日志采集工具并且已有日志服务…

10分钟搭建完成人脸通行系统 百度『乘风』人脸智能化平台了解一下

目前人脸技术已在企业办公、智慧社区、金融保险等多领域多场景中落地应用,发展潜力巨大。8月21日,百度大脑开放日“乘风新基建,加速产业智能化升级”专场活动在乌镇召开。会上,基于百度大脑领先的人脸识别技术,百度智能…

聚焦数字化智慧安防的新型社区

云栖号案例库:【点击查看更多上云案例】 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 引言 现如今,智慧城市建设已成为全球城市发展的必然趋势,全球仅…

Nexus 3.31.1-01搭建 maven 私服 windows

文章目录1. Nexus 3 下载2. 解压后目录3. 前台启动4. 浏览器访问5. 登录1. Nexus 3 下载 nexus3下载地址 2. 解压后目录 3. 前台启动 进行命令窗口,执行以下命令 nexus.exe /run等待出现这个信息 4. 浏览器访问 http://localhost:8081/ 点右上角登录 5. 登…