master
jager 3 years ago
parent 0df3603ef2
commit b8d1391bde

@ -24,9 +24,11 @@ var (
QywxRobotUrl = "" QywxRobotUrl = ""
DingTalkSecret = "" DingTalkSecret = ""
DingTalkRobotUrl = "" DingTalkRobotUrl = ""
WxgzhAppid = ""
WxgzhSecret = ""
TianApiKey = "" TianApiKey = ""
UpdateDuration = time.Second * 60 // 默认一分钟 UpdateDuration = time.Second * 60 // 默认一分钟
ThresholdValue float64 = 1 // 默认1% ThresholdValue float64 = 1 // 默认1%
StockCodes = []string{"600905", "600032", "002531", "600733", "000825", "002939"} // 三峡能源, 浙江新能, 天顺风能, 北汽蓝谷, 太钢不锈, 长城证券 StockCodes = []string{"600905", "600032", "002531", "600733", "000825", "002939"} // 三峡能源, 浙江新能, 天顺风能, 北汽蓝谷, 太钢不锈, 长城证券
FundCodes = []string{"006229", "162412", "008359", "161725", "012314", "162719", "501057"} FundCodes = []string{"006229", "162412", "008359", "161725", "012314", "162719", "501057"}
) )
@ -35,6 +37,8 @@ func load(v *viper.Viper) {
QywxRobotUrl = v.GetString("qywxRobotUrl") QywxRobotUrl = v.GetString("qywxRobotUrl")
DingTalkSecret = v.GetString("dingTalkSecret") DingTalkSecret = v.GetString("dingTalkSecret")
DingTalkRobotUrl = v.GetString("dingTalkRobotUrl") DingTalkRobotUrl = v.GetString("dingTalkRobotUrl")
WxgzhAppid = v.GetString("wxgzhAppid")
WxgzhSecret = v.GetString("wxgzhSecret")
TianApiKey = v.GetString("tianApiKey") TianApiKey = v.GetString("tianApiKey")
updates := v.GetInt("updateSecond") updates := v.GetInt("updateSecond")
if updates > 0 { if updates > 0 {

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jageros/hawox/attribute"
"github.com/jageros/hawox/contextx" "github.com/jageros/hawox/contextx"
"github.com/jageros/hawox/httpx" "github.com/jageros/hawox/httpx"
"github.com/jageros/hawox/logx" "github.com/jageros/hawox/logx"
@ -10,8 +11,11 @@ import (
"net/http" "net/http"
"stock/cfg" "stock/cfg"
"stock/fund" "stock/fund"
"stock/module"
"stock/msg" "stock/msg"
"stock/stock" "stock/stock"
"stock/user"
"stock/wxgzh"
"time" "time"
) )
@ -32,6 +36,19 @@ func main() {
timer.Initialize(ctx) // 定时器初始化 timer.Initialize(ctx) // 定时器初始化
// 初始化MongoDB
attribute.Initialize(ctx, func(opt *attribute.Option) {
opt.DBName = "stock-fund"
})
err = user.LoadAllUserIntoCache()
if err != nil {
logx.Fatal(err)
}
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()) {
logx.Info("今天不是交易日!") logx.Info("今天不是交易日!")
@ -63,7 +80,7 @@ func main() {
logx.Info("今天不是交易日!") logx.Info("今天不是交易日!")
return return
} }
text := fund.FundsMsg(cfg.FundCodes...) text := fund.FundsMsg(fundCodes...)
if text == "" { if text == "" {
logx.Errorf("收集基金数据为空!") logx.Errorf("收集基金数据为空!")
return return
@ -76,11 +93,12 @@ func main() {
} }
}) })
err = stock.Init(stockCodes...)
if err != nil {
logx.Fatal(err)
}
ctx.Go(func(ctx contextx.Context) error { ctx.Go(func(ctx contextx.Context) error {
ss, err := stock.NewStocks(cfg.StockCodes...)
if err != nil {
return err
}
var count int var count int
var opened, closed bool var opened, closed bool
@ -95,7 +113,7 @@ func main() {
if !closed { if !closed {
if stock.HasClose(time.Now()) { if stock.HasClose(time.Now()) {
logx.Info("--- 已闭市 ---") logx.Info("--- 已闭市 ---")
ss.Clear() stock.Clear()
} else { } else {
logx.Info("--- 未开市 ---") logx.Info("--- 未开市 ---")
} }
@ -111,7 +129,7 @@ func main() {
logx.Infof("--- 交易中 ---") logx.Infof("--- 交易中 ---")
} }
err = ss.Update() err = stock.Update()
if err != nil { if err != nil {
count++ count++
logx.Errorf("Update ErrCount=%d err=%v", count, err) logx.Errorf("Update ErrCount=%d err=%v", count, err)
@ -121,19 +139,33 @@ func main() {
continue continue
} }
text := ss.Msg() user.ForEachUser(func(u module.IUser) bool {
if text == "" { codes := u.Codes(false)
logx.Info("已更新数据,未超过阈值,无警告!") stk, err := stock.GetStocks(codes...)
continue if err != nil {
} logx.Error(err)
err = msg.Send(text) } else {
if err != nil { err = wxgzh.Send(u.OpenID(), stk)
count++ if err != nil {
logx.Errorf("SendMsg ErrCount=%d err=%v", count, err) logx.Error(err)
if count > 10 { }
return err
} }
} 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
// }
//}
} }
} }
}) })
@ -143,21 +175,12 @@ func main() {
r.GET("/sayhello", func(c *gin.Context) { r.GET("/sayhello", func(c *gin.Context) {
httpx.PkgMsgWrite(c, map[string]interface{}{"say": "hello world!"}) httpx.PkgMsgWrite(c, map[string]interface{}{"say": "hello world!"})
}) })
r.POST("/receive", func(c *gin.Context) {
msgs := &msg.RData{}
err := c.BindXML(msgs)
if err != nil {
logx.Error(err)
} else {
logx.Info(msgs)
}
c.String(http.StatusOK, "success") r.POST("/receive", wxgzh.Handle)
})
r.GET("/receive", func(c *gin.Context) { r.GET("/receive", func(c *gin.Context) {
echostr := c.Query("echostr") echostr := c.Query("echostr")
logx.Infof("=== %s ===", echostr) logx.Infof("配置接口 %s", echostr)
c.String(http.StatusOK, echostr) c.String(http.StatusOK, echostr)
}) })
}, func(s *httpx.Server) { }, func(s *httpx.Server) {

@ -3,10 +3,14 @@ qywxRobotUrl: ""
# 钉钉群机器人的secret # 钉钉群机器人的secret
dingTalkSecret: "" dingTalkSecret: ""
# 钉钉群机器人发消息的链接
# 钉钉群机器人发消息额链接
dingTalkRobotUrl: "" dingTalkRobotUrl: ""
# 微信公众号appid
wxgzhAppid: ""
# 微信公众号secret
wxgzhSecret: ""
# 天行数据获取节假日的api的key https://www.tianapi.com/apiview/139 # 天行数据获取节假日的api的key https://www.tianapi.com/apiview/139
tianApiKey: "" tianApiKey: ""

@ -27,7 +27,7 @@ const (
) )
var ( var (
jsonStr = regexp.MustCompile(`{(.*?)}`) jsonStr = regexp.MustCompile(`{(.*?)}`)
) )
type fund struct { type fund struct {
@ -50,11 +50,22 @@ func (f *fund) Msg() string {
return msg return msg
} }
type funds struct {
codes []string
}
func NewFunds(codes ...string) *funds {
return &funds{
codes: codes,
}
}
func FundsMsg(codes ...string) string { func FundsMsg(codes ...string) string {
if len(codes) <= 0 { if len(codes) <= 0 {
return "" return ""
} }
var msg = "基金定投估值 >>>\n" //var msg = "基金定投估值 >>>\n"
msg := ""
for _, code := range codes { for _, code := range codes {
fd, err := newFund(code) fd, err := newFund(code)
if err != nil { if err != nil {
@ -87,3 +98,17 @@ const msgTemplate = `%s
(%s) %s%% (%s) %s%%
` `
func (fs *funds) Arg(openid string) map[string]interface{} {
arg := map[string]interface{}{
"touser": openid,
"template_id": "SQXWp3RiYySb2GYG-vMYDsSIm-KZNM9szVpFOryUQGQ",
"data": map[string]interface{}{
"keyword": map[string]interface{}{
"value": FundsMsg(fs.codes...),
"color": "#173177",
},
},
}
return arg
}

@ -13,11 +13,14 @@
package fund package fund
import ( import (
"fmt" "stock/wxgzh"
"testing" "testing"
) )
func Test_Fund(t *testing.T) { func Test_Fund(t *testing.T) {
msg := FundsMsg() f := NewFunds("006229", "162412")
fmt.Println(msg) err := wxgzh.Send("o-KDV6NbRaanYz55fJuSgyR0qxxU", f)
if err != nil {
t.Error(err)
}
} }

@ -35,6 +35,7 @@ require (
github.com/subosito/gotenv v1.2.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect
github.com/tal-tech/go-zero v1.2.1 // indirect github.com/tal-tech/go-zero v1.2.1 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect github.com/ugorji/go/codec v1.1.7 // indirect
github.com/xiaonanln/go-xnsyncutil v0.0.5 // indirect
go.opentelemetry.io/otel v1.0.0-RC2 // indirect go.opentelemetry.io/otel v1.0.0-RC2 // indirect
go.opentelemetry.io/otel/trace v1.0.0-RC2 // indirect go.opentelemetry.io/otel/trace v1.0.0-RC2 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
@ -47,7 +48,9 @@ require (
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/eapache/queue.v1 v1.1.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

@ -422,6 +422,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiaonanln/go-xnsyncutil v0.0.5 h1:1kan2cg95e0quhKEBafu1lNG3UVI44BF0ThlJGa+lJQ=
github.com/xiaonanln/go-xnsyncutil v0.0.5/go.mod h1:PbwFumxH1s5Zc5mPk3A9GFaS/FdIP5WHobaRwQLS8xY=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -821,6 +823,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc=
gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
@ -829,6 +833,8 @@ gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdOD
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

@ -0,0 +1,21 @@
/**
* @Author: jager
* @Email: lhj168os@gmail.com
* @File: user
* @Date: 2021/12/21 3:17
* @package: module
* @Version: v1.0.0
*
* @Description:
*
*/
package module
type IUser interface {
OpenID() string
Codes(isFund bool) []string
HasSubscribed(isFund bool, code string) bool
Subscribe(isFund bool, codes ...string)
UnSubscribe(isFund bool, codes ...string)
}

@ -9,10 +9,13 @@ import (
"stock/cfg" "stock/cfg"
"strconv" "strconv"
"strings" "strings"
"sync"
) )
var ( var (
baseUrl = "https://hq.sinajs.cn/" baseUrl = "https://hq.sinajs.cn/"
fds *stocks
mx sync.RWMutex
) )
type IStock interface { type IStock interface {
@ -28,6 +31,14 @@ type stock struct {
nowRise float64 nowRise float64
} }
func (s *stock) Code() string {
return s.code
}
func (s *stock) Name() string {
return s.values[0]
}
func (s *stock) update(values []string) { func (s *stock) update(values []string) {
s.values = values s.values = values
} }
@ -130,41 +141,84 @@ func (s *stock) Msg() string {
return msg return msg
} }
// ==========================
func Init(codes ...string) error {
fds = &stocks{}
return fds.AddCodes(codes...)
}
type stocks struct { type stocks struct {
codes []string //codes []string
ss []*stock //ss []*stock
stkMap map[string]*stock
mx sync.RWMutex
}
func (sk *stocks) getStocks(codes ...string) *stocks {
var stks = &stocks{}
for _, code := range codes {
stks.stkMap[code] = sk.stkMap[code]
}
return stks
}
func (sk *stocks) Codes() []string {
var codes []string
for code := range sk.stkMap {
codes = append(codes, code)
}
return codes
}
func (sk *stocks) AddCodes(codes ...string) error {
sk.mx.Lock()
defer sk.mx.Unlock()
var cds []string
for _, code := range codes {
if _, ok := sk.stkMap[code]; ok {
continue
}
cds = append(cds, code)
}
stks, err := newStocks(cds...)
if err != nil {
return err
}
for _, stk := range stks {
sk.stkMap[stk.Code()] = stk
}
return nil
} }
func (sk *stocks) Clear() { func (sk *stocks) Clear() {
if len(sk.ss) <= 0 { sk.mx.Lock()
return defer sk.mx.Unlock()
for _, fd := range fds.stkMap {
fd.lastRise = 0
} }
sk.ss = []*stock{}
} }
func (sk *stocks) Update() error { func (sk *stocks) Update() error {
str, err := getStockStr(sk.codes) sk.mx.Lock()
sk.mx.Unlock()
str, err := getStockStr(sk.Codes())
if err != nil { if err != nil {
return err return err
} }
strs := strings.Split(str, ";\n") strs := strings.Split(str, ";\n")
if len(sk.ss) == 0 { for _, s := range strs {
sk.ss = []*stock{} ss := strings.Split(s, "\"")
for _, s := range strs { if len(ss) >= 2 {
ss := strings.Split(s, "\"") v := &stock{
if len(ss) >= 2 { code: ss[0][13:19],
v := &stock{ values: strings.Split(ss[1], ","),
values: strings.Split(ss[1], ","),
}
sk.ss = append(sk.ss, v)
} }
} if skk, ok := sk.stkMap[v.code]; ok {
} else { skk.update(v.values)
l := len(sk.ss) } else {
for i, s := range strs { sk.stkMap[v.code] = v
ss := strings.Split(s, "\"")
if len(ss) >= 2 && i < l {
sk.ss[i].update(strings.Split(ss[1], ","))
} }
} }
} }
@ -173,8 +227,10 @@ 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 var resp string
for _, s := range sk.ss { for _, s := range sk.stkMap {
if s.notify() { if s.notify() {
msg := s.Msg() msg := s.Msg()
resp = resp + msg + "\n" resp = resp + msg + "\n"
@ -183,33 +239,65 @@ func (sk *stocks) Msg() string {
return resp return resp
} }
type IArg interface { func (sk *stocks) Arg(openid string) map[string]interface{} {
Arg(openId string) map[string]interface{} arg := map[string]interface{}{
} "touser": openid,
"template_id": "yWuLbhAy7TTuqdeB9-VS6CR_t2rZQ8MHkJ62MF3VlS8",
func (sk *stocks) ForEachStock(f func(stk IArg) error) error { "data": map[string]interface{}{
for _, k := range sk.ss { "keyword": map[string]interface{}{
err := f(k) "value": sk.Msg(),
if err != nil { "color": "#173177",
return err },
} },
} }
return nil return arg
} }
func NewStocks(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, "股票代码为空")
} }
s := &stocks{ mx.Lock()
codes: codes, defer mx.Unlock()
if fds == nil {
fds = &stocks{}
} }
err := s.Update() err := fds.AddCodes(codes...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s, nil return fds.getStocks(codes...), nil
}
func Update() error {
return fds.Update()
}
func Clear() {
fds.Clear()
}
func newStocks(codes ...string) ([]*stock, error) {
if len(codes) <= 0 {
return nil, errcode.New(1, "股票代码为空")
}
str, err := getStockStr(codes)
if err != nil {
return nil, err
}
strs := strings.Split(str, ";\n")
var stks []*stock
for _, s := range strs {
ss := strings.Split(s, "\"")
if len(ss) >= 2 {
v := &stock{
values: strings.Split(ss[1], ","),
}
stks = append(stks, v)
}
}
return stks, nil
} }
func addPrefix(code string) string { func addPrefix(code string) string {
@ -251,66 +339,67 @@ const msgTemplate = `%s
%s %s %s %s
` `
func (s *stock) Arg(openId string) map[string]interface{} { //
//func (s *stock) Arg(openId string) map[string]interface{} {
arg := map[string]interface{}{ //
"touser": openId, // arg := map[string]interface{}{
"template_id": "L7fOGJURj-1HF4cIpFizCOOiAMqER3PG-pfgn37Dalw", // "touser": openId,
// "template_id": "L7fOGJURj-1HF4cIpFizCOOiAMqER3PG-pfgn37Dalw",
"data": map[string]interface{}{ //
"first": map[string]interface{}{ // "data": map[string]interface{}{
"value": s.values[0], // "first": map[string]interface{}{
"color": "#173177", // "value": s.values[0],
}, // "color": "#173177",
"keyword1": map[string]interface{}{ // },
"value": fmt.Sprintf("%s %s", s.values[30], s.values[31]), // "keyword1": map[string]interface{}{
"color": "#173177", // "value": fmt.Sprintf("%s %s", s.values[30], s.values[31]),
}, // "color": "#173177",
"keyword2": map[string]interface{}{ // },
"value": s.values[2], // "keyword2": map[string]interface{}{
"color": "#173177", // "value": s.values[2],
}, // "color": "#173177",
"keyword3": map[string]interface{}{ // },
"value": s.values[1], // "keyword3": map[string]interface{}{
"color": "#173177", // "value": s.values[1],
}, // "color": "#173177",
"keyword4": map[string]interface{}{ // },
"value": s.values[3], // "keyword4": map[string]interface{}{
"color": "#173177", // "value": s.values[3],
}, // "color": "#173177",
"keyword5": map[string]interface{}{ // },
"value": s.rise(), // "keyword5": map[string]interface{}{
"color": "#173177", // "value": s.rise(),
}, // "color": "#173177",
"keyword6": map[string]interface{}{ // },
"value": s.values[4], // "keyword6": map[string]interface{}{
"color": "#173177", // "value": s.values[4],
}, // "color": "#173177",
"keyword7": map[string]interface{}{ // },
"value": s.values[5], // "keyword7": map[string]interface{}{
"color": "#173177", // "value": s.values[5],
}, // "color": "#173177",
"keyword8": map[string]interface{}{ // },
"value": s.tradingVolume(), // "keyword8": map[string]interface{}{
"color": "#173177", // "value": s.tradingVolume(),
}, // "color": "#173177",
"keyword9": map[string]interface{}{ // },
"value": numFormat(s.values[9]), // "keyword9": map[string]interface{}{
"color": "#173177", // "value": numFormat(s.values[9]),
}, // "color": "#173177",
"keyword10": map[string]interface{}{ // },
"value": s.buyCount(), // "keyword10": map[string]interface{}{
"color": "#173177", // "value": s.buyCount(),
}, // "color": "#173177",
"keyword11": map[string]interface{}{ // },
"value": s.sellCount(), // "keyword11": map[string]interface{}{
"color": "#173177", // "value": s.sellCount(),
}, // "color": "#173177",
"remark": map[string]interface{}{ // },
"value": "欢迎再次购买!", // "remark": map[string]interface{}{
"color": "#173177", // "value": "欢迎再次购买!",
}, // "color": "#173177",
}, // },
} // },
return arg // }
} // return arg
//}

@ -14,32 +14,71 @@ package user
import ( import (
"github.com/jageros/hawox/attribute" "github.com/jageros/hawox/attribute"
"stock/module"
"sync" "sync"
) )
var users sync.Map var (
users = map[string]*User{}
mx sync.RWMutex
)
func LoadAllUserIntoCache() error { func LoadAllUserIntoCache() error {
attrs, err := attribute.LoadAll("user") attrs, err := attribute.LoadAll("user")
if err != nil { if err != nil {
return err return err
} }
mx.Lock()
for _, attr := range attrs { for _, attr := range attrs {
u := &User{attr: attr} u := &User{attr: attr}
users.Store(attr.GetAttrID(), u) users[u.OpenID()] = u
} }
mx.Unlock()
return nil return nil
} }
func GetUser(openId string) (*User, error) { func GetUser(openId string) (*User, error) {
u, ok := users.Load(openId) mx.RLock()
u, ok := users[openId]
mx.RUnlock()
if ok { if ok {
return u.(*User), nil return u, nil
} }
us, err := newUser(openId) us, err := newUser(openId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
users.Store(openId, us) mx.Lock()
users[openId] = us
mx.Unlock()
return us, nil return us, nil
} }
func ForEachUser(f func(u module.IUser) bool) {
mx.Lock()
defer mx.Unlock()
for _, u := range users {
if !f(u) {
break
}
}
}
func Codes(isFund bool) []string {
var codes = map[string]struct{}{}
ForEachUser(func(u module.IUser) bool {
cds := u.Codes(isFund)
for _, cd := range cds {
if _, ok := codes[cd]; ok {
continue
}
codes[cd] = struct{}{}
}
return true
})
var cods []string
for code := range codes {
cods = append(cods, code)
}
return cods
}

@ -14,11 +14,13 @@ package user
import ( import (
"github.com/jageros/hawox/attribute" "github.com/jageros/hawox/attribute"
"sync"
"time" "time"
) )
type User struct { type User struct {
attr *attribute.AttrMgr attr *attribute.AttrMgr
mx sync.RWMutex
} }
func newUser(openId string) (*User, error) { func newUser(openId string) (*User, error) {
@ -38,6 +40,8 @@ func (u *User) OpenID() string {
} }
func (u *User) Codes(isFund bool) []string { func (u *User) Codes(isFund bool) []string {
u.mx.RLock()
defer u.mx.RUnlock()
key := "stock" key := "stock"
if isFund { if isFund {
key = "fund" key = "fund"
@ -56,6 +60,8 @@ func (u *User) Codes(isFund bool) []string {
// HasSubscribed 查询用户是否订阅此票 // HasSubscribed 查询用户是否订阅此票
func (u *User) HasSubscribed(isFund bool, code string) bool { func (u *User) HasSubscribed(isFund bool, code string) bool {
u.mx.RLock()
defer u.mx.RUnlock()
key := "stock" key := "stock"
if isFund { if isFund {
key = "fund" key = "fund"
@ -73,6 +79,8 @@ func (u *User) HasSubscribed(isFund bool, code string) bool {
// Subscribe 订阅股票或基金 // Subscribe 订阅股票或基金
func (u *User) Subscribe(isFund bool, codes ...string) { func (u *User) Subscribe(isFund bool, codes ...string) {
u.mx.Lock()
defer u.mx.Unlock()
key := "stock" key := "stock"
if isFund { if isFund {
key = "fund" key = "fund"
@ -95,6 +103,8 @@ func (u *User) Subscribe(isFund bool, codes ...string) {
// UnSubscribe 取消订阅股票或基金 // UnSubscribe 取消订阅股票或基金
func (u *User) UnSubscribe(isFund bool, codes ...string) { func (u *User) UnSubscribe(isFund bool, codes ...string) {
u.mx.Lock()
defer u.mx.Unlock()
key := "stock" key := "stock"
if isFund { if isFund {
key = "fund" key = "fund"

@ -14,9 +14,14 @@ package wxgzh
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin"
"github.com/jageros/hawox/errcode" "github.com/jageros/hawox/errcode"
"github.com/jageros/hawox/httpc" "github.com/jageros/hawox/httpc"
"stock/stock" "github.com/jageros/hawox/logx"
"net/http"
"stock/module"
"stock/user"
"strings"
"time" "time"
) )
@ -43,21 +48,8 @@ type AccessToken struct {
Errmsg string `json:"errmsg"` Errmsg string `json:"errmsg"`
} }
type RData struct { type IArg interface {
ToUserName string `xml:"ToUserName"` Arg(openid string) map[string]interface{}
FromUserName string `xml:"FromUserName"`
CreateTime int `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
MsgID int64 `xml:"MsgId"`
}
type xml struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
} }
func getAccessToken(update bool) (string, error) { func getAccessToken(update bool) (string, error) {
@ -77,16 +69,16 @@ func getAccessToken(update bool) (string, error) {
return resp.AccessToken, nil return resp.AccessToken, nil
} }
func send(openID string, stk stock.IArg, recall bool) error { func send(openID string, arg IArg, recall bool) error {
token, err := getAccessToken(false) token, err := getAccessToken(false)
if err != nil { if err != nil {
return err return err
} }
url := fmt.Sprintf(sendUrl, token) url := fmt.Sprintf(sendUrl, token)
arg := stk.Arg(openID) msg := arg.Arg(openID)
resp := &Resp{} resp := &Resp{}
err = httpc.RequestWithInterface(httpc.POST, url, httpc.JSON, arg, nil, resp) err = httpc.RequestWithInterface(httpc.POST, url, httpc.JSON, msg, nil, resp)
if err != nil { if err != nil {
return err return err
} }
@ -97,7 +89,7 @@ func send(openID string, stk stock.IArg, recall bool) error {
return err return err
} }
if recall { if recall {
return send(openID, stk, false) return send(openID, arg, false)
} }
} }
return errcode.New(int32(resp.Errcode), resp.Errmsg) return errcode.New(int32(resp.Errcode), resp.Errmsg)
@ -105,6 +97,76 @@ func send(openID string, stk stock.IArg, recall bool) error {
return nil return nil
} }
func Send(openId string, stk stock.IArg) error { func Send(openId string, stk IArg) error {
return send(openId, stk, true) return send(openId, stk, true)
} }
func SendAll(stk IArg) error {
user.ForEachUser(func(u module.IUser) bool {
if u.HasSubscribed(false, "") {
err := Send(u.OpenID(), stk)
if err != nil {
logx.Error(err)
return false
}
}
return true
})
return nil
}
type rData struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int64 `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
MsgID int64 `xml:"MsgId"`
}
type xml struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int64 `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
}
func Handle(c *gin.Context) {
rMsg := &rData{}
err := c.BindXML(rMsg)
if err != nil {
c.String(http.StatusOK, err.Error())
c.Abort()
return
}
wMsg := &xml{
ToUserName: rMsg.FromUserName,
FromUserName: rMsg.ToUserName,
MsgType: "text",
CreateTime: time.Now().Unix(),
Content: "订阅股票:+st股票代码例如+st600905\n订阅基金+fd基金代码例如+fd161725\n" +
"取消订阅股票:-st股票代码例如-st600905\n取消订阅基金-fd基金代码例如-fd161725",
}
if rMsg.MsgType == "text" {
u, err := user.GetUser(rMsg.FromUserName)
if err != nil {
c.String(http.StatusOK, err.Error())
c.Abort()
return
}
switch {
case strings.HasPrefix(rMsg.Content, "+"):
u.Subscribe(rMsg.Content[1:3] == "fd", rMsg.Content[3:])
wMsg.Content = "订阅成功!"
case strings.HasPrefix(rMsg.Content, "-"):
u.UnSubscribe(rMsg.Content[1:3] == "fd", rMsg.Content[3:])
wMsg.Content = "成功取消订阅!"
}
}
c.XML(http.StatusOK, wMsg)
}

Loading…
Cancel
Save