博客后台模块

news2025/1/18 8:54:52

一、后台模块-准备工作

1. 前端工程启动

前端工程下载链接

https://pan.baidu.com/s/1TdFs4TqxlHh4DXyLwYuejQ 
提取码:mfkw

项目sql文件下载链接

链接:https://pan.baidu.com/s/1DQCGN4wISSDlOkqnVWYwxA 
提取码:mfkw

命令行进入keke-vue-admin文件夹

依次执行

npm install
npm run dev

   

后台的前端工程启动完毕 

2. 前端工程启动bug解决

这里后台的前端工程启动如果启动失败,大概率是node的版本过高,建议换至14.的版本,我这里用的是node 14.21.3版本

下载链接:

node 14.21.3版本下载

如果下载出现安装失败问题,可以参见我这个博客,里面有详细的解决方案

node重装-解铃还须系铃人-CSDN博客

3. 后台模块准备工作

第一步: 在keke-admin工程的src/main/java目录新建com.keke.BlogAdminApplication类,作为引导类,写入如下

package com.keke;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.keke.mapper")
public class BlogAdminApplication {
     public static void main(String[] args) {
          SpringApplication.run(BlogAdminApplication.class,args);
     }
}

第二步: 在keke-admin工程的resources目录新建File,文件名为application.yml文件,写入如下

server:
  port: 8989

spring:
  # 数据库连接信息
  datasource:
    url: jdbc:mysql://localhost:3306/keke_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password:
    driver-class-name: com.mysql.cj.jdbc.Driver

  servlet:
    # 文件上传
    multipart:
      # 单个上传文件的最大允许大小
      max-file-size: 20MB
      # HTTP请求中包含的所有文件的总大小的最大允许值
      max-request-size: 20MB

mybatis-plus:
  #  configuration:
  #    # 日志
  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 逻辑删除的字段
      logic-delete-field: delFlag
      # 代表已删除的值
      logic-delete-value: 1
      # 代表未删除的值
      logic-not-delete-value: 0
      # 主键自增策略,以mysql数据库为准
      id-type: auto

第三步: 在keke-framework工程的src/main/java/com.keke.domain.entity目录新建Tag类,写入如下

package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 标签(Tag)表实体类
 *
 * @author makejava
 * @since 2023-10-18 10:20:44
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("ke_tag")
public class Tag {
    private Long id;
    //标签名
    private String name;
    
    private Long createBy;
    
    private Date createTime;
    
    private Long updateBy;
    
    private Date updateTime;
    //删除标志(0代表未删除,1代表已删除)
    private Integer delFlag;
    //备注
    private String remark;

}

第四步:在keke-framework工程的src/main/java/com.keke.mapper目录新建TagMapper接口,写入如下

package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.Tag;


/**
 * 标签(Tag)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-18 10:21:07
 */
public interface TagMapper extends BaseMapper<Tag> {

}

第五步: 在keke-framework工程的src/main/java/com.keke.service目录新建TagService接口,写入如下

package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.Tag;


/**
 * 标签(Tag)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 10:21:06
 */
public interface TagService extends IService<Tag> {

}

第六步: 在keke-framework工程的src/main/java/com.keke.service目录新建impl.TagServiceImpl类,写入如下

package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.Tag;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import org.springframework.stereotype.Service;

/**
 * 标签(Tag)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 10:21:07
 */
@Service("tagService")
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagService {

}

第七步: 在keke-admin工程的src/main/java目录新建com.keke.controller.TagController类,写入如下

package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Tag;
import com.keke.service.TagService;
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;

@RestController
@RequestMapping("/tag")
public class TagController {

     @Autowired
     private TagService tagService;

     @GetMapping("/test")
     public ResponseResult test(){
          List<Tag> list = tagService.list();
          return ResponseResult.okResult(list);
     }
}

第八步: 由于huanf-framework公共模块里面有security的相关依赖和配置,为了让 '博客后台模块' 在启动时不报错,我们需要把keke-blog工程的security的相关代码提前写道keke-admin工程里面,这些代码我们在huanf-blog工程里面已经学过了,只是简单地在huanf-admin工程里面也弄(复制)一份这样的代码。

在keke-admin工程的src/main/java/com.keke目录新建filter.JwtAuthenticationTokenFilter类,写入如下。不用管下面的代码什么意思,登录功能的时候会学,注意这里的key为login:

package com.keke.filter;

import com.alibaba.fastjson.JSON;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.utils.JwtUtil;
import com.keke.utils.RedisCache;
import com.keke.utils.WebUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

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.Objects;


@Component
//博客前台的登录认证过滤器。OncePerRequestFilter是springsecurity提供的类
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    //RedisCache是我们在keke-framework工程写的工具类,用于操作redis
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //获取请求头中的token值
        String token = request.getHeader("token");
        //判断上面那行有没有拿到token值
        if(!StringUtils.hasText(token)){
            //说明该接口不需要登录,直接放行,不拦截
            filterChain.doFilter(request,response);
            return;
        }
        //JwtUtil是我们在keke-framework工程写的工具类。解析获取的token,把原来的密文解析为原文
        Claims claims = null;
        try {
            claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            //当token过期或token被篡改就会进入下面那行的异常处理
            e.printStackTrace();
            //报异常之后,把异常响应给前端,需要重新登录。ResponseResult、AppHttpCodeEnum、WebUtils是我们在keke-framework工程写的类
            ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
            WebUtils.renderString(response, JSON.toJSONString(result));
            return;
        }
        String userid = claims.getSubject();

        //在redis中,通过key来获取value,注意key我们是加过前缀的,取的时候也要加上前缀
        LoginUser loginUser = redisCache.getCacheObject("login:" + userid);
        //如果在redis获取不到值,说明登录是过期了,需要重新登录
        if(Objects.isNull(loginUser)){
            ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
            WebUtils.renderString(response, JSON.toJSONString(result));
            return;
        }

        //把从redis获取到的value,存入到SecurityContextHolder(Security官方提供的类)
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request,response);

    }
}

在keke-admin工程的src/main/java/com.keke目录新建config.SecurityConfig类,写入如下。不用管下面的代码什么意思,登录功能的时候会学

package com.keke.config;

import com.keke.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
//WebSecurityConfigurerAdapter是Security官方提供的类
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //注入我们在keke-blog工程写的JwtAuthenticationTokenFilter过滤器
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    AccessDeniedHandler accessDeniedHandler;



    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    //把官方的PasswordEncoder密码加密方式替换成BCryptPasswordEncoder
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
//                .antMatchers("/login").anonymous()
//这里新增必须要是登录状态才能访问退出登录的接口,即是认证过的状态
//                .antMatchers("/logout").authenticated()
             //   为方便测试认证过滤器,我们把查询友链的接口设置为需要登录才能访问。然后我们去访问的时候就能测试登录认证功能了
             //   .antMatchers("/link/getAllLink").authenticated()
                //
//                .antMatchers("/user/userInfo").authenticated()
                // 除上面外的所有请求全部不需要认证即可访问
                .anyRequest().permitAll();

        //配置我们自己写的认证和授权的异常处理
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);



        http.logout().disable();
        //将自定义filter加入security过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //允许跨域
        http.cors();
    }

}

第九步: 运行keke-admin工程的BlogAdminApplication类。在postman软件,使用GET请求访问如下

二、后台模块-登录功能

后台跟前台模块,共用一个sys_user表,所以这里的实现方式,和之前我们在前台模块的实现差不多

1. 接口分析

使用SpringSecurity安全框架来实现登录功能,并且实现登录的校验,也就是把数据库的用户表跟页面输入的用户名密码做比较

使用SpringSecurity安全框架来实现登录功能,并且实现登录的校验,也就是把数据库的用户表跟页面输入的用户名密码做比较

请求方式

请求路径

POST

/user/login

请求体:

{
    "userName":"用户名",
    "password":"密码"
}

响应体:

{
    "code": 200,
    "data": {
        "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODBmOThmYmJkNmI0NjM0OWUyZjY2NTM0NGNjZWY2NSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0Mzg3NDMxNiwiZXhwIjoxNjQzOTYwNzE2fQ.ldLBUvNIxQCGemkCoMgT_0YsjsWndTg5tqfJb77pabk"
    },
    "msg": "操作成功"
}

2. 思路分析

登录

①自定义登录接口

调用ProviderManager的方法进行认证 如果认证通过生成jwt

把用户信息存入redis中

②自定义UserDetailsService(之前在前台模块的时候写的UserDetailsService)

在这个实现类中去查询数据库

注意配置passwordEncoder为BCryptPasswordEncoder

校验:

①定义Jwt认证过滤器(之前在前台模块的时候写的JwtAuthenticationTokenFilter)

获取token

解析token获取其中的userid

从redis中获取用户信息

存入SecurityContextHolder

3. 相关依赖

<!--SpringSecurity启动器。需要用到登录功能就解开,不然就注释掉-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--redis依赖-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--fastjson依赖-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>

<!--jwt依赖-->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.0</version>
</dependency>

4.  登录+校验的代码实现

第一步: 在keke-framework工程的service目录新建SystemLoginService接口,写入如下

package com.keke.service;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.User;

public interface SystemLoginService {
     ResponseResult login(User user);
}

第二步: 在keke-framework工程的service目录新建impl.SystemLoginServiceImpl类,写入如下

package com.keke.service.impl;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.domain.entity.User;
import com.keke.service.BlogLoginService;
import com.keke.service.SystemLoginService;
import com.keke.utils.JwtUtil;
import com.keke.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


@Service
public class SystemLoginServiceImpl implements SystemLoginService {

     @Autowired
     private AuthenticationManager authenticationManager;

     @Autowired
     private RedisCache redisCache;

     @Override
     public ResponseResult login(User user) {
          UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
          Authentication authenticate = authenticationManager.authenticate(authenticationToken);
          //authenticationManager会默认调用UserDetailsService从内存中进行用户认证,我们实际需求是从数据库,因此我们要重新创建一个UserDetailsService的实现类
          //判断是否认证通过
          if(Objects.isNull(authenticate)){
               throw new RuntimeException("用户名或者密码错误");
          }
          //获取Userid,生成token
          LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
          String userId = loginUser.getUser().getId().toString();
          String jwt = JwtUtil.createJWT(userId);
          //把用户信息存入redis
          redisCache.setCacheObject("login:" + userId,loginUser);
          //把token和userInfo封装返回,因为响应回去的data有这两个属性,所以要封装Vo
          Map<String,String> systemLoginVo = new HashMap<>();
          systemLoginVo.put("token",jwt);
          return ResponseResult.okResult(systemLoginVo);
     }
}

第三步: 把keke-admin工程的SecurityConfig类修改为如下,为了测试校验功能,我们把接口设置为只有通过登录校验才能访问,注意要放行登录接口,其他接口均需认证

package com.keke.config;

import com.keke.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
//WebSecurityConfigurerAdapter是Security官方提供的类
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //注入我们在keke-blog工程写的JwtAuthenticationTokenFilter过滤器
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    AccessDeniedHandler accessDeniedHandler;



    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    //把官方的PasswordEncoder密码加密方式替换成BCryptPasswordEncoder
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                 //对于后台登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                //其他接口均需要认证
                .anyRequest().authenticated();

        //配置我们自己写的认证和授权的异常处理
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);



        http.logout().disable();
        //将自定义filter加入security过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //允许跨域
        http.cors();
    }

}

第四步: 在keke-admin工程的controller目录新建LoginController类,写入如下

package com.keke.controller;


import com.keke.annotation.KekeSystemLog;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.User;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.service.BlogLoginService;
import com.keke.service.SystemLoginService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(tags = "用户登录相关接口")
public class LoginController {

     @Autowired
     private SystemLoginService systemLoginService;

     @PostMapping("/user/login")
     @KekeSystemLog(businessName = "用户登录")
     public ResponseResult login(@RequestBody User user){
          if(!StringUtils.hasText(user.getUserName())){
               //提示必须要传用户名
               throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
          }
          return systemLoginService.login(user);
     }

}

第五步: 本地打开你的redis,postman

post请求,请求参数如下

{
    "userName":"HFuser",
    "password":"123"
}

可以看到控制台输出的日志信息

再拿着token去访问之前写的/test接口,可以看到访问成功

并且不携带token,接口无法访问

那这样我们登录校验的功能就实现完毕了,跟前台的实现方式几乎一样

二、后台模块-权限控制

1. 接口分析

接口设计。对应用户只能使用自己的权限所允许使用的功能

请求方式

请求地址

请求头

GET

/getInfo

需要token请求头

响应格式如下。如果用户id为1代表管理员,roles 中只需要有admin,permissions中需要有所有菜单类型为C或者F的,状态为正常的,未被删除的权限

{
	"code":200,
	"data":{
		"permissions":[
			"system:user:list",
            "system:role:list",
			"system:menu:list",
			"system:user:query",
			"system:user:add"
            //此次省略1000字
		],
		"roles":[
			"admin"
		],
		"user":{
			"avatar":"http://r7yxkqloa.bkt.clouddn.com/2022/03/05/75fd15587811443a9a9a771f24da458d.png",
			"email":"23412332@qq.com",
			"id":1,
			"nickName":"sg3334",
			"sex":"1"
		}
	},
	"msg":"操作成功"
}

之前在SpringSecurity的学习中就使用过RBAC权限模型。这里我们就是在RBAC权限模型的基础上去实现这个功能

RBAC权限模型最重要最难的就是设计好下面的5张表,有了5张表之后,就是简单的连表查询了

2. 权限表的字段

3. 角色表的字段

4. 用户表的字段

5. 中间表-角色&用户

6. 中间表-角色&权限

7. 代码实现

权限表中权限类型中的M表示目录,目录其实不会进行页面跳转,所以不需要处理

权限控制其实就是Menu表,对应的是后台中的菜单和按钮,如果有这些菜单和按钮的权限,就可以进行相应的操作

第一步: 把keke-framework工程的SystemCanstants类修改为如下,增加了两个判断权限类型的成员变量

package com.keke.constants;


//字面值(代码中的固定值)处理,把字面值都在这里定义成常量
public class SystemConstants {

    /**
     * 文章是草稿
     */
    public static final int ARTICLE_STATUS_DRAFT = 1;

    /**
     * 文章是正常发布状态
     */
    public static final int ARTICLE_STATUS_NORMAL = 0;

    /**
     * 文章列表当前查询页数
     */
    public static final int ARTICLE_STATUS_CURRENT = 1;

    /**
     * 文章列表每页显示的数据条数
     */
    public static final int ARTICLE_STATUS_SIZE = 10;

    /**
     * 分类表的分类状态是正常状态
     */
    public static final String STATUS_NORMAL = "0";

    /**
     * 友联审核通过
     */
    public static final String Link_STATUS_NORMAL = "0";

    /**
     * 评论区的某条评论是根评论
     */
    public static final String COMMENT_ROOT = "-1";

    /**
     * 文章评论
     */
    public static final String ARTICLE_COMMENT = "0";

    /**
     * 友链评论
     */
    public static final String LINK_COMMENT = "1";

    /**
     * redis中的文章浏览量key
     */
    public static final String REDIS_ARTICLE_KEY = "article:viewCount";

    /**
     * 浏览量自增1
     */
    public static final int REDIS_ARTICLE_VIEW_COUNT_INCREMENT = 1;


    /**
     * 菜单权限
     */
    public static final String MENU = "C";

    /**
     * 按钮权限
     */
    public static final String BUTTON = "F";


}

第二步: 在keke-framework工程的domain/vo目录新建AdminUserInfoVo类,写入如下,负责把指定字段返回给前端

package com.keke.domain.vo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminUserInfoVo {
     private List<String> permissions;
     private List<String> roles;
     private UserInfoVo user;
}

第三步: 在keke-framework工程的domain/entity目录新建Menu类,写入如下,让mybatisplus去查询我们的sys_menu权限表

package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 菜单权限表(Menu)表实体类
 *
 * @author makejava
 * @since 2023-10-18 20:55:24
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_menu")
public class Menu {
    //菜单ID
    private Long id;
    //菜单名称
    private String menuName;
    //父菜单ID
    private Long parentId;
    //显示顺序
    private Integer orderNum;
    //路由地址
    private String path;
    //组件路径
    private String component;
    //是否为外链(0是 1否)
    private Integer isFrame;
    //菜单类型(M目录 C菜单 F按钮)
    private String menuType;
    //菜单状态(0显示 1隐藏)
    private String visible;
    //菜单状态(0正常 1停用)
    private String status;
    //权限标识
    private String perms;
    //菜单图标
    private String icon;
    //创建者
    private Long createBy;
    //创建时间
    private Date createTime;
    //更新者
    private Long updateBy;
    //更新时间
    private Date updateTime;
    //备注
    private String remark;
    
    private String delFlag;

}

第四步: 在keke-framework工程的domain目录新建Role类,写入如下,让mybatisplus去查询我们的sys_role角色表

package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 角色信息表(Role)表实体类
 *
 * @author makejava
 * @since 2023-10-18 21:03:52
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role")
public class Role {
    //角色ID
    private Long id;
    //角色名称
    private String roleName;
    //角色权限字符串
    private String roleKey;
    //显示顺序
    private Integer roleSort;
    //角色状态(0正常 1停用)
    private String status;
    //删除标志(0代表存在 1代表删除)
    private String delFlag;
    //创建者
    private Long createBy;
    //创建时间
    private Date createTime;
    //更新者
    private Long updateBy;
    //更新时间
    private Date updateTime;
    //备注
    private String remark;

}

第五步: 在keke-framework工程的service目录新建RoleService接口,写入如下,用于查询用户的角色信息

package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.Role;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

}

第六步: 在keke-framework工程的service目录新建impl.RoleServiceImpl类,写入如下,是查询用户的角色信息的具体代码

package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.Role;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {


     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }
}

第七步: 在keke-framework工程的service目录新建MenuService接口,写入如下,用于查询超级管理员的权限信息

package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.Menu;

import java.util.List;


/**
 * 菜单权限表(Menu)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
public interface MenuService extends IService<Menu> {


     List<String> selectPermsByUserId(Long userId);
}

第八步: 在keke-framework工程的service目录新建impl.MenuServiceImpl类,写入如下,是查询用户的权限信息的具体代码

package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.entity.Menu;
import com.keke.mapper.MenuMapper;
import com.keke.service.MenuService;
import org.springframework.stereotype.Service;

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

/**
 * 菜单权限表(Menu)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
@Service("menuService")
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {



     //根据用户id查询权限关键字
     @Override
     public List<String> selectPermsByUserId(Long userId) {
          //如果用户id为1代表管理员,roles 中只需要有admin,
          // permissions中需要有所有菜单类型为C或者F的,状态为正常的,未被删除的权限
          if(userId==1L) {
               LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
               lambdaQueryWrapper.in(Menu::getMenuType, SystemConstants.MENU, SystemConstants.BUTTON);
               lambdaQueryWrapper.eq(Menu::getStatus, SystemConstants.STATUS_NORMAL);
               //由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapper
               List<Menu> menuList = list(lambdaQueryWrapper);
               //我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式
               List<String> permissions = menuList.stream()
                       .map(new Function<Menu, String>() {
                            @Override
                            public String apply(Menu menu) {
                                 String perms = menu.getPerms();
                                 return perms;
                            }
                       })
                       .collect(Collectors.toList());
               return permissions;
          }
          //否则返回这个用户所具有的权限
          //这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联
          MenuMapper menuMapper = getBaseMapper();
          //我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回
          return menuMapper.selectPermsByUserId(userId);
     }
}

第九步: 在huanf-framework工程的mapper目录新建MenuMapper接口,写入如下,封装查询非超级管理员的权限信息的具体逻辑

package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.Menu;

import java.util.List;


/**
 * 菜单权限表(Menu)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
public interface MenuMapper extends BaseMapper<Menu> {

     //Mapper的实现类对应xml映射文件
     List<String> selectPermsByUserId(Long userId);
}

第十步: 在keke-framework工程的resources目录新建mapper/MenuMapper.xml文件,写入如下,查询非超级管理员的权限信息的具体逻辑,即sql语句

<?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.keke.mapper.MenuMapper">
    <select id="selectPermsByUserId" resultType="java.lang.String">
-- 这里的逻辑是先用userId连表查询roleId,再用roleId连表查询menuId,再根据menuId
-- 查询对应的用户权限
            select
                m.`perms`
            from `sys_user_role` ur
                     left join `sys_role_menu` rm on ur.`role_id`=rm.`role_id`
                     left join `sys_menu` m on m.`id`=rm.`menu_id`
            where
                ur.`user_id`= #{userId} and
                m.`menu_type` in ('C','F') and
                m.`status`=0 and
                m.`del_flag`=0
    </select>
</mapper>

第十一步: 在keke-framework工程的mapper目录新建RoleMapper文件,写入如下,用于查询用户的角色信息

package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleMapper extends BaseMapper<Role> {


     List<String> selectRoleKeyByUserId(Long userId);
}

第十二步: 在keke-framework工程的resources/mapper目录新建RoleMapper.xml文件,写入如下,是查询用户的角色信息的具体代码,即实现功能的复杂sql语句

<?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.keke.mapper.RoleMapper">
    <select id="selectRoleKeyByUserId" resultType="java.lang.String">
        select
            `sys_role`.role_key
        from `sys_user_role`
                 left join `sys_role` on `sys_user_role`.role_id=`sys_role`.id
        where `sys_user_role`.user_id=#{useId}
          and `sys_role`.status=0
          and	`sys_role`.del_flag=0
    </select>
</mapper>

第十三步: 把keke-admin工程的LoginController类修改为如下,增加了查询角色信息、权限信息的接口

package com.keke.controller;


import com.keke.annotation.KekeSystemLog;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.domain.entity.User;
import com.keke.domain.vo.AdminUserInfoVo;
import com.keke.domain.vo.UserInfoVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.service.BlogLoginService;
import com.keke.service.MenuService;
import com.keke.service.RoleService;
import com.keke.service.SystemLoginService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@Api(tags = "用户登录相关接口")
public class LoginController {

     @Autowired
     private SystemLoginService systemLoginService;

     @Autowired
     private MenuService menuService;

     @Autowired
     private RoleService roleService;

     @PostMapping("/user/login")
     @KekeSystemLog(businessName = "后台用户登录")
     public ResponseResult login(@RequestBody User user){
          if(!StringUtils.hasText(user.getUserName())){
               //提示必须要传用户名
               throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
          }
          return systemLoginService.login(user);
     }

     @GetMapping("/getInfo")
     public ResponseResult<AdminUserInfoVo> getInfo(){
          //获取当前登录用户,用我们封装的SecurityUtils
          LoginUser loginUser = SecurityUtils.getLoginUser();
          //根据用户id查询权限信息
          Long userId = loginUser.getUser().getId();
          List<String> permissions = menuService.selectPermsByUserId(userId);
          //根据用户id查询角色信息
          List<String> roles = roleService.selectRoleKeyByUserId(userId);
          //获取userInfo信息
          User user = loginUser.getUser();
          UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
          //创建Vo,封装返回
          AdminUserInfoVo adminUserInfoVo = new AdminUserInfoVo(permissions,roles,userInfoVo);
          return ResponseResult.okResult(adminUserInfoVo);
     }

}

第十四步: 测试本地打开你的redis,postman

首先登录,拿到token

{
    "userName":"sg",
    "password":"1234"
}

然后我们访问 /getInfo 接口

至此,后台的权限控制接口就实现了,整体来说还是比较复杂的,但是我们把每一步都分解开来,一步一步的去完成,完善,就可以去实现 

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

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

相关文章

工业4.0时代数字化工厂的几个特点

随着工业4.0时代的到来&#xff0c;数字化工厂成为了制造业的重要组成部分。数字化工厂管理系统是一种应用数字化、自动化和物联网等技术与产业融合的全新生产方式&#xff0c;旨在提高生产效率、降低成本、提升产品质量&#xff0c;并增强企业的核心竞争力。 数字化工厂的核心…

用友GRP-U8 SQL注入漏洞复现

0x01 产品简介 用友GRP-U8R10行政事业财务管理软件是用友公司专注于国家电子政务事业&#xff0c;基于云计算技术所推出的新一代产品&#xff0c;是我国行政事业财务领域最专业的政府财务管理软件。 0x02 漏洞概述 用友GRP-U8的bx_historyDataCheck jsp、slbmbygr.jsp等接口存…

链上房产赛道项目 ESTATEX 研报:以 RWA 的方式释放房产市场的潜力

在上个世纪初&#xff0c;随着全球人口的指数型增长以及城市化趋势加速&#xff0c;全球房地产行业逐渐进入到发展的爆发期与红利期。一方面人口的暴增与城市化进程的的加速&#xff0c;让住宅和商业房地产逐渐形成了刚需&#xff0c;另一方面全球经济飞速发展&#xff0c;让越…

Leetcode.4 寻找两个正序数组的中位数

题目链接 Leetcode.4 寻找两个正序数组的中位数 hard 题目描述 给定两个大小分别为 m m m 和 n n n 的正序&#xff08;从小到大&#xff09;数组 n u m s 1 nums1 nums1 和 n u m s 2 nums2 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O…

Pinia学习-存储数据、修改数据以及持久化实现

Pinia是什么&#xff1f; Pinia 是 Vue 的存储库&#xff0c;实现全局变量的定义 这里定义的变量信息&#xff0c;任何页面都可以使用&#xff0c;代替原来的VueX 官网&#xff1a;https://pinia.web3doc.top/ 4.2 Pinia存储数据 4.2.1获取存储数据 实现步骤&#xff1a;…

比例夹管阀及其高精度压力和流量控制解决方案

摘要&#xff1a;针对卫生和无菌流体系统中柔性管路内的压力和流量控制&#xff0c;本文介绍了采用电控夹管阀的高精度控制解决方案。解决方案基于反馈控制原理&#xff0c;采用压力传感器或流量传感器进行测量并反馈给程序控制器&#xff0c;控制器驱动夹管阀来改变柔性管路的…

双目视觉实战--单视图测量方法

目录 一.简介 二、2D变换 1. 等距变换&#xff08;欧式变换&#xff09; 2. 相似变换 3. 仿射变换 4. 射影变换&#xff08;透视变换&#xff09; 5. 结论 三、影消点与影消线 1. 平面上的线 2. 直线的交点 3. 2D无穷远点 4. 无穷远直线 5. 无穷远点的透视变换与仿…

Yakit单兵作战神器简单使用

文章目录 免配置抓包破解编码数据包DNSLog 官网下载地址&#xff1a;https://yaklang.com/ 使用文档&#xff1a;https://www.yaklang.com/products/intro 免配置抓包 会启动一个和BurpSuite一样的内置浏览器 访问地址&#xff0c;就会拦截到数据包 也可以在history里查看 破解…

屏幕录制视频编辑软件 Camtasia 2023 mac中文版软件功能

Camtasia 2023 mac是一款功能强大的屏幕录制和视频编辑软件&#xff0c;可以用于制作教育课程、演示文稿、培训视频等。它具有一系列工具和功能&#xff0c;包括屏幕录制、视频编辑、音频编辑、字幕、特效等&#xff0c;使用户可以轻松地创建高质量的视频内容。 Camtasia2023的…

DH48WK 温控器参数设置

北京东昊力伟科技有限责任公司 温控仪、温度控制器 产品特点&#xff1a; 可外接温度传感器Pt100、Cu50、K、E、J、N、T、R、S、B兼容输入&#xff1b;PID控制输出、位式控制输出、继电器报警输出&#xff1b;控温能满足设定温度值的0.2℃&#xff1b;既可用于加热控制、也可…

VR数字政务为我们带来了哪些便捷之处?

每每在政务大厅排队的时候&#xff0c;总是在想未来政务服务会变成什么样子呢&#xff1f;会不会变得更加便捷呢&#xff1f;今天我们就来看看VR数字政务&#xff0c;能够为我们带来哪些便捷之处吧&#xff01; 传统的政务服务中&#xff0c;不仅办事流程复杂&#xff0c;而且每…

5.2 加载矢量图层(delimitedtext,spatialite,wfs,memory)

文章目录 前言加载矢量(vector)图层delimitedtextQGis导入CSV代码导入 SpatiaLite data provider (spatialite)QgsDataSourceUriQGis导入spatialite代码导入 Web服务WFS (web feature service) data provider (wfs)QGis添加图层代码添加 Memory data providerType (memory)QGis…

腾讯云服务器带宽下载速度快吗?多线BGP和CN2高速网络

腾讯云服务器公网带宽下载速度计算&#xff0c;1M公网带宽下载速度是128KB/秒&#xff0c;5M带宽下载速度是512KB/s&#xff0c;腾讯云10M带宽下载速度是1.25M/秒&#xff0c;腾讯云百科txybk.com来详细说下腾讯云服务器不同公网带宽实际下载速度以及对应的上传速度对照表&…

dig 简明教程

哈喽大家好&#xff0c;我是咸鱼 不知道大家在日常学习或者工作当中用 dig 命令多不多 dig 是 Domain Information Groper 的缩写&#xff0c;对于网络管理员和在域名系统(DNS)领域工作的小伙伴来说&#xff0c;它是一个非常常见且有用的工具。 无论是简单的 DNS 解析查找还…

第三章 数据结构与算法——栈和

栈和队列被称为插入和删除受限制的线性表。 &#x1f341;一、栈的基本概念 &#x1f315;&#xff08;一&#xff09;栈的概念&#xff1a; ①&#xff1a;栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈…

四川竹哲电子商务有限公司让抖音带货更轻松

随着电子商务的飞速发展&#xff0c;带货直播成为了新的行业热点。四川竹哲电子商务有限公司&#xff0c;一家在电子商务领域有着深厚实力和丰富经验的企业&#xff0c;正以其独特的视角和策略&#xff0c;引领着抖音带货的发展趋势&#xff0c;让这个新型商业模式更加轻松、高…

C++初阶(三)

文章目录 一、auto关键字(C11)1、auto简介2、auto使用规则1、 auto与指针和引用结合起来使用2、 在同一行定义多个变量 3、auto不能推导的场景1、 auto不能作为函数的参数2、 auto不能直接用来声明数组3、特性总结 二、基于范围的for循环(C11)1、范围for的语法2、 范围for的使用…

基于springboot校园二手书交易管理系统

功能如下图所示 摘要 校园二手书交易管理系统是一款基于Spring Boot框架的应用程序&#xff0c;旨在便捷管理大学校园内的二手书籍交易。该系统致力于为学生和教职工提供一个高效、便捷的平台&#xff0c;以便买卖二手书籍&#xff0c;从而减轻经济压力&#xff0c;减少资源浪费…

Linux高性能服务器编程 学习笔记 第十七章 系统监测工具

tcpdump是一款经典的抓包工具&#xff0c;即使今天我们已经有了像Wireshark这样更易于使用和掌握的抓包工具&#xff0c;tcpdump仍是网络程序员的必备利器。 tcpdump提供了一些选项用以过滤数据包或定制输出格式&#xff0c;常见的选项如下&#xff1a; 1.-n&#xff1a;使用I…

ROS 示例

参考链接1 话题中的Publisher与Subscriber 例1&#xff1a; 发送和接收 hello, world 1、创建工作空间 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace创建完成后&#xff0c;可以在工作空间的根目录下使用catkin_make命令编译整个工作空间&#xff1…