Commit fd9fb2a6 authored by dqjdda's avatar dqjdda
Browse files

Merge branch '2.3dev'

parents 7895e547 1839ef8d
...@@ -21,13 +21,12 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前 ...@@ -21,13 +21,12 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前
| | 后端源码 | 前端源码 | | | 后端源码 | 前端源码 |
|--- |--- | --- | |--- |--- | --- |
| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-qd | | github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-web |
| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-qt | | 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-web |
#### 系统功能 #### 系统功能
- 用户管理:提供用户的相关配置,新增用户后,默认密码为123456 - 用户管理:提供用户的相关配置,新增用户后,默认密码为123456
- 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限 - 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
- 权限管理:权限细化到接口,可以理解成按钮权限
- 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单 - 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
- 部门管理:可配置系统组织架构,树形表格展示 - 部门管理:可配置系统组织架构,树形表格展示
- 岗位管理:配置各个部门的职位 - 岗位管理:配置各个部门的职位
...@@ -44,16 +43,19 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前 ...@@ -44,16 +43,19 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前
- 支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试 - 支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试
#### 项目结构 #### 项目结构
项目采用分模块开发方式,将通用的配置放在公共模块,```system```模块为系统核心模块也是项目入口模块,```logging``` 模块为系统的日志模块,```tools``` 为第三方工具模块,包含了图床、邮件、七牛云、支付宝,```generator``` 为系统的代码生成模块 项目采用按功能分模块开发方式,将通用的配置放在公共模块,```system```模块为系统核心模块也是项目入口模块,```logging``` 模块为系统的日志模块,```tools``` 为第三方工具模块,包含了图床、邮件、七牛云、支付宝,```generator``` 为系统的代码生成模块
- eladmin-common 公共模块 - eladmin-common 公共模块
- exception 项目统一异常的处理 - annotation 为系统自定义注解
- mapper mapstruct的通用mapper - aspect 自定义注解的切面
- redis redis缓存相关配置 - base 提供了Entity、DTO基类和mapstruct的通用mapper
- swagger2 接口文档配置 - config 自定义权限实现、redis配置、swagger配置
- utils 系统通用工具类 - exception 项目统一异常的处理
- utils 系统通用工具类
- eladmin-system 系统核心模块(系统启动入口) - eladmin-system 系统核心模块(系统启动入口)
- config 配置跨域与静态资源,与数据权限 - config 配置跨域与静态资源,与数据权限
- modules 系统相关模块(登录授权、定时任务等) - thread 线程池相关
- modules 系统相关模块(登录授权、系统监控、定时任务等)
- eladmin-logging 系统日志模块 - eladmin-logging 系统日志模块
- eladmin-tools 系统第三方工具模块 - eladmin-tools 系统第三方工具模块
- eladmin-generator 系统代码生成模块 - eladmin-generator 系统代码生成模块
...@@ -78,6 +80,6 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前 ...@@ -78,6 +80,6 @@ eladmin基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前
</table> </table>
#### 项目捐赠 #### 项目捐赠
项目的发展离不开你的支持,请作者喝杯咖啡吧 ☕! [Donate](https://docs.auauz.net/#/jz) 项目的发展离不开你的支持,请作者喝杯咖啡吧!ps:辣条也行 ☕! [Donate](https://docs.auauz.net/#/jz)
#### 反馈交流 #### 反馈交流
- QQ交流群:891137268 - QQ交流群:891137268
...@@ -5,11 +5,10 @@ ...@@ -5,11 +5,10 @@
<parent> <parent>
<artifactId>eladmin</artifactId> <artifactId>eladmin</artifactId>
<groupId>me.zhengjie</groupId> <groupId>me.zhengjie</groupId>
<version>2.2</version> <version>2.3</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>eladmin-common</artifactId> <artifactId>eladmin-common</artifactId>
<name>公共模块</name> <name>公共模块</name>
</project> </project>
\ No newline at end of file
package me.zhengjie.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author jacky
* 用于标记匿名访问方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
...@@ -13,45 +13,42 @@ import java.lang.annotation.Target; ...@@ -13,45 +13,42 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Query { public @interface Query {
/** Dong ZhaoYang 2017/8/7 基本对象的属性名 */ // Dong ZhaoYang 2017/8/7 基本对象的属性名
String propName() default ""; String propName() default "";
/** Dong ZhaoYang 2017/8/7 查询方式 */ // Dong ZhaoYang 2017/8/7 查询方式
Type type() default Type.EQUAL; Type type() default Type.EQUAL;
/** /**
* 连接查询的属性名,如User类中的dept * 连接查询的属性名,如User类中的dept
* @return
*/ */
String joinName() default ""; String joinName() default "";
/** /**
* 默认左连接 * 默认左连接
* @return
*/ */
Join join() default Join.LEFT; Join join() default Join.LEFT;
/** /**
* 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username") * 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
* @return
*/ */
String blurry() default ""; String blurry() default "";
enum Type { enum Type {
/** jie 2019/6/4 相等 */ // jie 2019/6/4 相等
EQUAL EQUAL
/** Dong ZhaoYang 2017/8/7 大于等于 */ // Dong ZhaoYang 2017/8/7 大于等于
, GREATER_THAN , GREATER_THAN
/** Dong ZhaoYang 2017/8/7 小于等于 */ // Dong ZhaoYang 2017/8/7 小于等于
, LESS_THAN , LESS_THAN
/** Dong ZhaoYang 2017/8/7 中模糊查询 */ // Dong ZhaoYang 2017/8/7 中模糊查询
, INNER_LIKE , INNER_LIKE
/** Dong ZhaoYang 2017/8/7 左模糊查询 */ // Dong ZhaoYang 2017/8/7 左模糊查询
, LEFT_LIKE , LEFT_LIKE
/** Dong ZhaoYang 2017/8/7 右模糊查询 */ // Dong ZhaoYang 2017/8/7 右模糊查询
, RIGHT_LIKE , RIGHT_LIKE
/** Dong ZhaoYang 2017/8/7 小于 */ // Dong ZhaoYang 2017/8/7 小于
, LESS_THAN_NQ , LESS_THAN_NQ
//** jie 2019/6/4 包含 */ // jie 2019/6/4 包含
, IN , IN
} }
......
...@@ -12,7 +12,6 @@ import org.aspectj.lang.annotation.Pointcut; ...@@ -12,7 +12,6 @@ import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript; import org.springframework.data.redis.core.script.RedisScript;
...@@ -23,10 +22,13 @@ import java.lang.reflect.Method; ...@@ -23,10 +22,13 @@ import java.lang.reflect.Method;
@Aspect @Aspect
@Component @Component
public class LimitAspect { public class LimitAspect {
@Autowired
private RedisTemplate redisTemplate; private final RedisTemplate<Object,Object> redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class); private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
public LimitAspect(RedisTemplate<Object,Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Pointcut("@annotation(me.zhengjie.annotation.Limit)") @Pointcut("@annotation(me.zhengjie.annotation.Limit)")
public void pointcut() { public void pointcut() {
...@@ -41,20 +43,18 @@ public class LimitAspect { ...@@ -41,20 +43,18 @@ public class LimitAspect {
LimitType limitType = limit.limitType(); LimitType limitType = limit.limitType();
String key = limit.key(); String key = limit.key();
if (StringUtils.isEmpty(key)) { if (StringUtils.isEmpty(key)) {
switch (limitType) { if (limitType == LimitType.IP) {
case IP: key = StringUtils.getIp(request);
key = StringUtils.getIP(request); } else {
break; key = signatureMethod.getName();
default:
key = signatureMethod.getName();
} }
} }
ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_"))); ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_")));
String luaScript = buildLuaScript(); String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class); RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = (Number) redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
if (null != count && count.intValue() <= limit.count()) { if (null != count && count.intValue() <= limit.count()) {
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name()); logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
return joinPoint.proceed(); return joinPoint.proceed();
......
package me.zhengjie.aspect; package me.zhengjie.aspect;
/**
* 限流枚举
* @author /
*/
public enum LimitType { public enum LimitType {
// 默认
CUSTOMER, CUSTOMER,
// by ip addr // by ip addr
IP; IP;
} }
package me.zhengjie.base;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author Zheng Jie
* @Date 2019年10月24日20:48:53
*/
@Getter
@Setter
public class BaseDTO implements Serializable {
private Boolean isDelete;
private Timestamp createTime;
private Timestamp updateTime;
}
package me.zhengjie.base;
import lombok.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.sql.Timestamp;
import java.lang.reflect.Field;
/**
* @author Zheng Jie
* @Date 2019年10月24日20:46:32
*/
@Getter
@Setter
@MappedSuperclass
public class BaseEntity implements Serializable {
// 删除标识
@Column(name = "is_delete", columnDefinition = "bit default 0")
private Boolean isDelete = false;
@Column(name = "create_time")
@CreationTimestamp
private Timestamp createTime;
@Column(name = "update_time")
@UpdateTimestamp
private Timestamp updateTime;
public @interface Update {}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
package me.zhengjie.mapper; package me.zhengjie.base;
import java.util.List; import java.util.List;
...@@ -6,33 +6,25 @@ import java.util.List; ...@@ -6,33 +6,25 @@ import java.util.List;
* @author Zheng Jie * @author Zheng Jie
* @date 2018-11-23 * @date 2018-11-23
*/ */
public interface EntityMapper<D, E> { public interface BaseMapper<D, E> {
/** /**
* DTO转Entity * DTO转Entity
* @param dto
* @return
*/ */
E toEntity(D dto); E toEntity(D dto);
/** /**
* Entity转DTO * Entity转DTO
* @param entity
* @return
*/ */
D toDto(E entity); D toDto(E entity);
/** /**
* DTO集合转Entity集合 * DTO集合转Entity集合
* @param dtoList
* @return
*/ */
List <E> toEntity(List<D> dtoList); List <E> toEntity(List<D> dtoList);
/** /**
* Entity集合转DTO集合 * Entity集合转DTO集合
* @param entityList
* @return
*/ */
List <D> toDto(List<E> entityList); List <D> toDto(List<E> entityList);
} }
package me.zhengjie.config;
import me.zhengjie.utils.SecurityUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service(value = "el")
public class ElPermissionConfig {
public Boolean check(String ...permissions){
// 如果是匿名访问的,就放行
String anonymous = "anonymous";
if(Arrays.asList(permissions).contains(anonymous)){
return true;
}
// 获取当前用户的所有权限
List<String> elPermissions = SecurityUtils.getUserDetails().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// 判断当前用户的所有权限是否包含接口上定义的权限
List<String> list = Arrays.stream(permissions).filter(elPermissions::contains).collect(Collectors.toList());
return elPermissions.contains("admin") || list.size() != 0;
}
}
package me.zhengjie.redis; package me.zhengjie.config;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhengjie.utils.StringUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
...@@ -19,7 +23,12 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; ...@@ -19,7 +23,12 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Zheng Jie * @author Zheng Jie
...@@ -28,21 +37,19 @@ import java.time.Duration; ...@@ -28,21 +37,19 @@ import java.time.Duration;
@Slf4j @Slf4j
@Configuration @Configuration
@EnableCaching @EnableCaching
// 自动配置
@ConditionalOnClass(RedisOperations.class) @ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) @EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport { public class RedisConfig extends CachingConfigurerSupport {
/** /**
* 设置 redis 数据默认过期时间,默认1天 * 设置 redis 数据默认过期时间,默认6小时
* 设置@cacheable 序列化方式 * 设置@cacheable 序列化方式
* @return
*/ */
@Bean @Bean
public RedisCacheConfiguration redisCacheConfiguration(){ public RedisCacheConfiguration redisCacheConfiguration(){
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(1)); configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(6));
return configuration; return configuration;
} }
...@@ -51,21 +58,20 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -51,21 +58,20 @@ public class RedisConfig extends CachingConfigurerSupport {
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>(); RedisTemplate<Object, Object> template = new RedisTemplate<>();
//序列化 //序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// value值的序列化采用fastJsonRedisSerializer // value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer); template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer);
// 全局开启AutoType,这里方便开发,使用全局的方式
// 全局开启AutoType,不建议使用 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单 // 建议使用这种方式,小范围指定白名单
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.service.dto"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.service.dto");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.domain"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.domain");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.domain"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.domain");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.monitor.domain"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.monitor.domain");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.security"); // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.security");
// key的序列化采用StringRedisSerializer // key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer()); template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer());
...@@ -75,20 +81,27 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -75,20 +81,27 @@ public class RedisConfig extends CachingConfigurerSupport {
/** /**
* 自定义缓存key生成策略,默认将使用该策略 * 自定义缓存key生成策略,默认将使用该策略
* 使用方法 @Cacheable
* @return
*/ */
@Bean @Bean
@Override @Override
public KeyGenerator keyGenerator() { public KeyGenerator keyGenerator() {
return (target, method, params) -> { return (target, method, params) -> {
StringBuilder sb = new StringBuilder(); Map<String,Object> container = new HashMap<>();
sb.append(target.getClass().getName()); Class<?> targetClassClass = target.getClass();
sb.append(method.getName()); // 类地址
for (Object obj : params) { container.put("class",targetClassClass.toGenericString());
sb.append(JSON.toJSONString(obj).hashCode()); // 方法名称
container.put("methodName",method.getName());
// 包名称
container.put("package",targetClassClass.getPackage());
// 参数列表
for (int i = 0; i < params.length; i++) {
container.put(String.valueOf(i),params[i]);
} }
return sb.toString(); // 转为JSON字符串
String jsonString = JSON.toJSONString(container);
// 做SHA256 Hash计算,得到一个SHA256摘要作为Key
return DigestUtils.sha256Hex(jsonString);
}; };
} }
...@@ -97,7 +110,7 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -97,7 +110,7 @@ public class RedisConfig extends CachingConfigurerSupport {
public CacheErrorHandler errorHandler() { public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走 // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler"); log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { return new CacheErrorHandler() {
@Override @Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) { public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e); log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
...@@ -118,7 +131,74 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -118,7 +131,74 @@ public class RedisConfig extends CachingConfigurerSupport {
log.error("Redis occur handleCacheClearError:", e); log.error("Redis occur handleCacheClearError:", e);
} }
}; };
return cacheErrorHandler;
} }
} }
/**
* Value 序列化
*
* @author /
* @param <T>
*/
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private Class<T> clazz;
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
}
@Override
public T deserialize(byte[] bytes) {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(str, clazz);
}
}
/**
* 重写序列化器
*
* @author /
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
private StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
}
package me.zhengjie.swagger2; package me.zhengjie.config;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import springfox.documentation.schema.ModelRef; import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter; import springfox.documentation.service.Parameter;
...@@ -15,6 +23,8 @@ import springfox.documentation.spring.web.plugins.Docket; ...@@ -15,6 +23,8 @@ import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
/** /**
* api页面 /swagger-ui.html * api页面 /swagger-ui.html
...@@ -33,9 +43,10 @@ public class SwaggerConfig { ...@@ -33,9 +43,10 @@ public class SwaggerConfig {
private Boolean enabled; private Boolean enabled;
@Bean @Bean
@SuppressWarnings("all")
public Docket createRestApi() { public Docket createRestApi() {
ParameterBuilder ticketPar = new ParameterBuilder(); ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<Parameter>(); List<Parameter> pars = new ArrayList<>();
ticketPar.name(tokenHeader).description("token") ticketPar.name(tokenHeader).description("token")
.modelRef(new ModelRef("string")) .modelRef(new ModelRef("string"))
.parameterType("header") .parameterType("header")
...@@ -55,8 +66,43 @@ public class SwaggerConfig { ...@@ -55,8 +66,43 @@ public class SwaggerConfig {
private ApiInfo apiInfo() { private ApiInfo apiInfo() {
return new ApiInfoBuilder() return new ApiInfoBuilder()
.title("eladmin 接口文档") .title("eladmin 接口文档")
.version("2.1") .version("2.3")
.build(); .build();
} }
} }
/**
* 将Pageable转换展示在swagger中
*/
@Configuration
class SwaggerDataConfig {
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
}
};
}
@ApiModel
@Data
private static class Page {
@ApiModelProperty("页码 (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
private Integer size;
@ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
private List<String> sort;
}
}
...@@ -2,7 +2,6 @@ package me.zhengjie.exception; ...@@ -2,7 +2,6 @@ package me.zhengjie.exception;
import lombok.Getter; import lombok.Getter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.BAD_REQUEST;
/** /**
......
...@@ -2,33 +2,18 @@ package me.zhengjie.exception; ...@@ -2,33 +2,18 @@ package me.zhengjie.exception;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
/** /**
* @author Zheng Jie * @author Zheng Jie
* @date 2018-11-23 * @date 2018-11-23
*/ */
public class EntityExistException extends RuntimeException { public class EntityExistException extends RuntimeException {
public EntityExistException(Class clazz, Object... saveBodyParamsMap) { public EntityExistException(Class clazz, String field, String val) {
super(EntityExistException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, saveBodyParamsMap))); super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
}
private static String generateMessage(String entity, Map<String, String> saveBodyParams) {
return StringUtils.capitalize(entity) +
" 已存在 " +
saveBodyParams;
} }
private static <K, V> Map<K, V> toMap( private static String generateMessage(String entity, String field, String val) {
Class<K> keyType, Class<V> valueType, Object... entries) { return StringUtils.capitalize(entity)
if (entries.length % 2 == 1) + " with " + field + " "+ val + " existed";
throw new IllegalArgumentException("Invalid entries");
return IntStream.range(0, entries.length / 2).map(i -> i * 2)
.collect(HashMap::new,
(m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])),
Map::putAll);
} }
} }
\ No newline at end of file
...@@ -2,34 +2,18 @@ package me.zhengjie.exception; ...@@ -2,34 +2,18 @@ package me.zhengjie.exception;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
/** /**
* @author Zheng Jie * @author Zheng Jie
* @date 2018-11-23 * @date 2018-11-23
*/ */
public class EntityNotFoundException extends RuntimeException { public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(Class clazz, Object... searchParamsMap) { public EntityNotFoundException(Class clazz, String field, String val) {
super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap))); super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
}
private static String generateMessage(String entity, Map<String, String> searchParams) {
return StringUtils.capitalize(entity) +
" 不存在 " +
searchParams;
} }
private static <K, V> Map<K, V> toMap( private static String generateMessage(String entity, String field, String val) {
Class<K> keyType, Class<V> valueType, Object... entries) { return StringUtils.capitalize(entity)
if (entries.length % 2 == 1) + " with " + field + " "+ val + " does not exist";
throw new IllegalArgumentException("Invalid entries");
return IntStream.range(0, entries.length / 2).map(i -> i * 2)
.collect(HashMap::new,
(m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])),
Map::putAll);
} }
} }
\ No newline at end of file
...@@ -12,7 +12,7 @@ import java.time.LocalDateTime; ...@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
@Data @Data
class ApiError { class ApiError {
private Integer status; private Integer status = 400;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp; private LocalDateTime timestamp;
private String message; private String message;
...@@ -21,10 +21,17 @@ class ApiError { ...@@ -21,10 +21,17 @@ class ApiError {
timestamp = LocalDateTime.now(); timestamp = LocalDateTime.now();
} }
public ApiError(Integer status,String message) { public static ApiError error(String message){
this(); ApiError apiError = new ApiError();
this.status = status; apiError.setMessage(message);
this.message = message; return apiError;
}
public static ApiError error(Integer status, String message){
ApiError apiError = new ApiError();
apiError.setStatus(status);
apiError.setMessage(message);
return apiError;
} }
} }
......
...@@ -11,6 +11,7 @@ import org.springframework.security.access.AccessDeniedException; ...@@ -11,6 +11,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Objects;
import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.*;
/** /**
...@@ -23,91 +24,73 @@ public class GlobalExceptionHandler { ...@@ -23,91 +24,73 @@ public class GlobalExceptionHandler {
/** /**
* 处理所有不可知的异常 * 处理所有不可知的异常
* @param e
* @return
*/ */
@ExceptionHandler(Throwable.class) @ExceptionHandler(Throwable.class)
public ResponseEntity handleException(Throwable e){ public ResponseEntity handleException(Throwable e){
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage()); return buildResponseEntity(ApiError.error(e.getMessage()));
return buildResponseEntity(apiError);
} }
/** /**
* 处理 接口无权访问异常AccessDeniedException * 处理 接口无权访问异常AccessDeniedException
* @param e
* @return
*/ */
@ExceptionHandler(AccessDeniedException.class) @ExceptionHandler(AccessDeniedException.class)
public ResponseEntity handleAccessDeniedException(AccessDeniedException e){ public ResponseEntity handleAccessDeniedException(AccessDeniedException e){
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(FORBIDDEN.value(),e.getMessage()); return buildResponseEntity(ApiError.error(FORBIDDEN.value(),e.getMessage()));
return buildResponseEntity(apiError);
} }
/** /**
* 处理自定义异常 * 处理自定义异常
* @param e
* @return
*/ */
@ExceptionHandler(value = BadRequestException.class) @ExceptionHandler(value = BadRequestException.class)
public ResponseEntity<ApiError> badRequestException(BadRequestException e) { public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(e.getStatus(),e.getMessage()); return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));
return buildResponseEntity(apiError);
} }
/** /**
* 处理 EntityExist * 处理 EntityExist
* @param e
* @return
*/ */
@ExceptionHandler(value = EntityExistException.class) @ExceptionHandler(value = EntityExistException.class)
public ResponseEntity<ApiError> entityExistException(EntityExistException e) { public ResponseEntity<ApiError> entityExistException(EntityExistException e) {
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage()); return buildResponseEntity(ApiError.error(e.getMessage()));
return buildResponseEntity(apiError);
} }
/** /**
* 处理 EntityNotFound * 处理 EntityNotFound
* @param e
* @return
*/ */
@ExceptionHandler(value = EntityNotFoundException.class) @ExceptionHandler(value = EntityNotFoundException.class)
public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) { public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(NOT_FOUND.value(),e.getMessage()); return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage()));
return buildResponseEntity(apiError);
} }
/** /**
* 处理所有接口数据验证异常 * 处理所有接口数据验证异常
* @param e
* @returns
*/ */
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
// 打印堆栈信息 // 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e)); log.error(ThrowableUtil.getStackTrace(e));
String[] str = e.getBindingResult().getAllErrors().get(0).getCodes()[1].split("\\."); String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
StringBuffer msg = new StringBuffer(str[1]+":"); String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
msg.append(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); if("不能为空".equals(message)){
ApiError apiError = new ApiError(BAD_REQUEST.value(),msg.toString()); message = str[1] + ":" + message;
return buildResponseEntity(apiError); }
return buildResponseEntity(ApiError.error(message));
} }
/** /**
* 统一返回 * 统一返回
* @param apiError
* @return
*/ */
private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
return new ResponseEntity(apiError, HttpStatus.valueOf(apiError.getStatus())); return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
} }
} }
package me.zhengjie.redis;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* Value 序列化
*
* @author /
* @param <T>
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
package me.zhengjie.redis;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.Charset;
/**
* 重写序列化器
*
* @author /
*/
public class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
private final String target = "\"";
private final String replacement = "";
public StringRedisSerializer() {
this(Charset.forName("UTF8"));
}
public StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (string == null) {
return null;
}
string = string.replace(target, replacement);
return string.getBytes(charset);
}
}
\ No newline at end of file
package me.zhengjie.swagger2; import com.fasterxml.classmate.TypeResolver;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.data.domain.Pageable;import springfox.documentation.schema.AlternateTypeRule;import springfox.documentation.schema.AlternateTypeRuleConvention;import java.util.List;import static com.google.common.collect.Lists.newArrayList;import static springfox.documentation.schema.AlternateTypeRules.newRule; /** * 将Pageable转换展示在swagger中 */@Configurationpublic class SwaggerDataConfig { @Bean public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) { return new AlternateTypeRuleConvention() { @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public List<AlternateTypeRule> rules() { return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class))); } }; } @ApiModel static class Page { @ApiModelProperty("页码 (0..N)") private Integer page; @ApiModelProperty("每页显示的数目") private Integer size; @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc") private List<String> sort; public Integer getPage() { return page; } public void setPage(Integer page) { this.page = page; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = size; } public List<String> getSort() { return sort; } public void setSort(List<String> sort) { this.sort = sort; } }}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment