SpringSecurity的权限校验详解说明(附完整代码)

news2024/9/23 11:25:00

说明

SpringSecurity的权限校是基于SpringSecurity的安全认证的详解说明(附完整代码)
(https://blog.csdn.net/qq_51076413/article/details/129102660)的讲解,如果不了解SpringSecurity是怎么认证,请先看下【SpringSecurity的安全认证的详解说明(附完整代码)

基于SpringSecurity的安全认证的详解说明(附完整代码),继续讲解权限的校验




开启权限校验

@EnableGlobalMethodSecurity(prePostEnabled = true)

  • 首先在SecurityConfig类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)注解
  • 表示开启方法的权限校验(默认是关闭的,这里要开启校验)

下面以继承WebSecurityConfigurerAdapter 类配置的方法已过时,可以使用后面的配置

/**
 * @ApiNote: SpringSecurity配置信息
 * @Author: 陌路
 * @Date: 2023/2/18 12:14
 * @Tool: Created by IntelliJ IDEA
 */
//@Configuration
// 开启方法权限校验,默认是关闭的,这里要开启校验
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 注入自定义的过滤器,在用户名和密码认证之前执行(UsernamePasswordAuthenticationFilter之前)
    @Resource
    private TokenAuthorityFilter tokenAuthorityFilter;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;


    /**
     * @apiNote: 注入密码加密工具
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @apiNote: 注入AuthenticationManager对象来实现登录逻辑管理
     */
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * @apiNote: 配置请求认证和拦截
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭Security的CSRF功能防御
        http.csrf().disable()
                // 不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 配置接口权限,getUserList必须具备system:dept:index权限才允许访问,否则不允许访问
                .mvcMatchers("/getUserList").hasAnyAuthority("system:dept:index")
                // 允许所有用户访问登录路径:anonymous(匿名访问,即允许未登录时访问,登录时则不允许访问)
                .antMatchers("/user/login").anonymous()//匿名访问(未登录未认证的)
                // 除以上请求路径外,其他所有请求都必须经过认证才能访问成功
                .anyRequest().authenticated();
        // 添加自定义的请求过滤器(tokenAuthorityFilter)并定义在指定哪个过滤器(UsernamePasswordAuthenticationFilter)执行前执行
        http.addFilterBefore(tokenAuthorityFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加自定义异常处理器
        http.exceptionHandling()
                // 认证失败处理
                .authenticationEntryPoint(authenticationEntryPoint)
                // 授权失败处理
                .accessDeniedHandler(accessDeniedHandler);
        // 允许跨域配置
        http.cors();
    }

    // 测试密码的加密和密码的验证
    public static void main(String[] args) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 加密后的密文,每次加密结果都不一样,因为加密时会生成随机盐值
        String encode = passwordEncoder.encode("123456");
        // 校验用户输入的密码和加密后的密码是否一样,一样返回true,否则返回false
        boolean matches = passwordEncoder.matches("123456", encode);
        System.out.println("encode = " + encode);
        System.out.println("matches = " + matches);
    }
}

上面的配置方式已过时,可以使用下面的配置

/**
 * @ApiNote: SpringSecurity配置信息
 * @Author: 陌路
 * @Date: 2023/2/18 12:14
 * @Tool: Created by IntelliJ IDEA
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Resource
    private TokenAuthorityFilter tokenAuthorityFilter;
    @Resource
    private AuthenticationConfiguration authenticationConfiguration;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * @apiNote: 注入AuthenticationManager对象来实现登录逻辑管理
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }

    /**
     * @apiNote: 注入密码加密工具
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 配置接口权限,getUserList必须具备system:dept:index权限才允许访问,否则不允许访问
                .mvcMatchers("/getUserList").hasAnyAuthority("system:dept:index")
                // 允许所有用户访问登录路径:anonymous(匿名访问,即允许未登录时访问,登录时则不允许访问)
                .antMatchers("/user/login").anonymous()
                // 除以上请求路径外,其他所有请求都必须经过认证才能访问成功
                .anyRequest().authenticated()
                .and()
                // 添加自定义的请求过滤器(tokenAuthorityFilter)并定义在指定哪个过滤器(UsernamePasswordAuthenticationFilter)执行前执行
                .addFilterBefore(tokenAuthorityFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加异常处理器
        http.exceptionHandling()
                // 授权异常处理器
                .accessDeniedHandler(accessDeniedHandler)
                // 认证异常处理器
                .authenticationEntryPoint(authenticationEntryPoint);
        // 运行跨域配置
        http.cors();
        return http.build();
    }
}

定义权限信息

/**
 * @ApiNote: 菜单对象实体类,对应表(sys_menu)
 * @Author: 陌路
 * @Date: 2023/2/19 11:33
 * @Tool: Created by IntelliJ IDEA
 */
@Data
@NoArgsConstructor
@TableName("sys_menu")
@ToString(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Menu implements Serializable {
    private static final long serialVersionUID = -54979041104113736L;
    @TableId
    private Long id;
    private String menuName;//菜单名
    private String path;//路由地址
    private String component;//组件路径
    private String visible;//菜单状态(0显示 1隐藏)
    private String status;//菜单状态(0正常 1停用)
    private String perms;//权限标识
    private String icon; //菜单图标
    private Long createBy;
    private Date createTime;
    private Long updateBy;
    private Date updateTime;
    private Integer delFlag;//是否删除(0未删除 1已删除)
    private String remark;//备注
}

根据用户id获取权限信息

/**
 * @ApiNote: 菜单权限$
 * @Author: 陌路
 * @Date: 2023/2/19 11:35
 * @Tool: Created by IntelliJ IDEA
 */
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {
    /**
     * @apiNote: 根据用户id获取权限
     * @param: userId
     * @return: list
     */
    List<String> queryPermsByUserId(Long userId);
}

根据用户id获取所拥有的权限信息

    <select id="queryPermsByUserId" parameterType="long" resultType="string">
        SELECT DISTINCT M.PERMS
        FROM SYS_USER_ROLE U
                 LEFT JOIN SYS_ROLE R ON U.ROLE_ID = R.ID
                 LEFT JOIN SYS_ROLE_MENU RM ON U.ROLE_ID = RM.ROLE_ID
                 LEFT JOIN SYS_MENU M ON M.ID = RM.MENU_ID
        WHERE USER_ID = #{userId}
          AND R.STATUS = 0
          AND M.STATUS = 0
    </select>
  • 用户登录成功时,根据当前用户信息获取该用户所拥有的的权限,并将权限封装到LoginUser
  • LoginUser中再将权限信息通过SpringSecurity提供的方法(getAuthorities)配置到SpringSecurity管理中
  • menuMapper.queryPermsByUserId(user.getId());根据用户id获取所拥有的权限

配置文件配置MyBatis-Plus的信息

spring:
  # 数据库链接配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/security?characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: SpringSecurity-JWT

 # 热部署
  devtools:
    restart:
      enabled: true
      additional-paths: src/main/java

# 服务端口
server:
  port: 8090

# token有效期为5分钟
token:
  expire: 300000

# 用于生成JWT的盐值
jwt:
  secret: 1234567890

# xml文件路径地址
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
/**
 * @ApiNote: 用户数据认证
 * @Author: 陌路
 * @Date: 2023/2/18 11:34
 * @Tool: Created by IntelliJ IDEA
 */
@Service("userDetailsImpl")
public class UserDetailsImpl implements UserDetailsService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private MenuMapper menuMapper;

    /**
     * @apiNote: 根据用户名获取用户数据
     * @param: username 用户名
     * @return: UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户数据
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName, username));
        ApiUtils.checkParamsIsEmpty("未获取到用户数据,请检查用户名和密码是否正确!", user);
        // 根据用户信息查询相关权限
        List<String> list = menuMapper.queryPermsByUserId(user.getId());
        // 将用户数据和用户权限数据封装到LoginUser中并返回
        return new LoginUser(user, list);
    }
}

将权限封装到LoginUser

将权限信息封装到LoginUser

  • private List<String> permissions; // 用户权限信息
  • private List<GrantedAuthority> authorityList; //全部权限信息对象集合
  • public Collection<? extends GrantedAuthority> getAuthorities() {...} //封装成SpringSecurity需要的格式
  • public LoginUser(User user, List<String> permissions) {...} // 提供有参构造
/**
 * @ApiNote: 封装登录用户数据
 * @Author: 陌路
 * @Date: 2023/2/18 11:55
 * @Tool: Created by IntelliJ IDEA
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LoginUser implements UserDetails {
    // 实现SpringSecurity提供的UserDetails接口来管理用户数据
    private User user;
    private Long expire; // 过期时间
    private String token; // token
    private List<String> permissions; // 用户权限信息
    private List<GrantedAuthority> authorityList;

    /**
     * @apiNote: 获取当前登录用户信息
     */
    public static LoginUser getLoginUser() {
        LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return ApiUtils.getObj(loginUser, new LoginUser());
    }

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    /**
     * @apiNote: 用户权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 把permissions中的String权限封装到SimpleGrantedAuthority对象中(GrantedAuthority的实现类)
        if (ObjUtil.isEmpty(this.authorityList)) {
            this.authorityList = this.permissions
                    .stream()
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
        }
        return this.authorityList;
    }

    /**
     * @apiNote: 获取用户密码
     */
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    /**
     * @apiNote: 获取用户名
     */
    @Override
    public String getUsername() {
        return user.getUserName();
    }

    /**
     * @apiNote: 是否未过期(true : 未过期 , false : 已过期)
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * @apiNote: 是否锁定
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * @apiNote: 是否超时(true : 未超时 , false : 已超时)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * @apiNote: 当前用户是否可用(true:可用,false:不可用)
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

权限校验配置

  • 在自定义的请求过滤器中,配置封装好的权限信息即可
  • new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); // 传入封装好了的权限信息
/**
 * @ApiNote: 请求过滤器:是否认证是否有权访问
 * @Author: 陌路
 * @Date: 2023/2/18 13:04
 * @Tool: Created by IntelliJ IDEA
 */
@Component
public class TokenAuthorityFilter extends OncePerRequestFilter {
    @Resource
    private TokenUtils tokenUtils;
    @Resource
    private ContextLoader contextLoader;

    /**
     * @apiNote: 请求过滤器
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取token数据
        String authorityToken = ApiUtils.getStr(request.getHeader("Authorization"), request.getParameter("Authorization"));
        // token为空直接放行
        if (StringUtils.isBlank(authorityToken)) {
            filterChain.doFilter(request, response);
            return;
        }
        // 解析token数据得到userId
        String userId = tokenUtils.getUserId(authorityToken);
        // 从缓存中获取用户信息
        LoginUser loginUser = contextLoader.getCacheUser(userId + "_TOKEN_" + authorityToken);
        ApiUtils.checkParamsIsEmpty("请求失败,认证已过期!", loginUser, loginUser.getUser());
        // 将用户信息封装到SecurityContextHolder中
        //principal:用户数据,credentials:,authenticated:权限信息(loginUser.getAuthorities())
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);
    }
}

使用权限校验规则

  • 接口校验:在请求接口方法上添加@PreAuthorize("hasAnyAuthority('system:dept:index')")即可
  • 使用SpringSecurity默认配置校验规则
/**
 * @ApiNote: 请求接口控制器
 * @Author: 陌路
 * @Date: 2023/2/18 9:53
 * @Tool: Created by IntelliJ IDEA
 */
@RestController
@RequestMapping("/user/*")
public class IndexController {

    @Resource
    private UserService userService;

    /**
     * @apiNote: 获取用户列表
     * @return: Result
     */
    @GetMapping("getUserList")
    @PreAuthorize("hasAnyAuthority('system:dept:index')") // SpringSecurity校验配置
//    @PreAuthorize("@api.hasAuthority('system:dept:index')") // 自定义校验配置
    public Result getUserList() {
        return Result.ok(userService.queryList());
    }

    /**
     * @apiNote: 用户登录接口
     * @param: User对象实体
     * @return: Result
     */
    @PostMapping("login")
    public Result login(@RequestBody User user) {
        return userService.login(user);
    }

    /**
     * @apiNote: 用户退出登录
     * @return: Result
     */
    @GetMapping("logout")
    public Result logout() {
        return Result.res(userService.logout());
    }
}

SpringSecurity异常处理

  • 认证失败: 实现SpringSecurity提供的AuthenticationEntryPoint接口中的commence方法来处理认证失败后的业务
  • 统一处理:统一返回JSON异常提示信息
  • 在SpringSecurity配置类(SecurityConfiguration)中添加http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);即可
/**
 * @ApiNote: 认证失败处理类
 * @Author: 陌路
 * @Date: 2023/2/19 12:25
 * @Tool: Created by IntelliJ IDEA
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    /**
     * @apiNote: 认证失败处理
     * @return: JSON(认证失败,请重新登录)
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String authExceptionMessage = authException.getMessage();
        authExceptionMessage = StringUtils.isBlank(ApiUtils.getStr(authExceptionMessage)) ? "认证失败,请重新登录!" : authExceptionMessage;
        String jsonStr = JSONUtil.toJsonStr(Result.err(HttpStatus.UNAUTHORIZED.value(), authExceptionMessage));
        ApiUtils.printJsonMsg(jsonStr, response);
    }
}

在这里插入图片描述
授权异常统一处理

  • 授权失败:实现SpringSecurity提供的AccessDeniedHandler接口中的handle方法来处理授权失败业务
  • 统一处理:统一返回JSON异常提示信息
  • 在SpringSecurity配置类(SecurityConfiguration)中添加http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);即可
/**
 * @ApiNote: 授权失败处理类
 * @Author: 陌路
 * @Date: 2023/2/19 12:53
 * @Tool: Created by IntelliJ IDEA
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    /**
     * @apiNote: 授权失败处理
     * @return: JSON(您的权限不足,无法访问资源!)
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        String accessDeniedExceptionMessage = accessDeniedException.getMessage();
        accessDeniedExceptionMessage = StringUtils.isBlank(ApiUtils.getStr(accessDeniedExceptionMessage)) ? "不允许访问" : accessDeniedExceptionMessage;
        accessDeniedExceptionMessage = accessDeniedExceptionMessage.equals("不允许访问") ? "您的权限不足,无法访问资源!" : accessDeniedExceptionMessage;
        String jsonStr = JSONUtil.toJsonStr(Result.err(HttpStatus.FORBIDDEN.value(), accessDeniedExceptionMessage));
        ApiUtils.printJsonMsg(jsonStr, response);
    }
}

在这里插入图片描述

配置自定义校验规则

  • 需要自定义配置权限校验规则的可以按照SpringSecurity中的hasAuthority.()..方法自定义
  • 注意点:定义Bean的时候@Component("api")中一定要添加上值,调用时需要根据@Component中的值来调用当前的校验规则
/**
 * @ApiNote: 自定义权限校验配置$
 * @Author: 陌路
 * @Date: 2023/2/19 14:25
 * @Tool: Created by IntelliJ IDEA
 */
@Component("api")
public class ApiExpressionRoot {
    /**
     * @apiNote: 定义权限校验规则
     * @param: authority:接口方法中的权限码
     * @return: true:有权限允许方法。false:无权限不允许访问
     */
    public final boolean hasAuthority(String authority) {
    	// 获取权限信息列表(TokenAuthorityFilter)已经将数据存到到LoginUser,直接调用即可
        List<String> permissions = LoginUser.getLoginUser().getPermissions();
        if (ObjUtil.isNotEmpty(permissions)) {
            Set<String> set = new HashSet<>(permissions);
            // 判断用户权限中是否包含此权限代码
            return set.contains(authority);
        }
        return false;
    }
}

自定义校验规则的使用

  • 直接在请求接口方法上添加上自定义校验规则方法即可
  • 注意:调用时需要使用SPEL表达式调用,否则不会生效
  • 使用@PreAuthorize("@api.hasAuthority('system:dept:index')")其中@api是自定义配置类ApiExpressionRoot中定义的@Component("api"),这时自定义规则就会生效了
    /**
     * @apiNote: 获取用户列表
     * @return: Result
     */
    @GetMapping("getUserList") 
    //@PreAuthorize("hasAnyAuthority('system:dept:index')") // SpringSecurity校验配置
    @PreAuthorize("@api.hasAuthority('system:dept:index')") // 自定义校验配置
    public Result getUserList() {
        return Result.ok(userService.queryList());
    }

调用成功
在这里插入图片描述

用到的SQL语句脚本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
  `path` varchar(200) DEFAULT NULL COMMENT '路由地址',
  `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
  `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `create_by` bigint NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_by` bigint NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `del_flag` int NULL DEFAULT 0 COMMENT '是否删除(0未删除 1已删除)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '菜单表';

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (2, '部门管理', 'dept', 'system/dept/index', '0', '0', 'system:dept:index', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (3, '测试', 'test', 'system/test/index', '0', '0', 'system:test:index', '#', NULL, NULL, NULL, NULL, 0, NULL);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL,
  `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
  `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `del_flag` int NULL DEFAULT 0 COMMENT 'del_flag',
  `create_by` bigint NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_by` bigint NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COMMENT = '角色表';

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (3, 'CEO', 'ceo', '0', 0, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `sys_role` VALUES (4, 'Coder', 'coder', '0', 0, NULL, NULL, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `menu_id` bigint NOT NULL DEFAULT 0 COMMENT '菜单id',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE,
  INDEX `menu_id`(`menu_id`) USING BTREE
  -- CONSTRAINT `menu_id` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  -- CONSTRAINT `role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (3, 2);
INSERT INTO `sys_role_menu` VALUES (3, 3);
INSERT INTO `sys_role_menu` VALUES (4, 3);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(64) DEFAULT 'NULL' COMMENT '用户名',
  `nick_name` varchar(64) DEFAULT 'NULL' COMMENT '昵称',
  `password` varchar(64) DEFAULT 'NULL' COMMENT '密码',
  `status` char(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
  `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
  `phonenumber` varchar(32) DEFAULT NULL COMMENT '手机号',
  `sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
  `avatar` varchar(128) DEFAULT NULL COMMENT '头像',
  `user_type` char(1) DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
  `create_by` bigint NULL DEFAULT NULL COMMENT '创建人的用户id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint NULL DEFAULT NULL COMMENT '更新人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `del_flag` int NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户表';

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (3, 'molu', 'molu', '$2a$10$1s.BtZ6Ay/nU7VB/cgaTv.PiYezHYWOLntRsUqFgter/hsMDViZ0K', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `role_id` bigint NOT NULL DEFAULT 0 COMMENT '角色id',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE,
  INDEX `rold_id`(`role_id`) USING BTREE
  -- CONSTRAINT `rold_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  -- CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (3, 3);

SET FOREIGN_KEY_CHECKS = 1;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【1792. 最大平均通过率】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 一所学校里有一些班级&#xff0c;每个班级里有一些学生&#xff0c;现在每个班都会进行一场期末考试。给你一个二维数组 classes &#xff0c;其中 classes[i] [passi, totali] &#xff0c;表示你…

0xL4ugh 2023

这回跟着个队伍跑&#xff0c;不过还是2X以后的成绩&#xff0c;前边太卷了。自己会的部分&#xff0c;有些是别人已经提交了的。记录一下。Cryptocrypto 1给了一些数据&#xff0c;像这样就没有别的了ct [0, 1, 1, 2, 5, 10, 20, 40, 79, 159, 317, 635, 1269, 2538, 5077, 1…

2023.02.19 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.本文贡献5.方法5.1 Local Representation Learning5.2 Global Representation Learning5.3 Item Similarity Gating6.实验6.1 数据集6.2 结果7.结论深度学习1.对偶问题1.1 拉格朗日乘数法1.2 强对偶性2.SVM优化3.软间隔3.1 解决问题3.…

尚医通 (十八)微信登录

目录一、生成微信登录二维码1、准备工作2、后端开发service_user3、前端显示登录二维码4、二维码出现不了进行调试二、开发微信扫描回调1、准备工作2、后台开发3、前台开发三、分析代码四、bug一、生成微信登录二维码 1、准备工作 1、注册 2、邮箱激活 3、完善开发者资料 4、…

JSP中http与内置对象学习笔记

本博文讲述jsp客户端与服务器端的http、jsp内置对象与控制流和数据流实现 1.HTTP请求响应机制 HTTP协议是TCP/IP协议中的一个应用层协议&#xff0c;用于定义客户端与服务器之间交换数据的过程 1.1 HTTP请求 HTTP请求由请求行、消息报头、空行和请求数据4部分组成。 请求行…

ThreeJS 之界面控制

文章目录参考描述界面自适应问题resize 事件修改画布大小修改视锥体的宽高比全屏显示dblclick 事件检测全屏显示状态进入全屏显示状态退出全屏显示状态尾声参考 项目描述ThreeJS官方文档哔哩哔哩老陈打码搜索引擎BingMDN 文档document.mozFullScreenElementMDN 文档Element.re…

LeetCode题目笔记——6359. 替换一个数字后的最大差值

文章目录题目描述题目链接题目难度——简单方法一&#xff1a;替换代码/Python代码优化总结题目描述 给你一个整数 num 。你知道 Danny Mittal 会偷偷将 0 到 9 中的一个数字 替换 成另一个数字。 请你返回将 num 中 恰好一个 数字进行替换后&#xff0c;得到的最大值和最小值…

CTK学习:(一)编译CTK

CTK插件框架简介 CTK Plugin Framework是用于C++的动态组件系统,以OSGi规范为模型。在此框架下,应用程序由不同的组件组成,遵循面向服务的方法。 ctk是一个开源项目,Github 地址:https://github.com/commontk。 源码地址commontk/CTK: A set of common support code for…

信小程序点击按钮绘制定制转发分享图

1. 说明 先上代码片断分享链接&#xff1a; https://developers.weixin.qq.com/s/vl3ws9mA72GG 使用 painter 画图 按钮传递定制化信息 效果如下&#xff1a; 2. 关键代码说明 文件列表如下&#xff1a; {"usingComponents": {"painter": "/com…

基于springboot的停车场管理系统(程序+文档)

大家好✌&#xff01;我是CZ淡陌。将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。 &#x1f345;更多优质项目&#x1f447;&#x1f…

Android实例仿真之二

目录 三 从无入手 第一阶段 第二阶段 第三阶段 第四阶段 第五阶段 第六阶段 第七阶段 八 举两个典型例子&#xff1a; 九 逆向工程 三 从无入手 这节标题叫从无入手&#xff0c;什么意思呢&#xff1f;如果没有Android这个实例存在&#xff0c;你要做一个类似Android…

Mysql数据库事务

数据库事务 数据库事务由一组sql语句组成。 所有sql语句执行成功则事务整体成功&#xff1b;任一条sql语句失败则事务整体失败&#xff0c;数据恢复到事务之前的状态。 Mysql 事务操作 开始事务 start transaction;- 或 begin;事务开始后&#xff0c;对数据的增删改操作不…

MySQL最佳实践

一、MySQL查询执行过程 1.MySQL分层结构 MySQL8.0没有查询缓存的功能了,如果频繁修改缓存,将会损耗性能查询流程就按照分层结构就可以清楚,只要了解各个组件的各自功能就行分析器主要分析语法和词法是否正确优化器主要优化SQL语句 二、MySQL更新执行过程 更新主要涉及两个重…

SpringCloud - Ribbon负载均衡

目录 负载均衡流程 负载均衡策略 Ribbon加载策略 负载均衡流程 Ribbon将http://userservice/user/1请求拦截下来&#xff0c;帮忙找到真实地址http://localhost:8081LoadBalancerInterceptor类对RestTemplate的请求进行拦截&#xff0c;然后从Eureka根据服务id获取服务列表&…

正点原子ARM裸机开发篇

裸机就是手动的操作硬件来实现驱动设备&#xff0c;后面会有驱动框架不需要这么麻烦 第八章 汇编 LED 灯实验 核心过程 通过汇编语言来控制硬件&#xff08;驱动程序&#xff09; 代码流程 1、使能 GPIO1 时钟 GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制&…

SSL/STL是什么?怎么工作的?Keystore 和 Truststore是什么?

安全套接字层&#xff08;Secure Sockets Layer&#xff09;&#xff0c;也称为 SSL&#xff0c;是一种加密协议(encryption protocol)&#xff0c;可在 Internet 上的设备之间创建身份验证的通道(authenticated channel)&#xff0c;以便可以安全地共享信息。本质上&#xff0…

动态规划专题精讲1

致前行的人&#xff1a; 要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 前言&#xff1a; 本篇文章为大家带来一种重要的算法题&#xff0c;就是动态规划类型相关的题目&#xff0c;动态规划类的题目在笔试和面试中是考察非常高…

【日常点滴019】Python制作流浪气球游戏(导弹射击类)

Python制作流浪气球游戏&#xff08;导弹射击类&#xff09;教学课程代码&#xff08;分步教学版&#xff09;1、构建全局通用代码结构2、构建气球精灵类3、构建导弹精灵类4、碰撞检测5、构建游戏信息类 &#xff08;最终完整代码&#xff09;教学课程代码&#xff08;分步教学…

基于springboot+vue的食疗系统

基于springbootvue的食疗系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&…

java面试题-并发基础

1.多线程的出现是要解决什么问题的? 本质什么?提高程序性能&#xff1a;单线程程序只能按照固定的顺序依次执行每个任务&#xff0c;无法同时处理多个任务。多线程技术可以在同一时间内执行多个任务&#xff0c;从而提高程序的运行效率和响应速度。提高程序的并发性&#xff…