💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[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); } ```