diff --git a/src/main/java/xyz/wbsite/action/GlobalController.java b/src/main/java/xyz/wbsite/action/GlobalController.java
index 5c304a5..330e79c 100644
--- a/src/main/java/xyz/wbsite/action/GlobalController.java
+++ b/src/main/java/xyz/wbsite/action/GlobalController.java
@@ -4,14 +4,11 @@ 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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -23,35 +20,35 @@ 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.config.ActionConfig;
-import xyz.wbsite.frame.auth.LocalData;
+import xyz.wbsite.frame.base.LocalData;
+import xyz.wbsite.frame.base.Token;
+import xyz.wbsite.frame.base.VisitorEntity;
import xyz.wbsite.frame.base.BaseRequest;
import xyz.wbsite.frame.base.BaseResponse;
import xyz.wbsite.frame.base.ErrorType;
import xyz.wbsite.frame.base.Screen;
-import xyz.wbsite.frame.auth.Token;
+import xyz.wbsite.frame.listener.FrameListener;
+import xyz.wbsite.frame.provider.TokenProvider;
+import xyz.wbsite.frame.provider.VisitorProvider;
+import xyz.wbsite.frame.sse.Sser;
import xyz.wbsite.frame.utils.AESUtil;
-import xyz.wbsite.frame.utils.LogUtil;
import xyz.wbsite.frame.utils.MD5Util;
import xyz.wbsite.frame.utils.MapperUtil;
import xyz.wbsite.frame.utils.RequestUtil;
+import xyz.wbsite.frame.utils.StringUtil;
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)}
+ * 全局htm后缀入口{@link GlobalController#page(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命名规则,驼峰式命名
@@ -62,77 +59,17 @@ import java.util.regex.Pattern;
* @since 2017-01-01
*/
@Controller
-@ControllerAdvice
-public class GlobalController implements ErrorController {
+public class GlobalController {
@Value("${server.servlet.context-path}")
private String context;
- @Value("${web.home.page}")
+ @Value("${web.url.index}")
private String homePage;
- @Value("${web.login.page}")
+ @Value("${web.url.login}")
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:
- String errorUrl = RequestUtil.getErrorUrl(request);
- errorUrl = errorUrl.replaceFirst(context, "");
- if ((errorUrl.equals(homePage) || errorUrl.equals("/")) && LocalData.getToken() == null) {
- try {
- response.sendRedirect(context + loginPage);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return "403";
- case 500:
- return "500";
- default:
- return "403";
- }
- }
-
@RequestMapping("/")
public String home() {
Token token = LocalData.getToken();
@@ -151,45 +88,31 @@ public class GlobalController implements ErrorController {
* @param request
*/
@RequestMapping({"/**/*.htm"})
- public String action(Model model, HttpServletRequest request, HttpServletResponse response) {
+ public String page(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);
- }
+ String page = RequestUtil.getPage();
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();
+ layout = "screen/" + page;
View view = viewResolver.resolveViewName(layout, locale);
if (view == null) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND,"");
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "");
return null;
}
// 尝试执行Screen执行器(服务器渲染),并返回视图模板
try {
- String beanClassName = (ActionConfig.SCREEN_PREFIX + action).toLowerCase();
+ String beanClassName = (ActionConfig.SCREEN_PREFIX + page).toLowerCase();
Screen screenExec = LocalData.getApplicationContext().getBean(beanClassName, Screen.class);
screenExec.exec(model, request, response);
if (response.getStatus() != HttpServletResponse.SC_OK) {
- response.sendError(response.getStatus(),"");
+ response.sendError(response.getStatus(), "");
return null;
}
} catch (BeansException e) {
@@ -198,7 +121,7 @@ public class GlobalController implements ErrorController {
}
{//查找layout
- String[] split = action.split("/");
+ String[] split = page.split("/");
int lt = split.length;
while (lt > 0) {
@@ -230,11 +153,12 @@ public class GlobalController implements ErrorController {
}
}
} catch (Exception e) {
- return exceptionHandler(request, response, model, e);
+ throw new RuntimeException(e);
}
// todo 可在此获取共性数据(也可以在全局拦截器GlobalHandlerInterceptor、拦截器作用域比此更高),
// todo 例如用户信息等。其他业务数据在页面渲染后通过Ajax请求
+ model.addAttribute("token", LocalData.getToken());
return layout;
}
@@ -265,7 +189,7 @@ public class GlobalController implements ErrorController {
if (methodC == null) {
BaseResponse baseResponse = new BaseResponse();
- baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!");
+ baseResponse.addError(ErrorType.BUSINESS_ERROR, "未实现的资源方法!");
return baseResponse;
}
@@ -301,17 +225,16 @@ public class GlobalController implements ErrorController {
} catch (IllegalAccessException e) {
e.printStackTrace();
BaseResponse baseResponse = new BaseResponse();
- baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!");
+ 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() + "]");
+ baseResponse.addError(ErrorType.BUSINESS_ERROR, "资源方法执行错误[" + e.getTargetException().getMessage() + "]");
return baseResponse;
}
}
-
@RequestMapping(path = "/api/{module}/{target}/{method}", method = RequestMethod.POST)
@ResponseBody
public String api(
@@ -337,34 +260,60 @@ public class GlobalController implements ErrorController {
return MapperUtil.toJson(response);
}
- String data = null;
- String appSecret = "1234567890123456";
+ String data;
+ VisitorProvider visitorProvider = LocalData.getBean(VisitorProvider.class);
+ if (visitorProvider == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "接入提供者不存在!");
+ return MapperUtil.toJson(response);
+ }
+ // 检索接入对象
+ VisitorEntity visitorEntity = visitorProvider.getVisitor(appKey);
+ if (visitorEntity == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "接入信息错误!");
+ return MapperUtil.toJson(response);
+ }
+
// 解码
try {
- data = AESUtil.decrypt2String(encryptData, appSecret);
+ data = AESUtil.decrypt2String(encryptData, visitorEntity.getAppSecret());
} catch (Exception e) {
response.addError(ErrorType.BUSINESS_ERROR, "解码失败,请确认编码是否正确!");
return MapperUtil.toJson(response);
}
// 验证签名
- String sign_ = MD5Util.encode(appSecret + data + timestamp);
+ String sign_ = MD5Util.encode(visitorEntity.getAppSecret() + data + timestamp);
if (!sign_.equals(sign)) {
response.addError(ErrorType.BUSINESS_ERROR, "签名验证失败!");
- return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
}
// 时效性验证
long currentTime = System.currentTimeMillis();
if (currentTime - timestamp > 2 * 60 * 1000) {
response.addError(ErrorType.BUSINESS_ERROR, "请求过期, 或本地时间错误!");
- return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
}
// 权限验证
- if (!LocalData.getToken().hasRes(httpServletRequest.getServletPath())) {
- response.addError(ErrorType.BUSINESS_ERROR, "[" + httpServletRequest.getServletPath() + "]未授权的资源!");
- return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
+ if (StringUtil.isNotEmpty(token)) {
+ TokenProvider tokenProvider = LocalData.getBean(TokenProvider.class);
+ if (tokenProvider == null) {
+ LocalData.setToken(null);
+ } else {
+ Token build = tokenProvider.build(token);
+ LocalData.setToken(build);
+ }
+ if (!LocalData.getToken().hasRes(httpServletRequest.getServletPath())) {
+ response.addError(ErrorType.BUSINESS_ERROR, "[" + httpServletRequest.getServletPath() + "]未授权的资源!");
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
+ }
+ } else {
+ String excluded = LocalData.getEnvironment().getProperty("web.url.auth.excluded", "") + ",/api/wsys/User/login";
+ if (!excluded.contains(excluded)) {
+ response.addError(ErrorType.BUSINESS_ERROR, "请通过登录接口获取Token!");
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
+ }
}
// 开始处理业务
@@ -383,7 +332,7 @@ public class GlobalController implements ErrorController {
if (methodC == null) {
response.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!");
- return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
}
Parameter[] parameters = methodC.getParameters();
@@ -413,42 +362,79 @@ public class GlobalController implements ErrorController {
e.printStackTrace();
response.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!");
} catch (InvocationTargetException e) {
- LogUtil.dumpException(e.getTargetException());
- e.getTargetException().printStackTrace();
+ FrameListener instance = FrameListener.getInstance();
+ instance.onError(e.getTargetException());
response.addError(ErrorType.BUSINESS_ERROR, "方法执行错误[" + e.getTargetException().getMessage() + "]");
}
- return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), appSecret);
+ return AESUtil.encrypt2Base64(MapperUtil.toJson(response).getBytes(), visitorEntity.getAppSecret());
}
- private static ConcurrentHashMap sseMap = new ConcurrentHashMap();
+ @RequestMapping(path = "/msvr/{module}/{target}/{method}", method = RequestMethod.POST)
+ @ResponseBody
+ public BaseResponse msvr(
+ @PathVariable String module,
+ @PathVariable String target,
+ @PathVariable String method,
+ @RequestBody String data,
+ @RequestHeader String token) {
+ BaseResponse response = new BaseResponse();
- /**
- * 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);
+ LocalData.setToken(MapperUtil.toJava(token, Token.class));
+
+ // 开始处理业务
+ try {
+ String beanClassName = (ActionConfig.MSVR_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 response;
+ }
+
+ Parameter[] parameters = methodC.getParameters();
+ Object[] arg = new Object[parameters.length];
+
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter parameter = parameters[i];
+ if (BaseRequest.class.isAssignableFrom(parameter.getType())) {
+ arg[i] = MapperUtil.toJava(data, parameter.getType());
+ } else if (parameter.getType() == Token.class) {
+ arg[i] = LocalData.getToken();
+ } else {
+ arg[i] = null;
+ }
+ }
+ 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) {
+ FrameListener instance = FrameListener.getInstance();
+ instance.onError(e.getTargetException());
+ response.addError(ErrorType.BUSINESS_ERROR, "服务方法执行错误[" + e.getTargetException().getMessage() + "]");
}
- sseMap.put(userId, sseEmitter);
- return sseEmitter;
+ return response;
}
/**
- * Sse批量推送
- *
- * @param data 推送对象
+ * Sse推送服务,服务器向js推送自定义消息
+ * Sse推送{@link Sser#push(String, Object)}
+ * Sse全部推送{@link Sser#pushAll(Object)}
*/
- 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);
- }
- }
+ @RequestMapping(value = "/sse/{userId}", produces = "text/event-stream;charset=UTF-8")
+ public SseEmitter sse(@PathVariable String userId) {
+ return Sser.register(userId, new SseEmitter(10000000L));
}
}
diff --git a/src/main/java/xyz/wbsite/action/ajax/admin/ServicesAjax.java b/src/main/java/xyz/wbsite/action/ajax/admin/ServicesAjax.java
new file mode 100644
index 0000000..094e2be
--- /dev/null
+++ b/src/main/java/xyz/wbsite/action/ajax/admin/ServicesAjax.java
@@ -0,0 +1,108 @@
+package xyz.wbsite.action.ajax.admin;
+
+import com.alibaba.excel.support.ExcelTypeEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.multipart.MultipartFile;
+import xyz.wbsite.frame.base.Error;
+import xyz.wbsite.frame.excel.WExcel;
+import xyz.wbsite.frame.utils.LogUtil;
+import xyz.wbsite.frame.base.LocalData;
+import xyz.wbsite.frame.base.ErrorType;
+import xyz.wbsite.frame.utils.MapperUtil;
+import xyz.wbsite.frame.base.BaseResponse;
+import xyz.wbsite.frame.utils.ResponseUtil;
+import xyz.wbsite.frame.utils.ValidationUtil;
+import xyz.wbsite.frame.excel.exception.TemplateNotMatchException;
+import xyz.wbsite.module.admin.ent.Services;
+import xyz.wbsite.module.admin.mgr.ServicesManager;
+import xyz.wbsite.module.admin.req.ServicesCreateRequest;
+import xyz.wbsite.module.admin.req.ServicesDeleteRequest;
+import xyz.wbsite.module.admin.req.ServicesFindRequest;
+import xyz.wbsite.module.admin.req.ServicesUpdateRequest;
+import xyz.wbsite.module.admin.rsp.ServicesCreateResponse;
+import xyz.wbsite.module.admin.rsp.ServicesDeleteResponse;
+import xyz.wbsite.module.admin.rsp.ServicesFindResponse;
+import xyz.wbsite.module.admin.rsp.ServicesUpdateResponse;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ServicesAjax{
+
+ @Autowired
+ private ServicesManager servicesManager;
+
+ public ServicesCreateResponse create(ServicesCreateRequest request) {
+ return servicesManager.create(request, LocalData.getToken());
+ }
+
+ public ServicesDeleteResponse delete(ServicesDeleteRequest request) {
+ return servicesManager.delete(request, LocalData.getToken());
+ }
+
+ public ServicesUpdateResponse update(ServicesUpdateRequest request) {
+ return servicesManager.update(request, LocalData.getToken());
+ }
+
+ public ServicesFindResponse find(ServicesFindRequest request) {
+ return servicesManager.find(request, LocalData.getToken());
+ }
+
+ public Object template(){
+ return ResponseUtil.apply(new WExcel<>(Services.class));
+ }
+
+ public Object exports(ServicesFindRequest request) {
+ ServicesFindResponse response = servicesManager.find(request, LocalData.getToken());
+ if (response.hasError()) {
+ return response;
+ } else if (response.getTotalCount() == 0) {
+ response.addError(ErrorType.BUSINESS_ERROR, "导出数据为空");
+ return response;
+ }
+ return ResponseUtil.apply(new WExcel<>(Services.class).addDatas(response.getResult()));
+ }
+
+ public Object imports(MultipartFile file) {
+ BaseResponse baseResponse = new BaseResponse();
+ try {
+ // 检查文件格式
+ String originalFilename = file.getOriginalFilename() != null ? file.getOriginalFilename() : "";
+ if (!originalFilename.matches(".+(.xlsx?|.XLSX?)$")) {
+ baseResponse.addError(ErrorType.BUSINESS_ERROR, "上传文件格式错误!");
+ return baseResponse;
+ }
+ // 兼容2003以前老版本.xls
+ ExcelTypeEnum excelTypeEnum = originalFilename.matches(".+(.xlsx|.XLSX)$") ? ExcelTypeEnum.XLSX : ExcelTypeEnum.XLS;
+ WExcel sheet = new WExcel<>(Services.class).read(file.getBytes(), excelTypeEnum, new WExcel.Processor() {
+ @Override
+ public List exec(Services o, int index) {
+ ServicesCreateRequest request = MapperUtil.map(o, ServicesCreateRequest.class);
+ List validate = ValidationUtil.validate(request);
+ if (validate == null || validate.size() == 0) {
+ ServicesCreateResponse servicesCreateResponse = servicesManager.create(request, LocalData.getToken());
+ if (servicesCreateResponse.hasError()) {
+ for (Error error : servicesCreateResponse.getErrors()) {
+ validate.add(error.getMessage());
+ }
+ }
+ }
+ return validate;
+ }
+ });
+ // 当导入出现错误时可以将存在标注错误的Excel返回给用户改正
+ if (sheet.hasError()) {
+ return ResponseUtil.apply(sheet.getBytes(), sheet.getName() + "-检查.xlsx");
+ } else {
+ return baseResponse;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ LogUtil.dumpException(e);
+ baseResponse.addError(ErrorType.BUSINESS_ERROR, "上传文件出错");
+ } catch (TemplateNotMatchException e) {
+ baseResponse.addError(ErrorType.BUSINESS_ERROR, e.getMessage());
+ }
+ return baseResponse;
+ }
+}
diff --git a/src/main/java/xyz/wbsite/action/ajax/wframe/AuthAjax.java b/src/main/java/xyz/wbsite/action/ajax/wframe/AuthAjax.java
new file mode 100644
index 0000000..4e4a2e1
--- /dev/null
+++ b/src/main/java/xyz/wbsite/action/ajax/wframe/AuthAjax.java
@@ -0,0 +1,124 @@
+package xyz.wbsite.action.ajax.wframe;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import xyz.wbsite.config.CacheConfig;
+import xyz.wbsite.frame.base.ErrorType;
+import xyz.wbsite.frame.base.LocalData;
+import xyz.wbsite.frame.base.LoginRequest;
+import xyz.wbsite.frame.base.LoginResponse;
+import xyz.wbsite.frame.base.LogoutRequest;
+import xyz.wbsite.frame.base.LogoutResponse;
+import xyz.wbsite.frame.base.Token;
+import xyz.wbsite.frame.base.UserEntity;
+import xyz.wbsite.frame.base.VerifyCodeRequest;
+import xyz.wbsite.frame.base.VerifyCodeResponse;
+import xyz.wbsite.frame.provider.FrameProvider;
+import xyz.wbsite.frame.provider.TokenProvider;
+import xyz.wbsite.frame.provider.UserProvider;
+import xyz.wbsite.frame.utils.CookieUtil;
+import xyz.wbsite.frame.utils.IDgenerator;
+import xyz.wbsite.frame.utils.MD5Util;
+import xyz.wbsite.frame.utils.ValidationUtil;
+import xyz.wbsite.frame.utils.VerifyCodeUtil;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+public class AuthAjax {
+
+ @Autowired
+ private CacheConfig cacheConfig;
+
+ public VerifyCodeResponse verifyCode(VerifyCodeRequest request) {
+ VerifyCodeResponse response = new VerifyCodeResponse();
+ VerifyCodeUtil.Builder builder = new VerifyCodeUtil.Builder().build();
+ response.setVerifyCodeId(IDgenerator.nextId());
+ response.setVerifyCodeBase64(builder.toBase64());
+ cacheConfig.put(response.getVerifyCodeId(), builder.toCode(), 3 * 60 * 1000);
+ return response;
+ }
+
+ public LoginResponse login(LoginRequest request, HttpServletResponse httpServletResponse) {
+ LoginResponse response = new LoginResponse();
+
+ ValidationUtil.validate(request, response);
+ if (response.hasError()) {
+ return response;
+ }
+
+ // 获取验证码
+ String o = cacheConfig.remove(request.getVerifyCodeId(), String.class);
+ if (o == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "验证码已过期!");
+ return response;
+ }
+
+ // 验证验证码
+ if (!request.getVerifyCodeCode().toLowerCase().equals(o.toLowerCase())) {
+ response.addError(ErrorType.BUSINESS_ERROR, "验证码错误!");
+ cacheConfig.remove(request.getVerifyCodeId());
+ return response;
+ }
+
+ // 获取用户提供者
+ UserProvider userProvider = FrameProvider.getInstance().getUserProvider();
+ if (userProvider == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "用户提供者未实现!");
+ return response;
+ }
+
+ // 获取用户信息
+ UserEntity userEntity = userProvider.getUser(request.getUsername());
+ if (userEntity == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "用户名或密码错误!");
+ return response;
+ }
+
+ // 验证密码
+ String generatePwd = MD5Util.generatePwd(request.getPassword());
+ if (!generatePwd.equals(userEntity.getPassword())) {
+ response.addError(ErrorType.BUSINESS_ERROR, "用户名或密码错误!");
+ return response;
+ }
+
+ // 获取通行证提供者
+ TokenProvider tokenProvider = FrameProvider.getInstance().getTokenProvider();
+ if (tokenProvider == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "通行证提供者未实现!");
+ return response;
+ }
+
+ // 构建通行证
+ Token token = tokenProvider.build(userEntity);
+ if (token == null) {
+ response.addError(ErrorType.BUSINESS_ERROR, "通行证构建失败!");
+ return response;
+ }
+ response.setToken(token.getToken());
+ Cookie cookie = CookieUtil.newCookie("token", response.getToken());
+ httpServletResponse.addCookie(cookie);
+ return response;
+ }
+
+ public LogoutResponse logout(LogoutRequest request) {
+ LogoutResponse response = new LogoutResponse();
+
+ Token token = LocalData.getToken();
+ if (token != null) {
+ cacheConfig.clear(token.getToken());
+ }
+ CookieUtil.clearCookie("token");
+ return response;
+ }
+
+ public LogoutResponse changePwd(LogoutRequest request) {
+ LogoutResponse response = new LogoutResponse();
+
+ Token token = LocalData.getToken();
+ if (token != null) {
+ cacheConfig.clear(token.getToken());
+ }
+ CookieUtil.clearCookie("token");
+ return response;
+ }
+}
diff --git a/src/main/java/xyz/wbsite/action/page/control/Header.java b/src/main/java/xyz/wbsite/action/page/control/Header.java
new file mode 100644
index 0000000..0efd3d6
--- /dev/null
+++ b/src/main/java/xyz/wbsite/action/page/control/Header.java
@@ -0,0 +1,24 @@
+package xyz.wbsite.action.page.control;
+
+import xyz.wbsite.frame.base.Control;
+import org.springframework.ui.Model;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 对应resources/templates/control/header.ftl,当页面引入<#include controlHolder("nav")/> 访问对应页面时会执行该控制器。
+ *
+ * 在该对象中可以设置公共信息获取部分业务代码,而不需要每个页面访问Screen中都获取一次
+ *
+ * @author author
+ * @version 0.0.1
+ * @since 2020-11-01
+ */
+public class Header extends Control {
+
+ @Override
+ public void exec(Model model, HttpServletRequest request, HttpServletResponse response) {
+
+ }
+}
diff --git a/src/main/java/xyz/wbsite/config/ActionConfig.java b/src/main/java/xyz/wbsite/config/ActionConfig.java
index 7d2e71e..384daff 100644
--- a/src/main/java/xyz/wbsite/config/ActionConfig.java
+++ b/src/main/java/xyz/wbsite/config/ActionConfig.java
@@ -25,6 +25,9 @@ import java.util.regex.Pattern;
*
* 注册扫描Screen处理器 {@link ActionConfig#registryScreen}
* 注册扫描Control处理器 {@link ActionConfig#registryControl}
+ * 注册扫描Ajax处理器 {@link ActionConfig#registryAjax}
+ * 注册扫描Api处理器 {@link ActionConfig#registryApi}
+ * 注册扫描Wsvr处理器 {@link ActionConfig#registryWsvr}
*
* @author wangbing
* @version 0.0.1
@@ -37,6 +40,7 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
public static String CONTROL_PREFIX = "/control/";
public static String AJAX_PREFIX = "/ajax/";
public static String API_PREFIX = "/api/";
+ public static String MSVR_PREFIX = "/wsvr/";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
@@ -44,11 +48,12 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
Pattern compile = Pattern.compile("(.*)\\.config");
Matcher matcher = compile.matcher(aPackage);
if (matcher.find()) {
- String basePackage = matcher.group(1);
- registryScreen(basePackage + ".action.screen", beanDefinitionRegistry);
- registryControl(basePackage + ".action.control", beanDefinitionRegistry);
- registryAjax(basePackage + ".action.ajax", beanDefinitionRegistry);
- registryApi(basePackage + ".action.api", beanDefinitionRegistry);
+ String domain = matcher.group(1);
+ registryScreen(domain + ".action.page.screen", beanDefinitionRegistry);
+ registryControl(domain + ".action.page.control", beanDefinitionRegistry);
+ registryAjax(domain + ".action.ajax", beanDefinitionRegistry);
+ registryApi(domain + ".action.api", beanDefinitionRegistry);
+ registryWsvr(domain + ".action.msvr", beanDefinitionRegistry);
}
}
@@ -57,7 +62,7 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
}
- private int registryScreen(String basePackage, BeanDefinitionRegistry beanDefinitionRegistry) {
+ private int registryScreen(String domain, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
classPathBeanDefinitionScanner.resetFilters(false);
classPathBeanDefinitionScanner.addIncludeFilter(new AssignableTypeFilter(Screen.class));
@@ -65,15 +70,15 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanClassName = beanDefinition.getBeanClassName();
- String beamName = beanClassName.replaceAll(basePackage + ".", SCREEN_PREFIX).replaceAll("\\.","/").toLowerCase();
+ String beamName = beanClassName.replaceAll(domain + ".", SCREEN_PREFIX).replaceAll("\\.","/").toLowerCase();
LogUtil.i("registry screen " + beamName);
return beamName;
}
});
- return classPathBeanDefinitionScanner.scan(basePackage);
+ return classPathBeanDefinitionScanner.scan(domain);
}
- private int registryControl(String basePackage, BeanDefinitionRegistry beanDefinitionRegistry) {
+ private int registryControl(String domain, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
classPathBeanDefinitionScanner.resetFilters(false);
classPathBeanDefinitionScanner.addIncludeFilter(new AssignableTypeFilter(Control.class));
@@ -81,15 +86,15 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanClassName = beanDefinition.getBeanClassName();
- String beamName = beanClassName.replaceAll(basePackage + ".", CONTROL_PREFIX).replaceAll("\\.","/").toLowerCase();
+ String beamName = beanClassName.replaceAll(domain + ".", CONTROL_PREFIX).replaceAll("\\.","/").toLowerCase();
LogUtil.i("registry control " + beamName);
return beamName;
}
});
- return classPathBeanDefinitionScanner.scan(basePackage);
+ return classPathBeanDefinitionScanner.scan(domain);
}
- private int registryAjax(String basePackage, BeanDefinitionRegistry beanDefinitionRegistry) {
+ private int registryAjax(String domain, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
classPathBeanDefinitionScanner.resetFilters(false);
classPathBeanDefinitionScanner.addIncludeFilter(new TypeFilter() {
@@ -103,17 +108,17 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanClassName.endsWith("Ajax")) {
- beanClassName = beanClassName.substring(0, beanClassName.length() - 4);
+ beanClassName = beanClassName.substring(0, beanClassName.lastIndexOf("Ajax"));
}
- String beamName = beanClassName.replaceAll(basePackage + ".", AJAX_PREFIX).replaceAll("\\.","/").toLowerCase();
+ String beamName = beanClassName.replaceAll(domain + ".", AJAX_PREFIX).replaceAll("\\.","/").toLowerCase();
LogUtil.i("registry ajax " + beamName);
return beamName;
}
});
- return classPathBeanDefinitionScanner.scan(basePackage);
+ return classPathBeanDefinitionScanner.scan(domain);
}
- private int registryApi(String basePackage, BeanDefinitionRegistry beanDefinitionRegistry) {
+ private int registryApi(String domain, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
classPathBeanDefinitionScanner.addIncludeFilter(new TypeFilter() {
@Override
@@ -126,13 +131,36 @@ public class ActionConfig implements BeanDefinitionRegistryPostProcessor {
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanClassName.endsWith("Api")) {
- beanClassName = beanClassName.substring(0, beanClassName.length() - 3);
+ beanClassName = beanClassName.substring(0, beanClassName.lastIndexOf("Api"));
}
- String beamName = beanClassName.replaceAll(basePackage + ".", API_PREFIX).replaceAll("\\.","/").toLowerCase();
+ String beamName = beanClassName.replaceAll(domain + ".", API_PREFIX).replaceAll("\\.","/").toLowerCase();
LogUtil.i("registry api " + beamName);
return beamName;
}
});
- return classPathBeanDefinitionScanner.scan(basePackage);
+ return classPathBeanDefinitionScanner.scan(domain);
+ }
+
+ private int registryWsvr(String domain, BeanDefinitionRegistry beanDefinitionRegistry) {
+ ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
+ classPathBeanDefinitionScanner.addIncludeFilter(new TypeFilter() {
+ @Override
+ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
+ return true;
+ }
+ });
+ classPathBeanDefinitionScanner.setBeanNameGenerator(new BeanNameGenerator() {
+ @Override
+ public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
+ String beanClassName = beanDefinition.getBeanClassName();
+ if (beanClassName != null && beanClassName.endsWith("Msvr")) {
+ beanClassName = beanClassName.substring(0, beanClassName.lastIndexOf("Msvr"));
+ }
+ String beamName = beanClassName.replaceAll(domain + ".", MSVR_PREFIX).replaceAll("\\.","/").toLowerCase();
+ LogUtil.i("registry wsvr " + beamName);
+ return beamName;
+ }
+ });
+ return classPathBeanDefinitionScanner.scan(domain);
}
}
diff --git a/src/main/java/xyz/wbsite/config/CacheConfig.java b/src/main/java/xyz/wbsite/config/CacheConfig.java
new file mode 100644
index 0000000..857875b
--- /dev/null
+++ b/src/main/java/xyz/wbsite/config/CacheConfig.java
@@ -0,0 +1,90 @@
+package xyz.wbsite.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Cache:缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
+ * CacheManager:缓存管理器,管理各种缓存(Cache)组件
+ *
+ * @EnableCaching 开启缓存
+ * @Cacheable 方法注解,执行方法并缓存结果,有缓存时直接返回缓存不执行方法condition:是否使用缓存 unless:是否缓存结果
+ * @CacheEvict: 清空缓存
+ * @CachePut: 方法注解,执行方法并缓存结果
+ *
+ * keyGenerator 缓存数据时key生成策略
+ * serialize 缓存数据时value序列化策略
+ */
+@Configuration
+@EnableCaching
+@EnableScheduling
+public class CacheConfig {
+
+ @Autowired
+ private CacheConfig cacheConfig;
+
+ private Map