From f3eb49bd4b7834f8420c39f17fa818181fc23deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8A=9B?= Date: Sat, 7 May 2022 17:32:44 +0800 Subject: [PATCH] commit code --- .../com/storeroom/config/RsaProperties.java | 18 + system/pom.xml | 12 +- .../main/java/{ => com/storeroom}/AppRun.java | 6 +- .../storeroom/config/ConfigurerAdapter.java | 47 +++ .../com/storeroom/config/WebSocketConfig.java | 15 + .../config/thread/AsyncTaskExecutePool.java | 51 +++ .../config/thread/AsyncTaskProperties.java | 20 ++ .../config/thread/TheadFactoryName.java | 44 +++ .../config/thread/ThreadPoolExecutorUtil.java | 22 ++ .../config/ConfigBeanConfiguration.java | 6 +- .../controller/AuthorizationController.java | 125 +++++++ .../security/JwtAccessDeniedHandler.java | 3 +- .../security/JwtAuthenticationEntryPoint.java | 7 +- .../security/service/dto/AuthUserDto.java | 6 + .../system/controller/UserController.java | 188 ++++++++++ .../repository/DictDetailRepository.java | 17 + .../system/repository/DictRepository.java | 3 +- .../modules/system/service/DeptService.java | 102 ++++++ .../modules/system/service/JobService.java | 66 ++++ .../modules/system/service/MenuService.java | 102 ++++++ .../system/service/dto/JobQueryCriteria.java | 24 ++ .../system/service/impl/DataServiceImpl.java | 73 ++++ .../system/service/impl/DeptServiceImpl.java | 267 ++++++++++++++ .../service/impl/DictDetailServiceImpl.java | 80 +++++ .../system/service/impl/DictServiceImpl.java | 104 ++++++ .../system/service/impl/JobServiceImpl.java | 106 ++++++ .../system/service/impl/MenuServiceImpl.java | 339 ++++++++++++++++++ system/src/main/resources/application-dev.yml | 68 ++++ system/src/main/resources/application.yml | 43 +-- system/src/main/resources/banner.txt | 8 + .../src/main/resources/generator.properties | 27 ++ .../main/resources/log4jdbc.log4j2.properties | 4 + system/src/main/resources/logback.xml | 45 +++ 33 files changed, 2003 insertions(+), 45 deletions(-) create mode 100644 common/src/main/java/com/storeroom/config/RsaProperties.java rename system/src/main/java/{ => com/storeroom}/AppRun.java (89%) create mode 100644 system/src/main/java/com/storeroom/config/ConfigurerAdapter.java create mode 100644 system/src/main/java/com/storeroom/config/WebSocketConfig.java create mode 100644 system/src/main/java/com/storeroom/config/thread/AsyncTaskExecutePool.java create mode 100644 system/src/main/java/com/storeroom/config/thread/AsyncTaskProperties.java create mode 100644 system/src/main/java/com/storeroom/config/thread/TheadFactoryName.java create mode 100644 system/src/main/java/com/storeroom/config/thread/ThreadPoolExecutorUtil.java create mode 100644 system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java create mode 100644 system/src/main/java/com/storeroom/modules/system/controller/UserController.java create mode 100644 system/src/main/java/com/storeroom/modules/system/repository/DictDetailRepository.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/dto/JobQueryCriteria.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/DataServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/DeptServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/DictDetailServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/DictServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/JobServiceImpl.java create mode 100644 system/src/main/java/com/storeroom/modules/system/service/impl/MenuServiceImpl.java create mode 100644 system/src/main/resources/banner.txt create mode 100644 system/src/main/resources/generator.properties create mode 100644 system/src/main/resources/log4jdbc.log4j2.properties create mode 100644 system/src/main/resources/logback.xml diff --git a/common/src/main/java/com/storeroom/config/RsaProperties.java b/common/src/main/java/com/storeroom/config/RsaProperties.java new file mode 100644 index 0000000..d865251 --- /dev/null +++ b/common/src/main/java/com/storeroom/config/RsaProperties.java @@ -0,0 +1,18 @@ +package com.storeroom.config; + + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Data +@Component +public class RsaProperties { + + public static String privateKey; + + @Value("${rsa.private_key}") + public void setPrivateKey(String privateKey) { + RsaProperties.privateKey = privateKey; + } +} diff --git a/system/pom.xml b/system/pom.xml index 5977132..66377f1 100644 --- a/system/pom.xml +++ b/system/pom.xml @@ -24,11 +24,6 @@ 1.0 - - - org.springframework.boot - spring-boot-starter-websocket - @@ -66,6 +61,13 @@ 5.3.6 + + + + org.springframework.boot + spring-boot-starter-websocket + + org.quartz-scheduler diff --git a/system/src/main/java/AppRun.java b/system/src/main/java/com/storeroom/AppRun.java similarity index 89% rename from system/src/main/java/AppRun.java rename to system/src/main/java/com/storeroom/AppRun.java index 2df971b..bff1007 100644 --- a/system/src/main/java/AppRun.java +++ b/system/src/main/java/com/storeroom/AppRun.java @@ -1,3 +1,5 @@ +package com.storeroom; + import com.storeroom.annotaion.rest.AnonymousGetMapping; import com.storeroom.utils.SpringContextHolder; import io.swagger.annotations.Api; @@ -17,8 +19,8 @@ import org.springframework.web.bind.annotation.RestController; @EnableJpaAuditing(auditorAwareRef = "auditorAware") public class AppRun { - public static void main(String[] args){ - SpringApplication.run(AppRun.class,args); + public static void main(String[] args) { + SpringApplication.run(AppRun.class, args); } diff --git a/system/src/main/java/com/storeroom/config/ConfigurerAdapter.java b/system/src/main/java/com/storeroom/config/ConfigurerAdapter.java new file mode 100644 index 0000000..125cd08 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/ConfigurerAdapter.java @@ -0,0 +1,47 @@ +package com.storeroom.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +@Configuration +@EnableWebMvc +public class ConfigurerAdapter implements WebMvcConfigurer { + + /** 文件配置 */ + private final FileProperties properties; + + public ConfigurerAdapter(FileProperties properties) { + this.properties = properties; + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + + + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + FileProperties.YsPath path = properties.getPath(); + String avatarUtl = "file:" + path.getAvatar().replace("\\","/"); + String pathUtl = "file:" + path.getPath().replace("\\","/"); + registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0); + registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0); + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); + } +} diff --git a/system/src/main/java/com/storeroom/config/WebSocketConfig.java b/system/src/main/java/com/storeroom/config/WebSocketConfig.java new file mode 100644 index 0000000..da01ee7 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/WebSocketConfig.java @@ -0,0 +1,15 @@ +package com.storeroom.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/system/src/main/java/com/storeroom/config/thread/AsyncTaskExecutePool.java b/system/src/main/java/com/storeroom/config/thread/AsyncTaskExecutePool.java new file mode 100644 index 0000000..a0a9e05 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/thread/AsyncTaskExecutePool.java @@ -0,0 +1,51 @@ +package com.storeroom.config.thread; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + + +@Slf4j +@Configuration +public class AsyncTaskExecutePool implements AsyncConfigurer { + + /** 注入配置类 */ + private final AsyncTaskProperties config; + + public AsyncTaskExecutePool(AsyncTaskProperties config) { + this.config = config; + } + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //核心线程池大小 + executor.setCorePoolSize(config.getCorePoolSize()); + //最大线程数 + executor.setMaxPoolSize(config.getMaxPoolSize()); + //队列容量 + executor.setQueueCapacity(config.getQueueCapacity()); + //活跃时间 + executor.setKeepAliveSeconds(config.getKeepAliveSeconds()); + //线程名字前缀 + executor.setThreadNamePrefix("yxk-async-"); + // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + log.error("===="+throwable.getMessage()+"====", throwable); + log.error("exception method:"+method.getName()); + }; + } +} diff --git a/system/src/main/java/com/storeroom/config/thread/AsyncTaskProperties.java b/system/src/main/java/com/storeroom/config/thread/AsyncTaskProperties.java new file mode 100644 index 0000000..5301284 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/thread/AsyncTaskProperties.java @@ -0,0 +1,20 @@ +package com.storeroom.config.thread; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "task.pool") +public class AsyncTaskProperties { + + private int corePoolSize; + + private int maxPoolSize; + + private int keepAliveSeconds; + + private int queueCapacity; +} diff --git a/system/src/main/java/com/storeroom/config/thread/TheadFactoryName.java b/system/src/main/java/com/storeroom/config/thread/TheadFactoryName.java new file mode 100644 index 0000000..6f47f16 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/thread/TheadFactoryName.java @@ -0,0 +1,44 @@ +package com.storeroom.config.thread; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + + +@Component +public class TheadFactoryName implements ThreadFactory { + + private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public TheadFactoryName() { + this("yxk-pool"); + } + + private TheadFactoryName(String name){ + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + //此时namePrefix就是 name + 第几个用这个工厂创建线程池的 + this.namePrefix = name + + POOL_NUMBER.getAndIncrement(); + } + + @Override + public Thread newThread(Runnable r) { + //此时线程的名字 就是 namePrefix + -thread- + 这个线程池中第几个执行的线程 + Thread t = new Thread(group, r, + namePrefix + "-thread-"+threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } +} diff --git a/system/src/main/java/com/storeroom/config/thread/ThreadPoolExecutorUtil.java b/system/src/main/java/com/storeroom/config/thread/ThreadPoolExecutorUtil.java new file mode 100644 index 0000000..5bb7ad2 --- /dev/null +++ b/system/src/main/java/com/storeroom/config/thread/ThreadPoolExecutorUtil.java @@ -0,0 +1,22 @@ +package com.storeroom.config.thread; + +import com.storeroom.utils.SpringContextHolder; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolExecutorUtil { + + public static ThreadPoolExecutor getPoll(){ + AsyncTaskProperties properties = SpringContextHolder.getBean(AsyncTaskProperties.class); + return new ThreadPoolExecutor( + properties.getCorePoolSize(), + properties.getMaxPoolSize(), + properties.getKeepAliveSeconds(), + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(properties.getQueueCapacity()), + new TheadFactoryName() + ); + } +} 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 index 3893c0b..13a8588 100644 --- a/system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java +++ b/system/src/main/java/com/storeroom/modules/security/config/ConfigBeanConfiguration.java @@ -2,7 +2,7 @@ package com.storeroom.modules.security.config; import com.storeroom.modules.security.config.bean.LoginProperties; -import org.springframework.boot.autoconfigure.security.SecurityProperties; +import com.storeroom.modules.security.config.bean.SecurityProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,13 +11,13 @@ import org.springframework.context.annotation.Configuration; public class ConfigBeanConfiguration { @Bean - @ConfigurationProperties(prefix="login",ignoreUnknownFields=true) + @ConfigurationProperties(prefix="login") public LoginProperties loginProperties(){ return new LoginProperties(); } @Bean - @ConfigurationProperties(prefix = "jwt",ignoreUnknownFields = true) + @ConfigurationProperties(prefix = "jwt") public SecurityProperties securityProperties(){ return new SecurityProperties(); } diff --git a/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java b/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java new file mode 100644 index 0000000..ea1248b --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java @@ -0,0 +1,125 @@ +package com.storeroom.modules.security.controller; + + +import cn.hutool.core.util.IdUtil; +import com.storeroom.annotaion.rest.AnonymousDeleteMapping; +import com.storeroom.annotaion.rest.AnonymousGetMapping; +import com.storeroom.annotaion.rest.AnonymousPostMapping; +import com.storeroom.config.RsaProperties; +import com.storeroom.exception.BaseException; +import com.storeroom.exception.constant.ResponseStatus; +import com.storeroom.modules.security.config.bean.LoginCodeEnum; +import com.storeroom.modules.security.config.bean.LoginProperties; +import com.storeroom.modules.security.config.bean.SecurityProperties; +import com.storeroom.modules.security.security.TokenProvider; +import com.storeroom.modules.security.service.OnlineUserService; +import com.storeroom.modules.security.service.dto.AuthUserDto; +import com.storeroom.modules.security.service.dto.JwtUserDto; +import com.storeroom.utils.*; +import com.storeroom.utils.enums.DataStatusEnum; +import com.wf.captcha.base.Captcha; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +@Api(tags = "系统:系统授权接口") +public class AuthorizationController { + + private final SecurityProperties properties; + private final RedisUtils redisUtils; + private final OnlineUserService onlineUserService; + private final TokenProvider tokenProvider; + private final AuthenticationManagerBuilder authenticationManagerBuilder; + + @Resource + private LoginProperties loginProperties; + + + @ApiOperation("登录授权") + @AnonymousPostMapping(value = "/login") + public ApiResponse login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception { + // 密码解密 + String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword()); + // 查询验证码 + String code = (String) redisUtils.get(authUser.getUuid()); + // 清除验证码 + redisUtils.del(authUser.getUuid()); + if (StringUtils.isBlank(code)) { + throw new BaseException("验证码不存在或已过期"); + } + if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) { + throw new BaseException("验证码错误"); + } + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(authUser.getUsername(), password); + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + String token = tokenProvider.createToken(authentication); + final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal(); + // 保存在线信息 + onlineUserService.save(jwtUserDto, token, request); + // 返回 token 与 用户信息 + Map authInfo = new HashMap(2) {{ + put("token", properties.getTokenStartWith() + token); + put("user", jwtUserDto); + }}; + if (loginProperties.isSingleLogin()) { + //踢掉之前已经登录的token + onlineUserService.checkLoginOnUser(authUser.getUsername(), token); + } + return ApiResponse.success(authInfo); + } + + @ApiOperation("获取用户信息") + @GetMapping(value = "/info") + public ApiResponse getUserInfo() { + return ApiResponse.success(SecurityUtils.getCurrentUser()); + } + + @ApiOperation("获取验证码") + @AnonymousGetMapping(value = "/code") + public ApiResponse getCode() { + // 获取运算的结果 + Captcha captcha = loginProperties.getCaptcha(); + String uuid = properties.getCodeKey() + IdUtil.simpleUUID(); + //当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型 + String captchaValue = captcha.text(); + if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) { + captchaValue = captchaValue.split("\\.")[0]; + } + // 保存 + redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES); + // 验证码信息 + Map imgResult = new HashMap(2) {{ + put("img", captcha.toBase64()); + put("uuid", uuid); + }}; + return ApiResponse.success(imgResult); + } + + @ApiOperation("退出登录") + @AnonymousDeleteMapping(value = "/logout") + public ApiResponse logout(HttpServletRequest request) { + onlineUserService.logout(tokenProvider.getToken(request)); + return ApiResponse.success(ResponseStatus.SUCCESS); + } +} 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 index 2b2f742..ea20e3d 100644 --- a/system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java +++ b/system/src/main/java/com/storeroom/modules/security/security/JwtAccessDeniedHandler.java @@ -11,8 +11,9 @@ import java.io.IOException; @Component public class JwtAccessDeniedHandler implements AccessDeniedHandler { + @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { //当前用户在没有权限的情况下访问受保护的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 index 170a124..d94d5c4 100644 --- a/system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java +++ b/system/src/main/java/com/storeroom/modules/security/security/JwtAuthenticationEntryPoint.java @@ -13,7 +13,10 @@ import java.io.IOException; 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()); + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage()); } } 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 index 10bc6ab..5a9bba9 100644 --- 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 @@ -1,7 +1,13 @@ package com.storeroom.modules.security.service.dto; +import lombok.Getter; +import lombok.Setter; + import javax.validation.constraints.NotBlank; + +@Getter +@Setter public class AuthUserDto { @NotBlank diff --git a/system/src/main/java/com/storeroom/modules/system/controller/UserController.java b/system/src/main/java/com/storeroom/modules/system/controller/UserController.java new file mode 100644 index 0000000..bd2038e --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/controller/UserController.java @@ -0,0 +1,188 @@ +package com.storeroom.modules.system.controller; + + +import cn.hutool.core.collection.CollectionUtil; +import com.storeroom.config.RsaProperties; +import com.storeroom.exception.BaseException; +import com.storeroom.modules.system.domain.Dept; +import com.storeroom.modules.system.domain.User; +import com.storeroom.modules.system.domain.vo.UserPassVo; +import com.storeroom.modules.system.service.DataService; +import com.storeroom.modules.system.service.DeptService; +import com.storeroom.modules.system.service.RoleService; +import com.storeroom.modules.system.service.UserService; +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.utils.PageUtil; +import com.storeroom.utils.RsaUtils; +import com.storeroom.utils.SecurityUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Api(tags = "系统:用户管理") +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +public class UserController { + + + private final PasswordEncoder passwordEncoder; + private final UserService userService; + private final DataService dataService; + private final DeptService deptService; + private final RoleService roleService; + + + + @ApiOperation("导出用户数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('user:list')") + public void exportUser(HttpServletResponse response, UserQueryCriteria criteria) throws IOException { + userService.download(userService.queryAll(criteria), response); + } + + @ApiOperation("查询用户") + @GetMapping + @PreAuthorize("@el.check('user:list')") + public ResponseEntity queryUser(UserQueryCriteria criteria, Pageable pageable){ + if (!ObjectUtils.isEmpty(criteria.getDeptId())) { + criteria.getDeptIds().add(criteria.getDeptId()); + // 先查找是否存在子节点 + List data = deptService.findByPid(criteria.getDeptId()); + // 然后把子节点的ID都加入到集合中 + criteria.getDeptIds().addAll(deptService.getDeptChildren(data)); + } + // 数据权限 + List dataScopes = dataService.getDeptIds(userService.findByName(SecurityUtils.getCurrentUsername())); + // criteria.getDeptIds() 不为空并且数据权限不为空则取交集 + if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)){ + // 取交集 + criteria.getDeptIds().retainAll(dataScopes); + if(!CollectionUtil.isEmpty(criteria.getDeptIds())){ + return new ResponseEntity<>(userService.queryAll(criteria,pageable), HttpStatus.OK); + } + } else { + // 否则取并集 + criteria.getDeptIds().addAll(dataScopes); + return new ResponseEntity<>(userService.queryAll(criteria,pageable),HttpStatus.OK); + } + return new ResponseEntity<>(PageUtil.toPage(null,0),HttpStatus.OK); + } + + //@Log("新增用户") + @ApiOperation("新增用户") + @PostMapping + @PreAuthorize("@el.check('user:add')") + public ResponseEntity createUser(@Validated @RequestBody User resources){ + checkLevel(resources); + // 默认密码 123456 + resources.setPassword(passwordEncoder.encode("123456")); + userService.create(resources); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + //@Log("修改用户") + @ApiOperation("修改用户") + @PutMapping + @PreAuthorize("@el.check('user:edit')") + public ResponseEntity updateUser(@Validated(User.Update.class) @RequestBody User resources) throws Exception { + checkLevel(resources); + userService.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + //@Log("修改用户:个人中心") + @ApiOperation("修改用户:个人中心") + @PutMapping(value = "center") + public ResponseEntity centerUser(@Validated(User.Update.class) @RequestBody User resources){ + if(!resources.getId().equals(SecurityUtils.getCurrentUserId())){ + throw new BaseException("不能修改他人资料"); + } + userService.updateCenter(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + //@Log("删除用户") + @ApiOperation("删除用户") + @DeleteMapping + @PreAuthorize("@el.check('user:del')") + public ResponseEntity deleteUser(@RequestBody Set ids){ + for (Long id : ids) { + Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + Integer optLevel = Collections.min(roleService.findByUsersId(id).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + if (currentLevel > optLevel) { + throw new BaseException("角色权限不足,不能删除:" + userService.findById(id).getUsername()); + } + } + userService.delete(ids); + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("修改密码") + @PostMapping(value = "/updatePass") + public ResponseEntity updateUserPass(@RequestBody UserPassVo passVo) throws Exception { + String oldPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getOldPass()); + String newPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getNewPass()); + UserDto user = userService.findByName(SecurityUtils.getCurrentUsername()); + if(!passwordEncoder.matches(oldPass, user.getPassword())){ + throw new BaseException("修改失败,旧密码错误"); + } + if(passwordEncoder.matches(newPass, user.getPassword())){ + throw new BaseException("新密码不能与旧密码相同"); + } + userService.updatePass(user.getUsername(),passwordEncoder.encode(newPass)); + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation("修改头像") + @PostMapping(value = "/updateAvatar") + public ResponseEntity updateUserAvatar(@RequestParam MultipartFile avatar){ + return new ResponseEntity<>(userService.updateAvatar(avatar), HttpStatus.OK); + } + + //@Log("修改邮箱") +// @ApiOperation("修改邮箱") +// @PostMapping(value = "/updateEmail/{code}") +// public ResponseEntity updateUserEmail(@PathVariable String code,@RequestBody User user) throws Exception { +// String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,user.getPassword()); +// UserDto userDto = userService.findByName(SecurityUtils.getCurrentUsername()); +// if(!passwordEncoder.matches(password, userDto.getPassword())){ +// throw new BaseException("密码错误"); +// } +// verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + user.getEmail(), code); +// userService.updateEmail(userDto.getUsername(),user.getEmail()); +// return new ResponseEntity<>(HttpStatus.OK); +// } + + /** + * 如果当前用户的角色级别低于创建用户的角色级别,则抛出权限不足的错误 + * @param resources / + */ + private void checkLevel(User resources) { + Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + Integer optLevel = roleService.findByRoles(resources.getRoles()); + if (currentLevel > optLevel) { + throw new BaseException("角色权限不足"); + } + } + +} diff --git a/system/src/main/java/com/storeroom/modules/system/repository/DictDetailRepository.java b/system/src/main/java/com/storeroom/modules/system/repository/DictDetailRepository.java new file mode 100644 index 0000000..71f952a --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/repository/DictDetailRepository.java @@ -0,0 +1,17 @@ +package com.storeroom.modules.system.repository; + +import com.storeroom.modules.system.domain.DictDetail; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.List; + +public interface DictDetailRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据字典名称查询 + * @param name / + * @return / + */ + List findByDictName(String name); +} 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 index c8de84b..ad02d99 100644 --- a/system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java +++ b/system/src/main/java/com/storeroom/modules/system/repository/DictRepository.java @@ -1,6 +1,7 @@ package com.storeroom.modules.system.repository; -import cn.hutool.core.lang.Dict; + +import com.storeroom.modules.system.domain.Dict; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 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 index 3c25f5f..9331d20 100644 --- a/system/src/main/java/com/storeroom/modules/system/service/DeptService.java +++ b/system/src/main/java/com/storeroom/modules/system/service/DeptService.java @@ -1,4 +1,106 @@ package com.storeroom.modules.system.service; +import com.storeroom.modules.system.domain.Dept; +import com.storeroom.modules.system.service.dto.DeptDto; +import com.storeroom.modules.system.service.dto.DeptQueryCriteria; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + public interface DeptService { + + /** + * 查询所有数据 + * @param criteria 条件 + * @param isQuery / + * @throws Exception / + * @return / + */ + List queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception; + + /** + * 根据ID查询 + * @param id / + * @return / + */ + DeptDto findById(Long id); + + /** + * 创建 + * @param resources / + */ + void create(Dept resources); + + /** + * 编辑 + * @param resources / + */ + void update(Dept resources); + + /** + * 删除 + * @param deptDtos / + * + */ + void delete(Set deptDtos); + + /** + * 根据PID查询 + * @param pid / + * @return / + */ + List findByPid(long pid); + + /** + * 根据角色ID查询 + * @param id / + * @return / + */ + Set findByRoleId(Long id); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 获取待删除的部门 + * @param deptList / + * @param deptDtos / + * @return / + */ + Set getDeleteDepts(List deptList, Set deptDtos); + + /** + * 根据ID获取同级与上级数据 + * @param deptDto / + * @param depts / + * @return / + */ + List getSuperior(DeptDto deptDto, List depts); + + /** + * 构建树形数据 + * @param deptDtos / + * @return / + */ + Object buildTree(List deptDtos); + + /** + * 获取 + * @param deptList + * @return + */ + List getDeptChildren(List deptList); + + /** + * 验证是否被角色或用户关联 + * @param deptDtos / + */ + void verification(Set deptDtos); } 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 index 2927e4a..f2f8733 100644 --- a/system/src/main/java/com/storeroom/modules/system/service/JobService.java +++ b/system/src/main/java/com/storeroom/modules/system/service/JobService.java @@ -1,4 +1,70 @@ package com.storeroom.modules.system.service; +import com.storeroom.modules.system.domain.Job; +import com.storeroom.modules.system.service.dto.JobDto; +import com.storeroom.modules.system.service.dto.JobQueryCriteria; +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 JobService { + + /** + * 根据ID查询 + * @param id / + * @return / + */ + JobDto findById(Long id); + + /** + * 创建 + * @param resources / + * @return / + */ + void create(Job resources); + + /** + * 编辑 + * @param resources / + */ + void update(Job resources); + + /** + * 删除 + * @param ids / + */ + void delete(Set ids); + + /** + * 分页查询 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + Map queryAll(JobQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部数据 + * @param criteria / + * @return / + */ + List queryAll(JobQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 验证是否被用户关联 + * @param ids / + */ + void verification(Set ids); } 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 index 25cb6e5..4c89bf5 100644 --- a/system/src/main/java/com/storeroom/modules/system/service/MenuService.java +++ b/system/src/main/java/com/storeroom/modules/system/service/MenuService.java @@ -1,4 +1,106 @@ package com.storeroom.modules.system.service; +import com.storeroom.modules.system.domain.Menu; +import com.storeroom.modules.system.service.dto.MenuDto; +import com.storeroom.modules.system.service.dto.MenuQueryCriteria; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + public interface MenuService { + + /** + * 查询全部数据 + * @param criteria 条件 + * @param isQuery / + * @throws Exception / + * @return / + */ + List queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception; + + /** + * 根据ID查询 + * @param id / + * @return / + */ + MenuDto findById(long id); + + /** + * 创建 + * @param resources / + */ + void create(Menu resources); + + /** + * 编辑 + * @param resources / + */ + void update(Menu resources); + + /** + * 获取所有子节点,包含自身ID + * @param menuList / + * @param menuSet / + * @return / + */ + Set getChildMenus(List menuList, Set menuSet); + + /** + * 构建菜单树 + * @param menuDtos 原始数据 + * @return / + */ + List buildTree(List menuDtos); + + /** + * 构建菜单树 + * @param menuDtos / + * @return / + */ + Object buildMenus(List menuDtos); + + /** + * 根据ID查询 + * @param id / + * @return / + */ + Menu findOne(Long id); + + /** + * 删除 + * @param menuSet / + */ + void delete(Set menuSet); + + /** + * 导出 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 懒加载菜单数据 + * @param pid / + * @return / + */ + List getMenus(Long pid); + + /** + * 根据ID获取同级与上级数据 + * @param menuDto / + * @param objects / + * @return / + */ + List getSuperior(MenuDto menuDto, List objects); + + /** + * 根据当前用户获取菜单 + * @param currentUserId / + * @return / + */ + List findByUser(Long currentUserId); } diff --git a/system/src/main/java/com/storeroom/modules/system/service/dto/JobQueryCriteria.java b/system/src/main/java/com/storeroom/modules/system/service/dto/JobQueryCriteria.java new file mode 100644 index 0000000..1774d68 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/dto/JobQueryCriteria.java @@ -0,0 +1,24 @@ +package com.storeroom.modules.system.service.dto; + +import com.storeroom.annotaion.Query; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.sql.Timestamp; +import java.util.List; + + +@Data +@NoArgsConstructor +public class JobQueryCriteria { + + + @Query(type = Query.Type.INNER_LIKE) + private String name; + + @Query + private Boolean enabled; + + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/DataServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/DataServiceImpl.java new file mode 100644 index 0000000..88c76e3 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/DataServiceImpl.java @@ -0,0 +1,73 @@ +package com.storeroom.modules.system.service.impl; + + +import com.storeroom.modules.system.domain.Dept; +import com.storeroom.modules.system.service.DataService; +import com.storeroom.modules.system.service.DeptService; +import com.storeroom.modules.system.service.RoleService; +import com.storeroom.modules.system.service.dto.RoleSmallDto; +import com.storeroom.modules.system.service.dto.UserDto; +import com.storeroom.utils.enums.DataScopeEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "data") +public class DataServiceImpl implements DataService { + + private final RoleService roleService; + private final DeptService deptService; + + + /** + * 用户角色改变时需清理缓存 + * @param user / + * @return / + */ + @Override + @Cacheable(key = "'user:' + #p0.id") + public List getDeptIds(UserDto user) { + // 用于存储部门id + Set deptIds = new HashSet<>(); + // 查询用户角色 + List roleSet = roleService.findByUsersId(user.getId()); + // 获取对应的部门ID + for (RoleSmallDto role : roleSet) { + DataScopeEnum dataScopeEnum = DataScopeEnum.find(role.getDataScope()); + switch (Objects.requireNonNull(dataScopeEnum)) { + case THIS_LEVEL: + deptIds.add(user.getDept().getId()); + break; + case CUSTOMIZE: + deptIds.addAll(getCustomize(deptIds, role)); + break; + default: + return new ArrayList<>(deptIds); + } + } + return new ArrayList<>(deptIds); + } + + /** + * 获取自定义的数据权限 + * @param deptIds 部门ID + * @param role 角色 + * @return 数据权限ID + */ + public Set getCustomize(Set deptIds, RoleSmallDto role){ + Set depts = deptService.findByRoleId(role.getId()); + for (Dept dept : depts) { + deptIds.add(dept.getId()); + List deptChildren = deptService.findByPid(dept.getId()); + if (deptChildren != null && deptChildren.size() != 0) { + deptIds.addAll(deptService.getDeptChildren(deptChildren)); + } + } + return deptIds; + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/DeptServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..18400ec --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/DeptServiceImpl.java @@ -0,0 +1,267 @@ +package com.storeroom.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.storeroom.exception.BaseException; +import com.storeroom.modules.system.domain.Dept; +import com.storeroom.modules.system.domain.User; +import com.storeroom.modules.system.repository.DeptRepository; +import com.storeroom.modules.system.repository.RoleRepository; +import com.storeroom.modules.system.repository.UserRepository; +import com.storeroom.modules.system.service.DeptService; +import com.storeroom.modules.system.service.dto.DeptDto; +import com.storeroom.modules.system.service.dto.DeptQueryCriteria; +import com.storeroom.modules.system.service.mapstruct.DeptMapper; +import com.storeroom.utils.*; +import com.storeroom.utils.enums.DataScopeEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "dept") +public class DeptServiceImpl implements DeptService { + + private final DeptRepository deptRepository; + private final DeptMapper deptMapper; + private final UserRepository userRepository; + private final RedisUtils redisUtils; + private final RoleRepository roleRepository; + + + @Override + public List queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception { + Sort sort = Sort.by(Sort.Direction.ASC, "deptSort"); + String dataScopeType = SecurityUtils.getDataScopeType(); + if (isQuery) { + if(dataScopeType.equals(DataScopeEnum.ALL.getValue())){ + criteria.setPidIsNull(true); + } + List fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>()); + List fieldNames = new ArrayList(){{ add("pidIsNull");add("enabled");}}; + for (Field field : fields) { + //设置对象的访问权限,保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if(fieldNames.contains(field.getName())){ + continue; + } + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + List list = deptMapper.toDto(deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort)); + // 如果为空,就代表为自定义权限或者本级权限,就需要去重,不理解可以注释掉,看查询结果 + if(StringUtils.isBlank(dataScopeType)){ + return deduplication(list); + } + return list; + } + + @Override + @Cacheable(key = "'id:' + #p0") + public DeptDto findById(Long id) { + Dept dept = deptRepository.findById(id).orElseGet(Dept::new); + ValidationUtil.isNull(dept.getId(),"Dept","id",id); + return deptMapper.toDto(dept); + } + + @Override + public List findByPid(long pid) { + return deptRepository.findByPid(pid); + } + + @Override + public Set findByRoleId(Long id) { + return deptRepository.findByRoleId(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Dept resources) { + deptRepository.save(resources); + // 计算子节点数目 + resources.setSubCount(0); + // 清理缓存 + updateSubCnt(resources.getPid()); + // 清理自定义角色权限的datascope缓存 + delCaches(resources.getPid()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Dept resources) { + // 旧的部门 + Long oldPid = findById(resources.getId()).getPid(); + Long newPid = resources.getPid(); + if(resources.getPid() != null && resources.getId().equals(resources.getPid())) { + throw new BaseException("上级不能为自己"); + } + Dept dept = deptRepository.findById(resources.getId()).orElseGet(Dept::new); + ValidationUtil.isNull( dept.getId(),"Dept","id",resources.getId()); + resources.setId(dept.getId()); + deptRepository.save(resources); + // 更新父节点中子节点数目 + updateSubCnt(oldPid); + updateSubCnt(newPid); + // 清理缓存 + delCaches(resources.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set deptDtos) { + for (DeptDto deptDto : deptDtos) { + // 清理缓存 + delCaches(deptDto.getId()); + deptRepository.deleteById(deptDto.getId()); + updateSubCnt(deptDto.getPid()); + } + } + + @Override + public void download(List deptDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DeptDto deptDTO : deptDtos) { + Map map = new LinkedHashMap<>(); + map.put("部门名称", deptDTO.getName()); + map.put("部门状态", deptDTO.getEnabled() ? "启用" : "停用"); + map.put("创建日期", deptDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + public Set getDeleteDepts(List menuList, Set deptDtos) { + for (Dept dept : menuList) { + deptDtos.add(deptMapper.toDto(dept)); + List depts = deptRepository.findByPid(dept.getId()); + if(depts!=null && depts.size()!=0){ + getDeleteDepts(depts, deptDtos); + } + } + return deptDtos; + } + + @Override + public List getDeptChildren(List deptList) { + List list = new ArrayList<>(); + deptList.forEach(dept -> { + if (dept!=null && dept.getEnabled()) { + List depts = deptRepository.findByPid(dept.getId()); + if (depts.size() != 0) { + list.addAll(getDeptChildren(depts)); + } + list.add(dept.getId()); + } + } + ); + return list; + } + + @Override + public List getSuperior(DeptDto deptDto, List depts) { + if(deptDto.getPid() == null){ + depts.addAll(deptRepository.findByPidIsNull()); + return deptMapper.toDto(depts); + } + depts.addAll(deptRepository.findByPid(deptDto.getPid())); + return getSuperior(findById(deptDto.getPid()), depts); + } + + @Override + public Object buildTree(List deptDtos) { + Set trees = new LinkedHashSet<>(); + Set depts= new LinkedHashSet<>(); + List deptNames = deptDtos.stream().map(DeptDto::getName).collect(Collectors.toList()); + boolean isChild; + for (DeptDto deptDTO : deptDtos) { + isChild = false; + if (deptDTO.getPid() == null) { + trees.add(deptDTO); + } + for (DeptDto it : deptDtos) { + if (it.getPid() != null && deptDTO.getId().equals(it.getPid())) { + isChild = true; + if (deptDTO.getChildren() == null) { + deptDTO.setChildren(new ArrayList<>()); + } + deptDTO.getChildren().add(it); + } + } + if(isChild) { + depts.add(deptDTO); + } else if(deptDTO.getPid() != null && !deptNames.contains(findById(deptDTO.getPid()).getName())) { + depts.add(deptDTO); + } + } + + if (CollectionUtil.isEmpty(trees)) { + trees = depts; + } + Map map = new HashMap<>(2); + map.put("totalElements",deptDtos.size()); + map.put("content",CollectionUtil.isEmpty(trees)? deptDtos :trees); + return map; + } + + @Override + public void verification(Set deptDtos) { + Set deptIds = deptDtos.stream().map(DeptDto::getId).collect(Collectors.toSet()); + if(userRepository.countByDepts(deptIds) > 0){ + throw new BaseException("所选部门存在用户关联,请解除后再试!"); + } + if(roleRepository.countByDepts(deptIds) > 0){ + throw new BaseException("所选部门存在角色关联,请解除后再试!"); + } + } + + private void updateSubCnt(Long deptId){ + if(deptId != null){ + int count = deptRepository.countByPid(deptId); + deptRepository.updateSubCntById(count, deptId); + } + } + + private List deduplication(List list) { + List deptDtos = new ArrayList<>(); + for (DeptDto deptDto : list) { + boolean flag = true; + for (DeptDto dto : list) { + if (dto.getId().equals(deptDto.getPid())) { + flag = false; + break; + } + } + if (flag){ + deptDtos.add(deptDto); + } + } + return deptDtos; + } + + /** + * 清理缓存 + * @param id / + */ + public void delCaches(Long id){ + List users = userRepository.findByRoleDeptId(id); + // 删除数据权限 + redisUtils.delByKeys(CacheKey.DATA_USER, users.stream().map(User::getId).collect(Collectors.toSet())); + redisUtils.del(CacheKey.DEPT_ID + id); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/DictDetailServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/DictDetailServiceImpl.java new file mode 100644 index 0000000..1e7a1b1 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/DictDetailServiceImpl.java @@ -0,0 +1,80 @@ +package com.storeroom.modules.system.service.impl; + +import com.storeroom.modules.system.domain.Dict; +import com.storeroom.modules.system.domain.DictDetail; +import com.storeroom.modules.system.repository.DictDetailRepository; +import com.storeroom.modules.system.repository.DictRepository; +import com.storeroom.modules.system.service.DictDetailService; +import com.storeroom.modules.system.service.dto.DictDetailDto; +import com.storeroom.modules.system.service.dto.DictDetailQueryCriteria; +import com.storeroom.modules.system.service.mapstruct.DictDetailMapper; +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 java.util.List; +import java.util.Map; + + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "dict") +public class DictDetailServiceImpl implements DictDetailService { + + + private final DictRepository dictRepository; + private final DictDetailRepository dictDetailRepository; + private final DictDetailMapper dictDetailMapper; + private final RedisUtils redisUtils; + + + @Override + public Map queryAll(DictDetailQueryCriteria criteria, Pageable pageable) { + Page page = dictDetailRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(dictDetailMapper::toDto)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(DictDetail resources) { + dictDetailRepository.save(resources); + // 清理缓存 + delCaches(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(DictDetail resources) { + DictDetail dictDetail = dictDetailRepository.findById(resources.getId()).orElseGet(DictDetail::new); + ValidationUtil.isNull( dictDetail.getId(),"DictDetail","id",resources.getId()); + resources.setId(dictDetail.getId()); + dictDetailRepository.save(resources); + // 清理缓存 + delCaches(resources); + } + + @Override + @Cacheable(key = "'name:' + #p0") + public List getDictByName(String name) { + return dictDetailMapper.toDto(dictDetailRepository.findByDictName(name)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + DictDetail dictDetail = dictDetailRepository.findById(id).orElseGet(DictDetail::new); + // 清理缓存 + delCaches(dictDetail); + dictDetailRepository.deleteById(id); + } + + public void delCaches(DictDetail dictDetail){ + Dict dict = dictRepository.findById(dictDetail.getDict().getId()).orElseGet(Dict::new); + redisUtils.del(CacheKey.DICT_NAME + dict.getName()); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/DictServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/DictServiceImpl.java new file mode 100644 index 0000000..8d7c254 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/DictServiceImpl.java @@ -0,0 +1,104 @@ +package com.storeroom.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.storeroom.modules.system.domain.Dict; +import com.storeroom.modules.system.repository.DictRepository; +import com.storeroom.modules.system.service.DictService; +import com.storeroom.modules.system.service.dto.DictDetailDto; +import com.storeroom.modules.system.service.dto.DictDto; +import com.storeroom.modules.system.service.dto.DictQueryCriteria; +import com.storeroom.modules.system.service.mapstruct.DictMapper; +import com.storeroom.utils.*; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "dict") +public class DictServiceImpl implements DictService { + + private final DictRepository dictRepository; + private final DictMapper dictMapper; + private final RedisUtils redisUtils; + + @Override + public Map queryAll(DictQueryCriteria dict, Pageable pageable){ + Page page = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb), pageable); + return PageUtil.toPage(page.map(dictMapper::toDto)); + } + + @Override + public List queryAll(DictQueryCriteria dict) { + List list = dictRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, dict, cb)); + return dictMapper.toDto(list); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Dict resources) { + dictRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Dict resources) { + // 清理缓存 + delCaches(resources); + Dict dict = dictRepository.findById(resources.getId()).orElseGet(Dict::new); + ValidationUtil.isNull( dict.getId(),"Dict","id",resources.getId()); + dict.setName(resources.getName()); + dict.setDescription(resources.getDescription()); + dictRepository.save(dict); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + // 清理缓存 + List dicts = dictRepository.findByIdIn(ids); + for (Dict dict : dicts) { + delCaches(dict); + } + dictRepository.deleteByIdIn(ids); + } + + @Override + public void download(List dictDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (DictDto dictDTO : dictDtos) { + if(CollectionUtil.isNotEmpty(dictDTO.getDictDetails())){ + for (DictDetailDto dictDetail : dictDTO.getDictDetails()) { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dictDTO.getName()); + map.put("字典描述", dictDTO.getDescription()); + map.put("字典标签", dictDetail.getLabel()); + map.put("字典值", dictDetail.getValue()); + map.put("创建日期", dictDetail.getCreateTime()); + list.add(map); + } + } else { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dictDTO.getName()); + map.put("字典描述", dictDTO.getDescription()); + map.put("字典标签", null); + map.put("字典值", null); + map.put("创建日期", dictDTO.getCreateTime()); + list.add(map); + } + } + FileUtil.downloadExcel(list, response); + } + + public void delCaches(Dict dict){ + redisUtils.del(CacheKey.DICT_NAME + dict.getName()); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/JobServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/JobServiceImpl.java new file mode 100644 index 0000000..61c0126 --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/JobServiceImpl.java @@ -0,0 +1,106 @@ +package com.storeroom.modules.system.service.impl; + +import com.storeroom.exception.BaseException; +import com.storeroom.modules.system.domain.Job; +import com.storeroom.modules.system.repository.JobRepository; +import com.storeroom.modules.system.repository.UserRepository; +import com.storeroom.modules.system.service.JobService; +import com.storeroom.modules.system.service.dto.JobDto; +import com.storeroom.modules.system.service.dto.JobQueryCriteria; +import com.storeroom.modules.system.service.mapstruct.JobMapper; +import com.storeroom.utils.*; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +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 javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "job") +public class JobServiceImpl implements JobService { + + private final JobRepository jobRepository; + private final JobMapper jobMapper; + private final RedisUtils redisUtils; + private final UserRepository userRepository; + + @Override + public Map queryAll(JobQueryCriteria criteria, Pageable pageable) { + Page page = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(jobMapper::toDto).getContent(),page.getTotalElements()); + } + + @Override + public List queryAll(JobQueryCriteria criteria) { + List list = jobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + return jobMapper.toDto(list); + } + + @Override + @Cacheable(key = "'id:' + #p0") + public JobDto findById(Long id) { + Job job = jobRepository.findById(id).orElseGet(Job::new); + ValidationUtil.isNull(job.getId(),"Job","id",id); + return jobMapper.toDto(job); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Job resources) { + Job job = jobRepository.findByName(resources.getName()); + if(job != null){ + throw new BaseException("name",resources.getName()); + } + jobRepository.save(resources); + } + + @Override + @CacheEvict(key = "'id:' + #p0.id") + @Transactional(rollbackFor = Exception.class) + public void update(Job resources) { + Job job = jobRepository.findById(resources.getId()).orElseGet(Job::new); + Job old = jobRepository.findByName(resources.getName()); + if(old != null && !old.getId().equals(resources.getId())){ + throw new BaseException("name",resources.getName()); + } + ValidationUtil.isNull( job.getId(),"Job","id",resources.getId()); + resources.setId(job.getId()); + jobRepository.save(resources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set ids) { + jobRepository.deleteAllByIdIn(ids); + // 删除缓存 + redisUtils.delByKeys(CacheKey.JOB_ID, ids); + } + + @Override + public void download(List jobDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (JobDto jobDTO : jobDtos) { + Map map = new LinkedHashMap<>(); + map.put("岗位名称", jobDTO.getName()); + map.put("岗位状态", jobDTO.getEnabled() ? "启用" : "停用"); + map.put("创建日期", jobDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + @Override + public void verification(Set ids) { + if(userRepository.countByJobs(ids) > 0){ + throw new BaseException("所选的岗位中存在用户关联,请解除关联再试!"); + } + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/service/impl/MenuServiceImpl.java b/system/src/main/java/com/storeroom/modules/system/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..9d5233b --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/system/service/impl/MenuServiceImpl.java @@ -0,0 +1,339 @@ +package com.storeroom.modules.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.storeroom.exception.BaseException; +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.domain.vo.MenuMetaVo; +import com.storeroom.modules.system.domain.vo.MenuVo; +import com.storeroom.modules.system.repository.MenuRepository; +import com.storeroom.modules.system.repository.UserRepository; +import com.storeroom.modules.system.service.MenuService; +import com.storeroom.modules.system.service.RoleService; +import com.storeroom.modules.system.service.dto.MenuDto; +import com.storeroom.modules.system.service.dto.MenuQueryCriteria; +import com.storeroom.modules.system.service.dto.RoleSmallDto; +import com.storeroom.modules.system.service.mapstruct.MenuMapper; +import com.storeroom.utils.*; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "menu") +public class MenuServiceImpl implements MenuService { + + private final MenuRepository menuRepository; + private final UserRepository userRepository; + private final MenuMapper menuMapper; + private final RoleService roleService; + private final RedisUtils redisUtils; + + @Override + public List queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception { + Sort sort = Sort.by(Sort.Direction.ASC, "menuSort"); + if(Boolean.TRUE.equals(isQuery)){ + criteria.setPidIsNull(true); + List fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>()); + for (Field field : fields) { + //设置对象的访问权限,保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if("pidIsNull".equals(field.getName())){ + continue; + } + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + return menuMapper.toDto(menuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort)); + } + + @Override + @Cacheable(key = "'id:' + #p0") + public MenuDto findById(long id) { + Menu menu = menuRepository.findById(id).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Menu","id",id); + return menuMapper.toDto(menu); + } + + /** + * 用户角色改变时需清理缓存 + * @param currentUserId / + * @return / + */ + @Override + @Cacheable(key = "'user:' + #p0") + public List findByUser(Long currentUserId) { + List roles = roleService.findByUsersId(currentUserId); + Set roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet()); + LinkedHashSet menus = menuRepository.findByRoleIdsAndTypeNot(roleIds, 2); + return menus.stream().map(menuMapper::toDto).collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void create(Menu resources) { + if(menuRepository.findByTitle(resources.getTitle()) != null){ + throw new BaseException("title",resources.getTitle()); + } + if(StringUtils.isNotBlank(resources.getComponentName())){ + if(menuRepository.findByComponentName(resources.getComponentName()) != null){ + throw new BaseException("componentName",resources.getComponentName()); + } + } + if(resources.getPid().equals(0L)){ + resources.setPid(null); + } + if(resources.getIFrame()){ + String http = "http://", https = "https://"; + if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) { + throw new BaseException("外链必须以http://或者https://开头"); + } + } + menuRepository.save(resources); + // 计算子节点数目 + resources.setSubCount(0); + // 更新父节点菜单数目 + updateSubCnt(resources.getPid()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(Menu resources) { + if(resources.getId().equals(resources.getPid())) { + throw new BaseException("上级不能为自己"); + } + Menu menu = menuRepository.findById(resources.getId()).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Permission","id",resources.getId()); + + if(resources.getIFrame()){ + String http = "http://", https = "https://"; + if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) { + throw new BaseException("外链必须以http://或者https://开头"); + } + } + Menu menu1 = menuRepository.findByTitle(resources.getTitle()); + + if(menu1 != null && !menu1.getId().equals(menu.getId())){ + throw new BaseException("title",resources.getTitle()); + } + + if(resources.getPid().equals(0L)){ + resources.setPid(null); + } + + // 记录的父节点ID + Long oldPid = menu.getPid(); + Long newPid = resources.getPid(); + + if(StringUtils.isNotBlank(resources.getComponentName())){ + menu1 = menuRepository.findByComponentName(resources.getComponentName()); + if(menu1 != null && !menu1.getId().equals(menu.getId())){ + throw new BaseException("componentName",resources.getComponentName()); + } + } + menu.setTitle(resources.getTitle()); + menu.setComponent(resources.getComponent()); + menu.setPath(resources.getPath()); + menu.setIcon(resources.getIcon()); + menu.setIFrame(resources.getIFrame()); + menu.setPid(resources.getPid()); + menu.setMenuSort(resources.getMenuSort()); + menu.setCache(resources.getCache()); + menu.setHidden(resources.getHidden()); + menu.setComponentName(resources.getComponentName()); + menu.setPermission(resources.getPermission()); + menu.setType(resources.getType()); + menuRepository.save(menu); + // 计算父级菜单节点数目 + updateSubCnt(oldPid); + updateSubCnt(newPid); + // 清理缓存 + delCaches(resources.getId()); + } + + @Override + public Set getChildMenus(List menuList, Set menuSet) { + for (Menu menu : menuList) { + menuSet.add(menu); + List menus = menuRepository.findByPid(menu.getId()); + if(menus!=null && menus.size()!=0){ + getChildMenus(menus, menuSet); + } + } + return menuSet; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Set menuSet) { + for (Menu menu : menuSet) { + // 清理缓存 + delCaches(menu.getId()); + roleService.untiedMenu(menu.getId()); + menuRepository.deleteById(menu.getId()); + updateSubCnt(menu.getPid()); + } + } + + @Override + public List getMenus(Long pid) { + List menus; + if(pid != null && !pid.equals(0L)){ + menus = menuRepository.findByPid(pid); + } else { + menus = menuRepository.findByPidIsNull(); + } + return menuMapper.toDto(menus); + } + + @Override + public List getSuperior(MenuDto menuDto, List menus) { + if(menuDto.getPid() == null){ + menus.addAll(menuRepository.findByPidIsNull()); + return menuMapper.toDto(menus); + } + menus.addAll(menuRepository.findByPid(menuDto.getPid())); + return getSuperior(findById(menuDto.getPid()), menus); + } + + @Override + public List buildTree(List menuDtos) { + List trees = new ArrayList<>(); + Set ids = new HashSet<>(); + for (MenuDto menuDTO : menuDtos) { + if (menuDTO.getPid() == null) { + trees.add(menuDTO); + } + for (MenuDto it : menuDtos) { + if (menuDTO.getId().equals(it.getPid())) { + if (menuDTO.getChildren() == null) { + menuDTO.setChildren(new ArrayList<>()); + } + menuDTO.getChildren().add(it); + ids.add(it.getId()); + } + } + } + if(trees.size() == 0){ + trees = menuDtos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList()); + } + return trees; + } + + @Override + public List buildMenus(List menuDtos) { + List list = new LinkedList<>(); + menuDtos.forEach(menuDTO -> { + if (menuDTO!=null){ + List menuDtoList = menuDTO.getChildren(); + MenuVo menuVo = new MenuVo(); + menuVo.setName(ObjectUtil.isNotEmpty(menuDTO.getComponentName()) ? menuDTO.getComponentName() : menuDTO.getTitle()); + // 一级目录需要加斜杠,不然会报警告 + menuVo.setPath(menuDTO.getPid() == null ? "/" + menuDTO.getPath() :menuDTO.getPath()); + menuVo.setHidden(menuDTO.getHidden()); + // 如果不是外链 + if(!menuDTO.getIFrame()){ + if(menuDTO.getPid() == null){ + menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"Layout":menuDTO.getComponent()); + // 如果不是一级菜单,并且菜单类型为目录,则代表是多级菜单 + }else if(menuDTO.getType() == 0){ + menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"ParentView":menuDTO.getComponent()); + }else if(StringUtils.isNoneBlank(menuDTO.getComponent())){ + menuVo.setComponent(menuDTO.getComponent()); + } + } + menuVo.setMeta(new MenuMetaVo(menuDTO.getTitle(),menuDTO.getIcon(),!menuDTO.getCache())); + if(CollectionUtil.isNotEmpty(menuDtoList)){ + menuVo.setAlwaysShow(true); + menuVo.setRedirect("noredirect"); + menuVo.setChildren(buildMenus(menuDtoList)); + // 处理是一级菜单并且没有子菜单的情况 + } else if(menuDTO.getPid() == null){ + MenuVo menuVo1 = new MenuVo(); + menuVo1.setMeta(menuVo.getMeta()); + // 非外链 + if(!menuDTO.getIFrame()){ + menuVo1.setPath("index"); + menuVo1.setName(menuVo.getName()); + menuVo1.setComponent(menuVo.getComponent()); + } else { + menuVo1.setPath(menuDTO.getPath()); + } + menuVo.setName(null); + menuVo.setMeta(null); + menuVo.setComponent("Layout"); + List list1 = new ArrayList<>(); + list1.add(menuVo1); + menuVo.setChildren(list1); + } + list.add(menuVo); + } + } + ); + return list; + } + + @Override + public Menu findOne(Long id) { + Menu menu = menuRepository.findById(id).orElseGet(Menu::new); + ValidationUtil.isNull(menu.getId(),"Menu","id",id); + return menu; + } + + @Override + public void download(List menuDtos, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (MenuDto menuDTO : menuDtos) { + Map map = new LinkedHashMap<>(); + map.put("菜单标题", menuDTO.getTitle()); + map.put("菜单类型", menuDTO.getType() == null ? "目录" : menuDTO.getType() == 1 ? "菜单" : "按钮"); + map.put("权限标识", menuDTO.getPermission()); + map.put("外链菜单", menuDTO.getIFrame() ? "是" : "否"); + map.put("菜单可见", menuDTO.getHidden() ? "否" : "是"); + map.put("是否缓存", menuDTO.getCache() ? "是" : "否"); + map.put("创建日期", menuDTO.getCreateTime()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } + + private void updateSubCnt(Long menuId){ + if(menuId != null){ + int count = menuRepository.countByPid(menuId); + menuRepository.updateSubCntById(count, menuId); + } + } + + /** + * 清理缓存 + * @param id 菜单ID + */ + public void delCaches(Long id){ + List users = userRepository.findByMenuId(id); + redisUtils.del(CacheKey.MENU_ID + id); + redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet())); + // 清除 Role 缓存 + List roles = roleService.findInMenuId(new ArrayList(){{ + add(id); + }}); + redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet())); + } + +} diff --git a/system/src/main/resources/application-dev.yml b/system/src/main/resources/application-dev.yml index 55ac3f8..7e96b49 100644 --- a/system/src/main/resources/application-dev.yml +++ b/system/src/main/resources/application-dev.yml @@ -45,3 +45,71 @@ spring: wall: config: multi-statement-allow: true + + + +# 登录相关配置 +login: + # 登录缓存 + cache-enable: true + # 是否限制单用户登录 + single-login: false + # 验证码 + login-code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + + +#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 + + +#是否开启 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 \ No newline at end of file diff --git a/system/src/main/resources/application.yml b/system/src/main/resources/application.yml index c992a16..9a32865 100644 --- a/system/src/main/resources/application.yml +++ b/system/src/main/resources/application.yml @@ -43,23 +43,7 @@ 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 @@ -72,21 +56,18 @@ swagger: 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== + + +# 内存用户缓存配置 +user-cache: + # 最小回收数(当缓存数量达到此值时进行回收) + min-evictable-size: 512 + # 最小回收间隔 + min-evictable-interval: 1800000 + # 最小存活时间 (ms) + min-idle-time: 3600000 \ No newline at end of file diff --git a/system/src/main/resources/banner.txt b/system/src/main/resources/banner.txt new file mode 100644 index 0000000..38ce5a6 --- /dev/null +++ b/system/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + ██ ██ ██ ██ ██ ██ ████████ ██ ███████ +░░██ ██ ░░██ ██ ░██ ██ ██░░░░░░ ░██ ░██░░░░██ + ░░████ ░░██ ██ ░██ ██ ░██ ██████ ██████ ██████ █████ ░██ ░██ ██████ ██████ ██████████ + ░░██ ░░███ ░████ ░█████████░░░██░ ██░░░░██░░██░░█ ██░░░██ ░███████ ██░░░░██ ██░░░░██░░██░░██░░██ + ░██ ██░██ ░██░██ ░░░░░░░░██ ░██ ░██ ░██ ░██ ░ ░███████ ░██░░░██ ░██ ░██░██ ░██ ░██ ░██ ░██ + ░██ ██ ░░██ ░██░░██ ░██ ░██ ░██ ░██ ░██ ░██░░░░ ░██ ░░██ ░██ ░██░██ ░██ ░██ ░██ ░██ + ░██ ██ ░░██░██ ░░██ ████████ ░░██ ░░██████ ░███ ░░██████ ░██ ░░██░░██████ ░░██████ ███ ░██ ░██ + ░░ ░░ ░░ ░░ ░░ ░░░░░░░░ ░░ ░░░░░░ ░░░ ░░░░░░ ░░ ░░ ░░░░░░ ░░░░░░ ░░░ ░░ ░░ diff --git a/system/src/main/resources/generator.properties b/system/src/main/resources/generator.properties new file mode 100644 index 0000000..2ed9370 --- /dev/null +++ b/system/src/main/resources/generator.properties @@ -0,0 +1,27 @@ +#数据库类型转Java类型 +tinyint=Integer +smallint=Integer +mediumint=Integer +int=Integer +integer=Integer + +bigint=Long + +float=Float + +double=Double + +decimal=BigDecimal + +bit=Boolean + +char=String +varchar=String +tinytext=String +text=String +mediumtext=String +longtext=String + +date=Timestamp +datetime=Timestamp +timestamp=Timestamp \ No newline at end of file diff --git a/system/src/main/resources/log4jdbc.log4j2.properties b/system/src/main/resources/log4jdbc.log4j2.properties new file mode 100644 index 0000000..302525f --- /dev/null +++ b/system/src/main/resources/log4jdbc.log4j2.properties @@ -0,0 +1,4 @@ +# If you use SLF4J. First, you need to tell log4jdbc-log4j2 that you want to use the SLF4J logger +log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator +log4jdbc.auto.load.popular.drivers=false +log4jdbc.drivers=com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/system/src/main/resources/logback.xml b/system/src/main/resources/logback.xml new file mode 100644 index 0000000..6f35b02 --- /dev/null +++ b/system/src/main/resources/logback.xml @@ -0,0 +1,45 @@ + + + yxk-storeroom + + + + + + + ${log.pattern} + ${log.charset} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file