搭建WEB項目過程當中,哪些點須要注意:html
前端:freemarker、vue 前端
後端:spring boot、spring mvcvue
首先要弄清楚爲何要包裝統一結構結果數據,這是由於當任意的ajax請求超時或者越權操做時,系統能返回統一的錯誤信息給到前端,前端經過封裝統一的ajax請求統一處理這類錯誤信息(這樣統一就避免每次都須要額外處理)。java
那如何包裝結構呢?web
先封裝統一返回結果結構對象 JsonMessage:ajax
public class JsonMessage extends HashMap<String, Object> { private static final long serialVersionUID = -7149712196874923440L; public JsonMessage() { this.put("status", 200); } public JsonMessage(boolean status) { putStatus(status); } public JsonMessage(String msg) { this.put("status", 200); this.put("msg", msg); } public JsonMessage(boolean status, String msg) { this.put("msg", msg); putStatus(status); } public JsonMessage(String key,Object object) { this.put("status", 200); this.put(key, object); } public JsonMessage(boolean status, String msg, String key, Object value) { this.put("msg", msg); putStatus(status); this.put(key, value); } public JsonMessage putStatusAndMsg(int code, String msg) { this.put("status", code); this.put("msg", msg); return this; } public JsonMessage putStatusAndMsg(boolean status, String msg) { putStatus(status); this.put("msg", msg); return this; } public JsonMessage putStatus(int code) { this.put("status", code); return this; } public JsonMessage putStatus(boolean status) { if(status){ this.put("status", 200); }else{ this.put("status", 500); } return this; } public JsonMessage putRedirectUrl(String redirectUrl) { this.put("url", redirectUrl); this.put("status", 501); return this; } public JsonMessage putMsg(String msg) { this.put("msg", msg); return this; } public JsonMessage put(String arg0, Object arg1) { super.put(arg0, arg1); return this; } }
如何處理包裝結果給前端呢?spring
方法一:全部的controller裏ajax請求都返回JsonMessage對象;apache
方法二:經過 ResponseBodyAdvice 處理;json
方法三:經過 HandlerMethodReturnValueHandler 攔截@ResponseBody註解或自定義註解 處理(不太懂的童鞋請百度);後端
繼承 HandlerExceptionResolver 接口便可處理全部異常了,因此這也得分是否ajax請求。而後按不一樣請求類型處理:
/** * 統一異常處理,不管是正常跳轉請求仍是ajax請求都能處理, */ @Component public class GlobalExceptionResolver implements HandlerExceptionResolver { private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; String referer = request.getHeader("Referer"); String exceptionMsg = "系統異常,請稍後操做"; String userId = null; String userName = null; Object object = request.getSession().getAttribute(Constant.SESSION_USER); if (object != null) { LoginUser user = (LoginUser) object; userName = user.userName(); userId = user.getUserId(); } if (e instanceof BusinessException) { logger.warn(StringUtil.format("業務異常,當前請求URL:{} 操做用戶編號:{} {} \n訪問來源:{} \n參數:{}", request.getRequestURL(), userId, userName, referer, JsonUtils.beanToJson(request.getParameterMap())), e); BusinessException exception = (BusinessException) e; exceptionMsg = exception.getMessage(); } else { logger.error(StringUtil.format("系統異常,當前請求URL:{} 操做用戶編號:{} {} \n訪問來源:{} \n參數:{}", request.getRequestURL(), userId, userName, referer, JsonUtils.beanToJson(request.getParameterMap())), e); } if (AnnotationHandleUtils.isAjaxAnnotation(handlerMethod)) { JsonMessage jmsg = new JsonMessage(false, exceptionMsg); try { WebHelper.write(response, jmsg.toString(), HttpStatus.OK.value()); } catch (IOException e1) { logger.error("發送數據異常", e1); } return new ModelAndView(); } ModelAndView modelView = new ModelAndView("common/500"); //跳轉到500錯誤頁面 return modelView.addObject(Constant.ERROR_MES_KEY, exceptionMsg); } return null; } }
配置servlet 40四、500異常跳轉地址:
@Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/common/404.html"); ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/common/500.html"); ErrorPage errorpage = new ErrorPage("/common/500.html"); container.addErrorPages(error404Page, error500Page,errorpage); } }; }
由於前端採用的是Vue,清楚vue的知道它的表現就是經過model控制view的,因此前端就是在頁面渲染 mounted 的時候用ajax去請求,經過返回的字段信息判斷是否要顯示某按鈕或者連接或者視圖塊。
那後端要如何才能作到驗證權限呢?
採用 HandlerMethodReturnValueHandler 攔截全部須要返回權限信息的ajax請求,再根據 methodParameter能獲取到method對象,而後就能獲取到method上的權限註解信息了再統一調用鑑權服務,再把結果包裝到JsonMessage對象返回就能夠了。
首先要弄清楚爲何須要配,由於咱們須要按項目要求來下自定義Jackson轉換json規範,好比:date類型默認狀況是轉成時間戳,那這對於前端就須要再裝換才能夠。再好比null值的對象是否要在json中輸出默認是會輸出,那咱們也能夠改爲不輸出。固然還有其餘的就不舉例了。
/** * 經過繼承 WebMvcConfigurerAdapter 來配置spring mvc * */ @Configuration public class ApplicationConfiguration extends WebMvcConfigurerAdapter{ @Autowired private LoginInterceptor loginInterceptor; @Autowired private PermissionInterceptor permissionInterceptor; @Autowired private ResponseBodyResolver responseBodyResolver; /** * 能夠注入spring mvc提供的 RequestMappingHandlerAdapter bean */ @Autowired private RequestMappingHandlerAdapter requestMappingHandlerAdapter; @Autowired private DateConverter dateConverter; /** * 添加interceptors */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor); registry.addInterceptor(permissionInterceptor); super.addInterceptors(registry); } /** * 配置消息轉換器 */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new ByteArrayHttpMessageConverter()); converters.add(mappingJackson2HttpMessageConverter()); //配置jackson2 super.configureMessageConverters(converters); } public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){ MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); mappingJackson2HttpMessageConverter.setSupportedMediaTypes( Lists.newArrayList(MediaType.TEXT_PLAIN,MediaType.APPLICATION_JSON_UTF8)); ObjectMapper objectMapper= new ObjectMapper(); //屬性命名規則,這個通常不須要配 objectMapper.setPropertyNamingStrategy(new LowerCasePropertyNamingStrategy()); objectMapper.configure(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING, true); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //默認date屬性格式,能夠其它的 objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper); return mappingJackson2HttpMessageConverter; } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { returnValueHandlers.add(responseBodyResolver); super.addReturnValueHandlers(returnValueHandlers); } /** * 配置屬性編輯器,主要是當前端form提交字符串時轉成date類型 */ @PostConstruct public void webBindingInitializer(){ requestMappingHandlerAdapter.setWebBindingInitializer(dateConverter); } } @Component public class DateConverter implements WebBindingInitializer{ @Override public void initBinder(WebDataBinder binder, WebRequest request) { binder.setAutoGrowCollectionLimit(Integer.MAX_VALUE); // CustomDateEditor只要繼承PropertyEditorSupport CustomDateEditor dateEditor = new CustomDateEditor(CustomDateEditor.TIMEFORMAT, true); //註冊自定義的屬性編輯器 表示若是命令對象有Date類型的屬性,將使用該屬性編輯器進行類型轉換 binder.registerCustomEditor(Date.class, dateEditor); } }
6.1 項目依賴 pom.xml:
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.2.RELEASE</version> <relativePath /> </parent> <artifactId>web-demo</artifactId> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!-- spring-boot --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>--> </dependencies> </project>
application.properties:
logging.config=classpath:conf/xml/logback.xml #freemarker config info spring.freemarker.templateEncoding=UTF-8 spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=false spring.freemarker.expose-session-attributes=false spring.freemarker.request-context-attribute=rc spring.freemarker.templateLoaderPath=classpath:/templates/pages #這沒加後綴是由於在代碼裏手動標名後綴 spring.freemarker.suffix= spring.freemarker.view-names=*.html #server config server.session.timeout=1800 server.contextPath=/demo server.port=8080 #server.compression.enabled=true #server.compression.min-response-size=1024 #server.tomcat.max-threads=500 #resource config spring.resources.chain.cache=false spring.resources.static-locations=classpath:/static/ #spring.resources.cache-period=60 #cache config spring.cache.type=guava #緩存最大數量1000條, 緩存失效時間5分鐘 spring.cache.guava.spec=maximumSize=1000,expireAfterAccess=10m spring.http.multipart.max-file-size=5Mb spring.http.multipart.max-request-size=5Mb spring.http.multipart.enabled=true
6.2 啓動類:
@PropertySource(value={"classpath:conf/env/datasource.properties", "classpath:conf/env/message.properties", "classpath:conf/env/config.properties"}) @SpringBootApplication @EnableTransactionManagement @EnableAutoConfiguration(exclude=RabbitAutoConfiguration.class) @EnableCaching @MapperScan("com.test.demo.persistence") @ComponentScan(value={"com.test.demo"}) //導入spring xml文件 //@ImportResource(locations = { "classpath*:/spring.xml" }) public class WebDemoApplication { private final static Logger logger = LogManager.getLogger(WebDemoApplication.class); public static void main(String[] args) { System.setProperty("spring.config.location", "classpath:conf/env/application.properties"); SpringApplication.run(WebDemoApplication .class, args); logger.info("start completed !"); } @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return new embeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/common/404.html"); ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/common/500.html"); ErrorPage errorpage = new ErrorPage("/common/500.html"); container.addErrorPages(error404Page, error500Page,errorpage); } }; } }