版本升級及內容優化版本,改動內容:javascript
相關: html
基於前篇,新增功能:前端
源碼已集成到項目中: java
github源碼: https://github.com/wyait/manage.git
碼雲:https://gitee.com/wyait/manage.git
github對應項目源碼目錄:wyait-manage-1.2.0
碼雲對應項目源碼目錄:wyait-manage-1.2.0 git
Shiro 提供了相應的註解用於權限控制,若是使用這些註解就須要使用AOP 的功能來進行判斷,如Spring AOP;Shiro 提供了Spring AOP 集成用於權限註解的解析和驗證。github
@RequiresAuthentication 表示當前Subject已經經過login 進行了身份驗證;即Subject.isAuthenticated()返回true。 @RequiresUser 表示當前Subject已經身份驗證或者經過記住我登陸的。 @RequiresGuest 表示當前Subject沒有身份驗證或經過記住我登陸過,便是遊客身份。 @RequiresRoles(value={「admin」, 「user」}, logical= Logical.AND) @RequiresRoles(value={「admin」}) @RequiresRoles({「admin「}) 表示當前Subject須要角色admin 和user。 @RequiresPermissions (value={「user:a」, 「user:b」}, logical= Logical.OR) 表示當前Subject須要權限user:a或user:b。
Shiro的認證註解處理是有內定的處理順序的,若是有多個註解的話,前面的經過了會繼續檢查後面的,若不經過則直接返回,處理順序依次爲(與實際聲明順序無關):web
RequiresRoles RequiresPermissions RequiresAuthentication RequiresUser RequiresGuest
以上註解既能夠用在controller中,也能夠用在service中使用;
建議將shiro註解放在controller中,由於若是service層使用了spring的事物註解,那麼shiro註解將無效。ajax
shiro權限註解要生效,必須配置springAOP經過設置shiro的SecurityManager進行權限驗證。redis
/** * * @描述:開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 * 配置如下兩個bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)便可實現此功能 * </br>Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor(保證明現了Shiro內部lifecycle函數的bean執行) has run * </br>不使用註解的話,能夠註釋掉這兩個配置 * @建立人:wyait * @建立時間:2018年5月21日 下午6:07:56 * @return */ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }
場景:當用戶正常訪問網站時,由於某種緣由後端出現exception的時候,直接暴露異常信息或頁面顯示給用戶; spring
這種操做體驗不是咱們想要的。因此要對異常進行統一管理,能提升用戶體驗的同時,後臺能詳細定位到異常的問題點。
Spring Boot提供了默認的統一錯誤頁面,這是Spring MVC沒有提供的。在理解了Spring Boot提供的錯誤處理相關內容以後,咱們能夠方便的定義本身的錯誤返回的格式和內容。
在home頁面,手動建立兩個異常:普通異常和異步異常!
<p> 普通請求異常: <a href="/error/getError">點擊</a> </p> <p> ajax異步請求異常: <a href="javascript:void(0)" onclick="ajaxError()">點擊</a> </p> ... //js代碼 function ajaxError(){ $.get("/error/ajaxError",function(data){ layer.alert(data); }); }
/** * * @描述:普通請求異常 * @建立人:wyait * @建立時間:2018年5月24日 下午5:30:50 */ @RequestMapping("getError") public void toError(){ System.out.println(1/0); } /** * * @描述:異步異常 * @建立人:wyait * @建立時間:2018年5月24日 下午5:30:39 */ @RequestMapping("ajaxError") @ResponseBody public String ajaxError(){ System.out.println(1/0); return "異步請求成功!"; }
[2018-05-25 09:30:04.669][http-nio-8077-exec-8][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.wyait.manage.web.error.IndexErrorController.toError(IndexErrorController.java:18) ~[classes/:?] ... at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131] ... [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)] [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)] [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController' [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController' ... [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html]) [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html]) [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error' [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error' [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html' [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html' ...
經過日誌可知,springboot返回的錯誤頁面,是經過:org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml處理返回ModelAndView。
[2018-05-25 09:31:19.958][http-nio-8077-exec-6][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.wyait.manage.web.error.IndexErrorController.ajaxError(IndexErrorController.java:29) ~[classes/:?] ... at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131] ... [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController' [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController' ... [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5] [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5] [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.DispatcherServlet][1048]:Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling ...
經過日誌可知,springboot返回的錯誤信息,是經過:org.springframework.boot.autoconfigure.web.BasicErrorController.error處理返回ResponseEntity<String,Object>。
查看org.springframework.boot.autoconfigure.web包下面的類,跟蹤springboot對error異常處理機制。自動配置經過一個MVC error控制器處理錯誤
經過spring-boot-autoconfigure引入
查看springboot 處理error的類
springboot的自動配置,在web中處理error相關的自動配置類:ErrorMvcAutoConfiguration。查看與處理error相關的類:
ErrorAutoConfiguration類源碼//TODO
//4個BEAN @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); } @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); } @Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties); } @Bean public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() { return new PreserveErrorControllerTargetClassPostProcessor(); }
@Order(Ordered.HIGHEST_PRECEDENCE) public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { ... }
ErrorAttributes:
public interface ErrorAttributes { Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace); Throwable getError(RequestAttributes requestAttributes); }
HandlerExceptionResolver:
public interface HandlerExceptionResolver { /** * Try to resolve the given exception that got thrown during handler execution, * returning a {@link ModelAndView} that represents a specific error page if appropriate. */ ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
DefaultErrorAttributes類:
debug跟蹤源碼:即DispatcherServlet在doDispatch過程當中有異常拋出時:
一. 先由HandlerExceptionResolver.resolveException解析異常並保存在request中;
二. 再DefaultErrorAttributes.getErrorAttributes處理;DefaultErrorAttributes在處理過程當中,從request中獲取錯誤信息,將錯誤信息保存到RequestAttributes中;
三. 最後在獲取錯誤信息getError(RequestAttributes)時,從RequestAttributes中取到錯誤信息。
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { private final ErrorProperties errorProperties; ... @RequestMapping(produces = "text/html") public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView == null ? new ModelAndView("error", model) : modelAndView); } @RequestMapping @ResponseBody public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<Map<String, Object>>(body, status); } ... }
resolveErrorView方法(查找=error/「錯誤狀態碼」;的資源):
若是不是異常請求,會執行resolveErrorView方法;該方法會先在默認或配置的靜態資源路徑下查找error/HttpStatus(錯誤狀態碼)的資源文件,若是沒有;使用默認的error頁面。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { ... @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { //status:異常錯誤狀態碼 ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //視圖名稱,默認是error/+「status」錯誤狀態碼;好比:error/500、error/404 String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); } //在資源文件中查找error/500或error/404等頁面 private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; } ... }
BasicErrorController根據Accept頭的內容,輸出不一樣格式的錯誤響應。好比針對瀏覽器的請求生成html頁面,針對其它請求生成json格式的返回。
能夠經過配置error/HttpStatus頁面實現自定義錯誤頁面。
/** * {@link EmbeddedServletContainerCustomizer} that configures the container's error * pages. */ private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; protected ErrorPageCustomizer(ServerProperties properties) { this.properties = properties; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath()); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } }
將錯誤頁面註冊到內嵌的tomcat的servlet容器中。
//2個config配置 @Configuration static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) { this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; } @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } } @Configuration @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true) @Conditional(ErrorTemplateMissingCondition.class) protected static class WhitelabelErrorViewConfiguration { private final SpelView defaultErrorView = new SpelView( "<html><body><h1>Whitelabel Error Page</h1>" + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>" + "<div id='created'>${timestamp}</div>" + "<div>There was an unexpected error (type=${error}, status=${status}).</div>" + "<div>${message}</div></body></html>"); @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } // If the user adds @EnableWebMvc then the bean name view resolver from // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment. @Bean @ConditionalOnMissingBean(BeanNameViewResolver.class) public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; } }
DefaultErrorViewResolverConfiguration:默認的error視圖解析配置;
若是Spring MVC在處理業務的過程當中拋出異常,會被 Servlet 容器捕捉到,Servlet 容器再將請求轉發給註冊好的異常處理映射 /error 作響應處理。
springboot配置文件application.properties中關於error默認配置:
server.error.include-stacktrace=never # When to include a "stacktrace" attribute. server.error.path=/error # Path of the error controller. server.error.whitelabel.enabled=true # Enable the default error page displayed in browsers in case of a server error.
經過跟蹤springboot對異常處理得源碼跟蹤,根據業務須要,能夠細分前端響應的錯誤頁面,也能夠統一使用/error頁面+錯誤提示信息進行處理。
根據本身的需求自定義異常處理機制;具體可實施的操做以下:
能夠經過配置error/HttpStatus(錯誤狀態碼)頁面實現自定義錯誤頁面【底層實現,詳見:BasicErrorController源碼】;
能夠實現BasicErrorController,自定義普通請求的異常頁面響應信息和異步請求的響應信息,統一使用/error頁面進行錯誤響應提示;
1和2的方法可單獨使用,也能夠結合使用。
能夠根據不一樣的錯誤狀態碼,在前端細分不一樣的響應界面給用戶進行提示;資源路徑必須是:靜態資源路徑下/error/HttpStats(好比:/error/404等)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"></meta> <title>404友情提示</title> </head> <body> <h1>訪問的資源未找到(404)</h1> </body> </html>
404.html
500.html等,這裏只演示404。
普通請求,前端使用error頁面+自定義錯誤響應信息;
其餘請求(異步),統一自定義錯誤響應信息,規範處理異步響應的錯誤判斷和處理。
/** * * @項目名稱:wyait-manage * @類名稱:GlobalExceptionHandler * @類描述:統一異常處理,包括【普通調用和ajax調用】 * </br>ControllerAdvice來作controller內部的全局異常處理,但對於未進入controller前的異常,該處理方法是沒法進行捕獲處理的,SpringBoot提供了ErrorController的處理類來處理全部的異常(TODO)。 * </br>1.當普通調用時,跳轉到自定義的錯誤頁面;2.當ajax調用時,可返回約定的json數據對象,方便頁面統一處理。 * @建立人:wyait * @建立時間:2018年5月22日 上午11:44:55 * @version: */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory .getLogger(GlobalExceptionHandler.class); public static final String DEFAULT_ERROR_VIEW = "error"; /** * * @描述:針對普通請求和ajax異步請求的異常進行處理 * @建立人:wyait * @建立時間:2018年5月22日 下午4:48:58 * @param req * @param e * @return * @throws Exception */ @ExceptionHandler(value = Exception.class) @ResponseBody public ModelAndView errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { logger.debug(getClass().getName() + ".errorHandler】統一異常處理:request="+request); ModelAndView mv=new ModelAndView(); logger.info(getClass().getName() + ".errorHandler】統一異常處理:"+e.getMessage()); //1 獲取錯誤狀態碼 HttpStatus httpStatus=getStatus(request); logger.info(getClass().getName() + ".errorHandler】統一異常處理!錯誤狀態碼httpStatus:"+httpStatus); //2 返回錯誤提示 ExceptionEnum ee=getMessage(httpStatus); //3 將錯誤信息放入mv中 mv.addObject("type", ee.getType()); mv.addObject("code", ee.getCode()); mv.addObject("msg", ee.getMsg()); if(!ShiroFilterUtils.isAjax(request)){ //不是異步請求 mv.setViewName(DEFAULT_ERROR_VIEW); logger.debug(getClass().getName() + ".errorHandler】統一異常處理:普通請求。"); } logger.debug(getClass().getName() + ".errorHandler】統一異常處理響應結果:MV="+mv); return mv; } ... }
運行測試:先走GlobalExceptionHandler(使用註解@ControllerAdvice)類裏面的方法,然後又執行了BasicErrorController方法;被springboot自帶的BasicErrorController覆蓋。
自定義實現AbstractErrorController,添加響應的錯誤提示信息。
@RequestMapping(produces = "text/html") public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { ModelAndView mv = new ModelAndView(ERROR_PATH); /** model對象包含了異常信息 */ Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); // 1 獲取錯誤狀態碼(也能夠根據異常對象返回對應的錯誤信息) HttpStatus httpStatus = getStatus(request); // 2 返回錯誤提示 ExceptionEnum ee = getMessage(httpStatus); Result<String> result = new Result<String>( String.valueOf(ee.getType()), ee.getCode(), ee.getMsg()); // 3 將錯誤信息放入mv中 mv.addObject("result", result); logger.info("統一異常處理【" + getClass().getName() + ".errorHtml】統一異常處理!錯誤信息mv:" + mv); return mv; } @RequestMapping @ResponseBody //設置響應狀態碼爲:200,結合前端約定的規範處理。也可不設置狀態碼,前端ajax調用使用error函數進行控制處理 @ResponseStatus(value=HttpStatus.OK) public Result<String> error(HttpServletRequest request, Exception e) { /** model對象包含了異常信息 */ Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); // 1 獲取錯誤狀態碼(也能夠根據異常對象返回對應的錯誤信息) HttpStatus httpStatus = getStatus(request); // 2 返回錯誤提示 ExceptionEnum ee = getMessage(httpStatus); Result<String> result = new Result<String>( String.valueOf(ee.getType()), ee.getCode(), ee.getMsg()); // 3 將錯誤信息返回 // ResponseEntity logger.info("統一異常處理【" + getClass().getName() + ".error】統一異常處理!錯誤信息result:" + result); return result; }
針對異步請求,統一指定響應狀態碼:200;也能夠不指定,前端在處理異步請求的時候,能夠經過ajax的error函數進行控制。
這裏是繼承的AbstractErrorController類,自定義實現統一異常處理,也能夠直接實現ErrorController接口。
經過約定,前端ajax異步請求,進行統一的錯誤處理。
/** * 針對不一樣的錯誤可結合業務自定義處理方式 * @param result * @returns {Boolean} */ function isError(result){ var flag=true; if(result && result.status){ flag=false; if(result.status == '-1' || result.status=='-101' || result.status=='400' || result.status=='404' || result.status=='500'){ layer.alert(result.data); }else if(result.status=='403'){ layer.alert(result.data,function(){ //跳轉到未受權界面 window.location.href="/403"; }); } } return flag;//返回true }
使用方式:
... success:function(data){ //異常過濾處理 if(isError(data)){ alert(data); } }, ...
error.html頁面:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head th:include="layout :: htmlhead" th:with="title='wyait後臺管理'"> <meta charset="UTF-8"></meta> <title th:text="${result.status}"></title> </head> <body> <h1>出錯了</h1> <p><span th:text="${result.message}"></span>(<span th:text="${result.data}"></span>)</p> </body> </html>
普通請求:
異步請求:
前臺經過html頁面,發送請求到後臺查詢數據,在日誌中打印的sql語句顯示傳入的參數亂碼:
SELECT ... [2018-05-11 09:15:00.582][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:==> Parameters: 1(Integer), çè´º(String) [2018-05-11 09:15:00.585][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:<== Total: 1 ...
本地windows開發環境測試沒有亂碼問題;
前端頁面發送get請求,瀏覽器默認對get請求路徑進行URL編碼處理。
分頁查詢用戶列表!搜索條件:userSearch:UserSearchDTO{page=1, limit=10, uname='çç', umobile='', insertTimeStart='', insertTimeEnd=''},page:1,每頁記錄數量limit:10,請求編碼:UTF-8
Controller層在接收到這個uname參數時,已是亂碼,ISO-8859-1解碼後的結果。
具體編碼細節:TODO
開發前,默認必須統一編碼環境;正常都是設置爲utf-8。
spring boot 與spring mvc不一樣,在web應用中,spring boot默認的編碼格式爲UTF-8,而spring mvc的默認編碼格式爲iso-8859-1。
spring boot項目中若是沒有特殊需求,該編碼不須要修改。若是要強制其餘編碼格式,spring boot提供了設置方式:
# 默認utf-8配置 spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8
此時攔截器中返回的中文已經不亂碼了,可是controller中返回的數據可能會依舊亂碼。
@Bean public HttpMessageConverter<String> responseBodyConverter() { StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8")); return converter; } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); converters.add(responseBodyConverter()); }
也能夠在controller方法@RequestMapping上添加:
produces="text/plain;charset=UTF-8"
這種方法的弊端是限定了數據類型。
表單採用get方式提交,中文亂碼解決方案:
param = new String(param.getBytes("iso8859-1"), "utf-8");
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
在這裏添加一個屬性:URIEncoding,將該屬性值設置爲UTF-8,便可讓Tomcat(默認ISO-8859-1編碼)以UTF-8的編碼處理get請求。
修改完成後:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
//js代碼 param = encodeURI(param); // alert("第一次URL編碼:" + param); param = encodeURI(param); // alert("第二次URL編碼:" + param);
後臺代碼:
//兩次解碼 URLDecoder.decode(URLDecoder.decode(param,"utf-8"),"utf-8");
以上四種解決方案,可結合具體狀況進行使用。
異常日誌1:
[2018-05-21 18:00:51.574][http-nio-8280-exec-6][DEBUG][org.apache.shiro.web.servlet.SimpleCookie][389]:Found 'SHRIOSESSIONID' cookie value [fc6b7b64-6c59-4f82-853b-e2ca20135b99] [2018-05-21 18:00:51.575][http-nio-8280-exec-6][DEBUG][org.apache.shiro.mgt.DefaultSecurityManager][447]:Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous (session-less) Subject instance. org.apache.shiro.session.UnknownSessionException: There is no session with id [fc6b7b64-6c59-4f82-853b-e2ca20135b99] at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170) ~[shiro-all-1.3.1.jar:1.3.1]
異常日誌2【偶爾出現】:
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) ~[sunjce_provider.jar:1.7.0_85]
UnknownSessionException
UnknownSessionException: There is no session with id [...]
結合項目配置,分析問題緣由:
1,用戶退出後,瀏覽器中的SHIROSESSIONID依然存在;
2,再次發送請求時,攜帶SHIROSESSIONID,會在shiro的DefaultWebSecurityManager.getSessionKey(context)中,逐層跟蹤對應在sessionManager中session值,沒有的話,最終在AbstractSessionDAO.readSession(sessionID)中拋出異常。
//刪除cookie Cookie co = new Cookie("username", ""); co.setMaxAge(0);// 設置當即過時 co.setPath("/");// 根目錄,整個網站有效 servletResponse.addCookie(co);
@Bean public SimpleCookie sessionIdCookie() { //DefaultSecurityManager SimpleCookie simpleCookie = new SimpleCookie(); //若是在Cookie中設置了"HttpOnly"屬性,那麼經過程序(JS腳本、Applet等)將沒法讀取到Cookie信息,這樣能防止XSS×××。 simpleCookie.setHttpOnly(true); simpleCookie.setName("SHRIOSESSIONID"); simpleCookie.setMaxAge(86400000*3); return simpleCookie; }
手動實現shiro的logout方法,清除瀏覽器cookie;
源碼已集成到項目中:
github源碼: https://github.com/wyait/manage.git
碼雲:https://gitee.com/wyait/manage.git
github對應項目源碼目錄:wyait-manage-1.2.0
碼雲對應項目源碼目錄:wyait-manage-1.2.0
版本升級及內容優化版本,改動內容: