yzx hai 3 días
pai
achega
4217e994a6

+ 34 - 38
ruoyi-admin/src/main/java/com/ruoyi/cc/service/impl/FsConfServiceImpl.java

@@ -569,50 +569,46 @@ public class FsConfServiceImpl implements IFsConfService {
     @Override
     public String getLogs(String uuid, String logFile, String logType) {
         log.info(logFile);
-        // 使用ProcessBuilder来正确处理命令字符串
+        // SECURITY: Use Java file reading instead of shell commands to prevent command injection.
+        // Previously used "sh -c cat ... | grep '" + uuid + "'" which allowed RCE via shell metacharacters.
         String ansiRegex = "\u001b\\[[;\\d]*m"; // 匹配 ANSI 转义序列的正则表达式
-        List<String> commands = new ArrayList<>();
-        commands.add("sh");
-        commands.add("-c");
-        if (StringUtils.isBlank(uuid)) {
-            commands.add("tail -n 20 " + logFile);
-        } else {
-            commands.add("cat " + logFile + " | grep '" + uuid + "'");
-        }
-        log.info(StringUtils.join(commands.toArray(), " "));
-        String logs = "";
+        StringBuilder logs = new StringBuilder();
         try {
-
-            // 使用ProcessBuilder执行命令
-            ProcessBuilder processBuilder = new ProcessBuilder(commands);
-            Process process = processBuilder.start();
-
-            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
-
-            String line;
-            while ((line = reader.readLine()) != null) {
-                if ("fs".equals(logType)) {
-                    try{
-                        logs += JSONObject.parseObject(line).getString("log").replaceAll(ansiRegex, "") ;
-                    }catch (Exception e) {
-                        log.error(ExceptionUtil.getExceptionMessage(e));
+            java.io.File file = new java.io.File(logFile);
+            if (!file.exists() || !file.isFile()) {
+                log.warn("log file not found: {}", logFile);
+                return "";
+            }
+            if (StringUtils.isBlank(uuid)) {
+                // Read last 20 lines without shell
+                java.util.List<String> allLines = java.nio.file.Files.readAllLines(java.nio.file.Paths.get(logFile));
+                int start = Math.max(0, allLines.size() - 20);
+                for (int i = start; i < allLines.size(); i++) {
+                    logs.append(allLines.get(i)).append("\r\n");
+                }
+            } else {
+                // Filter lines containing uuid without shell grep
+                try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(logFile))) {
+                    String line;
+                    while ((line = br.readLine()) != null) {
+                        if (line.contains(uuid)) {
+                            if ("fs".equals(logType)) {
+                                try {
+                                    logs.append(JSONObject.parseObject(line).getString("log").replaceAll(ansiRegex, ""));
+                                } catch (Exception e) {
+                                    log.error(ExceptionUtil.getExceptionMessage(e));
+                                }
+                            } else {
+                                logs.append(line).append("\r\n");
+                            }
+                        }
                     }
-                } else {
-                    logs += line + "\r\n";
                 }
             }
-
-            // 读取命令的错误输出
-            BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
-            String errorLine;
-            while ((errorLine = errorReader.readLine()) != null) {
-                log.info(errorLine);
-            }
-            process.waitFor(); // 等待进程结束并获取退出值
-        } catch (IOException | InterruptedException e) {
-            e.printStackTrace();
+        } catch (java.io.IOException e) {
+            log.error("Failed to read log file: {}", logFile, e);
         }
-        return logs;
+        return logs.toString();
     }
 
     @Override

+ 17 - 0
sql/easycallcenter365.sql

@@ -5435,3 +5435,20 @@ INSERT  INTO `sys_post`(`post_id`,`post_code`,`post_name`,`post_sort`,`status`,`
 INSERT  INTO `sys_post`(`post_id`,`post_code`,`post_name`,`post_sort`,`status`,`create_by`,`create_time`,`update_by`,`update_time`,`remark`) VALUES (-1,'_agent','坐席',5,'0','admin','2025-09-03 17:41:43','',NULL,NULL);
 
 ALTER TABLE `cc_llm_agent_account` ADD COLUMN concurrent_num INT(3) DEFAULT 1 COMMENT '模型并发数';
+
+-- ----------------------------
+-- Table structure for cc_call_round
+-- ----------------------------
+CREATE TABLE `cc_call_round` (
+  `id` varchar(80) NOT NULL COMMENT 'primary key: uuid-roundNo',
+  `uuid` varchar(50) NOT NULL DEFAULT '' COMMENT 'call UUID',
+  `round_no` int NOT NULL DEFAULT '0' COMMENT 'round number (1-based)',
+  `asr_text` varchar(500) NOT NULL DEFAULT '' COMMENT 'ASR recognized text',
+  `llm_text` varchar(500) NOT NULL DEFAULT '' COMMENT 'LLM response text',
+  `asr_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'ASR duration (ms)',
+  `llm_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'LLM duration (ms)',
+  `tts_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'TTS playback duration (ms)',
+  `create_time` bigint NOT NULL DEFAULT '0' COMMENT 'record create timestamp',
+  PRIMARY KEY (`id`),
+  KEY `idx_uuid` (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='per-round call timing statistics';