主頁 > 後端開發 > SpringBoot(17)介面架構風格—RESTful與Swagger

SpringBoot(17)介面架構風格—RESTful與Swagger

2022-07-27 07:14:22 後端開發

1.認識REST

1.1什么是REST

??REST是軟體架構的規范體系結構,它將資源的狀態以適合客戶端的形式從服務器端發送到客戶端(或相反方向),在REST中,通過URL進行資源定位,用HTTP動作GET、POST、DELETE、PUSH等)描述操作,完成功能,

??道循RESTful風格,可以使開發的介面通用,以便呼叫者理解介面的作用,基于REST構建的 API 就是 RESTful ( REST 風格)API.

??各大機構提供的API基本都是RESTful風格的,這樣可以統一規范,減少溝通、學習和開發 的成本,

1.2 REST的特征

  • 客戶一服務器(client-server):提供服務的服務器和使用服務的客戶端需要被隔離對待,
  • 無狀態(stateless):服務器端不存盤客戶的請求中的資訊,客戶的每一個請求必須包含服務器處理該請求所需的所有資訊,所有的資源都可以通過URI定位,而且這個定位與其他資源無關,也不會因為其他資源的變化而變化,

??Restful是典型的基于HTTP的協議,HTTP連接最顯著的特點是:客戶端發送的每次請求都需要服務器回送回應;在請求結束后,主動釋放連接:

??從建立連接到關閉連接的程序稱為“一次連接”,前后的請求沒有必然的聯系,所以是無狀態的

??可快取(cachable):服務器必須讓客戶知道請求是否可以被快取,

  • 分層系統(layered System):服務器和客戶之間的通信必須被標準化,
  • 統一介面 (uniform interface):客戶和服務器之間通信的方法必須統一,REStful風格的 資料元操作 CRUD (create、read、update, delete)分別對應 HTTP 方法—— GET 用來獲取資源,POST用來新建資源,PUT用來更新資源,DELETE用來洗掉資源,這樣就統一了資料操作的介面,
  • HTTP狀態碼:狀態碼在REST中都有特定的意義:200、201、202204、400、401、 403、500,比如,401表示用戶身份認證失敗;403表示驗證身份通過了,但資源沒有權限進行操作,
  • 支持按需代碼(Code-On-Demand,可選):服務器可以提供一些代碼或腳本,并在客戶的運行環境中執行,

1.3 認識HTTP方法與CRUD動作映射

??RESTful風格使用同一個URL,通過約定不同的HTTP方法來實施不同的業務, 普通網頁的CRUD和RESTful風格的CRUD的區別,見下表,

??

??可以看出,RESTful風格的CRUD比傳統的CRUD簡單明了,它通過HTTP方法來區分增加、修改、洗掉和查詢,

1.4 實作RESTfuI風格的資料增加、洗掉、修改和查詢

??在SpringBoot中,如果要回傳JSON資料,則只需要在控制器中用@RestController注解, 如果提交HTTP方法,則只需要使用注解@RequestMapping來實作,它有以下兩個屬性,

  • Value:用來制定URI,
  • Method:用來制定HTTP請求方法,

??為了不重復編碼,盡量在類上使用@RequestMapping("")來指定上一級URL

??使用RESTful風格操作資料的方法見以下代碼,

??(1)獲取串列采用的是GET方式,回傳List,例如,下面代碼回傳User的List,

@RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/",method = RequestMethod.GET)
public List<User> getUserList(){
    List<User> userList = new ArrayList<User>(userRepository.findAll());
    return userList;
}

??(2)增加內容(提交內容)采用的是POST方式,一般回傳String型別或int型別的資料,見 以下代碼:

@RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/",method = RequestMethod.POST)
public String add(User user){
    userRepository.save(user);
    return "success";
}

??(3)洗掉內容,必須采用DEIETE方法,一般都是根據id主鍵進行洗掉的

@RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.DELETE)
public String delete(@PathVariable("id")Long id){
    userRepository.deleteById(id);
    return "success";
}

??(4)修改內容,則采用PUT方法,

@RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.PUT)
public String update(User user){
    userRepository.save(user);
    return "success";
}

??(5)查詢內容,和上面獲取串列的方法一樣,也是采用GET方法,

@RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.GET)
public User findUser(@PathVariable("id")Integer id){
    Optional<User> user = userRepository.findById(id.longValue());
    return user.get();
}

??對于RESTful風格的增加、洗掉、修改和查詢,可以撰寫測驗單元,也可以用Postman測驗, 分別用GET、POST、PUT、DELETE方法提交測驗,雖然這樣實作了 RESTful風格,但還有一 個問題——回傳的資料并不統一,在實際生產環境中還需要進行改進,所以需要設計統一的RESTful 風格的資料介面,

2.設計統一的RESTful 風格的資料介面

??近年來,隨著移動互聯網的發展,各種型別的客戶端層岀不窮,如果不統一資料介面,則會造成冗余編碼,增加成本,RESTful風格的API正適合通過一套統一的介面為PC、手機APP等設備提供資料服務,

2.1 版本控制

??隨著業務需求的變更、功能的迭代,API的更改是不可避免的,當一個API修改時,就會出現很多問題,比如,可能會在API中新增引數、修改回傳的資料型別,這就要考慮根據原先版本API 撰寫的客戶端如何保留或順利過渡,所以,需要進行版本控制,

??REST不提供版本控制指南,常用的方法可以分為3種,

??(1)通過URL

??通過URL是最直接的方法,盡管它違背了 URI應該參考唯一資源的原則,當版本更新時,還可以保障客戶端不會受到影響,如下面使用不同URL來確定不同版本,

??二級目錄的方式:

  • API 版本 V1: http:// eg.com/api/v1
  • API 版本 V2: http://eg.com/api/v2

??二級域名的方式:

  • API 版本 V1: http://v1.eg.com
  • API 版本 V2: http://v2.eg.com

??還可以包括日期、專案名稱或其他識別符號,這些識別符號對于開發API的團隊來說足夠有意義, 并旦隨著版本的變化也足夠靈活,

??(2)通過自定義請求頭

??自定義頭(例如,Accept-version)允許在版本之間保留URL,

??(3)通過Accept標頭

??客戶端在請求資源之前,必須要指定特定頭,然后API介面負責確定要發送哪個版本的資源

2.2 過濾資訊

??如果記錄數量很務,則服務器不可能一次都將它們回傳給用戶,API應該提供引數,實作分頁回傳結果,下面是一些常用的引數

  • ?limit=10:指定回傳記錄的數量,
  • ?page=5&size=10:指定第幾頁,以及每頁的記錄數,
  • ?search_type=1:指定篩選條件,

2.3 確定HTTP的方法

在RESTful中,HTTP的方法有以下幾種,

  • GET:代表請求資源,
  • POST:代表添加資源,
  • PUT:代表修改資源,PUT是進行全部的修改,大家在撰寫修改功能時可能會遇到這樣的情況:只修改了一個欄位,但提交之后導致其他欄位為空,這是因為,其他欄位的值沒有一 起提交,資料庫默認為空值,如果只修改一個或幾個欄位,則可以使用PATCH方法,
  • DELETE:代表洗掉資源,
  • HEAD:代表發送HTTP頭訊息,GET中其實也帶了 HTTP頭訊息,
  • PATCH: PUT與PATCH方法比較相似,但它們的用法卻完全不同,PUT用于替換資源, 而PATCH用于更新部分資源,
  • OPTIONS:用于獲取URI所支持的方法,回傳的回應訊息會在HTTP頭中包含"Allow” 的資訊,其值是所支持的方法,如GET,

2.4 確定HTTP的回傳狀態

HTTP的回傳狀態一般有以下幾種,

  • 200:成功,
  • 400:錯誤請求,
  • 404:沒找到資源,
  • 403:禁止,
  • 406:不能使用請求內容特性來回應請求資源,比如請求的是HTML檔案,但是消費者的 HTTP頭包含了 JSON要求,
  • 500:服務器內部錯誤,

2.5定義統一回傳的格式

為了保障前后端的資料互動的順暢,建議規范資料的回傳,并采用固定的資料格式封裝,如,

例外資訊:

{
    "code":"10001",
    "msg":"例外資訊",
    "data":null
}
{
	"code":200,
	"msg":"成功",
	"data":{
		"id":1,
		"name":"buretuzi",
		"age":2
	}
}

2.6 為手機APP、PC、H5網頁提供統一風格的API

??(1)實作回應的列舉類

??列舉是一種特殊的資料型別,它是一種"型別別",比型別多了一些特殊的約束,創建列舉型別要使用“enum”,表示所創建的型別都是java.lang.Enum(抽象類)的子類,見以下代碼:

package com.itheima.exception;

public enum ExceptionMsg {
    SUCCESS("200","操作成功"),
    FAILED("999999","操作失敗");
    private ExceptionMsg(String code,String msg){
        this.code = code;
        this.msg = msg;
    }
    private String code;
    private String msg;
}

??(2)實作回傳的物件物體

??實作回傳的物件物體,回傳Code和Message (資訊),見以下代碼:

package com.itheima.domain;

public class Response {
    private String rspCode="200";
    private String rspMsg="操作成功";
}

??(3)封裝回傳結果

??這里把回傳的結果逬行封裝,以顯示資料,見以下代碼:

package com.itheima.dao;

import com.itheima.domain.Response;

public class ReponseData extends Response {
    private Object data;
    public ReponseData(Object data) {
        this.data = https://www.cnblogs.com/liwenruo/archive/2022/07/26/data;
    }
}

??(4)統一處理例外

查看代碼
 package com.itheima.handler;

import com.itheima.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Map<String, Object> handleMissingServletRequestParameterException(MissingServletRequestParameterException e){
        logger.error("缺少請求引數");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",400);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Map<String, Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e){
        logger.error("缺少請求引數");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",400);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        logger.error("引數驗證失敗",e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String field = error.getField();
        String code = error.getDefaultMessage();
        String message = String.format("%s: %s",field,code);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",code);
        map.put("message",message);
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Map<String, Object> handleBindException(BindException e){
        logger.error("缺少請求引數",e);
        Map<String, Object> map = new HashMap<String, Object>();
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String field = error.getField();
        String code = error.getDefaultMessage();
        String message = String.format("%s: %s",field,code);
        map.put("code",code);
        map.put("message",message);
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public Map<String, Object> handleConstraintViolationException(ConstraintViolationException e){
        logger.error("缺少請求引數",e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String message = violation.getMessage();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",400);
        map.put("message",message);
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Map<String, Object> handleValidationException(ValidationException e){
        logger.error("引數驗證失敗",e);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",400);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Map<String, Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e){
        logger.error("不支持當前請求方法",e);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",400);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Map<String, Object> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e){
        logger.error("不支持當前媒體型別",e);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",415);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ResponseBody
    @ExceptionHandler(BusinessException.class)
    public Map<String, Object> businessExceptionHandler(BusinessException e){
        logger.error("自定義業務失敗",e);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",e.getCode());
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }

    @ExceptionHandler(value = https://www.cnblogs.com/liwenruo/archive/2022/07/26/Exception.class)
    public Map defaultErrorHandler(Exception e){
        logger.error("自定義業務失敗",e);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code",500);
        map.put("message",e.getMessage());
        //如果發生例外,則進行日志記錄、寫入資料庫或其他處理,此處省略
        return map;
    }
}

 ??(5)撰寫測驗控制器

package com.itheima.controller;

import com.itheima.exception.BusinessException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @RequestMapping("BusinessException")
    public String testBusinessExceptionStatus(@RequestParam("i") int i){
        if (i == 0){
            throw new BusinessException(600,"這是自定義例外");
        }
        return "success";
    }
}

??運行專案,訪問http://localhost:8080/BusinessException?i=0,在網頁中回傳如下 JSON 格式的資料:

??

??(6)實作資料的增加、洗掉、修改和查詢控制器

package com.itheima.controller;
import com.itheima.dao.ReponseData;
import com.itheima.dao.UserRepository;
import com.itheima.domain.Response;
import com.itheima.domain.User;
import com.itheima.exception.ExceptionMsg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;

@RestController
@RequestMapping("/user")
public class UserController {
    protected Response result(ExceptionMsg msg){
        return new Response();
    }
    protected Response result(){
        return new Response();
    }
    @Autowired
    private UserRepository userRepository;

    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "",method = RequestMethod.GET)
    public ReponseData getUserList(){
        List<User> list = new ArrayList<>(userRepository.findAll());
        return new ReponseData(ExceptionMsg.SUCCESS,list);
    }

    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "",method = RequestMethod.POST)
    public ReponseData add(User user) {
        userRepository.save(user);
        List<User> list = new ArrayList<>(userRepository.findAll());
        return new ReponseData(ExceptionMsg.SUCCESS,user);
    }

    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.DELETE)
    public Response delete(@PathVariable("id") Long id) {
        userRepository.deleteById(id);
        return result(ExceptionMsg.SUCCESS);
    }

    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.PUT)
    public ReponseData update(User user) {
        userRepository.save(user);
        return new ReponseData(ExceptionMsg.SUCCESS,user);
    }

    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "https://www.cnblogs.com/{id}",method = RequestMethod.GET)
    public ReponseData findUser(@PathVariable("id") int id) {
        Optional<User> user = userRepository.findById((long) id);
        if (user.get()!=null) {
            return new ReponseData(ExceptionMsg.SUCCESS,user);
        }
        return new ReponseData(ExceptionMsg.FAILED,user);
    }
}

3. 用Swagger實作介面檔案

????在專案開發中,一般都是前后端分離開發的,需要由前后端工程師共同定義介面:撰寫介面檔案,之后大家都根據這個介面檔案進行開發、維護,為了便于撰寫和維護穩定,可以使用Swagger來撰寫API介面檔案,以提升團隊的溝通效率,

??3.1 配置Swagger

??(1)添加Swagger依賴

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

??(2)創建Swagger配置類

package com.itheima.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
 * Swagger配置類
 * 在與Spring Boot集成時,放在與Application.java同級的目錄下
 * 通過注解@Configuration讓Spring來加載該類配置
 * 再通過注解@EnableSwagger2來啟用Swagger2
 */
@Configuration
@EnableSwagger2
public class Swagger2 {
    /**
     * 創建API應用
     * apiInfo增加API相關資訊
     * 通過select()函式回傳一個ApiSelectorBuilder實體,用來控制哪些介面暴露給Swagger來展現
     * 本例采用指定掃描的包路徑來定義指定要建立API的目錄
     */
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage(""))
                .paths(PathSelectors.any())
                .build();
    }
    /**
     * 創建該API的基本資訊(這些基本資訊會展現在檔案頁面中)
     * 訪問地址:http:/專案實際地址/swagger-ui.html
     */
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("RESTful APIs")
                .description("RESTful APIs")
                .termsOfServiceUrl("http://localhost:8080/")
                .contact("long")
                .version("1.0")
                .build();
    }
}

??代碼解釋如下,

  • @Configuration:   讓Spring來加載該類配置,
  • @EnableSwagger2:  啟用 Swagger2.createRestApi 函式創建 Docket 的 Bean
  • apilnfo():用來展示該API的基本資訊,
  • select():回傳一個ApiSelectorBuilder實體,用來控制哪些介面暴露給Swagger來展現,
  • apis(RequestHandlerSelectors.basePackage()):配置包掃描路徑,Swagger 會掃描包下所有Controller定義的API,并產生檔案內容,如果不想產生API,則使用注解 @Apilgnore

??(3)撰寫介面檔案

??在完成上述配置后,即生成了檔案,但是這樣生成的檔案主要針對請求本身,而描述自動根據方法等命名產生,對用戶并不友好,所以,通常需要自己增加一些說明以豐富檔案內容,可以通過以下注解來增加說明,

  • @Api:描述類/介面的主要用途,
  • @ApiOperation:描述方法用途,給API增加說明,
  • @ApilmplicitParam:   擋述方法的引數,給引數増加說距,
  • @ApilmplicitParams:   描述方法的引數(Multi-Params ),給引數增加說明,
  • @Apilgnore:忽略某類/方法/引數的義檔,
@ApiOperation(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "洗掉用戶",notes = "根據URL的id來指定洗掉物件")
@ApiImplicitParam(name = "id",valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "文章ID",required = true,dataType = "Long")
public String del(@PathVariable("id") Long id) {
    userRepository.deleteById(id);
    return "SUCCESS";
}

??完成上述代碼后,啟動專案,訪問http://localhost:8080/swagger-ui.html就能看到所展示的RESTful API的頁面,可以通過單擊具體的API測驗請求,來查看代碼中配置的資訊,以及引數的描述資訊,

??

??3.2 用RestTemplate發起請求

??(1)認識 RestTemplate

??在Java應用程式中訪問RESTful服務,可以使用Apache的HttpClient來實作,不過此方法使用起來太煩瑣,Spring提供了一種簡單便捷的模板類一RestTemplate來進行操作,RestTemplate是Spring提供的用于訪問REST服務的客戶端,它提供了多種便捷訪問遠程HTTP 服務的方法,能夠大大提高客戶端的撰寫效率,

??RestTemplate用于同步Client端的核心類,簡化與HTTP服務的通信,在默認情況下, RestTemplate默認依賴JDK的HTTP連接工具,也可以通過setRequestFactory屬性切換到不同的 HTTP 源,比如 Apache HttpComponents, Netty 和 OkHttp,

??RestTemplate簡化了提交表單資料的難度,并附帯自動轉換為JSON格式資料的功能,該類的入口主要是根據HTTP的6種方法制定的,見下表,

??

??此外,exchange和excute也可以使用上述方法,

??RestTemplate 默認使用 HttpMessageConverter 將 HTTP 訊息轉換成 POJO,或從 POJO 轉換成HTTP訊息,默認情況下會注冊MIME型別的轉換器,但也可以通過setMessageConverters 注冊其他型別的轉換器,

??(2)用 RestTemplate 發送 GET 請求

??1.創建測驗物體

package com.intehel.domain;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private long id;
    private String name;
}

??2.創建用于測驗的API

package com.intehel.controller;

import com.intehel.domain.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "/getParameter", method = RequestMethod.GET)
    public User getParameter(User user){
        return user;
    }
    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "/getuser1", method = RequestMethod.GET)
    public User user1(){
        return new User(1,"buretuzi");
    }
    @RequestMapping(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "/postuser", method = RequestMethod.POST)
    public User postUser(User user){
        System.out.println("name:"+user.getName());
        System.out.println("id:"+user.getId());
        return user;
    }
}

??3.使用getForEntity測驗

??(1)回傳String,不帶引數,見以下代碼:
package com.intehel;

import org.junit.Test;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class test {
    @Test
    public void nparameters(){
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        RestTemplate client = restTemplateBuilder.build();
        ResponseEntity<String> responseEntity = client.getForEntity("http://localhost:8080/getuser1",String.class);
        System.out.println(responseEntity.getBody());
    }
}

??控制臺列印結果:

??11:01:35.973 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/getuser1
??11:01:35.991 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
??11:01:36.073 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:01:36.073 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
??{"id":1,"name":"buretuzi"}

??(2)回傳String,帶引數的例子,

??   在呼叫服務提供者提供的介面時,有時需要傳遞引數,有以下兩種不同的方式,

????①用一個數字做占位符,最后是一個可變長度的引數,用來替換前面的占位符,使用方法見以下代碼:

@Test
public void withparameters(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    ResponseEntity<String> responseEntity = client.getForEntity("http://localhost:8080/getParameter?name={1}&id={2}",String.class,"buretuzi",2);
    System.out.println(responseEntity.getBody());
}

??列印結果:

??11:06:20.893 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/getParameter?name=buretuzi&id=2
??11:06:20.893 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
??11:06:20.908 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:06:20.908 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
??{"id":2,"name":"buretuzi"}

???② 使用name={name}這種形式,最后一個引數是一個map, map的key即為前邊占位符的名字,map的value為引數值,使用方法見以下代碼:

@Test
public void withparameters2(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    Map<String,String> map = new HashMap<String,String>();
    map.put("name", "buretuzi");
    ResponseEntity<String> responseEntity = client.getForEntity("http://localhost:8080/getParameter?name={name}&id=3",String.class,map);
    System.out.println(responseEntity.getBody());
}

??列印結果:

??11:19:28.842 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/getParameter?name=buretuzi&id=3
??11:19:28.848 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
??11:19:28.880 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:19:28.880 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
??{"id":3,"name":"buretuzi"}??

??(3)回傳物件,見以下代碼:
@Test
public void restUser(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    ResponseEntity<User> responseEntity = client.getForEntity("http://localhost:8080/getuser1",User.class);
    System.out.println(responseEntity.getBody().getId());
    System.out.println(responseEntity.getBody().getName());
}

??列印結果:

????1
????buretuzi

??4.使用 getForObject

??getForObject函式是對getForEntity函式的進一步封裝,如果你只關注回傳的訊息體的內容, 對其他資訊都不關注,則可以使用getForObject,見以下代碼:

@Test
public void getForObject(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    User user = client.getForObject("http://localhost:8080/getuser1",User.class);
    System.out.println(user.getName());
}

??(3)用 RestTemplate 發送 POST 請求

??在 RestTemplate 中,POST 請求可以通過 postForEntity、postForObject、postForLocation、exchange四種方法來發起,

??1.方法一:使用 postForEntity

  • postForEntity(String url,Object request,Class responseType,Object... urlVariables)
  • postForEntity(String url,Object request,Class responseType,Map urlVariables)
  • postForEntity(String url,Object request,Class responseType)

??2.方法二:使用 postForObject

  • postForObject(String url,Object request,Class responseType,Object... urlVariables)
  • postForObject(String url,Object request,Class responseType,Map urlVariables)
  • postForObject(String url,Object request,Class responseType)

??3.方法三:使用 postForLocation

??postForLocation也用于提交資源,在提交成功之后,會回傳新資源的URI,它的引數和前面兩種方法的引數基本一致,只不過該方法的回傳值為URI,表示新資源的位置

  • postForLocation(String url,Object request,Object... urlVariables)
  • postForLocation(String url,Object request,Map urlVariables)
  • postForLocation(String url,Object request)

??4.方法四:使用exchange

??使用exchange方法可以指定呼叫方式,使用方法如下:

??ResponseEntity<String> response=tempIate.exchang(newUrl, HttpMethod.DELETE, request, String.class);

??5.實作發送POST請求

??(1)使用 postForEntity
@Test
public void postForEntity(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    MultiValueMap<String,Object> paramMap = new LinkedMultiValueMap<String,Object>();
    paramMap.add("name","buretuzi");
    paramMap.add("id",4);
    ResponseEntity<User> responseEntity = client.postForEntity("http://localhost:8080/postuser",paramMap,User.class);
    System.out.println(responseEntity.getBody().getName());
}

??代碼解釋如下,

  • MultiValueMap:封裝引數,千萬不要替換為Map與HashMap,否則引數無法被傳遞
  • postForEntity("url", paramMap, User.class):引數分別表示要呼叫的服務的地址、上傳的引數、回傳的訊息體的資料型別,

??運行測驗單元,控制臺輸出如下結果:

??11:39:07.001 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP POST http://localhost:8080/postuser
??11:39:07.032 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
??11:39:07.032 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{name=[buretuzi], id=[4]}] with ??????????org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
??11:39:07.482 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:39:07.482 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [com.intehel.domain.User]
??buretuzi??

??(2)使用 postForObject

??postForObject和getForObject相對應,只關注回傳的訊息體,見以下代碼:

@Test
public void postForObject(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    MultiValueMap<String,Object> paramMap = new LinkedMultiValueMap<String,Object>();
    paramMap.add("name","buretuzi");
    paramMap.add("id",4);
    String response = client.postForObject("http://localhost:8080/postuser",paramMap,String.class);
    System.out.println(response);
}

??運行測驗單元,控制臺輸岀如下結果:

??11:44:46.470 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP POST http://localhost:8080/postuser
??11:44:46.470 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
??11:44:46.486 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{name=[buretuzi], id=[4]}] with org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
??11:44:46.918 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:44:46.918 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
??{"id":4,"name":"buretuzi"}?

??(3)使用postForexchange,見以下代碼:
@Test
public void postForExchange(){
    MultiValueMap<String,Object> paramMap = new LinkedMultiValueMap<String,Object>();
    paramMap.add("name","buretuzi");
    paramMap.add("id",4);
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    HttpHeaders headers = new HttpHeaders();
    HttpEntity<MultiValueMap<String,Object>> httpEntity = new HttpEntity<MultiValueMap<String,Object>>(paramMap,headers);
    ResponseEntity<String> response = client.exchange("http://localhost:8080/postuser", HttpMethod.POST, httpEntity,String.class,paramMap);
    System.out.println(response.getBody());
}

??運行測驗單元,控制臺輸岀如下結果:

??11:59:12.988 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP POST http://localhost:8080/postuser
??11:59:13.004 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
??11:59:13.004 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{name=[buretuzi], id=[4]}] with org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
??11:59:13.436 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
??11:59:13.436 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
??{"id":4,"name":"buretuzi"}

??(4)使用 postForLocation

??它用于提交資料,并獲取回傳的URI,一般登錄、注冊都是POST請求,操作完成之后,跳轉到某個頁面,這種場景就可以使用postForLocation所以,先要添加處理登錄的API,見以下代碼:

package com.intehel.controller;

import com.intehel.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;

@Controller
public class TestController {
    @RequestMapping(path = "success",method = RequestMethod.POST)
    @ResponseBody
    public String loginSuccess(String name){
        return "welcome"+name;
    }
    @RequestMapping(path = "post",method = RequestMethod.POST)
    public String post(HttpServletRequest request,
                       @RequestParam(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "name",required = false)String name,
                       @RequestParam(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "password",required = false)String password,
                       @RequestParam(valuehttps://www.cnblogs.com/liwenruo/archive/2022/07/26/= "id",required = false)Integer id){
        return "redirect:/success?name="+name+"&id="+id;
    }
}

??然后使用postForLocation請求,用法見以下代碼:

@Test
public void postForLocation(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    MultiValueMap<String,Object> paramMap = new LinkedMultiValueMap<String,Object>();
    paramMap.add("name","buretuzi");
    paramMap.add("id",4);
    RestTemplate client = restTemplateBuilder.build();
    URI response = client.postForLocation("http://localhost:8080/post",paramMap);
    System.out.println(response);
}

??運行測驗單元,控制臺輸出如下結果:

??13:59:06.415 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP POST http://localhost:8080/post
??13:59:06.415 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{name=[buretuzi], id=[4]}] with ??org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
??13:59:06.951 [main] DEBUG org.springframework.web.client.RestTemplate - Response 302 FOUND
??http://localhost:8080/success?name=buretuzi&id=4

??(4)用 RestTemplate 發送 PUT和DELETE 請求

??1.PUT請求

??在RestTemplate中,發送“修改”請求和前面介紹的postForEntity方法的引數基本一致, 只是修改請求沒有回傳值,用法如下:

@Test
public void put(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    User user = new User();
    user.setName("buretuzi");
    client.put("http://localhost:8080/{1}",user,4);
}

??2.DELETE 請求

??洗掉請求,可以通過呼叫DELETE方法來實作,用法見以下代碼:

@Test
public void delete(){
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate client = restTemplateBuilder.build();
    client.delete("http://localhost:8080/{1}",4);
}

??最后的“4”用來替換前面的占位符{1},

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/500363.html

標籤:其他

上一篇:【超詳細】手把手教你ElasticSearch集群搭建

下一篇:多商戶商城系統功能拆解18講-平臺端商家售后

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more