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

 

转载于:https://www.cnblogs.com/smiler/p/9292693.html

【Spring Boot】使用Spring Boot来搭建Java web项目以及开发过程 一、Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。SpringMVC是非常伟大的框架,开源... 阅读详情

相关推荐

Spring Boot入门(02):【教程】轻松搭建Spring Boot开发环境,启动你的第一个Web项目!

快速搭建开发环境和启动项目是每个开发人员必备的技能。本文将为您提供一些简单易懂的方法和技巧,以帮助您快速搭建开发环境和启动项目。我们将介绍如何选择适合您的开发工具、配置您的操作系统、安装必要的软件包和依赖项,并开始编写您的第一个应用程序。无论您是一位新手还是一位经验丰富的开发人员,本文都会给您提供有用的信息和实用的建议,让您在最短时间内快速搭建和启动开发项目。

**My Coding Family** 22万+

通过EmbeddedServletContainerCustomizer接口调优Tomcat

通过在application.properties设置对应的key-value对,可以配置Spring Boot应用程序的很多特性,例如POST、SSL、MySQL等等。如果需要更加复杂的调优,则可以利用Spring Boot提供的EmbeddedServletContainerCustomizer接口通过编程方式和修改配置信息。 尽管可以通过application.properties设置se...

weixin_43770982的博客 476

SpringBoot三层架构Demo

本项目下载后,直接在IDE中导入,由于是Maven项目,所以你本地要有Maven环境,要不然下载不了Jar包

《Spring Boot实战指南》05-00:前言

--

西安极客联盟 442

ddd-java:Spring Boot + Java [DDD示例]

ddd-java はじめに JSUG(日本Springユーザ会)の下记勉强会向けのサンプル実装です。 2014/11/27「SpringBootを用いたドメイン駆动设计」 Spring Boot 2使用Java8へ切へ替えています。Java7でのブ装コードを确认したいときは1.xブランチを参照してください。 本実ンプルくらとを利用してドメインモデリングの実装例を示例。つ际に2007年くらいい现在に至るまで现场で利用されている実装アプローチなので,参考例の一つとしてみてもらえればと思います。 ※JavaDocに记をしていますが,サンプルに特化させているので実际の制品コードが含まれているわけで

spring-ddd:一个使用Spring Boot和DDD提出的分层架构的项目示例

Spring Boot和分层架构

Spring Boot实现Web的常用功能

Spring Boot实现Web的常用功能 通常在Web开发中,会涉及静态资源的访问支持、视图解析器的配置、转换器和格式化器的定制、文件上传下载等功能,甚至还需要考虑到与Web服务器相关联的Servlet相关组件的定制。Spring Boot框架支持整合一些常用Web框架,从而实现Web开发,并默认支持Web开发中的一些通用功能。 Spring MVC的整合支持 为了实现并简化Web开发,Spring Boot为一些常用的Web开发框架提供了整合支持,例如Spring MVC、Spring WebFlux等

shi_zi_183的博客 1675

Spring Boot整合Web项目常用功能详解

主要介绍了Spring Boot整合Web项目常用功能详解,在Web应用开发过程中,可以通过Spring Boot的Starter来将这些常用功能进行整合与集中维护,以达到开箱即用的目的。,需要的朋友可以参考下

Spring Boot企业级开发教程课后习题——第5章Spring Boot实现Web的常用功能

1. Spring Boot项目中定制Spring MVC的扩展功能,需要提供实现(WebMvcConfigurer)接口的配置类。3. Spring Boot中使用路径扫描的方式整合内嵌式Servlet组件时,需要使用的注解有(ABC)(多选)4.下列关于 Spring Boot整合 Spring MVC实现文件上传及下载的说法中,正确的是(B)。1. Spring Boot为整合 Spring MVC实现Web开发,提供的功能特性不包括(C)。

WHT869706733的博客 4626

lidar点云比赛-Southlidar软件使用入门

lidar点云比赛-Southlidar软件使用入门 广西高校重点实验室培训基地 系统控制与信息处理重点实验室 本篇博客来自河池学院: 智控无人机小组 写作时间:2020.09.29 一、软件资源及安装 1.下载地址: https://pan.baidu.com/s/1jN1pynmAZOqj5mMx774pVg 提取码:gkvv (资源来自南方测绘) 2.安装 进入链接,找到软件文件夹, 内含文件即为安装包 3.下载成功后按提示安装即可 4.安装成功后桌面自动创建一个快捷方式 二、界面布

qq_49621416的博客 4891

DexProtector内存态匿名段检测绕过与dex重组技术

Dex文件是Android应用的核心执行单元,其结构规范(如header_item、class_def_item、code_item)决定了运行时加载与解析的底层逻辑。DexProtector通过将dex数据拆解为多个无文件名、受保护的内存匿名段(如[anon:dp_header]、[anon:dp_code]),规避传统基于文件特征或标准加载路径的检测机制。该策略依赖Android mmap内存管理与ART虚拟机对dex结构的刚性要求,形成可被观测的布局规律。理解其内存态组织原理,结合权限/大小/命名/内

weixin_30855761的博客 688

【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元

在Spring框架的发展历程中,随着项目的规模不断增大,Spring的配置也变得越来越复杂。Spring Boot的出现,正是为了解决这一问题,它基于Spring Framework提供了一套自动配置的解决方案,让开发者能够通过少量的配置快速启动一个Spring应用。

2509_94095062的博客 986

使用Kolla部署多节点Rocky版本OpenStack

openstack命令行客户端使用,post-deploy会在 /etc/kolla/目录下生成文件admin-openrc.sh。每次使用openstack命令时执行source /etc/kolla/admin-openrc.sh(manager节点执行)source /etc/kolla/admin-openrc.sh (将文件内容复制到/etc/profile中,即可一直生效)3.2.2 开始安装kolla-ansible,要带上--ignore-installed PyYAML,否则可能会报错。

gyc1206的博客 1926

spring boot 搭建web项目完整流程,以及遇到的一些坑坑洼洼总结

一、spring boot简介     spring boot是最近非常流行的,在spring的框架上改进的框架。该框架的目的是为了提高开发人员的速度,降低配置的难度等等,极大了简化了开发流程。具体的详细的说明请参考官方文档。在日常工作中,由于需要搭建一套环境或者框架的机会非常的少,大部分都是在原有的基础上开发,所以当让你搭建一个简单的框架,就会出现各种各样的困难,比如说,我在搭建的一个服务器的时...

fyhailin的专栏 15万+

万象RK3506-EG1800网关使用说明

摘要:HD-RK3506-EG1800是一款高性能工业物联网边缘计算网关,支持Buildroot和Ubuntu双系统。文档详细介绍了网关的网络配置(包括ETH0/ETH1的静态IP设置、WiFi连接)、时钟同步、HSL软件安装配置(含开机自启动服务)、系统日志优化以及刷机操作指南(Loader/Maskrom模式)。特别提供了基于lighttpd的网页管理方案,通过HTML界面实现网络参数可视化配置,包含身份验证、实时状态显示和表单提交功能。网关默认ETH0固定IP为192.168.1.10,提供完整的CL

tian_jiangnan的博客 174

Linux:在使用UEFI固件的计算机上内核是如何被启动的

启动计算机通常不是一件难事:按下电源键,稍等片刻,你就能看到一个登录界面,再输入正确的密码,就可以开启一天的网上冲浪之旅了。但偶尔这件事没那么顺利,有时候迎接你的不是熟悉的登录界面,而是一个令人生畏的命令提示符界面,一闪一闪的提示符告诉你:“你碰到麻烦了”。于是你对着错误提示查找解决方法,按照网页上的步骤,你对着提示符输入并执行了几条你完全不理解的命令,计算机又能正常启动了,但同时你发现你那存有大学舍友糗照的硬盘分区被清空了。

imred的专栏 3068
上一篇: 如何优雅的实现INotifyPropertyChanged接口
下一篇: vscode 输出乱码
dddp75921
博客等级 码龄12年 9粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值