初始
This commit is contained in:
34
third/arithmetic/SpartaApi/entity.go
Normal file
34
third/arithmetic/SpartaApi/entity.go
Normal file
@ -0,0 +1,34 @@
|
||||
package SpartaApi
|
||||
|
||||
//type UniversalEntity struct {
|
||||
// code string `json:"code" dc:"200:业务处理成功;40x:访问权限错误;50x:系统错误。"`
|
||||
// msg string `json:"msg" dc:"结果消息提示,如:success"`
|
||||
// data string `json:"leftTopPoint" dc:"业务执行结果(obj/arr)"`
|
||||
//}
|
||||
|
||||
type TokenEntity struct {
|
||||
Token string `json:"token" dc:"token"`
|
||||
ExpiresAt int `json:"expires_at" dc:"过期时间"`
|
||||
}
|
||||
|
||||
type RecognizeReq struct {
|
||||
CapUrl string `json:"capUrl" v:"required" dc:"v-在线图片地址"`
|
||||
RecType string `json:"recType" v:"required" dc:"v-识别算法模型"`
|
||||
Async string `json:"async" dc:"是否异步处理:false:默认值,算法识别完后同步返回,接口请求会等待(适用于识别时间较长的模型);true: 收到请求后,算法服务器会异步识别"`
|
||||
CallBackUrl string `json:"callBackUrl" dc:"若为异步,则需要此参数,系统会将结果post到这个接口中"`
|
||||
AreaHigh string `json:"area_high" dc:"若为异步,则需要此参数,系统会将结果post到这个接口中"`
|
||||
}
|
||||
|
||||
type RecognizeRes struct {
|
||||
HasTarget int `json:"hasTarget" dc:"是否监测到目标:1:是;0:否"`
|
||||
OriginalImgSize []int `json:"originalImgSize" dc:"原始图片尺寸([宽,高]),ex:[1920,1080]"`
|
||||
Targets []TargetsEntity `json:"targets" dc:"目标信息"`
|
||||
}
|
||||
|
||||
type TargetsEntity struct {
|
||||
PhoNumber int `json:"pho_number" dc:"在光伏板检测的时候会多输出一个参数代表光伏板的数量"`
|
||||
Type string `json:"type" dc:"目标信息"`
|
||||
Size []float64 `json:"size" dc:"目标外接矩形像素"`
|
||||
LeftTopPoint []float64 `json:"leftTopPoint" dc:"目标在画面中左上角位置信息"`
|
||||
Score float64 `json:"score" dc:"置信度得分(0~1)"`
|
||||
}
|
635
third/arithmetic/SpartaApi/entrance.go
Normal file
635
third/arithmetic/SpartaApi/entrance.go
Normal file
@ -0,0 +1,635 @@
|
||||
package SpartaApi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/tiger1103/gfast-cache/cache"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Sensitivity = 0.6 // 识别灵敏度 //AI工单(摄像头)
|
||||
CAMERA = "head smoke" // AI工单(摄像头)
|
||||
UAV_MATRIX = "pho hole shelves pile" // 方阵内容
|
||||
UAV_MACHINE = "excavator Roller Truck_crane Loader Submersible_drilling_rig Sprinkler Truck_mounted_crane Truck" // 大型机械
|
||||
|
||||
//远界
|
||||
YJCAMERA = "fire,hardhat,smoking"
|
||||
)
|
||||
|
||||
var Url = g.Cfg().MustGet(gctx.New(), "spartaApi.Url").String()
|
||||
var yjUrl = g.Cfg().MustGet(gctx.New(), "yjSpartaApi.Url").String()
|
||||
|
||||
// 定义一个映射来存储不同的类型及其对应的描述
|
||||
var typeDescriptions = map[string]string{
|
||||
"head": "未带安全帽",
|
||||
"smoke": "吸烟",
|
||||
"belt": "未系安全带",
|
||||
"excavator": "挖掘机",
|
||||
"Roller": "压路机",
|
||||
"Truck_crane": "汽车吊",
|
||||
"Loader": "装载机",
|
||||
"Submersible_drilling_rig": "潜挖钻机",
|
||||
"Sprinkler": "沙水车",
|
||||
"Truck_mounted_crane": "随车吊",
|
||||
"Truck": "货车",
|
||||
|
||||
//远界
|
||||
"nohelmet": "未戴安全帽",
|
||||
"cigarette": "吸烟",
|
||||
"fire": "火灾",
|
||||
"smoggy": "烟雾",
|
||||
}
|
||||
|
||||
// @Title GetDescription 2024/8/7 17:34:00
|
||||
// @Description 可复用的函数,根据输入的类型返回对应的描述
|
||||
// @Auth Cory
|
||||
func GetDescription(tp string) string {
|
||||
if description, exists := typeDescriptions[tp]; exists {
|
||||
return description
|
||||
}
|
||||
return "未知类型"
|
||||
}
|
||||
|
||||
//func init() {
|
||||
// fmt.Println("斯巴达----------------")
|
||||
// token, _ := spartaApiTokenFunc()
|
||||
// fmt.Println("token----------------", token)
|
||||
//}
|
||||
|
||||
// @Title spartaApiTokenFunc 2024/8/7 17:35:00
|
||||
// @Description 登录获取身份凭证
|
||||
// @Auth Cory
|
||||
func spartaApiTokenFunc() (token string, err error) {
|
||||
key := "arithmetic:token"
|
||||
ctx := gctx.New()
|
||||
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
|
||||
gfCache := cache.New(prefix)
|
||||
get, err := g.Redis().Get(ctx, gfCache.CachePrefix+key)
|
||||
if err != nil && get.String() != "" {
|
||||
token = get.String()
|
||||
return token, err
|
||||
} else {
|
||||
clientId := g.Cfg().MustGet(gctx.New(), "spartaApi.key")
|
||||
clientSecret := g.Cfg().MustGet(gctx.New(), "spartaApi.secret")
|
||||
uri := Url + "/token?clientId=" + clientId.String() + "&clientSecret=" + clientSecret.String()
|
||||
response, err := g.Client().Get(gctx.New(), uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tk *TokenEntity
|
||||
err = json.Unmarshal([]byte(response.ReadAllString()), &tk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
tk.Token = "Basic " + tk.Token
|
||||
}
|
||||
// 将token存储到redis中,tiken默认时间为秒,(这里少5000秒,防止token过期还存在redis中)
|
||||
commonService.Cache().Set(ctx, key, tk.Token, time.Duration(tk.ExpiresAt-5000)*time.Second)
|
||||
token = tk.Token
|
||||
return token, err
|
||||
}
|
||||
}
|
||||
|
||||
//// @Title CommonAlgorithmTwoFunc 2024/8/7 17:06:00
|
||||
//// @Description 提供算法得到的目标信息
|
||||
//// @Auth Cory
|
||||
//func CommonAlgorithmTwoFunc(ctx context.Context, recognize *RecognizeReq) (result *RecognizeRes, flag bool, err error) {
|
||||
// result = new(RecognizeRes)
|
||||
// flag = false
|
||||
// // 1、调用算法
|
||||
// body, err := ObjectIdentificationFunc(ctx, recognize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// // 2、组装数据
|
||||
// err = json.Unmarshal(body, &result)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// // 3、布尔值
|
||||
// if len(result.Targets) == 0 { // 表示无违规操作
|
||||
// return
|
||||
// } else { // 表述有违规操作
|
||||
// flag = true
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
|
||||
// @Title CommonAlgorithmTwoUav 2024/8/7 17:06:00
|
||||
// @Description 提供算法得到的目标信息【飞机专用】
|
||||
// @Auth Cory
|
||||
func CommonAlgorithmTwoUav(ctx context.Context, recognize *RecognizeReq) (result *RecognizeRes, flag bool, err error) {
|
||||
result = new(RecognizeRes)
|
||||
flag = false
|
||||
//// 1、调用算法
|
||||
//body, err := ObjectIdentificationFunc(ctx, recognize)
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
|
||||
recognizeResult := `{
|
||||
"hasTarget": 1,
|
||||
"originalImgSize": [1920, 1080],
|
||||
"targets": [
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [282, 274],
|
||||
"leftTopPoint": [3192, 2758],
|
||||
"score": 0.6154276132583618
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [343, 139],
|
||||
"leftTopPoint": [4577, 2836],
|
||||
"score": 0.722245454788208
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [372, 158],
|
||||
"leftTopPoint": [4554, 3761],
|
||||
"score": 0.7273581027984619
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [351, 129],
|
||||
"leftTopPoint": [4124, 2251],
|
||||
"score": 0.749356746673584
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [336, 152],
|
||||
"leftTopPoint": [4211, 2379],
|
||||
"score": 0.7633119225502014
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [343, 147],
|
||||
"leftTopPoint": [4321, 2515],
|
||||
"score": 0.7663174867630005
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [333, 152],
|
||||
"leftTopPoint": [4933, 2771],
|
||||
"score": 0.7939081192016602
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [334, 143],
|
||||
"leftTopPoint": [4574, 2966],
|
||||
"score": 0.8044442534446716
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [332, 138],
|
||||
"leftTopPoint": [4243, 2921],
|
||||
"score": 0.8075516819953918
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [315, 156],
|
||||
"leftTopPoint": [3628, 2535],
|
||||
"score": 0.8091028332710266
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [336, 129],
|
||||
"leftTopPoint": [3913, 3655],
|
||||
"score": 0.8116179704666138
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [342, 139],
|
||||
"leftTopPoint": [4471, 2290],
|
||||
"score": 0.814013659954071
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [350, 143],
|
||||
"leftTopPoint": [4528, 2432],
|
||||
"score": 0.8172851800918579
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [342, 171],
|
||||
"leftTopPoint": [3992, 2469],
|
||||
"score": 0.8185974359512329
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [338, 149],
|
||||
"leftTopPoint": [4433, 3345],
|
||||
"score": 0.8222267031669617
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [332, 134],
|
||||
"leftTopPoint": [4284, 2660],
|
||||
"score": 0.8247078657150269
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [328, 134],
|
||||
"leftTopPoint": [4616, 2713],
|
||||
"score": 0.8274019956588745
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [350, 139],
|
||||
"leftTopPoint": [4928, 3685],
|
||||
"score": 0.8295787572860718
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [339, 136],
|
||||
"leftTopPoint": [4908, 2905],
|
||||
"score": 0.8317150473594666
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [339, 135],
|
||||
"leftTopPoint": [4232, 3708],
|
||||
"score": 0.8319769501686096
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [323, 141],
|
||||
"leftTopPoint": [3461, 3167],
|
||||
"score": 0.8382804989814758
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [368, 132],
|
||||
"leftTopPoint": [3761, 3219],
|
||||
"score": 0.8471034169197083
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [338, 140],
|
||||
"leftTopPoint": [3894, 2986],
|
||||
"score": 0.8511925339698792
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [337, 144],
|
||||
"leftTopPoint": [3580, 2942],
|
||||
"score": 0.8524681329727173
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [348, 142],
|
||||
"leftTopPoint": [3620, 3467],
|
||||
"score": 0.8603440523147583
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [352, 145],
|
||||
"leftTopPoint": [4632, 2584],
|
||||
"score": 0.8645903468132019
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [365, 139],
|
||||
"leftTopPoint": [4585, 3633],
|
||||
"score": 0.8663488626480103
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [323, 138],
|
||||
"leftTopPoint": [3919, 2847],
|
||||
"score": 0.8686794638633728
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [394, 155],
|
||||
"leftTopPoint": [3644, 3335],
|
||||
"score": 0.8729261755943298
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [328, 143],
|
||||
"leftTopPoint": [4274, 3576],
|
||||
"score": 0.8783137798309326
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [300, 314],
|
||||
"leftTopPoint": [3247, 2571],
|
||||
"score": 0.879583477973938
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [660, 217],
|
||||
"leftTopPoint": [4903, 3817],
|
||||
"score": 0.8797935843467712
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [331, 170],
|
||||
"leftTopPoint": [3923, 2713],
|
||||
"score": 0.8806164264678955
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [332, 140],
|
||||
"leftTopPoint": [4240, 2782],
|
||||
"score": 0.8832747936248779
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [318, 143],
|
||||
"leftTopPoint": [4129, 3295],
|
||||
"score": 0.8922125101089478
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [349, 143],
|
||||
"leftTopPoint": [3867, 3116],
|
||||
"score": 0.8925455808639526
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [333, 138],
|
||||
"leftTopPoint": [4016, 3404],
|
||||
"score": 0.8939041495323181
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [353, 158],
|
||||
"leftTopPoint": [3942, 2604],
|
||||
"score": 0.8950177431106567
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [350, 148],
|
||||
"leftTopPoint": [4655, 3507],
|
||||
"score": 0.9090152382850647
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [322, 153],
|
||||
"leftTopPoint": [3587, 2792],
|
||||
"score": 0.9102197289466858
|
||||
},
|
||||
{
|
||||
"type": "pho",
|
||||
"size": [348, 159],
|
||||
"leftTopPoint": [4777, 3397],
|
||||
"score": 0.9125684499740601
|
||||
}
|
||||
],
|
||||
"pho_number": 41
|
||||
}`
|
||||
body := []byte(recognizeResult)
|
||||
|
||||
// 2、组装数据
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
// 3、布尔值
|
||||
if len(result.Targets) == 0 { // 表示无违规操作
|
||||
flag = false
|
||||
} else { // 表述有违规操作
|
||||
flag = true
|
||||
}
|
||||
|
||||
//绘图
|
||||
replace := strings.Replace(recognize.CapUrl, coryCommon.GlobalPath, "", 1)
|
||||
imgPath := coryCommon.FileToFunc(replace, 2)
|
||||
|
||||
num := 0
|
||||
mp := make(map[string]string)
|
||||
entity := coryCommon.TestDrawRectTextEntity{
|
||||
ImPath: imgPath,
|
||||
}
|
||||
var zuobiao []*coryCommon.CoordinatesListEntity
|
||||
for _, data := range result.Targets {
|
||||
if data.Score >= Sensitivity {
|
||||
tp := data.Type
|
||||
mp[tp] = GetDescription(tp)
|
||||
flag = true
|
||||
num = num + 1
|
||||
zuobiao = append(zuobiao, &coryCommon.CoordinatesListEntity{
|
||||
X: data.LeftTopPoint[0],
|
||||
Y: data.LeftTopPoint[1],
|
||||
W: data.Size[0],
|
||||
H: data.Size[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
entity.Coordinates = zuobiao
|
||||
coryCommon.TestDrawRectTextFunc(&entity)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
hat:安全帽识别;
|
||||
head:不戴安全帽识别;
|
||||
smoke:吸烟识别;
|
||||
belt : 安全带识别;
|
||||
waste :工程垃圾识别(暂无)
|
||||
excavator:挖掘机;
|
||||
Roller:压路机;
|
||||
Truck_crane:汽车吊;
|
||||
Loader:装载机;
|
||||
Submersible_drilling_rig:潜挖钻机;
|
||||
Sprinkler:洒水车;
|
||||
Truck_mounted_crane:随车吊;
|
||||
Truck:货车
|
||||
pho:光伏板
|
||||
hole:洞
|
||||
shelves:架子
|
||||
*/
|
||||
func ObjectIdentificationFunc(ctx context.Context, recognize *RecognizeReq) (body []byte, err error) {
|
||||
// 1、获取token
|
||||
token, err := spartaApiTokenFunc()
|
||||
if err != nil {
|
||||
err = errors.New("获取斯巴达token错误!" + err.Error())
|
||||
return
|
||||
}
|
||||
// 2、设置请求数据
|
||||
uri := Url + "/api/recognize?capUrl=" + recognize.CapUrl +
|
||||
"&recType=" + url.QueryEscape(recognize.RecType) +
|
||||
"&async=" + recognize.Async +
|
||||
"&callBackUrl=" + recognize.CallBackUrl +
|
||||
"&area_high=" + recognize.AreaHigh
|
||||
response, err := g.Client().ContentJson().SetHeaderMap(map[string]string{
|
||||
"Authorization": token,
|
||||
}).Get(gctx.New(), uri)
|
||||
defer response.Body.Close()
|
||||
// 3、返回数据
|
||||
body, err = io.ReadAll(response.Body)
|
||||
fmt.Println("请求的URL ", uri)
|
||||
return
|
||||
}
|
||||
|
||||
// @Title CommonAlgorithmFunc 2024/8/7 17:07:00
|
||||
// @Description 调用算法,得到目标信息,然后圈出来
|
||||
// @Auth Cory
|
||||
func CommonAlgorithmFunc(ctx context.Context, recognize *RecognizeReq) (mp map[string]string, flag bool, num int, err error) {
|
||||
flag = false
|
||||
num = 0
|
||||
// 0、记录绝对路径,以便后续操作 //absolutePath := "C:\\Users\\MSI\\Pictures\\222.png"
|
||||
absolutePath := recognize.CapUrl
|
||||
prefix := g.Cfg().MustGet(gctx.New(), "spartaApi.prefix")
|
||||
recognize.CapUrl = replacePrefix(recognize.CapUrl, "/resource/public/", prefix.String()+"/file/")
|
||||
// 1、调用算法
|
||||
body, err := ObjectIdentificationFunc(ctx, recognize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 2、组装数据
|
||||
var data []json.RawMessage
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
var result RecognizeRes
|
||||
for i, rawMsg := range data {
|
||||
switch i {
|
||||
case 0:
|
||||
err = json.Unmarshal(rawMsg, &result.HasTarget)
|
||||
case 1:
|
||||
err = json.Unmarshal(rawMsg, &result.OriginalImgSize)
|
||||
default:
|
||||
var target TargetsEntity
|
||||
err = json.Unmarshal(rawMsg, &target)
|
||||
if err == nil {
|
||||
result.Targets = append(result.Targets, target)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 3、遍历数据,然后在数据中绘画矩形框
|
||||
if result.HasTarget == 0 {
|
||||
return
|
||||
}
|
||||
mp = make(map[string]string)
|
||||
for _, data := range result.Targets {
|
||||
if data.Score >= Sensitivity {
|
||||
tp := data.Type
|
||||
mp[tp] = GetDescription(tp)
|
||||
flag = true
|
||||
num = num + 1
|
||||
coryCommon.Test_draw_rect_text(absolutePath, data.LeftTopPoint[0], data.LeftTopPoint[1], data.Size[0], data.Size[1])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// replacePrefix 将路径中指定前缀及其之前的部分替换成新的前缀
|
||||
func replacePrefix(originalPath, prefixToReplace, newPrefix string) string {
|
||||
// 查找要替换的前缀的位置
|
||||
index := strings.Index(originalPath, prefixToReplace)
|
||||
// 如果找到前缀
|
||||
if index != -1 {
|
||||
// 保留前缀之后的部分
|
||||
remainingPath := originalPath[index+len(prefixToReplace):]
|
||||
// 拼接新的路径
|
||||
newPath := newPrefix + remainingPath
|
||||
return newPath
|
||||
}
|
||||
// 如果未找到前缀,返回原始路径
|
||||
return originalPath
|
||||
}
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
远界算法
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
func CORYAInit(ctx context.Context) {
|
||||
req := RecognizeReq{
|
||||
CapUrl: "http://xny.yj-3d.com:7363/file/temporary/2025-06-22/sxt_1750583729497_972.jpg",
|
||||
RecType: YJCAMERA,
|
||||
}
|
||||
now := time.Now()
|
||||
marshal, _ := json.Marshal(req)
|
||||
fmt.Println("------------", string(marshal))
|
||||
fmt.Println("------------", now)
|
||||
result, flag, err := CommonAlgorithmTwoFunc(gctx.New(), &req)
|
||||
now2 := time.Now()
|
||||
|
||||
fmt.Println("result", result, flag, err)
|
||||
fmt.Println("===========用时", now2.Sub(now))
|
||||
fmt.Println("===========")
|
||||
}
|
||||
|
||||
// @Title CommonAlgorithmTwoFunc 2024/8/7 17:06:00
|
||||
// @Description 提供算法得到的目标信息(远界版本)
|
||||
// @Auth Cory
|
||||
func CommonAlgorithmTwoFunc(ctx context.Context, recognize *RecognizeReq) (result *RecognizeRes, flag bool, err error) {
|
||||
err = g.Try(ctx, func(ctx context.Context) {
|
||||
result = new(RecognizeRes)
|
||||
flag = false
|
||||
// 1、调用算法
|
||||
postData := map[string]interface{}{
|
||||
"type": recognize.RecType,
|
||||
"url": recognize.CapUrl,
|
||||
"extract": false,
|
||||
}
|
||||
response, err := g.Client().ContentJson().Post(ctx, yjUrl, postData)
|
||||
fmt.Println("1请求的URL ", err)
|
||||
liberr.ErrIsNil(ctx, err, "远界算法请求失败")
|
||||
defer response.Body.Close()
|
||||
body, err := io.ReadAll(response.Body)
|
||||
fmt.Println("2请求的URL ", err)
|
||||
liberr.ErrIsNil(ctx, err, "远界算法数据获取失败")
|
||||
fmt.Println("返回的数据为 ", string(body))
|
||||
// 2、组装数据
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//=======================
|
||||
//=======================
|
||||
//1、本地创建一个text来记录日志
|
||||
// 定义文件路径
|
||||
filePath := coryCommon.GetCWD() + "/coryLogs/logs/ai_log.txt"
|
||||
// 打开文件,如果文件不存在则创建
|
||||
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
// 写入日志信息
|
||||
marshal, _ := json.Marshal(result)
|
||||
marshal2, _ := json.Marshal(postData)
|
||||
_, err = file.WriteString(fmt.Sprintf("参数:%s,==================结果:%s\n", string(marshal), string(marshal2)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 3、布尔值
|
||||
if len(result.Targets) == 0 { // 表示无违规操作
|
||||
return
|
||||
} else { // 表述有违规操作
|
||||
flag = true
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
return
|
||||
}
|
668
third/create/a.go
Normal file
668
third/create/a.go
Normal file
@ -0,0 +1,668 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/sony/sonyflake"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/system"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
"github.com/tiger1103/gfast/v3/internal/consts"
|
||||
)
|
||||
|
||||
// 创建 gantt json 数据
|
||||
func CreateGanttJson(ctx context.Context, fangzhenName string, parentName string, data []entity.ProjectSchedule) (string, error) {
|
||||
// 层级: 方阵名 --> 施工项目名 --> 子施工项目名
|
||||
ganttData := make([]system.Gantt, 0, len(data))
|
||||
|
||||
// 添加项目名
|
||||
ganttData = append(ganttData, system.Gantt{Id: 1, StartDate: "2024-01-01 00:00:00", Duration: 15, Text: "方阵1", Parent: 0, Progress: 0, Open: true})
|
||||
ganttData = append(ganttData, system.Gantt{Id: 2, StartDate: "2024-01-01 00:00:00", EndDate: "2024-01-15 00:00:00", Duration: 15, Text: "防雷接地网", Parent: 1, Progress: 0, Open: true})
|
||||
|
||||
i := 3
|
||||
|
||||
for _, project := range data {
|
||||
ganttData = append(ganttData, system.Gantt{
|
||||
Id: int64(i),
|
||||
StartDate: project.StartDate + " 00:00:00",
|
||||
EndDate: project.EndDate + " 00:00:00",
|
||||
Duration: rand.Intn(10) + 1,
|
||||
Text: project.Name,
|
||||
// Progress: float64(project.CurrentProgress),
|
||||
Parent: 2,
|
||||
Open: true,
|
||||
})
|
||||
|
||||
aa := entity.ProjectSchedule{}
|
||||
if err := dao.ProjectSchedule.Ctx(ctx).Where(dao.ProjectSchedule.Columns().ParentId, project.ParentId).
|
||||
WhereNotNull(dao.ProjectSchedule.Columns().Types).Scan(&aa); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ganttData = append(ganttData, system.Gantt{
|
||||
Id: int64(i + 1),
|
||||
StartDate: aa.StartDate + " 00:00:00",
|
||||
EndDate: aa.EndDate + " 00:00:00",
|
||||
Duration: 1,
|
||||
Text: "计划中",
|
||||
Parent: int64(i),
|
||||
})
|
||||
|
||||
i += 2
|
||||
}
|
||||
|
||||
// 打印 gantt json 数据
|
||||
dd, _ := json.Marshal(ganttData)
|
||||
println(string(dd))
|
||||
|
||||
return string(dd), nil
|
||||
}
|
||||
|
||||
func CreateGanttJson2(ctx context.Context, fangzhenName string, parentName string, data []entity.ProjectSchedule) ([]system.Gantt, error) {
|
||||
// 层级: 方阵名 --> 施工项目名 --> 子施工项目名
|
||||
ganttData := make([]system.Gantt, 0, len(data))
|
||||
|
||||
// 添加项目名
|
||||
ganttData = append(ganttData, system.Gantt{Id: 1, StartDate: "2024-01-01 00:00:00", Duration: 15, Text: fangzhenName, Parent: 0, Progress: 0, Open: true})
|
||||
ganttData = append(ganttData, system.Gantt{Id: 2, StartDate: "2024-01-01 00:00:00", EndDate: "2024-01-15 00:00:00", Duration: 15, Text: parentName, Parent: 1, Progress: 0, Open: true})
|
||||
|
||||
i := 3
|
||||
|
||||
index := 0
|
||||
|
||||
for i < len(data) {
|
||||
ganttData = append(ganttData, system.Gantt{
|
||||
Id: int64(i),
|
||||
StartDate: data[index].StartDate + " 00:00:00",
|
||||
EndDate: data[index].EndDate + " 00:00:00",
|
||||
Duration: rand.Intn(10) + 1,
|
||||
Text: data[index].Name,
|
||||
Parent: 2,
|
||||
Open: true,
|
||||
})
|
||||
|
||||
ganttData = append(ganttData, system.Gantt{
|
||||
Id: int64(i + 1),
|
||||
StartDate: data[index+1].StartDate + " 00:00:00",
|
||||
EndDate: data[index+1].EndDate + " 00:00:00",
|
||||
Duration: 1,
|
||||
Text: "计划中",
|
||||
Parent: int64(i),
|
||||
})
|
||||
|
||||
index += 2
|
||||
i += 2
|
||||
|
||||
if index >= len(data) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ganttData, nil
|
||||
}
|
||||
|
||||
type projects struct {
|
||||
Name string
|
||||
StartTime string
|
||||
EndTime string
|
||||
Sub []subJ
|
||||
}
|
||||
type subJ struct {
|
||||
Name string
|
||||
ID int
|
||||
ParentID int
|
||||
StartTime string
|
||||
EndTime string
|
||||
}
|
||||
|
||||
func FetchGanttData(ctx context.Context, matrixID int) ([]system.Gantt, error) {
|
||||
projectData := []entity.ConstructionProject{}
|
||||
// 获取指定方阵下的所有父项目
|
||||
if err := dao.ConstructionProject.Ctx(ctx).
|
||||
Where(dao.ConstructionProject.Columns().FangzhenId, matrixID).Scan(&projectData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 父子项目列表
|
||||
projectList := []projects{}
|
||||
|
||||
// 构造所有的父项目及子项目
|
||||
for _, project := range projectData {
|
||||
detailData := []entity.ConstructionDetails{}
|
||||
if err := dao.ConstructionDetails.Ctx(ctx).
|
||||
Where(dao.ConstructionDetails.Columns().ConstructionId, project.ConstructionId).Scan(&detailData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果 start_time 和 end_time 为空则使用当前时间
|
||||
var startTime string
|
||||
var endTime string
|
||||
|
||||
if project.StartTime == nil {
|
||||
startTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
} else {
|
||||
startTime = project.StartTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
if project.EndTime == nil {
|
||||
endTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
} else {
|
||||
endTime = project.EndTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// 父
|
||||
projectItem := projects{
|
||||
Name: project.ConstructionName,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Sub: make([]subJ, 0, len(detailData)),
|
||||
}
|
||||
|
||||
// 子
|
||||
for _, detail := range detailData {
|
||||
// 如果 start_time 和 end_time 为空则使用当前时间
|
||||
var startTime string
|
||||
var endTime string
|
||||
|
||||
if detail.StartTime == nil {
|
||||
startTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
} else {
|
||||
startTime = detail.StartTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
if detail.EndTime == nil {
|
||||
endTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
} else {
|
||||
endTime = detail.EndTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
projectItem.Sub = append(projectItem.Sub, subJ{
|
||||
Name: detail.Name,
|
||||
ID: int(detail.Id),
|
||||
ParentID: int(detail.Id),
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
})
|
||||
}
|
||||
|
||||
projectList = append(projectList, projectItem)
|
||||
}
|
||||
|
||||
// 1 获取所有的排期数据
|
||||
schedulerList := []entity.ProjectSchedule{}
|
||||
if err := dao.ProjectSchedule.Ctx(ctx).WhereNot(dao.ProjectSchedule.Columns().Types, 0).
|
||||
Scan(&schedulerList); err != nil {
|
||||
}
|
||||
|
||||
// 子项目存在多个排期的情况下,需要将排期数据进行分组
|
||||
scheduleMap := make(map[int][]entity.ProjectSchedule, 0)
|
||||
for _, schedule := range schedulerList {
|
||||
if _, ok := scheduleMap[schedule.ParentId]; !ok {
|
||||
scheduleMap[schedule.ParentId] = make([]entity.ProjectSchedule, 0, 1)
|
||||
}
|
||||
scheduleMap[schedule.ParentId] = append(scheduleMap[schedule.ParentId], schedule)
|
||||
}
|
||||
|
||||
// 1.1 传入 parentid 返回一个子项目的排期数据
|
||||
findSub := func(parentID int) ([]entity.ProjectSchedule, bool) {
|
||||
if schedule, ok := scheduleMap[parentID]; ok {
|
||||
return schedule, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 主任务
|
||||
ganttList := make([]system.Gantt, 0, len(projectList))
|
||||
|
||||
taskIndex := 1
|
||||
// 查询任务是否存在排期如果存在则不添加
|
||||
for _, project := range projectList {
|
||||
// 父级数据
|
||||
ganttList = append(ganttList, system.Gantt{
|
||||
Id: int64(taskIndex),
|
||||
StartDate: project.StartTime,
|
||||
Duration: 15,
|
||||
Text: project.Name,
|
||||
Parent: 0,
|
||||
Open: true,
|
||||
})
|
||||
|
||||
taskIndex++
|
||||
|
||||
// 记录当前主项目的下标
|
||||
parentIndex := taskIndex
|
||||
for _, sub := range project.Sub {
|
||||
scheduleData, ok := findSub(sub.ParentID)
|
||||
// 如果有排期的情况下,填充真实数据
|
||||
if ok {
|
||||
// 添加子任务
|
||||
ganttList = append(ganttList, system.Gantt{
|
||||
Id: int64(taskIndex),
|
||||
StartDate: sub.StartTime,
|
||||
EndDate: sub.EndTime,
|
||||
Duration: rand.Intn(10) + 1,
|
||||
Text: sub.Name,
|
||||
Parent: int64(parentIndex) - 1,
|
||||
ConstructionId: sub.ID,
|
||||
})
|
||||
|
||||
// 记录当前子项目的下标
|
||||
subIndex := taskIndex
|
||||
taskIndex++
|
||||
|
||||
for _, schedule := range scheduleData {
|
||||
// 添加子任务排期计划
|
||||
ganttList = append(ganttList, system.Gantt{
|
||||
Id: int64(taskIndex),
|
||||
StartDate: schedule.StartDate + " 00:00:00",
|
||||
EndDate: schedule.EndDate + " 00:00:00",
|
||||
Duration: gconv.Int(schedule.PlaneNum),
|
||||
Text: "计划中",
|
||||
Parent: int64(subIndex),
|
||||
ConstructionId: sub.ID,
|
||||
Open: true,
|
||||
})
|
||||
taskIndex++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 添加子任务,如果没有排期的情况下,填充假数据,维护队列
|
||||
ganttList = append(ganttList, system.Gantt{
|
||||
Id: int64(taskIndex),
|
||||
StartDate: "2029-01-01 00:00:00",
|
||||
Duration: 15,
|
||||
Text: sub.Name,
|
||||
Parent: int64(parentIndex) - 1,
|
||||
ConstructionId: sub.ID,
|
||||
Open: true,
|
||||
})
|
||||
taskIndex++
|
||||
}
|
||||
}
|
||||
return ganttList, nil
|
||||
}
|
||||
|
||||
func FetchElementData(ctx context.Context, fangzhenId int) ([]*model.WorkStatusProgressRes, error) {
|
||||
// 获取所有的父项目
|
||||
projectData := []entity.WorkStatus{}
|
||||
if err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangzhenId).WhereNull(dao.WorkStatus.Columns().Parent).
|
||||
Scan(&projectData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取所有的子项目
|
||||
detailData := []entity.WorkStatus{}
|
||||
if err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangzhenId).WhereNotNull(dao.WorkStatus.Columns().Parent).Scan(&detailData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 根据关联关系进行数据拼接
|
||||
index := 1
|
||||
projectList := []*model.WorkStatusProgressRes{}
|
||||
for _, project := range projectData {
|
||||
// 父级数据
|
||||
projectItem := model.WorkStatusProgressRes{
|
||||
ID: index,
|
||||
Startat: project.StartAt.String(),
|
||||
Endat: project.EndAt.String(),
|
||||
Name: project.WorkName,
|
||||
Total: project.Total,
|
||||
Finished: project.Finished,
|
||||
Children: make([]model.WorkStatusProgressRes, 0, len(detailData)),
|
||||
}
|
||||
|
||||
index++
|
||||
// 子
|
||||
for _, detail := range detailData {
|
||||
if detail.Parent == int(project.Id) {
|
||||
projectItem.Children = append(projectItem.Children, model.WorkStatusProgressRes{
|
||||
ID: index,
|
||||
Startat: detail.StartAt.String(),
|
||||
Endat: detail.EndAt.String(),
|
||||
Name: detail.WorkName,
|
||||
Total: detail.Total,
|
||||
Finished: detail.Finished,
|
||||
})
|
||||
index++
|
||||
}
|
||||
}
|
||||
projectList = append(projectList, &projectItem)
|
||||
}
|
||||
|
||||
return projectList, nil
|
||||
}
|
||||
|
||||
func randomDate(dateStr string) string {
|
||||
t, _ := time.Parse("2006-01-02", dateStr)
|
||||
randDays := time.Duration(rand.Intn(2)+1) * 24 * time.Hour
|
||||
newDate := t.Add(-randDays)
|
||||
return newDate.Format("2006-01-02")
|
||||
}
|
||||
|
||||
// 获取雪花算法生成的 ID
|
||||
func GetSonyFlakeID() (uint64, error) {
|
||||
// 初始化 sonyFlake 配置
|
||||
st := sonyflake.Settings{}
|
||||
sonyFlake := sonyflake.NewSonyflake(st)
|
||||
if sonyFlake == nil {
|
||||
return 0, fmt.Errorf("需要先初始化以后再执行 GetID 函数")
|
||||
}
|
||||
|
||||
// 获取全局 ID
|
||||
id, err := sonyFlake.NextID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func GetSonyFlakeIDs(mID uint16, count int) (ids []uint64, err error) {
|
||||
// 初始化 sonyFlake 配置
|
||||
st := sonyflake.Settings{}
|
||||
sonyFlake := sonyflake.NewSonyflake(st)
|
||||
if sonyFlake == nil {
|
||||
err = fmt.Errorf("需要先初始化以后再执行 GetID 函数 err: %#v \n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取全局 ID
|
||||
ids = make([]uint64, count)
|
||||
for i := 0; i < count; i++ {
|
||||
id, err := sonyFlake.NextID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids[i] = id
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// FetchTemplateData 为方阵绑定父子项目
|
||||
func FetchTemplateData(ctx context.Context, protectionNetID int64) ([]do.WorkStatus, error) {
|
||||
facilityGroups := createFacilities()
|
||||
|
||||
// 雪花算法生成 ID
|
||||
ids, err := GetSonyFlakeIDs(1, 38)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details := make([]do.WorkStatus, 0, len(facilityGroups))
|
||||
|
||||
index := 0
|
||||
for _, group := range facilityGroups {
|
||||
projectID, err := dao.WorkStatus.Ctx(ctx).Data(do.WorkStatus{
|
||||
FangzhenId: protectionNetID,
|
||||
WorkId: ids[index],
|
||||
WorkName: group.Name,
|
||||
Total: group.FacilityCount,
|
||||
Type: group.TypeNumber,
|
||||
IsPercent: group.IsPercentage,
|
||||
Status: 0,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index++
|
||||
|
||||
for _, component := range group.Components {
|
||||
details = append(details, do.WorkStatus{
|
||||
FangzhenId: protectionNetID,
|
||||
Parent: projectID,
|
||||
WorkId: ids[index],
|
||||
WorkName: component.Name,
|
||||
Total: component.Quantity,
|
||||
IsPercent: component.IsPercentage,
|
||||
Type: component.TypeNumber,
|
||||
Status: 0,
|
||||
})
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
// 为方阵绑定施工项目
|
||||
func CreateConstructionProject(ctx context.Context, protectionNetID int64) error {
|
||||
facilityGroups := createFacilities()
|
||||
|
||||
details := make([]do.ConstructionDetails, len(facilityGroups))
|
||||
for _, group := range facilityGroups {
|
||||
projectID, err := dao.ConstructionProject.Ctx(ctx).Data(do.ConstructionProject{
|
||||
FangzhenId: protectionNetID,
|
||||
ConstructionName: group.Name,
|
||||
Total: group.FacilityCount,
|
||||
IsPercentage: group.IsPercentage,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, component := range group.Components {
|
||||
details = append(details, do.ConstructionDetails{
|
||||
Name: component.Name,
|
||||
ConstructionId: projectID,
|
||||
Total: component.Quantity,
|
||||
IsPercentage: component.IsPercentage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_, err := dao.ConstructionDetails.Ctx(ctx).Batch(50).Insert(details)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFacilities() []system.FacilityGroup {
|
||||
return []system.FacilityGroup{{
|
||||
Name: "防雷接地网",
|
||||
FacilityCount: nil,
|
||||
IsPercentage: &[]int{0}[0],
|
||||
TypeNumber: consts.LightningProtectionNet,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "接地沟",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.GroundingDitch,
|
||||
},
|
||||
{
|
||||
Name: "接地敷设",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.GroundingLaying,
|
||||
},
|
||||
{
|
||||
Name: "检测",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.GroundingTesting,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "围栏",
|
||||
FacilityCount: nil,
|
||||
IsPercentage: &[]int{0}[0],
|
||||
TypeNumber: consts.Fence,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "基础",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
Quantity: 100,
|
||||
TypeNumber: consts.FenceFoundation,
|
||||
},
|
||||
{
|
||||
Name: "安装",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
Quantity: 100,
|
||||
TypeNumber: consts.FenceInstallation,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "道路",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
TypeNumber: consts.Road,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "路基",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
Quantity: 100,
|
||||
TypeNumber: consts.RoadBase,
|
||||
},
|
||||
{
|
||||
Name: "排水沟",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
Quantity: 100,
|
||||
TypeNumber: consts.RoadDrainageDitch,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "低压部分",
|
||||
IsPercentage: &[]int{0}[0],
|
||||
FacilityCount: &[]int{0}[0],
|
||||
TypeNumber: consts.LowVoltage,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "钻孔",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageDrilling,
|
||||
},
|
||||
{
|
||||
Name: "桩基",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltagePileFoundation,
|
||||
},
|
||||
{
|
||||
Name: "支架",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageBracket,
|
||||
},
|
||||
{
|
||||
Name: "光伏板",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltagePhotovoltaicPanel,
|
||||
},
|
||||
{
|
||||
Name: "直流电缆",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageDC,
|
||||
},
|
||||
{
|
||||
Name: "接地线",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageGroundWire,
|
||||
},
|
||||
{
|
||||
Name: "逆变器安装",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.LowVoltageInverterInstallation,
|
||||
},
|
||||
{
|
||||
Name: "电缆沟开挖",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageCableTrenchExcavation,
|
||||
},
|
||||
{
|
||||
Name: "低压电缆敷设",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageLaying,
|
||||
},
|
||||
{
|
||||
Name: "调试",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
Quantity: 0,
|
||||
TypeNumber: consts.LowVoltageDebugging,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "高压部分",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltage,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "箱变基础",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageBoxChangeFoundation,
|
||||
},
|
||||
{
|
||||
Name: "箱变安装",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageBoxChangeInstallation,
|
||||
},
|
||||
{
|
||||
Name: "电缆沟开挖",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageCableLineExcavation,
|
||||
},
|
||||
{
|
||||
Name: "高压电缆敷设",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageCableLaying,
|
||||
},
|
||||
{
|
||||
Name: "高压电缆试验",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageCableTest,
|
||||
},
|
||||
{
|
||||
Name: "高压电缆调试试验",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.HighVoltageCableDebuggingTest,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "环网柜",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinet,
|
||||
Components: []system.Component{
|
||||
{
|
||||
Name: "基础",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinetFoundation,
|
||||
},
|
||||
{
|
||||
Name: "安装",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinetInstallation,
|
||||
},
|
||||
{
|
||||
Name: "敷设",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinetLaying,
|
||||
},
|
||||
{
|
||||
Name: "试验",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinetTest,
|
||||
},
|
||||
{
|
||||
Name: "调试试验",
|
||||
IsPercentage: &[]int{1}[0],
|
||||
TypeNumber: consts.RingMainCabinetDebuggingTest,
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
34
third/excel/equipment_materials.go
Normal file
34
third/excel/equipment_materials.go
Normal file
@ -0,0 +1,34 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
)
|
||||
|
||||
// EquipmentMaterials 设备和材料
|
||||
type EquipmentMaterials struct {
|
||||
EquipmentMaterialsName string `json:"equipment_materials_name"`
|
||||
TotalNumber int `json:"total_number"`
|
||||
TotalQuantityCount int `json:"total_quantity_count"`
|
||||
CumulativeArrivalQuantity int `json:"cumulative_arrival_quantity"`
|
||||
}
|
||||
|
||||
// 传入项目ID,开始时间,结束时间返回 EquipmentMaterials 列表
|
||||
func GetEquipmentMaterials(projectID string, startAt, endAt string) ([]EquipmentMaterials, error) {
|
||||
var list []EquipmentMaterials
|
||||
|
||||
if err := dao.BusEquipmentMaterialsInventory.Ctx(context.Background()).As("bmi").
|
||||
Fields("bem.equipment_materials_name, SUM(bmi.number) AS total_number, SUM(bem.quantity_count) AS total_quantity_count, (SELECT SUM(bi.number) FROM `bus_equipment_materials_inventory` bi WHERE bi.equipment_materials_id = bmi.equipment_materials_id AND bi.deleted_at IS NULL AND bi.out_put = 2) AS cumulative_arrival_quantity").
|
||||
LeftJoin("bus_equipment_materials bem", "bmi.equipment_materials_id = bem.equipment_materials_id").
|
||||
Where("bem.project_id = ?", projectID).
|
||||
Where("bmi.out_put = ?", 2).
|
||||
Where("bmi.created_at >= ?", startAt).
|
||||
Where("bmi.created_at <= ?", endAt).
|
||||
Group("bem.equipment_materials_name").
|
||||
Scan(&list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
513
third/excel/export.go
Normal file
513
third/excel/export.go
Normal file
@ -0,0 +1,513 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// 记录累计完成量和下一周计划量
|
||||
type ExcelInfo struct {
|
||||
// 累计完成量
|
||||
Accumulated int
|
||||
// 下一周计划量
|
||||
NextWeek int
|
||||
}
|
||||
|
||||
// 导出单个方阵
|
||||
func ExportExcel(fangZhenID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
type scheduler struct {
|
||||
// 实体
|
||||
WorkStatus *entity.WorkSchedule
|
||||
// 总进度
|
||||
FinishedPtr int
|
||||
// 总量
|
||||
Total int
|
||||
}
|
||||
|
||||
type Schdule map[string]scheduler
|
||||
|
||||
Get := func(scheduleMap Schdule, workID string) (int, int) {
|
||||
if scheduler, ok := scheduleMap[workID]; ok {
|
||||
return scheduler.FinishedPtr, scheduler.Total
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// 获取指定方阵ID的所有计划
|
||||
ScheduleData := []entity.WorkSchedule{}
|
||||
if err := dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().FangzhenId, fangZhenID).
|
||||
Scan(&ScheduleData); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 存储指定 workID 的累计完成量
|
||||
workMap := make(map[string]ExcelInfo)
|
||||
|
||||
// 计算下一周的日期
|
||||
nextWeekStart := lo.Must(time.Parse("2006-01-02", startAt)).AddDate(0, 0, 7)
|
||||
nextWeekEnd := lo.Must(time.Parse("2006-01-02", endAt)).AddDate(0, 0, 7)
|
||||
|
||||
scheduleMap := make(Schdule)
|
||||
// 遍历 ScheduleData 将数据按照 WorkID 分组
|
||||
lo.ForEach(ScheduleData, func(item entity.WorkSchedule, _ int) {
|
||||
if work, ok := workMap[item.WorkId]; ok {
|
||||
// 累加完成量
|
||||
work.Accumulated += item.FinishedNum
|
||||
workMap[item.WorkId] = work
|
||||
} else {
|
||||
workMap[item.WorkId] = ExcelInfo{
|
||||
Accumulated: item.FinishedNum,
|
||||
}
|
||||
}
|
||||
|
||||
// 如果计划处于下一周,则添加到下一周计划量中
|
||||
stime := lo.Must(time.Parse("2006-01-02", item.StartAt.Format("2006-01-02")))
|
||||
if stime.After(nextWeekStart) && stime.Before(nextWeekEnd) {
|
||||
var scheduleDetails []model.WorkScheduleDetail
|
||||
if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nextweekPlanNum := 0
|
||||
|
||||
for _, detail := range scheduleDetails {
|
||||
// 如果 Date 大于开始时间并小于结束时间
|
||||
if detail.Date <= endAt {
|
||||
nextweekPlanNum += detail.PlanNum
|
||||
}
|
||||
}
|
||||
|
||||
work := workMap[item.WorkId]
|
||||
work.NextWeek += nextweekPlanNum
|
||||
workMap[item.WorkId] = work
|
||||
}
|
||||
|
||||
// 如果开始时间大于当前时间,则跳过
|
||||
startTime := item.StartAt.Format("Y-m-d")
|
||||
if startTime > startAt {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果不存在则直接添加
|
||||
_, ok := scheduleMap[item.WorkId]
|
||||
if !ok {
|
||||
scheduleMap[item.WorkId] = scheduler{
|
||||
WorkStatus: &item,
|
||||
}
|
||||
}
|
||||
|
||||
// 反序列化 item.Detail 字段
|
||||
var scheduleDetails []model.WorkScheduleDetail
|
||||
if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lo.ForEach(scheduleDetails, func(detail model.WorkScheduleDetail, _ int) {
|
||||
// 如果 Date 大于开始时间并小于结束时间
|
||||
if detail.Date <= endAt {
|
||||
scheduler := scheduleMap[item.WorkId]
|
||||
scheduler.Total += detail.PlanNum
|
||||
scheduler.FinishedPtr += detail.FinishedNum
|
||||
scheduleMap[item.WorkId] = scheduler
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 获取指定方阵ID的所有项目
|
||||
workStatusList := []entity.WorkStatus{}
|
||||
if err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangZhenID).Scan(&workStatusList); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 父列表
|
||||
parentList := []entity.WorkStatus{}
|
||||
// 子列表,使用 map 存储,键为父元素的 ID
|
||||
childrenMap := make(map[int][]entity.WorkStatus)
|
||||
|
||||
// 遍历所有数据,将父级和子集分开
|
||||
for i := 0; i < len(workStatusList); i++ {
|
||||
item := workStatusList[i]
|
||||
if item.Parent == 0 {
|
||||
parentList = append(parentList, item)
|
||||
} else {
|
||||
childrenMap[item.Parent] = append(childrenMap[item.Parent], item)
|
||||
}
|
||||
}
|
||||
|
||||
index := 1
|
||||
|
||||
projectList := make([]*model.WorkStatusProgressRes, 0, len(parentList))
|
||||
// 遍历父级,将子集添加到父级的字段中
|
||||
for _, parent := range parentList {
|
||||
projectItem := model.WorkStatusProgressRes{
|
||||
ID: index,
|
||||
WorkID: parent.WorkId,
|
||||
Startat: parent.StartAt.String(),
|
||||
Endat: parent.EndAt.String(),
|
||||
Name: parent.WorkName,
|
||||
Total: parent.Total,
|
||||
Finished: parent.Finished,
|
||||
Status: parent.Status,
|
||||
WorkType: parent.Type,
|
||||
Children: make([]model.WorkStatusProgressRes, 0, len(workStatusList)),
|
||||
}
|
||||
index++
|
||||
|
||||
// 子类的总量和完成量
|
||||
var subTotal, subDone int
|
||||
|
||||
// 从 map 中获取子元素,将子元素添加到父元素的字段中
|
||||
for _, child := range childrenMap[int(parent.Id)] {
|
||||
// 如果计划数量等于实际数量,且原状态非3,则更新状态为2
|
||||
if child.Total == child.Finished && child.Status != 3 {
|
||||
// 并且 total,finished 不为 0
|
||||
if child.Total != 0 && child.Finished != 0 {
|
||||
child.Status = 2
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 WorkID 获取计划总量和实际进度
|
||||
finishedPtr, total := Get(scheduleMap, child.WorkId)
|
||||
|
||||
subTotal += child.Total
|
||||
subDone += child.Finished
|
||||
|
||||
projectItem.Children = append(projectItem.Children, model.WorkStatusProgressRes{
|
||||
ID: index,
|
||||
WorkID: child.WorkId,
|
||||
Startat: child.StartAt.String(),
|
||||
Endat: child.EndAt.String(),
|
||||
Name: child.WorkName,
|
||||
Total: child.Total,
|
||||
Finished: child.Finished,
|
||||
IsPercent: child.IsPercent,
|
||||
Status: child.Status,
|
||||
WorkType: child.Type,
|
||||
PlanProgress: &model.PlanProgress{
|
||||
Finished: &finishedPtr,
|
||||
Total: &total,
|
||||
},
|
||||
})
|
||||
index++
|
||||
}
|
||||
|
||||
// 修改父项目的总量和进度
|
||||
projectItem.Total = subTotal
|
||||
projectItem.Finished = subDone
|
||||
|
||||
projectList = append(projectList, &projectItem)
|
||||
}
|
||||
|
||||
return projectList, workMap, nil
|
||||
}
|
||||
|
||||
// 导出一个子项目
|
||||
func AccumulateProject(subProjectID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) {
|
||||
fangzhenList := []entity.QianqiFangzhen{}
|
||||
if err := dao.QianqiFangzhen.Ctx(context.Background()).
|
||||
Where(dao.QianqiFangzhen.Columns().ProjectId, subProjectID).Scan(&fangzhenList); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cumulativeProjects := []*model.WorkStatusProgressRes{}
|
||||
|
||||
// 用于判断是否为第一次迭代
|
||||
firstTime := true
|
||||
|
||||
excelMap := make(map[string]ExcelInfo)
|
||||
|
||||
// 遍历项目列表
|
||||
for index := 0; index < len(fangzhenList); index++ {
|
||||
// 获取当前项目
|
||||
project := fangzhenList[index]
|
||||
|
||||
// 获取当前项目的子项目
|
||||
childProjects, emap, err := ExportExcel(strconv.Itoa(project.Id), startAt, endAt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
excelMap = lo.Assign(emap, excelMap)
|
||||
|
||||
// 如果是第一次迭代,将子项目列表赋值给累积项目列表
|
||||
if firstTime {
|
||||
cumulativeProjects = childProjects
|
||||
// 更新标志变量,表示已经不是第一次迭代
|
||||
firstTime = false
|
||||
continue
|
||||
}
|
||||
|
||||
// 遍历子项目列表
|
||||
for childIndex := 0; childIndex < len(childProjects); childIndex++ {
|
||||
// 获取当前子项目和对应的累积项目
|
||||
singleChild := childProjects[childIndex]
|
||||
cumulativeChild := cumulativeProjects[childIndex]
|
||||
|
||||
// 更新累积项目的总数和完成数
|
||||
cumulativeChild.Total += singleChild.Total
|
||||
cumulativeChild.Finished += singleChild.Finished
|
||||
|
||||
// 如果当前子项目还有子项目,也进行同样的操作
|
||||
if len(singleChild.Children) > 0 {
|
||||
for subChildIndex := 0; subChildIndex < len(singleChild.Children); subChildIndex++ {
|
||||
// 获取当前子项目的子项目和对应的累积项目
|
||||
singleSubChild := singleChild.Children[subChildIndex]
|
||||
cumulativeSubChild := cumulativeChild.Children[subChildIndex]
|
||||
|
||||
// 更新累积项目的总数和完成数
|
||||
cumulativeSubChild.Total += singleSubChild.Total
|
||||
cumulativeSubChild.Finished += singleSubChild.Finished
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cumulativeProjects, excelMap, nil
|
||||
}
|
||||
|
||||
// 传递一个 []*model.WorkStatusProgressRes,将数据写入到 excel 中
|
||||
func WriteExcel(tableName string, list []*model.WorkStatusProgressRes, aiResult map[string]int) (*excelize.File, error) {
|
||||
f, err := excelize.OpenFile("resource/template.xlsx")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建一个工作表
|
||||
sheetName := "Sheet1"
|
||||
|
||||
headerPrefixes := []string{"一、", "二、", "三、", "四、", "五、", "六、", "七、", "八、", "九、", "十、", "十一、", "十二、"}
|
||||
childPrefixes := []string{"1、", "2、", "3、", "4、", "5、", "6、", "7、", "8、", "9、", "10、", "11、", "12、"}
|
||||
|
||||
// 修改表头
|
||||
f.SetCellValue(sheetName, "A1", tableName)
|
||||
|
||||
// 遍历 list
|
||||
rowIndex := 3
|
||||
lo.ForEach(list, func(item *model.WorkStatusProgressRes, index int) {
|
||||
setExcelValues(f, sheetName, rowIndex, item, headerPrefixes[index], aiResult)
|
||||
rowIndex++
|
||||
|
||||
// 遍历子集
|
||||
lo.ForEach(item.Children, func(child model.WorkStatusProgressRes, childIndex int) {
|
||||
setExcelValues(f, sheetName, rowIndex, &child, childPrefixes[childIndex], aiResult)
|
||||
rowIndex++
|
||||
})
|
||||
})
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func setExcelValues(f *excelize.File, sheetName string, i int, item *model.WorkStatusProgressRes, prefix string, aiResult map[string]int) {
|
||||
// 单元格 A
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("A%d", i), prefix+item.Name) // 父名
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("B%d", i), item.Total) // 总量
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("C%d", i), item.Finished) // 完成
|
||||
|
||||
var percentComplete float64
|
||||
if item.Total != 0 {
|
||||
percentComplete = float64(item.Finished) / float64(item.Total) * 100
|
||||
} else {
|
||||
percentComplete = 0
|
||||
}
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("D%d", i), fmt.Sprintf("%.f%%", percentComplete)) // 总量完成百分比
|
||||
|
||||
if item.PlanProgress != nil {
|
||||
total := 0
|
||||
if item.PlanProgress.Total != nil {
|
||||
total = *item.PlanProgress.Total
|
||||
}
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("E%d", i), total) // 计划量
|
||||
|
||||
// AI 填报数量 F
|
||||
// 用 workID 作为 key,获取 aiResult 中的值,如果不存在则为 0
|
||||
aiResultValue := aiResult[item.WorkID]
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("F%d", i), aiResultValue)
|
||||
|
||||
finished := 0
|
||||
if item.PlanProgress.Finished != nil {
|
||||
finished = *item.PlanProgress.Finished
|
||||
}
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("G%d", i), finished) // 计划完成量
|
||||
|
||||
var plannedPercentComplete float64
|
||||
if item.PlanProgress != nil && item.PlanProgress.Finished != nil && item.PlanProgress.Total != nil && *item.PlanProgress.Total != 0 {
|
||||
plannedPercentComplete = float64(*item.PlanProgress.Finished) / float64(*item.PlanProgress.Total) * 100
|
||||
} else {
|
||||
plannedPercentComplete = 0
|
||||
}
|
||||
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("H%d", i), fmt.Sprintf("%.f%%", plannedPercentComplete)) // 计划完成百分比
|
||||
}
|
||||
}
|
||||
|
||||
// 根据方阵ID获取子项目名
|
||||
func GetSubProjectName(fangZhenID string) (string, string) {
|
||||
// select sb.project_name
|
||||
// from sub_project as sb
|
||||
// left join qianqi_fangzhen as qf on qf.project_id = sb.id
|
||||
// where qf.id = 1959;
|
||||
|
||||
result, err := dao.SubProject.Ctx(context.Background()).As("sb").
|
||||
Where("qf.id", fangZhenID).
|
||||
InnerJoin("qianqi_fangzhen as qf", "qf.project_id = sb.id").
|
||||
Fields("sb.project_name,qf.name").All()
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// 获取子项目名
|
||||
subProjectName := result[0].GMap().Get("project_name").(string)
|
||||
// 方阵名
|
||||
fangZhenName := result[0].GMap().Get("name").(string)
|
||||
|
||||
return subProjectName, fangZhenName
|
||||
}
|
||||
|
||||
// 根据子项目ID获取其主项目名和子项目名
|
||||
func GetNameById(subProjectID string) (string, string) {
|
||||
// select sp.project_name as projectName,sb.project_name as subProjectName
|
||||
// from sys_project as sp
|
||||
// inner join sub_project as sb on sb.project_id = sp.id
|
||||
// where sb.id = 23;
|
||||
|
||||
result, err := dao.SubProject.Ctx(context.Background()).As("sb").
|
||||
Where("sb.id", subProjectID).
|
||||
InnerJoin("sys_project as sp", "sb.project_id = sp.id").
|
||||
Fields("sp.project_name as projectName,sb.project_name as subProjectName").All()
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// 获取主项目名
|
||||
projectName := result[0].GMap().Get("projectName").(string)
|
||||
// 获取子项目名
|
||||
subProjectName := result[0].GMap().Get("subProjectName").(string)
|
||||
|
||||
return projectName, subProjectName
|
||||
}
|
||||
|
||||
func SetExcelValue(f *excelize.File, sheetName string, index int, item model.WorkStatusProgressRes, excelmap map[string]ExcelInfo, postionName string) {
|
||||
// 设计数量 F10
|
||||
total := item.Total
|
||||
// 本周完成量 G10
|
||||
finished := item.Finished
|
||||
|
||||
// 累计完成量和下周计划量
|
||||
accumulated, nextWeek := 0, 0
|
||||
|
||||
if data, exists := excelmap[item.WorkID]; exists {
|
||||
accumulated = data.Accumulated
|
||||
nextWeek = data.NextWeek
|
||||
}
|
||||
|
||||
// 累计完成百分比 单元格 I10
|
||||
percentComplete := 0.0
|
||||
if total != 0 {
|
||||
percentComplete = float64(accumulated) / float64(total) * 100
|
||||
}
|
||||
|
||||
// 名称
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "C", index), item.Name)
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "C", index), fmt.Sprintf("%s%d", "C", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 位置
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "D", index), postionName)
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "D", index), fmt.Sprintf("%s%d", "D", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 单位 样式
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "E", index), fmt.Sprintf("%s%d", "E", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 设计数量
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "F", index), total)
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "F", index), fmt.Sprintf("%s%d", "F", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 本周
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "G", index), finished)
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "G", index), fmt.Sprintf("%s%d", "G", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 累计完成量
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "H", index), accumulated)
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "H", index), fmt.Sprintf("%s%d", "H", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 累计完成百分比
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%.f%%", percentComplete))
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%s%d", "I", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 合并 jk 单元格
|
||||
_ = f.MergeCell(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index))
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(Style)))
|
||||
|
||||
// 水平居中 jk 单元格
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
Fill: excelize.Fill{},
|
||||
Font: nil,
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center", // 水平居中
|
||||
Indent: 0,
|
||||
JustifyLastLine: false,
|
||||
ReadingOrder: 0,
|
||||
RelativeIndent: 0,
|
||||
ShrinkToFit: false,
|
||||
TextRotation: 0,
|
||||
Vertical: "", // 垂直居中
|
||||
WrapText: false,
|
||||
},
|
||||
})))
|
||||
|
||||
// 下周计划量
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "J", index), nextWeek)
|
||||
|
||||
// L
|
||||
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "L", index), fmt.Sprintf("%s%d", "L", index), lo.Must(f.NewStyle(Style)))
|
||||
}
|
||||
|
||||
func GetWeekNumbers(endTime string) (int, error) {
|
||||
end, err := time.Parse(time.DateOnly, endTime)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("解析结束时间错误: %w", err)
|
||||
}
|
||||
|
||||
_, endWeek := end.ISOWeek()
|
||||
|
||||
return endWeek, nil
|
||||
}
|
||||
|
||||
func CalculateTotalProgress(list []*model.WorkStatusProgressRes) float64 {
|
||||
var totalFinished, total int
|
||||
|
||||
var calculate func(w model.WorkStatusProgressRes)
|
||||
calculate = func(w model.WorkStatusProgressRes) {
|
||||
totalFinished += w.Finished
|
||||
total += w.Total
|
||||
for _, child := range w.Children {
|
||||
calculate(child)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
calculate(*item)
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return (float64(totalFinished) / float64(total)) * 100
|
||||
}
|
302
third/excel/template.go
Normal file
302
third/excel/template.go
Normal file
@ -0,0 +1,302 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
GlobalStyle int // 全局样式
|
||||
GlobalStyleBold int // 基于GlobalStyle的 加粗
|
||||
GlobalStyleBoldSize int // 基于GlobalStyle的 加粗 加字体大小
|
||||
)
|
||||
|
||||
var Style = &excelize.Style{
|
||||
// 边框
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
// 居中
|
||||
Alignment: &excelize.Alignment{
|
||||
Vertical: "center", // 上下居中
|
||||
Horizontal: "center", // 左右居中
|
||||
WrapText: true, // 自动换行
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
excelName = "Book1.xlsx"
|
||||
sheetName = "Sheet1"
|
||||
)
|
||||
|
||||
func styleFunc(f *excelize.File) {
|
||||
baseStyle := excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
Alignment: &excelize.Alignment{
|
||||
Vertical: "center",
|
||||
Horizontal: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
}
|
||||
|
||||
font := excelize.Font{
|
||||
Color: "#262626",
|
||||
Bold: true,
|
||||
}
|
||||
|
||||
// 创建全局样式
|
||||
GlobalStyle, _ = f.NewStyle(&baseStyle)
|
||||
|
||||
// 创建加粗样式
|
||||
baseStyle.Font = &font
|
||||
GlobalStyleBold, _ = f.NewStyle(&baseStyle)
|
||||
|
||||
// 创建加粗加字体大小样式
|
||||
font.Size = 12
|
||||
baseStyle.Font = &font
|
||||
GlobalStyleBoldSize, _ = f.NewStyle(&baseStyle)
|
||||
}
|
||||
|
||||
// @Title cellMerge
|
||||
// @Description 合并单元格
|
||||
// @Author 铁憨憨[cory] 2024-10-18 14:20:40
|
||||
// @Param f
|
||||
// @Param qie 单元格切片
|
||||
// @Param qie 影响行(height高度相关)
|
||||
// @Param height 单元格高度(0隐藏当前行 -1取消自定义高度 其他单元格高度)
|
||||
// @Param style 样式
|
||||
// @Param str 单元格值
|
||||
func cellMerge(f *excelize.File, qie []string, height float64, style int, str string) {
|
||||
if len(qie) > 1 {
|
||||
f.MergeCell(sheetName, qie[0], qie[1]) // 合并单元格
|
||||
}
|
||||
f.SetCellStyle(sheetName, qie[0], qie[len(qie)-1], style) // 设置样式
|
||||
|
||||
if height != -1 {
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
rowStr := strings.Join(re.FindAllString(qie[0], -1), "")
|
||||
if rowNum, err := strconv.Atoi(rowStr); err == nil {
|
||||
f.SetRowHeight(sheetName, rowNum, height) // 设置行高
|
||||
}
|
||||
}
|
||||
|
||||
f.SetCellValue(sheetName, qie[0], str) // 设置单元格的值
|
||||
}
|
||||
|
||||
// 创建一个基础的 Excel 模板返回 *excelize.File
|
||||
func CreateExcelTemplate() (*excelize.File, error) {
|
||||
f := excelize.NewFile()
|
||||
defer f.Close()
|
||||
|
||||
// 初始化样式
|
||||
styleFunc(f)
|
||||
|
||||
// 初始化单元格布局
|
||||
businessFunc(f)
|
||||
|
||||
index, err := f.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.SetActiveSheet(index)
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func businessFunc(f *excelize.File) {
|
||||
// 一、
|
||||
one := []string{"A1", "L1"}
|
||||
cellMerge(f, one, 64, GlobalStyleBoldSize, "兴义市捧乍猪场坪农业光伏电站项目EPC总承包项目周报")
|
||||
// 二、
|
||||
two := []string{"A2", "B2"}
|
||||
cellMerge(f, two, 30, GlobalStyle, "项目名称:")
|
||||
two2 := []string{"C2", "G2"}
|
||||
cellMerge(f, two2, -1, GlobalStyle, "兴义市捧乍猪场坪农业光伏电站项目EPC总承包项目")
|
||||
two3 := []string{"H2"}
|
||||
cellMerge(f, two3, -1, GlobalStyleBold, "周报期数")
|
||||
two4 := []string{"I2"}
|
||||
cellMerge(f, two4, -1, GlobalStyle, "53")
|
||||
two5 := []string{"J2", "J3"}
|
||||
cellMerge(f, two5, -1, GlobalStyle, "日期:")
|
||||
two6 := []string{"K2", "L3"}
|
||||
cellMerge(f, two6, -1, GlobalStyle, "2024.10.05-2024.10.11")
|
||||
// 三、
|
||||
three := []string{"A3"}
|
||||
cellMerge(f, three, -1, GlobalStyle, "序号")
|
||||
three2 := []string{"B3", "I3"}
|
||||
cellMerge(f, three2, -1, GlobalStyle, "工程实施主要情况")
|
||||
// 四、
|
||||
four := []string{"A4"}
|
||||
cellMerge(f, four, -1, GlobalStyle, "1")
|
||||
four2 := []string{"B4"}
|
||||
cellMerge(f, four2, -1, GlobalStyle, "项目基本信息")
|
||||
four3 := []string{"C4"}
|
||||
cellMerge(f, four3, -1, GlobalStyle, "装机容量:")
|
||||
four4 := []string{"D4", "E4"}
|
||||
cellMerge(f, four4, -1, GlobalStyle, "开工日期:2022-08-15")
|
||||
four5 := []string{"F4", "H4"}
|
||||
cellMerge(f, four5, -1, GlobalStyle, "计划并网日期:")
|
||||
four6 := []string{"I4"}
|
||||
cellMerge(f, four6, -1, GlobalStyle, "总进度完成:")
|
||||
four7 := []string{"J4", "L4"}
|
||||
cellMerge(f, four7, -1, GlobalStyle, "96.5%")
|
||||
// 五、
|
||||
five := []string{"A5"}
|
||||
cellMerge(f, five, -1, GlobalStyle, "2")
|
||||
five2 := []string{"B5"}
|
||||
cellMerge(f, five2, -1, GlobalStyle, "本周完成主要形象进度")
|
||||
five3 := []string{"C5", "L5"}
|
||||
cellMerge(f, five3, -1, GlobalStyle, "")
|
||||
// 六、
|
||||
six := []string{"A6"}
|
||||
cellMerge(f, six, -1, GlobalStyle, "3")
|
||||
six2 := []string{"B6"}
|
||||
cellMerge(f, six2, -1, GlobalStyle, "下周计划主要形象进度")
|
||||
six3 := []string{"C6", "L6"}
|
||||
cellMerge(f, six3, -1, GlobalStyle, "")
|
||||
// 七、
|
||||
seven := []string{"A7"}
|
||||
cellMerge(f, seven, -1, GlobalStyle, "4")
|
||||
seven2 := []string{"B7"}
|
||||
cellMerge(f, seven2, -1, GlobalStyle, "质量(施工质量、设备材料到货验收)情况")
|
||||
seven3 := []string{"C7", "L7"}
|
||||
cellMerge(f, seven3, -1, GlobalStyle, "")
|
||||
// 八、
|
||||
eight := []string{"A8"}
|
||||
cellMerge(f, eight, -1, GlobalStyle, "5")
|
||||
eight2 := []string{"B8"}
|
||||
cellMerge(f, eight2, -1, GlobalStyle, "人员到位情况及施工器具")
|
||||
eight3 := []string{"C8", "L8"}
|
||||
cellMerge(f, eight3, -1, GlobalStyle, "")
|
||||
// 九、
|
||||
nine := []string{"A9", "A23"}
|
||||
cellMerge(f, nine, -1, GlobalStyle, "6")
|
||||
nine2 := []string{"B9", "B23"}
|
||||
cellMerge(f, nine2, -1, GlobalStyle, "")
|
||||
nine3 := []string{"C9"}
|
||||
cellMerge(f, nine3, -1, GlobalStyle, "名称")
|
||||
nine4 := []string{"D9"}
|
||||
cellMerge(f, nine4, -1, GlobalStyle, "位置")
|
||||
nine5 := []string{"E9"}
|
||||
cellMerge(f, nine5, -1, GlobalStyle, "单位")
|
||||
nine6 := []string{"F9"}
|
||||
cellMerge(f, nine6, -1, GlobalStyle, "设计数量")
|
||||
nine7 := []string{"G9"}
|
||||
cellMerge(f, nine7, -1, GlobalStyle, "本周完成情况")
|
||||
nine8 := []string{"H9"}
|
||||
cellMerge(f, nine8, -1, GlobalStyle, "累计完成量")
|
||||
nine9 := []string{"I9"}
|
||||
cellMerge(f, nine9, -1, GlobalStyle, "累计完成率")
|
||||
nine10 := []string{"J9", "K9"}
|
||||
cellMerge(f, nine10, -1, GlobalStyle, "下周计划完成")
|
||||
nine11 := []string{"L9"}
|
||||
cellMerge(f, nine11, -1, GlobalStyle, "备注")
|
||||
// 十
|
||||
ten := []string{"B10", "B23"}
|
||||
cellMerge(f, ten, -1, GlobalStyle, "施工进度(根据项目类型及进度情况自行分项,不用太细,能体现整体进度即可)")
|
||||
ten2 := []string{"C10"}
|
||||
cellMerge(f, ten2, -1, GlobalStyle, "基础")
|
||||
ten3 := []string{"D10"}
|
||||
cellMerge(f, ten3, -1, GlobalStyle, "光伏区")
|
||||
ten4 := []string{"E10"}
|
||||
cellMerge(f, ten4, -1, GlobalStyle, "个")
|
||||
ten5 := []string{"F10"}
|
||||
cellMerge(f, ten5, -1, GlobalStyle, "45450")
|
||||
ten6 := []string{"G10"}
|
||||
cellMerge(f, ten6, -1, GlobalStyle, "1415")
|
||||
ten7 := []string{"H10"}
|
||||
cellMerge(f, ten7, -1, GlobalStyle, "15445")
|
||||
ten8 := []string{"I10"}
|
||||
cellMerge(f, ten8, -1, GlobalStyle, "90")
|
||||
ten9 := []string{"J10", "K10"}
|
||||
cellMerge(f, ten9, -1, GlobalStyle, "545")
|
||||
ten10 := []string{"L10"}
|
||||
cellMerge(f, ten10, -1, GlobalStyle, "备注")
|
||||
// 二十四、
|
||||
twentyFour := []string{"A24", "A41"}
|
||||
cellMerge(f, twentyFour, -1, GlobalStyle, "7")
|
||||
twentyFour2 := []string{"B24", "B41"}
|
||||
cellMerge(f, twentyFour2, -1, GlobalStyle, "主要设备材料到货情况(列出主要材料即可)")
|
||||
twentyFour3 := []string{"C24"}
|
||||
cellMerge(f, twentyFour3, -1, GlobalStyle, "设备材料名称")
|
||||
twentyFour4 := []string{"D24"}
|
||||
cellMerge(f, twentyFour4, -1, GlobalStyle, "")
|
||||
twentyFour5 := []string{"E24"}
|
||||
cellMerge(f, twentyFour5, -1, GlobalStyle, "单位")
|
||||
twentyFour6 := []string{"F24"}
|
||||
cellMerge(f, twentyFour6, -1, GlobalStyle, "设计数量")
|
||||
twentyFour7 := []string{"G24"}
|
||||
cellMerge(f, twentyFour7, -1, GlobalStyle, "本周到货量")
|
||||
twentyFour8 := []string{"H24"}
|
||||
cellMerge(f, twentyFour8, -1, GlobalStyle, "累计到货量")
|
||||
twentyFour9 := []string{"I24"}
|
||||
cellMerge(f, twentyFour9, -1, GlobalStyle, "累计到货率")
|
||||
twentyFour10 := []string{"J24", "K24"}
|
||||
cellMerge(f, twentyFour10, -1, GlobalStyle, "计划到货日期")
|
||||
twentyFour11 := []string{"L24"}
|
||||
cellMerge(f, twentyFour11, -1, GlobalStyle, "备注")
|
||||
// 二十五
|
||||
twentyFive2 := []string{"C25"}
|
||||
cellMerge(f, twentyFive2, -1, GlobalStyle, "角钢")
|
||||
twentyFive3 := []string{"D25"}
|
||||
cellMerge(f, twentyFive3, -1, GlobalStyle, "")
|
||||
twentyFive4 := []string{"E25"}
|
||||
cellMerge(f, twentyFive4, -1, GlobalStyle, "")
|
||||
twentyFive5 := []string{"F25"}
|
||||
cellMerge(f, twentyFive5, -1, GlobalStyle, "")
|
||||
twentyFive6 := []string{"G25"}
|
||||
cellMerge(f, twentyFive6, -1, GlobalStyle, "")
|
||||
twentyFive7 := []string{"H25"}
|
||||
cellMerge(f, twentyFive7, -1, GlobalStyle, "")
|
||||
twentyFive8 := []string{"I25"}
|
||||
cellMerge(f, twentyFive8, -1, GlobalStyle, "90")
|
||||
twentyFive9 := []string{"J25", "K25"}
|
||||
cellMerge(f, twentyFive9, -1, GlobalStyle, "")
|
||||
twentyFive10 := []string{"L25"}
|
||||
cellMerge(f, twentyFive10, -1, GlobalStyle, "")
|
||||
// 四十二
|
||||
fortyTwo := []string{"A42"}
|
||||
cellMerge(f, fortyTwo, -1, GlobalStyle, "8")
|
||||
fortyTwo2 := []string{"B42"}
|
||||
cellMerge(f, fortyTwo2, -1, GlobalStyle, "存在问题及需要协调的事项")
|
||||
fortyTwo3 := []string{"C42", "L42"}
|
||||
cellMerge(f, fortyTwo3, -1, GlobalStyle, "无")
|
||||
// 四十三
|
||||
fortyThree := []string{"A43"}
|
||||
cellMerge(f, fortyThree, 90, GlobalStyle, "9")
|
||||
fortyThree2 := []string{"B43"}
|
||||
cellMerge(f, fortyThree2, -1, GlobalStyle, "照片")
|
||||
fortyThree3 := []string{"C43", "E43"}
|
||||
cellMerge(f, fortyThree3, -1, GlobalStyle, "")
|
||||
fortyThree4 := []string{"F43", "H43"}
|
||||
cellMerge(f, fortyThree4, -1, GlobalStyle, "")
|
||||
fortyThree5 := []string{"I43", "L43"}
|
||||
cellMerge(f, fortyThree5, -1, GlobalStyle, "")
|
||||
// 四十四
|
||||
fortyFour := []string{"A44"}
|
||||
cellMerge(f, fortyFour, -1, GlobalStyle, "10")
|
||||
fortyFour2 := []string{"B44"}
|
||||
cellMerge(f, fortyFour2, -1, GlobalStyle, "填报:")
|
||||
fortyFour3 := []string{"C44", "L44"}
|
||||
cellMerge(f, fortyFour3, -1, GlobalStyleBold, "审核:")
|
||||
|
||||
// 整体样式,不需要动
|
||||
f.SetColWidth(sheetName, "A", "A", 8)
|
||||
f.SetColWidth(sheetName, "B", "B", 11)
|
||||
f.SetColWidth(sheetName, "C", "C", 26)
|
||||
f.SetColWidth(sheetName, "D", "D", 26)
|
||||
f.SetColWidth(sheetName, "H", "H", 13)
|
||||
f.SetCellStyle(sheetName, "A1", "B44", GlobalStyleBold) // 注意:B44到时候需要取excel最大行数
|
||||
}
|
32
third/isc/api.go
Normal file
32
third/isc/api.go
Normal file
@ -0,0 +1,32 @@
|
||||
package isc
|
||||
|
||||
type camera struct {
|
||||
CameraIndexCode string `json:"cameraIndexCode"`
|
||||
CameraName string `json:"cameraName"`
|
||||
Marker_id string `json:"marker_id"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
var ca Camera
|
||||
var cameras []Camera
|
||||
|
||||
var DbCameraMap = make(map[string]camera)
|
||||
|
||||
func InitIscApi() {
|
||||
//getAllCameraFromDb()
|
||||
//getCameraFromIsc()
|
||||
//GetStatusList()
|
||||
//createTimer()
|
||||
|
||||
//GetCameraList(1, 20)
|
||||
}
|
||||
|
||||
type Ret struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func getCameraFromIsc() {
|
||||
GetCameraList(1, 1000)
|
||||
}
|
263
third/isc/isc_auth.go
Normal file
263
third/isc/isc_auth.go
Normal file
@ -0,0 +1,263 @@
|
||||
package isc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppKey = "22422818" //海康平台的key
|
||||
var AppSecret = "smyUkLf2Rd9VOkLV9Ndv"
|
||||
|
||||
//var AppKey = "22422818" //海康平台的key
|
||||
//var AppSecret = "smyUkLf2Rd9VOkLV9Ndv"
|
||||
|
||||
var Protocol = "https://"
|
||||
var HOST = "192.168.1.153"
|
||||
var PORT = "2443"
|
||||
var reqError = "2003"
|
||||
var reqErrorMsg = "请求失败"
|
||||
var Ok = "0"
|
||||
var Method = "POST"
|
||||
|
||||
func GetStatusList() {
|
||||
config := map[string]interface{}{}
|
||||
config["includeSubNode"] = 1
|
||||
config["pageNo"] = 1
|
||||
config["pageSize"] = 1000
|
||||
config["regionId"] = "root000000"
|
||||
//config["indexCodes"] = []string{"7cbd5a4c2dab46579b09c297cf649721", "7ec4f9689dc1466d813c801a5a0686b2"}
|
||||
t := CameraStatus{Code: reqError, Msg: reqErrorMsg}
|
||||
//content, _ := request("/api/nms/v1/online/camera/get", config)
|
||||
content, _ := request("/api/nms/v1/online/camera/get", config)
|
||||
fmt.Println("获取设备状态")
|
||||
_ = json.Unmarshal([]byte(content), &t)
|
||||
for _, v := range t.Data.List {
|
||||
if ca, ok := DbCameraMap[v.IndexCode]; ok {
|
||||
ca.Status = v.Online
|
||||
DbCameraMap[v.IndexCode] = ca
|
||||
}
|
||||
//fmt.Println("v.Cn", v.Cn)
|
||||
//fmt.Println("v.Online", v.Online)
|
||||
//fmt.Println("v.DeviceIndexCode", v.DeviceIndexCode)
|
||||
//fmt.Println("v.IndexCode", v.IndexCode)
|
||||
//fmt.Println("v.CollectTime", v.CollectTime)
|
||||
//fmt.Println("************************")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*抓图接口*/
|
||||
func ManualCapture(cameraIndexCode string) CameraCap {
|
||||
config := map[string]interface{}{}
|
||||
config["cameraIndexCode"] = cameraIndexCode
|
||||
t := CameraCap{Code: reqError, Msg: reqErrorMsg}
|
||||
content, _ := request("/api/video/v1/manualCapture", config)
|
||||
_ = json.Unmarshal([]byte(content), &t)
|
||||
return t
|
||||
}
|
||||
|
||||
/*
|
||||
云台控制
|
||||
action number 0-开始 ,1-停止
|
||||
注:GOTO_PRESET命令下填任意值均可转到预置点,建议填0即可
|
||||
command string 不区分大小写
|
||||
说明:
|
||||
LEFT 左转
|
||||
RIGHT右转
|
||||
UP 上转
|
||||
DOWN 下转
|
||||
ZOOM_IN 焦距变大
|
||||
ZOOM_OUT 焦距变小
|
||||
LEFT_UP 左上
|
||||
LEFT_DOWN 左下
|
||||
RIGHT_UP 右上
|
||||
RIGHT_DOWN 右下
|
||||
FOCUS_NEAR 焦点前移
|
||||
FOCUS_FAR 焦点后移
|
||||
IRIS_ENLARGE 光圈扩大
|
||||
IRIS_REDUCE 光圈缩小
|
||||
WIPER_SWITCH 接通雨刷开关
|
||||
START_RECORD_TRACK 开始记录轨迹
|
||||
STOP_RECORD_TRACK 停止记录轨迹
|
||||
START_TRACK 开始轨迹
|
||||
STOP_TRACK 停止轨迹;
|
||||
以下命令presetIndex不可为空:
|
||||
GOTO_PRESET到预置点
|
||||
*/
|
||||
func CtrlPtz(cameraIndexCode string, action int, command string) Usual {
|
||||
config := map[string]interface{}{}
|
||||
config["cameraIndexCode"] = cameraIndexCode
|
||||
config["action"] = action
|
||||
config["command"] = command
|
||||
config["speed"] = 50 //默认速度为50
|
||||
t := Usual{Code: reqError, Msg: reqErrorMsg}
|
||||
content, _ := request("/api/video/v1/ptzs/controlling", config)
|
||||
_ = json.Unmarshal([]byte(content), &t)
|
||||
return t
|
||||
}
|
||||
|
||||
/*获取对讲地址*/
|
||||
func GettalkURLs(cameraIndexCode string) CameraTalk {
|
||||
config := map[string]interface{}{}
|
||||
config["cameraIndexCode"] = cameraIndexCode
|
||||
config["protocol"] = "ws"
|
||||
t := CameraTalk{Code: reqError, Msg: reqErrorMsg}
|
||||
content, _ := request("/api/video/v1/cameras/talkURLs", config)
|
||||
_ = json.Unmarshal([]byte(content), &t)
|
||||
return t
|
||||
}
|
||||
|
||||
/*获取预览地址 ws协议*/
|
||||
func GetpreviewURLs(cameraIndexCode string) CameraPreview {
|
||||
config := map[string]interface{}{}
|
||||
config["cameraIndexCode"] = cameraIndexCode
|
||||
config["protocol"] = "ws"
|
||||
t := CameraPreview{Code: reqError, Msg: reqErrorMsg}
|
||||
content, _ := request("/api/video/v1/cameras/previewURLs", config)
|
||||
//fmt.Println(api)
|
||||
//content, _ := request(api, config)
|
||||
fmt.Println(content)
|
||||
_ = json.Unmarshal([]byte(content), &t)
|
||||
return t
|
||||
}
|
||||
|
||||
/*获取监控点列表*/
|
||||
func GetCameraList(pageNo, pageSize int) {
|
||||
fmt.Println("开始获取第一页数据")
|
||||
config := map[string]interface{}{}
|
||||
config["pageNo"] = pageNo
|
||||
config["pageSize"] = pageSize
|
||||
t := CameraData{Code: reqError, Msg: reqErrorMsg}
|
||||
content, err := request("/api/resource/v1/cameras", config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("数据完成")
|
||||
fmt.Println(content)
|
||||
err = json.Unmarshal([]byte(content), &t)
|
||||
if err != nil {
|
||||
fmt.Println("数据反序列化失败")
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if t.Code == Ok {
|
||||
fmt.Println("监控点总数", t.Data.Total)
|
||||
fmt.Println("页码", t.Data.PageNo)
|
||||
fmt.Println("当前页数量", t.Data.PageSize)
|
||||
//fmt.Println(t.Data.List)
|
||||
for _, v := range t.Data.List {
|
||||
fmt.Println(v.CameraIndexCode, v.Name, v.Status)
|
||||
//if v.Status == 1 {
|
||||
// ca = v
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("数据获取失败")
|
||||
fmt.Println(t)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 请求
|
||||
func request(url string, body map[string]interface{}) (string, error) {
|
||||
configData, _ := json.Marshal(body)
|
||||
param := bytes.NewBuffer(configData)
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
url = "/artemis" + url
|
||||
headers, _ := getSignature(url, Method)
|
||||
url = Protocol + HOST + ":" + PORT + url
|
||||
fmt.Println(url)
|
||||
|
||||
// 2. 创建请求实例
|
||||
req, err := http.NewRequest(Method, url, param)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//header
|
||||
|
||||
for i := 0; i < len(headers); i++ {
|
||||
//fmt.Println(headers[i].Key, headers[i].Val)
|
||||
req.Header.Add(headers[i].Key, headers[i].Val)
|
||||
}
|
||||
//发送请求
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
content, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
func getSignature(url, method string) ([]Header, string) {
|
||||
var now = time.Now()
|
||||
var headers = []Header{
|
||||
{Key: "Accept", Val: "*/*"},
|
||||
{Key: "Content-Type", Val: "application/json"},
|
||||
{Key: "Date", Val: now.Format("2006-01-02 15:04:05")},
|
||||
{Key: "x-ca-key", Val: AppKey, Use: true},
|
||||
{Key: "x-ca-nonce", Val: RandAllString(32), Use: true},
|
||||
{Key: "x-ca-timestamp", Val: strconv.Itoa(int(now.UnixNano() / 1e6)), Use: true},
|
||||
}
|
||||
var arr = append(headers, Header{Key: "url", Val: url})
|
||||
var arr2 = append([]Header{{Val: method, Key: "method"}}, arr...)
|
||||
var str []string
|
||||
for i := 0; i < len(arr2); i++ {
|
||||
if arr2[i].Use {
|
||||
str = append(str, arr2[i].Key+":"+arr2[i].Val)
|
||||
} else {
|
||||
str = append(str, arr2[i].Val)
|
||||
}
|
||||
}
|
||||
sign := ComputeHmac256(strings.Join(str, "\n"), AppSecret)
|
||||
headers = append(headers, Header{Key: "x-ca-signature-headers", Val: "x-ca-key,x-ca-nonce,x-ca-timestamp"}, Header{Key: "x-ca-signature", Val: sign})
|
||||
return headers, sign
|
||||
}
|
||||
|
||||
func ComputeHmac256(message string, secret string) string {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(message))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Key string `json:"key"`
|
||||
Val string `json:"val"`
|
||||
Use bool `json:"use"`
|
||||
}
|
||||
|
||||
/*
|
||||
RandAllString 生成随机字符串([a~zA~Z0~9])
|
||||
|
||||
lenNum 长度
|
||||
*/
|
||||
func RandAllString(lenNum int) string {
|
||||
var CHARS = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
|
||||
|
||||
str := strings.Builder{}
|
||||
length := len(CHARS)
|
||||
for i := 0; i < lenNum; i++ {
|
||||
l := CHARS[rand.Intn(length)]
|
||||
str.WriteString(l)
|
||||
}
|
||||
return str.String()
|
||||
}
|
92
third/isc/struct.go
Normal file
92
third/isc/struct.go
Normal file
@ -0,0 +1,92 @@
|
||||
package isc
|
||||
|
||||
// 通用结构体
|
||||
type Usual struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
type Camera struct {
|
||||
CameraIndexCode string `json:"cameraIndexCode"` //监控点编号
|
||||
Name string `json:"name"` //监控点名称
|
||||
CameraType int `json:"cameraType"` //监控点类型
|
||||
CameraTypeName string `json:"cameraTypeName"` //监控点类型名称
|
||||
Longitude string `json:"longitude"` //监控点类型名称
|
||||
Latitude string `json:"latitude"` //监控点类型名称
|
||||
Altitude string `json:"altitude"` //监控点类型名称
|
||||
ChannelNo string `json:"channelNo"` //监控点类型名称
|
||||
Status int `json:"status"` //监控点在线状态 在线状态(0-未知,1-在线,2-离线),扩展字段,暂不使用
|
||||
}
|
||||
|
||||
// 监控点列表信息结构
|
||||
type CameraData struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Total int `json:"total"`
|
||||
PageNo int `json:"pageNo"`
|
||||
PageSize int `json:"pageSize"`
|
||||
List []Camera `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// 监控点的预览接口返回值结构
|
||||
type CameraPreview struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
//监控点的对讲接口返回值结构
|
||||
|
||||
type CameraTalk struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
//监控点的抓图接口返回值结构
|
||||
|
||||
type CameraCap struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
PicUrl string `json:"picUrl"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// 监控点的在线状态接口返回值结构
|
||||
type statusList struct {
|
||||
Online int `json:"online"`
|
||||
IndexCode string `json:"indexCode"`
|
||||
DeviceIndexCode string `json:"deviceIndexCode"`
|
||||
Cn string `json:"cn"`
|
||||
CollectTime string `json:"collectTime"`
|
||||
//"deviceType": "HIK%2FDS-9116HW-ST%2F-AF-DVR",
|
||||
//"deviceIndexCode": null,
|
||||
//"regionIndexCode": "ce91c758-5af4-4539-845a",
|
||||
//"collectTime": "2018-12-28T10:21:40.000+08:00",
|
||||
//"regionName": "NMS自动化",
|
||||
//"indexCode": "82896441ced946d5a51c6d6ca8e65851",
|
||||
//"cn": "Onvif-IPC(10.67.172.13 )",
|
||||
//"treatyType": "1",
|
||||
//"manufacturer": "hikvision",
|
||||
//"ip": null,
|
||||
//"port": null,
|
||||
//"online": 1
|
||||
}
|
||||
type CameraStatus struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
PageNo int `json:"pageNo"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
Total int `json:"total"`
|
||||
List []statusList `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
103
third/jiguang/config.go
Normal file
103
third/jiguang/config.go
Normal file
@ -0,0 +1,103 @@
|
||||
package jiguang
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var appKey = "9bbb84675085d124417ceb27"
|
||||
var masterSecret = "1a7dff8f3ce58df231481fce"
|
||||
|
||||
// @Title Notification 2024/7/25 15:45:00
|
||||
// @Description 通知
|
||||
// @Auth Cory
|
||||
type Notification struct {
|
||||
Platform interface{} `json:"platform" dc:"推送平台设置"`
|
||||
Audience interface{} `json:"audience" dc:"推送设备指定"`
|
||||
Notification struct {
|
||||
Android struct {
|
||||
Title string `json:"title" dc:"通知标题"`
|
||||
Alert string `json:"alert" dc:"通知内容"`
|
||||
} `json:"android" dc:"Android 平台上的通知"`
|
||||
} `json:"notification" dc:"通知内容体,是被推送到客户端的内容。"`
|
||||
}
|
||||
|
||||
// @Title SendAppClockingNotice 2024/7/25 15:35:00
|
||||
// @Description 发送APP的打卡通知信息 (需注意:registration_id一次最多只能发送1000)
|
||||
// @Auth Cory
|
||||
// @param 输入参数名 ---> "参数解释"
|
||||
// @Return error ---> "错误信息"
|
||||
func SendAppClockingNotice(ctx context.Context, deviceIds []string, dataStr string, flagStr string) {
|
||||
//1、发送位置及authorization
|
||||
url := "https://api.jpush.cn/v3/push"
|
||||
authorization := base64.StdEncoding.EncodeToString([]byte(appKey + ":" + masterSecret))
|
||||
|
||||
//2、创建 Resty 客户端对象
|
||||
client := resty.New()
|
||||
|
||||
//3、POST 请求示例
|
||||
splitIds := splitDeviceIds(deviceIds)
|
||||
for _, ids := range splitIds {
|
||||
//reqBody := map[string]interface{}{
|
||||
// "platform": []string{"android"},
|
||||
// "audience": map[string]interface{}{"registration_id": []string{"", ""}},
|
||||
// "notification": Notification{Android: AndroidEntity(struct {
|
||||
// Title string
|
||||
// Alert string
|
||||
// }{Title: "上下班打卡提醒", Alert: "即将进入打卡时间,请立即前往【定位考勤】页面进行打卡(如若已打卡或休息请忽略)"})},
|
||||
//}
|
||||
notification := Notification{
|
||||
Platform: []string{"android"},
|
||||
Audience: map[string]interface{}{"registration_id": ids},
|
||||
Notification: struct {
|
||||
Android struct {
|
||||
Title string `json:"title" dc:"通知标题"`
|
||||
Alert string `json:"alert" dc:"通知内容"`
|
||||
} `json:"android" dc:"Android 平台上的通知"`
|
||||
}(struct {
|
||||
Android struct {
|
||||
Title string
|
||||
Alert string
|
||||
}
|
||||
}{
|
||||
Android: struct {
|
||||
Title string
|
||||
Alert string
|
||||
}{
|
||||
Title: "中煤" + dataStr + flagStr + "打卡提醒",
|
||||
Alert: "即将进入打卡时间,请立即前往【定位考勤】页面进行打卡(如若已打卡或休息请忽略)",
|
||||
},
|
||||
}),
|
||||
}
|
||||
//4、发送post请求
|
||||
_, err := client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Authorization", "Basic "+authorization).
|
||||
SetBody(notification).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
fmt.Println("发送 POST 请求失败:", err)
|
||||
g.Log().Error(gctx.New(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Title splitDeviceIds 2024/7/26 15:56:00
|
||||
// @Description 数据切分(900一条)
|
||||
// @Auth Cory
|
||||
func splitDeviceIds(deviceIds []string) [][]string {
|
||||
var result [][]string
|
||||
for i := 0; i < len(deviceIds); i += 900 {
|
||||
end := i + 900
|
||||
if end > len(deviceIds) {
|
||||
end = len(deviceIds)
|
||||
}
|
||||
result = append(result, deviceIds[i:end])
|
||||
}
|
||||
return result
|
||||
}
|
3
third/livegbs/livegbs.go
Normal file
3
third/livegbs/livegbs.go
Normal file
@ -0,0 +1,3 @@
|
||||
package livegbs
|
||||
|
||||
var Token = ""
|
112
third/officeWeb365/office.go
Normal file
112
third/officeWeb365/office.go
Normal file
@ -0,0 +1,112 @@
|
||||
// @Author cory 2025/4/24 15:52:00
|
||||
package officeWeb365
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
|
||||
"golang.org/x/net/context"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SendFile199(ctx context.Context, relativePath string, typeStr string) (err error, urlPath string) {
|
||||
//1、根据相对路径获取到文件信息
|
||||
filePath := coryCommon.FileToFunc(relativePath, 2)
|
||||
|
||||
fieldName := "file" // 表单字段名
|
||||
|
||||
// 获取文件名
|
||||
fileName := filepath.Base(filePath) // 自动获取 "222.xlsx"
|
||||
|
||||
// 打开文件
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 创建 multipart body
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
// 创建文件字段(自动带文件名)
|
||||
part, err := writer.CreateFormFile(fieldName, fileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 将文件内容复制进去
|
||||
_, err = io.Copy(part, file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 关闭 multipart writer
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
url := ""
|
||||
sip := "http://192.168.1.177:8188"
|
||||
switch typeStr {
|
||||
case "1":
|
||||
url = sip + "/api/doc/view/uploadfile"
|
||||
case "2":
|
||||
url = sip + "/OnlineEditing/GetPreviewUrl"
|
||||
case "3":
|
||||
url = sip + "/api/ppt/view/uploadfile"
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 设置 multipart Content-Type
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
// 发出请求
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("请求错误!"), ""
|
||||
}
|
||||
|
||||
// 解析响应 JSON
|
||||
var responseData ResponseData
|
||||
err = json.Unmarshal(respBody, &responseData)
|
||||
if err != nil {
|
||||
return errors.New(string(respBody)), ""
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
if responseData.Code == 200 {
|
||||
return nil, strings.Replace(responseData.Data.ViewURL, sip, "", -1)
|
||||
} else {
|
||||
return errors.New("请求错误!"), ""
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseData 定义响应数据的结构体
|
||||
type ResponseData struct {
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
RequestID string `json:"request_id"`
|
||||
ViewURL string `json:"view_url"`
|
||||
} `json:"data"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
1
third/plane/dj/constant.go
Normal file
1
third/plane/dj/constant.go
Normal file
@ -0,0 +1 @@
|
||||
package dj
|
1217
third/plane/dj/dj.go
Normal file
1217
third/plane/dj/dj.go
Normal file
File diff suppressed because it is too large
Load Diff
203
third/plane/dj/errorcode.go
Normal file
203
third/plane/dj/errorcode.go
Normal file
@ -0,0 +1,203 @@
|
||||
package dj
|
||||
|
||||
var errorMap map[int]Error
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func InitError() {
|
||||
errorMap = make(map[int]Error)
|
||||
errors := []Error{
|
||||
{0, "成功"},
|
||||
{314000, "设备当前无法支持该操作,建议检查设备当前工作状态"},
|
||||
{314001, "飞行任务下发失败,请稍后重试"},
|
||||
{314002, "飞行任务下发失败,请稍后重试"},
|
||||
{314003, "航线文件格式不兼容,请检查航线文件是否正确"},
|
||||
{314004, "飞行任务下发失败,请稍后重试或重启机场后重试"},
|
||||
{314005, "飞行任务下发失败,请稍后重试或重启机场后重试"},
|
||||
{314006, "飞行器初始化失败,请重启机场后重试"},
|
||||
{314007, "机场传输航线至飞行器失败,请重启机场后重试"},
|
||||
{314008, "飞行器起飞前准备超时,请重启机场后重试"},
|
||||
{314009, "飞行器初始化失败,请重启机场后重试"},
|
||||
{314010, "航线执行失败,请重启机场后重试"},
|
||||
{314011, "机场系统异常,无法获取飞行任务执行结果"},
|
||||
{314012, "飞行器起飞前准备失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{314013, "飞行任务下发失败,机场无法获取到本次飞行任务的航线,无法执行飞行任务,请稍后重试"},
|
||||
{314014, "机场系统异常,飞行任务执行失败,请稍后重试"},
|
||||
{314015, "机场传输精准复拍航线至飞行器失败,无法执行飞行任务,请稍后重试或重启机场后重试"},
|
||||
{314016, "航线文件解析失败,无法执行飞行任务,请检查航线文件"},
|
||||
{314017, "机场系统异常,飞行任务执行失败,请稍后重试"},
|
||||
{314018, "飞行器 RTK 定位异常,无法执行飞行任务,请稍后重试或重启机场后重试"},
|
||||
{314019, "飞行器 RTK 收敛失败,无法执行飞行任务,请稍后重试或重启机场后重试"},
|
||||
{314020, "飞行器不在停机坪正中间或飞行器朝向不正确,无法执行飞行任务,请检查飞行器位置和朝向"},
|
||||
{314021, "飞行器 RTK 定位异常,无法执行飞行任务,请稍后重试或重启机场后重试"},
|
||||
{316001, "飞行器参数配置失败,请重启机场后重试"},
|
||||
{316002, "飞行器参数配置失败,请重启机场后重试"},
|
||||
{316003, "飞行器参数配置失败,请重启机场后重试"},
|
||||
{316004, "飞行器参数配置失败,请重启机场后重试"},
|
||||
{316005, "飞行器 RTK 收敛失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{316006, "飞行器降落前机场未开启舱盖或展开推杆,飞行器无法降落至机场,请尽快至机场部署现场检查飞行器状况"},
|
||||
{316007, "飞行器初始化失败,请重启机场后重试"},
|
||||
{316008, "机场获取飞行器控制权失败,无法执行飞行任务,请确认遥控器未锁定控制权"},
|
||||
{316009, "飞行器电量低于30%,无法执行飞行任务,请充电后重试(建议电量≥50%)"},
|
||||
{316010, "机场未检测到飞行器,无法执行飞行任务,请检查舱内是否有飞行器,机场与飞行器是否已对频,或重启机场后重试"},
|
||||
{316011, "飞行器降落位置偏移过大,请检查飞行器是否需要现场摆正"},
|
||||
{316012, "飞行器起飞前准备失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{316013, "飞行器起飞前准备失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{316014, "飞行器起飞前准备失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{316015, "飞行器 RTK 收敛位置距离机场过远,无法执行飞行任务,请重启机场后重试"},
|
||||
{316016, "飞行器降落至机场超时,可能是机场与飞行器断连导致,请通过直播查看飞行器是否降落至舱内"},
|
||||
{316017, "获取飞行器媒体数量超时,可能是机场与飞行器断连导致,请通过直播查看飞行器是否降落至舱内"},
|
||||
{316018, "飞行任务执行超时,可能是机场与飞行器断连导致,请通过直播查看飞行器是否降落至舱内"},
|
||||
{316019, "服务器内部错误,无法执行飞行任务,请稍后重试"},
|
||||
{316020, "飞行器使用的 RTK 信号源错误,请稍后重试"},
|
||||
{316021, "飞行器 RTK 信号源检查超时,请稍后重试"},
|
||||
{316022, "飞行器无法执行返航指令,请检查飞行器是否已开机,机场与飞行器是否已断连,请确认无以上问题后重试"},
|
||||
{316023, "飞行器无法执行返航指令,飞行器已被 B 控接管,请在 B 控操控飞行器,或关闭 B 控后重试"},
|
||||
{316024, "飞行器执行返航指令失败,请检查飞行器是否已起飞,确认飞行器已起飞后请重试"},
|
||||
{316025, "飞行器参数配置失败,请稍后重试或重启机场后重试"},
|
||||
{316026, "机场急停按钮被按下,无法执行飞行任务,请释放急停按钮后重试"},
|
||||
{316027, "飞行器参数配置超时,请稍后重试或重启机场后重试"},
|
||||
{316029, "机场急停按钮被按下,飞行器将飞往备降点降落,请立即检查飞行器是否已安全降落并将飞行器放回至机场"},
|
||||
{317001, "获取飞行器媒体文件数量失败,请重启机场后重试"},
|
||||
{317002, "飞行器存储格式化失败,飞行器未开机、未连接或未检测到相机,请确认无以上问题后重试,或重启飞行器后重试"},
|
||||
{317003, "飞行器存储格式化失败,请重启飞行器后重试"},
|
||||
{317004, "机场媒体文件格式化失败,请稍后重试或重启机场后重试"},
|
||||
{317005, "飞行器结束录像失败,本次飞行任务的媒体文件可能无法上传"},
|
||||
{319001, "机场作业中或(设备异常反馈)上传日志中,无法执行飞行任务,请等待当前飞行任务或操作执行完成后重试"},
|
||||
{319002, "机场系统运行异常,请重启机场后重试"},
|
||||
{319003, "机场系统运行异常,请重新下发任务"},
|
||||
{319004, "飞行任务执行超时,已自动终止本次飞行任务"},
|
||||
{319005, "云端与机场通信异常,无法执行飞行任务"},
|
||||
{319006, "取消飞行任务失败,飞行任务已经在执行中"},
|
||||
{319007, "修改飞行任务失败,飞行任务已经在执行中"},
|
||||
{319008, "机场时间与云端时间不同步,机场无法执行飞行任务"},
|
||||
{319009, "飞行任务下发失败,请稍后重试或重启机场后重试"},
|
||||
{319010, "机场固件版本过低,无法执行飞行任务,请升级机场固件为最新版本后重试"},
|
||||
{319015, "机场正在初始化中,无法执行飞行任务,请等待机场初始化完成后重试"},
|
||||
{319016, "机场正在执行其他飞行任务,无法执行本次飞行任务"},
|
||||
{319017, "机场正在处理上次飞行任务媒体文件,无法执行本次飞行任务,请稍后重试"},
|
||||
{319018, "机场正在自动导出日志中(设备异常反馈),无法执行飞行任务,请稍后重试"},
|
||||
{319019, "机场正在拉取日志中(设备异常反馈),无法执行飞行任务,请稍后重试"},
|
||||
{319025, "机场未准备完成,无法执行云端下发的飞行任务,请稍后重试"},
|
||||
{319026, "飞行器电池电量低于用户设置的任务开始执行的电量,请等待充电完成后再执行飞行任务"},
|
||||
{319027, "机场或飞行器剩余存储容量过低,无法执行飞行任务,请等待媒体文件上传,机场和飞行器存储容量释放后再执行飞行任务"},
|
||||
{319999, "机场系统运行异常,请重启机场后重试"},
|
||||
{321000, "航线执行异常,请稍后重试或重启机场后重试"},
|
||||
{321004, "航线文件解析失败,无法执行飞行任务,请检查航线文件"},
|
||||
{321005, "航线缺少断点信息,机场无法执行飞行任务"},
|
||||
{321257, "飞行任务已在执行中,请勿重复执行"},
|
||||
{321258, "飞行任务无法终止,请检查飞行器状态"},
|
||||
{321259, "飞行任务未开始执行,无法终止飞行任务"},
|
||||
{321260, "飞行任务未开始执行,无法中断飞行任务"},
|
||||
{321513, "航线规划高度已超过飞行器限高,机场无法执行飞行任务"},
|
||||
{321514, "任务失败,起点或终点位于限远区域的缓冲区内或超过了限远距离"},
|
||||
{321515, "航线穿过限飞区,机场无法执行飞行任务"},
|
||||
{321517, "飞行器触发避障,飞行任务执行被终止"},
|
||||
{321519, "飞行器接近限飞区或限远距离自动返航,无法完成航线飞行"},
|
||||
{321523, "飞行器起桨失败,请稍后重试,如果仍报错请联系大疆售后。"},
|
||||
{321524, "飞行器起飞前准备失败,可能是飞行器无发定位或档位错误导致,请检查飞行器状态"},
|
||||
{321769, "飞行器卫星定位信号差,无法执行飞行任务,请重启机场后重试"},
|
||||
{321770, "飞行器挡位错误,无法执行飞行任务,请重启机场后重试"},
|
||||
{321771, "飞行器返航点未设置,无法执行飞行任务,请重启机场后重试"},
|
||||
{321772, "飞行器电量低于30%,无法执行飞行任务,请充电后重试(建议电量≥50%)"},
|
||||
{321773, "飞行器执行飞行任务过程中低电量返航,无法完成航线飞行"},
|
||||
{321775, "飞行器航线飞行过程中失联,无法完成航线飞行"},
|
||||
{321776, "飞行器 RTK 收敛失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{321777, "飞行器未悬停,无法开始执行飞行任务"},
|
||||
{321778, "用户使用 B 控操控飞行器起桨,机场无法执行飞行任务"},
|
||||
{322282, "机场执行飞行任务过程中被中断,飞行器被云端用户或B控接管"},
|
||||
{322283, "机场执行飞行任务过程中被用户触发返航,无法完成航线飞行"},
|
||||
{322539, "航线的断点信息错误,机场无法执行飞行任务"},
|
||||
{324012, "日志压缩过程超时,所选日志过多,请减少选择的日志后重试"},
|
||||
{324013, "设备日志列表获取失败,请稍后重试"},
|
||||
{324014, "设备日志列表为空,请刷新页面或重启机场后重试"},
|
||||
{324015, "飞行器已关机或未连接,无法获取日志列表,请确认飞行器在舱内,通过远程调试将飞行器开机后重试"},
|
||||
{324016, "机场存储空间不足,日志压缩失败,请清理机场存储空间或稍后重试"},
|
||||
{324017, "日志压缩失败,无法获取所选飞行器日志,请刷新页面或重启机场后重试"},
|
||||
{324018, "日志文件拉取失败,导致本次设备异常反馈上传失败,请稍后重试或重启机场后重试"},
|
||||
{324019, "因机场网络异常,日志上传失败,请稍后重试。如果连续多次出现该问题,请联系代理商或大疆售后进行网络排障"},
|
||||
{325001, "云端下发给机场的命令不符合格式要求,机场无法执行"},
|
||||
{327500, "飞行器镜头除雾失败,请稍后重试"},
|
||||
{386535, "航线执行异常,请稍后重试或重启机场后重试"},
|
||||
{513001, "直播失败,飞行器不存在或飞行器类型错误"},
|
||||
{513002, "直播失败,相机不存在或相机类型错误"},
|
||||
{513003, "相机已经在直播中,请勿重复开启直播"},
|
||||
{513005, "直播失败,直播参数清晰度设置错误"},
|
||||
{513005, "直播失败,直播参数清晰度设置错误"},
|
||||
{513006, "操作失败,相机未开启直播"},
|
||||
{513008, "直播失败,设备端图传数据异常"},
|
||||
{513010, "直播失败,设备无法联网"},
|
||||
{513011, "操作失败,设备未开启直播"},
|
||||
{513012, "操作失败,设备已在直播中,不支持切换镜头"},
|
||||
{513013, "直播失败,直播使用的视频传输协议不支持"},
|
||||
{513014, "直播失败,直播参数错误或者不完整"},
|
||||
{513015, "直播异常,网络卡顿"},
|
||||
{513016, "直播异常,视频解码失败"},
|
||||
{513099, "直播失败,请稍后重试"},
|
||||
{514100, "机场运行异常,请重启机场后重试"},
|
||||
{514101, "推杆闭合失败,请检查停机坪上是否存在异物,飞行器方向是否放反,或重启机场后重试"},
|
||||
{514102, "推杆展开失败,请检查停机坪上是否存在异物,或重启机场后重试"},
|
||||
{514103, "飞行器电量低于30%,无法执行飞行任务,请充电后重试(建议电量≥50%)"},
|
||||
{514104, "飞行器电池开始充电失败,请重启机场后重试"},
|
||||
{514105, "飞行器电池停止充电失败,请重启机场后重试"},
|
||||
{514106, "飞行器电源控制异常,请重启机场后重试"},
|
||||
{514107, "舱盖开启失败,请检查舱盖周围是否存在异物,或重启机场后重试"},
|
||||
{514108, "舱盖关闭失败,请检查舱盖周围是否存在异物,或重启机场后重试"},
|
||||
{514109, "飞行器开机失败,请重启机场后重试"},
|
||||
{514110, "飞行器关机失败,请重启机场后重试"},
|
||||
{514111, "飞行器慢转收桨控制异常,请重启机场后重试"},
|
||||
{514112, "飞行器慢转收桨控制异常,请重启机场后重试"},
|
||||
{514113, "机场推杆与飞行器无法连接,请检查飞行器是否在舱内,推杆闭合时是否被卡住,充电连接器是否脏污或损坏"},
|
||||
{514114, "获取飞行器电源状态失败,请重启机场后重试"},
|
||||
{514116, "无法执行当前操作,机场正在执行其他控制指令,请稍后重试"},
|
||||
{514117, "舱盖开启或关闭未到位,请重启机场后重试"},
|
||||
{514118, "推杆展开或闭合未到位,请重启机场后重试"},
|
||||
{514120, "机场与飞行器断连,请重启机场后重试或重新对频"},
|
||||
{514121, "机场急停按钮被按下,请释放急停按钮"},
|
||||
{514122, "获取飞行器充电状态失败,请重启机场后重试"},
|
||||
{514123, "飞行器电池电量过低无法开机"},
|
||||
{514124, "获取飞行器电池信息失败,无法执行飞行任务,请重启机场后重试"},
|
||||
{514125, "飞行器电池电量已接近满电状态,无法开始充电,请使用至95%以下再进行充电"},
|
||||
{514134, "雨量过大,机场无法执行飞行任务,请稍后重试"},
|
||||
{514135, "风速过大≥12m/s,机场无法执行飞行任务,请稍后重试"},
|
||||
{514136, "机场供电断开,机场无法执行飞行任务,请恢复机场供电后重试"},
|
||||
{514137, "环境温度过低<-20℃,机场无法执行飞行任务,请稍后重试"},
|
||||
{514138, "飞行器电池正在保养中,机场无法执行飞行任务,请等待保养结束后重试"},
|
||||
{514139, "飞行器电池无法执行保养指令,飞行器电池无需保养"},
|
||||
{514140, "飞行器电池无法执行保养指令,飞行器电池无需保养"},
|
||||
{514141, "机场系统运行异常,请重启机场后重试"},
|
||||
{514142, "飞行器起飞前,机场推杆与飞行器无法连接,请检查飞行器是否在舱内,推杆闭合时是否被卡住,充电连接器是否脏污或损坏"},
|
||||
{514143, "推杆未闭合或闭合不到位,请稍后重试或重启机场后重试"},
|
||||
{514144, "舱盖未关闭或关闭不到位,请稍后重试或重启机场后重试"},
|
||||
{514145, "机场处于现场调试中,无法执行当前操作或执行飞行任务,请断开遥控器和机场的数据线连接后重试"},
|
||||
{514146, "机场处于远程调试中,无法执行飞行任务,请退出远程调试后重试"},
|
||||
{514147, "设备升级中,无法执行飞行任务,请等待升级完成后重试"},
|
||||
{514148, "机场已经在作业中,无法进行远程调试或再次执行飞行任务,请等待当前任务执行完成后重试"},
|
||||
{514149, "机场系统运行异常,无法执行飞行任务,请重启机场后重试"},
|
||||
{514150, "设备重启中,无法执行飞行任务,请等待重启完成后重试"},
|
||||
{514151, "设备升级中,无法执行设备重启指令,请等待升级完成后重试"},
|
||||
{514153, "机场已退出远程调试模式,无法执行当前操作"},
|
||||
{514170, "机场系统初始化中,无法执行当前操作或指令,请等待机场系统初始化完成后重试"},
|
||||
{514171, "云端下发给机场的命令不符合格式要求,机场无法执行"},
|
||||
{514180, "停止空调制冷或停止空调制热失败,请稍后重试"},
|
||||
{514181, "开启空调制冷失败,请稍后重试"},
|
||||
{514182, "开启空调制热失败,请稍后重试"},
|
||||
{514183, "开启空调除湿失败,请稍后重试"},
|
||||
{514184, "当前温度低于 0 ℃,无法开启空调制冷"},
|
||||
{514185, "当前温度高于 45 ℃,无法开启空调制热"},
|
||||
{514300, "网关异常"},
|
||||
{514301, "请求超时,连接断开"},
|
||||
{514302, "网络证书异常,连接失败"},
|
||||
{514303, "网络异常,连接断开"},
|
||||
{514304, "机场请求被拒,连接失败"},
|
||||
}
|
||||
for _, e := range errors {
|
||||
errorMap[e.Code] = e
|
||||
}
|
||||
}
|
||||
func GetErrorMap() map[int]Error {
|
||||
return errorMap
|
||||
}
|
259
third/plane/dj/minio.go
Normal file
259
third/plane/dj/minio.go
Normal file
@ -0,0 +1,259 @@
|
||||
package dj
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
awscs "github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
//// minio的配置信息
|
||||
//const (
|
||||
// Endpoint string = coryCommon.Global + ":9999"
|
||||
// AccessKeyID string = "admin"
|
||||
// SecretAccessKey string = "12345678"
|
||||
// BucketName string = "cory-create"
|
||||
// RoleARN string = "arn:aws:s3:::cory-create/*"
|
||||
// RoleSessionName string = "anysession"
|
||||
// Region string = "cn-chengdu"
|
||||
// UseSSL bool = true
|
||||
// DurationSeconds int64 = 3600
|
||||
//
|
||||
// // Endpoint string = "jl.yj-3d.com:8154"
|
||||
// // AccessKeyID string = "minioadmin"
|
||||
// // SecretAccessKey string = "minioadmin"
|
||||
// // BucketName string = "cory-create"
|
||||
// // RoleARN string = "arn:aws:s3:::cory-create/*"
|
||||
// // RoleSessionName string = "anysession"
|
||||
// // Region string = "cn-chengdu"
|
||||
// // UseSSL bool = true
|
||||
// // DurationSeconds int64 = 3600
|
||||
//)
|
||||
|
||||
var (
|
||||
Endpoint string
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
BucketName string
|
||||
RoleARN string
|
||||
RoleSessionName string
|
||||
Region string
|
||||
UseSSL bool
|
||||
DurationSeconds int64
|
||||
)
|
||||
|
||||
func init() {
|
||||
Endpoint = g.Cfg().MustGet(context.Background(), "minio.endpoint").String()
|
||||
AccessKeyID = g.Cfg().MustGet(context.Background(), "minio.accessKeyID").String()
|
||||
SecretAccessKey = g.Cfg().MustGet(context.Background(), "minio.secretAccessKey").String()
|
||||
BucketName = g.Cfg().MustGet(context.Background(), "minio.bucketName").String()
|
||||
RoleSessionName = g.Cfg().MustGet(context.Background(), "minio.roleSessionName").String()
|
||||
Region = g.Cfg().MustGet(context.Background(), "minio.region").String()
|
||||
UseSSL = g.Cfg().MustGet(context.Background(), "minio.useSSL").Bool()
|
||||
DurationSeconds = g.Cfg().MustGet(context.Background(), "minio.durationSeconds").Int64()
|
||||
RoleARN = g.Cfg().MustGet(context.Background(), "minio.roleARN").String()
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
IsEnablingSSL = false //是否开启SSL安全 (只针对初始化客户端)
|
||||
)
|
||||
|
||||
// PublicMinioClient 初始化MinIO客户端
|
||||
func PublicMinioClient() (minioClient *minio.Client, err error) {
|
||||
// 获取临时凭证
|
||||
token, accessKey, secretAccessKey := MinioVoucher()
|
||||
// 初始化MinIO客户端
|
||||
minioClient, err = minio.New(Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKey, secretAccessKey, token),
|
||||
Secure: IsEnablingSSL,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// MinioVoucher 获取minio的临时凭证
|
||||
func MinioVoucher() (sessionToken, accessKey, secretKey string) {
|
||||
// 创建AWS会话
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Endpoint: aws.String(Endpoint),
|
||||
DisableSSL: aws.Bool(UseSSL),
|
||||
Region: aws.String(Region),
|
||||
Credentials: awscs.NewStaticCredentials(AccessKeyID, SecretAccessKey, ""),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("创建AWS会话错误:%v", err)
|
||||
return
|
||||
}
|
||||
// 创建STS服务客户端
|
||||
stsSvc := sts.New(sess)
|
||||
|
||||
// 获取STS凭证
|
||||
input := &sts.AssumeRoleInput{
|
||||
RoleArn: aws.String(RoleARN),
|
||||
RoleSessionName: aws.String(RoleSessionName),
|
||||
DurationSeconds: aws.Int64(DurationSeconds),
|
||||
}
|
||||
result, err := stsSvc.AssumeRole(input)
|
||||
if err != nil {
|
||||
fmt.Println("获取STS凭证错误:", err)
|
||||
return
|
||||
}
|
||||
// 获取STS凭证的Token、AccessKey和SecretKey
|
||||
sessionToken = *result.Credentials.SessionToken
|
||||
accessKey = *result.Credentials.AccessKeyId
|
||||
secretKey = *result.Credentials.SecretAccessKey
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TheTemporaryAccessUrlIsObtainedBasedOnThePrefixOfTheSpecifiedBucket 根据指定桶的前缀获取到临时访问URL
|
||||
bucketName:桶名
|
||||
prefix:前缀
|
||||
expiry:URL能够访问的时间(最长7天或至少1秒) expiry := 3 * 24 * time.Hour 3天时间访问Url
|
||||
isRecursion:是否递归
|
||||
*/
|
||||
func TheTemporaryAccessUrlIsObtainedBasedOnThePrefixOfTheSpecifiedBucket(ctx context.Context, minioClient *minio.Client, bucketName string, prefix string, expiry time.Duration, isRecursion bool) (url []string) {
|
||||
//1、获取递归集
|
||||
objectSet := ListAndGenerateURLs(ctx, minioClient, bucketName, prefix, isRecursion)
|
||||
//2、根据集获取到临时可访问的URL
|
||||
for _, object := range objectSet {
|
||||
preSignature := GenerateAnOnlineScopedUrlBasedOnTheObject(ctx, minioClient, bucketName, object.Key, expiry)
|
||||
url = append(url, preSignature)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
DownloadTheFileWithTheSpecifiedPrefixBasedOnTheObjectSet 根据对象集下载指定前缀的文件
|
||||
bucketName:桶名
|
||||
prefix:前缀
|
||||
filePath:下载后保存路径
|
||||
isRecursion:是否递归获取对象集
|
||||
*/
|
||||
func DownloadTheFileWithTheSpecifiedPrefixBasedOnTheObjectSet(ctx context.Context, minioClient *minio.Client, bucketName string, prefix string, filePath string, isRecursion bool) (err error) {
|
||||
objectList := ListAndGenerateURLs(ctx, minioClient, bucketName, prefix, isRecursion)
|
||||
for _, object := range objectList {
|
||||
//split := strings.Split(object.Key, "/")
|
||||
//wz := split[len(split)-1]
|
||||
//fileName := strings.Split(wz, ".")[0]
|
||||
//if strings.Contains(fileName, "W") {
|
||||
// fmt.Println(wz)
|
||||
//}
|
||||
err = minioClient.FGetObject(ctx, bucketName, object.Key, filepath.ToSlash(filePath+"/"+object.Key), minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println("下载minio资源失败:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
DeletesTheFileWithTheSpecifiedPrefixBasedOnTheObjectSet 根据对象集删除指定前缀的文件
|
||||
bucketName:桶名
|
||||
prefix:前缀
|
||||
isRecursion:是否递归获取对象集
|
||||
*/
|
||||
func DeletesTheFileWithTheSpecifiedPrefixBasedOnTheObjectSet(ctx context.Context, minioClient *minio.Client, bucketName string, prefix string, isRecursion bool) (err error) {
|
||||
objectList := ListAndGenerateURLs(ctx, minioClient, bucketName, prefix, isRecursion)
|
||||
for _, object := range objectList {
|
||||
err = minioClient.RemoveObject(ctx, bucketName, object.Key, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println("删除minio资源失败:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
UploadDataAccordingToTemporaryCredentials 根据临时凭证上传数据
|
||||
filePath:本地文件路径
|
||||
objectName:存储到minio的地址(桶+前缀) 示例:cory-create/uav/key.txt
|
||||
*/
|
||||
func UploadDataAccordingToTemporaryCredentials(filePath string, objectName string) (err error) {
|
||||
minioClient, err := PublicMinioClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 打开文件
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
// 使用minio-go的PutObject方法上传文件
|
||||
n, err := minioClient.PutObject(context.Background(), BucketName, objectName, file, -1, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println("Error uploading file:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Successfully uploaded %s of size %d\n", objectName, n)
|
||||
return err
|
||||
}
|
||||
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
//=========================================================================================================================
|
||||
|
||||
/*
|
||||
ListAndGenerateURLs 获取指定前缀下的对象集
|
||||
bucketName:桶名
|
||||
prefix:前缀(获取哪个下面的对象集) 示例:"uav/9b0af64e1c274105b157a9781961ed99/DJI_202405170903_001_9b0af64e1c274105b157a9781961ed99"
|
||||
isRecursion:是否递归
|
||||
*/
|
||||
func ListAndGenerateURLs(ctx context.Context, minioClient *minio.Client, bucketName string, prefix string, isRecursion bool) (objectInfo []minio.ObjectInfo) {
|
||||
// 设置递归为 true,以获取所有对象,包括子目录中的对象
|
||||
objectCh := minioClient.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
|
||||
Prefix: prefix,
|
||||
Recursive: isRecursion,
|
||||
})
|
||||
// 等待通道中的对象被填充
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
fmt.Println("获取对象列表失败:", object.Err)
|
||||
continue
|
||||
}
|
||||
objectInfo = append(objectInfo, object)
|
||||
//fmt.Println("对象名称:", object.Key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateAnOnlineScopedUrlBasedOnTheObject 根据对象集生成可在线访问的URL
|
||||
func GenerateAnOnlineScopedUrlBasedOnTheObject(ctx context.Context, minioClient *minio.Client, bucketName string, objectName string, expiry time.Duration) string {
|
||||
// 生成预签名的 GET URL
|
||||
presignedURL, err := minioClient.PresignedGetObject(ctx, bucketName, objectName, expiry, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("生成预签名 URL 失败:%v", err)
|
||||
}
|
||||
//// 使用预签名 URL 访问对象
|
||||
//resp, err := minioClie.
|
||||
//nt.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
|
||||
//if err != nil {
|
||||
// log.Fatalf("获取对象失败:%v", err)
|
||||
//}
|
||||
//defer resp.Close()
|
||||
return presignedURL.String()
|
||||
}
|
||||
|
||||
// StatObjectFunc 获取对象的元数据
|
||||
func StatObjectFunc(ctx context.Context, minioClient *minio.Client, bucketName string, objectName string) (objInfo minio.ObjectInfo, err error) {
|
||||
objInfo, err = minioClient.StatObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
|
||||
return
|
||||
}
|
88
third/plane/event/event.go
Normal file
88
third/plane/event/event.go
Normal file
@ -0,0 +1,88 @@
|
||||
package event
|
||||
|
||||
// eventHandlerInfo定义了事件处理器的信息
|
||||
type eventHandlerInfo struct {
|
||||
handler EventHandler // 事件处理函数
|
||||
once bool // 是否只执行一次
|
||||
}
|
||||
|
||||
// EventHandler定义了事件处理函数的类型
|
||||
type EventHandler func(params ...any)
|
||||
|
||||
// Event表示一个事件对象
|
||||
type Event struct {
|
||||
handlers []eventHandlerInfo // 事件处理器列表
|
||||
}
|
||||
|
||||
// Attach将事件处理器附加到事件对象中,并返回处理器在列表中的索引
|
||||
func (e *Event) Attach(handler EventHandler) int {
|
||||
handlerInfo := eventHandlerInfo{handler, true} //默认只执行一次
|
||||
for i, h := range e.handlers {
|
||||
if h.handler == nil {
|
||||
e.handlers[i] = handlerInfo
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
e.handlers = append(e.handlers, handlerInfo)
|
||||
|
||||
return len(e.handlers) - 1
|
||||
}
|
||||
|
||||
// Detach从事件对象中移除指定索引处的处理器
|
||||
func (e *Event) Detach(handle int) {
|
||||
e.handlers[handle].handler = nil
|
||||
}
|
||||
|
||||
// Once将事件处理器附加到事件对象中,并将处理器标记为只执行一次
|
||||
func (e *Event) Once(handler EventHandler) {
|
||||
i := e.Attach(handler)
|
||||
e.handlers[i].once = true
|
||||
}
|
||||
|
||||
// EventPublisher表示事件发布者
|
||||
type EventPublisher struct {
|
||||
event Event // 事件对象
|
||||
}
|
||||
|
||||
// Event返回事件对象的指针
|
||||
func (p *EventPublisher) Event() *Event {
|
||||
return &p.event
|
||||
}
|
||||
|
||||
// Publish触发事件,依次执行事件对象中的处理器,并在处理器标记为只执行一次时将其从事件对象中移除
|
||||
func (p *EventPublisher) Publish(params ...any) {
|
||||
for i, h := range p.event.handlers {
|
||||
if h.handler != nil {
|
||||
h.handler(params...)
|
||||
if h.once {
|
||||
p.event.Detach(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetEvent(fn func(params ...any)) *EventPublisher {
|
||||
publisher := &EventPublisher{}
|
||||
|
||||
// 定义一个事件处理器函数
|
||||
handler := fn
|
||||
// 将事件处理器附加到事件列表中
|
||||
publisher.Event().Attach(handler)
|
||||
return publisher
|
||||
}
|
||||
|
||||
/*func main() {
|
||||
// 在这里编写您的代码逻辑
|
||||
// 创建一个事件发布者
|
||||
publisher := &EventPublisher{}
|
||||
|
||||
// 定义一个事件处理器函数
|
||||
handler := func() {
|
||||
fmt.Println("Event handled!")
|
||||
}
|
||||
// 将事件处理器附加到事件列表中
|
||||
publisher.Event().Attach(handler)
|
||||
// 发布事件
|
||||
publisher.Publish()
|
||||
}*/
|
125
third/plane/globe/globe.go
Normal file
125
third/plane/globe/globe.go
Normal file
@ -0,0 +1,125 @@
|
||||
package globe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
ALL = -1 //所有
|
||||
ENABLE = 1
|
||||
DISABLE = 0
|
||||
DESC = "desc"
|
||||
ASC = "asc"
|
||||
PAGE = 1
|
||||
PAGESIZE = 10
|
||||
ONLINE = 1
|
||||
OFFLINE = 0
|
||||
DEFAULTPWD = "123456"
|
||||
DEFAULTUSR = "admin"
|
||||
)
|
||||
|
||||
var DepartName = "一级部门" //默认的一级部门名称
|
||||
var IS_OFFLINE_VERSION = true //是否为单机版本
|
||||
const SOURCE = "static/source/"
|
||||
|
||||
type WS_MAP map[string]*ghttp.WebSocket
|
||||
|
||||
var WS_Status_map WS_MAP //状态数据ws
|
||||
var WS_Progress_drone_open_map WS_MAP //飞行器开机进度ws
|
||||
var WS_Progress_drone_close_map WS_MAP //飞行器关机进度ws
|
||||
var WS_Progress_cover_open_map WS_MAP //机库打开舱盖进度ws
|
||||
var WS_Progress_cover_close_map WS_MAP //机库关闭舱盖进度ws
|
||||
var WS_Progress_device_reboot_map WS_MAP //机库重启进度ws
|
||||
var WS_Progress_putter_open_map WS_MAP //推杆展开进度ws
|
||||
var WS_Progress_putter_close_map WS_MAP //推杆闭合进度ws
|
||||
var WS_Reply WS_MAP //状态恢复
|
||||
|
||||
var MutexRw sync.RWMutex //map的读写锁
|
||||
|
||||
const (
|
||||
TILESET = "tileset"
|
||||
BIM = "bim"
|
||||
LAYER = "layer"
|
||||
TERRAIN = "terrain"
|
||||
POINT = "point"
|
||||
LINE = "line"
|
||||
AREA = "area"
|
||||
MODEL = "model"
|
||||
KML = "kml"
|
||||
GEOJSON = "geojson"
|
||||
)
|
||||
|
||||
const (
|
||||
PAK = ".pak"
|
||||
MBTILES = ".mbtiles"
|
||||
CLT = ".clt"
|
||||
JCT = ".jct"
|
||||
)
|
||||
|
||||
var (
|
||||
PORT = "80"
|
||||
HOST = ""
|
||||
PROTOCOL = ""
|
||||
KEY = ""
|
||||
CRT = ""
|
||||
)
|
||||
|
||||
const (
|
||||
HTTP = "http"
|
||||
HTTPS = "https"
|
||||
)
|
||||
|
||||
func GetErrors(msg string) error {
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func GetAddr() string {
|
||||
//单机版本时 无代理,需要补全地址
|
||||
if IS_OFFLINE_VERSION {
|
||||
return PROTOCOL + "://" + HOST + ":" + PORT + "/"
|
||||
}
|
||||
//网络版时 有代理 不需要补全地址
|
||||
return ""
|
||||
}
|
||||
|
||||
/*clt数据包*/
|
||||
type Tile struct {
|
||||
MD5 string `json:"md5"`
|
||||
PATH string `json:"path"`
|
||||
Tile []byte `json:"tile"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func RenderData(request *ghttp.Request, data []byte) {
|
||||
request.Response.Header().Set("Cache-Control", "private,max-age="+strconv.Itoa(60*60))
|
||||
request.Response.WriteHeader(http.StatusOK)
|
||||
request.Response.Writer.Write(data)
|
||||
}
|
||||
func CloseDB(db *gorm.DB) {
|
||||
s, err := db.DB()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Close()
|
||||
}
|
||||
|
||||
var IS_AUTH_SUCCESS = false //是否授权通过
|
||||
|
||||
func CheckAuth() bool {
|
||||
return IS_AUTH_SUCCESS
|
||||
}
|
||||
func SetAuth(auth bool) {
|
||||
IS_AUTH_SUCCESS = auth
|
||||
}
|
||||
|
||||
/*坐标点*/
|
||||
type Point struct {
|
||||
Lng float64 `json:"lng" v:"required"`
|
||||
Lat float64 `json:"lat" v:"required"`
|
||||
Alt float64 `json:"alt" v:"required"`
|
||||
}
|
197
third/plane/mqtt/emqx.go
Normal file
197
third/plane/mqtt/emqx.go
Normal file
@ -0,0 +1,197 @@
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/dj"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/event"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var timer *gtimer.Timer
|
||||
var client mqtt.Client
|
||||
var eventMap map[string]*event.EventPublisher
|
||||
|
||||
const (
|
||||
osd = "osd" //设备端定频向云平台推送的设备属性(properties), 具体内容范围参见物模型内容
|
||||
state = "state" //设备端按需上报向云平台推送的设备属性(properties), 具体内容范围参见物模型内容
|
||||
services_reply = "services_reply" //设备对 service 的回复、处理结果
|
||||
events = "events" //设备端向云平台发送的,需要关注和处理的事件。 比如SD满了,飞机解禁禁飞区等信息(事件范围参见物模型内容)
|
||||
status = "status" //设备上下线、更新拓扑
|
||||
set_reply = "set_reply" //设备属性设置的响应
|
||||
requests = "requests" //设备端向云平台发送请求,为了获取一些信息,比如上传的临时凭证
|
||||
services = "services" //云平台向设备发送的服务(具体service identifier 见物模型内容)。
|
||||
)
|
||||
|
||||
var MsgCallBackMap map[string]CB
|
||||
|
||||
func InitMQTT() {
|
||||
eventMap = make(map[string]*event.EventPublisher)
|
||||
timer = gtimer.New()
|
||||
initMsgs()
|
||||
dj.InitError()
|
||||
connect()
|
||||
}
|
||||
|
||||
func initMsgs() {
|
||||
MsgCallBackMap = make(map[string]CB)
|
||||
msgs := []CB{
|
||||
{osd, dealOsd, nil},
|
||||
{state, deal_state, nil},
|
||||
{requests, deal_requests, nil},
|
||||
{services_reply, deal_services_reply, nil},
|
||||
{status, deal_status, nil},
|
||||
{events, deal_events, nil},
|
||||
{services, deal_services, nil},
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
MsgCallBackMap[msg.Flag] = msg
|
||||
}
|
||||
}
|
||||
|
||||
type CB struct {
|
||||
Flag string
|
||||
Recv func(mqtt.Client, mqtt.Message) //接收消息
|
||||
Reply func(mqtt.Client, mqtt.Message) //回复消息
|
||||
}
|
||||
|
||||
func connect() {
|
||||
host := g.Cfg().MustGet(gctx.New(), "mqtt.host").String()
|
||||
port := g.Cfg().MustGet(gctx.New(), "mqtt.port").String()
|
||||
username := g.Cfg().MustGet(gctx.New(), "mqtt.username").String()
|
||||
password := g.Cfg().MustGet(gctx.New(), "mqtt.password").String()
|
||||
clientid := g.Cfg().MustGet(gctx.New(), "mqtt.clientid").String()
|
||||
topics := g.Cfg().MustGet(gctx.New(), "mqtt.topics").String()
|
||||
fmt.Println(host, port, username, password, clientid, topics, "tcp://"+host+":"+port)
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker("tcp://" + host + ":" + port) // 设置MQTT代理服务器地址
|
||||
opts.SetClientID(clientid) // 设置客户端ID
|
||||
opts.SetPassword(password) // 设置客户端ID
|
||||
opts.SetUsername(username) // 设置客户端ID
|
||||
// 处理消息接收
|
||||
opts.SetDefaultPublishHandler(receiveHandler)
|
||||
opts.SetConnectionLostHandler(disconnected)
|
||||
opts.SetOnConnectHandler(connected)
|
||||
opts.SetKeepAlive(1000 * time.Second) //超时等待,防止客户端掉线
|
||||
opts.SetAutoReconnect(true)
|
||||
client = mqtt.NewClient(opts)
|
||||
// 连接MQTT代理服务器
|
||||
if token := client.Connect(); token.Wait() && token.Error() != nil {
|
||||
panic(token.Error())
|
||||
}
|
||||
arr := strings.Split(topics, ",")
|
||||
for _, topic := range arr {
|
||||
// 订阅主题
|
||||
fmt.Println("订阅主题", topic)
|
||||
if token := client.Subscribe(topic, 0, nil); token.Wait() && token.Error() != nil {
|
||||
fmt.Println(token.Error())
|
||||
}
|
||||
//fmt.Println(topic)
|
||||
}
|
||||
|
||||
//ctx := gctx.New()
|
||||
////格式化
|
||||
//DeviceFormat(ctx, "4SEDL9C001X8GE")
|
||||
//DroneFormat(ctx, "4SEDL9C001X8GE")
|
||||
//DeviceFormat(ctx, "7CTDM4100BG7HV")
|
||||
//DroneFormat(ctx, "7CTDM4100BG7HV")
|
||||
|
||||
//飞机升级
|
||||
//OtaCreateFunc(ctx, "8UUXN4P00A06NK")
|
||||
|
||||
//gtimer.SetTimeout(gctx.New(), 2*time.Second, func(ctx context.Context) {
|
||||
//sn := "4SEDL9C001X8GE"
|
||||
//err, d := speaker_audio_play_start(sn)
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
//}
|
||||
//整一个pcm的音频文件
|
||||
// point := dj.Point{}
|
||||
// point.Longitude = 106.541923087
|
||||
// point.Latitude = 23.458531397
|
||||
// point.Height = 823.8873291015625 + 10
|
||||
// //err, d := Flight_authority_grab(sn)
|
||||
// //if err != nil {
|
||||
// // return
|
||||
// //}
|
||||
// err, d := Fly_to_point(sn, point)
|
||||
//fmt.Println(d)
|
||||
// fmt.Println(err)
|
||||
//
|
||||
// sn = "4TADL210010014" //中煤广西机库的sn
|
||||
// //// //sn = "4TADL2E001002D" //机库的sn
|
||||
// //// //sn = "1581F5BMD2323001S928" //飞机的sn
|
||||
// ////dev_sn := "1581F5BMD23280014131" //吉林飞机的sn
|
||||
// //dev_sn := "1581F5BMD238V00172JR" //吉林飞机的sn
|
||||
// //camera_index := "165-0-7" //机库的摄像头id
|
||||
// ////camera_index = "53-0-0" //飞机摄像头1的id
|
||||
// //camera_index = "39-0-7" //飞机摄像头2的id
|
||||
// //Debug_mode_open(sn) //调试模式,先调试模式才能飞行器开机,推流不需要調試
|
||||
// //Debug_mode_close(sn)
|
||||
// //err, d := Cover_open(sn) //打开舱盖
|
||||
// //err, d := Cover_close(sn)
|
||||
// //err, d := Drone_open(sn) //飞行器开机
|
||||
// //err, d := Drone_close(sn) //飞行器关机
|
||||
// //err, d := Device_reboot(sn)
|
||||
// //
|
||||
// //fmt.Println(d)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //} //打开舱盖
|
||||
// ////fmt.Println("打开舱盖")
|
||||
// //err, d := Live_start_push(sn, sn, camera_index)
|
||||
// //err, d := Live_stop_push(sn, sn, camera_index)
|
||||
//
|
||||
// //fmt.Println(d)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// // return
|
||||
// //} //关闭舱盖
|
||||
// ////fmt.Println(d.Data.Result)
|
||||
// //// //device_reboot()
|
||||
// //// //if err != nil {
|
||||
// //// // return
|
||||
// //// //}
|
||||
// //// //fmt.Println(d)
|
||||
// //// //live_stop_push(sn, sn, camera_index)
|
||||
// ////
|
||||
//})
|
||||
}
|
||||
|
||||
func receiveHandler(client mqtt.Client, msg mqtt.Message) {
|
||||
topic := msg.Topic()
|
||||
arrs := strings.Split(topic, "/")
|
||||
|
||||
tp := arrs[len(arrs)-1]
|
||||
if v, ok := MsgCallBackMap[tp]; ok {
|
||||
v.Recv(client, msg)
|
||||
if v.Reply != nil {
|
||||
v.Reply(client, msg)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("非法topic", topic)
|
||||
}
|
||||
}
|
||||
func disconnected(client mqtt.Client, err error) {
|
||||
fmt.Println("断开了,准备重连")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
g.Log("uav").Error(gctx.New(), err)
|
||||
}
|
||||
|
||||
timer.Start()
|
||||
timer.Add(gctx.New(), time.Second*5, func(ctx context.Context) {
|
||||
connect()
|
||||
})
|
||||
}
|
||||
|
||||
func connected(client mqtt.Client) {
|
||||
fmt.Println("链接成功")
|
||||
timer.Stop()
|
||||
}
|
31
third/plane/mqtt/entity.go
Normal file
31
third/plane/mqtt/entity.go
Normal file
@ -0,0 +1,31 @@
|
||||
package mqtt
|
||||
|
||||
// LiveStreamingCapabilityUpdateEntity 直播能力更新 UP
|
||||
type LiveStreamingCapabilityUpdateEntity struct {
|
||||
Tid string `json:"tid"`
|
||||
Bid string `json:"bid"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Gateway string `json:"gateway"`
|
||||
Method string `json:"method"`
|
||||
Data struct {
|
||||
LiveCapacity struct {
|
||||
AvailableVideoNumber int `json:"available_video_number"`
|
||||
CoexistVideoNumberMax int `json:"coexist_video_number_max"`
|
||||
DeviceList []struct {
|
||||
Sn string `json:"sn"`
|
||||
AvailableVideoNumber int `json:"available_video_number"`
|
||||
CoexistVideoNumberMax int `json:"coexist_video_number_max"`
|
||||
CameraList []struct {
|
||||
CameraIndex string `json:"camera_index"`
|
||||
AvailableVideoNumber int `json:"available_video_number"`
|
||||
CoexistVideoNumberMax int `json:"coexist_video_number_max"`
|
||||
VideoList []struct {
|
||||
VideoIndex string `json:"video_index"`
|
||||
VideoType string `json:"video_type"`
|
||||
SwitchableVideoTypes []string `json:"switchable_video_types"`
|
||||
} `json:"video_list"`
|
||||
} `json:"camera_list"`
|
||||
} `json:"device_list"`
|
||||
} `json:"live_capacity"`
|
||||
} `json:"data"`
|
||||
}
|
1282
third/plane/mqtt/msg_deal.go
Normal file
1282
third/plane/mqtt/msg_deal.go
Normal file
File diff suppressed because it is too large
Load Diff
334
third/plane/plane.go
Normal file
334
third/plane/plane.go
Normal file
@ -0,0 +1,334 @@
|
||||
package plane
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/service"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/dj"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/globe"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/mqtt"
|
||||
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PLANE struct {
|
||||
}
|
||||
|
||||
func InitPlaneApi(group *ghttp.RouterGroup) {
|
||||
init_map()
|
||||
group.Group("/plane", func(group *ghttp.RouterGroup) {
|
||||
group.Bind(new(PLANE))
|
||||
group.Middleware(service.Middleware().Ctx, service.Middleware().Auth)
|
||||
})
|
||||
}
|
||||
|
||||
func init_map() {
|
||||
globe.WS_Status_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_drone_open_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_drone_close_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_cover_open_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_cover_close_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_device_reboot_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_putter_close_map = make(globe.WS_MAP)
|
||||
globe.WS_Progress_putter_open_map = make(globe.WS_MAP)
|
||||
globe.WS_Reply = make(globe.WS_MAP)
|
||||
}
|
||||
|
||||
type UsualRes struct {
|
||||
}
|
||||
|
||||
type DeviceGoHomeReq struct {
|
||||
g.Meta `path:"gohome" summary:"一键返航" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) DeviceGoHome(ctx context.Context, req *DeviceGoHomeReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Return_home(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type DeviceListReq struct {
|
||||
g.Meta `path:"list" summary:"获取飞机列表" method:"get" tags:"无人机相关"`
|
||||
}
|
||||
type DeviceListRes struct {
|
||||
}
|
||||
|
||||
func (receiver PLANE) DeviceList(ctx context.Context, req *DeviceListReq) (res *DeviceListRes, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type DeviceRebootReq struct {
|
||||
g.Meta `path:"device_reboot" summary:"机库重启" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) DeviceReboot(ctx context.Context, req *DeviceRebootReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Device_reboot(req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ClosePutterReq struct {
|
||||
g.Meta `path:"close_putter" summary:"关闭推杆" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) ClosePutterReq(ctx context.Context, req *ClosePutterReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Putter_close(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"push_rod": 2})
|
||||
return
|
||||
}
|
||||
|
||||
type OpenPutterReq struct {
|
||||
g.Meta `path:"open_putter" summary:"打开推杆" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) OpenPutterReq(ctx context.Context, req *OpenPutterReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Putter_open(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"push_rod": 1})
|
||||
return
|
||||
}
|
||||
|
||||
type OpenChargeReq struct {
|
||||
g.Meta `path:"open_charge" summary:"打开充电" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) OpenChargeReq(ctx context.Context, req *OpenChargeReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Charge_open(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"recharge": 1})
|
||||
return
|
||||
}
|
||||
|
||||
type CloseChargeReq struct {
|
||||
g.Meta `path:"close_charge" summary:"关闭充电" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) CloseCharge(ctx context.Context, req *CloseChargeReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Charge_close(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"recharge": 2})
|
||||
return
|
||||
}
|
||||
|
||||
type CloseDroneReq struct {
|
||||
g.Meta `path:"close_drone" summary:"飞行器关机(调试模式下)" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) CloseDrone(ctx context.Context, req *CloseDroneReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Drone_close(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"power": 2})
|
||||
return
|
||||
}
|
||||
|
||||
type OpenDroneReq struct {
|
||||
g.Meta `path:"open_drone" summary:"飞行器开机(调试模式下)" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) OpenDrone(ctx context.Context, req *OpenDroneReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Drone_open(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"power": 1})
|
||||
return
|
||||
}
|
||||
|
||||
type CloseCoverReq struct {
|
||||
g.Meta `path:"close_cover" summary:"关闭舱盖(调试模式下)" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) CloseCover(ctx context.Context, req *CloseCoverReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Cover_close(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"hatch_cover": 2})
|
||||
return
|
||||
}
|
||||
|
||||
type OpenCoverReq struct {
|
||||
g.Meta `path:"open_cover" summary:"打开舱盖(调试模式下)" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) OpenCover(ctx context.Context, req *OpenCoverReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Cover_open(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"hatch_cover": 1})
|
||||
return
|
||||
}
|
||||
|
||||
type CloseDebugReq struct {
|
||||
g.Meta `path:"close_debug" summary:"关闭调试模式" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) CloseDebug(ctx context.Context, req *CloseDebugReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Debug_mode_close(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"debug": 2})
|
||||
return
|
||||
}
|
||||
|
||||
type OpenDebugReq struct {
|
||||
g.Meta `path:"open_debug" summary:"打开调试模式" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) OpenDebug(ctx context.Context, req *OpenDebugReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Debug_mode_open(ctx, req.GatewaySn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
g.DB().Model("manage_button_state").
|
||||
Where("mq_client_id", req.GatewaySn).
|
||||
Update(g.Map{"debug": 1})
|
||||
return
|
||||
}
|
||||
|
||||
type StopLiveReq struct {
|
||||
g.Meta `path:"stop_live" summary:"停止直播" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
Devsn string `json:"devsn" v:"required" dc:"设备sn"`
|
||||
Camera_index string `json:"camera_index" v:"required" dc:"摄像头"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) StopLive(ctx context.Context, req *StopLiveReq) (res *UsualRes, err error) {
|
||||
err, d := mqtt.Live_stop_push(req.GatewaySn, req.Devsn, req.Camera_index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type StartLiveReq struct {
|
||||
g.Meta `path:"start_live" summary:"开始直播" method:"post" tags:"无人机相关"`
|
||||
GatewaySn string `json:"gateway_sn" v:"required" dc:"机库sn"`
|
||||
Devsn string `json:"devsn" v:"required" dc:"设备sn"`
|
||||
Camera_index string `json:"camera_index" v:"required" dc:"摄像头"`
|
||||
}
|
||||
type StartLiveRes struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func geterror(code int) error {
|
||||
if v, ok := dj.GetErrorMap()[code]; ok {
|
||||
return globe.GetErrors(v.Msg)
|
||||
} else {
|
||||
return globe.GetErrors("未知错误:" + strconv.Itoa(code))
|
||||
}
|
||||
}
|
||||
|
||||
func (receiver PLANE) StartLive(ctx context.Context, req *StartLiveReq) (res *StartLiveRes, err error) {
|
||||
err, d := mqtt.Live_start_push(req.GatewaySn, req.Devsn, req.Camera_index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Data.Result > 0 && d.Data.Result != 513003 /*正在直播中 ,可直接返回地址*/ {
|
||||
err = geterror(d.Data.Result)
|
||||
return
|
||||
}
|
||||
res = &StartLiveRes{}
|
||||
res.Url = d.Data.Url
|
||||
return
|
||||
}
|
107
third/plane/progress.go
Normal file
107
third/plane/progress.go
Normal file
@ -0,0 +1,107 @@
|
||||
package plane
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/globe"
|
||||
)
|
||||
|
||||
type Progerss_putter_close_Req struct {
|
||||
g.Meta `path:"progress/putter_close" summary:"推杆闭合进度" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_putter_close(ctx context.Context, req *Progerss_putter_close_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_putter_close_map)
|
||||
}
|
||||
|
||||
type Progerss_putter_open_Req struct {
|
||||
g.Meta `path:"progress/putter_open" summary:"推杆展开进度" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_putter_open(ctx context.Context, req *Progerss_putter_open_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_putter_open_map)
|
||||
}
|
||||
|
||||
type Progerss_Reply_Req struct {
|
||||
g.Meta `path:"progress/err_reply" summary:"up回复错误状态" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_Reply(ctx context.Context, req *Progerss_Reply_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Reply)
|
||||
}
|
||||
|
||||
type Progerss_device_reboot_Req struct {
|
||||
g.Meta `path:"progress/device_reboot" summary:"机库重启进度推送" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_device_reboot(ctx context.Context, req *Progerss_device_reboot_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_device_reboot_map)
|
||||
}
|
||||
|
||||
type Progerss_cover_close_Req struct {
|
||||
g.Meta `path:"progress/cover_close" summary:"机库关闭舱盖进度推送" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_cover_close(ctx context.Context, req *Progerss_cover_close_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_cover_close_map)
|
||||
}
|
||||
|
||||
type Progerss_cover_open_Req struct {
|
||||
g.Meta `path:"progress/cover_open" summary:"机库打开舱盖进度推送" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_cover_open(ctx context.Context, req *Progerss_cover_open_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_cover_open_map)
|
||||
}
|
||||
|
||||
type Progerss_drone_close_Req struct {
|
||||
g.Meta `path:"progress/drone_close" summary:"飞行器关机进度推送" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_drone_close(ctx context.Context, req *Progerss_drone_close_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_drone_close_map)
|
||||
}
|
||||
|
||||
type Progerss_drone_open_Req struct {
|
||||
g.Meta `path:"progress/drone_open" summary:"飞行器开机进度推送" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) Progerss_drone_open(ctx context.Context, req *Progerss_drone_open_Req) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Progress_drone_open_map)
|
||||
}
|
||||
|
||||
type DeviceStatusReq struct {
|
||||
g.Meta `path:"device_status" summary:"ws推送飞机状态" method:"get" tags:"无人机进度数据相关(ws)"`
|
||||
}
|
||||
type DeviceStatusRes struct {
|
||||
Code int `json:"code"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (receiver PLANE) DeviceStatus(ctx context.Context, req *DeviceStatusReq) (res *UsualRes, err error) {
|
||||
return save_ws(ctx, &globe.WS_Status_map)
|
||||
}
|
||||
|
||||
func save_ws(ctx context.Context, _map *globe.WS_MAP) (res *UsualRes, err error) {
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
ws, err := r.WebSocket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := r.Get("token").String()
|
||||
|
||||
globe.MutexRw.Lock()
|
||||
(*_map)[ip] = ws
|
||||
globe.MutexRw.Unlock()
|
||||
fmt.Println("ws连接了", ip)
|
||||
for {
|
||||
_, _, err = ws.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("ws断开了", ip)
|
||||
delete(*_map, ip)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
157
third/plane/wayline/struct/struct.go
Normal file
157
third/plane/wayline/struct/struct.go
Normal file
@ -0,0 +1,157 @@
|
||||
package _struct
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// 飞向首航点模式
|
||||
const (
|
||||
Safely = "safely"
|
||||
PointToPoint = "pointToPoint"
|
||||
)
|
||||
|
||||
// 航线结束动作
|
||||
const (
|
||||
GoHome = "goHome"
|
||||
AutoLand = "autoLand"
|
||||
GotoFirstWaypoint = "gotoFirstWaypoint"
|
||||
)
|
||||
|
||||
//失控是否继续执行航线
|
||||
|
||||
const (
|
||||
GoContinue = "goContinue" //继续执行航线
|
||||
ExecuteLostAction = "executeLostAction" //退出航线,执行失控动作
|
||||
)
|
||||
|
||||
// 失控动作类型
|
||||
const (
|
||||
GoBack = "goBack" //返航。飞行器从失控位置飞向起飞点
|
||||
Landing = "landing" //降落。飞行器从失控位置原地降落
|
||||
Hover = "hover" //悬停。飞行器从失控位置悬停
|
||||
)
|
||||
|
||||
// 预定义模板类型
|
||||
const (
|
||||
Waypoint = "waypoint" //航点飞行
|
||||
Mapping2d = "mapping2d" //建图航拍
|
||||
Mapping3d = "mapping3d" //倾斜摄影
|
||||
MappingStrip = "mappingStrip" //航带飞行
|
||||
)
|
||||
|
||||
// Placemark 结构表示 KML 的地点标记
|
||||
type Placemark struct {
|
||||
Point Point `xml:"Point"`
|
||||
Index int `xml:"wpml:index"`
|
||||
EllipsoidHeight float64 `xml:"wpml:ellipsoidHeight"`
|
||||
Height float64 `xml:"wpml:height"`
|
||||
UseGlobalSpeed int `xml:"wpml:useGlobalSpeed"`
|
||||
UseGlobalHeadingParam int `xml:"wpml:useGlobalHeadingParam"`
|
||||
UseGlobalTurnParam int `xml:"wpml:useGlobalTurnParam"`
|
||||
UseStraightLine int `xml:"wpml:useStraightLine"`
|
||||
//ActionGroup ActionGroup `xml:"wpml:actionGroup"`
|
||||
}
|
||||
|
||||
// Point 结构表示地点坐标
|
||||
type Point struct {
|
||||
Coordinates string `xml:"coordinates"`
|
||||
}
|
||||
|
||||
type PayloadParam struct {
|
||||
PayloadPositionIndex int `xml:"wpml:payloadPositionIndex"`
|
||||
ImageFormat string `xml:"wpml:imageFormat"`
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Author string `xml:"wpml:author"`
|
||||
UpdateTime int64 `xml:"wpml:updateTime"`
|
||||
CreateTime int64 `xml:"wpml:createTime"`
|
||||
MissionConfig MissionConfig `xml:"wpml:missionConfig"`
|
||||
Folder interface{} `xml:"Folder"`
|
||||
}
|
||||
|
||||
type PublicTemplate struct {
|
||||
TemplateType string `xml:"wpml:templateType"`
|
||||
TemplateId int `xml:"wpml:templateId"`
|
||||
AutoFlightSpeed float64 `xml:"wpml:autoFlightSpeed"`
|
||||
WaylineCoordinateSysParam struct {
|
||||
CoordinateMode string `xml:"wpml:coordinateMode"`
|
||||
HeightMode string `xml:"wpml:heightMode"`
|
||||
GlobalShootHeight float64 `xml:"wpml:globalShootHeight"`
|
||||
PositioningType string `xml:"wpml:positioningType"`
|
||||
SurfaceFollowModeEnable int `xml:"wpml:surfaceFollowModeEnable"`
|
||||
SurfaceRelativeHeight string `xml:"wpml:surfaceRelativeHeight"`
|
||||
} `xml:"wpml:waylineCoordinateSysParam"`
|
||||
}
|
||||
|
||||
type ActionGroup struct {
|
||||
ActionGroupId int `xml:"wpml:actionGroupId"`
|
||||
ActionGroupStartIndex int `xml:"wpml:actionGroupStartIndex"`
|
||||
ActionGroupEndIndex int `xml:"wpml:actionGroupEndIndex"`
|
||||
ActionGroupMode string `xml:"wpml:actionGroupMode"`
|
||||
ActionTrigger struct {
|
||||
ActionTriggerType string `xml:"actionTriggerType"`
|
||||
} `xml:"actionTrigger"`
|
||||
Action Action `xml:"action"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
ActionId int `xml:"actionId"`
|
||||
ActionActuatorFunc string `xml:"actionActuatorFunc"`
|
||||
ActionActuatorFuncParam ActionActuatorFuncParam `xml:"actionActuatorFuncParam"`
|
||||
}
|
||||
|
||||
type ActionActuatorFuncParam struct {
|
||||
FileSuffix string `xml:"fileSuffix"`
|
||||
PayloadPositionIndex string `xml:"payloadPositionIndex"`
|
||||
}
|
||||
|
||||
type MissionConfig struct {
|
||||
FlyToWaylineMode string `xml:"wpml:flyToWaylineMode"`
|
||||
FinishAction string `xml:"wpml:finishAction"`
|
||||
ExitOnRCLost string `xml:"wpml:exitOnRCLost"`
|
||||
ExecuteRCLostAction string `xml:"wpml:executeRCLostAction"`
|
||||
TakeOffSecurityHeight float64 `xml:"wpml:takeOffSecurityHeight"`
|
||||
GlobalTransitionalSpeed float64 `xml:"wpml:globalTransitionalSpeed"`
|
||||
GlobalRTHHeight float64 `xml:"wpml:globalRTHHeight"`
|
||||
|
||||
//TakeOffRefPoint xml.Name `xml:"wpml:takeOffRefPoint"`
|
||||
//
|
||||
//TakeOffRefPointAGLHeight xml.Name `xml:"wpml:takeOffRefPointAGLHeight"`
|
||||
|
||||
//DroneInfo struct {
|
||||
// DroneEnumValue xml.Name `xml:"wpml:droneEnumValue"`
|
||||
// DroneSubEnumValue xml.Name `xml:"wpml:droneSubEnumValue"`
|
||||
//} `xml:"wpml:droneInfo"`
|
||||
//PayloadInfo struct {
|
||||
// PayloadEnumValue xml.Name `xml:"wpml:payloadEnumValue"`
|
||||
// PayloadSubEnumValue xml.Name `xml:"wpml:payloadSubEnumValue"`
|
||||
// PayloadPositionIndex xml.Name `xml:"wpml:payloadPositionIndex"`
|
||||
//} `xml:"wpml:payloadInfo"`
|
||||
}
|
||||
|
||||
type WayPointTemplate struct {
|
||||
PublicTemplate
|
||||
GlobalWaypointTurnMode string `xml:"wpml:globalWaypointTurnMode"` //全局航点类型(全局航点转弯模式)coordinateTurn:协调转弯,不过点,提前转弯 toPointAndStopWithDiscontinuityCurvature:直线飞行,飞行器到点停 toPointAndStopWithContinuityCurvature:曲线飞行,飞行器到点停 toPointAndPassWithContinuityCurvature:曲线飞行,飞行器过点不停
|
||||
GlobalUseStraightLine int `xml:"wpml:globalUseStraightLine"` //全局航段轨迹是否尽量贴合直线
|
||||
GimbalPitchMode string `xml:"wpml:gimbalPitchMode"` //云台俯仰角控制模式 manual:手动控制。飞行器从一个航点飞向下一个航点的过程中,
|
||||
// 支持用户手动控制云台的俯仰角度。若无用户控制,则保持飞离航点时的云台俯仰角度。
|
||||
//usePointSetting:依照每个航点设置。飞行器从一个航点飞向下一个航点的过程中,云台俯仰角均匀过渡至下一个航点的俯仰角。
|
||||
GlobalHeight float64 `xml:"wpml:globalHeight"` //全局航线高度(相对起飞点高度
|
||||
Placemark []Placemark `xml:"Placemark"`
|
||||
//UseGlobalTransitionalSpeed string `xml:"wpml:useGlobalTransitionalSpeed"`
|
||||
//TransitionalSpeed string `xml:"wpml:transitionalSpeed"`
|
||||
//GlobalWaypointHeadingParam struct {
|
||||
// WaypointHeadingMode string `xml:"wpml:waypointHeadingMode"`
|
||||
// WaypointHeadingAngle string `xml:"wpml:waypointHeadingAngle"`
|
||||
// WaypointPoiPoint string `xml:"wpml:waypointPoiPoint"`
|
||||
// WaypointHeadingPathMode string `xml:"wpml:waypointHeadingPathMode"`
|
||||
//} `xml:"wpml:globalWaypointHeadingParam"` //全局偏航角模式参数
|
||||
}
|
||||
|
||||
// KML 结构表示完整的 KML 文件
|
||||
type KML struct {
|
||||
//XMLName xml.Attr `xml:"kml"`
|
||||
XMLName xml.Name `xml:"http://www.opengis.net/kml/2.2 kml"`
|
||||
XmlnsStream string `xml:"xmlns:wpml,attr"`
|
||||
//Test []Test `xml:"test"`
|
||||
Document Doc `xml:"Document"`
|
||||
}
|
78
third/plane/wayline/template/template.go
Normal file
78
third/plane/wayline/template/template.go
Normal file
@ -0,0 +1,78 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/tiger1103/gfast/v3/third/plane/wayline/struct"
|
||||
"os"
|
||||
)
|
||||
|
||||
func CreateWayPointTempalteWpml() {
|
||||
|
||||
}
|
||||
|
||||
func CreateWayPointTempalteKml() {
|
||||
kml := _struct.KML{
|
||||
//XmlnsStream: "http://www.dji.com/wpmz/1.0.3",
|
||||
Document: _struct.Doc{
|
||||
Author: "重庆远界大数据研究院有限公司",
|
||||
CreateTime: gtime.Now().TimestampMilli(),
|
||||
UpdateTime: gtime.Now().TimestampMilli(),
|
||||
},
|
||||
}
|
||||
msconfig := _struct.MissionConfig{
|
||||
FlyToWaylineMode: _struct.Safely,
|
||||
FinishAction: _struct.GoHome,
|
||||
ExitOnRCLost: _struct.ExecuteLostAction,
|
||||
ExecuteRCLostAction: _struct.Hover,
|
||||
TakeOffSecurityHeight: 5,
|
||||
GlobalTransitionalSpeed: 5,
|
||||
GlobalRTHHeight: 5,
|
||||
}
|
||||
//设置公共配置
|
||||
kml.Document.MissionConfig = msconfig
|
||||
tp := _struct.WayPointTemplate{}
|
||||
|
||||
tp.TemplateType = _struct.Waypoint //航点飞行
|
||||
tp.TemplateId = 0 //模板ID * 注:在一个kmz文件内该ID唯一。建议从0开始单调连续递增。在template.kml和waylines.wpml文件中,将使用该id将模板与所生成的可执行航线进行关联。
|
||||
tp.AutoFlightSpeed = 5 //全局航线飞行速度
|
||||
|
||||
tp.WaylineCoordinateSysParam.CoordinateMode = "WGS84"
|
||||
tp.WaylineCoordinateSysParam.HeightMode = "EGM96"
|
||||
tp.WaylineCoordinateSysParam.PositioningType = "GPS"
|
||||
tp.WaylineCoordinateSysParam.SurfaceFollowModeEnable = 0
|
||||
|
||||
tp.GlobalHeight = 100
|
||||
|
||||
kml.Document.Folder = tp
|
||||
createXML(kml, "template.xml")
|
||||
}
|
||||
|
||||
func createXML(v interface{}, file string) {
|
||||
// 生成 XML
|
||||
xmlBytes, err := xml.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("生成XML失败:", err)
|
||||
return
|
||||
}
|
||||
// 将 XML 写入文件
|
||||
xmlFile, err := os.Create(file)
|
||||
if err != nil {
|
||||
fmt.Println("创建文件失败:", err)
|
||||
return
|
||||
}
|
||||
defer xmlFile.Close()
|
||||
xmlWithVersion := []byte(xml.Header + string(xmlBytes))
|
||||
_, err = xmlFile.Write(xmlWithVersion)
|
||||
if err != nil {
|
||||
fmt.Println("写入文件失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("生成的KML文件:output.kml")
|
||||
}
|
||||
|
||||
//func CreateWayPointTempalte(points []globe.Point) {
|
||||
//
|
||||
//}
|
1
third/plane/wayline/wayline.go
Normal file
1
third/plane/wayline/wayline.go
Normal file
@ -0,0 +1 @@
|
||||
package wayline
|
149
third/progress/parent.go
Normal file
149
third/progress/parent.go
Normal file
@ -0,0 +1,149 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
)
|
||||
|
||||
type Scheduler struct {
|
||||
// 总进度
|
||||
FinishedPtr int
|
||||
// 总量
|
||||
Total int
|
||||
// 实体
|
||||
WorkStatus *entity.WorkSchedule
|
||||
}
|
||||
|
||||
type Schdule map[string]Scheduler
|
||||
|
||||
// 获取指定 WorkID 的计划和总量
|
||||
func (s Schdule) Get(workID string) (int, int) {
|
||||
if scheduler, ok := s[workID]; ok {
|
||||
return scheduler.FinishedPtr, scheduler.Total
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// WorkStatusGetProgress 传入一个方阵ID 获取其所有父级的进度
|
||||
func WorkStatusGetProgress(fangzhenID string) ([]*model.WorkStatusProgressRes, error) {
|
||||
var parentList []*model.WorkStatusProgressRes
|
||||
err := dao.WorkStatus.Ctx(context.Background()).As("parent").
|
||||
Fields("parent.id, parent.work_name as name ,parent.work_id,IFNULL(SUM(child.total), 0) AS total, IFNULL(SUM(child.finished), 0) AS finished").
|
||||
LeftJoin("work_status AS child", "parent.id = child.parent").
|
||||
Where("parent.parent IS NULL").
|
||||
Where("parent.fangzhen_id", fangzhenID).
|
||||
Group("parent.id, parent.work_name, parent.total").
|
||||
Scan(&parentList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(parentList) == 0 {
|
||||
return parentList, fmt.Errorf("未找到父级计划")
|
||||
}
|
||||
|
||||
return parentList, err
|
||||
}
|
||||
|
||||
// GetProgressBySubID 获取一共子项目下所有的父级进度
|
||||
func GetProgressBySubID(subProjectID string) ([]*model.WorkStatusProgressRes, error) {
|
||||
var fangzhenList []entity.QianqiFangzhen
|
||||
if err := dao.QianqiFangzhen.Ctx(context.Background()).
|
||||
Where(dao.QianqiFangzhen.Columns().ProjectId, subProjectID).Scan(&fangzhenList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(fangzhenList) == 0 {
|
||||
return nil, fmt.Errorf("未找到方阵")
|
||||
}
|
||||
|
||||
cumulativeProjects := []*model.WorkStatusProgressRes{}
|
||||
firstTime := true
|
||||
|
||||
// 遍历项目列表
|
||||
for index := 0; index < len(fangzhenList); index++ {
|
||||
// 获取当前项目
|
||||
project := fangzhenList[index]
|
||||
|
||||
// 获取当前项目的子项目
|
||||
childProjects, err := WorkStatusGetProgress(strconv.Itoa(project.Id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果是第一次迭代,将子项目列表赋值给累积项目列表
|
||||
if firstTime {
|
||||
cumulativeProjects = childProjects
|
||||
// 更新标志变量,表示已经不是第一次迭代
|
||||
firstTime = false
|
||||
continue
|
||||
}
|
||||
|
||||
// 遍历子项目列表后叠加到累积项目列表
|
||||
for childIndex := 0; childIndex < len(childProjects); childIndex++ {
|
||||
// 获取根据索引获取当前的 cumulativeProjects
|
||||
singleChild := childProjects[childIndex]
|
||||
comulativeChild := cumulativeProjects[childIndex]
|
||||
|
||||
// 将 singleChild 的 Total 和 Finished 叠加到 comulativeChild
|
||||
comulativeChild.Total += singleChild.Total
|
||||
comulativeChild.Finished += singleChild.Finished
|
||||
}
|
||||
}
|
||||
|
||||
return cumulativeProjects, nil
|
||||
}
|
||||
|
||||
// 传入主项目ID
|
||||
func GetProgressByProjectID(projectID string) ([]*model.WorkStatusProgressRes, error) {
|
||||
subProjects := []entity.SubProject{}
|
||||
if err := dao.SubProject.Ctx(context.Background()).
|
||||
Where(dao.SubProject.Columns().ProjectId, projectID).Scan(&subProjects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cumulativeProjects := []*model.WorkStatusProgressRes{}
|
||||
firstTime := true
|
||||
|
||||
// 遍历项目列表
|
||||
for index := 0; index < len(subProjects); index++ {
|
||||
// 获取当前项目
|
||||
project := subProjects[index]
|
||||
|
||||
// 获取当前项目的子项目
|
||||
childProjects, err := GetProgressBySubID(strconv.Itoa(int(project.Id)))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果是第一次迭代,将子项目列表赋值给累积项目列表
|
||||
if firstTime {
|
||||
cumulativeProjects = childProjects
|
||||
// 更新标志变量,表示已经不是第一次迭代
|
||||
firstTime = false
|
||||
continue
|
||||
}
|
||||
|
||||
// 遍历子项目列表后叠加到累积项目列表
|
||||
for childIndex := 0; childIndex < len(childProjects); childIndex++ {
|
||||
// 获取根据索引获取当前的 cumulativeProjects
|
||||
singleChild := childProjects[childIndex]
|
||||
|
||||
if childIndex < 0 || childIndex >= len(cumulativeProjects) {
|
||||
continue
|
||||
}
|
||||
comulativeChild := cumulativeProjects[childIndex]
|
||||
|
||||
// 将 singleChild 的 Total 和 Finished 叠加到 comulativeChild
|
||||
comulativeChild.Total += singleChild.Total
|
||||
comulativeChild.Finished += singleChild.Finished
|
||||
}
|
||||
}
|
||||
|
||||
return cumulativeProjects, nil
|
||||
}
|
110
third/reminders/reminder.go
Normal file
110
third/reminders/reminder.go
Normal file
@ -0,0 +1,110 @@
|
||||
package reminders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/samber/lo"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
)
|
||||
|
||||
type Reminder struct {
|
||||
Content string `copier:"ViolationType"` // 内容
|
||||
Title string // 标题
|
||||
ReceiverID string `copier:"UserId"` // 消息接收者的 User_id
|
||||
Type int `copier:"ReminderType"` // 类型
|
||||
Status int // 状态
|
||||
ProjectID int `copier:"ProjectId"` // 项目 ID
|
||||
TargetID int `copier:"OrderId"` // 对应跳转的主键ID
|
||||
|
||||
//TargetID int `copier:"OrderId"` // 是否需要转换
|
||||
}
|
||||
|
||||
// UserIDOrderIDDel 根据userID与orderID 删除提醒
|
||||
func UserIDOrderIDDel(userId string, orderID int64) error {
|
||||
columns := dao.Reminders.Columns()
|
||||
_, err := dao.Reminders.Ctx(context.Background()).Where(columns.UserId, userId).Where(columns.OrderId, orderID).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// PublishReminder 发布提醒
|
||||
func PublishReminder(message Reminder, convertOpenID bool) error {
|
||||
if convertOpenID && HasLetter(message.ReceiverID) {
|
||||
message.ReceiverID = GetUserIDByOpenID(message.ReceiverID)
|
||||
if message.ReceiverID == "" {
|
||||
return fmt.Errorf("未找到对应的用户")
|
||||
}
|
||||
}
|
||||
|
||||
var reminder do.Reminders
|
||||
copier.Copy(&reminder, &message)
|
||||
|
||||
// 保存到数据库
|
||||
_, err := dao.Reminders.Ctx(context.Background()).Insert(&reminder)
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckInReminder 打卡提醒
|
||||
// Reminders.CheckIn 表示上班打卡
|
||||
// Reminders.CheckOut 表示下班打卡
|
||||
func CheckInReminder(checkType ReminderType, projectID int, userID, time string) error {
|
||||
// 1. 构建消息模板
|
||||
message := Reminder{
|
||||
Type: checkType, // 打卡类型
|
||||
ProjectID: projectID, // 项目 ID
|
||||
ReceiverID: userID, // 消息接收者的 sys_user.Id
|
||||
Title: time, // 时间,只需要多少分钟,如 "10","20"
|
||||
}
|
||||
|
||||
return PublishReminder(message, false)
|
||||
}
|
||||
|
||||
// hasLetter 判断字符串中是否包含字母
|
||||
func HasLetter(s string) bool {
|
||||
for _, c := range s {
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetUserIDByOpenID 通过 OpenID 获取用户对应的 sys_user.Id
|
||||
func GetUserIDByOpenID(openID string) string {
|
||||
openid, err := dao.BusConstructionUser.Ctx(context.Background()).As("bu").
|
||||
Fields("su.id").
|
||||
InnerJoin("sys_user AS su", "bu.phone = su.mobile").
|
||||
Where("bu.openid", openID).Value()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return openid.String()
|
||||
}
|
||||
|
||||
type userOpenID struct {
|
||||
OpenID string
|
||||
ID int
|
||||
}
|
||||
|
||||
// GetUserIDByOpenIDs 传入多个 OpenID,返回对应的 User.ID
|
||||
func GetUserIDByOpenIDs(openIDs []string) map[string]string {
|
||||
// SELECT su.id,bu.openid
|
||||
// FROM bus_construction_user AS bu
|
||||
// INNER JOIN sys_user AS su ON bu.phone = su.mobile
|
||||
// WHERE bu.openid in ("oLYsI4x-L6ZVKCinQ4Rw1D9XtFZ0","oLYsI49aMJHTYorhmvVuRrasPTbU")
|
||||
|
||||
var userOpenIDList []userOpenID
|
||||
err := dao.BusConstructionUser.Ctx(context.Background()).As("bu").
|
||||
Fields("su.id", "bu.openid").
|
||||
InnerJoin("sys_user AS su", "bu.phone = su.mobile").
|
||||
WhereIn("bu.openid", openIDs).Scan(&userOpenIDList)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return lo.SliceToMap(userOpenIDList, func(item userOpenID) (string, string) {
|
||||
return item.OpenID, fmt.Sprintf("%d", item.ID)
|
||||
})
|
||||
}
|
20
third/reminders/reminder_var.go
Normal file
20
third/reminders/reminder_var.go
Normal file
@ -0,0 +1,20 @@
|
||||
package reminders
|
||||
|
||||
type ReminderType = int
|
||||
|
||||
// 提醒类型
|
||||
const (
|
||||
Security ReminderType = iota // 安全
|
||||
Quality // 质量
|
||||
AI // AI
|
||||
CheckIn // 上班打卡
|
||||
CheckOut // 下班打卡
|
||||
)
|
||||
|
||||
// 提醒状态
|
||||
|
||||
const (
|
||||
Remind = iota // 提醒
|
||||
Reform // 整改
|
||||
Recheck // 复检
|
||||
)
|
77
third/richText/strip.go
Normal file
77
third/richText/strip.go
Normal file
@ -0,0 +1,77 @@
|
||||
package richtext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RemoveRichText(text string) string {
|
||||
// Regular expression to replace <br/> and <p> tags with newline
|
||||
reBr := regexp.MustCompile(`<(br|p)(\s[^>]*)?>`)
|
||||
text = reBr.ReplaceAllString(text, "\n")
|
||||
|
||||
// Add four full-width spaces after each newline
|
||||
reNewline := regexp.MustCompile(`\n`)
|
||||
text = reNewline.ReplaceAllString(text, "\n ") // 两个全角空格等于四个半角空格,即四个汉字的距离
|
||||
|
||||
// Regular expression to replace with empty string
|
||||
reNbsp := regexp.MustCompile(` `)
|
||||
text = reNbsp.ReplaceAllString(text, "")
|
||||
|
||||
// Regular expression to match other HTML tags
|
||||
re := regexp.MustCompile("<[^>]*>")
|
||||
plainText := re.ReplaceAllString(text, "")
|
||||
|
||||
plainText = strings.TrimSpace(plainText)
|
||||
// plainText = " " + plainText
|
||||
|
||||
return plainText
|
||||
}
|
||||
|
||||
func ExtractImageURLs(text string) []string {
|
||||
re := regexp.MustCompile(`<img[^>]+\bsrc=["']([^"']+)["']`)
|
||||
matches := re.FindAllStringSubmatch(text, -1)
|
||||
|
||||
var urls []string
|
||||
for _, match := range matches {
|
||||
urls = append(urls, match[1])
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
func ConvertToRichText(plainText string, imageURLs []string) string {
|
||||
// 将纯文本分割成段落
|
||||
paragraphs := strings.Split(plainText, "\n")
|
||||
|
||||
// 创建一个StringBuilder来构建富文本
|
||||
var richText strings.Builder
|
||||
|
||||
// 将每个段落包装在<p>标签中
|
||||
for _, paragraph := range paragraphs {
|
||||
richText.WriteString("<p>")
|
||||
richText.WriteString(paragraph)
|
||||
richText.WriteString("</p>")
|
||||
}
|
||||
|
||||
// 将每个图片URL包装在<img>标签中,并附加到富文本中
|
||||
for _, imageURL := range imageURLs {
|
||||
richText.WriteString("<img src=\"")
|
||||
richText.WriteString(imageURL)
|
||||
richText.WriteString("\" />")
|
||||
}
|
||||
|
||||
// 返回富文本字符串
|
||||
return richText.String()
|
||||
}
|
||||
|
||||
// 传入一个字符串数组,将其包装为一个富文本图片标签
|
||||
func ConvertImageURLsToRichText(imageURLs []string) string {
|
||||
var richText strings.Builder
|
||||
for _, imageURL := range imageURLs {
|
||||
richText.WriteString(fmt.Sprintf(`<p><img src="%s" /></p>`, imageURL))
|
||||
}
|
||||
|
||||
return richText.String()
|
||||
}
|
112
third/schduler/schduler.go
Normal file
112
third/schduler/schduler.go
Normal file
@ -0,0 +1,112 @@
|
||||
package schduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
)
|
||||
|
||||
type scheduler struct {
|
||||
// 是否延期
|
||||
IsDelay int
|
||||
// 总进度
|
||||
FinishedPtr int
|
||||
// 总量
|
||||
Total int
|
||||
// 实体
|
||||
WorkStatus *entity.WorkSchedule
|
||||
}
|
||||
|
||||
type Schdule map[string]scheduler
|
||||
|
||||
// New 初始化计划
|
||||
func New(ScheduleData []entity.WorkSchedule) (Schdule, error) {
|
||||
ctx := context.Background()
|
||||
scheduleMap := make(Schdule)
|
||||
|
||||
for _, task := range ScheduleData {
|
||||
// 计划进度
|
||||
expectedProgress := task.PlanNum
|
||||
// 实际完成度
|
||||
actualProgress := task.FinishedNum
|
||||
|
||||
// 判断是否延期
|
||||
isDelayed := isDelay(task.EndAt.Format("Y-m-d"), expectedProgress, actualProgress)
|
||||
|
||||
// 如果 WorkID 不存在
|
||||
if _, exists := scheduleMap[task.WorkId]; !exists {
|
||||
newScheduler := scheduler{
|
||||
IsDelay: isDelayed,
|
||||
WorkStatus: &task,
|
||||
Total: expectedProgress,
|
||||
FinishedPtr: actualProgress,
|
||||
}
|
||||
|
||||
scheduleMap[task.WorkId] = newScheduler // 添加到 map 中
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
existingScheduler := scheduleMap[task.WorkId] // 存在则取出 Value
|
||||
|
||||
// 判断延期
|
||||
if existingScheduler.IsDelay != 1 { // 为了防止在存在多个计划的情况下,未延期的计划覆盖了已经延期的状态。
|
||||
// 修改 Work_Status 中 is_delay 修改为延期状态 1
|
||||
_, err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, task.WorkId).
|
||||
Data(g.Map{dao.WorkStatus.Columns().IsDelay: 1}).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingScheduler.IsDelay = isDelayed // 修改 map 中的状态
|
||||
}
|
||||
|
||||
// 多个计划的情况下叠加数据
|
||||
existingScheduler.Total += expectedProgress // 计划总量叠加
|
||||
existingScheduler.FinishedPtr += actualProgress // 实际进度叠加
|
||||
scheduleMap[task.WorkId] = existingScheduler
|
||||
}
|
||||
|
||||
return scheduleMap, nil
|
||||
}
|
||||
|
||||
// 获取指定 WorkID 的计划和总量
|
||||
func (s Schdule) Get(workID string) (finishedPtr, total int) {
|
||||
if sch, ok := s[workID]; ok {
|
||||
return sch.FinishedPtr, sch.Total
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// GetStatus 根据 WorkID 获取工作的状态,判断是否延期
|
||||
// s[workID] 中记录了该 WorkID 的计划总量和实际进度
|
||||
// total 是计划的总量
|
||||
// 如果实际进度(FinishedPtr)小于计划总量,则判断工作为延期(返回1)
|
||||
// 如果实际进度达到或超过计划总量,或者 WorkID 不存在于计划中,判断工作未延期(返回0)
|
||||
func (s Schdule) GetStatus(workID string, total int) int {
|
||||
if sch, ok := s[workID]; ok {
|
||||
if sch.FinishedPtr < total {
|
||||
return sch.IsDelay
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// isDelay 如果实际完成数量小于计划数量,且当前时间大于结束时间,则判断为延期
|
||||
// endAt 结束时间
|
||||
// planNum 计划数量
|
||||
// finishedNum 实际数量
|
||||
func isDelay(endAt string, planNum, finishedNum int) int {
|
||||
// 当天时间
|
||||
nowTime := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
||||
|
||||
// 如果实际完成数量小于计划数量,且当前时间大于结束时间,则判断为延期
|
||||
if finishedNum < planNum && nowTime > endAt {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
114
third/securityRequest.go
Normal file
114
third/securityRequest.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Package utility 安全请求,思路来源https://blog.csdn.net/David_jiahuan/article/details/106391956
|
||||
// @Author 铁憨憨[cory] 2024/9/11 9:38:00
|
||||
package utility
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 签名验证中间件
|
||||
func SignatureMiddleware(r *ghttp.Request) {
|
||||
if r.URL.Path == "/zm/api/wxApplet/wxApplet/appPort/releaseAppVersion" {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
// 获取请求中的参数
|
||||
signature := gconv.String(r.Header.Get("sign"))
|
||||
timestamp := gconv.Int64(r.Header.Get("timestamp"))
|
||||
nonce := gconv.String(r.Header.Get("nonce"))
|
||||
if signature == "" || timestamp == 0 || nonce == "" {
|
||||
r.Response.WriteJson(g.Map{"error": "无效验证"})
|
||||
r.ExitAll()
|
||||
return
|
||||
}
|
||||
//验证时间戳
|
||||
if !validateTimestamp(timestamp) {
|
||||
r.Response.WriteJson(g.Map{"error": "请求过期"})
|
||||
r.ExitAll()
|
||||
return
|
||||
}
|
||||
// 验证 nonce 防止重放攻击
|
||||
if !validateNonce(nonce) {
|
||||
r.Response.WriteJson(g.Map{"error": "无效请求:检测到重放"})
|
||||
r.ExitAll()
|
||||
return
|
||||
}
|
||||
serverSignature := generateServerSignature(strconv.FormatInt(timestamp, 10), nonce)
|
||||
// 验证签名
|
||||
if signature != serverSignature {
|
||||
r.Response.WriteJson(g.Map{"error": "签名错误"})
|
||||
r.ExitAll()
|
||||
return
|
||||
}
|
||||
//签名验证通过
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func validateTimestamp(unixTimestamp int64) bool {
|
||||
// 获取当前时间
|
||||
now := time.Now().UnixMilli()
|
||||
result := subtractBigFromInt64(now, unixTimestamp)
|
||||
// 将整数 1500 转换为 *big.Int 类型
|
||||
big1500 := big.NewInt(30000)
|
||||
// 验证时间差是否在 1500 毫秒内
|
||||
if result.Cmp(big1500) == -1 && result.Cmp(big.NewInt(0)) >= 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func subtractBigFromInt64(currentMillis int64, unixTimestamp int64) *big.Int {
|
||||
// 将两个 int64 类型的值转换为 *big.Int 类型
|
||||
bigCurrent := big.NewInt(currentMillis)
|
||||
bigUnixTimestamp := big.NewInt(unixTimestamp)
|
||||
// 计算差值
|
||||
result := big.NewInt(0).Sub(bigCurrent, bigUnixTimestamp)
|
||||
// 求差值的绝对值
|
||||
return result.Abs(result)
|
||||
}
|
||||
|
||||
// 验证 nonce
|
||||
func validateNonce(nonce string) bool {
|
||||
nonceKey := "reset" + nonce
|
||||
// 使用 Redis 来存储 nonce,防止重放
|
||||
redisClient := g.Redis()
|
||||
ctx := gctx.New()
|
||||
exists, err := redisClient.Exists(ctx, nonceKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if exists > 0 {
|
||||
return false // nonce 已经存在,说明是重放请求
|
||||
}
|
||||
// nonce 不存在,存储到 Redis 并设置60秒过期时间
|
||||
err = redisClient.SetEX(ctx, nonceKey, nonce, 60)
|
||||
if err != nil {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 生成服务端签名
|
||||
func generateServerSignature(timestamp, nonce string) string {
|
||||
sum := md5.Sum([]byte("coyrOrtiehanhan1223202409111457"))
|
||||
md5Sum := hex.EncodeToString(sum[:])
|
||||
// 拼接时间戳、nonce 和 secretKey
|
||||
signStr := md5Sum + timestamp + nonce
|
||||
// 对拼接后的字符串进行 MD5 加密
|
||||
md5Hash := md5.Sum([]byte(signStr))
|
||||
md5Hex := hex.EncodeToString(md5Hash[:])
|
||||
// 对 MD5 结果进行 SHA-256 加密
|
||||
sha256Hash := sha256.Sum256([]byte(md5Hex))
|
||||
sha256Hex := hex.EncodeToString(sha256Hash[:])
|
||||
return sha256Hex
|
||||
}
|
88
third/solaranalyzer/analyzer.go
Normal file
88
third/solaranalyzer/analyzer.go
Normal file
@ -0,0 +1,88 @@
|
||||
package solaranalyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
drillingHole = "hole" // 钻孔
|
||||
pillar = "pile" // 桩基
|
||||
bracket = "shelves" // 支架
|
||||
solarPanel = "pho" // 光伏板
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
Points []XYPosition // 光伏板的坐标点
|
||||
ID int // pv_module 表中的自增ID
|
||||
}
|
||||
|
||||
type XYPosition struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
// getSolarPanelRanges 解析光伏板 JSON
|
||||
func getSolarPanelRanges(file string) []Point {
|
||||
var points []Point
|
||||
|
||||
gjson.Parse(file).ForEach(func(_, record gjson.Result) bool {
|
||||
record.Get("detail").ForEach(func(_, detail gjson.Result) bool {
|
||||
id := record.Get("id").Int()
|
||||
|
||||
var longitudeAndLatitude []XYPosition
|
||||
gjson.Get(detail.String(), "positions").ForEach(func(_, position gjson.Result) bool {
|
||||
longitude := position.Get("lng").Float()
|
||||
latitude := position.Get("lat").Float()
|
||||
|
||||
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{X: longitude, Y: latitude})
|
||||
return true
|
||||
})
|
||||
|
||||
points = append(points, Point{ID: int(id), Points: longitudeAndLatitude})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return points
|
||||
}
|
||||
|
||||
// convertToLongitudeAndLatitude 根据 tif 文件将坐标转换为经纬度
|
||||
func convertToLongitudeAndLatitude(tifFilePath string, coordinatePoints []XYPosition) []XYPosition {
|
||||
positionsJSON, err := json.Marshal(coordinatePoints)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
convertPath, err := filepath.Abs("./convert")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
output, err := exec.Command(convertPath, filepath.Clean(tifFilePath), string(positionsJSON)).CombinedOutput()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outputLines := strings.Split(string(output), "\n")
|
||||
lastLine := outputLines[len(outputLines)-2] // 获取最后一行
|
||||
|
||||
// 解析二进制返回的 JSON, 并将其转换为 XYPosition 结构
|
||||
var longitudeAndLatitude []XYPosition
|
||||
gjson.Get(lastLine, "data").ForEach(func(_, position gjson.Result) bool {
|
||||
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{
|
||||
X: position.Get("y").Float(),
|
||||
Y: position.Get("x").Float(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
return longitudeAndLatitude
|
||||
}
|
57
third/solaranalyzer/bracket.go
Normal file
57
third/solaranalyzer/bracket.go
Normal file
@ -0,0 +1,57 @@
|
||||
package solaranalyzer
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tomchavakis/geojson/geometry"
|
||||
)
|
||||
|
||||
const (
|
||||
earthRadius = 6371000 // 地球半径(米)
|
||||
|
||||
radiusMeters = 0.07 // 半径(7厘米)
|
||||
|
||||
numPoints = 8 // 点的数量
|
||||
)
|
||||
|
||||
type GeoPoint struct {
|
||||
ID int
|
||||
Coordinates Coordinates
|
||||
}
|
||||
|
||||
type Coordinates struct {
|
||||
Lng float64
|
||||
Lat float64
|
||||
Alt float64
|
||||
}
|
||||
|
||||
// ExpandToCircle 用于桩点和支架
|
||||
// 以一个点为中心,扩展出一个圆形
|
||||
func (p Coordinates) ExpandToCircle() geometry.Polygon {
|
||||
circle := make([]Coordinates, numPoints)
|
||||
|
||||
for i := 0; i < numPoints; i++ {
|
||||
angle := 2 * math.Pi * float64(i) / float64(numPoints)
|
||||
|
||||
// 计算偏移量(米)
|
||||
dx := radiusMeters * math.Cos(angle)
|
||||
dy := radiusMeters * math.Sin(angle)
|
||||
|
||||
// 将米转换为经纬度
|
||||
dLng := dx / (earthRadius * math.Cos(p.Lat*math.Pi/180)) * 180 / math.Pi
|
||||
dLat := dy / earthRadius * 180 / math.Pi
|
||||
|
||||
circle[i] = Coordinates{
|
||||
Lng: p.Lng + dLng,
|
||||
Lat: p.Lat + dLat,
|
||||
Alt: p.Alt,
|
||||
}
|
||||
}
|
||||
|
||||
var coords []geometry.Point
|
||||
for _, point := range circle {
|
||||
coords = append(coords, geometry.Point{Lat: point.Lat, Lng: point.Lng})
|
||||
}
|
||||
|
||||
return geometry.Polygon{Coordinates: []geometry.LineString{{Coordinates: coords}}}
|
||||
}
|
695
third/solaranalyzer/sparta.go
Normal file
695
third/solaranalyzer/sparta.go
Normal file
@ -0,0 +1,695 @@
|
||||
package solaranalyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/samber/lo"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
|
||||
"github.com/tomchavakis/geojson/geometry"
|
||||
"github.com/tomchavakis/turf-go"
|
||||
)
|
||||
|
||||
type AIResult struct {
|
||||
SolarPanels []XYPosition // 光伏板的坐标点
|
||||
Brackets []XYPosition // 支架的坐标点
|
||||
Pillars []XYPosition // 立柱的坐标点
|
||||
Holes []XYPosition // 钻孔的坐标点
|
||||
}
|
||||
|
||||
// ToGeoPoints 将坐标转换为经纬度坐标
|
||||
func (r *AIResult) ToGeoPoints(tifPath string) *AIResult {
|
||||
r.SolarPanels = convertToLongitudeAndLatitude(tifPath, r.SolarPanels) // 光伏板
|
||||
r.Brackets = convertToLongitudeAndLatitude(tifPath, r.Brackets) // 支架
|
||||
r.Pillars = convertToLongitudeAndLatitude(tifPath, r.Pillars) // 立柱
|
||||
r.Holes = convertToLongitudeAndLatitude(tifPath, r.Holes) // 钻孔
|
||||
return r
|
||||
}
|
||||
|
||||
// IsCircleContainsPoint 判断圆形是否包含点,返回桩点的主键ID
|
||||
func (r *AIResult) IsCircleContainsPoint(m map[string]map[string]GeoPoint, usePillars string) []int {
|
||||
var ids []int
|
||||
|
||||
var elements []XYPosition
|
||||
switch usePillars {
|
||||
case drillingHole:
|
||||
elements = r.Holes
|
||||
case pillar:
|
||||
elements = r.Pillars
|
||||
case bracket:
|
||||
elements = r.Brackets
|
||||
}
|
||||
|
||||
for _, aiPillar := range elements {
|
||||
// AI 识别点
|
||||
aiPoint := geometry.Point{Lng: aiPillar.X, Lat: aiPillar.Y}
|
||||
|
||||
// 方阵层
|
||||
for _, points := range m {
|
||||
// 立柱层
|
||||
for _, dbPillar := range points {
|
||||
circle := dbPillar.Coordinates.ExpandToCircle()
|
||||
|
||||
if ok, err := turf.PointInPolygon(aiPoint, circle); ok && err == nil {
|
||||
ids = append(ids, dbPillar.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetFromDB 从数据库中获取光伏板、支架、桩点的数据
|
||||
func (r *AIResult) GetFromDB(projectID string) ([]Point, map[string]map[string]GeoPoint) {
|
||||
// 光伏板、支架
|
||||
var solarPanels []Point
|
||||
var pillars map[string]map[string]GeoPoint
|
||||
|
||||
if len(r.SolarPanels) != 0 {
|
||||
if points, err := getSolarPanelCenters(projectID); err == nil {
|
||||
solarPanels = points
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Pillars) != 0 {
|
||||
pillars = getPillars(projectID)
|
||||
}
|
||||
|
||||
return solarPanels, pillars
|
||||
}
|
||||
|
||||
// 传入 AI 结果
|
||||
// 1. 将其周围的组串,全部标记为已完成
|
||||
// 2. 根据其 【方阵ID】【Type = 13】更新其在 work_status 中的总数 ok
|
||||
// 3. 根据当前的 Name Etc: G1.123.1.1 将 pv_module 中的 G1.123.1 状态修改为已完成
|
||||
// 4. 记录该数据由 AI 识别。
|
||||
func (r *AIResult) Run(projectID string, id int) {
|
||||
// 获取光伏板和支架,桩点的数据
|
||||
panels, brackets := r.GetFromDB(projectID)
|
||||
fmt.Println("(内容)光伏板和支架: ", panels)
|
||||
fmt.Println("(内容)桩点: ", brackets)
|
||||
|
||||
// 预处理光伏板为多边形
|
||||
polygons := make([]geometry.Polygon, len(panels))
|
||||
for i, rect := range panels {
|
||||
coords := make([]geometry.Point, len(rect.Points))
|
||||
for j, p := range rect.Points {
|
||||
coords[j] = geometry.Point{Lng: p.X, Lat: p.Y}
|
||||
}
|
||||
polygons[i] = geometry.Polygon{Coordinates: []geometry.LineString{{Coordinates: coords}}}
|
||||
}
|
||||
fmt.Println("(内容)预处理光伏板为多边形: ", polygons)
|
||||
fmt.Println("(内容)光伏板坐标后点: ", r.SolarPanels)
|
||||
// 光伏板的主键ID
|
||||
var matchedRectangles []int
|
||||
// 匹配AI识别的光伏板中心点
|
||||
for _, point := range r.SolarPanels {
|
||||
for i, polygon := range polygons {
|
||||
if ok, err := turf.PointInPolygon(geometry.Point{Lng: point.X, Lat: point.Y}, polygon); err == nil && ok {
|
||||
matchedRectangles = append(matchedRectangles, panels[i].ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := dao.ManageTaskResult.Ctx(context.Background()).Insert(
|
||||
lo.Map(matchedRectangles, func(v int, _ int) do.ManageTaskResult {
|
||||
return do.ManageTaskResult{
|
||||
TaskId: id,
|
||||
PvId: v,
|
||||
}
|
||||
}))
|
||||
liberr.ErrIsNil(context.Background(), err)
|
||||
|
||||
if len(matchedRectangles) > 0 { // TODO: 去除写入数据库的逻辑
|
||||
// if err := ProcessMatchedRectangles(matchedRectangles); err != nil {
|
||||
// g.Log("uav").Error(context.Background(), "更新匹配到的光伏板失败: ", err)
|
||||
// }
|
||||
}
|
||||
|
||||
// 更新钻孔
|
||||
if len(r.Holes) > 0 {
|
||||
ids := r.IsCircleContainsPoint(brackets, drillingHole)
|
||||
if len(ids) > 0 {
|
||||
if err := processPillarIDs(ids, 12, id); err != nil {
|
||||
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新支架和桩点
|
||||
if len(r.Pillars) > 0 {
|
||||
ids := r.IsCircleContainsPoint(brackets, pillar)
|
||||
if len(ids) > 0 {
|
||||
if err := processPillarIDs(ids, 13, id); err != nil {
|
||||
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Brackets) > 0 {
|
||||
ids := r.IsCircleContainsPoint(brackets, bracket)
|
||||
if len(ids) > 0 {
|
||||
if err := processPillarIDs(ids, 14, id); err != nil {
|
||||
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通过主键ID,可以从 qianqi_guangfuban_ids_zhijia 表中查询到对应的 “方阵ID” 和 "子项目ID"
|
||||
// 将得到的结果分组,第一层为 方阵ID,第二层 G01.12.18,第三层数组,[ G01.12.18.1,G01.12.18.2,G01.12.18.3 ]
|
||||
// 如果第三层中有数据,则从数据库中,向前后查询相邻组串,并将其组串修改为已完成,同时取出第二层和方阵ID,用于更新 pv_module 和 work_status 表中的数据。
|
||||
func processPillarIDs(ids []int, t, id int) error {
|
||||
return nil
|
||||
|
||||
// 查询 qianqi_guangfuban_ids_zhijia 表
|
||||
var zhijias []entity.QianqiGuangfubanIdsZhijia
|
||||
if err := dao.QianqiGuangfubanIdsZhijia.Ctx(context.Background()).
|
||||
Fields("id,fangzhen_id,sub_projectid,name").
|
||||
WhereIn(dao.QianqiGuangfubanIdsZhijia.Columns().Id, ids).
|
||||
Scan(&zhijias); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 第一层方阵ID, 第二层支架名称, 第三层支架信息
|
||||
realGeoPoint := make(map[string]map[string][]entity.QianqiGuangfubanIdsZhijia)
|
||||
for _, zhijia := range zhijias {
|
||||
fangzhenID := zhijia.FangzhenId // 方阵ID
|
||||
zhijiaName := zhijia.Name[:strings.LastIndex(zhijia.Name, ".")] // 支架名称
|
||||
|
||||
if _, ok := realGeoPoint[fangzhenID]; !ok {
|
||||
realGeoPoint[fangzhenID] = make(map[string][]entity.QianqiGuangfubanIdsZhijia)
|
||||
}
|
||||
|
||||
realGeoPoint[fangzhenID][zhijiaName] = append(realGeoPoint[fangzhenID][zhijiaName], zhijia)
|
||||
}
|
||||
|
||||
// 查询支架及其相邻的组串
|
||||
for _, geoPoints := range realGeoPoint {
|
||||
for pointKey, pointList := range geoPoints {
|
||||
if len(pointList) > 1 {
|
||||
firstPoint := pointList[0]
|
||||
|
||||
simulationID := firstPoint.FangzhenId
|
||||
namePattern := firstPoint.Name[:strings.LastIndex(firstPoint.Name, ".")] + ".%"
|
||||
|
||||
var updatedPoints []entity.QianqiGuangfubanIdsZhijia
|
||||
if err := dao.QianqiGuangfubanIdsZhijia.Ctx(context.Background()).Where(dao.QianqiGuangfubanIdsZhijia.Columns().FangzhenId, simulationID).
|
||||
Where(dao.QianqiGuangfubanIdsZhijia.Columns().Name+" LIKE ?", namePattern).
|
||||
Scan(&updatedPoints); err == nil && len(updatedPoints) > 0 {
|
||||
realGeoPoint[simulationID][pointKey] = updatedPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据库状态
|
||||
for _, geoPoints := range realGeoPoint {
|
||||
for key, pointList := range geoPoints {
|
||||
// 取出所有的ID
|
||||
primaryIDs := lo.Map(pointList, func(v entity.QianqiGuangfubanIdsZhijia, _ int) int {
|
||||
return v.Id
|
||||
})
|
||||
|
||||
g.Try(context.Background(), func(ctx context.Context) {
|
||||
// 根据主键修改为已完成
|
||||
// 修改组串的状态
|
||||
// G01.12.18.1 -- G01.12.18.2 -- G01.12.18.3
|
||||
_, err := dao.QianqiGuangfubanIdsZhijia.Ctx(ctx).WhereIn(dao.QianqiGuangfubanIdsZhijia.Columns().Id, primaryIDs).
|
||||
Data(g.Map{
|
||||
dao.QianqiGuangfubanIdsZhijia.Columns().Status: 2,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
fangzhenID := pointList[0].FangzhenId
|
||||
|
||||
// 根据名字从 pv_module 中获取主键id
|
||||
result, err := dao.PvModule.Ctx(ctx).Fields("id").Where(dao.PvModule.Columns().Name, key).
|
||||
Where(dao.PvModule.Columns().FangzhenId, fangzhenID).
|
||||
Where(dao.PvModule.Columns().Type, t).One()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
// 获取主键ID
|
||||
keyID := result.Map()["id"].(int)
|
||||
dao.ManageTaskResult.Ctx(ctx).Data(do.ManageTaskResult{
|
||||
TaskId: id,
|
||||
PvId: keyID,
|
||||
}).Insert() // 记录
|
||||
|
||||
// 更新 PVModule 表中的状态
|
||||
// G01.12.18
|
||||
lastID, err := dao.PvModule.Ctx(ctx).WhereIn(dao.PvModule.Columns().Id, keyID).
|
||||
Where(dao.PvModule.Columns().FangzhenId, fangzhenID).
|
||||
Where(dao.PvModule.Columns().Type, t).
|
||||
Data(g.Map{
|
||||
dao.PvModule.Columns().Status: 2,
|
||||
}).Update()
|
||||
|
||||
// 获取影响行数并更新 work_status 表
|
||||
rows, err := lastID.RowsAffected()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
if rows > 0 {
|
||||
_, err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangzhenID).
|
||||
Where(dao.WorkStatus.Columns().Type, t).
|
||||
Data(g.Map{
|
||||
dao.WorkStatus.Columns().Finished: gdb.Raw(
|
||||
"finished + 1",
|
||||
),
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
}
|
||||
|
||||
// 根据 类型和方阵ID 获取对应的 work_id
|
||||
dd := entity.WorkStatus{}
|
||||
err = dao.WorkStatus.Ctx(ctx).Fields("work_id").Where(dao.WorkStatus.Columns().FangzhenId, fangzhenID).
|
||||
Where(dao.WorkStatus.Columns().Type, t).Scan(&dd)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
work_id := dd.WorkId
|
||||
err = UpdateWorkSchedule(work_id)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理匹配到的光伏板
|
||||
func ProcessMatchedRectangles(matchedRectangles []int) error {
|
||||
if len(matchedRectangles) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return g.Try(context.Background(), func(ctx context.Context) {
|
||||
timeNow := time.Now().Format("2006-01-02")
|
||||
// 更新光伏板状态
|
||||
_, err := dao.PvModule.Ctx(ctx).
|
||||
WhereIn(dao.PvModule.Columns().Id, matchedRectangles).
|
||||
Data(g.Map{
|
||||
dao.PvModule.Columns().Status: 3,
|
||||
dao.PvModule.Columns().DoneTime: timeNow,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err, "更新光伏板状态失败")
|
||||
|
||||
// 获取所有的 work_id
|
||||
pvModules := []entity.PvModule{}
|
||||
err = dao.PvModule.Ctx(ctx).Fields("work_id").WhereIn("id", matchedRectangles).Scan(&pvModules)
|
||||
liberr.ErrIsNil(ctx, err, "查询 pv_module 表失败")
|
||||
|
||||
// 提取所有的 work_id
|
||||
workIds := lo.Map(pvModules, func(module entity.PvModule, _ int) string {
|
||||
return module.WorkId
|
||||
})
|
||||
|
||||
// 查询 work_status 表
|
||||
var workStatus []entity.WorkStatus
|
||||
err = dao.WorkStatus.Ctx(ctx).WhereIn(dao.WorkStatus.Columns().WorkId, workIds).Scan(&workStatus)
|
||||
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
|
||||
|
||||
statusMap := make(map[string]entity.WorkStatus)
|
||||
for _, status := range workStatus {
|
||||
statusMap[status.WorkId] = status
|
||||
}
|
||||
|
||||
// 遍历 pv_module 表,根据 work_id 更新 work_status 表
|
||||
for _, pvModule := range pvModules {
|
||||
if _, ok := statusMap[pvModule.WorkId]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. 更新计划表 work_schdule 表中计划
|
||||
// timeNow := time.Now().Format("2006-01-02")
|
||||
|
||||
// 根据 work_id 查询所有的计划
|
||||
var workSchedules []entity.WorkSchedule
|
||||
err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, pvModule.WorkId).Scan(&workSchedules)
|
||||
if len(workSchedules) == 0 {
|
||||
liberr.ErrIsNil(ctx, errors.New("需要前往进度计划对应的方阵中,设置计划日期,随后再来实现进度复查功能!"))
|
||||
return
|
||||
}
|
||||
liberr.ErrIsNil(ctx, err, "查询 work_schedule 表失败")
|
||||
|
||||
// 根据计划起始和结束时间,当天位于哪个计划中
|
||||
for _, schedule := range workSchedules {
|
||||
startAt := schedule.StartAt.Format("Y-m-d")
|
||||
endAt := schedule.EndAt.Format("Y-m-d")
|
||||
|
||||
if timeNow >= startAt && timeNow <= endAt {
|
||||
// 反序列化 Detail
|
||||
var details []struct {
|
||||
Date string `json:"date"`
|
||||
PlanNum int `json:"planNum"`
|
||||
FinishedNum int `json:"finishedNum"` // 手动填充的完成数量
|
||||
AutoFill int `json:"autoFill"` // 是否自动填充
|
||||
}
|
||||
if err := json.Unmarshal([]byte(schedule.Detail), &details); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 遍历 details,找到当天的计划
|
||||
for i := range details {
|
||||
if details[i].Date == timeNow {
|
||||
details[i].AutoFill++ // 标记为 AI 识别
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化 details
|
||||
detailBytes, err := json.Marshal(details)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新 work_schedule 表
|
||||
_, err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, schedule.WorkId).
|
||||
Data(g.Map{
|
||||
dao.WorkSchedule.Columns().Detail: string(detailBytes),
|
||||
dao.WorkSchedule.Columns().FinishedNum: schedule.FinishedNum + 1,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err, "更新 work_schedule 表失败")
|
||||
|
||||
var currentStatus entity.WorkStatus
|
||||
err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, schedule.WorkId).Scan(¤tStatus)
|
||||
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
|
||||
|
||||
if currentStatus.Finished+1 <= currentStatus.Total {
|
||||
finished := currentStatus.Finished + 1
|
||||
|
||||
_, err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, currentStatus.WorkId).
|
||||
Where(dao.WorkStatus.Columns().Type, currentStatus.Type).
|
||||
Data(g.Map{
|
||||
dao.WorkStatus.Columns().Finished: finished,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err, "更新 work_status 表失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 传入一个 workID 修改其对应的计划数据
|
||||
func UpdateWorkSchedule(workID string) error {
|
||||
return g.Try(context.Background(), func(ctx context.Context) {
|
||||
// 查询 work_status 表
|
||||
var workStatus []entity.WorkStatus
|
||||
err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, workID).Scan(&workStatus)
|
||||
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
|
||||
|
||||
// 遍历 work_status 表,如果自增后的完成量小于等于总数,则将完成量加一
|
||||
for _, status := range workStatus {
|
||||
if status.Finished+1 <= status.Total {
|
||||
finished := status.Finished + 1
|
||||
|
||||
_, err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, status.WorkId).
|
||||
Data(g.Map{
|
||||
dao.WorkStatus.Columns().Finished: finished,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err, "更新 work_status 表失败")
|
||||
}
|
||||
|
||||
// 2. 更新计划表 work_schdule 表中计划
|
||||
timeNow := time.Now().Format("2006-01-02")
|
||||
|
||||
// 根据 work_id 查询所有的计划
|
||||
var workSchedules []entity.WorkSchedule
|
||||
err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, status.WorkId).Scan(&workSchedules)
|
||||
liberr.ErrIsNil(ctx, err, "查询 work_schedule 表失败")
|
||||
|
||||
// 会出现多个计划的情况下,需要根据计划的起始和结束时间来确定具体的计划
|
||||
for _, schedule := range workSchedules {
|
||||
startAt := schedule.StartAt.Format("Y-m-d")
|
||||
endAt := schedule.EndAt.Format("Y-m-d")
|
||||
|
||||
if timeNow >= startAt && timeNow <= endAt {
|
||||
// 反序列化 Detail
|
||||
var details []struct {
|
||||
Date string `json:"date"`
|
||||
PlanNum int `json:"planNum"`
|
||||
FinishedNum int `json:"finishedNum"` // 手动填充的完成数量
|
||||
AutoFill int `json:"autoFill"` // 是否自动填充
|
||||
}
|
||||
if err := json.Unmarshal([]byte(schedule.Detail), &details); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 遍历 details,找到当天的计划
|
||||
for i := range details {
|
||||
if details[i].Date == timeNow {
|
||||
// 为自动填充的数量加一
|
||||
details[i].AutoFill++
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化 details
|
||||
detailBytes, err := json.Marshal(details)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新 work_schedule 表
|
||||
_, err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, status.WorkId).
|
||||
Where(dao.WorkSchedule.Columns().Id, schedule.Id).
|
||||
Data(g.Map{
|
||||
dao.WorkSchedule.Columns().Detail: string(detailBytes),
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err, "更新 work_schedule 表失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// parseAIResult 解析识别的结果,提取出光伏板、支架和立柱的中心点
|
||||
// 该函数解析的 JSON 格式如下:
|
||||
//
|
||||
// {
|
||||
// "targets": [
|
||||
// {
|
||||
// "type": "pho", // 光伏板
|
||||
// "size": [100, 100],
|
||||
// "leftTopPoint": [10, 10]
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
func ParseAIResul2(fileContent string) *AIResult {
|
||||
// 钻孔、桩基、支架、光伏板
|
||||
var solarPanels, brackets, pillars, holes []XYPosition
|
||||
|
||||
gjson.Get(fileContent, "targets").ForEach(func(_, target gjson.Result) bool {
|
||||
targetType := target.Get("type").String()
|
||||
|
||||
if targetType != solarPanel && targetType != bracket && targetType != pillar && targetType != drillingHole {
|
||||
return true
|
||||
}
|
||||
|
||||
size := lo.FilterMap(target.Get("size").Array(), func(size gjson.Result, _ int) (int, bool) {
|
||||
return int(size.Int()), true
|
||||
})
|
||||
|
||||
leftTopPoint := lo.FilterMap(target.Get("leftTopPoint").Array(), func(point gjson.Result, _ int) (int, bool) {
|
||||
return int(point.Int()), true
|
||||
})
|
||||
|
||||
// 获取中心点
|
||||
centroidX := float64(leftTopPoint[0]) + float64(size[0])/2
|
||||
centroidY := float64(leftTopPoint[1]) + float64(size[1])/2
|
||||
position := XYPosition{X: centroidX, Y: centroidY}
|
||||
|
||||
switch targetType {
|
||||
case solarPanel:
|
||||
solarPanels = append(solarPanels, position)
|
||||
case bracket:
|
||||
brackets = append(brackets, position)
|
||||
case pillar:
|
||||
pillars = append(pillars, position)
|
||||
case drillingHole:
|
||||
holes = append(holes, position)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
marshal1, _ := json.Marshal(solarPanels)
|
||||
marshal2, _ := json.Marshal(brackets)
|
||||
marshal3, _ := json.Marshal(pillars)
|
||||
marshal4, _ := json.Marshal(holes)
|
||||
fmt.Println("光伏板的坐标点", string(marshal1))
|
||||
fmt.Println("支架的坐标点", string(marshal2))
|
||||
fmt.Println("立柱的坐标点", string(marshal3))
|
||||
fmt.Println("钻孔的坐标点", string(marshal4))
|
||||
|
||||
return &AIResult{
|
||||
SolarPanels: solarPanels,
|
||||
Brackets: brackets,
|
||||
Pillars: pillars,
|
||||
Holes: holes,
|
||||
}
|
||||
}
|
||||
|
||||
// getSolarPanelCenters 获取数据库中光伏板的中心点
|
||||
func getSolarPanelCenters(projectID string) ([]Point, error) {
|
||||
// 获取所有的子项目
|
||||
subProjectQuery := g.Model("sub_project").Fields("id").Where("project_id", projectID)
|
||||
|
||||
var pvModules []entity.PvModule
|
||||
if err := dao.PvModule.Ctx(context.Background()).
|
||||
WhereIn(dao.PvModule.Columns().SubProjectid, subProjectQuery).
|
||||
Where(dao.PvModule.Columns().Type, 15).
|
||||
WhereNot(dao.PvModule.Columns().Status, 2).
|
||||
WhereNot(dao.PvModule.Columns().Status, 3).
|
||||
Scan(&pvModules); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var points []Point
|
||||
|
||||
// tojson
|
||||
dd, _ := json.Marshal(pvModules)
|
||||
gjson.Parse(string(dd)).ForEach(func(_, record gjson.Result) bool {
|
||||
record.Get("detail").ForEach(func(_, detail gjson.Result) bool {
|
||||
id := record.Get("id").Int()
|
||||
|
||||
var longitudeAndLatitude []XYPosition
|
||||
gjson.Get(detail.String(), "positions").ForEach(func(_, position gjson.Result) bool {
|
||||
longitude := position.Get("lng").Float()
|
||||
latitude := position.Get("lat").Float()
|
||||
|
||||
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{X: longitude, Y: latitude})
|
||||
return true
|
||||
})
|
||||
|
||||
points = append(points, Point{ID: int(id), Points: longitudeAndLatitude})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return points, nil
|
||||
}
|
||||
|
||||
func getPillars(projectID string) map[string]map[string]GeoPoint {
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取所有的子项目
|
||||
subProjectQuery := g.Model("sub_project").Fields("id").Where("project_id", projectID)
|
||||
|
||||
// 查询 qianqi_guangfuban_ids_zhuangdian 表
|
||||
var zhuangdians []entity.QianqiGuangfubanIdsZhuangdian
|
||||
if err := dao.QianqiGuangfubanIdsZhuangdian.Ctx(ctx).
|
||||
Fields("id,fangzhen_id,detail,name").
|
||||
WhereIn(dao.QianqiGuangfubanIdsZhuangdian.Columns().SubProjectid, subProjectQuery).
|
||||
Scan(&zhuangdians); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 第一层方阵ID, 第二层立柱名称, 第三层立柱信息
|
||||
realGeoPoint := make(map[string]map[string]GeoPoint)
|
||||
for _, zhuangdian := range zhuangdians {
|
||||
fangzhenID := zhuangdian.FangzhenId
|
||||
pillarName := zhuangdian.Name
|
||||
|
||||
var coordinates struct {
|
||||
Position struct {
|
||||
Lng float64 `json:"lng"`
|
||||
Lat float64 `json:"lat"`
|
||||
Alt float64 `json:"alt"`
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal([]byte(zhuangdian.Detail), &coordinates); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := realGeoPoint[fangzhenID]; !ok {
|
||||
realGeoPoint[fangzhenID] = make(map[string]GeoPoint)
|
||||
}
|
||||
|
||||
realGeoPoint[fangzhenID][pillarName] = GeoPoint{
|
||||
ID: zhuangdian.Id,
|
||||
Coordinates: Coordinates{
|
||||
Lng: coordinates.Position.Lng,
|
||||
Lat: coordinates.Position.Lat,
|
||||
Alt: coordinates.Position.Alt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return realGeoPoint
|
||||
}
|
||||
|
||||
// UpdateTableData 更新表中的数据
|
||||
func UpdateTableData(task_id int) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 根据任务ID查询出所有的 PV Module ID
|
||||
var pvlist []struct {
|
||||
PvID int `orm:"pv_id"`
|
||||
FangZhenID int `orm:"fangzhen_id"`
|
||||
WorkID string `orm:"work_id"`
|
||||
TypeID int `orm:"type"`
|
||||
}
|
||||
|
||||
// 查询出基本信息
|
||||
err := dao.PvModule.Ctx(ctx).As("pv").Fields("pv.id as pv_id", "pv.fangzhen_id", "pv.work_id", "pv.type").
|
||||
LeftJoin("manage_task_result as mts", "pv.id = mts.pv_id").
|
||||
Where("mts.task_id = ?", task_id).Scan(&pvlist)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询 PV Module 表失败: %w", err)
|
||||
}
|
||||
|
||||
if len(pvlist) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return g.Try(ctx, func(ctx context.Context) {
|
||||
for _, v := range pvlist {
|
||||
if v.TypeID == 15 { // 光伏板
|
||||
if err := ProcessMatchedRectangles([]int{v.PvID}); err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新 PV Module 表
|
||||
lastID, err := dao.PvModule.Ctx(ctx).Where(dao.PvModule.Columns().Id, v.PvID).
|
||||
Where(dao.PvModule.Columns().FangzhenId, v.FangZhenID).
|
||||
Where(dao.PvModule.Columns().Type, v.TypeID).
|
||||
Data(g.Map{
|
||||
dao.PvModule.Columns().Status: 2,
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
rows, err := lastID.RowsAffected()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
if rows > 0 {
|
||||
// 更新 work_status 表
|
||||
_, err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, v.FangZhenID).
|
||||
Where(dao.WorkStatus.Columns().Type, v.TypeID).
|
||||
Data(g.Map{
|
||||
dao.WorkStatus.Columns().Finished: gdb.Raw(
|
||||
"finished + 1",
|
||||
),
|
||||
}).Update()
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
|
||||
UpdateWorkSchedule(v.WorkID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
176
third/thirdParty/tiandon.go
vendored
Normal file
176
third/thirdParty/tiandon.go
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
// Package thirdParty
|
||||
// @Author 铁憨憨[cory] 2025/7/4 14:06:00
|
||||
package thirdParty
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/tiger1103/gfast-cache/cache"
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
第三方接口:
|
||||
|
||||
1、获取token
|
||||
2、推送第三方数据
|
||||
3、获取第三方数据
|
||||
*/
|
||||
|
||||
const publicKeyBase64 = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoaejbjbttHyHuEzHL8lIX5GZZ6zIYrqJpEDlPM4V5LHn19rSAYp2FyAr8y5Ctny9uUdaYbkoFiVQgxWrAYo4X/3O0OFDsowE25FMOLQY0Mn5B6CvVR7Sdt3DqzIzM1tUnJCIbVGNfDMgxLrLwFN8RvOW8MPlB6LgOvlGMDbj+OQIDAQAB`
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
type LoginResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// CheckIfThereAreTokens redis中获取token,没有就获取
|
||||
func CheckIfThereAreTokens(ctx context.Context) (token string, err error) {
|
||||
key := "thirdParty:tiandon:token"
|
||||
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
|
||||
gfCache := cache.New(prefix)
|
||||
get, err := g.Redis().Get(ctx, gfCache.CachePrefix+key)
|
||||
if err != nil && get.String() != "" {
|
||||
token = get.String()
|
||||
return token, err
|
||||
} else {
|
||||
token, err = getTheToken(ctx)
|
||||
//过期时间为30分钟(1800秒),我这里少300秒,防止过期
|
||||
commonService.Cache().Set(ctx, key, token, time.Duration(1500)*time.Second)
|
||||
return token, err
|
||||
}
|
||||
}
|
||||
|
||||
// getTheToken token获取
|
||||
func getTheToken(ctx context.Context) (token string, err error) {
|
||||
err = g.Try(ctx, func(ctx g.Ctx) {
|
||||
// 1、构造加密密码
|
||||
encryptedPassword, err := rsaEncryptWithBase64PublicKey([]byte("Hkrsoft@#2023"), publicKeyBase64)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err, "密码加密失败")
|
||||
}
|
||||
// 2、构造 JSON 请求体
|
||||
data, _ := json.Marshal(LoginRequest{
|
||||
Username: "zhangweiwei",
|
||||
Password: encryptedPassword,
|
||||
})
|
||||
// 3、构造 HTTP 请求
|
||||
req, _ := http.NewRequest("POST", "https://claritypm.powerchina.cn/neSmartsite-api/loginCli", bytes.NewReader(data))
|
||||
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Origin", "https://claritypm.powerchina.cn")
|
||||
req.Header.Set("Referer", "https://claritypm.powerchina.cn/")
|
||||
// 4、发送请求
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err, "推送第三方数据失败")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 5、解析响应体
|
||||
var result LoginResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
liberr.ErrIsNil(ctx, err, "解析第三方数据失败")
|
||||
}
|
||||
token = result.Token
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// rsaEncryptWithBase64PublicKey RSA加密函数
|
||||
func rsaEncryptWithBase64PublicKey(data []byte, base64PubKey string) (string, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(base64PubKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pubInterface, err := x509.ParsePKIXPublicKey(decoded)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pub := pubInterface.(*rsa.PublicKey)
|
||||
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
type RealUser struct {
|
||||
UserName string `json:"userName"`
|
||||
ClassManagerFlag string `json:"classManagerFlag,omitempty"`
|
||||
Phone string `json:"phone"`
|
||||
Sex string `json:"sex"`
|
||||
CardType string `json:"cardType"`
|
||||
CardNumber string `json:"cardNumber"`
|
||||
UserType string `json:"userType"`
|
||||
CardDept string `json:"cardDept,omitempty"`
|
||||
Nation string `json:"nation,omitempty"`
|
||||
Birthday string `json:"birthday,omitempty"` // 格式:2025-01-01 00:00:00
|
||||
Address string `json:"address,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"` // 限一张 http 地址
|
||||
Pic string `json:"pic,omitempty"` // 限一张 http 地址
|
||||
SafetyEdu string `json:"safetyEdu,omitempty"` // 限一张 http 地址
|
||||
Technology string `json:"technology,omitempty"` // 限一张 http 地址
|
||||
CardStartTime string `json:"cardStartTime,omitempty"` // 格式:2025-01-01 00:00:00
|
||||
CardEndTime string `json:"cardEndTime,omitempty"`
|
||||
StudyLevel string `json:"studyLevel,omitempty"`
|
||||
PoliticsType string `json:"politicsType,omitempty"`
|
||||
InsuranceFlag string `json:"insuranceFlag,omitempty"`
|
||||
DiseaseFlag string `json:"diseaseFlag,omitempty"`
|
||||
LaborContractFlag string `json:"laborContractFlag,omitempty"`
|
||||
Contacts string `json:"contacts,omitempty"`
|
||||
ContactsPhone string `json:"contactsPhone,omitempty"`
|
||||
MarryRemark string `json:"marryRemark,omitempty"`
|
||||
}
|
||||
|
||||
// SendRy 发送工人信息
|
||||
func SendRy(ctx context.Context, user []*RealUser) (err error) {
|
||||
err = g.Try(ctx, func(ctx context.Context) {
|
||||
//1、获取token
|
||||
token, err := CheckIfThereAreTokens(ctx)
|
||||
liberr.ErrIsNil(ctx, err, "获取token失败")
|
||||
// 2、创建请求
|
||||
jsonData, _ := json.Marshal(user)
|
||||
fmt.Println(":::::::::::::::::::::: " + string(jsonData))
|
||||
req, err := http.NewRequest("POST",
|
||||
"https://claritypm.powerchina.cn/neSmartsite-api/realUser/tiandong/insertRealUser",
|
||||
bytes.NewReader(jsonData),
|
||||
)
|
||||
liberr.ErrIsNil(ctx, err, "创建客户端失败")
|
||||
// 3、设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
// 4、发送请求
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err, "发送数据到第三方失败")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result LoginResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
liberr.ErrIsNil(ctx, err, "解析第三方数据失败")
|
||||
}
|
||||
fmt.Println("l111响应状态码:", result.Code)
|
||||
fmt.Println("c222响应内容:", result.Msg)
|
||||
})
|
||||
return err
|
||||
}
|
246
third/todo/todo.go
Normal file
246
third/todo/todo.go
Normal file
@ -0,0 +1,246 @@
|
||||
package todo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/lo"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
"github.com/tiger1103/gfast/v3/third/reminders"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
)
|
||||
|
||||
type TaskGroup struct {
|
||||
Role int // 0 管理员 1 施工人员
|
||||
TsakType int // 补卡提醒
|
||||
ProjectID int // 项目 ID
|
||||
UserID string // sys.user.id 或 wx.openid
|
||||
MissingCardTime string // 缺卡时间
|
||||
}
|
||||
|
||||
// 考勤审批 struct
|
||||
type ReissueReminder struct {
|
||||
UserID string // sys.user.id 或 wx.openid
|
||||
Role int // 0 管理员 1 施工人员
|
||||
ProjectID int // 项目 ID
|
||||
CreatorID string // 创建人 ID
|
||||
TargetID int // 对应跳转的主键ID
|
||||
}
|
||||
|
||||
func CreateCardReminderTasks(todoList []TaskGroup) error {
|
||||
// 获取 OpenID 对应的 sys_user.id
|
||||
openIDToUserIDMap := reminders.GetUserIDByOpenIDs( // 2. 获取所有的 openid 对应的 sys_user.id
|
||||
lo.FilterMap(todoList, func(item TaskGroup, _ int) (string, bool) { // 1. 筛选出所有的 openid
|
||||
if reminders.HasLetter(item.UserID) {
|
||||
return item.UserID, true
|
||||
}
|
||||
return item.UserID, false
|
||||
}),
|
||||
)
|
||||
|
||||
// 替换 todoList 中的 userID
|
||||
for i := range todoList {
|
||||
if newUserID, ok := openIDToUserIDMap[todoList[i].UserID]; ok {
|
||||
todoList[i].UserID = newUserID
|
||||
}
|
||||
}
|
||||
|
||||
// 同一组的消息拥有相同的 UUID
|
||||
groupUUID := uuid.New().String()
|
||||
todoTasks := lo.Map(todoList, func(item TaskGroup, _ int) do.TodoTasks {
|
||||
return do.TodoTasks{
|
||||
TaskType: item.TsakType, // 补卡提醒
|
||||
ProjectId: item.ProjectID, // 项目 ID
|
||||
UserId: item.UserID, // 消息接收者的 sys_User.Id
|
||||
Role: item.Role,
|
||||
MissingCardTime: item.MissingCardTime, // 缺卡时间 示例:`03月14日 08:30上班卡;18:00下班卡`
|
||||
Uuid: groupUUID,
|
||||
}
|
||||
})
|
||||
|
||||
_, err := dao.TodoTasks.Ctx(context.Background()).Insert(todoTasks)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateMissingCardReminder 创建并发布一个补卡提醒
|
||||
// userID: 消息接收者的 sys_user.id
|
||||
// role: 0:普通员工 1:管理员
|
||||
// projectID: 项目 ID
|
||||
// missingCardTime: 缺卡时间见设计图
|
||||
func CreateMissingCardReminder(userID string, role int, projectID int, missingCardTime string, orderId int64) error {
|
||||
if reminders.HasLetter(userID) {
|
||||
userID = reminders.GetUserIDByOpenID(userID)
|
||||
if userID == "" {
|
||||
return fmt.Errorf("未找到对应的用户")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(userID, "_") {
|
||||
return fmt.Errorf("未找到对应的用户")
|
||||
}
|
||||
|
||||
// 构建消息模板
|
||||
task := do.TodoTasks{
|
||||
TaskType: Reissue, // 补卡提醒
|
||||
ProjectId: projectID, // 项目 ID
|
||||
UserId: userID, // 消息接收者的 sys_User.Id
|
||||
Role: role,
|
||||
OrderId: orderId,
|
||||
MissingCardTime: missingCardTime, // 缺卡时间 示例:`03月14日 08:30上班卡;18:00下班卡`
|
||||
}
|
||||
|
||||
// 发布提醒
|
||||
_, err := dao.TodoTasks.Ctx(context.Background()).Insert(&task)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateAttendanceApprovalReminder 发布一条考勤审批提醒
|
||||
// projectID: 项目 ID
|
||||
// userID: 消息接收者的 sys_user.id
|
||||
// creatorID:创建人 ID
|
||||
// targetID: 对应跳转的主键ID
|
||||
func CreateAttendanceApprovalReminder(userID string, projectID int, creatorID string, targetID int) error {
|
||||
if reminders.HasLetter(creatorID) || reminders.HasLetter(userID) {
|
||||
if creatorID == userID {
|
||||
creatorID = reminders.GetUserIDByOpenID(creatorID)
|
||||
userID = creatorID
|
||||
} else {
|
||||
if reminders.HasLetter(creatorID) {
|
||||
creatorID = reminders.GetUserIDByOpenID(creatorID)
|
||||
}
|
||||
if reminders.HasLetter(userID) {
|
||||
userID = reminders.GetUserIDByOpenID(userID)
|
||||
}
|
||||
}
|
||||
|
||||
if creatorID == "" || userID == "" {
|
||||
return fmt.Errorf("未找到对应的用户")
|
||||
}
|
||||
}
|
||||
|
||||
// 构建消息模板
|
||||
task := do.TodoTasks{
|
||||
TaskType: AttendanceApproval, // 考勤审批提醒
|
||||
ProjectId: projectID, // 项目 ID
|
||||
UserId: userID, // 消息接收者的 sys_User.Id
|
||||
Applicant: creatorID, // 创建人ID
|
||||
OrderId: targetID, // 对应跳转的主键ID
|
||||
Status: 0,
|
||||
}
|
||||
|
||||
// 发布提醒
|
||||
_, err := dao.TodoTasks.Ctx(context.Background()).Insert(&task)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateAskForLeaveReminder 发布一条请假审批提醒
|
||||
// userID: 消息接收者的 sys_user.id
|
||||
// projectID: 项目 ID
|
||||
// creatorID:创建人 ID
|
||||
// targetID: 对应跳转的主键ID
|
||||
func CreateAskForLeaveReminder(userID string, projectID int, creatorID string, targetID int) error {
|
||||
if reminders.HasLetter(creatorID) || reminders.HasLetter(userID) {
|
||||
if creatorID == userID {
|
||||
creatorID = reminders.GetUserIDByOpenID(creatorID)
|
||||
userID = creatorID
|
||||
} else {
|
||||
if reminders.HasLetter(creatorID) {
|
||||
creatorID = reminders.GetUserIDByOpenID(creatorID)
|
||||
}
|
||||
if reminders.HasLetter(userID) {
|
||||
userID = reminders.GetUserIDByOpenID(userID)
|
||||
}
|
||||
}
|
||||
|
||||
if creatorID == "" || userID == "" {
|
||||
return fmt.Errorf("未找到对应的用户")
|
||||
}
|
||||
}
|
||||
|
||||
// 构建消息模板
|
||||
task := do.TodoTasks{
|
||||
TaskType: ApprovalReminder, // 请假审批提醒
|
||||
UserId: userID, // 消息接收者的 sys_User.Id
|
||||
ProjectId: projectID, // 项目 ID
|
||||
Applicant: creatorID, // 创建人ID
|
||||
OrderId: targetID, // 对应跳转的主键ID
|
||||
Status: 0, // 未处理
|
||||
Role: "1", // 发送给施工人员
|
||||
}
|
||||
|
||||
// 发布提醒
|
||||
_, err := dao.TodoTasks.Ctx(context.Background()).Insert(&task)
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateAttendanceApprovalGroup(list []ReissueReminder, num int) error {
|
||||
UserIDs := lo.FilterMap(list, func(item ReissueReminder, _ int) (string, bool) {
|
||||
if reminders.HasLetter(item.UserID) {
|
||||
return item.UserID, true
|
||||
}
|
||||
return item.UserID, false
|
||||
})
|
||||
|
||||
CreatorIDs := lo.FilterMap(list, func(item ReissueReminder, _ int) (string, bool) {
|
||||
if reminders.HasLetter(item.CreatorID) {
|
||||
return item.CreatorID, true
|
||||
}
|
||||
|
||||
return item.CreatorID, false
|
||||
})
|
||||
|
||||
// 获取 OpenID 对应的 sys_user.id
|
||||
openIDToUserIDMap := reminders.GetUserIDByOpenIDs(append(UserIDs, CreatorIDs...))
|
||||
// 替换 todoList 中的 userID
|
||||
for i := range list {
|
||||
if newUserID, ok := openIDToUserIDMap[list[i].UserID]; ok {
|
||||
list[i].UserID = newUserID
|
||||
}
|
||||
|
||||
if newUserID, ok := openIDToUserIDMap[list[i].CreatorID]; ok {
|
||||
list[i].CreatorID = newUserID
|
||||
}
|
||||
}
|
||||
|
||||
// 同一组的消息拥有相同的 UUID
|
||||
groupUUID := uuid.New().String()
|
||||
todoTasks := lo.Map(list, func(item ReissueReminder, _ int) do.TodoTasks {
|
||||
return do.TodoTasks{
|
||||
TaskType: num, // 考勤审批提醒
|
||||
ProjectId: item.ProjectID, // 项目 ID
|
||||
UserId: item.UserID, // 消息接收者的 sys_User.Id
|
||||
Applicant: item.CreatorID, // 创建人ID
|
||||
OrderId: item.TargetID, // 对应跳转的主键ID
|
||||
Role: item.Role,
|
||||
Uuid: groupUUID,
|
||||
}
|
||||
})
|
||||
|
||||
_, err := dao.TodoTasks.Ctx(context.Background()).Insert(todoTasks)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarkTaskAsProcessed 标记特定的待办任务为已处理
|
||||
// 每个待办任务都与一个 OrderID 关联,可以通过 OrderID 查找任务详情
|
||||
// 用户完成补卡或审批操作后,应将相应的待办任务标记为已处理
|
||||
func MarkTaskAsProcessed(taskID int) error {
|
||||
return g.Try(context.Background(), func(ctx context.Context) {
|
||||
_, err := dao.TodoTasks.Ctx(ctx).Where("order_id", taskID).Update(g.Map{"status": 1})
|
||||
liberr.ErrIsNil(ctx, err, "标记代办任务为已处理失败")
|
||||
})
|
||||
}
|
||||
|
||||
// 根据 uuid 将一组待办任务标记为已处理
|
||||
func MarkTasksAsProcessedByUUID(id int64) error {
|
||||
return g.Try(context.Background(), func(ctx context.Context) {
|
||||
value, err2 := dao.TodoTasks.Ctx(ctx).Where("order_id", id).Where("status", 0).Limit(1).Fields("uuid").Value()
|
||||
liberr.ErrIsNil(ctx, err2)
|
||||
_, err := dao.TodoTasks.Ctx(ctx).Where("uuid", value.String()).Update(g.Map{"status": 1})
|
||||
liberr.ErrIsNil(ctx, err, "标记代办任务为已处理失败")
|
||||
})
|
||||
}
|
9
third/todo/todo_var.go
Normal file
9
third/todo/todo_var.go
Normal file
@ -0,0 +1,9 @@
|
||||
package todo
|
||||
|
||||
type TodoType = int
|
||||
|
||||
const (
|
||||
Reissue TodoType = iota // 补卡
|
||||
AttendanceApproval // 考勤审批
|
||||
ApprovalReminder // 请假审批
|
||||
)
|
94
third/ws/business.go
Normal file
94
third/ws/business.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Package ws
|
||||
// @Author 铁憨憨[cory] 2025/2/12 15:45:00
|
||||
package ws
|
||||
|
||||
/*
|
||||
TheSenderInformationOfTheAssemblyPersonnel 组装下发人员信息
|
||||
*/
|
||||
func TheSenderInformationOfTheAssemblyPersonnel(sn string, userId string, name string, face string) (err error) {
|
||||
sUuid := GenerateUUIDWithSixRandomDigits()
|
||||
people := PeopleInformation{
|
||||
Cmd: "to_device",
|
||||
From: sUuid,
|
||||
To: sn,
|
||||
Data: PeopleInData{
|
||||
Cmd: "addUser",
|
||||
UserID: userId,
|
||||
Name: name,
|
||||
FaceTemplate: face,
|
||||
IDValid: "",
|
||||
},
|
||||
}
|
||||
_, err = SendRequestAndWaitResponse(sn, sUuid, people)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SelectUserAll 获取打卡设备所有人员
|
||||
*/
|
||||
func SelectUserAll(sn string) (CommonResponse, error) {
|
||||
sUuid := GenerateUUIDWithSixRandomDigits()
|
||||
people := PersonnelInformationAcquisition{
|
||||
Cmd: "to_device",
|
||||
From: sUuid,
|
||||
To: sn,
|
||||
Data: PersonnelInformationAcquisitionTwo{
|
||||
Cmd: "getUserInfo",
|
||||
Value: 1,
|
||||
},
|
||||
}
|
||||
return SendRequestAndWaitResponse(sn, sUuid, people)
|
||||
}
|
||||
|
||||
/*
|
||||
DelByUserId 删除指定人员
|
||||
*/
|
||||
func DelByUserId(sn string, userId string) (CommonResponse, error) {
|
||||
sUuid := GenerateUUIDWithSixRandomDigits()
|
||||
people := DeletionOfPersonnel{
|
||||
Cmd: "to_device",
|
||||
From: sUuid,
|
||||
To: sn,
|
||||
Data: DeletionOfPersonnelData{
|
||||
Cmd: "delUser",
|
||||
UserID: userId,
|
||||
UserType: 0,
|
||||
},
|
||||
}
|
||||
return SendRequestAndWaitResponse(sn, sUuid, people)
|
||||
}
|
||||
|
||||
/*
|
||||
BatchDelete 批量删除指定人员
|
||||
*/
|
||||
func BatchDelete(sn string, userIds []string) (CommonResponse, error) {
|
||||
sUuid := GenerateUUIDWithSixRandomDigits()
|
||||
people := BatchDeletion{
|
||||
Cmd: "to_device",
|
||||
From: sUuid,
|
||||
To: sn,
|
||||
Data: BatchDeletionData{
|
||||
Cmd: "delMultiUser",
|
||||
UserIds: userIds,
|
||||
UserType: 0,
|
||||
},
|
||||
}
|
||||
return SendRequestAndWaitResponse(sn, sUuid, people)
|
||||
}
|
||||
|
||||
/*
|
||||
DelAll 删除指定考勤机全部人员
|
||||
*/
|
||||
func DelAll(sn string) (CommonResponse, error) {
|
||||
sUuid := GenerateUUIDWithSixRandomDigits()
|
||||
people := DeletionALlOfPersonnel{
|
||||
Cmd: "to_device",
|
||||
From: sUuid,
|
||||
To: sn,
|
||||
Data: DeletionALlOfPersonnelData{
|
||||
Cmd: "delAllUser",
|
||||
UserType: 0,
|
||||
},
|
||||
}
|
||||
return SendRequestAndWaitResponse(sn, sUuid, people)
|
||||
}
|
167
third/ws/construction.go
Normal file
167
third/ws/construction.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Package ws
|
||||
// @Author 铁憨憨[cory] 2025/2/12 15:20:00
|
||||
package ws
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
|
||||
/*
|
||||
============================================常量============================================
|
||||
============================================常量============================================
|
||||
============================================常量============================================
|
||||
*/
|
||||
const (
|
||||
DECLARE = "declare" //设备初上线
|
||||
PING = "ping" //心跳
|
||||
ToClient = "to_client" // 服务器消息下发到客户端的响应
|
||||
)
|
||||
|
||||
/*
|
||||
============================================公共============================================
|
||||
============================================公共============================================
|
||||
============================================公共============================================
|
||||
*/
|
||||
|
||||
// DeviceInfo 存储连接设备信息
|
||||
type DeviceInfo struct {
|
||||
IP string `json:"ip"`
|
||||
Port string `json:"port"`
|
||||
Conn *websocket.Conn `json:"conn"`
|
||||
}
|
||||
|
||||
// GenericMessage 通用消息结构,用于解析初始的 cmd 字段
|
||||
type GenericMessage struct {
|
||||
CMD string `json:"cmd"`
|
||||
}
|
||||
|
||||
// CommonResponse 公共响应结构
|
||||
type CommonResponse struct {
|
||||
Cmd string `json:"cmd"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
//Extra string `json:"extra"`
|
||||
Data CommonResponseData `json:"data"`
|
||||
}
|
||||
type CommonResponseData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
UserIds []string `json:"userIds" dc:"用户IDS"`
|
||||
UserID string `json:"user_id"`
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
DelFailed []DelMultiUserData `json:"delFailed"`
|
||||
}
|
||||
|
||||
type DelMultiUserData struct {
|
||||
UserID string `json:"user_id"`
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
/*
|
||||
============================================基础============================================
|
||||
============================================基础============================================
|
||||
============================================基础============================================
|
||||
*/
|
||||
|
||||
// DeclareMessage 设备上线消息
|
||||
type DeclareMessage struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Type string `json:"type"`
|
||||
SN string `json:"sn"`
|
||||
VersionCode string `json:"version_code"`
|
||||
VersionName string `json:"version_name"`
|
||||
Token string `json:"token"`
|
||||
Ip string `json:"ip"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type PongMessage struct {
|
||||
Cmd string `json:"cmd"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Data PongMessageData `json:"data"`
|
||||
}
|
||||
|
||||
// PongMessageData 心跳回复消息结构
|
||||
type PongMessageData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
}
|
||||
|
||||
/*
|
||||
============================================ws请求============================================
|
||||
============================================ws请求============================================
|
||||
============================================ws请求============================================
|
||||
*/
|
||||
|
||||
// PeopleInformation 人员信息下发
|
||||
type PeopleInformation struct {
|
||||
Cmd string `json:"cmd"` //该接口固定为to_device
|
||||
From string `json:"from"` //可不填写,填写uuid来做为发送请求或响应的标识
|
||||
To string `json:"to"` //设备号(请查看公共设置中的设备号)
|
||||
//Extra string `json:"extra"` //给服务端预留的补充字段,设备端不处理这个字段内容。设备在响应这条指令时原样返回
|
||||
Data PeopleInData `json:"data"`
|
||||
}
|
||||
type PeopleInData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
FaceTemplate string `json:"face_template"` // http 链接图
|
||||
IDValid string `json:"id_valid"` // 人员有效期(人员在这个时间点后,无法通行)格式:yyyy-MM-dd 或者 yyyy-MM-dd HH:mm,为 “” 则为永久
|
||||
}
|
||||
|
||||
// DeletionOfPersonnel 删除人员
|
||||
type DeletionOfPersonnel struct {
|
||||
Cmd string `json:"cmd"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Data DeletionOfPersonnelData `json:"data"`
|
||||
}
|
||||
type DeletionOfPersonnelData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
UserID string `json:"user_id"`
|
||||
UserType int `json:"user_type"` // 删除的用户类型:0-人脸接口下发的数据 1-人证比对接口下发的数据
|
||||
}
|
||||
|
||||
// BatchDeletion 批量删除
|
||||
type BatchDeletion struct {
|
||||
Cmd string `json:"cmd"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Data BatchDeletionData `json:"data"`
|
||||
}
|
||||
type BatchDeletionData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
UserIds []string `json:"user_ids"`
|
||||
UserType int `json:"user_type"` // 删除的用户类型:0-人脸接口下发的数据 1-人证比对接口下发的数据
|
||||
}
|
||||
|
||||
// DeletionALlOfPersonnel 删除全部人员
|
||||
type DeletionALlOfPersonnel struct {
|
||||
Cmd string `json:"cmd"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Data DeletionALlOfPersonnelData `json:"data"`
|
||||
}
|
||||
type DeletionALlOfPersonnelData struct {
|
||||
Cmd string `json:"cmd"`
|
||||
UserType int `json:"user_type"` // 删除的用户类型:0-人脸接口下发的数据 1-人证比对接口下发的数据
|
||||
}
|
||||
|
||||
// PersonnelInformationAcquisition 人员信息获取
|
||||
type PersonnelInformationAcquisition struct {
|
||||
Cmd string `json:"cmd"` //该接口固定为to_device
|
||||
From string `json:"from"` //可不填写,填写uuid来做为发送请求或响应的标识
|
||||
To string `json:"to"` //设备号(请查看公共设置中的设备号)
|
||||
Data PersonnelInformationAcquisitionTwo `json:"data"`
|
||||
}
|
||||
type PersonnelInformationAcquisitionTwo struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
/*
|
||||
============================================http请求============================================
|
||||
============================================http请求============================================
|
||||
============================================http请求============================================
|
||||
*/
|
||||
|
||||
//loggerMiddlewareloggerMiddlewareloggerMiddleware
|
265
third/ws/ws.go
Normal file
265
third/ws/ws.go
Normal file
@ -0,0 +1,265 @@
|
||||
// Package ws
|
||||
// @Author 铁憨憨[cory] 2025/2/12 15:18:00
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/system"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/service"
|
||||
"golang.org/x/net/context"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 存储所有连接的设备信息
|
||||
var connectedDevices = make(map[string]*DeviceInfo)
|
||||
|
||||
// 存储每个 uuid 对应的通道
|
||||
var responseChannels = make(map[string]chan CommonResponse)
|
||||
var responseChannelsMutex sync.Mutex
|
||||
|
||||
// HandleWebSocket 处理WebSocket连接的函数
|
||||
func HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := gctx.New()
|
||||
// 将HTTP连接升级为WebSocket连接
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("WebSocket升级失败:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 读取设备发送的第一条消息,从中提取 SN然后添加到设备列表中
|
||||
declare, err := addDevice(ctx, conn, r)
|
||||
if err != nil {
|
||||
log.Println("添加设备信息时出错:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 持续读取从客户端(其他服务器)发送过来的数据
|
||||
for {
|
||||
// 读取消息类型和消息内容
|
||||
messageType, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("读取消息时出错:", err)
|
||||
// 移除设备信息
|
||||
delete(connectedDevices, declare.SN)
|
||||
log.Printf("设备断开连接,设备信息: %+v", declare)
|
||||
err = service.BusAttendanceMachine().Change(ctx, &system.BusAttendanceMachineChangeReq{
|
||||
Sn: declare.SN,
|
||||
Status: "0",
|
||||
})
|
||||
log.Println("修改考勤设备状态时出错:", err)
|
||||
break
|
||||
}
|
||||
|
||||
// 先解析出 cmd 字段
|
||||
var genericMsg GenericMessage
|
||||
err = json.Unmarshal(message, &genericMsg)
|
||||
if err != nil {
|
||||
log.Println("解析 cmd 字段时出错:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据 cmd 字段进行不同处理
|
||||
switch genericMsg.CMD {
|
||||
case DECLARE:
|
||||
log.Println("设备在线:", declare)
|
||||
case PING:
|
||||
|
||||
declareMessage := DeclareMessage{}
|
||||
if json.Unmarshal(message, &declareMessage) != nil {
|
||||
return
|
||||
}
|
||||
if err = handlePing(ctx, conn, r, declareMessage.SN); err != nil {
|
||||
log.Println("处理心跳回复:", err)
|
||||
}
|
||||
case ToClient:
|
||||
if err := requestResponse(message); err != nil {
|
||||
log.Println("处理响应:", err)
|
||||
}
|
||||
default:
|
||||
log.Printf("收到未知消息---> 类型: %d, 消息内容: %s", messageType, string(message))
|
||||
}
|
||||
}
|
||||
// 清理通道资源
|
||||
responseChannelsMutex.Lock()
|
||||
for key, ch := range responseChannels {
|
||||
if connectedDevices[declare.SN] != nil {
|
||||
delete(responseChannels, key)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
responseChannelsMutex.Unlock()
|
||||
}
|
||||
|
||||
// addDevice 将设备信息添加到设备列表中
|
||||
func addDevice(ctx context.Context, conn *websocket.Conn, r *http.Request) (declareMessage DeclareMessage, err error) {
|
||||
// 读取设备发送的第一条消息,从中提取 SN
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
declareMessage = DeclareMessage{}
|
||||
if json.Unmarshal(message, &declareMessage) != nil {
|
||||
return
|
||||
}
|
||||
// 假设设备信息可以从请求中获取,这里简单从 RemoteAddr 解析
|
||||
ip, port := parseRemoteAddr(r.RemoteAddr)
|
||||
deviceInfo := &DeviceInfo{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
Conn: conn,
|
||||
}
|
||||
if declareMessage.SN != "" {
|
||||
// 存储设备信息
|
||||
connectedDevices[declareMessage.SN] = deviceInfo
|
||||
log.Printf("新设备连接,设备信息: %+v", declareMessage)
|
||||
// 变更状态
|
||||
err = service.BusAttendanceMachine().Register(ctx, &system.BusAttendanceMachineRegisterReq{
|
||||
Sn: declareMessage.SN,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return declareMessage, nil
|
||||
}
|
||||
|
||||
// handlePing 处理 ping 消息
|
||||
func handlePing(ctx context.Context, conn *websocket.Conn, r *http.Request, sn string) (err error) {
|
||||
pongMsg := PongMessageData{
|
||||
Cmd: "pong",
|
||||
}
|
||||
pongJSON, _ := json.Marshal(pongMsg)
|
||||
err = conn.WriteMessage(websocket.TextMessage, pongJSON)
|
||||
if err != nil {
|
||||
log.Println("发送 Pong 消息时出错:", err)
|
||||
return err
|
||||
}
|
||||
// 存储设备信息、变更状态
|
||||
connectedDevices[sn] = &DeviceInfo{
|
||||
Conn: conn,
|
||||
}
|
||||
err = service.BusAttendanceMachine().Register(ctx, &system.BusAttendanceMachineRegisterReq{
|
||||
Sn: sn,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// requestResponse 处理请求得到的响应
|
||||
func requestResponse(message []byte) (err error) {
|
||||
log.Println("请求响应:", string(message))
|
||||
var common CommonResponse
|
||||
err = json.Unmarshal(message, &common)
|
||||
if err != nil {
|
||||
log.Println("解析 cmd 字段时出错:", err)
|
||||
return err
|
||||
}
|
||||
// 根据UUID查找对应的通道
|
||||
responseChannelsMutex.Lock()
|
||||
if ch, ok := responseChannels[common.To]; ok {
|
||||
ch <- common
|
||||
delete(responseChannels, common.To)
|
||||
}
|
||||
responseChannelsMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据 SN 发送消息给对应的设备
|
||||
func sendMessageToDevice(sn string, uuid string, message interface{}) error {
|
||||
conn, exists := connectedDevices[sn]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
msgJSON, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Conn.WriteMessage(websocket.TextMessage, msgJSON)
|
||||
if err != nil {
|
||||
responseChannelsMutex.Lock()
|
||||
delete(responseChannels, uuid)
|
||||
responseChannelsMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendRequestAndWaitResponse(sn string, sUuid string, payload interface{}) (CommonResponse, error) {
|
||||
responseChan := make(chan CommonResponse, 1)
|
||||
// 存入全局映射
|
||||
responseChannelsMutex.Lock()
|
||||
responseChannels[sUuid] = responseChan
|
||||
responseChannelsMutex.Unlock()
|
||||
|
||||
// 发送请求
|
||||
err := sendMessageToDevice(sn, sUuid, payload)
|
||||
if err != nil {
|
||||
return CommonResponse{}, err
|
||||
}
|
||||
|
||||
// 等待响应
|
||||
select {
|
||||
case resp := <-responseChan:
|
||||
fmt.Printf("收到响应: %+v\n", resp)
|
||||
return resp, nil
|
||||
case <-time.After(10 * time.Second):
|
||||
responseChannelsMutex.Lock()
|
||||
delete(responseChannels, sUuid)
|
||||
responseChannelsMutex.Unlock()
|
||||
return CommonResponse{}, fmt.Errorf("等待响应超时")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=========================================================业务无关=========================================================
|
||||
=========================================================业务无关=========================================================
|
||||
=========================================================业务无关=========================================================
|
||||
*/
|
||||
|
||||
// 定义一个升级器,用于将HTTP连接升级为WebSocket连接
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
// 允许跨域访问,这里设置为允许所有来源
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// 解析远程地址,获取 IP 和端口
|
||||
func parseRemoteAddr(addr string) (string, string) {
|
||||
// 简单处理,假设地址格式为 IP:Port
|
||||
for i := len(addr) - 1; i >= 0; i-- {
|
||||
if addr[i] == ':' {
|
||||
return addr[:i], addr[i+1:]
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// GenerateUUIDWithSixRandomDigits 生成一个 UUID 并拼接 6 位随机数
|
||||
func GenerateUUIDWithSixRandomDigits() string {
|
||||
// 生成 UUID
|
||||
uuidStr := uuid.New().String()
|
||||
// 初始化随机数种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
// 生成 6 位随机数
|
||||
randomNum := rand.Intn(900000) + 100000
|
||||
// 将随机数转换为字符串
|
||||
randomNumStr := strconv.Itoa(randomNum)
|
||||
// 拼接 UUID 和 6 位随机数
|
||||
result := uuidStr + "-" + randomNumStr
|
||||
return result
|
||||
}
|
51
third/ys7/struct.go
Normal file
51
third/ys7/struct.go
Normal file
@ -0,0 +1,51 @@
|
||||
package ys7
|
||||
|
||||
const OK = "200"
|
||||
|
||||
//type Public struct {
|
||||
// Code string `json:"code"`
|
||||
// Msg string `json:"msg"`
|
||||
//}
|
||||
|
||||
type TP struct {
|
||||
Public
|
||||
Data struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpireTime int64 `json:"expireTime"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type HelMet struct {
|
||||
Public
|
||||
RequestId string `json:"requestId"`
|
||||
Data []struct {
|
||||
Id string `json:"id"`
|
||||
Height string `json:"height"`
|
||||
TargetList []struct {
|
||||
BodyRect struct {
|
||||
VmodelHF string `json:"vmodel_h_f"`
|
||||
VmodelWF string `json:"vmodel_w_f"`
|
||||
VmodelXF string `json:"vmodel_x_f"`
|
||||
VmodelYF string `json:"vmodel_y_f"`
|
||||
} `json:"body_rect"`
|
||||
AlarmFlg int `json:"alarm_flg"`
|
||||
UniformType int `json:"uniform_type"`
|
||||
ID int `json:"ID"`
|
||||
HelmetType int `json:"helmet_type"`
|
||||
HeadRect struct {
|
||||
VmodelHF string `json:"vmodel_h_f"`
|
||||
VmodelWF string `json:"vmodel_w_f"`
|
||||
VmodelXF string `json:"vmodel_x_f"`
|
||||
VmodelYF string `json:"vmodel_y_f"`
|
||||
} `json:"head_rect"`
|
||||
ColorType int `json:"color_type"`
|
||||
} `json:"target_list"`
|
||||
Width string `json:"width"`
|
||||
RuleInfo []interface{} `json:"rule_info"`
|
||||
RuleList [][]struct {
|
||||
Y string `json:"y"`
|
||||
X string `json:"x"`
|
||||
} `json:"rule_list"`
|
||||
ErrorCode int `json:"errorCode"`
|
||||
} `json:"data"`
|
||||
}
|
558
third/ys7/ys7.go
Normal file
558
third/ys7/ys7.go
Normal file
@ -0,0 +1,558 @@
|
||||
package ys7
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
)
|
||||
|
||||
var accessToken = "at.ddhhz6ea49jorccubhg3elqs19nvmxta-1754nu5ce8-017egq8-fmymwvvhv"
|
||||
|
||||
const (
|
||||
Ok = "200"
|
||||
Online = 1
|
||||
)
|
||||
|
||||
// 改变 accessToken
|
||||
func SetAccessToken(token string) {
|
||||
accessToken = token
|
||||
}
|
||||
|
||||
// 传入 serial 返回 Devicemap 中的设备信息
|
||||
func GetDevice(serial string) (Ys7Device, error) {
|
||||
device, ok := deviceMap[serial]
|
||||
if ok {
|
||||
return device, nil
|
||||
}
|
||||
return Ys7Device{}, fmt.Errorf("设备不存在")
|
||||
}
|
||||
|
||||
var deviceMap = make(map[string]Ys7Device)
|
||||
|
||||
/*当前包的入口方法*/
|
||||
func InitYs7() {
|
||||
/*获取一次token*/
|
||||
getAccessToken()
|
||||
/*获取一次设备列表*/
|
||||
getDeviceList(0, 50) // 可以提前获取数据库中的记录进行缓存,比对当前设备是否在数据库中已经存在,不存在则自动入库
|
||||
}
|
||||
|
||||
func GetAccessToken() string {
|
||||
return accessToken
|
||||
}
|
||||
|
||||
/*获取设备表*/
|
||||
func GetDeviceMap() map[string]Ys7Device {
|
||||
return deviceMap
|
||||
}
|
||||
|
||||
type TokenRes struct {
|
||||
Public
|
||||
Data struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpireTime int64 `json:"expireTime"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func getAccessToken() {
|
||||
// ctx := gctx.New()
|
||||
// appKey := g.Cfg().MustGet(ctx, "ys7.appKey").String()
|
||||
// appSecret := g.Cfg().MustGet(ctx, "ys7.appSecret").String()
|
||||
|
||||
appKey := "3acf9f1a43dc4209841e0893003db0a2"
|
||||
appSecret := "4bbf3e9394f55d3af6e3af27b2d3db36"
|
||||
|
||||
url := "https://open.ys7.com/api/lapp/token/get"
|
||||
dt := struct {
|
||||
AppKey string `json:"appKey"`
|
||||
AppSecret string `json:"appSecret"`
|
||||
}{AppKey: appKey, AppSecret: appSecret}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
} else {
|
||||
res := TokenRes{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
} else {
|
||||
accessToken = res.Data.AccessToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DeviceListRes struct {
|
||||
Public
|
||||
Page struct {
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
} `json:"page"`
|
||||
Data []Ys7Device `json:"data"`
|
||||
}
|
||||
|
||||
func delayRequest(page, pageSize int) {
|
||||
time.Sleep(time.Second * 5)
|
||||
getDeviceList(page, pageSize)
|
||||
}
|
||||
|
||||
func getDeviceList(page, pageSize int) {
|
||||
url := "https://open.ys7.com/api/lapp/device/list"
|
||||
dt := struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
PageStart int `json:"pageStart"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}{AccessToken: accessToken, PageSize: pageSize, PageStart: page}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
delayRequest(page, pageSize)
|
||||
} else {
|
||||
res := DeviceListRes{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
delayRequest(page, pageSize)
|
||||
} else {
|
||||
if res.Code == Ok {
|
||||
for _, datum := range res.Data {
|
||||
deviceMap[datum.DeviceSerial] = datum
|
||||
//marshal, err := json.Marshal(datum)
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
//fmt.Println(string(marshal))
|
||||
//if datum.Online() {
|
||||
// capRes, err := datum.Capture()
|
||||
// if err != nil {
|
||||
// fmt.Println("抓图识别")
|
||||
// } else {
|
||||
// SavePicture(capRes.Data.PicUrl, "static/"+gmd5.MustEncryptString(capRes.Data.PicUrl)+".jpg")
|
||||
// }
|
||||
//}
|
||||
}
|
||||
if (page+1)*pageSize < res.Page.Total {
|
||||
getDeviceList(page+1, pageSize)
|
||||
}
|
||||
} else {
|
||||
delayRequest(page, pageSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Ys7Device struct {
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DeviceType string `json:"deviceType"`
|
||||
Status int `json:"status"`
|
||||
Defence int `json:"defence"`
|
||||
DeviceVersion string `json:"deviceVersion"`
|
||||
}
|
||||
type Public struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (receiver *Ys7Device) Online() bool {
|
||||
if receiver.Status == Online {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func Post(url string, data interface{}) (res []byte, err error) {
|
||||
response, err := g.Client().Header(map[string]string{"Content-Type": "application/x-www-form-urlencoded"}).Post(gctx.New(), url, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.ReadAll(), nil
|
||||
}
|
||||
|
||||
/*开始云台控制*/
|
||||
func (receiver *Ys7Device) PTZStart(direction int) (res *Public, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/ptz/start"
|
||||
dt := struct {
|
||||
ChannelNo int `json:"channelNo"`
|
||||
Direction int `json:"direction"`
|
||||
Speed int `json:"speed"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
}{
|
||||
AccessToken: accessToken, DeviceSerial: receiver.DeviceSerial, ChannelNo: 1, Speed: 1, Direction: direction,
|
||||
}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
res = &Public{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*停止云台控制*/
|
||||
func (receiver *Ys7Device) PTZEnd(direction int) (res *Public, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/ptz/stop"
|
||||
dt := struct {
|
||||
ChannelNo int `json:"channelNo"`
|
||||
Direction int `json:"direction"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
}{
|
||||
AccessToken: accessToken, DeviceSerial: receiver.DeviceSerial, ChannelNo: 1, Direction: direction,
|
||||
}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
res = &Public{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type PresetAddRes struct {
|
||||
Public
|
||||
Data struct {
|
||||
Index int `json:"index"` // 预置点序号,C6设备是1-12,该参数需要开发者自行保存
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
/*添加预制点*/
|
||||
func (receiver *Ys7Device) AddPreset() (res *PresetAddRes, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/preset/add"
|
||||
dt := struct {
|
||||
ChannelNo int `json:"channelNo"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
}{
|
||||
AccessToken: accessToken, DeviceSerial: receiver.DeviceSerial, ChannelNo: 1,
|
||||
}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
res = &PresetAddRes{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*调用预制点*/
|
||||
func (receiver *Ys7Device) MovePreset(index int) (res *Public, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/preset/move"
|
||||
dt := struct {
|
||||
ChannelNo int `json:"channelNo"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
Index int `json:"index"`
|
||||
}{ChannelNo: 1, DeviceSerial: receiver.DeviceSerial, AccessToken: accessToken, Index: index}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
res = &Public{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*清除预制点*/
|
||||
func (receiver *Ys7Device) ClearPreset(index int) (res *Public, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/preset/clear"
|
||||
dt := struct {
|
||||
ChannelNo int `json:"channelNo"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
Index int `json:"index"`
|
||||
}{ChannelNo: 1, DeviceSerial: receiver.DeviceSerial, AccessToken: accessToken, Index: index}
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
res = &Public{}
|
||||
err = json.Unmarshal(post, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 开启设备视频加密
|
||||
func (r *Ys7Device) OpenEncrypt() error {
|
||||
url := "https://open.ys7.com/api/lapp/device/encrypt/on"
|
||||
dt := struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
}{
|
||||
AccessToken: accessToken,
|
||||
DeviceSerial: r.DeviceSerial,
|
||||
}
|
||||
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gjson.New(string(post)).Get("code").Int() != 200 {
|
||||
return fmt.Errorf("%s", gjson.New(string(post)).Get("msg").String())
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
_, err = dao.Ys7Devices.Ctx(context.Background()).
|
||||
Where(dao.Ys7Devices.Columns().DeviceSerial, r.DeviceSerial).
|
||||
Update(g.Map{
|
||||
"VideoEncrypted": 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 关闭设备视频加密
|
||||
func (r *Ys7Device) CloseEncrypt() error {
|
||||
url := "https://open.ys7.com/api/lapp/device/encrypt/off"
|
||||
dt := struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
}{
|
||||
AccessToken: accessToken,
|
||||
DeviceSerial: r.DeviceSerial,
|
||||
}
|
||||
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gjson.New(string(post)).Get("code").Int() != 200 {
|
||||
return fmt.Errorf("%s", gjson.New(string(post)).Get("msg").String())
|
||||
}
|
||||
|
||||
_, err = dao.Ys7Devices.Ctx(context.Background()).
|
||||
Where(dao.Ys7Devices.Columns().DeviceSerial, r.DeviceSerial).
|
||||
Update(g.Map{
|
||||
"VideoEncrypted": 0,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLiveAddress 获取播放地址
|
||||
func (r *Ys7Device) GetLiveAddress() (string, error) {
|
||||
url := "https://open.ys7.com/api/lapp/v2/live/address/get"
|
||||
dt := struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
ChannelNo int `json:"channelNo"`
|
||||
}{
|
||||
AccessToken: accessToken,
|
||||
DeviceSerial: r.DeviceSerial,
|
||||
ChannelNo: 1,
|
||||
}
|
||||
|
||||
post, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
Body := string(post)
|
||||
println(Body)
|
||||
if gjson.New(Body).Get("code").Int() != 200 {
|
||||
return "", fmt.Errorf("获取播放地址失败: %s", gjson.New(Body).Get("msg").String())
|
||||
}
|
||||
|
||||
return gjson.New(Body).Get("data.url").String(), nil
|
||||
}
|
||||
|
||||
/*抓图结果返回*/
|
||||
type CapRes struct {
|
||||
Public
|
||||
Data struct {
|
||||
PicUrl string `json:"picUrl"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
/*抓拍图片*/
|
||||
func (receiver *Ys7Device) Capture() (cap *CapRes, err error) {
|
||||
url := "https://open.ys7.com/api/lapp/device/capture"
|
||||
dt := struct {
|
||||
DeviceSerial string `json:"deviceSerial"`
|
||||
ChannelNo int `json:"channelNo"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
}{DeviceSerial: receiver.DeviceSerial, ChannelNo: 1, AccessToken: accessToken}
|
||||
res, err := Post(url, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cap = &CapRes{}
|
||||
err = json.Unmarshal(res, &cap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// cap.Data.PicUrl = string(TransHtmlJson([]byte(cap.Data.PicUrl)))
|
||||
return cap, nil
|
||||
}
|
||||
|
||||
// download file会将url下载到本地文件,它会在下载时写入,而不是将整个文件加载到内存中。
|
||||
|
||||
func SavePicture(url, dst string) (err error) {
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Create the file
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
//var appKey = ""
|
||||
//var secret = ""
|
||||
//var token = ""
|
||||
//
|
||||
//// InitYs7 安全帽 absolutePath绝对路径
|
||||
//func InitYs7(absolutePath string) (flag bool, num int, err error) {
|
||||
// getConfig()
|
||||
// GetAccesstoken()
|
||||
// imageData, err := os.ReadFile(absolutePath)
|
||||
// if err != nil {
|
||||
// err = errors.New("读取图片文件失败")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 将图片数据转换为Base64字符串
|
||||
// base64String := base64.StdEncoding.EncodeToString(imageData)
|
||||
// flag, num, err = HelmetCheck(base64String, absolutePath)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//func getConfig() {
|
||||
// appKey = g.Cfg().MustGet(gctx.New(), "ys7.key").String()
|
||||
// secret = g.Cfg().MustGet(gctx.New(), "ys7.secret").String()
|
||||
//}
|
||||
//
|
||||
//func GetAccesstoken() {
|
||||
// key := "ys7"
|
||||
// //从缓存捞取key
|
||||
// ctx := gctx.New()
|
||||
// get := commonService.Cache().Get(ctx, key)
|
||||
// if get != nil && get.String() != "" {
|
||||
// token = get.String()
|
||||
// } else {
|
||||
// getConfig()
|
||||
// url := "https://open.ys7.com/api/lapp/token/get"
|
||||
// dt := struct {
|
||||
// AppKey string `json:"appKey"`
|
||||
// AppSecret string `json:"appSecret"`
|
||||
// }{appKey, secret}
|
||||
// err, s := post(url, dt)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// tp := TP{}
|
||||
// err = json.Unmarshal(s, &tp)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if tp.Code == OK {
|
||||
// token = tp.Data.AccessToken
|
||||
// //将token存储到redis中,tiken默认时间为秒,实际计算为7天,(这里少100秒,防止token过期还存在redis中)
|
||||
// commonService.Cache().Set(ctx, key, tp.Data.AccessToken, time.Duration(tp.Data.ExpireTime-100)*time.Second)
|
||||
// token = tp.Data.AccessToken
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//func post(uri string, data interface{}) (error, []byte) {
|
||||
// response, err := g.Client().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), uri, data)
|
||||
// if err != nil {
|
||||
// return err, nil
|
||||
// }
|
||||
// return nil, response.ReadAll()
|
||||
//}
|
||||
//
|
||||
//// HelmetCheck 图片中标记安全帽
|
||||
//func HelmetCheck(image string, absolutePath string) (flag bool, num int, err error) {
|
||||
// flag = false
|
||||
// num = 0
|
||||
// url := "https://open.ys7.com/api/lapp/intelligence/target/analysis"
|
||||
// dt := struct {
|
||||
// AccessToken string `json:"accessToken"`
|
||||
// DataType int `json:"dataType"`
|
||||
// Image string `json:"image"`
|
||||
// ServiceType string `json:"serviceType"`
|
||||
// }{token, 1, image, "helmet"}
|
||||
// err, s := post(url, dt)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// tp := HelMet{}
|
||||
// err = json.Unmarshal(s, &tp)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if tp.Code == OK && len(tp.Data) > 0 {
|
||||
// for _, data := range tp.Data {
|
||||
// height, _ := strconv.ParseFloat(data.Height, 64)
|
||||
// width, _ := strconv.ParseFloat(data.Width, 64)
|
||||
// if len(tp.Data) <= 0 {
|
||||
// continue
|
||||
// }
|
||||
// for _, dataTwo := range data.TargetList {
|
||||
// HeadRect := dataTwo.HeadRect
|
||||
// helmetType := dataTwo.HelmetType
|
||||
// if helmetType == 2 || helmetType == 0 {
|
||||
// flag = true
|
||||
// num = num + 1
|
||||
// vmodel_h_f, _ := strconv.ParseFloat(HeadRect.VmodelHF, 64)
|
||||
// vmodel_w_f, _ := strconv.ParseFloat(HeadRect.VmodelWF, 64)
|
||||
// vmodel_x_f, _ := strconv.ParseFloat(HeadRect.VmodelXF, 64)
|
||||
// vmodel_y_f, _ := strconv.ParseFloat(HeadRect.VmodelYF, 64)
|
||||
//
|
||||
// vmodel_h_f = vmodel_h_f * height
|
||||
// vmodel_w_f = vmodel_w_f * width
|
||||
// vmodel_x_f = vmodel_x_f * width
|
||||
// vmodel_y_f = vmodel_y_f * height
|
||||
// coryCommon.Test_draw_rect_text(absolutePath,
|
||||
// vmodel_x_f, vmodel_y_f, vmodel_w_f, vmodel_h_f)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
//}
|
338
third/ys7/ys72.go
Normal file
338
third/ys7/ys72.go
Normal file
@ -0,0 +1,338 @@
|
||||
package ys7
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
|
||||
)
|
||||
|
||||
type Ys72 struct {
|
||||
FnLogin func() (string, error) // 登录回调
|
||||
Devices map[string]Ys7Device // 设备列表
|
||||
Total int // 总页数 和 当前页数
|
||||
}
|
||||
|
||||
var Ys72Instance = NewYs72()
|
||||
|
||||
// NewYs72 初始化
|
||||
func NewYs72() *Ys72 {
|
||||
ys7 := &Ys72{
|
||||
FnLogin: DefaultLogin,
|
||||
Devices: make(map[string]Ys7Device),
|
||||
Total: 999, // 第一次获取设备列表时,会刷新其实际值
|
||||
}
|
||||
|
||||
// 刷新 ys7.go 中的 AccessToken
|
||||
_ = ys7.GetAccessToken()
|
||||
|
||||
// 初始化设备列表
|
||||
//if err := ys7.Init(); err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
|
||||
// 定时任务更新设备列表
|
||||
go func() {
|
||||
for range time.Tick(3 * time.Minute) {
|
||||
if err := ys7.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := ys7.GetAllDeviceList(); err != nil {
|
||||
log.Println("更新设备列表失败:", err)
|
||||
}
|
||||
|
||||
// 获取设备截图并存入 Redis
|
||||
ys7.CaptureAllDevice()
|
||||
}
|
||||
}()
|
||||
|
||||
return ys7
|
||||
}
|
||||
|
||||
// Init 第一次初始化时从数据库获取设备列表存入 Devices
|
||||
func (ys *Ys72) Init() error {
|
||||
device := []entity.Ys7Devices{}
|
||||
|
||||
// 获取设备列表
|
||||
err := dao.Ys7Devices.Ctx(context.Background()).Scan(&device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将设备列表存入 Devices
|
||||
for _, v := range device {
|
||||
ys.Devices[v.DeviceSerial] = Ys7Device{
|
||||
DeviceSerial: v.DeviceSerial,
|
||||
DeviceName: v.DeviceName,
|
||||
DeviceType: v.DeviceType,
|
||||
Status: v.Status,
|
||||
Defence: int(v.Defence),
|
||||
DeviceVersion: v.DeviceVersion,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 遍历 ys.Devices 调用 Capture 获取设备截图并存入Redis
|
||||
func (ys *Ys72) CaptureAllDevice() {
|
||||
for _, device := range ys.Devices {
|
||||
// 获取设备截图
|
||||
resp, err := device.Capture()
|
||||
if err != nil {
|
||||
log.Println("获取设备截图失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 存入 Redis
|
||||
ctx := context.Background()
|
||||
g.Redis().Set(ctx, "ys7:capture:"+device.DeviceSerial, resp.Data.PicUrl)
|
||||
// 两小时后过期
|
||||
g.Redis().Expire(ctx, "ys7:capture:"+device.DeviceSerial, 2*60*60)
|
||||
}
|
||||
}
|
||||
|
||||
// 由 Redis 管理 AccessToken 的生命周期,如果获取失败则重新刷新
|
||||
func (ys *Ys72) GetAccessToken() string {
|
||||
ctx := context.Background()
|
||||
// 从 Redis 获取 AccessToken
|
||||
accessToken, err := g.Redis().Get(ctx, "ys7:accessToken")
|
||||
|
||||
// 兼容原本获取 AccessToken 的逻辑
|
||||
SetAccessToken(accessToken.String())
|
||||
|
||||
if err == nil && accessToken.String() != "" {
|
||||
return accessToken.String()
|
||||
}
|
||||
|
||||
// 未获取到 AccessToken 则重新登录
|
||||
if accessToken.String() == "" {
|
||||
// 如果登录失败则递归重试
|
||||
if err := ys.login(); err != nil {
|
||||
return ys.GetAccessToken()
|
||||
}
|
||||
|
||||
accessToken, err = g.Redis().Get(ctx, "ys7:accessToken")
|
||||
if err != nil {
|
||||
return accessToken.String()
|
||||
}
|
||||
}
|
||||
|
||||
return accessToken.String()
|
||||
}
|
||||
|
||||
// 传入 deviceSerial 获取设备信息
|
||||
func (ys *Ys72) GetDevice(deviceSerial string) (Ys7Device, error) {
|
||||
if device, ok := ys.Devices[deviceSerial]; ok {
|
||||
return device, nil
|
||||
}
|
||||
|
||||
return Ys7Device{}, fmt.Errorf("设备不存在: %s", deviceSerial)
|
||||
}
|
||||
|
||||
// 获取单页设备列表并存入 Devices
|
||||
func (ys *Ys72) getDeviceList(page int, deviceMapping map[string]Ys7Device) error {
|
||||
accessToken := ys.GetAccessToken()
|
||||
|
||||
req := struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
PageStart int `json:"pageStart"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}{AccessToken: accessToken, PageSize: 50, PageStart: page}
|
||||
|
||||
resp, err := Post("https://open.ys7.com/api/lapp/device/list", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := string(resp)
|
||||
|
||||
// 解析返回数据
|
||||
if gjson.Get(body, "code").Int() != 200 {
|
||||
return fmt.Errorf("获取设备列表失败: %s", gjson.Get(body, "msg").String())
|
||||
}
|
||||
|
||||
// 解析设备列表
|
||||
gjson.Get(body, "data").ForEach(func(_, value gjson.Result) bool {
|
||||
deviceSerial := value.Get("deviceSerial").String() // 设备序列号
|
||||
deviceName := value.Get("deviceName").String() // 设备名称
|
||||
deviceType := value.Get("deviceType").String() // 设备类型
|
||||
deviceStatus := int(value.Get("status").Int()) // 设备状态 1:在线 2:离线
|
||||
defence := int(value.Get("defence").Int()) // 是否布撤防 0:未布防 1:布防 不用管
|
||||
deviceVersion := value.Get("deviceVersion").String() // 设备版本
|
||||
|
||||
deviceMapping[deviceSerial] = Ys7Device{
|
||||
DeviceSerial: deviceSerial,
|
||||
DeviceName: deviceName,
|
||||
DeviceType: deviceType,
|
||||
Status: deviceStatus,
|
||||
Defence: defence,
|
||||
DeviceVersion: deviceVersion,
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
ys.Total = int(gjson.Get(body, "page.total").Int())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 依次调用 getDeviceList 获取所有设备列表
|
||||
func (ys *Ys72) GetAllDeviceList() error {
|
||||
// 创建一个设备map
|
||||
deviceMapping := make(map[string]Ys7Device)
|
||||
|
||||
// 遍历页码获取所有设备列表
|
||||
for page := 0; page <= ys.Total; page++ {
|
||||
if err := ys.getDeviceList(page, deviceMapping); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
newDevices := []do.Ys7Devices{} // 新设备列表
|
||||
updateDevices := []do.Ys7Devices{} // 更新设备列表
|
||||
|
||||
// 遍历 deviceMapping 对比 ys.Devices 筛选出增量或更新设备列表
|
||||
for deviceSerial, device := range deviceMapping {
|
||||
// 如果设备不存在则为新增设备
|
||||
if _, ok := ys.Devices[deviceSerial]; !ok {
|
||||
newDevices = append(newDevices, do.Ys7Devices{
|
||||
DeviceSerial: device.DeviceSerial,
|
||||
DeviceName: device.DeviceName,
|
||||
DeviceType: device.DeviceType,
|
||||
Status: device.Status,
|
||||
Defence: device.Defence,
|
||||
DeviceVersion: device.DeviceVersion,
|
||||
})
|
||||
} else {
|
||||
// 如果设备存在则对比设备状态
|
||||
if ok, newDevice := ys.CompareDevice(ys.Devices[deviceSerial], device); !ok {
|
||||
// 如果设备状态不同则更新设备
|
||||
updateDevices = append(updateDevices, do.Ys7Devices{
|
||||
DeviceSerial: newDevice.DeviceSerial,
|
||||
DeviceName: newDevice.DeviceName,
|
||||
DeviceType: newDevice.DeviceType,
|
||||
Status: newDevice.Status,
|
||||
Defence: newDevice.Defence,
|
||||
DeviceVersion: newDevice.DeviceVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新旧状态替换
|
||||
ys.Devices = deviceMapping
|
||||
|
||||
// 新设备列表入库
|
||||
if len(newDevices) > 0 {
|
||||
_, err := dao.Ys7Devices.Ctx(context.Background()).Data(newDevices).Batch(20).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 更新设备列表
|
||||
if len(updateDevices) > 0 {
|
||||
for _, device := range updateDevices {
|
||||
_, err := dao.Ys7Devices.Ctx(context.Background()).Data(device).
|
||||
Where(dao.Ys7Devices.Columns().DeviceSerial, device.DeviceSerial).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// _, err := dao.Ys7Devices.Ctx(context.Background()).Data(updateDevices).Batch(20).Update()
|
||||
// if err != nil {
|
||||
// log.Println("更新错误:", err)
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 登录并将 AccessToken 存入 Redis 由 Redis 管理其有效期
|
||||
// 当获取失败则重新登录后刷新周期
|
||||
func (ys *Ys72) login() error {
|
||||
accessToken, err := ys.FnLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
g.Redis().Set(ctx, "ys7:accessToken", accessToken)
|
||||
// 设置 key 将在 5 天后过期
|
||||
g.Redis().Expire(ctx, "ys7:accessToken", 5*24*60*60)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompareDevice 对比传递的两个设备结构体是否相同
|
||||
// 如果不同则返回 false 并构造出新的设备结构体
|
||||
func (ys *Ys72) CompareDevice(old, new Ys7Device) (bool, Ys7Device) {
|
||||
// 对比old 和 new 结构体的所有字段,是否相同
|
||||
if old == new {
|
||||
return true, Ys7Device{}
|
||||
}
|
||||
|
||||
// 构造出新的ys7Device
|
||||
return false, Ys7Device{
|
||||
DeviceSerial: new.DeviceSerial,
|
||||
DeviceName: new.DeviceName,
|
||||
DeviceType: new.DeviceType,
|
||||
Status: new.Status,
|
||||
Defence: new.Defence,
|
||||
DeviceVersion: new.DeviceVersion,
|
||||
}
|
||||
}
|
||||
|
||||
type defalutFn func() (string, error)
|
||||
|
||||
var DefaultLogin = defalutFn(func() (string, error) {
|
||||
appKey := "3acf9f1a43dc4209841e0893003db0a2"
|
||||
appSecret := "4bbf3e9394f55d3af6e3af27b2d3db36"
|
||||
|
||||
req := struct {
|
||||
AppKey string `json:"appKey"`
|
||||
AppSecret string `json:"appSecret"`
|
||||
}{AppKey: appKey, AppSecret: appSecret}
|
||||
|
||||
// 登录
|
||||
resp, err := Post("https://open.ys7.com/api/lapp/token/get", req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
body := string(resp)
|
||||
|
||||
// 解析返回数据
|
||||
if gjson.Get(body, "code").Int() != 200 {
|
||||
return "", fmt.Errorf("登录失败: %s", gjson.Get(body, "msg").String())
|
||||
}
|
||||
|
||||
// 返回 AccessToken
|
||||
return gjson.Get(body, "data.accessToken").String(), nil
|
||||
})
|
||||
|
||||
func StartAI(device Ys7Device) {
|
||||
capture, err := device.Capture()
|
||||
if err != nil {
|
||||
fmt.Println("定时快照错误!", err)
|
||||
} else {
|
||||
get, err := g.Client().Get(gctx.New(), capture.Data.PicUrl)
|
||||
if err != nil {
|
||||
} else {
|
||||
os.WriteFile("", get.ReadAll(), 0o666)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user