上传
上传对表单的限制:
- method=“post”
- enctype=“multipart/form-data”
- 添加文件表单项
<input type="file" name="xxx">
<form action="xxx" method="post" enctype="multipart/form-data">
</form>
上传对Servlet的限制:
request.getParameter("xxx")
返回的是String,在表单为enctype="multipart/form-data"时返回值为null,因此不能使用了- 使用
ServletInputString request.getInputStream()
,它返回整个请求体的流
首先写一个页面form.jsp用于上传:
<body>
<h1>上传1</h1>
<form action="<c:url value='/Upload1Servlet'/>" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
照 片:<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/>
</form>
</body>
Upload1Servlet
public class Upload1Servlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
ServletInputStream in=request.getInputStream();
//注意此处需要导入commmons-io.jar
String s=IOUtils.toString(in);
System.out.println(s);
}
}
将上传的请求打印到控制台上,在多部件表单中,每个表单项一个部件,每个部件包含自己的请求头、空行、请求体。
普通表单项:
- 1个头:Content-Disposition:包含name=“xxxx”,即表单项名称。
- 体就是表单项的值
文件表单项:
- 2个头:
Content-Disposition:包含name=“xxxx”,即表单项名称;还有一个filename=“xxx”,表示上传文件的名称
Content-Type:它是上传文件的MIME类型,例如:image/pjpeg,表示上传的是图片,图上中jpg扩展名的图片。 - 体就是上传文件的内容。
因此,如果要解析图片,需要拿到图片部件的数据。这里我们可以使用commons-fileupload,它会解析request中的上传数据,并将结果封装到一个FileItem对象中,我们只需调用FileItem的方法即可。
要使用commons-fileupload需要导入两个包:
commons-fileupload.rar
commons-io.jar(依赖)
上传三步
相关类
工厂:DiskFileItemFactory
解析器:ServletFileUpload
表单项:FileItem
- 创建工厂:
DiskFileItemFactory factory=new DiskFileItemFactory()
- 创建解析器:
ServletFileUpload sfu=new ServletFileUpload(factory)
- 使用解析器解析request,得到FileItem集合:
List<FileItem> fileItemList=sfu.parseRequest(request)
FileItem
boolean isFormField()
:是否为普通表单项
String getFiledName()
:返回当前表单名称
String getString(String charset)
:返回表单项的值
String getName()
:返回上传的文件名
long getSize()
:返回上传文件的字节数
InputStream getInputStream()
:返回上传文件对应的输入流
void write(File destFile)
:把上传的文件内容保存到指定的文件中
使用举例:
form2.jsp
<body>
<h1>上传2</h1>
<form action="<c:url value='/Upload2Servlet'/>" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
照 片:<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/>
</form>
</body>
Upload2Servlet
public class Upload2Servlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//得到工厂
DiskFileItemFactory factory=new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu=new ServletFileUpload(factory);
//得到FileItem集合
try {
List<FileItem> fileItemList=sfu.parseRequest(request);
FileItem fi1=fileItemList.get(0);
FileItem fi2=fileItemList.get(1);
System.out.println("普通表单项演示"+fi1.getFieldName()+"="+fi1.getString("utf-8"));
System.out.println("文件表单项演示:");
System.out.println("Content-type:"+fi2.getContentType());
System.out.println("Size:"+fi2.getSize());
System.out.println("filename"+fi2.getName());
//保存文件
File destFile=new File("d:/desktop/test.jpg");
try {
fi2.write(destFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
} catch (FileUploadException e) {
throw new RuntimeException(e);
}
}
}
上传的细节
- 文件必须保存到WEB-INF下,避免浏览器直接访问到(如果项目不再使用这个文件,就可以放在其他盘,项目之外)
- 有的浏览器上传的文件名是绝对路径,如C:\files\a.jpg,这需要切割
String filename=fi2.getName();
int index=filename.lastIndexOf("\\");
if(index!=-1)
filename=filename.substring(index+1);
- 文件名乱码或文件表单项乱码:request.setCharacterEncoding(“utf-8”),因为fileupload内部会调用request.getCharacterEncoding;或者使用
servletFileUpload.setHeaderEncoding("utf-8")
,优先级更高 - 文件同名问题:为每个文件添加名称前缀(uuid),后缀不能改,例如:
filename=CommonUtils.uuid()+"_"+filename;
- 目录打散:不能在一个目录下存放过多文件。
首字符打散:首字符作为目录
时间打散:当前日期做目录
哈希打散:
1.通过文件名称获取int值,即调用hashCode()
2.将int值转换为16进制
3.将16进制前两位用来生成两层目录,如1B2C3D4E5---->1/B/ - 上传文件大小限制:单个文件、整个请求
- 缓存大小与临时目录
改进后的上传
form3.jsp
<body>
<h1>上传3</h1>
<form action="<c:url value='/Upload3Servlet'/>" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
照 片:<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/>
</form>
</body>
Upload3Servlet:限制单个文件上传大小(必须在解析开始前调用)、限制表单总大小(必须在解析开始前调用)、解决文件名为绝对路径的问题、解决文件名重复的问题、利用哈希打散存放文件(在WEB-INF下新建files文件夹存放文件)
public class Upload3Servlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
DiskFileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload sfu=new ServletFileUpload(factory);
sfu.setFileSizeMax(100*1024);//限制单个文件大小为100k
sfu.setSizeMax(1024*1024);//限制整个表单大小1M
try {
List<FileItem> list=sfu.parseRequest(request);
FileItem fi=list.get(1);
//得到文件保存路径
String root=this.getServletContext().getRealPath("/WEB-INF/files/");
//得到文件名
String filename=fi.getName();
int index=filename.lastIndexOf("\\");
if(index!=-1)
filename=filename.substring(index+1);
String savename=CommonUtils.uuid()+"_"+filename;
//得到hashCode
int hCode=filename.hashCode();
//转换成16进制
String hex=Integer.toHexString(hCode);
//获取前两个字符用来生成目录
File dirFile=new File(root,hex.charAt(0)+"/"+hex.charAt(1));
//创建目录链
dirFile.mkdirs();
//创建目录文件
File destFile=new File(dirFile,savename);
//保存
fi.write(destFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
上传文件后在tomcat的项目下可以找到传送的文件,已经用哈希打散生成的目录存放:
缓存大小与临时目录
在创建工厂时设置缓存大小、临时目录:超出设置的默认值会向临时目录保存(每默认值大小保存一次),在fi.write()时才从临时目录拷贝到指定目录。
如:每20kb保存一次到临时目录f:/temp中
DiskFileItemFactory factory=new DiskFileItemFactory(20*1024,new File("f:/temp"));
下载
下载就是向客户端响应字节数据。
把一个文件变成字节数组,使用response.getOutputStream()来响应给浏览器。
下载的要求:
两个头:
Content-Type:传递给客户端的文件是什么MIME类型。如:image/pjpeg
Content-Disposition:它的默认值为inline,表示直接在浏览器窗口打开(如文字和图片)。需要下载弹框则使用attachment;filename=xxx
一个流:要下载的文件数据
public class Download1Servlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String filename="C:/Users/Pictures/板绘临摹/IMG_3862.JPG";
//通过文件名获取MIME类型
String contentType=this.getServletContext().getMimeType(filename);
String contentDisposition="attachment;filename=a.jpg";
//一个流
FileInputStream input=new FileInputStream(filename);
//设置头
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Disposition", contentDisposition);
//获取绑定了客户端的流
ServletOutputStream output=response.getOutputStream();
IOUtils.copy(input,output);
input.close();
}
}
下载的细节
显示在下载框的中文会乱码或不显示。
使用一个自己的类,根据不同浏览器处理编码:
public class DownUtils {
public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
String agent = request.getHeader("User-Agent"); //获取浏览器
if (agent.contains("Firefox")) {
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8"))
+ "?=";
} else if(agent.contains("MSIE")) {
filename = URLEncoder.encode(filename, "utf-8");
} else {
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
使用:
public class Download1Servlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String filename="C:/Users/徐钰洁&杰泥粉/Pictures/板绘临摹/IMG_3862.JPG";
//解决中文名乱码问题
String framename=DownUtils.filenameEncoding("哇啦啦.jpg",request);//here
//通过文件名获取MIME类型
String contentType=this.getServletContext().getMimeType(filename);
String contentDisposition="attachment;filename="+framename;
//一个流
FileInputStream input=new FileInputStream(filename);
//设置头
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Disposition", contentDisposition);
//获取绑定了客户端的流
ServletOutputStream output=response.getOutputStream();
IOUtils.copy(input,output);
input.close();
}
}
邮件
JavaMail:是java提供的一组API,用来发送和接收邮件
邮件协议:
SMTP:发邮件协议
POP3:收邮件协议
IMAP:收发邮件协议
服务器名称:
SMTP服务器的端口号为25,服务器名称为smtp.xxx.xxx,例如smtp.163.com、smtp.sina.com
POP3服务器的端口号为110,服务器名称为pop3.xxx.xxx,如pop3.163.com、pop3.sina.com
javamail发邮件
- 导包:mail.jar、activation.jar
- 核心类:
Session:得到了它,表示已经与服务器连接上了,与Connection作用类似
MimeMessage:表示一个邮件对象,可以调用它的setFrom(),设置发件人、收件人、主题、正文
Transport:它只有一个功能:发邮件
举例:
public class Demo1 {
public void fun1() throws AddressException, MessagingException {
//1.得到Session
Properties props=new Properties();
props.setProperty("mail.host", "smtp.163.com");//设置服务器主机名
props.setProperty("mail.smtp.auth", "true");//设置需要认证
Authenticator auth=new Authenticator() {//实现认证器接口
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("hhhh","123");//用户名和密码
}
};
Session session=Session.getInstance(props,auth);
//2.创建MimeMessage
MimeMessage msg=new MimeMessage(session);
msg.setFrom(new InternetAddress("jnf@163.com"));///设置发件人
msg.setRecipients(RecipientType.TO, "jnf@126.com");//设置收件人
msg.setRecipients(RecipientType.CC, "jnf@sohu.com");//设置抄送(围观者)
msg.setRecipients(RecipientType.BCC, "jnf@sina.com");//设置暗送(秘密围观者)
msg.setSubject("测试");//邮件标题
msg.setContent("芜湖", "text/html;charset=utf-8");//邮件正文
//3.发送
Transport.send(msg);
}
}
附件
当发送包含附件的邮件时,邮件体就为多部件形式
- 创建多一个多部件MimeMultipart
- 创建两个主体部件(MimeBodyPart),一个是文本内容,一个是附件
- 把MimeMultipart设置给MimeMessage的内容
public class Demo1 {
public void fun1() throws AddressException, MessagingException, IOException {
//1.得到Session
Properties props=new Properties();
props.setProperty("mail.host", "smtp.163.com");
props.setProperty("mail.smtp.auth", "true");
Authenticator auth=new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("hhhh","123");
}
};
Session session=Session.getInstance(props,auth);
//2.创建MimeMessage
MimeMessage msg=new MimeMessage(session);
msg.setFrom(new InternetAddress("jnf@163.com"));///设置发件人
msg.setRecipients(RecipientType.TO, "jnf@126.com");//设置收件人
msg.setRecipients(RecipientType.CC, "jnf@sohu.com");//设置抄送(围观者)
msg.setRecipients(RecipientType.BCC, "jnf@sina.com");//设置暗送(秘密围观者)
msg.setSubject("测试附件");
MimeMultipart list=new MimeMultipart();
//创建主体并设置内容
MimeBodyPart part1=new MimeBodyPart();
part1.setContent("包含附件,芜湖", "text/html;charset=utf-8");
list.addBodyPart(part1);
//创建第二个主体和并设置内容(绑定文件)
MimeBodyPart part2=new MimeBodyPart();
part2.attachFile(new File("f:/xx.jpg"));
part2.setFileName(MimeUtility.encodeText("测试附件.jpg"));//设置显示的文件名并处理乱码
msg.setContent(list);
//3.发送
Transport.send(msg);
}
}
如果测试时报javax.mail.AuthenticationFailedException: 535 authentication failed,检查:
- props.setProperty()设置的smtp是否正确
- 使用的邮箱是否开启了smtp服务。开启后得到的授权码填入
PasswordAuthentication(用户名,密码)
的密码中