RESTful接口开发

1 概述

package com.aaaaaa.manager.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.aaaaaa.manager.pojo.Item;
import com.aaaaaa.manager.service.ItemService;

@Controller
@RequestMapping("item/interface")
public class ItemInterfaceController {
    // http://127.0.0.1/query/1?rows=2
    // @RequestParam:获取请求参数的数据(包括表单提交的数据),就是获取rows=2 的2
    // @PathVariable:获取请求url路径上的数据,就是1

    @Autowired
    private ItemService itemService;

    // 根据id查询 GET
    // http://manager.aaaaaa.com/rest/item/interface/{id}
    /**
     * 根据id查询
     * 
     * @param id
     * @return
     */
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Item> queryItemById(@PathVariable Long id) {
        try {
            Item item = this.itemService.queryById(id);
            // 查询成功,返回200
            // return ResponseEntity.status(HttpStatus.OK).body(item);
            // return ResponseEntity.ok().body(item);
            return ResponseEntity.ok(item);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 如果服务器出错,返回500
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

    // 新增 POST
    // http://manager.aaaaaa.com/rest/item/interface
    /**
     * 新增
     * 
     * @param item
     * @return
     */
    @RequestMapping(method = RequestMethod.POST)
    // @ResponseBody:不加这个注解就会走视图解析器,返回页面,加这个注解就走转换器,返回数据
    // 加上@ResponseBody注解和返回ResponseEntity效果是一样的,都会走转换器,返回数据,所以使用任意一个即可,两个都用也没问题
    public ResponseEntity<Void> saveItem(Item item) {
        try {
            this.itemService.save(item);
            // 新增成功,返回201
            return ResponseEntity.status(HttpStatus.CREATED).build();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 如果服务器出错,返回500
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

    // 更新 PUT
    // http://manager.aaaaaa.com/rest/item/interface
    /**
     * 更新
     * 
     * @param item
     * @return
     */
    @RequestMapping(method = RequestMethod.PUT)
    public ResponseEntity<Void> updateItem(Item item) {
        try {
            this.itemService.updateByIdSelective(item);
            // 修改成功,返回204
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 如果服务器出错,返回500
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

    // 根据id删除 DELETE
    // http://manager.aaaaaa.com/rest/item/interface/{id}
    /**
     * 根据id删除
     * 
     * @param id
     * @return
     */
    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
    public ResponseEntity<Void> deleteItemById(@PathVariable Long id) {
        try {
            this.itemService.deleteById(id);
            // 删除成功,返回204
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 如果服务器出错,返回500
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

}

1.1 撰写目的

本文用于定义一种统一的RESTful接口设计方案,希望具有参考价值。本文所描述的方案比较学院派,在上一家公司提出没有被采纳,在所了解到的有限的若干家声称采用了RESTful风格的公司里,发现他们也偏离甚远。当然,他们这么做是有理由的,我也理解,这只是取舍问题。这篇文章其实是旧文了,2016年年底就已经写好,但是一直躺在电脑的硬盘里,不想白费了当时的功夫,因此在此公开。

 

1.2 为什么采用REST

目的是为了服务端与客户端的解耦。SOA仅仅是从结构上将前后端分离,但是实际上数据逻辑还是没有实现解耦,服务端接口升级往往会影响客户端,两者的行为需要严格约定。而REST采用HTTP协议进行约定,客户端仅仅需要按照HTTP协议来理解服务端返回的数据,虽然与业务相关的数据结构还是需要约定,但是这确实进一步解耦了服务端与客户端。

另外,由于严格遵照HTTP协议进行数据返回,对于安全的接口,可以在返回的Header里设置缓存策略(接口安全性的概念在下文会解释)。

1.3 文档结构

第二部分将阐述关于RESTful的若干个关键的概念,明确第二部分阐述的几个概念有利于设计、实现优雅规范的接口。

第三部分就URL命名的问题进行约定。

第四部分对消息实体进行约定。

第五部分对『向RESTful接口发起请求』进行阐述,约定要实现的方法,约定请求的头部和body的格式。

第六部分对接口的响应格式进行约定,包括响应消息的头部、状态码、JSON实体。

第七部分对版本控制的问题进行约定。

第八部分对RESTful接口的实现提出了实现工具的建议。

2 关键概念

明确一些关键的概念是很重要的,虽然RESTful风格的API设计方案并没有统一的标准,但是还是需要符合一定的原则进行设计,否则就不能称为RESTful风格的API。因为许多人并没有对REST进行充分的了解就宣称自己的API是RESTful风格的API,以至于RESTful的提出者Fielding博士本人无法忍受,在2008年为此专门写了一篇博客『REST APIs must be hypertext-driven』,hypertext-driven与HATEOAS是同一个概念的不同表述,在下文会进行阐述。

2.1 RESTful

REST不是一种协议,也不是一种文件格式,更不是一种开发框架。它是一系列的设计约束的集合:无状态性、将超媒体作为应用状态的引擎等。REST是Representation State Transfer的缩写,中文是『表述性状态转移』,这里就涉及到资源的表述与状态两个概念。

简单地说,资源可以看作是服务器上存储的所有数据,资源的表述则是服务器对外提供的指向这些资源的方式,使用JSON、XML等均可,一个资源可以有多种表述;资源的状态则是服务器的数据存储状态,例如在t时刻,服务器中存储了m条数据,这时候客户端向服务端提交了一个创建数据的请求,服务器处理了此请求并创建了一条数据,那么在t+1时刻,服务器中就存储了m+1条数据,这两个时刻的资源状态就是不一样的,t时刻发生的请求导致了资源状态的改变。

2.2 HATEOAS

Hypermedia As The Engine Of Application State,超媒体作为应用程序状态的引擎。这是REST区别于其他SOA风格的主要特点。客户端与服务端进行互动的时候,完全是通过服务端动态提供的超媒体进行的。除了对超媒体的一般理解,客户端不需要知道其他额外的知识。相反,在一些SOA接口的设计中,客户端与服务端的通信是要事先进行约定的,例如通过文档或者接口描述语言(Interface Description Language, IDL)。而基于HTTP协议的REST设计里,一般采用的就是请求与响应的Header来体现HATEOAS原则(具体请参考:https://en.wikipedia.org/wiki/HATEOAS)。这里也隐含这样一层含义:REST应尽可能地利用HTTP标准中现有的东西,例如Header、标准方法与状态码。

从标准的角度看,HTTP标准是一项RFC标准,世界认可;而其他自定义的SOA标准则可能是一项个人标准或者公司标准,最多是一项互联网草案(这对大部分公司来说都不可能),而一项标准越是被广为认可接受,其实现的通用性就越强。个人标准和公司标准都五花八门,这样对每一个标准都要参照其相关文档实现相应的行为逻辑是很麻烦的。

2.3 安全性

一个方法被调用1次与被调用0次是一样的,此方法就是安全的,否则就是不安全的。例如,一个方法A仅仅是读取数据,并不创建或者修改数据,不论A方法被调用多少次,都不对数据记录产生任何影响,A方法是安全的。而假如有另一个方法B对数据进行删除,B方法被调用1次后,数据会被删除(或者标识位被修改),系统里的数据发生了变化,那么B方法是不安全的。

2.4 幂等性

一个方法被同样地调用1次与被调用多次是一样的,即同样的输入会得到同样的输出,此方法就是幂等的,否则就不是幂等的。

2.3节中A方法与B方法都是幂等的,一个安全的方法一定是幂等的,一个幂等的方法不一定是安全的。

假设一个方法C对某个全局计数器执行自增操作并写入数据库,每次调用C方法都会对系统数据产生影响,那么C方法就不是幂等的。

3 URL命名

URL用于标识资源,因此URL应该以名词进行命名,例如/users, /users/children等。

一般URL会内嵌参数,例如要获取id为313的user的信息,那么URL应该为/users/313,前面的user采用复数,如果要列出其所有后代,则URL应为/users/313/children,children为复数形式,如果要获取其id为499的后代,则URL应为/users/313/children/499

4 消息实体

消息实体,就是请求和响应消息中的entity-body(也称为body),消息实体采用JSON字符串格式。

5 请求

5.1 方法

使用HTTP标准定义的请求方法。

本文由大发快三开奖结果发布于电脑,转载请注明出处:RESTful接口开发

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。