我們假定一個場景,使用者註冊, 需要引數
引數名 解釋 型別 是否必填 mobile 使用者手機號碼 字串 必填 sms_code 簡訊驗證碼 整數 必填 password 使用者密碼 字串 使用者密碼 re_password 重複使用者密碼 整數 重複使用者密碼 以下列舉會出現的問題情況
- 使用者簡訊不匹配
- 使用者簡訊型別錯誤
- 使用者密碼不相同
- 手機號已經使用過 直接登入即可
- 使用者密碼型別不符合要求
- 簡訊驗證碼不能為空
- 手機號碼不能為空
- 兩個密碼都不能為空
現在已知會複用的場景有 會在別的業務內有相同錯誤的型別 (具體業務不做贅述,業務不同,理解不同)
- 簡訊驗證 checkSms 驗證簡訊驗證碼是否正確 型別是否匹配
- 修改密碼 提示密碼型別錯誤等場景
這裡假定大家都不是大佬 業務有藕合 處理方案如下 (以下程式碼僅在checkSms下進行)
- 在checkSms函式裡面直接
//第一次寫文件 不會用markdown 你也可以用 response出去 這樣淺顯易懂 exit(json_encode(['code'=>-1,'msg'=>'簡訊驗證碼錯誤']));
- 每處都做判斷
if (false === checkSms($mobile,$code,$type)){ exit(json_encode(['code'=>-1,'msg'=>'簡訊驗證碼錯誤'])); }
- 看看我的方式 (這句要怎麼加粗啊)
- 在checkSms函式裡面直接
已知你有三套業務 且每套業務包含N個子模組 (別槓微服務/跨語言等,槓就是你贏 /狗頭)
- 現在出了問題 前端告訴你 code=-1 message=>’系統錯誤 || 需要登入 || 商品查詢失敗 || 簡訊失敗 等各種錯誤資訊 ‘
你什麼心情????????????? 開始到處找,這個message在哪, 誰寫的 ,什麼時候寫的,到底是哪個等 - 業務有藕合, 你在你的業務裡面用的某一個service(僅做偽例子)內的action 發現丟擲了一個你不清楚的異常,你去問,貼日誌
結果必然是 你找的人去執行我上面說的那條,依次遞迴. 直到Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 9216 bytes) in your problem
我就找不到在哪,我就是懵 日常維護老專案的同學豈不是人都沒了… 為什麼大家都是接盤,我缺過得這麼難
- 現在出了問題 前端告訴你 code=-1 message=>’系統錯誤 || 需要登入 || 商品查詢失敗 || 簡訊失敗 等各種錯誤資訊 ‘
與其到處去找不如直接告訴我是怎麼回事,但是你又很難保證大家寫的錯誤資訊內容都是一樣的
首先我們明白一個道理 你寫程式碼最害怕的是什麼 是bug嗎? 不是
是這個
你問我錯哪了? 我怎麼知道!!!!! 我就知道肯定報錯了 丟擲異常了 ,程式停了啊!!!ok 那麼我們知道了一個道理,當程式丟擲異常的時候,專案就會停掉.
同時我們也明白了一件事 叫做:
假定我們有一個業務模組叫做 User 裡面包含了一個控制器叫做 AuthController
內部需要完成一個login的行為<?php namespace App\Http\Controllers\Application\Auth; use App\Http\Controllers\Controller; use App\Http\Requests\Application\Auth\LoginRequest; use Illuminate\Http\JsonResponse; class AuthController extends Controller { // public function index(LoginRequest $loginRequest): JsonResponse { $data = $loginRequest->all(); //todo 驗證是否登入成功 //todo 登入成功之後需要從返回裡面獲取token 和 userInfo //todo 記錄日誌等行為 //todo 返回前端 return $this->success('登陸成功', compact('token', 'user')); } }
- 一般會怎麼做
if (!empty(login($username,$password))){ //todo 登入成功 //tonext}
- 一旦出錯 怎麼辦?????? 開始
if(1){ if(1){ if(1){ if(1){ //建議這裡直接用來測光速到底是多少 ,因為需求是無限長的 //並且你知道到底是什麼問題,什麼業務返回來的,到底的意思是什麼嘛? (突然成為派大星 ) } } } }
- 如果你覺得 上面這個方案或者類似這個方案很棒,那我收回剛才那張圖
- 一般會怎麼做
我們上面已知程式丟擲異常就會停掉,除非你繼續catch 然後拋什麼出來??????怕不是萬能交稅
1.我們設計讓我們的程式聽話,怎麼聽話,讓他犯錯自己會停,還會告訴你怎麼回事
2.怎麼實現,這麼做的意義是什麼
3.如何實現,這樣做有什麼別的意義沒
4.效能損耗問題回答問題
1.你是開發,程式是你的 你必須說什麼讓他聽什麼
2.此處僅做流程展示,最後會直接貼程式碼加註釋,如果沒耐心可以直接翻最後 建立異常應該都ok吧,不ok 就去看文件 你可以停在這了<?php namespace App\Exceptions; use Exception; class XxxException extends Exception { /** * @Message('簡訊驗證碼錯誤') */ const SMS_CODE_IS_ERROR = '300000000'; /** * @Message('簡訊驗證碼型別錯誤') */ const SMS_CODE_TYPE_IS_ERROR = '300000001'; /** * @Message('簡訊驗證碼不存在') */ const SMS_CODE_IS_NOT_EXISTS = '300000002'; } ?>
虛擬碼
//todo 驗證碼型別錯誤 throw new XxxException(XxxException::SMS_CODE_IS_ERROR);
程式現在是不是應該停下來了,因為當你exception的時候 下面程式碼不會執行了
但是新的問題出現了,如果這樣丟擲異常,前端怎麼辦???????????
此處小聲bb 前端處理不了跟我什麼關係啊,我是後臺啊,你有問題找前端啊.
那麼我們假設一下 如果我們告訴前端的是{code:'300000000','message:'簡訊驗證碼錯誤'}
是不是就很舒服了,前後端是一家 怎麼能鬧脾氣呢
那麼如果 Code統一,請問出問題你在發愁什麼? 是你的phpstorm不存在屬性追蹤嗎?怎麼實現???????????????
你問我,我也不到啊 我只能給你這個啊<?php namespace App\Exceptions; use App\Factory\ParseException; //解釋異常的工廠 嫌棄名字長就沒加Factory use App\Traits\ResponseTrait; //這個是我自己寫的一個簡單的trait 也會一併貼上去 use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Throwable; class Handler extends ExceptionHandler { use ResponseTrait; /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. * * @return void */ public function register() { $this->reportable(function (Throwable $e) { // }); } public function render($request, $e) { //資料庫沒查到資料或者資料是softdelete if ($e instanceof ModelNotFoundException) { return $this->error('500', '資料不存在或已刪除'); } //非允許請求方式 if ($e instanceof MethodNotAllowedHttpException) { return $this->error('422', '請求方式錯誤'); } //驗證失敗 if ($e instanceof ValidationException) { return $this->error(412, current(current($e->errors()))); } //這裡是為了相容其他的一些錯誤 if (Str::length($e->getMessage()) > 1 && Str::length($e->getCode()) > 1) { return $this->error($e->getCode(), $e->getMessage()); } //處理我們自己的錯誤 $result = ParseException::parseException($e); //這裡判斷的原因很簡單 因為可能這個code沒有按照規範宣告 if (is_array($result)) { return $this->error($result['code'], $result['message']); } // Object Not Found 你懂我意思吧? if ($e instanceof NotFoundHttpException) { return $this->error('404', '頁面路徑不存在'); } //這裡可以根據自己是否需要做兜底而決定是否兜底 } }
<?php namespace App\Factory; use Illuminate\Support\Facades\Log; class ParseException { public static function parseException(\Throwable $exception) { //註解 不懂得話建議直接看文件->反射 ,我講不明白這個東西 $annotation = new \ReflectionClass($exception); //翻轉 成code->constant $values = array_flip($annotation->getConstants()); if (empty($values)) { return false; } //拿到對應的constant $constant = $values[$exception->getMessage()]; //constant反射 $annotation_text = new \ReflectionClassConstant($exception, $constant); //獲取屬性註釋內容 $comment = $annotation_text->getDocComment(); try { //正則大法好 建議留意此處 preg_match("/Message\(\'(.*?)\'\)(\\r\\n|\\r|\\n)/U", $comment, $result); } catch (\Throwable $e) { return false; } if (false === isset($result[1])) { return false; } return [ 'code' => $exception->getMessage(), 'message' => $result[1] ]; } }
不要問我要 ResponseTrait 我相信一個簡單的 響應實現你是ok的
這樣實現的意義就是為了不管誰接手專案前端後端 看到錯誤資訊一目瞭然,就算某天領導說不要需要告訴使用者簡訊什麼錯了,就告訴他你簡訊錯了,你只需要去改constant而已!
並且可讀性高,ide支援 ,如果你覺得不合適,那我沒轍了 ,我盡力了效能損耗
目前沒發現很明顯的效能損耗,給出的調優方案也是 如果可以的話註解的類的屬性列表(讓你留意的地方)可以做快取而已 ,(因為我目前不需要去考慮這個,laravels大法好)
- 不出意外的話我的程式碼你拿著直接貼進去就可以用,但是我不建議你這麼做,因為一次吃飽不代表能一直吃飽,我希望你能清楚起碼也要點贊,不能白嫖這個道理
不要企圖假裝努力,因為結果不會陪你一起假裝~! (這個字的顏色怎麼改?)
本作品採用《CC 協議》,轉載必須註明作者和本文連結