邮件发送程序,用到了模版模式,帮我看看有啥问题。

06-06-20 sayschi
我做了一个邮件发送的Java程序,基本情况如下:

需要发的信件有 发货通知,订单确认信,订单取消信等等,我建立一个保存jsp模版的数据表,字段主要有ID(主键),TITLE(邮件主题),BODY(邮件内容),BODY设置为CLOB类型,其实是各种信件的jsp页面,上面有若干标签,如:<OrderNO>,<MemberName>(每种模版标签都不一样),发送之前选出邮件数据,并逐条把这些标签替换掉,然后调用邮件发送类SendMail.send(LetterTemplate letter)来循环发送。

LetterTemplate.java(邮件模版抽象类)

public abstract class LetterTemplate {
    
    /** 邮件主题 **/
    private String title = null;
    
    /** 邮件内容 **/
    private String template = null;
    
    /** 收件人地址 **/
    private String email = null;
    
    /** 标签对应的值(子类来引用这个变量), 格式为key:<OrderNO>, value:050500001 **/
    protected Map map = null;

    public LetterTemplate() {
        map = new HashMap(); 
    }

    /**
     * @return String
     * @roseuid 44220CAB00B5
     */
    public String getTitle() {
        return this.title;
    }

    /**
     * @return String
     * @roseuid 44220CC701C4
     */
    public String getTemplate() {
        return this.template;
    }

    /**
     * @return String
     * @roseuid 44220CD80092
     */
    public String getEmail() {
        return this.email;
    }
    
    /**
     * @param email The email to set.
     */
    public void setEmail(String email) {
        this.email = email;
    }
    /**
     * @param template The template to set.
     */
    public void setTemplate(String template) {
        this.template = template;
    }
    /**
     * @param title The title to set.
     */
    public void setTitle(String title) {
        this.title = title;
    }
    
    /**
     * 从数据库中选出数据,用来替换标签变量
     * @return String
     * @roseuid 44220CD80092
     */
    public abstract void setValue(Connection con, int orderID) throws SQLException;
    
    /**
     * 替换邮件主题
     * @return String
     * @roseuid 44220CD80092
     */
    public abstract String replaceTitle();
    
    /**
     * 替换邮件内容
     * @return String
     * @roseuid 44220CD80092
     */
    public abstract String replaceBody();
    
    
    /**
     * 变量替换
     * @return String
     * @roseuid 44220CD80092
     */
    final public void buildTemplate(Connection con, int orderID) throws SQLException {
        setValue(con, orderID);
        this.title = replaceTitle();
        this.template = replaceBody();
    }
    
    /**
     * 标签替换公用方法
     * @param replaceStr
     * @param label
     * @param map
     * @return
     */
    protected String replace(String replaceStr, String[] label) {
        
        for (int i = 0; i < label.length; i++) {
            if (replaceStr.indexOf(label[i]) != -1) {
                String mapValue = (String) map.get(label[i]);
                if (mapValue == null || mapValue.equals("null")) {
                    mapValue = "";
                }
                
                replaceStr = replaceStr.replaceAll(label[i], mapValue);
            }
        }
        return replaceStr;
    }

}

<p>

OrderSendOfLetter.java(订单发货通知,还有几个类省略,和这个相似)

public class OrderSendofLetter extends LetterTemplate {
    
    /** 本模版需要替换的变量(页面上定义的标签) * */
    private String[] label = new String[] { 
            "<MemberName>", 
            "<DeliveryType>",
            "<PaymentMethod>",
            "<PaymentRemark>", 
            "<OrderNO>", 
            "<MemberEmail>",
            "<MemberPostcode>", 
            "<MemberAddress>",
            "<MemberTelephone>", 
            "<ProductList>" 
            };

    public OrderSendofLetter() {
        
    }

    /**
     * 替换title标签
     */
    public String replaceTitle() {
        return replace(getTitle(), label);
    }
    
    /**
     * 替换body标签
     */
    public String replaceBody() {
        return replace(getTemplate(), label);
    }
   
    /**
     * 设置标签对应的值(每个子模版的集中业务)
     */
    public void setValue(Connection con, int orderID) throws SQLException{
                String sql = "select... "; //选出替换标签的数据
                ......
        try {
                        
                if (rs.next()) {
                name = rs.getString("name");
                deliveryType= rs.getInt("delivey_type");
                .....
                map.put("<MemberName>", name);
                map.put("<DeliveryType>", deliveryType);
                map.put("<PaymentMethod>", paymentMethod);
                map.put("<PaymentRemark>", paymentRemark);
                map.put("<OrderNO>", so_number);
                map.put("<MemberEmail>", email);
                map.put("<MemberPostcode>", post_code);
                map.put("<MemberAddress>", member_address);
                map.put("<MemberTelephone>", member_phone);
                ......
            }
        } finally {
            if (rs != null)
                try {
                    rs.close();
                } catch (Exception e) {
                }
            if (pstmt != null)
                try {
                    pstmt.close();
                } catch (Exception e) {
                }

        }

        
            
        } 

    }
    
}

<p>

MailGen.java(部分客户端代码)

public void dealConfirmMail() throws Exception {
LetterTemplate template = null;//邮件模版
SendMail sm = new SendMail();//发邮件工具类
String sql = "select ...";需要发的邮件都选出来
......
try {
......
rs = pstmt.executeQuery();

while (rs.next()) {
     type = rs.getInt("mail_type");//邮件类型
     orderID = rs.getInt("order_id");//订单ID
     email = getEmail("email");//收件人地址
 //静态工厂方法产生不同类型的邮件,如果邮件类型不正确抛出LetterTypeException异常
      template = UserLetterFactory.getInstance(type);

      //设置邮件邮件主题,邮件内容,收件人
      LetterTemplateBean temp = (LetterTemplateBean) letterMap
                                    .get(String.valueOf(type));
      template.setTitle(temp.getTitle());
      template.setTemplate(temp.getBody());
      email = getEmail(conn, orderID);

      //调用子类的setValue(),replace()方法
      template.buildTemplate(conn, orderID);
      sm.send(template);//发送邮件
      ....//do other thingings
}
} finally {
            if (rs != null)
                try {
                    rs.close();
                } catch (Exception e) {
                }
            if (pstmt != null)
                try {
                    pstmt.close();
                } catch (Exception e) {
                }
            if (conn != null)
                try {
                    conn.close();
                } catch (Exception e) {
                }

        }

<p>

终于贴完了,请banq大侠帮我看看程序有没什么问题,如何改进,谢谢。

banq
2006-06-21 15:10
我首先问你一个问题:如果对方邮件服务器在忙,或者由于某种原因比较慢,那么你是否让你的用户一直在电脑面前瞪着屏幕干等,万一出错,还送它一个服务器出错的信息?

建议看看下面这篇文章:

http://www.jdon.com/idea/jaas/06001.htm

sayschi
2006-06-21 15:30
如果对方邮件服务器在忙,或者由于某种原因比较慢,那么邮件就被退回来了,不再发送。

banq
2006-06-21 15:40
>对方邮件服务器在忙,或者由于某种原因比较慢

这里谈的不是对方服务器,sendmail是链接smtp邮件服务器,连接不上,或者缓慢情况都会发生,这就会影响你的程序执行,执行到sendmail会暂停或缓慢,甚至出错。

sayschi
2006-06-21 16:02
是的,我观察服务后台打印信息,有些信件发送只需要零点几秒,而有些却要达到2,3秒甚者更久,最坏的情况就是服务停止了。我在程序中我每发送一封信件,都要连接一次smtp服务器,造成缓慢的原因是不是就这个原因?,程序如下:

/**
     * 发送单个邮件
     * 
     * @param letter:邮件模版
     */
    public boolean send(LetterTemplate letter) throws Exception {
        String isHTML = prop.getProperty(Constant.KEY_IS_HTML);
        this.HTML = isHTML.equals("1") ? true : false;
        boolean flag = false;
        try {      
            if (connect(prop)) {//连接上服务器
                prop.put(Constant.KEY_MAIL_TITLE, letter.getTitle());
                prop.put(Constant.KEY_MAIL_CONTENT, letter.getTemplate());
                prop.put(Constant.KEY_TO_NAME, letter.getEmail());
                MimeMessage mimeMessage = createMimeMessage(prop);
                transport.sendMessage(mimeMessage, mimeMessage
                        .getAllRecipients());
                flag = true;
            } else {
                flag = false;
            }
            
        } catch (Exception e) {
            log.error(e.toString()+letter.getTitle());
            flag = false;
            throw e;
            
        } finally {
            if (this.transport != null) {
                try {
                    this.transport.close();
                } catch(Exception e) {
                    flag = false;
                }
            }    
        }
        return flag;
    }
<p>

asklxf
2006-06-22 08:49
你应该把发送程序做成后台线程并通过队列实现异步发送,发信时只要放入队列就不管了

另外你的模版设计也太复杂了,直接用一个String表示模版:

"hi, $username, your order $id has been sent at $date."

然后放入map用正则表达式替换:

map.put("username", "bill");

map.put("id", "123456");

map.put("date", "2006-06-12");

asklxf
2006-06-22 08:58
另外我觉得完全没有必要抽象出这么多的LetterTemplate子类,读一个Template String和相关数据是前台应用的逻辑,你把它放到LetterTemplate中就不得不在子类中查数据库,替换标签,我建议你把LetterTemplate改为:

public class LetterTemplate {

private String subject;

private String body;

private String to;

private String from;

private LetterTemplate(String from, String to, String subject,String body){

this.xxx=xxx;

}

public static LetterTemplate buildLetter(String from, String to, String subject,String body, Map values){

// replace body's $xxx with map value:

// TODO:

return new LetterTemplate(from, to, subject, body);

}

public String getXxx() ...

}

完全没有必要使用子类

sayschi
2006-06-22 12:01
> 另外我觉得完全没有必要抽象出这么多的LetterTemplate子类

> 烈桓Template

你说的很对,我把取模版数据的SQL放到了各个子类,其实这应该是客户端的代码,的确有些不太合理。我当时考虑到经常业务部门将常会提出修改信件模版或者增加新的信件模版这种需求,如果把不用到子类,客户端代码势必产生很多的if..else,并且代码很臃肿,造成维护困难。用了子类,就可以修改特定的模版而不必担心其他模版受到影响。还有就是模版上的信息很多,包括会员基本信息,帐户信息,还有订单头,订单行的等信息。

另外我邮件发送使用了作业调度框架Quartz,作业调度的时候,一次性取出符合条件的邮件比如status=0(0代表未发送),然后逐条发送。

猜你喜欢