master
jager 3 years ago
parent b8d1391bde
commit 05a5c7c82f

@ -4,7 +4,7 @@ plats = linux darwin
arch ?= amd64 arch ?= amd64
archs = amd64 arm arm64 archs = amd64 arm arm64
all: stock ss all: stock
define build_app define build_app
@echo 'building $(1) ...' @echo 'building $(1) ...'
@ -17,10 +17,6 @@ stock:
$(call build_app,stock,$(plat),$(arch)) $(call build_app,stock,$(plat),$(arch))
.PHONY: stock .PHONY: stock
ss:
$(call build_app,ss,$(plat),$(arch))
.PHONY: ss
clean: clean:
-@rm -f builder/* -@rm -f builder/*

@ -1,84 +0,0 @@
/**
* @Author: jager
* @Email: lhj168os@gmail.com
* @File: main
* @Date: 2021/12/20 2:24
* @package: ss
* @Version: v1.0.0
*
* @Description:
*
*/
package main
import (
"github.com/gin-gonic/gin"
"github.com/jageros/hawox/contextx"
"github.com/jageros/hawox/httpx"
"github.com/jageros/hawox/logx"
"net/http"
"stock/msg"
"time"
)
func main() {
ctx, cancel := contextx.Default()
defer cancel()
logx.Init(logx.DebugLevel, logx.SetCaller(), logx.SetRequest())
defer logx.Sync()
httpx.InitializeHttpServer(ctx, func(engine *gin.Engine) {
r := engine.Group("/api")
r.GET("/sayhello", func(c *gin.Context) {
httpx.PkgMsgWrite(c, map[string]interface{}{"say": "hello world!"})
})
r.POST("/receive", func(c *gin.Context) {
rMsg := &msg.RData{}
err := c.BindXML(rMsg)
if err != nil {
logx.Error(err)
} else {
logx.Info(rMsg)
}
wMsg := &xml{
ToUserName: rMsg.FromUserName,
FromUserName: rMsg.ToUserName,
CreateTime: int(time.Now().Unix()),
MsgType: "text",
Content: "收到,谢谢!",
}
c.XML(http.StatusOK, wMsg)
//c.XML(http.StatusOK, wMsg)
//err = msg.Rcv(rMsg.FromUserName)
//if err != nil {
// logx.Error(err)
//}
//c.String(http.StatusOK, "success")
})
r.GET("/receive", func(c *gin.Context) {
echostr := c.Query("echostr")
logx.Infof("=== %s ===", echostr)
c.String(http.StatusOK, echostr)
})
}, func(s *httpx.Server) {
s.Mode = "debug"
s.Port = 8567
})
err := ctx.Wait()
logx.Infof("Stop With: %v", err)
}
type xml struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
}

@ -46,8 +46,14 @@ func main() {
logx.Fatal(err) logx.Fatal(err)
} }
timer.AddTicker(time.Second*1200, func() {
logx.Info("开始保存用户数据进MongoDB...")
user.SaveAll()
logx.Info("保存用户数进MongoDB结束")
})
stockCodes := user.Codes(false) stockCodes := user.Codes(false)
fundCodes := user.Codes(true)
timer.RunEveryDay(9, 25, 0, func() { timer.RunEveryDay(9, 25, 0, func() {
if !stock.IsSellDay(time.Now()) { if !stock.IsSellDay(time.Now()) {
@ -80,12 +86,17 @@ func main() {
logx.Info("今天不是交易日!") logx.Info("今天不是交易日!")
return return
} }
text := fund.FundsMsg(fundCodes...)
if text == "" { fund.Clear()
logx.Errorf("收集基金数据为空!") user.ForEachUser(func(u module.IUser) bool {
return codes := u.Codes(true)
stk := fund.NewFundArg(codes...)
err = wxgzh.Send(u.OpenID(), stk)
if err != nil {
logx.Error(err)
} }
err := msg.Send(text) return true
})
if err != nil { if err != nil {
logx.Errorf("dingtalk.SendMsg(基金信息) err: %v", err) logx.Errorf("dingtalk.SendMsg(基金信息) err: %v", err)
} else { } else {
@ -93,9 +104,11 @@ func main() {
} }
}) })
if len(stockCodes) > 0 {
err = stock.Init(stockCodes...) err = stock.Init(stockCodes...)
if err != nil { if err != nil {
logx.Fatal(err) logx.Error(err)
}
} }
ctx.Go(func(ctx contextx.Context) error { ctx.Go(func(ctx contextx.Context) error {
@ -152,20 +165,6 @@ func main() {
} }
return true return true
}) })
//text := ss.Msg()
//if text == "" {
// logx.Info("已更新数据,未超过阈值,无警告!")
// continue
//}
//err = msg.Send(text)
//if err != nil {
// count++
// logx.Errorf("SendMsg ErrCount=%d err=%v", count, err)
// if count > 10 {
// return err
// }
//}
} }
} }
}) })
@ -185,7 +184,13 @@ func main() {
}) })
}, func(s *httpx.Server) { }, func(s *httpx.Server) {
s.Mode = "debug" s.Mode = "debug"
s.Port = 8567 s.Port = 16888
})
ctx.Go(func(ctx contextx.Context) error {
<-ctx.Done()
user.SaveAll()
return nil
}) })
err = ctx.Wait() // 等待停止信号 err = ctx.Wait() // 等待停止信号

@ -20,6 +20,7 @@ import (
"github.com/jageros/hawox/logx" "github.com/jageros/hawox/logx"
"regexp" "regexp"
"strconv" "strconv"
"sync"
) )
const ( const (
@ -28,17 +29,45 @@ const (
var ( var (
jsonStr = regexp.MustCompile(`{(.*?)}`) jsonStr = regexp.MustCompile(`{(.*?)}`)
fds = &funds{fdsMap: map[string]*fund{}}
) )
type fund struct { type fund struct {
Code string `json:"fundcode"` Code string `json:"fundcode"`
Name string `json:"name"` FName string `json:"name"`
UnitVal string `json:"dwjz"` UnitVal string `json:"dwjz"`
EstimateVal string `json:"gsz"` EstimateVal string `json:"gsz"`
RisePer string `json:"gszzl"` RisePer string `json:"gszzl"`
UpdateTime string `json:"gztime"` UpdateTime string `json:"gztime"`
} }
func (f *fund) Name() string {
return f.FName
}
func (f *fund) Update() error {
url := fmt.Sprintf(baseUrl, f.Code)
result, err := httpc.Request(httpc.GET, url, httpc.FORM, nil, nil)
if err != nil {
return err
}
ss := jsonStr.FindStringSubmatch(string(result))
if len(ss) <= 0 {
return errcode.New(404, "找不到基金:"+f.Code)
}
ff := &fund{}
err = json.Unmarshal([]byte(ss[0]), ff)
if err == nil {
f.FName = ff.FName
f.UnitVal = ff.UnitVal
f.EstimateVal = ff.EstimateVal
f.RisePer = ff.RisePer
f.UpdateTime = ff.UpdateTime
}
return err
}
func (f *fund) Msg() string { func (f *fund) Msg() string {
var rise string var rise string
last, err1 := strconv.ParseFloat(f.UnitVal, 64) last, err1 := strconv.ParseFloat(f.UnitVal, 64)
@ -46,17 +75,35 @@ func (f *fund) Msg() string {
if err1 == nil && err2 == nil { if err1 == nil && err2 == nil {
rise = fmt.Sprintf("%.5f", cur-last) rise = fmt.Sprintf("%.5f", cur-last)
} }
msg := fmt.Sprintf(msgTemplate, f.Name, f.UpdateTime, f.UnitVal, f.EstimateVal, rise, f.RisePer) msg := fmt.Sprintf(msgTemplate, f.FName, f.UpdateTime, f.UnitVal, f.EstimateVal, rise, f.RisePer)
return msg return msg
} }
type funds struct { type funds struct {
codes []string fdsMap map[string]*fund
mx sync.RWMutex
} }
func NewFunds(codes ...string) *funds { func (fs *funds) getFund(code string) *fund {
return &funds{ fs.mx.RLock()
codes: codes, defer fs.mx.RUnlock()
if fd, ok := fs.fdsMap[code]; ok {
return fd
}
return nil
}
func (fs *funds) addFund(fd *fund) {
fs.mx.Lock()
defer fs.mx.Unlock()
fs.fdsMap[fd.Code] = fd
}
func Clear() {
fds.mx.Lock()
defer fds.mx.Unlock()
fds = &funds{
fdsMap: map[string]*fund{},
} }
} }
@ -67,17 +114,23 @@ func FundsMsg(codes ...string) string {
//var msg = "基金定投估值 >>>\n" //var msg = "基金定投估值 >>>\n"
msg := "" msg := ""
for _, code := range codes { for _, code := range codes {
fd, err := newFund(code) fd := fds.getFund(code)
if err != nil { if fd == nil {
fd_, err := NewFund(code)
if err == nil {
fd = fd_
fds.addFund(fd_)
} else {
logx.Error(err) logx.Error(err)
continue continue
} }
}
msg += fd.Msg() msg += fd.Msg()
} }
return msg return msg
} }
func newFund(code string) (*fund, error) { func NewFund(code string) (*fund, error) {
url := fmt.Sprintf(baseUrl, code) url := fmt.Sprintf(baseUrl, code)
result, err := httpc.Request(httpc.GET, url, httpc.FORM, nil, nil) result, err := httpc.Request(httpc.GET, url, httpc.FORM, nil, nil)
if err != nil { if err != nil {
@ -99,13 +152,27 @@ const msgTemplate = `%s
` `
func (fs *funds) Arg(openid string) map[string]interface{} { type fundArg struct {
codes []string
}
func NewFundArg(codes ...string) *fundArg {
return &fundArg{
codes: codes,
}
}
func (fa *fundArg) Arg(openid string) map[string]interface{} {
msg := FundsMsg(fa.codes...)
if msg == "" {
return nil
}
arg := map[string]interface{}{ arg := map[string]interface{}{
"touser": openid, "touser": openid,
"template_id": "SQXWp3RiYySb2GYG-vMYDsSIm-KZNM9szVpFOryUQGQ", "template_id": "SQXWp3RiYySb2GYG-vMYDsSIm-KZNM9szVpFOryUQGQ",
"data": map[string]interface{}{ "data": map[string]interface{}{
"keyword": map[string]interface{}{ "keyword": map[string]interface{}{
"value": FundsMsg(fs.codes...), "value": msg,
"color": "#173177", "color": "#173177",
}, },
}, },

@ -14,7 +14,7 @@ import (
var ( var (
baseUrl = "https://hq.sinajs.cn/" baseUrl = "https://hq.sinajs.cn/"
fds *stocks fds = &stocks{stkMap: map[string]*stock{}}
mx sync.RWMutex mx sync.RWMutex
) )
@ -29,6 +29,7 @@ type stock struct {
values []string values []string
lastRise float64 lastRise float64
nowRise float64 nowRise float64
nowRiseStr string
} }
func (s *stock) Code() string { func (s *stock) Code() string {
@ -55,6 +56,7 @@ func (s *stock) notify() bool {
rs := (nowPrice - lastPrice) / lastPrice * 100 rs := (nowPrice - lastPrice) / lastPrice * 100
s.nowRise = rs s.nowRise = rs
s.nowRiseStr = fmt.Sprintf("%.2f%%", s.nowRise)
if (s.lastRise == 0 && rs != 0) || rs-s.lastRise >= cfg.ThresholdValue || s.lastRise-rs >= cfg.ThresholdValue { if (s.lastRise == 0 && rs != 0) || rs-s.lastRise >= cfg.ThresholdValue || s.lastRise-rs >= cfg.ThresholdValue {
s.lastRise = rs s.lastRise = rs
return true return true
@ -63,7 +65,21 @@ func (s *stock) notify() bool {
} }
func (s *stock) rise() string { func (s *stock) rise() string {
return fmt.Sprintf("%.2f%%", s.nowRise) if s.nowRiseStr != "" {
return s.nowRiseStr
}
lastPrice, err := strconv.ParseFloat(s.values[2], 64)
if err != nil || lastPrice == 0 {
return ""
}
nowPrice, err := strconv.ParseFloat(s.values[3], 64)
if err != nil {
return ""
}
rs := (nowPrice - lastPrice) / lastPrice * 100
s.nowRiseStr = fmt.Sprintf("%.2f%%", rs)
return s.nowRiseStr
} }
func (s *stock) buyCount() string { func (s *stock) buyCount() string {
@ -144,18 +160,18 @@ func (s *stock) Msg() string {
// ========================== // ==========================
func Init(codes ...string) error { func Init(codes ...string) error {
fds = &stocks{} fds = &stocks{stkMap: map[string]*stock{}}
return fds.AddCodes(codes...) return fds.AddCodes(codes...)
} }
type stocks struct { type stocks struct {
//codes []string
//ss []*stock
stkMap map[string]*stock stkMap map[string]*stock
mx sync.RWMutex mx sync.RWMutex
} }
func (sk *stocks) getStocks(codes ...string) *stocks { func (sk *stocks) getStocks(codes ...string) *stocks {
mx.RLock()
defer mx.RUnlock()
var stks = &stocks{} var stks = &stocks{}
for _, code := range codes { for _, code := range codes {
stks.stkMap[code] = sk.stkMap[code] stks.stkMap[code] = sk.stkMap[code]
@ -181,6 +197,9 @@ func (sk *stocks) AddCodes(codes ...string) error {
} }
cds = append(cds, code) cds = append(cds, code)
} }
if len(cds) <= 0 {
return nil
}
stks, err := newStocks(cds...) stks, err := newStocks(cds...)
if err != nil { if err != nil {
return err return err
@ -227,6 +246,17 @@ func (sk *stocks) Update() error {
} }
func (sk *stocks) Msg() string { func (sk *stocks) Msg() string {
sk.mx.RLock()
defer sk.mx.RUnlock()
var resp string
for _, s := range sk.stkMap {
msg := s.Msg()
resp = resp + msg + "\n"
}
return resp
}
func (sk *stocks) msg() string {
sk.mx.RLock() sk.mx.RLock()
defer sk.mx.RUnlock() defer sk.mx.RUnlock()
var resp string var resp string
@ -240,12 +270,16 @@ func (sk *stocks) Msg() string {
} }
func (sk *stocks) Arg(openid string) map[string]interface{} { func (sk *stocks) Arg(openid string) map[string]interface{} {
msg := sk.msg()
if msg == "" {
return nil
}
arg := map[string]interface{}{ arg := map[string]interface{}{
"touser": openid, "touser": openid,
"template_id": "yWuLbhAy7TTuqdeB9-VS6CR_t2rZQ8MHkJ62MF3VlS8", "template_id": "yWuLbhAy7TTuqdeB9-VS6CR_t2rZQ8MHkJ62MF3VlS8",
"data": map[string]interface{}{ "data": map[string]interface{}{
"keyword": map[string]interface{}{ "keyword": map[string]interface{}{
"value": sk.Msg(), "value": msg,
"color": "#173177", "color": "#173177",
}, },
}, },
@ -253,15 +287,28 @@ func (sk *stocks) Arg(openid string) map[string]interface{} {
return arg return arg
} }
func GetStock(code string) (*stock, error) {
if fds == nil {
fds = &stocks{stkMap: map[string]*stock{}}
}
err := fds.AddCodes(code)
if err != nil {
return nil, err
}
mx.RLock()
defer mx.RUnlock()
if fd, ok := fds.stkMap[code]; ok {
return fd, nil
}
return nil, errcode.New(1, "没有此股票数据")
}
func GetStocks(codes ...string) (*stocks, error) { func GetStocks(codes ...string) (*stocks, error) {
if len(codes) <= 0 { if len(codes) <= 0 {
return nil, errcode.New(1, "股票代码为空") return nil, errcode.New(1, "股票代码为空")
} }
mx.Lock()
defer mx.Unlock()
if fds == nil { if fds == nil {
fds = &stocks{} fds = &stocks{stkMap: map[string]*stock{}}
} }
err := fds.AddCodes(codes...) err := fds.AddCodes(codes...)
if err != nil { if err != nil {
@ -291,8 +338,13 @@ func newStocks(codes ...string) ([]*stock, error) {
for _, s := range strs { for _, s := range strs {
ss := strings.Split(s, "\"") ss := strings.Split(s, "\"")
if len(ss) >= 2 { if len(ss) >= 2 {
vs := strings.Split(ss[1], ",")
if len(vs) < 32 {
continue
}
v := &stock{ v := &stock{
values: strings.Split(ss[1], ","), code: ss[0][13:19],
values: vs,
} }
stks = append(stks, v) stks = append(stks, v)
} }

@ -14,6 +14,7 @@ package user
import ( import (
"github.com/jageros/hawox/attribute" "github.com/jageros/hawox/attribute"
"github.com/jageros/hawox/logx"
"stock/module" "stock/module"
"sync" "sync"
) )
@ -37,6 +38,17 @@ func LoadAllUserIntoCache() error {
return nil return nil
} }
func SaveAll() {
mx.Lock()
defer mx.Unlock()
for _, u := range users {
err := u.attr.Save(true)
if err != nil {
logx.Error(err)
}
}
}
func GetUser(openId string) (*User, error) { func GetUser(openId string) (*User, error) {
mx.RLock() mx.RLock()
u, ok := users[openId] u, ok := users[openId]

@ -19,7 +19,9 @@ import (
"github.com/jageros/hawox/httpc" "github.com/jageros/hawox/httpc"
"github.com/jageros/hawox/logx" "github.com/jageros/hawox/logx"
"net/http" "net/http"
"stock/fund"
"stock/module" "stock/module"
"stock/stock"
"stock/user" "stock/user"
"strings" "strings"
"time" "time"
@ -52,6 +54,11 @@ type IArg interface {
Arg(openid string) map[string]interface{} Arg(openid string) map[string]interface{}
} }
type IMsg interface {
Msg() string
Name() string
}
func getAccessToken(update bool) (string, error) { func getAccessToken(update bool) (string, error) {
if !update && time.Now().Unix() < expiresIn { if !update && time.Now().Unix() < expiresIn {
return accessToken, nil return accessToken, nil
@ -77,6 +84,9 @@ func send(openID string, arg IArg, recall bool) error {
url := fmt.Sprintf(sendUrl, token) url := fmt.Sprintf(sendUrl, token)
msg := arg.Arg(openID) msg := arg.Arg(openID)
if msg == nil {
return errcode.New(1, "arg == nil")
}
resp := &Resp{} resp := &Resp{}
err = httpc.RequestWithInterface(httpc.POST, url, httpc.JSON, msg, nil, resp) err = httpc.RequestWithInterface(httpc.POST, url, httpc.JSON, msg, nil, resp)
if err != nil { if err != nil {
@ -146,7 +156,8 @@ func Handle(c *gin.Context) {
FromUserName: rMsg.ToUserName, FromUserName: rMsg.ToUserName,
MsgType: "text", MsgType: "text",
CreateTime: time.Now().Unix(), CreateTime: time.Now().Unix(),
Content: "订阅股票:+st股票代码例如+st600905\n订阅基金+fd基金代码例如+fd161725\n" + Content: "查询股票:=st股票代码例如=st600905\n查询基金=fd基金代码例如=fd161725\n\n" +
"订阅股票:+st股票代码例如+st600905\n订阅基金+fd基金代码例如+fd161725\n\n" +
"取消订阅股票:-st股票代码例如-st600905\n取消订阅基金-fd基金代码例如-fd161725", "取消订阅股票:-st股票代码例如-st600905\n取消订阅基金-fd基金代码例如-fd161725",
} }
@ -159,12 +170,55 @@ func Handle(c *gin.Context) {
} }
switch { switch {
case len(rMsg.Content) < 9:
break
case strings.HasPrefix(rMsg.Content, "="):
code := rMsg.Content[3:]
isFund := rMsg.Content[1:3] == "fd"
var im IMsg
if isFund {
im, err = fund.NewFund(code)
} else {
im, err = stock.GetStock(code)
}
if err != nil {
wMsg.Content = "查询出错:\n" + err.Error()
} else {
wMsg.Content = "查询成功:\n" + im.Msg()
}
case strings.HasPrefix(rMsg.Content, "+"): case strings.HasPrefix(rMsg.Content, "+"):
u.Subscribe(rMsg.Content[1:3] == "fd", rMsg.Content[3:]) code := rMsg.Content[3:]
wMsg.Content = "订阅成功!" isFund := rMsg.Content[1:3] == "fd"
var im IMsg
if isFund {
im, err = fund.NewFund(code)
} else {
im, err = stock.GetStock(code)
}
if err != nil {
wMsg.Content = "订阅出错:\n" + err.Error()
} else {
u.Subscribe(isFund, code)
wMsg.Content = "订阅成功:\n" + im.Msg()
}
case strings.HasPrefix(rMsg.Content, "-"): case strings.HasPrefix(rMsg.Content, "-"):
u.UnSubscribe(rMsg.Content[1:3] == "fd", rMsg.Content[3:]) code := rMsg.Content[3:]
wMsg.Content = "成功取消订阅!" isFund := rMsg.Content[1:3] == "fd"
var im IMsg
if isFund {
im, err = fund.NewFund(code)
} else {
im, err = stock.GetStock(code)
}
if err != nil {
wMsg.Content = "取消订阅:\n" + err.Error()
} else {
wMsg.Content = "成功取消订阅:" + im.Name()
}
u.UnSubscribe(isFund, code)
} }
} }

Loading…
Cancel
Save