1 /* 2 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.kh.scan.dialog; 16 17 import com.intellij.notification.NotificationType; 18 import com.intellij.openapi.diagnostic.Logger; 19 import com.intellij.openapi.project.Project; 20 import com.intellij.openapi.ui.ValidationInfo; 21 import com.kh.scan.action.ScanDirAction; 22 import com.kh.scan.action.ScanResultDirAction; 23 import com.kh.scan.utils.FileInfo; 24 import com.kh.scan.utils.FileUtil; 25 import com.kh.scan.utils.GenNotification; 26 import org.apache.http.util.TextUtils; 27 import org.jetbrains.annotations.Nullable; 28 29 import javax.swing.JButton; 30 import javax.swing.JComponent; 31 import javax.swing.JDialog; 32 import javax.swing.JPanel; 33 import javax.swing.JTextField; 34 import javax.swing.KeyStroke; 35 import java.awt.event.KeyEvent; 36 import java.awt.event.WindowAdapter; 37 import java.awt.event.WindowEvent; 38 import java.awt.event.WindowListener; 39 import java.io.BufferedReader; 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.io.InputStreamReader; 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.concurrent.BlockingQueue; 48 import java.util.concurrent.LinkedBlockingQueue; 49 import java.util.concurrent.ThreadPoolExecutor; 50 import java.util.concurrent.TimeUnit; 51 52 /** 53 * GenerateDialogPane生成工具主界面 54 * 55 * @author: zhaoxudong@kaihong.com 56 * @see: select generate dialog 57 * @version: v1.0.0 58 * @since 2022-10-14 59 */ 60 public class ApiScanDialogPane extends JDialog { 61 private static final Logger LOG = Logger.getInstance(ApiScanDialogPane.class); 62 63 private JPanel contentPane; 64 private JTextField scanDirPathTextField; 65 private JTextField outScanResultPathTextField; 66 private JButton selectScanPath; 67 private JButton outSelectPath; 68 private boolean generateSuccess = true; 69 private String sErrorMessage = ""; 70 private String scanDir; 71 private String scanResultDir; 72 private final Project project; 73 private BlockingQueue blockingQueue = new LinkedBlockingQueue(100); 74 private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 64, 60L, 75 TimeUnit.SECONDS, blockingQueue, 76 new ThreadPoolExecutor.AbortPolicy()); 77 78 79 /** 80 * 构造函数 81 * 82 * @param project projectId 83 */ ApiScanDialogPane(Project project)84 public ApiScanDialogPane(Project project) { 85 this.project = project; 86 contentPane.registerKeyboardAction(actionEvent -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 87 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 88 selectScanPath.addActionListener(new ScanDirAction(selectScanPath, scanDirPathTextField, 89 outScanResultPathTextField)); 90 outSelectPath.addActionListener(new ScanResultDirAction(outSelectPath, outScanResultPathTextField)); 91 } 92 93 @Override addWindowListener(WindowListener windowListener)94 public synchronized void addWindowListener(WindowListener windowListener) { 95 super.addWindowListener(windowListener); 96 new WindowAdapter() { 97 /** 98 * close dialog 99 * 100 * @param windowEvent WindowEvent 101 */ 102 @Override 103 public void windowClosing(WindowEvent windowEvent) { 104 onCancel(); 105 } 106 }; 107 } 108 109 /** 110 * 验证文本选择框是否空。是否替换已存在的内容 111 * 112 * @return ValidationInfo 返回不符要求的信息。 113 */ 114 @Nullable validationInfo()115 public ValidationInfo validationInfo() { 116 String scanResultDirPath = outScanResultPathTextField.getText(); 117 String scanDirPath = scanDirPathTextField.getText(); 118 boolean isEmptyFile = TextUtils.isEmpty(scanDirPath) || TextUtils.isEmpty(scanResultDirPath); 119 120 ValidationInfo validationInfo = null; 121 if (isEmptyFile) { 122 String warnMsg = "扫描项目路径、结果输出路径不能为空"; 123 warningMessage(warnMsg); 124 validationInfo = new ValidationInfo(warnMsg); 125 return validationInfo; 126 } 127 128 File file = new File(scanResultDirPath + "/result.xlsx"); 129 if (file.exists()) { 130 ConfirmDialog confirmDialog = new ConfirmDialog("是否替换已存在的扫描结果?"); 131 if (!confirmDialog.showAndGet()) { 132 validationInfo = new ValidationInfo(String.format("不替换现有扫描结果文件:%s", file)); 133 return validationInfo; 134 } 135 } 136 return validationInfo; 137 } 138 onCancel()139 private void onCancel() { 140 dispose(); 141 } 142 warningMessage(String title)143 private void warningMessage(String title) { 144 String notiContent = "请选择扫描项目路径、结果输出路径"; 145 GenNotification.notifyMessage(this.project, notiContent, title, NotificationType.WARNING); 146 } 147 148 /** 149 * 获取指定输出目录下的文件列表 150 * 151 * @param outPath 输出目录 152 * @return 文件信息列表 153 */ getFileInfoList(File outPath)154 public List<FileInfo> getFileInfoList(File outPath) { 155 List<FileInfo> fileInfoList = new ArrayList<>(); 156 File[] files = outPath.listFiles(); 157 for (File file : files) { 158 fileInfoList.add(new FileInfo(file)); 159 } 160 return fileInfoList; 161 } 162 163 /** 164 * 执行主程序入口 165 * 166 * @return 执行状态 167 */ runFun()168 public boolean runFun() { 169 GenNotification.notifyMessage(this.project, "", "正在生成", NotificationType.INFORMATION); 170 scanDir = scanDirPathTextField.getText(); 171 scanResultDir = outScanResultPathTextField.getText(); 172 String command = genCommand(); 173 174 File outPath = new File(outScanResultPathTextField.getText()); 175 List<FileInfo> oldFileList = getFileInfoList(outPath); 176 177 try { 178 if (!TextUtils.isEmpty(command) && callExtProcess(command)) { 179 List<FileInfo> newFileList = getFileInfoList(outPath); 180 newFileList.removeAll(oldFileList); 181 if (generateSuccess) { 182 GenNotification.notifyGenResult(project, newFileList, "Api Scan Successfully", 183 NotificationType.INFORMATION); 184 return true; 185 } else { 186 GenNotification.notifyMessage(project, sErrorMessage, "提示", NotificationType.ERROR); 187 return false; 188 } 189 } 190 } catch (IOException | InterruptedException ex) { 191 GenNotification.notifyMessage(project, scanDirPathTextField.getText(), "Command exec error", 192 NotificationType.ERROR); 193 LOG.error(ex); 194 } 195 return false; 196 } 197 198 /** 199 * 生成命令行指令 200 * 201 * @return 返回命令行执行内容 202 */ genCommand()203 private String genCommand() { 204 String sysName = System.getProperties().getProperty("os.name").toUpperCase(); 205 String tmpDirFile = System.getProperty("java.io.tmpdir"); 206 String execFn; 207 if (sysName.contains("WIN")) { 208 execFn = "cmds/win/search-win.exe"; 209 tmpDirFile += "search-win.exe"; 210 } else if (sysName.contains("LINUX")) { 211 execFn = "cmds/linux/search-linux"; 212 tmpDirFile += "/search-linux"; 213 } else { 214 execFn = "cmds/mac/search-macos"; 215 tmpDirFile += "/search-macos"; 216 } 217 File file = new File(tmpDirFile); 218 try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(execFn)) { 219 if (inputStream == null) { 220 throw new IOException("exec File InputStream is Null"); 221 } 222 byte[] bs = inputStream.readAllBytes(); 223 writeTmpFile(tmpDirFile, bs); 224 if (sysName.contains("LINUX") || sysName.contains("MAC OS")) { 225 executable(tmpDirFile); 226 } 227 } catch (IOException | InterruptedException e) { 228 GenNotification.notifyMessage(this.project, e.getMessage(), "Can not Find File:" + execFn, 229 NotificationType.ERROR); 230 LOG.error(e); 231 232 return ""; 233 } 234 String command = file.toString(); 235 command = command + " -d " + scanDir + " -o " + scanResultDir; 236 return command; 237 } 238 callExtProcess(String command)239 private boolean callExtProcess(String command) throws IOException, InterruptedException { 240 if (TextUtils.isEmpty(command)) { 241 GenNotification.notifyMessage(this.project, "执行命令文件为空", "空命令行提示", NotificationType.ERROR); 242 return false; 243 } 244 String tmpDirFile = System.getProperty("java.io.tmpdir"); 245 Process process = Runtime.getRuntime().exec(command, null, new File(tmpDirFile)); 246 threadPool.execute(new BlockThread(process)); 247 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); 248 outputConsumer.start(); 249 outputConsumer.join(); 250 process.destroy(); 251 return true; 252 } 253 254 /** 255 * 赋值可执行文件权限。 256 * 257 * @param execFn 可执行命令 258 * @throws IOException 打开文件异常 259 * @throws InterruptedException 中断异常 260 */ executable(String execFn)261 private void executable(String execFn) throws IOException, InterruptedException { 262 callExtProcess("chmod a+x " + execFn); 263 } 264 265 /** 266 * 拷贝可执行文件到临时文件夹 267 * 268 * @param path 目标文件路径 269 * @param bs 字节内容 270 * @throws IOException exception 271 */ writeTmpFile(String path, byte[] bs)272 private void writeTmpFile(String path, byte[] bs) throws IOException { 273 File file = new File(path); 274 FileOutputStream fw = null; 275 if (!file.exists()) { 276 boolean isNewFile = file.createNewFile(); 277 if (!isNewFile) { 278 LOG.info("writeTmpFile createNewFile error"); 279 } 280 } 281 try { 282 fw = new FileOutputStream(file); 283 fw.write(bs, 0, bs.length); 284 } catch (IOException e) { 285 // 处理可能发生的IOException 286 LOG.error("Error reading from process streams", e); 287 } finally { 288 try { 289 fw.close(); 290 } catch (IOException e) { 291 LOG.error("Error closing stdInput", e); 292 } 293 } 294 } 295 296 /** 297 * 获取生成成功结果文件。 298 * 299 * @param process 进程ID 300 */ genResultLog(Process process)301 private void genResultLog(Process process) { 302 BufferedReader stdInput = null; 303 BufferedReader stdError = null; 304 try { 305 stdInput = new BufferedReader(new InputStreamReader(process.getInputStream(), 306 StandardCharsets.UTF_8)); 307 stdError = new BufferedReader(new InputStreamReader(process.getErrorStream(), 308 StandardCharsets.UTF_8)); 309 String sErr; 310 String sOut; 311 sErr = getErrorResult(stdError); 312 if (TextUtils.isEmpty(sErr)) { 313 sOut = genInputLog(stdInput); 314 if (!generateIsSuccess(sOut)) { 315 sErrorMessage = sOut; 316 } 317 } else { 318 generateSuccess = false; 319 sErrorMessage = sErr; 320 } 321 } catch (IOException e) { 322 // Handle exception 323 LOG.error(e); 324 } finally { 325 // Close resources in finally block to ensure they are closed even if an exception occurs 326 try { 327 stdInput.close(); 328 } catch (IOException e) { 329 LOG.error(e); 330 } 331 try { 332 stdError.close(); 333 } catch (IOException e) { 334 LOG.error(e); 335 } 336 } 337 } 338 339 /** 340 * 获取生成失败结果文件。 341 * 342 * @param stdError error buff 343 * @return ErrorResult 344 */ getErrorResult(BufferedReader stdError)345 private String getErrorResult(BufferedReader stdError) { 346 StringBuilder sErr = new StringBuilder(); 347 while (true) { 348 String sTmp; 349 try { 350 if ((sTmp = stdError.readLine()) == null) { 351 break; 352 } 353 sErr.append(sTmp).append(FileUtil.getNewline()); 354 } catch (IOException ioException) { 355 LOG.error(" genResultLog stdInput error" + ioException); 356 } 357 } 358 return sErr.toString(); 359 } 360 generateIsSuccess(String sOut)361 private boolean generateIsSuccess(String sOut) { 362 generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut); 363 return generateSuccess; 364 } 365 366 /** 367 * 获取生成文本内容。 368 * 369 * @param stdInput input buff 370 * @return 返回当前输入框内容 371 */ genInputLog(BufferedReader stdInput)372 private String genInputLog(BufferedReader stdInput) { 373 StringBuilder sOut = new StringBuilder(); 374 while (true) { 375 String sTmp; 376 try { 377 if ((sTmp = stdInput.readLine()) == null) { 378 break; 379 } 380 sOut.append(sTmp).append(FileUtil.getNewline()); 381 } catch (IOException ioException) { 382 LOG.error(" genResultLog stdInput error" + ioException); 383 } 384 } 385 return sOut.toString(); 386 } 387 388 static class StreamConsumer extends Thread { 389 InputStream is; 390 StreamConsumer(InputStream is)391 StreamConsumer(InputStream is) { 392 super.setName("StreamConsumer"); 393 this.is = is; 394 } 395 396 @Override run()397 public void run() { 398 InputStreamReader isr5 = null; 399 BufferedReader br5 = null; 400 try { 401 isr5 = new InputStreamReader(is, StandardCharsets.UTF_8); 402 br5 = new BufferedReader(isr5); 403 String line; 404 while ((line = br5.readLine()) != null) { 405 LOG.info(line); 406 } 407 } catch (IOException ioException) { 408 LOG.error("StreamConsumer io error" + ioException); 409 } finally { 410 // 确保BufferedReader5 br和InputStreamReader isr5被关闭 411 try { 412 br5.close(); 413 } catch (IOException e) { 414 LOG.error(e); 415 } 416 try { 417 isr5.close(); 418 } catch (IOException e) { 419 LOG.error(e); 420 } 421 } 422 } 423 } 424 getContentPanel()425 JPanel getContentPanel() { 426 return contentPane; 427 } 428 429 class BlockThread extends Thread { 430 Process process; 431 BlockThread(Process process)432 BlockThread(Process process) { 433 super.setName("BlockThread"); 434 this.process = process; 435 } 436 437 @Override run()438 public void run() { 439 BufferedReader br2 = null; 440 try { 441 br2 = new BufferedReader(new InputStreamReader(process.getInputStream(), 442 StandardCharsets.UTF_8)); 443 genResultLog(process); 444 while (br2.readLine() != null) { 445 LOG.info(" callExtProcess "); 446 } 447 } catch (IOException ioException) { 448 LOG.error(" callExtProcess error" + ioException); 449 } finally { 450 // 确保BufferedReader br2被关闭 451 try { 452 br2.close(); 453 } catch (IOException e) { 454 // 处理关闭BufferedReader时的异常 455 LOG.error(e); 456 } 457 } 458 } 459 } 460 } 461