草庐IT

关于网络框架设计封装的扯淡

wx60e66db461c2a 2023-03-28 原文

关于网络框架设计封装的扯淡

本blog的代码库:

HttpUtil2

1. 前后端交互协议设计

常规是data-code-msg三字段设计

也有data-code-msg-isSuccess. 其中isSuccess和code其实互为冗余.

但看了Facebook,google等大公司的接口交互协议,发现其实最全的是:

data-code-msg-errorData.

请求正确时:

{ "data": { "uid": "898997899788997" }, "code": "0", "msg": "success!", "success": true, "errorData": null }
请求错误时

错误原因千奇百怪,应使用map来解析errorData,避免解析异常.或直接使用optJSONObject("errorData")

{ "data": null, "code": "user.login.401", "msg": "unlogin", "success": false, "errorData": { "reason":"kickout", "time":1689799989 } }
为了debug方便,在开发/测试环境,后台500时,应将异常栈信息直接塞在msg里返回给前端.

2. 应该包含哪些功能

底层

从urlconnection到httpclient到okhttp

封装层

从volley/asyncHttpClient到retrofit

如今基本上是okhttp一统底层,上层retrofit+rxjava.

即使用retrofit,仍然有很多重复代码要写,需要更进一层的封装,方便日常crtl+c ,ctrl+v.

即使是crtl+c,也希望代码能少一点是一点.

那么一个封装完善的网络框架,还需要哪些功能?

先看看几个star比较多的封装库:

OkGo:

大强子的NET

rxHttp

结合日常开发经验,总结一下,其实有如下可塞入框架中:

其实,再想想,一个完善的客户端网络库,应该像postman一样基于配置,傻瓜易用.

封装网络框架,无非是吧这些个gui变成api而已.

3. 几个设计上的思想

开箱即用

跟spring boot一样,约定大于配置. 里面的配置项大多都有默认值.

初始化即使只是调用最简单的init方法,也能使用所有功能.

全量信息可访问

回调里要能拿到本次请求和响应的全量信息.

比如okhttp在他的callback里就能拿到整个call对象,以及整个response信息.

很多框架callback里只有解析后的data. 需要用到其他信息时就懵逼了.

全方位适应页面生命周期

管你传view,fragment,activity,lifecycleowner,viewmodel,通通自动处理.

你说view怎么拿到页面生命周期? context里层层剥开,总能拿到activity.

生命周期结束后自动取消请求.

取消请求有两种做法:

(在等待队列里没有区别,都是移出队列-->只是... okhttp-rxjava的线程模型下,基本都是立刻发出,没有等待)

  • 直接socket.close()关掉连接
  • 不干预底层,只是在回调里通过boolean值切断回调
retrofit和rxjava的takeutil,都是用的第一种.简单粗暴易实现,只是后端接口监控里多了一些0或者499的错误.

不用kotlin协程

kotlin协程很牛逼?抱歉,只是假协程,底层还是线程池切换.只是用同步方式写异步代码而已(跟js的async,await差不多).

当然这并非kotlin不行,而是jvm本身并未支持协程.

要真能实现像go一样的真协程,或者跳出jvm,自己调用epoll实现多路复用,那就牛逼了,我肯定抢着用kotlin来改写这个框架.

下面开始讲讲每个关键点的实现和使用

4. api使用:

直接看readme

HttpUtil.requestAsJsonArray("article/getArticleCommentList/v1.json",PostStandardJsonArray.class) .addParam("pageSize","30") .addParam("articleId","1738") .addParam("pageIndex","1") .post() .setCacheMode(CacheMode.FIRST_CACHE_THEN_REQUEST) // .setCacheMode(CacheStrategy.REQUEST_FAILED_READ_CACHE) .callback(new MyNetCallback<ResponseBean<List<PostStandardJsonArray>>>(true,null) { @Override public void onSuccess(ResponseBean<List<PostStandardJsonArray>> response) { MyLog.json(MyJson.toJsonStr(response.data)); } @Override public void onError(String msgCanShow) { MyLog.e(msgCanShow); } }); String url2 = "https://kiwivm.64clouds.com/dist/openvpn-install-2.4.5-I601.exe"; HttpUtil.download(url2) .setFileDownlodConfig( FileDownlodConfig.newBuilder() .verifyBySha1("76DAB206AE43FB81A15E9E54CAC87EA94BB5B384") .isOpenAfterSuccess(true) .build()) .callback(new MyNetCallback<ResponseBean<FileDownlodConfig>>() { @Override public void onSuccess(ResponseBean<FileDownlodConfig> response) { MyLog.i("path:"+response.data.filePath); } @Override public void onError(String msgCanShow) { MyLog.e(msgCanShow); } });

5.关键点

5.1 同步异步的支持

其实okhttp本身就有同步和异步的写法.

同步直接return,用try-catch包裹.

异步就使用callback.

但我们这里内部使用retrofit,基于rxjava.全部变成了回调的形式.

那么,就不追求同步的写法,直接以异步的形式写同步执行.

rxjava怎么同步执行?

不进行线程切换,就同步执行了. so easy

HttpUtil.requestString("article/getArticleCommentList/v1.json") .post() .setSync(true)//同步执行 .addParam("pageSize","30") .addParam("articleId","1738") .addParam("pageIndex","1") .callback(new MyNetCallback<ResponseBean<String>>(true,null) { @Override public void onSuccess(ResponseBean<String> response) { MyLog.i(response.data); } @Override public void onError(String msgCanShow) { MyLog.e(msgCanShow); } });

5.2 自动处理生命周期

原始时代:

本库使用的方式.

用静态map存储activity/fragment对象和请求, activity/fragment destory时,从map中取出请求,判断状态,进行cancel.

/** * 取消请求,常在activity ondestory处调用.直接传入activity即可,不会保存引用,直接识别其名字作为tag * * @param obj */ public static void cancelByTag(Object obj) { if (obj == null) { return; } List<retrofit2.Call> calls = callMap.remove(obj);//从gc root引用中删除 if (calls != null && calls.size() > 0) { for (retrofit2.Call call : calls) { try { if (call.isCanceled()) { return; } call.cancel(); } catch (Exception e) { ExceptionReporterHelper.reportException(e); } } } }

RxLifecycle + rxjava

onDestory时构建transformer,传给rxjava的takeUtil操作符.

本库未实现

livedata

observable转livedata,直接跟lifecyclerOwner挂钩.

本库已实现.

5.3 通用UI支持

loadingDialog

内置,默认不显示.可配置开关,UI样式

错误msg的toast

比较方便的做法是在onError里统一处理,默认关闭,可以通过链式api开启.

测试环境应toast: code+"\n"+msg. 且测试环境的msg应尽量带栈信息.

错误码转文案

一般,应在框架内统一处理.

分三个类型:

底层框架抛出的exception,应转为友好文案

http请求本身的错误码,比如400,500之类的,应提供统一文案

业务data-code-msg内,如果msg部分后台不做国际化,那么客户端应配置对应的翻译文案.

框架应自动处理前两个,并提供第三种业务错误文案的配置接口:

ExceptionFriendlyMsg.init(context, new IFriendlyMsg() { Map<String,Integer> errorMsgs = new HashMap<>(); { errorMsgs.put("user.login.89899",R.string.httputl_unlogin_error); } @Override public String toMsg(String code) { Integer res = errorMsgs.get(code); if(res != null && res != 0){ return context.getResources().getString(res); } return ""; } }); 内部已配置文案:(中文+英文)

5.4 响应体格式校验

bean validator这件事情在服务端接收客户端/浏览器请求时比较常用.已经发展成为了一项java规范.

其实这个需求在客户端并不强烈.服务端的返回大多数情况还是比较稳定的,出现丢字段,字段错误等情况比较少.

不过,为了小装一个X,我还是把这个功能实现了-->

其实也不是实现,只是把服务端常用的功能迁移到移动端,并进行了适配. 做了一点微小的工作.

请看:

AndroidBeanValidator

要移植到Android,需要考虑java8兼容性问题,性能(方法耗时),以及对apk大小的影响,默认使用的是Apache BVal 1.1.2.

String errorMsg = BeanValidator.validate(bean); //返回的errorMsg为空就说明校验通过 if(!TextUtils.isEmpty(errorMsg)){ //Toast.makeText(this,errorMsg,Toast.LENGTH_LONG).show(); Observable.error(xxx)//把errorMsg和指定errorCode往外抛 }else { //拿到合格的bean } 这个操作,放到bean刚被解析出来的时候做就行.

5.5 缓存控制:丰富的缓存模式

超越http协议本身的缓存控制模式

http协议本身缓存控制有哪些局限:

  • 只能缓存get请求

  • 老复杂的请求头

自己写的客户端,能受这点气?必须得改,大改!

  • 要能缓存任何请求

  • 要能一键支持常用业务模式

.setCacheMode(CacheMode.FIRST_CACHE_THEN_REQUEST) //缓存策略,分类参考:https://github.com/jeasonlzy/okhttp-OkGo //不使用缓存,该模式下,cacheKey,cacheMaxAge 参数均无效 public static final int NO_CACHE = 1; //完全按照HTTP协议的默认缓存规则,例如有304响应头时缓存。 public static final int DEFAULT = 2; //先请求网络,如果请求网络失败,则读取缓存,如果读取缓存失败,本次请求失败。成功或失败的回调只有一次 public static final int REQUEST_FAILED_READ_CACHE = 3; //优先使用缓存,如果缓存不存在才请求网络,成功或失败的回调只有一次 public static final int IF_NONE_CACHE_REQUEST = 4; //先使用缓存,不管是否存在,仍然请求网络,可能导致两次成功的回调或一次失败的回调. //成功回调里,有标识识别本次是缓存还是网络返回. public static final int FIRST_CACHE_THEN_REQUEST = 5; //只读取缓存,不请求网络 public static final int ONLY_CACHE = 6;

5.6 cookie

okhttp底层默认没有存cookie,但提供了接口,我们基于他的接口cookiejar实现.

一般有:

  • 不存储cookie

  • 只在内存存储cookie

  • cookie序列化到shareprefences/文件:

第三种跟浏览器行为比较像了.只不过没有浏览器恶心的各种跨域,安全限制,随便玩.

你说httpOnly?sameSite?不存在的,在我这就是几个key-value,想怎么搞就怎么搞.

不过作为一个框架,还是遵循一下最基本的,响应一下host和path还是要的.其他的,提供接口给别人自定义吧.松或者严都随意.

public static final int COOKIE_NONE = 1; public static final int COOKIE_MEMORY = 2; public static final int COOKIE_DISK = 3; private int cookieMode = COOKIE_DISK;//默认是做持久化操作 /** * 设置cookie管理策略 */ public GlobalConfig setCookieMode(int cookieMode) { this.cookieMode = cookieMode; return this; }

5.7 公共请求头,请求参数/请求体参数

可初始化时用map存储,每次请求时加入:

如果值初始化后就不变,那推荐使用这种方式.

如果会变化,就不能用这种.或者变化后更新缓存的map.

也可以利用okhttp的拦截器,在拦截器里加入.

对于请求头,get请求,很简单就实现了

但对于post json或者multiPart,就需要将json再变成map,然后加入,将multiPart还原,再加入.

可参考:

AddCommonHeaderAndParamInterceptor

如果涉及到请求体签名,那么务必将此拦截器加到签名拦截器之前.

5.8 请求超时

okhttp不是有超时设置么?

之前只有connecTimeout,read,write三个超时时间,现在看,已新增callTimeout,涵盖了okhttp层面的整个请求过程.

对于当初没有calltimeout的时代,单纯设置下面三个是不够的,因为dns解析过程并不能被这三者覆盖.

可以使用rxjava的timeout来控制整个流程的耗时.

如今依然优先使用rxjava来控制.因为okhttp的calltimeout无法覆盖自定义缓存读写的超时.

这种一般提供全局配置和单个请求配置

5.9 请求重试

okhttp本身有个重试api:

builder.retryOnConnectionFailure(boolean) 但只是tcp连接失败的重试.且只能重试一次

要不论什么错误都重试,且可指定重试次数,还是得靠rxjava的api. 这就不说了,直接用就行.

5.10 异常捕获和上报

别管okhttp/retrofit崩不崩,你作为一个封装框架,肯定不能崩.

任何情况都不能崩,得做到100% crash free.

有几个关键的地方:

拦截器内

作为应用拦截器第一个,对chain.proceed(request)加上try-catch,降级为ioException,可以被okhttp的error回调处理.

@Override public Response intercept(Chain chain) throws IOException { try { Response response = chain.proceed(chain.request()); } catch (Throwable e) { if (e instanceof IOException) { throw e; } else { //降级,让okhttp框架能处理错误,而不是crash throw new IOException(e); } } }

rxjava全局异常捕获:

这个一般在主工程做.框架内不参与.

RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { @Override public void accept(Throwable e) throws Exception { report(e); } });

自己框架层的回调里

回调的onSuccess和onError是使用者实现的,如果也出现了崩溃怎么办?也给你兜住!

onSuccess抛异常,降级给onError

onError还抛异常,模仿rxjava,降级给全局错误处理

if(bean.success){ try { onSuccess(callback,t); }catch (Throwable throwable){ onError(callback,throwable); } }else { onError(callback,bean.errorInfo); } private static <T> void onError(MyNetCallback<T> callback, Throwable e) { try { Tool.logd("-->http is onError: "+callback.getUrl() ); Tool.dismissLoadingDialog(callback.dialogConfig, callback.tagForCancel); ErrorCallbackDispatcher.dispatchException(callback, e); }catch (Throwable e2){ if(GlobalConfig.get().getErrorHandler() != null){ try { GlobalConfig.get().getErrorHandler().accept(e2); } catch (Exception exception) { exception.printStackTrace(); } }else { if(!GlobalConfig.get().isDebug()){ e2.printStackTrace(); } } //测试环境,都崩溃,提醒一下 if(GlobalConfig.get().isDebug()){ throw e2; } } }

5.11 debug功能

网络嘛,debug主要形式还是抓包

提供丰富多彩的看包的形式:

  • logcat

    改造okhttpLoggingInterceptor,请求体响应体直接一行打印,方便拷贝. 大于4000个字符切割分行.

  • 手机内抓包

    改造的chuck,基于okhttp拦截器,通知栏显示抓包内容.提供过滤过于频繁的刷屏请求,比如各种行为日志上报之类的.

  • pc代理抓包

    通常用fiddler或者chales.

    需要配置: 7.0以上debugable环境忽略证书

    <network-security-config> <debug-overrides> <trust-anchors> <certificates src="system"/> <certificates src="user"/> </trust-anchors> </debug-overrides> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> </trust-anchors> </base-config> </network-security-config> 或者直接网络框架在debug环境忽略证书

  • stetho-> flipper

    基于okhttp拦截器,抓包内容发送到pc上的客户端显示. 显示界面更高端大气上档次.

    改写flipper内置的拦截器,有额外加密的,解密后发明文过去显示.

    一行脚本集成: flipperUtil

5.12 线上监测

上报不麻烦,关键是统计分析怎么搞?有哪些现成的,自己搭又要怎么搭.

在上面的拦截器里添加上报即可. 关键是上报到哪里

构建exception,上报到sentry.

或者自己搭一条flume+elk的分析系统.

或者猥琐一点,构建event上报到事件统计平台,蹭他们的流量.

哪些参数

  • 错误信息:

    在上面拦截器/统一的错误回调里拿到并上报即可. 一般上报到统计平台看错误趋势,根据趋势看某时段前后台服务是否有异常. 这通常只是后台本身请求监控的补充.

    前几年利用谷歌分析的事件实时分析功能,将错误信息变成event上报,能实时看1min内,30min内的网络错误趋势,自带排序,爽得一逼,可惜后面谷歌分析移动端下线了,firebase上这个功能被运营占用了.

  • 请求分时信息:

    比如dns耗时,tcp耗时,tls,http请求响应,这些都可以通过okhttp的eventListener接口来获取.

5.13 安全

手段基本是:

  • https上玩的一些操作
  • 自定义加密
  • 请求头,请求体签名-防篡改

https

基本上就是这几个问题

什么是中间人攻击

如何防范中间人攻击

什么是单向证书校验,框架层如何实现

什么是双向证书校验,框架层如何实现

如何对抗证书校验? root手机+frida+okhttplogging的dex 参考: frida使用

自定义加密

拦截器里拿到请求体字节数组,加密,再构建新的requestBody,继续走即可.

final Buffer buffer = new Buffer(); requestBody.writeTo(buffer); final long size = buffer.size(); final byte[] bytes = new byte[(int) size]; buffer.readFully(bytes); final byte[] bytesEncrypted = encrypt(bytes); //加密成功/失败,最好在请求头加一个标识 return new RequestBody() { @Override public MediaType contentType() { return MediaType.parse(type); } @Override public long contentLength() { return bytesEncrypted.length; } @Override public void writeTo(BufferedSink sink) throws IOException { sink.write(bytesEncrypted); } };

请求头请求体签名

无非是加盐来生成sha1,sha256什么的,没什么好讲的.

5.14 gzip

okhttp已内置对响应体的gzip处理,这个不用再说.

如果请求体是比较大的字符串,那么用gzip压缩,流量收益方面还是可以的.

需要前后端支持.

我们在拦截器里进行gzip压缩.

gzip前无法指定gzip后的大小,可以再包裹一层,以设定请求体的contentLength

private RequestBody gzip(final RequestBody body, String type) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // We don't know the compressed length in advance! } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } 后端nginx上用lua脚本进行解压缩后再转发即可.

5.15 断点上传/下载

利用的是http头的range和content-range, 加上java 的randomAccessFile api.

主要还是工程问题比较难处理.写得好的框架不多.我这个没有做这个断点续传功能.

5.16 下载后处理

抄了些迅雷等下载软件的功能,用api的形式提供出来

比如:

  • 下载后校验md5/sha1
  • 下载后自动打开: 需要处理Android7的File uri permission
  • 下载后通知mediastore扫描
  • 是否隐藏文件: 下载一些隐私文件时用,你懂的.利用.nomedia空文件隐藏,防君子不防小人.
  • 通知栏显示下载进度/对话框显示下载进度

5.17 回调形式

  • callback
  • livedata
  • 返回observable

5.18 接口聚合

场景1 多图异步上传

public static io.reactivex.Observable<ResponseBean<S3Info>> uploadImgs(String type, final List<String> filePaths){ final List<S3Info> infos = new ArrayList<>(); io.reactivex.Observable<ResponseBean<S3Info>> observable = HttpUtil.requestAsJsonArray(getUploadTokenPath,S3Info.class) .get() .addParam("type", type) .addParam("contentType", IMAGE_JPEG) .addParam("cnt",filePaths.size()) .asObservable() .flatMap(new Function<ResponseBean<List<S3Info>>, ObservableSource<ResponseBean<S3Info>>>() { @Override public ObservableSource<ResponseBean<S3Info>> apply(ResponseBean<List<S3Info>> bean) throws Exception { infos.addAll(bean.bean); List<io.reactivex.ObservableSource<ResponseBean<S3Info>>> observables = new ArrayList<>(); for(int i = 0; i< bean.bean.size(); i++){ S3Info info = bean.bean.get(i); String filePath = filePaths.get(i); io.reactivex.Observable<ResponseBean<S3Info>> observable = HttpUtil.request(info.getUrl(),S3Info.class) .uploadBinary(filePath) .put() .setExtraFromOut(info) .responseAsString() .treatEmptyDataAsSuccess() .asObservable(); observables.add(observable); } return io.reactivex.Observable.merge(observables); } }); return observable; }

场景2:多接口异步请求,统一回调一次

后台微服务拆得太细,又不愿做聚合,只能客户端自己做.

在客户端,基于Rxjava实现通用的聚合接口请求.

每个接口可配置能否接受失败

代码

https://github.com/hss01248/HttpUtil2

https://github.com/hss01248/flipperUtil

https://github.com/hss01248/AndroidBeanValidator

https://github.com/hss01248/okhttpInterceptors

https://github.com/skyNet2017/r0capture/blob/main/frida%E4%BD%BF%E7%94%A8.md

有关关于网络框架设计封装的扯淡的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  3. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  4. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  5. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  6. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  7. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  8. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  9. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  10. ruby-on-rails - 设计通过 reset_password_token 获取用户 - 2

    我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow

随机推荐