我制作的程序是一个文档转换器,它接收文档并使用 libreoffice 将其转换为 pdf。
这是我的服务器(localhost:3000)
import express from "express";
import bodyParser from "body-parser";
import multer from "multer"; //To communicate files
import { dirname, extname } from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import { exec } from "child_process"; //Used to execute shell commands
import util from "util"; //built-in Node.js module that provides utility functions.
import { createServer } from 'node:http';
import { Server } from 'socket.io';
const execPromise = util.promisify(exec); // creates a Promise-based version of the exec function to use with asynchronous operations.
const __dirname = dirname(fileURLToPath(import.meta.url)); //To get the whole path upto working directory
const TestServer = express();
const port = 3000;
const server = createServer(TestServer);
const io = new Server(server);
TestServer.use(bodyParser.urlencoded({ extended: true }));
TestServer.use(express.static("public"));
let docName;
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'public/Document');
},
filename: (req, file, cb) => {
//console.log(file);
docName = file.originalname;
cb(null, file.originalname);
}
});
const upload = multer({ storage: storage });
TestServer.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
// A Multer error occurred (e.g., file size exceeded)
res.status(400).send('Multer error: ' + err.message);
} else if (err) {
// An unknown error occurred
res.status(500).send('Unknown error: ' + err.message);
} else {
// No error occurred, proceed to the next middleware or route handler
next();
}
});
//function to check if a particular file exists
function fileExists(filePath) {
try {
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
}
let pathToOffice;
const osPlatform = process.platform;
if (osPlatform === 'win32' || osPlatform === 'win64') {
pathToOffice = 'C:\\Program Files\\LibreOffice\\program\\soffice.exe';
} else if (osPlatform === 'darwin') {
pathToOffice = '/Applications/LibreOffice.app/Contents/MacOS/soffice';
} else if (osPlatform === 'linux') {
pathToOffice = '' // have to enter the path according to how soffice is in linux //TODO
} else {
console.error('Unsupported operating system');
process.exit(1);
}
async function convertWordToPdf(inputPath, outputPath) {
try {
// Convert using LibreOffice
const { stdout, stderr } = await execPromise(`"${pathToOffice}" --headless --invisible --convert-to pdf --outdir "${outputPath}" "${inputPath}"`);
if (stderr && !stderr.includes('Secure coding is not enabled for restorable state!')) {
throw new Error(stderr);
}
//console.log('Conversion successful:', stdout);
} catch (error) {
console.error('Error converting Word to PDF:', error);
throw error;
}
}
TestServer.get("/", (req, res) => {
//send homepage
res.render("uploadDoc.ejs");
});
TestServer.post("/getdocument", upload.fields([
{ name: 'doc-upload', maxCount: 1 }
]), async (req, res) => {
console.log(`Document successfully uploaded: ${docName}`);
//console.log(req.body);
console.log("---------------------------------------------------------------");
io.emit('uploadStatus', { status: 'uploading' });
//Convert process below and send the file
try {
if (fileExists(__dirname + `/public/Document/${docName}`)) {
const docNameWithoutExtension = docName.replace(extname(docName), '');
io.emit('uploadStatus', { status: 'converting' });
await convertWordToPdf(__dirname + `/public/Document/${docName}`, __dirname + `/public/Document/${docNameWithoutExtension}`);
io.emit('uploadStatus', { status: 'complete' });
res.render("downloadDoc.ejs", {
filepath: `/Document/${docNameWithoutExtension}/${docNameWithoutExtension}.pdf`
});
}
else {
res.send("<h1>UNSUCCESSFUL: File not found</h1>");
}
} catch (error) {
console.error("Failed to make conversion:", error.message);
res.send("<h1>UNSUCCESSFUL: Conversion failed</h1>");
}
});
io.on('connection', (socket) => {
console.log('a user connected');
// Send initial status to connected client
socket.on('formSubmit', (msg) => {
console.log(msg.data);
io.emit('uploadStatus', { status: 'idle' });
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
下面是我正在上传文档的客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Doc</title>
<link rel="stylesheet" href="/styles/styles.css">
</head>
<body class="m-0 p-0">
<div class="bouncing-blobs-container">
<div class="bouncing-blobs-glass"></div>
<div class="bouncing-blobs">
<div class="bouncing-blob bouncing-blob--blue"></div>
<div class="bouncing-blob bouncing-blob--blue"></div>
<div class="bouncing-blob bouncing-blob--blue"></div>
<div class="bouncing-blob bouncing-blob--white"></div>
<div class="bouncing-blob bouncing-blob--purple"></div>
<div class="bouncing-blob bouncing-blob--purple"></div>
<div class="bouncing-blob bouncing-blob--pink"></div>
</div>
</div>
<div class="px-36 py-4 h-screen w-screen">
<form id="form" action="/getdocument" method="post" enctype="multipart/form-data">
<div class="space-y-12">
<label for="document-upload-area" class="block text-sm font-medium leading-6 text-gray-900">Document
Upload</label>
<div class="mt-2 flex gap-4 justify-center">
<p class="text-sm self-center">Convert</p>
<select id="convert-from" name="convert-from" required
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6">
<option value="" disabled selected>--Select--</option>
<option value="doc">DOCX, DOC</option>
<option value="other-format">Other Format</option>
</select>
<p class="text-sm self-center">to</p>
<select id="convert-to" name="convert-to" required
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6">
<option value="" disabled selected>--Select--</option>
<option value="pdf">PDF</option>
</select>
</div>
<div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 px-6 py-10">
<div>
<svg class="mx-auto" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="40" zoomAndPan="magnify"
viewBox="0 0 30 30.000001" height="40" preserveAspectRatio="xMidYMid meet" version="1.0">
<defs>
<clipPath id="id1">
<path
d="M 2.742188 3.484375 L 27.417969 3.484375 L 27.417969 25.257812 L 2.742188 25.257812 Z M 2.742188 3.484375 "
clip-rule="nonzero" />
</clipPath>
</defs>
<g clip-path="url(#id1)">
<path fill="rgb(8.239746%, 42.349243%, 86.669922%)"
d="M 15.082031 3.484375 L 2.742188 5.902344 L 2.742188 22.839844 L 15.082031 25.257812 Z M 17.546875 5.902344 L 17.546875 8.324219 L 20.015625 8.324219 L 20.015625 10.742188 L 17.546875 10.742188 L 17.546875 13.160156 L 20.015625 13.160156 L 20.015625 15.582031 L 17.546875 15.582031 L 17.546875 18 L 20.015625 18 L 20.015625 20.417969 L 17.546875 20.417969 L 17.546875 22.839844 L 27.417969 22.839844 L 27.417969 5.902344 Z M 22.484375 8.324219 L 24.953125 8.324219 L 24.953125 10.742188 L 22.484375 10.742188 Z M 5.425781 9.890625 L 7.621094 9.890625 L 8.757812 12.570312 C 8.851562 12.789062 8.921875 13.042969 8.992188 13.332031 L 9.023438 13.332031 C 9.066406 13.160156 9.148438 12.894531 9.273438 12.550781 L 10.542969 9.890625 L 12.542969 9.890625 L 10.15625 14.332031 L 12.613281 18.851562 L 10.480469 18.851562 L 9.105469 15.929688 C 9.054688 15.828125 8.992188 15.621094 8.941406 15.332031 L 8.921875 15.332031 C 8.890625 15.46875 8.832031 15.675781 8.738281 15.953125 L 7.351562 18.851562 L 5.210938 18.851562 L 7.753906 14.367188 Z M 22.484375 13.160156 L 24.953125 13.160156 L 24.953125 15.582031 L 22.484375 15.582031 Z M 22.484375 18 L 24.953125 18 L 24.953125 20.417969 L 22.484375 20.417969 Z M 22.484375 18 "
fill-opacity="1" fill-rule="nonzero" />
</g>
</svg>
<div class="mt-4 flex text-sm leading-6 text-gray-600 justify-center">
<label for="doc-upload"
class="relative cursor-pointer rounded-md bg-transparent font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-0 hover:text-indigo-500">
<span>Upload a file</span>
<input id="doc-upload" name="doc-upload" type="file" class="sr-only">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p id="doc-name" class="text-lg leading-5 text-gray-600 text-center"></p>
</div>
</div>
</div>
<div id="buttons" class="mt-6 justify-center gap-x-6 hidden">
<a href="/" class="flex justify-center items-center">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button>
</a>
<button id="submit-btn" type="submit"
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Convert</button>
</div>
</form>
</div>
<script type="text/javascript">
document.getElementById('doc-upload').addEventListener('change', function () {
// File has been selected, show the buttons
document.getElementById('buttons').classList.remove("hidden");
document.getElementById('buttons').classList.add("flex");
// Display the selected file name
const fileNameElement = document.getElementById('doc-name');
const fileInput = document.getElementById('doc-upload');
if (fileInput.files.length > 0) {
fileNameElement.textContent = fileInput.files[0].name;
} else {
// Reset the text if no file is selected
fileNameElement.textContent = 'word DOCX and DOC';
}
});
//Animation taken from a youtube video which i cant remember.
const MIN_SPEED = 1.5
const MAX_SPEED = 2.5
function randomNumber(min, max) {
return Math.random() * (max - min) + min
}
class Blob {
constructor(el) {
this.el = el
const boundingRect = this.el.getBoundingClientRect()
this.size = boundingRect.width
this.initialX = randomNumber(0, window.innerWidth - this.size)
this.initialY = randomNumber(0, window.innerHeight - this.size)
this.el.style.top = `${this.initialY}px`
this.el.style.left = `${this.initialX}px`
this.vx =
randomNumber(MIN_SPEED, MAX_SPEED) * (Math.random() > 0.5 ? 1 : -1)
this.vy =
randomNumber(MIN_SPEED, MAX_SPEED) * (Math.random() > 0.5 ? 1 : -1)
this.x = this.initialX
this.y = this.initialY
}
update() {
this.x += this.vx
this.y += this.vy
if (this.x >= window.innerWidth - this.size) {
this.x = window.innerWidth - this.size
this.vx *= -1
}
if (this.y >= window.innerHeight - this.size) {
this.y = window.innerHeight - this.size
this.vy *= -1
}
if (this.x <= 0) {
this.x = 0
this.vx *= -1
}
if (this.y <= 0) {
this.y = 0
this.vy *= -1
}
}
move() {
this.el.style.transform = `translate(${this.x - this.initialX}px, ${this.y - this.initialY
}px)`
}
}
function initBlobs() {
const blobEls = document.querySelectorAll('.bouncing-blob')
const blobs = Array.from(blobEls).map((blobEl) => new Blob(blobEl))
function update() {
requestAnimationFrame(update)
blobs.forEach((blob) => {
blob.update()
blob.move()
})
}
requestAnimationFrame(update)
}
initBlobs()
</script>
</body>
</html>
我尝试集成 socket.io 以根据文档上传时间、转换开始时间和完成时间向客户端发送更新。但我找不到将数据发送到客户端的方法。从客户端,我可以通过触发事件发送一些数据,但这不是我想要的。我基本上需要服务器能够以某种方式将更新发送到客户端。
后端使用NodeJS、Express完成。前端使用EJS、tailwind将数据从服务器渲染到客户端。
有几点您需要考虑。
客户端没有实现socket客户端。 您刚刚在服务器端创建了socket.io,您需要在客户端实现连接。
我对服务器实现也持保留态度。您应该创建一个单独的 socket.io 服务器并连接到该服务器,并且 Express API 应该是单独的。
您的客户端实现应该如下所示。
<script src="http://localhost:4000/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost:4000', {
transports: [ 'websocket' ],
'reconnection': true,
'reconnectionDelay': 500,
'reconnectionDelayMax': 1000,
'reconnectionAttempts': 100
});
socket.on('connect', onConnect);
socket.on('disconnect',onDisconnect);
socket.on('connect_error', function(){
console.log('Connection Failed');
});
function onConnect(){
console.log('Connected Successully...');
}
function onDisconnect(){
console.log('Client disconnected');
}
</script>
一旦实现了与 socket.io 服务器的客户端连接。现在服务器应该能够将事件发送到客户端。 但就你的情况而言,这是行不通的。您想要使用 webapi 调用来调用 Socket.io 事件。
为此,您需要实现“socket.io-emitter”
您可以从
获取更多详情https://www.npmjs.com/package/socket.io-emitter
或来自socket.io本身的更新版本(推荐)
https://github.com/socketio/socket.io-redis-emitter
然后需要管理客户端的SocketID。将其作为附加参数发送到您的 API 帖子,因为您的 API 不知道发送事件的 SocketID,
您可以在连接函数中获取客户端套接字 ID,例如(在客户端)
function onConnect(){
console.log('Client Connected Successully...');
let ClientSocketID= socket.id;
}
在您的 API 中,您需要使用“socket.io-emitter”或 更新版本“@socket.io/redis-emitter”
您需要安装redis服务器,https://redis.io/
并且需要安装redis客户端 npm 安装 redis。
https://www.npmjs.com/package/redis
const { Emitter } = require("@socket.io/redis-emitter");
const { createClient } = require("redis");
const redisClient = createClient();
const io = new Emitter(redisClient);
io.to(CLIENT_SOCKET_ID).emit('EVENTNAME,SOME-JSON-Object);
总结: 1:创建2个独立的项目webapis和Socket.io服务器
2:在客户端实现socket.io客户端并连接到socket.io服务器。
3:在 API 中实现 socket.io-redis-emitter 并将客户端 SocketID 传递给它。并发出