文档章节

Spring Boot搭建Web项目要点

woter
 woter
发布于 07/09 17:57
字数 1672
阅读 1033
收藏 48
点赞 2
评论 4

搭建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:{} 操作用户编号:{} {} \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 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);
            }
        };
    }
    
}

 

© 著作权归作者所有

共有 人打赏支持
woter
粉丝 44
博文 108
码字总数 59177
作品 0
深圳
技术主管
加载中

评论(4)

woter
woter
骗你什么
呼呼南风
呼呼南风
骗子
woter
woter

引用来自“Tkks”的评论

freemarker, 前端?

没前后分离 freemarker做渲染模板
Tkks
Tkks
freemarker, 前端?
基于Spring Boot的登录demo

原文首发于我的博客 本项目基于Spring Boot框架,搭建了一个简单的登录微服务。 Spring Boot相对于传统的SSM(Spring MVC + Mybatis + Spring)框架用起来更加简单,不需要进行复杂的配置,方便...

tikyle
05/14
0
0
Spring、Spring Boot与Spring MVC

总论 Spring框架就像一个家族,有众多衍生产品例如boot、security、jpa等等。但他们的基础都是Spring的ioc和aop。ioc提供了依赖注入的容器,aop解决了面向横切面的编程;然后在此二者的基础上...

临江仙卜算子
05/08
0
2
spring boot简单实现rest服务

问题 最开始我是想使用Spring MVC搭建一个简单的REST服务,去官网看了看教程,现在Spring胆越来越大了,需要我集成一个maven的父项目,我并不愿意继承Spring的父项目。然后,就开始下面的探索...

亚林瓜子
06/12
0
0
SpringBoot 学习:(一)快速搭建项目

一、简介 从 Spring Boot 项目名称中的 Boot 可以看出来,Spring Boot 的作用在于创建和启动新的基于 Spring 框架的项目。它的目的是帮助开发人员很容易的创建出独立运行和产品级别的基于 Sp...

Element0506
2015/07/25
0
0
Eureka学习(一)--Eureka 服务器

spring-cloud Netflix 的主要模块: 服务发现:Eureka 断路器/降级:Hystrix 智能路由:Zuul 负载均衡:Ribbon 本文章将学习Eureka服务器端。 第一步:搭建项目,配置 pom.xml 第二步:配置 ...

FinalSmart
07/12
0
0
Spring Boot快速搭建Spring框架

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为...

afreon
2015/06/19
0
0
spring boot(1)入门

1 简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置...

刘胜球
2017/10/25
0
0
Spring Boot干货系列: (一)优雅的入门篇

     前言   Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做技术储备。   正文   首先声明,Spr...

后端编程嘟
2017/03/12
0
0
SpringBoot同时集成Guava和Redis作为缓存

参考网页 http://tramp.cincout.cn/2017/10/31/spring-boot-2017-10-31-spring-boot-multi-cache-manager/ 为什么要混用缓存(本地缓存和分布式缓存)? 这个要看项目实际需要。一种场景就是...

karma123
07/04
0
0
Spring Boot + Spring Data JPA 项目整合开发记录(持续更新)

刚换了公司,项目架构师提出新的系统架构时还是愣了一下,搭建难度较低,很容易上手,但是对Spring Data JPA的了解不够深入,所以还是有些吃力,在框架搭建初期有许多东西并没有很好的集成。...

华山猛男
07/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

shell中的函数、shell中的数组、告警系统需求分析

shell中的函数 格式: 格式: function f_name() { command } 函数必须要放在最前面 示例1(用来打印参数) 示例2(用于定义加法) 示例3(用于显示IP) shell中的数组 shell中的数组1 定义数...

Zhouliang6
今天
2
0
用 Scikit-Learn 和 Pandas 学习线性回归

      对于想深入了解线性回归的童鞋,这里给出一个完整的例子,详细学完这个例子,对用scikit-learn来运行线性回归,评估模型不会有什么问题了。 1. 获取数据,定义问题     没有...

wangxuwei
今天
1
0
MAC安装MAVEN

一:下载maven压缩包(Zip或tar可选),解压压缩包 二:打开终端输入:vim ~/.bash_profile(如果找不到该文件新建一个:touch ./bash_profile) 三:输入i 四:输入maven环境变量配置 MAVEN_HO...

WALK_MAN
今天
0
0
33.iptables备份与恢复 firewalld的9个zone以及操作 service的操作

10.19 iptables规则备份和恢复 10.20 firewalld的9个zone 10.21 firewalld关于zone的操作 10.22 firewalld关于service的操作 10.19 iptables规则备份和恢复: ~1. 保存和备份iptables规则 ~2...

王鑫linux
今天
2
0
大数据教程(2.11):keeperalived+nginx高可用集群搭建教程

上一章节博主为大家介绍了目前大型互联网项目的系统架构体系,相信大家应该注意到其中很重要的一块知识nginx技术,在本节博主将为大家分享nginx的相关技术以及配置过程。 一、nginx相关概念 ...

em_aaron
今天
1
0
Apache Directory Studio连接Weblogic内置LDAP

OBIEE默认使用Weblogic内置LDAP管理用户及组。 要整理已存在的用户及组,此前办法是导出安全数据,文本编辑器打开认证文件,使用正则表达式获取用户及组的信息。 后来想到直接用Apache Dire...

wffger
今天
2
0
HFS

FS,它是一种上传文件的软件。 专为个人用户所设计的 HTTP 档案系统 - Http File Server,如果您觉得架设 FTP Server 太麻烦,那么这个软件可以提供您更方便的档案传输系统,下载后无须安装,...

garkey
今天
1
0
Java IO类库之BufferedInputStream

一、BufferedInputStream介绍 /** * A <code>BufferedInputStream</code> adds * functionality to another input stream-namely, * the ability to buffer the input and to * sup......

老韭菜
今天
0
0
STM 32 窗口看门狗

http://bbs.elecfans.com/jishu_805708_1_1.html https://blog.csdn.net/a1985831055/article/details/77404131...

whoisliang
昨天
1
0
Dubbo解析(六)-服务调用

当dubbo消费方和提供方都发布和引用完成后,第四步就是消费方调用提供方。 还是以dubbo的DemoService举例 -- 提供方<dubbo:application name="demo-provider"/><dubbo:registry address="z...

青离
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部