從小案例學(xué)習(xí)Go語(yǔ)言-將Excel各部分內(nèi)容分發(fā)到不同的電子郵箱

關(guān)鍵點(diǎn):

  • Go語(yǔ)言讀取Excel
  • Go語(yǔ)言正則表達(dá)式
  • Go語(yǔ)言發(fā)送電子郵件

案例場(chǎng)景

今天公司行政部小妹妹跑來(lái)問(wèn)屡立,有什么辦法可以把工資條自動(dòng)發(fā)送到每個(gè)員工的企業(yè)郵箱里谎倔?公司每個(gè)員工的工資條以Excel的形式放在同一個(gè)文檔里坎拐,之前用OA發(fā)送,復(fù)制粘貼,操作相當(dāng)簡(jiǎn)單脓诡,但是公司要求改用電子郵件發(fā)送工資條后摇肌,給行政部的同事增加了較大的工作量擂红,而且每個(gè)月都需要做一次,這很浪費(fèi)時(shí)間围小,于是爽快的答應(yīng)幫忙解決昵骤。

情況梳理

公司工資條大概這個(gè)樣子的



為了方便,行政部門會(huì)把所有人的工資條按順序排列在同一個(gè)Excel文件里的同一個(gè)sheet里肯适。全貌會(huì)是以下這個(gè)樣子


Paste_Image.png

行政部的妹妹希望能夠自動(dòng)的把每個(gè)人自己的工資條發(fā)送到各自的郵箱里涉茧,所以至少得有個(gè)地方填寫郵箱號(hào)吧,于是我在每個(gè)人的工資條上增加了一行疹娶,標(biāo)記每個(gè)人的郵箱伴栓,于是文檔成了這樣

Paste_Image.png

好了,Excel格式確定下來(lái)雨饺,對(duì)于程序員來(lái)說(shuō)钳垮,他就只是個(gè)二維數(shù)組了。接下來(lái)额港,就開(kāi)始寫代碼吧饺窿。

用Go讀取Excel

Go語(yǔ)言自己有個(gè)CSV庫(kù),不過(guò)在這個(gè)場(chǎng)景里移斩,還是用github.com/tealeg/xlsx庫(kù)來(lái)處理xlsx文件更合適肚医。
創(chuàng)建go文件,將工資表與go文件放在同一個(gè)目錄向瓷,本文假設(shè)講工資表Excel 命名為 list.xlsx

package main

import (
    "bufio"
    "fmt"

    "github.com/tealeg/xlsx"
    "log"
)

func main() {
    excelFileName := "./list.xlsx"
    xlFile, err := xlsx.OpenFile(excelFileName)
    if err != nil {
        log.Fatalln("err:", err.Error())
    }
}

xlsx打開(kāi)Excel文件成功肠套,會(huì)返回一個(gè)xlsx.File對(duì)象,這個(gè)對(duì)象里除有一些基礎(chǔ)的文件操作方法猖任,還包含一個(gè)Sheets的對(duì)象你稚,這個(gè)對(duì)象是Excel文件中Sheet的map集合,可以通過(guò)遍歷獲得所有Sheet朱躺。
Sheet中包含一個(gè)名叫Rows的對(duì)象刁赖,這個(gè)對(duì)象是Sheet中所有行的集合。
Rows中包含一個(gè)名叫Cells的對(duì)象长搀,這個(gè)對(duì)象是行中所有格子的集合宇弛。

所以,一個(gè)xlsx.File對(duì)象不考慮其包含的方法的話就相當(dāng)于一個(gè)三維數(shù)組源请。

我們只需要做三次嵌套的循環(huán)就可以獲得其中的所有單元格數(shù)據(jù)枪芒,像這樣

func main() {
    excelFileName := "./list.xlsx"
    xlFile, err := xlsx.OpenFile(excelFileName)
    if err != nil {
        log.Fatalln("err:", err.Error())
    }
    for _, sheet := range xlFile.Sheets {
        for _, row := range sheet.Rows {
            for _, cell := range row.Cells {
                fmt.Printf("%s\n", cell.Value)
            }
        }
    }
}

接下來(lái)轿钠,要進(jìn)入關(guān)鍵點(diǎn),把數(shù)據(jù)讀取出來(lái)后病苗,要分隔沒(méi)個(gè)人的工資條疗垛,從之前的圖片上可以看出,當(dāng)表格中出現(xiàn)一次電子郵件內(nèi)容的單元格的時(shí)候硫朦,就是新的一個(gè)人的工資條了贷腕。所以,需要通過(guò)正則表達(dá)式判斷有沒(méi)有讀取到電子郵件的單元格咬展,如果讀取到泽裳,就要用新的存儲(chǔ)空間保存工資條的內(nèi)容。

用正則表達(dá)式找到含有Email地址的單元格

正則表達(dá)式判斷很簡(jiǎn)單破婆,創(chuàng)建一個(gè)函數(shù)涮总,讀取整行的數(shù)據(jù),如果其中出現(xiàn)了電子郵件祷舀,就返回真瀑梗,以及電子郵件字符串(這個(gè)地方可以不用穿反isEmail這個(gè)參數(shù),只需要判斷email是不是零值就可以了)

func isEmailRow(r []string) (isEmail bool, email string) {
    reg := regexp.MustCompile(`^[a-zA-Z_0-9.-]{1,64}@([a-zA-Z0-9-]{1,200}.){1,5}[a-zA-Z]{1,6}$`)
    for _, v := range r {
        if reg.MatchString(v) {
            return true, v
        }
    }
    return false, ""
}

為了后面操作方便裳扯,我用getCellValues函數(shù)將行的Cells直接讀取成字符串?dāng)?shù)組抛丽,并且過(guò)濾了空格和換行。

func getCellValues(r *xlsx.Row) (cells []string) {
    for _, cell := range r.Cells {
        txt := strings.Replace(strings.Replace(cell.Value, "\n", "", -1), " ", "", -1)
        cells = append(cells, txt)
    }
    return
}

我用了一個(gè)map來(lái)統(tǒng)一存放不同的人的工資條數(shù)據(jù)饰豺,并且用電子郵件作為鍵值亿鲜,然后將數(shù)據(jù)組裝成一個(gè)HTML的表格行代碼(因?yàn)樾枰l(fā)送HTML格式的電子郵件才能以表格的形式展現(xiàn))。于是冤吨,main里的循環(huán)代碼就變成了這樣

for _, sheet := range xlFile.Sheets {
        curMail := ""
        for _, row := range sheet.Rows {
            cells := getCellValues(row)
            //如果行包含電子郵件蒿柳,創(chuàng)建一個(gè)新字典項(xiàng)
            if isEmail, emailStr := isEmailRow(cells); isEmail {
                curMail = emailStr
            } 
            sendList[curMail] += fmt.Sprintf("<tr><td>%s</td></tr>", strings.Join(cells, "</td><td>"))
            
        }
    }

用Go語(yǔ)言發(fā)送電子郵件(SMTP)

Go語(yǔ)言發(fā)送電子郵件很簡(jiǎn)單,用標(biāo)準(zhǔn)包 net/smtp就足夠了漩蟆。

先封裝一個(gè)發(fā)送郵件的函數(shù),用官方的例子改造一下垒探。

func sendToMail(user, password, host, to, subject, body, mailtype string) error {
   auth := smtp.PlainAuth("", user, password, strings.Split(host, ":")[0])
   msg := []byte("To: " + to + "\r\nFrom: " + user + "\r\nSubject: " + subject + "\r\n" + "Content-Type: text/" + mailtype + "; charset=UTF-8" + "\r\n\r\n" + body)
   sendto := strings.Split(to, ";")
   err := smtp.SendMail(host, auth, user, sendto, msg)
   return err
}

再創(chuàng)建一個(gè)函數(shù),遍歷所有內(nèi)容并調(diào)用發(fā)送郵件函數(shù)發(fā)送出去

func sendMail(sendList map[string]string) {

    fmt.Printf("共需要發(fā)送%d封郵件\n", len(sendList))
    index := 1
    for mail, content := range sendList {
        fmt.Printf("發(fā)送第%d封", index)
        if err := sendToMail("xxx@mybigcompany.com",
            "thisismypassword",
            "smtp.mybigcompany.com:25",
            mail,
            "工資條",
            fmt.Sprintf("<table border='2'>%s</table>", content),
            "html"); err != nil {
            fmt.Printf(" ... 發(fā)送錯(cuò)誤(X) %s %s \n", mail, err.Error())

        } else {
            fmt.Printf(" ... 發(fā)送成功(V) %s \n", mail)
        }
        index++
        fmt.Printf("<table border='2'>%s</table> \n", content)
    }
}

最后爆安,將sendMail放在main函數(shù)中叛复,for迭代讀取出所有數(shù)據(jù)之后,就完成了扔仓。
行政的同事使用的是Windows,使用終端程序往往會(huì)讓他們摸不著頭腦咖耘,完全不知道發(fā)生什么事情翘簇,然而我也不可能花太多時(shí)間為這樣的小程序開(kāi)發(fā)界面,所以即便在終端運(yùn)行儿倒,也盡量提供友善的用戶體驗(yàn)版保,代碼中關(guān)鍵的信息都盡量輸出友好提示呜笑。程序結(jié)束后,做一個(gè)終端輸入等待彻犁,讓用戶看到運(yùn)行的結(jié)果叫胁。

    fmt.Print("按下回車結(jié)束")
    bufio.NewReader(os.Stdin).ReadLine()

完整代碼

package main

import (
    "bufio"
    "fmt"
    "net/smtp"
    "os"
    "regexp"

    "strings"

    "log"

    "github.com/tealeg/xlsx"
)

func main() {
    excelFileName := "./list.xlsx"
    xlFile, err := xlsx.OpenFile(excelFileName)
    if err != nil {
        log.Fatalln("err:", err.Error())
    }

    sendList := make(map[string]string)

    for _, sheet := range xlFile.Sheets {
        curMail := ""
        for _, row := range sheet.Rows {
            cells := getCellValues(row)
            //如果行包含電子郵件,創(chuàng)建一個(gè)新字典項(xiàng)
            if isEmail, emailStr := isEmailRow(cells); isEmail {
                curMail = emailStr
            } else {
                count := 0
                for _, c := range cells {
                    if len(c) > 0 {
                        count++
                    }
                }

                if count > 1 {
                    sendList[curMail] += fmt.Sprintf("<tr><td>%s</td></tr>", strings.Join(cells, "</td><td>"))
                } else {
                    sendList[curMail] += fmt.Sprintf("<tr><td colspan='%d'>%s</td></tr>", len(cells), strings.Join(cells, ""))
                }

            }

        }
    }

    sendMail(sendList)
    fmt.Print("按下回車結(jié)束")
    bufio.NewReader(os.Stdin).ReadLine()

}

func getCellValues(r *xlsx.Row) (cells []string) {
    for _, cell := range r.Cells {
        txt := strings.Replace(strings.Replace(cell.Value, "\n", "", -1), " ", "", -1)
        cells = append(cells, txt)
    }
    return
}

func isEmailRow(r []string) (isEmail bool, email string) {
    reg := regexp.MustCompile(`^[a-zA-Z_0-9.-]{1,64}@([a-zA-Z0-9-]{1,200}.){1,5}[a-zA-Z]{1,6}$`)
    for _, v := range r {
        if reg.MatchString(v) {
            return true, v
        }
    }
    return false, ""
}

func sendMail(sendList map[string]string) {

    fmt.Printf("共需要發(fā)送%d封郵件\n", len(sendList))
    index := 1
    for mail, content := range sendList {
        fmt.Printf("發(fā)送第%d封", index)
        if err := sendToMail("xxx@mybigcompany.com",
            "thesismypassword",
            "smtp.mybigcompany.com:25",
            mail,
            "工資條",
            fmt.Sprintf("<table border='2'>%s</table>", content),
            "html"); err != nil {
            fmt.Printf(" ... 發(fā)送錯(cuò)誤(X) %s %s \n", mail, err.Error())

        } else {
            fmt.Printf(" ... 發(fā)送成功(V) %s \n", mail)
        }
        index++
        //fmt.Printf("<table border='2'>%s</table> \n", content)
    }

}

func sendToMail(user, password, host, to, subject, body, mailtype string) error {
    auth := smtp.PlainAuth("", user, password, strings.Split(host, ":")[0])
    msg := []byte("To: " + to + "\r\nFrom: " + user + "\r\nSubject: " + subject + "\r\n" + "Content-Type: text/" + mailtype + "; charset=UTF-8" + "\r\n\r\n" + body)
    sendto := strings.Split(to, ";")
    err := smtp.SendMail(host, auth, user, sendto, msg)
    return err
}

Go語(yǔ)言交叉編譯汞幢,運(yùn)行在不同的操作系統(tǒng)

我用的Mac 64位驼鹅,需要編譯一個(gè)Windows 32位的可執(zhí)行程序,一句搞定

CGO_ENABLED=0 GOOS=windows GOARCH=386 go build

GOOS設(shè)置目標(biāo)系統(tǒng),可以是 windows, linux,darwin
GOARCH設(shè)置目標(biāo)系統(tǒng)是32位還是64位森篷,分別對(duì)應(yīng) 386和amd64
CGO_ENABLED設(shè)置是否需要使用CGO输钩,本例子不需要,設(shè)置為0仲智,如果需要使用CGO編譯买乃,設(shè)置為1

OK,任務(wù)完成钓辆,只要編輯一份如文中第三張圖那樣格式的文檔剪验,保存為list.xlsx,與編譯好的可執(zhí)行文件放在同一目錄前联,雙擊執(zhí)行碉咆,文檔中的內(nèi)容就會(huì)根據(jù)電子郵件單元格作為分割點(diǎn)分別發(fā)送到該電子郵箱里。

知識(shí)點(diǎn)總結(jié)

  • 使用github.com/tealeg/xlsx包讀取xlsx文件
  • 使用regexp包實(shí)現(xiàn)正則表達(dá)式判斷
  • 使用net/smtp包發(fā)送電子郵件
  • 使用交叉編譯命令生成不同系統(tǒng)上的可執(zhí)行文件

歡迎大家簡(jiǎn)書或我的個(gè)人博客與我交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛀恩,一起剝皮案震驚了整個(gè)濱河市疫铜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌双谆,老刑警劉巖壳咕,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異顽馋,居然都是意外死亡谓厘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門寸谜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)竟稳,“玉大人,你說(shuō)我怎么就攤上這事熊痴∷郑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵果善,是天一觀的道長(zhǎng)诊笤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)巾陕,這世上最難降的妖魔是什么讨跟? 我笑而不...
    開(kāi)封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任纪他,我火速辦了婚禮,結(jié)果婚禮上晾匠,老公的妹妹穿的比我還像新娘茶袒。我一直安慰自己,他們只是感情好凉馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布薪寓。 她就那樣靜靜地躺著,像睡著了一般句喜。 火紅的嫁衣襯著肌膚如雪预愤。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天咳胃,我揣著相機(jī)與錄音植康,去河邊找鬼。 笑死展懈,一個(gè)胖子當(dāng)著我的面吹牛销睁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播存崖,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冻记,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了来惧?” 一聲冷哼從身側(cè)響起冗栗,我...
    開(kāi)封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎供搀,沒(méi)想到半個(gè)月后隅居,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葛虐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年胎源,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屿脐。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涕蚤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出的诵,到底是詐尸還是另有隱情万栅,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布奢驯,位于F島的核電站申钩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘪阁。R本人自食惡果不足惜撒遣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管跺。 院中可真熱鬧义黎,春花似錦、人聲如沸豁跑。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)艇拍。三九已至狐蜕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卸夕,已是汗流浹背层释。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留快集,地道東北人贡羔。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像个初,于是被迫代替她去往敵國(guó)和親乖寒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Simple Excel Export 簡(jiǎn)單的Excel導(dǎo)出推薦http://www.cnblogs.com/hy...
    地獄咆哮Zzzzz閱讀 15,616評(píng)論 0 6
  • 使用首先需要了解他的工作原理 1.POI結(jié)構(gòu)與常用類 (1)創(chuàng)建Workbook和Sheet (2)創(chuàng)建單元格 (...
    長(zhǎng)城ol閱讀 8,414評(píng)論 2 25
  • 創(chuàng)建新工程 打開(kāi)Eclipse新建一個(gè)工程 點(diǎn)下一步 輸入名稱 點(diǎn)完成 新建一個(gè)目錄用來(lái)存在第三方庫(kù)文件 選擇目錄...
    長(zhǎng)新閱讀 2,190評(píng)論 3 1
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理院溺,服務(wù)發(fā)現(xiàn)楣嘁,斷路器,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 小小動(dòng)過(guò),結(jié)果是把自己傷了弄息,留了個(gè)丑陋的疤痊班,那個(gè)曾經(jīng)的男友現(xiàn)在跟另一半過(guò)得幸福的很。 吵架的時(shí)候沒(méi)人能動(dòng)腦子摹量,但是...
    印大鈔閱讀 759評(píng)論 2 1