如何设计安全可靠的开放接口---对请求参加密保护

2023-05-16

文章目录

  • 【如何设计安全可靠的开放接口】系列
  • 前言
  • AES加解密
  • 代码实现

【如何设计安全可靠的开放接口】系列

1. 如何设计安全可靠的开放接口—之Token
2. 如何设计安全可靠的开放接口—之AppId、AppSecret
3. 如何设计安全可靠的开放接口—之签名(sign)
4. 如何设计安全可靠的开放接口【番外篇】—关于MD5应用的介绍
5. 如何设计安全可靠的开放接口—还有哪些安全保护措施
6. 如何设计安全可靠的开放接口—对请求参加密保护
7. 如何设计安全可靠的开放接口【番外篇】— 对称加密算法

前言

前面提到过,到目前为止我们已经基本上实现了一个安全可靠的开放接口设计,本节我们再来完善最后一块拼图:对业务参数的加密。
有些时候,如果有些参数比较敏感,你不希望以明文的方式在网络中传输,那么就可以使用加解密的方式。

AES加解密

一般来说,要完成对业务参数的加解密,最常用的算法就是AES了,它是一种对称加密算法,也是当前公认的即安全,又兼顾性能的对称加密算法,加解密双方通过约定好的密钥来分别对明文加密,和密文解密,基本常见主流的工具包中都有其实现的API,可以直接拿来使用,非常方便。

代码实现

具体代码实现方式,就继续接着前面的案例,添加了clientCallWithEncryptionserverVerifyWithDecryption(string)两个方法,分别是接口请求方对请求的业务参数进行加密,和接口提供方对请求参数进行解密的版本。

import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.RandomStringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;


public class AppUtils {

    /**
     * key:appId、value:appSecret
     */
    static Map<String, String> appMap = Maps.newConcurrentMap();

    /**
     * key:appId、value:appAESSecret
     */
    static Map<String, String> appAesMap = Maps.newConcurrentMap();

    /**
     * 分别保存生成的公私钥对
     * key:appId,value:公私钥对
     */
    static Map<String, Map<String, String>> appKeyPair = Maps.newConcurrentMap();


    static Cache<String, String> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();

    private static final String AES_ALG = "AES";

    private static final String AES_CBC_PCK_ALG = "AES/CBC/PKCS5Padding";

    private static final byte[] AES_IV = initIv();


    public static void main(String[] args) throws Exception {
        // 模拟生成appId、appSecret
        String appId = initAppInfo();

        // 根据appId生成公私钥对
        initKeyPair(appId);

        // 模拟请求方
        String requestParam = clientCall();

        // 模拟提供方验证
        serverVerify(requestParam);

        System.out.println("***************************业务参数加密版本***************************");

        // 以下是带加密业务参数的版本
        String encryptionContent = clientCallWithEncryption();

        serverVerifyWithDecryption(encryptionContent);
    }

    private static void serverVerifyWithDecryption(String encryptionContent) throws Exception {
        APIRequestEntity apiRequestEntity = JSONObject.parseObject(encryptionContent, APIRequestEntity.class);
        Header header = apiRequestEntity.getHeader();

        // AES解密
        Object body = apiRequestEntity.getBody();
        String content = decrypt(body.toString(), appAesMap.get(header.getAppId()), "UTF-8");
        System.out.println();
        System.out.println("【业务参数解密后内容】:" + content);

        UserEntity userEntity = JSONObject.parseObject(content, UserEntity.class);

        // 首先,拿到参数后同样进行签名
        String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
        if (!sign.equals(header.getSign())) {
            throw new Exception("数据签名错误!");
        }

        // 从header中获取相关信息,其中appSecret需要自己根据传过来的appId来获取
        String appId = header.getAppId();
        String appSecret = getAppSecret(appId);
        String nonce = header.getNonce();
        String timestamp = header.getTimestamp();

        // 请求时间有效期校验
        long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) {
            throw new Exception("请求过期!");
        }

        // nonce有效性判断
        String str = cache.getIfPresent(appId + "_" + nonce);
        if (Objects.nonNull(str)) {
            throw new Exception("请求失效!");
        }
        cache.put(appId + "_" + nonce, "1");


        // 按照同样的方式生成appSign,然后使用公钥进行验签
        Map<String, String> data = Maps.newHashMap();
        data.put("appId", appId);
        data.put("nonce", nonce);
        data.put("sign", sign);
        data.put("timestamp", timestamp);
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[0]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("appSecret=").append(appSecret);


        if (!rsaVerifySignature(sb.toString(), appKeyPair.get(appId).get("publicKey"), header.getAppSign())) {
            throw new Exception("验签错误!");
        }

        System.out.println();
        System.out.println("【提供方】验证通过!");
    }

   
    private static byte[] initIv() {

        try {
            Cipher cipher = Cipher.getInstance(AppUtils.AES_CBC_PCK_ALG);
            int blockSize = cipher.getBlockSize();
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        } catch (Exception e) {
            int blockSize = 16;
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        }
    }

    public static String decrypt(String content, String key, String charset) throws Exception {
        Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
        IvParameterSpec iv = new IvParameterSpec(initIv());
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(key.getBytes()),
                AES_ALG), iv);
        byte[] cleanBytes = cipher.doFinal(Base64.decodeBase64(content.getBytes()));
        return new String(cleanBytes, charset);
    }

    public static String encrypt(String content, String aesKey, String charset) throws Exception {
        Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
        IvParameterSpec iv = new IvParameterSpec(AES_IV);
        cipher.init(Cipher.ENCRYPT_MODE,
                new SecretKeySpec(Base64.decodeBase64(aesKey.getBytes()), AES_ALG), iv);
        byte[] encryptBytes = cipher.doFinal(content.getBytes(charset));
        return new String(Base64.encodeBase64(encryptBytes));
    }

    private static String clientCallWithEncryption() throws Exception {
        // 假设接口请求方与接口提供方,已经通过其他渠道,确认了双方交互的appId、appSecret
        String appId = "123456";
        String appSecret = appMap.get(appId);
        String timestamp = String.valueOf(System.currentTimeMillis());

        // 随机数
        String nonce = RandomStringUtils.randomAlphanumeric(10);

        // 业务请求参数
        UserEntity userEntity = new UserEntity();
        userEntity.setUserId("1");
        userEntity.setPhone("13912345678");

        // 使用sha256的方式生成签名
        String sign = getSHA256Str(JSONObject.toJSONString(userEntity));

        Map<String, String> data = Maps.newHashMap();
        data.put("appId", appId);
        data.put("nonce", nonce);
        data.put("sign", sign);
        data.put("timestamp", timestamp);
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[0]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("appSecret=").append(appSecret);

        System.out.println("【请求方】拼接后的参数:" + sb.toString());
        System.out.println();

        // 使用sha256withRSA的方式对header中的内容加签
        String appSign = sha256withRSASignature(appKeyPair.get(appId).get("privateKey"), sb.toString());
        System.out.println("【请求方】appSign:" + appSign);
        System.out.println();

        // 请求参数组装
        Header header = Header.builder()
                .appId(appId)
                .nonce(nonce)
                .sign(sign)
                .timestamp(timestamp)
                .appSign(appSign)
                .build();
        APIRequestEntity apiRequestEntity = new APIRequestEntity();
        apiRequestEntity.setHeader(header);
        apiRequestEntity.setBody(encrypt(JSONObject.toJSONString(userEntity), appAesMap.get(appId), "UTF-8"));

        String requestParam = JSONObject.toJSONString(apiRequestEntity);
        System.out.println("【请求方】接口请求参数: " + requestParam);

        return requestParam;
    }

    private static String initAppInfo() {
        // appId、appSecret生成规则,依据之前介绍过的方式,保证全局唯一即可
        String appId = "123456";
        String appSecret = "654321";

        // AES密钥,同样每个appId分别对应一个AES密钥。
        String encryptionKey = "50AHsYx7H3OHVMdF123456";

        appMap.put(appId, appSecret);
        appAesMap.put(appId, encryptionKey);

        return appId;
    }

    private static void serverVerify(String requestParam) throws Exception {
        APIRequestEntity apiRequestEntity = JSONObject.parseObject(requestParam, APIRequestEntity.class);
        Header header = apiRequestEntity.getHeader();
        UserEntity userEntity = JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()), UserEntity.class);

        // 首先,拿到参数后同样进行签名
        String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
        if (!sign.equals(header.getSign())) {
            throw new Exception("数据签名错误!");
        }

        // 从header中获取相关信息,其中appSecret需要自己根据传过来的appId来获取
        String appId = header.getAppId();
        String appSecret = getAppSecret(appId);
        String nonce = header.getNonce();
        String timestamp = header.getTimestamp();

        // 请求时间有效期校验
        long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) {
            throw new Exception("请求过期!");
        }

        // nonce有效性判断
        String str = cache.getIfPresent(appId + "_" + nonce);
        if (Objects.nonNull(str)) {
            throw new Exception("请求失效!");
        }
        cache.put(appId + "_" + nonce, "1");


        // 按照同样的方式生成appSign,然后使用公钥进行验签
        Map<String, String> data = Maps.newHashMap();
        data.put("appId", appId);
        data.put("nonce", nonce);
        data.put("sign", sign);
        data.put("timestamp", timestamp);
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[0]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("appSecret=").append(appSecret);


        if (!rsaVerifySignature(sb.toString(), appKeyPair.get(appId).get("publicKey"), header.getAppSign())) {
            throw new Exception("验签错误!");
        }

        System.out.println();
        System.out.println("【提供方】验证通过!");

    }

    public static String clientCall() {
        // 假设接口请求方与接口提供方,已经通过其他渠道,确认了双方交互的appId、appSecret
        String appId = "123456";
        String appSecret = appMap.get(appId);
        String timestamp = String.valueOf(System.currentTimeMillis());

        // 随机数
        String nonce = RandomStringUtils.randomAlphanumeric(10);

        // 业务请求参数
        UserEntity userEntity = new UserEntity();
        userEntity.setUserId("1");
        userEntity.setPhone("13912345678");

        // 使用sha256的方式生成签名
        String sign = getSHA256Str(JSONObject.toJSONString(userEntity));

        Map<String, String> data = Maps.newHashMap();
        data.put("appId", appId);
        data.put("nonce", nonce);
        data.put("sign", sign);
        data.put("timestamp", timestamp);
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[0]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("appSecret=").append(appSecret);

        System.out.println("【请求方】拼接后的参数:" + sb.toString());
        System.out.println();

        // 使用sha256withRSA的方式对header中的内容加签
        String appSign = sha256withRSASignature(appKeyPair.get(appId).get("privateKey"), sb.toString());
        System.out.println("【请求方】appSign:" + appSign);
        System.out.println();

        // 请求参数组装
        Header header = Header.builder()
                .appId(appId)
                .nonce(nonce)
                .sign(sign)
                .timestamp(timestamp)
                .appSign(appSign)
                .build();
        APIRequestEntity apiRequestEntity = new APIRequestEntity();
        apiRequestEntity.setHeader(header);
        apiRequestEntity.setBody(userEntity);

        String requestParam = JSONObject.toJSONString(apiRequestEntity);
        System.out.println("【请求方】接口请求参数: " + requestParam);

        return requestParam;
    }


    /**
     * 私钥签名
     *
     * @param privateKeyStr
     * @param dataStr
     * @return
     */
    public static String sha256withRSASignature(String privateKeyStr, String dataStr) {
        try {
            byte[] key = Base64.decodeBase64(privateKeyStr);
            byte[] data = dataStr.getBytes();
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(data);
            return new String(Base64.encodeBase64(signature.sign()));
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常", e);
        }
    }

    /**
     * 公钥验签
     *
     * @param dataStr
     * @param publicKeyStr
     * @param signStr
     * @return
     * @throws Exception
     */
    public static boolean rsaVerifySignature(String dataStr, String publicKeyStr, String signStr) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(dataStr.getBytes());
        return signature.verify(Base64.decodeBase64(signStr));
    }

    /**
     * 生成公私钥对
     *
     * @throws Exception
     */
    public static void initKeyPair(String appId) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, String> keyMap = Maps.newHashMap();
        keyMap.put("publicKey", new String(Base64.encodeBase64(publicKey.getEncoded())));
        keyMap.put("privateKey", new String(Base64.encodeBase64(privateKey.getEncoded())));
        appKeyPair.put(appId, keyMap);
    }

    private static String getAppSecret(String appId) {
        return String.valueOf(appMap.get(appId));
    }


    @SneakyThrows
    public static String getSHA256Str(String str) {
        MessageDigest messageDigest;
        messageDigest = MessageDigest.getInstance("SHA-256");
        byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
        return Hex.encodeHexString(hash);
    }

}

对比带加密和不带加密版本的执行效果。

【请求方】拼接后的参数:appId=123456&nonce=TWDo7kxIkE&sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5&timestamp=1653486151381&appSecret=654321

【请求方】appSign:N9QZEUvaK6HrzGlg2tnry4BoqWpuvy9UnXw2UBmJIyc6kDwrofCwnJs1Djc6+m619vVNbsrM8/EOHFnROrxVeNwD0n3hCDAlYgZ4uZVI/d3ZAFQqbl9qiNJKp0UgyB/Za6oyT+xdtihwwZb6qH5J/UOeGBJLel61kxk7/tIgtQsZ9s2Akk4epjsLEYbGhlHzBT7Misq7Ij60ODr21eKtzdEj6NQeM+3mLkAF/O27BOcjBcOdExRYwtmwvHRtYUnBuapVcnLtDif+7oauHJDnEFCt5+n8P5Q0lGSf5kD7sVz9rviqGV271n8R/P3rIc0Y/g1rm5jm42uy0j7fwIIj7Q==

【请求方】接口请求参数: {"body":{"phone":"13912345678","userId":"1"},"header":{"appId":"123456","appSign":"N9QZEUvaK6HrzGlg2tnry4BoqWpuvy9UnXw2UBmJIyc6kDwrofCwnJs1Djc6+m619vVNbsrM8/EOHFnROrxVeNwD0n3hCDAlYgZ4uZVI/d3ZAFQqbl9qiNJKp0UgyB/Za6oyT+xdtihwwZb6qH5J/UOeGBJLel61kxk7/tIgtQsZ9s2Akk4epjsLEYbGhlHzBT7Misq7Ij60ODr21eKtzdEj6NQeM+3mLkAF/O27BOcjBcOdExRYwtmwvHRtYUnBuapVcnLtDif+7oauHJDnEFCt5+n8P5Q0lGSf5kD7sVz9rviqGV271n8R/P3rIc0Y/g1rm5jm42uy0j7fwIIj7Q==","nonce":"TWDo7kxIkE","sign":"c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5","timestamp":"1653486151381"}}

【提供方】验证通过!
***************************业务参数加密版本***************************
【请求方】拼接后的参数:appId=123456&nonce=5haU5Ltbm5&sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5&timestamp=1653486151555&appSecret=654321

【请求方】appSign:ytqqV+llqbpRBzzO2Kr/gEyLgmBnQHQ5iLDdEGvU2Rq6HhahC46i3hV85LI392Yduh98EvC9x2i8Vve6IHY4Gsp6pch4gw6DxvndB9Gk84NYBMYnsXc9+/ddKD44CbDMd7BzHB+A5zNcuXyJqFYQLxV+5GpHXFYtmqHB+jVUYK2ZnPxMi2PXkKENlsSYUdNh8xp2gM1OX+gB9T/yCoVTAN3itHAzBCAS5c52vTGbz9qKw7oUx4L5Hs5OkxAw8q3t8H+srX2pi/F9+1KOAIbw6Y7V4YggMXq3WtXTlPLnx7z+YJu9DsoevDKDdJPkiq86liUMmUFyXlAViW2bx26RYQ==

【请求方】接口请求参数: {"body":"RLdUZ42CyRpUAzu9CsDS8Q0X1ARoM0dynwSmF66bfkDxpWBDQC0xYFDjBJWk13WK","header":{"appId":"123456","appSign":"ytqqV+llqbpRBzzO2Kr/gEyLgmBnQHQ5iLDdEGvU2Rq6HhahC46i3hV85LI392Yduh98EvC9x2i8Vve6IHY4Gsp6pch4gw6DxvndB9Gk84NYBMYnsXc9+/ddKD44CbDMd7BzHB+A5zNcuXyJqFYQLxV+5GpHXFYtmqHB+jVUYK2ZnPxMi2PXkKENlsSYUdNh8xp2gM1OX+gB9T/yCoVTAN3itHAzBCAS5c52vTGbz9qKw7oUx4L5Hs5OkxAw8q3t8H+srX2pi/F9+1KOAIbw6Y7V4YggMXq3WtXTlPLnx7z+YJu9DsoevDKDdJPkiq86liUMmUFyXlAViW2bx26RYQ==","nonce":"5haU5Ltbm5","sign":"c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5","timestamp":"1653486151555"}}

业务参数解密后内容:{"phone":"13912345678","userId":"1"}

【提供方】验证通过!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何设计安全可靠的开放接口---对请求参加密保护 的相关文章

  • HDMI转CSI转换板给你做出来了

    小编前段时间一直在做无人机图像的项目 因为项目要求飞机飞行速度较快 小编就像找一款是全局快门 防抖 自动对焦 焦距定焦镜头的一款相机和镜头 首先想到的就是gopro 体积不大 价格也还行 小编之前出去玩一直用对成像效果也是很满意 于是小编就
  • cmakelist.txt 编写教程

    1 CMake编译原理 CMake是一种跨平台编译工具 xff0c 比make更为高级 xff0c 使用起来要方便得多 CMake主要是编写CMakeLists txt文件 xff0c 然后用cmake命令将CMakeLists txt文件
  • VS Code在线安装ESP-IDF出现乱码(已解决)

    VS Code在线安装ESP IDF出现乱码 xff08 已解决 xff09 VS Code安装ESP IDF插件的安装乱码解决办法 VS Code安装 按照乐鑫给出的要求是安装VS Code之前需要安装Git和Python3 xff08
  • ffmpeg推流rtmp指定udp传输

    RTMP Real Time Messaging Protocol 是一个用于音频 视频和数据的传输协议 RTMP 协议本身可以支持 TCP 或 UDP 作为其底层传输协议 在 RTMP 中 xff0c TCP 是默认的传输协议 xff0c
  • 单片机小白学习之路(十五)---定时器和计数器的理解(一)

    目标 xff1a 定时器和计数器的理解 一 1 定时器 计数器简介 定时器 计数器 xff08 Timer Counter xff0c 简称T C xff09 是单片机中最基本的接口之一 即可以定时又可以计数 常用于计数 延时 测量周期 脉
  • stm32---ADXL345

    ADXL345是一款三轴加速度传感器 xff0c 广泛用于手机 游戏手柄等设计 ADXL 支持标准的 I2C 或 SPI 数字接口 xff0c 自带 32 级 FIFO 存储 xff0c 并且内 部有多种运动状态检测和灵活的中断方式等特性
  • fastjson中JSONObject.parse方法使用注意

    今天遇到有同事在使用fastjson的JSONObject时 xff0c 直接在parse方法中传入了一个非json格式的字符串 xff0c 造成有时候报错 xff0c 有时候又能正常返回 问题现象 当你传入一个数值类型时 xff0c 可以
  • HZ和秒之间换算

    Hz和毫秒不能直接换算 xff0c 两者是交流电频率与周期的关系 xff0c 并且是倒数关系 xff1a 周期T 61 1 100 61 0 01秒 61 10毫秒 100Hz即100次 秒 xff0c 即60x100 60秒 xff0c
  • 野火 FireConfig 从SD卡下载镜像到EMMC

    1 用balenaEtcher把镜像下载到SD卡 2 拨码到SD卡启动 3 用MobaXterm当串口终端 xff0c 选择115200 xff0c 取消硬件流 4 输入用户名cat 密码fish 5 输入sudo fire config
  • VCC、VDD、VSS以及VBAT的区别

    原链接 xff1a https blog csdn net LemonLeeB article details 99417945 在STM32 的学习中 xff0c 发现有几种看起来相关的名称 xff0c 分别是VCC VDD VSS VB
  • LWIP_MDNS

    一 xff0e mdns1 什么是mdns xff1f mDNS协议适用于局域网内没有DNS服务器时的域名解析 xff0c 设备通过组播的方式交互DNS记录来完成域名解析 xff0c 约定的组播地址是 xff1a 224 0 0 251 x
  • 组播IGMP

    一 xff0e 什么是组播 xff1f 1 一个发送 组播源 xff0c 多个接收 xff0c 接收的有个特点就是在同一个组播组里面 xff0c 组播组有自己的IP 2 对于组播源来说 xff0c 发送命令到组播IP等于把命令发送到所有组成
  • 单片机小白学习之路(四十三)---LCD12864液晶显示

    目标 xff1a LCD12864原理的理解 1 LCD12864简介 LCD12864可以用来显示字符 数字 汉字 图形等内容 xff0c 其分辨率是128 64点 意思是横着有128个点 xff0c 竖直方向有64点 LCD12864
  • stm32---红外接受

    一个脉冲对应 560us 的连续载波 xff0c 一个逻辑 1 传输需要 2 25ms xff08 560us 脉冲 43 1680us 低电平 xff09 xff0c 一个逻辑 0 的传输需要 1 125ms xff08 560us 脉冲
  • printf重定向

    C语言中printf默认输出设备是显示器 xff0c 当开发板没有时我们就用串口来打印数据 int fputc int ch FILE p USART SendData USART1 ch 如果用串口2打印 xff0c 和换成USART2
  • SPI的CRC校验计算

    22 3 6 CRC计算 CRC校验仅用于保证全双工通信的可靠性 数据发送和数据接收分别使用单独的CRC计算器 通过对每一个接收位进行可编程的多项式运算来计算CRC CRC的计算是在由SPI CR1寄存器 中CPHA和CPOL位定义的采样时
  • 记录JPA并发save时遇到的坑

    前言 在JPA中 xff0c 使用save方法时是这样的 xff1a 如果我们save的对象指定了主键 xff0c 那么会根据主键先进行一次查询 xff0c 如果查询记录不存在则执行insert语句 xff0c 如果查询记录存在则执行upd
  • Openmv(一)OpenMV图像处理的基本方法

    一 图像处理基础知识 摄像头 xff1a 光学信号转换成电信号 计算机视觉中 xff0c 最简单的模型是小孔成像模型 小孔成像是一种理想模型 xff0c 实际镜头会存在场曲和畸变等 xff0c 但可以通过在标定过程中引入畸变参数解决 xff
  • CMakeLists详解

    CMakeLists详解 一 CMake简介 cmake 是一个跨平台 开源的构建系统 它是一个集软件构建 测试 打包于一身的软件 它使用与平台和编译器独立的配置文件来对软件编译过程进行控制 二 常用命令 1 指定cmake最小版本 cma
  • c++继承与多态总结

    不知不觉C 43 43 课程的学习已经接近尾声 xff0c 感觉自己对于c 43 43 的认知更近了一步 xff0c 粗略总结一下最近学习的继承与多态部分的知识 继承 C 43 43 的继承 继承有3种形式 xff1a 私有继承 保护继承

随机推荐

  • C++对象的销毁

    对象的销毁 一般来说 xff0c 需要销毁的对象都应该做清理 解决方案 1 为每个类都提供一个public的free函数 xff1b 2 对象不再需要时立即调用free函数进行清理 析构函数 1 C 43 43 的类中可以定义一个特殊的清理
  • C++中类中的函数重载

    类中的函数重载 函数重载的回顾 1 函数重载的本质就是为相互独立的不同函数 xff1b 2 C 43 43 中通过函数名和函数参数确定函数调用 xff1b 3 无法直接通过函数名得到重载函数的入口地址 xff1b 4 函数重载必然发生在同一
  • C++中的字符串类

    字符串类 历史遗留的问题 1 C语言不支持真正意义上的字符串 xff1b 2 C语言用字符数组和一组实现字符串操作 xff1b 3 C语言不支持自定义类型 xff0c 因此无法获得字符类型 xff1b 解决方案 1 从C到C 43 43 的
  • MySQL中的Block Nested Loop优化分析

    前言 一般在MySQL规范中 xff0c 都会规定如果两张表进行join查询 xff0c 那么join的字段一定要有索引 xff0c 在之前的文章中我们分析了MySQL join大小表前后顺序影响分析 xff0c 这是在有索引的情况下 xf
  • C++之类模板的概念和意义

    类模板 一些类主要用于存储和组织数据元素 类中数据组织的方式和数据元素的具体类型无关 如 xff1a 数组类 链表类 Stack Queue类 等 1 C 43 43 中将模板的思想应用于类 xff0c 使得类的实现不关注数据元素的具体类型
  • C++之单例类模板

    需求的提出 在架构设计时 xff0c 某些类在整个系统生命周期中最多只能有一个对象存在 xff08 Single Instance xff09 要控制类的对象数目 xff0c 必须对外隐藏构造函数 xff1b 思路 xff1a 1 将构造函
  • 【无标题】

    绘图控件GraphicsView 一 GraphicsView简介 1 QT有多种绘图相关的技术 xff0c 我们将在第2部分 2 4 QT绘图和图表 中比较详细系统的讲 2 本节简单讲一下GraphicsView的基本理论 xff0c 并
  • uboot源码分析之start.S解析

    1 start S引入 1 1 u boot lds中找到start S入口 1 在uboot中因为有汇编阶段参与 xff0c 因此不能直接找main c 整个程序的入口取决于链接脚本中ENTRY声明的地方 ENTRY start 因此 s
  • uboot启动第二阶段

    uboot启动第二阶段 start armboot函数简介 一个很长的函数 1 这个函数在uboot lib arm board c的第444行开始到908行结束 2 450行还不是全部 xff0c 因为里面还调用了别的函数 3 为什么这么
  • cmake设置编译类型为release命令

    cmake编译类型通常默认为debug xff0c 但是在编译软件时 xff0c 一般都需要使用release版本的 xff0c debug太慢了 设置为release版本可以在cmake文件里进行 xff0c 也可以在运行cmake命令时
  • 设计模式之单例模式(Singleton),以C++为例,实现日志输出。

    Hello大家好 xff0c 好久没更新了 xff0c 今天给大家补上最基础的设计模式 xff1a 单例模式 这个单例模式实在是我的心结啊 xff0c 2021年末左右面试京东算法岗 xff0c 面试官让我写一个单例 xff0c 没写出来
  • 源码分析MyBatis对数值(int、double)类型进行test判断的误区

    文章目录 问题描述问题分析验证解析表达式执行解析后表达式分别测试两个条件 查询Ognl官方文档验证问题解决 问题描述 在如下判断中 xff0c 如果type类型为int xff0c 那么对于type 61 39 39 部分判断会出现一些问题
  • Git报错:error: xxxx bytes of body are still expected.

    git一个很老的项目 xff0c 项目深度很深 xff0c 报错 xff1a error 7857 bytes of body are still expected fetch pack unexpected disconnect whil
  • 设计模式之代理模式(Proxy),以C++为例,实现远程代理、虚拟代理、保护代理等。

    兄弟姐妹们好 xff0c 又是好久没有更新了 xff0c 今天给大家简单介绍代理模式 xff0c 一个很简单的设计模式 xff0c 旨在不改变原对象的情况下通过代理对象来控制对原对象的访问 代理模式根据具体情况还可以分为远程代理 虚拟代理
  • C++ 互斥锁原理以及实际使用介绍

    兄弟姐妹们 xff0c 我又回来了 xff0c 今天带来实际开发中都需要使用的互斥锁的内容 xff0c 主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁 实现互斥 xff0c 可以有以下几种方式 xff1a 互斥量 xff08 Mute
  • 【C++】使用【windwos api】获取windwos计算机的基本信息

    今天来一篇获取windows计算机的基本信息的文章 xff0c 包含计算机名称 操作系统版本 处理器信息 内存信息 硬盘信息 显示器信息 网络信息 驱动程序信息 电源信息 其他硬件信息 目录 一 windwos系统包含的基本信息 二 获取信
  • C++ POCO库的基础介绍(Windwos和Linux)

    简单介绍C 43 43 POCO库能干什么 xff0c 后续有时间的话将根据其每个点详细解析 xff0c 关注我 本篇包含POCO库简单介绍 下载以及安装方式 简单代码示例 目录 一 POCO简单介绍 1 1 POCO库的基本模块 1 2
  • ROS踩坑记录

    ROS踩坑记录 问题 xff1a ubuntu 没有 dev ttyUSB0问题 xff1a 运行 launch 文件或 ROS 节点时出现 exit code 9 错误提示问题 xff1a windows使用vscode远程连接 xff0
  • STM32串口数据接收 --环形缓冲区

    STM32串口数据接收 环形缓冲区 环形缓冲区简介 在单片机中串口通信是我们使用最频繁的 xff0c 使用串口通信就会用到串口的数据接收与发送 xff0c 环形缓冲区方式接收数据可以更好的保证数据丢帧率第 在通信程序中 xff0c 经常使用
  • 如何设计安全可靠的开放接口---对请求参加密保护

    文章目录 如何设计安全可靠的开放接口 系列前言AES加解密代码实现 如何设计安全可靠的开放接口 系列 1 如何设计安全可靠的开放接口 之Token 2 如何设计安全可靠的开放接口 之AppId AppSecret 3 如何设计安全可靠的开放