整合完成Mybatis、Log4j2之後,接下來將進入到具體的功能研發過程,首先咱們先說明一下,進入具體研發過程之後,由於代碼會很多,所以在後續的功能研發的過程中,我們將會很少貼代碼(因為頁面前端和後台代碼加起來會很多,貼代碼還不如自己去GitHub下載),源碼大家可以直接到Github上下載,我們將會更多講解的是具體功能的研發方案以及所使用的關鍵技術。
在做具體功能之前,我們可能有很多條件需要考慮,如選擇哪種網頁布局和修飾架構,一些基本問題(如中文亂碼)如何解決,前端架構選擇等,首先先讓大家看一下我們Blog系統會做成什麼樣子。
我們部落格系統的大致需要做成如上圖所示的一個效果。
1、Sitemesh使用
一般情況下,我們所開發的應用中,頁面的布局和外觀基本都是一致的,而且頁面的功能表列和底部的著作權資訊等內容一般情況下都不會發生變化。然而在所有的頁面中,如果我們都將功能表列和底部著作權資訊都拷貝一份到每個頁面的話,如果有一天我們系統的菜單或者著作權資訊發生變化的話,我們就不得不修改所有的頁面。
那有沒有一種方法能夠將公用的部分統一處理,而其他不同的頁面只需要進行一些簡單的設定,就可以繼承或者共用公用部分的代碼呢。答案當然是肯定的,這就是我們需要瞭解和學習的裝飾器。
目前市面上的裝飾器有很多,我們的部落格系統選擇Sitemesh來作為我們系統的裝飾器。SiteMesh是一個網頁布局和修飾的架構,利用它可以將網頁的內容和頁面結構分離,以達到頁面結構共用的目的。
Sitemesh是由一個基於Web頁面配置、裝飾以及與現存Web應用整合的架構。它能協助我們在由大量頁面構成的項目中建立一致的頁面配置和外觀,如一致的導航條,一致的banner,一致的著作權,等等。它不僅僅能處理動態內容,如jsp,php,asp等產生的內容,它也能處理靜態內容,如htm的內容,使得它的內容也符合你的頁面結構的要求。甚至於它能將HTML檔案象include那樣將該檔案作為一個面板的形式嵌入到別的檔案中去。所有的這些,都是GOF的Decorator模式的最生動的實現。儘管它是由java語言來實現的,但它能與其他Web應用很好地整合。
使用Sitemesh首先需要引入兩個依賴,在blog_pc模組的pom.xml中:
<dependency><groupId>opensymphony</groupId> <artifactId>sitemesh</artifactId> <version>2.4.2</version></dependency><dependency><groupId>org.sitemesh</groupId> <artifactId>sitemesh</artifactId> <version>3.0.1</version></dependency>
其中OS(OpenSymphony)的SiteMesh是一個用來在JSP中實現頁面配置和裝飾(layout and decoration)的架構組件,能夠協助網站開發人員較容易實現頁面中動態內容和靜態裝飾外觀的分離。
依賴配置好之後,我們需要在web.xml中配置sitemesh的過濾器:
<!-- 添加Sitemesh 3過濾器start --><filter> <filter-name>sitemesh</filter-name> <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class></filter><filter-mapping> <filter-name>sitemesh</filter-name> <url-pattern>/*</url-pattern></filter-mapping><!-- 添加Sitemesh 3過濾器end -->
上面的這個配置表示所有的請求都使用com.opensymphony.module.sitemesh.filter.PageFilter過濾器進行過濾。
然後是配置Sitemesh描述符檔案,用於指定裝飾頁面和需要裝飾和不需要裝飾的頁面。首先需要在WEB-INF目錄下建立一個decorators.xml檔案,然後寫入如下配置:
<?xml version="1.0" encoding="utf-8"?><decorators defaultdir="/WEB-INF/views/layout/"> <!-- 此處用來定義不需要過濾的頁面 --> <!-- <excludes> <pattern>/static/*</pattern> </excludes> --> <!-- 用來定義裝飾器要過濾的頁面 --> <decorator name="default" page="blog_decor.jsp"> <pattern>/*</pattern> </decorator></decorators>
這樣sitemesh就配置完了,接下來就在/WEB-INF/views/layout/目錄下建立一個blog_decor.jsp的頁面,作為裝飾頁面。
我們部落格系統的裝飾頁面按照如下的方式設計:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ taglib prefix="sitemesh" uri="http://www.opensymphony.com/sitemesh/decorator" %><c:set var="ctx" value="${pageContext.request.contextPath}" /><!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate" /><meta http-equiv="pragma" content="no-cache" /><meta http-equiv="Expires" content="0" /><%@ include file="../include/head.jsp"%><title><sitemesh:title/></title><sitemesh:head/></head><body> <%@ include file="../include/header.jsp"%> <sitemesh:body/> <%@ include file="../include/bottom.jsp"%></body></html>
系統頭部和底部固定,中間內容取各個jsp頁面中的body標籤所包含的內容。
頭部代碼寫在了/WEB-INF/views/include/header.jsp檔案中,底部代碼寫在了/WEB-INF/views/include/bottom.jsp檔案中,這裡就不貼出來了,讀者自行到GitHub下載。
2、中文亂碼解決
在使用Spring MVC開發應用時,我們經常會遇到中文亂碼的問題,所以我們需要在web.xml檔案中配置一個字元編碼過濾器,將系統強制編碼為UTF-8,具體配置如下(在web.xml檔案中追加),此文不做具體講解。
<!-- 解決中文亂碼 start --><filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 解決中文亂碼 end -->
3、前端架構選擇
我們部落格系統前端架構選擇使用layerui,關於layerui更多的內容,請上layerui的官方網站查看。
http://layer.layui.com/,本文不做詳細說明。
4、Json資料支援
在我們系統研發過程中,在很多請款下,我們都需要使用json格式的資料,比如前後台通訊時,使用json格式的資料會更容易處理,資料處理時,也需要支援json、對象、map之間的轉換,所以我們系統也需要支援這些功能。所以我們的部落格系統使用大家都很熟知的jackson。
在部落格系統的父模組blog的pom中,我們添加相關依賴如下:
<!-- json資料 --><dependency><groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-asl</artifactId> <version>${jackson.version}</version></dependency><dependency><groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson_version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson_version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson_version}</version></dependency><dependency><groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-jaxb-annotations</artifactId> <version>${jackson_version}</version></dependency>
依賴配置完畢之後,還需要到blog_pc模組的spring-mvc.xml檔案中增加訊息轉換器(MappingJackson2HttpMessageConverter)和註解方法處理適配器(AnnotationMethodHandlerAdapter),如果不進行這個配置,我們使用如@ResponseBody註解的方法返回的對象需要轉換成json格式時就會報異常。
<!-- rest json related... start --><bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property></bean><bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="mappingJacksonHttpMessageConverter"/> </list> </property></bean><!-- rest json related... end -->
5、郵件發送使用(驗證碼發送)
好,現在基本上我們基礎的配置已經完成了(注意,關於前端CSS、JS、HTML這些東西,大家直接上GitHub拿下來就好了,代碼太多,不適合一一貼出來),我們先做一個註冊頁面,在控制器的包下建一個user包,然後建一個UserController,在控制器中建立一個方法,讓請求導向註冊頁面,我們部落格系統將註冊的jsp頁面放在了/WEB-INF/views/user目錄下,命名為register。
註冊頁面樣式大概如下圖:
我們這裡主要需要實現一個功能,那就是當使用者填寫完成使用者郵箱之後,點擊“擷取驗證碼”按鈕,我們會通過郵件的形式往使用者的郵箱發送一個註冊驗證碼。
這裡就涉及到一個關鍵的技術點,那就是郵件的發送,我們這裡給大家講一下使用javax.mail如何發送郵件以及驗證碼發送功能的實現。
使用javamail發送郵件,首先需要添加兩個依賴,因為郵件發送是一個公用的功能,我們在其他模組都有可能調用它,所以我們將發送郵件的這一塊放在common模組中,所以,在blog_common模組的pom.xml中,加入如下依賴:
<dependency><groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>${java_mail_version}</version></dependency><dependency><groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>${commons_email_version}</version></dependency>
依賴配置好之後,接下來要做的事情就是弄一個smtp服務,我們部落格系統使用qq郵箱提供的smtp服務來實現。
配置smtp服務首先需要到qq郵箱設定並開啟smtp服務,然後您將會獲得一個授權碼,特別要記住,發送郵件時設定的驗證資訊中,郵箱密碼是設定smtp服務時給您的授權碼,不是您自己的郵箱密碼,切記,同時使用qq的smtp服務時,一定要設定smtp的連接埠,不能使用預設連接埠,使用預設連接埠會報530錯誤,可以將連接埠設定成587。
在blog_pc模組的resources目錄下,建立一個config.properties設定檔,然後寫入您的smtp服務資訊。
mail_host=smtp.qq.commail_port=587mail_address=郵箱mail_passowrd=授權碼
在我們的部落格系統的公用模組,也就是blog_common模組中,我們建立一個config包,這個包下面我們用來實現擷取各種各樣的配置資訊的功能。
首先我們在config包中建立一個SysConfig.java類,該類用來讀取config.properties檔案中的配置資訊。所以我們在該類中需要設定四個私人靜態屬性(因為這些屬性一般配置之後的變更頻率非常低),分別是mail_host、mail_port、mail_address、mail_passowrd,當然後續如果還有配置資訊需要讀取,也可以設定到該類中,同時這些屬性只需要設定擷取(get)方法就行了,不必設定set方法,因為這些屬性的值我們將會從設定檔中擷取。
在SysConfig類中,我們在擷取設定檔時,因為我們僅僅只需要擷取一次,所以我們編寫一個靜態代碼塊,讓JVM在載入類的時候就給配置類設定屬性值,關鍵代碼如下。
public class SysConfig { private static String mail_host; private static String mail_address; private static String mail_passowrd; private static String mail_port; @IgnoreAssignment private static Logger logger = LogManager.getLogger(SysConfig.class); @IgnoreAssignment public static final InputStream fileInput = SysConfig.class.getResourceAsStream("/config.properties"); @IgnoreAssignment private static Properties prop = new Properties(); private SysConfig(){} static{ load(fileInput); } private static void load(InputStream is){ try { prop.load(is); } catch (IOException e) { logger.error("",e); } //給Config類屬性賦值 Class<SysConfig> configClass = SysConfig.class; Field[] fields = configClass.getDeclaredFields(); for (Field field : fields) { IgnoreAssignment ia = field.getAnnotation(IgnoreAssignment.class); if(ia == null){ String fieldName = field.getName(); try { Object valObj = field.get(configClass); String fieldValue = (valObj != null) ? String.valueOf(valObj) : ""; String proValue = prop.getProperty(fieldName); if(StringUtils.isEmpty(proValue)){ proValue = prop.getProperty(fieldName.replace("_", ".")); } if(!fieldValue.equals(proValue)){//如果值不一樣才賦值 field.setAccessible(true); field.set(configClass, proValue); } } catch (Exception e) { logger.error("欄位名{}賦值失敗", fieldName, e); } } } } public static String getMail_host() { return mail_host; } public static String getMail_address() { return mail_address; } public static String getMail_passowrd() { return mail_passowrd; } public static String getMail_port() { return mail_port; }}
基本配置完成之後,我們需要開發一個郵件發送的協助類,在blog_common模組下的utils包下建立一個MailUtils類,寫兩個方法,一個發送簡單的郵件,一個發送html郵件代碼如下:
public static String sendSimpleEmail(String fromName, String to, String subject, String content) throws EmailException{ String res = null; SimpleEmail simpleEmail = new SimpleEmail(); simpleEmail.setSocketConnectionTimeout(CONNECTION_TIMEOUT); simpleEmail.setSocketTimeout(TIMEOUT); simpleEmail.setHostName(SysConfig.getMail_host()); simpleEmail.setAuthentication(SysConfig.getMail_address(), SysConfig.getMail_passowrd()); simpleEmail.setFrom(SysConfig.getMail_address(), fromName); simpleEmail.setSmtpPort(Integer.parseInt(SysConfig.getMail_port())); simpleEmail.addTo(to); simpleEmail.setSubject(subject); simpleEmail.setMsg(content); res = simpleEmail.send(); return res;}public static String sendHtmlEmail(String fromName, String to, String subject, String content) throws EmailException{ String res = null; HtmlEmail htmlEmail = new HtmlEmail(); htmlEmail.setSocketConnectionTimeout(CONNECTION_TIMEOUT); htmlEmail.setSocketTimeout(TIMEOUT); htmlEmail.setHostName(SysConfig.getMail_host()); htmlEmail.setAuthentication(SysConfig.getMail_address(), SysConfig.getMail_passowrd()); htmlEmail.setFrom(SysConfig.getMail_address(), fromName); htmlEmail.setSmtpPort(Integer.parseInt(SysConfig.getMail_port())); htmlEmail.addTo(to); htmlEmail.setSubject(subject); htmlEmail.setMsg(content); htmlEmail.setCharset("utf-8"); res = htmlEmail.send(); return res;}
郵件的配置、前端架構的選擇什麼的基本已經完成,接下來就是產生驗證碼,驗證碼的產生其實就是產生一個固定長度的隨機數,我們提供兩種產生隨機數的方法,一種是有0的,一種是沒有0的,具體產生方法如下。
public class IdGenerator { private static final char[] numArr = {'0','1','2','3','4','5','6','7','8','9'}; private static final char[] numArrNoZero = {'1','2','3','4','5','6','7','8','9'}; private static final Random random = new Random(); private static String getRandom(char[] charArray,