vue+quasar+electron+springboot+mysql撸一个TODO LIST 看板

先看效果

写本项目的目的有几点:

  1. 学习下vue+electron桌面开发
  2. 学习下java和spring开发(本人一直使用PHP)
  3. 一直缺少一款能适合自己的TODO LIST软件,能有桌面端的

可直接打包成dmg、exe 等二进制文件使用。
这是我打包后的效果。

技术栈

  • vue
  • quasar
  • electron
  • springboot
  • mysql

spring技术

  • MDC 请求串
  • 全局参数校验
  • 记录请求参数和返回参数
  • 自定义注解拦截登录检测
  • JPA 关系映射

部分后端知识

自定义注解

@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());
        }
    }

源码地址

1 个赞

码友,你这返回null是啥意思? :neutral_face:

    public Iterable<TaskList> getAllTaskListWithTaskByUser(User user) {
        Iterable<Task> tasks = taskRepository.findAllByUser(user);
        return null;
    }

哈哈,这个应该没用到。所以我也没法现。

1 个赞