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>(10);
            this.ClassList = new List<File_Classes>(5);
        }

        #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") >= 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", 0);
                string description = EagleRequest.FormToString("description", string.Empty);
                try
                {
                    int state = FileManager.UpdateFileDescription(description, fileId, base.UserId);
                    if (state > 0)
                    {
                        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 * 20);
            StringBuilder newQuery = new StringBuilder(keyValues.Count * 20);
            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回忆录

时间: 2024-08-08 19:24:01

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

热点热词风云榜-为APP应用提供免费的新闻资讯API接口

热点热词-Yi18事件风云榜 (top.yi18.net) 基于热点关键词二开放的新闻事件关注榜, 系统实时采集人们关注事件-基于百度搜索.搜狗热词.google趋势而采集的实时关注 事件与新闻. 热点热词API文档(doc.yi18.net/topapidoc)专门开放其API接口,         主要 为了让APP开发者更好的为自己的APP应用添加新闻资讯相关的实时数据.为APP应用更 有吸引力. 现在主要有两个API接口: 1.取得热词信息列表 如:api.yi18.net/top/li

getRunningTasks API从Android LL开始 权限收敛

最近在做的一个小项目中,需要实时获取Activity栈顶以及它所属的App Process. 根据之前的了解,知道Android API有提供一个接口 public List<ActivityManager.RunningTaskInfo> getRunningTasks (int maxNum) 可以得到系统当前正在运行的Task列表,用maxNum限制要获取的数量(最近使用的最先取出). 实际运行程序时,发现并不能实时获取当前的Activity栈顶. (使用的是Android L的机器进行

Google 地图 API for Android

原文:Introduction to Google Maps API for Android 作者:Eunice Obugyei 译者:kmyhy 从健康类 app Runkeeper 到游戏 app 精灵宝可梦,位置服务对现代 app 来说越来越重要. 在本文中,我们将创建一个 app,名字就叫做 City Guide.这个 app 允许用户搜索一个地点,使用 Google 地图显示这个地点的位置并监听用户的位置改变. 我们将学习如何使用 Google 地图 API for Android,G

改变mvc web api 支持android ,ios ,ajax等方式跨域调用

公司一个移动后端的项目用到了 webapi 项目搭建到外网环境共app开发者调用测试接口时遇到了一个问题 接口不允许跨域调用 .查阅资料明白 同源策略原则根据请求报头值 Origin 与回应报头值 Access-Control-Allow-Origin 来判断是否允许调用 解决方法 1.ajax使用jsonp jsonp 是通过请求参数中加入回调函数参数值.webapi 收到回调函数参数值返回数据不再是单纯的json,而是根据回调函数参数值 的js方法调用,这样就避免的同源策略 需要webapi

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

时间过得真快,距离上次发随笔又是一年多.作为上次发的我的第一个WP8.1应用总结的后继,这次同样的主要功能,改为实现安卓版APP.前几个月巨硬收购Xamarin,把Xamarin集成到VS里了,大大方便了我广大.net码农.由于年初脱了WP的坑,换了个安卓低端机,想着什么时候装Xamarin开发个App玩玩. 上个月笔记本100G的C盘莫名其妙快满了,趁着重装系统的机会,安装了VS2015 with sp3,下载开发Android App需要的各种东东.这里要感谢[C#]VS2015开发环境的安

如何得到包含隐藏API的Android类库

Android SDK的很多API是隐藏的,我无法直接使用.但是我们通过编译Android系统源码可以得到完整的API库. 编译Android系统源码后可以在out\target\common\obj\JAVA_LIBRARIES目录中有它所有API库(java). 当然对于一般情况,out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar对于我们来说就足够了. 我们可以通过这个classes.jar这种An

Arcgis API for Android之GPS定位

欢迎大家增加Arcgis API for Android的QQ交流群:337469080 先说说写这篇文章的原因吧,在群内讨论的过程中,有人提到了定位的问题,刚好,自己曾经在做相关工作的时候做过相关的东西,所以就总结一下,给大家共享出来,因为本人水平有限,bug是在所难免,还望有更高的高人批评指正.废话不多说,直接进入主题. 要想在地图上定位并将定位结果实时显示出来,启发逻辑上非常easy:首先,接收并解析GPS或者网络的位置信息,一般来说,接受的位置信息是WGS84的经纬度的,可是我们的地图的

Xamarin.Android开发-APP欢迎页面

1.添加Xamarin.Android.Support.v4.dll 2.使用ViewPager控件 3.为ViewPager设置适配器,声明适配器继承PagerAdapter,重写3个方法,1个属性 ViewPager mVP = FindViewById<ViewPager>(Resource.Id.mYvp);mVP.Adapter = new MyPageAdapter(this); public override int Count{} public override Java.La

App开发如何利用Fidder,在api接口还没有实现的情况下模拟数据,继续开发

相信app开发很多时候,都是等后台出接口,拿到数据调试错误.殊不知,我们完全可以不用等,只要有约定好的接口定义文档,借助工具就能做到,自己模拟数据返回~      下面主要是在项目组开发过程中,使用Fidder,摸索总结的经验之谈.     一.初步介绍及前期准备 1.抓包工具简介 抓包工具有很多,以下列一下最常用的几个工具别简单介绍下 firebug:web最常用的调试工具,但是对于分析http请求的详细信息,不够强大.模拟http请求的功能也不够,且firebug常常是需要“无刷新修改”,如