review code.
This commit is contained in:
29
cmd/main.go
29
cmd/main.go
@@ -71,20 +71,18 @@ func main() {
|
|||||||
|
|
||||||
// runCollection 执行一次数据采集和存储
|
// runCollection 执行一次数据采集和存储
|
||||||
func runCollection(coll *collector.Collector, store *storage.Storage) {
|
func runCollection(coll *collector.Collector, store *storage.Storage) {
|
||||||
log.Println("开始采集...")
|
// 检查是否为开市时间
|
||||||
|
|
||||||
// 计算Ymd
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
ymd := now.Year()*10000 + int(now.Month())*100 + now.Day()
|
if !collector.IsTradingTime(now) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("开始采集...")
|
||||||
|
|
||||||
// 采集数据并检查变化
|
// 采集数据并检查变化
|
||||||
status, dataHash, changed, err := coll.CollectAndCheck()
|
status, dataHash, changed, err := coll.CollectAndCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("采集失败: %v", err)
|
log.Printf("采集失败: %v", err)
|
||||||
// 记录失败的日志
|
|
||||||
if err := store.SaveCollectionLog("", ymd, false, err.Error()); err != nil {
|
|
||||||
log.Printf("保存采集日志失败: %v", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,27 +92,20 @@ func runCollection(coll *collector.Collector, store *storage.Storage) {
|
|||||||
// 如果数据没有变化,只记录日志
|
// 如果数据没有变化,只记录日志
|
||||||
if !changed {
|
if !changed {
|
||||||
log.Println("数据未变化,跳过存储")
|
log.Println("数据未变化,跳过存储")
|
||||||
if err := store.SaveCollectionLog(dataHash, ymd, false, "数据未变化"); err != nil {
|
|
||||||
log.Printf("保存采集日志失败: %v", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据有变化,保存到数据库
|
// 数据有变化,保存到数据库
|
||||||
log.Println("数据已变化,开始存储到数据库...")
|
log.Println("数据已变化,开始存储到数据库...")
|
||||||
if err := store.SaveStatus(status, dataHash); err != nil {
|
if err := store.SaveData(status); err != nil {
|
||||||
log.Printf("保存数据失败: %v", err)
|
log.Printf("保存数据失败: %v", err)
|
||||||
// 记录失败的日志
|
|
||||||
if err := store.SaveCollectionLog(dataHash, ymd, true, err.Error()); err != nil {
|
|
||||||
log.Printf("保存采集日志失败: %v", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录成功的日志
|
// 记录成功的日志
|
||||||
if err := store.SaveCollectionLog(dataHash, ymd, true, "数据保存成功"); err != nil {
|
// if err := store.SaveCollectionLog(dataHash, ymd, true, "数据保存成功"); err != nil {
|
||||||
log.Printf("保存采集日志失败: %v", err)
|
// log.Printf("保存采集日志失败: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Printf("数据存储成功 - 资产账户: %s, 订单数: %d, 持仓数: %d, 行情数: %d",
|
log.Printf("数据存储成功 - 资产账户: %s, 订单数: %d, 持仓数: %d, 行情数: %d",
|
||||||
status.Data.Assets.AccountID,
|
status.Data.Assets.AccountID,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func NewCollector(url string) *Collector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchData 从HTTP接口获取数据
|
// FetchData 从HTTP接口获取数据
|
||||||
func (c *Collector) FetchData() (*types.Status, error) {
|
func (c *Collector) FetchData() (*types.StatusData, error) {
|
||||||
resp, err := c.httpClient.Get(c.url)
|
resp, err := c.httpClient.Get(c.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("HTTP请求失败: %w", err)
|
return nil, fmt.Errorf("HTTP请求失败: %w", err)
|
||||||
@@ -47,7 +47,7 @@ func (c *Collector) FetchData() (*types.Status, error) {
|
|||||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var status types.Status
|
var status types.StatusData
|
||||||
if err := json.Unmarshal(body, &status); err != nil {
|
if err := json.Unmarshal(body, &status); err != nil {
|
||||||
return nil, fmt.Errorf("JSON解析失败: %w", err)
|
return nil, fmt.Errorf("JSON解析失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (c *Collector) FetchData() (*types.Status, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CalculateHash 计算数据的SHA256哈希值
|
// CalculateHash 计算数据的SHA256哈希值
|
||||||
func (c *Collector) CalculateHash(status *types.Status) (string, error) {
|
func (c *Collector) CalculateHash(status *types.StatusData) (string, error) {
|
||||||
// 将数据序列化为JSON
|
// 将数据序列化为JSON
|
||||||
data, err := json.Marshal(status)
|
data, err := json.Marshal(status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,7 +87,7 @@ func (c *Collector) GetLastHash() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CollectAndCheck 采集数据并检查是否变化
|
// CollectAndCheck 采集数据并检查是否变化
|
||||||
func (c *Collector) CollectAndCheck() (*types.Status, string, bool, error) {
|
func (c *Collector) CollectAndCheck() (*types.StatusData, string, bool, error) {
|
||||||
// 获取数据
|
// 获取数据
|
||||||
status, err := c.FetchData()
|
status, err := c.FetchData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
41
collector/utils.go
Normal file
41
collector/utils.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTradingTime 判断当前时间是否为A股交易时间
|
||||||
|
// A股交易时间:
|
||||||
|
// - 工作日(周一至周五)
|
||||||
|
// - 上午:9:30 - 11:30
|
||||||
|
// - 下午:13:00 - 15:00
|
||||||
|
func IsTradingTime(t time.Time) bool {
|
||||||
|
// 检查是否为工作日(1=周一, 5=周五)
|
||||||
|
weekday := t.Weekday()
|
||||||
|
if weekday == time.Saturday || weekday == time.Sunday {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取小时和分钟
|
||||||
|
hour := t.Hour()
|
||||||
|
minute := t.Minute()
|
||||||
|
|
||||||
|
// 转换为分钟数便于比较
|
||||||
|
currentMinutes := hour*60 + minute
|
||||||
|
|
||||||
|
// 上午交易时间:9:30 - 11:30 (570 - 690分钟)
|
||||||
|
morningStart := 9*60 + 30 // 570
|
||||||
|
morningEnd := 11*60 + 30 // 690
|
||||||
|
|
||||||
|
// 下午交易时间:13:00 - 15:00 (780 - 900分钟)
|
||||||
|
afternoonStart := 13 * 60 // 780
|
||||||
|
afternoonEnd := 15 * 60 // 900
|
||||||
|
|
||||||
|
// 判断是否在交易时间段内
|
||||||
|
if (currentMinutes >= morningStart && currentMinutes < morningEnd) ||
|
||||||
|
(currentMinutes >= afternoonStart && currentMinutes < afternoonEnd) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
2649
exmple/status.json
2649
exmple/status.json
File diff suppressed because one or more lines are too long
14
go.mod
14
go.mod
@@ -4,16 +4,18 @@ go 1.26.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
gorm.io/driver/postgres v1.5.4
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.25.5
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
github.com/jackc/pgx/v5 v5.9.1 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
23
go.sum
23
go.sum
@@ -5,8 +5,14 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@@ -20,15 +26,32 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||||
|
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type CollectorAssets struct {
|
|||||||
MarketValue float64 `json:"market_value" gorm:"type:decimal(15,2);not null;default:0;column:market_value;comment:持仓市值"`
|
MarketValue float64 `json:"market_value" gorm:"type:decimal(15,2);not null;default:0;column:market_value;comment:持仓市值"`
|
||||||
Profit float64 `json:"profit" gorm:"type:decimal(15,2);not null;default:0;comment:当日盈亏"`
|
Profit float64 `json:"profit" gorm:"type:decimal(15,2);not null;default:0;comment:当日盈亏"`
|
||||||
TotalAsset float64 `json:"total_asset" gorm:"type:decimal(15,2);not null;default:0;column:total_asset;comment:总资产"`
|
TotalAsset float64 `json:"total_asset" gorm:"type:decimal(15,2);not null;default:0;column:total_asset;comment:总资产"`
|
||||||
DataHash string `json:"data_hash" gorm:"type:varchar(64);not null;index;comment:数据哈希值(用于变化检测)"`
|
|
||||||
CollectedAt time.Time `json:"collected_at" gorm:"not null;index;comment:数据采集时间"`
|
CollectedAt time.Time `json:"collected_at" gorm:"not null;index;comment:数据采集时间"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;comment:记录创建时间"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;comment:记录创建时间"`
|
||||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:软删除时间"`
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:软删除时间"`
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
go build -o ../builds/collector.exe ./cmd/main.go
|
go build -o D:\work\quant\qmt\bin\collector.exe ./cmd/main.go
|
||||||
@@ -86,210 +86,163 @@ func (s *Storage) AutoMigrate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveStatus 保存完整状态数据(使用事务)
|
// SaveStatus 保存完整状态数据(使用事务)
|
||||||
func (s *Storage) SaveStatus(status *types.Status, dataHash string) error {
|
func (s *Storage) SaveData(status *types.StatusData) error {
|
||||||
// 验证必要字段 - AccountID是所有Upsert的共同条件
|
// 验证必要字段 - AccountID是所有Upsert的共同条件
|
||||||
if status.Data.Assets.AccountID == "" {
|
if status.Data.Assets.AccountID == "" {
|
||||||
return fmt.Errorf("账户ID不能为空")
|
return fmt.Errorf("账户ID不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
// 计算Ymd (年月日数字格式,如20260407)
|
||||||
// 计算Ymd (年月日数字格式,如20260407)
|
now := time.Now()
|
||||||
now := time.Now()
|
ymd := now.Year()*10000 + int(now.Month())*100 + now.Day()
|
||||||
ymd := now.Year()*10000 + int(now.Month())*100 + now.Day()
|
|
||||||
|
|
||||||
// 保存资产快照 (先查询后更新/插入)
|
// 保存资产快照 (先查询后更新/插入)
|
||||||
var existingAsset models.CollectorAssets
|
var existingAsset models.CollectorAssets
|
||||||
err := tx.Where("account_id = ? AND ymd = ?", status.Data.Assets.AccountID, ymd).First(&existingAsset).Error
|
err := s.db.Where("account_id = ? AND ymd = ?", status.Data.Assets.AccountID, ymd).First(&existingAsset).Error
|
||||||
|
|
||||||
asset := models.CollectorAssets{
|
asset := models.CollectorAssets{
|
||||||
AccountID: status.Data.Assets.AccountID,
|
AccountID: status.Data.Assets.AccountID,
|
||||||
Ymd: ymd,
|
Ymd: ymd,
|
||||||
Cash: status.Data.Assets.Cash,
|
Cash: status.Data.Assets.Cash,
|
||||||
FrozenCash: status.Data.Assets.FrozenCash,
|
FrozenCash: status.Data.Assets.FrozenCash,
|
||||||
MarketValue: status.Data.Assets.MarketValue,
|
MarketValue: status.Data.Assets.MarketValue,
|
||||||
Profit: status.Data.Assets.Profit,
|
Profit: status.Data.Assets.Profit,
|
||||||
TotalAsset: status.Data.Assets.TotalAsset,
|
TotalAsset: status.Data.Assets.TotalAsset,
|
||||||
DataHash: dataHash,
|
CollectedAt: now,
|
||||||
CollectedAt: now,
|
}
|
||||||
|
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// 记录不存在,插入新记录
|
||||||
|
if err := s.db.Create(&asset).Error; err != nil {
|
||||||
|
return fmt.Errorf("插入资产快照失败: %w", err)
|
||||||
}
|
}
|
||||||
|
} else if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
// 查询出错
|
||||||
// 记录不存在,插入新记录
|
return fmt.Errorf("查询资产快照失败: %w", err)
|
||||||
if err := tx.Create(&asset).Error; err != nil {
|
} else {
|
||||||
return fmt.Errorf("插入资产快照失败: %w", err)
|
// 记录存在,更新现有记录
|
||||||
}
|
if asset.Cash != existingAsset.Cash || asset.FrozenCash != existingAsset.FrozenCash || asset.MarketValue != existingAsset.MarketValue || asset.Profit != existingAsset.Profit || asset.TotalAsset != existingAsset.TotalAsset {
|
||||||
} else if err != nil {
|
|
||||||
// 查询出错
|
|
||||||
return fmt.Errorf("查询资产快照失败: %w", err)
|
|
||||||
} else {
|
|
||||||
// 记录存在,更新现有记录
|
|
||||||
asset.ID = existingAsset.ID
|
asset.ID = existingAsset.ID
|
||||||
if err := tx.Save(&asset).Error; err != nil {
|
if err := s.db.Save(&asset).Error; err != nil {
|
||||||
return fmt.Errorf("更新资产快照失败: %w", err)
|
return fmt.Errorf("更新资产快照失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 批量保存订单 (先查询后更新/插入)
|
// 批量保存订单 (先查询后更新/插入)
|
||||||
if len(status.Data.Orders) > 0 {
|
if len(status.Data.Orders) > 0 {
|
||||||
for _, order := range status.Data.Orders {
|
for _, order := range status.Data.Orders {
|
||||||
// 验证必要条件: OrderID和StockCode
|
// 验证必要条件: OrderID和StockCode
|
||||||
if order.OrderID == 0 {
|
if order.OrderID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if order.StockCode == "" {
|
if order.StockCode == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 只存储成交的订单
|
// 只存储成交的订单
|
||||||
if order.OrderStatus != 56 {
|
if order.OrderStatus != 56 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var open_price float64
|
var open_price float64
|
||||||
var info types.OrderInfo
|
var info types.OrderInfo
|
||||||
err = json.Unmarshal([]byte(order.OrderRemark), &info)
|
err = json.Unmarshal([]byte(order.OrderRemark), &info)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
open_price = info.Op
|
open_price = info.Op
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询是否存在
|
// 查询是否存在
|
||||||
var existingOrder models.CollectorOrder
|
var cnt int64
|
||||||
err := tx.Where("account_id = ? AND order_id = ? AND ymd = ?",
|
s.db.Where("account_id = ? AND order_id = ? AND ymd = ?", status.Data.Assets.AccountID, order.OrderID, ymd).Count(&cnt)
|
||||||
status.Data.Assets.AccountID, order.OrderID, ymd).First(&existingOrder).Error
|
if cnt > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
orderRecord := models.CollectorOrder{
|
orderRecord := models.CollectorOrder{
|
||||||
OrderID: order.OrderID,
|
OrderID: order.OrderID,
|
||||||
AccountID: status.Data.Assets.AccountID,
|
AccountID: status.Data.Assets.AccountID,
|
||||||
StockCode: order.StockCode,
|
StockCode: order.StockCode,
|
||||||
Ymd: ymd,
|
Ymd: ymd,
|
||||||
Price: order.Price,
|
Price: order.Price,
|
||||||
Volume: order.Volume,
|
Volume: order.Volume,
|
||||||
OpenPrice: open_price,
|
OpenPrice: open_price,
|
||||||
TradedPrice: order.TradedPrice,
|
TradedPrice: order.TradedPrice,
|
||||||
TradedVolume: order.TradedVolume,
|
TradedVolume: order.TradedVolume,
|
||||||
OrderStatus: order.OrderStatus,
|
OrderStatus: order.OrderStatus,
|
||||||
OrderTime: order.OrderTime,
|
OrderTime: order.OrderTime,
|
||||||
OrderRemark: order.OrderRemark,
|
OrderRemark: order.OrderRemark,
|
||||||
DataHash: dataHash,
|
OffsetFlag: order.OffsetFlag,
|
||||||
OffsetFlag: order.OffsetFlag,
|
CollectedAt: now,
|
||||||
CollectedAt: now,
|
CreatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
// 记录不存在,插入新记录
|
||||||
// 记录不存在,插入新记录
|
if err := s.db.Create(&orderRecord).Error; err != nil {
|
||||||
if err := tx.Create(&orderRecord).Error; err != nil {
|
return fmt.Errorf("插入订单失败: %w", err)
|
||||||
return fmt.Errorf("插入订单失败: %w", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
// 查询出错
|
|
||||||
return fmt.Errorf("查询订单失败: %w", err)
|
|
||||||
} else {
|
|
||||||
// 记录存在,更新现有记录
|
|
||||||
orderRecord.ID = existingOrder.ID
|
|
||||||
if err := tx.Save(&orderRecord).Error; err != nil {
|
|
||||||
return fmt.Errorf("更新订单失败: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 批量保存持仓 (先查询后更新/插入)
|
// 批量保存持仓 (先查询后更新/插入)
|
||||||
if len(status.Data.Positions) > 0 {
|
if len(status.Data.Positions) > 0 {
|
||||||
for _, pos := range status.Data.Positions {
|
for _, pos := range status.Data.Positions {
|
||||||
// 验证必要条件: Code
|
// 验证必要条件: Code
|
||||||
if pos.Code == "" {
|
if pos.Code == "" {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询是否存在
|
||||||
|
var existingPosition models.CollectorPosition
|
||||||
|
err := s.db.Where("account_id = ? AND code = ? AND ymd = ?",
|
||||||
|
status.Data.Assets.AccountID, pos.Code, ymd).First(&existingPosition).Error
|
||||||
|
|
||||||
|
positionRecord := models.CollectorPosition{
|
||||||
|
AccountID: status.Data.Assets.AccountID,
|
||||||
|
Code: pos.Code,
|
||||||
|
Ymd: ymd,
|
||||||
|
Volume: pos.Volume,
|
||||||
|
CanUseVolume: pos.CanUseVolume,
|
||||||
|
FrozenVolume: pos.FrozenVolume,
|
||||||
|
AvgPrice: pos.AvgPrice,
|
||||||
|
OpenPrice: pos.OpenPrice,
|
||||||
|
CurrentPrice: pos.CurrentPrice,
|
||||||
|
MarketValue: pos.MarketValue,
|
||||||
|
Profit: pos.Profit,
|
||||||
|
ProfitRate: pos.ProfitRate,
|
||||||
|
MinProfitRate: pos.MinProfitRate,
|
||||||
|
CollectedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// 记录不存在,插入新记录
|
||||||
|
if err := s.db.Create(&positionRecord).Error; err != nil {
|
||||||
|
return fmt.Errorf("插入持仓失败: %w", err)
|
||||||
}
|
}
|
||||||
|
} else if err != nil {
|
||||||
// 查询是否存在
|
// 查询出错
|
||||||
var existingPosition models.CollectorPosition
|
return fmt.Errorf("查询持仓失败: %w", err)
|
||||||
err := tx.Where("account_id = ? AND code = ? AND ymd = ?",
|
} else {
|
||||||
status.Data.Assets.AccountID, pos.Code, ymd).First(&existingPosition).Error
|
if positionRecord.Volume != existingPosition.Volume ||
|
||||||
|
positionRecord.CanUseVolume != existingPosition.CanUseVolume ||
|
||||||
positionRecord := models.CollectorPosition{
|
positionRecord.FrozenVolume != existingPosition.FrozenVolume ||
|
||||||
AccountID: status.Data.Assets.AccountID,
|
positionRecord.AvgPrice != existingPosition.AvgPrice ||
|
||||||
Code: pos.Code,
|
positionRecord.OpenPrice != existingPosition.OpenPrice ||
|
||||||
Ymd: ymd,
|
positionRecord.CurrentPrice != existingPosition.CurrentPrice ||
|
||||||
Volume: pos.Volume,
|
positionRecord.MarketValue != existingPosition.MarketValue ||
|
||||||
CanUseVolume: pos.CanUseVolume,
|
positionRecord.Profit != existingPosition.Profit ||
|
||||||
FrozenVolume: pos.FrozenVolume,
|
positionRecord.ProfitRate != existingPosition.ProfitRate {
|
||||||
AvgPrice: pos.AvgPrice,
|
|
||||||
OpenPrice: pos.OpenPrice,
|
|
||||||
CurrentPrice: pos.CurrentPrice,
|
|
||||||
MarketValue: pos.MarketValue,
|
|
||||||
Profit: pos.Profit,
|
|
||||||
ProfitRate: pos.ProfitRate,
|
|
||||||
MinProfitRate: pos.MinProfitRate,
|
|
||||||
DataHash: dataHash,
|
|
||||||
CollectedAt: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
// 记录不存在,插入新记录
|
|
||||||
if err := tx.Create(&positionRecord).Error; err != nil {
|
|
||||||
return fmt.Errorf("插入持仓失败: %w", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
// 查询出错
|
|
||||||
return fmt.Errorf("查询持仓失败: %w", err)
|
|
||||||
} else {
|
|
||||||
// 记录存在,更新现有记录
|
// 记录存在,更新现有记录
|
||||||
positionRecord.ID = existingPosition.ID
|
positionRecord.ID = existingPosition.ID
|
||||||
if err := tx.Save(&positionRecord).Error; err != nil {
|
if err := s.db.Save(&positionRecord).Error; err != nil {
|
||||||
return fmt.Errorf("更新持仓失败: %w", err)
|
return fmt.Errorf("更新持仓失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 批量保存行情数据 (先查询后更新/插入)
|
return nil
|
||||||
if len(status.Data.TickData) > 0 {
|
|
||||||
for code, tick := range status.Data.TickData {
|
|
||||||
// 验证必要条件: StockCode
|
|
||||||
if code == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询是否存在
|
|
||||||
var existingTick models.CollectorTick
|
|
||||||
err := tx.Where("stock_code = ? AND ymd = ?", code, ymd).First(&existingTick).Error
|
|
||||||
|
|
||||||
tickRecord := models.CollectorTick{
|
|
||||||
StockCode: code,
|
|
||||||
Ymd: ymd,
|
|
||||||
LastPrice: tick.LastPrice,
|
|
||||||
Open: tick.Open,
|
|
||||||
High: tick.High,
|
|
||||||
Low: tick.Low,
|
|
||||||
LastClose: tick.LastClose,
|
|
||||||
Volume: tick.Volume,
|
|
||||||
Amount: tick.Amount,
|
|
||||||
PVolume: tick.PVolume,
|
|
||||||
Time: tick.Time,
|
|
||||||
TimeTag: tick.TimeTag,
|
|
||||||
StockStatus: tick.StockStatus,
|
|
||||||
DataHash: dataHash,
|
|
||||||
CollectedAt: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
// 记录不存在,插入新记录
|
|
||||||
if err := tx.Create(&tickRecord).Error; err != nil {
|
|
||||||
return fmt.Errorf("插入行情数据失败: %w", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
// 查询出错
|
|
||||||
return fmt.Errorf("查询行情数据失败: %w", err)
|
|
||||||
} else {
|
|
||||||
// 记录存在,更新现有记录
|
|
||||||
tickRecord.ID = existingTick.ID
|
|
||||||
if err := tx.Save(&tickRecord).Error; err != nil {
|
|
||||||
return fmt.Errorf("更新行情数据失败: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveCollectionLog 保存采集日志
|
// SaveCollectionLog 保存采集日志
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
// Status 状态数据结构
|
// Status 状态数据结构
|
||||||
type Status struct {
|
type StatusData struct {
|
||||||
Data Data `json:"data"`
|
Data Data `json:"data"`
|
||||||
Status Config `json:"status"`
|
Status Config `json:"status"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user