java学习day62(乐友商城)商品规格设计与商品查询的页面实现

news2024/11/25 18:56:24

1.商品规格数据结构

乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:

1.1.SPU和SKU

SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集

SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品

以图为例来看:

 

  • 本页的 华为Mate10 就是一个商品集(SPU)

  • 因为颜色、内存等不同,而细分出不同的Mate10,如亮黑色128G版。(SKU)

可以看出:

  • SPU是一个抽象的商品集概念,为了方便后台的管理。

  • SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU

1.2.数据库设计分析

1.2.1.思考并发现问题

弄清楚了SPU和SKU的概念区分,接下来我们一起思考一下该如何设计数据库表。

首先来看SPU,大家一起思考下SPU应该有哪些字段来描述?

id:主键
title:标题
description:描述
specification:规格
packaging_list:包装
after_service:售后服务
comment:评价
category_id:商品分类
brand_id:品牌

似乎并不复杂,但是大家仔细思考一下,商品的规格字段你如何填写?  

不同商品的规格不一定相同,数据库中要如何保存?

再看下SKU,大家觉得应该有什么字段?

id:主键
spu_id:关联的spu
price:价格
images:图片
stock:库存
颜色?
内存?
硬盘? 

碰到难题了,不同的商品分类,可能属性是不一样的,比如手机有内存,衣服有尺码,我们是全品类的电商网站,这些不同的商品的不同属性,如何设计到一张表中?

其实颜色、内存、硬盘属性都是规格参数中的字段。所以,要解决这个问题,首先要能清楚规格参数。

1.2.2.分析规格参数

仔细查看每一种商品的规格你会发现:

虽然商品规格千变万化,但是同一类商品(如手机)的规格是统一的,有图为证:

华为的规格:

 

三星的规格:

 

1.2.3.SKU的特有属性

SPU中会有一些特殊属性,用来区分不同的SKU,我们称为SKU特有属性。如华为META10的颜色、内存属性。

不同种类的商品,一个手机,一个衣服,其SKU属性不相同。

同一种类的商品,比如都是衣服,SKU属性基本是一样的,都是颜色、尺码等。

这样说起来,似乎SKU的特有属性也是与分类相关的?事实上,仔细观察你会发现,SKU的特有属性是商品规格参数的一部分

 

也就是说,我们没必要单独对SKU的特有属性进行设计,它可以看做是规格参数中的一部分。这样规格参数中的属性可以标记成两部分:

  • spu下所有sku共享的规格属性(称为全局属性)

  • 每个sku不同的规格属性(称为特有属性)

1.2.4.搜索属性

打开一个搜索页,我们来看看过滤的条件:

 

你会发现,过滤条件中的屏幕尺寸、运行内存、网路、机身内存、电池容量、CPU核数等,在规格参数中都能找到:

 

也就是说,规格参数中的数据,将来会有一部分作为搜索条件来使用。我们可以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。要注意的是,无论是SPU的全局属性,还是SKU的特有属性,都有可能作为搜索过滤条件的,并不冲突,而是有一个交集:  

1.3.规格参数表

1.3.1.表结构

我们看下规格参数的格式:

 

可以看到规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。

因此我们设计了两张表:

  • tb_spec_group:组,与商品分类关联

  • tb_spec_param:参数名,与组关联,一对多

1.3.2.规格组

规格参数分组表:tb_spec_group

CREATE TABLE `tb_spec_group` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id,一个分类下有多个规格组',
  `name` varchar(50) NOT NULL COMMENT '规格组的名称',
  PRIMARY KEY (`id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='规格参数的分组表,每个商品分类下有多个规格参数组';

 

规格组有3个字段:

  • id:主键

  • cid:商品分类id,一个分类下有多个模板

  • name:该规格组的名称。

1.3.2.规格参数

规格参数表:tb_spec_param

CREATE TABLE `tb_spec_param` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id',
  `group_id` bigint(20) NOT NULL,
  `name` varchar(255) NOT NULL COMMENT '参数名',
  `numeric` tinyint(1) NOT NULL COMMENT '是否是数字类型参数,true或false',
  `unit` varchar(255) DEFAULT '' COMMENT '数字类型参数的单位,非数字类型可以为空',
  `generic` tinyint(1) NOT NULL COMMENT '是否是sku通用属性,true或false',
  `searching` tinyint(1) NOT NULL COMMENT '是否用于搜索过滤,true或false',
  `segments` varchar(1000) DEFAULT '' COMMENT '数值类型参数,如果需要搜索,则添加分段间隔值,如CPU频率间隔:0.5-1.0',
  PRIMARY KEY (`id`),
  KEY `key_group` (`group_id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='规格参数组下的参数名';

按道理来说,我们的规格参数就只需要记录参数名、组id、商品分类id即可。但是这里却多出了很多字段,为什么?

还记得我们之前的分析吧,规格参数中有一部分是 SKU的通用属性,一部分是SKU的特有属性,而且其中会有一些将来用作搜索过滤,这些信息都需要标记出来。

通用属性

用一个布尔类型字段来标记是否为通用:

  • generic来标记是否为通用属性:

    • true:代表通用属性

    • false:代表sku特有属性

搜索过滤

与搜索相关的有两个字段:

  • searching:标记是否用作过滤

    • true:用于过滤搜索

    • false:不用于过滤

  • segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间

    • 比如电池容量,0~2000mAh,2000mAh~3000mAh,3000mAh~4000mAh

数值类型

某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:

  • numberic:是否为数值类型

    • true:数值类型

    • false:不是数值类型

  • unit:参数的单位

2.商品规格参数管理

2.1.页面布局

2.1.1.整体布局

打开规格参数页面,看到如下内容:

商品分类树我们之前已经做过,所以这里可以直接展示出来。

因为规格是跟商品分类绑定的,因此首先会展现商品分类树,并且提示你要选择商品分类,才能看到规格参数的模板。一起了解下页面的实现:

 

页面结构:  

这里使用了v-layout来完成页面布局,并且添加了row属性,代表接下来的内容是行布局(左右)。

可以看出页面分成2个部分:

  • <v-flex xs3>:左侧,内部又分上下两部分:商品分类树及标题

    • v-card-title:标题部分,这里是提示信息,告诉用户要先选择分类,才能看到模板

    • v-tree:这里用到的是我们之前讲过的树组件,展示商品分类树,

  • <v-flex xs9 class="px-1">:右侧:内部是规格参数展示

2.1.2.右侧规格

当我们点击一个分类时,最终要达到的效果:

 

可以看到右侧分为上下两部分:

  • 上部:面包屑,显示当前选中的分类

  • 下部:table,显示规格参数信息

页面实现:

 

可以看到右侧并不是我们熟悉的 v-data-table,而是一个spec-group组件(规格组)和spec-param组件(规格参数),这是我们定义的独立组件:

 

在SpecGroup中定义了表格:  

2.2.规格组的查询

2.2.1.树节点的点击事件

当我们点击树节点时,要将v-dialog打开,因此必须绑定一个点击事件:(Specification.vue)

 

我们来看下handleClick方法:(Specification.vue)

点击事件发生时,发生了两件事:

  • 记录当前选中的节点,选中的就是商品分类

  • showGroup被置为true,则规格组就会显示了。

同时,我们把被选中的节点(商品分类)的id传递给了SpecGroup组件:(Specification.vue)

 

2.2.2.页面查询规格组

来看下SpecGroup.vue中的实现:

 

 我们查看页面控制台,可以看到请求已经发出:

 

2.2.3.后端代码

实体类

leyou-item-interface中添加实体类:

 内容:

@Table(name = "tb_spec_group")
public class SpecGroup {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long cid;

    private String name;

    @Transient
    private List<SpecParam> params;

   // getter和setter省略
}

 

@Table(name = "tb_spec_param")
public class SpecParam {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long cid;
    private Long groupId;
    private String name;
    @Column(name = "`numeric`")
    private Boolean numeric;
    private String unit;
    private Boolean generic;
    private Boolean searching;
    private String segments;
    
    // getter和setter ...
}

leyou-item-service中编写业务:

mapper

public interface SpecGroupMapper extends Mapper<SpecGroup> {
}

controller

先分析下需要的东西,在页面的ajax请求中可以看出:

  • 请求方式:get

  • 请求路径:/spec/groups/{cid} ,这里通过路径占位符传递商品分类的id

  • 请求参数:商品分类id

  • 返回结果:页面是直接把resp.data赋值给了groups:

那么我们返回的应该是规格组SpecGroup的集合

代码:  

@RestController
@RequestMapping("spec")
public class SpecificationController {

    @Autowired
    private SpecificationService specificationService;

    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    @GetMapping("groups/{cid}")
    public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid")Long cid){
        List<SpecGroup> groups = this.specificationService.queryGroupsByCid(cid);
        if (CollectionUtils.isEmpty(groups)){
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(groups);
    }
}

service

@Service
public class SpecificationService {

    @Autowired
    private SpecGroupMapper groupMapper;

    /**
     * 根据分类id查询分组
     * @param cid
     * @return
     */
    public List<SpecGroup> queryGroupsByCid(Long cid) {
        SpecGroup specGroup = new SpecGroup();
        specGroup.setCid(cid);
        return this.groupMapper.select(specGroup);
    }
}

页面访问测试:

目前,我们数据库只为手机分类(76)提供了规格组:

我们访问:http://api.leyou.com/api/item/spec/groups/76

 

然后在后台系统中测试:  

2.3.规格参数查询

2.3.1.表格切换

当我们点击规格组,会切换到规格参数显示,肯定是在规格组中绑定了点击事件:

 

我们看下事件处理:  

 

可以看到这里是使用了父子通信,子组件触发了select事件:

再来看下父组件的事件绑定:

 事件处理:

这里我们记录了选中的分组,并且把标记设置为false,这样规格组就不显示了,而是显示:SpecParam

并且,我们把group也传递到spec-param组件:

 

2.3.2.页面查询规格参数

我们来看SpecParam.vue的实现:

 

 查看页面控制台,发现请求已经发出:

报404,因为我们还没有实现后台逻辑,接下来就去实现。

2.3.3.后台实现

SpecificationController

分析:

  • 请求方式:GET

  • 请求路径:/spec/params

  • 请求参数:gid,分组id

  • 返回结果:该分组下的规格参数集合List<SpecParam>

代码:

/**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
@GetMapping("params")
public ResponseEntity<List<SpecParam>> queryParams(@RequestParam("gid")Long gid){
    List<SpecParam>  params = this.specificationService.queryParams(gid);
    if (CollectionUtils.isEmpty(params)){
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

 SpecificationService

@Autowired
private SpecParamMapper paramMapper;

/**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
public List<SpecParam> queryParams(Long gid) {
    SpecParam param = new SpecParam();
    param.setGroupId(gid);
    return this.paramMapper.select(param);
}

SpecParamMapper

 

public interface SpecParamMapper extends Mapper<SpecParam> {
}

 测试:

 

3.SPU和SKU数据结构

规格确定以后,就可以添加商品了,先看下数据库表

3.1.SPU表

SPU表:

CREATE TABLE `tb_spu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id',
  `title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题',
  `sub_title` varchar(255) DEFAULT '' COMMENT '子标题',
  `cid1` bigint(20) NOT NULL COMMENT '1级类目id',
  `cid2` bigint(20) NOT NULL COMMENT '2级类目id',
  `cid3` bigint(20) NOT NULL COMMENT '3级类目id',
  `brand_id` bigint(20) NOT NULL COMMENT '商品所属品牌id',
  `saleable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0下架,1上架',
  `valid` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0已删除,1有效',
  `create_time` datetime DEFAULT NULL COMMENT '添加时间',
  `last_update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='spu表,该表描述的是一个抽象的商品,比如 iphone8';

与我们前面分析的基本类似,但是似乎少了一些字段,比如商品描述。

我们做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail

CREATE TABLE `tb_spu_detail` (
  `spu_id` bigint(20) NOT NULL,
  `description` text COMMENT '商品描述信息',
  `generic_spec` varchar(10000) NOT NULL DEFAULT '' COMMENT '通用规格参数数据',
  `special_spec` varchar(1000) NOT NULL COMMENT '特有规格参数及可选值信息,json格式',
  `packing_list` varchar(3000) DEFAULT '' COMMENT '包装清单',
  `after_service` varchar(3000) DEFAULT '' COMMENT '售后服务',
  PRIMARY KEY (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这张表中的数据都比较大,为了不影响主表的查询效率我们拆分出这张表。

需要注意的是这两个字段:generic_spec和special_spec。

前面讲过规格参数与商品分类绑定,一个分类下的所有SPU具有类似的规格参数。SPU下的SKU可能会有不同的规格参数信息,因此我们计划是这样:

  • SPUDetail中保存通用的规格参数信息。

  • SKU中保存特有规格参数。

来看下我们的表如何存储这些信息。

3.1.1.generic_spec字段

首先是generic_spec,其中保存通用规格参数信息的值,这里为了方便查询,使用了json格式:

整体来看:

 

json结构,其中都是键值对:

  • key:对应的规格参数的spec_param的id

  • value:对应规格参数的值

3.1.2.special_spec字段

我们说spu中只保存通用规格参数,那么为什么有多出了一个special_spec字段呢?

以手机为例,品牌、操作系统等肯定是全局通用属性,内存、颜色等肯定是特有属性。

当你确定了一个SPU,比如小米的:红米4X

全局属性值都是固定的了:

品牌:小米
型号:红米4X 

特有属性举例:

颜色:[香槟金, 樱花粉, 磨砂黑]
内存:[2G, 3G]
机身存储:[16GB, 32GB] 

颜色、内存、机身存储,作为SKU特有属性,key虽然一样,但是SPU下的每一个SKU,其值都不一样,所以值会有很多,形成数组。

我们在SPU中,会把特有属性的所有值都记录下来,形成一个数组:

里面又有哪些内容呢?

来看数据格式:

 

也是json结构:

  • key:规格参数id

  • value:spu属性的数组

那么问题来:特有规格参数应该在sku中记录才对,为什么在spu中也要记录一份?

因为我们有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时:

 

 刚好符合我们的结构,这样页面渲染就非常方便了。

3.2.SKU表

CREATE TABLE `tb_sku` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'sku id',
  `spu_id` bigint(20) NOT NULL COMMENT 'spu id',
  `title` varchar(255) NOT NULL COMMENT '商品标题',
  `images` varchar(1000) DEFAULT '' COMMENT '商品的图片,多个图片以‘,’分割',
  `price` bigint(15) NOT NULL DEFAULT '0' COMMENT '销售价格,单位为分',
  `indexes` varchar(100) COMMENT '特有规格属性在spu属性模板中的对应下标组合',
  `own_spec` varchar(1000) COMMENT 'sku的特有规格参数,json格式',
  `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0无效,1有效',
  `create_time` datetime NOT NULL COMMENT '添加时间',
  `last_update_time` datetime NOT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`id`),
  KEY `key_spu_id` (`spu_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku表,该表表示具体的商品实体,如黑色的64GB的iphone 8';

还有一张表,代表库存:

CREATE TABLE `tb_stock` (
  `sku_id` bigint(20) NOT NULL COMMENT '库存对应的商品sku id',
  `seckill_stock` int(9) DEFAULT '0' COMMENT '可秒杀库存',
  `seckill_total` int(9) DEFAULT '0' COMMENT '秒杀总数量',
  `stock` int(9) NOT NULL COMMENT '库存数量',
  PRIMARY KEY (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存表,代表库存,秒杀库存等信息';

问题:为什么要将库存独立一张表?

因为库存字段写频率较高,而SKU的其它字段以读为主,因此我们将两张表分离,读写不会干扰。

特别需要注意的是sku表中的indexes字段和own_spec字段。sku中应该保存特有规格参数的值,就在这两个字段中。

3.2.1.indexes字段

在SPU表中,已经对特有规格参数及可选项进行了保存,结构如下:

{
    "4": [
        "香槟金",
        "樱花粉",
        "磨砂黑"
    ],
    "12": [
        "2GB",
        "3GB"
    ],
    "13": [
        "16GB",
        "32GB"
    ]
}

这些特有属性如果排列组合,会产生12个不同的SKU,而不同的SKU,其属性就是上面备选项中的一个。

比如:

  • 红米4X,香槟金,2GB内存,16GB存储

  • 红米4X,磨砂黑,2GB内存,32GB存储

你会发现,每一个属性值,对应于SPUoptions数组的一个选项,如果我们记录下角标,就是这样:

  • 红米4X,0,0,0

  • 红米4X,2,0,1

既然如此,我们是不是可以将不同角标串联起来,作为SPU下不同SKU的标示。这就是我们的indexes字段。

 

这个设计在商品详情页会特别有用:

 当用户点击选中一个特有属性,你就能根据 角标快速定位到sku。

3.2.2.own_spec字段

看结构:

 {"4":"香槟金","12":"2GB","13":"16GB"}

保存的是特有属性的键值对。

SPU中保存的是可选项,但不确定具体的值,而SKU中的保存的就是具体的值。

3.3.导入图片信息

现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,我们需要把图片导入到虚拟机:

首先,把课前资料提供的数据上传到虚拟机下:/leyou/static目录:在leyou下创建static目录

 

 

然后,使用命令解压缩:

unzip images.zip 

修改Nginx配置,使nginx反向代理这些图片地址:

vim /opt/nginx/config/nginx.conf 

 修改成如下配置:

server {
    listen       80;
    server_name  image.leyou.com;

    # 监听域名中带有group的,交给FastDFS模块处理
    location ~/group([0-9])/ {
        ngx_fastdfs_module;
    }
    # 将其它图片代理指向本地的/leyou/static目录
    location / {
        root   /leyou/static/;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

}

 不要忘记重新加载nginx配置

nginx -s reload

 

4.商品查询

4.1.效果预览

接下来,我们实现商品管理的页面,先看下我们要实现的效果:

 

可以看出整体是一个table,然后有新增按钮。是不是跟昨天写品牌管理很像?

4.2.页面请求

先看整体页面结构(Goods.vue):

并且在Vue实例挂载后就会发起查询(mounted调用getDataFromServer方法初始化数据):

 

 我们刷新页面,可以看到浏览器发起已经发起了查询商品数据的请求:

 

因此接下来,我们编写接口即可。

4.3.后台提供接口

页面已经准备好,接下来在后台提供分页查询SPU的功能。

 

 

4.3.1.实体类

在leyou-item-interface工程中添加实体类:

SPU

@Table(name = "tb_spu")
public class Spu {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long brandId;
    private Long cid1;// 1级类目
    private Long cid2;// 2级类目
    private Long cid3;// 3级类目
    private String title;// 标题
    private String subTitle;// 子标题
    private Boolean saleable;// 是否上架
    private Boolean valid;// 是否有效,逻辑删除用
    private Date createTime;// 创建时间
    private Date lastUpdateTime;// 最后修改时间
	// 省略getter和setter
}

 SPU详情

@Table(name="tb_spu_detail")
public class SpuDetail {
    @Id
    private Long spuId;// 对应的SPU的id
    private String description;// 商品描述
    private String specialSpec;// 商品特殊规格的名称及可选值模板
    private String genericSpec;// 商品的全局规格属性
    private String packingList;// 包装清单
    private String afterService;// 售后服务
    // 省略getter和setter
}

 

4.4.2.mapper

public interface SpuMapper extends Mapper<Spu> {
}

4.3.3.controller

先分析:

  • 请求方式:GET

  • 请求路径:/spu/page

  • 请求参数:

    • page:当前页

    • rows:每页大小

    • key:过滤条件

    • saleable:上架或下架

  • 返回结果:商品SPU的分页信息。

    • 要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,怎么办?

      我们可以新建一个类,继承SPU,并且拓展cname和bname属性,写到leyou-item-interface

public class SpuBo extends Spu {

    String cname;// 商品分类名称
    
    String bname;// 品牌名称
    
    // 略 。。
}

编写controller代码:

我们把与商品相关的一切业务接口都放到一起,起名为GoodsController,业务层也是这样

@Controller
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @GetMapping("spu/page")
    public ResponseEntity<PageResult<SpuBo>> querySpuBoByPage(
            @RequestParam(value = "key", required = false)String key,
            @RequestParam(value = "saleable", required = false)Boolean saleable,
            @RequestParam(value = "page", defaultValue = "1")Integer page,
            @RequestParam(value = "rows", defaultValue = "5")Integer rows
    ){
        PageResult<SpuBo> pageResult = this.goodsService.querySpuBoByPage(key, saleable, page, rows);
        if(CollectionUtils.isEmpty(pageResult.getItems())){
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(pageResult);
    }

}

4.4.4.service

所有商品相关的业务(包括SPU和SKU)放到一个业务下:GoodsService。

@Service
public class GoodsService {

    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandMapper brandMapper;

    public PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows) {

        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        // 搜索条件
        if (StringUtils.isNotBlank(key)) {
            criteria.andLike("title", "%" + key + "%");
        }
        if (saleable != null) {
            criteria.andEqualTo("saleable", saleable);
        }

        // 分页条件
        PageHelper.startPage(page, rows);

        // 执行查询
        List<Spu> spus = this.spuMapper.selectByExample(example);
        PageInfo<Spu> pageInfo = new PageInfo<>(spus);

        List<SpuBo> spuBos = new ArrayList<>();
        spus.forEach(spu->{
            SpuBo spuBo = new SpuBo();
            // copy共同属性的值到新的对象
            BeanUtils.copyProperties(spu, spuBo);
            // 查询分类名称
            List<String> names = this.categoryService.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
            spuBo.setCname(StringUtils.join(names, "/"));

            // 查询品牌的名称
            spuBo.setBname(this.brandMapper.selectByPrimaryKey(spu.getBrandId()).getName());

            spuBos.add(spuBo);
        });

        return new PageResult<>(pageInfo.getTotal(), spuBos);

    }
}

4.4.5.Category中拓展查询名称的功能

页面需要商品的分类名称需要在这里查询,因此要额外提供查询分类名称的功能,

在CategoryService中添加功能:

public List<String> queryNamesByIds(List<Long> ids) {
    List<Category> list = this.categoryMapper.selectByIdList(ids);
    List<String> names = new ArrayList<>();
    for (Category category : list) {
        names.add(category.getName());
    }
    return names;
    // return list.stream().map(category -> category.getName()).collect(Collectors.toList());
}

mapper的selectByIdList方法是来自于通用mapper。不过需要我们在mapper上继承一个通用mapper接口:

public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { 

}

4.5.测试

刷新页面,查看效果:

 基本与预览的效果一致,OK!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/100875.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

恒温恒湿实验室(房)建设、设计SICOLAB

通用实验室是指适用于多学科的以实验台规模进行经常性科学研究和实验工作的实验室&#xff0c;其夏季空气调节室内计算参数为温度 26&#xff5e;28℃&#xff0c;相对湿度小于 65%&#xff0c;在规范中没有对温湿度控制精度及洁净度做相关要求。专用实验室是指有特定环境要求&…

C++基础学习三

目录儿六、分支语句和逻辑操作符6.1 if语句6.1.1 if6.1.2 if-else6.1.3 if-else if-else6.2 逻辑表达式6.2.1 逻辑或||6.2.2 逻辑与&&6.2.3 逻辑非!6.2.4 逻辑操作符的其他表示方式6.3 字符函数库cctype6.4 三目/元操作符6.5 switch语句6.5.1 switch引入枚举常量6.6 br…

【OpenCall】ICASSP2023通用会议理解及生成挑战赛邀请函

ICASSP2023 通用会议理解及生成挑战赛(General Meeting Understanding and Generation Challenge,缩写为 MUG)是ICASSP2023 系列大挑战(SPGC)之一&#xff0c;由魔搭ModelScope社区、阿里巴巴达摩院语音实验室&语言技术实验室&#xff0c;阿里云天池联合浙江大学数字媒体计…

Linux基本搭建和操作

Linux基本搭建和操作1、创建三台虚拟机2、创建使用SSH远程连接3、实现IP地址与主机名的映射4、关闭和禁用防火墙5、创建目录结构6、压缩打包7、安装软件包安装jdk安装mysql8、创建脚本文件9、运行脚本文件10、免密登录配置11、远程拷贝文件1、创建三台虚拟机 序号虚拟机名称静…

高效技巧-打表法

打表法 打表是一种典型的用空间换时间的技巧 一般指将所有可能需要用到的结果事先计算出来&#xff0c;这样后面需要用到时就可以直接查表获得。 打表常见的用法有如下几种: ①在程序中一次性计算出所有需要用到的结果&#xff0c;之后的查询直接取这些结果这个是最常用到的…

开源之路——如何发布属于自己的npm包

开源之路——如何发布属于自己的npm包1、前言2、起步2.1、初始化项目2.2、安装webpack相关依赖2.3、添加入口文件和封装方法2.4、设置源2.5、添加用户2.6、发布3、使用1、前言 在进行开发的过程当中&#xff0c;难免会出现一些重复性的工作&#xff0c;例如说我们要对一个数组…

电脑如何格式化重装系统

​众所周知&#xff0c;默认情况下&#xff0c;计算机重新安装系统将设置格式化磁盘。如果您选择其他需要格式化的硬盘&#xff0c;您必须如何操作&#xff1f;一般来说&#xff0c;我们是pe手动格式磁盘可以避免立即格式化硬盘造成的一些问题。让我们谈谈如何格式化硬盘重做系…

【软件测试】测试的天花板?资深测试怎么一路爬的......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 测试职业的天花板是…

脑图谱的验证方法

方法 目的 与其他分割比较区域内的同质性 比较不同分割的平均区域内同质性 有约束的几何扰动比较 验证分割区域内部的功能同质性 留一交叉验证 验证相似矩阵的稳定性 剪影宽度silhouette width (SI) 计算簇内和簇外的功能信号相似性差异 骰子系数dice coefficient 衡…

G1D36-import-keras.save_model-code-沐沐的调参课

1、python导入函数 https://zhuanlan.zhihu.com/p/64893308 服了 https://blog.csdn.net/weixin_45195364/article/details/119857246?spm1001.2101.3001.6650.6&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6-1198572…

OSCS开源安全周报第22期:NuGet 仓库中被发现 13.5 万个包含钓鱼地址的组件包

本周安全态势综述 OSCS 社区共收录安全漏洞15个&#xff0c;公开漏洞值得关注的是 Jenkins Google Login Plugin 存在开放重定向漏洞&#xff08;CVE-2022-46683&#xff09;&#xff0c;Netty <4.1.86.Final 存在拒绝服务漏洞&#xff08;CVE-2022-41881&#xff09;&…

【数据结构】Java实现顺序表

文章目录线性表顺序表顺序表的模拟实现1、新增元素,默认在数组最后新增2、判定是否包含某个元素3、查找某个元素对应的位置4、获取顺序表长度5、在 pos 位置新增元素6、获取 pos 位置的元素7、给 pos 位置的元素设为 value8、删除第一次出现的关键字key9、清除顺序表线性表 什…

接口性能测试,这个还真有用啊。

目录&#xff1a;导读 一、概述 二、为什么要做接口压力测试 三、接口压力测试的局限性 四、谁来做接口压力测试 五、如何做接口压力测试 六、如何设计接口压力测试方案 七、压力测试报告应该包含哪些结果 八、如何解读压力测试的结果 九、如何根据测试结果定位性能问…

Pandas提取数据的几种方式

文章目录前言Pandas读取数据的几种方式1. read_csv2. read_excel3. read_sql总结前言 快期末了&#xff0c;数据挖掘的大作业需要用到python的相关知识&#xff08;这太难为我这个以前主学C的人了&#xff0c;不过没办法还是得学&#x1f602;&#xff09;&#xff0c;下面是我…

[附源码]计算机毕业设计Python的疫苗接种管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

算法刷题打卡第50天:排序数组---快速排序

排序数组 难度&#xff1a;中等 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,5]快速排…

安科瑞霍尔闭环电流传感器在电动观光旅游车上的应用浅析

摘要&#xff1a; 本文介绍了基于霍尔闭环原理&#xff0c;即磁平衡式原理的电流传感器在电动观光旅游车上的使用方法&#xff0c;替代传统的霍尔器件&#xff0c;较好的解决了电动车行业现有霍尔传感器的基本问题&#xff0c;在稳定性上更加优越。 关键词&#xff1a;霍尔闭…

Linux----tr命令详细使用方法

【原文链接】Linux----tr命令详细使用方法 文章目录一、tr命令使用方法1.1 tr命令的作用1.2 tr命令格式1.3 tr命令常用的选项1.4 常用的匹配字符串二、tr命令常用实例2.1 如何查看文本中的控制字符2.2 将所有小写字母转换为大写字母2.3 将文件中的数字替换为&符号2.4 对命令…

android studio 升级 Dolphin | 2021.3.1 Patch 1 跟 View.isInEditMode,xml无法预览

最近一段时间Google又更新了AS的版本,一些小伙伴尝试了更新,发现在之前版本上好好的xml布局预览,在新版本上不显示了,新版本如下图所示。 一般来说出了新版本之后我们不会马上更新,因为会觉得新版本不稳定,问题多,但其实是问题不大,解决了就好了,那么我现在就遇到了一…

毕业设计 - 基于JSP的超市积分管理系统【源码 + 论文】

文章目录前言一、项目设计1. 模块设计系统功能需求管理员功能柜员功能2. 实现效果二、部分源码项目源码前言 今天学长向大家分享一个 web项目: 基于JSP的超市积分管理系统 一、项目设计 1. 模块设计 系统功能需求 &#xff08;1&#xff09;柜员信息的管理&#xff1a;包括…