github.com/chanxuehong/wechat@v0.0.0-20230222024006-36f0325263cd/mp/jssdk/card_ticket_server.go (about) 1 package jssdk 2 3 import ( 4 "errors" 5 "math/rand" 6 "strconv" 7 "sync/atomic" 8 "time" 9 "unsafe" 10 11 "github.com/chanxuehong/wechat/mp/core" 12 ) 13 14 // 卡劵 api_ticket 中控服务器接口. 15 type CardTicketServer interface { 16 Ticket() (ticket string, err error) // 请求中控服务器返回缓存的卡劵 api_ticket 17 RefreshTicket(currentTicket string) (ticket string, err error) // 请求中控服务器刷新卡劵 api_ticket 18 IIDB9BDD0A1E1DC11E5844AA4DB30FED8E1() // 接口标识, 没有实际意义 19 } 20 21 var _ CardTicketServer = (*DefaultCardTicketServer)(nil) 22 23 // DefaultCardTicketServer 实现了 CardTicketServer 接口. 24 // 25 // NOTE: 26 // 1. 用于单进程环境. 27 // 2. 因为 DefaultCardTicketServer 同时也是一个简单的中控服务器, 而不是仅仅实现 CardTicketServer 接口, 28 // 所以整个系统只能存在一个 DefaultCardTicketServer 实例! 29 type DefaultCardTicketServer struct { 30 coreClient *core.Client 31 32 refreshTicketRequestChan chan string // chan currentTicket 33 refreshTicketResponseChan chan refreshTicketResult // chan {ticket, err} 34 35 ticketCache unsafe.Pointer // *cardApiTicket 36 } 37 38 // NewDefaultCardTicketServer 创建一个新的 DefaultCardTicketServer. 39 func NewDefaultCardTicketServer(clt *core.Client) (srv *DefaultCardTicketServer) { 40 if clt == nil { 41 panic("nil core.Client") 42 } 43 srv = &DefaultCardTicketServer{ 44 coreClient: clt, 45 refreshTicketRequestChan: make(chan string), 46 refreshTicketResponseChan: make(chan refreshTicketResult), 47 } 48 49 go srv.ticketUpdateDaemon(time.Hour * 24 * time.Duration(100+rand.Int63n(200))) 50 return 51 } 52 53 func (srv *DefaultCardTicketServer) IIDB9BDD0A1E1DC11E5844AA4DB30FED8E1() {} 54 55 func (srv *DefaultCardTicketServer) Ticket() (ticket string, err error) { 56 if p := (*cardApiTicket)(atomic.LoadPointer(&srv.ticketCache)); p != nil { 57 return p.Ticket, nil 58 } 59 return srv.RefreshTicket("") 60 } 61 62 //type refreshTicketResult struct { 63 // ticket string 64 // err error 65 //} 66 67 func (srv *DefaultCardTicketServer) RefreshTicket(currentTicket string) (ticket string, err error) { 68 srv.refreshTicketRequestChan <- currentTicket 69 rslt := <-srv.refreshTicketResponseChan 70 return rslt.ticket, rslt.err 71 } 72 73 func (srv *DefaultCardTicketServer) ticketUpdateDaemon(initTickDuration time.Duration) { 74 tickDuration := initTickDuration 75 76 NEW_TICK_DURATION: 77 ticker := time.NewTicker(tickDuration) 78 for { 79 select { 80 case currentTicket := <-srv.refreshTicketRequestChan: 81 cardApiTicket, cached, err := srv.updateTicket(currentTicket) 82 if err != nil { 83 srv.refreshTicketResponseChan <- refreshTicketResult{err: err} 84 break 85 } 86 srv.refreshTicketResponseChan <- refreshTicketResult{ticket: cardApiTicket.Ticket} 87 if !cached { 88 tickDuration = time.Duration(cardApiTicket.ExpiresIn) * time.Second 89 ticker.Stop() 90 goto NEW_TICK_DURATION 91 } 92 93 case <-ticker.C: 94 cardApiTicket, _, err := srv.updateTicket("") 95 if err != nil { 96 break 97 } 98 newTickDuration := time.Duration(cardApiTicket.ExpiresIn) * time.Second 99 if abs(tickDuration-newTickDuration) > time.Second*5 { 100 tickDuration = newTickDuration 101 ticker.Stop() 102 goto NEW_TICK_DURATION 103 } 104 } 105 } 106 } 107 108 //func abs(x time.Duration) time.Duration { 109 // if x >= 0 { 110 // return x 111 // } 112 // return -x 113 //} 114 115 type cardApiTicket struct { 116 Ticket string `json:"ticket"` 117 ExpiresIn int64 `json:"expires_in"` 118 } 119 120 // updateTicket 从微信服务器获取新的卡劵 api_ticket 并存入缓存, 同时返回该卡劵 api_ticket. 121 func (srv *DefaultCardTicketServer) updateTicket(currentTicket string) (ticket *cardApiTicket, cached bool, err error) { 122 if currentTicket != "" { 123 if p := (*cardApiTicket)(atomic.LoadPointer(&srv.ticketCache)); p != nil && currentTicket != p.Ticket { 124 return p, true, nil // 无需更改 p.ExpiresIn 参数值, cached == true 时用不到 125 } 126 } 127 128 var incompleteURL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card&access_token=" 129 var result struct { 130 core.Error 131 cardApiTicket 132 } 133 if err = srv.coreClient.GetJSON(incompleteURL, &result); err != nil { 134 atomic.StorePointer(&srv.ticketCache, nil) 135 return 136 } 137 if result.ErrCode != core.ErrCodeOK { 138 atomic.StorePointer(&srv.ticketCache, nil) 139 err = &result.Error 140 return 141 } 142 143 // 由于网络的延时, 卡劵 api_ticket 过期时间留有一个缓冲区 144 switch { 145 case result.ExpiresIn > 31556952: // 60*60*24*365.2425 146 atomic.StorePointer(&srv.ticketCache, nil) 147 err = errors.New("expires_in too large: " + strconv.FormatInt(result.ExpiresIn, 10)) 148 return 149 case result.ExpiresIn > 60*60: 150 result.ExpiresIn -= 60 * 10 151 case result.ExpiresIn > 60*30: 152 result.ExpiresIn -= 60 * 5 153 case result.ExpiresIn > 60*5: 154 result.ExpiresIn -= 60 155 case result.ExpiresIn > 60: 156 result.ExpiresIn -= 10 157 default: 158 atomic.StorePointer(&srv.ticketCache, nil) 159 err = errors.New("expires_in too small: " + strconv.FormatInt(result.ExpiresIn, 10)) 160 return 161 } 162 163 ticketCopy := result.cardApiTicket 164 atomic.StorePointer(&srv.ticketCache, unsafe.Pointer(&ticketCopy)) 165 ticket = &ticketCopy 166 return 167 }