首页 文章 精选 留言 我的

精选列表

搜索[K2.6模型],共10000篇文章
优秀的个人博客,低调大师

前后分离模型之封装 Api 调用

Ajax 和异步处理 调用 API 访问数据采用的 Ajax 方式,这是一个异步过程,异步过程最基本的处理方式是事件或回调,其实这两种处理方式实现原理差不多,都需要在调用异步过程的时候传入一个在异步过程结束的时候调用的接口。比如 jQuery Ajax 的 success 就是典型的回调参数。不过使用 jQuery 处理异步推荐使用 Promise 处理方式。 Promise 处理方式也是通过注册回调函数来完成的。jQuery 的 Promise 和 ES6 的标准 Promise 有点不一样,但在 then 上可以兼容,通常称为 thenable。jQuery 的 Promise 没有提供 .catch() 接口,但它自己定义的 .done()、.fail() 和 .always() 三个注册回调的方式也很有特色,用起来很方便,它是在事件的方式来注册的(即,可以注册多个同类型的处理函数,在该触发的时候都会触发)。 当然更直观的一点的处理方式是使用 ES2017 带来的 async/await 方式,可以用同步代码的形式来写异步代码,当然也有一些坑在里面。对于前端工程师来说,最大的坑就是有些浏览器不支持,需要进行转译,所以如果前端代码没有构建过程,一般还是就用 ES5 的语法兼容性好一些(jQuery 的 Promise 是支持 ES5 的,但是标准 Promise 要 ES6 以后才可以使用)。 关于 JavaScript 异步处理相关的内容可以参考 从小小题目逐步走进 JavaScript 异步调用 闲谈异步调用“扁平”化 从地狱到天堂,Node 回调向 async/await 转变 理解 JavaScript 的 async/await 从不用 try-catch 实现的 async/await 语法说错误处理 自己封装工具函数 在处理 Ajax 的过程中,虽然有现成的库(比如 jQuery.ajax,axios 等),它毕竟是为了通用目的设计的,在使用的时候仍然不免繁琐。而在项目中,对 Api 进行调用的过程几乎都大同小异。如果设计得当,就连错误处理的方式都会是一样的。因此,在项目内的 Ajax 调用其实可以进行进一步的封装,使之在项目内使用起来更方便。如果接口方式发生变化,修改起来也更容易。 比如,当前接口要求使用 POST 方法调用(暂不考虑 RESTful),参数必须包括 action,返回的数据以 JSON 方式提供,如果出错,只要不是服务器异常都会返回特定的 JSON 数据,包括一个不等于 0 的 code 和可选的 message 属性。 那么用 jQuery 写这么一个 Ajax 调用,大概是这样 const apiUrl = "http://api.some.com/"; jQuery .ajax(url, { type: "post", dataType: "json", data: { action: "login", username: "uname", password: "passwd" } }) .done(function(data) { if (data.code) { alert(data.message || "登录失败!"); } else { window.location.assign("home"); } }) .fail(function() { alert("服务器错误"); }); 初步封装 同一项目中,这样的 Ajax 调用,基本上只有 data 部分和 .done 回调中的 else 部分不同,所以进行一次封装会大大减少代码量,可以这样封装 function appAjax(action, params) { var deffered = $.Deferred(); jQuery .ajax(apiUrl, { type: "post", dataType: "json", data: $.extend({ action: action }, params) }) .done(function(data) { // 当 code 为 0 或省略时,表示没有错误, // 其它值表示错误代码 if (data.code) { if (data.message) { // 如果服务器返回了消息,那么向用户呈现消息 // resolve(null),表示不需要后续进行业务处理 alert(data.message); deffered.resolve(); } else { // 如果服务器没返回消息,那么把 data 丢给外面的业务处理 deferred.reject(data); } } else { // 正常返回数据的情况 deffered.resolve(data); } }) .fail(function() { // Ajax 调用失败,向用户呈现消息,同时不需要进行后续的业务处理 alert("服务器错误"); deffered.resolve(); }); return deferred.promise(); } 而业务层的调用就很简单了 appAjax("login", { username: "uname", password: "passwd" }).done(function(data) { if (data) { window.location.assign("home"); } }).fail(function() { alert("登录失败"); }); 更换 API 调用接口 上面的封装对调用接口和返回数据进行了统一处理,把大部分项目接口约定的内容都处理掉了,剩下在每次调用时需要处理的就是纯粹的业务。 现在项目组决定不用 jQuery 的 Ajax,而是采用 axios 来调用 API(axios 不见得就比 jQuery 好,这里只是举例),那么只需要修改一下 appAjax() 的实现即可。所有业务调用都不需要修改。 假设现在的目标环境仍然是 ES5,那么需要第三方 Promise 提供,这里拟用 Bluebird,兼容原生 Promise 接口(在 HTML 中引入,未直接出现在 JS 代码中)。 function appAjax(action, params) { var deffered = $.Deferred(); axios .post(apiUrl, { data: $.extend({ action: action }, params) }) .then(function(data) { ... }, function() { ... }); return deferred.promise(); } 这次的封装采用了 axios 来实现 Web Api 调用。但是为了保持原来的接口(jQuery Promise 对象有提供 .done()、.fail() 和 .always() 事件处理),appAjax 仍然不得不返回 jQuery Promise。这样,即使所有地方都不再需要使用 jQuery,这里仍然得用。 项目中应该用还是不用 jQuery?请阅读为什么要用原生 JavaScript 代替 jQuery? 去除 jQuery 就只在这里使用 jQuery 总让人感觉如芒在背,想把它去掉。有两个办法 修改所有业务中的调用,去掉 .done()、.fail() 和 .always(),改成 .then()。这一步工作量较大,但基本无痛,因为 jQuery Promise 本身支持 .then()。但是有一点需要特别注意,这一点稍后说明 自己写个适配器,兼容 jQuery Promise 的接口,工作量也不小,但关键是要充分测试,避免差错。 上面提到第 1 种方法中有一点需要特别注意,那就是 .then() 和 .done() 系列函数在处理方式上有所不同。.then() 是按 Promise 的特性设计的,它返回的是另一个 Promise 对象;而 .done() 系列函数是按事件机制实现的,返回的是原来的 Promise 对象。所以像下面这样的代码在修改时就要注意了 appAjax(url, params) .done(function(data) { console.log("第 1 处处理", data) }) .done(function(data) { console.log("第 2 处处理", data) }); // 第 1 处处理 {} // 第 2 处处理 {} 简单的把 .done() 改成 .then() 之后(注意不需要使用 Bluebird,因为 jQuery Promise 支持 .then()) appAjax(url, params) .then(function(data) { console.log("第 1 处处理", data); }) .then(function(data) { console.log("第 2 处处理", data); }); // 第 1 处处理 {} // 第 2 处处理 undefined 原因上面已经讲了,这里正确的处理方式是合并多个 done 的代码,或者在 .then() 处理函数中返回 data: appAjax(url, params) .then(function(data) { console.log("第 1 处处理", data); return data; }) .then(function(data) { console.log("第 2 处处理", data); }); 使用 Promise 接口改善设计 我们的 appAjax() 接口部分也可以设计成 Promise 实现,这是一个更通用的接口。既使用不用 ES2015+ 特性,也可以使用像 jQuery Promise 或 Bluebird 这样的三方库提供的 Promise。 function appAjax(action, params) { // axios 依赖于 Promise,ES5 中可以使用 Bluebird 提供的 Promise return axios .post(apiUrl, { data: $.extend({ action: action }, params) }) .then(function(data) { // 这里调整了判断顺序,会让代码看起来更简洁 if (!data.code) { return data; } if (!data.message) { throw data; } alert(data.message); }, function() { alert("服务器错误"); }); } 不过现在前端有构建工具,可以使用 ES2015+ 配置 Babel,也可以使用 TypeScript …… 总之,选择很多,写起来也很方便。那么在设计的时候就不用局限于 ES5 所支持的内容了。所以可以考虑用 Promise + async/await 来实现 async function appAjax(action, params) { // axios 依赖于 Promise,ES5 中可以使用 Bluebird 提供的 Promise const data = await axios .post(apiUrl, { data: $.extend({ action: action }, params) }) // 这里模拟一个包含错误消息的结果,以便后面统一处理错误 // 这样就不需要用 try ... catch 了 .catch(() => ({ code: -1, message: "服务器错误" })); if (!data.code) { return data; } if (!data.message) { throw data; } alert(data.message); } 上面代码中使用 .catch() 来避免 try ... catch ... 的技巧在从不用 try-catch 实现的 async/await 语法说错误处理中提到过。 当然业务层调用也可以使用 async/await(记得写在 async 函数中): const data = await appAjax("login", { username: "uname", password: "passwd" }).catch(() => { alert("登录失败"); }); if (data) { window.location.assign("home"); } 对于多次 .done() 的改造: const data = await appAjax(url, params); console.log("第 1 处处理", data); console.log("第 2 处处理", data); 小结 本文以封装 Ajax 调用为例,看似在讲述异步调用。但实际想告诉大家的东西是:如何将一个常用的功能封装起来,实现代码重用和更简洁的调用;以及在封装的过程中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向公共标准靠拢——不知大家是否有所体会。

优秀的个人博客,低调大师

go-hbase的Scan模型源码分析

git地址在这里:https://github.com/Lazyshot/go-hbase 这是一个使用go操作hbase的行为。 分析scan行为 如何使用scan看下面这个例子,伪代码如下: func scan(phone string, start time.Time, end time.Time) ([]Loc, error) { ... client := hbase.NewClient(zks, "/hbase") client.SetLogLevel("DEBUG") scan := client.Scan(table) scan.StartRow = []byte(phone + strconv.Itoa(int(end.Unix()))) scan.StopRow = []byte(phone + strconv.Itoa(int(start.Unix()))) var locs []Loc scan.Map(func(ret *hbase.ResultRow) { var loc Loc for _, v := range ret.Columns { switch v.ColumnName { case "lbs:phone": loc.Phone = v.Value.String() case "lbs:lat": loc.Lat = v.Value.String() ... } } locs = append(locs, loc) }) return locs, nil } 首先是NewClient, 返回的结构是hbase.Client, 这个结构代表的是与hbase服务端交互的客户端实体。 这里没有什么好看的,倒是有一点要注意,在NewClient的时候,里面的zkRootReginPath是写死的,就是说hbase在zk中的地址是固定的。当然这个也是默认的。 func NewClient(zkHosts []string, zkRoot string) *Client { cl := &Client{ zkHosts: zkHosts, zkRoot: zkRoot, zkRootRegionPath: "/meta-region-server", servers: make(map[string]*connection), cachedRegionLocations: make(map[string]map[string]*regionInfo), prefetched: make(map[string]bool), maxRetries: max_action_retries, } cl.initZk() return cl } 下面是client.Scan client.Scan 返回的是 func newScan(table []byte, client *Client) *Scan { return &Scan{ client: client, table: table, nextStartRow: nil, families: make([][]byte, 0), qualifiers: make([][][]byte, 0), numCached: 100, closed: false, timeRange: nil, } } scan结构: type Scan struct { client *Client id uint64 table []byte StartRow []byte StopRow []byte families [][]byte qualifiers [][][]byte nextStartRow []byte numCached int closed bool //for filters timeRange *TimeRange location *regionInfo server *connection } 设置了开始位置,结束位置,就可以进行Map操作了。 func (s *Scan) Map(f func(*ResultRow)) { for { results := s.next() if results == nil { break } for _, v := range results { f(v) if s.closed { return } } } } 这个map的参数是一个函数f,没有返回值。框架的行为就是一个大循环,不断调用s.next(),注意,这里s.next返回回来的result可能是由多条,然后把这个多条数据每条进行一次实际的函数调用。结束循环有两个方法,一个是next中再也取不到数据(数据已经取完了)。还有一个是s.closed呗设置为true。 s.next() func (s *Scan) next() []*ResultRow { startRow := s.nextStartRow if startRow == nil { startRow = s.StartRow } return s.getData(startRow) } 这里其实是把startRow不断往前推进,但是每次从startRow获取多少数据呢?需要看getData getData 最核心的流程如下: func (s *Scan) getData(nextStart []byte) []*ResultRow { ... server, location := s.getServerAndLocation(s.table, nextStart) req := &proto.ScanRequest{ Region: &proto.RegionSpecifier{ Type: proto.RegionSpecifier_REGION_NAME.Enum(), Value: []byte(location.name), }, NumberOfRows: pb.Uint32(uint32(s.numCached)), Scan: &proto.Scan{}, } ... cl := newCall(req) server.call(cl) ... select { case msg := <-cl.responseCh: return s.processResponse(msg) } } 这里看到有一个s.numCached, 我们猜测这个是用来指定一次call请求调用回多少条数据的。 看call函数 func newCall(request pb.Message) *call { var responseBuffer pb.Message var methodName string switch request.(type) { ... case *proto.ScanRequest: responseBuffer = &proto.ScanResponse{} methodName = "Scan" ... } return &call{ methodName: methodName, request: request, responseBuffer: responseBuffer, responseCh: make(chan pb.Message, 1), } } type call struct { id uint32 methodName string request pb.Message responseBuffer pb.Message responseCh chan pb.Message } 可以看出,这个call是一个有responseBuffer的实际调用者。 下面看server.Call 至于这里的server, 我们不看代码流程了,只需要知道最后他返回的是connection这么个结构 type connection struct { connstr string id int name string socket net.Conn in *inputStream calls map[int]*call callId *atomicCounter isMaster bool } 创建是使用函数newConnection调用 func newConnection(connstr string, isMaster bool) (*connection, error) { id := connectionIds.IncrAndGet() log.Debug("Connecting to server[id=%d] [%s]", id, connstr) socket, err := net.Dial("tcp", connstr) if err != nil { return nil, err } c := &connection{ connstr: connstr, id: id, name: fmt.Sprintf("connection(%s) id: %d", connstr, id), socket: socket, in: newInputStream(socket), calls: make(map[int]*call), callId: newAtomicCounter(), isMaster: isMaster, } err = c.init() if err != nil { return nil, err } log.Debug("Initiated connection [id=%d] [%s]", id, connstr) return c, nil } 好,那么实际上就是调用connection.call(request *call) func (c *connection) call(request *call) error { id := c.callId.IncrAndGet() rh := &proto.RequestHeader{ CallId: pb.Uint32(uint32(id)), MethodName: pb.String(request.methodName), RequestParam: pb.Bool(true), } request.setid(uint32(id)) bfrh := newOutputBuffer() err := bfrh.WritePBMessage(rh) ... bfr := newOutputBuffer() err = bfr.WritePBMessage(request.request) ... buf := newOutputBuffer() buf.writeDelimitedBuffers(bfrh, bfr) c.calls[id] = request n, err := c.socket.Write(buf.Bytes()) ... } 逻辑就是先把requestHeader压入,再压入request.request call只是完成了请求转换成byte传输到hbase服务端,在什么地方进行消息回收呢? 回到NewConnection的方法,里面有个connection.init() func (c *connection) init() error { err := c.writeHead() if err != nil { return err } err = c.writeConnectionHeader() if err != nil { return err } go c.processMessages() return nil } 这里go c.processMessage() func (c *connection) processMessages() { for { msgs := c.in.processData() if msgs == nil || len(msgs) == 0 || len(msgs[0]) == 0 { continue } var rh proto.ResponseHeader err := pb.Unmarshal(msgs[0], &rh) if err != nil { panic(err) } callId := rh.GetCallId() call, ok := c.calls[int(callId)] delete(c.calls, int(callId)) exception := rh.GetException() if exception != nil { call.complete(fmt.Errorf("Exception returned: %s\n%s", exception.GetExceptionClassName(), exception.GetStackTrace()), nil) } else if len(msgs) == 2 { call.complete(nil, msgs[1]) } } } 这里将它简化下: func (c *connection) processMessages() { for { msgs := c.in.processData() call.complete(nil, msgs[1]) } } c.in.processData 是在input_stream.go中 func (in *inputStream) processData() [][]byte { nBytesExpecting, err := in.readInt32() ... if nBytesExpecting > 0 { buf, err := in.readN(nBytesExpecting) if err != nil && err == io.EOF { panic("Unexpected closed socket") } payloads := in.processMessage(buf) if len(payloads) > 0 { return payloads } } return nil } 先读取出一个int值,这个int值判断后面还有多少个bytes,再将后面的bytes读取进入到buf中,进行input_stream的processMessage处理。 我们这里还看到并没有执行我们map中定义的匿名方法。只是把消息解析出来了而已。 call.complete func (c *call) complete(err error, response []byte) { ... err2 := pb.Unmarshal(response, c.responseBuffer) ... c.responseCh <- c.responseBuffer } 这个函数有用的也就这两句话把responseBuffer里面的内容通过管道传递给responseCh 这里就看到getData的时候,被堵塞的地方 select { case msg := <-cl.responseCh: return s.processResponse(msg) } 那么这里就有把获取到的responseCh的消息进行processResponse处理。 func (s *Scan) processResponse(response pb.Message) []*ResultRow { ... results := res.GetResults() n := len(results) ... s.closeScan(s.server, s.location, s.id) ... tbr := make([]*ResultRow, n) for i, v := range results { tbr[i] = newResultRow(v) } return tbr } 这个函数并没有什么特别的行为,只是进行ResultRow的组装。 好吧,这个包有个地方可以优化,这个go-hbase的scan的时候,numCached默认是100,这个对于hbase来说太小了,完全可以调整大点,到2000~10000之间,你会发现scan的性能提升杠杠的。 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6076690.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

OSI七层模型及对应协议

OSI七个层次的功能 物理层 为数据链路层提供物理连接,在其上串行传送比特流,即所传送数据的单位是比特。此外,该层中还具有确定连接设备的电气特性和物理特性等功能。 数据链路层 负责在网络节点间的线路上通过检测、流量控制和重发等手段,无差错地传送以帧为单位的数据。为做到这一点,在每一帧中必须同时带有同步、地址、差错控制及流量控制等控制信息。 网络层 为了将数据分组从源(源端系统)送到目的地(目标端系统),网络层的任务就是选择合适的路由和交换节点,使源的传输层传下来的分组信息能够正确无误地按照地址找到目的地,并交付给相应的传输层,即完成网络的寻址功能。 传输层 传输层是高低层之间衔接的接口层。数据传输的单位是报文,当报文较长时将它分割成若干分组,然后交给网络层进行传输。传输层是计算机网络协议分层中的最关键一层,该层以上各层将不再管理信息传输问题。 会话层 该层对传输的报文提供同步管理服务。在两个不同系统的互相通信的应用进程之间建立、组织和协调交互。例如,确定是双工还是半双工工作。 表示层 该层的主要任务是把所传送的数据的抽象语法变换为传送语法,即把不同计算机内部的不同表示形式转换成网络通信中的标准表示形式。此外,对传送的数据加密(或解密)、正文压缩(或还原)也是表示层的任务。 应用层 该层直接面向用户,是OSI中的最高层。它的主要任务是为用户提供应用的接口,即提供不同计算机间的文件传送、访问与管理,电子邮件的内容处理,不同计算机通过网络交互访问的虚拟终端功能等。 OSI中的层 功能 TCP/IP协议族 应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 表示层 数据格式化,代码转换,数据加密 没有协议 会话层 解除或建立与别的接点的联系 没有协议 传输层 提供端对端的接口 TCP,UDP 网络层 为数据包选择路由 IP,ICMP,RIP,OSPF,BGP,IGMP 数据链路层 传输有地址的帧以及错误检测功能 SLIP,CSLIP,PPP,ARP,RARP,MTU 物理层 以二进制数据形式在物理媒体上传输数据 ISO2110,IEEE802。IEEE802.2 image.png 个人介绍: 高广超:多年一线互联网研发与架构设计经验,擅长设计与落地高可用、高性能、可扩展的互联网架构。 本文首发在 高广超的简书博客 转载请注明! 简书博客 头条号

优秀的个人博客,低调大师

相机的那些事儿 - 概念、模型及标定

说起相机大家都比较熟悉,现在已经是手机的标配和卖点,而且做的非常便捷易用,随便按都能拍出不错的照片,但如果想更手动、更专业一点,或者将相机用于工业应用(如机器视觉、摄影测量等),还是需要了解一下成像方面的东西,本文力求通俗易懂,先介绍一些相机相关的基本概念,然后对相机的标定过程进行简单的阐述。 一、基本概念 1、景深 我们拍照片的时候常有“虚化”的效果,其实就是利用“景深”来突出重点: 上图只有中间部分是清晰的,远景和近景都模糊掉,原理上从下图可以理解 即理论上只有处于镜头焦点距离的景物是成像清晰的,而在焦点前后,光线开始扩散,成像变的模糊,成像点形成一个扩大的圆:弥散圆(circle of confusion),而人眼的分辨能力有限,只有当弥散圆直径大到一定程度,我们才感觉到模糊,比如上图的草地看起来中间一段都是清晰的,这一段“看起来清晰

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册