feat(cron):设置cron时,cron任务实时生效,避免重启

- 新增 AddCronTask 函数用于添加 cron 任务
- 在 App 结构中添加 cronEntrys 字典用于管理 cron 任务 ID- 优化 SetStockAICron 函数,支持更新和删除 cron 任务
- 新增 GetFollowedStockByStockCode 函数用于获取关注的股票信息
- 更新前端 API 接口,添加 AddCronTask 方法
This commit is contained in:
ArvinLovegood 2025-03-30 15:10:55 +08:00
parent 95c3909dc9
commit b186a17a81
5 changed files with 123 additions and 33 deletions

86
app.go
View File

@ -30,9 +30,10 @@ import (
// App struct
type App struct {
ctx context.Context
cache *freecache.Cache
cron *cron.Cron
ctx context.Context
cache *freecache.Cache
cron *cron.Cron
cronEntrys map[string]cron.EntryID
}
// NewApp creates a new App application struct
@ -42,8 +43,9 @@ func NewApp() *App {
c := cron.New(cron.WithSeconds())
c.Start()
return &App{
cache: cache,
cron: c,
cache: cache,
cron: c,
cronEntrys: make(map[string]cron.EntryID),
}
}
@ -172,35 +174,45 @@ func (a *App) domReady(ctx context.Context) {
if follow.Cron == "" {
continue
}
logger.SugaredLogger.Errorf("添加自动分析任务:%s cron=%s", follow.Name, follow.Cron)
a.cron.AddFunc(follow.Cron, func() {
go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode)
ai := data.NewDeepSeekOpenAi(a.ctx)
msgs := ai.NewChatStream(follow.Name, follow.StockCode, "", nil)
var res strings.Builder
chatId := ""
question := ""
for msg := range msgs {
if msg["extraContent"] != nil {
res.WriteString(msg["extraContent"].(string) + "\n")
}
if msg["content"] != nil {
res.WriteString(msg["content"].(string))
}
if msg["chatId"] != nil {
chatId = msg["chatId"].(string)
}
if msg["question"] != nil {
question = msg["question"].(string)
}
}
data.NewDeepSeekOpenAi(a.ctx).SaveAIResponseResult(follow.StockCode, follow.Name, res.String(), chatId, question)
})
entryID, err := a.cron.AddFunc(follow.Cron, a.AddCronTask(follow))
logger.SugaredLogger.Errorf("添加自动分析任务:%s cron=%s entryID:%v", follow.Name, follow.Cron, entryID)
a.cronEntrys[follow.StockCode] = entryID
if err != nil {
return
}
}
}
func (a *App) AddCronTask(follow data.FollowedStock) func() {
return func() {
go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode)
ai := data.NewDeepSeekOpenAi(a.ctx)
msgs := ai.NewChatStream(follow.Name, follow.StockCode, "", nil)
var res strings.Builder
chatId := ""
question := ""
for msg := range msgs {
if msg["extraContent"] != nil {
res.WriteString(msg["extraContent"].(string) + "\n")
}
if msg["content"] != nil {
res.WriteString(msg["content"].(string))
}
if msg["chatId"] != nil {
chatId = msg["chatId"].(string)
}
if msg["question"] != nil {
question = msg["question"].(string)
}
}
data.NewDeepSeekOpenAi(a.ctx).SaveAIResponseResult(follow.StockCode, follow.Name, res.String(), chatId, question)
go runtime.EventsEmit(a.ctx, "warnMsg", "AI分析完成"+follow.Name+"_"+follow.StockCode)
}
}
func refreshTelegraphList() *[]string {
url := "https://www.cls.cn/telegraph"
response, err := resty.New().R().
@ -860,8 +872,18 @@ func (a *App) AddPrompt(prompt models.Prompt) string {
func (a *App) DelPrompt(id uint) string {
return data.NewPromptTemplateApi().DelPrompt(id)
}
func (a *App) SetStockAICron(cron, stockCode string) {
data.NewStockDataApi().SetStockAICron(cron, stockCode)
func (a *App) SetStockAICron(cronText, stockCode string) {
data.NewStockDataApi().SetStockAICron(cronText, stockCode)
if strutil.HasPrefixAny(stockCode, []string{"gb_"}) {
stockCode = strings.ToUpper(stockCode)
stockCode = strings.Replace(stockCode, "gb_", "us", 1)
stockCode = strings.Replace(stockCode, "GB_", "us", 1)
}
if entryID, exists := a.cronEntrys[stockCode]; exists {
a.cron.Remove(entryID)
}
follow := data.NewStockDataApi().GetFollowedStockByStockCode(stockCode)
a.cron.AddFunc(cronText, a.AddCronTask(follow))
}
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
notification := toast.Notification{

View File

@ -426,6 +426,7 @@ func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) {
stockCode = strings.Replace(stockCode, "GB_", "us", 1)
}
db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("cron", cron)
}
func (receiver StockDataApi) GetFollowList() *[]FollowedStock {
var result *[]FollowedStock
@ -475,6 +476,12 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
return result
}
func (receiver StockDataApi) GetFollowedStockByStockCode(code string) FollowedStock {
var result FollowedStock
db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(code)).First(&result)
return result
}
// GB18030ToUTF8 GB18030 转换为 UTF8
func GB18030ToUTF8(bs []byte) string {
reader := transform.NewReader(bytes.NewReader(bs), simplifiedchinese.GB18030.NewDecoder())

View File

@ -1,7 +1,9 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';
import {data} from '../models';
import {models} from '../models';
export function AddCronTask(arg1:data.FollowedStock):Promise<any>;
export function AddPrompt(arg1:models.Prompt):Promise<string>;

View File

@ -2,6 +2,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AddCronTask(arg1) {
return window['go']['main']['App']['AddCronTask'](arg1);
}
export function AddPrompt(arg1) {
return window['go']['main']['App']['AddPrompt'](arg1);
}

View File

@ -142,6 +142,61 @@ export namespace data {
return a;
}
}
export class FollowedStock {
StockCode: string;
Name: string;
Volume: number;
CostPrice: number;
Price: number;
PriceChange: number;
ChangePercent: number;
AlarmChangePercent: number;
AlarmPrice: number;
// Go type: time
Time: any;
Sort: number;
Cron: string;
IsDel: number;
static createFrom(source: any = {}) {
return new FollowedStock(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.StockCode = source["StockCode"];
this.Name = source["Name"];
this.Volume = source["Volume"];
this.CostPrice = source["CostPrice"];
this.Price = source["Price"];
this.PriceChange = source["PriceChange"];
this.ChangePercent = source["ChangePercent"];
this.AlarmChangePercent = source["AlarmChangePercent"];
this.AlarmPrice = source["AlarmPrice"];
this.Time = this.convertValues(source["Time"], null);
this.Sort = source["Sort"];
this.Cron = source["Cron"];
this.IsDel = source["IsDel"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class Settings {
ID: number;