前言

dio是一款Flutter 网络请求框架,在GitHub上目前有超过5.9k个star。由国人(Flutter中文网)开发,所以中文文档非常完善。

这里copy了dio官方的文档,便于自己开发时查阅,基于dio 3.0.4版本,若想查看最新版本文档,可以到diopub.dev主页或GigHub主页查阅

dio pub.dev主页

dio GitHub主页

dio

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等...

添加依赖

dependencies:dio: ^3.x.x  // 请使用pub上3.0.0分支的最新版本

dio 3.0.0为了支持Flutter Web,需要进行较大重构,因此无法直接兼容2.1.x, 如果你是2.1.x的用户,可以参照此文档升级到3.0,详情请查看 从2.1升级到3.0指南 。

一个极简的示例

import 'package:dio/dio.dart';
void getHttp() async {try {Response response = await Dio().get("http://www.baidu.com");print(response);} catch (e) {print(e);}
}

内容列表

  • 示例
  • Dio APIs
  • 请求配置
  • 响应数据
  • 拦截器
  • Cookie管理
  • 错误处理
  • 使用application/x-www-form-urlencoded编码
  • FormData
  • 转换器
  • HttpClientAdapter
  • 设置Http代理
  • Https证书校验
  • Http2支持
  • 请求取消
  • 继承 Dio class
  • Features and bugs

示例

发起一个 GET 请求 :

Response response;
Dio dio = Dio();
response = await dio.get("/test?id=12&name=wendu")
print(response.data.toString());
// 请求参数也可以通过对象传递,上面的代码等同于:
response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
print(response.data.toString());

发起一个 POST 请求:

response = await dio.post("/test", data: {"id": 12, "name": "wendu"});

发起多个并发请求:

response = await Future.wait([dio.post("/info"), dio.get("/token")]);

下载文件:

response = await dio.download("https://www.google.com/", "./xx.html");

以流的方式接收响应数据:

Response<ResponseBody> rs = await Dio().get<ResponseBody>(url,options: Options(responseType: ResponseType.stream), //设置接收类型为stream
);
print(rs.data.stream); //响应流

以二进制数组的方式接收响应数据:

Response<List<int>> rs = await Dio().get<List<int>>(url,options: Options(responseType: ResponseType.bytes), //设置接收类型为bytes
);
print(rs.data); //二进制数组

发送 FormData:

FormData formData = FormData.from({"name": "wendux","age": 25,});
response = await dio.post("/info", data: formData);

通过FormData上传多个文件:

FormData.fromMap({"name": "wendux","age": 25,"file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt"),"files": [await MultipartFile.fromFile("./text1.txt", filename: "text1.txt"),await MultipartFile.fromFile("./text2.txt", filename: "text2.txt"),]});
response = await dio.post("/info", data: formData);

监听发送(上传)数据进度:

response = await dio.post("http://www.dtworkroom.com/doris/1/2.0.0/test",data: {"aa": "bb" * 22},onSendProgress: (int sent, int total) {print("$sent $total");},
);

以流的形式提交二进制数据:

// 二进制数据
List<int> postData = <int>[...];
await dio.post(url,data: Stream.fromIterable(postData.map((e) => [e])), //创建一个Stream<List<int>>options: Options(headers: {Headers.contentLengthHeader: postData.length, // 设置content-length},),
);

注意:如果要监听提交进度,则必须设置content-length,反之则是可选的。

示例目录

你可以在这里查看dio的全部示例.

Dio APIs

创建一个Dio实例,并配置它

建议在项目中使用Dio单例,这样便可对同一个dio实例发起的所有请求进行一些统一的配置,比如设置公共header、请求基地址、超时时间等;这里有一个在Flutter工程中使用Dio单例(定义为top level变量)的示例供开发者参考。

你可以使用默认配置或传递一个可选 BaseOptions参数来创建一个Dio实例 :

Dio dio = Dio(); // 使用默认配置// 配置dio实例
dio.options.baseUrl = "https://www.xx.com/api";
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 3000;// 或者通过传递一个 `options`来创建dio实例
Options options = BaseOptions(baseUrl: "https://www.xx.com/api",connectTimeout: 5000,receiveTimeout: 3000,
);
Dio dio = Dio(options);

Dio实例的核心API是 :

Future request(String path, {data,Map queryParameters, Options options,CancelToken cancelToken, ProgressCallback onSendProgress, ProgressCallback onReceiveProgress)

  response = await request("/test",data: {"id": 12, "name": "xx"},options: Options(method: "GET"),);

请求方法别名

为了方便使用,Dio提供了一些其它的Restful API, 这些API都是request的别名。

Future get(...)

Future post(...)

Future put(...)

Future delete(...)

Future head(...)

Future put(...)

Future path(...)

Future download(...)

请求配置

BaseOptions描述的是Dio实例发起网络请求的的公共配置,而Options类描述了每一个Http请求的配置信息,每一次请求都可以单独配置,单次请求的Options中的配置信息可以覆盖BaseOptions中的配置,下面是BaseOptions的配置项:

{/// Http method.String method;/// 请求基地址,可以包含子路径,如: "https://www.google.com/api/".String baseUrl;/// Http请求头.Map<String, dynamic> headers;/// 连接服务器超时时间,单位是毫秒.int connectTimeout;/// 2.x中为接收数据的最长时限.int receiveTimeout;/// 请求路径,如果 `path` 以 "http(s)"开始, 则 `baseURL` 会被忽略; 否则,/// 将会和baseUrl拼接出完整的的url.String path = "";/// 请求的Content-Type,默认值是"application/json; charset=utf-8"./// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,/// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]/// 就会自动编码请求体.String contentType;/// [responseType] 表示期望以那种格式(方式)接受响应数据。/// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.////// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.////// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.ResponseType responseType;/// `validateStatus` 决定http响应状态码是否被dio视为请求成功, 返回`validateStatus`///  返回`true` , 请求结果就会按成功处理,否则会按失败处理.ValidateStatus validateStatus;/// 用户自定义字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中取到.Map<String, dynamic> extra;/// Common query parametersMap<String, dynamic /*String|Iterable<String>*/ > queryParameters;
}

这里有一个完成的示例.

响应数据

当请求成功时会返回一个Response对象,它包含如下字段:

{/// Http method.String method;/// 请求基地址,可以包含子路径,如: "https://www.google.com/api/".String baseUrl;/// Http请求头.Map<String, dynamic> headers;/// 连接服务器超时时间,单位是毫秒.int connectTimeout;/// 2.x中为接收数据的最长时限.int receiveTimeout;/// 请求路径,如果 `path` 以 "http(s)"开始, 则 `baseURL` 会被忽略; 否则,/// 将会和baseUrl拼接出完整的的url.String path = "";/// 请求的Content-Type,默认值是"application/json; charset=utf-8"./// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,/// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]/// 就会自动编码请求体.String contentType;/// [responseType] 表示期望以那种格式(方式)接受响应数据。/// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.////// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.////// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.ResponseType responseType;/// `validateStatus` 决定http响应状态码是否被dio视为请求成功, 返回`validateStatus`///  返回`true` , 请求结果就会按成功处理,否则会按失败处理.ValidateStatus validateStatus;/// 用户自定义字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中取到.Map<String, dynamic> extra;/// Common query parametersMap<String, dynamic /*String|Iterable<String>*/ > queryParameters;
}

示例如下:

  Response response = await dio.get("https://www.google.com");print(response.data);print(response.headers);print(response.request);print(response.statusCode);

拦截器

每个 Dio 实例都可以添加任意多个拦截器,他们组成一个队列,拦截器队列的执行顺序是FIFO。通过拦截器你可以在请求之前或响应之后(但还没有被 then 或 catchError处理)做一些统一的预处理操作。

dio.interceptors.add(InterceptorsWrapper(onRequest:(RequestOptions options) async {// 在请求被发送之前做一些事情return options; //continue// 如果你想完成请求并返回一些自定义数据,可以返回一个`Response`对象或返回`dio.resolve(data)`。// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义数据data.//// 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,或返回`dio.reject(errMsg)`,// 这样请求将被中止并触发异常,上层catchError会被调用。},onResponse:(Response response) async {// 在返回响应数据之前做一些预处理return response; // continue},onError: (DioError e) async {// 当请求失败时做一些预处理return e;//continue}
));

完成和终止请求/响应

在所有拦截器中,你都可以改变请求执行流, 如果你想完成请求/响应并返回自定义数据,你可以返回一个 Response 对象或返回 dio.resolve(data)的结果。 如果你想终止(触发一个错误,上层catchError会被调用)一个请求/响应,那么可以返回一个DioError 对象或返回 dio.reject(errMsg) 的结果.

dio.interceptors.add(InterceptorsWrapper(onRequest:(RequestOptions options){return dio.resolve("fake data")},
));
Response response = await dio.get("/test");
print(response.data);//"fake data"

拦截器中支持异步任务

拦截器中不仅支持同步任务,而且也支持异步任务, 下面是在请求拦截器中发起异步任务的一个实例:

dio.interceptors.add(InterceptorsWrapper(onRequest:(RequestOptions options){return dio.resolve("fake data")},
));
Response response = await dio.get("/test");
print(response.data);//"fake data"

Lock/unlock 拦截器

你可以通过调用拦截器的 lock()/unlock 方法来锁定/解锁拦截器。一旦请求/响应拦截器被锁定,接下来的请求/响应将会在进入请求/响应拦截器之前排队等待,直到解锁后,这些入队的请求才会继续执行(进入拦截器)。这在一些需要串行化请求/响应的场景中非常实用,后面我们将给出一个示例。

dio.interceptors.add(InterceptorsWrapper(onRequest:(RequestOptions options){return dio.resolve("fake data")},
));
Response response = await dio.get("/test");
print(response.data);//"fake data"

Clear()

你也可以调用拦截器的clear()方法来清空等待队列。

别名

请求拦截器被锁定时,接下来的请求将会暂停,这等价于锁住了dio实例,因此,Dio示例上提供了请求拦截器lock/unlock的别名方法:

dio.lock() == dio.interceptors.requestLock.lock()

dio.unlock() == dio.interceptors.requestLock.unlock()

dio.clear() == dio.interceptors.requestLock.clear()

示例

假设这么一个场景:出于安全原因,我们需要给所有的请求头中添加一个csrfToken,如果csrfToken不存在,我们先去请求csrfToken,获取到csrfToken后,再发起后续请求。 由于请求csrfToken的过程是异步的,我们需要在请求过程中锁定后续请求(因为它们需要csrfToken), 直到csrfToken请求成功后,再解锁,代码如下:

dio.interceptors.add(InterceptorsWrapper(onRequest: (Options options) async {print('send request:path:${options.path},baseURL:${options.baseUrl}');if (csrfToken == null) {print("no token,request token firstly...");//lock the dio.dio.lock();return tokenDio.get("/token").then((d) {options.headers["csrfToken"] = csrfToken = d.data['data']['token'];print("request token succeed, value: " + d.data['data']['token']);print('continue to perform request:path:${options.path},baseURL:${options.path}');return options;}).whenComplete(() => dio.unlock()); // unlock the dio} else {options.headers["csrfToken"] = csrfToken;return options;}}
));

完整的示例代码请点击 这里.

日志

我们可以添加 LogInterceptor 拦截器来自动打印请求、响应日志, 如:

dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志

由于拦截器队列的执行顺序是FIFO,如果把log拦截器添加到了最前面,则后面拦截器对options的更改就不会被打印(但依然会生效), 所以建议把log拦截添加到队尾。

Cookie管理

dio_cookie_manager 包是Dio的一个插件,它提供了一个Cookie管理器。详细示例可以移步dio_cookie_manager 。

自定义拦截器

开发者可以通过继承Interceptor 类来实现自定义拦截器,这是一个简单的缓存示例拦截器。

错误处理

当请求过程中发生错误时, Dio 会包装 Error/Exception 为一个 DioError:

  try {//404await dio.get("https://wendux.github.io/xsddddd");} on DioError catch (e) {// The request was made and the server responded with a status code// that falls out of the range of 2xx and is also not 304.if (e.response) {print(e.response.data);print(e.response.headers);print(e.response.request);} else {// Something happened in setting up or sending the request that triggered an Errorprint(e.request);print(e.message);}}

DioError 字段

 {/// 响应信息, 如果错误发生在在服务器返回数据之前,它为 `null`Response response;/// 错误描述.String message;/// 错误类型,见下文DioErrorType type;///原始的error或exception对象,通常type为DEFAULT时存在。dynamic error;
}

DioErrorType

enum DioErrorType {/// When opening  url timeout, it occurs.CONNECT_TIMEOUT,///  Whenever more than [receiveTimeout] (in milliseconds) passes between two events from response stream,///  [Dio] will throw the [DioError] with [DioErrorType.RECEIVE_TIMEOUT].//////  Note: This is not the receiving time limitation.RECEIVE_TIMEOUT,/// When the server response, but with a incorrect status, such as 404, 503...RESPONSE,/// When the request is cancelled, dio will throw a error with this type.CANCEL,/// Default error type, Some other Error. In this case, you can/// read the DioError.error if it is not null.DEFAULT
}

使用application/x-www-form-urlencoded编码

默认情况下, Dio 会将请求数据(除过String类型)序列化为 JSON. 如果想要以 application/x-www-form-urlencoded格式编码, 你可以显式设置contentType :

//Instance level
dio.options.contentType = Headers.formUrlEncodedContentType;
//or works once
dio.post("/info",data:{"id":5},options: Options(contentType:Headers.formUrlEncodedContentType));

这里有一个示例.

FormData

Dio支持发送 FormData, 请求数据将会以 multipart/form-data方式编码, FormData中可以一个或多个包含文件 .

FormData formData = FormData.from({"name": "wendux","age": 25,"file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt")
});
response = await dio.post("/info", data: formData);

注意: 只有 post 方法支持发送 FormData.

这里有一个完整的示例.

多文件上传

多文件上传时,通过给key加中括号“[]”方式作为文件数组的标记,大多数后台也会通过key[]这种方式来读取。不过RFC中并没有规定多文件上传就必须得加“[]”,所以有时不带“[]”也是可以的,关键在于后台和客户端得一致。v3.0.0 以后通过Formdata.fromMap()创建的Formdata,如果有文件数组,是默认会给key加上“[]”的,比如:

  FormData.fromMap({"files": [MultipartFile.fromFileSync("./example/upload.txt",filename: "upload.txt"),MultipartFile.fromFileSync("./example/upload.txt",filename: "upload.txt"),]});

最终编码时会key会为 "files[]",如果不想添加“[]”,可以通过Formdata的API来构建:

  var formData = FormData();formData.files.addAll([MapEntry("files",MultipartFile.fromFileSync("./example/upload.txt",filename: "upload.txt"),),MapEntry("files",MultipartFile.fromFileSync("./example/upload.txt",filename: "upload.txt"),),]);

这样构建的FormData的key是不会有“[]”。

转换器

转换器Transformer 用于对请求数据和响应数据进行编解码处理。Dio实现了一个默认转换器DefaultTransformer作为默认的 Transformer. 如果你想对请求/响应数据进行自定义编解码处理,可以提供自定义转换器,通过 dio.transformer设置。

请求转换器 Transformer.transformRequest(...) 只会被用于 'PUT'、 'POST'、 'PATCH'方法,因为只有这些方法才可以携带请求体(request body)。但是响应转换器 Transformer.transformResponse() 会被用于所有请求方法的返回数据。

Flutter中设置

如果你在开发Flutter应用,强烈建议json的解码通过compute方法在后台进行,这样可以避免在解析复杂json时导致的UI卡顿。

注意,根据笔者实际测试,发现通过compute在后台解码json耗时比直接解码慢很多,建议开发者仔细评估。

// 必须是顶层函数
_parseAndDecode(String response) {return jsonDecode(response);
}parseJson(String text) {return compute(_parseAndDecode, text);
}void main() {...// 自定义 jsonDecodeCallback(dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson;runApp(MyApp());
}

其它示例

这里有一个 自定义Transformer的示例.

执行流

虽然在拦截器中也可以对数据进行预处理,但是转换器主要职责是对请求/响应数据进行编解码,之所以将转化器单独分离,一是为了和拦截器解耦,二是为了不修改原始请求数据(如果你在拦截器中修改请求数据(options.data),会覆盖原始请求数据,而在某些时候您可能需要原始请求数据). Dio的请求流是:

请求拦截器 >> 请求转换器 >> 发起请求 >> 响应转换器 >> 响应拦截器 >> 最终结果

这是一个自定义转换器的示例.

HttpClientAdapter

HttpClientAdapter是 Dio 和 HttpClient之间的桥梁。2.0抽象出adapter主要是方便切换、定制底层网络库。Dio实现了一套标准的、强大API,而HttpClient则是真正发起Http请求的对象。我们通过HttpClientAdapter将Dio和HttpClient解耦,这样一来便可以自由定制Http请求的底层实现,比如,在Flutter中我们可以通过自定义HttpClientAdapter将Http请求转发到Native中,然后再由Native统一发起请求。再比如,假如有一天OKHttp提供了dart版,你想使用OKHttp发起http请求,那么你便可以通过适配器来无缝切换到OKHttp,而不用改之前的代码。

Dio 使用DefaultHttpClientAdapter作为其默认HttpClientAdapter,DefaultHttpClientAdapter使用dart:io:HttpClient 来发起网络请求。

设置Http代理

DefaultHttpClientAdapter 提供了一个onHttpClientCreate 回调来设置底层 HttpClient的代理,我们想使用代理,可以参考下面代码:

import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
...
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {// config the http clientclient.findProxy = (uri) {//proxy all request to localhost:8888return "PROXY localhost:8888";};// you can also create a HttpClient to dio// return HttpClient();
};

完整的示例请查看这里.

Https证书校验

有两种方法可以校验https证书,假设我们的后台服务使用的是自签名证书,证书格式是PEM格式,我们将证书的内容保存在本地字符串中,那么我们的校验逻辑如下:

String PEM="XXXXX"; // certificate content
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate  = (client) {client.badCertificateCallback=(X509Certificate cert, String host, int port){if(cert.pem==PEM){ // Verify the certificatereturn true;}return false;};
};

X509Certificate是证书的标准格式,包含了证书除私钥外所有信息,读者可以自行查阅文档。另外,上面的示例没有校验host,是因为只要服务器返回的证书内容和本地的保存一致就已经能证明是我们的服务器了(而不是中间人),host验证通常是为了防止证书和域名不匹配。

对于自签名的证书,我们也可以将其添加到本地证书信任链中,这样证书验证时就会自动通过,而不会再走到badCertificateCallback回调中:

(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate  = (client) {SecurityContext sc = SecurityContext();//file is the path of certificatesc.setTrustedCertificates(file);HttpClient httpClient = HttpClient(context: sc);return httpClient;
};

注意,通过setTrustedCertificates()设置的证书格式必须为PEM或PKCS12,如果证书格式为PKCS12,则需将证书密码传入,这样则会在代码中暴露证书密码,所以客户端证书校验不建议使用PKCS12格式的证书。

Http2支持

dio_http2_adapter 包提供了一个支持Http/2.0的Adapter,详情可以移步 dio_http2_adapter 。

请求取消

你可以通过 cancel token 来取消发起的请求:

CancelToken token = CancelToken();
dio.get(url, cancelToken: token).catchError((DioError err){if (CancelToken.isCancel(err)) {print('Request canceled! '+ err.message)}else{// handle error.}});
// cancel the requests with "cancelled" message.
token.cancel("cancelled");

注意: 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。

完整的示例请参考取消示例.

继承 Dio class

Dio 是一个拥有factory 构造函数的接口类,因此不能直接继承 Dio ,但是可以通过 DioForNative 或DioForBrowser 来间接实现:

import 'package:dio/dio.dart';
import 'package:dio/native_imp.dart'; //在浏览器中, import 'package:dio/browser_imp.dart'class Http extends DioForNative {Http([BaseOptions options]):super(options){// 构造函数做一些事}
}

我们也可以直接实现 Dio接口类 :

class MyDio with DioMixin implements Dio{// ...
}

Flutter 网络请求框架dio使用详解相关推荐

  1. 安卓项目实战之强大的网络请求框架okGo使用详解(一):实现get,post基本网络请求,下载上传进度监听以及对Callback自定义的深入理解

    1.添加依赖 //必须使用 compile 'com.lzy.net:okgo:3.0.4'//以下三个选择添加,okrx和okrx2不能同时使用,一般选择添加最新的rx2支持即可 compile ' ...

  2. Flutter实战之网络请求框架Dio入门使用

    本篇博文涉及到的demo很简单,就是通过调用天气查询接口来显示城市的天气信息.通过本demo可以了解: 1.CityPicker的简单使用 2.Dio网络请求库的简单使用 3.Flutter对json ...

  3. Flutter网络请求库DIO入门文档(1),android开发网

    }); response = await dio.post("http/test/upload", data: formData); //上传多个文件 formData = For ...

  4. Flutter网络请求库DIO入门文档,实战案例

    var request = await httpClient.getUrl(Uri.parse(url)); var response = await request.close(); if (res ...

  5. Python网络请求urllib和urllib3详解

    1. 简介 urllib是Python中请求url连接的官方标准库,在Python2中主要为urllib和urllib2,在Python3中整合成了urllib. 而urllib3则是增加了连接池等功 ...

  6. android网络请求库volley方法详解

    使用volley进行网络请求:需先将volley包导入androidstudio中 File下的Project Structrue,点加号导包 volley网络请求步骤: 1. 创建请求队列     ...

  7. Flutter网络请求库DIO的使用

    1. 导入dio包 目前dio库的最新版本是3.0.1,同使用其他三方库一样,Flutter中使用dio库同样需要配置pubspec.yaml文件. dependencies:flutter:sdk: ...

  8. Android 网络请求HttpURLConnection 和 HttpClient详解

    Android一般通过http协议向服务端接口发送请求,常用有POST和GET传输方式.这种请求通常借助于HttpClient,HttpClient 是 Apache Jakarta Common 下 ...

  9. SpringBoot - 网络请求客户端WebClient使用详解

    在 Spring 5 之前,如果我们想要调用其他系统提供的 HTTP 服务,通常可以使用 Spring 提供的 RestTemplate 来访问,不过由于 RestTemplate 是 Spring ...

最新文章

  1. vc连接数据库,对数据的基本操作
  2. memcached在windows下的基本使用方法
  3. 手把手玩转协同编辑(1):AST (Address Space Transformation)地址空间转换算法 基本介绍...
  4. wait/notify的基本使用
  5. 美股周一暴跌触发熔断:苹果、特斯拉股价大跌
  6. 电商夏季促销海报设计PSD模板,分解教你如何设计
  7. Vue中watch的使用
  8. 以整体思维看问题:解决单页应用,系统角色请求覆盖身份唯一标识(本项目中是session_id命名的)发送请求问题
  9. 自学python能找到工作吗-25岁从零开始学习python还能找到工作吗?
  10. 代码整洁之道-第5章-格式-读书笔记
  11. 漫谈android系统(4)bring up panel
  12. 微机实验报告2 显示程序实验
  13. 淘宝店铺固定背景代码
  14. Java 操作Word书签(三):用文本、图片、表格替换书签
  15. 关于人脸识别的最全研究!
  16. 渗透测试必备google插件
  17. EndNote实现章节后插入参考文献的方法
  18. 遥感监测草原产草量的方法
  19. 抖音怎么去赚钱?小白也能快速上手的抖音测试号项目。丨国仁网络资讯
  20. 它是最给力的数据分析体系,却被90%的新人忽略!

热门文章

  1. c语言分数乘法用指针,分数乘法练习题全套(整理打印版)
  2. 游戏服务器信息失败怎么回事,请重试应该怎么办呢? 解决方法: 遇到这样的情况一般是由于本地网络不正常、游戏服务器不正常或客户端出错导致...
  3. 终于弄个网站关于建站讨论学习(互联网互助联盟建站之家)
  4. 初学计算机网络(一):使用cmd命令
  5. PAD的EDGE插件的坑
  6. Linux中查看二进制文件
  7. python绘制图形微格课_什么是微格教学-海天清泓                          -51CTO博客...
  8. Javaweb学习(一)
  9. matlab在centos7中安装的提示错误
  10. Educational Codeforces Round 141 (Rated for Div. 2) 赛时口胡思路(青大acmer训练日常)小上一波分