wxgzh
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,10 +3,14 @@ qywxRobotUrl: ""
|
||||
|
||||
# 钉钉群机器人的secret
|
||||
dingTalkSecret: ""
|
||||
|
||||
# 钉钉群机器人发消息额链接
|
||||
# 钉钉群机器人发消息的链接
|
||||
dingTalkRobotUrl: ""
|
||||
|
||||
# 微信公众号appid
|
||||
wxgzhAppid: ""
|
||||
# 微信公众号secret
|
||||
wxgzhSecret: ""
|
||||
|
||||
# 天行数据获取节假日的api的key (https://www.tianapi.com/apiview/139)
|
||||
tianApiKey: ""
|
||||
|
||||
|
||||
29
fund/fund.go
29
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
3
go.mod
3
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
|
||||
)
|
||||
|
||||
6
go.sum
6
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=
|
||||
|
||||
21
module/user.go
Normal file
21
module/user.go
Normal file
@@ -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)
|
||||
}
|
||||
297
stock/stock.go
297
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) Clear() {
|
||||
if len(sk.ss) <= 0 {
|
||||
return
|
||||
func (sk *stocks) getStocks(codes ...string) *stocks {
|
||||
var stks = &stocks{}
|
||||
for _, code := range codes {
|
||||
stks.stkMap[code] = sk.stkMap[code]
|
||||
}
|
||||
sk.ss = []*stock{}
|
||||
return stks
|
||||
}
|
||||
|
||||
func (sk *stocks) Update() error {
|
||||
str, err := getStockStr(sk.codes)
|
||||
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() {
|
||||
sk.mx.Lock()
|
||||
defer sk.mx.Unlock()
|
||||
for _, fd := range fds.stkMap {
|
||||
fd.lastRise = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (sk *stocks) Update() error {
|
||||
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
|
||||
//}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
10
user/user.go
10
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"
|
||||
|
||||
104
wxgzh/wxgzh.go
104
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user