|
@@ -0,0 +1,323 @@
|
|
|
+package services
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "github.com/shopspring/decimal"
|
|
|
+ "hongze/hongze_edb_lib/models"
|
|
|
+ "hongze/hongze_edb_lib/models/future_good"
|
|
|
+ "hongze/hongze_edb_lib/services/alarm_msg"
|
|
|
+ "hongze/hongze_edb_lib/utils"
|
|
|
+ "io"
|
|
|
+ netHttp "net/http"
|
|
|
+ "reflect"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// refreshToken 同花顺刷新token
|
|
|
+var refreshToken = `eyJzaWduX3RpbWUiOiIyMDIzLTAzLTI0IDEzOjQ3OjExIn0=.eyJ1aWQiOiI1NzY2NDgxMDkifQ==.339B8D21168AC21A0F80840544E38378AB2D04A02D325F0CD1C44251915233F6`
|
|
|
+var tokenRedisKey = `THS_SERVER_ACCESS_TOKEN`
|
|
|
+
|
|
|
+// getEdbDataFromThsHttp 通过url获取同花顺的普通数据
|
|
|
+func getEdbDataFromThsHttp(edbCode, startDate, endDate string) (item models.EdbDataFromThs, err error) {
|
|
|
+ thsUrl := `https://quantapi.51ifind.com/api/v1/edb_service`
|
|
|
+ //indicators 是 半角逗号分隔的所有指标,宏观指标过多,推荐使用Windows超级命令生成。 "indicators":"M001620326,M002822183"
|
|
|
+ //functionpara 否 key-value格式,省略时不进行更新时间筛选。两个时间控件更新起始时间(startrtime)和更新结束时间(endrtime),不勾选时省略见下方代码块
|
|
|
+ //startdate 是 开始日期,支持”YYYYMMDD"”YYYY-MM-DD"”YYYY/MM/DD"三种时间格式 "startdate":"2018-01-01"
|
|
|
+ //enddate 是 结束日期,支持”YYYYMMDD"”YYYY-MM-DD"”YYYY/MM/DD"三种日期格式 "enddate":"2018-01-01
|
|
|
+ //发送创建请求
|
|
|
+ dataMap := map[string]interface{}{
|
|
|
+ "indicators": edbCode,
|
|
|
+ "startdate": startDate,
|
|
|
+ "enddate": endDate,
|
|
|
+ }
|
|
|
+
|
|
|
+ body, err, _ := postCurl(thsUrl, dataMap, 0)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ tmpItems := new(EdbDataFromThsInterface)
|
|
|
+ err = json.Unmarshal(body, &tmpItems)
|
|
|
+ if err != nil {
|
|
|
+ err = errors.New("GetEdbDataFromThs json.Unmarshal Err:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if tmpItems.Errorcode != 0 {
|
|
|
+ err = errors.New(tmpItems.Errmsg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 因为table里面的value有的时候返回的是string,有的是float64,所以需要用interface来反射取值
|
|
|
+ tablesList := make([]models.Tables, 0)
|
|
|
+ for _, table := range tmpItems.Tables {
|
|
|
+ tableIdList := make([]string, 0)
|
|
|
+ tableTimeList := make([]string, 0)
|
|
|
+ tableValueList := make([]float64, 0)
|
|
|
+
|
|
|
+ for _, tableId := range table.ID {
|
|
|
+ tableIdList = append(tableIdList, tableId)
|
|
|
+ }
|
|
|
+ for _, tableTime := range table.Time {
|
|
|
+ tableTimeList = append(tableTimeList, tableTime)
|
|
|
+ }
|
|
|
+
|
|
|
+ //指标数据
|
|
|
+ for _, tmpValue := range table.Value {
|
|
|
+ var tableValue float64
|
|
|
+ if reflect.TypeOf(tmpValue).Kind() == reflect.Float64 {
|
|
|
+ tableValue = reflect.ValueOf(tmpValue).Float()
|
|
|
+ } else if reflect.TypeOf(tmpValue).Kind() == reflect.String {
|
|
|
+ tmpTableValue, tmpErr := decimal.NewFromString(reflect.ValueOf(tmpValue).String())
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = tmpErr
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tableValue, _ = tmpTableValue.Truncate(4).Float64()
|
|
|
+ } else {
|
|
|
+ err = errors.New("错误的数据类型" + reflect.TypeOf(tmpValue).String())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tableValueList = append(tableValueList, tableValue)
|
|
|
+ }
|
|
|
+ tmpTable := models.Tables{
|
|
|
+ ID: tableIdList,
|
|
|
+ Time: tableTimeList,
|
|
|
+ Value: tableValueList,
|
|
|
+ }
|
|
|
+ tablesList = append(tablesList, tmpTable)
|
|
|
+ }
|
|
|
+ item = models.EdbDataFromThs{
|
|
|
+ DataVol: tmpItems.DataVol,
|
|
|
+ Errmsg: tmpItems.Errmsg,
|
|
|
+ Errorcode: tmpItems.Errorcode,
|
|
|
+ Perf: tmpItems.Perf,
|
|
|
+ Tables: tablesList,
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// getFutureGoodDataFromThsHttp 通过url获取同花顺的商品数据
|
|
|
+func getFutureGoodDataFromThsHttp(edbCode, startDate, endDate string) (item future_good.FutureGoodDataFromThs, err error) {
|
|
|
+ thsUrl := `https://quantapi.51ifind.com/api/v1/cmd_history_quotation`
|
|
|
+
|
|
|
+ //codes 是 半角逗号分隔的所有代码 "codes":"300033.SZ,600030.SH"
|
|
|
+ //indicators 是 半角逗号分隔的所有指标 "indicators":"preClose,open"
|
|
|
+ //functionpara 否 /key-value格式。所有key均取默认时,functionpara省略。 见下方说明
|
|
|
+ //startdate 是 开始日期,支持"YYYYMMDD""YYYY-MMDD""YYYY/MM/DD"三种日期格式
|
|
|
+ //"startdate":"2018-01-01"
|
|
|
+ //enddate 是 结束日期,支持"YYYYMMDD""YYYY-MMDD""YYYY/MM/DD"三种日期格式
|
|
|
+ //发送创建请求
|
|
|
+ dataMap := map[string]interface{}{
|
|
|
+ "codes": edbCode,
|
|
|
+ "indicators": `lastclose,open,high,low,close,avgprice,change,changeper,volume,amount,hsl,lastsettlement,settlement,zdsettlement,zdfsettlement,ccl,ccbd,zf,zjlx,zjcd`,
|
|
|
+ "startdate": startDate,
|
|
|
+ "enddate": endDate,
|
|
|
+ }
|
|
|
+
|
|
|
+ body, err, _ := postCurl(thsUrl, dataMap, 0)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ tmpItems := new(FutureGoodDataFromThsInterface)
|
|
|
+ err = json.Unmarshal(body, &tmpItems)
|
|
|
+ if err != nil {
|
|
|
+ err = errors.New("GetEdbDataFromThs json.Unmarshal Err:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if tmpItems.Errorcode != 0 {
|
|
|
+ err = errors.New(tmpItems.Errmsg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(tmpItems.Tables) <= 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ table := tmpItems.Tables[0]
|
|
|
+ item = future_good.FutureGoodDataFromThs{
|
|
|
+ DataVol: tmpItems.DataVol,
|
|
|
+ Errmsg: tmpItems.Errmsg,
|
|
|
+ Errorcode: tmpItems.Errorcode,
|
|
|
+ Perf: tmpItems.Perf,
|
|
|
+ Tables: future_good.FutureGoodDataTables{
|
|
|
+ Time: table.Time,
|
|
|
+ Open: table.Table.Open,
|
|
|
+ High: table.Table.High,
|
|
|
+ Low: table.Table.Low,
|
|
|
+ Close: table.Table.Close,
|
|
|
+ Volume: table.Table.Volume,
|
|
|
+ Amount: table.Table.Amount,
|
|
|
+ Ccl: table.Table.Ccl,
|
|
|
+ Settlement: table.Table.Settlement,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// BaseThsInterface 同花顺基础返回
|
|
|
+type BaseThsInterface struct {
|
|
|
+ ErrMsg string `json:"errmsg"`
|
|
|
+ ErrorCode int64 `json:"errorcode"`
|
|
|
+ Tables interface{} `json:"tables"`
|
|
|
+}
|
|
|
+
|
|
|
+// postCurl post请求同花顺接口
|
|
|
+func postCurl(urlStr string, dataMap map[string]interface{}, num int) (body []byte, err error, errMsg string) {
|
|
|
+ logMsg := ``
|
|
|
+ defer func() {
|
|
|
+ if err != nil {
|
|
|
+ if logMsg != `` {
|
|
|
+ errMsg = logMsg
|
|
|
+ go alarm_msg.SendAlarmMsg("post请求同花顺接口失败,ERR:"+err.Error()+";errMsg:"+errMsg, 3)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ token, err := GetAccessToken(false)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ jsonStrByte, err := json.Marshal(dataMap)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ reqStr := string(jsonStrByte)
|
|
|
+ req, _ := netHttp.NewRequest("POST", urlStr, strings.NewReader(reqStr))
|
|
|
+ req.Header.Add("Content-Type", "application/json")
|
|
|
+ req.Header.Add("access_token", token)
|
|
|
+
|
|
|
+ res, _ := netHttp.DefaultClient.Do(req)
|
|
|
+ defer res.Body.Close()
|
|
|
+ //解析resp并且存入关联表
|
|
|
+ body, err = io.ReadAll(res.Body)
|
|
|
+ if err != nil {
|
|
|
+ logMsg = fmt.Sprint("post err; request:", reqStr, "; errMsg:", err.Error())
|
|
|
+ utils.FileLog.Info(logMsg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ //logMsg = fmt.Sprint("post request:", reqStr, "; response:", string(body))
|
|
|
+ //utils.FileLog.Info(logMsg)
|
|
|
+ logMsg = fmt.Sprint("post request url:", urlStr, ";token:", token, ";params:", reqStr, ";response:", string(body))
|
|
|
+
|
|
|
+ var response BaseThsInterface
|
|
|
+ err = json.Unmarshal(body, &response)
|
|
|
+ if err != nil {
|
|
|
+ utils.FileLog.Info("post Err:", err.Error(), ";url:", urlStr, ";params:", reqStr, ";response:", string(body))
|
|
|
+ err = errors.New("Unmarshal Err:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ utils.FileLog.Info(fmt.Sprint("post request url:", urlStr, ";token:", token, ";params:", reqStr, ";response:", string(body)))
|
|
|
+
|
|
|
+ //如果是token失效,同时只是第一次请求(没有尝试强制刷新token,那么重新请求)
|
|
|
+ if utils.InArrayByInt([]int{-1010, -1302}, int(response.ErrorCode)) && num <= 0 {
|
|
|
+ //token失效
|
|
|
+ _, tmpErr := refreshAccessToken()
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = tmpErr
|
|
|
+ }
|
|
|
+ num++
|
|
|
+ return postCurl(urlStr, dataMap, num)
|
|
|
+ } else if response.ErrorCode != 0 {
|
|
|
+ utils.FileLog.Info(fmt.Sprint("post data err:", response.ErrMsg, ";url:", urlStr, ";params:", reqStr, ";response:", string(body)))
|
|
|
+ err = errors.New(response.ErrMsg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// GetAccessToken 获取accessToken
|
|
|
+func GetAccessToken(isRefresh bool) (token string, err error) {
|
|
|
+ defer func() {
|
|
|
+ if err != nil {
|
|
|
+ go alarm_msg.SendAlarmMsg("获取同花顺的token失败,ERR:"+err.Error(), 3)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ token, redisErr := utils.Rc.RedisString(tokenRedisKey)
|
|
|
+ //如果从redis中accessToken 获取失败或者token为空了,再或者需要强制刷新了,那么重新获取accessToken
|
|
|
+ if redisErr != nil || token == `` || isRefresh {
|
|
|
+ return refreshAccessToken()
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// refreshAccessToken 强制刷新获取accessToken
|
|
|
+func refreshAccessToken() (token string, err error) {
|
|
|
+ defer func() {
|
|
|
+ if err != nil {
|
|
|
+ go alarm_msg.SendAlarmMsg("刷新同花顺的token失败;ERR:"+err.Error(), 3)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ tokenInfo, tmpErr := getAccessToken()
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = tmpErr
|
|
|
+ return
|
|
|
+ }
|
|
|
+ token = tokenInfo.AccessToken
|
|
|
+
|
|
|
+ expireTime, err := time.ParseInLocation(utils.FormatDateTime, tokenInfo.ExpiredTime, time.Local)
|
|
|
+ if err != nil {
|
|
|
+ go alarm_msg.SendAlarmMsg("获取同花顺的token失败;同花顺token截止日期转换失败,ERR:"+err.Error(), 3)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //token存入redis
|
|
|
+ //err = utils.Rc.Put(tokenRedisKey, token, time.Duration(expireTime.Unix()-600)*time.Second)
|
|
|
+ // 本来是要设置下600s的过期时间,但因为不是强制刷新token,就不获取了
|
|
|
+ err = utils.Rc.Put(tokenRedisKey, token, time.Duration(expireTime.Unix())*time.Second)
|
|
|
+ if err != nil {
|
|
|
+ go alarm_msg.SendAlarmMsg("获取同花顺的token成功;同花顺token存入redis失败,ERR:"+err.Error(), 3)
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+type GetTokenResp struct {
|
|
|
+ ErrorCode int `json:"errorcode"`
|
|
|
+ ErrMsg string `json:"errmsg"`
|
|
|
+ Data TokenData `json:"data"`
|
|
|
+}
|
|
|
+
|
|
|
+type TokenData struct {
|
|
|
+ AccessToken string `json:"access_token"`
|
|
|
+ //ExpireIn int `json:"expire_in"`
|
|
|
+ ExpiredTime string `json:"expired_time"`
|
|
|
+}
|
|
|
+
|
|
|
+// getAccessToken token内部请求接口
|
|
|
+func getAccessToken() (tokenData TokenData, err error) {
|
|
|
+ defer func() {
|
|
|
+ if err != nil {
|
|
|
+ go alarm_msg.SendAlarmMsg("更新同花顺的token失败;ERR:"+err.Error(), 3)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ //getUrl := `https://quantapi.51ifind.com/api/v1/update_access_token` // 强制刷新token;目前因为生产和测试都是使用的一个账号,所以token不能因为环境的不同而改变
|
|
|
+ getUrl := `https://quantapi.51ifind.com/api/v1/get_access_token` // 获取当前token
|
|
|
+
|
|
|
+ req, _ := netHttp.NewRequest("GET", getUrl, nil)
|
|
|
+ req.Header.Add("Content-Type", "application/json")
|
|
|
+ req.Header.Add("refresh_token", refreshToken)
|
|
|
+
|
|
|
+ res, _ := netHttp.DefaultClient.Do(req)
|
|
|
+ defer res.Body.Close()
|
|
|
+ body, err := io.ReadAll(res.Body)
|
|
|
+ if err != nil {
|
|
|
+ err = errors.New("NewRequest Err:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ utils.FileLog.Info("同花顺刷新token:" + string(body))
|
|
|
+
|
|
|
+ var tokenResp GetTokenResp
|
|
|
+ err = json.Unmarshal(body, &tokenResp)
|
|
|
+ if err != nil {
|
|
|
+ err = errors.New("Unmarshal Err:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if tokenResp.ErrorCode != 0 {
|
|
|
+ err = errors.New("getAccessToken err:" + tokenResp.ErrMsg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tokenData = tokenResp.Data
|
|
|
+ return
|
|
|
+}
|