前言
曾经,因为不够注重基础吃了好多亏。总是很喜欢去看那些高大上的东西,却忽略了最基本的东西。然后会错误的以为自己懂的很多,但是其实是沙堆中筑高台,知道很多高大上的架构,但是基础的东西却不太了解。我觉得,可能这就是大部分开发工程师的通病吧。
所以,深入一门语言,也不用一直看重多高端、高大上的框架功能服务,尝试一下小案例,拓宽一下对于语言的更深层次的理解。
基础案例
- 中文拼音转换:根据用户名快速创建个人 id
- 解析二维码:帮助快速识别硬件码
- 压缩 zip:压缩文件,便于传输数据
- 执行定时任务:方便自动化操作
中文拼音转换
package main
import (
"fmt"
"github.com/mozillazg/go-pinyin"
)
func main() {
hans := "中国人"
// 默认
a := pinyin.NewArgs()
fmt.Println(pinyin.Pinyin(hans, a))
// [[zhong] [guo] [ren]]
// 包含声调
a.Style = pinyin.Tone
fmt.Println(pinyin.Pinyin(hans, a))
// [[zhōng] [guó] [rén]]
// 声调用数字表示
a.Style = pinyin.Tone2
fmt.Println(pinyin.Pinyin(hans, a))
// [[zho1ng] [guo2] [re2n]]
// 开启多音字模式
a = pinyin.NewArgs()
a.Heteronym = true
fmt.Println(pinyin.Pinyin(hans, a))
// [[zhong zhong] [guo] [ren]]
a.Style = pinyin.Tone2
fmt.Println(pinyin.Pinyin(hans, a))
// [[zho1ng zho4ng] [guo2] [re2n]]
fmt.Println(pinyin.LazyPinyin(hans, pinyin.NewArgs()))
// [zhong guo ren]
fmt.Println(pinyin.Convert(hans, nil))
// [[zhong] [guo] [ren]]
fmt.Println(pinyin.LazyConvert(hans, nil))
// [zhong guo ren]
}
实际应用过程中,只要能够获取需要转换的中文词语字符串数组即可实现中文拼音转换的操作,不过,虽然使用的包 github.com/Chain-Zhang/pinyin 相对于其他 golang 拼音转换项目的资料多一点,但貌似不维护了。
package main
import (
"fmt"
"github.com/mozillazg/go-pinyin"
"strings"
"reflect"
"github.com/astaxie/beego"
)
func main() {
hans := "中国人"
a := pinyin.LazyConvert(hans, nil)
// [zhong guo ren]
var test []string = []string{}
for a, v := range a{
beego.Info(v)
beego.Info(a)
if a == 0 {
test = append(test, v)
} else {
test = append(test, ",")
test = append(test, v)
}
}
beego.Info("处理1")
beego.Info(test)
// 通过这一条处理 strings.Trim
result := strings.Trim(fmt.Sprint(test), "[]")
// result := strings.Replace(strings.Trim(fmt.Sprint(test), "[]"), " ", ",", -1)
beego.Info(result)
beego.Info(reflect.TypeOf(result))
result2 := strings.Replace(result, " , ", "", -1)
beego.Info(result2)
// zhongguoren
}
运行代码如下:
go get -u github.com/mozillazg/go-pinyin
go run main.go
可得以下结果:
解析二维码
思路:
- 上传图片或者本地读取图片
- 将文件流写入到gozxing 的函数中解析二维码
这里采用上传文件的形式,如下:
package main
import (
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
"github.com/gin-gonic/gin"
"net/http"
"io"
)
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// The default memory allocation is 10M
file, err := c.FormFile("filename")
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
return
}
//文件大小限制
if file.Size > (10 << 20) {
c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", "文件太大,请重新上传"))
return
}
c.String(http.StatusOK, fmt.Sprintf("File %s uploaded success", file.Filename))
//fmt.Println(file.Filename, "文件名")
fi, err := file.Open()
if err != nil {
fmt.Println(err)
}
defer fi.Close()
str := GetPaymentStr(fi).String()
fmt.Println("qrcode_url:", str)
})
router.Run(":8000")
}
func GetPaymentStr(fi io.Reader) (paymentCodeUrl *gozxing.Result) {
img, _, err := image.Decode(fi)
if err != nil {
fmt.Println(err)
}
// prepare BinaryBitmap
bmp, _ := gozxing.NewBinaryBitmapFromImage(img)
// decode image
qrReader := qrcode.NewQRCodeReader()
result, err := qrReader.Decode(bmp, nil)
if err != nil {
fmt.Println(err)
}
return result
}
注意使用的图片格式包,需要哪种格式,才导入哪种格式,剔除冗余文件。
注意关闭文件流哦!
图片拷贝不如,基本就要再读写一次,不如同时开两个流
压缩文件成 zip
在软件很多应用开发过程中,经常需要使用到文件压缩。有时候是为了加快存盘速度,有时候是为了节省硬盘空间,有时候是为了提高传输效率。gzip是一种比较通用的压缩程序,golang系统自带的包里边compress/gzip就可以实现在代码中实现gzip的功能。
定义解压缩文件接口CompressFile和DeCompressFile:
gziptest.go:
package gziptest
import (
"compress/gzip"
"io"
"os"
)
//压缩文件Src到Dst
func CompressFile(Dst string, Src string) error {
newfile, err := os.Create(Dst)
if err != nil {
return err
}
defer newfile.Close()
file, err := os.Open(Src)
if err != nil {
return err
}
zw := gzip.NewWriter(newfile)
filestat, err := file.Stat()
if err != nil {
return nil
}
zw.Name = filestat.Name()
zw.ModTime = filestat.ModTime()
_, err = io.Copy(zw, file)
if err != nil {
return nil
}
zw.Flush()
if err := zw.Close(); err != nil {
return nil
}
return nil
}
//解压文件Src到Dst
func DeCompressFile(Dst string, Src string) error {
file, err := os.Open(Src)
if err != nil {
panic(err)
}
defer file.Close()
newfile, err := os.Create(Dst)
if err != nil {
panic(err)
}
defer newfile.Close()
zr, err := gzip.NewReader(file)
if err != nil {
panic(err)
}
filestat, err := file.Stat()
if err != nil {
panic(err)
}
zr.Name = filestat.Name()
zr.ModTime = filestat.ModTime()
_, err = io.Copy(newfile, zr)
if err != nil {
panic(err)
}
if err := zr.Close(); err != nil {
panic(err)
}
return nil
}
单元测试用例(调用函数):
gziptest_test.go:
package gziptest
import (
"os"
"testing"
)
func TestCompressFile(t *testing.T) {
pwd, _ := os.Getwd()
newfile, err := os.Create(pwd + "/test.txt")
if err != nil {
t.Fatal(err)
}
newfile.Write([]byte("hello world!!!!"))
newfile.Close()
err = CompressFile(pwd+"/test.gz", pwd+"/test.txt")
if err != nil {
t.Fatal(err)
}
}
func TestDeCompressFile(t *testing.T) {
pwd, _ := os.Getwd()
err := DeCompressFile(pwd+"/test2.txt", pwd+"/test.gz")
if err != nil {
t.Fatal(err)
}
}
测试结果:
C:/Go/bin/go.exe test -v [D:/go/src/gziptest]
=== RUN TestCompressFile
--- PASS: TestCompressFile (0.00s)
=== RUN TestDeCompressFile
--- PASS: TestDeCompressFile (0.00s)
PASS
ok gziptest 2.351s
同级目录下增加了三个文件:
- test.txt
- text2.txt
- text.gz
其中test.txt和test2.txt内容为:
hello world!!!!
1
test.gz内容为text.txt
执行定时任务
golang中需要定时执行某些任务,完成一些自动化操作。
自带原始库
func DocSyncTaskCronJob() {
ticker := time.NewTicker(time.Minute * 1) // 每分钟执行一次
for range ticker.C {
ProcTask()
}
}
func ProcTask() {
log.Println("hello world")
}
调研一下后发现Golang并没有十分完善的定时任务库。无法完成复杂的定时任务。
- 事件订阅/通知机制不成熟
- 无法适用于更灵活的场景,例如多节点的分布式任务调度执行
- 模块之间的职责不清晰,例如其实Timer模块是Scheduler调度器的一部分,Event定时器相关的部分也是Scheduler调度器的一部分,而Executor执行模块也存在任务调度的功能,实际上它只需要负责完成调度器交给它的任务就好
- 没有设计任务调度池,也就是但凡新建计划任务,就会在后台启动一个协程持续监听;一旦任务数量太多,后台停留的协程会越来越多,进程总的消耗就会变得非常夸张,非常可怕
- 任务调度时不存在优先级的概念,假如相同时间内有多个任务同时执行,哪个任务被优先调度完全取决于GMP的系统调度
定时任务库选择
目前比较主流两种go常用定时库
- robfig/cron:说到定时任务,会想到 crontab,其常见于Unix和类Unix的操作系统之中。robfig/cron 库使用了类 crontab 的方式来执行定时任务。
- jasonlvhit/gocron:类 crontab 的设置方式可能并不友好,jasonlvhit/gocron 提供了更为人性化的执行方式。
这里我们使用开源库:robfig/cron
安装库:
go get -u github.com/jasonlvhit/gocron
package main
import(
"fmt"
cron "github.com/robfig/cron/v3"
)
func main() {
crontab := cron.New()
task := func() {
fmt.Println("hello world")
}
// 添加定时任务, * * * * * 是 crontab,表示每分钟执行一次
crontab.AddFunc("* * * * *", task)
// 启动定时器
crontab.Start()
// 定时任务是另起协程执行的,这里使用 select 简答阻塞.实际开发中需要
// 根据实际情况进行控制
select {}
}
执行命令:
go get github.com/robfig/cron/v3@v3.0.0
go run main.go
执行效果如下:
当然,更为复杂的定时任务还有待大家去设计,这里只是一个个小小的案例。
总结
总的来说,一个个小小案例,能够帮助我们了解到许多的工具库与框架,并且理解一些小功能的实现思路,尔后,慢慢利用这些小功能积累成一个个高效、强大的服务模块,这也是徐徐渐进的过程,一起加油!