标签 go 下的文章

GORM不使用数据绑定获取任意SQL获取结果

在gorm中,想获取sql执行的结果,必须将返回结果绑定到slice或者struct中,但是如果我们想通用执行任意语句,在事先不知道表结构的情况下是没办法使用这个方式的,最后通过查阅资料发现一个曲线救国的方式

rows, err := db.Raw(sql).Rows()
    if err != nil {
        result = append(result, err.Error())
        return result
    }
    var colums []string
    for rows.Next() {
        //先获取所有的column
        if colums == nil {
            colums, _ = rows.Columns()
        }
        result = append(result, colums)
        //建立俩个interface数组,columnPointers中存在columns的地址
        columns := make([]interface{}, len(colums))
        columnPointers := make([]interface{}, len(colums))
        for i, _ := range columns {
            //赋值地址
            columnPointers[i] = &columns[i]
        }
        //扫描结果
        rows.Scan(columnPointers...)
        m := make(map[string]interface{})
        for i, colName := range colums {
            val := columnPointers[i].(*interface{})
            m[colName] = *val
        }

        //log.Println(m)
        result = append(result, m)
        //result = append(result, row)
    }
    return result
    //log.Println(result)

}

一开始不太好理解,然后去看了gorm Scan部分的代码
sql/sql.go

func (rs *Rows) Scan(dest ...interface{}) error {
    rs.closemu.RLock()
    ...一些不重要的代码...
    for i, sv := range rs.lastcols {
        //重点看convertAssignRows,是如何赋值的
        err := convertAssignRows(dest[i], sv, rs)
        if err != nil {
            return fmt.Errorf(`sql: Scan error on column index %d, name %q: %v`, i, rs.rowsi.Columns()[i], err)
        }
    }
    return nil
}

func convertAssignRows(dest, src interface{}, rows *Rows) error {
    // 通过反射找到具体类型
    var sv reflect.Value

    switch d := dest.(type) {
    //字符串指针
    case *string:
        .....
    case *[]byte:
        .....
    case *RawBytes:
        ......
    case *bool:
        .....
    //就是这里了,我们传进来的是指向columns的interface的指针,所以直接给columns的值修改了
    case *interface{}:
        *d = src
        return nil
    }

    ...一些不相关的代码...
}

参考资料

https://kylewbanks.com/blog/query-result-to-map-in-golang

https://gocn.vip/topics/2121

GO SMTP SSL链接出错问题

Golang自带了一个smtp模块,正常登录流程如下

hostname := fmt.Sprintf("%s:%s", host, port)  
domain := strings.Split(email, "@")[1]  
auth := smtp.PlainAuth("", email, password, domain)
smtpClient, err = smtp.Dial(hostname)  
if err != nil {  
   log.Println("Dial: ", err)  
   return false  
}
err = smtpClient.Auth(auth)  
if err != nil {  
   log.Println("Auth: ", err)  
   return false  
}

但是我发现在链接465即配置了SSL的smtp服务器会导致30秒之后直接返回一个EOF错误

最后通过翻阅smtp/smtp.go代码发现如下:

//默认使用该方法
func Dial(addr string) (*Client, error) {
    //先创建tcp链接
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        return nil, err
    }
    host, _, _ := net.SplitHostPort(addr)
    //在进入NewClient
    return NewClient(conn, host)
}

// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
func NewClient(conn net.Conn, host string) (*Client, error) {
    text := textproto.NewConn(conn)
    _, _, err := text.ReadResponse(220)
    //其实这里已经失败了,所以返回一个EOF
    if err != nil {
        text.Close()
        return nil, err
    }
    c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
    _, c.tls = conn.(*tls.Conn)
    return c, nil
}

那么我们只需要创建一个tls.Dial,然后在通过tls的Conn去创建一个NewClient即可。

最后修复代码如下

func SmtpLogin(host, port, email, password string, ssl bool) bool {
    hostname := fmt.Sprintf("%s:%s", host, port)
    domain := strings.Split(email, "@")[1]
    auth := smtp.PlainAuth("", email, password, domain)
    var smtpClient *smtp.Client
    var err error

    if ssl {
        tlsconfig := &tls.Config{
            InsecureSkipVerify: true,
            ServerName:         host,
        }
        //创建一个tls链接
        if conn, err := tls.Dial("tcp", hostname, tlsconfig); err != nil {
            log.Println("tls.Dial: ", err)
            return false
        } else {
            smtpClient, err = smtp.NewClient(conn, domain)
        }

    } else {

        if tcpConn, err := net.Dial("tcp", hostname); err != nil {
            log.Println("net.Dial: ", err)
            return false
        } else {
            smtpClient, err = smtp.NewClient(tcpConn, domain)
        }
    }

    if err != nil {
        log.Println("smtp.NewClient: ", err)
        return false
    }

    err = smtpClient.Auth(auth)
    if err != nil {
        log.Println("Auth: ", err)
        return false
    }
    return true
}

参考资料

https://gist.github.com/jim3ma/b5c9edeac77ac92157f8f8affa290f45
https://bastengao.com/blog/2019/11/go-smtp-ssl.html
https://gist.github.com/chrisgillis/10888032
https://stackoverflow.com/questions/57063411/go-smtp-unable-to-send-email-through-gmail-getting-eof