支付系统设计三:渠道网关设计03-参数验证

news2024/11/29 2:35:49

文章目录

  • 前言
  • 一、参数校验
    • 1. MessageDescription内容
    • 2. 参数验证执行器
    • 3. 字段验证处理器
    • 4. 验证器工厂
    • 5. style校验器
    • 6. 验证器接口
    • 7. 长度校验器
    • 8. 枚举类型校验器
    • 9. yml配置
      • 9.1 deduct.yml
      • 9.2 style.yml
  • 二、幂等校验
  • 总结


前言

在这里插入图片描述
在《支付系统设计三:渠道网关设计02-客户端报文解析》之后需要对上送参数进行校验、交易幂等校验。
本篇将讲解paygw对渠道(内部渠道paycore)请求参数的校验,即MessageDescription中datas值的校验。即如何对Map中的值进行校验:是否存在某值,即是否存在某key,对应的值是否合法,即value值长度、类型等是否合法。

fieldValidateExecutor.validate(transCode, messageDescription.getDatas());

一、参数校验

1. MessageDescription内容

经过客户端报文解析处理后paycore上送的报文内容经过解析脚本处理被解析填充到MessageDescription中的datas属性中,参数校验即根据上送的transCode获取到校验器对datas中的参数进行校验,是否满足接口定义。
在这里插入图片描述

交易时paycore请求paygw上送报文如下,下面要对header和body中的字段进行验证。

{
    "header": {
        "channelCode": "paycore",
        "transCode": "deduct",
        "channelDateTime": "20230510221050",
        "channelTransNo": "2023051000001"
    },
    "body": {
        "deptCode": "0308",
        "transAmount": 100,
        "acctNo": "622002324354354645665",
        "acctName": "张三",
        "mediaType": "01",
        "certType": "00",
        "certNo": "320390199909091234",
        "purpose": "代扣还款",
        "bizOrderNo": "20230510274235345001",
        "systemId": "1010",
        "productCode": "001",
		"dcFlag":"D",
		"orgId":"123"
    }
}

在这里插入图片描述

2. 参数验证执行器

/**
 * @author Kkk
 * @Describe: 参数验证执行器
 * 后台管理系统接口:transCode = transCode + transType
 * 运行时接口:transCode = transCode
 */
@Component
public class FieldValidateExecutor {

    /**
     * 字段验证处理器
     */
    @Autowired
    private FieldValidateYamlProcessor fieldValidateYamlProcessor;
    
    /**
     * 参数校验
     * @param transCode
     * @param data
     */
    public void validate(String transCode, Map<String, Object> data) {
        Map<String, List<FieldPolicy>> fieldPolicyListMap = this.fieldValidateYamlProcessor.getFieldPolicyListMap();
        if (!CollectionUtils.isEmpty(fieldPolicyListMap)) {
            List<FieldPolicy> fieldPolicyList = fieldPolicyListMap.get(transCode);
            if (!CollectionUtils.isEmpty(fieldPolicyList)) {
                for (FieldPolicy fieldPolicy : fieldPolicyList) {
                    fieldPolicy.validate(StringUtils.valueOf(data.get(fieldPolicy.getField())));
                }
            }
        }
    }
}

3. 字段验证处理器

/**
 * @author Kkk
 * @Describe: 字段验证处理器
 */
@Component
public class FieldValidateYamlProcessor extends YamlProcessor implements InitializingBean {
    /**
     * 字段验证yaml文件路径配置
     */
    private static final String FIELD_VALIDATE_YAML_CONFIG_PATH = "field.validate.yaml.config.path";

    /**
     * 应用环境
     */
    @Autowired
    private Environment env;

    /**
     * style校验器缓存(key为styleCode,value为Style对象)
     */
    private Map<String, Style> styleMap;

    /**
     * 字段策略缓存(key为交易码/交易码+交易类型,value为field策略列表)
     */
    private Map<String, List<FieldPolicy>> fieldPolicyListMap;

    private static final String STYLE = "style";
    private static final String FIELD = "field";
    private static final String FIELD_NAME = "fieldName";
    private static final String TYPE = "type";
    private static final String OPTION = "option";

    @Override
    public void afterPropertiesSet() throws InvocationTargetException, IllegalAccessException, IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(env.getProperty(FIELD_VALIDATE_YAML_CONFIG_PATH));
        super.setResources(resources);
        Map<String, Object> yamlMap = createMap();//yaml配置
        this.styleMap = initStyleMap(yamlMap);
        this.fieldPolicyListMap = initFieldPolicyListMap(yamlMap);
    }

    /**
     * 初始化style
     * @param yamlMap
     * @return
     */
    private Map<String, Style> initStyleMap(Map<String, Object> yamlMap) {
        Map<String, Style> returnMap = new HashMap<String, Style>();
        if (!CollectionUtils.isEmpty(yamlMap)) {
            Map<String, Map<String, Object>> styleMap = (Map<String, Map<String, Object>>) yamlMap.get(STYLE);
            if (!CollectionUtils.isEmpty(styleMap)) {
                for (Map.Entry<String, Map<String, Object>> entry : styleMap.entrySet()) {
                    returnMap.put(entry.getKey(), initStyle(entry.getValue()));
                }
            }
        }
        return returnMap;
    }

    /**
     * 初始化style
     * @param validateMap
     * @return
     */
    private Style initStyle(Map<String, Object> validateMap) {
        Map<String, Validate> validateMapWithStyle = initValidateMap(validateMap);
        Style style = new Style();
        style.setValidateMap(validateMapWithStyle);
        return style;
    }

    /**
     * @Description 初始化字段策略缓存
     * @Params
     * @Return
     * @Exceptions
     */
    private Map<String, List<FieldPolicy>> initFieldPolicyListMap(Map<String, Object> yamlMap) throws InvocationTargetException, IllegalAccessException {
        Map<String, List<FieldPolicy>> fieldPolicyListMap = new LinkedHashMap<String, List<FieldPolicy>>();
        if (!CollectionUtils.isEmpty(yamlMap)) {
            for (Map.Entry<String, Object> entry : yamlMap.entrySet()) {
                if (StringUtils.equals(STYLE, entry.getKey())) {
                    continue;
                }
                List<Map<String, Object>> fieldMapList = (List<Map<String, Object>>) entry.getValue();//交易字段列表
                if (!CollectionUtils.isEmpty(fieldMapList)) {
                    List<FieldPolicy> fieldPolicyList = new ArrayList<FieldPolicy>();
                    for (Map<String, Object> fieldMap : fieldMapList) {
                        String field = StringUtils.valueOf(fieldMap.get(FIELD));
                        String fieldName = StringUtils.valueOf(fieldMap.get(FIELD_NAME));
                        Style style = this.styleMap.get(StringUtils.valueOf(fieldMap.get(STYLE)));
                        Map<String, Validate> extendValidatorMap = getExtendValidatorMap(fieldMap);
                        fieldPolicyList.add(new FieldPolicy(field, fieldName, style, extendValidatorMap));
                    }
                    fieldPolicyListMap.put(entry.getKey(), fieldPolicyList);
                }
            }
        }

        return fieldPolicyListMap;
    }

    /**
     * 获取扩展验证器
     *
     * @param fieldMap
     * @return
     */
    private Map<String, Validate> getExtendValidatorMap(Map<String, Object> fieldMap) {
        Map<String, Object> map = MapUtils.paraFilter(fieldMap, FIELD, FIELD_NAME, STYLE);//扩展验证器
        return initValidateMap(map);
    }

    /**
     * 初始化校验器
     * @param map
     * @return
     */
    private Map<String, Validate> initValidateMap(Map<String, Object> map) {
        Map<String, Validate> extendValidateMap = new LinkedHashMap<String, Validate>();
        if (!CollectionUtils.isEmpty(map)) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String validateName = entry.getKey();
                if (StringUtils.equals(TYPE, validateName)) {
                    validateName = StringUtils.valueOf(entry.getValue());
                }
                Validate validate = ValidatorFactory.getValidator(validateName).init(StringUtils.valueOf(entry.getValue()));
                extendValidateMap.put(entry.getKey(), validate);
            }
        }
        return extendValidateMap;
    }

    /**
     * 解析yaml为map
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private Map<String, Object> createMap() throws InvocationTargetException, IllegalAccessException {
        final Map<String, Object> result = new LinkedHashMap<String, Object>();
        process(new MatchCallback() {
            @Override
            public void process(Properties properties, Map<String, Object> map) {
                merge(result, map);
            }
        });
        return result;
    }

    /**
     * 合并yaml中相同的key
     * @param output
     * @param map
     */
    private void merge(Map<String, Object> output, Map<String, Object> map) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            Object existing = output.get(key);
            if (value instanceof Map && existing instanceof Map) {
                Map<String, Object> result = new LinkedHashMap<String, Object>((Map) existing);
                merge(result, (Map) value);
                output.put(key, result);
            } else {
                output.put(key, value);
            }
        }
    }

    /**
     * 获取style
     * @param styleName
     * @return
     */
    public Style getStyle(String styleName) {
        if (CollectionUtils.isEmpty(this.styleMap)) {
            return null;
        }
        return this.styleMap.get(styleName);
    }

    public Map<String, Style> getStyleMap() {
        return styleMap;
    }

    public void setStyleMap(Map<String, Style> styleMap) {
        this.styleMap = styleMap;
    }

    public Map<String, List<FieldPolicy>> getFieldPolicyListMap() {
        return fieldPolicyListMap;
    }

    public void setFieldPolicyListMap(Map<String, List<FieldPolicy>> fieldPolicyListMap) {
        this.fieldPolicyListMap = fieldPolicyListMap;
    }
}

4. 验证器工厂

/**
 * @author Kkk
 * @Describe: 参数/字段验证器工厂
 */
public class ValidatorFactory {
    /**
     * 校验器容器
     */
    private static Map<String, Class<? extends Validate>> validatorMap = new HashMap<String, Class<? extends Validate>>();

    static {
        ValidatorFactory.regist("option", OptionValidator.class);
        ValidatorFactory.regist("length", LengthValidator.class);
        ValidatorFactory.regist("amount", AmountValidator.class);
        ValidatorFactory.regist("date", DateValidator.class);
        ValidatorFactory.regist("regex", RegexValidator.class);
        ValidatorFactory.regist("enums", EnumValidator.class);
    }

    private ValidatorFactory() {
        super();
    }

    /**
     * 注册校验器
     * @param validatorName
     * @param validateClass
     */
    public static void regist(String validatorName, Class<? extends Validate> validateClass) {
        validatorMap.put(validatorName, validateClass);
    }

    /**
     * 获取校验器
     * @param validatorName
     * @return
     */
    public static Validate getValidator(String validatorName) {
        try {
            return validatorMap.get(validatorName).newInstance();
        } catch (Exception e) {
            throw new PayGwException(SystemErrorCode.SYSTEM_ERROR);
        }
    }
}

5. style校验器

/**
 * @author Kkk
 * @Describe: style校验器
 */
public class Style {

    private Map<String, Validate> validateMap;

    /**
     * style校验
     * @param field
     * @param fieldName
     * @param value
     */
    public void validate(String field, String fieldName, String value) {
        if (!CollectionUtils.isEmpty(this.validateMap)) {
            for (Map.Entry<String, Validate> entry : this.validateMap.entrySet()) {
                Validate validator = entry.getValue();
                if (!validator.validate(value)) {
                    throw new PayCoreException(validator.getMessage(), new Object[]{fieldName});
                }
            }
        }
    }

    public Map<String, Validate> getValidateMap() {
        return validateMap;
    }

    public void setValidateMap(Map<String, Validate> validateMap) {
        this.validateMap = validateMap;
    }
}

6. 验证器接口

/**
 * @author Kkk
 * @Describe: 参数/字段验证器接口
 */
public interface Validate {

    /**
     * 初始化验证器
     * @param value
     * @return
     */
    Validate init(String value);

    /**
     * 参数/字段验证
     *
     * @param value 参数值
     * @return true:验证通过  false:验证未通过
     */
    boolean validate(String value);

    /**
     * 获取提示信息
     * @return
     */
    SystemErrorCode getMessage();
}

7. 长度校验器

/**
 * @author Kkk
 * @Describe: 长度校验器
 */
public class LengthValidator extends AbstractValidator {
    /**
     * 长度
     */
    private String length;

    @Override
    public boolean validate(String value) {
        return StringUtils.valueOf(value).matches("[\\s\\S]{" + length + "}");
    }

    @Override
    public SystemErrorCode getMessage() {
        return SystemErrorCode.LENGTH_VALIDATE_ERROR;
    }

    @Override
    public Validate init(String value) {
        this.length = value;
        return this;
    }
}

8. 枚举类型校验器

在com.kkk.common.enums路径下定义接口参数枚举类,对接口上送的参数是否符合枚举值进行校验。

/**
 * @author Kkk
 * @Describe: 枚举类型校验器
 */
public class EnumValidator extends TypeValidator {
    private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class);

    private String enumPackge = "com.kkk.common.enums.";

    private Class enumClass;

    /**
     * 参数/字段验证
     * @param value 参数值
     * @return true:验证通过  false:验证未通过
     */
    @Override
    public boolean validate(String value) {
        try {
            Object[] enums = enumClass.getEnumConstants();
            Method targetMethod = enumClass.getDeclaredMethod("getCode");
            for (Object enumObj : enums) {
                if (StringUtils.equalsIgnoreCase(value, targetMethod.invoke(enumObj).toString())) {
                    return true;
                }
            }
        } catch (Exception e) {
            logger.error("枚举验证不通过{}", value,e);
        }
        return false;
    }

    /**
     * 获取提示信息
     * @return
     */
    @Override
    public SystemErrorCode getMessage() {
        return SystemErrorCode.TYPE_VALIDATE_ERROR;
    }

    /**
     * 初始化验证器
     * @param value
     * @return
     */
    @Override
    public Validate init(String value) {
        try {
            enumClass = Class.forName(enumPackge + value);
        } catch (ClassNotFoundException e) {
            logger.error("枚举验证器初始化失败{}", value,e);
        }
        return this;
    }
}

9. yml配置

在field.validate.yaml.config.path路径下配置yml进行参数校验:

9.1 deduct.yml

#单笔代扣
deduct:
  - field: acctNo
    fieldName: 账号
    option: false
    style: acctNoStyle
  - field: acctName
    fieldName: 账户名称
    style: acctNameStyle
  - field: deptCode
    fieldName: 开户机构编码
    option: false
    style: deptCodeStyle
  - field: transAmount
    fieldName: 交易金额
    option: false
    style: transAmountStyle
  - field: certType
    fieldName: 证件类型
    style: certTypeStyle
  - field: certNo
    fieldName: 证件号码
    style: certNoStyle
  - field: mediaType
    fieldName: 介质类型
    style: mediaTypeStyle
  - field: provinceCode
    fieldName: 省编码
    style: areaCodeStyle
  - field: provinceName
    fieldName: 省名称
    style: areaNameStyle
  - field: cityCode
    fieldName: 市编码
    style: areaCodeStyle
  - field: cityName
    fieldName: 市名称
    style: areaNameStyle
  - field: goodsName
    fieldName: 商品名称
    style: goodsNameStyle
  - field: notifyKey
    fieldName: 异步通知地址
    style: urlStyle
  - field: instCode
    fieldName: 支付渠道编码
    style: instCodeStyle
  - field: productCode
    fieldName: 产品码
    option: false
    style: productCodeStyle
  - field: contractNo
    fieldName: 合同号
    style: contractNoStyle
  - field: purpose
    fieldName: 用途
    style: purposeStyle
  - field: remark
    fieldName: 备注
    style: remarkStyle
deductQry:
  - field: channelTransNo
    fieldName: 支付系统流水号
    option: false
    style: transNoStyle

9.2 style.yml

#此文件中定义字段校验style,在交易参数配置中引用相应的style进行校验
style:
  acctNoStyle:
      length: 1,32
      regex: ^[\w\-\.]{1,32}|(\w)+(\.\w+)*@(\w)+((\.\w+)+)$
  acctNameStyle:
      length: 1,64
      regex: ^[\w\-\.\u00b7\u2022\uff08\uff09\u0028\u0029\u201C\u201D\u4E00-\u9FA5]{1,64}$
  deptCodeStyle:
      length: 1,16
      regex: ^[\w\-]{1,16}$
  certTypeStyle:
      length: 2,2
      regex: ^\d{2,2}$
  certNoStyle:
      length: 1,32
      regex: ^\w{1,32}$
  dcFlagStyle:
      length: 1,1
      regex: ^D|C$

二、幂等校验

 /**
     * redis幂等性校验(交易码+业务渠道+业务渠道流水号)
     * @param transCode
     * @param channelCode
     * @param channelTransNo
     */
    public void uniqueCheck(String transCode, String channelCode, String channelTransNo) {

        //1、获取redisKey
        String redisKey = transCode + channelCode + channelTransNo;

        //2、使用redis进行唯一性判断;
        boolean isExist = false;
        try {
            isExist = redisService.exists(redisKey);
        } catch (Exception e) {
            LoggerUtil.error(logger, "幂等性校验-交易码({})-支付系统编码({})-支付系统流水号({})-redis幂等性校验异常", transCode, channelCode, channelTransNo, e);
        }

        if (isExist) {
            LoggerUtil.warn(logger, "幂等性校验-redisKey({})已经存在-value({})", redisKey, redisService.getByKey(redisKey));
            throw new PayGwException(SystemErrorCode.HAS_EXIST, new Object[]{"支付系统流水号(" + channelTransNo + ")"});
        }
    }

总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux内核编译不通过问题的两种排查方法(非正式)

目录 一、前言 二、 问题现象 三、问题排查思路1&#xff1a;问题所在处向上排查 3.1 整体思路 3.2 排查过程 3.3 资料查询 3.4 尝试动作1&#xff1a;开启相应配置项 3.4.1 检查内核配置项 3.4.2 开启配置项 3.4.3 尝试编译 四、问题排查思路2&#xff1a;从上向下排…

讯飞星火大模型体验报告

近日&#xff0c;科大讯飞召开了星火认知大模型成果发布会&#xff0c;会上表示讯飞星火大模型将突破开放式问答&#xff0c;对标ChatGPT&#xff0c;在中文能力上超过ChatGPT&#xff0c;在英文能力上与ChatGPT相当。对此&#xff0c;你怎么看&#xff1f; 笔者准备给bing/ch…

数据结构第二天:File Transfer 【树的应用:集合】

原题是英文的&#xff1a;题目详情 - 05-树8 File Transfer (pintia.cn) 我用软件翻译了一下: 我们有一个计算机网络和一系列双向连接。这些连接中的每一个都允许文件从一台计算机传输到另一台计算机。有没有可能从网络上的任何一台计算机向其他计算机发送文件? Input Specif…

STM32 学习笔记_7 定时器中断:输出比较

输出比较 电机相关比较重要。 OC Output Compare&#xff08;IC 是输入捕获&#xff0c;CC代指这两个单元&#xff09;&#xff0c;用于输出一定频率和占空比的PWM波形。 右下角四个就是CCR。只有通用计时器和高级计时器有&#xff0c;共用一个cnt计数器&#xff0c;高级计数…

(数字图像处理MATLAB+Python)第七章图像锐化-第四节:频域高通滤波与综合案例

文章目录 一&#xff1a;频域高通滤波&#xff08;1&#xff09;理想的高通滤波器&#xff08;2&#xff09;巴特沃斯高通滤波器&#xff08;3&#xff09;指数高通滤波器&#xff08;4&#xff09;梯形高通滤波器 二&#xff1a;综合案例——人像美化&#xff08;1&#xff09…

C语言函数大全-- w 开头的函数(2)

C语言函数大全 本篇介绍C语言函数大全-- w 开头的函数 1. wcstok 1.1 函数说明 函数声明函数功能wchar_t *wcstok(wchar_t *wcs, const wchar_t *delim, wchar_t **ptr);用于将一个长字符串拆分成几个短字符串&#xff08;标记&#xff09;&#xff0c;并返回第一个标记的地…

非煤矿山电子封条建设算法 yolov8

非煤矿山电子封条建设算法模型通过yolov8网络模型AI视频智能分析技术&#xff0c;算法模型对作业状态以及出井入井人员数量变化、人员睡岗离岗等情况实时监测分析&#xff0c;及时发现异常动态&#xff0c;自动推送生成的违规截图报警信息。现代目标检测器大部分都会在正负样本…

免费下载Sketch模板素材一文搞定!

对于设计师来说&#xff0c;UI 模板素材是提高设计效率和保证设计质量的重要工具。然而&#xff0c;很多设计师在使用 Sketch 时&#xff0c;会遇到 Sketch 模板素材不足、格式不兼容或使用成本高等问题。本文将为你介绍一款资源齐全、跨平台、无需下载、免费使用的 Sketch 模板…

Tips for Deep Learning

目录 Recipe of Deep Learning Good Results on Training Data&#xff1f; New activation function Adaptive learning rate Good Results on Testing Data&#xff1f; Early Stopping Regularization Dropout Recipe of Deep Learning 我们要做的第一件事是&#x…

http协议在万维网的一生

tcp与udp的区别 http协议位于应用程序层&#xff0c;必须经过传输层进行通信 tcp即传输控制协议&#xff0c;首先通过三次握手建立连接&#xff0c;然后传输数据&#xff0c;数据传输成功后 通过四次挥手关闭连接&#xff0c;如有数据丢失则会重试保证数据传输可靠性 是一个面向…

树的前中后序以及广度优先搜索和深度优先搜索

文章目录 基本概念定义一棵树前序遍历中序遍历后序遍历BFS广度优先遍历DFS深度优先遍历 基本概念 树是一个有n个有限节点组成一个具有层次关系的集合&#xff0c;每个节点有0个或者多个子节点&#xff0c;没有父节点的节点称为根节点&#xff0c;也就是说除了根节点以外每个节…

TS38.331中need -M/R/N/S 的含义

need M&#xff1a;这种字段需要UE在不存在时维护&#xff1b;need R&#xff1a;当RRC中此字段不存在时&#xff0c;UE需要释放&#xff1b;need N&#xff1a;当此字段不存在时&#xff0c;UE不需要采取任何行动&#xff08;即UE不需要保持该字段的任何现有值&#xff09;&am…

如何使用单片机点亮LED灯,并使用按键控制[51单片机]

首先先看一下我的板子&#xff0c;如果我们板子不相同&#xff0c;可能操作也不太相同 我们就不讲底层原理了&#xff0c;直接看&#xff0c;我们可以看到板子上有 8个LED灯 那这个8个LED用什么来控制呢&#xff0c;我们先看底层的线路图&#xff0c;所有的LEDD都连接到了P2带后…

云原生技术中的容器技术有哪些?

文章目录 云原生技术中的容器技术有哪些1、云原生的含义2、容器的含义3、云原生的技术的基石&#xff1a;容器技术4、容器技术有哪些? 结语 云原生技术中的容器技术有哪些 在现今的安全行业中云原生安全技术中的容器安全技术有哪些呢&#xff0c;很多用户都不知道具体的含义以…

Linux程序设计之UDP通信

1.UDP通信是不可靠的、无连接的通信&#xff0c;客户端只需要服务器端的地址信息即可与之进行通信。UDP通信的基本流程如下&#xff1a; 2.利用UDP实现服务器端与客户端的通信如下&#xff1a; /*UDP通信*/ //服务端#include <stdio.h> #include <string.h> #incl…

低造价形状记忆合金相变温度DSC热分析替代技术:帕尔贴热电装置和电阻温度测量

摘要&#xff1a;形状记忆合金&#xff08;SMA&#xff09;是一种先进的金属材料&#xff0c;其物理和机械性能本质上依赖于温度。为了快速和低成本的实现SMA相变温度和热滞后性能的测试表征&#xff0c;基于更灵敏的电阻温度依赖关系&#xff0c;本文提出了采用帕尔贴TEC加热制…

论文学习笔记:ViT Vision Transformer An Image is worth 16X16 words

论文阅读&#xff1a;ViT Vision Transformer An Image is worth 16X16 words 今天介绍的是 Transformer 在视觉领域的一篇非常重要的论文&#xff0c;Vision Transformer&#xff0c;这篇文章发表在 2021 ICLR 的会议上。长久以来 CNN 一直是 CV 领域最主流的模型&#xff0c…

从零开始:开发一款应用程序的完整流程技巧

在数字化快速发展的现在&#xff0c;开发一款应用程序已经不是一件困难的事情。低代码应用开发平台的诞生更是让应用开发变成一项人人都能快速掌握的技能。之前&#xff0c;我们使用Java、C语言等传统的语言技术进行应用开发&#xff0c;来满足企业的业务应用需求。但是&#x…

rm / -rf指令的作用是?

学习Linux 指令&#xff0c;它是操作系统的前端&#xff0c;学好这部分内容一方面可以帮助你应对工作场景&#xff0c;另一方面可以让你在学习操作系统底层知识前&#xff0c;对 Linux 有一个大概的了解。 学习 Linux 指令之前&#xff0c;先来说一下什么是 Shell&#xff1f;S…

使用FFMPEG加载外挂字幕小记

ffmpeg版本&#xff1a; FFMEPEG 4.4 继上一篇简易播放器实现后&#xff0c;优化过程中&#xff0c;加载外挂字幕小记的过程和遇到的坑记录如下&#xff1a; 视频字幕分为三种。 内嵌字幕&#xff0c;字幕与视频图像合二为一&#xff0c;成为视频帧的一部分。 内封字幕&…