APP并非一个人在战斗,还有API—Xamarin.Android回忆录
前言
一般来说,一个客户端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回忆录的更多相关文章
- 如何使用Google Map API开发Android地图应用
两年前开发过的GoogleMap已经大变样,最近有项目要用到GoogleMap,重新来配置Android GoogleMap开发环境,还真是踩了不少坑. 一.下载Android SDK Manager ...
- [置顶]
Xamarin android 调用Web Api(ListView使用远程数据)
xamarin android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...
- Xamarin.Android 调用Web Api(通过ListView展示远程获取的数据)
xamarin.android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin.android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...
- 【Android API】Android 4.1 API官方文档详解
原文:http://android.eoe.cn/topic/summary 翻译:[eoeAndroid原创团队]kris.流风而逝.贼寇在何方.snowxwyo.lsy4833406 更新日期:2 ...
- Google 地图 API for Android
原文:Introduction to Google Maps API for Android 作者:Eunice Obugyei 译者:kmyhy 从健康类 app Runkeeper 到游戏 app ...
- Android Google 地图 API for Android
从健康类 app Runkeeper 到游戏 app 精灵宝可梦,位置服务对现代 app 来说越来越重要. 在本文中,我们将创建一个 app,名字就叫做 City Guide.这个 app 允许用户搜 ...
- 跨过几个坑,终于完成了我的第一个Xamarin Android App!
时间过得真快,距离上次发随笔又是一年多.作为上次发的我的第一个WP8.1应用总结的后继,这次同样的主要功能,改为实现安卓版APP.前几个月巨硬收购Xamarin,把Xamarin集成到VS里了,大大方 ...
- Xamarin.Android 入门之:Android API版本设置
一.引言 Xamarin.Android有几个Android API级别设置,确定多个版本的Android应用程序的兼容性.本博客解释了这些设置意味着什么,如何配置它们,以及它们在运行时对您的应用程序 ...
- 【Xamarin挖墙脚系列:Xamarin.Android的API设计准则】
原文:[Xamarin挖墙脚系列:Xamarin.Android的API设计准则] 前言 楼主也是看着Xamarin的官方文档来的.基本也是照猫画虎.英语勉强凑合.翻译的不对的地方,大家多多指教.(这 ...
随机推荐
- 使用 Nodejs 搭建简单的Web服务器
使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块. ...
- MVVM设计模式和WPF中的实现(四)事件绑定
MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- Linux CentOS 配置JDK环境
一.下载JDK 下载JDK的方式有两种: 1.Linux中使用wget下载 1.使用命令安装wget yum install wget 2.下载 wget 'http://download.oracl ...
- Hawk 6. 高级话题:子流程系统
子流程的定义 当流程设计的越来越复杂,越来越长时,就难以进行管理了.因此,采用模块化的设计才会更加合理.本节我们介绍子流程的原理和使用. 所谓子流程,就是能先构造出一个流程,然后被其他流程调用.被调用 ...
- css3制作旋转动画
现在的css3真是强大,之前很多动画都是用jq来实现,但是css3制作的动画要比jq实现起来简单很多,今天呢,我自己也写了一个css旋转动画和大家分享.效果如下面的图片 思路:1.制作之前呢,我们先来 ...
- BPM任务管理解决方案分享
一.方案概述任务是企业管理者很多意志的直接体现,对于非常规性事务较多的企业,经常存在各类公司下达的各种任务跟进难.监控难等问题,任务不是完成效果不理解,就是时间超期,甚至很多公司管理层下达的任务都不了 ...
- Android studio使用git教程
①下载Git工具,配置到Android studio中 http://git-scm.com/downloads ------------------------------------------- ...
- 烂泥:数据库管理之phpmyadmin免密码配置
本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb 其实这篇文章很早就想写了,但是一直没有时间.刚好今天下午稍微空了点,就把这篇文章整理出来 ...
- Spring MVC注解开发入门
注解式开发初步 常用的两个注解: @Controller:是SpringMVC中最常用的注解,它可以帮助定义当前类为一个Spring管理的bean,同时指定该类是一个控制器,可以用来接受请求.标识当前 ...
- css样式让input垂直居中
css样式让input垂直居中 css代码: .div1{ border: 1px solid #CCC; width:1120px; height:40px; margin:auto; displa ...