使用SSM框架实现仿天猫主页

打开天猫首页,可以看到基本分为3大块:

  1. 商品分页列表+主推商品广告
  2. 知名品牌区域
  3. “天猫超市”、“居家生活”等按主题分块的商品区

这3大块可以分别用不同的模块处理:

  1. 分类管理模块:建立不同分类以及用分类划分商品;主推商品模块。
  2. 品牌管理模块:返回品牌活动和品牌logo
  3. 主题模块:返回主题列表以及主题内部的商品

分类管理

首先建立一个分类管理的后台页面,用来添加、删除和修改分类。


仿 分类管理.png

为了返回这个页面,需要:

1.在数据库建表category

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

2.有了数据库表,再配合mybatis-generator插件就可以生成对应的pojo、mapper和mapper的xml文件了。有了这些基本的数据操作有了,不过还需要做一些修改。用法参考IDEA中mybatis-generator插件的使用
3.需要一个Controller来处理当前的页面的业务,所以建立一个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 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对象,因为为了架构清晰,加入了service层。每一个service对应一个具体的业务,controller通过组合调用不同的service来完成任务。在service层选择连接不同的DAO层,可以是数据库也可以是Redis,这样可以是项目更灵活。
  • @Autowired是为了能够自动注入categoryService。自动注入的好处是解耦,spring容器会根据类型自动给categoryService赋值,如果要替换,只需要替换容器中的bean就可以了,而不需要动controller的代码。
  • 这里使用了PageHelper插件,它是通过MyBatis的插件功能,修改了select语句的sql,添加了limit属性从而实现分页。

4.创建CategoryService以及实现类CategoryServiceImpl:

public interface CategoryService {
    List 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 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);
    }
}

需要说明几点:

  • 为什么采用接口和实现类的模式而不是直接使用一个类?个人理解是为了以后更好的扩展,当有多个不同的逻辑实现并存时,可以更方便的替换实现。总的思想还是源于实现和声明的分离,更深入的还需要以后更多项目的认识。这里的讨论也不错
  • 使用MyBatis插件自动生成的mapper会额外增加一个example类,是用于查询的。这个的一个好处是可以一定程度防止SQL注入,因为SQL注入是通过把sql语句伪装成参数传入,从而修改了sql语句的意思。比如select * from user where id = 197837 and 1=1,这里的197837 and 1=1是传入的参数,但是却会查出所有表数据,但是使用example查询时是参数绑定的方式,实际编译的sql是select * from user where id = ?,这样sql的语义至少不会因为传参而被修改。

这样分类信息就查询出来了,下面就是对分类的修改。

分类新增和编辑
仿 分类编辑.png

对应的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数据很简单,service和mapper都准备好了,这里需要注意的是上传图片的功能。上传图片使用HTTP POST的multipart/form-data类型,这个前端都有相应的框架支持,后端使用MultipartFile类型接受,它源于org.springframework.web.multipart包,springMVC会处理从上传文件到这个类型的转化,前提是需要开启转化器的支持:

    
        
    

拿到图片数据后,写入本地,这里使用了session.getServletContext().getRealPath("/")来获取项目的绝对路径,然后由此确定图片存储位置。

如果图片功能更复杂,比如还需要生成对应的各种尺寸缩略图、图片是否违法违规的核查等,就需要一个图片处理的service和单独的处理模块来支持了。

产品和分类关联

分类的作用是用来将产品分组,需要把产品归类到一个个分类里,所以需要:

  1. 一个产品的模型,包括数据库表,pojo和对应的mapper等
  2. 因为一个分类里有多个产品,分类和产品属于“1对多”的关系,所以通过在产品的表里加入外键来实现和分类的关联:
 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关联到category表的id上了。

  1. 需要一个创建产品并且划分类别的页面:
仿 产品编辑.png

产品的业务就需要新增一个ProductController,在这里新增一个产品添加和更新的方法:

//新增产品
@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();
}
//更新产品
@RequestMapping("admin_product_update")
public String update(Product p) {
    productService.update(p);
    return "redirect:admin_product_list?cid="+p.getCid();
}

这里的参数直接就是Product,因为使用SpringMVC框架可以自动合成pojo对象,比如这里Product里有字段cid,会自动填充HTTP请求参数里的同名字段。如果参数不同名就会失败,也无法使用@RequestParam来关联参数。

首页展示
仿 前端 分类.png

有了分类列表的接口和产品管理的接口,首页这部分就可以展示出来。

品牌管理

需要:

  1. 品牌模型,包括数据库表、pojo和mapper等
  2. 产品需要关联品牌
  3. 品牌活动模块
  4. 品牌的创建、编辑等基本操作

创建数据库表:

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

然后使用Mybatis-generator生成对应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 brands = brandService.list();
        page.setTotal((int)new PageInfo<>(brands).getTotal());
        
        model.addAttribute("brands", brands);
        
        return "admin/listBrand";
    }
}

同样需要建立配套的service和实现类:

public interface BrandService {
    List list();
}
...................................
@Service
public class BrandServiceImpl implements BrandService {

    @Autowired
    BrandMapper brandMapper;

    @Override
    public List list() {
        BrandExample example = new BrandExample();
        example.setOrderByClause("name"); //默认按名字排序
        return brandMapper.selectByExample(example);
    }
}

注意要加@Controller注解和@Service注解,否则Spring容器找不到它们就无法做URL映射和依赖注入了。

增加品牌

现在品牌的数据还是空的,需要增加品牌的功能,首先在service里增加方法void addBrand(Brand brand);, BrandServiceImpl里添加实现:

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

因为图片存储功能和分类那一一致,所以提取这个功能到一个单独的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);
    }
}

后端品牌管理页面:


仿 品牌管理.png

有了数据,再回到主页,需要显示块状的品牌列表,所以提供一个新的接口,只返回一部分并且随机的品牌列表:

//BrandController部分
@RequestMapping("/randomHomeBrandList")
@ResponseBody
public List randomHomeBrandList(int count){
    return brandService.randomList(count);
}

//BrandServiceImpl部分
@Override
public List randomList(int count) {
    return brandMapper.randomSelect(count);
}
使用@ResponseBody是为了返回json类型,前端可以通过这个接口单独拉取品牌数据,换一批时可以局部替换。

关键是mapper.xml里的代码:


因为使用了mysql数据库,所以用了一个ORDER BY rand()来进行随机查询,如果是其他数据库可以加条件判断来确定sql.

前端的效果:


品牌列表.jpg
品牌活动
  1. 建立品牌活动表和对应的DAO层
  2. 建立后台编辑页面
  3. 建立对应的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)
);

创建了一个数据库表,包含标题、小标题、图片,以及一个关联到品牌的外键。

@RequestMapping("brandActivityManage")
public String brandActivityManage(Model model, Page page){
    
    PageHelper.offsetPage(page.getStart(), page.getCount());
    List brandActivities = brandActivityService.list();


    List 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";
}

这个是返回后台管理页面的方法,内部查询了所有的品牌活动。BrandActivity的pojo里除了数据库表对应的属性,还增加了一个mainBrandName,因为数据库表里存储的是id,显示的时候需要转换为名称。

因为需要所有的品牌做选择,所以本来就要查询出所有品牌列表,就直接在这用一个双层for循环给mainBrandName赋值了。否则需要用mainBrandId去数据库查到对应的Brand数据。

其他的增删改查的方法都大同小异,就没什么好写的了。

后台管理页面效果:


品牌活动管理.png

前台效果:


品牌活动.png

为了单独拉取活动数据,增加了一个接口:

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

接口很简单,同样是用@ResponseBody返回json类型数据。前段随便网上搜了的代码,为了掩饰凑合着用,思路就是ajax获取数据,事先在jsp文件里留一个div,比如这个

,然后请求完数据往这个div里加入内容:

//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 += "
  • "+data[i].title+" "+data[i].subtitle+ "更多
  • " } $(".brand-activities").append(item); // 显示到里面 } });

    你可能感兴趣的:(使用SSM框架实现仿天猫主页)