package service import ( "car-service/extend/util" "car-service/models" "encoding/json" "errors" "fmt" "github.com/astaxie/beego" "strings" "sync" "time" ) var latestToken *TokenResult var lock sync.RWMutex func getCacheToken() string { lock.Lock() defer lock.Unlock() if latestToken != nil { return latestToken.Token } return "" } func updateToken(newT *TokenResult) { lock.Lock() defer lock.Unlock() latestToken = newT } func init() { newToken, err := RefreshToken() if err == nil { updateToken(newToken) } ticker := time.NewTicker(time.Hour * 20) go func() { for { select { case <-ticker.C: if t,err := RefreshToken();err == nil { updateToken(t) } default: time.Sleep(time.Second * 5) } } }() } //批量解绑别名 func UnbindAlias(cid string) (bool, error) { var uc models.UserClient ucList := uc.GetByCid(cid) if ucList ==nil { return true,errors.New("ucList is nil") } var aliasArr []string for _,als := range ucList { aliasArr = append(aliasArr, als.PhoneNum) } appId := beego.AppConfig.String("pushAppId") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId retryTimes := 0 ReTry: token := getCacheToken() if token == "" { return false, errors.New("token is nil") } url := baseUrl + "/user/alias" header := map[string]string { "token": token, } caArr := make([]map[string]string, 0) for _,as := range aliasArr { caArr = append(caArr, map[string]string{ "cid": cid, "alias": as, }) } reqBody := map[string]interface{}{ "data_list": caArr, } b,err := util.DoDeleteRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, header) if err !=nil { fmt.Println("DoDelete err:", err) return false, err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return false, err } if result.Code == 0 { //解绑成功 return true, nil } else if result.Code == 10001 { //token过期 if retryTimes <=3 { newToken, err := RefreshToken() if err == nil { updateToken(newToken) retryTimes++ goto ReTry } } } return false, errors.New(result.Msg) } //一个别名最多允许绑定10个cid func BindAlias(cid, alias string) (bool, error) { appId := beego.AppConfig.String("pushAppId") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId retryTimes := 0 ReTry: token := getCacheToken() if token == "" { return false, errors.New("token is nil") } url := baseUrl+"/user/alias" caArr := make([]map[string]string, 0) caArr = append(caArr, map[string]string{ "cid": cid, "alias": alias, }) reqBody := map[string]interface{} { "data_list":caArr, } header := map[string]string { "token": token, } b, err := util.DoPostRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, nil, header) if err !=nil { fmt.Println("DoPost err:", err) return false, err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return false, err } if result.Code == 0 { //绑定成功 return true, nil } else if result.Code == 10001 { //token过期 if retryTimes <=3 { newToken, err := RefreshToken() if err == nil { updateToken(newToken) retryTimes++ goto ReTry } } } return false, errors.New(result.Msg) } type PushResult struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } type TokenResult struct { ExpireTime string `json:"expire_time"` Token string `json:"token"` } type CreatePushMsgResult struct { TaskId string `json:"taskid"` } func createPushMsg(title string, msg string) (bool,string, error) { appId := beego.AppConfig.String("pushAppId") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId retryTimes := 0 ReTry: token := getCacheToken() if token == "" { return false, "", errors.New("token is nil") } url := baseUrl+"/push/list/message" intent := "intent:#Intent;action=android.intent.action.oppopush;launchFlags=0x14000000;component=uni.UNIEDF0B5C/io.dcloud.PandoraEntry;S.UP-OL-SU=true;S.title="+title+";S.content="+msg+";S.payload=test;end" androidPush := map[string]map[string]map[string]string { "ups": { "notification": { "title": title, "body": msg, "big_text": msg, "click_type": "intent", "intent": intent, }, }, } iosPush := map[string]interface{}{ "type":"notify", "payload": "育英中学停车", "aps":map[string]interface{}{ "alert":map[string]string{ "title": title, "body": msg, }, "content-available":0, }, } reqBody := map[string]interface{} { "request_id": time.Now().Format("20060102150405") + util.GenValidateCode(6), "settings":map[string]int { "ttl": 8 * 3600 * 1000, }, "push_message": map[string]map[string]string { "notification": { "title": title, "body": msg, "click_type": "intent", "intent": intent, }, }, "push_channel": map[string]interface{} { "android": androidPush, "ios":iosPush, }, } header := map[string]string { "token": token, } b, err := util.DoPostRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, nil, header) if err !=nil { fmt.Println("DoPost err:", err) return false, "",err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return false, "",err } if result.Code == 0 { rb,uErr := json.Marshal(result.Data) if uErr == nil { var tResult CreatePushMsgResult if uErr =json.Unmarshal(rb, &tResult); uErr ==nil { return true, tResult.TaskId, nil } else { return false, "", uErr } } else { return false, "", uErr } } else if result.Code == 10001 { //token过期 if retryTimes <=3 { newToken, err := RefreshToken() if err == nil { updateToken(newToken) retryTimes++ goto ReTry } } } return false, "", errors.New(result.Msg) } //对已注册的用户进行消息推送。调用此接口前需调用创建消息接口设置消息内容 func PushByAlias(title string, msg string, isTest bool) (bool, error, []string) { var aliasArr []string pushUserM := make(map[string]string) var userE models.User allUsers, _ := userE.GetAllUsers() if allUsers != nil { for _,u := range allUsers { pushUserM[u.Id] = u.PhoneNum } } if len(pushUserM) == 0 { return false,errors.New("len(pushUserM) == 0"),aliasArr } carPersonM := make(map[string]string) //以车牌号为key,value是hik的personId csv := NewCarService() carPersons := csv.GetVehicleListByPerson("") if carPersons != nil { for _, cp := range carPersons { ncPlateNo := preDealPlateNo(cp.PlateNo) //去掉汉字,D和0替换成* if ncPlateNo != "" { carPersonM[ncPlateNo] = cp.PersonId } } } delPersonIdM := make(map[string]string) spaceNos := csv.FindSpaceNo("") for _,sn := range spaceNos { if sn.State == 1 && sn.PlateNo != "" { //已经把车停到停车场的车主,不再推送消息 realPlateNo := preDealPlateNo(sn.PlateNo) if realPlateNo != "" { if pId,ok := carPersonM[realPlateNo];ok { delPersonIdM[pId] = pId delete(carPersonM, realPlateNo) } } } } var uc models.UserClient for _,personId := range carPersonM { if phoneNum,ok := pushUserM[personId]; ok { //此人已注册到系统,并且车不在停车库内 if _,in := delPersonIdM[personId];!in { if uc.Exist(phoneNum) { aliasArr = append(aliasArr, phoneNum) } } } } lenAS := len(aliasArr) if lenAS == 0 { return false, errors.New("没有推送目标,aliasArr is empty"),aliasArr } if isTest { //只给内部手机号推 testPhones := beego.AppConfig.String("testPushPhones") if testPhones == "" { return false, errors.New("test push aliasArr is empty"),aliasArr } aliasArr = strings.Split(testPhones, ",") } cResult, taskId, ce := createPushMsg(title, msg) fmt.Println("createPushMsg taskId:", taskId, "cResult:",cResult, "err:", ce) if !cResult { return false, errors.New("创建推送前置消息失败"),aliasArr } //alias 单次推送长度上限是200 pushTime := 1 if lenAS > 200 { pushTime = lenAS / 200 if lenAS % 200 >0 { pushTime++ } } isSuccess := false for i:=0;i0 { flag, e := doPush(taskId, curAliasArr) if e != nil { fmt.Println("doPush err:", e) } if flag { isSuccess = true } } } if isSuccess { return true, nil,aliasArr } return false, errors.New("推送失败"),aliasArr } //预处理车牌号,去除首个汉字,以及忽略D和0 func preDealPlateNo(pn string) string { if pn != "" { r := []rune(pn) ncStr := string(r[1:]) newPlateNo := strings.ReplaceAll(ncStr, "D", "*") newPlateNo = strings.ReplaceAll(newPlateNo, "0", "*") return newPlateNo } return "" } func doPush(taskId string, aliasArr []string) (bool,error) { appId := beego.AppConfig.String("pushAppId") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId retryTimes := 0 ReTry: token := getCacheToken() if token == "" { return false, errors.New("token is nil") } url := baseUrl+"/push/list/alias" reqBody := map[string]interface{} { "audience":map[string]interface{}{ "alias": aliasArr, }, "taskid": taskId, "is_async": false, } header := map[string]string { "token": token, } b, err := util.DoPostRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, nil, header) if err !=nil { fmt.Println("DoPost err:", err) return false, err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return false, err } if result.Code == 0 { return true, nil } else if result.Code == 10001 { //token过期 if retryTimes <=3 { newToken, err := RefreshToken() if err == nil { updateToken(newToken) retryTimes++ goto ReTry } } } else { fmt.Println("推送结果:", result) } return false, errors.New("推送失败") } func NightPush(title string, msg string) (bool, error, []string, []string) { var aliasArr []string var carOwners []string pushUserM := make(map[string]string) var userE models.User allUsers, _ := userE.GetAllUsers() if allUsers != nil { for _,u := range allUsers { pushUserM[u.Id] = u.PhoneNum } } if len(pushUserM) == 0 { return false, errors.New("len(pushUserM) == 0"), aliasArr, carOwners } carPersonM := make(map[string]string) csv := NewCarService() carPersons := csv.GetVehicleListByPerson("") if carPersons != nil { for _, cp := range carPersons { cnPlateNo := preDealPlateNo(cp.PlateNo) if cnPlateNo != "" { carPersonM[cnPlateNo] = cp.PersonId } } } //夜间某些领导的车可以停在车库内 var vipPlateNoArr []string vipArr := strings.Split(beego.AppConfig.String("nightVipPlateNos"), ",") if vipArr != nil && len(vipArr) >0 { for _,po := range vipArr { if po != "" { cnPo := preDealPlateNo(po) if cnPo != "" { vipPlateNoArr = append(vipPlateNoArr, cnPo) } } } } hikPersonMap := csv.GetHikPersonMap() spaceNos := csv.FindSpaceNo("") var uc models.UserClient for _,sn := range spaceNos { if sn.State == 1 { //车尚在停车场的车牌 inCnPlateNo := preDealPlateNo(sn.PlateNo) if inCnPlateNo != "" { if !isVipCar(inCnPlateNo, vipPlateNoArr) { if personId,ok := carPersonM[inCnPlateNo];ok { if phoneNum,ok := pushUserM[personId]; ok { if uc.Exist(phoneNum) { aliasArr = append(aliasArr, phoneNum) } } if v,ex := hikPersonMap[personId]; ex { carOwners = append(carOwners, sn.PlateNo+"("+v.PersonName+")") } } else { //找不到车主,即视为临时车,将车牌作为车主姓名推送 if sn.PlateNo == "无车牌" { carOwners = append(carOwners, sn.PlateNo) } else { carOwners = append(carOwners, sn.PlateNo+"()") } } } } } } if len(aliasArr) == 0 { fmt.Println("没有推送目标,aliasArr is empty") return false, errors.New("没有推送目标,aliasArr is empty"), aliasArr, carOwners } cResult, taskId, ce := createPushMsg(title, msg) if !cResult { fmt.Println("createPushMsg taskId:", taskId, "err:", ce) return false, errors.New("创建推送前置消息失败"), aliasArr, carOwners } b,e := doPush(taskId, aliasArr) return b,e, aliasArr, carOwners } //此方法做测试使用 //获取停留在车库内车主的姓名,找不到姓名返回这牌 func GetLeftCarOwners() []string { var carOwners []string pushUserM := make(map[string]string) var userE models.User allUsers, _ := userE.GetAllUsers() if allUsers != nil { for _,u := range allUsers { pushUserM[u.Id] = u.PhoneNum } } carPersonM := make(map[string]string) csv := NewCarService() carPersons := csv.GetVehicleListByPerson("") if carPersons != nil { for _, cp := range carPersons { cnPlateNo := preDealPlateNo(cp.PlateNo) if cnPlateNo != "" { carPersonM[cnPlateNo] = cp.PersonId } } } //夜间某些领导的车可以停在车库内 var vipPlateNoArr []string vipArr := strings.Split(beego.AppConfig.String("nightVipPlateNos"), ",") if vipArr != nil && len(vipArr) >0 { for _,po := range vipArr { if po != "" { cnPo := preDealPlateNo(po) if cnPo != "" { vipPlateNoArr = append(vipPlateNoArr, cnPo) } } } } hikPersonMap := csv.GetHikPersonMap() spaceNos := csv.FindSpaceNo("") for _,sn := range spaceNos { if sn.State == 1 { //车尚在停车场的车牌 inCnPlateNo := preDealPlateNo(sn.PlateNo) if inCnPlateNo != "" { if !isVipCar(inCnPlateNo, vipPlateNoArr) { if personId,ok := carPersonM[inCnPlateNo];ok { if v,ex := hikPersonMap[personId]; ex { carOwners = append(carOwners, sn.PlateNo+"("+v.PersonName+")") } } else { //找不到车主,即视为临时车,将车牌作为车主姓名推送 if sn.PlateNo == "无车牌" { carOwners = append(carOwners, sn.PlateNo) } else { carOwners = append(carOwners, sn.PlateNo+"()") } } } } } } return carOwners } //vip车主,不接收请离开的通知,也不通知管理员这个车还在车库内 func isVipCar(targetPlateNo string, vipPlateNoArr []string) bool { b := false if vipPlateNoArr != nil { for _,v := range vipPlateNoArr { if targetPlateNo == v { b = true break } } } return b } //如果夜间有给车库内的车主推送“请尽快驶离”的消息,则告知管理员 有哪些车尚停在车库内 func Push2Manager(title string, msg string) (bool, error, []string) { if len(msg) > 256 { fmt.Println("夜间停留车太多,message:", msg) r := []rune(msg) msg = string(r[:125])+"..." } managers := beego.AppConfig.String("nightManagerPhones") if managers == "" { return false, errors.New("夜间推送管理员手机号未配置"), []string{} } managerArr := strings.Split(managers, ",") if len(managerArr) == 0 { fmt.Println("没有推送目标,managerArr is empty") return false, errors.New("管理员手机号未配置"), managerArr } cResult, taskId, ce := createPushMsg(title, msg) if !cResult { fmt.Println("createPushMsg taskId:", taskId, "err:", ce) return false, errors.New("创建推送前置消息失败"), managerArr } b,e := doPush(taskId, managerArr) return b,e, managerArr } /* func PushAll(title string, msg string) (bool,error) { appId := beego.AppConfig.String("pushAppId") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId retryTimes := 0 ReTry: token := getCacheToken() if token == "" { return false, errors.New("token is nil") } url := baseUrl+"/push/all" intent := "intent:#Intent;action=android.intent.action.oppopush;launchFlags=0x14000000;component=uni.UNIEDF0B5C/io.dcloud.PandoraEntry;S.UP-OL-SU=true;S.title="+title+";S.content="+msg+";S.payload=test;end" androidPush := map[string]map[string]map[string]string { "ups": { "notification": { "title": title, "body": msg, "click_type": "intent", "intent": intent, }, }, } iosPush := map[string]interface{}{ "type":"notify", "payload":"育英中学停车", "aps":map[string]interface{}{ "alert":map[string]string{ "title": title, "body": msg, }, "content-available":0, //"sound":"com.gexin.ios.silence", //"category":"ACTIONABLE", }, "auto_badge":"+1", } reqBody := map[string]interface{} { "request_id": time.Now().Format("20060102150405") + util.GenValidateCode(6), "settings":map[string]int { "ttl": 8 * 3600 * 1000, }, "audience": "all", "push_message": map[string]map[string]string { "notification": { "title": title, "body": msg, "click_type": "intent", "intent": intent, }, }, "push_channel": map[string]interface{} { "android": androidPush, "ios": iosPush, }, } header := map[string]string { "token": token, } b, err := util.DoPostRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, nil, header) if err !=nil { fmt.Println("DoPost err:", err) return false, err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return false, err } if result.Code == 0 { return true, nil } else if result.Code == 10001 { //token过期 if retryTimes <=3 { newToken, err := RefreshToken() if err == nil { updateToken(newToken) retryTimes++ goto ReTry } } } else { fmt.Println("推送结果:", result) } return false, errors.New("推送失败") }*/ func RefreshToken() (*TokenResult,error) { appId := beego.AppConfig.String("pushAppId") appKey := beego.AppConfig.String("pushAppKey") masterSecret := beego.AppConfig.String("pushMasterSecret") baseUrl := beego.AppConfig.String("pushBaseUrl") + appId url := baseUrl +"/auth" timestamp := fmt.Sprintf("%v", time.Now().UnixNano() / 1e6) sign := util.Sha256(appKey + timestamp + masterSecret) reqBody := map[string]interface{} { "sign": sign, "timestamp": timestamp, "appkey": appKey, } b, err := util.DoPostRequest(url, util.CONTENT_TYPE_UTF8_JSON, reqBody, nil, nil) if err !=nil { fmt.Println("DoPost err:", err) return nil, err } var result PushResult err = json.Unmarshal(b, &result) if err != nil { fmt.Println("unmarshal err:", err) return nil, err } if result.Code == 0 { db, err := json.Marshal(result.Data) if err != nil { fmt.Println("marshal err:", err) return nil, err } var t TokenResult err = json.Unmarshal(db, &t) if err != nil { fmt.Println("unmarshal err:", err) return nil, err } return &t, nil } return nil, errors.New("获取unipush平台token失败") }