前言

一般来说,一个客户端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. 使用 Nodejs 搭建简单的Web服务器

    使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块. ...

  2. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. Linux CentOS 配置JDK环境

    一.下载JDK 下载JDK的方式有两种: 1.Linux中使用wget下载 1.使用命令安装wget yum install wget 2.下载 wget 'http://download.oracl ...

  4. Hawk 6. 高级话题:子流程系统

    子流程的定义 当流程设计的越来越复杂,越来越长时,就难以进行管理了.因此,采用模块化的设计才会更加合理.本节我们介绍子流程的原理和使用. 所谓子流程,就是能先构造出一个流程,然后被其他流程调用.被调用 ...

  5. css3制作旋转动画

    现在的css3真是强大,之前很多动画都是用jq来实现,但是css3制作的动画要比jq实现起来简单很多,今天呢,我自己也写了一个css旋转动画和大家分享.效果如下面的图片 思路:1.制作之前呢,我们先来 ...

  6. BPM任务管理解决方案分享

    一.方案概述任务是企业管理者很多意志的直接体现,对于非常规性事务较多的企业,经常存在各类公司下达的各种任务跟进难.监控难等问题,任务不是完成效果不理解,就是时间超期,甚至很多公司管理层下达的任务都不了 ...

  7. Android studio使用git教程

    ①下载Git工具,配置到Android studio中 http://git-scm.com/downloads ------------------------------------------- ...

  8. 烂泥:数据库管理之phpmyadmin免密码配置

    本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb 其实这篇文章很早就想写了,但是一直没有时间.刚好今天下午稍微空了点,就把这篇文章整理出来 ...

  9. Spring MVC注解开发入门

    注解式开发初步 常用的两个注解: @Controller:是SpringMVC中最常用的注解,它可以帮助定义当前类为一个Spring管理的bean,同时指定该类是一个控制器,可以用来接受请求.标识当前 ...

  10. css样式让input垂直居中

    css样式让input垂直居中 css代码: .div1{ border: 1px solid #CCC; width:1120px; height:40px; margin:auto; displa ...