Web TWAIN SDK от Dynamsoft уже давно является лидером на рынке веб-сканирования документов, предоставляя многочисленным организациям возможность разрабатывать собственные системы управления документами. SDK состоял из двух основных компонентов: службы Dynamsoft для управления сканером и библиотеки JavaScript для внешней разработки. Раньше доступ к этому сервису можно было получить только через библиотеку JavaScript. В следующем выпуске служба Dynamsoft предоставит базовую функцию сканирования документов через REST API. Эта новая функция позволяет разработчикам использовать различные языки программирования для задач сканирования документов. Теперь, помимо веб-приложений, SDK также можно использовать для создания настольных и мобильных приложений для сканирования документов, а также серверные службы сканирования. В этой статье я покажу вам процесс использования нового REST API для сканирования документов в Node.js.

Пакет НПМ

https://www.npmjs.com/package/docscan4nodejs

Предварительные условия

В настоящее время REST API доступен исключительно для Windows, но скоро появится поддержка Linux (x64 и ARM64) и macOS.

Справочник по REST API

По умолчанию адрес хоста REST API установлен на http://127.0.0.1:18622. Чтобы изменить его на IP-адрес локальной сети, перейдите к http://127.0.0.1:18625/ в веб-браузере.

Параметры конечной точки /DWTAPI/ScanJobs описаны в Документации Dynamic Web TWAIN и управляют поведением сканера.

Например, вы можете установить разрешение 200 DPI и цветной тип пикселя:

let parameters = {
    license: "LICENSE-KEY",
    device: device,
};

parameters.config = {
    IfShowUI: false,
    PixelType: 2, // color
    Resolution: 200,
    IfFeederEnabled: false,
    IfDuplexEnabled: false,
};

Разработка функций Node.js для вызова REST API

Установите Axios для отправки HTTP-запросов напрямую из Node.js в RESTful API и получения их ответов.

npm install axios

Согласно справочнику REST API, мы реализуем пять функций: getDevices(), scanDocument(), deleteJob, getImageFiles и getImageStreams().

const axios = require('axios');
const fs = require('fs');
const path = require('path');

module.exports = {
    getDevices: async function (host) {
        return [];
    },
    scanDocument: async function (host, parameters) {
        return '';
    },
    deleteJob: async function (host, jobId) {
    },
    getImageFiles: async function (host, jobId, directory) {
        let images = [];
        return images;
    },
    getImageStreams: async function (host, jobId) {
        let streams = [];
        return streams;
    },
};
  • getDevices() получает список сканеров.
getDevices: async function (host) {
      devices = [];
      let url = host + '/DWTAPI/Scanners'
      try {
          let response = await axios.get(url)
              .catch(error => {
                  console.log(error);
              });

          if (response.status == 200 && response.data.length > 0) {
              console.log('\nAvailable scanners: ' + response.data.length);
              return response.data;
          }
      } catch (error) {
          console.log(error);
      }
      return [];
  },
  • scanDocument() создает задание сканирования и возвращает его идентификатор.
scanDocument: async function (host, parameters) {
      let url = host + '/DWTAPI/ScanJobs';

      try {
          let response = await axios.post(url, parameters)
              .catch(error => {
                  console.log('Error: ' + error);
              });

          let jobId = response.data;

          if (response.status == 201) {
              return jobId;
          }
          else {
              console.log(response);
          }
      }
      catch (error) {
          console.log(error);
      }


      return '';
  },
  • deleteJob() удаляет задание сканирования.
deleteJob: async function (host, jobId) {
      if (!jobId) return;

      let url = host + '/DWTAPI/ScanJobs/' + jobId;
      console.log('Delete job: ' + url);
      axios({
          method: 'DELETE',
          url: url
      })
          .then(response => {
              console.log('Deleted:', response.data);
          })
          .catch(error => {
          });
  },
  • getImageFiles() извлекает файлы изображений задания сканирования.
getImageFiles: async function (host, jobId, directory) {
      let images = [];
      let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument';
      console.log('Start downloading images......');
      while (true) {
          try {
              const response = await axios({
                  method: 'GET',
                  url: url,
                  responseType: 'stream',
              });

              if (response.status == 200) {
                  await new Promise((resolve, reject) => {
                      const timestamp = Date.now();
                      const imagePath = path.join(directory, `image_${timestamp}.jpg`);
                      const writer = fs.createWriteStream(imagePath);
                      response.data.pipe(writer);

                      writer.on('finish', () => {
                          images.push(imagePath);
                          console.log('Saved image to ' + imagePath + '\n');
                          resolve();
                      });

                      writer.on('error', (err) => {
                          console.log(err);
                          reject(err);
                      });
                  });
              }
              else {
                  console.log(response);
              }

          } catch (error) {
              console.error('No more images.');
              break;
          }
      }

      return images;
  },
  • getImageStreams() извлекает потоки изображений задания сканирования.
getImageStreams: async function (host, jobId) {
      let streams = [];
      let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument';
      console.log('Start downloading images......');
      while (true) {
          try {
              const response = await axios({
                  method: 'GET',
                  url: url,
                  responseType: 'stream',
              });

              if (response.status == 200) {
                  streams.push(response.data);
              }
              else {
                  console.log(response);
              }

          } catch (error) {
              console.error('No more images.');
              break;
          }
      }

      return streams;
  },

Сканирование документов из командной строки в терминале

Создадим файл app.js для сканирования документов из командной строки.

  1. Импортируйте docscan4nodejs и readline. Модуль docscan4nodejs — это то, что мы только что реализовали, а readline используется для чтения пользовательского ввода из командной строки.
const docscan4nodejs = require("docscan4nodejs")
 const readline = require('readline');

2. Создайте экземпляр readline.Interface для чтения вводимых пользователем данных из командной строки.

const rl = readline.createInterface({
     input: process.stdin,
     output: process.stdout
 });

 rl.question(questions, function (answer) {});

3. Получите все доступные сканеры, совместимые с TWAIN, SANE, ICA, WIA и eSCL. .

 let devices = await docscan4nodejs.getDevices(host);

4. Выберите сканер из списка для сканирования документов. Требуется действительный лицензионный ключ.

let parameters = {
     license: "LICENSE-KEY",
     device: devices[index].device,
 };

 parameters.config = {
     IfShowUI: false,
     PixelType: 2,
     //XferCount: 1,
     //PageSize: 1,
     Resolution: 200,
     IfFeederEnabled: false,
     IfDuplexEnabled: false,
 };

 docscan4nodejs.scanDocument(host, parameters).then((jobId) => {
     if (jobId !== '') {
         console.log('job id: ' + jobId);
         (async () => {
             let images = await docscan4nodejs.getImageFiles(host, jobId, './');
             for (let i = 0; i < images.length; i++) {
                 console.log('Image ' + i + ': ' + images[i]);
             }
             await docscan4nodejs.deleteJob(jobId);
         })();
     }

 });

5. Запустите скрипт в терминале.

node app.js

Получить все доступные сканеры

Получить документ

Реализация сканирования документов на стороне сервера для веб-приложений

Более продвинутый вариант использования предполагает реализацию сканирования документов на стороне сервера для веб-приложений. Ключевым преимуществом этой стратегии является то, что она позволяет сканировать документы непосредственно из веб-браузера, устраняя необходимость установки дополнительного программного обеспечения. Например, если в вашем офисе есть единственный сканер документов, и вы хотите поделиться им с несколькими коллегами, веб-приложение позволит им инициировать сканирование со своих отдельных компьютеров.

Мы используем express для создания веб-сервера и socket.io для передачи данных между сервером и клиентом.

npm install express socket.io

Веб-сервер Node.js

  1. Инициализируйте express и socket.io.
const express = require('express');
 const path = require('path');
 const fs = require('fs');
 const app = express();
 const http = require('http');
 const server = http.createServer(app);
 const io = require('socket.io')(server);

 const docscan4nodejs = require("docscan4nodejs")
 const { PassThrough } = require('stream');

 app.use(express.static('public'));
 app.use('/node_modules', express.static(__dirname + '/node_modules'));

 const connections = new Map();
 io.on('connection', (socket) => {
     connections.set(socket.id, socket);
     console.log(socket.id + ' connected');

     socket.on('disconnect', () => {
         console.log(socket.id + ' disconnected');
         connections.delete(socket.id);
     });

     socket.on('message', async (message) => {
            
     });

 });

 // Start the server
 const port = process.env.PORT || 3000;

 server.listen(port, '0.0.0.0', () => {
     console.log(`Server running at http://0.0.0.0:${port}/`);
 });

2. Как только соединение socket.io будет установлено, достаньте доступные сканеры и отправьте их клиенту.

io.on('connection', (socket) => {
     ...
        
     docscan4nodejs.getDevices(host).then((scanners) => {
         socket.emit('message', JSON.stringify({ 'devices': scanners }));
     });
 });

3. При получении события сканирования инициируйте сканирование документа и передайте полученный поток изображений веб-клиенту:

socket.on('message', async (message) => {
     let json = JSON.parse(message);
     if (json) {
         if (json['scan']) {
             console.log('device: ' + json['scan']);
             let parameters = {
                 license: "LICENSE-KEY",
                 device: json['scan'],
             };

             parameters.config = {
                 IfShowUI: false,
                 PixelType: 2,
                 //XferCount: 1,
                 //PageSize: 1,
                 Resolution: 200,
                 IfFeederEnabled: false,
                 IfDuplexEnabled: false,
             };

             let jobId = await docscan4nodejs.scanDocument(host, parameters);

             if (jobId !== '') {
                 console.log('job id: ' + jobId);
                 let streams = await docscan4nodejs.getImageStreams(host, jobId);
                 for (let i = 0; i < streams.length; i++) {
                     await new Promise((resolve, reject) => {
                         try {
                             const passThrough = new PassThrough();
                             const chunks = [];
    
                             streams[i].pipe(passThrough);
    
                             passThrough.on('data', (chunk) => {
                                 chunks.push(chunk);
                             });
    
                             passThrough.on('end', () => {
                                 const buffer = Buffer.concat(chunks);
                                 socket.emit('image', buffer);
                                 resolve();
                             });
                         }
                         catch (error) {
                             reject(error);
                         }
                     });
                 }
             }
         }
     }
 });

Веб-клиент

  1. Установите socket.io соединение с сервером.
const socket = io();
 var data = [];
 var devices = [];
 var selectSources = document.getElementById("sources");
 socket.on('message', (message) => {
 });

 socket.on('image', (buffer) => {
 });

2. Обновите элемент <select>, указав доступные сканеры.

socket.on('message', (message) => {
     try {
         let json = JSON.parse(message);
         if (json) {
             if (json['devices']) {
                 selectSources.options.length = 0;
                 devices = json['devices'];
                 for (let i = 0; i < devices.length; i++) {
                     console.log('\nIndex: ' + i + ', Name: ' + devices[i]['name']);
                     let option = document.createElement("option");
                     option.text = devices[i]['name'];
                     option.value = i.toString();
                     selectSources.add(option);
                 }
             }
         }
     } catch (error) {
         console.log(error)
     }
 });

3. Отправьте событие сканирования на сервер, когда пользователь нажимает кнопку Scan.

<button onclick="acquireImage()">Scan Documents</button>
 function acquireImage() {
     if (devices.length > 0 && selectSources.selectedIndex >= 0) {
         socket.emit('message', JSON.stringify({ 'scan': devices[selectSources.selectedIndex]['device'] }));
     }

 }

4. Отобразите поток изображений в элементе <img>.

socket.on('image', (buffer) => {
     // Convert the Buffer into a base64 string
     const base64Image = btoa(
         new Uint8Array(buffer)
             .reduce((data, byte) => data + String.fromCharCode(byte), '')
     );

     // Set the image src to display the image
     let img = document.getElementById('document-image');
     let url = `data:image/jpeg;base64,${base64Image}`;
     img.src = url;

     data.push(url);

     let option = document.createElement("option");
     option.selected = true;
     option.text = url;
     option.value = url;

     let thumbnails = document.getElementById("thumb-box");
     let newImage = document.createElement('img');
     newImage.setAttribute('src', url);
     if (thumbnails != null) {
         thumbnails.appendChild(newImage);
         newImage.addEventListener('click', e => {
             if (e != null && e.target != null) {
                 let target = e.target;
                 img.src = target.src;
             }
         });
     }
 });

Запустите веб-сервер на своем компьютере с Windows, а затем перейдите к http://LAN-IP:18625/ с помощью Safari на macOS.

Исходный код

https://github.com/yushulx/dynamsoft-service-REST-API

Оригинально опубликовано на сайте https://www.dynamsoft.com 6 сентября 2023 г.