This commit is contained in:
777
2025-12-22 13:52:16 +08:00
parent 35fe4b005f
commit 20457a3801
4 changed files with 156 additions and 95 deletions

View File

@@ -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, '');

View File

@@ -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, '');

View File

@@ -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);
}

View File

@@ -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());
}
}
}
}