一、文件上传
1、客户端操作 ---- 提供文件上传输入项
upload.jsp 提供form 表单
注意:
1) input file 必须含有name 属性
2) form 必须要以 post方式提交
3) 设置form表单 编码类型 multipart/form-data (默认 application/x-www-form-urlencoded)
application/x-www-form-urlencoded 格式 : key=value&key=value&key=value....
2、服务器操作 ---- 需要程序,解析上传的文件内容
request 提供 getInputStream ,通过该方法可以获得 HTTP请求体内容 -------- 完全可以手动解析请求体 ,完成文件上传 (麻烦)
文件上传开发框架 Apache 提供 commons-fileupload 框架 ,和 jsp-smartupload 起名
JSP model1 完成文件上传,都使用 jsp-smartupload 组件
JSP model2 以后 ,完成文件上传,都使用commons-fileupload 组件 ---- Struts框架默认文件上传引擎都是 commons-fileupload
Servlet3.0 规范中,提供文件上传的支持
二、使用commons-fileupload 进行开发
1、下载jar包 commons-fileupload、commons-io (upload 依赖 io )
2、编程实现
创建DiskFileItemFactory 工厂
创建 解析核心类 ServletFileUpload
解析request 获得 FileItem 的 List集合
遍历集合 获得每个 FileItem , 判断是否为文件上传 isFormField ---- true 不是文件上传 false 是文件上传
普通域 : getFieldName 获得name属性 getString 获得value属性
上传域 : getName 获得文件名称 getInputStream 获得上传文件内容
注意问题 :
1、如果用户 没有上传文件 ---- 判断用户是否上传文件
if (filename == null || filename.trim().length() == 0) {
throw new RuntimeException("用户没有上传文件!");
}
2、早期浏览器 ,在上传文件时,服务器获得客户端完整文件路径 ,而不只是文件名
int index = filename.lastIndexOf("\\");
if (index != -1) {
// 找到了 \\
filename = filename.substring(index + 1);
}
三、 fileupload API详解
1、DiskFileItemFactory
public void setSizeThreshold(int sizeThreshold) 设置内存缓存区大小
public void setRepository(java.io.File repository) 设置临时文件存放位置
上传文件时,内存一定要存在缓存区 ,默认10K ,小于10K的文件,保存在内存缓冲区 足够了,不会产生临时文件 (内存缓冲区 ---- 目标文件)
上传文件 大于 内存缓存区,产生临时文件 保存 repository 目录 (内存缓冲区 ---- 临时文件 ---- 目标文件 ) 好处 确保目标文件完整性
临时文件存在上传后删除问题 ,通过fileItem.delete 在上传后删除
2、ServletFileUpload
static boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) 判断form 是否为multipart/form-data 编码类型
List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request 获得 List<FileItem>
void setFileSizeMax(long fileSizeMax) 设置单个文件大小 / void setSizeMax(long sizeMax) 设置请求总大小
void setHeaderEncoding(java.lang.String encoding) 解决上传文件名 乱码问题 *****
void setProgressListener(ProgressListener pListener) 设置文件上传进度监听,获得文件上传进度信息
3、FileItem 代表请求中一个数据部分
boolean isFormField 判断是否为文件上传项,true 不是文件上传 false 是文件上传
普通项:getFieldName 获得name属性 getString 获得value 属性 ------ value乱码 getString("utf-8")
*** 因为文件上传 采用multipart/form-data 而不是传统url编码,所有getParameter setCharacterEncoding 都将没有效果
上传项:getName 文件名 getInputStream 文件内容流 delete 删除临时文件
四、 上传文件保存问题
1、安全问题
如果上传文件 直接存放WebRoot目录下 (或者除 WEB-INF、META-INF 其它子目录下) ----- 客户端可以通过URL 直接访问 (例如:商品中商品图片,新闻图片)
如果上传文件,不想让用户可以直接通过url访问,将上传文件 保存 WebRoot/WEB-INF 或者 不受tomcat服务器管理的目录 (d:\file) --- 必须通过服务器端程序才能访问(安全)
2、同一个目录下同名文件,产生上传覆盖效果 ,后上传文件会覆盖之前同名文件
UUID 保证文件名唯一
3、同一目录下文件数量过多问题 --- 采用目录分离算法
按用户分离目录、按照时间分离(月份)、按照固定文件数量分离 ------ 按照hashcode 编码分离
五、文件上传的进度监控
ProgressListener 监控文件上传的进度 ,监听器习惯上通过匿名内部类设置
pBytesRead 已经上传大小
pContentLength 总大小
pItems 当前是form第几个元素
文件上传进度条 --- 一定要使用 AJAX
原理:ProgressListener 将文件上传进度 保存Session、Application 范围, 通过另一个程序获得进度,在页面显示进度条
速度 = 已经上传大小/已用时间
剩余时间 = 剩余大小/速度
===================================================================================================================================================
六、文件下载 两种方式
不需要引入第三方jar包,只需要通过Servlet 程序将文件内容,用response输出流 写到客户端
方式一:通过超链接 下载文件
浏览器识别的格式 会直接打开 , 不识别格式弹出下载 窗口 ---- 缺省Servlet (org.apache.catalina.servlets.DefaultServlet)
方式二:通过Servlet程序 下载文件
不管浏览器是否识别文件格式,都要弹出下载窗口
将文件内容用response输出流写出,还需要设置两个头信息 Content-Type Content-Disposition
ServletContext 接口中 提供方法 getMimeType(filename) 根据文件名 获得 MIME 类型
response.setContentType(getServletContext().getMimeType(filename));
response.setHeader("Content-Disposition", "attachment;filename="+ filename);
七、文件下载综合案例
服务器端指定目录下方的所有文件的下载
1、显示目录下所有文件 ----- 树的遍历
2、点击文件 进行下载 ---- 存在下载文件名乱码 问题
IE(及其它浏览器) 显示中文 下载附件 ---- 需要 url编码
火狐 显示中文 下载附件 --- 需要 BASE64 编码
if (agent.contains("Firefox")) {
// 火狐浏览器
filename = "=?utf-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
} else {
// IE 及其他浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
重点:1) 广度非递归遍历 2) c:url 对中文参数进行编码 3) 下载附件时中文附件名 编码问题
八、内省的使用
内省 是一套 JDK提供 用于操作JavaBean对象 ,基于反射 API
1、通过Introspector 获得 BeanInfo
2、通过BeanInfo 获得 Class对应 属性描述器 PropertyDescriptor
属性 由getter 和setter 方法确定
例如 getName() ---- 存在name 属性、 setAge() --- 存放age属性
3、PropertyDescriptor 获得 write和read 的method对象
<jsp:setProperty property="*" /> 自动将请求数据 封装对应 java对象属性中
想在Servlet中 实现 <jsp:setProperty property="*" /> 相同效果 ----- 自己实现内省太麻烦 (使用 Apache 提供 BeanUtils)
使用BeanUtils
1) 下载 jar包 beanutils logging
2) BeanUtils.populate(person, request.getParameterMap());
============================================================================================================================
九、支持上传和下载
1、允许用户上传文件 (需要将文件相关信息保存到数据库)
2、查看上传文件列表
3、提供用户下载
数据库、工程、导入jar、配置文件
create database day20;
use day20;
create table resources(
id int primary key auto_increment,
uuidname varchar(60),
realname varchar(40),
savepath varchar(100),
uploadtime timestamp,
description varchar(255)
);
private int id;
private String uuidname; //上传文件的名称,文件的uuid名
private String realname; //上传文件的真实名称
private String savepath; //记住文件的位置
private Timestamp uploadtime; //文件的上传时间
private String description; //文件的描述
导入 jar : beanutils 、c3p0、dbutils、mysql驱动 、jstl 、fileupload
cn.itcast.servlet
cn.itcast.domain
========================================================================================
内容小结 :
1、综合 上传下载案例
2、指定目录文件下载 (文件目录 遍历、c:url编码参数、不同浏览器编码问题)
3、文件保存问题 (安全路径、唯一文件名、随机目录)
1、客户端操作 ---- 提供文件上传输入项
upload.jsp 提供form 表单
注意:
1) input file 必须含有name 属性
2) form 必须要以 post方式提交
3) 设置form表单 编码类型 multipart/form-data (默认 application/x-www-form-urlencoded)
application/x-www-form-urlencoded 格式 : key=value&key=value&key=value....
2、服务器操作 ---- 需要程序,解析上传的文件内容
request 提供 getInputStream ,通过该方法可以获得 HTTP请求体内容 -------- 完全可以手动解析请求体 ,完成文件上传 (麻烦)
文件上传开发框架 Apache 提供 commons-fileupload 框架 ,和 jsp-smartupload 起名
JSP model1 完成文件上传,都使用 jsp-smartupload 组件
JSP model2 以后 ,完成文件上传,都使用commons-fileupload 组件 ---- Struts框架默认文件上传引擎都是 commons-fileupload
Servlet3.0 规范中,提供文件上传的支持
二、使用commons-fileupload 进行开发
1、下载jar包 commons-fileupload、commons-io (upload 依赖 io )
2、编程实现
创建DiskFileItemFactory 工厂
创建 解析核心类 ServletFileUpload
解析request 获得 FileItem 的 List集合
遍历集合 获得每个 FileItem , 判断是否为文件上传 isFormField ---- true 不是文件上传 false 是文件上传
普通域 : getFieldName 获得name属性 getString 获得value属性
上传域 : getName 获得文件名称 getInputStream 获得上传文件内容
注意问题 :
1、如果用户 没有上传文件 ---- 判断用户是否上传文件
if (filename == null || filename.trim().length() == 0) {
throw new RuntimeException("用户没有上传文件!");
}
2、早期浏览器 ,在上传文件时,服务器获得客户端完整文件路径 ,而不只是文件名
int index = filename.lastIndexOf("\\");
if (index != -1) {
// 找到了 \\
filename = filename.substring(index + 1);
}
三、 fileupload API详解
1、DiskFileItemFactory
public void setSizeThreshold(int sizeThreshold) 设置内存缓存区大小
public void setRepository(java.io.File repository) 设置临时文件存放位置
上传文件时,内存一定要存在缓存区 ,默认10K ,小于10K的文件,保存在内存缓冲区 足够了,不会产生临时文件 (内存缓冲区 ---- 目标文件)
上传文件 大于 内存缓存区,产生临时文件 保存 repository 目录 (内存缓冲区 ---- 临时文件 ---- 目标文件 ) 好处 确保目标文件完整性
临时文件存在上传后删除问题 ,通过fileItem.delete 在上传后删除
2、ServletFileUpload
static boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) 判断form 是否为multipart/form-data 编码类型
List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request 获得 List<FileItem>
void setFileSizeMax(long fileSizeMax) 设置单个文件大小 / void setSizeMax(long sizeMax) 设置请求总大小
void setHeaderEncoding(java.lang.String encoding) 解决上传文件名 乱码问题 *****
void setProgressListener(ProgressListener pListener) 设置文件上传进度监听,获得文件上传进度信息
3、FileItem 代表请求中一个数据部分
boolean isFormField 判断是否为文件上传项,true 不是文件上传 false 是文件上传
普通项:getFieldName 获得name属性 getString 获得value 属性 ------ value乱码 getString("utf-8")
*** 因为文件上传 采用multipart/form-data 而不是传统url编码,所有getParameter setCharacterEncoding 都将没有效果
上传项:getName 文件名 getInputStream 文件内容流 delete 删除临时文件
四、 上传文件保存问题
1、安全问题
如果上传文件 直接存放WebRoot目录下 (或者除 WEB-INF、META-INF 其它子目录下) ----- 客户端可以通过URL 直接访问 (例如:商品中商品图片,新闻图片)
如果上传文件,不想让用户可以直接通过url访问,将上传文件 保存 WebRoot/WEB-INF 或者 不受tomcat服务器管理的目录 (d:\file) --- 必须通过服务器端程序才能访问(安全)
2、同一个目录下同名文件,产生上传覆盖效果 ,后上传文件会覆盖之前同名文件
UUID 保证文件名唯一
3、同一目录下文件数量过多问题 --- 采用目录分离算法
按用户分离目录、按照时间分离(月份)、按照固定文件数量分离 ------ 按照hashcode 编码分离
五、文件上传的进度监控
ProgressListener 监控文件上传的进度 ,监听器习惯上通过匿名内部类设置
pBytesRead 已经上传大小
pContentLength 总大小
pItems 当前是form第几个元素
文件上传进度条 --- 一定要使用 AJAX
原理:ProgressListener 将文件上传进度 保存Session、Application 范围, 通过另一个程序获得进度,在页面显示进度条
速度 = 已经上传大小/已用时间
剩余时间 = 剩余大小/速度
===================================================================================================================================================
六、文件下载 两种方式
不需要引入第三方jar包,只需要通过Servlet 程序将文件内容,用response输出流 写到客户端
方式一:通过超链接 下载文件
浏览器识别的格式 会直接打开 , 不识别格式弹出下载 窗口 ---- 缺省Servlet (org.apache.catalina.servlets.DefaultServlet)
方式二:通过Servlet程序 下载文件
不管浏览器是否识别文件格式,都要弹出下载窗口
将文件内容用response输出流写出,还需要设置两个头信息 Content-Type Content-Disposition
ServletContext 接口中 提供方法 getMimeType(filename) 根据文件名 获得 MIME 类型
response.setContentType(getServletContext().getMimeType(filename));
response.setHeader("Content-Disposition", "attachment;filename="+ filename);
七、文件下载综合案例
服务器端指定目录下方的所有文件的下载
1、显示目录下所有文件 ----- 树的遍历
2、点击文件 进行下载 ---- 存在下载文件名乱码 问题
IE(及其它浏览器) 显示中文 下载附件 ---- 需要 url编码
火狐 显示中文 下载附件 --- 需要 BASE64 编码
if (agent.contains("Firefox")) {
// 火狐浏览器
filename = "=?utf-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
} else {
// IE 及其他浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
重点:1) 广度非递归遍历 2) c:url 对中文参数进行编码 3) 下载附件时中文附件名 编码问题
八、内省的使用
内省 是一套 JDK提供 用于操作JavaBean对象 ,基于反射 API
1、通过Introspector 获得 BeanInfo
2、通过BeanInfo 获得 Class对应 属性描述器 PropertyDescriptor
属性 由getter 和setter 方法确定
例如 getName() ---- 存在name 属性、 setAge() --- 存放age属性
3、PropertyDescriptor 获得 write和read 的method对象
<jsp:setProperty property="*" /> 自动将请求数据 封装对应 java对象属性中
想在Servlet中 实现 <jsp:setProperty property="*" /> 相同效果 ----- 自己实现内省太麻烦 (使用 Apache 提供 BeanUtils)
使用BeanUtils
1) 下载 jar包 beanutils logging
2) BeanUtils.populate(person, request.getParameterMap());
============================================================================================================================
九、支持上传和下载
1、允许用户上传文件 (需要将文件相关信息保存到数据库)
2、查看上传文件列表
3、提供用户下载
数据库、工程、导入jar、配置文件
create database day20;
use day20;
create table resources(
id int primary key auto_increment,
uuidname varchar(60),
realname varchar(40),
savepath varchar(100),
uploadtime timestamp,
description varchar(255)
);
private int id;
private String uuidname; //上传文件的名称,文件的uuid名
private String realname; //上传文件的真实名称
private String savepath; //记住文件的位置
private Timestamp uploadtime; //文件的上传时间
private String description; //文件的描述
导入 jar : beanutils 、c3p0、dbutils、mysql驱动 、jstl 、fileupload
cn.itcast.servlet
cn.itcast.domain
========================================================================================
内容小结 :
1、综合 上传下载案例
2、指定目录文件下载 (文件目录 遍历、c:url编码参数、不同浏览器编码问题)
3、文件保存问题 (安全路径、唯一文件名、随机目录)
BeanUtils使用
DownloadListServlet.java
package cn.itcast.servlet;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;
public class DownloadListServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得文件 绝对路径
String path = request.getParameter("path");
// get方式乱码
path = new String(path.getBytes("ISO-8859-1"), "utf-8");
System.out.println(path);
// 等价于
// path = URLEncoder.encode(path, "ISO-8859-1");
// path = URLDecoder.decode(path, "utf-8");
// 截取文件名
String filename = path.substring(path.lastIndexOf("\\") + 1);
// 设置ContentType
response.setContentType(getServletContext().getMimeType(filename));
// 设置ContentDisposition
// 不同浏览器处理 附件乱码方式不同
String agent = request.getHeader("user-agent");
System.out.println(agent);
if (agent.contains("Firefox")) {
// 火狐浏览器
filename = "=?utf-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
} else {
// IE 及其他浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
response.setHeader("Content-Disposition", "attachment;filename="
+ filename);
// 流拷贝
InputStream in = new BufferedInputStream(new FileInputStream(path));
OutputStream out = response.getOutputStream();
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
in.close();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
DownloadServlet.java
package cn.itcast.servlet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 完成文件下载
*
* @author seawind
*
*/
public class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 从请求参数中 获得文件名
String filename = request.getParameter("filename");
// 判断文件是否存在
File file = new File(getServletContext().getRealPath("/download"),
filename);
if (file.exists() && file.isFile()) {
// 设置文件类型
response.setContentType(getServletContext().getMimeType(filename)); // tomcat/conf/web.xml
// 设置段体的安排方式 inline 、attachment
response.setHeader("Content-Disposition", "attachment;filename="
+ filename);
// 存在文件
InputStream in = new BufferedInputStream(new FileInputStream(file));
OutputStream out = response.getOutputStream();
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
in.close();
out.close();
} else {
// 不存在
throw new RuntimeException("下载的文件不存在!");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
UploadServlet.java
package cn.itcast.servlet;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import cn.itcast.utils.UploadUtils;
/**
* 文件上传
*
* @author seawind
*
*/
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("文件上传 ....");
// 将请求体 内容打印到 控制台
// InputStream in = request.getInputStream();
// int b;
// while ((b = in.read()) != -1) {
// System.out.write(b);
// }
// System.out.flush();
// in.close();
// 完成文件上传
if (!ServletFileUpload.isMultipartContent(request)) {
throw new RuntimeException("表单编码类型错误!不是multipart/form-data");
}
// 步骤一 创建文件项 工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 5MB 小于5MB 不会产生临时文件 ,超过5MB才会产生临时文件
factory.setSizeThreshold(1024 * 1024 * 5);
// 设置临时文件区 tmp
factory
.setRepository(new File(getServletContext().getRealPath("/tmp")));
// 步骤二 通过工厂 获得 解析核心类 ServletFileUpload
ServletFileUpload fileUpload = new ServletFileUpload(factory);
// 设置上传文件不能超过20M
fileUpload.setFileSizeMax(1024 * 1024 * 20);
// 解决上传文件名 乱码
fileUpload.setHeaderEncoding("utf-8");
// 获得开始上传的时间
final long start = System.currentTimeMillis();
// 设置监听器,监控文件上传进度
fileUpload.setProgressListener(new ProgressListener() {
@Override
// pBytesRead 已经上传字节数量
// pContentLength 上传文件总大小
// pItems 当前上传文件,是表单的第几个元素
public void update(long pBytesRead, long pContentLength, int pItems) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (pBytesRead != 0) {
System.out.println("上传文件总大小:" + pContentLength + ",已经上传大小:"
+ pBytesRead + ",当前上传文件是表单第" + pItems + "个元素");
long now = System.currentTimeMillis();// 当前时间
long hasUseTime = now - start; // 已用时间
// 速度
double speed = ((double) pBytesRead) / hasUseTime; // 单位字节/毫秒
// 剩余时间
long restTime = (long) ((pContentLength - pBytesRead) / speed);
System.out.println("上传速度:" + speed + "KB/S, 剩余时间:"
+ restTime + "毫秒");
}
}
});
// 步骤三 解析request
try {
List<FileItem> fileItems = fileUpload.parseRequest(request);
// 步骤四 遍历集合
for (FileItem fileItem : fileItems) {
// 判断哪个 fileItem 是文件上传
if (fileItem.isFormField()) {
// 不是文件上传
// 获得 name 和 value
String name = fileItem.getFieldName();
String value = fileItem.getString("utf-8");
System.out.println("这是 普通域 name:" + name + ", value:"
+ value);
} else {
// 是文件上传
// 获得文件名称
String filename = fileItem.getName();
// 判断用户是否上传文件
if (filename == null || filename.trim().length() == 0) {
throw new RuntimeException("用户没有上传文件!");
}
// 解决早期 浏览器提交客户端文件路径问题,从最后一个\\ 切割
int index = filename.lastIndexOf("\\");
if (index != -1) {
// 找到了 \\
filename = filename.substring(index + 1);
}
System.out.println("上传文件 :" + filename);
// 获得文件内容
InputStream in = new BufferedInputStream(fileItem
.getInputStream());
// 保证文件名唯一
filename = UUID.randomUUID() + "_" + filename;
// 将文件保存到服务器端
String path = getServletContext().getRealPath("/upload");
String ramdonDir = UploadUtils.generateRandonDir(filename);
File dirFile = new File(path + ramdonDir);// 目录不存在
dirFile.mkdirs();// 创建
OutputStream out = new BufferedOutputStream(
new FileOutputStream(new File(dirFile, filename)));
// 流拷贝
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
in.close();
out.close();
// 删除临时文件
fileItem.delete();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.io.tmpdir"));
}
}