搭建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);
}
};
} }

Spring Boot搭建Web项目常用功能的更多相关文章

  1. spring boot 搭建web项目常见五种返回形式

    在web项目中一般常见的五种返回形式: 返回页面,使用模板引擎,spring boot推荐使用thymeleaf,类似的还有freemarker等. 返回字符串(json),一般用于完全的前后端分离开 ...

  2. 使用Spring Boot开发Web项目(二)之添加HTTPS支持

    上篇博客使用Spring Boot开发Web项目我们简单介绍了使用如何使用Spring Boot创建一个使用了Thymeleaf模板引擎的Web项目,当然这还远远不够.今天我们再来看看如何给我们的We ...

  3. 【spring boot】5.spring boot 创建web项目并使用jsp作前台页面

    贼烦的是,使用spring boot 创建web项目,然后我再idea下创建的,but 仅仅启动spring boot的启动类,就算整个项目都是好着的,就算是能够进入controller中,也不能成功 ...

  4. 基于Spring boot的web项目搭建教程(一)

    前言: 本教程参考了大量前辈的代码,在此不方便一一列举.本教程使用IDEA开发工具搭建项目,对于本人的IDEA已经集成了某些插件,比如Lombok,Thymeleaf,yml等插件,这些插件不在文中提 ...

  5. Spring Boot从入门到实战:整合Web项目常用功能

    在Web应用开发过程中,一般都涵盖一些常用功能的实现,如数据库访问.异常处理.消息队列.缓存服务.OSS服务,以及接口日志配置,接口文档生成等.如果每个项目都来一套,则既费力又难以维护.可以通过Spr ...

  6. 使用Spring Boot开发Web项目

    前面两篇博客中我们简单介绍了Spring Boot项目的创建.并且也带小伙伴们来DIY了一个Spring Boot自动配置功能,那么这些东西说到底最终还是要回归到Web上才能体现出它的更大的价值,so ...

  7. Spring MVC 搭建web项目示例

    环境为Eclipse 1:新建Dynamic web project  : springMvcDemo 2:下载spring的jar包,把jar包复制到WEB-INF/lib目录下 3.添加配置文件w ...

  8. 03.基于IDEA+Spring+Maven搭建测试项目--常用dependency

    <!--常用的依赖配置--> <!--未展示完整的pom.xml文件内容--> <properties> <java.version>1.8</j ...

  9. spring boot 创建web项目并使用jsp作前台页面

    参考链接:https://www.cnblogs.com/sxdcgaq8080/p/7712874.html

随机推荐

  1. Jmeter学习过程中遇到的那些坑

    开个新帖,持续记录学习jmeter过程中遇到的坑... (1)出师不利 由于公司的产品都是客户端模式,所以所有的接口测试都从获取access-token开始.妹的...上来就是一个坑... 一开始的配 ...

  2. 服务器windows2008系统登录报错:由于远程桌面服务当前正忙,因此无法完成您尝试的任务。请在...

    1.问题描述:windows server 2008服务器通过远程桌面登录时很慢,登录不进去,把远程桌面关掉后,再用远程桌面登录时,出现下图提示. 把服务器接上显示器键盘鼠标后,卡在系统登录的欢迎界面 ...

  3. MAC OS 更新GIT版本时遇到的问题

    在更新git版本时,没有备份就删掉了原先的版本,在安装完2.18.0的新版本后,使用命令行git --version,返回错误git not a developer tool or in PATH. ...

  4. 不适合使用hadoop来解决的问题

    1.Hadoop能解决的问题必须是可以mapreduce的.一是问题可以拆分,二是子问题必须独立.比如斐波那契数列就不适合. 2.数据结构不满足key-value形式的.比如结构化的数据查询. 3.不 ...

  5. Spring Boot 2 - 使用CommandLineRunner与ApplicationRunner

    本篇文章我们将探讨CommandLineRunner和ApplicationRunner的使用. 在阅读本篇文章之前,你可以新建一个工程,写一些关于本篇内容代码,这样会加深你对本文内容的理解,关于如何 ...

  6. 初探系列 — Pharbers用于单点登录的权限架构

    一. 前言 就职公司 法伯科技是一家以数据科技为驱动, 专注于医药健康领域的循证咨询公司. 以数据科学家身份, 赋能医药行业. 让每位客户都能享受数据带来的价值, 洞察业务, 不止于数据, 让决策更精 ...

  7. Numpy学习三:数组运算

    1.转置 #reshape(shape)函数改变数组形状,shape是一个元组,表示数组的形状 创建一个包含15个元素的一维数组,通过reshape函数调整数组形状为3行5列的二维数组arr = np ...

  8. 微信APP支付,支付宝APP支付demo

    最近公司新开发的APP中,需要集成微信支付和支付宝支付,2个平台申请的都是APP支付.这是个人第一次单独的,完整的做完2个平台的支付. 这里我主要用到了2个接口:支付接口,订单查询接口,虽然2个平台的 ...

  9. gcc编译参数详解概述

    gcc 编译器是经常使用的,可是,自己却没有针对它做过专门的研究,当遇到问题了,总结一下,算是对未来有个积累吧. 一 关于编译告警: 1 -w : 关闭所有警告,不建议使用 2 -W 开启素有gcc ...

  10. python通过手机抓取微信公众号

    使用 Fiddler 抓包分析公众号 打开微信随便选择一个公众号,查看公众号的所有历史文章列表 在 Fiddler 上已经能看到有请求进来了,说明公众号的文章走的都是HTTPS协议,这些请求就是微信客 ...