1.pom 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.聲明自定義注解
2.1 聲明切面注解
import java.lang.annotation.*;
/**
* @author WANGCHENG
* @version 1.0
* @Description: 校驗(yàn)組合編輯權(quán)限注解
* @date 2023/08/04 20:12
*/
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckInstructionAuth {
/**
* 指令類型 默認(rèn):銷售
*/
CheckInstructionEditTypeEnum instructionType()
default CheckInstructionEditTypeEnum.SALE;
/**
* 操作類型,必選,用來錯誤提示。
* eg:選擇修改,校驗(yàn)提示:沒有XX組合的操作權(quán)限,不允許修改
*
* @return
*/
CheckInstructionEditTypeEnum.OperateTypeEnum operateType();
/**
* 校驗(yàn)數(shù)據(jù)類型 默認(rèn):當(dāng)前數(shù)據(jù)
*/
CheckInstructionEditTypeEnum.CheckDataTypeEnum checkDataType()
default CheckInstructionEditTypeEnum.CheckDataTypeEnum.CURRENT;
/**
* 獲取數(shù)據(jù)方式,默認(rèn):@BindingParam 標(biāo)注
*/
CheckInstructionEditTypeEnum.DataSourceEnum dataSource()
default CheckInstructionEditTypeEnum.DataSourceEnum.ANNOTATION;
/**
* 是否立即清除副本,默認(rèn)立即清除副本數(shù)據(jù)。
* 如果獲取數(shù)據(jù)方式為 THREAD_LOCAL,后續(xù)還需要使用該參數(shù),開發(fā)自行清除
*/
boolean isFlushThreadLocal() default true;
?2.1.1切面對應(yīng)枚舉
@AllArgsConstructor
public enum CheckInstructionEditTypeEnum {
SALE("SALE","銷售", "2"),
MARKET("MARKET","做市", "1"),
BID("BID","中標(biāo)", "2");
private String instructionCode;
/**
* 功能名稱
*/
private String name;
/**
* 組合分類標(biāo)簽值
*/
private String orgType;
public String getInstructionCode() {
return instructionCode;
}
public String getName() {
return name;
}
public String getOrgType() {
return orgType;
}
/**
* 參數(shù)類型來源
* DEFAULT_KEY 入?yún)?根據(jù)對象的默認(rèn)key 反射獲取,pid,pidList,pids,userId
* ANNOTATION 入?yún)⒏鶕?jù)注解獲取 和 @BindingParam 配合使用,可以是方法入?yún)⒆⒔饣蛘邔傩宰⒔?,推薦使用
* THREAD_LOCAL 副本(需要提前塞值)
*/
public enum DataSourceEnum {
DEFAULT_KEY,ANNOTATION,THREAD_LOCAL
}
/**
* DataSourceEnum 為 DEFAULT_KEY 時,默認(rèn)獲取的key
*/
public enum DataSourceDefaultKeyEnum {
pid,pidList,pids,userId
}
/**
* CURRENT 校驗(yàn)當(dāng)前數(shù)據(jù)
* GROUP 校驗(yàn)整組數(shù)據(jù),eg:索引號,code。組合下達(dá)指令時,可能 會整組索引指令下達(dá),需校驗(yàn)整組數(shù)據(jù)
*/
public enum CheckDataTypeEnum{
CURRENT,GROUP
}
/**
* 操作類型,用來錯誤信息提示
*/
@AllArgsConstructor
public enum OperateTypeEnum{
ADD("ADD","新增"),
UPDATE("UPDATE","修改"),
DELETE("DELETE","刪除"),
RELEASE("RELEASE","下達(dá)"),
DECILITER("DECILITER","拆合單");
private String code;
private String name;
/**
* 操作 code
* @return
*/
public String getCode() {
return code;
}
/**
* 操作名稱
* @return
*/
public String getName() {
return name;
}
}
}
?2.2 聲明綁定參數(shù)注解
/**
* @author WANGCHENG
* @version 1.0
* @Description: 標(biāo)注入?yún)⒌臄?shù)據(jù)類型,配合檢驗(yàn)組合權(quán)限
* @date 2023/08/04 20:12
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface BindingParam {
BindingParamTypeEnum value();
}
綁定參數(shù)對應(yīng)枚舉
public enum BindingParamTypeEnum {
pid,pidList,userId,isConfirm
}
3 切面邏輯
@Aspect
@Component
@Slf4j
public class CheckInstructionAuthAspect {
// 切點(diǎn)
@Pointcut(value = "@annotation(com.dsd.study.annotion.CheckInstructionAuth)")
public void pointcut() {}
/**
* 切點(diǎn)配置,CheckCombinedEditAuth 注解的地方
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object CheckCombinedEditAuth(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String targetClassName = joinPoint.getTarget().getClass().getCanonicalName();
String targetMethodName = joinPoint.getSignature().getName();
String target=targetClassName+"#"+targetMethodName;
log.info("校驗(yàn)指令權(quán)限目標(biāo)方法為={}",target);
if(!editAuthSwitch()){
return joinPoint.proceed();
}
//獲取方法,此處可將signature強(qiáng)轉(zhuǎn)為MethodSignature
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//參數(shù)注解,1維是參數(shù),2維是注解
CheckInstructionAuth checkInstructionAuth = method.getAnnotation(CheckInstructionAuth.class);
Object[] args = joinPoint.getArgs();
log.info("校驗(yàn)指令權(quán)限目標(biāo)方法入?yún)?{},={}",target,JSON.toJSONString(args));
CheckInstructionEditTypeEnum checkInstructionEditTypeEnum = checkInstructionAuth.instructionType();
Map<String, Object> paramData =new HashMap<>();
if(CheckInstructionEditTypeEnum.SALE.equals(checkInstructionEditTypeEnum)){
paramData=analysisDataSource(args,checkInstructionAuth,method);
}
log.info("{}校驗(yàn)指令權(quán)限解析入?yún)?{}",target,JSON.toJSONString(paramData));
String userId =(String) paramData.get(BindingParamTypeEnum.userId.name());
List<String> pidList =(List<String>)paramData.get(BindingParamTypeEnum.pidList.name());
if(StringUtils.isBlank(userId)){
log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查詢到用戶信息");
throw new BusinessException("未查詢到用戶信息");
}
List<String> userAuthList = getUserAuthList(userId, checkInstructionAuth);
if(pidList==null || pidList.size()==0){
log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查詢到校驗(yàn)的指令");
throw new BusinessException("未查詢到校驗(yàn)的指令");
}
List<String> checkDataList = getCheckData(pidList, checkInstructionAuth);
//權(quán)限對比
if(!compareAuth(userAuthList,checkDataList)){
log.info("userId={},權(quán)限對比,userAuthList={},checkDataList={}",
JSON.toJSONString(userAuthList),JSON.toJSONString(checkDataList));
checkDataList.removeAll(userAuthList);
if(checkDataList.size()>0){
//數(shù)據(jù)權(quán)限大于用戶擁有權(quán)限
List<String> combinedNameList = getCombinedNameByVcRemarksKey(checkDataList);
//沒有權(quán)限的組合
String combinedNameListStr = combinedNameList.stream()
.collect(Collectors.joining(","));
String operateName = checkInstructionAuth.operateType().getName();
log.info("userId={},權(quán)限不相等,沒有"+combinedNameListStr+"組合的操作權(quán)限,不允許{}",userId,operateName);
//沒有XX組合的操作權(quán)限,不允許新增
throw new BusinessException("沒有"+combinedNameListStr+"組合的操作權(quán)限,不允許"+operateName);
}
}
log.info("{}耗時為={}毫秒",target,System.currentTimeMillis()-startTime);
return joinPoint.proceed();
}
/**
* 根據(jù)組合code獲取組合名稱
* @param vcRemarksKeyList
* @return
*/
private List<String> getCombinedNameByVcRemarksKey(List<String> vcRemarksKeyList){
//偽代碼
return new ArrayList<>();
}
/**
* 比較List<string> 是否相等
* @param userAuthList
* @param checkDataList
* @return
*/
private boolean compareAuth(List<String> userAuthList,List<String> checkDataList){
if(userAuthList.size()!=checkDataList.size()){
return false;
}
String[] userAuthArry = userAuthList.toArray(new String[]{});
String[] checkDataArry = checkDataList.toArray(new String[]{});
Arrays.sort(userAuthArry);
Arrays.sort(checkDataArry);
return Arrays.equals(userAuthArry, checkDataArry);
}
/**
* 解析入?yún)?shù)據(jù),拿到需要的入?yún)? * @param args
* @return
*/
private Map<String,Object> analysisDataSource(Object[] args, CheckInstructionAuth checkCombinedEditAuth, Method method)
throws IllegalAccessException, BusinessException {
Map<String,Object> result=new HashMap<>();
String operateId=null;
List<String> pidList=new ArrayList<>();
CheckInstructionEditTypeEnum.DataSourceEnum dataSource = checkCombinedEditAuth.dataSource();
try {
if(CheckInstructionEditTypeEnum.DataSourceEnum.DEFAULT_KEY.equals(dataSource)){
//反射,獲取默認(rèn)的 key
for(Object obj:args){
if(obj instanceof Map){
Map<String,Object> objMap=(Map<String,Object>)obj;
getParamByDefaultKey(objMap,result);
}else{
//自定義對象默認(rèn)key
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field:fields){
field.setAccessible(true);
String name = field.getName();
if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name().equals(name)){
if(obj instanceof String){
operateId=(String)field.get(obj);
}
}else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pid.name().equals(name) ||
CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name().equals(name) ||
CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pids.name().equals(name)){
// key 為 pid,pids,pidList
if(obj instanceof String){
pidList.add((String)field.get(obj));
}else if(obj instanceof List){
pidList.addAll((List)field.get(obj));
}
}
}
}
}
}else if(CheckInstructionEditTypeEnum.DataSourceEnum.ANNOTATION.equals(dataSource)){
//注解,入?yún)⒓幼⒔? Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int index = 0;
for(Annotation[] annotationx:parameterAnnotations){
Object param=args[index];
for(Annotation annotationy:annotationx){
if(annotationy instanceof BindingParam){
BindingParam bindingParam=(BindingParam)annotationy;
getParamByBindingParam(bindingParam, param,result);
}
}
index++;
}
//注解,屬性加注解,注意:會覆蓋入?yún)⒆⒔獾娜≈? for(Integer i=0;i<args.length;i++){
Object obj=args[i];
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field:fields){
field.setAccessible(true);
//如果字段上有自定義注解@BindingParam
BindingParam bindingParam = field.getAnnotation(BindingParam.class);
getParamByBindingParam(bindingParam, field.get(obj),result);
}
}
}else if(CheckInstructionEditTypeEnum.DataSourceEnum.THREAD_LOCAL.equals(dataSource)){
operateId = (String) ThreadLocalUtils.get(BindingParamTypeEnum.userId.name());
List<String> pidListResult = (List<String>)ThreadLocalUtils.get(BindingParamTypeEnum.pidList.name());
pidList.addAll(pidListResult);
}
} catch (BusinessException e) {
throw e;
} finally {
if(CheckInstructionEditTypeEnum.DataSourceEnum.THREAD_LOCAL.equals(dataSource) &&
checkCombinedEditAuth.isFlushThreadLocal()){
ThreadLocalUtils.delete(BindingParamTypeEnum.userId.name());
ThreadLocalUtils.delete(BindingParamTypeEnum.pidList.name());
}
}
if(StringUtils.isNotBlank(operateId)){
result.put(BindingParamTypeEnum.userId.name(),operateId);
}
if(pidList.size()>0){
result.put(BindingParamTypeEnum.pidList.name(),pidList);
}
return result;
}
/**
* 入?yún)镸ap 解析數(shù)據(jù)
* @param map
* @param result
* @return
*/
private Map<String,Object> getParamByDefaultKey(Map<String,Object> map,Map<String,Object> result){
if(map==null){
return result;
}
for(Map.Entry<String,Object> mapx:map.entrySet()){
String key = mapx.getKey();
Object value = mapx.getValue();
if(value==null){
continue;
}
if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name().equals(key)){
if(value instanceof String){
result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name(),(String)value);
}
} else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pid.name().equals(key)){
if(value instanceof String){
result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name()
,Arrays.asList((String)value));
}
} else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name().equals(key) ||
CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pids.name().equals(key)){
if(value instanceof List){
if(value instanceof List){
List<Object> objList=(List<Object>)value;
if(objList.size()>0 && objList.get(0) instanceof String){
result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name()
,(List<String>)value);
}
}
}
}
}
return result;
}
/**
* 根據(jù)注解獲取入?yún)? * @param bindingParam
* @param param
* @return
*/
private Map<String,Object> getParamByBindingParam(BindingParam bindingParam, Object param, Map<String,Object> result)
throws BusinessException {
if(bindingParam==null){
log.info("沒有 @BindingParam 綁定參數(shù)");
return result;
}
if(bindingParam!=null){
BindingParamTypeEnum value = bindingParam.value();
if(BindingParamTypeEnum.pid.equals(value)){
if(param instanceof String){
result.put(BindingParamTypeEnum.pidList.name()
, Arrays.asList((String)param).stream().collect(Collectors.toList()));
}else{
log.info("@BindingParam 注解類型使用錯誤,pid只能綁定String類型");
throw new BusinessException("@BindingParam 注解類型使用錯誤,pid只能綁定String類型");
}
}else if(BindingParamTypeEnum.pidList.equals(value)){
if(param instanceof List){
List<Object> objList=(List)param;
if(objList!=null && objList.size()>0){
if(objList.get(0) instanceof String){
result.put(BindingParamTypeEnum.pidList.name(),(List<String>)param);
}else {
log.info("@BindingParam 注解類型值使用錯誤,pidList只能綁定List<String>類型");
throw new BusinessException("@BindingParam 注解類型值使用錯誤,pidList只能綁定List<String>類型");
}
}
}else{
log.info("@BindingParam 注解類型值使用錯誤,pidList只能綁定List類型");
throw new BusinessException("@BindingParam 注解類型值使用錯誤,pidList只能綁定List類型");
}
}else if(BindingParamTypeEnum.userId.equals(value)){
if(param instanceof String){
result.put(BindingParamTypeEnum.userId.name(),(String)param);
}else{
log.info("@BindingParam 注解類型值使用錯誤,userId只能綁定String類型");
throw new BusinessException("@BindingParam 注解類型值使用錯誤,userId只能綁定String類型");
}
}
}
return result;
}
/**
* 用戶存在的編輯組合權(quán)限
* @param userId
* @param checkCombinedEditAuth
* @return
*/
private List<String> getUserAuthList(String userId,CheckInstructionAuth checkCombinedEditAuth){
//獲取用戶權(quán)限,業(yè)務(wù)代碼
return new ArrayList<>();
}
/**
* 需要校驗(yàn)的組合數(shù)據(jù)
* @param pidList
* @param checkCombinedEditAuth
* @return
*/
private List<String> getCheckData(List<String> pidList, CheckInstructionAuth checkCombinedEditAuth){
CheckInstructionEditTypeEnum checkInstructionEditTypeEnum = checkCombinedEditAuth.instructionType();
List<String> checkDataList=new ArrayList<>();
if(CheckInstructionEditTypeEnum.SALE.equals(checkInstructionEditTypeEnum)){
//獲取需要校驗(yàn)的數(shù)據(jù)
}
//校驗(yàn)其它
return checkDataList;
}
/**
* 校驗(yàn)權(quán)限開關(guān),redis 控制。默認(rèn)開啟=1;
* @return
*/
private boolean editAuthSwitch(){
return true;
}
}
?4涉及的Util?
4.1 ThreadLocalUtil?
public class ThreadLocalUtils {
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL =
ThreadLocal.withInitial(() -> new ConcurrentHashMap<>(16));
/**
* 獲取到ThreadLocal中值
*
* @return ThreadLocal存儲的是Map
*/
public static Map<String, Object> getThreadLocal() {
return THREAD_LOCAL.get();
}
/**
* 從ThreadLocal中的Map獲取值
*
* @param key Map中的key
* @param <T> Map中的value的類型
* @return Map中的value值 可能為空
*/
public static <T> T get(String key) {
return get(key, null);
}
/**
* 從ThreadLocal中的Map獲取值
*
* @param key Map中的key
* @param defaultValue Map中的value的為null 是 的默認(rèn)值
* @param <T> Map中的value的類型
* @return Map中的value值 可能為空
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key, T defaultValue) {
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return null;
}
return (T) Optional.ofNullable(map.get(key)).orElse(defaultValue);
}
/**
* ThreadLocal中的Map設(shè)置值
*
* @param key Map中的key
* @param value Map中的value
*/
public static void set(String key, Object value) {
Map<String, Object> map = THREAD_LOCAL.get();
map.put(key, value);
}
/**
* ThreadLocal中的Map 添加Map
*
* @param keyValueMap 參數(shù)map
*/
public static void set(Map<String, Object> keyValueMap) {
Map<String, Object> map = THREAD_LOCAL.get();
map.putAll(keyValueMap);
}
/**
* 刪除ThreadLocal中的Map 中的value
*
* @param key Map中的key
*/
public static void delete(String key) {
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return;
}
map.remove(key);
}
/**
* 刪除ThreadLocal中的Map
*/
public static void remove() {
THREAD_LOCAL.remove();
}
/**
* 從ThreadLocal中的Map獲取值 根據(jù)可key的前綴
*
* @param prefix key 的前綴
* @param <T> Map中的value的類型
* @return 符合條件的Map
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, T> fetchVarsByPrefix(String prefix) {
Map<String, T> vars = new HashMap<>(16);
if (StringUtils.isBlank(prefix)) {
return vars;
}
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return vars;
}
return map.entrySet().stream().filter(test -> test.getKey().startsWith(prefix))
.collect(Collectors.toMap(Map.Entry::getKey, time -> (T) time.getValue()));
}
/**
* 刪除ThreadLocal中的Map 中的Value 按 Map中的Key的前綴
*
* @param prefix Map中的Key的前綴
*/
public static void deleteVarsByPrefix(String prefix) {
if (StringUtils.isBlank(prefix)) {
return;
}
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return;
}
map.keySet().stream().filter(o -> o.startsWith(prefix)).collect(Collectors.toSet()).forEach(map::remove);
}
}
4.2? 自定義異常
public class BusinessException extends Exception{
private static final long serialVersionUID = -3463054564635276929L;
/**
* 錯誤碼
*/
private String errCode;
/**
* 錯誤描述
*/
private String errDesc;
public BusinessException() {
super();
}
public BusinessException(String errDesc) {
super(errDesc);
this.errDesc = errDesc;
}
public BusinessException(String errCode, String errDesc) {
super(errCode);
this.errCode = errCode;
this.errDesc = errDesc;
}
public String getErrCode() {
return errCode;
}
public String getErrDesc() {
return errDesc;
}
}
5 使用示例
@Service
public class TestServiceImpl implements TestService {
@CheckInstructionAuth(operateType = CheckInstructionEditTypeEnum.OperateTypeEnum.RELEASE)
@Override
public void getData(Map<String, Object> map,@BindingParam(BindingParamTypeEnum.pidList) String userId) {
System.out.println("testAnnotation");
}
@CheckInstructionAuth(operateType = CheckInstructionEditTypeEnum.OperateTypeEnum.RELEASE)
@Override
public void getData(List<UserDto> userDto) {
System.out.println("testAnnotation");
}
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-634990.html
文章來源:http://www.zghlxwxcb.cn/news/detail-634990.html
到了這里,關(guān)于spring自定義注解+aop+@BindingParam的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!