色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Controller層代碼就該這么寫

jf_ro2CN3Fa ? 來源:掘金 gelald ? 作者:掘金 gelald ? 2022-11-21 10:28 ? 次閱讀

一個優秀的 Controller 層邏輯

從現狀看問題

改造 Controller 層邏輯

統一返回結構

統一包裝處理

參數校驗

自定義異常與統一攔截異常

總結

一個優秀的 Controller 層邏輯

說到 Controller,相信大家都不陌生,它可以很方便地對外提供數據接口。它的定位,我認為是「不可或缺的配角」。

說它不可或缺是因為無論是傳統的三層架構還是現在的 COLA 架構,Controller 層依舊有一席之地,說明他的必要性。

說它是配角是因為 Controller 層的代碼一般是不負責具體的邏輯業務邏輯實現,但是它負責接收和響應請求。

從現狀看問題

Controller 主要的工作有以下幾項:

接收請求并解析參數

調用 Service 執行具體的業務代碼(可能包含參數校驗)

捕獲業務邏輯異常做出反饋

業務邏輯執行成功做出響應

//DTO
@Data
publicclassTestDTO{
privateIntegernum;
privateStringtype;
}


//Service
@Service
publicclassTestService{

publicDoubleservice(TestDTOtestDTO)throwsException{
if(testDTO.getNum()<=?0)?{
????????????throw?new?Exception("輸入的數字需要大于0");
????????}
????????if?(testDTO.getType().equals("square"))?{
????????????return?Math.pow(testDTO.getNum(),?2);
????????}
????????if?(testDTO.getType().equals("factorial"))?{
????????????double?result?=?1;
????????????int?num?=?testDTO.getNum();
????????????while?(num?>1){
result=result*num;
num-=1;
}
returnresult;
}
thrownewException("未識別的算法");
}
}


//Controller
@RestController
publicclassTestController{

privateTestServicetestService;

@PostMapping("/test")
publicDoubletest(@RequestBodyTestDTOtestDTO){
try{
Doubleresult=this.testService.service(testDTO);
returnresult;
}catch(Exceptione){
thrownewRuntimeException(e);
}
}

@Autowired
publicDTOidsetTestService(TestServicetestService){
this.testService=testService;
}
}

如果真的按照上面所列的工作項來開發 Controller 代碼會有幾個問題:

參數校驗過多地耦合了業務代碼,違背單一職責原則

可能在多個業務中都拋出同一個異常,導致代碼重復

各種異常反饋和成功響應格式不統一,接口對接不友好

改造 Controller 層邏輯

統一返回結構

統一返回值類型無論項目前后端是否分離都是非常必要的,方便對接接口的開發人員更加清晰地知道這個接口的調用是否成功(不能僅僅簡單地看返回值是否為 null 就判斷成功與否,因為有些接口的設計就是如此)。

使用一個狀態碼、狀態信息就能清楚地了解接口調用情況:

//定義返回數據結構
publicinterfaceIResult{
IntegergetCode();
StringgetMessage();
}

//常用結果的枚舉
publicenumResultEnumimplementsIResult{
SUCCESS(2001,"接口調用成功"),
VALIDATE_FAILED(2002,"參數校驗失敗"),
COMMON_FAILED(2003,"接口調用失敗"),
FORBIDDEN(2004,"沒有權限訪問資源");

privateIntegercode;
privateStringmessage;

//省略get、set方法和構造方法
}

//統一返回數據結構
@Data
@NoArgsConstructor
@AllArgsConstructor
publicclassResult{
privateIntegercode;
privateStringmessage;
privateTdata;

publicstaticResultsuccess(Tdata){
returnnewResult<>(ResultEnum.SUCCESS.getCode(),ResultEnum.SUCCESS.getMessage(),data);
}

publicstaticResultsuccess(Stringmessage,Tdata){
returnnewResult<>(ResultEnum.SUCCESS.getCode(),message,data);
}

publicstaticResultfailed(){
returnnewResult<>(ResultEnum.COMMON_FAILED.getCode(),ResultEnum.COMMON_FAILED.getMessage(),null);
}

publicstaticResultfailed(Stringmessage){
returnnewResult<>(ResultEnum.COMMON_FAILED.getCode(),message,null);
}

publicstaticResultfailed(IResulterrorResult){
returnnewResult<>(errorResult.getCode(),errorResult.getMessage(),null);
}

publicstaticResultinstance(Integercode,Stringmessage,Tdata){
Resultresult=newResult<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
returnresult;
}
}

統一返回結構后,在 Controller 中就可以使用了,但是每一個 Controller 都寫這么一段最終封裝的邏輯,這些都是很重復的工作,所以還要繼續想辦法進一步處理統一返回結構。

統一包裝處理

Spring 中提供了一個類 ResponseBodyAdvice ,能幫助我們實現上述需求:

publicinterfaceResponseBodyAdvice{
booleansupports(MethodParameterreturnType,Class>converterType);

@Nullable
TbeforeBodyWrite(@NullableTbody,MethodParameterreturnType,MediaTypeselectedContentType,Class>selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse);
}

ResponseBodyAdvice 是對 Controller 返回的內容在 HttpMessageConverter 進行類型轉換之前攔截,進行相應的處理操作后,再將結果返回給客戶端。

那這樣就可以把統一包裝的工作放到這個類里面:

supports: 判斷是否要交給 beforeBodyWrite 方法執行,ture:需要;false:不需要

beforeBodyWrite: 對 response 進行具體的處理

//如果引入了swagger或knife4j的文檔生成組件,這里需要僅掃描自己項目的包,否則文檔無法正常生成
@RestControllerAdvice(basePackages="com.example.demo")
publicclassResponseAdviceimplementsResponseBodyAdvice{
@Override
publicbooleansupports(MethodParameterreturnType,Class>converterType){
//如果不需要進行封裝的,可以添加一些校驗手段,比如添加標記排除的注解
returntrue;
}


@Override
publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,Class>selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
//提供一定的靈活度,如果body已經被包裝了,就不進行包裝
if(bodyinstanceofResult){
returnbody;
}
returnResult.success(body);
}
}

經過這樣改造,既能實現對 Controller 返回的數據進行統一包裝,又不需要對原有代碼進行大量的改動。

參數校驗

Java API 的規范 JSR303 定義了校驗的標準 validation-api ,其中一個比較出名的實現是 hibernate validation。

spring validation 是對其的二次封裝,常用于 SpringMVC 的參數自動校驗,參數校驗的代碼就不需要再與業務邏輯代碼進行耦合了。

①@PathVariable 和 @RequestParam 參數校驗

Get 請求的參數接收一般依賴這兩個注解,但是處于 url 有長度限制和代碼的可維護性,超過 5 個參數盡量用實體來傳參。

對 @PathVariable 和 @RequestParam 參數進行校驗需要在入參聲明約束的注解。

如果校驗失敗,會拋出 MethodArgumentNotValidException 異常。

@RestController(value="prettyTestController")
@RequestMapping("/pretty")
publicclassTestController{

privateTestServicetestService;

@GetMapping("/{num}")
publicIntegerdetail(@PathVariable("num")@Min(1)@Max(20)Integernum){
returnnum*num;
}

@GetMapping("/getByEmail")
publicTestDTOgetByAccount(@RequestParam@NotBlank@EmailStringemail){
TestDTOtestDTO=newTestDTO();
testDTO.setEmail(email);
returntestDTO;
}

@Autowired
publicvoidsetTestService(TestServiceprettyTestService){
this.testService=prettyTestService;
}
}

校驗原理

在 SpringMVC 中,有一個類是 RequestResponseBodyMethodProcessor,這個類有兩個作用(實際上可以從名字上得到一點啟發)

用于解析 @RequestBody 標注的參數

處理 @ResponseBody 標注方法的返回值

解析 @RequestBoyd 標注參數的方法是 resolveArgument。

publicclassRequestResponseBodyMethodProcessorextendsAbstractMessageConverterMethodProcessor{
/**
*ThrowsMethodArgumentNotValidExceptionifvalidationfails.
*@throwsHttpMessageNotReadableExceptionif{@linkRequestBody#required()}
*is{@codetrue}andthereisnobodycontentorifthereisnosuitable
*convertertoreadthecontentwith.
*/
@Override
publicObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,
NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{

parameter=parameter.nestedIfOptional();
//把請求數據封裝成標注的DTO對象
Objectarg=readWithMessageConverters(webRequest,parameter,parameter.getNestedGenericParameterType());
Stringname=Conventions.getVariableNameForParameter(parameter);

if(binderFactory!=null){
WebDataBinderbinder=binderFactory.createBinder(webRequest,arg,name);
if(arg!=null){
//執行數據校驗
validateIfApplicable(binder,parameter);
//如果校驗不通過,就拋出MethodArgumentNotValidException異常
//如果我們不自己捕獲,那么最終會由DefaultHandlerExceptionResolver捕獲處理
if(binder.getBindingResult().hasErrors()&&isBindExceptionRequired(binder,parameter)){
thrownewMethodArgumentNotValidException(parameter,binder.getBindingResult());
}
}
if(mavContainer!=null){
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX+name,binder.getBindingResult());
}
}

returnadaptArgumentIfNecessary(arg,parameter);
}
}

publicabstractclassAbstractMessageConverterMethodArgumentResolverimplementsHandlerMethodArgumentResolver{
/**
*Validatethebindingtargetifapplicable.
*

Thedefaultimplementationchecksfor{@code@javax.validation.Valid}, *Spring's{@linkorg.springframework.validation.annotation.Validated}, *andcustomannotationswhosenamestartswith"Valid". *@parambindertheDataBindertobeused *@paramparameterthemethodparameterdescriptor *@since4.1.5 *@see#isBindExceptionRequired */ protectedvoidvalidateIfApplicable(WebDataBinderbinder,MethodParameterparameter){ //獲取參數上的所有注解 Annotation[]annotations=parameter.getParameterAnnotations(); for(Annotationann:annotations){ //如果注解中包含了@Valid、@Validated或者是名字以Valid開頭的注解就進行參數校驗 Object[]validationHints=ValidationAnnotationUtils.determineValidationHints(ann); if(validationHints!=null){ //實際校驗邏輯,最終會調用HibernateValidator執行真正的校驗 //所以SpringValidation是對HibernateValidation的二次封裝 binder.validate(validationHints); break; } } } }

②@RequestBody 參數校驗

Post、Put 請求的參數推薦使用 @RequestBody 請求體參數。

對 @RequestBody 參數進行校驗需要在 DTO 對象中加入校驗條件后,再搭配 @Validated 即可完成自動校驗。

如果校驗失敗,會拋出 ConstraintViolationException 異常。

//DTO
@Data
publicclassTestDTO{
@NotBlank
privateStringuserName;

@NotBlank
@Length(min=6,max=20)
privateStringpassword;

@NotNull
@Email
privateStringemail;
}

//Controller
@RestController(value="prettyTestController")
@RequestMapping("/pretty")
publicclassTestController{

privateTestServicetestService;

@PostMapping("/test-validation")
publicvoidtestValidation(@RequestBody@ValidatedTestDTOtestDTO){
this.testService.save(testDTO);
}

@Autowired
publicvoidsetTestService(TestServicetestService){
this.testService=testService;
}
}

校驗原理

聲明約束的方式,注解加到了參數上面,可以比較容易猜測到是使用了 AOP 對方法進行增強。

而實際上 Spring 也是通過 MethodValidationPostProcessor 動態注冊 AOP 切面,然后使用 MethodValidationInterceptor 對切點方法進行織入增強。

publicclassMethodValidationPostProcessorextendsAbstractBeanFactoryAwareAdvisingPostProcessorimplementsInitializingBean{

//指定了創建切面的Bean的注解
privateClassvalidatedAnnotationType=Validated.class;

@Override
publicvoidafterPropertiesSet(){
//為所有@Validated標注的Bean創建切面
Pointcutpointcut=newAnnotationMatchingPointcut(this.validatedAnnotationType,true);
//創建Advisor進行增強
this.advisor=newDefaultPointcutAdvisor(pointcut,createMethodValidationAdvice(this.validator));
}

//創建Advice,本質就是一個方法攔截器
protectedAdvicecreateMethodValidationAdvice(@NullableValidatorvalidator){
return(validator!=null?newMethodValidationInterceptor(validator):newMethodValidationInterceptor());
}
}

publicclassMethodValidationInterceptorimplementsMethodInterceptor{
@Override
publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{
//無需增強的方法,直接跳過
if(isFactoryBeanMetadataMethod(invocation.getMethod())){
returninvocation.proceed();
}

Class[]groups=determineValidationGroups(invocation);
ExecutableValidatorexecVal=this.validator.forExecutables();
MethodmethodToValidate=invocation.getMethod();
Set>result;
try{
//方法入參校驗,最終還是委托給HibernateValidator來校驗
//所以SpringValidation是對HibernateValidation的二次封裝
result=execVal.validateParameters(
invocation.getThis(),methodToValidate,invocation.getArguments(),groups);
}
catch(IllegalArgumentExceptionex){
...
}
//校驗不通過拋出ConstraintViolationException異常
if(!result.isEmpty()){
thrownewConstraintViolationException(result);
}
//Controller方法調用
ObjectreturnValue=invocation.proceed();
//下面是對返回值做校驗,流程和上面大概一樣
result=execVal.validateReturnValue(invocation.getThis(),methodToValidate,returnValue,groups);
if(!result.isEmpty()){
thrownewConstraintViolationException(result);
}
returnreturnValue;
}
}

③自定義校驗規則

有些時候 JSR303 標準中提供的校驗規則不滿足復雜的業務需求,也可以自定義校驗規則。

自定義校驗規則需要做兩件事情:

自定義注解類,定義錯誤信息和一些其他需要的內容

注解校驗器,定義判定規則

//自定義注解類
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy=MobileValidator.class)
public@interfaceMobile{
/**
*是否允許為空
*/
booleanrequired()defaulttrue;

/**
*校驗不通過返回的提示信息
*/
Stringmessage()default"不是一個手機號碼格式";

/**
*Constraint要求的屬性,用于分組校驗和擴展,留空就好
*/
Class[]groups()default{};
Class[]payload()default{};
}

//注解校驗器
publicclassMobileValidatorimplementsConstraintValidator{

privatebooleanrequired=false;

privatefinalPatternpattern=Pattern.compile("^1[34578][0-9]{9}$");//驗證手機號

/**
*在驗證開始前調用注解里的方法,從而獲取到一些注解里的參數
*
*@paramconstraintAnnotationannotationinstanceforagivenconstraintdeclaration
*/
@Override
publicvoidinitialize(MobileconstraintAnnotation){
this.required=constraintAnnotation.required();
}

/**
*判斷參數是否合法
*
*@paramvalueobjecttovalidate
*@paramcontextcontextinwhichtheconstraintisevaluated
*/
@Override
publicbooleanisValid(CharSequencevalue,ConstraintValidatorContextcontext){
if(this.required){
//驗證
returnisMobile(value);
}
if(StringUtils.hasText(value)){
//驗證
returnisMobile(value);
}
returntrue;
}

privatebooleanisMobile(finalCharSequencestr){
Matcherm=pattern.matcher(str);
returnm.matches();
}
}
,>

自動校驗參數真的是一項非常必要、非常有意義的工作。JSR303 提供了豐富的參數校驗規則,再加上復雜業務的自定義校驗規則,完全把參數校驗和業務邏輯解耦開,代碼更加簡潔,符合單一職責原則。

自定義異常與統一攔截異常

原來的代碼中可以看到有幾個問題:

拋出的異常不夠具體,只是簡單地把錯誤信息放到了 Exception 中

拋出異常后,Controller 不能具體地根據異常做出反饋

雖然做了參數自動校驗,但是異常返回結構和正常返回結構不一致

自定義異常是為了后面統一攔截異常時,對業務中的異常有更加細顆粒度的區分,攔截時針對不同的異常作出不同的響應。

而統一攔截異常的目的一個是為了可以與前面定義下來的統一包裝返回結構能對應上,另一個是我們希望無論系統發生什么異常,Http 的狀態碼都要是 200 ,盡可能由業務來區分系統的異常。

//自定義異常
publicclassForbiddenExceptionextendsRuntimeException{
publicForbiddenException(Stringmessage){
super(message);
}
}

//自定義異常
publicclassBusinessExceptionextendsRuntimeException{
publicBusinessException(Stringmessage){
super(message);
}
}

//統一攔截異常
@RestControllerAdvice(basePackages="com.example.demo")
publicclassExceptionAdvice{

/**
*捕獲{@codeBusinessException}異常
*/
@ExceptionHandler({BusinessException.class})
publicResulthandleBusinessException(BusinessExceptionex){
returnResult.failed(ex.getMessage());
}

/**
*捕獲{@codeForbiddenException}異常
*/
@ExceptionHandler({ForbiddenException.class})
publicResulthandleForbiddenException(ForbiddenExceptionex){
returnResult.failed(ResultEnum.FORBIDDEN);
}

/**
*{@code@RequestBody}參數校驗不通過時拋出的異常處理
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
publicResulthandleMethodArgumentNotValidException(MethodArgumentNotValidExceptionex){
BindingResultbindingResult=ex.getBindingResult();
StringBuildersb=newStringBuilder("校驗失敗:");
for(FieldErrorfieldError:bindingResult.getFieldErrors()){
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(",");
}
Stringmsg=sb.toString();
if(StringUtils.hasText(msg)){
returnResult.failed(ResultEnum.VALIDATE_FAILED.getCode(),msg);
}
returnResult.failed(ResultEnum.VALIDATE_FAILED);
}

/**
*{@code@PathVariable}和{@code@RequestParam}參數校驗不通過時拋出的異常處理
*/
@ExceptionHandler({ConstraintViolationException.class})
publicResulthandleConstraintViolationException(ConstraintViolationExceptionex){
if(StringUtils.hasText(ex.getMessage())){
returnResult.failed(ResultEnum.VALIDATE_FAILED.getCode(),ex.getMessage());
}
returnResult.failed(ResultEnum.VALIDATE_FAILED);
}

/**
*頂級異常捕獲并統一處理,當其他異常無法處理時候選擇使用
*/
@ExceptionHandler({Exception.class})
publicResulthandle(Exceptionex){
returnResult.failed(ex.getMessage());
}

}

總結

做好了這一切改動后,可以發現 Controller 的代碼變得非常簡潔,可以很清楚地知道每一個參數、每一個 DTO 的校驗規則,可以很明確地看到每一個 Controller 方法返回的是什么數據,也可以方便每一個異常應該如何進行反饋。這一套操作下來后,我們能更加專注于業務邏輯的開發,代碼簡介、功能完善,何樂而不為呢

,>
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Controller
    +關注

    關注

    0

    文章

    398

    瀏覽量

    57229
  • 代碼
    +關注

    關注

    30

    文章

    4857

    瀏覽量

    69526
  • spring
    +關注

    關注

    0

    文章

    340

    瀏覽量

    14574

原文標題:Controller層代碼就該這么寫,簡潔又優雅!

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何做到每天代碼?

    代碼必須是開源的,且要放到Github上(這樣強迫自己小心代碼,并會考慮代碼重用性及怎么創建項目前期的模塊)。當然,以上這些規則是靈活的。John Resig之所以制定
    發表于 04-25 19:16

    請問這個代碼怎么

    用了匿名科創的6.0版上位機 但是沒買他們的產品 所以沒有他們的代碼現在不知道怎么代碼 他們一幀數據是這么構成的 幀名稱+幀頭+發送設備地址+目標設備地址+功能字+數據長度+數據+和
    發表于 06-26 04:25

    用shell腳本curl命令調用自己代碼接口

    正文要求在頁面查詢到5000條數據,為了方便插入,用shell腳本curl命令調用自己代碼接口;**腳本如下:#!/bin/basha=0while [ $a -le 10
    發表于 10-19 14:48

    1602與51一個字母_源代碼

    1602與51一個字母_源代碼,感興趣的可以看看。
    發表于 07-19 16:55 ?2次下載

    怎樣來為armc代碼

    怎樣來為armc代碼
    發表于 10-30 10:32 ?12次下載
    怎樣來為arm<b class='flag-5'>寫</b>c<b class='flag-5'>代碼</b>

    代碼也能玩轉人工智能 Uber宣布開源Ludwig

    Uber 宣布開源 Ludwig,一個基于 TensorFlow 的工具箱,工具箱特點是不用代碼就能夠訓練和測試深度學習模型。
    的頭像 發表于 02-26 08:47 ?3064次閱讀

    使用python3的圖片壓縮代碼合集免費下載

    本文檔的主要內容詳細介紹的是使用python3的圖片壓縮代碼合集免費下載。
    發表于 03-12 08:00 ?0次下載

    程序員是怎么代碼的?常見問詳解

    騰訊程序員是怎么代碼的?,代碼,插件,sql,調用,編程
    的頭像 發表于 02-20 15:38 ?9888次閱讀

    我們如何用powerPCB設定4板的

    (power)? 4:no plane+component(如果單面放元件可以定義為no plane+route)? 注意: cam plane生成電源和地層是負片,并且不能在走線,而split
    發表于 06-18 17:34 ?973次閱讀

    怎么看懂別人的單片機項目代碼?

    記得剛開始接觸代碼的時候,總覺得很神秘,也好奇到底是怎樣的牛人,才能把這么多復雜的”天書”寫出來去。當時多希望自己一夜之間也擁有這種能力,能自己代碼去把自己的想法通過技術的手段制造出
    發表于 11-13 13:21 ?9次下載
    怎么看懂別人<b class='flag-5'>寫</b>的單片機項目<b class='flag-5'>代碼</b>?

    一個優秀的Controller邏輯

    依舊有一席之地,說明他的必要性;說它是配角是因為 Controller 代碼一般是不負責具體的邏輯業務邏輯實現,但是它負責接收和響應請求
    的頭像 發表于 08-03 10:55 ?985次閱讀

    qt用C++的2048小游戲源代碼

    qt用C++的2048小游戲源代碼
    發表于 09-27 11:48 ?3次下載

    如何把Controller代碼的更優雅?

    本篇主要要介紹的就是 controller 的處理,一個完整的后端請求由4部分組成。
    的頭像 發表于 11-01 10:09 ?1011次閱讀

    優秀的代碼都是如何分層的?看了直呼NB!

    的確在這些人眼中分層只是一個形式,前輩們的代碼這么的,其他項目代碼這么的,那么我也
    的頭像 發表于 06-09 14:39 ?690次閱讀
    優秀的<b class='flag-5'>代碼</b>都是如何分層的?看了直呼NB!

    阿里云內部全面推行AI代碼

    阿里云正在內部全面推行 AI 編程,使用通義靈碼輔助程序員代碼、讀代碼、查 BUG、優化代碼等。
    的頭像 發表于 04-07 09:22 ?631次閱讀
    主站蜘蛛池模板: 亚洲大片免费 | 偷窥wc美女毛茸茸视频 | 亚洲精品乱码久久久久久v 亚洲精品乱码电影在线观看 | 九九热这里都是精品 | 受被三个攻各种道具PLAY | 国产午夜精品一区二区 | WWW国产无套内射久久 | 亚洲精品一本之道高清乱码 | 99精品视频在线免费观看 | 日本边添边摸边做边爱边 | 色欲人妻无码AV专区 | 亚洲福利网站 | 亚洲不卡一卡2卡三卡4卡5卡 | 电影果冻传媒在线播放 | 久久热这里只有 精品 | xxx暴力xxx| 国产麻豆AV伦 | 东热rq大乱交 | 黑丝女仆恋上我 | 秘密教学26我们在做一次吧免费 | 久草热在线 | 午夜日本大胆裸艺术 | 男男gaygay拳头 | 艳妇臀荡乳欲伦岳TXT下载 | 日本一区不卡在线播放视频免费 | 国产三级在线精品男人的天堂 | 精品国产午夜福利在线观看蜜月 | 日本 稀土矿 | 999久久久国产 | 国产精品一区二区20P | 国产色精品久久人妻99蜜桃麻豆 | 日日干夜夜爽 | 久青草国产97香蕉在线视频 | 久久精品国产欧美成人 | 国产毛多水多高潮高清 | 女性酥酥影院 | 亚洲视频不卡 | 被强J高H纯肉公交车啊 | 射死你天天日 | 挺弄抽插喷射HH | 色综合久久中文色婷婷 |