這篇裏,做一些簡單輕鬆的配置,郵件服務器的連接與資源的存儲。
第一篇的架構中就有提到,通常在開發Web程序時,要連接的外部輔助系統不僅僅只是數據庫,還有很多其他的系統需要連接,故而將業務層下面一層叫做Pin, 來做與外部系統的數據交互。這裏就列舉一些:比如LDAP 服務器,即輕量級目錄訪問協議的服務器,簡單而言是一種優化了讀操作的數據庫;用來連接其他Web或非Web程序的Web-Service ;郵件服務器;資源管理服務器等等。
很明顯,我們的架構是:業務邏輯(Business)層將業務數據模型(Entity)傳遞給Pin層,Pin層將Entity解析爲外部系統能夠接受的形式後交由外部系統使用或幫助本系統代管;反方向地,Pin層將外部系統的數據讀到並封裝成本系統的Entity 後交給Business層做相應處理。
LDAP和Web-Service將會放在後面的兩篇。這裏,配置兩個簡單的:郵件服務器,資源管理服務器
1,郵件服務器。用法很容易想到,我們都見過很多的郵件服務器的Web Application,因爲
這裏筆者就不再廢話連篇,上代碼:
在@Configuration的ApplicationContext.java文件中ApplicationContext 類體中加入:
@Bean
public JavaMailSender mailSender() {
Properties parameters = WebConfiguration.getSysParams();
JavaMailSenderImpl jms = new JavaMailSenderImpl();
jms.setHost((String) parameters.get("mail.smtp.host"));
jms.setUsername((String) parameters.get("mail.smtp.username"));
jms.setPassword((String) parameters.get("mail.smtp.password"));
jms.setDefaultEncoding((String) parameters.get("mail.smtp.encoding"));
jms.setPort(Integer.parseInt((String) parameters.get("mail.smtp.port")));
jms.setProtocol((String) parameters.get("mail.transport.protocol"));
jms.setJavaMailProperties(parameters);
return jms;
}
JavaMailSender類在org.springframework.context-support-x.x.x.RELEASE.jar包中,不要忘記導此包入WEB-INF/lib下
當然,還要有JavaMail API的類庫,在Spring文檔http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mail.html中寫的是
mail.jar和activation.jar
由於activation.jar即JAF已經成爲標準的Java組件而被包含在1.6版本以上的JDK中,所以1.6版本以上的JDK不再需要activation.jar,然後新版的JavaMail API應該是6個jars.分別爲:mail.jar mailapi.jardsn.jap imap.jar pop3.jarsmtp.jar
Parameters 自然還在sysParams.properties文件中去寫。
mail.store.protocol=pop3
mail.transport.protocol=smtp
mail.smtp.encoding=utf-8
mail.smtp.host=127.0.0.1
mail.smtp.port=25
mail.smtp.username=root
mail.smtp.password=root
mail.smtp.socketFactory.port=465
mail.smtp.auth=true
mail.smtp.timeout=10000
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
而後,在pin層新建接口IMailPin.java, 在pin.imap下新建該接口實現類SpringMailPin.java
代碼如下:
package com.xxxxx.webmodel.pin.impl;
import java.io.Serializable;
import javax.annotation.Resource;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.stereotype.Component;
import com.xxxxx.webmodel.pin.IMailPin;
@Component
public class SpringMailPin implements IMailPin,Serializable {
private static final long serialVersionUID = -1313340434948728744L;
private JavaMailSender mailSender;
public JavaMailSender getMailSender() {
return mailSender;
}
@Resource
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
@Override
public void testMail() {
JavaMailSenderImpl jms = (JavaMailSenderImpl)this.mailSender;
System.out.println(jms.getHost());
MimeMessage mimeMsg =jms.createMimeMessage();
try {
mimeMsg.setSubject("Test James");
mimeMsg.setFrom(new InternetAddress("[email protected]"));
mimeMsg.setRecipient(RecipientType.TO, new Inter-netAddress("[email protected]"));
mimeMsg.setRecipient(RecipientType.CC, new Inter-netAddress("[email protected]"));
mimeMsg.setRecipient(RecipientType.BCC, new Inter-netAddress("[email protected]"));
mimeMsg.setText("Hello");
// MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMsg, true, "utf-8");
// mimeMessageHelper.setFrom("[email protected]");
// mimeMessageHelper.setTo("[email protected]");
//
// mimeMessageHelper.setCc("[email protected]");
// mimeMessageHelper.setBcc("[email protected]");
// mimeMessageHelper.setSubject("Test mail");
// mimeMessageHelper.setText("Hi, Hello", true);
mailSender.send(mimeMsg);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
然後還有簡單的單元測試類
package com.xxxxx.webmodel.test.pin;
import javax.annotation.Resource;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import com.xxxxx.webmodel.pin.IMailPin;
import com.xxxxx.webmodel.util.ApplicationContext;
import com.xxxxx.webmodel.util.WebConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={ApplicationContext.class})
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContext-TestExecutionListener.class, TransactionalTestExecutionListener.class })
public class MailPinTest {
private IMailPin mailPin;
public IMailPin getMailPin() {
return mailPin;
}
@Resource
public void setMailPin(IMailPin mailPin) {
this.mailPin = mailPin;
}
@BeforeClass
public static void init() throws Exception {
new WebConfiguration().onStartup(null);
}
@Test
public void test() {
mailPin.testMail();
}
}
可以運行起一個James做簡單測試http://james.apache.org/,3.0的新版還未有Release出Stable版本來,最新穩定版本是2.3.2.
Download Stable James Server2.3.2後的得到james-binary-2.3.2.tar.gz或james-binary-2.3.2.zip
解壓後放到自己的安裝目錄下,然後讀下面鏈接的內容,來獲知基本用法;
http://wiki.apache.org/james/JamesQuickstart
以及下面的內容,獲知如何將James安裝爲系統的service
http://wiki.apache.org/james/RunAsService
一切就緒後,測試。成功與否就查自己的郵件吧,有些郵件服務器發不到,多試一些。
比如,我在雅虎郵箱裏能收到上述測試郵件
其它需求可以在上述Spring文檔中查閱。
2,資源管理服務器。什麼是資源管理服務器?用來存儲諸如圖片,音頻,視頻,文檔等資源的服務器。有人會說,存儲在部署Web的服務器本身的硬盤上不就行了麼。 好,文件系統,最簡單的資源管理服務器。可是,把這些資源放到Local的文件系統上,這樣的Case會有不適合的情況。例如我們需要部署一個Web服務集羣以供併發量較高的訪問,有好多臺服務器部署着同樣的WebApp,一個控制Node去管理是哪臺機器響應用戶的訪問,這時,你就需要同步每一臺服務器的文件系統的同一個位置上的所有資源(文件)。雖然可以做到,但是跑一個線程去同步N個 /var/resources比起配置所有服務器通過某協議訪問一個固定的Socket(Host:Port)還是複雜,而且前者會遇到實時拖延問題:是說在一個時刻,A用戶訪問到了A服務器需要B資源,可是當時正好B資源還沒有被跑着的線程挪過來到A服務器上。所以用一個固定的資源管理器是個不錯的選擇,比如Amazon 的S3服務器。想地有點兒遠了,自己架設個FTP比什麼都強。
這部分很簡單,直接貼代碼嘍:
接口:
package com.xxxxx.webmodel.pin;
import java.io.InputStream;
public interface IResourcePin {
public boolean isExisting(String key) throws Exception;
public void storeResource(String key,Object data) throws Exception;
public <Form> Form achieveResource(String key,Class<Form> clasze)throws Exception;
public void removeResource(String key) throws Exception;
enum DataType{
Base64(String.class),ByteArray(byte[].class),InputStream(InputStream.class);
@SuppressWarnings("rawtypes")
DataType(Class claze){
this.dataType=claze;
}
@SuppressWarnings("rawtypes")
private Class dataType;
@SuppressWarnings("rawtypes")
public Class getDataType(){return dataType;}
}
}
測試方法:
package com.xxxxx.webmodel.test.pin;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.annotation.Resource;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.util.FileCopyUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import com.xxxxx.webmodel.pin.IResourcePin;
import com.xxxxx.webmodel.util.ApplicationContext;
import com.xxxxx.webmodel.util.WebConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={ApplicationContext.class})
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContext-TestExecutionListener.class, TransactionalTestExecutionListener.class })
public class ResourcePinTest {
private String testKey = "projectA/belong1/belong2/doc3";
private IResourcePin resourcePin;
@BeforeClass
public static void init() throws Exception{
new WebConfiguration().onStartup(null);
}
@Resource(name="ftpResourcePin")
public void setResourcePin(IResourcePin resourcePin){
this.resourcePin = resourcePin;
}
@Test
public void testIsExisting()throws Exception {
Assert.assertFalse(resourcePin.isExisting(testKey));
}
@Test
public void testStoreResource()throws Exception {
resourcePin.storeResource(testKey, "Test Resource".getBytes("UTF-8"));
Assert.assertTrue(resourcePin.isExisting(testKey));
resourcePin.removeResource(testKey);
resourcePin.storeResource(testKey, new ByteArrayInputStream("Test Re-source".getBytes("UTF-8")));
Assert.assertTrue(resourcePin.isExisting(testKey));
resourcePin.removeResource(testKey);
resourcePin.storeResource(testKey, new BASE64Encoder().encode("Test Re-source".getBytes("UTF-8")));
Assert.assertTrue(resourcePin.isExisting(testKey));
resourcePin.removeResource(testKey);
}
@Test
public void testAchieveResource() throws Exception{
resourcePin.storeResource(testKey, "Test Resource".getBytes("UTF-8"));
InputStream is0 =resourcePin.achieveResource(testKey, null);
byte[] resultBytes0 = FileCopyUtils.copyToByteArray(is0);
Assert.assertTrue(new String(resultBytes0,"UTF-8").equals("Test Resource"));
InputStream is =resourcePin.achieveResource(testKey, InputStream.class);
byte[] resultBytes = FileCopyUtils.copyToByteArray(is);
Assert.assertTrue(new String(resultBytes,"UTF-8").equals("Test Resource"));
byte[] byteArray = resourcePin.achieveResource(testKey, byte[].class);
Assert.assertTrue(new String(byteArray,"UTF-8").equals("Test Resource"));
String base64Code = resourcePin.achieveResource(testKey,String.class);
Assert.assertTrue(new String(new BASE64Decoder().decodeBuffer(base64Code),"UTF-8").equals("Test Resource"));
try{resourcePin.achieveResource(testKey, Integer.class);}
catch(Exception use){
Assert.assertTrue(use.getMessage().startsWith("Data Type is not support-ed"));
}
resourcePin.removeResource(testKey);
}
@Test
public void testRemoveResource() throws Exception{
resourcePin.storeResource(testKey, "Test Resource".getBytes("UTF-8"));
Assert.assertTrue(resourcePin.isExisting(testKey));
resourcePin.removeResource(testKey);
Assert.assertFalse(resourcePin.isExisting(testKey));
}
}
FileSystem實現:
package com.xxxxx.webmodel.pin.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.Serializable;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import com.xxxxx.webmodel.pin.IResourcePin;
import com.xxxxx.webmodel.util.WebConfiguration;
@Component
public class FileSystemResourcePin implements Serializable,IResourcePin {
/**
*
*/
private static final long serialVersionUID = -8508501371117792553L;
private String fileSystemRoot;
public static long getSerialversionuid() {
return serialVersionUID;
}
public FileSystemResourcePin(){
try{
this.fileSystemRoot = WebConfigura-tion.getSysParams().getProperty("resource.storage.relativePath");
if(!this.fileSystemRoot.startsWith("/"))this.fileSystemRoot='/'+this.fileSystemRoot;
}
catch(Exception e){this.fileSystemRoot= "/fileStorage";}
this.fileSystemRoot = this.getClass().getResource("/").getFile().replace("/WEB-INF/classes",this.fileSystemRoot);
}
@Override
public boolean isExisting(String key) throws Exception {
return isExisting(new File(this.fileSystemRoot,key));
}
private boolean isExisting(File file){
return file.exists();
}
@SuppressWarnings("unchecked")
@Override
public void storeResource(String key, Object data) throws Exception {
if(key==null||key.trim().length()==0||data==null) return;
@SuppressWarnings("rawtypes")
Class dataType = data.getClass();
for(DataType supportedType: DataType.values()){
if(supportedType.getDataType().isAssignableFrom(dataType))
{
File targetFile = new File(this.fileSystemRoot,key);
if(!targetFile.exists())
{target-File.getParentFile().mkdirs();targetFile.createNewFile();}
FileOutputStream fos = new FileOutputStream(targetFile);
switch(supportedType){
case Base64:data =(Object) new BASE64Decoder().decodeBuffer((String)data);
case ByteArray:data = (Object)new ByteArrayInputStream((byte[])data);
case InputStream:FileCopyUtils.copy((InputStream)data, fos);
default:return;
}
}
}
throw new Exception("Data Type is not supported");
}
@SuppressWarnings("unchecked")
@Override
public <Form> Form achieveResource(String key, Class<Form> clasze)
throws Exception {
File keyFile = new File(this.fileSystemRoot,key);
if(!keyFile.exists()||keyFile.isDirectory())return null;
if(clasze==null)return (Form) achieveResource(key,InputStream.class);
for(DataType supportedType: DataType.values()){
if(clasze.equals(supportedType.getDataType()))
{
FileInputStream fis = new FileInputStream(keyFile);
switch(supportedType){
case InputStream:return (Form)fis;
case ByteArray:return (Form)FileCopyUtils.copyToByteArray(fis);
case Base64:return (Form)new BASE64Encoder().encode(FileCopyUtils.copyToByteArray(fis));
}
}
}
throw new Exception("Data Type is not supported");
}
@Override
public void removeResource(String key) throws Exception {
new File(this.fileSystemRoot,key).delete();
}
}
FTP實現
package com.xxxxx.webmodel.pin.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import sun.net.ftp.FtpClient;
import sun.net.ftp.FtpProtocolException;
import com.xxxxx.webmodel.pin.IResourcePin;
import com.xxxxx.webmodel.util.WebConfiguration;
@Component("ftpResourcePin")
public class FTPResourcePin implements Serializable,IResourcePin{
/**
*
*/
private static final long serialVersionUID = -4273201499908924422L;
private FtpClient ftpClient;
private String ftpUser="admin";
private String password="password";
private SocketAddress ftpServerAddress = new InetSocket-Address("127.0.0.1",FtpClient.defaultPort());
private String releateRootPath="/ftproot";
public FTPResourcePin(){
String ftpURL = null;
try{
ftpURL =WebConfiguration.getSysParams().getProperty("resource.ftp.url");
if(ftpURL!=null){
int protocolIndex = ftpURL.indexOf("://");
if(protocolIndex>=0)ftpURL=ftpURL.substring(protocolIndex+3);
int usrIndex = ftpURL.indexOf('@');
if(usrIndex>=0){
this.ftpUser = ftpURL.substring(0,usrIndex);
ftpURL = ftpURL.substring(usrIndex+1);
}
int hostIndex = ftpURL.indexOf('/');
if(hostIndex>=0){
String[] socket = ftpURL.substring(0,hostIndex).split(":");
this.ftpServerAddress = new InetSocket-Address(socket[0],socket.length>1?Integer.parseInt(socket[1]):FtpClient.defaultPort());
ftpURL=ftpURL.substring(hostIndex);
}
this.releateRootPath=ftpURL.startsWith("/")?ftpURL:"/"+ftpURL;
}
this.releateRootPath+=WebConfiguration.getSysParams().getProperty("resource.storage.relativePath");
}
catch(Exception e){/*do as default value*/}
try {
this.ftpClient =FtpClient.create((InetSocketAddress)this.ftpServerAddress);
} catch (FtpProtocolException | IOException e) {
e.printStackTrace();
}
}
public static long getSerialversionuid() {
return serialVersionUID;
}
private void checkConnection(){
try {
if(!ftpClient.isConnected())
this.ftpClient.connect(this.ftpServerAddress);
if(!ftpClient.isLoggedIn())
{
try{this.password = WebConfigura-tion.getSysParams().getProperty("resource.ftp.password");}catch(Exception e){}
ftpClient.login(this.ftpUser, this.password.toCharArray());
}
} catch (FtpProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void gotoDirectory(String dir,Boolean write){
if(dir==null||dir.trim().length()==0)return;
if(write==null)write=false;
try {
dir = dir.replaceAll("\\\\+", "/");
if(dir.startsWith("/")){
this.checkConnection();
ftpClient.reInit();
dir=(this.releateRootPath+dir).replaceAll("/+", "/");
if(dir.startsWith("/"))dir=dir.substring(1);
}
String[] dirs = dir.split("/");
this.checkConnection();
for(String dire:dirs){
if(write)try{ftpClient.makeDirectory(dire);}
catch(sun.net.ftp.FtpProtocolException fpe){
if(!fpe.getMessage().contains("Cannot create a file when that file already exists"))
throw fpe;}
ftpClient.changeDirectory(dire);
}
} catch (FtpProtocolException | IOException e) {
if(e instanceof FtpProtocolException && e.getMessage().contains("The sys-tem cannot find the file specified"));
}
}
@Override
public boolean isExisting(String key) throws Exception {
if(key==null)
return false;
try{
key =('/'+key.replaceAll("\\\\+", "/")).replaceAll("/+", "/");
int lastIdx = key.lastIndexOf('/');
this.gotoDirectory(key.substring(0,lastIdx), null);
String fileName = key.substring(lastIdx+1);
InputStream is =ftpClient.nameList(fileName);
byte[] testContent = FileCopyUtils.copyToByteArray(is);
if(testContent!=null&&testContent.length>0)
return new String(testContent).trim().equals(fileName);
else return false;
}catch(Exception e){
if(e instanceof sun.net.ftp.FtpProtocolException && e.getMessage().contains("The system cannot find the file specified"))
return false;
else throw e;
}
}
@SuppressWarnings("unchecked")
@Override
public void storeResource(String key, Object data) throws Exception {
if(key==null||key.trim().length()==0||data==null) return;
@SuppressWarnings("rawtypes")
Class dataType = data.getClass();
for(DataType supportedType: DataType.values()){
if(supportedType.getDataType().isAssignableFrom(dataType))
{
OutputStream os = null;
try{
key =('/'+key.replaceAll("\\\\+", "/")).replaceAll("/+", "/");
int lastIdx = key.lastIndexOf('/');
this.gotoDirectory(key.substring(0,lastIdx), true);
os =ftpClient.putFileStream(key.substring(lastIdx+1));
switch(supportedType){
case Base64:data =(Object) new BASE64Decoder().decodeBuffer((String)data);
case ByteArray:data = (Object)new ByteArrayIn-putStream((byte[])data);
case InputStream:FileCopyUtils.copy((InputStream)data, os);
default:return;
}
}catch(Exception e){}
}
}
throw new Exception("Data Type is not supported");
}
@SuppressWarnings("unchecked")
@Override
public <Form> Form achieveResource(String key, Class<Form> clasze)
throws Exception {
if(key==null)return null;
if(clasze==null)return (Form) achieveResource(key,InputStream.class);
for(DataType supportedType: DataType.values()){
if(clasze.equals(supportedType.getDataType()))
{
InputStream is =null;
try{
key =('/'+key.replaceAll("\\\\+", "/")).replaceAll("/+", "/");
int lastIdx = key.lastIndexOf('/');
this.gotoDirectory(key.substring(0,lastIdx), null);
is =ftpClient.getFileStream(key.substring(lastIdx+1));
switch(supportedType){
case InputStream:return (Form)is;
case ByteArray:return (Form)FileCopyUtils.copyToByteArray(is);
case Base64:return (Form)new BASE64Encoder().encode(FileCopyUtils.copyToByteArray(is));
}
}catch(Exception e){}
}
}
throw new Exception("Data Type is not supported");
}
@Override
public void removeResource(String key) throws Exception {
try{
key =('/'+key.replaceAll("\\\\+", "/")).replaceAll("/+", "/");
int lastIdx = key.lastIndexOf('/');
this.gotoDirectory(key.substring(0,lastIdx), true);
String resName =key.substring(lastIdx+1);
ftpClient.deleteFile(resName);
String preName = key.substring(1,lastIdx);
String dirs[] = preName.split("/");
for(int i=dirs.length-1;i>-1;i--)
{
ftpClient.changeToParentDirectory();
ftpClient.removeDirectory(dirs[i]);
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
FTP的安裝與配置就不多說了,網絡上隨處可見,Windows 裏把IIS的FTP服務打開,Linux用相應的源管理軟件安裝個ftpd 或vsftpd, pure-ftpd都可以。配置好之後在sysParam裏填加上相應的properties
resource.storage.relativePath=/FileStorage
resource.ftp.url=ftp://[ftpusername]@[hostname{:pot}]/{爲該應用配置的路徑}
resource.ftp.password=ftppasswordvalue
在單元測試類中通過改變@Resource的name值注入不同的實現類,分別測試,成功。