使用aop切面,然后通过环绕通知的形式,对请求的接口做权限控制,区分当前用户的等级权限,根据当前用户等级权限的查询内容。
一、权限参数定义
1、自定义注解
package com.net.microservices.test.project.infrastructure.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Permissions {
}
2、权限参数获取方法
package com.net.microservices.test.project.infrastructure.aspect.dto;
import java.util.Set;
public interface BasePermissions {
/**
* 获取是否处理
*
* @return Boolean
*/
Boolean getHandle();
/**
* 设置是否处理
*
* @param handle Boolean
*/
void setHandle(Boolean handle);
/**
* 获取权限等级
*
* @return Integer
*/
Integer getLevel();
/**
* 设置权限等级
*
* @param level Integer
*/
void setLevel(Integer level);
/**
* 获取地市范围
*
* @return Set
*/
Set<String> getCityList();
/**
* 设置地市范围
*
* @param cityList Set
*/
void setCityList(Set<String> cityList);
}
3、自定义权限参数,可以获取当前用户等级权限(这里的参数需要实现BasePermissions接口)
package com.net.microservices.test.project.infrastructure.aspect.dto;
import lombok.Data;
import java.util.Set;
@Data
public class PermissionsDTO implements BasePermissions{
/**
* 是否处理权限
*/
private Boolean handle;
/**
* 权限级别
*/
private Integer level;
/**
* 区县范围
*/
private Set<String> cityList;
}
二、自定义权限切面处理逻辑
1、创建枚举值
package com.net.microservices.test.infrastructure.enums;
import lombok.Getter;
@Getter
public enum NumberEnum {
/**
* 正确
*/
TRUE("true",1),
/**
* 错误
*/
FALSE("false",0),
/**
* 零的枚举
*/
ZERO("0", 0),
/**
* 一的枚举
*/
ONE("1", 1),
/**
* 二的枚举
*/
TWO("2", 2),
/**
* 三的枚举
*/
THREE("3", 3),
/**
* 四的枚举
*/
FOUR("4", 4);
final String name;
final Integer code;
NumberEnum(String name, Integer code) {
this.name = name;
this.code = code;
}
}
package com.net.microservices.test.project.infrastructure.enums;
import lombok.Getter;
@Getter
public enum CityEnum {
/**
* 成都
*/
CHENG_DU(1, "成都"),
/**
* 宜宾
*/
YI_BIN(2, "宜宾"),
/**
* 眉山
*/
MEI_SHAN(3, "眉山"),
/**
* 乐山
*/
LE_SHAN(4, "乐山"),
/**
* 攀枝花
*/
PAN_ZHI_HUA(5, "攀枝花"),
/**
* 凉山
*/
LIANG_SHAN(6, "凉山"),
/**
* 资阳
*/
ZI_YANG(7, "资阳"),
/**
* 自贡
*/
ZI_GONG(8, "自贡"),
/**
* 内江
*/
NEI_JIANG(9, "内江"),
/**
* 内江
*/
LU_ZHOU(10, "泸州"),
/**
* 德阳
*/
DE_YANG(11, "德阳"),
/**
* 绵阳
*/
MIAN_YANG(12, "绵阳"),
/**
* 广元
*/
GUANG_YUAN(13, "广元"),
/**
* 巴中
*/
BA_ZHONG(14, "巴中"),
/**
* 遂宁
*/
SUI_NING(15, "遂宁"),
/**
* 南充
*/
NAN_CHONG(16, "南充"),
/**
* 达州
*/
DA_ZHOU(17, "达州"),
/**
* 广安
*/
GUANG_AN(18, "广安"),
/**
* 雅安
*/
YA_AN(19, "雅安"),
/**
* 甘孜
*/
GAN_ZI(20, "甘孜"),
/**
* 阿坝
*/
A_BA(21, "阿坝"),
/**
* 省
*/
S_GS_BB(22, "省"),
;
/**
* 地市编码
*/
final int cityCode;
/**
* 地市名称
*/
final String cityName;
CityEnum(int cityCode, String cityName) {
this.cityCode = cityCode;
this.cityName = cityName;
}
public static CityEnum getByCityCode(int cityCode) {
CityEnum[] values = CityEnum.values();
for (CityEnum value : values) {
if (value.cityCode == cityCode) {
return value;
}
}
return null;
}
public static CityEnum getByCityCode(Long cityCode) {
return getByCityCode((int) cityCode.longValue());
}
public static String getNameByCode(String cityCode) {
try {
int cityId = Integer.parseInt(cityCode);
CityEnum byRegionCode = getByCityCode(cityId);
if (null != byRegionCode) {
return byRegionCode.cityName;
}
} catch (NumberFormatException ignored) {
}
return null;
}
public static String getNameByCode(Long cityCode) {
return getNameByCode(cityCode.toString());
}
}
2、通过环绕注解@Around,自定义aop切面定义
package com.net.microservices.test.project.infrastructure.aspect;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.net.common.entity.OsUser;
import com.net.common.enums.ResponseData;
import com.net.common.enums.ResponseDataPage;
import com.net.common.util.StringUtil;
import com.net.common.util.TransEnumUtil;
import com.net.microservices.test.infrastructure.enums.NumberEnum;
import com.net.microservices.test.project.infrastructure.aspect.dto.BasePermissions;
import com.net.microservices.test.project.infrastructure.enums.CityEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Aspect
@Order(value = 999)
@Component
public class AspectPermissions {
/**
* 环绕通知
*
* @param joinPoint 连接点
* @return Object
* @throws Throwable 异常
*/
@Around(value = "@annotation(permissions)")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint, Permissions permissions) throws Throwable {
StopWatch stopWatch = StopWatch.createStarted();
log.info("【权限验证】执行接口开始,方法={}", joinPoint.getSignature());
Object[] args = joinPoint.getArgs();
BasePermissions basePermissions = null;
if (null != args) {
for (Object arg : args) {
if (arg instanceof BasePermissions) {
basePermissions = (BasePermissions) arg;
}
}
}
if (null == basePermissions) {
log.info("参数中未发现继承权限dto!");
} else {
OsUser user = OsUser.getInstance();
basePermissions.setHandle(false);
Integer level = getLevel(user);
basePermissions.setLevel(level);
if (NumberEnum.ZERO.getCode().equals(level)) {
log.info("账号具有省级权限!");
} else if (NumberEnum.ONE.getCode().equals(level)) {
log.info("账号具有地市管理员权限!");
basePermissions.setCityList(CollectionUtil.set(false, CityEnum.getNameByCode(user.getDomainId())));
} else if (NumberEnum.TWO.getCode().equals(level)) {
basePermissions.setCityList(CollectionUtil.set(false, CityEnum.getNameByCode(user.getDomainId())));
log.info("账号具有运营人员权限!");
} else {
log.info("账号无权限!");
return ResponseData.fail("账号无权限查看数据!");
}
}
Object proceed = joinPoint.proceed(joinPoint.getArgs());
if (null != basePermissions && !basePermissions.getHandle()) {
log.info("接口未处理权限,权限过滤未生效!");
}
stopWatch.stop();
long watchTime = stopWatch.getTime();
log.info("【权限验证-结束】执行接口结束,方法={}, 返回值={},耗时={} (毫秒)", joinPoint.getSignature(), proceed, watchTime);
return proceed;
}
/**
* 获取权限等级 , 99 代表无配置
*
* @param user 用户信息
* @return 等级
*/
private Integer getLevel(OsUser user) {
Map<String, String> enumMap = TransEnumUtil.getEnumMap("test_permissions_project");
Map<String, Integer> perMapperMap = new HashMap<>(3);
for (String s : enumMap.keySet()) {
String value = enumMap.get(s);
if (StringUtil.checkStr(value)) {
String[] split = value.split(";");
for (String v : split) {
perMapperMap.putIfAbsent(v, Integer.parseInt(s));
}
}
}
String roleIds = user.getRoleIds();
int level = 99;
if (StringUtil.checkStr(roleIds)) {
String[] roleIdItems = roleIds.split(",");
for (String roleIdItem : roleIdItems) {
// 对比配置用户id的权限等级
level = Math.min(level, MapUtil.getInt(perMapperMap, roleIdItem, 99));
}
}
return level;
}
}
3、权限验证方法:这里需要构造,然后获取用户等级,所属城市,权限(这里的权限对应自己配置的枚举值)
package com.net.microservices.test.project.infrastructure.aspect;
public interface LevelHandle {
/**
* 进行处理
*/
void handle();
}
package com.net.microservices.test.project.infrastructure.aspect;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.net.common.entity.OsUser;
import com.net.microservices.test.infrastructure.enums.NumberEnum;
import com.net.microservices.test.project.infrastructure.aspect.dto.BasePermissions;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.Getter;
import lombok.Setter;
import java.util.Set;
@Getter
@Setter
public class PermissionsHandle<R> {
SFunction<R, ?> cityName;
SFunction<R, ?> staffNo;
LevelHandle twoLevel;
LevelHandle oneLevel;
LevelHandle zeroLevel;
OsUser user;
public static <R> PermissionsHandle<R> build(SFunction<R, ?> cityName, LevelHandle twoLevel) {
PermissionsHandle<R> handle = new PermissionsHandle<>();
handle.setCityName(cityName);
handle.setTwoLevel(twoLevel);
return handle;
}
public static <R> PermissionsHandle<R> build(SFunction<R, ?> cityName, LevelHandle twoLevel, OsUser user) {
PermissionsHandle<R> build = PermissionsHandle.build(cityName, twoLevel);
build.setUser(user);
return build;
}
public static <R> PermissionsHandle<R> build(SFunction<R, ?> cityName, SFunction<R, ?> staffNo, OsUser user) {
PermissionsHandle<R> handle = new PermissionsHandle<>();
handle.setCityName(cityName);
handle.setUser(user);
handle.setStaffNo(staffNo);
return handle;
}
public void handle(BasePermissions basePermissions) {
Integer level = basePermissions.getLevel();
if (NumberEnum.ZERO.getCode().equals(level)) {
if (null != zeroLevel) {
zeroLevel.handle();
}
} else if (NumberEnum.ONE.getCode().equals(level)) {
if (null != oneLevel) {
oneLevel.handle();
}
} else if (NumberEnum.TWO.getCode().equals(level)) {
if (null != twoLevel) {
twoLevel.handle();
}
}
basePermissions.setHandle(true);
}
public void handleDefault(BasePermissions basePermissions, MPJLambdaWrapper<R> lqw) {
setOneLevel(() -> {
addCityCondition(basePermissions, lqw);
});
handle(basePermissions);
}
public void addCityCondition(BasePermissions basePermissions, MPJLambdaWrapper<R> lqw) {
if (null == user) {
user = OsUser.getInstance();
}
Set<String> cityList = basePermissions.getCityList();
if (CollectionUtil.isNotEmpty(cityList)) {
lqw.in(cityName, basePermissions.getCityList());
}
}
}
三、用户权限的使用方式
例子1:
1.1、接口调用通过注解获取用户等级
@ApiOperation(value = "查询项目统计")
@Permissions
@PostMapping(value = {"/project/stat"})
public ResponseData<ProjectStatRespDTO> projectStat(@Validated @RequestBody List<String> projectStatusList, PermissionsDTO permissionsDTO) {
// 通过注解@Permissions获取permissionsDTO参数
return ResponseData.ok(projectInfoService.queryProjectStat(projectStatusList, permissionsDTO));
}
1.2、业务代码处理
@Override
public ProjectStatRespDTO queryProjectStat(List<String> projectStatusList, PermissionsDTO permissionsDTO) {
OsUser osUser = OsUser.getInstance();
ProjectStatRespDTO dto = new ProjectStatRespDTO();
// 通过获取permissionsDTO等到用户权限等级
List<Map<String, Object>> projectLevelStatList = projectInfoDao.getBaseMapper().getProjectLevelSum(osUser, permissionsDTO, projectStatusList);
Map<String, List<Map<String, Object>>> mapProjectLevelStatList = projectLevelStatList.stream().collect(Collectors.groupingBy(e -> e.get("level").toString()));
List<Map<String, Object>> projectLevelList = new ArrayList<>();
Map<String, String> projectLevelEnumMap = TransEnumUtil.getEnumMap("test_s_project_level");
for ( Map.Entry<String,String> projectLevel : projectLevelEnumMap.entrySet()) {
if (!CollectionUtils.isEmpty(mapProjectLevelStatList.get(projectLevel.getValue()))) {
projectLevelList.add(mapProjectLevelStatList.get(projectLevel.getValue()).get(0));
} else {
Map<String, Object> mapStats = new HashMap<>(2);
mapStats.put("levelCount", 0);
mapStats.put("level", projectLevel.getValue());
projectLevelList.add(mapStats);
}
}
// 表示已经处理了权限
permissionsDTO.setHandle(true);
dto.setProjectLevelList(projectLevelList);
return dto;
}
例子2:
2.1 调用接口
@ApiOperation(value = "查询项目分页信息")
@Permissions
@PostMapping(value = {"/project/queryProject/page"})
public ResponseData<PageDataEntity<ProjectInfoRespDTO>> queryProjectPage(@Validated @RequestBody ProjectInfoReqDTO req) {
return ResponseData.ok(projectInfoService.queryProjectPage(req));
}
参数需要继承PermissionsPageDTO.class获取父类方法
package com.net.microservices.test.project.domain.dto.request;
import com.net.microservices.test.project.infrastructure.aspect.dto.PermissionsPageDTO;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProjectInfoReqDTO extends PermissionsPageDTO implements Serializable{
/**
* 项目级别
*/
@ApiModelProperty(value = "项目级别",example = "S")
private String projectLevel;
/**
* 项目名称
*/
@ApiModelProperty(value = "项目名称",example = "客流统计监控项目")
private String projectName;
/**
* 项目编码
*/
@ApiModelProperty(value = "项目编码",example = "4100020")
private String projectCode;
}
继承PageParamDTO.class和实现 BasePermissions接口
package com.net.microservices.test.project.infrastructure.aspect.dto;
import com.cttnet.microservices.test.infrastructure.common.dto.PageParamDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Set;
@EqualsAndHashCode(callSuper = true)
@Data
public class PermissionsPageDTO extends PageParamDTO implements BasePermissions {
/**
* 是否处理权限
*/
private Boolean handle;
/**
* 权限级别
*/
private Integer level;
/**
* 区县范围
*/
private Set<String> cityList;
}
package com.net.microservices.test.infrastructure.common.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* @author Yuzuru
* 统一分页参数对象
*/
@Data
@ApiModel("统一分页参数对象")
@Valid
public class PageParamDTO {
@NotNull
@ApiModelProperty(value = "当前页码", example = "1", required = true)
Integer currentPage;
@ApiModelProperty(value = "页面数据条数", example = "20", required = true)
@NotNull
Integer pageSize;
}
2.2业务代码处理
权限验证
@DS("testDB")
@Service
public class ProjectInfoServiceImpl extends ServiceImpl<ProjectInfoMapper, ProjectInfo>
implements ProjectInfoService{
// --------权限验证 String admin = "系统管理员";
// 1、先通过PermissionsHandle构造方法,生产获取用户的城市,再获取用户的等级
MPJLambdaWrapper<ProjectInfo> lqw = new MPJLambdaWrapper<>();
lqw.selectAs(ProjectInfo::getProjectType, ProjectInfoRespDTO::getProjectType);
PermissionsHandle<ProjectInfo> permissionsHandle = PermissionsHandle.build(ProjectInfo::getCity, () -> {
lqw.eq(ProjectUndertake::getUndertakenByStaffNo, osUser.getStaffNo());
}, osUser);
permissionsHandle.handleDefault(dto, lqw);
IPage<ProjectInfoRespDTO> projectPage = projectInfoDao.selectJoinListPage(new Page<ProjectInfo>(req.getCurrentPage(), req.getPageSize()), ProjectInfoRespDTO.class, lqw);
}
2.2.1、通过PermissionsHandle的构造方法,设置用户的等级、所属城市、权限。
2.2.2、然后再通过handle()方法进行处理
2.2.3、通过basePermissions.setHandle(true);代码表示权限设置成功,环绕通知不会报错。
public static <R> PermissionsHandle<R> build(SFunction<R, ?> cityName, SFunction<R, ?> staffNo, OsUser user) {
PermissionsHandle<R> handle = new PermissionsHandle<>();
handle.setCityName(cityName);
handle.setUser(user);
handle.setStaffNo(staffNo);
return handle;
}
public void handle(BasePermissions basePermissions) {
Integer level = basePermissions.getLevel();
if (NumberEnum.ZERO.getCode().equals(level)) {
if (null != zeroLevel) {
zeroLevel.handle();
}
} else if (NumberEnum.ONE.getCode().equals(level)) {
if (null != oneLevel) {
oneLevel.handle();
}
} else if (NumberEnum.TWO.getCode().equals(level)) {
if (null != twoLevel) {
twoLevel.handle();
}
}
basePermissions.setHandle(true);
}
public void handleDefault(BasePermissions basePermissions, MPJLambdaWrapper<R> lqw) {
setOneLevel(() -> {
addCityCondition(basePermissions, lqw);
});
handle(basePermissions);
}
public void addCityCondition(BasePermissions basePermissions, MPJLambdaWrapper<R> lqw) {
if (null == user) {
user = OsUser.getInstance();
}
Set<String> cityList = basePermissions.getCityList();
if (CollectionUtil.isNotEmpty(cityList)) {
lqw.in(cityName, basePermissions.getCityList());
}
}
上述代码,是处理接口调用的时候,通过环绕通知的模式,查询当前用户账号的权限,然后根据权限查询所属数据,数据分权处理。