Răsfoiți Sursa

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

renzheng 9 ani în urmă
părinte
comite
360a373c4a

+ 13 - 0
common/src/qfw/util/rpc/weixin.go

@@ -24,3 +24,16 @@ type BonusMsg struct {
 	Actname     string `json:"act_name"`     //活动名称
 	Remark      string `json:"remark"`       //说明
 }
+type Articles struct {
+	Title       string `json:"title"`
+	Description string `json:"description"`
+	Url         string `json:"url"`
+	Picurl      string `json:"picurl"`
+}
+type ImgTextMsg struct {
+	ToUser  string `json:"touser"`
+	MsgType string `json:"msgtype"`
+	News    struct {
+		Articles []Articles `json:"articles"`
+	} `json:"news"`
+}

+ 19 - 1
core/src/qfw/coreutil/weixinrpc.go

@@ -8,7 +8,7 @@ import (
 	qrpc "qfw/util/rpc"
 )
 
-var rpcserver = "120.25.216.197:83"
+var rpcserver = "127.0.0.1:83"
 
 func init() {
 	weixinrpc := util.ObjToString(coreconfig.MessageConfig["weixinrpc"])
@@ -36,6 +36,24 @@ func SendManagerNotifyMsg(p *qrpc.NotifyMsg) {
 
 }
 
+//微信远程调用,实现模板发送图文消息
+func SendImgTextMsg(p *qrpc.ImgTextMsg) {
+	util.Try(func() {
+		client, err := rpc.DialHTTP("tcp", rpcserver)
+		defer client.Close()
+		if err != nil {
+			log.Println(err.Error())
+			return
+		}
+		var repl qrpc.RpcResult
+		err = client.Call("WeiXinRpc.SendImgTextMsg", p, &repl)
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+
+}
+
 //微信远程调用,实现模板发送消息
 func SendIdentifyTplMsg(p *qrpc.NotifyMsg) {
 	util.Try(func() {

+ 19 - 3
core/src/qfw/coreutil/weixinrpc_test.go

@@ -18,9 +18,25 @@ const (
 )
 
 func TestWeixinRpc(t *testing.T) {
-	rpcserver = "112.74.194.135:91"
-	SendManagerNotifyMsg(&qrpc.NotifyMsg{Openid: "oJULtwzXo6EFV1Ah-XeyRBimXGM8", Title: "冻结通知", Remark: "时间:2015-12-22", Detail: "企明星通知", Result: "冻结内容通知"})
-
+	itm := new(qrpc.ImgTextMsg)
+	itm.ToUser = "oJULtw8sMXxZX7b-DvqpxIdl47zg"
+	itm.MsgType = "news"
+	var articles qrpc.Articles
+	articles.Title = "title"
+	articles.Description = "description"
+	articles.Url = "http://cdn2.qmx.top:9002/images/logo.png"
+	articles.Picurl = "http://cdn2.qmx.top:9002/images/logo.png"
+	itm.News.Articles = append(itm.News.Articles, articles)
+	//ff, _ := ioutil.ReadFile("../../test.png") //我还是喜欢用这个快速读文件
+	log.Println(itm)
+	//UploadMedia(&qrpc.File{Openid: "oJULtw8sMXxZX7b-DvqpxIdl47zg", Data: ff})
+	SendImgTextMsg(itm)
+	s := &qrpc.NotifyMsg{
+		Openid: "oJULtw8sMXxZX7b-DvqpxIdl47zg",
+	}
+	log.Println(s)
+	//SendIdentifyTplMsg(s)
+	//PostImage(&qrpc.NotifyMsg{Openid: "oJULtw8sMXxZX7b-DvqpxIdl47zg"})
 }
 
 type MsgStruct struct {

+ 16 - 7
weixin/src/config.json

@@ -1,7 +1,7 @@
 {
 	"port":"82",
 	"domain":"127.0.0.1",
-	"imgpath":"/opt/qfw_dev/web/staticres",
+	"imgpath":"E:/go_workspace/qfw/core/src/web/staticres",
 	"mongodbServers": "10.116.86.154:27080",
 	"elasticsearch":"http://10.116.86.154:9800",
     "elasticPoolSize": 30,
@@ -10,9 +10,9 @@
 	"rpcport":"83",
 	"serviceTip":"服务指南",
 	"appcontext":"weixin",
-	"appid":"wx9852f95aa927e646",
+	"appid":"wx76e1309b01a7b17e",
 	"token":"topnet2015",
-	"appsecret":"d4624c36b6795d1d99dcf0547af5443d",
+	"appsecret":"dd00e71cb2370432d9de848b674eb8e7",
     "aboutmeurl":"http://www.qimingxing.info/article/aboutme",
     "conactusurl":"http://www.qimingxing.info/article/contactus",
 	"wsqurl": "http://s.p.qq.com/pub/jump?d=AAAXeGLZ",
@@ -24,10 +24,10 @@
 	"loginTip":"您已经成功登录企明星。",
 	"freezeTip":"您的帐号已经冻结,请联系管理员。",
 	"messagetpl":{
-		"identifytplid":"sKn4r0or615ETYC-aI5OACPO_Q4-B8GYmgWoqcDghN4",
+		"identifytplid":"zqi2LoEAFgiwHjfeWJNnI18UmuOk3M1Rz9uSU-GxDek",
 		"offLinemsgtplid":"ExIeyFfoDNVJXhRDq09JbsjH_zbEJCB6gw6rxcV7atw",
 		"msgnotifytplid":"fcke7PqteAtclzHV3ScdiePH48vxaH6M5aJF0O_7by8",
-		"managernotifytplid":"DIwMrPQToOhGfa6ZAQCCrqquzbLKajiXsKY0K_lQtWQ"
+		"managernotifytplid":"Iky7z3veZ6hy-ISchmQSgBvRd-34KzSyuYr0_fUbesE"
 	},
 	"activity":{
 	"activitycode":"topcj",
@@ -40,6 +40,15 @@
 	"subscribemonitorcyc":3,
 	"subscribemonitortimes":12,
 	"mids":["oJULtwzXo6EFV1Ah-XeyRBimXGM8"],
-	"dir":"/opt/mntdisk/qfw-v1.1"
-
+	"dir":"/opt/mntdisk/qfw-v1.1",
+	"qmxcdn":"http://cdn.qmx.top:9000",
+	"rpcserver": "127.0.0.1:83",
+	"msgserver":"test.qmx.top:7070",
+	"distinguishWork":{
+		"title":"识别验证码",
+		"description":"请识别图中验证码并回复",
+		"in":"欢迎您进入了识别验证码工作,输入“结束识别验证码”退出本次工作。",
+		"out":"您已退出本次工作,辛苦了,再见。",
+		"reply":"已收到您的回复,谢谢。"
+	}
 }

+ 318 - 0
weixin/src/qfw/weixin/distinguishwork.go

@@ -0,0 +1,318 @@
+//人工识别验证码
+package weixin
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"math/rand"
+	util "mfw/util"
+	"net/http"
+	"net/rpc"
+	"os"
+	qfwutil "qfw/util"
+	qrpc "qfw/util/rpc"
+	wf "qfw/weixinconfig"
+	"sync"
+	"time"
+)
+
+const (
+	status_rest      = 0 //闲时,没有任务
+	status_working   = 1 //进入工作状态
+	status_waitReply = 2 //收到验证码,等待回复
+)
+
+var workLock = new(sync.Mutex)
+var waitWorks []map[string]interface{}    //待识别的验证码[{msgId:"",from:"",img:""}]
+var workers []map[string]interface{}      //工作的人集合[{openId:"",status:""}]
+var workering = make(map[string][]string) //工作的人{openId:[msgid,from]}
+var client *util.Client
+
+//
+type DistinguishWork struct {
+}
+
+func initDgWork() {
+	client, _ = util.StartClient(processevent, wf.SysConfig.Msgserver, []int{util.SERVICE_DISTINGUISH}, 20)
+	client.ResetMyName("识别验证码")
+}
+func processevent(p *util.Packet) {
+	event := int(p.Event)
+	switch event {
+	case util.SERVICE_DISTINGUISH:
+		//写数据
+		if ret := make(map[string]interface{}); json.Unmarshal(p.GetBusinessData(), &ret) == nil {
+			flag, _ := ret["flag"].(bool)
+			if flag {
+				imgTmp, err := json.Marshal(ret["img"])
+				if err != nil {
+					return
+				}
+				var img []byte
+				err = json.Unmarshal(imgTmp, &img)
+				if err != nil {
+					return
+				}
+				obj := map[string]interface{}{
+					"from":  p.From,
+					"msgId": p.Msgid,
+					"img":   img,
+				}
+				if len(workers) == 0 {
+					waitWorks = append(waitWorks, obj)
+				} else {
+					toWork(obj)
+				}
+			} else {
+				overtime(p.Msgid)
+			}
+		}
+	}
+}
+
+//开始工作,记录工作人的相关数
+func (a *DistinguishWork) Start(openId string) {
+	if client == nil {
+		initDgWork()
+	}
+	for _, v := range workers {
+		if v["openId"] == openId {
+			return
+		}
+	}
+	workers = append(workers, map[string]interface{}{"openId": openId, "status": status_rest})
+	toWork(nil)
+}
+
+//结束工作,删除工作人
+func (a *DistinguishWork) End(openId string) {
+	deleteWorker(openId)
+	for k, v := range workers {
+		if v["openId"] == openId {
+			workers = append(workers[:k], workers[k+1:]...)
+			break
+		}
+	}
+}
+
+//判断该人是否是工作模式
+func (a *DistinguishWork) IsWorking(openId string) bool {
+	for _, v := range workers {
+		if v["openId"] == openId {
+			return true
+		}
+	}
+	return false
+}
+
+//判断该人是否在等待回复
+func (a *DistinguishWork) IsWaitReply(openId string) bool {
+	for _, v := range workers {
+		if v["openId"] == openId {
+			return v["status"] == status_waitReply
+		}
+	}
+	return false
+}
+
+//超时处理,删除该任务
+func overtime(msgId string) {
+	for k, v := range waitWorks {
+		if v["msgId"] == msgId {
+			waitWorks = append(waitWorks[:k], waitWorks[k+1:]...)
+			break
+		}
+	}
+}
+
+//回复验证码
+func (a *DistinguishWork) Reply(openId, content string) {
+	obj := workering[openId]
+	if obj == nil || len(obj) < 2 {
+		return
+	}
+	tmp := make(map[string]interface{})
+	tmp["content"] = content
+	tmp["msgId"] = obj[0]
+	client.WriteObj(obj[1], obj[0], util.EVENT_RECIVE_CALLBACK, util.SENDTO_TYPE_P2P, tmp)
+	deleteWorker(openId)
+	updateWorker(openId, status_rest)
+	toWork(nil)
+}
+
+//删除工作的人
+func deleteWorker(openId string) {
+	delete(workering, openId)
+}
+
+//进行一个任务
+func toWork(o map[string]interface{}) {
+	openId, obj, err := worked(o)
+	if err == nil {
+		return
+	}
+	//如果任务在执行过程中出现异常,把该任务放入等待队列中,修改这个人的状态为空闲
+	obj["status"] = "-1"
+	waitWorks = append(waitWorks, obj)
+	deleteWorker(openId)
+	updateWorker(openId, status_rest)
+}
+
+func worked(o map[string]interface{}) (string, map[string]interface{}, error) {
+	openId, obj := getWorker(o)
+	if openId == "" { //没有闲人
+		return openId, obj, nil
+	}
+	from := obj["from"].(string)
+	msgId := obj["msgId"].(string)
+	img := obj["img"].([]byte)
+	//生成验证码图片
+	name, err := createImg(msgId, img)
+	if name == "" || err != nil {
+		log.Println("生成验证码图片出错:", err)
+		return openId, obj, err
+	}
+	workering[openId] = []string{msgId, from}
+	//发送验证码图片
+	var itm qrpc.ImgTextMsg
+	itm.ToUser = openId
+
+	var als qrpc.Articles
+	als.Title = wf.SysConfig.DistinguishWork["title"]
+	als.Description = wf.SysConfig.DistinguishWork["description"]
+	als.Url = wf.SysConfig.Qmxcdn + "/upload/spider/" + name
+	als.Picurl = wf.SysConfig.Qmxcdn + "/upload/spider/" + name
+
+	itm.News.Articles = append(itm.News.Articles, als)
+
+	var e error
+	qfwutil.Try(func() {
+		client, err := rpc.DialHTTP("tcp", wf.SysConfig.Rpcserver)
+		defer client.Close()
+		if err != nil {
+			log.Println(err.Error())
+			e = err
+			return
+		}
+		var repl qrpc.RpcResult
+		err = client.Call("WeiXinRpc.SendImgTextMsg", itm, &repl)
+		if err != nil {
+			log.Println(err.Error())
+			e = err
+			return
+		} else {
+			updateWorker(openId, status_waitReply)
+		}
+	}, func(e interface{}) {})
+	return openId, obj, e
+}
+
+//修改这个人的状态为等待回复
+func updateWorker(openId string, status int) {
+	for _, v := range workers {
+		if v["openId"] == openId {
+			v["status"] = status
+			break
+		}
+	}
+}
+
+//获取一个可以工作的人
+func getWorker(obj map[string]interface{}) (openId string, object map[string]interface{}) {
+	workLock.Lock()
+	defer workLock.Unlock()
+	if len(workers) == 0 {
+		return
+	}
+	if obj == nil && len(waitWorks) == 0 { //接下来没有任务了
+		return
+	}
+	//随机获取
+	var tmp []int
+	for k, v := range workers {
+		if v["status"] == status_rest {
+			tmp = append(tmp, k)
+		}
+	}
+	tmp_length := len(tmp)
+	if tmp_length == 0 { //都在忙,没有闲人
+		waitWorks = append(waitWorks, obj)
+		return
+	}
+	tmp_index := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(tmp_length)
+	workers_index := tmp[tmp_index]
+	workers[workers_index]["status"] = status_working
+	openId = workers[workers_index]["openId"].(string)
+	if len(waitWorks) > 0 {
+		object = waitWorks[0]
+		//删除第一个
+		waitWorks = waitWorks[1:]
+	} else if obj != nil { //没有待执行的任务
+		object = obj
+	}
+	return
+}
+
+//获取一个要识别的任务
+func createImg(msgId string, img []byte) (string, error) {
+	name := msgId + "-" + fmt.Sprint(time.Now().Unix()) + ".jpg"
+	path := wf.SysConfig.Imgpath + "/upload/spider"
+	fs, err := os.Open(path)
+	if err != nil && os.MkdirAll(path, 0700) != nil {
+		log.Println("创建文件夹出错:", err)
+		return "", err
+	}
+	defer fs.Close()
+	file, err := os.Create(path + "/" + name)
+	if err != nil {
+		log.Println("创建验证码图片出错:", err)
+		return "", err
+	}
+	log.Println(img)
+	_, err = file.Write(img)
+	if err != nil {
+		log.Println("写入验证码图片出错:", err)
+		return "", err
+	}
+	defer file.Close()
+	return name, nil
+}
+func Monitor(w http.ResponseWriter, r *http.Request) {
+	var h_p, h_w, h_wa string
+	for _, v := range workers {
+		h_p += fmt.Sprint(v) + "<br>"
+	}
+	for k, v := range workering {
+		h_w += fmt.Sprint(k) + ":" + fmt.Sprint(v) + "<br>"
+	}
+	for _, v := range waitWorks {
+		delete(v, "img")
+		h_wa += fmt.Sprint(v) + "<br>"
+	}
+	if h_p == "" {
+		h_p = "无"
+	}
+	if h_w == "" {
+		h_w = "无"
+	}
+	if h_wa == "" {
+		h_wa = "无"
+	}
+	html := `<!DOCTYPE HTML>
+<HTML>
+ <HEAD>
+	<meta http-equiv="content-type" content="text/html;charset=utf-8">
+ </HEAD>
+ <BODY>
+<button style='padding: 5px 10px;margin-right: 15px;' onclick="document.getElementById('person').style.display='block';document.getElementById('waitwork').style.display='none';document.getElementById('working').style.display='none';">工作的人</button>
+<button style='padding: 5px 10px;margin-right: 15px;' onclick="document.getElementById('person').style.display='none';document.getElementById('working').style.display='block';document.getElementById('waitwork').style.display='none';">工作中</button>
+<button style='padding: 5px 10px;' onclick="document.getElementById('person').style.display='none';document.getElementById('working').style.display='none';document.getElementById('waitwork').style.display='block';">等待工作</button>
+<br><br>
+<div id="person" style='display: none;'>` + h_p + `</div>
+<div id="working" style='display: none;'>` + h_w + `</div>
+<div id="waitwork" style='display: none;'>` + h_wa + `</div>
+ </BODY>
+</HTML>`
+	w.Write([]byte(html))
+}

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

@@ -24,6 +24,7 @@ const (
 
 var robotclient *http.Client
 var robotlock chan bool = make(chan bool, 100) //100个并发
+var distinguishWork DistinguishWork
 
 //初始化图灵123 HttpClient加上超时
 func InitRobotHttpClient() {
@@ -103,9 +104,20 @@ func MsgTxtHandler(w ResponseWriter, r *Request) {
 		w.ReplyNews([]Article{Article{
 			Title:       "四步设置剑鱼招标订阅,只需一分钟",
 			Description: "剑鱼招标订阅简单四步",
-			PicUrl:      "http://cdn.qmx.top:9000/images/swordfish/jianyu_weixin.jpg",
+			PicUrl:      wf.SysConfig.Qmxcdn + "/images/swordfish/jianyu_weixin.jpg",
 			Url:         "http://mp.weixin.qq.com/s?__biz=MzA5MTk0MTk5Ng==&mid=406103124&idx=1&sn=c12f1cc34899f0c3b36492885567315a#rd",
 		}})
+	} else if !distinguishWork.IsWorking(openid) && r.Content == "开始识别验证码" { //进入人工识别验证码工作
+		distinguishWork.Start(openid)
+		w.ReplyText(wf.SysConfig.DistinguishWork["in"])
+	} else if distinguishWork.IsWorking(openid) { //人工识别验证码工作中。。。
+		if r.Content == "结束识别验证码" {
+			distinguishWork.End(openid)
+			w.ReplyText(wf.SysConfig.DistinguishWork["out"])
+		} else if distinguishWork.IsWaitReply(openid) {
+			distinguishWork.Reply(openid, r.Content)
+			w.ReplyText(wf.SysConfig.DistinguishWork["reply"])
+		}
 	} else {
 		now := time.Now()
 		if now.Weekday() == 6 || now.Weekday() == 0 || (now.Hour() > 17 || now.Hour() < 9) {

+ 15 - 1
weixin/src/qfw/weixin/rpc/rpc.go

@@ -19,6 +19,7 @@ type WeiXinRpc struct {
 
 const (
 	tplapi_url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
+	itmapi_url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="
 	//identify_tplid   = "oGxkPyaV42z3KWykt58Tow9mBe_ImJvi8R3ajorVWOY" //认证通知
 	//offLineMsg_tplid = "ExIeyFfoDNVJXhRDq09JbsjH_zbEJCB6gw6rxcV7atw" //离线消息
 	//msgnotify_tplid  = "GOITP4nM6ry0XQYhIB67IFLv_mqvnWaCXEYZuWSwpNY" //消息提醒
@@ -110,7 +111,7 @@ func (wxrpc *WeiXinRpc) SendIdentifyTplMsg(param *qrpc.NotifyMsg, ret *qrpc.RpcR
 	msg.Data.Detail.Value = param.Detail
 	msg.Data.Result.Value = param.Result
 	msg.Data.Remark.Value = param.Remark
-
+	log.Println(msg)
 	err = wxrpc.wx.PostTextCustom(tplapi_url, &msg)
 	if err != nil {
 		log.Println(err.Error())
@@ -143,6 +144,7 @@ func (wxrpc *WeiXinRpc) SendManagerNotifyMsg(param *qrpc.NotifyMsg, ret *qrpc.Rp
 			} `json:"remark"`
 		} `json:"data"`
 	}
+	log.Println(managernotify_tplid)
 	msg.ToUser = param.Openid
 	msg.TplId = managernotify_tplid
 	msg.Url = param.Url
@@ -200,6 +202,18 @@ func (wxrpc *WeiXinRpc) SendOffLineMsg(param *qrpc.NotifyMsg, ret *qrpc.RpcResul
 	return
 }
 
+//发送图文消息
+func (wxrpc *WeiXinRpc) SendImgTextMsg(param *qrpc.ImgTextMsg, ret *qrpc.RpcResult) (err error) {
+	param.MsgType = "news"
+	err = wxrpc.wx.PostTextCustom(itmapi_url, param)
+	if err != nil {
+		log.Println(err.Error())
+	} else {
+		log.Println("send imgTextMsg success!")
+	}
+	return
+}
+
 var listen net.Listener
 
 func StartWeixinRpc(wx *weixin.Weixin) {

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

@@ -71,6 +71,8 @@ func InitWeixinSdk() {
 	http.HandleFunc("/"+wf.SysConfig.Appcontext+"/sendmsg", SendMsgAct)
 	//生成推广二维码
 	http.HandleFunc("/"+wf.SysConfig.Appcontext+"/adv/", AdvHandle)
+	//人工识别二维码监控
+	http.HandleFunc("/"+wf.SysConfig.Appcontext+"/distinguishwork/monitor", Monitor)
 
 	//执行其他一些初始化的动作
 	InitSSLClient()

+ 5 - 1
weixin/src/qfw/weixinconfig/weixinconfig.go

@@ -30,11 +30,15 @@ type wxconfig struct {
 	FreezeTip             string            `json:"freezeTip"`
 	Activity              map[string]string `json:"activity"` //活动配置
 	WeixinAutoRpl         string            `json:"weixinautorpl"`
-	WeixinAutoRpl2 string `json:"weixinautorpl2"`
+	WeixinAutoRpl2        string            `json:"weixinautorpl2"`
 	SubscribeMonitorCyc   int               `json:"subscribemonitorcyc"`
 	SubscribeMonitorTimes int               `json:"subscribemonitortimes"`
 	Mids                  []string          `json:"mids"`
 	Dir                   string            `json:"dir"`
+	DistinguishWork       map[string]string `json:"distinguishWork"`
+	Rpcserver             string            `json:"rpcserver"`
+	Msgserver             string            `json:"msgserver"`
+	Qmxcdn                string            `json:"qmxcdn"`
 }
 
 //系统配置