文档章节

Spring Boot搭建Web项目要点

woter
 woter
发布于 07/09 17:57
字数 1672
阅读 1438
收藏 58

搭建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
粉丝 49
博文 113
码字总数 61048
作品 0
深圳
技术主管
私信 提问
加载中

评论(3)

逗逼
我挺想知道 AnnotationHandleUtils.isAjaxAnnotation 这个是怎么实现的。
woter
woter

引用来自“Tkks”的评论

freemarker, 前端?

没前后分离 freemarker做渲染模板
Tkks
Tkks
freemarker, 前端?
Spring Boot2.X来临,扒一扒Spring家族的前世今生

当前互联网技术盛行,以Spring 框架为主导的Java 互联网技术成了主流,而基于Spring 技术衍生出来的Spring Boot,采用了“约定优于配置”的思想,极大地简化了Spring 框架的开发。 随着近些年...

异步社区
08/01
0
0
初试Spring Boot:构建第一个Web程序

Spring Boot主要提供快速构建项目的功能。本文中我们会使用Spring Boot构建第一个Web程序,同时介绍Spring Boot最简单的功能,例如运行单元测试,发布与调用REST服务等。 本文作者杨恩雄,选...

博文视点
11/14
0
0
基于Spring Boot的登录demo

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

tikyle
05/14
0
0
Spring Boot [Hello World]

导读: 通过上篇文章, 我们已经了解到了 Spring Boot 作为一个Spring的脚手架, 其核心思想便是约定大于配置,通过一层层的封装让我们可以在最短的时间内搭建一个web项目,从繁琐的配置中走...

yangrd
08/27
0
0
spring boot与spring mvc的区别是什么?

spring boot与spring mvc的区别是什么? 转载:https://blog.csdn.net/u014590757/article/details/79602309 spring boot只是一个配置工具,整合工具,辅助工具. springmvc是框架,项目中实际运...

Elsa晓冰
09/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

使用JavaScript编写iOS应用业务逻辑

JSAUIKitCocoa使你可以使用JavaScript编写对性能要求不高但可能变动性很大的iOS应用的业务逻辑部分,View组件、需要多线程支持的Model等则直接使用原生对象。 编写方式与React Native相似,但...

neal01
21分钟前
1
0
艺术品区块链溯源防伪平台(连载一)

Netkiller Blockchain 手札 作者正在找工作,联系方式 13113668890 Mr. Neo Chan, 陈景峯(BG7NYT) 中国广东省深圳市望海路半岛城邦三期 518067 +86 13113668890 <netkiller@msn.com> 文档始创...

netkiller-
21分钟前
2
0
0032-如何在CDH启用Kerberos的情况下安装及使用Sentry(二)

温馨提示:要看高清无码套图,请使用手机打开并单击图片放大查看。 5.Sentry列权限管理 1.在集群所有节点添加fayson_r用户 [root@ip-172-31-6-148 cdh-shell-bak]# useradd fayson_r[root@i...

Hadoop实操
25分钟前
1
0
Nginx配置中Location的优先级

根据Nginx的官方文档,Location标签一共有四个修饰符,分别是: (1) =:表示完全匹配; (2) ^~:匹配URI的前缀,并且后面的正则表达式不再匹配,如果一个URI同时满足两个规则的话,匹配最长的规...

cloes
昨天
1
0
Xcode 10 Archive 卡死问题

前段时间贪新鲜更新了xcode 10,发现就是自己没事找事后悔啊........ 首先是 libstdc++.6.0.9.tbd 已不被使用,以前的项目是一顿报错!!!一个个改也不是办法还有一些第三方的用到只好把lib...

壹峰
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部