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

如何用Go實現一個ORM

開發 開發工具
通過表結構,我們可以生成對應的結構體和持久層增刪改查代碼,我們再往前擴展一步,能否通過表結構生成的proto格式的message,以及一些常用的CRUD GRPC rpc接口定義。

為了提高開發效率和質量,我們常常需要ORM來幫助我們快速實現持久層增刪改查API,目前go語言實現的ORM有很多種,他們都有自己的優劣點,有的實現簡單,有的功能復雜,有的API十分優雅。在使用了多個類似的工具之后,總是會發現某些點無法滿足解決我們生產環境中碰到的實際問題,比如無法集成公司內部的監控,Trace組件,沒有database層的超時設置,沒有熔斷等,所以有必要公司自己內部實現一款滿足我們可自定義開發的ORM,好用的生產工具常常能夠對生產力產生飛躍式的提升。

為什么需要ORM

直接使用database/sql的痛點

首先看看用database/sql如何查詢數據庫我們用user表來做例子,一般的工作流程是先做技術方案,其中排在比較前面的是數據庫表的設計,大部分公司應該有嚴格的數據庫權限控制,不會給線上程序使用比較危險的操作權限,比如創建刪除數據庫,表,刪除數據等。表結構如下:

CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(100) NOT NULL COMMENT '名稱',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',
`ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`mtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

首先我們要寫出和表結構對應的結構體User,如果你足夠勤奮和努力,相應的json tag 和注釋都可以寫上,這個過程無聊且重復,因為在設計表結構的時候你已經寫過一遍了。

type User struct {
Id int64 `json:"id"`
Name string `json:"name"`
Age int64
Ctime time.Time
Mtime time.Time // 更新時間
}

定義好結構體,我們寫一個查詢年齡在20以下且按照id字段順序排序的前20名用戶的 go代碼

func FindUsers(ctx context.Context) ([]*User, error) {
rows, err := db.QueryContext(ctx, "SELECT `id`,`name`,`age`,`ctime`,`mtime` FROM user WHERE `age`<? ORDER BY `id` LIMIT 20 ", 20)
if err != nil {
return nil, err
}
defer rows.Close()
result := []*User{}
for rows.Next() {
a := &User{}
if err := rows.Scan(&a.Id, &a.Name, &a.Age, &a.Ctime, &a.Mtime); err != nil {
return nil, err
}
result = append(result, a)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return result, nil
}

當我們寫少量這樣的代碼的時候我們可能還覺得輕松,但是當你業務工期排的很緊,并且要寫大量的定制化查詢的時候,這樣的重復代碼會越來越多。上面的的代碼我們發現有這么幾個問題:

  1. SQL 語句是硬編碼在程序里面的,當我需要增加查詢條件的時候我需要另外再寫一個方法,整個方法需要拷貝一份,很不靈活。
  2. 在查詢表所有字段的情況下,第2行下面的代碼都是一樣重復的,不管sql語句后面的條件是怎么樣的。
  3. 我們發現第1行SQL語句編寫和rows.Scan()那行,寫的枯燥層度是和表字段的數量成正比的,如果一個表有50個字段或者100個字段,手寫是非常乏味的。
  4. 在開發過程中rows.Close() 和 rows.Err()忘記寫是常見的錯誤。

我們總結出來用database/sql標準庫開發的痛點:

開發效率很低

很顯然寫上面的那種代碼是很耗費時間的,因為手誤容易寫錯,無可避免要增加自測的時間。如果上面的結構體User、 查詢方法FindUsers() 代碼能夠自動生成,那么那將會極大的提高開發效率并且減少human error的發生從而提高開發質量。

心智負擔很重

如果一個開發人員把大量的時間花在這些代碼上,那么他其實是在浪費自己的時間,不管在工作中還是在個人項目中,應該把重點花在架構設計,業務邏輯設計,困難點攻堅上面,去探索和開拓自己沒有經驗的領域,這塊Dao層的代碼最好在10分鐘內完成。

ORM的核心組成

明白了上面的痛點,為了開發工作更舒服,更高效,我們嘗試著自己去開發一個ORM,核心的地方在于兩個方面:

圖片

  1. SQLBuilder:SQL語句要非硬編碼,通過某種鏈式調用構造器幫助我構建SQL語句。
  2. Scanner:從數據庫返回的數據可以自動映射賦值到結構體中。

SQL SelectBuilder

我們嘗試做個簡略版的查詢語句構造器,最終我們要達到如下圖所示的效果。

圖片

我們可以通過和SQL關鍵字同名的方法來表達SQL語句的固有關鍵字,通過go方法參數來設置其中動態變化的元素,這樣鏈式調用和寫SQL語句的思維順序是一致的,只不過我們之前通過硬編碼的方式變成了方法調用。

具體代碼如下:

type SelectBuilder struct {
builder *strings.Builder
column []string
tableName string
where []func(s *SelectBuilder)
args []interface{}
orderby string
offset *int64
limit *int64
}

func (s *SelectBuilder) Select(field ...string) *SelectBuilder {
s.column = append(s.column, field...)
return s
}

func (s *SelectBuilder) From(name string) *SelectBuilder {
s.tabelName = name
return s
}
func (s *SelectBuilder) Where(f ...func(s *SelectBuilder)) *SelectBuilder {
s.where = append(s.where, f...)
return s
}
func (s *SelectBuilder) OrderBy(field string) *SelectBuilder {
s.orderby = field
return s
}
func (s *SelectBuilder) Limit(offset, limit int64) *SelectBuilder {
s.offset = &offset
s.limit = &limit
return s
}
func GT(field string, arg interface{}) func(s *SelectBuilder) {
return func(s *SelectBuilder) {
s.builder.WriteString("`" + field + "`" + " > ?")
s.args = append(s.args, arg)
}
}
func (s *SelectBuilder) Query() (string, []interface{}) {
s.builder.WriteString("SELECT ")
for k, v := range s.column {
if k > 0 {
s.builder.WriteString(",")
}
s.builder.WriteString("`" + v + "`")
}
s.builder.WriteString(" FROM ")
s.builder.WriteString("`" + s.tableName + "` ")
if len(s.where) > 0 {
s.builder.WriteString("WHERE ")
for k, f := range s.where {
if k > 0 {
s.builder.WriteString(" AND ")
}
f(s)
}
}
if s.orderby != "" {
s.builder.WriteString(" ORDER BY " + s.orderby)
}
if s.limit != nil {
s.builder.WriteString(" LIMIT ")
s.builder.WriteString(strconv.FormatInt(*s.limit, 10))
}
if s.offset != nil {
s.builder.WriteString(" OFFSET ")
s.builder.WriteString(strconv.FormatInt(*s.offset, 10))
}
return s.builder.String(), s.args
}

  1. 通過結構體上的方法調用返回自身,使其具有鏈式調用能力,并通過方法調用設置結構體中的值,用以構成SQL語句需要的元素。
  2. SelectBuilder 包含性能較高的strings.Builder 來拼接字符串。
  3. Query()方法構建出真正的SQL語句,返回包含占位符的SQL語句和args參數。
  4. []func(s *SelectBuilder)通過函數數組來創建查詢條件,可以通過函數調用的順序和層級來生成 AND OR這種有嵌套關系的查詢條件子句。
  5. Where() 傳入的是查詢條件函數,為可變參數列表,查詢條件之間默認是AND關系。

外部使用起來效果:

b := SelectBuilder{builder: &strings.Builder{}}
sql, args := b.
Select("id", "name", "age", "ctime", "mtime").
From("user").
Where(GT("id", 0), GT("age", 0)).
OrderBy("id").
Limit(0, 20).
Query()

Scanner的實現

顧名思義Scanner的作用就是把查詢結果設置到對應的go對象上去,完成關系和對象的映射,關鍵核心就是通過反射獲知傳入對象的類型和字段類型,通過反射創建對象和值,并通過golang結構體的字段后面的tag來和查詢結果的表頭一一對應,達到動態給結構字段賦值的能力。

圖片

具體實現如下:

func ScanSlice(rows *sql.Rows, dst interface{}) error {
defer rows.Close()
// dst的地址
val := reflect.ValueOf(dst) // &[]*main.User
// 判斷是否是指針類型,go是值傳遞,只有傳指針才能讓更改生效
if val.Kind() != reflect.Ptr {
return errors.New("dst not a pointer")
}
// 指針指向的Value
val = reflect.Indirect(val) // []*main.User
if val.Kind() != reflect.Slice {
return errors.New("dst not a pointer to slice")
}
// 獲取slice中的類型
struPointer := val.Type().Elem() // *main.User
// 指針指向的類型 具體結構體
stru := struPointer.Elem() // main.User

cols, err := rows.Columns() // [id,name,age,ctime,mtime]
if err != nil {
return err
}
// 判斷查詢的字段數是否大于 結構體的字段數
if stru.NumField() < len(cols) { // 5,5
return errors.New("NumField and cols not match")
}
//結構體的json tag的value對應字段在結構體中的index
tagIdx := make(map[string]int) //map tag -> field idx
for i := 0; i < stru.NumField(); i++ {
tagname := stru.Field(i).Tag.Get("json")
if tagname != "" {
tagIdx[tagname] = i
}
}
resultType := make([]reflect.Type, 0, len(cols)) // [int64,string,int64,time.Time,time.Time]
index := make([]int, 0, len(cols)) // [0,1,2,3,4,5]
// 查找和列名相對應的結構體jsontag name的字段類型,保存類型和序號到resultType和index中
for _, v := range cols {
if i, ok := tagIdx[v]; ok {
resultType = append(resultType, stru.Field(i).Type)
index = append(index, i)
}
}
for rows.Next() {
// 創建結構體指針,獲取指針指向的對象
obj := reflect.New(stru).Elem() // main.User
result := make([]interface{}, 0, len(resultType)) //[]
// 創建結構體字段類型實例的指針,并轉化為interface{} 類型
for _, v := range resultType {
result = append(result, reflect.New(v).Interface()) // *Int64 ,*string ....
}
// 掃描結果
err := rows.Scan(result...)
if err != nil {
return err
}
for i, v := range result {
// 找到對應的結構體index
fieldIndex := index[i]
// 把scan 后的值通過反射得到指針指向的value,賦值給對應的結構體字段
obj.Field(fieldIndex).Set(reflect.ValueOf(v).Elem()) // 給obj 的每個字段賦值
}
// append 到slice
vv := reflect.Append(val, obj.Addr()) // append到 []*main.User, maybe addr change
val.Set(vv) // []*main.User
}
return rows.Err()
}

通過反射賦值流程,如果想知道具體的實現細節可以仔細閱讀上面代碼里面的注釋

圖片

  1. 以上主要的思想就是通過reflect包來獲取傳入dst的Slice類型,并通過反射創建其包含的對象,具體的步驟和解釋請仔細閱讀注釋和圖例。
  2. 通過指定的json tag 可以把查詢結果和結構體字段mapping起來,即使查詢語句中字段不按照表結構順序。
  3. ScanSlice是通用的Scanner。
  4. 使用反射創建對象明顯創建了多余的對象,沒有傳統的方式賦值高效,但是換來的巨大的靈活性在某些場景下是值得的。

有了SQLBuilder和Scanner 我們就可以這樣寫查詢函數了:

func FindUserReflect() ([]*User, error) {
b := SelectBuilder{builder: &strings.Builder{}}
sql, args := b.
Select("id", "name", "age", "ctime", "mtime").
From("user").
Where(GT("id", 0), GT("age", 0)).
OrderBy("id").
Limit(0, 20).
Query()

rows, err := db.QueryContext(ctx, sql, args...)
if err != nil {
return nil, err
}
result := []*User{}
err = ScanSlice(rows, &result)
if err != nil {
return nil, err
}
return result, nil
}

生成的查詢SQL語句和args如下:

SELECT `id`,`name`,`age`,`ctime`,`mtime` FROM `user` WHERE `id` > ? AND `age` > ? ORDER BY id LIMIT 20 OFFSET 0  [0 0]

自動生成

通過上面的使用的例子來看,我們的工作輕松了不少:

  • 第一:SQL語句不需要硬編碼了;
  • 第二:Scan不需要寫大量結構體字段和的乏味的重復代碼。

著實幫我們省了很大的麻煩。但是查詢字段還需要我們自己手寫,像這種

Select("id", "name", "age", "ctime", "mtime").

  • 其中傳入的字段需要我們硬編碼,我們可不可以再進一步,通過表結構定義來生成我們的golang結構體呢?答案是肯定的,要實現這一步我們需要一個SQL語句的解析器(https://github.com/xwb1989/sqlparser),把SQL DDL語句解析成go語言中如下的Table對象,其所包含的表名,列名、列類型、注釋等都能獲取到,再通過這些對象和寫好的模板代碼來生成我們實際業務使用的代碼。

Table對象如下:

type Table struct {
TableName string // table name
GoTableName string // go struct name
PackageName string // package name
Fields []*Column // columns
}
type Column struct {
ColumnName string // column_name
ColumnType string // column_type
ColumnComment string // column_comment
}

使用以上Table對象的模板代碼:

type {{.GoTableName}} struct {
{{- range .Fields }}
{{ .GoColumnName }} {{ .GoColumnType }} `json:"` `.`ColumnName `"` // {{ .ColumnComment }}
{{- end}}
}
const (
table = "``.`TableName`"
{{- range .Fields}}
{{ .GoColumnName}} = "``.`ColumnName`"
{{- end }}
)
var columns = []string{
{{- range .Fields}}
{{ .GoColumnName}},
{{- end }}
}

通過上面的模板我們用user表的建表SQL語句生成如下代碼:

type User struct {
Id int64 `json:"id"` // id字段
Name string `json:"name"` // 名稱
Age int64 `json:"age"` // 年齡
Ctime time.Time `json:"ctime"` // 創建時間
Mtime time.Time `json:"mtime"` // 更新時間
}
const (
table = "user"
Id = "id"
Name = "name"
Age = "age"
Ctime = "ctime"
Mtime = "mtime"
)
var Columns = []string{"id","name","age","ctime","mtime"}

那么我們在查詢的時候就可以這樣使用

Select(Columns...)

通過模板自動生成代碼,可以大大的減輕開發編碼負擔,使我們從繁重的代碼中解放出來。

reflect真的有必要嗎?

由于我們SELECT時選擇查找的字段和順序是不固定的,我們有可能 SELECT id, name, age FROM user,也可能 SELECT name, id FROM user,有很大的任意性,這種情況使用反射出來的結構體tag和查詢的列名來確定映射關系是必須的。但是有一種情況我們不需要用到反射,而且是一種最常用的情況,即:查詢的字段名和表結構的列名一致,且順序一致。這時候我們可以這么寫,通過DeepEqual來判斷查詢字段和表結構字段是否一致且順序一致來決定是否通過反射還是通過傳統方法來創建對象。用傳統方式創建對象(如下圖第12行)令我們編碼痛苦,不過可以通過模板來自動生成下面的代碼,以避免手寫,這樣既靈活方便好用,性能又沒有損耗,看起來是一個比較完美的解決方案。

func FindUserNoReflect(b *SelectBuilder) ([]*User, error) {
sql, args := b.Query()
rows, err := db.QueryContext(ctx, sql, args...)
if err != nil {
return nil, err
}
result := []*User{}
if DeepEqual(b.column, Columns) {
defer rows.Close()
for rows.Next() {
a := &User{}
if err := rows.Scan(&a.Id, &a.Name, &a.Age, &a.Ctime, &a.Mtime); err != nil {
return nil, err
}
result = append(result, a)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return result, nil
}
err = ScanSlice(rows, &result)
if err != nil {
return nil, err
}
return result, nil
}

總結

  1. 通過database/sql 庫開發有較大痛點,ORM就是為了解決以上問題而生,其存在是有意義的。
  2. ORM 兩個關鍵的部分是SQLBuilder和Scanner的實現。
  3. ORM Scanner 使用反射創建對象在性能上肯定會有一定的損失,但是帶來極大的靈活性,同時在查詢全表字段這種特殊情況下規避使用反射來提高性能。

展望

通過表結構,我們可以生成對應的結構體和持久層增刪改查代碼,我們再往前擴展一步,能否通過表結構生成的proto格式的message,以及一些常用的CRUD GRPC rpc接口定義。通過工具,我們甚至可以把前端的代碼都生成好,實現半自動化編程。我想這個是值得期待的。

參考資料:[1] ??https://github.com/ent/ent??

?本期作者:洪勝杰

B端技術中心高級開發工程師?

圖片




責任編輯:武曉燕 來源: 嗶哩嗶哩技術
相關推薦

2016-09-06 19:45:18

javascriptVue前端

2017-03-15 08:43:29

JavaScript模板引擎

2017-03-20 17:59:19

JavaScript模板引擎

2021-09-13 06:03:42

CSS 技巧搜索引擎

2020-10-26 08:19:53

算法隊列

2017-05-02 11:30:44

JavaScript數組惰性求值庫

2023-12-30 13:33:36

Python解析器JSON

2022-04-14 20:43:24

JavaScript原型鏈

2018-06-22 10:30:56

C語言虛擬機編譯器

2021-07-02 07:18:19

Goresults通道類型

2018-03-23 10:00:34

PythonTensorFlow神經網絡

2023-06-06 15:38:28

HTMLCSS開發

2017-12-12 15:24:32

Web Server單線程實現

2015-10-12 16:45:26

NodeWeb應用框架

2021-07-06 14:36:05

RustLinux內核模塊

2023-03-06 08:14:48

MySQLRedis場景

2024-03-28 08:36:57

2009-06-02 17:27:28

Hibernate框架ORM

2019-10-11 15:10:09

GVMGoLinux

2020-10-30 15:04:16

開發技能代碼
點贊
收藏

51CTO技術棧公眾號

91精品福利在线一区二区三区| www.亚洲精品| 日韩在线视频线视频免费网站| 超碰成人在线播放| wwwwxxxx在线观看| 国产日韩亚洲欧美综合| 亚洲一区美女视频在线观看免费| 久久亚洲AV无码| 国产午夜一区| 日韩一区二区电影在线| 欧美黄色免费影院| 免费日本一区二区三区视频| 成人av网站在线观看| 国产精品美乳一区二区免费| 亚洲精品乱码久久久久久蜜桃91| 国产又黄又爽视频| 国产欧美69| 最新国产成人av网站网址麻豆| 亚洲精品国产成人av在线| 色猫猫成人app| 一区二区三区四区高清精品免费观看 | 欧美丰满老妇熟乱xxxxyyy| 美女精品久久| 欧美性xxxxxxxx| 草草久久久无码国产专区| 福利在线视频网站| 国产区在线观看成人精品| 国产99在线播放| 国产精品无码一区二区桃花视频| 日日摸夜夜添夜夜添精品视频| 欧美激情精品久久久久久免费印度| 正在播放国产对白害羞| 欧美电影完整版在线观看| 欧美一区二区三区精品| 在线免费观看av的网站| 国产高清不卡| 日韩欧美在线视频日韩欧美在线视频 | 色综合久久影院| 久久久国产一区二区三区四区小说| 91久久国产综合久久蜜月精品| 伊人亚洲综合网| 久久精品官网| 欧美最顶级的aⅴ艳星| 国产伦精品一区二区三区精品| 国产精品久久久久77777丨| 欧美香蕉大胸在线视频观看| 国产 日韩 欧美在线| 国产成人在线视频免费观看| 日韩一区中文字幕| 亚洲欧美久久234| av资源网站在线观看| 亚洲精品进入| 91精品国产色综合久久久蜜香臀| 在线观看国产一级片| 在线手机中文字幕| 欧美日韩在线一区| 青青在线视频观看| 希岛爱理一区二区三区av高清| 精品美女久久久久久免费| 日本精品久久久久久久久久| 风流少妇一区二区三区91| 国内国产精品久久| 92国产精品视频| 色欲人妻综合网| 亚洲综合中文| 久久久精品国产一区二区| 乱h高h女3p含苞待放| 影视亚洲一区二区三区| 久久综合伊人77777| 国产97免费视频| 亚洲午夜激情在线| 欧美亚洲视频在线看网址| 午夜时刻免费入口| 一区二区三区韩国免费中文网站| 亚洲精品一区二区久| 亚洲欧美va天堂人熟伦| 99久久久久久中文字幕一区| 欧美精品在线免费播放| 日本三级免费看| 母乳一区在线观看| 国产精品午夜视频| 国产chinasex对白videos麻豆| 国产激情偷乱视频一区二区三区| 国产精品乱码| 韩国精品视频| 亚洲桃色在线一区| 日本中文字幕亚洲| 日韩av超清在线观看| 欧美日韩mp4| 亚洲色图欧美另类| 美女毛片一区二区三区四区| 久久精品国亚洲| 国产无遮挡裸体免费视频| 日韩精品欧美精品| 成人午夜电影免费在线观看| 青青草视频免费在线观看| 国产精品久久一级| 国产亚洲第一区| 黄色av网站在线看| 亚洲人成伊人成综合网小说| 欧美日本视频在线观看| 欧美日韩破处视频| 亚洲精品国精品久久99热| 国产又粗又猛又爽又黄的视频四季| 午夜国产精品视频免费体验区| 18久久久久久| 国产婷婷一区二区三区久久| 91麻豆免费在线观看| 欧美日韩一区二区三区电影| 成人教育av| 欧美一区二区三区播放老司机| 亚洲第九十七页| 一区二区中文字| 日韩暖暖在线视频| 高潮一区二区三区乱码| 中文字幕在线免费不卡| 男人天堂网视频| 97人人澡人人爽91综合色| 中文字幕日韩在线观看| 国产性xxxx高清| 国产精品亚洲视频| 一区二区免费在线观看| 亚洲欧美电影| 亚洲第一视频在线观看| www.av成人| 日产欧产美韩系列久久99| 精品在线视频一区二区三区| 天堂av资源在线观看| 欧美精品一级二级三级| 亚洲AV无码国产成人久久| 亚洲国产精品第一区二区| 91免费看国产| 日本a在线播放| 欧美性色黄大片| 日本少妇高潮喷水xxxxxxx| 99精品福利视频| 99在线视频首页| 性做久久久久久久| 国产欧美日韩激情| 国内自拍视频一区| 国产不卡一二三区| 欧美有码在线观看视频| 五月激情六月婷婷| 午夜精品久久久久久久久| 美女被爆操网站| 午夜日韩av| 5g国产欧美日韩视频| 黄网站在线播放| 欧美一区二区三区婷婷月色| 可以免费看av的网址| 久久www免费人成看片高清| 五月天亚洲综合| 精品国产黄a∨片高清在线| 少妇激情综合网| 亚洲中文字幕在线观看| 亚洲色图清纯唯美| 69久久精品无码一区二区| 欧美日韩天堂| 国产伦一区二区三区色一情 | 久久乐国产精品| 欧美亚洲精品在线观看| 精品久久中文字幕| 日本一级免费视频| 欧美96一区二区免费视频| 日本一区视频在线观看| 亚洲精品555| 久久精品视频一| 精品欧美在线观看| 亚洲高清视频中文字幕| 人妻少妇精品视频一区二区三区| 亚洲欧美日韩专区| 亚洲国产高清国产精品| 欧美一级网址| 久久91精品国产91久久跳| 欧美 日韩 国产 成人 在线 | 中文字幕第一页在线播放| 国产精品你懂的在线| 永久免费黄色片| 亚洲久久视频| 日韩福利二区| 国产精品麻豆| 欧美性视频网站| 色欧美激情视频在线| 精品免费日韩av| 亚洲天堂男人av| 亚洲少妇30p| 噜噜噜在线视频| 青青草成人在线观看| 欧美大片免费播放| 小嫩嫩12欧美| 成人黄色av网站| aa国产成人| 色黄久久久久久| 欧美77777| 欧美日韩国产天堂| 天天操天天爽天天干| 日本一区二区成人| 亚洲麻豆一区二区三区| 人人爽香蕉精品| 国产美女在线一区| 日韩电影免费网站| 好看的日韩精品| 99精品视频在线免费播放| 欧美精品一二区| 蜜芽tv福利在线视频| 国产剧情在线观看一区| 欧美国产在线电影| 丁香婷婷在线| 精品捆绑美女sm三区| 亚洲天堂久久久久| 精品人伦一区二区三区蜜桃免费| 国产精品三区在线观看| 国产欧美一区在线| 国产一卡二卡三卡四卡| 激情六月婷婷久久| 亚洲 中文字幕 日韩 无码| 亚洲一级一区| 中文字幕一区二区三区四区五区| 日韩美女毛片| 91在线在线观看| 欧美爱爱视频| 日本中文字幕成人| 欧美gv在线观看| 欧美日本在线视频中文字字幕| 日本中文字幕在线观看| 亚洲欧美国产日韩中文字幕| 天堂网在线资源| 欧美videos大乳护士334| a天堂在线观看视频| 欧美午夜精品免费| 成人a v视频| 99久久国产综合精品色伊| 无码国产精品一区二区高潮| 久久草av在线| 福利视频999| 麻豆国产精品777777在线| 国产免费人做人爱午夜视频| 99伊人成综合| 免费无码不卡视频在线观看| 亚洲二区免费| 老太脱裤子让老头玩xxxxx| 亚洲电影成人| 5月婷婷6月丁香| 亚洲中字黄色| 欧美精品色婷婷五月综合| 西西裸体人体做爰大胆久久久| 波多野结衣之无限发射| 国产欧美一区二区三区国产幕精品| 欧美一级欧美一级| 中文久久精品| 欧美少妇性生活视频| 久久深夜福利| 精品日韩久久久| 九九热在线视频观看这里只有精品| 日韩av卡一卡二| 国产在线精品免费| 性生交大片免费看l| 成人精品免费看| 国产伦精品一区二区三区妓女 | 久久久久国产精品一区二区| caopor在线视频| 日韩精品成人一区二区三区| 日韩av片网站| 国产一区欧美日韩| 亚洲成年人在线观看| 91免费精品国自产拍在线不卡| av在线网站观看| 日本一区二区视频在线观看| 99自拍视频在线| 亚洲一区免费视频| 黄色在线视频网址| 欧美日韩在线播放一区| 国产视频在线免费观看| 亚洲成色777777女色窝| 蜜芽tv福利在线视频| 精品国内产的精品视频在线观看| 国产原创中文av| 日韩亚洲欧美中文三级| 国产激情在线免费观看| 高清国产午夜精品久久久久久| 国产高清不卡无码视频| 国产在线一级片| av网站一区二区三区| 久久精品成人av| 中文字幕佐山爱一区二区免费| 精品亚洲永久免费| 色久优优欧美色久优优| 国产又黄又粗又硬| 日韩va亚洲va欧洲va国产| 国产毛片av在线| 亚洲国产成人精品一区二区| 香蕉av一区二区三区| 最近的2019中文字幕免费一页| 女同一区二区免费aⅴ| 日本在线观看天堂男亚洲 | 黄色网在线看| 2025国产精品视频| 国产午夜精品一区在线观看| 精品国产乱码久久久久软件| 天天超碰亚洲| 毛片av免费在线观看| 国产成人精品一区二| 亚洲欧美偷拍另类| 国产69精品久久久久777| 国产综合精品在线| 亚洲午夜激情av| 在线视频 91| 精品视频偷偷看在线观看| 男人的天堂在线视频免费观看 | 亚州国产精品| 97av中文字幕| 麻豆精品一区二区三区| 美国黄色a级片| 亚洲一区二区三区四区五区中文 | 黄页网站免费观看| 欧美视频在线一区二区三区| 婷婷亚洲一区二区三区| 欧美福利视频在线观看| 黄色成人在线观看网站| 欧美日韩一区二 | 亚洲精品中文字幕无码蜜桃| 成人免费黄色在线| 少妇aaaaa| 欧美高清hd18日本| www.久久热.com| 日本精品一区二区三区在线| 奇米777国产一区国产二区| 蜜臀av性久久久久蜜臀av| 九色综合狠狠综合久久| 日本高清黄色片| 日本乱人伦一区| 亚洲色图欧美视频| 97精品一区二区三区| 高清欧美性猛交xxxx黑人猛| 国产日产欧美一区二区| 精品午夜一区二区三区在线观看| 天天舔天天操天天干| 在线观看亚洲a| yiren22综合网成人| 国产成人啪精品视频免费网| 杨幂一区二区三区免费看视频| 日韩av三级在线| 91色.com| 国产真人无遮挡作爱免费视频| 亚洲精品一区中文字幕乱码| 在线看片国产福利你懂的| 久久影院理伦片| 久久激情综合| 伊人影院综合网| 欧美日韩国产首页| 中文字幕在线三区| 99精彩视频在线观看免费| 欧美日韩综合| 这里只有精品在线观看视频 | 中文字幕黄色av| 最近2019中文字幕在线高清 | 国产精品一区二区免费| 99国产精品视频免费观看一公开| 国产ts丝袜人妖系列视频| 日本高清免费不卡视频| 8888四色奇米在线观看| 成人在线播放av| 你懂的一区二区| 欧美大喷水吹潮合集在线观看| 一本在线高清不卡dvd| 触手亚洲一区二区三区| 91精品久久久久久久久青青| 你懂的网址国产 欧美| 中出视频在线观看| 在线观看国产91| 乱人伦中文视频在线| 成人在线免费网站| 性欧美长视频| 成年人免费视频播放| 日韩一区二区三区免费看 | 五月开心六月丁香综合色啪| 三上悠亚 电影| 黑人巨大精品欧美一区二区三区 | 国产精品三级| 午夜大片在线观看| 亚洲午夜精品在线| 精品三级久久久久久久电影聊斋| 成人激情春色网| 在线观看日韩av电影| 日本一区二区视频在线播放| 日韩一级免费一区| 欧美黑人疯狂性受xxxxx野外| 亚洲欧洲另类精品久久综合| 成人免费三级在线| 中文在线最新版天堂| 欧美激情视频网| 日韩88av| 在线精品一区二区三区| 欧美人xxxx| 在线手机中文字幕| 女人被男人躁得好爽免费视频| 久久夜色精品一区| www.五月激情| 国产精品视频资源| 亚洲理论在线|