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

大型電商項目異常治理--業務異常碼架構篇

開發 架構
日志里的異常碼亂成了一鍋粥:訂單服務拋 “120302 - 支付結果校驗失敗”,支付服務返回 “170103 - 訂單號不存在”,就連之前剛規范好的營銷服務,也跳出個 “139900 - 通用異?!薄?這串本該歸類到具體模塊的異常碼,竟被開發圖省事塞進了通用區間。

凌晨兩點的猿媛電商技術部,應急燈還亮著。李建國盯著屏幕上滾動的日志,指尖在鍵盤上敲得飛快 —— 剛上線的 “618 預售付尾款” 活動,半小時內突然涌來 200 多起投訴:用戶付完尾款,訂單卻顯示 “支付失敗”,可銀行賬單明明扣了錢。

日志里的異常碼亂成了一鍋粥:訂單服務拋 “120302 - 支付結果校驗失敗”,支付服務返回 “170103 - 訂單號不存在”,就連之前剛規范好的營銷服務,也跳出個 “139900 - 通用異?!薄?這串本該歸類到具體模塊的異常碼,竟被開發圖省事塞進了通用區間。

更糟的是,跨服務調用鏈路里,異常碼像斷了線的珠子:支付回調到訂單服務時,“170103” 突然變成 “-1”,沒人知道中間哪個環節丟了關鍵信息。李建國揉著發酸的眼睛,突然意識到:單一服務的異常碼規范只是開始,大型電商的復雜鏈路里,還藏著更難啃的硬骨頭……

一、2018 年:初創期的 “單服務裸奔”—— 能跑就行,哪管規范

2018 年夏天,王磊拿著攢下的 20 萬啟動資金,在望京 SOHO 旁邊租了個 10 平米的商住兩居室,拉上剛畢業的李建國,成立了 “猿媛電商”。當時的目標很簡單:做校園零食團購,讓學生們在宿舍里點幾下手機,就能買到便宜的薯片、可樂。

“建國,你先搭個能用的系統,不用太復雜,能下單、能收錢就行。” 王磊把一臺二手筆記本電腦推給李建國,“學生們下周就要開學了,咱們得趕在開學前上線。”

李建國那時候剛從培訓班畢業,只會基礎的 Spring Boot 和 MySQL,哪懂什么架構設計。他對著網上的教程,花了三天三夜,搭了個最簡單的單服務系統:

一個com.cxy.eshop包下,塞了所有代碼 ——Controller、Service、Dao 混在一起,連分層都沒有;數據庫就一張order表,訂單信息、用戶信息、商品信息全存在里面,字段名起得亂七八糟,user_name和cust_name并存,后來連他自己都分不清哪個是哪個。

異常處理?根本沒有。用戶下單失敗了,就返回 “下單出錯了”,支付出問題了,就返回 “支付失敗”,至于錯在哪,沒人知道?!爱敃r覺得能跑就行,反正訂單量少,學生們也不挑剔?!?李建國后來回憶起這段日子,總忍不住自嘲,“有次一個學生下單后沒收到貨,過來問我,我查了半天數據庫,才發現是把‘清華園’寫成了‘清華圓’,地址錯了?!?/p>

那時候的單服務,就像個 “五臟俱全的小破屋”—— 雖然簡陋,但勝在靈活。每天訂單量最多幾百單,服務器用的是阿里云 1 核 2G 的入門實例,跑得還挺順暢。李建國一個人就能搞定所有開發和運維,白天寫代碼,晚上盯著服務器日志,偶爾出點小問題,改一行代碼重新部署,就能解決。

王磊負責找貨源、談校園代理,每天開著二手面包車去批發市場進貨。兩人分工明確,第一個月就賺了 5 萬塊。慶功宴上,王磊拍著李建國的肩膀說:“建國,以后咱們做大了,就招更多人,你當技術負責人!”

李建國那時候還沒意識到,這種 “裸奔” 的單服務,很快就會跟不上業務的腳步。

二、2019 年:業務擴張倒逼 “微服務拆分”—— 不拆不行,再拖就死了

2019 年初,“猿媛電商” 的業務突然爆發了。王磊談下了北京 5 所高校的校園代理,還把業務從校園拓展到了周邊的寫字樓 —— 在寫字樓里放 “智能零食柜”,白領們掃碼就能下單,5 分鐘就能取貨。

訂單量一下從每天幾百單漲到了幾萬單,之前的單服務瞬間 “扛不住” 了:每天中午 12 點和下午 6 點的下單高峰,服務器 CPU 使用率直奔 100%,小程序加載半天出不來,用戶在群里罵 “什么破系統,比蝸牛還慢”;庫存數據經常出錯,明明零食柜里還有 10 包薯片,小程序卻顯示 “已售罄”,有次一個白領連續下單 5 次都失敗,直接投訴到了 12315;最嚴重的一次,支付回調延遲了 2 小時,用戶付了錢卻沒收到取貨碼,幾十個人圍著零食柜吵,王磊不得不親自去道歉,免費送了一周的零食,損失了好幾千塊。

“再這么下去,公司就得黃!” 王磊把李建國叫到辦公室,桌子拍得震天響,“必須改系統,不能再用單服務了!”

李建國這才意識到問題的嚴重性。他連夜查資料,發現行業內做電商的,大多用 “微服務架構”—— 把原來的單服務拆成多個小服務,每個服務負責一塊業務,互相獨立,就算一個服務出問題,其他服務也不受影響。

“那咱們就拆!” 李建國咬了咬牙,開始對著行業架構圖,梳理 “猿媛電商” 的業務模塊。他發現核心業務可以分成 6 塊:用戶管理、商品管理、訂單管理、支付、庫存、物流。

但真要拆分的時候,才發現沒那么簡單。原來的單服務里,代碼耦合得一塌糊涂 ——OrderService里既調用了支付接口,又操作了庫存數據,還得處理物流通知。李建國只能一行行讀代碼,把不同業務的邏輯拆分開。

“那段時間天天熬夜,眼睛紅得像兔子?!?李建國說,“有次拆訂單和庫存的邏輯,拆到凌晨 3 點,不小心把庫存扣減的代碼刪了,導致第二天線上出現‘超賣’,多賣了 200 包薯片,最后只能給用戶退款道歉。”

就這樣磕磕絆絆拆了三個月,李建國終于把單服務拆成了 6 個核心微服務,還加了個網關服務,用來轉發請求:

  • 用戶服務(user-service):負責用戶注冊、登錄、個人信息維護。之前用戶信息存在order表里,拆出來后建了user表和user_address表,解決了 “一個表存所有信息” 的混亂;
  • 商品服務(product-service):管理商品信息,包括名稱、價格、圖片、保質期。之前商品信息和訂單存在一起,拆出來后支持了 “商品上下架” 功能,運營終于不用再找李建國改代碼就能上下架商品;
  • 訂單服務(order-service):核心中的核心,負責生成訂單、取消訂單、查詢訂單。李建國把原來的OrderService拆成了OrderCreateService、OrderCancelService、OrderQueryService,職責更清晰;
  • 支付服務(pay-service):對接微信支付和支付寶,負責創建支付單、處理支付回調。之前支付邏輯嵌在訂單服務里,拆出來后支持了 “多支付方式”,還加了支付日志,方便排查支付問題;
  • 庫存服務(inventory-service):管理商品庫存,負責庫存扣減、庫存查詢。拆出來后加了并發控制,解決了 “超賣” 問題,這也是李建國最滿意的一個服務;
  • 物流服務(logistics-service):對接第三方物流公司,負責生成物流單、通知物流狀態。之前物流通知靠手動發短信,拆出來后自動通知,省了不少人力;
  • 網關服務(api-gateway):統一接收前端請求,轉發到對應的微服務,還加了權限校驗,防止非法請求。

拆分完成后,系統穩定性明顯提升了 —— 中午下單高峰,服務器 CPU 使用率降到了 60%;庫存超賣、支付回調延遲的問題,也很少出現了。王磊又招了幾個程序員,分別負責不同的服務,李建國終于不用一個人扛所有活了。

但好景不長,隨著業務繼續擴張,新的問題又出現了。

三、2020-2022 年:微服務 “野蠻生長”—— 服務越拆越多,異常卻越來越亂

2020 年,“猿媛電商” 又拓展了 “售后” 和 “營銷” 業務:用戶可以申請售后退款,還能領優惠券、參與滿減活動。為了支撐這些新業務,李建國又新增了 4 個微服務:

  • 客服服務(customer-service):處理用戶售后申請、投訴,客服小姐姐可以在系統里記錄工單;
  • 營銷服務(market-service):負責優惠券發放、滿減活動、會員積分,這是提升用戶復購的關鍵;
  • 履約服務(fulfill-service):連接訂單和物流,訂單支付后,通知倉庫備貨、發貨;
  • 倉儲服務(warehouse-service):管理倉庫庫存,負責撿貨、打包,之前庫存服務只管線上庫存,現在線下倉庫也需要專門的服務。

到 2022 年底,“猿媛電商” 的微服務數量已經達到 12 個,加上之前的用戶、商品、訂單等,總共 16 個服務。服務間的調用鏈路也越來越長,比如一個用戶使用優惠券下單的完整流程:

  • 前端發起請求,經過網關轉發到訂單服務;
  • 訂單服務調用用戶服務,驗證用戶是否登錄;
  • 調用商品服務,查詢商品價格和庫存狀態;
  • 調用營銷服務,驗證優惠券是否有效、是否可用;
  • 調用庫存服務,鎖定商品庫存(防止超賣);
  • 調用支付服務,創建支付單;
  • 用戶支付成功后,支付服務調用訂單服務的回調接口,更新訂單狀態;
  • 訂單服務調用履約服務,創建履約單;
  • 履約服務調用倉儲服務,通知倉庫撿貨;
  • 倉儲服務完成撿貨后,調用物流服務,生成物流單,通知快遞公司取貨。

這么長的鏈路,只要有一個服務拋出異常,整個流程就會卡住。更要命的是,每個服務的異常處理都 “各自為戰”—— 沒有統一的規范,每個程序員都按自己的習慣拋異常:

  • 用戶服務的 “用戶不存在”,返回 “-1”;
  • 商品服務的 “商品已下架”,返回 “goods_off_shelf”;
  • 營銷服務的 “優惠券已過期”,返回 “coupon_expired_1001”;
  • 訂單服務更亂,有時返回數字,有時返回字符串,遇到沒處理的異常,直接拋 RuntimeException,前端收到的就是 “500 Internal Server Error”。

“那時候前端開發小美,每天都要拿著錯誤信息跑來問我:‘建國哥,這個 “-1” 是啥意思?。窟@個 “coupon_expired_1001” 是哪個服務拋的???’” 李建國苦笑著說,“我也答不上來,只能讓她去對應的服務日志里找,有時候查半天都找不到問題根源。”

2022 年雙 11,線上出了個大故障:有用戶反映,用優惠券下單后,支付成功了,卻一直顯示 “待支付”,優惠券也被鎖定了,不能用也不能退。

李建國帶著團隊排查了整整 6 個小時,才找到問題所在:

  • 用戶下單時,營銷服務鎖定優惠券成功,但返回異常碼時,程序員手滑把 “200 - 鎖定成功” 寫成了 “201 - 鎖定失敗”;
  • 訂單服務收到 “201” 異常碼,以為優惠券鎖定失敗,就沒更新訂單狀態,但實際上庫存已經扣減了,優惠券也鎖定了;
  • 用戶支付成功后,支付服務調用訂單服務回調接口,訂單服務因為訂單狀態是 “待支付”,拒絕處理,返回 “order_status_error”;
  • 支付服務收到這個異常,不知道該怎么處理,就拋了個 RuntimeException,導致支付狀態沒同步到訂單服務。

最后,李建國只能手動修改數據庫,給用戶解鎖優惠券、更新訂單狀態,忙到凌晨 3 點才搞定。第二天,老板王磊把全部門的人叫到辦公室,發了大火:“咱們現在每天訂單量幾十萬,還這么搞異常處理,遲早要出大問題!必須定個規范,把這些異常管起來!”

也就是從那天起,李建國意識到:微服務光拆分還不夠,必須做 “異常治理”,尤其是統一的錯誤碼規范 —— 不然服務越多,亂得越厲害,最后整個系統都會變成 “一團亂麻”。

四、2023 年:異常治理的 “破局”—— 從錯誤碼規范開始,給異常 “立規矩”

2023 年初,“猿媛電商” 年交易額突破 10 億元,王磊把李建國提拔為技術經理,讓他牽頭做 “異常治理”。李建國做的第一件事,就是梳理所有服務的異常場景,制定統一的錯誤碼規范。

“那時候我帶著兩個程序員,花了一個月時間,把 16 個服務的所有異常都過了一遍?!?李建國說,“每個服務有多少個異常場景,每個場景該返回什么錯誤碼,都記在本子上,最后整理出了一個‘錯誤碼規范文檔’。”

這個規范的核心,是 “6 位數字錯誤碼”,分三段式結構:

  • 前兩位(服務標識):代表異常所屬的微服務,比如用戶服務是 10,商品服務是 11,訂單服務是 12,營銷服務是 13,這樣一看前兩位,就知道異常來自哪個服務;  系統級別錯誤:  

圖片圖片

    業務級別錯誤:

圖片圖片

  • 中間兩位(模塊標識):代表服務內的具體模塊,比如訂單服務的 “創建訂單” 是 01,“取消訂單” 是 02,“查詢訂單” 是 03,這樣能快速定位到具體業務模塊;

圖片圖片

  • 最后兩位(異常序號):代表該模塊下的具體異常,從 00 開始遞增,比如訂單服務 “創建訂單” 模塊下,“訂單已存在” 是 00,“商品不存在” 是 01,“優惠券不可用” 是 02。

   比如:

  • 120100:12(訂單服務)+01(創建訂單)+00(訂單已存在)→ 訂單已存在;
  • 130201:13(營銷服務)+02(優惠券鎖定)+01(優惠券已過期)→ 優惠券已過期;
  • 110302:11(商品服務)+03(商品查詢)+02(商品已下架)→ 商品已下架。為了讓規范落地,李建國還做了三件事:
  • 開發統一的異常枚舉包:在cxy-eshop-common工程里,為每個服務創建對應的異常枚舉類,比如UserErrorCodeEnum、ProductErrorCodeEnum、OrderErrorCodeEnum,每個枚舉值都按 “6 位錯誤碼 + 錯誤信息” 定義,還加了詳細注釋,說明異常場景和處理建議;
// 訂單服務異常枚舉示例
public enum OrderErrorCodeEnum {
    /**
     * 創建訂單 - 訂單已存在
     * 場景:用戶重復提交訂單(如連續點擊下單按鈕)
     * 處理建議:前端做按鈕置灰,后端加用戶ID+商品ID冪等校驗
     */
    ORDER_EXISTED("120100", "訂單已存在,請勿重復提交"),
    /**
     * 創建訂單 - 商品不存在
     * 場景:用戶下單時,商品已被刪除或下架
     * 處理建議:前端提示用戶“商品已下架”,引導用戶返回商品列表
     */
    PRODUCT_NOT_EXIST("120101", "商品不存在或已下架"),
    // 其他異常...
}
  • 做全公司培訓:李建國組織了 3 場培訓,給所有開發、測試、前端講錯誤碼規范,還搞了個 “錯誤碼默寫大賽”,默寫全對的獎勵零食大禮包,錯一個的罰抄 10 遍規范文檔?!澳菚r候連客服小姐姐都知道,看到 13 開頭的異常,就找營銷服務的開發,效率提升了不少?!?/li>
  • 加代碼審查:在代碼提交前,必須檢查異常處理是否符合規范,錯誤碼是否正確,不符合的一律打回。有次新員工小陳把訂單服務的 “取消訂單” 異常碼寫成了 120300(正確是 120200),被李建國打回,罰他抄了 5 遍規范文檔,從此再也沒人敢隨便寫錯誤碼。

五、異常治理前的 “異常迷宮”——try-catch 堆成山,排查故障靠 “猜”

錯誤碼規范落地后,李建國以為異常治理能順利推進,可沒過多久就發現:新的問題又來了 —— 服務間調用的異常處理還是一團糟,尤其是 RPC 服務端和 Web 層,重復的 try-catch 代碼堆得像座小山。

那天李建國翻營銷服務的代碼,看到MarketRemoteImpl里的lockUserCoupon方法,氣得差點把鍵盤摔了:

@Override
public JsonResult<Boolean> lockUserCoupon(LockUserCouponRequest request) {
    try {
        log.info("lockUserCoupon request:{}", JSON.toJSONString(request));
        // 調用優惠券服務鎖定優惠券
        Boolean result = couponService.lockUserCoupon(request);
        log.info("lockUserCoupon response:{}", result);
        return JsonResult.buildSuccess(result);
    } catch (MarketBizException e) {
        // 捕獲營銷業務異常
        log.error("lockUserCoupon biz error, request:{}", JSON.toJSONString(request), e);
        return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
    } catch (DubboException e) {
        // 捕獲Dubbo調用異常
        log.error("lockUserCoupon dubbo error, request:{}", JSON.toJSONString(request), e);
        return JsonResult.buildError("999999", "服務調用失敗,請稍后再試");
    } catch (Exception e) {
        // 捕獲系統未知異常
        log.error("lockUserCoupon system error, request:{}", JSON.toJSONString(request), e);
        return JsonResult.buildError(CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorCode(), 
                                    CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorMsg());
    }
}

“全公司 16 個服務,每個服務平均 10 個 RPC 接口,每個接口都這么寫 try-catch,改個日志格式都要改 160 處!” 李建國把開發們叫到會議室,把這段代碼投在屏幕上,“上次小陳改營銷服務的異常返回格式,漏了 3 個接口,導致線上出現‘999999’和‘-1’兩種系統錯誤碼,用戶投訴說‘你們系統怎么一會兒一個錯?’”

更頭疼的是 RPC 調用的異常流程。李建國畫了張流程圖,貼在會議室墻上:

  • 訂單服務(Dubbo 消費者)調用營銷服務(Dubbo 提供者)的lockUserCoupon接口;
  • 營銷服務拋出MarketBizException(錯誤碼 130201,優惠券已過期);
  • Dubbo 原生的ExceptionFilter把MarketBizException包裝成RuntimeException,還丟了錯誤碼;
  • 訂單服務消費者收到RuntimeException,進入自己的catch (Exception e)塊;
  • 訂單服務返回 “-1 - 系統未知異?!?給 Web 層;
  • Web 層的 Controller 又套了一層 try-catch,最后返回給前端的還是 “系統未知異?!薄?/li>

“用戶明明是優惠券過期,卻看到‘系統未知異?!懿涣R娘嗎?” 測試林曉拿著測試報告補充,“我上周測了 20 個異常場景,有 15 個最后都顯示‘系統未知異常’,根本沒法驗證異常處理是否正確?!?/p>

Web 層的問題也不小。訂單服務的OrderController里,每個接口都裹著 try-catch:

@PostMapping("/createOrder")
public JsonResult<CreateOrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
    try {
        log.info("createOrder request:{}", JSON.toJSONString(request));
        CreateOrderDTO result = orderService.createOrder(request);
        return JsonResult.buildSuccess(result);
    } catch (OrderBizException e) {
        log.error("createOrder biz error, request:{}", JSON.toJSONString(request), e);
        return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
    } catch (Exception e) {
        log.error("createOrder system error, request:{}", JSON.toJSONString(request), e);
        return JsonResult.buildError("-1", "系統繁忙,請稍后再試");
    }
}

“前端小美跟我吐槽,同一個‘訂單已存在’異常,在下單接口返回 120100,在取消訂單接口卻返回‘系統繁忙’,她都不知道該怎么統一提示用戶。” 李建國揉了揉太陽穴,“必須徹底重構異常處理流程,把這些重復的 try-catch 全干掉!”

六、RPC 服務端異常治理:Dubbo 過濾器 “救場”—— 干掉 try-catch,異常透傳不打折

李建國把 RPC 服務端治理的核心定為 “用 Dubbo 過濾器統一處理異常”。他翻了三天 Dubbo 官方文檔,終于理清了思路:重寫原生ExceptionFilter解決異常包裝問題,再開發自定義過濾器統一日志和異常返回,兩步走搞定 RPC 異常。

圖片圖片

第一步:定義統一業務異常 —— 給異常 “定家譜”

要讓過濾器識別業務異常,首先得有統一的異常父類。李建國在cxy-eshop-common工程里新建了BaseBizException,所有業務異常都繼承它:

package com.cxy.eshop.common.exception;
import lombok.Getter;
/**
 * 業務異常父類,所有業務異常必須繼承此類
 * 用于Dubbo過濾器識別業務異常,避免被包裝成RuntimeException
 */
@Getter
public class BaseBizException extends RuntimeException {
    /**
     * 錯誤碼(6位數字,遵循錯誤碼規范)
     */
    private final String errorCode;
    /**
     * 錯誤信息
     */
    private final String errorMsg;
    public BaseBizException(String errorCode, String errorMsg) {
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
    public BaseBizException(ErrorCode errorCodeEnum) {
        super(errorCodeEnum.getErrorMsg());
        this.errorCode = errorCodeEnum.getErrorCode();
        this.errorMsg = errorCodeEnum.getErrorMsg();
    }
}

然后讓各個服務的業務異常繼承它,比如營銷服務的MarketBizException:

package com.cxy.eshop.market.exception;
import com.cxy.eshop.common.exception.BaseBizException;
import com.cxy.eshop.common.exception.ErrorCode;
/**
 * 營銷服務業務異常
 */
public class MarketBizException extends BaseBizException {
    public MarketBizException(String errorCode, String errorMsg) {
        super(errorCode, errorMsg);
    }
    public MarketBizException(ErrorCode errorCodeEnum) {
        super(errorCodeEnum);
    }
}

“這樣一來,所有業務異常都有了‘家譜’,Dubbo 過濾器只要判斷異常是不是 BaseBizException 的子類,就能識別業務異常了?!?李建國在技術分享會上解釋。

第二步:重寫 Dubbo 原生 ExceptionFilter—— 阻止異常 “被包裝”

Dubbo 原生的ExceptionFilter會把自定義異常包裝成RuntimeException,李建國要做的就是 “截胡”—— 在包裝前把業務異常拎出來,直接返回錯誤碼和信息。

他在cxy-eshop-common里新建DubboExceptionFilter,繼承原生過濾器,重寫invoke方法:

package com.cxy.eshop.common.dubbo;
import com.cxy.eshop.common.exception.BaseBizException;
import com.cxy.eshop.common.exception.JsonResult;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.filter.ExceptionFilter;
import org.apache.dubbo.rpc.service.GenericService;
import java.lang.reflect.Method;
/**
 * 重寫Dubbo原生ExceptionFilter,解決業務異常被包裝問題
 */
@Activate(group = CommonConstants.PROVIDER)
public class DubboExceptionFilter extends ExceptionFilter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result = invoker.invoke(invocation);
        // 只處理有異常且非泛化調用的情況
        if (result.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = result.getException();
                // 關鍵:如果是業務異常,直接返回錯誤碼和信息,不包裝
                if (exception instanceof BaseBizException) {
                    BaseBizException bizException = (BaseBizException) exception;
                    JsonResult<Object> errorResult = JsonResult.buildError(
                            bizException.getErrorCode(), 
                            bizException.getErrorMsg()
                    );
                    // 用AsyncRpcResult包裝,避免Dubbo二次處理
                    return new AsyncRpcResult(ResultType.NORMAL_VALUE, errorResult, invocation);
                }
                // 非業務異常,走原生邏輯(比如檢查是否在方法聲明的異常列表中)
                Method method = invoker.getInterface().getMethod(
                        invocation.getMethodName(), 
                        invocation.getParameterTypes()
                );
                Class<?>[] exceptionClasses = method.getExceptionTypes();
                for (Class<?> exceptionClass : exceptionClasses) {
                    if (exception.getClass().equals(exceptionClass)) {
                        return result;
                    }
                }
                // 未聲明的非業務異常,包裝成系統錯誤
                JsonResult<Object> systemError = JsonResult.buildError(
                        "-1", "系統未知異常,請聯系管理員"
                );
                return new AsyncRpcResult(ResultType.NORMAL_VALUE, systemError, invocation);
            } catch (Throwable e) {
                // 防止過濾器自身出錯
                return result;
            }
        }
        return result;
    }
}

要讓這個過濾器生效,還得在cxy-eshop-common的resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter里加配置,用自定義過濾器覆蓋原生的:

# 覆蓋Dubbo原生ExceptionFilter,名稱必須是exception
exception=com.cxy.eshop.common.dubbo.DubboExceptionFilter

“這里踩過坑!” 李建國在文檔里加了個紅色警告,“一開始用了別的名稱,結果原生過濾器還在生效,業務異常還是被包裝,后來查文檔才知道,必須用‘exception’這個名稱才能覆蓋?!?/p>

第三步:新增 CustomerExceptionFilter—— 統一日志和耗時統計

解決了異常包裝問題,李建國又開發了CustomerExceptionFilter,把日志打印、耗時統計、異常返回全統一了:

package com.cxy.eshop.common.dubbo;
import com.alibaba.fastjson.JSON;
import com.cxy.eshop.common.exception.BaseBizException;
import com.cxy.eshop.common.exception.CommonErrorCodeEnum;
import com.cxy.eshop.common.exception.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
/**
 * 自定義Dubbo過濾器:統一日志、耗時統計、異常處理
 */
@Slf4j
@Activate(group = CommonConstants.PROVIDER, order = 100) // order越大,執行越靠后
public class CustomerExceptionFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long startTime = System.currentTimeMillis();
        String serviceName = invoker.getInterface().getName();
        String methodName = invocation.getMethodName();
        String paramJson = JSON.toJSONString(invocation.getArguments());
        log.info("[Dubbo調用開始] service:{}, method:{}, param:{}", 
                serviceName, methodName, paramJson);
        try {
            // 調用目標方法
            Result result = invoker.invoke(invocation);
            long costTime = System.currentTimeMillis() - startTime;
            if (result.hasException()) {
                // 處理異常(此時業務異常已被DubboExceptionFilter處理,這里只處理系統異常)
                Throwable e = result.getException();
                log.error("[Dubbo調用異常] service:{}, method:{}, param:{}, costTime:{}ms", 
                        serviceName, methodName, paramJson, costTime, e);
                JsonResult<Object> errorResult = JsonResult.buildError(
                        CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorCode(),
                        CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorMsg()
                );
                result.setValue(errorResult);
                result.setException(null); // 屏蔽原始異常,避免泄露敏感信息
            } else {
                log.info("[Dubbo調用成功] service:{}, method:{}, costTime:{}ms, result:{}", 
                        serviceName, methodName, costTime, JSON.toJSONString(result.getValue()));
            }
            return result;
        } catch (BaseBizException e) {
            // 捕獲方法內部主動拋出的業務異常
            long costTime = System.currentTimeMillis() - startTime;
            log.error("[Dubbo業務異常] service:{}, method:{}, param:{}, costTime:{}ms, errorCode:{}, errorMsg:{}", 
                    serviceName, methodName, paramJson, costTime, e.getErrorCode(), e.getErrorMsg(), e);
            JsonResult<Object> errorResult = JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
            return new AsyncRpcResult(ResultType.NORMAL_VALUE, errorResult, invocation);
        } catch (Exception e) {
            // 捕獲系統異常
            long costTime = System.currentTimeMillis() - startTime;
            log.error("[Dubbo系統異常] service:{}, method:{}, param:{}, costTime:{}ms", 
                    serviceName, methodName, paramJson, costTime, e);
            JsonResult<Object> errorResult = JsonResult.buildError(
                    CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorCode(),
                    CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR.getErrorMsg()
            );
            return new AsyncRpcResult(ResultType.NORMAL_VALUE, errorResult, invocation);
        }
    }
}

配置這個過濾器也得兩步:

  • 在META-INF/dubbo/org.apache.dubbo.rpc.Filter里注冊:
customerExceptionFilter=com.cxy.eshop.common.dubbo.CustomerExceptionFilter
  • 在每個服務的application.yml里啟用:
dubbo:
  provider:
    filter: customerExceptionFilter # 啟用自定義過濾器
    timeout: 3000

第四步:消除 Dubbo 服務端模板代碼 —— 代碼清爽得像 “剛洗澡”

過濾器部署完成后,李建國帶頭改造營銷服務的MarketRemoteImpl。之前 18 行的lockUserCoupon方法,現在只剩 3 行:

@Override
public JsonResult<Boolean> lockUserCoupon(LockUserCouponRequest request) {
    // 直接調用業務方法,不用try-catch,過濾器會處理異常
    Boolean result = couponService.lockUserCoupon(request);
    return JsonResult.buildSuccess(result);
}

“太爽了!” 負責營銷服務的小陳改完代碼,興奮地跑到李建國工位,“以前改個業務邏輯,還得小心翼翼別碰壞 try-catch,現在直接寫核心代碼,效率至少提升一倍!”

李建國還特意做了個對比測試:在營銷服務拋出 “130201 - 優惠券已過期” 異常,訂單服務調用后,直接拿到了包含錯誤碼的JsonResult,再也沒有被包裝成RuntimeException。前端收到異常后,準確顯示 “優惠券已過期,請更換優惠券”,用戶投訴量一下降了 40%。

七、 Web 層異常治理:全局攔截器 “收尾”——Controller 告別 try-catch

RPC 服務端搞定了,Web 層的問題還沒解決。李建國看著訂單服務OrderController里重復的 try-catch,決定用@RestControllerAdvice + @ExceptionHandler做全局異常攔截。

第一步:開發全局異常攔截器 ——Web 層的 “異常保安”

他在cxy-eshop-common里新建GlobalExceptionHandler,統一處理 Web 層所有異常:

package com.cxy.eshop.common.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.MethodArgumentNotValidException;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
/**
 * Web層全局異常攔截器,所有Controller的異常都會被這里處理
 * Order設置為最高優先級,確保先于其他攔截器執行
 */
@Slf4j
@RestControllerAdvice // 對所有@RestController生效,自動返回JSON格式
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    /**
     * 處理業務異常(BaseBizException及其子類)
     * 比如訂單服務的OrderBizException、營銷服務的MarketBizException
     */
    @ExceptionHandler(value = BaseBizException.class)
    public JsonResult<Object> handleBizException(BaseBizException e, HttpServletRequest request) {
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();
        // 打印業務異常日志,包含請求地址、請求方法,方便排查
        log.error("[Web業務異常] url:{}, method:{}, errorCode:{}, errorMsg:{}",
                requestUrl, method, e.getErrorCode(), e.getErrorMsg(), e);
        // 直接返回業務異常的錯誤碼和信息,符合前端預期格式
        return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
    }
    /**
     * 處理請求參數校驗異常(比如@NotNull、@NotBlank注解觸發的異常)
     * 之前這類異常需要在Controller里手動捕獲,現在統一處理
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public JsonResult<Object> handleParamValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();
        // 提取參數校驗失敗的信息(比如“訂單號不能為空”)
        String errorMsg = e.getBindingResult().getFieldErrors().stream()
                .map(fieldError -> fieldError.getField() + ":" + fieldError.getDefaultMessage())
                .collect(Collectors.joining("; "));
        // 打印參數校驗日志
        log.error("[Web參數校驗異常] url:{}, method:{}, errorMsg:{}",
                requestUrl, method, errorMsg, e);
        // 返回客戶端通用參數錯誤碼1002
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_VALID_ERROR);
    }
    /**
     * 處理系統未知異常(所有未捕獲的異常都會走到這里)
     */
    @ExceptionHandler(value = Exception.class)
    public JsonResult<Object> handleSystemException(Exception e, HttpServletRequest request) {
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();
        // 打印系統異常日志,包含堆棧信息,方便定位問題
        log.error("[Web系統未知異常] url:{}, method:{}, errorMsg:{}",
                requestUrl, method, e.getMessage(), e);
        // 返回系統未知異常碼-1,避免暴露敏感信息
        return JsonResult.buildError(CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR);
    }
}

寫完代碼,李建國特意加了三個關鍵處理邏輯:

  • 業務異常精準返回:直接提取BaseBizException的錯誤碼和信息,保證前端拿到的異常格式統一;
  • 參數校驗異常自動處理:之前需要在 Controller 里寫@Valid + BindingResult手動判斷,現在攔截器自動收集校驗失敗信息,返回 1002 客戶端參數錯誤碼;
  • 系統異常脫敏:只返回 “系統未知異?!?提示,不暴露堆棧信息,避免黑客利用漏洞。

“以前參數校驗要寫這么多代碼,” 李建國翻出之前的 Controller 代碼給同事看,“現在一行都不用寫,攔截器全搞定!”

第二步:改造 Controller—— 代碼 “瘦身”,告別 try-catch

全局攔截器部署到cxy-eshop-common后,李建國帶頭改造訂單服務的OrderController。之前 15 行的createOrder接口,現在只剩 5 行:

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;
    /**
     * 提交訂單接口
     * 改造后:無try-catch,無日志打印,無參數校驗判斷
     */
    @PostMapping("/createOrder")
    public JsonResult<CreateOrderDTO> createOrder(
            // @Valid觸發參數校驗,攔截器會處理校驗失敗異常
            @Valid @RequestBody CreateOrderRequest request) {
        // 只保留核心業務邏輯,異常全靠GlobalExceptionHandler處理
        CreateOrderDTO result = orderService.createOrder(request);
        return JsonResult.buildSuccess(result);
    }
    /**
     * 取消訂單接口
     * 之前因try-catch漏寫,導致返回“系統繁忙”,現在不會再出現
     */
    @PostMapping("/cancelOrder")
    public JsonResult<Boolean> cancelOrder(
            @RequestParam("orderId") String orderId,
            @RequestParam("userId") String userId) {
        Boolean result = orderService.cancelOrder(orderId, userId);
        return JsonResult.buildSuccess(result);
    }
}

負責訂單服務的開發老周改完代碼,興奮地拍了拍李建國的肩膀:“建國,你這攔截器太牛了!以前改個訂單狀態邏輯,還得小心翼翼別碰錯 try-catch,現在直接寫業務代碼,效率至少提了三成!”

更讓前端小美開心的是,異常格式終于統一了。之前同一個 “訂單已存在” 異常,在下單接口返回{"errorCode":"120100","errorMsg":"訂單已存在"},在取消訂單接口返回{"errorCode":"-1","errorMsg":"系統繁忙"},現在不管哪個接口拋出OrderBizException,都返回統一格式,她再也不用寫一堆 “if-else” 判斷錯誤碼了。

“現在我只要根據 errorCode 就能寫提示,12 開頭就是訂單服務的問題,13 開頭就是營銷服務,太省心了!” 小美特意跑來給李建國送了杯奶茶,“之前處理異常要寫 50 行代碼,現在 10 行就搞定!”

第三步:落地踩坑與優化 —— 細節決定成敗

但全局攔截器落地時,還是出了小插曲。測試林曉在測 “查詢訂單詳情” 接口時,發現傳入非法的訂單號(比如 “abc123”),攔截器返回的是 “系統未知異常”,而不是預期的 “訂單號格式錯誤”。

“建國哥,這不對啊!” 林曉拿著測試報告找到李建國,“訂單號格式錯誤應該是客戶端參數錯誤,返回 1004 才對,怎么返回 - 1 了?”

李建國查了日志才發現,訂單服務的getOrderDetail方法里,把 “訂單號格式錯誤” 拋成了IllegalArgumentException,而不是自定義的OrderBizException,導致攔截器把它當成系統異常處理,返回了 - 1。

“看來光有攔截器還不夠,得規范異常拋出!” 李建國立刻組織開發們開了個短會,強調兩條規則:

  • 業務相關異常必須拋自定義業務異常:比如參數格式錯誤、業務邏輯不滿足(如訂單已支付不能取消),必須拋對應服務的 BizException,指定明確錯誤碼;
  • 非業務異常(如空指針)要提前預防:通過參數校驗、判空等方式避免,實在無法避免的,在最外層 Service 拋BaseBizException(指定通用錯誤碼),不讓它走到系統異常攔截邏輯。

會后,老周把getOrderDetail里的IllegalArgumentException改成了OrderBizException:

public OrderDetailDTO getOrderDetail(String orderId) {
    // 訂單號格式校驗:如果不是數字,拋業務異常
    if (!orderId.matches("\\d+")) {
        throw new OrderBizException(OrderErrorCodeEnum.ORDER_ID_FORMAT_ERROR);
        // OrderErrorCodeEnum.ORDER_ID_FORMAT_ERROR的錯誤碼是120601,含義“訂單號格式錯誤”
    }
    // 后續業務邏輯...
}

再次測試,傳入 “abc123” 的訂單號,接口正確返回{"errorCode":"120601","errorMsg":"訂單號格式錯誤,僅支持數字"},林曉這才滿意地在測試報告上打了 “通過”。

還有個坑是 “攔截器優先級”??头盏拈_發小張自己寫了個CustomerExceptionHandler,優先級比全局攔截器還高,導致客服服務的業務異常被小張的攔截器處理,返回了非標準格式的錯誤信息。

李建國查了代碼,發現小張的攔截器沒加@Order注解,默認優先級比全局攔截器低,可小張在@RestControllerAdvice里指定了basePackages = "com.cxy.eshop.customer",只處理客服服務的 Controller,反而覆蓋了全局攔截器。

“解決辦法很簡單,在全局攔截器的@RestControllerAdvice里也加 basePackages,并且把優先級設為最高?!?李建國幫小張修改了全局攔截器的注解:

// 只處理公司內部服務的Controller,避免影響第三方依賴
@RestControllerAdvice(basePackages = "com.cxy.eshop")
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高優先級,確保先執行
public class GlobalExceptionHandler {
    // ...
}

這樣一來,全局攔截器會優先處理所有com.cxy.eshop包下的 Controller 異常,小張的客服服務攔截器只作為補充,不會覆蓋全局規則。

八、異常治理落地效果 —— 從 “混亂” 到 “有序” 的蛻變

經過三個月的攻堅,猿媛電商的異常治理終于全面落地。李建國做了個數據統計,效果讓全公司都驚訝:

  • 代碼冗余減少 60%:全公司 16 個服務,共刪除重復 try-catch 代碼約 8000 行,平均每個接口代碼量減少 50%;
  • 故障排查時間縮短 80%:之前排查一個異常平均需要 2 小時,現在看錯誤碼就能定位到服務和模塊,平均 20 分鐘就能解決;
  • 用戶投訴量下降 75%:因 “系統未知異?!?導致的投訴從每月 120 起,降到了每月 30 起以下;
  • 開發效率提升 40%:新接口開發時間從平均 2 天,縮短到 1.2 天,不用再花時間寫重復的異常處理代碼。

2023 年底的技術總結會上,李建國展示了治理前后的對比:

  • 治理前:用戶下單用優惠券,因營銷服務異常,返回 “系統繁忙”,用戶不知道是優惠券過期;
  • 治理后:相同場景下,接口返回 “130201 - 優惠券已過期,請更換優惠券”,用戶直接換券下單,轉化率提升了 15%。

老板王磊拿著這份數據,在全公司大會上表揚了技術部:“以前用戶總說咱們系統‘不穩定’,現在很少聽到這種抱怨了。異常治理不僅提升了用戶體驗,還幫公司省了不少售后成本,李建國這個技術經理,沒白提!”

2023 年底的公司年會上,王磊把 “年度技術貢獻獎” 頒給了李建國,獎金 10 萬元?!敖▏愀愕漠惓V卫?,比加 10 臺服務器還管用!” 王磊拍著他的肩膀說,“明年咱們要把業務拓展到全國,你繼續牽頭做架構優化,爭取成為真正的架構師!”

李建國拿著獎杯,看著臺下的張萌(此時已經是他的未婚妻),眼眶有點濕潤。他想起 2018 年剛入職時,那個連單服務都寫不規范的自己;想起 2019 年拆分微服務時,熬夜改 bug 的日子;想起 2022 年雙 11 排查故障時的焦慮。

“從單服務到微服務,再到異常治理,其實就是公司成長的縮影。” 李建國在獲獎感言里說,“技術從來不是孤立的,而是跟著業務走的 —— 業務需要什么,我們就做什么;哪里有問題,我們就解決哪里。這就是我們程序員的價值?!?/p>

臺下響起了熱烈的掌聲,張萌笑著給他比了個 “加油” 的手勢。李建國知道,這只是開始,未來還有更多的技術挑戰等著他,但他已經做好了準備 —— 跟著 “猿媛電商” 一起,繼續成長,繼續破局。

臺下的李建國看著身邊的張萌(此時已經是他的妻子),心里滿是感慨。他想起 2018 年剛入職時,那個連單服務異常都處理不好的自己;想起 2019 年拆分微服務時,熬夜改 bug 的焦慮;想起 2023 年推進異常治理時,遇到的各種阻力。

“異常治理不是終點,而是新的起點?!?李建國在年會的最后說,“接下來我們還要做異常監控平臺,把所有異常碼匯總起來,實時預警;還要做異常溯源,讓每個異常都能查到完整的調用鏈路。技術永遠在進步,我們也得跟著進步,才能跟上公司發展的腳步?!?/p>

散會后,張萌走過來,悄悄遞給李建國一個保溫杯:“別總熬夜了,現在系統穩定了,也該多陪陪我和孩子了。” 李建國笑著接過保溫杯,里面是他最愛喝的菊花茶。他知道,未來還有更多技術挑戰等著他,但有家人的支持,有團隊的配合,他有信心把猿媛電商的技術架構做得更穩定、更強大 —— 就像猿媛電商的成長一樣,從 “小破屋” 到 “高樓大廈”,一步一個腳印,踏實向前。

猿媛電商的 6 位異常碼規范剛在全服務落地滿三周,雙十一大促前的壓力測試就炸出了新漏洞:一批用戶在 “優惠券 + 滿減” 疊加下單時,前端同時彈出 “160102 - 訂單類型錯誤” 與 “150401 - 費用計算失敗” 兩個異常提示,后端日志里訂單服務和營銷服務的異常碼各執一詞,排查兩小時才發現 —— 營銷服務計算滿減時超時,卻未拋專屬的 “150402 - 滿減計算超時”,反而復用了訂單服務的通用錯誤碼,導致鏈路異常 “串線”。

更棘手的挑戰接踵而至:新增的生鮮業務要求異常碼關聯 “冷鏈中斷” 等特殊場景,6 位編碼的模塊位已不夠分配;海外站用戶投訴 “錯誤提示全是中文”,多語言適配要如何與異常碼綁定?

李建國團隊連夜啟動 “異常碼治理 2.0” 計劃:既要給異常碼加 “鏈路 ID” 實現跨服務溯源,還要設計可擴展的編碼規則??删驮诜桨冈u審當天,運維團隊突然上報 —— 生產環境出現 “異常碼雪崩”,近千條錯誤日志里,不同服務的異常碼竟指向同一個不存在的模塊位…… 這場突如其來的危機,會讓之前的治理成果功虧一簣嗎?歡迎評論后續精彩故事。

責任編輯:武曉燕 來源: 架構師修煉之路
相關推薦

2022-11-16 09:03:35

Sentry前端監控

2021-02-25 08:40:19

Java異常分類異常防護

2017-03-03 14:10:50

電商基礎架構建設

2012-10-26 15:11:56

云計算Puppet

2021-07-08 11:22:55

Java異常處理

2021-06-21 06:32:04

Python異常傳遞s自定義異常

2023-06-05 07:24:46

SQL治理防御體系

2024-11-27 16:38:07

2022-09-07 21:26:40

取貨碼vivo電商平臺

2021-10-08 09:57:38

Java開發架構

2016-08-18 23:37:24

2022-11-15 09:17:19

ArkTS鴻蒙

2012-11-12 10:32:48

IBMdw

2018-07-11 19:41:47

MySQL定義異常異常處理

2023-02-21 16:46:04

loongarch架構

2014-11-24 11:32:09

亞馬遜云服務AWS

2011-08-05 14:02:17

MySQL數據庫異常處理

2013-01-09 13:58:00

銀行移動電商移動互聯網

2022-12-28 08:17:19

異常處理code

2021-03-18 10:01:06

Java編譯異常運行異常
點贊
收藏

51CTO技術棧公眾號

久久永久免费| 韩日精品一区| 成人ar影院免费观看视频| 国内精品美女av在线播放| 国产乱了高清露脸对白| 九九九伊在线综合永久| 亚洲免费观看高清完整| 国内精品国语自产拍在线观看| 精品人妻无码一区二区性色| 久久久久亚洲| 亚洲激情国产精品| 精品亚洲一区二区三区四区| 国产黄色大片在线观看| 国产亚洲视频系列| 91久久国产自产拍夜夜嗨| 日韩视频在线观看一区| 亚洲成人三区| 亚洲视频欧洲视频| 自拍视频第一页| 欧美日韩尤物久久| 亚洲午夜羞羞片| 亚洲视频导航| 青青草视频在线观看| 国产毛片精品国产一区二区三区| 欧美在线精品免播放器视频| 熟女av一区二区| 国产91精品对白在线播放| 欧美一区二区三区在线观看视频| 欧美三级午夜理伦三级| 久久99亚洲网美利坚合众国| 国产精品久久久久久久岛一牛影视 | 色哟哟精品观看| 成人香蕉社区| 欧美一区二区三区免费| 91热这里只有精品| 麻豆视频在线看| 一区二区三区影院| 在线丝袜欧美日韩制服| 精品乱码一区二区三四区视频| 丁香六月久久综合狠狠色| 国产精品久久久久久中文字| 五月婷婷色丁香| 亚洲高清免费| 久久久久久12| 青青草精品在线视频| 色狮一区二区三区四区视频| 国产一区二区久久精品| 在线观看日韩精品视频| 国产亚洲成av人片在线观黄桃| 日韩一本二本av| 亚洲天堂网2018| 日韩毛片免费看| 欧美日韩日日骚| 向日葵污视频在线观看| 精品肉辣文txt下载| 日韩欧美精品网站| 成年人免费在线播放| 九色porny丨入口在线| 亚洲成人综合在线| 日韩视频免费播放| 99riav视频在线观看| 亚洲国产精品久久久久婷婷884 | 青草av.久久免费一区| 欧美综合在线第二页| 黑人一级大毛片| 亚洲一区二区三区高清不卡| 欧美亚洲国产另类| 国产一级免费视频| 免费观看日韩电影| 成人精品在线观看| 国内精品久久久久久久久久| 国产91精品入口| 国产91精品入口17c| 欧美自拍偷拍一区二区| 91丨porny丨蝌蚪视频| 欧美日韩精品免费在线观看视频| 国产区视频在线播放| 日本一区二区视频在线观看| 一区二区三区精品国产| 日本h片在线观看| 午夜av一区二区| www日韩视频| 99久久999| 精品久久人人做人人爱| 国产美女喷水视频| 亚洲第一福利社区| 这里只有精品视频| 永久看片925tv| 一本久道久久久| 国产精品偷伦一区二区| 精品国产av 无码一区二区三区| www.日韩大片| 视频一区二区在线| 18av在线视频| 色婷婷av一区二区三区大白胸| 欧美午夜aaaaaa免费视频| 国产精品亚洲欧美一级在线| 亚洲国产精品va在线看黑人动漫| 中字幕一区二区三区乱码| 91精品国产福利在线观看麻豆| 欧美激情视频在线| 中国一区二区视频| 岛国av在线一区| 视频一区视频二区视频三区高| h片在线免费| 色哟哟在线观看一区二区三区| 九九热精品国产| 图片婷婷一区| 美女扒开尿口让男人操亚洲视频网站| 日韩三级av在线| 狠狠色综合日日| 免费在线成人av电影| 99热国产在线中文| 日本道精品一区二区三区| 国产高潮失禁喷水爽到抽搐 | 亚洲天堂国产精品| 成人午夜免费av| 在线不卡视频一区二区| 伊人网在线播放| 日韩欧美国产综合在线一区二区三区| 谁有免费的黄色网址| 亚洲小说欧美另类婷婷| 国产欧美日韩高清| 男人天堂亚洲二区| 亚洲va欧美va人人爽| 国产大片一区二区三区| 欧洲美女日日| 欧美一区二区三区图| 亚洲精品国产精品乱码不卡| 中文字幕中文字幕中文字幕亚洲无线| 99999精品视频| jizz18欧美18| 超碰97人人做人人爱少妇| 中文字幕一区二区人妻| 91一区在线观看| 无码中文字幕色专区| 日本高清精品| 欧美伦理91i| 亚洲一卡二卡在线观看| 久久久99免费| 毛片一区二区三区四区| 啪啪激情综合网| 97精品国产97久久久久久免费| 国产精品一级视频| 中文字幕在线观看不卡| 无尽裸体动漫2d在线观看| 欧美激情在线免费| 国产精品999999| 国产在线一二| 在线精品视频一区二区三四| 受虐m奴xxx在线观看| 免费亚洲一区| 日韩av高清在线播放| 在线观看涩涩| 精品视频偷偷看在线观看| 天天操天天摸天天干| 91网站视频在线观看| 国产一区二区三区精彩视频 | 午夜精品影院在线观看| 日本电影一区二区三区| 亚洲最大网站| 亚洲人成在线播放| 夜夜躁日日躁狠狠久久av| 久久精品视频免费| 日本免费观看网站| 98精品久久久久久久| 91精品久久久久久久久中文字幕| 麻豆tv入口在线看| 欧美一区二区三区视频在线| 九九热精彩视频| av欧美精品.com| 欧美日韩在线中文| 欧美综合在线视频观看| 国产主播精品在线| 色呦呦在线观看视频| 亚洲精品一区二区三区在线观看| 国产成人精品片| 国产日韩欧美a| 亚洲综合av在线播放| 欧美成人一区二免费视频软件| 国产精品av一区| 色香欲www7777综合网| 色多多国产成人永久免费网站 | 久久精品久久99精品久久| 在线观看日韩羞羞视频| 亚洲成人五区| 国产精品jizz在线观看麻豆| 看黄网站在线| 亚洲第一区在线观看| 久久国产乱子伦精品| 国产精品第一页第二页第三页 | 国产精品嫩草99a| 免费不卡av网站| 性欧美videos另类喷潮| 亚洲一区二区三区精品视频 | 色爱区成人综合网| 精品国产18久久久久久二百| 欧美一区二区三区精品电影| 老司机午夜在线视频| 亚洲娇小xxxx欧美娇小| 中文字幕第31页| 亚洲大片精品永久免费| 国产精品一区二区亚洲| 成人av网站大全| 亚洲欧美日本一区二区三区| 亚洲韩日在线| japanese在线视频| 国产乱码精品一区二区亚洲| 91九色极品视频| 久久婷婷五月综合色丁香| 国产做受69高潮| av网页在线| 日韩高清免费观看| www.日韩在线观看| 欧美在线免费观看亚洲| 日韩欧美a级片| 亚洲麻豆国产自偷在线| 成人黄色免费网址| 99久久国产综合精品色伊| 国产精品探花在线播放| 麻豆国产一区二区| 国产免费黄色av| 亚洲国产国产亚洲一二三| 中文字幕超清在线免费观看| 激情五月综合| 欧美三日本三级少妇三99| 超碰97成人| 97se在线视频| 电影中文字幕一区二区| 国产精品丝袜高跟| 精品免费av在线| 日韩美女免费线视频| 麻豆蜜桃在线观看| 欧美极品欧美精品欧美视频| 在线中文字幕-区二区三区四区| 色妞一区二区三区| 视频三区在线观看| 亚洲精品久久久久国产| 亚洲精品一区二区三区不卡| 日韩一区二区在线观看| 国产精品毛片久久久久久久av| 欧美日韩精品免费| 亚洲无码精品一区二区三区| 欧美日韩国产在线看| wwwxxx亚洲| 狠狠躁夜夜躁人人躁婷婷91| 国产精品100| 色综合天天综合色综合av| 影音先锋亚洲天堂| 欧美视频在线观看免费网址| 国产精品成人国产乱| 午夜久久久影院| 黄色片视频网站| 午夜视频在线观看一区| 国内免费精品视频| 天天亚洲美女在线视频| 国产高清中文字幕| 色婷婷久久久综合中文字幕| 午夜久久久久久久久久影院| 在线观看视频一区二区欧美日韩| 亚洲国产无线乱码在线观看| 欧美特级限制片免费在线观看| 一级片免费观看视频| 制服.丝袜.亚洲.中文.综合| 国产激情无套内精对白视频| 亚洲精品一线二线三线| 三级在线视频| 在线精品视频视频中文字幕| 岛国成人毛片| 久久久久国产精品免费网站| 日韩伦理精品| 国产精品亚洲激情| 日本一区二区三区电影免费观看| 国产视频99| 精品国产一区二区三区久久久樱花 | 欧美一区二区性放荡片| 亚洲精品无amm毛片| 日韩美女av在线| 婷婷成人激情| 欧美高清在线观看| 深夜成人影院| 91在线国产电影| 精品亚洲免a| 亚洲人成人77777线观看| 亚洲欧美日韩高清在线| 国内精品在线观看视频| 美女视频一区二区三区| 亚洲欧美高清在线| 欧美高清在线精品一区| 精品国产欧美日韩不卡在线观看| 精品久久久国产| 一区二区久久精品66国产精品 | 日本中文字幕在线播放| 色综合五月天导航| 欧美极品免费| 国产精品初高中精品久久| 狠狠操综合网| 国产精品久久..4399| 热久久免费视频| 中国极品少妇xxxx| 国产精品伦理在线| 中文字幕一区在线播放| 欧美一区二区三区视频| 成年人在线视频免费观看| 久久免费少妇高潮久久精品99| 不卡亚洲精品| 久久久福利视频| 欧美精品入口| 中文字幕久久av| 久久久久久综合| 中文在线观看免费网站| 91精品午夜视频| www.国产精品.com| 2021国产精品视频| 99久久人爽人人添人人澡| 一区二区精品国产| 视频一区免费在线观看| 国产十八熟妇av成人一区| 成人免费小视频| 中文字幕日产av| 日韩精品中文字幕有码专区| 先锋成人av| 91色在线观看| 99re66热这里只有精品8| 97公开免费视频| 久久奇米777| 精品在线播放视频| 亚洲成人免费在线视频| 国产第一页在线视频| 5566av亚洲| 欧美 日韩 国产 一区| 天堂av2020| 17c精品麻豆一区二区免费| 最近中文字幕在线观看| 亚洲一级黄色片| 欧美电影免费看| 欧美高清视频一区二区三区在线观看| 精品成人国产| 秘密基地免费观看完整版中文| 一区二区三区欧美日| 国产又粗又猛视频| 日韩有码在线视频| 日本亚洲欧洲无免费码在线| 五月天色一区| 裸体在线国模精品偷拍| 18精品爽国产三级网站| 欧美日韩高清在线| 黄色网址免费在线观看| 91精品久久久久久蜜桃| 欧美日韩亚洲三区| 国产精品无码自拍| 亚洲午夜一区二区三区| 视频一区二区免费| 欧洲精品在线视频| 国产欧美日韩视频在线| 国产又大又黄又粗又爽| 国产一区999| 九九久久久久久久久激情| 久久亚洲AV无码| 91官网在线观看| 岛国最新视频免费在线观看| 国产精品免费一区二区三区都可以| 国产欧美日韩精品一区二区三区| 欧美一区二区福利| 911美女片黄在线观看游戏| 欧美3p在线观看| 水蜜桃一区二区| 久久爱www久久做| 免费精品在线视频| 欧美一级搡bbbb搡bbbb| 91黄色在线视频| 欧美在线观看一区| 免费网站看v片在线a| 999视频在线免费观看| 红桃视频亚洲| 欧美bbbbb性bbbbb视频| 欧美无乱码久久久免费午夜一区 | 97免费视频观看| 91老司机福利 在线| 中文字幕第三页| 九色成人免费视频| 偷窥自拍亚洲色图精选| 在线看的黄色网址| 一区二区三区成人在线视频| 无套内谢的新婚少妇国语播放| 国产精品www网站| 午夜精品免费| 中文字幕第4页| 欧美一卡二卡在线| 在线最新版中文在线| 中文字幕日韩精品一区二区| 色播五月综合网| 图片区小说区国产精品视频| 91精彩视频在线观看| 国产91精品入口17c| 麻豆精品在线看| 国产乡下妇女做爰视频| 中文字幕欧美日韩va免费视频| 国产精品久久久网站| 日本中文字幕精品—区二区|