mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	集成 Spring Security 组件,重构后,整体逻辑更加清晰
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "local": { |   "local": { | ||||||
|     "baseUrl": "http://127.0.0.1:8080/api", |     "baseUrl": "http://127.0.0.1:8080/api", | ||||||
|     "token": "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MDk2ODE2MzEsInN1YiI6ImE3ZGE1MWE2YWUyYTQxOWRhNmExYTlkYmJiMTVmZjc4In0.RXG7alSz64lE9oPSgbnYT_KsX7kvoHVhF5oHxXHztr1KjsttOqOppSmHGBYFI7Y75bsjEBSxSqbGsS1O1S2b1w" |     "token": "yudaoyuanma1" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
| package com.ruoyi.framework.config; | package cn.iocoder.dashboard.framework.security.config; | ||||||
| 
 | 
 | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import cn.iocoder.dashboard.framework.security.core.filter.JwtAuthenticationTokenFilter; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.handler.AuthenticationEntryPointImpl; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.handler.LogoutSuccessHandlerImpl; | ||||||
|  | import cn.iocoder.dashboard.framework.web.config.WebProperties; | ||||||
|  | import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.http.HttpMethod; | import org.springframework.http.HttpMethod; | ||||||
| import org.springframework.security.authentication.AuthenticationManager; | import org.springframework.security.authentication.AuthenticationManager; | ||||||
| @@ -11,12 +15,14 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur | |||||||
| import org.springframework.security.config.http.SessionCreationPolicy; | import org.springframework.security.config.http.SessionCreationPolicy; | ||||||
| import org.springframework.security.core.userdetails.UserDetailsService; | import org.springframework.security.core.userdetails.UserDetailsService; | ||||||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||||||
|  | import org.springframework.security.crypto.password.PasswordEncoder; | ||||||
|  | import org.springframework.security.web.AuthenticationEntryPoint; | ||||||
|  | import org.springframework.security.web.access.AccessDeniedHandler; | ||||||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||||||
| import org.springframework.security.web.authentication.logout.LogoutFilter; | import org.springframework.security.web.authentication.logout.LogoutFilter; | ||||||
| import org.springframework.web.filter.CorsFilter; | import org.springframework.web.filter.CorsFilter; | ||||||
| import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; | 
 | ||||||
| import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; | import javax.annotation.Resource; | ||||||
| import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * spring security配置 |  * spring security配置 | ||||||
| @@ -24,52 +30,73 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; | |||||||
|  * @author ruoyi |  * @author ruoyi | ||||||
|  */ |  */ | ||||||
| @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | ||||||
| public class SecurityConfig extends WebSecurityConfigurerAdapter | @EnableConfigurationProperties(SecurityProperties.class) | ||||||
| { | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 自定义用户认证逻辑 |      * 自定义用户认证逻辑 | ||||||
|      */ |      */ | ||||||
|     @Autowired |     @Resource | ||||||
|     private UserDetailsService userDetailsService; |     private UserDetailsService userDetailsService; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 认证失败处理类 |      * 认证失败处理类 | ||||||
|      */ |      */ | ||||||
|     @Autowired |     @Resource | ||||||
|     private AuthenticationEntryPointImpl unauthorizedHandler; |     private AuthenticationEntryPoint unauthorizedHandler; | ||||||
| 
 |     /** | ||||||
|  |      * 权限不够处理器 | ||||||
|  |      */ | ||||||
|  |     @Resource | ||||||
|  |     private AccessDeniedHandler accessDeniedHandler; | ||||||
|     /** |     /** | ||||||
|      * 退出处理类 |      * 退出处理类 | ||||||
|      */ |      */ | ||||||
|     @Autowired |     @Resource | ||||||
|     private LogoutSuccessHandlerImpl logoutSuccessHandler; |     private LogoutSuccessHandlerImpl logoutSuccessHandler; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * token认证过滤器 |      * Token 认证过滤器 | ||||||
|      */ |      */ | ||||||
|     @Autowired |     @Resource | ||||||
|     private JwtAuthenticationTokenFilter authenticationTokenFilter; |     private JwtAuthenticationTokenFilter authenticationTokenFilter; | ||||||
| 
 | 
 | ||||||
|     /** |     @Resource | ||||||
|      * 跨域过滤器 |     private WebProperties webProperties; | ||||||
|      */ |  | ||||||
|     @Autowired |  | ||||||
|     private CorsFilter corsFilter; |  | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 解决 无法直接注入 AuthenticationManager |      * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入 | ||||||
|      * |      * 通过覆写父类的该方法,添加 @Bean 注解,解决该问题 | ||||||
|      * @return |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |      */ | ||||||
|     @Bean |     @Bean | ||||||
|     @Override |     @Override | ||||||
|     public AuthenticationManager authenticationManagerBean() throws Exception |     public AuthenticationManager authenticationManagerBean() throws Exception { | ||||||
|     { |  | ||||||
|         return super.authenticationManagerBean(); |         return super.authenticationManagerBean(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |      * Spring Security 加密器 | ||||||
|  |      * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器 | ||||||
|  |      * | ||||||
|  |      * @see <a href="http://stackabuse.com/password-encoding-with-spring-security/">Password Encoding with Spring Security</a> | ||||||
|  |      */ | ||||||
|  |     @Bean | ||||||
|  |     public PasswordEncoder passwordEncoder() { | ||||||
|  |         return new BCryptPasswordEncoder(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 身份认证接口 | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void configure(AuthenticationManagerBuilder auth) throws Exception { | ||||||
|  |         auth.userDetailsService(userDetailsService) | ||||||
|  |                 .passwordEncoder(passwordEncoder()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 配置 URL 的安全配置 | ||||||
|  |      * | ||||||
|      * anyRequest          |   匹配所有请求路径 |      * anyRequest          |   匹配所有请求路径 | ||||||
|      * access              |   SpringEl表达式结果为true时可以访问 |      * access              |   SpringEl表达式结果为true时可以访问 | ||||||
|      * anonymous           |   匿名可以访问 |      * anonymous           |   匿名可以访问 | ||||||
| @@ -85,26 +112,23 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter | |||||||
|      * authenticated       |   用户登录后可访问 |      * authenticated       |   用户登录后可访问 | ||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     protected void configure(HttpSecurity httpSecurity) throws Exception |     protected void configure(HttpSecurity httpSecurity) throws Exception { | ||||||
|     { |  | ||||||
|         httpSecurity |         httpSecurity | ||||||
|                 // CSRF禁用,因为不使用session |                 // CSRF 禁用,因为不使用 Session | ||||||
|                 .csrf().disable() |                 .csrf().disable() | ||||||
|                 // 认证失败处理类 |                 // 基于 token 机制,所以不需要 Session | ||||||
|                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() |  | ||||||
|                 // 基于token,所以不需要session |  | ||||||
|                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() |                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() | ||||||
|                 // 过滤请求 |                 // 一堆自定义的 Spring Security 处理器 | ||||||
|  |                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) | ||||||
|  |                     .accessDeniedHandler(accessDeniedHandler).and() | ||||||
|  |                 // TODO 过滤请求 | ||||||
|                 .authorizeRequests() |                 .authorizeRequests() | ||||||
|                 // 对于登录login 验证码captchaImage 允许匿名访问 |                 // 登陆的接口,可匿名访问 | ||||||
|                 .antMatchers("/login", "/captchaImage").anonymous() |                 .antMatchers(webProperties.getApiPrefix() + "/login").anonymous() | ||||||
|                 .antMatchers( |                 // 通用的接口,可匿名访问 | ||||||
|                         HttpMethod.GET, |                 .antMatchers( webProperties.getApiPrefix() + "/captcha/**").anonymous() | ||||||
|                         "/*.html", |                 // TODO | ||||||
|                         "/**/*.html", |                 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | ||||||
|                         "/**/*.css", |  | ||||||
|                         "/**/*.js" |  | ||||||
|                 ).permitAll() |  | ||||||
|                 .antMatchers("/profile/**").anonymous() |                 .antMatchers("/profile/**").anonymous() | ||||||
|                 .antMatchers("/common/download**").anonymous() |                 .antMatchers("/common/download**").anonymous() | ||||||
|                 .antMatchers("/common/download/resource**").anonymous() |                 .antMatchers("/common/download/resource**").anonymous() | ||||||
| @@ -112,35 +136,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter | |||||||
|                 .antMatchers("/swagger-resources/**").anonymous() |                 .antMatchers("/swagger-resources/**").anonymous() | ||||||
|                 .antMatchers("/webjars/**").anonymous() |                 .antMatchers("/webjars/**").anonymous() | ||||||
|                 .antMatchers("/*/api-docs").anonymous() |                 .antMatchers("/*/api-docs").anonymous() | ||||||
|                 .antMatchers("/druid/**").anonymous() |                 .antMatchers("/druid/**").hasAnyAuthority("druid") // TODO 芋艿,未来需要在拓展下 | ||||||
|                 // 除上面外的所有请求全部需要鉴权认证 |                 // 除上面外的所有请求全部需要鉴权认证 | ||||||
|                 .anyRequest().authenticated() |                 .anyRequest().authenticated() | ||||||
|                 .and() |                 .and() | ||||||
|                 .headers().frameOptions().disable(); |                 .headers().frameOptions().disable(); | ||||||
|         httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); |         httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); | ||||||
|         // 添加JWT filter |         // 添加 JWT Filter | ||||||
|         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); |         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); | ||||||
|         // 添加CORS filter |  | ||||||
|         httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); |  | ||||||
|         httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * 强散列哈希加密实现 |  | ||||||
|      */ |  | ||||||
|     @Bean |  | ||||||
|     public BCryptPasswordEncoder bCryptPasswordEncoder() |  | ||||||
|     { |  | ||||||
|         return new BCryptPasswordEncoder(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 身份认证接口 |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     protected void configure(AuthenticationManagerBuilder auth) throws Exception |  | ||||||
|     { |  | ||||||
|         auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.config; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  | import org.springframework.boot.context.properties.ConfigurationProperties; | ||||||
|  | import org.springframework.validation.annotation.Validated; | ||||||
|  |  | ||||||
|  | import javax.validation.Valid; | ||||||
|  | import javax.validation.constraints.NotEmpty; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  | import java.time.Duration; | ||||||
|  |  | ||||||
|  | @ConfigurationProperties(prefix = "yudao.security") | ||||||
|  | @Validated | ||||||
|  | @Data | ||||||
|  | public class SecurityProperties { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * HTTP 请求时,访问令牌的请求 Header | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "Token Header 不能为空") | ||||||
|  |     private String tokenHeader; | ||||||
|  |     /** | ||||||
|  |      * Token 过期时间 | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "Token 过期时间不能为空") | ||||||
|  |     private Duration tokenTimeout; | ||||||
|  |     /** | ||||||
|  |      * Token 秘钥 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "Token 秘钥不能为空") | ||||||
|  |     private String tokenSecret; | ||||||
|  |     /** | ||||||
|  |      * Session 过期时间 | ||||||
|  |      * | ||||||
|  |      * 当 User 用户超过当前时间未操作,则 Session 会过期 | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "Session 过期时间不能为空") | ||||||
|  |     private Duration sessionTimeout; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * mock 模式的开关 | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "mock 模式的开关不能为空") | ||||||
|  |     private Boolean mockEnable; | ||||||
|  |     /** | ||||||
|  |      * mock 模式的秘钥 | ||||||
|  |      * 一定要配置秘钥,保证安全性 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "mock 模式的秘钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。 | ||||||
|  |     private String mockSecret = "yudaoyuanma"; | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,90 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.modules.system.enums.user.UserStatus; | ||||||
|  | import com.alibaba.fastjson.annotation.JSONField; | ||||||
|  | import lombok.Data; | ||||||
|  | import org.springframework.data.annotation.Transient; | ||||||
|  | import org.springframework.security.core.GrantedAuthority; | ||||||
|  | import org.springframework.security.core.userdetails.UserDetails; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Date; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 登陆用户信息 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | public class LoginUser implements UserDetails { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户编号 | ||||||
|  |      */ | ||||||
|  |     private Long userId; | ||||||
|  |     /** | ||||||
|  |      * 角色编号数组 | ||||||
|  |      */ | ||||||
|  |     private Set<Integer> roleIds; | ||||||
|  |     /** | ||||||
|  |      * 最后更新时间 | ||||||
|  |      */ | ||||||
|  |     private Date updateTime; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户名 | ||||||
|  |      */ | ||||||
|  |     private String username; | ||||||
|  |     /** | ||||||
|  |      * 密码 | ||||||
|  |      */ | ||||||
|  |     private String password; | ||||||
|  |     /** | ||||||
|  |      * 状态 | ||||||
|  |      */ | ||||||
|  |     private String status; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public String getPassword() { | ||||||
|  |         return password; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public String getUsername() { | ||||||
|  |         return username; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return UserStatus.OK.getCode().equals(status); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public Collection<? extends GrantedAuthority> getAuthorities() { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public boolean isAccountNonExpired() { | ||||||
|  |         return true; // 返回 true,不依赖 Spring Security 判断 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public boolean isAccountNonLocked() { | ||||||
|  |         return true; // 返回 true,不依赖 Spring Security 判断 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @JSONField(serialize = false) // 避免序列化 | ||||||
|  |     public boolean isCredentialsNonExpired() { | ||||||
|  |         return true;  // 返回 true,不依赖 Spring Security 判断 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,89 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.filter; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.hutool.extra.servlet.ServletUtil; | ||||||
|  | import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||||
|  | import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||||
|  | import cn.iocoder.dashboard.framework.web.core.handler.GlobalExceptionHandler; | ||||||
|  | import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; | ||||||
|  | import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||||
|  | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||
|  | import org.springframework.security.core.context.SecurityContextHolder; | ||||||
|  | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | import org.springframework.web.filter.OncePerRequestFilter; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  | import javax.servlet.FilterChain; | ||||||
|  | import javax.servlet.ServletException; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * JWT 过滤器,验证 token 的有效性 | ||||||
|  |  * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SecurityProperties securityProperties; | ||||||
|  |     @Resource | ||||||
|  |     private SysAuthService authService; | ||||||
|  |     @Resource | ||||||
|  |     private GlobalExceptionHandler globalExceptionHandler; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @SuppressWarnings("NullableProblems") | ||||||
|  |     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||||||
|  |             throws ServletException, IOException { | ||||||
|  |         String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); | ||||||
|  |         if (StrUtil.isNotEmpty(token)) { | ||||||
|  |             try { | ||||||
|  |                 // 验证 token 有效性 | ||||||
|  |                 LoginUser loginUser = authService.verifyTokenAndRefresh(token); | ||||||
|  |                 // 模拟 Login 功能,方便日常开发调试 | ||||||
|  |                 if (loginUser == null) { | ||||||
|  |                     loginUser = this.mockLoginUser(token); | ||||||
|  |                 } | ||||||
|  |                 // 设置当前用户 | ||||||
|  |                 if (loginUser != null) { | ||||||
|  |                     SecurityUtils.setLoginUser(loginUser, request); | ||||||
|  |                 } | ||||||
|  |             } catch (Throwable ex) { | ||||||
|  |                 CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex); | ||||||
|  |                 ServletUtils.writeJSON(response, result); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 继续过滤链 | ||||||
|  |         chain.doFilter(request, response); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 模拟登陆用户,方便日常开发调试 | ||||||
|  |      * | ||||||
|  |      * 注意,在线上环境下,一定要关闭该功能!!! | ||||||
|  |      * | ||||||
|  |      * @param token 模拟的 token,格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号 | ||||||
|  |      * @return 模拟的 LoginUser | ||||||
|  |      */ | ||||||
|  |     private LoginUser mockLoginUser(String token) { | ||||||
|  |         if (!securityProperties.getMockEnable()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         // 必须以 mockSecret 开头 | ||||||
|  |         if (!token.startsWith(securityProperties.getMockSecret())) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); | ||||||
|  |         return authService.mockLogin(userId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.handler; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; | ||||||
|  | import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||||
|  | import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.security.access.AccessDeniedException; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.security.web.access.AccessDeniedHandler; | ||||||
|  | import org.springframework.security.web.access.ExceptionTranslationFilter; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import javax.servlet.FilterChain; | ||||||
|  | import javax.servlet.ServletException; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 | ||||||
|  |  * | ||||||
|  |  * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | @Slf4j | ||||||
|  | @SuppressWarnings("JavadocReference") | ||||||
|  | public class AccessDeniedHandlerImpl implements AccessDeniedHandler { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) | ||||||
|  |             throws IOException, ServletException { | ||||||
|  |         // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 | ||||||
|  |         log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), | ||||||
|  |                 SecurityUtils.getLoginUser().getUserId(), e); | ||||||
|  |         // 返回 403 | ||||||
|  |         ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.handler; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; | ||||||
|  | import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||||
|  | import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.security.web.AuthenticationEntryPoint; | ||||||
|  | import org.springframework.security.web.access.ExceptionTranslationFilter; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import javax.servlet.FilterChain; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 | ||||||
|  |  * | ||||||
|  |  * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | @Slf4j | ||||||
|  | @SuppressWarnings("JavadocReference") // 忽略文档引用报错 | ||||||
|  | public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { | ||||||
|  |         log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); | ||||||
|  |         // 返回 401 | ||||||
|  |         ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.handler; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||||
|  | import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||||
|  | import org.springframework.security.core.Authentication; | ||||||
|  | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 自定义退出处理器 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SecurityProperties securityProperties; | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SecurityFrameworkService securityFrameworkService; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { | ||||||
|  |         // 执行退出 | ||||||
|  |         String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); | ||||||
|  |         if (StrUtil.isNotBlank(token)) { | ||||||
|  |             securityFrameworkService.logout(token); | ||||||
|  |         } | ||||||
|  |         // 返回成功 | ||||||
|  |         ServletUtils.writeJSON(response, null); | ||||||
|  | //        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.OK.value(), "退出成功"))); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.service; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||||
|  | import org.springframework.security.core.userdetails.UserDetailsService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Security 框架 Service 接口,定义 security 组件需要的功能 | ||||||
|  |  */ | ||||||
|  | public interface SecurityFrameworkService extends UserDetailsService { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 基于 token 退出登录 | ||||||
|  |      * | ||||||
|  |      * @param token token | ||||||
|  |      */ | ||||||
|  |     void logout(String token); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 校验 token 的有效性,并获取用户信息 | ||||||
|  |      * 通过后,刷新 token 的过期时间 | ||||||
|  |      * | ||||||
|  |      * @param token token | ||||||
|  |      * @return 用户信息 | ||||||
|  |      */ | ||||||
|  |     LoginUser verifyTokenAndRefresh(String token); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 模拟指定用户编号的 LoginUser | ||||||
|  |      * | ||||||
|  |      * @param userId 用户编号 | ||||||
|  |      * @return 登录用户 | ||||||
|  |      */ | ||||||
|  |     LoginUser mockLogin(Long userId); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | package cn.iocoder.dashboard.framework.security.core.util; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||||
|  | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||
|  | import org.springframework.security.core.context.SecurityContextHolder; | ||||||
|  | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||||||
|  | import org.springframework.util.StringUtils; | ||||||
|  |  | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 安全服务工具类 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | public class SecurityUtils { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 从请求中,获得认证 Token | ||||||
|  |      * | ||||||
|  |      * @param request 请求 | ||||||
|  |      * @param header 认证 Token 对应的 Header 名字 | ||||||
|  |      * @return 认证 Token | ||||||
|  |      */ | ||||||
|  |     public static String obtainAuthorization(HttpServletRequest request, String header) { | ||||||
|  |         String authorization = request.getHeader(header); | ||||||
|  |         if (!StringUtils.hasText(authorization)) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         int index = authorization.indexOf("Bearer "); | ||||||
|  |         if (index == -1) { // 未找到 | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return authorization.substring(index + 7).trim(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取当前用户 | ||||||
|  |      */ | ||||||
|  |     public static LoginUser getLoginUser() { | ||||||
|  |         return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置当前用户 | ||||||
|  |      * | ||||||
|  |      * @param loginUser 登陆用户 | ||||||
|  |      * @param request 请求 | ||||||
|  |      */ | ||||||
|  |     public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | ||||||
|  |         // 创建 UsernamePasswordAuthenticationToken 对象 | ||||||
|  |         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | ||||||
|  |                 loginUser, null, null); | ||||||
|  |         authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||||||
|  |         // 设置到上下文 | ||||||
|  |         SecurityContextHolder.getContext().setAuthentication(authenticationToken); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | /** | ||||||
|  |  * 基于 Spring Security 框架 | ||||||
|  |  * 实现安全认证功能 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | package cn.iocoder.dashboard.framework.security; | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | * 芋道 Spring Security 入门:<http://www.iocoder.cn/Spring-Boot/Spring-Security/?dashboard> | ||||||
|  | * Spring Security 基本概念:<http://www.iocoder.cn/Fight/Spring-Security-4-1-0-Basic-concept-description/?dashboard> | ||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.convert.auth; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||||
|  | import org.mapstruct.Mapper; | ||||||
|  | import org.mapstruct.Mapping; | ||||||
|  | import org.mapstruct.factory.Mappers; | ||||||
|  |  | ||||||
|  | @Mapper | ||||||
|  | public interface SysAuthConvert { | ||||||
|  |  | ||||||
|  |     SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class); | ||||||
|  |  | ||||||
|  |     @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略 | ||||||
|  |     LoginUser convert(SysUserDO bean); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.dal.mysql.dao; | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.dal.mysql.dao.user; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||||
|  | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
|  | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||||
|  | import org.apache.ibatis.annotations.Mapper; | ||||||
|  |  | ||||||
|  | @Mapper | ||||||
|  | public interface SysUserMapper extends BaseMapper<SysUserDO> { | ||||||
|  |  | ||||||
|  |     default SysUserDO selectByUsername(String username) { | ||||||
|  |         return selectOne(new QueryWrapper<SysUserDO>().eq("username", username)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,154 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user; | ||||||
|  |  | ||||||
|  | import java.util.Date; | ||||||
|  | import java.util.List; | ||||||
|  | import javax.validation.constraints.Email; | ||||||
|  | import javax.validation.constraints.NotBlank; | ||||||
|  | import javax.validation.constraints.Size; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.excel.Excel; | ||||||
|  | import cn.iocoder.dashboard.framework.excel.Excels; | ||||||
|  | import cn.iocoder.dashboard.framework.mybatis.core.BaseDO; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDept; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRole; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableField; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableId; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty; | ||||||
|  | import lombok.Data; | ||||||
|  | import lombok.EqualsAndHashCode; | ||||||
|  | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 用户 DO | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @TableName("sys_user") | ||||||
|  | @Data | ||||||
|  | @EqualsAndHashCode(callSuper = true) | ||||||
|  | // TODO 芋艿:数据库的字段默认值 | ||||||
|  | public class SysUserDO extends BaseDO { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户ID | ||||||
|  |      */ | ||||||
|  |     @TableId | ||||||
|  |     @Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号") | ||||||
|  |     private Long userId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 部门ID | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "部门编号", type = Excel.Type.IMPORT) | ||||||
|  |     private Long deptId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户账号 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "登录名称") | ||||||
|  |     @NotBlank(message = "用户账号不能为空") | ||||||
|  |     @Size(max = 30, message = "用户账号长度不能超过30个字符") | ||||||
|  |     private String username; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 加密后的密码 | ||||||
|  |      * | ||||||
|  |      * 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐 | ||||||
|  |      */ | ||||||
|  |     private String password; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户昵称 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "用户名称") | ||||||
|  |     @Size(max = 30, message = "用户昵称长度不能超过30个字符") | ||||||
|  |     private String nickname; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户邮箱 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "用户邮箱") | ||||||
|  |     @Email(message = "邮箱格式不正确") | ||||||
|  |     @Size(max = 50, message = "邮箱长度不能超过50个字符") | ||||||
|  |     private String email; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 手机号码 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "手机号码") | ||||||
|  |     @Size(max = 11, message = "手机号码长度不能超过11个字符") | ||||||
|  |     private String mobile; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户性别 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") | ||||||
|  |     private String sex; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户头像 | ||||||
|  |      */ | ||||||
|  |     private String avatar; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 帐号状态(0正常 1停用) | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") | ||||||
|  |     // TODO 芋艿:修改成枚举 | ||||||
|  |     private String status; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 最后登录IP | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "最后登录IP", type = Excel.Type.EXPORT) | ||||||
|  |     private String loginIp; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 最后登录时间 | ||||||
|  |      */ | ||||||
|  |     @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT) | ||||||
|  |     private Date loginDate; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     // TODO FROM 芋艿:下面的字段,需要忽略 | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 部门对象 | ||||||
|  |      */ | ||||||
|  |     @Excels({ | ||||||
|  |             @Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT), | ||||||
|  |             @Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT) | ||||||
|  |     }) | ||||||
|  |     @TableField(exist = false) | ||||||
|  |     private SysDept dept; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 角色对象 | ||||||
|  |      */ | ||||||
|  |     @TableField(exist = false) | ||||||
|  |     private List<SysRole> roles; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 角色组 | ||||||
|  |      */ | ||||||
|  |     @TableField(exist = false) | ||||||
|  |     private Long[] roleIds; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 岗位组 | ||||||
|  |      */ | ||||||
|  |     @TableField(exist = false) | ||||||
|  |     private Long[] postIds; | ||||||
|  |  | ||||||
|  |     // TODO 芋艿:后续清理掉 | ||||||
|  |     public boolean isAdmin() { | ||||||
|  |         return isAdmin(this.userId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static boolean isAdmin(Long userId) { | ||||||
|  |         return userId != null && 1L == userId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.enums; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.common.exception.ErrorCode; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 错误码枚举类 | ||||||
|  |  * | ||||||
|  |  * system 系统,使用 1-002-000-000 段 | ||||||
|  |  */ | ||||||
|  | public interface SysErrorCodeConstants { | ||||||
|  |  | ||||||
|  |     // ========== AUTH 模块 1002000000 ========== | ||||||
|  |     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); | ||||||
|  |     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); | ||||||
|  |     ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底,位置原因 | ||||||
|  |  | ||||||
|  |     // ========== TOKEN 模块 1002001000 ========== | ||||||
|  |     ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期"); | ||||||
|  |     ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败"); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package com.ruoyi.common.enums; | package cn.iocoder.dashboard.modules.system.enums.user; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 用户状态 |  * 用户状态 | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.auth; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 认证 Service 接口 | ||||||
|  |  * | ||||||
|  |  * 提供用户的账号密码登陆、token 的校验等认证相关的功能 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | public interface SysAuthService extends SecurityFrameworkService { | ||||||
|  |  | ||||||
|  |     String login(String username, String password, String captchaUUID, String captchaCode); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.auth; | ||||||
|  |  | ||||||
|  | import io.jsonwebtoken.Claims; | ||||||
|  |  | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Token Service 接口 | ||||||
|  |  * | ||||||
|  |  * 提供访问 Token 令牌,目前基于 JWT 实现 | ||||||
|  |  */ | ||||||
|  | public interface SysTokenService { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建 Token | ||||||
|  |      * | ||||||
|  |      * @param subject 主体 | ||||||
|  |      * @return Token 字符串 | ||||||
|  |      */ | ||||||
|  |     String createToken(String subject); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 解析 Token,返回 claims 数据声明 | ||||||
|  |      * | ||||||
|  |      * @param token Token | ||||||
|  |      * @return claims | ||||||
|  |      */ | ||||||
|  |     Claims parseToken(String token); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,215 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.auth.impl; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.IdUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||||
|  | import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||||
|  | import cn.iocoder.dashboard.modules.system.enums.user.UserStatus; | ||||||
|  | import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO; | ||||||
|  | import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; | ||||||
|  | import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; | ||||||
|  | import cn.iocoder.dashboard.modules.system.service.user.SysUserService; | ||||||
|  | import cn.iocoder.dashboard.util.date.DateUtils; | ||||||
|  | import io.jsonwebtoken.Claims; | ||||||
|  | import io.jsonwebtoken.JwtException; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.security.authentication.AuthenticationManager; | ||||||
|  | import org.springframework.security.authentication.BadCredentialsException; | ||||||
|  | import org.springframework.security.authentication.DisabledException; | ||||||
|  | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||
|  | import org.springframework.security.core.Authentication; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.security.core.userdetails.UserDetails; | ||||||
|  | import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | import org.springframework.util.Assert; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Date; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; | ||||||
|  | import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Auth Service 实现类 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @Slf4j | ||||||
|  | public class SysAuthServiceImpl implements SysAuthService { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SecurityProperties securityProperties; | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SysTokenService tokenService; | ||||||
|  |     @Resource | ||||||
|  |     private AuthenticationManager authenticationManager; | ||||||
|  |     @Resource | ||||||
|  |     private SysUserService userService; | ||||||
|  |     @Resource | ||||||
|  |     private SysLoginUserRedisDAO loginUserRedisDAO; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||||||
|  |         // 获取 username 对应的 SysUserDO | ||||||
|  |         SysUserDO user = userService.getUserByUserName(username); | ||||||
|  |         if (user == null) { | ||||||
|  |             throw new UsernameNotFoundException(username); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 创建 LoginUser 对象 | ||||||
|  |         return SysAuthConvert.INSTANCE.convert(user); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public LoginUser mockLogin(Long userId) { | ||||||
|  |         // 获取用户编号对应的 SysUserDO | ||||||
|  |         SysUserDO user = userService.getUser(userId); | ||||||
|  |         if (user == null) { | ||||||
|  |             throw new UsernameNotFoundException(String.valueOf(userId)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 创建 LoginUser 对象 | ||||||
|  |         LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | ||||||
|  |         loginUser.setUpdateTime(new Date()); | ||||||
|  |         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||||
|  |         return loginUser; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String login(String username, String password, String captchaUUID, String captchaCode) { | ||||||
|  |         // 判断验证码是否正确 | ||||||
|  |         this.verifyCaptcha(captchaUUID, captchaCode); | ||||||
|  |  | ||||||
|  |         // 使用账号密码,进行登陆。 | ||||||
|  |         LoginUser loginUser = this.login0(username, password); | ||||||
|  |  | ||||||
|  |         // 缓存登陆用户到 Redis 中 | ||||||
|  |         String sessionId = IdUtil.fastSimpleUUID(); | ||||||
|  |         loginUser.setUpdateTime(new Date()); | ||||||
|  |         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||||
|  |         loginUserRedisDAO.set(sessionId, loginUser); | ||||||
|  |  | ||||||
|  |         // 创建 Token | ||||||
|  |         // 我们在返回给前端的 JWT 中,使用 sessionId 作为 subject 主体,标识当前 User 用户 | ||||||
|  |         return tokenService.createToken(sessionId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void verifyCaptcha(String captchaUUID, String captchaCode) { | ||||||
|  |         //        String verifyKey = Constants.CAPTCHA_CODE_KEY + captchaUUID; | ||||||
|  | //        String captcha = redisCache.getCacheObject(verifyKey); | ||||||
|  | //        redisCache.deleteObject(verifyKey); | ||||||
|  | //        if (captcha == null) { | ||||||
|  | //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); | ||||||
|  | //            throw new CaptchaExpireException(); | ||||||
|  | //        } | ||||||
|  | //        if (!code.equalsIgnoreCase(captcha)) { | ||||||
|  | //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); | ||||||
|  | //            throw new CaptchaException(); | ||||||
|  | //        } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private LoginUser login0(String username, String password) { | ||||||
|  |         // 用户验证 | ||||||
|  |         Authentication authentication; | ||||||
|  |         try { | ||||||
|  |             // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证 | ||||||
|  |             // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息 | ||||||
|  |             authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); | ||||||
|  |         } catch (BadCredentialsException badCredentialsException) { | ||||||
|  |             // TODO 日志优化 | ||||||
|  | //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); | ||||||
|  |             throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||||||
|  |         } catch (DisabledException disabledException) { | ||||||
|  |             // TODO 日志优化 | ||||||
|  |             throw exception(AUTH_LOGIN_USER_DISABLED); | ||||||
|  |         } catch (AuthenticationException authenticationException) { | ||||||
|  |             // TODO 日志优化 | ||||||
|  |             throw exception(AUTH_LOGIN_FAIL_UNKNOWN); | ||||||
|  |         } | ||||||
|  |         // TODO 需要优化 | ||||||
|  | //        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); | ||||||
|  |         Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); | ||||||
|  |         return (LoginUser) authentication.getPrincipal(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获得 User 拥有的角色编号数组 | ||||||
|  |      * | ||||||
|  |      * @param userId 用户编号 | ||||||
|  |      * @return 角色编号数组 | ||||||
|  |      */ | ||||||
|  |     private Set<Integer> getUserRoleIds(Long userId) { | ||||||
|  |         // TODO 芋艿:读取角色编号 | ||||||
|  |         return Collections.emptySet(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void logout(String token) { | ||||||
|  | //        AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); TODO 需要搞一搞 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public LoginUser verifyTokenAndRefresh(String token) { | ||||||
|  |         // 验证 token 的有效性 | ||||||
|  |         String sessionId = this.verifyToken(token); | ||||||
|  |         // 获得 LoginUser | ||||||
|  |         LoginUser loginUser = loginUserRedisDAO.get(sessionId); | ||||||
|  |         if (loginUser == null) { | ||||||
|  | //            throw exception(AUTH_SESSION_TIMEOUT); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         // 刷新 LoginUser 缓存 | ||||||
|  |         this.refreshLoginUserCache(sessionId, loginUser); | ||||||
|  |         return loginUser; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private String verifyToken(String token) { | ||||||
|  |         Claims claims; | ||||||
|  |         try { | ||||||
|  |             claims = tokenService.parseToken(token); | ||||||
|  |         } catch (JwtException jwtException) { | ||||||
|  |             log.warn("[verifyToken][token({}) 解析发生异常]", token); | ||||||
|  | //            throw exception(TOKEN_PARSE_FAIL); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         // token 已经过期 | ||||||
|  |         if (DateUtils.isExpired(claims.getExpiration())) { | ||||||
|  | //            throw exception(TOKEN_EXPIRED); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         // 判断 sessionId 是否存在 | ||||||
|  |         String sessionId = claims.getSubject(); | ||||||
|  |         if (StrUtil.isBlank(sessionId)) { | ||||||
|  | //            throw exception(AUTH_SESSION_ID_NOT_FOUND); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return sessionId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void refreshLoginUserCache(String sessionId, LoginUser loginUser) { | ||||||
|  |         // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 | ||||||
|  |         if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < | ||||||
|  |                 securityProperties.getSessionTimeout().toMillis() / 3) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 重新加载 SysUserDO 信息 | ||||||
|  |         SysUserDO user = userService.getUser(loginUser.getUserId()); | ||||||
|  |         if (user == null || UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||||
|  |             throw exception(TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登陆界面 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 刷新 LoginUser 缓存 | ||||||
|  |         loginUser.setUpdateTime(new Date()); | ||||||
|  |         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||||
|  |         loginUserRedisDAO.set(sessionId, loginUser); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.auth.impl; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||||
|  | import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; | ||||||
|  | import cn.iocoder.dashboard.util.date.DateUtils; | ||||||
|  | import io.jsonwebtoken.Claims; | ||||||
|  | import io.jsonwebtoken.Jwts; | ||||||
|  | import io.jsonwebtoken.SignatureAlgorithm; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Token Service 实现类 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | public class SysTokenServiceImpl implements SysTokenService { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SecurityProperties securityProperties; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String createToken(String subject) { | ||||||
|  |         return Jwts.builder() | ||||||
|  |                 .signWith(SignatureAlgorithm.HS512, securityProperties.getTokenSecret()) | ||||||
|  |                 .setExpiration(DateUtils.addTime(securityProperties.getTokenTimeout())) | ||||||
|  |                 .setSubject(subject) | ||||||
|  |                 .compact(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Claims parseToken(String token) { | ||||||
|  |         return Jwts.parser() | ||||||
|  |                 .setSigningKey(securityProperties.getTokenSecret()) | ||||||
|  |                 .parseClaimsJws(token) | ||||||
|  |                 .getBody(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void main(String[] args) { | ||||||
|  |         String secret = "abcdefghijklmnopqrstuvwxyz"; | ||||||
|  |         Map<String, Object> map = new HashMap<>(); | ||||||
|  |         map.put("key1", "value1"); | ||||||
|  |         System.out.println(Jwts.builder() | ||||||
|  |                 .signWith(SignatureAlgorithm.HS512, secret) | ||||||
|  |                 .setClaims(map) | ||||||
|  |                 .compact()); | ||||||
|  |  | ||||||
|  |         System.out.println(Jwts.parser() | ||||||
|  |                 .setSigningKey(secret) | ||||||
|  |                 .parseClaimsJws("qyJhbGciOiJIUzUxMiJ9.eyJrZXkxIjoidmFsdWUxIn0.AHWncLRBlJkqrKaoWHZmMgbqYIT7rfLs8KCp9LuC0mdNfnx1xEMm1N9bgcD-0lc5sjySqsKiWzqJ3rpoyUSh0g") | ||||||
|  |                 .getBody()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,166 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.user; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 用户 Service 接口 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | public interface SysUserService { | ||||||
|  | //    /** | ||||||
|  | //     * 根据条件分页查询用户列表 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 用户信息集合信息 | ||||||
|  | //     */ | ||||||
|  | //    public List<SysUser> selectUserList(SysUser user); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通过用户名查询用户 | ||||||
|  |      * | ||||||
|  |      * @param username 用户名 | ||||||
|  |      * @return 用户对象信息 | ||||||
|  |      */ | ||||||
|  |     SysUserDO getUserByUserName(String username); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通过用户 ID 查询用户 | ||||||
|  |      * | ||||||
|  |      * @param userId 用户ID | ||||||
|  |      * @return 用户对象信息 | ||||||
|  |      */ | ||||||
|  |     SysUserDO getUser(Long userId); | ||||||
|  |  | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 根据用户ID查询用户所属角色组 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String selectUserRoleGroup(String userName); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 根据用户ID查询用户所属岗位组 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String selectUserPostGroup(String userName); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验用户名称是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名称 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String checkUserNameUnique(String userName); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验手机号码是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String checkPhoneUnique(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验email是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String checkEmailUnique(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验用户是否允许操作 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     */ | ||||||
|  | //    public void checkUserAllowed(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 新增用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int insertUser(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int updateUser(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户状态 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int updateUserStatus(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户基本信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int updateUserProfile(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户头像 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @param avatar 头像地址 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public boolean updateUserAvatar(String userName, String avatar); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 重置用户密码 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int resetPwd(SysUser user); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 重置用户密码 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @param password 密码 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int resetUserPwd(String userName, String password); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 通过用户ID删除用户 | ||||||
|  | //     * | ||||||
|  | //     * @param userId 用户ID | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int deleteUserById(Long userId); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 批量删除用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param userIds 需要删除的用户ID | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public int deleteUserByIds(Long[] userIds); | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 导入用户数据 | ||||||
|  | //     * | ||||||
|  | //     * @param userList 用户数据列表 | ||||||
|  | //     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 | ||||||
|  | //     * @param operName 操作用户 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName); | ||||||
|  | } | ||||||
| @@ -0,0 +1,447 @@ | |||||||
|  | package cn.iocoder.dashboard.modules.system.service.user; | ||||||
|  |  | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dao.user.SysUserMapper; | ||||||
|  | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 用户 业务层处理 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @Slf4j | ||||||
|  | public class SysUserServiceImpl implements SysUserService { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SysUserMapper userMapper; | ||||||
|  |  | ||||||
|  | //    @Autowired | ||||||
|  | //    private SysUserMapper userMapper; | ||||||
|  | // | ||||||
|  | //    @Autowired | ||||||
|  | //    private SysRoleMapper roleMapper; | ||||||
|  | // | ||||||
|  | //    @Autowired | ||||||
|  | //    private SysPostMapper postMapper; | ||||||
|  | // | ||||||
|  | //    @Autowired | ||||||
|  | //    private SysUserRoleMapper userRoleMapper; | ||||||
|  | // | ||||||
|  | //    @Autowired | ||||||
|  | //    private SysUserPostMapper userPostMapper; | ||||||
|  | // | ||||||
|  | //    @Autowired | ||||||
|  | //    private ISysConfigService configService; | ||||||
|  |  | ||||||
|  | //    /** | ||||||
|  | //     * 根据条件分页查询用户列表 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 用户信息集合信息 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    @DataScope(deptAlias = "d", userAlias = "u") | ||||||
|  | //    public List<SysUser> selectUserList(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.selectUserList(user); | ||||||
|  | //    } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SysUserDO getUserByUserName(String username) { | ||||||
|  |         return userMapper.selectByUsername(username); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SysUserDO getUser(Long userId) { | ||||||
|  |         return userMapper.selectById(userId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | //    /** | ||||||
|  | //     * 通过用户ID查询用户 | ||||||
|  | //     * | ||||||
|  | //     * @param userId 用户ID | ||||||
|  | //     * @return 用户对象信息 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public SysUser selectUserById(Long userId) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.selectUserById(userId); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 查询用户所属角色组 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String selectUserRoleGroup(String userName) | ||||||
|  | //    { | ||||||
|  | //        List<SysRole> list = roleMapper.selectRolesByUserName(userName); | ||||||
|  | //        StringBuffer idsStr = new StringBuffer(); | ||||||
|  | //        for (SysRole role : list) | ||||||
|  | //        { | ||||||
|  | //            idsStr.append(role.getRoleName()).append(","); | ||||||
|  | //        } | ||||||
|  | //        if (StringUtils.isNotEmpty(idsStr.toString())) | ||||||
|  | //        { | ||||||
|  | //            return idsStr.substring(0, idsStr.length() - 1); | ||||||
|  | //        } | ||||||
|  | //        return idsStr.toString(); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 查询用户所属岗位组 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String selectUserPostGroup(String userName) | ||||||
|  | //    { | ||||||
|  | //        List<SysPost> list = postMapper.selectPostsByUserName(userName); | ||||||
|  | //        StringBuffer idsStr = new StringBuffer(); | ||||||
|  | //        for (SysPost post : list) | ||||||
|  | //        { | ||||||
|  | //            idsStr.append(post.getPostName()).append(","); | ||||||
|  | //        } | ||||||
|  | //        if (StringUtils.isNotEmpty(idsStr.toString())) | ||||||
|  | //        { | ||||||
|  | //            return idsStr.substring(0, idsStr.length() - 1); | ||||||
|  | //        } | ||||||
|  | //        return idsStr.toString(); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验用户名称是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名称 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String checkUserNameUnique(String userName) | ||||||
|  | //    { | ||||||
|  | //        int count = userMapper.checkUserNameUnique(userName); | ||||||
|  | //        if (count > 0) | ||||||
|  | //        { | ||||||
|  | //            return UserConstants.NOT_UNIQUE; | ||||||
|  | //        } | ||||||
|  | //        return UserConstants.UNIQUE; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验用户名称是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String checkPhoneUnique(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); | ||||||
|  | //        SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); | ||||||
|  | //        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) | ||||||
|  | //        { | ||||||
|  | //            return UserConstants.NOT_UNIQUE; | ||||||
|  | //        } | ||||||
|  | //        return UserConstants.UNIQUE; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验email是否唯一 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String checkEmailUnique(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); | ||||||
|  | //        SysUser info = userMapper.checkEmailUnique(user.getEmail()); | ||||||
|  | //        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) | ||||||
|  | //        { | ||||||
|  | //            return UserConstants.NOT_UNIQUE; | ||||||
|  | //        } | ||||||
|  | //        return UserConstants.UNIQUE; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 校验用户是否允许操作 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public void checkUserAllowed(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) | ||||||
|  | //        { | ||||||
|  | //            throw new CustomException("不允许操作超级管理员用户"); | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 新增保存用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    @Transactional | ||||||
|  | //    public int insertUser(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        // 新增用户信息 | ||||||
|  | //        int rows = userMapper.insertUser(user); | ||||||
|  | //        // 新增用户岗位关联 | ||||||
|  | //        insertUserPost(user); | ||||||
|  | //        // 新增用户与角色管理 | ||||||
|  | //        insertUserRole(user); | ||||||
|  | //        return rows; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改保存用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    @Transactional | ||||||
|  | //    public int updateUser(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        Long userId = user.getUserId(); | ||||||
|  | //        // 删除用户与角色关联 | ||||||
|  | //        userRoleMapper.deleteUserRoleByUserId(userId); | ||||||
|  | //        // 新增用户与角色管理 | ||||||
|  | //        insertUserRole(user); | ||||||
|  | //        // 删除用户与岗位关联 | ||||||
|  | //        userPostMapper.deleteUserPostByUserId(userId); | ||||||
|  | //        // 新增用户与岗位管理 | ||||||
|  | //        insertUserPost(user); | ||||||
|  | //        return userMapper.updateUser(user); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户状态 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public int updateUserStatus(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.updateUser(user); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户基本信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public int updateUserProfile(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.updateUser(user); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 修改用户头像 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @param avatar 头像地址 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public boolean updateUserAvatar(String userName, String avatar) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.updateUserAvatar(userName, avatar) > 0; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 重置用户密码 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户信息 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public int resetPwd(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.updateUser(user); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 重置用户密码 | ||||||
|  | //     * | ||||||
|  | //     * @param userName 用户名 | ||||||
|  | //     * @param password 密码 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public int resetUserPwd(String userName, String password) | ||||||
|  | //    { | ||||||
|  | //        return userMapper.resetUserPwd(userName, password); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 新增用户角色信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户对象 | ||||||
|  | //     */ | ||||||
|  | //    public void insertUserRole(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        Long[] roles = user.getRoleIds(); | ||||||
|  | //        if (StringUtils.isNotNull(roles)) | ||||||
|  | //        { | ||||||
|  | //            // 新增用户与角色管理 | ||||||
|  | //            List<SysUserRole> list = new ArrayList<SysUserRole>(); | ||||||
|  | //            for (Long roleId : roles) | ||||||
|  | //            { | ||||||
|  | //                SysUserRole ur = new SysUserRole(); | ||||||
|  | //                ur.setUserId(user.getUserId()); | ||||||
|  | //                ur.setRoleId(roleId); | ||||||
|  | //                list.add(ur); | ||||||
|  | //            } | ||||||
|  | //            if (list.size() > 0) | ||||||
|  | //            { | ||||||
|  | //                userRoleMapper.batchUserRole(list); | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 新增用户岗位信息 | ||||||
|  | //     * | ||||||
|  | //     * @param user 用户对象 | ||||||
|  | //     */ | ||||||
|  | //    public void insertUserPost(SysUser user) | ||||||
|  | //    { | ||||||
|  | //        Long[] posts = user.getPostIds(); | ||||||
|  | //        if (StringUtils.isNotNull(posts)) | ||||||
|  | //        { | ||||||
|  | //            // 新增用户与岗位管理 | ||||||
|  | //            List<SysUserPost> list = new ArrayList<SysUserPost>(); | ||||||
|  | //            for (Long postId : posts) | ||||||
|  | //            { | ||||||
|  | //                SysUserPost up = new SysUserPost(); | ||||||
|  | //                up.setUserId(user.getUserId()); | ||||||
|  | //                up.setPostId(postId); | ||||||
|  | //                list.add(up); | ||||||
|  | //            } | ||||||
|  | //            if (list.size() > 0) | ||||||
|  | //            { | ||||||
|  | //                userPostMapper.batchUserPost(list); | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 通过用户ID删除用户 | ||||||
|  | //     * | ||||||
|  | //     * @param userId 用户ID | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    @Transactional | ||||||
|  | //    public int deleteUserById(Long userId) | ||||||
|  | //    { | ||||||
|  | //        // 删除用户与角色关联 | ||||||
|  | //        userRoleMapper.deleteUserRoleByUserId(userId); | ||||||
|  | //        // 删除用户与岗位表 | ||||||
|  | //        userPostMapper.deleteUserPostByUserId(userId); | ||||||
|  | //        return userMapper.deleteUserById(userId); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 批量删除用户信息 | ||||||
|  | //     * | ||||||
|  | //     * @param userIds 需要删除的用户ID | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    @Transactional | ||||||
|  | //    public int deleteUserByIds(Long[] userIds) | ||||||
|  | //    { | ||||||
|  | //        for (Long userId : userIds) | ||||||
|  | //        { | ||||||
|  | //            checkUserAllowed(new SysUser(userId)); | ||||||
|  | //        } | ||||||
|  | //        // 删除用户与角色关联 | ||||||
|  | //        userRoleMapper.deleteUserRole(userIds); | ||||||
|  | //        // 删除用户与岗位关联 | ||||||
|  | //        userPostMapper.deleteUserPost(userIds); | ||||||
|  | //        return userMapper.deleteUserByIds(userIds); | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    /** | ||||||
|  | //     * 导入用户数据 | ||||||
|  | //     * | ||||||
|  | //     * @param userList 用户数据列表 | ||||||
|  | //     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 | ||||||
|  | //     * @param operName 操作用户 | ||||||
|  | //     * @return 结果 | ||||||
|  | //     */ | ||||||
|  | //    @Override | ||||||
|  | //    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName) | ||||||
|  | //    { | ||||||
|  | //        if (StringUtils.isNull(userList) || userList.size() == 0) | ||||||
|  | //        { | ||||||
|  | //            throw new CustomException("导入用户数据不能为空!"); | ||||||
|  | //        } | ||||||
|  | //        int successNum = 0; | ||||||
|  | //        int failureNum = 0; | ||||||
|  | //        StringBuilder successMsg = new StringBuilder(); | ||||||
|  | //        StringBuilder failureMsg = new StringBuilder(); | ||||||
|  | //        String password = configService.selectConfigByKey("sys.user.initPassword"); | ||||||
|  | //        for (SysUser user : userList) | ||||||
|  | //        { | ||||||
|  | //            try | ||||||
|  | //            { | ||||||
|  | //                // 验证是否存在这个用户 | ||||||
|  | //                SysUser u = userMapper.selectUserByUserName(user.getUserName()); | ||||||
|  | //                if (StringUtils.isNull(u)) | ||||||
|  | //                { | ||||||
|  | //                    user.setPassword(SecurityUtils.encryptPassword(password)); | ||||||
|  | //                    user.setCreateBy(operName); | ||||||
|  | //                    this.insertUser(user); | ||||||
|  | //                    successNum++; | ||||||
|  | //                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功"); | ||||||
|  | //                } | ||||||
|  | //                else if (isUpdateSupport) | ||||||
|  | //                { | ||||||
|  | //                    user.setUpdateBy(operName); | ||||||
|  | //                    this.updateUser(user); | ||||||
|  | //                    successNum++; | ||||||
|  | //                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功"); | ||||||
|  | //                } | ||||||
|  | //                else | ||||||
|  | //                { | ||||||
|  | //                    failureNum++; | ||||||
|  | //                    failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在"); | ||||||
|  | //                } | ||||||
|  | //            } | ||||||
|  | //            catch (Exception e) | ||||||
|  | //            { | ||||||
|  | //                failureNum++; | ||||||
|  | //                String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; | ||||||
|  | //                failureMsg.append(msg + e.getMessage()); | ||||||
|  | //                log.error(msg, e); | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  | //        if (failureNum > 0) | ||||||
|  | //        { | ||||||
|  | //            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); | ||||||
|  | //            throw new CustomException(failureMsg.toString()); | ||||||
|  | //        } | ||||||
|  | //        else | ||||||
|  | //        { | ||||||
|  | //            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); | ||||||
|  | //        } | ||||||
|  | //        return successMsg.toString(); | ||||||
|  | //    } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | package cn.iocoder.dashboard.util.date; | ||||||
|  |  | ||||||
|  | import java.time.Duration; | ||||||
|  | import java.util.Date; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 时间工具类 | ||||||
|  |  */ | ||||||
|  | public class DateUtils { | ||||||
|  |  | ||||||
|  |     public static Date addTime(Duration duration) { | ||||||
|  |         return new Date(System.currentTimeMillis() + duration.toMillis()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static boolean isExpired(Date time) { | ||||||
|  |         return System.currentTimeMillis() > time.getTime(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package cn.iocoder.dashboard.util.servlet; | ||||||
|  |  | ||||||
|  | import cn.hutool.extra.servlet.ServletUtil; | ||||||
|  | import com.alibaba.fastjson.JSON; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 客户端工具类 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | public class ServletUtils { | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 | ||||||
|  |     public static void writeJSON(HttpServletResponse response, Object object) { | ||||||
|  |         String content = JSON.toJSONString(object); | ||||||
|  |         ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -27,6 +27,8 @@ yudao: | |||||||
|     token-secret: abcdefghijklmnopqrstuvwxyz |     token-secret: abcdefghijklmnopqrstuvwxyz | ||||||
|     token-timeout: 1d |     token-timeout: 1d | ||||||
|     session-timeout: 30m |     session-timeout: 30m | ||||||
|  |     mock-enable: true | ||||||
|  |     mock-secret: yudaoyuanma | ||||||
|   swagger: |   swagger: | ||||||
|     title: 管理后台 |     title: 管理后台 | ||||||
|     description: 提供管理员管理的所有功能 |     description: 提供管理员管理的所有功能 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV