mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
357 lines
10 KiB
Go
357 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/duke-git/lancet/v2/slice"
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
|
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
"go-stock/backend/data"
|
|
"go-stock/backend/db"
|
|
log "go-stock/backend/logger"
|
|
"go-stock/backend/models"
|
|
"os"
|
|
goruntime "runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
//go:embed frontend/dist
|
|
var assets embed.FS
|
|
|
|
//go:embed build/appicon.png
|
|
var icon []byte
|
|
|
|
//go:embed build/app.ico
|
|
var icon2 []byte
|
|
|
|
//go:embed build/screenshot/alipay.jpg
|
|
var alipay []byte
|
|
|
|
//go:embed build/screenshot/wxpay.jpg
|
|
var wxpay []byte
|
|
|
|
//go:embed build/stock_basic.json
|
|
var stocksBin []byte
|
|
|
|
//go:embed build/stock_base_info_hk.json
|
|
var stocksBinHK []byte
|
|
|
|
//go:embed build/stock_base_info_us.json
|
|
var stocksBinUS []byte
|
|
|
|
//go:generate cp -R ./data ./build/bin
|
|
|
|
var Version string
|
|
var VersionCommit string
|
|
|
|
func main() {
|
|
checkDir("data")
|
|
db.Init("")
|
|
go AutoMigrate()
|
|
|
|
//db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{
|
|
// Name: "默认分组",
|
|
// Sort: 0,
|
|
//})
|
|
|
|
// Create an instance of the app structure
|
|
app := NewApp()
|
|
AppMenu := menu.NewMenu()
|
|
FileMenu := AppMenu.AddSubmenu("设置")
|
|
FileMenu.AddText("显示搜索框", keys.CmdOrCtrl("s"), func(callbackData *menu.CallbackData) {
|
|
runtime.EventsEmit(app.ctx, "showSearch", 1)
|
|
})
|
|
FileMenu.AddText("隐藏搜索框", keys.CmdOrCtrl("d"), func(callbackData *menu.CallbackData) {
|
|
runtime.EventsEmit(app.ctx, "showSearch", 0)
|
|
})
|
|
FileMenu.AddText("刷新数据", keys.CmdOrCtrl("r"), func(callbackData *menu.CallbackData) {
|
|
//runtime.EventsEmit(app.ctx, "refresh", "setting-"+time.Now().Format("2006-01-02 15:04:05"))
|
|
runtime.EventsEmit(app.ctx, "refreshFollowList", "refresh-"+time.Now().Format("2006-01-02 15:04:05"))
|
|
})
|
|
FileMenu.AddSeparator()
|
|
FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
|
|
runtime.WindowFullscreen(app.ctx)
|
|
})
|
|
FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
|
|
runtime.WindowUnfullscreen(app.ctx)
|
|
})
|
|
|
|
if goruntime.GOOS == "windows" {
|
|
FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
|
|
runtime.WindowHide(app.ctx)
|
|
})
|
|
|
|
FileMenu.AddText("显示", keys.CmdOrCtrl("v"), func(_ *menu.CallbackData) {
|
|
runtime.WindowShow(app.ctx)
|
|
})
|
|
}
|
|
|
|
//FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
|
// runtime.Quit(app.ctx)
|
|
//})
|
|
log.SugaredLogger.Info("version: " + Version)
|
|
log.SugaredLogger.Info("commit: " + VersionCommit)
|
|
// Create application with options
|
|
//var width, height int
|
|
//var err error
|
|
//
|
|
width, _, err := getScreenResolution()
|
|
if err != nil {
|
|
log.SugaredLogger.Error("get screen resolution error")
|
|
width = 1456
|
|
//height = 768
|
|
}
|
|
|
|
darkTheme := data.NewSettingsApi(&data.Settings{}).GetConfig().DarkTheme
|
|
backgroundColour := &options.RGBA{R: 255, G: 255, B: 255, A: 1}
|
|
if darkTheme {
|
|
backgroundColour = &options.RGBA{R: 27, G: 38, B: 54, A: 1}
|
|
}
|
|
|
|
// Create application with options
|
|
err = wails.Run(&options.App{
|
|
Title: "go-stock",
|
|
Width: width * 4 / 5,
|
|
Height: 900,
|
|
MinWidth: 1456,
|
|
MinHeight: 768,
|
|
//MaxWidth: width,
|
|
//MaxHeight: height,
|
|
DisableResize: false,
|
|
Fullscreen: false,
|
|
Frameless: true,
|
|
StartHidden: false,
|
|
HideWindowOnClose: false,
|
|
EnableDefaultContextMenu: true,
|
|
BackgroundColour: backgroundColour,
|
|
Assets: assets,
|
|
Menu: AppMenu,
|
|
Logger: nil,
|
|
LogLevel: logger.DEBUG,
|
|
LogLevelProduction: logger.ERROR,
|
|
OnStartup: app.startup,
|
|
OnDomReady: app.domReady,
|
|
OnBeforeClose: app.beforeClose,
|
|
OnShutdown: app.shutdown,
|
|
WindowStartState: options.Normal,
|
|
SingleInstanceLock: &options.SingleInstanceLock{
|
|
UniqueId: "go-stock",
|
|
OnSecondInstanceLaunch: OnSecondInstanceLaunch,
|
|
},
|
|
Bind: []interface{}{
|
|
app,
|
|
},
|
|
// Windows platform specific options
|
|
Windows: &windows.Options{
|
|
WebviewIsTransparent: false,
|
|
WindowIsTranslucent: false,
|
|
DisableWindowIcon: false,
|
|
// DisableFramelessWindowDecorations: false,
|
|
WebviewUserDataPath: "",
|
|
},
|
|
// Mac platform specific options
|
|
Mac: &mac.Options{
|
|
TitleBar: &mac.TitleBar{
|
|
TitlebarAppearsTransparent: true,
|
|
HideTitle: false,
|
|
HideTitleBar: false,
|
|
FullSizeContent: false,
|
|
UseToolbar: false,
|
|
HideToolbarSeparator: true,
|
|
},
|
|
Appearance: mac.NSAppearanceNameDarkAqua,
|
|
WebviewIsTransparent: true,
|
|
WindowIsTranslucent: true,
|
|
About: &mac.AboutInfo{
|
|
Title: "go-stock",
|
|
Message: "",
|
|
Icon: icon,
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
log.SugaredLogger.Fatal(err)
|
|
}
|
|
|
|
}
|
|
|
|
func AutoMigrate() {
|
|
db.Dao.AutoMigrate(&data.StockInfo{})
|
|
db.Dao.AutoMigrate(&data.StockBasic{})
|
|
db.Dao.AutoMigrate(&data.FollowedStock{})
|
|
db.Dao.AutoMigrate(&data.IndexBasic{})
|
|
db.Dao.AutoMigrate(&data.Settings{})
|
|
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
|
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
|
db.Dao.AutoMigrate(&models.StockInfoUS{})
|
|
db.Dao.AutoMigrate(&data.FollowedFund{})
|
|
db.Dao.AutoMigrate(&data.FundBasic{})
|
|
db.Dao.AutoMigrate(&models.PromptTemplate{})
|
|
db.Dao.AutoMigrate(&data.Group{})
|
|
db.Dao.AutoMigrate(&data.GroupStock{})
|
|
db.Dao.AutoMigrate(&models.Tags{})
|
|
db.Dao.AutoMigrate(&models.Telegraph{})
|
|
db.Dao.AutoMigrate(&models.TelegraphTags{})
|
|
db.Dao.AutoMigrate(&models.LongTigerRankData{})
|
|
}
|
|
|
|
func initStockDataUS(ctx context.Context) {
|
|
defer func() {
|
|
go runtime.EventsEmit(ctx, "loadingMsg", "done")
|
|
}()
|
|
var v []models.StockInfoUS
|
|
err := json.Unmarshal(stocksBinUS, &v)
|
|
if err != nil {
|
|
log.SugaredLogger.Error(err.Error())
|
|
return
|
|
}
|
|
log.SugaredLogger.Infof("init stock data us %d", len(v))
|
|
var total int64
|
|
db.Dao.Model(&models.StockInfoUS{}).Count(&total)
|
|
if total != int64(len(v)) {
|
|
for _, item := range v {
|
|
var count int64
|
|
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", item.Code).Count(&count)
|
|
if count > 0 {
|
|
//log.SugaredLogger.Infof("stock data us %s exist", item.Code)
|
|
continue
|
|
}
|
|
db.Dao.Model(&models.StockInfoUS{}).Create(&item)
|
|
}
|
|
}
|
|
}
|
|
|
|
func initStockDataHK(ctx context.Context) {
|
|
defer func() {
|
|
go runtime.EventsEmit(ctx, "loadingMsg", "done")
|
|
}()
|
|
var v []models.StockInfoHK
|
|
err := json.Unmarshal(stocksBinHK, &v)
|
|
if err != nil {
|
|
log.SugaredLogger.Error(err.Error())
|
|
return
|
|
}
|
|
log.SugaredLogger.Infof("init stock data hk %d", len(v))
|
|
var total int64
|
|
db.Dao.Model(&models.StockInfoHK{}).Count(&total)
|
|
if total != int64(len(v)) {
|
|
for _, item := range v {
|
|
var count int64
|
|
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", item.Code).Count(&count)
|
|
if count > 0 {
|
|
//log.SugaredLogger.Infof("stock data hk %s exist", item.Code)
|
|
continue
|
|
}
|
|
db.Dao.Model(&models.StockInfoHK{}).Create(&item)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func updateBasicInfo() {
|
|
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
|
if config.UpdateBasicInfoOnStart {
|
|
//更新基本信息
|
|
go data.NewStockDataApi().GetStockBaseInfo()
|
|
go data.NewStockDataApi().GetIndexBasic()
|
|
}
|
|
}
|
|
|
|
func initStockData(ctx context.Context) {
|
|
defer func() {
|
|
go runtime.EventsEmit(ctx, "loadingMsg", "done")
|
|
}()
|
|
fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs"
|
|
log.SugaredLogger.Info("init stock data")
|
|
res := &data.TushareStockBasicResponse{}
|
|
err := json.Unmarshal(stocksBin, res)
|
|
if err != nil {
|
|
log.SugaredLogger.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
for _, item := range res.Data.Items {
|
|
stock := &data.StockBasic{}
|
|
stockData := map[string]any{}
|
|
for _, field := range strings.Split(fields, ",") {
|
|
//logger.SugaredLogger.Infof("field: %s", field)
|
|
idx := slice.IndexOf(res.Data.Fields, field)
|
|
if idx == -1 {
|
|
continue
|
|
}
|
|
stockData[field] = item[idx]
|
|
}
|
|
jsonData, _ := json.Marshal(stockData)
|
|
err := json.Unmarshal(jsonData, stock)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
stock.ID = 0
|
|
var count int64
|
|
db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Count(&count)
|
|
if count > 0 {
|
|
continue
|
|
} else {
|
|
db.Dao.Create(stock)
|
|
}
|
|
|
|
//db.Dao.Model(&data.StockBasic{}).FirstOrCreate(stock, &data.StockBasic{TsCode: stock.TsCode}).Where("ts_code = ?", stock.TsCode).Updates(stock)
|
|
}
|
|
|
|
//for _, item := range res.Data.Items {
|
|
// stock := &data.StockBasic{}
|
|
// stock.Exchange = convertor.ToString(item[0])
|
|
// stock.IsHs = convertor.ToString(item[1])
|
|
// stock.Name = convertor.ToString(item[2])
|
|
// stock.Industry = convertor.ToString(item[3])
|
|
// stock.ListStatus = convertor.ToString(item[4])
|
|
// stock.ActName = convertor.ToString(item[5])
|
|
// stock.ID = uint(item[6].(float64))
|
|
// stock.CurrType = convertor.ToString(item[7])
|
|
// stock.Area = convertor.ToString(item[8])
|
|
// stock.ListDate = convertor.ToString(item[9])
|
|
// stock.DelistDate = convertor.ToString(item[10])
|
|
// stock.ActEntType = convertor.ToString(item[11])
|
|
// stock.TsCode = convertor.ToString(item[12])
|
|
// stock.Symbol = convertor.ToString(item[13])
|
|
// stock.Cnspell = convertor.ToString(item[14])
|
|
// stock.Fullname = convertor.ToString(item[20])
|
|
// stock.Ename = convertor.ToString(item[21])
|
|
//
|
|
// var count int64
|
|
// db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Count(&count)
|
|
// if count > 0 {
|
|
// continue
|
|
// } else {
|
|
// db.Dao.Create(stock)
|
|
// }
|
|
//}
|
|
}
|
|
|
|
func checkDir(dir string) {
|
|
_, err := os.Stat(dir)
|
|
if os.IsNotExist(err) {
|
|
os.Mkdir(dir, os.ModePerm)
|
|
log.SugaredLogger.Info("create dir: " + dir)
|
|
}
|
|
}
|
|
|
|
// PanicHandler 捕获 panic 的包装函数
|
|
func PanicHandler() {
|
|
if r := recover(); r != nil {
|
|
fmt.Printf("Recovered from panic: %v\n", r)
|
|
debug.PrintStack()
|
|
}
|
|
}
|