前端工程下载链接
https://pan.baidu.com/s/1TdFs4TqxlHh4DXyLwYuejQ
提取码:mfkw
项目sql文件下载链接
链接:https://pan.baidu.com/s/1DQCGN4wISSDlOkqnVWYwxA
提取码:mfkw
命令行进入keke-vue-admin文件夹
依次执行
npm install
npm run dev
后台的前端工程启动完毕
这里后台的前端工程启动如果启动失败,大概率是node的版本过高,建议换至14.的版本,我这里用的是node 14.21.3版本
下载链接:
node 14.21.3版本下载
如果下载出现安装失败问题,可以参见我这个博客,里面有详细的解决方案
node重装-解铃还须系铃人-CSDN博客
第一步: 在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 {
}
第五步: 在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 {
}
第六步: 在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 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 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表,所以这里的实现方式,和之前我们在前台模块的实现差不多
使用SpringSecurity安全框架来实现登录功能,并且实现登录的校验,也就是把数据库的用户表跟页面输入的用户名密码做比较
使用SpringSecurity安全框架来实现登录功能,并且实现登录的校验,也就是把数据库的用户表跟页面输入的用户名密码做比较
请求方式 |
请求路径 |
POST |
/user/login |
请求体:
{
"userName":"用户名",
"password":"密码"
}
响应体:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODBmOThmYmJkNmI0NjM0OWUyZjY2NTM0NGNjZWY2NSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0Mzg3NDMxNiwiZXhwIjoxNjQzOTYwNzE2fQ.ldLBUvNIxQCGemkCoMgT_0YsjsWndTg5tqfJb77pabk"
},
"msg": "操作成功"
}
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService(之前在前台模块的时候写的UserDetailsService)
在这个实现类中去查询数据库
注意配置passwordEncoder为BCryptPasswordEncoder
校验:
①定义Jwt认证过滤器(之前在前台模块的时候写的JwtAuthenticationTokenFilter)
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.33
io.jsonwebtoken
jjwt
0.9.0
第一步: 在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 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,接口无法访问
那这样我们登录校验的功能就实现完毕了,跟前台的实现方式几乎一样
接口设计。对应用户只能使用自己的权限所允许使用的功能
请求方式 |
请求地址 |
请求头 |
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":"[email protected]",
"id":1,
"nickName":"sg3334",
"sex":"1"
}
},
"msg":"操作成功"
}
之前在SpringSecurity的学习中就使用过RBAC权限模型。这里我们就是在RBAC权限模型的基础上去实现这个功能
RBAC权限模型最重要最难的就是设计好下面的5张表,有了5张表之后,就是简单的连表查询了
权限表中权限类型中的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 permissions;
private List 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 {
}
第六步: 在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 implements RoleService {
//根据用户id查询角色信息
@Override
public List selectRoleKeyByUserId(Long userId) {
//如果userId为1,那么角色权限字符串就只需要返回一个admin
if(userId==1L){
List 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
第八步: 在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 implements MenuService {
//根据用户id查询权限关键字
@Override
public List selectPermsByUserId(Long userId) {
//如果用户id为1代表管理员,roles 中只需要有admin,
// permissions中需要有所有菜单类型为C或者F的,状态为正常的,未被删除的权限
if(userId==1L) {
LambdaQueryWrapper
第九步: 在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 {
//Mapper的实现类对应xml映射文件
List selectPermsByUserId(Long userId);
}
第十步: 在keke-framework工程的resources目录新建mapper/MenuMapper.xml文件,写入如下,查询非超级管理员的权限信息的具体逻辑,即sql语句
第十一步: 在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 {
List selectRoleKeyByUserId(Long userId);
}
第十二步: 在keke-framework工程的resources/mapper目录新建RoleMapper.xml文件,写入如下,是查询用户的角色信息的具体代码,即实现功能的复杂sql语句
第十三步: 把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 getInfo(){
//获取当前登录用户,用我们封装的SecurityUtils
LoginUser loginUser = SecurityUtils.getLoginUser();
//根据用户id查询权限信息
Long userId = loginUser.getUser().getId();
List permissions = menuService.selectPermsByUserId(userId);
//根据用户id查询角色信息
List 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 接口
至此,后台的权限控制接口就实现了,整体来说还是比较复杂的,但是我们把每一步都分解开来,一步一步的去完成,完善,就可以去实现