当前位置:   article > 正文

Java-发送邮件(附件、图片)---(四)类-MimeMultipart、MimeBodyPart、FileDataSource、DataHandler、_java mimemultipart

java mimemultipart

1:MimeMultipart和MimeBodyPart
A、简介
Message表示一个邮件,messgaes.getContent()返回一个Multipart对象。一个Multipart对象包含一个或多个BodyPart对象,来组成邮件的正文部分(包括附件)。

B、Multipart
javax.mail.Multipart
public abstract class Multipart
Multipart是一个容器它转载多个body Part(正文、附件或内嵌资源)。Part的getContent()方法就返回一个Multipart对象。
javax.mail.internet.MimeMultipart
public class MimeMultipart extends Multipart
MimeMultipart是Multipart的实现类,默认类别是mixed。其他multipart子类型如:related和alternative可以通过new MimeMultipart(“alternative”);来实现。

B.0、Multipart的content-type
总体来说,MIME消息由消息头和消息体两大部分组成。现在我们关注的是MIME邮件,因此在以下的讨论中姑且称“消息”为“邮件”。
邮件头包含了发件人、收件人、主题、时间、MIME版本、邮件内容的类型等重要信息。每条信息称为一个域,由域名后加“: ”和信息内容构成,可以是一行,较长的也可以占用多行。域的首行必须“顶头”写,即左边不能有空白字符(空格和制表符);续行则必须以空白字符打头,且第一个空白字符不是信息本身固有的,解码时要过滤掉。
邮件体包含邮件的内容,它的类型由邮件头的“Content-Type”域指出。常见的简单类型有text/plain(纯文本)和text/html(超文本)。有时也会出现的multipart类型,是MIME邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。常见的multipart类型有三种:multipart/mixed, multipart/related和multipart/alternative。
multipart/mixed:附件。
multipart/related:内嵌资源。
multipart/alternative:纯文本与超文本共存。
可以看出,如果在邮件中要添加附件,必须定义multipart/mixed段;如果存在内嵌资源,至少要定义multipart/related段;如果纯文本与超文本共存,至少要定义multipart/alternative段。什么是“至少”?举个例子说,如果只有纯文本与超文本正文,那么在邮件头中将类型扩大化,定义为multipart/related,甚至multipart/mixed,都是允许的。
multipart诸类型的共同特征是,在段头指定“boundary”参数字符串,段体内的每个子段以此串定界。所有的子段都以–=”boundary行”开始,父段则以–=”boundary行” –结束。段与段之间也以空行分隔。
前文,在邮件体是multipart类型的情况下,邮件体的开始部分(第一个–=_”boundary行”之前)可以有一些附加的文本行,相当于注释,解码时应忽略。段间也可以有一些附加的文本行,不会显示出来,如果有兴趣,不妨验证一下。

B.1、javax.mail.Multipart
属性
protected String contentType
This field specifies the content-type of this multipart object.
返回Multipart的content-type类型。类型包括”alternative”, “mixed”, “related”, “parallel”, “signed”等。
protected Part parent
The Part containing this Multipart, if known.
父Part,一般是message。
protected Vector parts
Vector of BodyPart objects.
方法
1.操作BodyPart
void addBodyPart(BodyPart part)
Adds a Part to the multipart.
void addBodyPart(BodyPart part, int index)
Adds a BodyPart at position index.
BodyPart getBodyPart(int index)
Get the specified Part.
boolean removeBodyPart(BodyPart part)
Remove the specified part from the multipart message.
void removeBodyPart(int index)
Remove the part at specified location (starting from 0).
2.操作父Part
Part getParent()
Return the Part that contains this Multipart object, or null if not known.
void setParent(Part parent)
Set the parent of this Multipart to be the specified Part.
3.取得content-type
String getContentType()
Return the content-type of this Multipart.
4.取得BodyPart数量
int getCount()
Return the number of enclosed BodyPart objects.

B.2、javax.mail.internet.MimeMultipart
属性
protected DataSource ds
The DataSource supplying our InputStream.
protected boolean parsed
Have we parsed the data from our InputStream yet? Defaults to true; set to false when our constructor is given a DataSource with an InputStream that we need to parse.
构造函数
MimeMultipart()
Default constructor.
MimeMultipart(DataSource ds)
Constructs a MimeMultipart object and its bodyparts from the given DataSource.
MimeMultipart(String subtype)
Construct a MimeMultipart object of the given subtype.
创建一个指定子类型的MimeMultipart对象。默认为mixed,你可设置related或alternative等。
方法
1. 操作BodyPart
void addBodyPart(BodyPart part)
Adds a Part to the multipart.
void addBodyPart(BodyPart part, int index)
Adds a BodyPart at position index.
BodyPart getBodyPart(int index)
Get the specified BodyPart.
BodyPart getBodyPart(String CID)
Get the MimeBodyPart referred to by the given ContentID (CID).
void removeBodyPart(int index)
Remove the part at specified location (starting from 0).
2. 操作前文
String getPreamble()
Get the preamble text, if any, that appears before the first body part of this multipart.
void setPreamble(String preamble)
Set the preamble text to be included before the first body part.
3.设置子类型
void setSubType(String subtype)
Set the subtype.
4.
boolean isComplete()
Return true if the final boundary line for this multipart was seen.

C、BodyPart
javax.mail.Part
public interface Part
javax.mail.internet. MimePart
public interface MimePart extends Part
javax.mail.BodyPart
public abstract class BodyPart implements Part
BodyPart是一个包含在Multipart中的Part。它是一个Part也包含attribute和content。
javax.mail.internet.MimeBodyPart
public class MimeBodyPart extends BodyPart implements MimePart
MimeBodyPart是BodyPart的实现类。
javax.mail.internet.PreencodedMimeBodyPart
public class PreencodedMimeBodyPart extends MimeBodyPart

C.1、javax.mail.BodyPart
属性
Multipart parent
The Multipart object containing this BodyPart, if known.
这是和message的区别所在,虽然都实现Part接口,但BodyPart是包含在Multipart中的。它的parent是(Multipart)message.getContent()。

C.2、javax.mail.internet.MimeBodyPart
属性
protected byte[] content
Byte array that holds the bytes of the content of this Part.
一个字节数组存放BodyPart内容的字节流。
protected InputStream contentStream
If the data for this body part was supplied by an InputStream that implements the SharedInputStream interface, contentStream is another such stream representing the content of this body part.
若BodyPart的数据提供一个InputStream,contentStream是另一种流代表BodyPart的内容。
protected DataHandler dh
The DataHandler object representing this Part’s content.
代表BodyPart内容的DataHandler对象。
protected InternetHeaders headers
The InternetHeaders object that stores all the headers of this body part.
InternetHeaders 对象存放所有BodyPart的标题。
构造函数
MimeBodyPart()
An empty MimeBodyPart object is created.
创建一个空的MimeBodyPart对象。
MimeBodyPart(InputStream is)
Constructs a MimeBodyPart by reading and parsing the data from the specified input stream.
MimeBodyPart(InternetHeaders headers, byte[] content)
Constructs a MimeBodyPart using the given header and content bytes.
方法
1.操作附件
DataHandler
DataHandler getDataHandler()
Return a DataHandler for this body part’s content.
void setDataHandler(DataHandler dh)
This method provides the mechanism to set this body part’s content.
2.操作附件名
void setFileName(String filename)
Set the filename associated with this body part, if possible.
设置了附件名称则会在邮件BodyPart头中增加一行表示附件的代码Content-Disposition: attachment; filename=xxxx.xls
String getFileName()
Get the filename associated with this body part.
获取Content-Disposition: attachment; filename=xxxx.xls中的filename。
3.操作正文
void setText(String text)
Convenience method that sets the given String as this part’s content, with a MIME type of “text/plain”.
void setText(String text, String charset)
Convenience method that sets the given String as this part’s content, with a MIME type of “text/plain” and the specified charset.
void setText(String text, String charset, String subtype)
Convenience method that sets the given String as this part’s content, with a primary MIME type of “text” and the specified MIME subtype.
以上三个方法都是用于设置MimeBodyPart的内容(文本内容)的,不同之处在于是否设置指定的字符或指定的MIME类型。

2、FileDataSource
public class FileDataSource
extends Object
implements DataSource
FileDataSource 类实现一个封装文件的简单 DataSource 对象。它通过 FileTypeMap 对象提供数据分类服务。

FileDataSource 分类语义

FileDataSource 类将文件的数据分类委托给一个从 FileTypeMap 类子类化的对象。setFileTypeMap 方法可用于为 FileDataSource 实例显示地设置 FileTypeMap。如果没有设置任何 FileTypeMap,则 FileDataSource 将调用 FileTypeMap 的 getDefaultFileTypeMap 方法获取系统的默认 FileTypeMap。

构造方法摘要
FileDataSource(File file)
根据 File 对象创建 FileDataSource。
FileDataSource(String name)
根据指定的路径名创建 FileDataSource。

方法摘要
String getContentType()
此方法以字符串形式返回数据的 MIME 类型。
File getFile()
返回与此 FileDataSource 对应的 File 对象。
InputStream getInputStream()
此方法将返回一个表示数据的 InputStream;如果不能返回,则抛出 IOException。
String getName()
返回此对象的名称。
OutputStream getOutputStream()
此方法将返回一个表示数据的 OutputStream;如果不能返回,则抛出 IOException。
void setFileTypeMap(FileTypeMap map)
设置用于此 FileDataSource 的 FileTypeMap。
构造方法详细信息
FileDataSource
public FileDataSource(File file)
根据 File 对象创建 FileDataSource。注:在调用要求文件打开的方法之前,文件实际上不会打开。
参数:
file - 文件
FileDataSource
public FileDataSource(String name)
根据指定的路径名创建 FileDataSource。注:在调用要求文件打开的方法之前,文件实际上不会打开。
参数:
name - 与系统有关的文件名。
方法详细信息
getInputStream
public InputStream getInputStream()
throws IOException
此方法将返回一个表示数据的 InputStream;如果不能返回,则抛出 IOException。每次调用此方法都会返回一个新的 InputStream 实例。
指定者:
接口 DataSource 中的 getInputStream
返回:
一个 InputStream
抛出:
IOException
getOutputStream
public OutputStream getOutputStream()
throws IOException
此方法将返回一个表示数据的 OutputStream;如果不能返回,则抛出 IOException。每次调用此方法都会返回一个新的 OutputStream 实例。
指定者:
接口 DataSource 中的 getOutputStream
返回:
一个 OutputStream
抛出:
IOException
getContentType
public String getContentType()
此方法以字符串形式返回数据的 MIME 类型。此方法使用当前安装的 FileTypeMap。如果没有显示地设置任何 FileTypeMap,FileDataSource 将对 FileTypeMap 调用 getDefaultFileTypeMap 方法获取默认的 FileTypeMap。注:默认情况下,使用的 FileTypeMap 是 MimetypesFileTypeMap。
指定者:
接口 DataSource 中的 getContentType
返回:
MIME 类型
另请参见:
FileTypeMap.getDefaultFileTypeMap()
getName
public String getName()
返回此对象的名称。FileDataSource 将返回对象的文件名。
指定者:
接口 DataSource 中的 getName
返回:
对象的名称。
另请参见:
DataSource
getFile
public File getFile()
返回与此 FileDataSource 对应的 File 对象。
返回:
此对象表示的文件的 File 对象。
setFileTypeMap
public void setFileTypeMap(FileTypeMap map)
设置用于此 FileDataSource 的 FileTypeMap。
参数:
map - 此对象的 FileTypeMap。

3、DataHandler
javax.activation
类 DataHandler
java.lang.Object
继承者 javax.activation.DataHandler
所有已实现的接口:
Transferable
public class DataHandler
extends Object
implements Transferable
DataHandler 类为在多种不同源和格式下可用的数据提供一致的接口。它使用 DataContentHandler 管理简单流到字符串的转换以及相关操作。它提供对能够操作数据的命令的访问。使用 CommandMap 可以找到这些命令。

DataHandler 和 Transferable 接口

DataHandler 实现 Transferable 接口,以便数据能够用于 AWT 数据传输操作,比如,剪切、粘贴和拖放操作。Transferable 接口的实现依赖于已安装的 DataContentHandler 对象的可用性,该 DataContentHandler 对象与 DataHandler 的特定实例中所表示的数据的 MIME 类型相对应。

DataHandler 和 CommandMap

DataHandler 跟踪当前 CommandMap,它用于命令(getCommand、getAllCommands、getPreferredCommands)的服务请求。使用 setCommandMap 方法,DataHandler 的每个实例都可以有一个与其相关的 CommandMap。如果没有设置 CommandMap,则 DataHandler 调用 CommandMap 中的 getDefaultCommandMap 方法,并使用其返回值。有关更多信息,请参见 CommandMap。

DataHandler 和 URL

当用 URL 构造 DataHandler 时,当前 DataHandler 实现创建一个 URLDataSource 的私有实例。

从以下版本开始:
1.6
另请参见:
CommandMap, DataContentHandler, DataSource, URLDataSource
构造方法摘要
DataHandler(DataSource ds)
创建引用指定 DataSource 的 DataHandler 实例。
DataHandler(Object obj, String mimeType)
创建表示此 MIME 类型对象的 DataHandler 实例。
DataHandler(URL url)
创建引用 URL 的 DataHandler 实例。

方法摘要
CommandInfo[] getAllCommands()
返回此数据类型的所有命令。
Object getBean(CommandInfo cmdinfo)
一个便捷方法,它接受 CommandInfo 对象并且实例化相应的命令,通常是 JavaBean 组件。
CommandInfo getCommand(String cmdName)
获取命令 cmdName。
Object getContent()
以其首选 Object 的形式返回数据。
String getContentType()
返回从源对象中获取的此对象的 MIME 类型。
DataSource getDataSource()
返回与此 DataHandler 实例关联的 DataSource。
InputStream getInputStream()
获取此对象的 InputSteam。
String getName()
返回数据对象的名称。
OutputStream getOutputStream()
获取此 DataHandler 的 OutputStream,以允许重写底层数据。
CommandInfo[] getPreferredCommands()
返回此数据类型的首选 命令。
Object getTransferData(DataFlavor flavor)
返回一个对象,该对象表示要传输的数据。
DataFlavor[] getTransferDataFlavors()
返回此数据在其中可用的 DataFlavor。
boolean isDataFlavorSupported(DataFlavor flavor)
返回此对象是否支持指定的数据 flavor。
void setCommandMap(CommandMap commandMap)
设置此 DataHandler 使用的 CommandMap。
static void setDataContentHandlerFactory(DataContentHandlerFactory newFactory)
设置 DataContentHandlerFactory。
void writeTo(OutputStream os)
将数据写入 OutputStream。

从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

构造方法详细信息
DataHandler
public DataHandler(DataSource ds)
创建引用指定 DataSource 的 DataHandler 实例。数据以字节流的形式存在。DataSource 将提供一个 InputStream 来访问数据。
参数:
ds - DataSource
DataHandler
public DataHandler(Object obj,
String mimeType)
创建表示此 MIME 类型对象的 DataHandler 实例。当应用程序已经有 Java Object 形式的数据内存表示形式 (in-memory representation) 时,使用此构造方法。
参数:
obj - Java Object
mimeType - 对象的 MIME 类型
DataHandler
public DataHandler(URL url)
创建引用 URL 的 DataHandler 实例。DataHandler 在内部创建一个 URLDataSource 实例来表示 URL。
参数:
url - URL 对象
方法详细信息
getDataSource
public DataSource getDataSource()
返回与此 DataHandler 实例关联的 DataSource。
对于已经用 DataSource 实例化的 DataHandler,此方法返回用来创建该 DataHandler 对象的 DataSource。在其他情况下,DataHandler 根据用来构造 DataHandler 的数据构造 DataSource。对于没有用 DataSource 实例化的 DataHandler,为其创建的 DataSource 将被缓存,以提高性能。

返回:
对此 DataHandler 有效的 DataSource 对象
getName
public String getName()
返回数据对象的名称。如果此 DataHandler 是用 DataSource 创建的,则此方法直接调用 DataSource.getName 方法;否则返回 null。
返回:
对象的名称
getContentType
public String getContentType()
返回从源对象中获取的此对象的 MIME 类型。注意,这是带有参数的完整 类型。
返回:
MIME 类型
getInputStream
public InputStream getInputStream()
throws IOException
获取此对象的 InputSteam。
对于用 DataSource 实例化的 DataHandler,该 DataHandler 调用 DataSource.getInputStream 方法,并将结果返回给调用者。

对于用 Object 实例化的 DataHandler,该 DataHandler 首先尝试找到 Object 的 DataContentHandler。如果 DataHandler 找不到此 MIME 类型的 DataContentHandler,则抛出 UnsupportedDataTypeException。如果能够找到,则它将创建一个管道和一个线程。该线程使用 DataContentHandler 的 writeTo 方法将流数据写入管道的一端。管道的另一端返回给调用者。由于为复制数据创建了一个线程,所以在复制过程中发生的 IOException 不能传送回调用者。结果将是一个空流。

返回:
表示此数据的 InputStream
抛出:
IOException - 如果发生 I/O 错误
另请参见:
DataContentHandler.writeTo(java.lang.Object, java.lang.String, java.io.OutputStream), UnsupportedDataTypeException
writeTo
public void writeTo(OutputStream os)
throws IOException
将数据写入 OutputStream。
如果 DataHandler 是用 DataSource 创建的,那么 writeTo 获取 InputStream 并将字节从 Inputstream 复制到传入的 Outputstream。

如果 DataHandler 是用 Object 创建的,则 writeTo 获取对象类型的 DataContentHandler。如果找到了 DataContentHandler,则它对 DataContentHandler 调用 writeTo 方法。

参数:
os - 要写入的 OutputStream
抛出:
IOException - 如果发生 I/O 错误
getOutputStream
public OutputStream getOutputStream()
throws IOException
获取此 DataHandler 的 OutputStream,以允许重写底层数据。如果 DataHandler 是用 DataSource 创建的,则调用 DataSource 的 getOutputStream 方法。否则,返回 null。
返回:
OutputStream
抛出:
IOException
另请参见:
DataSource.getOutputStream(), URLDataSource
getTransferDataFlavors
public DataFlavor[] getTransferDataFlavors()
返回此数据在其中可用的 DataFlavor。
返回能够提供数据的 flavor 的 DataFlavor 对象所组成的数组。该数组通常根据提供数据的首选项来排序(从描述最多的到描述最少的)。

DataHandler 尝试查找与数据的 MIME 类型对应的 DataContentHandler。如果找到,则该 DataHandler 调用 DataContentHandler 的 getTransferDataFlavors 方法。

如果不 能找到 DataContentHandler,并且 DataHandler 是用 DataSource (或 URL)创建的,则返回一个表示此对象 MIME 类型和 java.io.InputStream 类的 DataFlavor。如果 DataHandler 是用对象和 MIME 类型创建的,则 getTransferDataFlavors 返回一个表示此对象 MIME 类型和对象类的 DataFlavor。

指定者:
接口 Transferable 中的 getTransferDataFlavors
返回:
其中可以传输此数据的数据 flavor 所组成的数组
另请参见:
DataContentHandler.getTransferDataFlavors()
isDataFlavorSupported
public boolean isDataFlavorSupported(DataFlavor flavor)
返回此对象是否支持指定的数据 flavor。
此方法迭代 getTransferDataFlavors 返回的 DataFlavor,将每个 DataFlavor 与指定 flavor 进行比较。

指定者:
接口 Transferable 中的 isDataFlavorSupported
参数:
flavor - 为数据所请求的 flavor
返回:
如果支持该数据 flavor,则返回 true
另请参见:
getTransferDataFlavors()
getTransferData
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException,
IOException
返回一个对象,该对象表示要传输的数据。返回对象的类是由该数据 flavor 的表示形式类定义的。
对于用 DataSource 或 URL 创建的 DataHandler:

DataHandler 尝试为此 MIME 类型查找一个 DataContentHandler。如果找到,则将传入的 DataFlavor 和数据的类型传递给其 getTransferData 方法。如果 DataHandler 没有找到 DataContentHandler,并且 flavor 指定了此对象的 MIME 类型和 java.io.InputStream 类,则返回此对象的 InputStream。否则,抛出 UnsupportedFlavorException。

对于用 Object 创建的 DataHandler:

DataHandler 尝试为此 MIME 类型查找一个 DataContentHandler。如果找到,则将传入的 DataFlavor 和数据的类型传递给其 getTransferData 方法。如果 DataHandler 没有找到 DataContentHandler,并且 flavor 指定了此对象的 MIME 类型和它的类,则返回此 DataHandler 的引用对象。否则,抛出 UnsupportedFlavorException。

指定者:
接口 Transferable 中的 getTransferData
参数:
flavor - 为数据所请求的 flavor
返回:
对象
抛出:
UnsupportedFlavorException - 如果不能将该数据转换为所请求的 flavor
IOException - 如果发生 I/O 错误
另请参见:
ActivationDataFlavor
setCommandMap
public void setCommandMap(CommandMap commandMap)
设置此 DataHandler 使用的 CommandMap。将其设置为 null 会使该 CommandMap 还原为 CommandMap.getDefaultCommandMap 方法所返回的 CommandMap。更改该 CommandMap 或将其设置为 null 将清除以前的 CommandMap 所缓存的所有数据。
参数:
commandMap - 此 DataHandler 中使用的 CommandMap
另请参见:
CommandMap.setDefaultCommandMap(javax.activation.CommandMap)
getPreferredCommands
public CommandInfo[] getPreferredCommands()
返回此数据类型的首选 命令。此方法调用 CommandMap 中与此 DataHandler 实例关联的 getPreferredCommands 方法。此方法返回一个表示可用命令子集的数组。如果存在由此 DataHandler 表示的 MIME 类型的多个命令,则已安装的 CommandMap 将选择适当的命令。
返回:
表示首选命令的 CommandInfo 对象
另请参见:
CommandMap.getPreferredCommands(java.lang.String)
getAllCommands
public CommandInfo[] getAllCommands()
返回此数据类型的所有命令。此方法返回一个数组,此数组包含此 DataHandler 所表示的数据类型的所有命令。此 DataHandler 所表示的底层数据的 MIME 类型用于调用与此 DataHandler 关联的 CommandMap 的 getAllCommands 方法。
返回:
表示所有命令的 CommandInfo 对象
另请参见:
CommandMap.getAllCommands(java.lang.String)
getCommand
public CommandInfo getCommand(String cmdName)
获取命令 cmdName。使用此 DataHandler 中已安装的 CommandMap 所定义的搜索语义。此 DataHandler 所表示的底层数据的 MIME 类型用于调用与此 DataHandler 关联的 CommandMap 的 getCommand 方法。
参数:
cmdName - 命令名
返回:
与命令对应的 CommandInfo
另请参见:
CommandMap.getCommand(java.lang.String, java.lang.String)
getContent
public Object getContent()
throws IOException
以其首选 Object 的形式返回数据。
如果 DataHandler 是用一个对象实例化的,则返回该对象。

如果 DataHandler 是用 DataSource 实例化的,则此方法使用 DataContentHandler 返回此 DataHandler 所表示数据的内容对象。如果找不到此数据类型的 DataContentHandler,则 DataHandler 返回数据的 InputStream。

返回:
内容。
抛出:
IOException - 如果此操作中发生 IOException。
getBean
public Object getBean(CommandInfo cmdinfo)
一个便捷方法,它接受 CommandInfo 对象并且实例化相应的命令,通常是 JavaBean 组件。
此方法调用 CommandInfo 的 getCommandObject 方法,带有用于加载 javax.activation.DataHandler 类本身的 ClassLoader。

参数:
cmdinfo - 与命令对应的 CommandInfo
返回:
已实例化的命令对象
setDataContentHandlerFactory
public static void setDataContentHandlerFactory(DataContentHandlerFactory newFactory)
设置 DataContentHandlerFactory。首先调用 DataContentHandlerFactory 来查找 DataContentHandler。DataContentHandlerFactory 只能被设置一次。
如果 DataContentHandlerFactory 已经被设置,则此方法抛出 Error。

参数:
newFactory - DataContentHandlerFactory
抛出:
Error - 如果工厂已经被定义。
另请参见:
DataContentHandlerFactory

http://www.apihome.cn/api/java/DataHandler.html

http://www.apihome.cn/api/java/FileDataSource.html

http://blog.itpub.net/15182208/viewspace-730172/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/591873
推荐阅读
相关标签
  

闽ICP备14008679号