浏览代码

Merge branch 'dev2.5.7' into release

lianbingjie 6 年之前
父节点
当前提交
a135be56e1
共有 51 个文件被更改,包括 3881 次插入253 次删除
  1. 36 39
      src/config.json
  2. 8 0
      src/jfw/front/applysub.go
  3. 1 1
      src/jfw/front/pchelper.go
  4. 8 0
      src/jfw/front/wxkeyset.go
  5. 31 0
      src/jfw/modules/pushRemind/src/conf/conf.go
  6. 36 0
      src/jfw/modules/pushRemind/src/config.json
  7. 23 0
      src/jfw/modules/pushRemind/src/main.go
  8. 157 0
      src/jfw/modules/pushRemind/src/push/push.go
  9. 16 4
      src/jfw/modules/push_v1/src/config.json
  10. 25 17
      src/jfw/modules/push_v1/src/main.go
  11. 52 39
      src/jfw/modules/push_v1/src/push/config/config.go
  12. 2 1
      src/jfw/modules/push_v1/src/push/dopush/job.go
  13. 28 23
      src/jfw/modules/push_v1/src/push/dopush/matchjob.go
  14. 133 66
      src/jfw/modules/push_v1/src/push/dopush/pushjob.go
  15. 12 12
      src/jfw/modules/push_v1/src/push/dopush/pushtimetask.go
  16. 9 3
      src/jfw/modules/push_v1/src/push/util/rpccall.go
  17. 27 3
      src/jfw/modules/push_v1/src/push/util/util.go
  18. 0 27
      src/jfw/modules/pushproject/src/rpcpush/appPushServiceCall.go
  19. 13 0
      src/jfw/modules/pushsubscribe/src/match/config.json
  20. 34 0
      src/jfw/modules/pushsubscribe/src/match/config/config.go
  21. 142 0
      src/jfw/modules/pushsubscribe/src/match/dfa/interestanalysis.go
  22. 45 0
      src/jfw/modules/pushsubscribe/src/match/dfa/interestanalysis_test.go
  23. 40 0
      src/jfw/modules/pushsubscribe/src/match/job/job.go
  24. 603 0
      src/jfw/modules/pushsubscribe/src/match/job/matchjob.go
  25. 27 0
      src/jfw/modules/pushsubscribe/src/match/job/timetask.go
  26. 30 0
      src/jfw/modules/pushsubscribe/src/match/main.go
  27. 二进制
      src/jfw/modules/pushsubscribe/src/match/src
  28. 1 0
      src/jfw/modules/pushsubscribe/src/match/task.json
  29. 102 0
      src/jfw/modules/pushsubscribe/src/match/util/util.go
  30. 65 0
      src/jfw/modules/pushsubscribe/src/public/entity.go
  31. 60 0
      src/jfw/modules/pushsubscribe/src/push/config.json
  32. 95 0
      src/jfw/modules/pushsubscribe/src/push/config/config.go
  33. 24 0
      src/jfw/modules/pushsubscribe/src/push/job/job.go
  34. 804 0
      src/jfw/modules/pushsubscribe/src/push/job/pushjob.go
  35. 139 0
      src/jfw/modules/pushsubscribe/src/push/job/timetask.go
  36. 56 0
      src/jfw/modules/pushsubscribe/src/push/main.go
  37. 二进制
      src/jfw/modules/pushsubscribe/src/push/src
  38. 93 0
      src/jfw/modules/pushsubscribe/src/push/util/excel.go
  39. 116 0
      src/jfw/modules/pushsubscribe/src/push/util/rpccall.go
  40. 160 0
      src/jfw/modules/pushsubscribe/src/push/util/util.go
  41. 1 0
      src/jfw/modules/pushsubscribe/src/push/xlsx/readme.txt
  42. 二进制
      src/jfw/modules/pushsubscribe/src/push/xlsx/temp.xlsx
  43. 12 12
      src/jfw/modules/weixin/src/config.json
  44. 6 0
      src/jfw/modules/weixin/src/endless/doc.go
  45. 564 0
      src/jfw/modules/weixin/src/endless/endless.go
  46. 11 0
      src/jfw/modules/weixin/src/endless/serve.go
  47. 4 4
      src/jfw/modules/weixin/src/jrpc/jrpc.go
  48. 4 1
      src/jfw/modules/weixin/src/main.go
  49. 20 1
      src/jfw/modules/weixin/src/wx/wx.go
  50. 6 0
      src/jfw/pay/weixin.go
  51. 二进制
      src/src

+ 36 - 39
src/config.json

@@ -1,5 +1,5 @@
 {
-    "mongodbServers": "192.168.3.18:27080",
+    "mongodbServers": "127.0.0.1:27080",
     "mongodbPoolSize": "25",
     "mongodbName": "qfw",
     "influxaddr": "http://192.168.3.207:8086",
@@ -46,7 +46,8 @@
         "sealRep": "/active/sealfriends/%s",
         "sealIndex": "/active/seal/index",
         "sealSend": "/active/seal/sealMsg",
-        "futureIndex": "/active/future/index"
+		"futureIndex": "/active/future/index",
+		"keysetIndex":"/wxkeyset/keyset/index?tiptext=%s"
     },
     "jy_activeset": {
         "activitystartcode": "3201000000",
@@ -198,15 +199,15 @@
     "advertName": "广告",
     "advertUrl": "/swordfish/about",
     "wxJianyu": {
-        "appid": "wxd66e9589c9fecff6",
-        "appsecret": "4d9d4b9ddab59e65fcb7bed125fbd342",
+        "appid": "wx5b1c6e7cc4dac0e4",
+        "appsecret": "b026103ffebd2291b3edb7a269612112",
         "pay": {
             "mchid": "1418321102",
             "key": "topnet2016topnet2016topnet2016ab",
             "attachmsg": "剑鱼打赏",
             "bodymsg": "剑鱼-招标信息打赏",
             "detailmsg": "招标推送信息[%s] 打赏%s元钱",
-            "sjdc_attachmsg": "剑鱼-历史数据",
+			"sjdc_attachmsg": "剑鱼-历史数据",
             "sjdc_bodymsg": "剑鱼-历史数据",
             "sjdc_detailmsg": "剑鱼-历史数据"
         }
@@ -232,7 +233,7 @@
         "msg": "f 开关状态:%s //-2 从配置文件重置,-1 关闭,1 打开<br><br>c 并发数:%d //-2 不限制并发数,-1 无条件直接限制,>0 限制并发数<br><br>t 个人查询限制时间:%ds //-1 不限制<br><br>p 限制页数:%d"
     },
     "share": {
-        "forceShareEnabled": false,
+        "forceShareEnabled" : false,
         "onlineDate": "2018-08-20 00:00:00",
         "regDaysForNewUser": 5,
         "shareIntervalDays": 7,
@@ -241,31 +242,29 @@
         "shareTimesUpperLimitR": 4000,
         "shareTimesUpperLimitIrr": 1000
     },
-    "cookiedomain": ".qmx.top",
-    "pcHelper": {
-        "subscribeTokenTimeout": 15,
-        "version": "V2.5.1"
+	"cookiedomain": ".qmx.top",
+    "pcHelper":{
+        "subscribeTokenTimeout":15,
+        "version":"V2.5.1"
     },
-    "mysql": {
-        "dbName": "jianyu",
-        "address": "192.168.3.207:3366",
-        "userName": "root",
-        "passWord": "Topnet123"
-    },
-    "mail": [
-        {
-            "addr": "smtp.exmail.qq.com",
-            "port": 465,
-            "pwd": "ue9Rg9Sf4CVtdm5a",
-            "user": "public03@topnet.net.cn"
-        },
-        {
-            "addr": "smtp.exmail.qq.com",
-            "port": 465,
-            "pwd": "ue9Rg9Sf4CVtdm5a",
-            "user": "public03@topnet.net.cn"
-        }
-    ],
+	"mysql":{
+		"dbName":   "jianyu",
+		"address":  "192.168.3.207:3366",
+		"userName": "root",
+		"passWord": "Topnet123"
+	},
+	"mail":[
+    {
+        "addr": "smtp.exmail.qq.com",
+        "port": 465,
+        "pwd": "ue9Rg9Sf4CVtdm5a",
+        "user": "public03@topnet.net.cn"
+    },{
+        "addr": "smtp.exmail.qq.com",
+        "port": 465,
+        "pwd": "ue9Rg9Sf4CVtdm5a",
+        "user": "public03@topnet.net.cn"
+    }],
     "sealAmount": {
         "销冠王": "30000",
         "全才": "30000",
@@ -284,13 +283,11 @@
         "脑洞倍儿大": "10000",
         "靠实力说话": "10000"
     },
-    "makepicurl": "http://39.105.183.186:11120",
-    "intranetip": {
-        "jy": "http://webxzh.qmx.top"
-    },
-    "getpicurl": "https://res.jianyu360.com/",
-    "sealSendTime": 14400,
-    "appPushServiceRpc": "127.0.0.1:5566",
-	"pushTimeout": 300,
-	"ninePushTimeout": 172800
+	"makepicurl":"http://39.105.183.186:11120",
+	"intranetip":{
+		"jy":"http://webxzh.qmx.top"
+	},
+	"getpicurl":"https://res.jianyu360.com/",
+	"sealSendTime":14400,
+	"appPushServiceRpc":"127.0.0.1:5566"
 }

+ 8 - 0
src/jfw/front/applysub.go

@@ -268,6 +268,14 @@ func (a *Applysub) Index() error {
 			}
 		}
 	}()
+	if tiptext := a.GetString("tiptext"); tiptext != "" {
+		go mongodb.Save("opentip_log", map[string]interface{}{
+			"tiptext":    tiptext,
+			"userid":     util.ObjToString(a.GetSession("userId")),
+			"createtime": time.Now().Unix(),
+			"type":       "applysub",
+		})
+	}
 	return a.Render("/weixin/applysub/index.html")
 }
 

+ 1 - 1
src/jfw/front/pchelper.go

@@ -68,7 +68,7 @@ func (l *PcHelper) Login() error {
 	//}
 	mac := l.GetString("mac")
 	if mac == "" {
-		return l.Render("/pchelper/login.html", nil)
+		return l.Render("/pchelper/login.html")
 	}
 	reToken := ""
 	reOpenId := ""

+ 8 - 0
src/jfw/front/wxkeyset.go

@@ -21,6 +21,14 @@ func (m *Front) WxKeyset(tpl string) error {
 	if userid == "" {
 		return m.Redirect("/swordfish/share/-1")
 	}
+	if tiptext := m.GetString("tiptext"); tiptext != "" {
+		go mongodb.Save("opentip_log", map[string]interface{}{
+			"tiptext":    tiptext,
+			"userid":     userid,
+			"createtime": time.Now().Unix(),
+			"type":       "keyset",
+		})
+	}
 	if tpl == "index" && isInTSguide(userid) {
 		return m.Redirect("/front/tenderSubscribe/guide")
 	}

+ 31 - 0
src/jfw/modules/pushRemind/src/conf/conf.go

@@ -0,0 +1,31 @@
+package conf
+
+import (
+	"qfw/util"
+)
+
+var MSysConfig SysConfig
+
+type SysConfig struct {
+	MongodbServers     string      `json:"MongodbServers"`
+	MongodbPoolSize    int         `json:"MongodbPoolSize"`
+	RedisAddrs         string      `json:"RedisAddrs"`
+	Webdomain          string      `json:"Webdomain"`
+	Weixinrpc          string      `json:"Weixinrpc"`
+	PushPoolSize       int         `json:"pushPoolSize"`       //推送程序协程数量
+	SysTiming          int64       `json:"sysTiming"`          //程序循环周期(分钟)
+	NewUserRemindTimer int64       `json:"newUserRemindTimer"` //新用户注册多久后提醒(分钟)
+	PushCycle          int         `json:"pushCycle"`          //同类文案一个用户多久收到一次(分钟)
+	CWText             CopyWriting `json:"copywriting"`        //文案
+}
+
+type CopyWriting struct {
+	No_order_new []string
+	No_open_new  []string
+	No_order_old []string
+	No_open_old  []string
+}
+
+func init() {
+	util.ReadConfig(&MSysConfig)
+}

+ 36 - 0
src/jfw/modules/pushRemind/src/config.json

@@ -0,0 +1,36 @@
+{
+	"mongodbServers": "127.0.0.1:27080",
+	"mongodbPoolSize": 5,
+	"redisAddrs":"push=47.106.230.136:6379",
+	"webdomain":"http://web-jydev-wky.jianyu360.cn",
+	"weixinrpc": "127.0.0.1:8083",
+	"PushPoolSize":30,
+	"sysTiming":10,
+	"newUserRemindTimer":20,
+	"pushCycle":30,
+	"copywriting":{
+		"no_order_new":[
+			"1_马上开启订阅功能,随时随地接收招标信息。<a href='%s'>开始订阅</a>",
+			"2_订阅你关注的招标信息,及时接收消息推送。<a href='%s'>开始订阅</a>",
+			"3_你的订阅功能尚未启用,<a href='%s'>点击启用</a>,及时接收招标信息!",
+			"4_别忘了订阅你的项目关键词,及时接收招标信息推送。<a href='%s'>开始订阅</a>",
+			"5_<a href='%s'>点击订阅关键词</a>,及时接收最新招标信息。"
+		],
+		"no_open_new":[
+			"6_你的消息推送功能待激活,<a href='%s'>点击马上激活</a>",
+			"7_<a href='%s'>点击开启消息推送</a>,及时接收订阅信息"
+		],
+		"no_order_old":[
+			"1_马上开启订阅功能,随时随地接收招标信息。<a href='%s'>开始订阅</a>",
+			"2_订阅你关注的招标信息,及时接收消息推送。<a href='%s'>开始订阅</a>",
+			"3_你的订阅功能尚未启用,<a href='%s'>点击启用</a>,及时接收招标信息!",
+			"4_别忘了订阅你的项目关键词,及时接收招标信息推送。<a href='%s'>开始订阅</a>",
+			"5_<a href='%s'>点击订阅关键词</a>,及时接收最新招标信息。"
+		],
+		"no_open_old":[
+			"8_一直收不到消息推送?激活一下就好!<a href='%s'>点击激活</a>",
+			"6_你的消息推送功能待激活,<a href='%s'>点击马上激活</a>",
+			"7_<a href='%s'>点击开启消息推送</a>,及时接收订阅信息"
+		]
+	}		
+}

+ 23 - 0
src/jfw/modules/pushRemind/src/main.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"conf"
+	"log"
+	"push"
+	"qfw/util/mongodb"
+	"qfw/util/redis"
+	"time"
+)
+
+func main() {
+	mongodb.InitMongodbPool(conf.MSysConfig.MongodbPoolSize, conf.MSysConfig.MongodbServers, "qfw")
+	redis.InitRedis(conf.MSysConfig.RedisAddrs)
+	start()
+	chan bool(nil) <- true
+}
+func start() {
+	log.Println("订阅提醒-开始推送")
+	push.GetPushObj()
+	log.Println("订阅提醒-推送结束")
+	time.AfterFunc(time.Duration(conf.MSysConfig.SysTiming)*time.Minute, start)
+}

+ 157 - 0
src/jfw/modules/pushRemind/src/push/push.go

@@ -0,0 +1,157 @@
+package push
+
+import (
+	"conf"
+	"encoding/json"
+	"fmt"
+	"log"
+	"math/rand"
+	"net/rpc"
+	"qfw/util"
+	"qfw/util/mongodb"
+	"qfw/util/redis"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var se util.SimpleEncrypt
+var threeDay int64 = 60 * 60 * 24 * 3
+var newUserRemind int64
+var pushCycle int
+
+var pushPool = make(chan bool, conf.MSysConfig.PushPoolSize)
+
+func init() {
+	se = util.SimpleEncrypt{Key: "topnet"}
+	newUserRemind = conf.MSysConfig.NewUserRemindTimer * 60
+	pushCycle = conf.MSysConfig.PushCycle * 60
+}
+
+//事件类型(1:关注事件,2:扫码事件,3:发送客服消息处理,4:支付操作)
+func GetPushObj() {
+	objArr := redis.GetKeysByPattern("push", "remind&&*")
+	for _, v := range objArr {
+		key := string(v.([]uint8))
+		//获取openid
+		s_m_openid := strings.Split(key, "&&")[1]
+		if s_m_openid == "" {
+			continue
+		}
+		pushPool <- true
+		go func(openid string) {
+			log.Println("openid--", openid)
+			defer func() {
+				<-pushPool
+			}()
+			hasKey, apply, registedate := getUserStates(openid)
+			//log.Println("是否有关键词:", hasKey, "是否开通:", apply, "注册时间", registedate)
+			text, kind := getRandomCopyWriting(hasKey, apply, openid, registedate)
+			log.Println("reminded_"+kind+"_"+openid, redis.Get("push", "reminded_"+kind+"_"+openid))
+			if text != "" && redis.Get("push", "reminded&&"+kind+"&&"+openid) == nil {
+				if err := pushMsg(openid, text); err != nil {
+					log.Println(err)
+				} else {
+					redis.Put("push", "reminded&&"+kind+"&&"+openid, 1, pushCycle)
+				}
+			}
+		}(s_m_openid)
+
+	}
+}
+
+//发送模板消息
+func pushMsg(openid, text string) error {
+	url := "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="
+	param, e := json.Marshal(map[string]interface{}{
+		"url": url,
+		"data": map[string]interface{}{
+			"touser":  openid,
+			"msgtype": "text",
+			"text": map[string]string{
+				"content": text,
+			},
+		},
+	})
+	if e != nil {
+		return e
+	}
+	client, err := rpc.DialHTTP("tcp", conf.MSysConfig.Weixinrpc)
+	defer client.Close()
+	if err != nil {
+		return err
+	}
+	var repl string
+	err = client.Call("WeiXinRpc.SendCustomMsg", param, &repl)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//查询用户是否设置关键词和是否开通微信推送
+func getUserStates(openid string) (hasKey, apply bool, registedate int64) {
+	uMsg := mongodb.FindOneByField("user", map[string]interface{}{"s_m_openid": openid}, `{"i_applystatus":1,"o_jy":1,"l_registedate":1}`)
+	if uMsg != nil && len(*uMsg) > 0 {
+		if util.IntAll((*uMsg)["i_applystatus"]) == 1 {
+			apply = true
+		}
+		o_jy := util.ObjToMap((*uMsg)["o_jy"])
+		if (*o_jy)["a_key"] != nil && len((*o_jy)["a_key"].([]interface{})) > 0 {
+			hasKey = true
+		}
+		if (*uMsg)["l_registedate"] != nil {
+			registedate = util.Int64All((*uMsg)["l_registedate"])
+		}
+	}
+	return
+}
+
+//根据用户状态,随机返回文案
+func getRandomCopyWriting(hasKey, apply bool, openid string, registedate int64) (string, string) {
+	var textArr []string
+	isNew := false
+	//三天内注册为新用户
+	if time.Now().Unix()-int64(registedate) < threeDay {
+		isNew = true
+	}
+	//新用户提醒时间
+	if isNew && time.Now().Unix()-int64(registedate) < newUserRemind {
+		return "", ""
+	}
+	//没有设置订阅词提醒
+	if !hasKey {
+		if isNew {
+			textArr = conf.MSysConfig.CWText.No_order_new
+		} else {
+			textArr = conf.MSysConfig.CWText.No_order_old
+		}
+		arr := strings.Split(textArr[RandInt(1, len(textArr)+1)-1], "_")
+		id := arr[0]
+		wen := arr[1]
+		//订阅词文案
+		return fmt.Sprintf(wen, conf.MSysConfig.Webdomain+"/front/sess/"+se.EncodeString(openid+",uid,"+strconv.Itoa(int(time.Now().Unix()))+",keysetIndex")+"__"+id), "key"
+	}
+
+	//没有开通订阅推送提醒
+	if hasKey && !apply {
+		if isNew {
+			textArr = conf.MSysConfig.CWText.No_open_new
+		} else {
+			textArr = conf.MSysConfig.CWText.No_open_old
+		}
+		arr := strings.Split(textArr[RandInt(1, len(textArr)+1)-1], "_")
+		id := arr[0]
+		wen := arr[1]
+		return fmt.Sprintf(wen, conf.MSysConfig.Webdomain+"/front/applysub/index?tiptext="+id), "apply"
+	}
+	return "", ""
+}
+
+func RandInt(min, max int) int {
+	rand.Seed(time.Now().UnixNano())
+	if min >= max || min == 0 || max == 0 {
+		return max
+	}
+	return rand.Intn(max-min) + min
+}

+ 16 - 4
src/jfw/modules/push_v1/src/config.json

@@ -1,5 +1,5 @@
 {
-	"jianyuDomain": "http://webwcj.qmx.top",
+	"jianyuDomain": "https://web-jydev-wcj.jianyu360.cn",
 	"cassandra": {
 		"cachesize": 10000,
 		"host": ["192.168.3.207"],
@@ -7,7 +7,7 @@
 		"size": 5,
 		"timeout": 20
 	},
-	"redisServers": "push=192.168.3.18:1379",
+	"redisServers": "push=192.168.3.18:1379,pushcache=192.168.3.18:1379",
 	"elasticPoolSize": 10,
 	"elasticSearch": "http://192.168.3.18:9800",
 	"mail_content": "<tr><td><num>%d</num></td><td><div class='tit'><a style='color: #000;text-decoration: none;' href='%s?mail' >%s</a></div></td><td style='float: right;' class='infos' ><span class='%s'>%s</span><span class='%s'>%s</span><span class='%s'>%s</span><span class='time'>%s</span></td></tr>",
@@ -24,7 +24,7 @@
 	"messyCodeEmailReg": "(@(126|163)\\.com)$",
 	"mgoAddr": "192.168.3.18:27080",
 	"mgoSize": 10,
-	"testids": ["5c8079df25ef8714d04fb0b3"],
+	"testids": ["5ba4aded25ef871eec5dedf4"],
 	"weixinRpcServer": "127.0.0.1:8083",
 	"wxColor": "#2cb7ca",
 	"wxContent": "剑鱼推送",
@@ -41,15 +41,27 @@
 		"14:00",
 		"18:00"
 	],
+	"cassandraPollSize":10,
 	"matchPoolSize": 60,
 	"pushPoolSize": 60,
+	"minutePushSize": 300,
+	"fastigiumMinutePushSize": 100,
+	"fastigiumTime":"9-11",
 	"mailPollSize": 20,
 	"wxPollSize": 40,
 	"appPollSize": 50,
 	"pushDuration": 2,
 	"matchDuration": 1, 
 	"retry": 3,
+	"mailReTry":2,
+	"mailSleep":200,
+	"cassandraSleep":200,
+	"appSleep":5,
+	"wxSleep":5,
+	"pcHelperSleep":5,
+	"isPushMail":true,
 	"inactivityPushHour": 10,
 	"userBatch":2,
-	"pushBatch":2
+	"pushBatch":2,
+	"ninePushRedisTimeout": 172800
 }

+ 25 - 17
src/jfw/modules/push_v1/src/main.go

@@ -11,13 +11,22 @@ import (
 	"qfw/util/elastic"
 	"qfw/util/mongodb"
 	"qfw/util/redis"
+	"time"
 	ca "ucbsutil/cassandra"
 
 	"github.com/donnie4w/go-logger/logger"
 )
 
-//初始化
-func init() {
+//主应用,定时任务
+func main() {
+	//dopush.Jobs.Match.Execute(0)
+	//dopush.Jobs.Push.Execute(2)
+	//return
+	sleep := flag.Int("s", 0, "程序启动完以后,实时推送休眠s分钟再开始")
+	modle := flag.Int("m", 0, "默认:0;1 匹配数据,2 推送数据,3 匹配+推送,4 先执行-t的任务后定时任务运行")
+	taskType := flag.Int("t", 1, "默认:1实时推送;2 实时推送+一天三次的八点推送,3 一天三次推送,4 九点推送")
+	moveDatas := flag.String("v", "y", "默认:y,是否迁移数据")
+	flag.Parse()
 	logger.SetConsole(false)
 	logger.SetRollingDaily("./logs", "push.log")
 	mongodb.InitMongodbPool(SysConfig.MgoSize, SysConfig.MgoAddr, "qfw")
@@ -33,29 +42,28 @@ func init() {
 			"timeout":   SysConfig.Cassandra.Timeout,
 		},
 	)
-}
-
-//主应用,定时任务
-func main() {
-	//dopush.Jobs.Match.Execute(0)
-	//dopush.Jobs.Push.Execute(2)
-	//return
-	modle := flag.Int("m", 0, "默认:0;1 匹配数据,2 推送数据,3 匹配+推送")
-	taskType := flag.Int("t", 1, "默认:1实时推送;2 实时推送+一天三次的八点推送,3 一天三次推送,4 九点推送")
-	flag.Parse()
 	log.Println("订阅推送程序启动")
+	isMoveDatas := *moveDatas == "y"
 	if *modle == 1 {
-		dopush.Jobs.Match.Execute(0)
+		dopush.Jobs.Match.Execute(0, false)
 	} else if *modle == 2 {
-		dopush.Jobs.Push.Execute(*taskType)
+		dopush.Jobs.Push.Execute(*taskType, isMoveDatas)
 	} else if *modle == 3 {
-		dopush.Jobs.Match.Execute(0)
-		dopush.Jobs.Push.Execute(*taskType)
+		dopush.Jobs.Match.Execute(0, true)
+		dopush.Jobs.Push.Execute(*taskType, isMoveDatas)
 	} else {
+		if *modle == 4 {
+			dopush.Jobs.Push.Execute(*taskType, isMoveDatas)
+		}
 		go dopush.Task.Match.Execute()
-		go dopush.Task.RealPush.Execute()
 		go dopush.Task.OtherPush.Execute()
 		go dopush.Task.NinePush.Execute()
+		if *sleep > 0 {
+			log.Println("实时推送先休眠", *sleep, "m")
+			time.AfterFunc(time.Duration(*sleep)*time.Minute, dopush.Task.RealPush.Execute)
+		} else {
+			go dopush.Task.RealPush.Execute()
+		}
 		flag := make(chan bool)
 		<-flag
 	}

+ 52 - 39
src/jfw/modules/push_v1/src/push/config/config.go

@@ -8,45 +8,57 @@ import (
 )
 
 type sysConfig struct {
-	JianyuDomain       string      `json:"jianyuDomain"`
-	Cassandra          *cassandra  `json:"cassandra"`
-	RedisServers       string      `json:"redisServers"`
-	ElasticPoolSize    int         `json:"elasticPoolSize"`
-	ElasticSearch      string      `json:"elasticSearch"`
-	MatchPoolSize      int         `json:"matchPoolSize"`
-	PushPoolSize       int         `json:"pushPoolSize"`
-	Mail_content       string      `json:"mail_content"`
-	Mail_html          string      `json:"mail_html"`
-	Mail_title         string      `json:"mail_title"`
-	Mails              []*pushMail `json:"mails"`
-	MaxPushSize        int         `json:"maxPushSize"`
-	MaxSearch          int         `json:"maxSearch"`
-	MessyCodeEmailReg  string      `json:"messyCodeEmailReg"`
-	MgoAddr            string      `json:"mgoAddr"`
-	MgoSize            int         `json:"mgoSize"`
-	TestIds            []string    `json:"testIds"`
-	WeixinRpcServer    string      `json:"weixinRpcServer"`
-	WxColor            string      `json:"wxColor"`
-	WxGroup            string      `json:"wxGroup"`
-	WxContent          string      `json:"wxContent"`
-	WxTitle            string      `json:"wxTitle"`
-	WxDetailColor      string      `json:"wxDetailColor"`
-	AppPushServiceRpc  string      `json:"appPushServiceRpc"`
-	PcHelper           string      `json:"pcHelper"`
-	FilterWords        []string    `json:"filterWords"`
-	PushDuration       int64       `json:"pushDuration"`
-	MatchDuration      int64       `json:"matchDuration"`
-	Retry              int         `json:"retry"`
-	InactivityPushHour int         `json:"inactivityPushHour"`
-	UserBatch          int         `json:"userBatch"`
-	PushBatch          int         `json:"pushBatch"`
-	StartPushTime      string      `json:"startPushTime"`
-	EndPushTime        string      `json:"endPushTime"`
-	OncePushTime       string      `json:"oncePushTime"`
-	OtherPushTimes     []string    `json:"otherPushTimes"`
-	MailPollSize       int         `json:"mailPollSize"`
-	WxPollSize         int         `json:"wxPollSize"`
-	AppPollSize        int         `json:"appPollSize"`
+	JianyuDomain            string      `json:"jianyuDomain"`
+	Cassandra               *cassandra  `json:"cassandra"`
+	RedisServers            string      `json:"redisServers"`
+	ElasticPoolSize         int         `json:"elasticPoolSize"`
+	ElasticSearch           string      `json:"elasticSearch"`
+	MatchPoolSize           int         `json:"matchPoolSize"`
+	PushPoolSize            int         `json:"pushPoolSize"`
+	Mail_content            string      `json:"mail_content"`
+	Mail_html               string      `json:"mail_html"`
+	Mail_title              string      `json:"mail_title"`
+	Mails                   []*pushMail `json:"mails"`
+	MaxPushSize             int         `json:"maxPushSize"`
+	MaxSearch               int         `json:"maxSearch"`
+	MessyCodeEmailReg       string      `json:"messyCodeEmailReg"`
+	MgoAddr                 string      `json:"mgoAddr"`
+	MgoSize                 int         `json:"mgoSize"`
+	TestIds                 []string    `json:"testIds"`
+	WeixinRpcServer         string      `json:"weixinRpcServer"`
+	WxColor                 string      `json:"wxColor"`
+	WxGroup                 string      `json:"wxGroup"`
+	WxContent               string      `json:"wxContent"`
+	WxTitle                 string      `json:"wxTitle"`
+	WxDetailColor           string      `json:"wxDetailColor"`
+	AppPushServiceRpc       string      `json:"appPushServiceRpc"`
+	PcHelper                string      `json:"pcHelper"`
+	FilterWords             []string    `json:"filterWords"`
+	PushDuration            int64       `json:"pushDuration"`
+	MatchDuration           int64       `json:"matchDuration"`
+	Retry                   int         `json:"retry"`
+	InactivityPushHour      int         `json:"inactivityPushHour"`
+	UserBatch               int         `json:"userBatch"`
+	PushBatch               int         `json:"pushBatch"`
+	StartPushTime           string      `json:"startPushTime"`
+	EndPushTime             string      `json:"endPushTime"`
+	OncePushTime            string      `json:"oncePushTime"`
+	OtherPushTimes          []string    `json:"otherPushTimes"`
+	MailPollSize            int         `json:"mailPollSize"`
+	WxPollSize              int         `json:"wxPollSize"`
+	AppPollSize             int         `json:"appPollSize"`
+	MailReTry               int         `json:"mailReTry"`
+	MailSleep               int         `json:"mailSleep"`
+	CassandraSleep          int         `json:"cassandraSleep"`
+	AppSleep                int         `json:"appSleep"`
+	WxSleep                 int         `json:"wxSleep"`
+	PcHelperSleep           int         `json:"pcHelperSleep"`
+	IsPushMail              bool        `json:"isPushMail"`
+	CassandraPollSize       int         `json:"cassandraPollSize"`
+	MinutePushSize          int         `json:"minutePushSize"`
+	FastigiumMinutePushSize int         `json:"fastigiumMinutePushSize"`
+	FastigiumTime           string      `json:"fastigiumTime"`
+	NinePushRedisTimeout    int         `json:"ninePushRedisTimeout"`
 }
 type pushMail struct {
 	Addr string `json:"addr"`
@@ -131,6 +143,7 @@ func init() {
 			User:     v.User,
 			Pwd:      v.Pwd,
 			PoolSize: SysConfig.MailPollSize,
+			ReTry:    SysConfig.MailReTry,
 		}
 		break
 	}

+ 2 - 1
src/jfw/modules/push_v1/src/push/dopush/job.go

@@ -24,7 +24,8 @@ const (
 )
 
 type Job interface {
-	Execute(taskType int)
+	MySelf() interface{}
+	Execute(taskType int, isMove bool)
 }
 
 type jobs struct {

+ 28 - 23
src/jfw/modules/push_v1/src/push/dopush/matchjob.go

@@ -87,13 +87,17 @@ type MatchJob struct {
 	saveBatch         []map[string]interface{}
 }
 
+func (m *MatchJob) MySelf() interface{} {
+	return m
+}
+
 //定时任务,匹配数据,存库
-func (m *MatchJob) Execute(taskType int) {
+func (m *MatchJob) Execute(taskType int, flag bool) {
 	defer util.Catch()
 	st, _ := time.ParseInLocation(util.Date_Full_Layout, TaskConfig.StartTime, time.Local)
 	lastTime := st.Unix()
 	_id := util.ObjToString(TaskConfig.LastId)
-	logger.Info("匹配数据,开始执行任务", _id, lastTime)
+	logger.Info("开始匹配数据任务。。。", _id, lastTime)
 	//获取本次查询的最大id
 	idQuery := ""
 	if _id == "" {
@@ -115,6 +119,8 @@ func (m *MatchJob) Execute(taskType int) {
 		logger.Info("匹配数据,加载数据到内存中的时候,未查找到数据!")
 		return
 	}
+	MoveLock.Lock()
+	defer MoveLock.Unlock()
 	m.lastUserId = ""
 	user_batch_index := 0
 	for {
@@ -142,7 +148,7 @@ func (m *MatchJob) Execute(taskType int) {
 		}
 	}
 	//
-	logger.Info("匹配数据结束", lastid, comeintime)
+	logger.Info("匹配数据任务结束。。。", lastid, comeintime)
 	//
 	TaskConfig.LastId = lastid
 	TaskConfig.StartTime = util.FormatDateWithObj(&comeintime, util.Date_Full_Layout)
@@ -150,7 +156,7 @@ func (m *MatchJob) Execute(taskType int) {
 	m.users = &map[string]*MemberInterest{}
 }
 func (m *MatchJob) ToMatch(batchIndex int, a_key_user, a_notkey_user, s_key_user, s_notkey_user map[string]*[]*MemberInterest) {
-	logger.Info("开始匹配第", batchIndex, "批用户")
+	logger.Info("匹配数据任务,开始匹配第", batchIndex, "批用户")
 	a_p := &Pjob{
 		Key_user:    &a_key_user,
 		Notkey_user: &a_notkey_user,
@@ -168,14 +174,12 @@ func (m *MatchJob) ToMatch(batchIndex int, a_key_user, a_notkey_user, s_key_user
 	//开启智能订阅的用户
 	s_key_user = make(map[string]*[]*MemberInterest)
 	s_notkey_user = make(map[string]*[]*MemberInterest)
-	logger.Info("第", batchIndex, "批用户匹配结束")
+	logger.Info("匹配数据任务,第", batchIndex, "批用户匹配结束")
 }
 func (m *MatchJob) Save(a_p, s_p *Pjob) {
-	logger.Info("开始保存到pushmail_temp表")
+	logger.Info("匹配数据任务,开始保存到pushmail_temp表")
 	userMap := m.EachAllBidInfo(a_p, s_p)
 	//加锁,保存数据和转移数据不能同时进行
-	MoveLock.Lock()
-	defer MoveLock.Unlock()
 	index := 0
 	for openid, listInfos := range *userMap {
 		var pushArray = make(sortList, 0)
@@ -236,14 +240,14 @@ func (m *MatchJob) Save(a_p, s_p *Pjob) {
 		}
 		index++
 		if index%500 == 0 {
-			logger.Info("保存到pushmail_temp表", index)
+			logger.Info("匹配数据任务,保存到pushmail_temp表", index)
 		}
 	}
 	if len(m.saveBatch) > 0 {
 		mongodb.SaveBulk("pushspace_temp", m.saveBatch...)
 		m.saveBatch = []map[string]interface{}{}
 	}
-	logger.Info("保存到pushmail_temp表结束", index)
+	logger.Info("匹配数据任务,保存到pushmail_temp表结束", index)
 }
 
 //加载数据到内存中
@@ -265,15 +269,15 @@ func (m *MatchJob) LoadBidding(_id, lastid string, lastTime int64) bool {
 	if len(idQuery) > 0 {
 		c_query["_id"] = idQuery
 	}
-	logger.Info("mongodb query:", c_query)
+	logger.Info("匹配数据任务,mongodb query:", c_query)
 	count := mongodb.Count("bidding", c_query)
-	logger.Info("本次推送共查到数据", count, "条")
+	logger.Info("匹配数据任务,本次数据", count, "条")
 	if count == 0 {
 		return false
 	}
 	if count > SysConfig.MaxSearch {
 		count = SysConfig.MaxSearch
-		logger.Info("目前数据多于", SysConfig.MaxSearch, ",只加载了", SysConfig.MaxSearch, "条!")
+		logger.Info("匹配数据任务,目前数据多于", SysConfig.MaxSearch, ",只加载了", SysConfig.MaxSearch, "条!")
 	}
 	var res []map[string]interface{}
 	sess := mongodb.GetMgoConn()
@@ -285,7 +289,7 @@ func (m *MatchJob) LoadBidding(_id, lastid string, lastTime int64) bool {
 		tmp["_id"] = util.BsonIdToSId(tmp["_id"])
 		res = append(res, tmp)
 		if index%500 == 0 {
-			logger.Info("当前加载数据:", index)
+			logger.Info("匹配数据任务,当前加载数据:", index)
 		}
 		tmp = make(map[string]interface{})
 		if index >= count {
@@ -293,7 +297,7 @@ func (m *MatchJob) LoadBidding(_id, lastid string, lastTime int64) bool {
 		}
 	}
 	m.datas = &res
-	logger.Info(count, "条数据已经加载完成!")
+	logger.Info("匹配数据任务,", count, "条数据已经加载完成!")
 	return true
 }
 
@@ -315,7 +319,7 @@ func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
 	if len(_idq) > 0 {
 		q["_id"] = _idq
 	}
-	logger.Info("开始第", user_batch_index, "批匹配数据加载用户", q)
+	logger.Info("匹配数据任务,开始加载第", user_batch_index, "批用户", q)
 	session := mongodb.GetMgoConn()
 	defer mongodb.DestoryMongoConn(session)
 	query := session.DB(DbName).C("user").Find(&q).Select(&map[string]interface{}{
@@ -380,7 +384,8 @@ func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
 		if len(allkeys) == 0 {
 			continue
 		}
-		logger.Info("第", user_batch_index, "批匹配数据加载用户,s_m_openid", s_m_openid, "a_m_openid", a_m_openid, "s_phone", s_phone, "jpushid", jpushid, "opushid", opushid, "applystatus", applystatus)
+		userId := fmt.Sprintf("%x", string(temp["_id"].(bson.ObjectId)))
+		logger.Info("匹配数据任务,第", user_batch_index, "批用户,userid", userId, "s_m_openid", s_m_openid, "a_m_openid", a_m_openid, "s_phone", s_phone, "jpushid", jpushid, "opushid", opushid, "applystatus", applystatus)
 		keys := []string{}                           //过滤后的关键词
 		notkeys := []string{}                        //排除词
 		key_notkey := map[string]map[string]bool{}   //关键词所对应的排除词
@@ -431,7 +436,7 @@ func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
 			modifydate = util.FormatDate(&now, util.Date_Short_Layout)
 		}
 		user := &MemberInterest{
-			Id:           fmt.Sprintf("%x", string(temp["_id"].(bson.ObjectId))),
+			Id:           userId,
 			OriginalKeys: keysTemp,
 			Keys:         keys, //原始关键词
 			NotKeys:      notkeys,
@@ -448,7 +453,7 @@ func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
 			Jpushid:      jpushid,
 			Opushid:      opushid,
 			UserType:     userType,
-			RateMode:     util.IntAllDef(o_msgset["i_ratemode"], 1),
+			RateMode:     util.IntAllDef(o_msgset["i_ratemode"], 2),
 			AllKeys:      allkeysTemp, //原始关键词
 			SmartSet:     smartset,
 			DataExport:   util.IntAllDef(temp["i_dataexport"], 0),
@@ -466,7 +471,7 @@ func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
 			break
 		}
 	}
-	logger.Info("第", user_batch_index, "批加载用户结束", n)
+	logger.Info("匹配数据任务,第", user_batch_index, "批用户加载结束", n)
 	return n
 }
 
@@ -494,7 +499,7 @@ func (m *MatchJob) MakeKeyUser(keys []string, user *MemberInterest, key_user *ma
 //遍历数据并执行推送操作
 func (m *MatchJob) EachAllBidInfo(a_p *Pjob, s_p *Pjob) *map[string]*list.List {
 	defer util.Catch()
-	logger.Info("开始遍历数据。。。")
+	logger.Info("匹配数据任务,开始遍历数据。。。")
 	userMap := &map[string]*list.List{}
 	var count int
 	for _, temp := range *m.datas {
@@ -542,11 +547,11 @@ func (m *MatchJob) EachAllBidInfo(a_p *Pjob, s_p *Pjob) *map[string]*list.List {
 			}
 		}(temp)
 		if count%500 == 0 {
-			logger.Info("当前信息索引:", count)
+			logger.Info("匹配数据任务,当前信息索引:", count)
 		}
 	}
 	m.eachInfoWaitGroup.Wait()
-	logger.Info("数据遍历完成!")
+	logger.Info("匹配数据任务,数据遍历完成!")
 	return userMap
 }
 

+ 133 - 66
src/jfw/modules/push_v1/src/push/dopush/pushjob.go

@@ -24,36 +24,67 @@ var (
 	MoveFields = []string{"s_m_openid", "a_m_openid", "phone", "usertype", "jpushid", "opushid", "words", "ratemode", "wxpush", "apppush", "mailpush", "smartset", "timestamp", "subscribe", "applystatus", "appphonetype", "email", "size", "modifydate", "mergeorder"}
 )
 
+func init() {
+	mySelf := Jobs.Push.MySelf().(*PushJob)
+	//推送1分钟限制
+	mySelf.minutePushPool = make(chan bool, SysConfig.MinutePushSize)
+	mySelf.fastigiumMinutePushPool = make(chan bool, SysConfig.FastigiumMinutePushSize)
+	for i := 0; i < SysConfig.MinutePushSize; i++ {
+		mySelf.minutePushPool <- true
+	}
+	for i := 0; i < SysConfig.FastigiumMinutePushSize; i++ {
+		mySelf.fastigiumMinutePushPool <- true
+	}
+	go func() {
+		t := time.NewTicker(time.Minute)
+		for {
+			select {
+			case <-t.C:
+				for i := 0; i < SysConfig.MinutePushSize-len(mySelf.minutePushPool); i++ {
+					mySelf.minutePushPool <- true
+				}
+				for i := 0; i < SysConfig.FastigiumMinutePushSize-len(mySelf.fastigiumMinutePushPool); i++ {
+					mySelf.fastigiumMinutePushPool <- true
+				}
+			}
+		}
+	}()
+}
+
 type PushJob struct {
-	pushPool    chan bool
-	pushWait    *sync.WaitGroup
-	pushJobLock *sync.Mutex
-	lastId      string
-	pushDatas   *[]map[string]interface{}
+	pushPool                chan bool
+	minutePushPool          chan bool
+	fastigiumMinutePushPool chan bool
+	pushWait                *sync.WaitGroup
+	pushJobLock             *sync.Mutex
+	lastId                  string
+	pushDatas               *[]map[string]interface{}
+}
+
+func (p *PushJob) MySelf() interface{} {
+	return p
 }
 
 //taskType 1--实时推送 2--实时推送+一天三次的8点推送 3--一天三次推送 4--九点推送
-func (p *PushJob) Execute(taskType int) {
+func (p *PushJob) Execute(taskType int, isMoveDatas bool) {
 	p.pushJobLock.Lock()
 	defer p.pushJobLock.Unlock()
 	logger.Info("开始推送任务。。。", taskType)
-	p.Move()
+	if isMoveDatas {
+		p.Move()
+	}
 	p.Push(taskType)
 }
 func (p *PushJob) Move() {
-	logger.Info("开始迁移数据。。。")
+	logger.Info("推送任务,开始迁移数据。。。")
 	MoveLock.Lock()
 	defer MoveLock.Unlock()
 	sess := mongodb.GetMgoConn()
 	defer mongodb.DestoryMongoConn(sess)
 	it := sess.DB(DbName).C("pushspace_temp").Find(nil).Sort("_id").Iter()
-	index := 0
-	saveArray := []map[string]interface{}{}
-	updateArray_d := []map[string]interface{}{}
-	updateArray_q := []map[string]interface{}{}
-	updateArray_s := []map[string]interface{}{}
+	_index := 0
 	pushspace_temp := map[string]map[string]interface{}{}
-	logger.Info("开始把pushspace_temp表的数据加载到内存中")
+	logger.Info("推送任务,开始把pushspace_temp表的数据加载到内存中")
 	for _temp := make(map[string]interface{}); it.Next(&_temp); {
 		userId := util.ObjToString(_temp["userid"])
 		if pushspace_temp[userId] != nil {
@@ -66,8 +97,17 @@ func (p *PushJob) Move() {
 		}
 		pushspace_temp[userId] = _temp
 		_temp = make(map[string]interface{})
+		_index++
+		if _index%500 == 0 {
+			logger.Info("推送任务,pushspace_temp表的数据加载到内存中:", index)
+		}
 	}
-	logger.Info("pushspace_temp表的数据加载完成")
+	logger.Info("推送任务,pushspace_temp表的数据加载完成")
+	index := 0
+	saveArray := []map[string]interface{}{}
+	updateArray_d := []map[string]interface{}{}
+	updateArray_q := []map[string]interface{}{}
+	updateArray_s := []map[string]interface{}{}
 	for userId, temp := range pushspace_temp {
 		var data map[string]interface{}
 		sess.DB(DbName).C("pushspace").Find(map[string]interface{}{"userid": userId}).Select(map[string]interface{}{"list": 1, "templist": 1}).One(&data)
@@ -95,12 +135,12 @@ func (p *PushJob) Move() {
 					continue
 				}
 				if rLength > 0 && rLength+pLength > SysConfig.MaxPushSize {
-					start := rLength + pLength - SysConfig.MaxPushSize
-					var relationinfoTemp = make([]interface{}, 0)
-					if start < rLength {
-						relationinfoTemp = append(relationinfoTemp, list[start:]...)
-					}
+					relationinfoTemp := []interface{}{}
 					relationinfoTemp = append(relationinfoTemp, list_temp...)
+					relationinfoTemp = append(relationinfoTemp, list...)
+					if len(relationinfoTemp) > SysConfig.MaxPushSize {
+						relationinfoTemp = relationinfoTemp[:]
+					}
 					setMap[v+"list"] = relationinfoTemp
 					setMap[v+"size"] = SysConfig.MaxPushSize
 				} else { //追加
@@ -123,7 +163,7 @@ func (p *PushJob) Move() {
 		}
 		index++
 		if index%500 == 0 {
-			logger.Info("迁移数据:", index)
+			logger.Info("推送任务,迁移数据:", index)
 		}
 	}
 	if len(saveArray) > 0 {
@@ -132,7 +172,7 @@ func (p *PushJob) Move() {
 	if len(updateArray_q) > 0 {
 		p.UpdateBulk(sess, &updateArray_d, &updateArray_q, &updateArray_s)
 	}
-	logger.Info("迁移数据结束。。。", index)
+	logger.Info("推送任务,迁移数据结束。。。", index)
 }
 func (p *PushJob) SaveBulk(sess *mgo.Session, array *[]map[string]interface{}) {
 	coll := sess.DB(DbName).C("pushspace")
@@ -142,7 +182,7 @@ func (p *PushJob) SaveBulk(sess *mgo.Session, array *[]map[string]interface{}) {
 	}
 	_, err := bulk.Run()
 	if nil != err {
-		logger.Info("BulkError", err)
+		logger.Info("推送任务,BulkError", err)
 	} else {
 		p.DelBulk(sess, array)
 	}
@@ -156,7 +196,7 @@ func (p *PushJob) UpdateBulk(sess *mgo.Session, array_d, array_q, array_s *[]map
 	}
 	_, err := bulk.Run()
 	if nil != err {
-		logger.Info("UpdateBulkError", err)
+		logger.Info("推送任务,UpdateBulkError", err)
 	} else {
 		p.DelBulk(sess, array_d)
 	}
@@ -174,11 +214,11 @@ func (p *PushJob) DelBulk(sess *mgo.Session, array *[]map[string]interface{}) {
 	}
 	_, err := bulk.Run()
 	if nil != err {
-		logger.Info("DelBulkError", err)
+		logger.Info("推送任务,DelBulkError", err)
 	}
 }
 func (p *PushJob) Push(taskType int) {
-	logger.Info("开始推送。。。")
+	logger.Info("推送任务,开始推送。。。")
 	p.lastId = ""
 	batch_index := 0
 	for {
@@ -215,7 +255,7 @@ func (p *PushJob) Push(taskType int) {
 					ModifyDate:   util.ObjToString(v["modifydate"]),
 					MergeOrder:   v["mergeorder"],
 				}
-				logger.Info("开始推送用户,userid", u.Id, "s_m_openid", u.S_m_openid, "a_m_openid", u.A_m_openid, "phone", u.Phone, "jpushid", u.Jpushid, "opushid", u.Opushid, "applystatus", u.ApplyStatus)
+				logger.Info("推送任务,开始推送用户,userid", u.Id, "s_m_openid", u.S_m_openid, "a_m_openid", u.A_m_openid, "phone", u.Phone, "jpushid", u.Jpushid, "opushid", u.Opushid, "applystatus", u.ApplyStatus)
 				wxPush, appPush, mailPush := 0, 0, 0
 				list := p.ToSortList(v["list"], u.Id)
 				templist := p.ToSortList(v["templist"], u.Id)
@@ -286,22 +326,26 @@ func (p *PushJob) Push(taskType int) {
 						mailPush = 0
 					}
 				}
-				logger.Info("本次推送任务类型", taskType, "用户的接收方式", u.Id, wxPush, appPush, mailPush, t_wxpush, t_mailpush)
+				logger.Info("推送任务,本次推送任务类型", taskType, "用户的接收方式", u.Id, wxPush, appPush, mailPush, t_wxpush, t_mailpush)
 				if wxPush == 1 || appPush == 1 || mailPush == 1 {
-					wxPushStatus, appPushStatus, mailPushStatus := 0, 0, 0
+					isSaveSuccess := false
+					//wxPushStatus, appPushStatus, mailPushStatus := 0, 0, 0
 					//开通订阅推送或邮箱推送的用户,由实时推送修改成九点推送,app推送用list字段,微信和邮箱推送用templist字段
 					if list != nil && templist != nil {
-						_, appPushStatus, _ = p.DealSend(true, 0, appPush, 0, u, &list)
-						wxPushStatus, _, mailPushStatus = p.DealSend(false, wxPush, 0, mailPush, u, &templist)
+						isSaveSuccess, _, _, _ = p.DealSend(taskType, true, 0, appPush, 0, u, &list)
+						if !isSaveSuccess {
+							return
+						}
+						p.DealSend(taskType, false, wxPush, 0, mailPush, u, &templist)
 					} else if list != nil {
-						wxPushStatus, appPushStatus, mailPushStatus = p.DealSend(true, wxPush, appPush, mailPush, u, &list)
+						isSaveSuccess, _, _, _ = p.DealSend(taskType, true, wxPush, appPush, mailPush, u, &list)
+						if !isSaveSuccess {
+							return
+						}
 					} else if templist != nil {
-						wxPushStatus, _, mailPushStatus = p.DealSend(false, wxPush, 0, mailPush, u, &templist)
+						p.DealSend(taskType, false, wxPush, 0, mailPush, u, &templist)
 					}
-					if wxPushStatus == -1 && appPushStatus == -1 && mailPushStatus == -1 {
-						return
-					}
-					if wxPush == 1 {
+					/*if wxPush == 1 {
 						if wxPushStatus == -1 {
 							wxPush = -1
 						} else {
@@ -314,27 +358,27 @@ func (p *PushJob) Push(taskType int) {
 						} else {
 							mailPush = 0
 						}
-					}
-				}
-				//判断是否要删除数据
-				_sess := mongodb.GetMgoConn()
-				defer mongodb.DestoryMongoConn(_sess)
-				if wxPush == -1 || mailPush == -1 {
-					//如果该用户还有微信或者邮箱推送,把list字段的值挪到templist
-					update := map[string]interface{}{}
-					set := map[string]interface{}{
-						"tempwxpush":   wxPush,
-						"tempmailpush": mailPush,
-					}
-					if templist == nil {
-						update["$rename"] = map[string]interface{}{"list": "templist"}
+					}*/
+					//判断是否要删除数据
+					_sess := mongodb.GetMgoConn()
+					defer mongodb.DestoryMongoConn(_sess)
+					if wxPush == -1 || mailPush == -1 {
+						//如果该用户还有微信或者邮箱推送,把list字段的值挪到templist
+						update := map[string]interface{}{}
+						set := map[string]interface{}{
+							"tempwxpush":   wxPush,
+							"tempmailpush": mailPush,
+						}
+						if templist == nil {
+							update["$rename"] = map[string]interface{}{"list": "templist"}
+						} else {
+							update["$unset"] = map[string]interface{}{"list": ""}
+						}
+						update["$set"] = set
+						_sess.DB(DbName).C("pushspace").UpdateId(v["_id"], update)
 					} else {
-						update["$unset"] = map[string]interface{}{"list": ""}
+						_sess.DB(DbName).C("pushspace").RemoveId(v["_id"])
 					}
-					update["$set"] = set
-					_sess.DB(DbName).C("pushspace").UpdateId(v["_id"], update)
-				} else {
-					_sess.DB(DbName).C("pushspace").RemoveId(v["_id"])
 				}
 			}(temp)
 		}
@@ -345,7 +389,7 @@ func (p *PushJob) Push(taskType int) {
 	p.pushWait.Wait()
 	logger.Info("推送任务结束。。。", taskType)
 }
-func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *MemberInterest, sl *sortList) (wxPushStatus, appPushStatus, mailPushStatus int) {
+func (p *PushJob) DealSend(taskType int, isSave bool, wxPush, appPush, mailPush int, k *MemberInterest, sl *sortList) (isSaveSuccess bool, wxPushStatus, appPushStatus, mailPushStatus int) {
 	defer util.Catch()
 	str := fmt.Sprintf("<div>根据您设置的关键词(%s),给您推送以下信息:</div>", strings.Join(k.OriginalKeys, ";"))
 	mailContent := ""
@@ -472,16 +516,33 @@ func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *Member
 		}
 	}
 	if i == 0 {
-		logger.Info("没有要推送的数据!", k.S_m_openid, k.A_m_openid, k.Phone)
+		logger.Info("推送任务,没有要推送的数据!", k.S_m_openid, k.A_m_openid, k.Phone)
 		return
 	}
 	if isSave {
 		//推送记录id
-		pushId := putil.SaveSendInfo(k, time.Now(), str, o_pushinfo, matchKey_infoIndex)
+		pushId := putil.SaveSendInfo(taskType, k, str, o_pushinfo, matchKey_infoIndex)
 		if pushId == "" {
-			logger.Info("保存到cassandra出错", k.S_m_openid, k.A_m_openid, k.Phone)
-			wxPushStatus, appPushStatus, mailPushStatus = -1, -1, -1
+			logger.Info("推送任务,保存到cassandra出错", k.Id, k.S_m_openid, k.A_m_openid, k.Phone)
 			return
+		} else {
+			logger.Info("推送任务,成功保存到cassandra", pushId, k.Id, k.S_m_openid, k.A_m_openid, k.Phone)
+		}
+		isSaveSuccess = true
+	}
+	//九点推送的时候,限制一分钟最大的推送数量
+	if taskType == 4 && (wxPush == 1 || appPush == 1) {
+		hour := time.Now().Hour()
+		fastigiumStart, fastigiumEnd := 0, 0
+		fastigiumTimes := strings.Split(SysConfig.FastigiumTime, "-")
+		if len(fastigiumTimes) == 2 {
+			fastigiumStart = util.IntAll(fastigiumTimes[0])
+			fastigiumEnd = util.IntAll(fastigiumTimes[1])
+		}
+		if hour >= fastigiumStart && hour <= fastigiumEnd {
+			<-p.fastigiumMinutePushPool //高峰期
+		} else {
+			<-p.minutePushPool //正常期
 		}
 	}
 	if wxPush == 1 {
@@ -560,7 +621,7 @@ func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *Member
 				wxPushStatus = -1
 			}
 		}
-		logger.Info("微信推送", isPushOk, k.S_m_openid, k.RateMode, k.ApplyStatus)
+		logger.Info("推送任务,微信推送", isPushOk, k.Id, k.S_m_openid, k.RateMode, k.ApplyStatus)
 	}
 	if appPush == 1 {
 		if len([]rune(jpushtitle)) > 80 {
@@ -599,7 +660,7 @@ func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *Member
 		} else {
 			appPushStatus = -1
 		}
-		logger.Info("app推送", isPushOk, k.S_m_openid, k.A_m_openid, k.Phone, k.AppPhoneType, k.Jpushid, k.Opushid, k.RateMode)
+		logger.Info("推送任务,app推送", isPushOk, k.Id, k.S_m_openid, k.A_m_openid, k.Phone, k.AppPhoneType, k.Jpushid, k.Opushid, k.RateMode)
 	}
 	//发送邮件
 	if mailPush == 1 {
@@ -611,7 +672,7 @@ func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *Member
 		} else {
 			mailPushStatus = -1
 		}
-		logger.Info("发送邮件", isPushOk, k.S_m_openid, k.A_m_openid, k.Phone, k.Email, k.DataExport)
+		logger.Info("推送任务,发送邮件", isPushOk, k.Id, k.S_m_openid, k.A_m_openid, k.Phone, k.Email, k.DataExport)
 	}
 	if wxPush == 1 || appPush == 1 || (mailPush == 1 && wxPush == 0 && appPush == 0) {
 		//pc端助手推送
@@ -628,6 +689,9 @@ func (p *PushJob) DealSend(isSave bool, wxPush, appPush, mailPush int, k *Member
 
 //推送邮件(含附件)
 func (p *PushJob) SendMail(email, subject, html string, fmdatas []map[string]interface{}) bool {
+	if !SysConfig.IsPushMail {
+		return true
+	}
 	defer util.Catch()
 	//生成附件
 	var fnamepath, rename string
@@ -645,6 +709,9 @@ func (p *PushJob) SendMail(email, subject, html string, fmdatas []map[string]int
 	if fnamepath != "" {
 		os.Remove(fnamepath)
 	}
+	if SysConfig.MailSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.MailSleep) * time.Millisecond)
+	}
 	return status
 }
 
@@ -683,7 +750,7 @@ func (p *PushJob) OncePushBatch(batch_index, taskType int) int {
 			"$gt": bson.ObjectIdHex(p.lastId),
 		}
 	}
-	logger.Info("开始第", batch_index, "批推送用户", query)
+	logger.Info("推送任务,开始加载第", batch_index, "批用户", query)
 	it := sess.DB(DbName).C("pushspace").Find(query).Sort("_id").Iter()
 	for temp := make(map[string]interface{}); it.Next(&temp); {
 		i++
@@ -694,7 +761,7 @@ func (p *PushJob) OncePushBatch(batch_index, taskType int) int {
 			break
 		}
 	}
-	logger.Info("第", batch_index, "批加载推送用户结束", p.lastId)
+	logger.Info("推送任务,第", batch_index, "批用户加载结束", p.lastId)
 	return i
 }
 func (p *PushJob) ToSortList(list interface{}, userId string) sortList {

+ 12 - 12
src/jfw/modules/push_v1/src/push/dopush/pushtimetask.go

@@ -26,10 +26,10 @@ type MatchTimeTask struct {
 }
 
 func (m *MatchTimeTask) Execute() {
-	Jobs.Match.Execute(0)
+	Jobs.Match.Execute(0, true)
 	util.WriteSysConfig("./task.json", &TaskConfig)
 	time.AfterFunc(time.Duration(SysConfig.MatchDuration)*time.Minute, func() {
-		Jobs.Match.Execute(0)
+		m.Execute()
 	})
 }
 
@@ -46,7 +46,7 @@ func (r *RealPushTimeTask) Execute() {
 	//程序启动在开始时间之前
 	if now.Before(start) {
 		sub := start.Sub(now)
-		log.Println("start", SysConfig.StartPushTime, "push after", sub)
+		log.Println("start", SysConfig.StartPushTime, "pushjob after", sub)
 		time.AfterFunc(sub, func() {
 			go r.run(2)
 			ticker := time.NewTicker(time.Hour * 24)
@@ -61,7 +61,7 @@ func (r *RealPushTimeTask) Execute() {
 		go r.run(1)
 		start = start.AddDate(0, 0, 1)
 		sub := start.Sub(now)
-		log.Println("start", SysConfig.StartPushTime, "push after", sub)
+		log.Println("start", SysConfig.StartPushTime, "pushjob after", sub)
 		timer := time.NewTimer(sub)
 		for {
 			select {
@@ -80,14 +80,14 @@ func (r *RealPushTimeTask) run(taskType int) {
 	now := time.Now()
 	end := time.Date(now.Year(), now.Month(), now.Day(), util.IntAll(e_h_m[0]), util.IntAll(e_h_m[1]), 0, 0, time.Local)
 	if now.Before(end) {
-		Jobs.Push.Execute(taskType)
+		Jobs.Push.Execute(taskType, true)
 	}
 	//隔天的话,不继续
 	//判断下一轮是否还需要继续
 	if now.Day() != time.Now().Day() || time.Now().After(end) {
 		return
 	}
-	log.Println("start push after", SysConfig.PushDuration, "m")
+	log.Println("start pushjob after", SysConfig.PushDuration, "m")
 	time.AfterFunc(time.Duration(SysConfig.PushDuration)*time.Minute, func() {
 		r.run(1)
 	})
@@ -109,14 +109,14 @@ func (o *OtherPushTimeTask) Execute() {
 			newDate = newDate.AddDate(0, 0, 1)
 		}
 		sub := newDate.Sub(now)
-		log.Println("start", otherpushtime, "push after", sub)
+		log.Println("start", otherpushtime, "pushjob after", sub)
 		time.AfterFunc(sub, func() {
-			go Jobs.Push.Execute(3)
+			go Jobs.Push.Execute(3, true)
 			ticker := time.NewTicker(time.Hour * 24)
 			for {
 				select {
 				case <-ticker.C:
-					go Jobs.Push.Execute(3)
+					go Jobs.Push.Execute(3, true)
 				}
 			}
 		})
@@ -135,14 +135,14 @@ func (n *NinePushTimeTask) Execute() {
 			newDate = newDate.AddDate(0, 0, 1)
 		}
 		sub := newDate.Sub(now)
-		log.Println("start", SysConfig.OncePushTime, "push after", sub)
+		log.Println("start", SysConfig.OncePushTime, "pushjob after", sub)
 		time.AfterFunc(sub, func() {
-			go Jobs.Push.Execute(4)
+			go Jobs.Push.Execute(4, true)
 			ticker := time.NewTicker(time.Hour * 24)
 			for {
 				select {
 				case <-ticker.C:
-					go Jobs.Push.Execute(4)
+					go Jobs.Push.Execute(4, true)
 				}
 			}
 		})

+ 9 - 3
src/jfw/modules/push_v1/src/push/util/rpccall.go

@@ -27,7 +27,9 @@ func SendWeixin(k *MemberInterest, Remarks string, o_pushinfo map[string]map[str
 		util.Catch()
 		<-wxPushPool
 	}()
-	time.Sleep(5 * time.Millisecond)
+	if SysConfig.WxSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.WxSleep) * time.Millisecond)
+	}
 	var repl qrpc.RpcResult
 	now := time.Now()
 	p := &qrpc.NotifyMsg{
@@ -71,7 +73,9 @@ func SendApp(m map[string]interface{}) bool {
 		util.Catch()
 		<-appPushPool
 	}()
-	time.Sleep(5 * time.Millisecond)
+	if SysConfig.AppSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.AppSleep) * time.Millisecond)
+	}
 	var repl string
 	client, err := rpc.DialHTTP("tcp", SysConfig.AppPushServiceRpc)
 	if err != nil {
@@ -91,7 +95,9 @@ func SendApp(m map[string]interface{}) bool {
 //
 func SendPcHelper(m map[string]interface{}) bool {
 	defer util.Catch()
-	time.Sleep(5 * time.Millisecond)
+	if SysConfig.PcHelperSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.PcHelperSleep) * time.Millisecond)
+	}
 	var repl string
 	client, err := rpc.DialHTTP("tcp", SysConfig.PcHelper)
 	if err != nil {

+ 27 - 3
src/jfw/modules/push_v1/src/push/util/util.go

@@ -5,13 +5,15 @@ import (
 	"fmt"
 	. "push/config"
 	"qfw/util"
+	"qfw/util/redis"
 	"time"
-
 	ca "ucbsutil/cassandra"
 
 	"gopkg.in/mgo.v2/bson"
 )
 
+var cassandraPoll = make(chan bool, SysConfig.CassandraPollSize)
+
 //重新设置用户类型
 func GetUserType(s_m_openid, a_m_openid, phone string, userType int) int {
 	if userType == 0 {
@@ -100,8 +102,15 @@ func ModeTransform(userType int, o_msgset map[string]interface{}) (int, int, int
 }
 
 //保存发送信息
-func SaveSendInfo(k *MemberInterest, now time.Time, str string, o_pushinfo map[string]map[string]interface{}, matchKey_infoIndex map[string]string) string {
-	pushid := now.Unix()
+func SaveSendInfo(taskType int, k *MemberInterest, str string, o_pushinfo map[string]map[string]interface{}, matchKey_infoIndex map[string]string) string {
+	cassandraPoll <- true
+	defer func() {
+		<-cassandraPoll
+	}()
+	if SysConfig.CassandraSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.CassandraSleep) * time.Millisecond)
+	}
+	pushid := time.Now().Unix()
 	md, _ := json.Marshal(o_pushinfo)
 	openid := util.GetOldOpenid(k.S_m_openid, k.A_m_openid, k.Phone, k.MergeOrder)
 	wxpush := map[string]interface{}{
@@ -122,6 +131,21 @@ func SaveSendInfo(k *MemberInterest, now time.Time, str string, o_pushinfo map[s
 		"matchki":  matchKey_infoIndex,
 	}
 	if ca.SaveCacheByTimeOut("jy_push", wxpush, 10) {
+		redisKey := "pushsubscribe_" + k.Id
+		if taskType == 4 {
+			redisDatas := redis.Get("pushcache", redisKey)
+			if redisDatas != nil {
+				list := []interface{}{wxpush}
+				redisList, _ := redisDatas.([]interface{})
+				list = append(list, redisList...)
+				if len(list) > 500 {
+					list = list[:500]
+				}
+				redis.Put("pushcache", redisKey, list, SysConfig.NinePushRedisTimeout)
+			}
+		} else {
+			redis.Del("pushcache", redisKey)
+		}
 		return fmt.Sprint(pushid)
 	}
 	return ""

+ 0 - 27
src/jfw/modules/pushproject/src/rpcpush/appPushServiceCall.go

@@ -1,27 +0,0 @@
-package rpcpush
-
-import (
-	"config"
-	"encoding/json"
-	"log"
-	"net/rpc"
-	"qfw/util"
-)
-
-func AppPushServiceCall(m map[string]interface{}) bool {
-	defer util.Catch()
-	var repl string
-	client, err := rpc.DialHTTP("tcp", config.Sysconfig["appPushServiceRpc"].(string))
-	if err != nil {
-		log.Println(err.Error())
-		return false
-	}
-	defer client.Close()
-	b, _ := json.Marshal(m)
-	err = client.Call("Rpc.Push", b, &repl)
-	if err != nil {
-		log.Println(err.Error())
-		return false
-	}
-	return repl == "y"
-}

+ 13 - 0
src/jfw/modules/pushsubscribe/src/match/config.json

@@ -0,0 +1,13 @@
+{
+	"elasticPoolSize": 10,
+	"elasticSearch": "http://192.168.3.18:9800",
+	"maxPushSize": 50,
+	"maxSearch": 5000,
+	"mgoAddr": "192.168.3.18:27080",
+	"mgoSize": 10,
+	"testids": ["5c8f4f4325ef8723d0bc1082"],
+	"filterWords":["项目","中标","公告"],
+	"matchPoolSize": 60,
+	"matchDuration": 1, 
+	"userBatch":2
+}

+ 34 - 0
src/jfw/modules/pushsubscribe/src/match/config/config.go

@@ -0,0 +1,34 @@
+package config
+
+import (
+	"qfw/util"
+)
+
+type sysConfig struct {
+	ElasticPoolSize int      `json:"elasticPoolSize"`
+	ElasticSearch   string   `json:"elasticSearch"`
+	MaxPushSize     int      `json:"maxPushSize"`
+	MaxSearch       int      `json:"maxSearch"`
+	MgoAddr         string   `json:"mgoAddr"`
+	MgoSize         int      `json:"mgoSize"`
+	TestIds         []string `json:"testIds"`
+	FilterWords     []string `json:"filterWords"`
+	MatchPoolSize   int      `json:"matchPoolSize"`
+	MatchDuration   int64    `json:"matchDuration"`
+	UserBatch       int      `json:"userBatch"`
+}
+
+type taskConfig struct {
+	StartTime string `json:"startTime"`
+	LastId    string `json:"lastId"`
+}
+
+var (
+	SysConfig  *sysConfig
+	TaskConfig *taskConfig
+)
+
+func init() {
+	util.ReadConfig("./config.json", &SysConfig)
+	util.ReadConfig("./task.json", &TaskConfig)
+}

+ 142 - 0
src/jfw/modules/pushsubscribe/src/match/dfa/interestanalysis.go

@@ -0,0 +1,142 @@
+/**
+ *兴趣分析
+ *
+ */
+package dfa
+
+import (
+	"log"
+	"strings"
+)
+
+//DFA实现
+type DFA struct {
+	link        map[string]interface{} //存放or
+	linkAnd     map[string]int         //存放and
+	linkAndWord map[string]interface{} //存放and中的拆分词
+
+}
+
+//添加词组,用于初始化,该方法是可以调用多次的
+func (d *DFA) AddWord(words ...string) {
+	if d.link == nil {
+		d.link = make(map[string]interface{})
+		d.linkAnd = make(map[string]int)
+		d.linkAndWord = make(map[string]interface{})
+	}
+	var nowMap *map[string]interface{}
+	for _, key := range words {
+		keys := strings.Split(key, "+")
+		lenkeys := len(keys)
+		if lenkeys > 1 {
+			d.linkAnd[key] = lenkeys
+			for k := 0; k < lenkeys; k++ {
+				minKey := keys[k]
+				nowMap = &d.linkAndWord
+				for i := 0; i < len(minKey); i++ {
+					kc := minKey[i : i+1]
+					if v, ok := (*nowMap)[kc]; ok {
+						nowMap, _ = v.(*map[string]interface{})
+					} else {
+						newMap := map[string]interface{}{}
+						newMap["YN"] = "N"
+						(*nowMap)[kc] = &newMap
+						nowMap = &newMap
+					}
+					if i == len(minKey)-1 {
+						(*nowMap)["YN"] = "Y"
+						if (*nowMap)["key"] == nil {
+							(*nowMap)["key"] = make(map[string]int)
+						}
+						(*nowMap)["key"].(map[string]int)[key] = k
+					}
+				}
+			}
+		} else {
+			nowMap = &d.link
+			for i := 0; i < len(key); i++ {
+				kc := key[i : i+1]
+				if v, ok := (*nowMap)[kc]; ok {
+					nowMap, _ = v.(*map[string]interface{})
+				} else {
+					newMap := map[string]interface{}{}
+					newMap["YN"] = "N"
+					(*nowMap)[kc] = &newMap
+					nowMap = &newMap
+				}
+
+				if i == len(key)-1 {
+					(*nowMap)["YN"] = "Y"
+				}
+			}
+		}
+	}
+}
+func (d *DFA) Clear() {
+	d.link = nil
+}
+
+//从给定的内容中找出匹配上的关键词
+func (d *DFA) Analy(src string) []string {
+	if d.link == nil {
+		log.Println("请先添加词组")
+		return []string{}
+	}
+	keywords := []string{}
+	tempMap := make(map[string][]bool)
+	for i := 0; i < len(src); i++ {
+		nowMap := &d.link
+		length := 0 // 匹配标识数默认为0
+		//flag := false // 敏感词结束标识位:用于敏感词只有1位的情况
+		for j := i; j < len(src); j++ {
+			word := src[j : j+1]
+			nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+			if nowMap != nil {
+				length = length + 1
+				tag, _ := (*nowMap)["YN"].(string)
+				if "Y" == tag {
+					//flag = true
+					keywords = append(keywords, src[i:i+length])
+				}
+			} else {
+				break
+			}
+		}
+		nowMap = &d.linkAndWord
+		length = 0
+		for j := i; j < len(src); j++ {
+			word := src[j : j+1]
+			nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+			if nowMap != nil {
+				length = length + 1
+				tag, _ := (*nowMap)["YN"].(string)
+				if "Y" == tag {
+					mkeys := (*nowMap)["key"].(map[string]int)
+					for k, v := range mkeys {
+						tempBool := tempMap[k]
+						if tempBool == nil {
+							tempBool = make([]bool, d.linkAnd[k])
+							tempMap[k] = tempBool
+						}
+						tempBool[v] = true
+					}
+				}
+			} else {
+				break
+			}
+		}
+	}
+	for k, v := range tempMap {
+		ball := true
+		for _, m := range v {
+			if !m {
+				ball = false
+				break
+			}
+		}
+		if ball {
+			keywords = append(keywords, k)
+		}
+	}
+	return keywords
+}

+ 45 - 0
src/jfw/modules/pushsubscribe/src/match/dfa/interestanalysis_test.go

@@ -0,0 +1,45 @@
+package dfa
+
+import (
+	"log"
+	"strings"
+	"testing"
+	"time"
+)
+
+var d *DFA = &DFA{}
+
+func copyMap(m map[string]int) (m2 map[string]int) {
+	m2 = make(map[string]int)
+	for k, v := range m {
+		m2[k] = v
+	}
+	return m2
+}
+
+func TestAnaly(t *testing.T) {
+	d.AddWord("办公", "办+楼", "河+省", "完+你们8")
+	log.Println(strings.Split("河+南+", "+")[2])
+	t1 := time.Now()
+	log.Println(d.Analy("这胡省锦河涛写给江泽民的信我们你们于办公楼上你完就是啊。"), "=====")
+	log.Println(time.Now().Sub(t1).Seconds())
+	d.Clear()
+	//log.Println(d.Analy("这是胡锦涛写给江泽民的信啊。"))
+
+}
+
+func Test_Label(t *testing.T) {
+	log.Println("000----")
+
+	for _, v := range []int{1, 2, 3, 4, 5} {
+		log.Println(v)
+	L1:
+		for _, vv := range []string{"a", "b", "c", "d"} {
+			log.Println(vv)
+			if vv == "add" {
+				break L1
+			}
+		}
+	}
+	log.Println("111----")
+}

+ 40 - 0
src/jfw/modules/pushsubscribe/src/match/job/job.go

@@ -0,0 +1,40 @@
+package job
+
+import (
+	"match/config"
+	. "public"
+	"sync"
+)
+
+const (
+	BulkSize  = 200
+	ShowField = `"_id","title","detail","projectscope","publishtime","toptype","subtype","type","area","href","areaval","infoformat",` +
+		`"projectname","buyer","winner","budget","bidamount","bidopentime","s_subscopeclass"`
+	SortQuery         = `{"publishtime":"desc"}`
+	DB                = "bidding"
+	IDRange           = `{"range":{"id":{"gt":"%s","lte":"%s"}}},{"range":{"publishtime":{"gt": %d}}}`
+	TimeRange         = `{"range":{"comeintime":{"gte":%d,"lte":%d}}}`
+	MaxId             = `{"query":{"filtered":{"filter":{"bool":{"must":{"range":{"id":{"gt":"%s"}}}}}}},"_source":["_id","comeintime"],"sort":{"id":"desc"},"from":0,"size":1}`
+	Mongodb_ShowField = `{"title":1,"detail":1,"projectscope":1,"publishtime":1,"toptype":1,"subtype":1,"type":1,"area":1,"href":1,"areaval":1,"infoformat":1,"projectname":1,"buyer":1,"winner":1,"budget":1,"bidamount":1,"bidopentime":1,"s_subscopeclass":1}`
+	DbName            = "qfw"
+)
+
+type Job interface {
+	Execute()
+}
+
+type jobs struct {
+	Match Job
+}
+
+var Jobs = &jobs{
+	Match: &MatchJob{
+		datas:             &[]map[string]interface{}{},
+		users:             &map[string]*UserInfo{},
+		matchPool:         make(chan bool, config.SysConfig.MatchPoolSize),
+		eachInfoWaitGroup: &sync.WaitGroup{},
+		saveWaitGroup:     &sync.WaitGroup{},
+		userMapLock:       &sync.Mutex{},
+		saveBatch:         []map[string]interface{}{},
+	},
+}

+ 603 - 0
src/jfw/modules/pushsubscribe/src/match/job/matchjob.go

@@ -0,0 +1,603 @@
+package job
+
+import (
+	"container/list"
+	"encoding/json"
+	"fmt"
+	. "match/config"
+	"match/dfa"
+	putil "match/util"
+	. "public"
+	"qfw/util"
+	"qfw/util/elastic"
+	"qfw/util/mongodb"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/donnie4w/go-logger/logger"
+	"gopkg.in/mgo.v2/bson"
+)
+
+var (
+	SaveFields = []string{"_id", "area", "bidamount", "bidopentime", "budget", "buyer", "otitle", "projectname", "publishtime", "s_subscopeclass", "subtype", "title", "toptype", "type", "winner"}
+)
+
+type Pjob struct {
+	InterestDfa    *dfa.DFA
+	NotInterestDfa *dfa.DFA
+	Key_user       *map[string]*[]*UserInfo
+	Notkey_user    *map[string]*[]*UserInfo
+}
+
+//所有用户的关键词和排除词
+func (p *Pjob) CreateDaf() {
+	//关键词
+	p.InterestDfa = &dfa.DFA{}
+	interestWords := make([]string, 0)
+	for k, _ := range *p.Key_user {
+		interestWords = append(interestWords, k)
+	}
+	p.InterestDfa.AddWord(interestWords...)
+	//排除关键词
+	p.NotInterestDfa = &dfa.DFA{}
+	notInterestWords := make([]string, 0)
+	for k, _ := range *p.Notkey_user {
+		notInterestWords = append(notInterestWords, k)
+	}
+	p.NotInterestDfa.AddWord(notInterestWords...)
+}
+
+type MatchUser struct {
+	User *UserInfo
+	Keys []string
+}
+type MatchJob struct {
+	datas             *[]map[string]interface{} //本次加载的数据
+	users             *map[string]*UserInfo     //所有用户
+	matchPool         chan bool
+	eachInfoWaitGroup *sync.WaitGroup
+	saveWaitGroup     *sync.WaitGroup
+	userMapLock       *sync.Mutex
+	lastUserId        string
+	saveBatch         []map[string]interface{}
+}
+
+//定时任务,匹配数据,存库
+func (m *MatchJob) Execute() {
+	defer util.Catch()
+	st, _ := time.ParseInLocation(util.Date_Full_Layout, TaskConfig.StartTime, time.Local)
+	lastTime := st.Unix()
+	_id := util.ObjToString(TaskConfig.LastId)
+	logger.Info("开始匹配数据任务。。。", _id, lastTime)
+	//获取本次查询的最大id
+	idQuery := ""
+	if _id == "" {
+		idQuery = strings.Replace(fmt.Sprintf(MaxId, _id), `"gt"`, `"gte"`, -1)
+	} else {
+		idQuery = fmt.Sprintf(MaxId, _id)
+	}
+	resId := elastic.Get(DB, DB, idQuery)
+	lastid := ""
+	var comeintime interface{}
+	if resId != nil && *resId != nil && len(*resId) == 1 {
+		lastid = util.ObjToString((*resId)[0]["_id"])
+		comeintime = (*resId)[0]["comeintime"]
+	} else {
+		logger.Info("匹配数据,获取本次查询的最大id的时候,未查找到数据!", idQuery)
+		return
+	}
+	if !m.LoadBidding(_id, lastid, lastTime) {
+		logger.Info("匹配数据,加载数据到内存中的时候,未查找到数据!")
+		return
+	}
+	m.lastUserId = ""
+	user_batch_index := 0
+	for {
+		user_batch_index++
+		user_batch_size := m.OnceUserBatch(user_batch_index)
+		if user_batch_size == 0 {
+			break
+		}
+		a_key_user := make(map[string]*[]*UserInfo)
+		a_notkey_user := make(map[string]*[]*UserInfo)
+		//开启智能订阅的用户
+		s_key_user := make(map[string]*[]*UserInfo)
+		s_notkey_user := make(map[string]*[]*UserInfo)
+		for _, v := range *m.users {
+			m.MakeKeyUser(v.Keys, v, &a_key_user)
+			m.MakeKeyUser(v.NotKeys, v, &a_notkey_user)
+			if v.SmartSet == 1 {
+				m.MakeKeyUser(v.Keys, v, &s_key_user)
+				m.MakeKeyUser(v.NotKeys, v, &s_notkey_user)
+			}
+		}
+		m.ToMatch(user_batch_index, a_key_user, a_notkey_user, s_key_user, s_notkey_user)
+		if user_batch_size < SysConfig.UserBatch {
+			break
+		}
+	}
+	//
+	logger.Info("匹配数据任务结束。。。", lastid, comeintime)
+	//
+	TaskConfig.LastId = lastid
+	TaskConfig.StartTime = util.FormatDateWithObj(&comeintime, util.Date_Full_Layout)
+	m.datas = &[]map[string]interface{}{}
+	m.users = &map[string]*UserInfo{}
+}
+func (m *MatchJob) ToMatch(batchIndex int, a_key_user, a_notkey_user, s_key_user, s_notkey_user map[string]*[]*UserInfo) {
+	logger.Info("匹配数据任务,开始匹配第", batchIndex, "批用户")
+	a_p := &Pjob{
+		Key_user:    &a_key_user,
+		Notkey_user: &a_notkey_user,
+	}
+	a_p.CreateDaf()
+	//
+	s_p := &Pjob{
+		Key_user:    &s_key_user,
+		Notkey_user: &s_notkey_user,
+	}
+	s_p.CreateDaf()
+	m.Save(a_p, s_p)
+	a_key_user = make(map[string]*[]*UserInfo)
+	a_notkey_user = make(map[string]*[]*UserInfo)
+	//开启智能订阅的用户
+	s_key_user = make(map[string]*[]*UserInfo)
+	s_notkey_user = make(map[string]*[]*UserInfo)
+	logger.Info("匹配数据任务,第", batchIndex, "批用户匹配结束")
+}
+func (m *MatchJob) Save(a_p, s_p *Pjob) {
+	logger.Info("匹配数据任务,开始保存到pushmail_temp表")
+	userMap := m.EachAllBidInfo(a_p, s_p)
+	//加锁,保存数据和转移数据不能同时进行
+	index := 0
+	for openid, listInfos := range *userMap {
+		var pushArray = make(SortList, 0)
+		for e := listInfos.Front(); e != nil; e = e.Next() {
+			matchInfo := *(e.Value.(*MatchInfo))
+			pushArray = append(pushArray, &matchInfo)
+		}
+		//取最新50条
+		sort.Sort(pushArray)
+		var array []*MatchInfo
+		size := 0
+		for _, v2 := range pushArray {
+			size++
+			info := map[string]interface{}{}
+			for _, field := range SaveFields {
+				if (*v2.Info)[field] == nil {
+					continue
+				}
+				info[field] = (*v2.Info)[field]
+			}
+			array = append(array, &MatchInfo{
+				Info: &info,
+				Keys: v2.Keys,
+			})
+			if len(array) == SysConfig.MaxPushSize {
+				break
+			}
+		}
+		user := (*m.users)[openid]
+		m.saveBatch = append(m.saveBatch, map[string]interface{}{
+			"s_m_openid":   user.S_m_openid,
+			"a_m_openid":   user.A_m_openid,
+			"phone":        user.Phone,
+			"jpushid":      user.Jpushid,
+			"opushid":      user.Opushid,
+			"appphonetype": user.AppPhoneType,
+			"userid":       user.Id,
+			"ratemode":     user.RateMode,
+			"wxpush":       user.WxPush,
+			"apppush":      user.AppPush,
+			"mailpush":     user.MailPush,
+			"smartset":     user.SmartSet,
+			"usertype":     user.UserType,
+			"email":        user.Email,
+			"dataexport":   user.DataExport,
+			"list":         array,
+			"size":         size,
+			"subscribe":    user.Subscribe,
+			"applystatus":  user.ApplyStatus,
+			"words":        user.OriginalKeys,
+			"modifydate":   user.ModifyDate,
+			"mergeorder":   user.MergeOrder,
+			"timestamp":    time.Now().Unix(),
+		})
+		if len(m.saveBatch) == BulkSize {
+			mongodb.SaveBulk("pushspace_temp", m.saveBatch...)
+			m.saveBatch = []map[string]interface{}{}
+		}
+		index++
+		if index%500 == 0 {
+			logger.Info("匹配数据任务,保存到pushmail_temp表", index)
+		}
+	}
+	if len(m.saveBatch) > 0 {
+		mongodb.SaveBulk("pushspace_temp", m.saveBatch...)
+		m.saveBatch = []map[string]interface{}{}
+	}
+	logger.Info("匹配数据任务,保存到pushmail_temp表结束", index)
+}
+
+//加载数据到内存中
+func (m *MatchJob) LoadBidding(_id, lastid string, lastTime int64) bool {
+	defer util.Catch()
+	c_query := map[string]interface{}{
+		"publishtime": map[string]interface{}{
+			"$gt": lastTime - 7*86400,
+		},
+		"extracttype": 1,
+	}
+	idQuery := map[string]interface{}{}
+	if _id != "" {
+		idQuery["$gt"] = bson.ObjectIdHex(_id)
+	}
+	if lastid != "" {
+		idQuery["$lte"] = bson.ObjectIdHex(lastid)
+	}
+	if len(idQuery) > 0 {
+		c_query["_id"] = idQuery
+	}
+	logger.Info("匹配数据任务,mongodb query:", c_query)
+	count := mongodb.Count("bidding", c_query)
+	logger.Info("匹配数据任务,本次数据共", count, "条")
+	if count == 0 {
+		return false
+	}
+	if count > SysConfig.MaxSearch {
+		count = SysConfig.MaxSearch
+		logger.Info("匹配数据任务,目前数据多于", SysConfig.MaxSearch, ",只加载了", SysConfig.MaxSearch, "条!")
+	}
+	var res []map[string]interface{}
+	sess := mongodb.GetMgoConn()
+	defer mongodb.DestoryMongoConn(sess)
+	it := sess.DB(DbName).C("bidding").Find(c_query).Select(mongodb.ObjToOth(Mongodb_ShowField)).Sort("_id").Iter()
+	index := 0
+	for tmp := make(map[string]interface{}); it.Next(&tmp); {
+		index++
+		tmp["_id"] = util.BsonIdToSId(tmp["_id"])
+		res = append(res, tmp)
+		if index%500 == 0 {
+			logger.Info("匹配数据任务,当前加载数据:", index)
+		}
+		tmp = make(map[string]interface{})
+		if index >= count {
+			break
+		}
+	}
+	m.datas = &res
+	logger.Info("匹配数据任务,", count, "条数据已经加载完成!")
+	return true
+}
+
+//初始化用户缓存
+func (m *MatchJob) OnceUserBatch(user_batch_index int) int {
+	defer util.Catch()
+	m.users = &map[string]*UserInfo{}
+	//遍历用户
+	q := map[string]interface{}{
+		"i_appid": 2,
+	}
+	_idq := map[string]interface{}{}
+	if len(SysConfig.TestIds) > 0 {
+		_idq["$in"] = putil.ToObjectIds(SysConfig.TestIds)
+	}
+	if m.lastUserId != "" {
+		_idq["$gt"] = bson.ObjectIdHex(m.lastUserId)
+	}
+	if len(_idq) > 0 {
+		q["_id"] = _idq
+	}
+	logger.Info("匹配数据任务,开始加载第", user_batch_index, "批用户", q)
+	session := mongodb.GetMgoConn()
+	defer mongodb.DestoryMongoConn(session)
+	query := session.DB(DbName).C("user").Find(&q).Select(&map[string]interface{}{
+		"_id":           1,
+		"o_jy":          1,
+		"s_m_openid":    1,
+		"a_m_openid":    1,
+		"s_phone":       1,
+		"s_jpushid":     1,
+		"s_opushid":     1,
+		"i_ispush":      1,
+		"i_dataexport":  1,
+		"i_type":        1,
+		"i_smartset":    1,
+		"i_supersearch": 1,
+		"s_appponetype": 1,
+		"i_applystatus": 1,
+		"a_mergeorder":  1,
+	}).Iter()
+	n := 0
+	for temp := make(map[string]interface{}); query.Next(temp); {
+		s_m_openid := util.ObjToString(temp["s_m_openid"])
+		a_m_openid := util.ObjToString(temp["a_m_openid"])
+		s_phone := util.ObjToString(temp["s_phone"])
+		userType := putil.GetUserType(s_m_openid, a_m_openid, s_phone, util.IntAllDef(temp["i_type"], 0))
+		isPush := util.IntAllDef(temp["i_ispush"], 1)
+		//公众号取关用户,后面还有pc助手推送,暂时不过滤app用户
+		if userType == 0 && isPush == 0 {
+			continue
+		}
+		applystatus := util.IntAll(temp["i_applystatus"])
+		o_msgset, _ := temp["o_jy"].(map[string]interface{})
+		wxpush, apppush, mailpush := putil.ModeTransform(userType, o_msgset)
+		jpushid := util.ObjToString(temp["s_jpushid"])
+		opushid := util.ObjToString(temp["s_opushid"])
+		var allkeysTemp []elastic.KeyConfig
+		_bs, err := json.Marshal(o_msgset["a_key"])
+		if err == nil {
+			json.Unmarshal(_bs, &allkeysTemp)
+		}
+		allkeys := []elastic.KeyConfig{}
+		if len(allkeysTemp) > 0 {
+			//一个字或者配置文件中的词,不推送
+			for _, vs := range allkeysTemp {
+				isFilter := false
+				vskey := strings.Replace(strings.Join(vs.Keys, ""), " ", "", -1)
+				if len([]rune(vskey)) == 1 {
+					continue
+				}
+				for _, fv := range SysConfig.FilterWords {
+					if fv == vskey {
+						isFilter = true
+						break
+					}
+				}
+				if !isFilter {
+					allkeys = append(allkeys, vs)
+				}
+			}
+		}
+		////////////////
+		if len(allkeys) == 0 {
+			continue
+		}
+		userId := fmt.Sprintf("%x", string(temp["_id"].(bson.ObjectId)))
+		logger.Info("匹配数据任务,第", user_batch_index, "批用户,userid", userId, "s_m_openid", s_m_openid, "a_m_openid", a_m_openid, "s_phone", s_phone, "jpushid", jpushid, "opushid", opushid, "applystatus", applystatus)
+		keys := []string{}                           //过滤后的关键词
+		notkeys := []string{}                        //排除词
+		key_notkey := map[string]map[string]bool{}   //关键词所对应的排除词
+		key_area := map[string]map[string]bool{}     //关键词所对应的信息范围
+		key_infotype := map[string]map[string]bool{} //关键词所对应的信息类型
+		for _, vs := range allkeys {
+			key := strings.Join(vs.Keys, "+")
+			keys = append(keys, key)
+			notkeys = append(notkeys, vs.NotKeys...)
+			//转大写
+			keyTemp := strings.ToUpper(key)
+			//建立与排除词的对应关系
+			for _, notkey := range vs.NotKeys {
+				notkeyTemp := strings.ToUpper(notkey)
+				if key_notkey[keyTemp] == nil {
+					key_notkey[keyTemp] = map[string]bool{}
+				}
+				key_notkey[keyTemp][notkeyTemp] = true
+			}
+			//建立与信息范围的对应关系
+			for _, area := range vs.Areas {
+				if key_area[keyTemp] == nil {
+					key_area[keyTemp] = map[string]bool{}
+				}
+				key_area[keyTemp][area] = true
+			}
+			//建立与信息类型的对应关系
+			for _, infotype := range vs.InfoTypes {
+				if key_infotype[keyTemp] == nil {
+					key_infotype[keyTemp] = map[string]bool{}
+				}
+				key_infotype[keyTemp][infotype] = true
+			}
+		}
+		//
+		keysTemp := []string{} //原始关键词
+		for _, vs := range allkeysTemp {
+			keysTemp = append(keysTemp, strings.Join(vs.Keys, "+"))
+		}
+		smartset := util.IntAllDef(temp["i_smartset"], 0)
+		modifydate := ""
+		md, _ := o_msgset["l_modifydate"].(int64)
+		if md > 0 {
+			modifydate = util.FormatDateByInt64(&md, util.Date_Short_Layout)
+		}
+		if modifydate == "" {
+			now := time.Now()
+			modifydate = util.FormatDate(&now, util.Date_Short_Layout)
+		}
+		user := &UserInfo{
+			Id:           userId,
+			OriginalKeys: keysTemp,
+			Keys:         keys, //原始关键词
+			NotKeys:      notkeys,
+			Key_notkey:   key_notkey,
+			Key_area:     key_area,
+			Key_infotype: key_infotype,
+			WxPush:       wxpush,
+			AppPush:      apppush,
+			MailPush:     mailpush,
+			Email:        util.ObjToString(o_msgset["s_email"]),
+			S_m_openid:   s_m_openid,
+			A_m_openid:   a_m_openid,
+			Phone:        s_phone,
+			Jpushid:      jpushid,
+			Opushid:      opushid,
+			UserType:     userType,
+			RateMode:     util.IntAllDef(o_msgset["i_ratemode"], 2),
+			AllKeys:      allkeysTemp, //原始关键词
+			SmartSet:     smartset,
+			DataExport:   util.IntAllDef(temp["i_dataexport"], 0),
+			ModifyDate:   modifydate,
+			AppPhoneType: util.ObjToString(temp["s_appponetype"]),
+			ApplyStatus:  applystatus,
+			Subscribe:    isPush,
+			MergeOrder:   temp["a_mergeorder"],
+		}
+		(*m.users)[user.Id] = user
+		m.lastUserId = user.Id
+		temp = make(map[string]interface{})
+		n++
+		if n == SysConfig.UserBatch {
+			break
+		}
+	}
+	logger.Info("匹配数据任务,第", user_batch_index, "批用户加载结束", n)
+	return n
+}
+
+//把用户挂在词下面
+func (m *MatchJob) MakeKeyUser(keys []string, user *UserInfo, key_user *map[string]*[]*UserInfo) {
+	mp := map[string]bool{}
+	for _, key := range keys {
+		v := strings.ToUpper(key)
+		if v == "" || mp[v] {
+			continue
+		}
+		mp[v] = true
+		var arr *[]*UserInfo
+		if nil == (*key_user)[v] {
+			arr = &[]*UserInfo{}
+			(*key_user)[v] = arr
+		} else {
+			arr = (*key_user)[v]
+			(*key_user)[v] = arr
+		}
+		*arr = append(*arr, user)
+	}
+}
+
+//遍历数据并执行推送操作
+func (m *MatchJob) EachAllBidInfo(a_p *Pjob, s_p *Pjob) *map[string]*list.List {
+	defer util.Catch()
+	logger.Info("匹配数据任务,开始遍历数据。。。")
+	userMap := &map[string]*list.List{}
+	var count int
+	for _, temp := range *m.datas {
+		m.matchPool <- true
+		m.eachInfoWaitGroup.Add(1)
+		count++
+		go func(info map[string]interface{}) {
+			defer func() {
+				m.eachInfoWaitGroup.Done()
+				<-m.matchPool
+			}()
+			title := util.ObjToString(info["title"])
+			if title == "" {
+				return
+			}
+			titleTemp := strings.ToUpper(title)
+			area := util.ObjToString(info["area"])
+			toptype := util.ObjToString(info["toptype"])
+			//订阅词
+			keys := a_p.InterestDfa.Analy(titleTemp)
+			//排除词
+			notkeys := a_p.NotInterestDfa.Analy(titleTemp)
+			users := m.GetFinalUser(keys, notkeys, a_p.Key_user, area, toptype, true)
+			//开启智能匹配的用户,匹配projectscope
+			if s_p != nil {
+				projectscope := util.ObjToString(info["projectscope"])
+				if projectscope == "" {
+					projectscope = util.ObjToString(info["detail"])
+				}
+				if projectscope != "" {
+					projectscopeTemp := strings.ToUpper(projectscope)
+					keys = s_p.InterestDfa.Analy(projectscopeTemp)
+					notkeys = s_p.NotInterestDfa.Analy(projectscopeTemp)
+					s_users := m.GetFinalUser(keys, notkeys, s_p.Key_user, area, toptype, false)
+					for _, s_u := range *s_users {
+						if (*users)[s_u.User.Id] != nil {
+							continue
+						}
+						(*users)[s_u.User.Id] = s_u
+					}
+				}
+			}
+			if len(*users) > 0 {
+				m.EachInfoToUser(users, &info, userMap)
+			}
+		}(temp)
+		if count%500 == 0 {
+			logger.Info("匹配数据任务,当前信息索引:", count)
+		}
+	}
+	m.eachInfoWaitGroup.Wait()
+	logger.Info("匹配数据任务,数据遍历完成!")
+	return userMap
+}
+
+//获取最终的用户,排除词、信息范围、信息类型之后的
+//返回匹配上的用户和没有匹配到的用户
+func (m *MatchJob) GetFinalUser(keys, notkeys []string, key_user *map[string]*[]*UserInfo, area, toptype string, flag bool) *map[string]*MatchUser {
+	keyMap := map[string]bool{}
+	for _, v := range keys {
+		keyMap[v] = true
+	}
+	y_users := map[string]*MatchUser{} //匹配到用户
+	//遍历所有用户
+	for k, us := range *key_user {
+		if !keyMap[k] { //改关键词没有匹配到的用户
+			continue
+		}
+		for _, u := range *us {
+			//获取该词下面所有的用户
+			//遍历我的排除词,如果存在的话,排除自己
+			isContinue := false
+			for _, notkey := range notkeys {
+				if u.Key_notkey[k][notkey] {
+					isContinue = true
+					break
+				}
+			}
+			if isContinue {
+				continue
+			}
+			//遍历我的信息范围,看该信息是不是在我的信息范围中
+			if len(u.Key_area[k]) > 0 && !u.Key_area[k][area] {
+				continue
+			}
+			//遍历我的信息类型,看该信息是不是在我的信息类型中
+			if len(u.Key_infotype[k]) > 0 && !u.Key_infotype[k][toptype] {
+				continue
+			}
+			matchUser := y_users[u.Id]
+			if matchUser == nil {
+				matchUser = &MatchUser{
+					User: u,
+					Keys: []string{},
+				}
+			}
+			matchUser.Keys = append(matchUser.Keys, k)
+			y_users[u.Id] = matchUser
+		}
+	}
+	//获取最终没有匹配到的用户,进行正文或者范围匹配
+	users := map[string]*MatchUser{}
+	for k, v := range *m.users {
+		if y_users[k] == nil {
+			continue
+		}
+		users[v.Id] = &MatchUser{
+			User: v,
+			Keys: y_users[k].Keys,
+		}
+	}
+	return &users
+}
+
+//遍历用户加入到此条信息上
+func (m *MatchJob) EachInfoToUser(users *map[string]*MatchUser, info *map[string]interface{}, userMap *map[string]*list.List) {
+	defer m.userMapLock.Unlock()
+	m.userMapLock.Lock()
+	for k, v := range *users {
+		l := (*userMap)[k]
+		if l == nil {
+			l = list.New()
+		}
+		l.PushBack(&MatchInfo{
+			Info: info,
+			Keys: v.Keys,
+		})
+		(*userMap)[k] = l
+	}
+}

+ 27 - 0
src/jfw/modules/pushsubscribe/src/match/job/timetask.go

@@ -0,0 +1,27 @@
+package job
+
+import (
+	"log"
+	. "match/config"
+	"qfw/util"
+	"time"
+)
+
+type timeTask struct {
+	Match *MatchTimeTask //匹配数据
+}
+
+var Task = &timeTask{
+	Match: &MatchTimeTask{},
+}
+
+type MatchTimeTask struct {
+}
+
+func (m *MatchTimeTask) Execute() {
+	Jobs.Match.Execute()
+	util.WriteSysConfig("./task.json", &TaskConfig)
+	t := time.Duration(SysConfig.MatchDuration) * time.Minute
+	log.Println("start match job after", t)
+	time.AfterFunc(t, m.Execute)
+}

+ 30 - 0
src/jfw/modules/pushsubscribe/src/match/main.go

@@ -0,0 +1,30 @@
+//订阅推送-匹配数据服务
+package main
+
+import (
+	"flag"
+	"log"
+	. "match/config"
+	"match/job"
+	"qfw/util/elastic"
+	"qfw/util/mongodb"
+
+	"github.com/donnie4w/go-logger/logger"
+)
+
+func main() {
+	modle := flag.Int("m", 0, "默认:0;1 匹配数据")
+	flag.Parse()
+	logger.SetConsole(false)
+	logger.SetRollingDaily("./logs", "match.log")
+	mongodb.InitMongodbPool(SysConfig.MgoSize, SysConfig.MgoAddr, "qfw")
+	elastic.InitElasticSize(SysConfig.ElasticSearch, SysConfig.ElasticPoolSize)
+	log.Println("订阅推送-匹配数据程序启动。。。")
+	if *modle == 1 {
+		job.Jobs.Match.Execute()
+	} else {
+		go job.Task.Match.Execute()
+		flag := make(chan bool)
+		<-flag
+	}
+}

二进制
src/jfw/modules/pushsubscribe/src/match/src


+ 1 - 0
src/jfw/modules/pushsubscribe/src/match/task.json

@@ -0,0 +1 @@
+{"startTime":"","lastId":"596f20265d11e1c7455dc5bc"}

+ 102 - 0
src/jfw/modules/pushsubscribe/src/match/util/util.go

@@ -0,0 +1,102 @@
+package util
+
+import (
+	"qfw/util"
+
+	"gopkg.in/mgo.v2/bson"
+)
+
+//重新设置用户类型
+func GetUserType(s_m_openid, a_m_openid, phone string, userType int) int {
+	if userType == 0 {
+		if s_m_openid != "" && a_m_openid == "" && phone == "" {
+			userType = 0 //公众号
+		} else if s_m_openid == "" && phone != "" {
+			userType = 1 //app手机号
+		} else if s_m_openid == "" && a_m_openid != "" {
+			userType = 2 //app微信
+		} else if s_m_openid != "" && a_m_openid == "" && phone == "" {
+			userType = 3 //用户合并以后只有微信用户
+		} else if s_m_openid == "" && (a_m_openid != "" || phone != "") {
+			userType = 4 //用户合并以后只有app用户
+		} else if s_m_openid != "" && (a_m_openid != "" || phone != "") {
+			userType = 5 //用户合并以后公众号和app用户都有
+		} else {
+			userType = -1
+		}
+	}
+	return userType
+}
+
+//推送方式转换
+func ModeTransform(userType int, o_msgset map[string]interface{}) (int, int, int) {
+	mode := util.IntAll(o_msgset["i_mode"])
+	wxpush := util.IntAll(o_msgset["i_wxpush"])
+	apppush := util.IntAll(o_msgset["i_apppush"])
+	mailpush := util.IntAll(o_msgset["i_mailpush"])
+	if wxpush == 1 || apppush == 1 || mailpush == 1 {
+		return wxpush, apppush, mailpush
+	}
+	//老的app用户
+	if userType == 1 || userType == 2 {
+		switch mode {
+		case 0, 1:
+			apppush = 1
+			break
+		case 2:
+			mailpush = 1
+			break
+		case 3:
+			apppush = 1
+			mailpush = 1
+			break
+		}
+		if apppush == 0 && mailpush == 0 {
+			apppush = 1
+		}
+	} else if userType == 0 {
+		switch mode {
+		case 0, 1:
+			wxpush = 1
+			break
+		case 2:
+			mailpush = 1
+			break
+		case 3:
+			wxpush = 1
+			mailpush = 1
+			break
+		}
+		if wxpush == 0 && mailpush == 0 {
+			wxpush = 1
+		}
+	} else {
+		switch mode {
+		case 0, 1, 3:
+			if userType == 3 {
+				wxpush = 1
+			} else if userType == 4 {
+				apppush = 1
+			} else if userType == 5 {
+				wxpush = 1
+				apppush = 1
+			}
+			if mode == 3 {
+				mailpush = 1
+			}
+			break
+		case 2:
+			mailpush = 1
+			break
+		}
+	}
+	return wxpush, apppush, mailpush
+}
+
+func ToObjectIds(ids []string) []bson.ObjectId {
+	_ids := []bson.ObjectId{}
+	for _, v := range ids {
+		_ids = append(_ids, bson.ObjectIdHex(v))
+	}
+	return _ids
+}

+ 65 - 0
src/jfw/modules/pushsubscribe/src/public/entity.go

@@ -0,0 +1,65 @@
+package public
+
+import (
+	"qfw/util"
+	"qfw/util/elastic"
+)
+
+type UserInfo struct {
+	Id           string                     //mongoid
+	Province     string                     //省份
+	Key_notkey   map[string]map[string]bool //关键词-排除词
+	Key_area     map[string]map[string]bool //关键词-信息范围
+	Key_infotype map[string]map[string]bool //关键词-信息类型
+	OriginalKeys []string                   //用户兴趣
+	Keys         []string                   //用户兴趣
+	NotKeys      []string                   //用户不感兴趣
+	S_m_openid   string                     //公众号openid
+	A_m_openid   string                     //app微信登录openid
+	Phone        string                     //app手机号登录
+	Jpushid      string
+	Opushid      string
+	InterestDate int64
+	WxPush       int
+	AppPush      int
+	MailPush     int
+	RateMode     int
+	SmartSet     int //智能订阅 1开启 0关闭
+	Email        string
+	DataExport   int //是否导出数据 1开启 0关闭
+	AllKeys      []elastic.KeyConfig
+	ModifyDate   string
+	AppPhoneType string
+	ApplyStatus  int
+	Subscribe    int
+	UserType     int
+	MergeOrder   interface{}
+	//
+	//Active int
+	//Fail   *Fail //失败重试
+}
+
+type Fail struct {
+	Wx    int
+	App   int
+	Email int
+}
+
+type SortList []*MatchInfo
+
+func (s SortList) Len() int {
+	return len(s)
+}
+
+func (s SortList) Less(i, j int) bool {
+	return util.Int64All((*s[i].Info)["publishtime"]) > util.Int64All((*s[j].Info)["publishtime"])
+}
+
+func (s SortList) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+type MatchInfo struct {
+	Info *map[string]interface{}
+	Keys []string
+}

+ 60 - 0
src/jfw/modules/pushsubscribe/src/push/config.json

@@ -0,0 +1,60 @@
+{
+	"jianyuDomain": "https://web-jydev-wcj.jianyu360.cn",
+	"cassandra": {
+		"cachesize": 10000,
+		"host": ["192.168.3.207"],
+		"open": true,
+		"size": 5,
+		"timeout": 20
+	},
+	"redisServers": "pushcache=192.168.3.18:3379",
+	"mail_content": "<tr><td><num>%d</num></td><td><div class='tit'><a style='color: #000;text-decoration: none;' href='%s?mail' >%s</a></div></td><td style='float: right;' class='infos' ><span class='%s'>%s</span><span class='%s'>%s</span><span class='%s'>%s</span><span class='time'>%s</span></td></tr>",
+	"mail_html": "<body><style> *,body,html{margin:0;padding:0;font-family: tahoma, arial, 'Hiragino Sans GB', 'Microsoft YaHei', 宋体, sans-serif;font-size:16px; }#all{margin:0 auto;width:1024px;overflow:hidden;}.head{margin:5x;margin-top:20px;}.des{padding-bottom:15px;border-bottom:1px solid #e8ecee;color: #686868;}td a:hover {color: #fe7379;text-decoration: underline;} .tit{width:560px;overflow: hidden;    white-space: nowrap;text-overflow: ellipsis;}.area {background-color: #2cb7ca;border-radius: 3px;color: #fff;padding: 1px 2px;}.type {background-color: #ffba00;border-radius: 3px;color: #fff;padding: 1px 2px;margin-left:5px;}.industry {background-color: #25c78c;border-radius: 3px;color: #fff;padding: 1px 2px;margin-left:5px;}.infos span{display:inline-block;margin-left:5px;}td{padding-top:8px;padding-bottom:8px;height:20px;line-height:20px;}num{padding:0 5px 0 0; font-size:16px;color:#2cb7ca;font-weight:bolder;}.keys{color:blue;} </style><div id='all'><div class='head'><IMG width='100px' src=http://www.zhaobiao.info/images/swordfish/sf_01.png /></div><div class='head des'>根据您设置的关键词 :<span class='keys'>%s</span>,剑鱼为您推送30天之内的信息。点击标题可查看详情信息</div><table cellpadding='0' cellspacing='0'>%s</table></div> </body>",
+	"mail_title": "您有新的%s信息-剑鱼招标订阅",
+	"mails": [{
+		"addr": "smtp.exmail.qq.com",
+		"port": 465,
+		"pwd": "ue9Rg9Sf4CVtdm5a",
+		"user": "public03@topnet.net.cn"
+	}],
+	"maxPushSize": 50,
+	"messyCodeEmailReg": "(@(126|163)\\.com)$",
+	"mgoAddr": "192.168.3.18:27080",
+	"mgoSize": 10,
+	"testids": ["5c8f4f4325ef8723d0bc1082"],
+	"weixinRpcServer": "127.0.0.1:8083",
+	"wxColor": "#2cb7ca",
+	"wxContent": "剑鱼推送",
+	"wxGroup": "招标信息",
+	"wxTitle": "根据你订阅的关键词“%s”,剑鱼为你推送以下信息。如果不想继续收到此类信息,可进入招标订阅的设置页面取消订阅。",
+	"wxDetailColor":"#686868",
+	"appPushServiceRpc":"127.0.0.1:5566",
+	"pcHelper":"192.168.20.129:8082",
+	"startPushTime":"08:00",
+	"endPushTime":"20:00",
+	"oncePushTime": "9:00",
+	"otherPushTimes":[
+		"14:00",
+		"18:00"
+	],
+	"cassandraPollSize":10,
+	"pushPoolSize": 60,
+	"minutePushSize": 300,
+	"fastigiumMinutePushSize": 100,
+	"fastigiumTime":"9-11",
+	"mailPollSize": 20,
+	"wxPollSize": 40,
+	"appPollSize": 50,
+	"pushDuration": 2,
+	"retry": 3,
+	"mailReTry":2,
+	"mailSleep":200,
+	"cassandraSleep":200,
+	"appSleep":5,
+	"wxSleep":5,
+	"pcHelperSleep":5,
+	"isPushMail":true,
+	"inactivityPushHour": 10,
+	"pushBatch":2,
+	"ninePushRedisTimeout": 172800
+}

+ 95 - 0
src/jfw/modules/pushsubscribe/src/push/config/config.go

@@ -0,0 +1,95 @@
+package config
+
+import (
+	"qfw/util"
+	"qfw/util/mail"
+	"regexp"
+)
+
+type sysConfig struct {
+	JianyuDomain            string      `json:"jianyuDomain"`
+	Cassandra               *cassandra  `json:"cassandra"`
+	RedisServers            string      `json:"redisServers"`
+	PushPoolSize            int         `json:"pushPoolSize"`
+	Mail_content            string      `json:"mail_content"`
+	Mail_html               string      `json:"mail_html"`
+	Mail_title              string      `json:"mail_title"`
+	Mails                   []*pushMail `json:"mails"`
+	MaxPushSize             int         `json:"maxPushSize"`
+	MessyCodeEmailReg       string      `json:"messyCodeEmailReg"`
+	MgoAddr                 string      `json:"mgoAddr"`
+	MgoSize                 int         `json:"mgoSize"`
+	TestIds                 []string    `json:"testIds"`
+	WeixinRpcServer         string      `json:"weixinRpcServer"`
+	WxColor                 string      `json:"wxColor"`
+	WxGroup                 string      `json:"wxGroup"`
+	WxContent               string      `json:"wxContent"`
+	WxTitle                 string      `json:"wxTitle"`
+	WxDetailColor           string      `json:"wxDetailColor"`
+	AppPushServiceRpc       string      `json:"appPushServiceRpc"`
+	PcHelper                string      `json:"pcHelper"`
+	PushDuration            int64       `json:"pushDuration"`
+	Retry                   int         `json:"retry"`
+	InactivityPushHour      int         `json:"inactivityPushHour"`
+	PushBatch               int         `json:"pushBatch"`
+	StartPushTime           string      `json:"startPushTime"`
+	EndPushTime             string      `json:"endPushTime"`
+	OncePushTime            string      `json:"oncePushTime"`
+	OtherPushTimes          []string    `json:"otherPushTimes"`
+	MailPollSize            int         `json:"mailPollSize"`
+	WxPollSize              int         `json:"wxPollSize"`
+	AppPollSize             int         `json:"appPollSize"`
+	MailReTry               int         `json:"mailReTry"`
+	MailSleep               int         `json:"mailSleep"`
+	CassandraSleep          int         `json:"cassandraSleep"`
+	AppSleep                int         `json:"appSleep"`
+	WxSleep                 int         `json:"wxSleep"`
+	PcHelperSleep           int         `json:"pcHelperSleep"`
+	IsPushMail              bool        `json:"isPushMail"`
+	CassandraPollSize       int         `json:"cassandraPollSize"`
+	MinutePushSize          int         `json:"minutePushSize"`
+	FastigiumMinutePushSize int         `json:"fastigiumMinutePushSize"`
+	FastigiumTime           string      `json:"fastigiumTime"`
+	NinePushRedisTimeout    int         `json:"ninePushRedisTimeout"`
+}
+type pushMail struct {
+	Addr string `json:"addr"`
+	Port int    `json:"port"`
+	Pwd  string `json:"pwd"`
+	User string `json:"user"`
+}
+type cassandra struct {
+	Cachesize int      `json:"cachesize"`
+	Host      []string `json:"host"`
+	Open      bool     `json:"open"`
+	Size      int      `json:"size"`
+	Timeout   int      `json:"timeout"`
+}
+
+var (
+	Gmails       chan *mail.GmailAuth
+	Gmail        *mail.GmailAuth
+	Se           = util.SimpleEncrypt{Key: "topnet"}
+	Re           = regexp.MustCompile("<[^>]+>([^<]+)?<[^>]+>")
+	SysConfig    *sysConfig
+	WxContentLen int
+)
+
+func init() {
+	//html过滤
+	util.ReadConfig("./config.json", &SysConfig)
+	//
+	WxContentLen = len([]rune(SysConfig.WxContent))
+	//Gmails = make(chan *mail.GmailAuth, len(SysConfig.Mails))
+	for _, v := range SysConfig.Mails {
+		Gmail = &mail.GmailAuth{
+			SmtpHost: v.Addr,
+			SmtpPort: v.Port,
+			User:     v.User,
+			Pwd:      v.Pwd,
+			PoolSize: SysConfig.MailPollSize,
+			ReTry:    SysConfig.MailReTry,
+		}
+		break
+	}
+}

+ 24 - 0
src/jfw/modules/pushsubscribe/src/push/job/job.go

@@ -0,0 +1,24 @@
+package job
+
+import (
+	"push/config"
+	"sync"
+)
+
+type Job interface {
+	MySelf() interface{}
+	Execute(taskType int, isMove bool)
+}
+
+type jobs struct {
+	Nine Job
+	Push Job
+}
+
+var Jobs = &jobs{
+	Push: &PushJob{
+		pushPool:    make(chan bool, config.SysConfig.PushPoolSize),
+		pushWait:    &sync.WaitGroup{},
+		pushJobLock: &sync.Mutex{},
+	},
+}

+ 804 - 0
src/jfw/modules/pushsubscribe/src/push/job/pushjob.go

@@ -0,0 +1,804 @@
+package job
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	. "public"
+	. "push/config"
+	putil "push/util"
+	"qfw/util"
+	"qfw/util/mail"
+	"qfw/util/mongodb"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/donnie4w/go-logger/logger"
+	mgo "gopkg.in/mgo.v2"
+	"gopkg.in/mgo.v2/bson"
+)
+
+const (
+	BulkSize = 200
+	DbName   = "qfw"
+)
+
+var (
+	messyCodeEmailReg = regexp.MustCompile(SysConfig.MessyCodeEmailReg)
+	MoveFields        = []string{"s_m_openid", "a_m_openid", "phone", "usertype", "jpushid", "opushid", "words", "ratemode", "wxpush", "apppush", "mailpush", "smartset", "timestamp", "subscribe", "applystatus", "appphonetype", "email", "size", "modifydate", "mergeorder"}
+)
+
+func init() {
+	mySelf := Jobs.Push.MySelf().(*PushJob)
+	//推送1分钟限制
+	mySelf.minutePushPool = make(chan bool, SysConfig.MinutePushSize)
+	mySelf.fastigiumMinutePushPool = make(chan bool, SysConfig.FastigiumMinutePushSize)
+	for i := 0; i < SysConfig.MinutePushSize; i++ {
+		mySelf.minutePushPool <- true
+	}
+	for i := 0; i < SysConfig.FastigiumMinutePushSize; i++ {
+		mySelf.fastigiumMinutePushPool <- true
+	}
+	go func() {
+		t := time.NewTicker(time.Minute)
+		for {
+			select {
+			case <-t.C:
+				for i := 0; i < SysConfig.MinutePushSize-len(mySelf.minutePushPool); i++ {
+					mySelf.minutePushPool <- true
+				}
+				for i := 0; i < SysConfig.FastigiumMinutePushSize-len(mySelf.fastigiumMinutePushPool); i++ {
+					mySelf.fastigiumMinutePushPool <- true
+				}
+			}
+		}
+	}()
+}
+
+type PushJob struct {
+	pushPool                chan bool
+	minutePushPool          chan bool
+	fastigiumMinutePushPool chan bool
+	pushWait                *sync.WaitGroup
+	pushJobLock             *sync.Mutex
+	lastId                  string
+	pushDatas               *[]map[string]interface{}
+}
+
+func (p *PushJob) MySelf() interface{} {
+	return p
+}
+
+//taskType 1--实时推送 2--实时推送+一天三次的8点推送 3--一天三次推送 4--九点推送
+func (p *PushJob) Execute(taskType int, isMoveDatas bool) {
+	p.pushJobLock.Lock()
+	defer p.pushJobLock.Unlock()
+	logger.Info("开始推送任务。。。", taskType)
+	if isMoveDatas {
+		p.Move()
+	}
+	p.Push(taskType)
+}
+func (p *PushJob) Move() {
+	logger.Info("推送任务,开始迁移数据。。。")
+	sess := mongodb.GetMgoConn()
+	defer mongodb.DestoryMongoConn(sess)
+	nowUnix := time.Now().Unix()
+	it := sess.DB(DbName).C("pushspace_temp").Find(map[string]interface{}{
+		"timestamp": map[string]interface{}{
+			"$lt": nowUnix,
+		},
+	}).Sort("_id").Iter()
+	_index := 0
+	pushspace_temp := map[string]map[string]interface{}{}
+	logger.Info("推送任务,开始把pushspace_temp表的数据加载到内存中")
+	for _temp := make(map[string]interface{}); it.Next(&_temp); {
+		userId := util.ObjToString(_temp["userid"])
+		if pushspace_temp[userId] != nil {
+			sl := make(SortList, 0)
+			if newList := p.ToSortList(_temp["list"], userId); newList != nil {
+				sl = append(sl, newList...)
+			}
+			oldList, _ := pushspace_temp[userId]["list"].(SortList)
+			sl = append(sl, oldList...)
+			sort.Sort(sl)
+			if len(sl) > SysConfig.MaxPushSize {
+				sl = sl[:SysConfig.MaxPushSize]
+			}
+			_temp["list"] = sl
+		} else {
+			_temp["list"] = p.ToSortList(_temp["list"], userId)
+		}
+		pushspace_temp[userId] = _temp
+		_temp = make(map[string]interface{})
+		_index++
+		if _index%500 == 0 {
+			logger.Info("推送任务,pushspace_temp表的数据加载到内存中:", _index)
+		}
+	}
+	logger.Info("推送任务,pushspace_temp表的数据加载完成")
+	index := 0
+	saveArray := []map[string]interface{}{}
+	updateArray_d := []map[string]interface{}{}
+	updateArray_q := []map[string]interface{}{}
+	updateArray_s := []map[string]interface{}{}
+	for userId, temp := range pushspace_temp {
+		var data map[string]interface{}
+		sess.DB(DbName).C("pushspace").Find(map[string]interface{}{"userid": userId}).Select(map[string]interface{}{"list": 1, "templist": 1}).One(&data)
+		if data == nil { //批量新增
+			saveArray = append(saveArray, temp)
+			if len(saveArray) == BulkSize {
+				p.SaveBulk(sess, &saveArray, nowUnix)
+			}
+		} else { //批量更新
+			setMap := map[string]interface{}{}
+			for _, field := range MoveFields {
+				if temp[field] == nil {
+					continue
+				}
+				setMap[field] = temp[field]
+			}
+			//
+			newList, _ := temp["list"].(SortList)
+			if newList == nil || len(newList) == 0 {
+				continue
+			}
+			pLength := len(newList)
+			pushAll := make(map[string]interface{})
+			for _, v := range []string{"", "temp"} {
+				oldList := p.ToSortList(data[v+"list"], userId)
+				if v == "temp" && oldList == nil {
+					continue
+				}
+				rLength := len(oldList)
+				if rLength+pLength > SysConfig.MaxPushSize {
+					newList = append(newList, oldList...)
+					if len(newList) > SysConfig.MaxPushSize {
+						newList = newList[:SysConfig.MaxPushSize]
+					}
+					setMap[v+"list"] = newList
+					setMap[v+"size"] = SysConfig.MaxPushSize
+				} else { //追加
+					setMap[v+"size"] = rLength + pLength
+					pushAll[v+"list"] = newList
+				}
+			}
+			upSet := map[string]interface{}{
+				"$set": setMap,
+			}
+			if len(pushAll) > 0 {
+				upSet["$pushAll"] = pushAll
+			}
+			updateArray_d = append(updateArray_d, map[string]interface{}{"userid": userId})
+			updateArray_q = append(updateArray_q, map[string]interface{}{"_id": data["_id"]})
+			updateArray_s = append(updateArray_s, upSet)
+			if len(updateArray_q) == BulkSize {
+				p.UpdateBulk(sess, &updateArray_d, &updateArray_q, &updateArray_s, nowUnix)
+			}
+		}
+		index++
+		if index%500 == 0 {
+			logger.Info("推送任务,迁移数据:", index)
+		}
+	}
+	if len(saveArray) > 0 {
+		p.SaveBulk(sess, &saveArray, nowUnix)
+	}
+	if len(updateArray_q) > 0 {
+		p.UpdateBulk(sess, &updateArray_d, &updateArray_q, &updateArray_s, nowUnix)
+	}
+	logger.Info("推送任务,迁移数据结束。。。", index)
+}
+func (p *PushJob) SaveBulk(sess *mgo.Session, array *[]map[string]interface{}, nowUnix int64) {
+	coll := sess.DB(DbName).C("pushspace")
+	bulk := coll.Bulk()
+	for _, v := range *array {
+		bulk.Insert(v)
+	}
+	_, err := bulk.Run()
+	if nil != err {
+		logger.Info("推送任务,BulkError", err)
+	} else {
+		p.DelBulk(sess, array, nowUnix)
+	}
+	*array = []map[string]interface{}{}
+}
+func (p *PushJob) UpdateBulk(sess *mgo.Session, array_d, array_q, array_s *[]map[string]interface{}, nowUnix int64) {
+	coll := sess.DB(DbName).C("pushspace")
+	bulk := coll.Bulk()
+	for k, v := range *array_q {
+		bulk.Update(v, (*array_s)[k])
+	}
+	_, err := bulk.Run()
+	if nil != err {
+		logger.Info("推送任务,UpdateBulkError", err)
+	} else {
+		p.DelBulk(sess, array_d, nowUnix)
+	}
+	*array_d = []map[string]interface{}{}
+	*array_q = []map[string]interface{}{}
+	*array_s = []map[string]interface{}{}
+}
+func (p *PushJob) DelBulk(sess *mgo.Session, array *[]map[string]interface{}, nowUnix int64) {
+	coll := sess.DB(DbName).C("pushspace_temp")
+	bulk := coll.Bulk()
+	for _, v := range *array {
+		bulk.RemoveAll(map[string]interface{}{
+			"userid": v["userid"],
+			"timestamp": map[string]interface{}{
+				"$lt": nowUnix,
+			},
+		})
+	}
+	_, err := bulk.Run()
+	if nil != err {
+		logger.Info("推送任务,DelBulkError", err)
+	}
+}
+func (p *PushJob) Push(taskType int) {
+	logger.Info("推送任务,开始推送。。。")
+	p.lastId = ""
+	batch_index := 0
+	for {
+		batch_index++
+		batch_size := p.OncePushBatch(batch_index, taskType)
+		for _, temp := range *p.pushDatas {
+			p.pushPool <- true
+			p.pushWait.Add(1)
+			go func(v map[string]interface{}) {
+				defer func() {
+					<-p.pushPool
+					p.pushWait.Done()
+				}()
+				words, _ := v["words"].([]interface{})
+				u := &UserInfo{
+					Id:           util.ObjToString(v["userid"]),
+					OriginalKeys: util.ObjArrToStringArr(words),
+					WxPush:       util.IntAll(v["wxpush"]),
+					AppPush:      util.IntAll(v["apppush"]),
+					MailPush:     util.IntAll(v["mailpush"]),
+					Email:        util.ObjToString(v["email"]),
+					S_m_openid:   util.ObjToString(v["s_m_openid"]),
+					A_m_openid:   util.ObjToString(v["a_m_openid"]),
+					Phone:        util.ObjToString(v["phone"]),
+					Jpushid:      util.ObjToString(v["jpushid"]),
+					Opushid:      util.ObjToString(v["opushid"]),
+					UserType:     util.IntAll(v["usertype"]),
+					RateMode:     util.IntAllDef(v["ratemode"], 1),
+					SmartSet:     util.IntAllDef(v["smartset"], 1),
+					DataExport:   util.IntAll(v["dataexport"]),
+					AppPhoneType: util.ObjToString(v["appphonetype"]),
+					ApplyStatus:  util.IntAll(v["applystatus"]),
+					Subscribe:    util.IntAllDef(v["subscribe"], 1),
+					ModifyDate:   util.ObjToString(v["modifydate"]),
+					MergeOrder:   v["mergeorder"],
+				}
+				logger.Info("推送任务,开始推送用户,userid", u.Id, "s_m_openid", u.S_m_openid, "a_m_openid", u.A_m_openid, "phone", u.Phone, "jpushid", u.Jpushid, "opushid", u.Opushid, "applystatus", u.ApplyStatus)
+				wxPush, appPush, mailPush := 0, 0, 0
+				list := p.ToSortList(v["list"], u.Id)
+				templist := p.ToSortList(v["templist"], u.Id)
+				if taskType == 1 {
+					if u.WxPush == 1 {
+						if u.ApplyStatus == 1 {
+							wxPush = -1
+						} else {
+							wxPush = 1
+						}
+					}
+					if u.AppPush == 1 {
+						appPush = 1
+					}
+					if u.MailPush == 1 {
+						mailPush = -1
+					}
+				} else if taskType == 2 || taskType == 4 {
+					if u.WxPush == 1 {
+						wxPush = 1
+					}
+					if u.AppPush == 1 {
+						appPush = 1
+					}
+					if u.MailPush == 1 {
+						mailPush = 1
+					}
+				} else if taskType == 3 {
+					if u.WxPush == 1 && u.ApplyStatus == 1 {
+						wxPush = 1
+					}
+					if u.MailPush == 1 {
+						mailPush = 1
+					}
+				} else if taskType == 4 {
+					if u.WxPush == 1 {
+						wxPush = 1
+					}
+					if u.AppPush == 1 {
+						appPush = 1
+					}
+					if u.MailPush == 1 {
+						mailPush = 1
+					}
+				}
+				//再对取消关注以及app没有登录的用户进行过滤
+				if u.Subscribe == 0 {
+					wxPush = 0
+				}
+				if u.Jpushid == "" && u.Opushid == "" {
+					appPush = 0
+				}
+				if mailPush != 0 {
+					if (u.UserType == 0 || u.UserType == 3) && u.Subscribe == 0 {
+						mailPush = 0
+					} else if (u.UserType == 1 || u.UserType == 2 || u.UserType == 4) && u.Jpushid == "" && u.Opushid == "" {
+						mailPush = 0
+					} else if u.UserType == 5 && u.Subscribe == 0 && u.Jpushid == "" && u.Opushid == "" {
+						mailPush = 0
+					}
+				}
+				t_wxpush, t_mailpush := util.IntAll(v["tempwxpush"]), util.IntAll(v["tempmailpush"])
+				if templist != nil {
+					if wxPush == 1 && t_wxpush == 0 {
+						wxPush = 0
+					}
+					if mailPush == 1 && t_mailpush == 0 {
+						mailPush = 0
+					}
+				}
+				logger.Info("推送任务,本次推送任务类型", taskType, "用户的接收方式", u.Id, wxPush, appPush, mailPush, t_wxpush, t_mailpush)
+				if wxPush == 1 || appPush == 1 || mailPush == 1 {
+					isSaveSuccess := false
+					//wxPushStatus, appPushStatus, mailPushStatus := 0, 0, 0
+					//开通订阅推送或邮箱推送的用户,由实时推送修改成九点推送,app推送用list字段,微信和邮箱推送用templist字段
+					if list != nil && templist != nil {
+						isSaveSuccess, _, _, _ = p.DealSend(taskType, true, 0, appPush, 0, u, &list)
+						if !isSaveSuccess {
+							return
+						}
+						p.DealSend(taskType, false, wxPush, 0, mailPush, u, &templist)
+					} else if list != nil {
+						isSaveSuccess, _, _, _ = p.DealSend(taskType, true, wxPush, appPush, mailPush, u, &list)
+						if !isSaveSuccess {
+							return
+						}
+					} else if templist != nil {
+						p.DealSend(taskType, false, wxPush, 0, mailPush, u, &templist)
+					}
+					/*if wxPush == 1 {
+						if wxPushStatus == -1 {
+							wxPush = -1
+						} else {
+							wxPush = 0
+						}
+					}
+					if mailPush == 1 {
+						if mailPushStatus == -1 {
+							mailPush = -1
+						} else {
+							mailPush = 0
+						}
+					}*/
+					//判断是否要删除数据
+					_sess := mongodb.GetMgoConn()
+					defer mongodb.DestoryMongoConn(_sess)
+					if wxPush == -1 || mailPush == -1 {
+						//如果该用户还有微信或者邮箱推送,把list字段的值挪到templist
+						update := map[string]interface{}{}
+						set := map[string]interface{}{
+							"tempwxpush":   wxPush,
+							"tempmailpush": mailPush,
+						}
+						if templist == nil {
+							update["$rename"] = map[string]interface{}{"list": "templist"}
+						} else {
+							update["$unset"] = map[string]interface{}{"list": ""}
+						}
+						update["$set"] = set
+						_sess.DB(DbName).C("pushspace").UpdateId(v["_id"], update)
+					} else {
+						_sess.DB(DbName).C("pushspace").RemoveId(v["_id"])
+					}
+				}
+			}(temp)
+		}
+		if batch_size < SysConfig.PushBatch {
+			break
+		}
+	}
+	p.pushWait.Wait()
+	logger.Info("推送任务结束。。。", taskType)
+}
+func (p *PushJob) DealSend(taskType int, isSave bool, wxPush, appPush, mailPush int, k *UserInfo, sl *SortList) (isSaveSuccess bool, wxPushStatus, appPushStatus, mailPushStatus int) {
+	defer util.Catch()
+	str := fmt.Sprintf("<div>根据您设置的关键词(%s),给您推送以下信息:</div>", strings.Join(k.OriginalKeys, ";"))
+	mailContent := ""
+	//发送内容组合
+	i := 0
+	jpushtitle := ""
+	lastInfoDate := int64(0)
+	TitleArray := []string{}
+	o_pushinfo := map[string]map[string]interface{}{}
+	matchKey_infoIndex := map[string]string{}
+	publishTitle := map[string]bool{}
+	//邮件附件
+	var fmdatas = []map[string]interface{}{}
+	for _, ks := range *sl {
+		k2 := *ks.Info
+		title := strings.Replace(k2["title"].(string), "\n", "", -1)
+		area := util.ObjToString(k2["area"])
+		if area == "A" {
+			area = "全国"
+		}
+		newTitle := fmt.Sprintf("[%s]%s", area, title)
+		if publishTitle[newTitle] {
+			continue
+		}
+		publishTitle[title] = true
+		i++
+		TitleArray = append(TitleArray, Re.ReplaceAllString(newTitle, "$1"))
+		if i == 1 {
+			jpushtitle = title
+			lastInfoDate = util.Int64All(k2["publishtime"])
+		}
+		//_sid := util.EncodeArticleId(util.BsonIdToSId(k2["_id"]))
+		//url := fmt.Sprintf("%s/pcdetail/%s.html", Domain, _sid)
+		_sid := util.EncodeArticleId2ByCheck(util.ObjToString(k2["_id"]))
+		//增加行业的处理
+		industry := ""
+		industryclass := "industry"
+		if k2["s_subscopeclass"] != nil {
+			k2sub := strings.Split(util.ObjToString(k2["s_subscopeclass"]), ",")
+			if len(k2sub) > 0 {
+				industry = k2sub[0]
+				if industry != "" {
+					ss := strings.Split(industry, "_")
+					if len(ss) > 1 {
+						industry = ss[0]
+					}
+				}
+			}
+		}
+		if mailPush == 1 { //关于邮件的处理
+			mailSid := util.CommonEncodeArticle("mailprivate", util.ObjToString(k2["_id"]))
+			url := fmt.Sprintf("%s/article/mailprivate/%s.html", SysConfig.JianyuDomain, mailSid)
+			classArea := "area"
+			classType := "type"
+			types := util.ObjToString(k2["subtype"])
+			if types == "" {
+				types = util.ObjToString(k2["toptype"])
+				if types == "" {
+					types = "其他"
+				}
+			}
+			dates := util.LongToDate(k2["publishtime"], false)
+			//标题替换
+			otitle := title
+			for _, kw := range k.OriginalKeys {
+				kws := strings.Split(kw, "+")
+				n := 0
+				otitle2 := otitle
+				for _, kwn := range kws {
+					ot := strings.Replace(otitle2, kwn, "<span class='keys'>"+kwn+"</span>", 1)
+					if ot != otitle {
+						n++
+						otitle2 = ot
+					} else {
+						break
+					}
+				}
+				if n == len(kws) {
+					otitle = otitle2
+					break
+				}
+			}
+			if industry == "" {
+				industryclass = ""
+			}
+			mailContent += fmt.Sprintf(SysConfig.Mail_content, i, url, otitle, classArea, area, classType, types, industryclass, industry, dates)
+		}
+		str += "<div class='tslist'><span class='xh'>" + fmt.Sprintf("%d", i) + ".</span><a class='bt' target='_blank' eid='" + _sid + "' href='" + util.ObjToString(k2["href"]) + "'>[<span class='area'>" + area + "</span>]" + title + "</a></div>"
+		o_pushinfo[strconv.Itoa(i)] = map[string]interface{}{
+			"publishtime":   k2["publishtime"],
+			"stype":         util.ObjToString(k2["type"]),
+			"topstype":      util.ObjToString(k2["toptype"]),
+			"substype":      util.ObjToString(k2["subtype"]),
+			"subscopeclass": industry,
+			"buyer":         k2["buyer"],
+			"projectname":   k2["projectname"],
+			"budget":        k2["budget"],
+			"bidopentime":   k2["bidopentime"],
+			"winner":        k2["winner"],
+			"bidamount":     k2["bidamount"],
+		}
+		//附件数据
+		fmdata := map[string]interface{}{
+			"publishtime": k2["publishtime"],
+			"subtype":     k2["subtype"],
+			"buyer":       k2["buyer"],
+			"projectname": k2["projectname"],
+			"budget":      k2["budget"],
+			"bidopentime": k2["bidopentime"],
+			"winner":      k2["winner"],
+			"bidamount":   k2["bidamount"],
+		}
+		fmdatas = append(fmdatas, fmdata)
+		//匹配到的关键词
+		for _, key := range (*ks).Keys {
+			if matchKey_infoIndex[key] != "" {
+				matchKey_infoIndex[key] = matchKey_infoIndex[key] + ","
+			}
+			matchKey_infoIndex[key] = matchKey_infoIndex[key] + strconv.Itoa(i)
+		}
+		if i >= SysConfig.MaxPushSize {
+			//限制最大信息条数
+			break
+		}
+	}
+	if i == 0 {
+		logger.Info("推送任务,没有要推送的数据!", k.S_m_openid, k.A_m_openid, k.Phone)
+		return
+	}
+	if isSave {
+		//推送记录id
+		pushId := putil.SaveSendInfo(taskType, k, str, o_pushinfo, matchKey_infoIndex)
+		if pushId == "" {
+			logger.Info("推送任务,保存到cassandra出错", k.Id, k.S_m_openid, k.A_m_openid, k.Phone)
+			return
+		} else {
+			logger.Info("推送任务,成功保存到cassandra", pushId, k.Id, k.S_m_openid, k.A_m_openid, k.Phone)
+		}
+		isSaveSuccess = true
+	}
+	//九点推送的时候,限制一分钟最大的推送数量
+	if taskType == 4 && (wxPush == 1 || appPush == 1) {
+		hour := time.Now().Hour()
+		fastigiumStart, fastigiumEnd := 0, 0
+		fastigiumTimes := strings.Split(SysConfig.FastigiumTime, "-")
+		if len(fastigiumTimes) == 2 {
+			fastigiumStart = util.IntAll(fastigiumTimes[0])
+			fastigiumEnd = util.IntAll(fastigiumTimes[1])
+		}
+		if hour >= fastigiumStart && hour <= fastigiumEnd {
+			<-p.fastigiumMinutePushPool //高峰期
+		} else {
+			<-p.minutePushPool //正常期
+		}
+	}
+	if wxPush == 1 {
+		isPushOk := true
+		if k.ApplyStatus == 1 {
+			TmpTip := ""
+			minute := time.Now().Unix() - lastInfoDate
+			if minute > -1 && minute < 61 {
+				TmpTip = fmt.Sprintf("%d秒前发布的", minute)
+			} else {
+				minute = minute / 60
+				if minute < 121 {
+					if minute < 1 {
+						minute = 1
+					}
+					TmpTip = fmt.Sprintf("%d分钟前发布的", minute)
+				}
+			}
+			Tip1 := util.If(TmpTip == "", "", TmpTip+":\n").(string)
+			LastTip := ""
+			if i > 1 {
+				LastTip = fmt.Sprintf("...(共%d条)", i)
+			}
+			LastTipLen := len([]rune(LastTip))
+			wxTitleKeys := strings.Join(k.OriginalKeys, ";")
+			if len([]rune(wxTitleKeys)) > 8 {
+				wxTitleKeys = string([]rune(wxTitleKeys)[:8]) + "..."
+			}
+			wxtitle := fmt.Sprintf(SysConfig.WxTitle, wxTitleKeys)
+			TitleLen := len([]rune(wxtitle))
+			GroupLen := len([]rune(k.ModifyDate))
+			reLen := 200 - TitleLen - GroupLen - WxContentLen - len([]rune(Tip1))
+			//if infoType == 2 {
+			//	reLen = reLen - 4
+			//}
+			WXTitle := ""
+			bshow := false
+			for n := 1; n < len(TitleArray)+1; n++ {
+				curTitle := TitleArray[n-1]
+				tmptitle := WXTitle + fmt.Sprintf("%d %s\n", n, curTitle)
+				ch := reLen - len([]rune(tmptitle))
+				if ch < LastTipLen { //加上后大于后辍,则没有完全显示
+					if ch == 0 && n == len(TitleArray) {
+						WXTitle = tmptitle
+						bshow = true
+					} else {
+						ch_1 := reLen - len([]rune(WXTitle)) - LastTipLen
+						if ch_1 > 8 {
+							curLen := len([]rune(curTitle))
+							if ch_1 > curLen {
+								ch_1 = curLen
+							}
+							WXTitle += fmt.Sprintf("%d %s\n", n, string([]rune(curTitle)[:ch_1-3]))
+						}
+					}
+				} else if ch == LastTipLen {
+					WXTitle = tmptitle
+					if n == len(TitleArray) {
+						bshow = true
+					}
+				} else {
+					WXTitle = tmptitle
+					if n == len(TitleArray) {
+						bshow = true
+					}
+				}
+			}
+			if bshow {
+				LastTip = ""
+			}
+			//推送微信
+			isPushOk = putil.SendWeixin(k, Tip1+WXTitle+LastTip, o_pushinfo, matchKey_infoIndex, wxtitle)
+			if isPushOk {
+				wxPushStatus = 1
+			} else {
+				wxPushStatus = -1
+			}
+		}
+		logger.Info("推送任务,微信推送", isPushOk, k.Id, k.S_m_openid, k.RateMode, k.ApplyStatus)
+	}
+	if appPush == 1 {
+		if len([]rune(jpushtitle)) > 80 {
+			jpushtitle = string([]rune(jpushtitle)[:80]) + "..."
+		}
+		if i > 1 {
+			jpushtitle = fmt.Sprintf("1. %s\n...(共%d条)", jpushtitle, i)
+		}
+		go mongodb.Update("user", map[string]interface{}{
+			"_id": bson.ObjectIdHex(k.Id),
+		}, map[string]interface{}{
+			"$inc": map[string]interface{}{
+				"i_apppushunread": 1,
+			},
+		}, false, false)
+		sess_openid := k.A_m_openid
+		if sess_openid == "" {
+			sess_openid = k.Phone
+		}
+		if sess_openid == "" {
+			sess_openid = k.S_m_openid
+		}
+		isPushOk := putil.SendApp(map[string]interface{}{
+			"phoneType": k.AppPhoneType,
+			"descript":  jpushtitle,
+			"type":      "bid",
+			"userId":    k.Id,
+			"openId":    sess_openid,
+			"url":       "/jyapp/free/sess/" + Se.EncodeString(sess_openid+",uid,"+strconv.Itoa(int(time.Now().Unix()))+",historypush"),
+			//"url":         "/jyapp/free/sess/" + push.Se.EncodeString(k.Openid+",uid,"+strconv.Itoa(int(time.Now().Unix()))+",wxpushlist") + "__" + pushid,
+			"otherPushId": k.Opushid,
+			"jgPushId":    k.Jpushid, //极光-推送id
+		})
+		if isPushOk {
+			appPushStatus = 1
+		} else {
+			appPushStatus = -1
+		}
+		logger.Info("推送任务,app推送", isPushOk, k.Id, k.S_m_openid, k.A_m_openid, k.Phone, k.AppPhoneType, k.Jpushid, k.Opushid, k.RateMode)
+	}
+	//发送邮件
+	if mailPush == 1 {
+		html := fmt.Sprintf(SysConfig.Mail_html, strings.Replace(strings.Join(k.OriginalKeys, ";"), "+", " ", -1), mailContent)
+		subject := fmt.Sprintf(SysConfig.Mail_title, "招标")
+		isPushOk := p.SendMail(k.Email, subject, html, fmdatas)
+		if isPushOk {
+			mailPushStatus = 1
+		} else {
+			mailPushStatus = -1
+		}
+		logger.Info("推送任务,发送邮件", isPushOk, k.Id, k.S_m_openid, k.A_m_openid, k.Phone, k.Email, k.DataExport)
+	}
+	if wxPush == 1 || appPush == 1 || (mailPush == 1 && wxPush == 0 && appPush == 0) {
+		//pc端助手推送
+		openid := k.S_m_openid
+		if openid == "" {
+			openid = k.Phone
+		}
+		if openid != "" {
+			putil.SendPcHelper(map[string]interface{}{"clientCode": openid})
+		}
+	}
+	return
+}
+
+//推送邮件(含附件)
+func (p *PushJob) SendMail(email, subject, html string, fmdatas []map[string]interface{}) bool {
+	if !SysConfig.IsPushMail {
+		return true
+	}
+	defer util.Catch()
+	//生成附件
+	var fnamepath, rename string
+	if len(fmdatas) > 0 { //开启导出
+		fnamepath, rename = putil.GetBidInfoXlsx(fmdatas)
+		if messyCodeEmailReg.MatchString(email) {
+			rename = time.Now().Format("2006-01-02") + ".xlsx"
+		}
+	}
+	//gmail := <-Gmails
+	//defer func() {
+	//Gmails <- gmail
+	//}()
+	status := mail.GSendMail("剑鱼招标订阅", email, "", "", subject, html, fnamepath, rename, Gmail)
+	if fnamepath != "" {
+		os.Remove(fnamepath)
+	}
+	if SysConfig.MailSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.MailSleep) * time.Millisecond)
+	}
+	return status
+}
+
+/************************************************/
+func (p *PushJob) OncePushBatch(batch_index, taskType int) int {
+	p.pushDatas = &[]map[string]interface{}{}
+	i := 0
+	sess := mongodb.GetMgoConn()
+	defer mongodb.DestoryMongoConn(sess)
+	var query map[string]interface{}
+	//实时推送,只查找ratemode==1的用户
+	if taskType == 1 || taskType == 2 {
+		query = map[string]interface{}{
+			"ratemode": 1,
+		}
+	} else if taskType == 3 {
+		query = map[string]interface{}{
+			"ratemode":    1,
+			"applystatus": 1,
+		}
+	} else if taskType == 4 {
+		query = map[string]interface{}{
+			"ratemode": 2,
+		}
+	} else {
+		logger.Error("taskType error", taskType)
+		return i
+	}
+	if len(SysConfig.TestIds) > 0 {
+		query["userid"] = map[string]interface{}{
+			"$in": SysConfig.TestIds,
+		}
+	}
+	if p.lastId != "" {
+		query["_id"] = map[string]interface{}{
+			"$gt": bson.ObjectIdHex(p.lastId),
+		}
+	}
+	logger.Info("推送任务,开始加载第", batch_index, "批用户", query)
+	it := sess.DB(DbName).C("pushspace").Find(query).Sort("_id").Iter()
+	for temp := make(map[string]interface{}); it.Next(&temp); {
+		i++
+		p.lastId = util.BsonIdToSId(temp["_id"])
+		*p.pushDatas = append(*p.pushDatas, temp)
+		temp = make(map[string]interface{})
+		if i == SysConfig.PushBatch {
+			break
+		}
+	}
+	logger.Info("推送任务,第", batch_index, "批用户加载结束", p.lastId)
+	return i
+}
+func (p *PushJob) ToSortList(list interface{}, userId string) SortList {
+	if list == nil {
+		return nil
+	}
+	b, err := json.Marshal(list)
+	if err != nil {
+		return nil
+	}
+	sl := make(SortList, 0)
+	err = json.Unmarshal(b, &sl)
+	if err != nil {
+		return nil
+	}
+	sort.Sort(sl)
+	return sl
+}

+ 139 - 0
src/jfw/modules/pushsubscribe/src/push/job/timetask.go

@@ -0,0 +1,139 @@
+package job
+
+import (
+	"log"
+	. "push/config"
+	"qfw/util"
+	"strings"
+	"time"
+)
+
+type timeTask struct {
+	RealPush  *RealPushTimeTask  //实时推送
+	NinePush  *NinePushTimeTask  //九点推送
+	OtherPush *OtherPushTimeTask //一天三次
+}
+
+var Task = &timeTask{
+	RealPush:  &RealPushTimeTask{},  //实时推送
+	NinePush:  &NinePushTimeTask{},  //九点推送
+	OtherPush: &OtherPushTimeTask{}, //一天三次
+}
+
+type RealPushTimeTask struct {
+}
+
+func (r *RealPushTimeTask) Execute() {
+	s_h_m := strings.Split(SysConfig.StartPushTime, ":")
+	if len(s_h_m) != 2 {
+		log.Fatalln("error:startpushtime", SysConfig.StartPushTime)
+	}
+	now := time.Now()
+	start := time.Date(now.Year(), now.Month(), now.Day(), util.IntAll(s_h_m[0]), util.IntAll(s_h_m[1]), 0, 0, time.Local)
+	//程序启动在开始时间之前
+	if now.Before(start) {
+		sub := start.Sub(now)
+		log.Println("start", SysConfig.StartPushTime, "pushjob after", sub)
+		time.AfterFunc(sub, func() {
+			go r.run(2)
+			ticker := time.NewTicker(time.Hour * 24)
+			for {
+				select {
+				case <-ticker.C:
+					go r.run(2)
+				}
+			}
+		})
+	} else {
+		go r.run(1)
+		start = start.AddDate(0, 0, 1)
+		sub := start.Sub(now)
+		log.Println("start", SysConfig.StartPushTime, "pushjob after", sub)
+		timer := time.NewTimer(sub)
+		for {
+			select {
+			case <-timer.C:
+				timer.Reset(time.Hour * 24)
+				go r.run(2)
+			}
+		}
+	}
+}
+func (r *RealPushTimeTask) run(taskType int) {
+	e_h_m := strings.Split(SysConfig.EndPushTime, ":")
+	if len(e_h_m) != 2 {
+		log.Fatalln("endpushtime", SysConfig.EndPushTime)
+	}
+	now := time.Now()
+	end := time.Date(now.Year(), now.Month(), now.Day(), util.IntAll(e_h_m[0]), util.IntAll(e_h_m[1]), 0, 0, time.Local)
+	if now.Before(end) {
+		Jobs.Push.Execute(taskType, true)
+	}
+	//隔天的话,不继续
+	//判断下一轮是否还需要继续
+	if now.Day() != time.Now().Day() || time.Now().After(end) {
+		return
+	}
+	log.Println("start pushjob after", SysConfig.PushDuration, "m")
+	time.AfterFunc(time.Duration(SysConfig.PushDuration)*time.Minute, func() {
+		r.run(1)
+	})
+}
+
+type OtherPushTimeTask struct {
+}
+
+func (o *OtherPushTimeTask) Execute() {
+	for _, otherpushtime := range SysConfig.OtherPushTimes {
+		h_m := strings.Split(otherpushtime, ":")
+		if len(h_m) != 2 {
+			log.Fatalln("error:otherpushtimes", otherpushtime)
+			return
+		}
+		now := time.Now()
+		newDate := time.Date(now.Year(), now.Month(), now.Day(), util.IntAll(h_m[0]), util.IntAll(h_m[1]), 0, 0, time.Local)
+		if newDate.Before(now) {
+			newDate = newDate.AddDate(0, 0, 1)
+		}
+		sub := newDate.Sub(now)
+		log.Println("start", otherpushtime, "pushjob after", sub)
+		time.AfterFunc(sub, func() {
+			go Jobs.Push.Execute(3, true)
+			ticker := time.NewTicker(time.Hour * 24)
+			for {
+				select {
+				case <-ticker.C:
+					go Jobs.Push.Execute(3, true)
+				}
+			}
+		})
+	}
+}
+
+type NinePushTimeTask struct {
+}
+
+func (n *NinePushTimeTask) Execute() {
+	h_m := strings.Split(SysConfig.OncePushTime, ":")
+	if len(h_m) == 2 {
+		now := time.Now()
+		newDate := time.Date(now.Year(), now.Month(), now.Day(), util.IntAll(h_m[0]), util.IntAll(h_m[1]), 0, 0, time.Local)
+		if newDate.Before(now) {
+			newDate = newDate.AddDate(0, 0, 1)
+		}
+		sub := newDate.Sub(now)
+		log.Println("start", SysConfig.OncePushTime, "pushjob after", sub)
+		time.AfterFunc(sub, func() {
+			go Jobs.Push.Execute(4, true)
+			ticker := time.NewTicker(time.Hour * 24)
+			for {
+				select {
+				case <-ticker.C:
+					go Jobs.Push.Execute(4, true)
+				}
+			}
+		})
+	} else {
+		log.Fatalln("error:oncepushtime", SysConfig.OtherPushTimes)
+	}
+}

+ 56 - 0
src/jfw/modules/pushsubscribe/src/push/main.go

@@ -0,0 +1,56 @@
+//订阅推送-推送服务
+package main
+
+import (
+	"flag"
+	"log"
+	. "push/config"
+	"push/job"
+	"qfw/util/mongodb"
+	"qfw/util/redis"
+	"time"
+	ca "ucbsutil/cassandra"
+
+	"github.com/donnie4w/go-logger/logger"
+)
+
+func main() {
+	sleep := flag.Int("s", 0, "程序启动完以后,实时推送休眠s分钟再开始")
+	modle := flag.Int("m", 0, "0 定时任务模式推送;1 非定时任务模式推送;2 定时任务模式推送之前先执行-t的任务")
+	taskType := flag.Int("t", 1, "1 实时推送;2 实时推送+一天三次的八点推送;3 一天三次推送;4 九点推送")
+	moveDatas := flag.String("v", "y", "是否迁移数据")
+	flag.Parse()
+	logger.SetConsole(false)
+	logger.SetRollingDaily("./logs", "push.log")
+	mongodb.InitMongodbPool(SysConfig.MgoSize, SysConfig.MgoAddr, "qfw")
+	redis.InitRedis(SysConfig.RedisServers)
+	//初始化cassandra
+	ca.ViewCacheLen = true
+	ca.InitCassandra("jianyu",
+		SysConfig.Cassandra.Size,
+		SysConfig.Cassandra.Host,
+		map[string]int{
+			"cachesize": SysConfig.Cassandra.Cachesize,
+			"timeout":   SysConfig.Cassandra.Timeout,
+		},
+	)
+	log.Println("订阅推送-推送程序启动。。。")
+	isMoveDatas := *moveDatas == "y"
+	if *modle == 1 {
+		job.Jobs.Push.Execute(*taskType, isMoveDatas)
+	} else {
+		if *modle == 2 {
+			job.Jobs.Push.Execute(*taskType, isMoveDatas)
+		}
+		go job.Task.OtherPush.Execute()
+		go job.Task.NinePush.Execute()
+		if *sleep > 0 {
+			log.Println("实时推送先休眠", *sleep, "m")
+			time.AfterFunc(time.Duration(*sleep)*time.Minute, job.Task.RealPush.Execute)
+		} else {
+			go job.Task.RealPush.Execute()
+		}
+		flag := make(chan bool)
+		<-flag
+	}
+}

二进制
src/jfw/modules/pushsubscribe/src/push/src


+ 93 - 0
src/jfw/modules/pushsubscribe/src/push/util/excel.go

@@ -0,0 +1,93 @@
+package util
+
+import (
+	"fmt"
+	"log"
+	"math/rand"
+	"os"
+	qu "qfw/util"
+	"time"
+
+	"github.com/tealeg/xlsx"
+)
+
+var Sheets = map[int]xlsx.Sheet{}
+
+func init() {
+	fx, err := xlsx.OpenFile("./xlsx/temp.xlsx")
+	if err != nil {
+		log.Println(err.Error())
+		os.Exit(0)
+	}
+	for k, st := range fx.Sheets {
+		Sheets[k] = *st
+	}
+}
+func GetBidInfoXlsx(data []map[string]interface{}) (fnamepath, rename string) {
+	fx := xlsx.NewFile()
+	sheet := Sheets[0]
+	style := xlsx.NewStyle()
+	style.Alignment.WrapText = true
+	//信息
+	for _, v := range data {
+		row := sheet.AddRow()
+
+		projectname := qu.ObjToString(v["projectname"])
+		if projectname == "" {
+			projectname = qu.ObjToString(v["title"])
+		}
+		cell := row.AddCell()
+		cell.SetValue(projectname)
+		cell.SetStyle(style)
+
+		cell = row.AddCell()
+		cell.SetValue(v["subtype"])
+
+		budget := qu.Float64All(v["budget"]) / float64(10000)
+		cell = row.AddCell()
+		if budget != 0 {
+			cell.SetValue(budget)
+		} else {
+			cell.SetValue("")
+		}
+
+		cell = row.AddCell()
+		cell.SetValue(v["buyer"])
+		cell.SetStyle(style)
+
+		bpt := v["bidopentime"]
+		bidopentime := qu.FormatDateWithObj(&bpt, "2006-01-02 15:04")
+		cell = row.AddCell()
+		cell.SetValue(bidopentime)
+
+		cell = row.AddCell()
+		cell.SetValue(v["winner"])
+		cell.SetStyle(style)
+
+		bidamount := qu.Float64All(v["bidamount"]) / float64(10000)
+		cell = row.AddCell()
+		if bidamount != 0 {
+			cell.SetValue(bidamount)
+		} else {
+			cell.SetValue("")
+		}
+
+		pt := v["publishtime"]
+		publishtime := qu.FormatDateWithObj(&pt, qu.Date_Short_Layout)
+		cell = row.AddCell()
+		cell.SetValue(publishtime)
+	}
+	fx.AppendSheet(sheet, "剑鱼")
+
+	t := time.Now()
+	rename = "剑鱼招标订阅_" + t.Format("2006-01-02") + "_推送信息表.xlsx"
+	fnamepath = "./xlsx/" + t.Format("20060102150405") + fmt.Sprint(rand.Intn(10000)) + ".xlsx"
+
+	err := fx.Save(fnamepath)
+	//log.Println("err", err)
+	if err != nil {
+		return "", ""
+	} else {
+		return fnamepath, rename
+	}
+}

+ 116 - 0
src/jfw/modules/pushsubscribe/src/push/util/rpccall.go

@@ -0,0 +1,116 @@
+package util
+
+import (
+	"encoding/json"
+	"net/rpc"
+	. "public"
+	. "push/config"
+	"qfw/util"
+	"qfw/util/mongodb"
+	qrpc "qfw/util/rpc"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/donnie4w/go-logger/logger"
+	"gopkg.in/mgo.v2/bson"
+)
+
+var (
+	wxPushPool  = make(chan bool, SysConfig.WxPollSize)
+	appPushPool = make(chan bool, SysConfig.AppPollSize)
+)
+
+//微信远程调用,实现模板发送消息
+func SendWeixin(k *UserInfo, Remarks string, o_pushinfo map[string]map[string]interface{}, matchKey_infoIndex map[string]string, wxtitle string) bool {
+	wxPushPool <- true
+	defer func() {
+		util.Catch()
+		<-wxPushPool
+	}()
+	if SysConfig.WxSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.WxSleep) * time.Millisecond)
+	}
+	var repl qrpc.RpcResult
+	now := time.Now()
+	p := &qrpc.NotifyMsg{
+		Openid:      k.S_m_openid,
+		Title:       wxtitle,
+		Remark:      Remarks,
+		Detail:      SysConfig.WxGroup,
+		Date:        "",
+		Service:     util.FormatDate(&now, util.Date_Short_Layout),
+		Color:       SysConfig.WxColor,
+		DetailColor: SysConfig.WxDetailColor,
+		Url:         SysConfig.JianyuDomain + "/front/sess/" + Se.EncodeString(k.S_m_openid+",uid,"+strconv.Itoa(int(time.Now().Unix()))+",rssset"),
+	}
+	client, err := rpc.DialHTTP("tcp", SysConfig.WeixinRpcServer)
+	if err != nil {
+		logger.Error(err.Error())
+		return false
+	}
+	defer client.Close()
+	err = client.Call("WeiXinRpc.SubscribePush", p, &repl)
+	if err != nil {
+		logger.Error(err.Error())
+	}
+	res := string(repl)
+	if strings.Contains(res, "[46004]") || strings.Contains(res, "[65302]") || strings.Contains(res, "[43004]") || strings.Contains(res, "[40003]") {
+		mongodb.Update("user", map[string]interface{}{"_id": bson.ObjectIdHex(k.Id)}, map[string]interface{}{
+			"$set": map[string]interface{}{
+				"i_ispush": 0,
+			},
+		}, false, false)
+		return true
+	}
+	if repl == "Y" {
+		return true
+	}
+	return false
+}
+func SendApp(m map[string]interface{}) bool {
+	appPushPool <- true
+	defer func() {
+		util.Catch()
+		<-appPushPool
+	}()
+	if SysConfig.AppSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.AppSleep) * time.Millisecond)
+	}
+	var repl string
+	client, err := rpc.DialHTTP("tcp", SysConfig.AppPushServiceRpc)
+	if err != nil {
+		logger.Error(err.Error())
+		return false
+	}
+	defer client.Close()
+	b, _ := json.Marshal(m)
+	err = client.Call("Rpc.Push", b, &repl)
+	if err != nil {
+		logger.Error(err.Error())
+		return false
+	}
+	return repl == "y"
+}
+
+//
+func SendPcHelper(m map[string]interface{}) bool {
+	defer util.Catch()
+	if SysConfig.PcHelperSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.PcHelperSleep) * time.Millisecond)
+	}
+	var repl string
+	client, err := rpc.DialHTTP("tcp", SysConfig.PcHelper)
+	if err != nil {
+		logger.Error(err.Error())
+		return false
+	}
+	defer client.Close()
+	b, _ := json.Marshal(m)
+	err = client.Call("Service.PushMsg", b, &repl)
+	if err != nil {
+		logger.Error(err.Error())
+		return false
+	}
+	return repl == "y"
+}

+ 160 - 0
src/jfw/modules/pushsubscribe/src/push/util/util.go

@@ -0,0 +1,160 @@
+package util
+
+import (
+	"encoding/json"
+	"fmt"
+	. "public"
+	. "push/config"
+	"qfw/util"
+	"qfw/util/redis"
+	"time"
+	ca "ucbsutil/cassandra"
+
+	"gopkg.in/mgo.v2/bson"
+)
+
+var cassandraPoll = make(chan bool, SysConfig.CassandraPollSize)
+
+//重新设置用户类型
+func GetUserType(s_m_openid, a_m_openid, phone string, userType int) int {
+	if userType == 0 {
+		if s_m_openid != "" && a_m_openid == "" && phone == "" {
+			userType = 0 //公众号
+		} else if s_m_openid == "" && phone != "" {
+			userType = 1 //app手机号
+		} else if s_m_openid == "" && a_m_openid != "" {
+			userType = 2 //app微信
+		} else if s_m_openid != "" && a_m_openid == "" && phone == "" {
+			userType = 3 //用户合并以后只有微信用户
+		} else if s_m_openid == "" && (a_m_openid != "" || phone != "") {
+			userType = 4 //用户合并以后只有app用户
+		} else if s_m_openid != "" && (a_m_openid != "" || phone != "") {
+			userType = 5 //用户合并以后公众号和app用户都有
+		} else {
+			userType = -1
+		}
+	}
+	return userType
+}
+
+//推送方式转换
+func ModeTransform(userType int, o_msgset map[string]interface{}) (int, int, int) {
+	mode := util.IntAll(o_msgset["i_mode"])
+	wxpush := util.IntAll(o_msgset["i_wxpush"])
+	apppush := util.IntAll(o_msgset["i_apppush"])
+	mailpush := util.IntAll(o_msgset["i_mailpush"])
+	if wxpush == 1 || apppush == 1 || mailpush == 1 {
+		return wxpush, apppush, mailpush
+	}
+	//老的app用户
+	if userType == 1 || userType == 2 {
+		switch mode {
+		case 0, 1:
+			apppush = 1
+			break
+		case 2:
+			mailpush = 1
+			break
+		case 3:
+			apppush = 1
+			mailpush = 1
+			break
+		}
+		if apppush == 0 && mailpush == 0 {
+			apppush = 1
+		}
+	} else if userType == 0 {
+		switch mode {
+		case 0, 1:
+			wxpush = 1
+			break
+		case 2:
+			mailpush = 1
+			break
+		case 3:
+			wxpush = 1
+			mailpush = 1
+			break
+		}
+		if wxpush == 0 && mailpush == 0 {
+			wxpush = 1
+		}
+	} else {
+		switch mode {
+		case 0, 1, 3:
+			if userType == 3 {
+				wxpush = 1
+			} else if userType == 4 {
+				apppush = 1
+			} else if userType == 5 {
+				wxpush = 1
+				apppush = 1
+			}
+			if mode == 3 {
+				mailpush = 1
+			}
+			break
+		case 2:
+			mailpush = 1
+			break
+		}
+	}
+	return wxpush, apppush, mailpush
+}
+
+//保存发送信息
+func SaveSendInfo(taskType int, k *UserInfo, str string, o_pushinfo map[string]map[string]interface{}, matchKey_infoIndex map[string]string) string {
+	cassandraPoll <- true
+	defer func() {
+		<-cassandraPoll
+	}()
+	if SysConfig.CassandraSleep > 0 {
+		time.Sleep(time.Duration(SysConfig.CassandraSleep) * time.Millisecond)
+	}
+	pushid := time.Now().Unix()
+	md, _ := json.Marshal(o_pushinfo)
+	openid := util.GetOldOpenid(k.S_m_openid, k.A_m_openid, k.Phone, k.MergeOrder)
+	wxpush := map[string]interface{}{
+		"id":       time.Now().Format(util.Date_Short_Layout),
+		"openid":   openid,
+		"date":     pushid,
+		"words":    k.OriginalKeys,
+		"uid":      k.Id,
+		"province": k.Province,
+		"interest": k.OriginalKeys,
+		"content":  str,
+		"pushinfo": string(md),
+		"size":     len(o_pushinfo),
+		"appid":    2,
+		"ratemode": k.RateMode,
+		"sendmode": 9000 + k.WxPush*100 + k.AppPush*10 + k.MailPush,
+		"smartset": k.SmartSet,
+		"matchki":  matchKey_infoIndex,
+	}
+	if ca.SaveCacheByTimeOut("jy_push", wxpush, 10) {
+		redisKey := "pushsubscribe_" + k.Id
+		if taskType == 4 {
+			redisDatas := redis.Get("pushcache", redisKey)
+			if redisDatas != nil {
+				list := []interface{}{wxpush}
+				redisList, _ := redisDatas.([]interface{})
+				list = append(list, redisList...)
+				if len(list) > 500 {
+					list = list[:500]
+				}
+				redis.Put("pushcache", redisKey, list, SysConfig.NinePushRedisTimeout)
+			}
+		} else {
+			redis.Del("pushcache", redisKey)
+		}
+		return fmt.Sprint(pushid)
+	}
+	return ""
+}
+func ToObjectIds(ids []string) []bson.ObjectId {
+	_ids := []bson.ObjectId{}
+	for _, v := range ids {
+		_ids = append(_ids, bson.ObjectIdHex(v))
+	}
+	return _ids
+}

+ 1 - 0
src/jfw/modules/pushsubscribe/src/push/xlsx/readme.txt

@@ -0,0 +1 @@
+excel数据导出临时目录

二进制
src/jfw/modules/pushsubscribe/src/push/xlsx/temp.xlsx


+ 12 - 12
src/jfw/modules/weixin/src/config.json

@@ -1,27 +1,27 @@
 {
-    "mongodbServers": "192.168.3.18:27080",
+    "mongodbServers": "127.0.0.1:27080",
     "mongodbPoolSize": "5",
     "mongodbName": "qfw",
     "cassandra": [
         "192.168.3.207"
     ],
     "cassandrasize": 5,
-    "redisServers": "sso=192.168.3.18:3379,other=192.168.3.18:3379,jyop_other=192.168.3.18:3379",
-    "weixinport": "8080",
+    "redisServers": "sso=47.106.230.136:6379,other=47.106.230.136:6379,jyop_other=47.106.230.136:6379,push=47.106.230.136:6379",
+    "weixinport": "82",
     "weixinrpcport": "8083",
     "webrpcport": "127.0.0.1:8084",
     "jyop_webrpcport": "127.0.0.1:8012",
-    "webdomain": "http://webwcj.qmx.top",
-    "appid": "wx76e1309b01a7b17e",
+    "webdomain": "http://web-jydev-wky.jianyu360.cn/",
+    "appid": "wx79f2cc873dbea989",
     "apptoken": "top2016top2016",
-    "appsecret": "dd00e71cb2370432d9de848b674eb8e7",
-    "proxysess": "http://webwcj.qmx.top/front/sess/%s",
+    "appsecret": "fb3f77e9c6725811b5c39f6d58bd58a3",
+    "proxysess": "http://web-jydev-wky.jianyu360.cn/front/sess/%s",
     "followCompany": 10,
     "welcomemsg": "用剑鱼,所有功能完全免费,\n和传统的会员制说再见!\n\n<a href='%s'>点击这里</a>设置关键词,或直接回复“订阅 关键词”,如“订阅 教学设备”,您将随时随地接收招标信息!\n\n剑鱼,让投标无限可能!",
-    "tpl_subscribe_push_id": "NTYrV-yHFjHzlmH_gM3rDTAYl3DPJO-EXZY1C3Ntr94",
-    "tpl_push_id": "_CeF7oeZ0pr1Qp278LAE0uF2fS7_19rWjDbzmDa3sLU",
-    "tpl_bidopen_id": "3URB-9evmkJ9wntI8iGMEl8_elSAC3wL3ZrQQX-q0cg",
-    "tpl_managernotify_id": "LB0XHcbNQKcbgUdDfDncuJW10jay4EfoJ9j-UVh2j5A",
+    "tpl_subscribe_push_id": "9TRkUQDkRQmvovccZ2j8EwSb1GN62_GNXkfUfslzojY",
+    "tpl_push_id": "9TRkUQDkRQmvovccZ2j8EwSb1GN62_GNXkfUfslzojY",
+    "tpl_bidopen_id": "un9TXq11fjCP_YicyVqNbpok6c2tSJwz6RIse8XJ7QE",
+    "tpl_managernotify_id": "_PXghFaMrIh6zH-7ioqpliHhqJGhZvsF_ebbmq9LMF8",
     "tpl_logapply_id": "nWhZ25_llXTKg-3ulTpwAVIdDzRxnxyQ36MdHAiANOk",
     "tpl_activeapply_id": "zZ5ELQuVLfWH3a9cNAf2OBAnn30iY-ywI1rqByJ6KTI",
     "weixinAutoRpl": "感谢使用剑鱼!\n1.<a href='%s'>【订阅招标信息】</a>\n2.<a href='%s'>【搜索招标信息】</a>\n3.<a href='http://mp.weixin.qq.com/mp/homepage?__biz=MzIyNTM1NDUyNw==&hid=1&sn=f9e98da1975f85011ee138a4ee5cfbe8#wechat_redirect'>【产品帮助】</a>\n4.<a href='http://www.myfans.cc/30a78e9b78'>【招标社区】</a> \n5.<a href='%s'>点击这里,查看“%s”的相关招标信息</a>\n6.快速订阅:回复“订阅”加上你的关键词,比如“订阅 教学设备”\n7.如需人工协助,请回复“客服”\n8.<a href='http://webwh.qmx.top/jyapp/free/download/normal?source=weixinAutoRpl'>下载剑鱼APP,更加方便快捷</a>",
@@ -89,4 +89,4 @@
         }
 		
     ]
-}
+}

+ 6 - 0
src/jfw/modules/weixin/src/endless/doc.go

@@ -0,0 +1,6 @@
+/*
+endless provides a drop in  replacement for the `net/http` stl functions `ListenAndServe` and `ListenAndServeTLS` to achieve zero downtime restarts and code upgrades.
+
+The code is based on the excellent post [Graceful Restart in Golang](http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/) by [Grisha Trubetskoy](https://github.com/grisha). I took his code as a start. So a lot of credit to Grisha!
+*/
+package endless

+ 564 - 0
src/jfw/modules/weixin/src/endless/endless.go

@@ -0,0 +1,564 @@
+// +build linux
+
+package endless
+
+import (
+	"crypto/tls"
+	"errors"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"os/exec"
+	"os/signal"
+	"runtime"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	// "github.com/fvbock/uds-go/introspect"
+)
+
+const (
+	PRE_SIGNAL = iota
+	POST_SIGNAL
+
+	STATE_INIT
+	STATE_RUNNING
+	STATE_SHUTTING_DOWN
+	STATE_TERMINATE
+)
+
+var (
+	runningServerReg     sync.RWMutex
+	runningServers       map[string]*endlessServer
+	runningServersOrder  []string
+	socketPtrOffsetMap   map[string]uint
+	runningServersForked bool
+
+	DefaultReadTimeOut    time.Duration
+	DefaultWriteTimeOut   time.Duration
+	DefaultMaxHeaderBytes int
+	DefaultHammerTime     time.Duration
+
+	isChild     bool
+	socketOrder string
+
+	hookableSignals []os.Signal
+)
+
+func init() {
+	flag.BoolVar(&isChild, "continue", false, "listen on open fd (after forking)")
+	flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
+
+	runningServerReg = sync.RWMutex{}
+	runningServers = make(map[string]*endlessServer)
+	runningServersOrder = []string{}
+	socketPtrOffsetMap = make(map[string]uint)
+
+	DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB)
+
+	// after a restart the parent will finish ongoing requests before
+	// shutting down. set to a negative value to disable
+	DefaultHammerTime = 60 * time.Second
+
+	hookableSignals = []os.Signal{
+		syscall.SIGHUP,
+		syscall.SIGUSR1,
+		syscall.SIGUSR2,
+		syscall.SIGINT,
+		syscall.SIGTERM,
+		syscall.SIGTSTP,
+	}
+}
+
+type endlessServer struct {
+	http.Server
+	EndlessListener  net.Listener
+	SignalHooks      map[int]map[os.Signal][]func()
+	tlsInnerListener *endlessListener
+	wg               sync.WaitGroup
+	sigChan          chan os.Signal
+	isChild          bool
+	state            uint8
+	lock             *sync.RWMutex
+}
+
+/*
+NewServer returns an intialized endlessServer Object. Calling Serve on it will
+actually "start" the server.
+*/
+func NewServer(addr string, handler http.Handler, fn func()) (srv *endlessServer) {
+	runningServerReg.Lock()
+	defer runningServerReg.Unlock()
+	if !flag.Parsed() {
+		flag.Parse()
+	}
+	if len(socketOrder) > 0 {
+		for i, addr := range strings.Split(socketOrder, ",") {
+			socketPtrOffsetMap[addr] = uint(i)
+		}
+	} else {
+		socketPtrOffsetMap[addr] = uint(len(runningServersOrder))
+	}
+
+	srv = &endlessServer{
+		wg:      sync.WaitGroup{},
+		sigChan: make(chan os.Signal),
+		isChild: isChild,
+		SignalHooks: map[int]map[os.Signal][]func(){
+			PRE_SIGNAL: map[os.Signal][]func(){
+				syscall.SIGHUP: []func(){
+					fn,
+				},
+				syscall.SIGUSR1: []func(){},
+				syscall.SIGUSR2: []func(){},
+				syscall.SIGINT:  []func(){},
+				syscall.SIGTERM: []func(){},
+				syscall.SIGTSTP: []func(){},
+			},
+			POST_SIGNAL: map[os.Signal][]func(){
+				syscall.SIGHUP:  []func(){},
+				syscall.SIGUSR1: []func(){},
+				syscall.SIGUSR2: []func(){},
+				syscall.SIGINT:  []func(){},
+				syscall.SIGTERM: []func(){},
+				syscall.SIGTSTP: []func(){},
+			},
+		},
+		state: STATE_INIT,
+		lock:  &sync.RWMutex{},
+	}
+
+	srv.Server.Addr = addr
+	srv.Server.ReadTimeout = DefaultReadTimeOut
+	srv.Server.WriteTimeout = DefaultWriteTimeOut
+	srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
+	srv.Server.Handler = handler
+
+	runningServersOrder = append(runningServersOrder, addr)
+	runningServers[addr] = srv
+
+	return
+}
+
+/*
+ListenAndServe listens on the TCP network address addr and then calls Serve
+with handler to handle requests on incoming connections. Handler is typically
+nil, in which case the DefaultServeMux is used.
+*/
+func ListenAndServe(addr string, handler http.Handler, fn func()) error {
+	server := NewServer(addr, handler, fn)
+	return server.ListenAndServe()
+}
+
+/*
+ListenAndServeTLS acts identically to ListenAndServe, except that it expects
+HTTPS connections. Additionally, files containing a certificate and matching
+private key for the server must be provided. If the certificate is signed by a
+certificate authority, the certFile should be the concatenation of the server's
+certificate followed by the CA's certificate.
+*/
+func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler, destoryfn func()) error {
+	server := NewServer(addr, handler, destoryfn)
+	return server.ListenAndServeTLS(certFile, keyFile)
+}
+
+func (srv *endlessServer) getState() uint8 {
+	srv.lock.RLock()
+	defer srv.lock.RUnlock()
+
+	return srv.state
+}
+
+func (srv *endlessServer) setState(st uint8) {
+	srv.lock.Lock()
+	defer srv.lock.Unlock()
+
+	srv.state = st
+}
+
+/*
+Serve accepts incoming HTTP connections on the listener l, creating a new
+service goroutine for each. The service goroutines read requests and then call
+handler to reply to them. Handler is typically nil, in which case the
+DefaultServeMux is used.
+
+In addition to the stl Serve behaviour each connection is added to a
+sync.Waitgroup so that all outstanding connections can be served before shutting
+down the server.
+*/
+func (srv *endlessServer) Serve() (err error) {
+	defer log.Println(syscall.Getpid(), "Serve() returning...")
+	srv.setState(STATE_RUNNING)
+	err = srv.Server.Serve(srv.EndlessListener)
+	log.Println(syscall.Getpid(), "Waiting for connections to finish...")
+	srv.wg.Wait()
+	srv.setState(STATE_TERMINATE)
+	return
+}
+
+/*
+ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
+to handle requests on incoming connections. If srv.Addr is blank, ":http" is
+used.
+*/
+func (srv *endlessServer) ListenAndServe() (err error) {
+	addr := srv.Addr
+	if addr == "" {
+		addr = ":http"
+	}
+
+	go srv.handleSignals()
+
+	l, err := srv.getListener(addr)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	srv.EndlessListener = newEndlessListener(l, srv)
+
+	if srv.isChild {
+		syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
+	}
+
+	log.Println(syscall.Getpid(), srv.Addr)
+	return srv.Serve()
+}
+
+/*
+ListenAndServeTLS listens on the TCP network address srv.Addr and then calls
+Serve to handle requests on incoming TLS connections.
+
+Filenames containing a certificate and matching private key for the server must
+be provided. If the certificate is signed by a certificate authority, the
+certFile should be the concatenation of the server's certificate followed by the
+CA's certificate.
+
+If srv.Addr is blank, ":https" is used.
+*/
+func (srv *endlessServer) ListenAndServeTLS(certFile, keyFile string) (err error) {
+	addr := srv.Addr
+	if addr == "" {
+		addr = ":https"
+	}
+
+	config := &tls.Config{}
+	if srv.TLSConfig != nil {
+		*config = *srv.TLSConfig
+	}
+	if config.NextProtos == nil {
+		config.NextProtos = []string{"http/1.1"}
+	}
+
+	config.Certificates = make([]tls.Certificate, 1)
+	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return
+	}
+
+	go srv.handleSignals()
+
+	l, err := srv.getListener(addr)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	srv.tlsInnerListener = newEndlessListener(l, srv)
+	srv.EndlessListener = tls.NewListener(srv.tlsInnerListener, config)
+
+	if srv.isChild {
+		syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
+	}
+
+	log.Println(syscall.Getpid(), srv.Addr)
+	return srv.Serve()
+}
+
+/*
+getListener either opens a new socket to listen on, or takes the acceptor socket
+it got passed when restarted.
+*/
+func (srv *endlessServer) getListener(laddr string) (l net.Listener, err error) {
+	if srv.isChild {
+		var ptrOffset uint = 0
+		runningServerReg.RLock()
+		defer runningServerReg.RUnlock()
+		if len(socketPtrOffsetMap) > 0 {
+			ptrOffset = socketPtrOffsetMap[laddr]
+			// log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr])
+		}
+
+		f := os.NewFile(uintptr(3+ptrOffset), "")
+		l, err = net.FileListener(f)
+		if err != nil {
+			err = fmt.Errorf("net.FileListener error: %v", err)
+			return
+		}
+	} else {
+		l, err = net.Listen("tcp", laddr)
+		if err != nil {
+			err = fmt.Errorf("net.Listen error: %v", err)
+			return
+		}
+	}
+	return
+}
+
+/*
+handleSignals listens for os Signals and calls any hooked in function that the
+user had registered with the signal.
+*/
+func (srv *endlessServer) handleSignals() {
+	var sig os.Signal
+
+	signal.Notify(
+		srv.sigChan,
+		hookableSignals...,
+	)
+
+	pid := syscall.Getpid()
+	for {
+		sig = <-srv.sigChan
+		srv.signalHooks(PRE_SIGNAL, sig)
+		switch sig {
+		case syscall.SIGHUP:
+			log.Println(pid, "Received SIGHUP. forking.")
+			err := srv.fork()
+			if err != nil {
+				log.Println("Fork err:", err)
+			}
+		case syscall.SIGUSR1:
+			log.Println(pid, "Received SIGUSR1.")
+		case syscall.SIGUSR2:
+			log.Println(pid, "Received SIGUSR2.")
+			srv.hammerTime(0 * time.Second)
+		case syscall.SIGINT:
+			log.Println(pid, "Received SIGINT.")
+			srv.shutdown()
+		case syscall.SIGTERM:
+			log.Println(pid, "Received SIGTERM.")
+			srv.shutdown()
+		case syscall.SIGTSTP:
+			log.Println(pid, "Received SIGTSTP.")
+		default:
+			log.Printf("Received %v: nothing i care about...\n", sig)
+		}
+		srv.signalHooks(POST_SIGNAL, sig)
+	}
+}
+
+func (srv *endlessServer) signalHooks(ppFlag int, sig os.Signal) {
+	if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
+		return
+	}
+	for _, f := range srv.SignalHooks[ppFlag][sig] {
+		f()
+	}
+	return
+}
+
+/*
+shutdown closes the listener so that no new connections are accepted. it also
+starts a goroutine that will hammer (stop all running requests) the server
+after DefaultHammerTime.
+*/
+func (srv *endlessServer) shutdown() {
+	if srv.getState() != STATE_RUNNING {
+		return
+	}
+
+	srv.setState(STATE_SHUTTING_DOWN)
+	if DefaultHammerTime >= 0 {
+		go srv.hammerTime(DefaultHammerTime)
+	}
+	// disable keep-alives on existing connections
+	srv.SetKeepAlivesEnabled(false)
+	err := srv.EndlessListener.Close()
+	if err != nil {
+		log.Println(syscall.Getpid(), "Listener.Close() error:", err)
+	} else {
+		log.Println(syscall.Getpid(), srv.EndlessListener.Addr(), "Listener closed.")
+	}
+}
+
+/*
+hammerTime forces the server to shutdown in a given timeout - whether it
+finished outstanding requests or not. if Read/WriteTimeout are not set or the
+max header size is very big a connection could hang...
+
+srv.Serve() will not return until all connections are served. this will
+unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe(TLS) to
+return.
+*/
+func (srv *endlessServer) hammerTime(d time.Duration) {
+	defer func() {
+		// we are calling srv.wg.Done() until it panics which means we called
+		// Done() when the counter was already at 0 and we're done.
+		// (and thus Serve() will return and the parent will exit)
+		if r := recover(); r != nil {
+			log.Println("WaitGroup at 0", r)
+		}
+	}()
+	if srv.getState() != STATE_SHUTTING_DOWN {
+		return
+	}
+	time.Sleep(d)
+	log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
+	for {
+		if srv.getState() == STATE_TERMINATE {
+			break
+		}
+		srv.wg.Done()
+		runtime.Gosched()
+	}
+}
+
+func (srv *endlessServer) fork() (err error) {
+	runningServerReg.Lock()
+	defer runningServerReg.Unlock()
+
+	// only one server isntance should fork!
+	if runningServersForked {
+		return errors.New("Another process already forked. Ignoring this one.")
+	}
+
+	runningServersForked = true
+
+	var files = make([]*os.File, len(runningServers))
+	var orderArgs = make([]string, len(runningServers))
+	// get the accessor socket fds for _all_ server instances
+	for _, srvPtr := range runningServers {
+		// introspect.PrintTypeDump(srvPtr.EndlessListener)
+		switch srvPtr.EndlessListener.(type) {
+		case *endlessListener:
+			// normal listener
+			files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.EndlessListener.(*endlessListener).File()
+		default:
+			// tls listener
+			files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
+		}
+		orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
+	}
+
+	// log.Println(files)
+	path := os.Args[0]
+	var args []string
+	if len(os.Args) > 1 {
+		for _, arg := range os.Args[1:] {
+			if arg == "-continue" {
+				break
+			}
+			args = append(args, arg)
+		}
+	}
+	args = append(args, "-continue")
+	if len(runningServers) > 1 {
+		args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
+		// log.Println(args)
+	}
+
+	cmd := exec.Command(path, args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.ExtraFiles = files
+	// cmd.SysProcAttr = &syscall.SysProcAttr{
+	// 	Setsid:  true,
+	// 	Setctty: true,
+	// 	Ctty:    ,
+	// }
+
+	err = cmd.Start()
+	if err != nil {
+		log.Fatalf("Restart: Failed to launch, error: %v", err)
+	}
+
+	return
+}
+
+type endlessListener struct {
+	net.Listener
+	stopped bool
+	server  *endlessServer
+}
+
+func (el *endlessListener) Accept() (c net.Conn, err error) {
+	tc, err := el.Listener.(*net.TCPListener).AcceptTCP()
+	if err != nil {
+		return
+	}
+
+	tc.SetKeepAlive(true)                  // see http.tcpKeepAliveListener
+	tc.SetKeepAlivePeriod(3 * time.Minute) // see http.tcpKeepAliveListener
+
+	c = endlessConn{
+		Conn:   tc,
+		server: el.server,
+	}
+
+	el.server.wg.Add(1)
+	return
+}
+
+func newEndlessListener(l net.Listener, srv *endlessServer) (el *endlessListener) {
+	el = &endlessListener{
+		Listener: l,
+		server:   srv,
+	}
+
+	return
+}
+
+func (el *endlessListener) Close() error {
+	if el.stopped {
+		return syscall.EINVAL
+	}
+
+	el.stopped = true
+	return el.Listener.Close()
+}
+
+func (el *endlessListener) File() *os.File {
+	// returns a dup(2) - FD_CLOEXEC flag *not* set
+	tl := el.Listener.(*net.TCPListener)
+	fl, _ := tl.File()
+	return fl
+}
+
+type endlessConn struct {
+	net.Conn
+	server *endlessServer
+}
+
+func (w endlessConn) Close() error {
+	err := w.Conn.Close()
+	if err == nil {
+		w.server.wg.Done()
+	}
+	return err
+}
+
+/*
+RegisterSignalHook registers a function to be run PRE_SIGNAL or POST_SIGNAL for
+a given signal. PRE or POST in this case means before or after the signal
+related code endless itself runs
+*/
+func (srv *endlessServer) RegisterSignalHook(prePost int, sig os.Signal, f func()) (err error) {
+	if prePost != PRE_SIGNAL && prePost != POST_SIGNAL {
+		err = fmt.Errorf("Cannot use %v for prePost arg. Must be endless.PRE_SIGNAL or endless.POST_SIGNAL.")
+		return
+	}
+	for _, s := range hookableSignals {
+		if s == sig {
+			srv.SignalHooks[prePost][sig] = append(srv.SignalHooks[prePost][sig], f)
+			return
+		}
+	}
+	err = fmt.Errorf("Signal %v is not supported.")
+	return
+}

+ 11 - 0
src/jfw/modules/weixin/src/endless/serve.go

@@ -0,0 +1,11 @@
+// +build !linux
+
+package endless
+
+import (
+	"net/http"
+)
+
+func ListenAndServe(addr string, handler http.Handler, fn func()) error {
+	return http.ListenAndServe(addr, handler)
+}

+ 4 - 4
src/jfw/modules/weixin/src/jrpc/jrpc.go

@@ -382,21 +382,21 @@ func (w *WeiXinRpc) SendCustomMsg(p *[]byte, ret *string) error {
 	err := json.Unmarshal(*p, &d)
 	url, _ := d["url"].(string)
 	if url == "" || d["data"] == nil {
-		err = errors.New("SendCustomTplMsg 参数错误!")
+		err = errors.New("SendCustomMsg 参数错误!")
 		log.Println(err, d)
 		return err
 	}
 	if err != nil {
-		log.Println("SendCustomTplMsg error", err)
+		log.Println("SendCustomMsg error", err, d["data"])
 		return err
 	}
 	_, err = w.Wwx.PostCustomMsg(url, d["data"])
 	if err != nil {
 		*ret = err.Error()
-		log.Println("SendCustomTplMsg error", err)
+		log.Println("SendCustomMsg error", err, d["data"])
 	} else {
 		*ret = "Y"
-		log.Println("SendCustomTplMsg success!")
+		log.Println("SendCustomMsg success!")
 	}
 	return nil
 }

+ 4 - 1
src/jfw/modules/weixin/src/main.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	. "config"
+	"endless"
 	"jrpc"
 	"log"
 	"net"
@@ -13,6 +14,8 @@ import (
 	"wx"
 )
 
+var count = 0
+
 func main() {
 	//开启oauth和微信
 	go func() {
@@ -30,7 +33,7 @@ func main() {
 		http.HandleFunc("/wx/lsm/", wx.AdvHandle)
 		http.HandleFunc("/wx/token", wx.GetToken)
 		// 设置监听的端口
-		err := http.ListenAndServe(":"+Sysconfig["weixinport"].(string), nil)
+		err := endless.ListenAndServe(":"+Sysconfig["weixinport"].(string), nil, func() {})
 		if err != nil {
 			log.Println("ListenAndServe: ", err)
 		}

+ 20 - 1
src/jfw/modules/weixin/src/wx/wx.go

@@ -92,8 +92,12 @@ func init() {
 func MsgTxtHandler(w ResponseWriter, r *Request) {
 	log.Println("所有指令", r.FromUserName, r.Content) //测试信息
 	now := time.Now()
-	//openid := r.FromUserName
+	openid := r.FromUserName
 	//user, err := Mux.GetUserInfo(openid)
+	//剑鱼订阅提醒  (1:关注事件,2:扫码事件,3:发送客服消息处理,4:支付操作)
+	if openid != "" {
+		redis.Put("push", "remind&&"+openid, "3", 172800)
+	}
 	autoReply_reg, _ := regexp.Compile("剑鱼|招标|中标|订阅|公告|项目|设置|推送")
 	shalong_text, shalong_img := "", ""
 	if strings.Replace(r.Content, " ", "", -1) == "沙龙" {
@@ -588,6 +592,11 @@ func Subscribe(w ResponseWriter, r *Request) {
 	if isRepeatCall(openid, r.CreateTime) {
 		return
 	}
+	//剑鱼订阅提醒  (1:关注事件,2:扫码事件,3:发送客服消息处理,4:支付操作)
+	if openid != "" {
+		redis.Put("push", "remind&&"+openid, "1", 172800)
+	}
+
 	user, err := Mux.GetUserInfo(openid)
 	if err != nil {
 		log.Println(err)
@@ -891,6 +900,11 @@ func ScanHandler(w ResponseWriter, r *Request) {
 	log.Println("openid======", openid)
 	m, b := tools.MQFW.FindOneByField("user", `{"s_m_openid":"`+openid+`"}`, `{"_id":1,"s_headimage":1,"o_jy":1}`)
 	if b {
+		//剑鱼订阅提醒  (1:关注事件,2:扫码事件,3:发送客服消息处理,4:支付操作)
+		if openid != "" {
+			redis.Put("push", "remind&&"+openid, "2", 172800)
+		}
+
 		pccodepre := "0"
 		RSource := ""
 		if len(r.EventKey) > 1 {
@@ -1078,6 +1092,11 @@ func UnSubscribe(w ResponseWriter, r *Request) {
 		saveSubscribe(openid, r.Event, r.EventKey, 0)
 		updateIsPush(openid, 0)
 		updateUserLog(openid) //修改用户日志
+		//剑鱼订阅提醒--取消
+		if openid != "" {
+			redis.Del("push", "remind&&"+openid)
+		}
+
 	}()
 }
 

+ 6 - 0
src/jfw/pay/weixin.go

@@ -12,6 +12,7 @@ import (
 	"log"
 	"net/http"
 	"qfw/util"
+	"qfw/util/redis"
 	"strings"
 	"time"
 
@@ -181,6 +182,11 @@ func (p *WxPayAction) PayCallback() {
 	ret := util.XmlToMap(string(by))
 	flag := false
 	if ret["return_code"] == "SUCCESS" && ret["appid"] == public.WxStruct.Appid && ret["mch_id"] == public.WxStruct.Mchid {
+		//剑鱼订阅提醒  (1:关注事件,2:扫码事件,3:发送客服消息处理,4:支付操作)
+		openid := ret["openid"]
+		if openid != "" {
+			redis.Put("push", "remind&&"+openid, "4", 172800)
+		}
 		if ret["sign"] == util.CreateWxSign(fmt.Sprintf("&key=%s", public.WxStruct.Key), ret) {
 			if strings.HasPrefix(ret["out_trade_no"], "a_") { //打赏
 				query := map[string]interface{}{

二进制
src/src