Skip to content

Commit 2f2b19f

Browse files
author
spark
committed
feat(app): 添加钉钉消息发送功能和股票涨跌报警
- 新增 SendDingDingMessage 和 SetAlarmChangePercent 函数- 实现钉钉消息发送和股票涨跌报警逻辑 - 更新前端界面,增加报警值设置和消息发送功能 - 新增 DingDingAPI 结构体和相关方法
1 parent 685a7d2 commit 2f2b19f

File tree

10 files changed

+227
-18
lines changed

10 files changed

+227
-18
lines changed

app.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ package main
22

33
import (
44
"context"
5+
"github.com/coocood/freecache"
56
"github.com/wailsapp/wails/v2/pkg/runtime"
67
"go-stock/backend/data"
78
"go-stock/backend/logger"
89
)
910

1011
// App struct
1112
type App struct {
12-
ctx context.Context
13+
ctx context.Context
14+
cache *freecache.Cache
1315
}
1416

1517
// NewApp creates a new App application struct
1618
func NewApp() *App {
17-
return &App{}
19+
cacheSize := 512 * 1024
20+
cache := freecache.NewCache(cacheSize)
21+
return &App{
22+
cache: cache,
23+
}
1824
}
1925

2026
// startup is called at application startup
@@ -90,3 +96,21 @@ func (a *App) GetStockList(key string) []data.StockBasic {
9096
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
9197
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
9298
}
99+
100+
func (a *App) SetAlarmChangePercent(val float64, stockCode string) string {
101+
return data.NewStockDataApi().SetAlarmChangePercent(val, stockCode)
102+
}
103+
104+
func (a *App) SendDingDingMessage(message string, stockCode string) string {
105+
ttl, _ := a.cache.TTL([]byte(stockCode))
106+
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
107+
if ttl > 0 {
108+
return ""
109+
}
110+
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
111+
if err != nil {
112+
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
113+
return ""
114+
}
115+
return data.NewDingDingAPI().SendDingDingMessage(message)
116+
}

backend/data/dingding_api.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package data
2+
3+
import (
4+
"github.com/go-resty/resty/v2"
5+
"go-stock/backend/logger"
6+
)
7+
8+
// @Author spark
9+
// @Date 2025/1/3 13:53
10+
// @Desc
11+
//-----------------------------------------------------------------------------------
12+
13+
const dingding_robot_url = "https://oapi.dingtalk.com/robot/send?access_token=0237527b404598f37ae5d83ef36e936860c7ba5d3892cd43f64c4159d3ed7cb1"
14+
15+
type DingDingAPI struct {
16+
client *resty.Client
17+
}
18+
19+
func NewDingDingAPI() *DingDingAPI {
20+
return &DingDingAPI{
21+
client: resty.New(),
22+
}
23+
}
24+
25+
func (DingDingAPI) SendDingDingMessage(message string) string {
26+
// 发送钉钉消息
27+
resp, err := resty.New().R().
28+
SetHeader("Content-Type", "application/json").
29+
SetBody(message).
30+
Post(dingding_robot_url)
31+
if err != nil {
32+
logger.SugaredLogger.Error(err.Error())
33+
return "发送钉钉消息失败"
34+
}
35+
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
36+
return "发送钉钉消息成功"
37+
}
38+
39+
func (DingDingAPI) SendToDingDing(title, message string) string {
40+
// 发送钉钉消息
41+
resp, err := resty.New().R().
42+
SetHeader("Content-Type", "application/json").
43+
SetBody(&Message{
44+
Msgtype: "markdown",
45+
Markdown: Markdown{
46+
Title: "go-stock " + title,
47+
Text: message,
48+
},
49+
At: At{
50+
IsAtAll: true,
51+
},
52+
}).
53+
Post(dingding_robot_url)
54+
if err != nil {
55+
logger.SugaredLogger.Error(err.Error())
56+
return "发送钉钉消息失败"
57+
}
58+
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
59+
return "发送钉钉消息成功"
60+
}
61+
62+
type Message struct {
63+
Msgtype string `json:"msgtype"`
64+
Markdown Markdown `json:"markdown"`
65+
At At `json:"at"`
66+
}
67+
68+
type Markdown struct {
69+
Title string `json:"title"`
70+
Text string `json:"text"`
71+
}
72+
73+
type At struct {
74+
AtMobiles []string `json:"atMobiles"`
75+
AtUserIds []string `json:"atUserIds"`
76+
IsAtAll bool `json:"isAtAll"`
77+
}

backend/data/dingding_api_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package data
2+
3+
import (
4+
"github.com/go-resty/resty/v2"
5+
"testing"
6+
)
7+
8+
// @Author spark
9+
// @Date 2025/1/3 13:53
10+
// @Desc
11+
//-----------------------------------------------------------------------------------
12+
13+
func TestRobot(t *testing.T) {
14+
resp, err := resty.New().R().
15+
SetHeader("Content-Type", "application/json").
16+
SetBody(`{
17+
"msgtype": "markdown",
18+
"markdown": {
19+
"title":"go-stock",
20+
"text": "#### 杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n"
21+
},
22+
"at": {
23+
"isAtAll": true
24+
}
25+
}`).
26+
Post(dingding_robot_url)
27+
if err != nil {
28+
t.Error(err)
29+
}
30+
t.Log(resp.String())
31+
}

backend/data/stock_data_api.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,17 @@ type StockBasic struct {
127127
}
128128

129129
type FollowedStock struct {
130-
StockCode string
131-
Name string
132-
Volume int64
133-
CostPrice float64
134-
Price float64
135-
PriceChange float64
136-
ChangePercent float64
137-
Time time.Time
138-
Sort int64
139-
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
130+
StockCode string
131+
Name string
132+
Volume int64
133+
CostPrice float64
134+
Price float64
135+
PriceChange float64
136+
ChangePercent float64
137+
AlarmChangePercent float64
138+
Time time.Time
139+
Sort int64
140+
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
140141
}
141142

142143
func (receiver FollowedStock) TableName() string {
@@ -309,6 +310,15 @@ func (receiver StockDataApi) SetCostPriceAndVolume(price float64, volume int64,
309310
return "设置成功"
310311
}
311312

313+
func (receiver StockDataApi) SetAlarmChangePercent(val float64, stockCode string) string {
314+
err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", stockCode).Update("alarm_change_percent", val).Error
315+
if err != nil {
316+
logger.SugaredLogger.Error(err.Error())
317+
return "设置失败"
318+
}
319+
return "设置成功"
320+
}
321+
312322
func (receiver StockDataApi) GetFollowList() []FollowedStock {
313323
var result []FollowedStock
314324
db.Dao.Model(&FollowedStock{}).Order("sort asc,time desc").Find(&result)

frontend/src/components/stock.vue

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
<script setup>
22
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
3-
import {Greet, Follow, UnFollow, GetFollowList, GetStockList, SetCostPriceAndVolume} from '../../wailsjs/go/main/App'
3+
import {
4+
Greet,
5+
Follow,
6+
UnFollow,
7+
GetFollowList,
8+
GetStockList,
9+
SetCostPriceAndVolume,
10+
SendDingDingMessage, SetAlarmChangePercent
11+
} from '../../wailsjs/go/main/App'
412
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
513
import { WindowFullscreen,WindowUnfullscreen,EventsOn } from '../../wailsjs/runtime'
614
import {Add, StarOutline} from '@vicons/ionicons5'
@@ -22,7 +30,8 @@ const formModel = ref({
2230
name: "",
2331
code: "",
2432
costPrice: 0.000,
25-
volume: 0
33+
volume: 0,
34+
alarm: 0,
2635
})
2736
2837
const data = reactive({
@@ -171,7 +180,6 @@ async function monitor() {
171180
result.highRate=((result["今日最高价"]-result["今日开盘价"])*100/result["今日开盘价"]).toFixed(2)+"%"
172181
result.lowRate=((result["今日最低价"]-result["今日开盘价"])*100/result["今日开盘价"]).toFixed(2)+"%"
173182
174-
175183
if (roundedNum>0) {
176184
result.type="error"
177185
result.color="#E88080"
@@ -195,6 +203,9 @@ async function monitor() {
195203
}else if(result.profitAmount<0){
196204
result.profitType="success"
197205
}
206+
if(Math.abs(res[0].AlarmChangePercent)>0&&roundedNum>res[0].AlarmChangePercent){
207+
SendMessage(result)
208+
}
198209
}
199210
results.value[result["股票名称"]]=result
200211
})
@@ -224,6 +235,7 @@ function setStock(code,name){
224235
formModel.value.code=code
225236
formModel.value.volume=res[0].Volume
226237
formModel.value.costPrice=res[0].CostPrice
238+
formModel.value.alarm=res[0].AlarmChangePercent
227239
modalShow.value=true
228240
}
229241
@@ -241,8 +253,13 @@ function showK(code,name){
241253
}
242254
243255
244-
function updateCostPriceAndVolumeNew(code,price,volume){
256+
function updateCostPriceAndVolumeNew(code,price,volume,alarm){
245257
console.log(code,price,volume)
258+
if(alarm){
259+
SetAlarmChangePercent(alarm,code).then(result => {
260+
//message.success(result)
261+
})
262+
}
246263
SetCostPriceAndVolume(code,price,volume).then(result => {
247264
modalShow.value=false
248265
message.success(result)
@@ -267,6 +284,33 @@ function fullscreen(){
267284
}
268285
data.fullscreen=!data.fullscreen
269286
}
287+
288+
function SendMessage(result){
289+
let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now()
290+
let markdown="### go-stock市场行情\n\n"+
291+
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
292+
"- 当前价格: "+result["当前价格"]+" "+result.s+"\n" +
293+
"- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" +
294+
"- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" +
295+
"- 昨收价: "+result["昨日收盘价"]+"\n" +
296+
"- 今开价: "+result["今日开盘价"]+"\n" +
297+
"- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" +
298+
"- 成本数量: "+result.volume+"\n" +
299+
"- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+
300+
"![image]("+img+")\n"
301+
let msg='{' +
302+
' "msgtype": "markdown",' +
303+
' "markdown": {' +
304+
' "title":"'+result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.s+'",' +
305+
' "text": "'+markdown+'"' +
306+
' },' +
307+
' "at": {' +
308+
' "isAtAll": true' +
309+
' }' +
310+
' }'
311+
SendDingDingMessage(msg,result["股票代码"])
312+
}
313+
270314
</script>
271315

272316
<template>
@@ -309,7 +353,7 @@ function fullscreen(){
309353
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'])"> 分时 </n-button>
310354
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
311355
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
312-
356+
<!-- <n-button size="tiny" type="info" @click="SendMessage(result)"> 钉钉 </n-button>-->
313357
</n-flex>
314358
</template>
315359
</n-card >
@@ -342,9 +386,12 @@ function fullscreen(){
342386
<n-form-item label="数量(股)" path="volume">
343387
<n-input-number v-model:value="formModel.volume" min="0" placeholder="请输入股票数量" />
344388
</n-form-item>
389+
<n-form-item label="涨跌报警值(%)" path="alarm">
390+
<n-input-number v-model:value="formModel.alarm" min="0" placeholder="请输入涨跌报警值(%)" />
391+
</n-form-item>
345392
</n-form>
346393
<template #footer>
347-
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume)">保存</n-button>
394+
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm)">保存</n-button>
348395
</template>
349396
</n-modal>
350397

frontend/wailsjs/go/main/App.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
1010

1111
export function Greet(arg1:string):Promise<data.StockInfo>;
1212

13+
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
14+
15+
export function SetAlarmChangePercent(arg1:number,arg2:string):Promise<string>;
16+
1317
export function SetCostPriceAndVolume(arg1:string,arg2:number,arg3:number):Promise<string>;
1418

1519
export function UnFollow(arg1:string):Promise<string>;

frontend/wailsjs/go/main/App.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export function Greet(arg1) {
1818
return window['go']['main']['App']['Greet'](arg1);
1919
}
2020

21+
export function SendDingDingMessage(arg1, arg2) {
22+
return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2);
23+
}
24+
25+
export function SetAlarmChangePercent(arg1, arg2) {
26+
return window['go']['main']['App']['SetAlarmChangePercent'](arg1, arg2);
27+
}
28+
2129
export function SetCostPriceAndVolume(arg1, arg2, arg3) {
2230
return window['go']['main']['App']['SetCostPriceAndVolume'](arg1, arg2, arg3);
2331
}

frontend/wailsjs/go/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export namespace data {
88
Price: number;
99
PriceChange: number;
1010
ChangePercent: number;
11+
AlarmChangePercent: number;
1112
// Go type: time
1213
Time: any;
1314
Sort: number;
@@ -26,6 +27,7 @@ export namespace data {
2627
this.Price = source["Price"];
2728
this.PriceChange = source["PriceChange"];
2829
this.ChangePercent = source["ChangePercent"];
30+
this.AlarmChangePercent = source["AlarmChangePercent"];
2931
this.Time = this.convertValues(source["Time"], null);
3032
this.Sort = source["Sort"];
3133
this.IsDel = source["IsDel"];

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.21
55
toolchain go1.23.0
66

77
require (
8+
github.com/coocood/freecache v1.2.4
89
github.com/duke-git/lancet/v2 v2.3.4
910
github.com/glebarez/sqlite v1.11.0
1011
github.com/go-resty/resty/v2 v2.16.2
@@ -19,6 +20,7 @@ require (
1920

2021
require (
2122
github.com/bep/debounce v1.2.1 // indirect
23+
github.com/cespare/xxhash/v2 v2.1.2 // indirect
2224
github.com/dustin/go-humanize v1.0.1 // indirect
2325
github.com/glebarez/go-sqlite v1.21.2 // indirect
2426
github.com/go-ole/go-ole v1.2.6 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
22
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
3+
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
4+
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
6+
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
37
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
48
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
59
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

0 commit comments

Comments
 (0)