一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - 使用Python的Django框架實現事務交易管理的教程

使用Python的Django框架實現事務交易管理的教程

2020-06-09 09:59Python教程網 Python

這篇文章主要介紹了使用Python的Django框架實現事務交易管理的教程,針對數據庫的事務行為進行一系列操作,要的朋友可以參考下

 如果你花費了很多的時間去進行Django數據庫事務處理的話,你將會了解到這是讓人暈頭轉向的。

在過去,只是提供了簡單的基礎文檔,要想清楚知道它是怎么使用的,還必須要通過創(chuàng)建和執(zhí)行Django的事務處理。

這里有眾多的Django事務處理的名詞,例如:commit_on_success , commit_manually , commit_unless_maneged,rollback_unless_managed,enter_transaction_management,leace_transaction_management,這些只是其中的一部分而已。

最讓人感到幸運的是,Django 1.6版本發(fā)布后。現在你可以緊緊使用幾個函數就可以實現事務處理了,在我們進入學習這些函數的幾秒之前。首先,我們要明確下面這些問題:

  •     什么是事務處理呢?
  •     Django 1.6中的出現了那些新的事物處理優(yōu)先權?

在進入“要怎么才是Django 1.6版本中正確的事務處理”之前,請先了解一下下面的詳細案例:

    帶狀案例

    事務

  •             推薦的方式
  •             使用分隔符
  •             每一個HTTP請求事務

    保存點

    嵌套的事務


事務是什么?

根據SQL-92所說,"一個SQL事務(有時簡稱為事務)是一個可以原子性恢復的SQL語句執(zhí)行序列"。換句話說,所有的SQL語句一起執(zhí)行和提交。同樣的,當回滾時,所有語句也一并回滾。

例如:
 

?
1
2
3
4
5
6
# START
note = Note(title="my first note", text="Yay!")
note = Note(title="my second note", text="Whee!")
address1.save()
address2.save()
# COMMIT

所以一個事務是 數據庫中單個的工作單元。它是由一個start transaction開始和一個commit或者顯式的rollback結束。

Django 1.6之前的事務管理有什么問題?

為了完整地回答這個問題,我們必須闡述一下事務在數據庫、客戶端以及Django中是如何處理的。

數據庫

數據庫中的每一條語句都運行在一個事務中,這個事務甚至可以只包含一條語句。

幾乎所有的數據庫都有一個AUTOCOMMIT設置,通常它被默認設置為True。AUTOCOMMIT將所有語句包裝到一個事務里,只要語句成功執(zhí)行,這個事務就立即被提交。當然你也可以手動調用START_TRANSACTION,它會暫時將AUTOCOMMIT掛起,直到你調用COMMIT_TRANSACTION或者ROLLBACK。

然后,這種方式將會使AUTOCOMMIT設置的作用于每條語句后的隱式提交失效。


然而,有諸如像sqlite3和mysqldb的python客戶端庫,它允許python程序與數據庫本身相連接。這些庫遵循一套如何訪問與查詢數據庫的標準。該DB API 2.0標準,被描述在PEP 249之中。雖然它可能讓人閱讀稍干一些。一個重要帶走的是,在PEP 249狀態(tài)之中,默認數據庫應該關閉自動提交功能。

這明顯與數據庫內發(fā)生了什么矛盾沖突:

  •     數據庫語句總是要在一個事務中運行。此數據庫一般會為你打開自動提交功能。
  •     不過,根據PEP 249,這不應該發(fā)生。
  •     客戶端庫必須反映在數據庫之中發(fā)生了什么?但由于他們不允許默認打開自動提交功能。他們只是簡單地在一個事務中包裹sql語句。就像數據庫。

好啦,在我身邊呆久一些吧。

 

Django

進入Django,Django也有關于事務處理的話要說。在Django 1.5和更早的版本。當你寫數據到數據庫時,Django基本上是運行一個開放的事務和自動提交該事務功能。所以每次你所稱謂的像諸如model.save() 或者model.update()的東西,Django生成相應的sql語句,并提交該事務。

也有在Django 1.5和更早的版本,它是建議你使用TransactionMiddleware綁定http請求事務。每個請求提供了一個事務。如果返回的響應沒有異常,Django會提交此事務。但如果你的視圖功能拋出一個錯誤,回滾將被調用。這實際上說明,它關閉了自動提交功能。如果你想要標準化,數據庫級別自動提交風格式的事務管理,你必須管理你自己的交易-通常是通過使用事務裝飾你的視圖功能,例如@transaction.commit_manually,或者@transaction.commit_on_success.

吸一口氣,或者兩口。


這意味著什么?

是啊,在那兒有許多事情要做,而事實證明,大多數開發(fā)者正需要這個標準數據庫級的自動提交功能-有意義的事務往往是留在幕后處理的。做你自己的事,直到你需要手動調整他們。

在Django 1.6版本之中,什么是正確關于事務管理呢?

現在,歡迎來到Django 1.6.盡力忘掉一切吧,我們只是談論而已,只是記得在Django 1.6中,你可以使用數據庫,需要時可以手動自動提交和管理事務。從本質上來說,我們有一個更簡單的模型,基本上是要把設計什么樣的數據庫擺在首位。

好啦!大功告成,讓我們寫代碼吧?

 

Stripe案例

下面,我們使用處理一個用戶注冊的例子,調用了Stripe來處理信用卡進程。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def register(request):
  user = None
  if request.method == 'POST':
    form = UserForm(request.POST)
    if form.is_valid():
 
      customer = Customer.create("subscription",
       email = form.cleaned_data['email'],
       description = form.cleaned_data['name'],
       card = form.cleaned_data['stripe_token'],
       plan="gold",
      )
 
      cd = form.cleaned_data     
      try:
        user = User.create(cd['name'], cd['email'], cd['password'],
          cd['last_4_digits'])
 
        if customer:
          user.stripe_id = customer.id
          user.save()
        else:
          UnpaidUsers(email=cd['email']).save()
 
      except IntegrityError:
        form.addError(cd['email'] + ' is already a member')
      else:
        request.session['user'] = user.pk
        return HttpResponseRedirect('/')
 
  else:
   form = UserForm()
 
  return render_to_response(
    'register.html',
    {
     'form': form,
     'months': range(1, 12),
     'publishable': settings.STRIPE_PUBLISHABLE,
     'soon': soon(),
     'user': user,
     'years': range(2011, 2036),
    },
    context_instance=RequestContext(request)
  )

例子首先調用了Customer.create,實際上就是調用Stripe來處理信用卡進程,然后我們創(chuàng)建一個新用戶。如果我們得到來自Stripe的響應,我們就用stripe_id更新新創(chuàng)建的用戶。如果我們沒有得到響應(Stripe已關閉),我們將用新創(chuàng)建用戶的email向UnpaidUsers表增加一個新條目,這樣我們可以讓他們稍后重試他們信用卡信息。


思路是這樣的:如果Stripe沒有響應,用戶依然可以注冊,然后開始使用我們的網站。我們將在稍后的時候讓用戶提供信用卡的信息。

“我明白這是一個特殊的例子,并且這也不是我想完成的功能的方式,但是它的目的是展示交易”

考慮交易,牢記住在Django1.6中提供了對于數據庫的“AUTOCOMMIT”功能。接下來看一下數據庫相關的代碼:
 

?
1
2
3
4
5
6
7
8
9
10
11
cd = form.cleaned_data
try:
  user = User.create(cd['name'], cd['email'], cd['password'], cd['last_4_digits'])
 
  if customer:
    user.stripe_id = customer.id
    user.save()
  else:
    UnpaidUsers(email=cd['email']).save()
 
except IntegrityError:

你能發(fā)現問題了嗎?如果“UnpaidUsers(email=cd['email']).save()” 運行失敗,會發(fā)生什么?

有一個用戶,注冊了系統(tǒng);然后系統(tǒng)認為已經核對過信用卡了。但是事實上,系統(tǒng)并沒有核對過。

我們僅僅想得到其中一種結果:

1.在數據庫中創(chuàng)建了用戶,并有了stripe_id

2.在數據庫中創(chuàng)建了用戶,但是沒有stripe_id。同時在相關的“UnpaidUsers”行,存有相同的郵件地址

這就意味著,我們想要的是分開的數據庫語句頭完成任務或者回滾。這個例子很好的說明了這個交易。


首先,我們寫一些測試用例來驗證事情是否按照我們想象的方式運行: 
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@mock.patch('payments.models.UnpaidUsers.save', side_effect = IntegrityError)
def test_registering_user_when_strip_is_down_all_or_nothing(self, save_mock):
 
  #create the request used to test the view
  self.request.session = {}
  self.request.method='POST'
  self.request.POST = {'email' : '[email protected]',
             'name' : 'pyRock',
             'stripe_token' : '...',
             'last_4_digits' : '4242',
             'password' : 'bad_password',
             'ver_password' : 'bad_password',
            }   
 
  #mock out stripe and ask it to throw a connection error
  with mock.patch('stripe.Customer.create', side_effect =
          socket.error("can't connect to stripe")) as stripe_mock:
 
    #run the test
    resp = register(self.request)
 
    #assert there is no record in the database without stripe id.
    users = User.objects.filter(email="[email protected]")
    self.assertEquals(len(users), 0)
 
    #check the associated table also didn't get updated
    unpaid = UnpaidUsers.objects.filter(email="[email protected]")
    self.assertEquals(len(unpaid), 0)

當我們嘗試去保存“UnpaidUsers”,測試上方的解釋器就會跑出異常'IntegrityError' 。

接下來是解釋這個問題的答案,“當“UnpaidUsers(email=cd['email']).save()”運行的時候到底發(fā)生了什么?” 下面一段代碼創(chuàng)建了一段對話,我們需要在注冊函數中給出一些合適的信息。然后“with mock.patch” 會強制系統(tǒng)去認為Stripe沒響應,最終就跳到我們的測試用例中。

?
1
resp = register(self.request)

上面這段話僅僅是調用我們的注冊視圖去傳遞請求。然后我們僅僅需要去核對表是否有更新:
 

?
1
2
3
4
5
6
7
#assert there is no record in the database without stripe_id.
users = User.objects.filter(email="[email protected]")
self.assertEquals(len(users), 0)
 
#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="[email protected]")
self.assertEquals(len(unpaid), 0)

所以如果我們運行了測試用例,那么它就該運行失敗:
 

?
1
2
3
4
5
6
7
8
9
10
11
======================================================================
FAIL: test_registering_user_when_strip_is_down_all_or_nothing (tests.payments.testViews.RegisterPageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/j1z0/.virtualenvs/django_1.6/lib/python2.7/site-packages/mock.py", line 1201, in patched
  return func(*args, **keywargs)
 File "/Users/j1z0/Code/RealPython/mvp_for_Adv_Python_Web_Book/tests/payments/testViews.py", line 266, in test_registering_user_when_strip_is_down_all_or_nothing
  self.assertEquals(len(users), 0)
AssertionError: 1 != 0
 
----------------------------------------------------------------------

贊。這就是我們最終想要的結果。

記住:我們這里已經練習了“測試驅動開發(fā)”的能力。錯誤信息提示我們:用戶信息已經被保存到數據庫中,但是這個并不是我們想要的,因為我們并沒有付費!

事務交易用于挽救這樣問題 ...

事務

對于Django1.6,有很多種方式來創(chuàng)建事務。

這里簡單介紹幾種。
推薦的方法

依據Django1.6的文檔,“Django提供了一種簡單的API去控制數據庫的事務交易...原子操作用來定義數據庫事務的屬性。原子操作允許我們在數據庫保證的前提下,創(chuàng)建一堆代碼。如果這些代碼被成功的執(zhí)行,所對應的改變也會提交到數據庫中。如果有異常發(fā)生,那么操作就會回滾。”

原子操作可以被用于解釋操作或者是內容管理。所以如果我們用作為內容管理的時候,我們的注冊函數的代碼就會如下:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.db import transaction
 
try:
  with transaction.atomic():
    user = User.create(cd['name'], cd['email'], cd['password'], cd['last_4_digits'])
 
    if customer:
      user.stripe_id = customer.id
      user.save()
    else:
      UnpaidUsers(email=cd['email']).save()
 
except IntegrityError:
  form.addError(cd['email'] + ' is already a member')

注意在“with transaction.atomic()”這一行。這塊代碼將會在事務內部執(zhí)行。所以如果我們重新運行了我們的測試,他們都將會通過。

記住:事務是一個工作單元,所以當“UnpaidUsers”調用失敗的時候,內容管理的所有操作都會被一起回滾。

使用裝飾器

除了上面的做法,我們能使用Python的裝飾器特性來使用事務。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@transaction.atomic():
def register(request):
  ...snip....
 
  try:
    user = User.create(cd['name'], cd['email'], cd['password'], cd['last_4_digits'])
 
    if customer:
      user.stripe_id = customer.id
      user.save()
    else:
        UnpaidUsers(email=cd['email']).save()
 
  except IntegrityError:
    form.addError(cd['email'] + ' is already a member')

如果我們重新跑一次測試,那還是會一樣失敗。

為啥呢?為啥事務沒有正確回滾呢?原因在與transaction.atomic會嘗試捕獲某種異常,而我們代碼里人肉捕抓了(例如 try-except 代碼塊里的IntegrityError 異常),所以 transaction.atomic 永遠看不到這個異常,所以標準的AUTOCOMMIT 流程就此無效掉。

但是,刪掉try-catch語句會導致異常沒有捕獲,然后代碼流程十有八九會就此亂掉。所以啊,也就是說不能去掉try-catch。
 


所以,技巧是將原子上下文管理器放入我們在第一個解決方案中的 try-catch 代碼段里。

再看一下正確的代碼:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.db import transaction
 
try:
  with transaction.atomic():
    user = User.create(cd['name'], cd['email'], cd['password'], cd['last_4_digits'])
 
    if customer:
      user.stripe_id = customer.id
      user.save()
    else:
      UnpaidUsers(email=cd['email']).save()
 
except IntegrityError:
  form.addError(cd['email'] + ' is already a member')

當 UnpaidUsers 觸發(fā) IntegrityError 時,上下文管理器 transaction.atomic() 會捕獲到它,并執(zhí)行回滾操作。此時我們的代碼在異常處理中執(zhí)行(即行 theform.addErrorline),將會完成回滾操作,并且,如果必要的話,也可以安全的進行數據庫調用。也要注意:任何在上下文管理器 thetransaction.atomic() 前后的數據庫調用都不會受到它的執(zhí)行結果的影響。

針對每次HTTP請求的事務交易

Django1.5和1.6版本都允許用戶操作請求事務模式。在這種模式下,Django會自動在事務中,處理你的視圖函數。如果視圖函數拋出異常,Django會自動回滾事務;否則Django會提交事務。

為了實現這個功能,你需要在你想要有此功能的數據庫的配置中,設置“ATOMIC_REQUEST”為真。所以在我們的“settings.py”需要有如下設置:
 

?
1
2
3
4
5
6
7
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': os.path.join(SITE_ROOT, 'test.db'),
    'ATOMIC_REQUEST': True,
  }
}

如果我們把解釋器放到視圖函數中,以上設置就會生效。所以這并沒有符合我們的想法。

但是,這里依然值得注意的是解釋器:“ATOMIC_REQUESTS”和“@transaction.atomic”仍然會有可能在有異常拋出的時候,處理這些錯誤。為了去捕捉這些錯誤,你需要去完成一些常規(guī)的中間件,或者需要去覆蓋“urls.hadler500”,或者是創(chuàng)建新的“500.html”模板。

保存點

盡管事務是有原子性的,但還是能夠打散為多個“保存點”——你可看作是“部分事務”。

例如,你有個事務包含了4條SQL語句,你可以在第二個SQL之后創(chuàng)建一個保存點。一旦保存點創(chuàng)建成功,就算第三條或第四條SQL執(zhí)行失敗,你仍舊能夠做一個部分回滾,忽視后面兩條SQL,僅保留前面兩條。

基本上,這就像是提供了一個切割的能力:一個普通的事務能夠被切分為多個更“輕量”的事務,然后能進行部分回滾或部分提交。

    但一定要注意,小心整個事務被無端回滾掉(例如由于拋出了IntegrityError異常但卻沒有捕抓,那所有的保存點都會因此被回滾掉的)

來看個示例代碼,了解怎么玩轉保存點。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@transaction.atomic()
def save_points(self,save=True):
 
  user = User.create('jj','inception','jj','1234')
  sp1 = transaction.savepoint()
 
  user.name = 'zheli hui guadiao, T.T'
  user.stripe_id = 4
  user.save()
 
  if save:
    transaction.savepoint_commit(sp1)
  else:
    transaction.savepoint_rollback(sp1)

示例中,整個函數都是屬于一個事務的。新User對象創(chuàng)建后,我們創(chuàng)建并得到了一個保存點。然后后續(xù)3行語句——
 

?
1
2
3
user.name = 'zheli hui guadiao, T.T'
user.stripe_id = 4
user.save()

——不屬于剛才的保存點,因此他們有可能是屬于下面的savepoint_rollback或savepoint_commit的一部分。假設是savepoint_rollback, 那代碼行user = User.create('jj','inception','jj','1234')仍舊會成功提交到數據庫中 ,而下面三行則不會成功。
 

采用另外一種方法,下面的兩種測試用例描述了保存點是如何工作的:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def test_savepoint_rollbacks(self):
 
  self.save_points(False)
 
  #verify that everything was stored
  users = User.objects.filter(email="inception")
  self.assertEquals(len(users), 1)
 
  #note the values here are from the original create call
  self.assertEquals(users[0].stripe_id, '')
  self.assertEquals(users[0].name, 'jj')
 
 
def test_savepoint_commit(self):
  self.save_points(True)
 
  #verify that everything was stored
  users = User.objects.filter(email="inception")
  self.assertEquals(len(users), 1)
 
  #note the values here are from the update calls
  self.assertEquals(users[0].stripe_id, '4')
  self.assertEquals(users[0].name, 'starting down the rabbit hole')

同樣,在我們提交或者回滾保存點之后,我們仍然可以繼續(xù)在同一個事務中工作。同時,這個運行結果不受之前保存點輸出結果的影響。

例如,如果我們按照如下例子更新“save_points”函數,

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@transaction.atomic()
def save_points(self,save=True):
 
  user = User.create('jj','inception','jj','1234')
  sp1 = transaction.savepoint()
 
  user.name = 'starting down the rabbit hole'
  user.save()
 
  user.stripe_id = 4
  user.save()
 
  if save:
    transaction.savepoint_commit(sp1)
  else:
    transaction.savepoint_rollback(sp1)
 
  user.create('limbo','illbehere@forever','mind blown',
      '1111')

即使無論是“savepoint_commit”或者“savepoint_rollback”被“limbo”這個用戶調用了,這個事務仍然會被成功創(chuàng)建。如果沒有創(chuàng)建成功,整個事務將會被回滾。

嵌套事務

采用“savepoint()”,“savepoint_commit”和“savepoint_rollback”去手動指定保存點,將會自動一個嵌套事務,同時這個嵌套事務會自動為我們創(chuàng)建一個保存點。并且,如果我們遇到錯誤,這個事務將會回滾。

下面用一個擴展的例子來說明:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@transaction.atomic()
def save_points(self,save=True):
 
  user = User.create('jj','inception','jj','1234')
  sp1 = transaction.savepoint()
 
  user.name = 'starting down the rabbit hole'
  user.save()
 
  user.stripe_id = 4
  user.save()
 
  if save:
    transaction.savepoint_commit(sp1)
  else:
    transaction.savepoint_rollback(sp1)
 
  try:
    with transaction.atomic():
      user.create('limbo','illbehere@forever','mind blown',
          '1111')
      if not save: raise DatabaseError
  except DatabaseError:
    pass

這里我們可以看到:在我們處理保存點之后,我們采用“thetransaction.atomic”的上下文管理區(qū)擦出我們創(chuàng)建的"limbo"這個用戶。當上下文管理被調用的時候,它會創(chuàng)建一個保存點(因為我們已經在事務里面了),同時這個保存點將會依據已經存在的上下文管理器去被執(zhí)行或者回滾。

這樣下面兩個測試用例就描述了這個行文:

 
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def test_savepoint_rollbacks(self):
 
     self.save_points(False)
 
    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)
 
    #savepoint was rolled back so we should have original values
    self.assertEquals(users[0].stripe_id, '')
    self.assertEquals(users[0].name, 'jj')
 
    #this save point was rolled back because of DatabaseError
    limbo = User.objects.filter(email="illbehere@forever")
    self.assertEquals(len(limbo),0)
 
  def test_savepoint_commit(self):
    self.save_points(True)
 
    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)
 
    #savepoint was committed
    self.assertEquals(users[0].stripe_id, '4')
    self.assertEquals(users[0].name, 'starting down the rabbit hole')
 
    #save point was committed by exiting the context_manager without an exception
    limbo = User.objects.filter(email="illbehere@forever")
    self.assertEquals(len(limbo),1)


因此,在現實之中你可以使用原子或者在事務之中創(chuàng)建保存點的保存點。使用原子,你不必要很仔細地擔心提交和會滾,當這種情況發(fā)生時,你可以完全控制其中的保存點。
結論

如果你有任何以往使用Django更早版本事務處理的經驗,你可以看到很多更簡單地事務處理模型。如下,在默認情況下,也有自動提交功能,它是一個很好的例子,即Django與python兩者都引以為豪所提供的“理智的”默認值。對于如此多的系統(tǒng),你將不需要直接地來處理事務。只是讓“自動提交功能”來完成其工作,但如果你這樣做,我將希望這個帖子能提供你所需要像專家一樣在Django之中管理的事務處理。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 我和老丈洗澡同性 | 富士av105| 欧美日韩高清观看一区二区 | 精品一区二区三区中文 | 亚洲美女aⅴ久久久91 | 深夜福利免费观看 | 国产伦精一区二区三区视频 | 久久综久久美利坚合众国 | 丝瓜香蕉视频 | 亚洲AV无码乱码在线观看浪潮 | 国产精品美女福利视频免费专区 | 玩乳h文奶水和尚 | 扒开女人下面使劲桶屁股动漫 | 高清视频免费 | 四虎黄色影视库 | 99热这里只有精品国产免费 | 国偷盗摄自产福利一区在线 | 四虎影院永久网站 | 亚洲免费国产 | 日本高清视频在线免费观看 | 538亚洲欧美国产日韩在线精品 | 动漫jk美女被爆羞羞漫画 | 国内自拍网红在综合图区 | 成年无限观看onlyfans | 国产一级真人毛爱做毛片 | 国产精品一区二区三区免费 | 亚洲区精品久久一区二区三区 | 成人18视频在线观看 | 三星w999 | 99久久免费国产特黄 | 久久亚洲精品AV无码四区 | 色先锋 影音先锋a 资源站 | 国产精品主播在线 | 天天射久久 | 天天综合天天色 | 国产成人精品本亚洲 | kk4444在线影视播放 | 女人pp被扒开流水了 | 丝瓜草莓香蕉绿巨人幸福宝 | 国产91在线免费 | 国产欧美曰韩一区二区三区 |