欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于golang編寫一個(gè)word/excel/ppt轉(zhuǎn)pdf的工具

 更新時(shí)間:2024年11月15日 09:49:21   作者:翻斗  
這篇文章主要為大家詳細(xì)介紹了如何基于golang編寫一個(gè)word/excel/ppt轉(zhuǎn)pdf的工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

需求

公司客戶有需求,需要轉(zhuǎn)換doc文件為pdf文件,并且保持格式完全不變。

工程師用各種Java類庫(kù),無(wú)論是doc4j、POI還是Aspose.Doc、Libreoffice組件還是各種線上API服務(wù),轉(zhuǎn)換結(jié)果都不甚滿意。

于是我這邊接手這個(gè)活了。

調(diào)研

其實(shí),最符合客戶需求的莫過于原生Windows Office Word的導(dǎo)出功能了。

需要能夠操作Windows的Office Word程序,那么需要能夠直接訪問其系統(tǒng)組件,需要類似COM/OLE系統(tǒng)庫(kù),說干就干。

1、運(yùn)維做弄了一個(gè)配置比較低的EC2機(jī)器,windows10系統(tǒng)。

2、我這邊找了一些庫(kù),python的comtypes.client,但是有點(diǎn)問題,單跑沒問題,做成服務(wù),在web線程中做這個(gè)事情,就有問題,具體找了下,應(yīng)該還是線程問題,想了想,不做了(因?yàn)楸旧砭筒幌胗胮ython寫, )

3、趕緊找了下golang中對(duì)應(yīng)的OLE庫(kù),找到了一個(gè),看了下文檔,直接寫了出來(lái)。

實(shí)現(xiàn)

話不多說,直接上核心代碼看看:

下面是基礎(chǔ)的解析過程,其實(shí)就是模擬以下四個(gè)步驟:

1、打開Office對(duì)應(yīng)的程序(Word/Excel/PPT)

2、導(dǎo)出為PDF文件

3、關(guān)閉文件

4、退出Office程序

基礎(chǔ)邏輯

package office

import (
	ole "github.com/go-ole/go-ole"
	"github.com/go-ole/go-ole/oleutil"
	log "github.com/sirupsen/logrus"
)

/// 更多內(nèi)容請(qǐng)參考官方COM文檔 https://docs.microsoft.com/zh-cn/office/vba/api/word.application
type Operation struct {
	OpType    string
	Arguments []interface{}
}

/// 部分應(yīng)用不允許隱藏 ,比如ppt,所以Visible需要設(shè)定下
type ConvertHandler struct {
	FileInPath      string
	FileOutPath     string
	ApplicationName string
	WorkspaceName   string
	Visible         bool
	DisplayAlerts   int
	OpenFileOp      Operation
	ExportOp        Operation
	CloseOp         Operation
	QuitOp          Operation
}

type DomConvertObject struct {
	Application *ole.IDispatch
	Workspace   *ole.IDispatch
	SingleFile  *ole.IDispatch
}

func (handler ConvertHandler) Convert() {
	ole.CoInitialize(0)
	defer ole.CoUninitialize()

	log.Println("handle open start")
	dom := handler.Open()
	log.Println("handle open end")
	log.Println("handler in file path is " + handler.FileInPath)
	log.Println("handler out file path is " + handler.FileOutPath)

	defer dom.Application.Release()
	defer dom.Workspace.Release()
	defer dom.SingleFile.Release()

	handler.Export(dom)
	log.Println("handle export end")

	handler.Close(dom)
	log.Println("handle close end")

	handler.Quit(dom)
	log.Println("handle quit end")

}
func (handler ConvertHandler) Open() DomConvertObject {
	var dom DomConvertObject
	unknown, err := oleutil.CreateObject(handler.ApplicationName)
	if err != nil {
		panic(err)
	}
	dom.Application = unknown.MustQueryInterface(ole.IID_IDispatch)

	oleutil.MustPutProperty(dom.Application, "Visible", handler.Visible)
	oleutil.MustPutProperty(dom.Application, "DisplayAlerts", handler.DisplayAlerts)

	dom.Workspace = oleutil.MustGetProperty(dom.Application, handler.WorkspaceName).ToIDispatch()

	dom.SingleFile = oleutil.MustCallMethod(dom.Workspace, handler.OpenFileOp.OpType, handler.OpenFileOp.Arguments...).ToIDispatch()
	return dom
}

func (handler ConvertHandler) Export(dom DomConvertObject) {
	oleutil.MustCallMethod(dom.SingleFile, handler.ExportOp.OpType, handler.ExportOp.Arguments...)

}

func (handler ConvertHandler) Close(dom DomConvertObject) {
	if handler.ApplicationName == "PowerPoint.Application" {
		oleutil.MustCallMethod(dom.SingleFile, handler.CloseOp.OpType, handler.CloseOp.Arguments...)
	} else {
		oleutil.MustCallMethod(dom.Workspace, handler.CloseOp.OpType, handler.CloseOp.Arguments...)
	}
}

func (handler ConvertHandler) Quit(dom DomConvertObject) {
	oleutil.MustCallMethod(dom.Application, handler.QuitOp.OpType, handler.QuitOp.Arguments...)

不同格式的適配

支持Word/Excel/PPT轉(zhuǎn)pdf,下面是Word轉(zhuǎn)pdf的代碼:

package office

func ConvertDoc2Pdf(fileInputPath string, fileOutputPath string) {

	openArgs := []interface{}{fileInputPath}

	/// https://docs.microsoft.com/zh-cn/office/vba/api/word.document.exportasfixedformat
	exportArgs := []interface{}{fileOutputPath, 17}

	closeArgs := []interface{}{}

	quitArgs := []interface{}{}

	convertHandler := ConvertHandler{
		FileInPath:      fileInputPath,
		FileOutPath:     fileOutputPath,
		ApplicationName: "Word.Application",
		WorkspaceName:   "Documents",
		Visible:         false,
		DisplayAlerts:   0,
		OpenFileOp: Operation{
			OpType:    "Open",
			Arguments: openArgs,
		},
		ExportOp: Operation{
			OpType:    "ExportAsFixedFormat",
			Arguments: exportArgs,
		},
		CloseOp: Operation{

			OpType:    "Close",
			Arguments: closeArgs,
		},
		QuitOp: Operation{

			OpType:    "Quit",
			Arguments: quitArgs,
		},
	}
	convertHandler.Convert()
}

提供web service接口

package web

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"office-convert/office"
	"os"
	"path"
	"path/filepath"
	"runtime/debug"
	"strconv"

	log "github.com/sirupsen/logrus"
)

const PORT = 10000
const SAVED_DIR = "files"

type ConvertRequestInfo struct {
	FileInUrl  string `json:"file_in_url"`
	SourceType string `json:"source_type"`
	TargetType string `json:"target_type"`
}

func logStackTrace(err ...interface{}) {
	log.Println(err)
	stack := string(debug.Stack())
	log.Println(stack)
}

func convertHandler(w http.ResponseWriter, r *http.Request) {
	defer func() {
		if r := recover(); r != nil {
			w.WriteHeader(503)
			fmt.Fprintln(w, r)
			logStackTrace(r)
		}
	}()
	if r.Method != "POST" {
		w.WriteHeader(400)
		fmt.Fprintf(w, "Method not support")
		return
	}

	var convertRequestInfo ConvertRequestInfo
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Println(err)
	}
	json.Unmarshal(reqBody, &convertRequestInfo)

	log.Println(convertRequestInfo)
	log.Println(convertRequestInfo.FileInUrl)

	downloadFile(convertRequestInfo.FileInUrl)

	fileOutAbsPath := getFileOutAbsPath(convertRequestInfo.FileInUrl, convertRequestInfo.TargetType)
	convert(convertRequestInfo)

	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/octet-stream")
	//文件過大的話考慮使用io.Copy進(jìn)行流式拷貝
	outFileBytes, err := ioutil.ReadFile(fileOutAbsPath)
	if err != nil {
		panic(err)
	}
	w.Write(outFileBytes)

}

func convert(convertRequestInfo ConvertRequestInfo) {

	fileOutAbsPath := getFileOutAbsPath(convertRequestInfo.FileInUrl, convertRequestInfo.TargetType)
	switch convertRequestInfo.SourceType {
	case "doc", "docx":
		office.ConvertDoc2Pdf(getFileInAbsPath(convertRequestInfo.FileInUrl), fileOutAbsPath)
		break
	case "xls", "xlsx":
		office.ConvertXsl2Pdf(getFileInAbsPath(convertRequestInfo.FileInUrl), fileOutAbsPath)
		break
	case "ppt", "pptx":
		office.ConvertPPT2Pdf(getFileInAbsPath(convertRequestInfo.FileInUrl), fileOutAbsPath)
		break
	}
}

func getNameFromUrl(inputUrl string) string {
	u, err := url.Parse(inputUrl)
	if err != nil {
		panic(err)
	}
	return path.Base(u.Path)
}

func getCurrentWorkDirectory() string {
	cwd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	return cwd
}

func getFileInAbsPath(url string) string {
	fileName := getNameFromUrl(url)
	currentWorkDirectory := getCurrentWorkDirectory()
	absPath := filepath.Join(currentWorkDirectory, SAVED_DIR, fileName)
	return absPath
}

func getFileOutAbsPath(fileInUrl string, targetType string) string {
	return getFileInAbsPath(fileInUrl) + "." + targetType
}

func downloadFile(url string) {
	log.Println("Start download file url :", url)
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	fileInAbsPath := getFileInAbsPath(url)
	dir := filepath.Dir(fileInAbsPath)
	// log.Println("dir is " + dir)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		log.Println("dir is not exists")
		os.MkdirAll(dir, 0644)
	}
	out, err := os.Create(fileInAbsPath)
	log.Println("save file to " + fileInAbsPath)
	if err != nil {
		panic(err)
	}

	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		panic(err)
	}

	log.Println("Download file end url :", url)
}

func StartServer() {

	log.Println("start service ...")
	http.HandleFunc("/convert", convertHandler)
	http.ListenAndServe("127.0.0.1:"+strconv.Itoa(PORT), nil)
}

部署/使用

編譯 (可跳過)

如果要編譯源碼,得到exe文件,可以執(zhí)行命令go build -ldflags "-H windowsgui" 生成 office-convert.exe 。不想編譯的話,可以在prebuilt下找到對(duì)應(yīng)exe文件。

運(yùn)行

方法一:普通運(yùn)行

雙擊執(zhí)行 office-convert.exe 即可,但是如果程序報(bào)錯(cuò),或者電腦異常關(guān)機(jī),不會(huì)重啟

方法二:后臺(tái)運(yùn)行(定時(shí)任務(wù)啟動(dòng),可以自動(dòng)恢復(fù))

windows要做到定時(shí)啟動(dòng)/自動(dòng)恢復(fù),還挺麻煩的。。。

1、復(fù)制文件

將prebuilt下兩個(gè)文件復(fù)制到 C:\Users\Administrator\OfficeConvert\ 目錄下

2、修改COM訪問權(quán)限

當(dāng)我們以服務(wù)、定時(shí)任務(wù)啟動(dòng)程序的時(shí)候,會(huì)報(bào)錯(cuò),提示空指針錯(cuò)誤。

原因就是微軟限制了COM組件在非UI Session的情況下使用(防止惡意病毒之類),如果要允許,需要做如下處理:

參考這里

  • Open Component Services (Start -> Run, type in dcomcnfg)
  • Drill down to Component Services -> Computers -> My Computer and click on DCOM Config
  • Right-click on Microsoft Excel Application and choose Properties
  • In the Identity tab select This User and enter the ID and password of an interactive user account (domain or local) and click Ok

注意,上圖是演示,賬號(hào)密碼填寫該機(jī)器的Administrator賬號(hào)密碼

3、定時(shí)任務(wù)

創(chuàng)建windows定時(shí)任務(wù),每1分鐘調(diào)用check_start.bat文件,該文件自動(dòng)檢查office-convert.exe是否運(yùn)行,沒有就啟動(dòng)。

注意: 上圖只是演示,具體位置填寫 C:\Users\Administrator\OfficeConvert\check_start.bat

Web部署

使用nginx作為反向代理,具體位置在 C:\Users\Administrator\nginx-1.20.2\nginx-1.20.2下,修改conf/nginx.conf文件,代理127.0.0.1:10000即可,
有公網(wǎng)IP(比如xxx.com)的話,配置DNS解析convert-tools.xxx.com到此機(jī)器ip。

server {
        listen       80;
        server_name  convert-tools.xxx.net;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://127.0.0.1:10000;
        }
        # ...其他設(shè)置
}

請(qǐng)求

已部署到Windows機(jī)器,訪問URL:http://127.0.0.1:10000 (如果上面配置了域名,則訪問 http://convert-tools.xxx.com/convert)

請(qǐng)求相關(guān)

Method : POST

Content-Type: application/json

Body:

{
    "file_in_url":"https://your_docx_file_url",
    "source_type":"docx",
    "target_type":"pdf"
}
參數(shù) 是否必須取值范圍說明
file_in_url滿足下面source_type的各類文檔url待轉(zhuǎn)換的文檔的網(wǎng)絡(luò)連接
source_type[doc,docx,xls,xlsx,ppt,pptx]文檔類型
target_typepdf暫時(shí)只支持PDF,后續(xù)會(huì)支持更多

響應(yīng)

根據(jù)HTTP狀態(tài)碼做判斷

200 : ok

其他: 有錯(cuò)

Body:

轉(zhuǎn)換的文件的二進(jìn)制流

如果status_code非200,是對(duì)應(yīng)的報(bào)錯(cuò)信息

到此這篇關(guān)于基于golang編寫一個(gè)word/excel/ppt轉(zhuǎn)pdf的工具的文章就介紹到這了,更多相關(guān)go word/excel/ppt轉(zhuǎn)pdf內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 簡(jiǎn)單聊聊Golang中defer預(yù)計(jì)算參數(shù)

    簡(jiǎn)單聊聊Golang中defer預(yù)計(jì)算參數(shù)

    在golang當(dāng)中defer代碼塊會(huì)在函數(shù)調(diào)用鏈表中增加一個(gè)函數(shù)調(diào)用,下面這篇文章主要給大家介紹了關(guān)于Golang中defer預(yù)計(jì)算參數(shù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-03-03
  • Golang Defer基礎(chǔ)操作詳解

    Golang Defer基礎(chǔ)操作詳解

    在golang當(dāng)中,defer代碼塊會(huì)在函數(shù)調(diào)用鏈表中增加一個(gè)函數(shù)調(diào)用。這個(gè)函數(shù)調(diào)用不是普通的函數(shù)調(diào)用,而是會(huì)在函數(shù)正常返回,也就是return之后添加一個(gè)函數(shù)調(diào)用。因此,defer通常用來(lái)釋放函數(shù)內(nèi)部變量
    2022-10-10
  • 使用Golang的gomail庫(kù)實(shí)現(xiàn)郵件發(fā)送功能

    使用Golang的gomail庫(kù)實(shí)現(xiàn)郵件發(fā)送功能

    本篇博客詳細(xì)介紹了如何使用Golang語(yǔ)言中的gomail庫(kù)來(lái)實(shí)現(xiàn)郵件發(fā)送的功能,首先,需要準(zhǔn)備工作,包括安裝Golang環(huán)境、gomail庫(kù),以及申請(qǐng)126郵箱的SMTP服務(wù)和獲取授權(quán)碼,其次,介紹了在config文件中配置SMTP服務(wù)器信息的步驟
    2024-10-10
  • go語(yǔ)言版的ip2long函數(shù)實(shí)例

    go語(yǔ)言版的ip2long函數(shù)實(shí)例

    這篇文章主要介紹了go語(yǔ)言版的ip2long函數(shù),實(shí)例分析了Go語(yǔ)言實(shí)現(xiàn)的ip2long函數(shù)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Go?復(fù)合類型之字典類型使用教程示例

    Go?復(fù)合類型之字典類型使用教程示例

    這篇文章主要為大家介紹了Go復(fù)合類型之字典類型使用教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • 詳解如何修改Go結(jié)構(gòu)體的私有字段

    詳解如何修改Go結(jié)構(gòu)體的私有字段

    在 Go 語(yǔ)言中,結(jié)構(gòu)體字段的訪問權(quán)限是由字段名的首字母決定的:首字母大寫表示公共字段(public),首字母小寫表示私有字段(private),本文給大家介紹了如何修改Go結(jié)構(gòu)體的私有字段,需要的朋友可以參考下
    2025-01-01
  • Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單網(wǎng)絡(luò)端口掃描方法

    Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單網(wǎng)絡(luò)端口掃描方法

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單網(wǎng)絡(luò)端口掃描方法,實(shí)例分析了Go語(yǔ)言網(wǎng)絡(luò)程序的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過程解析示例

    Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過程解析示例

    這篇文章主要為大家介紹了Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過程解析示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 一文帶你了解如何正確理解和使用Golang中nil

    一文帶你了解如何正確理解和使用Golang中nil

    在?Golang?中,nil?是一個(gè)預(yù)定義的標(biāo)識(shí)符,在不同的上下文環(huán)境中有不同的含義,但通常表示“無(wú)”、“空”或“零值”,本文主要來(lái)帶大家了解下nil的正確使用,需要的可以參考下
    2023-12-12
  • golang?run時(shí)報(bào)undefined錯(cuò)誤的解決

    golang?run時(shí)報(bào)undefined錯(cuò)誤的解決

    這篇文章主要介紹了golang?run時(shí)報(bào)undefined錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03

最新評(píng)論