Swagger詳細(xì)了解一下

簡(jiǎn)介

Swagger 是最流行的 API 開發(fā)工具,它遵循 OpenAPI Specification(OpenAPI 規(guī)范,也簡(jiǎn)稱 OAS)。
Swagger 可以貫穿于整個(gè) API 生態(tài),如 API 的設(shè)計(jì)、編寫 API 文檔、測(cè)試和部署。
Swagger 是一種通用的,和編程語(yǔ)言無(wú)關(guān)的 API 描述規(guī)范。

應(yīng)用場(chǎng)景

  • 如果你的 RESTful API 接口都開發(fā)完成了,你可以用 Swagger-editor 來(lái)編寫 API 文檔( yaml 文件 或 json 文件),然后通過(guò) Swagger-ui 來(lái)渲染該文件,以非常美觀的形式將你的 API 文檔,展現(xiàn)給你的團(tuán)隊(duì)或者客戶。
  • 如果你的 RESTful API 還未開始,也可以使用 Swagger ,來(lái)設(shè)計(jì)和規(guī)范你的 API,以 Annotation (注解)的方式給你的源代碼添加額外的數(shù)據(jù)。這樣,Swagger 就可以檢測(cè)到這些數(shù)據(jù),自動(dòng)生成對(duì)應(yīng)的 API 文檔。

規(guī)范

Swagger Specification(Swagger 規(guī)范),規(guī)定了如何對(duì) API 的信息進(jìn)行正確描述。
Swagger 規(guī)范,以前稱作 Swagger Specification,現(xiàn)在稱作 OpenAPI Specification(簡(jiǎn)稱 OAS)。
Swagger 規(guī)范本身是與編程語(yǔ)言無(wú)關(guān)的,它支持兩種語(yǔ)法風(fēng)格:

  • YAML 語(yǔ)法
  • JSON 語(yǔ)法

這兩種語(yǔ)法風(fēng)格可以相互轉(zhuǎn)換,都可以用來(lái)對(duì)我們的 RESTful API 接口的信息進(jìn)行準(zhǔn)確描述,便于人類和機(jī)器閱讀。
在 Swagger 中,用于描述 API 信息的文檔被稱作 Swagger 文檔。Swagger 的規(guī)范主要有兩種:

  • Swagger 2.0
  • OpenAPI 3.0

關(guān)于 Swagger 規(guī)范的詳細(xì)信息,請(qǐng)參考官方文檔

Swagger文檔

Swagger 文檔(文件),指的是符合 Swagger 規(guī)范的文件,用于對(duì) API 的信息進(jìn)行完整地描述。
Swagger 文檔是整個(gè) Swagger 生態(tài)的核心。
Swagger 文檔的類型有兩種:yaml 文件和 json 文件。
yaml 文件用的是 YAML 語(yǔ)法風(fēng)格;json 文件用的是 JSON 語(yǔ)法風(fēng)格。這兩種文件都可以用來(lái)描述 API 的信息,且可以相互轉(zhuǎn)換。
簡(jiǎn)單的說(shuō),Swagger 文檔就是 API 文檔,只不過(guò) Swagger 文檔是用特定的語(yǔ)法來(lái)編寫的。Swagger 文檔本身看起來(lái)并不美觀,這時(shí),就需要一個(gè)好的 UI 工具將其渲染一番,這個(gè)工具就是 Swagger-ui。
我們可以用任何編輯器來(lái)編寫 Swagger 文檔,但為了方便在編輯的同時(shí),檢測(cè) Swagger 文檔是否符合規(guī)范,就有了 Swagger-editor 編輯器。


在這里插入圖片描述

Swagger工具

Swagger提供了多種工具,幫助解決api的不同的情況下的問(wèn)題


在這里插入圖片描述

Swagger-editor

【功能】

  • 編寫 Swagger 文檔
  • 實(shí)時(shí)檢測(cè) Swagger 文檔是否符合 Swagger 規(guī)范
  • 調(diào)試 Swagger 文檔里描述的 API 接口
  • 轉(zhuǎn)換 Swagger 文檔(yaml 轉(zhuǎn) json,或 json 轉(zhuǎn) yaml)

【安裝】

  • Web 版本的 Swagger-editor 直接運(yùn)行在公網(wǎng)上,Swagger 已經(jīng)給我們配置好了在線的 Swagger-editor。
  • 也可以選擇本地運(yùn)行 Swagger-editor,需要 Node.js 環(huán)境支持。

本文使用docker部署,下載swagger-editor的容器

docker pull swaggerapi/swagger-editor
docker run -d -p 81:8080 swaggerapi/swagger-editor 
//啟動(dòng),81:8080 將容器的8080端口暴露給localhost的81端口

在瀏覽中輸入:localhost:81,就可以在容器中編輯api文檔

在這里插入圖片描述

【使用說(shuō)明】
Swagger-editor 分為菜單欄和主體界面兩個(gè)部分。
主體界面分為左右兩欄,左側(cè)是編輯區(qū),右側(cè)是顯示區(qū)。

  • 編輯區(qū)里默認(rèn)有一個(gè) Swagger 文檔的樣例,你可以將其清空,編寫自己的 API 描述。
  • 顯示區(qū)是對(duì)應(yīng)編輯區(qū)中的Swagger 文檔的 UI 渲染情況,也就是說(shuō),右側(cè)顯示區(qū)的結(jié)果和使用 Swagger-ui 渲染 Swagger 文檔后的顯示結(jié)果基本一致。

Swagger-editor 的菜單欄包含以下幾個(gè)菜單:

  • File: 用于導(dǎo)入、導(dǎo)出、轉(zhuǎn)換、清空 Swagger 文檔
  • Edit: 用于轉(zhuǎn)換為標(biāo)準(zhǔn)的 YAML 格式文件,比如刪除空白行等
  • Generate Server: 用于構(gòu)建服務(wù)器端 stub
  • Generate Client: 用于構(gòu)建客戶端 SDK

選擇菜單欄【File】Save as YAML,保存為swagger.yaml文件,就是我們所說(shuō)的swagger文檔。

文檔編輯參考swagger從入門到精通

Swagger-ui

Swagger-ui 是一套 HTML/CSS/JS 框架,用于渲染 Swagger 文檔,以便提供美觀的 API 文檔界面。也就是說(shuō),Swagger-ui 是一個(gè) UI 渲染工具。
【安裝】
docker部署,下載swagger-ui的容器

docker pull swaggerapi/swagger-ui

【使用】

  1. 使用上面部署的Swagger-editor,在編輯框中完成文檔編輯后在頁(yè)面上上方點(diǎn)擊 File -> Download JSON,將文件下載到本地(/Users/jiangsuyao/Downloads)命名為swagger.json
  2. json文件掛在到容器中
//-e:執(zhí)行容器中/foo/swagger.json
//-v:將/Users/fanfan/Downloads中的swagger.json掛在到 /foo中執(zhí)行
docker run -p 82:8080 -e SWAGGER_JSON=/foo/swagger.json -v /Users/jiangsuyao/Downloads:/foo swaggerapi/swagger-ui

瀏覽器輸入:localhost:82,即可看到與Swagger-editor的顯示區(qū)同樣的內(nèi)容

在這里插入圖片描述

【基于swagger-ui的接口測(cè)試】
1. 選擇接口點(diǎn)擊【try it out】
在這里插入圖片描述

2. 修改“Example Value Model”里面參數(shù),點(diǎn)擊“Execute”發(fā)送請(qǐng)求
在這里插入圖片描述

3. 點(diǎn)擊發(fā)送后會(huì)出現(xiàn)下面視圖,不管發(fā)送成功/失敗。你可以通過(guò)下面視圖來(lái)查看請(qǐng)求數(shù)據(jù):
在這里插入圖片描述

【springboot集成swagger-ui自動(dòng)生成API文檔】

  1. 添加依賴
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.2.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.2.2</version>
</dependency>
  1. 編寫配置文件
    在application同級(jí)目錄新建swagger2文件,添加swagger2配置類
package com.abel.example;
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;

@Configuration
@EnableSwagger2
public class Swagger2 {
    /**
     * 創(chuàng)建API應(yīng)用
     * apiInfo() 增加API相關(guān)信息
     * 通過(guò)select()函數(shù)返回一個(gè)ApiSelectorBuilder實(shí)例,用來(lái)控制哪些接口暴露給Swagger來(lái)展現(xiàn),
     * 本例采用指定掃描的包路徑來(lái)定義指定要建立API的目錄。
     *
     * @return
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.abel.example.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 創(chuàng)建該API的基本信息(這些基本信息會(huì)展現(xiàn)在文檔頁(yè)面中)
     * 訪問(wèn)地址:http://項(xiàng)目實(shí)際地址/swagger-ui.html
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot中使用Swagger2構(gòu)建RESTful APIs")
                .description("更多請(qǐng)關(guān)注https://blog.csdn.net/u012373815")
                .termsOfServiceUrl("https://blog.csdn.net/u012373815")
                .contact("abel")
                .version("1.0")
                .build();
    }
}
  1. 在controller上添加注解,自動(dòng)生成API
package com.abel.example.controller;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

import com.abel.example.bean.User;
import io.swagger.annotations.*;
import org.apache.log4j.Logger;
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.*;


import com.abel.example.service.UserService;
import com.abel.example.util.CommonUtil;


@Controller
@RequestMapping(value = "/users")
@Api(value = "用戶的增刪改查")
public class UserController {

    @Autowired
    private UserService userService;


    /**
     * 查詢所有的用戶
     * api :localhost:8099/users
     * @return
     */
    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "獲取用戶列表,目前沒(méi)有分頁(yè)")
    public ResponseEntity<Object> findAll() {
        return new ResponseEntity<>(userService.listUsers(), HttpStatus.OK);
    }

    /**
     * 通過(guò)id 查找用戶
     * api :localhost:8099/users/1
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "通過(guò)id獲取用戶信息", notes="返回用戶信息")
    public ResponseEntity<Object> getUserById(@PathVariable Integer id) {
        return new ResponseEntity<>(userService.getUserById(Long.valueOf(id)), HttpStatus.OK);
    }


    /**
     * 通過(guò)spring data jpa 調(diào)用方法
     * api :localhost:8099/users/byname?username=xxx
     * 通過(guò)用戶名查找用戶
     * @param request
     * @return
     */
    @RequestMapping(value = "/byname", method = RequestMethod.GET)
    @ResponseBody
    @ApiImplicitParam(paramType = "query",name= "username" ,value = "用戶名",dataType = "string")
    @ApiOperation(value = "通過(guò)用戶名獲取用戶信息", notes="返回用戶信息")
    public ResponseEntity<Object> getUserByUserName(HttpServletRequest request) {
        Map<String, Object> map = CommonUtil.getParameterMap(request);
        String username = (String) map.get("username");
        return new ResponseEntity<>(userService.getUserByUserName(username), HttpStatus.OK);
    }

    /**
     * 通過(guò)spring data jpa 調(diào)用方法
     * api :localhost:8099/users/byUserNameContain?username=xxx
     * 通過(guò)用戶名模糊查詢
     * @param request
     * @return
     */
    @RequestMapping(value = "/byUserNameContain", method = RequestMethod.GET)
    @ResponseBody
    @ApiImplicitParam(paramType = "query",name= "username" ,value = "用戶名",dataType = "string")
    @ApiOperation(value = "通過(guò)用戶名模糊搜索用戶信息", notes="返回用戶信息")
    public ResponseEntity<Object> getUsers(HttpServletRequest request) {
        Map<String, Object> map = CommonUtil.getParameterMap(request);
        String username = (String) map.get("username");
        return new ResponseEntity<>(userService.getByUsernameContaining(username), HttpStatus.OK);
    }


    /**
     * 添加用戶啊
     * api :localhost:8099/users
     *
     * @param user
     * @return
     */
    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    @ApiModelProperty(value="user",notes = "用戶信息的json串")
    @ApiOperation(value = "新增用戶", notes="返回新增的用戶信息")
    public ResponseEntity<Object> saveUser(@RequestBody User user) {
        return new ResponseEntity<>(userService.saveUser(user), HttpStatus.OK);
    }

    /**
     * 修改用戶信息
     * api :localhost:8099/users
     * @param user
     * @return
     */
    @RequestMapping(method = RequestMethod.PUT)
    @ResponseBody
    @ApiModelProperty(value="user",notes = "修改后用戶信息的json串")
    @ApiOperation(value = "新增用戶", notes="返回新增的用戶信息")
    public ResponseEntity<Object> updateUser(@RequestBody User user) {
        return new ResponseEntity<>(userService.updateUser(user), HttpStatus.OK);
    }

    /**
     * 通過(guò)ID刪除用戶
     * api :localhost:8099/users/2
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    @ApiOperation(value = "通過(guò)id刪除用戶信息", notes="返回刪除狀態(tài)1 成功 0 失敗")
    public ResponseEntity<Object> deleteUser(@PathVariable Integer id) {
        return new ResponseEntity<>(userService.removeUser(id.longValue()), HttpStatus.OK);
    }
}

注解說(shuō)明

  • @Api:用在類上,說(shuō)明該類的作用。
  • @ApiOperation:注解來(lái)給API增加方法說(shuō)明。
  • @ApiImplicitParams : 用在方法上包含一組參數(shù)說(shuō)明。
  • @ApiImplicitParam:用來(lái)注解來(lái)給方法入?yún)⒃黾诱f(shuō)明。
  • @ApiResponses:用于表示一組響應(yīng)
  • @ApiResponse:用在@ApiResponses中,一般用于表達(dá)一個(gè)錯(cuò)誤的響應(yīng)信息
  • @ApiModel:描述一個(gè)Model的信息(一般用在請(qǐng)求參數(shù)無(wú)法使用@ApiImplicitParam注解進(jìn)行描述的時(shí)候)
  • @ApiModelProperty:描述一個(gè)model的屬性

其中
@ApiResponse參數(shù):

  • code:數(shù)字,如400
  • message:信息,如“參數(shù)填寫錯(cuò)誤”
  • response:拋出異常的類
    @ApiImplicitParam參數(shù):
  • paramTpye:指定參數(shù)放在哪些地方(header/query/path/body/form)
  • name:參數(shù)名
  • dataTpye:參數(shù)類型
  • required:是否必輸(true/false)
  • value:說(shuō)明參數(shù)的意思
  • defaultValue:參數(shù)默認(rèn)值
  1. 下載Swagger UI組件
    去官網(wǎng)下載Zip包,或者在github上下載也可以,需要將dist文件夾下的所有文件的復(fù)制到webapp目錄下

原理就是在系統(tǒng)加載的時(shí)候,Swagger配置類去掃描所有添加注釋的接口,并且儲(chǔ)存起來(lái)通過(guò)下面地址進(jìn)行訪問(wèn),返回JSON數(shù)據(jù),在前端界面顯示出來(lái)。
啟動(dòng)項(xiàng)目后,訪問(wèn)http://localhost:8099/swagger-ui.html,顯示如下:

在這里插入圖片描述

Swagger-Codegen

Swagger Codegen是一個(gè)開源的代碼生成器,根據(jù)Swagger定義的RESTful API可以自動(dòng)建立服務(wù)端和客戶端的連接。Swagger Codegen的源碼可以在Github上找到。
GitHub:https://github.com/swagger-api/swagger-codegen
【安裝】
首先機(jī)器上需要有jdk,然后只要下載一個(gè)cli的文件就可以了

//下載
# wget https://oss.sonatype.org/content/repositories/releases/io/swagger/swagger-codegen-cli/2.2.1/swagger-codegen-cli-2.2.1.jar
//下載之后運(yùn)行,返回結(jié)果可查看其支持的語(yǔ)言
# java -jar swagger-codegen-cli-2.2.1.jar
Available languages: [android, aspnet5, async-scala, cwiki, csharp, cpprest, dart, flash, python-flask, go, groovy, java, jaxrs, jaxrs-cxf, jaxrs-resteasy, jaxrs-spec, inflector, javascript, javascript-closure-angular, jmeter, nancyfx, nodejs-server, objc, perl, php, python, qt5cpp, ruby, scala, scalatra, silex-PHP, sinatra, rails5, slim, spring, dynamic-html, html, html2, swagger, swagger-yaml, swift, tizen, typescript-angular2, typescript-angular, typescript-node, typescript-fetch, akka-scala, CsharpDotNet2, clojure, haskell, lumen, go-server]
//查看支持某個(gè)語(yǔ)言的具體使用幫助,比如java
# java -jar swagger-codegen-cli-2.2.1.jar config-help -l java

【使用】
利用swagger-codegen根據(jù)服務(wù)生成客戶端代碼

//http://petstore.swagger.io/v2/swagger.json是官方的一個(gè)例子,我們可以改成自己的服務(wù)
# java -jar swagger-codegen-cli-2.2.1.jar generate -i http://petstore.swagger.io/v2/swagger.json -l java -o samples/client/pestore/java

在上面這段代碼里,使用了三個(gè)參數(shù),分別是-i和-l和-o。

  • -i,指定swagger描述文件的路徑,url地址或路徑文件;該參數(shù)為必須

  • -l,指定生成客戶端代碼的語(yǔ)言,該參數(shù)為必須

  • -o,指定生成文件的位置(默認(rèn)當(dāng)前目錄)

除了可以指定上面三個(gè)參數(shù),還有一些常用的:

  • -c ,json格式的配置文件的路徑;文件為json格式,支持的配置項(xiàng)因語(yǔ)言的不同而不同

  • -a, 當(dāng)獲取遠(yuǎn)程swagger定義時(shí),添加授權(quán)頭信息;URL-encoded格式化的name,逗號(hào)隔開的多個(gè)值

  • --api-package, 指定生成的api類的包名

  • --artifact-id ,指定pom.xml的artifactId的值

  • --artifact-version ,指定pom.xml的artifact的版本

  • --group-id, 指定pom.xml的groupId的值

  • --model-package, 指定生成的model類的包名

  • -s ,指定該參數(shù)表示不覆蓋已經(jīng)存在的文件

  • -t ,指定模版文件所在目錄
    生成好的客戶端代碼:


    在這里插入圖片描述

參考文檔:
https://blog.csdn.net/u012373815/article/details/82685962
https://www.cnblogs.com/shamo89/p/7680771.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容