golang
Golang读取文件内容
读文件
读取的文件放在file/test:也就是file包下的test这个文件,里面写多一点文件
- 读文件方式一:利用ioutil.ReadFile直接从文件读取到[]byte中
func Read0() (string){
f, err := ioutil.ReadFile("file/test")
if err != nil {
fmt.Println("read fail", err)
}
return string(f)
}
- 读文件方式二:先从文件读取到file中,在从file读取到buf, buf在追加到最终的[]byte
func Read1() (string){
//获得一个file
f, err := os.Open("file/test")
if err != nil {
fmt.Println("read fail")
return ""
}
//把file读取到缓冲区中
defer f.Close()
var chunk []byte
buf := make([]byte, 1024)
for {
//从file读取到buf中
n, err := f.Read(buf)
if err != nil && err != io.EOF{
fmt.Println("read buf fail", err)
return ""
}
//说明读取结束
if n == 0 {
break
}
//读取到最终的缓冲区中
chunk = append(chunk, buf[:n]...)
}
return string(chunk)
//fmt.Println(string(chunk))
}
- 读文件方式三:先从文件读取到file, 在从file读取到Reader中,从Reader读取到buf, buf最终追加到[]byte
//先从文件读取到file, 在从file读取到Reader中,从Reader读取到buf, buf最终追加到[]byte,这个排第三
func Read2() (string) {
fi, err := os.Open("file/test")
if err != nil {
panic(err)
}
defer fi.Close()
r := bufio.NewReader(fi)
var chunks []byte
buf := make([]byte, 1024)
for {
n, err := r.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
if 0 == n {
break
}
//fmt.Println(string(buf))
chunks = append(chunks, buf...)
}
return string(chunks)
//fmt.Println(string(chunks))
}
- 读文件方式四:读取到file中,再利用ioutil将file直接读取到[]byte中
//读取到file中,再利用ioutil将file直接读取到[]byte中, 这是最优
func Read3() (string){
f, err := os.Open("file/test")
if err != nil {
fmt.Println("read file fail", err)
return ""
}
defer f.Close()
fd, err := ioutil.ReadAll(f)
if err != nil {
fmt.Println("read to fd fail", err)
return ""
}
return string(fd)
}
读取速度比较
经过我的测试,这四种方式读的速度排名是:前者为优
方式四 > 方式一 > 方式三 > 方式四
写文件
- 写文件方式一:使用 io.WriteString 写入文件
func Write0() {
fileName := "file/test1"
strTest := "测试测试"
var f *os.File
var err error
if CheckFileExist(fileName) { //文件存在
f, err = os.OpenFile(fileName, os.O_APPEND, 0666) //打开文件
if err != nil{
fmt.Println("file open fail", err)
return
}
}else { //文件不存在
f, err = os.Create(fileName) //创建文件
if err != nil {
fmt.Println("file create fail")
return
}
}
//将文件写进去
n, err1 := io.WriteString(f, strTest)
if err1 != nil {
fmt.Println("write error", err1)
return
}
fmt.Println("写入的字节数是:", n)
}
- 写文件方式二:使用 ioutil.WriteFile 写入文件
func Write1() {
fileName := "file/test2"
strTest := "测试测试"
var d = []byte(strTest)
err := ioutil.WriteFile(fileName, d, 0666)
if err != nil {
fmt.Println("write fail")
}
fmt.Println("write success")
}
- 写文件方式三:使用 File(Write,WriteString) 写入文件
func Write2() {
fileName := "file/test3"
strTest := "测试测试"
var d1 = []byte(strTest)
f, err3 := os.Create(fileName) //创建文件
if err3 != nil{
fmt.Println("create file fail")
}
defer f.Close()
n2, err3 := f.Write(d1) //写入文件(字节数组)
fmt.Printf("写入 %d 个字节n", n2)
n3, err3 := f.WriteString("writesn") //写入文件(字节数组)
fmt.Printf("写入 %d 个字节n", n3)
f.Sync()
}
- 写文件方式四:使用 bufio.NewWriter 写入文件
func Write3() {
fileName := "file/test3"
f, err3 := os.Create(fileName) //创建文件
if err3 != nil{
fmt.Println("create file fail")
}
w := bufio.NewWriter(f) //创建新的 Writer 对象
n4, err3 := w.WriteString("bufferedn")
fmt.Printf("写入 %d 个字节n", n4)
w.Flush()
f.Close()
}
- 检查文件是否存在:
func CheckFileExist(fileName string) bool {
_, err := os.Stat(fileName)
if os.IsNotExist(err) {
return false
}
return true
}
作者:e0c52543163a
链接:https://www.jianshu.com/p/711c453bff16
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
golang 中 strings 包的 Replace 用法介绍
函数声明为:
func Replace(s, old, new string, n int) string
1
官方描述为:
返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
1
示例代码为,每行的结果见每行上面的注释部分:
func main() {
// non-overlapping: “123” repeat 6 times in s
s := “123lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l”
old := “123”
new := “888”
fmt.Println(“non-overlapping: “)
// n < 0 ,用 new 替换所有匹配上的 old;n=-1: 888lafaldsjglad888lkfasdf888djfal888lkdjga888lksjfla888l
fmt.Println(“n=-1: “, strings.Replace(s, old, new, -1 ))
// 不替换任何匹配的 old;n=0: 123lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l
fmt.Println(“n=0: “, strings.Replace(s, old, new, 0 ))
// 用 new 替换第一个匹配的 old;n=1: 888lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l
fmt.Println(“n=1: “, strings.Replace(s, old, new, 1 ))
// 用 new 替换前 5 个匹配的 old(实际多于 5 个);n=5: 888lafaldsjglad888lkfasdf888djfal888lkdjga888lksjfla123l
fmt.Println(“n=5: “, strings.Replace(s, old, new, 5 ))
// 用 new 替换前 7 个匹配上的 old(实际没那么多);n=7: 888lafaldsjglad888lkfasdf888djfal888lkdjga888lksjfla888l
fmt.Println(“n=7: “, strings.Replace(s, old, new, 7 ))
// overlapping:
s = “888888888888888888”
old = “888”
new = “666”
fmt.Println(“overlapping: “)
// n < 0 ,用 new 替换所有匹配上的 old;n=-1: 666666666666666666
fmt.Println(“n=-1: “, strings.Replace(s, old, new, -1 ))
// 不替换任何匹配的 old;n=0: 888888888888888888
fmt.Println(“n=0: “, strings.Replace(s, old, new, 0 ))
// 用 new 替换第一个匹配的 old;n=1: 666888888888888888
fmt.Println(“n=1: “, strings.Replace(s, old, new, 1 ))
// 用 new 替换前 5 个匹配的 old(实际多于 5 个);n=5: 666666666666666888
fmt.Println(“n=5: “, strings.Replace(s, old, new, 5 ))
// 用 new 替换前 7 个匹配上的 old(实际没那么多);n=7: 666666666666666666
fmt.Println(“n=7: “, strings.Replace(s, old, new, 7 ))
}
Go 并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
例如:
go f(x, y, z)
开启一个新的 goroutine:
f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
实例
import (
“fmt”
“time”
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say(“world”)
say(“hello”)
}
执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:
world
hello
hello
world
world
hello
hello
world
world
hello
通道(channel)
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch v := <-ch // 从 ch 接收数据 // 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
实例
import “fmt”
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
输出结果为:
-5 17 12
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
实例
import “fmt”
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
执行输出结果为:
1 2
Go 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
实例
import (
“fmt”
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
执行输出结果为:
0 1 1 2 3 5 8 13 21 34
golang gin绑定json(ShouldBindJSON)
绑定json(ShouldBindJSON)
func main() {
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Login information is not complete"})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
router.Run(":8080")
}
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
Golang实现Sign in With Apple服务端登录验证
2019年之后,对于Apple App来说,如果要支持第三方登录,则必须同时支持苹果的第三方登录,即Sign in With Apple, 本文主要介绍如何使用Go语言实现Sign in With Apple时服务端的验证, 即Generate and Validate Tokens。或者不支持第三方登录, 直接使用电话号码或者账号密码的方式进行注册以及登录。
登录流程
流程大概可以描述为:
- app请求通过Apple进行第三方登录,此时,客户端将会获得包括用户唯一凭证UserID(与微信的OpenId类似), 用户全名Full Name, 验证用的Code(IdentityCode)以及验证用的Token(IdentityToken)。
- 客户端将获得的数据发送给服务器,由服务器通过IdentityCode或者IdentityToken来验证此次登录是否有效。
- 如果验证通过, 服务端处理完自己内部的登录流程后, 将对应的登录结果(状态)返回给客户端。
在第二步服务器的验证过程中,服务器只需要选择Code或者Token中的任意一种进行验证即可:
- IdentityToken: 根据Apple官方文档, Token验证方式为JSON WEB Token(JWT), 按照对应的方式进行验证即可。
- IdentityCode: 根据Apple官方文档, 通过Code验证需要Apple开发者对该App进行配置的额外client_id, client_secret以及redirect_uri三个参数。
IdentityToken验证
此种验证方法为传统的JWT验证, Token由Header, Payload以及Signature三部分组成, 通过JSON序列化每一部分,然后使用Base64URL编码后通过.拼接起来的字符串。
- Header: 包括的字段如下,
- kid: 表示用于验证签名的Apple公钥
- alg: 表示用于签名的算法
- Payload: 包括的字段有如下,
- iss(string): 表示Token签发机构, 值固定为: https://appleid.apple.com
- aud(string): 表示Apple App的ID
- exp(int64): 表示Token的过期时间, 时间戳
- iat(int64): 表示client_secret生成时间,时间戳
- sub(string): 表示用户唯一标识
- c_hash(string): 文档中没看到这个字段, 作用未知
- auth_time(int64): 表示签名生成时间
- email(string): 表示用户邮箱, 可能是真实的也可能Apple处理过的密文邮件地址,取决于用户登录时是否选择了隐藏邮箱
- email_verified(bool): 表示用户邮箱是否已验证, 由于Apple总是返回已验证了的邮箱, 所以这个字段的值总是为true, 但是需要注意的是, Apple返回的true, 可能是字符串也可能是bool类型, 需要自己处理一下。
- nonce(string): 只有当发起登录请求的时候传递了此参数, 在验证的时候才会返回,目的是为了降低被攻击的可能性
- nonce_supported(bool): 表示是否支持nonce, 如果为true, 则需要判断nonce字段值是否正确
- is_private_email(bool): 表示用户提供的邮箱地址是否是Apple处理了的代理邮箱地址
- real_user_status(int): 表示用户是否是真实用户: 0(Unsupported: 表示当前系统版本不支持该字段的值, 只有在IOS 14及以上版本, macOS 11及以上版本, watchOS 7及以上版本才支持), 1(Unknown: 系统无法识别是否是真实用户), 2(LikelyReal: 几乎可以确定为真实用户)
- Signature: 表示签名字段,用Base64URL对Header和Payload分别编码,然后用.拼接, 最后使用RSA以及SHA256进行签名得到的结果
一个Header和Payload的例子为:
1
2 3 4 5 6 7 8 9 10 11 |
{
“alg”: “RS256”, “kid”: “ABC123DEFG” } { “iss”: “DEF123GHIJ”, “iat”: 1437179036, “exp”: 1493298100, “aud”: “https://appleid.apple.com”, “sub”: “com.mytest.app” } |
一个IdentityToken例子如下:
1
|
eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmZ1bi5BcHBsZUxvZ2luIiwiZXhwIjoxNTY4NzIxNzY5LCJpYXQiOjE1Njg3MjExNjksInN1YiI6IjAwMDU4MC4wODdjNTU0ZGNlMzU0NjZmYTg1YzVhNWQ1OTRkNTI4YS4wODAxIiwiY19oYXNoIjoiel9KY0RscFczQjJwN3ExR0Nna1JaUSIsImF1dGhfdGltZSI6MTU2ODcyMTE2OX0.WmSa4LzOzYsdwTqAJ_8mub4Ls3eyFkxZoGLoy-U7DatsTd_JEwAs3_OtV4ucmj6ENT3153iCpYY6vBxSQromOMcXsN74IrUQew24y_zflN2g4yU8ZVvBCbTrR_6p9f2fbeWjZiyNcbPCha0dv45E3vBjyHhmffWnk3vyndBBiwwuqod4pyCZ3UECf6Vu-o7dygKFpMHPS1ma60fEswY5d-_TJAFk1HaiOfFo0XbL6kwqAGvx8HnraIxyd0n8SbBVxV_KDxf15hdotUizJDW7N2XMdOGQpNFJim9SrEeBhn9741LWqkWCgkobcvYBZsrvnUW6jZ87SLi15rvIpq8_fw
|
根据上面可以得出验证IdentityToken的步骤为:
- 以.为分隔点, 将IdentityToken分隔为三部分, 第三部分为签名, 留着用于验证
- 使用Base64URL解码对应的Header和Payload, 并JSON反序列化为对应的结构体(或者键值对), 并且对Payload中相应对值进行验证,如exp, sub, iat, aud
- 通过接口从Apple Server获取RSA公钥,接口地址https://appleid.apple.com/auth/keys, 这里需要注意, 获取到的结果通常为两个,需要用选择与Header中的kid值匹配的那个Key
- 步骤3返回的Key中包含了RSA公钥中的N和E的值,同样是用Base64URL编码后的值, 需要解码, 然后再构造RSA公钥
-
得到公钥后,将步骤1中得到的Base64URL编码的Header和Payload再次拼接起来,然后调用rsa.VerifyPKCS1v15()方法进行签名验证, 注意这里的Hash类型为SHA256
验证代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
func (v *Validator) CheckIdentityToken(token string) (JWTToken, error) {
if token == “” { return nil, ErrInvalidIdentityToken } appleToken, err := parseToken(token) if err != nil { return nil, err } key, err := fetchKeysFromApple(appleToken.header.Kid) if err != nil { return nil, err } if key == nil { return nil, ErrFetchKeysFail } pubKey, err := generatePubKey(key.N, key.E) //利用获取到的公钥解密token中的签名数据 //苹果使用的是SHA256 h.Write([]byte(appleToken.headerStr + “.” + appleToken.claimsStr)) return appleToken, rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, h.Sum(nil), sig) |
IdentityCode验证
按照官方文档, IdentityCode的验证相对来说限制要高一点,没有那么通用, 因为验证过程中需要用到client_id, client_secret, redirect_uri三个参数, 由于每个Apple App这三个参数都不相同, 所以没有IdentityToken那么通用。
根据官方文档, IdentityCode的验证需要调用接口向Apple Server验证, 接口地址为: https://appleid.apple.com/auth/token
文档中已经说得很明白, 具体代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
func (v *Validator) CheckIdentityCode(code string) (*TokenResponse, error) {
if code == “” { return nil, ErrInvalidIdentityCode } if v.clientID == “” { return nil, ErrInvalidClientID } if v.clientSecret == “” { return nil, ErrInvalidClientSecret } //验证IdentityCode时需要填写redirect_uri参数,且redirect_uri参数必须是https协议 if uri := strings.ToLower(v.redirectUri); strings.HasPrefix(uri, “https://”) { return nil, ErrInvalidRedirectURI } param := fmt.Sprintf(“client_id=%s&client_secret=%s&code=%s&grant_type=authorization_code&redirect_uri=%s”, v.clientID, v.clientSecret, code, v.redirectUri) if response.StatusCode != http.StatusOK { data, err := ioutil.ReadAll(response.Body) var tkResult *TokenResponse |
详细代码请前往Github
原文地址Golang实现Sign in With Apple服务端登录验证
golang 设置国内加速
1 | go env - w GOPROXY = https: / / goproxy.cn,direct |
- 阿里云
配置如下:
export GOPROXY=https://mirrors.aliyun.com/goproxy/
- nexus社区提供的
配置如下:
export GOPROXY=https://gonexus.dev
- goproxy.io 的
配置如下:
export GOPROXY=https://goproxy.io/
- 基于athens的公共服务
配置如下:
export GOPROXY=https://athens.azurefd.net
- 官方提供的(jfrog,golang)
export GOPROXY=https://gocenter.io
export GOPROXY=https://proxy.golang.org
- 七牛云赞助支持的
export GOPROXY=https://goproxy.cn
Windows 系统下安装
Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.4.2.windows-amd64.msi)的安装包来安装。
下载地址:https://studygolang.com/dl
默认情况下 .msi 文件会安装在 c:\Go 目录下。 这时候c:\Go\bin 目录应该在 Path 环境变量中,环境变量中会生成一个GOROOT,内容为你的go安装目录,一般在系统盘(C:)下。

image.png

image.png
安装测试
go version
输出类似以下
go version go1.15 windows/amd64
如果没有输出信息,提示go无法识别,检查你的go是否在环境变量中,以及goroot是否有配置,这是两个东西,。
假如你已经安装过,可以直接进行新版本的安装,他会自动检测版本并提示你是否卸载旧版本。C:安装路径一般是你的C/下面,会有一个GO文件夹。
UNIX/Linux/Mac OS X, 和 FreeBSD 安装
以下介绍了在UNIX/Linux/Mac OS X, 和 FreeBSD系统下使用源码安装方法:
1、下载二进制包:go1.4.linux-amd64.tar.gz。
2、将下载的二进制包解压至 /usr/local目录。
tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
3、将 /usr/local/go/bin 目录添加至PATH环境变量:
export PATH=$PATH:/usr/local/go/bin
注意:MAC 系统下你可以使用 .pkg 结尾的安装包直接双击来完成安装,安装目录在 /usr/local/go/ 下。