一、切面记录日志
1.首先定义自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
int type() default 0;
}
2.创建切面LogAspect.Java
@Slf4j
@Aspect
@Component
public class LogAspect {
@Resource
private SysLogService logService;
ThreadLocal<Long> currentTime = new ThreadLocal<>();
/**
* 配置切入点
*/
@Pointcut("@annotation(com.xxxx.xxxxx.util.Log)")
public void logPointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
/**
* 配置环绕通知,使用在方法logPointcut()上注册的切入点
*
* @param joinPoint join point for advice
*/
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
SysUser currentUser = UserUtils.getCurrentUser();
currentTime.set(System.currentTimeMillis());
result = joinPoint.proceed();
SysLog log = new SysLog("INFO", System.currentTimeMillis() - currentTime.get());
currentTime.remove();
HttpServletRequest request = RequestHolder.getHttpServletRequest();
if(currentUser != null) {
logService.saveLog(currentUser.getName(), IpAddressUtil.getIpAddr(request), joinPoint,
log, currentUser.getId());
} else {
Object userInfo = ((HashMap) ((R) result).getData()).get("userInfo");
logService.saveLog(((UserVO) userInfo).getName(), IpAddressUtil.getIpAddr(request), joinPoint,
log, ((UserVO) userInfo).getId());
}
return result;
}
/**
* 配置异常通知
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
SysUser currentUser = UserUtils.getCurrentUser();
SysLog log = new SysLog("ERROR", System.currentTimeMillis() - currentTime.get());
currentTime.remove();
log.setExceptionDetail(ThrowableUtil.getStackTrace(e));
HttpServletRequest request = RequestHolder.getHttpServletRequest();
if(currentUser != null){
logService.saveLog(currentUser.getName(), IpAddressUtil.getIpAddr(request),
(ProceedingJoinPoint) joinPoint, log, currentUser.getId());
} else {
logService.saveLog(null, IpAddressUtil.getIpAddr(request), (ProceedingJoinPoint) joinPoint, log, null);
}
}
}
3.保存log的service方法
public boolean saveLog(String username, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog, Long uid) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Log aopLog = method.getAnnotation(Log.class);
// 方法路径
String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
StringBuilder params = new StringBuilder("{");
// 描述
if (sysLog != null) {
sysLog.setDescription(aopLog.value());
}
if (uid != null) {
sysLog.setUid(uid);
}
assert sysLog != null;
sysLog.setRequestIp(ip);
//参数值
Object[] argValues = joinPoint.getArgs();
String loginPath = "login";
if (loginPath.equals(signature.getName())) {
try {
//参数名称
String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
if (argValues != null) {
params.append(" ").append(argNames[1]).append(": ").append(argValues[1]);
}
assert argValues != null;
String phone = new JSONObject(argValues[1]).get("phone").toString();
username = userService.getByPhone(phone).getName();
} catch (Exception e) {
log.error("插入登录日志错误:{}", e);
}
} else {
//参数名称
String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
if (argValues != null) {
for (int i = 0; i < argValues.length; i++) {
//编辑文章内容太长,导致数据库查询很卡,所以日志内容直接截取
String argValueString = argValues[i].toString();
if (argValueString.length() > 10000) {
argValueString = argValueString.substring(0, 10000);
}
params.append(" ").append(argNames[i]).append(": ").append(argValueString);
}
}
}
sysLog.setIpSource(StringUtils.getCityInfo(sysLog.getRequestIp()));
sysLog.setMethod(methodName);
sysLog.setUsername(username);
sysLog.setParams(params.toString() + " }");
return super.save(sysLog);
}
二、切面控制权限
1. 创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthAnnotation {
String value();
}
2.切面方法
@Aspect
@Component
@Slf4j
public class AuthorizeAspect {
@Resource
private SysRolePermissionService rolePermissionService;
@Pointcut("@annotation(com.xxx.xxxxx.util.AuthAnnotation)")
public void AuthLoginVerify() {
}
@Before("AuthLoginVerify()")
public void doAdminAuthVerify(JoinPoint joinPoint) {
// 判断是否进行权限验证
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//从切面中获取当前方法
Method method = signature.getMethod();
//得到了方,提取出他的注解
AuthAnnotation action = method.getAnnotation(AuthAnnotation.class);
// 进行权限验证
SysUser loginUser = UserUtils.getCurrentUser();
authRuleVerify(action.value(), loginUser.getId());
}
/**
* 权限验证
*
* @param authPermission
*/
private void authRuleVerify(String authPermission, Long userId) {
if (authPermission != null && authPermission.length() > 0) {
List<String> permission = rolePermissionService.getPermissionIds(userId);
PredicateOptional.of(permission.size()).ifTrueThrow(size -> size == 0, "当前用户还未分配角色,请联系管理员分配!");
if (!permission.contains(authPermission)) {
throw new BusinessException("暂无操作权限!!");
}
}
}
}
三、防止接口在指定时间内重复提交
1.创建自定义注解
@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class RepeatCommitAspectAmend {
private final RedisLock redisLock;
@Pointcut("@annotation(forbidRepeatCommit)")
public void pointCut(ForbidRepeatCommit forbidRepeatCommit) {
}
@Around("pointCut(forbidRepeatCommit)")
public Object around(ProceedingJoinPoint pjp, ForbidRepeatCommit forbidRepeatCommit) throws Throwable {
int lockSeconds = forbidRepeatCommit.lockTime();
HttpServletRequest request = getRequest();
Assert.notNull(request, "request can not null");
String bearerToken = request.getHeader("authorization");
String[] tokens = bearerToken.split(" ");
String token = tokens[1];
String path = request.getServletPath();
String key = getKey(token, path);
String clientId = getClientId();
Signature signature = pjp.getSignature();
// 参数名数组
String[] parameterNames = ((MethodSignature) signature).getParameterNames();
// 构造参数组集合
List<Object> argList = new ArrayList<>();
for (Object arg : pjp.getArgs()) {
// request/response无法使用toJSON
if (arg instanceof HttpServletRequest) {
argList.add("request");
} else if (arg instanceof HttpServletResponse) {
argList.add("response");
} else {
argList.add(JSON.toJSON(arg));
}
}
Object previousId = ((JSONObject) argList.get(0)).get("previousId");
boolean isSuccess = redisLock.tryLock(previousId+"", clientId, lockSeconds);
log.info("tryLock key = [{}], clientId = [{}]", key, clientId);
if (isSuccess) {
log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
// 获取锁成功
Object result;
try {
// 执行进程
result = pjp.proceed();
} finally {
// 解锁
//redisLock.releaseLock(key, clientId);
log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
}
return result;
} else {
// 获取锁失败,认为是重复提交的请求
log.info("tryLock fail, key = [{}]", key);
return R.fail(ErrorCode.DIAGNOSIS_REPEAT_OPERATION, ErrorCode.DIAGNOSIS_REPEAT_OPERATION.getMsg());
}
}
private String getKey(String token, String path) {
return token + path;
}
private String getClientId() {
return UUID.randomUUID().toString();
}
public static HttpServletRequest getRequest() {
ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return ra.getRequest();
}
3.RedisLock.Java
@Service
@RequiredArgsConstructor
public class RedisLock {
private final RedisUtils redisUtils;
public boolean tryLock(String lockKey, String clientId, long seconds) {
if(redisUtils.hasKey(lockKey)){
return false;
}
return redisUtils.set(lockKey, clientId, seconds);
}
public void releaseLock(String lockKey, String clientId) {
redisUtils.del(lockKey);
}
}
4.RedisUtils.Java
@Component
public class RedisUtils {
@Autowired
private RedisTemplate redisTemplate;
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
}