javaMail 邮件工厂发送类

package red.sea.email.common;






import java.util.Properties;


import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import java.io.File;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.SendFailedException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.sun.mail.smtp.SMTPMessage;
import com.sun.mail.util.MailSSLSocketFactory;


import red.sea.commons.util.BeanTool;
import red.sea.mail.data.MailEntity;
import red.sea.mail.data.OaMailAccount;
import red.sea.mail.data.OaMailResource;
import red.sea.mail.service.IOaMailOutlookService;




/***
 * 邮件发送工具类
 * 1.读取账户中配置的邮箱账户信息
 * 2.根据邮件队列中的数据进行搜索轮询。
 * 3.根据轮询结果进行邮件的发送,发送失败则进行失败提示,并保存到数据库。
 *使用生产消费类:
 *MailSendQueueTool.mapPool.put("邮件resourceId唯一", new MailEntity(m,s,l));  
 *
 */
public class MailSendQueueTool implements ServletContextListener {

    /**
     * 缓存对象 map
     */
  //  public static CachePool<String, Object> mapPool = CachePool.getInstance();
    //线程数
   // private static final int NTHREADS=5;  
    // 使用线程池来避免 为每个请求创建一个线程。  
   // private static final Executor threadPool=Executors.newFixedThreadPool(NTHREADS);  
    
    
    /***
* 邮件发送服务改造。
* 0.设置监听定时器邮件定时发送邮件。
* 1.改造为异步邮件发送服务。
* 2.使用多线程和队列进行邮箱发送。
* @param mailAccount
* @param mailresource
* @param attachment
* @return
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
 System.out.println("监听器启动......");
  new MailSendDaemon().startTread();
  System.out.println("监听器结束.............");
  
/*Timer timer = new Timer(); 
timer.schedule(new TimerTask() {
@Override
public void run() {
//启动邮件发送线程。
System.out.println("启动邮件定时发送服务,queue剩余"+mapPool.size()+"条");
if(mapPool.size()!=0){
startThread();
}else{
System.out.println("队列中数量0无邮件可发.........");
}
System.out.println("关闭邮件定时服务:"+new Date());
}},1000*60,20000); //1分钟之后发邮件,每隔5秒钟发送一次邮件。
*/
}



    
 








@Override
public void contextDestroyed(ServletContextEvent sce) {

}

}



----------------------------------------------------------------------------------------------------------------------------------------------------------------


package red.sea.email.common;


import java.text.DateFormat;
import java.util.Properties;


import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.SendFailedException;
import javax.mail.internet.AddressException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;






import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sun.mail.smtp.SMTPMessage;
import com.sun.mail.util.MailSSLSocketFactory;


import red.sea.commons.util.BeanTool;
import red.sea.email.data.MailAccount;
import red.sea.email.data.MailList;
import red.sea.email.service.IMailListService;






/***
 * 邮件发送工具类
 * 1.读取账户中配置的邮箱账户信息
 * 2.根据邮件队列中的数据进行搜索轮询。
 * 3.根据轮询结果进行邮件的发送,发送失败则进行失败提示,并保存到数据库。
 *
 */
public class MailSendTool {

private static Log log = LogFactory.getLog(MailSendTool.class);
//设置线程池
private static Executor executor = Executors.newFixedThreadPool(10); 
private static String protocol2 = "smtp";





/***
* 邮件发送服务改造。
* 1.改造为异步邮件发送服务。
* 2.使用多线程和队列进行邮箱发送。
* @param mailAccount
* @param mailresource
* @param attachment
* @return
*/
public int sendMail( MailAccount mailAccount, 
MailList mailresource,  List attachment) {

//是否启用ssl发送邮件
Properties properties = new Properties();
properties.setProperty("mail.smtp.host", mailAccount.getSmtpServer());
properties.put("mail.smtp.auth", "true"); // 允许smtp校验

//如果启用ssl邮箱发送服务
if(mailAccount.getIsSendSsl().equals("1")){
properties.setProperty("mail.smtp.port", mailAccount.getSendPort());
MailSSLSocketFactory sf = null;
try {
   sf = new MailSSLSocketFactory();
} catch (GeneralSecurityException e) {
 //  logger.fatal("error", e);
}
sf.setTrustAllHosts(true);
properties.put("mail.smtp.ssl.enable", "true");
//关键
//properties.put("mail.smtp.starttls.enable","true");
properties.put("mail.smtp.ssl.socketFactory", sf);
properties.put("mail.smtp.socketFactory.fallback", "false");
properties.put("mail.smtp.socketFactory.port",  mailAccount.getSendPort());
}


Session session = Session.getInstance(properties, null);
Transport transport = null;
SMTPMessage message = null;
int flag = -1;
try {
// 验证发件服务器
try{

if(transport==null){
flag = 4;
transport = session.getTransport("smtp");
transport.connect(mailAccount.getSmtpServer(), mailAccount.getEmail(),mailAccount.getPwd());
}
}catch (Exception e) {
System.out.println("连接出错:"+e.getMessage());
}
flag = 5;
message = new SMTPMessage(session);
// 为true时,则即使地址数组addresses中有错误的地址,邮件也可以发送给其余正确的地址
message.setSendPartial(true);
//在这儿可以指定发送人姓名的编码方式,默认为GBK
message.setFrom(new InternetAddress(mailAccount.getEmail(),
mailAccount.getName(),"UTF-8"));
if (!mailresource.getMRev().equalsIgnoreCase("")) {
String[] addresses = mailresource.getMRev().split(";");
for (int i = 0; i < addresses.length; i++) {
message.addRecipients(Message.RecipientType.TO,
addresses[i]);
}


}

//抄送
if (!mailresource.getMCc().equalsIgnoreCase("")) {
String[] addresses = mailresource.getMCc().split(";");
for (int i = 0; i < addresses.length; i++) {
message.addRecipients(Message.RecipientType.CC,
addresses[i]);
}
}

//密送
if (!mailresource.getMBcc().equalsIgnoreCase("")) {
String[] addresses = mailresource.getMBcc().split(";");
for (int i = 0; i < addresses.length; i++) {
message.addRecipients(Message.RecipientType.BCC,
addresses[i]);
}
}

//在这儿可以指定邮件主题的编码方式,默认为GBK
message.setSubject(mailresource.getMTitle(),"GBK");
// 设置发送级别
message.addHeader("X-Priority", "3");
// 设置是否回执
if (mailresource.getIsReturnReceipt().equalsIgnoreCase("1")) {
message.addHeader("Disposition-Notification-To", mailAccount
.getEmail());
}


// 向multipart对象中添加邮件的各个部分内容,包括文本内容和附件
           Multipart multipart = new MimeMultipart();
           
           // 添加邮件正文
           BodyPart contentPart = new MimeBodyPart();
           contentPart.setContent(mailresource.getMContent(), "text/html;charset=UTF-8");
           multipart.addBodyPart(contentPart);
           
           // 添加附件的内容
           if (attachment != null) {
            //多附件上传
            //File[] f=attachment;
            for(int i=0;i<attachment.size();i++){
            File uploadFile=(File) attachment.get(i);
           
            BodyPart attachmentBodyPart = new MimeBodyPart();
                    DataSource source = new FileDataSource(uploadFile);
                    attachmentBodyPart.setDataHandler(new DataHandler(source));
                    
                    // 解决文件名乱码的方法,其实用MimeUtility.encodeWord就可以很方便的搞定
                    // 这里很重要,通过下面的Base64编码的转换可以保证你的中文附件标题名在发送时不会变成乱码
                   //sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
                   //attachmentBodyPart.setFileName("=?GBK?B?" + enc.encode(uploadFile.getName().getBytes()) + "?=");
                    
                    //MimeUtility.encodeWord可以避免文件名乱码
                    attachmentBodyPart.setFileName(MimeUtility.encodeText(uploadFile.getName()));
                    multipart.addBodyPart(attachmentBodyPart);
                  //  multipart.addBodyPart(attachmentBodyPart);
            }
           }
           
           // 将multipart对象放到message中
           message.setContent(multipart);

// Set the content
message.setSentDate(new Date());

// 保存邮件
           message.saveChanges();


// Send message
transport.sendMessage(message, message.getAllRecipients());
//return 0;// 成功
//发送成功修改数据状态为已发送。

System.out.println("发送成功。。");
} catch (NoSuchProviderException e) {
//log.error("邮箱校验错误:", e);
e.printStackTrace();
//return 1;
} catch (AuthenticationFailedException e) {
//log.error("邮箱校验错误:", e);
e.printStackTrace();
//return 2;
} catch (SendFailedException e) {
Address[] address = e.getInvalidAddresses();
if(address!=null){
int invalidAddress = e.getInvalidAddresses().length;
if(invalidAddress > 0){ // 有错误地址导致发送失败
sendFailMail(mailAccount, message, e);
//发送失败后,设置数据库result表发送失败。
/*for(Address feiledMail:address){
//修改发送状态
// modifySendState( mailAccount, mailresource,feiledMail,"邮箱不存在,发送失败","0");
}*/
}
}
//log.error("邮箱校验错误:", e);
e.printStackTrace();
return 3;
} catch (MessagingException e) {// 其他邮件错误
//log.error("邮箱校验错误:", e);
e.printStackTrace();
//return flag;
} catch (Exception e) {
//log.error("邮箱校验错误:", e);
e.printStackTrace();
return 6;
} finally {
if (transport != null) {
try {
transport.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

return 0;
}




/***
* 邮件发送失败修改发送状态
* @param mailAccount
* @param mailresource
* @param feiledMail
*/
private void modifySendState(MailAccount mailAccount,
MailList mailresource, Address feiledMail,String info,String state) {
IMailListService mailListService =(IMailListService) BeanTool.getBean( "mailListService" );

Map<Object,Object> map=new HashMap<Object, Object>();
//邮件id
map.put("resourceID", mailresource.getMId());
//邮件接收者
map.put("receiveMailId", feiledMail);
//邮件发送状态,0失败;1成功
map.put("state", state);
//提示内容
map.put("info", info);
//修改邮箱状态
//mailListService.updateStateByResourceId(map);
}










/** 
     * 将列表中的字符串转换成InternetAddress对象 
     * @param list 邮件字符串地址列表 
     * @return InternetAddress对象数组 
     */  
    private InternetAddress[] InternetAddresses(Object[] list) {  
        if (list == null || list.length<0) {  
            return null;  
        }  
        int size = list.length;  
        InternetAddress[] arr = new InternetAddress[size];  
        for (int i = 0; i < size; i++) {  
            try {  
                arr[i] = new InternetAddress(list[i].toString());  
            } catch (AddressException e) {  
                e.printStackTrace();  
            }  
        }  
        return arr;  
    }  
    
    
 // 发送失败后,给用户发送提示邮件
  public static void sendFailMail(MailAccount mailAccount, Message message, String folderTitle, String info){
  Transport transport = null;
  String userName = mailAccount.getName();
  String sendTo = mailAccount.getEmail();
  try {
  // -----------------提示邮件内容------开始-------------------
  StringBuffer sb = new StringBuffer("");
  sb.append("您从").append(folderTitle).append("发送的邮件:<br><br>");
  if(message!=null){
  sb.append(getOriginalMessageContent(message, userName));
  } else {
  sb.append("没有发送成功<br>");
  }
  sb.append("发送失败的原因是:").append(info).append("<br><br>");
  // -----------------提示邮件内容------结束-------------------
  //mailAccount superAccount = new QuerySuperAccountMail();
  Properties properties = new Properties();
  properties.put("mail.smtp.host", mailAccount.getSendPort());
  properties.put("mail.smtp.auth", "true");
  Session session = Session.getInstance(properties, null);
  transport = session.getTransport(protocol2);
  transport.connect(null, mailAccount.getEmail(),
  mailAccount.getPwd());
  MimeMessage newMessage = new MimeMessage(session);
  String sendFrom = mailAccount.getEmail();
  if(sendFrom==null){
  sendFrom = "";
  }
  getNewMessage(sb, message, newMessage, sendFrom, sendTo);
  // Send newMessage
  transport.sendMessage(newMessage, newMessage.getAllRecipients());
  } catch (NoSuchProviderException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (AuthenticationFailedException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (SendFailedException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (MessagingException e1) { // 其他邮件错误
  log.error("邮箱校验错误:", e1);
  } catch (Exception e1) {
  log.error("邮箱校验错误:", e1);
  } finally {
  if (transport != null) {
  try {
  transport.close();
  } catch (Exception e1) {
  }
  }
  }
  }
 
  // 群发时,有错误地址导致发送失败,调用此方法给用户发送一封提示邮件
  private static void sendFailMail(MailAccount mailAccount,
  Message message, SendFailedException e) {
  int invalidAddressNum = 0;
  int validSentAddressNum = 0;
  int validUnsentAddressNum = 0;
  if(e.getInvalidAddresses()!=null){
  invalidAddressNum = e.getInvalidAddresses().length;
  }
  if(e.getValidSentAddresses()!=null){
  validSentAddressNum = e.getValidSentAddresses().length;
  }
  if(e.getValidUnsentAddresses()!=null){
  validUnsentAddressNum = e.getValidUnsentAddresses().length;
  }
  String userName = mailAccount.getName();
  String sendTo = mailAccount.getEmail();
  Transport transport = null;
  try {
  // -----------------提示邮件内容------开始-------------------
  StringBuffer sb = new StringBuffer("您发送的邮件:<br><br>");
  sb.append(getOriginalMessageContent(message, userName));
  sb.append("发送失败的原因是:某些收件人或抄送人地址有错误<br><br>");
  sb.append("其中: <br>");
  sb.append("错误的地址数为:").append(invalidAddressNum).append("<br>");
  if(invalidAddressNum > 0){
  sb.append("错误地址如下:").append(getAddresses(e.getInvalidAddresses())).append("<br>");
  }
  sb.append("正确且送达的地址数为:").append(validSentAddressNum).append("<br>");
  sb.append("正确但未送达的地址数为:").append(validUnsentAddressNum).append("<br>");
  if(validUnsentAddressNum > 0){ // 有未送达的正确地址
  sb.append("正确但未送达的地址如下: ").append(getAddresses(e.getValidUnsentAddresses())).append("<br>");
  }
  // -----------------提示邮件内容------结束-------------------
  //QuerySuperAccountMail superAccount = new QuerySuperAccountMail();
  Properties properties = new Properties();
  properties.put("mail.smtp.host", mailAccount.getSendPort());
  properties.put("mail.smtp.auth", "true");
  Session session = Session.getInstance(properties, null);
  transport = session.getTransport(protocol2);
  transport.connect(null, mailAccount.getEmail(),
  mailAccount.getEmail());
  MimeMessage newMessage = new MimeMessage(session);
  String sendFrom = mailAccount.getEmail();
  if(sendFrom==null){
  sendFrom = "";
  }
  getNewMessage(sb, message, newMessage, sendFrom, sendTo);
  // Send newMessage
  transport.sendMessage(newMessage, newMessage.getAllRecipients());
  } catch (NoSuchProviderException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (AuthenticationFailedException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (SendFailedException e1) {
  log.error("邮箱校验错误:", e1);
  } catch (MessagingException e1) { // 其他邮件错误
  log.error("邮箱校验错误:", e1);
  } catch (Exception e1) {
  log.error("邮箱校验错误:", e1);
  } finally {
  if (transport != null) {
  try {
  transport.close();
  } catch (Exception e1) {
  }
  }
  }
  }
 
  private static String getOriginalMessageContent(Message message,
  String userName) throws MessagingException {
  StringBuffer sb = new StringBuffer("------------Original Message------------<br>");
  sb.append("  发件人:   ").append(userName).append("<br>");
  Address[] sendToAddr = message.getRecipients(Message.RecipientType.TO);
  Address[] sendCcAddr = message.getRecipients(Message.RecipientType.CC);
  if (sendToAddr != null) {
  // 如果地址不多,则列出收件人
  if (sendToAddr.length < 20) {
  sb.append("  收件人:   ").append(getAddresses(sendToAddr)).append("<br>");
  } else {
  sb.append("  收件人:   ").append("收件人较多,此处略").append("<br>");
  }
  }
  if (sendCcAddr != null) {
  // 如果地址不多,则列出抄送人
  if (sendCcAddr.length < 20) {
  sb.append("  抄送人:   ").append(getAddresses(sendCcAddr)).append("<br>");
  } else {
  sb.append("  抄送人:   ").append("抄送人较多,此处略").append("<br>");
  }
  }
  sb.append("  主题:     ").append(message.getSubject()).append("<br>");
  sb.append("  发送时间: ").append(parseDate(message)).append("<br>");
  sb.append("------------Original Message------------<br><br>");
  sb.append("没有发送成功!原邮件副本见附件<br>");
  return sb.toString();
  }
 
 


 
  // 从传入的Address数组里,取出所有地址
  private static String getAddresses(Address[] addresses){
  if(addresses==null){
  return "";
  }
  int len = addresses.length;
  StringBuffer sb = new StringBuffer("");
  for(int i=0; i<len; i++){
  InternetAddress addr = (InternetAddress)addresses[i];
  sb.append(addr.getAddress()).append(";");
  }
  return sb.toString();
  }
 
  private static String parseDate(Message message) throws MessagingException{
  String sendTime = "";
  Date date = message.getSentDate();
  if(date!=null){
  // 格式化日期时间
  sendTime = DateFormat.getDateTimeInstance().format(date);
  }
  if (sendTime.equals("")) {
  // 取得邮件生成日期时间
  String[] createDateArr = message.getHeader("Date");
  if (createDateArr != null && createDateArr.length > 0) {
  sendTime = createDateArr[0];
  }
  }
  return sendTime;
  }
 
  private static void getNewMessage(StringBuffer sb, Message message, MimeMessage newMessage, String sendFrom, String sendTo)
throws MessagingException, IOException {
// 在这儿可以指定发送人姓名的编码方式,默认为GBK
newMessage.setFrom(new InternetAddress(sendFrom, "系统管理员", "GBK"));
if (!sendTo.equalsIgnoreCase("")) {
newMessage.addRecipients(Message.RecipientType.TO, sendTo);
}
// 在这儿可以指定邮件主题的编码方式,默认为GBK
if(message==null){
newMessage.setSubject("未送达:原邮件不存在", "GBK");
} else {
newMessage.setSubject("未送达:" + message.getSubject(), "GBK");
}
// 优先级设为普通级
newMessage.addHeader("X-Priority", "3");
Multipart mp = new MimeMultipart();
// 提示邮件分两部分,第一部分是正文内容,第二部分是附件(把原邮件作为附件)
int bodyPartLength = 2;
if(message==null){
// 原邮件不存在,无法作为附件,所以提示邮件bodyPatr长度为1
bodyPartLength = 1;
}
MimeBodyPart amimebodypart[] = new MimeBodyPart[bodyPartLength];
for (int j = 0; j < bodyPartLength; j++)
amimebodypart[j] = new MimeBodyPart();
amimebodypart[0].setDataHandler(new DataHandler(
new ByteArrayDataSource(sb.toString(),
"text/html; charset=GBK")));
mp.addBodyPart(amimebodypart[0]);
if(message!=null){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
message.writeTo(bos);
amimebodypart[1].setDataHandler(new DataHandler(
new ByteArrayDataSource(bos.toByteArray(), "message/rfc822; charset=GBK")));
amimebodypart[1].setFileName(MimeUtility.encodeWord(message.getSubject()+".eml"));
mp.addBodyPart(amimebodypart[1]);
}
// Set the content
newMessage.setContent(mp);
newMessage.setSentDate(new Date());
newMessage.saveChanges();
}


public static void main(String[] args) {
/*OaMailAccount occ=new OaMailAccount();
occ.setSmtpServer("smtp.qq.com");
occ.setMailUserId("2364106@qq.com");
occ.setMailPassword("kmfiacrytthdbjfb");
occ.setMailAddress("2364106@qq.com");
occ.setMailUserName("奥迪中国");
occ.setIsSendSsl("1");
occ.setSendPort("587");
occ.setSmtpServer("121.8.125.74");
occ.setMailUserId("hrtest888@redsea888.com");
occ.setMailPassword("hrtest666");
occ.setMailAddress("hrtest888@redsea888.com");
occ.setMailUserName("朱锦全");
occ.setIsSendSsl("1");
occ.setSendPort("25");

OaMailResource cr=new OaMailResource();
cr.setResTo("hrtest888@hr-soft.cn");
cr.setResCc("");
cr.setResBcc("");
cr.setResTitle("发ssl邮件并且带附件");
cr.setResContent("发ssl邮件并且带附件内容");
cr.setIsReturnReceipt("0");

File file=new File("D:\\E\\JEECMS简介.docx");

List<File> fileList=new ArrayList<File>();

//fileList.add(file);
System.out.println("开始发送。");
//发送
sendMail(occ, cr, fileList);
System.out.println("发送完成。");*/

MailAccount occ=new MailAccount();
occ.setSmtpServer("smtp.163.com");
occ.setEmail("18026214961@163.com");
occ.setPwd("123abc");
occ.setName("奥迪中国");
occ.setIsSendSsl("1");
occ.setSendPort("587");

MailList cr=new MailList();
cr.setMRev("zhujq201191@126.com");
cr.setMCc("");
cr.setMBcc("");
cr.setMTitle("main方法测试002");
cr.setMContent("main方法测试002");
cr.setIsReturnReceipt("0");

//File file=new File("E:\\mail\\3a劳务派遣企业等级评价申请表.doc");

List<File> fileList=new ArrayList<File>();

//fileList.add(file);
MailSendTool sendTool=new MailSendTool();
//发送
sendTool.sendMail( occ,  cr,  fileList);
//sendMail();
System.out.println("发送完成。");
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值