摘要:给用户发送邮件的场景,其实也是比较常见的,比如用户注册需要邮箱验证,用户异地登录发送邮件通知等等,在这里我以 RabbitMQ 实现异步发送邮件。
项目git地址:https://github.com/gitcaiqing/RabbitMQ-Email
1.项目结构
2.构建项目
创建maven项目,引入jar包,pom.xml配置如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sc</groupId>
<artifactId>RabbitMQ-Emali</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.4.RELEASE</spring.version>
<jackson.version>2.5.0</jackson.version>
<shiro.version>1.2.3</shiro.version>
</properties>
<dependencies>
<!--rabbitmq包 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<!-- spring mail需要的jar包 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<!-- spring核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- json -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.项目配置,着重实现邮件发送,配置简化处理
3.1 RabbitMQ和邮件地址配置config->config.properties
#RabbitMQ 连接配置
rmq.host=127.0.0.1
rmq.port=5672
rmq.user=guest
rmq.password=guest
rmq.channelCacheSize=25
rmq.virtual=/
#邮件发送配置
# 163 mail server
mail.protocol=smtp
mail.port=25
mail.host=smtp.163.com
#发送邮件的邮箱地址账号信息
mail.from=xxxxxxxxx@163.com
mail.username=xxxxxxx@163.com
mail.password=xxxxxxxxx
# qq mail server
#mail.protocol=smtp
#mail.port=465
#mail.host=smtp.exmail.qq.com
#mail.username=xxx@qq.com
#mail.password=
3.2 spring 配置spring->applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!--引入配置属性文件 -->
<context:property-placeholder location="classpath*:config/*.properties" />
<context:component-scan base-package="com.sc"/>
</beans>
3.3 spring邮件配置spring->spring-mail.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<description>Spring 邮件发送bean配置</description>
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.host}"></property>
<property name="port" value="${mail.port}"></property>
<property name="username" value="${mail.username}"></property>
<property name="password" value="${mail.password}"></property>
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.timeout">25000</prop>
<prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
<!-- 如果是网易邮箱, mail.smtp.starttls.enable 设置为 false-->
<prop key="mail.smtp.starttls.enable">true</prop>
</props>
</property>
</bean>
</beans>
3.4 RabbitMQ配置spring-rabbitmq.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<description>Spring RabbitMQ 路由模式(Routing)配置</description>
<!--引入配置属性文件 -->
<context:property-placeholder location="classpath:config/*.properties" />
<!-- 配置RabbitMQ连接 -->
<!-- channel-cache-size,channel的缓存数量,默认值为25 -->
<!-- cache-mode,缓存连接模式,默认值为CHANNEL(单个connection连接,连接之后关闭,自动销毁) -->
<rabbit:connection-factory id="connectionFactory" host="${rmq.host}" port="${rmq.port}"
username="${rmq.user}" password="${rmq.password}" virtual-host="${rmq.virtual}" channel-cache-size="${rmq.channelCacheSize}" cache-mode="CHANNEL"/>
<rabbit:admin connection-factory="connectionFactory"/>
<!--
定义消息队列
durable:是否持久化,如果想在RabbitMQ退出或崩溃的时候,不失去queue和消息,需要同时标志队列(queue)
和交换机(exchange)持久化,即rabbit:queue标签和rabbit:direct-exchange中的durable=true,而消息(message)
默认是持久化的,可以看类org.springframework.amqp.core.MessageProperties中的属性
public static final MessageDeliveryMode DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT;
auto_delete: 当所有消费客户端连接断开后,是否自动删除队列
exclusive: 仅创建者可以使用的私有队列,断开后自动删除;
-->
<rabbit:queue id="emailqueue" name="SpringRabbit-Email-Queue" durable="true" auto-delete="false" exclusive="false"/>
<!--
绑定队列
rabbitmq的exchangeType常用的三种模式:direct,fanout,topic三种,此处为direct模式,即rabbit:direct-exchange标签,
Direct交换器很简单,如果是Direct类型,就会将消息中的RoutingKey与该Exchange关联的所有Binding中的BindingKey进行比较,如果相等,
则发送到该Binding对应的Queue中。有一个需要注意的地方:如果找不到指定的exchange,就会报错。
但routing key找不到的话,不会报错,这条消息会直接丢失,所以此处要小心,
auto-delete:自动删除,如果为Yes,则该交换机所有队列queue删除后,自动删除交换机,默认为false
-->
<rabbit:direct-exchange name="SpringRabbit-Email-Exchange" durable="true" auto-declare="true" auto-delete="false">
<rabbit:bindings>
<rabbit:binding queue="emailqueue" key="info"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- spring template声明 -->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="SpringRabbit-Email-Exchange"></rabbit:template>
<!-- 消费者 -->
<bean id="consumer" class="com.sc.consumer.Consumer"/>
<!-- 队列监听-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener queues="emailqueue" ref="consumer" />
</rabbit:listener-container>
</beans>
3.5 spring mvc配置servlet->spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<!-- 自动扫描,只扫描Controller -->
<context:component-scan base-package="com.sc">
</context:component-scan>
<!-- 启用spring mvc 注解 -->
<mvc:annotation-driven/>
<!-- 试图解析后台 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views" />
<property name="suffix" value=".jsp" />
<property name="order" value="1" />
</bean>
</beans>
3.6 web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>RabbitMQ-Emali</display-name>
<!-- 配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value> classpath*:spring/*.xml,classpath*:servlet/*.xml</param-value>
</context-param>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring MVC servlet -->
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:servlet/*.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
4.创建用户对象entity->user
简单创建用户对象包含用户名,密码,邮箱属性
package com.sc.entity;
public class User implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String pwd;
private String email;
public User(String username, String pwd, String email) {
super();
this.username = username;
this.pwd = pwd;
this.email = email;
}
public String getUsername() {return username;}
public String getPwd() {return pwd;}
public String getEmail() {return email;}
public void setUsername(String username) {this.username = username;}
public void setPwd(String pwd) {this.pwd = pwd;}
public void setEmail(String email) {this.email = email;}
@Override
public String toString() {
return "User [username=" + username + ", pwd=" + pwd + ", email=" + email + "]";
}
}
5.邮件发送服务及其实现(生产者端)
package com.sc.service;
import com.sc.entity.User;
public interface MailSendService {
void sendMail(User user);
}
实现类
package com.sc.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;
import com.sc.entity.User;
import com.sc.service.MailSendService;
@Service
public class MailSendServiceImpl implements MailSendService {
@Autowired
private MailSender mailSender;
/**
* 发送邮件
*/
@Override
public void sendMail(User use) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xiaoqingcaivip@163.com");
message.setTo(use.getEmail());
message.setSubject(use.getUsername());
//这里暂时已用户名作为发送的内容
message.setText(use.getUsername());
mailSender.send(message);
}
}
6.用户登录业务中邮件信息发布
这里可忽略登录业务,这里其实就是代指一个业务入口,简单处理。主要关注产生邮件信息
package com.sc.service;
public interface UserService {
//登陆
void login(String username, String pwd, String email);
}
实现类
package com.sc.service.impl;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sc.entity.User;
import com.sc.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void login(String username, String pwd, String email) {
//TODO 执行登陆的业务逻辑,这里主要是介绍消息队列发送邮件,这里就忽略
User user = new User(username, pwd, email);
//这里将用户对象作为队列消息发送
rabbitTemplate.convertAndSend("info", user);
}
}
7消费者端消费信息,即实现邮件的异步发送
package com.sc.consumer;
import org.apache.commons.logging.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import com.rabbitmq.client.Channel;
import com.sc.entity.User;
import com.sc.service.MailSendService;
/**
* 消费的消费者 实现 MessageListener或ChannelAwareMessageListener(需手动确认的实现此接口)
*/
public class Consumer implements ChannelAwareMessageListener{
private static final Logger log = LoggerFactory.getLogger(Consumer.class);
@Autowired
private MailSendService mailSendService;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
if(StringUtils.isEmpty(new String(message.getBody(),"UTF-8"))) {
return;
}
User user = (User) SerializationUtils.deserialize(message.getBody());
log.info("消费者消费{}",user);
//发送邮件
mailSendService.sendMail(user);
//手动确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.编写Controller层的接口测试
package com.sc.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.sc.service.UserService;
@Controller
@RequestMapping("/user")
public class UserLoginController {
@Autowired
private UserService userService;
@RequestMapping(value="/login", method=RequestMethod.POST)
@ResponseBody
public boolean login(String username,String pwd,String email) {
//这里简单模拟正常注册,没有数据库交互
//执行登陆逻辑
userService.login(username,pwd,email);
return true;
}
}