精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

用Go語言編寫一門工具的終極指南

開發 后端
我以前構建過一個工具,以讓生活更輕松。這個工具被稱為: gomodifytags ,它會根據字段名稱自動填充結構體的標簽字段。

我以前構建過一個工具,以讓生活更輕松。這個工具被稱為: gomodifytags ,它會根據字段名稱自動填充結構體的標簽字段。示例如下:

用Go語言編寫一門工具的***指南
(在 vim-go 中使用 gomodifytags 的一個用法示例)

使用這樣的工具可以 輕松管理 結構體的多個字段。該工具還可以添加和刪除標簽,管理標簽選項(如omitempty),定義轉換規則(snake_case、camelCase 等)等等。但是這個工具是如何工作的? 在后臺中它究竟使用了哪些 Go 包? 有很多這樣的問題需要回答。

這是一篇非常長的博客文章,解釋了如何編寫類似這樣的工具以及如何構建它的每一個細節。 它包含許多特有的細節、提示和技巧和某些未知的 Go 位。

拿一杯咖啡,開始深入探究吧!

首先,列出這個工具需要完成的功能:

  • 它需要讀取源文件,理解并能夠解析 Go 文件
  • 它需要找到相關的結構體
  • 找到結構體后,需要獲取其字段名稱
  • 它需要根據字段名更新結構標簽(根據轉換規則,即:snake_case)
  • 它需要能夠使用這些改動來更新文件,或者能夠以可接受的方式輸出改動

我們首先來看看 結構體標簽的定義 是什么,之后我們會學習所有的部分,以及它們如何組合在一起,從而構建這個工具。

用Go語言編寫一門工具的***指南

結構體的標簽 值 (其內容,比如`json:"foo"`)并 不是官方標準的一部分 ,不過,存在一個非官方的規范,使用 reflect 包定義了其格式,這種方法也被 stdlib(例如 encoding/ json)包所采用。它是通過 reflect.StructTag 類型定義的:

用Go語言編寫一門工具的***指南

結構標簽的定義比較簡潔所以不容易理解。該定義可以分解如下:

  • 結構標簽是一個字符串(字符串類型)
  • 結構標簽的 Key 是非引號字符串
  • 結構標簽的 value 是一個帶引號的字符串
  • 結構標簽的 key 和 value 用冒號(:)分隔。冒號隔開的一個 key 和對應的 value 稱為 “key value 對”。
  • 一個結構標簽可以包含多個 key valued 對(可選)。key-value 對之間用空格隔開。
  • 可選設置不屬于定義的一部分。類似 encoding/json 包將 value 解析為逗號分開的列表。value 的***個逗號后面的任何部分都是可選設置的一部分,例如:“ foo, omitempty,string”。其中 value 擁有一個叫 “foo” 的名字和可選設置 [“omitempty”, "string"]
  • 由于結構標簽是一個字符串,需要雙引號或者反引號包含。又因為 value 也需要引號包含,經常用反引號包含結構標簽。

以上規則概況如下:

用Go語言編寫一門工具的***指南
(結構標簽的定義有許多隱含細節)

已經了解什么是結構標簽,接下來可以根據需要修改結構標簽。問題來了,如何才能很容易的對所做的修改進行解析?很幸運,reflect.StructTag 包含一個可以解析結構標簽并返回特定 key 的 value 的方法。示例如下:

 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "reflect" 
  6.  
  7. func main() { 
  8.     tag := reflect.StructTag(`species:"gopher" color:"blue"`) 
  9.     fmt.Println(tag.Get("color"), tag.Get("species")) 

輸出:

  1. blue gopher 

如果 key 不存在則返回空串。

這是非常有幫助的, 但是 ,它有一些附加說明,使其不適合我們,因為我們需要更多的靈活性。這些是:

  • 它無法檢測到標簽是否存在 格式錯誤 (即:鍵被引用了,值是未引用等)
  • 它不知道選項的 語義
  • 它沒有辦法 迭代現有的標簽 或返回它們。 我們必須知道我們要修改哪些標簽。 如果不知道其名字怎么辦?
  • 修改現有標簽是不可能的。
  • 我們不能重新 構建新的struct標簽 。

為了改進這一點,我編寫了一個自定義的Go包,它修復了上面的所有問題,并提供了一個可以輕松修改struct標簽的每個方面的API。

用Go語言編寫一門工具的***指南

這個包被稱為 structtag ,并且可以從 github.com/fatih/structtag 獲取到。這個包允許我們以一種整潔的方式 解析和修改標簽 。以下是一個完整的可工作的示例,復制/粘貼并自行嘗試下:

 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.  
  6.     "github.com/fatih/structtag" 
  7.  
  8. func main() { 
  9.     tag := `json:"foo,omitempty,string" xml:"foo"
  10.  
  11.     // parse the tag 
  12.     tags, err := structtag.Parse(string(tag)) 
  13.     if err != nil { 
  14.         panic(err) 
  15.     } 
  16.  
  17.     // iterate over all tags 
  18.     for _, t := range tags.Tags() { 
  19.         fmt.Printf("tag: %+v\n", t) 
  20.     } 
  21.  
  22.     // get a single tag 
  23.     jsonTag, err := tags.Get("json"
  24.     if err != nil { 
  25.         panic(err) 
  26.     } 
  27.  
  28.     // change existing tag 
  29.     jsonTag.Name = "foo_bar" 
  30.     jsonTag.Options = nil 
  31.     tags.Set(jsonTag) 
  32.  
  33.     // add new tag 
  34.     tags.Set(&structtag.Tag{ 
  35.         Key:     "hcl"
  36.         Name:    "foo"
  37.         Options: []string{"squash"}, 
  38.     }) 
  39.  
  40.     // print the tags 
  41.     fmt.Println(tags) // Output: json:"foo_bar" xml:"foo" hcl:"foo,squash" 

既然我們已經知道如何解析一個struct標簽了,以及修改它或創建一個新的,現在是時候來修改一個有效的Go源文件了。在上面的示例中,標簽已經存在了,但是如何從現有的Go結構中獲取標簽呢?

簡要回答:通過 AST 。AST( Abstract Syntax Tree ,抽象語法樹)允許我們從源代碼中檢索每個單獨的標識符(node)。下圖中你可以看到一個結構類型的AST(簡化版):

用Go語言編寫一門工具的***指南
(結構體的基本的Go ast.Node 表示)

在這棵樹中,我們可以檢索和操縱每個標識符,每個字符串和每個括號等。這些都由 AST 節點表示。例如,我們可以通過替換表示它的節點中的名字將字段名稱從“Foo”更改為“Bar”。相同的邏輯也適用于struct標簽。

要 得到Go AST ,我們需要解析源文件并將其轉換為AST。實際上,這兩者都是通過一個步驟處理的。

要做到這一點,我們將使用 go/parser 包來 解析 文件以獲取(整個文件的)AST,然后使用 go/ast 包來遍歷整棵樹(我們也可以手動執行, 但這是另一篇博文的主題)。下面代碼你可以看到一個完整的例子:

 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "go/ast" 
  6.     "go/parser" 
  7.     "go/token" 
  8.  
  9. func main() { 
  10.     src := `package main 
  11.         type Example struct { 
  12.     Foo string` + " `json:\"foo\"` }" 
  13.  
  14.     fset := token.NewFileSet() 
  15.     file, err := parser.ParseFile(fset, "demo", src, parser.ParseComments) 
  16.     if err != nil { 
  17.         panic(err) 
  18.     } 
  19.  
  20.     ast.Inspect(file, func(x ast.Node) bool { 
  21.         s, ok := x.(*ast.StructType) 
  22.         if !ok { 
  23.             return true 
  24.         } 
  25.  
  26.         for _, field := range s.Fields.List { 
  27.             fmt.Printf("Field: %s\n", field.Names[0].Name
  28.             fmt.Printf("Tag:   %s\n", field.Tag.Value) 
  29.         } 
  30.         return false 
  31.     }) 

上面代碼輸出如下:

 

  1. Field: Foo  
  2. Tag: `json:"foo"

上面代碼執行以下操作:

  • 我們定義了僅包含一個結構體的有效Go包的實例。
  • 我們使用 go/parser 包來解析這個字符串。解析器包也可以從磁盤讀取文件(或整個包)。
  • 在我們解析之后,我們保存我們的節點(分配給變量文件)并查找由 *ast.StructType 定義的AST節點(參見AST映像作為參考)。遍歷樹是通過ast.Inspect()函數完成的。它會遍歷所有節點,直到它收到false值。這是非常方便的,因為它不需要知道每個節點。
  • 我們打印結構體的字段名稱和結構標簽。

我們現在可以完成 兩件重要的事情了 ,首先,我們知道如何 解析一個 Go 源文件 并檢索其中結構體的標簽(通過go/parser)。其次,我們知道 如何解析 Go 結構體標簽 ,并根據需要進行修改(通過 github.com/fatih/structtag )。

既然我們有了這些,我們可以通過使用這兩個重要的代碼片段開始構建我們的工具(名為 gomodifytags )。該工具應順序執行以下操作:

  • 獲取配置,以識別我們要修改哪個結構體
  • 根據配置查找和修改結構體
  • 輸出結果

由于 gomodifytags 將主要由編輯器來執行,我們打算通過 CLI 標志傳遞配置信息。第二步包含多個步驟,如解析文件、找到正確的結構體,然后修改結構(通過修改 AST 完成)。***,我們將輸出結果,或是按照原始的 Go 源文件或是某種自定義協議(如 JSON,稍后再說)。

以下是 gomodifytags 簡化之后的主要功能:

用Go語言編寫一門工具的***指南

讓我們開始詳細解釋每個步驟。為了保持簡單,我將嘗試以萃取形式解釋重要的部分。盡管一切都是一樣的,一旦你讀完了這篇博文,你將能夠在無需任何指導的情況下通讀整個源代碼(你將會在本指南的***找到所有資源)

讓我們從***步開始,了解如何 獲取配置 。以下是我們的配置文件,其中包含所有的必要信息

 

  1. type config struct { 
  2.     // first section - input & output 
  3.     file     string 
  4.     modified io.Reader 
  5.     output   string 
  6.     write    bool 
  7.  
  8.     // second section - struct selection 
  9.     offset     int 
  10.     structName string 
  11.     line       string 
  12.     start, end int 
  13.  
  14.     // third section - struct modification 
  15.     remove    []string 
  16.     add       []string 
  17.     override  bool 
  18.     transform string 
  19.     sort      bool 
  20.     clear     bool 
  21.     addOpts    []string 
  22.     removeOpts []string 
  23.     clearOpt   bool 

它分為 三個 主要部分:

***部分包含有關如何和哪個文件要讀入的配置。這可以是本地文件系統的文件名,也可以是直接來自stdin的數據(主要用在編輯器中)。它還設置了如何輸出結果(Go源文件或JSON形式),以及我們是否應該覆寫文件,而不是輸出到stdout中。

第二部分定義了如何選擇一個結構體及其字段。有多種方法可以做到這一點。我們可以通過它的偏移(光標位置)、結構名稱,單行(僅指定字段)或一系列行來定義它。***,我們總是需要得到起始行號。例如在下面的例子中,你可以看到一個例子,我們用它的名字來選擇結構體,然后提取起始行號,以便我們可以選擇正確的字段:

用Go語言編寫一門工具的***指南

而編輯器***使用 字節偏移量 。例如下面你可以看到我們的光標剛好在“Port”字段名稱之后,從那里我們可以很容易地得到起始行號:

用Go語言編寫一門工具的***指南

 

config配置中的 第三 部分實際上是一個到我們的 structtagpackage的 一對一的映射。它基本上允許我們在讀取字段后將配置傳遞給structtag包。如你所知,structtag包允許我們解析一個struct標簽并在各個部分進行修改。但是,它不會覆寫或更新結構體的域值。

我們該如何獲得配置呢? 我們只需使用flag包,然后為配置中的每個字段創建一個標志,然后給他們賦值。舉個例子:

 

  1. flagFile := flag.String("file""""Filename to be parsed"
  2. cfg := &config{ 
  3.     file: *flagFile, 

我們對 配置中的每個字段 執行相同操作。相關完整的列表請查看gomodifytag的當前master分支上的 flag 定義。

一旦我們有了配置,我們就可以做一些基本的驗證了:

 

  1. func main() { 
  2.     cfg := config{ ... } 
  3.  
  4.     err := cfg.validate() 
  5.     if err != nil { 
  6.         log.Fatalln(err) 
  7.     } 
  8.  
  9.     // continue parsing 
  10.  
  11. // validate validates whether the config is valid or not 
  12. func (c *config) validate() error { 
  13.     if c.file == "" { 
  14.         return errors.New("no file is passed"
  15.     } 
  16.  
  17.     if c.line == "" && c.offset == 0 && c.structName == "" { 
  18.         return errors.New("-line, -offset or -struct is not passed"
  19.     } 
  20.  
  21.     if c.line != "" && c.offset != 0 || 
  22.         c.line != "" && c.structName != "" || 
  23.         c.offset != 0 && c.structName != "" { 
  24.         return errors.New("-line, -offset or -struct cannot be used together. pick one"
  25.     } 
  26.  
  27.     if (c.add == nil || len(c.add) == 0) && 
  28.         (c.addOptions == nil || len(c.addOptions) == 0) && 
  29.         !c.clear && 
  30.         !c.clearOption && 
  31.         (c.removeOptions == nil || len(c.removeOptions) == 0) && 
  32.         (c.remove == nil || len(c.remove) == 0) { 
  33.         return errors.New("one of " + 
  34.             "[-add-tags, -add-options, -remove-tags, -remove-options, -clear-tags, -clear-options]" + 
  35.             " should be defined"
  36.     } 
  37.  
  38.     return nil 

將驗證部分代碼放到一個單一的函數中,使得測試測試更簡單。既然我們已經知道如何獲取配置并進行驗證,我們繼續去解析文件:

用Go語言編寫一門工具的***指南

我們在一開始就討論了如何解析一個文件。這里解析的是config結構體中的方法。實際上,所有的方法都是config結構體的一部分:

 

  1. func main() { 
  2.     cfg := config{} 
  3.  
  4.     node, err := cfg.parse() 
  5.     if err != nil { 
  6.         return err 
  7.     } 
  8.  
  9.     // continue find struct selection ... 
  10.  
  11. func (c *config) parse() (ast.Node, error) { 
  12.     c.fset = token.NewFileSet() 
  13.     var contents interface{} 
  14.     if c.modified != nil { 
  15.         archive, err := buildutil.ParseOverlayArchive(c.modified) 
  16.         if err != nil { 
  17.             return nil, fmt.Errorf("failed to parse -modified archive: %v", err) 
  18.         } 
  19.         fc, ok := archive[c.file] 
  20.         if !ok { 
  21.             return nil, fmt.Errorf("couldn't find %s in archive", c.file) 
  22.         } 
  23.         contents = fc 
  24.     } 
  25.  
  26.     return parser.ParseFile(c.fset, c.file, contents, parser.ParseComments) 

解析函數只完成了一件事。解析源碼并返回一個ast.Node。如果我們僅傳遞文件,這是非常簡單的,在這種情況下,我們使用parser.ParseFile()函數。需要注意的是token.NewFileSet(),它創建一個類型為*token.FileSet。我們將它存儲在c.fset中,但也傳遞給parser.ParseFile()函數。為什么呢?

因為 fileset 用于獨立地為每個文件存儲每個節點的位置信息。這將在以后對于獲得ast.Node的確切信息非常有幫助(請注意,ast.Node使用一個緊湊的位置信息,稱為token.Pos。要獲取更多的信息,它需要通過token.FileSet.Position()函數來獲取一個token.Position,其中包含更多的信息)

讓我們繼續。如果通過 stdin 傳遞源文件,它會變得更加有趣。config.modified 字段是易于測試的 io.Reader ,但實際上我們通過 stdin 傳遞它。我們如何檢測是否需要從 stdin 讀取呢?

我們詢問用戶是否 想 通過 stdin 傳遞內容。在這種情況下,本工具的用戶需要傳遞--modified 標志(這是一個 布爾 標志)。如果用戶傳遞了該標志,我們只需將 stdin 分配給 c.modified 即可:

 

  1. flagModified = flag.Bool("modified"false
  2.     "read an archive of modified files from standard input"
  3.  
  4. if *flagModified { 
  5.     cfg.modified = os.Stdin 

如果你再次檢查上面的 config.parse() 函數,你將看到我們檢查 .modified 字段是否已分配,因為 stdin 是一個任意數據的流,我們需要能夠根據給定的協議對其進行解析。在這種情況下,我們假定其中包含以下內容:

  • 文件名,后跟換行符
  • (十進制)文件大小,后跟換行符
  • 文件的內容

因為我們知道文件大小,我們可以毫無問題地解析此文件的內容。任何大于給定文件大小的部分,我們僅需停止解析。

這種 方法 也被其他幾種工具所使用(如 guru、gogetdoc 等),并且它對編輯器來說是非常有用的。因為這樣可以讓編輯器傳遞修改后的文件內容, 并且無需保存到文件系統中 。因此它被命名為“modified”。

既然我們已經擁有了 Node ,讓我們繼續下一步的“查找結構體”:

用Go語言編寫一門工具的***指南

我們的主函數中,我們將使用在上一步中解析的 ast.Node 中調用 findSelection() 函數:

 

  1. func main() { 
  2.     // ... parse file and get ast.Node  
  3.     start, end, err := cfg.findSelection(node) 
  4.     if err != nil { 
  5.         return err 
  6.     }  
  7.     // continue rewriting the node with the start&end position 

cfg.findSelection() 函數會根據配置文件和我們選定結構體的方式來返回指定結構體的開始和結束位置。它在給定 Node 上進行迭代,然后返回其起始位置(和以上的配置一節中的解釋類似):

用Go語言編寫一門工具的***指南

(檢索步驟會迭代所有 node ,直到其找到一個 *ast.StructType ,然后返回它在文件中的起始位置。)

責任編輯:未麗燕 來源: 開源中國翻譯文章
相關推薦

2019-11-18 11:00:58

程序員編程語言

2015-07-28 15:35:48

學習語言

2022-07-25 19:48:47

Go

2021-07-09 06:48:30

語言Scala編程

2014-12-03 09:48:36

編程語言

2011-12-30 09:33:02

程序員語言

2012-03-28 09:40:40

JavaScript

2025-10-10 08:15:09

2014-09-26 09:29:12

Python

2022-02-27 14:45:16

編程語言JavaC#

2022-11-04 11:11:15

語言入職項目

2012-09-04 11:20:31

2017-04-07 10:45:43

編程語言

2017-04-07 16:49:00

語言程序編程

2022-02-21 11:15:59

編程語言后端開發

2020-09-27 15:52:02

編程語言C 語言Python

2023-02-08 07:35:43

Java語言面向對象

2024-06-27 09:00:00

人工智能編程語言軟件開發

2011-07-14 17:58:11

編程語言

2022-09-07 08:05:32

GScript?編程語言
點贊
收藏

51CTO技術棧公眾號

五月激情四射婷婷| 人妻久久久一区二区三区| 国产又粗又猛又爽又| 日韩成人a**站| 欧美日韩精品久久久| 一区二区三区偷拍| 国产综合在线播放| 久久资源在线| 久久久国产91| 日本一卡二卡在线| 久久亚洲精品人成综合网| 亚洲精品视频观看| 免费99视频| 国产视频一区二区三| 国产精品最新自拍| 久久五月情影视| av网站有哪些| 精品视频一区二区三区| 日韩欧美一区二区在线| 影音先锋男人的网站| 天堂成人在线观看| 极品少妇xxxx精品少妇偷拍| 91精品国产99| 51精品免费网站| 思热99re视热频这里只精品| 欧美一级在线视频| 国产免费视频传媒| 牛牛精品一区二区| 亚洲精品一二三| 亚洲精品日韩精品| 视频在线观看你懂的| 国产麻豆精品theporn| 国产成人精品电影| 日韩av一二三区| 亚洲区综合中文字幕日日| 亚洲欧美日韩高清| 午夜男人的天堂| 精品一区二区三区亚洲| 欧美视频中文字幕| 欧美 激情 在线| av中文在线资源库| 亚洲在线免费播放| 国产成人免费高清视频| 精品无人乱码| 91麻豆精品在线观看| 粉嫩av一区二区三区免费观看| 一级片aaaa| 日韩高清不卡一区二区三区| 清纯唯美亚洲综合| 日本高清不卡码| 亚洲网站视频| 欧美国产精品va在线观看| 男人av资源站| 999久久久免费精品国产| 亚洲图片欧美午夜| 无码人妻丰满熟妇啪啪欧美| 欧美激情影院| 日韩av在线精品| 日韩aaaaa| 精品国产影院| 日韩av在线电影网| 亚洲国产精品无码久久久久高潮| 国产伦乱精品| 亚洲国产私拍精品国模在线观看| 成人欧美精品一区二区| 色播一区二区| 亚洲成年人在线播放| 精品国产aⅴ一区二区三区东京热 久久久久99人妻一区二区三区 | 亚洲成人影院麻豆| 国产精品美女久久久久久久网站| 亚洲 日韩 国产第一区| 尤物视频在线免费观看| 中文字幕在线观看不卡视频| 国产91av视频在线观看| 国产在线观看a视频| 亚洲猫色日本管| 欧美大黑帍在线播放| 第一福利在线视频| 色老综合老女人久久久| 2025韩国理伦片在线观看| 亚洲日本免费电影| 日韩免费电影网站| 人妻丰满熟妇av无码久久洗澡| 深夜福利久久| 久久精品电影网站| 久久精品久久精品久久| 亚洲欧美日韩视频二区| 国产欧美日韩亚洲精品| 国产黄色一级大片| 99国产精品久久久久久久久久| 欧美中日韩免费视频| 老司机精品影院| 亚洲综合成人在线视频| 国产亚洲精品网站| 国产精品一区二区精品| 亚洲国产精品成人av| 亚洲自拍偷拍图| 牛夜精品久久久久久久99黑人| 久久999免费视频| 久久国产黄色片| 蜜臀99久久精品久久久久久软件 | 国产尤物视频在线观看| 福利一区二区在线观看| 日韩wuma| 高清电影在线免费观看| 欧美在线免费观看亚洲| 亚洲一区和二区| 国产在视频线精品视频www666| www.亚洲人.com| 日韩一区二区视频在线| 久久国产欧美日韩精品| 蜜桃精品久久久久久久免费影院 | 成人动态视频| 一区二区在线视频| 日本一区二区不卡在线| 久久99精品久久久久久久久久久久| 国产chinese精品一区二区| 成年在线观看免费人视频| 亚洲午夜一二三区视频| 天天干天天玩天天操| 国产精品极品国产中出| 精品国产一区久久久| 亚洲成人第一网站| 成人av三级| 久久久久国产精品一区三寸| 91日本视频在线| 国产在线91| 亚洲午夜在线电影| 国产亚洲视频一区| 成久久久网站| 欧美一区亚洲一区| 国产自产一区二区| 一区二区三区中文字幕| 日韩欧美理论片| 欧美日韩国产在线观看网站| 午夜精品福利在线观看| 精品人妻av一区二区三区| 中文字幕制服丝袜成人av| 国产成人av影视| 啪啪激情综合网| 久久青草精品视频免费观看| 99久久一区二区| 中文字幕在线观看不卡视频| 国产精品自拍视频在线| 欧美一区二区三区高清视频| 国产成人精彩在线视频九色| 日本午夜在线视频| 精品久久久香蕉免费精品视频| 色欲无码人妻久久精品| 中文字幕一区二区三区欧美日韩| 国产精品三级在线| 日本三级在线视频| 欧美老人xxxx18| 久久精品在线观看视频| 久久激情五月婷婷| 在线观看成人免费| 国产精品美女久久久久| 欧美成人精品h版在线观看| 国产叼嘿视频在线观看| 亚洲激情欧美激情| 国产高潮失禁喷水爽到抽搐| 影音先锋在线一区| 精品无码久久久久国产| 亚洲美女尤物影院| 亚洲视频在线看| 青娱乐在线免费视频| 欧美国产国产综合| 亚洲一区二区在线视频观看| 一区二区三区网站| 99伊人久久| 97人人爽人人澡人人精品| 国产精品亚洲产品| 亚洲欧美在线播放| 国产成人麻豆免费观看| 中文字幕不卡在线观看| 天天色天天综合网| 欧美激情一级片一区二区| 国产在线一区二| 婷婷综合六月| 精品国产欧美一区二区五十路| 国产精品久久欧美久久一区| 亚洲精品成人在线| 内射中出日韩无国产剧情| 日韩电影一区二区三区| 国产精品h视频| 波多野结衣在线一区二区| 97色在线播放视频| 亚洲成人影院麻豆| 精品少妇一区二区三区在线视频| 日本在线小视频| 国产日韩精品一区二区三区| 亚洲高清在线不卡| 亚洲精品韩国| 亚洲aⅴ天堂av在线电影软件| 国产精区一区二区| 1769国内精品视频在线播放| 精品视频一二三| 日韩精品影音先锋| 男人天堂视频在线| 亚洲一区二区三区中文字幕| 欧美成人午夜精品免费| 国产精品一区二区久激情瑜伽| 国产综合中文字幕| 91九色精品国产一区二区| 官网99热精品| 国产69精品久久| 国语自产在线不卡| 香港伦理在线| 日韩精品高清在线| www.激情五月| 在线精品观看国产| 精品在线视频观看| 国产精品不卡在线| 欧美色图亚洲激情| 成人性色生活片免费看爆迷你毛片| 国产精品免费成人| 一区视频在线看| 中文字幕99| 在线日韩一区| 欧美xxxx在线观看| 99久久久精品视频| 久久爱www成人| 91成人理论电影| 精品三级在线| 青青草原成人在线视频| av中文字幕电影在线看| 久久精品视频99| yourporn在线观看中文站| 亚洲国产精品久久久久久| jlzzjlzzjlzz亚洲人| 欧美视频完全免费看| 日韩精品在线免费视频| 亚洲国产日韩在线一区模特| 91传媒免费观看| 中文乱码免费一区二区| 人妻av无码一区二区三区| bt7086福利一区国产| 又黄又爽又色的视频| 久久精品国产亚洲aⅴ| 国产精品无码av无码| 久久aⅴ乱码一区二区三区| 又大又硬又爽免费视频| 午夜视频精品| 亚洲 欧洲 日韩| 日韩综合一区| 亚洲欧美精品在线观看| 欧美日韩在线网站| 三区精品视频| 不卡在线一区| 一区二区三区电影| 99精品视频在线观看免费播放| 亚洲午夜精品久久久久久浪潮| 精品中文一区| 欧美群妇大交群中文字幕| 久久精品国产露脸对白| 精品午夜一区二区三区在线观看| 黄色在线视频网| 麻豆国产精品777777在线| 日本中文字幕影院| 国产精品一二三区在线| 成人在线观看一区二区| av福利精品导航| 女尊高h男高潮呻吟| 久久综合色8888| 九九热免费在线| 中文字幕一区免费在线观看 | 欧美激情在线一区二区三区| 白白色免费视频| 国产精品丝袜黑色高跟| 北条麻妃在线观看视频| 一区二区视频在线看| 精品无码久久久久久久| 大桥未久av一区二区三区| 亚洲熟妇无码乱子av电影| 在线一区二区三区四区| 在线视频欧美亚洲| 欧美一级高清片| 亚洲日本在线播放| 国产一区二区三区18| 久久77777| 国外成人在线直播| 日韩欧美精品电影| 91亚洲人电影| 秋霞影院一区二区三区| 日本一区二区三区视频免费看| 不卡视频在线| 免费人成自慰网站| 免费在线成人| 黄色片免费网址| 97久久人人超碰| 懂色av粉嫩av浪潮av| 一区二区三区不卡视频在线观看| 制服.丝袜.亚洲.中文.综合懂色| 欧美在线播放高清精品| 黄色小视频大全| 欧洲精品一区二区三区| 成人激情视频小说免费下载| jazzjazz国产精品麻豆| 日产精品高清视频免费| 欧美午夜在线| 欧美精品成人网| 成人高清免费观看| 日本高清黄色片| 调教+趴+乳夹+国产+精品| 中文无码精品一区二区三区| 精品国产一区二区三区四区四| 深夜福利在线观看直播| 久久成年人免费电影| 欧美日韩五码| 高清日韩一区| 亚洲激情五月| 韩国日本美国免费毛片| 成人永久看片免费视频天堂| 国产精品酒店视频| 欧美日韩一区二区三区| 精品人妻一区二区三区蜜桃| 伊人青青综合网站| 蜜桃视频在线观看免费视频| 亚洲一区中文字幕| 精品久久精品| 成年人观看网站| 国产69精品一区二区亚洲孕妇 | 超碰97久久国产精品牛牛| 亚洲毛片aa| 男女av一区三区二区色多| 91丨porny丨九色| 国产精品美女视频| 五月婷婷激情视频| 亚洲国产一区二区三区在线观看 | 久久精品福利视频| 韩日精品一区| 欧美日韩一区二区视频在线观看| 欧美日韩国产探花| 亚洲色图偷拍视频| 国产精品久久久久久久久免费樱桃| 国产成人一区二区三区影院在线| 日韩欧美国产精品一区| 日本视频在线播放| 成人妇女免费播放久久久| 精品国产中文字幕第一页| 国产综合免费视频| 91蜜桃传媒精品久久久一区二区 | 日韩欧美在线中文字幕| 欧美 日韩 国产 在线| 欧美黑人巨大精品一区二区| 国产在线视频欧美一区| www.亚洲一区二区| 国产精品一区二区免费不卡| www.xxxx日本| 日韩欧美在线1卡| а天堂中文在线官网| 91亚洲国产成人久久精品网站 | 日日骚av一区| 九九热这里有精品| 亚洲精品一卡二卡三卡四卡| 全部av―极品视觉盛宴亚洲| 影音先锋男人在线| 欧美日韩一级片在线观看| aiai在线| 91欧美视频网站| 一级毛片免费高清中文字幕久久网| 岛国av在线免费| 自拍视频在线观看一区二区| 国产美女裸体无遮挡免费视频| 久久精品国产亚洲精品2020| 国产精一区二区| 国产精品三级一区二区| 波波电影院一区二区三区| 欧美一二三区视频| 亚洲一级片在线看| 91九色成人| 免费毛片网站在线观看| 91污片在线观看| 中文字幕一二三四| 久久国产精品电影| 国产精品宾馆| 国产免费人做人爱午夜视频| 国产精品毛片大码女人| 99精品免费观看| 97国产精品免费视频| 精品国产一区二区三区av片| 亚洲第一色av| 偷拍日韩校园综合在线| 国产一二三区在线视频| 91久久在线播放| 国产欧美精品久久| 手机看片日韩av| 欧美成人艳星乳罩| 国产综合色区在线观看| 久久av秘一区二区三区| 不卡电影免费在线播放一区| 亚洲成人av网址| 欧美精品aaa| 色婷婷一区二区三区| 在线免费看黄色片| 欧洲一区在线观看| 天天干在线视频论坛| 日韩欧美在线一区二区| 国产很黄免费观看久久| 日韩免费av网站|