打開天貓首頁苍柏,可以看到基本分為3大塊:
- 商品分頁列表+主推商品廣告
- 知名品牌區(qū)域
- “天貓超市”节视、“居家生活”等按主題分塊的商品區(qū)
這3大塊可以分別用不同的模塊處理:
- 分類管理模塊:建立不同分類以及用分類劃分商品澜搅;主推商品模塊鳄橘。
- 品牌管理模塊:返回品牌活動和品牌logo
- 主題模塊:返回主題列表以及主題內(nèi)部的商品
分類管理
首先建立一個分類管理的后臺頁面折柠,用來添加讳苦、刪除和修改分類带膜。
為了返回這個頁面,需要:
1.在數(shù)據(jù)庫建表category
CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)
2.有了數(shù)據(jù)庫表鸳谜,再配合mybatis-generator插件就可以生成對應(yīng)的pojo膝藕、mapper和mapper的xml文件了。有了這些基本的數(shù)據(jù)操作有了咐扭,不過還需要做一些修改芭挽。用法參考IDEA中mybatis-generator插件的使用
3.需要一個Controller來處理當(dāng)前的頁面的業(yè)務(wù)滑废,所以建立一個CategoryController
,并提供分裂列表獲取的方法:
package com.shiwei.tmall.controller;
import ***(略)
@Controller
@RequestMapping("")
public class CategoryController {
@Autowired
CategoryService categoryService;
@RequestMapping("/admin_category_list")
public String list(Model model, Page page){
page.setCount(30);
PageHelper.offsetPage(page.getStart(), page.getCount());
List<Category> categories = categoryService.list();
int total = (int) new PageInfo<>(categories).getTotal();
page.setTotal(total);
model.addAttribute("cs", categories);
model.addAttribute("page", page);
return "admin/listCategory";
}
}
這里需要說明:
- 使用了一個CategoryService對象,因?yàn)闉榱思軜?gòu)清晰袜爪,加入了service層蠕趁。每一個service對應(yīng)一個具體的業(yè)務(wù),controller通過組合調(diào)用不同的service來完成任務(wù)辛馆。在service層選擇連接不同的DAO層俺陋,可以是數(shù)據(jù)庫也可以是Redis,這樣可以是項(xiàng)目更靈活昙篙。
-
@Autowired
是為了能夠自動注入categoryService腊状。自動注入的好處是解耦,spring容器會根據(jù)類型自動給categoryService賦值瓢对,如果要替換寿酌,只需要替換容器中的bean就可以了胰苏,而不需要動controller的代碼硕蛹。 - 這里使用了
PageHelper
插件,它是通過MyBatis的插件功能硕并,修改了select語句的sql法焰,添加了limit
屬性從而實(shí)現(xiàn)分頁。
4.創(chuàng)建CategoryService以及實(shí)現(xiàn)類CategoryServiceImpl
:
public interface CategoryService {
List<Category> list();
void add(Category category);
void delete(Integer id);
Category get(Integer id);
void update(Category category);
}
@Service
public class CategoryServiceImpl implements CategoryService {
private CategoryMapper categoryMapper = null;
@Autowired
public CategoryServiceImpl(CategoryMapper categoryMapper){
this.categoryMapper = categoryMapper;
}
@Override
public List<Category> list() {
CategoryExample example =new CategoryExample();
example.setOrderByClause("id");
return categoryMapper.selectByExample(example);
}
@Override
public void add(Category category) {
categoryMapper.insert(category);
}
@Override
public void delete(Integer id) {
categoryMapper.deleteByPrimaryKey(id);
}
@Override
public Category get(Integer id) {
return categoryMapper.selectByPrimaryKey(id);
}
@Override
public void update(Category category) {
categoryMapper.updateByPrimaryKeySelective(category);
}
}
需要說明幾點(diǎn):
- 為什么采用接口和實(shí)現(xiàn)類的模式而不是直接使用一個類倔毙?個人理解是為了以后更好的擴(kuò)展埃仪,當(dāng)有多個不同的邏輯實(shí)現(xiàn)并存時(shí),可以更方便的替換實(shí)現(xiàn)陕赃÷羊龋總的思想還是源于實(shí)現(xiàn)和聲明的分離,更深入的還需要以后更多項(xiàng)目的認(rèn)識么库。這里的討論也不錯
- 使用MyBatis插件自動生成的mapper會額外增加一個example類傻丝,是用于查詢的。這個的一個好處是可以一定程度防止SQL注入诉儒,因?yàn)镾QL注入是通過把sql語句偽裝成參數(shù)傳入葡缰,從而修改了sql語句的意思。比如
select * from user where id = 197837 and 1=1
,這里的197837 and 1=1
是傳入的參數(shù)忱反,但是卻會查出所有表數(shù)據(jù)泛释,但是使用example查詢時(shí)是參數(shù)綁定的方式,實(shí)際編譯的sql是select * from user where id = ?
,這樣sql的語義至少不會因?yàn)閭鲄⒍恍薷摹?/li>
這樣分類信息就查詢出來了温算,下面就是對分類的修改怜校。
分類新增和編輯
對應(yīng)的Controller代碼:
//保存分類圖片
private void saveCategoryImage(String homePath, MultipartFile imgFile, Integer categoryId) throws IOException{
//判空處理
if (imgFile == null || imgFile.isEmpty()){
return;
}
File imageFolder= new File(homePath+"/img/category");
File file = new File(imageFolder,categoryId+".jpg");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
imgFile.transferTo(file);
}
@RequestMapping("/admin_category_add")
public String add(Category category, HttpSession session, MultipartFile imgFile) throws IOException {
if (category.getName() == null){
return "";
}
categoryService.add(category);
saveCategoryImage(session.getServletContext().getRealPath("/"), imgFile, category.getId());
return "redirect:/admin_category_list";
}
@RequestMapping("admin_category_update")
public String update(Category category, HttpSession session, MultipartFile imgFile, @Param("id") Integer id) throws IOException{
categoryService.update(category);
saveCategoryImage(session.getServletContext().getRealPath("/"), imgFile, category.getId());
return "redirect:admin_category_list";
}
插入category數(shù)據(jù)很簡單,service和mapper都準(zhǔn)備好了注竿,這里需要注意的是上傳圖片的功能茄茁。上傳圖片使用HTTP POST的multipart/form-data類型宇智,這個前端都有相應(yīng)的框架支持,后端使用MultipartFile
類型接受胰丁,它源于org.springframework.web.multipart
包随橘,springMVC會處理從上傳文件到這個類型的轉(zhuǎn)化,前提是需要開啟轉(zhuǎn)化器的支持:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
</bean>
拿到圖片數(shù)據(jù)后锦庸,寫入本地机蔗,這里使用了session.getServletContext().getRealPath("/")
來獲取項(xiàng)目的絕對路徑,然后由此確定圖片存儲位置甘萧。
如果圖片功能更復(fù)雜萝嘁,比如還需要生成對應(yīng)的各種尺寸縮略圖、圖片是否違法違規(guī)的核查等扬卷,就需要一個圖片處理的service和單獨(dú)的處理模塊來支持了牙言。
產(chǎn)品和分類關(guān)聯(lián)
分類的作用是用來將產(chǎn)品分組,需要把產(chǎn)品歸類到一個個分類里怪得,所以需要:
- 一個產(chǎn)品的模型咱枉,包括數(shù)據(jù)庫表,pojo和對應(yīng)的mapper等
- 因?yàn)橐粋€分類里有多個產(chǎn)品徒恋,分類和產(chǎn)品屬于“1對多”的關(guān)系蚕断,所以通過在產(chǎn)品的表里加入外鍵來實(shí)現(xiàn)和分類的關(guān)聯(lián):
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`subTitle` varchar(255) DEFAULT NULL,
`originalPrice` float DEFAULT NULL,
`promotePrice` float DEFAULT NULL,
`stock` int(11) DEFAULT NULL,
`cid` int(11) DEFAULT NULL,
`createDate` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_product_category` (`cid`),
CONSTRAINT `fk_product_category` FOREIGN KEY (`cid`) REFERENCES `category` (`id`)
)
這里通過一個外鍵,把字段cid
關(guān)聯(lián)到category表的id
上了入挣。
- 需要一個創(chuàng)建產(chǎn)品并且劃分類別的頁面:
產(chǎn)品的業(yè)務(wù)就需要新增一個ProductController,在這里新增一個產(chǎn)品添加和更新的方法:
//新增產(chǎn)品
@RequestMapping("admin_product_add")
public String add(Model model, Product p) {
p.setCreateDate(new Date());
productService.add(p);
return "redirect:admin_product_list?cid="+p.getCid();
}
//更新產(chǎn)品
@RequestMapping("admin_product_update")
public String update(Product p) {
productService.update(p);
return "redirect:admin_product_list?cid="+p.getCid();
}
這里的參數(shù)直接就是Product亿乳,因?yàn)槭褂肧pringMVC框架可以自動合成pojo對象,比如這里Product里有字段cid
,會自動填充HTTP請求參數(shù)里的同名字段径筏。如果參數(shù)不同名就會失敗葛假,也無法使用@RequestParam
來關(guān)聯(lián)參數(shù)。
首頁展示
有了分類列表的接口和產(chǎn)品管理的接口滋恬,首頁這部分就可以展示出來聊训。
品牌管理
需要:
- 品牌模型,包括數(shù)據(jù)庫表夷恍、pojo和mapper等
- 產(chǎn)品需要關(guān)聯(lián)品牌
- 品牌活動模塊
- 品牌的創(chuàng)建魔眨、編輯等基本操作
創(chuàng)建數(shù)據(jù)庫表:
create table brand(
id int not null auto_increment,
name varchar(127),
primary key(id)
);
然后使用Mybatis-generator生成對應(yīng)pojo和mapper文件,再就是增加一個BrandController:
@RequestMapping("/")
@Controller
public class BrandController {
@Autowired
BrandService brandService;
@RequestMapping("/brandList")
public String list(Model model, Page page){
page.setCount(30);
PageHelper.offsetPage(page.getStart(), page.getCount());
List<Brand> brands = brandService.list();
page.setTotal((int)new PageInfo<>(brands).getTotal());
model.addAttribute("brands", brands);
return "admin/listBrand";
}
}
同樣需要建立配套的service和實(shí)現(xiàn)類:
public interface BrandService {
List<Brand> list();
}
...................................
@Service
public class BrandServiceImpl implements BrandService {
@Autowired
BrandMapper brandMapper;
@Override
public List<Brand> list() {
BrandExample example = new BrandExample();
example.setOrderByClause("name"); //默認(rèn)按名字排序
return brandMapper.selectByExample(example);
}
}
注意要加@Controller注解和@Service注解酿雪,否則Spring容器找不到它們就無法做URL映射和依賴注入了遏暴。
增加品牌
現(xiàn)在品牌的數(shù)據(jù)還是空的,需要增加品牌的功能指黎,首先在service里增加方法void addBrand(Brand brand);
, BrandServiceImpl里添加實(shí)現(xiàn):
@Override
@Transactional
public void addBrand(Brand brand) {
brandMapper.insert(brand);
}
再在controller里添加增加的方法:
@RequestMapping("/addBrand")
public String addBrand(HttpSession session, Brand brand, MultipartFile imgFile) throws IOException{
brandService.addBrand(brand);
imageSaveService.saveImage(session.getServletContext().getRealPath("/"), ImageSaveService.BRAND_IMAGE_KEY, imgFile, brand.getId());
return "redirect:/brandList";
}
因?yàn)閳D片存儲功能和分類那一一致朋凉,所以提取這個功能到一個單獨(dú)的service類里了,通過key來識別存儲不同模塊的圖片:
@Service
public class ImageSaveService {
public static final String CATEGORY_IMAGE_KEY = "category";
public static final String BRAND_IMAGE_KEY = "brand";
//保存圖片
public void saveImage(String homePath, String pathKey, MultipartFile imgFile, Integer itemId) throws IOException {
//判空處理
if (imgFile == null || imgFile.isEmpty()){
return;
}
File imageFolder= new File(homePath+"/img/"+pathKey);
File file = new File(imageFolder,itemId+".jpg");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
imgFile.transferTo(file);
}
}
后端品牌管理頁面:
有了數(shù)據(jù)醋安,再回到主頁杂彭,需要顯示塊狀的品牌列表墓毒,所以提供一個新的接口,只返回一部分并且隨機(jī)的品牌列表:
//BrandController部分
@RequestMapping("/randomHomeBrandList")
@ResponseBody
public List randomHomeBrandList(int count){
return brandService.randomList(count);
}
//BrandServiceImpl部分
@Override
public List<Brand> randomList(int count) {
return brandMapper.randomSelect(count);
}
使用@ResponseBody是為了返回json類型亲怠,前端可以通過這個接口單獨(dú)拉取品牌數(shù)據(jù)所计,換一批時(shí)可以局部替換。
關(guān)鍵是mapper.xml里的代碼:
<select id="randomSelect" parameterType="int" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from brand
ORDER BY rand()
LiMIT 0, #{count}
</select>
因?yàn)槭褂昧薽ysql數(shù)據(jù)庫团秽,所以用了一個ORDER BY rand()
來進(jìn)行隨機(jī)查詢主胧,如果是其他數(shù)據(jù)庫可以加條件判斷來確定sql.
前端的效果:
品牌活動
- 建立品牌活動表和對應(yīng)的DAO層
- 建立后臺編輯頁面
- 建立對應(yīng)的controller和service
create table BrandActivity(
id int not null auto_increment,
title varchar(20),
subtitle varchar(50),
mainBrand int,
pageLink varchar(255),
primary key(id),
constraint fk_ba_b foreign key (mainBrand) references brand(id)
);
創(chuàng)建了一個數(shù)據(jù)庫表,包含標(biāo)題习勤、小標(biāo)題踪栋、圖片,以及一個關(guān)聯(lián)到品牌的外鍵图毕。
@RequestMapping("brandActivityManage")
public String brandActivityManage(Model model, Page page){
PageHelper.offsetPage(page.getStart(), page.getCount());
List<BrandActivity> brandActivities = brandActivityService.list();
List<Brand> brands = brandService.list();
model.addAttribute("brands",brands);
model.addAttribute("brandActivities",brandActivities);
for (BrandActivity brandActivity: brandActivities){
int mbid = brandActivity.getMainBrand();
for (Brand b: brands){
if (mbid == b.getId()){
brandActivity.setMainBrandName(b.getName());
}
}
}
return "admin/listBrandActivity";
}
這個是返回后臺管理頁面的方法夷都,內(nèi)部查詢了所有的品牌活動。BrandActivity的pojo里除了數(shù)據(jù)庫表對應(yīng)的屬性予颤,還增加了一個mainBrandName
,因?yàn)閿?shù)據(jù)庫表里存儲的是id,顯示的時(shí)候需要轉(zhuǎn)換為名稱囤官。
因?yàn)樾枰械钠放谱鲞x擇,所以本來就要查詢出所有品牌列表荣瑟,就直接在這用一個雙層for循環(huán)給mainBrandName
賦值了治拿。否則需要用mainBrandId
去數(shù)據(jù)庫查到對應(yīng)的Brand數(shù)據(jù)摩泪。
其他的增刪改查的方法都大同小異笆焰,就沒什么好寫的了。
后臺管理頁面效果:
前臺效果:
為了單獨(dú)拉取活動數(shù)據(jù)见坑,增加了一個接口:
@RequestMapping("/brandActivityList")
@ResponseBody
public List<BrandActivity> list(){
return brandActivityService.list();
}
接口很簡單嚷掠,同樣是用@ResponseBody返回json類型數(shù)據(jù)。前段隨便網(wǎng)上搜了的代碼荞驴,為了掩飾湊合著用不皆,思路就是ajax獲取數(shù)據(jù),事先在jsp文件里留一個div熊楼,比如這個<div class="brand-activities"></div>
,然后請求完數(shù)據(jù)往這個div里加入內(nèi)容:
//brand-activities就是要修改的div
$.ajax({
type: "GET",
url: "./brandActivityList",
data: null,
dataType: "json",
success: function(data){
var item = "";
$(".brand-activities").empty(); //清空
for(var i = 0 ; i < data.length; i++) {
item += "<li class='brand-activity-item'> <div class='brand-activity-header'>"+data[i].title+" "+data[i].subtitle+
"<a style='text-align: right;vertical-align: center font-size: 13pt; flex: 1'>更多</a> </div> <div class='brand-activity-img' style='text-align: center;width: 100%'> <img src='./img/brandActivity/"+data[i].id+
".jpg' style='width: 100%;vertical-align: middle;'> </div> <a class='brand-mask' href="+data[i].pageLink+
"> </a> </li>"
}
$(".brand-activities").append(item); // 顯示到里面
}
});