Log4j2 日志脱敏

2023-11-08

     日志脱敏首先要搞清楚,影响的数据范围,是要全局支持日志脱敏,还是只针对部分代码。

如果涉及到敏感数据的业务代码较少,建议写个数据脱敏工具类,在打印日志的时候调用,灵活可靠,影响范围小。

一、第一种方案:全局方式

          针对log4j2的日志脱敏实现方案,重写rewrite方法,对敏感数据进行脱敏操作
 需要在log4j2.xml 引入此插件,插件名称为DataMaskingRewritePolicy。

1、重写rewrite方法

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 
 *  针对log4j2的日志脱敏实现方案,重写rewrite方法,调整日志内容
 * 
 * 需要在log4j2.xml 引入此插件,插件名称为DataMaskingRewritePolicy
 *
 */
@Plugin(name = "DataMaskingRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
public class DataMaskingRewritePolicy implements RewritePolicy {

	/*
	 * 脱敏符号
	 */
	private static final String ASTERISK = "****";

	/*
	 * 引号
	 */
	private static final String QUOTATION_MARK = "\"";

	// 使用静态内部类创建对象,节省空间
	private static class StaticDataMaskingRewritePolicy {
		private static final DataMaskingRewritePolicy dataMaskingRewritePolicy = new DataMaskingRewritePolicy();
	}

	// 需要加密的字段配置数组
	private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth",
			"cvv" };
	// 将数组转换为集合,方便查找
	private static final List<String> encryptionKeys = new ArrayList<>();

	public DataMaskingRewritePolicy() {
		if (CollectionUtils.isEmpty(encryptionKeys)) {
			encryptionKeys.addAll(Arrays.asList(encryptionKeyArrays));
		}
	}

	/**
	 * 日志修改方法,可以对日志进行过滤,修改
	 *
	 * @param logEvent
	 * @return
	 */
	@Override
	public LogEvent rewrite(LogEvent logEvent) {
		if (!(logEvent instanceof Log4jLogEvent)) {
			return logEvent;
		}

		Log4jLogEvent log4jLogEvent = (Log4jLogEvent) logEvent;

		Message message = log4jLogEvent.getMessage();
		if ((message instanceof ParameterizedMessage)) {
			ParameterizedMessage parameterizedMessage = (ParameterizedMessage) message;

			Object[] params = parameterizedMessage.getParameters();
			if (params == null || params.length <= 0) {
				return logEvent;
			}
			Object[] newParams = new Object[params.length];
			for (int i = 0; i < params.length; i++) {
				try {
					if(params[i] instanceof JSONObject){
						JSONObject jsonObject = (JSONObject) params[i];
						// 处理json格式的日志
						newParams[i] = encryptionJson(jsonObject, encryptionKeys);
					} else{
						newParams[i] = params[i];
					}
			
				} catch (Exception e) {
					newParams[i] = params[i];
				}
			}

			ParameterizedMessage m = new ParameterizedMessage(parameterizedMessage.getFormat(), newParams,
					parameterizedMessage.getThrowable());
			Log4jLogEvent.Builder builder = log4jLogEvent.asBuilder().setMessage(m);
			return builder.build();
		} else if (message instanceof SimpleMessage) {
			
			SimpleMessage newMessage = decryptionSimpleMessage((SimpleMessage) message);
			Log4jLogEvent.Builder builder = log4jLogEvent.asBuilder().setMessage(newMessage);
			return builder.build();
		} else {

			return logEvent;
		}

	}

	/**
	 * 单例模式创建(静态内部类模式)
	 *
	 * @return
	 */
	@PluginFactory
	public static DataMaskingRewritePolicy createPolicy() {
		return StaticDataMaskingRewritePolicy.dataMaskingRewritePolicy;
	}

	private SimpleMessage decryptionSimpleMessage(SimpleMessage simpleMessage) {
		String messsage = simpleMessage.toString();
		String newMessage = messsage;

		if (!StringUtils.isEmpty(messsage)) {

			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(messsage, key));

			// 包含敏感词
			if (isContain) {

				for (String key : encryptionKeyArrays) {
					int keyLength = key.length();
					// 敏感词
					String targetStr = new String("<" + key + ">");
					StringBuffer targetSb = new StringBuffer(targetStr);
					int startIndex = newMessage.indexOf(targetStr);

					/*
					 * 如<password>123456</password>替换为 <password>****</password>
					 */
					if (startIndex > -1 && newMessage.indexOf(targetSb.append(ASTERISK).append("</").append(key).append(">").toString()) == -1) {
						int endIndex = newMessage.indexOf(targetStr);
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex + keyLength + 2) + ASTERISK
									+ newMessage.substring(endIndex, newMessage.length());
						}
					}

					/*
					 * 如password:123456替换为password:****
					 */
					 targetStr = key + ":";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(targetStr + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如password=123456替换为password=****
					 */
					if (newMessage.indexOf(key + "=") > -1 && newMessage.indexOf(key + "=" + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(key + "=") + keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如"password":"123456" 替换为"password":"****"
					 */
					String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
					if (newMessage.indexOf(qmKey) > -1 && newMessage.indexOf(qmKey + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(qmKey) + keyLength + 3;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK + endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK;
						}
					}

				}
				return new SimpleMessage(newMessage);
			}

		}

		return simpleMessage;

	}

	/**
	 * 处理日志,递归获取值
	 *
	 */
	private Object encryptionJson(Object object, List<String> encryptionKeys) {
		String jsonString = JSON.toJSONString(object);
		if (object instanceof JSONObject) {
			JSONObject json = JSON.parseObject(jsonString);
			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(jsonString, key));
			if (isContain) {
				// 判断当前字符串中有没有key值
				Set<String> keys = json.keySet();
				keys.forEach(key -> {
					boolean result = encryptionKeys.stream().anyMatch(ekey -> Objects.equal(ekey, key));
					if (result) {
						String value = json.getString(key);
						// 加密
						json.put(key, ASTERISK);
					} else {
						json.put(key, encryptionJson(json.get(key), encryptionKeys));
					}
				});
			}
			return json;
		} else if (object instanceof JSONArray) {
			JSONArray jsonArray = JSON.parseArray(jsonString);
			for (int i = 0; i < jsonArray.size(); i++) {
				JSONObject jsonObject = jsonArray.getJSONObject(i);
				// 转换
				jsonArray.set(i, encryptionJson(jsonObject, encryptionKeys));
			}
			return jsonArray;
		}
		return object;
	}
}

2、修改log4j2.xml配置,增加Rewrite重新日志配置

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <properties>
        <!--设置日志文件存储路径为tomcat/logs/${APP_NAME}-->
        <Property name="LOG_FILE_PATH">${sys:catalina.home}/logs/${APP_NAME}</Property>
        <!--设置日志输出格式-->
        <Property name="PATTERN_FORMAT">[%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n</Property>
        <Property name="LOG_LEVEL">info</Property>
    </properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
        
        	 <PatternLayout pattern="[Shop][%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n"/>
        </Console>
        
        <!-- 配置重写日志 -->
        <Rewrite name="rewrite">
            <DataMaskingRewritePolicy/>
            <AppenderRef ref="Console"/>
            <!-- 将catalina日志重写 -->
            <!-- <AppenderRef ref="Catalina"/> -->
        </Rewrite>
 
    </Appenders>

    <Loggers>
    	<logger name="org.springframework" level="${LOG_LEVEL}">
    		<AppenderRef ref="Console" />
    	</logger>
		<logger name="org.test" level="${LOG_LEVEL}">
    		<AppenderRef ref="rewrite" />
    	</logger>
    
        <Root level="info">
            <!-- <AppenderRef ref="Console" /> -->
            <!-- <AppenderRef ref="RollingInfoFile" /> -->
        </Root>
    </Loggers>
    
</configuration>

3、单元测试

	@org.junit.Test
	public void testLog4j2JSONString(){
		
		 Runtime r = Runtime.getRuntime();
	        r.gc();//计算内存前先垃圾回收一次
	        long start = System.currentTimeMillis();//开始Time
	        long startMem = r.totalMemory(); // 开始Memory

		for (int i = 0; i < 1; i++) {
			logger.info("\"password\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\"");
		}
		
		//输出
        long endMem =r.freeMemory(); // 末尾Memory
        long end = System.currentTimeMillis();//末尾Time
        System.out.println("用时消耗: "+String.valueOf(end - start)+"ms");
        System.out.println("内存消耗: "+String.valueOf((startMem- endMem)/1024)+"KB");
	}

二、第二种方案:自定义脱敏工具类



import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

/**
 * String数据脱敏处理

 *
 */
public class StringMaskUtil {

	/*
	 * 脱敏符号
	 */
	private static final String ASTERISK = "****";

	
	// 需要脱敏的字段配置数组
	private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth","cvv","number"};

	/*
	 * 引号
	 */
	private static final String QUOTATION_MARK = "\"";


	// 将数组转换为集合,方便查找
	private static final List<String> encryptionKeys = new ArrayList<>();
	
	// 使用静态内部类创建对象,节省空间
	private static class  StaticStringMaskUtil {
		private static final StringMaskUtil stringMaskUtil = new StringMaskUtil();
	}
	
	public StringMaskUtil() {
		if (CollectionUtils.isEmpty(encryptionKeys)) {
			encryptionKeys.addAll(Arrays.asList(encryptionKeyArrays));
		}
	}
	
	public static StringMaskUtil instance(){
		return StaticStringMaskUtil.stringMaskUtil;
	}
	
	/**
	 * 支持的格式
	 * 
	 * 1、<key>value</key>
	 * 2、key=value,key1=value1
	 * 3、key:value,key1:value1
	 * 4、"key":"value","key1":"value1"
	 * 
	 * @param messsage
	 * @return
	 */
	public String mask(String messsage){
		
		String newMessage = messsage;
		if (!StringUtils.isEmpty(messsage)) {

			boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(messsage, key));

			// 包含敏感词
			if (isContain) {

				for (String key : encryptionKeys) {
					int keyLength = key.length();
					// 敏感词
					String targetStr = new String("<" + key + ">");
					StringBuffer targetSb = new StringBuffer(targetStr);
					int startIndex = newMessage.indexOf(targetStr);

					/*
					 * 如<password>123456</password>替换为 <password>****</password>
					 */
					if (startIndex > -1 && newMessage.indexOf(targetSb.append(ASTERISK).append("</").append(key).append(">").toString()) == -1) {
						int endIndex = newMessage.indexOf("</" + key + ">");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex + keyLength + 2) + ASTERISK
									+ newMessage.substring(endIndex, newMessage.length());
						}
					}

					/*
					 * 如,password:123456替换为password:****
					 */
					 targetStr =","+ key + ":";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(targetStr + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 2;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}else if(newMessage.startsWith(key + ":")){
						startIndex = keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如,password=123456替换为password=****
					 */
					 targetStr =","+ key + "=";
					if (newMessage.indexOf(targetStr) > -1 && newMessage.indexOf(key + "=" + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(targetStr) + keyLength + 2;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}else if(newMessage.startsWith(key + "=")){
						startIndex = keyLength + 1;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK
									+ endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + ASTERISK;
						}
					}

					/*
					 * 如"password":"123456" 替换为"password":"****"
					 */
					String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
					if (newMessage.indexOf(qmKey) > -1 && newMessage.indexOf(qmKey + ASTERISK) == -1) {

						startIndex = newMessage.indexOf(qmKey) + keyLength + 3;
						String endMessage = newMessage.substring(startIndex, newMessage.length());
						int endIndex = endMessage.indexOf(",");
						if (endIndex > -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK + endMessage.substring(endIndex, endMessage.length());
						} else if (endMessage.indexOf(",") == -1 && endMessage.indexOf("=") == -1) {
							newMessage = newMessage.substring(0, startIndex) + QUOTATION_MARK + ASTERISK
									+ QUOTATION_MARK;
						}
					}

				}
			}

		}
		return newMessage;
	}
	
	
}

自定义脱敏工具类单元测试

	@org.junit.Test
	public void testLog4jString(){
		
		Runtime r = Runtime.getRuntime();
        r.gc();//计算内存前先垃圾回收一次
        long start = System.currentTimeMillis();//开始Time
        long startMem = r.totalMemory(); // 开始Memory
		
		for (int i = 0; i < 1; i++) {
			System.out.println(StringMaskUtil.instance().mask("oldpassword=123456,expireYear=2022,expireMonth=12,cvv=844"));
			System.out.println(StringMaskUtil.instance().mask("password:123456,aaexpireYear:2022,expireMonth:12,cvv:844"));
			System.out.println(StringMaskUtil.instance().mask("password=123456,expireYear=2022,expireMonth=12,cvv=844"));
			System.out.println(StringMaskUtil.instance().mask("\"oldpassword\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\""));
			System.out.println(StringMaskUtil.instance().mask("<password>123456</password><expireYear>123456</expireYear><expireMonth>123456</expireMonth><cvv>123456</cvv>"));
		}
		//输出
        long endMem =r.freeMemory(); // 末尾Memory
        long end = System.currentTimeMillis();//末尾Time
        System.out.println("用时消耗: "+String.valueOf(end - start)+"ms");
        System.out.println("内存消耗: "+String.valueOf((startMem- endMem)/1024)+"KB");
	}

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

Log4j2 日志脱敏 的相关文章

  • SAML 服务提供商 Spring Security

    当使用预先配置的服务提供者元数据时 在 Spring Security 中 是否应该有 2 个用于扩展元数据委托的 bean 定义 一份用于 IDP 元数据 一份用于 SP 元数据
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • Android在排序列表时忽略大小写

    我有一个名为路径的列表 我目前正在使用以下代码对字符串进行排序 java util Collections sort path 这工作正常 它对我的 列表进行排序 但是它以不同的方式处理第一个字母的情况 即它用大写字母对列表进行排序 然后用
  • JavaFX 中具有自定义内容的 ListView

    How i can make custom ListView with JavaFx for my app I need HBox with image and 2 Labels for each line listView 您可以通过查看
  • 如何在不超过最大值的情况下增加变量?

    我正在为学校开发一个简单的视频游戏程序 我创建了一个方法 如果调用该方法 玩家将获得 15 点生命值 我必须将生命值保持在最大值 100 并且由于我目前的编程能力有限 我正在做这样的事情 public void getHealed if h
  • 我可以使用子接口重新编译公共 API 并保持二进制兼容性吗?

    我有一个公共 API 在多个项目中多次使用 public interface Process
  • 画透明圆,外面填充

    我有一个地图视图 我想在其上画一个圆圈以聚焦于给定区域 但我希望圆圈倒转 也就是说 圆的内部不是被填充 而是透明的 其他所有部分都被填充 请参阅这张图片了解我的意思 http i imgur com zxIMZ png 上半部分显示了我可以
  • 如何使用 Maven 打包并运行具有依赖项的简单命令行应用程序?

    我对 java 和 Maven 都是全新的 所以这可能非常简单 如果我遵循maven2hello world此处的说明 http maven apache org guides getting started maven in Five m
  • Java 中的“Lambdifying”scala 函数

    使用Java和Apache Spark 已用Scala重写 面对旧的API方法 org apache spark rdd JdbcRDD构造函数 其参数为 AbstractFunction1 abstract class AbstractF
  • 以编程方式在java的resources/source文件夹中创建文件?

    我有两个资源文件夹 src 这是我的 java 文件 资源 这是我的资源文件 图像 properties 组织在文件夹 包 中 有没有办法以编程方式在该资源文件夹中添加另一个 properties 文件 我尝试过这样的事情 public s
  • IntelliJ - 调试模式 - 在程序内存中搜索文本

    我正在与无证的第三方库合作 我知道有一定的String存储在库深处的某个字段中的某处 我可以预测的动态值 但我想从库的 API 中获取它 有没有一种方法可以通过以下方式进行搜索 类似于全文搜索 full程序内存处于调试模式并在某个断点处停止
  • 如何在JSTL中调​​用java方法? [复制]

    这个问题在这里已经有答案了 这可能是重复的问题 我只想调用不是 getter 或 setter 方法的方法例如 xyz 类的 makeCall someObj stringvalue Java类 Class XYZ public Strin
  • Java中的Object类是什么?

    什么是或什么类型private Object obj Object http download oracle com javase 6 docs api java lang Object html是Java继承层次结构中每个类的最终祖先 从
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • FileOutputStream.close() 中的设备 ioctl 不合适

    我有一些代码可以使用以下命令将一些首选项保存到文件中FileOutputStream 这是我已经写了一千遍的标准代码 FileOutputStream out new FileOutputStream file try BufferedOu
  • Trie 数据结构 - Java [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 是否有任何库或文档 链接提供了在 java 中实现 Trie 数据结构的更多信息 任何帮助都会很棒 Thanks 你可以阅读Java特里树
  • 如何从 Maven 存储库引用本机 DLL?

    如果 JAR 附带 Maven 存储库中的本机 DLL 我需要在 pom xml 中放入什么才能将该 DLL 放入打包中 更具体地举个例子Jacob http search maven org artifactdetails 7Cnet s
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • 如何修复:“无法解析类型 java.lang.CharSequence。它是从所需的 .class 文件间接引用的”消息? [复制]

    这个问题在这里已经有答案了 我正在尝试使用这个字符串 amountStr amountStr replace replace replace 但我收到一条错误消息 我知道我收到的错误消息是因为我刚刚发布的字符串已过时 所以我想知道该字符串的
  • 如何在 JFreeChart 中设置多个系列的线条粗细?

    我创建了很多图表 在他们每个人中我都需要打电话 renderer setSeriesStroke i new BasicStroke 2 0f 对于每个系列 renderer is chart getXYPlot getRenderer 我

随机推荐

  • 对IOC和DI的通俗理解

    学习过spring框架的人一定都会听过Spring的IoC 控制反转 DI 依赖注入 这两个概念 对于初学Spring的人来说 总觉得IoC DI这两个概念是模糊不清的 是很难理解的 今天和大家分享网上的一些技术大牛们对Spring框架的I
  • Flink ML API,为实时机器学习设计的算法接口与迭代引擎

    摘要 本文整理自阿里巴巴高级技术专家林东 阿里巴巴技术专家高赟 云骞 在 Flink Forward Asia 2021 核心技术专场的演讲 主要内容包括 面向实时机器学习的 API 流批一体的迭代引擎 Flink ML 生态建设 一 面向
  • Linux下快速查看CPU使用情况的相关命令

    Linux下快速查看CPU使用情况比较常用的命令是free top ps 这篇文章来看下如何在Linux下检查服务器的CPU使用情况 我的Linux是Linux Ubuntu server 15 04 如果是图形界面 有些统计会看起来更直观
  • Intellij Idea创建一个简单的java项目

    2016年11月12日 我即将要离开象牙塔 校园 踏入社会 想想未来我是某个公司的一个程序员 再对比一下小时的梦想 好像出入挺大的 今天我不得不为即将的工作准备 一个java开发工程师 但是我现在是一个小小的菜鸟 所以要学习 好了 不说这些
  • 简单说说对QT中moveToThread实现多线程操作的理解

    在平时的编码过程中经常碰到QT的多线程问题 也大量接触了QT中的两种主流多线程写法 一种是继承QThread类并重载run函数 在run函数中写一个状态机或者计时器来实现对线程运作 一种是通过moveToThread的方式实现事件托管从而实
  • nginx在Linux上搭建

    一 Nginx介绍和常用功能 1 Nginx介绍 Nginx是一个高性能的HTTP和反向代理 服务器 百度百科的介绍 常见功能 Http代理 反向代理 作为web服务器最常用的功能之一 尤其是反向代理 正向代理和反向代理不理解的可以看htt
  • 轮播图插件使用

    React使用最广泛的轮播图插件之一是 react slick react slick 是一个基于React的响应式轮播图组件 具有许多可定制的选项和功能 要使用 react slick 插件 您可以按照以下步骤进行 1 安装 react
  • 华为数据中心产品汇总介绍

    AR G3路由器产品定位 AR G3系列企业路由器是秉承华为在数据通信 无线 接入网 核心网领域的深厚积累 依托自主知识产权的VRP平台 通用路由平台 主要是以TCP IP协议为核心 实现了数据链路层 网络层和应用层的多种协议 推出的面向企
  • yagmail发送带图片和链接的邮件

    方法 在正文中插入图片 yagmail inlne 图片路径 在正文中插入链接 a href 链接地址 链接名称 a 效果如图 经过测试的完整代码 导入yagmail第三方库 import yagmail yagmail SMTP user
  • 冻结表格列PyQt

    QT有个官方的例子 Frozen Column Example 在Qt Creator例子查找即可 官方例子python版本 Frozen Column Example Qt for Python 不过官方python版应该是机器直接翻译的
  • C++ continue 语句

    C 中的 continue 语句有点像 break 语句 但它不是强迫终止 continue 会跳过当前循环中的代码 强迫开始下一次循环 对于 for 循环 continue 语句会导致执行条件测试和循环增量部分 对于 while 和 do
  • 十进制转换为二进制代码

    十进制转换为二进制代码 十进制转换为二进制 十进制如何转二进制 将该数字不断除以2直到商为零 然后将余数由下至上依次写出 即可得到该数字的二进制表示 以将数字21转化为二进制为例 当商为零时 将余数由下至上依次写出 即为21的二进制表示 i
  • SpringBoot整合框架——数据库

    目录 一 整合JDBC使用 1 1 SpringData简介 1 2 创建测试项目测试数据源 1 3 JDBCTemplate JdbcTemplate主要提供以下几类方法 1 4 测试 二 整合Druid
  • java 有限状态机_有限状态机的4种Java实现对比

    写在前面 2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上 内容详细 图文并茂 有需要学习的朋友可以Star一下 GitHub地址 https github com abel max Java Study Not
  • 【Redis】Redis安装与配置:

    文章目录 一 下载与安装 二 服务启动与停止 1 启动 2 设置后台运行 3 设置密码 解开注释 将默认密码foobared修改为你的 4 设置远程连接 一 下载与安装 redis https redis io download tar z
  • python详细安装教程-Pycharm及python安装详细教程(图解)

    首先我们来安装python 1 首先进入网站下载 点击打开链接 或自己输入网址https www python org downloads 进入之后如下图 选择图中红色圈中区域进行下载 2 下载完成后如下图所示 3 双击exe文件进行安装
  • sqli-labs Less-4

    本系列文章使用的靶场环境为sqli labs 环境下载地址 https github com Audi 1 sqli labs 持续跟新 一直到通过此靶场为止 1 判断注入类型 index php id 1 单引号回显正常 双引号会报错 然
  • Pyhon加载模块的两种方法

    一 在Python中添加 1 找到Settings 2 找到Project Interpreter 3 点击加号 4 在搜索栏搜索想要的模块 二 利用cmd安装 1 打开cmd 2 输入python 查看能否显示版本信息 不能的话需要配置环
  • APP 测试过程中缺陷总结

    1 拍照视频 问题1 视频拍照 文案和图标不一致 操作1 拍摄照片 点击拍摄视频 查看照片大图 确认 操作2 看系统是否存在两模式 定制和非定制 且都拥有这个视频和拍照功能 定制模式下切换到视频时 退出登录 登录到非定制版 2 上传 问题1
  • Log4j2 日志脱敏

    日志脱敏首先要搞清楚 影响的数据范围 是要全局支持日志脱敏 还是只针对部分代码 如果涉及到敏感数据的业务代码较少 建议写个数据脱敏工具类 在打印日志的时候调用 灵活可靠 影响范围小 一 第一种方案 全局方式 针对log4j2的日志脱敏实现方