|
|
|
|
package xyz.wbsite.dbtool.web.action;
|
|
|
|
|
|
|
|
|
|
import org.apache.tomcat.util.http.fileupload.FileUtils;
|
|
|
|
|
import xyz.wbsite.dbtool.web.config.ActionConfig;
|
|
|
|
|
import xyz.wbsite.dbtool.web.frame.base.*;
|
|
|
|
|
import xyz.wbsite.dbtool.web.frame.auth.LocalData;
|
|
|
|
|
import xyz.wbsite.dbtool.web.frame.utils.FileUtil;
|
|
|
|
|
import xyz.wbsite.dbtool.web.frame.utils.MapperUtil;
|
|
|
|
|
import org.springframework.beans.BeansException;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.boot.web.servlet.error.ErrorController;
|
|
|
|
|
import org.springframework.http.HttpHeaders;
|
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
|
import org.springframework.http.MediaType;
|
|
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
|
|
import org.springframework.stereotype.Controller;
|
|
|
|
|
import org.springframework.ui.Model;
|
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
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 java.lang.reflect.InvocationTargetException;
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
|
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)}
|
|
|
|
|
* 全局异常捕捉{@link GlobalController#exceptionHandler(HttpServletRequest, HttpServletResponse, Model, Exception)}
|
|
|
|
|
* 全局上传接口{@link GlobalController#upload(HttpServletRequest)}
|
|
|
|
|
* 全局下载接口{@link GlobalController#download(String)}
|
|
|
|
|
* 全局消息订阅{@link GlobalController#sse(String)}
|
|
|
|
|
* <p>
|
|
|
|
|
* 说明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("<a>").append(message).append("</a><br>");
|
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
|
msg.append("<a>").append(exception.getStackTrace()[i]).append("</a><br>");
|
|
|
|
|
}
|
|
|
|
|
} 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("/upload")
|
|
|
|
|
@ResponseBody
|
|
|
|
|
public BaseResponse upload(HttpServletRequest request) {
|
|
|
|
|
FileUploadResponse fileUploadResponse = new FileUploadResponse();
|
|
|
|
|
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
|
|
|
|
|
MultipartFile target = multipartHttpServletRequest.getFile("file");
|
|
|
|
|
|
|
|
|
|
String fileName = target.getOriginalFilename();
|
|
|
|
|
|
|
|
|
|
//========
|
|
|
|
|
//处理文件
|
|
|
|
|
//========
|
|
|
|
|
fileUploadResponse.setId(1L);
|
|
|
|
|
fileUploadResponse.setUrl("example.com\\img\\1.jpg");
|
|
|
|
|
fileUploadResponse.setDownloadUrl("example.com\\img\\1.jpg");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (target != null) {
|
|
|
|
|
fileUploadResponse.addError(ErrorType.BUSINESS_ERROR, "文件上传成功,但未处理文件[" + fileName + "]!");
|
|
|
|
|
} else {
|
|
|
|
|
fileUploadResponse.addError(ErrorType.BUSINESS_ERROR, "文件上传失败!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fileUploadResponse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@RequestMapping("/ajax/{module}/{target}/{method}")
|
|
|
|
|
@ResponseBody
|
|
|
|
|
public BaseResponse ajax(@PathVariable String module, @PathVariable String target, @PathVariable String method, @RequestBody String param) {
|
|
|
|
|
BaseResponse baseResponse = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
String beanClassName = (ActionConfig.AJAX_PREFIX + module + "/" + target).toLowerCase();
|
|
|
|
|
Object ajax = LocalData.getApplicationContext().getBean(beanClassName);
|
|
|
|
|
|
|
|
|
|
Class ajaxClass = ajax.getClass();
|
|
|
|
|
Method methodC = ajaxClass.getMethod(method, String.class);
|
|
|
|
|
Object invoke = methodC.invoke(ajax, param);
|
|
|
|
|
if (invoke instanceof BaseResponse) {
|
|
|
|
|
baseResponse = (BaseResponse) invoke;
|
|
|
|
|
} else {
|
|
|
|
|
baseResponse = new BaseResponse();
|
|
|
|
|
baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法返回值错误!");
|
|
|
|
|
}
|
|
|
|
|
} catch (BeansException e) {
|
|
|
|
|
baseResponse = new BaseResponse();
|
|
|
|
|
baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的目标!");
|
|
|
|
|
} catch (NoSuchMethodException e) {
|
|
|
|
|
baseResponse = new BaseResponse();
|
|
|
|
|
baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!");
|
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
|
baseResponse = new BaseResponse();
|
|
|
|
|
baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!");
|
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
|
baseResponse = new BaseResponse();
|
|
|
|
|
baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执行错误!");
|
|
|
|
|
}
|
|
|
|
|
return baseResponse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RequestMapping("/download")
|
|
|
|
|
@ResponseBody
|
|
|
|
|
public ResponseEntity<byte[]> download(@RequestParam(value = "file", required = false) String file) throws IOException {
|
|
|
|
|
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
|
|
|
|
|
|
|
|
|
//========
|
|
|
|
|
//下载DEMO
|
|
|
|
|
//========
|
|
|
|
|
if (file == null) {
|
|
|
|
|
file = "test.txt";
|
|
|
|
|
headers.setContentDispositionFormData("attachment", new String(file.getBytes("UTF-8"), "iso-8859-1"));
|
|
|
|
|
return new ResponseEntity<byte[]>("test".getBytes(),
|
|
|
|
|
headers, HttpStatus.CREATED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new ResponseEntity<byte[]>(FileUtil.readFileToByteArray(new File(file)),
|
|
|
|
|
headers, HttpStatus.CREATED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static ConcurrentHashMap<String, SseEmitter> 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|