您的位置:首页 > 运维架构 > 网站架构

Java Web Application 自架构 六 邮件服务器与资源存储

2013-01-03 22:54 645 查看
这篇里,做一些简单轻松的配置,邮件服务器的连接与资源的存储。

第一篇的架构中就有提到,通常在开发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.jar dsn.jap imap.jar pop3.jar smtp.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("xxxxxx@tom.com"));
mimeMsg.setRecipient(RecipientType.TO, new Inter-netAddress("xxxxx@live.com"));
mimeMsg.setRecipient(RecipientType.CC, new Inter-netAddress("xxxxxx@yahoo.com"));
mimeMsg.setRecipient(RecipientType.BCC, new Inter-netAddress("xxxxx@mail.com"));
mimeMsg.setText("Hello");
//          MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMsg, true, "utf-8");
//          mimeMessageHelper.setFrom("hitmit1314@tom.com");
//          mimeMessageHelper.setTo("palmtale@live.com");
//
//          mimeMessageHelper.setCc("palmtale@yahoo.com");
//          mimeMessageHelper.setBcc("palmtale@mail.com");
//          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.gzjames-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;

@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;

@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;
@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值注入不同的实现类,分别测试,成功。

本文出自 “掌心童林” 博客,转载请与作者联系!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: