猫哥不懂技术

莫催稿,催稿也不交

CodeTyphon 实现访问数据服务

目前绝大多数应用程序都具备访问服务器的能力,不论是请求远程给予数据支持,又或者是单纯的检查新版本,都需要通过客户端来向服务器端发起请求,并得到服务端的回应。可以这么说,在互联网的时代,完全不依赖网络的应用程序已经很难很难生存,几乎已经绝迹了。

之前我们已经掌握了如何来开发一个服务器端应用,以及如何将它部署到生产环境中。现在是时候让服务端与客户端进行数据交互了,让我们来看一下如何让客户端程序获取服务器端的数据,将员工管理工具改成具有网络交互属性的程序。


『建立数据服务器』

还是像以前一样,先新建一个 Http Server Application,即独立的服务器端应用。一般在开发时,都会直接建立独立服务器以方便调试,只有在调试通过后,才修改应用类型,并进行部署操作。

重复的步骤不再赘述,这里需要做的是与数据库进行连接,所以在 WebModule 内放置数据库组件:

图1 放置数据库组件

按图2所示设置组件的属性,并完成组件之间的关联:

图2 设置属性

与开发桌面应用时一样,进行数据库的连接,当然作为服务器来说,并不需要所谓的数据感知组件,因此也没有必要放置TDataSource或是任何可视化组件,直接对数据集进行访问即可。

此处我们设计的是,使用一个TSQLQuery来完成所有的查询工作,在此处不需要在设计期就写入SQL语句并打开数据集,它仅在接收到客户端请求时才工作,SQL语句也是动态变化的。

我们先提供一个查询员工列表的方法,来看看服务器如何输出数据,编写一个服务端用的GetEmpList方法,它和客户端和同名方法有比较大的区别:

图3 服务端的GetEmpList方法

这里我们采用Json格式作为返回值,它包含两个对象,result用于指示请求的执行情况,请求执行成功返回ok,后面的data用于保存返回的具体数据。以此拼好一个Json字符串并予以返回。

然后只需要编写WebModule的OnRequest事件即可:

图4 编写OnRequest

看到这里应该明白了,当前的服务器接收请求时,必须先带上action参数,该参数用于指出要执行的方法,后面的参数都是执行具体方法时所需参数。

最后就是注册HTTP模块并运行服务器了:

图5 注册HTTP模块


『测试API接口』

上面我们已经完成了一个返回Json数据的服务器程序,一般情况下,我们把这种纯粹返回数据的请求方式称为API接口。下面来做个简单的测试,用浏览器发起请求:

http://localhost:12300/employee?action=getemplist

若是看到以下界面,即表示数据已顺利返回,不要觉得奇怪,这里没有HTML页面,服务器的设计就是返回纯数据。

图6 浏览器测试API请求


『让客户端来请求』

关闭上面的服务器端项目,打开员工管理工具项目,新增一个 pascal unit,并加入一个 HTTP 请求的方法框架。对于需要请求网络的情况,我们需要引用 fphttp 和 fphttpclient 单元:

图7 新建API单元

然后填完 CallAPI 方法,主要是对 TFPHTTPClient 这个类的应用,参考一下类的源码就很容易知道它的作用了。在 CodeTyphon 中,并不是所有的类都有文档,有些东西必须靠自己研究发现,通常情况下,我们可以借助 Code Ocean 来快速的找到自己想到的示例。

图8 实现HTTP请求

简单讲一下代码的核心思想,先生成 TFPHTTPClient 对象的实例,使用其 Get 方法来完成GET请求,这其中同时也完成了对参数字符串的拼装。在请求返回时,判断状态码是否 200,如果不是则将返回的内容清空,以避免服务器返回无法被解析成 Json 的内容时,造成客户端异常。在此处其实也已经决定了客户端解析的方案,如果返回为空字符串,则不进行解析,否则就按 Json 进行解析。

这个 CallAPI 方法一经写好,它可以适应任意的 GET 请求,我们切换到 frm_main 的代码处,将客户端的 GetEmpList 方法修改了:

图9 修改客户端代码

将原本直接操作数据库的方法修改为向服务器端请求,并解析返回的 Json 数据予以显示。这段代码看起来比直接操作数据库复杂多了,是不是很希望将它改得和以前一样简单?别担心,后面会讲到封装的方法,如果每个请求都要写那么多类似结构的代码,那么这样的开发者并不合格。

现在还需要多做一件事,由于数据从服务器而来,就必须考虑到网络的问题,比如网速变慢,则请求响应的时间也会变长,在主线程里直接请求就会卡住界面。这个时候我们需要将请求放到线程里执行,当请求完毕时再通知主界面刷新,让网络请求对界面的影响减到最小。


『使用多线程』

来声明一个线程类,这个线程可以跟据传入的 action 和参数来执行 CallAPI 方法,并在执行完成后,发送回调。由于线程类原本就要求实现子类来完成具体任务,我们必须继承 TThread 并覆盖它的抽象方法,同时需要一个自定义的回调函数来完成这一切:

图10 声明线程内和回调函数

然后按下 Ctrl+Shift+C 快捷键,让IDE自动生成实现代码框架,我们直接将代码填完,首先是构造函数,在此它起到保存参数供后续调用的作用:

图11 线程的构造函数

构造函数内,FreeOnTerminate 指出了线程的管理方式,当线程终止时自动释放,这个时候我们无须手动释放线程,也不会造成内存泄漏。OnTerminate 回调指出了当线程终止时,要执行的方法,在此我们是希望执行我们自己定义的 Callback 回调,需要从系统提供的回调中跳转。

图12 调用自定义的回调函数

在系统的回调里,调用自定义的回调。最后是实现抽象方法 Execute,在 TThread 基类中,该方法未被实现,因为基类不知道线程要做什么具体的事情,所以将执行方法留空了,继承线程类时,自然已经明确具体要执行任务,可以填完 Execute 方法:

图13 实现Execute方法

完成线程的实现后,我们再一次切换回 frm_main 的代码,对 GetEmpList 方法作一些修改:

图14 修改GetEmpList方法

一下子减掉了很多代码,把那些代码都放到 GetEmpListCallback 内执行,也就是在线程执行完成时给予的回调了:

图15 线程回调


『使用框架来解析返回数据』

下面我们来把服务器端补全,关掉员工管理工具项目,打开服务器端项目。跟据客户端已实现的功能点,我们需要在服务器端也实现新增、编辑、删除员工信息,获取员工具体信息,给员工发奖金等功能。还是先完成函数的声明:

图16 服务器端方法声明

下面来逐一实现,其实都是数据库操作,除了查询操作返回具体数据外,其他的操作均只返回成功或失败,即 Json 内 result 对象为 ok 或 fail。

图17 新增员工信息的服务器端方法

修改与删除的代码与此类似,均是通过SQL语句来实现,类似的方法不再给出具体代码,请各位开发者自行思考并补完它们(参考文末附件源码内的实现)。下面来看一下 GetEmployeeInfo 方法,这个方法将做较多的事,包括计算员工奖金:

图18 获取员工详细信息

这个方法着实有点复杂了,返回的数据也是对象和数组嵌套,在实际开发中,这种情况也相当多见,毕竟还是要实现需求为优先。到了现在,一个合理的数据解析框架变得非常必要。不能再像之前那样发送请求后直接解析 Json 了,那样太复杂,我们需要更高效且简单的机制。

FCL为我们提供了非常好的运行时(RTTI)机制,可以动态的获取类的信息,包括类的成员列表,方法等。知道了这些很显然的就是要将 Json 数据自动填入,跟据这一思想,就可以先写出从 Json 填数据到类的方法,也可以逆向的将类成员变成 Json 对象:

图19 从Json填充对象

这个方法实现了从 Json 来填充对象,那么对于 Json 字符串,也同样简单:

图20 从Json字符串填充对象

现在可以试试反过来,从对象生成 Json 字符串:

图21 从对象生成Json字符串

以上准备工作做好后,就可以尝试来写一个简单的框架,来适应数据的解析。之前已经定义过了 API 的返回格式,必定包含 result 字段,可能包含 data 字段,data 可能是对象也可能是数组,且对象和数组可以相互包含。听上去很复杂,但是实际处理时,我们完全可以忽略掉具体对象,也就是说,要使用泛型。先写一个模板,把 result 这个明确的成员定义好,同时对于 data 的内容,需要能从 Json 来填充,也需要定义一个接口:

图22 定义接口

在这里接口的作用是规范子类,将 data 的内容进行统一,虽然不知道 data 的内容到底是什么,但是它们都必须可以从 Json 进行填充。有了这个基础,就可以写出完整的 API 返回处理类了:

图23 定义API返回处理类

看到这里应该已经很明确了,属性内的 APIResult 即是对应 Json 内的 result 字段,而 Data 即是对应 Json 内的 data 字段,由于不知道具体类型,这里使用泛型来指代。下面我们直接实现它,很简单的:

图24 实现从Json解析

最后别忘了释放掉那个未知的 Data,避免产生内存泄漏:

图25 释放Data

好了,现在我们就可以去解析 Json 了,直接把服务器端返回的数据解析成类。先来个简单的,处理掉没有 data 字段的情况:

图26 解析通用返回

看上去似乎这个类什么事都没做,只是实现了 IAPIIntf 而已。其实这已经够了,因为框架主要讲的是结构,满足结构自然会被解析出来,针对没有 data 的情况,自然就不用做任何处理了。

下面再来看一下如何解析 GetEmpList 方法的返回值,该方法返回的 data 是一个数组,数组内的对象也需要被定义:

图27 定义GetEmpList方法的返回解析

这样一看就很明确了,注意在此所有的对象都要继承自 TInterfacedObject 并实现 IAPIIntf,在实现 List 的填充时,也是调用了被填充类的 fill 方法,该方法来自接口,也是一个规范统一的处理。需要注意的是,这里出现了一种新的访问区分符 published,它和 public 类似,都是允许公开访问,但是 published 同时也允许了将其分管的属性写入 RTTI。换言之,只有 published 下的属性才能够被 RTTI 访问到。

最后实现的 TResultEmpList 是泛型类 TBaseAPIResult 的具现化类,将 TModuleEmpList 填入其中,则在 TBAseAPIResult 处理时,会自动将T替换成填入的类型。在 Free Pascal 中,泛型类必须由其具现化类来实现。

图28 实现填充

别忘了实现 Destroy 方法,将数组内的元素释放掉,否则会产生内存泄漏。不急着写最后那个方法,先测试一下它能否正确工作,我们在客户端的 GetEmpListCallback 方法内使用这个新的解析方案:

图29 修改为新的解析方案

运行一下程序就可以看到效果了,现在就不再需要面对丑陋的解析 Json 的代码,交给框架去做吧,以对象形式进行操作给了开发者相当好的体验。现在我们把最后的功能点,即获取员工具体信息的解析,把它实现了:

图30 定义GetEmpInfo的解析类

这里所有的工作量均在字段,输入这些字段是比较累人的体力活,要善用 IDE 提供的快捷键,如 Ctrl+Space 补全单词和 Ctrl+Shift+C 自动补全类,熟练后如果你打字的速度不那么慢的话,2分钟内足够完成这些输入了。

接下去依然是实现,只看最关键的一部分即已足够:

图31 解析GetEmpInfo

这段代码的含义即是,先通过 TJsonUtils,也就是我们刚才写的一个从 Json 来填充对象的单元,来完成对简单成员的填充。然后再建立 List 这个复杂对象的实例,再进行填充。

现在再尝试把客户端内 lstEmpClick 方法修改一下,改为从服务器请求数据,并且解析后显示出来,在这里我们同样需要使用线程。由于这个项目实在简单,前面设计的线程类依然可以使用,如果项目很复杂,重新继承一个线程类会更好。

图32  修改lstEmpClick方法

由于现在的数据来自服务器端,而不是直接来自数据库,我们需要去掉界面上的数据感知组件,换成普通的组件来替代。另一种解决方案是使用内存数据集,获取到数据后再写到内存里,使用这种方案可以保持使用数据感知组件。此处使用的方案是在 lfm 内直接替换相关组件。

图33 解析返回的内容并显示

运行起来看看效果,已可以看到,当点击左侧列表内的员工名称时,右侧即可显示出员工的详细信息:

图34 运行效果


『全面修改为从服务器获取数据』

继续动手把其他的功能点全部改成通过服务器进行,有了上面的经验,接下去的改动就显得简单了,我们只看新增和修改员工信息。首先改动新增修改窗口的构造函数,我们不再通过按钮来判断是否进入编辑状态,而是通过员工编号,若传入的员工编号不为 0,则进入编辑状态,否则进入新增状态:

图35 修改构造函数

针对编辑的情况,发起请求获取员工信息,和主窗口一样,在回调函数内将数据予以显示。当然此处还可以通过让主窗口传入 TResultEmpInfo 对象,直接进行编辑,这样主窗口需要有一个变量来保存已选定的员工信息。

图36 在回调中显示要编辑的数据

然后实现提交数据,与直接操作数据库不同,不能将新增与编辑混为一谈了。也就是我们需要分别实现新建与修改的请求,并且获取服务器给出的返回信息,当然它们的回调函数可以用同一个,最初我们在调计时,就已经考虑到了多个请求使用同一个回调的可能性,在回调内返回 action 的值以用作区分:

图37 发起保存数据请求

然后在回调函数中决定下一步操作,按原逻辑,若是保存数据成功,则关闭窗口,若不成功,则弹出提示:

图38 处理服务器返回的结果

对于服务器端的改动不再赘述,请各位开发者参考之前的改法,自行修改 WebModule 的 OnRequest 回调,以使其可以接收处理 action 为 addemp 和 editemp 的请求。在本书配套源码内,已包含了完整的源码样例,可供参考。

通过客户端访问服务器是一项非常有用的技能,现在我们的员工管理工具已经成为了一个互联网应用,不需要在本地架设数据库了。当服务端程序被部署后,即可以通过IP地址或域名来访问相关服务。如果感兴趣的话,不妨再为员工管理工具增加一个检查新版本的功能,使它向互联网方向再靠得更近一些。


样例代码下载:点击此处


发表评论:

Powered By Z-BlogPHP 1.5.1 Zero

Copyright Rarnu 2017. All Rights Reserved.