zoukankan      html  css  js  c++  java
  • Spring Boot搭建Web项目常用功能

    搭建WEB项目过程中,哪些点需要注意:

    1、技术选型:

          前端:freemarker、vue 

          后端:spring boot、spring mvc

    2、如何包装返回统一结构结果数据?

         首先要弄清楚为什么要包装统一结构结果数据,这是因为当任意的ajax请求超时或者越权操作时,系统能返回统一的错误信息给到前端,前端通过封装统一的ajax请求统一处理这类错误信息(这样统一就避免每次都需要额外处理)。

        那如何包装结构呢?

        先封装统一返回结果结构对象 JsonMessage:

    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;
        }
    
    }

          如何处理包装结果给前端呢?

          方法一:所有的controller里ajax请求都返回JsonMessage对象;

          方法二:通过 ResponseBodyAdvice 处理;

          方法三:通过 HandlerMethodReturnValueHandler 拦截@ResponseBody注解或自定义注解  处理(不太懂的童鞋请百度);

    3、如果统一处理异常?

         继承 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:{} 操作用户编号:{} {} 
    访问来源:{} 
    参数:{}", request.getRequestURL(), userId,
    			userName, referer, JsonUtils.beanToJson(request.getParameterMap())), e);
    		BusinessException exception = (BusinessException) e;
    		exceptionMsg = exception.getMessage();
    	    } else {
    		logger.error(StringUtil.format("系统异常,当前请求URL:{} 操作用户编号:{} {} 
    访问来源:{} 
    参数:{}", 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 404、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);
                }
            };
     }

    4、如果优雅的处理按钮级别权限?

         因为前端采用的是Vue,清楚vue的知道它的表现就是通过model控制view的,所以前端就是在页面渲染 mounted 的时候用ajax去请求,通过返回的字段信息判断是否要显示某按钮或者链接或者视图块。

         那后端要如何才能做到验证权限呢?

         采用 HandlerMethodReturnValueHandler 拦截所有需要返回权限信息的ajax请求,再根据 methodParameter能获取到method对象,然后就能获取到method上的权限注解信息了再统一调用鉴权服务,再把结果包装到JsonMessage对象返回就可以了。

    5、如何配置消息装换器?

         首先要弄清楚为什么需要配,因为我们需要按项目要求来下自定义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、项目示例:

     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);
                }
            };
        }
        
    }
     
  • 相关阅读:
    大数据概述 106
    编译原理学习随笔 106
    我与ruby第一次接触
    xml在joomla表单中的应用详解
    joomla2.5传统组件开发解析
    joomla2.5开发系列教程原创(1)2.5与1.5异同点
    CSS2简写和常用css总结笔记
    C语言开发php扩展链接库初学
    ruby转战Ubuntu,真折腾?
    joomla搜索功能开发和结果分页探讨
  • 原文地址:https://www.cnblogs.com/smiler/p/9292693.html
Copyright © 2011-2022 走看看