精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

午夜看片在线免费| 国产精品suv一区二区69| 国产激情精品一区二区三区| 亚洲自拍另类综合| 欧美高清性xxxxhd | 性欧美ⅴideo另类hd| 99久久久无码国产精品| 国产精品丝袜视频| 成年人午夜视频| 五月精品视频| 日韩国产在线看| 午夜影院免费观看视频| 韩日成人影院| 亚洲成人精品一区| 在线播放豆国产99亚洲| 牛牛热在线视频| 成人免费视频免费观看| 成人高清视频观看www| 国产精品视频一区在线观看| 综合久久久久| 色阁综合伊人av| 37p粉嫩大胆色噜噜噜| 国产中文欧美日韩在线| 欧美三日本三级三级在线播放| 国产玉足脚交久久欧美| 在线视频自拍| 国产欧美日韩激情| 久久精品国产精品青草色艺| 精品人妻无码一区二区| 麻豆精品在线视频| 日韩av片免费在线观看| 国产69精品久久久久久久久久| www.99riav| 六十路在线观看| 成人国产一区二区三区精品| 亚洲国产精品麻豆| 国产在线拍揄自揄拍无码| 电影在线一区| 久久综合狠狠综合久久激情 | 超碰97av在线| 美女av一区| 亚洲成人精品在线| 国产日韩视频一区| 中文字幕日韩在线| 欧美岛国在线观看| 亚洲乱妇老熟女爽到高潮的片 | 国产精品有限公司| 丁香六月天婷婷| 懂色av一区二区在线播放| 亚洲精品日韩av| 国产成人av免费看| 国产高清一区日本| 高清视频一区| 欧美熟妇交换久久久久久分类 | 欧美一区二区三区婷婷月色| 91精产国品一二三产区别沈先生| 未满十八勿进黄网站一区不卡| 欧美日韩精品系列| 日韩av影视大全| 亚洲精品国产九九九| 精品国产一区久久| 国产网站无遮挡| 国产成人1区| 最近2019免费中文字幕视频三| 亚洲一级片在线播放| 91嫩草亚洲精品| 美女视频黄免费的亚洲男人天堂| 久久免费小视频| 国产情侣一区| 国产精品美女免费| 国产精品久久久久久免费| 国产精品一区一区三区| 国产精品播放| 国产高清视频在线播放| 亚洲欧美在线视频| 日韩美女爱爱视频| 国产精品伦理| 欧美精品一级二级| 亚洲成年人av| 欧美视频网址| 色综合久久精品亚洲国产| 日韩污视频在线观看| 日韩高清一级片| 亚洲一区二区三区香蕉| 五月婷婷丁香网| 日本一区二区三区在线不卡| 久久天天东北熟女毛茸茸| av资源网在线播放| 欧美日韩中文字幕一区| 99久久久无码国产精品性波多| 婷婷成人在线| 久久亚洲春色中文字幕| 五月婷婷视频在线| 国产一区二区中文字幕| 欧美日韩国产免费一区二区三区| 久热国产在线| 一本色道综合亚洲| 亚洲精品一二三四| 欧美日韩激情在线一区二区三区| 色综合天天狠天天透天天伊人| 无码人妻丰满熟妇区bbbbxxxx| 国产精品一区二区男女羞羞无遮挡| 久久99精品国产一区二区三区| 老司机av在线免费看| 欧美日韩人人澡狠狠躁视频| 三日本三级少妇三级99| 精品久久国产| 97香蕉超级碰碰久久免费的优势| 91精品国产乱码久久久| 久久综合九色综合97婷婷| 成人性做爰片免费视频| av高清一区| 日韩精品在线免费| 精品无码久久久久久久久| 欧美bbbbb| 久久天天狠狠| 超碰97国产精品人人cao| 欧美精品精品一区| 手机看片福利视频| 国产模特精品视频久久久久| 成人在线视频电影| a毛片在线看免费观看| 欧美色视频在线| 无码一区二区三区在线| 国产日韩高清一区二区三区在线| 亚洲伊人久久综合| 免费黄色在线| 欧美日韩激情一区| 国产伦精品一区二区三区视频女| 99综合在线| 成人欧美大片| 欧美一区二区视频网站| 波多野结衣家庭教师在线观看 | www.国产在线视频| 国产一区一区| 九九久久久久久久久激情| 一二三四区在线| 欧美国产日韩a欧美在线观看| 18禁男女爽爽爽午夜网站免费| 精品无人区一区二区| 久久久久久久久久久91| 亚洲成a人片在线| 伊人一区二区三区| 久久黄色一级视频| 欧美日韩少妇| 国产高清在线精品一区二区三区| 伊人电影在线观看| 日韩视频免费观看高清完整版 | 亚洲国产1区| 国产经品一区二区| 白白色在线观看| 亚洲精品国产欧美| 日韩 欧美 中文| 91麻豆福利精品推荐| 成年人视频网站免费观看| 日韩系列在线| 国产91精品最新在线播放| 国产特黄在线| 欧美日韩亚洲另类| 婷婷久久综合网| 国产精品123| 国产日本在线播放| 婷婷成人在线| 国产日韩欧美另类| 国产黄大片在线观看画质优化| 日韩久久久精品| 日韩和一区二区| 久久精品视频在线看| 亚洲一区二区三区四区五区| 欧美一区精品| 久久久综合香蕉尹人综合网| 国产成人精品一区二区三区视频 | 亚洲激情国产精品| 亚洲成人第一网站| 一区视频在线播放| 欧美做受高潮中文字幕| 久久一综合视频| 中文字幕超清在线免费观看| av在线亚洲色图| 欧美一区二区三区成人久久片| 一区二区亚洲欧洲国产日韩| 自拍偷拍第八页| 中文字幕亚洲视频| 无码人妻一区二区三区精品视频| 亚洲九九精品| 神马影院午夜我不卡| 欧美高清hd| 日韩av免费在线| 91精品久久久久久粉嫩| 亚洲精选中文字幕| 国产精品无码白浆高潮| 懂色av一区二区三区| 99热6这里只有精品| jizz一区二区| 亚洲另类第一页| 亚洲国产激情| 亚洲激情啪啪| 日韩av网站在线免费观看| 国产精品一区二区久久久久| 高清在线视频不卡| 久久久999精品视频| 日本一区高清| 日韩一区二区在线看| 无码人妻久久一区二区三区不卡| 亚洲精品国产无套在线观| 久久久久亚洲av成人无码电影| 高清日韩电视剧大全免费| 中文久久久久久| 99国产精品视频免费观看一公开| 伊人久久大香线蕉精品| 午夜先锋成人动漫在线| 97人人模人人爽人人少妇| 日韩制服一区| 日本成人激情视频| av成人福利| 久久视频在线看| 超碰在线影院| 国产视频在线观看一区二区| 高清乱码毛片入口| 在线成人午夜影院| 国产免费www| 日韩欧美国产视频| 国产主播在线观看| 一区二区三区小说| 我要看黄色一级片| 国产精品久久777777| 亚洲综合网在线观看| 99久久精品免费看国产免费软件| 在线观看日本www| 久久国产婷婷国产香蕉| 网站一区二区三区| 日韩电影免费一区| 国产熟人av一二三区| 在线亚洲伦理| 欧美女人性生活视频| 日韩视频久久| 奇米精品一区二区三区| 欧美午夜免费影院| 大地资源网在线观看免费官网| 欧美电影免费观看高清| 五月天色一区| 欧美大片aaaa| 一级日韩一区在线观看| 久久人人99| 在线看无码的免费网站| 天天综合网91| 麻豆一区二区三区在线观看| 中文字幕人成人乱码| 天天在线免费视频| 综合久久亚洲| 欧美亚洲黄色片| 激情一区二区| 国产超级av在线| 天堂久久久久va久久久久| 日本999视频| 另类小说视频一区二区| 91亚洲一区二区| 成人免费视频caoporn| 中文字幕一区二区久久人妻网站| 26uuu精品一区二区| 久久久久亚洲av无码a片| 国产精品嫩草久久久久| 动漫性做爰视频| 亚洲不卡一区二区三区| 亚洲成人第一网站| 911国产精品| 狠狠躁夜夜躁av无码中文幕| 日韩精品极品在线观看播放免费视频| 黄色av网址在线免费观看| 日韩在线观看免费全集电视剧网站| 成视频免费观看在线看| 久久久之久亚州精品露出| 欧美成人精品一区二区男人小说| 国产精品狼人色视频一区| 精品一区二区三区免费看| 精品无人区一区二区三区竹菊| 精品国产一区二区三区久久久樱花 | 激情视频在线播放| 懂色aⅴ精品一区二区三区蜜月| 中文字幕乱码一区二区| 日韩欧美二区三区| 美女欧美视频在线观看免费 | 日韩在线 中文字幕| 在线综合亚洲欧美在线视频| 欧美一级片免费| 深夜福利91大全| h片在线观看视频免费免费| 国产精品日韩精品| 国产一级成人av| 一区二区不卡在线观看| 亚洲美女视频在线免费观看| 日本人视频jizz页码69| 北岛玲一区二区三区四区| 国产精品一区二区亚洲| 五月综合激情网| 97人人爽人人爽人人爽| 亚洲人在线视频| 色呦呦在线视频| 国产精品电影在线观看| 国产66精品| 在线视频91| 性生交生活影碟片| 亚洲视频一二三区| 日本中文字幕第一页| 日韩午夜在线观看视频| 国产网站在线播放| 韩国国内大量揄拍精品视频| 91成人app| 亚洲国产精品视频一区| 西西裸体人体做爰大胆久久久| 久久av一区二区三| 中文字幕一区二区在线播放| 精品国产乱子伦| 精品亚洲一区二区三区在线观看 | 91热福利电影| 成人a'v在线播放| 成人一级片网站| 99精品欧美一区二区三区综合在线| av激情在线观看| 欧美夫妻性生活| 91在线观看| 日本一区二区三区四区视频| 老汉色老汉首页av亚洲| 91黄色在线看| 国产v综合v亚洲欧| 欧美日韩精品在线观看视频| 欧美美女直播网站| 在线免费观看黄| 国产日韩av在线| 日韩欧美自拍| 黄色三级视频在线| 久久久久免费观看| 亚洲欧美综合自拍| 亚洲男子天堂网| 中文字幕在线直播| 欧美一二三四五区| 天堂影院一区二区| 国产精品亚洲无码| 色婷婷综合激情| 成人高清在线| 国产精品最新在线观看| 99热在线成人| www.国产福利| 一区二区视频在线| 性做久久久久久久久久| 欧美激情精品久久久久久| 第四色中文综合网| 国产真人做爰毛片视频直播| 成+人+亚洲+综合天堂| 日韩特级黄色片| 亚洲欧美色图片| 日韩欧美精品一区二区综合视频| 亚洲三区视频| 国产一区视频网站| 久久亚洲精品大全| 日韩精品欧美国产精品忘忧草| 亚洲精品中文字幕| 性欧美精品一区二区三区在线播放| 久久精品99国产精品日本| 九九热最新地址| 欧美成人女星排名| 蜜桃视频在线观看播放| 婷婷亚洲婷婷综合色香五月| 精品一区二区免费看| 久久久久亚洲AV| 日韩久久精品成人| 成人黄色免费网站| 8x8ⅹ国产精品一区二区二区| 成人精品高清在线| 无码人妻aⅴ一区二区三区有奶水| 色av中文字幕一区| jizz国产精品| av动漫免费看| 中文字幕一区二区三区色视频| 高h调教冰块play男男双性文| 日韩av电影在线免费播放| 久久久久电影| 国产视频久久久久久| 欧美日韩国产免费| 国产丝袜在线播放| 四虎永久在线精品免费一区二区| 国产麻豆午夜三级精品| www.国产色| 欧美精品一区二区免费| 国产成人影院| 下面一进一出好爽视频| 色一区在线观看| 99视频免费在线观看| 日本不卡一区二区三区在线观看| 韩国v欧美v日本v亚洲v| 欧美性猛交bbbbb精品| 欧美www在线| 欧州一区二区| 精品熟女一区二区三区| 欧美人与性动xxxx| 成人亚洲欧美| 国产精品视频网站在线观看| 国产精品天天摸av网| 国产精品国产高清国产| 亚洲综合大片69999|