瀏覽代碼

init files

chao 3 年之前
父節點
當前提交
e150baff01
共有 23 個文件被更改,包括 1546 次插入0 次删除
  1. 27 0
      pom.xml
  2. 29 0
      src/main/java/com/caimei365/manager/config/WebConfig.java
  3. 39 0
      src/main/java/com/caimei365/manager/config/security/AuthenticationEntryPointImpl.java
  4. 62 0
      src/main/java/com/caimei365/manager/config/security/AuthenticationProviderImpl.java
  5. 22 0
      src/main/java/com/caimei365/manager/config/security/ConstantKey.java
  6. 29 0
      src/main/java/com/caimei365/manager/config/security/GlobalCorsFilter.java
  7. 27 0
      src/main/java/com/caimei365/manager/config/security/GrantedAuthorityImpl.java
  8. 60 0
      src/main/java/com/caimei365/manager/config/security/JwtAuthenticationFilter.java
  9. 120 0
      src/main/java/com/caimei365/manager/config/security/JwtLoginFilter.java
  10. 172 0
      src/main/java/com/caimei365/manager/config/security/JwtService.java
  11. 89 0
      src/main/java/com/caimei365/manager/config/security/SecurityConfig.java
  12. 44 0
      src/main/java/com/caimei365/manager/config/security/UserDetailsServiceImpl.java
  13. 124 0
      src/main/java/com/caimei365/manager/controller/SysUserApi.java
  14. 69 0
      src/main/java/com/caimei365/manager/dao/SysUserDao.java
  15. 90 0
      src/main/java/com/caimei365/manager/entity/PaginationVo.java
  16. 70 0
      src/main/java/com/caimei365/manager/entity/ResponseJson.java
  17. 49 0
      src/main/java/com/caimei365/manager/entity/SysMenu.java
  18. 45 0
      src/main/java/com/caimei365/manager/entity/SysUser.java
  19. 85 0
      src/main/java/com/caimei365/manager/service/RedisService.java
  20. 60 0
      src/main/java/com/caimei365/manager/service/SysUserService.java
  21. 164 0
      src/main/java/com/caimei365/manager/service/impl/SysUserServiceImpl.java
  22. 21 0
      src/main/resources/application.yml
  23. 49 0
      src/main/resources/mapper/SysUserDao.xml

+ 27 - 0
pom.xml

@@ -34,12 +34,39 @@
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.2.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.4.1</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-spring-boot-starter</artifactId>
+                    <groupId>org.mybatis.spring.boot</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
 
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.78</version>
+        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>

+ 29 - 0
src/main/java/com/caimei365/manager/config/WebConfig.java

@@ -0,0 +1,29 @@
+package com.caimei365.manager.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Configuration
+public class WebConfig {
+    /**
+     * BCryptPasswordEncoder 解析器注入到容器
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+}

+ 39 - 0
src/main/java/com/caimei365/manager/config/security/AuthenticationEntryPointImpl.java

@@ -0,0 +1,39 @@
+package com.caimei365.manager.config.security;
+
+import com.alibaba.fastjson.JSON;
+import com.caimei365.manager.entity.ResponseJson;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * 自定义认证拦截器
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Slf4j
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
+
+    public AuthenticationEntryPointImpl() {}
+
+    /**
+     * @param request 遇到了认证异常authException用户请求
+     * @param response 将要返回给客户的相应
+     */
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
+        log.debug("预认证入口被调用。拒绝访问,AuthenticationException={}", exception.getMessage());
+        // 没有权限,返回403
+        // response.sendError(403, "Access Denied");
+        ResponseJson<Void> errorJson = ResponseJson.error("访问失败,需要身份认证", null);
+        response.setCharacterEncoding("UTF-8");
+        response.getWriter().write(JSON.toJSONString(errorJson));
+    }
+
+}

+ 62 - 0
src/main/java/com/caimei365/manager/config/security/AuthenticationProviderImpl.java

@@ -0,0 +1,62 @@
+package com.caimei365.manager.config.security;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+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.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * 自定义身份认证验证组件
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Slf4j
+public class AuthenticationProviderImpl implements AuthenticationProvider {
+
+    private final UserDetailsService userDetailsService;
+    private final BCryptPasswordEncoder bCryptPasswordEncoder;
+
+    public AuthenticationProviderImpl(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder){
+        this.userDetailsService = userDetailsService;
+        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
+    }
+
+    /**
+     * 用来验证用户身份 (对传递的Authentication对象的身份验证)
+     *
+     * @param authentication 传递的Authentication对象
+     * @return 包含凭证的经过完全认证的对象
+     * @throws AuthenticationException 份验证失败异常
+     */
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        // 获取认证的用户名 & 密码
+        String name = authentication.getName();
+        String password = authentication.getCredentials().toString();
+        // 认证逻辑
+        UserDetails userDetails = userDetailsService.loadUserByUsername(name);
+        if (bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
+            log.info("用户登录成功,username={}", name);
+            // 生成令牌 这里令牌里面存入了:name,password,authorities, 当然你也可以放其他内容
+            return new UsernamePasswordAuthenticationToken(name, password, userDetails.getAuthorities());
+        } else {
+            throw new BadCredentialsException("密码错误");
+        }
+    }
+
+    /**
+     * 判断当前的AuthenticationProvider 是否支持对应的Authentication对象
+     * @param authentication Authentication对象
+     * @return boolean
+     */
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return authentication.equals(UsernamePasswordAuthenticationToken.class);
+    }
+}

+ 22 - 0
src/main/java/com/caimei365/manager/config/security/ConstantKey.java

@@ -0,0 +1,22 @@
+package com.caimei365.manager.config.security;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+public class ConstantKey {
+    /**
+     * 签名key
+     */
+    public static final String SIGNING_KEY = "Charles@Jwt!&Secret^#";
+    /**
+     * Token存入header的键名
+     */
+    public static final String TOKEN_NAME = "X-Token";
+    /**
+     * Token过期时间(分钟)
+     */
+    public static final Integer TOKEN_EXPIRE = 5;
+}

+ 29 - 0
src/main/java/com/caimei365/manager/config/security/GlobalCorsFilter.java

@@ -0,0 +1,29 @@
+package com.caimei365.manager.config.security;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 全局跨域过滤器
+ *
+ * @author : Charles
+ * @date : 2021/12/14
+ */
+public class GlobalCorsFilter implements Filter {
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        HttpServletResponse res = (HttpServletResponse) response;
+        res.setHeader("Access-Control-Allow-Origin", "*");
+        res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
+        res.setHeader("Access-Control-Max-Age", "3600");
+        res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control, X-Token");
+        chain.doFilter(request, res);
+    }
+    @Override
+    public void destroy() {
+    }
+}

+ 27 - 0
src/main/java/com/caimei365/manager/config/security/GrantedAuthorityImpl.java

@@ -0,0 +1,27 @@
+package com.caimei365.manager.config.security;
+
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * 权限类型,负责存储权限和角色
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+public class GrantedAuthorityImpl implements GrantedAuthority {
+
+    private String authority;
+
+    public GrantedAuthorityImpl(String authority) {
+        this.authority = authority;
+    }
+
+    public void setAuthority(String authority) {
+        this.authority = authority;
+    }
+
+    @Override
+    public String getAuthority() {
+        return this.authority;
+    }
+}

+ 60 - 0
src/main/java/com/caimei365/manager/config/security/JwtAuthenticationFilter.java

@@ -0,0 +1,60 @@
+package com.caimei365.manager.config.security;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * 自定义JWT认证过滤器
+ *
+ * 该类继承自BasicAuthenticationFilter,在doFilterInternal方法中,
+ * 从http头的Authorization 项读取token数据,然后用Jwts包提供的方法校验token的合法性。
+ * 如果校验通过,就认为这是一个取得授权的合法请求
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Slf4j
+public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
+
+    private final JwtService jwtService;
+    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtService jwtService) {
+        super(authenticationManager);
+        this.jwtService = jwtService;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        response.setCharacterEncoding("UTF-8");
+        chain.doFilter(request, response);
+    }
+
+    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
+        // 解析token
+        String userinfo = jwtService.parseToken(request);
+        if (userinfo != null) {
+            // 获取用户权限和角色
+            String[] split = userinfo.split("-")[1].split(",");
+            ArrayList<GrantedAuthority> authorities = new ArrayList<>();
+            for (String s : split) {
+                authorities.add(new GrantedAuthorityImpl(s));
+            }
+            // 返回Authentication
+            return new UsernamePasswordAuthenticationToken(userinfo, null, authorities);
+        }
+        log.warn("访问[{}]失败,需要身份认证", request.getRequestURI());
+        return null;
+    }
+}

+ 120 - 0
src/main/java/com/caimei365/manager/config/security/JwtLoginFilter.java

@@ -0,0 +1,120 @@
+package com.caimei365.manager.config.security;
+
+import com.alibaba.fastjson.JSON;
+import com.caimei365.manager.entity.ResponseJson;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * 自定义JWT登录过滤器
+ *
+ * 验证用户名密码正确后,生成一个token,并将token返回给客户端
+ * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的3个方法
+ *     attemptAuthentication:接收并解析用户凭证。
+ *     successfulAuthentication:用户成功登录后被调用,我们在这个方法里生成token。
+ *     unsuccessfulAuthentication:认证失败后被调用
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Slf4j
+public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
+
+    private final AuthenticationManager authenticationManager;
+    private final JwtService jwtService;
+    public JwtLoginFilter(AuthenticationManager authenticationManager, JwtService jwtService) {
+        this.authenticationManager = authenticationManager;
+        this.jwtService = jwtService;
+    }
+
+    /**
+     * 尝试身份认证(接收并解析用户凭证)
+     */
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+//        String username = request.getParameter("username");
+//        String password = request.getParameter("password");
+        String username = this.getBodyParams(request).get(SPRING_SECURITY_FORM_USERNAME_KEY);
+        String password = this.getBodyParams(request).get(SPRING_SECURITY_FORM_PASSWORD_KEY);
+        return authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>())
+        );
+    }
+
+    /**
+     * 认证成功(用户成功登录后,这个方法会被调用,我们在这个方法里生成token)
+     */
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {
+        try {
+            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
+            // 定义存放角色集合的对象
+            List<String> roleList = new ArrayList<>();
+            for (GrantedAuthority grantedAuthority : authorities) {
+                roleList.add(grantedAuthority.getAuthority());
+            }
+            // 生成token
+            String token = jwtService.createToken(auth.getName(), roleList);
+            /*
+             * 返回token
+             */
+            log.info("用户登录成功,生成token={}", token);
+            // 登录成功后,返回token到header里面
+            response.addHeader("X-Token", token);
+            // 设置用户信息及token到body里面
+            Map<String, Object> resultMap = new HashMap();
+            resultMap.put("username",auth.getName());
+            resultMap.put("token",token);
+            resultMap.put("roles",roleList);
+            ResponseJson<Map<String, Object>> resultJson = ResponseJson.success("登录成功", resultMap);
+            response.setCharacterEncoding("UTF-8");
+            response.getWriter().write(JSON.toJSONString(resultJson));
+        } catch (IOException e) {
+            log.error("IOException:", e);
+        }
+    }
+
+    /**
+     * 认证失败调用
+     */
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
+        log.warn("登录失败[{}],AuthenticationException={}", request.getRequestURI(), exception.getMessage());
+        // 登录失败,返回错误信息
+        ResponseJson<Void> result = ResponseJson.error(exception.getMessage(), null);
+        response.setCharacterEncoding("UTF-8");
+        response.getWriter().write(JSON.toJSONString(result));
+    }
+
+    private ThreadLocal<Map<String,String>> threadLocal = new ThreadLocal<>();
+    /**
+     * 获取body参数  body中的参数只能获取一次
+     */
+    private Map<String,String> getBodyParams(HttpServletRequest request){
+        Map<String,String> bodyParams =  threadLocal.get();
+        if(bodyParams==null) {
+            ObjectMapper objectMapper = new ObjectMapper();
+            try (InputStream is = request.getInputStream()) {
+                bodyParams = objectMapper.readValue(is, Map.class);
+            } catch (IOException ignored) {}
+            if(bodyParams==null) {
+                bodyParams = new HashMap<>();
+            }
+            threadLocal.set(bodyParams);
+        }
+        return bodyParams;
+    }
+}

+ 172 - 0
src/main/java/com/caimei365/manager/config/security/JwtService.java

@@ -0,0 +1,172 @@
+package com.caimei365.manager.config.security;
+
+import com.caimei365.manager.service.RedisService;
+import io.jsonwebtoken.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * JWT 服务工具类
+ *
+ * @author : Charles
+ * @date : 2021/12/13
+ */
+@Slf4j
+@Service
+public class JwtService {
+    @Resource
+    private RedisService redisService;
+    /**
+     * 生成token
+     * @param username 用户名
+     * @param roleList 角色列表
+     */
+    public String createToken(String username, List<String> roleList) {
+        Calendar calendar = Calendar.getInstance();
+        // 设置签发时间
+        calendar.setTime(new Date());
+        Date now = calendar.getTime();
+        // 设置过期时间
+        calendar.add(Calendar.MINUTE, ConstantKey.TOKEN_EXPIRE);
+        Date time = calendar.getTime();
+        String token = Jwts.builder()
+                .setSubject(username + "-" + roleList)
+                // 签发时间
+                .setIssuedAt(now)
+                // 过期时间
+                .setExpiration(time)
+                // 自定义算法与签名:这里算法采用HS512,常量中定义签名key
+                .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
+                .compact();
+        // 将token存入redis,并设置超时时间为token过期时间
+        long expire = time.getTime() - now.getTime();
+        redisService.set(token, token, expire);
+        return token;
+    }
+
+    /**
+     * 解析Token
+     */
+    public String parseToken(HttpServletRequest request) {
+        String userinfo = null;
+        String token = request.getHeader(ConstantKey.TOKEN_NAME);
+        if (StringUtils.hasLength(token)) {
+            String cacheToken = String.valueOf(redisService.get(token));
+            if (StringUtils.hasLength(cacheToken) && !"null".equals(cacheToken)) {
+                try {
+                    Claims claims = Jwts.parser()
+                            // 设置生成token的签名key
+                            .setSigningKey(ConstantKey.SIGNING_KEY)
+                            // 解析token
+                            .parseClaimsJws(cacheToken).getBody();
+                    // 取出用户信息
+                    userinfo = claims.getSubject();
+                    // 重设Redis超时时间
+                    resetRedisExpire(token, claims);
+                } catch (ExpiredJwtException e) {
+                    log.info("Token过期续签,ExpiredJwtException={}", e.getMessage());
+                    Claims claims = e.getClaims();
+                    // 取出用户信息
+                    userinfo = claims.getSubject();
+                    // 刷新Token
+                    refreshToken(token, claims);
+                } catch (UnsupportedJwtException e) {
+                    log.warn("访问[{}]失败,UnsupportedJwtException={}", request.getRequestURI(), e.getMessage());
+                } catch (MalformedJwtException e) {
+                    log.warn("访问[{}]失败,MalformedJwtException={}", request.getRequestURI(), e.getMessage());
+                } catch (SignatureException e) {
+                    log.warn("访问[{}]失败,SignatureException={}", request.getRequestURI(), e.getMessage());
+                } catch (IllegalArgumentException e) {
+                    log.warn("访问[{}]失败,IllegalArgumentException={}", request.getRequestURI(), e.getMessage());
+                }
+            }
+        }
+        return userinfo;
+    }
+
+    /**
+     * 解析Token,取出用户名(Token过期仍取出用户名)
+     */
+    public String getUsername(String token){
+        String username = null;
+        if (StringUtils.hasLength(token)) {
+            String userinfo = null;
+            try {
+                Claims claims = Jwts.parser()
+                        // 设置生成token的签名key
+                        .setSigningKey(ConstantKey.SIGNING_KEY)
+                        // 解析token
+                        .parseClaimsJws(token).getBody();
+                // 取出用户信息
+                userinfo = claims.getSubject();
+            } catch (ExpiredJwtException e) {
+                Claims claims = e.getClaims();
+                // 取出用户信息
+                userinfo = claims.getSubject();
+            } catch (Exception ignored){}
+            if (StringUtils.hasLength(userinfo)){
+                username = userinfo.split("-")[0];
+            }
+        }
+        return username;
+    }
+
+
+    /**
+     * 重设Redis超时时间
+     * 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间)
+     */
+    private void resetRedisExpire(String token, Claims claims) {
+        // 当前时间
+        long current = System.currentTimeMillis();
+        // token签发时间
+        long issuedAt = claims.getIssuedAt().getTime();
+        // token过期时间
+        long expiration = claims.getExpiration().getTime();
+        // 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间)
+        long expireAt = current + (expiration - issuedAt);
+        // 重设Redis超时时间
+        redisService.expire(token, expireAt);
+    }
+
+    /**
+     * 刷新Token
+     * 刷新Token的时机: 当cacheToken已过期 并且Redis在有效期内
+     * 重新生成Token并覆盖Redis的v值(这时候k、v值不一样了),然后设置Redis过期时间为:新Token过期时间
+     */
+    private void refreshToken(String token, Claims claims) {
+        // 当前时间
+        long current = System.currentTimeMillis();
+        /*
+         * 重新生成token
+         */
+        Calendar calendar = Calendar.getInstance();
+        // 设置签发时间
+        calendar.setTime(new Date());
+        Date now = calendar.getTime();
+        // 设置过期时间: TOKEN_EXPIRE分钟
+        calendar.add(Calendar.MINUTE, ConstantKey.TOKEN_EXPIRE);
+        Date time = calendar.getTime();
+        String refreshToken = Jwts.builder()
+                .setSubject(claims.getSubject())
+                // 签发时间
+                .setIssuedAt(now)
+                // 过期时间
+                .setExpiration(time)
+                // 算法与签名(同生成token):这里算法采用HS512,常量中定义签名key
+                .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
+                .compact();
+        // 将refreshToken覆盖Redis的v值,并设置超时时间为refreshToken过期时间
+        long expire = time.getTime() - now.getTime();
+        redisService.set(token, token, expire);
+        // 打印日志
+        log.info("刷新token执行时间: {}", (System.currentTimeMillis() - current) + " 毫秒");
+    }
+}

+ 89 - 0
src/main/java/com/caimei365/manager/config/security/SecurityConfig.java

@@ -0,0 +1,89 @@
+package com.caimei365.manager.config.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * SpringSecurity配置类
+ * 通过继承 WebSecurityConfigurerAdapter 实现自定义Security策略
+ * `@Configuration`:声明当前类是一个配置类
+ * `@EnableWebSecurity`:开启WebSecurity模式
+ * `@EnableGlobalMethodSecurity(securedEnabled=true)`:开启注解,支持方法级别的权限控制
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Resource
+    private JwtService jwtService;
+
+    @Resource
+    private UserDetailsService userDetailsService;
+
+    @Resource
+    private BCryptPasswordEncoder bCryptPasswordEncoder;
+
+    /**
+     * 全局请求忽略规则配置
+     */
+    @Override
+    public void configure(WebSecurity web) {
+        // 需要放行的URL
+        web.ignoring().antMatchers("/register", "/hello");
+    }
+
+    /**
+     * 自定义认证策略:登录的时候会进入
+     */
+    @Override
+    public void configure(AuthenticationManagerBuilder auth) {
+        // 2. 通过实现 AuthenticationProvider 自定义身份认证验证组件
+        auth.authenticationProvider(new AuthenticationProviderImpl(userDetailsService, bCryptPasswordEncoder));
+    }
+
+    /**
+     * 自定义 HTTP 验证规则
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+            // 关闭Session
+            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+            // 所有请求需要身份认证
+            .and().authorizeRequests().anyRequest().authenticated()
+            .and()
+            // 自定义JWT登录过滤器
+            .addFilter(new JwtLoginFilter(authenticationManager(), jwtService))
+            // 自定义JWT认证过滤器
+            .addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtService))
+            // 自定义认证拦截器,也可以直接使用内置实现类Http403ForbiddenEntryPoint
+            .exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointImpl())
+            // 允许跨域
+            .and().cors()
+            // 保证跨域过滤器首先触发
+            .and().addFilterBefore(new GlobalCorsFilter(), ChannelProcessingFilter.class)
+            // 禁用跨站伪造
+            .csrf().disable();
+    }
+}

+ 44 - 0
src/main/java/com/caimei365/manager/config/security/UserDetailsServiceImpl.java

@@ -0,0 +1,44 @@
+package com.caimei365.manager.config.security;
+
+import com.caimei365.manager.dao.SysUserDao;
+import com.caimei365.manager.entity.SysUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+    @Resource
+    private SysUserDao sysUserDao;
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        SysUser user = sysUserDao.findByUsername(username);
+        if(user == null){
+            throw new UsernameNotFoundException("用户" + username + "不存在!");
+        }
+        // 获取用户角色列表
+        List<String> roleList = sysUserDao.getRoleList(user.getId());
+        // 设置权限和角色
+        ArrayList<GrantedAuthority> authorities = new ArrayList<>();
+        for (String role : roleList) {
+            if (StringUtils.hasLength(role)){
+                authorities.add( new GrantedAuthorityImpl(role));
+            }
+        }
+        return new User(user.getUsername(), user.getPassword(), authorities);
+    }
+}

+ 124 - 0
src/main/java/com/caimei365/manager/controller/SysUserApi.java

@@ -0,0 +1,124 @@
+package com.caimei365.manager.controller;
+
+import com.caimei365.manager.config.security.ConstantKey;
+import com.caimei365.manager.config.security.JwtService;
+import com.caimei365.manager.entity.PaginationVo;
+import com.caimei365.manager.entity.ResponseJson;
+import com.caimei365.manager.entity.SysMenu;
+import com.caimei365.manager.entity.SysUser;
+import com.caimei365.manager.service.SysUserService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@RestController
+@RequestMapping("/sys")
+public class SysUserApi {
+
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private JwtService jwtService;
+
+    /**
+     * 获取用户信息
+     */
+    @GetMapping("/user/info")
+    public ResponseJson<SysUser> getUserInfo(HttpServletRequest request) {
+        String token = request.getHeader(ConstantKey.TOKEN_NAME);
+        String username = jwtService.getUsername(token);
+        return sysUserService.getInfoByUsername(username);
+    }
+
+    /**
+     * 获取所有菜单列表
+     */
+    @GetMapping("/menu/list")
+    public ResponseJson<PaginationVo<SysMenu>> menuList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+                                                        @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
+        return sysUserService.getMenuList(pageNum, pageSize);
+    }
+
+    /**
+     * 根据ID获取菜单
+     */
+    @GetMapping("/menu/{id}")
+    public ResponseJson<SysMenu> getMenu(@PathVariable("id") Integer id) {
+        if (null == id || id <= 0) {
+            return ResponseJson.error("菜单Id不能为空!", null);
+        }
+        return sysUserService.getMenu(id);
+    }
+
+    /**
+     * 根据ID更新菜单
+     */
+    @PostMapping("/menu/update/{id}")
+    public ResponseJson<Void> updateMenu(@PathVariable("id") Integer id, SysMenu menu) {
+        if (null == id || id <= 0) {
+            return ResponseJson.error("菜单Id不能为空!", null);
+        }
+        return sysUserService.updateMenu(id, menu);
+    }
+
+    /**
+     * 根据ID删除菜单
+     */
+    @PostMapping("/menu/delete/{id}")
+    public ResponseJson<Void> deleteMenu(@PathVariable("id") Integer id) {
+        if (null == id || id <= 0) {
+            return ResponseJson.error("菜单Id不能为空!", null);
+        }
+        return sysUserService.deleteMenu(id);
+    }
+
+
+
+
+
+
+
+
+
+
+    /**
+     * 退出登录
+     */
+    @PostMapping("/user/logout")
+    public ResponseJson<Void> logout(HttpServletRequest request) {
+        String token = request.getHeader(ConstantKey.TOKEN_NAME);
+        return sysUserService.logout(token);
+    }
+
+    /**
+     * 注册接口
+     */
+    @PostMapping("/user/register")
+    public ResponseJson<SysUser> register(SysUser sysUser) {
+        return sysUserService.register(sysUser);
+    }
+
+//    /**
+//     * 测试公开接口
+//     */
+//    @GetMapping("/hello")
+//    public ResponseJson<Void> hello() {
+//        return ResponseJson.success("访问成功!公开接口:/hello",null);
+//    }
+//
+//    /**
+//     * 测试需要认证的接口
+//     */
+//    @GetMapping("/private")
+//    public ResponseJson<Void> hello2() {
+//        return ResponseJson.success("访问成功!非公开接口:/private", null);
+//    }
+}

+ 69 - 0
src/main/java/com/caimei365/manager/dao/SysUserDao.java

@@ -0,0 +1,69 @@
+package com.caimei365.manager.dao;
+
+
+import com.caimei365.manager.entity.SysMenu;
+import com.caimei365.manager.entity.SysUser;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Mapper
+public interface SysUserDao {
+
+    /**
+     * 根据用户名称查找用户
+     */
+    SysUser findByUsername(String username);
+
+    /**
+     * 新增系统用户
+     */
+    void insertSysUser(SysUser sysUser);
+
+    /**
+     * 新增用户角色关系表
+     */
+    void insertUserRoleRelation(Integer userId, Integer roleId);
+
+    /**
+     * 获取用户角色名称列表
+     */
+    List<String> getRoleList(Integer userId);
+
+    /**
+     * 获取用户角色Id列表
+     */
+    List<Integer> getRoleIdsByUserId(String username);
+
+    /**
+     * 根据角色Ids获取菜单列表
+     */
+    List<SysMenu> getMenusByRoleIds(@Param("roleIds") List<Integer> roleIds);
+
+    /**
+     * 获取所有菜单列表
+     */
+    List<SysMenu> getMenuList();
+
+    /**
+     * 根据ID获取菜单
+     */
+    SysMenu getMenu(Integer id);
+
+    /**
+     * 根据ID更新菜单
+     */
+    void updateMenu(SysMenu menu);
+
+    /**
+     * 根据ID删除菜单
+     */
+    void deleteMenu(Integer id);
+}

+ 90 - 0
src/main/java/com/caimei365/manager/entity/PaginationVo.java

@@ -0,0 +1,90 @@
+package com.caimei365.manager.entity;
+
+import com.github.pagehelper.Page;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/16
+ */
+@Data
+public class PaginationVo<T> implements Serializable {
+    /** 当前页码 */
+    @Builder.Default
+    private int pageNum = 1;
+    /** 每页大小 */
+    @Builder.Default
+    private int pageSize = 10;
+    /** 总记录数 */
+    private int totalRecord;
+    /** 总页数 */
+    private int totalPage;
+    /** 是否有后一页 */
+    private boolean hasNextPage;
+    /** 是否有前一页 */
+    private boolean hasPreviousPage;
+    /** 查询结果 */
+    List<T> results;
+
+    public PaginationVo(List<T> list) {
+        if (list instanceof Page) {
+            Page<T> page = (Page<T>) list;
+            this.pageNum = page.getPageNum();
+            this.pageSize = page.getPageSize();
+            this.totalRecord = (int) page.getTotal();
+            this.totalPage = page.getPages();
+            setHasPreviousAndNext();
+            this.results = page;
+        } else if (list != null) {
+            this.pageSize = list.size();
+            this.totalRecord = list.size();
+            this.totalPage = 1;
+            this.results = list;
+        }
+    }
+
+    public PaginationVo(int pageNum, int pageSize, int totalRecord, List<T> results) {
+        super();
+        this.pageNum = pageNum;
+        this.pageSize = pageSize;
+        this.totalRecord = totalRecord;
+        this.totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
+        this.results = results;
+        hasPreviousPage = this.pageNum >= 2;
+        hasNextPage = this.pageNum < totalPage;
+    }
+
+    public PaginationVo(int pageNum, int maxSize, int totalRecord, int totalPage,
+                        List<T> results) {
+        super();
+        this.pageNum = pageNum;
+        this.pageSize = maxSize;
+        this.totalRecord = totalRecord;
+        this.totalPage = totalPage;
+        this.results = results;
+        hasPreviousPage = this.pageNum >= 2;
+        hasNextPage = this.pageNum < totalPage;
+    }
+
+    public PaginationVo() {super();}
+
+    public void setHasPreviousAndNext() {
+        hasPreviousPage = this.pageNum >= 2;
+        hasNextPage = this.pageNum < totalPage;
+    }
+
+    public int countTotalPage() {
+        int total = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
+        this.setTotalPage(total);
+        return total;
+    }
+
+    private static final long serialVersionUID = 1L;
+}
+

+ 70 - 0
src/main/java/com/caimei365/manager/entity/ResponseJson.java

@@ -0,0 +1,70 @@
+package com.caimei365.manager.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 全局API返回JSON
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Data
+public class ResponseJson<T> implements Serializable {
+    /** 自定义状态码 */
+    private int code;
+    /** 提示信息 */
+    private String msg;
+    /** 返回数据 */
+    private T data;
+
+    private ResponseJson(int code, String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public static ResponseJson<Void> success() {
+        return new ResponseJson<>(0, "操作成功", null);
+    }
+
+    public static<T> ResponseJson<T> success(T data) {
+        return new ResponseJson<>(0, "操作成功", data);
+    }
+
+    public static<T> ResponseJson<T> success(String msg, T data) {
+        return new ResponseJson<>(0, msg, data);
+    }
+
+    public static<T> ResponseJson<T> success(int code, String msg, T data) {
+        return new ResponseJson<>(code, msg, data);
+    }
+
+    public static ResponseJson<Void> error() {
+        return new ResponseJson<>(-1, "操作失败", null);
+    }
+
+    public static ResponseJson<Void> error(String msg) {
+        return new ResponseJson<>(-1, msg, null);
+    }
+
+    public static ResponseJson<Void> error(int code, String msg) {
+        return new ResponseJson<>(code, msg, null);
+    }
+
+    public static<T> ResponseJson<T> error(String msg, T data) {
+        return new ResponseJson<>(-1, msg, data);
+    }
+
+    public static<T> ResponseJson<T> error(int code, String msg, T data) {
+        return new ResponseJson<>(code, msg, data);
+    }
+
+    @Override
+    public String toString() {
+        return "ResponseJson{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}';
+    }
+
+    private static final long serialVersionUID = 1L;
+}

+ 49 - 0
src/main/java/com/caimei365/manager/entity/SysMenu.java

@@ -0,0 +1,49 @@
+package com.caimei365.manager.entity;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/10
+ */
+@Data
+public class SysMenu {
+    /**
+     * Id
+     */
+    private Integer id;
+    /**
+     * 菜单名称
+     */
+    private String title;
+    /**
+     * 菜单名称
+     */
+    private String name;
+    /**
+     * 图标
+     */
+    private String icon;
+    /**
+     * 菜单层级
+     */
+    private Integer level;
+    /**
+     * 父级菜单Id
+     */
+    private Integer parentId;
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    /**
+     * 状态:0启用,1停用
+     */
+    private Integer status;
+
+    private static final long serialVersionUID = 1L;
+}

+ 45 - 0
src/main/java/com/caimei365/manager/entity/SysUser.java

@@ -0,0 +1,45 @@
+package com.caimei365.manager.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+@Data
+public class SysUser {
+    /**
+     * 用户Id
+     */
+    private Integer id;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 头像
+     */
+    private String avatar;
+    /**
+     * 角色Ids,用","隔开
+     */
+    private String roleIds;
+    /**
+     * 角色名称集合
+     */
+    private List<String> roles;
+    /**
+     * 可访问菜单集合
+     */
+    private List<SysMenu> menus;
+
+    private static final long serialVersionUID = 1L;
+}

+ 85 - 0
src/main/java/com/caimei365/manager/service/RedisService.java

@@ -0,0 +1,85 @@
+package com.caimei365.manager.service;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis 服务工具类
+ *
+ * @author : Charles
+ * @date : 2021/12/10
+ */
+@Service
+public class RedisService {
+    @Resource
+    private RedisTemplate<Serializable, Object> redisTemplate;
+    /**
+     * 读取缓存
+     */
+    public Object get(String key) {
+        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+        return operations.get(key);
+    }
+    /**
+     * 判断缓存中是否存在
+     */
+    public boolean exists(String key) {
+        return StringUtils.hasLength(key) && Boolean.TRUE.equals(redisTemplate.hasKey(key));
+    }
+    /**
+     * 删除缓存
+     */
+    public void remove(String key) {
+        if (exists(key)) {
+            redisTemplate.delete(key);
+        }
+    }
+    /**
+     * 写入缓存
+     */
+    public boolean set(String key, Object value) {
+        boolean result = false;
+        try {
+            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+            operations.set(key, value);
+            result = true;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+    /**
+     * 写入缓存 并 加上过期时间(毫秒)
+     */
+    public boolean set(String key, Object value, Long expireTimeMillis) {
+        boolean result = false;
+        try {
+            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+            operations.set(key, value);
+            redisTemplate.expire(key, expireTimeMillis, TimeUnit.MILLISECONDS);
+            result = true;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+    /**
+     * 写入过期时间(毫秒)
+     */
+    public boolean expire(String key, Long expireTimeMillis) {
+        boolean result = false;
+        try {
+            redisTemplate.expire(key, expireTimeMillis, TimeUnit.MILLISECONDS);
+            result = true;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+}

+ 60 - 0
src/main/java/com/caimei365/manager/service/SysUserService.java

@@ -0,0 +1,60 @@
+package com.caimei365.manager.service;
+
+
+import com.caimei365.manager.entity.PaginationVo;
+import com.caimei365.manager.entity.ResponseJson;
+import com.caimei365.manager.entity.SysMenu;
+import com.caimei365.manager.entity.SysUser;
+
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/2
+ */
+
+public interface SysUserService {
+
+    /**
+     * 获取用户信息
+     */
+    ResponseJson<SysUser> getInfoByUsername(String username);
+
+    /**
+     * 获取所有菜单列表
+     */
+    ResponseJson<PaginationVo<SysMenu>> getMenuList(int pageNum, int pageSize);
+
+    /**
+     * 根据ID获取菜单
+     */
+    ResponseJson<SysMenu> getMenu(Integer id);
+
+    /**
+     * 根据ID更新菜单
+     */
+    ResponseJson<Void> updateMenu(Integer id, SysMenu menu);
+
+    /**
+     * 根据ID删除菜单
+     */
+    ResponseJson<Void> deleteMenu(Integer id);
+
+
+//    /**
+//     * 获取用户可访问菜单
+//     */
+//    ResponseJson<List<SysMenu>> menuList(String username);
+    /**
+     * 退出登录
+     */
+    ResponseJson<Void> logout(String token);
+    /**
+     * 注册接口
+     */
+    ResponseJson<SysUser> register(SysUser sysUser);
+
+
+}

+ 164 - 0
src/main/java/com/caimei365/manager/service/impl/SysUserServiceImpl.java

@@ -0,0 +1,164 @@
+package com.caimei365.manager.service.impl;
+
+import com.caimei365.manager.dao.SysUserDao;
+import com.caimei365.manager.entity.PaginationVo;
+import com.caimei365.manager.entity.ResponseJson;
+import com.caimei365.manager.entity.SysMenu;
+import com.caimei365.manager.entity.SysUser;
+import com.caimei365.manager.service.RedisService;
+import com.caimei365.manager.service.SysUserService;
+import com.github.pagehelper.PageHelper;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/12/10
+ */
+@Service
+public class SysUserServiceImpl implements SysUserService {
+
+    @Resource
+    private SysUserDao sysUserDao;
+
+    @Resource
+    private BCryptPasswordEncoder passwordEncoder;
+
+    @Resource
+    private RedisService redisService;
+
+    /**
+     * 获取用户信息
+     */
+    @Override
+    public ResponseJson<SysUser> getInfoByUsername(String username) {
+        if (StringUtils.hasLength(username)) {
+            SysUser sysUser = sysUserDao.findByUsername(username);
+            // 获取用户角色列表
+            List<String> roleList = sysUserDao.getRoleList(sysUser.getId());
+            sysUser.setRoles(roleList);
+
+            // 获取用户角色Id
+            List<Integer> roleIds = sysUserDao.getRoleIdsByUserId(username);
+            List<SysMenu> menus = null;
+            if (!CollectionUtils.isEmpty(roleIds)) {
+                // 根据角色Id获取菜单列表
+                menus = sysUserDao.getMenusByRoleIds(roleIds);
+            }
+            sysUser.setMenus(menus);
+
+            return ResponseJson.success(sysUser);
+        }
+        return ResponseJson.error("用户数据异常", null);
+    }
+
+    /**
+     * 获取所有菜单列表
+     */
+    @Override
+    public ResponseJson<PaginationVo<SysMenu>> getMenuList(int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        List<SysMenu> menus = sysUserDao.getMenuList();
+        PaginationVo<SysMenu> pageData = new PaginationVo<>(menus);
+        return ResponseJson.success(pageData);
+    }
+
+    /**
+     * 根据ID获取菜单
+     *
+     * @param id
+     */
+    @Override
+    public ResponseJson<SysMenu> getMenu(Integer id) {
+        SysMenu menu = sysUserDao.getMenu(id);
+        return ResponseJson.success(menu);
+    }
+
+    /**
+     * 根据ID更新菜单
+     *
+     * @param id
+     */
+    @Override
+    public ResponseJson<Void> updateMenu(Integer id, SysMenu menu) {
+        menu.setId(id);
+        sysUserDao.updateMenu(menu);
+        return ResponseJson.success();
+    }
+
+    /**
+     * 根据ID删除菜单
+     *
+     * @param id
+     */
+    @Override
+    public ResponseJson<Void> deleteMenu(Integer id) {
+        sysUserDao.deleteMenu(id);
+        return ResponseJson.success();
+    }
+
+//    /**
+//     * 获取用户可访问菜单
+//     */
+//    @Override
+//    public ResponseJson<List<SysMenu>> menuList(String username) {
+//        if (!StringUtils.hasLength(username)) {
+//            return ResponseJson.error("用户信息异常", null);
+//        }
+//        // 获取用户角色Id
+//        List<Long> roleIds = sysUserDao.getRoleIdsByUserId(username);
+//        List<SysMenu> menus = null;
+//        if (!CollectionUtils.isEmpty(roleIds)) {
+//            // 根据角色Id获取菜单列表
+//            menus = sysUserDao.getMenuListByRoleIds(roleIds);
+//        }
+//        return ResponseJson.success(menus);
+//    }
+
+    /**
+     * 退出登录
+     *
+     * @param token
+     */
+    @Override
+    public ResponseJson<Void> logout(String token) {
+        if (StringUtils.hasLength(token)) {
+            redisService.remove(token);
+        }
+        return ResponseJson.success();
+    }
+
+    @Override
+    public ResponseJson<SysUser> register(SysUser sysUser) {
+        if (StringUtils.hasLength(sysUser.getUsername()) && StringUtils.hasLength(sysUser.getPassword())) {
+            // 密码加密
+            String encodePassword = passwordEncoder.encode(sysUser.getPassword());
+            sysUser.setPassword(encodePassword);
+            // 新增用户
+            sysUserDao.insertSysUser(sysUser);
+            // 角色Ids,用","隔开
+            String roleIds = sysUser.getRoleIds();
+            if (StringUtils.hasLength(roleIds)) {
+                // 设置用户角色
+                String[] split = roleIds.split(",");
+                for (String s : split) {
+                    if (StringUtils.hasLength(s)) {
+                        // 保存用户角色关系
+                        sysUserDao.insertUserRoleRelation(sysUser.getId(), Integer.valueOf(s));
+                    }
+                }
+            }
+            return ResponseJson.success("注册成功", sysUser);
+        }
+        return ResponseJson.error("用户名或密码不能为空", null);
+    }
+
+}

+ 21 - 0
src/main/resources/application.yml

@@ -1 +1,22 @@
+server:
+  port: 18015
+  servlet:
+    encoding:
+      charset: UTF-8
 
+spring:
+  datasource:
+    url: jdbc:mysql://192.168.2.100:3306/security?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
+    username: developer
+    password: 05bZ/OxTB:X+yd%1
+  redis:
+    host: 192.168.2.100
+    port: 6379
+    #password: 123456
+    # Redis?????????0?
+    database: 0
+    # ??????????
+    timeout: 5000
+
+mybatis:
+  mapper-locations: classpath:mapper/*.xml

+ 49 - 0
src/main/resources/mapper/SysUserDao.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.caimei365.manager.dao.SysUserDao">
+    <insert id="insertSysUser" keyProperty="id" keyColumn="id" useGeneratedKeys="true">
+        INSERT INTO sys_user(username, password) VALUES(#{username}, #{password})
+    </insert>
+    <insert id="insertUserRoleRelation">
+        INSERT INTO sys_role_user(user_id, role_id) VALUES(#{userId}, #{roleId})
+    </insert>
+    <select id="findByUsername" resultType="com.caimei365.manager.entity.SysUser">
+        SELECT id,username,PASSWORD,avatar FROM sys_user WHERE username=#{username}
+    </select>
+    <select id="getRoleList" resultType="java.lang.String">
+        SELECT r.role_name FROM sys_role r
+                 LEFT JOIN sys_role_user ru ON r.id = ru.role_id
+        WHERE ru.user_id = #{userId}
+    </select>
+    <select id="getRoleIdsByUserId" resultType="java.lang.Integer">
+        SELECT DISTINCT ru.role_id FROM sys_role_user ru
+        LEFT JOIN sys_user u ON ru.user_id = u.id
+        WHERE u.username=#{username}
+    </select>
+    <select id="getMenusByRoleIds" resultType="com.caimei365.manager.entity.SysMenu">
+        SELECT m.id, m.title , m.name, m.icon, m.level, m.parent_id AS parentId, m.status, m.create_time AS createTime
+        FROM sys_menu m
+                 LEFT JOIN sys_role_menu rm ON m.id = rm.menu_id
+        WHERE m.del_flag = 0 AND m.status = 0 AND rm.role_id IN
+        <foreach item="roleId" collection="roleIds" open="(" separator="," close=")">
+            #{roleId}
+        </foreach>
+    </select>
+    <select id="getMenuList" resultType="com.caimei365.manager.entity.SysMenu">
+        SELECT m.id, m.title , m.name, m.icon, m.level, m.parent_id AS parentId, m.status, m.create_time AS createTime
+        FROM sys_menu m
+        WHERE m.del_flag = 0
+    </select>
+    <select id="getMenu" resultType="com.caimei365.manager.entity.SysMenu">
+        SELECT m.id, m.title , m.name, m.icon, m.level, m.parent_id AS parentId, m.status, m.create_time AS createTime
+        FROM sys_menu m
+        WHERE m.id = #{id}
+    </select>
+    <update id="updateMenu">
+        UPDATE sys_menu SET title=#{title}, name=#{name}, icon=#{icon}, parent_id=#{parentId}, status=#{status}, update_time=NOW()
+        WHERE id = #{id}
+    </update>
+    <update id="deleteMenu">
+        UPDATE sys_menu SET del_flag=1, update_time=NOW() WHERE id = #{id}
+    </update>
+</mapper>