nnnn
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475777, 'ip黑名单', '1738084052270563330', '1', 'ipBlack', 'cai/ipBlack/index', 1, 0, 'C', '0', '0', 'cai:ipBlack:list', '#', 'admin', sysdate(), '', null, 'ip黑名单菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475778, 'ip黑名单查询', 1996853670150475777, '1', '#', '', 1, 0, 'F', '0', '0', 'cai:ipBlack:query', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475779, 'ip黑名单新增', 1996853670150475777, '2', '#', '', 1, 0, 'F', '0', '0', 'cai:ipBlack:add', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475780, 'ip黑名单修改', 1996853670150475777, '3', '#', '', 1, 0, 'F', '0', '0', 'cai:ipBlack:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475781, 'ip黑名单删除', 1996853670150475777, '4', '#', '', 1, 0, 'F', '0', '0', 'cai:ipBlack:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853670150475782, 'ip黑名单导出', 1996853670150475777, '5', '#', '', 1, 0, 'F', '0', '0', 'cai:ipBlack:export', '#', 'admin', sysdate(), '', null, '');
|
||||
@@ -1,19 +0,0 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570817, 'ip访问记录', '1738084052270563330', '1', 'ipRecord', 'cai/ipRecord/index', 1, 0, 'C', '0', '0', 'cai:ipRecord:list', '#', 'admin', sysdate(), '', null, 'ip访问记录菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570818, 'ip访问记录查询', 1996853672289570817, '1', '#', '', 1, 0, 'F', '0', '0', 'cai:ipRecord:query', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570819, 'ip访问记录新增', 1996853672289570817, '2', '#', '', 1, 0, 'F', '0', '0', 'cai:ipRecord:add', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570820, 'ip访问记录修改', 1996853672289570817, '3', '#', '', 1, 0, 'F', '0', '0', 'cai:ipRecord:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570821, 'ip访问记录删除', 1996853672289570817, '4', '#', '', 1, 0, 'F', '0', '0', 'cai:ipRecord:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
values(1996853672289570822, 'ip访问记录导出', 1996853672289570817, '5', '#', '', 1, 0, 'F', '0', '0', 'cai:ipRecord:export', '#', 'admin', sysdate(), '', null, '');
|
||||
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,12 +34,12 @@ public class LoginLogController extends BaseController {
|
||||
@SaCheckPermission("cai:loginLog:list")
|
||||
@GetMapping("/list")
|
||||
public R<List<LoginLogInfo>> list(String mobile) {
|
||||
// List<String> list = LoginLogByFileUtil.getLog(mobile);
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("2025-12-21 08:31:43 [XNIO-1 task-193] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.45.250;url=/api/auth/login;method=POST;title=登陆;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result=null;exception=您的账号已被封禁;;StartTime:2025-12-21 08:31:43,EndTime:2025-12-21 08:31:43,CostTime:5ms");
|
||||
list.add("2025-12-21 09:14:34 [XNIO-1 task-8] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.44.24;url=/api/auth/login;method=POST;title=登陆;currentUserId=44554;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result=null;exception=您的账号已被封禁;;StartTime:2025-12-21 09:14:34,EndTime:2025-12-21 09:14:34,CostTime:7ms");
|
||||
list.add("2025-12-21 09:55:08 [XNIO-1 task-193] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.44.156;url=/api/auth/login;method=POST;title=登陆;currentUserId=6501;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result={\"code\":200,\"msg\":\"操作成功\",\"data\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhcHBfdXNlcjo2NTAxIiwicm5TdHIiOiJYR3pFcngxOWxXcm1MSEZhMGRhV0dDWFNERFhPaDBGeiIsInVzZXJJZCI6NjUwMX0.Coy1DgKMgtyEIpEpLKrvm_3w8SEjeKujfaTKu3l9AyI\",\"userInfo\":{\"userId\":6501,\"inviteId\":4387,\"type\":0,\"usercode\":\"6953\",\"nickname\":\"用户6953\",\"mobile\":\"13588246608\",\"avatar\":\"images/avatar/man.png\",\"avatarState\":0,\"gender\":2,\"birthday\":null,\"age\":18,\"cityId\":0,\"city\":null,\"isAnchor\":0,\"openVideoStatus\":1,\"status\":0,\"finishStatus\":0,\"imToken\":\"716d4d9bbe1b4441b88065b6bc0c543d\",\"userAccount\":{\"userId\":6501,\"coin\":2537,\"incomeCoin\":0},\"userCount\":{\"userId\":6501,\"newFansCount\":266,\"newVisitorCount\":5302,\"fansCount\":36,\"followCount\":24,\"footCount\":6691,\"visitorCount\":5302}}}};;StartTime:2025-12-21 09:55:07,EndTime:2025-12-21 09:55:08,CostTime:148ms");
|
||||
list.add("2025-12-21 10:30:21 [XNIO-1 task-46] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.45.234;url=/api/auth/login;method=POST;title=登陆;currentUserId=6501;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result={\"code\":200,\"msg\":\"操作成功\",\"data\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhcHBfdXNlcjo2NTAxIiwicm5TdHIiOiI4U2dKZ3hiWVF0VHpjNmhSRzdXV3NYSGMyaGFQYmJ3eCIsInVzZXJJZCI6NjUwMX0.2KrY8jHwHl67A25JtRCmAldnOvSpcU1mtnYlcUFLhy0\",\"userInfo\":{\"userId\":6501,\"inviteId\":4387,\"type\":0,\"usercode\":\"6953\",\"nickname\":\"用户6953\",\"mobile\":\"13588246608\",\"avatar\":\"images/avatar/man.png\",\"avatarState\":0,\"gender\":2,\"birthday\":null,\"age\":18,\"cityId\":0,\"city\":null,\"isAnchor\":0,\"openVideoStatus\":1,\"status\":0,\"finishStatus\":0,\"imToken\":\"716d4d9bbe1b4441b88065b6bc0c543d\",\"userAccount\":{\"userId\":6501,\"coin\":2517,\"incomeCoin\":0},\"userCount\":{\"userId\":6501,\"newFansCount\":266,\"newVisitorCount\":5302,\"fansCount\":36,\"followCount\":24,\"footCount\":6692,\"visitorCount\":5302}}}};;StartTime:2025-12-21 10:30:21,EndTime:2025-12-21 10:30:21,CostTime:148ms");
|
||||
List<String> list = LoginLogByFileUtil.getLog(mobile);
|
||||
// List<String> list = new ArrayList<>();
|
||||
// list.add("2025-12-21 08:31:43 [XNIO-1 task-193] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.45.250;url=/api/auth/login;method=POST;title=登陆;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result=null;exception=您的账号已被封禁;;StartTime:2025-12-21 08:31:43,EndTime:2025-12-21 08:31:43,CostTime:5ms");
|
||||
// list.add("2025-12-21 09:14:34 [XNIO-1 task-8] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.44.24;url=/api/auth/login;method=POST;title=登陆;currentUserId=44554;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result=null;exception=您的账号已被封禁;;StartTime:2025-12-21 09:14:34,EndTime:2025-12-21 09:14:34,CostTime:7ms");
|
||||
// list.add("2025-12-21 09:55:08 [XNIO-1 task-193] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.44.156;url=/api/auth/login;method=POST;title=登陆;currentUserId=6501;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result={\"code\":200,\"msg\":\"操作成功\",\"data\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhcHBfdXNlcjo2NTAxIiwicm5TdHIiOiJYR3pFcngxOWxXcm1MSEZhMGRhV0dDWFNERFhPaDBGeiIsInVzZXJJZCI6NjUwMX0.Coy1DgKMgtyEIpEpLKrvm_3w8SEjeKujfaTKu3l9AyI\",\"userInfo\":{\"userId\":6501,\"inviteId\":4387,\"type\":0,\"usercode\":\"6953\",\"nickname\":\"用户6953\",\"mobile\":\"13588246608\",\"avatar\":\"images/avatar/man.png\",\"avatarState\":0,\"gender\":2,\"birthday\":null,\"age\":18,\"cityId\":0,\"city\":null,\"isAnchor\":0,\"openVideoStatus\":1,\"status\":0,\"finishStatus\":0,\"imToken\":\"716d4d9bbe1b4441b88065b6bc0c543d\",\"userAccount\":{\"userId\":6501,\"coin\":2537,\"incomeCoin\":0},\"userCount\":{\"userId\":6501,\"newFansCount\":266,\"newVisitorCount\":5302,\"fansCount\":36,\"followCount\":24,\"footCount\":6691,\"visitorCount\":5302}}}};;StartTime:2025-12-21 09:55:07,EndTime:2025-12-21 09:55:08,CostTime:148ms");
|
||||
// list.add("2025-12-21 10:30:21 [XNIO-1 task-46] INFO c.ruoyi.framework.aspectj.LogAspect - record logs:ip=172.56.45.234;url=/api/auth/login;method=POST;title=登陆;currentUserId=6501;user-agent=yan yu/1.0.6 (iPhone; iOS 26.2; Scale/3.00);params={\"username\":\"13588246608\"};result={\"code\":200,\"msg\":\"操作成功\",\"data\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhcHBfdXNlcjo2NTAxIiwicm5TdHIiOiI4U2dKZ3hiWVF0VHpjNmhSRzdXV3NYSGMyaGFQYmJ3eCIsInVzZXJJZCI6NjUwMX0.2KrY8jHwHl67A25JtRCmAldnOvSpcU1mtnYlcUFLhy0\",\"userInfo\":{\"userId\":6501,\"inviteId\":4387,\"type\":0,\"usercode\":\"6953\",\"nickname\":\"用户6953\",\"mobile\":\"13588246608\",\"avatar\":\"images/avatar/man.png\",\"avatarState\":0,\"gender\":2,\"birthday\":null,\"age\":18,\"cityId\":0,\"city\":null,\"isAnchor\":0,\"openVideoStatus\":1,\"status\":0,\"finishStatus\":0,\"imToken\":\"716d4d9bbe1b4441b88065b6bc0c543d\",\"userAccount\":{\"userId\":6501,\"coin\":2517,\"incomeCoin\":0},\"userCount\":{\"userId\":6501,\"newFansCount\":266,\"newVisitorCount\":5302,\"fansCount\":36,\"followCount\":24,\"footCount\":6692,\"visitorCount\":5302}}}};;StartTime:2025-12-21 10:30:21,EndTime:2025-12-21 10:30:21,CostTime:148ms");
|
||||
List<LoginLogInfo> collect = list.stream().map(LoginLogInfo::new).collect(Collectors.toList());
|
||||
return R.ok(collect);
|
||||
}
|
||||
|
||||
@@ -7,71 +7,171 @@ import org.apache.commons.exec.environment.EnvironmentUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 登录日志查询工具类(Linux环境专用,UTF-8编码日志)
|
||||
* 功能:执行Linux命令查询指定手机号的登录日志,最多返回10条
|
||||
* 核心优化:解决换行符/引号/流覆盖问题,适配UTF-8编码,增强安全&健壮性
|
||||
*/
|
||||
@Slf4j
|
||||
public class LoginLogByFileUtil {
|
||||
private static final String LOG_FILE_PATH = "/home/server/api/logs/sys-console.log";
|
||||
private static final int COMMAND_TIMEOUT_MS = 5000;
|
||||
private static final Charset LOG_FILE_CHARSET = StandardCharsets.UTF_8; // 确认日志为UTF-8编码
|
||||
private static final int MAX_LOG_LINES = 10;
|
||||
|
||||
public static List<String> getLog(String mobile){
|
||||
// 1. 定义命令(带管道的Linux命令,需用/bin/sh -c包裹)
|
||||
String commandFormat = "tac /home/server/api/logs/sys-console.log | grep \"%s\" | grep -m 10 \"auth/login\"";
|
||||
String command = String.format(commandFormat, mobile);
|
||||
CommandLine cmdLine = CommandLine.parse("/bin/sh -c " + command); // 自动处理参数分隔
|
||||
/**
|
||||
* 获取指定手机号的登录日志(按时间倒序,最多10条)
|
||||
* @param mobile 手机号(支持纯数字/带+86等格式)
|
||||
* @return 非空日志行列表(无数据返回空列表,不会返回null)
|
||||
*/
|
||||
public static List<String> getLog(String mobile) {
|
||||
// 1. 入参校验(避免无效执行)
|
||||
if (mobile == null || mobile.trim().isEmpty()) {
|
||||
log.warn("查询登录日志失败:手机号为空");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String cleanMobile = mobile.trim();
|
||||
|
||||
// 2. 配置执行器(设置超时、流处理)
|
||||
DefaultExecutor executor = new DefaultExecutor();
|
||||
executor.setExitValue(0); // 期望的退出码(0表示成功)
|
||||
// 2. 构建安全的管道命令(防注入+引号兼容)
|
||||
String pipeCommand = buildSafePipeCommand(cleanMobile);
|
||||
CommandLine cmdLine = buildCommandLine(pipeCommand);
|
||||
|
||||
// 设置进程超时(5秒,适配你的快速执行场景)
|
||||
ExecuteWatchdog watchdog = new ExecuteWatchdog(5000); // 5000ms = 5s
|
||||
executor.setWatchdog(watchdog);
|
||||
|
||||
// 3. 处理输出(ByteArrayOutputStream存储10行结果,或用PumpStreamHandler实时处理)
|
||||
// 场景1:存储结果到内存(仅10行,内存无压力)
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, outputStream); // 合并标准输出/错误流
|
||||
executor.setStreamHandler(streamHandler);
|
||||
|
||||
// 场景2:实时处理每行输出(推荐,避免存储)
|
||||
// PumpStreamHandler streamHandler = new PumpStreamHandler(new LogOutputStream() {
|
||||
// @Override
|
||||
// protected void processLine(String line, int level) {
|
||||
// // 实时处理每行数据(如输出到前端、写入日志)
|
||||
// System.out.println(line);
|
||||
// }
|
||||
// });
|
||||
// executor.setStreamHandler(streamHandler);
|
||||
// 3. 初始化执行器&流处理(分开捕获stdout/stderr,避免覆盖)
|
||||
DefaultExecutor executor = initExecutor();
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
||||
executor.setStreamHandler(new PumpStreamHandler(stdout, stderr));
|
||||
|
||||
try {
|
||||
// 4. 执行命令(环境变量使用系统默认,可自定义)
|
||||
// 4. 执行命令(获取系统默认环境变量)
|
||||
Map<String, String> env = EnvironmentUtils.getProcEnvironment();
|
||||
executor.execute(cmdLine, env);
|
||||
|
||||
// 5. 解析结果(场景1的方式)
|
||||
String result = outputStream.toString(StandardCharsets.UTF_8.name());
|
||||
String[] split = result.split(System.lineSeparator());
|
||||
List<String> lines = Arrays.stream(split).collect(Collectors.toList());
|
||||
System.out.println("匹配的行数:" + lines.size());
|
||||
lines.forEach(System.out::println);
|
||||
int exitCode = executor.execute(cmdLine, env);
|
||||
// 5. 解析结果(核心修复:UTF-8编码+Linux换行符+过滤)
|
||||
return parseCommandResult(stdout, stderr, exitCode);
|
||||
|
||||
} catch (ExecuteException e) {
|
||||
if (watchdog.killedProcess()) {
|
||||
log.error("命令执行超时,已终止进程");
|
||||
} else {
|
||||
log.error("命令执行失败,退出码:" + e.getExitValue());
|
||||
}
|
||||
log.error(e.getMessage(),e);
|
||||
throw new BaseException("命令执行失败");
|
||||
handleExecuteException(e, executor.getWatchdog());
|
||||
} catch (IOException e) {
|
||||
log.error("IO异常:" + e.getMessage());
|
||||
log.error(e.getMessage(),e);
|
||||
throw new BaseException("IO异常");
|
||||
log.error("日志查询IO异常:手机号={}, 异常信息={}", cleanMobile, e.getMessage(), e);
|
||||
throw new BaseException("日志查询失败:系统IO异常");
|
||||
} finally {
|
||||
// 6. 资源释放(避免内存泄漏)
|
||||
closeStream(stdout);
|
||||
closeStream(stderr);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建安全的管道命令(防注入+适配grep固定字符串匹配)
|
||||
*/
|
||||
private static String buildSafePipeCommand(String mobile) {
|
||||
// 转义手机号特殊字符(防注入)+ 用单引号包裹(适配Shell)
|
||||
String escapedMobile = escapeShellSpecialChars(mobile);
|
||||
// 命令逻辑:tac倒序读文件 → grep -F固定字符串匹配(避免正则干扰)→ 限制行数
|
||||
return String.format("grep 'auth/login.*%s' %s | tail -10 | tac", escapedMobile,LOG_FILE_PATH);
|
||||
// return String.format(
|
||||
// "tac %s | grep -F '%s' | grep -m %d 'auth/login'",
|
||||
// LOG_FILE_PATH, escapedMobile, MAX_LOG_LINES
|
||||
// );
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建CommandLine(避免parse方法的引号解析问题)
|
||||
*/
|
||||
private static CommandLine buildCommandLine(String pipeCommand) {
|
||||
CommandLine cmdLine = new CommandLine("/bin/sh");
|
||||
cmdLine.addArgument("-c");
|
||||
cmdLine.addArgument(pipeCommand, false); // 关键:不自动转义,保持命令原样
|
||||
return cmdLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化执行器(超时+退出码兼容)
|
||||
*/
|
||||
private static DefaultExecutor initExecutor() {
|
||||
DefaultExecutor executor = new DefaultExecutor();
|
||||
// 兼容grep无匹配的退出码1(仅0/1视为正常)
|
||||
executor.setExitValues(new int[]{0, 1});
|
||||
// 设置超时看门狗
|
||||
ExecuteWatchdog watchdog = new ExecuteWatchdog(COMMAND_TIMEOUT_MS);
|
||||
executor.setWatchdog(watchdog);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析命令结果(核心:UTF-8编码+Linux换行符\n分割)
|
||||
*/
|
||||
private static List<String> parseCommandResult(ByteArrayOutputStream stdout,
|
||||
ByteArrayOutputStream stderr,
|
||||
int exitCode) {
|
||||
// 打印错误流(排查问题用,不影响结果)
|
||||
String stderrStr = new String(stderr.toByteArray(), LOG_FILE_CHARSET).trim();
|
||||
if (!stderrStr.isEmpty()) {
|
||||
log.warn("命令执行错误流输出:{}", stderrStr);
|
||||
}
|
||||
|
||||
// 解析标准输出(核心:UTF-8编码 + 强制用\n分割Linux输出)
|
||||
String stdoutStr = new String(stdout.toByteArray(), LOG_FILE_CHARSET);
|
||||
// 分割+过滤:强制用\n分割(Linux命令输出固定是\n),仅过滤纯空行
|
||||
List<String> logLines = Arrays.stream(stdoutStr.split("\n"))
|
||||
.map(String::trim)
|
||||
.filter(line -> !line.isEmpty()) // 仅过滤纯空行,保留有效内容
|
||||
.collect(Collectors.toList());
|
||||
// 退出码1表示无匹配结果,返回空列表
|
||||
if (exitCode == 1 && logLines.isEmpty()) {
|
||||
log.info("未查询到匹配的登录日志");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return logLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义Shell特殊字符(防命令注入)
|
||||
*/
|
||||
private static String escapeShellSpecialChars(String str) {
|
||||
if (str == null) return "";
|
||||
// 转义Shell中特殊字符:单引号/反斜杠/分号/管道符等
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("`", "\\`")
|
||||
.replace("$", "\\$")
|
||||
.replace(";", "\\;")
|
||||
.replace("|", "\\|")
|
||||
.replace("<", "\\<")
|
||||
.replace(">", "\\>");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理命令执行异常
|
||||
*/
|
||||
private static void handleExecuteException(ExecuteException e, ExecuteWatchdog watchdog) {
|
||||
if (watchdog != null && watchdog.killedProcess()) {
|
||||
log.error("命令执行超时({}ms),已终止进程", COMMAND_TIMEOUT_MS);
|
||||
throw new BaseException("日志查询超时:请稍后重试");
|
||||
} else {
|
||||
log.error("命令执行失败,退出码:{},异常信息:{}",
|
||||
e.getExitValue(), e.getMessage(), e);
|
||||
throw new BaseException("日志查询失败:命令执行异常(退出码:" + e.getExitValue() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭字节输出流(静默关闭,避免IO异常)
|
||||
*/
|
||||
private static void closeStream(ByteArrayOutputStream stream) {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("关闭字节输出流失败,异常信息:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user