使用SSM框架實(shí)現(xiàn)仿天貓主頁(yè)

打開天貓首頁(yè),可以看到基本分為3大塊:

  1. 商品分頁(yè)列表+主推商品廣告
  2. 知名品牌區(qū)域
  3. “天貓超市”、“居家生活”等按主題分塊的商品區(qū)

這3大塊可以分別用不同的模塊處理:

  1. 分類管理模塊:建立不同分類以及用分類劃分商品;主推商品模塊。
  2. 品牌管理模塊:返回品牌活動(dòng)和品牌logo
  3. 主題模塊:返回主題列表以及主題內(nèi)部的商品

分類管理

首先建立一個(gè)分類管理的后臺(tái)頁(yè)面,用來(lái)添加、刪除和修改分類。


仿 分類管理.png

為了返回這個(gè)頁(yè)面,需要:

1.在數(shù)據(jù)庫(kù)建表category

CREATE TABLE `category` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
)

2.有了數(shù)據(jù)庫(kù)表,再配合mybatis-generator插件就可以生成對(duì)應(yīng)的pojo、mapper和mapper的xml文件了。有了這些基本的數(shù)據(jù)操作有了,不過(guò)還需要做一些修改。用法參考IDEA中mybatis-generator插件的使用
3.需要一個(gè)Controller來(lái)處理當(dāng)前的頁(yè)面的業(yè)務(wù),所以建立一個(gè)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";
    }
}

這里需要說(shuō)明:

  • 使用了一個(gè)CategoryService對(duì)象,因?yàn)闉榱思軜?gòu)清晰,加入了service層。每一個(gè)service對(duì)應(yīng)一個(gè)具體的業(yè)務(wù),controller通過(guò)組合調(diào)用不同的service來(lái)完成任務(wù)。在service層選擇連接不同的DAO層,可以是數(shù)據(jù)庫(kù)也可以是Redis,這樣可以是項(xiàng)目更靈活。
  • @Autowired是為了能夠自動(dòng)注入categoryService。自動(dòng)注入的好處是解耦,spring容器會(huì)根據(jù)類型自動(dòng)給categoryService賦值,如果要替換,只需要替換容器中的bean就可以了,而不需要?jiǎng)觕ontroller的代碼。
  • 這里使用了PageHelper插件,它是通過(guò)MyBatis的插件功能,修改了select語(yǔ)句的sql,添加了limit屬性從而實(shí)現(xiàn)分頁(yè)。

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);
    }
}

需要說(shuō)明幾點(diǎn):

  • 為什么采用接口和實(shí)現(xiàn)類的模式而不是直接使用一個(gè)類?個(gè)人理解是為了以后更好的擴(kuò)展,當(dāng)有多個(gè)不同的邏輯實(shí)現(xiàn)并存時(shí),可以更方便的替換實(shí)現(xiàn)。總的思想還是源于實(shí)現(xiàn)和聲明的分離,更深入的還需要以后更多項(xiàng)目的認(rèn)識(shí)。這里的討論也不錯(cuò)
  • 使用MyBatis插件自動(dòng)生成的mapper會(huì)額外增加一個(gè)example類,是用于查詢的。這個(gè)的一個(gè)好處是可以一定程度防止SQL注入,因?yàn)镾QL注入是通過(guò)把sql語(yǔ)句偽裝成參數(shù)傳入,從而修改了sql語(yǔ)句的意思。比如select * from user where id = 197837 and 1=1,這里的197837 and 1=1是傳入的參數(shù),但是卻會(huì)查出所有表數(shù)據(jù),但是使用example查詢時(shí)是參數(shù)綁定的方式,實(shí)際編譯的sql是select * from user where id = ?,這樣sql的語(yǔ)義至少不會(huì)因?yàn)閭鲄⒍恍薷摹?/li>

這樣分類信息就查詢出來(lái)了,下面就是對(duì)分類的修改。

分類新增和編輯
仿 分類編輯.png

對(duì)應(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ù)很簡(jiǎn)單,service和mapper都準(zhǔn)備好了,這里需要注意的是上傳圖片的功能。上傳圖片使用HTTP POST的multipart/form-data類型,這個(gè)前端都有相應(yīng)的框架支持,后端使用MultipartFile類型接受,它源于org.springframework.web.multipart包,springMVC會(huì)處理從上傳文件到這個(gè)類型的轉(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("/")來(lái)獲取項(xiàng)目的絕對(duì)路徑,然后由此確定圖片存儲(chǔ)位置。

如果圖片功能更復(fù)雜,比如還需要生成對(duì)應(yīng)的各種尺寸縮略圖、圖片是否違法違規(guī)的核查等,就需要一個(gè)圖片處理的service和單獨(dú)的處理模塊來(lái)支持了。

產(chǎn)品和分類關(guān)聯(lián)

分類的作用是用來(lái)將產(chǎn)品分組,需要把產(chǎn)品歸類到一個(gè)個(gè)分類里,所以需要:

  1. 一個(gè)產(chǎn)品的模型,包括數(shù)據(jù)庫(kù)表,pojo和對(duì)應(yīng)的mapper等
  2. 因?yàn)橐粋€(gè)分類里有多個(gè)產(chǎn)品,分類和產(chǎn)品屬于“1對(duì)多”的關(guān)系,所以通過(guò)在產(chǎn)品的表里加入外鍵來(lái)實(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`)
)

這里通過(guò)一個(gè)外鍵,把字段cid關(guān)聯(lián)到category表的id上了。

  1. 需要一個(gè)創(chuàng)建產(chǎn)品并且劃分類別的頁(yè)面:
仿 產(chǎn)品編輯.png

產(chǎn)品的業(yè)務(wù)就需要新增一個(gè)ProductController,在這里新增一個(gè)產(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框架可以自動(dòng)合成pojo對(duì)象,比如這里Product里有字段cid,會(huì)自動(dòng)填充HTTP請(qǐng)求參數(shù)里的同名字段。如果參數(shù)不同名就會(huì)失敗,也無(wú)法使用@RequestParam來(lái)關(guān)聯(lián)參數(shù)。

首頁(yè)展示
仿 前端 分類.png

有了分類列表的接口和產(chǎn)品管理的接口,首頁(yè)這部分就可以展示出來(lái)。

品牌管理

需要:

  1. 品牌模型,包括數(shù)據(jù)庫(kù)表、pojo和mapper等
  2. 產(chǎn)品需要關(guān)聯(lián)品牌
  3. 品牌活動(dòng)模塊
  4. 品牌的創(chuàng)建、編輯等基本操作

創(chuàng)建數(shù)據(jù)庫(kù)表:

create table brand(
    id int not null auto_increment,
    name varchar(127),
    primary key(id)
    );

然后使用Mybatis-generator生成對(duì)應(yīng)pojo和mapper文件,再就是增加一個(gè)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容器找不到它們就無(wú)法做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片存儲(chǔ)功能和分類那一一致,所以提取這個(gè)功能到一個(gè)單獨(dú)的service類里了,通過(guò)key來(lái)識(shí)別存儲(chǔ)不同模塊的圖片:

@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);
    }
}

后端品牌管理頁(yè)面:


仿 品牌管理.png

有了數(shù)據(jù),再回到主頁(yè),需要顯示塊狀的品牌列表,所以提供一個(gè)新的接口,只返回一部分并且隨機(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類型,前端可以通過(guò)這個(gè)接口單獨(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ù)庫(kù),所以用了一個(gè)ORDER BY rand()來(lái)進(jìn)行隨機(jī)查詢,如果是其他數(shù)據(jù)庫(kù)可以加條件判斷來(lái)確定sql.

前端的效果:


品牌列表.jpg
品牌活動(dòng)
  1. 建立品牌活動(dòng)表和對(duì)應(yīng)的DAO層
  2. 建立后臺(tái)編輯頁(yè)面
  3. 建立對(duì)應(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)建了一個(gè)數(shù)據(jù)庫(kù)表,包含標(biāo)題、小標(biāo)題、圖片,以及一個(gè)關(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";
}

這個(gè)是返回后臺(tái)管理頁(yè)面的方法,內(nèi)部查詢了所有的品牌活動(dòng)。BrandActivity的pojo里除了數(shù)據(jù)庫(kù)表對(duì)應(yīng)的屬性,還增加了一個(gè)mainBrandName,因?yàn)閿?shù)據(jù)庫(kù)表里存儲(chǔ)的是id,顯示的時(shí)候需要轉(zhuǎn)換為名稱。

因?yàn)樾枰械钠放谱鲞x擇,所以本來(lái)就要查詢出所有品牌列表,就直接在這用一個(gè)雙層for循環(huán)給mainBrandName賦值了。否則需要用mainBrandId去數(shù)據(jù)庫(kù)查到對(duì)應(yīng)的Brand數(shù)據(jù)。

其他的增刪改查的方法都大同小異,就沒(méi)什么好寫的了。

后臺(tái)管理頁(yè)面效果:


品牌活動(dòng)管理.png

前臺(tái)效果:


品牌活動(dòng).png

為了單獨(dú)拉取活動(dòng)數(shù)據(jù),增加了一個(gè)接口:

@RequestMapping("/brandActivityList")
@ResponseBody
public List<BrandActivity> list(){
    return brandActivityService.list();
}

接口很簡(jiǎn)單,同樣是用@ResponseBody返回json類型數(shù)據(jù)。前段隨便網(wǎng)上搜了的代碼,為了掩飾湊合著用,思路就是ajax獲取數(shù)據(jù),事先在jsp文件里留一個(gè)div,比如這個(gè)<div class="brand-activities"></div>,然后請(qǐng)求完數(shù)據(jù)往這個(gè)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);   // 顯示到里面

    }
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 前言:首先這個(gè)項(xiàng)目是基于網(wǎng)站http://www.how2j.com 參考而完成的一個(gè)學(xué)習(xí)項(xiàng)目,僅供學(xué)習(xí)參考。 項(xiàng)...
    RichardYee閱讀 600評(píng)論 0 0
  • 經(jīng)過(guò)上篇文章Spring、Spring MVC與Mybatis整合工程搭建我們便將SSM的環(huán)境搭建了,接下來(lái)我們便...
    codingXiaxw閱讀 23,124評(píng)論 1 38
  • 作用: SSM是sping+springMVC+mybatis集成的框架。是標(biāo)準(zhǔn)的MVC模式,將整個(gè)系統(tǒng)劃分為表現(xiàn)...
    夢(mèng)晝初心閱讀 27,301評(píng)論 0 16
  • Mybatis 創(chuàng)建數(shù)據(jù)庫(kù) Mybatis框架原理(掌握) 1、Mybatis 是什么? Mybatis 是一個(gè)持...
    printf200閱讀 732評(píng)論 0 4
  • 一片秋葉,幾朵白云鹿足印小徑,鳥鳴伴風(fēng)聲空枝濯濯,蒼然蕭瑟滿地落葉,燦燦澄澄駐足林間,聽(tīng)濤聲陣陣登高望遠(yuǎn),目送南歸...
    叢子_c03a閱讀 343評(píng)論 1 4