第五章

1. 登录问题解决

在进行登录认证的时候,认证代码是写在buyer-api中的,这显然是不合适的,因为只有sso才具有登录认证的能力,所以相应的认证代码需要调用sso来实现。

package com.mszlu.shop.buyer.handler.security;

import com.alibaba.fastjson.JSON;
import com.mszlu.shop.common.cache.CachePrefix;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.security.UserEnums;
import com.mszlu.shop.common.utils.ResponseUtil;
import com.mszlu.shop.common.utils.token.SecretKeyUtil;
import com.mszlu.shop.common.utils.token.SecurityKey;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.sso.api.SSOApi;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * 认证结果过滤器
 */
@Slf4j
public class BuyerAuthenticationFilter extends BasicAuthenticationFilter {


    private StringRedisTemplate redisTemplate;
    private SSOApi ssoApi;

    /**
     * 自定义构造器
     *
     * @param authenticationManager
     * @param ssoApi
     */
    public BuyerAuthenticationFilter(AuthenticationManager authenticationManager
            , StringRedisTemplate redisTemplate, SSOApi ssoApi) {
        super(authenticationManager);
        this.redisTemplate = redisTemplate;
        this.ssoApi = ssoApi;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        //从header中获取jwt
        String jwt = request.getHeader(SecurityKey.ACCESS_TOKEN);
        try {
            //如果没有token 则return
            if (StringUtils.isBlank(jwt)) {
                chain.doFilter(request, response);
                return;
            }
            //获取用户信息,存入context
            UsernamePasswordAuthenticationToken authentication = getAuthentication(jwt, response);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            log.error("BuyerAuthenticationFilter-> member authentication exception:", e);
        }
        chain.doFilter(request, response);
    }

    /**
     * 解析用户
     *
     * @param jwt
     * @param response
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String jwt, HttpServletResponse response) {

        try {

            AuthUser authUser = ssoApi.checkToken(jwt);

            if (authUser != null) {
                //构造返回信息
                List<GrantedAuthority> auths = new ArrayList<>();
                auths.add(new SimpleGrantedAuthority("ROLE_" + authUser.getRole().name()));
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authUser.getUsername(), null, auths);
                authentication.setDetails(authUser);
                return authentication;
            }
            ResponseUtil.output(response, 401, Result.noLogin());
            return null;
        } catch (Exception e) {
            log.error("user analysis exception:", e);
        }
        ResponseUtil.output(response, 401, Result.noLogin());
        return null;
    }

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.mszlu.shop.sso.api;

import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.model.buyer.vo.member.MemberVo;
import com.mszlu.shop.sso.service.MemberService;
import com.mszlu.shop.sso.service.VerificationService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

@DubboService(version = "1.0.0")
public class SSOApiImpl implements SSOApi {

    @Autowired
    private MemberService memberService;
    @Autowired
    private VerificationService verificationService;

    @Override
    public MemberVo findMemberById(Long id) {
        return memberService.findMemberById(id);
    }

    @Override
    public AuthUser checkToken(String token) {
        return verificationService.checkToken(token);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 public AuthUser checkToken(String token) {
        try {
            Claims claims
                    = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKey())
                    .parseClaimsJws(token).getBody();
            //获取存储在claims中的用户信息
            String json = claims.get(SecurityKey.USER_CONTEXT).toString();
            AuthUser authUser = JSON.parseObject(json, AuthUser.class);

            //校验redis中是否有权限
            Boolean hasKey = redisTemplate.hasKey(CachePrefix.ACCESS_TOKEN.name() + UserEnums.MEMBER.name() + token);
            if (hasKey != null && hasKey) {
                return authUser;
            }
        }catch (ExpiredJwtException e) {
            //token过期了
            log.debug("user analysis exception:", e);
        }
        return null;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

2. 商品搜索

2.1 安装ES

##下载es的镜像
docker pull elasticsearch:7.12.1
##创建es的容器 并启动  single-node单机
docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.12.1
##测试
http://192.168.200.100:9200/
##下载kibana的镜像
docker pull kibana:7.12.1
##准备kibana的配置文件
docker inspect es的容器id(docker ps -a 查看)
## 找到  "IPAddress": "172.17.0.2" 找出es对应的容器ip地址
## Default Kibana configuration for docker target
server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://172.17.0.2:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true

##启动kibana
docker run -d --restart=always --log-driver json-file --log-opt max-size=100m --log-opt max-file=2 --name kibana -p 5601:5601 -v /opt/docker/es/kibana.yml:/usr/share/kibana/config/kibana.yml kibana:7.12.1
##测试
http://192.168.200.100:5601/
##命令
GET /_search ##获取所有数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2.2 安装IK分词器

##进入容器
docker exec -it es
##安装ik插件
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
##如果插件缺失 可以宿主机下载 copy到doker容器中
docker cp ik/config 容器ID:/usr/share/elasticsearch/plugins/analysis-ik
##重启容器
1
2
3
4
5
6
7

2.3 商品表

见资料

package com.mszlu.shop.model.buyer.pojo.goods;

import lombok.Data;
import java.math.BigDecimal;

/**
 * 商品
 */
@Data
public class Goods  {

    private Long id;
    /**
     * 商品名称
     */
    private String goodsName;
    /**
     * 商品编号
     */
    private String sn;
    /**
     * 品牌id
     */
    private String brandId;

    private String categoryPath;

    //计量单位
    private String goodsUnit;

    /**
     * 卖点
     */
    private String sellingPoint;

    /**
     * 重量
     */
    private Double weight;
    /**
     * 上架状态
     *
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsStatusEnum
     */
    private Integer marketEnable;
    /**
     * 详情
     */
    private String intro;
    /**
     * 商品价格
     */
    private BigDecimal price;
    /**
     * 成本价格
     */
    private BigDecimal cost;

    /**
     * 购买数量
     */
    private Integer buyCount;
    /**
     * 库存
     */
    private Integer quantity;
    /**
     * 商品好评率
     */
    private Double grade;
    /**
     * 缩略图路径
     */
    private String thumbnail;
    /**
     * 小图路径
     */
    private String small;
    /**
     * 原图路径
     */
    private String original;
    /**
     * 店铺分类id
     */
    private String storeCategoryPath;
    /**
     * 评论数量
     */
    private Integer commentNum;
    /**
     * 卖家id
     */
    private String storeId;
    /**
     * 卖家名字
     */
    private String storeName;
    /**
     * 运费模板id
     */
    private String templateId;
    /**
     * 审核状态
     *
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsAuthEnum
     */
    private Integer isAuth;
    /**
     * 审核信息
     */
    private String authMessage;
    /**
     * 下架原因
     */
    private String underMessage;
    /**
     * 是否自营
     */
    private Boolean selfOperated;
    /**
     * 商品移动端详情
     */
    private String mobileIntro;
    /**
     * 商品视频
     */
    private String goodsVideo;


    private boolean recommend;

    private String salesModel;


    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsTypeEnum
     */
    private Integer goodsType;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

商品的sku表:

package com.mszlu.shop.model.buyer.pojo.goods;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;

/**
 * 商品sku
 */
@Data
public class GoodsSku {

    private Long id;

    @ApiModelProperty(value = "商品id")
    private String goodsId;

    @ApiModelProperty(value = "规格信息json", hidden = true)
    @JsonIgnore
    private String specs;

    @ApiModelProperty(value = "规格信息")
    private String simpleSpecs;

    @ApiModelProperty(value = "配送模版id")
    private String freightTemplateId;

    @ApiModelProperty(value = "是否是促销商品")
    private Boolean isPromotion;

    @ApiModelProperty(value = "促销价")
    private Double promotionPrice;

    @ApiModelProperty(value = "商品名称")
    private String goodsName;

    @ApiModelProperty(value = "商品编号")
    private String sn;

    @ApiModelProperty(value = "品牌id")
    private String brandId;

    @ApiModelProperty(value = "分类path")
    private String categoryPath;

    @ApiModelProperty(value = "计量单位")
    private String goodsUnit;

    @ApiModelProperty(value = "卖点")
    private String sellingPoint;

    @ApiModelProperty(value = "重量")
    private Double weight;
    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsStatusEnum
     */
    @ApiModelProperty(value = "上架状态")
    private Integer marketEnable;

    @ApiModelProperty(value = "商品详情")
    private String intro;

    @ApiModelProperty(value = "商品价格")
    private BigDecimal price;

    @ApiModelProperty(value = "成本价格")
    private BigDecimal cost;

    @ApiModelProperty(value = "浏览数量")
    private Integer viewCount;

    @ApiModelProperty(value = "购买数量")
    private Integer buyCount;

    @ApiModelProperty(value = "库存")
    private Integer quantity;

    @ApiModelProperty(value = "商品好评率")
    private Double grade;

    @ApiModelProperty(value = "缩略图路径")
    private String thumbnail;

    @ApiModelProperty(value = "大图路径")
    private String big;

    @ApiModelProperty(value = "小图路径")
    private String small;

    @ApiModelProperty(value = "原图路径")
    private String original;

    @ApiModelProperty(value = "店铺分类id")
    private String storeCategoryPath;

    @ApiModelProperty(value = "评论数量")
    private Integer commentNum;

    @ApiModelProperty(value = "卖家id")
    private String storeId;

    @ApiModelProperty(value = "卖家名字")
    private String storeName;

    @ApiModelProperty(value = "运费模板id")
    private String templateId;

    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsAuthEnum
     */
    @ApiModelProperty(value = "审核状态")
    private Integer isAuth;

    @ApiModelProperty(value = "审核信息")
    private String authMessage;

    @ApiModelProperty(value = "下架原因")
    private String underMessage;

    @ApiModelProperty(value = "是否自营")
    private Boolean selfOperated;

    @ApiModelProperty(value = "商品移动端详情")
    private String mobileIntro;

    @ApiModelProperty(value = "商品视频")
    private String goodsVideo;

    @ApiModelProperty(value = "是否为推荐商品", required = true)
    private boolean recommend;

    @ApiModelProperty(value = "销售模式", required = true)
    private String salesModel;
    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsTypeEnum
     */
    @ApiModelProperty(value = "商品类型", required = true)
    private Integer goodsType;

    public Double getWeight() {
        if (weight == null) {
            return 0d;
        }
        return weight;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

sku表和goods表的关系为,一个商品可以对应 多个sku。

比如 苹果手机有多个sku信息(16G 土豪金,64G 玫瑰金等)

涉及到的枚举:

package com.mszlu.shop.model.buyer.eums.goods;

import java.util.HashMap;
import java.util.Map;

/**
 * 商品类型枚举
 */
public enum GoodsStatusEnum {
    /**
     * 上架
     */
    UPPER(1,"上架"),
    /**
     * 下架
     */
    DOWN(2,"下架");

    private int code;
    private String message;

    private static final Map<Integer, GoodsStatusEnum> CODE_MAP = new HashMap<>(2);

    static{
        for(GoodsStatusEnum goodsStatusEnum: values()){
            CODE_MAP.put(goodsStatusEnum.getCode(), goodsStatusEnum);
        }
    }

    GoodsStatusEnum(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public static GoodsStatusEnum codeOf(int code){
        return CODE_MAP.get(code);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.mszlu.shop.model.buyer.eums.goods;

import com.mszlu.shop.model.buyer.eums.ClientType;

import java.util.HashMap;
import java.util.Map;

/**
 * 商品审核
 */
public enum GoodsAuthEnum {
    /**
     * 需要审核 并且待审核
     */
    TOBEAUDITED(1,"待审核"),
    /**
     * 审核通过
     */
    PASS(2,"审核通过"),
    /**
     * 审核通过
     */
    REFUSE(3,"审核拒绝");
    private int code;
    private String message;

    private static final Map<Integer, GoodsAuthEnum> CODE_MAP = new HashMap<>(3);

    static{
        for(GoodsAuthEnum goodsAuthEnum: values()){
            CODE_MAP.put(goodsAuthEnum.getCode(), goodsAuthEnum);
        }
    }

    GoodsAuthEnum(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public static GoodsAuthEnum codeOf(int code){
        return CODE_MAP.get(code);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.mszlu.shop.model.buyer.eums.goods;

import java.util.HashMap;
import java.util.Map;

/**
 * 商品类型
 */
public enum GoodsTypeEnum {


    PHYSICAL_GOODS(1,"实物商品"),

    VIRTUAL_GOODS(2,"虚拟商品"),

    E_COUPON(3,"电子卡券");


    private int code;
    private String message;

    private static final Map<Integer, GoodsTypeEnum> CODE_MAP = new HashMap<>(3);

    static{
        for(GoodsTypeEnum goodsTypeEnum: values()){
            CODE_MAP.put(goodsTypeEnum.getCode(), goodsTypeEnum);
        }
    }

    GoodsTypeEnum(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public static GoodsTypeEnum codeOf(int code){
        return CODE_MAP.get(code);
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

2.4 将商品数据导入ES

为了加快访问速度,以及可以进行分词搜索,一般商品数据都是存在ES当中的。

2.4.1 导包

model中:

 <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-elasticsearch</artifactId>
            <version>4.2.1</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <artifactId>transport</artifactId>
                    <groupId>org.elasticsearch.client</groupId>
                </exclusion>
            </exclusions>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12

service-impl:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
1
2
3
4

配置:

##es的配置
spring.elasticsearch.rest.uris=http://192.168.200.100:9200
spring.data.elasticsearch.repositories.enabled=true
spring.data.elasticsearch.client.reactive.endpoints=192.168.200.100:9200
1
2
3
4

2.4.2 编码

ES的index定义:

package com.mszlu.shop.model.buyer.pojo.es;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.mszlu.shop.model.buyer.eums.PromotionTypeEnum;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * es商品索引
 **/
@Data
@Document(indexName = "es_goods")
@ToString
@NoArgsConstructor
public class EsGoodsIndex implements Serializable {

    @Id
    private String id;

    /**
     * 商品id
     */
    @Field(type = FieldType.Text)
    private String goodsId;

    /**
     * 商品名称
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String goodsName;

    /**
     * 商品编号
     */
    @Field(type = FieldType.Keyword)
    private String sn;

    /**
     * 卖家id
     */
    @Field(type = FieldType.Text)
    private String storeId;

    /**
     * 卖家名称
     */
    @Field(type = FieldType.Text)
    private String storeName;

    /**
     * 销量
     */
    @Field(type = FieldType.Integer)
    private Integer buyCount;

    /**
     * 小图
     */
    private String small;

    /**
     * 缩略图
     */
    private String thumbnail;

    /**
     * 品牌id
     */
    @Field(type = FieldType.Integer, fielddata = true)
    private String brandId;

    /**
     * 分类path
     */
    @Field(type = FieldType.Keyword, fielddata = true)
    private String categoryPath;

    /**
     * 店铺分类id
     */
    @Field(type = FieldType.Keyword)
    private String storeCategoryPath;

    /**
     * 商品价格
     */
    @Field(type = FieldType.Double)
    private Double price;

    /**
     * 促销价
     */
    @Field(type = FieldType.Double)
    private Double promotionPrice;

    /**
     * 如果是积分商品需要使用的积分
     */
    @Field(type = FieldType.Integer)
    private Integer point;

    /**
     * 评价数量
     */
    @Field(type = FieldType.Integer)
    private Integer commentNum;

    /**
     * 好评数量
     */
    @Field(type = FieldType.Integer)
    private Integer highPraiseNum;

    /**
     * 好评率
     */
    @Field(type = FieldType.Double)
    private Double grade;

    /**
     * 详情
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String intro;

    /**
     * 商品移动端详情
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String mobileIntro;

    /**
     * 是否自营
     */
    @Field(type = FieldType.Boolean)
    private Boolean selfOperated;

    /**
     * 是否为推荐商品
     */
    @Field(type = FieldType.Boolean)
    private Boolean recommend;

    /**
     * 销售模式
     */
    @Field(type = FieldType.Text)
    private String salesModel;

    /**
     * 审核状态
     */
    @Field(type = FieldType.Integer)
    private Integer isAuth;

    /**
     * 卖点
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String sellingPoint;

    /**
     * 上架状态
     */
    @Field(type = FieldType.Integer)
    private Integer marketEnable;

    /**
     * 商品视频
     */
    @Field(type = FieldType.Text)
    private String goodsVideo;

    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Date releaseTime;

    /**
     * 商品类型 详情GoodsTypeEnum
     */
    private Integer goodsType;

    /**
     * 商品属性(参数和规格)
     */
    @Field(type = FieldType.Nested)
    private List<EsGoodsAttribute> attrList;

    /**
     * 商品促销活动集合
     * key 为 促销活动类型
     *
     * @see PromotionTypeEnum
     * value 为 促销活动实体信息
     */
    @Field(type = FieldType.Nested)
    private Map<String, Object> promotionMap;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package com.mszlu.shop.buyer.service;

import com.mszlu.shop.model.buyer.params.EsGoodsSearchParam;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.vo.goods.GoodsPageVo;

public interface GoodsService {

    void importES();
}

1
2
3
4
5
6
7
8
9
10
11
package com.mszlu.shop.buyer.service.impl.goods;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.shop.buyer.mapper.GoodsMapper;
import com.mszlu.shop.buyer.service.GoodsService;
import com.mszlu.shop.model.buyer.params.EsGoodsSearchParam;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.pojo.es.EsGoodsIndex;
import com.mszlu.shop.model.buyer.pojo.goods.Goods;
import com.mszlu.shop.model.buyer.vo.goods.GoodsPageVo;
import org.apache.dubbo.config.annotation.DubboService;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@DubboService(version = "1.0.0")
public class GoodsServiceImpl implements GoodsService {

    @Resource
    private GoodsMapper goodsMapper;
	@Autowired
    private GoodsSkuMapper goodsSkuMapper;
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;

    @Override
    public void importES() {
        //导入es数据的时候,导入sku的数据,一般情况sku的数据 会有商品的数据属性,同时还有其余的属性(比如规格等信息)
        //一个商品 会对应多个sku
        LambdaQueryWrapper<GoodsSku> queryWrapper1 = new LambdaQueryWrapper<>();
        List<GoodsSku> goodsSkusList = goodsSkuMapper.selectList(queryWrapper1);
        for (GoodsSku goodsSku : goodsSkusList) {
            EsGoodsIndex esGoodsIndex = new EsGoodsIndex();
            BeanUtils.copyProperties(goodsSku,esGoodsIndex);

            esGoodsIndex.setId(goodsSku.getId().toString());
            esGoodsIndex.setGoodsId(goodsSku.getGoodsId().toString());
            esGoodsIndex.setPrice(goodsSku.getPrice().doubleValue());
            BigDecimal promotionPrice = goodsSku.getPromotionPrice();
            if (promotionPrice != null) {
                esGoodsIndex.setPromotionPrice(promotionPrice.doubleValue());
            }
            elasticsearchTemplate.save(esGoodsIndex);
        }
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

dao:

package com.mszlu.shop.buyer.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.shop.model.buyer.pojo.goods.Goods;

public interface GoodsMapper extends BaseMapper<Goods> {
}

package com.mszlu.shop.buyer.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.shop.model.buyer.pojo.goods.GoodsSku;

public interface GoodsSkuMapper extends BaseMapper<GoodsSku> {
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

2.4.3 写测试用例

package com.mszlu.shop.dubbo.service;

import com.mszlu.shop.buyer.service.GoodsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestES {

    @Autowired
    private GoodsService goodsService;

    @Test
    public void testImportGoods(){
        goodsService.importES();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

2.5 搜索接口说明

接口路径:/buyer/goods/es

参数:

​ 搜索参数和分页参数(之前写过)

package com.mszlu.shop.model.buyer.params;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
public class EsGoodsSearchParam implements Serializable {

    @ApiModelProperty(value = "关键字")
    private String keyword;

    @ApiModelProperty(value = "分类")
    private String categoryId;

    @ApiModelProperty(value = "品牌,可以多选 品牌Id@品牌Id@品牌Id")
    private String brandId;

    @ApiModelProperty(value = "价格", example = "10_30")
    private String price;

    @ApiModelProperty(value = "属性:参数名_参数值@参数名_参数值", example = "屏幕类型_LED@屏幕尺寸_15英寸")
    private String prop;

    @ApiModelProperty(value = "规格项列表")
    private List<String> nameIds;

    @ApiModelProperty(value = "卖家id,搜索店铺商品的时候使用")
    private String storeId;

    @ApiModelProperty(value = "商家分组id,搜索店铺商品的时候使用")
    private String storeCatId;

    @ApiModelProperty(hidden = true)
    private Map<String, List<String>> notShowCol = new HashMap<>();

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

返回结果:

package com.mszlu.shop.model.buyer.vo.goods;

import com.mszlu.shop.model.buyer.pojo.es.EsGoodsIndex;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class GoodsPageVo implements Serializable {


    private Long totalElements;

    private List<EsGoodsIndex> content;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

2.6 Controller

  @ApiOperation(value = "从ES中获取商品信息")
    @GetMapping("/es")
    public Result<GoodsPageVo> getGoodsByPageFromEs(EsGoodsSearchParam goodsSearchParams, PageParams pageParams) {
        GoodsPageVo goodsPageVo = goodsSearchService.searchGoods(goodsSearchParams, pageParams);
        return Result.success(goodsPageVo);
    }
1
2
3
4
5
6

2.7 Service

@DubboReference(version = "1.0.0")
private GoodsService goodsService;  

public GoodsPageVo searchGoods(EsGoodsSearchParam goodsSearchParams, PageParams pageParams) {
        return goodsService.searchGoods(goodsSearchParams,pageParams);
    }
1
2
3
4
5
6

2.8 dubbo服务

 @Override
    public GoodsPageVo searchGoods(EsGoodsSearchParam goodsSearchParams, PageParams pageParams) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        MatchQueryBuilder matchQueryBuilder =  QueryBuilders.matchQuery("goodsName",goodsSearchParams.getKeyword());
        MatchQueryBuilder matchQueryBuilder1 =  QueryBuilders.matchQuery("sellingPoint",goodsSearchParams.getKeyword());
        boolQueryBuilder.must(matchQueryBuilder);
        boolQueryBuilder.should(matchQueryBuilder1);
        PageRequest pageRequest = PageRequest.of(pageParams.getPageNumber() -1, pageParams.getPageSize());
        NativeSearchQuery query =
                new NativeSearchQueryBuilder()
                        .withQuery(boolQueryBuilder)
                        .withPageable(pageRequest)
                        .build();
        SearchHits<EsGoodsIndex> search = elasticsearchTemplate.search(query, EsGoodsIndex.class);
        SearchPage<EsGoodsIndex> searchHits = SearchHitSupport.searchPageFor(search, pageRequest);

        GoodsPageVo goodsPageVo = new GoodsPageVo();
        goodsPageVo.setTotalElements(searchHits.getTotalElements());
        List<EsGoodsIndex> collect = searchHits.getContent().stream().map(SearchHit::getContent).collect(Collectors.toList());
        goodsPageVo.setContent(collect);
        return goodsPageVo;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

3. 搜索商品详情

3.1 接口说明

接口路径:/goods/sku/{goodsId}/{skuId}

参数说明:

名称类型描述
goodsIdString商品id
skuIdStringsku id

返回值:

package com.mszlu.shop.model.buyer.vo.goods;

import lombok.Data;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

@Data
public class GoodsDetailVo implements Serializable {

    //商品sku数据
    private GoodsSkuVO data;

    //商品类别名称列表
    private List<String> categoryName;

    //规格信息
    private List<GoodsSkuSpecVO> specs;
    //促销信息
    private Map<String, Object> promotionMap;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.mszlu.shop.model.buyer.vo.goods;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

@Data
public class GoodsSkuVO implements Serializable {

    private Long id;

    @ApiModelProperty(value = "商品id")
    private String goodsId;

    @ApiModelProperty(value = "规格信息")
    private String simpleSpecs;

    @ApiModelProperty(value = "配送模版id")
    private String freightTemplateId;

    @ApiModelProperty(value = "是否是促销商品")
    private Boolean isPromotion;

    @ApiModelProperty(value = "促销价")
    private Double promotionPrice;

    @ApiModelProperty(value = "商品名称")
    private String goodsName;

    @ApiModelProperty(value = "商品编号")
    private String sn;

    @ApiModelProperty(value = "品牌id")
    private String brandId;

    @ApiModelProperty(value = "分类path")
    private String categoryPath;

    @ApiModelProperty(value = "计量单位")
    private String goodsUnit;

    @ApiModelProperty(value = "卖点")
    private String sellingPoint;

    @ApiModelProperty(value = "重量")
    private Double weight;
    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsStatusEnum
     */
    @ApiModelProperty(value = "上架状态")
    private Integer marketEnable;

    @ApiModelProperty(value = "商品详情")
    private String intro;

    @ApiModelProperty(value = "商品价格")
    private BigDecimal price;

    @ApiModelProperty(value = "成本价格")
    private BigDecimal cost;

    @ApiModelProperty(value = "浏览数量")
    private Integer viewCount;

    @ApiModelProperty(value = "购买数量")
    private Integer buyCount;

    @ApiModelProperty(value = "库存")
    private Integer quantity;

    @ApiModelProperty(value = "商品好评率")
    private Double grade;

    @ApiModelProperty(value = "缩略图路径")
    private String thumbnail;

    @ApiModelProperty(value = "大图路径")
    private String big;

    @ApiModelProperty(value = "小图路径")
    private String small;

    @ApiModelProperty(value = "原图路径")
    private String original;

    @ApiModelProperty(value = "店铺分类id")
    private String storeCategoryPath;

    @ApiModelProperty(value = "评论数量")
    private Integer commentNum;

    @ApiModelProperty(value = "卖家id")
    private String storeId;

    @ApiModelProperty(value = "卖家名字")
    private String storeName;

    @ApiModelProperty(value = "运费模板id")
    private String templateId;

    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsAuthEnum
     */
    @ApiModelProperty(value = "审核状态")
    private Integer isAuth;

    @ApiModelProperty(value = "审核信息")
    private String authMessage;

    @ApiModelProperty(value = "下架原因")
    private String underMessage;

    @ApiModelProperty(value = "是否自营")
    private Boolean selfOperated;

    @ApiModelProperty(value = "商品移动端详情")
    private String mobileIntro;

    @ApiModelProperty(value = "商品视频")
    private String goodsVideo;

    @ApiModelProperty(value = "是否为推荐商品", required = true)
    private boolean recommend;

    @ApiModelProperty(value = "销售模式", required = true)
    private String salesModel;
    /**
     * @see com.mszlu.shop.model.buyer.eums.goods.GoodsTypeEnum
     */
    @ApiModelProperty(value = "商品类型", required = true)
    private Integer goodsType;

    private List<SpecValueVO> specList;

    //商品相册
    private List<String> goodsGalleryList;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.mszlu.shop.model.buyer.vo.goods;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 商品规格VO
 */
@Data
public class GoodsSkuSpecVO implements Serializable {


    @ApiModelProperty(value = "商品skuId")
    private String skuId;

    @ApiModelProperty(value = "商品sku所包含规格")
    private List<SpecValueVO> specValues;

    @ApiModelProperty(value = "库存")
    private Integer quantity;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.mszlu.shop.model.buyer.vo.goods;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 规格值
 */
@Data
public class SpecValueVO implements Serializable {

    private String specName;

    private String specValue;

    private Integer specType;
    /**
     * 规格图片
     */
    private List<SpecImages> specImage;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mszlu.shop.model.buyer.vo.goods;

import lombok.Data;

import java.io.Serializable;

@Data
public  class SpecImages implements Serializable {


        private String url;

        private String name;

        private String status;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

3.2 Controller

 @GetMapping(value = "/sku/{goodsId}/{skuId}")
    public Result<Map<String, Object>> getSku(@PathVariable("goodsId") String goodsId,
                                              @PathVariable("skuId") String skuId) {
            // 读取选中的列表
        return goodsBuyerService.getGoodsSkuDetail(goodsId, skuId);

    }
1
2
3
4
5
6
7

3.3 Service

package com.mszlu.shop.buyer.service.goods;

import com.mszlu.shop.buyer.service.GoodsSkuService;
import com.mszlu.shop.common.vo.Result;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class GoodsBuyerService {

    @DubboReference(version = "1.0.0")
    private GoodsSkuService goodsSkuService;

    public Result<Map<String, Object>> getGoodsSkuDetail(String goodsId, String skuId) {

        return goodsSkuService.getGoodsSkuDetail(goodsId,skuId);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3.4 Dubbo服务

package com.mszlu.shop.buyer.service.impl.goods;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mszlu.shop.buyer.mapper.GoodsSkuMapper;
import com.mszlu.shop.buyer.service.CategoryService;
import com.mszlu.shop.buyer.service.GoodsService;
import com.mszlu.shop.buyer.service.GoodsSkuService;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.pojo.es.EsGoodsIndex;
import com.mszlu.shop.model.buyer.pojo.goods.GoodsSku;
import com.mszlu.shop.model.buyer.vo.goods.*;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@DubboService(version = "1.0.0")
public class GoodsSkuServiceImpl implements GoodsSkuService {

    @Resource
    private GoodsSkuMapper goodsSkuMapper;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private CategoryService categoryService;

    @Override
    public Result<GoodsDetailVo> getGoodsSkuDetail(String goodsId, String skuId) {
        /**
         * 目的:GoodsDetailVo
         * 1. 根据skuid 查询 goodsSku表 ,转换为GoodsSkuVO对象
         * 2. categoryName 表中有 分类对应id列表,根据分类的id列表 去分类表中查询
         * 3. specs sku数据中 json的spec字符串,处理对应的字符串即可
         * 4. 促销信息 暂时不理会
         */
        //如果skuid 为null 或者 查询不出来数据,怎么办?
        //还有商品id,可以根据商品id 查询sku数据
        if (goodsId == null){
            return Result.fail(-999,"参数错误");
        }
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        GoodsSku goodsSku;
        if (skuId == null){
            //根据商品id查询
            LambdaQueryWrapper<GoodsSku> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(GoodsSku::getGoodsId,Long.parseLong(goodsId));
            List<GoodsSku> goodsSkus = goodsSkuMapper.selectList(queryWrapper);
            if (goodsSkus.size() <= 0){
                return Result.fail(-999,"参数错误");
            }
             goodsSku = goodsSkus.get(0);
        }else{
             goodsSku = goodsSkuMapper.selectById(Long.parseLong(skuId));
        }
        GoodsSkuVO goodsSkuVO = getGoodsSkuVO(goodsSku);
        goodsDetailVo.setData(goodsSkuVO);
        //分类列表
        String categoryPath = goodsSku.getCategoryPath();

        List<String> categoryNames = categoryService.getCategoryNameByIds(Arrays.asList(categoryPath.split(",")));
        goodsDetailVo.setCategoryName(categoryNames);

        //规则参数
        List<SpecValueVO> specList = goodsSkuVO.getSpecList();
        List<GoodsSkuSpecVO> goodsSkuSpecVOList = new ArrayList<>();
        GoodsSkuSpecVO goodsSkuSpecVO = new GoodsSkuSpecVO();
        goodsSkuSpecVO.setSkuId(goodsSku.getId().toString());
        goodsSkuSpecVO.setQuantity(goodsSku.getQuantity());
        goodsSkuSpecVO.setSpecValues(specList);
        goodsSkuSpecVOList.add(goodsSkuSpecVO);
        goodsDetailVo.setSpecs(goodsSkuSpecVOList);
        //促销信息
        goodsDetailVo.setPromotionMap(new HashMap<>());
        return Result.success(goodsDetailVo);
    }

    public GoodsSkuVO getGoodsSkuVO(GoodsSku goodsSku) {

        GoodsSkuVO goodsSkuVO = new GoodsSkuVO();
        BeanUtils.copyProperties(goodsSku, goodsSkuVO);
        //获取sku信息
        JSONObject jsonObject = JSON.parseObject(goodsSku.getSpecs());
        //用于接受sku信息
        List<SpecValueVO> specValueVOS = new ArrayList<>();
        //用于接受sku相册
        List<String> goodsGalleryList = new ArrayList<>();
        //循环提交的sku表单
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            SpecValueVO specValueVO = new SpecValueVO();
            if (entry.getKey().equals("images")) {
                specValueVO.setSpecName(entry.getKey());
                if (entry.getValue().toString().contains("url")) {
                    List<SpecImages> specImages = JSON.parseArray(entry.getValue().toString(), SpecImages.class);
                    specValueVO.setSpecImage(specImages);
                    goodsGalleryList = specImages.stream().map(SpecImages::getUrl).collect(Collectors.toList());
                }
            } else {
                specValueVO.setSpecName(entry.getKey());
                specValueVO.setSpecValue(entry.getValue().toString());
            }
            specValueVOS.add(specValueVO);
        }
        goodsSkuVO.setGoodsGalleryList(goodsGalleryList);
        goodsSkuVO.setSpecList(specValueVOS);
        return goodsSkuVO;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
 @Override
    public List<String> getCategoryNameByIds(List<String> idList) {
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Category::getId,idList);
        List<Category> categories = categoryMapper.selectList(queryWrapper);
        List<String> strings = categories.stream().map(Category::getName).collect(Collectors.toList());
        return strings;
    }
1
2
3
4
5
6
7
8