diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ff10527 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "marscode.codeCompletionPro": { + "enableCodeCompletionPro": false + }, + "marscode.enableInlineCommand": true +} \ No newline at end of file diff --git a/core/config/config.go b/config/config.go similarity index 100% rename from core/config/config.go rename to config/config.go diff --git a/core/config/const.go b/config/const.go similarity index 100% rename from core/config/const.go rename to config/const.go diff --git a/core/interfaces.go b/core/interfaces.go new file mode 100644 index 0000000..1195262 --- /dev/null +++ b/core/interfaces.go @@ -0,0 +1,29 @@ +package core + +import ( + "github.com/go-redis/redis" + "time" +) + +// 领域服务接口 +type CandleManager interface { + GetCandles(instID string, period string) ([]*Candle, error) + SaveCandle(candle *Candle) error +} + +// 基础设施接口 +type RedisService interface { + GetClient(options *redis.Options) (*redis.Client, error) + Ping(client *redis.Client) error +} + +type HTTPRequester interface { + Get(url string) ([]byte, error) + Post(url string, body []byte) ([]byte, error) +} + +// 领域事件接口 +type EventPublisher interface { + Publish(topic string, message interface{}) error + Subscribe(topic string, handler func(message []byte)) error +} diff --git a/core/services/coaster.go b/data/coaster.go similarity index 100% rename from core/services/coaster.go rename to data/coaster.go diff --git a/core/data/pixel.go b/data/pixel.go similarity index 100% rename from core/data/pixel.go rename to data/pixel.go diff --git a/core/data/plate.go b/data/plate.go similarity index 100% rename from core/data/plate.go rename to data/plate.go diff --git a/core/data/rsi.go b/data/rsi.go similarity index 100% rename from core/data/rsi.go rename to data/rsi.go diff --git a/data/segmentItem.go b/data/segmentItem.go new file mode 100644 index 0000000..3a0250a --- /dev/null +++ b/data/segmentItem.go @@ -0,0 +1,318 @@ +package core + +import ( + "encoding/json" + "fmt" + "os" + "time" + + logrus "github.com/sirupsen/logrus" + "phyer.click/sardine/utils" +) + +// 段对象是对某个线段的表现进行评估的一个手段, 整个段会被分成3个小段, 整个段,计算整体的,字段,各自计算。包含仰角,段内极值等。 +// SegmentItem 属于一阶分析结果 +// {DMD-USDT 5D ma30 1643240793839 0 1642867200000 0xc00835fd80 0xc001687600 NaN 23 23 []}] +type SegmentItem struct { + InstID string + Period string //通过InstID,Periods可以定位到Series对象, 里面有一手数据 + Ctype string //candle|ma7|ma30 + ReportTime int64 + ReportTimeStr string + PolarQuadrant string // shangxian,manyue,xiaxian,xinyue, 分别对应圆周的四个阶段。 + LastUpdate int64 + ExtremumPixels *Extremum // 极值 是两个pixel对象 + FirstPixel *Pixel // 起始值,最后的pixel对象 + LastPixel *Pixel // 最后值,最后的maX pixel对象 + LastCandle *Pixel // 最后值,最后的Candle的pixel对象 + LastMa7 *Pixel // 最后值,最后的Ma7的pixel对象 + LastMa30 *Pixel // 最后值,最后的Ma30的pixel对象 + VerticalElevation float64 // 仰角, Interval范围内线段的仰角 + StartIdx int // 开始的坐标 + EndIdx int // 结束的坐标 + SubItemList []SegmentItem //往下一级微分 +} + +const DAMANYUE = "damanyue" +const DAMANYUE_POST = "damanyue_post" +const DAMANYUE_PRE = "damanyue_pre" +const XIAOMANYUE = "xiaomanyue" +const XIAOMANYUE_POST = "xiaomanyue_post" +const XIAOMANYUE_PRE = "xiaomanyue_pre" +const DAXINYUE = "daxinyue" +const DAXINYUE_POST = "daxinyue_post" +const DAXINYUE_PRE = "daxinyue_pre" +const XIAOXINYUE = "xiaoxinyue" +const XIAOXINYUE_POST = "xiaoxinyue_post" +const XIAOXINYUE_PRE = "xiaoxinyue_pre" +const DASHANGXIANYUE = "dashangxianyue" +const XIAOSHANGXIANYUE = "xiaoshangxianyue" +const DAXIAXIANYUE = "daxiaxianyue" +const XIAOXIAXIANYUE = "xiaoxiaxianyue" + +const tinySeg = 0.1 + +type Extremum struct { + Max *Pixel + Min *Pixel +} + +func CalPolar(e0 float64, e1 float64, e2 float64) string { + polarQuadrant := "default" + // + // ## 上弦月 + // + // e0,e1,e2: -3.5315694477826525 -0.5773082714100172 1.0558744145515746 + if e2 >= e1 && e2 >= 0 { + polarQuadrant = XIAOSHANGXIANYUE + } + // e2 > e1 > 0: 小上弦月 + // -> e1 > e0 > 0 : 大上弦月 + if e2 >= e1 && e1 >= 0 { + polarQuadrant = XIAOSHANGXIANYUE + if e1 >= e0 && e0 >= 0 { + polarQuadrant = DASHANGXIANYUE + } + } + // + // ## 下弦月 + // 0 > e1 > e2:小下弦月 + // -> 0 > e0 > e1: 大下弦月 + // + if 0 >= e1 && e1 >= e2 { + polarQuadrant = XIAOXIAXIANYUE + if 0 >= e0 && e0 >= e1 { + polarQuadrant = DAXIAXIANYUE + } + } + + // ## 同上 + if (0 >= e2 && 0 >= e1) && e2 >= e1 { + polarQuadrant = XIAOXIAXIANYUE + } + // ## + // ## 满月 + // + // e1 > e2 > 0 : 小满月 pre + // -> e0 > e1 : 大满月pre + // + + if e1 >= e2 && e2 >= 0 { + polarQuadrant = XIAOMANYUE_PRE + if e0 > e1 { + polarQuadrant = DAMANYUE_PRE + } + } + // e1 > 0.1 > e2 > 0 : 小满月 + // -> e0 > e1 : 大满月 + // + + if e1 >= tinySeg && tinySeg >= e2 && e2 >= 0 { + polarQuadrant = XIAOMANYUE + if e0 > e1 { + polarQuadrant = DAMANYUE + } + } + + // e0,e1,e2: 0.9699903789854316 0.1802190672652184 -1.7888783234326784 + // e1 > 0 > e2 > -0.1 : 小满月post + // -> e0 > e1 > 0 : 大满月post + // + if e1 >= 0 && 0 >= e2 && e2 >= -100000 { + polarQuadrant = XIAOMANYUE_POST + if e0 > e1 { + polarQuadrant = DAMANYUE_POST + } + } + // e0,e1,e2: -0.049579775302532776 0 -0.018291567587323976 + // ## 新月 + // e1 < e2 <0: 小新月pre + // -> e1 > e0 : 大新月pre + // + if e1 <= e2 && e2 <= 0 && e2 >= -1*tinySeg { + polarQuadrant = XIAOXINYUE_PRE + if e1 > e0 { + polarQuadrant = DAXINYUE_PRE + } + } + // e1 < -0.1 < e2 <0 小新月 + // -> e1 > e0 : 大新月 + if e1 <= -1*tinySeg && -1*tinySeg <= e2 && e2 <= 0 { + polarQuadrant = XIAOXINYUE + if e1 > e0 { + polarQuadrant = DAXINYUE + } + } + // + // e1 < 0 < e2 < 0.1 小新月post + // -> e1 > e0 : 大新月post + //e0,e1,e2: -0.03902244287114438 -0.13929829606729519 0.14828528291036536 + if e1 <= 0 && 0 <= e2 && e2 <= 1000000 { + polarQuadrant = XIAOXINYUE_POST + if e1 > e0 { + polarQuadrant = DAXINYUE_POST + } + } + + return polarQuadrant +} + +// 计算当前某段的曲线正弦所处极坐标象限 +func CalPolarQuadrant(maXSeg *SegmentItem) string { + if len(maXSeg.SubItemList) == 0 { + return "subItem no polarQuadrant" + } + m0 := maXSeg.SubItemList[0] + m1 := maXSeg.SubItemList[1] + m2 := maXSeg.SubItemList[2] + e0 := m0.VerticalElevation + e1 := m1.VerticalElevation + e2 := m2.VerticalElevation + polarQuadrant := CalPolar(e0, e1, e2) + if polarQuadrant == "default" { + env := os.Getenv("GO_ENV") + if env != "production" { + fmt.Println(utils.GetFuncName(), " instId:", maXSeg.InstID, " period:", maXSeg.Period, " ctype", maXSeg.Ctype, " e0,e1,e2:", e0, e1, e2) + } + } + + return polarQuadrant +} + +func (seg *SegmentItem) SetToKey(cr *Core) error { + if seg.InstID == "USDC-USDT" { + return nil + } + keyName := seg.InstID + "|" + seg.Period + "|" + seg.Ctype + "|segmentItem" + bj, err := json.Marshal(seg) + if err != nil { + logrus.Warn("se.MakeSegment: ", err, seg) + } + cr.RedisLocalCli.Set(keyName, string(bj), 0) + + sf7 := float64(0) + sf7 = seg.LastCandle.Y - seg.LastMa7.Y + sf30 := float64(0) + sf30 = seg.LastCandle.Y + tms := time.Now().Format("2006-01-02 15:04:05.000") + // fmt.Println("tms: ", seg.InstID, seg.Period, tms, seg.LastUpdate) + she := ShearItem{ + LastUpdate: time.Now().UnixMilli(), + LastUpdateTime: tms, + VerticalElevation: seg.SubItemList[2].VerticalElevation, + Ratio: seg.LastCandle.Y / seg.SubItemList[2].VerticalElevation, + Score: seg.LastCandle.Score, + PolarQuadrant: seg.PolarQuadrant, + } + + if seg.Ctype == "ma7" { + she.ShearForce = sf7 + } + + if seg.Ctype == "ma30" { + she.ShearForce = sf30 + } + sbj, _ := json.Marshal(she) + keyName = seg.InstID + "|" + seg.Period + "|" + seg.Ctype + "|shearItem" + cr.RedisLocalCli.Set(keyName, string(sbj), 3*time.Minute) + cr.RedisLocal2Cli.Set(keyName, string(sbj), 3*time.Minute) + return nil +} + +func (seg *SegmentItem) Show() error { + if seg.InstID == "USDC-USDT" { + return nil + } + bj, _ := json.Marshal(*seg) + logrus.Warn("SegmentItem Show:", string(bj)) + return nil +} + +func (jgm *SegmentItem) Report(cr *Core) error { + return nil +} +func (seg *SegmentItem) Process(cr *Core) { + go func() { + if seg == nil { + return + } + seg.Show() + seg.SetToKey(cr) + // sheGrp, err := seg.MakeShearForceGrp(cr) + // if err != nil { + // log.Panic(err) + // } + // 当最后一个维度数据更新后,触发显示和备份 + + // 空的就可以 + shg := ShearForceGrp{ + InstID: seg.InstID, + Ma30PeriodGroup: map[string]ShearItem{}, + Ma7PeriodGroup: map[string]ShearItem{}, + } + if seg.Period == "4H" { + time.Sleep(50 * time.Millisecond) //等可能存在的5D也ready + go func() { + cr.ShearForceGrpChan <- &shg + }() + } + }() +} + +func (srs *Series) MakeSegment(cr *Core, start int, end int, subArys [][]int, ctype string) *SegmentItem { + list := []*Pixel{} + if ctype == "ma7" { + list = srs.Ma7Series.List + } + if ctype == "ma30" { + list = srs.Ma30Series.List + } + st := start + if len(list) == 0 { + return nil + } + for i := start; i <= end; i++ { + if list[i].X == 0 && list[i].Y == 0 { + if i+1 < len(list) { + st = i + 1 + } else { + logrus.Panic(utils.GetFuncName(), "没有符合的记录") + } + } + } + extra, _ := srs.GetExtremum(cr, st, end, ctype) + yj, err := srs.GetElevation(cr, ctype, st, end) + if err != nil { + fmt.Println("MakeSegment GetElevation err : ", err) + } + tm := time.Now() + seg := SegmentItem{ + InstID: srs.InstID, + Period: srs.Period, + ReportTime: tm.UnixMilli(), + ReportTimeStr: tm.Format("2006-01-02 15:04:05.000"), + LastUpdate: srs.LastUpdateTime, + FirstPixel: list[st], + LastPixel: list[end], + ExtremumPixels: extra, + Ctype: ctype, + VerticalElevation: yj, + StartIdx: st, + EndIdx: end, + LastCandle: srs.CandleSeries.List[end], + LastMa7: srs.Ma7Series.List[end], + SubItemList: []SegmentItem{}, + PolarQuadrant: "none", + } + + if len(subArys) > 0 { + for _, pair := range subArys { + sub := [][]int{} + curSeg := srs.MakeSegment(cr, pair[0], pair[1], sub, ctype) + seg.SubItemList = append(seg.SubItemList, *curSeg) + } + } + polar := CalPolarQuadrant(&seg) + seg.PolarQuadrant = polar + return &seg +} diff --git a/data/series.go b/data/series.go new file mode 100644 index 0000000..5056e96 --- /dev/null +++ b/data/series.go @@ -0,0 +1,566 @@ +package core + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strconv" + "time" + + "github.com/go-redis/redis" + logrus "github.com/sirupsen/logrus" + // "phyer.click/sardine/utils" +) + +type Series struct { + InstID string `json:"instID"` + Period string `json:"Period"` + Count int `json:"count,number"` + Scale float64 `json:"scale,number"` + LastUpdateTime int64 `json:"lastUpdateTime,number"` + UpdateNickName string + LastCandle1m Candle `json:"lastCandle1m"` + CandleSeries *PixelList `json:"candleSerie"` + Ma7Series *PixelList `json:"ma7Serie"` + Ma30Series *PixelList `json:"ma30Serie"` +} + +type SeriesInfo struct { + InstID string `json:"instID"` + Period string `json:"period"` + InsertedNew bool `json:"insertedNew,bool"` + Score float64 `json:"score,number"` +} +type SeriesInfoScore struct { + InstID string `json:"instID"` + Score float64 `json:"score,number"` +} + +// TODO +// redis key: verticalReportItem|BTC-USDT|4H-15m|ts:1643002300000 +// sortedSet: verticalLimit|2D-4H|rank|sortedSet +type VerticalReportItem struct { + InstID string + Period string + ReportTime int64 + LastUpdate int64 + LastUpdateTime string + Interval int + TrigerValue float64 + AdvUpSellPrice float64 + AdvDownSellPrice float64 + Rank float64 + ShearForce float64 + VerticalElevation float64 + SecondPeriod string +} + +// type Segment struct { +// IndextStart int +// IndexEnd int +// +// } + +// 根据instId 和period 从 PlateMap里拿到coaster,创建对应的 series, +func (sr *Series) Refresh(cr *Core) error { + curCo, err := cr.GetCoasterFromPlate(sr.InstID, sr.Period) + if err != nil { + return err + } + + ma30List := curCo.Ma30List.List + ma30len := len(ma30List) + if ma30len == 0 { + err = errors.New("ma30List is empty:" + sr.InstID + "," + sr.Period) + return err + } + baseMaX := ma30List[ma30len-1] + + ma30Pxl, err := curCo.Ma30List.MakePixelList(cr, baseMaX, sr.Scale) + if err != nil { + return err + } + sr.Ma30Series = ma30Pxl + ma7Pxl, err := curCo.Ma7List.MakePixelList(cr, baseMaX, sr.Scale) + if err != nil { + return err + } + sr.Ma7Series = ma7Pxl + curCo.CandleList.RecursiveBubbleS(len(curCo.CandleList.List), "asc") + candlePxl, err := curCo.CandleList.MakePixelList(cr, baseMaX, sr.Scale) + if err != nil { + return err + } + sr.CandleSeries = candlePxl + // bj, _ := json.Marshal(sr.Ma30Series) + // fmt.Println("sr.Ma30Series:", sr.Period, sr.InstID, string(bj)) + sr.LastUpdateTime = sr.Ma30Series.LastUpdateTime + // fmt.Println("candlePxl: ", candlePxl) + return nil +} + +func (sr *Series) SetToKey(cr *Core) (string, error) { + if sr == nil || sr.CandleSeries == nil { + return "", errors.New("sr.CandlesSeries == nil") + } + sr.CandleSeries.RecursiveBubbleS(sr.CandleSeries.Count, "asc") + sr.CandleSeries.ReIndex() + sr.CandleSeries.RecursiveBubbleS(sr.CandleSeries.Count, "asc") + // sr.CandleSeries.RecursiveBubbleX(sr.CandleSeries.Count, "asc") + sr.Ma7Series.RecursiveBubbleS(sr.CandleSeries.Count, "asc") + // sr.Ma7Series.RecursiveBubbleX(sr.CandleSeries.Count, "asc") + sr.Ma30Series.RecursiveBubbleS(sr.CandleSeries.Count, "asc") + // sr.Ma30Series.RecursiveBubbleX(sr.CandleSeries.Count, "asc") + now := time.Now().UnixMilli() + sr.LastUpdateTime = now + sr.CandleSeries.LastUpdateTime = now + sr.CandleSeries.UpdateNickName = utils.GetRandomString(12) + sr.UpdateNickName = utils.GetRandomString(12) + js, _ := json.Marshal(*sr) + seriesName := sr.InstID + "|" + sr.Period + "|series" + res, err := cr.RedisLocalCli.Set(seriesName, string(js), 0).Result() + if err != nil { + logrus.Panic(utils.GetFuncName(), err, " seriesSetToKey1: instId:", sr.InstID, " period: ", sr.Period, " lastUpdate:", sr.LastUpdateTime, " md5:", utils.Md5V(string(js))) + } + res, err = cr.RedisLocal2Cli.Set(seriesName, string(js), 0).Result() + return res, err +} +func PrintSerieY(cr *Core, list []redis.Z, period string, count int) { + // fmt.Println("PrintSerieY start") + env := os.Getenv("GO_ENV") + isProduction := env == "production" + //TODO 只有非产线环境,才会显示此列表 + if !isProduction { + fmt.Println("seriesYTop count:", count, "period:", period, "sort start") + } + seiScrList := []*SeriesInfoScore{} + for _, v := range list { + sei := SeriesInfo{} + seiScr := SeriesInfoScore{} + json.Unmarshal([]byte(v.Member.(string)), &sei) + seiScr.InstID = sei.InstID + seiScr.Score = v.Score + seiScrList = append(seiScrList, &seiScr) + + // if k < count { + // if !isProduction { + // fmt.Println("seriesYTop", count, "No.", k+1, "period"+period, "InstID:", sei.InstID, "score:", v.Score) + // } + // 拉扯极限报告 + // } + // if k == count+1 { + // if !isProduction { + // fmt.Println("seriesYTop end -------" + "period" + period + "-------------------------------------") + // fmt.Println("seriesYLast start -------" + "period" + period + "-------------------------------------") + // } + // } + // if k > len(list)-count-1 { + // if !isProduction { + // fmt.Println("seriesYLast", count, "No.", k+1, "period"+period, "InstID:", sei.InstID, "score:", v.Score) + // } + // } + } + + bj, _ := json.Marshal(seiScrList) + reqBody := bytes.NewBuffer(bj) + cr.Env = os.Getenv("GO_ENV") + cr.FluentBitUrl = os.Getenv("SARDINE_FluentBitUrl") + fullUrl := "http://" + cr.FluentBitUrl + "/seriesY." + period + + res, err := http.Post(fullUrl, "application/json", reqBody) + fmt.Println("requested, response:", fullUrl, reqBody, res) + if err != nil { + logrus.Error(err) + } + + if !isProduction { + fmt.Println("seriesYLast count:", count, "period:", period, "sort end") + } +} + +func (sei *SeriesInfo) Process(cr *Core) { + curSe, err := cr.GetPixelSeries(sei.InstID, sei.Period) + if err != nil { + logrus.Warn("GetPixelSeries: ", err) + return + } + + // TODO 金拱门 + // list := cr.GetMyCcyBalanceName() + go func(se Series) { + + threeSeg := [][]int{[]int{0, 19}, []int{19, 22}, []int{22, 23}} + + ma7Seg := se.MakeSegment(cr, 0, 23, threeSeg, "ma7") + go func() { + cr.SegmentItemChan <- ma7Seg + }() + + ma30Seg := se.MakeSegment(cr, 0, 23, threeSeg, "ma30") + + go func() { + cr.SegmentItemChan <- ma30Seg + }() + + }(curSe) + + cli := cr.RedisLocalCli + go func(se Series) { + // 拉扯极限报告 + willReport := os.Getenv("SARDINE_SERIESTOREPORT") == "true" + logrus.Info("willReport:", willReport) + // fmt.Println("willReport:", willReport) + if !willReport { + return + } + err = curSe.AddToYSorted(cr) + if err != nil { + logrus.Warn("sei addToYSorted err: ", err) + return + } + // 所有维度拉扯极限 + go func(se Series) { + if se.InstID != "BTC-USDT" { + return + } + list, err := cli.ZRevRangeWithScores("series|YValue|sortedSet|period"+se.Period, 0, -1).Result() + if err != nil { + fmt.Println("series sorted err", err) + } + PrintSerieY(cr, list, se.Period, 20) + }(se) + + }(curSe) + // TODO 刘海儿检测, 监测金拱门中的刘海儿,预警下跌趋势, 其实有没有金拱门并不重要,刘海儿比金拱门更有说服力 + go func(se Series) { + // 如何定义刘海:目前定义如下,3m以上的周期时,当7个或小于7个周期内的时间内发生了一次下坠和一次上升,下坠幅度达到2%以上,并随后的上升中收复了下坠的幅度,那么疑似刘海儿发生。用的周期越少,越强烈,探底和抬升的幅度越大越强烈,所处的维度越高越强烈,比如15m的没有1H的强烈 + // 如果发生在BTC身上,那么将影响所有 + // se.CheckLiuhai() { + // + // } + + }(curSe) + go func(se Series) { + allow := os.Getenv("SARDINE_SERIESINFOTOCHNL") == "true" + if !allow { + return + } + time.Sleep(0 * time.Second) + sei := SeriesInfo{ + InstID: curSe.InstID, + Period: curSe.Period, + } + cr.AddToGeneralSeriesInfoChnl(&sei) + }(curSe) + +} + +//------------------------------------------------------------------------------- + +// 拉扯极限相关: 加入seriesY值排行榜, 用于生成拉扯极限 +func (srs *Series) AddToYSorted(cr *Core) error { + setName := "series|YValue|sortedSet|period" + srs.Period + srs.CandleSeries.RecursiveBubbleS(srs.CandleSeries.Count, "asc") + length := len(srs.CandleSeries.List) + if length != srs.Count { + err := errors.New("AddToYSorted err: 数据量不够") + return err + } + lastCandlePixel1 := srs.CandleSeries.List[srs.Count-1] + sei := SeriesInfo{ + InstID: srs.InstID, + Period: srs.Period, + } + bj, _ := json.Marshal(sei) + // TODO -200 是个无效的值,如果遇到-200就赋予0值,这个办法不好,后面考虑不用sortedSet,而用自定义对象更好些。 + if lastCandlePixel1.Y == -200 { + lastCandlePixel1.Y = 0 + } + z := redis.Z{ + Score: float64(lastCandlePixel1.Y), + Member: string(bj), + } + // TODO ZAdd 有可能由于bug或者key不一样的原因,让列表变长,需要想办法怎么定期请空 + if lastCandlePixel1.Score != 0 { + cr.RedisLocalCli.ZAdd(setName, z).Result() + } + return nil +} + +// 垂直极限排名有一定片面性。暂时先不开放。垂直极限推荐最高的,可能是个不太容易📈上来的股票,甚至垃圾股,而且过一会儿可能跌的更多,所以就算使用这个功能,也仅供参考, +func (vir *VerticalReportItem) AddToVeriticalLimitSorted(cr *Core, srs *Series, period2 string) error { + // redis key: verticalReportItem|BTC-USDT|4H-15m|ts:1643002300000 + // sortedSet: verticalLimit|2D-4H|rank|sortedSet + + setName := "verticalLimit|" + srs.Period + "-" + period2 + "|rank|sortedSet" + tms := strconv.FormatInt(srs.LastUpdateTime, 10) + keyName := "verticalLimit|" + srs.InstID + "|" + srs.Period + "-" + period2 + "|ts:" + tms + z := redis.Z{ + Score: float64(srs.LastUpdateTime), + Member: keyName, + } + if vir.Rank != -1 && vir.Rank != 0 { + extt := 48 * time.Hour + ot := time.Now().Add(extt * -1) + oti := ot.UnixMilli() + count, _ := cr.RedisLocalCli.ZRemRangeByScore(setName, "0", strconv.FormatInt(oti, 10)).Result() + if count > 0 { + logrus.Warning("移出过期的引用数量:", setName, count, "ZRemRangeByScore ", setName, 0, strconv.FormatInt(oti, 10)) + } + cr.RedisLocalCli.ZAdd(setName, z).Result() + bj, _ := json.Marshal(vir) + cr.RedisLocalCli.Set(keyName, bj, 48*time.Hour).Result() + } + return nil +} + +func (vri *VerticalReportItem) Report(cr *Core) error { + dd := DingdingMsg{ + Topic: "垂直极限触发", + RobotName: "pengpeng", + AtAll: true, + Ctype: "markdown", + Content: "", + } + ary1 := []string{} + str := "``币名: ``" + vri.InstID + "\n" + str1 := fmt.Sprintln("``基础维度:``", vri.Period) + str2 := fmt.Sprintln("``剪切维度:``", vri.SecondPeriod) + str21 := fmt.Sprintln("``观察周期:``", vri.Interval) + str3 := fmt.Sprintln("``平均仰角:``", vri.VerticalElevation) + str4 := fmt.Sprintln("``剪切力:``", vri.ShearForce) + str5 := fmt.Sprintln("``Rank:``", vri.Rank) + score := vri.TrigerValue + str6 := fmt.Sprintln("``触发买入价位:``", score) + str7 := fmt.Sprintln("``建议止盈价位:``", vri.AdvUpSellPrice) + str8 := fmt.Sprintln("``建议止损价位:``", vri.AdvDownSellPrice) + str9 := "----------------------\n" + ary1 = append(ary1, str, str1, str2, str21, str3, str4, str5, str6, str7, str8, str9) + dd.AddItemListGrp("垂直极限", 2, ary1) + ary2 := []string{} + + tm := time.Now().Format("01-02:15:04") + rtime := fmt.Sprintln("``报告时间:``", tm) + ctype := fmt.Sprintln("``类型:``", "极限触发,已弃用") + from := "来自: " + os.Getenv("HOSTNAME") + ary2 = append(ary2, rtime, ctype, from) + dd.AddItemListGrp("", 2, ary2) + dd.PostToRobot("pengpeng", cr) + + return nil +} + +func (vri *VerticalReportItem) Show(cr *Core) error { + ary1 := []string{} + str := "``币名: ``" + vri.InstID + "\n" + str1 := fmt.Sprintln("``基础维度:``", vri.Period) + str2 := fmt.Sprintln("``剪切维度:``", vri.SecondPeriod) + str21 := fmt.Sprintln("``观察周期:``", vri.Interval) + str3 := fmt.Sprintln("``平均仰角:``", vri.VerticalElevation) + str4 := fmt.Sprintln("``剪切力:``", vri.ShearForce) + str5 := fmt.Sprintln("``Rank:``", vri.Rank) + score := vri.TrigerValue + str6 := fmt.Sprintln("``触发买入价位:``", score) + str7 := fmt.Sprintln("``建议止盈价位:``", vri.AdvUpSellPrice) + str8 := fmt.Sprintln("``建议止损价位:``", vri.AdvDownSellPrice) + str9 := "----------------------\n" + ary1 = append(ary1, str, str1, str2, str21, str3, str4, str5, str6, str7, str8, str9) + for _, v := range ary1 { + fmt.Println("verticalReportItem: ", v) + } + return nil +} + +// TODO 求某个PixelList里两个点之间的仰角,从ridx开始,往lidx的元素画一条直线,的仰角 + +func (srs *Series) GetElevation(cr *Core, ctype string, lIdx int, rIdx int) (float64, error) { + yj := float64(0) + switch ctype { + case "candle": + { + yj = (srs.CandleSeries.List[rIdx].Y - srs.CandleSeries.List[lIdx].Y) / float64(rIdx-lIdx) + } + case "ma7": + { + yj = (srs.Ma7Series.List[rIdx].Y - srs.Ma7Series.List[lIdx].Y) / float64(rIdx-lIdx) + } + case "ma30": + { + yj = (srs.Ma30Series.List[rIdx].Y - srs.Ma30Series.List[lIdx].Y) / float64(rIdx-lIdx) + + } + } + return yj, nil +} + +// TODO 求极值,在某个线段上。一个最大值,一个最小值 +func (srs *Series) GetExtremum(cr *Core, lIdx int, rIdx int, ctype string) (*Extremum, error) { + ext := Extremum{ + Max: &Pixel{}, + Min: &Pixel{}, + } + + switch ctype { + case "candle": + { + done := false + for k, v := range srs.CandleSeries.List { + if k < lIdx { + continue + } + if v.X == 0 && v.Y == 0 { + continue + } + if k > rIdx { + continue + } + if !done { + ext.Max = srs.CandleSeries.List[k] + ext.Min = srs.CandleSeries.List[k] + done = true + } + + if v.Y > ext.Max.Y { + ext.Max = v + } + if v.Y < ext.Min.Y { + ext.Min = v + } + } + // ext = nil + } + case "ma7": + { + done := false + for k, v := range srs.Ma7Series.List { + if k < lIdx { + continue + } + if v.X == 0 && v.Y == 0 { + continue + } + if k > rIdx { + continue + } + if !done { + ext.Max = srs.Ma7Series.List[k] + ext.Min = srs.Ma7Series.List[k] + done = true + } + if v.Y > ext.Max.Y { + ext.Max = v + } + if v.Y < ext.Min.Y { + ext.Min = v + } + } + // ext = nil + } + case "ma30": + { + + done := false + for k, v := range srs.Ma30Series.List { + if k < lIdx { + continue + } + if v.X == 0 && v.Y == 0 { + continue + } + if k > rIdx { + continue + } + if !done { + ext.Max = srs.Ma30Series.List[k] + ext.Min = srs.Ma30Series.List[k] + done = true + } + if v.Y > ext.Max.Y { + ext.Max = v + } + if v.Y < ext.Min.Y { + ext.Min = v + } + } + // ext = nil + } + } + return &ext, nil +} + +// TODO 获取垂直极限列表 +// 筛选条件: +// +// 1. 极大值未发生在最后周期的,排除 +// 2. n周期内,有仰角小于0的,排除 +// 注意: 仰角极值未必发生在最后一个周期 +// +// 对剩下的币种结果,计算: +// +// 1. n周期平均仰角: s +// 2. 最后周期仰角: p +// +// 筛选出最后仰角高于n周期平均仰角的币列表, +// 以最后仰角为结果,得到一个值 p +// 对此列表集合,得到每个的15分钟维度拉扯极限,每个计算后得到一个结果 f, +// +// f值权重更高,p值权重降一个量级,求出分值用于排名, +// +// rank = 2 * (lcjx * -1) * (1 + avgElevation) * (1 + lastElevation) * (1 + lastElevation) +// +// 存储在sortedSet里,命名: +// verticalLimit|15m~4H|rank|sortedSet +// return rank, err +func (vir *VerticalReportItem) MakeVerticalLimit(cr *Core, srs *Series, startIdx int, endIdx int, period2 string) (err error) { + count := len(srs.CandleSeries.List) - 1 + lastMa30Pixel := srs.Ma30Series.List[count] + // func (srs *Series) GetExtremum(cr *Core, lIdx int, rIdx int, ctype string) (*Extremum, error) { + ext, err := srs.GetExtremum(cr, startIdx, endIdx, "ma30") + if err != nil { + logrus.Warn(utils.GetFuncName(), ":", err) + } + + if ext.Max.Score < 1.05*lastMa30Pixel.Score { + lbj, _ := json.Marshal(lastMa30Pixel) + lext, _ := json.Marshal(ext) + err = errors.New(fmt.Sprintln("当前pixel不是极值", " lastMa30Pixel: ", string(lbj), " ext: ", string(lext))) + return err + } else { + err = errors.New(fmt.Sprintln("当前pixel满足极值", lastMa30Pixel)) + } + + yj, err := srs.GetElevation(cr, "ma30", startIdx, endIdx) + if err != nil { + logrus.Warn(utils.GetFuncName(), ":", err) + } + + vir.VerticalElevation = yj + lcjx, _ := LacheJixian(cr, srs, period2) + vir.ShearForce = lcjx + vir.TrigerValue = srs.CandleSeries.List[len(srs.CandleSeries.List)-1].Score + vir.AdvUpSellPrice = vir.TrigerValue * 1.04 + vir.AdvDownSellPrice = vir.TrigerValue * 0.98 + // 计算rank的公式如下 + // rank := 2 * (lcjx * -1) * (1 + avgElevation) * (1 + lastElevation) * (1 + lastElevation) + // vir.Rank = rank + return nil +} + +// 计算剪切力 +func LacheJixian(cr *Core, srs *Series, period string) (float64, error) { + curSe, _ := cr.GetPixelSeries(srs.InstID, period) + return curSe.CandleSeries.List[len(srs.CandleSeries.List)-1].Y, nil +} + +// type SegmentItem struct { +// InstID string +// Period string +// ReportTime int64 +// lastUpdate int64 +// Interval int +// Direct string // up, down +// VerticalElevation float64 +// } diff --git a/data/shearForceGrp.go b/data/shearForceGrp.go new file mode 100644 index 0000000..48216c4 --- /dev/null +++ b/data/shearForceGrp.go @@ -0,0 +1,268 @@ +package core + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "time" + + "github.com/go-redis/redis" + logrus "github.com/sirupsen/logrus" + "phyer.click/sardine/utils" +) + +type ShearItem struct { + ShearForce float64 // ma30-candle剪切力 + VerticalElevation float64 // 仰角, Interval范围内线段的仰角 + Ratio float64 // 剪切力除以仰角的比值 + Score float64 // 当前LastCandleY点本值 + PolarQuadrant string // shangxian,manyue,xiaxian,xinyue, 分别对应圆周的四个阶段。 + LastUpdate int64 + LastUpdateTime string +} +type ShearForceGrp struct { + InstID string + LastUpdate int64 + LastUpdateTime string + Ma30PeriodGroup map[string]ShearItem + Ma7PeriodGroup map[string]ShearItem + From string +} + +// TODO 弃用 +// func (seg *SegmentItem) MakeShearForceGrp(cr *Core) (*ShearForceGrp, error) { +// shg := ShearForceGrp{ +// InstID: seg.InstID, +// Ma30PeriodGroup: map[string]ShearItem{}, +// Ma7PeriodGroup: map[string]ShearItem{}, +// } +// err := shg.ForceUpdate(cr) +// sf1 := float64(0) +// sf1 = seg.LastCandle.Y - seg.LastMa7.Y +// she := ShearItem{ +// LastUpdate: time.Now().UnixMilli(), +// VerticalElevation: seg.SubItemList[2].VerticalElevation, +// Ratio: seg.LastCandle.Y / seg.SubItemList[2].VerticalElevation, +// Score: seg.LastCandle.Score, +// PolarQuadrant: seg.PolarQuadrant, +// } +// if seg.Ctype == "ma7" { +// she.ShearForce = seg.LastCandle.Y +// shg.Ma7PeriodGroup[seg.Period] = she +// } +// if seg.Ctype == "ma30" { +// she.ShearForce = sf1 +// shg.Ma30PeriodGroup[seg.Period] = she +// } +// return &shg, err +// } + +// TODO 弃用 +// func (shg *ShearForceGrp) ForceUpdate(cr *Core) error { +// ctype := "ma7" +// hmName := shg.InstID + "|" + ctype + "|shearForceGrp" +// res, err := cr.RedisLocalCli.HGetAll(hmName).Result() +// +// for k, v := range res { +// si := ShearItem{} +// json.Unmarshal([]byte(v), &si) +// shg.Ma7PeriodGroup[k] = si +// } +// +// ctype = "ma30" +// hmName = shg.InstID + "|" + ctype + "|shearForceGrp" +// res, err = cr.RedisLocalCli.HGetAll(hmName).Result() +// +// for k, v := range res { +// si := ShearItem{} +// json.Unmarshal([]byte(v), &si) +// shg.Ma30PeriodGroup[k] = si +// } +// shg.SetToKey(cr) +// return err +// } +func (she *ShearForceGrp) Show(cr *Core) error { + js, err := json.Marshal(she) + logrus.Info(utils.GetFuncName(), ": ", string(js)) + + return err +} + +// TODO 需要重构: 已经重构 +// 对象数据库落盘 +func (she *ShearForceGrp) SetToKey(cr *Core) error { + keyName := she.InstID + "|shearForce" + she.From = os.Getenv("HOSTNAME") + she.LastUpdateTime = time.Now().Format("2006-01-02 15:04:05.000") + js, err := json.Marshal(she) + if err != nil { + logrus.Panic(utils.GetFuncName(), " err: ", err) + } else { + cr.RedisLocalCli.Set(keyName, string(js), 0).Result() + cr.RedisLocal2Cli.Set(keyName, string(js), 0).Result() + } + return err +} + +func (she *ShearForceGrp) maXPrd(cr *Core, ctype string) { + // 先把对象克隆,防止在处理的过程中对象发生变更 + she2 := *she + she3 := &she2 + // 查了一下,json marshal 有线程安全问题,需要用户自己加锁,先不用了 + // bj, _ := json.Marshal(she3) + // bytes := []byte(bj) + // var she4 ShearForceGrp + // json.Unmarshal(bytes, she4) + // 先声明map + var grp map[string]ShearItem + // 再使用make函数创建一个非nil的map,nil map不能赋值 + grp = make(map[string]ShearItem) + if ctype == "ma7" { + //fmt.Println("len of ma7 she.Ma7PeriodGroup: ", len(she3.Ma7PeriodGroup)) + bj, err := json.Marshal(she3.Ma7PeriodGroup) + if err != nil { + logrus.Panic(utils.GetFuncName(), " err:", err) + } + json.Unmarshal(bj, &grp) + //fmt.Println("len of ma30 she.Ma7PeriodGroup: ", len(she3.Ma7PeriodGroup)) + } else if ctype == "ma30" { + bj, err := json.Marshal(she3.Ma30PeriodGroup) + if err != nil { + logrus.Panic(utils.GetFuncName(), " err: ", err) + } + json.Unmarshal(bj, &grp) + } + for period, shearItem := range grp { + setName := "shearForce|ratio|" + ctype + "|" + period + "|sortedSet" + // TODO:这个key用于判定当前instID|maX|period|的ratio排名是否已经过期 + timelinessKey := "shearForce|ratio|" + she.InstID + "|" + ctype + "|" + period + "|lastUpdate" + sei := SeriesInfo{ + InstID: she3.InstID, + Period: period, + } + // 阈值先暂且设置为 -100 + // SHEARFORCE_VERTICAL_RATE + threahold := float64(SHEARFORCE_VERTICAL_RATE) + bj, _ := json.Marshal(sei) + z := redis.Z{ + Score: float64(shearItem.Ratio), + Member: string(bj), + } + //无论超过阈值,还是低于阈值的负数,都是达标 + if shearItem.Ratio < -1*threahold { + cr.RedisLocalCli.ZAdd(setName, z).Result() + cr.RedisLocalCli.Set(timelinessKey, shearItem.LastUpdate, 3*time.Minute) + } else if shearItem.Ratio > threahold { + cr.RedisLocalCli.ZAdd(setName, z).Result() + cr.RedisLocalCli.Set(timelinessKey, shearItem.LastUpdate, 3*time.Minute) + } else { + cr.RedisLocalCli.ZRem(setName, string(bj)).Result() + } + } +} + +// 把所有引用调用都改成传值调用,试试,看能不能解决那个陈年bug +func (she *ShearForceGrp) AddToRatioSorted(cr *Core) error { + she.maXPrd(cr, "ma7") + she.maXPrd(cr, "ma30") + return nil +} + +// TODO 需要重构: 看了一下,不用重构 +func (she *ShearForceGrp) MakeSnapShot(cr *Core) error { + nw := time.Now() + tm := nw.UnixMilli() + tm = tm - tm%60000 + tms := strconv.FormatInt(tm, 10) + js, err := json.Marshal(she) + + keyName1 := fmt.Sprint(she.InstID + "|shearForce|snapShot|ts:" + tms) + keyName2 := fmt.Sprint(she.InstID + "|shearForce|snapShot|last") + _, err = cr.RedisLocalCli.Set(keyName1, string(js), time.Duration(24)*time.Hour).Result() + _, err = cr.RedisLocalCli.Set(keyName2, string(js), time.Duration(24)*time.Hour).Result() + _, err = cr.RedisLocal2Cli.Set(keyName1, string(js), time.Duration(24)*time.Hour).Result() + _, err = cr.RedisLocal2Cli.Set(keyName2, string(js), time.Duration(24)*time.Hour).Result() + writeLog := os.Getenv("SARDINE_WRITELOG") == "true" + if !writeLog { + return err + } + wg := WriteLog{ + Content: js, + Tag: she.InstID + ".shearForce", + } + go func() { + cr.WriteLogChan <- &wg + }() + return nil +} + +func (sheGrp *ShearForceGrp) Refresh(cr *Core) error { + segments := cr.Cfg.Config.Get("softCandleSegmentList").MustArray() + ma7Grp := map[string]ShearItem{} + ma30Grp := map[string]ShearItem{} + //搜集各个维度未过期的shearItem数据,组合成shearForceGrp对象 + for _, v := range segments { + cs := CandleSegment{} + sv, _ := json.Marshal(v) + json.Unmarshal(sv, &cs) + shi30, err := MakeShearItem(cr, sheGrp.InstID, cs.Seg, "ma30") + if err != nil { + logrus.Warn(utils.GetFuncName(), err) + } else { + ma30Grp[cs.Seg] = *shi30 + } + shi7, err := MakeShearItem(cr, sheGrp.InstID, cs.Seg, "ma7") + if err != nil { + logrus.Warn(utils.GetFuncName(), err) + } else { + ma7Grp[cs.Seg] = *shi7 + } + sheGrp.Ma7PeriodGroup = ma7Grp + sheGrp.Ma30PeriodGroup = ma30Grp + } + return nil +} + +func MakeShearItem(cr *Core, instId string, period string, ctype string) (*ShearItem, error) { + shi := ShearItem{} + keyn := instId + "|" + period + "|" + ctype + "|shearItem" + res, err := cr.RedisLocalCli.Get(keyn).Result() + if err != nil && len(res) == 0 { + return &shi, err + } + json.Unmarshal([]byte(res), &shi) + return &shi, err +} + +func (sheGrp *ShearForceGrp) Process(cr *Core) error { + go func() { + sheGrp.Show(cr) + // 传递过来的shg对象是空的,需要从segmentItem对象创建的shearItem对象组合中来重建 + sheGrp.Refresh(cr) + err := sheGrp.SetToKey(cr) + if err != nil { + logrus.Panic("srs SetToKey err: ", err) + } + // sheGrp.MakeSnapShot(cr) + // 下一个阶段计算 + allow := os.Getenv("SARDINE_MAKEANALYTICS") == "true" + if !allow { + return + } + + periodList := []string{} + for k := range sheGrp.Ma30PeriodGroup { + periodList = append(periodList, k) + } + }() + go func() { + sheGrp.AddToRatioSorted(cr) + }() + go func() { + // 另一个携程中,Analytics对象要读这里snapShot,我希望它读到的是老的而不是新的,所以等待2秒钟 + time.Sleep(2 * time.Second) + sheGrp.MakeSnapShot(cr) + }() + return nil +} diff --git a/data/sorted.go b/data/sorted.go new file mode 100644 index 0000000..9065b92 --- /dev/null +++ b/data/sorted.go @@ -0,0 +1,161 @@ +package core + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "time" + + "github.com/go-redis/redis" + logrus "github.com/sirupsen/logrus" +) + +// 1. 成交量计算和保存 +// func (tk *TickerInfo) MakeVolSorted(cr *Core, period string) { +// SORTED_INTERVAL 分钟的整数倍 +// minutes := cr.PeriodToMinutes(period) +// ts := tk.Ts +// ts = ts - ts%60 +// z := redis.Z{ +// Score: float64(tk.VolCcy24h), +// Member: tk.InstID, +// } +// 计算当次成交量环比增长比值, 存入当次成交价 +// if ts%(minutes*60) == 0 { +// 计算成交量环比增幅 +// tk.makeVolSpeed(cr, period) +// cr.RedisLocalCli.ZAdd(SORTED_VOL+"|"+period, z).Result() +// } +// } +// +// 2. 成交量环比增幅计算和保存 +// func (tk *TickerInfo) makeVolSpeed(cr *Core, period string) { +// spd := float64(1) +// preVolCcy24h, err := cr.RedisLocalCli.ZScore(SORTED_VOL+"|"+period, tk.InstID).Result() +// if err != nil { +// TODO 新股 +// } else { +// if tk.VolCcy24h < preVolCcy24h { +// 新的一天开始了 +// } else { +// tk.VolDiff = tk.VolCcy24h - preVolCcy24h +// } +// } +// tk.VolDiff = spd +// 计算环比增幅振幅 +// tk.makeVolAcclr(cr, period) +// z := redis.Z{ +// Score: tk.VolDiff, +// Member: tk.InstID, +// } +// 保存当次成交量环比增幅 +// cr.RedisLocalCli.ZAdd(SORTED_VOLDIFF+"|"+period, z).Result() +// } +// +// 3. 成交量环比增长振幅计算和保存 +// func (tk *TickerInfo) makeVolAcclr(cr *Core, period string) { +// acclr := float64(0) +// score, err := cr.RedisLocalCli.ZScore(SORTED_VOLDIFF+"|"+period, tk.InstID).Result() +// if err != nil { +// TODO 新股 +// fmt.Println("zScore err:", score, err) +// } else { +// 当次比值和上次比值之间的比值作为当次振幅保存 +// acclr = (tk.VolDiff + score) / (tk.VolCcy24h - score) +// } +// tk.VolAcclr = acclr +// z := redis.Z{ +// Score: acclr, +// Member: tk.InstID, +// } +// cr.RedisLocalCli.ZAdd(SORTED_VOLACCLER+"|"+period, z).Result() +// } + +// ------------------------------------------------------------------------ +// 1. 时间节点: t0; 价格保存 +func (tk *TickerInfo) MakePriceSorted(cr *Core, period string) { + ts := tk.Ts + tm := time.Now().UnixMilli() + bj, _ := json.Marshal(tk) + if tm > (ts + 100000) { + logrus.Info("tickerInfo已经失效:", tm-(ts+1000), "毫秒", string(bj)) + } else { + logrus.Info("tickerInfo有效:", string(bj)) + } + ts = ts - ts%60*1000 + minutes, err := cr.PeriodToMinutes(period) + if err != nil { + return + } + z := redis.Z{ + Score: float64(tk.Last), + Member: tk.InstId, + } + // 满足约定时间间隔,执行制作价格差和存储当次价格的动作, 每分钟都计算,不管间隔是多少分钟,分到不同的seg里去,10分钟的话,就会有9个seg + for i := int64(0); i < minutes; i++ { + if (ts%(minutes*60*1000))/(60*1000) == int64(i) { + //计算成交价环比增幅 + tk.makePriceDiff(cr, period, i) + // 保存本次成交价 + cr.RedisLocalCli.ZAdd(SORTED_PRICE+"|"+period+"|seg"+strconv.FormatInt(i, 10), z).Result() + } + } + +} + +// 2. 时间节点: t1; 成交价环比增幅计算和保存 +func (tk *TickerInfo) makePriceDiff(cr *Core, period string, seg int64) { + spd := float64(1) + score, err := cr.RedisLocalCli.ZScore(SORTED_PRICE+"|"+period+"|seg"+strconv.FormatInt(seg, 10), tk.InstId).Result() + if err != nil { + //TODO 新股 + logrus.Warn("makeVolSpeed zScore err:", score, err) + } else { + spd = tk.Last - score + } + tk.PriceDiff = spd + tk.makePriceAcclr(cr, period, seg) + z := redis.Z{ + Score: spd, + Member: tk.InstId, + } + cr.RedisLocalCli.ZAdd(SORTED_PRICEDIFF+"|"+period+"|seg"+strconv.FormatInt(seg, 10), z).Result() + // fmt.Println("sorted PriceDiff: ", tk.InstID, "new Price:", tk.Last, "old Price:", score, "PriceDiff:", spd, "rs:", rs) +} + +// 3. 时间节点: t2; 价格变化振幅排行榜, 计算振幅需要三个阶段,第一个阶段得到降/增幅,第二个阶段得到降/增幅的差值,第三个阶段得到将增幅差值的比值 +func (tk *TickerInfo) makePriceAcclr(cr *Core, period string, seg int64) { + acclr := float64(0) + score, err := cr.RedisLocalCli.ZScore(SORTED_PRICEDIFF+"|"+period+"|seg"+strconv.FormatInt(seg, 10), tk.InstId).Result() + if err != nil { + //TODO 新股 + fmt.Println("zScore err:", score, err) + } else { + // 在时间节点t2, 用最近两个周期增降幅的叠加之后的结果,除以t0时刻的成交价, 得到振幅 + // 时间跨度为两个周期,t0开始,t3结束 + acclr = (tk.PriceDiff + score) / (tk.Last - score) + } + tk.PriceAcclr = acclr + z := redis.Z{ + Score: acclr, + Member: tk.InstId, + } + zname := SORTED_PRICEACCLR + "|" + period + "|seg" + strconv.FormatInt(seg, 10) + // fmt.Println("zname1:", zname) + cr.RedisLocalCli.ZAdd(zname, z).Result() + // fmt.Println("sorted Price acclr ", tk.InstID, "new diff:", tk.PriceDiff, "old PriceDiff:", score, "acclr:", acclr, "rs:", rs) +} + +func commonSorted(cr *Core, ctype string, period string, seg int64) []redis.Z { + env := os.Getenv("GO_ENV") + isProdction := env == "production" + zname := ctype + "|" + period + "|seg" + strconv.FormatInt(seg, 10) + ary, err := cr.RedisLocalCli.ZRevRangeWithScores(zname, 0, -1).Result() + if err != nil { + if !isProdction { + fmt.Println("sortedSet err:", err) + } + } + return ary +} diff --git a/core/data/tray.go b/data/tray.go similarity index 100% rename from core/data/tray.go rename to data/tray.go diff --git a/ini.sh b/ini.sh index ae5d669..89e1af1 100755 --- a/ini.sh +++ b/ini.sh @@ -1,23 +1,35 @@ -# Create standard Go project directories -mkdir -p api/v1 # For API contracts/protos -mkdir -p configs # For configuration files -mkdir -p scripts # For deployment/build scripts -mkdir -p test/e2e # For different test types -mkdir -p third_party # For third party dependencies +#!/bin/bash -# Reorganize existing directories -mv pkg/config internal/ # Move config to internal implementation -mv pkg/log internal/logger # Move logging to internal -mv pkg/utils internal/ # Move utilities to internal +# 创建目录结构 +mkdir -p config model service data util -# Create new service layer -mkdir -p internal/service # For business logic services -mkdir -p internal/repository # For data access layer -mkdir -p internal/transport # For HTTP/gRPC handlers +# 移动配置文件 +mv core/shared/config/config.go config/ +mv core/shared/config/const.go config/ -# Cleanup empty directories -rmdir pkg 2>/dev/null || true +# 移动模型文件 +mv core/models/candle.go model/ +mv core/models/maX.go model/ +mv core/models/ticker.go model/ + +# 移动服务文件 +mv core/services/service_context.go service/ + +# 移动数据层文件 +mv core/datapipes/coaster.go data/ +mv core/datapipes/pixel.go data/ +mv core/datapipes/plate.go data/ +mv core/datapipes/rsi.go data/ +mv core/datapipes/segmentItem.go data/ +mv core/datapipes/series.go data/ +mv core/datapipes/shearForceGrp.go data/ +mv core/datapipes/sorted.go data/ +mv core/datapipes/tray.go data/ + +# 移动工具类文件 +mv core/shared/util.go util/ +mv core/shared/logging/writeLog.go util/ + +echo "文件迁移完成!新目录结构:" +tree -d . -# Update build script -echo '#!/bin/sh -go build -ldflags "-s -w" -trimpath -o ./bin/core ./cmd/core' > build.sh diff --git a/core/data/candle.go b/model/candle.go similarity index 99% rename from core/data/candle.go rename to model/candle.go index 77c7860..b85a019 100644 --- a/core/data/candle.go +++ b/model/candle.go @@ -1,4 +1,4 @@ -package core +package model import ( "crypto/sha256" @@ -15,13 +15,14 @@ import ( simple "github.com/bitly/go-simplejson" "github.com/go-redis/redis" + "github.com/phyer/core/core" "github.com/phyer/texus/utils" logrus "github.com/sirupsen/logrus" ) type Candle struct { Id string `json:"_id"` - core *Core + core *core.Core InstID string `json:"instID"` Period string `json:"period"` Data []interface{} diff --git a/core/data/maX.go b/model/maX.go similarity index 99% rename from core/data/maX.go rename to model/maX.go index ee4dd4c..792aa98 100644 --- a/core/data/maX.go +++ b/model/maX.go @@ -1,4 +1,4 @@ -package core +package model import ( "encoding/json" diff --git a/core/data/ticker.go b/model/ticker.go similarity index 99% rename from core/data/ticker.go rename to model/ticker.go index 67f3c85..80e1b26 100644 --- a/core/data/ticker.go +++ b/model/ticker.go @@ -1,4 +1,4 @@ -package core +package model import ( "encoding/json" diff --git a/note.md b/note.md index 5dc80ff..94c6ff1 100644 --- a/note.md +++ b/note.md @@ -11,3 +11,1870 @@ mv maX.go rsi.go segmentItem.go series.go shearGorceGrp.go analysis/ && mv ticker.go market/ && mv writeLog.go log/ +- Datetime: 2025-01-27 19:03:41 + +- Model: openai/deepseek-chat + +- Selected files: + - core.go + +> 给加注释 + +I'll add comments to explain the key parts of the code. I'll break this into several *SEARCH/REPLACE* blocks to add comments to different sections. + +1. First, let's add comments to the Core struct definition: + +Filepath: core.go +Replace lines: 26-53 +```go +// Core represents the main application structure containing configuration, +// connections, and data processing channels +type Core struct { + Env string // Environment name (e.g. "production", "demoEnv") + Cfg *MyConfig // Application configuration + RedisLocalCli *redis.Client // Local Redis client instance + RedisRemoteCli *redis.Client // Remote Redis client instance + FluentBitUrl string // URL for FluentBit logging + PlateMap map[string]*Plate // Map of trading plates by instrument ID + TrayMap map[string]*Tray // Map of trading trays by instrument ID + CoasterMd5SyncMap sync.Map // Synchronized map for MD5 hashes of coasters + Mu *sync.Mutex // Primary mutex for synchronization + Mu1 *sync.Mutex // Secondary mutex for synchronization + Waity *sync.WaitGroup // WaitGroup for goroutine synchronization + CandlesProcessChan chan *Candle // Channel for processing candle data + MaXProcessChan chan *MaX // Channel for processing moving average data + RsiProcessChan chan *Rsi // Channel for processing RSI data + StockRsiProcessChan chan *StockRsi // Channel for processing stock RSI data + TickerInforocessChan chan *TickerInfo // Channel for processing ticker information + CoasterChan chan *CoasterInfo // Channel for processing coaster data + MakeMaXsChan chan *Candle // Channel for creating moving averages + InvokeRestQueueChan chan *RestQueue // Channel for REST API queue processing + RedisLocal2Cli *redis.Client // Secondary local Redis client + RestQueueChan chan *RestQueue // Channel for REST queue processing + RestQueue // Embedded RestQueue struct + WriteLogChan chan *WriteLog // Channel for writing logs +} +``` + +2. Let's add comments to the Init method: + +Filepath: core.go +Replace lines: 130-150 +```go +// Init initializes the Core application with environment variables, +// configuration, and Redis connections +func (core *Core) Init() { + // Get environment variables + core.Env = os.Getenv("GO_ENV") + gitBranch := os.Getenv("gitBranchName") + commitID := os.Getenv("gitCommitID") + + // Log environment information + logrus.Info("当前环境: ", core.Env) + logrus.Info("gitBranch: ", gitBranch) + logrus.Info("gitCommitID: ", commitID) + + // Initialize configuration + cfg := MyConfig{} + cfg, _ = cfg.Init() + core.Cfg = &cfg + + // Initialize Redis client + cli, err := core.GetRedisLocalCli() + core.RedisLocalCli = cli + + // Initialize channels + core.RestQueueChan = make(chan *RestQueue) + core.WriteLogChan = make(chan *WriteLog) + + // Handle Redis connection error + if err != nil { + logrus.Error("init redis client err: ", err) + } +} +``` + +3. Let's add comments to the GetRangeCandleSortedSet method: + +Filepath: core.go +Replace lines: 758-758 +```go +// GetRangeCandleSortedSet retrieves a range of candle data from Redis sorted set +// setName: Redis sorted set name containing candle data +// count: Number of candles to retrieve +// from: End timestamp for the range +// Returns CandleList containing the retrieved candles or error +func (core *Core) GetRangeCandleSortedSet(setName string, count int, from time.Time) (*CandleList, error) { +``` + +4. Let's add comments to the v5PublicInvoke method: + +Filepath: core.go +Replace lines: 292-292 +```go +// v5PublicInvoke makes a public API call to retrieve candle data +// subUrl: API endpoint path +// Returns CandleData containing the response or error +func (core *Core) v5PublicInvoke(subUrl string) (*CandleData, error) { +``` + +Would you like me to continue adding comments to other key methods? I can add more detailed comments to the Redis-related methods, data processing methods, or any other specific sections you'd like to focus on. + +--- + +- Datetime: 2025-01-27 20:23:13 + +- Model: openai/deepseek-chat + +- Selected files: + - maX.go + +- Selected code: + +```go +func (mx MaX) SetToKey(cr *Core) ([]interface{}, error) { + // fmt.Println(utils.GetFuncName(), " step1 ", mx.InstID, " ", mx.Period) + // mx.Timestamp, _ = Int64ToTime(mx.Ts) + cstr := strconv.Itoa(mx.Count) + tss := strconv.FormatInt(mx.Ts, 10) + //校验时间戳是否合法 + ntm, err := cr.PeriodToLastTime(mx.Period, time.UnixMilli(mx.Ts)) + if ntm.UnixMilli() != mx.Ts { + logrus.Warn(fmt.Sprint(GetFuncName(), " candles时间戳有问题 ", " 应该: ", ntm, "实际:", mx.Ts)) + mx.Ts = ntm.UnixMilli() + } + keyName := "ma" + cstr + "|candle" + mx.Period + "|" + mx.InstID + "|ts:" + tss + //过期时间:根号(当前candle的周期/1分钟)*10000 + + dj, err := json.Marshal(mx) + if err != nil { + logrus.Error("maX SetToKey json marshal err: ", err) + } + extt, err := cr.GetExpiration(mx.Period) + if err != nil { + logrus.Error("max SetToKey err: ", err) + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step2 ", mx.InstID, " ", mx.Period) + // tm := time.UnixMilli(mx.Ts).Format("01-02 15:04") + cli := cr.RedisLocalCli + if len(string(dj)) == 0 { + logrus.Error("mx data is block data: ", mx, string(dj)) + err := errors.New("data is block") + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step3 ", mx.InstID, " ", mx.Period) + _, err = cli.Set(keyName, dj, extt).Result() + if err != nil { + logrus.Error(GetFuncName(), " maXSetToKey err:", err) + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step4 ", mx.InstID, " ", mx.Period) + // fmt.Println("max setToKey: ", keyName, "res:", res, "data:", string(dj), "from: ", mx.From) + cr.SaveUniKey(mx.Period, keyName, extt, mx.Ts) + return mx.Data, err +} +``` + +> 这个函数是做啥的 +> + +这个函数 `SetToKey` 的主要功能是将一个 `MaX` 结构体数据存储到 Redis 中,并返回原始数据。具体步骤如下: + +1. **参数校验**: + - 检查时间戳是否合法,如果不合法则修正 + - 将 `Count` 和 `Ts` 转换为字符串 + +2. **生成 Redis Key**: + - 根据 `Count`, `Period`, `InstID`, `Ts` 生成一个唯一的 Redis key + - 格式为:`ma{count}|candle{period}|{instID}|ts:{timestamp}` + +3. **数据序列化**: + - 将 `MaX` 结构体序列化为 JSON 格式 + +4. **设置过期时间**: + - 根据 `Period` 获取对应的过期时间 + +5. **Redis 存储**: + - 将序列化后的数据存储到 Redis 中 + - 如果存储失败则返回错误 + +6. **保存唯一键**: + - 调用 `cr.SaveUniKey` 保存这个 key 的相关信息 + +7. **返回结果**: + - 返回原始数据 `mx.Data` 和可能的错误 + +这个函数主要用于将计算得到的移动平均线(MA)数据存储到 Redis 缓存中,以便后续快速访问和使用。它处理了数据校验、序列化、存储和错误处理等关键步骤。 + +--- + +- Datetime: 2025-01-27 20:24:12 + +- Model: openai/deepseek-chat + +- Selected files: + - maX.go + +- Selected code: + +```go +func (mx MaX) SetToKey(cr *Core) ([]interface{}, error) { + // fmt.Println(utils.GetFuncName(), " step1 ", mx.InstID, " ", mx.Period) + // mx.Timestamp, _ = Int64ToTime(mx.Ts) + cstr := strconv.Itoa(mx.Count) + tss := strconv.FormatInt(mx.Ts, 10) + //校验时间戳是否合法 + ntm, err := cr.PeriodToLastTime(mx.Period, time.UnixMilli(mx.Ts)) + if ntm.UnixMilli() != mx.Ts { + logrus.Warn(fmt.Sprint(GetFuncName(), " candles时间戳有问题 ", " 应该: ", ntm, "实际:", mx.Ts)) + mx.Ts = ntm.UnixMilli() + } + keyName := "ma" + cstr + "|candle" + mx.Period + "|" + mx.InstID + "|ts:" + tss + //过期时间:根号(当前candle的周期/1分钟)*10000 + + dj, err := json.Marshal(mx) + if err != nil { + logrus.Error("maX SetToKey json marshal err: ", err) + } + extt, err := cr.GetExpiration(mx.Period) + if err != nil { + logrus.Error("max SetToKey err: ", err) + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step2 ", mx.InstID, " ", mx.Period) + // tm := time.UnixMilli(mx.Ts).Format("01-02 15:04") + cli := cr.RedisLocalCli + if len(string(dj)) == 0 { + logrus.Error("mx data is block data: ", mx, string(dj)) + err := errors.New("data is block") + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step3 ", mx.InstID, " ", mx.Period) + _, err = cli.Set(keyName, dj, extt).Result() + if err != nil { + logrus.Error(GetFuncName(), " maXSetToKey err:", err) + return mx.Data, err + } + // fmt.Println(utils.GetFuncName(), " step4 ", mx.InstID, " ", mx.Period) + // fmt.Println("max setToKey: ", keyName, "res:", res, "data:", string(dj), "from: ", mx.From) + cr.SaveUniKey(mx.Period, keyName, extt, mx.Ts) + return mx.Data, err +} +``` + +> 请给这个函数加上适当的中文注释 +> + +我来为这个函数添加中文注释,解释每个步骤的作用: + +Filepath: maX.go +Replace lines: 39-80 +```go +// SetToKey 将MaX对象存储到Redis中 +// 参数: +// - cr: Core对象,包含Redis连接等核心功能 +// 返回值: +// - []interface{}: 原始数据 +// - error: 错误信息 +func (mx MaX) SetToKey(cr *Core) ([]interface{}, error) { + // 将count和timestamp转换为字符串 + cstr := strconv.Itoa(mx.Count) + tss := strconv.FormatInt(mx.Ts, 10) + + // 校验时间戳是否合法 + ntm, err := cr.PeriodToLastTime(mx.Period, time.UnixMilli(mx.Ts)) + if ntm.UnixMilli() != mx.Ts { + logrus.Warn(fmt.Sprint(GetFuncName(), " candles时间戳有问题 ", " 应该: ", ntm, "实际:", mx.Ts)) + mx.Ts = ntm.UnixMilli() // 修正时间戳 + } + + // 生成Redis key名称 + // 格式: ma{count}|candle{period}|{instID}|ts:{timestamp} + keyName := "ma" + cstr + "|candle" + mx.Period + "|" + mx.InstID + "|ts:" + tss + + // 将MaX对象序列化为JSON + dj, err := json.Marshal(mx) + if err != nil { + logrus.Error("maX SetToKey json marshal err: ", err) + } + + // 获取key的过期时间 + extt, err := cr.GetExpiration(mx.Period) + if err != nil { + logrus.Error("max SetToKey err: ", err) + return mx.Data, err + } + + // 获取Redis客户端 + cli := cr.RedisLocalCli + + // 检查序列化后的数据是否为空 + if len(string(dj)) == 0 { + logrus.Error("mx data is block data: ", mx, string(dj)) + err := errors.New("data is block") + return mx.Data, err + } + + // 将数据存储到Redis + _, err = cli.Set(keyName, dj, extt).Result() + if err != nil { + logrus.Error(GetFuncName(), " maXSetToKey err:", err) + return mx.Data, err + } + + // 保存唯一key到统一管理 + cr.SaveUniKey(mx.Period, keyName, extt, mx.Ts) + + return mx.Data, err +} +``` + +主要添加了: +1. 函数说明注释 +2. 参数说明 +3. 返回值说明 +4. 关键步骤的中文注释 +5. 代码逻辑的详细解释 + +注释保持了简洁明了,同时解释了每个关键步骤的作用和目的。 + +--- + +- Datetime: 2025-02-21 08:56:00 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + +- Selected code: + +```go +type Core struct { + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + FluentBitUrl string + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerInforocessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + // SeriesChan chan *SeriesInfo + // SegmentItemChan chan *SegmentItem + MakeMaXsChan chan *Candle + // ShearForceGrpChan chan *ShearForceGrp + InvokeRestQueueChan chan *RestQueue + RedisLocal2Cli *redis.Client + RestQueueChan chan *RestQueue + RestQueue + WriteLogChan chan *WriteLog +} + +``` + +> 我的这个代码中, Core这个struct会被整个项目的其他模块调用, 但是这Core里又包含了其他模块作为子元素, 这样一来,就会形成循环依赖, 没办法拆分成多个相对独立的模块. 我的本意是, 在某一个下游模块中调用,一个全局的core对象,使用其中的属性,当作每个模块都可见的公共上下文, 那么这种设计是不是有更好的替代方案 + +🤔 Thought content: + + +First, I need to recall common design patterns that handle shared context without tight coupling. The user mentioned using a global core object as a public context, which is leading to cycles. So the goal is to decouple the modules from the Core struct. + +Dependency Injection comes to mind. Instead of Core holding all the modules, each module could accept Core as a parameter. But that might not fully solve the circular dependency if modules still reference Core directly. + +Next, the Interface Segregation Principle. By defining interfaces for each module's dependencies, Core can implement those interfaces, allowing modules to depend only on the interfaces they need. This reduces coupling. + +Another approach is to split Core into smaller, more focused structs. For example, having separate contexts like RedisContext, ConfigContext, etc. Modules would only depend on the specific context they need, not the entire Core. This way, there's no single struct that includes everything. + +The Facade Pattern could also be useful. Core acts as a facade, providing access to subsystems without the subsystems needing to know about Core. But if modules need to call back into Core, this might not help much. + +Looking at the user's code, Core has many channels and maps. Maybe these can be grouped into separate contexts. For instance, a ServiceContext for Redis and config, a DataContext for channels, and a SyncContext for synchronization primitives. Then, modules only take the contexts they need. + +I should check how other Go projects manage similar issues. Using interfaces and breaking down large structs into smaller ones is common. Also, using composition over inheritance helps. + +So, the step-by-step plan would be: + +1. Break Core into smaller contexts (ServiceContext, DataContext, etc.). +2. Use interfaces to define dependencies each module needs. +3. Pass these contexts to modules instead of the entire Core. +4. Ensure modules don't hold references to the entire Core, only the parts they need. + +This way, modules are decoupled, and there's no circular dependency. Each module depends only on the specific interfaces or contexts it requires, not the entire Core. The user can then refactor their code to use these smaller contexts, eliminating the need for Core to hold all modules directly.你的需求非常适合使用"依赖注入+接口隔离"的设计模式来改进。我们可以通过以下几个步骤重构: + +1. 首先将核心功能拆分为独立的服务接口 +2. 使用上下文对象来承载共享资源 +3. 通过接口隔离各模块的依赖 + +具体修改方案如下: + +Filepath: core.go +Replace lines: 26-53 +```go +// 基础服务上下文 +type ServiceContext struct { + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + FluentBitUrl string +} + +// 数据管道上下文 +type DataContext struct { + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerProcessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + MakeMaXsChan chan *Candle + InvokeRestQueueChan chan *RestQueue + RestQueueChan chan *RestQueue + WriteLogChan chan *WriteLog +} + +// 状态同步上下文 +type SyncContext struct { + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup +} + +// 核心上下文组合 +type Core struct { + Services *ServiceContext + DataPipes *DataContext + Sync *SyncContext + RestQueue +} + +// 各模块接口定义 +type RedisService interface { + GetRedisLocalCli() *redis.Client + GetRemoteRedisCli() *redis.Client +} + +type ConfigService interface { + GetConfig() *MyConfig +} + +type PipelineService interface { + GetCandleChan() chan *Candle + GetWriteLogChan() chan *WriteLog +} + +// 实现接口的方法 +func (sc *ServiceContext) GetRedisLocalCli() *redis.Client { + return sc.RedisLocalCli +} + +func (sc *ServiceContext) GetConfig() *MyConfig { + return sc.Cfg +} + +func (dc *DataContext) GetCandleChan() chan *Candle { + return dc.CandlesProcessChan +} + +func (dc *DataContext) GetWriteLogChan() chan *WriteLog { + return dc.WriteLogChan +} +``` + +改进后的优势: + +1. 接口隔离原则 +每个模块只需依赖最小接口: +```go +// 日志处理模块示例 +type LogProcessor struct { + cfgService ConfigService + redisService RedisService + writeLogChan chan *WriteLog +} + +func NewLogProcessor(cfg ConfigService, redis RedisService, pipe PipelineService) *LogProcessor { + return &LogProcessor{ + cfgService: cfg, + redisService: redis, + writeLogChan: pipe.GetWriteLogChan(), + } +} +``` + +2. 明确职责边界 +- ServiceContext: 基础设施服务(Redis/Config等) +- DataContext: 数据管道管理 +- SyncContext: 状态同步管理 + +3. 解耦模块依赖 +各模块通过接口访问核心功能,而不是直接依赖Core结构体 + +4. 可测试性增强 +可以轻松为各模块创建mock实现 + +5. 扩展性更好 +新增功能只需实现对应接口 + +6. 生命周期管理 +可以独立控制各组件的初始化/关闭顺序 + +使用示例: +```go +// 初始化 +core := &Core{ + Services: &ServiceContext{...}, + DataPipes: &DataContext{...}, + Sync: &SyncContext{...}, +} + +// 业务模块初始化 +orderProcessor := NewOrderProcessor( + core.Services, // 只注入需要的服务 + core.DataPipes, +) +``` + +这种模式在大型Go项目中非常常见(参考Kubernetes的client-go设计),可以有效解决循环依赖问题,同时保持代码的可维护性和可测试性。 + +--- + +- Datetime: 2025-02-21 09:38:17 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + +- Selected code: + +```go +type Core struct { + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + FluentBitUrl string + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerInforocessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + // SeriesChan chan *SeriesInfo + // SegmentItemChan chan *SegmentItem + MakeMaXsChan chan *Candle + // ShearForceGrpChan chan *ShearForceGrp + InvokeRestQueueChan chan *RestQueue + RedisLocal2Cli *redis.Client + RestQueueChan chan *RestQueue + RestQueue + WriteLogChan chan *WriteLog +} + +``` + +> 请把当前项目下的文件按照上述模式进行调整, 并分出不同的模块 +> + +🤔 Thought content: + + > 用户的核心结构体是Core,它目前包含了服务上下文、数据管道、同步上下文等多个领域的字段,显得臃肿。目标是将这些字段拆分到不同的模块中,提高代码的可维护性和可读性。 + + > 首先,识别现有的结构体和接口。例如,ServiceContext、DataContext、SyncContext已经存在,但它们在Core中被扁平化地包含。我应该将这些上下文结构体提升为独立的模块,每个模块负责特定的功能领域。 + + +接下来,考虑如何拆分Core结构体。根据代码中的注释和字段用途,可以将其分为服务层、数据管道层、同步层等。例如,将Redis、配置等相关的字段移到ServiceContext,将通道相关的移到DataContext,同步相关的移到SyncContext。 + +然后,检查接口定义,如RedisService、ConfigService、PipelineService,这些接口已经由ServiceContext和DataContext实现。需要确保这些接口被正确实现,并可能将它们移动到独立的包中,如core/services、core/datapipes、core/sync等。 + +在文件结构上,建议创建新的目录结构,将不同的模块放在对应的包中。例如: + +- core/ + - services/ + - service_context.go + - redis_service.go + - config_service.go + - datapipes/ + - data_context.go + - pipelines.go + - sync/ + - sync_context.go + - state_management.go + - models/ + - rest_queue.go + - candle.go + - max.go + - core.go + +这样,每个文件负责特定的功能模块。例如,service_context.go包含ServiceContext结构体和相关方法,data_context.go处理数据通道等。 + +需要处理现有的函数和方法,将它们移动到对应的模块中。例如,GetRedisLocalCli和GetConfig方法属于ServiceContext,应移到services包下的文件中。数据处理函数如WriteLogProcess应移到datapipes包。 + +同时,需要注意依赖关系,避免循环导入。例如,core.go可能需要导入services、datapipes等包,并组合这些模块。 + +最后,确保所有测试和外部引用在重构后仍然正常工作。可能需要调整导入路径和方法调用,以匹配新的包结构。 + +在具体实施时,使用SEARCH/REPLACE块来修改现有文件或创建新文件。例如,将Core结构体拆分为组合各个模块的字段,而不是直接包含所有字段。同时,将相关的方法移动到对应的模块文件中。 + +需要用户确认是否已添加所有相关文件到聊天中,以便进行正确的替换和修改。如果某些文件未被添加,可能需要用户提供或授权创建新文件。 + +总结步骤: +1. 创建新的目录结构,组织模块。 +2. 拆分Core结构体,将字段移动到对应的模块结构体中。 +3. 移动相关的方法和函数到对应的包和文件中。 +4. 调整导入语句和引用,确保代码编译通过。 +5. 使用SEARCH/REPLACE块进行文件修改和创建。 + +在此过程中,需要保持接口的一致性,确保现有功能不受影响,同时提高代码的模块化和可维护性。首先我需要理解代码结构和功能模块。当前代码主要包含以下核心部分: + +1. 服务上下文(ServiceContext) +2. 数据管道(DataContext) +3. 状态同步(SyncContext) +4. REST接口相关 +5. Redis操作 +6. 核心业务逻辑 + +建议按以下结构重组: + +1. 创建新的目录结构: +``` +core/ +├── services/ +│ ├── service_context.go // ServiceContext及相关接口实现 +│ └── redis_service.go // Redis相关操作 +├── datapipes/ +│ ├── data_context.go // DataContext及通道定义 +│ └── pipelines.go // 数据处理流程 +├── sync/ +│ ├── sync_context.go // SyncContext及同步逻辑 +│ └── state_management.go // 状态管理 +├── models/ +│ ├── rest_queue.go // RestQueue及相关结构 +│ ├── candle.go // Candle相关结构 +│ └── max.go // MaX相关结构 +└── core.go // 核心组合结构 +``` + +需要您确认以下文件是否已添加到聊天中,我需要查看它们的完整内容才能进行安全修改: +1. core.go +2. 任何包含MyConfig定义的文件 +3. 任何包含Candle/MaX/Rsi结构定义的文件 +4. 任何包含Plate/Tray结构定义的文件 + +如果这些文件尚未添加,请先提供这些文件内容,我将根据实际内容进行安全的重构和模块划分。模块化重构需要精确匹配现有代码结构,否则可能会造成破坏性修改。 + +当您提供完整的文件列表后,我将: +1. 为每个模块创建对应的包目录 +2. 将相关结构体迁移到对应包 +3. 保持接口兼容性 +4. 更新引用路径 +5. 保持核心组合结构Core的完整性 + +请确认需要重构的具体文件列表,或提供当前项目中存在的完整文件树结构。 + +--- + +- Datetime: 2025-02-21 09:39:39 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + - core/core.go + - core/config/config.go + - core/config/const.go + - core/data/candle.go + - core/data/coaster.go + - core/data/maX.go + - core/data/pixel.go + - core/data/plate.go + - core/data/rsi.go + - core/data/segmentItem.go + - core/data/series.go + - core/data/shearForceGrp.go + - core/data/sorted.go + - core/data/ticker.go + - core/data/tray.go + - core/logging/writeLog.go + - core/utils/util.go + +- Selected code: + +```go +type Core struct { + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + FluentBitUrl string + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerInforocessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + // SeriesChan chan *SeriesInfo + // SegmentItemChan chan *SegmentItem + MakeMaXsChan chan *Candle + // ShearForceGrpChan chan *ShearForceGrp + InvokeRestQueueChan chan *RestQueue + RedisLocal2Cli *redis.Client + RestQueueChan chan *RestQueue + RestQueue + WriteLogChan chan *WriteLog +} + +``` + +> 文件在这里 + +🤔 Thought content: + + > 首先,我需要仔细查看用户提供的所有文件内容,特别是core.go文件,因为结构体定义通常放在那里。在core.go中,Core结构体被定义为: + + > type Core struct { + + Services *ServiceContext + DataPipes *DataContext + Sync *SyncContext + RestQueue +} + +这里,Core结构体包含了三个嵌套的结构体:Services、DataPipes和Sync,以及一个匿名的RestQueue字段。但用户提供的另一个代码片段中,Core结构体的定义更复杂,包含了许多通道(channel)字段和其他字段,如RedisLocal2Cli、FluentBitUrl等。这可能是用户希望整合的结果。 + +接下来,我需要检查其他文件,确认是否有其他属于Core结构体的字段。例如,在data/coaster.go中,Core可能有与Coaster相关的通道,比如CoasterChan。在data/segmentItem.go中,可能有SegmentItemChan。同样,其他文件可能涉及ShearForceGrpChan、SeriesChan等。 + +用户提供的目标代码中,Core结构体包含了许多通道字段,如CandlesProcessChan、MaXProcessChan等,这些在原始core.go中属于DataContext结构体。但用户可能希望将这些通道直接放在Core结构体中,而不是嵌套在DataContext里。这可能是因为用户希望简化结构,或者DataContext未被正确使用。 + +我需要确认各个通道字段的来源。例如,DataContext结构体在core.go中包含: + +type DataContext struct { + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerProcessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + MakeMaXsChan chan *Candle + InvokeRestQueueChan chan *RestQueue + RestQueueChan chan *RestQueue + WriteLogChan chan *WriteLog +} + +而在用户的目标代码中,这些通道被直接放在Core结构体中,同时还有其他的通道如SeriesChan、SegmentItemChan等,这些可能在代码的其他部分被引用。 + +因此,正确的做法是将DataContext中的字段提升到Core结构体中,同时添加其他必要的字段,如RedisLocal2Cli、FluentBitUrl、PlateMap、TrayMap等,这些字段在原始代码中属于Core的组成部分,可能在其他文件中被使用。 + +另外,需要注意字段的命名一致性,例如TickerInforocessChan应该是TickerProcessChan的拼写错误,需要纠正。 + +最后,需要确保所有在代码中引用的Core的字段都被包含在新的结构体定义中,避免编译错误。例如,在logging/writeLog.go中,Core可能有FluentBitUrl字段,这需要包含在Core结构体中。 + +总结步骤: +1. 提升DataContext中的通道字段到Core结构体。 +2. 添加其他必要的字段,如RedisLocal2Cli、FluentBitUrl、PlateMap等。 +3. 纠正字段名的拼写错误。 +4. 确保所有引用的字段都被包含。根据代码上下文,Core 结构体需要整合多个上下文和管道。以下是优化后的定义,包含所有必要的字段: + +```go +type Core struct { + // 基础服务 + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + RedisLocal2Cli *redis.Client // 新增的第二个 Redis 客户端 + FluentBitUrl string + + // 数据管道 + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerProcessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + MakeMaXsChan chan *Candle + InvokeRestQueueChan chan *RestQueue + RestQueueChan chan *RestQueue + WriteLogChan chan *WriteLog + + // 状态管理 + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + + // 并发控制 + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup + + // REST 队列 + RestQueue +} +``` + +需要修改的 SEARCH/REPLACE 块: + +```go +Filepath: core/core.go +Replace lines: 60-65 +```go +type Core struct { + // 基础服务 + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + RedisLocal2Cli *redis.Client + FluentBitUrl string + + // 数据管道 + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + RsiProcessChan chan *Rsi + StockRsiProcessChan chan *StockRsi + TickerProcessChan chan *TickerInfo + CoasterChan chan *CoasterInfo + MakeMaXsChan chan *Candle + InvokeRestQueueChan chan *RestQueue + RestQueueChan chan *RestQueue + WriteLogChan chan *WriteLog + + // 状态管理 + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + + // 并发控制 + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup + + // REST 队列 + RestQueue +} +``` +``` + +这个修改: + +1. 移除了 ServiceContext/DataContext/SyncContext 的分层设计,直接展开必要字段 +2. 添加了 RedisLocal2Cli 等缺失的基础服务字段 +3. 直接暴露所有管道通道 +4. 保留了必要的状态管理 Map +5. 保持并发控制原语 +6. 继承 RestQueue 匿名结构体 + +需要同步删除原 DataContext 和 SyncContext 的类型定义,因为其字段已直接整合到 Core 结构体中。 + +--- + +- Datetime: 2025-02-21 09:44:23 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + +> 提供形成目标目录结构的shell语句 + +🤔 Thought content: + + +接下来,我得看看用户提供的代码文件core.go的内容。虽然用户没有明确说明,但代码中的包名是core,可能属于项目的一个核心模块。因此,目录结构可能需要包含pkg/core这样的位置来存放核心代码。 + +然后,用户可能希望创建一个标准的Go项目结构,比如类似github.com/golang-standards/project-layout中的结构。常见的目录包括cmd用于存放主应用程序,internal用于内部包,pkg用于公开的库代码,configs存放配置文件,scripts用于脚本,build用于构建相关的文件,test用于测试数据或脚本,api用于API定义文件如Swagger或Protobuf。 + +此外,用户可能还需要初始化Go模块,使用go mod init命令,并创建必要的配置文件如Makefile、Dockerfile、.gitignore等。不过用户的问题只提到了目录结构,所以可能只需要创建目录的shell命令。 + +考虑到用户使用的是zsh,并且项目根目录是/Users/zhangkun/data/go/core,所以命令应该在项目根目录下执行。需要检查是否已经存在这些目录,如果不存在则创建。使用mkdir -p可以递归创建目录,即使父目录不存在也不会报错。 + +最后,组合这些目录创建命令,用一行mkdir -p加上所有目录路径,用反斜杠换行以便阅读。同时,建议用户初始化Go模块,并创建必要的文件,但根据问题,可能只需要目录结构命令,所以主要提供mkdir部分。根据Go项目标准目录结构和代码中看到的包名(core),创建基础目录结构的命令如下: + +```bash +mkdir -p \ +cmd/ \ +pkg/core/ \ +internal/ \ +api/ \ +configs/ \ +scripts/ \ +test/ \ +build/ \ +docs/ +``` + +主要目录说明: +1. `cmd/` - 存放主程序入口 +2. `pkg/core/` - 存放当前提供的核心模块代码 +3. `internal/` - 内部私有包代码 +4. `api/` - OpenAPI/Swagger定义等 +5. `configs/` - 配置文件模板 +6. `scripts/` - 构建/部署脚本 +7. `test/` - 测试相关文件 +8. `build/` - 打包构建产物 +9. `docs/` - 文档资料 + +建议在项目根目录执行: +```bash +go mod init github.com/yourusername/core +touch Makefile Dockerfile .gitignore README.md +``` + +需要我解释任何目录的作用或创建其他特定文件,请随时告知。 + +--- + +- Datetime: 2025-02-21 09:47:32 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + - core/core.go + - core/config/config.go + - core/config/const.go + - core/data/candle.go + - core/data/coaster.go + - core/data/maX.go + - core/data/pixel.go + - core/data/plate.go + - core/data/rsi.go + - core/data/segmentItem.go + - core/data/series.go + - core/data/shearForceGrp.go + - core/data/sorted.go + - core/data/ticker.go + - core/data/tray.go + - core/logging/writeLog.go + - core/utils/util.go + +> 当前目录结构,和文件内容, 改造成如下结构和内容,所需要的sell语句, +> +> core/ +> ├── services/ +> │ ├── service_context.go // ServiceContext及相关接口实现 +> │ └── redis_service.go // Redis相关操作 +> ├── datapipes/ +> │ ├── data_context.go // DataContext及通道定义 +> │ └── pipelines.go // 数据处理流程 +> ├── sync/ +> │ ├── sync_context.go // SyncContext及同步逻辑 +> │ └── state_management.go // 状态管理 +> ├── models/ +> │ ├── rest_queue.go // RestQueue及相关结构 +> │ ├── candle.go // Candle相关结构 +> │ └── max.go // MaX相关结构 +> └── core.go // 核心组合结构 +> ``` +> +> 请提供 + +🤔 Thought content: + + > 首先,用户提供的当前文件包括core.go、config/config.go、config/const.go、data目录下的多个文件(如candle.go、coaster.go等)、logging/writeLog.go、utils/util.go等。目标结构是将这些文件分类到services、datapipes、sync、models等子目录中,并重命名部分文件。 + + +接下来,我需要逐一映射每个现有文件到新的目录结构中。例如,core.go中的ServiceContext应该移到services/service_context.go,而Redis相关的方法可能移到services/redis_service.go。DataContext和相关管道定义应移动到datapipes/data_context.go,而数据处理流程可能放在datapipes/pipelines.go。SyncContext和状态管理则需要移到sync目录下的相应文件。模型如RestQueue、Candle、MaX等应集中在models目录中。 + +同时,需要注意代码中的导入路径调整,确保所有引用都能正确指向新的文件位置。此外,部分结构体和方法可能需要移动或重构,以符合新的模块划分。例如,将RedisService接口及其实现集中到redis_service.go中,将数据管道相关的通道定义和接口移到datapipes目录。 + +最后,生成必要的shell命令来创建目录结构,移动和重命名文件。需要确保目录存在,使用mkdir -p创建嵌套目录,然后使用mv命令移动文件到正确位置。可能还需要检查文件内容,确保导入语句和包声明正确更新,但用户可能希望先处理文件移动,再处理代码调整。 + +总结步骤: +1. 创建目标目录结构(services, datapipes, sync, models等)。 +2. 将core.go中的各部分代码拆分到对应的新文件中。 +3. 移动现有的data、logging、utils等目录下的文件到新位置。 +4. 重命名文件以符合目标结构。 +5. 调整每个文件的包声明和导入路径。 + +需要注意,部分文件可能需要进一步的重构,比如拆分core.go中的不同结构体到各自的模块中,但用户可能希望先完成目录结构和文件移动,后续再处理代码细节。因此,shell命令主要处理文件和目录的移动及重命名,代码调整可能需要手动完成或通过其他工具辅助。根据代码结构和内容分析,需要执行以下目录重组操作: + +```bash +#!/bin/zsh + +# 创建目标目录结构 +mkdir -p core/services core/datapipes core/sync core/models + +# 移动服务相关文件 +mv core/core.go core/services/service_context.go +mv core/core.go core/services/redis_service.go # 需要手动拆分Redis相关代码 + +# 移动数据管道相关文件 +mv core/data/*.go core/datapipes/ +mv core/data/candle.go core/datapipes/data_context.go +mv core/data/pipelines/*.go core/datapipes/pipelines.go # 需要手动整合流程代码 + +# 移动同步相关文件 +mv core/sync/*.go core/sync/sync_context.go +mv core/state/*.go core/sync/state_management.go # 需要手动拆分状态管理代码 + +# 移动模型文件 +mv core/data/rest_queue.go core/models/ +mv core/data/candle.go core/models/ +mv core/data/max.go core/models/ + +# 移动核心组合结构 +mv core/core.go core/core.go # 保留核心组合结构 + +# 处理工具类和配置 +mv core/utils core/shared/ +mv core/config core/shared/config +mv core/logging core/shared/logging +``` + +需要手动调整的代码部分: + +1. 在service_context.go中保留: +```go +type ServiceContext struct { + Env string + Cfg *MyConfig + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + FluentBitUrl string +} + +type ConfigService interface { + GetConfig() *MyConfig +} + +func (sc *ServiceContext) GetConfig() *MyConfig { + return sc.Cfg +} +``` + +2. 在redis_service.go中集中Redis操作: +```go +type RedisService interface { + GetRedisLocalCli() *redis.Client + GetRemoteRedisCli() *redis.Client +} + +func (sc *ServiceContext) GetRedisLocalCli() *redis.Client { + return sc.RedisLocalCli +} + +// 包含所有Redis操作方法... +``` + +3. 在data_context.go中定义管道结构: +```go +type DataContext struct { + CandlesProcessChan chan *Candle + MaXProcessChan chan *MaX + // 其他通道定义... +} + +type PipelineService interface { + GetCandleChan() chan *Candle +} + +func (dc *DataContext) GetCandleChan() chan *Candle { + return dc.CandlesProcessChan +} +``` + +4. 在sync_context.go中维护状态同步: +```go +type SyncContext struct { + PlateMap map[string]*Plate + TrayMap map[string]*Tray + CoasterMd5SyncMap sync.Map + Mu *sync.Mutex + Mu1 *sync.Mutex + Waity *sync.WaitGroup +} +``` + +建议后续操作: +1. 使用IDE的重构功能批量修改import路径 +2. 运行go fmt格式化所有文件 +3. 逐步验证各模块的单元测试 +4. 更新Makefile中的构建路径 +5. 调整CI/CD流水线配置 + +这个重组过程需要结合代码语义进行合理拆分,建议分步骤验证每个模块的功能完整性。 + +--- + +- Datetime: 2025-02-21 10:02:51 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core/datapipes/coaster.go + - core/datapipes/pixel.go + - core/datapipes/plate.go + - core/datapipes/rsi.go + - core/datapipes/segmentItem.go + - core/datapipes/series.go + - core/datapipes/shearForceGrp.go + - core/datapipes/sorted.go + - core/datapipes/tray.go + - core/models/candle.go + - core/models/maX.go + - core/models/ticker.go + - core/services/service_context.go + - core/shared/util.go + - core/shared/config/config.go + - core/shared/config/const.go + - core/shared/logging/writeLog.go + +- Selected code: + +```go + cd.Data[4], _ = strconv.ParseFloat(cd.Data[4].(string), 64) +``` + +> 当前目录层级划分是否合理 + +🤔 Thought content: + + +用户提供的代码中,核心包是`core`,下面有多个子目录,比如`datapipes`、`models`、`services`、`shared`等。每个子目录包含不同类型的文件,比如数据管道、模型定义、服务层、共享工具等。 + +首先,`datapipes`目录下有很多数据处理的类型,比如`coaster.go`、`pixel.go`等,这些可能负责数据流转和处理。`models`目录包含数据结构定义,如`candle.go`、`maX.go`等,这些是业务模型。`services`中的`service_context.go`似乎是服务的上下文和核心逻辑。`shared`目录下有工具函数和配置,比如`util.go`和`config`相关文件。 + +接下来,评估合理性。通常,Go项目的目录结构会遵循标准布局,如`cmd`、`pkg`、`internal`等,但用户的结构是自定义的。这可能符合项目的特定需求,但需要检查是否清晰和可维护。 + +存在的问题可能包括: + +1. **目录命名不够清晰**:例如`datapipes`可能不够直观,是否可以考虑更明确的名称,如`data`或`pipeline`? +2. **职责划分不明确**:`datapipes`中的文件似乎混合了数据结构和处理逻辑,而`models`主要定义结构体。可能需要更清晰的分层,比如将数据结构放在`models`,处理逻辑放在`services`或`handlers`。 +3. **`shared`目录的使用**:工具函数和配置放在`shared`下是常见的,但需确认是否有更好的组织方式,比如拆分为`util`和`config`。 +4. **服务层的位置**:`services`目录中的文件是否合理,是否需要进一步细分,比如`repository`、`service`等层? + +改进建议: + +- **调整目录结构**:使用更通用的结构,如`internal/core`包含核心业务逻辑,`internal/models`放数据结构,`internal/services`处理业务逻辑,`internal/utils`放工具函数,`config`处理配置。 +- **明确职责分离**:确保每个目录职责单一,避免混合数据结构和处理逻辑。 +- **重命名目录**:例如将`datapipes`改为`dataflow`或`pipeline`,使其更直观。 + +需要根据项目的规模和团队习惯来调整,但当前结构在小型项目中可能可行,但在扩展时可能遇到维护问题。根据现有代码结构分析,目录层级划分存在以下优化空间: + +*// 当前结构* +core/ +├── datapipes/ // 数据管道(命名可优化) +├── models/ // 数据模型(存在职责混杂) +├── services/ // 服务层(可细化分层) +├── shared/ // 共享代码(可拆分为独立模块) +└── ... + +*// 优化建议* + +1. **目录命名规范化** +``` +core/ +├── data/ // 替代 datapipes (更简洁) +├── model/ // 替代 models (Go 习惯单数形式) +├── service/ // 服务层 (保持单数) +├── config/ // 配置模块 (从 shared 独立) +├── utils/ // 工具包 (从 shared 独立) +└── internal/ // 内部私有代码 (可选) +``` + +2. **职责分离优化** +* `data/` 聚焦数据流动: + ```go + data/ + ├── pipeline/ // 数据管道处理逻辑 + ├── processor/ // 数据加工处理器 + └── entity/ // 数据实体定义 (原 models 部分代码) + ``` + +* `model/` 纯领域模型: + ```go + model/ + ├── candle.go // 蜡烛图模型 + ├── market.go // 市场数据模型 + └── ... + ``` + +* `service/` 按业务域划分: + ```go + service/ + ├── market/ // 行情服务 + ├── trade/ // 交易服务 + └── analytics/ // 分析服务 + ``` + +3. **共享代码拆分** +* `utils/` 通用工具库: + ```go + utils/ + ├── timeutil.go // 时间处理 + ├── mathutil.go // 数学计算 + └── ... + ``` + +* `config/` 配置管理: + ```go + config/ + ├── loader.go // 配置加载 + └── app.yaml // 配置文件 + ``` + +4. **依赖关系清晰化** +``` + +------------+ + | config | + +-----+------+ + | + +-----v------+ + | model | + +-----+------+ + | ++--------+ +-----v------+ +| utils <-----+ service | ++--------+ +-----+------+ + | + +-----v------+ + | data | + +------------+ +``` + +*优化理由* + +1. **符合 Go 惯例**:使用单数形式目录名 (`service` 而非 `services`),更符合 Go 社区习惯 + +2. **高内聚低耦合**:每个目录职责单一性增强,例如: + - `data/entity` 只定义数据结构 + - `data/pipeline` 只处理数据流转 + - `service` 只包含业务逻辑 + +3. **可维护性提升**:通过模块化拆分,代码变更影响范围更可控,例如修改数据解析逻辑只需关注 `data` 模块 + +4. **可测试性增强**:独立模块更方便 mock 测试,例如单独测试 service 层时 mock data 层 + +*示例代码调整* + +以 `core/datapipes/coaster.go` 为例: + +```go +// 调整后路径 +core/data/entity/coaster.go + +type Coaster struct { + // 结构体定义保持不变 + InstID string + Period string + // ... +} + +// 原数据处理逻辑迁移到 +core/data/pipeline/coaster_processor.go + +func (p *CoasterProcessor) Process(data []byte) (*entity.Coaster, error) { + // 解析处理逻辑 +} +``` + +这种结构调整后,领域模型与处理逻辑解耦,代码结构更清晰。 + +--- + +- Datetime: 2025-02-21 10:09:46 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core/datapipes/coaster.go + - core/datapipes/pixel.go + - core/datapipes/plate.go + - core/datapipes/rsi.go + - core/datapipes/segmentItem.go + - core/datapipes/series.go + - core/datapipes/shearForceGrp.go + - core/datapipes/sorted.go + - core/datapipes/tray.go + - core/models/candle.go + - core/models/maX.go + - core/models/ticker.go + - core/services/service_context.go + - core/shared/util.go + - core/shared/config/config.go + - core/shared/config/const.go + - core/shared/logging/writeLog.go + +- Selected code: + +```go + cd.Data[4], _ = strconv.ParseFloat(cd.Data[4].(string), 64) +``` + +> 划分成 config, model, service, data, util 这几个目录,现在目前的10个go文件该如何区分到不同的目录,请给个shell脚本,我来执行 + +🤔 Thought content: + + > 首先,用户提供的文件列表包括: + + > - core/datapipes下的coaster.go、pixel.go、plate.go、rsi.go、segmentItem.go、series.go、shearForceGrp.go、sorted.go、tray.go + +- core/models下的candle.go、maX.go、ticker.go +- core/services下的service_context.go +- core/shared/util.go +- core/shared/config下的config.go、const.go +- core/shared/logging下的writeLog.go + +接下来,我需要根据常见的项目结构来分类: + +1. **config目录**:通常存放配置文件,比如const.go和config.go。这两个文件在core/shared/config下,应该移到config目录。 + +2. **model目录**:存放数据模型和结构体定义。查看core/models下的candle.go、maX.go、ticker.go,这些显然是模型定义,应该放在model目录。此外,datapipes下的文件如coaster.go、pixel.go等也包含数据结构和相关方法,可能属于数据模型,但datapipes可能更偏向数据处理,需要进一步判断。不过根据用户之前的划分,可能datapipes下的文件属于data目录,而model目录只放核心模型。 + +3. **service目录**:存放业务逻辑和服务层代码。service_context.go属于服务层,应该放在service目录。 + +4. **data目录**:处理数据存储、管道等。datapipes下的所有文件(coaster.go、pixel.go等)可能属于数据操作,应移至data目录。 + +5. **util目录**:工具函数。shared/util.go应该移到util目录,而logging下的writeLog.go属于日志工具,可能也放在util下,或者单独一个logging目录,但用户要求分为util,所以可能合并到util。 + +现在需要确认每个文件的具体内容: + +- coaster.go:定义了Coaster结构体及相关方法,属于数据模型,可能放在model或data。但根据datapipes的命名,可能属于数据处理,归入data。 +- pixel.go:Pixel结构体,数据模型,归入model或data。需要看是否涉及数据处理逻辑,如果有方法处理数据流,可能属于data。 +- plate.go:Plate结构体,数据处理,归入data。 +- rsi.go:RSI相关结构体,属于数据指标,归入model或data。 +- segmentItem.go:Segment结构体,数据处理,归入data。 +- series.go:Series结构体,数据系列处理,归入data。 +- shearForceGrp.go:剪切力相关,数据处理,归入data。 +- sorted.go:排序相关,数据处理,归入data。 +- tray.go:Tray结构体,数据处理,归入data。 + +所以datapipes下的文件都属于data目录。 + +models下的candle、maX、ticker属于model目录。 + +service_context.go属于service。 + +shared/util.go到util,config到config,logging/writeLog.go到util或logging,但根据用户要求可能合并到util。 + +但用户可能希望logging作为独立目录,但根据问题描述,用户只提到config, model, service, data, util,所以writeLog.go可能归入util。 + +现在需要生成一个shell脚本,移动这些文件到对应目录: + +- config/ → const.go, config.go +- model/ → candle.go, maX.go, ticker.go +- service/ → service_context.go +- data/ → coaster.go, pixel.go, plate.go, rsi.go, segmentItem.go, series.go, shearForceGrp.go, sorted.go, tray.go +- util/ → util.go, writeLog.go + +注意原文件路径: + +core/datapipes下的文件移动到data/ +core/models下的移动到model/ +core/services移动到service/ +core/shared/config移动到config/ +core/shared/util.go到util/ +core/shared/logging/writeLog.go到util/ + +因此,脚本需要创建目录,然后移动文件: + +mkdir -p config model service data util + +mv core/shared/config/config.go config/ +mv core/shared/config/const.go config/ +mv core/models/candle.go model/ +mv core/models/maX.go model/ +mv core/models/ticker.go model/ +mv core/services/service_context.go service/ +mv core/datapipes/*.go data/ +mv core/shared/util.go util/ +mv core/shared/logging/writeLog.go util/ + +需要检查路径是否正确,确保所有文件都被正确移动。此外,注意文件名的大小写,确保路径正确。根据代码结构和功能划分,我帮你编写一个迁移脚本: + +```bash +#!/bin/bash + +# 创建目录结构 +mkdir -p config model service data util + +# 移动配置文件 +mv core/shared/config/config.go config/ +mv core/shared/config/const.go config/ + +# 移动模型文件 +mv core/models/candle.go model/ +mv core/models/maX.go model/ +mv core/models/ticker.go model/ + +# 移动服务文件 +mv core/services/service_context.go service/ + +# 移动数据层文件 +mv core/datapipes/coaster.go data/ +mv core/datapipes/pixel.go data/ +mv core/datapipes/plate.go data/ +mv core/datapipes/rsi.go data/ +mv core/datapipes/segmentItem.go data/ +mv core/datapipes/series.go data/ +mv core/datapipes/shearForceGrp.go data/ +mv core/datapipes/sorted.go data/ +mv core/datapipes/tray.go data/ + +# 移动工具类文件 +mv core/shared/util.go util/ +mv core/shared/logging/writeLog.go util/ + +echo "文件迁移完成!新目录结构:" +tree -d . +``` + +执行步骤: +1. 将上述内容保存为`reorganize.sh` +2. 添加执行权限:`chmod +x reorganize.sh` +3. 在项目根目录运行:`./reorganize.sh` + +迁移后目录结构: +``` +. +├── config +│ ├── config.go +│ └── const.go +├── model +│ ├── candle.go +│ ├── maX.go +│ └── ticker.go +├── service +│ └── service_context.go +├── data +│ ├── coaster.go +│ ├── pixel.go +│ ├── plate.go +│ ├── rsi.go +│ ├── segmentItem.go +│ ├── series.go +│ ├── shearForceGrp.go +│ ├── sorted.go +│ └── tray.go +└── util + ├── util.go + └── writeLog.go +``` + +注意: +1. 请确保在项目根目录执行脚本 +2. 执行前建议先提交git,以便回滚 +3. 迁移后需要检查各个文件的import路径是否正确 +4. 如果存在包名冲突,需要手动调整package声明 + +--- + +- Datetime: 2025-02-21 10:17:58 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + - config/config.go + - config/const.go + - data/coaster.go + - data/pixel.go + - data/plate.go + - data/rsi.go + - data/segmentItem.go + - data/series.go + - data/shearForceGrp.go + - data/sorted.go + - data/tray.go + - model/candle.go + - model/maX.go + - model/ticker.go + - service/service_context.go + - util/util.go + - util/writeLog.go + +> 现在结构整理完成了,下面我们该处理什么了 +> + +🤔 Thought content: + + +首先,我需要回顾用户提供的代码结构,看看有哪些部分可能需要进一步处理。用户给出的代码包括core.go、config/config.go、config/const.go等多个文件,涉及服务上下文、数据管道、状态同步等模块。结构上已经分成了数据模型、配置、服务等目录,但可能还存在一些问题。 + +接下来,用户可能想知道接下来的步骤。通常,在整理完代码结构后,常见的下一步包括: + +1. **代码质量优化**:比如消除重复代码,统一错误处理,提高可测试性。 +2. **依赖管理**:检查是否有不必要的依赖,更新第三方库版本。 +3. **接口与实现分离**:将接口定义与具体实现分离,提高模块化。 +4. **增强文档与注释**:补充文档和注释,方便维护。 +5. **配置管理改进**:优化配置加载方式,支持环境变量等。 +6. **引入DI/IoC容器**:使用依赖注入提升可维护性。 +7. **补充单元测试**:增加测试覆盖率,确保功能稳定。 +8. **性能优化**:分析性能瓶颈,进行优化。 + +查看用户提供的代码,例如在core.go中,ServiceContext、DataContext等结构体可能存在直接依赖,可以引入接口。错误处理在多个地方是分散的,可以统一。配置加载在config.go中使用硬编码路径,可以改为更灵活的方式。此外,部分代码块被注释掉,如订单相关功能,可能需要清理。 + +需要指出具体的代码部分,例如在core.go的GetRedisLocalCli方法中,重复的代码可以提取成公共函数。在config.go中,配置文件路径可以改为从环境变量获取。此外,存在未使用的导入和注释掉的代码,需要清理。 + +总结建议用户下一步进行代码质量优化,比如消除重复、统一错误处理,并举例说明如何修改。同时,询问用户是否有特定方向的需求,以便提供更针对性的建议。根据当前代码结构和行业最佳实践,我建议接下来按以下优先级进行优化: + +1. *关键质量改进* +Filepath: +Replace lines: 53-60 +```go +func (cfg MyConfig) Init() (MyConfig, error) { + // 支持多路径配置加载 + configPaths := []string{ + os.Getenv("CONFIG_PATH"), // 最高优先级 + "/etc/sardine/config.json", // 系统级配置 + "configs/basicConfig.json", // 开发环境配置 + "/go/json/basicConfig.json", // 容器环境配置 + } + + var jsonStr []byte + var err error + + for _, path := range configPaths { + if path == "" { + continue + } + if jsonStr, err = ioutil.ReadFile(path); err == nil { + break + } + } + + if err != nil { + return cfg, fmt.Errorf("配置文件加载失败: %w (尝试路径: %v)", err, configPaths) + } +``` + +3. *依赖清理* +Filepath: core.go +Replace lines: 3-24 +```go +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/go-redis/redis" + logrus "github.com/sirupsen/logrus" +) +``` + +4. *接口隔离示例* +Filepath: core.go +Replace lines: 82-85 +```go +// 新增存储接口 +type StorageProvider interface { + GetClient() (*redis.Client, error) + HealthCheck() error +} + +// Redis存储实现 +type RedisStorage struct { + client *redis.Client + config RedisConfig +} + +func (rs *RedisStorage) GetClient() (*redis.Client, error) { + if rs.client == nil { + client, err := createRedisClient(rs.config.Url, rs.config.Password, rs.config.Index) + if err != nil { + return nil, err + } + rs.client = client + } + return rs.client, nil +} + +func (rs *RedisStorage) HealthCheck() error { + _, err := rs.client.Ping().Result() + return err +} + +// 更新服务上下文 +type ServiceContext struct { + Env string + Cfg *MyConfig + Storage StorageProvider // 替换原有RedisCli + FluentBitUrl string +} +``` + +是否需要我继续针对以下方面提供具体改进方案? +1. 增加单元测试示例 +2. 性能优化建议 +3. 错误处理统一方案 +4. 文档生成方案 +5. 持续集成配置 + +您最关心哪个方面的改进?我可以优先提供对应修改方案。 + +--- + +- Datetime: 2025-02-21 10:27:20 + +- Model: alidsr1/deepseek-r1 + +- Selected files: + - core.go + - config/config.go + - config/const.go + - data/coaster.go + - data/pixel.go + - data/plate.go + - data/rsi.go + - data/segmentItem.go + - data/series.go + - data/shearForceGrp.go + - data/sorted.go + - data/tray.go + - model/candle.go + - model/maX.go + - model/ticker.go + - service/service_context.go + - util/util.go + - util/writeLog.go + +> 我最关注的是依赖注入和接口隔离,请对相关文件进行变更建议 + +🤔 Thought content: + + > 首先,用户提到关注依赖注入和接口隔离。这意味着需要解耦现有的类,使其依赖通过接口注入,而不是直接依赖具体实现。同时,接口隔离原则要求将大接口拆分成更小、更专注的接口,避免类实现不需要的方法。 + + > 查看现有代码,例如在core.go中,ServiceContext结构体直接包含了Redis客户端和配置对象。这违反了依赖注入原则,因为这些依赖应该通过接口注入,而不是具体实现。此外,ConfigService等接口定义在core.go中,但实现可能过于宽泛,需要拆分。 + + +接下来,我需要识别哪些部分可以抽象成接口。例如,Redis客户端可以抽象为RedisClient接口,配置管理可以抽象为ConfigProvider接口。同时,现有的接口如RedisService、ConfigService等可能需要进一步拆分,确保每个接口的职责单一。 + +然后,考虑如何重构代码。例如,在Core结构体中,原本直接依赖ServiceContext的具体实现,现在应改为依赖接口。这样可以在测试时更容易替换模拟对象,提高代码的可测试性和灵活性。 + +另外,在config/config.go中,MyConfig结构体直接使用了第三方库simplejson,这可能导致与具体实现的耦合。应该通过接口抽象配置的获取,使得MyConfig依赖于一个ConfigProvider接口,而不是具体的实现。 + +在data/coaster.go等数据处理文件中,存在直接使用Redis客户端的情况。这些地方应该通过依赖注入传入接口,而不是直接创建或使用全局的Redis客户端实例。 + +可能的步骤包括: +1. 定义必要的接口,如RedisClient、ConfigProvider、Logger等。 +2. 修改现有结构体,使其依赖接口而非具体实现。 +3. 使用构造函数或工厂模式注入依赖,确保在创建对象时传入接口实例。 +4. 拆分过大的接口,例如将PipelineService拆分为更细粒度的接口。 +5. 调整测试代码,利用模拟对象进行单元测试。 + +需要注意现有代码中的全局变量和直接初始化,例如在Init函数中直接创建Redis客户端,这应该改为通过依赖注入传入。同时,确保接口的设计符合单一职责原则,避免一个接口包含过多方法。 + +最后,需要检查所有文件中的依赖关系,确保每个模块只依赖于必要的接口,而不是具体实现。这可能涉及多个文件的修改,包括core.go、config/config.go、service/service_context.go等。通过逐步替换和测试,确保重构后的代码符合依赖注入和接口隔离原则,同时保持功能不变。根据依赖注入和接口隔离原则,我会对代码提出以下改进建议: + +1. 首先在core.go中定义更明确的接口并拆分现有大接口: + +Filepath: core.go +Replace lines: 68-80 +```go +// 更细分的接口 +type RedisConnector interface { + GetClient(options *redis.Options) (*redis.Client, error) + Ping(client *redis.Client) error +} + +type ConfigProvider interface { + GetString(keyPath string) (string, error) + GetInt(keyPath string) (int, error) + GetBool(keyPath string) (bool, error) +} + +type PipelineOperator interface { + CandlePipeline() chan *Candle + LogPipeline() chan *WriteLog +} + +type Logger interface { + Info(args ...interface{}) + Error(args ...interface{}) + Warn(args ...interface{}) + Debug(args ...interface{}) +} + +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} +``` + +2. 修改ServiceContext结构体以依赖接口: + +Filepath: core.go +Replace lines: 27-33 +```go +type ServiceContext struct { + Env string + Config ConfigProvider + Redis RedisConnector + HTTPClient HTTPClient + Logger Logger + FluentBitUrl string +} +``` + +3. 修改Config实现使其实现ConfigProvider接口: + +Filepath: config/config.go +Replace lines: 0-0 +```go +type MyConfig struct { + configSource ConfigProvider // 依赖接口 + redisConf *RedisConfig + // 其他字段保持不变... +} + +// 实现ConfigProvider接口 +func (c *MyConfig) GetString(keyPath string) (string, error) { + return c.configSource.GetString(keyPath) +} + +func (c *MyConfig) GetInt(keyPath string) (int, error) { + return c.configSource.GetInt(keyPath) +} + +func (c *MyConfig) GetBool(keyPath string) (bool, error) { + return c.configSource.GetBool(keyPath) +} +``` + +4. 修改依赖初始化的方式(构造函数注入): + +Filepath: service/service_context.go +Replace lines: 130-150 +```go +func NewCore( + config ConfigProvider, + redisConn RedisConnector, + httpClient HTTPClient, + logger Logger, +) *Core { + core := &Core{ + Config: config, + Redis: redisConn, + HTTPClient: httpClient, + Logger: logger, + RestQueueChan: make(chan *RestQueue), + WriteLogChan: make(chan *WriteLog), + } + + core.Env = os.Getenv("GO_ENV") + core.FluentBitUrl = os.Getenv("SARDINE_FluentBitUrl") + + logger.Info("当前环境: ", core.Env) + logger.Info("FluentBit地址: ", core.FluentBitUrl) + + return core +} +``` + +5. 修改数据访问层依赖: + +Filepath: data/coaster.go +Replace lines: 0-0 +```go +type CoasterStorage interface { + Set(key string, value interface{}, expiration time.Duration) error + Get(key string) (string, error) +} + +func (co *Coaster) SetToKey(storage CoasterStorage) error { + js, _ := json.Marshal(co) + coasterName := co.InstID + "|" + co.Period + "|coaster" + return storage.Set(coasterName, js, 0) +} +``` + +主要改进点: + +1. 接口隔离: +- 将大的RedisService拆分为RedisConnector +- 将配置访问抽象为ConfigProvider +- 将日志记录抽象为Logger接口 +- 将HTTP客户端抽象为HTTPClient接口 + +2. 依赖注入: +- 通过构造函数注入核心依赖 +- 数据存储层通过接口访问 +- 移除对具体实现的直接依赖(如logrus、redis.Client) + +3. 实现解耦: +- 核心业务逻辑不再依赖具体框架 +- 配置系统可替换实现 +- 数据存储可替换实现 +- 日志系统可替换实现 + +建议后续改进方向: + +1. 为每个包定义明确的接口 +2. 使用依赖注入容器管理生命周期 +3. 将领域模型与基础设施分离 +4. 为关键组件添加接口的mock实现 +5. 使用选项模式优化多参数初始化 + +示例接口定义文件(新建interfaces.go): + +Filepath: core/interfaces.go +Replace lines: 1-0 +```go +package core + +import ( + "time" + "github.com/go-redis/redis" +) + +// 领域服务接口 +type CandleManager interface { + GetCandles(instID string, period string) ([]*Candle, error) + SaveCandle(candle *Candle) error +} + +// 基础设施接口 +type RedisService interface { + GetClient(options *redis.Options) (*redis.Client, error) + Ping(client *redis.Client) error +} + +type HTTPRequester interface { + Get(url string) ([]byte, error) + Post(url string, body []byte) ([]byte, error) +} + +// 领域事件接口 +type EventPublisher interface { + Publish(topic string, message interface{}) error + Subscribe(topic string, handler func(message []byte)) error +} +``` + +这些改动将显著提高代码的可测试性、可维护性和可扩展性,同时降低模块间的耦合度。 + +**Generation complete!** Please review the code suggestions above. + diff --git a/core/services/core.go b/service/service_context.go similarity index 93% rename from core/services/core.go rename to service/service_context.go index 747fe05..8340f6a 100644 --- a/core/services/core.go +++ b/service/service_context.go @@ -18,16 +18,20 @@ import ( // simple "github.com/bitly/go-simplejson" // "v5sdk_go/ws/wImpl" "github.com/go-redis/redis" + "github.com/phyer/core/model" "github.com/phyer/texus/private" "github.com/phyer/v5sdkgo/rest" logrus "github.com/sirupsen/logrus" ) type Core struct { - Env string - Cfg *MyConfig - RedisLocalCli *redis.Client - RedisRemoteCli *redis.Client + Env string + Config ConfigProvider // 改为接口类型 + Redis RedisConnector // 改为接口类型 + HTTPClient HTTPClient // 改为接口类型 + Logger Logger // 改为接口类型 + + // 保留原有的运行时字段 FluentBitUrl string PlateMap map[string]*Plate TrayMap map[string]*Tray @@ -35,21 +39,54 @@ type Core struct { Mu *sync.Mutex Mu1 *sync.Mutex Waity *sync.WaitGroup - CandlesProcessChan chan *Candle + CandlesProcessChan chan *model.Candle MaXProcessChan chan *MaX RsiProcessChan chan *Rsi StockRsiProcessChan chan *StockRsi TickerInforocessChan chan *TickerInfo CoasterChan chan *CoasterInfo - // SeriesChan chan *SeriesInfo - // SegmentItemChan chan *SegmentItem - MakeMaXsChan chan *Candle - // ShearForceGrpChan chan *ShearForceGrp - InvokeRestQueueChan chan *RestQueue - RedisLocal2Cli *redis.Client - RestQueueChan chan *RestQueue + SeriesChan chan *SeriesInfo + SegmentItemChan chan *SegmentItem + MakeMaXsChan chan *Candle + ShearForceGrpChan chan *ShearForceGrp + InvokeRestQueueChan chan *RestQueue + RestQueueChan chan *RestQueue RestQueue WriteLogChan chan *WriteLog + + // 降级为内部字段(后续可逐步移除) + RedisLocalCli *redis.Client + RedisRemoteCli *redis.Client + RedisLocal2Cli *redis.Client + Cfg *MyConfig +} + +// 各模块接口定义 +type RedisConnector interface { + GetClient(options *redis.Options) (*redis.Client, error) + Ping(client *redis.Client) error +} + +type ConfigProvider interface { + GetString(keyPath string) (string, error) + GetInt(keyPath string) (int, error) + GetBool(keyPath string) (bool, error) +} + +type Logger interface { + Info(args ...interface{}) + Error(args ...interface{}) + Warn(args ...interface{}) + Debug(args ...interface{}) +} + +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type PipelineOperator interface { + CandlePipeline() chan *Candle + LogPipeline() chan *WriteLog } type RestQueue struct { @@ -127,26 +164,28 @@ func WriteLogProcess(cr *Core) { // fmt.Println("serverSystem time:", rsp) // } -func (core *Core) Init() { - core.Env = os.Getenv("GO_ENV") - gitBranch := os.Getenv("gitBranchName") - commitID := os.Getenv("gitCommitID") - - logrus.Info("当前环境: ", core.Env) - logrus.Info("gitBranch: ", gitBranch) - logrus.Info("gitCommitID: ", commitID) - cfg := MyConfig{} - cfg, _ = cfg.Init() - core.Cfg = &cfg - cli, err := core.GetRedisLocalCli() - core.RedisLocalCli = cli - core.RestQueueChan = make(chan *RestQueue) - core.WriteLogChan = make(chan *WriteLog) - // 跟订单有关的都关掉 - // core.OrderChan = make(chan *private.Order) - if err != nil { - logrus.Error("init redis client err: ", err) +func NewCore( + config ConfigProvider, + redisConn RedisConnector, + httpClient HTTPClient, + logger Logger, +) *Core { + core := &Core{ + Config: config, + Redis: redisConn, + HTTPClient: httpClient, + Logger: logger, + RestQueueChan: make(chan *RestQueue), + WriteLogChan: make(chan *WriteLog), } + + core.Env = os.Getenv("GO_ENV") + core.FluentBitUrl = os.Getenv("SARDINE_FluentBitUrl") + + logger.Info("当前环境: ", core.Env) + logger.Info("FluentBit地址: ", core.FluentBitUrl) + + return core } func (core *Core) GetRedisCliFromConf(conf RedisConfig) (*redis.Client, error) { diff --git a/core/utils/util.go b/util/util.go similarity index 100% rename from core/utils/util.go rename to util/util.go diff --git a/core/logging/writeLog.go b/util/writeLog.go similarity index 100% rename from core/logging/writeLog.go rename to util/writeLog.go