From 3c1bff3aaa5a819a28d8cc7590c43eedf6b87e0e Mon Sep 17 00:00:00 2001 From: bing Date: Mon, 10 Oct 2022 10:34:56 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- go.mod | 1 + go.sum | 2 + go/gin/main.go | 19 ++++ go/notify/index.html | 25 ++++++ go/notify/notify.go | 126 ++++++++++++++++++++++++++ go/notify/notify/consumer.go | 66 ++++++++++++++ go/notify/notify/message.go | 6 ++ go/notify/notify/notify.go | 104 +++++++++++++++++++++ go/test/main.go | 5 ++ go/wechat/main.go | 169 +++++++++++++++++++++-------------- go/wechat/wechat/api.go | 112 +++++++++++++++++++++-- go/wechat/wechat/wechat.go | 103 ++++++++++++++++++--- 13 files changed, 653 insertions(+), 89 deletions(-) create mode 100644 go/gin/main.go create mode 100644 go/notify/index.html create mode 100644 go/notify/notify.go create mode 100644 go/notify/notify/consumer.go create mode 100644 go/notify/notify/message.go create mode 100644 go/notify/notify/notify.go create mode 100644 go/test/main.go diff --git a/.gitignore b/.gitignore index 39aa122..92b1626 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ # vendor/ .vscode -*.log \ No newline at end of file +*.log + +go/bin \ No newline at end of file diff --git a/go.mod b/go.mod index 28e2699..b333d71 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/spf13/cobra v1.3.0 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 + gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225 k8s.io/api v0.23.1 k8s.io/apimachinery v0.23.1 k8s.io/client-go v0.23.1 diff --git a/go.sum b/go.sum index 5d817c7..acab370 100644 --- a/go.sum +++ b/go.sum @@ -868,6 +868,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225 h1:xy+AV3uSExoRQc2qWXeZdbhFGwBFK/AmGlrBZEjbvuQ= +gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225/go.mod h1:SiXNRpUllqhl+GIw2V/BtKI7BUlz+uxov9vBFtXHqh8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go/gin/main.go b/go/gin/main.go new file mode 100644 index 0000000..0f5c492 --- /dev/null +++ b/go/gin/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + + "github.com/gin-gonic/gin" +) + + +func get(c *gin.Context){ + //c.ClientIP() + log.Println() +} + +func main(){ + engine := gin.Default() + engine.GET("/", get) + engine.Run("0.0.0.0:10000") +} \ No newline at end of file diff --git a/go/notify/index.html b/go/notify/index.html new file mode 100644 index 0000000..87c298b --- /dev/null +++ b/go/notify/index.html @@ -0,0 +1,25 @@ + + + + SSE test + + + +

SSE test

+
+ +
+ + \ No newline at end of file diff --git a/go/notify/notify.go b/go/notify/notify.go new file mode 100644 index 0000000..580bf64 --- /dev/null +++ b/go/notify/notify.go @@ -0,0 +1,126 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "time" + + "blog.bing89.com/go/notify/notify" + "github.com/gin-gonic/gin" +) + +type Client struct { + User string + ID string + msgChan chan string +} + +func (c *Client)Write(msg string){ + c.msgChan <- msg +} + +type NServer struct { + notifier *notify.Notifier + conn net.Conn + flusher http.Flusher +} + +var ch = make(chan string) + +func (n *NServer)Get(c *gin.Context){ + user := c.Query("user") + id := c.Query("client") + if user == "" || id == "" { + c.String(http.StatusBadRequest, "failed") + return + } + + flusher, ok := c.Writer.(http.Flusher) + if !ok { + c.String(http.StatusBadRequest, "failed") + return + } + // h, _, err := c.Writer.Hijack() + // if err != nil { + // c.String(http.StatusBadRequest, err.Error()) + // return + // } + // if n.conn == nil { + // n.conn = h + // } + c.Header("Content-Type", "text/event-stream") + c.Header("Cache-Control", "no-cache") + c.Header("Connection", "keep-alive") + c.Header("Access-Control-Allow-Origin", "*") + + // if n.flusher == nil { + // n.flusher = flusher + // } + for str := range ch { + + c.Writer.Write([]byte(fmt.Sprintf("data: %v\n\n", str))) + flusher.Flush() + } + // err := n.notifier.AddConsumer(c) + // if err != nil { + // fmt.Println("add consumer:", err) + // } + // c.Next() + // time.Sleep(100*time.Second) +} + +func (n *NServer)Run(){ + ticker := time.NewTicker(2*time.Second) + for t := range ticker.C { + str := fmt.Sprintf("data: %v\n\n", t.Format("2006-01-02 15:04:05")) + fmt.Println("send data to n") + if n.conn != nil { + n, err := n.conn.Write([]byte(str)) + fmt.Println("sent it length: ", n, " result: ", err) + } + ch <- str + // if n.flusher != nil { + // n.flusher.Flush() + // } + // n.notifier.SendMessage("2", fmt.Sprintf("hello world %d", t.Unix())) + } +} + +func main(){ + n := NServer{ + notifier: notify.NewNotifier(), + } + // ctx, cancel := context.WithCancel(context.Background()) + // defer cancel() + // go n.notifier.Run(ctx) + engine := gin.Default() + engine.GET("/api/notify", n.Get) + go n.Run() + engine.Run("0.0.0.0:18000") +} + +// package main + +// import ( +// "gopkg.in/antage/eventsource.v1" +// "log" +// "net/http" +// "strconv" +// "time" +// ) + +// func main() { +// es := eventsource.New(nil, nil) +// defer es.Close() +// http.Handle("/events", es) +// go func() { +// id := 1 +// for { +// es.SendEventMessage("tick", "tick-event", strconv.Itoa(id)) +// id++ +// time.Sleep(2 * time.Second) +// } +// }() +// log.Fatal(http.ListenAndServe(":18000", nil)) +// } \ No newline at end of file diff --git a/go/notify/notify/consumer.go b/go/notify/notify/consumer.go new file mode 100644 index 0000000..1016bfb --- /dev/null +++ b/go/notify/notify/consumer.go @@ -0,0 +1,66 @@ +package notify + +import ( + // "compress/gzip" + "compress/gzip" + "fmt" + "io" + "strings" + + "github.com/gin-gonic/gin" +) + +type Consumer struct { + UserID string + ClientID string + conn io.WriteCloser +} + +func NewConsumer(c *gin.Context)(*Consumer, error){ + user := c.Query("user") + clientID := c.Query("client") + if clientID == "" { + return nil, fmt.Errorf("client id is empty") + } + conn, _, err := c.Writer.Hijack() + if err != nil { + return nil, err + } + consumer := Consumer{ + UserID: user, + ClientID: clientID, + conn: conn, + } + useGzip := false + if strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") { + useGzip = true + } + + err = consumer.init(useGzip) + if err != nil { + conn.Close() + } + return &consumer, err +} + +func (c *Consumer)Write(msg string)error{ + _, err := c.conn.Write([]byte(fmt.Sprintf("data: %s\n\n", msg))) + return err +} + +func (c *Consumer)init(gz bool)error{ + if err := c.Write("HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n"); err != nil { + return err + } + if err := c.Write("Vary: Accept-Encoding\r\n"); err != nil { + return err + } + if gz { + if err := c.Write("Content-Encoding: gzip\r\n"); err != nil { + return err + } + c.conn = gzip.NewWriter(c.conn) + } + return c.Write("\r\n") + return nil +} \ No newline at end of file diff --git a/go/notify/notify/message.go b/go/notify/notify/message.go new file mode 100644 index 0000000..63a849b --- /dev/null +++ b/go/notify/notify/message.go @@ -0,0 +1,6 @@ +package notify + +type Message struct { + User string + Message string +} \ No newline at end of file diff --git a/go/notify/notify/notify.go b/go/notify/notify/notify.go new file mode 100644 index 0000000..383fab3 --- /dev/null +++ b/go/notify/notify/notify.go @@ -0,0 +1,104 @@ +package notify + +import ( + "context" + "sync" + + "github.com/gin-gonic/gin" +) + +type Notifier struct { + Consumers map[string]*Consumer + userClients map[string][]string + lock sync.Mutex + consumerChan chan *Consumer + messageChan chan Message +} + +func NewNotifier()*Notifier{ + return &Notifier{ + Consumers: make(map[string]*Consumer), + lock: sync.Mutex{}, + consumerChan: make(chan *Consumer), + messageChan: make(chan Message), + userClients: map[string][]string{}, + } +} + +func (n *Notifier)AddConsumer(c *gin.Context)error{ + consumer, err := NewConsumer(c) + if err != nil{ + return err + } + go func () { + n.consumerChan <- consumer + }() + return nil +} + +func (n *Notifier)recieveMessage(ctx context.Context){ + for { + select{ + case <- ctx.Done(): + return + case msg := <- n.messageChan: + n.notify(msg) + } + } +} + +func (n *Notifier)notify(msg Message){ + n.lock.Lock() + defer n.lock.Unlock() + clts, ok := n.userClients[msg.User]; + if !ok || len(clts) == 0{ + return + } + newClts := []string{} + for _, clt := range clts{ + client, ok := n.Consumers[clt] + if ok { + err := client.Write(msg.Message) + if err != nil { + delete(n.Consumers, clt) + }else{ + newClts = append(newClts, clt) + } + } + } + n.userClients[msg.User] = newClts +} + +func (n *Notifier)recieveConsumer(ctx context.Context){ + for { + select{ + case <- ctx.Done(): + return + case c := <- n.consumerChan: + n.addConsumer(c) + } + } +} + +func (n *Notifier)addConsumer(c *Consumer){ + n.lock.Lock() + defer n.lock.Unlock() + n.Consumers[c.ClientID] = c + us := []string{} + if u, ok := n.userClients[c.UserID]; ok { + us=append(us, u...) + } + us=append(us, c.ClientID) + n.userClients[c.UserID] = us +} + +func (n *Notifier)Run(ctx context.Context){ + go n.recieveConsumer(ctx) + n.recieveMessage(ctx) +} + +func (n *Notifier)SendMessage(user, msg string){ + go func () { + n.messageChan <- Message{User: user, Message: msg} + }() +} \ No newline at end of file diff --git a/go/test/main.go b/go/test/main.go new file mode 100644 index 0000000..39c4c98 --- /dev/null +++ b/go/test/main.go @@ -0,0 +1,5 @@ +package main + +func main(){ + +} \ No newline at end of file diff --git a/go/wechat/main.go b/go/wechat/main.go index b4af910..6478134 100644 --- a/go/wechat/main.go +++ b/go/wechat/main.go @@ -1,42 +1,43 @@ package main import ( - "crypto/sha1" - "encoding/xml" "fmt" + "html/template" "log" "net/http" - "sort" - "strings" - "time" + "blog.bing89.com/go/wechat/wechat" "github.com/gin-gonic/gin" ) const ( - appID = "wxc39301065b66300c" - secret = "7fee3a1cba8c555cf6c4caec4f05844c" - token = "qwertyuiopasdfghjkl" + appID = "wxc39301065b66300c" + secret = "7fee3a1cba8c555cf6c4caec4f05844c" + token = "qwertyuiopasdfghjkl" + Secret = "0a05cb757a9e4fdc9b1aac4f0406a3df" + Appid = "wx2fa7fb238aa6896a" + Encodingaeskey = "Lw2YDq9Svzhi5HeTzldcG5ID7rlgsJkQ5tDv8wwa092" ) -////// -type messageEntity struct { - Signature string `form:"signature"` - Timestamp string `form:"timestamp"` - Nonce string `form:"nonce"` - EchoStr string `form:"echostr"` +type Server struct { + w *wechat.Wechat + users map[string]*wechat.UserInfo } -func (m *messageEntity) CheckSignature(token string) bool { - item := []string{token, m.Timestamp, m.Nonce} - sort.Strings(item) - itemByte := strings.Join(item, "") - signature := fmt.Sprintf("%x", sha1.Sum([]byte(itemByte))) - return signature == m.Signature +func NewServer() (*Server, error) { + w, err := wechat.NewWechat(Appid, Secret) + if err != nil { + return nil, err + } + s := Server{ + w: w, + users: make(map[string]*wechat.UserInfo), + } + return &s, nil } -func Check(c *gin.Context) { - me := &messageEntity{} +func (s *Server) Check(c *gin.Context) { + me := &wechat.MessageEntity{} me.Signature = c.Query("signature") me.Timestamp = c.Query("timestamp") me.Nonce = c.Query("nonce") @@ -47,43 +48,25 @@ func Check(c *gin.Context) { c.AbortWithError(http.StatusForbidden, fmt.Errorf("token invalid")) return } + c.Set("echostr", me.EchoStr) c.Next() } //// -type foucsData struct { - ToUserName string `xml:"ToUserName"` - FromUserName string `xml:"FromUserName"` - CreateTime int64 `xml:"CreateTime"` - MsgType string `xml:"MsgType"` - Event string `xml:"Event"` -} -//// -type textMessage struct { - ToUserName string `xml:"ToUserName"` - FromUserName string `xml:"FromUserName"` - CreateTime int64 `xml:"CreateTime"` - MsgType string `xml:"MsgType"` - Content string `xml:"Content"` -} - -func cdata(data string)string{ +func cdata(data string) string { return fmt.Sprintf(``, data) } -func handleGet(c *gin.Context) { - me := &messageEntity{} - me.Signature = c.Query("signature") - me.Timestamp = c.Query("timestamp") - me.Nonce = c.Query("nonce") - me.EchoStr = c.Query("echostr") - log.Println("signature:", me.Signature, "timestamp:", me.Timestamp, "noce:", me.Nonce, "echostr:", me.EchoStr) +func (s *Server) handleGet(c *gin.Context) { + echoStr := c.GetString("echostr") log.Println("token is ok") - c.String(http.StatusOK, me.EchoStr) + c.String(http.StatusOK, echoStr) } -func handlePost(c *gin.Context) { - data := foucsData{} + +//关注 +func (s *Server) handlePost(c *gin.Context) { + data := wechat.FoucsRequest{} err := c.BindXML(&data) if err != nil { log.Println(err) @@ -91,22 +74,21 @@ func handlePost(c *gin.Context) { log.Println("data: ", data) openID := c.Query("openid") log.Println("openid is ", openID) - t := textMessage{ - ToUserName: cdata(data.FromUserName), - FromUserName: cdata(data.ToUserName), - CreateTime: time.Now().Unix(), - MsgType: cdata("text"), - Content: cdata("你关注了我!"), + res, err := s.w.SendText(data.FromUserName, "welcome to xk.design.") + log.Printf("res: %s\nerr:%v", string(res), err) + ui, err := s.w.GetUserInfo(data.FromUserName) + if err == nil { + s.users[ui.OpenID] = ui + ui.Show() } - d, err := xml.Marshal(&t) if err != nil { - log.Println("marshar message faild", err) + log.Printf("get ui err:%v", err) } - c.String(http.StatusOK, string(d)) + c.String(http.StatusOK, "success") } -func handlePut(c *gin.Context) { - data :=foucsData{} +func (s *Server) handlePut(c *gin.Context) { + data := wechat.FoucsRequest{} err := c.BindXML(&data) if err != nil { log.Println(err) @@ -115,8 +97,8 @@ func handlePut(c *gin.Context) { c.String(http.StatusOK, "success") } -func handleDelete(c *gin.Context) { - data := foucsData{} +func (s *Server) handleDelete(c *gin.Context) { + data := wechat.FoucsRequest{} err := c.BindXML(&data) if err != nil { log.Println(err) @@ -125,13 +107,62 @@ func handleDelete(c *gin.Context) { c.String(http.StatusOK, "success") } +//@router /auth/login +func (s *Server) login(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.AbortWithError(http.StatusBadRequest, fmt.Errorf("no code")) + return + } + ticket, err := s.w.CreateQRCode() + if err != nil { -func main() { + } + log.Printf("ticket: %s\nurl: %s\n err:%v\n", ticket.Ticket, ticket.URL, ticket.ExpireSeconds) + log.Println("==========> code is: ", code) + c.HTML(http.StatusOK, "next", gin.H{"next": template.HTML(ticket.URL)}) + +} + +func (s *Server) Template(name string) *template.Template { + tmpl := template.New(name) + tmpl.Parse(s.html()) + return tmpl +} + +func (s *Server) html() string { + return ` + + + Login + ` +} + +func (s *Server) Run(addr string) error { engine := gin.Default() - engine.Use(Check) - engine.GET("/api/wechat/signature", handleGet) - engine.POST("/api/wechat/signature", handlePost) - engine.PUT("/api/wechat/signature", handlePut) - engine.DELETE("/api/wechat/signature", handleDelete) - engine.Run("0.0.0.0:15043") + engine.SetHTMLTemplate(s.Template("next")) + apiGroup := engine.Group("/api/wechat") + apiGroup.Use(s.Check) + apiGroup.GET("/signature", s.handleGet) + apiGroup.POST("/signature", s.handlePost) + apiGroup.PUT("/signature", s.handlePut) + apiGroup.DELETE("/signature", s.handleDelete) + authGroup := engine.Group("/auth") + authGroup.GET("/login", s.login) + + return engine.Run(addr) +} + +//https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc39301065b66300c&redirect_uri=http://106.55.59.210:15043/auth/login&response_type=code&scope=snsapi_userinfo#wechat_redirect +// https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc39301065b66300c&redirect_uri=http%3A%2F%2F106.55.59.210%3A15043%2Fauth%2Flogin&response_type=code&scope=snsapi_userinfo&connect_redirect=1#wechat_redirect +func main() { + s, err := NewServer() + if err != nil { + log.Fatal(err) + } + + err = s.Run("0.0.0.0:15043") + if err != nil { + log.Fatal(err) + } } diff --git a/go/wechat/wechat/api.go b/go/wechat/wechat/api.go index 739e3a3..09110e9 100644 --- a/go/wechat/wechat/api.go +++ b/go/wechat/wechat/api.go @@ -1,8 +1,19 @@ package wechat +import ( + "crypto/sha1" + "encoding/json" + "fmt" + "sort" + "strings" +) + const ( APIGetAccessToken = "https://api.weixin.qq.com/cgi-bin/token" - APISendMessage= "https://api.weixin.qq.com/cgi-bin/message/custom/send" + APISendMessage = "https://api.weixin.qq.com/cgi-bin/message/custom/send" + APIGetUserInfo = "https://api.weixin.qq.com/cgi-bin/user/info" + APIGetGrantToken = "https://api.weixin.qq.com/sns/oauth2/access_token" + APICreateQRCode = "https://api.weixin.qq.com/cgi-bin/qrcode/create" ) type MessageType string @@ -16,7 +27,98 @@ type TextMessage struct { } type Message struct { - ToUser string `json:"touser"` - Type MessageType `json:"msgtype"` - Text TextMessage `json:"text"` -} \ No newline at end of file + ToUser string `json:"touser"` + Type MessageType `json:"msgtype"` + Text TextMessage `json:"text"` +} + +func (m *Message) Show() { + data, err := json.MarshalIndent(m, "", "\t") + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(data)) +} + +////// +type MessageEntity struct { + Signature string `form:"signature"` + Timestamp string `form:"timestamp"` + Nonce string `form:"nonce"` + EchoStr string `form:"echostr"` +} + +func (m *MessageEntity) CheckSignature(token string) bool { + item := []string{token, m.Timestamp, m.Nonce} + sort.Strings(item) + itemByte := strings.Join(item, "") + signature := fmt.Sprintf("%x", sha1.Sum([]byte(itemByte))) + return signature == m.Signature +} + +type FoucsRequest struct { + ToUserName string `xml:"ToUserName"` + FromUserName string `xml:"FromUserName"` + CreateTime int64 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Event string `xml:"Event"` +} + +/* +{ + "subscribe": 1, + "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", + "language": "zh_CN", + "subscribe_time": 1382694957, + "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL", + "remark": "", + "groupid": 0, + "tagid_list":[128,2], + "subscribe_scene": "ADD_SCENE_QR_CODE", + "qr_scene": 98765, + "qr_scene_str": "" +} +*/ +type UserInfo struct { + Subscribe int `json:"subscribe"` + OpenID string `json:"openid"` + Language string `json:"language"` + SubscribeTime int64 `json:"subscribe_time"` + UnionID string `json:"unionid"` + Remark string `json:"remark"` + GroupID int `json:"groupid"` + LagIDList []int `json:"tagid_list"` + SubscribeScene string `json:"subscribe_scene"` + QRScene int `json:"qr_scene"` + QRSceneStr string `json:"qr_scene_str"` + Nickname string `json:"nickname"` + Sex int `json:"sex"` + Province string `json:"province"` + Country string `json:"country"` + HeadImage string `json:"headimgurl"` +} + +func (u *UserInfo) Show() { + data, err := json.MarshalIndent(u, "", "\t") + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(data)) +} + +type GrantToken struct { + Token string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` + ExpiredAt int64 `json:"expiredAt"` + RefreshToken string `json:"refresh_token"` + OpenID string `json:"openid"` + Scope string `json:"scope"` +} + +type Ticket struct { + Ticket string `json:"ticket"` + ExpireSeconds int `json:"expire_seconds"` + URL string `json:"url"` +} diff --git a/go/wechat/wechat/wechat.go b/go/wechat/wechat/wechat.go index 906b4d2..2802e01 100644 --- a/go/wechat/wechat/wechat.go +++ b/go/wechat/wechat/wechat.go @@ -3,6 +3,7 @@ package wechat import ( "encoding/json" "fmt" + "log" "time" "blog.bing89.com/go/wechat/curl" @@ -13,7 +14,7 @@ type Token struct { ExpiresIn int64 `json:"expires_in"` ExpiredAt int64 `json:"expiredAt"` ErrCode int `json:"errcode"` - ErrMsg string `json:"errmsg"` + ErrMsg string `json:"errmsg"` } type Wechat struct { @@ -22,38 +23,42 @@ type Wechat struct { token Token } -func NewWechat(id, secret string) *Wechat { +func NewWechat(id, secret string) (*Wechat, error) { w := Wechat{ AppID: id, AppSecret: secret, } - w.checkToken() - return &w + err := w.checkToken() + return &w, err } -func (w *Wechat) checkToken() { +func (w *Wechat) checkToken() error { if w.token.Token == "" || w.token.ExpiredAt < time.Now().Unix() { - w.getToken() + return w.getToken() } + return nil } func (w *Wechat) queryStr() string { return fmt.Sprintf("appid=%s&secret=%s", w.AppID, w.AppSecret) } -func (w *Wechat)queryToken()string { - w.checkToken() - return fmt.Sprintf("access_token=%s", w.token.Token) +func (w *Wechat) queryToken() (string, error) { + err := w.checkToken() + return fmt.Sprintf("access_token=%s", w.token.Token), err } func (w *Wechat) getToken() error { - url := fmt.Sprintf("%s??grant_type=client_credential&%s", APIGetAccessToken, w.queryStr()) + url := fmt.Sprintf("%s?grant_type=client_credential&%s", APIGetAccessToken, w.queryStr()) data, err := curl.SimpleGet(url) if err != nil { return err } - err = json.Unmarshal(data, &w.token) + token := Token{} + err = json.Unmarshal(data, &token) + log.Println("====>token:", string(data), " err:", err) if err == nil { + w.token = token w.token.ExpiredAt = w.token.ExpiresIn + time.Now().Unix() } return err @@ -62,12 +67,82 @@ func (w *Wechat) getToken() error { func (w *Wechat) SendText(to, text string) ([]byte, error) { msg := Message{ ToUser: to, - Type: MessageTypeText, + Type: MessageTypeText, Text: TextMessage{ Content: text, }, } - url := fmt.Sprintf("%s?%s", APISendMessage, w.queryToken()) + token, err := w.queryToken() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s?%s", APISendMessage, token) + log.Println("=====>", url) + msg.Show() data, err := curl.Post(url, &msg, nil) - return data ,err + return data, err +} + +func (w *Wechat)GetUserInfo(openID string)(*UserInfo, error){ + token, err := w.queryToken() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s?lang=zh_CN&%s&openid=%s", APIGetUserInfo, token, openID) + data, err := curl.SimpleGet(url) + if err != nil { + return nil, err + } + ui := UserInfo{} + err = json.Unmarshal(data, &ui) + return &ui, err +} + +func (w *Wechat)GetGrantToken(code string)(*GrantToken, error){ + url := fmt.Sprintf("%s?%s&code=%s&grant_type=authorization_code", APIGetGrantToken, w.queryStr(), code) + + data, err := curl.SimpleGet(url) + if err != nil { + return nil, err + } + token := GrantToken{} + err = json.Unmarshal(data, &token) + return &token, err } +//{"expire_seconds":"60","action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": '.$id.'}}} +type Scene struct{ + Str string `json:"scene_str"` +} + +type ActionInfo struct { + ActionInfo Scene `json:"scene"` +} +type qrCodeRequest struct{ + ExpireSeconds int `json:"expire_seconds"` + Action string `json:"action_name"` + ActionInfo ActionInfo `json:"action_info"` +} + +func (w *Wechat)CreateQRCode()(*Ticket, error){ + req := qrCodeRequest{ + ExpireSeconds: 3600, + Action: "QR_STR_SCENE", + ActionInfo: ActionInfo{ + Scene{ + Str: fmt.Sprintf("aaa%d", time.Now().UnixNano()), + }, + }, + } + token, err := w.queryToken() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s?%s", APICreateQRCode, token) + data, err := curl.Post(url, req, nil) + if err != nil { + return nil, err + } + ticket := Ticket{} + err = json.Unmarshal(data, &ticket) + return &ticket, err +} \ No newline at end of file