八月雨中飞雪 - Java
type: channel
link: https://zhouzihao.cn/index.php/category/Java/
items: SpringBoot + Vue 实现 Python 在线调试器 - 技术方案文档
items: Java实现Minio上传文件加解密操作
items: 若依分离版多环境Redis配置（单机/集群自适应）
items: 若依分离版Redis集群配置指南
items: 使用wkhtmltopdf 完成html转PDF
items: 基于Spring Boot的HTTP请求签名验证实现解析
items: 若依集成redisson实现redis分布式锁
items: Java 使用FreeMarker生成Html
items: Java 使用docx4j+ Freemarker生成word文档
items: JAVA自动化编写单元测试

SpringBoot + Vue 实现 Python 在线调试器 - 技术方案文档
type: item
date: 2026-01-16T10:40:00+08:00
description: SpringBoot + Vue 实现 Python 在线调试器 - 技术方案文档📋 目录项目概述技术栈架构设计核心实现方法关键技术点API接口设计调试功能实现原理前端交互实现部署方案项目概述Python 在线调试器是一个基于 Web 的 Python 代码执行和调试工具，支持在线编写、运行和交互式调试 Python 代码。项目采用前后端分离架构，前端负责用户界面和交互，后端负责代码执行和调试逻辑。技术栈后端技术栈技术/框架版本用途Java17编程语言Spring Boot3.1.5Web框架Spring Web-RESTful API支持Spring Validation-参数验证Jackson-JSON序列化/反序列化Maven3.6+项目构建和依赖管理Python3.x代码执行环境核心依赖：spring-boot-starter-web: Web开发支持spring-boot-starter-websocket: WebSocket支持（预留扩展）spring-boot-starter-validation: 参数验证jackson-databind: JSON处理前端技术栈技术/框架版本用途Vue.js3.3.4前端框架Vite5.0.0构建工具和开发服务器CodeMirror 66.x代码编辑器Axios1.6.0HTTP客户端Node.js16+运行环境npm-包管理器核心依赖：@codemirror/lang-python: Python语言支持@codemirror/view: 编辑器视图@codemirror/state: 编辑器状态管理@codemirror/theme-one-dark: 深色主题@vitejs/plugin-vue: Vite Vue插件架构设计整体架构┌─────────────────────────────────────────────────────────┐ │ 浏览器 (Browser) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Vue 3 前端应用 │ │ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ │ │ CodeMirror 6 │ │ Axios HTTP │ │ │ │ │ │ 编辑器 │ │ 客户端 │ │ │ │ │ └──────────────┘ └──────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────┬───────────────────────────────────────┘ │ HTTP/REST API ┌─────────────────┴───────────────────────────────────────┐ │ Spring Boot 后端 (Port: 8080) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ PythonController │ │ │ │ (REST API 端点) │ │ │ └────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────────┴─────────────────────────────────┐ │ │ │ PythonExecutionService │ │ │ │ (代码执行和调试逻辑) │ │ │ └────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────────┴─────────────────────────────────┐ │ │ │ ProcessBuilder + Python Process │ │ │ │ (执行Python代码) │ │ │ └────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────────┴─────────────────────────────────┐ │ │ │ Python 3.x (系统安装) │ │ │ │ - pdb (Python调试器) │ │ │ │ - 代码执行 │ │ │ └──────────────────────────────────────────────────┘ │ └─────…

Java实现Minio上传文件加解密操作
type: item
date: 2025-05-09T09:56:00+08:00
description: 一、背景与需求在云存储场景中，数据安全是核心需求之一。MinIO作为高性能对象存储服务，支持通过客户端加密（CSE）在数据上传前完成加密，确保即使存储服务器被攻破，攻击者也无法获取明文数据。本文将详解如何通过Java实现MinIO文件的加密上传与解密下载，结合AES对称加密算法和BouncyCastle加密库，提供完整代码示例及安全实践建议。二、技术选型与原理1. 加密方案对比方式特点适用场景服务端加密MinIO自动处理加密，密钥由服务端管理对密钥管理要求低的场景客户端加密数据在客户端加密后上传，密钥由应用管理（本文采用此方案）高安全性需求场景2. 核心算法选择AES-256-CBC：采用256位密钥和CBC模式，需配合随机IV增强安全性BouncyCastle库：提供AES算法的完整实现，需添加依赖：<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>三、完整代码实现1. 加密上传核心逻辑public void minioFileEncryptionUpload(String bucketName, String folder, String objectName, String filePath) { LOGGER.info("准备加密上传文件至MinIO，路径：{}", filePath); try { // 1. 检查并创建桶 boolean b = minioUtil.checkBukect(bucketName); if (!b) { LOGGER.info("桶：{}，不存在！创建", bucketName); minioUtil.createBucket(bucketName); } boolean f = minioUtil.doesObjectExist(bucketName, folder); if (!f) { LOGGER.info("文件夹：{}，不存在！创建", folder); } LOGGER.info("上传文件至minio开始"); // 2. 确保文件夹存在（通过上传空对象模拟） String folderKey = folder.endsWith("/") ? folder : folder + "/"; if (!minioUtil.doesObjectExist(bucketName, folderKey)) { LOGGER.info("文件夹：{} 不存在，创建空对象", folderKey); // 修正：明确设置空对象的 Content-Type minioUtil.putObject( bucketName, new MockMultipartFile("folder", "", "application/json", "".getBytes()), // 修改点：指定默认类型 folderKey, "application/json" // 修改点：显式传递 Content-Type ); } // 3. 加载密钥 Key secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), AES_ALGORITHM); // 4. 读取文件并加密 File file = new File(filePath); try (InputStream fileInputStream = new FileInputStream(file); CipherInputStream encryptedStream = new CipherInputStream(fileInputStream, getCipher(secretKey, Cipher.ENCRYPT_MODE))) { // 5. 构建加密后的 MultipartFile（修复点：动态推断 Content-Type） String detectedContentType = Files.probeContentType(file.toPath()); // 使用系统 API 推断类型 if (detectedContentType == null) { detectedContentType = "application/octet-stream"; // 默认类型 } MultipartFile encryptedFile = new MockMultipartFile( file.getName(), file.getName(), detectedContentType, // …

若依分离版多环境Redis配置（单机/集群自适应）
type: item
date: 2025-04-25T15:17:00+08:00
description: 功能概述实现Spring Boot项目在本地开发、测试环境和生产环境无缝切换Redis模式，通过统一配置体系适配不同部署场景。核心代码实现1. Redisson多模式配置类package com.aspire.datasynchron.framework.config; import com.aspire.datasynchron.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.redisson.config.ClusterServersConfig; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; /** * Redisson 配置类，支持 Redis 集群模式 * * @author ruoyi */ @Configuration public class RedissonConfig { @Value("${spring.redis.mode:single}") // 默认单机模式 private String mode; // 单机模式配置 @Value("${spring.redis.host:127.0.0.1}") private String host; @Value("${spring.redis.port:6379}") private int port; private static final Logger log = LoggerFactory.getLogger(RedissonConfig.class); @Value("${spring.redis.cluster.nodes:10.120.75.50:7400,10.120.75.50:7401,10.120.75.50:7402,10.120.75.51:7403,10.120.75.51:7404,10.120.75.51:7405}") private String clusterNodes; // 集群节点列表，格式：192.168.1.101:7001,192.168.1.102:7002,... @Value("${spring.redis.password}") private String password; // Redis 密码（可为空） private RedissonClient redissonClient; // 成员变量 @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean(RedissonClient.class) public RedissonClient redissonClient() { if ("single".equalsIgnoreCase(mode) && StringUtils.isNotBlank(clusterNodes)) { Config config = new Config(); String address = "redis://" + host + ":" + port; SingleServerConfig serverConfig = config.useSingleServer() .setAddress(address) .setDatabase(1); if (StringUtils.hasText(password)) { serverConfig.setPassword(password); } retur…

若依分离版Redis集群配置指南
type: item
date: 2025-04-23T16:35:00+08:00
description: 环境要求组件版本JDK17Maven3.9.4Ruoyi3.8.9一、Redisson集群配置优化1.1 配置类修改说明package com.aspire.datasynchron.framework.config; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.redisson.config.ClusterServersConfig; import org.redisson.config.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; /** * Redisson 配置类，支持 Redis 集群模式 * * @author ruoyi */ @Configuration public class RedissonConfig { private static final Logger log = LoggerFactory.getLogger(RedissonConfig.class); @Value("${spring.redis.cluster.nodes:10.120.75.50:7400,10.120.75.50:7401,10.120.75.50:7402,10.120.75.51:7403,10.120.75.51:7404,10.120.75.51:7405}") private String clusterNodes; // 集群节点列表，格式：192.168.1.101:7001,192.168.1.102:7002,... @Value("${spring.redis.password}") private String password; // Redis 密码（可为空） private RedissonClient redissonClient; // 成员变量 @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean(RedissonClient.class) public RedissonClient redissonClient() { Config config = new Config(); log.info("Redisson 配置初始化开始..."); log.info("集群节点列表: {}", clusterNodes); // 配置 Redis 集群模式 ClusterServersConfig clusterConfig = config.useClusterServers(); // 格式化节点地址，添加 redis:// 前缀 String[] nodes = clusterNodes.split(","); String[] formattedNodes = new String[nodes.length]; for (int i = 0; i < nodes.length; i++) { formattedNodes[i] = "redis://" + nodes[i].trim(); } clusterConfig.addNodeAddress(formattedNodes); // 如果启用了密码认证，则设置密码 if (password != null && !password.isEmpty()) { clusterConfig.setPassword(password); } // 可选配置 clusterConfig.setScanInterval(2000); // 集群状态扫描间隔（毫秒） clusterConfig.setIdleConnectionTimeout(10000); // 空闲…

使用wkhtmltopdf 完成html转PDF
type: item
date: 2025-04-10T09:37:00+08:00
description: 一、搭建所需环境下载wkhtmltoimage首先，你需要从wkhtmltopdf的官方网站（https://wkhtmltopdf.org/downloads.html）下载wkhtmltoimage的Windows版本。确保选择与你的系统架构（32位或64位）相匹配的版本。解压下载的文件下载完成后，解压下载的文件到一个你喜欢的目录，例如C:\wkhtmltoimage。配置环境变量为了让系统能够识别wkhtmltoimage的路径，你需要将其添加到系统的环境变量中。具体操作如下：​ 右键点击“此电脑”或“我的电脑”，选择“属性”。​ 点击“高级系统设置”。​ 在“系统属性”窗口中，点击“环境变量”按钮。​ 在“系统变量”区域，找到名为Path的变量并选择它，然后点击“编辑”。​ 在打开的窗口中，点击“新建”，然后添加wkhtmltoimage的安装路径，例如D:\safeware\wkhtmltopdf\bin。​ 点击“确定”保存更改。验证安装打开命令提示符（cmd）并输入以下命令来验证wkhtmltoimage是否正确安装并可以正常运行：wkhtmltoimage --version二、编写工具类以下在已在window环境验证package com.aspire.sxcrec.util; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; public class HtmlToPdfConverter { private static String wkhtmltopdfPath = "D:\\safeware\\wkhtmltopdf\\bin\\wkhtmltopdf.exe"; // Windows // 或 // String wkhtmltopdfPath = "/usr/local/bin/wkhtmltopdf"; // Linux/Mac public static void main(String[] args) { String htmlFilePath = "E:\\Desktop\\export12.html"; // 输入HTML文件路径 String outputPdfPath = "E:\\Desktop\\output.pdf"; // 输出PDF路径 try { // 构建命令 ProcessBuilder processBuilder = new ProcessBuilder( wkhtmltopdfPath, htmlFilePath, outputPdfPath ); // 启动进程 Process process = processBuilder.start(); // 捕获错误流（wkhtmltopdf 通常将日志输出到错误流） BufferedReader errorReader = new BufferedReader( new InputStreamReader(process.getErrorStream()) ); String line; while ((line = errorReader.readLine()) != null) { System.err.println(line); // 打印错误信息 } // 等待命令执行完成 int exitCode = process.waitFor(); if (exitCode == 0) { System.out.println("PDF 生成成功！"); } else { System.out.println("PDF 生成失败，错误码: " + exitCode); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }

基于Spring Boot的HTTP请求签名验证实现解析
type: item
date: 2025-04-07T11:04:00+08:00
description: 概述在分布式系统交互中，API接口的安全性至关重要。本文将深入解析基于Spring Boot实现的HTTP请求签名验证机制，该方案支持GET/POST等多种请求方式，提供时效性验证和数据完整性保障。以下是核心实现的技术要点解析。功能特性多协议支持：完整覆盖GET、POST（JSON/Form-Data）等常见请求类型时效控制：5分钟有效期的请求时间窗口多重验证：公钥校验 + 签名验证 + 时间戳的三重防护机制安全过滤：自动排除签名参数参与验签计算编码兼容：自动处理URL特殊字符编码问题核心实现解析1. 主校验流程public boolean verifySignature(HttpServletRequest request, HttpServletResponse response, GridAccountUser accountUser) { // 获取公钥配置 StateGridAccount gridAccount = stateGridService .getStateGridAccountById(accountUser.getAccountId()); // 请求类型路由 if(HttpMethod.POST.matches(request.getMethod())) { // 处理JSON/Form-Data类型 } else if(HttpMethod.GET.matches(request.getMethod())) { // 处理GET参数 } // 统一返回校验结果 }2. 关键技术点2.1 时间戳验证private Boolean verifyTimestamp(String timestampStr) { long timestamp = Long.parseLong(timestampStr); long currentTime = System.currentTimeMillis(); return Math.abs(currentTime - timestamp) <= 300_000; // 5分钟有效期 }防止重放攻击要求客户端服务端时间同步误差窗口可配置化建议2.2 请求体处理JSON请求处理：String requestStr = getRequestBody(request); JSONObject jsonObject = JSON.parseObject(requestStr); map.remove("sign"); // 过滤签名参数Form-Data处理：Map<String, Object> formData = WebUtils.getParametersStartingWith(request, ""); formData.remove("sign");GET请求处理：Map<String, String[]> queryParams = request.getParameterMap(); signature = signature.replaceAll(" ", "+"); // 处理URL编码2.3 签名验证SignUtil.verifySignature( uri, // 请求路径 data.toString(), // 过滤后的参数 Long.parseLong(timestamp), signature, publicKey );优化建议参数序列化优化当前同时使用Fastjson和Hutool的JSONUtil，建议统一JSON处理库推荐使用Jackson进行标准化处理异常处理增强try { // 验签逻辑 } catch (NumberFormatException e) { log.error("时间戳格式异常: {}", timestampStr); throw new InvalidTimestampException(); } catch (SignatureException e) { log.warn("签名验证失败: {}", e.getMessage()); }性能优化添加公钥缓存机制（RedisCache）采用连接池管理数据库查询安全增强添加重放攻击计数器支持动态时间窗口配置增加黑名单IP机制注意事项时间同步：确保NTP服务的时间同步密钥管理：建议采用密钥轮换机制空参数处理：需要明确空字符串和null的处理策略编码一致性：统一使用UTF-8字符集日志脱敏：敏感参数需要做日志过滤总结该实现方案为API接口安全提供了基础保障，在实际生产环境中可根据业务需求扩展以下功能：增加流量限频控制实现双向证书验证支持多种哈希算法添加OpenAPI规范支持集成API管理平台通过持续优化验签流程和完善监控机制，可以有效构建安全可靠的API网关体系。附完整代…

若依集成redisson实现redis分布式锁
type: item
date: 2025-04-03T18:24:00+08:00
description: java 17若依：3.8.9maven仓库链接：https://central.sonatype.com/artifact/org.redisson/redisson一、引入maven依赖<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.45.1</version> </dependency>二.添加工具类RedisLock.javapackage com.ruoyi.common.core.redis; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * redis锁工具类 * * @author ruoyi */ @Component public class RedisLock { @Autowired private RedissonClient redissonClient; /** * 获取锁 * * @param lockKey 锁实例key * @return 锁信息 */ public RLock getRLock(String lockKey) { return redissonClient.getLock(lockKey); } /** * 加锁 * * @param lockKey 锁实例key * @return 锁信息 */ public RLock lock(String lockKey) { RLock lock = getRLock(lockKey); lock.lock(); return lock; } /** * 加锁 * * @param lockKey 锁实例key * @param leaseTime 上锁后自动释放锁时间 * @return true=成功；false=失败 */ public Boolean tryLock(String lockKey, long leaseTime) { return tryLock(lockKey, 0, leaseTime, TimeUnit.SECONDS); } /** * 加锁 * * @param lockKey 锁实例key * @param leaseTime 上锁后自动释放锁时间 * @param unit 时间颗粒度 * @return true=加锁成功；false=加锁失败 */ public Boolean tryLock(String lockKey, long leaseTime, TimeUnit unit) { return tryLock(lockKey, 0, leaseTime, unit); } /** * 加锁 * * @param lockKey 锁实例key * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @param unit 时间颗粒度 * @return true=加锁成功；false=加锁失败 */ public Boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { RLock rLock = getRLock(lockKey); boolean tryLock = false; try { tryLock = rLock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } return tryLock; } /** * 释放锁 * * @param lockKey 锁实例key */ public void unlock(String lockKey) { RLock lock = getRLock(lockKey); lock.unlock(); } /** * 释放锁 * * @param lock 锁信息 */ public void unlock(RLock lock) { lock.unlock(); } } 3、新增配置RedissonConfig.javapa…

Java 使用FreeMarker生成Html
type: item
date: 2025-02-20T10:19:00+08:00
description: Java 使用freemarker生成html技术方案java 1.8 + Freemarkermaven依赖<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency>创建模板文件，保存至src/main/resources/templates/，文件名为template.html ${name}的简历 ${name}的简历 联系信息 邮箱: ${email} 电话: ${phone} 工作经历 <#list experience as job> ${job.position} 在 ${job.company}（${job.startYear} - ${job.endYear}） ${job.description} </#list> 教育背景 <#list education as degree> ${degree.degree} ${degree.school}（${degree.year}年） </#list> 实现代码package com.ruoyi.system.utils; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import java.io.*; import java.util.Map; /** * @Program: RuoYi-master * @ClassName: GenerateHtmlUtil * @author: zhouzihao * @date: 2025年2月20日, 0020 下午 03:33 * @version: 1.0.0 * @Description: * @Time： 2025-02-20 15:33 */ public class GenerateHtmlUtil { // 模板路径 private static final String templatesPath = "/templates"; // 模板文件 private static final String templatesFile = "template.html"; public static void generateHtml(Map<String, Object> data, String filePName) { // 配置 Freemarker Configuration cfg = new Configuration(Configuration.VERSION_2_3_30); cfg.setClassForTemplateLoading(GenerateHtmlUtil.class, templatesPath); // 获取 HTML 模板 try { Template template = cfg.getTemplate(templatesFile); // 5. 创建输出流 try (Writer writer = new FileWriter(new File(filePName))) { // 6. 合并模板和数据模型，生成 HTML 文件 template.process(data, writer); System.out.println("HTML file generated successfully!"); } catch (IOException | TemplateException e) { e.printStackTrace(); } } catch (IOException e) { throw new RuntimeException(e); } } }

Java 使用docx4j+ Freemarker生成word文档
type: item
date: 2025-02-20T10:18:00+08:00
description: Java 使用docx4j+ Freemarker生成word文档技术方案java 1.8 + docx4j + Freemarkermaven依赖<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>6.1.2</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-ImportXHTML</artifactId> <version>6.1.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.36</version> </dependency>创建模板文件，保存至src/main/resources/templates/，文件名为template.html ${name}的简历 ${name}的简历 联系信息 邮箱: ${email} 电话: ${phone} 工作经历 <#list experience as job> ${job.position} 在 ${job.company}（${job.startYear} - ${job.endYear}） ${job.description} </#list> 教育背景 <#list education as degree> ${degree.degree} ${degree.school}（${degree.year}年） </#list> 实现代码package com.ruoyi.system.utils; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.docx4j.Docx4J; import org.docx4j.convert.in.xhtml.XHTMLImporterImpl; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.exceptions.InvalidFormatException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.Map; /** * @Program: RuoYi-master * @ClassName: GenerateWord * @author: zhouzihao * @date: 2025年2月20日, 0020 下午 03:25 * @version: 1.0.0 * @Description: * @Time： 2025-02-20 15:25 */ public class GenerateWordUtil { // 模板路径 private static final String templatesPath = "/templates"; // 模板文件 private static final String templatesFile = "template.html"; public static void generateWord(Map<String, Object> data, String filePName) { // 配置 Freemarker Configuration cfg = new Configuration(Configuration.VERSION_2_3_30); …

JAVA自动化编写单元测试
type: item
date: 2025-01-17T10:53:00+08:00
description: 编写单元测试工具：idea插件：Squaretest一、安装Squaretest插件File——>Settings——>Plugins，搜索Squaretest，然后install就好了，插件安装完成后需要重启一下二、激活​ 免费试用三十天三、使用教程1. 打开你的类，光标定位到代码里，右击鼠标选择Generate…2. 选择生成单元测试3. 选择第二项后就会弹出一个框看下面这里它自动会识别出当前类需要Mock的成员变量，直接点ok生成后效果4. 生成后文件路径5. 查询覆盖率