From 8db94da233f57cb5f194732633e9addf86e05585 Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Tue, 15 Jul 2025 18:49:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(stock):=E6=9B=B4=E6=96=B0=E8=82=A1?= =?UTF-8?q?=E7=A5=A8=E5=9F=BA=E7=A1=80=E4=BF=A1=E6=81=AF=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 CheckStockBaseInfo 方法,用于更新股票基础信息 - 修改 domReady 方法,移除初始化股票数据的逻辑 - 更新 StockBasic、StockInfoHK 和 StockInfoUS 模型,添加行业代码和名称字段 - 修改 getDCStockInfo 方法,支持获取更详细的股票信息 - 添加 DCToTsCode 函数,用于将东财代码转换为 TS 代码 - 优化行业报告信息获取功能 --- app.go | 96 +++++++++-- app_test.go | 21 +++ backend/data/market_news_api.go | 25 +++ backend/data/market_news_api_test.go | 3 + backend/data/stock_data_api.go | 107 ++++++++++--- backend/data/stock_data_api_test.go | 3 +- backend/models/models.go | 4 + backend/util/html_to_markdown.go | 221 ++++++++++++++++++++++++++ backend/util/html_to_markdown_test.go | 6 + frontend/wailsjs/go/models.ts | 4 + go.mod | 2 +- 11 files changed, 456 insertions(+), 36 deletions(-) create mode 100644 backend/util/html_to_markdown.go create mode 100644 backend/util/html_to_markdown_test.go diff --git a/app.go b/app.go index 751ed85..494af66 100644 --- a/app.go +++ b/app.go @@ -301,21 +301,24 @@ func (a *App) CheckUpdate() { // domReady is called after front-end resources have been loaded func (a *App) domReady(ctx context.Context) { defer PanicHandler() + defer func() { + go runtime.EventsEmit(ctx, "loadingMsg", "done") + }() - if stocksBin != nil && len(stocksBin) > 0 { - go runtime.EventsEmit(a.ctx, "loadingMsg", "检查A股基础信息...") - go initStockData(a.ctx) - } - - if stocksBinHK != nil && len(stocksBinHK) > 0 { - go runtime.EventsEmit(a.ctx, "loadingMsg", "检查港股基础信息...") - go initStockDataHK(a.ctx) - } - - if stocksBinUS != nil && len(stocksBinUS) > 0 { - go runtime.EventsEmit(a.ctx, "loadingMsg", "检查美股基础信息...") - go initStockDataUS(a.ctx) - } + //if stocksBin != nil && len(stocksBin) > 0 { + // go runtime.EventsEmit(a.ctx, "loadingMsg", "检查A股基础信息...") + // go initStockData(a.ctx) + //} + // + //if stocksBinHK != nil && len(stocksBinHK) > 0 { + // go runtime.EventsEmit(a.ctx, "loadingMsg", "检查港股基础信息...") + // go initStockDataHK(a.ctx) + //} + // + //if stocksBinUS != nil && len(stocksBinUS) > 0 { + // go runtime.EventsEmit(a.ctx, "loadingMsg", "检查美股基础信息...") + // go initStockDataUS(a.ctx) + //} updateBasicInfo() // Add your action here @@ -424,6 +427,7 @@ func (a *App) domReady(ctx context.Context) { a.cron.AddFunc("30 05 8,12,20 * * *", func() { logger.SugaredLogger.Errorf("Checking for updates...") a.CheckUpdate() + a.CheckStockBaseInfo() }) }() @@ -460,7 +464,71 @@ func (a *App) domReady(ctx context.Context) { logger.SugaredLogger.Infof("domReady-cronEntrys:%+v", a.cronEntrys) } +func (a *App) CheckStockBaseInfo() { + defer PanicHandler() + stockBasics := &[]data.StockBasic{} + resty.New().R(). + SetHeader("user", "go-stock"). + SetResult(stockBasics). + Get("https://go-stock.sparkmemory.top/stock_basic.json") + + for _, stock := range *stockBasics { + stockInfo := &data.StockBasic{ + TsCode: stock.TsCode, + Name: stock.Name, + Symbol: stock.Symbol, + BKCode: stock.BKCode, + BKName: stock.BKName, + } + db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).First(stockInfo) + if stockInfo.ID == 0 { + db.Dao.Model(&data.StockBasic{}).Create(stockInfo) + } else { + db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Updates(stockInfo) + } + } + + stockHKBasics := &[]models.StockInfoHK{} + resty.New().R(). + SetHeader("user", "go-stock"). + SetResult(stockHKBasics). + Get("https://go-stock.sparkmemory.top/stock_base_info_hk.json") + for _, stock := range *stockHKBasics { + stockInfo := &models.StockInfoHK{ + Code: stock.Code, + Name: stock.Name, + BKName: stock.BKName, + BKCode: stock.BKCode, + } + db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).First(stockInfo) + if stockInfo.ID == 0 { + db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo) + } else { + db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stock.Code).Updates(stockInfo) + } + } + stockUSBasics := &[]models.StockInfoUS{} + resty.New().R(). + SetHeader("user", "go-stock"). + SetResult(stockUSBasics). + Get("https://go-stock.sparkmemory.top/stock_base_info_us.json") + for _, stock := range *stockUSBasics { + stockInfo := &models.StockInfoUS{ + Code: stock.Code, + Name: stock.Name, + BKName: stock.BKName, + BKCode: stock.BKCode, + } + db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).First(stockInfo) + if stockInfo.ID == 0 { + db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo) + } else { + db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stock.Code).Updates(stockInfo) + } + } + +} func (a *App) NewsPush(news *[]models.Telegraph) { for _, telegraph := range *news { //if telegraph.IsRed { diff --git a/app_test.go b/app_test.go index be40857..4d9caee 100644 --- a/app_test.go +++ b/app_test.go @@ -1,7 +1,10 @@ package main import ( + "encoding/json" + "go-stock/backend/db" "go-stock/backend/logger" + "go-stock/backend/models" "testing" "time" ) @@ -23,3 +26,21 @@ func TestIsUSTradingTime(t *testing.T) { t.Log(IsUSTradingTime(time.Now())) } + +func TestCheckStockBaseInfo(t *testing.T) { + db.Init("./data/stock.db") + NewApp().CheckStockBaseInfo() +} + +func TestJson(t *testing.T) { + db.Init("./data/stock.db") + + jsonStr := "{\n\t\t\"id\" : 3334,\n\t\t\"created_at\" : \"2025-02-28 16:49:31.8342514+08:00\",\n\t\t\"updated_at\" : \"2025-02-28 16:49:31.8342514+08:00\",\n\t\t\"deleted_at\" : null,\n\t\t\"code\" : \"PUK.US\",\n\t\t\"name\" : \"英国保诚集团\",\n\t\t\"full_name\" : \"\",\n\t\t\"e_name\" : \"\",\n\t\t\"exchange\" : \"NASDAQ\",\n\t\t\"type\" : \"stock\",\n\t\t\"is_del\" : 0,\n\t\t\"bk_name\" : null,\n\t\t\"bk_code\" : null\n\t}" + + v := &models.StockInfoUS{} + json.Unmarshal([]byte(jsonStr), v) + logger.SugaredLogger.Infof("v:%+v", v) + + db.Dao.Model(v).Updates(v) + +} diff --git a/backend/data/market_news_api.go b/backend/data/market_news_api.go index 913f49f..147fd6d 100644 --- a/backend/data/market_news_api.go +++ b/backend/data/market_news_api.go @@ -15,6 +15,7 @@ import ( "go-stock/backend/db" "go-stock/backend/logger" "go-stock/backend/models" + "go-stock/backend/util" "strconv" "strings" "time" @@ -832,3 +833,27 @@ func (m MarketNewsApi) GetPMI() *models.PMIResp { return res } +func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) { + url := "https://data.eastmoney.com/report/zw_industry.jshtml?infocode=" + infoCode + resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R(). + SetHeader("Host", "data.eastmoney.com"). + SetHeader("Origin", "https://data.eastmoney.com"). + SetHeader("Referer", "https://data.eastmoney.com/report/industry.jshtml"). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"). + Get(url) + if err != nil { + logger.SugaredLogger.Errorf("GetIndustryReportInfo err:%s", err.Error()) + return + } + body := resp.Body() + //logger.SugaredLogger.Debugf("GetIndustryReportInfo:%s", body) + doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) + title, _ := doc.Find("div.c-title").Html() + content, _ := doc.Find("div.ctx-content").Html() + //logger.SugaredLogger.Infof("GetIndustryReportInfo:\n%s\n%s", title, content) + markdown, err := util.HTMLToMarkdown(title + content) + if err != nil { + return + } + logger.SugaredLogger.Infof("GetIndustryReportInfo markdown:\n%s", markdown) +} diff --git a/backend/data/market_news_api_test.go b/backend/data/market_news_api_test.go index d0f0b7a..edf1938 100644 --- a/backend/data/market_news_api_test.go +++ b/backend/data/market_news_api_test.go @@ -201,3 +201,6 @@ func TestGetPMI(t *testing.T) { md := util.MarkdownTableWithTitle("采购经理人指数(PMI)", res.PMIResult.Data) logger.SugaredLogger.Debugf(md) } +func TestGetIndustryReportInfo(t *testing.T) { + NewMarketNewsApi().GetIndustryReportInfo("AP202507151709216483") +} diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index e65f3fe..e91f082 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -153,6 +153,8 @@ type StockBasic struct { IsHs string `json:"is_hs"` ActName string `json:"act_name"` ActEntType string `json:"act_ent_type"` + BKName string `json:"bk_name"` + BKCode string `json:"bk_code"` } type FollowedStock struct { @@ -1454,7 +1456,7 @@ func getSinaStockInfo(receiver StockDataApi, page, pageSize int) *[]models.SinaS func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) { //m:105,m:106,m:107 //美股 //m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股 - fs := "" + fs := "m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048" switch market { case "hk": fs = "m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2" @@ -1462,62 +1464,108 @@ func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) { fs = "m:105,m:106,m:107" } - url := "https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery371047843066631541353_1745889398012&fs=%s&fields=f12,f13,f14,f19,f1,f2,f4,f3,f152,f17,f18,f15,f16,f5,f6&fid=f3&pn=%d&pz=%d&po=1&dect=1" + url := "https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=data&fs=%s&fields=f12,f13,f14,f1,f2,f4,f3,f152,f5,f6,f7,f15,f18,f16,f17,f10,f8,f9,f23,f100,f265&fid=f3&pn=%d&pz=%d&po=1&dect=1&wbp2u=|0|0|0|web&_=%d" + sprintfUrl := fmt.Sprintf(url, fs, page, pageSize, time.Now().UnixMilli()) + logger.SugaredLogger.Infof("url:%s", sprintfUrl) resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R(). SetHeader("Host", "push2.eastmoney.com"). + SetHeader("Referer", "https://quote.eastmoney.com/center/gridlist.html"). SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"). - Get(fmt.Sprintf(url, fs, page, pageSize)) + Get(sprintfUrl) if err != nil { logger.SugaredLogger.Errorf("err:%s", err.Error()) return } body := string(resp.Body()) - body = strutil.ReplaceWithMap(body, map[string]string{ - "jQuery371047843066631541353_1745889398012(": "", - ");": "", - }) - js := "var d=" + body + logger.SugaredLogger.Infof("resp:%s", body) vm := otto.New() - _, err = vm.Run(js) - _, err = vm.Run("var data = JSON.stringify(d);") - value, err := vm.Get("data") - data := make(map[string]any) - err = json.Unmarshal([]byte(value.String()), &data) + vm.Run("function data(res){return res};") + val, err := vm.Run(body) + if err != nil { + logger.SugaredLogger.Errorf("vm.Run error:%v", err.Error()) + } + value, _ := val.Object().Value().Export() + marshal, err := json.Marshal(value) + data := make(map[string]any) + err = json.Unmarshal(marshal, &data) if err != nil { - logger.SugaredLogger.Errorf("json:%s", value.String()) logger.SugaredLogger.Errorf("json.Unmarshal error:%v", err.Error()) } logger.SugaredLogger.Infof("resp:%s", data) if data["data"] != nil { datas := data["data"].(map[string]any) total := datas["total"].(float64) - diff := datas["diff"].(map[string]any) + diff := datas["diff"].([]any) logger.SugaredLogger.Infof("total:%d", int(total)) for k, item := range diff { stock := item.(map[string]any) - logger.SugaredLogger.Infof("k:%s,%s:%s", k, stock["f14"], stock["f12"]) + logger.SugaredLogger.Infof("k:%d,%s:%s:%s %s:%s", k, stock["f14"], stock["f12"], DCToTsCode(stock["f12"].(string)), stock["f100"], stock["f265"]) + + if market == "" { + stockInfo := &StockBasic{ + Symbol: stock["f12"].(string), + TsCode: DCToTsCode(stock["f12"].(string)), + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), + } + db.Dao.Model(&StockBasic{}).Where("symbol = ?", stockInfo.Symbol).First(stockInfo) + logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo) + if stockInfo.ID == 0 { + db.Dao.Model(&StockBasic{}).Create(stockInfo) + } else { + stockInfo = &StockBasic{ + Symbol: stock["f12"].(string), + TsCode: DCToTsCode(stock["f12"].(string)), + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), + } + db.Dao.Model(&StockBasic{}).Where("symbol = ?", stockInfo.Symbol).Updates(stockInfo) + } + } if market == "hk" { stockInfo := &models.StockInfoHK{ - Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK", - Name: stock["f14"].(string), + Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK", + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), } db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stockInfo.Code).First(stockInfo) logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo) if stockInfo.ID == 0 { db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo) + } else { + stockInfo = &models.StockInfoHK{ + Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK", + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), + } + db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stockInfo.Code).Updates(stockInfo) } } if market == "us" { stockInfo := &models.StockInfoUS{ - Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US", - Name: stock["f14"].(string), + Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US", + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), } db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stockInfo.Code).First(stockInfo) logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo) if stockInfo.ID == 0 { db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo) + } else { + stockInfo = &models.StockInfoUS{ + Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US", + Name: stock["f14"].(string), + BKName: stock["f100"].(string), + BKCode: stock["f265"].(string), + } + db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stockInfo.Code).Updates(stockInfo) } } @@ -1526,6 +1574,25 @@ func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) { } } +func DCToTsCode(dcCode string) string { + //北京证券交易所 8(83、87、88 等) 创新型中小企业(专精特新为主) + //上海证券交易所 6(60、688 等) 大盘蓝筹、科创板(高新技术) + //深圳证券交易所 0、3(000、002、30 等) 中小盘、创业板(成长型创新企业) + switch dcCode[0:1] { + case "8": + return dcCode + ".BJ" + case "9": + return dcCode + ".BJ" + case "6": + return dcCode + ".SH" + case "0": + return dcCode + ".SZ" + case "3": + return dcCode + ".SZ" + } + return "" +} + func (receiver StockDataApi) GetHKStockInfo(pageSize int) { url := "https://stock.gtimg.cn/data/hk_rank.php?board=main_all&metric=price&pageSize=%d&reqPage=1&order=desc&var_name=list_data" resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R(). diff --git a/backend/data/stock_data_api_test.go b/backend/data/stock_data_api_test.go index 817c525..c4d5ca3 100644 --- a/backend/data/stock_data_api_test.go +++ b/backend/data/stock_data_api_test.go @@ -110,7 +110,8 @@ func TestGetHKStockInfo(t *testing.T) { //NewStockDataApi().GetSinaHKStockInfo() //m:105,m:106,m:107 //美股 //m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股 - for i := 1; i <= 592; i++ { + //287 224 605 + for i := 1; i <= 605; i++ { NewStockDataApi().getDCStockInfo("us", i, 20) time.Sleep(time.Duration(random.RandInt(1, 3)) * time.Second) } diff --git a/backend/models/models.go b/backend/models/models.go index 831e9d3..1f431b2 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -172,6 +172,8 @@ type StockInfoHK struct { FullName string `json:"fullName"` EName string `json:"eName"` IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` + BKName string `json:"bk_name"` + BKCode string `json:"bk_code"` } func (receiver StockInfoHK) TableName() string { @@ -187,6 +189,8 @@ type StockInfoUS struct { Exchange string `json:"exchange"` Type string `json:"type"` IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` + BKName string `json:"bk_name"` + BKCode string `json:"bk_code"` } func (receiver StockInfoUS) TableName() string { diff --git a/backend/util/html_to_markdown.go b/backend/util/html_to_markdown.go new file mode 100644 index 0000000..1317e48 --- /dev/null +++ b/backend/util/html_to_markdown.go @@ -0,0 +1,221 @@ +package util + +// @Author spark +// @Date 2025/7/15 14:08 +// @Desc +//----------------------------------------------------------------------------------- + +import ( + "bytes" + "fmt" + "golang.org/x/net/html" + "strings" +) + +// HTMLNode 表示HTML文档中的一个节点 +type HTMLNode struct { + Type html.NodeType + Data string + Attr []html.Attribute + Children []*HTMLNode +} + +// HTMLToMarkdown 将HTML转换为Markdown +func HTMLToMarkdown(htmlContent string) (string, error) { + doc, err := html.Parse(strings.NewReader(htmlContent)) + if err != nil { + return "", err + } + + root := parseHTMLNode(doc) + var buf bytes.Buffer + convertNode(&buf, root, 0) + + return buf.String(), nil +} + +// parseHTMLNode 递归解析HTML节点 +func parseHTMLNode(n *html.Node) *HTMLNode { + node := &HTMLNode{ + Type: n.Type, + Data: n.Data, + Attr: n.Attr, + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + node.Children = append(node.Children, parseHTMLNode(c)) + } + + return node +} + +// convertNode 递归转换节点为Markdown +func convertNode(buf *bytes.Buffer, node *HTMLNode, depth int) { + switch node.Type { + case html.ElementNode: + convertElementNode(buf, node, depth) + case html.TextNode: + // 处理文本节点,去除多余的空白 + text := strings.TrimSpace(node.Data) + if text != "" { + buf.WriteString(text) + } + } + + // 递归处理子节点 + for _, child := range node.Children { + convertNode(buf, child, depth+1) + } + + // 处理需要在结束标签后添加内容的元素 + switch node.Data { + case "p", "h1", "h2", "h3", "h4", "h5", "h6", "li": + buf.WriteString("\n\n") + case "blockquote": + buf.WriteString("\n") + } +} + +// convertElementNode 转换元素节点为Markdown +func convertElementNode(buf *bytes.Buffer, node *HTMLNode, depth int) { + switch node.Data { + case "h1": + buf.WriteString("# ") + case "h2": + buf.WriteString("## ") + case "h3": + buf.WriteString("### ") + case "h4": + buf.WriteString("#### ") + case "h5": + buf.WriteString("##### ") + case "h6": + buf.WriteString("###### ") + case "p": + // 段落标签不需要特殊标记,直接处理内容 + case "strong", "b": + buf.WriteString("**") + case "em", "i": + buf.WriteString("*") + case "u": + buf.WriteString("") + case "s", "del": + buf.WriteString("~~") + case "a": + //href := getAttrValue(node.Attr, "href") + buf.WriteString("[") + case "img": + src := getAttrValue(node.Attr, "src") + alt := getAttrValue(node.Attr, "alt") + buf.WriteString(fmt.Sprintf("![%s](%s)", alt, src)) + case "ul": + // 无序列表不需要特殊标记,子项会处理 + case "ol": + // 有序列表不需要特殊标记,子项会处理 + case "li": + if isParentListType(node, "ul") { + buf.WriteString("- ") + } else { + // 计算当前列表项的序号 + index := 1 + if parent := findParentList(node); parent != nil { + for i, sibling := range parent.Children { + if sibling == node { + index = i + 1 + break + } + } + } + buf.WriteString(fmt.Sprintf("%d. ", index)) + } + case "blockquote": + buf.WriteString("> ") + case "code": + if isParentPre(node) { + // 父节点是pre,使用代码块 + buf.WriteString("\n```\n") + } else { + // 行内代码 + buf.WriteString("`") + } + case "pre": + // 前置代码块由子节点code处理 + case "br": + buf.WriteString("\n") + case "hr": + buf.WriteString("\n---\n") + } + + // 处理闭合标签 + if needsClosingTag(node.Data) { + defer func() { + switch node.Data { + case "strong", "b": + buf.WriteString("**") + case "em", "i": + buf.WriteString("*") + case "u": + buf.WriteString("") + case "s", "del": + buf.WriteString("~~") + case "a": + href := getAttrValue(node.Attr, "href") + buf.WriteString(fmt.Sprintf("](%s)", href)) + case "code": + if isParentPre(node) { + buf.WriteString("\n```\n") + } else { + buf.WriteString("`") + } + } + }() + } +} + +// getAttrValue 获取属性值 +func getAttrValue(attrs []html.Attribute, key string) string { + for _, attr := range attrs { + if attr.Key == key { + return attr.Val + } + } + return "" +} + +// isParentListType 检查父节点是否为指定类型的列表 +func isParentListType(node *HTMLNode, listType string) bool { + parent := findParentList(node) + return parent != nil && parent.Data == listType +} + +// findParentList 查找父列表节点 +func findParentList(node *HTMLNode) *HTMLNode { + // 简化实现,实际应该递归查找父节点 + if node.Type == html.ElementNode && (node.Data == "ul" || node.Data == "ol") { + return node + } + return nil +} + +// isParentPre 检查父节点是否为pre +func isParentPre(node *HTMLNode) bool { + if len(node.Children) == 0 { + return false + } + for _, child := range node.Children { + if child.Type == html.ElementNode && child.Data == "pre" { + return true + } + } + return false +} + +// needsClosingTag 判断元素是否需要闭合标签 +func needsClosingTag(tag string) bool { + switch tag { + case "img", "br", "hr", "input", "meta", "link": + return false + default: + return true + } +} diff --git a/backend/util/html_to_markdown_test.go b/backend/util/html_to_markdown_test.go new file mode 100644 index 0000000..80362de --- /dev/null +++ b/backend/util/html_to_markdown_test.go @@ -0,0 +1,6 @@ +package util + +// @Author spark +// @Date 2025/7/15 14:08 +// @Desc +//----------------------------------------------------------------------------------- diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 6897338..b8c1760 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -426,6 +426,8 @@ export namespace data { is_hs: string; act_name: string; act_ent_type: string; + bk_name: string; + bk_code: string; static createFrom(source: any = {}) { return new StockBasic(source); @@ -454,6 +456,8 @@ export namespace data { this.is_hs = source["is_hs"]; this.act_name = source["act_name"]; this.act_ent_type = source["act_ent_type"]; + this.bk_name = source["bk_name"]; + this.bk_code = source["bk_code"]; } convertValues(a: any, classs: any, asMap: boolean = false): any { diff --git a/go.mod b/go.mod index 1444d3d..ea3e883 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/tidwall/gjson v1.14.2 github.com/wailsapp/wails/v2 v2.10.1 go.uber.org/zap v1.27.0 + golang.org/x/net v0.38.0 golang.org/x/sys v0.31.0 golang.org/x/text v0.23.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -82,7 +83,6 @@ require ( go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/net v0.38.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.5 // indirect