整合完成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.com
mail_port=587
mail_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, int length){
StringBuilder sb = new StringBuilder();
int range = charArray.length;
for (int i = 0; i < length; i++) {
sb.append(charArray[random.nextInt(range)]);
}
return sb.toString();
}
public static String getRandomNumNoZero(int length){
return getRandom(numArrNoZero, length);
}
public static String getRandomNum(int length){
return getRandom(numArr, length);
}
}
发送验证码的时候,我们调用生成随机数的方法生成一个随机数,然后启动一个线程异步发送邮件,同时在数据库中记录邮箱、验证码、发送时间等信息,待提交注册信息时做验证。
在接口模块的用户服务接口中创建一个接口方法sendVerifyCode(String email),然后在用户服务中实现该方法。具体实现如下:
@Override
public boolean sendVerifyCode(String email) {
String verifyCode = IdGenerator.getRandomNum(4);
logger.info("验证码为:"+verifyCode);
MailSender.send(email, "Ron博客验证码", "您的验证码为"+verifyCode);
return true;
}
邮件的发送我们启动了一个线程去做,因为有时候发送邮件时间会很长。
public class MailSender extends Thread {
private Logger logger=LogManager.getLogger(this.getClass());
protected static final String FROM_NAME = "Ron博客邮件通知";
protected String toEmail;
protected String title;
protected String content;
public MailSender(String toEmail, String title, String content) {
this.title=title;
this.toEmail=toEmail;
this.content=content;
}
@Override
public void run() {
try {
MailUtils.sendSimpleEmail(FROM_NAME, toEmail, title, content);
} catch (EmailException e) {
logger.error("向【{}】发送“{}”邮件失败!", toEmail, title, e);
}
}
public static void send(String toEmail,String title,String content){
new MailSender(toEmail,title,content).run();
}
}
Ok,这样基本上验证码邮件基本就能发送成功了,关于验证码如何验证,我们下次再说。
项目源码:https://github.com/Ron-Zheng/blog-system