Spring Boot搭建Web項目常用功能

来源:https://www.cnblogs.com/smiler/archive/2018/07/11/9292693.html
-Advertisement-
Play Games

搭建WEB項目過程中,哪些點需要註意: 1、技術選型: 前端:freemarker、vue 後端:spring boot、spring mvc 2、如何包裝返回統一結構結果數據? 首先要弄清楚為什麼要包裝統一結構結果數據,這是因為當任意的ajax請求超時或者越權操作時,系統能返回統一的錯誤信息給到前 ...


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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.Socket簡介 Socket也稱作“套接字“,是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象為幾個簡單的介面供應用層調用以實現進程在網路中通信。它分為流式套接字和數據包套接字,分別對應網路傳輸控制層的TCP和UDP協議。TCP協議是一種面向連接的、可靠的、基於位元組流的傳輸 ...
  • 相信看過 每天學點SpringCloud(一):簡單服務提供者消費者調用的同學都發現了,在最後消費者調用提供者的時候把提供者的地址硬編碼在了代碼中,這樣的方式肯定是不行的,今天,我們就是要Eureka來解決這個問題 創建Eureka服務 1.我們在原先項目的基礎上再新建一個項目cloud-demo- ...
  • 微服務 將整體功能按著模塊劃分成多個獨立的單元,這些單元可以獨立部署,它們之前通過輕量級的web api方式進行通訊,對於微服務框架來說,最流行的就是springcloud和Service Fabric,前者是java開發,後者是.net的產品,今天主要介紹一下springcloud! 參考文章:h ...
  • 系統介紹: 1.系統採用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC瀏覽器使用) 2.springmvc +spring4.3.7+ mybaits3.3 SSM 普通java web(非maven, 附贈pom.xml文件) 資料庫:mysql 3.開發工具:my ...
  • 基於.net core 的微服務,網上很多介紹都是千篇一律基於類似webapi,通過http請求形式進行訪問,但這並不符合大家使用習慣.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 調用遠程的服務,如果你正在為此苦惱, 本文 ...
  • 大型網站架構從來都不是一個預先定義的架構,而是一個演進式的架構。很少有一個網站從建站開始,就能夠因具備大型網站的所有屬性而一成不變的,從最簡單的LAMP架構,再到基於IOE的大型集中式應用架構,再演變成時下的分散式應用架構,隨著網站用戶規模的擴大,架構也在不斷演進。從實體機到虛擬機再到當前流行的Do... ...
  • 最近開始學習SpringCloud,在此把我學習的過程記錄起來,跟大家分享一下,一起學習。想學習SpringCloud的同學趕快上車吧。 本次學習使用得SpringBoot版本為2.0.3.RELEASE,SpringCloud版本為Finchley.RELEASE 創建父Maven工程 首先我們創 ...
  • 在我的理解中,面向對象就是一種萬物皆對象的編程思想,就是把現實世界中所有的事物都當做對象來看待,而每一個對象可以看成是一個事物的實例,面向對象是以對象為中心,以消息為驅動,所以程式=對象+消息; 面向對象有三大特征:封裝 繼承 多態 封裝:將屬性和行為抽象成一個類,將其屬性私有化,行為公開化,提高了 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...