116 lines
2.4 KiB
Go
116 lines
2.4 KiB
Go
package utils
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"image"
|
||
_ "image/jpeg"
|
||
_ "image/png"
|
||
"os"
|
||
"time"
|
||
|
||
"github.com/makiuchi-d/gozxing"
|
||
multiqrcode "github.com/makiuchi-d/gozxing/multi/qrcode"
|
||
"github.com/makiuchi-d/gozxing/qrcode"
|
||
_ "golang.org/x/image/bmp"
|
||
)
|
||
|
||
var (
|
||
ErrQRCodeNotFound = errors.New("未检测到二维码")
|
||
ErrQRCodeDecodeFail = errors.New("二维码解码失败")
|
||
ErrImageDecode = errors.New("图片无法解析")
|
||
)
|
||
|
||
func DecodeFile(ctx context.Context, filePath string, timeout time.Duration) ([]string, error) {
|
||
/*
|
||
解码封装:
|
||
- 统一在此处施加“单张超时”,避免上层 caller 需要重复处理
|
||
- 使用 goroutine 执行实际解码,让 ctx 超时/取消可以及时返回
|
||
- 返回值为 []string:支持“单图多码”
|
||
*/
|
||
if timeout > 0 {
|
||
var cancel context.CancelFunc
|
||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||
defer cancel()
|
||
}
|
||
|
||
type result struct {
|
||
contents []string
|
||
err error
|
||
}
|
||
|
||
ch := make(chan result, 1)
|
||
go func() {
|
||
contents, err := decodeFile(filePath)
|
||
ch <- result{contents: contents, err: err}
|
||
}()
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
case r := <-ch:
|
||
return r.contents, r.err
|
||
}
|
||
}
|
||
|
||
func decodeFile(filePath string) ([]string, error) {
|
||
/*
|
||
识别策略:
|
||
1) 先尝试多码识别(QRCodeMultiReader)
|
||
2) 如果多码失败,再回退到单码识别(QRCodeReader)
|
||
失败时返回稳定的业务错误(ErrImageDecode/ErrQRCodeNotFound/ErrQRCodeDecodeFail)
|
||
*/
|
||
f, err := os.Open(filePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer f.Close()
|
||
|
||
img, _, err := image.Decode(f)
|
||
if err != nil {
|
||
return nil, ErrImageDecode
|
||
}
|
||
|
||
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
|
||
if err != nil {
|
||
return nil, ErrImageDecode
|
||
}
|
||
|
||
mr := multiqrcode.NewQRCodeMultiReader()
|
||
rs, err := mr.DecodeMultiple(bmp, nil)
|
||
if err == nil && len(rs) > 0 {
|
||
return uniqueTexts(rs), nil
|
||
}
|
||
|
||
r := qrcode.NewQRCodeReader()
|
||
single, err2 := r.Decode(bmp, nil)
|
||
if err2 == nil && single != nil {
|
||
return []string{single.GetText()}, nil
|
||
}
|
||
|
||
if err2 != nil {
|
||
return nil, ErrQRCodeDecodeFail
|
||
}
|
||
return nil, ErrQRCodeNotFound
|
||
}
|
||
|
||
func uniqueTexts(rs []*gozxing.Result) []string {
|
||
seen := map[string]struct{}{}
|
||
out := make([]string, 0, len(rs))
|
||
for _, r := range rs {
|
||
if r == nil {
|
||
continue
|
||
}
|
||
t := r.GetText()
|
||
if t == "" {
|
||
continue
|
||
}
|
||
if _, ok := seen[t]; ok {
|
||
continue
|
||
}
|
||
seen[t] = struct{}{}
|
||
out = append(out, t)
|
||
}
|
||
return out
|
||
}
|