URL入参出参请求头可配置化

整体思路

通过spring的Spell表达式解析变量的参数值,参数名定义为${XXX},在解析参数值后,将${XXX}替换成#XXX以匹配Spell表达式。

核心实现类

package com.example.spring_boot_study.spring.spell;import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.spring_boot_study.SpringEl.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertyPlaceholderHelper;import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@Slf4j
@Component
public class DynamicUrlDemo {private static final String VAR_PREFIX = "${";private static final String VAR_SUFFIX = "}";private static final String POINT = ".";private static final String EQUAL = "=";private static final String WELL = "#";// 将表达式中含有多个${XXX}的进行分组,拿到所有参数名(如${XXX}+${YYY})// 正则的含义是匹配${开始,非}字符的N个字符,其中以非}字符的N个字符作为分组,即分组为XXX和YYYprivate static final Pattern VAR_PATTERN = Pattern.compile("\\$\\{([^}]*)");private final PropertyPlaceholderHelper placeholderHelper =new PropertyPlaceholderHelper(VAR_PREFIX, VAR_SUFFIX);private final ExpressionParser parser = new SpelExpressionParser(SpringElUtil.CONFIG);// 允许为空的参数@Value("${valueMayBeEmptyParams:a,b,c}")private String valueMayBeEmptyParams;public void handle(RequestDemo requestDemo, JSONObject msgJson) {String url = requestDemo.getUrl();Map<String, Object> delayHeaders = new HashMap<>(2);// 解析URL里包含的变量${XXX}url = placeholderHelper.replacePlaceholders(url, variable -> {VariableParser variableParser = VariableParseSelector.selectParser(variable);if (variableParser == null) {throw new RuntimeException(variable + "没有对应的参数解析器");}Object object = variableParser.parseVariable(variable, msgJson);return String.valueOf(object);});try {// 1、构建请求头String requestHeader = requestDemo.getRequestHeader();Map<String, String> headers = new HashMap<>(4);if (StringUtils.isNotEmpty(requestHeader)) {JSONObject requestHeaderJson = JSON.parseObject(requestHeader);// 移除需要在解析完body后执行解析的头部字段 延后执行// (即requestBody是所有入参的摘要算法结果,有些请求为了安全可能要对所有入参做一个摘要算法)requestHeaderJson.entrySet().removeIf(header -> {if (header.getValue().toString().contains("${requestBody}")) {delayHeaders.put(header.getKey(), header.getValue());return true;}return false;});headers = buildHeaders(requestHeaderJson, msgJson);}String responseStr;String requestConfig = requestDemo.getRequestConfig();Object object = JSON.parse(requestConfig);HttpRequest httpRequest = HttpRequest.of(url).addHeaders(headers).timeout(10000);if (object instanceof JSONObject) {// 2、构建请求参数JSONObject requestJson = (JSONObject) object;Map<String, Object> form = parseParams(requestJson, msgJson);// 写入${requestBody}if (!delayHeaders.isEmpty()) {msgJson.put("requestBody", new JSONArray(Collections.singletonList(form)).toJSONString());httpRequest.addHeaders(buildHeaders(new JSONObject(delayHeaders), msgJson));}// 3、执行请求if (RequestType.GET.getTypeCode() == requestDemo.getRequestType()) {responseStr = httpRequest.method(Method.GET).form(form).execute().body();} else {String header = httpRequest.header("Content-Type");if (header != null && header.contains("application/json")) {responseStr = httpRequest.method(Method.POST).body(JSON.toJSONString(form)).execute().body();} else {responseStr = httpRequest.method(Method.POST).form(form).execute().body();}}} else if (object instanceof JSONArray) {JSONArray jsonArray = (JSONArray) object;List<Map<String, Object>> form = new ArrayList<>(jsonArray.size());jsonArray.forEach(obj -> form.add(parseParams((JSONObject) obj, msgJson)));// 写入${requestBody}if (!delayHeaders.isEmpty()) {httpRequest.addHeaders(buildHeaders(new JSONObject(delayHeaders), msgJson));}// 3、执行请求if (RequestType.GET.getTypeCode() == requestDemo.getRequestType()) {responseStr = httpRequest.method(Method.GET).body(JSON.toJSONString(form)).execute().body();} else {responseStr = httpRequest.method(Method.POST).body(JSON.toJSONString(form)).execute().body();}} else {log.error("无法解析请求参数配置:{}", requestConfig);return;}String responseConfig = requestDemo.getResponseConfig();// 如果需要对调用结果进行处理,如落库等,则另外用一个策略模式对url进行相应处理// 跟参数解析器一样,不过以url为keyif (checkReportResult(responseStr, responseConfig)) {log.info("请求[{}]成功", url);} else {log.info("请求[{}]失败", url);}} catch (Exception e) {log.error("请求[{}]异常", url);}}/*** 解析并构建头部*/private Map<String, String> buildHeaders(JSONObject requestHeaderJson, JSONObject msgJson) {Map<String, String> headers = new HashMap<>(4);Map<String, Object> headerParamMap = parseParams(requestHeaderJson, msgJson);if (MapUtil.isNotEmpty(headerParamMap)) {headerParamMap.forEach((key, value) -> {if (value != null && StringUtils.isNoneBlank(value.toString())) {headers.put(key, value.toString());}});}return headers;}/*** 解析请求参数,把Json配置转换成Map** @param config  入参配置* @param msgJson 消息体* @return 参数*/private Map<String, Object> parseParams(JSONObject config, JSONObject msgJson) {Map<String, Object> paramMap = new HashMap<>(config.size());EvaluationContext ctx;try {ctx = SpringElUtil.createContext();} catch (NoSuchMethodException e) {throw new RuntimeException("创建SpringEL表达式上下文异常。");}config.forEach((paramKey, paramValueExpress) -> {Object key;// 字段名也支持表达式 但暂时不打算支持任意位置的,如果以后支持字段名任意位置的表达式 把条件去掉if (paramKey.charAt(0) == '$') {// 字段名也支持表达式key = parseValue(ctx, paramKey, msgJson);} else {key = paramKey;}if (paramValueExpress instanceof JSONObject) {Map<String, Object> paramValue = parseParams((JSONObject) paramValueExpress, msgJson);paramMap.put(String.valueOf(key), paramValue);} else if (paramValueExpress instanceof JSONArray) {List<Map<String, Object>> paramValueList = new ArrayList<>(((JSONArray) paramValueExpress).size());((JSONArray) paramValueExpress).forEach(subParamValueExpress -> paramValueList.add(parseParams((JSONObject) subParamValueExpress,msgJson)));paramMap.put(String.valueOf(key), paramValueList);} else {try {Object paramValue = parseValue(ctx, paramValueExpress, msgJson);paramMap.put(String.valueOf(key), paramValue);log.info("解析参数[{}]对应的表达式[{}]值为[{}].", key, paramValueExpress, paramValue);} catch (Exception e) {log.info("解析参数[{}]对应的表达式[{}]跳过.", key, paramValueExpress);}}});return paramMap;}/*** 解析参数值的表达式* 逻辑是${XXX}代表参数名,解析完后替换成#XXX以匹配Spring Spell表达式的语法* 之所以这么定义,是为了区分#XXX()的方法,更方便解析参数去获取对应的值** @param msgJson      消息体* @param valueExpress 表达式* @return 参数值*/private Object parseValue(EvaluationContext ctx, Object valueExpress, JSONObject msgJson) {String varExpress = String.valueOf(valueExpress);if (varExpress.contains(VAR_PREFIX) && varExpress.contains(VAR_SUFFIX)) {List<String> varList = this.getVariableNames(varExpress);for (String varName : varList) {if (ctx.lookupVariable(varName) == null) {// 策略模式获取定义参数值VariableParser variableParser = VariableParseSelector.selectParser(varName);if (variableParser == null) {throw new RuntimeException(varName + "找不到对应的变量解析器。");}Object varValue = variableParser.parseVariable(varName, msgJson);if (varName.indexOf(valueMayBeEmptyParams) != -1&& (varValue == null|| StringUtils.isBlank(varValue.toString()))) {throw new RuntimeException(varName + "解析变量值失败。");}ctx.setVariable(varName, varValue);}// 将 ${oppoPhoneIdValue} 替换成 #oppoPhoneIdValue,匹配Spell表达式varExpress = varExpress.replaceAll("\\$\\{" + varName + "}", "\\#" + varName);}}if (varExpress.contains(WELL)) {return parser.parseExpression(varExpress).getValue(ctx);} else {// 直接赋值的,即写死的参数return valueExpress;}}/*** 检查请求是否正确** @param responseStr    响应体* @param responseConfig 响应配置* @return 是否成功*/private boolean checkReportResult(String responseStr, String responseConfig) {try {if (responseConfig.contains(EQUAL)) {String[] configs = StringUtils.split(responseConfig, EQUAL);String resultKey = configs[0];String expectValue = configs[1];String[] keys = StringUtils.split(resultKey, POINT);String resultValue = getResultValue(JSON.parseObject(responseStr), keys, 0);return expectValue.equalsIgnoreCase(resultValue);} else {return responseConfig.equalsIgnoreCase(responseStr);}} catch (Exception e) {log.error("根据出参配置[{}]解析响应结果[{}]产生异常[{}]。", responseConfig, responseStr, e.getMessage(), e);return false;}}/*** 递归获取请求成功的值* 配置是result.data.code=200这样的形式* @param resultJson* @param keys* @param index* @return*/private String getResultValue(JSONObject resultJson, String[] keys, int index) {if (index >= keys.length) {log.error("根据[{}]无法获取返回值", Arrays.toString(keys));return "";}Object value = resultJson.get(keys[index]);if (value == null || StringUtils.isBlank(value.toString())) {return "";}if (value instanceof JSONObject) {resultJson = (JSONObject) value;return getResultValue(resultJson, keys, index + 1);} else {return value.toString();}}/*** 获取表达式中${}中的值** @param content 变量表达式* @return 变量列表*/private List<String> getVariableNames(String content) {Matcher matcher = VAR_PATTERN.matcher(content);List<String> variableList = new ArrayList<>();while (matcher.find()) {variableList.add(matcher.group(1));}return variableList;}
}

参数解析器

package com.example.spring_boot_study.spring.spell;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 变量解析器选择器*/
public class VariableParseSelector {private static final Map<String, VariableParser> PARSER_MAP = new ConcurrentHashMap<>();public static void registerParser(String variableName, VariableParser variableParser) {PARSER_MAP.put(variableName, variableParser);}public static VariableParser selectParser(String variableName) {return PARSER_MAP.get(variableName);}
}
package com.example.spring_boot_study.spring.spell;import com.alibaba.fastjson.JSONObject;import java.util.Map;/*** Description: 变量解析接口*/
public interface VariableParser {/*** 变量配置解析** @param variableName 变量名* @param msgJson      消息体(包含所有参数对应的参数值)* @return 变量*/Object parseVariable(String variableName, JSONObject msgJson);
}
package com.example.spring_boot_study.spring.spell;import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import java.util.Map;/*** Description:* 参数解析器*/
@Component
public class OppoExposeParser implements VariableParser, InitializingBean {/*** 有些参数值是需要调具体的service去获取的,可以在这里实现* 也可以在封装msgJson的时候实现好。这里用到的是参数值封装好在msgJson的形式* @param variableName 变量名* @param msgJson      消息体* @return*/@Overridepublic Object parseVariable(String variableName,  JSONObject msgJson) {switch (variableName) {case "oppoPhoneIdName": {if (true) {return "oppoPhoneIdName";}return "companyName";}case "oppoPhoneIdValue": {return msgJson.get("oppoPhoneIdValue");}case "oppoExposeTimestamp": {return msgJson.get("oppoExposeTimestamp");}case "oppoAdId": {return msgJson.get("oppoAdId");}case "oppoType": {return msgJson.get("oppoType");}case "requestBody": {return msgJson.get("requestBody");}default:return "";}}@Overridepublic void afterPropertiesSet() throws Exception {VariableParseSelector.registerParser("oppoPhoneIdName", this);VariableParseSelector.registerParser("oppoPhoneIdValue", this);VariableParseSelector.registerParser("oppoExposeTimestamp", this);VariableParseSelector.registerParser("oppoAdId", this);VariableParseSelector.registerParser("oppoType", this);VariableParseSelector.registerParser("oppoType", this);}
}

工具类

package com.example.spring_boot_study.spring.spell;import cn.hutool.crypto.digest.MD5;
import com.example.spring_boot_study.SpringEl.AesFunction;
import com.example.spring_boot_study.SpringEl.DateFunction;
import com.example.spring_boot_study.SpringEl.HmacSha1Function;
import com.example.spring_boot_study.SpringEl.MathFunction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;import java.util.HashMap;
import java.util.Map;/*** Description: SpringEl获取静态Method*/
@Slf4j
public class SpringElUtil {/*** IMMEDIATE - 在即时模式下,尽快编译表达式。这通常是在第一次解释评估之后。* 如果编译的表达式失败(通常是由于类型改变,如上所述),则表达式评估的调用者将收到异常。*/public static final SpelParserConfiguration CONFIG = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,SpringElUtil.class.getClassLoader());/*** 对公共method的进行缓存 防止每次都getDeclaredMethod* 所有方法必须都是静态方法* 这里使用自定义函数,简化方法的调用* 只演示ceil方法,aes方法等其他所需方法可自行实现*/private static final Map<String, Object> METHOD_CACHE = new HashMap<String, Object>() {{try {put("ceil", Math.class.getDeclaredMethod("ceil", double.class));
//            put("hmac", HmacSha1Function.class.getDeclaredMethod("encrypt", String.class, String.class));put("aes", AesFunction.class.getDeclaredMethod("strEncodeBase64", String.class, String.class));
//            put("currentTimeMillis", DateFunction.class.getDeclaredMethod("currentTimeMillis"));
//            put("currentTimeSeconds", DateFunction.class.getDeclaredMethod("currentTimeSeconds"));
//            put("currentStringDate", DateFunction.class.getDeclaredMethod("currentStringDate"));} catch (NoSuchMethodException e) {log.error("SpringEl获取方法失败", e);}}};public static EvaluationContext createContext() throws NoSuchMethodException {//这里因为安全 设置了只读模式,如需其他模式可以自行更改//  StandardEvaluationContext有注入风险,所以使用SimpleEvaluationContext//SimpleEvaluationContext是 StandardEvaluationContext子集,功能没那么多EvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().build();METHOD_CACHE.forEach(ctx::setVariable);return ctx;}public static void main(String[] args) throws Exception{ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = SpringElUtil.createContext();Object value = parser.parseExpression("#ceil(1.345)").getValue(context);System.out.println(value);}}
package com.example.spring_boot_study.spring.spell;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;/*** AES加密*/
public class AesFunction {private static final String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding";public static String strEncodeBase64(String data, String base64Key) {final Key dataKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES");try {Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5PADDING);cipher.init(Cipher.ENCRYPT_MODE, dataKey);byte[] encryptData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(encryptData).replaceAll("\r", "").replaceAll("\n", "");} catch (Exception e) {e.printStackTrace();}return "";}public static void main(String[] args) {System.out.println(AesFunction.strEncodeBase64("123", "XGAXicVG5GMBsx5bueOe4w=="));}}

入参:

package com.example.spring_boot_study.spring.spell;import lombok.Data;@Data
public class RequestDemo {/*** 请求URL,如果需要带动态参数(非写死的参数),以${参数名形式}*/private String url;/*** 请求类型 1、Get 2、Post*/private Integer requestType;/*** 请求头配置*/private String requestHeader;/*** 请求配置*/private String requestConfig;/*** 响应配置*/private String responseConfig;
}

模拟入参:

 RequestDemo requestDemo = new RequestDemo();requestDemo.setRequestConfig("{\n" +"\t\"timestamp \": \"${oppoExposeTimestamp}\",\n" +"\t\"adId\": \"${oppoAdId}\",\n" +"\t\"dataType\": 2,\n" +"\t\"ascribeType\": 1,\n" +"\t\"channel\": 1,\n" +"\t\"type\": \"${oppoType}\",\n" +"\t\"pkg\": \"com.credit\",\n" +"\t\"${oppoPhoneIdName}\": \"#aes(${oppoPhoneIdValue},'XGAXicVG5GMBsx5bueOe4w==')\"\n" +"}");// 注意我这里构建了一个md5(String str)的方法,而我在SpringElUtil里并没有自定义md5(String str)// 方法,所以肯定会报错,需要啥方法就要预先在SpringElUtil定义requestDemo.setRequestHeader("{\n" +"\t\"Content-Type\": \"application/json;charset=UTF-8\",\n" +"\t\"signature\": \"#md5(${requestBody}+${oppoExposeTimestamp}+'e0u6fnlag06lc3pl')\",\n" +"\t\"timestamp\": \"${oppoExposeTimestamp}\"\n" +"}");// 封装参数对应的值JSONObject msgJson = new JSONObject();msgJson.put("custNo", "123");msgJson.put("oppoPhoneIdName", "123");msgJson.put("oppoPhoneIdValue", "123");msgJson.put("oppoExposeTimestamp", new Date());msgJson.put("oppoAdId", "123");msgJson.put("oppoType", "123");// 可以自己写一个Controller实现具体的接口,我这里随便写的// 下面的返回也是一样,调试时,屏蔽http调用,构造返回参数即可// 调用真实的url,可根据具体要求设置responseConfigrequestDemo.setUrl("/test");requestDemo.setResponseConfig("result.data.code=200");

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

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

相关文章

大模型相关内容的研究学习

大模型研究学习 1.大模型的“幻觉” 幻觉可以分为事实性幻觉和忠实性幻觉。 事实性幻觉&#xff0c;是指模型生成的内容与可验证的现实世界事实不一致。 比如问模型“第一个在月球上行走的人是谁&#xff1f;”&#xff0c;模型回复“Charles Lindbergh在1951年月球先驱任务…

the7主题下载,探索WordPress主题的无限可能

在数字时代&#xff0c;一个出色的网站是任何企业或个人品牌的必备。但在这个竞争激烈的网络世界中&#xff0c;如何让您的网站脱颖而出&#xff1f;答案就是 the7 —— 一款专为创造独特和视觉冲击力强的网站而设计的 WordPress 主题。 1. 无限设计可能性 the7 以其独特的设…

探索政务热线24小时在线服务:提升政府服务效能与民众满意度

一. 引言 在信息化、网络化日益深入的今天&#xff0c;政府服务的方式也在不断地变革与创新。政务热线系统作为政府与民众沟通的重要桥梁&#xff0c;其重要性不言而喻。政务热线不仅是政府倾听民众声音、回应社会关切的重要渠道&#xff0c;更是推动政府服务向数字化、智能化…

代码随想录Day40:Leetcode343、96

Leetcode343&#xff1a; 问题描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 代码及注释解析&#xff1a; class Solution { publ…

Linux-CentOS-7忘记密码-修改登录密码图文详解

Linux-CentOS-7忘记密码-修改登录密码图文详解 1.重启系统&#xff1a; 在登录界面&#xff0c;选择要登录的用户并点击"Power"按钮&#xff0c;然后选择"Restart"或"Reboot"重新启动系统。 在系统启动时持续按下 “e” 键进入编辑模式。 2…

谷歌 I/O 2024大会全面硬钢OpenAI;腾讯宣布旗下的混元文生图大模型;阿里巴巴技术下的AI自动视频剪辑工具

✨ 1: 谷歌 I/O 2024 谷歌 I/O 2024 发布了众多新技术&#xff0c;包括 Gemini AI、大语言模型和通用 AI 智能体等&#xff0c;全面颠覆搜索体验。 谷歌 I/O 2024发布会带来许多令人兴奋的新功能和技术创新&#xff1a; Gemini 1.5 Pro&#xff1a;一个极其强大的语言模型&am…

文献检索神器分享:一键筛选顶刊论文,还能免费下载全文!

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 信息爆炸的时代&#xff0c;文献是根本读不完。一个关键词能搜出来几万篇&#xff0c;而且有些结论还是完全相反的&#xff0c;到底该读哪些&#xff1f; 第一步的文献筛选很重…

Java面试八股之float和double的区别

Java中float和double的区别 存储空间与精度&#xff1a; double&#xff1a;占据64位&#xff08;8字节&#xff09;存储空间&#xff0c;属于双精度浮点数。它可以提供较高的精度&#xff0c;通常能够精确表示大约15到17位十进制数字&#xff0c;适合用于需要较高精度计算或…

汇凯金业:3个高效的黄金投资技巧

黄金投资中的高效技巧往往承载了许多投资前辈的智慧与经验教训&#xff0c;成为新手投资者宝贵的学习资料。历史上积累的黄金投资经验可以作为新投资者的学习榜样。 3个高效的黄金投资技巧 一、稳健的中长期投资策略 在金属投资领域虽然不乏短线交易高手&#xff0c;但新手投资…

Cocos Creator 2D Mask与Layout 使用详解

Cocos Creator是一款强大的2D游戏开发引擎&#xff0c;提供了丰富的功能和工具&#xff0c;使开发者可以轻松创建出高质量的游戏。其中&#xff0c;2D Mask和Layout是Cocos Creator中常用的两个组件&#xff0c;它们可以帮助开发者实现更加复杂和精美的游戏界面设计。本文将详细…

《Fundamentals of Power Electronics》——阻抗和传递函数的图解构造

通常&#xff0c;我们可以通过观察画出近似的波德图&#xff0c;而不需要大量杂乱的代数和不可避免的相关代数错误。使用这种方法可以对电路的工作原理有很大的了解。在不同频率下&#xff0c;哪些元件主导电路响应变得很清楚&#xff0c;因此合适的近似变得很明显。可以直接得…

JVM运行时内存:程序计数器

文章目录 1. 程序计数器的作用2. 程序计数器的基本特征3. 程序计数器的问题 运行时内存整体结构如下图所示: 1. 程序计数器的作用 为了保证程序(在操作系统中理解为进程)能够连续地执行下去&#xff0c;CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作…

WSL数据迁移(迁移ext4.vhdx)

WSL的ubuntu默认安装在C盘&#xff0c;数据文件位于&#xff1a;C:\Users\hzgdi\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_79rhkp1fndgsc\LocalState\ext4.vhdx, 使用一段时间后&#xff0c;尤其时使用大量的docker image 和容器后&#xff0c;占用空间较…

【JavaScript】尺寸和位置

DOM对象相关的尺寸和位置属性 用于获取和修改元素在页面中的尺寸和位置。 只读属性&#xff1a; clientWidth和clientHeight&#xff1a;获取元素可视区域的宽度和高度&#xff08;padding content&#xff09;&#xff0c;不包括边框和滚动条。 offsetWidth和offsetHeight…

C# WinForm —— 15 DateTimePicker 介绍

1. 简介 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般以 dtp 开头Format设置显示时间的格式&#xff0c;包含Long&#xff1a; Short&#xff1a; Time&#xff1a; Custom&#xff1a;采用标准的时间格式 还是 自定义的格式CustomFormat自定…

如何搭建本地DNS服务器

一、搭建本地DNS服务器 1.初始化设置 systemctl disable --now firewalld.service #关闭防火墙&#xff0c;并开机不自启 setenforce 0 #临时关闭selinux防火墙 vim /etc/selinux/config …

1727jsp思想政治活动Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 思想政治活动管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff…

ArcGIS软件损坏怎么修复?10.7分享

前言 我们经常ArcGIS用着用着就会出现一些莫名奇怪的情况&#xff0c;比如ArcGIS的工具箱都打&#xff0c;字体丢失等、dll文件缺失。尝试了很多方法之后没有效果的&#xff0c;我们可以对软件做修复 那么修复改如果做呢&#xff1f; 不需要卸载软件&#xff0c;直接安装deskt…

C语言编程资源分享 包管理器 内存泄露 串口通迅 编译器GCC 静态分析 x86汇编 第三方依赖 媒体处理 内存调试 性能工具 模糊测试

C语言编程资源分享 C语言是一种通用的、过程式的计算机编程语言,支持结构化编程、词汇变量作用域和递归,是静态类型检查的。下面是一个简单的C语言程序示例,它展示了如何编写一个输出“Hello, World!”的程序: #include <stdio.h> int main() {printf("Hello…

智慧安防监控EasyCVR视频汇聚管理平台视频播放花屏的原因分析及处理

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…