1. 限流

在访问接口的时候,时常会遇到这种场景,某一个接口不希望用户频繁的访问,比如登录的时候的滑块验证,因为滑块验证要拉取图片,会造成一定的带宽消耗,影响服务器的性能,这时候,就需要加一个限制,比如一分钟内同一个IP不能请求超过10次,这个功能我们把它称为限流。

其他的场景还有发短信,需要限流的一些接口

1.1 定义限流注解

package com.mszlu.shop.common.aop.limit;



import com.mszlu.shop.common.eunms.LimitTypeEnums;

import java.lang.annotation.*;

/**
 * 限流注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LimitPoint {
    /**
     * 资源的名字 无实际意义,但是可以用于排除异常
     *
     * @return String
     */
    String name() default "";

    /**
     * 资源的key
     * <p>
     * 如果下方 limitType 值为自定义,那么全局限流参数来自于此
     * 如果limitType 为ip,则限流条件 为 key+ip
     *
     * @return String
     */
    String key() default "";

    /**
     * Key的prefix redis前缀,可选
     *
     * @return String
     */
    String prefix() default "";

    /**
     * 给定的时间段 单位秒
     *
     * @return int
     */
    int period() default 60;

    /**
     * 最多的访问限制次数
     *
     * @return int
     */
    int limit() default 10;

    /**
     * 类型  ip限制 还是自定义key值限制
     * 建议使用ip,自定义key属于全局限制,ip则是某节点设置,通常情况使用IP
     *
     * @return LimitType
     */
    LimitTypeEnums limitType() default LimitTypeEnums.IP;
}
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
package com.mszlu.shop.common.eunms;


/**
 * redis 限流类型
 */

public enum LimitTypeEnums {
    /**
     * 自定义key(即全局限流)
     */
    CUSTOMER,
    /**
     * 根据请求者IP(IP限流)
     */
    IP
}

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

1.2 限流实现

 		<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
            <scope>compile</scope>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

定义业务异常:

package com.mszlu.shop.common.exception;

import com.mszlu.shop.common.eunms.BusinessCodeEnum;
import lombok.Data;

/**
 * 全局业务异常类
 */
@Data
public class BusinessException extends RuntimeException {

    public static String DEFAULT_MESSAGE = "网络错误,请稍后重试!";

    /**
     * 异常消息
     */
    private String msg = DEFAULT_MESSAGE;

    /**
     * 错误码
     */
    private BusinessCodeEnum resultCode;

    public BusinessException(String msg) {
        this.resultCode = BusinessCodeEnum.DEFAULT_SYS_ERROR;
        this.msg = msg;
    }

    public BusinessException() {
        super();
    }

    public BusinessException(BusinessCodeEnum resultCode) {
        this.resultCode = resultCode;
    }

    public BusinessException(BusinessCodeEnum resultCode, String message) {
        this.resultCode = resultCode;
        this.msg = message;
    }

}

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

AOP代码:

package com.mszlu.shop.common.aop.limit;

import com.google.common.collect.ImmutableList;
import com.mszlu.shop.common.eunms.BusinessCodeEnum;
import com.mszlu.shop.common.eunms.LimitTypeEnums;
import com.mszlu.shop.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;

/**
 * 流量拦截
 */
@Aspect
@Configuration
@Slf4j
public class LimitInterceptor {

    private RedisTemplate<String, Serializable> redisTemplate;
    @Autowired
    private DefaultRedisScript<Long> limitScript;

    @Before("@annotation(limitPointAnnotation)")
    public void interceptor(LimitPoint limitPointAnnotation) {
        LimitTypeEnums limitTypeEnums = limitPointAnnotation.limitType();
        String name = limitPointAnnotation.name();
        String key;
        int limitPeriod = limitPointAnnotation.period();
        int limitCount = limitPointAnnotation.limit();
        switch (limitTypeEnums) {
            case CUSTOMER:
                key = limitPointAnnotation.key();
                break;
            default:
                key = limitPointAnnotation.key() + getIpAddress();
        }
        //不可变、线程安全的列表集合
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitPointAnnotation.prefix(), key));
        try {
            Long count = redisTemplate.execute(limitScript, keys, limitCount, limitPeriod);
            log.info("限制请求:{}, 当前请求:{},缓存key:{}", limitCount, count, key);
            //如果缓存里没有值,或者他的值小于限制频率
            if (count == null || limitCount < count ) {
                throw new BusinessException(BusinessCodeEnum.LIMIT_ERROR);
            }
        }
        //如果从redis中执行都值判定为空,则这里跳过
        catch (NullPointerException e) {
            return;
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("服务器异常,请稍后再试");
        }
    }


    /**
     * 默认unknown常量值
     */
    private static final String UNKNOWN = "unknown";

    /**
     * 获取ip
     * @return ip
     */
    public String getIpAddress() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
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

因为限流是在Api层用的,所以在Buyer-api模块中导入redis的支持

##redis 配置
spring.redis.host=localhost
spring.redis.port=6379
1
2
3

1.2.1 流量限制的Redis lua脚本

package com.mszlu.shop.common.aop.limit;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

/**
 * redis 脚本
 */
@Configuration
public class LuaScript {

//    /**
//     * 库存扣减脚本
//     */
//    @Bean
//    public DefaultRedisScript<Boolean> quantityScript() {
//        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
//        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/quantity.lua")));
//        redisScript.setResultType(Boolean.class);
//        return redisScript;
//    }

    /**
     * 流量限制脚本
     * @return
     */
    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/limit.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
}

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

在resources下,新建script目录,创建limit.lua文件

local c
c = redis.call('get',KEYS[1])
-- 调用不超过最大值,则直接返回
if c and tonumber(c) > tonumber(ARGV[1]) then
    return c;
end
-- 执行计算器自加
c = redis.call('incr',KEYS[1])
if tonumber(c) == 1 then
-- 从第一次调用开始限流,设置对应键值的过期
    redis.call('expire',KEYS[1],ARGV[2])
end
return c;
1
2
3
4
5
6
7
8
9
10
11
12
13

1.2.2 异常处理

  @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Result doException(BusinessException e){
        e.printStackTrace();
        log.error("出异常了:{}",e.getMessage());
        return Result.fail(e.getResultCode().getCode(),e.getMessage());
    }
1
2
3
4
5
6
7

1.3 开启限流支持

package com.mszlu.shop.common.aop.limit;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(LimitConfig.class)
public @interface EnableLimit {
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.mszlu.shop.common.aop.limit;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan("com.mszlu.shop.common.aop.limit")
@Configuration
public class LimitConfig {
}

1
2
3
4
5
6
7
8
9
10

1.3 自定义RedisTemplate

package com.mszlu.shop.common.redis;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 自定义redis配置
 */

@Slf4j
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        //value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        //key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }


}

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

加入EnableLimit中

package com.mszlu.shop.common.aop.limit;

import com.mszlu.shop.common.redis.RedisConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({LimitConfig.class, RedisConfig.class})
public @interface EnableLimit {
}

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

1.4 使用

  @GetMapping("/{verificationEnums}")
    @ApiOperation(value = "获取校验接口")
    @LimitPoint(name = "获取校验接口",key = "getSliderImage")
1
2
3

修改前端:

加限流判断

在component下 verify下的index.vue:

 getImg () { // 获取验证图片
      getVerifyImg(this.type).then(res => {
        if(!res.success && res.code==1001){
          this.$Message.error('访问过于频繁');
          return;
        }
        this.data = res.result;
      });
    }
1
2
3
4
5
6
7
8
9

2. 获取结算页面购物车详情

结算前,需要获取选中的购物车中的商品

加入校验框架,可以对传入的参数做一些校验

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

2.1 接口说明

接口url:/buyer/trade/carts/checked?way=CART

请求方式:GET

请求参数:

参数名称参数类型说明
waystring结算方式:购物车 CART/立即购买:BUY_NOW/拼团购买:PINTUAN / 积分购买:POINT

返回数据:

{
    "success":true,
    "message":"success",
    "code":2000000000,
    "result":{
        "sn":null,
        "parentOrderSn":null,
        "cartList":[
            {
                "skuList":[
                    {
                        "sn":null,
                        "goodsSku":{
                            "updateTime":"1970-01-19T18:39:31.928+08:00",
                            "createTime":"2021-04-30T11:56:26.757+08:00",
                            "deleteFlag":false,
                            "id":1387979130431078400,
                            "goodsId":1376848829046849536,
                            "simpleSpecs":" 绿色",
                            "freightTemplateId":"1376425599173656576",
                            "isPromotion":null,
                            "promotionPrice":null,
                            "goodsName":"朵唯(DOOV) X11Pro 安卓智能手机  绿色",
                            "sn":"32424",
                            "brandId":"1349563815406886916",
                            "categoryPath":"1348576427264204941,1348576427264204942,1348576427264204943",
                            "goodsUnit":"个",
                            "sellingPoint":"6+128G 翡翠绿 6.53英寸水滴全面屏 4G全网通双卡双待 超薄侧面指纹 八核游戏手机 学生拍照备用老人商务手机",
                            "weight":2,
                            "marketEnable":1,
                            "intro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\" style=\"max-width:100%;\"/><br/></p>",
                            "price":4542,
                            "cost":4324,
                            "viewCount":null,
                            "buyCount":null,
                            "quantity":1000,
                            "grade":null,
                            "thumbnail":"https://static.mszlu.com/mall/47ee79c36c8c40c4ae9d61ead39f35ae.jpg?imageView2/1/w/400/h/400/q/75",
                            "big":null,
                            "small":null,
                            "original":null,
                            "storeCategoryPath":"1376372682651598848",
                            "commentNum":null,
                            "storeId":"1376369067769724928",
                            "storeName":"码神自营",
                            "templateId":null,
                            "isAuth":2,
                            "authMessage":null,
                            "underMessage":null,
                            "selfOperated":false,
                            "mobileIntro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\"/><br/></p>",
                            "goodsVideo":null,
                            "recommend":false,
                            "salesModel":"RETAIL",
                            "goodsType":1
                        },
                        "num":1,
                        "purchasePrice":4542,
                        "subTotal":4542,
                        "checked":true,
                        "isFreeFreight":false,
                        "point":null,
                        "invalid":false,
                        "errorMessage":"",
                        "isShip":true,
                        "cartType":"CART",
                        "storeId":"1376369067769724928",
                        "storeName":"码神自营",
                        "priceDetailDTO":{
                            "goodsPrice":4542,
                            "discountPrice":0,
                            "couponPrice":0,
                            "freightPrice":0,
                            "updatePrice":0,
                            "distributionCommission":0,
                            "platFormCommission":227.1,
                            "siteCouponPrice":0,
                            "siteCouponPoint":0,
                            "siteCouponCommission":0,
                            "flowPrice":4542,
                            "billPrice":4542
                        },
                        "priceDetailVO":null
                    }
                ],
                "sn":null,
                "checked":true,
                "weight":0,
                "goodsNum":1,
                "remark":"",
                "storeId":"1376369067769724928",
                "storeName":"码神自营",
                "priceDetailVO":null,
                "priceDetailDTO":{
                    "goodsPrice":4542,
                    "discountPrice":0,
                    "couponPrice":0,
                    "freightPrice":0,
                    "updatePrice":0,
                    "distributionCommission":0,
                    "platFormCommission":227.1,
                    "siteCouponPrice":0,
                    "siteCouponPoint":0,
                    "siteCouponCommission":0,
                    "flowPrice":4542,
                    "billPrice":4542
                },
                "couponList":[

                ]
            }
        ],
        "skuList":[
            {
                "sn":null,
                "goodsSku":{
                    "updateTime":"1970-01-19T18:39:31.928+08:00",
                    "createTime":"2021-04-30T11:56:26.757+08:00",
                    "deleteFlag":false,
                    "id":1387979130431078400,
                    "goodsId":1376848829046849536,
                    "simpleSpecs":" 绿色",
                    "freightTemplateId":"1376425599173656576",
                    "isPromotion":null,
                    "promotionPrice":null,
                    "goodsName":"朵唯(DOOV) X11Pro 安卓智能手机  绿色",
                    "sn":"32424",
                    "brandId":"1349563815406886916",
                    "categoryPath":"1348576427264204941,1348576427264204942,1348576427264204943",
                    "goodsUnit":"个",
                    "sellingPoint":"6+128G 翡翠绿 6.53英寸水滴全面屏 4G全网通双卡双待 超薄侧面指纹 八核游戏手机 学生拍照备用老人商务手机",
                    "weight":2,
                    "marketEnable":1,
                    "intro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\" style=\"max-width:100%;\"/><br/></p>",
                    "price":4542,
                    "cost":4324,
                    "viewCount":null,
                    "buyCount":null,
                    "quantity":1000,
                    "grade":null,
                    "thumbnail":"https://static.mszlu.com/mall/47ee79c36c8c40c4ae9d61ead39f35ae.jpg?imageView2/1/w/400/h/400/q/75",
                    "big":null,
                    "small":null,
                    "original":null,
                    "storeCategoryPath":"1376372682651598848",
                    "commentNum":null,
                    "storeId":"1376369067769724928",
                    "storeName":"码神自营",
                    "templateId":null,
                    "isAuth":2,
                    "authMessage":null,
                    "underMessage":null,
                    "selfOperated":false,
                    "mobileIntro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\"/><br/></p>",
                    "goodsVideo":null,
                    "recommend":false,
                    "salesModel":"RETAIL",
                    "goodsType":1
                },
                "num":1,
                "purchasePrice":4542,
                "subTotal":4542,
                "checked":true,
                "isFreeFreight":false,
                "point":null,
                "invalid":false,
                "errorMessage":"",
                "isShip":true,
                "cartType":"CART",
                "storeId":"1376369067769724928",
                "storeName":"码神自营",
                "priceDetailDTO":{
                    "goodsPrice":4542,
                    "discountPrice":0,
                    "couponPrice":0,
                    "freightPrice":0,
                    "updatePrice":0,
                    "distributionCommission":0,
                    "platFormCommission":227.1,
                    "siteCouponPrice":0,
                    "siteCouponPoint":0,
                    "siteCouponCommission":0,
                    "flowPrice":4542,
                    "billPrice":4542
                },
                "priceDetailVO":null
            }
        ],
        "priceDetailVO":null,
        "cartTypeEnum":"CART",
        "clientType":null,
        "memberName":null,
        "memberId":null,
        "distributionId":null,
        "priceDetailDTO":{
            "goodsPrice":4542,
            "discountPrice":0,
            "couponPrice":0,
            "freightPrice":0,
            "updatePrice":0,
            "distributionCommission":0,
            "platFormCommission":227.1,
            "siteCouponPrice":0,
            "siteCouponPoint":0,
            "siteCouponCommission":0,
            "flowPrice":4542,
            "billPrice":4542
        }
    }
}
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

返回的数据模型:

TradeVo

2.2 编码

2.2.1 Controller代码

 @ApiOperation(value = "获取结算页面购物车详情")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "way", value = "购物车购买:CART/立即购买:BUY_NOW/拼团购买:PINTUAN / 积分购买:POINT ", required = true, paramType = "query")
    })
    @GetMapping("/checked")
    public Result<TradeVo> cartChecked(String way) {
        //读取选中的列表
        return this.buyerCartService.getCheckedTradeVo(CartTypeEnum.valueOf(way));
    }
1
2
3
4
5
6
7
8
9

2.2.2 Service代码

 public Result<TradeVo> getCheckedTradeVo(CartTypeEnum cartType) {
        AuthUser currentUser = UserContext.getCurrentUser();

        return cartService.getCheckedTradeVo(cartType,currentUser.getId());
    }
1
2
3
4
5

2.2.3 Dubbo服务

/**
     * 结算页面购物车详情
     * @param cartType
     * @param userId
     * @return
     */
    Result<TradeVo> getCheckedTradeVo(CartTypeEnum cartType, String userId);
1
2
3
4
5
6
7
@Override
    public Result<TradeVo> getCheckedTradeVo(CartTypeEnum cartType, String userId) {
        //读取对应购物车的商品信息
        TradeVo tradeVo = createTradeVo(cartType, userId);
        //对购物车进行渲染
        //是否为单品渲染
        boolean isSingle = cartType.equals(CartTypeEnum.PINTUAN) || cartType.equals(CartTypeEnum.POINTS);
        if (isSingle){
            //根据不同的步骤 进行渲染
            tradeBuilder.renderCartBySteps(tradeVo, RenderStepStatement.checkedSingleRender);
        }else{
            tradeBuilder.renderCartBySteps(tradeVo, RenderStepStatement.checkedRender);
        }
        return Result.success(tradeVo);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2.2.4 定义渲染步骤

package com.mszlu.shop.buyer.service.impl.trade.domain.render;


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

/**
 * 价格渲染 步骤声明
 *
 */
public class RenderStepStatement {

    /**
     * 购物车购物车渲染
     * 校验商品 》 满优惠渲染  》  渲染优惠  》计算价格
     */
    public static RenderStepEnums[] cartRender = {
            RenderStepEnums.CHECK_DATA,
            RenderStepEnums.SKU_PROMOTION,
            RenderStepEnums.FULL_DISCOUNT,
            RenderStepEnums.CART_PRICE};

    /**
     * 结算页渲染
     * 过滤选择的商品 》 校验商品 》 满优惠渲染  》  渲染优惠  》
     * 优惠券渲染  》 计算运费  》  计算价格
     */
    public static RenderStepEnums[] checkedRender = {
            RenderStepEnums.CHECKED_FILTER,
            RenderStepEnums.CHECK_DATA,
            RenderStepEnums.SKU_PROMOTION,
            RenderStepEnums.FULL_DISCOUNT,
            RenderStepEnums.COUPON,
            RenderStepEnums.SKU_FREIGHT,
            RenderStepEnums.CART_PRICE,
    };


    /**
     * 单个商品优惠,不需要渲染满减优惠
     * 用于特殊场景:例如积分商品,拼团商品,虚拟商品等等
     */
    public static RenderStepEnums[] checkedSingleRender = {
            RenderStepEnums.CHECK_DATA,
            RenderStepEnums.SKU_PROMOTION,
            RenderStepEnums.SKU_FREIGHT,
            RenderStepEnums.CART_PRICE
    };

    /**
     * 交易创建前渲染
     * 渲染购物车 生成SN 》分销人员佣金渲染 》平台佣金渲染
     */
    public static RenderStepEnums[] singleTradeRender = {
            RenderStepEnums.CHECK_DATA,
            RenderStepEnums.SKU_PROMOTION,
            RenderStepEnums.SKU_FREIGHT,
            RenderStepEnums.CART_PRICE,
            RenderStepEnums.CART_SN,
            RenderStepEnums.DISTRIBUTION,
            RenderStepEnums.PLATFORM_COMMISSION
    };
    /**
     * 交易创建前渲染
     * 渲染购物车 生成SN 》分销人员佣金渲染 》平台佣金渲染
     */
    public static RenderStepEnums[] tradeRender = {
            RenderStepEnums.CHECKED_FILTER,
            RenderStepEnums.CHECK_DATA,
            RenderStepEnums.SKU_PROMOTION,
            RenderStepEnums.FULL_DISCOUNT,
            RenderStepEnums.COUPON,
            RenderStepEnums.SKU_FREIGHT,
            RenderStepEnums.CART_PRICE,
            RenderStepEnums.CART_SN,
            RenderStepEnums.DISTRIBUTION,
            RenderStepEnums.PLATFORM_COMMISSION
    };
}

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
package com.mszlu.shop.model.buyer.eums;

/**
 * 购物车渲染枚举
 */
public enum RenderStepEnums {

    /**
     * 购物车渲染枚举
     */
    CHECK_DATA("校验商品"),
    CHECKED_FILTER("选择商品过滤"),
    COUPON("优惠券价格渲染"),
    SKU_PROMOTION("商品促销计算"),
    FULL_DISCOUNT("满减计算"),
    SKU_FREIGHT("运费计算"),
    DISTRIBUTION("分配需要分配的促销金额"),
    PLATFORM_COMMISSION("平台佣金"),
    CART_PRICE("购物车金额计算"),
    CART_SN("交易编号创建");

    private String distribution;

    public String getDistribution() {
        return distribution;
    }

    RenderStepEnums(String distribution) {
        this.distribution = distribution;
    }
}

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

2.2.5 定义渲染步骤枚举

public interface CartRenderStep {

    //0-> 校验商品 1-》 满优惠渲染 2->渲染优惠 3->优惠券渲染 4->计算运费 5->计算价格 6->分销渲染 7->其他渲染

    void render(TradeVo tradeVo);


    /**
     * 渲染价格步骤
     *
     * @return 渲染枚举
     */
    RenderStepEnums step();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//每一个实现类 标识步骤 
@Override
    public RenderStepEnums step() {
        return RenderStepEnums.CART_PRICE;
    }
1
2
3
4
5

2.2.6 根据步骤渲染

/**
     * 根据渲染步骤,渲染购物车信息
     *
     * @param tradeVo     交易模型
     * @param defaultRender 渲染枚举
     */
    public void renderCartBySteps(TradeVo tradeVo, RenderStepEnums[] defaultRender) {
        for (RenderStepEnums step : defaultRender) {
            for (CartRenderStep render : cartRenderSteps) {
                try {
                    if (render.step().equals(step)) {
                        render.render(tradeVo);
                    }
                } catch (Exception e) {
                    log.error("购物车{}渲染异常:", render.getClass(), e);
                }
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

2.3 测试

image-20211207001140260

3. 获取会员收件地址分页列表

3.1 数据库

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

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

import java.io.Serializable;

/**
 * 会员地址
 */
@Data
@ApiModel(value = "会员地址")
public class MemberAddress extends BaseEntity implements Serializable {

    @ApiModelProperty(value = "会员ID", hidden = true)
    private String memberId;

    @ApiModelProperty(value = "收货人姓名")
    private String name;

    @ApiModelProperty(value = "手机号码")
    private String mobile;

    @ApiModelProperty(value = "地址名称, ','分割")
    private String consigneeAddressPath;

    @ApiModelProperty(value = "地址id,','分割 ")
    private String consigneeAddressIdPath;

    @ApiModelProperty(value = "详细地址")
    private String detail;

    @ApiModelProperty(value = "是否为默认收货地址")
    private Boolean isDefault;

    @ApiModelProperty(value = "地址别名")
    private String alias;

    @ApiModelProperty(value = "经度")
    private String lon;

    @ApiModelProperty(value = "纬度")
    private String lat;
}
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
CREATE TABLE `ms_mall`.`ms_member_address`  (
  `id` bigint(0) NOT NULL COMMENT 'ID',
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',
  `delete_flag` bit(1) NULL DEFAULT NULL COMMENT '删除标志 true/false 删除/未删除',
  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',
  `alias` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址别名',
  `consignee_address_id_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址ID',
  `consignee_address_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址名称',
  `detail` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '详细地址',
  `is_default` bit(1) NULL DEFAULT NULL COMMENT '是否为默认收货地址',
  `lat` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '纬度',
  `lon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经度',
  `member_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '会员ID',
  `mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收货人姓名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

3.2 接口说明

接口url:/buyer/memberAddress

请求方式:GET

请求参数:

参数名称参数类型说明

返回数据:

3.3 编码

3.3.1 Controller代码

package com.mszlu.shop.buyer.controller.members;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mszlu.shop.buyer.service.members.BuyerMemberAddressService;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.pojo.MemberAddress;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(tags = "买家端,会员地址接口")
@RequestMapping("memberAddress")
public class MemberAddressController {

    @Autowired
    private BuyerMemberAddressService buyerMemberAddressService;

    @ApiOperation(value = "获取会员收件地址分页列表")
    @GetMapping
    public Result<IPage<MemberAddress>> page(PageParams pageParams) {
        return buyerMemberAddressService.getAddressByMember(pageParams);
    }
}

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

3.3.2 Service代码

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

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mszlu.shop.buyer.handler.security.UserContext;
import com.mszlu.shop.buyer.service.MemberAddressService;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.pojo.MemberAddress;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

@Service
public class BuyerMemberAddressService {

    @DubboReference(version = "1.0")
    private MemberAddressService memberAddressService;

    public Result<IPage<MemberAddress>> getAddressByMember(PageParams pageParams) {
        AuthUser currentUser = UserContext.getCurrentUser();
        return memberAddressService.getAddressByMember(pageParams,currentUser.getId());
    }
}

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

3.3.3 Dubbo服务

package com.mszlu.shop.buyer.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.pojo.MemberAddress;

public interface MemberAddressService {
    /**
     * 获取会员地址
     * @param pageParams
     * @param userId
     * @return
     */
    Result<IPage<MemberAddress>> getAddressByMember(PageParams pageParams, String userId);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mszlu.shop.buyer.service.impl.members;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDto;
import com.mszlu.shop.buyer.mapper.MemberAddressMapper;
import com.mszlu.shop.buyer.service.MemberAddressService;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.params.PageParams;
import com.mszlu.shop.model.buyer.pojo.MemberAddress;
import org.apache.dubbo.config.annotation.DubboService;

import javax.annotation.Resource;

@DubboService(version = "1.0")
public class MemberAddressServiceImpl implements MemberAddressService {

    @Resource
    private MemberAddressMapper memberAddressMapper;

    @Override
    public Result<IPage<MemberAddress>> getAddressByMember(PageParams pageParams, String userId) {
        LambdaQueryWrapper<MemberAddress> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(MemberAddress::getMemberId,userId);
        Page<MemberAddress> memberAddressPage = memberAddressMapper.selectPage(new Page<>(pageParams.getPageNumber(), pageParams.getPageSize()), queryWrapper);
        return Result.success(memberAddressPage);
    }
}

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
package com.mszlu.shop.buyer.mapper;

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

public interface MemberAddressMapper extends BaseMapper<MemberAddress> {
}

1
2
3
4
5
6
7
8

3.4 测试

image-20211207004149867

4. 获取优惠券可用数量

此页面需要获取优惠券的可用数量,暂时设为0,后续做到优惠券在进行补充

4.1 接口说明

接口url:/buyer/trade/carts/coupon/num?way=CART

请求方式:GET

4.2 编码

@ApiOperation(value = "获取购物车可用优惠券数量")
    @GetMapping("/coupon/num")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "way", value = "购物车购买:CART/立即购买:BUY_NOW/拼团购买:PINTUAN / 积分购买:POINT ", required = true, paramType = "query")
    })
    public Result<Long> cartCouponNum(String way) {
        return this.buyerCartService.getCanUseCoupon(CartTypeEnum.valueOf(way));
    }
1
2
3
4
5
6
7
8
 public Result<Long> getCanUseCoupon(CartTypeEnum cartType) {
        return Result.success(0L);
    }
1
2
3