1. 程式人生 > >6.Spring RabbitMQ整合實現案例之 非同步郵件傳送

6.Spring RabbitMQ整合實現案例之 非同步郵件傳送

摘要:給使用者傳送郵件的場景,其實也是比較常見的,比如使用者註冊需要郵箱驗證,使用者異地登入傳送郵件通知等等,在這裡我以 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
#傳送郵件的郵箱地址賬號資訊
[email protected]
[email protected] mail.password=xxxxxxxxx # qq mail server #mail.protocol=smtp #mail.port=465 #mail.host=smtp.exmail.qq.com #[email protected] #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("[email protected]");
                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;
	}
}