文檔生成是開發人員生活中非常普遍的需求。無論是電子商務網站、管理應用程序還是其他任何東西。它可以是發票生成、保險文件準備、醫生處方、人力資源報價生成、工資單生成,你可以想到大量的用例。總是需要生成文檔。
從開發人員的角度來看,有幾種常見的方法可以完成這項工作。
這些方法對我沒有幫助??蛻粝M约憾ㄖ扑麄兊奈募?。我一直在尋找一種方法,發現eDocGen是一種單點解決方案。
與其他服務不同,eDocGen 提供了可以集成到我們應用程序中的 RestAPI。
在本文中,我們將討論如何將 eDocGen 集成到我們的 js 應用程序中,以從各種數據格式(如 JSON/XML/Database 模式)生成文檔。請免費試用以開始編碼。
讓我們潛入并編寫代碼。
出于演示目的,我創建了一個在 nodejs 上運行的示例 js 應用程序。
請按照以下步驟為我們設置編碼游樂場。
用于npm init創建 package.json
添加axios, form-data, request,xhr2開發此應用程序所需的依賴項npm install axios form-data request xhr2
我們需要一個索引文件作為我們應用程序的起點。在根目錄中創建一個 index.js 文件并修改 package.json 如下所示。
JSON
scripts": {
"start": "node index.js"
}
現在我們有一個基本的應用程序可以開始。這些步驟結束后,package.json 應該如下所示。
JSON
{
"name": "nodejs-multiple-upload-files",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"axios": "^0.27.2",
"form-data": "^4.0.0",
"request": "^2.88.2",
"xhr2": "^0.2.1"
}
}
雖然這篇文章是關于文檔生成的,但我們需要登錄才能獲取我們的訪問令牌。這是一個典型的JWT令牌,將用于授權文檔生成 API。
JavaScript
var XMLHttpRequest = require("xhr2");
var xhr = new XMLHttpRequest();
module.exports.getToken = function (callback) {
var data = JSON.stringify({
username: "<your username>",
password: "<password>",
});
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
token = JSON.parse(this.responseText).token;
console.log("User Token", token);
callback(token);
}
});
xhr.open("POST", "https://app.edocgen.com/login");
xhr.setRequestHeader("content-type", "application/json");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
};
我們可以將令牌在應用程序中緩存一個小于過期時間的時間段,并使用它來生成文檔或上傳模板。到期時間過后,我們可以刷新令牌。緩存可以是 Redis 或內存緩存。這取決于您的應用程序設計。
如上所述,eDocGen 允許用戶自定義和上傳模板。但是如何動態映射數據呢?有一些將數據映射到文檔的規則。我們將看到如何使用規則創建模板。
看看這個文件。
eDocGen{}對動態字段使用由 括起來的標簽。我們可以動態添加文字、logo、表格、條件語句、數學計算等。
例如,在上圖中,
字符串字段: {Invoice_Number}并{Invoice_Date}配置為替換為模板中的文本。模板中 {} 內的任何內容都將與輸入數據匹配并替換。
動態表: 當表中存在需要循環和替換的數據數組時,動態表將是一個不錯的選擇。表中的行以 開頭{#tablename}和結尾{/tablename}。在上面的示例中,發票表中的一行在第一列以 {#IT} 開頭,在最后一列以 {/IT} 結尾。行中的列可以有字符串字段。在我們的示例中,{Item_description}并且{Amount}
圖片: eDocGen 提供動態添加圖片到模板的功能。請按照以下步驟操作。
基于條件的動態字段(If-Else):可以使用條件標簽有條件地顯示內容。例如,當語言為英語時,文檔中會顯示{#language == "english"} 英語內容。同樣,單個文檔模板可以支持多種語言。
數學計算: eDocGen 支持基于模板中定義的公式的數學計算。可以使用以下公式計算發票中項目金額的總和。
JSON
{
IT // array of items
| summation:'Amount' // value that needs to be used for calculation
| format_number: ",” // format of the value
}
請前往JSON-to-pdf了解更多詳情。
準備好模板后,就可以將其上傳以供使用。有兩種方法。
對于演示,我使用 UI 來上傳模板。成功上傳后,我們會得到一個 ID 作為響應。這是將用于生成文檔的 ID。
如果您希望使用 API,請在此處留下 Upload API 結構供您參考。
JSON
"/api/v1/document": {
"post": {
"tags": [
"Document"
],
"description": "Upload template to eDocGen",
"produces": [
"application/json"
],
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"name": "documentFile",
"description": "file to upload",
"required": true,
"type": "file",
"in": "formData"
},
{
"name": "x-access-token",
"in": "header",
"description": "JWT auth token from login",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "Successfully uploaded document file"
},
"other": {
"description": "Operation failed"
}
}
}
}
現在我們準備好了模板。讓我們生成文檔。
文檔生成有兩個階段。
我們要求生成包含所需詳細信息的文檔,并得到確認。該過程異步發生在屏幕后面。
應用程序接口:POST-/api/v1/document/generate/bulk
表格數據
文檔 ID | 模板的id |
格式 | pdf/docx(模板應支持格式) |
輸出文件名 | 輸出文件的文件名。 |
輸入文件 | 該文件包含標記值。支持 json、xlsx 和 xml。 |
內容類型 | 多部分/表單數據 |
x-訪問令牌 | 來自登錄的 JWT 身份驗證令牌 |
inputFile 中的數據應該是模板定義的結構。例如,對于上面的模板映射將如下所示。
可以使用從上述步驟中獲得的輸出 ID 和輸出文件的名稱下載生成的文檔。
我們將在這里使用兩個 API。
由于文檔生成是異步發生的,要知道文檔是否生成,我們將使用/api/v1/output/nameapi。
來自 API 的成功響應/api/v1/output/name將下載文件。
我將這兩個步驟組合在一個 js 文件中,如下所示。
爪哇
let login = require("../edocgen_login");
const fs = require("fs");
const uuid = require("uuid");
const FormData = require("form-data");
let axios = require("axios");
let fileName = uuid.v4();
const headers = {
"Content-Type": "multipart/form-data",
"x-access-token": "null",
};
const hostName = "https://app.edocgen.com/api/v1/document/generate/bulk";
const outputFormat = "<format>";// pdf / docx
const documentId = "<template_id>"; // id of the template we want to use
module.exports.generateFiles = function () {
let authToken = login.getToken(function handleUsersList(token) {
headers["x-access-token"] = token;
var formBody = new FormData();
formBody.append("documentId", documentId);
formBody.append("format", outputFormat);
formBody.append("outputFileName", fileName);
// json data for the template
formBody.append("inputFile", fs.createReadStream("./JSON_Data_Single.json")); // local path forjson file
let config = {
method: "post",
url: hostName,
headers: headers,
data: formBody,
};
console.log(`https://app.edocgen.com/api/v1/output/name/${fileName}.${outputFormat}`);
let config_output = {
method: "get",
url:`https://app.edocgen.com/api/v1/output/name/${fileName}.${outputFormat}`,
headers: headers,
};
const MAX_RETRY = 50;
let currentRetry = 0;
// max retry for 50 times
function errorHandler() {
if (currentRetry < MAX_RETRY) {
currentRetry++;
console.log("Document is not prepared yet! Retrying...");
sendWithRetry(processResponse);
} else {
console.log("No luck. Document is not generated. Retried multiple times.");
}
}
// sendWithRetry checks for file existence
// on success, it proceeds to download the file
// on failure, it retries
// todo: introduce spin lock
function sendWithRetry(callback) {
axios(config_output)
.then(function (response) {
if (response.data.output.length !== 1) {
throw new axios.Cancel("Document is not found. Throw error.");
} else {
callback(response);
}
})
.catch(errorHandler);
}
axios(config)
.then(function (response) {
sendWithRetry(processResponse);
})
.catch(function (error) {
console.log(error);
});
});
};
function processResponse(response) {
const outputId = response.data.output[0]._id;
console.log(
"Output Document is Generated. Id = ",
response.data.output[0]._id
);
let config_download = {
method: "get",
url: `https://app.edocgen.com/api/v1/output/download/${outputId}`,
headers: headers,
responseType: "arraybuffer",
};
axios(config_download)
.then(function (response) {
console.log("Output file is downloaded " + `${fileName}.${outputFormat}`);
fs.writeFileSync(`./${fileName}.${outputFormat}`, response.data);
})
.catch(function (error) {
console.log("Error while downloading");
console.log(error);
});
}
當數據為單個 JSON 時,將生成給定格式的單個文檔。
當數據是對象數組時,將生成每個數組元素的文檔并將其壓縮到文件中。
XML 數據的過程很簡單。我們需要做的就是傳遞 XML 文件來代替 JSON 數據。
就像JSON to document,XML to Document 我們也需要documentId, outputFileName, format and inputFile。除輸入文件外,與 JSON 相同的所有內容都將是 XML 文件。
示例 XML 數據如下所示
XML
<?xml version="1.0" encoding="UTF-8" ?>
<marker>
<values>
<Invoice_Number>SBU-2053501</Invoice_Number>
<Invoice_Date>31-07-2020</Invoice_Date>
<Terms_Payment>Net 15</Terms_Payment>
<Company_Name>ABC company</Company_Name>
<Billing_Contact>ABC-Contact1</Billing_Contact>
<Address>New york, United State</Address>
<Email>support@edocgen.com</Email>
<Logo>621cd2b783a6095d7b15a443</Logo>
<Sum1>6,751</Sum1>
<para>61b334ee7c00363e11da3439</para>
<ITH>
<Heading1>Item Description</Heading1>
<Heading2>Amount</Heading2>
</ITH>
<IT>
<Item_Description>Product Fees: X</Item_Description>
<Amount>5,000</Amount>
</IT>
</values>
<marker>
我為 XML 作為數據源所做的代碼更改很簡單,如下所示
JavaScript
var formBody = new FormData();
formBody.append("documentId", documentId);
formBody.append("format", outputFormat);
formBody.append("outputFileName", fileName);
formBody.append("inputFile", fs.createReadStream("./XML_Invoice.xml"));
從數據庫生成文檔幾乎與其他數據源相同。但在這種情況下,我們需要提供連接詳細信息和 SQL 查詢,而不是上傳 inputFile。
SQL 查詢的輸出列應與文檔模板中的標簽匹配。
讓我們看看如何在代碼中進行配置。
JavaScript
const templateId = "<template id>";
const dbVendor = "mysql";
const dbUrl = "<jdbc connection URL>";
const dbLimit = "100";
const dbPassword = "<database password>";
const dbQuery = "SELECT JSON_ARRAY(first, last) FROM customers;";
const outputFormat = "pdf";
// form data prepareation
let formBody = new FormData();
formBody.append("documentId", templateId);
formBody.append("format", outputFormat);
formBody.append("dbVendor", dbVendor);
formBody.append("dbUrl", dbUrl);
formBody.append("dbLimit", dbLimit);
formBody.append("dbPassword", dbPassword);
formBody.append("dbQuery", dbQuery);
formBody.append("outputFileName", fileName);
其他一切都將保持不變。
eDocGen 提供了通過電子郵件發送生成的文檔的功能。
應用程序接口:POST-/api/v1/output/email
出局 | 將需要通過電子郵件發送的輸出 ID 放在這里 |
電子郵件ID | 將用戶電子郵件放在這里 |
內容類型 | 多部分/表單數據 |
x-訪問令牌 | 來自登錄的 JWT 身份驗證令牌 |
let login = require("../edocgen_login");
let axios = require("axios");
const hostName = "https://app.edocgen.com/api/v1/output/email";
const headers = {
"Content-Type": "application/json",
"x-access-token": "null",
};
const outId = "<output ID>"; // Put output ID here which need to be sent via email
const emailId = "<user email>"; // Put user email here
module.exports.generateFiles = function () {
let authToken = login.getToken(function handleUsersList(token) {
headers["x-access-token"] = token;
let payload = { outId: outId, emailId: emailId };
let config = {
method: "post",
url: hostName,
headers: headers,
data: payload,
};
axios(config)
.then(function (response) {
console.log("Mail sent");
})
.catch(function (error) {
console.log(error);
});
});
};
來自 eDocGen 的電子郵件如下所示
還有很多其他的功能我在這里無法涵蓋。但我希望這篇文章可以為您提供一個從哪里開始的想法。
前一陣兒被某網站的 JS 反爬流程難住了,至今也沒明白它的反扒原理和攻破方法。最終找到了一個自動化腳本工具 autoit 3,用一個笨方法將人手動點擊瀏覽器的動作腳本化,達到網頁數據獲取目的,拿到網頁文件后,再用代碼解析,曲線完成任務。
本文將介紹這個自動化的過程,并帶編寫一個完整的 autoit 3 爬蟲腳本,希望對各位讀者朋友有所啟發。
以國家信息安全漏洞共享平臺為例,它在返回數據前發起了兩次 512 響應,第三次瀏覽器帶著動態生成的 Cookie 信息才能得到數據。
這次咱們直接從網頁入手,操作鍵盤找到“下一頁” 按鈕,按下 Enter 鍵完全請求。通過鍵盤定位到 “下頁” 按鈕的過程為:
接著就可以編寫自動化腳本了,把剛剛的手動操作翻譯成腳本命令:
這個流程,對其他高反扒的信息發布網站,也是適用的。
按照上面的流程,編寫 autoit 自動化腳本,創建一個 myspider.au3 文件:
#include <AutoItConstants.au3>
;;切換為英文輸入法,保證瀏覽器輸入正常
$hWnd = WinGetHandle("[ACTIVE]");$hWnd 為目標窗口句柄,這里設置的是當前活動窗口
$ret = DllCall("user32.dll", "long", "LoadKeyboardLayout", "str", "08040804", "int", 1 + 0)
DllCall("user32.dll", "ptr", "SendMessage", "hwnd", $hWnd, "int", 0x50, "int", 1, "int", $ret[0])
$url = "https://www.cnvd.org.cn/flaw/list.htm"
spiderData($url)
Func spiderData($url)
;;打開 Chrome 瀏覽器窗口
$chromePath = "C:\Users\admin\AppData\Local\Google\Chrome\Application\chrome.exe"
Run($chromePath)
;;登錄窗口顯示
WinWaitActive("[CLASS:Chrome_WidgetWin_1]")
;; 休息2000毫秒
Sleep(2000)
;; 移動窗口
WinMove("[CLASS:Chrome_WidgetWin_1]", "打開新的標簽頁 - Google Chrome", 0, 0,1200,740,2)
;; 休息500毫秒
Sleep(500)
;;地址欄輸入URL 并按下 Enter 鍵
Send($url)
Sleep(500)
Send("{enter}")
Sleep(3000)
;; 循環爬取需要的頁數,測試只爬3頁
For $i = 1 To 3 Step 1
;;打開右鍵另存為按鈕: Ctrl+S
send("^s")
Sleep(2000)
WinWait("[CLASS:#32770]","",10)
;;將存儲路徑設置到另存為組件輸入框 Edit1 里
$timeNow = @YEAR & "" & @MON & "" & @MDAY & "" & @HOUR & "" & @MIN
$savePath = "F:\A2021Study\ListData\" &$timeNow & "_page" & $i & ".html"
ControlSetText("另存為","", "Edit1", $savePath)
;;點擊確定
ControlClick("另存為","","Button2")
;;再次確定
WinWait("[CLASS:#32770]","",10)
ControlClick("確認另存為","","Button1")
;; 等待保存操作完成
Sleep(3000)
;; 定位到下一頁按鈕,并觸發點擊下一頁
send("{END}")
Send("+{TAB 15}")
Send("{enter}")
;;點擊確定后,等待網頁加載完成
Sleep(3000)
Next
;; 整個操作完成,則關閉瀏覽器
Send("^w")
EndFunc
腳本編寫過程中,有幾點需要注意:
因為爬蟲要作為定時任務運行的,為避免打開太多瀏覽器窗口,因此需要在腳本結束時關閉瀏覽器。
數據爬取一般分為列表頁和詳情頁,定位點擊每一條詳情的過程比較麻煩,所以爬取詳情頁面的和列表分開,用 Java 代碼解析所有詳情 URL 后,再由另一個 autoit 腳本去獲取詳情頁面,這個流程大家可以自己寫一下,這里就不詳細介紹了。
最后再匯總下整個爬取的流程:
第一步,執行爬取列表的 autoit 腳本,得到列表頁面 html;
第二步,解析列表頁 html ,得到所有詳情頁面的 URL ,寫入到文件中;
第三步,執行爬取詳情頁面的 autoit 腳本,它遍歷第二步的目標 URL ,得到詳情頁 html ;
第四步,解析詳情頁 html 文件,得到詳情數據。
總控流程、第二步和第四步的解析都用 Java 代碼完成,用 Runtime.getRuntime().exec("cmd /c E:\A2021Study\Autoit3\myspider.au3") 調用腳本,文件路徑是反斜杠。
這個方法雖然有點笨,但完全是人工操作瀏覽器,能夠對抗反爬蟲策略,感興趣的朋友可以執行下本文的腳本試試。
autoit 還是蠻有意思的,語法也很簡單,DirCreate 創建文件,iniread 讀取配置項,一行代碼頂 Java 幾十行,不得不承認 Java 操作文件才是最麻煩的哇!
:如何用 JS 一次獲取 HTML 表單的所有字段 ?
考慮一個簡單的 HTML 表單,用于將任務保存在待辦事項列表中:
<form>
<label for="name">用戶名</label>
<input type="text" id="name" name="name" required>
<label for="description">簡介</label>
<input type="text" id="description" name="description" required>
<label for="task">任務</label>
<textarea id="task" name="task" required></textarea>
<button type="submit">提交</button>
</form>
上面每個字段都有對應的的type,ID和 name屬性,以及相關聯的label。用戶單擊“提交”按鈕后,我們如何從此表單中獲取所有數據?
有兩種方法:一種是用黑科技,另一種是更清潔,也是最常用的方法。為了演示這種方法,我們先創建form.js,并引入文件中。
首先,我們在表單上為Submit事件注冊一個事件偵聽器,以停止默認行為(它們將數據發送到后端)。
然后,使用this.elements或event.target.elements訪問表單字段:
相反,如果需要響應某些用戶交互而動態添加更多字段,那么我們需要使用FormData。
首先,我們在表單上為submit事件注冊一個事件偵聽器,以停止默認行為。接著,我們從表單構建一個FormData對象:
const form = document.forms[0];
form.addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(this);
});
除了append()、delete()、get()、set()之外,FormData 還實現了Symbol.iterator。這意味著它可以用for...of 遍歷:
const form = document.forms[0];
form.addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(this);
for (const formElement of formData) {
console.log(formElement);
}
})
除了上述方法之外,entries()方法獲取表單對象形式:
const form = document.forms[0];
form.addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(this);
const entries = formData.entries();
const data = Object.fromEntries(entries);
});
這也適合Object.fromEntries() (ECMAScript 2019)
為什么這有用?如下所示:
const form = document.forms[0];
form.addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(this);
const entries = formData.entries();
const data = Object.fromEntries(entries);
// send out to a REST API
fetch("https://some.endpoint.dev", {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
})
.then(/**/)
.catch(/**/);
});
一旦有了對象,就可以使用fetch發送有效負載。
小心:如果在表單字段上省略name屬性,那么在FormData對象中剛沒有生成。
要從HTML表單中獲取所有字段,可以使用:
使用FormData構建具有所有字段的對象,之后可以轉換,更新或將其發送到遠程API。*
作者:VALENTINO GAGLIARDI 譯者:前端小智 來源:valentinog
原文:https://www.valentinog.com/blog/form-data/
*請認真填寫需求信息,我們會在24小時內與您取得聯系。