资源使用记录情况方案初探

       在业务需求中,有时对与危险操作,比如删除需要提醒用户删除该资源可能造成的影响。特别是对于基础资源被创建后,其他业务资源有可能处于正在使用该基础资源的状态。对于该基础资源的删除,有必要提醒用户当前有哪些业务资源在使用。下面将重点介绍一种通过数据库结合AOP来维护管理基础资源使用情况的方案。其他基于事件的方案将做简要分析。

      对于资源的使用情况记录,在这里只是简单的记录下当前操作的业务类型、做了什么操作、操作结果是正常还是失败、操作时间,实际项目中,如果我们需要记录的更详细,可以记录当前操作人的详细信息。我们还可以记录用户调用了哪个类的哪个方法,我们可以使用JoinPoint参数获取或者利用环绕通知ProceedingJoinPoint去获取。可以精确的定位到类、方法、参数,如果有必要我们就可以记录在操作记录数据库中中,看业务需求和我们的资源使用情况来进行设计。如果再细致的记录资源使用情况,我们可以针对错误再建立一个错误操作表,在发生错误的情况下(异常通知里)记录操作错误的信息。

  实现的大致思路是:

    1.前期准备,设计资源使用记录表和资源使用记录类,编写资源使用记录entity和repository以及实现

    2.自定义注解,注解中加入几个属性,属性可以标识操作的类型(方法是做什么的)

    3.编写切面,切点表达式使用上面的注解直接定位到使用注解的方法,

    4.编写通知,通过定位到方法,获取上面的注解以及注解的属性,然后从请求参数和数据库操作对象repository中获取信息,最后根据业务处理一些资源使用信息之后调用资源使用Service存储资源使用情况信息。

     其实资源使用记录可以针对Controller层进行切入,也可以选择Service层进行切入,当前选择的是基于Service层进行资源使用情况记录。有些AOP操作记录有的用前置通知,有的用环绕通知,当前选择在环绕通知中完成,因为环绕通知中可以完成前置、后置、最终、异常通知的所有功能。

一、数据库实现


@Data
@Entity
@NoArgsConstructor
@Table(name = "tb_resource_usage_record")
@EqualsAndHashCode(callSuper = false)
public class ResourceUsageRecordEntity {


    @Id
    @Column(name="id" ,nullable = false , unique = true )
    private String id;

    /**
     * 资源id, 如entId
     */
    @Column(name="resource_id" ,nullable = false)
    private String resourceId;

    /**
     * 资源类型: 如ent, region, building,
     */
    @Column(name="resource_type" ,nullable = false)
    private String resourceType;

    /**
     * 操作业务
     */
    @Column(name="operate_business")
    private String operateBusiness;

    /**
     * 操作结果, 0-正常, -1 异常
     */
    @Column(name="operate_result")
    private Integer operateResult;

    @Column(name="create_time")
    private Date createTime;

}

二、数据库Jpa操作对象实现


@Repository
public interface ResourceUsageRepository extends JpaRepository<ResourceUsageRecordEntity, String>,
        JpaSpecificationExecutor<ResourceUsageRecordEntity> {


    @Transactional
    @Modifying
    @Query("delete from ResourceUsageRecordEntity rs where rs.resourceId in (:resourceIds)")
    Integer deleteByResourceIds(@Param("resourceIds") List<String> resourceIds);
}

  三、自定义增加和删除使用情况记录注解

         使用情况增加注解

/**
 * 运行时可见
 * 方法注解, 类注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AddRecord {


    /**
     * 资源类型,ent, building, region
     * @return
     */
    String resourceType() default "ent";

    /**
     * 当前操作业务, 参考OperateTypeEnum
     * @return
     */
    String operateBusiness();

}

使用情况删除注解

  

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DeleteRecord {

    /**
     * 包含有entId或buildingId的数据库操作对象名, 如:controlRoomConfigRepository
     * @return
     */
    String repositoryName();
}

增加和删除使用情况的AOP代理实现类

@Aspect
@Slf4j
@Component
public class AddRecordAspect {

    @Autowired
    private ResourceUsageRepository resourceUsageRepository;

    @Pointcut("@annotation(AddRecord)")
    public void pointCut() {}


    @Around(value = "pointCut()")
    public Object aroundAddRecord(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1.方法执行前的处理,相当于前置通知
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法
        Method method = signature.getMethod();
        // 获取方法上面的注解
        AddRecord recordAnno = method.getAnnotation(AddRecord.class);
        //获取资源类型
        String resourceType = recordAnno.resourceType();
        // 获取操作业务类型
        String operateBusiness = recordAnno.operateBusiness();

        String[] parameterNames = signature.getParameterNames();
        String resourceId = "";
        if (null != parameterNames && parameterNames.length >0){
            for (int i = 0; i < parameterNames.length; i++) {
                String value = joinPoint.getArgs()[i] != null ? JSON.toJSONString(joinPoint.getArgs()[i]) : Strings.EMPTY;
                if (StringUtils.isNotEmpty(value)) {
                    resourceId = this.findResourceId(value);
                }
            }
        }

        Object result = new Object();
        ResourceUsageRecordEntity entity = new ResourceUsageRecordEntity();

        if(StringUtils.isNotBlank(resourceId)){
            entity.setResourceId(resourceId);
        }else {
            return result;
        }
        entity.setId(UUIDUtil.generateId());
        entity.setResourceType(resourceType);
        entity.setOperateBusiness(operateBusiness);


        try {
            //让代理方法执行
            result = joinPoint.proceed();
            // 2.相当于后置通知(方法成功执行之后走这里)
            entity.setOperateResult(0);// 设置操作结果
        } catch (Exception e) {
            // 3.相当于异常通知部分
            entity.setOperateResult(-1);// 设置操作结果异常
            log.error(SystemLogUtil.logWithErrorCodeAndParams(CommonApiErrorCodeEnum.SERVICE_ERROR.getErrorCode(),
                    "aroundAddRecord.error", "operateBusiness", "resourceId"), operateBusiness,
                    resourceId);
        }
            // 4.相当于最终通知
        entity.setCreateTime(new Date());// 设置操作日期
        resourceUsageRepository.save(entity);// 添加资源使用记录

        return result;
    }

    private String findResourceId(String value){
        String resourceId = StringUtils.EMPTY;
        if(StringUtils.isBlank(value)){
            return resourceId;
        }
        JSONObject jsonObject = JSONObject.parseObject(value);
        String entId = (String)jsonObject.get("entId");
        String enterpriseId = (String)jsonObject.get("enterpriseId");
        String buildingId = (String)jsonObject.get("buildingId");
        if(StringUtils.isNotBlank(entId)){
            resourceId = entId;
        }else if(StringUtils.isNotBlank(enterpriseId)){
            resourceId = enterpriseId;
        } else if(StringUtils.isNotBlank(buildingId)){
            resourceId = buildingId;
        }
        return resourceId;
    }
}


@Aspect
@Slf4j
@Component
public class DeleteRecordAspect {

    @Autowired
    private ResourceUsageRepository resourceUsageRepository;

    @Pointcut("@annotation(DeleteRecord)")
    public void pointCut() {}

    @Around(value = "pointCut()")
    public Object aroundDeleteRecord(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = new Object();
        // 1.方法执行前的处理,相当于前置通知
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法
        Method method = signature.getMethod();
        // 获取方法上面的注解
        DeleteRecord recordAnno = method.getAnnotation(DeleteRecord.class);
        //获取jpa repositoryName
        String repositoryName = recordAnno.repositoryName();
        Object bean = SpringBeanContext.getBean(repositoryName);
        JpaRepository jpaRepository = (JpaRepository)bean;

        String[] parameterNames = signature.getParameterNames();
        List<String> deleteIds = Lists.newArrayList();

        if (null != parameterNames && parameterNames.length >0){
            for (int i = 0; i < parameterNames.length; i++) {
                String value = joinPoint.getArgs()[i] != null ? JSON.toJSONString(joinPoint.getArgs()[i]) : Strings.EMPTY;
                if (StringUtils.isNotEmpty(value)) {
                    deleteIds  = JSONObject.parseArray(value, String.class);
                }
            }
        }
        List<Object> deleteObjects = jpaRepository.findAllById(deleteIds);
        if(CollectionUtils.isEmpty(deleteObjects)){
            return result;
        }
        List<String> deleteResourceIds = deleteObjects.stream().map(this::findResourceId).collect(Collectors.toList());

        if(CollectionUtils.isEmpty(deleteResourceIds)){
            return result;
        }
        try {
            //让代理方法执行
            result = joinPoint.proceed();
            // 2.相当于后置通知(方法成功执行之后走这里)

        } catch (Exception e) {
            // 3.相当于异常通知部分
           log.error(SystemLogUtil.logWithErrorCodeAndParams(CommonApiErrorCodeEnum.SERVICE_ERROR.getErrorCode(),
                   "aroundDeleteRecord.error", "repositoryName", "deleteIds"), repositoryName,
                   JSON.toJSONString(deleteIds));
        }
        // 4.相当于最终通知
        resourceUsageRepository.deleteByResourceIds(deleteResourceIds);// 添加资源使用记录

        return result;
    }


    private String findResourceId(Object value){
        String resourceId = StringUtils.EMPTY;
        if(ObjectUtils.isEmpty(value)){
            return resourceId;
        }
        JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(value));
        String entId = (String)jsonObject.get("entId");
        String enterpriseId = (String)jsonObject.get("enterpriseId");
        String buildingId = (String)jsonObject.get("buildingId");
        if(StringUtils.isNotBlank(entId)){
            resourceId = entId;
        }else if(StringUtils.isNotBlank(enterpriseId)){
            resourceId = enterpriseId;
        } else if(StringUtils.isNotBlank(buildingId)){
            resourceId = buildingId;
        }
        return resourceId;
    }

}

四、测试结果

使用示例

    @Override
    @Transactional
    @AddRecord(resourceType = "ent", operateBusiness = OperateTypeEnum.CONTROLROOM_CODE)
    public ControlRoomConfigVO addControlRoom(ControlRoomConfigAddQuery query) {

        ControlRoomConfigEntity configEntity = mapperFacade.map(query, ControlRoomConfigEntity.class);
// ...
}

测试结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值