RESTful接口

一、RESTful接口原理

1.WebService技术介绍

一言以蔽之:WebService是一种跨编程语言和跨操作系统平台的远程调用技术

所谓跨编程语言和跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。

所谓远程调用,就是一台计算机a上 的一个程序可以调用到另外一台计算机b上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银 行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程 序可以调用这些服务功能,这样扩展了自己系统的市场占有率,往大的概念上吹,就是所谓的SOA应用。

其实可以从多个角度来理解 WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过 Web来调用这个应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次 看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何 你喜欢的语言,在任何你喜欢的平台上写Web service ,只要我们可以通过Web service标准对这些服务进行查询和访问。

WebService平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,WebService平台 必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。Web service平台必须提供一种标准来描述 Web service,让客户可以得到足够的信息来调用这个Web service。最后,我们还必须有一种方法来对这个Web service进行远 程调用,这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。

2.REST软件架构

REST 即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

在主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务进行图书查找;雅虎提供的Web服务也是REST风格的。

REST是在Browser/Server的基础上添加了另外3个规范性原则组成,第一个为统一接口,第二个为分层系统,第三个为按需代码。

统一接口 为REST定义了对系统资源进行操作统一的方法和链接入口,REST架构的核心就是资源,它将互联网中所有的可访问、操作的数据信息都看作资源进行处理,从而简化了REST对不同数据信息的处理方式和过程,也为REST的高度 重用性以及不同分布式异构系统的高交互性奠定了基础。

分层系统 的定义使得web Service的定义和实现Web系统不同的层次之间具有良好的独立性,从而降低了系统层次依赖耦合性和复杂性,而良好的接口封装、应用功能实现等干扰性大大降低,从而为Web系统的可维护性、扩展性等奠定了良好的基础。

按需代码 则是web Service可选的要求,通过按需代码开发者可以在客户端的应用程序进行功能扩展,从而实现对客户需求的满足,从而使得系统更加人性化,提升其友好性。

通过基于 REST 的 API 公开系统资源是一种灵活的方法,可以为不同种类的应用程序提供以标准方式格式化的数据。 它可以帮助满足集成需求(这对于构建可在其中容易地组合 (Mashup) 数据的系统非常关键),并帮助将基于 REST 的基本服务集扩展或构建为更大的集合。

3.REST接口设计规范

随着互联网和移动设备的发展,人们对Web应用的使用需求也增加,传统的动态页面由于低效率而渐渐被HTML+JavaScript(Ajax)的前后端分离 所取代,并且安卓、IOS、小程序等形式客户端层出不穷,客户端的种类出现多元化,而客户端和服务端就需要接口进行通信,但接口设计的规范性 就成了一个挑战性的问题:

一套结构清晰、符合标准、易于理解、扩展方便 让大部分人都能够理解接受 的接口风格就显得越来越重要,而RESTful风格的接口 (RESTful API)刚好有以上特点,就逐渐被实践应用而变得流行起来。

REST接口设计的风格大致有以下几个主要特征

以资源为基础 :资源可以是一个图片、音乐、一个XML格式、HTML格式或者JSON格式等网络上的一个实体,除了一些二进制的资源外普通的文本资源更多以JSON为载体、面向用户的一组数据(通常从数据库中查询而得到)。

统一接口 : 对资源的操作包括获取、创建、修改和删除,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。换言而知,使用RESTful风格的接口但从接口上你可能只能定位其资源,但是无法知晓它具体进行了什么操作,需要具体了解其发生了什么操作动作要从其HTTP请求方法类型上进行判断。具体的HTTP方法和方法含义如下:

  1. GET (SELECT):从服务器取出资源(一项或多项)。
  2. POST (CREATE):在服务器新建一个资源。
  3. PUT (UPDATE):在服务器更新资源(客户端提供完整资源数据)。
  4. PATCH (UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
  5. DELETE (DELETE):从服务器删除资源。

当然也有很多在具体使用的时候使用PUT表示更新。从请求的流程来看,RESTful API和传统API大致架构如下

URI指向资源 :URI = Universal Resource Identifier 统一资源标志符,用来标识抽象或物理资源的一个紧凑字符串。URI包括URL和URN,在这里更多时候可能代指URL(统一资源定位符)。RESTful是面向资源的,每种资源可能由一个或多个URI对应,但一个URI只指向一种资源。

无状态 :服务器不能保存客户端的信息, 每一次从客户端发送的请求中,要包含所有必须的状态信息,会话信息由客户端保存, 服务器端根据这些状态信息来处理请求。 当客户端可以切换到一个新状态的时候发送请求信息, 当一个或者多个请求被发送之后, 客户端就处于一个状态变迁过程中。 每一个应用的状态描述可以被客户端用来初始化下一次的状态变迁。

URL设计规范

URL为统一资源定位器 ,接口属于服务端资源,首先要通过URL这个定位到资源才能去访问,而通常一个完整的URL组成由以下几个部分构成:

URI = scheme "://" host ":" port "/" path [ "?" query ][ "#" fragment ]

scheme: 指底层用的协议,如http、https、ftp

host: 服务器的IP地址或者域名

port: 端口,http默认为80端口

path: 访问资源的路径,就是各种web 框架中定义的route路由

query: 查询字符串,为发送给服务器的参数,在这里更多发送数据分页、排序等参数。

fragment: 锚点,定位到页面的资源

  • 通常一个RESTful API的path组成如下

/{version}/{resources}/{resource_id}

version:API版本号,有些版本号放置在头信息中也可以,通过控制版本号有利于应用迭代。

resources:资源,RESTful API推荐用小写英文单词的复数形式。

resource_id:资源的id,访问或操作该资源。

  • 如果资源级别较大,其下还可细分很多子资源也可以灵活设计URL的path ,例如:

/{version}/{resources}/{resource_id}/{subresources}/{subresource_id}

  • 如果增删改查无法满足业务要求,可以在URL末尾加上action ,例如

/{version}/{resources}/{resource_id}/action

其中action就是对资源的操作。

  • 接口的安全性和幂等性

在谈及GET,POST,PUT,DELETE的时候,就必须提一下接口的安全性和幂等性,其中安全性是指方法不会修改资源状态,即读的为安全的,写的操作为非安全的。而幂等性的意思是操作一次和操作多次的最终效果相同,客户端重复调用也只返回同一个结果。

上述四个HTTP请求方法的安全性和幂等性如下:

  • 对于RESTful API的URL具体设计的规范总结如下:
  1. 不用大写字母,所有单词使用英文且小写。
  2. 连字符用中杠"-"而不用下杠"_"
  3. 正确使用 "/"表示层级关系,URL的层级不要过深,并且越靠前的层级应该相对越稳定
  4. 结尾不要包含正斜杠分隔符"/"
  5. URL中不出现动词 ,用请求方式表示动作
  6. 资源表示用复数不要用单数
  7. 不要使用文件扩展名

示例:

4.状态码和返回数据

服务端处理完成后客户端也可能不知道具体成功了还是失败了,服务器响应时,包含状态码和返回数据两个部分。

状态码

要正确使用各类状态码来表示该请求的处理执行结果。状态码主要分为五大类:

  • 1xx:相关信息
  • 2xx:操作成功
  • 3xx:重定向
  • 4xx:客户端错误
  • 5xx:服务器错误

每一大类有若干小类,状态码的种类比较多,而主要常用状态码罗列在下面:

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。

201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

204 NO CONTENT - [DELETE]:用户删除数据成功。

400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。

401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。

403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。

404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。

410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。

422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。

500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

返回结果

针对不同操作,服务器向用户返回数据,而不同的API封装的返回实体类也不同,但都返回JSON格式数据给客户端。

二、RESTful创建示例

1.访问SpringBoot的官网,使用脚手架(https://start.spring.io/ )生成项目框架:

-- 选择 SpringWeb

-- 选择 SpringWebService

-- 选择 模板 : Apache FreeMaker

-- 选择 MyBatis Framwwork/ 或者手动添加 MybatisPlus依赖

-- 选择 MySql Driver 或者 MariaDB Driver

构建工具选择 Gradle 或者 Maven

java 复制代码
Build.gradle:



plugins {

    id 'java'

    id 'org.springframework.boot' version '3.1.5'

    id 'io.spring.dependency-management' version '1.1.3'

}



group = 'com.firestudio'

version = '0.0.1-SNAPSHOT'



java {

    sourceCompatibility = '17'

}



repositories {

    mavenCentral()

}



dependencies {

    //implementation 'org.apache.velocity:velocity-engine-core:2.3'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

    implementation 'org.springframework.boot:spring-boot-starter-data-rest'

    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.springframework.boot:spring-boot-starter-websocket'

    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.4.1'

    implementation 'org.projectlombok:lombok:1.18.28'



    runtimeOnly 'com.mysql:mysql-connector-j'

    // lombok,编译有效,打包无效

    compileOnly 'org.projectlombok:lombok'

    // gradle 5.0以上版本注解处理不再compile classpath,需要增加 annotation processor path

    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testImplementation 'com.baomidou:mybatis-plus-generator:3.5.4.1'

}



tasks.named('test') {

    useJUnitPlatform()

}

2.使用MyBatisPlus自动生成数据库访问代码

java 复制代码
public static void GenCode(){
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/bd_admin1?characterEncoding=UTF-8&useUnicode=true&useSSL=false", "root", "")
                // 全局配置
                .globalConfig(builder -> {
                    builder.author("wanglicheng") // 设置作者
                            .commentDate("2023-12-12 12:12:12")   //注释日期
                            .outputDir(System.getProperty("user.dir") + "/src/main/java") // 指定输出目录
                            .disableOpenDir() //禁止打开输出目录,默认打开
                    ;
                })
                // 包配置
                .packageConfig(builder -> {
                    builder.parent("com.firestudio.hellospring") // 设置父包名
                            //.mapper("dao")  //mapper包名, 不修改就默认为: mapper
                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mappers")); // 设置mapperXml生成路径
                })
                // 策略配置
                .strategyConfig(builder -> {
                    builder.addInclude("user") // 设置需要生成的表名
                            .addTablePrefix("sys_") // 设置过滤表前缀
                            // Entity 策略配置
                            .entityBuilder()
                            .enableLombok() //开启 Lombok
                            .enableFileOverride() // 覆盖已生成文件
                            .naming(NamingStrategy.underline_to_camel)  //数据库表映射到实体的命名策略:下划线转驼峰命
                            .columnNaming(NamingStrategy.underline_to_camel)    //数据库表字段映射到实体的命名策略:下划线转驼峰命
                            // Mapper 策略配置
                            .mapperBuilder()
                            .enableBaseResultMap()
                            .enableBaseColumnList()
                            //.formatMapperFileName("%sDao")   //默认为: <表名>Mapper
                            .enableFileOverride() // 覆盖已生成文件
                            // Service 策略配置
                            .serviceBuilder()
                            //.enableFileOverride() // 覆盖已生成文件: 业务逻辑层, 最好不覆盖
                            .formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名,如 UserService
                            .formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名,如 UserServiceImpl
                            // Controller 策略配置
                            //.controllerBuilder()   //是否生成Controller
                            .enableFileOverride() // 覆盖已生成文件
                    ;
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();

    }

使用idea创建springboot项目;

添加相关依赖

配置application.properties代码以连接数据库

在com.firestudio.hellospring中创建controller软件包,并在其中创建HelloController.java

运行代码,运行成功

通过浏览器访问

MyBatisPlus自动生成数据库访问代码

java 复制代码
package com.firestudio.hellospring;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import com.firestudio.hellospring.mapper.UserMapper;
import com.firestudio.hellospring.service.UserService;
import org.junit.jupiter.api.Test;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collections;

public class CodeGeneratorTest {

    public static void main(String[] args) {
        System.out.println("Hello, Test!");

        //生成代码
        GenCode();
    }

    public static void GenCode(){
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/db_admin1?characterEncoding=UTF-8&useUnicode=true&useSSL=false", "root", "")
                // 全局配置
                .globalConfig(builder -> {
                    builder.author("wanglicheng") // 设置作者
                            .commentDate("yyyy-MM-dd hh:mm:ss")   //注释日期
                            .outputDir(System.getProperty("user.dir") + "/src/main/java") // 指定输出目录
                            .disableOpenDir() //禁止打开输出目录,默认打开
                    ;
                })
                // 包配置
                .packageConfig(builder -> {
                    builder.parent("com.firestudio.hellospring") // 设置父包名
                            //.mapper("dao")  //mapper包名, 不修改就默认为: mapper
                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mappers")); // 设置mapperXml生成路径
                })
                // 策略配置
                .strategyConfig(builder -> {
                    builder.addInclude("user") // 设置需要生成的表名
                            .addTablePrefix("sys_") // 设置过滤表前缀
                            // Entity 策略配置
                            .entityBuilder()
                            .enableLombok() //开启 Lombok
                            .enableFileOverride() // 覆盖已生成文件
                            .naming(NamingStrategy.underline_to_camel)  //数据库表映射到实体的命名策略:下划线转驼峰命
                            .columnNaming(NamingStrategy.underline_to_camel)    //数据库表字段映射到实体的命名策略:下划线转驼峰命
                            // Mapper 策略配置
                            .mapperBuilder()
                            .enableBaseResultMap()
                            .enableBaseColumnList()
                            //.formatMapperFileName("%sDao")   //默认为: <表名>Mapper
                            .enableFileOverride() // 覆盖已生成文件
                            // Service 策略配置
                            .serviceBuilder()
                            //.enableFileOverride() // 覆盖已生成文件: 业务逻辑层, 最好不覆盖
                            .formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名,如 UserService
                            .formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名,如 UserServiceImpl
                    // Controller 策略配置
                    //.controllerBuilder()   //是否生成Controller
                    //.enableFileOverride() // 覆盖已生成文件
                    ;
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();

    }
    @Test
    public void testCodeGen(){


    }
}

使用bootTestRun生成数据库访问代码,启动Hellospring

生成的相关的数据库访问代码

浏览器访问localhost:8080/user/list

浏览器访问localhost:8080/user/1

3.编写控制器代码 xxController,并完成REST风格的接口设计

java 复制代码
package com.firestudio.hellospring.controller;

import com.firestudio.hellospring.entity.User;
import com.firestudio.hellospring.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author teacher-wang
 * @since 2023-11-08 12:09:31
 */
@RestController  //@Controller + @ResponseBody
@RequestMapping("/user")
@AllArgsConstructor
public class UserController{

    private final UserService userService;

    // @RequestMapping(value = "/{id}", method = GET, produces = "application/json")
    @GetMapping("/{id}")
    public User queryUserById(@PathVariable("id") Integer id) {

        return userService.getById(id);
    }
    @GetMapping("/list")
    public List<User> queryUserById() {
        return userService.list();
    }

    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable("id") Integer id) {
        userService.removeById(id);
        return "Delete OK!";
    }

    @PostMapping("/")
    public String createUser(@RequestBody User user) {
        userService.saveOrUpdate(user);  //插入或者更新记录  , save()方法是插入记录
        return "Create OK!";
    }

    @PutMapping("/")
    public String modifyUser(@RequestBody User user) {
        userService.updateById(user);  //更新记录
        return "Modify OK!";
    }
}

三、总结

1.常见的虚拟化解决方案有哪些?

  1. 服务器虚拟化:通过将服务器资源分配给多个虚拟机,每个虚拟机可以运行一个独立的操作系统和应用程序。这种技术可以减少硬件成本、提高服务器的利用率和管理效率。业界领先的服务器虚拟化平台有VMware vSphere和Microsoft Hyper-V等。
  2. 网络虚拟化:通过网络设备将多个网络连接组合成一个逻辑网络,实现网络的虚拟化和抽象化。这种技术可以简化网络结构、提高网络安全性和可管理性。网络虚拟化平台有VMware NSX和Cisco ACI等。
  3. 存储虚拟化:将多种、多个存储设备统一管理起来,为使用者提供大容量、高数据传输性能的存储系统。存储虚拟化产品有VMware VSAN和EMC VMAX等。
  4. 应用虚拟化:将应用程序从底层操作系统中抽象出来,实现在任何时间、任何地点、任何设备上的访问。这种技术可以提高应用程序的可用性和可访问性。应用虚拟化平台有VMware ThinApp和Microsoft App-V等。

2.存储扩容的方法

  1. 按位扩充:扩大存储器的位数,存储单元数目不变,每个单元的位数增加。
  2. 按字扩充:芯片的存储单元存储的信息位数不变,但是存储单元的个数增加。
  3. 内存覆盖(进程内部):把用户空间分成一个固定区和若干个覆盖区。将经常活跃的部分放在固定区,其余部分按调用关系分段。首先将那些即将要访问的段放入覆盖区,其他段放在外存中,在需要调用前,系统再将其调入覆盖区,替换覆盖区中原有的段。
  4. 内存交换(进程之间):把处于等待状态(或在CPU调度原则下被剥夺运行权利)的程序从内存移到辅存,把内存空间腾出来,这一过程又叫换出;把准备好竞争CPU运行的程序从辅存移到内存,这一过程又称为换入。中级调度就是釆用交换技术。它由计算机操作系统进行交换,不同进程,不同程序里的程序段。它必须足够大,并且提供对这些内存映像的直接访问。转移时间与所交换的内存空间成正比。

3.创建虚拟机以实现ECS计算的方法

  1. 登录云服务提供商的控制台。
  2. 进入ECS管理界面,选择创建实例选项。
  3. 选择实例规格、地域和网络等基本设置。
  4. 配置操作系统镜像和登录方式。
  5. 完成高级设置(可选),如安全组、标签等。
  6. 确认配置并创建实例。
相关推荐
向前看-1 小时前
验证码机制
前端·后端
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
Clockwiseee4 小时前
php伪协议
windows·安全·web安全·网络安全
AskHarries5 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
xcLeigh6 小时前
网络安全 | 防火墙的工作原理及配置指南
安全·web安全
isolusion6 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp6 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder7 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
安全小王子7 小时前
Kali操作系统简单介绍
网络·web安全