当前位置: 首页 > news >正文

Spring Boot对访问密钥加密解密——RSA

场景

用户无需登录,仅仅根据给定的访问keyId和keySecret就可以访问接口。

  1. keyId 等可以明文发送(不涉及机密),后端直接从请求头读取。
  2. keySecret 不可明文,需要加密后放在另一个请求头(或请求体)里,比如 Encrypted-Data。
  3. timestamp + 6位随机值(nonce) 用来防重放或检验请求时效,需要在明文里也传递给后端,以方便后端做校验。
  4. 后端拿到 Encrypted-Data 后,使用私钥解密得到真正的 keySecret、然后再和明文传递过来的 timestamp、randomValue 做进一步匹配。
# 请求头中的信息
keyId
timestamp
randomValue
Encrypted-Data="{\"keySecret\":\"%s\",\"timestamp\":\"%s\",\"randomValue\":\"%s\"}"

客户端

前端调用:拼装并加密数据

假设我们有如下信息:

  • keyId:“myKeyId”
  • keySecret:“myKeySecret”(需加密)
  • timestamp:“20251225103449”(格式 yyyyMMddHHmmss)
  • randomValue:6 位随机数,如 “538201”
生成随机数 & 时间戳
function generateTimestamp() {// 生成形如 20251225103449 的日期字符串 (yyyyMMddHHmmss)const now = new Date();const yyyy = now.getFullYear();const MM = String(now.getMonth() + 1).padStart(2, '0');const dd = String(now.getDate()).padStart(2, '0');const HH = String(now.getHours()).padStart(2, '0');const mm = String(now.getMinutes()).padStart(2, '0');const ss = String(now.getSeconds()).padStart(2, '0');return `${yyyy}${MM}${dd}${HH}${mm}${ss}`;
}function generateRandomValue6() {// 生成 6 位随机数return String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
}
加密 keySecret

假设使用 jsencrypt 进行 RSA 加密,并把 keySecret + timestamp + randomValue 打包在一个 JSON 对象里加密。

import axios from 'axios';
import JSEncrypt from 'jsencrypt';// RSA 公钥
const PUBLIC_KEY_PEM = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqh...
-----END PUBLIC KEY-----
`;// 拼装并加密
function encryptKeyInfo(keySecret, timestamp, randomValue) {const payload = {keySecret,timestamp,randomValue};const jsonStr = JSON.stringify(payload);const jsEncrypt = new JSEncrypt();jsEncrypt.setPublicKey(PUBLIC_KEY_PEM);const encrypted = jsEncrypt.encrypt(jsonStr);if (!encrypted) {throw new Error("加密失败,请检查公钥格式或数据大小");}return encrypted; // Base64 编码的加密结果
}// 发送请求示例
async function callSecureApi() {const keyId = "myKeyId";const timestamp = generateTimestamp(); const randomValue = generateRandomValue6(); const keySecret = "myKeySecret";  // 需要加密的机密// 把 keySecret、timestamp、randomValue 一并加密const encryptedData = encryptKeyInfo(keySecret, timestamp, randomValue);// 发请求时,除了 "Encrypted-Data" 外,把 keyId/timestamp/randomValue 也都明文放在 header// 这样后端可做交叉验证try {const resp = await axios.post('/api/secure',{}, // body 可为空或随意{headers: {'keyId': keyId,'timestamp': timestamp,'randomValue': randomValue,'Encrypted-Data': encryptedData}});console.log('后端返回:', resp.data);} catch (e) {console.error(e);}
}

Java调用

maven依赖

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.14</version>
</dependency>

代码调用

package com.demo.test;import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;import javax.crypto.Cipher;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Random;public class ThirdPartyCaller {// 假设你目标服务的地址private static final String BASE_URL = "http://localhost:13131";// 例如 "http://localhost:8080" 或生产环境 "https://yourdomain.com"public static void main(String[] args) {try {// 1) 先获取公钥(PEM 格式)String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs3ZYer3Ibej9o4IRlzkC\n" +"2Hjk0roGuR4ckIafh/DZNHv/JfHgM7R10BLfTnkiGyrps7Lk36Gm9RnafduphCMX\n" +"nG7nZdCYKt16aE4Tqb7FZ/pLz8WOjpxiSLA361w+knG9UCyytmhXjo6wZ8zxNoSd\n" +"ePhMkGBVxwCbeZS9wldBbwpRpMY1Dsyve7C8COhEiWIFXz0ruMsskukCue2Q6nNh\n" +"6dLIN17MtEqa7in7Q6xPvyNPsCkfI8PAvQqGO5thdZoTcT7XPHrBJesfuS8sSmtF\n" +"xMPfI/Nke/KTykeDcf4PHKd0GP5c6+/p0XVkzxHm7sbFEEdII41e1Gd81gJ6bPQc\n" +"dQIDAQAB\n" +"-----END PUBLIC KEY-----";// 2) 解析 PEM 得到 PublicKey 对象PublicKey pubKey = parsePublicKeyFromPem(publicKeyPem);// 3) 准备要加密和要发送的字段String keyId = "qwertyuiopasdfghjklzxcvbnm";String keySecret = "demo@123!";String timestamp = generateTimestamp();         // yyyyMMddHHmmssString randomValue = generateRandomValue6();    // 6位随机数// 4) 组装 JSON,再用公钥 RSA 加密//   若与前端示例一致,可把 (keySecret, timestamp, randomValue) 放到 JSONString payloadJson = String.format("{\"keySecret\":\"%s\",\"timestamp\":\"%s\",\"randomValue\":\"%s\"}",keySecret, timestamp, randomValue);String encryptedData = rsaEncrypt(pubKey, payloadJson);// 5) 调用 /api/secure 接口String result = callSecureApi(keyId, timestamp, randomValue, encryptedData);System.out.println("调用 /aliyun_sms/send 响应: " + result);} catch (Exception e) {e.printStackTrace();}}/*** 第一步:解析 PEM 格式的公钥字符串,得到 PublicKey 对象* 如果服务器只返回纯Base64(无头尾),可根据实际情况调整。*/private static PublicKey parsePublicKeyFromPem(String publicKeyPem) throws Exception {// 移除头尾行和换行,保留中间的Base64部分String base64 = publicKeyPem.replaceAll("-----BEGIN PUBLIC KEY-----", "").replaceAll("-----END PUBLIC KEY-----", "").replaceAll("\\s", "");byte[] decoded = Base64.getDecoder().decode(base64);X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);KeyFactory keyFactory = KeyFactory.getInstance("RSA");return keyFactory.generatePublic(keySpec);}/*** 第二步:把要加密的JSON用RSA公钥加密*/private static String rsaEncrypt(PublicKey publicKey, String data) throws Exception {Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] encrypted = cipher.doFinal(data.getBytes());return Base64.getEncoder().encodeToString(encrypted);}/*** 生成时间戳 (yyyyMMddHHmmss)*/private static String generateTimestamp() {DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");return LocalDateTime.now().format(fmt);}/*** 生成6位随机数 (000000~999999)*/private static String generateRandomValue6() {Random random = new Random();int num = random.nextInt(1_000_000);return String.format("%06d", num);}/*** 第三步:调用 /api/secure 接口,并在header中带上 keyId, timestamp, randomValue, Encrypted-Data*/private static String callSecureApi(String keyId, String timestamp,String randomValue, String encryptedData) throws IOException {String url = BASE_URL + "/aliyun_sms/send";try (CloseableHttpClient httpClient = HttpClients.createDefault()) {HttpPost post = new HttpPost(url);// 设置headerpost.setHeader("keyId", keyId);post.setHeader("timestamp", timestamp);post.setHeader("randomValue", randomValue);post.setHeader("Encrypted-Data", encryptedData);post.setHeader("Content-Type", "application/json;charset=UTF-8");// 此示例没给 body,若需要可在 body 放更多数据//post.setEntity(new StringEntity("{\"name\":\"张三\"}", "UTF-8"));try (CloseableHttpResponse response = httpClient.execute(post)) {int statusCode = response.getStatusLine().getStatusCode();String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");if (statusCode == 200) {return respBody;} else {throw new IOException("调用 /api/secure 失败, HTTP状态=" + statusCode + ", body=" + respBody);}}}}
}

服务端

RSA私钥解密

package com.demo.lcpd.utils;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;@Component
public class RsaDecryptor {@Value("${security.rsa.privateKeyPem}")private String privateKeyPem;public String decrypt(String encryptedBase64) throws Exception {// 1) 去掉 PEM 格式里的头尾和换行,只保留真正的Base64部分String base64Key = removePemFormatting(privateKeyPem);// 2) 把Base64解码还原成私钥对象byte[] keyBytes = Base64.getDecoder().decode(base64Key);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");PrivateKey privateKey = keyFactory.generatePrivate(keySpec);// 3) 解密byte[] encryptedBytes = Base64.getDecoder().decode(encryptedBase64);Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] decrypted = cipher.doFinal(encryptedBytes);return new String(decrypted, StandardCharsets.UTF_8);}private String removePemFormatting(String pem) {// 移除行首行尾 (-----BEGIN PRIVATE KEY----- / -----END PRIVATE KEY-----) 以及换行return pem.replaceAll("-----BEGIN PRIVATE KEY-----", "").replaceAll("-----END PRIVATE KEY-----", "").replaceAll("\\s", "");}}

自定义拦截器

package com.demo.lcpd.intercept;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.demo.lcpd.utils.RsaDecryptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;@Component
public class RsaAuthInterceptor implements HandlerInterceptor {@Resourceprivate RsaDecryptor rsaDecryptor;private Map<String, String> keyStore = new HashMap<>();//添加允许通过的keyId和keySecretpublic RsaAuthInterceptor() {keyStore.put("qwertyuiopasdfghjklzxcvbnm", "demo@123!");}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String keyId = request.getHeader("keyId");String timestamp = request.getHeader("timestamp");String randomValue = request.getHeader("randomValue");String encryptedData = request.getHeader("Encrypted-Data");if (keyId == null || timestamp == null || randomValue == null || encryptedData == null) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing auth headers");return false;}try {// 2) RSA 私钥解密String decryptedJson = rsaDecryptor.decrypt(encryptedData);// decryptedJson 形如: {"keySecret":"myKeySecret","timestamp":"20251225103449","randomValue":"538201"}// 3) 解析 JSONObjectMapper objectMapper = new ObjectMapper();JsonNode node = objectMapper.readTree(decryptedJson);String decryptedKeySecret = node.get("keySecret").asText();String decryptedTimestamp = node.get("timestamp").asText();String decryptedRandomValue = node.get("randomValue").asText();// 4) 与明文传递的 timestamp、randomValue 对比if (!timestamp.equals(decryptedTimestamp) || !randomValue.equals(decryptedRandomValue)) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Timestamp or randomValue mismatch");return false;}// 5) 校验 keyId 是否存在,以及 keySecret 是否匹配if (!keyStore.containsKey(keyId)) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid keyId");return false;}String expectedSecret = keyStore.get(keyId);if (!expectedSecret.equals(decryptedKeySecret)) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid keySecret");return false;}// 6) 校验 timestamp 是否在合理范围 (例如 ±15分钟)if (!checkTimestampValid(timestamp)) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Timestamp expired or invalid");return false;}// 7) 防重放:检查 (keyId + timestamp + randomValue) 是否已使用过// 这里仅示例,不做具体实现// boolean notUsed = checkAndMarkUsed(keyId, timestamp, randomValue);// if (!notUsed) {//     response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Replay attack detected");//     return;// }// 校验成功return true;} catch (Exception e) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Decryption or parse error");return false;}}private boolean checkTimestampValid(String timestampStr) {try {// 解析 yyyyMMddHHmmssDateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");LocalDateTime reqTime = LocalDateTime.parse(timestampStr, fmt);LocalDateTime now = LocalDateTime.now();// 假设 15 分钟有效期return !reqTime.isBefore(now.minusMinutes(15)) && !reqTime.isAfter(now.plusMinutes(15));} catch (Exception e) {return false;}}
}

注册拦截器

package com.demo.lcpd.conf;import com.demo.lcpd.intercept.RsaAuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Resourceprivate RsaAuthInterceptor rsaAuthInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(rsaAuthInterceptor).addPathPatterns("/**");}
}

在 Java 中生成 RSA 公私钥

使用 KeyPairGenerator

public class RsaKeyGeneratorPemExample {public static void main(String[] args) throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);KeyPair keyPair = keyPairGenerator.generateKeyPair();// Base64String publicKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());String privateKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());// 包装成 PEMString publicKeyPem = convertToPem(publicKeyBase64, true);String privateKeyPem = convertToPem(privateKeyBase64, false);System.out.println(publicKeyPem);System.out.println(privateKeyPem);}private static String convertToPem(String base64Key, boolean isPublic) {int lineLength = 64;StringBuilder sb = new StringBuilder();if (isPublic) {sb.append("-----BEGIN PUBLIC KEY-----\n");} else {sb.append("-----BEGIN PRIVATE KEY-----\n");}for (int i = 0; i < base64Key.length(); i += lineLength) {int end = Math.min(i + lineLength, base64Key.length());sb.append(base64Key, i, end).append("\n");}if (isPublic) {sb.append("-----END PUBLIC KEY-----\n");} else {sb.append("-----END PRIVATE KEY-----\n");}return sb.toString();}}

运行后就能得到标准的 PEM 形式公私钥,类似:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFA...
...LQIDAQAB
-----END PUBLIC KEY----------BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQ...
...2ww==
-----END PRIVATE KEY-----

这两个就是 PEM 格式的“公钥”和“私钥”。

前端使用的 PUBLIC_KEY_PEM 是什么?

可以是带头尾的 PEM

在前端(例如使用 jsencrypt)时,最常见的做法就是直接使用 PEM 格式:

const PUBLIC_KEY_PEM = -----BEGIN PUBLIC KEY----- MIIBIjANBgkqh... -----END PUBLIC KEY-----;

然后在加密时,像这样写:

import JSEncrypt from 'jsencrypt';const jsEncrypt = new JSEncrypt();
// 直接 set PEM 格式的字符串即可
jsEncrypt.setPublicKey(PUBLIC_KEY_PEM);
const encrypted = jsEncrypt.encrypt("hello world");

jsencrypt 这个库支持 PEM 格式字符串(包含头尾)。也就是说,如果你在 Java 端生成了 PEM,直接整段复制到前端就行。

也可以是纯 Base64 字符串

有些前端库也允许你只传Base64 编码的公钥,而不含头尾。那你就可以给它传:

// 纯 Base64,不带 -----BEGIN----
const PUBLIC_KEY_BASE64 = "MIIBIjANBgkq…";const jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(`-----BEGIN PUBLIC KEY-----
${PUBLIC_KEY_BASE64}
-----END PUBLIC KEY-----`);

要点总结

1.后端生成 RSA 公私钥:

  • 公钥用来给前端加密;
  • 私钥留在后端解密,不要泄露。

2.前端使用 jsencrypt:

  • PEM 格式(带 -----BEGIN PUBLIC KEY-----)的公钥即可;
  • 把要加密的字段 (keySecret, timestamp, randomValue) JSON 化 -> 调用 .encrypt(…) -> 得到密文(Base64)。

3.后端使用私钥解密:

  • 先去掉 PEM 的头尾,只保留 Base64 -> 解码 -> Cipher.getInstance(“RSA/ECB/PKCS1Padding”) 解密;
  • 拿到明文 JSON,解析出 keySecret, timestamp, randomValue;
  • 与前端同时传来的明文 timestamp, randomValue 做比对;再检查 keyId 对应的 keySecret 是否匹配。

4.时间戳 + 随机值:

  • 可做防重放;
  • 时间戳超时或重复使用同一 (keyId + timestamp + randomValue),则拒绝请求。

5.配合 HTTPS:

  • RSA 加密可以避免明文 keySecret 出现在网络上,但最佳实践还是要用 HTTPS,防止更多层面的攻击、劫持、篡改。

相关文章:

Spring Boot对访问密钥加密解密——RSA

场景 用户无需登录&#xff0c;仅仅根据给定的访问keyId和keySecret就可以访问接口。 keyId 等可以明文发送&#xff08;不涉及机密&#xff09;&#xff0c;后端直接从请求头读取。keySecret 不可明文&#xff0c;需要加密后放在另一个请求头&#xff08;或请求体&#xff0…...

Vue介绍

一、Vue框架简介 Vue.js是一个用于构建用户界面的渐进式JavaScript框架。它的核心库只关注视图层,易于上手,并且可以与其他库或现有项目进行整合。其特点包括响应式数据绑定、组件化开发和虚拟DOM等。 响应式数据绑定 Vue通过Object.defineProperty()方法来进行数据劫持。当…...

表单元素(标签)有哪些?

HTML 中的表单元素&#xff08;标签&#xff09;用于收集用户输入的数据&#xff0c;常见的有以下几种&#xff1a; 文本输入框 <input type"text">&#xff1a;用于单行文本输入&#xff0c;如用户名、密码等。可以通过设置maxlength属性限制输入字符数&…...

人工智能与云计算的结合:如何释放数据的无限潜力?

引言&#xff1a;数据时代的契机 在当今数字化社会&#xff0c;数据已成为推动经济与技术发展的核心资源&#xff0c;被誉为“21世纪的石油”。从个人消费行为到企业运营决策&#xff0c;再到城市管理与国家治理&#xff0c;每个环节都在生成和积累海量数据。然而&#xff0c;数…...

TCP Analysis Flags 之 TCP Out-Of-Order

前言 默认情况下&#xff0c;Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态&#xff0c;并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时&#xff0c;会对每个 TCP 数据包进行一次分析&#xff0c;数据包按照它们在数据包列表中出现的顺序进行处理。可…...

【MyBatis 核心工作机制】注解式开发与动态代理原理

有很多朋友可能已经在开发中熟练使用 MyBatis 或者刚开始学习 MyBatis&#xff0c;对于它的一些工作机制不太了解。“咦&#xff0c;怎么写几个注解&#xff0c;写几个配置文件&#xff0c;就能实现这些效果呢&#xff0c;好神奇呀&#xff01;”当你看完这篇博客之后&#xf…...

深度学习在图像识别中的最新进展与实践案例

深度学习在图像识别中的最新进展与实践案例 在当今信息爆炸的时代&#xff0c;图像作为信息传递的重要载体&#xff0c;其处理与分析技术显得尤为重要。深度学习&#xff0c;作为人工智能领域的一个分支&#xff0c;凭借其强大的特征提取与模式识别能力&#xff0c;在图像识别…...

vue3中如何自定义插件

英译汉插件 i18n.ts export default {install: (app: any, options: any) > {// 注入一个全局可用的$translate()方法app.config.globalProperties.$translate (key: string) > {// 获取options对象的深层属性// 使用key作为索引return key.split(".").redu…...

【机器学习】回归

文章目录 1. 如何训练回归问题2. 泛化能力3. 误差来源4. 正则化5. 交叉验证 1. 如何训练回归问题 第一步&#xff1a;定义模型 线性模型&#xff1a; y ^ b ∑ j w j x j \hat{y} b \sum_{j} w_j x_j y^​b∑j​wj​xj​ 其中&#xff0c;( w ) 是权重&#xff0c;( b )…...

Maven项目中不修改 pom.xml 状况下直接运行OpenRewrite的配方

在Java 的Maven项目中&#xff0c;可以在pom.xml 中配置插件用来运行OpenRewrite的Recipe&#xff0c;但是有一些场景是希望不修改pom.xml 文件就可以运行Recipe&#xff0c;比如&#xff1a; 因为不需要经常运行 OpenRewrite&#xff0c;所以不想在pom.xml 加入不常使用的插件…...

【翻译】Sora 系统卡-12月9日

Sora System ard | OpenAI 简介 Sora 概述 Sora 是 OpenAI 的视频生成模型&#xff0c;旨在接收文本、图像和视频输入并生成新视频作为输出。用户可以创建各种格式的分辨率高达 1080p&#xff08;最长 20 秒&#xff09;的视频&#xff0c;从文本生成新内容&#xff0c;或增强…...

如何在 Spring Boot 微服务中设置和管理多个数据库

在现代微服务架构中&#xff0c;通常需要与多个数据库交互的服务。这可能是由于各种原因&#xff0c;例如遗留系统集成、不同类型的数据存储需求&#xff0c;或者仅仅是为了优化性能。Spring Boot 具有灵活的配置和强大的数据访问库&#xff0c;可以轻松配置多个数据库。在本综…...

Ubuntu20.04安装Foxit Reader 福昕阅读器

Ubuntu20.04安装Foxit Reader 福昕阅读器 文章目录 Ubuntu20.04安装Foxit Reader 福昕阅读器 先更新一下源 sudo apt update sudo apt upgrade下载Foxit Reader的稳定版本 wget https://cdn01.foxitsoftware.com/pub/foxit/reader/desktop/linux/2.x/2.4/en_us/FoxitReader.e…...

学习threejs,THREE.CircleGeometry 二维平面圆形几何体

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.CircleGeometry 圆形…...

Tonghttpserver6.0.1.3 使用整理(by lqw)

文章目录 1.声明2.关于单机版控制台和集中管理控制台3.单机版控制台3.1安装&#xff0c;启动和查看授权信息3.2一些常见的使用问题&#xff08;单机控制台&#xff09;3.3之前使用的是nginx&#xff0c;现在要配nginx.conf上的配置&#xff0c;在THS上如何配置3.4如何配置密码过…...

redis开发与运维-redis0401-补充-redis流水线与Jedis执行流水线

文章目录 【README】【1】redis流水线Pipeline【1.1】redis流水线概念【1.2】redis流水线性能测试【1.2.1】使用流水线与未使用流水线的性能对比【1.2.2】使用流水线与redis原生批量命令的性能对比【1.2.3】流水线缺点 【1.3】Jedis客户端执行流水线【1.3.1】Jedis客户端执行流…...

OPPO Java面试题及参考答案

Java 语言的特点 Java 是一种面向对象的编程语言,它具有以下显著特点。 首先是简单性。Java 的语法相对简单,它摒弃了 C 和 C++ 语言中一些复杂的特性,比如指针操作。这使得程序员能够更专注于业务逻辑的实现,而不是陷入复杂的语法细节中。例如,Java 的内存管理是自动进行…...

Ubuntu 22.04 升级 24.04 问题记录

一台闲置笔记本使用的 ubuntu 还是 18.04&#xff0c;最近重新使用&#xff0c;发现版本过低&#xff0c;决定升级&#xff0c;于是完成了 18.04 -> 20.04 -> 22. 04 -> 24.04 的三连跳。 一、升级过程中黑屏 主要问题是在 22.04 升级到 24.04 过程中出现了黑屏仅剩…...

Java重要面试名词整理(五):Redis

文章目录 Redis高级命令Redis持久化RDB快照&#xff08;snapshot&#xff09;**AOF&#xff08;append-only file&#xff09;****Redis 4.0 混合持久化** 管道&#xff08;Pipeline&#xff09;**StringRedisTemplate与RedisTemplate详解**Redis集群方案gossip脑裂 Redis LuaR…...

单元测试中创建多个线程测试 ThreadLocal

单元测试中创建多个线程测试 ThreadLocal 在单元测试中&#xff0c;可以通过以下方式创建多个线程来测试 ThreadLocal 的行为。 目标 验证 ThreadLocal 在多线程环境下是否能正确隔离每个线程的数据。 实现步骤 定义需要测试的类 包含 ThreadLocal 对象的类&#xff0c;提供…...

iDP3复现代码数据预处理全流程(二)——vis_dataset.py

vis_dataset.py 主要作用在于点云数据的可视化&#xff0c;并可以做一些简单的预处理 关键参数基本都在 vis_dataset.sh 中定义了&#xff0c;需要改动的仅以下两点&#xff1a; 1. 点云图像保存位置&#xff0c;因为 dataset_path 被设置为了绝对路径&#xff0c;因此需要相…...

容器化部署服务全流程

系列文章目录 文章目录 系列文章目录前言一、什么是容器&#xff1f;二、如何安装docker三、如何写dockerfile四、如何启动服务五、常见命令总结总结 前言 这篇文章&#xff0c;‌主要目的是通过容器化技术简化应用程序的部署、运行和管理&#xff0c;提高开发、测试和生产环境…...

Flutter DragTarget拖拽控件详解

文章目录 1. DragTarget 控件的构造函数主要参数&#xff1a; 2. DragTarget 的工作原理3. 常见用法示例 1&#xff1a;实现一个简单的拖拽目标解释&#xff1a;示例 2&#xff1a;与 Draggable 结合使用解释&#xff1a; 4. DragTarget 的回调详解5. 总结 DragTarget 是 Flutt…...

操作系统动态分区分配算法-首次适应算法c语言实现

目录 一、算法原理 二、算法特点 1.优先利用低址空闲分区&#xff1a; 2.查找开销&#xff1a; 3.内存碎片&#xff1a; 三、内存回收四种情况 1.回收区上面&#xff08;或后面&#xff09;的分区是空闲分区&#xff1a; 2.回收区下面&#xff08;或前面&#xff09;的…...

mybatis-plus自动填充时间的配置类实现

mybatis-plus自动填充时间的配置类实现 在实际操作过程中&#xff0c;我们并不希望创建时间、修改时间这些来手动进行&#xff0c;而是希望通过自动化来完成&#xff0c;而mybatis-plus则也提供了自动填充功能来实现这一操作&#xff0c;接下来&#xff0c;就来了解一下mybatis…...

Vite内网ip访问,两种配置方式和修改端口号教程

目录 问题 两种解决方式 结果 总结 preview.host preview.port 问题 使用vite运行项目的时候&#xff0c;控制台会只出现127.0.0.1&#xff08;localhost&#xff09;本地地址访问项目。不可以通过公司内网ip访问&#xff0c;其他团队成员无法访问&#xff0c;这是因为没…...

【星海随笔】删除ceph

cephadm shell ceph osd set noout ceph osd set norecover ceph osd set norebalance ceph osd set nobackfill ceph osd set nodown ceph osd set pause参考文献&#xff1a; https://blog.csdn.net/lyf0327/article/details/90294011 systemctl stop ceph-osd.targetyum re…...

HarmonyOS NEXT实战:自定义封装多种样式导航栏组件

涉及知识点和装饰器 ComponentV2&#xff0c;Local&#xff0c; Builder&#xff0c;BuilderParam&#xff0c;Extend&#xff0c; Require &#xff0c;Param&#xff0c;Event等第三方库&#xff1a;ZRouter &#xff0c;如项目中本来就用了ZRouter路由库&#xff0c;案例中…...

大数据面试笔试宝典之Flink面试

1.Flink 是如何支持批流一体的? F link 通过一个底层引擎同时支持流处理和批处理. 在流处理引擎之上,F link 有以下机制: 1)检查点机制和状态机制:用于实现容错、有状态的处理; 2)水印机制:用于实现事件时钟; 3)窗口和触发器:用于限制计算范围,并定义呈现结果的…...

pytorch整体环境打包安装到另一台电脑上

步骤一&#xff1a;安装conda-pack 首先利用 pip list 指令检查conda环境安装在哪里&#xff0c;在系统环境&#xff08;base&#xff09;下&#xff0c;于是我是使用的conda指令完成的。 # 使用Conda安装&#xff08;如果已安装conda&#xff09; conda install conda-pack …...