当后端程序由于各种原因出错时,如果将错误信息直接抛给客户端,用户肯定会一脸懵逼,除非当时的用户是你们的开发,本文主要讲述如果拦截全局异常,不把异常抛给用户,同时,如果做过客户端,应该知道,Android和ios都有第三方crash上报平台,如果能在出错后发消息到钉钉则很方便,本文主要解决这两件事。
拦截异常主要使用 @ControllerAdvice 注解,具体来讲就是,新建一个包,比如 crash,然后新建一个 类 比如 GlobalDefaultExceptionHandler,这个类的代码如下:
import com.alibaba.fastjson.JSON; import io.sentry.Sentry; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import top.kpromise.user.response.Result; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Map; @ControllerAdvice public class GlobalDefaultExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Map<String, Object> defaultExceptionHandler(HttpServletRequest request, Exception e) { e.printStackTrace(); System.out.println("uri " + request.getRequestURI()); String params = JSON.toJSONString(request.getParameterMap()); if (params.equals("{}")) { params = getRequestJson(request); } String title = request.getRequestURI(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String crashTime = df.format(new Date()); String text = "#### API 请求失败 @13067856109\n" + "> url " + request.getRequestURI() + "\n\n" + "> 请求参数 \n" + params + "\n\n" + "> 堆栈信息 \n" + e.getMessage() + "\n\n" + "> ###### 出错时间 " + crashTime + " \n"; ArrayList<String> phones = new ArrayList<>(); phones.add("13067856109"); new DingTalkMessage(new DingTalkMessageBuilder().markdownMessage(title, text).at(phones)).send(); Sentry.capture(e); return Result.data(null, "系统异常,请联系客服"); } private static String getRequestJsonString(HttpServletRequest request) throws Exception { String submitMethod = request.getMethod().toUpperCase(); if (submitMethod.equals("GET")) { return new String(request.getQueryString().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8) .replaceAll("%22", "\""); } else { return getRequestPostStr(request); } } private static String getRequestJson(HttpServletRequest request) { try { return getRequestJsonString(request); } catch (Exception e) { return null; } } private static byte[] getRequestPostBytes(HttpServletRequest request) throws IOException { int contentLength = request.getContentLength(); if (contentLength < 0) { return null; } byte[] buffer = new byte[contentLength]; for (int i = 0; i < contentLength; ) { int readLen = request.getInputStream().read(buffer, i, contentLength - i); if (readLen == -1) { break; } i += readLen; } return buffer; } private static String getRequestPostStr(HttpServletRequest request) throws Exception { byte[] buffer = getRequestPostBytes(request); String charEncoding = request.getCharacterEncoding(); if (charEncoding == null) { charEncoding = "UTF-8"; } if (buffer == null) { return "{}"; } return new String(buffer, charEncoding); } }
这里,我获取了用户请求的 url,以及参数,然后调用了 钉钉 机器人,总结起来,要拦截全局异常,只需要: 新增一个类,这个类添加 @ControllerAdvice 注解 然后这个类里写一个处理异常的方法,这个方法添加 @ExceptionHandler(Exception.class) 和 @ResponseBody 注解,前者的意思是,这个方法可以处理所有的 Exception ,后者的意思是 重写 返回的数据(没错,这么理解更简单),这就够了。但是这里,我增加了获取用户上传的 json 字符串以及 发送到 钉钉机器人,而且还发送到了自建的sentry 上面。
钉钉机器人文档可参考:https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
DingTalkMessage 源码如下:
import okhttp3.*; import java.io.IOException; public class DingTalkMessage { private static final String URL = "https://oapi.dingtalk.com/robot/send?access_token=" + "2ce54522b84e1c7193e9998e9b9a2afd859aa7e635354d55a91cc6xxxxxx"; private DingTalkMessageBuilder builder; public DingTalkMessage(DingTalkMessageBuilder builder) { this.builder = builder; } public void send() { String json = builder.build(); OkHttpClient okHttpClient = new OkHttpClient(); RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); Request request = new Request.Builder().post(body).url(URL).build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.getMessage(); } @Override public void onResponse(Call call, okhttp3.Response response) { try { ResponseBody responseBody = response.body(); if (responseBody != null) { System.out.println(responseBody.string()); } }catch (Exception ignore){ } } }); } }
DingTalkMessageBuilder 源码如下:
import com.alibaba.fastjson.JSON; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class DingTalkMessageBuilder { private final HashMap<String, Object> map; public DingTalkMessageBuilder() { map = new HashMap<>(); } public DingTalkMessageBuilder markdownMessage(String title, String text) { map.put("msgtype", "markdown"); Map<String, String> contentMap = new HashMap<>(); contentMap.put("title", title); contentMap.put("text", text); map.put("markdown", contentMap); return this; } public DingTalkMessageBuilder at(ArrayList<String> phones) { Map<String, Object> at = new HashMap<>(); at.put("atMobiles", phones); at.put("isAtAll", false); map.put("at", at); return this; } String build() { return JSON.toJSONString(map); } }
具体代码请点击下面,如果觉得有用请给个 star 谢谢。