Browse Source

加依尔隧道数据推送

dev
18209 1 month ago
parent
commit
641f5f329e
  1. 8
      .idea/.gitignore
  2. 9
      .idea/goInOut.iml
  3. 8
      .idea/modules.xml
  4. 6
      .idea/vcs.xml
  5. 196
      adaptors/安心云es数据to南京智行.go
  6. 1
      adaptors/知物云es主题特征to中交华联.go
  7. 30
      configFiles/弃用备份/config_安心云加依尔2号隧道_南京智行.yaml
  8. 36
      consumers/JYES_NJZX/dataModel.go
  9. 3160
      consumers/JYES_NJZX/protoDataFiles/MonitorDataProtocol.pb.go
  10. 2
      consumers/consumerGZG2ZJHL.go
  11. 1
      consumers/consumerHBJCAS.go
  12. 205
      consumers/consumerJYESNJZX.go
  13. 3
      consumers/consumerManage.go
  14. 3
      go.mod
  15. 4
      go.sum
  16. 10
      utils/timeRange.go

8
.idea/.gitignore

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/goInOut.iml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/goInOut.iml" filepath="$PROJECT_DIR$/.idea/goInOut.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

196
adaptors/安心云es数据to南京智行.go

@ -0,0 +1,196 @@
package adaptors
import (
"encoding/hex"
"encoding/json"
"fmt"
"goInOut/consumers/JYES_NJZX"
"goInOut/consumers/JYES_NJZX/protoDataFiles"
"goInOut/dbOperate"
"goInOut/models"
"google.golang.org/protobuf/proto"
"log"
"strings"
"time"
)
// Adaptor_AXYES_NJZX 安心云依加尔山es 特征数据 to 南京智行平台
type Adaptor_AXYES_NJZX struct {
//传感器code转换信息
PointInfo map[int64]map[int64]int64
StructInfo map[int64]int64
//一些必要信息
Info map[string]string
Redis *dbOperate.RedisHelper
}
func (the *Adaptor_AXYES_NJZX) Transform(structId int64, factorId int, rawMsg string) []NeedPush {
//es查到的数据分装进结构体里面
esAggDateHistogram := JYES_NJZX.EsThemeDateValue{}
var needPush []NeedPush
err := json.Unmarshal([]byte(rawMsg), &esAggDateHistogram)
if err != nil {
return nil
}
Payload := the.EsDataValueChangeToNJZX(structId, factorId, esAggDateHistogram)
if len(Payload) == 0 {
return needPush
}
needPush = append(needPush, NeedPush{
Payload: Payload,
})
return needPush
}
func (the *Adaptor_AXYES_NJZX) EsDataValueChangeToNJZX(structId int64, factorId int, esDataValue JYES_NJZX.EsThemeDateValue) (result []byte) {
buckets := esDataValue.Hits.Hits
//数据汇总
complexData := &protoDataFiles.ComplexData{}
for _, sensorBucket := range buckets {
sensorId := sensorBucket.Source.SensorName //安心云de测点id
//优先redis获取
station := models.Station{}
k1 := fmt.Sprintf("station:%d", sensorId)
errRedis := the.Redis.GetObj(k1, &station)
if errRedis != nil {
log.Printf("redis 获取[s:%d,f:%d]测点[%d]标签异常", structId, factorId, sensorId)
continue
}
monitorCodeStr := the.getPointCodeFromLabel(station.Labels)
if monitorCodeStr == "" {
log.Printf("redis 获取[s:%d,f:%d]测点[%d],标签信息[%s]转换int64异常,跳过", structId, factorId, sensorId, station.Labels)
continue
}
dataDefinition := the.ChangeToNJZXData(factorId, monitorCodeStr, sensorBucket)
complexData.SensorData = append(complexData.SensorData, dataDefinition)
}
//v, _ := json.Marshal(complexData)
//log.Printf("[struct:%d,factor:%d] 特征数据=> %s", structId, factorId, v)
result, _ = proto.Marshal(complexData)
log.Printf("[struct:%d,factor:%d] protobuf数据=> %s", structId, factorId, hex.EncodeToString(result))
return result
}
func (the *Adaptor_AXYES_NJZX) getMonitorTypeByFactorId(factorId int) protoDataFiles.MonitoryType {
//监测因素 2温湿度 4温度 18裂缝检测
//103净空收敛 102拱顶沉降 96二次衬彻应变
//107道床及拱腰结构沉降 156风速 578风向
switch factorId {
case 2: //温湿度
return protoDataFiles.MonitoryType_RHS
case 4: //温度
return protoDataFiles.MonitoryType_TMP
case 18: //裂缝检测
return protoDataFiles.MonitoryType_CRK
case 103: //净空收敛
return protoDataFiles.MonitoryType_INC //无对应
case 102: //拱顶沉降
return protoDataFiles.MonitoryType_CRK //无对应
case 96: //二次衬彻应变
return protoDataFiles.MonitoryType_RSG
case 107: //道床及拱腰结构沉降
return protoDataFiles.MonitoryType_VIB //无对应
case 156: //风速
return protoDataFiles.MonitoryType_WDS
case 578: //风向
return protoDataFiles.MonitoryType_WDD
default:
log.Printf("factorId=%d,无匹配的MonitorType", factorId)
return protoDataFiles.MonitoryType_RHS
}
}
func (the *Adaptor_AXYES_NJZX) parseTimeToTimestamp(timeStr string) (int64, error) {
// 解析时间字符串为 time.Time 对象
parsedTime, err := time.Parse(time.RFC3339, timeStr)
if err != nil {
return 0, err
}
// 返回 Unix 时间戳(秒数)
return parsedTime.Unix(), nil
}
func (the *Adaptor_AXYES_NJZX) ChangeToNJZXData(factorId int, monitorCodeStr string, dateBucket JYES_NJZX.Hits) *protoDataFiles.SensorData {
Atime, _ := the.parseTimeToTimestamp(dateBucket.Source.CollectTime)
monitoryType := the.getMonitorTypeByFactorId(factorId)
dataDefinitionData := &protoDataFiles.SensorData{
MonitorType: monitoryType,
SensorNo: monitorCodeStr,
UpTime: Atime,
}
switch factorId {
case 2: //温湿度
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Rhs{Rhs: &protoDataFiles.RHSRealTime{
Temperature: []float32{float32(dateBucket.Source.Data["temperature"])},
Humidity: []float32{float32(dateBucket.Source.Data["humidity"])},
}}
case 4: //温度
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Tmp{Tmp: &protoDataFiles.TMPRealTime{
Temperature: []float32{float32(dateBucket.Source.Data["temperature"])},
}}
case 18: //裂缝检测
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Crk{Crk: &protoDataFiles.CRKRealTime{
CrackWidth: []float32{float32(dateBucket.Source.Data["crack"])},
}}
case 103: //净空收敛//123456789
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Rhs{Rhs: &protoDataFiles.RHSRealTime{
Temperature: []float32{float32(dateBucket.Source.Data["crack"])},
Humidity: []float32{float32(dateBucket.Source.Data["crack"])},
}}
case 102: //拱顶沉降//123456789
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Rhs{Rhs: &protoDataFiles.RHSRealTime{
Temperature: []float32{float32(dateBucket.Source.Data["crack"])},
Humidity: []float32{float32(dateBucket.Source.Data["crack"])},
}}
case 96: //二次衬彻应变
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Rsg{Rsg: &protoDataFiles.RSGRealTime{
Strain: []float32{float32(dateBucket.Source.Data["strain"])},
}}
case 107: //道床及拱腰结构沉降//123456789
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Rhs{Rhs: &protoDataFiles.RHSRealTime{
Temperature: []float32{float32(dateBucket.Source.Data["crack"])},
Humidity: []float32{float32(dateBucket.Source.Data["crack"])},
}}
case 156: //风速
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Wds{Wds: &protoDataFiles.WDSRealTime{
WindSpeed: []float32{float32(dateBucket.Source.Data["speed"])},
}}
case 578: //风向
dataDefinitionData.DataBody = &protoDataFiles.SensorData_Wdd{Wdd: &protoDataFiles.WDDRealTime{
WindDirection: []float32{float32(dateBucket.Source.Data["direction"])},
}}
}
return dataDefinitionData
}
func (the *Adaptor_AXYES_NJZX) getUniqueCode(structId int64) (uniqueCode int64) {
if v, ok := the.StructInfo[structId]; ok {
uniqueCode = v
}
return uniqueCode
}
func (the *Adaptor_AXYES_NJZX) getPointCodeFromLabel(label string) string {
//解析label {code:wd01}
pointUniqueCode := ""
if len(label) > 3 {
newLabel := strings.TrimLeft(label, "{code:")
str := strings.TrimRight(newLabel, "}")
if str == "" {
log.Printf("测点标签转换异常[%s]", label)
}
pointUniqueCode = str
}
return pointUniqueCode
}

1
adaptors/知物云es主题特征to中交华联.go

@ -149,6 +149,7 @@ func (the *Adaptor_ZWYES_ZJHL) EsAggTopToHBJCAS(structId int64, factorId int, es
log.Printf("[struct:%d,factor:%d] protobuf数据=> %s", structId, factorId, hex.EncodeToString(result))
return result
}
func (the *Adaptor_ZWYES_ZJHL) getMonitorTypeByFactorId(factorId int) protoFiles_zjhl_v3.MonitoryType {
//结构温度4 桥墩倾斜 15 裂缝18 支座位移20 桥面振动28 风速156 加速度三项监测592
switch factorId {

30
configFiles/弃用备份/config_安心云加依尔2号隧道_南京智行.yaml

@ -0,0 +1,30 @@
consumer: consumerJYESNJZX
ioConfig:
in:
http:
url: https://esproxy.anxinyun.cn/savoir_themes/_search
out:
mqtt:
host: 120.205.24.17
port: 1883
userName:
password:
clientId: bridge_goinout
topics:
t/t6540000001/rt
monitor:
cron10min: 8/10 * * * * #31 0/1 * * * #
info:
rc4key: t/gzgyy0219
queryComponent:
redis:
address: 10.8.30.160:30379 #按照实际项目来
#结构物id对应
structInfo:
#加依尔4983 -> 映射对方平台id
4983: 56232
#点位id对应信息
pointInfo: #监测因素 2温湿度 4温度 18裂缝检测 103净空收敛 102拱顶沉降 96二次衬彻应变 107道床及拱腰结构沉降 156风速 578风向

36
consumers/JYES_NJZX/dataModel.go

@ -0,0 +1,36 @@
package JYES_NJZX
type EsThemeDateValue struct {
Took int `json:"took"`
TimedOut bool `json:"timed_out"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
} `json:"_shards"`
Hits struct {
Total int `json:"total"`
MaxScore float64 `json:"max_score"`
Hits []Hits `json:"hits"`
} `json:"hits"`
}
type Hits struct {
Index string `json:"_index"`
Type int `json:"_type"`
Id string `json:"_id"`
Score int `json:"_score"`
Source Source `json:"_source"`
}
type Source struct {
SensorName string `json:"sensor_name"`
FactorName string `json:"factor_name"`
FactorProtoCode string `json:"factor_proto_code"`
Data map[string]float64 `json:"data"`
Factor int `json:"factor"`
CollectTime string `json:"collect_time"`
Sensor int `json:"sensor"`
Structure int `json:"structure"`
IotaDevice []string `json:"iota_device"`
CreateTime string `json:"create_time"`
}

3160
consumers/JYES_NJZX/protoDataFiles/MonitorDataProtocol.pb.go

File diff suppressed because it is too large

2
consumers/consumerGZG2ZJHL.go

@ -128,6 +128,7 @@ func (the *consumerGZG2ZJHL) getStructIds() []int64 {
}
return structIds
}
func (the *consumerGZG2ZJHL) getEs1HourAggData() {
start, end := utils.GetTimeRangeByHour(-1)
log.Printf("查询数据时间范围 %s - %s", start, end)
@ -187,7 +188,6 @@ func (the *consumerGZG2ZJHL) getEs10minAggData() {
}
}
}
}
func (the *consumerGZG2ZJHL) crc16rc4(transBytes []byte) []byte {

1
consumers/consumerHBJCAS.go

@ -201,6 +201,7 @@ func (the *consumerHBJCAS) crc16rc4(transBytes []byte) []byte {
cipher1.XORKeyStream(dest1, needRC4)
return dest1
}
func (the *consumerHBJCAS) getESQueryStrByHour(structureId int64, factorId int, start, end string) string {
aggSubSql := getEsAggSubSqlByAxyFactorId(factorId)
esQuery := fmt.Sprintf(`

205
consumers/consumerJYESNJZX.go

@ -0,0 +1,205 @@
package consumers
import (
"crypto/rc4"
"encoding/hex"
"fmt"
"goInOut/adaptors"
"goInOut/consumers/HBJCAS"
"goInOut/dbOperate"
"goInOut/monitors"
"goInOut/utils"
"gopkg.in/yaml.v3"
"log"
"time"
)
type consumerJYESNJZX struct {
//数据缓存管道
ch chan []adaptors.NeedPush
//具体配置
Info HBJCAS.ConfigFile
InHttp *dbOperate.HttpHelper
outMqtt *dbOperate.MqttHelper
monitor *monitors.CommonMonitor
infoRedis *dbOperate.RedisHelper
}
func (the *consumerJYESNJZX) LoadConfig(cfgStr string) {
// 将 yaml 格式的数据解析到结构体中
err := yaml.Unmarshal([]byte(cfgStr), &the.Info)
if err != nil {
log.Printf("读取配置文件[%s]异常 err=%v", cfgStr, err.Error())
panic(err)
}
}
func (the *consumerJYESNJZX) Initial(cfg string) error {
the.LoadConfig(cfg)
err := the.InputInitial()
if err != nil {
return err
}
err = the.OutputInitial()
if err != nil {
return err
}
err = the.infoComponentInitial()
return err
}
func (the *consumerJYESNJZX) InputInitial() error {
the.ch = make(chan []adaptors.NeedPush, 200)
//数据入口
the.InHttp = &dbOperate.HttpHelper{Url: the.Info.IoConfig.In.Http.Url, Token: ""}
the.monitor = &monitors.CommonMonitor{
MonitorHelper: &monitors.MonitorHelper{},
}
the.monitor.Start()
for _, cron := range the.Info.Monitor {
the.monitor.RegisterTask(cron, the.getEsAggData)
}
return nil
}
func (the *consumerJYESNJZX) OutputInitial() error {
//数据出口
the.outMqtt = dbOperate.MqttInitial(
the.Info.IoConfig.Out.Mqtt.Host,
the.Info.IoConfig.Out.Mqtt.Port,
the.Info.IoConfig.Out.Mqtt.ClientId,
the.Info.IoConfig.Out.Mqtt.UserName,
the.Info.IoConfig.Out.Mqtt.Password,
false, //按照具体项目来
"")
return nil
}
func (the *consumerJYESNJZX) infoComponentInitial() error {
//数据出口
addr := the.Info.QueryComponent.Redis.Address
the.infoRedis = dbOperate.NewRedisHelper("", addr)
return nil
}
func (the *consumerJYESNJZX) getEsAggData() {
start, end := utils.GetTimeRangeBy1minByOffset(-5)
//取向前偏移5分钟的一分钟的数据
log.Printf("查询数据时间范围 %s - %s", start, end)
factorIds := []int{2, 4, 18, 103, 102, 96, 107, 156, 578}
//监测因素 2温湿度 4温度 18裂缝检测 103净空收敛 102拱顶沉降 96二次衬彻应变 107道床及拱腰结构沉降 156风速 578风向
//架伊尔大桥的结构物id
var structId int64
for strutId, _ := range the.Info.StructInfo {
structId = strutId
}
adaptor := the.getAdaptor()
adaptor.PointInfo = the.Info.PointInfo
adaptor.StructInfo = the.Info.StructInfo
for _, factorId := range factorIds {
esQuery := the.getESQueryStr(structId, factorId, start, end)
auth := map[string]string{"Authorization": "Bear 85a441d4-022b-4613-abba-aaa8e2693bf7"}
esAggResultStr := the.InHttp.HttpGetWithHeader(esQuery, auth)
log.Printf("esAggResultStr[%s]", esAggResultStr)
needPushes := adaptor.Transform(structId, factorId, esAggResultStr)
for i := range needPushes {
needPushes[i].Payload = the.crc16rc4(needPushes[i].Payload)
log.Printf("topic[%s],Payload=> %s", needPushes[i].Topic, hex.EncodeToString(needPushes[i].Payload))
}
if len(needPushes) > 0 {
the.ch <- needPushes
}
}
}
func (the *consumerJYESNJZX) getAdaptor() (adaptor adaptors.Adaptor_AXYES_NJZX) {
return adaptors.Adaptor_AXYES_NJZX{
Redis: the.infoRedis,
}
}
func (the *consumerJYESNJZX) crc16rc4(transBytes []byte) []byte {
resultByCrc16 := utils.NewCRC16CCITT().GetWCRCin(transBytes)
needRC4 := append(transBytes, resultByCrc16...)
rc4KeyStr, ok := the.Info.OtherInfo["rc4key"]
if !ok {
log.Panicf("未配置 rc4key")
}
rc4Key := []byte(rc4KeyStr) //the.RC4Key
// 加密操作
dest1 := make([]byte, len(needRC4))
rc4.NewCipher(rc4Key)
cipher1, _ := rc4.NewCipher(rc4Key)
cipher1.XORKeyStream(dest1, needRC4)
return dest1
}
func (the *consumerJYESNJZX) getESQueryStr(structureId int64, factorId int, start, end string) string {
esQuery := fmt.Sprintf(`
{
"size": 20,
"query": {
"bool": {
"must": [
{
"term": {
"structure": {
"value": %d
}
}
},
{
"term": {
"factor": {
"value": %d
}
}
},
{
"range": {
"collect_time": {
"gte": "%s",
"lte": "%s"
}
}
}
]
}
},
}
`, structureId, factorId, start, end)
return esQuery
}
func (the *consumerJYESNJZX) Work() {
go func() {
for {
needPushList := <-the.ch
if len(the.ch) > 0 {
log.Printf("取出ch数据,剩余[%d] ", len(the.ch))
}
for _, push := range needPushList {
if push.Topic != "" {
the.outMqtt.Publish(push.Topic, push.Payload)
continue
}
//没有标记topic 的 按照配置文件里面的推送
for _, topic := range the.Info.IoConfig.Out.Mqtt.Topics {
the.outMqtt.Publish(topic, push.Payload)
}
}
time.Sleep(100 * time.Millisecond)
}
}()
}

3
consumers/consumerManage.go

@ -38,6 +38,9 @@ func GetConsumer(name string) (consumer IConsumer) {
case "consumerGZG2ZJHL":
consumer = new(consumerGZG2ZJHL)
case "consumerJYESNJZX":
consumer = new(consumerJYESNJZX)
default:
consumer = nil
}

3
go.mod

@ -14,7 +14,7 @@ require (
github.com/redis/go-redis/v9 v9.7.0
github.com/robfig/cron/v3 v3.0.1
golang.org/x/text v0.17.0
google.golang.org/protobuf v1.31.0
google.golang.org/protobuf v1.36.6
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
)
@ -28,6 +28,7 @@ require (
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect

4
go.sum

@ -33,6 +33,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
@ -171,6 +173,8 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

10
utils/timeRange.go

@ -35,3 +35,13 @@ func GetTimeRangeBy10minByOffset(offsetMin int) (start, stop string) {
stop = startTime.Format("2006-01-02T15:04:00.000+08:00")
return
}
func GetTimeRangeBy1minByOffset(offsetMin int) (start, stop string) {
offset := time.Duration(offsetMin) * time.Minute
now := time.Now().Add(offset)
m := now.Second() // 获取当前时间的秒数,1分钟的偏移点
startTime := now.Add(time.Second * -1 * time.Duration(m)) // 调整到当前分钟的开始
start = startTime.Format("2006-01-02T15:04:00.000+08:00") // 格式化为所需的时间字符串
stop = startTime.Add(time.Minute).Format("2006-01-02T15:04:00.000+08:00") // 结束时间是当前分钟的下一分钟
return
}

Loading…
Cancel
Save