ActiveMQ 簡介
ActiveMQ 是 Apache 旗下產品,是一款優秀的消息中間件。主要解決應用耦合,異步消息,流量削鋒等問題,實現高性能,高可用。
你可以把 ActiveMQ 想象成一個大的容器,首先生產者把消息發送到這個大容器中,然後消費者監聽,如果有消息就從這個大容器中消費消息,起到一個緩衝的作用。
下載並運行
下載完成後進行解壓,進入 apache-activemq-5.15.3\bin\win64 目錄(32 爲系統進入 Win32),然後點擊運行 activemq.bat。
可以通過 http://localhost:8161/ 訪問 ActiveMQ 消息管理後臺頁面
點擊 Manage ActiveMQ broker 進行登錄,用戶名和密碼均爲:admin。
登錄後可以查看 Queues 消息隊列等信息
ActiveMQ 相關配置
<?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:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd ">
<!-- 掃瞄包-->
<context:component-scan base-package="chen.dreamland.www.activemq" />
<!-- ActiveMQ 連接工廠 -->
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
<!-- 如果連接網絡:tcp://ip:61616;未連接網絡:tcp://localhost:61616 以及用戶名,密碼-->
<amq:connectionFactory id="amqConnectionFactory" brokerURL="tcp://localhost:61616" userName="admin" password="admin" />
<!-- Spring Caching連接工廠 -->
<!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- 同上,同理 -->
<!-- <constructor-arg ref="amqConnectionFactory" /> -->
<!-- Session緩存數量 -->
<property name="sessionCacheSize" value="100" />
</bean>
<!-- Spring JmsTemplate 的消息生產者 start-->
<!-- 定義JmsTemplate的Queue類型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="connectionFactory" />
<!-- 非pub/sub模型(發佈/訂閱),即隊列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- Spring JmsTemplate 的消息生產者 end -->
<!-- 消息消費者 start-->
<!-- 定義Queue監聽器 -->
<jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
<!-- 默認註冊bean名稱,應該是類名首字母小寫 -->
<jms:listener destination="login_msg" ref="smsAuthenCode"/>
</jms:listener-container>
<!-- 消息消費者 end -->
</beans>
主要是 ActiveMQ 的連接配置以及生產者和消費者的配置。
web.xml 引入 applicationContext-activemq.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring-mybatis.xml,
classpath*:applicationContext-redis.xml,
classpath*:applicationContext-activemq.xml
</param-value>
</context-param>
通過 web.xml 加載剛纔配置的 applicationContext-activemq.xml。
ActiveMQ 消息監聽器的創建(消費者)
配置文件配置完成並引入後,創建消息監聽器,監聽消息的存在。
在 chen.dreamland.www 包下新建 activemq 包,在 activemq 包下新建消息監聽器類 SmsAuthenCode.java:
@Component
public class SmsAuthenCode implements MessageListener {
public void onMessage(Message message) {
MapMessage mapMessage = (MapMessage) message;
// 調用SMS服務發送短信 SmsSystem阿里大於發送短信給客戶手機實現類
try {
// 大於發送短信 Map 來自ActiveMQ 生成者
SendMessage.sendMessages( mapMessage.getString("code"),
mapMessage.getString("telephone") );
System.out.println( "-----發送消息成功..."+mapMessage.getString("code"));
} catch (Exception e) {//JMS
e.printStackTrace();
}
}
}
代碼解讀如下:
(1)不知道屬於哪一層時使用 @Component 註解,將對象交給 Spring 管理;
(2)消息監聽類實現消息監聽接口 MessageListener 重寫 onMessage 方法;
(3)將消息 message 強制轉換爲 MapMessage;
(4)從 MapMessage 中取出手機驗證碼和手機號,調用發送短信的方法將短信發送給用戶,下文將對這一部分做解釋。
阿里大於發送短信類 SendMessage 的創建
創建過程主要包括以下四步。
1.首先,下載阿里大於 SDK,選擇 Java下載。
2.然後,將 aliyun-java-sdk-core-3.3.1.jar 和 aliyun-java-sdk-dysmsapi-1.0.0.jar 打包到本地 Maven 倉庫。
這兩個文件分別在解壓目錄的 java/api_sdk/aliyun-java-sdk-core 和 java/api_sdk/aliyun-java-sdk-dysmsapi 下。
打開 CMD,輸入:
mvn install:install-file -Dfile=D:\安裝包\alidayu\java\api_sdk\aliyun-java-sdk-core\aliyun-java-sdk-core-3.3.1.jar -DgroupId=chen.dreamland.www -DartifactId=dayu-sdk-core -Dversion=3.3.1 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true
回車,再輸入下面的代碼,回車:
mvn install:install-file -Dfile=D:\安裝包\alidayu\java\api_sdk\aliyun-java-sdk-dysmsapi\aliyun-java-sdk-dysmsapi-1.0.0.jar -DgroupId=chen.dreamland.www -DartifactId=dayu-sdk-dysmsapi -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true
注意: -Dfile 是你的 jar 包所在路徑。
3.接着,在 pom.xml 中引入剛纔打包好的阿里大於依賴:
<dependency>
<groupId>wang.dreamland.www</groupId>
<artifactId>dayu-sdk-core</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>wang.dreamland.www</groupId>
<artifactId>dayu-sdk-dysmsapi</artifactId>
<version>1.0.0</version>
</dependency>
groupId 對應上面的 -DgroupId,artifactId 對應上面的 -DartifactId,version 對應上面的 -Dversion。
4.最後,在 activemq 包下新建發送短信類 SendMessage.java:
public class SendMessage {
private static String accessKeyId = “你的accessKeyId”;//你的accessKeyId,參考本文檔步驟2
private static String accessKeySecret = “你的accessKeySecret”;//你的accessKeySecret,參考本文檔步驟2
private static String setSignName = “你的短信簽名名稱”;
private static String dayutemplateCode = “你的短信模板CODE”;
public static void sendMessages(String code,String phone){
//設置超時時間-可自行調整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化ascClient需要的幾個參數
final String product = "Dysmsapi";//短信API產品名稱
final String domain = "dysmsapi.aliyuncs.com";//短信API產品域名
//替換成你的AK
//初始化ascClient,暫時不支持多region
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
accessKeySecret);
try {
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
} catch (ClientException e) {
e.printStackTrace();
}
IAcsClient acsClient = new DefaultAcsClient(profile);
//組裝請求對象
SendSmsRequest request = new SendSmsRequest();
//必填:待發送手機號。支持以逗號分隔的形式進行批量調用,批量上限爲20個手機號碼,批量調用相對於單條調用及時性稍有延遲,驗證碼類型的短信推薦使用單條調用的方式
request.setPhoneNumbers(phone);
//必填:短信簽名-可在短信控制檯中找到
request.setSignName(setSignName);
//必填:短信模板-可在短信控制檯中找到
request.setTemplateCode(dayutemplateCode);
//可選:模板中的變量替換JSON串,如模板內容爲"親愛的${name},您的驗證碼爲${code}"時,此處的值爲
//"{\"number\":\"" + code + "\"}"
request.setTemplateParam("{\"code\":\"" + code + "\"}");
//可選:outId爲提供給業務方擴展字段,最終在短信回執消息中將此值帶回給調用者
request.setOutId("yourOutId");
//請求失敗這裏會拋ClientException異常
SendSmsResponse sendSmsResponse = null;
try {
sendSmsResponse = acsClient.getAcsResponse(request);
} catch (ServerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClientException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
//請求成功
}
}
}
手機快捷登錄流程
手機快捷登錄的流程,主要包括下面幾步。
1.手機號 input 框離焦事件判斷。
- 判斷該手機號是否合法;
- 如果手機號合法,則判斷該手機號是否是已激活用戶。
2.獲取驗證碼點擊事件。
- 判斷步驟1是否返回 true;
- 返回 true 則發送獲取驗證碼並同時設置倒計時 60s;
- 倒計時60s結束才能點擊重新獲取。
3.驗證碼 input 框離焦事件。
- 判斷驗證碼是否爲6位純數字(防止惡意登錄),是則返回 true。
4.登錄點擊事件
- 判斷手機號和驗證碼均返回 true 時則提交表單。
這裏主要說下獲取驗證碼點擊事件,其他的和之前的原理一樣:
//獲取驗證碼
$(function () {
var go = document.getElementById(‘go’);
go.onclick = function (ev){
if(!flag2){
$("#phone_span").text("手機號碼非法或者未註冊!").css("color","red");
}else {
// 發送短信給用戶手機..
// 1 發送一個HTTP請求,通知服務器 發送短信給目標用戶
var telephone =$("input[name='telephone']").val();// 用戶輸入的手機號
// 用戶輸入手機號校驗通過
$("#go").attr("disabled", "disabled");
countDown(60);
$.ajax({
method: 'POST',
url: '${ctx}/sendSms',
data : {
telephone : telephone
},
success:function(data) {
var tt = data["msg"];
if(tt){
alert("發送短信成功!");
}else{
alert("發送短信出錯,請聯繫管理員");
}
}
});
}
var oEvent = ev || event;
//js阻止鏈接默認行爲,沒有停止冒泡
oEvent.preventDefault();
}
});
//倒計時
function countDown(s){
if(s <= 0){
$("#go").text("重新獲取");
$("#go").removeAttr("disabled");
return;
}
/* $("#go").val(s + "秒後重新獲取");*/
$("#go").text(s + "秒後重新獲取");
setTimeout("countDown("+(s-1)+")",1000);
}
代碼解讀如下:
(1) $(function () {}); 代表頁面加載完成函數;
(2)通過 document.getElementById 獲取文檔對象 DOM,賦值給 go;
(3)給 go 綁定一個 onclick 點擊事件函數;
(4)如果檢驗手機號返回的是 flag2=false,則給予錯誤提示;
(5)如果檢驗手機號成功後,通過屬性過濾選擇器獲取 input 框對象,並通過對象的 val() 方法獲取手機號;
(6)通過 $("#go").attr(“disabled”, “disabled”) 方法將獲取驗證碼按鈕設置成不可點擊;
(7)調用 countDown 倒計時方法,如果倒計時到0秒後,按鈕顯示重新獲取,並移除 disabled 屬性,按鈕可再次點擊,如果 0<s<=60 則按鈕上顯示 s 秒後重新獲取,不可點擊,每秒 s-1;
(8)發送 AJAX 請求,映射 URL 爲 /sendSms,請求參數是手機號;
(9)success 回調函數返回的結果如果是true,則提示發送成功。否則提示發送失敗,聯繫管理員;
(10)oEvent.preventDefault() 阻止鏈接默認行爲。主要是爲了返回錯誤信息時停留在手機快捷登錄標籤。
後臺創建映射 URL 爲 /sendSms 的方法,如下:
@Autowired
private UserService userService;
@Autowired// redis數據庫操作模板
private RedisTemplate<String, String> redisTemplate;// jdbcTemplate HibernateTemplate
@Autowired
@Qualifier("jmsQueueTemplate")
private JmsTemplate jmsTemplate;// mq消息模板.
/**
* 發送手機驗證碼
* @param model
* @param telephone
* @return
*/
@RequestMapping("/sendSms")
@ResponseBody
public Map<String,Object> index(Model model, @RequestParam(value = "telephone",required = false) final String telephone ) {
Map map = new HashMap<String,Object>( );
try { // 發送驗證碼操作
final String code = RandStringUtils.getCode();
redisTemplate.opsForValue().set(telephone, code, 60, TimeUnit.SECONDS);// 60秒 有效 redis保存驗證碼
log.debug("--------短信驗證碼爲:"+code);
// 調用ActiveMQ jmsTemplate,發送一條消息給MQ
jmsTemplate.send("login_msg", new MessageCreator() {
public Message createMessage(javax.jms.Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("telephone",telephone);
mapMessage.setString("code", code);
return mapMessage;
}
});
} catch (Exception e) {
map.put( "msg",false );
}
map.put( "msg",true );
return map;
}
代碼解讀如下:
(1)通過 @Autowired 註解注入 UserService、RedisTemplate,通過 @Autowired 和 @Qualifier 結合注入 JmsTemplate,參數是配置文件中配置的隊列 ID,主要是爲了區分有多個類型的情況,指定注入(這裏可以不指定),比如,如果再定義一個訂閱模式就需要加 @Qualifier 註解進行指定注入:
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="connectionFactory" />
<!-- pub/sub模型(發佈/訂閱) -->
<property name="pubSubDomain" value="true" />
</bean>
(2)通過 RandStringUtils 工具類隨機生成六位數字驗證碼。
public class RandStringUtils {
public static String getCode(){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
sb = sb.append( getRandomString());
}
return sb.toString();
}
public static int getRandomString(){
Random r = new Random();
int num = r.nextInt(9);
return num;
}
public static String getPhones(String[] arr){
String str = "";
if (arr!=null&&arr.length >0) {
for (int i = 0; i < arr.length; i++) {
str = str + arr[i] + ",";
}
}
return str.substring(0,str.length()-1);
}
public static void main(String[] args) {
int randomString = getRandomString();
String code = getCode();
System.out.println(randomString);
System.out.println(code);
}
}
(3)將6位隨機驗證碼保存到 Redis 中,時效爲60秒,key=手機號,value=驗證碼。
(4)調用 ActiveMQ 消息模板對象生產一條消息,發送給 MQ。
注意:第一個參數 login_msg 是配置文件中配置的目的地:
<jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
<!-- 默認註冊bean名稱,應該是類名首字母小寫 -->
<jms:listener destination="login_msg" ref="smsAuthenCode"/>
</jms:listener-container>
監聽器 smsAuthenCode 監聽到消息之後就會調用發送短信功能。
第二個參數是接口 MessageCreator,這裏傳入的是匿名內部類,通過 new 接口的形式並實現接口方法。
(5)如果有異常則把 false 放入 map,否則把 true 放入 map,最後返回 map。
登錄流程
登錄流程主要包括以下幾個步驟:
-
用戶點擊獲取驗證碼,後臺生成6位隨機驗證碼,一份存入 Redis 數據庫中,key=“手機號”,value=“驗證碼”;
-
ActiveMQ 生產者發送消息給 ActiveMQ 中間件,等待消費者消費;
-
ActiveMQ 消費者監聽到消息之後發送短信給用戶;
-
輸入驗證碼點擊登錄;
-
後臺獲取用戶的手機號以及驗證碼,根據用戶的手機號去 Redis 數據庫中取對應的驗證碼,然後與用戶輸入的驗證碼進行比對,一致則登錄成功,跳轉到 personal.jsp,否則跳轉到 login.jsp。
用戶輸入手機驗證碼後點擊登錄,對應的點擊事件如下:
//登錄
$("#phone_btn").click(function () {
if(checkPhone()&& checkPhoneCode()){
// 校驗用戶名和密碼
$("#phone_span").text("").css("color","red");
$("#phone_form").submit();
}else {
alert("請輸入手機號和6位驗證碼!");
}
});
以上代碼用於檢驗手機號和手機驗證碼,正確後提交表單,否則提示錯誤!
修改 doLogin 方法如下:
@RequestMapping("/doLogin")
public String doLogin(Model model, @RequestParam(value = "username",required = false) String email,
@RequestParam(value = "password",required = false) String password,
@RequestParam(value = "code",required = false) String code,
@RequestParam(value = "telephone",required = false) String telephone,
@RequestParam(value = "phone_code",required = false) String phone_code,
@RequestParam(value = "state",required = false) String state,
@RequestParam(value = "pageNum",required = false) Integer pageNum ,
@RequestParam(value = "pageSize",required = false) Integer pageSize) {
//判斷是否是手機登錄
if (StringUtils.isNotBlank(telephone)) {
//手機登錄
String yzm = redisTemplate.opsForValue().get( telephone );//從redis獲取驗證碼
if(phone_code.equals(yzm)){
//驗證碼正確
User user = userService.findByPhone(telephone);
getSession().setAttribute("user", user);
model.addAttribute("user", user);
log.info("手機快捷登錄成功");
return "/personal/personal";
}else {
//驗證碼錯誤或過期
model.addAttribute("error","phone_fail");
return "../login";
}
} else {
//賬號登錄
if (StringUtils.isBlank(code)) {
model.addAttribute("error", "fail");
return "../login";
}
int b = checkValidateCode(code);
if (b == -1) {
model.addAttribute("error", "fail");
return "../login";
} else if (b == 0) {
model.addAttribute("error", "fail");
return "../login";
}
password = MD5Util.encodeToHex(Constants.SALT + password);
User user = userService.login(email, password);
if (user != null) {
if ("0".equals(user.getState())) {
//未激活
model.addAttribute("email", email);
model.addAttribute("error", "active");
return "../login";
}
log.info("用戶登錄登錄成功");
getSession().setAttribute("user", user);
model.addAttribute("user", user);
return "/personal/personal";
} else {
log.info("用戶登錄登錄失敗");
model.addAttribute("email", email);
model.addAttribute("error", "fail");
return "../login";
}
}
}
代碼解讀如下:
(1)主要是根據手機號是否爲空判斷是手機登錄還是賬號登錄,如果手機號不爲空則爲手機快捷登錄;
(2)根據手機號從 Redis 中獲取手機驗證碼,如果 Redis 中的驗證碼和用戶輸入的驗證碼一致,則登錄成功,根據手機號查詢用戶,將用戶 user 存入 Session 和 model 中,返回個人中心頁面;
(3)如果不一致,說明驗證碼錯誤或者過期,將 phone_fail 添加到 model 中,並返回到登錄頁面。
最後重新啓動項目測試,手機快捷登錄成功!