SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题

Java21 + SpringBoot3整合springdoc-openapi,自动生成在线接口文档,支持SpringSecurity和JWT认证方式 近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21和开发,后端使用JWT等技术栈,前端提供了vueangularreactuniapp微信小程序等多种脚手架工程。本文主要介绍在项目中如何整合实现自动生成在线接口文档,JDK版本是Java21。OpenAPI 规范(OAS),是定义一个标准的、与具体编程语言无关的RESTful API的规范。 阅读详情
因为篇幅原因请先参考这篇文章实现登录功能https://blog.youkuaiyun.com/grd_java/article/details/121925792
源码,码云 https://gitee.com/yin_zhipeng/spring-security-scaffolding

一、 数据库表,和实体类

因为前端是VUE写到,所以需要保存组件、路径等

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、菜单管理TODO

最终结果
在这里插入图片描述
在这里插入图片描述

1. 实体类

通过Mybatis-plus插件代码生成器,自动生成,然后添加一个字段children
在这里插入图片描述

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import java.util.List;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdMenu对象", description="")
public class DdMenu implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "父菜单id")
    private Integer pid;

    @ApiModelProperty(value = "菜单名")
    private String name;

    @ApiModelProperty(value = "菜单类型(目录,按钮)")
    private String type;

    @ApiModelProperty(value = "菜单权限")
    private String permission;

    @ApiModelProperty(value = "url")
    private String url;

    @ApiModelProperty(value = "path")
    private String path;

    @ApiModelProperty(value = "组件")
    private String component;

    @ApiModelProperty(value = "图标")
    @TableField("iconCls")
    private String iconCls;

    @ApiModelProperty(value = "是否保持激活")
    @TableField("keepAlive")
    private Boolean keepAlive;

    @ApiModelProperty(value = "是否要求权限")
    @TableField("requireAuth")
    private Boolean requireAuth;

    @ApiModelProperty(value = "是否启用")
    private Boolean enabled;

    @ApiModelProperty(value = "子菜单")
    @TableField(exist = false)//告诉Mybatis,表中没有这个字段,否则操作时会去数据库找这个字段
    private List<DdMenu> children;
}

2. Redis 缓存

  1. 启动redis
    在这里插入图片描述
  2. 添加依赖
    在这里插入图片描述
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pool2 对象池依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  1. redis配置
    在这里插入图片描述
spring:
  redis: #redis配置
    host: 127.0.0.1 #你的redis地址
    port: 6379 #端口号
    database: 0
    timeout: 1800000
    lettuce:
      pool:
        max-active: 1024 # 最大连接数
        max-wait: -1 #最大阻塞等待时间(负数表示没限制)
        max-idle: 200 #最大空闲连接
        min-idle: 5 #最小空闲连接
  1. Redis配置类
    在这里插入图片描述
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置类
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //String 类型 key序列器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //String 类型 value序列器
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Hash 类型 key序列器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //Hash 类型 value序列器
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        //配置工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

3. 编写逻辑,查询菜单

  1. controller
    在这里插入图片描述
import com.dd.security.entity.DdMenu;
import com.dd.security.service.DdMenuService;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@RestController
@RequestMapping("/security/dd-menu")
public class DdMenuController {
    @Autowired
    private DdMenuService ddMenuService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<DdMenu> getMenusByUserId(){
        return ddMenuService.getMenusByUserId();
    }
}
  1. service
    在这里插入图片描述
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.mapper.DdMenuMapper;
import com.dd.security.service.DdMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.awt.*;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Service
public class DdMenuServiceImpl extends ServiceImpl<DdMenuMapper, DdMenu> implements DdMenuService {

    @Autowired
    private DdMenuMapper ddMenuMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据用户id获取菜单
     */
    @Override
    public List<DdMenu> getMenusByUserId() {
        //Security全局上下文获取UserDetails对象
        DdUser user = (DdUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //获取redis对象
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //先从redis中查询
        List<DdMenu> menus = (List<DdMenu>) valueOperations.get("menu_" + user.getId());
        //如果redis中没有,查询mysql数据库
        if(CollectionUtils.isEmpty(menus)){
            menus = ddMenuMapper.getMenusByUserId(user.getId());
            //将查询出来的内容放在reids中
            valueOperations.set("menu_" + user.getId(),menus);
        }
        //返回结果
        return menus;
    }
}

  1. mapper
    在这里插入图片描述
select
	distinct
	m1.*,
	m2.id as id2,
	m2.pid as pid2,
	m2.`name` as name2,
	m2.type as type2,
	m2.permission as permission2,
	m2.url as url2,
	m2.path as path2,
	m2.component as component2,
	m2.iconCls as iconCls2,
	m2.keepAlive as keepAlive2,
	m2.requireAuth as requireAuth,
	m2.enabled as enabled2
from
	dd_menu as m1 
inner join
	dd_menu as m2 #自关联
on
	m1.id = m2.pid
inner join
	dd_role_menu as rm
on
	rm.mid = m2.id
inner join
	dd_user_role as ur
on
	ur.rid = rm.rid
where
	ur.uid = 1

在这里插入图片描述
在这里插入图片描述

<?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.dd.security.mapper.DdMenuMapper">

    <!-- DdMenu映射对象-->
    <resultMap id="BaseResultMap" type="com.dd.security.entity.DdMenu">
        <id column="id" property="id" />
        <result column="pid" property="pid" />
        <result column="name" property="name" />
        <result column="type" property="type" />
        <result column="permission" property="permission" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="enabled" property="enabled" />
    </resultMap>
    <!--子菜单,继承上面DdMenu映射对象-->
    <resultMap id="Menus" type="com.dd.security.entity.DdMenu" extends="BaseResultMap">
        <collection property="children" ofType="com.dd.security.entity.DdMenu">
            <id column="id2" property="id" />
            <result column="pid2" property="pid" />
            <result column="name2" property="name" />
            <result column="type2" property="type" />
            <result column="permission2" property="permission" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <select id="getMenusByUserId" resultMap="Menus">
        select
            distinct
            m1.*,
            m2.id as id2,
            m2.pid as pid2,
            m2.`name` as name2,
            m2.type as type2,
            m2.permission as permission2,
            m2.url as url2,
            m2.path as path2,
            m2.component as component2,
            m2.iconCls as iconCls2,
            m2.keepAlive as keepAlive2,
            m2.requireAuth as requireAuth,
            m2.enabled as enabled2
        from
            dd_menu as m1
                inner join
            dd_menu as m2
            on
                m1.id = m2.pid
                inner join
            dd_role_menu as rm
            on
                rm.mid = m2.id
                inner join
            dd_user_role as ur
            on
                ur.rid = rm.rid
        where
            ur.uid = #{id}
    </select>
</mapper>

三、url角色权限

我们如果只控制菜单,用户登录后进入管理系统,只能看到自己角色权限对应的菜单,但是如果这个用户此时通过某种手段获取了我们后台的接口对应的url,那么他就算前端看不到相应的菜单和按钮,无法通过事件访问后端接口。也可以直接通过浏览器地址栏输入url,因为它已经登录过,这时,就可以直接访问url了,尽管我们不想让他具有访问这些接口的权限,让他看不见相应的菜单和按钮,此时也只能眼睁睁看着他获取数据
如何处理这种情况呢,我们的菜单表有一个url字段,记录了url地址
  1. 我们在菜单实体类中,添加访问此url和菜单,需要的角色、权限属性
  2. 规定,菜单表中的url都需要特定的角色和权限才能访问,取余的url,可以允许没有角色或拥有LOGIN_ROLE默认角色的用户访问。
  3. 当用户访问后端时,先过滤请求,判断用户请求的是否是菜单表的url,如果是,判断是否具有相应权限,没有权限拦截请求,返回权限不足。如果不是菜单表url,为其添加默认角色LOGIN_ROLE角色
最终效果

在这里插入图片描述
在这里插入图片描述
访问需要ROLE_admin角色的路径可以正常访问
在这里插入图片描述
访问需要其它角色的url提示权限不足
在这里插入图片描述
访问不需要授权的路径,不会触发过滤,不会拦截判断url需要的权限
在这里插入图片描述

1. url权限,在全局中设置用户访问的url,需要哪些权限

1. 实现获取每个菜单所需角色权限

实现根据角色获取菜单列表,就是每个菜单有哪些角色有权限

在这里插入图片描述

  1. 修改菜单实体类,添加角色属性
    在这里插入图片描述
  2. service接口
    在这里插入图片描述
    在这里插入图片描述
  3. mapper
    在这里插入图片描述
    在这里插入图片描述
<!--子菜单,继承上面DdMenu映射对象-->
<resultMap id="MenusWithRoler" type="com.dd.security.entity.DdMenu" extends="BaseResultMap">
    <collection property="roles" ofType="com.dd.security.entity.DdRole">
        <id column="rid" property="id" />
        <result column="rname" property="name" />
        <result column="rnameZh" property="nameZH" />
    </collection>
</resultMap>
<select id="getMenusWithRole" resultMap="MenusWithRoler">
    select
        m.*,
        r.id as rid,
        r.`name` as rname,
        r.name_zh as rnameZh
    from
        dd_menu as m
    inner join
        dd_role_menu as rm
    on
        m.id = rm.mid
    inner join
        dd_role as r
    on
        r.id = rm.rid
    order by m.id
</select>

2. SpringSecurity 过滤器,拦截请求,获取url,判断url需要哪些角色权限

截图中忘了加@Component注解
在这里插入图片描述

import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRole;
import com.dd.security.service.DdMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * 权限控制
 * 根据请求url分析所需角色
 */
 @Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private DdMenuService ddMenuService;


    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取FilterInvocation对象
        FilterInvocation filterInvocation = (FilterInvocation) o;
        //获取请求url
        String requestUrl = filterInvocation.getRequestUrl();
        //获取到每个菜单的对应角色,就是这个url,哪些角色有权限
        List<DdMenu> menusWithRole = ddMenuService.getMenusWithRole();
        //遍历菜单
        for (DdMenu m : menusWithRole){
            //如果用户请求url和菜单中url匹配
            if(antPathMatcher.match(m.getUrl(),requestUrl)){
                //流式编程+lambda表达式:类名::方法名 方法引用,DdRole::getName 表示引用DdRole类的getName方法
                //类名::new 构造方法引用,String[]::new 表示引用String[]的构造方法,构造一个数组
                //获取角色列表,也就是说,用户请求的url,需要具备以下角色
                String[] roles = m.getRoles().stream().map(DdRole::getName).toArray(String[]::new);

                //org.springframework.security.access.SecurityConfig;
                //将我们角色放到Security中
                return SecurityConfig.createList(roles);
            }
        }
        //如果用户请求url不是菜单中url,给予默认角色ROLE_LOGIN(登录角色),也就是必须拥有登录角色才能访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

2. 获取用户角色,判断用户角色权限能否访问url

1. 实现获取角色

  1. 实体类
    在这里插入图片描述
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdUser对象,使用Spring Security框架就要继承UserDetails接口,实现方法,将返回值改为true", description="")
public class DdUser implements UserDetails {
    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private Integer id;
    private String username;
    private String password;

    @ApiModelProperty(value = "用户角色")
    @TableField(exist = false)
    private List<DdRole> roles;
    /**
     * 所有权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        List<SimpleGrantedAuthority> authorities =
                roles.stream()
                        //将每一个角色,遍历成Security指定的权限字符对象,
                        // 比如ROLE_admin要封装成new SimpleGrantedAuthority("ROLE_admin")
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());//然后返回为list
        return authorities;//将角色权限返回
    }

    /**
     * 账号是否过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账号是否被锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 凭证(密码)是否过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否启用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}
  1. service
    在这里插入图片描述
    在这里插入图片描述
  2. mapper
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
<!--/**
 * 根据用户id获取用户拥有角色
 */-->
<select id="getRolesByUserId" resultType="com.dd.security.entity.DdRole" parameterType="java.lang.Integer">
    select
        r.id as id,
        r.name as name,
        r.name_zh as nameZh
    from
        dd_role as r
            inner join
        dd_user_role as ur
        on
            r.id = ur.rid
    where
        ur.uid = #{userId}
</select>
  1. 修改获取用户信息
    在这里插入图片描述
  2. 修改自定义UserDetailsService
    在这里插入图片描述
 /**
  * 配置UserDetailsService
  */
 @Bean
 @Override
 public UserDetailsService userDetailsService() {
     return username->{
         DdUser user = ddUserService.getLoginInfoByUsername(username);
         if(user == null){
             throw new UsernameNotFoundException("用户名或密码不正确");
         }
         List<DdRole> rolesByUserId = ddRoleService.getRolesByUserId(user.getId());
         user.setRoles(rolesByUserId);
         return user;
     };
 }

2. Security 过滤器,判断用户角色是否可以访问

截图中忘了加@Component注解
在这里插入图片描述

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * 权限控制
 * 判断用户角色
 */
import java.util.Collection;
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    /**
     *
     * @param authentication 当前访问用户
     * @param o
     * @param collection Collection<ConfigAttribute> 我们在上一个过滤器CustomFilter implements FilterInvocationSecurityMetadataSource中配置了
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //遍历ConfigAttribute
        for(ConfigAttribute configAttribute : collection){
            //获取当前访问url,需要的角色权限,这些值在再CustomFilter中设置进去的
            String needRole = configAttribute.getAttribute();
            //判断url是否登录即可访问,再CustomFilter 中设置
            if("ROLE_LOGIN".equals(needRole)){
                //如果当前用户是匿名用户(未登录用户),抛异常,让用户登录
                if(authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("尚未登录,请登录!!!");
                }else{
                    return;
                }
            }
            //如果当前url,不是登录就可以访问的
            //判断当前用户的GrantedAuthority里面有没有需要的角色,如果有就放行
            //如果用户没有相应角色,就不放行,同时:没登录的用户因为没有角色,也不会被放行
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for(GrantedAuthority authority:authorities){
                if(authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员!!!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

3. 配置SpringSecurity,让过滤器生效

说明:这些过滤器不会过滤在Security配置类中放行的路径

在这里插入图片描述

  1. 引入两个过滤器
    在这里插入图片描述
  2. 动态权限配置两个过滤器
    在这里插入图片描述
@Override
protected void configure(HttpSecurity http) throws Exception {

    //使用JWT不需要csrg
    http.csrf().disable()
            //使用Token,不需要session
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()//接下来配置授权
            .authorizeRequests()
            //下面授权在configure(WebSecurity web)方法中配置了,这里不需要了
//                .antMatchers("/login","/logout").permitAll()//允许访问/login,/logout的请求无需认证即可通行
            .anyRequest().authenticated()//除了上面配置的,剩下的请求全部拦截,必须认证通过才能访问
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {//动态权限配置
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setAccessDecisionManager(customUrlDecisionManager);//判断用户角色是否可以访问url过滤器
                    o.setSecurityMetadataSource(customFilter);//根据请求url分析所需角色过滤器
                    return o;
                }
            })
            .and()//接下来配置缓存
            .headers()
            .cacheControl()
            ;
    //添加JWT登录授权过滤拦截器
    http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    //添加自定义未授权,和未登录结果返回,前后端分离,需要返回状态码
    http.exceptionHandling()
            .accessDeniedHandler(restAccessDeniedHandler)
            .authenticationEntryPoint(restAuthorizationEntryPoint);

}

4. 测试

在这里插入图片描述
在这里插入图片描述

四、权限组功能实现

1. 角色

SpringSecurity 的角色必须带有ROLE_前缀,否则不会被SpringSecurity捕获

正确的角色名:ROLE_admin、ROLE_adsfasdf
错误的角色名:r_admin、admin、ROLE_

  1. 所以当我们添加角色时,要判断用户是否以ROLE_开头,不是就补充上再执行添加逻辑,是就直接执行添加逻辑
  1. Controller
    在这里插入图片描述
import com.dd.common_utils.Result;
import com.dd.security.entity.DdRole;
import com.dd.security.service.DdRoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/security/dd-role")
public class DdRoleController {
    @Autowired
    private DdRoleService ddRoleService;

    @ApiOperation("获取所有角色")
    @GetMapping("/")
    public Result getAllRoles(){
        List<DdRole> list = ddRoleService.list();
        if(list.isEmpty()){
            return Result.error().message("没有获取的任何角色信息!!!");
        }
        return Result.ok().data("RoleAllList",list);
    }
    @ApiOperation("添加角色")
    @PostMapping("/")
    public Result addRole(@RequestBody DdRole ddRole){
        //如果角色名不是ROLE_打头,就补充上
        if(!ddRole.getName().startsWith("ROLE_")){
            ddRole.setName("ROLE_"+ddRole.getName());
        }
        if(ddRoleService.save(ddRole)){
            return Result.ok().message("添加成功");
        }
        return Result.error().message("添加失败");
    }

    @ApiOperation("删除角色")
    @DeleteMapping("/role/{id}")
    public Result deleteRole(@PathVariable(value = "id",name = "id") Integer id){
        if(ddRoleService.removeById(id)){
            return Result.ok().message("删除成功");
        }
        return Result.error().message("删除失败");
    }
}

2. 菜单

  1. controller
    在这里插入图片描述
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRoleMenu;
import com.dd.security.service.DdMenuService;
import com.dd.security.service.DdRoleMenuService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@RestController
@RequestMapping("/security/dd-menu")
public class DdMenuController {
    @Autowired
    private DdMenuService ddMenuService;
    @Autowired
    private DdRoleMenuService ddRoleMenuService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<DdMenu> getMenusByUserId(){
        return ddMenuService.getMenusByUserId();
    }

    @ApiOperation(value = "查询所有菜单")
    @GetMapping("/menus")
    public Result getMenus(){
        List<DdMenu> list = ddMenuService.list();
        if(list.isEmpty()){
            return Result.error().message("没有菜单!!");
        }
        return Result.ok().data("menuAllList",list);
    }

    @ApiOperation(value = "根据角色id查询菜单id")
    @GetMapping("/menuIdByRoleId/{rid}")
    public Result getMenuIdByRoleId(@PathVariable Integer rid){
        List<Integer> mids = ddRoleMenuService.list(new QueryWrapper<DdRoleMenu>().eq("rid", rid))
                .stream().map(DdRoleMenu::getMid).collect(Collectors.toList());
        if(mids.isEmpty()){
            return Result.error().message("获取菜单失败");
        }
        return Result.ok().data("menuId",mids);
    }

    @ApiOperation(value = "根据角色id查询菜单")
    @GetMapping("/menuByRoleId/{rid}")
    public Result getMenuByRoleId(@PathVariable Integer rid){
        List<DdMenu> list = ddMenuService.getMenuByRoleId(rid);
        if(list.isEmpty()){
            return Result.error().message("没有菜单!!");
        }
        return Result.ok().data("menuList",list);
    }

    @ApiOperation(value = "更新角色菜单")
    @PutMapping("/")
    public Result updateMenuRole(Integer rid,Integer[] mids){
        return ddMenuService.updateMenuRole(rid,mids);
    }
}
  1. service
    在这里插入图片描述
    在这里插入图片描述
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRoleMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.mapper.DdMenuMapper;
import com.dd.security.mapper.DdRoleMenuMapper;
import com.dd.security.service.DdMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dd.security.service.DdRoleMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.awt.*;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Service
public class DdMenuServiceImpl extends ServiceImpl<DdMenuMapper, DdMenu> implements DdMenuService {

    @Autowired
    private DdMenuMapper ddMenuMapper;
    @Autowired
    private DdRoleMenuMapper ddRoleMenuMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据用户id获取菜单
     */
    @Override
    public List<DdMenu> getMenusByUserId() {
        //Security全局上下文获取UserDetails对象
        DdUser user = (DdUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //获取redis对象
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //先从redis中查询
        List<DdMenu> menus = (List<DdMenu>) valueOperations.get("menu_" + user.getId());
        //如果redis中没有,查询mysql数据库
        if(CollectionUtils.isEmpty(menus)){
            menus = ddMenuMapper.getMenusByUserId(user.getId());
            //将查询出来的内容放在reids中
            valueOperations.set("menu_" + user.getId(),menus);
        }
        //返回结果
        return menus;
    }

    /**
     * 根据角色获取菜单列表
     */
    @Override
    public List<DdMenu> getMenusWithRole() {
        return ddMenuMapper.getMenusWithRole();
    }

    @Override
    public List<DdMenu> getMenuByRoleId(Integer rid) {
        return ddMenuMapper.getMenuByRoleId(rid);
    }

    /**
     * 更新角色菜单
     * 先将当前角色所有菜单都删除,然后再更新
     * @param rid 角色id
     * @param mids 要更新的菜单id
     */
    @Override
    @Transactional//自己写更新的操作,一定要加事务
    public Result updateMenuRole(Integer rid, Integer[] mids) {
        //先全删了,否则得一次次判断,太费资源
        ddRoleMenuMapper.delete(new QueryWrapper<DdRoleMenu>().eq("rid", rid));
        //要更新的菜单id为空,则直接返回
        if(null == mids||mids.length == 0){
            return Result.ok().message("更新菜单成功");
        }
        //如果传了菜单id过来,则更新
        ddMenuMapper.insertRecord(rid,mids);
        return null;
    }
}
  1. mapper
    在这里插入图片描述
    在这里插入图片描述
    <!--更新角色菜单-->
    <insert id="insertRecord">
        insert into dd_role_menu(rid,mid) values
        <foreach collection="mids" item="mid" separator=",">
            (#{rid},#{mid})
        </foreach>
    </insert>

3. 用户

  1. controller
    在这里插入图片描述
    在这里插入图片描述
import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author testjava
 * @since 2021-12-13
 */
@RestController
@Api("传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON")
@RequestMapping("/security/dd-user-role")
public class DdUserRoleController {
    @Autowired
    private DdUserService ddUserService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @ApiOperation("获取所有操作员以及它拥有的角色和权限,传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON")
    @PostMapping("/")
    public Result getAllUser(@RequestBody DdUser ddUser){
        List<DdUser> list = ddUserService.getAllUser(ddUser);
        if(list.isEmpty()){
           return Result.error().message("没有获取的用户信息!!!");
        }
        return Result.ok().data("allUserList",list);
    }

    @ApiOperation("更新用户")
    @PutMapping("/")
    public Result updateUser(@RequestBody DdUser ddUser){
        ddUser.setPassword(null);//不允许直接改密码

        if(ddUserService.updateById(ddUser)){
          return Result.ok().message("修改用户成功!!!");
        }
        return Result.error().message("修改失败!!!");
    }

    @ApiOperation("更新用户拥有的角色")
    @PutMapping("/updateUserRole")
    public Result updateUserRole(Integer uid,Integer[] rids){
        return ddUserService.updateUserRole(uid,rids);
    }

    @ApiOperation("修改密码")
    @PutMapping("/updatePasswordById/{id}/{password}")
    public Result updatePassword(@PathVariable Integer id, @PathVariable String password){
        String encode = passwordEncoder.encode(password);
        DdUser ddUser = new DdUser();
        ddUser.setId(id);
        ddUser.setPassword(encode);
        if(ddUserService.updateById(ddUser)){
           return Result.ok().message("修改密码成功!!!");
        }
        return Result.error().message("修改密码失败!!!");
    }

    @ApiOperation("删除用户")
    @DeleteMapping("/{id}")
    public Result deleteById(@PathVariable Integer id){
        if(ddUserService.removeById(id)){
            return Result.ok().message("删除成功!!!");
        }
        return Result.error().message("删除失败!!!");
    }

}
  1. service
    在这里插入图片描述
    在这里插入图片描述
@Autowired
private DdUserRoleMapper ddUserRoleMapper;
/**
 * 获取所有操作员
 */
@Override
public List<DdUser> getAllUser(DdUser ddUser) {

    return ddUserMapper.getAllUser(ddUser);
}

/**
 * 更新用户拥有的角色
 * @param uid 用户id
 * @param rids 用户要修改的角色id
 */
@Override
@Transactional//自己写更新的操作,一定要加事务
public Result updateUserRole(Integer uid, Integer[] rids) {
    //先全删了,否则得一次次判断,太费资源
    ddUserRoleMapper.delete(new QueryWrapper<DdUserRole>().eq("uid", uid));
    //要更新的菜单id为空,则直接返回
    if(null == rids||rids.length == 0){
        return Result.ok().message("更新用户角色成功");
    }
    //如果传了菜单id过来,则更新
    ddUserMapper.insertRecord(uid,rids);
    return Result.ok().message("更新菜单成功");
}
  1. mapper
    在这里插入图片描述
    在这里插入图片描述
<?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.dd.security.mapper.DdUserMapper">

    <!--结果集封装-->
    <resultMap type="com.dd.security.entity.DdUser" id="DdUserResult">
        <result property="id"    column="id"    />
        <result property="username"    column="username"    />
        <result property="password"    column="password"    />
    </resultMap>

    <resultMap id="DdUserRoleResult" type="com.dd.security.entity.DdUser" extends="DdUserResult">
        <collection property="roles" ofType="com.dd.security.entity.DdRole">
            <id column="rid" property="id" />
            <result column="rname" property="name" />
            <result column="rnameZh" property="nameZh" />
        </collection>
    </resultMap>

    <select id="selectByUsername" parameterType="java.lang.String" resultType="com.dd.security.entity.DdUser">
        select id,username,password from dd_user where username=#{username}
    </select>

    <select id="getAllUser" resultMap="DdUserRoleResult" parameterType="com.dd.security.entity.DdUser">
        select
        u.id as id,
            u.username as username,
            u.`password` as `password`,
            r.id as rid,
            r.`name` as rname,
            r.name_zh as rnameZh
        from
            dd_user as u
        inner join
            dd_user_role as ur
        on
            u.id = ur.uid
        inner join
            dd_role as r
        on
            ur.rid = r.id
        <where>
            <trim>
                <if test="id != null  and id != '' and id!=0">and u.id = #{id}</if>
                <if test="username != null  and username != ''">and u.username like concat('%', #{username}, '%')</if>
                <if test="roles != null">and r.name_zh like concat('%', #{roles[0].nameZh}, '%')</if>
            </trim>
        </where>
    </select>
    <!--更新用户角色-->
    <insert id="insertRecord">
        insert into dd_user_role(uid,rid) values
        <foreach collection="rids" item="rid" separator=",">
            (#{uid},#{rid})
        </foreach>
    </insert>
</mapper>

4. 主要功能测试

  1. 菜单
    在这里插入图片描述
    在这里插入图片描述

五、 使用redis存储验证码

存储

在这里插入图片描述

        //获取用户ip作为键值
        String remoteAddr = request.getRemoteAddr();
        //获取redis对象
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //设置验证码到redis,时限60秒,key为ip地址+"captcha"
        valueOperations.set(remoteAddr+"captcha",code,60, TimeUnit.SECONDS);
登录时获取

在这里插入图片描述

        ValueOperations<String,Object> valueOperations = redisTemplate.opsForValue();
        String remoteAddr = request.getRemoteAddr();
        String captcha = (String)valueOperations.get(remoteAddr + "captcha");

六、使用分页查询和添加操作

一些小伙伴私信我,没做分页,没做添加,我这里提供整合分页的方法,大家自己写个controller接口,照猫画虎吧

1. 分页

  1. 集成PageHelper插件
  1. 引入依赖
    在这里插入图片描述
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>
  1. yaml配置文件配置插件
    在这里插入图片描述
# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
接下来一点很重要,保证你的实体类中,没有直接实例化的容器,否则当容器没有内容时,会报空指针异常
  1. 下面roles是一个集合,并在实例化时,进行集合注入,此时一旦roles为null,pageHeader依然执行方法,进行对集合的操作,此时就会报空指针异常。所以我们加if判断
    在这里插入图片描述
  2. 另外,这个玩意遍历拼接的是role的name属性,所以最好对name也做判断
    在这里插入图片描述
整合完成以后,我们就可以使用了,接下来提供新接口使用分页插件实现分页

在这里插入图片描述

    @ApiOperation("分页获取所有操作员以及它拥有的角色和权限,传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON")
    @PostMapping("/page/{current}/{size}")
    public Result getAllUser(@RequestBody DdUser ddUser,@PathVariable Integer current,@PathVariable Integer size){
        Page<Object> objects = PageHelper.startPage(current, size);

        List<DdUser> list = ddUserService.getAllUser(ddUser);
        if(list.isEmpty()){
            return Result.error().message("没有获取的用户信息!!!");
        }
        System.out.println("当前页码(从1开始的)"+objects.getPageNum());
        System.out.println("总页数"+objects.getPages());
        System.out.println("共有多少条数据"+objects.getTotal());
        return Result.ok()
                .data("allUserListPage",objects)
                .data("pageNum",objects.getPageNum())
                .data("pages",objects.getPages())
                .data("total",objects.getTotal());
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

现在,只有你想分页,就按照上面的步骤,提供新接口,然后在要执行sql的地方上面,添加代码PageHelper.startPage(current,size);即可
  1. 注意,它只对此行代码下面的第一条执行的sql进行分页

2. 添加操作

  1. 我们要生成随机id,可以借助一个工具hutool-all,我们引入maven依赖
    在这里插入图片描述
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>
  1. 为了方便我将,我将数据库和java实体类的id都换成了字符串型,你可能需要改一些地方,mapper.xml文件中也可能需要修改
    在这里插入图片描述
  2. controller,使用工具类生成随机id,注意密码需要加密
    在这里插入图片描述
    @ApiOperation("添加用户")
    @PostMapping("/add")
    public Result addUser (@RequestBody DdUser ddUser){
        Snowflake snowflake = IdUtil.getSnowflake(0,0);
        ddUser.setId(snowflake.nextIdStr());//生成一个随机id
        //密码需要加密
        String encode = passwordEncoder.encode(ddUser.getPassword());
        ddUser.setPassword(encode);
        if(ddUserService.save(ddUser)){
            return Result.ok().message("添加成功");
        }
        return Result.error().message("添加失败");
    }
  1. 测试
    在这里插入图片描述

七、解决高版本循环依赖问题

实现了权限管理系统后,出现高版本循环依赖的问题,如下
  1. 当spring boot版本为2.6.X以上时,出现问题
    在这里插入图片描述
    在这里插入图片描述
原因如下
  1. SecurityConfig配置类中,注入了private DdUserService ddUserService;
    在这里插入图片描述
  2. DdUserServiceImpl又注入了UserDetailsService
    在这里插入图片描述
解决方案
  1. SecurityConfig注释private DdUserService ddUserService;,然后使用DdUserMapper
    在这里插入图片描述
    在这里插入图片描述
循环依赖解决,但是又出现了最近经常出现的问题,springboot2.6.x与swagger2不兼容的问题,这个和咱代码就没关系了,解决方案如下,修改配置,或者升级swagger或者降低springboot版本到2.5.x
  1. 解决循环依赖,新问题如下,表面上是swagger2找不到东西了,其实是ant_path_matcher参数的问题
    在这里插入图片描述
  2. 配置yaml参数
    在这里插入图片描述
spring:
  main:
    allow-circular-references: true # 关闭spring boot 循环注入检测
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 解决spring boot 高版本不兼容swagger2.x版本问题
  1. 问题解决,项目跑起来了,但是此时swagger不能用了,显示跨域问题
    在这里插入图片描述
  2. 修改跨域配置
    在这里插入图片描述
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
//                .allowedOrigins("*")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

八、前端对接

由于篇幅限制,我将其放在这篇文章中:https://blog.youkuaiyun.com/grd_java/article/details/122770777
SpringBoot CRM系统源码全解析:从架构设计到权限控制 在现代企业级应用开发中,SpringBoot凭借其简化配置与内嵌容器特性,已成为Java后端的主流框架选择。而CRM客户关系管理系统作为典型的业务管理系统,其技术架构与权限设计往往反映了真实工程实践中的通用解决方案。基于SpringBoot构建的CRM系统通常采用MyBatis-Plus作为持久层框架,以提升CRUD开发效率;借助Spring Security与JWT实现无状态认证与RBAC权限控制,保障多角色数据安全。同时,数据权限的动态SQL隔离、客户公海机制、状态机流转等设计,也为处理复杂业务场景提供 阅读详情

相关推荐

SpringBoot+Security+Vue前后端分离开发权限管理系统

适用人群所有的IT从业者,尤其适合快速掌握新技术,快速增长工作经验人群,对教育公平,教育公益,教育爱心公益人士 课程概述该互联网实战项目是基于?Spring?Boot?2+?SpringSecurity5+Element?UI+Vue?Admin?Template+蚂蚁可视化AntV?等技术栈开发的项目,采用分布式,多模块,前后端分离开发。包括图形展示、权限管理、用户管理等功能。【后端技术】技术说明Spring?Boot2MVC框架?开发的一站式解决方案Spring?Security5?认证和授权框架MyBatisPlus3.3.1?基于?MyBatis?框架的快速研发框架MyBatisCode工具生成?MyBatis?相关代码Jackson提供了处理?JSON?数据的工具Lombok简化对象封装工具?Druid???数据库连接池?【前端技术】Vue???????互联网最火的前端框架Vue?Router路由框架Vuex全局状态管理框架Axios前端?HTTP?框架Element?UI前端?UI?框架Vue?Element?Admin前端模板Antv??蚂蚁金服可视化技术,阿里巴巴可视化

SpringBoot2.6 + SpringSecurity 分离版

SpringBoot2.6+SpringSecurity(前后端分离版)

普通人一枚 4037

zemax混合式非序列模拟

注意不要点击左侧的非序列模式,那个时纯粹的非序列,会清除序列模式的数据。第2、4、6透镜材质为空气,是为了让光线进入到多重透镜内部。这时普通的优化方式会失败。平行光束经过多焦透镜时,会汇聚在不同焦距处。所以还需要控制能量的大小,进行优化。接下来用非序列模式设计一个多焦透镜。像面的孔径为圆形通光孔:0-1.2。透镜不同位置处有不同的聚焦位置。透镜的z轴位置相同,重合。

fendoudecheng的博客 3067

SpringSecurity权限管理基本概念和整体架构介绍

Spring Security是⼀个功能强⼤、可⾼度定制的身份验证和访问控制框架。它是保护基于Spring的应⽤程序的事实标准。Spring Security是⼀个⾯向Java应⽤程序提供身份验证和安全性的框架。与所有Spring项⽬⼀样,Spring Security的真正威⼒在于它可以轻松地扩展以满⾜定制需求。是⼀个功能强⼤、可⾼度定制的身份验证和访问控制的框架。或者说⽤来实现系统中权限管理的框架。

写写平时的技术与感想 3790

测站坐标系统 -- 东北天(ENU)坐标系、站心坐标系

测站坐标系统以观测站(或地面上某一个观测点为中心建立坐标系统,将这类坐标系称为测站坐标系统。测站坐标系通常以用户所在的位置点P为坐标原点三个坐标轴分别是相互垂直的东向北向和天向,因而测站坐标系又称为东北天(ENU)坐标系。由于测站坐标系以站心为坐标原点,因此也称为站心坐标系。如图所示,测站坐标系的天向与大地坐标系(LLA经纬度坐标系)在此点的高程方向一致。测站坐标系固定在地球上,本质上是一种地固坐标系。(1)以站心为坐标原点。(2)Z轴与站心点的椭球法线重合,向上为正。

qq_43381998的博客 1万+

利用Matlab绘制非线性系统相图:从理论到实践

本文详细介绍了如何利用Matlab进行非线性系统的相图绘制与相平面分析。从理论基础的状态空间转换出发,通过无阻尼线性谐振子和范德波尔振荡器等实例,手把手演示了使用ode45求解器绘制相图、分析平衡点与极限环的完整流程,并分享了方向场绘制、参数分岔探索及常见错误解决方案等高级技巧,为动力学系统分析与控制设计提供了直观强大的可视化工具。

yhn456789的博客 905

痛快,SpringBoot终于禁掉了循环依赖

Spring的Bean管理,一直是整个体系中津津乐道的东西。尤其是Bean的循环依赖,更是很多面试官最喜欢考察的2B知识点之一。 但事实上,项目中存在Bean的循环依赖,是代码质量低下的表现。多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误。 还好,SpringBoot终于受不了这种滥用,默认把循环依赖给禁用了! 从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动! 验证代码小片段 为了验证这个功能,我们只需要两段小代

shyyyyyyyyyyy的博客 4691

FreeModbus从入门到实战:手把手教你用STM32实现工业级Modbus RTU通信

本文详细介绍了如何使用STM32和FreeModbus实现工业级Modbus RTU通信,涵盖硬件搭建、协议栈移植及优化技巧。重点解析了FreeModbus的移植过程,包括串口驱动适配、定时器配置和工业级稳定性优化,为开发者提供了一套完整的工业通信解决方案。

weixin_33737774的博客 348

SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

SpringSecurity实战一—项目简介和开发环境准备 一、简介 ​ 在企业应用中,认证和授权是非常重要的一部分内容,业界最出名的两个框架就是大名鼎鼎的 Shiro和Spring Security。本次我选取的是和SpringBoot更好兼容的SpringSecurity。

CoderMy的博客 2万+

Halcon实战:5分钟搞定自定义直线卡尺工具(附完整代码下载)

本文详细介绍了如何使用Halcon快速开发自定义直线卡尺工具,通过交互式ROI绘制、边缘检测和直线拟合三个核心函数,实现高效工业视觉检测。文章包含完整代码下载,帮助开发者在5分钟内掌握这一实用工具的开发技巧,提升直线检测的精度和效率。

perl8的博客 250

SAP ABAP SM50 的另类用途 - ABAP 工作进程对数据库表读取操作的检测

今天是2020年五一国际劳动节,Jerry祝大家劳动节快乐。 虽然五一国际劳动节来源于一百多年前国外无产阶级的一次大罢工,但由于国情原因,国内很多行业的从业人员,好像天生就是劳碌命。比如程序员行业。 2014年五一劳动节,我在和一个不是总能重现的SAP CRM incident做斗争。详情参考Jerry之前的文章记一次SAP开发工程师给微软Azure报incident的体验. 当时处理完这个复杂...

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 8676

SpringSecurity原理剖析与权限系统设计

Spring Secutity和Apache Shiro是Java领域的两大主流开源安全框架,也是权限系统设计的主要技术选型。本文主要介绍Spring Secutity的实现原理,并基于Spring Secutity设计基于RBAC的权限系统。 一、技术选型 为何把Spring Secutity作为权限系统的技术选型,主要考虑了以下几个方面: 数据鉴权的能力:Spring Secutity支...

zdy0_2004的专栏 582

【Matlab】matlab代码实现迭代学习控制

该文介绍了在Matlab中实现迭代学习控制(ILC)的方法。ILC通过利用历史轨迹数据来优化重复任务的控制性能。文章提供了一个步进电机控制示例,包括:1)定义正弦轨迹;2)设置100次迭代和0.1学习率;3)建立二阶系统模型;4)执行ILC算法计算控制输入;5)绘制期望轨迹与实际输出的对比图。该示例展示了ILC的基本实现过程,并指出实际应用可能需要更复杂的设计。代码演示了误差计算、控制输入更新和结果可视化等关键步骤。

unber的博客 200

SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题

基于SpringSecurity实现登录,搭建权限管理系统 文章目录一、环境说明(太基本的就不讲了,本文主要讲如何前后端分离模式整合Security)二、配置Security1. 用户实体类继承UserDetails接口2. 编写JWT工具类3. 配置JWT过滤器4. 自定义返回结果5. Security配置类三、实现登录逻辑1. 专门给登录用户的实体类2....

殷志鹏的博客 8055

微信支付接口「系统繁忙 SYSTEM_ERROR」/「文件不存在」报错分析与解决方案 接口开发实现SDK

微信支付的“平台证书模式”只有5年的有效期,过期后微信支付将不再签发新的平台证书,转而要求开发者使用“微信支付公钥”来处理敏感信息加密及回调签名验证。然而,由于以下几点原因,许多开发者在调用 获取平台证书。尽管微信支付在技术迭代上有其必要性,但报错设计过于不友好。诸如“系统繁忙”这样的提示很难直接关联到平台证书失效或公钥切换问题。这样的信息表达,开发者不仅看不懂,还需要花费大量时间分析排查,非常影响效率。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 779

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

SpringSecurity权限管理系统实战—二、日志、接口文档等实现

上次我们已经搭建好了my-springsecurity-plus的基本环境,本次我们我们要实现功能有系统日志配置、配置swagger接口文档、配置druid连接池等

CoderMy的博客 8030

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

xx管理系统之JAVA后端

项目框架:SpringBoot+SpringSecurity+jwt+mybatis+swagger2 前端框架:vue+element 1.idea 新建SpringBoot项目 初始结构如下 导入pom.xml依赖配置 <properties> <java.version>1.8</java.version> <spring.version>2.3.5.RELEASE</spring.version>

qq_40286424的博客 629

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

springboot日志可视化_SpringSecurity权限管理系统实战—二、日志、接口文档等实现...

系列目录目录点这前言本篇文章的内容有点杂,搞得我都不知道怎么取标题了。上次我们已经搭建好了my-springsecurity-plus的基本环境,本次我们我们要实现功能有系统日志配置、配置swagger接口文档、配置druid连接池等一、Banner替换可以有些第一次接触到这个名词的小伙伴不清楚banner是什么,其实就是在运行springboot项目时控制台打印出的图案,就是下面这个东...

weixin_39740346的博客 454

基于springboot的宠物医院管理系统的设计与实现

宠物行业快速发展,宠物医疗需求激增。传统宠物医院依赖纸质记录和人工管理,存在效率低、易出错、数据难以共享等问题。信息化转型成为行业刚需,SpringBoot框架因其快速开发、微服务支持等特性成为理想技术选型。采用SpringBoot简化后端开发,整合MyBatis/JPA实现数据持久化,结合Vue/React构建前后端分离架构。通过RESTful API规范接口设计,利用Redis缓存提升性能,为同类医疗系统提供可复用的技术方案。系统实现电子病历管理、预约挂号、药品库存预警、财务统计等功能,降低运营成本30

2501_93025599的博客 624

Springboot超市商品销售管理系统37x2w(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

因此,开发一套基于Spring Boot的超市商品销售管理系统,旨在通过数字化、智能化的手段,实现商品销售信息的实时更新、精准分析和高效管理,从而提升超市的运营效率和客户服务水平。[8]杨梵.软件测试技术的关键能力培养探讨[J].福建电脑,2022,38(09):71-74.DOI:10.16707/j.cnki.fjpc.2022.09.016.[4] 李斌,邓思思,蔡思婷,陈琳敏,崔春兰,罗群.大数据时代煤田勘探钻孔地质空间数据库设计与实现[J].自然资源信息化,2022(01):19-24.

laodeng22567的博客 1204

SpringBoot+Vue实验报告管理系统设计与实现

在现代教育信息化建设中,实验报告管理系统是提升教学效率的关键工具。基于前后端分离架构,采用SpringBoot和Vue技术栈可以构建高性能、易扩展的管理系统。SpringBoot作为Java生态的微服务框架,提供了完善的REST API开发支持;Vue.js则以其渐进式特性和丰富的UI组件库,特别适合开发复杂的交互界面。系统实现了实验报告的在线提交、批注、版本管理等功能,解决了传统纸质报告易丢失、批改效率低等痛点。通过整合PDF.js实现报告预览与批注,结合MinIO对象存储保障文件安全,该系统已在高校实验

conghuang7343的博客 386

基于springboot+vue的JavaWeb的水资源管理系统

Spring Boot 2.7.x(简化配置和依赖管理),Spring MVC(Web层),Spring Data JPA(数据库操作)。:Thymeleaf(模板引擎)或Vue.js(可选前后端分离方案),Bootstrap(响应式布局)。:Lombok(简化代码),Swagger(API文档生成),Log4j2(日志管理)。:MySQL 8.0(关系型数据库),Redis(缓存高频访问数据如水质监测结果)。:Spring Security(权限控制,支持OAuth2.0)。

java python定制开发 407

基于springboot家具定制管理系统设计开发

注:实际开发需根据需求调整模块细节,建议结合UML图完善设计文档。

java python定制开发 568

基于springboot共享便捷城市图书借阅管理系统

Spring Boot 2.7.x(提供快速开发能力,集成Spring MVC、Spring Data JPA等)MySQL 8.0(主数据库存储用户、图书、借阅信息)MongoDB(可选,存储图书评论等非结构化数据)Spring Security(用户认证与授权)JUnit 5 + Mockito(单元测试)Spring Data JPA(数据库操作)Redis(缓存借阅记录、热门图书)Vue.js 3.x(前端交互)Axios(HTTP请求)Docker(容器化部署)Lombok(简化代码)

540

SpringBoot3+Vue3前后端分离博客系统:从环境搭建到功能测试全流程指南

这次我们来看一个基于 SpringBoot3 和 Vue3 的前后端分离博客系统。对于 2026 届的 Java 毕业生来说,这是一个非常典型的毕业设计选题,它涵盖了现代企业级 Web 开发的核心技术栈。这个项目的重点不在于概念有多新,而在于它能否让你在本地环境顺利跑起来,并理解前后端分离架构的完整工作流程。 这个项目是一个个人博客系统,后端采用 SpringBoot3 框架,前端使用 Vue3 构建,两者通过 RESTful API 进行通信。从 GitHub 仓库信息来看,项目状态为“正在开发中”,这意

dengyulong的专栏 393

SpringBoot学习笔记

SpringBoot入门 本文为狂神说SpringBoot教程学习笔记 狂神说-JavaSpringBoot教程IDEA版 第一个springboot程序 在IDEA中创建springboot工程 目录:在同级目录下建包 controller包中的hellocontroller: 通过完成配置可以实现在网页中使用地址访问内容 在pom.xml中添加spring-boot-starter-web依赖即可实现web功能,其中内嵌了tomcat package com.jinfz.cont

yijia7590jfz的博客 820
上一篇: SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题
下一篇: java 常用算法---持续补充
ydenergy_殷志鹏
博客等级 码龄7年 5885粉丝 543原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值