mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(backend/data):添加腾讯股票数据接口支持
- 新增腾讯股票数据接口 URL - 实现腾讯股票数据解析逻辑,支持港股和 A 股 - 更新 GetStockCodeRealTimeData 方法,支持腾讯股票数据 - 添加腾讯股票数据解析单元测试
This commit is contained in:
parent
8ba26b6250
commit
338064e536
@ -32,6 +32,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
|
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
|
||||||
|
const txStockUrl = "http://qt.gtimg.cn/?_=%d&q=%s"
|
||||||
|
|
||||||
const tushareApiUrl = "http://api.tushare.pro"
|
const tushareApiUrl = "http://api.tushare.pro"
|
||||||
|
|
||||||
type StockDataApi struct {
|
type StockDataApi struct {
|
||||||
@ -291,8 +293,55 @@ func (receiver StockDataApi) GetStockBaseInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
|
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
|
||||||
|
stockInfos := make([]StockInfo, 0)
|
||||||
|
|
||||||
codes := slice.JoinFunc(StockCodes, ",", func(s string) string {
|
hkcodes := slice.Filter(StockCodes, func(i int, s string) bool {
|
||||||
|
return strutil.HasPrefixAny(s, []string{"hk", "HK"})
|
||||||
|
})
|
||||||
|
|
||||||
|
if hkcodes != nil && len(hkcodes) > 0 {
|
||||||
|
hkcodesStr := slice.JoinFunc(hkcodes, ",", func(s string) string {
|
||||||
|
return "r_" + strings.ToLower(s)
|
||||||
|
})
|
||||||
|
url := fmt.Sprintf(txStockUrl, time.Now().Unix(), hkcodesStr)
|
||||||
|
resp, err := receiver.client.R().
|
||||||
|
SetHeader("Host", "qt.gtimg.cn").
|
||||||
|
SetHeader("Referer", "https://gu.qq.com/").
|
||||||
|
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
|
||||||
|
Get(url)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err.Error())
|
||||||
|
return &[]StockInfo{}, err
|
||||||
|
}
|
||||||
|
str := GB18030ToUTF8(resp.Body())
|
||||||
|
dataStr := strutil.SplitAndTrim(strings.Trim(str, "\n"), ";")
|
||||||
|
if len(dataStr) == 0 {
|
||||||
|
return &[]StockInfo{}, errors.New("获取股票信息失败,请检查股票代码是否正确")
|
||||||
|
}
|
||||||
|
for _, data := range dataStr {
|
||||||
|
stockData, err := ParseTxStockData(data)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stockInfos = append(stockInfos, *stockData)
|
||||||
|
go func() {
|
||||||
|
var count int64
|
||||||
|
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Count(&count)
|
||||||
|
if count == 0 {
|
||||||
|
db.Dao.Model(&StockInfo{}).Create(stockData)
|
||||||
|
} else {
|
||||||
|
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Updates(stockData)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
szzsusCodes := slice.Filter(StockCodes, func(i int, s string) bool {
|
||||||
|
return !strutil.HasPrefixAny(s, []string{"hk", "HK"})
|
||||||
|
})
|
||||||
|
|
||||||
|
codes := slice.JoinFunc(szzsusCodes, ",", func(s string) string {
|
||||||
if strings.HasPrefix(s, "us") {
|
if strings.HasPrefix(s, "us") {
|
||||||
s = strings.Replace(s, "us", "gb_", 1)
|
s = strings.Replace(s, "us", "gb_", 1)
|
||||||
}
|
}
|
||||||
@ -314,7 +363,6 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]
|
|||||||
return &[]StockInfo{}, err
|
return &[]StockInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stockInfos := make([]StockInfo, 0)
|
|
||||||
str := GB18030ToUTF8(resp.Body())
|
str := GB18030ToUTF8(resp.Body())
|
||||||
dataStr := strutil.SplitEx(str, "\n", true)
|
dataStr := strutil.SplitEx(str, "\n", true)
|
||||||
if len(dataStr) == 0 {
|
if len(dataStr) == 0 {
|
||||||
@ -509,6 +557,105 @@ func GB18030ToUTF8(bs []byte) string {
|
|||||||
return string(d)
|
return string(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseTxStockData(data string) (*StockInfo, error) {
|
||||||
|
//v_r_hk09660="100~地平线机器人-W~09660~6.240~5.690~5.800~192659034.0~0~0~6.240~0~0~0~0~0~0~0~0~0~6.240~0~0~0~0~0~0~0~0~0~192659034.0~2025/04/29
|
||||||
|
//13:41:04~0.550~9.67~6.450~5.710~6.240~192659034.0~1180471843.140~0~32.51~~0~0~13.01~691.1364~823.6983~HORIZONROBOT-W~0.00~10.380~3.320~1.07~-16.03~0~0~0~0~0~32.51~6.40~1.74~600~73.33~17.96~GP~19.70~11.51~-0.95~-18.54~44.44~13200293682.00~11075904412.00~32.51~0.000~6.127~56.39~HKD~1~30";
|
||||||
|
datas := strutil.SplitAndTrim(data, "=", "\"")
|
||||||
|
if len(datas) < 2 {
|
||||||
|
return nil, fmt.Errorf("invalid data format")
|
||||||
|
}
|
||||||
|
var result map[string]string
|
||||||
|
var err error
|
||||||
|
if strutil.ContainsAny(datas[0], []string{"v_r_hk", "v_hk"}) {
|
||||||
|
result, err = ParseTxHKStockData(datas)
|
||||||
|
}
|
||||||
|
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||||
|
marshal, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("json.Marshal error:%s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成marshal: %s", marshal)
|
||||||
|
stockInfo := &StockInfo{}
|
||||||
|
err = json.Unmarshal(marshal, &stockInfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("json.Unmarshal error:%s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成stockInfo: %+v", stockInfo)
|
||||||
|
|
||||||
|
return stockInfo, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseTxHKStockData(datas []string) (map[string]string, error) {
|
||||||
|
//v_r_hk09660="
|
||||||
|
//100~ 0
|
||||||
|
//地平线机器人-W~ 1
|
||||||
|
//09660~ 2
|
||||||
|
//6.270~ 3 当前价
|
||||||
|
//5.690~ 4 昨收价
|
||||||
|
//5.800~ 5 开盘价
|
||||||
|
//195083034.0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//6.270~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//0~
|
||||||
|
//6.270~
|
||||||
|
//0~0~0~0~0~0~0~0~0~
|
||||||
|
//195083034.0~
|
||||||
|
//2025/04/29 13:45:41~ 30 当前时间
|
||||||
|
//0.580~
|
||||||
|
//10.19~
|
||||||
|
//6.450~ 最高价
|
||||||
|
//5.710~ 最低价
|
||||||
|
//6.270~
|
||||||
|
//195083034.0~
|
||||||
|
//1195673623.140~
|
||||||
|
//0~
|
||||||
|
//32.66
|
||||||
|
//~~0~0~13.01~694.4592~827.6584~HORIZONROBOT-W~0.00~10.380~3.320~1.06~-18.71~0~0~0~0~0~32.66~6.43~1.76~600~74.17~18.53~GP~19.70~11.51~-0.48~-18.15~45.14~13200293682.00~11075904412.00~32.66~0.000~6.129~57.14~HKD~1~30";
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
stockCode := strutil.ReplaceWithMap(datas[0], map[string]string{
|
||||||
|
"v_r_": "",
|
||||||
|
"v_": "",
|
||||||
|
})
|
||||||
|
result["股票代码"] = stockCode
|
||||||
|
|
||||||
|
parts := strutil.SplitAndTrim(datas[1], "~")
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成 len: %v", len(parts))
|
||||||
|
if len(parts) < 35 {
|
||||||
|
return nil, fmt.Errorf("invalid data format")
|
||||||
|
}
|
||||||
|
result["股票名称"] = parts[1]
|
||||||
|
result["当前价格"] = parts[3]
|
||||||
|
result["昨日收盘价"] = parts[4]
|
||||||
|
result["今日开盘价"] = parts[5]
|
||||||
|
|
||||||
|
result["今日最高价"] = parts[33]
|
||||||
|
result["今日最低价"] = parts[34]
|
||||||
|
timestr := strutil.ReplaceWithMap(parts[30], map[string]string{
|
||||||
|
"/": "-",
|
||||||
|
})
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成 时间: %v", timestr)
|
||||||
|
result["日期"] = strutil.SplitAndTrim(timestr, " ", "")[0]
|
||||||
|
result["时间"] = strutil.SplitAndTrim(timestr, " ", "")[1]
|
||||||
|
|
||||||
|
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
||||||
datas := strutil.SplitAndTrim(data, "=", "\"")
|
datas := strutil.SplitAndTrim(data, "=", "\"")
|
||||||
if len(datas) < 2 {
|
if len(datas) < 2 {
|
||||||
|
@ -109,6 +109,12 @@ func TestGetHKStockInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseTxStockData(t *testing.T) {
|
||||||
|
str := "v_r_hk09660=\"100~地平线机器人-W~09660~6.340~5.690~5.800~210980204.0~0~0~6.340~0~0~0~0~0~0~0~0~0~6.340~0~0~0~0~0~0~0~0~0~210980204.0~2025/04/29\n14:14:52~0.650~11.42~6.450~5.710~6.340~210980204.0~1295585259.040~0~33.03~~0~0~13.01~702.2123~836.8986~HORIZONROBOT-W~0.00~10.380~3.320~1.00~-53.74~0~0~0~0~0~33.03~6.50~1.90~600~76.11~19.85~GP~19.70~11.51~0.63~-17.23~46.76~13200293682.00~11075904412.00~33.03~0.000~6.141~58.90~HKD~1~30\";"
|
||||||
|
info, _ := ParseTxStockData(str)
|
||||||
|
logger.SugaredLogger.Infof("%+#v", info)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRealTimeStockPriceInfo(t *testing.T) {
|
func TestGetRealTimeStockPriceInfo(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -167,7 +173,7 @@ func TestParseFullSingleStockData(t *testing.T) {
|
|||||||
func TestNewStockDataApi(t *testing.T) {
|
func TestNewStockDataApi(t *testing.T) {
|
||||||
db.Init("../../data/stock.db")
|
db.Init("../../data/stock.db")
|
||||||
stockDataApi := NewStockDataApi()
|
stockDataApi := NewStockDataApi()
|
||||||
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745", "gb_tsla")
|
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745", "gb_tsla", "hk09660", "hk00700")
|
||||||
for _, data := range *datas {
|
for _, data := range *datas {
|
||||||
t.Log(data)
|
t.Log(data)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user