配置文件及日志文件脱敏

2023-11-05

配置文件脱敏

使用原因:在项目中,经常需要在配置文件里配置一些敏感信息,比如数据库用户名和密码,redis、mq的连接信息等。如果直接写明文,很容易造成密码泄露等安全问题。

jasypt简介

Jasypt是一个Java库,它允许开发者以最小的改动为项目添加基本的加密功能,而且不需要对密码学的工作原理有深刻的了解。

Jasypt特点
  1. 高安全性、基于标准的加密技术,既可用于单向加密也可用于双向加密。加密密码、文本、数字、二进制文件
  2. 与Hibernate的透明集成
  3. 适合集成到基本Spring的应用程序中,也可与Spring Security透明的集成
  4. 对应用程序的配置(即数据源)进行加密的综合能力
  5. 在多处理器/多核系统中具有高性能加密的特殊功能
  6. 开放的API,可与任何JCE供应商一起使用
  7. 配置相关的加密信息,就能够实现在项目运行的时候,自动把配置文件已经加密的信息解密成明文,供程序使用

可以加密所有的Spring环境配置信息,比如系统变量、环境变量、命令行变量、applicationproperties,yaml配置等

jar包
 <!--配置文件加密-->
 <!--方式一: 直接引入jasypt-spring-boot-starter-->
 <dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>2.0.0</version>
 </dependency>

<!-- 方式二: 需在启动类添加@EnableEncryptableProperties -->
 <dependency>
      <groupId>com.github.ulisesbocchio</groupId>
      <artifactId>jasypt-spring-boot</artifactId>
      <version>3.0.3</version>
 </dependency>

需要注意版本对应

jasypt-spring-boot-starter 依赖的 spring-boot-starter
2.1.0 2.0.3.RELEASE 2.2.6.RELEASE
2.0.0 2.0.0.RELEASE 2.2.6.RELEASE
1.18 1.5.10.RELEASE 2.2.6.RELEASE
1.12 1.5.1.RELEASE 2.2.6.RELEASE

需要注加解密的类型一致,如:

2.0.0;2.1.0;2.1.1;2.1.2版本默认加密方式为:PBEWithMD5AndDES
3.0.3版本默认加密方式为:PBEWITHHMACSHA512ANDAES_256
当引入3.0.3依赖,却没有添加相关jasypt加解密配置,而密文通过【PBEWithMD5AndDES】来加密,启动会报错。
需要切换为【PBEWITHHMACSHA512ANDAES_256】方式进行。
配置文件中密码加解密工具类
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.util.text.BasicTextEncryptor;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 配置文件中密码加解密工具类
 */
public class JasypUtil {
     public static void main(String[] args) {
//        /*加密方式为: PBEWithMD5AndDES*/
//        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
//        //加密所需的salt(盐)
//        textEncryptor.setPassword("XVo2eH1oYD+Mc5hwZUdp6w==");
//        //要加密的数据(数据库的用户名或密码)
//        String passwordEncrypt = textEncryptor.encrypt("admin_123");
//        //解密
//        String passwordDencrypt = textEncryptor.decrypt(passwordEncrypt);
//        System.out.println("解密后的password明文" + passwordDencrypt);
//        System.out.println("password密文:" + passwordEncrypt);

        // 加密方式:PBEWithMD5AndDES
        String factor = encryptWithMD5("123456", "123456");
        System.out.println("加密后的密钥: "+factor);
        String password = encryptWithMD5("admin_123","1x6rd4Yu7IVQ1/O64gggqw==");
        System.out.println("加密后的密码: "+password);
 
        //加密方式:PBEWITHHMACSHA512ANDAES_256
//        String factor = encryptWithSHA512("123456", "123456");
//        System.out.println("加密后的密钥: "+factor);
//        String password = encryptWithSHA512("admin_123","cSkz30kwdEEKkThXtpxGhGse9HeNtofAzvzAFgVK58cXPOVeOX7Dm2tZ9IqRTyFL");
//        System.out.println("加密后的密码: "+password);
    }
    private static final String PBEWITHMD5ANDDES = "PBEWithMD5AndDES";
    private static final String PBEWITHHMACSHA512ANDAES_256 = "PBEWITHHMACSHA512ANDAES_256";
    /**
     * @param plainText 待加密的原文
     * @param factor    加密秘钥
     * @return java.lang.String
     * @Description: Jasyp加密(PBEWithMD5AndDES)
     * @Version: 1.0.0
     */
public static String encryptWithMD5(String plainText, String factor) {
         // 1. 创建加解密工具实例
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        // 2. 加解密配置
        EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig();
        config.setAlgorithm(PBEWITHMD5ANDDES);
        config.setPassword(factor);
        encryptor.setConfig(config);
        // 3. 加密
        return encryptor.encrypt(plainText);
    }
    
/**
     * @param encryptedText 待解密密文
     * @param factor        解密秘钥
     * @return java.lang.String
     * @Description: Jaspy解密(PBEWithMD5AndDES)
     * @Version: 1.0.0
     */
     public static String decryptWithMD5(String encryptedText, String factor) {
        // 1. 创建加解密工具实例
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        // 2. 加解密配置
         EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig();
        config.setAlgorithm(PBEWITHMD5ANDDES);
        config.setPassword(factor);
        encryptor.setConfig(config);
        // 3. 解密
        return encryptor.decrypt(encryptedText);
    }

/**
     * @param plainText 待加密的原文
     * @param factor    加密秘钥
     * @return java.lang.String
     * @Description: Jasyp 加密(PBEWITHHMACSHA512ANDAES_256)
     * @Version: 2.1.1;2.1.1;3.0.3
     */
     public static String encryptWithSHA512(String plainText, String factor) {
        // 1. 创建加解密工具实例
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
       // 2. 加解密配置
       SimpleStringPBEConfig config = new SimpleStringPBEConfig();
       config.setPassword(factor);
        config.setAlgorithm(PBEWITHHMACSHA512ANDAES_256);
        // 为减少配置文件的书写,以下都是 Jasyp 3.x 版本,配置文件默认配置
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
		config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        // 3. 加密
        return encryptor.encrypt(plainText);
    }

/**
     * @param encryptedText 待解密密文
     * @param factor        解密秘钥
     * @return java.lang.String
     * @Description: Jaspy解密(PBEWITHHMACSHA512ANDAES_256)
     * @Version: 2.1.1;2.1.1;3.0.3
     */
     public static String decryptWithSHA512(String encryptedText, String factor) {
        // 1. 创建加解密工具实例
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        // 2. 加解密配置
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(factor);
        config.setAlgorithm(PBEWITHHMACSHA512ANDAES_256);
        // 为减少配置文件的书写,以下都是 Jasyp 3.x 版本,配置文件默认配置
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        // 3. 解密
        return encryptor.decrypt(encryptedText);
    }

}
配置文件
datasource:
    url: jdbc:postgresql://xxip:host/publicdb?allowMultiQueries=true
    username: postgres
    password: PASSWORD(jzABo0VIZC0/vnIHIO4yuzeqiiNSlOMm)     
    driver-class-name: org.postgresql.Driver
jasypt:
  encryptor:
    #密钥
    password: 1x6rd4Yu7IVQ1/O64gggqw==    
    algorithm: PBEWITHHMACSHA512ANDAES_256
    #指定前缀、后缀
    property:
      prefix: 'PASSWORD('
      suffix: ')'

配置文件加入秘钥配置项jasypt.encryptor.password,并将需要脱敏的value值替换成预先经过加密的内容

PASSWORD(jzABo0VIZC0/vnIHIO4yuzeqiiNSlOMm)

关于密钥放置位置:

1.配置文件中(若使用明文配置,仍有安全问题,可加密后放置于配置文件中)

jasypt:
  encryptor:
    password: 1x6rd4Yu7IVQ1/O64gggqw== 

2.启动类里:不易更改密钥

public class SpringBootRun {
    public static void main(String[] args) {
     /*配置加解密密钥,与配置文件的密文分开方*/
     System.setProperty("jasypt.encryptor.password","1x6rd4Yu7IVQ1/O64gggqw==");
     SpringApplication.run(SpringBootRun.class, args);
    }

3.放置于启动配置中

-Djasypt.encryptor.password=1x6rd4Yu7IVQ1/O64gggqw==

日志文件脱敏

Java程序中实现日志文件脱敏的方法有很多种,以下是其中一些常用的方法:

  1. 使用log4j等日志框架:许多Java应用程序使用日志框架(如log4j)来记录日志。这些框架通常提供了自定义布局和过滤器等功能,可以轻松地实现日志脱敏。例如,在log4j中,您可以编写自定义布局或过滤器来删除或替换敏感信息,然后将其添加到日志配置文件中。
  2. 在代码中手动替换敏感信息:您可以在Java代码中手动替换敏感信息,而不是将其打印到日志文件。例如,如果需要脱敏的信息是电话号码,则可以使用正则表达式或其他替换方法将其替换为星号或其他字符。然后,您可以将替换后的字符串记录到日志文件中。
  3. 使用AOP或拦截器:另一种实现日志文件脱敏的方法是使用AOP(面向切面编程)或拦截器。在这种方法中,您可以创建一个切面或拦截器,并将其应用于所有需要记录的方法上。然后,在切面或拦截器中,您可以检查参数、返回值和异常,并删除或替换敏感信息。

无论您选择哪种方法,都需要仔细考虑哪些信息应该脱敏以及如何脱敏。请注意,脱敏不一定是完全安全的,因此需要在记录日志时采取其他安全措施,例如加密和访问控制等。

log4j实现脱敏

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * log4j2 脱敏插件
 * 继承AbstractStringLayout
 **/
@Plugin(name = "CustomPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class CustomPatternLayout extends AbstractStringLayout {
			public final static Logger logger = LoggerFactory.getLogger(CustomPatternLayout.class);
            private PatternLayout patternLayout;
			 protected CustomPatternLayout(Charset charset, String pattern) {
           			super(charset);
       			    patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
                    initRule();
            }
		
			/**
 		    * 要匹配的正则表达式map
    		*/
    		private static Map<String, Pattern> REG_PATTERN_MAP = new HashMap<>();
   		   private static Map<String, String> KEY_REG_MAP = new HashMap<>();
			
			private void initRule() {
        		try {
            		if (MapUtils.isEmpty(Log4j2Rule.regularMap)) {
               			 return;
           			 }
					Log4j2Rule.regularMap.forEach((a, b) -> {
               			 if (StringUtils.isNotBlank(a)) {
                  	    	   Map<String, String> collect = Arrays.stream(a.split(",")).collect(Collectors.toMap(c -> c, w -> b, (key1, key2) -> key1));
                  	     		KEY_REG_MAP.putAll(collect);
               			 }
						 Pattern compile = Pattern.compile(b);
                         REG_PATTERN_MAP.put(b, compile);
                  });
             } catch (Exception e) {
            logger.info(">>>>>> 初始化日志脱敏规则失败 ERROR:", e);
        }

    }
  
	/**
     * 处理日志信息,进行脱敏
     * 1.判断配置文件中是否已经配置需要脱敏字段
     * 2.判断内容是否有需要脱敏的敏感信息
     * 2.1 没有需要脱敏信息直接返回
     * 2.2 处理: 身份证 ,姓名,手机号敏感信息
     */
	public String hideMarkLog(String logStr) {
        try {
			//1.判断配置文件中是否已经配置需要脱敏字段
            if (StringUtils.isBlank(logStr) || MapUtils.isEmpty(KEY_REG_MAP) || MapUtils.isEmpty(REG_PATTERN_MAP)) {
                return logStr;
            }
            //2.判断内容是否有需要脱敏的敏感信息
            Set<String> charKeys = KEY_REG_MAP.keySet();
            for (String key : charKeys) {
                if (logStr.contains(key)) {
                    String regExp = KEY_REG_MAP.get(key);
                    logStr = matchingAndEncrypt(logStr, regExp, key);
                }
            }
             return logStr;
        } catch (Exception e) {
            logger.info(">>>>>>>>> 脱敏处理异常 ERROR: ", e);
            //如果抛出异常为了不影响流程,直接返回原信息
             return logStr;
        }
    }


   /**
     * 正则匹配对应的对象
     * @param msg
     * @param regExp
     * @return
     */
	private static String matchingAndEncrypt(String msg, String regExp, String key) {
        Pattern pattern = REG_PATTERN_MAP.get(regExp);
		if (pattern == null) {
            logger.info(">>> logger 没有匹配到对应的正则表达式 ");
            return msg;
        }
		Matcher matcher = pattern.matcher(msg);
        int length = key.length() + 5;
        boolean contains = Log4j2Rule.USER_NAME_STR.contains(key);
        String hiddenStr = "";
		while (matcher.find()) {
            String originStr = matcher.group();
            if (contains) {
				// 计算关键词和需要脱敏词的距离小于5。
                int i = msg.indexOf(originStr);
                if (i < 0) {
                    continue;
                }
                int span = i - length;
                int startIndex = span >= 0 ? span : 0;
                String substring = msg.substring(startIndex, i);
                if (StringUtils.isBlank(substring) ||  !substring.contains(key)) {
                    continue;
                }
                hiddenStr = hideMarkStr(originStr);
                msg = msg.replace(originStr, hiddenStr);
         } else {
                hiddenStr = hideMarkStr(originStr);
                msg = msg.replace(originStr, hiddenStr);
            }
            }
        return msg;
    }

    /**
     * 标记敏感文字规则
     * @param needHideMark
     * @return
     */
	private static String hideMarkStr(String needHideMark) {
        if (StringUtils.isBlank(needHideMark)) {
            return "";
        }
        int startSize = 0, endSize = 0, mark = 0, length = needHideMark.length();

        StringBuffer hideRegBuffer = new StringBuffer("(\\S{");
        StringBuffer replaceSb = new StringBuffer("$1");
        if (length > 4) {
            int i = length / 3;
            startSize = i;
            endSize = i;
        } else {
            startSize = 1;
            endSize = 0;
        }

		mark = length - startSize - endSize;
        for (int i = 0; i < mark; i++) {
            replaceSb.append("*");
        }
        hideRegBuffer.append(startSize).append("})\\S*(\\S{").append(endSize).append("})");
        replaceSb.append("$2");
        needHideMark = needHideMark.replaceAll(hideRegBuffer.toString(), replaceSb.toString());
        return needHideMark;
    }


    /**
     * 创建插件
     */
	@PluginFactory
    public static Layout createLayout(@PluginAttribute(value = "pattern") final String pattern,@PluginAttribute(value = "charset") final Charset charset) {
		return new CustomPatternLayout(charset, pattern);
    }

  @Override
    public String toSerializable(LogEvent event) {
        return hideMarkLog(patternLayout.toSerializable(event));
    }

}

import java.util.HashMap;
import java.util.Map;

/**
 * 现在拦截加密的日志有三类:
 * 1,身份证
 * 2,姓名
 * 3,身份证号
 * 加密的规则后续可以优化在配置文件中
 **/
public class Log4j2Rule {

    /**
     * 正则匹配 关键词 类别
     */
	public static Map<String, String> regularMap = new HashMap<>();

	/**
     * TODO  可配置
     * 此项可以后期放在配置项中
     */
	public static final String USER_NAME_STR = "Name,name,联系人,姓名";
    public static final String USER_IDCARD_STR = "empCard,idCard,身份证,证件号";
	public static final String USER_PHONE_STR = "mobile,Phone,phone,电话,手机";
    public static final String USER_address_STR = "address,地址";

	/**
     * 正则匹配,自己根据业务要求自定义
     */
     private static String IDCARD_REGEXP = "(\\d{17}[0-9Xx]|\\d{14}[0-9Xx])";
    private static String USERNAME_REGEXP = "[\\u4e00-\\u9fa5]{2,4}";
    private static String PHONE_REGEXP = "(?<!\\d)(?:(?:1[3456789]\\d{9})|(?:861[356789]\\d{9}))(?!\\d)";

	private static String ADDRESS_REGEXP = "(\\S{3})\\S{2}(\\S*)\\S{2}";

    static {
		regularMap.put(USER_NAME_STR, USERNAME_REGEXP);
        regularMap.put(USER_IDCARD_STR, IDCARD_REGEXP);
        regularMap.put(USER_PHONE_STR, PHONE_REGEXP);
        regularMap.put(USER_address_STR,ADDRESS_REGEXP);
    }

}

自定义脱敏策略

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义jackson注解,标注在属性上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
    //脱敏策略
    SensitiveStrategy strategy();
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;

/**
 * 序列化注解自定义实现
 * JsonSerializer<String>:指定String 类型,serialize()方法用于将修改后的数据载入
 */
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
		private SensitiveStrategy strategy;
		 @Override
       public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
			gen.writeString(strategy.desensitizer().apply(value));
    }

	/**
     * 获取属性上的注解属性
     */
	@Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
		Sensitive annotation = property.getAnnotation(Sensitive.class);
		if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
			this.strategy = annotation.strategy();
            return this;
        }
		return prov.findValueSerializer(property.getType(), property);

    }
}
import java.util.function.Function;

/**
 * 脱敏策略,枚举类,针对不同的数据定制特定的策略
 */
public enum SensitiveStrategy {
    /**
     * 用户名
     */
	USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
    /**
     * 身份证
     */
	ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
    /**
     * 手机号
     */
	PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
    /**
     * 地址
     */
     ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));

	private final Function<String, String> desensitizer;

    SensitiveStrategy(Function<String, String> desensitizer) {
        this.desensitizer = desensitizer;
    }

	public Function<String, String> desensitizer() {
        return desensitizer;
    }
}
import lombok.Data;

@Data
public class Person {

	/**
     * 真实姓名
     */
    @Sensitive(strategy = SensitiveStrategy.USERNAME)
    private String realName;

	/**
     * 地址
     */
//    @Sensitive(strategy = SensitiveStrategy.ADDRESS)
    private String address;

	/**
     * 电话号码
     */
//    @Sensitive(strategy = SensitiveStrategy.PHONE)
    private String phoneNumber;

	/**
     * 身份证号码
     */
//    @Sensitive(strategy = SensitiveStrategy.ID_CARD)
    private String idCard;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

配置文件及日志文件脱敏 的相关文章

随机推荐

  • PAT乙级1041 考试座位号 (15 分)

    1041 考试座位号 15 分 一 问题描述 每个 PAT 考生在参加考试时都会被分配两个座位号 一个是试机座位 一个是考试座位 正常情况下 考生在入场时先得到试机座位号码 入座进入试机状态后 系统会显示该考生的考试座位号码 考试时考生需要
  • 机器学习:Jupyter Notebook中numpy的使用

    一 Jupyter Notebook的魔法命令 模块 方法 或者help 模块 方法 查看模块 方法的解释文档 1 run 机械学习中主要应用两个魔法命令 run timeit 魔法命令格式 命令 run 将模块引用并在Jupyter No
  • Java设计模式——备忘录模式

    文章目录 备忘录模式 备忘录模式 主要目的是保存一个对象的某个状态 以便在适当的时候恢复对象 个人觉得叫备份模式更形象些 通俗的讲下 假设有原始类A A中有各种属性 A可以决定需要备份的属性 备忘录类B是用来存储A的一些内部状态 类C呢 就
  • PyQt5 pyqtSignal: 自定义信号传入的参数方法

    PyQt5 pyqtSignal 自定义信号传入的参数方法 在PyQt5当中 用户是可以自定义信号与槽函数的 这里想讲的是如何在pyqtSignal中传入任何的参数 一般来说 我们会在pyqtSignal 中传入不同的参数 以便完成不同类之
  • 我去公司面试,人事经理面试没有过,我却把责任链模式学会了

    设计模式在开发当中是运用特别多的 设计模式就是参照我们日常生活特性 抽象出特性 从而某种实现达到具体要求 当然这当中一定是灵活转变 责任链正式拉开序幕 我去某某互联网公司去面试 好的方向的流程大致应该是 你上招聘软件投递简历 简历筛选通过
  • 查看电脑是否开启虚拟化

    第一步 win R快捷键输入cmd 第二步 输入systeminfo命令 即可查看电脑配置信息
  • 5.1 数组

    C 为基本的数据类型 整数 浮点数 字符型和布尔型 提供了内置的支持 就像在上一章我们为复数类定义了重载的运算符那样 内置的支持也称为协助函数 helper function 支持这些数据类型完成各种允许的运算 也就是说基本数据类型也可以说
  • 【华为OD机试真题2023B卷 JAVA&JS】计算最接近的数

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 计算最接近的数 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 给定一个数组X和正整数K 请找出使表达式X i X i 1 X i K 1 结果最接近于数组中位数的下标
  • Modbus协议详解2:通信方式、地址规则、主从机通信状态

    首先我们要清楚 Modbus是一种串行链路上的主从协议 在通信线路上只能有一个主机存在 不会有多主机存在的情况 虽然主机只有一个 但是从机是可以有多个的 Modbus的通信过程都是由主机发起的 从机在接收到主机的请求后再进行响应 从机不会主
  • 30个 BeageBone 嵌入式项目

    特点 展示了如何使用 BeagleBone Black 编程和构建有趣且引人入胜的项目 学习如何将 BeagleBone Black 连接到您的计算机并对其进行编程 快速掌握 BoneScript 和其他编程工具 30 个 BeagleBo
  • 2023网站seo过时了吗?

    不完全是 虽然SEO过去的一些策略可能已经不再有效 但SEO本身并没有过时 实际上 随着搜索引擎算法的不断发展和用户对搜索结果质量的不断提高 合法 道德以及有效的SEO策略依然能够帮助网站获得搜索引擎排名并吸引大量有针对性的流量 尽管SEO
  • 接收I/O请求完成通知

    那片土在蓝天上 燃烧的翅膀 Windows via C C 学习笔记 设备I O之 接收I O请求完成通知 上一篇 讨论了如何发送I O请求 在异步的设备I O请求方式下 要考虑的问题就是当I O请求完成之后 驱动程序如何通知你的应用程序
  • Docker入门——简单记录

    Docker Docker是一个虚拟环境 某种意义上说它就是一个带有终端命令行的虚拟机 狭义上解释 Docker是一个容器 在这个容器里 Docker中的镜像不再是静态不可操作的 可以对安装的系统镜像进行软件下载 比如gcc g 之类 只不
  • 【自然语言处理

    Transformer由论文 Attention is All You Need 提出 论文地址为 https arxiv org pdf 1706 03762 pdf 文章目录 一 Transformer 整体结构 二 Transform
  • win32原生API实现OpenGL例子(无glew,glut等第三方依赖库)

    正文 话不多说 直接上代码 include
  • 【java面试常见4】

    文章目录 1 面向对象的特征有哪些方面 2 作用域public private protected 以及不写时的区别 3 String 是最基本的数据类型吗 4 float 型float f 3 4是否正确 5 short s1 1 s1
  • Git操作与仓库创建

    Git简介 首先了解一个概念 版本控制 简单来说就是如果你做文案工作 每次提交之后 你的领导会让你修改 一篇稿子可能修改十几次 但是最后定稿的很可能不是最新修改的那一稿 所以就需要有个版本控制的方法 可以回溯到你所修改的任何一版 并且可以拿
  • ServerletContext

    ServerletContext ServerletContext是一种各serverlet项目间互相传输数据的存储器 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img s29palnw 1613542859309
  • c++基础 STL 第三篇:(deque容器和stack容器)

    文章目录 一 deque 的基本概念 二 deque 的构造函数 三 deque 的赋值操作 四 deque 的大小操作 五 deque 的插入和删除 六 deque 的数据获取 七 stack 的基本概念 八 stack 的常用接口 一
  • 配置文件及日志文件脱敏

    配置文件脱敏 使用原因 在项目中 经常需要在配置文件里配置一些敏感信息 比如数据库用户名和密码 redis mq的连接信息等 如果直接写明文 很容易造成密码泄露等安全问题 jasypt简介 Jasypt是一个Java库 它允许开发者以最小的