# 登錄注冊功能
[TOC]
## 導學
在我們學習完成Java Web的課程之后,我們通過一個項目完成對之前所學知識的整合,并體會MVC模式下的開發工作。實現頭像上傳,驗證碼校驗等功能。
## MVC設計模式
MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典范,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯聚集到一個部件里面,在改進和個性化定制界面及用戶交互的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用于映射傳統的輸入、處理和輸出功能在一個邏輯的圖形化用戶界面的結構中。
原先我們的代碼隨意存放,堆成一堆。現在要求我們遵循MVC的設計模式,簡單來說,就是要求開發人員將代碼分門別類進行存放。

>[info]Servlet + Jsp + JavaBean 就是MVC模式
Servlet是控制器==Controller
Jsp是頁面展現==View
JavaBean是處理和封裝數據==Model
## 什么是Java Bean
**JavaBean 其實可以看做是一種約定俗成的,實體類書寫規范。目的是使其他的 Java 類可以通過反射機制發現和操作這些 JavaBean 的屬性。**
定義: **JavaBean 其實就是按照一種規范書寫的代表實體的 Java 類**。名稱中的“Bean”是用 于 Java 的可重用軟件組件的慣用叫法。
規范嚴格意義上需要有一下四點:
1. 屬性私有
2. 提供 getset 方法(public 聲明,命名符合規范)操作私有屬性
3. 提供一個無參構造方法
4. 實現了 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.jar
commons-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 block
e.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的問題,所以回答出問題的用戶就可以被認為是人類。
**驗證碼生成過程:**
1. 在內存中生成一個圖片
2. 生產隨機的4個字幕或數字
3. 將隨機產生的字母或數字寫入圖片(此時圖片還是虛擬的,存儲在內存中)
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 height
graphics.fillRect(0, 0, width, height);
// 步驟三 繪制邊框
graphics.setColor(Color.WHITE);
//邊框不能超過圖片寬高
graphics.drawRect(0, 0, width - 1, height - 1);
// 步驟四 四個隨機數字
//在Graphics這個畫筆工具中沒有使文字旋轉的方法,所以使用它的子類Graphics2D
Graphics2D 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);
}
// 將上面圖片輸出到瀏覽器 ImageIO
graphics.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>
~~~