package xyz.wbsite.dbtool.web.action; import com.fasterxml.jackson.core.TreeNode; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.View; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; import xyz.wbsite.dbtool.web.config.ActionConfig; import xyz.wbsite.dbtool.web.frame.auth.LocalData; import xyz.wbsite.dbtool.web.frame.base.BaseRequest; import xyz.wbsite.dbtool.web.frame.base.BaseResponse; import xyz.wbsite.dbtool.web.frame.base.ErrorType; import xyz.wbsite.dbtool.web.frame.base.Screen; import xyz.wbsite.dbtool.web.frame.base.Token; import xyz.wbsite.dbtool.web.frame.utils.MapperUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 全局请求Controller,如果无特殊请求,则不需再增加其他Controller * 全局htm后缀入口{@link GlobalController#action(Model, HttpServletRequest, HttpServletResponse)} * 全局ajax入口{@link GlobalController#ajax(String, String, String, HttpServletRequest, HttpServletResponse, String, MultipartFile)} * 全局异常捕捉{@link GlobalController#exceptionHandler(HttpServletRequest, HttpServletResponse, Model, Exception)} * 全局消息订阅{@link GlobalController#sse(String)} *

* 说明Request命名规则,驼峰式命名 * Api#Example#Request ==> 目标#动作#Request * * @author author * @version 0.0.1 * @since 2019-06-16 */ @Controller @ControllerAdvice public class GlobalController implements ErrorController { @Value("${web.welcome.page}") private String homePage; @Value("${web.login.page}") private String loginPage; @Autowired private FreeMarkerViewResolver viewResolver; /** * 全局异常捕捉 * * @param request * @param response * @param exception 要捕获的异常 * @return */ @ExceptionHandler(Exception.class) public String exceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, Exception exception) { StringBuffer msg = new StringBuffer(""); if (exception != null) { msg = new StringBuffer(""); String message = exception.toString(); int length = exception.getStackTrace().length; if (length > 0) { msg.append("").append(message).append("
"); for (int i = 0; i < length; i++) { msg.append("").append(exception.getStackTrace()[i]).append("
"); } } else { msg.append(message); } } response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); model.addAttribute("msg", msg.toString()); return "500"; } private final static String ERROR_PATH = "/error"; @Override public String getErrorPath() { return ERROR_PATH; } @RequestMapping(value = ERROR_PATH) public String error(HttpServletRequest request, HttpServletResponse response) { switch (response.getStatus()) { case 404: return "404"; case 403: try { response.sendRedirect("/login.htm"); } catch (IOException e) { e.printStackTrace(); } return "403"; case 500: return "500"; default: return "403"; } } @RequestMapping("/") public String home() { Token token = LocalData.getToken(); if (token == null) { return "redirect:" + loginPage; } else { return "redirect:" + homePage; } } /** * 当未明确指定控制器时,走该请求,默认返回对应的layout布局和screen视图 * 当需要使用layout时,不需要返回值,ViewNameTranslator会处理对应关系 * * @param model * @param request */ @RequestMapping({"/**/*.htm"}) public String action(Model model, HttpServletRequest request, HttpServletResponse response) { String servletPath = request.getServletPath();// /**/*.htm String layout = "/layout/default"; String action = LocalData.getAction();// **/* Pattern compile = Pattern.compile("^/(.+)\\.htm"); Matcher matcher = compile.matcher(servletPath); if (matcher.find()) { action = matcher.group(1); LocalData.setAction(action); } try { LocaleResolver localeResolver = (LocaleResolver) request.getAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE); Locale locale = localeResolver.resolveLocale(request); {//查询screen String[] split = action.split("/"); StringBuilder sb = new StringBuilder(""); sb.append("screen"); for (int i = 0; i < split.length; i++) { sb.append(File.separator); sb.append(split[i]); } layout = sb.toString(); View view = viewResolver.resolveViewName(layout, locale); if (view == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return error(request, response); } // 尝试执行Screen执行器(服务器渲染),并返回视图模板 try { String beanClassName = (ActionConfig.SCREEN_PREFIX + action).toLowerCase(); Screen screenExec = LocalData.getApplicationContext().getBean(beanClassName, Screen.class); screenExec.exec(model, request, response); if (response.getStatus() != HttpServletResponse.SC_OK) { return error(request, response); } } catch (BeansException e) { } } {//查找layout String[] split = action.split("/"); int lt = split.length; while (lt > 0) { StringBuilder sb = new StringBuilder(""); sb.append("layout"); for (int i = 0; i < lt - 1; i++) { sb.append(File.separator); sb.append(split[i]); } layout = sb.toString() + File.separator + split[split.length - 1]; View view = viewResolver.resolveViewName(layout, locale); //无法找到对应layout,使用默认layout if (view == null) { layout = sb.toString() + File.separator + "default"; View defaultView = viewResolver.resolveViewName(layout, locale); if (null == defaultView && lt == 1) { System.err.println("can not find layout/default.ftl"); } else if (null == defaultView) { lt--; } else { break; } } else { break; } } } } catch (Exception e) { return exceptionHandler(request, response, model, e); } //todo 可在此获取共性数据(也可以在全局拦截器GlobalHandlerInterceptor、拦截器作用域比此更高), //todo 例如用户信息等。其他业务数据在页面渲染后通过Ajax请求 return layout; } @RequestMapping("/ajax/{module}/{target}/{method}") @ResponseBody public Object ajax( @PathVariable String module, @PathVariable String target, @PathVariable String method, HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) String data, @RequestParam(name = "file", required = false) MultipartFile file) { try { String beanClassName = (ActionConfig.AJAX_PREFIX + module + "/" + target).toLowerCase(); Object ajax = LocalData.getApplicationContext().getBean(beanClassName); Class ajaxClass = ajax.getClass(); Method[] methods = ajaxClass.getDeclaredMethods(); Method methodC = null; for (Method meth : methods) { if (meth.getName().equals(method)) { methodC = meth; } } if (methodC == null) { BaseResponse baseResponse = new BaseResponse(); baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!"); return baseResponse; } Parameter[] parameters = methodC.getParameters(); Object[] arg = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; if (parameter.getType() == HttpServletRequest.class) { arg[i] = request; } else if (parameter.getType() == HttpServletResponse.class) { arg[i] = response; } else if (parameter.getType() == TreeNode.class) { arg[i] = MapperUtil.toTree(data); } else if (parameter.getType() == String.class) { arg[i] = data; } else if (parameter.getType() == MultipartFile.class) { arg[i] = file; } else if (BaseRequest.class.isAssignableFrom(parameter.getType())) { arg[i] = MapperUtil.toJava(data, parameter.getType()); } } return methodC.invoke(ajax, arg); } catch (BeansException e) { e.printStackTrace(); BaseResponse baseResponse = new BaseResponse(); baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的目标!"); return baseResponse; } catch (IllegalAccessException e) { e.printStackTrace(); BaseResponse baseResponse = new BaseResponse(); baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!"); return baseResponse; } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); BaseResponse baseResponse = new BaseResponse(); baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执行错误[" + e.getTargetException().getMessage() + "]"); return baseResponse; } } private static ConcurrentHashMap sseMap = new ConcurrentHashMap(); /** * Sse推送服务,服务器向js推送自定义消息 * Sse容器{@link GlobalController#sseMap} * Sse批量推送{@link GlobalController#pushAll} */ @RequestMapping(value = "/sse/{userId}", produces = "text/event-stream;charset=UTF-8") public SseEmitter sse(@PathVariable String userId) { SseEmitter sseEmitter = new SseEmitter(10000000L); if (sseMap.get(userId) != null) { sseMap.remove(userId); } sseMap.put(userId, sseEmitter); return sseEmitter; } /** * Sse批量推送 * * @param data 推送对象 */ public static void pushAll(Object data) { for (String s : sseMap.keySet()) { try { sseMap.get(s).send(MapperUtil.toJson(data), MediaType.APPLICATION_JSON); } catch (IOException e) { sseMap.remove(s); } } } }