commit message

This commit is contained in:
Chopper
2021-05-13 10:41:46 +08:00
commit 3785bdb3bb
1424 changed files with 100110 additions and 0 deletions

391
framework/pom.xml Normal file
View File

@@ -0,0 +1,391 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.lili</groupId>
<artifactId>lili-shop-parent</artifactId>
<version>1.0.1</version>
</parent>
<artifactId>framework</artifactId>
<packaging>jar</packaging>
<properties>
<swagger-bootstrap-ui-version>1.9.6</swagger-bootstrap-ui-version>
<alipay-sdk-version>4.13.40.ALL</alipay-sdk-version>
<mysql-connector-version>5.1.48</mysql-connector-version>
<mybatis-plus-version>3.3.1.tmp</mybatis-plus-version>
<Hutool-version>5.5.8</Hutool-version>
<TinyPinyin-verions>2.0.3.RELEASE</TinyPinyin-verions>
<jasypt-version>3.0.0</jasypt-version>
<neetl-version>2.9.10</neetl-version>
<lombok-version>1.18.10</lombok-version>
<minio-version>6.0.11</minio-version>
<aliyun-version>4.5.18</aliyun-version>
<aliyun-sdk-oss-version>3.11.1</aliyun-sdk-oss-version>
<aliyun-sdk-dysms-version>2.0.1</aliyun-sdk-dysms-version>
<rocketmq-version>2.1.1</rocketmq-version>
<jwt-version>0.10.7</jwt-version>
<antlr4-runtime-version>4.7.2</antlr4-runtime-version>
<sharding-jdbc-version>4.0.0</sharding-jdbc-version>
<druid-version>1.1.20</druid-version>
<simple-http-version>1.0.3</simple-http-version>
<antlr4-version>4.7.2</antlr4-version>
<okhttp-version>4.4.1</okhttp-version>
<httpclient-version>4.5.12</httpclient-version>
<beetl-version>2.9.10</beetl-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<skipTests>true</skipTests>
<knife4j.version>2.0.8</knife4j.version>
<de.codecentric>2.3.1</de.codecentric>
<userAgentUtils>1.21</userAgentUtils>
<interceptor-api>1.2</interceptor-api>
</properties>
<dependencies>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>${interceptor-api}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${de.codecentric}</version>
</dependency>
<!-- 开源多维码生成工具 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<!-- 开源多维码生成工具 -->
<dependency>
<groupId>com.github.xkzhangsan</groupId>
<artifactId>xk-time</artifactId>
<version>2.2.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<exclusions>
<exclusion>
<artifactId>HdrHistogram</artifactId>
<groupId>org.hdrhistogram</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--mongodb依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-version}</version>
</dependency>
<!-- Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Swagger API文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.springfox</groupId>-->
<!-- <artifactId>springfox-swagger-ui</artifactId>-->
<!-- <version>${swagger-version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <artifactId>guava</artifactId>-->
<!-- <groupId>com.google.guava</groupId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <artifactId>javassist</artifactId>-->
<!-- <groupId>org.javassist</groupId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.springfox</groupId>-->
<!-- <artifactId>springfox-swagger2</artifactId>-->
<!-- <version>${swagger-version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui-version}</version>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${Hutool-version}</version>
</dependency>
<dependency>
<groupId>io.github.biezhi</groupId>
<artifactId>TinyPinyin</artifactId>
<version>${TinyPinyin-verions}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
<!-- Jasypt加密 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>${jasypt-version}</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>${beetl-version}</version>
</dependency>
<!-- Minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio-version}</version>
<exclusions>
<exclusion>
<artifactId>jsr305</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 阿里云核心包-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun-version}</version>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-sdk-oss-version}</version>
<exclusions>
<exclusion>
<artifactId>aliyun-java-sdk-core</artifactId>
<groupId>com.aliyun</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${aliyun-sdk-dysms-version}</version>
</dependency>
<!--脚本编程-->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<!-- rocketmq-spring-boot-starter -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq-version}</version>
</dependency>
<!-- token加密 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt-version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt-version}</version>
<scope>runtime</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- <scope>test</scope>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.junit.vintage</groupId>-->
<!-- <artifactId>junit-vintage-engine</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<!-- 解决版本提示问题 -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
<version>${antlr4-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--sharding jdbc springboot-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc-version}</version>
<exclusions>
<exclusion>
<artifactId>groovy</artifactId>
<groupId>org.codehaus.groovy</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>${sharding-jdbc-version}</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-version}</version>
</dependency>
<dependency>
<groupId>com.xkcoding.http</groupId>
<artifactId>simple-http</artifactId>
<version>${simple-http-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.antlr/antlr4-runtime -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr4-runtime-version}</version>
</dependency>
<!-- http client -->
<!-- <dependency>-->
<!-- <groupId>org.apache.httpcomponents</groupId>-->
<!-- <artifactId>httpclient</artifactId>-->
<!-- <version>${httpclient-version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.squareup.okhttp3</groupId>-->
<!-- <artifactId>okhttp</artifactId>-->
<!-- <version>${okhttp-version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
</dependency>
<!--用户端类型处理-->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>${userAgentUtils}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,81 @@
package cn.lili.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Date;
/**
* 数据库基础实体类
*
* @author Chopper
* @version v1.0
* @since 2020/8/20 14:34
*/
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler", "fieldHandler"})
@AllArgsConstructor
@NoArgsConstructor
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@TableId
@TableField
@Column(columnDefinition = "bigint(20)")
@ApiModelProperty(value = "唯一标识", hidden = true)
private String id;
@CreatedBy
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建者", hidden = true)
private String createBy;
@CreatedDate
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间", hidden = true)
private Date createTime;
@LastModifiedBy
@TableField(fill = FieldFill.UPDATE)
@ApiModelProperty(value = "更新者", hidden = true)
private String updateBy;
@LastModifiedDate
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.UPDATE)
@ApiModelProperty(value = "更新时间", hidden = true)
private Date updateTime;
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "删除标志 true/false 删除/未删除", hidden = true)
private Boolean deleteFlag ;
}

View File

@@ -0,0 +1,45 @@
package cn.lili.base;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
/**
* 数据库基础实体类
*
* @author Chopper
* @version v1.0
* @since 2020/8/20 14:34
*/
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler", "fieldHandler"})
@AllArgsConstructor
@NoArgsConstructor
public abstract class IdEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@TableId
@TableField
@Column(columnDefinition = "bigint(20)")
@ApiModelProperty(value = "唯一标识", hidden = true)
private String id;
}

View File

@@ -0,0 +1,46 @@
package cn.lili.base.mybatisplus;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.utils.SnowFlake;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 字段填充审计
*
* @author lili
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
AuthUser authUser = UserContext.getCurrentUser();
if (authUser != null) {
this.setFieldValByName("createBy", authUser.getUsername(), metaObject);
}
this.setFieldValByName("createTime", new Date(), metaObject);
//有值,则写入
if (metaObject.hasGetter("deleteFlag")) {
this.setFieldValByName("deleteFlag", false, metaObject);
}
if (metaObject.hasGetter("id")) {
this.setFieldValByName("id", String.valueOf(SnowFlake.getId()), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
AuthUser authUser = UserContext.getCurrentUser();
if (authUser != null) {
this.setFieldValByName("updateBy", authUser.getUsername(), metaObject);
}
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}

View File

@@ -0,0 +1,21 @@
package cn.lili.base.mybatisplus;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Chopper
*/
@Configuration
@MapperScan({"cn.lili.modules.*.*.mapper","cn.lili.modules.*.mapper"})
public class MybatisPlusConfig {
/**
* 分页插件,自动识别数据库类型
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

View File

@@ -0,0 +1,98 @@
package cn.lili.common.aop.limiter;
import cn.lili.common.aop.limiter.annotation.LimitPoint;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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;
import java.lang.reflect.Method;
/**
* 流量拦截
* @author Chopper
*/
@Aspect
@Configuration
@Slf4j
public class LimitInterceptor {
private RedisTemplate<String, Serializable> redisTemplate;
private DefaultRedisScript<Number> limitScript;
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Autowired
public void setLimitScript(DefaultRedisScript<Number> limitScript) {
this.limitScript = limitScript;
}
@Around("execution(public * *(..)) && @annotation(cn.lili.common.aop.limiter.annotation.LimitPoint)")
public Object interceptor(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
LimitPoint limitPointAnnotation = method.getAnnotation(LimitPoint.class);
LimitType limitType = limitPointAnnotation.limitType();
String name = limitPointAnnotation.name();
String key;
int limitPeriod = limitPointAnnotation.period();
int limitCount = limitPointAnnotation.limit();
switch (limitType) {
case IP:
key = limitPointAnnotation.key() + getIpAddress();
break;
case CUSTOMER:
key = limitPointAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitPointAnnotation.prefix(), key));
try {
Number count = redisTemplate.execute(limitScript, keys, limitCount, limitPeriod);
log.info("Access try count is {} for name={} and key = {}", count, name, key);
if (count != null && count.intValue() <= limitCount) {
return pjp.proceed();
} else {
throw new RuntimeException("访问过于频繁,请稍后再试");
}
} catch (Throwable e) {
if (e instanceof RuntimeException) {
throw new RuntimeException(e.getLocalizedMessage());
}
throw new RuntimeException("服务器异常,请稍后再试");
}
}
private static final String UNKNOWN = "unknown";
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;
}
}

View File

@@ -0,0 +1,20 @@
package cn.lili.common.aop.limiter;
/**
* redis 限流类型
*
* @author Chopper
* @since 2018年2月02日 下午4:58:52
*/
public enum LimitType {
/**
* 自定义key(即全局限流)
*/
CUSTOMER,
/**
* 根据请求者IPIP限流
*/
IP
}

View File

@@ -0,0 +1,64 @@
package cn.lili.common.aop.limiter.annotation;
import cn.lili.common.aop.limiter.LimitType;
import java.lang.annotation.*;
/**
* 限流注解
*
* @author Chopper
* @since 2018-02-05
*/
@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
*/
LimitType limitType() default LimitType.IP;
}

View File

@@ -0,0 +1,29 @@
package cn.lili.common.aop.syslog.annotation;
import java.lang.annotation.*;
/**
* 系统日志AOP注解
*
* @author Chopper
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SystemLogPoint {
/**
* 日志名称
*
* @return
*/
String description() default "";
/**
* 自定义日志内容
*
* @return
*/
String customerLog() default "";
}

View File

@@ -0,0 +1,172 @@
package cn.lili.common.aop.syslog.interceptor;
import cn.lili.common.aop.syslog.annotation.SystemLogPoint;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.utils.IpHelper;
import cn.lili.common.utils.SpelUtil;
import cn.lili.common.utils.ThreadPoolUtil;
import cn.lili.modules.base.entity.systemlog.SystemLogVO;
import cn.lili.modules.connect.util.IpUtils;
import cn.lili.modules.permission.service.SystemLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Spring AOP实现日志管理
*
* @author Chopper
*/
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SystemLogAspect {
/**
* 启动线程异步记录日志
*/
private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<>("SYSTEM-LOG");
private final SystemLogService systemLogService;
private final HttpServletRequest request;
private final IpHelper ipHelper;
/**
* Controller层切点,注解方式
*/
@Pointcut("@annotation(cn.lili.common.aop.syslog.annotation.SystemLogPoint)")
public void controllerAspect() {
}
/**
* 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
*/
@Before("controllerAspect()")
public void doBefore() {
beginTimeThreadLocal.set(new Date());
}
/**
* 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
*
* @param joinPoint 切点
*/
@AfterReturning(returning = "rvt", pointcut = "controllerAspect()")
public void after(JoinPoint joinPoint, Object rvt) {
try {
Map map = spelFormat(joinPoint, rvt);
String description = map.get("description").toString();
String customerLog = map.get("customerLog").toString();
Map<String, String[]> logParams = request.getParameterMap();
AuthUser authUser = UserContext.getCurrentUser();
SystemLogVO systemLogVO = new SystemLogVO();
if (authUser == null) {
//如果是商家则记录商家id否则记录-1代表平台id
systemLogVO.setStoreId(-2L);
//请求用户
systemLogVO.setUsername("游客");
} else {
//如果是商家则记录商家id否则记录-1代表平台id
systemLogVO.setStoreId(authUser.getRole().equals(UserEnums.STORE) ? Long.parseLong(authUser.getStoreId()) : -1);
//请求用户
systemLogVO.setUsername(authUser.getUsername());
}
//日志标题
systemLogVO.setName(description);
//日志请求url
systemLogVO.setRequestUrl(request.getRequestURI());
//请求方式
systemLogVO.setRequestType(request.getMethod());
//请求参数
systemLogVO.setMapToParams(logParams);
//响应参数 此处数据太大了,所以先注释掉
// systemLogVO.setResponseBody(JSONUtil.toJsonStr(rvt));
//请求IP
systemLogVO.setIp(IpUtils.getIpAddress(request));
//IP地址
systemLogVO.setIpInfo(ipHelper.getIpCity(request));
//写入自定义日志内容
systemLogVO.setCustomerLog(customerLog);
//请求开始时间
long beginTime = beginTimeThreadLocal.get().getTime();
long endTime = System.currentTimeMillis();
//请求耗时
Long usedTime = endTime - beginTime;
systemLogVO.setCostTime(usedTime.intValue());
//调用线程保存
ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(systemLogVO, systemLogService));
} catch (Exception e) {
log.error("系统日志保存异常", e);
}
}
/**
* 保存日志
*/
private static class SaveSystemLogThread implements Runnable {
private final SystemLogVO systemLogVO;
private final SystemLogService systemLogService;
public SaveSystemLogThread(SystemLogVO systemLogVO, SystemLogService systemLogService) {
this.systemLogVO = systemLogVO;
this.systemLogService = systemLogService;
}
@Override
public void run() {
try {
systemLogService.saveLog(systemLogVO);
} catch (Exception e) {
log.error("系统日志保存异常,内容{}", systemLogVO, e);
}
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
private static Map<String, String> spelFormat(JoinPoint joinPoint, Object rvt) {
Map<String, String> result = new HashMap<>();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
SystemLogPoint systemLogPoint = signature.getMethod().getAnnotation(SystemLogPoint.class);
String description = systemLogPoint.description();
String customerLog = SpelUtil.compileParams(joinPoint, rvt, systemLogPoint.customerLog());
result.put("description", description);
result.put("customerLog", customerLog);
return result;
}
}

View File

@@ -0,0 +1,235 @@
package cn.lili.common.cache;
import org.springframework.data.redis.core.ZSetOperations;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 缓存接口
*
* @author Chopper
*/
public interface Cache<T> {
/**
* Get an item from the cache, nontransactionally
*
* @param key
* @return the cached object or <tt>null</tt>
*/
T get(Object key);
/**
* Get an item from the cache, nontransactionally
*
* @param key
* @return the cached object or <tt>null</tt>
*/
String getString(Object key);
/**
* multiGet
*
* @param keys 要查询的key集合
* @return
*/
List multiGet(Collection keys);
/**
* 批量set
*
* @param map
*/
void multiSet(Map map);
/**
* 批量删除
*
* @param keys 要删除的key集合
*/
void multiDel(Collection keys);
/**
* Add an item to the cache, nontransactionally, with
* failfast semantics
*
* @param key
* @param value
*/
void put(Object key, T value);
/**
* 往缓存中写入内容
*
* @param key
* @param value
* @param exp 超时时间,单位为秒
*/
void put(Object key, T value, Long exp);
/**
* 往缓存中写入内容
*
* @param key
* @param value
* @param exp
* @param timeUnit 写入单位
*/
void put(Object key, T value, Long exp, TimeUnit timeUnit);
/**
* 删除
*
* @param key
*/
void remove(Object key);
/**
* 删除
*
* @param key
*/
void vagueDel(Object key);
/**
* Clear the cache
*/
void clear();
/**
* 往缓存中写入内容
*
* @param key 缓存key
* @param hashKey 缓存中hashKey
* @param hashValue hash值
*/
void putHash(Object key, Object hashKey, Object hashValue);
/**
* 玩缓存中写入内容
*
* @param key
* @param map
*/
void putAllHash(Object key, Map map);
/**
* 读取缓存值
*
* @param key
* @param hashKey
* @return
*/
T getHash(Object key, Object hashKey);
/**
* 读取缓存值
*
* @param key
* @return
*/
Map<Object, Object> getHash(Object key);
/**
* 是否包含
*
* @param key
* @return
*/
boolean hasKey(Object key);
/**
* 模糊匹配key
*
* @param pattern
* @return
*/
List<String> keys(String pattern);
//-----------------------------------------------用于特殊场景redis去重计数---------------------------------------------
/**
* 累计数目
* 效率较高的 计数器
* 如需清零按照普通key 移除即可
*
* @param key
* @return
*/
Long cumulative(Object key, Object value);
/**
* 计数器结果
* <p>
* 效率较高的 计数器 统计返回
* 如需清零按照普通key 移除即可
*
* @param key
* @return
*/
Long counter(Object key);
/**
* 批量计数
*
* @param keys 要查询的key集合
* @return
*/
List multiCounter(Collection keys);
/**
* 计数器结果
* <p>
* 效率较高的 计数器 统计返回
* 如需清零按照普通key 移除即可
*
* @param key
* @return
*/
Long mergeCounter(Object... key);
//---------------------------------------------------用于特殊场景redis去重统计-----------------------------------------
//-----------------------------------------------redis计数---------------------------------------------
/**
* redis 计数器 累加
* 注到达liveTime之后该次增加取消即自动-1而不是redis值为空
*
* @param key 为累计的key同一key每次调用则值 +1
* @param liveTime 单位秒后失效
* @return
*/
Long incr(String key, long liveTime);
//-----------------------------------------------redis计数---------------------------------------------
/**
* 使用Sorted Set记录keyword
* zincrby命令对于一个Sorted Set存在的就把分数加x(x可自行设定)不存在就创建一个分数为1的成员
*
* @param sortedSetName sortedSetName的Sorted Set不用预先创建不存在会自动创建存在则向里添加数据
* @param keyword 关键词
*/
void incrementScore(String sortedSetName, String keyword);
/**
* zrevrange命令, 查询Sorted Set中指定范围的值
* 返回的有序集合中score大的在前面
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
*
* @param sortedSetName sortedSetName
* @param start 查询范围开始位置
* @param end 查询范围结束位置
* @return
*/
Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer start, Integer end);
}

View File

@@ -0,0 +1,474 @@
package cn.lili.common.cache;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.modules.promotion.entity.enums.PromotionTypeEnum;
/**
* 缓存前缀
*
* @author pikachu
* @version 4.1
* @since 1.0
* 2018/3/19
*/
public enum CachePrefix {
/**
* nonce
*/
NONCE,
/**
* 在线人数
*/
ONLINE_NUM,
/**
* 会员分布数据
*/
MEMBER_DISTRIBUTION,
/**
* 在线会员统计
*/
ONLINE_MEMBER,
/**
* token 信息
*/
ACCESS_TOKEN,
/**
* token 信息
*/
REFRESH_TOKEN,
/**
* 联合登录响应
*/
CONNECT_RESULT,
/**
* 微信联合登陆 session key
*/
SESSION_KEY,
/**
* 权限
*/
PERMISSION_LIST,
/**
* 部门id列表
*/
DEPARTMENT_IDS,
/**
* 用户错误登录限制
*/
LOGIN_TIME_LIMIT,
/**
* 系统设置
*/
SETTING,
/**
* 验证码滑块源
*/
VERIFICATION,
/**
* 验证码滑块源
*/
VERIFICATION_IMAGE,
/**
* 快递平台
*/
EXPRESS,
/**
* 图片验证码
*/
CAPTCHA,
/**
* 商品
*/
GOODS,
/**
* 商品SKU
*/
GOODS_SKU,
/**
* 运费模板脚本
*/
SHIP_SCRIPT,
/**
* 商品sku
*/
SKU,
/**
* sku库存
*/
SKU_STOCK,
/**
* 促销商品sku库存
*/
PROMOTION_GOODS_STOCK,
/**
* 商品库存
*/
GOODS_STOCK,
/**
* 商品分类
*/
CATEGORY,
/**
* 浏览次数
*/
VISIT_COUNT,
/**
* 存储方案
*/
UPLOADER,
/**
* 地区
*/
REGION,
/**
* 短信网关
*/
SPlATFORM,
/**
* 短信验证码前缀
*/
_CODE_PREFIX,
/**
* smtp
*/
SMTP,
/**
* 系统设置
*/
SETTINGS,
/**
* 电子面单
*/
WAYBILL,
/**
* 短信验证码
*/
SMS_CODE,
/**
* 邮箱验证码
*/
EMAIL_CODE,
/**
* 管理员角色权限对照表
*/
ADMIN_URL_ROLE,
/**
* 店铺管理员角色权限对照表
*/
SHOP_URL_ROLE,
/**
* 手机验证标识
*/
MOBILE_VALIDATE,
/**
* 邮箱验证标识
*/
EMAIL_VALIDATE,
/**
* 店铺运费模版列表
*/
SHIP_TEMPLATE,
/**
* 店铺中某个运费模版
*/
SHIP_TEMPLATE_ONE,
//================促销=================
/**
* 促销活动
*/
PROMOTION,
/**
* 促销活动
*/
PROMOTION_GOODS,
/*** 单品立减 */
STORE_ID_MINUS,
/*** 第二件半价 */
STORE_ID_HALF_PRICE,
/*** 满优惠 */
STORE_ID_FULL_DISCOUNT,
/**
* 限时抢购活动缓存key前缀
*/
STORE_ID_SECKILL,
/**
* 团购活动缓存key前缀
*/
STORE_ID_GROUP_BUY,
/**
* 积分商品缓存key前缀
*/
STORE_ID_EXCHANGE,
//================交易=================
/**
* 购物车原始数据
*/
CART_ORIGIN_DATA_PREFIX,
/**
* 立即购买原始数据
*/
BUY_NOW_ORIGIN_DATA_PREFIX,
/**
* 交易原始数据
*/
TRADE_ORIGIN_DATA_PREFIX,
/**
* 立即购买sku
*/
CART_SKU_PREFIX,
/**
* 购物车视图
*/
CART_MEMBER_ID_PREFIX,
/**
* 购物车,用户选择的促销信息
*/
CART_PROMOTION_PREFIX,
/**
* 交易_交易价格的前缀
*/
PRICE_SESSION_ID_PREFIX,
/**
* 交易_交易单
*/
TRADE_SESSION_ID_PREFIX,
/**
* 结算参数
*/
CHECKOUT_PARAM_ID_PREFIX,
/**
* 交易单号前缀
*/
TRADE_SN_CACHE_PREFIX,
/**
* 订单编号前缀
*/
ORDER_SN_CACHE_PREFIX,
/**
* 订单编号标记
*/
ORDER_SN_SIGN_CACHE_PREFIX,
/**
* 订单编号前缀
*/
PAY_LOG_SN_CACHE_PREFIX,
/**
* 合同编号
*/
CONTRACT_SN_CACHE_PREFIX,
/**
* 零钱
*/
SMALL_CHANGE_CACHE_PREFIX,
/**
* 售后服务单号前缀
*/
AFTER_SALE_SERVICE_PREFIX,
/**
* 交易
*/
TRADE,
/**
* 站点导航栏
*/
SITE_NAVIGATION,
/**
* 支付参数
*/
PAYMENT_CONFIG,
/**
* 流程控制
*/
FLOW,
/**
* 热门搜索
*/
HOT_WORD,
/**
* 会员积分
*/
MEMBER_POINT,
/**
* 会员积分
*/
POINT_ORDER,
/**
* 微博登录
*/
WEIBO_STATE,
/**
* 微博登录
*/
QQ_STATE,
/**
* 微博登录
*/
GITHUB_STATE,
/**
* 验证码key
*/
VERIFICATION_KEY,
/**
* 验证码验证结果
*/
VERIFICATION_RESULT,
/**
* 微信CGItoken
*/
WECHAT_CGI_ACCESS_TOKEN,
/**
* 微信JSApitoken
*/
WECHAT_JS_API_TOKEN,
/**
* 微信会话信息
*/
WECHAT_SESSION_PARAMS,
/**
* 第三方用户权限
*/
ALIPAY_CONFIG,
/**
* 微信支付配置
*/
WECHAT_PAY_CONFIG,
/**
* 微信支付平台证书配置
*/
WECHAT_PLAT_FORM_CERT,
/**
* 第三方用户权限
*/
CONNECT_AUTH,
/**
* 平台PageView 统计
*/
PV,
/**
* 平台UserView 统计
*/
UV,
/**
* 平台 商品PV 统计
*/
GOODS_PV,
/**
* 平台 商品UV 统计
*/
GOODS_UV,
/**
* 店铺PageView 统计
*/
STORE_PV,
/**
* 店铺UserView 统计
*/
STORE_UV,
/**
* 店铺 商品PV 统计
*/
STORE_GOODS_PV,
/**
* 店铺 商品UV 统计
*/
STORE_GOODS_UV,
/**
* 分销员
*/
DISTRIBUTION,
/**
* 找回手机
*/
FIND_MOBILE,
/**
* 文章分类
*/
ARTICLE_CATEGORY,
/**
* 文章
*/
ARTICLE_CACHE
;
public static String removePrefix(String str) {
return str.substring(str.lastIndexOf("}_") + 2);
}
//通用获取缓存key值
public String getPrefix() {
return "{" + this.name() + "}_";
}
//通用获取缓存key值
public String getPrefix(PromotionTypeEnum typeEnum) {
return "{" + this.name() + "_" + typeEnum.name() + "}_";
}
//获取缓存key值 + 用户端例如三端都有用户体系需要分别登录如果用户名一致则redis中的权限可能会冲突出错
public String getPrefix(UserEnums user) {
return "{" + this.name() + "_" + user.name() + "}_";
}
}

View File

@@ -0,0 +1,101 @@
package cn.lili.common.cache;
import cn.lili.common.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* MybatisRedisCache
*
* @author Chopper
* @version v1.0
* @since
* 2020-04-01 2:59 下午
* 不赞成使用此方式注解统一使用Cacheable 更为合适
*
* 使用方法 @CacheNamespace(implementation= MybatisRedisCache.class,eviction=MybatisRedisCache.class)
*/
@Deprecated
@Slf4j
public class MybatisRedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplate<Object, Object> getRedisTemplate() {
return (RedisTemplate<Object, Object>) SpringContextUtil.getBean("redisTemplate");
}
private final String id;
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
try {
if (value != null) {
log.info("写入缓存:" + key.toString()+"----"+value.toString());
getRedisTemplate().opsForValue().set(key.toString(), value);
}
} catch (Exception e) {
log.error("写入mybatis缓存异常 ", e);
}
}
@Override
public Object getObject(Object key) {
try {
if (key != null) {
log.info("获取缓存:" + key);
return getRedisTemplate().opsForValue().get(key.toString());
}
} catch (Exception e) {
log.error("mybatis缓存获取异常 ", e);
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null) {
getRedisTemplate().delete(key.toString());
}
return null;
}
@Override
public void clear() {
Set<Object> keys = getRedisTemplate().keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
getRedisTemplate().delete(keys);
}
}
@Override
public int getSize() {
Long size = getRedisTemplate().execute(RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}

View File

@@ -0,0 +1,243 @@
package cn.lili.common.cache.impl;
import cn.lili.common.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* redis 缓存实现
*
* @author Chopepr
*/
@Component
public class RedisCache implements Cache {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
public RedisCache() {
}
@Override
public Object get(Object key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public String getString(Object key) {
try {
return redisTemplate.opsForValue().get(key).toString();
} catch (Exception e) {
return null;
}
}
@Override
public List multiGet(Collection keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
@Override
public void multiSet(Map map) {
redisTemplate.opsForValue().multiSet(map);
}
@Override
public void multiDel(Collection keys) {
redisTemplate.delete(keys);
}
@Override
public void put(Object key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
@Override
public void put(Object key, Object value, Long exp) {
put(key, value, exp, TimeUnit.SECONDS);
}
@Override
public void put(Object key, Object value, Long exp, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, exp, timeUnit);
}
@Override
public void remove(Object key) {
redisTemplate.delete(key);
}
/**
* 删除
*
* @param key
*/
@Override
public void vagueDel(Object key) {
Set<Object> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
}
@Override
public void clear() {
Set keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
@Override
public void putHash(Object key, Object hashKey, Object hashValue) {
redisTemplate.opsForHash().put(key, hashKey, hashValue);
}
@Override
public void putAllHash(Object key, Map map) {
redisTemplate.opsForHash().putAll(key, map);
}
@Override
public Object getHash(Object key, Object hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
@Override
public Map<Object, Object> getHash(Object key) {
return this.redisTemplate.opsForHash().entries(key);
}
@Override
public boolean hasKey(Object key) {
return this.redisTemplate.opsForValue().get(key) != null;
}
/**
* 获取符合条件的key
*
* @param pattern 表达式
* @return
*/
@Override
public List<String> keys(String pattern) {
List<String> keys = new ArrayList<>();
this.scan(pattern, item -> {
//符合条件的key
String key = new String(item, StandardCharsets.UTF_8);
keys.add(key);
});
return keys;
}
/**
* scan 实现
*
* @param pattern 表达式
* @param consumer 对迭代到的key进行操作
*/
private void scan(String pattern, Consumer<byte[]> consumer) {
this.redisTemplate.execute((RedisConnection connection) -> {
try (Cursor<byte[]> cursor =
connection.scan(ScanOptions.scanOptions()
.count(Long.MAX_VALUE)
.match(pattern).build())) {
cursor.forEachRemaining(consumer);
return null;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
});
}
@Override
public Long cumulative(Object key, Object value) {
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
// add 方法对应 PFADD 命令
return operations.add(key, value);
}
@Override
public Long counter(Object key) {
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
// add 方法对应 PFADD 命令
return operations.size(key);
}
@Override
public List multiCounter(Collection keys) {
if (keys == null) {
return new ArrayList();
}
List<Long> result = new ArrayList<>();
for (Object key : keys) {
result.add(counter(key));
}
return result;
}
@Override
public Long mergeCounter(Object... key) {
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
// 计数器合并累加
return operations.union(key[0], key);
}
@Override
public Long incr(String key, long liveTime) {
RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
Long increment = entityIdCounter.getAndIncrement();
if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始设置过期时间
entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
}
return increment;
}
/**
* 使用Sorted Set记录keyword
* zincrby命令对于一个Sorted Set存在的就把分数加x(x可自行设定)不存在就创建一个分数为1的成员
*
* @param sortedSetName sortedSetName的Sorted Set不用预先创建不存在会自动创建存在则向里添加数据
* @param keyword 关键词
*/
@Override
public void incrementScore(String sortedSetName, String keyword) {
// x 的含义请见本方法的注释
double x = 1.0;
this.redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, x);
}
/**
* zrevrange命令, 查询Sorted Set中指定范围的值
* 返回的有序集合中score大的在前面
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
*
* @param sortedSetName sortedSetName
* @param start 查询范围开始位置
* @param end 查询范围结束位置
* @return
*/
@Override
public Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer start, Integer end) {
return this.redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetName, start, end);
}
}

View File

@@ -0,0 +1,100 @@
package cn.lili.common.delayqueue;
import cn.lili.common.utils.RedisUtil;
import cn.lili.common.utils.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.Calendar;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* 延时队列工厂
*
* @author paulG
* @since 2020/11/7
**/
@Slf4j
public abstract class AbstractDelayQueueMachineFactory {
@Autowired
private RedisUtil redisUtil;
/**
* 插入任务id
*
* @param jobId 任务id(队列内唯一)
* @param time 延时时间(单位 :秒)
* @return 是否插入成功
*/
public boolean addJobId(String jobId, Integer time) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, time);
long delaySeconds = instance.getTimeInMillis() / 1000;
return redisUtil.zadd(setDelayQueueName(), delaySeconds, jobId);
}
private void startDelayQueueMachine() {
log.info(String.format("延时队列机器{%s}开始运作", setDelayQueueName()));
// 发生异常捕获并且继续不能让战斗停下来
while (true) {
try {
// 获取当前时间的时间戳
long now = System.currentTimeMillis() / 1000;
// 获取当前时间前的任务列表
Set<DefaultTypedTuple> tuples = redisUtil.zrangeByScoreWithScores(setDelayQueueName(), 0, now);
// 如果不为空则遍历判断其是否满足取消要求
if (!CollectionUtils.isEmpty(tuples)) {
for (DefaultTypedTuple tuple : tuples) {
String jobId = (String) tuple.getValue();
Long num = redisUtil.zremove(setDelayQueueName(), jobId);
// 如果移除成功, 则执行
if (num > 0) {
ThreadPoolUtil.execute(() -> invoke(jobId));
}
}
}
} catch (Exception e) {
// log.warn(String.format("处理延时任务发生异常,异常原因为{%s}", e.getMessage()), e);
} finally {
// 间隔一秒钟搞一次
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 最终执行的任务方法
*
* @param jobId 任务id
*/
public abstract void invoke(String jobId);
/**
* 要实现延时队列的名字
*/
public abstract String setDelayQueueName();
@PostConstruct
public void init() {
new Thread(this::startDelayQueueMachine).start();
}
}

View File

@@ -0,0 +1,22 @@
package cn.lili.common.delayqueue;
/**
* 延时任务工具类
*
* @author paulG
* @since 2021/5/7
**/
public class DelayQueueTools {
/**
* 组装延时任务唯一键
*
* @param type 延时任务类型
* @param id id
* @return 唯一键
*/
public static String wrapperUniqueKey(DelayQueueType type, String id) {
return "{TIME_TRIGGER_" + type.name() + "}_" + id;
}
}

View File

@@ -0,0 +1,30 @@
package cn.lili.common.delayqueue;
/**
* 延时任务类型
*
* @author paulG
* @since 2021/5/7
**/
public enum DelayQueueType {
/**
* 促销活动
*/
PROMOTION("促销活动"),
/**
* 拼团订单
*/
PINTUAN_ORDER("拼团订单");
private final String description;
DelayQueueType(String des) {
this.description = des;
}
public String description() {
return this.description;
}
}

View File

@@ -0,0 +1,29 @@
package cn.lili.common.delayqueue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 拼团订单延时任务信息
*
* @author paulG
* @since 2021/5/7
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PintuanOrderMessage {
/**
* 拼团活动id
*/
private String pintuanId;
/**
* 父拼团订单sn
*/
private String orderSn;
}

View File

@@ -0,0 +1,52 @@
package cn.lili.common.delayqueue;
import cn.lili.modules.promotion.entity.enums.PromotionStatusEnum;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 信息队列传输促销信息实体
*
* @author paulG
* @date 2020/10/30
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PromotionMessage {
/**
* 促销id
*/
private String promotionId;
/**
* 促销类型
*/
private String promotionType;
/**
* 促销状态
*/
private String promotionStatus;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
public <T> UpdateWrapper<T> updateWrapper() {
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", promotionId);
updateWrapper.set("promotion_status", PromotionStatusEnum.valueOf(promotionStatus));
return updateWrapper;
}
}

View File

@@ -0,0 +1,186 @@
package cn.lili.common.elasticsearch;
import cn.hutool.core.bean.BeanUtil;
import cn.lili.config.elasticsearch.ElasticsearchProperties;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.HttpAsyncResponseConsumerFactory;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.io.IOException;
/**
* @author paulG
* @since 2020/10/14
**/
@Slf4j
public abstract class BaseElasticsearchService {
protected static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
// 默认缓冲限制为100MB此处修改为30MB。
builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024));
COMMON_OPTIONS = builder.build();
}
@Autowired
@Qualifier("elasticsearchClient")
protected RestHighLevelClient client;
@Autowired
private ElasticsearchProperties elasticsearchProperties;
/**
* build DeleteIndexRequest
*
* @param index elasticsearch index name
* @author fxbin
*/
private static DeleteIndexRequest buildDeleteIndexRequest(String index) {
return new DeleteIndexRequest(index);
}
/**
* build IndexRequest
*
* @param index elasticsearch index name
* @param id request object id
* @param object request object
* @return {@link IndexRequest}
* @author fxbin
*/
protected static IndexRequest buildIndexRequest(String index, String id, Object object) {
return new IndexRequest(index).id(id).source(BeanUtil.beanToMap(object), XContentType.JSON);
}
/**
* create elasticsearch index (asyc)
*
* @param index elasticsearch index
* @author fxbin
*/
protected void createIndexRequest(String index) {
try {
CreateIndexRequest request = new CreateIndexRequest(index);
// Settings for this index
request.settings(Settings.builder().put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards()).put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas()));
CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS);
log.info(" whether all of the nodes have acknowledged the request : {}", createIndexResponse.isAcknowledged());
log.info(" Indicates whether the requisite number of shard copies were started for each shard in the index before timing out :{}", createIndexResponse.isShardsAcknowledged());
} catch (Exception e) {
throw new ElasticsearchException("创建索引 {" + index + "} 失败:" + e.getMessage());
}
}
/**
* Description: 判断某个index是否存在
*
* @param index index名
* @return boolean
* @author fanxb
* @date 2019/7/24 14:57
*/
public boolean indexExist(String index) {
try {
GetIndexRequest request = new GetIndexRequest(index);
request.local(false);
request.humanReadable(true);
request.includeDefaults(false);
return client.indices().exists(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new ElasticsearchException("获取索引 {" + index + "} 是否存在失败:" + e.getMessage());
}
}
/**
* delete elasticsearch index
*
* @param index elasticsearch index name
* @author fxbin
*/
protected void deleteIndexRequest(String index) {
DeleteIndexRequest deleteIndexRequest = buildDeleteIndexRequest(index);
try {
client.indices().delete(deleteIndexRequest, COMMON_OPTIONS);
} catch (IOException e) {
throw new ElasticsearchException("删除索引 {" + index + "} 失败:" + e.getMessage());
}
}
/**
* exec updateRequest
*
* @param index elasticsearch index name
* @param id Document id
* @param object request object
* @author fxbin
*/
protected void updateRequest(String index, String id, Object object) {
try {
UpdateRequest updateRequest = new UpdateRequest(index, id).doc(BeanUtil.beanToMap(object), XContentType.JSON);
client.update(updateRequest, COMMON_OPTIONS);
} catch (IOException e) {
throw new ElasticsearchException("更新索引 {" + index + "} 数据 {" + object + "} 失败: " + e.getMessage());
}
}
/**
* exec deleteRequest
*
* @param index elasticsearch index name
* @param id Document id
* @author fxbin
*/
protected void deleteRequest(String index, String id) {
try {
DeleteRequest deleteRequest = new DeleteRequest(index, id);
client.delete(deleteRequest, COMMON_OPTIONS);
} catch (IOException e) {
throw new ElasticsearchException("删除索引 {" + index + "} 数据id {" + id + "} 失败: " + e.getMessage());
}
}
/**
* search all
*
* @param index elasticsearch index name
* @return {@link SearchResponse}
* @author fxbin
*/
protected SearchResponse search(String index) {
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
try {
searchResponse = client.search(searchRequest, COMMON_OPTIONS);
} catch (IOException e) {
e.printStackTrace();
}
return searchResponse;
}
}

View File

@@ -0,0 +1,16 @@
package cn.lili.common.elasticsearch;
/**
* elasticsearch 索引后缀
*
* @author paulG
* @since 2020/10/13
**/
public class EsSuffix {
/**
* 商品索引后缀
*/
public static final String GOODS_INDEX_NAME = "goods";
}

View File

@@ -0,0 +1,166 @@
package cn.lili.common.enums;
/**
* @author Chopper
* @version v4.1
* @Description: 错误码 5位
* 第一位 1:商品2:会员3:订单,4:店铺,5:页面,6:其他
* @since 2020/11/17 4:44 下午
*/
public enum MessageCode {
E401("认证过期"),
E10001("商品ID不存在"),
E10002("商品名称不正确名称应为2-50字符"),
E10003("此规格已绑定分类不允许删除"),
E10004("该分类名称已存在"),
E10005("父分类不存在"),
E10006("最多为三级分类,添加失败"),
E10007("此类别下存在子类别不能删除"),
E10008("此类别下存在商品不能删除"),
E10009("通过categoryId获取分类失败"),
E10010("不能重复添加分销商品"),
E10011("无法将此商品放入回收站"),
E10012("无法彻底删除从此商品"),
E10013("无法下架此商品"),
E10014("无法还原此商品"),
E20001("用户名密码不能为空"),
E20002("该用户名已被注册"),
E20003("该手机号已被注册"),
E20004("用户不存在"),
E20005("修改资料成功"),
E20006("修改资料失败"),
E20007("手机号不存在"),
E20008("重置密码成功"),
E20009("修改手机号成功"),
E20010("旧密码不正确"),
E20011("修改密码成功"),
E20012("用户未登录"),
E20013("发布评价成功"),
E20014("领取优惠券成功"),
E20015("分销商不存在"),
E20016("分销商已申请,无需重复提交"),
E20017("密码错误"),
E20018("分销商清退失败"),
E20019("审核分销商失败"),
E20020("用户名或密码错误"),
E20021("订单状态不允许申请售后,请联系平台或商家"),
E20022("无权操作此数据!"),
E20023("此用户已禁用!"),
E20024("未对当前用户做手机号码校验!"),
E20025("可提现金额不足!"),
E20026("零钱提现失败!"),
E20027("请填写审核备注!"),
E20028("请勿重复签到"),
E20029("分销商不存在或已被清退"),
E20030("分销商提现记录不存在"),
E20031("分销商提现状态不正确,无法提现"),
E20032("余额账户不存在,无法操作"),
E20033("验证码错误,请重新输入"),
E20034("最多可以设置10个角色"),
E20035("订单已支付,请勿反复支付"),
E20036("该订单已部分支付,请前往订单中心进行支付"),
E20037("余额充值已支付成功,请勿重复支付"),
E20038("余额不足以支付订单,请充值"),
E20039("支付业务异常,请稍后重试"),
E20040("当前订单不需要付款,请返回订单列表重新操作"),
E20041("非当前用户的手机号"),
E20101("联合第三方登录,未绑定会员"),
E20102("联合第三方登录,授权信息错误"),
E20103("会员发票信息重复"),
E20104("会员发票信息不存在"),
E20105("权限不足"),
E30001("购物车添加成功"),
E30002("购物车数量更新成功"),
E30003("购物车选中更新成功"),
E30004("清空购物车成功"),
E30005("删除购物车中的一个或多个产品成功"),
E30006("读取结算页的购物车异常"),
E30007("创建订单异常,请稍后重试"),
E30008("订单状态错误,无法进行确认收货"),
E30009("订单确定收货成功"),
E30010("订单不存在"),
E30011("已支付的订单不能修改金额"),
E30013("物流错误"),
E30014("当前订单不能发货"),
E30015("非当前会员的订单"),
E30016("无法重复提交评价"),
E30017("付款金额和应付金额不一致"),
E30018("售后已审核,无法重复操作"),
E30019("物流公司错误,请重新选择"),
E30020("请选择要投诉的商品"),
E30021("当前订单无法核销"),
E30022("当前售后单无法取消"),
E30023("当前订单未支付,不能申请售后"),
E30024("订单已支付,不能再次进行支付"),
E30025("收银台信息获取错误"),
E30026("积分不足,不能兑换"),
E30027("优惠券已使用/已过期,不能使用"),
E30028("投诉异常,请稍后重试"),
E40001("非当前店铺数据,无法操作"),
E40002("此店铺不存在"),
E40003("店铺名称已存在!"),
E40004("参数有误!"),
E40005("已有店铺,无需重复申请"),
E40006("只有已出账结算单可以核对"),
E40007("只有已核对结算单可以审核"),
E40008("只有已审核结算单可以支付"),
E40009("只有商家可以操作结算单核对"),
E40010("只有管理可以操作结算单核对"),
E40011("存在不属于此店铺的商品"),
E50001("该分类名称已存在"),
E50002("父分类不存在"),
E50003("最多为三级分类,添加失败"),
E50004("最多为三级分类,修改失败"),
E50005("文章分类名称重复,修改失败"),
E50006("该文章分类下存在子分类,不能删除"),
E50007("专题自动发布,无需操作发布"),
E50008("当前页面为开启状态,无法删除"),
E50009("当前页面为唯一页面,无法删除"),
E50010("页面已发布,无需重复提交"),
E50011("需传入查询ID"),
E50012("当前消息模板不存在"),
E50013("短信签名已存在"),
E50014("该文章分类下存在文章,不能删除"),
E60000("请先登录再访问此接口"),
E60001("操作失败"),
E60002("非法请求"),
E60003("请求正在处理,请稍后再试"),
E60004("验证码为空或已过期,请重新获取"),
E60005("验证失败"),
E60007("OSS未配置"),
E60008("支付暂不支持"),
E60009("错误的客户端"),
E60010("角色已绑定部门,请逐个删除"),
E60011("角色已绑定管理员,请逐个删除"),
E60012("菜单已绑定角色,请先删除或编辑角色"),
E60013("部门已经绑定管理员,请先删除或编辑管理员"),
S21001("分销商清退成功"),
S21002("审核分销商成功"),
S44000("操作成功"),
S50001("删除文章分类成功");
private final String value;
MessageCode(String value) {
this.value = value;
}
public Integer getCode() {
return Integer.parseInt(this.name().replace("E", "").replace("S", ""));
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,284 @@
package cn.lili.common.enums;
/**
* 返回状态码
* 第一位 1:商品2:用户3:交易,4:促销,5:店铺,6:页面,7:设置,8:其他
* @author Chopper
* @date 2020/4/8 1:36 下午
*/
public enum ResultCode {
/**
* 成功状态码
*/
SUCCESS(200, "成功"),
/**
* 失败返回码
*/
ERROR(400,"失败"),
/**
* 失败返回码
*/
DEMO_SITE_EXCEPTION(400,"演示站点禁止使用"),
/**
* 分类
*/
CATEGORY_NOT_EXIST(10001,"商品分类不存在"),
CATEGORY_NAME_IS_EXIST(10002,"该分类名称已存在"),
CATEGORY_PARENT_NOT_EXIST(10003,"该分类名称已存在"),
CATEGORY_BEYOND_THREE(10004,"最多为三级分类,添加失败"),
CATEGORY_HAS_CHILDREN(10005,"此类别下存在子类别不能删除"),
CATEGORY_HAS_GOODS(10006,"此类别下存在商品不能删除"),
CATEGORY_SAVE_ERROR(10007,"此类别下存在商品不能删除"),
CATEGORY_PARAMETER_SAVE_ERROR(10008,"分类绑定参数组添加失败"),
CATEGORY_PARAMETER_UPDATE_ERROR(10009,"分类绑定参数组添加失败"),
/**
* 商品
*/
GOODS_NOT_EXIST(11001,"商品已下架"),
GOODS_NAME_ERROR(11002,"商品名称不正确名称应为2-50字符"),
GOODS_UNDER_ERROR(11003,"商品下架失败"),
GOODS_UPPER_ERROR(11004,"商品上架失败"),
GOODS_AUTH_ERROR(11005,"商品审核失败"),
/**
* 参数
*/
PARAMETER_SAVE_ERROR(12001,"参数添加失败"),
PARAMETER_UPDATE_ERROR(12002,"参数编辑失败"),
/**
* 规格
*/
SPEC_SAVE_ERROR(13001,"规格修改失败"),
SPEC_UPDATE_ERROR(13002,"规格修改失败"),
SPEC_DELETE_ERROR(13003,"此规格已绑定分类不允许删除"),
/**
* 品牌
*/
BRAND_SAVE_ERROR(14001,"品牌添加失败"),
BRAND_UPDATE_ERROR(14002,"品牌修改失败"),
BRAND_DISABLE_ERROR(14003,"品牌禁用失败"),
BRAND_DELETE_ERROR(14004,"品牌删除失败"),
/**
* 用户
*/
USER_EDIT_SUCCESS(20001,"用户修改成功"),
USER_NOT_EXIST(20002, "用户不存在"),
USER_NOT_LOGIN(20003, "用户未登录"),
USER_AUTH_EXPIRED(20004, "认证过期"),
USER_AUTHORITY_ERROR(20005,"权限不足"),
USER_CONNECT_LOGIN_ERROR(20006,"未找到登录信息"),
USER_NAME_EXIST(20007, "该用户名已被注册"),
USER_PHONE_EXIST(20008, "该手机号已被注册"),
USER_PHONE_NOT_EXIST(20009, "手机号不存在"),
USER_PASSWORD_ERROR(20010, "密码不正确"),
USER_NOT_PHONE(20011,"非当前用户的手机号"),
USER_CONNECT_ERROR(20012,"联合第三方登录,授权信息错误"),
USER_RECEIPT_REPEAT_ERROR(20013,"会员发票信息重复"),
USER_RECEIPT_NOT_EXIST(20014,"会员发票信息不存在"),
USER_EDIT_ERROR(20015,"用户修改失败"),
USER_OLD_PASSWORD_ERROR(20016, "旧密码不正确"),
USER_COLLECTION_EXIST(2001,"无法重复收藏"),
/**
* 权限
*/
PERMISSION_DEPARTMENT_ROLE_ERROR(21001,"角色已绑定部门,请逐个删除"),
PERMISSION_USER_ROLE_ERROR(21002,"角色已绑定管理员,请逐个删除"),
PERMISSION_MENU_ROLE_ERROR(21003,"菜单已绑定角色,请先删除或编辑角色"),
PERMISSION_DEPARTMENT_DELETE_ERROR(21004,"部门已经绑定管理员,请先删除或编辑管理员"),
PERMISSION_BEYOND_TEN(21005,"最多可以设置10个角色"),
/**
* 分销
*/
DISTRIBUTION_CLOSE(22000, "分销功能关闭"),
DISTRIBUTION_NOT_EXIST(22001, "分销员不存在"),
DISTRIBUTION_IS_APPLY(22002, "分销员已申请,无需重复提交"),
DISTRIBUTION_AUDIT_ERROR(22003, "审核分销员失败"),
DISTRIBUTION_RETREAT_ERROR(22004, "分销员清退失败"),
DISTRIBUTION_CASH_NOT_EXIST(22005, "分销员提现记录不存在"),
DISTRIBUTION_GOODS_DOUBLE(22006, "不能重复添加分销商品"),
/**
* 购物车
*/
CART_ERROR(30001, "读取结算页的购物车异常"),
SHIPPING_NOT_APPLY(30005, "当前选择地址暂不支持配送!"),
/**
* 订单
*/
ORDER_ERROR(31001, "创建订单异常,请稍后重试"),
ORDER_NOT_EXIST(31002, "订单不存在"),
ORDER_DELIVERED_ERROR(31003, "订单状态错误,无法进行确认收货"),
ORDER_UPDATE_PRICE_ERROR(31004, "已支付的订单不能修改金额"),
ORDER_LOGISTICS_ERROR(31005, "物流错误"),
ORDER_DELIVER_ERROR(31006, "物流错误"),
ORDER_NOT_USER(31007, "非当前会员的订单"),
ORDER_TAKE_ERROR(31008,"当前订单无法核销"),
/**
* 支付
*/
PAY_SUCCESS(32001,"支付成功"),
PAY_INCONSISTENT_ERROR(32002,"付款金额和应付金额不一致"),
PAY_DOUBLE_ERROR(32003,"订单已支付,不能再次进行支付"),
PAY_CASHIER_ERROR(32004,"收银台信息获取错误"),
PAY_ERROR(32005,"支付业务异常,请稍后重试"),
PAY_BAN(32006,"当前订单不需要付款,请返回订单列表重新操作"),
PAY_PARTIAL_ERROR(32007,"该订单已部分支付,请前往订单中心进行支付"),
PAY_NOT_SUPPORT(32008,"支付暂不支持"),
PAY_CLIENT_TYPE_ERROR(32009,"错误的客户端"),
PAY_POINT_ENOUGH(32010,"积分不足,不能兑换"),
PAY_NOT_EXIST_ORDER(32011,"支付订单不存在"),
/**
* 售后
*/
AFTER_SALES_NOT_PAY_ERROR(33001,"当前订单未支付,不能申请售后"),
AFTER_SALES_CANCEL_ERROR(33002,"当前售后单无法取消"),
AFTER_SALES_BAN(33003,"订单状态不允许申请售后,请联系平台或商家"),
AFTER_SALES_DOUBLE_ERROR(33004,"售后已审核,无法重复操作"),
AFTER_SALES_LOGISTICS_ERROR(33005,"物流公司错误,请重新选择"),
/**
* 投诉
*/
COMPLAINT_ORDER_ITEM_EMPTY_ERROR(33100,"订单不存在"),
COMPLAINT_SKU_EMPTY_ERROR(33101,"商品已下架,如需投诉请联系平台客服"),
COMPLAINT_ERROR(33102,"投诉异常,请稍后重试"),
/**
* 余额
*/
WALLET_INSUFFICIENT(34001,"余额不足以支付订单,请充值!"),
WALLET_WITHDRAWAL_INSUFFICIENT(34002,"可提现金额不足!"),
WALLET_ERROR_INSUFFICIENT(34003,"零钱提现失败!"),
WALLET_REMARK_ERROR(34004,"请填写审核备注!"),
/**
* 评价
*/
EVALUATION_DOUBLE_ERROR(35001,"无法重复提交评价"),
/**
* 签到
*/
MEMBER_SIGN_REPEAT(40001,"请勿重复签到"),
/**
* 优惠券
*/
COUPON_EDIT_STATUS_SUCCESS(41001,"修改状态成功!"),
COUPON_CANCELLATION_SUCCESS(41002,"会员优惠券作废成功"),
COUPON_EXPIRED(41003,"优惠券已使用/已过期,不能使用"),
COUPON_EDIT_STATUS_ERROR(41004,"优惠券修改状态失败!"),
/**
* 拼团
*/
PINTUAN_MANUAL_OPEN_SUCCESS(42001,"手动开启拼团活动成功"),
PINTUAN_MANUAL_CLOSE_SUCCESS(42002,"手动关闭拼团活动成功"),
PINTUAN_ADD_SUCCESS(42003,"添加拼团活动成功"),
PINTUAN_EDIT_SUCCESS(42004,"修改拼团活动成功"),
PINTUAN_DELETE_SUCCESS(42005,"删除拼团活动成功"),
PINTUAN_MANUAL_OPEN_ERROR(42006,"手动开启拼团活动失败"),
PINTUAN_MANUAL_CLOSE_ERROR(42007,"手动关闭拼团活动失败"),
PINTUAN_ADD_ERROR(42008,"添加拼团活动失败"),
PINTUAN_EDIT_ERROR(42009,"修改拼团活动失败"),
PINTUAN_DELETE_ERROR(42010,"删除拼团活动失败"),
/**
* 满额活动
*/
FULL_DISCOUNT_EDIT_SUCCESS(43001,"修改满优惠活动成功"),
FULL_DISCOUNT_EDIT_DELETE(43002,"删除满优惠活动成功"),
/**
* 店铺
*/
STORE_NOT_EXIST(50001,"此店铺不存在"),
STORE_NAME_EXIST_ERROR(50002,"店铺名称已存在!"),
STORE_APPLY_DOUBLE_ERROR(50003,"已有店铺,无需重复申请!"),
/**
* 结算单
*/
BILL_CHECK_ERROR(51001,"只有已出账结算单可以核对"),
BILL_COMPLETE_ERROR(51002,"只有已审核结算单可以支付"),
/**
* 文章
*/
ARTICLE_CATEGORY_NAME_EXIST(60001,"文章分类名称已存在"),
ARTICLE_CATEGORY_PARENT_NOT_EXIST(60002,"文章分类父分类不存在"),
ARTICLE_CATEGORY_BEYOND_TWO(60003,"最多为二级分类,操作失败"),
ARTICLE_CATEGORY_DELETE_ERROR(60004,"该文章分类下存在子分类,不能删除"),
ARTICLE_CATEGORY_HAS_ARTICLE(60005,"该文章分类下存在文章,不能删除"),
ARTICLE_CATEGORY_NO_DELETION(60007,"默认文章分类不能进行删除"),
ARTICLE_NO_DELETION(60008,"默认文章不能进行删除"),
/**
* 页面
*/
PAGE_NOT_EXIST(61001,"页面不存在"),
PAGE_OPEN_DELETE_ERROR(61002,"当前页面为开启状态,无法删除"),
PAGE_DELETE_ERROR(61003,"当前页面为唯一页面,无法删除"),
PAGE_RELEASE_ERROR(61004,"页面已发布,无需重复提交"),
/**
* 设置
*/
SETTING_NOT_TO_SET(70001,"该参数不需要设置"),
/**
* 短信
*/
SMS_SIGN_EXIST_ERROR(80001,"短信签名已存在"),
/**
* 站内信
*/
NOTICE_NOT_EXIST(80101,"当前消息模板不存在"),
/**
* OSS
*/
OSS_NOT_EXIST(80201,"OSS未配置"),
/**
* 验证码
*/
VERIFICATION_SEND_SUCCESS(80301,"短信验证码,发送成功"),
VERIFICATION_ERROR(80302,"验证失败"),
VERIFICATION_SMS_ERROR(80303,"短信验证码错误,请重新校验"),
VERIFICATION_SMS_EXPIRED_ERROR(80304,"验证码已失效,请重新校验")
;
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
}

View File

@@ -0,0 +1,129 @@
package cn.lili.common.enums;
/**
* 日志枚举
*
* @author Chopper
*/
public enum StoreLogType {
/**
* 默认0操作
*/
OPERATION,
/**
* 1登录
*/
LOGIN,
/**
* 商品
*/
//商品上架
//商品下架
//发布商品
//修改商品
//删除商品
//添加商品模板
//编辑商品库存
//增加店铺商品分类
//编辑店铺商品分类
/**
* 订单
*/
//发货
//修改收件信息
/**
* 售后
*/
//审核退货单
//确定收货
//拒绝收货
//退款
//拒绝退款
/**
* 营销
*/
//添加分销商品
//下架分销商品
//编辑分销商品
//创建满减活动
//编辑满减活动
//下架满减活动
//上架满减活动
//删除满减活动
//添加优惠券
//编辑优惠券
//关闭优惠券
//参与秒杀活动
//编辑秒杀活动
//下架秒杀活动
//上架秒杀活动
//删除秒杀活动
/**
* 设置
*/
//选择物流公司
//取消物流公司
//添加运费模板
//编辑运费模板
//删除运费模板
//设置店铺信息
//修改店铺退货地址
//新增自提点
//编辑自提点
//删除自提点
//发布店铺首页
}

View File

@@ -0,0 +1,25 @@
package cn.lili.common.enums;
/**
* 开关枚举
* @author Chopper
*/
public enum SwitchEnum {
/**
* 开关
*/
OPEN("开启"), CLOSE("关闭");
private String description;
SwitchEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,109 @@
package cn.lili.common.exception;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.utils.ResultUtil;
import cn.lili.common.vo.ResultMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 全局异常异常处理
*
* @author Chopper
*/
@RestControllerAdvice
@Slf4j
public class GlobalControllerExceptionHandler {
/**
* 如果超过长度,则前后段交互体验不佳,使用默认错误消息
*/
static Integer MAX_LENGTH = 200;
/**
* 自定义异常
*
* @param e
* @return
*/
@ExceptionHandler(ServiceException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ResultMessage<Object> handleServiceException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
log.error("全局异常[ServiceException]:", e);
//如果是自定义异常,则获取异常,返回自定义错误消息
if (e instanceof ServiceException) {
ResultCode resultCode=((ServiceException) e).getResultCode();
if (resultCode != null) {
return ResultUtil.error(resultCode.code(), resultCode.message());
}
}
//默认错误消息
String errorMsg = "服务器异常,请稍后重试";
if (e != null && e.getMessage() != null && e.getMessage().length() < MAX_LENGTH) {
errorMsg = e.getMessage();
}
return ResultUtil.error(400, errorMsg);
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ResultMessage<Object> runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
log.error("全局异常[RuntimeException]:", e);
return ResultUtil.error(400, "服务器异常,请稍后重试");
}
// /**
// * 通用的接口映射异常处理方
// */
// @Override
// protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
// if (ex instanceof MethodArgumentNotValidException) {
// MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg(exception.getBindingResult().getAllErrors().get(0).getDefaultMessage()), status);
// }
// if (ex instanceof MethodArgumentTypeMismatchException) {
// MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
// logger.error("参数转换失败,方法:" + exception.getParameter().getMethod().getName() + ",参数:" + exception.getName()
// + ",信息:" + exception.getLocalizedMessage());
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("参数转换失败"), status);
// }
// ex.printStackTrace();
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("未知异常,请联系管理员"), status);
// }
/**
* bean校验未通过异常
*
* @see javax.validation.Valid
* @see org.springframework.validation.Validator
* @see org.springframework.validation.DataBinder
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResultMessage<Object> validExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
BindException exception = (BindException) e;
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
for (FieldError error : fieldErrors) {
return ResultUtil.error(400,error.getDefaultMessage());
}
return ResultUtil.error(ResultCode.ERROR);
}
}

View File

@@ -0,0 +1,30 @@
package cn.lili.common.exception;
import cn.lili.common.enums.ResultCode;
import lombok.Data;
/**
* @author Chopper
*/
@Data
public class ServiceException extends RuntimeException {
private String msg;
private ResultCode resultCode;
public ServiceException(String msg) {
super(msg);
this.msg = msg;
}
public ServiceException() {
super("网络错误,请稍后重试!");
this.msg = "网络错误,请稍后重试!";
}
public ServiceException(ResultCode resultCode) {
this.resultCode = resultCode;
}
}

View File

@@ -0,0 +1,43 @@
package cn.lili.common.heath;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.Map;
/**
* 数据库检验工具
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/12/24 19:31
*/
@Configuration
public class DataSourceHealthConfig extends DataSourceHealthContributorAutoConfiguration {
@Value("${spring.datasource.dbcp2.validation-query:select 1}")
private String defaultQuery;
public DataSourceHealthConfig(Map<String, DataSource> dataSources, ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
super(dataSources, metadataProviders);
}
@Override
protected AbstractHealthIndicator createIndicator(DataSource source) {
DataSourceHealthIndicator indicator = (DataSourceHealthIndicator) super.createIndicator(source);
if (!StringUtils.hasText(indicator.getQuery())) {
indicator.setQuery(defaultQuery);
}
return indicator;
}
}

View File

@@ -0,0 +1,23 @@
package cn.lili.common.rocketmq;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
/**
* @author paulG
* @since 2020/11/4
**/
@Slf4j
public class RocketmqSendCallback implements SendCallback {
@Override
public void onSuccess(SendResult sendResult) {
log.info("async onSuccess SendResult={}", sendResult);
}
@Override
public void onException(Throwable throwable) {
log.error("async onException Throwable", throwable);
}
}

View File

@@ -0,0 +1,14 @@
package cn.lili.common.rocketmq;
/**
* @author paulG
* @since 2020/11/4
**/
public class RocketmqSendCallbackBuilder {
public static RocketmqSendCallback commonCallback() {
return new RocketmqSendCallback();
}
}

View File

@@ -0,0 +1,23 @@
package cn.lili.common.rocketmq.tags;
/**
* @author paulG
* @since 2020/12/9
**/
public enum AfterSaleTagsEnum {
REFUND("售后退款"),
AFTER_SALE_STATUS_CHANGE("售后单状态改变");
private final String description;
AfterSaleTagsEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,29 @@
package cn.lili.common.rocketmq.tags;
/**
* @author paulG
* @since 2020/12/9
**/
public enum GoodsTagsEnum {
GENERATOR_GOODS_INDEX("生成商品索引"),
GOODS_DELETE("删除商品"),
GOODS_AUDIT("审核商品"),
GOODS_COLLECTION("收藏商品"),
BUY_GOODS_COMPLETE("购买商品完成"),
SKU_DELETE("删除商品SKU"),
VIEW_GOODS("查看商品"),
GOODS_COMMENT_COMPLETE("商品评价");
private final String description;
GoodsTagsEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,25 @@
package cn.lili.common.rocketmq.tags;
/**
* @author paulG
* @since 2020/12/9
**/
public enum MemberTagsEnum {
MEMBER_REGISTER("会员注册"),
MEMBER_SING("会员签到"),
MEMBER_WITHDRAWAL("会员提现"),
MEMBER_POINT_CHANGE("会员积分变动");
private final String description;
MemberTagsEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,27 @@
package cn.lili.common.rocketmq.tags;
/**
* @author paulG
* @since 2020/12/9
**/
public enum MqOrderTagsEnum {
ORDER_CREATE("订单创建"),
STATUS_CHANGE("订单状态改变");
private final String description;
MqOrderTagsEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,23 @@
package cn.lili.common.rocketmq.tags;
/**
* @author paulG
* @since 2020/12/9
**/
public enum OtherTagsEnum {
MESSAGE("站内消息提醒"),
SMS("短信消息提醒");
private final String description;
OtherTagsEnum(String description) {
this.description = description;
}
public String description() {
return description;
}
}

View File

@@ -0,0 +1,73 @@
package cn.lili.common.security;
import cn.lili.common.security.enums.UserEnums;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* @author Chopper
*/
@Data
@AllArgsConstructor
public class AuthUser implements Serializable {
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickName;
/**
* id
*/
private String id;
/**
* 长期有效用于手机app登录场景或者信任场景等
*/
private Boolean longTerm = false;
/**
* @see UserEnums
* 角色
*/
private UserEnums role;
/**
* 如果角色是商家则存在此店铺id字段
* storeId
*/
private String storeId;
/**
* 如果角色是商家,则存在此店铺名称字段
* storeName
*/
private String storeName;
/**
* 是否是超级管理员
*/
private Boolean isSuper = false;
public AuthUser(String username, String id, String nickName, UserEnums role) {
this.username = username;
this.id = id;
this.role = role;
this.nickName = nickName;
}
public AuthUser(String username, String id, UserEnums manager, String nickName, Boolean isSuper) {
this.username = username;
this.id = id;
this.role = manager;
this.isSuper = isSuper;
this.nickName = nickName;
}
}

View File

@@ -0,0 +1,27 @@
package cn.lili.common.security;
import cn.lili.common.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义 拒绝访问响应
*
* @author Chopper
*/
@Component
@Slf4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
ResponseUtil.output(response, ResponseUtil.resultMap(false, 401, "抱歉,您没有访问权限"));
}
}

View File

@@ -0,0 +1,42 @@
package cn.lili.common.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* SecurityBean
*
* @author Chopper
* @version v1.0
* 2020-11-14 15:03
*/
@Configuration
public class SecurityBean {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
/**
* 定义跨域配置
*
* @return bean
*/
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(CorsConfiguration.ALL);
config.addAllowedHeader(CorsConfiguration.ALL);
config.addAllowedMethod(CorsConfiguration.ALL);
source.registerCorsConfiguration("/**", config);
return source;
}
}

View File

@@ -0,0 +1,36 @@
package cn.lili.common.security.context;
import cn.lili.common.security.AuthUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
/**
* 获取用户信息 处理
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/14 20:35
*/
@Component
public class AuthenticationHandler {
/**
* 获取当前用户信息
*
* @return
*/
public AuthUser getAuthUser() {
//获取spring security 权限信息如果token有权限在这里就会得到内容
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
Object object = authentication.getDetails();
if (object instanceof AuthUser) {
return (AuthUser) authentication.getDetails();
}
return null;
}
}

View File

@@ -0,0 +1,60 @@
package cn.lili.common.security.context;
import cn.lili.common.cache.Cache;
import cn.lili.common.enums.MessageCode;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.SecurityEnum;
import cn.lili.common.token.SecretKeyUtil;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
/**
* 用户上下文
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/14 20:27
*/
public class UserContext {
private static AuthenticationHandler authenticationHandler;
public static void setHolder(AuthenticationHandler authenticationHandler) {
UserContext.authenticationHandler = authenticationHandler;
}
public static AuthUser getCurrentUser() {
return authenticationHandler.getAuthUser();
}
/**
* 根据jwt获取token重的用户信息
*
* @param cache 缓存
* @param accessToken token
* @return
*/
public static AuthUser getAuthUser(Cache cache, String accessToken) {
try {
if (cache.keys("*" + accessToken).size() == 0) {
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
}
//获取token的信息
Claims claims
= Jwts.parser()
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
.parseClaimsJws(accessToken).getBody();
//获取存储在claims中的用户信息
String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
return new Gson().fromJson(json, AuthUser.class);
} catch (Exception e) {
return null;
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.lili.common.security.context;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 给予用户上下文,初始化参数
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/14 20:30
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserContextInit implements ApplicationRunner {
/**
* 用户信息holder认证信息的获取者
*/
private final AuthenticationHandler authenticationHandler;
/**
* 在项目加载时指定认证信息获取者
* 默认是由spring 安全上下文中获取
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) {
UserContext.setHolder(authenticationHandler);
}
}

View File

@@ -0,0 +1,24 @@
package cn.lili.common.security.enums;
/**
* 安全相关常量
*
* @author Chopper
*/
public enum SecurityEnum {
/**
* 存在与header中的token参数头 名
*/
HEADER_TOKEN("accessToken"), USER_CONTEXT("userContext"), JWT_SECRET("secret");
String value;
SecurityEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,28 @@
package cn.lili.common.security.enums;
/**
* token角色类型
*
* @author Chopper
* @version v1.0
* @Description:
* @since 2020/8/18 15:23
*/
public enum UserEnums {
/**
* 角色
*/
MEMBER("会员"),
STORE("商家"),
MANAGER("管理员"),
SYSTEM("系统");
private final String role;
UserEnums(String role) {
this.role = role;
}
public String getRole() {
return role;
}
}

View File

@@ -0,0 +1,76 @@
package cn.lili.common.sms;
import cn.lili.modules.message.entity.dos.SmsSign;
import cn.lili.modules.message.entity.dos.SmsTemplate;
import java.util.Map;
/**
* @author Chopper
* @version v4.1
* @Description:
* @since 2021/2/1 6:05 下午
*/
public interface AliSmsUtil {
/**
* 申请短信签名
*
* @param smsSign 短信签名
*/
void addSmsSign(SmsSign smsSign) throws Exception;
/**
* 删除短信签名
*
* @param signName 签名名称
*/
void deleteSmsSign(String signName) throws Exception;
/**
* 查询短信签名申请状态
*
* @param signName 签名名称
*/
Map<String, Object> querySmsSign(String signName) throws Exception;
/**
* 修改未审核通过的短信签名,并重新提交审核。
*
* @param smsSign 短信签名
*/
void modifySmsSign(SmsSign smsSign) throws Exception;
/**
* 修改未审核通过的短信模板,并重新提交审核。
*
* @param smsTemplate 短信模板
* @throws Exception
*/
void modifySmsTemplate(SmsTemplate smsTemplate) throws Exception;
/**
* 查看短信模板
*
* @param templateCode 短信模板CODE
* @throws Exception
*/
Map<String, Object> querySmsTemplate(String templateCode) throws Exception;
/**
* 申请短信模板
*
* @param smsTemplate 短信模板
* @return
* @throws Exception
*/
String addSmsTemplate(SmsTemplate smsTemplate) throws Exception;
/**
* 删除短信模板
*
* @param templateCode 短信模板CODE
* @throws Exception
*/
void deleteSmsTemplate(String templateCode) throws Exception;
}

View File

@@ -0,0 +1,57 @@
package cn.lili.common.sms;
import cn.lili.common.verification.enums.VerificationEnums;
import java.util.List;
import java.util.Map;
/**
* 短信接口
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/30 15:44
*/
public interface SmsUtil {
/**
* 验证码发送
*
* @param mobile 手机号
* @param verificationEnums 验证码场景
* @param uuid 用户标识uuid
*/
void sendSmsCode(String mobile, VerificationEnums verificationEnums, String uuid);
/**
* 验证码验证
*
* @param mobile 手机号
* @param verificationEnums 验证码场景
* @param uuid 用户标识uuid
* @param code 待验证code
*/
boolean verifyCode(String mobile, VerificationEnums verificationEnums, String uuid, String code);
/**
* 短信发送
*
* @param mobile 接收手机号
* @param param 参数
* @param templateCode 模版code
*/
void sendSmsCode(String signName, String mobile, Map<String, String> param, String templateCode);
/**
* 短信批量发送
*
* @param mobile 接收手机号
* @param signName 签名
* @param templateCode 模版code
*/
void sendBatchSms(String signName, List<String> mobile, String templateCode);
}

View File

@@ -0,0 +1,343 @@
package cn.lili.common.sms.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.lili.common.cache.Cache;
import cn.lili.common.cache.CachePrefix;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.sms.AliSmsUtil;
import cn.lili.common.sms.SmsUtil;
import cn.lili.common.utils.CommonUtil;
import cn.lili.common.verification.enums.VerificationEnums;
import cn.lili.modules.message.entity.dos.SmsSign;
import cn.lili.modules.message.entity.dos.SmsTemplate;
import cn.lili.modules.connect.util.Base64Utils;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.SmsSetting;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.system.service.SettingService;
import com.aliyun.dysmsapi20170525.models.*;
import com.aliyun.teaopenapi.models.Config;
import com.google.gson.Gson;
import com.xkcoding.http.util.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 短信网管阿里云实现
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/30 15:44
*/
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SmsUtilAliImplService implements SmsUtil, AliSmsUtil {
private final Cache cache;
private final SettingService settingService;
private final MemberService memberService;
@Override
public void sendSmsCode(String mobile, VerificationEnums verificationEnums, String uuid) {
String code = CommonUtil.getRandomNum();
code = "111111";
switch (verificationEnums) {
//如果某个模版需要自定义,则在此处进行调整
case LOGIN:
case REGISTER:
case FIND_USER: {
//准备发送短信参数
Map<String, String> params = new HashMap<>();
params.put("code", code);
cache.put(cacheKey(verificationEnums, mobile, uuid), code, 300L);
//this.sendSmsCode("北京宏业汇成科技有限公司",mobile, params, verificationEnums.getSmsTemplate());
break;
}
case UPDATE_PASSWORD: {
Member member = memberService.getById(UserContext.getCurrentUser().getId());
if (member == null || StringUtil.isEmpty(member.getMobile())) {
return;
}
String memberMobile = member.getMobile();
//准备发送短信参数
Map<String, String> params = new HashMap<>();
params.put("code", code);
cache.put(cacheKey(verificationEnums, memberMobile, uuid), code, 300L);
//this.sendSmsCode("北京宏业汇成科技有限公司",mobile, params, verificationEnums.getSmsTemplate());
break;
}
//如果不是有效的验证码手段,则此处不进行短信操作
default:
return;
}
}
@Override
public boolean verifyCode(String mobile, VerificationEnums verificationEnums, String uuid, String code) {
Object result = cache.get(cacheKey(verificationEnums, mobile, uuid));
if (code.equals(result)) {
//校验之后,删除
cache.remove(cacheKey(verificationEnums, mobile, uuid));
return true;
} else {
return false;
}
}
@Override
public void sendSmsCode(String signName, String mobile, Map<String, String> param, String templateCode) {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName(signName)
.setPhoneNumbers(mobile)
.setTemplateCode(templateCode)
.setTemplateParam(JSONUtil.toJsonStr(param));
try {
SendSmsResponse response = client.sendSms(sendSmsRequest);
System.out.println(response.getBody().getCode());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void sendBatchSms(String signName, List<String> mobile, String templateCode) {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
List<String> sign = mobile;
sign.replaceAll(e -> signName);
//手机号拆成多个小组进行发送
List<List<String>> mobileList = new ArrayList<>();
//签名名称多个小组
List<List<String>> signNameList = new ArrayList<>();
//循环分组
for (int i = 0; i < (mobile.size() / 100 + (mobile.size() % 100 == 0 ? 0 : 1)); i++) {
int endPoint = Math.min((100 + (i * 100)), mobile.size());
mobileList.add(mobile.subList((i * 100), endPoint));
signNameList.add(sign.subList((i * 100), endPoint));
}
//发送短信
for (int i = 0; i < mobileList.size(); i++) {
SendBatchSmsRequest sendBatchSmsRequest = new SendBatchSmsRequest()
.setPhoneNumberJson(JSONUtil.toJsonStr(mobileList.get(i)))
.setSignNameJson(JSONUtil.toJsonStr(signNameList.get(i)))
.setTemplateCode(templateCode);
try {
client.sendBatchSms(sendBatchSmsRequest);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void addSmsSign(SmsSign smsSign) throws Exception {
//设置参数添加短信签名
com.aliyun.dysmsapi20170525.Client client = this.createClient();
AddSmsSignRequest.AddSmsSignRequestSignFileList signFileList0 = new AddSmsSignRequest.AddSmsSignRequestSignFileList()
.setFileContents(Base64Utils.encode(smsSign.getBusinessLicense()))
.setFileSuffix(smsSign.getBusinessLicense().substring(smsSign.getBusinessLicense().lastIndexOf(".") + 1));
AddSmsSignRequest.AddSmsSignRequestSignFileList signFileList1 = new AddSmsSignRequest.AddSmsSignRequestSignFileList()
.setFileContents(Base64Utils.encode(smsSign.getLicense()))
.setFileSuffix(smsSign.getLicense().substring(smsSign.getBusinessLicense().lastIndexOf(".") + 1));
AddSmsSignRequest addSmsSignRequest = new AddSmsSignRequest()
.setSignName(smsSign.getSignName())
.setSignSource(smsSign.getSignSource())
.setRemark(smsSign.getRemark())
.setSignFileList(java.util.Arrays.asList(
signFileList0,
signFileList1
));
AddSmsSignResponse response = client.addSmsSign(addSmsSignRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
}
@Override
public void deleteSmsSign(String signName) throws Exception {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
DeleteSmsSignRequest deleteSmsSignRequest = new DeleteSmsSignRequest()
.setSignName(signName);
DeleteSmsSignResponse response = client.deleteSmsSign(deleteSmsSignRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
}
@Override
public Map<String, Object> querySmsSign(String signName) throws Exception {
//设置参数查看短信签名
com.aliyun.dysmsapi20170525.Client client = this.createClient();
QuerySmsSignRequest querySmsSignRequest = new QuerySmsSignRequest().setSignName(signName);
QuerySmsSignResponse response = client.querySmsSign(querySmsSignRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
Map<String, Object> map = new HashMap<>();
map.put("SignStatus", response.getBody().getSignStatus());
map.put("Reason", response.getBody().getReason());
return map;
}
@Override
public void modifySmsSign(SmsSign smsSign) throws Exception {
//设置参数添加短信签名
com.aliyun.dysmsapi20170525.Client client = this.createClient();
ModifySmsSignRequest.ModifySmsSignRequestSignFileList signFileList0 = new ModifySmsSignRequest.ModifySmsSignRequestSignFileList()
.setFileContents(Base64Utils.encode(smsSign.getBusinessLicense()))
.setFileSuffix(smsSign.getBusinessLicense().substring(smsSign.getBusinessLicense().lastIndexOf(".") + 1));
ModifySmsSignRequest.ModifySmsSignRequestSignFileList signFileList1 = new ModifySmsSignRequest.ModifySmsSignRequestSignFileList()
.setFileContents(Base64Utils.encode(smsSign.getLicense()))
.setFileSuffix(smsSign.getLicense().substring(smsSign.getBusinessLicense().lastIndexOf(".") + 1));
ModifySmsSignRequest modifySmsSign = new ModifySmsSignRequest()
.setSignName(smsSign.getSignName())
.setSignSource(smsSign.getSignSource())
.setRemark(smsSign.getRemark())
.setSignFileList(java.util.Arrays.asList(
signFileList0,
signFileList1
));
ModifySmsSignResponse response = client.modifySmsSign(modifySmsSign);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
}
@Override
public void modifySmsTemplate(SmsTemplate smsTemplate) throws Exception {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
ModifySmsTemplateRequest modifySmsTemplateRequest = new ModifySmsTemplateRequest()
.setTemplateType(smsTemplate.getTemplateType())
.setTemplateName(smsTemplate.getTemplateName())
.setTemplateContent(smsTemplate.getTemplateContent())
.setRemark(smsTemplate.getRemark())
.setTemplateCode(smsTemplate.getTemplateCode());
ModifySmsTemplateResponse response = client.modifySmsTemplate(modifySmsTemplateRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
}
@Override
public Map<String, Object> querySmsTemplate(String templateCode) throws Exception {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
QuerySmsTemplateRequest querySmsTemplateRequest = new QuerySmsTemplateRequest()
.setTemplateCode(templateCode);
QuerySmsTemplateResponse response = client.querySmsTemplate(querySmsTemplateRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
Map<String, Object> map = new HashMap<>();
map.put("TemplateStatus", response.getBody().getTemplateStatus());
map.put("Reason", response.getBody().getReason());
map.put("TemplateCode", response.getBody().getTemplateCode());
return map;
}
@Override
public String addSmsTemplate(SmsTemplate smsTemplate) throws Exception {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
AddSmsTemplateRequest addSmsTemplateRequest = new AddSmsTemplateRequest()
.setTemplateType(1)
.setTemplateName(smsTemplate.getTemplateName())
.setTemplateContent(smsTemplate.getTemplateContent())
.setRemark(smsTemplate.getRemark());
AddSmsTemplateResponse response = client.addSmsTemplate(addSmsTemplateRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
return response.getBody().getTemplateCode();
}
@Override
public void deleteSmsTemplate(String templateCode) throws Exception {
com.aliyun.dysmsapi20170525.Client client = this.createClient();
DeleteSmsTemplateRequest deleteSmsTemplateRequest = new DeleteSmsTemplateRequest()
.setTemplateCode(templateCode);
DeleteSmsTemplateResponse response = client.deleteSmsTemplate(deleteSmsTemplateRequest);
if (!response.getBody().getCode().equals("OK")) {
throw new ServiceException(response.getBody().getMessage());
}
}
/**
* 使用AK&SK初始化账号Client
*
* @return Client
* @throws Exception
*/
public com.aliyun.dysmsapi20170525.Client createClient() {
try {
Setting setting = settingService.getById(SettingEnum.SMS_SETTING.name());
if (StrUtil.isBlank(setting.getSettingValue())) {
throw new ServiceException("您还未配置阿里云短信");
}
SmsSetting smsSetting = new Gson().fromJson(setting.getSettingValue(), SmsSetting.class);
Config config = new Config();
// 您的AccessKey ID
//config.accessKeyId = smsSetting.getAccessKeyId();
config.accessKeyId = "LTAI4G4deX59EyjpEULaJdsU";
// 您的AccessKey Secret
//config.accessKeySecret = smsSetting.getAccessSecret();
config.accessKeySecret = "BlRBpl7WBman6GYYwLKMiKqMTXFhWf";
// 访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
return new com.aliyun.dysmsapi20170525.Client(config);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成缓存key
*
* @param verificationEnums 验证场景
* @param mobile 手机号码
* @param uuid 用户标识 uuid
* @return
*/
static String cacheKey(VerificationEnums verificationEnums, String mobile, String uuid) {
return CachePrefix.SMS_CODE.getPrefix() + verificationEnums.name() + mobile;
}
}

View File

@@ -0,0 +1,36 @@
package cn.lili.common.test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* BaseTest
*
* @author Chopper
* @version v1.0
* @since
* 2020-06-13 12:17
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional(rollbackFor = Exception.class)
@Rollback()
@ContextConfiguration
@Configuration
@ComponentScan("cn.lili")
public class BaseTest implements TestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
//设置环境变量 解决es冲突
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
}

View File

@@ -0,0 +1,19 @@
package cn.lili.common.token;
/**
* 权限枚举值
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/25 09:21
*/
public enum PermissionEnum {
/**
* 超级权限,查看权限
*/
SUPER, QUERY
}

View File

@@ -0,0 +1,27 @@
package cn.lili.common.token;
import com.google.api.client.repackaged.org.apache.commons.codec.binary.Base64;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
/**
* SignWithUtil
*
* @author Chopper
* @version v1.0
* 2020-11-18 17:30
*/
public class SecretKeyUtil {
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decodeBase64("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=");//自定义
javax.crypto.SecretKey key = Keys.hmacShaKeyFor(encodedKey);
return key;
}
public static SecretKey generalKeyByDecoders() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="));
}
}

View File

@@ -0,0 +1,24 @@
package cn.lili.common.token;
import lombok.Data;
/**
* Token 实体类
*
* @author Chopper
* @version v1.0
* 2020-11-13 10:02
*/
@Data
public class Token {
/**
* 访问token
*/
private String accessToken;
/**
* 刷新token
*/
private String refreshToken;
}

View File

@@ -0,0 +1,138 @@
package cn.lili.common.token;
import cn.lili.common.cache.Cache;
import cn.lili.common.cache.CachePrefix;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.SecurityEnum;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.config.properties.JWTTokenProperties;
import com.google.gson.Gson;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* TokenUtil
*
* @author Chopper
* @version v1.0
* 2020-11-12 18:44
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TokenUtil {
private final JWTTokenProperties tokenProperties;
private final Cache cache;
/**
* 构建token
*
* @param username 主体
* @param claim 私有声明
* @param longTerm 长时间特殊token 如:移动端,微信小程序等
* @return
*/
public Token createToken(String username, Object claim, boolean longTerm, UserEnums userEnums) {
Token token = new Token();
//访问token
String accessToken = createToken(username, claim, tokenProperties.getTokenExpireTime());
cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1,
tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
//刷新token生成策略如果是长时间有效的token用于app则默认15天有效期刷新token。如果是普通用户登录则刷新token为普通token2倍数
Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 2;
String refreshToken = createToken(username, claim, expireTime);
cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expireTime, TimeUnit.MINUTES);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
return token;
}
/**
* 刷新token
*
* @param oldRefreshToken 刷新token
* @return token
*/
public Token refreshToken(String oldRefreshToken, UserEnums userEnums) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
.parseClaimsJws(oldRefreshToken).getBody();
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
//token 过期 认证失败等
throw new ServiceException(ResultCode.USER_AUTH_EXPIRED);
}
//获取存储在claims中的用户信息
String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
AuthUser authUser = new Gson().fromJson(json, AuthUser.class);
String username = authUser.getUsername();
//获取是否长期有效的token
boolean longTerm = authUser.getLongTerm();
//如果缓存中有刷新token &&
if (cache.hasKey(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken)) {
Token token = new Token();
//访问token
String accessToken = createToken(username, authUser, tokenProperties.getTokenExpireTime());
cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
//如果是信任登录设备则刷新token长度继续延长
Long expirationTime = tokenProperties.getTokenExpireTime() * 2;
if (longTerm) {
expirationTime = 60 * 24 * 15L;
}
//刷新token生成策略如果是长时间有效的token用于app则默认15天有效期刷新token。如果是普通用户登录则刷新token为普通token2倍数
String refreshToken = createToken(username, authUser, expirationTime);
cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expirationTime, TimeUnit.MINUTES);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
cache.remove(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken);
return token;
} else {
throw new ServiceException(ResultCode.USER_AUTH_EXPIRED);
}
}
/**
* 生成token
*
* @param username 主体
* @param claim 私有神明内容
* @param expirationTime 过期时间(分钟)
* @return
*/
private String createToken(String username, Object claim, Long expirationTime) {
// JWT 生成
return Jwts.builder()
// jwt 私有声明
.claim(SecurityEnum.USER_CONTEXT.getValue(), new Gson().toJson(claim))
// JWT的主体
.setSubject(username)
// 失效时间 当前时间+过期分钟
.setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000))
// 签名算法和密钥
.signWith(SecretKeyUtil.generalKey())
.compact();
}
}

View File

@@ -0,0 +1,38 @@
package cn.lili.common.token.base;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.token.Token;
/**
* AbstractToken
* 抽象token定义生成token类
*
* @author Chopper
* @version v1.0
* 2020-11-13 10:13
*/
public abstract class AbstractTokenGenerate {
/**
* 生成token
*
* @param username 用户名
* @param longTerm 是否长时间有效
* @return
*/
public abstract Token createToken(String username, Boolean longTerm);
/**
* 刷新token
*
* @param refreshToken 刷新token
* @return token
*/
public abstract Token refreshToken(String refreshToken);
/**
* 默认role
*/
public UserEnums role = UserEnums.MANAGER;
}

View File

@@ -0,0 +1,132 @@
package cn.lili.common.token.base.generate;
import cn.lili.common.cache.Cache;
import cn.lili.common.cache.CachePrefix;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.token.PermissionEnum;
import cn.lili.common.token.Token;
import cn.lili.common.token.TokenUtil;
import cn.lili.common.token.base.AbstractTokenGenerate;
import cn.lili.modules.permission.entity.dos.AdminUser;
import cn.lili.modules.permission.entity.vo.UserMenuVO;
import cn.lili.modules.permission.service.AdminUserService;
import cn.lili.modules.permission.service.RoleMenuService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 管理员token生成
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/16 10:51
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ManagerTokenGenerate extends AbstractTokenGenerate {
private AdminUserService adminUserService;
private final TokenUtil tokenUtil;
private final RoleMenuService roleMenuService;
private final Cache cache;
@Autowired
public void setAdminUserService(AdminUserService adminUserService) {
this.adminUserService = adminUserService;
}
@Override
public Token createToken(String username, Boolean longTerm) {
// 生成token
AdminUser adminUser = adminUserService.findByUsername(username);
AuthUser user = new AuthUser(adminUser.getUsername(), adminUser.getId(), UserEnums.MANAGER, adminUser.getNickName(), adminUser.getIsSuper());
List<UserMenuVO> userMenuVOList = roleMenuService.findAllMenu(user.getId());
//缓存权限列表
cache.put(CachePrefix.PERMISSION_LIST.getPrefix(UserEnums.MANAGER) + user.getId(), this.permissionList(userMenuVOList));
return tokenUtil.createToken(username, user, longTerm, UserEnums.MANAGER);
}
@Override
public Token refreshToken(String refreshToken) {
return tokenUtil.refreshToken(refreshToken, UserEnums.MANAGER);
}
/**
* 获取用户权限
*
* @param userMenuVOList
* @return
*/
private Map<String, List<String>> permissionList(List<UserMenuVO> userMenuVOList) {
Map<String, List<String>> permission = new HashMap<>();
if (userMenuVOList == null || userMenuVOList.size() == 0) {
return permission;
}
List<String> superPermissions = new ArrayList<>();
List<String> queryPermissions = new ArrayList<>();
initPermission(superPermissions, queryPermissions);
//循环权限菜单
userMenuVOList.forEach(menu -> {
//循环菜单,赋予用户权限
if (menu.getPath() != null) {
//获取路径集合
String[] paths = menu.getPath().split("\\|");
//for循环路径集合
for (String path : paths) {
//如果是超级权限 则计入超级权限
if (menu.getIsSupper()) {
//如果已有超级权限,则这里就不做权限的累加
if (!superPermissions.contains(path)) {
superPermissions.add(path);
}
}
//否则计入浏览权限
else {
//如果已有超级权限,或者已有普通查看权限,则这里就不做权限的累加
if (!superPermissions.contains(path) && !queryPermissions.contains(path)) {
queryPermissions.add(path);
}
}
}
}
//去除无效的权限
superPermissions.forEach(queryPermissions::remove);
});
permission.put(PermissionEnum.SUPER.name(), superPermissions);
permission.put(PermissionEnum.QUERY.name(), queryPermissions);
return permission;
}
/**
* 初始赋予的权限,查看权限包含首页流量统计权限,
* 超级权限包含个人信息维护,密码修改权限
*
* @param superPermissions 超级权限
* @param queryPermissions 查询权限
*/
void initPermission(List<String> superPermissions, List<String> queryPermissions) {
//用户信息维护
superPermissions.add("/manager/user/info");
superPermissions.add("/manager/user/edit");
superPermissions.add("/manager/user/editPassword*");
//统计查看
queryPermissions.add("/manager/statistics*");
}
}

View File

@@ -0,0 +1,74 @@
package cn.lili.common.token.base.generate;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.token.Token;
import cn.lili.common.token.TokenUtil;
import cn.lili.common.token.base.AbstractTokenGenerate;
import cn.lili.config.context.ThreadContextHolder;
import cn.lili.modules.base.entity.enums.ClientTypeEnum;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 会员token生成
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/16 10:50
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MemberTokenGenerate extends AbstractTokenGenerate {
private MemberService memberService;
private final TokenUtil tokenUtil;
@Override
public Token createToken(String username, Boolean longTerm) {
Member member = memberService.findByUsername(username);
//获取客户端类型
String clientType = ThreadContextHolder.getHttpRequest().getHeader("clientType");
ClientTypeEnum clientTypeEnum;
try {
//如果客户端为空则缺省值为PCpc第三方登录时不会传递此参数
if (clientType == null) {
clientTypeEnum = ClientTypeEnum.PC;
} else {
clientTypeEnum = ClientTypeEnum.valueOf(clientType);
}
} catch (IllegalArgumentException e) {
clientTypeEnum = ClientTypeEnum.UNKNOWN;
}
//记录最后登录时间,客户端类型
member.setLastLoginDate(new Date());
member.setClientEnum(clientTypeEnum.name());
memberService.updateById(member);
AuthUser authUser = new AuthUser(member.getUsername(), member.getId(),member.getNickName(), UserEnums.MEMBER);
// 登陆成功生成token
return tokenUtil.createToken(username, authUser, longTerm, UserEnums.MEMBER);
}
@Override
public Token refreshToken(String refreshToken) {
return tokenUtil.refreshToken(refreshToken, UserEnums.MEMBER);
}
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
}

View File

@@ -0,0 +1,68 @@
package cn.lili.common.token.base.generate;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.token.Token;
import cn.lili.common.token.TokenUtil;
import cn.lili.common.token.base.AbstractTokenGenerate;
import cn.lili.common.enums.SwitchEnum;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.store.entity.dos.Store;
import cn.lili.modules.store.service.StoreService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 商家token生成
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/16 10:51
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class StoreTokenGenerate extends AbstractTokenGenerate {
private MemberService memberService;
private StoreService storeService;
private final TokenUtil tokenUtil;
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
@Autowired
public void setStoreService(StoreService storeService) {
this.storeService = storeService;
}
@Override
public Token createToken(String username, Boolean longTerm) {
// 生成token
Member member = memberService.findByUsername(username);
if (member.getHaveStore().equals(SwitchEnum.CLOSE.name())) {
throw new ServiceException("该会员未开通店铺");
}
AuthUser user = new AuthUser(member.getUsername(), member.getId(),member.getNickName(), UserEnums.STORE);
LambdaQueryWrapper<Store> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Store::getMemberId, member.getId());
Store store = storeService.getOne(queryWrapper);
user.setStoreId(store.getId());
user.setStoreName(store.getStoreName());
return tokenUtil.createToken(username, user, longTerm, UserEnums.STORE);
}
@Override
public Token refreshToken(String refreshToken) {
return tokenUtil.refreshToken(refreshToken, UserEnums.STORE);
}
}

View File

@@ -0,0 +1,83 @@
package cn.lili.common.trigger;
import cn.hutool.json.JSONUtil;
import cn.lili.common.cache.Cache;
import cn.lili.common.rocketmq.RocketmqSendCallbackBuilder;
import cn.lili.common.trigger.delay.PromotionDelayQueue;
import cn.lili.common.trigger.interfaces.TimeTrigger;
import cn.lili.common.trigger.model.TimeTriggerMsg;
import cn.lili.common.trigger.util.TimeTriggerUtil;
import cn.lili.common.utils.DateUtil;
import cn.lili.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* @author paulG
* @since 2020/11/5
**/
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RocketmqTimerTrigger implements TimeTrigger {
private final RocketMQTemplate rocketMQTemplate;
private final Cache<Integer> cache;
private PromotionDelayQueue promotionDelayQueue;
@Autowired
public void setPromotionDelayQueue(PromotionDelayQueue promotionDelayQueue) {
this.promotionDelayQueue = promotionDelayQueue;
}
@Override
public void add(String executorName, Object param, Long triggerTime, String uniqueKey, String topic) {
TimeTriggerMsg timeTriggerMsg = new TimeTriggerMsg(executorName, triggerTime, param, uniqueKey, topic);
Message<TimeTriggerMsg> message = MessageBuilder.withPayload(timeTriggerMsg).build();
this.rocketMQTemplate.asyncSend(topic, message, RocketmqSendCallbackBuilder.commonCallback());
}
@Override
public void add(TimeTriggerMsg timeTriggerMsg) {
this.add(timeTriggerMsg.getTriggerExecutor(), timeTriggerMsg.getParam(), timeTriggerMsg.getTriggerTime(), timeTriggerMsg.getUniqueKey(), timeTriggerMsg.getTopic());
}
@Override
public void addDelay(TimeTriggerMsg timeTriggerMsg, int delayTime) {
String uniqueKey = timeTriggerMsg.getUniqueKey();
if (StringUtils.isEmpty(uniqueKey)) {
uniqueKey = StringUtils.getRandStr(10);
}
String generateKey = TimeTriggerUtil.generateKey(timeTriggerMsg.getTriggerExecutor(), timeTriggerMsg.getTriggerTime(), uniqueKey);
this.cache.put(generateKey, 1);
if (Boolean.TRUE.equals(promotionDelayQueue.addJobId(JSONUtil.toJsonStr(timeTriggerMsg), delayTime))) {
log.info("add Redis key {} --------------------------", generateKey);
log.info("定时执行在【" + DateUtil.toString(timeTriggerMsg.getTriggerTime(), "yyyy-MM-dd HH:mm:ss") + "】,消费【" + timeTriggerMsg.getParam().toString() + "");
} else {
log.info("延时任务添加失败!");
}
}
@Override
public void edit(String executorName, Object param, Long oldTriggerTime, Long triggerTime, String uniqueKey, int delayTime, String topic) {
this.delete(executorName, oldTriggerTime, uniqueKey, topic);
this.addDelay(new TimeTriggerMsg(executorName, triggerTime, param, uniqueKey, topic), delayTime);
}
@Override
public void delete(String executorName, Long triggerTime, String uniqueKey, String topic) {
String generateKey = TimeTriggerUtil.generateKey(executorName, triggerTime, uniqueKey);
log.info("delete redis key {} -----------------------", generateKey);
this.cache.remove(generateKey);
}
}

View File

@@ -0,0 +1,35 @@
package cn.lili.common.trigger.delay;
import cn.hutool.json.JSONUtil;
import cn.lili.common.delayqueue.AbstractDelayQueueMachineFactory;
import cn.lili.common.trigger.interfaces.TimeTrigger;
import cn.lili.common.trigger.model.TimeTriggerMsg;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 促销延迟队列
*
* @author paulG
* @version v4.1
* @date 2020/11/17 7:19 下午
* @description
* @since 1
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PromotionDelayQueue extends AbstractDelayQueueMachineFactory {
private final TimeTrigger timeTrigger;
@Override
public void invoke(String jobId) {
timeTrigger.add(JSONUtil.toBean(jobId, TimeTriggerMsg.class));
}
@Override
public String setDelayQueueName() {
return "promotion_delay";
}
}

View File

@@ -0,0 +1,64 @@
package cn.lili.common.trigger.interfaces;
import cn.lili.common.trigger.model.TimeTriggerMsg;
/**
* 延时执行接口
*
* @author Chopper
*/
public interface TimeTrigger {
/**
* 添加延时任务
*
* @param executorName 执行器beanId
* @param param 执行参数
* @param triggerTime 执行时间 时间戳 秒为单位
* @param uniqueKey 如果是一个 需要有 修改/取消 延时任务功能的延时任务,<br/>
* 请填写此参数,作为后续删除,修改做为唯一凭证 <br/>
* 建议参数为COUPON_{ACTIVITY_ID} 例如 coupon_123<br/>
* 业务内全局唯一
* @param topic rocketmq topic
*/
void add(String executorName, Object param, Long triggerTime, String uniqueKey, String topic);
/**
* 添加延时任务
*
* @param timeTriggerMsg 延时任务信息
*/
void add(TimeTriggerMsg timeTriggerMsg);
/**
* 添加延时任务
*
* @param timeTriggerMsg 延时任务信息
* @param delayTime 延时时间(秒)
*/
void addDelay(TimeTriggerMsg timeTriggerMsg, int delayTime);
/**
* 修改延时任务
*
* @param executorName 执行器beanId
* @param param 执行参数
* @param triggerTime 执行时间 时间戳 秒为单位
* @param oldTriggerTime 旧的任务执行时间
* @param uniqueKey 添加任务时的唯一凭证
* @param delayTime 延时时间(秒)
* @param topic rocketmq topic
*/
void edit(String executorName, Object param, Long oldTriggerTime, Long triggerTime, String uniqueKey, int delayTime, String topic);
/**
* 删除延时任务
*
* @param executorName 执行器
* @param triggerTime 执行时间
* @param uniqueKey 添加任务时的唯一凭证
* @param topic rocketmq topic
*/
void delete(String executorName, Long triggerTime, String uniqueKey, String topic);
}

View File

@@ -0,0 +1,17 @@
package cn.lili.common.trigger.interfaces;
/**
* 延时任务执行器接口
* @author Chopper
*
*/
public interface TimeTriggerExecutor {
/**
* 执行任务
* @param object 任务参数
*/
void execute(Object object);
}

View File

@@ -0,0 +1,24 @@
package cn.lili.common.trigger.model;
/**
* @author paulG
* @since 2020/8/20
**/
public abstract class TimeExecuteConstant {
/**
* 促销延迟加载执行器
*/
public static final String PROMOTION_EXECUTOR = "promotionTimeTriggerExecutor";
/**
* 拼团延迟加载执行器
*/
public static final String PINTUAN_EXECUTOR = "pintuanTimeTriggerExecutor";
/**
* 拼团延迟加载执行器
*/
public static final String FULL_DISCOUNT_EXECUTOR = "fullDiscountTimeTriggerExecutor";
}

View File

@@ -0,0 +1,50 @@
package cn.lili.common.trigger.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 延时任务消息
*
* @author Chopper
* @version v1.0
* @since
* 2019-02-12 下午5:46
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimeTriggerMsg implements Serializable {
private static final long serialVersionUID = 8897917127201859535L;
/**
* 执行器beanId
*/
private String triggerExecutor;
/**
* 执行器 执行时间
*/
private Long triggerTime;
/**
* 执行器参数
*/
private Object param;
/**
* 唯一KEY
*/
private String uniqueKey;
/**
* 信息队列主题
*/
private String topic;
}

View File

@@ -0,0 +1,28 @@
package cn.lili.common.trigger.util;
/**
* 延时任务mq实现内容提供加密算法以及任务前缀参数
*
* @author Chopper
*/
public class TimeTriggerUtil {
/**
* 前缀
*/
private static final String PREFIX = "{rocketmq_trigger}_";
/**
* 生成延时任务标识key
*
* @param executorName 执行器beanId
* @param triggerTime 执行时间
* @param uniqueKey 自定义表示
* @return 延时任务标识key
*/
public static String generateKey(String executorName, Long triggerTime, String uniqueKey) {
return PREFIX + (executorName + triggerTime + uniqueKey).hashCode();
}
}

View File

@@ -0,0 +1,117 @@
package cn.lili.common.utils;
import org.springframework.web.multipart.MultipartFile;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.io.*;
/**
* base64转为multipartFile工具类
*
* @author Chopper
*/
public class Base64DecodeMultipartFile implements MultipartFile {
private final byte[] imgContent;
private final String header;
public Base64DecodeMultipartFile(byte[] imgContent, String header) {
this.imgContent = imgContent;
this.header = header.split(";")[0];
}
@Override
public String getName() {
return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
}
@Override
public String getOriginalFilename() {
return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];
}
@Override
public String getContentType() {
return header.split(":")[1];
}
@Override
public boolean isEmpty() {
return imgContent == null || imgContent.length == 0;
}
@Override
public long getSize() {
return imgContent.length;
}
@Override
public byte[] getBytes() throws IOException {
return imgContent;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(imgContent);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
new FileOutputStream(dest).write(imgContent);
}
public static MultipartFile base64Convert(String base64) {
String[] baseStrs = base64.split(",");
Decoder decoder = Base64.getDecoder();
byte[] b = decoder.decode(baseStrs[1]);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
return new Base64DecodeMultipartFile(b, baseStrs[0]);
}
public static InputStream base64ToInputStream(String base64) {
ByteArrayInputStream stream = null;
try {
byte[] bytes = Base64.getDecoder().decode(base64);
stream = new ByteArrayInputStream(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return stream;
}
public static String inputStreamToStream(InputStream in) {
byte[] data = null;
// 读取图片字节数组
try {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while ((rc = in.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
data = swapStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return Base64.getEncoder().encodeToString(data);
}
}

View File

@@ -0,0 +1,59 @@
package cn.lili.common.utils;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 对象属性复制
* @author Chopper
*/
public class BeanUtil {
/**
* 复制属性
* @param objectFrom
* @param objectTo
*/
public static void copyProperties(Object objectFrom,Object objectTo){
BeanUtils.copyProperties(objectFrom, objectTo);
}
/**
* 获取属性名数组
*/
public static String[] getFiledName(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
Field[] superFields = o.getClass().getSuperclass().getDeclaredFields();
String[] fieldNames = new String[fields.length + superFields.length];
int index = 0;
for (int i = 0; i < fields.length; i++) {
fieldNames[index] = fields[i].getName();
index++;
}
for (int i = 0; i < superFields.length; i++) {
if (superFields[i].getName().equals("id")) {
continue;
}
fieldNames[index] = superFields[i].getName();
index++;
}
return fieldNames;
}
/* 根据属性名获取属性值
* */
public static Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[]{});
Object value = method.invoke(o, new Object[]{});
return value;
} catch (Exception e) {
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
package cn.lili.common.utils;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* CheckMobileUtil
*
* @author Chopper
* @version v1.0
* 2021-02-04 14:56
*/
public class CheckMobileUtil {
// \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔),
// 字符串在编译时会被转码一次,所以是 "\\b"
// \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔)
static String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i"
+ "|windows (phone|ce)|blackberry"
+ "|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp"
+ "|laystation portable)|nokia|fennec|htc[-_]"
+ "|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
static String tableReg = "\\b(ipad|tablet|(Nexus 7)|up.browser"
+ "|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
//移动设备正则匹配:手机端、平板
static Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE);
static Pattern tablePat = Pattern.compile(tableReg, Pattern.CASE_INSENSITIVE);
/**
* 检测是否是移动设备访问
*
* @param request 浏览器标识 获取方式:
* @return true:移动设备接入false:pc端接入
* @Title: check
*/
public static boolean check(HttpServletRequest request) {
String userAgent = request.getHeader("USER-AGENT").toLowerCase();
if (null == userAgent) {
userAgent = "";
}
// 匹配
Matcher matcherPhone = phonePat.matcher(userAgent);
Matcher matcherTable = tablePat.matcher(userAgent);
if (matcherPhone.find() || matcherTable.find()) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,51 @@
package cn.lili.common.utils;
import java.util.Random;
import java.util.UUID;
/**
* 通用工具
* @author Chopper
*/
public class CommonUtil {
/**
* 以UUID重命名
* @param fileName
* @return
*/
public static String rename(String fileName) {
String extName = fileName.substring(fileName.lastIndexOf("."));
return UUID.randomUUID().toString().replace("-", "") + extName;
}
/**
* 随机6位数生成
*/
public static String getRandomNum() {
Random random = new Random();
int num = random.nextInt(999999);
//不足六位前面补0
String str = String.format("%06d", num);
return str;
}
/**
* 批量递归删除时 判断target是否在ids中 避免重复删除
* @param target
* @param ids
* @return
*/
public static Boolean judgeIds(String target, String[] ids){
Boolean flag = false;
for(String id : ids){
if(id.equals(target)){
flag = true;
break;
}
}
return flag;
}
}

View File

@@ -0,0 +1,90 @@
package cn.lili.common.utils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* CookieUtil
*
* @author Chopper
* @version v1.0
* 2020-12-14 09:32
*/
public class CookieUtil {
/**
* 新增cookie
*
* @param key key值
* @param value 对应值
* @param response 响应
*/
public static void addCookie(String key, String value, HttpServletResponse response) {
try {
Cookie c = new Cookie(key, value);
c.setPath("/");
response.addCookie(c);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 新增cookie
*
* @param key key值
* @param value 对应值
* @param maxAge cookie 有效时间
* @param response 响应
*/
public static void addCookie(String key, String value, Integer maxAge, HttpServletResponse response) {
try {
Cookie c = new Cookie(key, value);
c.setMaxAge(maxAge);
c.setPath("/");
response.addCookie(c);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 删除cookie
*
* @param key key值
* @param response 响应
*/
public static void delCookie(String key, HttpServletResponse response) {
try {
Cookie c = new Cookie(key, "");
c.setMaxAge(0);
response.addCookie(c);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取cookie
*
* @param key key值
* @param request 请求
* @return cookie value
*/
public static String getCookie(String key, HttpServletRequest request) {
try {
if (request.getCookies() == null) {
return null;
}
for (int i = 0; i < request.getCookies().length; i++) {
if (request.getCookies()[i].getName().equals(key)) {
return request.getCookies()[i].getValue();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,134 @@
package cn.lili.common.utils;
import java.math.BigDecimal;
/**
* 金额计算工具
*/
public final class CurrencyUtil {
/**
* 默认除法运算精度
*/
private static final int DEF_DIV_SCALE = 2;
/**
* 这个类不能实例化
*/
private CurrencyUtil() {
}
/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static Double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的乘法运算。
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static Double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后10位以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。 当发生除不尽的情况时由scale参数指定精度以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
//如果被除数等于0则返回0
if (v2 == 0) {
return 0;
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 金额转分
*
* @param money
* @return
*/
public static Integer fen(Double money) {
double price = mul(money, 100);
return (int) price;
}
/**
* 金额转分
*
* @param money
* @return
*/
public static double reversalFen(Double money) {
double price = div(money, 100);
return price;
}
public static void main(String[] args) {
System.out.println(fen(23.4324));
}
}

View File

@@ -0,0 +1,387 @@
package cn.lili.common.utils;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.*;
/**
* 日期相关的操作
*
* @author Chopper
*/
public class DateUtil {
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String STANDARD_DATE_FORMAT = "yyyy-MM-dd";
public static final String STANDARD_DATE_NO_UNDERLINE_FORMAT = "yyyyMMdd";
public static final String FULL_DATE = "yyyyMMddHHmmss";
/**
* 当天的开始时间
*
* @return
*/
public static Date startOfTodDayTime() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
/**
* 当天的开始时间
*
* @return
*/
public static Date startOfTodDayTime(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
/**
* 当天的开始时间
*
* @return
*/
public static long startOfTodDay() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date date = calendar.getTime();
return date.getTime() / 1000;
}
public static void main(String[] args) {
}
/**
* 当天的结束时间
*
* @return
*/
public static Date endOfDate() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
/**
* 当天的结束时间
*
* @return
*/
public static Date endOfDate(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
/**
* 某天的年月日
*
* @param dayUntilNow 距今多少天以前
* @return 年月日map key为 year month day
*/
public static Map<String, Object> getYearMonthAndDay(int dayUntilNow) {
Map<String, Object> map = new HashMap<String, Object>(3);
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
calendar.add(Calendar.DATE, -dayUntilNow);
map.put("year", calendar.get(Calendar.YEAR));
map.put("month", calendar.get(Calendar.MONTH) + 1);
map.put("day", calendar.get(Calendar.DAY_OF_MONTH));
return map;
}
/**
* 将一个字符串转换成日期格式
*
* @param date 字符串日期
* @param pattern 日期格式
* @return
*/
public static Date toDate(String date, String pattern) {
if ("".equals("" + date)) {
return null;
}
if (pattern == null) {
pattern = STANDARD_DATE_FORMAT;
}
SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH);
Date newDate = new Date();
try {
newDate = sdf.parse(date);
} catch (Exception ex) {
ex.printStackTrace();
}
return newDate;
}
/**
* 获取上个月的开始结束时间
*
* @return
*/
public static Long[] getLastMonth() {
// 取得系统当前时间
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;
// 取得系统当前时间所在月第一天时间对象
cal.set(Calendar.DAY_OF_MONTH, 1);
// 日期减一,取得上月最后一天时间对象
cal.add(Calendar.DAY_OF_MONTH, -1);
// 输出上月最后一天日期
int day = cal.get(Calendar.DAY_OF_MONTH);
String months = "";
String days = "";
if (month > 1) {
month--;
} else {
year--;
month = 12;
}
if (!(String.valueOf(month).length() > 1)) {
months = "0" + month;
} else {
months = String.valueOf(month);
}
if (!(String.valueOf(day).length() > 1)) {
days = "0" + day;
} else {
days = String.valueOf(day);
}
String firstDay = "" + year + "-" + months + "-01";
String lastDay = "" + year + "-" + months + "-" + days + " 23:59:59";
Long[] lastMonth = new Long[2];
lastMonth[0] = DateUtil.getDateline(firstDay);
lastMonth[1] = DateUtil.getDateline(lastDay, STANDARD_FORMAT);
return lastMonth;
}
/**
* 把日期转换成字符串型
*
* @param date 日期
* @return
*/
public static String toString(Date date) {
return toString(date,STANDARD_FORMAT);
}
/**
* 把日期转换成字符串型
*
* @param date 日期
* @param pattern 类型
* @return
*/
public static String toString(Date date, String pattern) {
if (date == null) {
return "";
}
if (pattern == null) {
pattern = STANDARD_DATE_FORMAT;
}
String dateString = "";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
try {
dateString = sdf.format(date);
} catch (Exception ex) {
ex.printStackTrace();
}
return dateString;
}
/**
* 时间戳转换成时间类型
*
* @param time 时间戳
* @param pattern 格式
* @return
*/
public static String toString(Long time, String pattern) {
if (time > 0) {
if (time.toString().length() == 10) {
time = time * 1000;
}
Date date = new Date(time);
String str = DateUtil.toString(date, pattern);
return str;
}
return "";
}
/**
* 判断当前时间是否在某个时间范围
*
* @param start 开始时间,以秒为单位的时间戳
* @param end 结束时间,以秒为单位的时间戳
* @return 是否在范围内
*/
public static boolean inRangeOf(long start, long end) {
long now = getDateline();
return start <= now && end >= now;
}
/**
* 获取指定日期的时间戳
*
* @param date 指定日期
* @return 时间戳
*/
public static long getDateline(String date) {
return toDate(date, STANDARD_DATE_FORMAT).getTime() / 1000;
}
/**
* 获取当前时间的时间戳
*
* @return 时间戳
*/
public static long getDateline() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间格式化字符串
*
* @return 时间戳
*/
public static String getCurrentDateStr(String format) {
return toString(new Date(), format);
}
/**
* 获取当前时间格式化字符串
*
* @return 格式化的时间
*/
public static String getCurrentDateStr() {
return toString(new Date(), FULL_DATE);
}
/**
* 根据日期格式及日期获取时间戳
*
* @param date 日期
* @param pattern 日期格式
* @return 时间戳
*/
public static long getDateline(String date, String pattern) {
return toDate(date, pattern).getTime() / 1000;
}
/**
* 获取几个月之前的日期时间戳
*
* @param beforeMonth 几个月之前
* @return
*/
public static long getBeforeMonthDateline(int beforeMonth) {
SimpleDateFormat format = new SimpleDateFormat(STANDARD_FORMAT);
Calendar c = Calendar.getInstance();
//过去一月
c.setTime(new Date());
c.add(Calendar.MONTH, (0 - beforeMonth));
Date m = c.getTime();
String mon = format.format(m);
return getDateline(mon, STANDARD_FORMAT);
}
/**
* 获取当前天的结束时间
*
* @return 当前天的结束时间
*/
public static Date getCurrentDayEndTime() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.DATE, cal.get(Calendar.DATE) + 1);
cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) - 1);
return cal.getTime();
}
/**
* 获取延时时间(秒)
*
* @param startTime 开始时间
* @return 延时时间(秒)
*/
public static Integer getDelayTime(Long startTime) {
int time = Math.toIntExact((startTime - System.currentTimeMillis()) / 1000);
// 如果时间为负数则改为一秒后执行
if (time <= 0) {
time = 1;
}
return time;
}
/**
* 获取某年某月开始时间
*
* @param year 年
* @param month 月
* @return 开始时间
*/
public static Date getBeginTime(int year, int month) {
YearMonth yearMonth = YearMonth.of(year, month);
LocalDate localDate = yearMonth.atDay(1);
LocalDateTime startOfDay = localDate.atStartOfDay();
ZonedDateTime zonedDateTime = startOfDay.atZone(ZoneId.of("Asia/Shanghai"));
return Date.from(zonedDateTime.toInstant());
}
/**
* 获取某年某月结束时间
*
* @param year 年
* @param month 月
* @return 结束时间
*/
public static Date getEndTime(int year, int month) {
YearMonth yearMonth = YearMonth.of(year, month);
LocalDate endOfMonth = yearMonth.atEndOfMonth();
LocalDateTime localDateTime = endOfMonth.atTime(23, 59, 59, 999);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
return Date.from(zonedDateTime.toInstant());
}
}

View File

@@ -0,0 +1,56 @@
package cn.lili.common.utils;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import java.io.IOException;
/**
* 代理对象实例化
* @author Chopper
*/
public class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
}
};
private final Gson context;
private HibernateProxyTypeAdapter(Gson context) {
this.context = context;
}
@Override
public HibernateProxy read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
// Retrieve the original (not proxy) class
Class<?> baseType = Hibernate.getClass(value);
// Get the TypeAdapter of the original class, to delegate the serialization
TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
// Get a filled instance of the original class
Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
.getImplementation();
// Serialize the value
delegate.write(out, unproxiedValue);
}
}

View File

@@ -0,0 +1,232 @@
package cn.lili.common.utils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpClientUtils {
// org.apache.http.impl.client.CloseableHttpClient
private static CloseableHttpClient httpClient = null;
// 这里就直接默认固定了,因为以下三个参数在新建的method中仍然可以重新配置并被覆盖.
static final int connectionRequestTimeout = 30000;// ms毫秒,从池中获取链接超时时间
static final int connectTimeout = 60000;// ms毫秒,建立链接超时时间
static final int socketTimeout = 60000;// ms毫秒,读取超时时间
// 总配置,主要涉及是以下两个参数,如果要作调整没有用到properties会比较后麻烦,但鉴于一经粘贴,随处可用的特点,就不再做依赖性配置化处理了.
// 而且这个参数同一家公司基本不会变动.
static final int maxTotal = 500;// 最大总并发,很重要的参数
static final int maxPerRoute = 100;// 每路并发,很重要的参数
// 正常情况这里应该配成MAP或LIST
// 细化配置参数,用来对每路参数做精细化处理,可以管控各ip的流量,比如默认配置请求baidu:80端口最大100个并发链接,
static final String detailHostName = "http://www.baidu.com";// 每个细化配置之ip(不重要,在特殊场景很有用)
// 每个细化配置之port(不重要,在特殊场景很有用)
static final int detailPort = 80;
// 每个细化配置之最大并发数(不重要,在特殊场景很有用)
static final int detailMaxPerRoute = 100;
private static CloseableHttpClient getHttpClient() {
if (null == httpClient) {
synchronized (HttpClientUtils.class) {
if (null == httpClient) {
httpClient = init();
}
}
}
return httpClient;
}
/**
* 链接池初始化 这里最重要的一点理解就是. 让CloseableHttpClient 一直活在池的世界里, 但是HttpPost却一直用完就消掉.
* 这样可以让链接一直保持着.
*/
private static CloseableHttpClient init() {
CloseableHttpClient newHotpoint;
// 设置连接池
ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", plainsf).register("https", sslsf).build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
// 将最大连接数增加
cm.setMaxTotal(maxTotal);
// 将每个路由基础的连接增加
cm.setDefaultMaxPerRoute(maxPerRoute);
// 细化配置开始,其实这里用Map或List的for循环来配置每个链接,在特殊场景很有用.
// 将每个路由基础的连接做特殊化配置,一般用不着
HttpHost httpHost = new HttpHost(detailHostName, detailPort);
// 将目标主机的最大连接数增加
cm.setMaxPerRoute(new HttpRoute(httpHost), detailMaxPerRoute);
// 细化配置结束
// 请求重试处理
HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
if (executionCount >= 2) {// 如果已经重试了2次就放弃
return false;
}
if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试
return true;
}
if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
return false;
}
if (exception instanceof InterruptedIOException) {// 超时
return false;
}
if (exception instanceof UnknownHostException) {// 目标服务器不可达
return false;
}
if (exception instanceof SSLException) {// SSL握手异常
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
// 如果请求是幂等的,就再次尝试
return !(request instanceof HttpEntityEnclosingRequest);
};
// 配置请求的超时设置
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectionRequestTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
newHotpoint = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).setRetryHandler(httpRequestRetryHandler).build();
return newHotpoint;
}
public static String doGet(String url, Map<String, String> param) {
// httpClient
CloseableHttpClient httpClient = getHttpClient();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpClient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} else {
System.out.println(response.getStatusLine().getStatusCode());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url, Map<String, String> param) {
// 创建HttpClient对象
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPostJson(String url, String json) {
// 创建HttpClient对象
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}

View File

@@ -0,0 +1,75 @@
package cn.lili.common.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import cn.lili.modules.connect.util.IpUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* ip工具
*
* @author Chopper
*/
@Slf4j
@Component
public class IpHelper {
//qq lbs 地区查询key
@Value("${lili.lbs.key}")
private String key;
//qq lbs 地区查询key
@Value("${lili.lbs.sk}")
private String sk;
private static final String api = "https://apis.map.qq.com";
/**
* 获取IP返回地理信息
*
* @param
* @return
*/
public String getIpCity(HttpServletRequest request) {
String url = "/ws/location/v1/ip?key=" + key + "&ip=" + IpUtils.getIpAddress(request);
String sign = SecureUtil.md5(url + sk);
url = api + url + "&sign=" + sign;
String result = "未知";
try {
String json = HttpUtil.get(url, 3000);
JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
String status = jsonObject.get("status").getAsString();
if ("0".equals(status)) {
JsonObject address = jsonObject.get("result").getAsJsonObject().get("ad_info").getAsJsonObject();
String nation = address.get("nation").getAsString();
String province = address.get("province").getAsString();
String city = address.get("city").getAsString();
String district = address.get("district").getAsString();
if (StrUtil.isNotBlank(nation) && StrUtil.isBlank(province)) {
result = nation;
} else {
result = province;
if (StrUtil.isNotBlank(city)) {
result += " " + city;
}
if (StrUtil.isNotBlank(district)) {
result += " " + district;
}
}
}
} catch (Exception e) {
log.info("获取IP地理信息失败");
}
return result;
}
}

View File

@@ -0,0 +1,59 @@
package cn.lili.common.utils;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
/**
* 加密解密
* @author Chopper
*/
public class JasyptUtil {
/**
* Jasypt生成加密结果
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
* @param value 待加密值
* @return
*/
public static String encyptPwd(String password,String value){
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setConfig(cryptor(password));
String result = encryptor.encrypt(value);
return result;
}
/**
* 解密
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
* @param value 待解密密文
* @return
*/
public static String decyptPwd(String password,String value){
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setConfig(cryptor(password));
encryptor.decrypt(value);
String result = encryptor.decrypt(value);
return result;
}
public static SimpleStringPBEConfig cryptor(String password){
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize(1);
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
return config;
}
public static void main(String[] args){
//加密 若修改了第一个参数加密password记得在配置文件同步修改
System.out.println(encyptPwd("jasypt.encryptor.password","123456"));
//解密
System.out.println(decyptPwd("jasypt.encryptor.password","PYVnAYh+j5C3jkMV1d+myj6JzDaUk7pcfTWUaYsvQdEVkuvIVf7Y0mOU9XkffxT8"));
}
}

View File

@@ -0,0 +1,57 @@
package cn.lili.common.utils;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import org.springframework.cglib.beans.BeanMap;
import java.util.HashMap;
import java.util.Map;
/**
* 对象转换工具
* @author Chopper
*/
public class ObjectUtil {
public static String mapToString(Map<String, String[]> paramMap){
if (paramMap == null) {
return "";
}
Map<String, Object> params = new HashMap<>(16);
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
String key = param.getKey();
String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
String obj = StrUtil.endWithIgnoreCase(param.getKey(), "password") ? "******" : paramValue;
params.put(key,obj);
}
return new Gson().toJson(params);
}
public static String mapToStringAll(Map<String, String[]> paramMap){
if (paramMap == null) {
return "";
}
Map<String, Object> params = new HashMap<>(16);
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
String key = param.getKey();
String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
params.put(key, paramValue);
}
return new Gson().toJson(params);
}
public static <T> Map<String, Object> beanToMap(T bean) {
Map<String, Object> map = new HashMap<>(16);
if (bean != null) {
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key+"", beanMap.get(key));
}
}
return map;
}
}

View File

@@ -0,0 +1,58 @@
package cn.lili.common.utils;
import cn.lili.common.enums.MessageCode;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
/**
* 全局统一判定是否可操作某属性
*
* @author Chopper
* @version v1.0
* 2020-08-20 18:07
*/
public class OperationalJudgment<t> {
/**
* 需要判定的对象必须包含属性 memberIdstoreId 代表判定的角色
*
* @param object 判定的对象
* @param <t>
* @return
*/
public static <t> t judgment(t object) {
return judgment(object, "memberId", "storeId");
}
/**
* 需要判定的对象必须包含属性 memberIdstoreId 代表判定的角色
*
* @param object
* @param buyerIdField
* @param storeIdField
* @param <t>
* @return 返回判定本身,防止多次查询对象
*/
public static <t> t judgment(t object, String buyerIdField, String storeIdField) {
AuthUser tokenUser = UserContext.getCurrentUser();
switch (tokenUser.getRole()) {
case MANAGER:
return object;
case MEMBER:
if (tokenUser.getId().equals(BeanUtil.getFieldValueByName(buyerIdField, object))) {
return object;
} else {
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
}
case STORE:
if (tokenUser.getStoreId().equals(BeanUtil.getFieldValueByName(storeIdField, object))) {
return object;
} else {
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
}
}
return object;
}
}

View File

@@ -0,0 +1,151 @@
package cn.lili.common.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.lili.common.vo.PageVO;
import cn.lili.common.vo.SearchVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 分页工具
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/26 15:23
*/
public class PageUtil {
/**
* Mybatis-Plus分页封装
*
* @param page
* @return
*/
public static <T> Page<T> initPage(PageVO page) {
Page<T> p;
int pageNumber = page.getPageNumber();
int pageSize = page.getPageSize();
String sort = page.getSort();
String order = page.getOrder();
if (pageNumber < 1) {
pageNumber = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100;
}
if (StrUtil.isNotBlank(sort)) {
Boolean isAsc = false;
if (StrUtil.isBlank(order)) {
isAsc = false;
} else {
if ("desc".equals(order.toLowerCase())) {
isAsc = false;
} else if ("asc".equals(order.toLowerCase())) {
isAsc = true;
}
}
p = new Page<>(pageNumber, pageSize);
if (isAsc) {
p.addOrder(OrderItem.asc(sort));
} else {
p.addOrder(OrderItem.desc(sort));
}
} else {
p = new Page<>(pageNumber, pageSize);
}
return p;
}
/**
* 生成条件搜索 全对象对比 equals
* 如果需要like 需要另行处理
*
* @param object
* @return
*/
public static <T> QueryWrapper<T> initWrapper(Object object) {
return initWrapper(object, null);
}
/**
* 生成条件搜索 全对象对比
*
* @param object
* @param searchVo
* @return
*/
public static <T> QueryWrapper<T> initWrapper(Object object, SearchVO searchVo) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
// 创建时间区间判定
if (searchVo != null && StrUtil.isNotBlank(searchVo.getStartDate()) && StrUtil.isNotBlank(searchVo.getEndDate())) {
Date start = cn.hutool.core.date.DateUtil.parse(searchVo.getStartDate());
Date end = cn.hutool.core.date.DateUtil.parse(searchVo.getEndDate());
queryWrapper.between("create_time", start, DateUtil.endOfDay(end));
}
if (object != null) {
String[] fieldNames = BeanUtil.getFiledName(object);
//遍历所有属性
for (int j = 0; j < fieldNames.length; j++) {
//获取属性的名字
String key = fieldNames[j];
//获取值
Object value = BeanUtil.getFieldValueByName(key, object);
//如果值不为空才做查询处理
if (value != null && !"".equals(value)) {
//字段数据库中,驼峰转下划线
queryWrapper.eq(StringUtils.camel2Underline(key), value);
}
}
}
return queryWrapper;
}
/**
* List 手动分页
*
* @param page
* @param list
* @return
*/
public static <T> List<T> listToPage(PageVO page, List<T> list) {
int pageNumber = page.getPageNumber() - 1;
int pageSize = page.getPageSize();
if (pageNumber < 0) {
pageNumber = 0;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100;
}
int fromIndex = pageNumber * pageSize;
int toIndex = pageNumber * pageSize + pageSize;
if (fromIndex > list.size()) {
return new ArrayList<>();
} else if (toIndex >= list.size()) {
return list.subList(fromIndex, list.size());
} else {
return list.subList(fromIndex, toIndex);
}
}
}

View File

@@ -0,0 +1,157 @@
package cn.lili.common.utils;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class PasswordUtil {
/**
* JAVA6支持以下任意一种算法
* PBEWITHMD5ANDDES
* PBEWITHMD5ANDTRIPLEDES
* PBEWITHSHAANDDESEDE
* PBEWITHSHA1ANDRC2_40
* PBKDF2WITHHMACSHA1
* */
/**
* 定义使用的算法为:PBEWITHMD5andDES算法
*/
public static final String ALGORITHM = "PBEWithMD5AndDES";
/**
* 定义迭代次数为1000次
*/
private static final int ITERATIONCOUNT = 1000;
/**
* 获取加密算法中使用的盐值,解密中使用的盐值必须与加密中使用的相同才能完成操作.
* 盐长度必须为8字节
*
* @return byte[] 盐值
*/
public static byte[] getSalt() throws Exception {
//实例化安全随机数
SecureRandom random = new SecureRandom();
//产出盐
return random.generateSeed(8);
}
/**
* 根据PBE密码生成一把密钥
*
* @param password 生成密钥时所使用的密码
* @return Key PBE算法密钥
*/
private static Key getPBEKey(String password) throws Exception {
// 实例化使用的算法
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
// 设置PBE密钥参数
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
// 生成密钥
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return secretKey;
}
/**
* 加密明文字符串
*
* @param plaintext 待加密的明文字符串
* @param password 生成密钥时所使用的密码
* @param salt 盐值
* @return 加密后的密文字符串
* @throws Exception
*/
public static String encrypt(String plaintext, String password, byte[] salt) throws Exception {
Key key = getPBEKey(password);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONCOUNT);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
byte encipheredData[] = cipher.doFinal(plaintext.getBytes());
return bytesToHexString(encipheredData);
}
/**
* 解密密文字符串
*
* @param ciphertext 待解密的密文字符串
* @param password 生成密钥时所使用的密码(如需解密,该参数需要与加密时使用的一致)
* @param salt 盐值(如需解密,该参数需要与加密时使用的一致)
* @return 解密后的明文字符串
* @throws Exception
*/
public static String decrypt(String ciphertext, String password, byte[] salt) throws Exception {
Key key = getPBEKey(password);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONCOUNT);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
byte[] passDec = cipher.doFinal(hexStringToBytes(ciphertext));
return new String(passDec);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param src 字节数组
* @return
*/
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 将十六进制字符串转换为字节数组
*
* @param hexString 十六进制字符串
* @return
*/
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
}

View File

@@ -0,0 +1,646 @@
package cn.lili.common.utils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis封装工具类
*
* @author paulG
* @since 2020/11/7
**/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RedisUtil {
private final RedisTemplate redisTemplate;
//=============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public <T> T get(String key, Class<T> clazz) {
Object o = key == null ? null : redisTemplate.opsForValue().get(key);
return (T) o;
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================listByParentId=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时 0 表头1 第二个元素依次类推index<0时-1表尾-2倒数第二个元素依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public <T> boolean lPush(String key, List<T> value) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================ZSet=================================
/**
* 向Zset里添加成员
*
* @param key
* @param score
* @param value
* @return
*/
public boolean zadd(String key, long score, String value) {
Boolean result = redisTemplate.opsForZSet().add(key, value, score);
return result;
}
/**
* 获取 某key 下 某一分值区间的队列
*
* @param key
* @param from
* @param to
* @return
*/
public Set<DefaultTypedTuple> zrangeByScoreWithScores(String key, int from, long to) {
Set<DefaultTypedTuple> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, from, to);
return set;
}
/**
* 移除 Zset队列值
*
* @param key
* @param value
* @return
*/
public Long zremove(String key, String... value) {
return redisTemplate.opsForZSet().remove(key, value);
}
}

View File

@@ -0,0 +1,42 @@
package cn.lili.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 用户名验证工具类
* @author Chopper
*/
public class RegularUtil {
/**
* 手机号
*/
private static final Pattern mobile = Pattern.compile("^1[3|4|5|8][0-9]\\d{8}$");
/**
* 邮箱
*/
private static final Pattern email = Pattern.compile("^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$");
public static boolean Mobile(String v){
Matcher m = mobile.matcher(v);
if(m.matches()){
return true;
}
return false;
}
public static boolean Email(String v){
Matcher m = email.matcher(v);
if(m.matches()){
return true;
}
return false;
}
}

View File

@@ -0,0 +1,125 @@
package cn.lili.common.utils;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* response 输出响应工具
*
* @author Chopper
*/
@Slf4j
public class ResponseUtil {
static final String ENCODING = "UTF-8";
static final String CONTENT_TYPE = "application/json;charset=UTF-8";
/**
* 输出前端内容以及状态指定
*
* @param response
* @param status
* @param content
*/
public static void output(HttpServletResponse response, Integer status, String content) {
ServletOutputStream servletOutputStream = null;
try {
response.setCharacterEncoding(ENCODING);
response.setContentType(CONTENT_TYPE);
response.setStatus(status);
servletOutputStream = response.getOutputStream();
servletOutputStream.write(content.getBytes());
} catch (Exception e) {
log.error("response output error:", e);
} finally {
if (servletOutputStream != null) {
try {
servletOutputStream.flush();
servletOutputStream.close();
} catch (IOException e) {
log.error("response output IO close error:", e);
}
}
}
}
/**
* response 输出JSON
*
* @param response
* @param status response 状态
* @param resultMap
*/
public static void output(HttpServletResponse response, Integer status, Map<String, Object> resultMap) {
response.setStatus(status);
output(response, resultMap);
}
/**
* response 输出JSON
*
* @param response
* @param resultMap
*/
public static void output(HttpServletResponse response, Map<String, Object> resultMap) {
ServletOutputStream servletOutputStream = null;
try {
response.setCharacterEncoding(ENCODING);
response.setContentType(CONTENT_TYPE);
servletOutputStream = response.getOutputStream();
servletOutputStream.write(new Gson().toJson(resultMap).getBytes());
} catch (Exception e) {
log.error("response output error:", e);
} finally {
if (servletOutputStream != null) {
try {
servletOutputStream.flush();
servletOutputStream.close();
} catch (IOException e) {
log.error("response output IO close error:", e);
}
}
}
}
/**
* 构造响应
*
* @param flag
* @param code
* @param msg
* @return
*/
public static Map<String, Object> resultMap(boolean flag, Integer code, String msg) {
return resultMap(flag, code, msg, null);
}
/**
* 构造响应
*
* @param flag
* @param code
* @param msg
* @param data
* @return
*/
public static Map<String, Object> resultMap(boolean flag, Integer code, String msg, Object data) {
Map<String, Object> resultMap = new HashMap<String, Object>(16);
resultMap.put("success", flag);
resultMap.put("message", msg);
resultMap.put("code", code);
resultMap.put("timestamp", System.currentTimeMillis());
if (data != null) {
resultMap.put("result", data);
}
return resultMap;
}
}

View File

@@ -0,0 +1,114 @@
package cn.lili.common.utils;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.vo.ResultMessage;
/**
* @author lili
*/
public class ResultUtil<T> {
/**
* 抽象类,存放结果
*/
private final ResultMessage<T> resultMessage;
/**
* 正常响应
*/
private static final Integer SUCCESS = 200;
/**
* 业务异常
*/
private static final Integer ERROR = 400;
/**
* 构造话方法,给响应结果默认值
*/
public ResultUtil() {
resultMessage = new ResultMessage<>();
resultMessage.setSuccess(true);
resultMessage.setMessage("success");
resultMessage.setCode(SUCCESS);
}
/**
* 返回数据
*
* @param t
* @return
*/
public ResultMessage<T> setData(T t) {
this.resultMessage.setResult(t);
return this.resultMessage;
}
/**
* 服务器异常 追加状态码
*
* @param resultCode 返回码
*/
public ResultMessage<T> setErrorMsg(ResultCode resultCode) {
this.resultMessage.setSuccess(false);
this.resultMessage.setMessage(resultCode.message());
this.resultMessage.setCode(resultCode.code());
return this.resultMessage;
}
/**
* 服务器异常 追加状态码
* @param code 状态码
* @param msg 返回消息
*/
public ResultMessage<T> setErrorMsg(Integer code, String msg) {
this.resultMessage.setSuccess(false);
this.resultMessage.setMessage(msg);
this.resultMessage.setCode(code);
return this.resultMessage;
}
/**
* 返回成功消息
*
* @param resultCode 返回码
* @return 返回成功消息
*/
public ResultMessage<T> setSuccessMsg(ResultCode resultCode) {
this.resultMessage.setSuccess(true);
this.resultMessage.setMessage(resultCode.message());
this.resultMessage.setCode(resultCode.code());
return this.resultMessage;
}
//抽象静态方法,返回结果集
public static <T> ResultMessage<T> data(T t) {
return new ResultUtil<T>().setData(t);
}
/**
* 返回成功
* @param resultCode 返回状态码
*/
public static <T> ResultMessage<T> success(ResultCode resultCode) {
return new ResultUtil<T>().setSuccessMsg(resultCode);
}
/**
* 返回失败
* @param resultCode 返回状态码
*/
public static <T> ResultMessage<T> error(ResultCode resultCode) {
return new ResultUtil<T>().setErrorMsg(resultCode);
}
/**
* 返回失败
* @param code 状态码
* @param msg 返回消息
*/
public static <T> ResultMessage<T> error(Integer code, String msg) {
return new ResultUtil<T>().setErrorMsg(code, msg);
}
}

View File

@@ -0,0 +1,41 @@
package cn.lili.common.utils;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import java.util.Date;
/**
* 雪花分布式id获取
*
* @author Chopper
*/
public class SnowFlake {
/**
* 机器id
*/
private static long workerId = 0L;
/**
* 机房id
*/
private static long datacenterId = 0L;
private static Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
public static long getId() {
return snowflake.nextId();
}
/**
* 生成字符,带有前缀
* @param prefix
* @return
*/
public static String createStr(String prefix) {
return prefix + DateUtil.toString(new Date(), "yyyyMMdd") + SnowFlake.getId();
}
public static String getIdStr() {
return snowflake.nextId() + "";
}
}

View File

@@ -0,0 +1,76 @@
package cn.lili.common.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* SpelUtil
*
* @author Chopper
* @version v1.0
* 2021-01-11 10:45
*/
public class SpelUtil {
// spel表达式解析器
private static SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
// 参数名发现器
private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 转换 jspl参数
*
* @param joinPoint
* @param spel
* @return
*/
public static String compileParams(JoinPoint joinPoint, String spel) { // Spel表达式解析日志信息
// 获得方法参数名数组
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod());
if (parameterNames != null && parameterNames.length > 0) {
EvaluationContext context = new StandardEvaluationContext();
//获取方法参数值
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i], args[i]); // 替换spel里的变量值为实际值 比如 #user --> user对象
}
return spelExpressionParser.parseExpression(spel).getValue(context).toString();
}
return "";
}
/**
* 转换 jspl参数
*
* @param joinPoint
* @param spel
* @return
*/
public static String compileParams(JoinPoint joinPoint, Object rvt, String spel) { // Spel表达式解析日志信息
// 获得方法参数名数组
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod());
if (parameterNames != null && parameterNames.length > 0) {
EvaluationContext context = new StandardEvaluationContext();
//获取方法参数值
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i], args[i]); // 替换spel里的变量值为实际值 比如 #user --> user对象
}
context.setVariable("rvt", rvt);
return spelExpressionParser.parseExpression(spel).getValue(context).toString();
}
return "";
}
}

View File

@@ -0,0 +1,62 @@
package cn.lili.common.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author Chopper
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
applicationContext = appContext;
}
/**
* 获取applicationContext
* @return spring applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
* @param name bean的名字
* @return bean实例
*/
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
* @param clazz bean的类型
* @param <T> bean的类型
* @return bean实例
*/
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
* @param name bean的名字
* @param clazz bean的类型
* @param <T> bean的类型
* @return bean实例
*/
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}

View File

@@ -0,0 +1,255 @@
package cn.lili.common.utils;
import cn.hutool.core.util.StrUtil;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* 字串工具类
*
* @author pikachu
*/
public class StringUtils extends StrUtil {
/**
* MD5加密方法
*
* @param str String
* @return String
*/
public static String md5(String str) {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
return null;
}
byte[] resultByte = messageDigest.digest(str.getBytes());
StringBuffer result = new StringBuffer();
for (int i = 0; i < resultByte.length; ++i) {
int v = 0xFF & resultByte[i];
if (v < 16) {
result.append("0");
}
result.append(Integer.toHexString(v));
}
return result.toString();
}
/**
* 将object转为数字
*
* @param obj 需要转object的对象
* @param checked 如果为true格式不正确抛出异常
* @return
*/
public static int toInt(Object obj, boolean checked) {
int value = 0;
if (obj == null) {
return 0;
}
try {
value = Integer.parseInt(obj.toString());
} catch (Exception ex) {
if (checked) {
throw new RuntimeException("整型数字格式不正确");
} else {
return 0;
}
}
return value;
}
/**
* 将一个字串转为long如果无空则返回默认值
*
* @param str 要转换的数字字串
* @param defaultValue 默认值
* @return
*/
public static Long toLong(String str, Long defaultValue) {
Long value = defaultValue;
if (str == null || "".equals(str)) {
return defaultValue;
}
try {
value = Long.parseLong(str);
} catch (Exception ex) {
return defaultValue;
}
return value;
}
/**
* 将一个object转为double 如果object 为 null 则返回0
*
* @param obj 需要转成Double的对象
* @param checked 如果为true格式不正确抛出异常
* @return
*/
public static Double toDouble(Object obj, boolean checked) {
Double value = 0d;
if (obj == null) {
if (checked) {
throw new RuntimeException("数字格式不正确");
} else {
return 0D;
}
}
try {
value = Double.valueOf(obj.toString());
} catch (Exception ex) {
if (checked) {
throw new RuntimeException("数字格式不正确");
} else {
return 0D;
}
}
return value;
}
/**
* 将一个字串转为Double如果无空则返回默认值
*
* @param str 要转换的数字字串
* @param defaultValue 默认值
* @return
*/
public static Double toDouble(String str, Double defaultValue) {
Double value = defaultValue;
if (str == null || "".equals(str)) {
return 0d;
}
try {
value = Double.valueOf(str);
} catch (Exception ex) {
ex.printStackTrace();
value = defaultValue;
}
return value;
}
/**
* 获取随机数
*
* @param n 随机次数
* @return
*/
public static String getRandStr(int n) {
Random random = new Random();
String sRand = "";
for (int i = 0; i < n; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
}
return sRand;
}
/**
* 判断一个数组是否为空并且长度大于0
*
* @param list
* @return true 不空/false 空
*/
public static boolean isNotEmpty(List list) {
return list != null && list.size() > 0;
}
/**
* 切个字符串,如果超出长度则切割
*
* @param var
* @param size
* @return
*/
public static String subStringLength(String var, Integer size) {
if (var.length() > size) {
return var.substring(0, (size - 4)).toString() + "...";
}
return var;
}
/**
* 对象转map
*
* @param obj
* @return
* @throws Exception
*/
public static Map<String, Object> objectToMap(Object obj) throws Exception {
if (obj == null) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (key.compareToIgnoreCase("class") == 0) {
continue;
}
Method getter = property.getReadMethod();
Object value = getter != null ? getter.invoke(obj) : null;
map.put(key, value);
}
return map;
}
/**
* 驼峰法转下划线
*/
public static String camel2Underline(String str) {
if (StrUtil.isBlank(str)) {
return "";
}
if (str.length() == 1) {
return str.toLowerCase();
}
StringBuffer sb = new StringBuffer();
for (int i = 1; i < str.length(); i++) {
if (Character.isUpperCase(str.charAt(i))) {
sb.append("_" + Character.toLowerCase(str.charAt(i)));
} else {
sb.append(str.charAt(i));
}
}
return (str.charAt(0) + sb.toString()).toLowerCase();
}
/**
* 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr}
* 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise}
*
* @param str 给定的字符串
* @param appendStr 需要追加的内容
* @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容
* @return 追加后的字符串
*/
public static String appendIfNotContain(String str, String appendStr, String otherwise) {
if (isEmpty(str) || isEmpty(appendStr)) {
return str;
}
if (str.contains(appendStr)) {
return str.concat(otherwise);
}
return str.concat(appendStr);
}
}

View File

@@ -0,0 +1,77 @@
package cn.lili.common.utils;
import java.util.concurrent.*;
/**
* @author Chopper
*/
public class ThreadPoolUtil {
/**
* 核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
*/
private static final int SIZE_CORE_POOL = 5;
/**
* 线程池维护线程的最大数量
*/
private static final int SIZE_MAX_POOL = 10;
/**
* 线程池维护线程所允许的空闲时间
*/
private static final long ALIVE_TIME = 2000;
/**
* 线程缓冲队列
*/
private static final BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(100);
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, bqueue, new ThreadPoolExecutor.CallerRunsPolicy());
public static ThreadPoolExecutor threadPool;
static {
pool.prestartAllCoreThreads();
}
/**
* 无返回值直接执行, 管他娘的
*
* @param runnable
*/
public static void execute(Runnable runnable) {
getThreadPool().execute(runnable);
}
/**
* 返回值直接执行, 管他娘的
*
* @param callable
*/
public static <T> Future<T> submit(Callable<T> callable) {
return getThreadPool().submit(callable);
}
/**
* dcs获取线程池
*
* @return 线程池对象
*/
public static ThreadPoolExecutor getThreadPool() {
if (threadPool != null) {
return threadPool;
} else {
synchronized (ThreadPoolUtil.class) {
if (threadPool == null) {
threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
return threadPool;
}
return threadPool;
}
}
}
public static ThreadPoolExecutor getPool() {
return pool;
}
public static void main(String[] args) {
System.out.println(pool.getPoolSize());
}
}

View File

@@ -0,0 +1,102 @@
package cn.lili.common.utils;
import com.xkcoding.http.util.MapUtil;
import lombok.Setter;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* <p>
* 构造URL
* </p>
*
* @author yangkai.shen (https://xkcoding.com)
* @since 1.9.0
*/
@Setter
public class UrlBuilder {
private final Map<String, String> params = new LinkedHashMap<>(7);
private String baseUrl;
private UrlBuilder() {
}
/**
* @param baseUrl 基础路径
* @return the new {@code UrlBuilder}
*/
public static UrlBuilder fromBaseUrl(String baseUrl) {
UrlBuilder builder = new UrlBuilder();
builder.setBaseUrl(baseUrl);
return builder;
}
/**
* 只读的参数Map
*
* @return unmodifiable Map
* @since 1.15.0
*/
public Map<String, Object> getReadOnlyParams() {
return Collections.unmodifiableMap(params);
}
/**
* 添加参数
*
* @param key 参数名称
* @param value 参数值
* @return this UrlBuilder
*/
public UrlBuilder queryParam(String key, Object value) {
if (StringUtils.isEmpty(key)) {
throw new RuntimeException("参数名不能为空");
}
String valueAsString = (value != null ? value.toString() : null);
this.params.put(key, valueAsString);
return this;
}
/**
* 添加参数
*
* @param value 参数值
* @return this UrlBuilder
*/
public UrlBuilder pathAppend(String value) {
if (StringUtils.isEmpty(value)) {
throw new RuntimeException("参数不能为空");
}
this.setBaseUrl(this.baseUrl += value);
return this;
}
/**
* 构造url
*
* @return url
*/
public String build() {
return this.build(false);
}
/**
* 构造url
*
* @param encode 转码
* @return url
*/
public String build(boolean encode) {
if (MapUtil.isEmpty(this.params)) {
return this.baseUrl;
}
String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
String paramString = MapUtil.parseMapToString(this.params, encode);
return baseUrl + paramString;
}
}

View File

@@ -0,0 +1,83 @@
package cn.lili.common.utils;
import eu.bitwalker.useragentutils.DeviceType;
import eu.bitwalker.useragentutils.UserAgent;
import javax.servlet.http.HttpServletRequest;
/**
* 客户端类型统计
*
* @author Chopper
* @version v1.0
* 2021-02-26 10:53
*/
public class UserAgentUtils {
/**
* 获取设备类型
*
* @param request
* @return
*/
public static DeviceType getDeviceType(HttpServletRequest request) {
return getUserAgent(request).getOperatingSystem().getDeviceType();
}
/**
* 是否是PC
*
* @param request
* @return
*/
public static boolean isComputer(HttpServletRequest request) {
return DeviceType.COMPUTER.equals(getDeviceType(request));
}
/**
* 是否是手机
*
* @param request
* @return
*/
public static boolean isMobile(HttpServletRequest request) {
return DeviceType.MOBILE.equals(getDeviceType(request));
}
/**
* 是否是平板
*
* @param request
* @return
*/
public static boolean isTablet(HttpServletRequest request) {
return DeviceType.TABLET.equals(getDeviceType(request));
}
/**
* 是否是手机和平板
*
* @param request
* @return
*/
public static boolean isMobileOrTablet(HttpServletRequest request) {
DeviceType deviceType = getDeviceType(request);
return DeviceType.MOBILE.equals(deviceType) || DeviceType.TABLET.equals(deviceType);
}
/**
* 获取用户代理对象
*
* @param request
* @return
*/
public static UserAgent getUserAgent(HttpServletRequest request) {
return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
}
}

View File

@@ -0,0 +1,32 @@
package cn.lili.common.validation;
import cn.lili.common.validation.impl.MobileValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 手机号码校验注解
*
* @author Bulbasaur
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {MobileValidator.class})
public @interface Mobile {
String regexp() default "1[3|4|5|7|8]\\d{9}";
String message() default "手机号码格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,25 @@
package cn.lili.common.validation.impl;
import cn.lili.common.validation.Mobile;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private static Pattern pattern = Pattern.compile("^0?(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])[0-9]{8}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
Matcher m = pattern.matcher(value);
return m.matches();
}
@Override
public void initialize(Mobile constraintAnnotation) {
}
}

View File

@@ -0,0 +1,136 @@
package cn.lili.common.verification;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* ImageUtil
*
* @author Chopper
* @version v1.0
* 2020-11-17 14:50
*/
public class ImageUtil {
/**
* 添加水印
*
* @param oriImage
* @param text
* @throws IOException
*/
public static void addWatermark(BufferedImage oriImage, String text) {
Graphics2D graphics2D = oriImage.createGraphics();
// 设置水印文字颜色
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// 设置水印文字Font
graphics2D.setColor(Color.black);
// 设置水印文字透明度
graphics2D.setFont(new Font("宋体", Font.BOLD, 30));
// 第一参数->设置的内容,后面两个参数->文字在图片上的坐标位置(x,y)
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.4f));
graphics2D.drawString(text, 10, 40);
graphics2D.dispose();
}
/**
* @param oriImage 原图
* @param templateImage 模板图
* @param newImage 新抠出的小图
* @param x 随机扣取坐标X
* @param y 随机扣取坐标y
*/
public static void cutByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage newImage,
int x, int y) {
// 临时数组遍历用于高斯模糊存周边像素值
int[][] matrix = new int[3][3];
int[] values = new int[9];
int xLength = templateImage.getWidth();
int yLength = templateImage.getHeight();
// 模板图像宽度
for (int i = 0; i < xLength; i++) {
// 模板图片高度
for (int j = 0; j < yLength; j++) {
// 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
int rgb = templateImage.getRGB(i, j);
if (rgb < 0) {
newImage.setRGB(i, j, oriImage.getRGB(x + i, y + j));
// 抠图区域高斯模糊
readPixel(oriImage, x + i, y + j, values);
fillMatrix(matrix, values);
oriImage.setRGB(x + i, y + j, avgMatrix(matrix));
}
// 防止数组越界判断
if (i == (xLength - 1) || j == (yLength - 1)) {
continue;
}
int rightRgb = templateImage.getRGB(i + 1, j);
int downRgb = templateImage.getRGB(i, j + 1);
// 描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0)
|| (rgb < 0 && downRgb >= 0)) {
newImage.setRGB(i, j, Color.GRAY.getRGB());
// oriImage.setRGB(x + i, y + j, Color.white.getRGB());
}
}
}
}
public static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
int xStart = x - 1;
int yStart = y - 1;
int current = 0;
for (int i = xStart; i < 3 + xStart; i++)
for (int j = yStart; j < 3 + yStart; j++) {
int tx = i;
if (tx < 0) {
tx = -tx;
} else if (tx >= img.getWidth()) {
tx = x;
}
int ty = j;
if (ty < 0) {
ty = -ty;
} else if (ty >= img.getHeight()) {
ty = y;
}
pixels[current++] = img.getRGB(tx, ty);
}
}
public static void fillMatrix(int[][] matrix, int[] values) {
int filled = 0;
for (int[] x : matrix) {
for (int j = 0; j < x.length; j++) {
x[j] = values[filled++];
}
}
}
public static int avgMatrix(int[][] matrix) {
int r = 0;
int g = 0;
int b = 0;
for (int[] x : matrix) {
for (int j = 0; j < x.length; j++) {
if (j == 1) {
continue;
}
Color c = new Color(x[j]);
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
return new Color(r / 8, g / 8, b / 8).getRGB();
}
}

View File

@@ -0,0 +1,105 @@
package cn.lili.common.verification;
import cn.lili.common.utils.Base64DecodeMultipartFile;
import cn.lili.common.vo.SerializableStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 验证码工具
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/17 14:34
*/
@Component
@Slf4j
public class SliderImageUtil {
private static final int BOLD = 5;
private static final String IMG_FILE_TYPE = "jpg";
private static final String TEMP_IMG_FILE_TYPE = "png";
/**
* 根据模板切图
*
* @param sliderFile
* @param originalFile
* @return
* @throws Exception sliderFile, originalFile
*/
public static Map<String, Object> pictureTemplatesCut(SerializableStream sliderFile, SerializableStream originalFile) throws Exception {
Random random = new Random();
Map<String, Object> pictureMap = new HashMap<>();
// 拼图
BufferedImage sliderImage = ImageIO.read(Base64DecodeMultipartFile.base64ToInputStream(sliderFile.getBase64()));
int sliderWidth = sliderImage.getWidth();
int sliderHeight = sliderImage.getHeight();
// 原图
BufferedImage originalImage = ImageIO.read(Base64DecodeMultipartFile.base64ToInputStream(originalFile.getBase64()));
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
// 随机生成抠图坐标X,Y
// X轴距离右端targetWidth Y轴距离底部targetHeight以上
int randomX = random.nextInt(originalWidth - 3 * sliderWidth) + 2 * sliderWidth;
int randomY = random.nextInt(originalHeight - sliderHeight);
log.info("原图大小{} x {},随机生成的坐标 X,Y 为({}{}", originalWidth, originalHeight, randomX, randomY);
// 新建一个和模板一样大小的图像TYPE_4BYTE_ABGR表示具有8位RGBA颜色分量的图像正常取imageTemplate.getType()
BufferedImage newImage = new BufferedImage(sliderWidth, sliderHeight, sliderImage.getType());
// 得到画笔对象
Graphics2D graphics = newImage.createGraphics();
// 如果需要生成RGB格式需要做如下配置,Transparency 设置透明
newImage = graphics.getDeviceConfiguration().createCompatibleImage(sliderWidth, sliderHeight,
Transparency.TRANSLUCENT);
// 新建的图像根据模板颜色赋值,源图生成遮罩
ImageUtil.cutByTemplate(originalImage, sliderImage, newImage, randomX, randomY);
// 设置“抗锯齿”的属性
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(new BasicStroke(BOLD, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics.drawImage(newImage, 0, 0, null);
graphics.dispose();
//添加水印
ImageUtil.addWatermark(originalImage, "请滑动拼图");
ByteArrayOutputStream newImageOs = new ByteArrayOutputStream();// 新建流。
ImageIO.write(newImage, TEMP_IMG_FILE_TYPE, newImageOs);// 利用ImageIO类提供的write方法将bi以png图片的数据模式写入流。
byte[] newImagery = newImageOs.toByteArray();
ByteArrayOutputStream oriImagesOs = new ByteArrayOutputStream();// 新建流。
ImageIO.write(originalImage, IMG_FILE_TYPE, oriImagesOs);// 利用ImageIO类提供的write方法将bi以jpg图片的数据模式写入流。
byte[] oriImageByte = oriImagesOs.toByteArray();
pictureMap.put("slidingImage", "data:image/png;base64," + Base64Utils.encodeToString(newImagery));
pictureMap.put("backImage", "data:image/png;base64," + Base64Utils.encodeToString(oriImageByte));
// x轴
pictureMap.put("randomX", randomX);
// y轴
pictureMap.put("randomY", randomY);
pictureMap.put("originalHeight", originalHeight);
pictureMap.put("originalWidth", originalWidth);
pictureMap.put("sliderHeight", sliderHeight);
pictureMap.put("sliderWidth", sliderWidth);
return pictureMap;
}
}

View File

@@ -0,0 +1,36 @@
package cn.lili.common.verification;
import cn.lili.common.cache.Cache;
import cn.lili.common.cache.CachePrefix;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 验证码sdk
*
* @author Chopper
* @version v4.0
* @Description:
* @since 2020/11/17 15:43
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class VerificationSDK {
private final Cache cache;
/**
* 生成一个token 用于获取校验中心的验证码逻辑
*
* @return
*/
public boolean checked(String verificationKey, String uuid) {
//生成校验KEY在验证码服务做记录
String key = CachePrefix.VERIFICATION_KEY.getPrefix() + verificationKey;
cache.get(key);
return true;
}
}

View File

@@ -0,0 +1,46 @@
package cn.lili.common.verification.aop;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.verification.aop.annotation.Verification;
import cn.lili.common.verification.enums.VerificationEnums;
import cn.lili.common.verification.service.VerificationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
/**
* 验证码验证拦截
*
* @author Chopper
*/
@Aspect
@Configuration
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class VerificationInterceptor {
private final VerificationService verificationService;
@Before("@annotation(cn.lili.common.verification.aop.annotation.Verification)")
public void interceptor(JoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Verification verificationAnnotation = method.getAnnotation(Verification.class);
VerificationEnums verificationEnums = verificationAnnotation.type();
String uuid = verificationAnnotation.uuid();
boolean result = verificationService.check(uuid, verificationEnums);
if (result) {
return;
}
throw new ServiceException(ResultCode.VERIFICATION_ERROR);
}
}

View File

@@ -0,0 +1,32 @@
package cn.lili.common.verification.aop.annotation;
import cn.lili.common.verification.enums.VerificationEnums;
import java.lang.annotation.*;
/**
* 限流注解
*
* @author Chopper
* @since 2018-02-05
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Verification {
/**
* uuid
*
* @return String
*/
String uuid();
/**
* 验证类型
*
* @return
*/
VerificationEnums type();
}

View File

@@ -0,0 +1,26 @@
package cn.lili.common.verification.enums;
/**
* VerificationEnums
*
* @author Chopper
* @version v1.0
* 2020-12-22 19:11
*/
public enum VerificationEnums {
/**
* 登录
* 注册
* 找回用户
* 修改密码
* 支付钱包密码
*/
LOGIN,
REGISTER,
FIND_USER,
UPDATE_PASSWORD,
WALLET_PASSWORD;
}

View File

@@ -0,0 +1,37 @@
package cn.lili.common.verification.service;
import cn.lili.common.verification.enums.VerificationEnums;
import java.io.IOException;
import java.util.Map;
/**
* 验证码模块
*/
public interface VerificationService {
/**
* 获取校验对象
*
* @param verificationEnums 校验枚举
* @param uuid uuid
* @return 校验对象
*/
Map<String, Object> createVerification(VerificationEnums verificationEnums, String uuid) throws IOException;
/**
* 预校验
*
* @param xPos 位移距离
* @param uuid 用户唯一表示
* @param verificationEnums 校验枚举
* @return
*/
boolean preCheck(Integer xPos, String uuid, VerificationEnums verificationEnums);
/**
* @param uuid 用户唯一表示
* @param verificationEnums 校验枚举
* @return
*/
boolean check(String uuid, VerificationEnums verificationEnums);
}

View File

@@ -0,0 +1,178 @@
package cn.lili.common.verification.service.impl;
import cn.lili.common.cache.Cache;
import cn.lili.common.cache.CachePrefix;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.utils.StringUtils;
import cn.lili.common.verification.SliderImageUtil;
import cn.lili.common.verification.enums.VerificationEnums;
import cn.lili.common.verification.service.VerificationService;
import cn.lili.common.vo.SerializableStream;
import cn.lili.modules.base.entity.dos.VerificationSource;
import cn.lili.modules.base.entity.vo.VerificationVO;
import cn.lili.modules.base.service.VerificationSourceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* 认证处理类
*
* @author Chopper
* @version v1.0
* 2020-11-17 14:59
*/
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class VerificationServiceImpl implements VerificationService {
private final VerificationSourceService verificationSourceService;
private final Cache cache;
/**
* 创建校验
*
* @return 验证码参数
*/
@Override
public Map<String, Object> createVerification(VerificationEnums verificationEnums, String uuid) throws IOException {
if (uuid == null) {
throw new ServiceException("非法请求,请重新刷新页面操作");
}
//获取验证码配置
VerificationVO verificationVO = verificationSourceService.getVerificationCache();
List<VerificationSource> verificationResources = verificationVO.getVerificationResources();
List<VerificationSource> verificationSlider = verificationVO.getVerificationSlider();
Random random = new Random();
// 随机选择需要切的图下标
int resourceNum = random.nextInt(verificationResources.size());
// 随机选择剪切模版下标
int sliderNum = random.nextInt(verificationSlider.size());
// 随机选择需要切的图片地址
String originalResource = verificationResources.get(resourceNum).getResource();
// 随机选择剪切模版图片地址
String sliderResource = verificationSlider.get(sliderNum).getResource();
try {
//获取缓存中的资源
SerializableStream originalFile = getInputStream(originalResource);
SerializableStream sliderFile = getInputStream(sliderResource);
Map<String, Object> resultMap = SliderImageUtil.pictureTemplatesCut(sliderFile, originalFile);
// 生成验证参数 120可以验证 无需手动清除120秒有效时间自动清除
cache.put(cacheKey(verificationEnums, uuid), resultMap.get("randomX"), 120L);
resultMap.put("key", cacheKey(verificationEnums, uuid));
// 移除横坐标移动距离
resultMap.remove("randomX");
return resultMap;
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据网络地址,获取源文件
* 这里简单说一下这里是将不可序列化的inputstream序列化对象存入redis缓存
* @param originalResource
* @return
*/
private SerializableStream getInputStream(String originalResource) throws Exception {
Object object = cache.get(CachePrefix.VERIFICATION_IMAGE.getPrefix() + originalResource);
if (object != null) {
return (SerializableStream) object;
}
if (StringUtils.isNotEmpty(originalResource)) {
URL url = new URL(originalResource);
InputStream inputStream = url.openStream();
SerializableStream serializableStream = new SerializableStream(inputStream);
cache.put(CachePrefix.VERIFICATION_IMAGE.getPrefix() + originalResource, serializableStream);
return serializableStream;
}
return null;
}
/**
* 预校验图片 用于前端回显
*
* @param xPos X轴移动距离
* @param verificationEnums 验证key
* @return 验证是否成功
*/
@Override
public boolean preCheck(Integer xPos, String uuid, VerificationEnums verificationEnums) {
Integer randomX = (Integer) cache.get(cacheKey(verificationEnums, uuid));
if (randomX == null) {
return false;
}
log.debug("{}{}", randomX, xPos);
//验证结果
boolean result = Math.abs(randomX - xPos) < 3;
if (result) {
//验证成功,则记录验证结果 验证有效时间120秒
cache.put(cacheResult(verificationEnums, uuid), true, 120L);
return result;
}
return false;
}
/**
* 验证码校验
*
* @param uuid 用户标识
* @param verificationEnums 验证key
* @return 验证是否成功
*/
@Override
public boolean check(String uuid, VerificationEnums verificationEnums) {
Object object = cache.get(cacheResult(verificationEnums, uuid));
if (object == null) {
return false;
} else {
cache.remove(cacheResult(verificationEnums, uuid));
return true;
}
}
/**
* 生成缓存key 记录缓存需要验证的内容
*
* @param verificationEnums 验证码枚举
* @param uuid 用户uuid
* @return 缓存key
*/
public static String cacheKey(VerificationEnums verificationEnums, String uuid) {
return CachePrefix.VERIFICATION_KEY.getPrefix() + verificationEnums.name() + uuid;
}
/**
* 生成缓存key 记录缓存验证的结果
*
* @param verificationEnums 验证码枚举
* @param uuid 用户uuid
* @return 缓存key
*/
public static String cacheResult(VerificationEnums verificationEnums, String uuid) {
return CachePrefix.VERIFICATION_RESULT.getPrefix() + verificationEnums.name() + uuid;
}
}

View File

@@ -0,0 +1,55 @@
package cn.lili.common.vo;
import cn.lili.common.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 查询参数
*
* @author Chopper
* @date 2020/11/26 14:43
*/
@Data
public class PageVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "页号")
private Integer pageNumber = 1;
@ApiModelProperty(value = "页面大小")
private Integer pageSize = 10;
@ApiModelProperty(value = "排序字段")
private String sort;
@ApiModelProperty(value = "排序方式 asc/desc")
private String order;
@ApiModelProperty(value = "需要驼峰转换蛇形", notes = "一般不做处理,如果数据库中就是蛇形,则这块需要处理。")
private Boolean notConvert;
public Integer getMongoPageNumber() {
int i = pageNumber - 1;
if (i < 0) {
return pageNumber;
} else {
return i;
}
}
public String getSort() {
if (!StringUtils.isEmpty(sort)) {
if (notConvert == null || Boolean.FALSE.equals(notConvert)) {
return StringUtils.camel2Underline(sort);
} else {
return sort;
}
}
return sort;
}
}

View File

@@ -0,0 +1,43 @@
package cn.lili.common.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 前后端交互VO
*
* @author Chopper
* @date 2020/11/26 14:40
*/
@Data
public class ResultMessage<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标志
*/
private boolean success;
/**
* 消息
*/
private String message;
/**
* 返回代码
*/
private Integer code;
/**
* 时间戳
*/
private long timestamp = System.currentTimeMillis();
/**
* 结果对象
*/
private T result;
}

Some files were not shown because too many files have changed in this diff Show More