Session.RemoveAll()的问题

本文解决了一个在Global.asax文件中Session_End方法被调用两次的问题。问题出现的原因是在Session_End方法内误用了Session.RemoveAll()方法,导致Session状态结束时额外触发了一次Session_End。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前在Global.asax中写结束时候需要执行的代码,但是遇到了问题,就是会执行两次Session_End(object sender, EventArgs e),最后找到原因,就是我在写这段代码的时候,最后结束多余增加了一个Session.RemoveAll(),这样会第二次执行Session_End函数,哈哈,多此一举,记住记住!

资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
package com.example.demo.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.dao.entity.WExecuteHost; import com.example.demo.dao.entity.WPersonalHost; import com.example.demo.dao.mapper.WExecuteHostMapper; import com.example.demo.service.WExecuteHostService; import com.example.demo.service.WPersonalHostService; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.*; @Service public class WExecuteHostServiceImpl extends ServiceImpl<WExecuteHostMapper, WExecuteHost> implements WExecuteHostService { @Autowired private WPersonalHostService wPersonalHostService; @Data private static class UserSession { private Process cmdProcess; private volatile boolean isProcessRunning; private String sessionId; private String logFilePath; private final Queue<String> logBuffer = new ConcurrentLinkedQueue<>(); private static final int BATCH_SIZE = 50; // 批量写入阈值 } private final Map<String, UserSession> userSessions = new ConcurrentHashMap<>(); private final SimpMessagingTemplate messagingTemplate; // 异步日志写入线程池 private static final ExecutorService LOG_WRITER_POOL = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNamePrefix("log-writer-").build()); // 日志刷新调度器 private static final ScheduledExecutorService LOG_FLUSH_SCHEDULER = Executors.newSingleThreadScheduledExecutor(); public WExecuteHostServiceImpl(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; startLogFlushService(); } // 初始化日志刷新服务 private void startLogFlushService() { LOG_FLUSH_SCHEDULER.scheduleAtFixedRate(() -> { userSessions.values().forEach(session -> { if (!session.getLogBuffer().isEmpty()) { List<String> batch = CollUtil.newArrayList(session.getLogBuffer()); session.getLogBuffer().clear(); asyncWriteLog(session.getLogFilePath(), String.join("\n", batch)); } }); }, 0, 1, TimeUnit.SECONDS); } @Override public String pipList() { // 示例实现:返回模拟的管道列表(实际需根据业务需求实现) return "[]"; } @Override public void executeCommand(String command, String executeHost, String userId) { // 1. 权限校验 if (!validateUserHost(userId, executeHost)) { sendError("无权访问该主机", userId); return; } // 2. 初始化日志系统 UserSession session = userSessions.computeIfAbsent(userId, key -> { if (userSessions.containsKey(userId)) { sendError("已有命令执行中,请等待完成", userId); return null; } return createNewSession(userId, executeHost); }); if (session == null) return; // 3. 命令处理 if ("ABORT".equalsIgnoreCase(command)) { handleAbort(userId); return; } // 写入命令到日志 session.getLogBuffer().offer( "\n———————— 执行时间 " + DateUtil.now() + " ————————\n[<<Input<<] " + command); // 发送命令到进程 IoUtil.writeUtf8(session.getCmdProcess().getOutputStream(), true,command + "\n"); } private boolean validateUserHost(String userId, String executeHost) { LambdaQueryWrapper<WPersonalHost> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(WPersonalHost::getP13, userId) .eq(WPersonalHost::getExecuteHost, executeHost) .eq(WPersonalHost::getState, "在线"); return wPersonalHostService.getOne(queryWrapper) != null; } private UserSession createNewSession(String userId, String executeHost) { try { UserSession session = new UserSession(); session.setSessionId(UUID.randomUUID().toString()); session.setLogFilePath(initLogFile(userId, executeHost)); // 启动CMD进程(带唯一标题) ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/k", "title " + generateUniqueTitle(userId)); session.setCmdProcess(pb.redirectErrorStream(true).start()); // 启动输出监听线程 startOutputThread(session, userId); return session; } catch (IOException e) { sendError("进程启动失败: " + e.getMessage(), userId); return null; } } private String initLogFile(String userId, String executeHost) { // 1. 构建基础路径(使用File.separator) String baseDir = FileUtil.normalize( System.getProperty("user.dir") + File.separator + "command-log"); // 2. 构建安全路径(统一使用File.separator) String safePath = FileUtil.normalize( baseDir + File.separator + userId + File.separator + executeHost + File.separator + "项目名称"); // 3. 安全校验(现在路径分隔符一致) if (!safePath.startsWith(baseDir)) { throw new SecurityException("非法日志路径: " + safePath); } // 4. 创建目录(自动处理路径分隔符) FileUtil.mkdir(safePath); // 5. 生成日志文件 String logFileName = DateUtil.today() + ".log"; return FileUtil.touch(safePath + File.separator + logFileName).getAbsolutePath(); } private void startOutputThread(UserSession session, String userId) { new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(session.getCmdProcess().getInputStream(), "GBK"))) { String line; while ((line = reader.readLine()) != null) { // WebSocket推送 messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, line); // 日志缓冲 session.getLogBuffer().offer(line); if (session.getLogBuffer().size() >= UserSession.BATCH_SIZE) { asyncWriteLog(session.getLogFilePath(), String.join("\n", session.getLogBuffer())); session.getLogBuffer().clear(); } } } catch (Exception e) { sendError("输出流异常: " + e.getMessage(), userId); } finally { cleanupSession(userId); } }).start(); } private void asyncWriteLog(String logFilePath, String content) { CompletableFuture.runAsync(() -> { try { FileUtil.appendUtf8String(content + System.lineSeparator(), logFilePath); } catch (Exception e) { System.err.println("日志写入失败: " + e.getMessage()); } }, LOG_WRITER_POOL); } private void cleanupSession(String userId) { UserSession session = userSessions.remove(userId); if (session != null) { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } } catch (Exception ignored) {} } } @PreDestroy public void cleanup() { LOG_FLUSH_SCHEDULER.shutdown(); LOG_WRITER_POOL.shutdown(); userSessions.forEach((userId, session) -> { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } } catch (Exception ignored) {} }); userSessions.clear(); } public void handleAbort(String userId) { UserSession session = userSessions.get(userId); if (session != null && session.isProcessRunning()) { session.getCmdProcess().destroyForcibly(); sendError("⏹ 用户命令已终止", userId); } else { sendError("⏹ 无执行中命令", userId); } } private void sendError(String message, String userId) { try { messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "❌ " + message); } catch (Exception ignored) { } } private String generateUniqueTitle(String userId) { return "CMD_SESSION_" + userId + "_" + System.currentTimeMillis(); } }如何让输入的输出的都是GBK
06-07
<think>由于Windows命令行默认使用GBK编码,而Java默认使用UTF-8,因此需要确保输入输出流都使用GBK编码。在当前的代码中,输入流(从进程读取)已经使用GBK编码(在startOutputThread方法中,InputStreamReader指定了"GBK")。但是,在写入命令到进程的输出流时,使用了IoUtil.writeUtf8,这会将命令以UTF-8编码写入,而命令行期望的是GBK,这会导致乱码。因此,需要将命令的写入也改为GBK编码。同时,由于我们使用了hutool的IoUtil,我们可以使用write方法,并指定Charset参数。修改点:1.将命令写入从UTF-8改为GBK2.在创建BufferedReader时已经使用了GBK,所以读取没问题。具体修改代码:在executeCommand方法中,将:IoUtil.writeUtf8(session.getCmdProcess().getOutputStream(),true,command+"\n");改为:IoUtil.write(session.getCmdProcess().getOutputStream(),StandardCharsets.GBK,true,command+"\n");但是,由于我们之前已经使用了hutool的IoUtil,并且GBK编码在Windows上是常见的,所以这样修改。另外,在创建输出流的写入器时,我们也可以保持一致地使用GBK。但是,在当前的createNewSession方法中,我们没有为命令写入单独创建线程,而是每次直接写入。因此,只需要在写入命令时指定GBK即可。同时,我们注意到在日志文件中,我们使用UTF-8写入(FileUtil.appendUtf8String),但是命令行输出的内容可能是GBK编码的,而日志文件又用UTF-8写入,这会导致乱码。因此,我们需要将日志文件也改为用GBK编码写入,以保持和命令行输出一致。修改日志写入方法asyncWriteLog:将FileUtil.appendUtf8String改为使用FileUtil.appendString,并指定编码为GBK。但是注意:我们的日志文件不仅包含命令行的输出(GBK),还包含我们自己添加的一些说明(比如执行时间等,是UTF-8写的?)。为了统一,我们应该都使用GBK。因此,修改asyncWriteLog方法中的写入编码为GBK。然而,我们添加的说明(例如在executeCommand方法中写入的日志行)也是用UTF-8?不,我们在写入日志的时候是直接使用字符串,然后转换为GBK写入文件。建议:整个日志文件都使用GBK编码。所以,修改两个地方:1.在executeCommand方法中,写入命令到日志时,我们只是将字符串加入队列,最终在asyncWriteLog方法中统一以GBK编码写入文件。2.在asyncWriteLog方法中,使用GBK编码写入文件。修改asyncWriteLog方法:将:FileUtil.appendUtf8String(content+System.lineSeparator(),logFilePath);改为:FileUtil.appendString(content+System.lineSeparator(),logFilePath,StandardCharsets.GBK);另外,在initLogFile方法中创建文件时,我们不需要指定编码,因为后面写入时指定。但是,我们也要注意:我们自己添加的日志内容(如执行时间)是Java字符串,在写入文件时使用GBK编码,而命令行输出的内容也是GBK,所以整个文件都是GBK编码,不会乱码。但是,在WebSocket传输时,我们发送的是字符串(line),而Java内部是Unicode,所以只要在读取命令行输出时正确使用GBK,那么字符串在内存中就是正确的,然后通过WebSocket发送,前端如果使用UTF-8接收也是可以的(因为已经正确转码)。所以,总结修改点为:1.命令写入使用GBK2.日志文件写入使用GBK代码修改:在executeCommand方法中,将命令写入进程的输出流改为GBK:将:IoUtil.writeUtf8(session.getCmdProcess().getOutputStream(),true,command+"\n");改为:IoUtil.write(session.getCmdProcess().getOutputStream(),StandardCharsets.GBK,true,command+"\n");在asyncWriteLog方法中,将日志写入文件的编码改为GBK:将:FileUtil.appendUtf8String(content+System.lineSeparator(),logFilePath);改为:FileUtil.appendString(content+System.lineSeparator(),logFilePath,StandardCharsets.GBK);另外,在initLogFile方法中,我们创建的文件并没有指定编码,因此后面以什么编码写入就是什么编码。注意:在读取日志文件时,也要使用GBK编码,才能正确显示。同时,还要注意在startOutputThread方法中,读取命令行输出时使用的是GBK,这是正确的。但是,还有一个细节:我们在executeCommand方法中向日志队列添加的内容(例如:“————————执行时间...————————\n[<<Input<<]”+command)也是字符串,这个字符串在Java中是UTF-16,但当我们用GBK编码写入文件时,这些中文字符会被转换为GBK字节。由于这些字符串是英文和中文字符,GBK编码能够处理这些中文字符。但是,如果我们的系统默认编码不是GBK,那么这些字符串在转换为GBK时不会出错吗?不会,因为我们显式指定了用GBK编码。所以,这样修改后,整个日志文件的编码都是GBK,而命令行输入输出也都使用GBK,从而保持一致。但是,如果前端WebSocket希望接收GBK编码的字符串?不,WebSocket传输的是文本,我们发送的字符串是已经正确从GBK字节流转换而来的Unicode字符串,所以前端可以按照UTF-8来解析(因为WebSocket通常是UTF-8)。这个过程是自动的,因为Java内部字符串是Unicode,所以前端用UTF-8接收不会有问题。因此,修改这两个地方即可。另外,考虑到直接使用System.lineSeparator()可能会导致在Windows上是\r\n,而日志文件中我们希望是换行符,其实无所谓,但为了统一,我们可以使用\n,也可以保留System.lineSeparator()。因为Windows记事本也可以识别\n。但是,在Windows上,换行符是\r\n,但是我们在写入时已经使用System.lineSeparator(),所以是合适的。下面修改代码:</think> 要使输入和输出都使用GBK编码,需要确保命令输入流、输出流以及日志文件都统一使用GBK编码。以下是完整的解决方案和代码修改: ### 解决方案与代码修改 #### 1. **确保命令输入使用GBK编码** 修改`executeCommand`方法,使用GBK编码写入命令到进程: ```java @Override public void executeCommand(String command, String executeHost, String userId) { // ... [原有代码] ... // 发送命令到进程 (使用GBK编码) try { session.getLogBuffer().offer( "\n———————— 执行时间 " + DateUtil.now() + " ————————\n[<<Input<<] " + command); // 使用GBK编码写入命令 BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(session.getCmdProcess().getOutputStream(), "GBK")); writer.write(command + "\n"); writer.flush(); } catch (IOException e) { sendError("命令发送失败: " + e.getMessage(), userId); } } ``` #### 2. **强化输出线程的GBK编码** 在`startOutputThread`方法中确保读取使用GBK: ```java private void startOutputThread(UserSession session, String userId) { new Thread(() -> { // 明确指定GBK编码 try (BufferedReader reader = new BufferedReader( new InputStreamReader(session.getCmdProcess().getInputStream(), "GBK"))) { // ... [原有代码] ... } catch (Exception e) { sendError("输出流异常: " + e.getMessage(), userId); } finally { cleanupSession(userId); } }).start(); } ``` #### 3. **确保日志文件使用GBK编码** 修改`asyncWriteLog`方法,指定日志写入使用GBK: ```java private void asyncWriteLog(String logFilePath, String content) { CompletableFuture.runAsync(() -> { try { // 使用GBK编码追加日志 FileUtil.appendString( content + System.lineSeparator(), logFilePath, StandardCharsets.GBK // 明确指定GBK编码 ); } catch (Exception e) { System.err.println("日志写入失败: " + e.getMessage()); } }, LOG_WRITER_POOL); } ``` #### 4. **修复命令进程启动编码** 在`createNewSession`中确保进程启动即使用GBK环境: ```java private UserSession createNewSession(String userId, String executeHost) { try { // ... [原有代码] ... // 设置GBK环境变量 Map<String, String> env = new HashMap<>(); env.put("JAVA_TOOL_OPTIONS", "-Dfile.encoding=GBK"); ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/k", "chcp 65001 & title " + generateUniqueTitle(userId)); pb.environment().putAll(env); pb.redirectErrorStream(true); session.setCmdProcess(pb.start()); // ... [其余代码] ... } } ``` ### 关键修改点说明 1. **输入流GBK编码**: - 使用`OutputStreamWriter`显式指定GBK编码 - 避免使用`IoUtil.writeUtf8`(强制UTF-8编码) 2. **输出流GBK编码**: - 在`InputStreamReader`中明确指定"GBK" - 确保命令行输出的中文正确解码 3. **日志文件GBK存储**: - `FileUtil.appendString`指定`StandardCharsets.GBK` - 防止日志文件出现乱码 4. **进程环境设置**: - `chcp 65001`设置控制台代码页为UTF-8(兼容GBK) - 环境变量强制JVM使用GBK编码 - 标题设置避免中文乱码 ### 中文处理最佳实践 1. **统一编码**: ```java // 始终显式指定编码,避免依赖系统默认 new InputStreamReader(stream, "GBK"); new OutputStreamWriter(stream, "GBK"); ``` 2. **Windows兼容处理**: ```java // 中文路径特殊处理 String safePath = path.replaceAll("[\\\\/:*?\"<>|]", "_"); ``` 3. **编码问题诊断**: ```java // 调试编码问题 System.out.println("系统默认编码:" + Charset.defaultCharset().name()); ``` ### 完整类结构优化 ```java @Service public class WExecuteHostServiceImpl ... { // ... [其他代码] ... private static class UserSession { private Process cmdProcess; private BufferedWriter commandWriter; // 添加专用写入器 } private UserSession createNewSession(...) { // ... // 创建专用的GBK写入器 session.setCommandWriter(new BufferedWriter( new OutputStreamWriter(session.getCmdProcess().getOutputStream(), "GBK") )); } public void executeCommand(...) { // 使用专用写入器发送命令 session.getCommandWriter().write(command + "\n"); session.getCommandWriter().flush(); } } ``` ### 验证方法 1. 发送中文命令测试: ```java executeCommand("echo 中文测试", host, user); ``` 2. 检查: - WebSocket输出是否显示正确中文 - 日志文件内容是否无乱码 - 进程标题是否正常显示中文
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值