|
@@ -0,0 +1,306 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
+ "log"
|
|
|
+ "net/http"
|
|
|
+ "qfw/util"
|
|
|
+ "qfw/util/redis"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ redigo "github.com/garyburd/redigo/redis"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ weixinHost = "https://api.weixin.qq.com/cgi-bin"
|
|
|
+ weixinJsApiTicketURL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
|
|
|
+ WxTokenKey = "WxToken_%s"
|
|
|
+ WxTicketKey = "WxTicket_%s"
|
|
|
+ RedisCode = "sso"
|
|
|
+ MaxExpSec = 5400
|
|
|
+ retryMaxN = 3
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ Config *config
|
|
|
+ AllCountT = map[string]*countTocken{}
|
|
|
+ AllWxs = map[string]*wx{}
|
|
|
+)
|
|
|
+
|
|
|
+type wx struct {
|
|
|
+ Appid string
|
|
|
+ Appsecret string
|
|
|
+ TokenChan chan accessToken
|
|
|
+ TicketChan chan jsApiTicket
|
|
|
+}
|
|
|
+type config struct {
|
|
|
+ Wxs []*wx
|
|
|
+ Redis string
|
|
|
+}
|
|
|
+type accessToken struct {
|
|
|
+ Token string
|
|
|
+ Expires time.Time
|
|
|
+ Lasttime *int64
|
|
|
+}
|
|
|
+
|
|
|
+type wxToken struct {
|
|
|
+ AccessToken string `json:"access_token"`
|
|
|
+ ExpiresIn int64 `json:"expires_in"`
|
|
|
+ LastTime int64 `json:"last_time"`
|
|
|
+}
|
|
|
+
|
|
|
+type wxTicket struct {
|
|
|
+ Ticket string `json:"ticket"`
|
|
|
+ ExpiresIn int64 `json:"expires_in"`
|
|
|
+}
|
|
|
+
|
|
|
+type jsApiTicket struct {
|
|
|
+ Ticket string
|
|
|
+ Expires time.Time
|
|
|
+}
|
|
|
+type countTocken struct {
|
|
|
+ allTimes int64
|
|
|
+ dayTimes int64
|
|
|
+ day int
|
|
|
+}
|
|
|
+
|
|
|
+func init() {
|
|
|
+ util.ReadConfig(&Config)
|
|
|
+ redis.InitRedis(Config.Redis)
|
|
|
+ for _, v := range Config.Wxs {
|
|
|
+ v.TokenChan = make(chan accessToken)
|
|
|
+ v.TicketChan = make(chan jsApiTicket)
|
|
|
+ AllWxs[v.Appid] = v
|
|
|
+ AllCountT[v.Appid] = &countTocken{0, 0, time.Now().Day()}
|
|
|
+ }
|
|
|
+ go func() {
|
|
|
+ for {
|
|
|
+ for CountK, CountT := range AllCountT {
|
|
|
+ if time.Now().Day() != CountT.day {
|
|
|
+ CountT.dayTimes = 0
|
|
|
+ CountT.day = time.Now().Day()
|
|
|
+ log.Println(CountK, "wx-tocken改变统计时间:", CountT.day, ",次数:", CountT.dayTimes)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ time.Sleep(30 * time.Second)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ go func() {
|
|
|
+ for {
|
|
|
+ defer util.Catch()
|
|
|
+ L:
|
|
|
+ for {
|
|
|
+ conn := redis.RedisPool[RedisCode].Get()
|
|
|
+ if conn.Err() == nil {
|
|
|
+ psc := redigo.PubSubConn{Conn: conn}
|
|
|
+ if err := psc.Subscribe(redigo.Args{}.AddFlat("WxRefreshSub")...); err != nil {
|
|
|
+ log.Println(err)
|
|
|
+ }
|
|
|
+ for {
|
|
|
+ msg := psc.Receive()
|
|
|
+ switch n := msg.(type) {
|
|
|
+ case error:
|
|
|
+ log.Println("redis receive error", msg)
|
|
|
+ conn.Close()
|
|
|
+ break L
|
|
|
+ case redigo.Message:
|
|
|
+ res := string(n.Data)
|
|
|
+ if flag := "token_"; strings.HasPrefix(res, flag) {
|
|
|
+ params := strings.Split(strings.TrimLeft(res, flag), " ")
|
|
|
+ appid := params[0]
|
|
|
+ c := AllWxs[appid]
|
|
|
+ if c != nil {
|
|
|
+ token := <-c.TokenChan
|
|
|
+ //意外情况,导致token失效,需要重新获取token
|
|
|
+ if token.Token == params[1] {
|
|
|
+ refreshAccessToken(appid)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.Println("appid", appid, "不存在")
|
|
|
+ }
|
|
|
+ } else if flag := "ticket_"; strings.HasPrefix(res, flag) {
|
|
|
+ appid := strings.TrimLeft(res, flag)
|
|
|
+ c := AllWxs[appid]
|
|
|
+ if c != nil {
|
|
|
+ <-c.TicketChan
|
|
|
+ } else {
|
|
|
+ log.Println("appid", appid, "不存在")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ time.Sleep(1 * time.Second)
|
|
|
+ }
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+}
|
|
|
+func main() {
|
|
|
+ log.Println("程序启动。。。")
|
|
|
+ for _, v := range AllWxs {
|
|
|
+ log.Println("初始化 token ticket", v.Appid)
|
|
|
+ go createAccessToken(v.Appid)
|
|
|
+ go createJsApiTicket(v.Appid)
|
|
|
+ }
|
|
|
+ <-chan bool(nil)
|
|
|
+}
|
|
|
+func createAccessToken(appid string) {
|
|
|
+ lt := int64(0)
|
|
|
+ token := accessToken{"", time.Now(), <}
|
|
|
+ AllWxs[appid].TokenChan <- token
|
|
|
+ tokenKey := fmt.Sprintf("WxToken_%s", appid)
|
|
|
+ for {
|
|
|
+ ret, err := redis.GetNewBytes(RedisCode, tokenKey)
|
|
|
+ if err == nil {
|
|
|
+ if ret != nil {
|
|
|
+ var res wxToken
|
|
|
+ err := json.Unmarshal(*ret, &res)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "从redis加载token出错", err)
|
|
|
+ } else {
|
|
|
+ token.Token = res.AccessToken
|
|
|
+ token.Expires = time.Now().Add(time.Duration(res.ExpiresIn * 1000 * 1000 * 1000))
|
|
|
+ *token.Lasttime = res.LastTime
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ *token.Lasttime = 0
|
|
|
+ token.Expires = time.Now()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (time.Now().Unix()-*token.Lasttime) > MaxExpSec || time.Since(token.Expires).Seconds() >= 0 {
|
|
|
+ AllCountT[appid].allTimes++
|
|
|
+ AllCountT[appid].dayTimes++
|
|
|
+ resp, err := http.Get(weixinHost + "/token?grant_type=client_credential&appid=" + appid + "&secret=" + AllWxs[appid].Appsecret)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "Get access token failed: ", err)
|
|
|
+ } else {
|
|
|
+ defer resp.Body.Close()
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "Read access token failed: ", err)
|
|
|
+ } else {
|
|
|
+ var res wxToken
|
|
|
+ if err := json.Unmarshal(body, &res); err != nil {
|
|
|
+ log.Println(appid, "Parse access token failed: ", err)
|
|
|
+ } else {
|
|
|
+ token.Token = res.AccessToken
|
|
|
+ token.Expires = time.Now().Add(time.Duration(res.ExpiresIn * 1000 * 1000 * 1000))
|
|
|
+ *token.Lasttime = time.Now().Unix()
|
|
|
+ redis.Put(RedisCode, tokenKey, map[string]interface{}{
|
|
|
+ "access_token": res.AccessToken,
|
|
|
+ "expires_in": token.Expires.Unix(),
|
|
|
+ "last_time": token.Lasttime,
|
|
|
+ }, MaxExpSec)
|
|
|
+ log.Println(appid, "获取tocken:", AllCountT[appid].dayTimes, "token", res.AccessToken, "expires_in", res.ExpiresIn, "expires", token.Expires.Unix())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ AllWxs[appid].TokenChan <- token
|
|
|
+ if AllCountT[appid].dayTimes > 100 {
|
|
|
+ log.Println(appid, ">>-->>获取tocken...<<--<<", AllCountT[appid].dayTimes, "次")
|
|
|
+ time.Sleep(1 * time.Minute)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//刷新token
|
|
|
+func refreshAccessToken(appid string) {
|
|
|
+ redis.Del(RedisCode, fmt.Sprintf(WxTokenKey, appid))
|
|
|
+ <-AllWxs[appid].TokenChan
|
|
|
+}
|
|
|
+
|
|
|
+func createJsApiTicket(appid string) {
|
|
|
+ ticket := jsApiTicket{"", time.Now()}
|
|
|
+ AllWxs[appid].TicketChan <- ticket
|
|
|
+ ticketKey := fmt.Sprintf(WxTicketKey, appid)
|
|
|
+ for {
|
|
|
+ ret, err := redis.GetNewBytes(RedisCode, ticketKey)
|
|
|
+ if err == nil {
|
|
|
+ if ret != nil {
|
|
|
+ var res wxTicket
|
|
|
+ err := json.Unmarshal(*ret, &res)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "从redis加载ticket出错", err)
|
|
|
+ } else {
|
|
|
+ ticket.Ticket = res.Ticket
|
|
|
+ ticket.Expires = time.Now().Add(time.Duration(res.ExpiresIn * 1000 * 1000 * 1000))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ticket.Expires = time.Now()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if time.Since(ticket.Expires).Seconds() >= 0 {
|
|
|
+ t, err := getJsApiTicket(appid, ticketKey)
|
|
|
+ if err == nil {
|
|
|
+ ticket = *t
|
|
|
+ }
|
|
|
+ }
|
|
|
+ AllWxs[appid].TicketChan <- ticket
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func getJsApiTicket(appid, ticketKey string) (*jsApiTicket, error) {
|
|
|
+ reply, err := sendGetRequest(appid, weixinJsApiTicketURL+"?type=jsapi&access_token=")
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "getJsApiTicket error", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ var res wxTicket
|
|
|
+ if err := json.Unmarshal(reply, &res); err != nil {
|
|
|
+ log.Println(appid, "getJsApiTicket json.Unmarshal error", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ var ticket jsApiTicket
|
|
|
+ ticket.Ticket = res.Ticket
|
|
|
+ ticket.Expires = time.Now().Add(time.Duration(res.ExpiresIn * 1000 * 1000 * 1000))
|
|
|
+ redis.Put(RedisCode, ticketKey, map[string]interface{}{
|
|
|
+ "ticket": res.Ticket,
|
|
|
+ "expires_in": ticket.Expires.Unix(),
|
|
|
+ }, int(res.ExpiresIn))
|
|
|
+ log.Println(appid, "获取ticket:", "ticket", res.Ticket, "expires_in", res.ExpiresIn, "expires", ticket.Expires.Unix())
|
|
|
+ return &ticket, nil
|
|
|
+}
|
|
|
+
|
|
|
+func sendGetRequest(appid, reqURL string) ([]byte, error) {
|
|
|
+ for i := 0; i < retryMaxN; i++ {
|
|
|
+ token := <-AllWxs[appid].TokenChan
|
|
|
+ if time.Since(token.Expires).Seconds() < 0 {
|
|
|
+ r, err := http.Get(reqURL + token.Token)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "get JsApiTicket error", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ defer r.Body.Close()
|
|
|
+ reply, err := ioutil.ReadAll(r.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(appid, "Read access token failed: ", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ var result struct {
|
|
|
+ ErrorCode int `json:"errcode,omitempty"`
|
|
|
+ ErrorMessage string `json:"errmsg,omitempty"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal(reply, &result); err != nil {
|
|
|
+ log.Println(appid, "Parse JsApiTicket error", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ switch result.ErrorCode {
|
|
|
+ case 0:
|
|
|
+ return reply, nil
|
|
|
+ case 42001, 40001:
|
|
|
+ refreshAccessToken(appid)
|
|
|
+ log.Println(appid, "reget the tocken by ", result.ErrorCode, result.ErrorMessage)
|
|
|
+ continue
|
|
|
+ default:
|
|
|
+ return nil, errors.New(fmt.Sprintf("WeiXin send get request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil, errors.New("WeiXin post request too many times:" + reqURL)
|
|
|
+}
|