新增:新增功能权限适配及校验

1.后端 API 注解鉴权使用方式:@SaCheckPermission("system:user:add")
2.前端全局指令函数使用方式:v-permission="['system:user:add']"
3.前端权限判断函数使用方式:checkPermission(['system:user:add'])
This commit is contained in:
2023-03-02 23:39:22 +08:00
parent 843cac4e54
commit 94be1f9553
51 changed files with 548 additions and 149 deletions

View File

@@ -30,8 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.annotation.CrudRequestMapping;
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.query.SortQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
@@ -72,6 +75,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@GetMapping
protected R<PageDataVO<V>> page(@Validated Q query, @Validated PageQuery pageQuery) {
this.checkPermission("list");
PageDataVO<V> pageDataVO = baseService.page(query, pageQuery);
return R.ok(pageDataVO);
}
@@ -89,6 +93,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@GetMapping("/tree")
protected R<List<Tree<Long>>> tree(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission("list");
List<Tree<Long>> list = baseService.tree(query, sortQuery, false);
return R.ok(list);
}
@@ -106,6 +111,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@GetMapping("/list")
protected R<List<V>> list(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission("list");
List<V> list = baseService.list(query, sortQuery);
return R.ok(list);
}
@@ -122,6 +128,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@GetMapping("/{id}")
protected R<D> get(@PathVariable Long id) {
this.checkPermission("list");
D detail = baseService.get(id);
return R.ok(detail);
}
@@ -137,6 +144,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@PostMapping
protected R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody C request) {
this.checkPermission("add");
Long id = baseService.add(request);
return R.ok("新增成功", id);
}
@@ -152,6 +160,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@PutMapping
protected R update(@Validated(BaseRequest.Update.class) @RequestBody C request) {
this.checkPermission("update");
baseService.update(request);
return R.ok("修改成功");
}
@@ -168,6 +177,7 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@ResponseBody
@DeleteMapping("/{ids}")
protected R delete(@PathVariable List<Long> ids) {
this.checkPermission("delete");
baseService.delete(ids);
return R.ok("删除成功");
}
@@ -185,6 +195,20 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "导出数据")
@GetMapping("/export")
protected void export(@Validated Q query, @Validated SortQuery sortQuery, HttpServletResponse response) {
this.checkPermission("export");
baseService.export(query, sortQuery, response);
}
/**
* 权限认证
*
* @param subPermission
* 部分权限码
*/
private void checkPermission(String subPermission) {
CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class);
String path = crudRequestMapping.value();
String permissionPrefix = String.join(":", StrUtil.splitTrim(path, "/"));
StpUtil.checkPermission(String.format("%s:%s", permissionPrefix, subPermission));
}
}

View File

@@ -41,6 +41,7 @@ import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Opt;
@@ -165,7 +166,10 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T, V, D, Q, C ext
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C request) {
T entity = BeanUtil.copyProperties(request, entityClass);
String idName = this.currentEntityIdName();
Object idValue = ReflectUtil.getFieldValue(request, idName);
T entity = this.getById(idValue);
BeanUtil.copyProperties(request, entity, CopyOptions.create().ignoreNullValue());
baseMapper.updateById(entity);
}
@@ -211,8 +215,8 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T, V, D, Q, C ext
* ID
* @return 实体信息
*/
protected T getById(Long id) {
T entity = baseMapper.selectById(id);
protected T getById(Object id) {
T entity = baseMapper.selectById(Convert.toStr(id));
CheckUtils.throwIfNull(entity, String.format("ID为 [%s] 的记录已不存在", id));
return entity;
}

View File

@@ -31,7 +31,12 @@ public class Constants {
/**
* 超级管理员角色编码
*/
public static final String ADMIN_ROLE_CODE = "admin";
public static final String SUPER_ADMIN = "admin";
/**
* 全部权限标识
*/
public static final String ALL_PERMISSION = "*";
/**
* 默认密码

View File

@@ -36,6 +36,8 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
@@ -163,7 +165,20 @@ public class GlobalExceptionHandler {
}
/**
* 拦截认证异常-未登录异常
* 拦截文件上传异常-超过上传大小限制
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
log.error("请求地址'{}',上传文件失败,文件大小超过限制", request.getRequestURI(), e);
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
String errorMsg = String.format("请上传小于 %s MB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
LogContextHolder.setErrorMsg(errorMsg);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 认证异常-登录认证
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(NotLoginException.class)
@@ -188,15 +203,22 @@ public class GlobalExceptionHandler {
}
/**
* 拦截文件上传异常-超过上传大小限制
* 认证异常-权限认证
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
log.error("请求地址'{}'上传文件失败,文件大小超过限制", request.getRequestURI(), e);
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
String errorMsg = String.format("请上传小于 %s MB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
LogContextHolder.setErrorMsg(errorMsg);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(NotPermissionException.class)
public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
log.error("请求地址'{}'权限码校验失败'{}'", request.getRequestURI(), e);
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
}
/**
* 认证异常-角色认证
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(NotRoleException.class)
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
log.error("请求地址'{}',角色权限校验失败'{}'", request.getRequestURI(), e);
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
}
}

View File

@@ -18,6 +18,7 @@ package top.charles7c.cnadmin.common.model.dto;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Set;
import lombok.Data;
@@ -118,4 +119,14 @@ public class LoginUser implements Serializable {
* 登录时间
*/
private LocalDateTime loginTime;
/**
* 权限码集合
*/
private Set<String> permissions;
/**
* 角色编码集合
*/
private Set<String> roles;
}