Struts2实现单文件上传,多文件上传与下载(十)

勿以恶小而为之,勿以善小而不为--------------------------刘备

劝诸君,多行善事积福报,莫作恶

上一章简单介绍了Struts2实现国际化操作及中英文切换(九)如果没有看过,请观看上一章

上传文件和下载文件的重要性就不说了,常用于上传文档,更换头像。

Struts2框架本身提供了一个文件上传的拦截器,在默认的拦截器里面。

一.文件上传前的准备

一.一 添加jar包

Struts2上传时,还需要依赖apache的两个jar包

在这里插入图片描述

一.二 注意点

  1. Struts2上传文件时,用<s:file name="upload"> 或者<input type="file" name="upload" />均可。 建议使用 s:file 的元素。
  2. form表单提交时,提交方式为post提交。(避免文件过大,get方式不支持)
  3. form表单提交时,enctype类型需要改变,应该变成:enctype="multipart/form-data"
  4. 文件上传对应的Action进行接收文件时, 对象属性 File Xxx需要与<s:file >中的name相同,
    如<s:file> name为upload,则Action中File相应的为upload.
    <s:file>name为uploadImage,则Action中File为uploadeImage.
  5. Action中取得的File upload.getName() 取得的并不是上传文件的真实名称,只是一个缓存的名称,没有任何的意义。
  6. Action中想取得上传文件的名称和上传类型,需要定义两个String 类型,格式必须是XxxFileName,XxxContentType。其中Xxx为File的值
    XxxFileName 取得文件的名称,XxxContentType 取得文件的类型

二 实现单文件上传

二.一 编写 上传文件表单 /content/single.jsp

<s:form action="File_single.action" namespace="/" method="post" enctype="multipart/form-data">
	用户名: <s:textfield  name="name"/>  <br/>
	密码: <s:password name="password"/><br/>
	上传文件:<s:file name="upload"/><br/>
		<s:submit value="提交"/>
		<s:reset value="重置"/>
</s:form>

需要保证 类型为post 提交,enctype 类型为 multipart/form-data 。

注意,此时 file 元素的名称为upload。

二.二 编写FileAction 进行处理文件接收

FileAction.java

package com.yjl.web.action;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.yjl.pojo.User;

/**
* @author 两个蝴蝶飞
* @version 创建时间:Aug 28, 2018 8:35:56 AM
* 关于文件上传和下载的Action实现
*/
public class FileAction extends ActionSupport implements ModelDriven<User>{
	private static final long serialVersionUID = 1L;
	private Logger logger=Logger.getLogger(FileAction.class);
	//在文件上传时,也同样可以获得其他标签的值.
	private User user=new User();
	@Override
	public User getModel() {
		return user;
	}
	/**
	 * 关于文件上传的具体的操作
	 * @param uploade 与前端表单中file标签的name值相同.
	 * @param XxxFileName 上传文件的名称
	 * @param XxxContentType 上传文件的类型
	 */
	private File upload;
	private String uploadFileName;
	private String uploadContentType;
	//实现它们三个的setter和getter方法
	public File getUpload() {
		return upload;
	}
	public void setUpload(File upload) {
		this.upload = upload;
	}
	public String getUploadFileName() {
		return uploadFileName;
	}
	public void setUploadFileName(String uploadFileName) {
		this.uploadFileName = uploadFileName;
	}
	public String getUploadContentType() {
		return uploadContentType;
	}
	public void setUploadContentType(String uploadContentType) {
		this.uploadContentType = uploadContentType;
	}
	/**
	 *单文件上传操作
	 */
	public String single(){
		//在实际开发中,需要判断一下是否上传了文件,即upload是否为空,不为空,才进行上传的操作。
		logger.info("可以取出user的值:"+user.toString());
		if(upload!=null){
			//1.输出上传的类型
			logger.info("上传文件的名称用File取得:"+this.getUpload().getName());
			logger.info("直接用属性取得名称:"+this.getUploadFileName());
			logger.info("文件上传的类型:"+this.getUploadContentType());
			//2.设置上传文件的放置位置,通常放在服务器下面的upload文件夹下.
			String path=ServletActionContext.getServletContext().getRealPath("/upload");
			//3.这是一个目录,如果这个目录不存在,需要创建这个目录(包括其父目录)
			File srcFile=new File(path);
			if(!srcFile.exists()){
				srcFile.mkdirs();
			}
			//4.通常还需要对上传的文件名进行UUID的操作,使文件名不能重复。这里不做处理
			String fileName=this.getUploadFileName();
			//5.调用FileUtils类执行上传的操作.
			try {
				FileUtils.copyFile(upload,new File(srcFile,fileName));
				logger.info("文件上传成功");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return SUCCESS;
	}
}

二.三 配置struts.xml文件

<struts>
	<!--修改国际化编码 -->
	<constant name="struts.i18n.encoding" value="UTF-8"></constant>
	<!--修改是否为开发者模式 -->
	<constant name="struts.devMode" value="true"></constant>
	<!--修改ui样式表 -->
	<constant name="struts.ui.theme" value="simple"></constant>

	<package name="file" extends="struts-default" namespace="/">
		<action name="File_*" class="com.yjl.web.action.FileAction" method="{1}">
				<result name="success">/WEB-INF/content/success.jsp</result>
		</action>
		<!-- 配置跳转页面 -->
		<action name="*">
			<result>/WEB-INF/content/{1}.jsp</result>
		</action>
	</package>
</struts>

二.四 重启服务器,验证文件上传

输入网址: http://localhost:8080/Struts_File/single,

进行上传文件, 老蝴蝶这儿上传一张照片。

在这里插入图片描述

查看logger输出

在这里插入图片描述

发现file.getName()并没有任何意义。

查看服务器upload文件夹下是否有这张图片:

在这里插入图片描述

存在图片,文件上传操作成功。

二. 五 设置上传时,允许的格式和大小

二.五.一 配置格式 和大小

通过文件上传拦截器,进行配置

在上传的时候,一般需要特别指定文件上传时的格式和大小。 Struts2自带的文件上传拦截器已经帮我们实现了这一点,

我们只需要进行简单的配置即可。

在struts.xml配置文件中添加拦截器 fileUpload

<package name="file" extends="struts-default" namespace="/">
	
	<interceptors>
		<interceptor-stack name="defaultStack">
			<!-- 引入已经定义好的文件上传拦截器,将参数传入进去,名称为fileUpload -->
			<interceptor-ref name="fileUpload">
				<!-- 上传文件的大小 -->
				<param name="maximumSize">500000000</param>
				<!-- 标准MINE名称 -->
                <param name="allowedTypes">text/plain,application/vnd.ms-powerpoint</param>
                <!-- 后缀名  可以传递.txt和.ppt结尾的,此时图片是不行的-->
                <param name="allowedExtensions">.txt,.ppt</param>
			</interceptor-ref>
			<interceptor-ref name="defaultStack"></interceptor-ref>
		</interceptor-stack>
	</interceptors>
	
	<action name="File_*" class="com.yjl.web.action.FileAction" method="{1}">
			<result name="success">/WEB-INF/content/success.jsp</result>
	</action>
	<!-- 配置跳转页面 -->
	<action name="*">
		<result>/WEB-INF/content/{1}.jsp</result>
	</action>
</package>

二.五.二 验证配置

如果此刻上传图片的话:


在这里插入图片描述


会显示异常:


在这里插入图片描述


上传.txt文档的话:


在这里插入图片描述


是可以正常上传的。

一般设置图片时,常用的格式为:

<interceptors>
	<interceptor-stack name="defaultStack">
		<!-- 引入已经定义好的文件上传拦截器,将参数传入进去,名称为fileUpload -->
		<interceptor-ref name="fileUpload">
			<!-- 上传文件的大小 -->
			<param name="maximumSize">500000000</param>
            <!-- 后缀名  设置常见的图片形式-->
            <param name="allowedExtensions">.bmp,.jpg,.png,.gif</param>
		</interceptor-ref>
		<interceptor-ref name="defaultStack"></interceptor-ref>
	</interceptor-stack>
</interceptors>

二.五.三 设置 struts.multipart.maxSize 常量,控制文件大小

有的时候,可能会上传视频或者其他大文件,可以在struts.xml常量中配置相应的上传文件大小。

默认的大小是2M,即使你在设置类型时,用了maximumSize,设置了一个很大的值。

那么当大小超过2M时,也是错误的。

这个时候需要设置另外一个参数。常量:struts.multipart.maxSize

<constant name="struts.multipart.maxSize" value="1000000000"/> 

三. 实现多文件上传

在实现多文件上传时,有了单文件上传的基础上,是非常简单的,只需要将原来的对象,改变成数组即可。

将File upload改成File[] upload即可。 uploadFileName和uploadeContentType也同样需要改变成字符串数组的样式。

三.一 编写 /content/login2.jsp 页面

login.jsp页面

file是多选形式,用多个name值相同的样式。

<s:form action="MoreFile_multiple.action" namespace="/" method="post" enctype="multipart/form-data">
			用户名: <s:textfield  name="name"/>  <br/>
			密码: <s:password name="password"/><br/>
			<!-- 名称相同 -->
			上传文件1:<s:file name="upload"/><br/>
			上传文件2:<s:file name="upload"/><br/>
   			<s:submit value="提交"/>
   			<s:reset value="重置"/>
</s:form>

三.二 创建 MoreFileAction.java

MoreFileAction:

package com.yjl.web.action;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

/**
* @author 两个蝴蝶飞
* @version 创建时间:Aug 28, 2018 8:35:56 AM
* 关于文件上传和下载的Action实现
*/
public class FileAction extends ActionSupport{
	private static final long serialVersionUID = 1L;
	private Logger logger=Logger.getLogger(FileAction.class);
	/**
	 * 关于文件上传的具体的操作
	 * @param uploade 与前端表单中file标签的name值相同.
	 * @param XxxFileName 上传文件的名称
	 * @param XxxContentType 上传文件的类型
	 */
	private File []upload;
	private String []uploadFileName;
	private String []uploadContentType;
	//实现它们三个的setter和getter方法
	public File[] getUpload() {
		return upload;
	}
	public void setUpload(File []upload) {
		this.upload = upload;
	}
	public String[] getUploadFileName() {
		return uploadFileName;
	}
	public void setUploadFileName(String []uploadFileName) {
		this.uploadFileName = uploadFileName;
	}
	public String[] getUploadContentType() {
		return uploadContentType;
	}
	public void setUploadContentType(String []uploadContentType) {
		this.uploadContentType = uploadContentType;
	}
	/**
	 *多文件上传操作
	 */
	public String multiple(){
		//在实际开发中,需要判断一下是否上传了文件,即upload是否为空,不为空,才进行上传的操作。
		if(upload!=null){
			for(int i=0;i<upload.length;i++){
				//1.输出上传的类型
				logger.info("上传文件的名称用File取得:"+this.getUpload()[i].getName());
				logger.info("直接用属性取得名称:"+this.getUploadFileName()[i]);
				logger.info("文件上传的类型:"+this.getUploadContentType()[i]);
				//2.设置上传文件的放置位置,通常放在服务器下面的upload文件夹下.
				String path=ServletActionContext.getServletContext().getRealPath("/upload");
				//3.这是一个目录,如果这个目录不存在,需要创建这个目录(包括其父目录)
				File srcFile=new File(path);
				if(!srcFile.exists()){
					srcFile.mkdirs();
				}
				//4.通常还需要对上传的文件名进行UUID的操作,使文件名不能重复。这里不做处理
				String fileName=this.getUploadFileName()[i];
				//5.调用FileUtils类执行上传的操作.
				try {
					FileUtils.copyFile(upload[i],new File(srcFile,fileName));
					logger.info("文件上传成功");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return SUCCESS;
	}
}

三.三 配置struts.xml 文件

struts.xml

<action name="MoreFile_*" class="com.yjl.web.action.MoreFileAction" method="{1}">
				<result name="success">/WEB-INF/content/success.jsp</result>
</action>

三.四 重启服务器测试

输入网址: http://localhost:8080/Struts_File/login2

进行相关的测试:

上传文件:

在这里插入图片描述

控制台打印输出:

在这里插入图片描述

查看Tomcat 目录:

在这里插入图片描述

多文件上传操作成功。

四. 文件上传实例应用

在实际项目中,不仅要能正常的上传文件,还应该把上传文件的位置返回。

前端页面和struts.xml的配置都一样,主要是对Action的处理。 我通常的作法是创建一个BaseAction的类,

与第四章项目中的BaseAction相同,只是这里添加了上传的操作。

BaseAction

package com.yjl.web.action;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.text.SimpleDateFormat;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
/**
* @author 两个蝴蝶飞
* @version 创建时间:2018年8月23日 下午4:40:13
* @description BaseAction的工具类
*/
@SuppressWarnings(value= {"rawtypes","unchecked"})
public class BaseAction<T> extends ActionSupport implements ModelDriven<T>{
	private static final long serialVersionUID = -7180401147510521582L;
	private Logger logger=Logger.getLogger(BaseAction.class);
	private T t;
	private Class clazz;
	public BaseAction() {
		//得到当前的类
		Class class1=this.getClass();
		//得到运行中的父类
		ParameterizedType parameterizedType=(ParameterizedType) class1.getGenericSuperclass();
		clazz=(Class) parameterizedType.getActualTypeArguments()[0];
		try {
			t=(T) clazz.newInstance();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		logger.info("当前类的类:"+clazz.getName()+"完成初始化");
	}
	@Override
	public T getModel() {
		return t;
	}
	//添加实现文件上传和下载的操作
	/**
	 * 关于文件上传的具体的操作
	 * @param uploade 与前端表单中file标签的name值相同.
	 * @param XxxFileName 上传文件的名称
	 * @param XxxContentType 上传文件的类型
	 */
	private File upload;
	private String uploadFileName;
	private String uploadContentType;
	//实现它们三个的setter和getter方法
	public File getUpload() {
		return upload;
	}
	public void setUpload(File upload) {
		this.upload = upload;
	}
	public String getUploadFileName() {
		return uploadFileName;
	}
	public void setUploadFileName(String uploadFileName) {
		this.uploadFileName = uploadFileName;
	}
	public String getUploadContentType() {
		return uploadContentType;
	}
	public void setUploadContentType(String uploadContentType) {
		this.uploadContentType = uploadContentType;
	}
	/**
	 *得到上传的路径
	 */
	public String getUploadPath(){
		//在实际开发中,需要判断一下是否上传了文件,即upload是否为空,不为空,才进行上传的操作。
		String contextPath=null;
		if(upload!=null){
			//1.输出上传的类型
			logger.info("上传文件的名称用File取得:"+this.getUpload().getName());
			logger.info("直接用属性取得名称:"+this.getUploadFileName());
			logger.info("文件上传的类型:"+this.getUploadContentType());
			//2.设置上传文件的放置位置,通常放在服务器下面的upload文件夹下.
			String path=ServletActionContext.getServletContext().getRealPath("/upload");
			//3.这是一个目录,如果这个目录不存在,需要创建这个目录(包括其父目录)
			File srcFile=new File(path);
			if(!srcFile.exists()){
				srcFile.mkdirs();
			}
			//4.通常还需要对上传的文件名进行UUID的操作,使文件名不能重复。文件的返回路径是fileName.
			String fileName=path+getAnglePath(this.getUploadFileName());
			//5.调用FileUtils类执行上传的操作.
			try {
				//写复制方法
				FileUtils.copyFile(upload,new File(fileName));
				//将fileName进行相应的处理,去掉前面的一些无用的东西。 得到项目名
				String context=ServletActionContext.getRequest().getContextPath().substring(1);
				contextPath=getPath(fileName,context);
				logger.info("在Tomcat下的目录为:"+contextPath);
				logger.info("文件上传成功");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return contextPath;
	}
	/**
	 * 将一个文件名设置成唯一的文件名
	 * @param fileName 普通的文件名
	 * @return 返回一个唯一的文件名
	 */
	public String getAnglePath(String fileName) {
		//不采用UUID的形式,可以自定义当前的时间字符串来处理.
		SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmssSSS");
		String angle=sdf.format(new java.util.Date());
		return File.separator+angle+"_"+fileName;
	}
	/**
	 * 将磁盘上的文件在Tomcat下的文件,转换成在Tomcat上的文件
	 * @param realPath 在磁盘上的路径
	 * @param name 项目名
	 * @return 将磁盘上的文件在Tomcat下的文件,转换成在Tomcat上的文件
	 */
	public String getPath(String realPath, String name) {
		int index = realPath.indexOf(name);
		String[] args = realPath.substring(index).split("\\");
		StringBuffer sb = new StringBuffer("/");
		for (int i = 0; i < args.length; i++) {
			if (i != args.length - 1) {
				sb.append(args[i]).append("/");
			} else {
				sb.append(args[i]);
			}
		}
		return sb.toString();
	}
	
}

在我们写的FileAction中,只需要继承BaseAction类,然后调用方法即可。

package com.yjl.web.action;
import org.apache.log4j.Logger;
import com.yjl.pojo.User;
/**
* @author 两个蝴蝶飞
* @version 创建时间:Aug 28, 2018 8:35:56 AM
* 关于文件上传和下载的Action实现
*/
public class FileAction extends BaseAction<User>{
	private static final long serialVersionUID = 1L;
	private Logger logger=Logger.getLogger(FileAction.class);
	public String login(){
		String path=getUploadPath();
		logger.info("上传的路径是:"+path);
		return SUCCESS;
	}
	
}

文件上传后的控制台:

在这里插入图片描述

如果没有上传文件的话,

在这里插入图片描述

这样就完成了。

如果在上传时,出现中文乱码的问题,那是因为没有在 struts.xml中添加格式处理常量:

<!--修改国际化编码 -->
<constant name="struts.i18n.encoding" value="UTF-8"></constant>

文件上传操作完成。

五. 文件下载

在下载时,只需要指定文件的名称,就会去相应的文件目录中去寻找,去下载。

在该项目下新建一个upload文件夹,里面放置一些文件。

四.一 下载前查询所有的文件

一般在下载之前,会将可以下载的文件以列表的形式进行显示,或者将那个文件进行相应的显示。

五.一.一 创建 ShowFileAction

ShowFileAction中:

package com.yjl.web.action;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import com.yjl.pojo.User;
import com.yjl.utils.BaseAction;
/**
* @author 两个蝴蝶飞
* @version 创建时间:Aug 28, 2018 8:35:56 AM
* 关于文件上传和下载的Action实现
*/
public class ShowFileAction extends BaseAction<User>{
	private static final long serialVersionUID = 1L;
	private Logger logger=Logger.getLogger(FileAction.class);
	public String login(){
		String path=getUploadPath();
		logger.info("上传的路径是:"+path);
		return SUCCESS;
	}
	//要下载的文件
	private List<File> downFileList=new ArrayList<File>();
	public List<File> getDownFileList() {
		return downFileList;
	}
	public void setDownFileList(List<File> downFileList) {
		this.downFileList = downFileList;
	}
	public String downFileList(){
		//1.找到path路径
		String path=ServletActionContext.getServletContext().getRealPath("/upload");
		File pFile=new File(path);
		//2.找到pFile文件夹下及下属文件夹下所有的文件。
		List<File> temp=new ArrayList<File>();
		//3.将所有的文件都放置到列表中
		downFileList=ergodic(pFile, temp);
		logger.info("长度是:"+downFileList.size());
		//4. 将集合转换成数组
		return "downFileList";
	}
	private List<File> ergodic(File file,List<File> resultFileList){
		if(file==null||!file.exists()){
			return new ArrayList<File>();
		}
        File[] files = file.listFiles();
        if(files==null||files.length<=0){
			return new ArrayList<File>();
		}
        for (File f : files) {
            if(f.isDirectory()){// 判断是否文件夹
                ergodic(f,resultFileList);// 调用自身,查找子目录
            }else{
            	resultFileList.add(f);
            } 	
        }
        return resultFileList;
    }	
}

在Action中看有的人是用的File []数组,这里用集合了,也是可以的。

五.一.二 配置 struts.xml 文件

在struts.xml中是正常的配置。

<!-- 下载文件显示 -->
		<action name="ShowFile_*" class="com.yjl.web.action.ShowFileAction" method="{1}">
				<result name="success">/WEB-INF/content/success.jsp</result>
				<result name="downFileList">/WEB-INF/content/list.jsp</result>
		</action>

五.一.三 编写 /content/list.jsp 页面

<body>
	这是可以下载文件的列表<br/>
	<s:iterator value="downFileList" var="f">
		<s:property value="#f.name"/><s:a action="getDownFile?fileName=%{#f.name}" namespace="/">下载</s:a>
		<br/>
	</s:iterator>
</body>

五.一.四 重启服务器,验证界面

输入网址: http://localhost:8080/Struts_File/ShowFile_downFileList

会显示可以下载的文件:

有图片

五.二 下载文件

五.二.一 编写 DownFileAction

主要的Action代码:

public class DownFileAction extends ActionSupport{
	private static final long serialVersionUID = 1L;
	  //@param fileName 下载时传入的文件名
		private String fileName;
		//下载时,显示的文件名
		private String showFileName;
		//文件类型
		private String contentType;
		public String getFileName() {
			return this.fileName;
			
		}
		public void setFileName(String fileName) {
			//如果前台传入的是中文乱码,那么需要进行乱码处理.
			//如果前台传入的不是乱码,那么不需要进行处理。 
			this.fileName=fileName;
			
			/*try {
				this.fileName=new String(fileName.getBytes("ISO-8859-1"),"UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}*/
		}
		 /**
	     * 获取文件的类型
	     */
	    public String getContentType() {
	        return ServletActionContext.getServletContext().getMimeType(fileName);
	    }
	    public void setContentType(String contentType) {
	        this.contentType = contentType;
	    }
		
		public String getShowFileName() {
		     return showFileName;
		}
		public void setShowFileName(String showFileName) {
			this.showFileName=showFileName;
		}
		
		public InputStream  getDownloadFile() throws Exception{
			//设置一下,展示的名称。
			setShowFileName(fileName);
			String realPath=File.separator+"upload"+File.separator+fileName;
			InputStream is=ServletActionContext.getServletContext().getResourceAsStream(realPath);
			return is;
		}
		public String download(){
			return SUCCESS;
		}
}

五.二.二 配置struts.xml 文件

<!-- 配置下载页面 -->
<action name="getDownFile" class="com.yjl.web.action.DownFileAction" method="download">
			<!--下载时候的配置-->
			<result type="stream">  
				 <!--文件类型 -->
				<param name="contentType">${contentType}</param>
				<!-- 下载的文件数据存放的方法,该方法返回一个InputStream   
				例如取值为inputStream的属性需要编写getInputStream()方法-->  
				<param name="inputName">downloadFile</param>  
				<!--下载时,客户端显示的下载的文件名 -->  
				<param name="contentDisposition">attachment;filename=${showFileName}</param> 
				<!-- 数据的缓冲大小 -->  
				<param name="bufferSize">1024</param> 
				  <!--解决中文文件名乱码-->
				  <param name="encode">true</param>
			</result>  
</action>

五.二.三 下载验证

输入网址: http://localhost:8080/Struts_File/ShowFile_downFileList

点击链接,可以进行下载, 但是下载中文时, 火狐浏览器会乱码,而谷歌浏览器正常。

本章节的代码链接为:

链接:https://pan.baidu.com/s/1P9bA-OX__modBaQHg7HO6g 
提取码:lm3v

谢谢您的观看!!!

原文地址:https://www.cnblogs.com/yjltx/p/13071716.html