From 9358e5ec2d2b65fec4ef9a1be7d1a1e1e2cf9d2d Mon Sep 17 00:00:00 2001
From: liuxiaolong <liuxiaolong@aiotlink.com>
Date: 星期一, 10 八月 2020 16:31:34 +0800
Subject: [PATCH] new push schedule

---
 conf/app.conf           |    3 
 service/msgPush.go      |  358 +++++++++++++++++++++++++++++++++++++++
 models/car.go           |    4 
 controllers/user.go     |    5 
 extend/util/httpUtil.go |   35 +++
 service/carService.go   |   20 +-
 service/userService.go  |   10 +
 controllers/car.go      |   52 +++++
 8 files changed, 464 insertions(+), 23 deletions(-)

diff --git a/conf/app.conf b/conf/app.conf
index 8536dda..adad79f 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -7,8 +7,6 @@
 sqlconn =
 gendoc = true
 downdoc = true
-latestVersion = 1.0.1
-latestUrl = http://www.baidu.com
 juheweihaokey = 8c0efc4439080ef6c6aa2c29688c9550
 pushAppId = WfOtCAmf0w6jS3B59V0mb5
 pushAppKey = hVBAHDe85F9ZviAUYknxb4
@@ -20,3 +18,4 @@
 hikUrl = https://172.16.35.49:443
 hikAppKey = 26577698
 hikAppSecret = ZHhxujl06e0e5jsJLaiB
+pushLowerLimit = 5
diff --git a/controllers/car.go b/controllers/car.go
index 2329ce3..4d4141b 100644
--- a/controllers/car.go
+++ b/controllers/car.go
@@ -17,11 +17,17 @@
 }
 
 //瀹炴椂璁$畻鍓╀綑杞︿綅鏁伴噺锛岃揪鍒版潯浠跺氨鎺ㄩ��
+//瑕佹眰锛�
+//1.鍋滆溅鏁伴噺灏忎簬绛変簬5涓帹閫侊紝5涓互涓嬬殑鏁伴噺鍙樺寲閮芥帹閫侊紝鎺ㄩ�佺粰宸叉敞鍐屾墜鏈哄彿鐨勭敤鎴凤紝鏈敞鍐岀殑涓嶆帹
+//2.婊¤冻涓婁竴鏉★紝濡傛灉姝よ溅杈嗗凡缁忚繘鍏ュ鏍″仠杞﹀満浜嗭紝灏变笉鍐嶇粰杩欎釜杞︾墝瀵瑰簲鐨勬墜鏈哄彿鎺ㄩ�佺┖浣欒溅浣嶆秷鎭�
+//3.鏅氫笂10鐐瑰埌10鐐瑰崐锛屾瘡闂撮殧10鍒嗛挓锛岀粰鍋滆溅鍦哄唴鐨勮溅杈嗘帹閫佹秷鎭細璇峰敖蹇┒鍑哄仠杞﹀満
 func ComputeSpaceLeftRealTime() {
 	ticker := time.NewTicker(3 * time.Second)
 	prePushLeft := 0
 	sv := service.NewCarService()
 	initCacheM := false
+	lowerLimit,_ := beego.AppConfig.Int("pushLowerLimit") //[0,5]
+	nightPushTimes := 0
 	for {
 		select {
 			case <-ticker.C:
@@ -29,13 +35,14 @@
 				if flag {
 					left := hikSta.Left
 					if !initCacheM {
-						models.SetSpaceNo(hikSta.TotalPermPlace)
+						models.SetSpaceNo(hikSta.TotalPlace)
 						initCacheM = true
 					}
-					if left <=5 && left != prePushLeft {
+
+					if left <=lowerLimit && left != prePushLeft {
 						go func() {
 							message := fmt.Sprintf("%s 鍓╀綑杞︿綅锛�%d涓�", time.Now().Format("2006-01-02 15:04:05"), left)
-							b, e := service.Push("鑲茶嫳鏅烘収鍋滆溅", message)
+							b, e := service.PushByAlias("鑲茶嫳鏅烘収鍋滆溅", message)
 
 							prePushLeft = left
 
@@ -44,11 +51,47 @@
 					}
 					updateSpaceLeft(left)
 				}
+				//鍒ゆ柇褰撳墠鏄惁鍦�22:00-22:30涔嬮棿
+				now := time.Now()
+				if now.Hour() == 21 && now.Minute()>=0 || now.Minute() <=29{
+					if now.Minute() == 0 {
+						if nightPushTimes ==0 {
+							go nightPush()
+							nightPushTimes++
+						}
+					} else if now.Minute() == 10 {
+						if nightPushTimes == 1 {
+							go nightPush()
+							nightPushTimes++
+						}
+					} else if now.Minute() == 20 {
+						if nightPushTimes == 2{
+							go nightPush()
+							nightPushTimes++
+						}
+					} else if now.Minute() == 29 {
+						if nightPushTimes == 3{
+							go nightPush()
+							nightPushTimes++
+						}
+					}
+				} else {
+					nightPushTimes = 0
+				}
 
 		default:
 			time.Sleep(500 * time.Millisecond)
 		}
 	}
+}
+
+func nightPush(){
+
+	message := fmt.Sprintf("%s 璇峰敖蹇┒鍑哄仠杞﹀満", time.Now().Format("2006-01-02 15:04:05"))
+	b, e := service.NightPush("鑲茶嫳鏅烘収鍋滆溅", message)
+
+	fmt.Println("b:", b,"e:",e, "message:", message)
+
 }
 
 var cacheSpaceLeft int
@@ -70,9 +113,6 @@
 // @Failure 403 {string} json ""
 // @router /statistic [get]
 func (c *CarController) Statistic() {
-	//sv := service.NewCarService()
-	//hikStc := sv.Statistic()
-	//left := hikStc.Left
 	left := getSpaceLeft()
 	sta := models.CarStatistic{
 		Left: left,
diff --git a/controllers/user.go b/controllers/user.go
index 2025f12..29b3f0c 100644
--- a/controllers/user.go
+++ b/controllers/user.go
@@ -63,15 +63,16 @@
 func (u *UserController) Login() {
 	phoneNum := u.GetString("phoneNum")
 	cod := u.GetString("code")
+	cid := u.GetString("cid")  //unipush clientid
 	resp := code.Code{}
 	fmt.Println("phoneNum:", phoneNum, "code:", cod)
-	if phoneNum == "" || cod == "" {
+	if phoneNum == "" || cod == ""{
 		resp.Success= false
 		resp.Status= http.StatusBadRequest
 		resp.Data= "鍙傛暟鏈夎"
 	} else {
 		var sv service.UserService
-		b, info, e := sv.Login(phoneNum, cod)
+		b, info, e := sv.Login(phoneNum, cod, cid)
 
 		if b {
 			resp.Success= true
diff --git a/extend/util/httpUtil.go b/extend/util/httpUtil.go
index 303612b..621755e 100644
--- a/extend/util/httpUtil.go
+++ b/extend/util/httpUtil.go
@@ -79,4 +79,39 @@
 		return resultBytes, err
 	}
 	return resultBytes, nil
+}
+
+//鏋勯�燿elete璇锋眰
+func DoDeleteRequest(url string, contentType string, body map[string]interface{}, headers map[string]string) ([]byte, error) {
+	var resultBytes []byte
+	var bodyJson []byte
+	if body != nil {
+		var err error
+		bodyJson, err = json.Marshal(body)
+		if err != nil {
+			return resultBytes, err
+		}
+	}
+	request, err := http.NewRequest("DELETE", url, bytes.NewBuffer(bodyJson))
+	if err != nil {
+		return resultBytes, err
+	}
+	request.Header.Set("Content-type", contentType)
+	// add headers
+	if headers != nil {
+		for key, val := range headers {
+			request.Header.Add(key, val)
+		}
+	}
+	client := &http.Client{}
+	resp, err := client.Do(request)
+	if err != nil {
+		return resultBytes, err
+	}
+	defer resp.Body.Close()
+	resultBytes, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return resultBytes, err
+	}
+	return resultBytes, nil
 }
\ No newline at end of file
diff --git a/models/car.go b/models/car.go
index 1e7eca2..14db294 100644
--- a/models/car.go
+++ b/models/car.go
@@ -25,8 +25,8 @@
 }
 
 type CarStatistic struct {
-	TotalPermPlace 		int 		`json:"totalPermPlace"`
-	Left           		int 		`json:"left"`
+	TotalPlace int `json:"totalPlace"`
+	Left       int `json:"left"`
 }
 
 type PosInfo struct {
diff --git a/service/carService.go b/service/carService.go
index 4727b3e..318e706 100644
--- a/service/carService.go
+++ b/service/carService.go
@@ -25,14 +25,14 @@
 
 func (sv *CarService) Statistic() (*models.CarStatistic, bool) {
 	m := models.CarStatistic{
-		TotalPermPlace: 0,
-		Left:           0,
+		TotalPlace: 0,
+		Left:       0,
 	}
 
 	remainList := sv.getRemainSpaceNum("")
 	if remainList != nil {
 		for _,r:=range remainList {
-			m.TotalPermPlace += r.TotalPermPlace
+			m.TotalPlace += r.TotalPlace
 			m.Left += r.LeftPlace
 		}
 		return &m, true
@@ -145,11 +145,13 @@
 func (sv *CarService) FindSpaceNo(userId string) models.PosResult {
 	resultList := make(models.PosResult,0)
 	var myPlateNosMap = make(map[string]string)
-	vehicles := sv.GetVehicleListByPerson(userId)
-	if vehicles != nil {
-		for _,veh := range vehicles {
-			myPlateNosMap[veh.PlateNo] = veh.PlateNo
-			//myPlateNos = append(myPlateNos, veh.PlateNo)
+	if userId !="" {
+		vehicles := sv.GetVehicleListByPerson(userId)
+		if vehicles != nil {
+			for _,veh := range vehicles {
+				myPlateNosMap[veh.PlateNo] = veh.PlateNo
+				//myPlateNos = append(myPlateNos, veh.PlateNo)
+			}
 		}
 	}
 
@@ -347,7 +349,7 @@
 	url := "/artemis/api/resource/v2/vehicle/advance/vehicleList"
 	reqBody := map[string]interface{} {
 		"pageNo": 1,
-		"pageSize": 100,
+		"pageSize": 1000,
 		"personIds": personId,
 	}
 	pageResult := sv.getHikPageResult(url, reqBody)
diff --git a/service/msgPush.go b/service/msgPush.go
index 844ae2e..f13e9a2 100644
--- a/service/msgPush.go
+++ b/service/msgPush.go
@@ -2,6 +2,7 @@
 
 import (
 	"car-service/extend/util"
+	"car-service/models"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -45,6 +46,94 @@
 	}()
 }
 
+//瑙g粦鎵�鏈変笌璇ュ埆鍚嶇粦瀹氱殑cid
+//func UnbindAlias(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/"+alias
+//	header := map[string]string {
+//		"token": token,
+//	}
+//	b,err := util.DoDeleteRequest(url, util.CONTENT_TYPE_UTF8_JSON, nil, 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 { //瑙g粦鎴愬姛
+//		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涓猚id
+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"`
@@ -56,7 +145,274 @@
 	Token 		string 			`json:"token"`
 }
 
-func Push(title string, msg string) (bool,error) {
+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"
+	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]map[string]map[string]map[string]string {
+			"android": {
+				"ups": {
+					"notification": {
+						"title": title,
+						"body": msg,
+						"click_type": "intent",
+						"intent": intent,
+					},
+				},
+			},
+		},
+	}
+	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) (bool, error) {
+	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 true,nil
+	}
+	carPersonM := make(map[string]string)
+	var csv CarService
+	carPersons := csv.GetVehicleListByPerson("")
+	if carPersons != nil {
+		for _, cp := range carPersons {
+			carPersonM[cp.PlateNo] = cp.PersonId
+		}
+	}
+	spaceNos := csv.FindSpaceNo("")
+	for _,sn := range spaceNos {
+		if sn.State == 1 && sn.PlateNo != "" { //宸茬粡鎶婅溅鍋滃埌鍋滆溅鍦虹殑杞︿富锛屼笉鍐嶆帹閫佹秷鎭�
+			if _,ok := carPersonM[sn.PlateNo];ok {
+				delete(carPersonM, sn.PlateNo)
+			}
+		}
+	}
+	for _,personId := range carPersonM {
+		if phoneNum,ok := pushUserM[personId]; ok { //姝や汉宸叉敞鍐屽埌绯荤粺,骞朵笖杞︿笉鍦ㄥ仠杞﹀簱鍐�
+			aliasArr = append(aliasArr, phoneNum)
+		}
+	}
+	if len(aliasArr) == 0 {
+		fmt.Println("娌℃湁鎺ㄩ�佺洰鏍�,aliasArr is empty")
+		return true, nil
+	}
+
+	cResult, taskId, ce := createPushMsg(title, msg)
+	if !cResult {
+		fmt.Println("createPushMsg taskId:", taskId, "err:", ce)
+		return false, errors.New("鍒涘缓鎺ㄩ�佸墠缃秷鎭け璐�")
+	}
+
+	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": true,
+	}
+	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) {
+	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 true,nil
+	}
+	carPersonM := make(map[string]string)
+	var csv CarService
+	carPersons := csv.GetVehicleListByPerson("")
+	if carPersons != nil {
+		for _, cp := range carPersons {
+			carPersonM[cp.PlateNo] = cp.PersonId
+		}
+	}
+	spaceNos := csv.FindSpaceNo("")
+	for _,sn := range spaceNos {
+		if sn.State == 1 && sn.PlateNo != "" { //宸茬粡鎶婅溅鍋滃埌鍋滆溅鍦虹殑杞︿富锛屼笉鍐嶆帹閫佹秷鎭�
+			if personId,ok := carPersonM[sn.PlateNo];ok {
+				if phoneNum,ok := pushUserM[personId]; ok { //姝や汉宸叉敞鍐屽埌绯荤粺,骞朵笖杞︿笉鍦ㄥ仠杞﹀簱鍐�
+					aliasArr = append(aliasArr, phoneNum)
+				}
+			}
+		}
+	}
+
+	if len(aliasArr) == 0 {
+		fmt.Println("娌℃湁鎺ㄩ�佺洰鏍�,aliasArr is empty")
+		return true, nil
+	}
+
+	cResult, taskId, ce := createPushMsg(title, msg)
+	if !cResult {
+		fmt.Println("createPushMsg taskId:", taskId, "err:", ce)
+		return false, errors.New("鍒涘缓鎺ㄩ�佸墠缃秷鎭け璐�")
+	}
+
+	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": true,
+	}
+	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 PushAll(title string, msg string) (bool,error) {
 	appId := beego.AppConfig.String("pushAppId")
 	baseUrl := beego.AppConfig.String("pushBaseUrl") + appId
 	retryTimes := 0
diff --git a/service/userService.go b/service/userService.go
index 731ee51..2caaeaf 100644
--- a/service/userService.go
+++ b/service/userService.go
@@ -16,7 +16,7 @@
 
 }
 
-func (sv *UserService) Login(phoneNum, code string) (bool,*vo.UserInfo,error) {
+func (sv *UserService) Login(phoneNum, code, cid string) (bool,*vo.UserInfo,error) {
 	if verifyCode(phoneNum, code) {
 		carSv := NewCarService()
 
@@ -54,6 +54,10 @@
 						plateNos = append(plateNos, up.PlateNo)
 					}
 				}
+				//瀹㈡埛绔痗id缁戝畾鍒悕
+				if cid != "" {
+					go BindAlias(cid, phoneNum)
+				}
 				return true, &vo.UserInfo{
 					UserId: u.Id,
 					PhoneNum: phoneNum,
@@ -72,6 +76,10 @@
 					plateNos = append(plateNos, up.PlateNo)
 				}
 			}
+			//瀹㈡埛绔痗id缁戝畾鍒悕
+			if cid != "" {
+				go BindAlias(cid, phoneNum)
+			}
 			return true, &vo.UserInfo{
 				UserId: tmpUser.Id,
 				PhoneNum: phoneNum,

--
Gitblit v1.8.0