axios经常封装使用 你了解axios的原理吗 axios源码解析
liebian365 2024-10-22 15:41 17 浏览 0 评论
一、axios的使用
安装:
npm install axios
使用:
//引入axios
const axios = require('axios');
import axios from 'axios';
关于axios的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:
发送请求
import axios from 'axios';
axios(config) // 直接传入配置
axios(url[, config]) // 传入url和配置
axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置
axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置
axios.request(option) // 调用 request 方法
const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力
axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 调用 all 和传入 spread 回调
拦截器,类似于中间件的概念,是axios的核心功能之一,主要是分为两种:请求拦截器和响应拦截器。有了拦截器,我们能在网络请求之前,对网络请求配置做出处理。在返回数据之前,对返回的数据做处理。
中间件,拦截器:
一般用于对一个目标方法的前置或后置切片操作,可以将一些额外的脏逻辑写到其他的文件中管理,提高目标方法的简洁性。
请求拦截器
axios.interceptors.request.use(function (config) {
// 这里写发送请求前处理的代码
return config;
}, function (error) {
// 这里写发送请求错误相关的代码
return Promise.reject(error);
});
响应拦截器
axios.interceptors.response.use(function (response) {
// 这里写得到响应数据后处理的代码
return response;
}, function (error) {
// 这里写得到错误响应处理的代码
return Promise.reject(error);
});
取消请求
// 方式一
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('xxxx', {
cancelToken: source.token
})
// 取消请求 (请求原因是可选的)
source.cancel('主动取消请求');
// 方式二
const CancelToken = axios.CancelToken;
let cancel;
axios.get('xxxx', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
cancel('主动取消请求');
默认配置
请求配置可以在每个请求中单独设置,也可以在defaults中为全局设置。
//默认baseUrl
axios.defaults.baseUrl = 'https://jsonplaceholder.typicode.com';
//默认超时时间
axios.defaults.timeout = 3000;
//默认Authorization头
axios.defaults.headers.common['Authorization'] = 'AUTH_TOKEN';
数据转换
请求数据转换
axios.defaults.transformRequest.push((data, headers)=>{
//处理请求的data
return data;
});
返回数据转换
axios.defaults.transformResponse.push((data, headers)=>{
//处理返回的data
return data;
});
请求配置
在上一步的发起请求的方法中,我们都能看到config这个配置参数,通过设置这个参数的值,可以达到配置请求的目的。在axios中,config是沟通调用方和网络库的桥梁,
常用的配置项如下所示:
{
// `url` 是用于请求的服务器 URL,相对路径/绝对路径
url: '/api/users',
// `method` 是创建请求时使用的http方法,包括get, post, put, delete等
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
name: 'John'
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'John'
},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求花费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `adapter` 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` 表示用于响应数据的解码方式
responseEncoding: 'utf8', // default
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `cancelToken` 指定用于取消请求的 cancel token
cancelToken: new CancelToken(function (cancel) {
}),
...
}
JAVASCRIPT 复制 全屏
二、实现一个简易版axios
构建一个Axios构造函数,核心代码为request
class Axios {
constructor() {
}
request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
}
xhr.send(data);
})
}
}
导出axios实例
// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
return req;
}
// 得到最后的全局变量axios
let axios = CreateAxiosFn();
上述就已经能够实现axios({ })这种方式的请求
下面是来实现下axios.method()这种形式的请求
// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
Axios.prototype[met] = function() {
console.log('执行'+met+'方法');
// 处理单个方法
if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
return this.request({
method: met,
url: arguments[0],
...arguments[1] || {}
})
} else { // 3个参数(url[,data[,config]])
return this.request({
method: met,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
}
}
})
将Axios.prototype上的方法搬运到request上
首先实现个工具类,实现将b方法混入到a,并且修改this指向
const utils = {
extend(a,b, context) {
for(let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
}
}
}
}
修改导出的方法
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
// 增加代码
utils.extend(req, Axios.prototype, axios)
return req;
}
构建拦截器的构造函数
class InterceptorsManage {
constructor() {
this.handlers = [];
}
use(fullfield, rejected) {
this.handlers.push({
fullfield,
rejected
})
}
}
实现axios.interceptors.response.use和axios.interceptors.request.use
class Axios {
constructor() {
// 新增代码
this.interceptors = {
request: new InterceptorsManage,
response: new InterceptorsManage
}
}
request(config) {
...
}
}
执行语句axios.interceptors.response.use和axios.interceptors.request.use的时候,实现获取axios实例上的interceptors对象,然后再获取response或request拦截器,再执行对应的拦截器的use方法
把Axios上的方法和属性搬到request过去
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
// 混入方法, 处理axios的request方法,使之拥有get,post...方法
utils.extend(req, Axios.prototype, axios)
// 新增代码
utils.extend(req, axios)
return req;
}
现在request也有了interceptors对象,在发送请求的时候,会先获取request拦截器的handlers的方法来执行
首先将执行ajax的请求封装成一个方法
request(config) {
this.sendAjax(config)
}
sendAjax(config){
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
获得handlers中的回调
request(config) {
// 拦截器和请求组装队列
let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理
// 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fullfield, interceptor.rejected)
})
// 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fullfield, interceptor.rejected)
})
// 执行队列,每次执行一对,并给promise赋最新的值
let promise = Promise.resolve(config);
while(chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
chains大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']这种形式
这样就能够成功实现一个简易版axios
三、源码分析
首先看看目录结构
axios发送请求有很多实现的方法,实现入口文件为axios.js
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用
// Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
var instance = bind(Axios.prototype.request, context);
// 把Axios.prototype上的方法扩展到instance对象上,
// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// 把context对象上的自身属性和方法扩展到instance上
// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
// 这样,instance 就有了 defaults、interceptors 属性。
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);
// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
module.exports = axios;
主要核心是 Axios.prototype.request,各种请求方式的调用实现都是在 request 内部实现的, 简单看下 request 的逻辑
Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
// 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的config
if (typeof config === 'string') {
config = arguments[1] || {};
// 把 url 放置到 config 对象中,便于之后的 mergeConfig
config.url = arguments[0];
} else {
// 如果 config 参数是否是 字符串,则整体都当做config
config = config || {};
}
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法
config.method = config.method ? config.method.toLowerCase() : 'get';
/*
something... 此部分会在后续拦截器单独讲述
*/
};
// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
request入口参数为config,可以说config贯彻了axios的一生
axios 中的 config主要分布在这几个地方:
- 默认配置 defaults.js
- config.method默认为 get
- 调用 createInstance 方法创建 axios实例,传入的config
- 直接或间接调用 request 方法,传入的 config
// axios.js
// 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);
// 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Axios.js
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法
config.method = config.method ? config.method.toLowerCase() : 'get';
从源码中,可以看到优先级:默认配置对象default < method:get < Axios的实例属性this.default < request参数
下面重点看看request方法
Axios.prototype.request = function request(config) {
/*
先是 mergeConfig ... 等,不再阐述
*/
// Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重,后续重点
var chain = [dispatchRequest, undefined];
// push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象
var promise = Promise.resolve(config);
// 循环拦截器的链
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器
}
// 返回 promise
return promise;
};
拦截器interceptors是在构建axios实例化的属性
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(), // 请求拦截
response: new InterceptorManager() // 响应拦截
};
}
InterceptorManager构造函数
// 拦截器的初始化 其实就是一组钩子函数
function InterceptorManager() {
this.handlers = [];
}
// 调用拦截器实例的use时就是往钩子函数中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null
// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
请求拦截器方法是被 unshift到拦截器中,响应拦截器是被push到拦截器中的。最终它们会拼接上一个叫dispatchRequest的方法被后续的 promise 顺序执行
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
// 判断请求是否已被取消,如果已经被取消,抛出已取消
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url
if (config.baseURL && !isAbsoluteURL(config.url)) {
// 组合baseURL与url形成完整的请求路径
config.url = combineURLs(config.baseURL, config.url);
}
config.headers = config.headers || {};
// 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化
// 比如将headers中的Accept,Content-Type统一处理成大写
// 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type
// 等一系列操作
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 合并不同配置的headers,config.headers的配置优先级更高
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
// 删除headers中的method属性
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法
var adapter = config.adapter || defaults.adapter;
// 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同)
return adapter(config).then(
// 请求正确返回的回调
function onAdapterResolution(response) {
// 判断是否以及取消了请求,如果取消了请求抛出以取消
throwIfCancellationRequested(config);
// 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化
// 例如,使用JSON.parse对响应正文进行解析
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
},
// 请求失败的回调
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
);
};
再来看看axios是如何实现取消请求的,实现文件在CancelToken.js
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。
// 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。
// 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求
executor(function cancel(message) {
// 判断请求是否已经取消过,避免多次执行
if (token.reason) {
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
CancelToken.source = function source() {
// source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
// 返回创建的 CancelToken 实例以及取消方法
return {
token: token,
cancel: cancel
};
};
实际上取消请求的操作是在 xhr.js 中也有响应的配合的
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 取消请求
request.abort();
reject(cancel);
});
}
巧妙的地方在 CancelToken中 executor 函数,通过resolve函数的传递与执行,控制promise的状态
小结
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)