mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-23 05:00:25 +08:00
Merge branch 'develop'
This commit is contained in:
commit
ec158e84a2
Binary file not shown.
@ -0,0 +1,95 @@
|
||||
package com.youlai.mall.sms.pojo.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.mall.sms.pojo.enums.CouponCategoryEnum;
|
||||
import com.youlai.mall.sms.pojo.enums.DistributeTargetEnum;
|
||||
import com.youlai.mall.sms.pojo.enums.ProductLineEnum;
|
||||
import com.youlai.mall.sms.pojo.vo.TemplateRuleVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板实体类:基础属性 + 规则属性
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmsCouponTemplate {
|
||||
|
||||
/**
|
||||
* 主键自增ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 是否是可用状态
|
||||
*/
|
||||
private Integer available;
|
||||
|
||||
/**
|
||||
* 是否过期
|
||||
*/
|
||||
private Integer expired;
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠券logo
|
||||
*/
|
||||
private String logo;
|
||||
|
||||
/**
|
||||
* 优惠券描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 优惠券分类
|
||||
*/
|
||||
private CouponCategoryEnum category;
|
||||
|
||||
/**
|
||||
* 产品线
|
||||
*/
|
||||
private ProductLineEnum productLine;
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 优惠券模板编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 目标用户
|
||||
*/
|
||||
private DistributeTargetEnum target;
|
||||
|
||||
/**
|
||||
* 优惠券规则
|
||||
*/
|
||||
private TemplateRuleVO rule;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private String gmtCreatedBy;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
private String gmtModifiedBy;
|
||||
|
||||
}
|
@ -41,15 +41,14 @@ public class SmsSeckillSession implements Serializable {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
private Date gmtCreate;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
private Date gmtModified;
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<SmsSeckillSkuRelation> relations;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
package com.youlai.mall.sms.pojo.dto;
|
||||
|
||||
import com.youlai.mall.sms.pojo.vo.TemplateRuleVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:微服务之间调用的优惠券模板信息
|
||||
* @date 2021/6/27
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CouponTemplateDTO {
|
||||
|
||||
/**
|
||||
* 优惠券模板主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券模板名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 分类
|
||||
*/
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 产品线
|
||||
*/
|
||||
private String productLine;
|
||||
|
||||
/**
|
||||
* 优惠券模板编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 目标用户
|
||||
*/
|
||||
private Integer target;
|
||||
|
||||
/**
|
||||
* 优惠券规则
|
||||
*/
|
||||
private TemplateRuleVO rule;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.youlai.mall.sms.pojo.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券分类
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CouponCategoryEnum {
|
||||
|
||||
MANJIAN("满减券", "001"),
|
||||
ZHEKOU("折扣券", "002"),
|
||||
LIJIAN("立减券", "003"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 优惠券描述(分类)
|
||||
*/
|
||||
@JsonValue
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 优惠券分类编码
|
||||
*/
|
||||
@EnumValue
|
||||
private String code;
|
||||
|
||||
public static CouponCategoryEnum of(String code) {
|
||||
Objects.requireNonNull(code);
|
||||
return Stream.of(values()).filter(bean -> bean.code.equals(code))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException(code + " code not exist"));
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.youlai.mall.sms.pojo.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:优惠券分发目标枚举
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DistributeTargetEnum {
|
||||
|
||||
SINGLE("单用户",1),
|
||||
MULTI("多用户",2),
|
||||
;
|
||||
|
||||
/**
|
||||
* 分发目标描述
|
||||
*/
|
||||
@JsonValue
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 分发目标编码
|
||||
*/
|
||||
@EnumValue
|
||||
private Integer code;
|
||||
|
||||
public static DistributeTargetEnum of(Integer code) {
|
||||
Objects.requireNonNull(code);
|
||||
return Stream.of(values()).filter(bean -> bean.code.equals(code))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException(code + " code not exist"));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.youlai.mall.sms.pojo.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:有效期类型枚举
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PeriodTypeEnum {
|
||||
|
||||
REGULAR("固定的(固定日期)",1),
|
||||
SHIFT("变动的(以领取之日开始计算)",2),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 有效期描述
|
||||
*/
|
||||
@JsonValue
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 有效期编码
|
||||
*/
|
||||
@EnumValue
|
||||
private Integer code;
|
||||
|
||||
public static PeriodTypeEnum of(Integer code) {
|
||||
Objects.requireNonNull(code);
|
||||
return Stream.of(values()).filter(bean -> bean.code.equals(code))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException(code + " code not exist"));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.youlai.mall.sms.pojo.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 产品线枚举
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ProductLineEnum {
|
||||
|
||||
YOULAI("有来", 1),
|
||||
WUHUI("无回", 2),
|
||||
;
|
||||
|
||||
/**
|
||||
* 产品线描述
|
||||
*/
|
||||
@JsonValue
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 产品线编码
|
||||
*/
|
||||
@EnumValue
|
||||
private Integer code;
|
||||
|
||||
public static ProductLineEnum of(String code) {
|
||||
Objects.requireNonNull(code);
|
||||
return Stream.of(values()).filter(bean -> bean.code.equals(code))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException(code + " code not exist"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.youlai.mall.sms.pojo.form;
|
||||
|
||||
import com.youlai.mall.sms.pojo.vo.TemplateRuleVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板创建请求对象
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CouponTemplateForm {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券模板名称
|
||||
*/
|
||||
@NotBlank(message = "请填写优惠券模板名称")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠券 logo
|
||||
*/
|
||||
private String logo;
|
||||
|
||||
/**
|
||||
* 优惠券描述
|
||||
*/
|
||||
@NotBlank(message = "请填写优惠券描述")
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 优惠券分类
|
||||
*/
|
||||
@NotBlank(message = "请选择优惠券分类")
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 优惠券产品线
|
||||
*/
|
||||
@NotBlank(message = "请选择优惠券产品线")
|
||||
private Integer productLine;
|
||||
|
||||
/**
|
||||
* 优惠券总数量
|
||||
*/
|
||||
@NotNull(message = "请输入优惠券总数量")
|
||||
@Min(value = 1, message = "优惠券数量必须大于1")
|
||||
@Max(value = Integer.MAX_VALUE, message = "优惠券数量不能大于 " + Integer.MAX_VALUE)
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 目标用户
|
||||
*/
|
||||
private Integer target;
|
||||
|
||||
/**
|
||||
* 优惠券规则
|
||||
*/
|
||||
private TemplateRuleVO rule;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.youlai.mall.sms.pojo.vo;
|
||||
|
||||
import com.youlai.common.base.BaseVO;
|
||||
import com.youlai.mall.sms.pojo.enums.CouponCategoryEnum;
|
||||
import com.youlai.mall.sms.pojo.enums.DistributeTargetEnum;
|
||||
import com.youlai.mall.sms.pojo.enums.ProductLineEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板实体类
|
||||
* @date 2021/7/3
|
||||
*/
|
||||
@ApiModel(value = "优惠券模板模型",description = "优惠券模板模型")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CouponTemplateVO extends BaseVO {
|
||||
|
||||
/**
|
||||
* 主键自增ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 是否是可用状态
|
||||
*/
|
||||
private Integer available;
|
||||
|
||||
/**
|
||||
* 是否过期
|
||||
*/
|
||||
private Integer expired;
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠券logo
|
||||
*/
|
||||
private String logo;
|
||||
|
||||
/**
|
||||
* 优惠券描述
|
||||
*/
|
||||
private String intro;
|
||||
|
||||
/**
|
||||
* 优惠券分类
|
||||
*/
|
||||
private CouponCategoryEnum category;
|
||||
|
||||
/**
|
||||
* 产品线
|
||||
*/
|
||||
private ProductLineEnum productLine;
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 优惠券模板编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 目标用户
|
||||
*/
|
||||
private DistributeTargetEnum target;
|
||||
|
||||
/**
|
||||
* 优惠券规则
|
||||
*/
|
||||
private TemplateRuleVO rule;
|
||||
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.youlai.mall.sms.pojo.vo;
|
||||
|
||||
import com.youlai.mall.sms.pojo.enums.PeriodTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券规则对象定义
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TemplateRuleVO {
|
||||
|
||||
/**
|
||||
* 优惠券过期规则
|
||||
*/
|
||||
private Expiration expiration;
|
||||
|
||||
/**
|
||||
* 折扣
|
||||
*/
|
||||
private Discount discount;
|
||||
|
||||
/**
|
||||
* 限领张数限制
|
||||
*/
|
||||
private Integer limitation;
|
||||
|
||||
/**
|
||||
* 使用范围:地域 + 商品类型
|
||||
*/
|
||||
private Usage usage;
|
||||
|
||||
/**
|
||||
* 权重(可以和哪些优惠券叠加使用,同一类的优惠券一定不能叠加)
|
||||
*/
|
||||
private String weight;
|
||||
|
||||
/**
|
||||
* 有效期规则定义
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Expiration {
|
||||
|
||||
/**
|
||||
* 有效期规则,对应 PeriodType 的 code 字段
|
||||
*/
|
||||
private Integer period;
|
||||
|
||||
/**
|
||||
* 有效间隔:只对变动性有效期有效
|
||||
*/
|
||||
private Integer gap;
|
||||
|
||||
/**
|
||||
* 优惠券模板的失效日期,两类规则都有效
|
||||
*/
|
||||
private Long deadline;
|
||||
|
||||
boolean validate() {
|
||||
// 最简化校验
|
||||
return null != PeriodTypeEnum.of(period) && gap > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 折扣的规则
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Discount {
|
||||
|
||||
/**
|
||||
* 额度:满减(20),折扣(85),立减(10)
|
||||
*/
|
||||
private Integer quota;
|
||||
|
||||
/**
|
||||
* 基准:需要满多少才可用
|
||||
*/
|
||||
private Integer base;
|
||||
|
||||
boolean validate() {
|
||||
// 最简化校验
|
||||
return quota > 0 && base > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠券使用范围
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Usage {
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 商品类型,list[文娱,生鲜,家居,全品类]
|
||||
*/
|
||||
private String goodsType;
|
||||
|
||||
boolean validate() {
|
||||
return StringUtils.isNotEmpty(province)
|
||||
&& StringUtils.isNotEmpty(province)
|
||||
&& StringUtils.isNotEmpty(province);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 优惠营销系统
|
||||
@ -16,6 +17,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@EnableScheduling
|
||||
public class SmsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.youlai.mall.sms.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 自定义异步线程池
|
||||
* @date 2021/6/27
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
public class AsyncPoolConfig implements AsyncConfigurer {
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(20);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("youlai_async_");
|
||||
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
|
||||
return new AsyncExceptionHandler();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
|
||||
// 1、打印异常堆栈
|
||||
throwable.printStackTrace();
|
||||
// 2、日志记录错误信息
|
||||
log.error("AsyncError:{}, Method:{}, Param:{}", throwable.getMessage(), method.getName(), Arrays.asList(objects));
|
||||
// 3、TODO 发生异常后通知管理人员(邮件,短信)进一步处理
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.youlai.mall.sms.controller.admin;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.sms.pojo.form.CouponTemplateForm;
|
||||
import com.youlai.mall.sms.pojo.vo.CouponTemplateVO;
|
||||
import com.youlai.mall.sms.service.ISmsCouponTemplateService;
|
||||
import com.youlai.mall.sms.service.ITemplateBaseService;
|
||||
import io.swagger.annotations.Api;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:优惠券模板API接口
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Api(tags = "优惠券模板API接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/coupon_template")
|
||||
public class CouponTemplateController {
|
||||
|
||||
@Autowired
|
||||
private ITemplateBaseService templateBaseService;
|
||||
|
||||
@Autowired
|
||||
private ISmsCouponTemplateService couponTemplateService;
|
||||
|
||||
/**
|
||||
* 创建优惠券模板
|
||||
*
|
||||
* @param form 提交表单
|
||||
* @return result
|
||||
*/
|
||||
@PostMapping("/template")
|
||||
public Result<Object> createTemplate(@RequestBody CouponTemplateForm form) {
|
||||
log.info("Create Coupon Template , form:{}", form);
|
||||
couponTemplateService.createTemplate(form);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改优惠券模板
|
||||
* @param form 提交表单
|
||||
* @return result
|
||||
*/
|
||||
@PutMapping("/template")
|
||||
public Result<Object> updateTemplate(@RequestBody CouponTemplateForm form){
|
||||
log.info("Update Coupon Template,form:{}",form);
|
||||
couponTemplateService.updateTemplate(form);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询优惠券模板详情
|
||||
* @param id 优惠券模板ID
|
||||
* @return result
|
||||
*/
|
||||
@GetMapping("/template/info")
|
||||
public Result<CouponTemplateVO> getTemplateInfo(@RequestParam("id") Long id){
|
||||
log.info("Query Coupon Template Info , id:{}",id);
|
||||
CouponTemplateVO templateVO = templateBaseService.queryTemplateInfo(id);
|
||||
return Result.success(templateVO);
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface SmsCouponDao extends BaseMapper<SmsCoupon> {
|
||||
public interface SmsCouponMapper extends BaseMapper<SmsCoupon> {
|
||||
int deleteByPrimaryKey(Long id);
|
||||
|
||||
int insertSelective(SmsCoupon record);
|
@ -5,7 +5,7 @@ import com.youlai.mall.sms.pojo.domain.SmsCouponRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmsCouponRecordDao extends BaseMapper<SmsCouponRecord> {
|
||||
public interface SmsCouponRecordMapper extends BaseMapper<SmsCouponRecord> {
|
||||
int deleteByPrimaryKey(Long id);
|
||||
|
||||
int insertSelective(SmsCouponRecord record);
|
@ -0,0 +1,14 @@
|
||||
package com.youlai.mall.sms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:优惠券模板Dao接口定义
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Mapper
|
||||
public interface SmsCouponTemplateMapper extends BaseMapper<SmsCouponTemplate> {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.youlai.mall.sms.service;
|
||||
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:异步服务接口
|
||||
* @date 2021/6/27
|
||||
*/
|
||||
public interface IAsyncService {
|
||||
|
||||
/**
|
||||
* 通过优惠券模板异步的创建优惠券码
|
||||
* @param template {@link SmsCouponTemplate} 优惠券模板实体
|
||||
*/
|
||||
void asyncConstructCouponByTemplate(SmsCouponTemplate template);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.youlai.mall.sms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.pojo.form.CouponTemplateForm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板业务接口
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
public interface ISmsCouponTemplateService extends IService<SmsCouponTemplate> {
|
||||
|
||||
/**
|
||||
* 创建优惠券模板
|
||||
*
|
||||
* @param form {@link CouponTemplateForm} 模板信息请求对象
|
||||
* @return {@link SmsCouponTemplate} 优惠券模板实体类
|
||||
*/
|
||||
SmsCouponTemplate createTemplate(CouponTemplateForm form);
|
||||
|
||||
|
||||
/**
|
||||
* 修改优惠券模板
|
||||
* @param form {@link CouponTemplateForm} 模板信息请求对象
|
||||
* @return {@link SmsCouponTemplate} 优惠券模板实体类
|
||||
*/
|
||||
SmsCouponTemplate updateTemplate(CouponTemplateForm form);
|
||||
|
||||
/**
|
||||
* 查询所有可用优惠券模板列表
|
||||
*
|
||||
* @param available 是否可用
|
||||
* @param expired 是否过期
|
||||
* @return 优惠券模板
|
||||
*/
|
||||
List<SmsCouponTemplate> findAllUsableTemplate(boolean available, boolean expired);
|
||||
|
||||
/**
|
||||
* 查询未过期的优惠券模板列表
|
||||
*
|
||||
* @param expired 是否过期
|
||||
* @return 优惠券模板列表
|
||||
*/
|
||||
List<SmsCouponTemplate> findAllNotExpiredTemplate(Integer expired);
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.youlai.mall.sms.service;
|
||||
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.pojo.vo.CouponTemplateVO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板基础业务接口
|
||||
* @date 2021/7/3
|
||||
*/
|
||||
public interface ITemplateBaseService {
|
||||
|
||||
/**
|
||||
* 根据优惠券模板id 获取优惠券模板信息
|
||||
* @param id 优惠券模板ID
|
||||
* @return {@link SmsCouponTemplate} 优惠券模板实体类
|
||||
*/
|
||||
CouponTemplateVO queryTemplateInfo(Long id);
|
||||
|
||||
/**
|
||||
* 查询所有可用优惠券模板列表
|
||||
* @return 优惠券模板列表
|
||||
*/
|
||||
List<SmsCouponTemplate> findAllUsableTemplate();
|
||||
|
||||
/**
|
||||
* 根据优惠券模板ID集合查询可用模板列表
|
||||
* @param ids 优惠券模板ID集合
|
||||
* @return 优惠券模板列表
|
||||
*/
|
||||
Map<Long, SmsCouponTemplate> findAllTemplateByIds(Collection<Long> ids);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.youlai.mall.sms.service.impl;
|
||||
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.youlai.common.constant.RedisConstants;
|
||||
import com.youlai.common.redis.utils.RedisUtils;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponTemplateMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.service.IAsyncService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:异步服务接口实现
|
||||
* @date 2021/6/27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AsyncServiceImpl implements IAsyncService {
|
||||
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
private final SmsCouponTemplateMapper couponTemplateMapper;
|
||||
|
||||
@Async("getAsyncExecutor")
|
||||
@Override
|
||||
public void asyncConstructCouponByTemplate(SmsCouponTemplate template) {
|
||||
Stopwatch watch = Stopwatch.createStarted();
|
||||
Set<String> couponCodes = buildCouponCode(template);
|
||||
String couponCodeKey = String.format("%s%s", RedisConstants.SMS_COUPON_TEMPLATE_CODE_KEY, String.valueOf(template.getId()));
|
||||
redisUtils.lSet(couponCodeKey, couponCodes);
|
||||
log.info("Push CouponCode To Redis, Coupon Template " +
|
||||
"ID:{}, Name:{}, TOTAL:{}", template.getId(), template.getName(), template.getTotal());
|
||||
template.setAvailable(1);
|
||||
couponTemplateMapper.updateById(template);
|
||||
|
||||
watch.stop();
|
||||
log.info("Construct CouponCode By Coupon Template Use:{}ms", watch.elapsed(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造优惠券码
|
||||
* 优惠券码对应于每一张优惠券,一共18位
|
||||
* 前四位:产品线+类型
|
||||
* 中间六位:日期随机
|
||||
* 后八位:0 ~ 9 随机数构成
|
||||
*
|
||||
* @param template {@link SmsCouponTemplate} 优惠券模板实体类
|
||||
* @return Set<String> 与 template.total 总优惠券数量
|
||||
*/
|
||||
private Set<String> buildCouponCode(SmsCouponTemplate template) {
|
||||
|
||||
Stopwatch watch = Stopwatch.createStarted();
|
||||
Set<String> result = new HashSet<>(template.getTotal());
|
||||
String prefix4 = template.getProductLine().getCode() + template.getCategory().getCode();
|
||||
|
||||
String date = FastDateFormat.getInstance("yyMMdd").format(new Date());
|
||||
for (Integer i = 0; i < template.getTotal(); i++) {
|
||||
result.add(buildCouponCodeSuffix14(date));
|
||||
}
|
||||
while (result.size() < template.getTotal()) {
|
||||
result.add(prefix4 + buildCouponCodeSuffix14(date));
|
||||
}
|
||||
|
||||
// 断言
|
||||
assert result.size() == template.getTotal();
|
||||
watch.stop();
|
||||
log.info("Build Coupon Code use:{}ms", watch.elapsed(TimeUnit.MILLISECONDS));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造优惠券码后十四位
|
||||
*
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
private String buildCouponCodeSuffix14(String date) {
|
||||
char[] bases = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
|
||||
// 中间六位
|
||||
List<Character> chars = date.chars().mapToObj(obj -> (char) obj).collect(Collectors.toList());
|
||||
Collections.shuffle(chars);
|
||||
String mid6 = chars.stream().map(Objects::toString).collect(Collectors.joining());
|
||||
|
||||
// 后八位
|
||||
String suffix8 = RandomStringUtils.random(1, bases) + RandomStringUtils.randomNumeric(7);
|
||||
return mid6 + suffix8;
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponRecordDao;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponRecordMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCoupon;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponRecord;
|
||||
import com.youlai.mall.sms.pojo.enums.CouponStateEnum;
|
||||
@ -30,7 +30,7 @@ import static com.youlai.mall.sms.pojo.constant.AppConstants.COUPON_LOCK;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordDao, SmsCouponRecord> implements ICouponRecordService {
|
||||
public class CouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordMapper, SmsCouponRecord> implements ICouponRecordService {
|
||||
|
||||
@Autowired
|
||||
private ISmsCouponService couponService;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.youlai.mall.sms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponRecordDao;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponRecordMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponRecord;
|
||||
import com.youlai.mall.sms.service.ISmsCouponRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -15,5 +15,5 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SmsCouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordDao, SmsCouponRecord> implements ISmsCouponRecordService {
|
||||
public class SmsCouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordMapper, SmsCouponRecord> implements ISmsCouponRecordService {
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.base.BasePageQuery;
|
||||
import com.youlai.common.web.util.BeanMapperUtils;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponDao;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCoupon;
|
||||
import com.youlai.mall.sms.pojo.form.CouponForm;
|
||||
import com.youlai.mall.sms.pojo.vo.SmsCouponVO;
|
||||
@ -22,7 +22,7 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SmsCouponServiceImpl extends ServiceImpl<SmsCouponDao, SmsCoupon> implements ISmsCouponService {
|
||||
public class SmsCouponServiceImpl extends ServiceImpl<SmsCouponMapper, SmsCoupon> implements ISmsCouponService {
|
||||
@Override
|
||||
public SmsCouponVO detail(String couponId) {
|
||||
log.info("根据优惠券ID获取优惠券详情,couponId={}", couponId);
|
||||
|
@ -0,0 +1,95 @@
|
||||
package com.youlai.mall.sms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponTemplateMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.pojo.enums.CouponCategoryEnum;
|
||||
import com.youlai.mall.sms.pojo.enums.DistributeTargetEnum;
|
||||
import com.youlai.mall.sms.pojo.form.CouponTemplateForm;
|
||||
import com.youlai.mall.sms.service.IAsyncService;
|
||||
import com.youlai.mall.sms.service.ISmsCouponTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 优惠券模板业务实现类
|
||||
* @date 2021/6/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SmsCouponTemplateServiceImpl extends ServiceImpl<SmsCouponTemplateMapper, SmsCouponTemplate>
|
||||
implements ISmsCouponTemplateService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private IAsyncService asyncService;
|
||||
|
||||
|
||||
@Override
|
||||
public SmsCouponTemplate createTemplate(CouponTemplateForm form) {
|
||||
// 1、form 表单参数校验
|
||||
// 2、不允许出现同名的模板
|
||||
if (null == findByCouponTemplateName(form.getName())) {
|
||||
throw new BizException("Coupon Template Name Exist");
|
||||
}
|
||||
|
||||
// 构造 CouponTemplate 并保存到数据库
|
||||
SmsCouponTemplate template = formToTemplate(form);
|
||||
this.save(template);
|
||||
|
||||
// 根据优惠券模板异步生成优惠券码
|
||||
asyncService.asyncConstructCouponByTemplate(template);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsCouponTemplate updateTemplate(CouponTemplateForm form) {
|
||||
// TODO 开发中。。。
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsCouponTemplate> findAllUsableTemplate(boolean available, boolean expired) {
|
||||
QueryWrapper<SmsCouponTemplate> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("available", available)
|
||||
.eq("expired", expired);
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsCouponTemplate> findAllNotExpiredTemplate(Integer expired) {
|
||||
QueryWrapper<SmsCouponTemplate> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("expired", expired);
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板名称查询实体类
|
||||
*
|
||||
* @param name 模板名称
|
||||
* @return 模板实体类
|
||||
*/
|
||||
private SmsCouponTemplate findByCouponTemplateName(String name) {
|
||||
QueryWrapper<SmsCouponTemplate> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("name", name);
|
||||
return getOne(queryWrapper);
|
||||
}
|
||||
|
||||
private SmsCouponTemplate formToTemplate(CouponTemplateForm form) {
|
||||
SmsCouponTemplate template = new SmsCouponTemplate();
|
||||
template.setName(form.getName());
|
||||
template.setLogo(form.getLogo());
|
||||
template.setCategory(CouponCategoryEnum.of(form.getCategory()));
|
||||
template.setTotal(form.getTotal());
|
||||
template.setTarget(DistributeTargetEnum.of(form.getTarget()));
|
||||
template.setRule(form.getRule());
|
||||
template.setCode("");
|
||||
return template;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.youlai.mall.sms.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.BeanMapperUtils;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.pojo.vo.CouponTemplateVO;
|
||||
import com.youlai.mall.sms.service.ISmsCouponTemplateService;
|
||||
import com.youlai.mall.sms.service.ITemplateBaseService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:优惠券模板基础业务实现类
|
||||
* @date 2021/7/3
|
||||
*/
|
||||
@Service
|
||||
public class TemplateBaseServiceImpl implements ITemplateBaseService {
|
||||
|
||||
@Autowired
|
||||
private ISmsCouponTemplateService couponTemplateService;
|
||||
|
||||
@Override
|
||||
public CouponTemplateVO queryTemplateInfo(Long id) {
|
||||
SmsCouponTemplate template = couponTemplateService.getById(id);
|
||||
if (null == template) {
|
||||
throw new BizException("Template Is Not Exist: " + id);
|
||||
}
|
||||
return BeanMapperUtils.map(template,CouponTemplateVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsCouponTemplate> findAllUsableTemplate() {
|
||||
return couponTemplateService.findAllUsableTemplate(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, SmsCouponTemplate> findAllTemplateByIds(Collection<Long> ids) {
|
||||
List<SmsCouponTemplate> templates = couponTemplateService.listByIds(ids);
|
||||
if (CollUtil.isEmpty(templates)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
return templates.stream()
|
||||
.collect(Collectors.toMap(SmsCouponTemplate::getId, Function.identity()));
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.youlai.mall.sms.shedule;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponTemplate;
|
||||
import com.youlai.mall.sms.pojo.vo.TemplateRuleVO;
|
||||
import com.youlai.mall.sms.service.ISmsCouponTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc:定时清理已过期的优惠券模板
|
||||
* @date 2021/7/3
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ScheduleTask {
|
||||
|
||||
@Autowired
|
||||
private ISmsCouponTemplateService couponTemplateService;
|
||||
|
||||
/**
|
||||
* 下线已过期的优惠券模板
|
||||
*/
|
||||
@Scheduled(fixedRate = 60 * 60 * 1000)
|
||||
public void offlineCouponTemplate() {
|
||||
log.info("Start To Expired CouponTemplate.");
|
||||
// 查询未过期的优惠券模板
|
||||
List<SmsCouponTemplate> templates = couponTemplateService.findAllNotExpiredTemplate(1);
|
||||
if (CollUtil.isEmpty(templates)) {
|
||||
log.info("Done To Expired CouponTemplate.");
|
||||
return;
|
||||
}
|
||||
|
||||
Date nowTime = new Date();
|
||||
List<SmsCouponTemplate> expiredTemplates = new ArrayList<>(templates.size());
|
||||
for (SmsCouponTemplate template : templates) {
|
||||
TemplateRuleVO rule = template.getRule();
|
||||
if (rule.getExpiration().getDeadline() < nowTime.getTime()) {
|
||||
template.setExpired(0);
|
||||
expiredTemplates.add(template);
|
||||
}
|
||||
}
|
||||
couponTemplateService.updateBatchById(expiredTemplates);
|
||||
log.info("Update Expired CouponTemplate Num:{}", expiredTemplates.size());
|
||||
log.info("Done To Expired CouponTemplate.");
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.youlai.mall.sms;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: 营销系统测试
|
||||
* @date 2021/7/3
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class SmsApplicationTest {
|
||||
|
||||
@Test
|
||||
public void contestLoad(){
|
||||
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ public class SysPermissionServiceImpl extends ServiceImpl<SysPermissionMapper, S
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
|
||||
@Override
|
||||
public List<SysPermission> listPermRoles() {
|
||||
return this.baseMapper.listPermRoles();
|
||||
@ -50,6 +51,7 @@ public class SysPermissionServiceImpl extends ServiceImpl<SysPermissionMapper, S
|
||||
urlPermRoles.put(perm, roles);
|
||||
});
|
||||
redisTemplate.opsForHash().putAll(GlobalConstants.URL_PERM_ROLES_KEY, urlPermRoles);
|
||||
redisTemplate.convertAndSend("cleanRoleLocalCache","true");
|
||||
}
|
||||
// 初始化URL【按钮->角色(集合)】规则
|
||||
List<SysPermission> btnPermList = permissions.stream()
|
||||
|
@ -11,26 +11,3 @@ spring:
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
sentinel:
|
||||
enabled: true
|
||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
||||
transport:
|
||||
client-ip: localhost
|
||||
dashboard: localhost:8080
|
||||
datasource:
|
||||
# 限流规则,flow为key,随便定义
|
||||
flow:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-flow-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: flow
|
||||
# 降级规则
|
||||
degrade:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-degrade-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: degrade
|
||||
|
@ -61,7 +61,7 @@ public class OAuthController {
|
||||
* 方式一:client_id、client_secret放在请求路径中(注:当前版本已废弃)
|
||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||
*/
|
||||
String clientId = JwtUtils.getAuthClientId();
|
||||
String clientId = JwtUtils.getOAuthClientId();
|
||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||
switch (client) {
|
||||
case TEST: // knife4j接口测试文档使用 client_id/client_secret : client/123456
|
||||
|
@ -124,11 +124,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
* <p>
|
||||
*
|
||||
* 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式
|
||||
* 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
|
@ -34,7 +34,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
String clientId = JwtUtils.getAuthClientId();
|
||||
String clientId = JwtUtils.getOAuthClientId();
|
||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||
|
||||
Result result;
|
||||
|
@ -9,38 +9,8 @@ spring:
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
config:
|
||||
# docker启动nacos-server需要配置
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
group: DEFAULT_GROUP
|
||||
sentinel:
|
||||
enabled: true
|
||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
||||
transport:
|
||||
client-ip: localhost
|
||||
dashboard: localhost:8080
|
||||
datasource:
|
||||
# 降级规则
|
||||
degrade:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-degrade-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: degrade
|
||||
|
||||
# 开启feign对sentinel的支持
|
||||
feign:
|
||||
sentinel:
|
||||
enabled: true
|
||||
|
||||
# jwt 配置
|
||||
jwt:
|
||||
config:
|
||||
enabled: true
|
||||
key-location: jwt.jks
|
||||
key-alias: jwt
|
||||
key-pass: 123456
|
||||
iss: youlai.tech
|
||||
sub: all
|
||||
access-exp-days: 30
|
@ -2,7 +2,6 @@ package com.youlai.common.constant;
|
||||
|
||||
public interface AuthConstants {
|
||||
|
||||
|
||||
/**
|
||||
* 认证请求头key
|
||||
*/
|
||||
|
@ -0,0 +1,14 @@
|
||||
package com.youlai.common.constant;
|
||||
|
||||
/**
|
||||
* @author xinyi
|
||||
* @desc: MQ消息常量
|
||||
* @date 2021/6/27
|
||||
*/
|
||||
public interface MQConstants {
|
||||
|
||||
/**
|
||||
* SMS服务优惠券消息Topic
|
||||
*/
|
||||
String SMS_COUPON_TOPIC = "sms_coupon_topic";
|
||||
}
|
@ -4,4 +4,26 @@ public interface RedisConstants {
|
||||
|
||||
String BUSINESS_NO_PREFIX = "business_no:";
|
||||
|
||||
/**
|
||||
* 优惠券码KEY前缀
|
||||
*/
|
||||
String SMS_COUPON_TEMPLATE_CODE_KEY = "sms_coupon_template_code_";
|
||||
|
||||
/**
|
||||
* 用户当前所有可用优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_USABLE_KEY = "sms_user_coupon_usable_";
|
||||
|
||||
/**
|
||||
* 用户当前所有已使用优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_USED_KEY = "sms_user_coupon_used_";
|
||||
|
||||
/**
|
||||
* 用户当前所有已过期优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_EXPIRED_KEY = "sms_user_coupon_expired_";
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
@Configuration
|
||||
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
|
||||
|
@ -10,26 +10,40 @@ import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import sun.misc.BASE64Decoder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*
|
||||
* @author xianrui
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static JSONObject getJwtPayload() {
|
||||
String jwtPayload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(jwtPayload);
|
||||
String payload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(URLDecoder.decode(payload,"UTF-8"));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JWT获取用户ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
Long id = getJwtPayload().getLong(AuthConstants.USER_ID_KEY);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析JWT获取获取用户名
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getUsername() {
|
||||
String username = getJwtPayload().getStr(AuthConstants.USER_NAME_KEY);
|
||||
return username;
|
||||
@ -37,7 +51,7 @@ public class JwtUtils {
|
||||
|
||||
/**
|
||||
* 获取登录认证的客户端ID
|
||||
* <p>
|
||||
*
|
||||
* 兼容两种方式获取Oauth2客户端信息(client_id、client_secret)
|
||||
* 方式一:client_id、client_secret放在请求路径中
|
||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||
@ -45,7 +59,7 @@ public class JwtUtils {
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String getAuthClientId() {
|
||||
public static String getOAuthClientId() {
|
||||
String clientId;
|
||||
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
@ -66,15 +80,17 @@ public class JwtUtils {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT获取用户角色列表
|
||||
*
|
||||
* @return 角色列表
|
||||
*/
|
||||
public static List<String> getRoles() {
|
||||
List<String> roles = null;
|
||||
JSONObject payload = getJwtPayload();
|
||||
if (payload != null && payload.size() > 0) {
|
||||
List<String> list = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
||||
List<String> roles = list.stream().collect(Collectors.toList());
|
||||
return roles;
|
||||
if (payload != null && payload.containsKey(AuthConstants.JWT_AUTHORITIES_KEY)) {
|
||||
roles = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
||||
}
|
||||
return null;
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.youlai.gateway.component;
|
||||
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class RedisChannelListener implements MessageListener {
|
||||
|
||||
@Autowired
|
||||
private UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||
|
||||
@Override
|
||||
public void onMessage(Message message, byte[] bytes) {
|
||||
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
|
||||
String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
|
||||
urlPermRolesLocalCache.remove(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.youlai.gateway.component;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author DaniR
|
||||
* @version 1.0
|
||||
* @description 本地缓存设置
|
||||
* @createDate 2021/6/16 10:08
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UrlPermRolesLocalCache<T> {
|
||||
private Cache<String,T> localCache = null;
|
||||
|
||||
@PostConstruct
|
||||
private void init(){
|
||||
localCache = CacheBuilder.newBuilder()
|
||||
//设置本地缓存容器的初始容量
|
||||
.initialCapacity(1)
|
||||
//设置本地缓存的最大容量
|
||||
.maximumSize(10)
|
||||
//设置写缓存后多少秒过期
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS).build();
|
||||
}
|
||||
|
||||
|
||||
public void setLocalCache(String key,T object){
|
||||
localCache.put(key,object);
|
||||
}
|
||||
|
||||
public <T> T getCache(String key){
|
||||
return (T) localCache.getIfPresent(key);
|
||||
}
|
||||
public void remove(String key){
|
||||
localCache.invalidate(key);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.youlai.gateway.config;
|
||||
|
||||
import com.youlai.gateway.component.RedisChannelListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
|
||||
@Configuration
|
||||
public class RedisGatewyConfig {
|
||||
|
||||
@Autowired
|
||||
private RedisConnectionFactory connectionFactory;
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(){
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.addMessageListener(messageListenerAdapter(),channelTopic());
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageListenerAdapter messageListenerAdapter(){
|
||||
return new MessageListenerAdapter(redisChannelListener());
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisChannelListener redisChannelListener(){
|
||||
return new RedisChannelListener();
|
||||
}
|
||||
@Bean
|
||||
ChannelTopic channelTopic(){
|
||||
return new ChannelTopic("cleanRoleLocalCache");
|
||||
}
|
||||
|
||||
}
|
@ -69,9 +69,7 @@ public class ResourceServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权
|
||||
*
|
||||
* @return
|
||||
* 自定义未授权响应
|
||||
*/
|
||||
@Bean
|
||||
ServerAccessDeniedHandler accessDeniedHandler() {
|
||||
@ -114,7 +112,6 @@ public class ResourceServerConfig {
|
||||
|
||||
/**
|
||||
* 本地获取JWT验签公钥
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Bean
|
||||
|
@ -3,13 +3,13 @@ package com.youlai.gateway.security;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSON;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.youlai.common.constant.AuthConstants;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.gateway.component.UrlPermRolesLocalCache;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
@ -23,22 +23,28 @@ import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 网关自定义鉴权管理器
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2020-05-01
|
||||
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||
|
||||
private RedisTemplate redisTemplate;
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
// 本地缓存
|
||||
private final UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||
|
||||
// 是否演示环境
|
||||
@Value("${local-cache.enabled}")
|
||||
private Boolean localCacheEnabled;
|
||||
|
||||
@Override
|
||||
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||
@ -47,61 +53,68 @@ public class ResourceServerManager implements ReactiveAuthorizationManager<Autho
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
}
|
||||
PathMatcher pathMatcher = new AntPathMatcher(); // 【声明定义】Ant路径匹配模式,“请求路径”和缓存中权限规则的“URL权限标识”匹配
|
||||
PathMatcher pathMatcher = new AntPathMatcher(); // Ant匹配器
|
||||
String method = request.getMethodValue();
|
||||
String path = request.getURI().getPath();
|
||||
String restfulPath = method + ":" + path; // Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14961707.html
|
||||
|
||||
|
||||
// 移动端请求需认证但无需鉴权判断
|
||||
String token = request.getHeaders().getFirst(AuthConstants.AUTHORIZATION_KEY);
|
||||
|
||||
// 移动端请求无需鉴权,只需认证(即JWT的验签和是否过期判断)
|
||||
if (pathMatcher.match(GlobalConstants.APP_API_PATTERN, path)) {
|
||||
// 如果token以"bearer "为前缀,到这一步说明是经过NimbusReactiveJwtDecoder#decode和JwtTimestampValidator#validate等解析和验证通过的,即已认证
|
||||
if (StrUtil.isNotBlank(token) && token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
||||
// 如果token以"bearer "为前缀,到这里说明JWT有效即已认证
|
||||
if (StrUtil.isNotBlank(token)
|
||||
&& token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
} else {
|
||||
return Mono.just(new AuthorizationDecision(false));
|
||||
}
|
||||
}
|
||||
|
||||
// Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14396990.html
|
||||
String restfulPath = request.getMethodValue() + ":" + path;
|
||||
log.info("请求方法:RESTFul请求路径:{}", restfulPath);
|
||||
// 缓存取 URL权限-角色集合 规则数据
|
||||
// urlPermRolesRules = [{'key':'GET:/api/v1/users/*','value':['ADMIN','TEST']},...]
|
||||
Map<String, Object> urlPermRolesRules;
|
||||
if (localCacheEnabled) {
|
||||
urlPermRolesRules = (Map<String, Object>) urlPermRolesLocalCache.getCache(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
if (null == urlPermRolesRules) {
|
||||
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
urlPermRolesLocalCache.setLocalCache(GlobalConstants.URL_PERM_ROLES_KEY, urlPermRolesRules);
|
||||
}
|
||||
} else {
|
||||
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
}
|
||||
|
||||
// 缓存取【URL权限标识->角色集合】权限规则
|
||||
Map<String, Object> permRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
|
||||
// 根据 “请求路径” 和 权限规则中的“URL权限标识”进行Ant匹配,得出拥有权限的角色集合
|
||||
Set<String> hasPermissionRoles = CollectionUtil.newHashSet(); // 【声明定义】有权限的角色集合
|
||||
boolean needToCheck = false; // 【声明定义】是否需要被拦截检查的请求,如果缓存中权限规则中没有任何URL权限标识和此次请求的URL匹配,默认不需要被鉴权
|
||||
// 根据请求路径判断有访问权限的角色列表
|
||||
List<String> authorizedRoles = new ArrayList<>(); // 拥有访问权限的角色
|
||||
boolean requireCheck = false; // 是否需要鉴权,默认“没有设置权限规则”不用鉴权
|
||||
|
||||
for (Map.Entry<String, Object> permRoles : permRolesRules.entrySet()) {
|
||||
String perm = permRoles.getKey(); // 缓存权限规则的键:URL权限标识
|
||||
for (Map.Entry<String, Object> permRoles : urlPermRolesRules.entrySet()) {
|
||||
String perm = permRoles.getKey();
|
||||
if (pathMatcher.match(perm, restfulPath)) {
|
||||
List<String> roles = Convert.toList(String.class, permRoles.getValue()); // 缓存权限规则的值:有请求路径访问权限的角色集合
|
||||
hasPermissionRoles.addAll(Convert.toList(String.class, roles));
|
||||
if (needToCheck == false) {
|
||||
needToCheck = true;
|
||||
List<String> roles = Convert.toList(String.class, permRoles.getValue());
|
||||
authorizedRoles.addAll(Convert.toList(String.class, roles));
|
||||
if (requireCheck == false) {
|
||||
requireCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("拥有接口访问权限的角色:{}", hasPermissionRoles.toString());
|
||||
// 没有设置权限规则放行;注:如果默认想拦截所有的请求请移除needToCheck变量逻辑即可,根据需求定制
|
||||
if (needToCheck == false) {
|
||||
if (requireCheck == false) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
}
|
||||
|
||||
// 判断用户JWT中携带的角色是否有能通过权限拦截的角色
|
||||
// 判断JWT中携带的用户角色是否有权限访问
|
||||
Mono<AuthorizationDecision> authorizationDecisionMono = mono
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.flatMapIterable(Authentication::getAuthorities)
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.any(authority -> {
|
||||
log.info("用户权限 : {}", authority); // ROLE_ROOT
|
||||
String role = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 角色编码:ROOT
|
||||
if (GlobalConstants.ROOT_ROLE_CODE.equals(role)) { // 如果是超级管理员则放行
|
||||
return true;
|
||||
String roleCode = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 用户的角色
|
||||
if (GlobalConstants.ROOT_ROLE_CODE.equals(roleCode)) {
|
||||
return true; // 如果是超级管理员则放行
|
||||
}
|
||||
boolean hasPermission = CollectionUtil.isNotEmpty(hasPermissionRoles) && hasPermissionRoles.contains(role); // 用户角色中只要有一个满足则通过权限校验
|
||||
return hasPermission;
|
||||
boolean hasAuthorized = CollectionUtil.isNotEmpty(authorizedRoles) && authorizedRoles.contains(roleCode);
|
||||
return hasAuthorized;
|
||||
})
|
||||
.map(AuthorizationDecision::new)
|
||||
.defaultIfEmpty(new AuthorizationDecision(false));
|
||||
|
@ -7,10 +7,10 @@ import com.nimbusds.jose.JWSObject;
|
||||
import com.youlai.common.constant.AuthConstants;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.gateway.util.ResponseUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
@ -23,22 +23,23 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 安全拦截全局过滤器
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2020-06-12
|
||||
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
// 是否演示环境
|
||||
@Value("${demo}")
|
||||
private Boolean isDemoEnv;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
private String env;
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@ -47,8 +48,8 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
// 演示环境禁止删除和修改
|
||||
if (isDemoEnv
|
||||
// 线上演示环境禁止修改和删除
|
||||
if (env.equals("prod")
|
||||
&& (HttpMethod.DELETE.toString().equals(request.getMethodValue()) // 删除方法
|
||||
|| HttpMethod.PUT.toString().equals(request.getMethodValue())) // 修改方法
|
||||
) {
|
||||
@ -74,7 +75,7 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
// 存在token且不是黑名单,request写入JWT的载体信息
|
||||
request = exchange.getRequest().mutate()
|
||||
.header(AuthConstants.JWT_PAYLOAD_KEY, payload)
|
||||
.header(AuthConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload,"UTF-8"))
|
||||
.build();
|
||||
exchange = exchange.mutate().request(request).build();
|
||||
return chain.filter(exchange);
|
||||
|
@ -11,7 +11,7 @@ import springfox.documentation.swagger.web.*;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Author haoxr
|
||||
* @author xianrui
|
||||
* @Date 2021-02-25 16:34
|
||||
* @Version 1.0.0
|
||||
* @ https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/handler/SwaggerHandler.java
|
||||
|
@ -8,9 +8,9 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @auth haoxr
|
||||
* @link https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/config/SwaggerHeaderFilter.java
|
||||
* @auth xianrui
|
||||
* @date 2021-02-25 16:29
|
||||
* @link https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/config/SwaggerHeaderFilter.java
|
||||
*/
|
||||
@Component
|
||||
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
|
||||
|
@ -13,29 +13,7 @@ spring:
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
sentinel:
|
||||
enabled: false # 网关流控开关
|
||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
||||
transport:
|
||||
client-ip: localhost
|
||||
dashboard: localhost:8080
|
||||
datasource:
|
||||
# 网关限流规则,gw-flow为key,随便定义
|
||||
gw-flow:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-gw-flow-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: gw-flow
|
||||
# 网关API自定义分组
|
||||
gw-api-group:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-gw-api-group-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: gw-api-group
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -15,24 +15,3 @@ spring:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
sentinel:
|
||||
eager: true
|
||||
transport:
|
||||
dashboard: e.youlai.tech:8858
|
||||
datasource:
|
||||
# 网关限流
|
||||
gw-flow:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-gw-flow-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: gw-flow
|
||||
# 网关API自定义分组
|
||||
gw-api-group:
|
||||
nacos:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
dataId: ${spring.application.name}-gw-api-group-rules
|
||||
groupId: SENTINEL_GROUP
|
||||
data-type: json
|
||||
rule-type: gw-api-group
|
||||
|
Loading…
Reference in New Issue
Block a user