先看效果

寫本項目的目的有幾點:
- 學習下vue+electron桌面開發
- 學習下java和spring開發(本人一直使用PHP)
- 一直缺少一款能適合自己的TODO LIST軟件,能有桌面端的

可直接打包成dmg、exe 等二進制文件使用。
這是我打包后的效果。
技術棧
- vue
- quasar
- electron
- springboot
- mysql
部分后端知識
自定義注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
自定義一個loginRequired注解,標注是否需要登錄
然后在攔截器里進行全局檢測
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
LoginRequired classRequired = method.getDeclaringClass().getAnnotation(LoginRequired.class);
// 判斷接口是否需要登錄
LoginRequired methodRequired = method.getAnnotation(LoginRequired.class);
if (classRequired == null && methodRequired == null) {
return true;
}
appService.initSession(); //token 方式驗證
if (request.getSession().getAttribute(App.SESSION_USER) != null) {
return true;
}
@RestControllerAdvice
利用@RestControllerAdvice注解進行全局控制器異常攔截
/**
* ConstraintViolationException
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response handleConstraintViolationException(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new Response(ResponseRet.parametrErrror, "參數錯誤", errors);
}
/**
* 違反約束異常 字段不為空等
*
* @param ex
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public Response handHibernateException(ConstraintViolationException ex) {
return new Response(ResponseRet.dbExecuteFail, ex.getSQLException().toString());
}
@ExceptionHandler(GenericJDBCException.class)
public Response handGenericJDBCException(GenericJDBCException ex) {
return new Response(ResponseRet.dbExecuteFail, ex.getSQLException().toString());
}
你可以全局處理entity定義的參數約束,或其他異常。
p6spy記錄sql和耗時

@Override
public void onAfterExecuteQuery(PreparedStatementInformation statementInformation, long timeElapsedNanos, SQLException e) {
App.sqlCount.set(App.sqlCount.get() + 1);
Long duration = timeElapsedNanos / 1000000;
App.sqlDuration.set(App.sqlDuration.get() + duration);
Log.info(String.format("執行sql || %s 耗時 %s ms", statementInformation.getSqlWithValues(), duration));
}
@Override
public void onAfterExecuteUpdate(PreparedStatementInformation statementInformation, long timeElapsedNanos, int rowCount, SQLException e) {
App.sqlCount.set(App.sqlCount.get() + 1);
Log.info(App.sqlCount.get().toString());
Long duration = timeElapsedNanos / 1000000;
App.sqlDuration.set(App.sqlDuration.get() + duration);
String singleLineSql = statementInformation.getSqlWithValues().replaceAll("\n", "\\\\n");
Log.info(String.format("執行sql || %s 耗時 %s ms", singleLineSql, duration));
}
記錄帶所有參數的sql和執行耗時
繼承DispatcherServlet記錄請求參數
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
//創建一個 json 對象,用來存放 http 日志信息
ObjectNode rootNode = mapper.createObjectNode();
rootNode.put("uri", requestWrapper.getRequestURI());
rootNode.put("clientIp", requestWrapper.getRemoteAddr());
// rootNode.set("requestHeaders", mapper.valueToTree(getRequestHeaders(requestWrapper)));
String method = requestWrapper.getMethod();
String contentType = requestWrapper.getContentType();
rootNode.put("method", method);
try {
super.doDispatch(requestWrapper, responseWrapper);
} finally {
if (method.equals("GET") || method.equals("DELETE")) {
rootNode.set("request", mapper.valueToTree(requestWrapper.getParameterMap()));
} else if (contentType.equals("application/x-www-form-urlencoded")) {
rootNode.set("request", mapper.valueToTree(requestWrapper.getParameterMap()));
} else {
JsonNode newNode = mapper.readTree(requestWrapper.getContentAsByteArray());
rootNode.set("request", newNode);
}
rootNode.put("status", responseWrapper.getStatus());
JsonNode newNode = mapper.readTree(responseWrapper.getContentAsByteArray());
rootNode.set("response", newNode);
responseWrapper.copyBodyToResponse();
// rootNode.set("responseHeaders", mapper.valueToTree(getResponsetHeaders(responseWrapper)));
Log.info(rootNode.toString());
}
}
