├── README.md ├── assets └── img │ └── qr-code.png ├── internal ├── model │ ├── token.go │ └── answer.go ├── dao │ ├── dao.go │ ├── dao_test.go │ ├── token_test.go │ └── token.go └── wchat │ └── wchat.go ├── docker-compose.yml ├── api ├── api.go ├── api_test.go ├── wechat_test.go └── wechat.go ├── Makefile ├── utils └── util.go ├── Dockerfile ├── .github └── workflows │ └── test.yml ├── go.mod ├── main.go ├── .gitignore └── go.sum /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![](assets/img/qr-code.png) 3 | -------------------------------------------------------------------------------- /assets/img/qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodFrm/wxmp/master/assets/img/qr-code.png -------------------------------------------------------------------------------- /internal/model/token.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Token struct { 4 | UserId string 5 | Num int64 6 | Token string 7 | } 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | redis: 6 | image: redis 7 | container_name: wx_redis 8 | ports: 9 | - 6379:6379 10 | -------------------------------------------------------------------------------- /internal/model/answer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Answer struct { 4 | Index int32 5 | Result []struct { 6 | Correct []struct { 7 | Content interface{} 8 | Option interface{} 9 | } 10 | Type int32 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/CodFrm/wxmp/internal/dao" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Handel(r *gin.Engine, d *dao.Dao) *gin.Engine { 9 | 10 | NewWechat(r, d) 11 | 12 | return r 13 | } 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: test cover build 3 | 4 | build: 5 | go build . 6 | 7 | test: 8 | go test -v ./... 9 | 10 | cover: 11 | go test -cover -coverprofile=coverage.data ./... 12 | 13 | cover-html: cover 14 | go tool cover -html=coverage.data -o coverage.html 15 | 16 | -------------------------------------------------------------------------------- /utils/util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "math/rand" 4 | 5 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 6 | 7 | func RandStringRunes(n int) string { 8 | b := make([]rune, n) 9 | for i := range b { 10 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 11 | } 12 | return string(b) 13 | } 14 | -------------------------------------------------------------------------------- /internal/dao/dao.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | redis "github.com/go-redis/redis/v7" 5 | ) 6 | 7 | type Dao struct { 8 | redis *redis.Client 9 | } 10 | 11 | type DaoConfig struct { 12 | Redis *redis.Options 13 | } 14 | 15 | func New(conf *DaoConfig) *Dao { 16 | return &Dao{ 17 | redis: redis.NewClient(conf.Redis), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/dao/dao_test.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/alicebob/miniredis" 5 | "github.com/go-redis/redis/v7" 6 | "testing" 7 | ) 8 | 9 | var dao *Dao 10 | 11 | func TestMain(m *testing.M) { 12 | mr, err := miniredis.Run() 13 | if err != nil { 14 | panic(err) 15 | } 16 | dao = New(&DaoConfig{ 17 | Redis: &redis.Options{ 18 | Addr: mr.Addr(), 19 | }}) 20 | m.Run() 21 | } 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13.2-alpine3.10 as build 2 | 3 | ENV NAME=wxmp 4 | 5 | WORKDIR $GOPATH/src/$NAME 6 | COPY . $GOPATH/src/$NAME 7 | 8 | RUN go build -o $NAME . && cp $NAME / 9 | 10 | FROM alpine:3.10 11 | 12 | COPY --from=build /wxmp . 13 | 14 | EXPOSE 8080 15 | 16 | RUN chmod +x wxmp 17 | 18 | CMD ./wxmp --wx_app_id=$WX_APPID --wx_app_secret=$WX_APP_SECRET --wx_encode_aes_key=$WX_ENCODE_KEY --wx_token=$WX_TOKEN --server_addr=$SERVER_ADDR 19 | -------------------------------------------------------------------------------- /api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/CodFrm/wxmp/internal/dao" 5 | "github.com/alicebob/miniredis" 6 | "github.com/go-redis/redis/v7" 7 | "testing" 8 | ) 9 | 10 | var wechat *Wechat 11 | 12 | func TestMain(m *testing.M) { 13 | mr, err := miniredis.Run() 14 | if err != nil { 15 | panic(err) 16 | } 17 | wechat = &Wechat{ 18 | token: dao.New(&dao.DaoConfig{ 19 | Redis: &redis.Options{ 20 | Addr: mr.Addr(), 21 | }}), 22 | } 23 | m.Run() 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push] 3 | jobs: 4 | 5 | test: 6 | name: Test 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Build 20 | run: go build -v . 21 | 22 | - name: Get test 23 | run: | 24 | go test -v ./... 25 | go test -cover ./... 26 | -------------------------------------------------------------------------------- /internal/wchat/wchat.go: -------------------------------------------------------------------------------- 1 | package wchat 2 | 3 | import ( 4 | "github.com/silenceper/wechat" 5 | "github.com/silenceper/wechat/menu" 6 | "log" 7 | ) 8 | 9 | var WChat *wechat.Wechat 10 | 11 | func Init(wc *wechat.Wechat) { 12 | mu := wc.GetMenu() 13 | err := mu.SetMenu([]*menu.Button{{ 14 | Name: "token菜单", 15 | SubButtons: []*menu.Button{{ 16 | Type: "click", 17 | Name: "查询余额", 18 | Key: "balance", 19 | }, { 20 | Type: "click", 21 | Name: "绑定key", 22 | Key: "bind", 23 | }, { 24 | Type: "click", 25 | Name: "申请token", 26 | Key: "create", 27 | }}, 28 | }}) 29 | if err != nil { 30 | log.Printf(err.Error()) 31 | } 32 | WChat = wc 33 | } 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CodFrm/wxmp 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect 7 | github.com/alicebob/miniredis v2.5.0+incompatible 8 | github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect 9 | github.com/gin-gonic/gin v1.7.7 10 | github.com/go-redis/redis/v7 v7.0.0-beta.4 11 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 12 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 13 | github.com/modern-go/reflect2 v1.0.1 // indirect 14 | github.com/silenceper/wechat v2.0.0+incompatible 15 | github.com/stretchr/testify v1.4.0 16 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 // indirect 17 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /internal/dao/token_test.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/go-redis/redis/v7" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestToken(t *testing.T) { 10 | //查询token 11 | tk, err := dao.FindByUserID("qq") 12 | assert.Nil(t, tk) 13 | assert.Error(t, err, redis.Nil) 14 | //创建token 15 | token, err := dao.CreateToken("qq") 16 | assert.Nil(t, err) 17 | token2, err := dao.FindByUserID("qq") 18 | assert.Equal(t, token, token2.Token) 19 | assert.Nil(t, err) 20 | token3, _ := dao.CreateToken("wx2") 21 | _, err = dao.CreateToken("wx2") 22 | assert.Error(t, err, "已经拥有token了:"+token3) 23 | //绑定 24 | assert.Nil(t, dao.BindToken("wx", "qq", token2.Token)) 25 | assert.Error(t, dao.BindToken("wx", "qq", "error"), "Token不匹配") 26 | assert.Error(t, dao.BindToken("wx2", "qq", token2.Token), "已经绑定过token了:"+token3) 27 | assert.Error(t, dao.BindToken("wx", "qq2", token2.Token), "没有找到token") 28 | } 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/CodFrm/wxmp/api" 6 | "github.com/CodFrm/wxmp/internal/dao" 7 | "github.com/CodFrm/wxmp/internal/wchat" 8 | "github.com/gin-gonic/gin" 9 | "github.com/go-redis/redis/v7" 10 | "github.com/silenceper/wechat" 11 | "github.com/silenceper/wechat/cache" 12 | "log" 13 | ) 14 | 15 | var config = &wechat.Config{} 16 | var addr string 17 | var redisconf string 18 | 19 | func init() { 20 | flag.StringVar(&config.AppID, "wx_app_id", "", "") 21 | flag.StringVar(&config.AppSecret, "wx_app_secret", "", "") 22 | flag.StringVar(&config.EncodingAESKey, "wx_encode_aes_key", "", "") 23 | flag.StringVar(&config.Token, "wx_token", "", "") 24 | flag.StringVar(&addr, "server_addr", ":8080", "") 25 | flag.StringVar(&redisconf, "redis", "127.0.0.1:6379", "") 26 | flag.Parse() 27 | } 28 | 29 | func main() { 30 | config.Cache = cache.NewMemory() 31 | wc := wechat.NewWechat(config) 32 | d := dao.New(&dao.DaoConfig{Redis: &redis.Options{ 33 | Addr: redisconf, 34 | }}) 35 | wchat.Init(wc) 36 | r := gin.Default() 37 | if err := api.Handel(r, d).Run(addr); err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/dao/token.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "errors" 5 | "github.com/CodFrm/wxmp/internal/model" 6 | "github.com/CodFrm/wxmp/utils" 7 | "github.com/go-redis/redis/v7" 8 | ) 9 | 10 | type Token interface { 11 | FindByUserID(userid string) (*model.Token, error) 12 | CreateToken(userid string) (string, error) 13 | BindToken(userid string, olduserid string, token string) error 14 | } 15 | 16 | func (d *Dao) FindByUserID(userid string) (*model.Token, error) { 17 | ret := &model.Token{} 18 | m := d.redis.HGet("cxmooc:genuser", userid) 19 | if m.Err() != nil { 20 | return nil, m.Err() 21 | } 22 | ret.Token = m.Val() 23 | m = d.redis.Get("cxmooc:vtoken:" + ret.Token) 24 | if m.Err() != nil { 25 | return nil, m.Err() 26 | } 27 | ret.Num, _ = m.Int64() 28 | return ret, nil 29 | } 30 | 31 | func (d *Dao) CreateToken(userid string) (string, error) { 32 | token, err := d.FindByUserID(userid) 33 | if err != nil && err != redis.Nil { 34 | return "", err 35 | } 36 | if token != nil { 37 | return "", errors.New("已经拥有token了:" + token.Token) 38 | } 39 | randToken := utils.RandStringRunes(11) 40 | if m := d.redis.HSet("cxmooc:genuser", userid, randToken); m.Err() != nil { 41 | return "", m.Err() 42 | } 43 | if m := d.redis.Set("cxmooc:vtoken:"+randToken, 100, 0); m.Err() != nil { 44 | return "", m.Err() 45 | } 46 | return randToken, nil 47 | } 48 | 49 | func (d *Dao) BindToken(userid string, olduserid string, token string) error { 50 | t, err := d.FindByUserID(olduserid) 51 | if err != nil { 52 | if err == redis.Nil { 53 | return errors.New("没有找到token") 54 | } 55 | return err 56 | } 57 | if t.Token != token { 58 | return errors.New("Token不匹配") 59 | } 60 | t, err = d.FindByUserID(userid) 61 | if t != nil { 62 | return errors.New("已经绑定过token了:" + t.Token) 63 | } 64 | if err != nil && err != redis.Nil { 65 | return err 66 | } 67 | if m := d.redis.HSet("cxmooc:genuser", userid, token); m.Err() != nil { 68 | return m.Err() 69 | } 70 | if m := d.redis.IncrBy("cxmooc:vtoken:"+token, 100); m.Err() != nil { 71 | return m.Err() 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Created by https://www.gitignore.io/api/go,jetbrains 4 | # Edit at https://www.gitignore.io/?templates=go,jetbrains 5 | 6 | ### Go ### 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Dependency directories (remove the comment below to include it) 21 | # vendor/ 22 | 23 | ### Go Patch ### 24 | /vendor/ 25 | /Godeps/ 26 | 27 | ### JetBrains ### 28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 29 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 30 | 31 | # User-specific stuff 32 | .idea 33 | .idea/**/workspace.xml 34 | .idea/**/tasks.xml 35 | .idea/**/usage.statistics.xml 36 | .idea/**/dictionaries 37 | .idea/**/shelf 38 | 39 | # Generated files 40 | .idea/**/contentModel.xml 41 | 42 | # Sensitive or high-churn files 43 | .idea/**/dataSources/ 44 | .idea/**/dataSources.ids 45 | .idea/**/dataSources.local.xml 46 | .idea/**/sqlDataSources.xml 47 | .idea/**/dynamic.xml 48 | .idea/**/uiDesigner.xml 49 | .idea/**/dbnavigator.xml 50 | 51 | # Gradle 52 | .idea/**/gradle.xml 53 | .idea/**/libraries 54 | 55 | # Gradle and Maven with auto-import 56 | # When using Gradle or Maven with auto-import, you should exclude module files, 57 | # since they will be recreated, and may cause churn. Uncomment if using 58 | # auto-import. 59 | # .idea/modules.xml 60 | # .idea/*.iml 61 | # .idea/modules 62 | # *.iml 63 | # *.ipr 64 | 65 | # CMake 66 | cmake-build-*/ 67 | 68 | # Mongo Explorer plugin 69 | .idea/**/mongoSettings.xml 70 | 71 | # File-based project format 72 | *.iws 73 | 74 | # IntelliJ 75 | out/ 76 | 77 | # mpeltonen/sbt-idea plugin 78 | .idea_modules/ 79 | 80 | # JIRA plugin 81 | atlassian-ide-plugin.xml 82 | 83 | # Cursive Clojure plugin 84 | .idea/replstate.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | # Editor-based Rest Client 93 | .idea/httpRequests 94 | 95 | # Android studio 3.1+ serialized cache file 96 | .idea/caches/build_file_checksums.ser 97 | 98 | ### JetBrains Patch ### 99 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 100 | 101 | # *.iml 102 | # modules.xml 103 | # .idea/misc.xml 104 | # *.ipr 105 | 106 | # Sonarlint plugin 107 | .idea/**/sonarlint/ 108 | 109 | # SonarQube Plugin 110 | .idea/**/sonarIssues.xml 111 | 112 | # Markdown Navigator plugin 113 | .idea/**/markdown-navigator.xml 114 | .idea/**/markdown-navigator/ 115 | 116 | # End of https://www.gitignore.io/api/go,jetbrains 117 | -------------------------------------------------------------------------------- /api/wechat_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/silenceper/wechat/message" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestWechat_WxHandel(t *testing.T) { 10 | handel := wechat.WxHandel() 11 | 12 | //创建token 13 | reply := handel(message.MixMessage{ 14 | CommonToken: message.CommonToken{ 15 | FromUserName: "88888888", 16 | MsgType: message.MsgTypeEvent, 17 | }, 18 | Event: message.EventClick, 19 | EventKey: "create", 20 | }) 21 | qqtoken := reply.MsgData.(*message.Text).Content 22 | 23 | reply = handel(message.MixMessage{ 24 | CommonToken: message.CommonToken{ 25 | FromUserName: "sq", 26 | MsgType: message.MsgTypeText, 27 | }, 28 | Content: "申请token", 29 | }) 30 | assert.Equal(t, len(reply.MsgData.(*message.Text).Content), 11) 31 | //绑定token 32 | reply = handel(message.MixMessage{ 33 | CommonToken: message.CommonToken{ 34 | FromUserName: "wx", 35 | MsgType: message.MsgTypeText, 36 | }, 37 | Content: "88888888+" + qqtoken, 38 | }) 39 | assert.Equal(t, "绑定成功", reply.MsgData.(*message.Text).Content) 40 | //重新绑定 41 | reply = handel(message.MixMessage{ 42 | CommonToken: message.CommonToken{ 43 | FromUserName: "wx", 44 | MsgType: message.MsgTypeText, 45 | }, 46 | Content: "88888888+" + qqtoken, 47 | }) 48 | assert.NotEqual(t, "绑定成功", reply.MsgData.(*message.Text).Content) 49 | //token信息 50 | reply = handel(message.MixMessage{ 51 | CommonToken: message.CommonToken{ 52 | FromUserName: "wx", 53 | MsgType: message.MsgTypeText, 54 | }, 55 | Content: "token", 56 | }) 57 | assert.Equal(t, "你的token为:"+qqtoken+" 剩余:200", reply.MsgData.(*message.Text).Content) 58 | reply = handel(message.MixMessage{ 59 | CommonToken: message.CommonToken{ 60 | FromUserName: "88888888", 61 | MsgType: message.MsgTypeEvent, 62 | }, 63 | Event: message.EventClick, 64 | EventKey: "balance", 65 | }) 66 | assert.Equal(t, "你的token为:"+qqtoken+" 剩余:200", reply.MsgData.(*message.Text).Content) 67 | reply = handel(message.MixMessage{ 68 | CommonToken: message.CommonToken{ 69 | FromUserName: "88888888", 70 | MsgType: message.MsgTypeText, 71 | }, 72 | Content: "token", 73 | }) 74 | assert.Equal(t, "你的token为:"+qqtoken+" 剩余:200", reply.MsgData.(*message.Text).Content) 75 | 76 | reply = handel(message.MixMessage{ 77 | CommonToken: message.CommonToken{ 78 | FromUserName: "0000", 79 | MsgType: message.MsgTypeText, 80 | }, 81 | Content: "token", 82 | }) 83 | assert.Equal(t, "你还没有token,请可以发送 qq号码+token 将原有的token和微信公众号进行绑定,例如: 88888888+ilyedbyd (绑定成功自动增加100点),发送 \"申请token\" 可申请一个新的token.", reply.MsgData.(*message.Text).Content) 84 | 85 | reply = handel(message.MixMessage{ 86 | CommonToken: message.CommonToken{ 87 | FromUserName: "0000", 88 | MsgType: message.MsgTypeText, 89 | }, 90 | Content: "查 我们通常所说的历史就是二阶历史。", 91 | }) 92 | assert.Equal(t, "错误", reply.MsgData.(*message.Text).Content) 93 | 94 | reply = handel(message.MixMessage{ 95 | CommonToken: message.CommonToken{ 96 | FromUserName: "0000", 97 | MsgType: message.MsgTypeText, 98 | }, 99 | Content: "查 我们现在看不到《红楼梦》哪个章节", 100 | }) 101 | assert.Equal(t, "B:秦可卿淫丧天香楼\nC:警幻情榜\nD:卫若兰射圃文字\n", reply.MsgData.(*message.Text).Content) 102 | 103 | } 104 | -------------------------------------------------------------------------------- /api/wechat.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/CodFrm/wxmp/internal/dao" 7 | "github.com/CodFrm/wxmp/internal/model" 8 | "github.com/CodFrm/wxmp/internal/wchat" 9 | "github.com/gin-gonic/gin" 10 | "github.com/go-redis/redis/v7" 11 | "github.com/silenceper/wechat/message" 12 | "io/ioutil" 13 | "net/http" 14 | "net/url" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | type Wechat struct { 21 | token dao.Token 22 | } 23 | 24 | func NewWechat(r *gin.Engine, token dao.Token) *Wechat { 25 | w := &Wechat{token: token} 26 | r.Any("/wchat", w.Wchat()) 27 | return w 28 | } 29 | 30 | func (w *Wechat) Wchat() gin.HandlerFunc { 31 | return func(c *gin.Context) { 32 | server := wchat.WChat.GetServer(c.Request, c.Writer) 33 | server.SetMessageHandler(w.WxHandel()) 34 | server.Serve() 35 | server.Send() 36 | } 37 | } 38 | 39 | func (w *Wechat) WxHandel() func(message.MixMessage) *message.Reply { 40 | return func(msg message.MixMessage) (ret *message.Reply) { 41 | content := "发送查+题目内容即可查询题目答案(eg.查 我们通常所说的历史就是二阶历史),访问地址: http://cx.icodef.com/query.html 也可以进行查询哦,发送token可以查看token相关命令" 42 | defer func() { 43 | ret = &message.Reply{ 44 | MsgType: message.MsgTypeText, 45 | MsgData: message.NewText(content), 46 | } 47 | }() 48 | switch msg.MsgType { 49 | case message.MsgTypeText: 50 | { 51 | cnt := strings.Trim(msg.Content, " ") 52 | if cnt == "token" { 53 | content = w.getToken(msg.FromUserName) 54 | } else if cnt == "申请token" { 55 | if token, err := w.token.CreateToken(msg.FromUserName); err != nil { 56 | content = err.Error() 57 | } else { 58 | content = token 59 | } 60 | } else { 61 | if regex, err := regexp.Compile(`^(\d+)\+(\w+)`); err != nil { 62 | content = err.Error() 63 | return 64 | } else { 65 | str := regex.FindStringSubmatch(msg.Content) 66 | if str != nil { 67 | if err := w.token.BindToken(msg.FromUserName, str[1], str[2]); err != nil { 68 | content = err.Error() 69 | } else { 70 | content = "绑定成功" 71 | } 72 | return 73 | } 74 | } 75 | if regex, err := regexp.Compile(`^查 (.*?)$`); err != nil { 76 | content = err.Error() 77 | return 78 | } else { 79 | str := regex.FindStringSubmatch(msg.Content) 80 | if str != nil { 81 | req, err := http.NewRequest("POST", "http://cx.icodef.com/v2/answer", bytes.NewBuffer([]byte( 82 | "topic[0]="+url.QueryEscape(str[1]), 83 | ))) 84 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 85 | client := &http.Client{} 86 | resp, err := client.Do(req) 87 | if err != nil { 88 | content = err.Error() 89 | return 90 | } 91 | defer resp.Body.Close() 92 | b, err := ioutil.ReadAll(resp.Body) 93 | if err != nil { 94 | content = err.Error() 95 | return 96 | } 97 | data := make([]model.Answer, 0) 98 | if err := json.Unmarshal(b, &data); err != nil { 99 | content = err.Error() 100 | return 101 | } 102 | if len(data[0].Result) <= 0 { 103 | content = "未找到答案" 104 | return 105 | } 106 | for _, v := range data[0].Result { 107 | if v.Type == 3 { 108 | if v.Correct[0].Content.(bool) { 109 | content = "正确" 110 | } else { 111 | content = "错误" 112 | } 113 | } else { 114 | content = "" 115 | for _, correct := range v.Correct { 116 | content = content + correct.Option.(string) + ":" + correct.Content.(string) + "\n" 117 | } 118 | } 119 | } 120 | } 121 | return 122 | } 123 | } 124 | } 125 | case message.MsgTypeEvent: 126 | { 127 | if msg.Event == message.EventClick { 128 | switch msg.EventKey { 129 | case "balance": 130 | { 131 | content = w.getToken(msg.FromUserName) 132 | } 133 | case "bind": 134 | { 135 | content = "可以发送 qq号码+token 将原有的token和微信公众号进行绑定,例如: 88888888+ilyedbyd (绑定成功自动增加100点)" 136 | } 137 | case "create": 138 | { 139 | if token, err := w.token.CreateToken(msg.FromUserName); err != nil { 140 | content = err.Error() 141 | } else { 142 | content = token 143 | } 144 | } 145 | default: 146 | { 147 | content = "发送查+题目内容即可查询题目答案(eg.查 我们通常所说的历史就是二阶历史),访问地址: http://cx.icodef.com/query.html 也可以进行查询哦,发送token可以查看token相关命令" 148 | } 149 | } 150 | } else if msg.Event == message.EventSubscribe { 151 | content = "欢迎关注icodef.com" 152 | } 153 | } 154 | } 155 | return 156 | } 157 | } 158 | 159 | func (w *Wechat) getToken(uid string) string { 160 | if t, err := w.token.FindByUserID(uid); err != nil { 161 | if err == redis.Nil { 162 | return "你还没有token,可以发送 qq号码+token 将原有的token和微信公众号进行绑定,例如: 88888888+ilyedbyd (绑定成功自动增加100点),如果没有token,发送 \"申请token\" 可申请一个新的token." 163 | } 164 | return err.Error() 165 | } else { 166 | return "你的token为:" + t.Token + " 剩余:" + strconv.Itoa(int(t.Num)) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U= 2 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= 3 | github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= 4 | github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= 5 | github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= 6 | github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 7 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 8 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 15 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 16 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 17 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 18 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 19 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 20 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 21 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 22 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 23 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 24 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 25 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 26 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 27 | github.com/go-redis/redis/v7 v7.0.0-beta.4 h1:p6z7Pde69EGRWvlC++y8aFcaWegyrKHzOBGo0zUACTQ= 28 | github.com/go-redis/redis/v7 v7.0.0-beta.4/go.mod h1:xhhSbUMTsleRPur+Vgx9sUHtyN33bdjxY+9/0n9Ig8s= 29 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 30 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 31 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 32 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 33 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 34 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 35 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 36 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 37 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 38 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 39 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 40 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 41 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 42 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 43 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 47 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 48 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 49 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 50 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 51 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 52 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 53 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/silenceper/wechat v2.0.0+incompatible h1:3vbX2Vek+vtlhGAZX8dMGRGfxRRuhXn8xrEi0EzTYBI= 57 | github.com/silenceper/wechat v2.0.0+incompatible/go.mod h1:JKAk3URsmQk11QCP/n3xq71Z5XdtCaLI61zRNbhFdVk= 58 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 59 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 60 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 61 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 62 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 63 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 64 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 65 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 66 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 h1:1b6PAtenNyhsmo/NKXVe34h7JEZKva1YB/ne7K7mqKM= 67 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= 68 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 69 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 70 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 71 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 72 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 73 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= 74 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 75 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 78 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 79 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 81 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 83 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 84 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 85 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 86 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 88 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 89 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 90 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 91 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 92 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 93 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 94 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 95 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 96 | --------------------------------------------------------------------------------