Browse Source

commit of system-1.0.jar

master
刘力 3 years ago
parent
commit
9793823c8b
  1. 22
      common/src/main/java/com/storeroom/annotaion/DataPermission.java
  2. 14
      common/src/main/java/com/storeroom/annotaion/Query.java
  3. 18
      common/src/main/java/com/storeroom/exception/BadConfigurationException.java
  4. 38
      common/src/main/java/com/storeroom/utils/CacheKey.java
  5. 83
      common/src/main/java/com/storeroom/utils/EncryptUtils.java
  6. 46
      common/src/main/java/com/storeroom/utils/PageUtil.java
  7. 194
      common/src/main/java/com/storeroom/utils/QueryHelp.java
  8. 29
      common/src/main/java/com/storeroom/utils/ValidationUtil.java
  9. 53
      common/src/main/java/com/storeroom/utils/enums/RequestMethodEnum.java
  10. 3
      system/src/main/java/AppRun.java
  11. 24
      system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java
  12. 179
      system/src/main/java/com/storeroom/modules/security/config/SpringSecurityConfig.java
  13. 50
      system/src/main/java/com/storeroom/modules/security/config/bean/LoginCode.java
  14. 30
      system/src/main/java/com/storeroom/modules/security/config/bean/LoginCodeEnum.java
  15. 89
      system/src/main/java/com/storeroom/modules/security/config/bean/LoginProperties.java
  16. 55
      system/src/main/java/com/storeroom/modules/security/config/bean/SecurityProperties.java
  17. 19
      system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java
  18. 19
      system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java
  19. 24
      system/src/main/java/com/storeroom/modules/security/security/TokenConfigurer.java
  20. 92
      system/src/main/java/com/storeroom/modules/security/security/TokenFilter.java
  21. 111
      system/src/main/java/com/storeroom/modules/security/security/TokenProvider.java
  22. 173
      system/src/main/java/com/storeroom/modules/security/service/OnlineUserService.java
  23. 29
      system/src/main/java/com/storeroom/modules/security/service/UserCacheClean.java
  24. 72
      system/src/main/java/com/storeroom/modules/security/service/UserDetailsServiceImpl.java
  25. 16
      system/src/main/java/com/storeroom/modules/security/service/dto/AuthUserDto.java
  26. 65
      system/src/main/java/com/storeroom/modules/security/service/dto/JwtUserDto.java
  27. 54
      system/src/main/java/com/storeroom/modules/security/service/dto/OnlineUserDto.java
  28. 69
      system/src/main/java/com/storeroom/modules/system/domain/Dept.java
  29. 36
      system/src/main/java/com/storeroom/modules/system/domain/Dict.java
  30. 40
      system/src/main/java/com/storeroom/modules/system/domain/DictDetail.java
  31. 56
      system/src/main/java/com/storeroom/modules/system/domain/Job.java
  32. 92
      system/src/main/java/com/storeroom/modules/system/domain/Menu.java
  33. 79
      system/src/main/java/com/storeroom/modules/system/domain/Role.java
  34. 108
      system/src/main/java/com/storeroom/modules/system/domain/User.java
  35. 18
      system/src/main/java/com/storeroom/modules/system/domain/vo/MenuMetaVo.java
  36. 29
      system/src/main/java/com/storeroom/modules/system/domain/vo/MenuVo.java
  37. 15
      system/src/main/java/com/storeroom/modules/system/domain/vo/UserPassVo.java
  38. 51
      system/src/main/java/com/storeroom/modules/system/repository/DeptRepository.java
  39. 24
      system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java
  40. 23
      system/src/main/java/com/storeroom/modules/system/repository/JobRepository.java
  41. 69
      system/src/main/java/com/storeroom/modules/system/repository/MenuRepository.java
  42. 61
      system/src/main/java/com/storeroom/modules/system/repository/RoleRepository.java
  43. 112
      system/src/main/java/com/storeroom/modules/system/repository/UserRepository.java
  44. 10
      system/src/main/java/com/storeroom/modules/system/service/DataService.java
  45. 4
      system/src/main/java/com/storeroom/modules/system/service/DeptService.java
  46. 45
      system/src/main/java/com/storeroom/modules/system/service/DictDetailService.java
  47. 57
      system/src/main/java/com/storeroom/modules/system/service/DictService.java
  48. 4
      system/src/main/java/com/storeroom/modules/system/service/JobService.java
  49. 4
      system/src/main/java/com/storeroom/modules/system/service/MenuService.java
  50. 118
      system/src/main/java/com/storeroom/modules/system/service/RoleService.java
  51. 99
      system/src/main/java/com/storeroom/modules/system/service/UserService.java
  52. 61
      system/src/main/java/com/storeroom/modules/system/service/dto/DeptDto.java
  53. 29
      system/src/main/java/com/storeroom/modules/system/service/dto/DeptQueryCriteria.java
  54. 16
      system/src/main/java/com/storeroom/modules/system/service/dto/DeptSmallDto.java
  55. 23
      system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailDto.java
  56. 15
      system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailQueryCriteria.java
  57. 22
      system/src/main/java/com/storeroom/modules/system/service/dto/DictDto.java
  58. 12
      system/src/main/java/com/storeroom/modules/system/service/dto/DictQueryCriteria.java
  59. 12
      system/src/main/java/com/storeroom/modules/system/service/dto/DictSmallDto.java
  60. 27
      system/src/main/java/com/storeroom/modules/system/service/dto/JobDto.java
  61. 16
      system/src/main/java/com/storeroom/modules/system/service/dto/JobSmallDto.java
  62. 73
      system/src/main/java/com/storeroom/modules/system/service/dto/MenuDto.java
  63. 24
      system/src/main/java/com/storeroom/modules/system/service/dto/MenuQueryCriteria.java
  64. 46
      system/src/main/java/com/storeroom/modules/system/service/dto/RoleDto.java
  65. 17
      system/src/main/java/com/storeroom/modules/system/service/dto/RoleQueryCriteria.java
  66. 18
      system/src/main/java/com/storeroom/modules/system/service/dto/RoleSmallDto.java
  67. 50
      system/src/main/java/com/storeroom/modules/system/service/dto/UserDto.java
  68. 31
      system/src/main/java/com/storeroom/modules/system/service/dto/UserQueryCriteria.java
  69. 208
      system/src/main/java/com/storeroom/modules/system/service/impl/RoleServiceImpl.java
  70. 239
      system/src/main/java/com/storeroom/modules/system/service/impl/UserServiceImpl.java
  71. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/DeptMapper.java
  72. 11
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictDetailMapper.java
  73. 11
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictMapper.java
  74. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictSmallMapper.java
  75. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/JobMapper.java
  76. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/MenuMapper.java
  77. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleMapper.java
  78. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleSmallMapper.java
  79. 12
      system/src/main/java/com/storeroom/modules/system/service/mapstruct/UserMapper.java
  80. 41
      system/src/main/resources/application.yml

22
common/src/main/java/com/storeroom/annotaion/DataPermission.java

@ -0,0 +1,22 @@
package com.storeroom.annotaion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
/**
* Entity 中的字段名称
*/
String fieldName() default "";
/**
* Entity 中与部门关联的字段名称
*/
String joinName() default "";
}

14
common/src/main/java/com/storeroom/annotaion/Query.java

@ -32,21 +32,21 @@ public @interface Query {
String blurry() default "";
enum Type {
// 相等
// jie 2019/6/4 相等
EQUAL
// 大于等于
// 大于等于
, GREATER_THAN
// 小于等于
// 小于等于
, LESS_THAN
// 中模糊查询
, INNER_LIKE
// 左模糊查询
// 左模糊查询
, LEFT_LIKE
// 右模糊查询
// 右模糊查询
, RIGHT_LIKE
// 小于
// 小于
, LESS_THAN_NQ
// 包含
// 包含
, IN
// 不包含
, NOT_IN

18
common/src/main/java/com/storeroom/exception/BadConfigurationException.java

@ -0,0 +1,18 @@
package com.storeroom.exception;
public class BadConfigurationException extends RuntimeException{
public BadConfigurationException(){super();}
public BadConfigurationException(String message){super(message);}
public BadConfigurationException(String message,Throwable cause){
super(message, cause);
}
public BadConfigurationException(Throwable cause){super(cause);}
protected BadConfigurationException(String message,Throwable cause,boolean enableSuppression,boolean writableStackTrace){
super(message, cause, enableSuppression, writableStackTrace);
}
}

38
common/src/main/java/com/storeroom/utils/CacheKey.java

@ -0,0 +1,38 @@
package com.storeroom.utils;
public interface CacheKey {
/**
* 用户
*/
String USER_ID = "user::id:";
/**
* 数据
*/
String DATA_USER = "data::user:";
/**
* 菜单
*/
String MENU_ID = "menu::id:";
String MENU_USER = "menu::user:";
/**
* 角色授权
*/
String ROLE_AUTH = "role::auth:";
/**
* 角色信息
*/
String ROLE_ID = "role::id:";
/**
* 部门
*/
String DEPT_ID = "dept::id:";
/**
* 岗位
*/
String JOB_ID = "job::id:";
/**
* 数据字典
*/
String DICT_NAME = "dict::name:";
}

83
common/src/main/java/com/storeroom/utils/EncryptUtils.java

@ -0,0 +1,83 @@
package com.storeroom.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
/**
* 加密工具类
*/
public class EncryptUtils {
private static final String STR_PARAM = "Passw0rd";
private static Cipher cipher;
private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
private static DESKeySpec getDesKeySpec(String source) throws Exception {
if (source == null || source.length() == 0){
return null;
}
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
String strKey = "Passw0rd";
return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
}
/**
* 对称加密
*/
public static String desEncrypt(String source) throws Exception {
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
return byte2hex(
cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
}
/**
* 对称解密
*/
public static String desDecrypt(String source) throws Exception {
byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
byte[] retByte = cipher.doFinal(src);
return new String(retByte);
}
private static String byte2hex(byte[] inStr) {
String stmp;
StringBuilder out = new StringBuilder(inStr.length * 2);
for (byte b : inStr) {
stmp = Integer.toHexString(b & 0xFF);
if (stmp.length() == 1) {
// 如果是0至F的单位字符串则添加0
out.append("0").append(stmp);
} else {
out.append(stmp);
}
}
return out.toString();
}
private static byte[] hex2byte(byte[] b) {
int size = 2;
if ((b.length % size) != 0){
throw new IllegalArgumentException("长度不是偶数");
}
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += size) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
}

46
common/src/main/java/com/storeroom/utils/PageUtil.java

@ -0,0 +1,46 @@
package com.storeroom.utils;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class PageUtil extends cn.hutool.core.util.PageUtil{
/**
* List 分页
*/
public static List toPage(int page, int size , List list) {
int fromIndex = page * size;
int toIndex = page * size + size;
if(fromIndex > list.size()){
return new ArrayList();
} else if(toIndex >= list.size()) {
return list.subList(fromIndex,list.size());
} else {
return list.subList(fromIndex,toIndex);
}
}
/**
* Page 数据处理预防redis反序列化报错
*/
public static Map<String,Object> toPage(Page page) {
Map<String,Object> map = new LinkedHashMap<>(2);
map.put("content",page.getContent());
map.put("totalElements",page.getTotalElements());
return map;
}
/**
* 自定义分页
*/
public static Map<String,Object> toPage(Object object, Object totalElements) {
Map<String,Object> map = new LinkedHashMap<>(2);
map.put("content",object);
map.put("totalElements",totalElements);
return map;
}
}

194
common/src/main/java/com/storeroom/utils/QueryHelp.java

@ -0,0 +1,194 @@
package com.storeroom.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.storeroom.annotaion.DataPermission;
import com.storeroom.annotaion.Query;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@Slf4j
@SuppressWarnings({"unchecked","all"})
public class QueryHelp {
public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
List<Predicate> list = new ArrayList<>();
if(query == null){
return cb.and(list.toArray(new Predicate[0]));
}
// 数据权限验证
DataPermission permission = query.getClass().getAnnotation(DataPermission.class);
if(permission != null){
// 获取数据权限
List<Long> dataScopes = SecurityUtils.getCurrentUserDataScope();
if(CollectionUtil.isNotEmpty(dataScopes)){
if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
Join join = root.join(permission.joinName(), JoinType.LEFT);
list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));
} else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));
}
}
}
try {
List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
for (Field field : fields) {
boolean accessible = field.isAccessible();
// 设置对象的访问权限保证对private的属性的访
field.setAccessible(true);
Query q = field.getAnnotation(Query.class);
if (q != null) {
String propName = q.propName();
String joinName = q.joinName();
String blurry = q.blurry();
String attributeName = isBlank(propName) ? field.getName() : propName;
Class<?> fieldType = field.getType();
Object val = field.get(query);
if (ObjectUtil.isNull(val) || "".equals(val)) {
continue;
}
Join join = null;
// 模糊多字段
if (ObjectUtil.isNotEmpty(blurry)) {
String[] blurrys = blurry.split(",");
List<Predicate> orPredicate = new ArrayList<>();
for (String s : blurrys) {
orPredicate.add(cb.like(root.get(s)
.as(String.class), "%" + val.toString() + "%"));
}
Predicate[] p = new Predicate[orPredicate.size()];
list.add(cb.or(orPredicate.toArray(p)));
continue;
}
if (ObjectUtil.isNotEmpty(joinName)) {
String[] joinNames = joinName.split(">");
for (String name : joinNames) {
switch (q.join()) {
case LEFT:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.LEFT);
} else {
join = root.join(name, JoinType.LEFT);
}
break;
case RIGHT:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.RIGHT);
} else {
join = root.join(name, JoinType.RIGHT);
}
break;
case INNER:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.INNER);
} else {
join = root.join(name, JoinType.INNER);
}
break;
default: break;
}
}
}
switch (q.type()) {
case EQUAL:
list.add(cb.equal(getExpression(attributeName,join,root)
.as((Class<? extends Comparable>) fieldType),val));
break;
case GREATER_THAN:
list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root)
.as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case LESS_THAN:
list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root)
.as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case LESS_THAN_NQ:
list.add(cb.lessThan(getExpression(attributeName,join,root)
.as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case INNER_LIKE:
list.add(cb.like(getExpression(attributeName,join,root)
.as(String.class), "%" + val.toString() + "%"));
break;
case LEFT_LIKE:
list.add(cb.like(getExpression(attributeName,join,root)
.as(String.class), "%" + val.toString()));
break;
case RIGHT_LIKE:
list.add(cb.like(getExpression(attributeName,join,root)
.as(String.class), val.toString() + "%"));
break;
case IN:
if (CollUtil.isNotEmpty((Collection<Object>)val)) {
list.add(getExpression(attributeName,join,root).in((Collection<Object>) val));
}
break;
case NOT_IN:
if (CollUtil.isNotEmpty((Collection<Object>)val)) {
list.add(getExpression(attributeName,join,root).in((Collection<Object>) val).not());
}
break;
case NOT_EQUAL:
list.add(cb.notEqual(getExpression(attributeName,join,root), val));
break;
case NOT_NULL:
list.add(cb.isNotNull(getExpression(attributeName,join,root)));
break;
case IS_NULL:
list.add(cb.isNull(getExpression(attributeName,join,root)));
break;
case BETWEEN:
List<Object> between = new ArrayList<>((List<Object>)val);
list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),
(Comparable) between.get(0), (Comparable) between.get(1)));
break;
default: break;
}
}
field.setAccessible(accessible);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
int size = list.size();
return cb.and(list.toArray(new Predicate[size]));
}
@SuppressWarnings("unchecked")
private static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {
if (ObjectUtil.isNotEmpty(join)) {
return join.get(attributeName);
} else {
return root.get(attributeName);
}
}
private static boolean isBlank(final CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
public static List<Field> getAllFields(Class clazz, List<Field> fields) {
if (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
getAllFields(clazz.getSuperclass(), fields);
}
return fields;
}
}

29
common/src/main/java/com/storeroom/utils/ValidationUtil.java

@ -0,0 +1,29 @@
package com.storeroom.utils;
import cn.hutool.core.util.ObjectUtil;
import com.storeroom.exception.BaseException;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
/**
* 验证工具类
*/
public class ValidationUtil {
/**
* 验证空
*/
public static void isNull(Object obj, String entity, String parameter , Object value){
if(ObjectUtil.isNull(obj)){
String msg = entity + " 不存在: "+ parameter +" is "+ value;
throw new BaseException(msg);
}
}
/**
* 验证是否为邮箱
*/
public static boolean isEmail(String email) {
return new EmailValidator().isValid(email, null);
}
}

53
common/src/main/java/com/storeroom/utils/enums/RequestMethodEnum.java

@ -0,0 +1,53 @@
package com.storeroom.utils.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum RequestMethodEnum {
/**
* 搜寻 @AnonymousGetMapping
*/
GET("GET"),
/**
* 搜寻 @AnonymousPostMapping
*/
POST("POST"),
/**
* 搜寻 @AnonymousPutMapping
*/
PUT("PUT"),
/**
* 搜寻 @AnonymousPatchMapping
*/
PATCH("PATCH"),
/**
* 搜寻 @AnonymousDeleteMapping
*/
DELETE("DELETE"),
/**
* 否则就是所有 Request 接口都放行
*/
ALL("All");
/**
* Request 类型
*/
private final String type;
public static RequestMethodEnum find(String type) {
for (RequestMethodEnum value : RequestMethodEnum.values()) {
if (type.equals(value.getType())) {
return value;
}
}
return ALL;
}
}

3
system/src/main/java/AppRun.java

@ -14,13 +14,14 @@ import org.springframework.web.bind.annotation.RestController;
@Api(hidden = true)
@SpringBootApplication
@EnableTransactionManagement
@EnableJpaAuditing(auditorAwareRef = "audiorAware")
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class AppRun {
public static void main(String[] args){
SpringApplication.run(AppRun.class,args);
}
@Bean
public SpringContextHolder springContextHolder(){
return new SpringContextHolder();

24
system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java

@ -0,0 +1,24 @@
package com.storeroom.modules.security.config;
import com.storeroom.modules.security.config.bean.LoginProperties;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConfigBeanConfiguration {
@Bean
@ConfigurationProperties(prefix="login",ignoreUnknownFields=true)
public LoginProperties loginProperties(){
return new LoginProperties();
}
@Bean
@ConfigurationProperties(prefix = "jwt",ignoreUnknownFields = true)
public SecurityProperties securityProperties(){
return new SecurityProperties();
}
}

179
system/src/main/java/com/storeroom/modules/security/config/SpringSecurityConfig.java

@ -0,0 +1,179 @@
package com.storeroom.modules.security.config;
import com.storeroom.annotaion.AnonymousAccess;
import com.storeroom.modules.security.config.bean.SecurityProperties;
import com.storeroom.modules.security.security.JwtAccessDeniedHandler;
import com.storeroom.modules.security.security.JwtAuthenticationEntryPoint;
import com.storeroom.modules.security.security.TokenConfigurer;
import com.storeroom.modules.security.security.TokenProvider;
import com.storeroom.modules.security.service.OnlineUserService;
import com.storeroom.modules.security.service.UserCacheClean;
import com.storeroom.utils.enums.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
//密码交给框架管理
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 放行资源
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行允许匿名和带Token访问细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证 测试需求注释
//.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}

50
system/src/main/java/com/storeroom/modules/security/config/bean/LoginCode.java

@ -0,0 +1,50 @@
package com.storeroom.modules.security.config.bean;
import lombok.Data;
@Data
public class LoginCode {
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟为单位
*/
private Long expiration = 2L;
/**
* 验证码长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height=36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize=25;
public LoginCodeEnum getCodeType() {
return codeType;
}
}

30
system/src/main/java/com/storeroom/modules/security/config/bean/LoginCodeEnum.java

@ -0,0 +1,30 @@
package com.storeroom.modules.security.config.bean;
/**
* 验证码配置枚举
*/
public enum LoginCodeEnum {
/**
* 算术
*/
arithmetic,
/**
* 中文
*/
chinese,
/**
* 中文闪图
*/
chinese_gif,
/**
* 闪图
*/
gif,
spec
}

89
system/src/main/java/com/storeroom/modules/security/config/bean/LoginProperties.java

@ -0,0 +1,89 @@
package com.storeroom.modules.security.config.bean;
import com.storeroom.exception.BadConfigurationException;
import com.storeroom.utils.StringUtils;
import com.wf.captcha.*;
import com.wf.captcha.base.Captcha;
import lombok.Data;
import java.awt.*;
import java.util.Objects;
@Data
public class LoginProperties {
private boolean singleLogin= false;
private LoginCode loginCode;
/**
* 用户登录信息缓存
*/
private boolean cacheEnable;
public boolean isSingleLogin() {
return singleLogin;
}
public boolean isCacheEnable() {
return cacheEnable;
}
/**
* 获取验证码生产类
* @return
*/
public Captcha getCaptcha(){
if (Objects.isNull(loginCode)){
loginCode=new LoginCode();
if (Objects.isNull(loginCode.getCodeType())){
loginCode.setCodeType(LoginCodeEnum.arithmetic);
}
}
return switchCaptcha(loginCode);
}
/**
* 依据配置信息生产验证码
* @param loginCode
* @return
*/
private Captcha switchCaptcha(LoginCode loginCode){
Captcha captcha;
synchronized (this){
switch (loginCode.getCodeType()){
case arithmetic:
captcha=new ArithmeticCaptcha(loginCode.getWidth(),loginCode.getHeight());
//几位数运算默认是两位
captcha.setLen(loginCode.getLength());
break;
case chinese:
captcha=new ChineseCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
case chinese_gif:
captcha=new ChineseGifCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case gif:
captcha=new GifCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
case spec:
captcha=new SpecCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
default:
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum");
}
}
if (StringUtils.isNotBlank(loginCode.getFontName())){
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
}
return captcha;
}
}

55
system/src/main/java/com/storeroom/modules/security/config/bean/SecurityProperties.java

@ -0,0 +1,55 @@
package com.storeroom.modules.security.config.bean;
import lombok.Data;
/**
* Jwt 配置
*/
@Data
public class SecurityProperties {
/**
* Request Headers:Authorization
*/
private String header;
/**
* 令牌前缀最后留个空格 Bearer
*/
private String tokenStartWith;
/**
* 必须使用最少88位的Base64对该令牌进行编码
*/
private String base64Secret;
/**
* 令牌过期时间此处单位/毫秒
*/
private Long tokenValidityInSeconds;
/**
* 在线用户 key,根据 key 查询 redis 中在线用户的数据
*/
private String onlineKey;
/**
* 验证码key
*/
private String codeKey;
/**
* token 续期检查
*/
private Long detect;
/**
* 续期时间
*/
private Long renew;
public String getTokenStartWith(){
return tokenStartWith+" ";
}
}

19
system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java

@ -0,0 +1,19 @@
package com.storeroom.modules.security.security;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//当前用户在没有权限的情况下访问受保护的REST资源时将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}

19
system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java

@ -0,0 +1,19 @@
package com.storeroom.modules.security.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}

24
system/src/main/java/com/storeroom/modules/security/security/TokenConfigurer.java

@ -0,0 +1,24 @@
package com.storeroom.modules.security.security;
import com.storeroom.modules.security.config.bean.SecurityProperties;
import com.storeroom.modules.security.service.OnlineUserService;
import com.storeroom.modules.security.service.UserCacheClean;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

92
system/src/main/java/com/storeroom/modules/security/security/TokenFilter.java

@ -0,0 +1,92 @@
package com.storeroom.modules.security.security;
import cn.hutool.core.util.StrUtil;
import com.storeroom.modules.security.config.bean.SecurityProperties;
import com.storeroom.modules.security.service.OnlineUserService;
import com.storeroom.modules.security.service.UserCacheClean;
import com.storeroom.modules.security.service.dto.OnlineUserDto;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
public class TokenFilter extends GenericFilterBean {
private static final Logger log= LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 用户在线
* @param userCacheClean 用户缓存清理工具
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheClean = userCacheClean;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}

111
system/src/main/java/com/storeroom/modules/security/security/TokenProvider.java

@ -0,0 +1,111 @@
package com.storeroom.modules.security.security;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.storeroom.modules.security.config.bean.SecurityProperties;
import com.storeroom.utils.RedisUtils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public static final String AUTHORITIES_KEY="user";
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
Key key = Keys.hmacShaKeyFor(keyBytes);
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
/**
* 创建Token 设置永不过期
* Token 的时间有效性转到Redis 维护
*
* @param authentication /
* @return /
*/
public String createToken(Authentication authentication) {
return jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, authentication.getName())
.setSubject(authentication.getName())
.compact();
}
/**
* 依据Token 获取鉴权信息
*
* @param token /
* @return /
*/
Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
User principal = new User(claims.getSubject(), "******", new ArrayList<>());
return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
}
public Claims getClaims(String token) {
return jwtParser
.parseClaimsJws(token)
.getBody();
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
}

173
system/src/main/java/com/storeroom/modules/security/service/OnlineUserService.java

@ -0,0 +1,173 @@
package com.storeroom.modules.security.service;
import com.storeroom.modules.security.config.bean.SecurityProperties;
import com.storeroom.modules.security.service.dto.JwtUserDto;
import com.storeroom.modules.security.service.dto.OnlineUserDto;
import com.storeroom.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@Service
@Slf4j
public class OnlineUserService {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
/**
* 保存早先用户
* @param jwtUserDto /
* @param token /
* @param request /
*/
public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){
String dept = jwtUserDto.getUser().getDept().getName();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
String address = StringUtils.getCityInfo(ip);
OnlineUserDto onlineUserDto = null;
try {
onlineUserDto = new OnlineUserDto(jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
log.error(e.getMessage(),e);
}
redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);
}
/**
* 查询全部数据
* @param filter /
* @param pageable /
* @return /
*/
public Map<String,Object> getAll(String filter, Pageable pageable){
List<OnlineUserDto> onlineUserDtos = getAll(filter);
return PageUtil.toPage(
PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos),
onlineUserDtos.size()
);
}
/**
* 查询全部数据不分页
* @param filter /
* @return /
*/
public List<OnlineUserDto> getAll(String filter){
List<String> keys = redisUtils.scan(properties.getOnlineKey() + "*");
Collections.reverse(keys);
List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
for (String key : keys) {
OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);
if(StringUtils.isNotBlank(filter)){
if(onlineUserDto.toString().contains(filter)){
onlineUserDtos.add(onlineUserDto);
}
} else {
onlineUserDtos.add(onlineUserDto);
}
}
onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
return onlineUserDtos;
}
/**
* 踢出用户
* @param key /
*/
public void kickOut(String key){
key = properties.getOnlineKey() + key;
redisUtils.del(key);
}
/**
* 退出登录
* @param token /
*/
public void logout(String token) {
String key = properties.getOnlineKey() + token;
redisUtils.del(key);
}
/**
* 导出
* @param all /
* @param response /
* @throws IOException /
*/
public void download(List<OnlineUserDto> all, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (OnlineUserDto user : all) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("用户名", user.getUserName());
map.put("部门", user.getDept());
map.put("登录IP", user.getIp());
map.put("登录地点", user.getAddress());
map.put("浏览器", user.getBrowser());
map.put("登录日期", user.getLoginTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
/**
* 查询用户
* @param key /
* @return /
*/
public OnlineUserDto getOne(String key) {
return (OnlineUserDto)redisUtils.get(key);
}
/**
* 检测用户是否在之前已经登录已经登录踢下线
* @param userName 用户名
*/
public void checkLoginOnUser(String userName, String igoreToken){
List<OnlineUserDto> onlineUserDtos = getAll(userName);
if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){
return;
}
for(OnlineUserDto onlineUserDto : onlineUserDtos){
if(onlineUserDto.getUserName().equals(userName)){
try {
String token =EncryptUtils.desDecrypt(onlineUserDto.getKey());
if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){
this.kickOut(token);
}else if(StringUtils.isBlank(igoreToken)){
this.kickOut(token);
}
} catch (Exception e) {
log.error("checkUser is error",e);
}
}
}
}
/**
* 根据用户名强退用户
* @param username /
*/
@Async
public void kickOutForUsername(String username) {
List<OnlineUserDto> onlineUsers = getAll(username);
for (OnlineUserDto onlineUser : onlineUsers) {
if (onlineUser.getUserName().equals(username)) {
kickOut(onlineUser.getKey());
}
}
}
}

29
system/src/main/java/com/storeroom/modules/security/service/UserCacheClean.java

@ -0,0 +1,29 @@
package com.storeroom.modules.security.service;
import com.storeroom.utils.StringUtils;
import org.springframework.stereotype.Component;
@Component
public class UserCacheClean {
/**
* 清理特定用户缓存信息
* 用户信息变更时
*
* @param userName
*/
public void cleanUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
UserDetailsServiceImpl.userDtoCache.remove(userName);
}
}
/**
* 清理所有用户的缓存信息
* ,如发生角色授权信息变化可以简便的全部失效缓存
*/
public void cleanAll() {
UserDetailsServiceImpl.userDtoCache.clear();
}
}

72
system/src/main/java/com/storeroom/modules/security/service/UserDetailsServiceImpl.java

@ -0,0 +1,72 @@
package com.storeroom.modules.security.service;
import com.storeroom.exception.BaseException;
import com.storeroom.modules.security.config.bean.LoginProperties;
import com.storeroom.modules.security.service.dto.JwtUserDto;
import com.storeroom.modules.system.service.DataService;
import com.storeroom.modules.system.service.RoleService;
import com.storeroom.modules.system.service.UserService;
import com.storeroom.modules.system.service.dto.UserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.persistence.EntityNotFoundException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
private final DataService dataService;
private final LoginProperties loginProperties;
public void setEnableCache(boolean enableCache) {
this.loginProperties.setCacheEnable(enableCache);
}
/**
* 用户信息缓存
*
* @see {@link UserCacheClean}
*/
static Map<String, JwtUserDto> userDtoCache = new ConcurrentHashMap<>();
@Override
public JwtUserDto loadUserByUsername(String username) {
boolean searchDb = true;
JwtUserDto jwtUserDto = null;
if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) {
jwtUserDto = userDtoCache.get(username);
searchDb = false;
}
if (searchDb) {
UserDto user;
try {
user = userService.findByName(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException("", e);
}
if (user == null) {
throw new UsernameNotFoundException("");
} else {
if (!user.getEnabled()) {
throw new BaseException("账号未激活!");
}
jwtUserDto = new JwtUserDto(
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
userDtoCache.put(username, jwtUserDto);
}
}
return jwtUserDto;
}
}

16
system/src/main/java/com/storeroom/modules/security/service/dto/AuthUserDto.java

@ -0,0 +1,16 @@
package com.storeroom.modules.security.service.dto;
import javax.validation.constraints.NotBlank;
public class AuthUserDto {
@NotBlank
private String username;
@NotBlank
private String password;
private String code;
private String uuid = "";
}

65
system/src/main/java/com/storeroom/modules/security/service/dto/JwtUserDto.java

@ -0,0 +1,65 @@
package com.storeroom.modules.security.service.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.storeroom.modules.system.service.dto.UserDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
private final UserDto user;
private final List<Long> dataScopes;
@JsonIgnore
private final List<GrantedAuthority> authorities;
public Set<String> getRoles() {
return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
}
@Override
@JsonIgnore
public String getPassword() {
return user.getPassword();
}
@Override
@JsonIgnore
public String getUsername() {
return user.getUsername();
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return user.getEnabled();
}
}

54
system/src/main/java/com/storeroom/modules/security/service/dto/OnlineUserDto.java

@ -0,0 +1,54 @@
package com.storeroom.modules.security.service.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUserDto {
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 岗位
*/
private String dept;
/**
* 浏览器
*/
private String browser;
/**
* IP
*/
private String ip;
/**
* 地址
*/
private String address;
/**
* token
*/
private String key;
/**
* 登录时间
*/
private Date loginTime;
}

69
system/src/main/java/com/storeroom/modules/system/domain/Dept.java

@ -0,0 +1,69 @@
package com.storeroom.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
@Entity
@Getter
@Setter
@Table(name="sys_dept")
public class Dept extends BaseEntity implements Serializable {
@Id
@Column(name = "dept_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "depts")
@ApiModelProperty(value = "角色")
private Set<Role> roles;
@ApiModelProperty(value = "排序")
private Integer deptSort;
@NotBlank
@ApiModelProperty(value = "部门名称")
private String name;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "上级部门")
private Long pid;
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dept dept = (Dept) o;
return Objects.equals(id, dept.id) &&
Objects.equals(name, dept.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

36
system/src/main/java/com/storeroom/modules/system/domain/Dict.java

@ -0,0 +1,36 @@
package com.storeroom.modules.system.domain;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name="sys_dict")
public class Dict extends BaseEntity implements Serializable {
@Id
@Column(name = "dict_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "dict",cascade={CascadeType.PERSIST,CascadeType.REMOVE})
private List<DictDetail> dictDetails;
@NotBlank
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "描述")
private String description;
}

40
system/src/main/java/com/storeroom/modules/system/domain/DictDetail.java

@ -0,0 +1,40 @@
package com.storeroom.modules.system.domain;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Entity
@Getter
@Setter
@Table(name="sys_dict_detail")
public class DictDetail extends BaseEntity implements Serializable {
@Id
@Column(name = "detail_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JoinColumn(name = "dict_id")
@ManyToOne(fetch=FetchType.LAZY)
@ApiModelProperty(value = "字典", hidden = true)
private Dict dict;
@ApiModelProperty(value = "字典标签")
private String label;
@ApiModelProperty(value = "字典值")
private String value;
@ApiModelProperty(value = "排序")
private Integer dictSort = 999;
}

56
system/src/main/java/com/storeroom/modules/system/domain/Job.java

@ -0,0 +1,56 @@
package com.storeroom.modules.system.domain;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Getter
@Setter
@Table(name="sys_job")
public class Job extends BaseEntity implements Serializable {
@Id
@Column(name = "job_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@ApiModelProperty(value = "岗位名称")
private String name;
@NotNull
@ApiModelProperty(value = "岗位排序")
private Long jobSort;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Job job = (Job) o;
return Objects.equals(id, job.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

92
system/src/main/java/com/storeroom/modules/system/domain/Menu.java

@ -0,0 +1,92 @@
package com.storeroom.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
@Entity
@Getter
@Setter
@Table(name = "sys_menu")
public class Menu extends BaseEntity implements Serializable {
@Id
@Column(name = "menu_id")
@NotNull(groups = {Update.class})
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "menus")
@ApiModelProperty(value = "菜单角色")
private Set<Role> roles;
@ApiModelProperty(value = "菜单标题")
private String title;
@Column(name = "name")
@ApiModelProperty(value = "菜单组件名称")
private String componentName;
@ApiModelProperty(value = "排序")
private Integer menuSort = 999;
@ApiModelProperty(value = "组件路径")
private String component;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "菜单类型,目录、菜单、按钮")
private Integer type;
@ApiModelProperty(value = "权限标识")
private String permission;
@ApiModelProperty(value = "菜单图标")
private String icon;
@Column(columnDefinition = "bit(1) default 0")
@ApiModelProperty(value = "缓存")
private Boolean cache;
@Column(columnDefinition = "bit(1) default 0")
@ApiModelProperty(value = "是否隐藏")
private Boolean hidden;
@ApiModelProperty(value = "上级菜单")
private Long pid;
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@ApiModelProperty(value = "外链菜单")
private Boolean iFrame;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Menu menu = (Menu) o;
return Objects.equals(id, menu.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

79
system/src/main/java/com/storeroom/modules/system/domain/Role.java

@ -0,0 +1,79 @@
package com.storeroom.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import com.storeroom.base.BaseEntity;
import com.storeroom.utils.enums.DataScopeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class Role extends BaseEntity implements Serializable {
@Id
@Column(name = "role_id")
@NotNull(groups = {Update.class})
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "roles")
@ApiModelProperty(value = "用户", hidden = true)
private Set<User> users;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "sys_roles_menus",
joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "menu_id",referencedColumnName = "menu_id")})
@ApiModelProperty(value = "菜单", hidden = true)
private Set<Menu> menus;
@ManyToMany
@JoinTable(name = "sys_roles_depts",
joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "dept_id",referencedColumnName = "dept_id")})
@ApiModelProperty(value = "部门", hidden = true)
private Set<Dept> depts;
@NotBlank
@ApiModelProperty(value = "名称", hidden = true)
private String name;
@ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义")
private String dataScope = DataScopeEnum.THIS_LEVEL.getValue();
@Column(name = "level")
@ApiModelProperty(value = "级别,数值越小,级别越大")
private Integer level = 3;
@ApiModelProperty(value = "描述")
private String description;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Role role = (Role) o;
return Objects.equals(id, role.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

108
system/src/main/java/com/storeroom/modules/system/domain/User.java

@ -0,0 +1,108 @@
package com.storeroom.modules.system.domain;
import com.storeroom.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
@Entity
@Getter
@Setter
@Table(name="sys_user")
public class User extends BaseEntity implements Serializable {
@Id
@Column(name = "user_id")
@NotNull(groups = Update.class)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户角色")
@JoinTable(name = "sys_users_roles",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")})
private Set<Role> roles;
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户岗位")
@JoinTable(name = "sys_users_jobs",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "job_id",referencedColumnName = "job_id")})
private Set<Job> jobs;
@OneToOne
@JoinColumn(name = "dept_id")
@ApiModelProperty(value = "用户部门")
private Dept dept;
@NotBlank
@Column(unique = true)
@ApiModelProperty(value = "用户名称")
private String username;
@NotBlank
@ApiModelProperty(value = "用户昵称")
private String nickName;
@Email
@NotBlank
@ApiModelProperty(value = "邮箱")
private String email;
@NotBlank
@ApiModelProperty(value = "电话号码")
private String phone;
@ApiModelProperty(value = "用户性别")
private String gender;
@ApiModelProperty(value = "头像真实名称",hidden = true)
private String avatarName;
@ApiModelProperty(value = "头像存储的路径", hidden = true)
private String avatarPath;
@ApiModelProperty(value = "密码")
private String password;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "是否为admin账号", hidden = true)
private Boolean isAdmin = false;
@Column(name = "pwd_reset_time")
@ApiModelProperty(value = "最后修改密码的时间", hidden = true)
private Date pwdResetTime;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
}

18
system/src/main/java/com/storeroom/modules/system/domain/vo/MenuMetaVo.java

@ -0,0 +1,18 @@
package com.storeroom.modules.system.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class MenuMetaVo implements Serializable {
private String title;
private String icon;
private Boolean noCache;
}

29
system/src/main/java/com/storeroom/modules/system/domain/vo/MenuVo.java

@ -0,0 +1,29 @@
package com.storeroom.modules.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class MenuVo implements Serializable {
private String name;
private String path;
private Boolean hidden;
private String redirect;
private String component;
private Boolean alwaysShow;
private MenuMetaVo meta;
private List<MenuVo> children;
}

15
system/src/main/java/com/storeroom/modules/system/domain/vo/UserPassVo.java

@ -0,0 +1,15 @@
package com.storeroom.modules.system.domain.vo;
import lombok.Data;
/**
* 修改密码的 VO
*/
@Data
public class UserPassVo {
private String oldPass;
private String newPass;
}

51
system/src/main/java/com/storeroom/modules/system/repository/DeptRepository.java

@ -0,0 +1,51 @@
package com.storeroom.modules.system.repository;
import com.storeroom.modules.system.domain.Dept;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Set;
public interface DeptRepository extends JpaRepository<Dept, Long>, JpaSpecificationExecutor<Dept> {
/**
* 根据 PID 查询
* @param id pid
* @return /
*/
List<Dept> findByPid(Long id);
/**
* 获取顶级部门
* @return /
*/
List<Dept> findByPidIsNull();
/**
* 根据角色ID 查询
* @param roleId 角色ID
* @return /
*/
@Query(value = "select d.* from sys_dept d, sys_roles_depts r where " +
"d.dept_id = r.dept_id and r.role_id = ?1", nativeQuery = true)
Set<Dept> findByRoleId(Long roleId);
/**
* 判断是否存在子节点
* @param pid /
* @return /
*/
int countByPid(Long pid);
/**
* 根据ID更新sub_count
* @param count /
* @param id /
*/
@Modifying
@Query(value = " update sys_dept set sub_count = ?1 where dept_id = ?2 ",nativeQuery = true)
void updateSubCntById(Integer count, Long id);
}

24
system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java

@ -0,0 +1,24 @@
package com.storeroom.modules.system.repository;
import cn.hutool.core.lang.Dict;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
import java.util.Set;
public interface DictRepository extends JpaRepository<Dict, Long>, JpaSpecificationExecutor<Dict> {
/**
* 删除
* @param ids /
*/
void deleteByIdIn(Set<Long> ids);
/**
* 查询
* @param ids /
* @return /
*/
List<Dict> findByIdIn(Set<Long> ids);
}

23
system/src/main/java/com/storeroom/modules/system/repository/JobRepository.java

@ -0,0 +1,23 @@
package com.storeroom.modules.system.repository;
import com.storeroom.modules.system.domain.Job;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.Set;
public interface JobRepository extends JpaRepository<Job, Long>, JpaSpecificationExecutor<Job> {
/**
* 根据名称查询
* @param name 名称
* @return /
*/
Job findByName(String name);
/**
* 根据Id删除
* @param ids /
*/
void deleteAllByIdIn(Set<Long> ids);
}

69
system/src/main/java/com/storeroom/modules/system/repository/MenuRepository.java

@ -0,0 +1,69 @@
package com.storeroom.modules.system.repository;
import com.storeroom.modules.system.domain.Menu;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public interface MenuRepository extends JpaRepository<Menu, Long>, JpaSpecificationExecutor<Menu> {
/**
* 根据菜单标题查询
* @param title 菜单标题
* @return /
*/
Menu findByTitle(String title);
/**
* 根据组件名称查询
* @param name 组件名称
* @return /
*/
Menu findByComponentName(String name);
/**
* 根据菜单的 PID 查询
* @param pid /
* @return /
*/
List<Menu> findByPid(long pid);
/**
* 查询顶级菜单
* @return /
*/
List<Menu> findByPidIsNull();
/**
* 根据角色ID与菜单类型查询菜单
* @param roleIds roleIDs
* @param type 类型
* @return /
*/
@Query(value = "SELECT m.* FROM sys_menu m, sys_roles_menus r WHERE " +
"m.menu_id = r.menu_id AND r.role_id IN ?1 AND type != ?2 order by m.menu_sort asc",nativeQuery = true)
LinkedHashSet<Menu> findByRoleIdsAndTypeNot(Set<Long> roleIds, int type);
/**
* 获取节点数量
* @param id /
* @return /
*/
int countByPid(Long id);
/**
* 更新节点数目
* @param count /
* @param menuId /
*/
@Modifying
@Query(value = " update sys_menu set sub_count = ?1 where menu_id = ?2 ",nativeQuery = true)
void updateSubCntById(int count, Long menuId);
}

61
system/src/main/java/com/storeroom/modules/system/repository/RoleRepository.java

@ -0,0 +1,61 @@
package com.storeroom.modules.system.repository;
import com.storeroom.modules.system.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Set;
public interface RoleRepository extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {
/**
* 根据名称查询
* @param name /
* @return /
*/
Role findByName(String name);
/**
* 删除多个角色
* @param ids /
*/
void deleteAllByIdIn(Set<Long> ids);
/**
* 根据用户ID查询
* @param id 用户ID
* @return /
*/
@Query(value = "SELECT r.* FROM sys_role r, sys_users_roles u WHERE " +
"r.role_id = u.role_id AND u.user_id = ?1",nativeQuery = true)
Set<Role> findByUserId(Long id);
/**
* 解绑角色菜单
* @param id 菜单ID
*/
@Modifying
@Query(value = "delete from sys_roles_menus where menu_id = ?1",nativeQuery = true)
void untiedMenu(Long id);
/**
* 根据部门查询
* @param deptIds /
* @return /
*/
@Query(value = "select count(1) from sys_role r, sys_roles_depts d where " +
"r.role_id = d.role_id and d.dept_id in ?1",nativeQuery = true)
int countByDepts(Set<Long> deptIds);
/**
* 根据菜单Id查询
* @param menuIds /
* @return /
*/
@Query(value = "SELECT r.* FROM sys_role r, sys_roles_menus m WHERE " +
"r.role_id = m.role_id AND m.menu_id in ?1",nativeQuery = true)
List<Role> findInMenuId(List<Long> menuIds);
}

112
system/src/main/java/com/storeroom/modules/system/repository/UserRepository.java

@ -0,0 +1,112 @@
package com.storeroom.modules.system.repository;
import com.storeroom.modules.system.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.Date;
import java.util.List;
import java.util.Set;
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
/**
* 根据用户名查询
* @param username 用户名
* @return /
*/
User findByUsername(String username);
/**
* 根据邮箱查询
* @param email 邮箱
* @return /
*/
User findByEmail(String email);
/**
* 根据手机号查询
* @param phone 手机号
* @return /
*/
User findByPhone(String phone);
/**
* 修改密码
* @param username 用户名
* @param pass 密码
* @param lastPasswordResetTime /
*/
@Modifying
@Query(value = "update sys_user set password = ?2 , pwd_reset_time = ?3 where username = ?1",nativeQuery = true)
void updatePass(String username, String pass, Date lastPasswordResetTime);
/**
* 修改邮箱
* @param username 用户名
* @param email 邮箱
*/
@Modifying
@Query(value = "update sys_user set email = ?2 where username = ?1",nativeQuery = true)
void updateEmail(String username, String email);
/**
* 根据角色查询用户
* @param roleId /
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r WHERE" +
" u.user_id = r.user_id AND r.role_id = ?1", nativeQuery = true)
List<User> findByRoleId(Long roleId);
/**
* 根据角色中的部门查询
* @param deptId /
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r, sys_roles_depts d WHERE " +
"u.user_id = r.user_id AND r.role_id = d.role_id AND d.dept_id = ?1 group by u.user_id", nativeQuery = true)
List<User> findByRoleDeptId(Long deptId);
/**
* 根据菜单查询
* @param id 菜单ID
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles ur, sys_roles_menus rm WHERE\n" +
"u.user_id = ur.user_id AND ur.role_id = rm.role_id AND rm.menu_id = ?1 group by u.user_id", nativeQuery = true)
List<User> findByMenuId(Long id);
/**
* 根据Id删除
* @param ids /
*/
void deleteAllByIdIn(Set<Long> ids);
/**
* 根据岗位查询
* @param ids /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u, sys_users_jobs j WHERE u.user_id = j.user_id AND j.job_id IN ?1", nativeQuery = true)
int countByJobs(Set<Long> ids);
/**
* 根据部门查询
* @param deptIds /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u WHERE u.dept_id IN ?1", nativeQuery = true)
int countByDepts(Set<Long> deptIds);
/**
* 根据角色查询
* @param ids /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u, sys_users_roles r WHERE " +
"u.user_id = r.user_id AND r.role_id in ?1", nativeQuery = true)
int countByRoles(Set<Long> ids);
}

10
system/src/main/java/com/storeroom/modules/system/service/DataService.java

@ -0,0 +1,10 @@
package com.storeroom.modules.system.service;
import com.storeroom.modules.system.service.dto.UserDto;
import java.util.List;
public interface DataService {
List<Long> getDeptIds(UserDto user);
}

4
system/src/main/java/com/storeroom/modules/system/service/DeptService.java

@ -0,0 +1,4 @@
package com.storeroom.modules.system.service;
public interface DeptService {
}

45
system/src/main/java/com/storeroom/modules/system/service/DictDetailService.java

@ -0,0 +1,45 @@
package com.storeroom.modules.system.service;
import com.storeroom.modules.system.domain.DictDetail;
import com.storeroom.modules.system.service.dto.DictDetailDto;
import com.storeroom.modules.system.service.dto.DictDetailQueryCriteria;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.Map;
public interface DictDetailService {
/**
* 创建
* @param resources /
*/
void create(DictDetail resources);
/**
* 编辑
* @param resources /
*/
void update(DictDetail resources);
/**
* 删除
* @param id /
*/
void delete(Long id);
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Map<String,Object> queryAll(DictDetailQueryCriteria criteria, Pageable pageable);
/**
* 根据字典名称获取字典详情
* @param name 字典名称
* @return /
*/
List<DictDetailDto> getDictByName(String name);
}

57
system/src/main/java/com/storeroom/modules/system/service/DictService.java

@ -0,0 +1,57 @@
package com.storeroom.modules.system.service;
import com.storeroom.modules.system.domain.Dict;
import com.storeroom.modules.system.service.dto.DictDto;
import com.storeroom.modules.system.service.dto.DictQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface DictService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Map<String,Object> queryAll(DictQueryCriteria criteria, Pageable pageable);
/**
* 查询全部数据
* @param dict /
* @return /
*/
List<DictDto> queryAll(DictQueryCriteria dict);
/**
* 创建
* @param resources /
* @return /
*/
void create(Dict resources);
/**
* 编辑
* @param resources /
*/
void update(Dict resources);
/**
* 删除
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 导出数据
* @param queryAll 待导出的数据
* @param response /
* @throws IOException /
*/
void download(List<DictDto> queryAll, HttpServletResponse response) throws IOException;
}

4
system/src/main/java/com/storeroom/modules/system/service/JobService.java

@ -0,0 +1,4 @@
package com.storeroom.modules.system.service;
public interface JobService {
}

4
system/src/main/java/com/storeroom/modules/system/service/MenuService.java

@ -0,0 +1,4 @@
package com.storeroom.modules.system.service;
public interface MenuService {
}

118
system/src/main/java/com/storeroom/modules/system/service/RoleService.java

@ -0,0 +1,118 @@
package com.storeroom.modules.system.service;
import com.storeroom.modules.system.domain.Role;
import com.storeroom.modules.system.service.dto.RoleDto;
import com.storeroom.modules.system.service.dto.RoleQueryCriteria;
import com.storeroom.modules.system.service.dto.RoleSmallDto;
import com.storeroom.modules.system.service.dto.UserDto;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.GrantedAuthority;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public interface RoleService {
/**
* 查询全部数据
* @return /
*/
List<RoleDto> queryAll();
/**
* 根据ID查询
* @param id /
* @return /
*/
RoleDto findById(long id);
/**
* 创建
* @param resources /
*/
void create(Role resources);
/**
* 编辑
* @param resources /
*/
void update(Role resources);
/**
* 删除
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 根据用户ID查询
* @param id 用户ID
* @return /
*/
List<RoleSmallDto> findByUsersId(Long id);
/**
* 根据角色查询角色级别
* @param roles /
* @return /
*/
Integer findByRoles(Set<Role> roles);
/**
* 修改绑定的菜单
* @param resources /
* @param roleDTO /
*/
void updateMenu(Role resources, RoleDto roleDTO);
/**
* 解绑菜单
* @param id /
*/
void untiedMenu(Long id);
/**
* 待条件分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(RoleQueryCriteria criteria, Pageable pageable);
/**
* 查询全部
* @param criteria 条件
* @return /
*/
List<RoleDto> queryAll(RoleQueryCriteria criteria);
/**
* 导出数据
* @param queryAll 待导出的数据
* @param response /
* @throws IOException /
*/
void download(List<RoleDto> queryAll, HttpServletResponse response) throws IOException;
/**
* 获取用户权限信息
* @param user 用户信息
* @return 权限信息
*/
List<GrantedAuthority> mapToGrantedAuthorities(UserDto user);
/**
* 验证是否被用户关联
* @param ids /
*/
void verification(Set<Long> ids);
/**
* 根据菜单Id查询
* @param menuIds /
* @return /
*/
List<Role> findInMenuId(List<Long> menuIds);
}

99
system/src/main/java/com/storeroom/modules/system/service/UserService.java

@ -0,0 +1,99 @@
package com.storeroom.modules.system.service;
import com.storeroom.modules.system.domain.User;
import com.storeroom.modules.system.service.dto.UserDto;
import com.storeroom.modules.system.service.dto.UserQueryCriteria;
import org.springframework.data.domain.Pageable;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface UserService {
/**
* 根据ID查询
* @param id ID
* @return /
*/
UserDto findById(long id);
/**
* 新增用户
* @param resources /
*/
void create(User resources);
/**
* 编辑用户
* @param resources /
* @throws Exception /
*/
void update(User resources) throws Exception;
/**
* 删除用户
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 根据用户名查询
* @param userName /
* @return /
*/
UserDto findByName(String userName);
/**
* 修改密码
* @param username 用户名
* @param encryptPassword 密码
*/
void updatePass(String username, String encryptPassword);
/**
* 修改头像
* @param file 文件
* @return /
*/
Map<String, String> updateAvatar(MultipartFile file);
/**
* 修改邮箱
* @param username 用户名
* @param email 邮箱
*/
void updateEmail(String username, String email);
/**
* 查询全部
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(UserQueryCriteria criteria, Pageable pageable);
/**
* 查询全部不分页
* @param criteria 条件
* @return /
*/
List<UserDto> queryAll(UserQueryCriteria criteria);
/**
* 导出数据
* @param queryAll 待导出的数据
* @param response /
* @throws IOException /
*/
void download(List<UserDto> queryAll, HttpServletResponse response) throws IOException;
/**
* 用户自助修改资料
* @param resources /
*/
void updateCenter(User resources);
}

61
system/src/main/java/com/storeroom/modules/system/service/dto/DeptDto.java

@ -0,0 +1,61 @@
package com.storeroom.modules.system.service.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
public class DeptDto extends BaseDTO implements Serializable {
private Long id;
private String name;
private Boolean enabled;
private Integer deptSort;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<DeptDto> children;
private Long pid;
private Integer subCount;
public Boolean getHasChildren() {
return subCount > 0;
}
public Boolean getLeaf() {
return subCount <= 0;
}
public String getLabel() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DeptDto deptDto = (DeptDto) o;
return Objects.equals(id, deptDto.id) &&
Objects.equals(name, deptDto.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

29
system/src/main/java/com/storeroom/modules/system/service/dto/DeptQueryCriteria.java

@ -0,0 +1,29 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.DataPermission;
import com.storeroom.annotaion.Query;
import lombok.Data;
import java.sql.Timestamp;
import java.util.List;
@Data
@DataPermission(fieldName = "id")
public class DeptQueryCriteria {
@Query(type = Query.Type.INNER_LIKE)
private String name;
@Query
private Boolean enabled;
@Query
private Long pid;
@Query(type = Query.Type.IS_NULL, propName = "pid")
private Boolean pidIsNull;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

16
system/src/main/java/com/storeroom/modules/system/service/dto/DeptSmallDto.java

@ -0,0 +1,16 @@
package com.storeroom.modules.system.service.dto;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Data
public class DeptSmallDto implements Serializable {
private Long id;
private String name;
}

23
system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailDto.java

@ -0,0 +1,23 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
public class DictDetailDto extends BaseDTO implements Serializable {
private Long id;
private DictSmallDto dict;
private String label;
private String value;
private Integer dictSort;
}

15
system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailQueryCriteria.java

@ -0,0 +1,15 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.Query;
import lombok.Data;
@Data
public class DictDetailQueryCriteria {
@Query(type = Query.Type.INNER_LIKE)
private String label;
@Query(propName = "name",joinName = "dict")
private String dictName;
}

22
system/src/main/java/com/storeroom/modules/system/service/dto/DictDto.java

@ -0,0 +1,22 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
@Getter
@Setter
public class DictDto extends BaseDTO implements Serializable {
private Long id;
private List<DictDetailDto> dictDetails;
private String name;
private String description;
}

12
system/src/main/java/com/storeroom/modules/system/service/dto/DictQueryCriteria.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.Query;
import lombok.Data;
@Data
public class DictQueryCriteria {
@Query(blurry = "name,description")
private String blurry;
}

12
system/src/main/java/com/storeroom/modules/system/service/dto/DictSmallDto.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DictSmallDto {
private Long id;
}

27
system/src/main/java/com/storeroom/modules/system/service/dto/JobDto.java

@ -0,0 +1,27 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@NoArgsConstructor
public class JobDto extends BaseDTO implements Serializable {
private Long id;
private Integer jobSort;
private String name;
private Boolean enabled;
public JobDto(String name, Boolean enabled) {
this.name = name;
this.enabled = enabled;
}
}

16
system/src/main/java/com/storeroom/modules/system/service/dto/JobSmallDto.java

@ -0,0 +1,16 @@
package com.storeroom.modules.system.service.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class JobSmallDto implements Serializable {
private Long id;
private String name;
}

73
system/src/main/java/com/storeroom/modules/system/service/dto/MenuDto.java

@ -0,0 +1,73 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
public class MenuDto extends BaseDTO implements Serializable {
private Long id;
private List<MenuDto> children;
private Integer type;
private String permission;
private String title;
private Integer menuSort;
private String path;
private String component;
private Long pid;
private Integer subCount;
private Boolean iFrame;
private Boolean cache;
private Boolean hidden;
private String componentName;
private String icon;
public Boolean getHasChildren() {
return subCount > 0;
}
public Boolean getLeaf() {
return subCount <= 0;
}
public String getLabel() {
return title;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MenuDto menuDto = (MenuDto) o;
return Objects.equals(id, menuDto.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

24
system/src/main/java/com/storeroom/modules/system/service/dto/MenuQueryCriteria.java

@ -0,0 +1,24 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.Query;
import lombok.Data;
import java.sql.Timestamp;
import java.util.List;
@Data
public class MenuQueryCriteria {
@Query(blurry = "title,component,permission")
private String blurry;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
@Query(type = Query.Type.IS_NULL, propName = "pid")
private Boolean pidIsNull;
@Query
private Long pid;
}

46
system/src/main/java/com/storeroom/modules/system/service/dto/RoleDto.java

@ -0,0 +1,46 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
@Getter
@Setter
public class RoleDto extends BaseDTO implements Serializable {
private Long id;
private Set<MenuDto> menus;
private Set<DeptDto> depts;
private String name;
private String dataScope;
private Integer level;
private String description;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RoleDto roleDto = (RoleDto) o;
return Objects.equals(id, roleDto.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

17
system/src/main/java/com/storeroom/modules/system/service/dto/RoleQueryCriteria.java

@ -0,0 +1,17 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.Query;
import lombok.Data;
import java.sql.Timestamp;
import java.util.List;
@Data
public class RoleQueryCriteria {
@Query(blurry = "name,description")
private String blurry;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

18
system/src/main/java/com/storeroom/modules/system/service/dto/RoleSmallDto.java

@ -0,0 +1,18 @@
package com.storeroom.modules.system.service.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class RoleSmallDto implements Serializable {
private Long id;
private String name;
private Integer level;
private String dataScope;
}

50
system/src/main/java/com/storeroom/modules/system/service/dto/UserDto.java

@ -0,0 +1,50 @@
package com.storeroom.modules.system.service.dto;
import com.alibaba.fastjson.annotation.JSONField;
import com.storeroom.base.BaseDTO;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;
@Getter
@Setter
public class UserDto extends BaseDTO implements Serializable {
private Long id;
private Set<RoleSmallDto> roles;
private Set<JobSmallDto> jobs;
private DeptSmallDto dept;
private Long deptId;
private String username;
private String nickName;
private String email;
private String phone;
private String gender;
private String avatarName;
private String avatarPath;
@JSONField(serialize = false)
private String password;
private Boolean enabled;
@JSONField(serialize = false)
private Boolean isAdmin = false;
private Date pwdResetTime;
}

31
system/src/main/java/com/storeroom/modules/system/service/dto/UserQueryCriteria.java

@ -0,0 +1,31 @@
package com.storeroom.modules.system.service.dto;
import com.storeroom.annotaion.Query;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
public class UserQueryCriteria implements Serializable {
@Query
private Long id;
@Query(propName = "id", type = Query.Type.IN, joinName = "dept")
private Set<Long> deptIds = new HashSet<>();
@Query(blurry = "email,username,nickName")
private String blurry;
@Query
private Boolean enabled;
private Long deptId;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

208
system/src/main/java/com/storeroom/modules/system/service/impl/RoleServiceImpl.java

@ -0,0 +1,208 @@
package com.storeroom.modules.system.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.storeroom.exception.BaseException;
import com.storeroom.modules.security.service.UserCacheClean;
import com.storeroom.modules.system.domain.Menu;
import com.storeroom.modules.system.domain.Role;
import com.storeroom.modules.system.domain.User;
import com.storeroom.modules.system.repository.RoleRepository;
import com.storeroom.modules.system.repository.UserRepository;
import com.storeroom.modules.system.service.RoleService;
import com.storeroom.modules.system.service.dto.RoleDto;
import com.storeroom.modules.system.service.dto.RoleQueryCriteria;
import com.storeroom.modules.system.service.dto.RoleSmallDto;
import com.storeroom.modules.system.service.dto.UserDto;
import com.storeroom.modules.system.service.mapstruct.RoleMapper;
import com.storeroom.modules.system.service.mapstruct.RoleSmallMapper;
import com.storeroom.utils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements RoleService {
private final RoleRepository roleRepository;
private final RoleMapper roleMapper;
private final RoleSmallMapper roleSmallMapper;
private final RedisUtils redisUtils;
private final UserRepository userRepository;
private final UserCacheClean userCacheClean;
@Override
public List<RoleDto> queryAll() {
Sort sort = Sort.by(Sort.Direction.ASC, "level");
return roleMapper.toDto(roleRepository.findAll(sort));
}
@Override
public List<RoleDto> queryAll(RoleQueryCriteria criteria) {
return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
}
@Override
public Object queryAll(RoleQueryCriteria criteria, Pageable pageable) {
Page<Role> page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
return PageUtil.toPage(page.map(roleMapper::toDto));
}
@Override
@Cacheable(key = "'id:' + #p0")
@Transactional(rollbackFor = Exception.class)
public RoleDto findById(long id) {
Role role = roleRepository.findById(id).orElseGet(Role::new);
ValidationUtil.isNull(role.getId(), "Role", "id", id);
return roleMapper.toDto(role);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(Role resources) {
if (roleRepository.findByName(resources.getName()) != null) {
throw new BaseException("username", resources.getName());
}
roleRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(Role resources) {
Role role = roleRepository.findById(resources.getId()).orElseGet(Role::new);
ValidationUtil.isNull(role.getId(), "Role", "id", resources.getId());
Role role1 = roleRepository.findByName(resources.getName());
if (role1 != null && !role1.getId().equals(role.getId())) {
throw new BaseException("username", resources.getName());
}
role.setName(resources.getName());
role.setDescription(resources.getDescription());
role.setDataScope(resources.getDataScope());
role.setDepts(resources.getDepts());
role.setLevel(resources.getLevel());
roleRepository.save(role);
// 更新相关缓存
delCaches(role.getId(), null);
}
@Override
public void updateMenu(Role resources, RoleDto roleDTO) {
Role role = roleMapper.toEntity(roleDTO);
List<User> users = userRepository.findByRoleId(role.getId());
// 更新菜单
role.setMenus(resources.getMenus());
delCaches(resources.getId(), users);
roleRepository.save(role);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void untiedMenu(Long menuId) {
// 更新菜单
roleRepository.untiedMenu(menuId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
// 更新相关缓存
delCaches(id, null);
}
roleRepository.deleteAllByIdIn(ids);
}
@Override
public List<RoleSmallDto> findByUsersId(Long id) {
return roleSmallMapper.toDto(new ArrayList<>(roleRepository.findByUserId(id)));
}
@Override
public Integer findByRoles(Set<Role> roles) {
if (roles.size() == 0) {
return Integer.MAX_VALUE;
}
Set<RoleDto> roleDtos = new HashSet<>();
for (Role role : roles) {
roleDtos.add(findById(role.getId()));
}
return Collections.min(roleDtos.stream().map(RoleDto::getLevel).collect(Collectors.toList()));
}
@Override
@Cacheable(key = "'auth:' + #p0.id")
public List<GrantedAuthority> mapToGrantedAuthorities(UserDto user) {
Set<String> permissions = new HashSet<>();
// 如果是管理员直接返回
if (user.getIsAdmin()) {
permissions.add("admin");
return permissions.stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
Set<Role> roles = roleRepository.findByUserId(user.getId());
permissions = roles.stream().flatMap(role -> role.getMenus().stream())
.filter(menu -> StringUtils.isNotBlank(menu.getPermission()))
.map(Menu::getPermission).collect(Collectors.toSet());
return permissions.stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public void download(List<RoleDto> roles, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (RoleDto role : roles) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("角色名称", role.getName());
map.put("角色级别", role.getLevel());
map.put("描述", role.getDescription());
map.put("创建日期", role.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
@Override
public void verification(Set<Long> ids) {
if (userRepository.countByRoles(ids) > 0) {
throw new BaseException("所选角色存在用户关联,请解除关联再试!");
}
}
@Override
public List<Role> findInMenuId(List<Long> menuIds) {
return roleRepository.findInMenuId(menuIds);
}
/**
* 清理缓存
* @param id /
*/
public void delCaches(Long id, List<User> users) {
users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users;
if (CollectionUtil.isNotEmpty(users)) {
users.forEach(item -> userCacheClean.cleanUserCache(item.getUsername()));
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
redisUtils.delByKeys(CacheKey.DATA_USER, userIds);
redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds);
}
redisUtils.del(CacheKey.ROLE_ID + id);
}
}

239
system/src/main/java/com/storeroom/modules/system/service/impl/UserServiceImpl.java

@ -0,0 +1,239 @@
package com.storeroom.modules.system.service.impl;
import com.storeroom.config.FileProperties;
import com.storeroom.exception.BaseException;
import com.storeroom.modules.security.service.OnlineUserService;
import com.storeroom.modules.security.service.UserCacheClean;
import com.storeroom.modules.system.domain.User;
import com.storeroom.modules.system.repository.UserRepository;
import com.storeroom.modules.system.service.UserService;
import com.storeroom.modules.system.service.dto.JobSmallDto;
import com.storeroom.modules.system.service.dto.RoleSmallDto;
import com.storeroom.modules.system.service.dto.UserDto;
import com.storeroom.modules.system.service.dto.UserQueryCriteria;
import com.storeroom.modules.system.service.mapstruct.UserMapper;
import com.storeroom.utils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
private final FileProperties properties;
private final RedisUtils redisUtils;
private final UserCacheClean userCacheClean;
private final OnlineUserService onlineUserService;
@Override
public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
return PageUtil.toPage(page.map(userMapper::toDto));
}
@Override
public List<UserDto> queryAll(UserQueryCriteria criteria) {
List<User> users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));
return userMapper.toDto(users);
}
@Override
@Cacheable(key = "'id:' + #p0")
@Transactional(rollbackFor = Exception.class)
public UserDto findById(long id) {
User user = userRepository.findById(id).orElseGet(User::new);
ValidationUtil.isNull(user.getId(), "User", "id", id);
return userMapper.toDto(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(User resources) {
if (userRepository.findByUsername(resources.getUsername()) != null) {
throw new BaseException("username is not null");
}
if (userRepository.findByEmail(resources.getEmail()) != null) {
throw new BaseException("email is not null");
}
if (userRepository.findByPhone(resources.getPhone()) != null) {
throw new BaseException("phone is not null");
}
userRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(User resources) throws Exception {
User user = userRepository.findById(resources.getId()).orElseGet(User::new);
ValidationUtil.isNull(user.getId(), "User", "id", resources.getId());
User user1 = userRepository.findByUsername(resources.getUsername());
User user2 = userRepository.findByEmail(resources.getEmail());
User user3 = userRepository.findByPhone(resources.getPhone());
if (user1 != null && !user.getId().equals(user1.getId())) {
throw new BaseException("username", resources.getUsername());
}
if (user2 != null && !user.getId().equals(user2.getId())) {
throw new BaseException("email", resources.getEmail());
}
if (user3 != null && !user.getId().equals(user3.getId())) {
throw new BaseException("phone", resources.getPhone());
}
// 如果用户的角色改变
if (!resources.getRoles().equals(user.getRoles())) {
redisUtils.del(CacheKey.DATA_USER + resources.getId());
redisUtils.del(CacheKey.MENU_USER + resources.getId());
redisUtils.del(CacheKey.ROLE_AUTH + resources.getId());
}
// 如果用户被禁用则清除用户登录信息
if(!resources.getEnabled()){
onlineUserService.kickOutForUsername(resources.getUsername());
}
user.setUsername(resources.getUsername());
user.setEmail(resources.getEmail());
user.setEnabled(resources.getEnabled());
user.setRoles(resources.getRoles());
user.setDept(resources.getDept());
user.setJobs(resources.getJobs());
user.setPhone(resources.getPhone());
user.setNickName(resources.getNickName());
user.setGender(resources.getGender());
userRepository.save(user);
// 清除缓存
delCaches(user.getId(), user.getUsername());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCenter(User resources) {
User user = userRepository.findById(resources.getId()).orElseGet(User::new);
User user1 = userRepository.findByPhone(resources.getPhone());
if (user1 != null && !user.getId().equals(user1.getId())) {
throw new BaseException("Bad is phone", resources.getPhone());
}
user.setNickName(resources.getNickName());
user.setPhone(resources.getPhone());
user.setGender(resources.getGender());
userRepository.save(user);
// 清理缓存
delCaches(user.getId(), user.getUsername());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
// 清理缓存
UserDto user = findById(id);
delCaches(user.getId(), user.getUsername());
}
userRepository.deleteAllByIdIn(ids);
}
@Override
public UserDto findByName(String userName) {
User user = userRepository.findByUsername(userName);
if (user == null) {
throw new BaseException("name is not null", userName);
} else {
return userMapper.toDto(user);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePass(String username, String pass) {
userRepository.updatePass(username, pass, new Date());
flushCache(username);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, String> updateAvatar(MultipartFile multipartFile) {
// 文件大小验证
FileUtil.checkSize(properties.getAvatarMaxSize(), multipartFile.getSize());
// 验证文件上传的格式
String image = "gif jpg png jpeg";
String fileType = FileUtil.getExtensionName(multipartFile.getOriginalFilename());
if(fileType != null && !image.contains(fileType)){
throw new BaseException("文件格式错误!, 仅支持 " + image +" 格式");
}
User user = userRepository.findByUsername(SecurityUtils.getCurrentUsername());
String oldPath = user.getAvatarPath();
File file = FileUtil.upload(multipartFile, properties.getPath().getAvatar());
user.setAvatarPath(Objects.requireNonNull(file).getPath());
user.setAvatarName(file.getName());
userRepository.save(user);
if (StringUtils.isNotBlank(oldPath)) {
FileUtil.del(oldPath);
}
@NotBlank String username = user.getUsername();
flushCache(username);
return new HashMap<String, String>(1) {{
put("avatar", file.getName());
}};
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEmail(String username, String email) {
userRepository.updateEmail(username, email);
flushCache(username);
}
@Override
public void download(List<UserDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (UserDto userDTO : queryAll) {
List<String> roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList());
Map<String, Object> map = new LinkedHashMap<>();
map.put("用户名", userDTO.getUsername());
map.put("角色", roles);
map.put("部门", userDTO.getDept().getName());
map.put("岗位", userDTO.getJobs().stream().map(JobSmallDto::getName).collect(Collectors.toList()));
map.put("邮箱", userDTO.getEmail());
map.put("状态", userDTO.getEnabled() ? "启用" : "禁用");
map.put("手机号码", userDTO.getPhone());
map.put("修改密码的时间", userDTO.getPwdResetTime());
map.put("创建日期", userDTO.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
/**
* 清理缓存
*
* @param id /
*/
public void delCaches(Long id, String username) {
redisUtils.del(CacheKey.USER_ID + id);
flushCache(username);
}
/**
* 清理 登陆时 用户缓存信息
*
* @param username /
*/
private void flushCache(String username) {
userCacheClean.cleanUserCache(username);
}
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/DeptMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Dept;
import com.storeroom.modules.system.service.dto.DeptDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DeptMapper extends BaseMapper<DeptDto, Dept> {
}

11
system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictDetailMapper.java

@ -0,0 +1,11 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.DictDetail;
import com.storeroom.modules.system.service.dto.DictDetailDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", uses = {DictSmallMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DictDetailMapper extends BaseMapper<DictDetailDto, DictDetail> {
}

11
system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictMapper.java

@ -0,0 +1,11 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Dict;
import com.storeroom.modules.system.service.dto.DictDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DictMapper extends BaseMapper<DictDto, Dict> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictSmallMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Dict;
import com.storeroom.modules.system.service.dto.DictSmallDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DictSmallMapper extends BaseMapper<DictSmallDto, Dict> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/JobMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Job;
import com.storeroom.modules.system.service.dto.JobDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",uses = {DeptMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface JobMapper extends BaseMapper<JobDto, Job> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/MenuMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Menu;
import com.storeroom.modules.system.service.dto.MenuDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MenuMapper extends BaseMapper<MenuDto, Menu> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Role;
import com.storeroom.modules.system.service.dto.RoleDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", uses = {MenuMapper.class, DeptMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface RoleMapper extends BaseMapper<RoleDto, Role> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleSmallMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.Role;
import com.storeroom.modules.system.service.dto.RoleSmallDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface RoleSmallMapper extends BaseMapper<RoleSmallDto, Role> {
}

12
system/src/main/java/com/storeroom/modules/system/service/mapstruct/UserMapper.java

@ -0,0 +1,12 @@
package com.storeroom.modules.system.service.mapstruct;
import com.storeroom.base.BaseMapper;
import com.storeroom.modules.system.domain.User;
import com.storeroom.modules.system.service.dto.UserDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper extends BaseMapper<UserDto, User> {
}

41
system/src/main/resources/application.yml

@ -43,9 +43,50 @@ task:
# 队列容量
queue-capacity: 50
#jwt
jwt:
header: Authorization
# 令牌前缀
token-start-with: Bearer
# 必须使用最少88位的Base64对该令牌进行编码
base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
# 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
token-validity-in-seconds: 14400000
# 在线用户key
online-key: online-token-
# 验证码
code-key: code-key-
# token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
detect: 1800000
# 续期时间范围,默认1小时,单位毫秒
renew: 3600000
code:
expriration: 300
#是否开启 swagger-ui
swagger:
enabled: true
# IP 本地解析
ip:
local-parsing: true
# 文件存储路径
file:
mac:
path: ~/file/
avatar: ~/avatar/
linux:
path: /home/yxk_App/file/
avatar: /home/yxk_App/avatar/
windows:
path: D:\yxk_App\file\
avatar: D:\yxk_App\avatar\
# 文件大小 /M
maxSize: 100
avatarMaxSize: 5
#密码加密传输 此处为私钥
rsa:
private_key: MIICWwIBAAKBgQDyhGeZ23kNfDysndtEMHQgX+3YcKPNny2YFtlfvYTpHmVClbaiZK+g2VetX/mhkiSQtyp5OwSy+dm6WVX7QOvUO7iPNs2Hl+33d6rzzMhROs46rCTBpukgdaT0N6SC8NWkBF9ogoDzhEGNKIV0AGHRPWmL75ghflUPD2VflmAnywIDAQABAoGAPL47MMdPD7ihfd7gD7lPLNi6Oy8jaBpJkkGO2rMeekFZvY7AOvabIt+tXUifvv9a10B5i/njWGzKQymjJpaBOp+JswuBKEq8ZFqeyQJWsrj1zJC+HaCImP1frvgdtWIu1tldVE44X7jIQSFAd26JJsPslMntj/iE7VBkzQRYhjkCQQD/3+zu4OJP5XdSq4Y+HzIJ7u2JS8u/SxRQYQh1E6/yydgyWE96cze4jZQQTvxBRsOrXvBlVJdT+QmcV0YnlyMtAkEA8qLOBevOgH5qq1nLRMYU6zb0k5OiAaiI+Oq1mZn0kjcisyFZopAs2FH07DVlh+tzbGdzt4Q3PjLSEqhVFU4x1wJARljoKRzG27R4w8/IjpfBCB4aTF78W1Fm+lpTGu0YuKVpvR2ubDn1HdY+2OT+UWwFK75kVVeWa03SqJsN/KB+2QJAKD9XO2Y1F91gZlH7xMmyuJ2iDkTD79B8AAY232bJSeO5bstOagfOWIenv/LPh69Hsyip6jwVScz2ScAAdQtGewJACeSyLWjPnQ4ZgWrJyz6dyPgGKprDzQ00UMlvH3F2KJ9Ll99yfBah++GQXPfzjhD2ouWAQ+GaiS/RdSNDX1RoFg==
Loading…
Cancel
Save