# 前言
*****
在使用thinkphp框架開發中,遇到很多需要捕獲異常的情況
* 如往數據庫插入數據時如果異常則拋出:數據插入失敗,
* 請求第三方接口異常時候拋出:接口異常,原因:XXXX
* 其他更多場景
```php
<?php
//我們捕獲異常的代碼可能是這樣的
try {
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
} catch (\Exception $e) {
return json(['code' => -1, msg => '用戶添加失敗:' . $e->getMessage()]);
}
}
```
那除了不停的try catch之外,是否有更加便捷高效的方法來統一捕獲這些異常,并且能夠在第一時間自動利用微信消息通知我們呢?答案是肯定的.本文將與大家分享thinkphp5.1的異常處理接管以及使用微信企業號實時通知程序異常告警,以便開發者在第一時間排查問題并做出對應處理.
# Thinkphp5.X 之異常自定義接管
*****
thinkphp框架為我們提供了一個非常便利的功能就是異常自定義接管,首先我們為什么要自己去接管異常,有何使用場景?
* 例如在開發API接口時希望只要程序有異常時對前端統一拋出: {code:-1,msg:"具體錯誤信息"}, 方便前端給用戶彈出對應提示語 .
* 即上文提到的希望在程序有異常時通知開發者,以便主動做出對應處理,不必等待用戶主動反饋報錯再去排查.
首先建議讀者先仔細閱讀 [ThinkPHP5.1完全開發手冊之異常處理章節]( http://www.hmoore.net/manual/thinkphp5_1/354092), 我們先在應用配置文件app.php中配置參數 exception_handle 
app\common\controller\Handle 異常接管類的文件可能如下
```php
<?php
namespace app\common\controller;
use Exception;
use think\exception\Handle as TpHandle;
use think\exception\HttpException;
use think\exception\ValidateException;
use think\console\Output as ConsoleOutput;
class Handle extends TpHandle
{
protected function alarm(Exception $exception)
{
try {
//將異常所在文件,以及行數 通知開發者 方便排查異常原因
$errmsg = $exception->getMessage();
$data = [
'title' => '程序異常通知',
'keyword1' => "file: " . $exception->getFile() . ';line:' . $exception->getLine(),
'keyword2' => "message: " . $errmsg,
];
//發送微信企業消息 通知開發者 下文會詳細講解
send_workweixin_textcard($data);
} catch (Exception $e) {
//需手動捕獲異常,防止上文異常后死循環
trace('消息發送失敗:'. $e->getMessage() . $errmsg, 'error');
}
}
public function report(Exception $e)
{
//異常通知
$this->alarm($e);
//交由Thinkphp框架繼續處理
parent::report($e);
}
//在CLI命令行模式有異常時將由此方法接管
/**
* @param Output $output
* @param Exception $e
*/
public function renderForConsole(ConsoleOutput $output, Exception $e)
{
//異常通知
$this->alarm($e);
//交由Thinkphp框架繼續處理
parent::renderForConsole($output, $e);
}
//在普通模式有異常時將由此方法接管
/**
* @param Exception $e
*/
public function render(Exception $e)
{
try {
$errmsg = $e->getMessage();
$data = [
'title' => '程序異常通知',
'keyword1' => "url: " . $url,
'keyword2' => "message: " . $errmsg,
];
//發送微信企業消息 通知開發者 下文會詳細講解
send_workweixin_textcard($data);
$data = [
'code' => intval($e->getCode()) ?: -1,
'msg' => $e->getMessage(),
];
//因本項目是API接口開發 統一返回固定格式
return json($data);
} catch (Exception $e) {
//需手動捕獲異常,防止上文異常后造成死循環
trace('消息發送失敗'. $e->getMessage(), 'error');
}
//交由Thinkphp框架繼續處理
return parent::render($e);
}
}
```
> 讓我們測試一下 當程序異常時會如何捕獲異常,我們嘗試測試如下代碼:
```php
<?php
public function index()
{
//未定義變量 直接echo 將拋出異常
echo $b;
}
```
> 我們通過url訪問此方法看如何提示

至此,我們已經實現了統一捕獲框架異常并按照統一格式拋出,極其適合API接口開發的項目, 這樣永遠拋出的都是一個合法的json且可被對方識別.方便將異常返回給前端.
# 利用微信企業號實時通知程序異常
*****
1. 首先我們需要申請微信企業號,[點擊進入企業號官網]( https://work.weixin.qq.com),注冊的流程將不再做過多講解.
2. 創建應用以及獲取企業ID[登錄企業微信后臺后點我查看](https://work.weixin.qq.com/wework_admin/frame#profile)和secret密鑰 以獲取access_token, 開發文檔可參考 [點擊進入開發文檔]( https://work.weixin.qq.com/api/doc#10013/第三步:獲取access_token)
3. 組轉指定格式數據給對應開發者微信號推送消息



因本人實際項目中使用的是封裝的比較復雜方法,故附上偽代碼,僅供參考 提供思路
```php
<?php
public function send_workweixin_textcard($data)
{
$corpid = '換成您wx開頭的企業ID';
$agentid = '換成您的應用ID';
$access_token = $this->get_access_token();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' . $access_token;
$description = "<div class=\"normal\">" . $data['keyword1'] . "</div><br>";
if (!empty($data['keyword2'])) {
$description .= "<div class=\"highlight\">" . $data['keyword2'] . "</div><br>";
}
$post_data = [
'corpid' => $corpid,
'data' => [
'touser' => 'xieyongfa',//成員ID列表(消息接收者,多個接收者用‘|’分隔,最多支持1000個)。特殊情況:指定為@all,則向關注該企業應用的全部成員發送
'msgtype' => 'textcard',
'agentid' => $agentid,
'textcard' => [
'title' => $data['title'],
'description' => $description,
],
]
];
curl_post($url,json_encode($post_data)); //發送消息 curl post請求請自行百度實現
}
protected function get_access_token()
{
$corpid = '換成您wx開頭的企業ID';
$corpsecret = '換成您的應用密鑰';
$url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' . $corpid . '&corpsecret=' . $corpsecret;
$json = curl_get($url); //curl 方法請自行百度
$result = json_decode($json, true);
if (isset($result['errcode']) || $result['errcode'] === 0) {
return $result['access_token']; //todo實際場景中請做好緩存邏輯
} else {
throw new \Exception('access_token獲取異常' . $result['errmsg'], $result['errcode']);
}
}
```
效果圖:這樣我們即可快速定位到異常的文件以及行數

# 后記
## 開發幫助及交流
如您對本文感興趣想與我聯系交流 您可以
+ 郵件至:xieyongfa@ecarde.cn
+ QQ:2392523899 [點我聊天](http://wpa.qq.com/msgrd?v=3&uin=2392523899&site=qq&menu=yes&from=message&isappinstalled=0)
+ 微信交流
