Flex通信篇——构建企业级HTTP通信层

概述

RIA和SOA是一对绝配。SOA强调把业务以接口方式向外界提供不关注前端的呈现,而RIA则强调用户体现,结合两者优势能够设计出用户体现良好、灵活的、易扩展、易集成的系统。要处理好RIA前端和SOA后端,需要搭建一个健壮的企业级通信层,该层职责:

  • 负责处理RIA前端和SOA后端的数据交互。
  • 封装SOA业务接口,便于开发调用。
  • 采用异步通信方式,SOA业务接口请求返回时进行回调。
  • SOA业务接口调用错误处理机制。

SOA业务接口的设计

  • 不使用webservice,采用自定义数据格式,数据简单些。
  • 针对Flex,返回格式采用xml数据格式方便写。
  • 安全策略:用户调用登录接口后返回登录信息,其中还回信息中包含令牌(session),每次调用登录以外的业务接口都必须传入令牌参数作验证。同一用户,每次登录令牌值都会刷新,即:最后登录的获得使用权。
  • 大部分业务接口采用GET方式调用,少数采用POST方式,后台应该统一处理GET,POST参数。

SOA通信格式的设计

业务接口调用成功返回格式(XML)

<?xml version="1.0" encoding="utf-8">

<rsp>

{业务所需的XML节点信息}

</rsp>

业务接口调用失败返回格式(XML)

<?xml version="1.0" encoding="utf-8">

<error_rsp>

<code>{错误码}</code>

<msg>{错误描述}</msg>

</error_rsp>

业务接口常用参数

  • v:version,业务接口版本号,用于版本控制,同一接口根据不同版本号可以有不同的实现逻辑。
  • f:format,返回格式,指定返回信息的格式,常用的有xml,json,Flex使用xml方便些。
  • api:业务接口名称,用于区分接口类型。
  • t:时间戳,用于解决客户端浏览器缓存问题,服务端不处理该参数。
  • session:令牌/会话码,除了登录接口,其他接口一般都需要这个参数。

调用例子

  • 登录成功
    • Request: http://server1:2009/LCMS2/api.jhtm?api=lcms.user.login&v=2.0&f=xml&uname=Hunk&pwd=123
    • Reponse:

      <?xml version="1.0" encoding="UTF-8" ?>

      <rsp>

      <uid>51</uid>

      <urole>4</urole>

      <session>cbff8aca-8b56-48d7-a740-534f3a84e575</session>

      </rsp>

  • 登录失败
    • Request: http://server1:2009/LCMS2/api.jhtm?api=lcms.user.login&v=2.0&f=xml&uname=User1&pwd=123
    • Reponse:

      <?xml version="1.0" encoding="UTF-8" ?>

      <error_rsp>

      <code>500</code>

      <msg>用户不存在</msg>

      </error_rsp>

  • 获取用户列表
    • Request: http://server1:2009/LCMS2/api.jhtm?api=lcms.user.get&v=2.0&f=xml&session=cbff8aca-8b56-48d7-a740-534f3a84e575&page_no=1&page_size=10
    • Reponse:totalResults是不分页时的查找总数量,返回第一页的item(用户),每页10行

      <?xml version="1.0" encoding="UTF-8" ?>

      <rsp>

      <totalResults>37</totalResults>

      <item><uid>51</uid><uname>hunk</uname><urole>4</urole></item>

      <item><uid>48</uid><uname>hunk4</uname><urole>3</urole></item>

      </rsp>

  • 删除用户
    • Request: http://server1:2009/LCMS2/api.jhtm?api=lcms.user.delete&v=2.0&f=xml&session=cbff8aca-8b56-48d7-a740-534f3a84e575&del_uid=36
    • Reponse:

      <?xml version="1.0" encoding="UTF-8" ?>

      <rsp>

      <true />

      </rsp>

HttpRequest类

该类用于封装请求参数,方便调用,声明为internal,只在包内使用,代码如下:

package cwn.lcms

{

import mx.collections.ArrayCollection;

internal class HttpRequest

{

//用于存放Get参数

private var _GetParams:ArrayCollection = new ArrayCollection();

//用于存放Post参数

private var _PostParams:ArrayCollection = new ArrayCollection();

//根路径

public var RootUrl:String;

private var _Disposed:Boolean = false;

public function HttpRequest(root:String = "")

{

RootUrl = root;

}

//需要对参数进行url加密

private function Encode(value:String):String

{

return encodeURIComponent(value);

}

public function AddGetParam(name:String, value:String):void

{

_GetParams.addItem(name + "=" + Encode(value));

}

public function AddPostParam(name:String, value:String):void

{

_PostParams.addItem(name + "=" + Encode(value));

}

public function GetUrl():String

{

var url:String = RootUrl;

if (url.lastIndexOf("?") < 0)

url += "?";

for (var i:int = 0; i < _GetParams.length; i++)

{

url += _GetParams[i] + "&";

}

if (_GetParams.length > 0)

url = url.substr(0, url.length - 1);

return url;

}

public function get HasPostData():Boolean

{

return _PostParams.length > 0;

}

public function GetPostData():String

{

if (!HasPostData)

return null;

var data:String = "";

for (var i:int = 0; i < _PostParams.length; i++)

{

data += _PostParams[i] + "&";

}

data = data.substr(0, data.length - 1);

return data;

}

public function Dispose():void

{

if (_Disposed)

return;

_Disposed = true;

_GetParams.removeAll();

_PostParams.removeAll();

_GetParams = null;

_PostParams = null;

}

}

}

各种数据类

定义各种业务接口相关的数据类,把XML节点转成Object,利用IDE智能感知,方便编码,以下是UserData类的定义。

package cwn.lcms.data

{

public class UserData

{

public var Id:String = "";

public var Name:String = "";

public var Password:String = "";

public var Session:String = "";

public var Role:int = 0;

public function UserData(xml:XML = null)

{

FromXml(xml);

}

public function FromXml(xml:XML):void

{

if (xml == null)

return;

Id = xml.uid;

Name = xml.uname;

Role = xml.urole;

Session = xml.session;

}

public function Clone():UserData

{

var data:UserData = new UserData();

data.Id = Id;

data.Name = Name;

data.Password = Password;

data.Session = Session;

data.Role = Role;

return data;

}

}

}

WebAPI类

该类是通信层最核心的类,以静态属性、方法为主,先定义一些主要的属性和工具方法,如下:

package cwn.lcms

{

import cwn.lcms.data.UserData;

import flash.events.Event;

import flash.events.IOErrorEvent;

import flash.events.SecurityErrorEvent;

import flash.external.ExternalInterface;

import flash.net.URLLoader;

import flash.net.URLRequest;

import flash.net.URLVariables;

import mx.collections.ArrayCollection;

public final class WebAPI

{

private static var _Debug:Boolean = false; //调试控制

private static var _HttpCache:ArrayCollection = new ArrayCollection(); //存放http请求

public static var Url:String = null; //根路径

public static var Session:String = null; //会话码

public static var SessionErrorFunction:Function; //会话错误回调方法

public static var CurrentUser:UserData = null; //当前登录用户数据

private static function RemoveCache(value:Object):void

{

var i:int = _HttpCache.getItemIndex(value);

if (i >= 0)

{

var loader:URLLoader = _HttpCache.removeItemAt(i) as URLLoader;

if (loader != null)

{

loader.removeEventListener(IOErrorEvent.IO_ERROR, IOErrorEventHandler);

loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, SecurityErrorHandler);

}

}

}

//执行HTTP请求并缓存请求,callbackXml:Function(result:XML):void

private static function Invoke(http:HttpRequest, callbackXml:Function = null):void

{

if (_Debug)

{

trace(http.GetUrl());

}

var loader:URLLoader = new URLLoader();

_HttpCache.addItem(loader);

loader.addEventListener(IOErrorEvent.IO_ERROR, IOErrorEventHandler);

loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, SecurityErrorHandler);

loader.addEventListener(Event.COMPLETE, function(event:Event):void

{

OnInvokeCallback(loader.data, callbackXml);

RemoveCache(loader);

});

var request:URLRequest = new URLRequest(http.GetUrl());

request.method = "GET";

if (http.HasPostData) //当有Post参数时,改用POST方式请求

{

request.method = "POST";

request.data = new URLVariables(http.GetPostData());

}

loader.load(request);

}

//HTTP请求成功回调,callbackXml:Function(result:XML):void

private static function OnInvokeCallback(response:Object, callbackXml:Function = null):void

{

try

{

if (_Debug)

{

trace("rsp:" + response);

}

var result:XML = XML(response);

if (result.localName() == "rsp") //调用业务接口成功

{

if (callbackXml != null)

callbackXml(result);

}

else if (result.localName() == "error_rsp") //调用业务接口错误

{

if (int(result.code) == 8 && SessionErrorFunction != null) //session错误处理

{

SessionErrorFunction();

LcmsError.ShowInfo("该用户已在其他机器上登录,请重新登录。");

}

else

LcmsError.ShowError(result); //输出错误信息

if (callbackXml != null)

callbackXml(null);

}

else

LcmsError.ShowInfo("[数据格式错误]/r/n" + response); //输出错误信息

}

catch (e:Error)

{

LcmsError.ShowInfo("[未知异常]/r/n" + e.message + "/r/n" + e.getStackTrace()); //输出错误信息

}

}

//HTTP请求错误处理

private static function SecurityErrorHandler(event:SecurityErrorEvent):void

{

LcmsError.ShowInfo("[无访问权限]/r/n" + event.text); //输出错误信息

RemoveCache(event.currentTarget);

}

//HTTP请求错误处理

private static function IOErrorEventHandler(event:IOErrorEvent):void

{

LcmsError.ShowInfo("[服务器连接失败]/r/n" + event.text); //输出错误信息

RemoveCache(event.currentTarget);

}

//创建HTTP请求,并设置必须的参数

private static function CreateRequest(api:String, version:String = "2.0", format:String = "xml"):HttpRequest

{

var http:HttpRequest = new HttpRequest();

http.RootUrl = Url;

http.AddGetParam("api", api);

http.AddGetParam("v", version);

http.AddGetParam("f", format);

var date:Date = new Date();

http.AddGetParam("t", date.time.toString());

return http;

}

}

}

然后,就可以根据业务需要封装所需的接口,下面简单介绍几个接口。

  • 登录

public static function Login(name:String, password:String, callbackXml:Function = null):void

{

var http:HttpRequest = CreateRequest("lcms.user.login");

http.AddGetParam("uname", name);

http.AddGetParam("pwd", password);

Invoke(http, callbackXml);

http.Dispose();

}

  • 获取用户列表

public static function GetUser(name:String = null, role:String = null, pageNO:String = "1", pageSize:String = "25", callbackXml:Function = null):void

{

var http:HttpRequest = CreateRequest("lcms.user.get");

http.AddGetParam("session", Session);

if (name != null)

http.AddGetParam("uname", name);

if (role != null)

http.AddGetParam("urole", role);

if (pageNO != null)

http.AddGetParam("page_no", pageNO);

if (pageSize != null)

http.AddGetParam("page_size", pageSize);

Invoke(http, callbackXml);

http.Dispose();

}

  • 删除用户

public static function DeleteUser(id:String, callbackXml:Function = null):void

{

var http:HttpRequest = CreateRequest("lcms.user.delete");

http.AddGetParam("session", Session);

http.AddGetParam("del_uid", id);

Invoke(http, callbackXml);

http.Dispose();

}

使用WebAPI

  • WebAPI初始化设置

private function SessionError():void

{

//跳转到登陆

}

cwn.lcms.WebAPI.Url = "http://server1:2009/LCMS2/api.jhtm";

cwn.lcms.WebAPI.SessionErrorFunction = SessionError;

cwn.lcms.WebAPI.Session = null;

cwn.lcms.WebAPI.CurrentUser = null;

  • 登录

private function LoginCallback(xml:Object):void

{

//…

cwn.lcms.WebAPI.Session = null;

cwn.lcms.WebAPI.CurrentUser = null;

if (xml == null)

return;

var user:UserData = new UserData(XML(xml));

//…

cwn.lcms.WebAPI.CurrentUser = user;

if (user.Session != "")

cwn.lcms.WebAPI.Session = user.Session;

//登陆成功跳转

}

cwn.lcms.WebAPI.Login("Hunk", "123", LoginCallback);

  • 获取用户列表

function GetUserCallback(xml:Object):void

{

if (xml == null)

return;

_Data.removeAll();//ArrayCollection

for each (var item:XML in xml.item)

{

var user:UserData = new UserData(item);

_Data.addItem(user);

}

//数据绑定

}

cwn.lcms.WebAPI.GetUser(null, null, "1", "25", GetUserCallback);

注:设计不唯一,上述是已实践的个案参考。

系列索引

Flex通信篇——Flex和外部应用程序进行通信

Flex通信篇——Flex和外部进行异步通信

Flex通信篇——Flex键盘组合键

Flex通信篇——构建企业级HTTP通信层

后记

写Blog有一段时间,终于能完成一个完整系列,即使略带稚气,也要好好表彰下自己:-)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值