[TOC]
## 步骤 1 : 先运行,看到效果,再学习
先将完整的 spring 项目(向老师要相关资料),配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
## 步骤 2 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较**正确答案** ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,**学习有效果,排错有效率**,可以较为明显地提升学习速度,跨过学习路上的各个槛。
## 步骤 3 : 效果
如图所示,新增加的分类,就是有图片的了。
![](https://box.kancloud.cn/00ce52e0fabd1b8ff78dc932b31f087f_1385x650.png)
## 步骤 4 : 页面中的增加分类部分
增加分类的代码是整合在listCategory.jsp中的
需要注意几点:
1. `method="post"` 用于保证中文的正确提交
2. 必须有`enctype="multipart/form-data"`,这样才能上传文件
3. `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>
~~~
## 步骤 5 : 为空判断
对分类名称和分类图片做了为空判断,当为空的时候,不能提交
其中用到的函数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>
~~~
## 步骤 6 : CategoryMapper.xml
在CategoryMapper.xml中新增加 插入分类的SQL语句
> 注: 需要加上2个属性:`keyProperty="id" useGeneratedKeys="true"` 以确保Category对象通过mybatis增加到数据库之后得到的id增长值会被设置在Category对象上。 因为在保存分类图片的时候需要用到这个id值,所以这一步是必须的。
~~~
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dodoke.tmall.mapper.CategoryMapper">
<select id="list" resultType="Category">
select * from t_category order by id desc
<if test="start!=null and count!=null">
limit #{start},#{count}
</if>
</select>
<select id="total" resultType="int">
select count(*) from t_category
</select>
<insert id="add" keyProperty="id" useGeneratedKeys="true" parameterType="Category">
insert into t_category(name) values(#{name})
</insert>
</mapper>
~~~
## 步骤 7 : CategoryMapper
新增add方法
~~~
package com.dodoke.tmall.mapper;
import java.util.List;
import com.dodoke.tmall.pojo.Category;
import com.dodoke.tmall.util.Page;
public interface CategoryMapper {
List<Category> list(Page page);
public int total();
void add(Category category);
}
~~~
## 步骤 8 : CategoryService
新增add方法
~~~
package com.dodoke.tmall.service;
import java.util.List;
import com.dodoke.tmall.pojo.Category;
import com.dodoke.tmall.util.Page;
public interface CategoryService {
List<Category> list(Page page);
int total();
void add(Category category);
}
~~~
## 步骤 9 : CategoryServiceImpl
新增add方法的实现
~~~
package com.dodoke.tmall.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.dodoke.tmall.mapper.CategoryMapper;
import com.dodoke.tmall.pojo.Category;
import com.dodoke.tmall.service.CategoryService;
import com.dodoke.tmall.util.Page;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
CategoryMapper categoryMapper;
@Override
public List<Category> list(Page page) {
return categoryMapper.list(page);
}
@Override
public int total() {
return categoryMapper.total();
}
@Override
public void add(Category category) {
categoryMapper.add(category);
}
}
~~~
## 步骤 10 : UploadedImageFile
新增UploadedImageFile ,其中有一个MultipartFile 类型的属性,用于接受上传文件的注入。
> 注: 这里的属性名称image必须和页面中的增加分类部分中的`type="file"`的name值保持一致。
`<input id="categoryPic" accept="image/*" type="file" name="image" />`
~~~
package com.dodoke.tmall.util;
import org.springframework.web.multipart.MultipartFile;
public class UploadedImageFile {
MultipartFile image;
public MultipartFile getImage() {
return image;
}
public void setImage(MultipartFile image) {
this.image = image;
}
}
~~~
## 步骤 11 : 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;
}
}
~~~
## 步骤 12 : CategoryController
CategoryController新增add方法
1. add方法映射路径admin_category_add的访问
* 参数 Category c接受页面提交的分类名称。name对应`category.name`
`input id="name" name="name" type="text" class="form-control"></td>`
* 参数 session 用于在后续获取当前应用的路径
* UploadedImageFile 用于接受上传的图片
2. 通过categoryService保存c对象
3. 通过session获取ControllerContext,再通过getRealPath定位存放分类图片的路径。
4. 根据分类id创建文件名
5. 如果`/img/category`目录不存在,则创建该目录,否则后续保存浏览器传过来图片,会提示无法保存
6. 通过UploadedImageFile 把浏览器传递过来的图片保存在上述指定的位置
7. 通过`ImageUtil.change2jpg(file); `确保图片格式一定是jpg,而不仅仅是后缀名是jpg.
8. 客户端跳转到admin_category_list
~~~
package com.dodoke.tmall.controller;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.dodoke.tmall.pojo.Category;
import com.dodoke.tmall.service.CategoryService;
import com.dodoke.tmall.util.ImageUtil;
import com.dodoke.tmall.util.Page;
import com.dodoke.tmall.util.UploadedImageFile;
@RequestMapping("")
@Controller
public class CategoryController {
@Autowired
CategoryService categoryService;
@RequestMapping("admin_category_list")
public String list(Model model,Page page) {
List<Category> cs = categoryService.list(page);
int total = categoryService.total();
page.setTotal(total);
model.addAttribute("cs", cs);
model.addAttribute("page",page);
return "admin/listCategory";
}
/**
* 新增分类
* @param c 分类对象
* @param session 用于在后续获取当前应用的路径
* @param uploadedImageFile 用于接受上传的图片
* @return 页面路径
* @throws IOException
*/
@RequestMapping("admin_category_add")
public String add(Category c, HttpSession session, UploadedImageFile uploadedImageFile) throws IOException {
// 新增分类
categoryService.add(c);
// 通过session获取ControllerContext,再通过getRealPath定位存放分类图片的路径。
File imageFolder= new File(session.getServletContext().getRealPath("img/category"));
// 根据分类id创建文件名
File file = new File(imageFolder,c.getId() + ".jpg");
// 如果/img/category目录不存在,则创建该目录,否则后续保存浏览器传过来图片,会提示无法保存
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
System.out.println(file);
// 通过UploadedImageFile 把浏览器传递过来的图片保存在上述指定的位置
uploadedImageFile.getImage().transferTo(file);
// 通过ImageUtil.change2jpg(file); 确保图片格式一定是jpg,而不仅仅是后缀名是jpg.
BufferedImage img = ImageUtil.change2jpg(file);
// 重新写入图片
ImageIO.write(img, "jpg", file);
// 客户端跳转到admin_category_list
return "redirect:/admin_category_list";
}
}
~~~
* 问题1:请问multipartfile转换和`imageIO.write`方法是否重复
> 并不重复
> 1. transfer是把浏览器上传的文件保存在服务器
> 2. 把图片强制转换为jpg格式,获得BufferedImage对象
> 3. 将转化的对象保存到服务器覆盖原有图片。确保保存格式是jpg。 否则有可能是png,bmp,gif等其他格式的。
>
* 问题2:为什么要把图片装换成jpg格式呢,用上传的原格式如png格式,不行吗,这样转换有什么好处吗
> 如果要使用原文件的后缀名,那么系统就必须记录后缀名名称。 而且除了png,还会jpg,gif,bmp等等其他格式,都要格外记录。 转换为jpg,那么就不需要记录这个信息了,大家都是jpg,方便管理
>
* 问题3:如何理解`session.getServletContext().getRealPath("img/category")`
> `request.getSession().getServletContext()` 获取的是Servlet容器对象,相当于tomcat容器了.
> `getRealPath(“/”) `获取实际路径,“/”指代项目根目录,所以代码返回的是项目在容器中的实际发布运行的根路径。
> `session.getServletContext().getRealPath("img/category")`这个路径就是“根路径`/img/category`”。
> 在我的电脑是(可以通过Debug模式,打断点查看):`D:\project\spring\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\tmall_ssm\img\category`
>
* 问题4:图片是如何和数据库的字段联系起来的呢?
> 图片保存成文件的时候,使用的文件名是其对应数据库数据的id字段的值
* 问题5:add方法中,Category对象,图片对象等,获取不到前台传过来的值。
> 确认SpringMVC是否添加文件解析。
```
<!-- 对上传文件的解析 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
```
## 步骤 13 : 中文问题
中文问题,统一交由web.xml 中定义的`org.springframework.web.filter.CharacterEncodingFilter`来进行处理
其他的配合动作
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" `才能正确提交中文
## 步骤 14 : 测试
测试地址:`http://localhost:8080/tmall_ssm/admin_category_list`
填写分类名称,选择分类图片,点击提交。
效果:
![](https://box.kancloud.cn/00ce52e0fabd1b8ff78dc932b31f087f_1385x650.png)
- 项目简介
- 功能一览
- 前台
- 后台
- 开发流程
- 需求分析-展示
- 首页
- 产品页
- 分类页
- 搜索结果页
- 购物车查看页
- 结算页
- 确认支付页
- 支付成功页
- 我的订单页
- 确认收货页
- 确认收货成功页
- 评价页
- 需求分析-交互
- 分类页排序
- 立即购买
- 加入购物车
- 调整订单项数量
- 删除订单项
- 生成订单
- 订单页功能
- 确认付款
- 确认收货
- 提交评价信息
- 登录
- 注册
- 退出
- 搜索
- 前台需求列表
- 需求分析后台
- 分类管理
- 属性管理
- 产品管理
- 产品图片管理
- 产品属性设置
- 用户管理
- 订单管理
- 后台需求列表
- 表结构设计
- 数据建模
- 表与表之间的关系
- 后台-分类管理
- 可运行的项目
- 静态资源
- JSP包含关系
- 查询
- 分页
- 增加
- 删除
- 编辑
- 修改
- 做一遍
- 重构
- 分页方式
- 分类逆向工程
- 所有逆向工程
- 后台其他页面
- 属性管理实现
- 产品管理实现
- 产品图片管理实现
- 产品属性值设置
- 用户管理实现
- 订单管理实现
- 前端
- 前台-首页
- 可运行的项目
- 静态资源
- ForeController
- home方法
- home.jsp
- homePage.jsp
- 前台-无需登录
- 注册
- 登录
- 退出
- 产品页
- 模态登录
- 分类页
- 搜索
- 前台-需要登录
- 购物流程
- 立即购买
- 结算页面
- 加入购物车
- 查看购物车页面
- 登录状态拦截器
- 其他拦截器
- 购物车页面操作
- 订单状态图
- 生成订单
- 我的订单页
- 我的订单页操作
- 评价产品
- 总结