engineerper 1 年間 前
コミット
ed00c4914f
6 ファイル変更241 行追加3 行削除
  1. 2 0
      config.yaml
  2. 87 3
      eventServer/eventServer.go
  3. 70 0
      eventServer/plateNumber.go
  4. 20 0
      model/app/carinfo.go
  5. 61 0
      service/app/carinfo.go
  6. 1 0
      service/app/enter.go

+ 2 - 0
config.yaml

@@ -56,3 +56,5 @@ hikvision:
   user: admin
   password: kk176@lc
   streamBaseUrl: "webrtc://106.52.134.22/live/"
+
+

+ 87 - 3
eventServer/eventServer.go

@@ -4,7 +4,13 @@ import (
 	"encoding/xml"
 	"fmt"
 	"github.com/sirupsen/logrus"
+	mail "github.com/xhit/go-simple-mail/v2"
+	"io"
 	"io/ioutil"
+	"lc-fangdaosha/model/app"
+	"lc-fangdaosha/service"
+	"log"
+	"mime/multipart"
 	"net/http"
 	"strings"
 	"time"
@@ -33,7 +39,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
 		w.Header().Add("Date", time.Now().String())
 		w.Header().Add("Connection", "close")
 	}()
-	//logRequest(r)
+	logRequest(r)
 	contentType := r.Header.Get("Content-Type")
 	if strings.Contains(contentType, "application/xml") {
 		bytes, err := ioutil.ReadAll(r.Body)
@@ -50,13 +56,91 @@ func handler(w http.ResponseWriter, r *http.Request) {
 		//处理事件 todo 邮箱
 		handleEvent_(event)
 	} else if strings.Contains(contentType, "multipart/form-data") {
-		fmt.Println("multipart/form-data 事件")
-		//handleMultipart(r)
+		handleMultipart(r)
 	} else {
 		logrus.WithField("Content-Type", contentType).Error("该Content-Type没有写处理逻辑")
 		return
 	}
 }
+
+//TODO 限制事件重复触发,一段时间内不重复发送邮件,但保持报警状态
+
+var eventService = service.ServiceGroupApp.AppServiceGroup.EventService
+var carInfoService = service.ServiceGroupApp.AppServiceGroup.CarInfoService
+
+// 处理多文件事件
+func handleMultipart(r *http.Request) {
+	// todo  远程联动
+	multipartReader := multipart.NewReader(r.Body, "boundary")
+	var msg string //邮件消息
+	// 循环读取每个 part
+	var eventAlert EventNotificationAlert
+	for {
+		part, err := multipartReader.NextPart()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Println("Failed to read part:", err)
+			continue
+		}
+		// 检查 part 的 Content-Disposition
+		formName := part.FormName()
+		//if formName != "intrusionImage" {
+		if !strings.Contains(formName, "Image") {
+			//不含图片的xml部分数据
+			xmlData, err := ioutil.ReadAll(part)
+			if err != nil {
+				return
+			}
+			xml.Unmarshal(xmlData, &eventAlert)
+
+			msg = handleEvent_(eventAlert)
+			continue
+		}
+		//处理图片部分数据
+		contentType := part.Header.Get("Content-Type")
+		eventCode := part.Header.Get("Content-ID")
+		picName := timeFmt(eventAlert.DateTime) + ".jpeg"
+		data, _ := ioutil.ReadAll(part)
+		if strings.Contains(eventAlert.ChannelName, "PlateNumber") {
+			//todo 识别车牌,记录次数
+			number, err := CallLicensePlateRecognitionAPI(data)
+			if err != nil {
+				fmt.Println("车牌识别失败:", err)
+				//time.Sleep(1000)
+			}
+			//todo 处理车牌号
+			carInfoService.ProcessPlateNumber(number)
+			return
+		}
+		f := &mail.File{
+			Name:     picName,
+			MimeType: contentType,
+			Data:     data,
+			Inline:   true,
+		}
+		picture := &app.Picture{
+			Name: picName,
+			Time: time.Now(),
+			Mime: contentType,
+			Size: len(data),
+		}
+		pictureData := &app.PictureData{
+			Data: data,
+		}
+		event := &app.Event{
+			EventCode:  eventCode,
+			MacAddress: eventAlert.MacAddress,
+			EventType:  eventAlert.EventType,
+		}
+		//保存图片
+		go eventService.Save(event, picture, pictureData)
+		//邮件通知
+		SendAlarmEmail(eventAlert.MacAddress, msg, f)
+	}
+}
+
 func handleEvent_(event EventNotificationAlert) string {
 	var eType string
 	if event.EventType == "duration" {

+ 70 - 0
eventServer/plateNumber.go

@@ -0,0 +1,70 @@
+package eventServer
+
+import (
+	"encoding/base64"
+	"fmt"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
+	ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
+)
+
+func CallLicensePlateRecognitionAPI(file []byte) (number string, err error) {
+	// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
+	credential := common.NewCredential(
+		"SecretId",
+		"SecretKey",
+	)
+	// 实例化一个client选项
+	cpf := profile.NewClientProfile()
+	cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
+	// 实例化要请求产品的client对象,clientProfile是可选的
+	client, _ := ocr.NewClient(credential, regions.Shanghai, cpf)
+
+	// 实例化一个请求对象,每个接口都会对应一个request对象
+	request := ocr.NewLicensePlateOCRRequest()
+	base64String := base64.StdEncoding.EncodeToString(file)
+	request.ImageBase64 = common.StringPtr(base64String)
+
+	// 返回的resp是一个LicensePlateOCRResponse的实例,与请求对象对应
+	response, err := client.LicensePlateOCR(request)
+	if _, ok := err.(*errors.TencentCloudSDKError); ok {
+		fmt.Printf("An API error has returned: %s", err)
+		return
+	}
+	if err != nil {
+		panic(err)
+	}
+
+	// 输出json格式的字符串回包
+	fmt.Printf("%s\n", response.ToJsonString())
+	fmt.Printf("车牌:%s\n", *response.Response.Number)
+	return *response.Response.Number, err
+}
+
+type AutoGenerated struct {
+	Response struct {
+		Color             string `json:"Color"`
+		Confidence        int    `json:"Confidence"`
+		LicensePlateInfos []struct {
+			Color      string `json:"Color"`
+			Confidence int    `json:"Confidence"`
+			Number     string `json:"Number"`
+			Rect       struct {
+				Height int `json:"Height"`
+				Width  int `json:"Width"`
+				X      int `json:"X"`
+				Y      int `json:"Y"`
+			} `json:"Rect"`
+		} `json:"LicensePlateInfos"`
+		Number string `json:"Number"`
+		Rect   struct {
+			Height int `json:"Height"`
+			Width  int `json:"Width"`
+			X      int `json:"X"`
+			Y      int `json:"Y"`
+		} `json:"Rect"`
+		RequestID string `json:"RequestId"`
+	} `json:"Response"`
+}

+ 20 - 0
model/app/carinfo.go

@@ -0,0 +1,20 @@
+package app
+
+import (
+	"lc-fangdaosha/global"
+	"time"
+)
+
+// CarInfo 结构体
+type CarInfo struct {
+	global.GVA_MODEL
+	PlateNumber string `json:"plateNumber" form:"plateNumber" gorm:"column:plate_number;comment:车牌号;"`
+	//CarType     string    `json:"carType" form:"carType" gorm:"column:car_type;comment:车类型;"`
+	Time  time.Time `json:"time" form:"time" gorm:"column:time;comment:出现时间;"`
+	Count int       `json:"count" form:"count" gorm:"column:count;comment:出现次数;"`
+}
+
+// TableName CarInfo 自定义表名
+func (CarInfo) TableName() string {
+	return "car_info"
+}

+ 61 - 0
service/app/carinfo.go

@@ -0,0 +1,61 @@
+package app
+
+import (
+	"fmt"
+	"lc-fangdaosha/global"
+	"lc-fangdaosha/model/app"
+	"time"
+)
+
+type CarInfoService struct {
+}
+
+// Save 保存到数据库
+func (carInfoService *CarInfoService) Save(car []*app.CarInfo) {
+	// 插入数据
+	err := global.Db.Create(car)
+	if err != nil {
+		fmt.Println("插入数据失败:", err)
+		return
+	}
+
+}
+
+// ProcessPlateNumber 处理车牌号
+func (carInfoService *CarInfoService) ProcessPlateNumber(plateNumber string) {
+	var cars []*app.CarInfo //定义切片用于存储app.CarInfo类型的指针
+	for i, car := range cars {
+		if car.PlateNumber == plateNumber { //检查当前车辆的车牌号是否与给定的plateNumber相等
+			currentTime := time.Now()                    //获取当前时间,并将其赋值给变量currentTime
+			if currentTime.Sub(car.Time).Minutes() > 1 { //检查当前时间与车辆信息的最后更新时间之间的差值是否大于1分钟
+				car.Count++
+				car.Time = currentTime //更新车辆信息的最后更新时间为当前时间
+			}
+			cars[i] = car //将更新后的车辆信息重新赋值给cars切片中对应的位置
+			return
+		}
+	}
+	//创建一个新的app.CarInfo类型的实例,并设置其车牌号、计数器和最后更新时间为当前时间
+	newCar := &app.CarInfo{
+		PlateNumber: plateNumber,
+		Count:       1,
+		Time:        time.Now(),
+	}
+	cars = append(cars, newCar) //将新创建的车辆信息添加到cars切片的末尾
+	// 保存到数据库
+	carInfoService.Save(cars)
+	//// 获取当前时间
+	//now := time.Now()
+	//// 判断同一辆车在一分钟内是否已出现
+	//plateNumberMap := make(map[string]*app.CarInfo)
+	//if plateNumberMap[plateNumber] != nil && now.Sub(plateNumberMap[plateNumber].Time) < time.Minute {
+	//	return
+	//}
+	//
+	//// 更新车牌号出现时间和次数
+	//plateNumberMap[plateNumber] = &app.CarInfo{
+	//	Time:        now,
+	//	Count:       1,
+	//	PlateNumber: plateNumber,
+	//}
+}

+ 1 - 0
service/app/enter.go

@@ -4,4 +4,5 @@ type ServiceGroup struct {
 	CameraService
 	GatewayService
 	EventService
+	CarInfoService
 }