责任链模式的高级用法:多级校验、工作流,这样写代码才足够优雅!_spring职责链 动态编排-程序员宅基地

技术标签: 实战设计模式  java实现责任链模式案例  责任链模式  互联网公司责任链模式实战  设计模式  设计模式实战  

在这里插入图片描述

一、开篇

阅读本文可以了解哪些知识?

  • 结合具体案例,领略责任链模式的魅力。
  • 责任链模式实现流程编排动态扩展
  • 使用 Sping @Resource 注解注入的骚操作
  • 使用递归算法设置责任链路。

二、简介

责任链模式,简而言之,就是将多个操作组装成一条链路进行处理。请求在链路上传递,链路上的每一个节点就是一个处理器,每个处理器都可以对请求进行处理,或者传递给链路上的下一个处理器处理。
责任链模式-链路传递



三、应用场景

责任链模式的应用场景,在实际工作中,通常有如下两种应用场景。

  1. 操作需要经过一系列的校验,通过校验后才执行某些操作。
  2. 工作流。企业中通常会制定很多工作流程,一级一级的去处理任务。

下面通过两个案例来学习一下责任链模式。


案例一:创建商品多级校验场景

以创建商品为例,假设商品创建逻辑分为以下三步完成:①创建商品、②校验商品参数、③保存商品。

第②步校验商品又分为多种情况的校验,必填字段校验、规格校验、价格校验、库存校验等等。这些检验逻辑像一个流水线,要想创建出一个商品,必须通过这些校验。如下流程图所示:

创建商品的坏味道

伪代码如下:

创建商品步骤,需要经过一系列的参数校验,如果参数校验失败,直接返回失败的结果;通过所有的参数校验后,最终保存商品信息。
伪代码

如上代码看起来似乎没什么问题,它非常工整,而且代码逻辑很清晰。(PS:我没有把所有的校验代码都罗列在一个方法里,那样更能产生对比性,但我觉得抽象并分离单一职责的函数应该是每个程序员最基本的规范!)

但是随着业务需求不断地叠加,相关的校验逻辑也越来越多,新的功能使代码越来越臃肿可维护性较差。更糟糕的是,这些校验组件不可复用,当你有其他需求也需要用到一些校验时,你又变成了Ctrl+C , Ctrl+V程序员,系统的维护成本也越来越高。如下图所示:
业务逻辑堆叠
伪代码同上,这里就不赘述了。

终于有一天,你忍无可忍了,决定重构这段代码

使用责任链模式优化 :创建商品的每个校验步骤都可以作为一个单独的处理器,抽离为一个单独的,便于复用。这些处理器形成一条链式调用,请求在处理器链上传递,如果校验条件不通过,则处理器不再向下传递请求,直接返回错误信息;若所有的处理器都通过检验,则执行保存商品步骤。

在这里插入图片描述



案例一实战:责任链模式实现创建商品校验


UML图:一览众山小

在这里插入图片描述

AbstractCheckHandler表示处理器抽象类,负责抽象处理器行为。其有3个子类,分别是:

  1. NullValueCheckHandler:空值校验处理器
  2. PriceCheckHandler:价格校验处理
  3. StockCheckHandler:库存校验处理器

AbstractCheckHandler 抽象类中, handle()定义了处理器的抽象方法,其子类需要重写handle()方法以实现特殊的处理器校验逻辑;

protected ProductCheckHandlerConfig config 是处理器的动态配置类,使用protected声明,每个子类处理器都持有该对象。该对象用于声明当前处理器、以及当前处理器的下一个处理器nextHandler,另外也可以配置一些特殊属性,比如说接口降级配置、超时时间配置等。

AbstractCheckHandler nextHandler 是当前处理器持有的下一个处理器的引用,当前处理器执行完毕时,便调用nextHandler执行下一处理器的handle()校验方法;

protected Result next() 是抽象类中定义的,执行下一个处理器的方法,使用protected声明,每个子类处理器都持有该对象。当子类处理器执行完毕(通过)时,调用父类的方法执行下一个处理器nextHandler

HandlerClient 是执行处理器链路的客户端,HandlerClient.executeChain()方法负责发起整个链路调用,并接收处理器链路的返回值。


撸起袖子开始撸代码吧 ~

商品参数对象:保存商品的入参

ProductVO是创建商品的参数对象,包含商品的基础信息。并且其作为责任链模式中多个处理器的入参,多个处理器都以ProductVO为入参进行特定的逻辑处理。实际业务中,商品对象特别复杂。咱们化繁为简,简化商品参数如下:

/**
 * 商品对象
 */
@Data
@Builder
public class ProductVO {
    
    /**
     * 商品SKU,唯一
     */
    private Long skuId;
    /**
     * 商品名称
     */
    private String skuName;
    /**
     * 商品图片路径
     */
    private String imgPath;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 库存
     */
    private Integer stock;
}

抽象类处理器:抽象行为,子类共有属性、方法

AbstractCheckHandler:处理器抽象类,并使用@Component注解注册为由Spring管理的Bean对象,这样做的好处是,我们可以轻松的使用Spring来管理这些处理器Bean

/**
 * 抽象类处理器
 */
@Component
public abstract class AbstractCheckHandler {
    

    /**
     * 当前处理器持有下一个处理器的引用
     */
    @Getter
    @Setter
    protected AbstractCheckHandler nextHandler;


    /**
     * 处理器配置
     */
    @Setter
    @Getter
    protected ProductCheckHandlerConfig config;

    /**
     * 处理器执行方法
     * @param param
     * @return
     */
    public abstract Result handle(ProductVO param);

    /**
     * 链路传递
     * @param param
     * @return
     */
    protected Result next(ProductVO param) {
    
        //下一个链路没有处理器了,直接返回
        if (Objects.isNull(nextHandler)) {
    
            return Result.success();
        }

        //执行下一个处理器
        return nextHandler.handle(param);
    }

}

AbstractCheckHandler抽象类处理器中,使用protected声明子类可见的属性和方法。使用 @Component注解,声明其为SpringBean对象,这样做的好处是可以利用Spring轻松管理所有的子类,下面会看到如何使用。抽象类的属性和方法说明如下:

  • public abstract Result handle():表示抽象的校验方法,每个处理器都应该继承AbstractCheckHandler抽象类处理器,并重写其handle方法,各个处理器从而实现特殊的校验逻辑,实际上就是多态的思想。

  • protected ProductCheckHandlerConfig config:表示每个处理器的动态配置类,可以通过“配置中心”动态修改该配置,实现处理器的“动态编排”和“顺序控制”。配置类中可以配置处理器的名称下一个处理器、以及处理器是否降级等属性。

  • protected AbstractCheckHandler nextHandler:表示当前处理器持有下一个处理器的引用,如果当前处理器handle()校验方法执行完毕,则执行下一个处理器nextHandlerhandle()校验方法执行校验逻辑。

  • protected Result next(ProductVO param):此方法用于处理器链路传递,子类处理器执行完毕后,调用父类的next()方法执行在config 配置的链路上的下一个处理器,如果所有处理器都执行完毕了,就返回结果了。

ProductCheckHandlerConfig配置类 :

/**
 * 处理器配置类
 */
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
    
    /**
     * 处理器Bean名称
     */
    private String handler;
    /**
     * 下一个处理器
     */
    private ProductCheckHandlerConfig next;
    /**
     * 是否降级
     */
    private Boolean down = Boolean.FALSE;
}

子类处理器:处理特有的校验逻辑

AbstractCheckHandler抽象类处理器有3个子类分别是:

  1. NullValueCheckHandler:空值校验处理器
  2. PriceCheckHandler:价格校验处理
  3. StockCheckHandler:库存校验处理器

各个处理器继承AbstractCheckHandler抽象类处理器,并重写其handle()处理方法以实现特有的校验逻辑。

  • NullValueCheckHandler:空值校验处理器。针对性校验创建商品中必填的参数。如果校验未通过,则返回错误码ErrorCode,责任链在此截断(停止),创建商品返回被校验住的错误信息。注意代码中的降级配置super.getConfig().getDown()是获取AbstractCheckHandler处理器对象中保存的配置信息,如果处理器配置了降级,则跳过该处理器,调用super.next()执行下一个处理器逻辑。

同样,使用@Component注册为由Spring管理的Bean对象,

/**
 * 空值校验处理器
 */
@Component
public class NullValueCheckHandler extends AbstractCheckHandler{
    

    @Override
    public Result handle(ProductVO param) {
    
        System.out.println("空值校验 Handler 开始...");
        
        //降级:如果配置了降级,则跳过此处理器,执行下一个处理器
        if (super.getConfig().getDown()) {
    
            System.out.println("空值校验 Handler 已降级,跳过空值校验 Handler...");
            return super.next(param);
        }
        
        //参数必填校验
        if (Objects.isNull(param)) {
    
            return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        }
        //SkuId商品主键参数必填校验
        if (Objects.isNull(param.getSkuId())) {
    
            return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
        }
        //Price价格参数必填校验
        if (Objects.isNull(param.getPrice())) {
    
            return Result.failure(ErrorCode.PARAM_PRICE_NULL_ERROR);
        }
        //Stock库存参数必填校验
        if (Objects.isNull(param.getStock())) {
    
            return Result.failure(ErrorCode.PARAM_STOCK_NULL_ERROR);
        }
        
        System.out.println("空值校验 Handler 通过...");
        
        //执行下一个处理器
        return super.next(param);
    }
}


  • PriceCheckHandler:价格校验处理。针对创建商品的价格参数进行校验。这里只是做了简单的判断价格>0的校验,实际业务中比较复杂,比如“价格门”这些防范措施等。
/**
 * 价格校验处理器
 */
@Component
public class PriceCheckHandler extends AbstractCheckHandler{
    
    @Override
    public Result handle(ProductVO param) {
    
        System.out.println("价格校验 Handler 开始...");

        //非法价格校验
        boolean illegalPrice =  param.getPrice().compareTo(BigDecimal.ZERO) <= 0;
        if (illegalPrice) {
    
            return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
        }
        //其他校验逻辑...

        System.out.println("价格校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}

  • StockCheckHandler:库存校验处理器。针对创建商品的库存参数进行校验。
/**
 * 库存校验处理器
 */
@Component
public class StockCheckHandler extends AbstractCheckHandler{
    
    @Override
    public Result handle(ProductVO param) {
    
        System.out.println("库存校验 Handler 开始...");

        //非法库存校验
        boolean illegalStock = param.getStock() < 0;
        if (illegalStock) {
    
            return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
        }
        //其他校验逻辑..

        System.out.println("库存校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}

客户端:执行处理器链路

HandlerClient客户端类负责发起整个处理器链路的执行,通过executeChain()方法。如果处理器链路返回错误信息,即校验未通过,则整个链路截断(停止),返回相应的错误信息。

public class HandlerClient {
    

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
    
      //执行处理器
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
    
          System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

以上,责任链模式相关的类已经创建好了。接下来就可以创建商品了。


创建商品:抽象步骤,化繁为简

createProduct()创建商品方法抽象为2个步骤:①参数校验②创建商品。参数校验使用责任链模式进行校验,包含:空值校验价格校验库存校验等等,只有链上的所有处理器均校验通过,才调用saveProduct()创建商品方法;否则返回校验错误信息。在createProduct()创建商品方法中,通过责任链模式,我们将校验逻辑进行解耦createProduct()创建商品方法中不需要关注都要经过哪些校验处理器,以及校验处理器的细节。

/**
 * 创建商品
 * @return
 */
@Test
public Result createProduct(ProductVO param) {
    

    //参数校验,使用责任链模式
    Result paramCheckResult = this.paramCheck(param);
    if (!paramCheckResult.isSuccess()) {
    
        return paramCheckResult;
    }

    //创建商品
    return this.saveProduct(param);
}

参数校验:责任链模式

参数校验paramCheck()方法使用责任链模式进行参数校验,方法内没有声明具体都有哪些校验,具体有哪些参数校验逻辑是通过多个处理器链传递的。如下:

/**
 * 参数校验:责任链模式
 * @param param
 * @return
 */
private Result paramCheck(ProductVO param) {
    

    //获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
    ProductCheckHandlerConfig handlerConfig = this.getHandlerConfigFile();

    //获取处理器
    AbstractCheckHandler handler = this.getHandler(handlerConfig);

    //责任链:执行处理器链路
    Result executeChainResult = HandlerClient.executeChain(handler, param);
    if (!executeChainResult.isSuccess()) {
    
        System.out.println("创建商品 失败...");
        return executeChainResult;
    }

    //处理器链路全部成功
    return Result.success();
}

paramCheck()方法步骤说明如下:

步骤1:获取处理器配置

通过getHandlerConfigFile()方法获取处理器配置类对象,配置类保存了链上各个处理器的上下级节点配置,支持流程编排动态扩展。通常配置是通过Ducc(京东自研的配置中心)Nacos(阿里开源的配置中心)等配置中心存储的,支持动态变更实时生效。基于此,我们便可以实现校验处理器的编排、以及动态扩展了。我这里没有使用配置中心存储处理器链路的配置,而是使用JSON串的形式去模拟配置,大家感兴趣的可以自行实现。

/**
 * 获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
 * @return
 */
private ProductCheckHandlerConfig getHandlerConfigFile() {
    
    //配置中心存储的配置
    String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":true,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
    //转成Config对象
    ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);
    return handlerConfig;
}

ConfigJson存储的处理器链路配置JSON串,在代码中可能不便于观看,我们可以使用json.cn等格式化看一下,如下,配置的整个调用链路规则特别清晰。

在这里插入图片描述
getHandlerConfigFile()类获到配置类的结构如下,可以看到,就是把在配置中心储存的配置规则,转换成配置类ProductCheckHandlerConfig对象,用于程序处理。注意,此时配置类中存储的仅仅是处理器Spring Beanname而已,并非实际处理器对象

请添加图片描述
接下来,通过配置类获取实际要执行的处理器。

步骤2:根据配置获取处理器

上面步骤1通过getHandlerConfigFile()方法获取到处理器链路配置规则后,再调用getHandler()获取处理器。
getHandler()参数是如上ConfigJson配置的规则,即步骤1转换成的ProductCheckHandlerConfig对象;根据ProductCheckHandlerConfig配置规则转换成处理器链路对象。代码如下:

/**
 * 使用Spring注入:所有继承了AbstractCheckHandler抽象类的Spring Bean都会注入进来。Map的Key对应Bean的name,Value是name对应相应的Bean
 */
@Resource
private Map<String, AbstractCheckHandler> handlerMap;

/**
 * 获取处理器
 * @param config
 * @return
 */
private AbstractCheckHandler getHandler (ProductCheckHandlerConfig config) {
    
    //配置检查:没有配置处理器链路,则不执行校验逻辑
    if (Objects.isNull(config)) {
    
        return null;
    }
    //配置错误
    String handler = config.getHandler();
    if (StringUtils.isBlank(handler)) {
    
        return null;
    }
    //配置了不存在的处理器
    AbstractCheckHandler abstractCheckHandler = handlerMap.get(config.getHandler());
    if (Objects.isNull(abstractCheckHandler)) {
    
        return null;
    }
    
    //处理器设置配置Config
    abstractCheckHandler.setConfig(config);
    
    //递归设置链路处理器
    abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

    return abstractCheckHandler;
}

步骤2-1:配置检查。

代码14~27行,进行了配置的一些检查操作。如果配置错误,则获取不到对应的处理器。代码23行handlerMap.get(config.getHandler())是从所有处理器映射Map中获取到对应的处理器Spring Bean

注意第5行代码,handlerMap存储了所有的处理器映射,是通过Spring @Resource注解注入进来的。注入的规则是:所有继承了AbstractCheckHandler抽象类(它是Spring管理的Bean)的子类(子类也是Spring管理的Bean)都会注入进来。

注入进来的handlerMapMapKey对应BeannameValuename对应的Bean实例,也就是实际的处理器,这里指空值校验处理器价格校验处理器库存校验处理器。如下:
请添加图片描述
这样根据配置ConfigJson 步骤1:获取处理器配置)中handler:"priceCheckHandler"的配置,使用handlerMap.get(config.getHandler())便可以获取到对应的处理器Spring Bean对象了。

步骤2-2:保存处理器规则。

代码29行,将配置规则保存到对应的处理器中abstractCheckHandler.setConfig(config),子类处理器就持有了配置的规则。

步骤2-3:递归设置处理器链路

代码32行,递归设置链路上的处理器

//递归设置链路处理器 abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

这一步可能不太好理解,结合ConfigJson配置的规则来看,似乎就很很容易理解了。

在这里插入图片描述

由上而下,NullValueCheckHandler 空值校验处理器通过setNextHandler()方法设置自己持有的下一节点的处理器,也就是价格处理器PriceCheckHandler

接着,PriceCheckHandler价格处理器,同样需要经过步骤2-1配置检查步骤2-2保存配置规则,并且最重要的是,它也需要设置下一节点的处理器StockCheckHandler库存校验处理器。

StockCheckHandler库存校验处理器也一样,同样需要经过步骤2-1配置检查步骤2-2保存配置规则,但请注意StockCheckHandler的配置,它的next规则配置了null,这表示它下面没有任何处理器要执行了,它就是整个链路上的最后一个处理节点。

通过递归调用getHandler()获取处理器方法,就将整个处理器链路对象串联起来了。如下:
请添加图片描述

友情提示递归虽香,但使用递归一定要注意截断递归的条件处理,否则可能造成死循环哦!

实际上,getHandler()获取处理器对象的代码就是把在配置中心配置的规则ConfigJson,转换成配置类ProductCheckHandlerConfig对象,再根据配置类对象,转换成实际的处理器对象,这个处理器对象持有整个链路的调用顺序


步骤3:客户端执行调用链路

public class HandlerClient {
    

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
    
      //执行处理器
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
    
          System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

getHandler()获取完处理器后,整个调用链路的执行顺序也就确定了,此时,客户端该干活了

HandlerClient.executeChain(handler, param)方法是HandlerClient客户端类执行处理器整个调用链路的,并接收处理器链路的返回值。

executeChain()通过AbstractCheckHandler.handle()触发整个链路处理器顺序执行,如果某个处理器校验没有通过!handlerResult.isSuccess(),则返回错误信息;所有处理器都校验通过,则返回正确信息Result.success()


总结:串联方法调用流程

基于以上,再通过流程图来回顾一下整个调用流程。

在这里插入图片描述


测试:代码执行结果

场景1:创建商品参数中有空值(如下skuId参数为null),链路被空值处理器截断,返回错误信息

//创建商品参数
ProductVO param = ProductVO.builder()
      .skuId(null).skuName("华为手机").imgPath("http://...")
      .price(new BigDecimal(1))
      .stock(1)
      .build();

测试结果

在这里插入图片描述

场景2:创建商品价格参数异常(如下price参数),被价格处理器截断,返回错误信息

ProductVO param = ProductVO.builder()
      .skuId(1L).skuName("华为手机").imgPath("http://...")
      .price(new BigDecimal(-999))
      .stock(1)
      .build();

测试结果

在这里插入图片描述

场景 3:创建商品库存参数异常(如下stock参数),被库存处理器截断,返回错误信息。

//创建商品参数,模拟用户传入
ProductVO param = ProductVO.builder()
      .skuId(1L).skuName("华为手机").imgPath("http://...")
      .price(new BigDecimal(1))
      .stock(-999)
      .build();

测试结果

在这里插入图片描述

场景4:创建商品所有处理器校验通过,保存商品。

//创建商品参数,模拟用户传入
ProductVO param = ProductVO.builder()
      .skuId(1L).skuName("华为手机").imgPath("http://...")
      .price(new BigDecimal(999))
      .stock(1).build();

测试结果

在这里插入图片描述



案例二:工作流,费用报销审核流程

同事小贾最近刚出差回来,她迫不及待的就提交了费用报销的流程。根据金额不同,分为以下几种审核流程。报销金额低于1000元,三级部门管理者审批即可,1000到5000元除了三级部门管理者审批,还需要二级部门管理者审批,而5000到10000元还需要一级部门管理者审批。即有以下几种情况:

  1. 小贾需报销500元,三级部门管理者审批即可。
  2. 小贾需报销2500元,三级部门管理者审批通过后,还需要二级部门管理者审批,二级部门管理者审批通过后,才完成报销审批流程。
  3. 小贾需报销7500元,三级管理者审批通过后,并且二级管理者审批通过后,流程流转到一级部门管理者进行审批,一级管理者审批通过后,即完成了报销流程。
    在这里插入图片描述

### UML图

AbstractFlowHandler作为处理器抽象类,抽象了approve()审核方法,一级、二级、三级部门管理者处理器继承了抽象类,并重写其approve()审核方法,从而实现特有的审核逻辑。

在这里插入图片描述

配置类如下所示,每层的处理器都要配置审核人、价格审核规则(审核的最大、最小金额)、下一级处理人。配置规则是可以动态变更的,如果三级部门管理者可以审核的金额增加到2000元,修改一下配置即可动态生效。

在这里插入图片描述

代码实现与案例一相似,感兴趣的自己动动小手吧~



四、责任链的优缺点

在这里插入图片描述

在这里插入图片描述

五、源码查看

github:https://github.com/rongtao7/MyNotes


如果喜欢这篇文章,请不要吝啬你的赞哦,创作不易,感谢!

请添加图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/rongtaoup/article/details/122638812

智能推荐

你需要来自XXX的权限才能对此文件夹进行更改 win10_你需要来自laptop-de4k3vqi\2778227451的权限才能对此文件夹进行更改-程序员宅基地

文章浏览阅读9k次。无法删除一般是两种:1.有后台正在运行的服务,要在服务中关闭该项2.还有一种是以你为权限不够,则要提升权限本章主要是针对第二种windows 10 删除文件夹出现问题:你需要来自XXX的权限才能对此文件夹进行更改你需要来自SYSTEM的权限才能对此文件夹进行更改解决流程:右击打开此文件夹属性,如下图,点击高级按钮出现下图,点击更改:..._你需要来自laptop-de4k3vqi\2778227451的权限才能对此文件夹进行更改

MTK支持的DRM简介_mtk_oma_download_support-程序员宅基地

文章浏览阅读5.9k次。一、DRM 基本信息:1.MTK支持的DRM类型a.OMA DRM V1.0 (Open Mobile Alliance) DRMb.Widevine(Google Widevine) L32.OMA DRM 支持的文件类型a.DRM content MIME DRM内容文件类型: application/vnd.oma.drm.content ---_mtk_oma_download_support

Arch下装BCM43142无线网驱的多种尝试,最终成功_arch安装bcm43142驱动-程序员宅基地

文章浏览阅读3.1k次。起因:想用电脑开个热点分享wifi给手机用,但是装了Arch之后,没有装wifi网卡驱动,今天折腾了一整天终于装好了,虽然。。。并不能开热点,原因是BCM43142类型的Linux网卡驱动不支持AP模式。但是还是记录一下,我的解决方案是准备买一块无线网卡。期间尝试:1.算是失败尝试吧:一篇国外的博客:Installing broadcom bcm43142 Wireless_arch安装bcm43142驱动

360脱壳-native函数还原笔记-2017-06-25_原创]360加固之oncreate函数还原并重打包-程序员宅基地

文章浏览阅读720次。接触andorid逆向脱壳一段时间了,刚刚感觉开始入门,最开始时的过反调试,花费了大量的时间,但时间长了,汇编指令慢慢熟悉,学会了一些调试技巧,反调试方法知道了,就那么些反调试的方法,过反调就比较顺利了,所以刚开始学逆向的同学,应该也会比较头疼的,记录下我的逆向学习之旅,增强下记忆,记录分享也是自我提升的过程吧。轻松过掉反调试后,就能有更多的时间去研究壳本身流程和加解密操作。先前的dex加_原创]360加固之oncreate函数还原并重打包

jquery异常 -- Uncaught TypeError: $(...).on is not a function_uncaught typeerror: $(...).onclick is not a functi-程序员宅基地

文章浏览阅读1.3w次,点赞2次,收藏6次。jquery异常 – Uncaught TypeError: $(…).on is not a function报错解决办法第一种 :引入更高的js第二种:使用bind_uncaught typeerror: $(...).onclick is not a function

CANoe测试TC8的环境搭建以及带有VLAN标签的DUT网卡该如何配置CANoe测试环境_tc8 vlan-程序员宅基地

文章浏览阅读3.8k次,点赞6次,收藏12次。CANoe环境需要硬件和软件,硬件就是CANoe设备,用来连接电脑和DUT,TC8测试的是Ethernet,那么CANoe设备必须要支持Ethernet才行,目前VN5640以上都是支持的。软件需要安装驱动和CANoe开发测试环境,还有执行TC8的demo工程,路径是:C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 12.0.216\Ethernet\Test\EthernetTC8Test确保以太网线路是通的,状态是link up的_tc8 vlan

随便推点

win10 x64 System.IO.FileNotFoundException: 未能加载文件或程序集“System.Data.SQLite.dll”或它的某一个依赖项_未能加载文件或程序集“system.data.sqlite.dll”或它的某一个依赖项。找不到指定-程序员宅基地

文章浏览阅读5k次。问题:在程序运行的过程中,会报如下的问题System.IO.FileNotFoundException: 未能加载文件或程序集“System.Data.SQLite.dll”或它的某一个依赖项。找不到指定的模块。 文件名:“System.Data.SQLite.dll” 在 YZ.IntelligentWeighbridge.DA.DataCommand.ExecuteScalar[T](String sql, Object param, Boolean isQuery)解决方案..._未能加载文件或程序集“system.data.sqlite.dll”或它的某一个依赖项。找不到指定

Unity 3D加载外部资源_unity3d c# 动态加载外部模型-程序员宅基地

文章浏览阅读1.5k次。Unity 3D里有两种动态加载机制: 一是Resources.Load; 一是通过AssetBundle; 其实两者本质上没有什么区别。Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时动态加载,可以指定路径和来源的。其实场景里所有静态的对象也有这么一个加载过程,只是_unity3d c# 动态加载外部模型

解剖Nginx·模块开发篇(2)ngx_http_hello_world_module 模块基本结构定义_ngx_http_loc_conf_offset是什么意思-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏6次。解剖Nginx·模块开发篇(2)ngx_http_hello_world_module 基本结构定义作者:柳大·Poechant(钟超)邮箱:zhongchao.ustc#gmail.com(# -> @)博客:Blog.CSDN.net/Poechant日期:June 2nd, 2012HelloWorld 是一个典型的 location 模块。什么是 location 模块?在 Nginx 中_ngx_http_loc_conf_offset是什么意思

QT PySide 连连看小游戏_frvis-程序员宅基地

文章浏览阅读1k次。学了PySide,花了一天时间做出了以前很想写的一个游戏,然后写完主体的逻辑之后就不想再接着往下写了。在windows下写的6*6的方格,总共6种图像,网上找的6张gif图片,然后把大小用windows的画图变成50*50像素的图像,这是点击之前的图像。然后把gif图中白色的像素涂黑变成点击之后的图像(这个也是用windows画图解决的)。刚接触python,感觉格式会很好,但是self这个有点不爽_frvis

目标检测(YOLO,SSD,Efficientdet,RCNN系列)_efficientdet_lite fastrcnn-程序员宅基地

文章浏览阅读6.3k次,点赞7次,收藏54次。文章目录前言一、YOLO 系列1.1 yolo v3yolo v3 网络yolo v3 预测流程yolo v3 训练流程(暂时没梳理出来)1.2 yolo v4二、SSD 系列SSD网络SSD预测流程SSD训练流程三、Retinanet(后面更新)四、Efficientdet(后面更新)五、RCNN 系列5.1 Faster R-CNNFaster R-CNN 网络Faster R-CNN 训练5.2 Mask R-CNNMask R-CNN 网络5.4 Keypoint R-CNN5.5六、图像分割前言_efficientdet_lite fastrcnn

ruoyi-vue部署在nginx非根目录刷新404(已解决)_若依 不部署在根目录-程序员宅基地

文章浏览阅读5.2k次,点赞2次,收藏8次。我的前端框架用的是ruoyi,已知vue-element-admin框架也是通用的我是在nginx的html目录下新建多个二级目录存放不同的前端项目注意步骤:1.vue.config.js里面的publicPath应该改成"production"?"/你的文件夹名称/":"/",注意要把前面的/替换成你的nginx的html下前端项目的文件夹名称2.router文件夹下index.js最下面有个export default new Router,里面的mode:设置成history,设置base:为_若依 不部署在根目录

推荐文章

热门文章

相关标签