Golang
本文最后更新于 2024-06-04,文章内容可能已经过时。
获取minio文件列表报错invalid URL escape
事件起因是需要做一个minio文件夹展示
在获取桶中的文件列表时报错:invalid URL escape "%#$"
打断点一路排查到下面的源码:
func (c *Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken // Validate bucket name. if err := s3utils.CheckValidBucketName(bucketName); err != nil { return ListBucketV2Result{}, err } // Validate object prefix. if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil { return ListBucketV2Result{}, err } // Get resources properly escaped and lined up before // using them in http request. urlValues := make(url.Values)
// Always set list-type in ListObjects V2 urlValues.Set("list-type", "2")
if metadata { urlValues.Set("metadata", "true") }
// Set this conditionally if asked if startAfter != "" { urlValues.Set("start-after", startAfter) }
// Always set encoding-type in ListObjects V2 urlValues.Set("encoding-type", "url")
// Set object prefix, prefix value to be set to empty is okay. urlValues.Set("prefix", objectPrefix)
// Set delimiter, delimiter value to be set to empty is okay. urlValues.Set("delimiter", delimiter)
// Set continuation token if continuationToken != "" { urlValues.Set("continuation-token", continuationToken) }
// Fetch owner when listing if fetchOwner { urlValues.Set("fetch-owner", "true") }
// Set max keys. if maxkeys > 0 { urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys)) }
// Execute GET on bucket to list objects. resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ bucketName: bucketName, queryValues: urlValues, contentSHA256Hex: emptySHA256Hex, customHeader: headers, }) defer closeResponse(resp) if err != nil { return ListBucketV2Result{}, err } if resp != nil { if resp.StatusCode != http.StatusOK { return ListBucketV2Result{}, httpRespToErrorResponse(resp, bucketName, "") } }
// Decode listBuckets XML. listBucketResult := ListBucketV2Result{} if err = xmlDecoder(resp.Body, &listBucketResult); err != nil { return listBucketResult, err }
// This is an additional verification check to make // sure proper responses are received. if listBucketResult.IsTruncated && listBucketResult.NextContinuationToken == "" { return listBucketResult, ErrorResponse{ Code: "NotImplemented", Message: "Truncated response should have continuation token set", } }
for i, obj := range listBucketResult.Contents { listBucketResult.Contents[i].Key, err = decodeS3Name(obj.Key, listBucketResult.EncodingType) if err != nil { return listBucketResult, err } listBucketResult.Contents[i].LastModified = listBucketResult.Contents[i].LastModified.Truncate(time.Millisecond) }
for i, obj := range listBucketResult.CommonPrefixes { listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType) if err != nil { return listBucketResult, err } }
// Success. return listBucketResult, nil}
Go
最后一个for循环这里报错
for i, obj := range listBucketResult.CommonPrefixes { listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType) if err != nil { return listBucketResult, err } }
Go
通过断点查看到listBucketResult.CommonPrefixes第一个元素是"'空字符串,但是obj 解析出来却是#@$!#%#$%/ ,很神奇
通义千问的回答:
这个问题看起来你遇到的情况是由于数据解析或解码时出现了错误,导致期望的数据与实际获得的数据不一致。具体来说,你提到的listBucketResult.CommonPrefixes的第一个元素在预期是空字符串(""),但实际获取到的是#@!#%#$%/这样的非法或乱码字符序列。这可能涉及几个方面的原因和解决方向:1. 数据编码解码问题:数据在存储或传输过程中可能被错误地编码或解码。特别是如果数据中包含特殊字符(如#, $, @, #, % 等)且没有正确处理,可能导致解码时出现问题。检查decodeS3Name函数的实现,确保它能正确处理了所有可能的字符,特别是特殊字符和编码类型。2. 解码类型混淆:确保listBucketResult.CommonPrefixes中数据的编码类型与你解码的预期一致。如果数据被错误地以一种编码类型存储(比如URL编码,Base64,百分比编码等),而解码时使用了另一种方式,可能会导致混乱的结果。3. 错误处理:decodeS3Name函数内部可能存在逻辑错误处理了异常情况,导致错误字符没有被正确捕获并返回。确保对非法或意外情况有合适的错误处理机制,而不是错误地生成了奇怪的输出。4. 字符串截断点检查:你提到的错误输出#@!#%#$%/,看起来像是一种不完整的转义序列,可能是因为错误处理导致的截断点问题。检查是否在解码过程中正确处理了字符串的边界,没有提前结束或截断开了未完成的转义序列。5. 测试和日志记录:在decodeS3Name函数的关键点增加日志记录,记录传入和传出的数据,有助于跟踪问题具体发生在哪个环节。总之,你需要详细审查数据的处理流程,特别是解码和解码步骤,确保所有数据一致性,以及错误处理的逻辑健全。如果decodeS3Name是自定义的函数,复审阅其源码找出可能的逻辑缺陷是关键
Plain Text
但是这个是minio官方的包,ai回答的几个问题都不切实际.
猜测是以前minio上传的文件有问题,存在一些奇怪的命名?
用另一个环境的s3正常获取文件列表,估计报错的s3是测试用的,以前只管丢文件进去,现在新增个需求需要文件列表,哦豁,给我遇到了.
Gorm
goalng的orm框架
gorm.model 自带ID、软删除deleteAt、createAt、updateAt
gorm的软删除是更新删除时间,没有删除的时候是NULL
使用 IS NULL可以走索引
但是IS NOT NULL不可以
并且NULL值作为联合索引会导致索引失效
硬删除使用Unscoped()
init函数执行顺序
在main包中导入别的包时,会优先执行导入包中的init函数,再执行main包的init函数
同一个包中不同文件的init函数的执行顺序,是根据文件名的先后顺序来确定的
当递归导入包时,init函数的执行顺序也是递归的
在同一个文件中导入不同包时,包的init执行顺序跟导入的顺序一致
filepath.walkDir
最后还是递归去推文件夹...
预分配内存和直接使用 性能差距
go 版本1.21
100,000以内直接使用性能比 预分配更好??? 偶尔预分配内存比直接使用好一点 差距也不大..100,000Pre-allocate: 1.4812131s
Direct append: 1.3112955s10,000,000
Pre-allocate: 1.1199524s
Direct append: 1.0088049s
Plain Text
func main() { result := testing.Benchmark(BenchmarkPreAllocate) fmt.Printf("Pre-allocate: %v\n", result.T)
result = testing.Benchmark(BenchmarkDirectAppend) fmt.Printf("Direct append: %v\n", result.T)}func BenchmarkPreAllocate(b *testing.B) { for i := 0; i < b.N; i++ { arr := make([]string, 0, 100) for j := 0; j < 100; j++ { arr = append(arr, "F1747902535533854721") //模拟业务操作耗时 time.Sleep(1 * time.Millisecond) } }}
func BenchmarkDirectAppend(b *testing.B) { for i := 0; i < b.N; i++ { var arr []string for j := 0; j < 100; j++ { arr = append(arr, "F1747902535533854721") //模拟业务操作耗时 time.Sleep(1 * time.Millisecond) } }}
Go
实际应该看下面的代码块执行结果pre: 84.4864ms
pre: 72.2162ms
pre: 94.6278ms
pre: 63.8266ms
pre: 67.3263ms
pre: 56.8733ms
pre: 60.8382ms
pre: 60.8385ms
pre: 62.8336ms
pre: 61.8554ms
pre: 87.7657ms
pre: 58.8441ms
pre: 70.828ms
pre: 71.7597ms
pre: 59.8416ms
pre: 64.8255ms
pre: 62.8421ms
pre: 53.471ms
pre: 74.8001ms
pre: 81.7836ms
pre: 44.8797ms
pre: 58.843ms
pre: 56.8474ms
pre: 58.8537ms
pre: 46.8743ms
pre: 57.8457ms
pre: 64.8274ms
pre: 58.842ms
pre: 50.8645ms
pre: 57.8452ms
Pre-allocate: 1.0164032s
dir: 554.4596ms
dir: 583.0342ms
dir: 593.4523ms
Direct append: 1.1774837s
进程 已完成,退出代码为 0
很明显在相同时间内,预分配内存执行了30次而切片append执行执行了三次。。。差距过大!
Plain Text
func main() { result := testing.Benchmark(BenchmarkPreAllocate) fmt.Printf("Pre-allocate: %v\n", result.T)
result = testing.Benchmark(BenchmarkDirectAppend) fmt.Printf("Direct append: %v\n", result.T)}func BenchmarkPreAllocate(b *testing.B) { for i := 0; i < b.N; i++ { s := time.Now() arr := make([]string, 0, 10000000) for j := 0; j < 10000000; j++ { arr = append(arr, "F1747902535533854721") //模拟业务操作耗时 //time.Sleep(1 * time.Millisecond) } fmt.Println("pre:", time.Since(s)) }}func BenchmarkDirectAppend(b *testing.B) { for i := 0; i < b.N; i++ { s := time.Now() var arr []string for j := 0; j < 10000000; j++ { arr = append(arr, "F1747902535533854721") //模拟业务操作耗时 //time.Sleep(1 * time.Millisecond) } fmt.Println("dir:", time.Since(s)) }}
Go
context
golang context处理中断和异常
Go 语言中处理中断和异常通常是通过 panic/recover 机制来实现的。panic
可以触发一个运行时异常,而 recover
可以在 defer 函数中捕获这个异常,从而防止程序崩溃。以下是一个简单的例子,展示了如何使用 panic
和 recover
来处理异常:
package main
import (
"fmt"
)
func main() {
f := func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
panic("Panic in f")
}
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in main", r)
}
}()
f()
panic("Panic in main")
}
Go
在这个例子中,main
函数中首先调用了 f
函数,在 f
函数中触发了一个 panic
。通过在 f
函数外部设置一个 defer
语句,并在其中调用 recover
,可以捕获并处理 f
函数中的 panic
。同样,在 main
函数的 defer
中也调用了 recover
,以处理在 main
函数中可能发生的 panic
。注意,recover
必须在 defer
函数中直接被调用,否则它不会有效。此外,panic
一旦被触发,程序会立即停止当前函数的执行,并开始逐层返回调用栈,查找是否有 recover
。如果没有找到,程序最终会导致崩溃。
context处理超时和退出示例
package main
import (
"context"
"fmt"
"time"
)
func timeConsuming(ctx context.Context, costTime int) {
ctx.Done()
for i := 1; i <= costTime; i++ {
// 模拟一些耗时操作
time.Sleep(1 * time.Second)
fmt.Printf("协程正在运行第%v次...\n", i)
}
}
func main() {
// 创建一个父级 context,设置超时时间为 5 秒钟
parentCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 创建一个子级 context,用于控制协程
childCtx, childCancel := context.WithCancel(parentCtx)
defer childCancel()
costTime := 5 // 模拟耗时 5 秒钟
// 启动一个协程
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
// 如果收到取消信号,退出协程
fmt.Println("协程退出")
return
case <-time.After(15 * time.Second):
fmt.Println("协程超时")
default:
timeConsuming(childCtx, costTime)
}
}
}(childCtx)
// 等待 3 秒钟,然后取消子级 context
time.Sleep(3 * time.Second)
fmt.Println("取消协程")
childCancel()
// 继续等待 3 秒钟,模拟主协程的一些其他操作
time.Sleep(3 * time.Second)
fmt.Println("主协程退出")
}
Go
context详解:
详解Golang中Context的原理和使用技巧_Golang_脚本之家
Golang 的 Context 包,中文可以称之为“上下文”,是用来在 goroutine 协程之间进行上下文信息传递的,这些上下文信息包括 kv 数据、取消信号、超时时间、截止时间等。本文主要介绍了Context的原理和使用技巧,希望对大家有所帮助
https://www.jb51.net/article/268744.htm
图解 Golang Context
介绍context 是整个 Go 生态系统的关键部分。在这篇简短的文章中,我将解释 context 在 Go 应用程序中是如何工作的。 概述从非常高级的角度来看,可以将 Context 与n叉树数据结构进行比较。查看下图: 在本文中,…
https://zhuanlan.zhihu.com/p/467991975