[TOC]
## 步驟 1 : 先運行,看到效果,再學習
先將完整的 spring 項目(向老師要相關資料),配置運行起來,確認可用之后,再學習做了哪些步驟以達到這樣的效果。
## 步驟 2 : 模仿和排錯
在確保可運行項目能夠正確無誤地運行之后,再嚴格照著教程的步驟,對代碼模仿一遍。
模仿過程難免代碼有出入,導致無法得到期望的運行結果,此時此刻通過比較**正確答案** ( 可運行項目 ) 和自己的代碼,來定位問題所在。
采用這種方式,**學習有效果,排錯有效率**,可以較為明顯地提升學習速度,跨過學習路上的各個檻。
## 步驟 3 : 效果
如圖所示,新增加的分類,就是有圖片的了。

## 步驟 4 : 頁面中的增加分類部分
增加分類的代碼是整合在listCategory.jsp中的
需要注意幾點:
1. `method="post"` 用于保證中文的正確提交
2. 必須有`enctype="multipart/form-data"`,這樣才能上傳文件
3. `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>
~~~
## 步驟 5 : 為空判斷
對分類名稱和分類圖片做了為空判斷,當為空的時候,不能提交
其中用到的函數checkEmpty,在adminHeader.jsp 中定義

~~~
<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`
填寫分類名稱,選擇分類圖片,點擊提交。
效果:

- 項目簡介
- 功能一覽
- 前臺
- 后臺
- 開發流程
- 需求分析-展示
- 首頁
- 產品頁
- 分類頁
- 搜索結果頁
- 購物車查看頁
- 結算頁
- 確認支付頁
- 支付成功頁
- 我的訂單頁
- 確認收貨頁
- 確認收貨成功頁
- 評價頁
- 需求分析-交互
- 分類頁排序
- 立即購買
- 加入購物車
- 調整訂單項數量
- 刪除訂單項
- 生成訂單
- 訂單頁功能
- 確認付款
- 確認收貨
- 提交評價信息
- 登錄
- 注冊
- 退出
- 搜索
- 前臺需求列表
- 需求分析后臺
- 分類管理
- 屬性管理
- 產品管理
- 產品圖片管理
- 產品屬性設置
- 用戶管理
- 訂單管理
- 后臺需求列表
- 表結構設計
- 數據建模
- 表與表之間的關系
- 后臺-分類管理
- 可運行的項目
- 靜態資源
- JSP包含關系
- 查詢
- 分頁
- 增加
- 刪除
- 編輯
- 修改
- 做一遍
- 重構
- 分頁方式
- 分類逆向工程
- 所有逆向工程
- 后臺其他頁面
- 屬性管理實現
- 產品管理實現
- 產品圖片管理實現
- 產品屬性值設置
- 用戶管理實現
- 訂單管理實現
- 前端
- 前臺-首頁
- 可運行的項目
- 靜態資源
- ForeController
- home方法
- home.jsp
- homePage.jsp
- 前臺-無需登錄
- 注冊
- 登錄
- 退出
- 產品頁
- 模態登錄
- 分類頁
- 搜索
- 前臺-需要登錄
- 購物流程
- 立即購買
- 結算頁面
- 加入購物車
- 查看購物車頁面
- 登錄狀態攔截器
- 其他攔截器
- 購物車頁面操作
- 訂單狀態圖
- 生成訂單
- 我的訂單頁
- 我的訂單頁操作
- 評價產品
- 總結