laser.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /**
  2. * laser_cal Node.js 调用封装
  3. *
  4. * 支持三种调用模式:
  5. * 1. module 模式:通过 Python 解释器 + laser_wrapper.py 加载 .pyd/.so 共享库
  6. * 2. exe 模式:直接调用编译后的可执行程序(standalone 目录)
  7. * 3. onefile 模式:直接调用编译后的单文件可执行程序
  8. *
  9. * 使用示例:
  10. * const laser = require('./laser');
  11. *
  12. * // 自动检测模式
  13. * const result = await laser.callLaser('getpath');
  14. * const result = await laser.callLaser('loaddata', base64JsonData);
  15. */
  16. const { execFile } = require('child_process');
  17. const path = require('path');
  18. const fs = require('fs');
  19. // ============================================================
  20. // 配置区(根据实际部署路径修改)
  21. // ============================================================
  22. const PROJECT_NAME = 'laser_cal';
  23. const DIST_DIR = path.join(__dirname, '..', 'dist', 'program');
  24. const WRAPPER_SCRIPT = path.join(__dirname, 'laser_wrapper.py');
  25. // Python 路径(module 模式需要)
  26. // 可通过环境变量 PYTHON_PATH 覆盖
  27. const PYTHON_CMD = process.env.PYTHON_PATH
  28. || (process.platform === 'win32'
  29. ? path.join(__dirname, '..', 'myvenv', 'Scripts', 'python.exe')
  30. : 'python3');
  31. // ============================================================
  32. // 自动检测可用的调用模式
  33. // ============================================================
  34. function detectMode() {
  35. const exeName = process.platform === 'win32' ? `${PROJECT_NAME}.exe` : PROJECT_NAME;
  36. // 优先检测 onefile 模式(exe 在 dist/program/ 下)
  37. const onefilePath = path.join(DIST_DIR, exeName);
  38. if (fs.existsSync(onefilePath)) {
  39. return { mode: 'exe', execPath: onefilePath };
  40. }
  41. // 检测 standalone 模式(exe 在 dist/program/api_test.dist/ 下)
  42. const standalonePath = path.join(DIST_DIR, 'api_test.dist', exeName);
  43. if (fs.existsSync(standalonePath)) {
  44. return { mode: 'exe', execPath: standalonePath };
  45. }
  46. // 回退到 module 模式(使用 Python + wrapper)
  47. return { mode: 'module', execPath: null };
  48. }
  49. // ============================================================
  50. // 核心调用函数
  51. // ============================================================
  52. /**
  53. * 调用 laser_cal 后端
  54. * @param {string} apiName - API 名称: getpath | loaddata | historydata | deletedata
  55. * @param {string|null} b64Data - Base64 编码的 JSON 参数(loaddata/historydata/deletedata 需要)
  56. * @returns {Promise<object>} - 解析后的 JSON 结果
  57. */
  58. function callLaser(apiName, b64Data = null) {
  59. const detected = detectMode();
  60. if (detected.mode === 'exe') {
  61. return callExe(detected.execPath, apiName, b64Data);
  62. } else {
  63. return callModule(apiName, b64Data);
  64. }
  65. }
  66. /**
  67. * 直接调用可执行文件(exe/standalone 模式)
  68. */
  69. function callExe(exePath, apiName, b64Data = null) {
  70. return new Promise((resolve, reject) => {
  71. const args = [apiName];
  72. if (b64Data) {
  73. args.push(b64Data);
  74. }
  75. const options = {
  76. maxBuffer: 1024 * 1024 * 50,
  77. cwd: path.dirname(exePath) // 设置工作目录为 exe 所在目录(确保找到 license)
  78. };
  79. execFile(exePath, args, options, (error, stdout, stderr) => {
  80. if (error) {
  81. return reject(new Error(`Execution failed: ${error.message}\nStderr: ${stderr}`));
  82. }
  83. parseOutput(stdout, resolve, reject);
  84. });
  85. });
  86. }
  87. /**
  88. * 通过 Python 解释器调用(module 模式)
  89. */
  90. function callModule(apiName, b64Data = null) {
  91. return new Promise((resolve, reject) => {
  92. const args = [WRAPPER_SCRIPT, apiName];
  93. if (b64Data) {
  94. args.push(b64Data);
  95. }
  96. const options = {
  97. maxBuffer: 1024 * 1024 * 50
  98. };
  99. execFile(PYTHON_CMD, args, options, (error, stdout, stderr) => {
  100. if (error) {
  101. return reject(new Error(`Python execution failed: ${error.message}\nStderr: ${stderr}`));
  102. }
  103. parseOutput(stdout, resolve, reject);
  104. });
  105. });
  106. }
  107. /**
  108. * 解析 stdout JSON 输出
  109. */
  110. function parseOutput(stdout, resolve, reject) {
  111. try {
  112. const lines = stdout.trim().split('\n');
  113. const lastLine = lines[lines.length - 1];
  114. const result = JSON.parse(lastLine);
  115. if (result.error) {
  116. reject(new Error(result.error));
  117. } else {
  118. resolve(result);
  119. }
  120. } catch (e) {
  121. reject(new Error(`Failed to parse output: ${stdout}\nError: ${e.message}`));
  122. }
  123. }
  124. // ============================================================
  125. // 兼容旧接口
  126. // ============================================================
  127. /**
  128. * 兼容旧版 callPython 接口
  129. */
  130. function callPython(apiName, b64Data = null) {
  131. return callLaser(apiName, b64Data);
  132. }
  133. // ============================================================
  134. // 导出
  135. // ============================================================
  136. module.exports = {
  137. callLaser,
  138. callPython, // 向后兼容
  139. callExe,
  140. callModule,
  141. detectMode
  142. };