diff --git a/cfg/cfg.go b/cfg/cfg.go index bc32546..2b2647d 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -24,9 +24,11 @@ var ( QywxRobotUrl = "" DingTalkSecret = "" DingTalkRobotUrl = "" + WxgzhAppid = "" + WxgzhSecret = "" TianApiKey = "" - UpdateDuration = time.Second * 60 // 默认一分钟 - ThresholdValue float64 = 1 // 默认1% + UpdateDuration = time.Second * 60 // 默认一分钟 + ThresholdValue float64 = 1 // 默认1% StockCodes = []string{"600905", "600032", "002531", "600733", "000825", "002939"} // 三峡能源, 浙江新能, 天顺风能, 北汽蓝谷, 太钢不锈, 长城证券 FundCodes = []string{"006229", "162412", "008359", "161725", "012314", "162719", "501057"} ) @@ -35,6 +37,8 @@ func load(v *viper.Viper) { QywxRobotUrl = v.GetString("qywxRobotUrl") DingTalkSecret = v.GetString("dingTalkSecret") DingTalkRobotUrl = v.GetString("dingTalkRobotUrl") + WxgzhAppid = v.GetString("wxgzhAppid") + WxgzhSecret = v.GetString("wxgzhSecret") TianApiKey = v.GetString("tianApiKey") updates := v.GetInt("updateSecond") if updates > 0 { diff --git a/cmd/stock/main.go b/cmd/stock/main.go index aaf08e6..4cb57fb 100644 --- a/cmd/stock/main.go +++ b/cmd/stock/main.go @@ -2,6 +2,7 @@ package main import ( "github.com/gin-gonic/gin" + "github.com/jageros/hawox/attribute" "github.com/jageros/hawox/contextx" "github.com/jageros/hawox/httpx" "github.com/jageros/hawox/logx" @@ -10,8 +11,11 @@ import ( "net/http" "stock/cfg" "stock/fund" + "stock/module" "stock/msg" "stock/stock" + "stock/user" + "stock/wxgzh" "time" ) @@ -32,6 +36,19 @@ func main() { 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() { if !stock.IsSellDay(time.Now()) { logx.Info("今天不是交易日!") @@ -63,7 +80,7 @@ func main() { logx.Info("今天不是交易日!") return } - text := fund.FundsMsg(cfg.FundCodes...) + text := fund.FundsMsg(fundCodes...) if text == "" { logx.Errorf("收集基金数据为空!") return @@ -76,11 +93,12 @@ func main() { } }) + err = stock.Init(stockCodes...) + if err != nil { + logx.Fatal(err) + } + ctx.Go(func(ctx contextx.Context) error { - ss, err := stock.NewStocks(cfg.StockCodes...) - if err != nil { - return err - } var count int var opened, closed bool @@ -95,7 +113,7 @@ func main() { if !closed { if stock.HasClose(time.Now()) { logx.Info("--- 已闭市 ---") - ss.Clear() + stock.Clear() } else { logx.Info("--- 未开市 ---") } @@ -111,7 +129,7 @@ func main() { logx.Infof("--- 交易中 ---") } - err = ss.Update() + err = stock.Update() if err != nil { count++ logx.Errorf("Update ErrCount=%d err=%v", count, err) @@ -121,19 +139,33 @@ func main() { continue } - 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 + user.ForEachUser(func(u module.IUser) bool { + codes := u.Codes(false) + stk, err := stock.GetStocks(codes...) + if err != nil { + logx.Error(err) + } else { + err = wxgzh.Send(u.OpenID(), stk) + if err != nil { + logx.Error(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) { 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) { echostr := c.Query("echostr") - logx.Infof("=== %s ===", echostr) + logx.Infof("配置接口 %s", echostr) c.String(http.StatusOK, echostr) }) }, func(s *httpx.Server) { diff --git a/config.yaml b/config.yaml index 17a92f3..bfc73f3 100644 --- a/config.yaml +++ b/config.yaml @@ -3,10 +3,14 @@ qywxRobotUrl: "" # 钉钉群机器人的secret dingTalkSecret: "" - -# 钉钉群机器人发消息额链接 +# 钉钉群机器人发消息的链接 dingTalkRobotUrl: "" +# 微信公众号appid +wxgzhAppid: "" +# 微信公众号secret +wxgzhSecret: "" + # 天行数据获取节假日的api的key (https://www.tianapi.com/apiview/139) tianApiKey: "" diff --git a/fund/fund.go b/fund/fund.go index c63d47a..0ef6dce 100644 --- a/fund/fund.go +++ b/fund/fund.go @@ -27,7 +27,7 @@ const ( ) var ( - jsonStr = regexp.MustCompile(`{(.*?)}`) + jsonStr = regexp.MustCompile(`{(.*?)}`) ) type fund struct { @@ -50,11 +50,22 @@ func (f *fund) Msg() string { return msg } +type funds struct { + codes []string +} + +func NewFunds(codes ...string) *funds { + return &funds{ + codes: codes, + } +} + func FundsMsg(codes ...string) string { if len(codes) <= 0 { return "" } - var msg = "基金定投估值 >>>\n" + //var msg = "基金定投估值 >>>\n" + msg := "" for _, code := range codes { fd, err := newFund(code) if err != nil { @@ -87,3 +98,17 @@ const msgTemplate = `%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 +} diff --git a/fund/fund_test.go b/fund/fund_test.go index 4f07e2e..9c7aa90 100644 --- a/fund/fund_test.go +++ b/fund/fund_test.go @@ -13,11 +13,14 @@ package fund import ( - "fmt" + "stock/wxgzh" "testing" ) func Test_Fund(t *testing.T) { - msg := FundsMsg() - fmt.Println(msg) + f := NewFunds("006229", "162412") + err := wxgzh.Send("o-KDV6NbRaanYz55fJuSgyR0qxxU", f) + if err != nil { + t.Error(err) + } } diff --git a/go.mod b/go.mod index ff8f419..8e3a5db 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect github.com/tal-tech/go-zero v1.2.1 // 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/trace v1.0.0-RC2 // indirect go.uber.org/atomic v1.7.0 // indirect @@ -47,7 +48,9 @@ require ( golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/protobuf v1.27.1 // 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/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8f225c4..d5d3b3a 100644 --- a/go.sum +++ b/go.sum @@ -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/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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.27/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/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= @@ -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/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 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/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/module/user.go b/module/user.go new file mode 100644 index 0000000..667e396 --- /dev/null +++ b/module/user.go @@ -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) +} diff --git a/stock/stock.go b/stock/stock.go index 40e7939..fb7d173 100644 --- a/stock/stock.go +++ b/stock/stock.go @@ -9,10 +9,13 @@ import ( "stock/cfg" "strconv" "strings" + "sync" ) var ( baseUrl = "https://hq.sinajs.cn/" + fds *stocks + mx sync.RWMutex ) type IStock interface { @@ -28,6 +31,14 @@ type stock struct { 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) { s.values = values } @@ -130,41 +141,84 @@ func (s *stock) Msg() string { return msg } +// ========================== + +func Init(codes ...string) error { + fds = &stocks{} + return fds.AddCodes(codes...) +} + type stocks struct { - codes []string - ss []*stock + //codes []string + //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() { - if len(sk.ss) <= 0 { - return + sk.mx.Lock() + defer sk.mx.Unlock() + for _, fd := range fds.stkMap { + fd.lastRise = 0 } - sk.ss = []*stock{} } func (sk *stocks) Update() error { - str, err := getStockStr(sk.codes) + sk.mx.Lock() + sk.mx.Unlock() + str, err := getStockStr(sk.Codes()) if err != nil { return err } + strs := strings.Split(str, ";\n") - if len(sk.ss) == 0 { - sk.ss = []*stock{} - for _, s := range strs { - ss := strings.Split(s, "\"") - if len(ss) >= 2 { - v := &stock{ - values: strings.Split(ss[1], ","), - } - sk.ss = append(sk.ss, v) + for _, s := range strs { + ss := strings.Split(s, "\"") + if len(ss) >= 2 { + v := &stock{ + code: ss[0][13:19], + values: strings.Split(ss[1], ","), } - } - } else { - l := len(sk.ss) - for i, s := range strs { - ss := strings.Split(s, "\"") - if len(ss) >= 2 && i < l { - sk.ss[i].update(strings.Split(ss[1], ",")) + if skk, ok := sk.stkMap[v.code]; ok { + skk.update(v.values) + } else { + sk.stkMap[v.code] = v } } } @@ -173,8 +227,10 @@ func (sk *stocks) Update() error { } func (sk *stocks) Msg() string { + sk.mx.RLock() + defer sk.mx.RUnlock() var resp string - for _, s := range sk.ss { + for _, s := range sk.stkMap { if s.notify() { msg := s.Msg() resp = resp + msg + "\n" @@ -183,33 +239,65 @@ func (sk *stocks) Msg() string { return resp } -type IArg interface { - Arg(openId string) map[string]interface{} -} - -func (sk *stocks) ForEachStock(f func(stk IArg) error) error { - for _, k := range sk.ss { - err := f(k) - if err != nil { - return err - } +func (sk *stocks) Arg(openid string) map[string]interface{} { + arg := map[string]interface{}{ + "touser": openid, + "template_id": "yWuLbhAy7TTuqdeB9-VS6CR_t2rZQ8MHkJ62MF3VlS8", + "data": map[string]interface{}{ + "keyword": map[string]interface{}{ + "value": sk.Msg(), + "color": "#173177", + }, + }, } - return nil + return arg } -func NewStocks(codes ...string) (*stocks, error) { +func GetStocks(codes ...string) (*stocks, error) { if len(codes) <= 0 { return nil, errcode.New(1, "股票代码为空") } - s := &stocks{ - codes: codes, + mx.Lock() + defer mx.Unlock() + if fds == nil { + fds = &stocks{} } - err := s.Update() + err := fds.AddCodes(codes...) if err != nil { 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 { @@ -251,66 +339,67 @@ const msgTemplate = `%s 前五总买单:%s 前五总卖单:%s ` -func (s *stock) Arg(openId string) map[string]interface{} { - - arg := map[string]interface{}{ - "touser": openId, - "template_id": "L7fOGJURj-1HF4cIpFizCOOiAMqER3PG-pfgn37Dalw", - - "data": map[string]interface{}{ - "first": map[string]interface{}{ - "value": s.values[0], - "color": "#173177", - }, - "keyword1": map[string]interface{}{ - "value": fmt.Sprintf("%s %s", s.values[30], s.values[31]), - "color": "#173177", - }, - "keyword2": map[string]interface{}{ - "value": s.values[2], - "color": "#173177", - }, - "keyword3": map[string]interface{}{ - "value": s.values[1], - "color": "#173177", - }, - "keyword4": map[string]interface{}{ - "value": s.values[3], - "color": "#173177", - }, - "keyword5": map[string]interface{}{ - "value": s.rise(), - "color": "#173177", - }, - "keyword6": map[string]interface{}{ - "value": s.values[4], - "color": "#173177", - }, - "keyword7": map[string]interface{}{ - "value": s.values[5], - "color": "#173177", - }, - "keyword8": map[string]interface{}{ - "value": s.tradingVolume(), - "color": "#173177", - }, - "keyword9": map[string]interface{}{ - "value": numFormat(s.values[9]), - "color": "#173177", - }, - "keyword10": map[string]interface{}{ - "value": s.buyCount(), - "color": "#173177", - }, - "keyword11": map[string]interface{}{ - "value": s.sellCount(), - "color": "#173177", - }, - "remark": map[string]interface{}{ - "value": "欢迎再次购买!", - "color": "#173177", - }, - }, - } - return arg -} +// +//func (s *stock) Arg(openId string) map[string]interface{} { +// +// arg := map[string]interface{}{ +// "touser": openId, +// "template_id": "L7fOGJURj-1HF4cIpFizCOOiAMqER3PG-pfgn37Dalw", +// +// "data": map[string]interface{}{ +// "first": map[string]interface{}{ +// "value": s.values[0], +// "color": "#173177", +// }, +// "keyword1": map[string]interface{}{ +// "value": fmt.Sprintf("%s %s", s.values[30], s.values[31]), +// "color": "#173177", +// }, +// "keyword2": map[string]interface{}{ +// "value": s.values[2], +// "color": "#173177", +// }, +// "keyword3": map[string]interface{}{ +// "value": s.values[1], +// "color": "#173177", +// }, +// "keyword4": map[string]interface{}{ +// "value": s.values[3], +// "color": "#173177", +// }, +// "keyword5": map[string]interface{}{ +// "value": s.rise(), +// "color": "#173177", +// }, +// "keyword6": map[string]interface{}{ +// "value": s.values[4], +// "color": "#173177", +// }, +// "keyword7": map[string]interface{}{ +// "value": s.values[5], +// "color": "#173177", +// }, +// "keyword8": map[string]interface{}{ +// "value": s.tradingVolume(), +// "color": "#173177", +// }, +// "keyword9": map[string]interface{}{ +// "value": numFormat(s.values[9]), +// "color": "#173177", +// }, +// "keyword10": map[string]interface{}{ +// "value": s.buyCount(), +// "color": "#173177", +// }, +// "keyword11": map[string]interface{}{ +// "value": s.sellCount(), +// "color": "#173177", +// }, +// "remark": map[string]interface{}{ +// "value": "欢迎再次购买!", +// "color": "#173177", +// }, +// }, +// } +// return arg +//} diff --git a/user/cache.go b/user/cache.go index 9cb8508..83ae573 100644 --- a/user/cache.go +++ b/user/cache.go @@ -14,32 +14,71 @@ package user import ( "github.com/jageros/hawox/attribute" + "stock/module" "sync" ) -var users sync.Map +var ( + users = map[string]*User{} + mx sync.RWMutex +) func LoadAllUserIntoCache() error { attrs, err := attribute.LoadAll("user") if err != nil { return err } + mx.Lock() for _, attr := range attrs { u := &User{attr: attr} - users.Store(attr.GetAttrID(), u) + users[u.OpenID()] = u } + mx.Unlock() return nil } func GetUser(openId string) (*User, error) { - u, ok := users.Load(openId) + mx.RLock() + u, ok := users[openId] + mx.RUnlock() if ok { - return u.(*User), nil + return u, nil } us, err := newUser(openId) if err != nil { return nil, err } - users.Store(openId, us) + mx.Lock() + users[openId] = us + mx.Unlock() 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 +} diff --git a/user/user.go b/user/user.go index d69f238..cc0fc1f 100644 --- a/user/user.go +++ b/user/user.go @@ -14,11 +14,13 @@ package user import ( "github.com/jageros/hawox/attribute" + "sync" "time" ) type User struct { attr *attribute.AttrMgr + mx sync.RWMutex } func newUser(openId string) (*User, error) { @@ -38,6 +40,8 @@ func (u *User) OpenID() string { } func (u *User) Codes(isFund bool) []string { + u.mx.RLock() + defer u.mx.RUnlock() key := "stock" if isFund { key = "fund" @@ -56,6 +60,8 @@ func (u *User) Codes(isFund bool) []string { // HasSubscribed 查询用户是否订阅此票 func (u *User) HasSubscribed(isFund bool, code string) bool { + u.mx.RLock() + defer u.mx.RUnlock() key := "stock" if isFund { key = "fund" @@ -73,6 +79,8 @@ func (u *User) HasSubscribed(isFund bool, code string) bool { // Subscribe 订阅股票或基金 func (u *User) Subscribe(isFund bool, codes ...string) { + u.mx.Lock() + defer u.mx.Unlock() key := "stock" if isFund { key = "fund" @@ -95,6 +103,8 @@ func (u *User) Subscribe(isFund bool, codes ...string) { // UnSubscribe 取消订阅股票或基金 func (u *User) UnSubscribe(isFund bool, codes ...string) { + u.mx.Lock() + defer u.mx.Unlock() key := "stock" if isFund { key = "fund" diff --git a/wxgzh/wxgzh.go b/wxgzh/wxgzh.go index f1ff15c..5b0fe10 100644 --- a/wxgzh/wxgzh.go +++ b/wxgzh/wxgzh.go @@ -14,9 +14,14 @@ package wxgzh import ( "fmt" + "github.com/gin-gonic/gin" "github.com/jageros/hawox/errcode" "github.com/jageros/hawox/httpc" - "stock/stock" + "github.com/jageros/hawox/logx" + "net/http" + "stock/module" + "stock/user" + "strings" "time" ) @@ -43,21 +48,8 @@ type AccessToken struct { Errmsg string `json:"errmsg"` } -type RData struct { - ToUserName string `xml:"ToUserName"` - 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"` +type IArg interface { + Arg(openid string) map[string]interface{} } func getAccessToken(update bool) (string, error) { @@ -77,16 +69,16 @@ func getAccessToken(update bool) (string, error) { 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) if err != nil { return err } url := fmt.Sprintf(sendUrl, token) - arg := stk.Arg(openID) + msg := arg.Arg(openID) 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 { return err } @@ -97,7 +89,7 @@ func send(openID string, stk stock.IArg, recall bool) error { return err } if recall { - return send(openID, stk, false) + return send(openID, arg, false) } } return errcode.New(int32(resp.Errcode), resp.Errmsg) @@ -105,6 +97,76 @@ func send(openID string, stk stock.IArg, recall bool) error { return nil } -func Send(openId string, stk stock.IArg) error { +func Send(openId string, stk IArg) error { 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) +}