使用electron-vue+go寫一個處理excel表格小軟件(2)

問題

使用node-xlsx處理excel一次最多能處理30M的文件,所以來個80M的話就要手動拆成3個文件,這看起來太蠢了,需要換一個能處理大excel文件的庫。將界面優化成這樣子:
在這裏插入圖片描述

思路

經過測試,js最受歡迎的庫js-xlsx也不能處理80M的excel文件,所以可能需要使用另外一種語言的庫,如果使用另外一種語言,需要考慮怎麼選語言,上手難度,開發成本,還有就是是否能嵌入到electron的桌面應用裏,如果能嵌入,該怎麼進行數據通信等等問題。

  1. 在electron裏支持用node開闢一個子進程去運行其他語言打包出來的可執行文件,這樣子就可以實現electron裏跑其他語言寫的程序
  2. 經過了解,python和go都比較容易上手,而且python有xlsxWriter庫,go有excelize庫都可以處理超大的excel文件,都是解析出來一個數組,後面處理數組就行。 因爲python是解釋型語言,打包成可執行文件時會連解釋器一起打包,所以就算只寫了幾行代碼,打包出來的包也會是幾百兆,go是編譯型語言,編譯打包出來的包比較小,嵌入electron以後比較輕量,所以選用了go語言
  3. 因爲還不熟悉新語言語法,所以做一個tcp的socket通信,將go進程解析出來的數組傳到node進程,處理數組生成文件部分就可以用之前寫好的node部分邏輯進行處理了
  4. 之前node進程裏只處理了生成文件,如果需要用go的excelize庫生成excel格式的話還需要將放置生成文件的文件夾路徑返回給go進程,go再批量處理excel文件格式

go部分

主要流程

  1. 創建一個tcp的socket
func createServer() {
	// 建立socket,監聽端口  第一步:綁定端口
	netListen, err := net.Listen("tcp", "127.0.0.1:9800")
	CheckError(err)
	// defer延遲關閉改資源,以免引起內存泄漏
	defer netListen.Close()

	Log("Waiting for clients")
	for {
		conn, err := netListen.Accept() // 第二步:獲取連接
		if err != nil {
			continue // 出錯退出當前一次循環
		}

		Log(conn.RemoteAddr().String(), " tcp connect success")
		// 這句代碼的前面加上一個 go,就可以讓服務器併發處理不同的Client發來的請求
		go handleConnection(conn) // 使用goroutine來處理用戶的請求
	}
}
func handleConnection(conn net.Conn) {
	buffer := make([]byte, 2048)
	for { // 無限循環
		n, err := conn.Read(buffer) // 第三步:讀取從該端口傳來的內容
		words := "ok"               // 向鏈接中寫數據
		conn.Write([]byte(words))
		if err != nil {
			Log(conn.RemoteAddr().String(), " connection error: ", err)
			return // 出錯後返回
		}
		tcpData := string(buffer[:n]) // 接收到的數據
	}
}
  1. 接收待處理excel文件的絕對路徑,返回解析出來的數組給node進程處理並生成拆分好的excel文件
  2. 接收放置輸出文件的文件夾絕對路徑,批量處理裏面的excel文件格式

遇到的坑

  1. go一些庫比較新的版本需要在https://pkg.go.dev/搜,在github裏直接搜名字可能搜不到想要的版本鏈接,比如excelize這個庫,直接在github搜是找不到v2的鏈接的
  2. 靜態語言寫起來比動態語言麻煩挺多,走到哪一步都是需要確定類型的
  3. 在和node進程通信的時候要適當地加time.Sleep(),不然調用conn.Write()的時候會使本來應該兩次傳遞的消息變成了一次

node部分

主要流程

  1. 在electron的主進程裏開闢一個子進程來運行go程序
const isWin = /^win/.test(process.platform)
console.log(process.platform)
const path = require('path')

let pyProc = null

const createPyProc = () => {
  let port = '4242'
  let script = path.resolve(__dirname, 'go', isWin ? 'testGo.exe' : 'testGo')
  if (process.env.NODE_ENV === 'production') {
    script = path.join(process.resourcesPath, 'app.asar.unpacked/go', isWin ? 'testGo.exe' : 'testGo')
  }
  console.log(script)
  pyProc = require('child_process').execFile(script, [port]) // 開闢一個子進程運行go打包出來的可執行程序
  if (pyProc != null) {
    console.log('child process success')
  }
}

const exitPyProc = () => {
  pyProc.kill()
  pyProc = null
}

app.on('ready', createPyProc)
app.on('will-quit', exitPyProc)
  1. 在界面點擊開始處理後,開始與go進程進行tcp通信
async () => { 
	let tcpData = ''
	const client = net.connect({ port: 9800 })
	client.write(JSON.stringify(this.sourcePathList))
	client.on('data', async data => {
	  tcpData += data.toString()
	  if (tcpData.startsWith('[')) {
	    // 第一個通信會回拋數組
	    if (tcpData.endsWith('數據傳輸結束標記')) {
	      data = JSON.parse(tcpData.replace(/數據傳輸結束標記$/, ''))
	      for (let i = 0; i < data.length; i++) { // 遍歷循環用node處理每個表的數據,拆分並生成文件
	        await splitXlsx(
	          data[i],
	          i + 1,
	          data.length,
	          this.folderPath,
	          this.log,
	          this.sourcePathList.length
	        ).catch(e => {
	          this.log.error = e
	          this.isLoading = false
	        })
	      }
	      tcpData = ''
	      // 處理完以後,傳文件夾過去給go批量生成xlsx格式
	      client.write(this.folderPath)
	    }
	  } else {
	    // 之後的通信都是回拋處理狀態
	    if (tcpData.startsWith('處理中斷')) {
	      this.log.error = tcpData
	      this.isLoading = false
	    } else {
	      this.log.text = tcpData
	      if (this.log.text.startsWith('處理結束')) {
	        this.isLoading = false
	      }
	    }
	    tcpData = ''
	  }
	})
}
  1. 待處理文件絕對路徑傳給go進程,go進程解析出一個大數組後傳回給node進程處理拆分數組並循環生成新的excel文件,最終將輸出文件所在文件夾絕對路徑傳給go進程,go進程批量處理excel文件的樣式格式和打印格式

遇到的坑

  1. 因爲需要運行在win和mac系統,所以需要根據系統環境來選擇go的可執行程序文件
  2. 在打包electron安裝包的時候,需要將go的可執行程序文件打包進去,我用的是electron-builder打包,需要在package.json裏增加打包配置項
    "build": {
        // ...
        "extraResources": [
          {
            "from": "src/main/go", // go文件夾裏有go的可執行程序文件
            "to": "app.asar.unpacked/go"
          }
        ],
        // ...
     }
    
    這樣子就相當於直接複製這些不需打包的靜態文件去到安裝包裏的app.asar.unpacked文件夾裏面,在生產環境取文件路徑時可以這樣取
     if (process.env.NODE_ENV === 'production') {
        script = path.join(process.resourcesPath, 'app.asar.unpacked/go', isWin ? 'testGo.exe' : 'testGo')
      }
    
    win/mac系統都是可以這樣配置的
  3. 由於go是解析完全部80M的文件,解析出來的數組超級大,在tcp中傳數據緩衝區會不夠用,所以數據變成分塊傳輸,這樣node進程就無法確定數據傳輸完畢的時機,需要在go進程傳輸的數據上加上結束標識,這樣在node進程就需要自己拼接分塊傳輸的數據,並且識別結束標識。
  4. 在excel中的時間戳和js計算出來的時間戳是不一樣的
    兩者的換算公式如下:
    js時間戳轉excel時間戳
    const  excelTimeNum = (Number(new Date()) / 1000 + 8 * 3600) / 86400 + 70 * 365 + 19
    
    excel時間戳轉js時間戳
    const jsTimeNum = new Date(((excelTimeNum - 19 - 70 * 365) * 86400 - 8 * 3600) * 1000)
    

源碼鏈接

electron部分
go部分

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章