本來就打算針對(duì)一些固定的特別點(diǎn)的業(yè)務(wù)(QQ與網(wǎng)易郵件欠啤、攔截設(shè)計(jì)荚藻、短信、定時(shí)器等等)來進(jìn)行記錄以及解析原理洁段,這些會(huì)比較零散記錄在JavaWeb的分類里面应狱,感興趣的童鞋可以去看下。
有人問為什么要郵件短信一起寫祠丝,呃疾呻,短信的東西,非巨型公司都是用第三方的纽疟,第三方的文檔支持都十分完整罐韩,短信例子的話,我一會(huì)也是引用第三方去做一個(gè)而已污朽。主要在于原理散吵,兩個(gè)都是十分相近的業(yè)務(wù),而且都是在TCP/IP的應(yīng)用層封裝蟆肆,并且設(shè)計(jì)的重傳方案基本相似矾睦。
重傳方案,為了避免本篇過長炎功,我會(huì)在后面一篇文章寫出枚冗,結(jié)合線程或者定時(shí)器的重傳方案。
文章結(jié)構(gòu):(1)郵件實(shí)現(xiàn)與原理蛇损;(2)短信實(shí)現(xiàn)與原理赁温。
DEMO在本文最下方坛怪。
文章目錄:
(1)郵件實(shí)現(xiàn)與原理
- 郵件概述(郵件在互聯(lián)網(wǎng)通信架構(gòu)中的位置)
- 一個(gè)郵件系統(tǒng)的組成分析
- 郵件系統(tǒng)--郵件通信過程如圖
- 郵件協(xié)議詳解
- Java一鍵實(shí)現(xiàn)郵件發(fā)送
- Java郵件封裝和異步實(shí)現(xiàn)
(2)短信實(shí)現(xiàn)與原理
- 短信的概述:(短信在互聯(lián)網(wǎng)通信架構(gòu)中的位置)
- 短信原理
- 短信系統(tǒng)--短信通信過程
- 短信的協(xié)議
- 一鍵實(shí)現(xiàn)短信發(fā)送
- Java短信封裝和異步實(shí)現(xiàn)
一、郵件實(shí)現(xiàn)與原理:
(1)郵件概述(郵件在互聯(lián)網(wǎng)通信架構(gòu)中的位置):
上圖就是互聯(lián)網(wǎng)的TCP/IP架構(gòu)股囊。具體詳情請(qǐng)見我的【計(jì)算機(jī)網(wǎng)絡(luò)系列】袜匿。
應(yīng)用層定義了應(yīng)用程序使用互聯(lián)網(wǎng)的規(guī)程。電子郵件的協(xié)議就建立在這一層稚疹。
/*
* 為什么建立在TCP/IP的應(yīng)用層居灯??原因是内狗?怪嫌?
*
* 首先確立在現(xiàn)今社會(huì),企業(yè)的正式工作都是通過郵件進(jìn)行柳沙。郵件對(duì)于可靠性要求非常高岩灭,所以就要求一個(gè)可靠的傳輸協(xié)議。就把郵件協(xié)議建立在TCP/IP的應(yīng)用層基礎(chǔ)上了偎行。
IP 協(xié)議的主要功能包括無連結(jié)數(shù)據(jù)報(bào)傳送﹑數(shù)據(jù)報(bào)尋徑以及差錯(cuò)處理三部分川背。IP協(xié)議的特點(diǎn)是點(diǎn)到點(diǎn)的贰拿,IP對(duì)等實(shí)體間的通信不經(jīng)過中間機(jī)器蛤袒,對(duì)等實(shí)體所在的機(jī)器位于同一物理網(wǎng)絡(luò),對(duì)等機(jī)器之間有直接的物理連接膨更。IP層的主要功能是屏蔽下面物理層的差別妙真,向上一層提供一致的數(shù)據(jù)格式。所有要傳輸?shù)臄?shù)據(jù)荚守,被按照一定的格式分組封裝層IP數(shù)據(jù)報(bào)珍德,數(shù)據(jù)報(bào)單元通過尋徑等機(jī)制進(jìn)行傳輸,在接收方數(shù)據(jù)報(bào)進(jìn)行重組矗漾,得到最初要傳送的數(shù)據(jù)锈候。由于IP協(xié)議是不可靠的數(shù)據(jù)傳輸協(xié)議,由于網(wǎng)絡(luò)的擁塞而發(fā)生的數(shù)據(jù)丟失等情況是不可避免的敞贡,因此Internet 還必須有一定的控制重傳機(jī)制泵琳,這就是差錯(cuò)與控制報(bào)文協(xié)議(ICMP)。
但I(xiàn)P協(xié)議還不能解決數(shù)據(jù)分組在傳輸過程中可能出現(xiàn)的問題誊役。(透明傳輸?shù)葐栴})获列。
因此,還需要TCP協(xié)議來提供可靠的并且無差錯(cuò)的通信服務(wù)蛔垢。TCP協(xié)議被稱作一種端對(duì)端協(xié)議击孩。這是因?yàn)樗鼮閮膳_(tái)計(jì)算機(jī)之間的連接起了重要作用:當(dāng)一臺(tái)計(jì)算機(jī)需要與另一臺(tái)遠(yuǎn)程計(jì)算機(jī)連接時(shí),TCP協(xié)議會(huì)讓它們建立一個(gè)連接鹏漆、發(fā)送和接收數(shù)據(jù)以及終止連接巩梢。傳輸控制協(xié)議TCP協(xié)議利用重發(fā)技術(shù)和擁塞控制機(jī)制创泄,向應(yīng)用程序提供可靠的通信連接,使它能夠自動(dòng)適應(yīng)網(wǎng)上的各種變化括蝠。
IP協(xié)議只保證計(jì)算機(jī)能發(fā)送和接收分組數(shù)據(jù)验烧,而TCP協(xié)議則可提供一個(gè)可靠的、可流控的又跛、全雙工的信息流傳輸服務(wù)碍拆。雖然IP和TCP這兩個(gè)協(xié)議的功能不盡相同,也可以分開單獨(dú)使用慨蓝,但它們是在同一時(shí)期作為一個(gè)協(xié)議來設(shè)計(jì)的感混,并且在功能上也是互補(bǔ)的。只有兩者的結(jié)合礼烈,才能保證 Internet 在復(fù)雜的環(huán)境下正常運(yùn)行弧满。凡是要連接到 Internet 的計(jì)算機(jī),都必須同時(shí)安裝和使用這兩個(gè)協(xié)議此熬,因此在實(shí)際中常把這兩個(gè)協(xié)議統(tǒng)稱作TCP/IP協(xié)議庭呜。 TCP/IP 協(xié)議除了TCP協(xié)議和IP協(xié)議,還包含物理接口和IP層之間的ARP/RARP協(xié)議犀忱,應(yīng)用層的FTP協(xié)議﹑SMTP協(xié)議和BOOTP協(xié)議等募谎,所用的這些協(xié)議構(gòu)成Intenet 的TCP/IP 協(xié)議族。
*/
(2)一個(gè)郵件系統(tǒng)的組成分析:
一個(gè)郵件系統(tǒng)組成必須包括郵件服務(wù)器阴汇,然后是用戶代理和郵件傳送協(xié)議数冬。
(一)郵件服務(wù)器。(存儲(chǔ)用戶郵箱的地方)
是一個(gè)供在網(wǎng)上存儲(chǔ)郵件的空間搀庶。
一般每個(gè)郵件服務(wù)器的提供商都有自己的郵件服務(wù)器拐纱,只要你申請(qǐng)了他的郵箱賬號(hào),你就會(huì)在他的郵件服務(wù)器上擁有自己郵箱哥倔。像Google秸架,騰訊都是郵件服務(wù)的提供商,他們都有自己的郵件服務(wù)器咆蒿,如果你申請(qǐng)了Gamil郵箱东抹,那么在Google的郵件服務(wù)器上面,你就有自己的一塊存儲(chǔ)空間了蜡秽。同樣府阀,如果你申請(qǐng)了qq郵箱,那么在qq郵件服務(wù)器上面也有你自己的空間了芽突,也就是你的郵箱试浙。當(dāng)你要收取信件的時(shí)候,你就需要連接到不同的服務(wù)器上面寞蚌。不同的郵件服務(wù)提供商田巴,他們的郵件服務(wù)器的地址是不一樣的钠糊。后面會(huì)介紹一些常用的郵件服務(wù)器的地址。
(二) 用戶代理:(用戶讀取郵件的地方)
就是你用來從郵件服務(wù)器上讀取或者發(fā)送郵件到郵件服務(wù)器上的一個(gè)軟件壹哺。
比如常用的OutLook,qq郵箱(公司呈現(xiàn)出來的抄伍,郵箱服務(wù)器是看不到的另一區(qū)域)等等。我們知道管宵,我們的郵件都是存儲(chǔ)在郵件服務(wù)器上面的截珍,我們發(fā)送郵件的時(shí)候,去往郵件服務(wù)器上面發(fā)箩朴,我們收取郵件的時(shí)候岗喉,也需要從服務(wù)器上面讀。為了方便的完成這些工作炸庞,我們就需要用戶代理钱床。
(三) 郵件傳送協(xié)議:(郵箱信息發(fā)送的約定)
是指郵件在傳送過程中必須遵守的約定,它規(guī)定了不同的服務(wù)器(或客戶端)之間應(yīng)如何交換信息埠居。
比較常見的有郵件服務(wù)器之間的通信協(xié)議SMTP以及用戶代理與郵件服務(wù)器之間的通信協(xié)議POP3查牌。(注意:郵件服務(wù)器之間的協(xié)議是使用SMTP,用戶發(fā)送郵件到郵件服務(wù)器使用的還是SMTP協(xié)議滥壕,用戶從郵件服務(wù)器讀取郵件用的才是POP3協(xié)議)纸颜。
(3)郵件系統(tǒng)--郵件通信過程如圖:
圖取自教材的計(jì)算機(jī)網(wǎng)絡(luò)--謝希任
郵件通信過程(文字描述):
1)發(fā)信人調(diào)用自己的用戶代理撰寫、編輯郵件捏浊,并寫清楚收件人的郵箱地址懂衩;
2)發(fā)信人的用戶代理根據(jù)發(fā)信人編輯的信息,生成一封符合郵件格式的郵件金踪;
3)發(fā)信人的用戶代理把郵件發(fā)送到發(fā)信人的的郵件服務(wù)器上,郵件服務(wù)器上面有一個(gè)緩沖隊(duì)列牵敷,發(fā)送到郵件服務(wù)器上面的郵件都會(huì)加入到緩沖隊(duì)列中胡岔,等待郵件服務(wù)器上的SMTP客戶端進(jìn)行發(fā)送;
4) 發(fā)信人的郵件服務(wù)器的 SMTP 客戶端與接收方郵件服務(wù)器的 SMTP 服務(wù)器建立 TCP 連接枷餐,發(fā)信人的郵件服務(wù)器使用SMTP協(xié)議把這封郵件發(fā)送到收件人的郵件服務(wù)器上(它會(huì)自動(dòng)根據(jù)收件人的郵箱來分析出收件人的郵箱服務(wù)器)靶瘸;
5)收件人的郵件服務(wù)器收到郵件后,把這封郵件放到收件人在這個(gè)服務(wù)器上的郵箱中毛肋,等待收件人進(jìn)行讀仍惯洹;
6)收件人使用他的用戶代理來收取郵件润匙。首先用戶代理使用POP3協(xié)議來連接收件人所在的郵件服務(wù)器诗眨,身份驗(yàn)證成功后,用戶代理就可以把郵件服務(wù)器上面的收件人郵箱里面的郵件讀取出來孕讳,并展示給收件人匠楚。
(4)郵件協(xié)議詳解:
(一)SMTP:
SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協(xié)議巍膘,是一種提供可靠且有效電子郵件傳輸?shù)膮f(xié)議。SMTP是建立在FTP文件傳輸服務(wù)上的一種郵件服務(wù)芋簿,主要用于傳輸系統(tǒng)之間的郵件信息并提供與來信有關(guān)的通知峡懈。SMTP主要負(fù)責(zé)底層的郵件系統(tǒng)如何將郵件從一臺(tái)機(jī)器傳至另外一臺(tái)機(jī)器。
SMTP提供了一種郵件傳輸?shù)臋C(jī)制与斤,當(dāng)收件方和發(fā)件方都在一個(gè)網(wǎng)絡(luò)上時(shí)肪康,可以把郵件直傳給對(duì)方;當(dāng)雙方不在同一個(gè)網(wǎng)絡(luò)上時(shí)撩穿,需要通過一個(gè)或幾個(gè)中間服務(wù)器轉(zhuǎn)發(fā)梅鹦。SMTP首先由發(fā)件方提出申請(qǐng),要求與接收方SMTP建立雙向的通信渠道冗锁,收件方可以是最終收件人也可以是中間轉(zhuǎn)發(fā)的服務(wù)器齐唆。收件方服務(wù)器確認(rèn)可以建立連接后,雙發(fā)就可以開始通信冻河。
(二)POP3:
是把郵件從電子郵箱服務(wù)器中傳輸?shù)奖镜赜?jì)算機(jī)客戶端的協(xié)議箍邮。
POP3(Post Office Protocol 3)即郵局協(xié)議的第3個(gè)版本,它是規(guī)定個(gè)人計(jì)算機(jī)如何連接到互聯(lián)網(wǎng)上的郵件服務(wù)器進(jìn)行收發(fā)郵件的協(xié)議叨叙。它是因特網(wǎng)電子郵件的第一個(gè)離線協(xié)議標(biāo)準(zhǔn)锭弊,POP3協(xié)議允許用戶從服務(wù)器上把郵件存儲(chǔ)到本地主機(jī)(即自己的計(jì)算機(jī))上,同時(shí)根據(jù)客戶端的操作刪除或保存在郵件服務(wù)器上的郵件擂错,而POP3服務(wù)器則是遵循POP3協(xié)議的接收郵件服務(wù)器味滞,用來接收電子郵件的。
(三)IMAP:
Internet Mail Access Protocol(交互式郵件存取協(xié)議)钮呀。它的主要作用是郵件客戶端(例如MS Outlook Express)可以通過這種協(xié)議從郵件服務(wù)器上獲取郵件的信息剑鞍,下載郵件等。IMAP協(xié)議運(yùn)行在TCP/IP協(xié)議之上爽醋,使用的端口是143蚁署。它與POP3協(xié)議的主要區(qū)別是用戶可以不用把所有的郵件全部下載,可以通過客戶端直接對(duì)服務(wù)器上的郵件進(jìn)行操作蚂四。
IMAP協(xié)議比較自由的功能是用戶可以維護(hù)自己在服務(wù)器上的郵件目錄光戈;可以直接抓取郵件的特定部分(例如只有文本)。
IMAP的一個(gè)與POP3的區(qū)別是:IMAP它只下載郵件的主題遂赠,并不是把所有的郵件內(nèi)容都下載下來久妆,而是你郵箱當(dāng)中還保留著郵件的副本,沒有把你原郵箱中的郵件刪除跷睦,你用郵件客戶軟件閱讀郵件時(shí)才下載郵件的內(nèi)容筷弦。
(5)Java一鍵實(shí)現(xiàn)郵件發(fā)送:(QQ郵箱為例,一會(huì)封裝用網(wǎng)易郵箱(很多坑))
(一)先導(dǎo)入庫:
<!-- poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<!--zdk add 2017-5-16-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<!-- Java郵件操作類-->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.5</version>
</dependency>
(二)給發(fā)信的郵箱申請(qǐng)協(xié)議服務(wù)開通
(三)一鍵復(fù)制即可實(shí)現(xiàn):
我們需要修改的東西:郵箱送讲、郵箱服務(wù)器授權(quán)碼
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.Properties;
/**
* Created by 符柱成 on 2017/6/6.
*/
public class JavaMailSendTest {
// 發(fā)件人的 郵箱 和 密碼(替換為自己的郵箱和密碼)
// PS: 某些郵箱服務(wù)器為了增加郵箱本身密碼的安全性奸笤,給 SMTP 客戶端設(shè)置了獨(dú)立密碼(有的郵箱稱為“授權(quán)碼”),
// 對(duì)于開啟了獨(dú)立密碼的郵箱, 這里的郵箱密碼必需使用這個(gè)獨(dú)立密碼(授權(quán)碼)惋啃。
public static String myEmailAccount = "751197996@qq.com";//我們申請(qǐng)服務(wù)的郵箱
public static String myEmailPassword = "";//這個(gè)就要填上我們剛剛拿到的授權(quán)碼
// 發(fā)件人郵箱的 SMTP 服務(wù)器地址, 必須準(zhǔn)確, 不同郵件服務(wù)器地址不同, 一般(只是一般, 絕非絕對(duì))格式為: smtp.xxx.com
// 網(wǎng)易163郵箱的 SMTP 服務(wù)器地址為: smtp.163.com;qq郵箱的SMTP服務(wù)器地址:smtp.qq.com
public static String myEmailSMTPHost = "smtp.qq.com";
// 收件人郵箱(替換為自己知道的有效郵箱)
public static String receiveMailAccount = "1433317518@qq.com";
public static void main(String[] args) throws Exception {
// 1. 創(chuàng)建參數(shù)配置, 用于連接郵件服務(wù)器的參數(shù)配置
Properties props = new Properties(); // 參數(shù)配置
props.setProperty("mail.transport.protocol", "smtp"); // 使用的協(xié)議(JavaMail規(guī)范要求)
props.setProperty("mail.smtp.host", myEmailSMTPHost); // 發(fā)件人的郵箱的 SMTP 服務(wù)器地址
props.setProperty("mail.smtp.auth", "true"); // 需要請(qǐng)求認(rèn)證
// PS: 某些郵箱服務(wù)器要求 SMTP 連接需要使用 SSL 安全認(rèn)證 (為了提高安全性, 郵箱支持SSL連接, 也可以自己開啟),
// 如果無法連接郵件服務(wù)器, 仔細(xì)查看控制臺(tái)打印的 log, 如果有有類似 “連接失敗, 要求 SSL 安全連接” 等錯(cuò)誤,
// 打開下面 /* ... */ 之間的注釋代碼, 開啟 SSL 安全連接监右。
/*
// SMTP 服務(wù)器的端口 (非 SSL 連接的端口一般默認(rèn)為 25, 可以不添加, 如果開啟了 SSL 連接,
// 需要改為對(duì)應(yīng)郵箱的 SMTP 服務(wù)器的端口, 具體可查看對(duì)應(yīng)郵箱服務(wù)的幫助,
// QQ郵箱的SMTP(SLL)端口為465或587, 其他郵箱自行去查看)
final String smtpPort = "465";
props.setProperty("mail.smtp.port", smtpPort);
props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.socketFactory.port", smtpPort);
*/
final String smtpPort = "465";
props.setProperty("mail.smtp.port", smtpPort);
props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.socketFactory.port", smtpPort);
// 2. 根據(jù)配置創(chuàng)建會(huì)話對(duì)象, 用于和郵件服務(wù)器交互
Session session = Session.getDefaultInstance(props);
session.setDebug(true); // 設(shè)置為debug模式, 可以查看詳細(xì)的發(fā)送 log
// 3. 創(chuàng)建一封郵件
MimeMessage message = createMimeMessage(session, myEmailAccount, receiveMailAccount);
// 4. 根據(jù) Session 獲取郵件傳輸對(duì)象
Transport transport = session.getTransport();
// 5. 使用 郵箱賬號(hào) 和 密碼 連接郵件服務(wù)器, 這里認(rèn)證的郵箱必須與 message 中的發(fā)件人郵箱一致, 否則報(bào)錯(cuò)
//
// PS_01: 成敗的判斷關(guān)鍵在此一句, 如果連接服務(wù)器失敗, 都會(huì)在控制臺(tái)輸出相應(yīng)失敗原因的 log,
// 仔細(xì)查看失敗原因, 有些郵箱服務(wù)器會(huì)返回錯(cuò)誤碼或查看錯(cuò)誤類型的鏈接, 根據(jù)給出的錯(cuò)誤
// 類型到對(duì)應(yīng)郵件服務(wù)器的幫助網(wǎng)站上查看具體失敗原因边灭。
//
// PS_02: 連接失敗的原因通常為以下幾點(diǎn), 仔細(xì)檢查代碼:
// (1) 郵箱沒有開啟 SMTP 服務(wù);
// (2) 郵箱密碼錯(cuò)誤, 例如某些郵箱開啟了獨(dú)立密碼;
// (3) 郵箱服務(wù)器要求必須要使用 SSL 安全連接;
// (4) 請(qǐng)求過于頻繁或其他原因, 被郵件服務(wù)器拒絕服務(wù);
// (5) 如果以上幾點(diǎn)都確定無誤, 到郵件服務(wù)器網(wǎng)站查找?guī)椭? //
// PS_03: 仔細(xì)看log, 認(rèn)真看log, 看懂log, 錯(cuò)誤原因都在log已說明。
transport.connect(myEmailAccount, myEmailPassword);
// 6. 發(fā)送郵件, 發(fā)到所有的收件地址, message.getAllRecipients() 獲取到的是在創(chuàng)建郵件對(duì)象時(shí)添加的所有收件人, 抄送人, 密送人
transport.sendMessage(message, message.getAllRecipients());
// 7. 關(guān)閉連接
transport.close();
}
/**
* 創(chuàng)建一封只包含文本的簡單郵件
*
* @param session 和服務(wù)器交互的會(huì)話
* @param sendMail 發(fā)件人郵箱
* @param receiveMail 收件人郵箱
* @return
* @throws Exception
*/
public static MimeMessage createMimeMessage(Session session, String sendMail, String receiveMail) throws Exception {
// 1. 創(chuàng)建一封郵件
MimeMessage message = new MimeMessage(session);
// 2. From: 發(fā)件人
message.setFrom(new InternetAddress(sendMail, "符柱成主頁", "UTF-8"));
// 3. To: 收件人(可以增加多個(gè)收件人健盒、抄送绒瘦、密送)
message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveMail, "XX用戶", "UTF-8"));
// 4. Subject: 郵件主題
message.setSubject("重要通知啊,老哥", "UTF-8");
// 5. Content: 郵件正文(可以使用html標(biāo)簽)
message.setContent("輔助:打聲招呼而已", "text/html;charset=UTF-8");
// 6. 設(shè)置發(fā)件時(shí)間
message.setSentDate(new Date());
// 7. 保存設(shè)置
message.saveChanges();
return message;
}
}
(6)Java郵件封裝和異步實(shí)現(xiàn):(網(wǎng)易郵箱為例)
為什么要異步扣癣?惰帽?郵件和短信都要經(jīng)過復(fù)雜的網(wǎng)絡(luò)通信,這就意味著很可能極其耗時(shí)父虑,使用同步方式该酗,很容易導(dǎo)致主線程卡死,導(dǎo)致極差的體驗(yàn)士嚎。
所以呜魄,我們應(yīng)該以異步方式去執(zhí)行此業(yè)務(wù),然后直接告訴用戶已經(jīng)發(fā)送(發(fā)送情況失敗畢竟很少見)莱衩。
(一)定接口與實(shí)現(xiàn)類:
public interface EmailService {
/*
* hisEmail收件人email
* subject主題(標(biāo)題)
* content內(nèi)容(文本)
*/
void sendEmail(String hisEmail, String subject, String content);
}
記得配置好config.properties爵嗅。分別是發(fā)件人郵箱(開通SMTP服務(wù)的),授權(quán)碼笨蚁,郵件名字
SENDER_MAILBOX=睹晒??括细?
MAIL_PASSWPRD=伪很??勒极?
MAIL_NAME=BSS\u7CFB\u7EDF\u90AE\u7BB1
可以看到跟使用qq郵箱有很大區(qū)別是掰,但是必須如此配置使用,必須使用Authenticator 去驗(yàn)證
public class EmailServiceImpl implements EmailService {
private static final Log log = LogFactory.getLog(EmailServiceImpl.class);
// 發(fā)件人的 郵箱 和 密碼(替換為自己的郵箱和密碼)
// 對(duì)于開啟了獨(dú)立密碼的郵箱, 這里的郵箱密碼必需使用這個(gè)獨(dú)立密碼(授權(quán)碼)辱匿。
public static String myEmailAccount = "";
public static String myEmailPassword = "";
public static String myEmailName = "";
// qq郵箱的SMTP服務(wù)器地址:smtp.qq.com
public static String myEmailSMTPHost = "smtp.126.com";
static {
//博主自己封裝了一個(gè)獲取本地文件的配置參數(shù)方式,大家可以參考使用炫彩。針對(duì)config.properties的匾七,想改別的文件請(qǐng)大家自行修改。
try {
myEmailAccount = Config.getConfigValue("SENDER_MAILBOX");
myEmailPassword = Config.getConfigValue("MAIL_PASSWPRD");
myEmailName = Config.getConfigValue("MAIL_NAME");
System.out.println(myEmailAccount);
System.out.println(myEmailPassword);
System.out.println(myEmailName);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void sendEmail(String hisEmail, String subject, String content) {
System.out.println("myEmailAccount :" + myEmailAccount);
System.out.println("myEmailPassword :" + myEmailPassword);
try {
// 1. 創(chuàng)建參數(shù)配置, 用于連接郵件服務(wù)器的參數(shù)配置
final Properties props = new Properties(); // 參數(shù)配置
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", "smtp.126.com");
// 發(fā)件人的賬號(hào)
props.put("mail.user", myEmailAccount);
// 發(fā)件人的密碼
props.put("mail.password", myEmailPassword);
//網(wǎng)易郵箱必須這樣江兢,使用Authenticator昨忆,進(jìn)行一系列的驗(yàn)證。不然就是給你504杉允,驗(yàn)證失敗或者辣雞郵件發(fā)不出去
Authenticator authenticator = new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
// 使用環(huán)境屬性和授權(quán)信息邑贴,創(chuàng)建郵件會(huì)話
Session mailSession = Session.getInstance(props, authenticator);
// 創(chuàng)建郵件消息
MimeMessage message = new MimeMessage(mailSession);
// 設(shè)置發(fā)件人
String username = props.getProperty("mail.user");
InternetAddress form = new InternetAddress(username);
message.setFrom(form);
// 設(shè)置收件人
InternetAddress to = new InternetAddress(hisEmail);
message.setRecipient(RecipientType.TO, to);
// 設(shè)置郵件標(biāo)題
message.setSubject(subject);
// 設(shè)置郵件的內(nèi)容體
message.setContent(content, "text/html;charset=UTF-8");
// 發(fā)送郵件
Transport.send(message);
} catch (AddressException e) {
e.printStackTrace();
} catch (MessagingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
(二)采用工廠模式:
public class EmailServiceFactory {
public static EmailService getEmailService(){
return new EmailServiceImpl();
}
}
(三)使用線程異步一鍵調(diào)用:
public class EmailTest {
//創(chuàng)建一個(gè)線程池席里,可裝載大概3個(gè)線程任務(wù)的
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
// 收件人郵箱(替換為自己知道的有效郵箱)1433317518
public static String receiveMailAccount = "751197996@qq.com";
public static void main(String[] args) {
//這種寫法詳情請(qǐng)去參考lambda表達(dá)式
executorService.submit(() -> EmailServiceFactory.getEmailService().sendEmail(receiveMailAccount, "BSS系統(tǒng)工單", "<body><p>工單工號(hào)GGGGDADA</p></body>"));
//調(diào)用 shutdown() 方法,ExecutorService 并不會(huì)馬上關(guān)閉拢驾,而是不再接收新的任務(wù)奖磁,一但所有的線程結(jié)束執(zhí)行當(dāng)前任務(wù),ExecutorServie 才會(huì)真的關(guān)閉繁疤。所有在調(diào)用 shutdown() 方法之前提交到 ExecutorService 的任務(wù)都會(huì)執(zhí)行咖为。
//調(diào)用時(shí)機(jī)由你自己去決定
//你希望立即關(guān)閉 ExecutorService,你可以調(diào)用 shutdownNow() 方法稠腊。這個(gè)方法會(huì)嘗試馬上關(guān)閉所有正在執(zhí)行的任務(wù)躁染,并且跳過所有已經(jīng)提交但是還沒有運(yùn)行的任務(wù)。但是對(duì)于正在執(zhí)行的任務(wù)架忌,是否能夠成功關(guān)閉它是無法保證的吞彤,有可能他們真的被關(guān)閉掉了,也有可能它會(huì)一直執(zhí)行到任務(wù)結(jié)束叹放。這是一個(gè)最好的嘗試饰恕。
executorService.shutdown();
}
}
二、短信實(shí)現(xiàn)與原理:
(1)短信的概述:(短信在互聯(lián)網(wǎng)通信架構(gòu)中的位置)
首先短信與郵件同屬于TCP/IP這一層许昨。SMS表示短信服務(wù)懂盐。簡單來講,它是在手機(jī)之間發(fā)送文字信息或從個(gè)人計(jì)算機(jī)或手持設(shè)備向手機(jī)發(fā)送信息的一種方式糕档。短信的“短”指的是文本信息的最大發(fā)送量:160個(gè)字符(字母莉恼、數(shù)字或拉丁字母中的符號(hào))。至于其他字母速那,例如中文俐银,一條短信的最大發(fā)送量為70個(gè)字符。
(2)短信原理:
在理解其原理前端仰,需要先懂得手機(jī)的通信原理捶惜。
手機(jī)通信原理:
盡管您沒有使用手機(jī)打電話,您的手機(jī)也在不停地發(fā)送和接收著信息荔烧。它通過被稱為控制通道的通路與手機(jī)發(fā)射塔進(jìn)行通信吱七。這種通訊的目的是讓手機(jī)系統(tǒng)了解自己所在的信號(hào)區(qū)域,以便在您移動(dòng)時(shí)鹤竭,手機(jī)可以切換到其他信號(hào)區(qū)域踊餐。每隔一段時(shí)間,手機(jī)和發(fā)射塔將交換數(shù)據(jù)包以確定一切工作正常臀稚。手機(jī)也使用控制通道來建立呼叫吝岭。當(dāng)有人打電話給您時(shí),手機(jī)發(fā)射塔將通過控制通道向手機(jī)發(fā)送信號(hào),然后手機(jī)就會(huì)振鈴窜管。同時(shí)散劫,手機(jī)發(fā)射塔為手機(jī)提供兩個(gè)語音信道頻率用來進(jìn)行通話。
手機(jī)通信為什么這樣做幕帆?获搏?
在美國的標(biāo)準(zhǔn)模擬手機(jī)系統(tǒng)中,手機(jī)運(yùn)營商獲準(zhǔn)在整個(gè)城市使用約800個(gè)頻率蜓肆。運(yùn)營商將城市細(xì)分成小區(qū)颜凯,每個(gè)小區(qū)面積通常約26平方公里。通常把小區(qū)看作是一個(gè)大六邊形網(wǎng)格上的一個(gè)個(gè)六邊形仗扬。
由于手機(jī)和基站使用低功率發(fā)射器症概,因此相同頻率可以在非鄰小區(qū)中重復(fù)使用。每個(gè)小區(qū)有一個(gè)基站早芭,由一個(gè)塔和一個(gè)安裝有無線電設(shè)備的小機(jī)房組成彼城。在模擬系統(tǒng)中,一個(gè)小區(qū)使用七分之一可用的雙工語音信道退个。換句話說募壕,在由七個(gè)小區(qū)構(gòu)成的六邊形網(wǎng)格中,每個(gè)小區(qū)使用七分之一的可用信道语盈,因此每個(gè)小區(qū)都有唯一的一組頻率舱馅,彼此間不會(huì)發(fā)生沖突:手機(jī)運(yùn)營商通常可以在一個(gè)城市中使用832個(gè)無線電頻率刀荒。每部手機(jī)在每次通話期間使用兩個(gè)頻率代嗤,即一個(gè)雙工信道,因此每個(gè)運(yùn)營商通常有395個(gè)語音信道缠借。其他42個(gè)頻率用于控制信道干毅。
因此,每個(gè)小區(qū)大約有56個(gè)語音信道可用泼返。也就是在任何小區(qū)中硝逢,可以有56個(gè)人同時(shí)用手機(jī)通話。以上是第一代的蜂窩方案設(shè)計(jì)绅喉。往后的方案基于此去擴(kuò)展渠鸽。
基站與小區(qū)內(nèi)手機(jī)之間的傳輸不會(huì)超出該小區(qū)太遠(yuǎn)。蜂窩方案要求無論城市大小柴罐,都需要有大量的基站拱绑。一般的大城市可能有數(shù)百個(gè)發(fā)射塔,不過由于很多人使用手機(jī)丽蝎,因此按用戶平均下來,成本仍能保持較低。每個(gè)運(yùn)營商在各個(gè)城市還會(huì)設(shè)置一個(gè)中心局屠阻,也稱為移動(dòng)電話交換局(MTSO)红省。該局處理與普通陸地電話系統(tǒng)的所有電話連接,控制所轄區(qū)域的所有基站国觉。
短信通信原理:
基于手機(jī)通信原理吧恃,控制通道也為SMS短信提供通路。當(dāng)朋友給您發(fā)送SMS短信時(shí)麻诀,該條短信將以控制通道上小型數(shù)據(jù)包的形式先通過SMSC(短信業(yè)務(wù)中心)痕寓,然后通過手機(jī)發(fā)射塔,再由發(fā)射塔將短信發(fā)送到手機(jī)蝇闭。同理呻率,當(dāng)您發(fā)送短信時(shí),手機(jī)將通過控制通道將短信發(fā)送到發(fā)射塔呻引,再由發(fā)射塔傳送到SMSC礼仗,最后從這個(gè)位置到達(dá)接收目標(biāo)。
短信的實(shí)際數(shù)據(jù)格式包括短信的長度逻悠、時(shí)戳元践、目標(biāo)電話號(hào)碼以及格式等等。
為什么是160個(gè)字符童谒?就是為什么叫短信单旁??
手機(jī)短信可以提供像數(shù)字頁那樣的短量數(shù)據(jù)饥伊。為了避免使用多于標(biāo)準(zhǔn)的轉(zhuǎn)發(fā)和回復(fù)操作而使系統(tǒng)過載象浑,短信技術(shù)的開發(fā)人員一致同意使用一次160個(gè)字符的最大發(fā)送量。但是160個(gè)字符的限制并不是絕對(duì)的撵渡。字符長度限制可能會(huì)因網(wǎng)絡(luò)融柬、手機(jī)型號(hào)以及無線運(yùn)營商的差異而不同。許多手機(jī)在達(dá)到160個(gè)字符的限制時(shí)將不允許繼續(xù)鍵入趋距。這樣您只能在發(fā)送后才可以繼續(xù)鍵入粒氧。但是,許多服務(wù)可以自動(dòng)將你所發(fā)送的短信拆分成若干個(gè)小于等于160個(gè)字符的信息塊节腐。這樣外盯,您就可以鍵入并發(fā)送一長條短信,只不過它會(huì)以幾條短信的方式傳輸翼雀。
相對(duì)于電話的優(yōu)勢(shì):
短信交流要比電話交流更加私密饱苟,而且省時(shí)。
手機(jī)短信是一種存儲(chǔ)和轉(zhuǎn)發(fā)服務(wù)狼渊,這意味著类垦,如果您向朋友發(fā)送一條短信,短信不會(huì)直接進(jìn)入到您朋友的手機(jī)上城须。這種方法的優(yōu)勢(shì)在于蚤认,您朋友的手機(jī)不必開機(jī)或處于服務(wù)區(qū)內(nèi),您也可以發(fā)送短信糕伐。您發(fā)出的短信將被存儲(chǔ)在短消息業(yè)務(wù)中心(可以根據(jù)需要存儲(chǔ)數(shù)日)砰琢,當(dāng)您的朋友打開手機(jī)或進(jìn)入服務(wù)區(qū)時(shí),就會(huì)立即收到這條信息良瞧。如果不將它刪除陪汽,這條短信將始終存儲(chǔ)在您朋友的SIM卡上。
除了一人對(duì)一人的短信交流褥蚯,SMS也可以用于同時(shí)將一條短信發(fā)送給很多人挚冤,包括聯(lián)系人列表或是特定區(qū)域的所有用戶。這種服務(wù)叫做群發(fā)遵岩,企業(yè)用它來聯(lián)系各組員工或通過在線服務(wù)向訂閱用戶發(fā)布新聞或其他信息你辣。
(3)短信系統(tǒng)--短信通信過程:
當(dāng)朋友給您發(fā)送SMS短信時(shí),該條短信將通過基站以控制通道上小型數(shù)據(jù)包的形式先通過SMSC(短信業(yè)務(wù)中心)尘执,然后通過手機(jī)發(fā)射塔舍哄,再由發(fā)射塔(基站)將短信發(fā)送到手機(jī)。同理誊锭,當(dāng)您發(fā)送短信時(shí)表悬,手機(jī)將通過控制通道將短信發(fā)送到發(fā)射塔,再由發(fā)射塔傳送到SMSC丧靡,最后從這個(gè)位置到達(dá)接收目標(biāo)蟆沫。
(4)短信的協(xié)議
標(biāo)準(zhǔn)的短信協(xié)議是SMPP。
SMPP(ShortMessage Peer to Peer)協(xié)議是一個(gè)開放的消息轉(zhuǎn)換協(xié)議温治;它定義了一系列操作的協(xié)議數(shù)據(jù)單元(PDUS)和當(dāng)SMPP運(yùn)行時(shí)ESMS應(yīng)用系統(tǒng)與SMSC之間交換的數(shù)據(jù)格式饭庞。從而完成SMSC與ESMES(外部短消息實(shí)體)的信息交換。SMPP是基于SMSC與ESME之間的請(qǐng)求和響應(yīng)協(xié)議數(shù)據(jù)單元的交換熬荆,每一個(gè)SMPP操作都由一個(gè)請(qǐng)求PDU和相應(yīng)的一個(gè)響應(yīng)PDU組成舟山,這種交換一般是基于IP網(wǎng)絡(luò)。
SMPP協(xié)議是一個(gè)應(yīng)用層協(xié)議卤恳,不提供傳輸功能累盗。因此,底層網(wǎng)絡(luò)連接將提供點(diǎn)對(duì)點(diǎn)的可靠數(shù)據(jù)傳輸突琳。這些傳輸包括加密包若债,窗口,流量控制和錯(cuò)誤處理等拆融。
但是蠢琳,廣大運(yùn)營商門都是自己定義自己的協(xié)議啊终。如中國電信:SGMP;中國移動(dòng):CMPP挪凑; 中國聯(lián)通:SGIP孕索。
(5)一鍵實(shí)現(xiàn)短信發(fā)送:
public class MessageTest {
//短信服務(wù)提供商。這個(gè)就百度吧躏碳,很多,真的很多散怖。我隨便找的一家提供商菇绵。
private static String Url = "http://106.ihuyi.cn/webservice/sms.php?method=Submit";
public static void main(String[] args) {
/*
方式二是一鍵完成短信功能的展示而已
*/
HttpClient client = new HttpClient();
PostMethod method = new PostMethod(Url);
client.getParams().setContentCharset("GBK");// 在頭文件中設(shè)置轉(zhuǎn)碼
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=GBK");
int mobile_code = (int)((Math.random()*9+1)*100000);
String content = new String("您的驗(yàn)證碼是:" + mobile_code + "。請(qǐng)不要把驗(yàn)證碼泄露給其他人镇眷。");
NameValuePair[] data = {//提交短信
new NameValuePair("account", "xxxxxx"),// 注冊(cè)的用戶名
new NameValuePair("password", "xxxxxxx"), //查看密碼請(qǐng)登錄用戶中心->驗(yàn)證碼咬最、通知短信->帳戶及簽名設(shè)置->APIKEY
//new NameValuePair("password", util.StringUtil.MD5Encode("密碼")),
new NameValuePair("mobile", "xxxxx"),//要發(fā)的手機(jī)
new NameValuePair("content", content),//要發(fā)的內(nèi)容
};
method.setRequestBody(data);
try {
client.executeMethod(method);//發(fā)送短信
Header[] headers = method.getResponseHeaders();//短信返回信息
int statusCode = method.getStatusCode();//狀態(tài)碼
System.out.println("statusCode:" + statusCode);
for (Header h : headers) {//響應(yīng)頭的打印
System.out.println(h.toString());
}
String result = null;
result = new String(method.getResponseBodyAsString().getBytes(
"gbk"));//打印響應(yīng)體
System.out.println(result);
method.releaseConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
實(shí)現(xiàn)的效果如下:
(6)Java短信封裝和異步實(shí)現(xiàn):
同樣用工廠模式:
public class MessageServiceFactory {
public static MessageService getMobileMessageService(){
return new MessageServiceSupport(){
public String getType() {
return MessageServiceSupport.PHONO_MESSAGE_TYPE;
}
};
}
}
接口
public interface MessageService {
/**
*
* @param acceptorName 消息接收者名稱
* @param acceptor 消息接收者,若是短信為手機(jī)號(hào)碼,若是郵件則是郵箱地址
* @param context 消息內(nèi)容
* @return 成功返回true, 內(nèi)部采用異步機(jī)制,這里返回成功只是代表初步校驗(yàn)成功,比如手機(jī)號(hào)碼或郵箱格式校驗(yàn)
*/
public boolean send(String acceptorName, String acceptor, String context);
/**
*
* @param acceptorName 消息接收者名稱
* @param acceptor 消息接收者,若是短信為手機(jī)號(hào)碼,若是郵件則是郵箱地址
* @param context 消息內(nèi)容
* @return 成功返回true, 內(nèi)部采用異步機(jī)制,這里返回成功只是代表初步校驗(yàn)成功,比如手機(jī)號(hào)碼或郵箱格式校驗(yàn)
*/
public boolean[] send(String acceptorName, String[] acceptor, String context);
public boolean send(String acceptorName, String acceptor, String context, String title);
/**
*
* @param acceptorUserId 接收者的用戶ID, cf_user.userid
* @param context
* @return
*/
public boolean send(Long acceptorUserId, String context);
/**
*
* @param acceptorLoginId 接收者的登陸ID, cf_user.loginid
* @param context
* @return
*/
public boolean send(String acceptorLoginId, String context);
}
短信接口的實(shí)現(xiàn)類:
可以基于我暴露出來的狀態(tài)碼設(shè)計(jì),進(jìn)行一些短信的分門別類判斷欠动。
public abstract class MessageServiceSupport implements MessageService {
private static String Url = "http://106.ihuyi.cn/webservice/sms.php?method=Submit";
public abstract String getType();
public static String PHONO_MESSAGE_TYPE = "1";
protected boolean checkAcceptor(String acceptor){
if(acceptor == null || "".equals(acceptor)){
return false;
}
if(PHONO_MESSAGE_TYPE.equals(getType())){
// TODO 驗(yàn)證手機(jī)號(hào)碼
}
return true;
}
public boolean send(String acceptorName, String acceptor, String context){
return send(acceptorName, acceptor, context, "無標(biāo)題");
}
public boolean send(String acceptorName, String acceptor, String context, String title){
if(checkAcceptor(acceptor)){
try {
//如果有業(yè)務(wù)需求要保存永乌,就用此bean保存。
CfMessage message = new CfMessage();
message.setType(getType());
message.setAcceptor(acceptor);
message.setAcceptorname(acceptorName);
message.setTitle(title);
message.setContext(context);
message.setCreatetime(new Date());
message.setRecordstatus(1);
//前面應(yīng)該加短信內(nèi)容狀態(tài)判斷
//TODO 短信內(nèi)容判斷具伍,因?yàn)橐话愕谌降腁PI會(huì)根據(jù)內(nèi)容去發(fā)送
//調(diào)用發(fā)送短信
sendMessage(acceptor,context);
return true;
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
public void sendMessage(String acceptor,String content){
HttpClient client = new HttpClient();
PostMethod method = new PostMethod(Url);
client.getParams().setContentCharset("GBK");
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=GBK");
NameValuePair[] data = {//提交短信
new NameValuePair("account", "C65868831"),
new NameValuePair("password", "fc422f9380ae002985db316ecce0ab27"), //查看密碼請(qǐng)登錄用戶中心->驗(yàn)證碼翅雏、通知短信->帳戶及簽名設(shè)置->APIKEY
//new NameValuePair("password", util.StringUtil.MD5Encode("密碼")),
new NameValuePair("mobile", acceptor),
new NameValuePair("content", content),
};
method.setRequestBody(data);
try {
client.executeMethod(method);//發(fā)送短信
Header[] headers = method.getResponseHeaders();//短信返回信息
int statusCode = method.getStatusCode();
System.out.println("statusCode:" + statusCode);
for (Header h : headers) {
System.out.println(h.toString());
}
String result = null;
result = new String(method.getResponseBodyAsString().getBytes(
"gbk"));
System.out.println(result);
method.releaseConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean[] send(String acceptorName, String[] acceptors, String context) {
if(acceptors == null || acceptors.length == 0){
return null;
}
boolean[] result = new boolean[acceptors.length];
int i = 0;
for(String acceptor : acceptors){
result[i++] = send(acceptorName, acceptor, context);
}
return result;
}
public boolean send(Long acceptorUserId, String context){
if(acceptorUserId == null ){
return false;
}
try {
List<User> userList=null ;
if(userList != null && userList.size() == 1){
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public boolean send(String acceptorLoginId, String context){
if(acceptorLoginId == null || "".equals(acceptorLoginId)){
return false;
}
try {
List<User> userList=null ;
if(userList != null && userList.size() == 1){
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
至于如何異步實(shí)現(xiàn),請(qǐng)參考上面郵件的發(fā)送人芽。
好了,WEB后臺(tái)--郵件和短信業(yè)務(wù)實(shí)現(xiàn)(包括Java一鍵實(shí)現(xiàn)萤厅、封裝和異步)以及原理詳解講完了橄抹,這是實(shí)習(xí)時(shí)候所負(fù)責(zé)的一些功能,在這里寫出來記錄惕味,這是積累的必經(jīng)一步楼誓,我會(huì)繼續(xù)出這個(gè)系列文章,分享經(jīng)驗(yàn)給大家名挥。歡迎在下面指出錯(cuò)誤疟羹,共同學(xué)習(xí)!躺同!你的點(diǎn)贊是對(duì)我最好的支持8蟛隆!