云API是什么?我们为什么要用?

首先,我们来看一看官网对云API的介绍。
APICloud的“云API”规划的初衷是为了让开发者只需要少量、甚至不需要服务端编码,就可以自动生成移动应用所需要的各种云服务接口。“云API”服务包括数据服务、推送、云修复和大数据分析等。

APICloud的数据服务支持自动生成RESTful API,在移动场景中为APP提供灵活的数据服务支持。同时NoSQL的大量应用为APICloud的大数据提供了充分支撑。APICloud的“云API”提供基于ACL(Access Control List)和RBAC (Role Based Access Control)的访问控制模型安全机制,让开发者的移动应用在业务灵活性和安全性中找到平衡。
APICloud的“云API”具有典型的“云”特性和“大数据”特性。

为App开发者提供云端的API服务和数据存储服务,动态生成RESTful API,支持在线NoSQL数据表设计、API调试及用量分析;同时提供推送、云修复、大数据分析等服务, 极大的简化了服务器端开发工作。

一句话来说就是,我们不需要编写后台代码,直接就可以通过系统生成的RESTful API去访问数据库的内容,除此之外还提供了很多其他的功能。

废话不多说,下面就开始APICloud的云API的探索。

1.进入apicloud官网,注册一个账号。
2.登录进入开发控制台,新建一个应用。

api云服务 云api是什么_api

输入应用名称与说明,创建。
3.进入到该应用的大主页。

api云服务 云api是什么_移动应用_02

在这里,我们可以看到我们这个应用的appKey,appID,以及其他的标识。后面会用得到。
4.我们关注的是云数据库,所以点击左侧栏Database。

api云服务 云api是什么_api_03

点击打开云服务。

api云服务 云api是什么_api_04

选择一个云存储服务商。七牛云是以图片音频为主的,我们暂且不需要,所以我们选择又拍云服务商,确定。

api云服务 云api是什么_api云服务_05

我们会发现,这个数据库自带5张系统表:accessToken、file、role、roleMapping、user。
前面的下划线,只是为了区分出这是系统表。而真正使用的时候,是不带下划线的。
他们都有什么具体的作用呢,暂且我们先不管,后面再做介绍。

这里可以自己创建class,这里的class与SQL数据库的表相对应。创建一个class,暂且可以理解为创建了一张表。我们尝试着创建一个信息Class–message。

api云服务 云api是什么_移动应用_06

点击创建

api云服务 云api是什么_移动应用_07

我们会发现,这个class默认就有3个字段。分别是id,createdAt,updateAt,代表什么意思,不言而喻。
我们为message创建一个String 类型的content字段。

api云服务 云api是什么_api云服务_08

其他的删除,重命名等操作,都可以在上面找得到。大家可以自行尝试。

我们想试着去操作一下这个数据库的增删改查。
OK,点击左侧的API调试。

api云服务 云api是什么_api云服务_09

在这里可以进行API在线测试,对表进行增删改查的操作。

1.试着先给message增加一条数据。
class选择message表,method选择create a new instance。
在body处输入:
{“content”:”第一条message的内容”}

api云服务 云api是什么_api云服务_10

点击发送请求

api云服务 云api是什么_nosql_11

我们可以看得到请求URL路径,响应的Response Body,以及响应码,响应头。

api云服务 云api是什么_api云服务_12

还能看得到这个增添操作的ajax的代码实现。

好的,我们回到数据库,看看有什么变化。

api云服务 云api是什么_云api_13

的确message里面多了一条数据,测试成功。

api云服务 云api是什么_api云服务_14

API在线测试,还有很多,这里就不一一去尝试了。

API在线测试是成功了,那么我们在android中,如何实现这个操作呢?官网并没有给出java的实现例子。那么没关系,我们可以通过模仿ajax的网络请求去实现。

在我们的项目中,大家对okhttp的应用颇有了解。那么,我们就用okhttp去实现。
在写代码之前,首先要对RESTFUL 风格的API做一个简单的介绍。
Restful API可以让您用任何可以发送 http 请求的设备来与 API Cloud 进行交互。
这里拿官网的例子来看看。

api云服务 云api是什么_api_15

HTTP请求的方法POST,DELETE,PUT,GET分别对应着增删改查。
我们很容易就找到了规律。
例如第一行,post是创建,路径是/mcm/api/,
也就是说为className增加一条数据。

这里不做深入的解析,有兴趣的,来这里看:
http://www.ruanyifeng.com/blog/2014/05/restful_api.html

现在开始代码测试:

在这之前,我已经给系统表user增加了一系列的自定义字段,nickname,realname等;

OkHttpUtils.post()
        .url("https://d.apicloud.com/mcm/api/user")
        .addHeader("Content-Type", " application/json;charset=UTF-8")
        .addHeader("X-APICloud-AppId", Constant.APPID)
        .addHeader("X-APICloud-AppKey", Constant.getAppkey())
        .addParams("username", "qq")
        .addParams("password", "123456")
        .addParams("email", "270949894@qq.com")
        .addParams("nickname", "wym")
        .addParams("studentID", "201324133121")
        .addParams("realname", "吴一鸣")
        .addParams("sex", "0")
        .addParams("mobile", "15622625081")
        .build()
        .execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String response, int id) {
                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
            }
        });

运行第一次,创建一个用户,

api云服务 云api是什么_api_16

运行第二次,用户已存在,

api云服务 云api是什么_nosql_17

到数据库去看,已经多了一条数据。

api云服务 云api是什么_nosql_18

下面来分析一下代码:

.addHeader("Content-Type", " application/json;charset=UTF-8")
.addHeader("X-APICloud-AppId", Constant.APPID)
.addHeader("X-APICloud-AppKey", Constant.getAppkey())

对于POST和PUT请求,请求的主体必须是 JSON 格式,而且 HTTP header 的 Content-Type 需要设置为 application/json。
用户验证是通过 HTTP header 来进行的。
X-APICloud-AppId头标明正在运行的是哪个App程序。
X-APICloud-AppKey头用来授权鉴定终端。
那么AppId和Appkey怎么得到,我们看:

public class Constant {
    public static String APPID = "A6929067641945";
    public static String APPKEY = "AC9AE246-4F70-D990-96E7-183E2D9EA001";
    public static String getAppkey(){
        long now = new Date().getTime();
        return Decript.SHA1(Constant.APPID + "UZ" + Constant.APPKEY + "UZ" + now) + "." + now;
    }
}

AppId从应用控制管理页面可以得到。
Appkey的获得,有这样的一个公式:

appKey = SHA1(应用ID + ‘UZ’ + 应用KEY +’UZ’ + 当前时间毫秒数)+ ‘.’ +当前时间毫秒数
其中SHA1是一个加密的方法。

.addParams("username", "qq")
.addParams("password", "123456")

不言而喻。

这里贴上SHA1的算法,也可以自己去下载:

public static String SHA1(String decript) {
    try {
        MessageDigest digest = java.security.MessageDigest
                .getInstance("SHA-1");
        digest.update(decript.getBytes());
        byte messageDigest[] = digest.digest();
        // Create Hex String
        StringBuffer hexString = new StringBuffer();
        // 字节数组转换为 十六进制 数
        for (int i = 0; i < messageDigest.length; i++) {
            String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
            if (shaHex.length() < 2) {
                hexString.append(0);
            }
            hexString.append(shaHex);
        }
        return hexString.toString();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return "";
}

好了,接着继续:
服务器数据库有了user的信息,我们就尝试获取user信息。
我们可以通过这个url去访问指定id的user。
https://d.apicloud.com/mcm/api/user/[id]

api云服务 云api是什么_api云服务_19

在数据库可以看到,id = 57e7905be26c90a0079722ea

我们就获取它的数据:

String url = "https://d.apicloud.com/mcm/api/user/" + "57e7905be26c90a0079722ea";
OkHttpUtils.get()
        .url(url)
        .addHeader("Content-Type", " application/json;charset=UTF-8")
        .addHeader("X-APICloud-AppId", Constant.APPID)
        .addHeader("X-APICloud-AppKey", Constant.getAppkey())
        .build()
        .execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String response, int id) {
                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
            }
        });

api云服务 云api是什么_nosql_20

返回401,401代表没有权限。
我们到后台去修改一下权限

api云服务 云api是什么_nosql_21

把Get权限设置为所有用户都拥有的权限

api云服务 云api是什么_api云服务_22

再一次运行,可以获得数据了。

api云服务 云api是什么_api云服务_23

我们用膝盖想一想,估计就是没有登录,才无法获得权限。

好的,先把权限设置回仅登录才拥有权限。
接下来,就做登录操作:
访问url
https://d.apicloud.com/mcm/api/user/login

OkHttpUtils.post()
        .url("https://d.apicloud.com/mcm/api/user/login")
        .addHeader("Content-Type", " application/json;charset=UTF-8")
        .addHeader("X-APICloud-AppId", Constant.APPID)
        .addHeader("X-APICloud-AppKey", Constant.getAppkey())
        .addParams("username", "qq"
        .addParams("password", "123456"
        .build()
        .execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String response, int id) {
                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();

            }
        });

运行结果:

api云服务 云api是什么_api_24

从返回结果里面,可以看到一个id,估计就是一个类token的东西。我们
用膝盖想一想,估计这个id就是证明已经登录的密钥。我们去看看数据库:

api云服务 云api是什么_api_25

这里有一个accessTokens(数据类型为Relation,后面再解释),他是可以点击的,我们点击进去。

api云服务 云api是什么_nosql_26

左侧_user下面列出了一个_accessToken,有一条数据,她的内容是:
lmmuarLbfE0k18XIakPtOZLbigtURnEKubtWRAXc0tfDxS9XfhNqppsj3SxN3W7D

他恰好就是用户登录的时候,返回的那个id。

好了,我们有了密钥,再去访问user表拿数据。依上面的例子:
添加上这一句:
.addHeader(“authorization”,”lmmuarLbfE0k18XIakPtOZLbigtURnEKubtWRAXc0tfDxS9XfhNqppsj3SxN3W7D”)

OkHttpUtils.get()
        .url(url)
        .addHeader("Content-Type", " application/json;charset=UTF-8")
        .addHeader("X-APICloud-AppId", Constant.APPID)
        .addHeader("X-APICloud-AppKey", Constant.getAppkey())
        .addHeader("authorization","lmmuarLbfE0k18XIakPtOZLbigtURnEKubtWRAXc0tfDxS9XfhNqppsj3SxN3W7D")//就多了这一句
        .build()
        .execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String response, int id) {
                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
            }
        });

这样,就可以正常访问user的数据了。

那么accessToken到底是个啥?
我们可以看到,_accessToken是个系统表。点开看:

api云服务 云api是什么_api_27

我们能猜到,这个表就是存放登录产生的token的地方。
每一次登录,返回的id都是不一样的,那么这里肯定也就每次产生一条新的数据存放。
我们来进行多几次登录的请求:

api云服务 云api是什么_nosql_28

刷新一下,_accessToken里确实多了几条数据。

接下来,继续做测试。
下面测得是,更新user的一条数据。
https://d.apicloud.com/mcm/api/user/[id]

OkHttpUtils.post()
        .url("https://d.apicloud.com/mcm/api/user/"+"57e7905be26c90a0079722ea")
        .addHeader("Content-Type", " application/json;charset=UTF-8")
        .addHeader("X-APICloud-AppId", Constant.APPID)
        .addHeader("X-APICloud-AppKey", Constant.getAppkey())
        .addHeader("authorization","lmmuarLbfE0k18XIakPtOZLbigtURnEKubtWRAXc0tfDxS9XfhNqppsj3SxN3W7D")
        .addParams("_method","PUT")
        .addParams("nickname","更新名字")
        .build()
        .execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String response, int id) {
                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
            }
        });

api云服务 云api是什么_api云服务_29

更新成功。

其实一切的方法都可以用post去提交,然后通过这个方法来设置方法。
.addParams(“_method”,”PUT”)

下面测试批次操作:

为了减少网络交互的次数太多带来的时间浪费, 您可以在一个请求中对多个对象进行 create/update/delete 操作.
在一个批次中每一个操作都有相应的方法、路径和主体, 这些参数可以代替您通常会使用的 HTTP 方法. 这些操作会以发送过去的顺序来执行,
官方ajax的实现是这样的:

$.ajax({
      "url": "https://d.apicloud.com/mcm/api/batch",
      "type": "POST",
      "cache": false,
      "headers": {
        "X-APICloud-AppId": "{{your_app_id}}",
        "X-APICloud-AppKey": "{{加密后的key}}"
      },
      "data": {
        "requests": [{
            "method": "POST",
            "path": "/mcm/api/company",
            "body": {
                "name": "apicloud",
                "address": "北京市..."
            }
        },
        {
            "method": "POST",
            "path": "/mcm/api/company",
            "body": {
                "name": "百度",
                "address": "北京市西二旗"
            }
        }]
    }
}).done(function (data, status, header) {
      //success body
}).fail(function (header, status, errorThrown) {
      //fail body
})

不过,使用okhttp似乎是模拟不了这个参数的传递。如果你有方法,请告诉我。
官方提供了httpclient的一个封装java-sdk包,去操作这些增删改查。可以到这里下载:
https://github.com/APICloud-com/Java-sdk
里面也有使用教程。

下面我们进行一个批次操作,生成两个user对象。

Resource resource = new Resource(Constant.APPID, Constant.APPKEY, null);
        //如果操作需要权限,在这里设置权限,设置token
        //resource.setAuthorization("lmmuarLbfE0k18XIakPtOZLbigtURnEKubtWRAXc0tfDxS9XfhNqppsj3SxN3W7D");
        JSONObject params = new JSONObject();
        JSONArray array = new JSONArray();
        JSONObject json = new JSONObject();
        json.put("method", "POST");
        json.put("path", "/mcm/api/user");
        JSONObject body = new JSONObject();
        body.put("username", "user1");
        body.put("password", "123456");
        json.put("body", body);
        array.add(json);

        JSONObject json1 = new JSONObject();
        json1.put("method", "POST");
        json1.put("path", "/mcm/api/user");
        JSONObject body1 = new JSONObject();
        body1.put("username", "user2");
        body1.put("password", "123456");
        json1.put("body", body1);
        array.add(json1);

        params.put("requests", array);
        final String returnJson = resource.batch(params);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, returnJson, Toast.LENGTH_SHORT).show();
            }
        });
    }
}).start();

api云服务 云api是什么_api云服务_30

返回数据正常。

api云服务 云api是什么_api_31

数据库里也多了两条数据。

需要注意的是:
1.okhttp的请求是在子线程执行的,而这个java-sdk是基于httpclient的,她的请求是在原线程执行的,所以要放到子线程里去操作。
2.这里的appkey是不需要自己手动去加密的,sdk已经进行了封装。

我们可以看一下他的源码:

private String domain = "https://d.apicloud.com";

/**
 * @param appId
 * @param appKey
 * @param domain 为空或者null为默认https
 */
public Resource(String appId,String appKey,String domain){

   if(null!=domain&&!"".equals(domain)){
      this.domain = domain;
   }
   headers.put("X-APICloud-AppId", appId);
   headers.put("X-APICloud-AppKey", HttpUtils.encrypt(appId,appKey,"SHA-1"));
}

你们做了尝试了吗?是否发现结果报错了。
java.lang.ClassCastException: com.alibaba.fastjson.JSONArray cannot be cast to com.alibaba.fastjson.JSONObject
没错,因为这一次返回的不是一个对象,而是一个数组。

我们可以追踪代码
public String batch(JSONObject params){

   //校验是否传递参数
   if(params==null){
      //return JSONObject.parseObject("{status:0,msg:\"请传递参数\"}");
      return "{status:0,msg:\"请传递参数\"}";
   }

   String url = domain+"/mcm/api/batch";
   headers.put("Content-Type", "application/json");

   return HttpUtils.doPost(url, headers, null, params.toJSONString());
}
batch调用了doPost

public static JSONObject doPost(String url,Map<String,String> headers,Map<String,String> params,String jsonString) {
   HttpClient client = new HttpClient();
   //post请求
   PostMethod postMethod = new PostMethod(url);
   //设置header
   setHeaders(postMethod,headers);
   //设置post请求参数
   setParams(postMethod,params);
   //设置post传递的json数据
   if(jsonString!=null&&!"".equals(jsonString)){
      postMethod.setRequestEntity(new ByteArrayRequestEntity(jsonString.getBytes()));
   }
   String responseStr = "";
   try {
      client.executeMethod(postMethod);
      responseStr = postMethod.getResponseBodyAsString();
   } catch (Exception e) {
      log.error(e);
      e.printStackTrace();
      responseStr="{status:\""+e.toString()+"\"}";
      //responseStr="{status:0}";
   }
   return JSONObject.parseObject(responseStr);
}

错误就在这一句
JSONObject.parseObject(responseStr);

因为respinseStr是一个数组。
我们可以手动改成直接改成
return responseStr;
不需要进行转化。其他的地方跟着改变一下即可。

批量操作的响应会是一个列表, 列表的元素数量和给定的操作数量是一致的。

剩下的操作,都是类似的了。自己去摸索吧。

下面把该sdk的操作都集中快速过一遍
创建对象createUser:

resource = new Resource(Constant.APPID, Constant.APPKEY, null);
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", "user4");
jsonObject.put("password", "123456");
JSONObject jsonObjectResult = resource.createUser(jsonObject);

用户登录:

resource = new Resource(Constant.APPID, Constant.APPKEY, null);
JSONObject jsonObjectResult = resource.userLogin("qq", "123456");

获取指定id的对象:

resource = new Resource(Constant.APPID, Constant.APPKEY, null);
resource.setAuthorization(loginBean.getId());
JSONObject jsonObjectResult = resource.getObject("user",loginBean.getUserId());

筛选指定条件的user:

resource.setAuthorization(loginBean.getId());
JSONObject jsonObjectResult = resource.doFilterSearch("user", "{\"where\":{\"username\":\"qq\"}}");

更新user:

resource.setAuthorization(loginBean.getId());
JSONObject jsonObject = new JSONObject();
jsonObject.put("nickname", "yummy");
JSONObject jsonObjectResult = resource.updateObject("user", loginBean.getUserId(), jsonObject);

下面是重点了。

针对1对n的设计,我们需要考虑集合的大小规模,并不是所有一切都在父文档中内嵌一个数组子文档,这有三种方案:

一.1对很少(例如一个人有多部手机,或者多个地址,这些顶多就数个,很少)
这种情况,可以直接把文档嵌入。不过需要考虑到,是否需要独立实体。如果需要独立,则采用二方法。
因为这种设计具有内嵌文档设计中所有的优缺点。最主要的优点就是不需要单独执行一条语句去获取内嵌的内容。最主要的缺点是你无法把这些内嵌文档当做单独的实体去访问。

api云服务 云api是什么_云api_32

二.1对多(例如一个产品有几百个零件,一般不会超过1000个)
这种情况,用一个数组存放objectId。
这种引用的方式是对内嵌优缺点的补充。每个零件是个单独的文档,可以很容易的独立去搜索和更新他们。
巧妙的是,这一种方式,其实也是多对多的一种解决方案。一个产品可以嵌入多个产品objectID,一个零件又可以被多个产品嵌入objectID,所以在多对多时不需要一张单独的连接表。

api云服务 云api是什么_api云服务_33

三.1对很多(例如一部机器有非常多的日志)
所以采用“父级引用”,可以理解为关系数据库的外键一样。在每个日志文档中保存这个主机的ObjectID。

api云服务 云api是什么_nosql_34

关于数据库设计,参考以下链接,有兴趣的自己去读。
http://www.jianshu.com/p/bb0caddff60a

有了这些设计的概念,接下来,我们就开始设计我们的数据库了。
假设一个用户user,会收到消息message。
很明显这是一个一对多的例子,一个user会有多条信息。

1.先去建立一个数据表message,并且添加一个content(String)字段。

api云服务 云api是什么_云api_35

2.user表和message产生关联。
这里考虑到,一个用户会有很多的信息。我们考虑采用上面提到的数据库设计法则的方法二。
这里采用Relation(用于一对多)类型来实现。为user表添加一个Relation类型的指向目标表message的字段。

api云服务 云api是什么_api_36

我们又想有一个需求,能通过某条信息,快速定位这条信息是由谁发出的。
这里采用Pointer(用于一对一)类型来实现.为message表添加一个Pointer类型的指向目标表user的字段。

api云服务 云api是什么_api_37

表单创建完毕,回到程序中做测试:
假设现在有一条信息,归属于某个指定id的user,我们可以这样存储这条信息。

resource.setAuthorization(loginBean.getId());
JSONObject params = new JSONObject();
params.put("content", "信息");
params.put("user", loginBean.getUserId());
JSONObject returnJson = resource.createRelationObject("user", loginBean.getUserId(), "message", params);

返回结果:

api云服务 云api是什么_nosql_38

数据库:
user表

api云服务 云api是什么_nosql_39

点击message的Relation,进入user下的message

api云服务 云api是什么_api_40

message表

api云服务 云api是什么_云api_41

这样就实现了一对多的需求。

我们直接也通过id去访问user,

resource.getObject("user",loginBean.getUserId())

返回结果:

api云服务 云api是什么_移动应用_42

同样的操作,如果是通过okhttp来访问,则返回这样的结果:

api云服务 云api是什么_移动应用_43

比上面的多了几个字段:status,realm,verificationToken。
猜出,或许为null值的,okhttp一样解析。而resource的处理则是去掉Null值的字段。

在结果里并没有返回有关于message的相关信息。

我们采用getRelationObject去获取指定用户的所有message:

resource.setAuthorization(loginBean.getId());
JSONObject params = new JSONObject();
JSONObject returnJson = resource.getRelationObject("user", loginBean.getUserId(), "message");

api云服务 云api是什么_api云服务_44

返回了message信息的结果,里面有一个user(uz*R*id)的字段。

我们直接也通过id去访问Message,返回结果:

api云服务 云api是什么_api云服务_45

这个结果和上面的访问结果是一模一样的。

而作为pointer类型的”user”字段,存放的是user的id;

我们可以通过include 来展示relation 和pointer指向的数据。

JSONObject jsonObjectResult = resource.doFilterSearch("user", "{\"include\":{\"message\":\"userPointer\"},\"where\":{\"username\":\"qq\"}}");

不知为何,总是报401,理应登录了就可以正常获取数据。问了客服也没有解决。暂且先放着这个问题。

api云服务 云api是什么_api云服务_46

为了能继续进行我们的探究,把后台user表find权限打开,再一次运行

api云服务 云api是什么_api_47

可能有点乱,不过还是可以看得到这里筛选出指定username为”qq”的User信息,并且把user里面的relation指向的message数据也展示出来。message里面的pointer指向的数据user也展示出来。

大体上,我们就把api走了一个流程,其中的几个坑也都遇到了。对于其他的更多了方法,可以去官方文档去看。

最后才发现,我的截图数据,都是我的信息~~好可怕。也不想回去做重复工作去改了。