标签 Golang 下的文章

前言

没有前言。

生成Payload

我们可以直接使用golang-jwt中的RegisteredClaims结构:

jwt.RegisteredClaims{
    // 发行人主题
    Issuer:    "",
    // 被签发者主题
    Subject:   "",
    // 用户标识符
    Audience:  nil,
    // 过期时间
    ExpiresAt: nil,
    // 不早于
    NotBefore: nil,
    // 签发时间
    IssuedAt:  nil,
    // JWT的唯一标识符
    ID:        "",
}

此部分参见:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1

或者我们可以自定义一个结构体,只需要引用RegisteredClaims就行:

// 嵌套结构
type CustomInfo struct {
    Name string `json:"name"`
}

// 自定义 payload
type CustomTokenBody struct {
    *jwt.RegisteredClaims
    User CustomInfo `json:"user"`
    Role string
}

生成JWT

选择签署算法:

// HMAC_HS256_HS384_HS512
// RSA_RS256_RS384_RS512
// ECDSA_ES256_ES384_ES512
// EdDSA_Ed25519
signingMethodECSDA := jwt.GetSigningMethod("ES256")

新建JWT并签署Payload

token := jwt.New(signingMethodECSDA)

// payload
token.Claims = &CustomTokenBody{
    &jwt.RegisteredClaims{
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 10)),
    },
    CustomInfo{
        Name: "yeziruo",
    },
    "yeziruo",
}
// 使用私钥签署
// key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
signedString, err := token.SignedString(key)
if err != nil {
    panic(err)
}
fmt.println(signedString)

最后生成:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjYxNTk2NjMsInVzZXIiOnsibmFtZSI6InllemlydW8ifSwiUm9sZSI6InllemlydW8ifQ.mCatXHq0ooh49Eusr2aWLJKwwy6K_zogDBVCTc3eBzY_ygePVnFL78QpzkGeuCCyhtjdobuKGx0VxBs6PlYrQA
//payload:{"exp":1666159663,"user":{"name":"yeziruo"},"Role":"yeziruo"}

解析JWT

共有三种解析方法:

// 解析并验证
jwt.Parse
// 仅解析
jwt.ParseUnverified
// 解析并验证
jwt.ParseWithClaims

ParseParseWithClaims有一定区别:

// 解析JWT
parse, err := jwt.Parse(signedString, func(token *jwt.Token) (interface{}, error) {
    // 如果你有多个私钥用于签名,那么可以在此通过 kid 来选择相应的密钥
    // 这里获取的 token.Claims 是一个 map
    // 返回公钥
    return key.Public(), nil
})

parse, err = jwt.ParseWithClaims(signedString, &CustomTokenBody{}, func(token *jwt.Token) (interface{}, error) {
    // 这里获取的 token.Claims 会被填充到指定的结构体
    return key.Public(), nil
})

// err 可以返回验证失败原因
// fmt.println(err)
fmt.Println(parse.Valid)

将其作为Gin中间件

func JWTAuth(priKey *ecdsa.PrivateKey) gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        parse, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
            return priKey.Public(), nil
        })
        if err != nil || !parse.Valid {
            msg := "token verification failed"
            if err != nil {
                msg = err.Error()
            }
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    http.StatusUnauthorized,
                "message": msg,
            })
            c.Abort()
        } else {
            c.Set("jwt", parse)
            c.Next()
        }
    }
}

2219143001.png

参见

https://datatracker.ietf.org/doc/html/rfc7519
https://pkg.go.dev/github.com/golang-jwt/jwt

前言

本文为以下两篇文章的续文:

使用OpenSSL搭建简单一级CA

使用OpenSSL搭建简单多级CA与CA管理

本文有一些概念涉及前文,故建议阅读后再实践本文章。

生成与解析csr

生成密钥

# "crypto/rand","crypto/rsa"
priKey, err := rsa.GenerateKey(rand.Reader, 2048)
// error_check
pubKey := priKey.Public()

保存密钥

密钥通常使用PEM格式保存,以开头,以结尾,中间是使用Base64编码的内容,为方便下文的操作,封装成一个工具函数:

# "encoding/pem"
func PEMEncoding(key []byte, blockType int) []byte {
    keyType := "PUBLIC KEY"
    if blockType == 1 {
        keyType = "RSA PRIVATE KEY"
    } else if blockType == 2 {
        keyType = "CERTIFICATE REQUEST"
    } else if blockType == 3 {
        keyType = "CERTIFICATE"
    } else if blockType == 4 {
        keyType = "X509 CRL"
    }
    block := &pem.Block{
        Type:  keyType,
        Bytes: key,
    }
    return pem.EncodeToMemory(block)
}

写入文件:

# "crypto/x509"
// 使用 PKCS1 编码密钥
priKeySource := x509.MarshalPKCS1PrivateKey(priKey)
pubKeySource, err := x509.MarshalPKIXPublicKey(pubKey)
// error_check
pubKeySource := x509.MarshalPKCS1PrivateKey(priKey)
_ = os.WriteFile("ca_pri.key", PEMEncoding(priKeySource, 1), 0755)
_ = os.WriteFile("ca_pub.key", PEMEncoding(pubKeySource, 0), 0755)

生成请求

生成证书请求需要使用x509.CertificateRequest结构体:

# "crypto/x509"
x509.CertificateRequest{
    // 经过 DER 编码的ASN.1信息(包含CSR、签名算法和签名)
    // 读取时自动填充
    Raw:                      nil,
    // 经过 DER 编码的证书请求信息
    RawTBSCertificateRequest: nil,
    // 经过 DER 编码的公钥信息
    RawSubjectPublicKeyInfo:  nil,
    // 经过 DER 编码的主题信息
    RawSubject:               nil,
    // CSR版本
    Version:                  0,
    // 签名
    Signature:                nil,
    // 签名算法
    SignatureAlgorithm:       0,
    // 公钥算法
    PublicKeyAlgorithm:       0,
    // 公钥
    PublicKey:                nil,
    // 主题信息
    Subject:                  pkix.Name{},
    // 已弃用,可被解析的 CSR 信息
    Attributes:               nil,
    // CSR 的扩展信息
    Extensions:               nil,
    // CSR 的原始扩展信息,当这项不为空时将覆盖其他指定的扩展
    // 解析时不会填充
    ExtraExtensions:          nil,
    // 主题备用名称
    DNSNames:                 nil,
    EmailAddresses:           nil,
    IPAddresses:              nil,
    URIs:                     nil,
}

生成请求:

# "crypto/x509","crypto/rand"
caRequest := x509.CertificateRequest{
    Subject: pkix.Name{
        Country:            []string{"CN"},
        Organization:       []string{"blog.yeziruo.com"},
        CommonName:         "Yeziruo Root CA",
    },
}
// 随机数流,CertificateRequest,私钥
csr, err := x509.CreateCertificateRequest(rand.Reader, &caRequest, priKey)
// error_check
_ = os.WriteFile("ca_req.csr", PemEncoding(csr, 2), 0755)

解析请求

使用x509.ParseCertificateRequest函数来解析CSR文件,返回CertificateRequest对象:

# "crypto/x509"
caRequest, err = x509.ParseCertificateRequest(csr)
// error_check

签发根证书

证书的签发需要使用x509.Certificate结构体,但该结构体参数过多,故只解释本文所使用的:

# "crypto/x509"
x509.Certificate{
    // 颁发者主题
    Issuer:                      pkix.Name{},
    // 被颁发者主题
    Subject:                     pkix.Name{},
    // 不早于
    NotBefore:                   time.Time{},
    // 不晚于
    NotAfter:                    time.Time{},
    // 基础密钥用法
    KeyUsage:                    0,
    // 扩展密钥用法
    ExtKeyUsage:                 nil,
    // 基本约束
    BasicConstraintsValid:       false,
    // 是证书颁发机构
    IsCA:                        false,
    // 证书最大路径,该级 CA 能签发子 CA 的证书链路径深度
    MaxPathLen:                  0,
    // 不允许该 CA 签发子 CA
    MaxPathLenZero:              false,
    // OCSP 响应服务器
    OCSPServer:                  nil,
    // 备用 DNS 名称
    ExcludedDNSDomains:          nil,
    // CRL 分发点
    CRLDistributionPoints:       nil,
}

序列号生成:

# rand2 "math/rand"
// sn := big.NewInt(rand2.Int63())
sn := big.NewInt(rand2.Uint64())
// 序列号不能为默认,也不能为有序序号,必须是一个很大的随机数
// ”序列号熵“问题

生成根证书:

# "crypto/x509"
ca := x509.Certificate{
    // 从 CSR 中复制主题信息
    Subject: caRequest.Subject,
    // 序列号
    SerialNumber:          sn,
    NotBefore:             time.Now(),
    NotAfter:              time.Now().AddDate(5, 0, 0),
    BasicConstraintsValid: true,
    IsCA:                  true,
    MaxPathLenZero:        true,
    ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
    KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
}

// 随机数流,被颁发者,颁发者,被颁发者公钥,颁发者私钥
// 可以通过这个函数简单的新建子 CA
crt, err := x509.CreateCertificate(rand.Reader, &ca, &ca, pubKey, priKey)
// error_check
_ = os.WriteFile("ca.crt", PEMEncoding(crt, 3), 0755)

23231501.png

签发最终证书

同上文所述:

# "crypto/x509","crypto/rand","crypto/rsa",rand2 "math/rand"
cliPriKey, err := rsa.GenerateKey(rand.Reader, 2048)
// error_check
_ = os.WriteFile("cli_pri.key", PEMEncoding(x509.MarshalPKCS1PrivateKey(cliPriKey), 1), 0755)

sn = big.NewInt(rand2.Int63())
cli := x509.Certificate{
    Subject:               pkix.Name{CommonName: "local1.yeziruo.com"},
    DNSNames:              []string{"local1.yeziruo.com", "local2.yeziruo.com"},
    SerialNumber:          sn,
    NotBefore:             time.Now(),
    NotAfter:              time.Now().AddDate(1, 0, 0),
    BasicConstraintsValid: true,
    IsCA:                  false,
    ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
    KeyUsage:              x509.KeyUsageDigitalSignature,
    CRLDistributionPoints: []string{"http://192.168.122.250/ca.crl"},
    OCSPServer:            []string{"http://192.168.122.250/"},
}
cliCrt, err := x509.CreateCertificate(rand.Reader, &cli, &ca, cliPriKey.Public(), priKey)
// error_check
_ = os.WriteFile("cli.crt", PEMEncoding(cliCrt, 3), 0755)

23232401.png

生成CRL列表

// 重新读取证书,让其自动填充一些信息
cca, err := x509.ParseCertificate(crt)
// error_check
revokedObj := pkix.RevokedCertificate{
    // 被吊销证书序列号
    SerialNumber: cli.SerialNumber,
    // 吊销时间
    RevocationTime: time.Now(),
}

revokedList := x509.RevocationList{
    // 签发者主题信息
    Issuer: ca.Subject,
    // 被吊销的证书列表
    RevokedCertificates: []pkix.RevokedCertificate{revokedObj},
    // 一个自增的 CRL 序列号
    Number: big.NewInt(1),
    // 生成时间
    ThisUpdate: time.Time{},
    // 下一次更新时间
    NextUpdate: time.Time{},
}
list, err := x509.CreateRevocationList(rand.Reader, &revokedList, cca, priKey)
// error_check
_ = os.WriteFile("ca.crl", PEMEncoding(list, 4), 0755)

23235201.png

搭建OCSP响应服务器

OCSP协议使用HTTP承载,同时解析请求需要用到扩展加密库:

package main

import (
    "bytes"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "golang.org/x/crypto/ocsp"
    "net/http"
    "os"
    "time"
)

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("http://localhost:8081/")
    err := http.ListenAndServe("localhost:8081", nil)
    if err != nil {
        panic(err)
    }
}

func loadPriKey(fn string) (*rsa.PrivateKey, error) {
    key, err := os.ReadFile(fn)
    if err != nil {
        return nil, err
    }
    block, _ := pem.Decode(key)
    priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        return nil, err
    }
    return priKey, nil
}

func loadCertificate(fn string) (*x509.Certificate, error) {
    key, err := os.ReadFile(fn)
    if err != nil {
        return nil, err
    }
    block, _ := pem.Decode(key)
    crt, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
        return nil, err
    }
    return crt, nil
}

// no nonce ext support
// https://github.com/grimm-co/GOCSP-responder
func handler(w http.ResponseWriter, r *http.Request) {
    ca, err := loadCertificate("ca.crt")
    cli, err := loadCertificate("cli.crt")
    priKry, err := loadPriKey("ca_pri.key")
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    if r.Header.Get("Content-Type") != "application/ocsp-request" && r.Method != "POST" {
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    blob := new(bytes.Buffer)
    _, err = blob.ReadFrom(r.Body)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    //request, err := ocsp.ParseRequest(blob.Bytes())
    //if err != nil {
    //    return
    //}
    status := ocsp.Response{
        Status:           ocsp.Revoked,
        SerialNumber:     cli.SerialNumber,
        ProducedAt:       cli.NotBefore,
        ThisUpdate:       time.Now(),
        NextUpdate:       time.Now().AddDate(0, 0, 15),
        RevokedAt:        time.Now().AddDate(0, 0, -1),
        RevocationReason: 0,
        // Certificate:      ca,
    }

    response, err := ocsp.CreateResponse(ca, cli, status, priKry)
    if err != nil {
        return
    }
    w.Write(response)
}

23230001.png

参考

https://pkg.go.dev/crypto/x509

https://pkg.go.dev/golang.org/x/crypto/ocsp

https://github.com/grimm-co/GOCSP-responder

前言

本文主要介绍Golang Gin框架中全局处理错误的方法,水一下文章。

使用嵌套

就是在路由函数外面套层壳,侵入性大,不推荐这种处理方式。

type handle func(c *gin.Context) error

func errorHandle(f handle) func(c *gin.Context) {
    return func(c *gin.Context) {
        err := f(c)
        if err != nil {
            c.JSON(http.StatusInternalServerError, err.Error())
        }
    }
}

使用:

// 套在原路由函数,有多少个套多少次
router.GET("/b", errorHandle(func(c *gin.Context) error {
    return fmt.Errorf("1145141919810")
}))

使用panicrecover

Gin带有一个gin.Recovery中间件,用于捕获panic,说明我们可以通过中间件的方式捕获错误,而且panicrecover是原生的,非常方便,但只能捕获同一协程下的错误。

自定义错误接口:

package custom

var ErrorMessage map[int]string

type RequestError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Stack   string `json:"stack,omitempty"`
}

func (err *RequestError) Error() string {
    return err.Message
}

func NewRequestError(code int, err ...error) *RequestError {
    e := new(RequestError)
    e.Code = code
    if v, ok := ErrorMessage[code]; ok == true {
        e.Message = v
    } else {
        e.Message = "server error"
    }
    // runtime.Stack()
    if len(err) >= 1 {
        for i := range err {
            e.Stack += err[i].Error()
            e.Stack += "\n"
        }
    }
    return e
}

func init() {
    ErrorMessage = make(map[int]string)
    // 以此类推定义错误信息
    ErrorMessage[401] = "Unauthorized"
}

自定义中间件:

package custom

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 类型断言,因为 panic 是个 any ,什么都可以丢
                if v, ok := err.(*RequestError); ok == true {
                    c.JSON(v.Code, v)
                } else {
                    c.String(http.StatusInternalServerError, "Internal Server Error")
                }
                c.Abort()
            }
        }()
        // 传递下一棒
        c.Next()
    }
}

使用:

router.Use(custom.ErrorHandler())

router.GET("/a", func(c *gin.Context) {
    // 抛个错误
    panic(custom.NewRequestError(401))
    c.String(http.StatusOK, "hello")
})

15154424.png

前言

本文不是Lego使用教程。

由于SSL证书一年也就申请给几次,所以一般我都是手动去配置申请,上次介绍了Certbot提供acme模块用法,本文将介绍lego这个ACME客户端所提供的的库的用法。基本上重新造了个轮子,没有去用内部具体的接口。

实现

package main

import (
    "crypto"
    "crypto/x509"
    "encoding/pem"
    "github.com/go-acme/lego/v4/certcrypto"
    "github.com/go-acme/lego/v4/certificate"
    "github.com/go-acme/lego/v4/challenge/dns01"
    "github.com/go-acme/lego/v4/lego"
    "github.com/go-acme/lego/v4/log"
    "github.com/go-acme/lego/v4/registration"
    "io/ioutil"
    "time"
)

// 参见: https://pkg.go.dev/github.com/go-acme/lego/[email protected]/registration#User
type AcmeUser struct {
    Email        string
    Registration *registration.Resource
    key          crypto.PrivateKey
}

func (u *AcmeUser) GetEmail() string {
    return u.Email
}

func (u *AcmeUser) GetRegistration() *registration.Resource {
    return u.Registration
}
func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
    return u.key
}

// 实现 Provider 接口
// 参见: https://pkg.go.dev/github.com/go-acme/lego/v4/[email protected]#Provider
// lego本身提供一些DNS服务商的接口,但我一般都是手动申请
type plainDnsProvider struct{}

func (p *plainDnsProvider) Present(domain, token, keyAuth string) error {
    log.Infof("domain: _acme-challenge.%s token: %s", domain, token)
    return nil
}

func (p *plainDnsProvider) CleanUp(domain, token, keyAuth string) error {
    return nil
}

func main() {
    // 本地ACME测试用
    // httpClient := &http.Client{
    //     Transport: &http.Transport{
    //         TLSClientConfig: &tls.Config{InsecureSkipVerify: true
    //     },
    // }}

    key, err := ioutil.ReadFile("account_private.key")
    if err != nil {
        panic(err)
    }
    block, _ := pem.Decode(key)
    priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        panic(err)
    }

    // 加载账户信息
    user := AcmeUser{
        Email: "[email protected]",
        key:   priKey,
    }
    config := lego.NewConfig(&user)
    // Pebble 默认地址
    // config.CADirURL = "https://127.0.0.1:14000/dir"
    // config.CADirURL = "https://api.buypass.com/acme/directory"
    config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
    config.UserAgent = "acm_go/0.0.1"
    // 秘钥类型
    config.Certificate.KeyType = certcrypto.RSA2048
    // 本地测试用
    // config.HTTPClient = httpClient

    // 新建客户端
    client, err := lego.NewClient(config)
    if err != nil {
        panic(err)
    }

    // 账户注册
    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
    // 使用秘钥查找账户
    // reg, err := client.Registration.ResolveAccountByKey()
    if err != nil {
        panic(err)
    }
    user.Registration = reg

    // 生成证书请求
    request := certificate.ObtainRequest{
        Domains: []string{"yajyusenpai.coat.co.jp"},
        // 证书链
        Bundle: true,
    }

    dnsProvider := plainDnsProvider{}
    // dns.NewDNSChallengeProviderByName(provider_name)
    // dnspodConfig := dnspod.Config{config...}
    // dnspodProvider := dnspod.NewDNSProviderConfig(&dnspodConfig)

    // 以DNS方式验证
    err = client.Challenge.SetDNS01Provider(&dnsProvider, dns01.AddDNSTimeout(6*time.Minute))
    if err != nil {
        panic(err)
    }

    // 自动轮询
    certificates, err := client.Certificate.Obtain(request)
    if err != nil {
        panic(err)
    }

    // 导出
    _ = ioutil.WriteFile("certificate.crt", certificates.Certificate, 0755)
    _ = ioutil.WriteFile("certificate_private.key", certificates.PrivateKey, 0755)
}

220707063204.jpg

前言

Golang中没有原生的队列实现,故实现了一个简单的队列,了解队列的工作方式。

互斥锁

互斥锁是并发控制常用的一种锁,保证了同一时刻同一个资源对象只有一个线程或协程访问,避免出现对变量进行拷贝再赋值导致的问题,而在队列中,使用互斥锁能够保证同一时刻只有入队或出队操作,避免出现混乱,简单的用法如下:

package main

import (
    "fmt"
    "sync"
)

func main() {
    // 互斥锁
    var locker sync.Mutex
    // 一个共享变量
    num := 0

    for i := 0; i < 114514; i++ {
        go func() {
            locker.Lock()
            num++
            locker.Unlock()
            fmt.Println(num)
        }()
    }
    for {
        if num >= 114514 {
            break
        }
    }
    fmt.Println("---------")
    fmt.Println(num)
}

可以尝试去除这个互斥锁运行,但你会发现最后num并没有达到114514,所以程序不会自动结束运行。

切片实现

队列的实现方式有很多种,使用链表性能更优,适合先进先出,但我这里使用了切片来实现,实现了入队出队操作,对内置切片进行维护,包括前移和增容操作。

package main

import (
    "fmt"
    "sync"
)

type Queue struct {
    // 内部维护的切片
    queueSlice []interface{}
    // 容量
    cap uint
    // 起始队列位置
    begin uint
    // 队列尾部位置
    end uint
    // 默认队列大小
    defaultLength uint
    // 并发互斥锁
    locker sync.Mutex
}

func QueueInit(defaultLength uint) *Queue {
    return &Queue{
        queueSlice:    make([]interface{}, defaultLength, defaultLength),
        cap:           defaultLength,
        begin:         0,
        end:           0,
        defaultLength: defaultLength,
        locker:        sync.Mutex{},
    }
}

func (q *Queue) Length() uint {
    // 通过队尾队列开始位置计算长度
    q.locker.Lock()
    length := q.end - q.begin
    q.locker.Unlock()
    return length
}

func (q *Queue) Push(value interface{}) {
    q.locker.Lock()
    // 未超出容量
    if q.end < q.cap {
        q.queueSlice[q.end] = value
        q.end++
    } else {
        // 当长度小于容量进行前移
        if q.end-q.begin < q.cap {
                    // 用for移动会更好
                    // copy,append 也是有开销的
            temp := q.queueSlice[q.begin:q.end]
            q.queueSlice = make([]interface{}, q.cap, q.cap)
            copy(q.queueSlice, temp)
            q.begin = 0
            q.end = uint(len(temp))
            q.queueSlice[q.end] = value
            q.end++
        }
        // 长度达到容量对切片进行扩容
        if q.end >= q.cap {
            temp := make([]interface{}, q.end-q.begin)
            copy(temp, q.queueSlice[q.begin:q.end])
                    // 扩增一倍容量
            q.queueSlice = make([]interface{}, q.cap+q.defaultLength, q.cap+q.defaultLength*2)
            q.cap = q.cap + q.defaultLength
            copy(q.queueSlice, temp)
            q.queueSlice[q.end] = value
            q.end++
        }
    }
    // fmt.Println("|-value", value, "cap", q.cap, "begin", q.begin, "end", q.end)
    q.locker.Unlock()
}

func (q *Queue) Get() interface{} {
    q.locker.Lock()
    if q.begin < q.end {
        value := q.queueSlice[q.begin]
        q.begin++
        q.locker.Unlock()
        return value
    }
    q.locker.Unlock()
    return nil
}

func main() {
    q := QueueInit(6)
    go func(queue *Queue) {
        for {
            value := queue.Get()
            if value == nil {
                continue
            }
            fmt.Println("out", value)
        }
    }(q)
    for i := 0; i <= 27; i++ {
        q.Push(i)
        fmt.Println("in", i)
    }
    for {
        if q.Length() == 0 {
            break
        }
    }
    fmt.Println("len", q.Length())
}

链表实现

package main

import (
    // container/list
    "fmt"
)

type Node struct {
    prev  *Node
    next  *Node
    value interface{}
}

type Queue struct {
    length uint
    // 始节点
    begin  *Node
    // 尾节点
    end    *Node
}

func (q *Queue) Push(value interface{}) {
    node := &Node{prev: nil, next: nil, value: value}
    if q.length == 0 {
        q.begin = node
        q.end = node
        q.length++
        return
    }
    q.end.next = node
    node.prev = q.end
    q.end = node
    q.length++
}

func (q *Queue) Get() interface{} {
    if q.length == 0 {
        return nil
    }
    value := q.begin.value
    q.begin = q.begin.next
    q.length--
    return value
}

func NewQueue() *Queue {
    return &Queue{
        length: 0,
        begin:  nil,
        end:    nil,
    }
}

func main() {
    q := NewQueue()
    go func(q *Queue) {
        for {
            value := q.Get()
            if value == nil {
                break
            }
            fmt.Println("out", value)
        }
    }(q)
    for i := 0; i < 100; i++ {
        q.Push(i)
        fmt.Println("in", i)
    }
    for {
        if q.length == 0 {
            break
        }
    }
}

去网上查了一圈资料后,还是觉的os好用。

权限表

常量名权限
os.O_RDONLY只读
os.O_WRONLY只写
os.O_RDWR读写
os.O_APPEND追加
os.O_CREATE不存在时创建文件
os.O_TRUNC打开时截断文件

写文件

package main
import ("fmt"
        "os")
func checkerr(err error){
        if err != nil{
                fmt.Println(err)
                os.Exit(1)
        }
}
func main(){
        f,err := os.OpenFile("test.txt",os.O_RDWR|os.O_APPEND|os.O_CREATE,0644) //注意文件权限
        checkerr(err)
        defer f.Close()
        data := []byte("-1s -1s -1s\n") //转字节
        bytesw,err := f.Write(data) //写操作
        checkerr(err)
        fmt.Println("bytes:",bytesw)
}

读文件

package main
import ("fmt"
        "os")
func checkerr(err error){
        if err != nil{
                fmt.Println(err)
                os.Exit(1)
        }
}
func main(){
        f,err := os.Open("test.txt") //使用这种方式为只读
        //f,err := os.OpenFile("test.txt",os.O_RDONLY,0644)
        checkerr(err)
        defer f.Close()
        b := make([]byte,6) //读取6个字节,如果要读取全部,要用ioutil.ReadAll(fileObj)
        br,err := f.Read(b)
        checkerr(err)
        fmt.Println("bytes:",br)
        fmt.Println("data:",b)
        fmt.Println("str:",string(b))
}


从网上查到有Go中有两个包可以读写文件,一个是os包,另一个是ioutil包。
我主要用的是ioutil包,用起来比较简单,当然os也是有他的优势的。

读取文件

package main
import ("fmt"
        "reflect"
        "io/ioutil")
func main(){
        data,err := ioutil.ReadFile("test.txt")
        if err != nil{
                fmt.Println(err)
        }
        fmt.Println("Type:",reflect.TypeOf(data))
        fmt.Println("Data",string(data))
}
//output:
Type: []uint8
Data China Mogic Association
Founded in 1926, near the Huangpu river.
//可以看到,读出的是uint8类型的切片

写文件

这会覆盖文件!

package main
import "io/ioutil"
func checkrerr(err error){
        if err != nil{
                panic(err)
        }
}
func main(){
        data := []byte("+1s +1s +1s") //转字节
        err := ioutil.WriteFile("test.txt",data,0644)//文件掩码:-rw-r--r--
        checkrerr(err)
}

ERROR

概念

除了panicrecover之外,Go中还有一个内置的错误接口类型,任何类型只要实现Error() string方法,都可以传递error接口 类型变量,Go语言处理错误的方式是将error作为函数最后一个返回值,在调用函数时,通过检查其返回的error值是否为nil来进 行错误处理。

type error interface{ //接口类型
    Error() string
}

Go语言提供了两个函数返回实现了error接口的具体类型实例,一般的错误可以使这两个函数进行封装,当然,遇到复杂的错误也可以 自定义错误类型,只要实现error接口即可:

func New(text string) error{
    return &errorString{text}
}

最佳实践

  1. 在多个返回值的函数中,error一般作为最后一个返回值
  2. 如果一个函数返回error类型的变量,则先判断是否为nil
  3. defer语句应该放到err判断后面,不然可能产生panic
  4. 在错误逐层向上传递的过程中,错误信息应该不断的丰富与完善,而不是简单的抛出下层调用的错误,便于分析错误

33_error_0.PNG

其他要说的

Golang全面深入系列之 Error:https://studygolang.com/articles/12625
Go 语言如何实现error的优雅处理:https://blog.csdn.net/baogang409/article/details/72604333

阶段性总结

学了有一段时间了,也写了一些Go,感觉执行速度挺快的,毕竟不像Python这种解释性语言。部分语法与Python也很相像,从Python转到Go也不觉的陌生,学Go主要是写网络方面的程序,看网上的评价说Go对于网络编程性能还不错,要不然会有ngrok这种内网穿透的东东出 现。最后,路还长着,一次只学一点,细嚼慢咽的学。