乐优商城(十七)——搜索微服务(数据导入修改)

目录

一、索引库数据导入

1.1 创建搜索微服务

1.2 索引库数据格式分析

1.2.1 以结果为导向

1.2.2 需要什么数据

1.2.3 最终的数据结构

1.3 商品微服务提供接口

1.3.1 商品分类名称查询

1.3.2 编写FigenClient

1.4 导入数据

1.4.1 创建GoodsRepository

1.4.2 创建索引

1.4.3 导入数据


一、索引库数据导入

1.1 创建搜索微服务

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.search</groupId>
    <artifactId>leyou-search</artifactId>

    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <!-- eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml

server:
  port: 8083
spring:
  application:
    name: search-service
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 192.168.19.121:9300
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 5
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true  #当你获取host时,返回的不是主机名,而是ip
    ip-address: 127.0.0.1
    lease-expiration-duration-in-seconds: 10 #10秒不发送九过期
    lease-renewal-interval-in-seconds: 5 #每隔5秒发一次心跳

启动类

package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: 98050
 * Time: 2018-10-11 16:43
 * Feature: 启动器,开启fegin功能
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LySearchService {
    public static void main(String[] args) {
        SpringApplication.run(LySearchService.class,args);
    }
}

1.2 索引库数据格式分析

目前已有的数据是SKU和SPU,那么如何保存在索引库中?

1.2.1 以结果为导向

可以看到,每一个搜索结果都有至少1个商品,当选择大图下方的小图,商品会跟着变化。

因此,搜索的结果是SPU,即多个SKU的集合

既然搜索的结果是SPU,那么我们索引库中存储的应该也是SPU,但是却需要包含SKU的信息。

1.2.2 需要什么数据

直观能看到的:图片、价格、标题、副标题

暗藏的数据:spu的id,sku的id

另外,页面还有过滤条件:

这些过滤条件也都需要存储到索引库中,包括:

商品分类、品牌、可用来搜索的规格参数等

综上所述,需要的数据格式有:

spuId、skuId、商品分类id、品牌id、图片、价格、商品的创建时间、sku信息集、可搜索的规格参数

1.2.3 最终的数据结构

package com.leyou.pojo;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @Author: 98050
 * Time: 2018-10-11 17:21
 * Feature:搜索时对应的实体类
 */
@Document(indexName = "goods", type = "docs", shards = 1, replicas = 0)
public class Goods {
    @Id
    /**
     * spuId
     */
    private Long id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    /**
     * 所有需要被搜索的信息,包含标题,分类,甚至品牌
     */
    private String all;
    @Field(type = FieldType.Keyword, index = false)
    /**
     * 卖点
     */
    private String subTitle;
    /**
     * 品牌id
     */
    private Long brandId;
    /**
     * 1级分类id
     */
    private Long cid1;
    /**
     * 2级分类id
     */
    private Long cid2;
    /**
     * 3级分类id
     */
    private Long cid3;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 价格
     */
    private List<Long> price;
    @Field(type = FieldType.Keyword, index = false)
    /**
     * sku信息的json结构
     */
    private String skus;
    /**
     * 可搜索的规格参数,key是参数名,值是参数值
     */
    private Map<String, Object> specs;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAll() {
        return all;
    }

    public void setAll(String all) {
        this.all = all;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Long getBrandId() {
        return brandId;
    }

    public void setBrandId(Long brandId) {
        this.brandId = brandId;
    }

    public Long getCid1() {
        return cid1;
    }

    public void setCid1(Long cid1) {
        this.cid1 = cid1;
    }

    public Long getCid2() {
        return cid2;
    }

    public void setCid2(Long cid2) {
        this.cid2 = cid2;
    }

    public Long getCid3() {
        return cid3;
    }

    public void setCid3(Long cid3) {
        this.cid3 = cid3;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public List<Long> getPrice() {
        return price;
    }

    public void setPrice(List<Long> price) {
        this.price = price;
    }

    public String getSkus() {
        return skus;
    }

    public void setSkus(String skus) {
        this.skus = skus;
    }

    public Map<String, Object> getSpecs() {
        return specs;
    }

    public void setSpecs(Map<String, Object> specs) {
        this.specs = specs;
    }
}

一些特殊字段解释:

  • all:用来进行全文检索的字段,里面包含标题、商品分类信息

  • price:价格数组,是所有sku的价格集合。方便根据价格进行筛选过滤

  • skus:用于页面展示的sku信息,不索引,不搜索。包含skuId、image、price、title字段

  • specs:所有规格参数的集合。key是参数名,值是参数值。

    例如:在specs中存储 内存:4G,6G,颜色为红色,转为json就是:

{
    "specs":{
        "内存":[4G,6G],
        "颜色":"红色"
    }
}

当存储到索引库时,elasticsearch会处理为两个字段:

  • specs.内存:[4G,6G]

  • specs.颜色:红色

另外, 对于字符串类型,还会额外存储一个字段,这个字段不会分词,用作聚合。

  • specs.颜色.keyword:红色

1.3 商品微服务提供接口

索引库中的数据来自于数据库,不能直接去查询商品的数据库,因为真实开发中,每个微服务都是相互独立的,包括数据库也是一样。所以只能调用商品微服务提供的接口服务。

先确定需要的数据:

  • SPU信息

  • SKU信息

  • SPU的详情

  • 商品分类名称(拼接all字段)

再确定需要哪些服务:

  • 第一:分批查询spu的服务,已有。

  • 第二:根据spuId查询sku的服务,已有

  • 第三:根据spuId查询SpuDetail的服务,已有

  • 第四:根据商品分类id,查询商品分类名称,没有

  • 第五:根据商品品牌id,查询商品的品牌,没有

因此我们需要额外提供一个查询商品分类名称的接口。

1.3.1 商品分类名称查询

    @GetMapping("names")
    public ResponseEntity<List<String>> queryNameByIds(@RequestParam("ids")List<Long> ids){
        List<String> list = categoryService.queryNameByIds(ids);
        if (list == null || list.size() < 1){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }else {
            return ResponseEntity.ok(list);
        }
    }

1.3.2 编写FigenClient

存在的问题

在编写FeignClient时,代码遵循SpringMVC的风格,因此与商品微服务的Controller完全一致。如下所示:

@FeignClient(value = "item-service")
@RequestMapping("/goods")
public interface GoodsClient {

    /**
     * 分页查询商品
     * @param page
     * @param rows
     * @param saleable
     * @param key
     * @return
     */
    @GetMapping("/spu/page")
    ResponseEntity<PageResult<SpuBo>> querySpuByPage(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "rows", defaultValue = "5") Integer rows,
            @RequestParam(value = "saleable", defaultValue = "true") Boolean saleable,
            @RequestParam(value = "key", required = false) String key);

    /**
     * 根据spu商品id查询详情
     * @param id
     * @return
     */
    @GetMapping("/spu/detail/{id}")
    ResponseEntity<SpuDetail> querySpuDetailById(@PathVariable("id") Long id);

    /**
     * 根据spu的id查询sku
     * @param id
     * @return
     */
    @GetMapping("sku/list")
    ResponseEntity<List<Sku>> querySkuBySpuId(@RequestParam("id") Long id);
}

以上的这些代码直接从商品微服务中拷贝而来,完全一致。差别就是没有方法的具体实现。但是这样就存在一定的问题:

  • 代码冗余。尽管不用写实现,只是写接口,但服务调用方要写与服务controller一致的代码,有几个消费者就要写几次。

  • 增加开发成本。调用方还得清楚知道接口的路径,才能编写正确的FeignClient。

解决方案

  • 服务提供方不仅提供实体类,还要提供api接口声明

  • 调用方不用字自己编写接口方法声明,直接继承提供方给的Api接口即可

第一步

服务的提供方在leyou-item-interface中提供API接口,并编写接口声明

添加依赖

需要引入springMVC及leyou-common的依赖:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.leyou.common</groupId>
            <artifactId>leyou-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

商品分类服务接口

package com.leyou.item.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @Author: 98050
 * Time: 2018-10-11 20:05
 * Feature:商品分类服务接口
 */
@RequestMapping("category")
public interface CategoryApi {

    /**
     * 根据id,查询分类名称
     * @param ids
     * @return
     */
    @GetMapping("names")
    ResponseEntity<List<String>> queryNameByIds(@RequestParam("ids")List<Long> ids);
}

商品服务接口

返回值不再使用ResponseEntity

package com.leyou.item.api;

import com.leyou.common.pojo.PageResult;
import com.leyou.item.bo.SpuBo;
import com.leyou.item.pojo.Sku;
import com.leyou.item.pojo.SpuDetail;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @Author: 98050
 * Time: 2018-10-11 20:05
 * Feature:商品服务接口
 */
public interface GoodsApi {

    /**
     * 分页查询
     * @param page
     * @param rows
     * @param sortBy
     * @param desc
     * @param key
     * @param saleable
     * @return
     */
    @GetMapping("/spu/page")
    PageResult<SpuBo> querySpuByPage(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "rows", defaultValue = "5") Integer rows,
            @RequestParam(value = "sortBy", required = false) String sortBy,
            @RequestParam(value = "desc", defaultValue = "false") Boolean desc,
            @RequestParam(value = "key", required = false) String key,
            @RequestParam(value = "saleable",defaultValue = "true") Boolean saleable);

    /**
     * 根据spu商品id查询详情
     * @param id
     * @return
     */
    @GetMapping("/spu/detail/{id}")
    SpuDetail querySpuDetailById(@PathVariable("id") Long id);

    /**
     * 根据Spu的id查询其下所有的sku
     * @param id
     * @return
     */
    @GetMapping("sku/list")
    List<Sku> querySkuBySpuId(Long id);
}

第二步

在调用方leyou-search中编写FeignClient,但不用写方法声明了,直接继承leyou-item-interface提供的api接口:

商品的FeignClient 

package com.leyou.client;

import com.leyou.item.api.GoodsApi;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * Time: 2018-10-11 20:50
 * Feature:商品FeignClient
 */
@FeignClient(value = "item-service")
public interface GoodsClient extends GoodsApi {
}

商品分类的FeignClient

package com.leyou.client;

import com.leyou.item.api.CategoryApi;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * Time: 2018-10-11 20:49
 * Feature:商品分类FeignClient
 */
@FeignClient(value = "item-service")
public interface CategoryClient extends CategoryApi {
}

项目结构

测试:

package com.leyou.client;

import com.leyou.LySearchService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

/**
 * Author: 98050
 * Time: 2018-10-11 21:15
 * Feature:
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LySearchService.class)
public class CategoryClientTest {

    @Autowired
    private CategoryClient categoryClient;

    @Test
    public void testQueryCategories() {
        List<String> names = this.categoryClient.queryNameByIds(Arrays.asList(1L, 2L, 3L)).getBody();
        names.forEach(System.out::println);
    }
}

结果:

1.4 导入数据

1.4.1 创建GoodsRepository

package com.leyou.repository;

import com.leyou.pojo.Goods;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * @Author: 98050
 * Time: 2018-10-11 22:17
 * Feature:
 */
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {
}

1.4.2 创建索引

新建一个测试类,在里面进行数据的操作:

package com.leyou.client;

import com.leyou.LySearchService;
import com.leyou.pojo.Goods;
import com.leyou.repository.GoodsRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @Author: 98050
 * Time: 2018-10-11 22:13
 * Feature:elasticsearch goods索引创建
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LySearchService.class)
public class ElasticsearchTest {
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void createIndex(){
        // 创建索引
        this.elasticsearchTemplate.createIndex(Goods.class);
        // 配置映射
        this.elasticsearchTemplate.putMapping(Goods.class);
    }
}

结果:

1.4.3 导入数据

导入数据其实就是查询数据,然后把查询到的Spu转变为Goods来保存,因此先编写一个SearchService,然后在里面定义一个方法, 把Spu转为Goods

package com.leyou.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.client.CategoryClient;
import com.leyou.client.GoodsClient;
import com.leyou.client.SpecClient;
import com.leyou.item.pojo.Sku;
import com.leyou.item.pojo.Spu;
import com.leyou.item.pojo.SpuDetail;
import com.leyou.pojo.Goods;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

/**
 * @Author: 98050
 * Time: 2018-10-11 22:59
 * Feature: 数据导入
 */
@Service
public class SearchService {
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    private ObjectMapper mapper = new ObjectMapper();

    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        //1.查询商品分类名称
        List<String> names = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3())).getBody();
        //2.查询sku
        List<Sku> skus = this.goodsClient.querySkuBySpuId(spu.getId());
        //3.查询详情
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId());

        //4.处理sku,仅封装id,价格、标题、图片、并获得价格集合

        List<Long> prices = new ArrayList<>();
        List<Map<String,Object>> skuLists = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            Map<String,Object> skuMap = new HashMap<>();
            skuMap.put("id",sku.getId());
            skuMap.put("title",sku.getTitle());
            skuMap.put("price",sku.getPrice());
            //取第一张图片
            skuMap.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(),",")[0]);
            skuLists.add(skuMap);
        });

        //提取公共属性
        List<Map<String,Object>> genericSpecs = mapper.readValue(spuDetail.getSpecifications(),new TypeReference<List<Map<String,Object>>>(){});
        //过滤规格模板,把所有可搜索的信息保存到Map中
        Map<String,Object> specMap = new HashMap<>();

        String searchable = "searchable";
        String v = "v";
        String k = "k";
        String options = "options";

        genericSpecs.forEach(m -> {
            List<Map<String, Object>> params = (List<Map<String, Object>>) m.get("params");
            params.forEach(spe ->{
                if ((boolean)spe.get(searchable)){
                    if (spe.get(v) != null){
                        specMap.put(spe.get(k).toString(), spe.get(v));
                    }else if (spe.get(options) != null){
                        specMap.put(spe.get(k).toString(), spe.get(options));
                    }
                }
            });
        });
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " "));
        goods.setPrice(prices);
        goods.setSkus(mapper.writeValueAsString(skuLists));
        goods.setSpecs(specMap);
        return goods;
    }

}

然后编写一个测试类,循环查询Spu,然后调用SearchService中的方法,把SPU变为Goods,然后写入索引库:

package com.leyou.client;

import com.leyou.common.pojo.PageResult;
import com.leyou.item.bo.SpuBo;
import com.leyou.LySearchService;
import com.leyou.pojo.Goods;
import com.leyou.repository.GoodsRepository;
import com.leyou.service.impl.SearchServiceImpl;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.*;

/**
 * @Author: 98050
 * Time: 2018-10-11 22:13
 * Feature:elasticsearch goods索引创建
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LySearchService.class)
public class ElasticsearchTest {
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpuClient spuClient;

    @Autowired
    private SearchServiceImpl searchService;

    @Test
    public void createIndex(){
        // 创建索引
        this.elasticsearchTemplate.createIndex(Goods.class);
        // 配置映射
        this.elasticsearchTemplate.putMapping(Goods.class);
    }

    @Test
    public void loadData() throws IOException {
        List<SpuBo> list = new ArrayList<>();
        int page = 1;
        int row = 100;
        int size;
        do {
            //分页查询数据
            PageResult<SpuBo> result = this.goodsClient.querySpuByPage(page, row, null, true, null, true);
            List<SpuBo> spus = result.getItems();
            size = spus.size();
            page ++;
            list.addAll(spus);
        }while (size == 100);

        //创建Goods集合
        List<Goods> goodsList = new ArrayList<>();
        //遍历spu
        for (SpuBo spu : list) {
            try {
                System.out.println("spu id" + spu.getId());
                Goods goods = this.searchService.buildGoods(spu);
                goodsList.add(goods);
            } catch (IOException e) {
                System.out.println("查询失败:" + spu.getId());
            }
        }
        this.goodsRepository.saveAll(goodsList);
    }

    @Test
    public void testAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withQuery(QueryBuilders.termQuery("cid3",76)).withSourceFilter(new FetchSourceFilter(new String[]{""},null)).withPageable(PageRequest.of(0,1));
        Page<Goods> goodsPage = this.goodsRepository.search(queryBuilder.build());
        goodsPage.forEach(System.out::println);
    }

    @Test
    public void testDelete(){
        this.goodsRepository.deleteById((long) 2);
    }

}

通过kibana查询, 可以看到数据成功导入:

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
自己做的乐优商城的XMIND文件,学习分享下。乐优商城 搭建父工程 pom.xml 添加依赖 springCloud mybatis启动器 通用Mapper启动器 mysql驱动 分页助手启动器 FastDFS客户端 其他配置 构建设置 环境设置 EurekaServer注册中心 添加的依赖 启动类 application.yml 创建Zuul网关 依赖 启动类 application.yml 创建商品微服务 ly-item-interface:主要是对外暴露的API接口及相关实体类 ly-item-service:所有业务逻辑及内部使用接口 创建父工程ly-item ly-item-interface ly-item-service 依赖 启动器 application.yml 添加商品微服务的路由规则 通用工具模块Common utils CookieUtils IdWorker JsonUtils NumberUtils 依赖 通用异常处理 测试结构 pojo service @Service web @RestController @RequestMapping @Autowired @PostMapping 引入Common依赖 Common advice 拦截异常、 CommonExceptionHandler ResponseEntity @ControllerAdvice @ExceptionHandler enums 异常的枚举 、ExceptionEnum exception 自定义异常、LyException 接口RuntimeException @Getter @NoArgsConstructor @AllArgsConstructor vo 异常结果处理对象、ExceptionResult @Data 构造方法ExceptionResult ly-item-service CategoryQuery 分类查询 实体类 @Table(name="tb_category") 声明此对象映射到数据库数据表,通过它可以为实体指定表(talbe) @Data 注解在类上, 为类提供读写属性, 此外还提供了 equals()、hashCode()、toString() 方法 @Id & @GeneratedValue(strategy= GenerationType.IDENTITY) 自动增长,适用于支持自增字段的数据库 mapper Mapper IdListMapper 根据id操作数据库 @RequestMapping("category") Controller @RestController @Controller 处理HTTP请求 @ResponseBody 返回 json 数据 @GetMapping("list") ResponseEntity @ResponseBody可以直接返回Json结果 不仅可以返回json结果,还可以定义返回的HttpHeaders和HttpStatus service @Service 自动注册到Spring容器,不需要再在applicationContext.xml文件定义bean了 @Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 select select * from category c where c.pid = #{pid} CollectionUtils.isnotblank 判断集合是否为空 测试 可以利用url直接查询数据库,能否访问得到数据 报错 启动类 没有扫描到 @MapperScan("com.leyou.item.mapper") ,目录结构关系 访问网页报错 CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 跨域问题 浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。因此:跨域问题 是针对ajax的一种限制。 解决跨域问题的方案 CORS 规范化的跨域请求解决方案,安全可靠 什么是cors 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 原理 简单请求 当浏览器发现发现的ajax请求是简单请求时,会在请求头中携带一个字段:Origin 如果服务器允许跨域,需要在返回的响应头中携带下面信息 Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*,代表任意 Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true 实现非常简单 gateway网关中编写一个配置类 GlobalCorsConfig 添加CORS配置信息 允许的域,不要写*,否则cookie就无法使用了 是否发送Cookie信息 允许的请求方式 允许的头信息 有效时长 添加映射路径,我们拦截一切请求 返回新的CorsFilter 提交方式 GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源 BUG 分类不能打开,当添加后却能打开。 修改一天的BUG 最后发现是实体类里属性大小写的问题引起。 注意 Bule_bird 就必须写成 BlueBird Brand 查询 实体类 PageResult 响应结果 分页结果一般至少需要两个数据 总条数 total 当前页数据 items 有些还需要总页数 总页数 totalPage Controller @RequestParam(value = "page",defaultValue = "1") Integer page GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上 defaultValue 默认值 required 默认值为true , 当为false时 这个注解可以不传这个参数 null || .size()==0 ResponseEntity(HttpStatus.NOT_FOUND) 返回404没找到 ResponseEntity.ok 返回ok状态 service 开始分页 通用分页拦截器 PageHelper.startPage(page,row); 过滤 Example查询 Example example = new Example(Brand.class); mybatis的逆向工程中会生成实例及实例对应的example,example用于添加条件,相当where后面的部分 xxxExample example = new xxxExample(); Criteria criteria = new Example().createCriteria(); StringUtils.isNotBlank isNotBlank(str) 等价于 str != null && str.length > 0 && str.trim().length > 0 str.trim() 去掉字符串头尾的空格 测试 报错500 com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'idASC' in 'order clause' 错误:(desc ? "DESC" : "ASC"); 正确:(desc ? " DESC" : " ASC"); 字符串空格问题 新增 Controller (Brand brand,@RequestParam("cids") List cids) ResponseEntity 无返回值 new ResponseEntity(HttpStatus.CREATED); 201成功 service @Transactional 自动纳入 Spring 的事务管理 使用默认配置,抛出异常之后,事务会自动回滚,数据不会插入到数据库。 setId(null) insert(brand) 新增中间表 mapper @Insert (#{cid},#{bid}) @Param 表示给参数命名,名称就是括号中的内容 name 命名为aa,然后sql语句....where s_name= #{aa} 中就可以根据aa得到参数值 修改 回显 Controller @PathVariable("bid") 通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。 select * from tb_category where id in (select category_id from tb_category_brand where brand_id = #{bid}) 测试 报错500 空指针异常 调用Service时候 忘记@Autowired 保存 VO视图对象 @NoArgsConstructor 生成一个无参数的构造方法 @AllArgsConstructor 会生成一个包含所有变量 Controller @PutMapping 添加信息,倾向于用@PostMapping,如果是更新信息,倾向于用@PutMapping。两者差别不是很明显 return ResponseEntity.ok().build(); 无返回值 service 根据id修改 先删除后新增 删除(前端有问题,待完善) spec Group 品牌分类id查询 实体类 @Transient 指定该属性或字段不是永久的。 它用于注释实体类,映射超类或可嵌入类的属性或字段。 @Column(name = "'numeric'") 用来标识实体类中属性与数据表中字段的对应关系 name 定义了被标注字段在数据库表中所对应字段的名称; mapper service Controller 测试 报错500 实体类@table路径写错 新增 Controller @RequestBody 常用其来处理application/json类型 子主题 2 将请求体中的JSON字符串绑定到相应的bean上 修改 Controller @PutMapping service updateByPrimaryKey 删除 Controller @DeleteMapping @PathVariable Param 规格组id查询规格 url:params?gid=14 @GetMapping("params") Controller @RequestParam 新增 @PostMapping("param") @RequestBody ResponseEntity.status(HttpStatus.CREATED).build(); 修改 @RequestBody 删除 @PathVariable 分支主题 3 遇到的问题 pom.xml 文件未下载完整,删掉后重新下载 能存在重复文件,IDEA不能确定文件路径,需要搜索删掉多余的 Param 删除 小问题:数据库删除后页面没有立即显示 Brand 删除(前端有问题,待完善)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值