You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

441 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package ${basePackage}.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.RequestMethod;
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 ${basePackage}.config.ActionConfig;
import ${basePackage}.frame.auth.LocalData;
import ${basePackage}.frame.base.BaseRequest;
import ${basePackage}.frame.base.BaseResponse;
import ${basePackage}.frame.base.ErrorType;
import ${basePackage}.frame.base.Screen;
import ${basePackage}.frame.base.Token;
import ${basePackage}.frame.utils.AESUtil;
import ${basePackage}.frame.utils.LogUtil;
import ${basePackage}.frame.utils.MD5Util;
import ${basePackage}.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)}
* <p>
* 说明Request命名规则驼峰式命名
* Api#Example#Request ==> 目标#动作#Request
*
* @author author
* @version 0.0.1
* @since 2017-01-01
*/
@Controller
@ControllerAdvice
public class GlobalController implements ErrorController {
@Value("${r'${web.welcome.page}'}")
private String homePage;
@Value("${r'${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:
if (LocalData.getToken() == null) {
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 httpServletRequest,
HttpServletResponse httpServletResponse,
@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] = httpServletRequest;
} else if (parameter.getType() == HttpServletResponse.class) {
arg[i] = httpServletResponse;
} 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;
}
}
@RequestMapping(path = "/api/{module}/{target}/{method}", method = RequestMethod.POST)
@ResponseBody
public String api(
@PathVariable String module,
@PathVariable String target,
@PathVariable String method,
@RequestParam(required = false) String appKey,
@RequestParam(required = false) String sign,
@RequestParam(required = false) Long timestamp,
@RequestParam(required = false) String token,
@RequestParam(required = false) String encryptData,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
BaseResponse response = new BaseResponse();
if (appKey == null) {
response.addError(ErrorType.BUSINESS_ERROR, "应用码参数[appKey]不存在!");
return MapperUtil.toJson(response);
} else if (sign == null) {
response.addError(ErrorType.BUSINESS_ERROR, "签名参数[sign]不存在!");
return MapperUtil.toJson(response);
} else if (timestamp == null) {
response.addError(ErrorType.BUSINESS_ERROR, "时间戳参数[timestamp]不存在!");
return MapperUtil.toJson(response);
}
String data = null;
String appSecret = "1234567890123456";
// 解码
try {
data = AESUtil.decrypt2String(encryptData, appSecret);
} catch (Exception e) {
response.addError(ErrorType.BUSINESS_ERROR, "解码失败,请确认编码是否正确!");
return MapperUtil.toJson(response);
}
// 验证签名
String sign_ = MD5Util.encode(appSecret + data + timestamp);
if (!sign_.equals(sign)) {
response.addError(ErrorType.BUSINESS_ERROR, "签名验证失败!");
return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
}
// 时效性验证
long currentTime = System.currentTimeMillis();
if (currentTime - timestamp > 2 * 60 * 1000) {
response.addError(ErrorType.BUSINESS_ERROR, "请求过期, 或本地时间错误!");
return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
}
// 权限验证
if (!LocalData.getToken().hasResource(httpServletRequest.getServletPath())) {
response.addError(ErrorType.BUSINESS_ERROR, "[" + httpServletRequest.getServletPath() + "]未授权的资源!");
return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
}
// 开始处理业务
try {
String beanClassName = (ActionConfig.API_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) {
response.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!");
return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
}
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] = httpServletRequest;
} else if (parameter.getType() == HttpServletResponse.class) {
arg[i] = httpServletResponse;
} else if (BaseRequest.class.isAssignableFrom(parameter.getType())) {
arg[i] = MapperUtil.toJava(data, parameter.getType());
} else if (parameter.getType() == TreeNode.class) {
arg[i] = MapperUtil.toTree(data);
}
}
response = (BaseResponse) methodC.invoke(ajax, arg);
} catch (BeansException e) {
e.printStackTrace();
response.addError(ErrorType.BUSINESS_ERROR, "未找到对应的目标!");
} catch (IllegalAccessException e) {
e.printStackTrace();
response.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!");
} catch (InvocationTargetException e) {
LogUtil.dumpException(e.getTargetException());
e.getTargetException().printStackTrace();
response.addError(ErrorType.BUSINESS_ERROR, "方法执行错误[" + e.getTargetException().getMessage() + "]");
}
return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
}
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);
}
}
}
}

Powered by TurnKey Linux.