RESTful API 设计与实现

前言

  • 在实际的项目开发中,进行至接口设计阶段时,后端开发人员和前端开发人员都会参与其中,根据已制定的规范对接口进行设计和返回数据格式的约定(不同项目组规范可能不同),接口的请求方式不会仅仅只有 GET 方式,返回结果的数据格式反而会比较统一,返回结果一般会进行封装。本篇文章将会对 api 设计及数据规范进行简单的介绍,之后结合实际案例对数据交互进行编码实现。

RESTful介绍

功能

资源:

  • 互联网所有的事物都可以被抽象为资源

资源操作:

  • 使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
  • 分别对应添加、删除、修改、查询
传统方式操作资源

通过不同的参数来实现不同的效果!方法单一,post和get.

  • http://127.0.0.1/item/queryltem.action?id=1查询,GET
  • http://127.0.0.1/item/saveltem.action新增,POST
  • http://127.0.0.1/item/updateltem.action更新,POST
  • http://127.0.0.1/item/deleteltem.action?id=1删除,GET或POST
使用RESTful操作资源

可以通过不同的请求方式来达到不同的效果!,实现URL的复用
如下:请求地址一样但是功能可以不同!

  • http://127.0.0.1/item/1查询, GET
  • http://127.0.0.1/item新增, POST.
  • http://127.0.0.1/item更新, PUT
  • http://127.0.0.1/item/1删除, DELETE

RESTful api 设计规范

  • 目前比较流行的一套接口规范就是 RESTful api,REST(Representational State Transfer),中文翻译叫"表述性状态转移",

基本原则一:URI

  • 应该将 api 部署在专用域名之下。
  • URL 中尽量不用大写。
  • URI 中不应该出现动词,动词应该使用 HTTP 方法表示但是如果无法表示,也可使用动词,例如:search 没有对应的
  • HTTP 方法,可以在路径中使用 search,更加直观。
  • URI 中的名词表示资源集合,使用复数形式。
  • URI 可以包含 queryString,避免层级过深。

基本原则二:HTTP 动词

对于资源的具体操作类型,由 HTTP 动词表示,常用的 HTTP 动词有下面五个:

  • GET:从服务器取出资源(一项或多项)。
  • POST:在服务器新建一个资源。
  • PUT:在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH:在服务器更新资源(客户端提供改变的属性)。
  • DELETE:从服务器删除资源。

还有两个不常用的 HTTP 动词:

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

例子:

用户管理模块:

1. [POST]   http://127.0.0.1/item   // 新增
2. [GET]    http://127.0.0.1/item/1 // 列表查询
3. [PUT]    http://127.0.0.1/item   // 修改
4. [DELETE] http://127.0.0.1/item/1  // 删除

基本原则三:状态码(Status Codes)

处理请求后,服务端需向客户端返回的状态码和提示信息。

常见状态码**(状态码可自行设计,只需开发者约定好规范即可)**:

  • 200:SUCCESS 请求成功。
  • 401:Unauthorized 无权限。
  • 403:Forbidden 禁止访问。
  • 410:Gone 无此资源。
  • 500:INTERNAL SERVER ERROR 服务器发生错误。 …

基本原则四:错误处理

如果服务器发生错误或者资源不可达,应该向用户返回出错信息。

基本原则五:服务端数据返回

后端的返回结果最好使用 JSON 格式,且格式统一。

基本原则六:版本控制

规范的 api 应该包含版本信息,在 RESTful api 中,最简单的包含版本的方法是将版本信息放到 url 中,如:

[GET]    http://127.0.0.1/v1/users?page=1&rows=10
[PUT]    http://127.0.0.1/v1/users/12

另一种做法是,使用 HTTP header 中的 accept 来传递版本信息。

接口安全原则的注意事项:

安全原则一:Authentication 和 Permission

Authentication 指用户认证,Permission 指权限机制,这两点是使 RESTful api 强大、灵活和安全的基本保障。

常用的认证机制是 Basic Auth 和 OAuth,RESTful api 开发中,除非 api 非常简单,且没有潜在的安全性问题,否则,认证机制是必须实现的,并应用到 api 中去。Basic Auth 非常简单,很多框架都集成了 Basic Auth 的实现,自己写一个也能很快搞定,OAuth 目前已经成为企业级服务的标配,其相关的开源实现方案非常丰富。

安全原则二:CORS

CORS 即 Cross-origin resource sharing,在 RESTful api 开发中,主要是为 js 服务的,解决调用 RESTful api 时的跨域问题。

由于固有的安全机制,js 的跨域请求时是无法被服务器成功响应的。现在前后端分离日益成为 web 开发主流方式的大趋势下,后台逐渐趋向指提供 api 服务,为各客户端提供数据及相关操作,而网站的开发全部交给前端搞定,网站和 api 服务很少部署在同一台服务器上并使用相同的端口,js 的跨域请求时普遍存在的,开发 RESTful api 时,通常都要考虑到 CORS 功能的实现,以便 js 能正常使用 api。

目前各主流 web 开发语言都有很多优秀的实现 CORS 的开源库,我们在开发 RESTful api 时,要注意 CORS 功能的实现,直接拿现有的轮子来用即可。

RESTFul风格和AJAX结合

目的

实现增删改查

环境

  • springboot 2.4.3
  • mybatis-springboot 2.1.4
  • jquery 3.5.1
  • mybatis-generator 1.3.5
  • thymeleaf

准备工作

导入相关启动器和jar包

  • springboot web启动器(内置tomcat)
  • mybatis-springboot启动器(整合mybatis)
  • webjars(导入jquery,不联网也能使用)
  • thymeleaf (解析templates下的html页面)
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.5.1</version>
    </dependency>
    
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	
	<dependency>
     <groupId>mysql</groupId>
   		  <artifactId>mysql-connector-java</artifactId>
   		  <scope>runtime</scope>
	 </dependency>
    
</dependencies>

新建一个user表,添加一条数据

CREATE TABLE `user`(

	`id` INT(10) NOT NULL AUTO_INCREMENT ,
	`username` VARCHAR(20) NOT NULL,
	`password` VARCHAR(30) NOT NULL,
	PRIMARY KEY(`id`)
 
)ENGINE=INNODB DEFAULT CHARSET =utf8;


INSERT INTO `user`(`id`,`username`,`password`) VALUES (1,'admin','123456');

使用Mybatis-Generator自动生成代码

  • 在pom.xml中增加插件配置
<build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
           </plugin>

           <plugin>
               <groupId>org.mybatis.generator</groupId>
               <artifactId>mybatis-generator-maven-plugin</artifactId>
               <version>1.3.5</version>
               <dependencies>
                   <dependency>
                       <groupId> mysql</groupId>
                       <artifactId>mysql-connector-java</artifactId>
                       <version> 5.1.39</version>
                   </dependency>
                   <dependency>
                       <groupId>org.mybatis.generator</groupId>
                       <artifactId>mybatis-generator-core</artifactId>
                       <version>1.3.5</version>
                   </dependency>
               </dependencies>
               <executions>
                   <execution>
                       <id>Generate MyBatis Artifacts</id>
                       <phase>package</phase>
                       <goals>
                           <goal>generate</goal>
                       </goals>
                   </execution>
               </executions>
               <configuration>
                   <verbose>true</verbose>
                   <!-- 是否覆盖 -->
                   <overwrite>true</overwrite>
                   <!-- MybatisGenerator的配置文件位置 -->
                   <configurationFile>
                   src/main/resources/mybatisGeneratorConfig.xml
                   </configurationFile>
               </configuration>
           </plugin>
       </plugins>
   </build>

编写自动生成代码的配置文件

  • src/main/resources/mybatisGeneratorConfig.xm
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="my-blog-generator-config" targetRuntime="MyBatis3">
        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="utf-8"/>
        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
        <!--创建Java类时对注释进行控制-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库地址及登陆账号密码 改成你自己的配置-->
        <jdbcConnection
                driverClass="com.mysql.jdbc.Driver"
                connectionURL="jdbc:mysql://localhost:3306/my_blog_db"
                userId="root"
                password="root">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成实体类设置-->
        <javaModelGenerator targetPackage="com.rm.pojo" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成Mapper文件设置-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成Dao类设置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.rm.dao"
                             targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--需要自动生成代码的表及对应的类名设置-->
        <table tableName="user" domainObjectName="User"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
        </table>
    </context>
</generatorConfiguration>

配置数据源

  • 及绑定mapper映射文件
spring.datasource.url=jdbc:mysql://localhost:3306/my_blog_db
spring.datasource.name=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/*.xml

点击插件生成
在这里插入图片描述
生成代码后的项目目录
在这里插入图片描述
代码生成后,如不需要,可删除配置文件及plugins

在主启动器中添加mapper扫描,将其注入到spring容器

  • 这样就不用对mapper进行一个个的添加@Mapper注解了
@MapperScan("com.rm.dao")
@SpringBootApplication
public class RestfulApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestfulApplication.class, args);
    }

}

需要从mysql数据库查询数据一定要添加依赖

<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <scope>runtime</scope>
 </dependency>

前端页面

  • test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Restful测试</title>
</head>
<body>


<div id="test0">
    <h1>查询所有用户</h1>
    <a href="#" onclick="test0()">点击查询所有用户</a>
    <p>所有用户数据:</p>
    <div id="t0"></div>

</div>

<div id="test1">
    <h1>添加新用户</h1>

    <input id="aid" placeholder="请输入id">
    <input id="username" placeholder="请输入姓名">
    <input id="password" placeholder="请输入密码">
    <a href="#" onclick="test1()">点击添加</a>

  <div>
  <p>添加的新用户:</p>
  <p id="t1"></p>
  </div>
</div>

<div id="test2">
    <h1>根据id查询用户</h1>

    <input id="sid" placeholder="请输入需要查询的id">

    <a href="#" onclick="test2()">点击查询</a>

    <div>
        <div>id为<p id="ids"> </p>的用户数据为:</div>
        <p id="t2"></p>
    </div>
</div>

<div id="test3">
    <h1>根据id删除用户</h1>

    <input id="did" placeholder="请输入需要删除的用户id">

    <a href="#" onclick="test3()">点击删除</a>

    <div>
        <div>id为<p id="idd"> </p>的用户被删除了</div>
    </div>
</div>

<div id="test4">
    <h1>修改用户</h1>
    <h5>本质和添加用户类似</h5>

    <input id="uid" placeholder="请输入需要修改的id">
    <input id="uusername" placeholder="请输入姓名">
    <input id="upassword" placeholder="请输入密码">
    <a href="#" onclick="test4()">点击修改</a>

    <div>
        <div>id为<p id="idu"> </p>的用户修改后的数据如下:</div>
        <div id="t4"></div>
    </div>

</div>


</body>

<script src="webjars/jquery/3.5.1/jquery.js"></script>

<script type="text/javascript">
    function test0() {
        $.ajax({
            url:'test0',
            type:'GET',
            contentType: 'application/json; charset=utf-8',
            success:function (data) {
                $("#t0").html(JSON.stringify(data));
            },
            error:function (data) {
                $('#t0').html('接口异常,请联系管理员!');
            }
        })

    };

    function test1() {
        var id=$("#aid").val();
        var username =$("#username").val();
        var password=$("#password").val();

        var user={
            id:id,
            username :username,
            password:password
        };

        console.log(user);

       //创建一个Ajax请求,点击请求查询用户信息并展示在页面上
        $.ajax({
            type:"POST",
            url:"test1",
            dataType:"json",
            contentType: 'application/json; charset=utf-8',
            data:JSON.stringify(user),
            success: function (result) {
                $('#t1').html(JSON.stringify(result));
            },
            error: function () {
                $('#t1').html('接口异常,请联系管理员!');
            },


        });

    };


    function test2() {
        var sid =$("#sid").val();

        $.ajax({
            type: "POST",
            url:"test2/" + sid,
            dataType:"json",
            contentType: 'application/json; charset=utf-8',
            data:sid,
            success: function (result) {
                $('#t2').html(JSON.stringify(result));
                var u=JSON.stringify(result);
                $('#ids').html(JSON.parse(u).id);
            },
            error: function () {
                $('#t2').html('接口异常,请联系管理员!');
            },


        });

    }

    function test3() {
        var did =$("#did").val();

        $.ajax({
            type: "DELETE",
            url:"test3/" + did,
            dataType:"json",
            contentType: 'application/json; charset=utf-8',
            data:did,
            success: function (result) {

                $('#idd').html(result);
            },
            error: function () {
                $('#idd').html('接口异常,请联系管理员!');
            },


        });

    }


    function test4() {
        var id=$("#uid").val();
        var username =$("#uusername").val();
        var password=$("#upassword").val();

        var user2={
            id:id,
            username :username,
            password:password
        };
        
        //创建一个Ajax请求,点击请求查询用户信息并展示在页面上
        $.ajax({
            type: "PUT",
            url:"test4",
            dataType:"json",
            contentType: 'application/json; charset=utf-8',
            data:JSON.stringify(user2),
            success: function (result) {
                $("#idu").html(JSON.parse(JSON.stringify(result)).id);
                $('#t4').html(JSON.stringify(result));
            },
            error: function () {
                $('#t4').html('接口异常,请联系管理员!');
            },


        });

    };

</script>
</html>

Controller

package com.rm.conroller;

import com.rm.dao.UserMapper;
import com.rm.pojo.User;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@Controller
public class MyController {

    //为了节省时间,不添加service层
    @Resource
    private UserMapper userMapper;



    @RequestMapping("/t")
    public String t1(){
        return "test";
    }


    @ResponseBody
    @GetMapping("/test0")
    public List<User> getAll(){

        List<User> all = userMapper.getAll();
        return all;
    }

    @ResponseBody
    @PostMapping("/test1")
    //添加新用户
    public User test1(@RequestBody User user){

        userMapper.insert(user);
        return user;
    }



    @ResponseBody
    @PostMapping("/test2/{sid}")
    //根据id查询用户
    public User test2(@PathVariable("sid") int sid){
        User user = userMapper.selectByPrimaryKey(sid);
        return user;
    }

    @ResponseBody
    @DeleteMapping("/test3/{did}")
    //添加新用户
    public int test3(@PathVariable("did") int did){
        userMapper.deleteByPrimaryKey(did);
        return did;
    }

    @ResponseBody
    @PutMapping("/test4")
    //添加新用户
    public User test4(@RequestBody User user){

        int i = userMapper.updateByPrimaryKey(user);
        return user;
    }




}

结果

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值