添加权限 按天授权
This commit is contained in:
@@ -19,4 +19,10 @@ public @interface FuncPermission {
|
|||||||
* 功能权限编码,对应 bg_func_permission.func_code
|
* 功能权限编码,对应 bg_func_permission.func_code
|
||||||
*/
|
*/
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务资源标识表达式,SpEL,从方法参数中取值
|
||||||
|
* <p>如 #jobId、#dto.jobId,为空表示不做当日幂等,每次调用均扣减</p>
|
||||||
|
*/
|
||||||
|
String key() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,25 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.jiayunet.annotation.FuncPermission;
|
import org.jiayunet.annotation.FuncPermission;
|
||||||
import org.jiayunet.service.FuncPermissionService;
|
import org.jiayunet.service.FuncPermissionService;
|
||||||
import org.jiayunet.tool.UserSecurityTool;
|
import org.jiayunet.tool.UserSecurityTool;
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||||
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 功能权限校验切面
|
* 功能权限校验切面
|
||||||
* <p>拦截 @FuncPermission 注解,校验权限并扣减库存后放行,业务异常时自动回退</p>
|
* <p>拦截 @FuncPermission 注解,当日已解锁的资源直接放行,否则校验权限并扣减库存后放行,业务异常时自动回退</p>
|
||||||
*
|
*
|
||||||
* @author zk
|
* @author zk
|
||||||
*/
|
*/
|
||||||
@@ -21,6 +31,10 @@ import org.springframework.stereotype.Component;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class FuncPermissionAspect {
|
public class FuncPermissionAspect {
|
||||||
|
|
||||||
|
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||||
|
|
||||||
|
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private FuncPermissionService funcPermissionService;
|
private FuncPermissionService funcPermissionService;
|
||||||
|
|
||||||
@@ -29,11 +43,18 @@ public class FuncPermissionAspect {
|
|||||||
|
|
||||||
Long userId = UserSecurityTool.getUserId();
|
Long userId = UserSecurityTool.getUserId();
|
||||||
String funcCode = funcPermission.value();
|
String funcCode = funcPermission.value();
|
||||||
|
String bizKey = resolveBizKey(joinPoint, funcPermission.key());
|
||||||
|
|
||||||
log.info("功能权限校验 userId:{} funcCode:{}", userId, funcCode);
|
log.info("功能权限校验 userId:{} funcCode:{} bizKey:{}", userId, funcCode, bizKey);
|
||||||
|
|
||||||
|
// 当日已解锁该资源,直接放行不再扣减
|
||||||
|
if (!bizKey.isEmpty() && funcPermissionService.existsTodayUnlock(userId, funcCode, bizKey)) {
|
||||||
|
log.info("当日已解锁,跳过扣减 userId:{} funcCode:{} bizKey:{}", userId, funcCode, bizKey);
|
||||||
|
return joinPoint.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
// 校验权限 + 扣减库存,返回使用记录ID
|
// 校验权限 + 扣减库存,返回使用记录ID
|
||||||
Long logId = funcPermissionService.checkAndDeduct(userId, funcCode);
|
Long logId = funcPermissionService.checkAndDeduct(userId, funcCode, bizKey);
|
||||||
|
|
||||||
// 放行,业务异常时回退使用记录和库存
|
// 放行,业务异常时回退使用记录和库存
|
||||||
try {
|
try {
|
||||||
@@ -44,4 +65,27 @@ public class FuncPermissionAspect {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析业务资源标识
|
||||||
|
* <p>注解 key 为 SpEL 表达式,从方法参数取值,如 #jobId、#dto.jobId</p>
|
||||||
|
*
|
||||||
|
* @param joinPoint 连接点
|
||||||
|
* @param keyExpression SpEL 表达式
|
||||||
|
* @return 业务资源标识实际值,表达式为空或求值为 null 时返回空串
|
||||||
|
*/
|
||||||
|
private String resolveBizKey(ProceedingJoinPoint joinPoint, String keyExpression) {
|
||||||
|
if (keyExpression == null || keyExpression.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
Method method = AopUtils.getMostSpecificMethod(signature.getMethod(), joinPoint.getTarget().getClass());
|
||||||
|
|
||||||
|
EvaluationContext context = new MethodBasedEvaluationContext(
|
||||||
|
joinPoint.getTarget(), method, joinPoint.getArgs(), PARAMETER_NAME_DISCOVERER);
|
||||||
|
|
||||||
|
Object value = EXPRESSION_PARSER.parseExpression(keyExpression).getValue(context);
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import org.jiayunet.exception.BusinessException;
|
|||||||
import org.jiayunet.exception.BusinessExpCodeEnum;
|
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||||
import org.jiayunet.mapper.FuncPermissionMapper;
|
import org.jiayunet.mapper.FuncPermissionMapper;
|
||||||
import org.jiayunet.mapper.UserFuncPermissionStockMapper;
|
import org.jiayunet.mapper.UserFuncPermissionStockMapper;
|
||||||
|
import org.jiayunet.mapper.UserFuncUnlockMapper;
|
||||||
import org.jiayunet.mapper.UserFuncUsageLogMapper;
|
import org.jiayunet.mapper.UserFuncUsageLogMapper;
|
||||||
import org.jiayunet.pojo.po.FuncPermission;
|
import org.jiayunet.pojo.po.FuncPermission;
|
||||||
import org.jiayunet.pojo.po.UserFuncPermissionStock;
|
import org.jiayunet.pojo.po.UserFuncPermissionStock;
|
||||||
|
import org.jiayunet.pojo.po.UserFuncUnlock;
|
||||||
import org.jiayunet.pojo.po.UserFuncUsageLog;
|
import org.jiayunet.pojo.po.UserFuncUsageLog;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -38,15 +40,34 @@ public class FuncPermissionService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserFuncUsageLogMapper userFuncUsageLogMapper;
|
private UserFuncUsageLogMapper userFuncUsageLogMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserFuncUnlockMapper userFuncUnlockMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当日是否已存在该业务资源的授权记录
|
||||||
|
* <p>存在说明用户当日已解锁该资源,可直接放行不再扣减</p>
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param funcCode 功能权限编码
|
||||||
|
* @param bizKey 业务资源标识实际值
|
||||||
|
* @return true=当日已授权
|
||||||
|
*/
|
||||||
|
public boolean existsTodayUnlock(Long userId, String funcCode, String bizKey) {
|
||||||
|
|
||||||
|
return userFuncUnlockMapper.exists(new LambdaQueryWrapper<UserFuncUnlock>().eq(UserFuncUnlock::getUserId, userId).eq(UserFuncUnlock::getFuncCode, funcCode).eq(UserFuncUnlock::getUnlockDate, LocalDate.now()).eq(UserFuncUnlock::getBizKey, bizKey));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验用户功能权限并扣减库存
|
* 校验用户功能权限并扣减库存
|
||||||
* <p>1. 查功能权限定义 2. 判断每日免费额度是否充足 3. 免费额度用完则查付费库存 4. 校验时间维度 5. 校验次数维度并原子扣减 6. 插入使用记录</p>
|
* <p>1. 查功能权限定义 2. 判断每日免费额度是否充足 3. 免费额度用完则查付费库存 4. 校验时间维度 5. 校验次数维度并原子扣减 6. 插入使用记录</p>
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param funcCode 功能权限编码
|
* @param funcCode 功能权限编码
|
||||||
|
* @param bizKey 业务资源标识实际值,为空表示不做当日幂等
|
||||||
* @return 使用记录ID(用于异常回退)
|
* @return 使用记录ID(用于异常回退)
|
||||||
*/
|
*/
|
||||||
public Long checkAndDeduct(Long userId, String funcCode) {
|
public Long checkAndDeduct(Long userId, String funcCode, String bizKey) {
|
||||||
// 1. 查功能权限定义
|
// 1. 查功能权限定义
|
||||||
FuncPermission funcPermission = funcPermissionMapper.selectOne(
|
FuncPermission funcPermission = funcPermissionMapper.selectOne(
|
||||||
new LambdaQueryWrapper<FuncPermission>()
|
new LambdaQueryWrapper<FuncPermission>()
|
||||||
@@ -69,6 +90,7 @@ public class FuncPermissionService {
|
|||||||
);
|
);
|
||||||
if (todayUsed < dailyFreeCount) {
|
if (todayUsed < dailyFreeCount) {
|
||||||
// 免费额度未用完,插入使用记录,直接放行
|
// 免费额度未用完,插入使用记录,直接放行
|
||||||
|
insertUnlock(userId, funcCode, bizKey);
|
||||||
return insertUsageLog(userId, funcCode);
|
return insertUsageLog(userId, funcCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +113,7 @@ public class FuncPermissionService {
|
|||||||
// 5. 次数维度校验
|
// 5. 次数维度校验
|
||||||
if (stock.getCountLimit() == 0) {
|
if (stock.getCountLimit() == 0) {
|
||||||
// 不限次,直接放行
|
// 不限次,直接放行
|
||||||
|
insertUnlock(userId, funcCode, bizKey);
|
||||||
return insertUsageLog(userId, funcCode);
|
return insertUsageLog(userId, funcCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,9 +129,30 @@ public class FuncPermissionService {
|
|||||||
throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "功能使用次数已用完");
|
throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "功能使用次数已用完");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insertUnlock(userId, funcCode, bizKey);
|
||||||
return insertUsageLog(userId, funcCode);
|
return insertUsageLog(userId, funcCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入当日解锁记录
|
||||||
|
* <p>bizKey 为空表示不做当日幂等,跳过插入</p>
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param funcCode 功能编码
|
||||||
|
* @param bizKey 业务资源标识实际值
|
||||||
|
*/
|
||||||
|
private void insertUnlock(Long userId, String funcCode, String bizKey) {
|
||||||
|
if (bizKey == null || bizKey.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UserFuncUnlock unlock = new UserFuncUnlock();
|
||||||
|
unlock.setUserId(userId);
|
||||||
|
unlock.setFuncCode(funcCode);
|
||||||
|
unlock.setUnlockDate(LocalDate.now());
|
||||||
|
unlock.setBizKey(bizKey);
|
||||||
|
userFuncUnlockMapper.insert(unlock);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入使用记录
|
* 插入使用记录
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.jiayunet.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.jiayunet.pojo.po.UserFuncUnlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户功能当日解锁记录Mapper
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserFuncUnlockMapper extends CommonMapper<UserFuncUnlock> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.jiayunet.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户功能当日解锁记录表
|
||||||
|
* <p>同一用户当日对同一业务资源重复访问时不再扣减次数</p>
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "bg_user_func_unlock")
|
||||||
|
public class UserFuncUnlock {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能编码
|
||||||
|
*/
|
||||||
|
private String funcCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解锁日期
|
||||||
|
*/
|
||||||
|
private LocalDate unlockDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务资源标识,如岗位ID
|
||||||
|
*/
|
||||||
|
private String bizKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Instant createTime;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user