[TOC]
# 步驟 1 : 頁面
增加分類的頁面是做在上查詢結果頁面的·
需要注意幾點:
1. form的action="admin_category_add",會導致訪問CategoryServlet的add()方法
2. method="post" 用于保證中文的正確提交
3. 必須有enctype="multipart/form-data",這樣才能上傳文件
4. accept="image/*" 這樣把上傳的文件類型限制在了圖片

~~~
<div class="panel panel-warning addDiv">
<div class="panel-heading">新增分類</div>
<div class="panel-body">
<form method="post" id="addForm" action="admin_category_add" enctype="multipart/form-data">
<table class="addTable">
<tr>
<td>分類名稱</td>
<td><input id="name" name="name" type="text" class="form-control"></td>
</tr>
<tr>
<td>分類圖片</td>
<td>
<input id="categoryPic" accept="image/*" type="file" name="image" />
</td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
~~~
# 步驟 2: 為空判斷
對分類名稱和分類圖片做了為空判斷,當為空的時候,不能提交
其中用到的函數checkEmpty,在adminHeader.jsp 中定義

~~~
<script>
$(function(){
$("#addForm").submit(function(){
if(!checkEmpty("name","分類名稱"))
return false;
if(!checkEmpty("categoryPic","分類圖片"))
return false;
return true;
});
});
</script>
~~~
# 步驟 3 : 提交數據
當填寫了名稱,并且選中了圖片之后,就可以提交數據。
提交數據會導致CategoryServlet.add()方法被調用
# 步驟 4 : 獲取上傳文件的輸入流
前部分代碼是固定寫法,用來做一些準備工作。
```
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 設置上傳文件的大小限制為10M
factory.setSizeThreshold(1024 * 10240);
```
直到遍歷出Item,一個Item就是對應一個瀏覽器提交的數據。
```
List items = upload.parseRequest(request);
```
因為瀏覽器指定了以二進制的形式提交數據,那么就不能通過常規的手段獲取非File字段,比如:
```
request.getParameter("heroName")
```
在遍歷Item時(Item即對應瀏覽器提交的字段),可以通過
```
item.isFormField
```
來判斷是否是常規字段還是提交的文件。 當item.isFormField返回true的時候,就表示是常規字段。
然后通過item.getFieldName()和item.getString()就知道分別是哪個字段,以及字段的值了。
```
package com.dodoke.controller;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.annotation.WebServlet;
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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.dodoke.dao.impl.CategoryDaoImpl;
import com.dodoke.dao.inter.CategoryDao;
import com.dodoke.util.Page;
/**
* Servlet implementation class BaseBackServlet
*/
@WebServlet("/BaseBackServlet")
public abstract class BaseBackServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public abstract String add(HttpServletRequest request, HttpServletResponse response);
public abstract String delete(HttpServletRequest request, HttpServletResponse response);
public abstract String edit(HttpServletRequest request, HttpServletResponse response);
public abstract String update(HttpServletRequest request, HttpServletResponse response);
public abstract String list(HttpServletRequest request, HttpServletResponse response, Page page);
public CategoryDao categoryDao = new CategoryDaoImpl();
public void service(HttpServletRequest request, HttpServletResponse response) {
int start = 0;
int count = 5;
try {
start = Integer.parseInt(request.getParameter("page.start"));
count = Integer.parseInt(request.getParameter("page.count"));
} catch (Exception e) {
e.printStackTrace();
}
Page page = new Page(start, count);
try {
/* 借助反射,調用對應的方法 */
String method = (String) request.getAttribute("method");
Method m;
String redirect;
if ("list".equals(method)) {
m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
javax.servlet.http.HttpServletResponse.class, Page.class);
redirect = m.invoke(this, request, response, page).toString();
} else {
m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
javax.servlet.http.HttpServletResponse.class);
redirect = m.invoke(this, request, response).toString();
}
/* 根據方法的返回值,進行相應的客戶端跳轉,服務端跳轉,或者僅僅是輸出字符串 */
System.out.println(redirect);
if (redirect.startsWith("@")) {
response.sendRedirect(redirect.substring(1));
} else if (redirect.startsWith("%")) {
response.getWriter().print(redirect.substring(1));
} else {
request.getRequestDispatcher(redirect).forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
*
* @param request
* @param params
* @return
*/
public InputStream parseUpload(HttpServletRequest request, Map<String, String> params) {
InputStream is = null;
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 設置上傳文件的大小限制為10M
factory.setSizeThreshold(1024 * 10240);
List items = upload.parseRequest(request);
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (!item.isFormField()) {
// item.getInputStream() 獲取上傳文件的輸入流
is = item.getInputStream();
} else {
String paramName = item.getFieldName();
String paramValue = item.getString();
paramValue = new String(paramValue.getBytes("ISO-8859-1"), "UTF-8");
params.put(paramName, paramValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return is;
}
}
```
# 步驟 5 : 接受數據并處理
在add()方法中做了如下操作:
1\. parseUpload 獲取上傳文件的輸入流
2\. parseUpload 方法會修改params 參數,并且把瀏覽器提交的name信息放在其中
3\. 從params 中取出name信息,并根據這個name信息,借助categoryDAO,向數據庫中插入數據。
4\. 根據request.getServletContext().getRealPath( "img/category"),定位到存放分類圖片的目錄
5\. 文件命名以保存到數據庫的分類對象的id+".jpg"的格式命名
6\. 根據步驟1獲取的輸入流,把瀏覽器提交的文件,復制到目標文件
7\. 借助ImageUtil.change2jpg()方法把格式真正轉化為jpg,而不僅僅是后綴名為.jpg
> 注:
> 1. 為什么不能直接使用request.getParameter("name")的方式來獲取數據?
> 因為當瀏覽器提交的數據是二進制的時候,Servlet不能夠通過這種方式直接獲取參數。
> 2. 為什么要用request.getServletContext().getRealPath( )的方式定位d:/xxxxx/xxxx/x/xxx/img/category 這目錄,而不是用硬編碼寫死?
因為在部署到Linux 實際運行的時候,Linux上的目錄就有是其他的路徑了,比如 /usr/public/tmall/img/category,只有采用這種方式才能兼容
> 3. 第七步為什么要這么做?
因為瀏覽器提交來的圖片文件,有可能是png,gif,bmp等非jpg格式的圖片。 僅僅修改文件的后綴名有可能會導致顯示異常。
```
package com.dodoke.controller;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.dodoke.bean.Category;
import com.dodoke.util.ImageUtil;
import com.dodoke.util.Page;
/**
* Servlet implementation class CategoryServlet
*/
@WebServlet("/CategoryServlet")
public class CategoryServlet extends BaseBackServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public String add(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> params = new HashMap<>();
// 獲取上傳文件的輸入流
InputStream is = super.parseUpload(request, params);
// 獲取參數
String name = params.get("name");
Category c = new Category();
c.setName(name);
// 新增分類
categoryDao.add(c);
// 設置文件路徑
File imageFolder = new File(request.getSession().getServletContext().getRealPath("img/category"));
// 根據文件目錄和文件名稱,創建文件對象
File file = new File(imageFolder, c.getId() + ".jpg");
// 創建目錄(若沒有category,則創建;否者,不做處理)
file.getParentFile().mkdirs();
try {
// inputStream.available()查看流的大小
if (null != is && 0 != is.available()) {
// 復制文件
// 創建一個輸出流對象
try (FileOutputStream fos = new FileOutputStream(file)) {
// 創建一個接受1024kb大小的byte數組
byte b[] = new byte[1024 * 1024];
int length = 0;
// 循環從is流中每次讀取1024Kb大小的字節,并通過fs.read(b),將其存儲到byte數組b中
// read方法就是讀取輸入流到字節數組中,當返回值是0的時候,表示讀完了,再繼續讀就返回-1
while (-1 != (length = is.read(b))) {
// 將b數組里指定長度length的內容寫入
fos.write(b, 0, length);
}
// 有的時候,需要立即把數據寫入到硬盤,而不是等緩存滿了才寫出去。 這時候就需要用到flush
fos.flush();
// 通過如下代碼,把文件保存為jpg格式
BufferedImage img = ImageUtil.change2jpg(file);
ImageIO.write(img, "jpg", file);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "@admin_category_list";
}
public String delete(HttpServletRequest request, HttpServletResponse response) {
int id = Integer.parseInt(request.getParameter("id"));
categoryDao.delete(id);
return "@admin_category_list";
}
public String edit(HttpServletRequest request, HttpServletResponse response) {
int id = Integer.parseInt(request.getParameter("id"));
Category c = categoryDao.get(id);
request.setAttribute("c", c);
return "admin/editCategory.jsp";
}
public String update(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> params = new HashMap<>();
InputStream is = super.parseUpload(request, params);
System.out.println(params);
String name = params.get("name");
int id = Integer.parseInt(params.get("id"));
Category c = new Category();
c.setId(id);
c.setName(name);
categoryDao.update(c);
File imageFolder = new File(request.getSession().getServletContext().getRealPath("img/category"));
File file = new File(imageFolder, c.getId() + ".jpg");
file.getParentFile().mkdirs();
try {
if (null != is && 0 != is.available()) {
try (FileOutputStream fos = new FileOutputStream(file)) {
byte b[] = new byte[1024 * 1024];
int length = 0;
while (-1 != (length = is.read(b))) {
fos.write(b, 0, length);
}
fos.flush();
BufferedImage img = ImageUtil.change2jpg(file);
ImageIO.write(img, "jpg", file);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "@admin_category_list";
}
public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
List<Category> cs = categoryDao.list(page.getStart(), page.getCount());
int total = categoryDao.getTotal();
page.setTotal(total);
request.setAttribute("thecs", cs);
request.setAttribute("page", page);
return "admin/listCategory.jsp";
}
}
```
# 步驟 6 : ImageUtil工具類
ImageUtil 工具類提供3個方法
1. change2jpg
確保圖片文件的二進制格式是jpg。
僅僅通過`ImageIO.write(img, "jpg", file);`不足以保證轉換出來的jpg文件顯示正常。這段轉換代碼,可以確保轉換后jpg的圖片顯示正常,而不會出現暗紅色( 有一定幾率出現)。 這也是百度上找到的。不過找了很多代碼哦,才找到這一段能真正生效,而且不會發生錯誤的。
2. 后兩種resizeImage用于改變圖片大小,在上傳產品圖片的時候會用到。 這里不展開,到時候再講
~~~
package com.dodoke.tmall.util;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.PixelGrabber;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageUtil {
/**
* 把圖片強制轉換為jpg格式,獲得BufferedImage對象
* @param f 圖片文件
* @return BufferedImage
*/
public static BufferedImage change2jpg(File f) {
try {
Image i = Toolkit.getDefaultToolkit().createImage(f.getAbsolutePath());
PixelGrabber pg = new PixelGrabber(i, 0, 0, -1, -1, true);
pg.grabPixels();
int width = pg.getWidth(), height = pg.getHeight();
final int[] RGB_MASKS = { 0xFF0000, 0xFF00, 0xFF };
final ColorModel RGB_OPAQUE = new DirectColorModel(32, RGB_MASKS[0], RGB_MASKS[1], RGB_MASKS[2]);
DataBuffer buffer = new DataBufferInt((int[]) pg.getPixels(), pg.getWidth() * pg.getHeight());
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, RGB_MASKS, null);
BufferedImage img = new BufferedImage(RGB_OPAQUE, raster, false, null);
return img;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void resizeImage(File srcFile, int width,int height, File destFile) {
try {
if(!destFile.getParentFile().exists())
destFile.getParentFile().mkdirs();
Image i = ImageIO.read(srcFile);
i = resizeImage(i, width, height);
ImageIO.write((RenderedImage) i, "jpg", destFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Image resizeImage(Image srcImage, int width, int height) {
try {
BufferedImage buffImg = null;
buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
buffImg.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
return buffImg;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
~~~
# 步驟 7 : 中文問題
中文問題,統一交由EncodingFilter來進行處理
```
request.setCharacterEncoding("UTF-8");
```
對提交的數據進行UTF-8編碼。
其他的配合動作
1. 在jsp中要加上
```
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*"%>
```
其中contentType="text/html; charset=UTF-8"的作用是告訴瀏覽器提交數據的時候,使用UTF-8編碼
2. 在form里method="post" 才能正確提交中文
EncodingFilter:
```
package com.dodoke.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class EncodingFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
```
web.xml:
```
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>tmall_j2ee</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.dodoke.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>BackServletFilter</filter-name>
<filter-class>com.dodoke.filter.BackServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BackServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
```
# 步驟 8 : 客戶端跳轉
最后,add() 方法里返回了
return "@admin\_category\_list";
這樣就客戶端跳轉到了頁面:
http://127.0.0.1:8080/tmall/admin\_category\_list
為什么這樣就能進行客戶端跳轉? 這部分邏輯在BaseBackServlet的service() 方法的72-78行:
當返回值以"@"開頭的時候,就進行客戶端跳轉response.sendRedirect(redirect.substring(1));
```
/* 根據方法的返回值,進行相應的客戶端跳轉,服務端跳轉,或者僅僅是輸出字符串 */
System.out.println(redirect);
if (redirect.startsWith("@")) {
response.sendRedirect(redirect.substring(1));
} else if (redirect.startsWith("%")) {
response.getWriter().print(redirect.substring(1));
} else {
request.getRequestDispatcher(redirect).forward(request, response);
}
```
- 項目簡介
- 功能一覽
- 前臺
- 后臺
- 開發流程
- 需求分析-展示
- 首頁
- 產品頁
- 分類頁
- 搜索結果頁
- 購物車查看頁
- 結算頁
- 確認支付頁
- 支付成功頁
- 我的訂單頁
- 確認收貨頁
- 評價頁
- 頁頭信息展示
- 需求分析-交互
- 分類頁排序
- 立即購買
- 加入購物車
- 調整訂單項數量
- 刪除訂單項
- 生成訂單
- 訂單頁功能
- 確認付款
- 確認收貨
- 提交評價信息
- 登錄
- 注冊
- 退出
- 搜索
- 前臺需求列表
- 需求分析后臺
- 分類管理
- 屬性管理
- 產品管理
- 產品圖片管理
- 產品屬性設置
- 用戶管理
- 訂單管理
- 后臺需求列表
- 表結構設計
- 數據建模
- 表與表之間的關系
- 實體類設計
- DAO類設計
- 工具類
- CategoryDao設計
- Service業務類設計
- 后臺-分類管理
- 可運行的項目
- 靜態資源
- FILTER配合SERVLET
- JSP包含關系
- 查詢
- 分頁
- 增加
- 刪除
- 編輯
- 修改
- 后臺其他管理
- 屬性管理
- 產品管理
- 產品圖片管理
- 產品屬性值設置
- 用戶管理
- 訂單管理