导学
在我们学习完成Java Web的课程之后,我们通过一个项目完成对之前所学知识的整合,并体会MVC模式下的开发工作。实现头像上传,验证码校验等功能。
MVC设计模式
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
原先我们的代码随意存放,堆成一堆。现在要求我们遵循MVC的设计模式,简单来说,就是要求开发人员将代码分门别类进行存放。
Servlet + Jsp + JavaBean 就是MVC模式 Servlet是控制器==Controller Jsp是页面展现==View JavaBean是处理和封装数据==Model
什么是Java Bean
JavaBean 其实可以看做是一种约定俗成的,实体类书写规范。目的是使其他的 Java 类可以通过反射机制发现和操作这些 JavaBean 的属性。
定义: JavaBean 其实就是按照一种规范书写的代表实体的 Java 类。名称中的“Bean”是用 于 Java 的可重用软件组件的惯用叫法。
规范严格意义上需要有一下四点:
- 属性私有
- 提供 getset 方法(public 声明,命名符合规范)操作私有属性
- 提供一个无参构造方法
- 实现了 Serializable 接口。
这些特点使它们有更好的封装性和可重用性。并且可以被序列化(持久化),保存在硬 盘或者在网络上传输。
对于不需要持久化的实体,不实现第四条也是可以的。所以开发中常见的实体类,可能并没有严格遵守 JavaBean 的规范书写。只是实现了前两条或前三条。达到了最根本的目的:使其他的 Java 类可以通过反射机制发现和操作这些 JavaBean 的属性。
样例:
//这里是一个符合 JavaBean 规范的实体类样例:import java.io.Serializable;//类实现了 Serializable 接口public class User implements Serializable{//所有属性都是私有的private String username;private String password;private String path;//提供一个无参构造方法public User() {}//为私有属性提供 public 的 get/set 方法,以供其他类访问此属性public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}//一般会重写 toString 方便显示信息@Override public String toString() {return "User [username=" + username + ", password=" + password + ", path=" + path + "]";}}
补充说明: Java 提供的 Serializable 接口只是一个空接口(也就是没有默认实现,只做标记用), 在 Java 的语义中,如果一个类实现了 Serializable 接口,那么就代表这个类以及其子 类是自动支持序列化和反序列化的。 当我们让实体类实现 Serializable 接口时,其实是在告诉 JVM 此类可被序列化。是为 了方便以后实现序列化和反序列化功能。但还是要自己来实现具体如何序列化的。例如 将对象写入文件(序列化)从文件中读取对象信息(反序列化)。
文件上传
什么是文件上传:
将本地磁盘文件,通过IO写入到服务器的过程
文件上传的技术:
- Servlet3.0(需要有servlet的开发环境)
- JSPSmartUPload(是第一代jsp+javabean的设计模式经常使用的,一般嵌入到jsp中。)
- FileUpload(通用)
- 框架(很多框架底层使用的也是FileUpload)
文件上传的三要素:
- 表单的提交方式必须是POST,因为不会限制文件的大小
- 表单中需要有文件上传表单项,必须有name属性
<input type="file" name="upload"> - 表单的enctype属性的值需要是
multipart/form-data
文件上传的原理:
利用FileUpload实现文件上传需要引入两个Jar包
//具体的版本号可能不一定相同,可以选择最新下载commons-fileupload-1.2.1.jarcommons-io-1.4.jar
利用multipart/form-data将上传内容进行解析,并产生分割符,将普通文件参数与上传文件进行分割。
FileUpload简介:
常用类API:
- DiskFileItemFactory :磁盘文件项工厂-用于设置上传缓冲区的大小和临时文件存储位置。
- ServletFileUpload :核心解析类-解析请求中的上传文件项。
- FileItem :文件项-可用于分辨是否是文件,还是普通参数。
文件上传示例代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><h1>文件上传</h1><form action="${pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data"><input type="text" name="name"><br/><input type="file" name="upload"><br/><input type="submit" value="上传"></form></body></html>
@WebServlet("/UploadServlet")public class UploadServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1.创建磁盘文件项工厂-空构造会默认设置文件缓冲区和临时文件存放位置DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();// 2.创建核心解析类-配合文件项工厂,生成解析器ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);// 3.解析请求对象,将请求分成几个部分(FileItem)try {List<FileItem> list= fileUpload.parseRequest(request);// 4.遍历集合获得每个部分的对象for(FileItem fileItem:list){// 判断是普通项还是文件上传项if(fileItem.isFormField()){//true 代表普通项 false 代表文件上传项// 普通项// 获得普通项的名称:String name = fileItem.getFieldName();// 获得普通项的值: 注意设置enctype="multipart/form-data",就无法使用getParameter来获取对应参数了String value = fileItem.getString("UTF-8");System.out.println(name+" "+value);}else{// 文件上传项:实际 用户本地硬盘->(输入流) 程序 (输出流)->服务器硬盘// 获得文件的名称:String fileName = fileItem.getName();// 获得文件的输入流:InputStream is = fileItem.getInputStream();// 需要将文件写入到服务器的某个路径即可:String path = getServletContext().getRealPath("/upload");System.out.println(path);// 创建输出流 与 输入流进行对接:OutputStream os = new FileOutputStream(path+"\\"+fileName);int len = 0;byte[] b = new byte[1024];while((len = is.read(b))!=-1){os.write(b, 0, len);}is.close();os.close();}}} catch (FileUploadException e) {// TODO Auto-generated catch blocke.printStackTrace();}}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request, response);}}
验证码
为什么要使用验证码:
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试的缩写,是一种区分用户是计算机还是人的公共全自动程序可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上用验证码是现在很多网站通行的方式,我们利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
验证码生成过程:
- 在内存中生成一个图片
- 生产随机的4个字幕或数字
- 将随机产生的字母或数字写入图片(此时图片还是虚拟的,存储在内存中)
- 将图片显示到页面上
生成验证码代码:
/*** 生成验证码图片*/@WebServlet("/CheckImgServlet")public class CheckImgServlet extends HttpServlet {/*** 取其某一范围的color** @param fc* int 范围参数1* @param bc* int 范围参数2* @return Color*/private Color getRandColor(int fc, int bc) {// 取其随机颜色Random random = new Random();if (fc > 255) {fc = 255;}if (bc > 255) {bc = 255;}int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {int width = 120;int height = 30;// 步骤一 绘制一张内存中图片-需要设置宽高和类型BufferedImage bufferedImage = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);// 步骤二 图片绘制背景颜色 ---通过绘图对象Graphics graphics = bufferedImage.getGraphics();// 得到画图工具对象 --- 画笔// 绘制任何图形之前 都必须指定一个颜色//getRandColor(200, 250)获取graphics.setColor(getRandColor(200, 250));//需要将背景色填充到图片上,x轴起始位置 y轴起始位置 width heightgraphics.fillRect(0, 0, width, height);// 步骤三 绘制边框graphics.setColor(Color.WHITE);//边框不能超过图片宽高graphics.drawRect(0, 0, width - 1, height - 1);// 步骤四 四个随机数字//在Graphics这个画笔工具中没有使文字旋转的方法,所以使用它的子类Graphics2DGraphics2D graphics2d = (Graphics2D) graphics;// 设置输出字体graphics2d.setFont(new Font("宋体", Font.BOLD, 18));//设置验证码字符库String words ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";Random random = new Random();// 生成随机数// 定义x坐标int x = 10;for (int i = 0; i < 4; i++) {// 随机颜色graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));// 旋转 -30 --- 30度int jiaodu = random.nextInt(60) - 30;// 换算弧度double theta = jiaodu * Math.PI / 180;// 生成一个随机数字int index = random.nextInt(words.length()); // 生成随机数 0 到 length - 1// 获得字母数字char c = words.charAt(index);// 将c 输出到图片graphics2d.rotate(theta, x, 20);//设置文字的输出 文字 文字输出位置(每个文字的位置都不同)文字出现位置高度graphics2d.drawString(String.valueOf(c), x, 20);//旋转方法会基于上一次旋转的弧度进行旋转,所以需要使上一次的旋转转回来graphics2d.rotate(-theta, x, 20);x += 30;}// 步骤五 绘制干扰线graphics.setColor(getRandColor(160, 200));int x1;int x2;int y1;int y2;for (int i = 0; i < 30; i++) {x1 = random.nextInt(width);x2 = random.nextInt(12);y1 = random.nextInt(height);y2 = random.nextInt(12);graphics.drawLine(x1, y1, x1 + x2, x2 + y2);}// 将上面图片输出到浏览器 ImageIOgraphics.dispose();// 释放资源// 利用图片io类,进行图片的输出 图片对象 图片格式 响应对象输出流对象ImageIO.write(bufferedImage, "jpg", response.getOutputStream());}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><form action="" method="post">验证码:<input type="text" name="checkcode"/><img src="${pageContext.request.contextPath }/CheckImgServlet"><br/><input type="submit" value="提交"></form></body></html>
