From 9793823c8ba4f8832f862da941f22b4ad055fccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8A=9B?= Date: Sat, 7 May 2022 13:07:07 +0800 Subject: [PATCH] commit of system-1.0.jar --- .../storeroom/annotaion/DataPermission.java | 22 ++ .../java/com/storeroom/annotaion/Query.java | 14 +- .../exception/BadConfigurationException.java | 18 ++ .../java/com/storeroom/utils/CacheKey.java | 38 +++ .../com/storeroom/utils/EncryptUtils.java | 83 ++++++ .../java/com/storeroom/utils/PageUtil.java | 46 ++++ .../java/com/storeroom/utils/QueryHelp.java | 194 ++++++++++++++ .../com/storeroom/utils/ValidationUtil.java | 29 +++ .../utils/enums/RequestMethodEnum.java | 53 ++++ system/src/main/java/AppRun.java | 3 +- .../config/ConfigBeanConfiguration.java | 24 ++ .../security/config/SpringSecurityConfig.java | 179 +++++++++++++ .../security/config/bean/LoginCode.java | 50 ++++ .../security/config/bean/LoginCodeEnum.java | 30 +++ .../security/config/bean/LoginProperties.java | 89 +++++++ .../config/bean/SecurityProperties.java | 55 ++++ .../security/JwtAccessDeniedHandler.java | 19 ++ .../security/JwtAuthenticationEntryPoint.java | 19 ++ .../security/security/TokenConfigurer.java | 24 ++ .../security/security/TokenFilter.java | 92 +++++++ .../security/security/TokenProvider.java | 111 ++++++++ .../security/service/OnlineUserService.java | 173 +++++++++++++ .../security/service/UserCacheClean.java | 29 +++ .../service/UserDetailsServiceImpl.java | 72 ++++++ .../security/service/dto/AuthUserDto.java | 16 ++ .../security/service/dto/JwtUserDto.java | 65 +++++ .../security/service/dto/OnlineUserDto.java | 54 ++++ .../storeroom/modules/system/domain/Dept.java | 69 +++++ .../storeroom/modules/system/domain/Dict.java | 36 +++ .../modules/system/domain/DictDetail.java | 40 +++ .../storeroom/modules/system/domain/Job.java | 56 ++++ .../storeroom/modules/system/domain/Menu.java | 92 +++++++ .../storeroom/modules/system/domain/Role.java | 79 ++++++ .../storeroom/modules/system/domain/User.java | 108 ++++++++ .../modules/system/domain/vo/MenuMetaVo.java | 18 ++ .../modules/system/domain/vo/MenuVo.java | 29 +++ .../modules/system/domain/vo/UserPassVo.java | 15 ++ .../system/repository/DeptRepository.java | 51 ++++ .../system/repository/DictRepository.java | 24 ++ .../system/repository/JobRepository.java | 23 ++ .../system/repository/MenuRepository.java | 69 +++++ .../system/repository/RoleRepository.java | 61 +++++ .../system/repository/UserRepository.java | 112 ++++++++ .../modules/system/service/DataService.java | 10 + .../modules/system/service/DeptService.java | 4 + .../system/service/DictDetailService.java | 45 ++++ .../modules/system/service/DictService.java | 57 +++++ .../modules/system/service/JobService.java | 4 + .../modules/system/service/MenuService.java | 4 + .../modules/system/service/RoleService.java | 118 +++++++++ .../modules/system/service/UserService.java | 99 ++++++++ .../modules/system/service/dto/DeptDto.java | 61 +++++ .../system/service/dto/DeptQueryCriteria.java | 29 +++ .../system/service/dto/DeptSmallDto.java | 16 ++ .../system/service/dto/DictDetailDto.java | 23 ++ .../service/dto/DictDetailQueryCriteria.java | 15 ++ .../modules/system/service/dto/DictDto.java | 22 ++ .../system/service/dto/DictQueryCriteria.java | 12 + .../system/service/dto/DictSmallDto.java | 12 + .../modules/system/service/dto/JobDto.java | 27 ++ .../system/service/dto/JobSmallDto.java | 16 ++ .../modules/system/service/dto/MenuDto.java | 73 ++++++ .../system/service/dto/MenuQueryCriteria.java | 24 ++ .../modules/system/service/dto/RoleDto.java | 46 ++++ .../system/service/dto/RoleQueryCriteria.java | 17 ++ .../system/service/dto/RoleSmallDto.java | 18 ++ .../modules/system/service/dto/UserDto.java | 50 ++++ .../system/service/dto/UserQueryCriteria.java | 31 +++ .../system/service/impl/RoleServiceImpl.java | 208 +++++++++++++++ .../system/service/impl/UserServiceImpl.java | 239 ++++++++++++++++++ .../system/service/mapstruct/DeptMapper.java | 12 + .../service/mapstruct/DictDetailMapper.java | 11 + .../system/service/mapstruct/DictMapper.java | 11 + .../service/mapstruct/DictSmallMapper.java | 12 + .../system/service/mapstruct/JobMapper.java | 12 + .../system/service/mapstruct/MenuMapper.java | 12 + .../system/service/mapstruct/RoleMapper.java | 12 + .../service/mapstruct/RoleSmallMapper.java | 12 + .../system/service/mapstruct/UserMapper.java | 12 + system/src/main/resources/application.yml | 41 +++ 80 files changed, 3902 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/com/storeroom/annotaion/DataPermission.java create mode 100644 common/src/main/java/com/storeroom/exception/BadConfigurationException.java create mode 100644 common/src/main/java/com/storeroom/utils/CacheKey.java create mode 100644 common/src/main/java/com/storeroom/utils/EncryptUtils.java create mode 100644 common/src/main/java/com/storeroom/utils/PageUtil.java create mode 100644 common/src/main/java/com/storeroom/utils/QueryHelp.java create mode 100644 common/src/main/java/com/storeroom/utils/ValidationUtil.java create mode 100644 common/src/main/java/com/storeroom/utils/enums/RequestMethodEnum.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/SpringSecurityConfig.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/bean/LoginCode.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/bean/LoginCodeEnum.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/bean/LoginProperties.java create mode 100644 system/src/main/java/com/storeroom/modules/security/config/bean/SecurityProperties.java create mode 100644 system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java create mode 100644 system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java create mode 100644 system/src/main/java/com/storeroom/modules/security/security/TokenConfigurer.java create mode 100644 system/src/main/java/com/storeroom/modules/security/security/TokenFilter.java create mode 100644 system/src/main/java/com/storeroom/modules/security/security/TokenProvider.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/OnlineUserService.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/UserCacheClean.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/UserDetailsServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/dto/AuthUserDto.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/dto/JwtUserDto.java create mode 100644 system/src/main/java/com/storeroom/modules/security/service/dto/OnlineUserDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/Dept.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/Dict.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/DictDetail.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/Job.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/Menu.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/Role.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/User.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/vo/MenuMetaVo.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/vo/MenuVo.java create mode 100644 system/src/main/java/com/storeroom/modules/system/domain/vo/UserPassVo.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/DeptRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/JobRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/MenuRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/RoleRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/UserRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/DataService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/DeptService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/DictDetailService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/DictService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/JobService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/MenuService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/RoleService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/UserService.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DeptDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DeptQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DeptSmallDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DictDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DictQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/DictSmallDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/JobDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/JobSmallDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/MenuDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/MenuQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/RoleDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/RoleQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/RoleSmallDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/UserDto.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/UserQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/RoleServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/UserServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/DeptMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictDetailMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictSmallMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/JobMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/MenuMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleSmallMapper.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/mapstruct/UserMapper.java diff --git a/common/src/main/java/com/storeroom/annotaion/DataPermission.java b/common/src/main/java/com/storeroom/annotaion/DataPermission.java new file mode 100644 index 0000000..f832827 --- /dev/null +++ b/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 ""; +} diff --git a/common/src/main/java/com/storeroom/annotaion/Query.java b/common/src/main/java/com/storeroom/annotaion/Query.java index 6f14f28..62d7500 100644 --- a/common/src/main/java/com/storeroom/annotaion/Query.java +++ b/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 diff --git a/common/src/main/java/com/storeroom/exception/BadConfigurationException.java b/common/src/main/java/com/storeroom/exception/BadConfigurationException.java new file mode 100644 index 0000000..7e31646 --- /dev/null +++ b/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); + } +} diff --git a/common/src/main/java/com/storeroom/utils/CacheKey.java b/common/src/main/java/com/storeroom/utils/CacheKey.java new file mode 100644 index 0000000..a0eb9bf --- /dev/null +++ b/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:"; +} diff --git a/common/src/main/java/com/storeroom/utils/EncryptUtils.java b/common/src/main/java/com/storeroom/utils/EncryptUtils.java new file mode 100644 index 0000000..25c7d04 --- /dev/null +++ b/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; + } +} diff --git a/common/src/main/java/com/storeroom/utils/PageUtil.java b/common/src/main/java/com/storeroom/utils/PageUtil.java new file mode 100644 index 0000000..8a7619b --- /dev/null +++ b/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 toPage(Page page) { + Map map = new LinkedHashMap<>(2); + map.put("content",page.getContent()); + map.put("totalElements",page.getTotalElements()); + return map; + } + + /** + * 自定义分页 + */ + public static Map toPage(Object object, Object totalElements) { + Map map = new LinkedHashMap<>(2); + map.put("content",object); + map.put("totalElements",totalElements); + return map; + } +} diff --git a/common/src/main/java/com/storeroom/utils/QueryHelp.java b/common/src/main/java/com/storeroom/utils/QueryHelp.java new file mode 100644 index 0000000..7df154c --- /dev/null +++ b/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 Predicate getPredicate(Root root, Q query, CriteriaBuilder cb) { + List 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 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 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 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) fieldType),val)); + break; + case GREATER_THAN: + list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root) + .as((Class) fieldType), (Comparable) val)); + break; + case LESS_THAN: + list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root) + .as((Class) fieldType), (Comparable) val)); + break; + case LESS_THAN_NQ: + list.add(cb.lessThan(getExpression(attributeName,join,root) + .as((Class) 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)val)) { + list.add(getExpression(attributeName,join,root).in((Collection) val)); + } + break; + case NOT_IN: + if (CollUtil.isNotEmpty((Collection)val)) { + list.add(getExpression(attributeName,join,root).in((Collection) 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 between = new ArrayList<>((List)val); + list.add(cb.between(getExpression(attributeName, join, root).as((Class) 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 Expression getExpression(String attributeName, Join join, Root 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 getAllFields(Class clazz, List fields) { + if (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + getAllFields(clazz.getSuperclass(), fields); + } + return fields; + } +} diff --git a/common/src/main/java/com/storeroom/utils/ValidationUtil.java b/common/src/main/java/com/storeroom/utils/ValidationUtil.java new file mode 100644 index 0000000..d46bba4 --- /dev/null +++ b/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); + } +} diff --git a/common/src/main/java/com/storeroom/utils/enums/RequestMethodEnum.java b/common/src/main/java/com/storeroom/utils/enums/RequestMethodEnum.java new file mode 100644 index 0000000..af9f5b1 --- /dev/null +++ b/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; + } +} diff --git a/system/src/main/java/AppRun.java b/system/src/main/java/AppRun.java index 482b4e0..2df971b 100644 --- a/system/src/main/java/AppRun.java +++ b/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(); diff --git a/system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java b/system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java new file mode 100644 index 0000000..3893c0b --- /dev/null +++ b/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(); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/config/SpringSecurityConfig.java b/system/src/main/java/com/storeroom/modules/security/config/SpringSecurityConfig.java new file mode 100644 index 0000000..53e53c4 --- /dev/null +++ b/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 handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods(); + // 获取匿名标记 + Map> 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> getAnonymousUrl(Map handlerMethodMap) { + Map> anonymousUrls = new HashMap<>(6); + Set get = new HashSet<>(); + Set post = new HashSet<>(); + Set put = new HashSet<>(); + Set patch = new HashSet<>(); + Set delete = new HashSet<>(); + Set all = new HashSet<>(); + for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = infoEntry.getValue(); + AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); + if (null != anonymousAccess) { + List 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; + } + + +} diff --git a/system/src/main/java/com/storeroom/modules/security/config/bean/LoginCode.java b/system/src/main/java/com/storeroom/modules/security/config/bean/LoginCode.java new file mode 100644 index 0000000..ba859b9 --- /dev/null +++ b/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; + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/config/bean/LoginCodeEnum.java b/system/src/main/java/com/storeroom/modules/security/config/bean/LoginCodeEnum.java new file mode 100644 index 0000000..0f32fb4 --- /dev/null +++ b/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 + +} diff --git a/system/src/main/java/com/storeroom/modules/security/config/bean/LoginProperties.java b/system/src/main/java/com/storeroom/modules/security/config/bean/LoginProperties.java new file mode 100644 index 0000000..3b6df78 --- /dev/null +++ b/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; + } + + + +} diff --git a/system/src/main/java/com/storeroom/modules/security/config/bean/SecurityProperties.java b/system/src/main/java/com/storeroom/modules/security/config/bean/SecurityProperties.java new file mode 100644 index 0000000..f16b335 --- /dev/null +++ b/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+" "; + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java b/system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..2b2f742 --- /dev/null +++ b/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()); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java b/system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..170a124 --- /dev/null +++ b/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()); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/security/TokenConfigurer.java b/system/src/main/java/com/storeroom/modules/security/security/TokenConfigurer.java new file mode 100644 index 0000000..06f57bb --- /dev/null +++ b/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 { + 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/security/TokenFilter.java b/system/src/main/java/com/storeroom/modules/security/security/TokenFilter.java new file mode 100644 index 0000000..bcee483 --- /dev/null +++ b/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; + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/security/TokenProvider.java b/system/src/main/java/com/storeroom/modules/security/security/TokenProvider.java new file mode 100644 index 0000000..969bfaf --- /dev/null +++ b/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; + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/OnlineUserService.java b/system/src/main/java/com/storeroom/modules/security/service/OnlineUserService.java new file mode 100644 index 0000000..80493af --- /dev/null +++ b/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 getAll(String filter, Pageable pageable){ + List onlineUserDtos = getAll(filter); + return PageUtil.toPage( + PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos), + onlineUserDtos.size() + ); + } + + /** + * 查询全部数据,不分页 + * @param filter / + * @return / + */ + public List getAll(String filter){ + List keys = redisUtils.scan(properties.getOnlineKey() + "*"); + Collections.reverse(keys); + List 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 all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (OnlineUserDto user : all) { + Map 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 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 onlineUsers = getAll(username); + for (OnlineUserDto onlineUser : onlineUsers) { + if (onlineUser.getUserName().equals(username)) { + kickOut(onlineUser.getKey()); + } + } + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/UserCacheClean.java b/system/src/main/java/com/storeroom/modules/security/service/UserCacheClean.java new file mode 100644 index 0000000..cc056eb --- /dev/null +++ b/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(); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/UserDetailsServiceImpl.java b/system/src/main/java/com/storeroom/modules/security/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..d1e98db --- /dev/null +++ b/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 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; + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/dto/AuthUserDto.java b/system/src/main/java/com/storeroom/modules/security/service/dto/AuthUserDto.java new file mode 100644 index 0000000..10bc6ab --- /dev/null +++ b/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 = ""; +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/dto/JwtUserDto.java b/system/src/main/java/com/storeroom/modules/security/service/dto/JwtUserDto.java new file mode 100644 index 0000000..2221383 --- /dev/null +++ b/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 dataScopes; + + @JsonIgnore + private final List authorities; + + public Set 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(); + } +} diff --git a/system/src/main/java/com/storeroom/modules/security/service/dto/OnlineUserDto.java b/system/src/main/java/com/storeroom/modules/security/service/dto/OnlineUserDto.java new file mode 100644 index 0000000..83d00bc --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/Dept.java b/system/src/main/java/com/storeroom/modules/system/domain/Dept.java new file mode 100644 index 0000000..0cf1a82 --- /dev/null +++ b/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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/Dict.java b/system/src/main/java/com/storeroom/modules/system/domain/Dict.java new file mode 100644 index 0000000..986c472 --- /dev/null +++ b/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 dictDetails; + + @NotBlank + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/DictDetail.java b/system/src/main/java/com/storeroom/modules/system/domain/DictDetail.java new file mode 100644 index 0000000..6f7d7a4 --- /dev/null +++ b/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; + +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/Job.java b/system/src/main/java/com/storeroom/modules/system/domain/Job.java new file mode 100644 index 0000000..b396b57 --- /dev/null +++ b/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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/Menu.java b/system/src/main/java/com/storeroom/modules/system/domain/Menu.java new file mode 100644 index 0000000..86f3658 --- /dev/null +++ b/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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/Role.java b/system/src/main/java/com/storeroom/modules/system/domain/Role.java new file mode 100644 index 0000000..6542330 --- /dev/null +++ b/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 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 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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/User.java b/system/src/main/java/com/storeroom/modules/system/domain/User.java new file mode 100644 index 0000000..7038b47 --- /dev/null +++ b/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 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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/vo/MenuMetaVo.java b/system/src/main/java/com/storeroom/modules/system/domain/vo/MenuMetaVo.java new file mode 100644 index 0000000..22a51d6 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/vo/MenuVo.java b/system/src/main/java/com/storeroom/modules/system/domain/vo/MenuVo.java new file mode 100644 index 0000000..4de13f3 --- /dev/null +++ b/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 children; +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/vo/UserPassVo.java b/system/src/main/java/com/storeroom/modules/system/domain/vo/UserPassVo.java new file mode 100644 index 0000000..2874803 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/DeptRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/DeptRepository.java new file mode 100644 index 0000000..0cf9eab --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 根据 PID 查询 + * @param id pid + * @return / + */ + List findByPid(Long id); + + /** + * 获取顶级部门 + * @return / + */ + List 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 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); +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java new file mode 100644 index 0000000..c8de84b --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 删除 + * @param ids / + */ + void deleteByIdIn(Set ids); + + /** + * 查询 + * @param ids / + * @return / + */ + List findByIdIn(Set ids); +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/JobRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/JobRepository.java new file mode 100644 index 0000000..7836086 --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 根据名称查询 + * @param name 名称 + * @return / + */ + Job findByName(String name); + + /** + * 根据Id删除 + * @param ids / + */ + void deleteAllByIdIn(Set ids); +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/MenuRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/MenuRepository.java new file mode 100644 index 0000000..e19afc5 --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 根据菜单标题查询 + * @param title 菜单标题 + * @return / + */ + Menu findByTitle(String title); + + /** + * 根据组件名称查询 + * @param name 组件名称 + * @return / + */ + Menu findByComponentName(String name); + + /** + * 根据菜单的 PID 查询 + * @param pid / + * @return / + */ + List findByPid(long pid); + + /** + * 查询顶级菜单 + * @return / + */ + List 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 findByRoleIdsAndTypeNot(Set 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); +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/RoleRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/RoleRepository.java new file mode 100644 index 0000000..545c52c --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 根据名称查询 + * @param name / + * @return / + */ + Role findByName(String name); + + /** + * 删除多个角色 + * @param ids / + */ + void deleteAllByIdIn(Set 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 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 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 findInMenuId(List menuIds); +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/UserRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/UserRepository.java new file mode 100644 index 0000000..808e994 --- /dev/null +++ b/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, JpaSpecificationExecutor { + + /** + * 根据用户名查询 + * @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 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 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 findByMenuId(Long id); + + /** + * 根据Id删除 + * @param ids / + */ + void deleteAllByIdIn(Set 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 ids); + + /** + * 根据部门查询 + * @param deptIds / + * @return / + */ + @Query(value = "SELECT count(1) FROM sys_user u WHERE u.dept_id IN ?1", nativeQuery = true) + int countByDepts(Set 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 ids); +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/DataService.java b/system/src/main/java/com/storeroom/modules/system/service/DataService.java new file mode 100644 index 0000000..baf18da --- /dev/null +++ b/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 getDeptIds(UserDto user); +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/DeptService.java b/system/src/main/java/com/storeroom/modules/system/service/DeptService.java new file mode 100644 index 0000000..3c25f5f --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/DeptService.java @@ -0,0 +1,4 @@ +package com.storeroom.modules.system.service; + +public interface DeptService { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/DictDetailService.java b/system/src/main/java/com/storeroom/modules/system/service/DictDetailService.java new file mode 100644 index 0000000..b6189fa --- /dev/null +++ b/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 queryAll(DictDetailQueryCriteria criteria, Pageable pageable); + + /** + * 根据字典名称获取字典详情 + * @param name 字典名称 + * @return / + */ + List getDictByName(String name); +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/DictService.java b/system/src/main/java/com/storeroom/modules/system/service/DictService.java new file mode 100644 index 0000000..62b361b --- /dev/null +++ b/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 queryAll(DictQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param dict / + * @return / + */ + List queryAll(DictQueryCriteria dict); + + /** + * 创建 + * @param resources / + * @return / + */ + void create(Dict resources); + + /** + * 编辑 + * @param resources / + */ + void update(Dict resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/JobService.java b/system/src/main/java/com/storeroom/modules/system/service/JobService.java new file mode 100644 index 0000000..2927e4a --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/JobService.java @@ -0,0 +1,4 @@ +package com.storeroom.modules.system.service; + +public interface JobService { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/MenuService.java b/system/src/main/java/com/storeroom/modules/system/service/MenuService.java new file mode 100644 index 0000000..25cb6e5 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/MenuService.java @@ -0,0 +1,4 @@ +package com.storeroom.modules.system.service; + +public interface MenuService { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/RoleService.java b/system/src/main/java/com/storeroom/modules/system/service/RoleService.java new file mode 100644 index 0000000..610df43 --- /dev/null +++ b/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 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 ids); + + /** + * 根据用户ID查询 + * @param id 用户ID + * @return / + */ + List findByUsersId(Long id); + + /** + * 根据角色查询角色级别 + * @param roles / + * @return / + */ + Integer findByRoles(Set 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 queryAll(RoleQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 获取用户权限信息 + * @param user 用户信息 + * @return 权限信息 + */ + List mapToGrantedAuthorities(UserDto user); + + /** + * 验证是否被用户关联 + * @param ids / + */ + void verification(Set ids); + + /** + * 根据菜单Id查询 + * @param menuIds / + * @return / + */ + List findInMenuId(List menuIds); +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/UserService.java b/system/src/main/java/com/storeroom/modules/system/service/UserService.java new file mode 100644 index 0000000..c1c5ad1 --- /dev/null +++ b/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 ids); + + /** + * 根据用户名查询 + * @param userName / + * @return / + */ + UserDto findByName(String userName); + + /** + * 修改密码 + * @param username 用户名 + * @param encryptPassword 密码 + */ + void updatePass(String username, String encryptPassword); + + /** + * 修改头像 + * @param file 文件 + * @return / + */ + Map 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 queryAll(UserQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 用户自助修改资料 + * @param resources / + */ + void updateCenter(User resources); +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DeptDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DeptDto.java new file mode 100644 index 0000000..888dc83 --- /dev/null +++ b/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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DeptQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DeptQueryCriteria.java new file mode 100644 index 0000000..8eb74f0 --- /dev/null +++ b/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 createTime; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DeptSmallDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DeptSmallDto.java new file mode 100644 index 0000000..7ff4093 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailDto.java new file mode 100644 index 0000000..dfedbc7 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DictDetailQueryCriteria.java new file mode 100644 index 0000000..5823546 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DictDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DictDto.java new file mode 100644 index 0000000..5444165 --- /dev/null +++ b/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 dictDetails; + + private String name; + + private String description; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DictQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DictQueryCriteria.java new file mode 100644 index 0000000..1c21b22 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/DictSmallDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/DictSmallDto.java new file mode 100644 index 0000000..ddcd5f3 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/JobDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/JobDto.java new file mode 100644 index 0000000..6cfba67 --- /dev/null +++ b/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; + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/JobSmallDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/JobSmallDto.java new file mode 100644 index 0000000..9458cfa --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/MenuDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/MenuDto.java new file mode 100644 index 0000000..4c76444 --- /dev/null +++ b/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 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/MenuQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/MenuQueryCriteria.java new file mode 100644 index 0000000..926776c --- /dev/null +++ b/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 createTime; + + @Query(type = Query.Type.IS_NULL, propName = "pid") + private Boolean pidIsNull; + + @Query + private Long pid; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/RoleDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/RoleDto.java new file mode 100644 index 0000000..987fa70 --- /dev/null +++ b/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 menus; + + private Set 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/RoleQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/RoleQueryCriteria.java new file mode 100644 index 0000000..21d1e97 --- /dev/null +++ b/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 createTime; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/RoleSmallDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/RoleSmallDto.java new file mode 100644 index 0000000..b741446 --- /dev/null +++ b/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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/UserDto.java b/system/src/main/java/com/storeroom/modules/system/service/dto/UserDto.java new file mode 100644 index 0000000..bb814e0 --- /dev/null +++ b/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 roles; + + private Set 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; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/UserQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/UserQueryCriteria.java new file mode 100644 index 0000000..8f4b9e6 --- /dev/null +++ b/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 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 createTime; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/RoleServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..609381b --- /dev/null +++ b/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 queryAll() { + Sort sort = Sort.by(Sort.Direction.ASC, "level"); + return roleMapper.toDto(roleRepository.findAll(sort)); + } + + @Override + public List 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 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 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 ids) { + for (Long id : ids) { + // 更新相关缓存 + delCaches(id, null); + } + roleRepository.deleteAllByIdIn(ids); + } + + @Override + public List findByUsersId(Long id) { + return roleSmallMapper.toDto(new ArrayList<>(roleRepository.findByUserId(id))); + } + + @Override + public Integer findByRoles(Set roles) { + if (roles.size() == 0) { + return Integer.MAX_VALUE; + } + Set 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 mapToGrantedAuthorities(UserDto user) { + Set permissions = new HashSet<>(); + // 如果是管理员直接返回 + if (user.getIsAdmin()) { + permissions.add("admin"); + return permissions.stream().map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + Set 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 roles, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (RoleDto role : roles) { + Map 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 ids) { + if (userRepository.countByRoles(ids) > 0) { + throw new BaseException("所选角色存在用户关联,请解除关联再试!"); + } + } + + @Override + public List findInMenuId(List menuIds) { + return roleRepository.findInMenuId(menuIds); + } + + /** + * 清理缓存 + * @param id / + */ + public void delCaches(Long id, List users) { + users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users; + if (CollectionUtil.isNotEmpty(users)) { + users.forEach(item -> userCacheClean.cleanUserCache(item.getUsername())); + Set 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/UserServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..cc73edd --- /dev/null +++ b/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 page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); + return PageUtil.toPage(page.map(userMapper::toDto)); + } + + @Override + public List queryAll(UserQueryCriteria criteria) { + List 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 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 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(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 queryAll, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (UserDto userDTO : queryAll) { + List roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList()); + Map 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); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DeptMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DeptMapper.java new file mode 100644 index 0000000..7daef03 --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictDetailMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictDetailMapper.java new file mode 100644 index 0000000..7dc848d --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictMapper.java new file mode 100644 index 0000000..bff6862 --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictSmallMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/DictSmallMapper.java new file mode 100644 index 0000000..0e0d794 --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/JobMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/JobMapper.java new file mode 100644 index 0000000..f7984f2 --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/MenuMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/MenuMapper.java new file mode 100644 index 0000000..80dbad2 --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleMapper.java new file mode 100644 index 0000000..c576d0b --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleSmallMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/RoleSmallMapper.java new file mode 100644 index 0000000..c5b78ca --- /dev/null +++ b/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 { +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/mapstruct/UserMapper.java b/system/src/main/java/com/storeroom/modules/system/service/mapstruct/UserMapper.java new file mode 100644 index 0000000..76fd78d --- /dev/null +++ b/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 { +} diff --git a/system/src/main/resources/application.yml b/system/src/main/resources/application.yml index 13ce4eb..c992a16 100644 --- a/system/src/main/resources/application.yml +++ b/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==