package tasks import ( "fmt" "io/ioutil" "net/http" "strings" "time" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/jx-callback/business/jxutils/eventhub/syseventhub" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/model/legacymodel" "git.rosy.net.cn/jx-callback/globals/api" "git.rosy.net.cn/baseapi/platformapi/weimobapi" "git.rosy.net.cn/baseapi/platformapi/yilianyunapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/globals" "github.com/astaxie/beego" "github.com/astaxie/beego/orm" ) const ( weixinTokenExpires = 7200 * time.Second dingdingTokenExpires = 7200 * time.Second elmTokenExpires = 20 * 24 * 3600 * time.Second weimobTokenExpires = 7200 * time.Second maxRefreshGap = 5 * 60 * time.Second yilianyunTokenExpires = 30 * 24 * 3600 * time.Second ) type ElmTokenForCompatible struct { Error string `json:"error"` ErrorDescription string `json:"error_description"` AccessToken string `json:"accessToken"` TokenType string `json:"tokenType"` Expires int `json:"expires"` RefreshToken string `json:"refreshToken"` Success bool `json:"success"` } type CallResult struct { Code string `json:"code"` Desc string `json:"desc"` Data string `json:"data"` } func RefreshConfig(configKey string, expiresTime time.Duration, configGetter func() (string, string), configSetter func(value string)) error { sleepGap := expiresTime / 10 needRefreshGap := expiresTime * 8 / 10 if sleepGap > maxRefreshGap { sleepGap = maxRefreshGap } refreshFunc := func() (string, error) { curConfig := &legacymodel.Config{ Thirdparty: configKey, } db := orm.NewOrm() // 0: don't refresh, 1 update, 2 insert handleType := 0 if err := db.Read(curConfig, "Thirdparty"); err != nil { if err != orm.ErrNoRows { globals.SugarLogger.Errorf("db error:%v, curConfig:%v", err, curConfig) return "", err } handleType = 2 } else { configSetter(curConfig.Token) if curConfig.Date <= utils.Time2Str(time.Now().Add(-needRefreshGap)) { handleType = 1 } } if handleType != 0 { if curConfig.Token, curConfig.Date = configGetter(); curConfig.Token == "" { if beego.BConfig.RunMode == "prod" { globals.SugarLogger.Errorf("RefreshConfig %s get empty token", configKey) } else { globals.SugarLogger.Infof("RefreshConfig %s get empty token", configKey) } return "", nil } globals.SugarLogger.Debugf("RefreshConfig refresh %s, value:%s", configKey, curConfig.Token) if curConfig.Date == "" { curConfig.Date = utils.GetCurTimeStr() } var num int64 var err error if handleType == 1 { num, err = db.Update(curConfig, "Token", "Date") } else { num, err = db.Insert(curConfig) } if err != nil || num == 0 { globals.SugarLogger.Errorf("db error:%v, num:%d, curConfig:%v", err, num, curConfig) return "", err } configSetter(curConfig.Token) } return curConfig.Token, nil } token, err := refreshFunc() // 这样写的目的是强制第一次调用时要刷新一次 if err == nil { if token != "" { configSetter(token) } utils.CallFuncAsync(func() { for { time.Sleep(sleepGap) refreshFunc() } }) } return err } func RefreshWeixinToken() (err error) { if api.WeixinAPI != nil { err = RefreshConfig("wechat", weixinTokenExpires, func() (token string, expireTimeStr string) { globals.SugarLogger.Debugf("RefreshWeixinToken RunMode:%s", beego.BConfig.RunMode) if globals.IsProductEnv() { if beego.BConfig.RunMode == "prod" { if tokenInfo, err := api.WeixinAPI.CBRetrieveToken(); err == nil { globals.SugarLogger.Debugf("RefreshWeixinToken tokenInfo:%s", utils.Format4Output(tokenInfo, true)) token = tokenInfo.AccessToken } else { globals.SugarLogger.Errorf("RefreshWeixinToken RefreshToken failed with error:%v", err) } } else { token = getWXTokenFromProd(api.WeixinAPI.CBGetToken()) } } return token, "" }, func(value string) { syseventhub.SysEventHub.OnNewWXToken(value) api.WeixinAPI.CBSetToken(value) }) } return err } func RefreshElmToken() (err error) { if api.ElmAPI != nil { err = RefreshConfig("eleme", elmTokenExpires, func() (string, string) { if beego.BConfig.RunMode == "prod" { if tokenInfo, err := api.ElmAPI.RefreshTokenIndividual(); err == nil { tokenInfo2 := &ElmTokenForCompatible{ Error: "", ErrorDescription: "", AccessToken: tokenInfo.AccessToken, TokenType: tokenInfo.TokenType, Expires: tokenInfo.ExpiresIn, RefreshToken: "", Success: true, } return string(utils.MustMarshal(tokenInfo2)), "" } } return "", "" }, func(value string) { var tokenInfo ElmTokenForCompatible err := utils.UnmarshalUseNumber([]byte(value), &tokenInfo) if err == nil { api.ElmAPI.SetToken(tokenInfo.AccessToken) } }) } return err } func RefreshWeimobToken() (err error) { if api.WeimobAPI != nil { err = RefreshConfig("weimob", weimobTokenExpires, func() (string, string) { if beego.BConfig.RunMode == "prod" { if tokenInfo, err := api.WeimobAPI.RefreshTokenByRefreshToken(); err == nil { return string(utils.MustMarshal(tokenInfo)), utils.Time2Str(time.Now().Add((time.Duration(tokenInfo.ExpiresIn) - weimobTokenExpires/time.Second) * time.Second)) } } return "", "" }, func(value string) { var tokenInfo *weimobapi.TokenInfo err := utils.UnmarshalUseNumber([]byte(value), &tokenInfo) if err == nil { api.WeimobAPI.SetToken(tokenInfo) } }) } return err } func RefreshDingDingToken() (err error) { api.DingDingAPI.RetrieveToken() return RefreshConfig("dingding", dingdingTokenExpires, func() (string, string) { globals.SugarLogger.Debugf("RefreshDingDingToken RunMode:%s", beego.BConfig.RunMode) if true { //beego.BConfig.RunMode == "prod" { if token, err := api.DingDingAPI.RetrieveToken(); err == nil { globals.SugarLogger.Debugf("RefreshDingDingToken tokenInfo:%s", token) return token, "" } else { globals.SugarLogger.Errorf("RefreshDingDingToken RefreshToken failed with error:%v", err) } } return "", "" }, func(value string) { api.DingDingAPI.SetToken(value) }) } func SaveWeimobToken(token *weimobapi.TokenInfo) (err error) { db := dao.GetDB() config := &legacymodel.Config{ Thirdparty: "weimob", Token: string(utils.MustMarshal(token)), Date: utils.Time2Str(time.Now().Add((time.Duration(token.ExpiresIn) - weimobTokenExpires/time.Second) * time.Second)), LastOperator: model.AdminName, } return dao.CreateOrUpdate(db, config) } func RefreshYilianyunToken() (err error) { return RefreshConfig("yilianyun", yilianyunTokenExpires, func() (string, string) { globals.SugarLogger.Debugf("RefreshYilianyunToken RunMode:%s", beego.BConfig.RunMode) if beego.BConfig.RunMode == "prod" { if tokenInfo, err := api.YilianyunAPI.RetrieveToken(); err == nil { return string(utils.MustMarshal(tokenInfo)), "" } else { globals.SugarLogger.Errorf("RefreshYilianyunToken RefreshToken failed with error:%v", err) } } return "", "" }, func(value string) { var tokenInfo *yilianyunapi.TokenInfo err := utils.UnmarshalUseNumber([]byte(value), &tokenInfo) if err == nil { api.YilianyunAPI.SetToken(tokenInfo.AccessToken) } }) } func getWXTokenFromProd(oldToken string) (token string) { token = oldToken if globals.GetWeixinTokenKey != "" && globals.GetWeixinTokenURL != "" { for { waitSecond := 5 * 60 response, err := http.Get(fmt.Sprintf("%s?accessKey=%s&oldToken=%s&waitSecond=%d", globals.GetWeixinTokenURL, globals.GetWeixinTokenKey, oldToken, waitSecond)) if err == nil { defer response.Body.Close() if response.StatusCode == http.StatusOK { data, err2 := ioutil.ReadAll(response.Body) if err = err2; err == nil { var result CallResult if err = utils.UnmarshalUseNumber(data, &result); err == nil { if result.Code == "0" { token = strings.Replace(result.Data, "\"", "", -1) break } else { err = fmt.Errorf("return code is:%s", result.Code) } } } } else { err = platformapi.ErrHTTPCodeIsNot200 } } if err != nil { globals.SugarLogger.Infof("getWXTokenFromProd failed with error:%v", err) time.Sleep(30 * time.Second) } } } return token }