[TOC]
# 步骤 1 : 页面
增加分类的页面是做在上查询结果页面的·
需要注意几点:
1. form的action="admin_category_add",会导致访问CategoryServlet的add()方法
2. method="post" 用于保证中文的正确提交
3. 必须有enctype="multipart/form-data",这样才能上传文件
4. accept="image/*" 这样把上传的文件类型限制在了图片
![](https://box.kancloud.cn/68b914db7c091d7e589fc9e77f246f44_525x265.png)
~~~
<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 中定义
![](https://box.kancloud.cn/bff31d7d842102149d3f299a1e96e3bd_566x158.png)
~~~
<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包含关系
- 查询
- 分页
- 增加
- 删除
- 编辑
- 修改
- 后台其他管理
- 属性管理
- 产品管理
- 产品图片管理
- 产品属性值设置
- 用户管理
- 订单管理