Browse Source

Merge branch 'develop' of 192.168.3.17:zhanghongbo/qfw into develop

zhangjinkun 9 years ago
parent
commit
ed1eaac847
43 changed files with 1055 additions and 243 deletions
  1. 4 0
      common/src/gopkg.in/mgo.v2/session.go
  2. 3 2
      common/src/main.go
  3. 18 8
      common/src/mainIndex.go
  4. 4 2
      common/src/qfw/util/common.go
  5. 13 6
      common/src/qfw/util/date.go
  6. 151 3
      common/src/qfw/util/elastic/elasticutil.go
  7. 8 0
      common/src/qfw/util/elastic/elasticutil_test.go
  8. 1 0
      common/src/qfw/util/encrypt_test.go
  9. 3 2
      common/src/qfw/util/redis/redisutil.go
  10. 1 1
      core/src/message.json
  11. 53 26
      core/src/qfw/manage/auditing.go
  12. 1 0
      core/src/qfw/mobile/mobile.go
  13. 35 1
      core/src/qfw/mobile/wxmenu.go
  14. 5 5
      core/src/qfw/search/searchService.go
  15. 6 5
      core/src/qfw/search/wxsearchservice.go
  16. 65 19
      core/src/qfw/yellowpage/yellowpagemanager.go
  17. 1 1
      core/src/timetask.json
  18. BIN
      core/src/web/staticres/images/swordfish/sf_01.jpg
  19. 17 3
      core/src/web/staticres/js/chart.js
  20. 7 0
      core/src/web/staticres/js/qfw.js
  21. 45 1
      core/src/web/staticres/wxswordfish/style.css
  22. 1 1
      core/src/web/templates/common/bottom.html
  23. 3 3
      core/src/web/templates/enterprise/detail.html
  24. 3 16
      core/src/web/templates/manage/tj.html
  25. 5 5
      core/src/web/templates/member/incmobile/baseinfo.html
  26. 1 1
      core/src/web/templates/member/incmobile/detailindex.html
  27. 4 4
      core/src/web/templates/member/incmobile/list.html
  28. 1 1
      core/src/web/templates/search/enterpriseList.html
  29. 39 0
      core/src/web/templates/swordfish/wxex.html
  30. 11 0
      core/src/web/templates/swordfish/wxpush.html
  31. 2 2
      core/src/web/templates/swordfish/wxrssset.html
  32. 1 2
      push/src/config.json
  33. 2 1
      push/src/qfw/push/dopush/dopush.go
  34. BIN
      weixin/doc/kfex.rar
  35. 10 8
      weixin/src/config.json
  36. 43 4
      weixin/src/main.go
  37. 1 1
      weixin/src/qfw/weixin/dao/userdao.go
  38. 1 2
      weixin/src/qfw/weixin/distinguishwork.go
  39. 8 1
      weixin/src/qfw/weixin/msgtxtchandler.go
  40. 272 0
      weixin/src/qfw/weixin/weixincustomer.go
  41. 178 106
      weixin/src/qfw/weixin/weixinsdk.go
  42. 26 0
      weixin/src/qfw/weixin/wxcustomer_test.go
  43. 2 0
      weixin/src/qfw/weixinconfig/weixinconfig.go

+ 4 - 0
common/src/gopkg.in/mgo.v2/session.go

@@ -1975,6 +1975,10 @@ func (c *Collection) Pipe(pipeline interface{}) *Pipe {
 
 // Iter executes the pipeline and returns an iterator capable of going
 // over all the generated results.
+func (p *Pipe) SetAllowDisk(b bool) *Pipe {
+	p.allowDisk = b
+	return p
+}
 func (p *Pipe) Iter() *Iter {
 	// Clone session and set it to Monotonic mode so that the server
 	// used for the query may be safely obtained afterwards, if

+ 3 - 2
common/src/main.go

@@ -2,7 +2,6 @@ package main
 
 import (
 	"fmt"
-	"gopkg.in/mgo.v2/bson"
 	"log"
 	"os"
 	util "qfw/util"
@@ -10,12 +9,14 @@ import (
 	. "qfw/util/mongodbutil"
 	"strconv"
 	"strings"
+
+	"gopkg.in/mgo.v2/bson"
 )
 
 var N = 200
 
 //生enterprise表
-func main() {
+func mainS() {
 	//src.exe 192.168.3.18:27080 192.168.3.18:27090 http://192.168.3.18:9800 enterprise all enterprise enterprise 2000
 	// 参数 mongodb(网站库) mongodb(公示库) elastic collection area index-db inde-type N
 	log.Println("-------------开始-----------------")

+ 18 - 8
common/src/mainIndex.go

@@ -8,30 +8,38 @@ import (
 	"strconv"
 )
 
-var NN = 200
+var NN = 1000
+var T = "qfw"
 
-//生content。service
-func mainS() {
+//生content。service
+func main() {
 	//6 参数 mongodb elastic collection index-db inde-type N
+	if len(os.Args) < 7 {
+		log.Println("程序 mgoIp elasticIp mgoCollection elasticIndex elasticType saveSize table")
+		return
+	}
+	if len(os.Args) > 7 {
+		T = os.Args[7]
+	}
 	n1, _ := strconv.Atoi(os.Args[6])
 	if n1 > 0 {
 		NN = n1
 	}
-	mongodb.InitMongodbPool(1, os.Args[1], "qfw")
-	elastic.InitElastic(os.Args[2])
+	mongodb.InitMongodbPool(1, os.Args[1], T)
+	elastic.InitElasticSize((os.Args[2]), 5)
 	session := mongodb.GetMgoConn()
 	var m map[string]interface{} = nil
-	query := session.DB("qfw").C(os.Args[3]).Find(m).Iter()
+	query := session.DB(T).C(os.Args[3]).Find(m).Iter()
 	arr := make([]map[string]interface{}, NN)
 	var n int
 	i := 0
 	for tmp := make(map[string]interface{}); query.Next(tmp); i = i + 1 {
 		n++
-		log.Printf("current index:=%d \n", n)
 		arr[i] = tmp
 		tmp = make(map[string]interface{})
 		if i == NN-1 {
-			elastic.BulkSave(os.Args[4], os.Args[5], &arr, true)
+			tmp := arr
+			elastic.BulkSave(os.Args[4], os.Args[5], &tmp, true)
 			i = 0
 			arr = make([]map[string]interface{}, NN)
 		}
@@ -42,4 +50,6 @@ func mainS() {
 	if i > 0 {
 		elastic.BulkSave(os.Args[4], os.Args[5], &arr, true)
 	}
+	mongodb.DestoryMongoConn(session)
+	log.Println("over....", n)
 }

+ 4 - 2
common/src/qfw/util/common.go

@@ -193,8 +193,9 @@ func Int64All(num interface{}) int64 {
 	} else if i3, ok3 := num.(float32); ok3 {
 		return int64(i3)
 	} else if i4, ok4 := num.(string); ok4 {
-		in, _ := strconv.Atoi(i4)
-		return int64(in)
+		i64, _ := strconv.ParseInt(i4, 10, 64)
+		//in, _ := strconv.Atoi(i4)
+		return i64
 	} else if i5, ok5 := num.(int16); ok5 {
 		return int64(i5)
 	} else if i6, ok6 := num.(int8); ok6 {
@@ -445,6 +446,7 @@ func EndWith(value, str string) bool {
 //出错拦截
 func Catch() {
 	if r := recover(); r != nil {
+		log.Println(r)
 		for skip := 0; ; skip++ {
 			_, file, line, ok := runtime.Caller(skip)
 			if !ok {

+ 13 - 6
common/src/qfw/util/date.go

@@ -2,6 +2,7 @@
 package util
 
 import (
+	"fmt"
 	"time"
 )
 
@@ -20,14 +21,20 @@ func FormatDate(src *time.Time, layout string) string {
 //兼容从Java转换过来的数据,java生成的时间戳是按微秒算的,Go中只能按毫秒或纳秒算
 func formatDateWithInt64(src *int64, layout string) string {
 	var tmp int64
-	if (*src) > 6000000000 {
-		tmp = (*src) / 1000
-	} else if (*src) < 1 {
-		return ""
+	if *src > 0 {
+		if len(fmt.Sprint(*src)) > 10 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
+
 	} else {
-		tmp = (*src)
+		if len(fmt.Sprint(*src)) > 11 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
 	}
-
 	date := time.Unix(tmp, 0)
 	return FormatDate(&date, layout)
 }

+ 151 - 3
common/src/qfw/util/elastic/elasticutil.go

@@ -8,13 +8,14 @@ import (
 	//	"bytes"
 	"encoding/json"
 	"fmt"
-	es "gopkg.in/olivere/elastic.v1"
 	"log"
 	"qfw/util"
 	"qfw/util/mongodb"
 	mongodbutil "qfw/util/mongodbutil"
 	"reflect"
 
+	es "gopkg.in/olivere/elastic.v1"
+
 	"strconv"
 	"strings"
 )
@@ -189,6 +190,7 @@ func Get(index, itype, query string) *[]map[string]interface{} {
 
 //分页查询
 //{"name":"张三","$and":[{"age":{"$gt":10}},{"age":{"$lte":20}}]}
+//fields直接是 `"_id","title"`
 func GetPage(index, itype, query, order, field string, start, limit int) *[]map[string]interface{} {
 	return Get(index, itype, MakeQuery(query, order, field, start, limit))
 }
@@ -739,10 +741,156 @@ func Count(index, itype string, query interface{}) int64 {
 		if err != nil {
 			log.Println("统计出错", err.Error())
 		}
-		if n > 5000 {
-			n = 5000
+		if n > 500 {
+			n = 500
 		}
 		return n
 	}
 	return 0
 }
+
+//ngram精确查询
+/*
+{
+  "query": {
+    "bool": {
+      "should": [
+        {
+	"bool":{
+	  "must":[
+	  {     "multi_match": {
+            "query": "智能",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+        },{
+          "multi_match": {
+            "query": "机器",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+        },{
+          "multi_match": {
+            "query": "2016",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+	  }
+	  ]
+	}
+        },
+
+{
+	"bool":{
+	  "must":[
+	  {          "multi_match": {
+            "query": "河南",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+        },{
+          "multi_match": {
+            "query": "工商",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+        },{
+          "multi_match": {
+            "query": "2016",
+            "type": "phrase",
+            "fields": [
+              "title"
+            ],
+           "analyzer": "my_ngram"
+          }
+	  }
+	  ]
+	}
+        }
+      ],"minimum_should_match": 1
+    }
+  },
+  "_source": [
+    "_id",
+    "title"
+  ],
+  "from": 0,
+  "size": 10,
+  "sort": [{
+      "publishtime": "desc"
+    }]
+}
+
+*/
+//"2016+智能+办公,"河南+工商"
+//["2016+智能+办公","河南+工商"]
+//QStr = `{"query":{"bool":{should":[$or],"minimum_should_match" : 1}}}`
+//{"bool":{"must":[]}}
+//{"multi_match": {"query": "$word","type": "phrase", "fields": [$field],"analyzer": "my_ngram"}}
+const (
+	NgramStr  = `{"query":{"bool":{"must":[%s],"should":[%s],"minimum_should_match" : 1}}}`
+	NgramMust = `{"bool":{"must":[%s]}}`
+	minq      = `{"multi_match": {"query": "%s","type": "phrase", "fields": [%s],"analyzer": "my_ngram"}}`
+)
+
+func GetNgramQuery(query interface{}, mustquery, findfields string) (qstr string) {
+	var words []string
+	if q, ok := query.(string); ok {
+		words = strings.Split(q, ",")
+	} else if q, ok := query.([]string); ok {
+		words = q
+	} else if q, ok := query.([]interface{}); ok {
+		words = util.ObjArrToStringArr(q)
+	}
+	if words != nil {
+		new_minq := fmt.Sprintf(minq, "%s", findfields)
+		musts := []string{}
+		for _, qs_words := range words {
+			qws := strings.Split(qs_words, "+")
+			mq := []string{}
+			for _, qs_word := range qws {
+				mq = append(mq, fmt.Sprintf(new_minq, qs_word))
+			}
+			musts = append(musts, fmt.Sprintf(NgramMust, strings.Join(mq, ",")))
+		}
+		qstr = fmt.Sprintf(NgramStr, mustquery, strings.Join(musts, ","))
+		log.Println("ngram-query", qstr)
+	}
+	return
+}
+
+func GetByNgram(index, itype string, query interface{}, mustquery, findfields, order, fields string, start, limit int) *[]map[string]interface{} {
+	defer util.Catch()
+	qstr := GetNgramQuery(query, mustquery, findfields)
+	if qstr != "" {
+		if len(fields) > 0 {
+			qstr = qstr[:len(qstr)-1] + `,"_source":[` + fields + "]}"
+		}
+		if len(order) > 0 {
+			qstr = qstr[:len(qstr)-1] + `,"sort":[` + SR(SR(SR(SR(order, ",", "},{", -1), " ", "", -1), ":-1", `:"desc"`, -1), ":1", `:"asc"`, -1) + `]}`
+		}
+		if start > -1 {
+			qstr = qstr[:len(qstr)-1] + `,"from":` + strconv.Itoa(start) + `,"size":` + strconv.Itoa(limit) + "}"
+		}
+		log.Println("ngram-find", qstr)
+		return Get(index, itype, qstr)
+	} else {
+		return nil
+	}
+
+}

+ 8 - 0
common/src/qfw/util/elastic/elasticutil_test.go

@@ -206,3 +206,11 @@ func Test_q(t *testing.T) {
 	//log.Println(MakeQuery(`{"_id":"556349a6a442ab325177ade0"}`, "", `"_id"`, -1, -1))
 	//Count("enterprise","enterprise",)
 }
+
+func Test_bidding(t *testing.T) {
+	InitElasticSize("http://192.168.3.18:9800", 1)
+	//log.Println(Count("bidding_v1", "bidding", GetNgramQuery("河南+工商", `"title"`)))
+	//log.Println(GetByNgram("bidding_v1", "bidding", "河南省", `"title"`, `{"publishtime":-1}`, `"title"`, 0, 10))
+	res := GetPage("bidding", "bidding", "", `{"comeintime":-1}`, `"_id"`, 0, 1)
+	log.Println((*res)[0]["_id"])
+}

+ 1 - 0
common/src/qfw/util/encrypt_test.go

@@ -29,6 +29,7 @@ func Test_sim(t *testing.T) {
 	log.Println("=====", se.EncodeString(",1349385977,ASD"))
 	//log.Println("---", se.DecodeString("GyUlIhEDSGheAzEpJl8kHB9oVEwmLRkDPTN/CB1AHQtcX1RFHkNGWgYLFgcWHFNTRVwbAQ=="))
 	log.Println("---", se.DecodeString("GycHKzoDMykjDSEOMz0gLzI6FTtANCBNAiseVkkBHQtcX1FCRlpCV1JDR0MHFhUBBwccBxYA"))
+	log.Println("==---==", se.DecodeString("Gw01HikBOSI0OzwhOUIKAh83Fj5AIwcBJSAKDUlYRVtGWVRCQFhGVkkHAwACCgMdBwcRDREdGwE="))
 
 	now := time.Now()
 	tom := time.Date(now.Year(), now.Month(), now.Day(), 18, 0, 0, 0, time.Local)

+ 3 - 2
common/src/qfw/util/redis/redisutil.go

@@ -4,19 +4,20 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	redigo "github.com/garyburd/redigo/redis"
 	"log"
 	"qfw/util/mongodb"
 	"runtime"
 	"strings"
 	"time"
+
+	redigo "github.com/garyburd/redigo/redis"
 )
 
 var RedisPool map[string]*redigo.Pool
 
 //初始化redis 1为多个连接池,2为共用一个连接池
 func InitRedis(addrs string) {
-	InitRedisBySize(addrs, 1000, 500, 240)
+	InitRedisBySize(addrs, 300, 30, 240)
 }
 
 func InitRedisBySize(addrs string, maxSize, maxIdle, timeout int) {

+ 1 - 1
core/src/message.json

@@ -1,6 +1,6 @@
 {
 	"weixinrpc":"127.0.0.1:82",
-	"swordfishaction":"/swordfish/page",
+	"swordfishaction":"/swordfish/excessive",
 	"signature":"/member/credit/myCredit",
 	"entsearchaction":"/wx/search/enterprise/index.html",
 	"lawsearchaction":"/law/qfw/index",

+ 53 - 26
core/src/qfw/manage/auditing.go

@@ -86,7 +86,6 @@ func (s *SystemManage) Countlist() {
 	compareafter := s.GetString("compareafter")
 	comparetype := s.GetString("comparetype")
 	checked := s.GetString("checked")
-
 	if comparetype != "nfish" {
 		//第一条图标数据
 		t1, _ := time.ParseInLocation("2006-01-02 15:04:05", timebefore+" 00:00:00", time.Local)
@@ -108,54 +107,82 @@ func (s *SystemManage) Countlist() {
 		t1, _ := time.ParseInLocation("2006-01-02 15:04:05", timebefore+" 00:00:00", time.Local)
 		t2, _ := time.ParseInLocation("2006-01-02 15:04:05", timeafter+" 00:00:00", time.Local)
 		condition := M{"l_date": M{"$gte": t1.Unix(), "$lte": t2.Unix()}}
-		timers := *Find("web_viewcount", condition, `{"l_date": 1}`, `{"l_date":1,"i_viewcount":1}`, false, -1, -1)
+		timers := *Find("web_viewcount", condition, `{"l_date": 1}`, nil, false, -1, -1)
 		ns := make([]map[string]interface{}, 0)
+		ns2 := make([]map[string]interface{}, 0)
 		for _, v := range timers {
 			var tmp = make(map[string]interface{})
 			tmp["_id"] = v["_id"]
 			tmp["intime"] = v["l_date"]
 			tmp["nfish"] = v["i_viewcount"]
+			if tmp["nfish"] == nil {
+				tmp["nfish"] = "0"
+			}
 			ns = append(ns, tmp)
 		}
-		log.Println(ns)
-		s.ServeJson(M{"t": ns, "c": "[]"})
+		for _, v := range timers {
+			var tmp = make(map[string]interface{})
+			tmp["_id"] = v["_id"]
+			tmp["intime"] = v["l_date"]
+			tmp["nfish"] = v["i_nviewcount"]
+			if tmp["nfish"] == nil {
+				tmp["nfish"] = "0"
+			}
+			ns2 = append(ns2, tmp)
+		}
+		s.ServeJson(M{"t": ns, "c": ns2})
 	}
 }
 
 func (s *SystemManage) Count() {
 	rs := *Find("tj", nil, `{"intime": 0}`, nil, false, 0, 1)
 	//获取关键词
-	msg := *Find("user", `{"o_msgset":{"$exists":true}}`, nil, `{"o_msgset":1}`, false, -1, -1)
+	msg := *Find("user", `{"$or":[{"o_msgset":{"$exists":true}},{"o_jy":{"$exists":true}}]}`, nil, `{"o_msgset":1,"o_jy":1}`, false, -1, -1)
 	msgcount := 0
-	tenderarr := make(map[string]int)
-	bidarr := make(map[string]int)
+	keys := map[string]int{}
 	for _, v := range msg {
-		obj := v["o_msgset"].(map[string]interface{})
-		if obj["tender"] != nil {
-			tender := obj["tender"].(map[string]interface{})
-			if tender["a_key"] != nil {
-				arr := tender["a_key"].([]interface{})
-				for _, v := range arr {
-					vs := v.(string)
-					tenderarr[vs] = tenderarr[vs] + 1
+		if v["o_msgset"] != nil {
+			obj := v["o_msgset"].(map[string]interface{})
+			if obj["tender"] != nil {
+				tender := obj["tender"].(map[string]interface{})
+				if tender["a_key"] != nil {
+					arr := tender["a_key"].([]interface{})
+					for _, v := range arr {
+						vs := v.(string)
+						keys[vs] = keys[vs] + 1
+					}
 				}
 			}
-		}
-		if obj["bid"] != nil {
-			bid := obj["bid"].(map[string]interface{})
-			if bid["a_key"] != nil {
-				arr := bid["a_key"].([]interface{})
-				for _, v := range arr {
-					vs := v.(string)
-					bidarr[vs] = bidarr[vs] + 1
+
+			if obj["bid"] != nil {
+				bid := obj["bid"].(map[string]interface{})
+				if bid["a_key"] != nil {
+					arr := bid["a_key"].([]interface{})
+					for _, v := range arr {
+						vs := v.(string)
+						keys[vs] = keys[vs] + 1
+					}
 				}
 			}
 		}
+		if v["o_jy"] != nil {
+			obj := v["o_jy"].(map[string]interface{})
+			jykeys := []interface{}{}
+			if obj["a_key"] != nil {
+				jykeys = obj["a_key"].([]interface{})
+			}
+
+			for _, v := range jykeys {
+				vs := v.(string)
+				keys[vs] = keys[vs] + 1
+			}
+		}
 	}
-	msgcount = len(tenderarr) + len(bidarr)
+	msgcount = len(keys)
 	servicemap := make(map[string]interface{})
 	if len(rs) != 0 {
 		rsOne := rs[0]
+
 		//获取服务种类总表
 		services := rsOne["services"]
 		if services != nil {
@@ -179,10 +206,10 @@ func (s *SystemManage) Count() {
 			}
 
 		}
+
 		s.T["rs"] = rsOne
 		s.T["msgcount"] = msgcount
-		s.T["tender"] = tenderarr
-		s.T["bid"] = bidarr
+		s.T["keys"] = keys
 		s.T["servicename"] = servicemap
 	}
 	s.Render("/manage/tj.html")

+ 1 - 0
core/src/qfw/mobile/mobile.go

@@ -24,6 +24,7 @@ type Mobile struct {
 	wxpushAjaxReq  xweb.Mapper `xweb:"/wxpush/bid/ajaxReq"`
 	wxpushView     xweb.Mapper `xweb:"/wxpush/bid/wxpushview/(.*)"` //推送结果预览
 	mobileindex    xweb.Mapper `xweb:"/mobile/mobileindex"`
+	wxex           xweb.Mapper `xweb:"/swordfish/excessive"`
 }
 
 func init() {

+ 35 - 1
core/src/qfw/mobile/wxmenu.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/go-xweb/xweb"
+	. "gopkg.in/mgo.v2/bson"
 	"log"
 	"math/rand"
 	"net/rpc"
@@ -47,6 +48,40 @@ func (m *Mobile) Share(shareid string) error {
 	return m.Render("/swordfish/wxshare.html", &m.T)
 }
 
+//过度页面
+func (m *Mobile) Wxex() error {
+	var newsubscribe = "N"
+	if m.Session().Get("userId") != nil {
+		userInfo := mongodb.FindById("user", m.GetSession("userId").(string), nil)
+		if *userInfo != nil {
+			if (*userInfo)["s_unionid"] != nil || (*userInfo)["s_unionid"] != "" {
+				var unid = (*userInfo)["s_unionid"]
+				query := make(M)
+				query["s_unionid"] = unid
+				userdata := mongodb.Find("user", query, `{"l_date":-1}`, `{"s_m_openid":1}`, false, 0, 2)
+				if len(*userdata) > 0 {
+					for _, opendata := range *userdata {
+						if opendata["s_m_openid"] != (*userInfo)["s_m_openid"] {
+							var opid = opendata["s_m_openid"]
+							queryop := make(M)
+							queryop["s_m_openid"] = opid
+							jysb := mongodb.Find("jy_subscribe", queryop, `{"l_date":-1}`, `{"s_event":1}`, false, 0, 1)
+							if (*jysb)[0]["s_event"] == "subscribe" {
+								newsubscribe = "Y"
+							}
+						}
+					}
+
+				}
+			}
+		}
+		m.T["newsubscribe"] = newsubscribe
+		return m.Render("/swordfish/wxex.html", &m.T)
+	} else {
+		return m.Redirect("/swordfish/share/-1")
+	}
+}
+
 //进入订阅页面
 func (m *Mobile) Wxrssset() error {
 	searchname := m.GetString("searchnametwo")
@@ -110,7 +145,6 @@ func (m *Mobile) Wxrssset() error {
 			//取Shareid
 			m.T["shareid"] = cutil.FindMyShareId("topjy", m.Session().Get("s_m_openid").(string))
 		}
-
 		return m.Render("/swordfish/wxrssset.html", &m.T)
 	} else {
 		return m.Redirect("/swordfish/share/-1")

+ 5 - 5
core/src/qfw/search/searchService.go

@@ -348,7 +348,7 @@ func searhWebContentent(querymap map[string]string, n *Search, reqType string) (
 							  "must":[{"prefix":{"OpLocDistrict":"` + ipcity + `"}}]
 						    }
 						}
-						,"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","excDirect"]
+						,"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","Ycml"]
 						,"from":0,
 						"size":` + fmt.Sprintf("%v", perPage) + `,
 						  "sort": [{"_score": "desc"},{"OpSint":"desc"},{"RegCap":"desc"}]
@@ -512,7 +512,7 @@ func searhWebContentent(querymap map[string]string, n *Search, reqType string) (
 			"s_synopsis":{"force_source": true},
 			"stock":{"force_source": true}
         }
-    },"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","excDirect"]
+    },"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","Ycml"]
 	,"from":` + fmt.Sprintf("%v", ((currentPage-1)*perPage)) + `,
 	"size":` + fmt.Sprintf("%v", perPage) +
 			`,"sort":[` + sort + `] }`
@@ -545,6 +545,7 @@ func searhWebContentent(querymap map[string]string, n *Search, reqType string) (
 
 			EntName, _ := res[i]["EntName"].(string)
 			tmpNo := res[i]["EntType"]
+			res[i]["EntTypeCopy"] = tmpNo
 			res[i]["RegCapEntType"] = tmpNo
 			if tmpNo != nil {
 				switch tmpNo.(string) {
@@ -613,12 +614,11 @@ func searhWebContentent(querymap map[string]string, n *Search, reqType string) (
 			tmpdate, _ := res[i]["EstDate"]
 
 			//res[i]["OperatorLabel"] = getDiffName(EntType)[0]
-			if tmpdate != nil {
+			if tmpdate != nil && IntAll(tmpdate) != 0 {
 				res[i]["EstDate"] = FormatDateWithObj(&tmpdate, Date_Short_Layout)
 			} else {
 				res[i]["EstDate"] = ""
 			}
-
 		}
 	}
 	//log.Println(total)
@@ -741,7 +741,7 @@ func searhWebContententMp(querymap map[string]string, n *Search, reqType string,
 				res[i]["stock"] = template.HTML(stock)
 			}
 			tmpdate, _ := res[i]["EstDate"]
-			if tmpdate != nil {
+			if tmpdate != nil && IntAll(tmpdate) != 0 {
 				res[i]["EstDate"] = FormatDateWithObj(&tmpdate, Date_Short_Layout)
 			} else {
 				res[i]["EstDate"] = ""

+ 6 - 5
core/src/qfw/search/wxsearchservice.go

@@ -89,7 +89,7 @@ func FormatData(data *[]map[string]interface{}, action *xweb.Action) string {
 			"EstDate":       estDate,
 			"OpStateName":   fmt.Sprint(v["OpStateName"]),
 			"status":        "n",
-			"excDirect":     fmt.Sprint(v["excDirect"]),
+			"Ycml":          fmt.Sprint(v["Ycml"]),
 		}
 		//是否关注
 		entId := tmp["_id"]
@@ -152,7 +152,7 @@ func wxsearhWebContentent(querymap map[string]string, n *Wxsearch, reqType strin
 						      }
 						    }
 						  }
-						,"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","RegCapCurName","s_avatar","excDirect"]
+						,"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","RegCapCurName","s_avatar","Ycml"]
 						,"from": ` + fmt.Sprintf("%v", ((currentPage-1)*perPage)) + `,
 						"size":` + fmt.Sprintf("%v", perPage) + `,
 						  "sort": [{"_score": "desc"},{"OpSint":"desc"},{"RegCap":"desc"}]
@@ -293,7 +293,7 @@ func wxsearhWebContentent(querymap map[string]string, n *Wxsearch, reqType strin
 			"s_synopsis":{"force_source": true},
 			"stock":{"force_source": true}
         }
-    },"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","excDirect"]
+    },"_source":["_id","EntName","RegOrgName","RegNo","EntType","LeRep","EstDate","OpLocDistrictName","RegCap","OpStateName","OpState","s_servicenames","s_action","OpLocDistrict","s_submitid","RegCapCurName","s_avatar","Ycml"]
 	,"from":` + fmt.Sprintf("%v", ((currentPage-1)*perPage)) + `,
 	"size":` + fmt.Sprintf("%v", perPage) +
 			`,"sort":[` + sort + `] }`
@@ -321,6 +321,7 @@ func wxsearhWebContentent(querymap map[string]string, n *Wxsearch, reqType strin
 
 			EntName, _ := res[i]["EntName"].(string)
 			tmpNo := res[i]["EntType"]
+			res[i]["EntTypeCopy"] = tmpNo
 			res[i]["RegCapEntType"] = tmpNo
 			if tmpNo != nil {
 				switch tmpNo.(string) {
@@ -423,7 +424,7 @@ func wxsearhWebContententMp(querymap map[string]string, n *Wxsearch, reqType str
 	    "_source": [
 	        "_id","EntName","RegOrgName","RegNo","EntType",
 	        "LeRep","EstDate","OpLocDistrictName","OpStateName",
-	        "s_servicenames","s_action","OpLocDistrict","s_submitid","excDirect"
+	        "s_servicenames","s_action","OpLocDistrict","s_submitid","Ycml"
 	    ],
 	    "from": 0,"size": 10,
 	    "sort": [{"OpSint": "desc","RegCap": "desc"}]
@@ -501,7 +502,7 @@ func wxsearhWebContententMp(querymap map[string]string, n *Wxsearch, reqType str
 				res[i]["stock"] = template.HTML(stock)
 			}
 			tmpdate, _ := res[i]["EstDate"]
-			if tmpdate != nil {
+			if tmpdate != nil && IntAll(tmpdate) != 0 {
 				res[i]["EstDate"] = FormatDateWithObj(&tmpdate, Date_Short_Layout)
 			} else {
 				res[i]["EstDate"] = ""

+ 65 - 19
core/src/qfw/yellowpage/yellowpagemanager.go

@@ -48,7 +48,7 @@ func FormatDate(src *interface{}) string {
 }
 
 func GetEntInfo(id string) map[string]interface{} {
-	res := mongodbutil.FindById("enterprise", cf.SysConfig.EntMongodbAlias, cf.SysConfig.EntMongodbName, id, `{"RegNo":1, "EntName":1, "EntType":1, "EntTypeName":1, "OpLocDistrict":1, "LeRep":1, "LegCerNO":1, "Tel":1, "Dom":1, "OpScope":1, "OpFrom":1, "OpTo":1, "RegCap":1, "EstDate":1, "CompForm":1, "CompFormName":1, "OpState":1, "OpStateName":1, "RegOrgName":1, "IssBLicDate":1, "Timestamp":1,"SourceType":1, "Nb_email":1,"IndustryPhyName":1,"investor":1,"alterInfo":1,"staffinfo":1,"RegCapCurName":1,"excDirect":1}`)
+	res := mongodbutil.FindById("enterprise", cf.SysConfig.EntMongodbAlias, cf.SysConfig.EntMongodbName, id, `{"RegNo":1, "EntName":1, "EntType":1, "EntTypeName":1, "OpLocDistrict":1, "LeRep":1, "LegCerNO":1, "Tel":1, "Dom":1, "OpScope":1, "OpFrom":1, "OpTo":1, "RegCap":1, "EstDate":1, "CompForm":1, "CompFormName":1, "OpState":1, "OpStateName":1, "RegOrgName":1, "IssBLicDate":1, "Timestamp":1,"SourceType":1, "Nb_email":1,"IndustryPhyName":1,"investor":1,"alterInfo":1,"staffinfo":1,"RegCapCurName":1,"Ycml":1,"i_scale":1}`)
 	if res != nil && len(*res) > 0 {
 		opl, _ := (*res)["OpLocDistrict"].(string)
 		if len(opl) >= 2 {
@@ -67,17 +67,18 @@ func GetEntInfo(id string) map[string]interface{} {
 			(*res)["OpFrom"] = FormatDate(&OpFrom)
 		}
 		OpTo := (*res)["OpTo"]
-		if OpFrom != nil {
+		if OpTo != nil {
 			(*res)["OpTo"] = FormatDate(&OpTo)
 		}
 		IssBLicDate := (*res)["IssBLicDate"]
-		if OpFrom != nil {
+		if IssBLicDate != nil {
 			(*res)["IssBLicDate"] = FormatDate(&IssBLicDate)
 		}
 		EstDate := (*res)["EstDate"]
-		if OpFrom != nil {
+		if EstDate != nil {
 			(*res)["EstDate"] = FormatDate(&EstDate)
 		}
+		(*res)["EntTypeCopy"] = (*res)["EntType"]
 		var EntType string
 		if (*res)["EntType"] != nil && (*res)["EntType"].(string) != "" {
 			EntType = (*res)["EntType"].(string)
@@ -323,22 +324,67 @@ func findRelation(certype, cerno, name, rtype string, level int, ls *list.List,
 
 //存储节点,只关心3个元素,节点名称、节点id、关系节点id
 func storeNode(data *map[string]interface{}, ls *list.List, dir /*方向反转*/ bool, level int) {
-	invtype := fmt.Sprint((*data)["invtype"])
+	invtype := ""
+	if (*data)["invtype"] != nil {
+		invtype = fmt.Sprint((*data)["invtype"])
+	}
 	//invcertype, _ := (*data)["invcertype"].(string)
-	invcerno := fmt.Sprint((*data)["invcerno"])
-	invname := fmt.Sprint((*data)["invname"])
-	entregno := fmt.Sprint((*data)["entregno"])
-	entname := fmt.Sprint((*data)["entname"])
-	invweight := fmt.Sprint((*data)["weight"])
-	invacconam := fmt.Sprint((*data)["invacconam"])
-	regcap := fmt.Sprint((*data)["regcap"])
-	invregcap := fmt.Sprint((*data)["invregcap"])
-	legcerno := fmt.Sprint((*data)["legcerno"])
-	opstate := fmt.Sprint((*data)["opstate"])
-	invopstate := fmt.Sprint((*data)["invopstate"])
-	regcapcurname := fmt.Sprint((*data)["regcapcurname"])
-	shortname := fmt.Sprint((*data)["shortname"])
-	entmid := util.BsonIdToSId((*data)["entmid"])
+	invcerno := ""
+	if (*data)["invcerno"] != nil {
+		invcerno = fmt.Sprint((*data)["invcerno"])
+	}
+	invname := ""
+	if (*data)["invname"] != nil {
+		invname = fmt.Sprint((*data)["invname"])
+	}
+	entregno := ""
+	if (*data)["entregno"] != nil {
+		entregno = fmt.Sprint((*data)["entregno"])
+	}
+	entname := ""
+	if (*data)["entname"] != nil {
+		entname = fmt.Sprint((*data)["entname"])
+	}
+	invweight := "0"
+	if (*data)["weight"] != nil {
+		invweight = fmt.Sprint((*data)["weight"])
+	}
+	invacconam := "0"
+	if (*data)["invacconam"] != nil {
+		invacconam = fmt.Sprint((*data)["invacconam"])
+	}
+	regcap := "0"
+	if (*data)["regcap"] != nil {
+		regcap = fmt.Sprint((*data)["regcap"])
+	}
+	invregcap := "0"
+	if (*data)["invregcap"] != nil {
+		invregcap = fmt.Sprint((*data)["invregcap"])
+	}
+	legcerno := ""
+	if (*data)["legcerno"] != nil {
+		legcerno = fmt.Sprint((*data)["legcerno"])
+	}
+	opstate := ""
+	if (*data)["opstate"] != nil {
+		opstate = fmt.Sprint((*data)["opstate"])
+	}
+	invopstate := ""
+	if (*data)["invopstate"] != nil {
+		invopstate = fmt.Sprint((*data)["invopstate"])
+	}
+	regcapcurname := ""
+	if (*data)["regcapcurname"] != nil {
+		regcapcurname = fmt.Sprint((*data)["regcapcurname"])
+	}
+	shortname := ""
+	if (*data)["shortname"] != nil {
+		shortname = fmt.Sprint((*data)["shortname"])
+	}
+	entmid := ""
+	if (*data)["entmid"] != nil {
+		entmid = util.BsonIdToSId((*data)["entmid"])
+	}
 	ls.PushBack([]string{invtype, invcerno, invname, entregno, entname, strconv.Itoa(level), invweight, invacconam, regcap, invregcap, legcerno, opstate, invopstate, regcapcurname, util.BsonIdToSId((*data)["_id"]), shortname, entmid})
 }
 

+ 1 - 1
core/src/timetask.json

@@ -1 +1 @@
-{"comment":{"c_rate":720,"commentrate":900},"market":{"demand":{"attr":["i_hits","i_bids","i_status"],"timepoint":"2016-06-13 10:20:06"},"service":{"attr":["i_hits","i_sales","i_comments","i_score","i_appcounts"],"timepoint":"2016-06-13 10:20:06"}},"marketisstart":true,"marketrate":300}
+{"comment":{"c_rate":720,"commentrate":900},"market":{"demand":{"attr":["i_hits","i_bids","i_status"],"timepoint":"2016-07-01 08:21:25"},"service":{"attr":["i_hits","i_sales","i_comments","i_score","i_appcounts"],"timepoint":"2016-07-01 08:21:25"}},"marketisstart":true,"marketrate":300}

BIN
core/src/web/staticres/images/swordfish/sf_01.jpg


+ 17 - 3
core/src/web/staticres/js/chart.js

@@ -36,17 +36,26 @@ function setOptions(rs){
 				"type":"line",
 				"data":rs.yData[0]			//此处为纵坐标值	
 				};
+		var name=$(me).text()+"(对比)";
+		if($(me).text()== "剑鱼用户(活跃)"){
+			name="(新)剑鱼用户(活跃)";
+		}
 		var s2={
-				"name":$(me).text()+"(对比)",
+				"name":name,
 				"type":"line",
 				"data":rs.yData[1]			//此处为纵坐标值	
 			};
 		xAxis.push(x1);
 		series.push(s1);
-		if(check.prop("checked")&& rs.xData1.length>0) {
+		if(check.prop("checked")&& rs.xData1.length>0 || $(me).text()== "剑鱼用户(活跃)") {
 			xAxis.push(x2);
 			series.push(s2);
-			legend.push($(me).text()+"(对比)");
+			if ($(me).text()== "剑鱼用户(活跃)"){
+				legend.push("(新)剑鱼用户(活跃)");
+			}else{
+				legend.push($(me).text()+"(对比)");
+			}
+			
 		}
 		 option = {
 			tooltip : {
@@ -74,6 +83,11 @@ function setOptions(rs){
 }
 function loadData(me){
 	if(me){
+		if($(me).text()=="剑鱼用户(活跃)"){
+			$("#comparecheck").parent().hide();
+		}else{
+			$("#comparecheck").parent().show();
+		}
 		$("#comparetype").val($(me).attr("id"));
 	}
 	var data={};

+ 7 - 0
core/src/web/staticres/js/qfw.js

@@ -751,6 +751,13 @@ function processAlterInfo(alterInfo){
 			altDate = $.trim(altDate);
 			if(altDate == "" || altDate == "null" || altDate == "空"){
 				altDate = null;
+			}else{
+				var altDateTmp = altDate;
+				try{
+					altDate = new Date(altDate).FormatEnhance("yyyy-mm-dd HH:MM:ss");
+				 }catch(e){
+					altDate = altDateTmp;
+				 }
 			}
 		}
 		object.AltDate = altDate;

+ 45 - 1
core/src/web/staticres/wxswordfish/style.css

@@ -706,4 +706,48 @@ img{
 	top: 0px;
 	bottom: 0px;
 	display: none;
-}
+}
+.hidden{
+	display:none;
+}
+.text-center{
+	text-align:center;
+}
+.sf_promptone{
+	padding:40px 20px 0px 10px;
+}
+.sf_promptone div{
+	width:100%;
+}
+.sf_font1{
+	color:#999;
+	text-align:left;
+}
+.sf_promptone img{
+	width: 60%;
+}
+.sf_button span{
+    border: 1px solid #24C0D7;
+    display: inline-block;
+    width: 20%;
+    padding: 7px;
+    border-radius: 6px;
+	margin-bottom:20px;
+}
+.sf_tis{
+	text-align:center;
+	padding:20px;
+}
+.sf_tis img{
+	text-align:center;
+	width:60%;
+}
+.sf_mc a{
+	text-decoration:none;
+	color:blue;
+}
+
+
+
+
+

+ 1 - 1
core/src/web/templates/common/bottom.html

@@ -12,7 +12,7 @@
 		<div class="text-center hidden-xs"><img src="{{Msg "seo" "cdn"}}/images/bottom_logo.png"/></div>
 		<div class="qfw-bottom-text text-center">
 			<a href="/front/web_A_help.html">关于我们</a><span>|</span><a href="/front/web_L_help.html">联系我们</a><span>|</span>&nbsp;&nbsp;<span style="color:white">法律声明&nbsp;&nbsp;&nbsp;&nbsp;</span><span>|</span><a href="/front/web_H_help.html">帮助中心</a><span>|</span><a href="/front/web_O_help.html">意见反馈</a>
-			<div style="line-height:25px">©2015-2016&nbsp;拓普丰联&nbsp;版权所有<span>&nbsp;|&nbsp;</span><a href="http://www.miibeian.gov.cn/state/outPortal/loginPortal.action" target="_blank">豫ICP备15016539号-1</a></div>
+			<div style="line-height:25px">©2015-2016&nbsp;拓普丰联&nbsp;版权所有<span>&nbsp;|&nbsp;</span><a href="http://www.miibeian.gov.cn/state/outPortal/loginPortal.action" target="_blank">京ICP备14030217号-2</a></div>
 		</div>
 	</div>
 </div>

+ 3 - 3
core/src/web/templates/enterprise/detail.html

@@ -204,7 +204,7 @@
 							<span>
 								<div>
 									<span id="entinfo-status" class="entinfo-status">
-										{{if .T.res.excDirect}}
+										{{if .T.res.Ycml}}
 										<span class="ent-exception">经营异常</span>
 										{{end}}
 									</span>
@@ -284,8 +284,8 @@
 					  	<div class="entinfo-entscale">
 							<img src="{{Msg "seo" "cdn"}}/images/entcommunity/rens.png">
 							<div>
-								<span class="entinfo-disabled">公司规模</span>
-								<span></span>
+								<span{{if not .T.res.i_scale}} class="entinfo-disabled"{{end}}>公司规模</span>
+								<span>{{if .T.res.i_scale}}{{.T.res.i_scale}}人{{end}}</span>
 							</div>
 						</div>
 						<a class="entinfo-entscale-round"></a>

+ 3 - 16
core/src/web/templates/manage/tj.html

@@ -110,7 +110,7 @@
 						</tr>
 						<tr>
 							<td></td>
-							<td>剑鱼:<span class="number">{{.T.rs.sfish}}</span> &nbsp;&nbsp;<a  href="javascript:void(0);" onclick="controlShowOrHide(this)" value="用户订阅详细" data-set="countfish">用户订阅详细</a></td>
+							<td>剑鱼:<span class="number"><script>document.write({{.T.rs.snfish}}+{{.T.rs.sfish}});</script></span> &nbsp;&nbsp;</td>
 							<td>订阅关键词:<span class="number">{{.T.msgcount}}</span>,<a href="javascript:void(0);" onclick="controlShowOrHide(this)" value="关键词汇总表" data-set="f-keytable">关键词汇总表</a></td>
 							<td></td>
 							<td></td>
@@ -123,23 +123,10 @@
 				</div>
 				<div class="showtop f-keytable lm" style="margin-top:20px;display:none;">
 					<div style="text-align:right;margin-bottom:10px;color:#ff5a5f;">
-						招标订阅关键字
+						订阅关键字
 					</div>
 					<ul>
-						{{range $k,$v:=.T.tender}}
-							<li>
-							{{$k}}(<span>{{$v}}</span>)
-							</li>
-						{{end}}
-					</ul>
-					<div style="clear:both;">
-						<hr />
-					</div>
-					<div style="text-align:right;margin-bottom:10px;color:#ff5a5f;">
-						中标订阅关键字
-					</div>
-					<ul>
-						{{range $k,$v:=.T.bid}}
+						{{range $k,$v:=.T.keys}}
 							<li>
 							{{$k}}(<span>{{$v}}</span>)
 							</li>

+ 5 - 5
core/src/web/templates/member/incmobile/baseinfo.html

@@ -91,13 +91,13 @@
 			</div>
 			<span class="col-xs-7 entvar ">{{if .T.res.RegOrgName}}{{.T.res.RegOrgName}}{{else}}空{{end}}</span>
 		</li>
-<!--		<li>
+		<li>
 			<div class="col-xs-5">
-				<span class="glyphicon glyphicon-flag colorc"></span>
-				<span class="ent entnull">公司规模</span>
+				<img src="{{Msg "seo" "cdn"}}/images/entcommunity/rens.png"  width=23 />
+				<span class="ent {{if not .T.res.i_scale}}entnull{{end}}">公司规模</span>
 			</div>
-			<span class="col-xs-7 entvar">空</span>
-		</li>-->
+			<span class="col-xs-7 entvar">{{if .T.res.i_scale}}{{.T.res.i_scale}}人{{else}}{{end}}</span>
+		</li>
 		<li>
 			<div class="col-xs-12">
 				<img src="{{Msg "seo" "cdn"}}/images/entcommunity/ren.png"  width=23 />

+ 1 - 1
core/src/web/templates/member/incmobile/detailindex.html

@@ -129,7 +129,7 @@
 					
 				</script>
 				</div>
-				{{if .T.res.excDirect}}<span class="excDirect">经营异常</span>{{end}}
+				{{if .T.res.Ycml}}<span class="excDirect">经营异常</span>{{end}}
 			</div>
 		</div>
 		<div class="col-xs-12 index-middle">

+ 4 - 4
core/src/web/templates/member/incmobile/list.html

@@ -154,7 +154,7 @@
 		var data={{.T.data}}
 		var entcard={{.T.querymap.entcard}};
 		var strVar = "";
-		strVar += "<li class=\"col-xs-12\"><table align=\"center\"><caption><div class=\"left\"><a href=\"/enterprise/$Id.html\">$EntName</a><b class=\"$status\" style='display:inline-block;'>$OpStateName<\/b><b class=\"$excClass\" style='display:inline-block;'>$excDirect<\/b><\/div><div style='float:right;margin-right:10px;' data-id='$Id' onclick='follow(this)'><i class='glyphicon $gz' style=\"font-size:11px;\">&nbsp;<\/i><span>$gzt</span><\/div><\/caption><tr><td class=\"left\">注册号:<span>$RegNo<\/span><\/td><td align='right'>法定代表人:<span>$LeRep<\/span><\/td><\/tr><tr><td class=\"left\">成立时间:<span>$EstDate<\/span><\/td><td align='right'>所在地:<span>$OpLocDistrict<\/span><\/td><\/tr><tr><td colspan=\"2\"><hr class=\"list\"><\/td><\/tr><\/table><\/li>";
+		strVar += "<li class=\"col-xs-12\"><table align=\"center\"><caption><div class=\"left\"><a href=\"/enterprise/$Id.html\">$EntName</a><b class=\"$status\" style='display:inline-block;'>$OpStateName<\/b><b class=\"$excClass\" style='display:inline-block;'>$Ycml<\/b><\/div><div style='float:right;margin-right:10px;' data-id='$Id' onclick='follow(this)'><i class='glyphicon $gz' style=\"font-size:11px;\">&nbsp;<\/i><span>$gzt</span><\/div><\/caption><tr><td class=\"left\">注册号:<span>$RegNo<\/span><\/td><td align='right'>法定代表人:<span>$LeRep<\/span><\/td><\/tr><tr><td class=\"left\">成立时间:<span>$EstDate<\/span><\/td><td align='right'>所在地:<span>$OpLocDistrict<\/span><\/td><\/tr><tr><td colspan=\"2\"><hr class=\"list\"><\/td><\/tr><\/table><\/li>";
 		function loadData(){
 			   var li="";
 				var len=i+5;
@@ -178,8 +178,8 @@
 											 .replace("$RegNo",data[i]["RegNo"])
 											 .replace("$LeRep",data[i]["LeRep"])
 											 .replace("$OpLocDistrict",data[i]["OpLocDistrict"])
-											 .replace("$EstDate",data[i]["EstDate"]=="1970-01-01"?"":data[i]["EstDate"])
-											 .replace("$excDirect",data[i]["excDirect"]=="true"?"经营异常":"$excDirect")
+											 .replace("$EstDate",data[i]["EstDate"])
+											 .replace("$Ycml",data[i]["Ycml"]=="true"?"经营异常":"$Ycml")
 											 .replace("$Id",data[i]["_id"])
 											 .replace("$Id",data[i]["_id"])
 											 .replace("$OpStateName",data[i]["OpStateName"]=="<nil>"?"":data[i]["OpStateName"].substr(0,2));
@@ -188,7 +188,7 @@
 					}else{
 						tmp=tmp.replace("$status","ent-status-g");
 					}
-					tmp=tmp.replace("<b class=\"$excClass\" style='display:inline-block;'>$excDirect<\/b>","");
+					tmp=tmp.replace("<b class=\"$excClass\" style='display:inline-block;'>$Ycml<\/b>","");
 					tmp=tmp.replace("$excClass","excDirect");
 					if(data[i]["status"]=="y"){
 						tmp=tmp.replace("$gz","yiguanzhu").replace("$gzt","已关注");	

+ 1 - 1
core/src/web/templates/search/enterpriseList.html

@@ -89,7 +89,7 @@
 									</script>
 									</span>
 									{{end}}
-									{{if $v.excDirect}}
+									{{if $v.Ycml}}
 									<span class="margin-l-5 ent-exception">经营异常</span>
 									{{end}}
 									{{ if index $v "s_action"}}

+ 39 - 0
core/src/web/templates/swordfish/wxex.html

@@ -0,0 +1,39 @@
+<html>
+<head>
+<title>企明星-剑鱼-提醒页面</title>
+<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" />
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="renderer" content="webkit">
+<link href="{{Msg "seo" "cdn"}}/wxswordfish/style.css" rel="stylesheet">
+<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
+<script src="{{Msg "seo" "cdn"}}/js/jquery.js"></script>
+<script>
+	var newsubscribe = {{.T.newsubscribe}};
+	$(function(){
+		if (newsubscribe == "Y"){	
+		window.location.href="http://mp.weixin.qq.com/s?__biz=MzIyNTM1NDUyNw==&mid=100000024&idx=1&sn=c2f4271a361bfb90b22abb1d6818accc#rd";
+		return false;
+		}else{
+			$(".sf_promptone").removeClass("hidden");
+		};
+		$(".sf_button").click(function(){
+			window.location.href="/swordfish/page";
+		});
+	})
+</script>
+</head>
+<body>
+<div class="sf_promptone text-center hidden">
+
+<div class="sf_font1">  为了提供更好的服务,剑鱼招标订阅开通了独立的微信公众号,企明星公众号中的剑鱼很快将不再更新。</div>
+<br>
+<div>请长按下面的图片、选择“识别二维码”。</div>
+<div><img src="/images/swordfish/sf_01.jpg" alt="剑鱼招标订阅微信公众号"></div>
+<div>关注“剑鱼招标订阅”</div>
+<br>
+<div class="sf_font1">  您不用担心原订阅记录会消失,只要扫码关注新服务号,订阅关键词依然还在,招标信息将会通过新号继续向您推送,使用剑鱼将不再消耗积分。您在启用新服务号并成功订阅后,原“企明星”的订阅服务将会自动关闭。</div>
+<br>
+<div class="sf_button text-center"><span>跳 过</span></div>
+</div>
+</body>
+</html>

+ 11 - 0
core/src/web/templates/swordfish/wxpush.html

@@ -177,6 +177,17 @@ a{
 </style>
 </head>
 <body>
+<div class="sf_tis">
+<div>长按图片、选择“识别二维码”</div>
+<br>
+<div>关注“剑鱼招标订阅”</div>
+<div><img src="/images/swordfish/sf_01.jpg"/></div>
+<br>
+<div style="text-align:left;">  您不用担心原订阅记录会消失,只要扫码关注新服务号,订阅关键词依然还在,招标信息将会通过新号继续向您推送,使用剑鱼将不再消耗积分。您在启用新服务号并成功订阅后,原“企明星”的订阅服务将会自动关闭。</div>
+
+
+
+</div>
 {{include "/swordfish/wxtoolbar.html"}}
 {{include "/common/baiducc.html"}}
 </body>

+ 2 - 2
core/src/web/templates/swordfish/wxrssset.html

@@ -21,8 +21,8 @@ if(sessionStorage&&sessionStorage.version=="1"){
 <script>
 	var msgset= {{.T.msgset}};
 	initShare({{.T.signature}},{{.T.shareid}});
-	var searchname = {{.T.searchname}}
-	var s_type = {{.T.s_type}}
+	var searchname = {{.T.searchname}};
+	var s_type = {{.T.s_type}};
 </script>
 </head>
 <body>

+ 1 - 2
push/src/config.json

@@ -1,2 +1 @@
-{"bid":{"interval":60,"lastpushtime":"2015-10-10 10:10:10"},"bidStartTime":"2015-12-30 17:28:48","bidTitle":"亲!剑鱼为您速报最新鲜的中标信息啦","bidViewDomain":"192.168.3.132","durationMinutes":10,"fixPush":"oJULtwzXo6EFV1Ah-XeyRBimXGM8","mail_bid":"\u003cdiv\u003e%s\u003c/div\u003e,想了解更多信息,请访问http://www.qimingxing.info。","maxPushSize":50,"mgoAddr":"192.168.3.18:27080","mgoSize":10,"province":{"上海":9,"云南":25,"内蒙古":5,"北京":1,"台湾":32,"吉林":7,"四川":13,"天津":2,"宁夏":30,"安徽":12,"山东":15,"山西":4,"广东":19,"广西":20,"新疆":31,"江苏":10,"江西":14,"河北":3,"河南":16,"浙江":11,"海南":21,"湖北":17,"湖南":18,"澳门":34,"甘肃":28,"福建":13,"西藏":26,"贵州":24,"辽宁":6,"重庆":22,"陕西":27,"青海":29,"香港":33,"黑龙江":8},"pushInfoScopeDays":50,"rpcPort":"8766","smtpAddr":"smtp.exmail.qq.com","smtpFromUser":"企明星","smtpPort":465,"smtpPwd":"qy123456","smtpUser":"qyfw@topnet.net.cn","tenderStartTime":"2016-05-25 18:10:19","tenderTitle":"亲!剑鱼为您速报最新鲜的招标信息啦","weixinRpcServer":"127.0.0.1:82","weixin_bid":"\u003cdiv\u003e%s最新招标信息\u003c/div\u003e\u003cdiv\u003e%s\u003c/div\u003e","wxRpcRemark":"请到网站个人中心查看详细."}
-
+{"bid":{"interval":60,"lastpushtime":"2015-10-10 10:10:10"},"bidStartTime":"2016-05-26 00:00:00","bidTitle":"亲!剑鱼为您速报最新鲜的中标信息啦","bidViewDomain":"192.168.3.132","durationMinutes":10,"fixPush":"oJULtwzXo6EFV1Ah-XeyRBimXGM8","mail_bid":"\u003cdiv\u003e%s\u003c/div\u003e,想了解更多信息,请访问http://www.qimingxing.info。","maxPushSize":50,"mgoAddr":"192.168.3.18:27080","mgoSize":10,"province":{"上海":9,"云南":25,"内蒙古":5,"北京":1,"台湾":32,"吉林":7,"四川":13,"天津":2,"宁夏":30,"安徽":12,"山东":15,"山西":4,"广东":19,"广西":20,"新疆":31,"江苏":10,"江西":14,"河北":3,"河南":16,"浙江":11,"海南":21,"湖北":17,"湖南":18,"澳门":34,"甘肃":28,"福建":13,"西藏":26,"贵州":24,"辽宁":6,"重庆":22,"陕西":27,"青海":29,"香港":33,"黑龙江":8},"pushInfoScopeDays":50,"rpcPort":"8766","smtpAddr":"smtp.exmail.qq.com","smtpFromUser":"企明星","smtpPort":465,"smtpPwd":"qy123456","smtpUser":"qyfw@topnet.net.cn","tenderStartTime":"2016-05-30 17:07:34","tenderTitle":"亲!剑鱼为您速报最新鲜的招标信息啦","weixinRpcServer":"127.0.0.1:82","weixin_bid":"\u003cdiv\u003e%s最新招标信息\u003c/div\u003e\u003cdiv\u003e%s\u003c/div\u003e","wxRpcRemark":"请到网站个人中心查看详细."}

+ 2 - 1
push/src/qfw/push/dopush/dopush.go

@@ -392,7 +392,8 @@ func SendWeixin(k *push.MemberInterest, TITLE, ShortTitle, str, stype string, no
 		wxDate = time.Now().Local().Add(time.Duration(-n2) * time.Second).Add(time.Duration(-n1*24) * time.Hour).Format(util.Date_Full_Layout)
 	}
 
-	wxstr := "\n点击下方“详情”查看详细信息。\n以上" + ShortTitle + "信息,是剑鱼根据关键字“" + strings.Join(k.Interest, ";") + "”奋力查找并推送,如不合您心意,请猛戳企明星菜单“剑鱼”进行修改。"
+	//wxstr := "\n点击下方“详情”查看详细信息。\n以上" + ShortTitle + "信息,是剑鱼根据关键字“" + strings.Join(k.Interest, ";") + "”奋力查找并推送,如不合您心意,请猛戳企明星菜单“剑鱼”进行修改。"
+	wxstr := "\n为了提供更好的服务,剑鱼招标订阅开通了独立的微信公众号,企明星公众号中的剑鱼很快将不再更新,点击“详情”可关注公众号“剑鱼招标订阅”。感谢您的支持!"
 	push.SendWinXin(&qrpc.NotifyMsg{
 		Openid:  k.Openid,
 		Title:   push.PushConfig[stype+"Title"].(string),

BIN
weixin/doc/kfex.rar


+ 10 - 8
weixin/src/config.json

@@ -1,18 +1,18 @@
 {
-	"port":"82",
+	"port":"80",
 	"domain":"127.0.0.1",
 	"imgpath":"E:/go_workspace/qfw/core/src/web/staticres",
-	"mongodbServers": "10.116.86.154:27080",
-	"elasticsearch":"http://10.116.86.154:9800",
+	"mongodbServers": "192.168.3.18:27080",
+	"elasticsearch":"http://192.168.3.18:9800",
     "elasticPoolSize": 30,
 	"mongodbPoolSize":5,
-	"mongodbName":"qfw",	"redisServers":"enterprise=10.116.86.154:1379,service=10.116.86.154:2379,other=10.116.86.154:3379,sso=10.116.86.154:1379,credit=10.116.86.154:4379",
+	"mongodbName":"qfw",	"redisServers":"enterprise=192.168.3.14:1379,service=192.168.3.14:2379,other=192.168.3.14:3379,sso=192.168.3.14:1379,credit=192.168.3.14:4379",
 	"rpcport":"83",
 	"serviceTip":"服务指南",
 	"appcontext":"weixin",
-	"appid":"wx76e1309b01a7b17e",
-	"token":"topnet2015",
-	"appsecret":"dd00e71cb2370432d9de848b674eb8e7",
+	"appid":"wxb8c5625e055bd967",
+	"token":"zzfykc",
+	"appsecret":"288436fdde6349725b4869d54162d3c6",
     "aboutmeurl":"http://www.qimingxing.info/article/aboutme",
     "conactusurl":"http://www.qimingxing.info/article/contactus",
 	"wsqurl": "http://s.p.qq.com/pub/jump?d=AAAXeGLZ",
@@ -54,5 +54,7 @@
 		"out":"您已退出本次工作,辛苦了,再见。",
 		"reply":"已收到您的回复,谢谢。",
 		"msgError":"该功能暂不可用,请稍后再试!"
-	}
+	},
+	"kfprefix":"企明星-",
+	"kfport":"9896"
 }

+ 43 - 4
weixin/src/main.go

@@ -1,8 +1,10 @@
 package main
 
 import (
-	"endless"
+	"encoding/json"
+	//"endless"
 	"log"
+	"net/http"
 	//"net/http"
 	"qfw/util"
 	"qfw/util/elastic"
@@ -32,6 +34,9 @@ func init() {
 	weixin.InitWeixinSdk()
 	//连接消息总线
 	go weixin.InitDgWork()
+	go kf()
+	go weixin.GetKfMsgJob()
+	go weixin.GetOnlineKfJob()
 }
 
 func main() {
@@ -39,8 +44,42 @@ func main() {
 	//启动Rpc服务
 	rpc.StartWeixinRpc(weixin.Mux)
 	//启动web服务
-	//http.ListenAndServe(":"+wf.SysConfig.Port, nil) // 启动接收微信数据服务器
-	endless.ListenAndServe(":"+wf.SysConfig.Port, nil, func() {
-		rpc.DestoryRpc()
+	http.ListenAndServe(":"+wf.SysConfig.Port, nil) // 启动接收微信数据服务器
+	/*
+		endless.ListenAndServe(":"+wf.SysConfig.Port, nil, func() {
+			rpc.DestoryRpc()
+		})
+		**/
+	b := make(chan bool, 1)
+	<-b
+}
+
+func kf() {
+	defer util.Catch()
+	http.HandleFunc("/wxkf/getkflist", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		//内存
+		b, _ := json.Marshal(weixin.GetKfList(10))
+		w.Write(b)
+	})
+	http.HandleFunc("/wxkf/getkflist0", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		//url
+		b, _ := json.Marshal(weixin.GetKfList(0))
+		w.Write(b)
+	})
+	http.HandleFunc("/wxkf/getkflist1", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		//url+客服信息
+		b, _ := json.Marshal(weixin.GetKfList(1))
+		w.Write(b)
+	})
+
+	http.HandleFunc("/msginit/init", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		weixin.GetKfMsg(1)
+		b, _ := json.Marshal(map[string]interface{}{"res": true})
+		w.Write(b)
 	})
+	http.ListenAndServe(":"+wf.SysConfig.Kfport, nil)
 }

+ 1 - 1
weixin/src/qfw/weixin/dao/userdao.go

@@ -92,7 +92,7 @@ func AddUser(openid, unionid, bindweixin, userphoto string) (err error, flag int
 	} else {
 		log.Println("update user:", userphoto)
 		//已经注册过
-		Update("user", M{"s_unionid": unionid, "i_type": 3}, `{"$set":{"s_m_openid":"`+openid+`","s_avatar":"`+userphoto+`"}}`, true, false)
+		Update("user", M{"s_m_openid": openid}, `{"$set":{"s_avatar":"`+userphoto+`"}}`, true, false)
 		return errors.New("该微信号已经注册过"), 0
 	}
 }

+ 1 - 2
weixin/src/qfw/weixin/distinguishwork.go

@@ -49,8 +49,7 @@ func init() {
 }
 
 func InitDgWork() {
-	client, _ = util.StartClient(processevent, wf.SysConfig.Msgserver, []int{util.SERVICE_DISTINGUISH}, 20)
-	client.ResetMyName("识别验证码")
+	client, _ = util.StartClient(processevent, wf.SysConfig.Msgserver, "识别验证码", []int{util.SERVICE_DISTINGUISH})
 }
 func processevent(p *util.Packet) {
 	event := int(p.Event)

+ 8 - 1
weixin/src/qfw/weixin/msgtxtchandler.go

@@ -131,7 +131,14 @@ func MsgTxtHandler(w ResponseWriter, r *Request) {
 			dao.SaveWeixinOfflineMessage(r.FromUserName, r.Content, now.Unix())
 			w.ReplyText(wf.SysConfig.WeixinAutoRpl)
 		} else {
-			w.Reply2CustomerService()
+			//w.Reply2CustomerService()
+			//转接到平台客服
+			s := GetMinKf()
+			if s == "" {
+				w.Reply2CustomerService()
+			} else {
+				w.TransferCustomerService2(r.FromUserName, GetMinKf())
+			}
 		}
 	}
 }

+ 272 - 0
weixin/src/qfw/weixin/weixincustomer.go

@@ -0,0 +1,272 @@
+package weixin
+
+import (
+	"encoding/json"
+	"log"
+	"qfw/util"
+	"qfw/util/mongodb"
+	"qfw/weixinconfig"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+)
+
+//定时获取客服状态
+//客服分类,平台客服 企明星-小微,业务客服 工商注册-小张
+//处理客服类型
+/**
+{
+   "kf_online_list": [
+       {
+           "kf_account": "test1@test",
+           "status": 1,
+           "kf_id": "1001",
+           "accepted_case": 1
+       },
+       {
+           "kf_account": "test2@test",
+           "status": 1,
+           "kf_id": "1002",
+           "accepted_case": 2
+       }
+   ]
+}
+**/
+
+//通过url获取在线客服信息
+func GetKfList(t int) []map[string]interface{} {
+	if t == 10 {
+		return ONLINE_KF
+	}
+	defer util.Catch()
+	ONLINE_KF_LOCK.Lock()
+	defer ONLINE_KF_LOCK.Unlock()
+	defer util.Catch()
+	l := Mux.GetOnlineKfList()
+	kf_online_list := l["kf_online_list"].([]interface{})
+	QMX_ONLINE_KF = arr{}
+	ONLINE_KF = []map[string]interface{}{}
+	if len(kf_online_list) > 0 {
+		GetKfInfoByMap(t)
+		for _, onlist := range kf_online_list {
+			tmp := onlist.(map[string]interface{})
+			kf_account := tmp["kf_account"].(string)
+			onekf := KF_INFO[kf_account]
+			if onekf == nil {
+				log.Println("数据库客服列表不完整...")
+			} else {
+				for k, v := range onekf {
+					tmp[k] = v
+				}
+			}
+			kf_nick := tmp["kf_nick"].(string)
+			if strings.HasPrefix(kf_nick, weixinconfig.SysConfig.Kfprefix) {
+				k := map[string]interface{}{}
+				k["kf_account"] = tmp["kf_account"].(string)
+				k["accepted_case"] = util.IntAll(tmp["accepted_case"])
+				QMX_ONLINE_KF = append(QMX_ONLINE_KF, k)
+			}
+			ONLINE_KF = append(ONLINE_KF, tmp)
+		}
+	}
+	//排序
+	sort.Sort(&QMX_ONLINE_KF)
+	//log.Println(res)
+	return ONLINE_KF
+}
+
+//定时更新在线客服
+func GetOnlineKfJob() {
+	hour := time.Now().Hour()
+	if hour > 6 && hour < 23 {
+		GetKfList(0)
+	}
+	time.AfterFunc(1*time.Minute, GetOnlineKfJob)
+}
+
+//获取最少的客服
+func GetMinKf() string {
+	defer util.Catch()
+	ONLINE_KF_LOCK.Lock()
+	defer ONLINE_KF_LOCK.Unlock()
+	res := ""
+	if QMX_ONLINE_KF.Len() > 0 {
+		res = QMX_ONLINE_KF[0]["kf_account"].(string)
+	}
+	return res
+}
+
+type arr []map[string]interface{}
+
+func (a *arr) Len() int {
+	return len(*a)
+}
+func (a *arr) Less(i, j int) bool {
+	return (*a)[i]["accepted_case"].(int) < (*a)[j]["accepted_case"].(int)
+}
+func (a *arr) Swap(i, j int) {
+	tmp := (*a)[i]
+	(*a)[i] = (*a)[j]
+	(*a)[j] = tmp
+}
+
+/**
+{
+    "kf_list" : [
+       {
+          "kf_account" : "test1@test",
+          "kf_headimgurl" : "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjfUS8Ym0GSaLic0FD3vN0V8PILcibEGb2fPfEOmw/0",
+          "kf_id" : "1001",
+          "kf_nick" : "ntest1",
+          "kf_wx" : "kfwx1"
+       }]
+}
+**/
+const (
+	KF_COLLECTION    = "wxkf_info"
+	KFMSG_COLLECTION = "wxkf_msg"
+)
+
+var Last_kf_time = int64(0) //获取客服的最后时间
+var KF_INFO = map[string]map[string]interface{}{}
+var KF_LOCK = sync.Mutex{}                 //客服信息锁
+var ONLINE_KF_LOCK = sync.Mutex{}          //在线客服锁
+var ONLINE_KF = []map[string]interface{}{} //所有在线客服
+var QMX_ONLINE_KF = arr{}                  //平台在线客服
+var MSG_LOCL = sync.Mutex{}                //客服消息锁
+var Last_msg_time = int64(0)               //获取聊天记录的最后时间
+
+//获取客服信息
+func GetKfInfoByApi() {
+	defer util.Catch()
+	KF_LOCK.Lock()
+	defer KF_LOCK.Unlock()
+	if time.Now().Unix()-Last_kf_time > 60 {
+		Last_kf_time = time.Now().Unix()
+		l := Mux.GetKfList()
+		if l != nil && len(l) > 0 {
+			kf_list := l["kf_list"].([]interface{})
+			if len(kf_list) > 0 {
+				KF_INFO = map[string]map[string]interface{}{}
+				for _, kf := range kf_list {
+					onekf := kf.(map[string]interface{})
+					kf_account := onekf["kf_account"].(string)
+					KF_INFO[kf_account] = onekf
+				}
+				if mongodb.Del(KF_COLLECTION, nil) {
+					if !mongodb.SaveBulk(KF_COLLECTION, util.ObjArrToMapArr(kf_list)...) {
+						time.Sleep(10 * time.Second)
+						mongodb.SaveBulk(KF_COLLECTION, util.ObjArrToMapArr(kf_list)...)
+					}
+				}
+			}
+		}
+	}
+}
+
+//从内存中或Url获取客服信息/0从内存 /1从url
+func GetKfInfoByMap(t int) map[string]map[string]interface{} {
+	if t == 0 {
+		if len(KF_INFO) == 0 {
+			KF_LOCK.Lock()
+			defer KF_LOCK.Unlock()
+			res := mongodb.Find(KF_COLLECTION, nil, nil, nil, false, -1, -1)
+			if res != nil && *res != nil && len(*res) > 0 {
+				for _, kf := range *res {
+					kf_account := kf["kf_account"].(string)
+					KF_INFO[kf_account] = kf
+				}
+			} else {
+				GetKfInfoByApi()
+			}
+		}
+	} else if t == 1 {
+		GetKfInfoByApi()
+	}
+	return KF_INFO
+}
+
+type Msg struct {
+	MsgId     int64 `json:"msgid"`
+	StartTime int64 `json:"starttime"`
+	EndTime   int64 `json:"endtime"`
+	Number    int   `json:"number"`
+}
+
+//获取客服聊天记录
+//查询时间不能超过24小时
+//t=0 正常 t=1初始化取24小时
+func GetKfMsg(t int) {
+	defer util.Catch()
+	MSG_LOCL.Lock()
+	defer MSG_LOCL.Unlock()
+	now := time.Now().Unix() - 3*60
+	if Last_msg_time == 0 {
+		res := mongodb.Find(KFMSG_COLLECTION, nil, `{"l_gettime":-1}`, `{"l_gettime":1}`, false, 0, 1)
+		if res != nil && *res != nil && len(*res) == 1 {
+			Last_msg_time = util.Int64All((*res)[0]["l_gettime"])
+		}
+		if Last_msg_time == 0 || now-Last_msg_time > 86400 {
+			if t == 1 {
+				Last_msg_time = now - 86400
+			} else {
+				log.Println("请检查客服信息初始化..")
+				return
+			}
+		}
+	}
+	msg := Msg{}
+	msg.MsgId = 1
+	msg.Number = 2000
+	msg.EndTime = now
+	msg.StartTime = Last_msg_time
+	for i := 0; i < 15; i++ {
+		bs, err := Mux.PostCustomMsg("https://api.weixin.qq.com/customservice/msgrecord/getmsglist?access_token=", msg)
+		if err == nil {
+			var res map[string]interface{}
+			_err := json.Unmarshal(bs, &res)
+			if _err == nil && res != nil {
+				recordlist := res["recordlist"].([]interface{})
+				recMap := []map[string]interface{}{}
+				for _, record := range recordlist {
+					tmp := record.(map[string]interface{})
+					tmp["l_gettime"] = now
+					tmp["opercode"] = util.IntAll(tmp["opercode"])
+					recMap = append(recMap, tmp)
+				}
+				//保存客服消息
+				if len(recMap) > 0 {
+					if !mongodb.SaveBulk(KFMSG_COLLECTION, recMap...) {
+						break
+					}
+				}
+				num := util.IntAll(res["number"])
+				mid := util.Int64All(res["msgid"])
+				log.Println(num, mid, i, len(recMap))
+				if num < msg.Number {
+					Last_msg_time = now
+					break
+				} else {
+					msg.MsgId = mid
+				}
+			} else {
+				log.Println(_err)
+				break
+			}
+		} else {
+			log.Println(err)
+			break
+		}
+		time.Sleep(1 * time.Second)
+	}
+}
+
+//定时抓取客服记录
+func GetKfMsgJob() {
+	hour := time.Now().Hour()
+	if hour > 6 && hour < 20 {
+		GetKfMsg(0)
+	}
+	time.AfterFunc(15*time.Minute, GetKfMsgJob)
+}

+ 178 - 106
weixin/src/qfw/weixin/weixinsdk.go

@@ -18,7 +18,7 @@ import (
 	"path/filepath"
 	"regexp"
 	"sort"
-	"strings"
+	"sync/atomic"
 	"time"
 )
 
@@ -27,7 +27,7 @@ const (
 	msgEvent          = "event"
 	EventSubscribe    = "subscribe"
 	EventUnsubscribe  = "unsubscribe"
-	EventScan         = "scan"
+	EventScan         = "SCAN"
 	EventView         = "VIEW"
 	EventClick        = "CLICK"
 	EventLocation     = "LOCATION"
@@ -78,6 +78,7 @@ const (
 	weixinHost               = "https://api.weixin.qq.com/cgi-bin"
 	weixinQRScene            = "https://api.weixin.qq.com/cgi-bin/qrcode"
 	weixinShowQRScene        = "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+	weixinMaterialURL        = "https://api.weixin.qq.com/cgi-bin/material"
 	weixinShortURL           = "https://api.weixin.qq.com/cgi-bin/shorturl"
 	weixinUserInfo           = "https://api.weixin.qq.com/cgi-bin/user/info"
 	weixinFileURL            = "http://file.api.weixin.qq.com/cgi-bin/media"
@@ -88,17 +89,19 @@ const (
 	// Max retry count
 	retryMaxN = 3
 	// Reply format
-	replyText               = "<xml>%s<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>"
-	replyImage              = "<xml>%s<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"
-	replyVoice              = "<xml>%s<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>"
-	replyVideo              = "<xml>%s<MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>"
-	replyMusic              = "<xml>%s<MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl><ThumbMediaId><![CDATA[%s]]></ThumbMediaId></Music></xml>"
-	replyNews               = "<xml>%s<MsgType><![CDATA[news]]></MsgType><ArticleCount>%d</ArticleCount><Articles>%s</Articles></xml>"
-	replyHeader             = "<ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%d</CreateTime>"
-	replyArticle            = "<item><Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>"
-	transferCustomerService = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
-	reply2CustomerService   = "<xml>%s<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
-
+	replyText                = "<xml>%s<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>"
+	replyImage               = "<xml>%s<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"
+	replyVoice               = "<xml>%s<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>"
+	replyVideo               = "<xml>%s<MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>"
+	replyMusic               = "<xml>%s<MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl><ThumbMediaId><![CDATA[%s]]></ThumbMediaId></Music></xml>"
+	replyNews                = "<xml>%s<MsgType><![CDATA[news]]></MsgType><ArticleCount>%d</ArticleCount><Articles>%s</Articles></xml>"
+	replyHeader              = "<ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%d</CreateTime>"
+	replyArticle             = "<item><Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>"
+	transferCustomerService  = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
+	transferCustomerService2 = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType><TransInfo><KfAccount><![CDATA[%s]]></KfAccount></TransInfo></xml>"
+	reply2CustomerService    = "<xml>%s<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
+	// Material request
+	requestMaterial = `{"type":"%s","offset":%d,"count":%d}`
 	// QR scene request
 	requestQRScene      = `{"expire_seconds":%d,"action_name":"QR_SCENE","action_info":{"scene":{"scene_id":%d}}}`
 	requestQRLimitScene = `{"action_name":"QR_LIMIT_SCENE","action_info":{"scene":{"scene_id":%d}}}`
@@ -200,6 +203,38 @@ type UserInfo struct {
 	GroupId       int    `json:"groupid,omitempty"`
 }
 
+type Material struct {
+	MediaId    string `json:"media_id,omitempty"`
+	Name       string `json:"name,omitempty"`
+	UpdateTime int64  `json:"update_time,omitempty"`
+	CreateTime int64  `json:"create_time,omitempty"`
+	Url        string `json:"url,omitempty"`
+	Content    struct {
+		NewsItem []struct {
+			Title            string `json:"title,omitempty"`
+			ThumbMediaId     string `json:"thumb_media_id,omitempty"`
+			ShowCoverPic     int    `json:"show_cover_pic,omitempty"`
+			Author           string `json:"author,omitempty"`
+			Digest           string `json:"digest,omitempty"`
+			Content          string `json:"content,omitempty"`
+			Url              string `json:"url,omitempty"`
+			ContentSourceUrl string `json:"content_source_url,omitempty"`
+		} `json:"news_item,omitempty"`
+	} `json:"content,omitempty"`
+}
+
+type Materials struct {
+	TotalCount int        `json:"total_count,omitempty"`
+	ItemCount  int        `json:"item_count,omitempty"`
+	Items      []Material `json:"item,omitempty"`
+}
+
+type TmplData map[string]TmplItem
+type TmplItem struct {
+	Value string `json:"value,omitempty"`
+	Color string `json:"color,omitempty"`
+}
+
 // Use to output reply
 type ResponseWriter interface {
 	// Get weixin
@@ -208,12 +243,14 @@ type ResponseWriter interface {
 	// Reply message
 	ReplyOK()
 	ReplyText(text string)
-	Reply2CustomerService()
 	ReplyImage(mediaId string)
 	ReplyVoice(mediaId string)
 	ReplyVideo(mediaId string, title string, description string)
 	ReplyMusic(music *Music)
 	ReplyNews(articles []Article)
+
+	Reply2CustomerService()
+
 	TransferCustomerService(serviceId string)
 	// Post message
 	PostText(text string) error
@@ -222,12 +259,14 @@ type ResponseWriter interface {
 	PostVideo(mediaId string, title string, description string) error
 	PostMusic(music *Music) error
 	PostNews(articles []Article) error
-	PostTemplateMessage(templateid string, url string, data interface{}) (string, error)
+	PostTemplateMessage(templateid string, url string, data TmplData) (int32, error)
 	// Media operator
 	UploadMediaFromFile(mediaType string, filepath string) (string, error)
 	DownloadMediaToFile(mediaId string, filepath string) error
 	UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
 	DownloadMedia(mediaId string, writer io.Writer) error
+	//
+	TransferCustomerService2(serviceId, kfaccount string)
 	GetUserBaseInfo(openid string) (map[string]interface{}, error)
 }
 
@@ -262,17 +301,16 @@ type jsApiTicket struct {
 }
 
 type Weixin struct {
-	token      string
-	routes     []*route
-	tokenChan  chan accessToken
-	ticketChan chan jsApiTicket
-	userData   interface{}
-	appId      string
-	appSecret  string
+	token        string
+	routes       []*route
+	tokenChan    chan accessToken
+	ticketChan   chan jsApiTicket
+	userData     interface{}
+	appId        string
+	appSecret    string
+	refreshToken int32
 }
 
-//
-
 // Convert qr scene to url
 func (qr *QRScene) ToURL() string {
 	return (weixinShowQRScene + "?ticket=" + qr.Ticket)
@@ -284,9 +322,10 @@ func New(token string, appid string, secret string) *Weixin {
 	wx.token = token
 	wx.appId = appid
 	wx.appSecret = secret
+	wx.refreshToken = 0
 	if len(appid) > 0 && len(secret) > 0 {
 		wx.tokenChan = make(chan accessToken)
-		go createAccessToken(wx.tokenChan, appid, secret)
+		go wx.createAccessToken(wx.tokenChan, appid, secret)
 		wx.ticketChan = make(chan jsApiTicket)
 		go createJsApiTicket(wx.tokenChan, wx.ticketChan)
 	}
@@ -307,6 +346,11 @@ func (wx *Weixin) GetAppSecret() string {
 	return wx.appSecret
 }
 
+func (wx *Weixin) RefreshAccessToken() {
+	atomic.StoreInt32(&wx.refreshToken, 1)
+	<-wx.tokenChan
+}
+
 // Register request callback.
 func (wx *Weixin) HandleFunc(pattern string, handler HandlerFunc) {
 	regex, err := regexp.Compile(pattern)
@@ -333,28 +377,6 @@ func (wx *Weixin) PostText(touser string, text string) error {
 	return postMessage(wx.tokenChan, &msg)
 }
 
-//Post custom message(消息结构体完全由开发人员自定义)
-func (wx *Weixin) PostTextCustom(url string, obj interface{}) error {
-	data, err := json.Marshal(obj)
-	if err != nil {
-		return err
-	}
-	//log.Println("custom msg:", string(data))
-	_, err = postRequest(url, wx.tokenChan, data)
-	return err
-}
-
-//Post custom message(消息结构体完全由开发人员自定义)
-func (wx *Weixin) PostCustomMsg(url string, obj interface{}) (bs []byte, err error) {
-	var data []byte
-	data, err = json.Marshal(obj)
-	if err != nil {
-		return
-	}
-	bs, err = postRequest(url, wx.tokenChan, data)
-	return
-}
-
 // Post image message
 func (wx *Weixin) PostImage(touser string, mediaId string) error {
 	var msg struct {
@@ -462,6 +484,20 @@ func (wx *Weixin) DownloadMedia(mediaId string, writer io.Writer) error {
 	return downloadMedia(wx.tokenChan, mediaId, writer)
 }
 
+// Batch Get Material
+func (wx *Weixin) BatchGetMaterial(materialType string, offset int, count int) (*Materials, error) {
+	reply, err := postRequest(weixinMaterialURL+"/batchget_material?access_token=", wx.tokenChan,
+		[]byte(fmt.Sprintf(requestMaterial, materialType, offset, count)))
+	if err != nil {
+		return nil, err
+	}
+	var materials Materials
+	if err := json.Unmarshal(reply, &materials); err != nil {
+		return nil, err
+	}
+	return &materials, nil
+}
+
 // Get ip list
 func (wx *Weixin) GetIpList() ([]string, error) {
 	reply, err := sendGetRequest(weixinHost+"/getcallbackip?access_token=", wx.tokenChan)
@@ -530,16 +566,12 @@ func (wx *Weixin) ShortURL(url string) (string, error) {
 
 // Custom menu
 func (wx *Weixin) CreateMenu(menu *Menu) error {
-	data, err := json.Marshal(menu)
+	data, err := marshal(menu)
 	if err != nil {
 		return err
-	} else {
-		tmp := string(data)
-		tmp = strings.Replace(tmp, "\\u0026", "&", -1)
-		log.Println("MenuJson", string(tmp))
-		_, err := postRequest(weixinHost+"/menu/create?access_token=", wx.tokenChan, []byte(tmp))
-		return err
 	}
+	_, err = postRequest(weixinHost+"/menu/create?access_token=", wx.tokenChan, data)
+	return err
 }
 
 func (wx *Weixin) GetMenu() (*Menu, error) {
@@ -599,12 +631,12 @@ func (wx *Weixin) AddTemplate(shortid string) (string, error) {
 	return templateId.Id, nil
 }
 
-func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url string, data interface{}) (string, error) {
+func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url string, data TmplData) (int32, error) {
 	var msg struct {
-		ToUser     string      `json:"touser"`
-		TemplateId string      `json:"template_id"`
-		Url        string      `json:"url,omitempty"`
-		Data       interface{} `json:"data,omitempty"`
+		ToUser     string   `json:"touser"`
+		TemplateId string   `json:"template_id"`
+		Url        string   `json:"url,omitempty"`
+		Data       TmplData `json:"data,omitempty"`
 	}
 	msg.ToUser = touser
 	msg.TemplateId = templateid
@@ -612,17 +644,17 @@ func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url stri
 	msg.Data = data
 	msgStr, err := marshal(msg)
 	if err != nil {
-		return "", err
+		return 0, err
 	}
 	reply, err := postRequest(weixinHost+"/message/template/send?access_token=", wx.tokenChan, msgStr)
 	if err != nil {
-		return "", err
+		return 0, err
 	}
 	var resp struct {
-		MsgId string `json:"msgid,omitempty"`
+		MsgId int32 `json:"msgid,omitempty"`
 	}
 	if err := json.Unmarshal(reply, &resp); err != nil {
-		return "", err
+		return 0, err
 	}
 	return resp.MsgId, nil
 }
@@ -708,7 +740,6 @@ func (wx *Weixin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		log.Println("Weixin receive message failed:", err)
 		http.Error(w, "", http.StatusBadRequest)
 	} else {
-		//log.Println("recive data:", string(data))
 		var msg Request
 		if err := xml.Unmarshal(data, &msg); err != nil {
 			log.Println("Weixin parse message failed:", err)
@@ -764,7 +795,6 @@ func checkSignature(t string, w http.ResponseWriter, r *http.Request) bool {
 	}
 	h := sha1.New()
 	h.Write([]byte(str))
-	//fmt.Printf("checksignature:%s %s \n %s %s \n", str, timestamp, signature, nonce)
 	return fmt.Sprintf("%x", h.Sum(nil)) == signature
 }
 
@@ -812,11 +842,12 @@ func getJsApiTicket(c chan accessToken) (*jsApiTicket, error) {
 
 }
 
-func createAccessToken(c chan accessToken, appid string, secret string) {
+func (wx *Weixin) createAccessToken(c chan accessToken, appid string, secret string) {
 	token := accessToken{"", time.Now()}
 	c <- token
 	for {
-		if time.Since(token.expires).Seconds() >= 0 {
+		swapped := atomic.CompareAndSwapInt32(&wx.refreshToken, 1, 0)
+		if swapped || time.Since(token.expires).Seconds() >= 0 {
 			var expires time.Duration
 			token.token, expires = authAccessToken(appid, secret)
 			token.expires = time.Now().Add(expires)
@@ -843,7 +874,6 @@ func sendGetRequest(reqURL string, c chan accessToken) ([]byte, error) {
 	for i := 0; i < retryMaxN; i++ {
 		token := <-c
 		if time.Since(token.expires).Seconds() < 0 {
-			log.Println("token:", token.token)
 			r, err := http.Get(reqURL + token.token)
 			if err != nil {
 				return nil, err
@@ -856,15 +886,14 @@ func sendGetRequest(reqURL string, c chan accessToken) ([]byte, error) {
 			var result response
 			if err := json.Unmarshal(reply, &result); err != nil {
 				return nil, err
-			} else {
-				switch result.ErrorCode {
-				case 0:
-					return reply, nil
-				case 42001: // access_token timeout and retry
-					continue
-				default:
-					return nil, errors.New(fmt.Sprintf("WeiXin send get request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
-				}
+			}
+			switch result.ErrorCode {
+			case 0:
+				return reply, nil
+			case 42001: // access_token timeout and retry
+				continue
+			default:
+				return nil, errors.New(fmt.Sprintf("WeiXin send get request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
 			}
 		}
 	}
@@ -877,29 +906,24 @@ func postRequest(reqURL string, c chan accessToken, data []byte) ([]byte, error)
 		if time.Since(token.expires).Seconds() < 0 {
 			r, err := http.Post(reqURL+token.token, "application/json; charset=utf-8", bytes.NewReader(data))
 			if err != nil {
-				log.Println("err1", err.Error())
 				return nil, err
 			}
 			defer r.Body.Close()
 			reply, err := ioutil.ReadAll(r.Body)
-			log.Println("repl:", string(reply))
 			if err != nil {
-				log.Println("err2", err.Error())
 				return nil, err
 			}
 			var result response
 			if err := json.Unmarshal(reply, &result); err != nil {
-				log.Println("err3", err.Error())
 				return nil, err
-			} else {
-				switch result.ErrorCode {
-				case 0:
-					return reply, nil
-				case 42001: // access_token timeout and retry
-					continue
-				default:
-					return nil, errors.New(fmt.Sprintf("WeiXin send post request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
-				}
+			}
+			switch result.ErrorCode {
+			case 0:
+				return reply, nil
+			case 42001: // access_token timeout and retry
+				continue
+			default:
+				return nil, errors.New(fmt.Sprintf("WeiXin send post request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
 			}
 		}
 	}
@@ -907,7 +931,7 @@ func postRequest(reqURL string, c chan accessToken, data []byte) ([]byte, error)
 }
 
 func postMessage(c chan accessToken, msg interface{}) error {
-	data, err := json.Marshal(msg)
+	data, err := marshal(msg)
 	if err != nil {
 		return err
 	}
@@ -949,15 +973,14 @@ func uploadMedia(c chan accessToken, mediaType string, filename string, reader i
 			err = json.Unmarshal(reply, &result)
 			if err != nil {
 				return "", err
-			} else {
-				switch result.ErrorCode {
-				case 0:
-					return result.MediaId, nil
-				case 42001: // access_token timeout and retry
-					continue
-				default:
-					return "", errors.New(fmt.Sprintf("WeiXin upload[%d]: %s", result.ErrorCode, result.ErrorMessage))
-				}
+			}
+			switch result.ErrorCode {
+			case 0:
+				return result.MediaId, nil
+			case 42001: // access_token timeout and retry
+				continue
+			default:
+				return "", errors.New(fmt.Sprintf("WeiXin upload[%d]: %s", result.ErrorCode, result.ErrorMessage))
 			}
 		}
 	}
@@ -1024,11 +1047,6 @@ func (w responseWriter) ReplyText(text string) {
 	msg := fmt.Sprintf(replyText, w.replyHeader(), text)
 	w.writer.Write([]byte(msg))
 }
-func (w responseWriter) Reply2CustomerService() {
-	msg := fmt.Sprintf(reply2CustomerService, w.replyHeader(), "")
-	//log.Println("repl msg:", msg)
-	w.writer.Write([]byte(msg))
-}
 
 // Reply image message
 func (w responseWriter) ReplyImage(mediaId string) {
@@ -1070,6 +1088,12 @@ func (w responseWriter) TransferCustomerService(serviceId string) {
 	w.writer.Write([]byte(msg))
 }
 
+func (w responseWriter) TransferCustomerService2(serviceId, kfaccount string) {
+	msg := fmt.Sprintf(transferCustomerService2, serviceId, w.fromUserName, time.Now().Unix(), kfaccount)
+	log.Println("send custom msg:::", msg)
+	w.writer.Write([]byte(msg))
+}
+
 // Post text message
 func (w responseWriter) PostText(text string) error {
 	return w.wx.PostText(w.toUserName, text)
@@ -1101,7 +1125,7 @@ func (w responseWriter) PostNews(articles []Article) error {
 }
 
 // Post template message
-func (w responseWriter) PostTemplateMessage(templateid string, url string, data interface{}) (string, error) {
+func (w responseWriter) PostTemplateMessage(templateid string, url string, data TmplData) (int32, error) {
 	return w.wx.PostTemplateMessage(w.toUserName, templateid, url, data)
 }
 
@@ -1125,6 +1149,37 @@ func (w responseWriter) DownloadMedia(mediaId string, writer io.Writer) error {
 	return w.wx.DownloadMedia(mediaId, writer)
 }
 
+//
+func (wx *Weixin) PostCustomMsg(url string, obj interface{}) (bs []byte, err error) {
+	var data []byte
+	data, err = json.Marshal(obj)
+	if err != nil {
+		return
+	}
+	bs, err = postRequest(url, wx.tokenChan, data)
+	return
+}
+
+//
+func (wx *Weixin) GetKfList() (ret map[string]interface{}) {
+	bs, err := sendGetRequest("https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=", wx.tokenChan)
+	if err != nil {
+		return nil
+	}
+	json.Unmarshal(bs, &ret)
+	return
+}
+
+//
+func (wx *Weixin) GetOnlineKfList() (ret map[string]interface{}) {
+	bs, err := sendGetRequest("https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=", wx.tokenChan)
+	if err != nil {
+		return nil
+	}
+	json.Unmarshal(bs, &ret)
+	return
+}
+
 func (rw responseWriter) GetUserBaseInfo(openid string) (map[string]interface{}, error) {
 	return rw.wx.GetUserBaseInfo(openid)
 }
@@ -1142,3 +1197,20 @@ func (w *Weixin) GetUserBaseInfo(openid string) (map[string]interface{}, error)
 		return nil, err
 	}
 }
+
+func (w responseWriter) Reply2CustomerService() {
+	msg := fmt.Sprintf(reply2CustomerService, w.replyHeader(), "")
+	//log.Println("repl msg:", msg)
+	w.writer.Write([]byte(msg))
+}
+
+//Post custom message(消息结构体完全由开发人员自定义)
+func (wx *Weixin) PostTextCustom(url string, obj interface{}) error {
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return err
+	}
+	//log.Println("custom msg:", string(data))
+	_, err = postRequest(url, wx.tokenChan, data)
+	return err
+}

+ 26 - 0
weixin/src/qfw/weixin/wxcustomer_test.go

@@ -0,0 +1,26 @@
+package weixin
+
+import (
+	"log"
+	"sort"
+	"testing"
+)
+
+func Test_1(t *testing.T) {
+	A := arr{}
+	m1 := map[string]interface{}{
+		"accepted_case": 2,
+		"kf_account":    "as@123",
+	}
+	m2 := map[string]interface{}{
+		"accepted_case": 6,
+		"kf_account":    "as@444",
+	}
+	m3 := map[string]interface{}{
+		"accepted_case": 16,
+		"kf_account":    "a1111s@4ee",
+	}
+	A = append(A, m2, m1, m3)
+	sort.Sort(&A)
+	log.Println(A[0]["kf_account"])
+}

+ 2 - 0
weixin/src/qfw/weixinconfig/weixinconfig.go

@@ -39,6 +39,8 @@ type wxconfig struct {
 	Rpcserver             string                 `json:"rpcserver"`
 	Msgserver             string                 `json:"msgserver"`
 	Qmxcdn                string                 `json:"qmxcdn"`
+	Kfprefix              string                 `json:"kfprefix"`
+	Kfport                string                 `json:"kfport"`
 }
 
 //系统配置