前言

一般来说,一个客户端APP并非独立存在的,很多时候需要与服务器交互。大体可分为两方面的数据,常规字符串数据和文件数据,因为这两种数据很可能传输方式不一样,比如字符串之类的数据,使用HTTP协议,选择json或xml作为数据传输结构,而以json最方便简洁,所以我们近年来的项目,就直接使用json,不再使用xml了。但是作为文件,使用HTTP协议显然不够利索,而直接使用TCP协议是更好的选择。文件传输一般都是在服务端有服务一直在监听相应的端口,客户只需要使用TCP协议,根据服务端制定的规则上传文件即可,今天不做过多介绍。这里主要介绍基于HTTP协议的API。

服务端的Api

Api项目结构

在具体讲述细节之前,先看看我们目前正在使用的Api项目结构,所有对外发布的接口实际上都是通过每个Controller来实现的。

Api文档

由于Api是对外发布的,一旦发布并有客户端在使用时,稳定性就变得非常重要。因此一个良好的Api至少要满足稳定性这个基本要求,所以Api的约定文档变得非常重要,这是以后维护的基础。这是我们的文档结构

我们对外发布的Api的域名是 http://api.kankanbaobei.com 如果你直接访问,肯定是错误的,因为没有给出任何有效的接口名称。如果你体验过我们的手机APP,里面有很多图片列表,这个图片列表的接口名称是:/file/list 那么获取图片列表的基本Url是:http://api.kankanbaobei.com/file/list 如果你访问这个,不会出现找不到的错误了,但是会出现以下错误:

{"Success":false,"Code":11,"Description":"请求的Token错误"}

这个时候Api的安全验证机制起作用了,那怎么才能获取的正确的数据呢?为此我们还是先看看Api安全验证机制是怎么设计的吧。先看下面这张图:

token是对客户端传入字符串的验证,具体验证方式看上去比较复杂,实际上理解了就不复杂,说明如下:

具体算法如下:(兄弟们,我是不是比较够诚意呢)

不出意外,你访问上图中的网址,即可看到结果,由于url太长,我做个链接:

点击这里查看结果

返回的数据结构如下,也就是你在手机APP上看到的图片列表,代码太长,我保留了两张图片的代码量。

{
"Success": true,
"Code": 200,
"Description": "Ok",
"FileList": [
{
"ChildrenList": [],
"ClassList": [],
"CreateTime": "2014-07-07 16:11:49",
"Description": "",
"Id": 15228,
"Tidied": false,
"Type": 3,
"Url": "http://baobei.oss.aliyuncs.com/uploadfile/other/9ac/9acc2e13e4ac8b98a7cd49a9902ea0a7_861.mp4",
"UserId": 861,
"RecordingDate": "2014-07-07 16:11:49",
"FileSize": 1132580,
"Thumbnail": "http://baobei.oss.aliyuncs.com/uploadfile/other/9ac/9acc2e13e4ac8b98a7cd49a9902ea0a7_861_480_960.jpg",
"State": 1
},
{
"ChildrenList": [
{
"Id": 925,
"RealName": "王军"
}
],
"ClassList": [],
"CreateTime": "2014-05-02 22:35:13",
"Description": "我们正在做早操",
"Id": 7702,
"Tidied": false,
"Type": 3,
"Url": "http://baobei.oss.aliyuncs.com/uploadfile/initdata/video_2.mp4",
"UserId": 861,
"RecordingDate": "2014-05-02 22:35:13",
"FileSize": 7196151,
"Thumbnail": "http://baobei.oss.aliyuncs.com/uploadfile/initdata/video_2_480_960.jpg",
"State": 1
}
]
}

当正式用户使用的话,上面的url是只能够使用一次的,如果多次使用,会出现以下错误的:

{"Success":false,"Code":13,"Description":"请求的序列号错误"}

不知道你注意到上面系统参数里面有这个callid参数没?这是个时间戳,主要防重放攻击。系统会要求每次请求的CallId必须大于上一次的CallId。

另外还有一个很重要的参数version,这个参数表示api的版本,api不可能不变,但变动不应该影响客户端已经在使用的api,所以用version来表示不同的api版本,保证以往发布的api版本的稳定,要回顾这些系统级的参数,请参考上面系统级参数那张图。

Api设计总结

经过了以上的折磨后,我想我应该把Api设计基本上说清楚,Api设计总结如下:

1,定义全局规则,比如采用的字符编码,统一返回的数据格式等

2,定义系统级参数,每次访问都需要带上的参数。比如apikey,version,callid,token等

3,说明token签名规则

4,定义每个接口具体的参数

总体说来,每个url由这4部分组成

1,Api域名,如我们的 http://api.kankanbaobei.com

2,接口名称,比如我们获取老师文件列表的接口名称:/file/list

3,接口参数,包括系统级参数和接口参数

4,计算出来的token

如果接口是post方式,比如修改密码,那么 提交的url是前面两部分,后面的参数需要post提交。

Api代码实现

通过api返回的数据结构是相对固定的,我们使用的NewtonSoft.Json序列化实体结构,我们的结构大体如下(具体属性有所删除,但不影响阅读):

namespace BaoBei.Api.Services
{
public class Result
{
/// <summary>
/// 执行是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 执行结果代号
/// </summary>
public int Code { get; set; }
/// <summary>
/// 执行结果描述信息
/// </summary>
public string Description { get; set; }
/// <summary>
/// 公共数据,一般用于除特定类型以外的数据
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Data { get; set; } /// <summary>
/// 用户信息
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Api_UserInfo UserInfo { get; set; } /// <summary>
/// 文件结果集,目前只能以集合的方式直接赋值
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<Api_File> FileList { get; set; } }
}

NewtonSoft.Json下面的这个特性非常方便,在返回数据结构中,不是所有的属性都返回,而是根据实际情况,返回接口所需要的结构,比如不需要UserInfo属性,则不为其赋值即可,返回的数据结构中就没有这个属性。这样设计上也比较方便,而接口返回的数据也比较整齐。

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]

数据结构难免会嵌套,比如上面的 Api_File (为什么这个类名叫 Api_File,其实没别的原因,主要是和系统共享项目(BaoBei.Core)中的File实体区分)

比如这个类中有以下两个属性 ChildrenList 和 ClassList

[Serializable]
public class Api_File
{
public List<File_Children> ChildrenList { get; private set; }
public List<File_Classes> ClassList { get; private set; } #region 构造函数
/// <summary>
/// 默认构造函数
/// </summary>
public Api_File(){
this.ChildrenList = new List<File_Children>();
this.ClassList = new List<File_Classes>();
} #region CreateTime:创建时间
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime{get;set;}
#endregion #region Description:对文件的描述
/// <summary>
/// 对文件的描述
/// </summary>
public String Description{get;set;}
#endregion #region Id:文件Id
/// <summary>
/// 文件Id
/// </summary>
public Int32 Id{get;set;}
#endregion }

基本数据结构弄清楚了后,看看一些安全验证的代码,实际上安全验证的代码就是根据Api文档来写的。

#region 安全验证
/// <summary>
/// 安全验证
/// </summary>
/// <param name="apiKey">apiKey,目前就是用户Id</param>
/// <param name="maxRestrictTimes">每分钟最大请求次数</param>
/// <param name="currentCallId">当前请求序列号</param>
/// <param name="secret">用户的密钥</param>
/// <param name="collection">请求参数集合</param>
/// <returns></returns>
public static Result Verify(int apiKey, int maxRestrictTimes, long currentCallId, string secret, NameValueCollection collection)
{
if (!VerifyToken(collection, secret))
return ApiUtils.GetResult(false, CodeConstants.TokenInvalid, "请求的Token错误");
if (!VerifyCallIdIsOk(apiKey, currentCallId))
return ApiUtils.GetResult(false, CodeConstants.CallIdInvalid, "请求的序列号错误");
if (!VerifyOutOfRestrictTimes(apiKey, maxRestrictTimes))
return ApiUtils.GetResult(false, CodeConstants.OutOfRequestTimes, "在一分钟内已经达到最大请求次数");
return ApiUtils.GetResult(true, CodeConstants.Success, "Ok");
}
#endregion

返回的Result就是那个数据结构,这个时候返回的是公共部分,就是无论哪个接口返回的数据,都会包含这个公共部分,就是Success,Code,Description,具体可参看前面那个数据返回结构代码,里面也有说明。具体每个接口返回的数据,还是以获取文件接口为例(不好意思,让你失望了),我刚才看了看获取文件列表的代码非常长,我这里以修改文件描述为例,完整代码如下:

/// <summary>
/// 修改文件描述
/// </summary>
/// <returns></returns>
[HttpPost]
public ContentResult Description()
{
base.IsPost = true;//当前请求的是否是Post方式
if (base.Version.CompareTo("1.0") >= )//判断Api版本
{
NameValueCollection collection = Request.Form;
Result result = ApiUtils.Verify(base.UserId, UserInfoProvider.Instance.GetMaxRestrictRequestTimes(base.UserId),
base.CurrentCallId, UserInfoProvider.Instance.GetUserSecret(base.UserId), collection);
if (!result.Success)
return Content(ApiUtils.Serialize(result)); int fileId = EagleRequest.FormToInt32("fileId", );
string description = EagleRequest.FormToString("description", string.Empty);
try
{
int state = FileManager.UpdateFileDescription(description, fileId, base.UserId);
if (state > )
{
result.Description = "Ok";
return Content(ApiUtils.Serialize(result));
}
return Content(ApiUtils.GetResultJson(false, CodeConstants.ExecuteFailed, "操作失败,无权限或者不存在该文件")); }
catch (Exception e)
{
Logger.Error(e);
return Content(ApiUtils.GetResultJson(false, CodeConstants.Exception, "错误:" + e.Message));
}
}
else
{
return Content(ApiUtils.GetResultJson(false, CodeConstants.ApiVersionInvalid, "Api版本号错误"));
}
}

其实所有的接口都会有前面几句验证的代码,以上为Api代码的实现,基本流程是这样的,不知道是否对你那么一些用处?

客户端使用Api

首先还是需要获取到数据,所以需要有个请求数据的公共方法,这些公共方法都在PCL类库中,以便共享到其他项目中:

同样,我们还是使用的是与服务端相同的数据结构,拷贝过来就可以,仍然使用NewtonSoft.Json反序列化,非常方便。以获取文件列表为例,核心代码如下:

private void GetFileList(int count, int fileId, bool nextPage, int specifiedTeacherId, DateTime? startCreateTime, DateTime? endCreateTime, bool? tidied, OnFinishRequestApiResultCallback callback)
{
Dictionary<string, string> keyValues = ApiSettings.ApiSystemKeyValues;
keyValues.Add("count", count.ToString());
keyValues.Add("fileid", fileId.ToString());
keyValues.Add("nextpage", nextPage.ToString());
keyValues.Add("specifiedTeacherId", specifiedTeacherId.ToString());
if (tidied.HasValue)
{
keyValues.Add("tidied", tidied.Value.ToString());
}
if (startCreateTime.HasValue)
{
keyValues.Add("startcreatetime", startCreateTime.Value.ToString());
}
if (endCreateTime.HasValue)
{
keyValues.Add("endcreatetime", endCreateTime.Value.ToString());
}
HttpClient httpClient = new HttpClient();
httpClient.Get(Url.Create(ListActionName, keyValues), callback);
}

其中很关键的Url.Create方法的代码如下:

public static string Create(string apiMethodName, Dictionary<string, string> keyValues)
{
keyValues = keyValues.OrderBy(o => o.Key).ToDictionary(key => key.Key, value => value.Value);//进行字段排序
StringBuilder code = new StringBuilder(keyValues.Count * );
StringBuilder newQuery = new StringBuilder(keyValues.Count * );
foreach (string key in keyValues.Keys)
{
code.Append(key + "=" + keyValues[key]);
newQuery.Append(key + "=" + Uri.EscapeDataString(keyValues[key]) + "&");
}
return string.Format("{0}{1}/?{2}", ApiSettings.Domain, apiMethodName, newQuery.ToString() +
"token=" + Sha1.Create(code.ToString() + ApiSettings.ApiSecret));
}

至此,这以后就是表现层调用这些数据了,这一节与Xamarin.Android关系甚少,但是确实必须的,不然往后可能不清楚整个流程是如何设计的,不利于理解,我个人认为是这样。

今天先写到这里,算是对Api有一个大概流程的介绍(不知道你看是否觉得清晰,O(∩_∩)O),希望对你有那么一点点用处。

谢谢。

APP并非一个人在战斗,还有API—Xamarin.Android回忆录的更多相关文章

  1. 如何使用Google Map API开发Android地图应用

    两年前开发过的GoogleMap已经大变样,最近有项目要用到GoogleMap,重新来配置Android GoogleMap开发环境,还真是踩了不少坑. 一.下载Android SDK Manager ...

  2. [置顶] Xamarin android 调用Web Api(ListView使用远程数据)

    xamarin android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...

  3. Xamarin.Android 调用Web Api(通过ListView展示远程获取的数据)

    xamarin.android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin.android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...

  4. 【Android API】Android 4.1 API官方文档详解

    原文:http://android.eoe.cn/topic/summary 翻译:[eoeAndroid原创团队]kris.流风而逝.贼寇在何方.snowxwyo.lsy4833406 更新日期:2 ...

  5. Google 地图 API for Android

    原文:Introduction to Google Maps API for Android 作者:Eunice Obugyei 译者:kmyhy 从健康类 app Runkeeper 到游戏 app ...

  6. Android Google 地图 API for Android

    从健康类 app Runkeeper 到游戏 app 精灵宝可梦,位置服务对现代 app 来说越来越重要. 在本文中,我们将创建一个 app,名字就叫做 City Guide.这个 app 允许用户搜 ...

  7. 跨过几个坑,终于完成了我的第一个Xamarin Android App!

    时间过得真快,距离上次发随笔又是一年多.作为上次发的我的第一个WP8.1应用总结的后继,这次同样的主要功能,改为实现安卓版APP.前几个月巨硬收购Xamarin,把Xamarin集成到VS里了,大大方 ...

  8. Xamarin.Android 入门之:Android API版本设置

    一.引言 Xamarin.Android有几个Android API级别设置,确定多个版本的Android应用程序的兼容性.本博客解释了这些设置意味着什么,如何配置它们,以及它们在运行时对您的应用程序 ...

  9. 【Xamarin挖墙脚系列:Xamarin.Android的API设计准则】

    原文:[Xamarin挖墙脚系列:Xamarin.Android的API设计准则] 前言 楼主也是看着Xamarin的官方文档来的.基本也是照猫画虎.英语勉强凑合.翻译的不对的地方,大家多多指教.(这 ...

随机推荐

  1. java Web项目创建之一(普通java web项目的创建与发布)

    1.创建新的web项目 file->new_>Dynamic Web Project(如图) 或file->new->Project->Web->Dynamic W ...

  2. Android 判断一个 View 是否可见 getLocalVisibleRect(rect) 与 getGlobalVisibleRect(rect)

    Android 判断一个 View 是否可见 getLocalVisibleRect(rect) 与 getGlobalVisibleRect(rect) [TOC] 这两个方法的区别 View.ge ...

  3. jquery插件的用法之cookie 插件

    一.使用cookie 插件 插件官方网站下载地址:http://plugins.jquery.com/cookie/ cookie 插件的用法比较简单,直接粘贴下面代码示例: //生成一个cookie ...

  4. 【转】39个让你受益的HTML5教程

    闲话少说,本文作者为大家收集了网上学习HTML5的资源,期望它们可以帮助大家更好地学习HTML5. 好人啊! 不过,作者原来说的40个只有39个,因为第5个和第8个是重复的. 原文在此! 1. 五分钟 ...

  5. 获取 dhcp IP 过程分析 - 每天5分钟玩转 OpenStack(91)

    前面我们已经讨论了 DHCP agent 的配置以及 namespace 如何隔离 dnsmasq 服务,本节将以 cirros-vm1 为例分析获取 DHCP IP 的详细过程. 在创建 insta ...

  6. C# 索引器,实现IEnumerable接口的GetEnumerator()方法

    当自定义类需要实现索引时,可以在类中实现索引器. 用Table作为例子,Table由多个Row组成,Row由多个Cell组成, 我们需要实现自定义的table[0],row[0] 索引器定义格式为 [ ...

  7. Oracle补全日志(Supplemental logging)

    Oracle补全日志(Supplemental logging)特性因其作用的不同可分为以下几种:最小(Minimal),支持所有字段(all),支持主键(primary key),支持唯一键(uni ...

  8. 用C++实现Linux中shell的ls功能

    实现输出当前目录下的文件名 ls功能: 方法一: #include <iostream> #include <algorithm> #include <stdio.h&g ...

  9. 乐乎环球WiFi

    乐乎环球WiFi招商加盟 随身WiFi设备 乐乎环球Wifi是由北京蔚蓝创智科技有限公司研发的产品,是一款可以在全球100多个国家和地区实现免漫游4G高速上网的随身WiFi设备.和普通MiFi设备相比 ...

  10. 技术笔记:Delphi多线程应用读写锁

    在多线程应用中锁是一个很简单又很复杂的技术,之所以要用到锁是因为在多进程/线程环境下,一段代码可能会被同时访问到,如果这段代码涉及到了共享资源(数据)就需要保证数据的正确性.也就是所谓的线程安全.之前 ...