配置config类
该类主要重写了getPermissionList()和getRoleList()方法
其中:
getPermissionList():负责为当前登录的对象给予权限
getRoleList():负责为当前登录的对象绑定角色
例如:
当前用户管理员登录了系统,那么他的角色是admin,拥有的权限为admin:addEmployee
若游客登录,那么他的角色为nologin,只有浏览前台页面的权限(默认为无)
下列代码主要流程:
从方法提供的Object o这个参数(实际为用户id),查询数据库获取权限列表(权限内容存储在数据库),为登录者赋权。
每次请求都会进入该方法需要查询数据库,这里使用了 @Cacheable(value = "xxxx")注解,存储到缓存中,减少数据库查询次数。
Component // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
@Slf4j
public class AuthConfig implements StpInterface {
@Autowired
private EmployeeService employeeService;
@Autowired
private SysRoleService roleService;
@Autowired
private RoleMappingService roleMappingService;
//添加某些权限可以访问的
@Override
@Cacheable(value = "PermissionList")
public List<String> getPermissionList(Object o, String loginType) {
List<String> list = new ArrayList<>();
Long roleId = Long.valueOf(o.toString());
RoleMapping role = getTypeById(roleId);
if(role==null){
// 游客登录
return list;
}
int type = role.getType();
if(type==1){
// 管理员
//添加“新增员工”权限
list.add("admin:addEmployee");
//添加“编辑内容”权限
list.add("admin:edit");
}
else if(type==2){
//员工权限
// 查询编辑权限
Integer isEdit = role.getIsEdit();
if(isEdit==1){
list.add("admin:edit");
}
}
return list;
}
//添加某些角色可以访问的
@Override
@Cacheable(value = "RoleList")
public List<String> getRoleList(Object o, String loginType) {
List<String> list = new ArrayList<>();
Long roleId = Long.valueOf(o.toString());
RoleMapping role = getTypeById(roleId);
String name ;
if(role!=null){
// 已存在的用户
Integer type = role.getType();
name = roleService.getById(type).getName();
}else {
//游客
name = roleService.getById(NOLOGIN_TYPE).getName();
}
list.add(name);
return list;
}
private RoleMapping getTypeById(Long id){
LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMapping::getRoleId,id);
RoleMapping role = roleMappingService.getOne(wrapper);
return role;
}
}
看完这段代码,可能会好奇,哪儿传入的用户id的,没看见欸,下面就来介绍login方法
用户注册
参考地址: sa-token.cc/doc.html#/u…
@Override
public Result login(String username, String password) {
//MD5加密
password= MD5Util.getMD5(password);
//通过账户查这个员工对象,这里就不走Service层了
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getUsername, username);
Employee empResult = getOne(wrapper);
//判断用户是否存在
if (empResult==null){
return Result.error("账户不存在");
//密码是否正确
}else if (!empResult.getPassword().equals(password)){
return Result.error("账户密码错误");
//员工账户状态是否正常,1状态正常,0封禁
}else if (empResult.getStatus()!=1){
// 获取封禁时间
long disableTime = StpUtil.getDisableTime(Long.valueOf(empResult.getId()));
long minutes = Convert.convertTime(disableTime, TimeUnit.SECONDS, TimeUnit.MINUTES);
return Result.error("当前账户正在封禁,还剩"+minutes+"分钟");
//状态正常允许登陆
}else {
log.info("登陆成功!!!");
/* String token = UUID.randomUUID().toString();
redisUtils.set(EMPLOYEE_TOKEN_PREFIX+token,empResult.getId(),EMPLOYEE_TOKEN_TTL);*/
//用户登录, 设置三天有效期
StpUtil.login(empResult.getId(),new SaLoginModel()
.setDevice("PC")
.setTimeout(60 * 60 * 24 * 3));
// 获取token
String token = StpUtil.getTokenInfo().getTokenValue();
log.info("登录生成的token为"+token);
empResult.setToken(token);
//把员工对象存入localSession作用域
return Result.success(empResult);
}
}
逐一分析代码~
开头一段就是简单的用户登录验证,主要需要注意的是,这儿采用了empResult.getStatus()方法,可以获取当前用户封禁状态以及剩余的解放时间,存储在redis内。得益于sa-token的整合,我们并不需要直接操作redis,他会自动存储到redis内
参考地址: sa-token.cc/doc.html#/u…
//MD5加密
password= MD5Util.getMD5(password);
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getUsername, username);
Employee empResult = getOne(wrapper);
//判断用户是否存在
if (empResult==null){
return Result.error("账户不存在");
}
//密码是否正确
else if (!empResult.getPassword().equals(password)){
return Result.error("账户密码错误");
}
//员工账户状态是否正常,1状态正常,0封禁
else if (empResult.getStatus()!=1){
// 获取封禁时间
long disableTime = StpUtil.getDisableTime(Long.valueOf(empResult.getId()));
long minutes = Convert.convertTime(disableTime, TimeUnit.SECONDS, TimeUnit.MINUTES);
return Result.error("当前账户正在封禁,还剩"+minutes+"分钟");
//状态正常允许登陆
}
后一段是用户在校验成功后,注册登录信息的代码。
从注释中可以看到,我一开始是使用redis存储token的,使用sa-token后,就直接使用login()方法替代了,它可以自动替我存储到redis和前端请求头中的token。login()方法内的参数就是用户id
log.info("登陆成功!!!");
/* String token = UUID.randomUUID().toString();
redisUtils.set(EMPLOYEE_TOKEN_PREFIX+token,empResult.getId(),EMPLOYEE_TOKEN_TTL);*/
//用户登录, 设置三天有效期
StpUtil.login(empResult.getId(),new SaLoginModel()
.setDevice("PC")
.setTimeout(60 * 60 * 24 * 3));
// 获取token
String token = StpUtil.getTokenInfo().getTokenValue();
log.info("登录生成的token为"+token);
empResult.setToken(token);
//把员工对象存入localSession作用域
return Result.success(empResult);
撤销权限
上面说到了如何授权,那么再拓展下撤销权限
下面这段代码的作用是:
前端按下封禁用户或者禁止编辑按钮后,会向后台传入修改权限用户的id和修改后的状态代码,1代表正常,0代表封禁
//更新员工状态
@ResponseBody
@PutMapping("/employee")
@SaCheckPermission("admin:addEmployee")
@CacheEvict(value={"employeeCache","RoleList","PermissionList"},allEntries = true)
@Transactional
public Result updateStatus( @RequestBody Employee employee) {
Long id = employee.getId();
RoleMapping roleMapping = new RoleMapping();
//封禁操作
Integer status = employee.getStatus();
if(status!=null){
//封禁操作
if(status==0){
// 先踢下线后封禁
// 先踢下线
StpUtil.kickout(id);
// 再封禁账号
StpUtil.disable(id,EMPLOYEE_DISABLE_LIMIT);
}else {
// 解除封禁
StpUtil.untieDisable(id);
}
employee.setStatus(status);
}else{
Integer isEdit = employee.getIsEdit();
roleMapping.setRoleId(id);
roleMapping.setIsEdit(isEdit);
LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMapping::getRoleId,id);
roleMappingService.update(roleMapping,wrapper);
}
//拿新的状态值
log.info("更新的用户信息为:"+employee);
//员工信息更新后,会自动清除缓存
employeeService.updateById(employee);
return Result.success(employee);
}
再次逐端分析~
若status不为空,则为封禁相关操作,否则执行else的禁止编辑操作
这里使用了官方提供的踢人下线和封禁账号方法,若指定封禁了时间,需要配合定时任务解除封禁状态。
例如我指定了封禁该用户1天,这个具体的实现是存储到redis内的,TTL即为封禁事件,若时间一过,那么理论上用户解除封禁。但是!!!你若此时将用户的状态(封禁状态)存入了数据库,一天后sa-token可不会替你修改数据库内容的,所以记得加上定时器任务。
当然,你要你压根没设计将用户状态更新到数据库,而在登录判断时候直接使用StpUtil.isDisable()方法判断用户状态的,当我没说计定时任务这段话.....
定时任务实现参考我这篇:juejin.cn/post/724553…
if(status!=null){
//封禁操作
if(status==0){
// 先踢下线后封禁
// 先踢下线
StpUtil.kickout(id);
// 再封禁账号
StpUtil.disable(id,EMPLOYEE_DISABLE_LIMIT);
}else {
// 解除封禁
StpUtil.untieDisable(id);
}
employee.setStatus(status);
}
这是禁止编辑操作,直接修改角色数据库中角色状态,待下次请求时,会直接执行config类的if判断,从而做到刷新权限。这里需要注意的是,若在config类两个方法上加了缓存,更新后记得清除缓存,否则会出现数据库字段更新成功,前台选项响应不了的情况,亲身经历.....
else{
Integer isEdit = employee.getIsEdit();
roleMapping.setRoleId(id);
roleMapping.setIsEdit(isEdit);
LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMapping::getRoleId,id);
roleMappingService.update(roleMapping,wrapper);
}
完整项目参考地址: gitee.com/gitee-enter…