复制项目
This commit is contained in:
191
internal/rpc/msg/qrcode_detect.go
Normal file
191
internal/rpc/msg/qrcode_detect.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright © 2023 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"
|
||||
"git.imall.cloud/openim/protocol/constant"
|
||||
"git.imall.cloud/openim/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
// PictureElem 用于解析图片消息内容
|
||||
type PictureElem struct {
|
||||
SourcePicture struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"sourcePicture"`
|
||||
BigPicture struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"bigPicture"`
|
||||
SnapshotPicture struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"snapshotPicture"`
|
||||
}
|
||||
|
||||
// checkImageContainsQRCode 检测图片中是否包含二维码
|
||||
// userType: 0-普通用户(不能发送包含二维码的图片),1-特殊用户(可以发送)
|
||||
func (m *msgServer) checkImageContainsQRCode(ctx context.Context, msgData *sdkws.MsgData, userType int32) error {
|
||||
// userType=1 的用户可以发送包含二维码的图片,不进行检测
|
||||
if userType == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 只检测图片类型的消息
|
||||
if msgData.ContentType != constant.Picture {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析图片消息内容
|
||||
var pictureElem PictureElem
|
||||
if err := json.Unmarshal(msgData.Content, &pictureElem); err != nil {
|
||||
// 如果解析失败,记录警告但不拦截
|
||||
log.ZWarn(ctx, "failed to parse picture message", err, "content", string(msgData.Content))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取图片URL(优先使用原图,如果没有则使用大图)
|
||||
imageURL := pictureElem.SourcePicture.URL
|
||||
if imageURL == "" {
|
||||
imageURL = pictureElem.BigPicture.URL
|
||||
}
|
||||
if imageURL == "" {
|
||||
imageURL = pictureElem.SnapshotPicture.URL
|
||||
}
|
||||
if imageURL == "" {
|
||||
// 没有有效的图片URL,无法检测
|
||||
log.ZWarn(ctx, "no valid image URL found in picture message", nil, "pictureElem", pictureElem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 下载图片并检测二维码
|
||||
hasQRCode, err := m.detectQRCodeInImage(ctx, imageURL, "")
|
||||
if err != nil {
|
||||
// 检测失败时,记录错误但不拦截(避免误拦截)
|
||||
log.ZWarn(ctx, "QR code detection failed", err, "imageURL", imageURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasQRCode {
|
||||
log.ZWarn(ctx, "检测到二维码,拒绝发送", nil, "imageURL", imageURL, "userType", userType)
|
||||
return servererrs.ErrImageContainsQRCode.WrapMsg("userType=0的用户不能发送包含二维码的图片")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectQRCodeInImage 下载图片并检测是否包含二维码
|
||||
func (m *msgServer) detectQRCodeInImage(ctx context.Context, imageURL string, logPrefix string) (bool, error) {
|
||||
// 创建带超时的HTTP客户端
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
// 下载图片
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imageURL, nil)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "创建HTTP请求失败", err, "imageURL", imageURL)
|
||||
return false, errs.WrapMsg(err, "failed to create request")
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "下载图片失败", err, "imageURL", imageURL)
|
||||
return false, errs.WrapMsg(err, "failed to download image")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.ZError(ctx, "下载图片状态码异常", nil, "statusCode", resp.StatusCode, "imageURL", imageURL)
|
||||
return false, errs.WrapMsg(fmt.Errorf("unexpected status code: %d", resp.StatusCode), "failed to download image")
|
||||
}
|
||||
|
||||
// 限制图片大小(最大10MB)
|
||||
const maxImageSize = 10 * 1024 * 1024
|
||||
limitedReader := io.LimitReader(resp.Body, maxImageSize+1)
|
||||
|
||||
// 创建临时文件
|
||||
tmpFile, err := os.CreateTemp("", "qrcode_detect_*.tmp")
|
||||
if err != nil {
|
||||
log.ZError(ctx, "创建临时文件失败", err)
|
||||
return false, errs.WrapMsg(err, "failed to create temp file")
|
||||
}
|
||||
tmpFilePath := tmpFile.Name()
|
||||
|
||||
// 确保检测完成后删除临时文件(无论成功还是失败)
|
||||
defer func() {
|
||||
// 确保文件已关闭后再删除
|
||||
if tmpFile != nil {
|
||||
_ = tmpFile.Close()
|
||||
}
|
||||
// 删除临时文件,忽略文件不存在的错误
|
||||
if err := os.Remove(tmpFilePath); err != nil && !os.IsNotExist(err) {
|
||||
log.ZWarn(ctx, "删除临时文件失败", err, "tmpFile", tmpFilePath)
|
||||
}
|
||||
}()
|
||||
|
||||
// 保存图片到临时文件
|
||||
written, err := io.Copy(tmpFile, limitedReader)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "保存图片到临时文件失败", err, "tmpFile", tmpFilePath)
|
||||
return false, errs.WrapMsg(err, "failed to save image")
|
||||
}
|
||||
|
||||
// 关闭文件以便后续读取
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
log.ZError(ctx, "关闭临时文件失败", err, "tmpFile", tmpFilePath)
|
||||
return false, errs.WrapMsg(err, "failed to close temp file")
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
if written > maxImageSize {
|
||||
log.ZWarn(ctx, "图片过大", nil, "size", written, "maxSize", maxImageSize)
|
||||
return false, errs.WrapMsg(fmt.Errorf("image too large: %d bytes", written), "image size exceeds limit")
|
||||
}
|
||||
|
||||
// 使用优化的并行解码器检测二维码
|
||||
hasQRCode, err := m.detectQRCodeWithDecoder(ctx, tmpFilePath, "")
|
||||
if err != nil {
|
||||
log.ZError(ctx, "二维码检测失败", err, "tmpFile", tmpFilePath)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return hasQRCode, nil
|
||||
}
|
||||
|
||||
// detectQRCodeWithDecoder 使用优化的解码器检测二维码
|
||||
func (m *msgServer) detectQRCodeWithDecoder(ctx context.Context, imagePath string, logPrefix string) (bool, error) {
|
||||
// 使用Custom解码器(已移除Quirc解码器依赖)
|
||||
customDecoder := &CustomQRDecoder{}
|
||||
|
||||
// 执行解码
|
||||
hasQRCode, err := customDecoder.Decode(ctx, imagePath, logPrefix)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "解码器检测失败", err, "decoder", customDecoder.Name())
|
||||
return false, err
|
||||
}
|
||||
|
||||
return hasQRCode, nil
|
||||
}
|
||||
Reference in New Issue
Block a user