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  }