关键字:golang、入门、hello
时间:2018年11月

root@debian:~# apt-get install golang
root@debian:~# mkdir hello
root@debian:~# cd hello/
root@debian:~/hello# vim main.go
//package 本文件所属包的包名
package main

import (
    //"需要调用包的路径"
    "fmt"
)

func main() {
    //包名.方法名(参数, ...)
    fmt.Println("Hello world!")
}
:wq
root@debian:~/hello# go build
root@debian:~/hello# ls
hello  main.go
root@debian:~/hello# ./hello
Hello world!
root@debian:~/hello#

注:
1、要生成可执行二进制文件,必须要有名为main的package和名为main的func。
2、import的fmt是路径,其完整路径是/usr/lib/go/src/fmt,fmt.Println的fmt是包名,二者是不同的东西,容易混淆。

关键字:golang、股票、ohlc、指标
时间:2018年9月

// Copyright 2018 Wu Peng. All rights reserved.
//
package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
)

func main() {
	downloadSymbols("symbols.csv")

	symbol := "sz002027"
	downloadOHLC(symbol, "ohlc-"symbol+".csv")

	symbol = "sh600585"
	downloadIndicators(symbol, "indicators-"+symbol+".json")

	symbol = "sh600660"
	downloadBonus(symbol, "bonus-"+symbol+".json")
}

// downloadSymbols() save symbols to a csv file.
//
// Format as follows:
// symbol
// --------
// sh600000
// sh600001
// sh600002
// ...
// --------
func downloadSymbols(filename string) {
	resp, err := http.Get("http://quote.eastmoney.com/stocklist.html")
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	content := ""
	reg := regexp.MustCompile("s[hz][60]0\\d{4,4}")
	for _, s := range reg.FindAllString(string(body), -1) {
		content += s + "\n"
	}
	err = ioutil.WriteFile(filename, []byte(content), 0666)
}

// downloadOHLC() save a stock's OHLC data to a file.
//
// Format as follows:
// date   open  close high  low   volume
// -------------------------------------
// 180102 14.11 14.08 14.51 13.98 550796
// 180103 14.15 13.79 14.29 13.73 727753
// 180104 13.85 13.66 13.86 13.51 729298
// 180105 13.65 13.79 14.10 13.52 573245
// 180108 13.66 13.51 13.98 13.30 697819
// ...
// -------------------------------------
func downloadOHLC(symbol string, filename string) error {
	url := fmt.Sprintf("http://data.gtimg.cn/flashdata/hushen/daily/18/%s.js", symbol)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("http.Get():", err)
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 404 {
		fmt.Println("http.Get():", "404 page not find.")
		return errors.New("HTTP 404.")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioutil.ReadAll()", err)
		return err
	}
	str := string(body)
	str = strings.Replace(str, "\\n\\", "", -1)
	str = strings.Replace(str, "daily_data_18=\"\n", "", -1)
	str = strings.Replace(str, "\";", "", -1)
	err = ioutil.WriteFile(filename, []byte(str), 0666)
	if err != nil {
		fmt.Println("ioutil.WriteFile()", err)
	}
	return nil
}

// downloadIndicators() save a stock's indicators to a file.
//
// Format as follows:
// ----------------------------------
// [{
//   "date"       :"报告期",
//   "jbmgsy"     :"基本每股收益(元)",
//   "kfmgsy"     :"扣非每股收益(元)",
//   "xsmgsy"     :"稀释每股收益(元)",
//   "mgjzc"      :"每股净资产(元)",
//   "mggjj"      :"每股公积金(元)",
//   "mgwfply"    :"每股未分配利润(元)",
//   "mgjyxjl"    :"每股经营现金流(元)",
//   "yyzsr"      :"营业总收入(元)",
//   "mlr"        :"毛利润(元)",
//   "gsjlr"      :"归属净利润(元)",
//   "kfjlr"      :"扣非净利润(元)",
//   "yyzsrtbzz"  :"营业总收入同比增长(%)",
//   "gsjlrtbzz"  :"归属净利润同比增长(%)",
//   "kfjlrtbzz"  :"扣非净利润同比增长(%)",
//   "yyzsrgdhbzz":"营业总收入滚动环比增长(%)",
//   "gsjlrgdhbzz":"归属净利润滚动环比增长(%)",
//   "kfjlrgdhbzz":"扣非净利润滚动环比增长(%)",
//   "jqjzcsyl"   :"加权净资产收益率(%)",
//   "tbjzcsyl"   :"摊薄净资产收益率(%)",
//   "tbzzcsyl"   :"摊薄总资产收益率(%)",
//   "mll"        :"毛利率(%)",
//   "jll"        :"净利率(%)",
//   "sjsl"       :"实际税率(%)",
//   "yskyysr"    :"预收款/营业收入",
//   "xsxjlyysr"  :"销售现金流/营业收入",
//   "jyxjlyysr"  :"经营现金流/营业收入",
//   "zzczzy"     :"总资产周转率(次)",
//   "yszkzzts"   :"应收账款周转天数(天)",
//   "chzzts"     :"存货周转天数(天)",
//   "zcfzl"      :"资产负债率(%)",
//   "ldzczfz"    :"流动负债/总负债(%)",
//   "ldbl"       :"流动比率",
//   "sdbl"       :"速动比率"
// },{
//   ...
// }]
// -------------------------------------
func downloadIndicators(symbol string, filename string) error {
	url := fmt.Sprintf("http://emweb.securities.eastmoney.com/NewFinanceAnalysis/MainTargetAjax?ctype=4&type=0&code=%s", symbol)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("http.Get():", err)
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 404 {
		fmt.Println("http.Get():", "404 page not find.")
		return errors.New("HTTP 404.")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioutil.ReadAll()", err)
		return err
	}
	err = ioutil.WriteFile(filename, body, 0666)
	if err != nil {
		fmt.Println("ioutil.WriteFile()", err)
		return err
	}
	return nil
}

// downloadBonus() save a stock's bonus to a file.
func downloadBonus(symbol string, filename string) error {
	url := fmt.Sprintf("http://emweb.securities.eastmoney.com/PC_HSF10/BonusFinancing/BonusFinancingAjax?code=%s", symbol)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("http.Get():", err)
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 404 {
		fmt.Println("http.Get():", "404 page not find.")
		return errors.New("HTTP 404.")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioutil.ReadAll()", err)
		return err
	}
	err = ioutil.WriteFile(filename, body, 0666)
	if err != nil {
		fmt.Println("ioutil.WriteFile()", err)
		return err
	}
	return nil
}

关键字:golang、百度、图像识别、ocr、api
时间:2018年9月

开通API

通过ai.baidu.com或cloud.baidu.com进行登录,进入“管理控制台”后,在“已开通服务”板块下的“文字识别”中申请开通API。

API使用流程

1、调用获取access token的接口,需提供客户API Key和Secret Key;
2、调用具体api,需提供access token;
3、access token可以反复使用,但有一定的有效期,失效后需要重新获取。

Demo代码

// Copyright 2018 Wu Peng. All rights reserved.

// Example of how to use baidu OCR APIs.
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type Session struct {
	AccessToken   string `json:"access_token"`
	RefreshToken  string `json:"refresh_token"`
	SessionKey    string `json:"session_key"`
	SessionSecret string `json:"session_secret"`
	Scope         string `json:"scope"`
	ExpiresIn     int    `json:"expires_in"`
}

type Result struct {
	LogId     int64       `json:"log_id"`
	Direction int         `json:"direction"`
	Wrn       int         `json:"words_result_num"`
	Wr        WordsResult `json:"words_result"`
}

type WordsResult struct {
	Model        Words `json:"品牌型号"`
	IssueDate    Words `json:"发证日期"`
	UseCharacter Words `json:"使用性质"`
	EngineNo     Words `json:"发动机号码"`
	PlateNo      Words `json:"号牌号码"`
	Owner        Words `json:"所有人"`
	Address      Words `json:"住址"`
	RegisterDate Words `json:"注册日期"`
	Vin          Words `json:"车辆识别代号"`
	VehicleType  Words `json:"车辆类型"`
}

type Words struct {
	Words string `json:"words"`
}

type VehicleLicense struct {
	PlateNo      string
	VehicleType  string
	Owner        string
	Address      string
	UseCharacter string
	Model        string
	Vin          string
	EngineNo     string
	RegisterDate string
	IssueDate    string
}

const (
	clientID     = "LQeYIP5a*********RjGP9W7"          // API Key
	clientSecret = "laLiGBQuEbYu******AZ55urYRAYOFy9"  // Secret Key
)

func main() {
	token, err := accessToken(clientID, clientSecret)
	if err != nil {
		fmt.Println("accessToken()", err)
		return
	}
	fmt.Println("Token:", *token)

	img, err := ioutil.ReadFile("1.JPG")
	if err != nil {
		fmt.Println("ioutil.ReadFile()", err)
		return
	}
	var vl VehicleLicense
	err = ImageToText(*token, img, &vl)
	if err != nil {
		fmt.Println("ImageToText()", err)
		return
	}
	fmt.Println("PlateNo: ", vl.PlateNo)
	fmt.Println("VIN: ", vl.Vin)

}

func accessToken(id string, secret string) (token *string, err error) {
	apiURL := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", id, secret)
	resp, err := http.Get(apiURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var session Session
	err = json.Unmarshal(body, &session)
	if err != nil {
		return nil, err
	}
	token = &session.AccessToken
	return token, nil
}

func ImageToText(token string, image []byte, vl *VehicleLicense) error {
	apiURL := fmt.Sprintf("https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license?access_token=%s", token)
	param := "image=" + url.QueryEscape(base64.StdEncoding.EncodeToString(image))
	resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(param))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var result Result
	err = json.Unmarshal(body, &result)
	if err != nil {
		return err
	}
	convert(result.Wr, vl)
	return nil
}

func convert(wr WordsResult, vl *VehicleLicense) {
	vl.PlateNo = wr.PlateNo.Words
	vl.VehicleType = wr.VehicleType.Words
	vl.Owner = wr.Owner.Words
	vl.Address = wr.Address.Words
	vl.UseCharacter = wr.UseCharacter.Words
	vl.Model = wr.Model.Words
	vl.Vin = wr.Vin.Words
	vl.EngineNo = wr.EngineNo.Words
	vl.RegisterDate = wr.RegisterDate.Words
	vl.IssueDate = wr.IssueDate.Words
	return
}

关键字:golang、goroutine、channel、要点
时间:2018年8月

Golang编程的关键

golang编程语言相比其他语言的关键有两个,一个是goroutine,另一个是channel

关于goroutine

goroutine,相较于其他编程语言,可以理解为创建创建线程。但其他语言你需要控制线程的总数不能过多,否则CPU会大量消耗到线程切换上。但goroutine则完全不用顾虑,你可以任意调用。举个例子:

for i:=0;i<100000;i++ {
    go time.Sleep(time.Second * 300)
}

设想一下:如果你使用其他语言,创建了10万个线程,每个线程进行300秒的sleep,CPU会是什么状态?

关于channel

channel好比是阻塞队列,当队列为空时,取数据是会阻塞等待的;当队列满时,放入数据时是会阻塞等待的。

c := make(chan int, 10)
for i:=0;i<20;i++ {
    go func() {
        c <- i
        fmt.Printf("%d\n", i) // 通道c中放满10个数据后,则进入阻塞状态,此处不再打印。
    }()
}
time.Sleep(time.Second * 10)
for j:=0;j<10;j++ {
    <- c // 从通道c中取出数据后,则继续打印i。
 
}
c := make(chan int, 10)
for i:=0;i<10;i++ {
    go func() {
        n <- c
        fmt.Printf("%d\n", i) // 通道c中有数据后,才会执行此处的打印。
    }()
}
time.Sleep(time.Second * 10)
for j:=0;j<10;j++ {
    c <- j // 向通道c中放入数据,则开始打印i。
}

其他

像sync.WaitGroup等都是通过channel实现。

关键字:golang、redis、客户端、client
时间:2017年8月

前言

未找到redis官方提供的golang版客户端,但在第三方提供的客户端中发现了redigo,它是目前主流客户端之一。

代码

package main

import (
	"github.com/garyburd/redigo/redis"
	"fmt"
)

func main() {
	
	c, err := redis.Dial("tcp", "192.168.1.10:6379")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	reply, err := c.Do("SET", "abc", "123456")
	fmt.Printf("reply: %v\n", reply)

	value, err := redis.String(c.Do("GET", "abc"))
	fmt.Printf("key: abc, value: %s\n", value)
}

关键字:golang、包、推荐、第三方
时间:2018年8月

包名 用途 特点 github地址
gorm ORM框架 github.com/jinzhu/gorm
sarama kafka客户端 github.com/Shopify/sarama
glog 日志库 google开发,无文件自动分割和滚动功能。 github.com/golang/glog
redigo redis客户端 与命令行使用方法相似。 github.com/gomodule/redigo
go-redis redis客户端 github.com/go-redis/redis
go-zookeeper zookeeper客户端 github.com/samuel/go-zookeeper/zk
go-sqlite3 sqlite3库 github.com/mattn/go-sqlite3
goquery html解析 github.com/PuerkitoBio/goquery

关键字:golang、tcp、tcpserver、框架
时间:2017年6月

前言

各种语言tcp服务器端都有如下的经典入门代码

listen()
while(1) {
    accept()
    new thread()
}

这样的代码仅仅只能用于学习tcp的接收和发送等基本使用方法。由于线程间切换带来的开销,使得这段代码无法用于大规模TCP连接的场景。当面对大规模TCP连接时,必须引入“非阻塞”、“select”等模型和概念。go语言的并行机制让这样的经典代码可以用于大规模TCP连接的场景。

代码

package main

import (
    "fmt"
    "net"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()
    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("[Error]", err.Error())
            break
        }
        // Do something here!
        fmt.Println("Read:", string(buf[0:n]))
    }
}

func main() {
    listen, err := net.Listen("tcp", ":9999") // 监听任意ip的9999端口
    if err != nil {
        fmt.Println("[Error]", err.Error())
        return
    }
    defer listen.Close()
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("[Error]", err.Error())
            continue
        }
        fmt.Println("[INFO]", conn.RemoteAddr().String(), " connected.")
        go handleConnection(conn) // 并行处理连接
    }
}

关键字:go、golang、http2、apns、推送、最简
时间:2017年5月

代码

package main

import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"

    "golang.org/x/crypto/pkcs12"
    "golang.org/x/net/http2"
)

func main() {

    f, err := ioutil.ReadFile("./app.p12") // app.p12是推送证书
    if err != nil {
        fmt.Println("file error!")
        return
    }   

    privateKey, x509cert, err := pkcs12.Decode(f, "123456") // 123456是证书的密码
    if err != nil {
        fmt.Println("password error!")
        return
    }   

    tlscert := tls.Certificate{
        Certificate: [][]byte{x509cert.Raw},
        PrivateKey: privateKey,
        Leaf: x509cert,
    }   

    config := &tls.Config{
        Certificates: []tls.Certificate{tlscert},
    }   

    tr := &http2.Transport{TLSClientConfig: config}
    httpClient := http.Client{Transport: tr} 

    url := "https://api.push.apple.com/3/device/"
    token := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    payload := "{\"aps\":{\"alert\":\"hello\",\"sound\":\"msg_high.m4a\"}}"

    req, err := http.NewRequest("POST", url+token, strings.NewReader(payload))
    if err != nil {
        fmt.Println("new error")
        return
    }

    req.Header.Set("apns-topic", "com.xxx.appname") // com.xxx.appname是bundle id
    resp, err := httpClient.Do(req)
    if err != nil {
        fmt.Println("request error")
        return
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusOK {
        fmt.Println("apns-id: ", resp.Header.Get("apns-id"))
    } else {
        fmt.Println("response error")
        body, err := ioutil.ReadAll(resp.Body)
        if err!= nil{
            return
        }
        fmt.Println("resp.Body:\n", string(body))
    }
}