陈文强 před 2 roky
revize
9e8cf1c3b5
51 změnil soubory, kde provedl 1841 přidání a 0 odebrání
  1. binární
      .DS_Store
  2. binární
      apiserver/.DS_Store
  3. 44 0
      apiserver/config/config.go
  4. 7 0
      apiserver/config/config_test.go
  5. 42 0
      apiserver/dao/dao.go
  6. 12 0
      apiserver/model/http/http.go
  7. 19 0
      apiserver/model/http/mcs.go
  8. 42 0
      apiserver/model/http/message.go
  9. 17 0
      apiserver/model/http/wxmsg.go
  10. 32 0
      apiserver/model/message.go
  11. 1 0
      apiserver/model/model.go
  12. 6 0
      apiserver/model/user.go
  13. 1 0
      apiserver/server/mcs.go
  14. 42 0
      apiserver/server/message.go
  15. 121 0
      apiserver/server/server.go
  16. 7 0
      apiserver/server/server_test.go
  17. 77 0
      apiserver/server/wxoffice.go
  18. 132 0
      apiserver/service/job.go
  19. 145 0
      apiserver/service/message.go
  20. 98 0
      apiserver/service/send_test.go
  21. 23 0
      apiserver/service/service.go
  22. 14 0
      apiserver/service/user.go
  23. 126 0
      apiserver/service/wxoffce.go
  24. 22 0
      client/client.go
  25. 8 0
      cmd/apiserver/conf.toml
  26. binární
      cmd/apiserver/main
  27. 25 0
      cmd/apiserver/main.go
  28. 20 0
      cmd/client/main.go
  29. 10 0
      doc/doc.md
  30. 38 0
      doc/json/message.json
  31. 11 0
      doc/json/message_type.json
  32. 11 0
      doc/json/message_type_test.json
  33. 8 0
      doc/sql/alter.sql
  34. 6 0
      doc/sql/insert.sql
  35. 13 0
      doc/sql/msg_push.sql
  36. 36 0
      doc/sql/notice_init.sql
  37. 10 0
      doc/sql/query.sql
  38. 9 0
      doc/xml/wxevent.xml
  39. 36 0
      go.mod
  40. 140 0
      go.sum
  41. binární
      main
  42. 4 0
      middleware/notice/dding.go
  43. 33 0
      middleware/notice/notice_test.go
  44. 57 0
      middleware/notice/wxoffice.go
  45. 53 0
      pkg/http/client.go
  46. 72 0
      pkg/log/file.go
  47. 25 0
      pkg/log/handler.go
  48. 138 0
      pkg/log/log.go
  49. 27 0
      pkg/log/stdout.go
  50. 1 0
      pkg/setting/setting.go
  51. 20 0
      pkg/util/util.go

binární
.DS_Store


binární
apiserver/.DS_Store


+ 44 - 0
apiserver/config/config.go

@@ -0,0 +1,44 @@
+package config
+
+import (
+	"flag"
+	"time"
+
+	"github.com/BurntSushi/toml"
+	"kpt.notice/pkg/log"
+)
+
+var (
+	confPath string
+	Conf     = &Config{}
+)
+
+func init() {
+	flag.StringVar(&confPath, "conf", "conf.toml", "default config path")
+}
+func Init() (err error) {
+	if _, err = toml.DecodeFile(confPath, &Conf); err != nil {
+		panic(err)
+	}
+	return
+}
+
+type Config struct {
+	// Log *log.Config  `toml:"log"`
+	DB     *MySQLConfig `toml:"db"`
+	Listen string       `toml:"listen"`
+	Log    *log.Config  `toml:"log"`
+}
+
+type MySQLConfig struct {
+	DSN         string        `toml:"dsn"`
+	Active      int           `toml:"active"`
+	Idle        int           `toml:"idle"`
+	IdleTimeout time.Duration `toml:"idle_timeout"`
+}
+
+type Conifg struct {
+	Level  string `toml:"level"`
+	File   string `toml:"file"`
+	Stdout bool   `toml:"stdout"`
+}

+ 7 - 0
apiserver/config/config_test.go

@@ -0,0 +1,7 @@
+package config
+
+import "testing"
+
+func Testinit(t *testing.T) {
+	Init()
+}

+ 42 - 0
apiserver/dao/dao.go

@@ -0,0 +1,42 @@
+package dao
+
+import (
+	"time"
+
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/schema"
+	"kpt.notice/apiserver/config"
+	"kpt.notice/pkg/log"
+)
+
+type Dao struct {
+	DB *gorm.DB
+}
+
+func New(c *config.Config) (d *Dao) {
+	// dsn := "root:kepaiteng!QAZ@tcp(222.73.129.15:31306)/notice?charset=utf8&parseTime=True&loc=Local"
+	db, err := gorm.Open(mysql.Open(c.DB.DSN),
+		// db, err := gorm.Open(mysql.Open(dsn),
+		&gorm.Config{
+			NamingStrategy: schema.NamingStrategy{
+				SingularTable: true,
+			},
+		})
+
+	if err != nil || db == nil {
+		log.Errorf("gorm.Open error: %v===%v", err, c.DB.DSN)
+		return nil
+	}
+	sqlDB, _ := db.DB()
+	// SetMaxOpenConns 设置打开数据库连接的最大数量。
+	sqlDB.SetMaxOpenConns(c.DB.Active)
+	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
+	sqlDB.SetMaxIdleConns(c.DB.Idle)
+	// SetConnMaxLifetime 设置了连接可复用的最大时间。
+	sqlDB.SetConnMaxLifetime(c.DB.IdleTimeout * time.Second)
+	d = &Dao{
+		DB: db,
+	}
+	return
+}

+ 12 - 0
apiserver/model/http/http.go

@@ -0,0 +1,12 @@
+package http
+
+type Response struct {
+	Success bool        `json:"success"`
+	Code    int         `json:"code"`
+	Message string      `json:"message"`
+	Data    interface{} `json:"data"`
+}
+
+type Request interface {
+	Validate() error
+}

+ 19 - 0
apiserver/model/http/mcs.go

@@ -0,0 +1,19 @@
+package http
+
+type McsRequest struct {
+	Request bool    `json:"request"`
+	ID      int     `json:"id"`
+	Method  string  `json:"method"`
+	Data    McsData `json:"data"`
+}
+
+type McsData struct {
+	DevID    string `json:"devId"`
+	PeopleNo string `json:"peopleNo"`
+	WorkNo   string `json:"workNo"`
+	StorType int    `json:"storType"`
+}
+
+func (p *McsRequest) Validate() error {
+	return nil
+}

+ 42 - 0
apiserver/model/http/message.go

@@ -0,0 +1,42 @@
+package http
+
+type MessageResp struct {
+	Rows int `json:"rows"`
+}
+
+type MessageReq struct {
+	MsgTypeID   int         `json:"msg_type_id"`
+	Miniprogram Miniprogram `json:"miniprogram"`
+	Target      []string    `json:"target"`
+	Keys        []string    `json:"keys"`
+	Content     []Tag       `json:"content"`
+}
+type Miniprogram struct {
+	AppID    string `json:"appid"`
+	PagePath string `json:"pagepath"`
+}
+type Tag struct {
+	Value string `json:"value"`
+	Color string `json:"color"`
+}
+
+type MessageTypeReq struct {
+	ID           int    `json:"id"`
+	TypeName     string `json:"type_name"`
+	SysName      string `json:"sys_name"`
+	RemindTypeID int    `json:"remind_type_id"`
+	RemindType   string `json:"remind_type"`
+	PushDate     string `json:"push_date"`
+	PushTime     string `json:"push_time"`
+	IntervalTime int    `json:"interval_time"`
+	PushLimit    int    `json:"push_limit"`
+	TemplateID   string `json:"template_id"`
+}
+
+func (m MessageTypeReq) Validate() error {
+	return nil
+}
+
+func (p MessageReq) Validate() error {
+	return nil
+}

+ 17 - 0
apiserver/model/http/wxmsg.go

@@ -0,0 +1,17 @@
+package http
+
+type WxMessage struct {
+	XMLName      string `xml:"xml"`
+	Text         string `xml:"chardata"`
+	ToUserName   string `xml:"ToUserName"`
+	FromUserName string `xml:"FromUserName"`
+	CreateTime   string `xml:"CreateTime"`
+	MsgType      string `xml:"MsgType"`
+	Event        string `xml:"Event"`
+	EventKey     string `xml:"EventKey"`
+	Ticket       string `xml:"Ticket"`
+}
+
+func (p WxMessage) Validate() error {
+	return nil
+}

+ 32 - 0
apiserver/model/message.go

@@ -0,0 +1,32 @@
+package model
+
+import "time"
+
+type Message struct {
+	ID           int       `gorm:"primary_key;AUTO_INCREMENT"`
+	MsgTypeID    int       `gorm:"column:msg_type_id"`
+	RemindTypeID int       `gorm:"column:remind_type_id"`
+	MsgContent   string    `gorm:"column:msg_content"`
+	Target       string    `gorm:"column:target"`
+	CreatedAt    time.Time `gorm:"column:create_at"`
+	UpdateAt     time.Time `gorm:"column:update_at"`
+	status       int       `gorm:"column:status"`
+	PushCount    int       `gorm:"column:push_count"`
+	PushLimit    int       `gorm:"column:push_limit"`
+}
+
+type MessageType struct {
+	ID           int    `gorm:"primary_key;AUTO_INCREMENT"`
+	TypeName     string `gorm:"column:type_name"`
+	SysName      string `gorm:"column:sys_name"`
+	RemindTypeID int    `gorm:"column:remind_type_id"`
+	RemindType   string `gorm:"column:remind_type"`
+	// the method of message pushing
+	//  can set some different date,like monday,tuesday,wednesday,thursday,friday,saturday,sunday
+	PushDate string `gorm:"column:push_date"`
+	//  can set some different time,like 00:00:00,01:00:00,02:00:00,03:00:00,04:00:00,05:00:00,06:00:00,07:00:00,08:00:00,09:00:00,10:00:00,11:00:00,12:00:00,13:00:00,14:00:00,15:00:00,16:00:00,17:00:00,18:00:00,19:00:00,20:00:00,21:00:00,22:00:00,23:00:00
+	PushTime     string `gorm:"column:push_time"`
+	IntervalTime int    `gorm:"column:interval_time"`
+	PushLimit    int    `gorm:"column:push_limit"`
+	TemplateID   string `gorm:"column:template_id"`
+}

+ 1 - 0
apiserver/model/model.go

@@ -0,0 +1 @@
+package model

+ 6 - 0
apiserver/model/user.go

@@ -0,0 +1,6 @@
+package model
+
+type User struct {
+	Phone  string `gorm:"column:phone"`
+	Openid string `gorm:"column:openid"`
+}

+ 1 - 0
apiserver/server/mcs.go

@@ -0,0 +1 @@
+package server

+ 42 - 0
apiserver/server/message.go

@@ -0,0 +1,42 @@
+package server
+
+import (
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+	"kpt.notice/apiserver/model/http"
+)
+
+//消息存储
+func postMessage(c *gin.Context) {
+	msgReq := new(http.MessageReq)
+	if !Bind(c, msgReq) {
+		return
+	}
+	resp, err := svc.InsertMessage(msgReq)
+	eJSON(c, resp, err)
+}
+
+// save message type
+func postMsgType(c *gin.Context) {
+	msgTypeReq := new(http.MessageTypeReq)
+	if !Bind(c, msgTypeReq) {
+		return
+	}
+	res, err := svc.InsertMessageType(msgTypeReq)
+	eJSON(c, res, err)
+}
+
+// get all  message type
+func getMsgType(c *gin.Context) {
+	paramid := c.PostForm("id")
+	var id int
+	if paramid != "" {
+		id, _ = strconv.Atoi(paramid)
+	} else {
+		id = 0
+	}
+	res := svc.QueryMsgType(id)
+	eJSON(c, res, nil)
+
+}

+ 121 - 0
apiserver/server/server.go

@@ -0,0 +1,121 @@
+package server
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"kpt.notice/apiserver/config"
+	"kpt.notice/apiserver/model/http"
+	"kpt.notice/apiserver/service"
+	"kpt.notice/pkg/log"
+)
+
+var (
+	svc *service.Service
+)
+
+/*
+router init
+*/
+func Init(s *service.Service, conf *config.Config) {
+	svc = s
+	engine := gin.New()
+	engine.Use(gin.Recovery())
+	engine.Use(Cors())
+	route(engine)
+	if err := engine.Run(conf.Listen); err != nil {
+		panic(err)
+	}
+}
+
+/*
+	跨域
+*/
+func Cors() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		method := c.Request.Method               //请求方法
+		origin := c.Request.Header.Get("Origin") //请求头部
+		var headerKeys []string                  // 声明请求头keys
+		for k := range c.Request.Header {
+			headerKeys = append(headerKeys, k)
+		}
+
+		headerStr := strings.Join(headerKeys, ", ")
+		if headerStr != "" {
+			headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
+		} else {
+			headerStr = "access-control-allow-origin, access-control-allow-headers"
+		}
+		if origin != "" {
+			c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+			c.Header("Access-Control-Allow-Origin", "*")                                        // 这是允许访问所有域
+			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
+			//  header的类型
+			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, id,  Token, name, optname, thumbnail, session, X_Requested_With, Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
+			//              允许跨域设置                                                                                                      可以返回其他子段
+			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma, FooBar") // 跨域关键设置 让浏览器可以解析
+			c.Header("Access-Control-Max-Age", "172800")                                                                                                                                                                  // 缓存请求信息 单位为秒
+			c.Header("Access-Control-Allow-Credentials", "false")                                                                                                                                                         //  跨域请求是否需要带cookie信息 默认设置为true
+			c.Set("content-type", "application/json")                                                                                                                                                                     // 设置返回格式是json
+		}
+
+		//放行所有OPTIONS方法
+		if method == "OPTIONS" {
+			c.JSON(200, "Options Request!")
+		}
+		// 处理请求
+		c.Next() //  处理请求
+	}
+}
+
+/*
+	router  function list
+*/
+func route(e *gin.Engine) {
+	e.GET("/wx", tokenAuth) //wx office server address auth
+	// receive wx message ,include common message and event message
+	e.POST("/wx", wxMessage)
+	// create temporary wx code for bind wx account
+	e.GET("/wx/code", getCode)
+	//receive business message
+	e.POST("/notice/message", postMessage)
+	// query notice type
+	e.GET("/notice/msgtype", getMsgType)
+	// save notice type
+	e.POST("/notice/msgtype", postMsgType)
+}
+
+/*
+	http 响应
+*/
+func eJSON(c *gin.Context, data interface{}, err error) {
+	code := 200
+	success := true
+	message := "ok"
+	if err != nil {
+		code, success, data, message = 400, false, nil, err.Error()
+	}
+	resp := http.Response{
+		Code:    code,
+		Success: success,
+		Data:    data,
+		Message: message,
+	}
+	c.JSON(code, &resp)
+
+}
+
+/*
+requet validate
+*/
+func Bind(c *gin.Context, req http.Request) bool {
+	log.Errorf("cbody====%+v", c.Request.Body)
+	log.Errorf("receivebody====%+v", req)
+	if err := c.ShouldBindJSON(req); err != nil {
+		eJSON(c, nil, err)
+		return false
+	}
+	req.Validate()
+	return true
+}

+ 7 - 0
apiserver/server/server_test.go

@@ -0,0 +1,7 @@
+package server
+
+import "testing"
+
+func TestXxx(t *testing.T) {
+
+}

+ 77 - 0
apiserver/server/wxoffice.go

@@ -0,0 +1,77 @@
+package server
+
+import (
+	"crypto/sha1"
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"sort"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+
+	"kpt.notice/apiserver/service"
+	"kpt.notice/middleware/notice"
+)
+
+// func getMsgType(c *gin.Context) {
+// 	name := c.PostForm("remind_type")
+// 	id, err := svc.QueryRemindTypeID(name)
+// 	eJSON(c, id, err)
+// }
+
+/*
+	获取临时二维码
+*/
+func getCode(c *gin.Context) {
+	sceneID := c.PostForm("scenne_id")
+	fmt.Printf("scenne_id=======%+v\n", sceneID)
+	c.String(200, sceneID)
+	resp := service.CreateQRCode(sceneID)
+	if resp == nil {
+		c.String(500, "error")
+		return
+	}
+	c.Data(200, "image/jpg", resp)
+}
+
+// 服务器地址认证
+func tokenAuth(c *gin.Context) {
+	req := c.Request
+	timestamp := req.FormValue("timestamp")
+	nonce := req.FormValue("nonce")
+	signnature := req.FormValue("signature")
+	echostr := req.FormValue("echostr")
+	token := "123"
+	arr := []string{token, timestamp, nonce}
+	sort.Strings(arr)
+	str := strings.Join(arr, "")
+	h := sha1.New()
+	h.Write([]byte(str))
+	sha1str := hex.EncodeToString(h.Sum(nil))
+	if sha1str == signnature {
+		c.String(200, echostr)
+	}
+
+}
+
+/*
+	接收微信服务器推送的消息
+*/
+func wxMessage(c *gin.Context) {
+	// wxmsg := new(http.WxMessage)
+	// if !Bind(c, wxmsg) {
+	// 	return
+	// }
+	body, err := ioutil.ReadAll(c.Request.Body)
+	if err != nil {
+		c.String(400, err.Error())
+	}
+	output := service.ReceiveMessage(body)
+	if output == nil {
+		c.String(400, "error")
+	} else {
+		c.JSON(200, output)
+		notice.ServerAcc.Server.Response(c.Writer, c.Request, output)
+	}
+}

+ 132 - 0
apiserver/service/job.go

@@ -0,0 +1,132 @@
+package service
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/robfig/cron"
+	"kpt.notice/middleware/notice"
+)
+
+func JobInit() {
+	c := cron.New()
+	// 公众号消息
+	c.AddFunc("30 * * * * *", SendInstantMsg)
+	c.AddFunc("00 30 09 * * *", SendDelayMsg)
+	c.AddFunc("00 30 09 * * *", SendPeriodMsg)
+	// c.Start()
+}
+
+// period message sending to wx
+func SendPeriodMsg() {
+	fmt.Printf("send period msg\n")
+	//query messages from db
+	msgs, err := svc.getMsgByType(3)
+
+	//read  message list
+	for _, val := range msgs {
+		fmt.Printf("message++++++++%#v\n", val)
+
+		data := make(map[string]interface{})
+		json.Unmarshal([]byte(val.MsgContent), &data)
+		targets := make([]string, 10)
+		json.Unmarshal([]byte(val.Target), &targets)
+
+		// fmt.Printf("data1=%v", data)
+		// sli := strings.Split(val.Target, ",")
+		log.Default().Printf("%#v\n", targets)
+		// users, err := svc.QueryOpenid(targets)
+		if err != nil {
+			panic(err)
+		}
+		for _, openid := range targets {
+			data["touser"] = openid
+			log.Default().Printf("data2++++++++++%v\n", data)
+			jsonstr, _ := json.Marshal(data)
+			log.Default().Printf("jsonstr++++++++\n%v", string(jsonstr))
+			resp, tmperr := notice.MsgTemplate(jsonstr)
+			log.Default().Printf("wxresp=========%#v\n", string(resp))
+			if tmperr != nil {
+				log.Default().Printf("wxresp=========%#v\n", string(resp))
+			}
+		}
+	}
+}
+
+// send delay message
+func SendDelayMsg() {
+	fmt.Printf("send delay msg\n")
+	msgs, err := svc.getMsgByType(2)
+	if err != nil {
+	}
+	for _, val := range msgs {
+		fmt.Printf("message++++++++%#v\n", val)
+
+		data := make(map[string]interface{})
+		json.Unmarshal([]byte(val.MsgContent), &data)
+		targets := make([]string, 10)
+		json.Unmarshal([]byte(val.Target), &targets)
+		log.Default().Printf("%#v\n", targets)
+		// users, err := svc.QueryOpenid(targets)
+		if err != nil {
+			panic(err)
+		}
+		for _, openid := range targets {
+			data["touser"] = openid
+			jsonstr, _ := json.Marshal(data)
+			resp, tmperr := notice.MsgTemplate(jsonstr)
+			log.Default().Printf("wxresp=========%#v\n", string(resp))
+			if tmperr == nil {
+				m := map[string]interface{}{
+					"update_at":  time.Now(),
+					"push_count": val.PushCount + 1,
+				}
+				if val.PushCount+1 == val.PushLimit {
+					m["status"] = 1
+				}
+				svc.UpdateMessage(val.RemindTypeID, val.ID, m)
+			}
+		}
+	}
+}
+
+//instant message sending to wx
+func SendInstantMsg() {
+	fmt.Printf("send instant msg\n")
+	//query messages from db
+	msgs, err := svc.getMsgByType(1)
+	if err != nil {
+	}
+	//read  message list
+	for _, val := range msgs {
+		fmt.Printf("message++++++++%#v\n", val)
+
+		data := make(map[string]interface{})
+		json.Unmarshal([]byte(val.MsgContent), &data)
+		targets := make([]string, 10)
+		json.Unmarshal([]byte(val.Target), &targets)
+
+		// fmt.Printf("data1=%v", data)
+		// sli := strings.Split(val.Target, ",")
+		log.Default().Printf("%#v\n", targets)
+		// users, err := svc.QueryOpenid(targets)
+		if err != nil {
+			panic(err)
+		}
+		for _, openid := range targets {
+			data["touser"] = openid
+			log.Default().Printf("data2++++++++++%v\n", data)
+			jsonstr, _ := json.Marshal(data)
+			log.Default().Printf("jsonstr++++++++\n%v", string(jsonstr))
+			resp, tmperr := notice.MsgTemplate(jsonstr)
+			log.Default().Printf("wxresp=========%#v\n", string(resp))
+			if tmperr == nil {
+				svc.UpdateMessage(val.RemindTypeID, val.ID, map[string]interface{}{
+					"update_at": time.Now(),
+				})
+			}
+		}
+	}
+}

+ 145 - 0
apiserver/service/message.go

@@ -0,0 +1,145 @@
+package service
+
+import (
+	"errors"
+
+	"gorm.io/gorm"
+	"kpt.notice/apiserver/model"
+	"kpt.notice/apiserver/model/http"
+	"kpt.notice/pkg/log"
+	"kpt.notice/pkg/util"
+)
+
+func (svc *Service) UpdateMessage(statics int, id int, val map[string]interface{}) (err error) {
+
+	var res *gorm.DB
+	if statics == 1 {
+		// update update_at
+		res = svc.DB.Model(&model.Message{}).Where("id= ?", id).Updates(val)
+	}
+	if statics == 2 {
+		// update push_count,update_at,status
+		res = svc.DB.Model(&model.Message{}).Where("id= ?", id).Updates(val)
+	}
+	if statics == 3 {
+		// update status
+		res = svc.DB.Model(&model.Message{}).Where("id= ?", id).Updates(val)
+	}
+
+	if res.Error != nil {
+		return res.Error
+	}
+	return
+}
+
+// send  message
+//type statics ,1:instant,
+func (svc *Service) getMsgByType(statics int) (msgs []model.Message, err error) {
+	// instant message
+	var sql string
+	if statics == 1 {
+		sql = `select * from message m join message_type mt on m.msg_type_id = mt.id 
+		where m.status = 1 and mt.remind_type_id = 1 and timestampdiff(second,m.update_at,now())>7200`
+		err = svc.DB.Exec(sql).Find(&msgs).Error
+		// err = svc.DB.Where("msg_type_id =? and status =0 and timestampdiff(second,update_at,now())>7200", 1).Find(&msgs).Error
+	}
+	if statics == 2 {
+		sql = `select m.* from message m join message_type mt on m.msg_type_id = mt.id 
+		where m.status = 1 and mt.remind_type_id = 2 and timestampdiff(hour,m.create_at,now())>=48 
+		and timestampdiff(hour,m.update_at,now())>=24 and m.push_count mt.notice_count`
+	}
+	if statics == 3 {
+		sql = `select * from message m join message_type mt on m.msg_type_id = mt.id 
+		where m.status = 1 and mt.remind_type_id = 3 
+        and    (   mt.push_date=concat('w', weekday(now() ) +1)   mt.push_date=concat('w', day(now() ) +1)  )`
+	}
+	return
+}
+func (svc *Service) InsertMessage(msg *http.MessageReq) (resp *http.MessageResp, err error) {
+	log.Error("InsertMessage=============enter")
+	tmp := make(map[string]interface{})
+	tmp["miniprogram"] = msg.Miniprogram
+	mt := svc.QueryMsgType(msg.MsgTypeID)
+	if mt == nil {
+		log.Errorf("InsertMessage=====QueryMsgType====%v", errors.New("没有匹配的模板"))
+		return nil, errors.New("没有匹配的模板")
+	}
+	tmp["template_id"] = mt[0].TemplateID
+	data := make(map[string]http.Tag)
+	for i, v := range msg.Keys {
+		data[v] = msg.Content[i]
+	}
+	tmp["data"] = data
+	msgDb := model.Message{
+		Target:       util.MarshalToString(msg.Target),
+		MsgTypeID:    msg.MsgTypeID,
+		RemindTypeID: mt[0].RemindTypeID,
+		MsgContent:   util.MarshalToString(tmp),
+	}
+
+	result := svc.DB.Create(&msgDb)
+	if result.Error != nil {
+		log.Errorf("InsertMessage=====Create%v", result.Error)
+		return nil, result.Error
+	}
+	resp = &http.MessageResp{
+		Rows: int(result.RowsAffected),
+	}
+	err = result.Error
+	return
+}
+
+// query the id or remind_type of the message  by name
+func (svc *Service) QueryRemindTypeID(name string) (int, error) {
+	m := new(model.MessageType)
+	tx := svc.DB.Where("type_name = ?").First(m)
+	if tx.RowsAffected == 0 {
+		return 0, errors.New("没有匹配的类型")
+	}
+	return m.ID, nil
+}
+
+func (svc *Service) InsertMessageType(req *http.MessageTypeReq) (m *model.MessageType, err error) {
+	m = &model.MessageType{
+		SysName:      req.SysName,
+		TypeName:     req.TypeName,
+		RemindTypeID: req.RemindTypeID,
+		RemindType:   req.RemindType,
+		PushDate:     req.PushDate,
+		PushTime:     req.PushTime,
+		IntervalTime: req.IntervalTime,
+		PushLimit:    req.PushLimit,
+		TemplateID:   req.TemplateID,
+	}
+	result := svc.DB.Create(m)
+	if result.Error != nil {
+		return nil, result.Error
+	}
+	return m, nil
+
+}
+
+// func (svc *Service) queryMsgType(id int) (m *model.MessageType, err error) {
+// 	if tx.RowsAffected == 0 {
+// 		return nil, errors.New("没有匹配的模板")
+// 	}
+// 	return m, nil
+// }
+func (svc *Service) QueryMsgType(id int) (m []model.MessageType) {
+	log.Error("QueryMsgType=============enter")
+	log.Errorf("QueryMsgType========params:%v", id)
+	var tx *gorm.DB
+	if id > 0 {
+		tx = svc.DB.Where("id = ?", id).First(&m)
+		log.Errorf("QueryMsgType========sql:%v", tx)
+	} else if id == 0 {
+		tx = svc.DB.Find(m)
+	}
+
+	if tx.RowsAffected == 0 {
+		log.Errorf("QueryMsgType=====%v", errors.New("没有匹配的模板"))
+		return nil
+	}
+	log.Error("QueryMsgType=====out=====")
+	return m
+}

+ 98 - 0
apiserver/service/send_test.go

@@ -0,0 +1,98 @@
+package service
+
+import (
+	"testing"
+
+	"kpt.notice/apiserver/config"
+	"kpt.notice/apiserver/model"
+	"kpt.notice/pkg/log"
+)
+
+func TestReceiveMessage(t *testing.T) {
+	config.Init()
+	log.Init(config.Conf.Log)
+	New(config.Conf)
+	log.Error("ReceiveMessage=====================")
+	b := []byte(`<xml>
+		<ToUserName><![CDATA[oLd1b56PwpexCa0QK4NCkza9TKyY]]></ToUserName>
+		<FromUserName><![CDATA[oLd1b56PwpexCa0QK4NCkza9TKyY]]></FromUserName>
+		<CreateTime>123456789</CreateTime>
+		<MsgType><![CDATA[event]]></MsgType>
+		<Event><![CDATA[subscribe]]></Event>
+		<EventKey><![CDATA[qrscene_tmr.shengmu23]]></EventKey>
+		<Ticket><![CDATA[TICKET]]></Ticket>
+	  </xml>`)
+
+	ReceiveMessage(b)
+}
+func testInit() *Service {
+	config.Init()
+	log.Init(config.Conf.Log)
+	return New(config.Conf)
+}
+func TestInsertMessage(t *testing.T) {
+	svc := testInit()
+	for i := 1; i < 3; i++ {
+		log.Error("i=======", i)
+		var m []model.MessageType
+		switch {
+		case i == 1:
+			defer svc.QueryMsgType(1)
+			{
+				if err := recover(); err != nil {
+					log.Errorf("%v", err)
+				} else {
+					m = svc.QueryMsgType(1)
+				}
+			}
+		case i == 2:
+			svc.DB.Where("id = ?", 1).Find(&m)
+		}
+		log.Errorf("%#v", m)
+	}
+	// file, err := ioutil.ReadFile("/Users/desire/kptdev/msg_push/json/message.json")
+	// if err != nil || len(file) == 0 {
+	// 	panic(err)
+	// }
+
+	// req := new(http.MessageReq)
+	// json.Unmarshal([]byte(file), req)
+	// // log.Errorf("%#v", req)
+	// svc.InsertMessage(req)
+}
+
+func TestCreateQRcode(t *testing.T) {
+	// resp, err := CreateQRCode("")
+	// if err == nil {
+	// 	m := make(map[string]string)
+	// 	json.Unmarshal(resp, &m)
+	// 	// t.Errorf("%#v", m["expire_seconds"])
+	// 	t.Errorf("%v", string(resp))
+	// 	t.Errorf("%v\n", url.QueryEscape(m["ticket"]))
+	// } else {
+	// 	t.Errorf("%#v", err)
+	// }
+}
+func TestGetMsgByType(t *testing.T) {
+	// res, err := getMsgByType(1)
+	// if err != nil {
+	// 	t.Error(err)
+	// }
+	// t.Errorf("%#v", res)
+}
+func TestUpdateStatus(t *testing.T) {
+	// GormDB()
+	// UpdateMessage(20)
+
+}
+func TestMsgSend(t *testing.T) {
+	// GormDB()
+	SendInstantMsg()
+
+}
+
+func TestQueryOpenid(t *testing.T) {
+	// GormDB()
+	// res, _ := QueryOpenid([]string{"15800900542"})
+	// t.Errorf("%#v", res)
+}

+ 23 - 0
apiserver/service/service.go

@@ -0,0 +1,23 @@
+package service
+
+import (
+	"gorm.io/gorm"
+	"kpt.notice/apiserver/config"
+	"kpt.notice/apiserver/dao"
+)
+
+var svc *Service
+
+type Service struct {
+	DB *gorm.DB
+}
+
+func New(c *config.Config) (s *Service) {
+	dao := dao.New(c)
+	s = &Service{
+		DB: dao.DB,
+	}
+	svc = s
+	JobInit()
+	return
+}

+ 14 - 0
apiserver/service/user.go

@@ -0,0 +1,14 @@
+package service
+
+import "kpt.notice/apiserver/model"
+
+func (svc *Service) InsertUser(phone, openid string) (err error) {
+	sql := `insert user (openid,phone) values(?,?) ON DUPLICATE KEY UPDATE phone=? `
+	return svc.DB.Exec(sql, openid, phone, phone).Error
+}
+
+func (svc *Service) QueryOpenid(phones []string) (users []model.User, err error) {
+
+	err = svc.DB.Where("phone in ?", phones).Find(&users).Error
+	return
+}

+ 126 - 0
apiserver/service/wxoffce.go

@@ -0,0 +1,126 @@
+package service
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/fastwego/offiaccount/type/type_event"
+	"github.com/fastwego/offiaccount/type/type_message"
+	"kpt.notice/middleware/notice"
+	"kpt.notice/pkg/http"
+	"kpt.notice/pkg/log"
+	"kpt.notice/pkg/util"
+)
+
+// 获取公众号临时二维码
+func CreateQRCode(sceneID string) []byte {
+	var resp []byte
+	if resp = notice.CreateQRCode(sceneID); resp != nil {
+		return nil
+	}
+	fmt.Printf("%#v\n", resp)
+	m := make(map[string]string)
+	json.Unmarshal(resp, &m)
+	m = map[string]string{
+		"ticket": m["ticket"],
+	}
+	url := "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+	res := http.HttpGet(url, m)
+	if res == nil {
+		return nil
+	}
+	clientRespBody, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		log.Errorf("ioutil.ReadAll error: %+v", err)
+		return nil
+	}
+	return clientRespBody
+}
+func ReceiveMessage(body []byte) (output *type_message.ReplyMessageText) {
+	log.Error("ReceiveMessage=====================enter")
+	msg, err := notice.ServerAcc.Server.ParseXML(body)
+	if err != nil {
+		log.Errorf("ReceiveMessage==parse xml error: %v", err)
+		output = nil
+	}
+	switch msg.(type) {
+	case type_message.MessageText:
+		p := msg.(type_message.MessageText)
+		output = ReceiveText(&p)
+	case type_event.EventSubscribe:
+		p := msg.(type_event.EventSubscribe)
+		output = ReceiveEvent(&p)
+	default:
+		output = nil
+	}
+	return
+}
+
+/*
+	接收微信服务器推送的事件
+*/
+func ReceiveEvent(msg *type_event.EventSubscribe) (output *type_message.ReplyMessageText) {
+	log.Errorf("ReceiveEvent======\n")
+	switch msg.Event.Event {
+	case "subscribe":
+		key := string(type_message.CDATA(msg.EventKey))
+		scene_id := strings.Split(key, "qrscene_")
+		m := map[string]interface{}{
+			"userinfo": scene_id[1],
+			"openid":   msg.FromUserName,
+		}
+		json.Marshal(m)
+		url := "http://kpttest.kptyun.com/userwxopenid/binding"
+		resp := http.HttpPost(url, "application/json", util.MapToString(m))
+		respContent := "公众号绑定系统成功"
+		if resp == nil {
+			respContent = "公众号绑定系统失败,请联系管理员"
+		}
+		output = WxResponse(type_message.CDATA(msg.FromUserName), type_message.CDATA(msg.ToUserName))
+		output.Content = type_message.CDATA(respContent)
+		return
+	default:
+		return nil
+	}
+}
+
+/*
+	接收微信服务器推送的文本消息
+*/
+func ReceiveText(msg *type_message.MessageText) (resp *type_message.ReplyMessageText) {
+	log.Errorf("ReceiveText: %+v", msg)
+	conText := string(type_message.CDATA(msg.Content))
+	openid := string(type_message.CDATA(msg.FromUserName))
+	//update openid of message in db
+	err := svc.InsertUser(conText, openid)
+	var respText string = "手机号绑定成功,欢迎使用"
+	if err != nil {
+		respText = "手机号绑定失败,请重新输入"
+	}
+
+	resp = WxResponse(type_message.CDATA(msg.FromUserName), type_message.CDATA(msg.ToUserName))
+	resp.Content = type_message.CDATA(respText)
+	return
+}
+func WxResponse(from, to type_message.CDATA) *type_message.ReplyMessageText {
+	return &type_message.ReplyMessageText{
+		ReplyMessage: type_message.ReplyMessage{
+			ToUserName:   from,
+			FromUserName: to,
+			CreateTime:   strconv.FormatInt(time.Now().Unix(), 10),
+			MsgType:      type_message.ReplyMsgTypeText,
+		},
+	}
+}
+
+/*
+	发送模板消息
+*/
+// func sendMessage(c *gin.Context) {
+// 	svc.SendInstantMsg()
+
+// }

+ 22 - 0
client/client.go

@@ -0,0 +1,22 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"strings"
+)
+
+func BindUserWithWX(url, contentType, body string) (*http.Response, error) {
+	// body := `{"userinfo":"tmr.shengmu23.bbc","openid":"123"}`
+	// url :="http://kpttest.kptyun.com/userwxopenid/binding"
+	// contentType:="application/json"
+	resp, err := http.Post(url, contentType, strings.NewReader(body))
+	if err != nil {
+		log.Default().Printf("%+v\n", err)
+		return nil, err
+	}
+	defer resp.Body.Close()
+	fmt.Printf("%+v\n", resp)
+	return resp, nil
+}

+ 8 - 0
cmd/apiserver/conf.toml

@@ -0,0 +1,8 @@
+listen = "0.0.0.0:8089"
+[db]
+dsn = "root:kepaiteng!QAZ@tcp(222.73.129.15:31306)/notice?charset=utf8&parseTime=True&loc=Local"
+idle = 5
+idle_timeout = 3600
+[log]
+stdout = true
+level = "info"

binární
cmd/apiserver/main


+ 25 - 0
cmd/apiserver/main.go

@@ -0,0 +1,25 @@
+package main
+
+import (
+	"flag"
+
+	"kpt.notice/apiserver/config"
+	"kpt.notice/pkg/log"
+
+	"kpt.notice/apiserver/server"
+	"kpt.notice/apiserver/service"
+)
+
+func main() {
+	flag.Parse()
+	// init the config
+	config.Init()
+	//log init
+	log.Init(config.Conf.Log)
+	// service components init
+	svc := service.New(config.Conf)
+
+	// server components init
+	server.Init(svc, config.Conf)
+	//job init
+}

+ 20 - 0
cmd/client/main.go

@@ -0,0 +1,20 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+func main() {
+	body := `{"userinfo":"tmr.shengmu23.bbc","openid":"123"}`
+	resp, err := http.Post("http://kpttest.kptyun.com/userwxopenid/binding",
+		"application/json", strings.NewReader(body))
+
+	if err != nil {
+		panic(err)
+	}
+	defer resp.Body.Close()
+	fmt.Printf("%+v\n", resp)
+
+}

+ 10 - 0
doc/doc.md

@@ -0,0 +1,10 @@
+# notice
+## server http interface
+### no1.CreateWXcode
+method:get
+url:/wx/code
+param:scene_id=tmr.userid.username
+return:image
+### no2
+## client http
+

+ 38 - 0
doc/json/message.json

@@ -0,0 +1,38 @@
+{
+    "msg_type_id": 1,
+    "target": [
+        "15800900542"
+    ],
+    "miniprogram": {
+        "appid": "wx9ab2b5b25701da0a",
+        "pagepath": "pages/index/index?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjAwMDI4IiwicGFzc3dvcmQiOiJlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSIsImV4cCI6MTY1NDIzMDMxMywiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2twdHl1bi9nby1hZG1pbi8ifQ.jKLR74kTy9tXqFH5OwlwK7zTGKvMVbrOecsaJpxbxq8"
+    },
+    "keys": [
+        "first",
+        "keyword1",
+        "keyword2",
+        "keyword3",
+        "remark"
+    ],
+    "content": [
+        {
+            "value": "混料"
+        },
+        {
+            "value": "2014年9月22日",
+            "color": "#173177"
+        },
+        {
+            "value": "test0601",
+            "color": "#173177"
+        },
+        {
+            "value": "tmr名称:23牧场搅拌站\n饲料名称:青木",
+            "color": "#173177"
+        },
+         {
+            "value": "remark",
+            "color": "#173177"
+        }
+    ]
+}

+ 11 - 0
doc/json/message_type.json

@@ -0,0 +1,11 @@
+{
+    "sys_name":"tmrwatch",
+    "type_name":"contact approve",
+    "remind_type_id":0,
+    "remind_type":"立即",
+    "push_date":"w1,w3",
+    "push_time":"09:30",
+    "interval_time":3600,
+    "push_limit":1,
+    "template_id":"2"
+}

+ 11 - 0
doc/json/message_type_test.json

@@ -0,0 +1,11 @@
+
+{
+    "sys_name": "tmrWatch",
+    "type_name": "11",
+    "remind_type_id": 1,
+    "remind_type": "立即",
+    "push_date": "",
+    "interval_time": 0,
+    "push_limit": 1,
+    "template_id": "BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo"
+}

+ 8 - 0
doc/sql/alter.sql

@@ -0,0 +1,8 @@
+-- alter table msg_type 
+-- modify column `remind_type_id` int(11) unsigned not null default '0' FIRST remind_type;
+-- modify column `remind_type_id` int(11)  FIRST remind_type
+-- drop index u_msg_type on message_type
+
+alter table message
+-- add push_count int(11) unsigned not null default '0' AFTER `status`;
+add remind_type_id int(11) unsigned not null default '0' AFTER `status`;

+ 6 - 0
doc/sql/insert.sql

@@ -0,0 +1,6 @@
+insert into message_type
+     (sys_name,type_name,remind_type_id,remind_type,push_date,push_time,interval_time,notice_count,template_id)
+values  
+    ('tmrwatch','合同审批',1,'立即','0','0','2','0','BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo'),
+    ('tmrwatch','客户回访未完成',2,'延时','48','9:30','24',3,'BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo'),
+    ('tmrwatch','合同金额超期未收',3,'指定周期','w1,w3,w5','9:30','0',3,'BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo')

+ 13 - 0
doc/sql/msg_push.sql

@@ -0,0 +1,13 @@
+
+drop table if exists `msg_push`.`message`;
+create table `msg_push`.`message` (
+    `id` int(11) not null auto_increment,
+    `sys_name` varchar(64) not null default '',
+    `remind_type` varchar(64) not null default '',
+    `target` varchar(64) not null default '',
+    `content` text not null ,
+    `status` int(11) not null default 0,
+    `create_at` DATETIME not null default CURRENT_TIMESTAMP,
+    `update_at` DATETIME not null ON UPDATE CURRENT_TIMESTAMP ,
+     primary key(`id`)
+)

+ 36 - 0
doc/sql/notice_init.sql

@@ -0,0 +1,36 @@
+create database if not exists `notice` default character set utf8 collate utf8_general_ci;
+DROP TABLE IF EXISTS `notice`.`msg_type`;
+create table `notice`.`message_type`(
+    `id` int(11) unsigned not null auto_increment,
+    `sys_name` varchar(64) not null default '',
+    `type_name` varchar(64) not null default '',
+    `remind_type_id` int(11) unsigned not null default 0,
+    `remind_type` varchar(64) not null default '',
+    `push_date` varchar(64) not null default '',
+    `push_time` varchar(64) not null default '' comment '推送时间,different type have different format',
+    `interval_time` int(11) not null default 0 comment '提醒间隔时间,单位hour',
+    `push_limit` int(11) not null default 0 ,
+    `template_id` varchar(64) not null default '',
+    primary key (`id`),
+    UNIQUE key `u_msg_type` (`remind_type`)
+
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '消息类型';
+drop table if exists `notice`.`message`;
+create table `notice`.`message`(
+    `id` int(11) not null auto_increment,
+    `msg_type_id` int(11) not null default 0,
+    `msg_content` text not null ,
+    `target` varchar(64) not null default '',
+    `push_count` int(11) not null default 0,
+    `push_limit` int(11) not null default 0,
+    `status` int(11) not null default 0,
+    `create_at` DATETIME not null default CURRENT_TIMESTAMP,
+    `update_at` DATETIME not null ON UPDATE CURRENT_TIMESTAMP ,
+    primary key(`id`)
+)ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '消息';
+-- create table `user`(
+--     `id` int(11) not null auto_increment,
+--     openid varchar(64) not null default '',
+--     phone varchar(64) not null default '',
+--     primary key (`id`)
+-- )ENGINE=InnoDB DEFAULT CHARSET=utf8 comment='用户表';

+ 10 - 0
doc/sql/query.sql

@@ -0,0 +1,10 @@
+-- select m.* from message m join message_type mt on m.msg_type_id = mt.id 
+-- 		where m.status = 1 and mt.remind_type_id = 2 and timestampdiff(hour,m.create_at,now())>=48 
+-- 		and timestampdiff(hour,m.update_at,now())>=24 and m.push_count < mt.notice_count;
+select * from message m join message_type mt on m.msg_type_id = mt.id 
+		where m.status = 1 and mt.remind_type_id = 3 
+        and    (  find_in_set( mt.push_date,concat('w', weekday(now() ) +1) )or
+        find_in_set(  mt.push_date,concat('w', day(now() ) +1)  ))
+        
+
+

+ 9 - 0
doc/xml/wxevent.xml

@@ -0,0 +1,9 @@
+<xml>
+		<ToUserName><![CDATA[oLd1b56PwpexCa0QK4NCkza9TKyY]]></ToUserName>
+		<FromUserName><![CDATA[oLd1b56PwpexCa0QK4NCkza9TKyY]]></FromUserName>
+		<CreateTime>123456789</CreateTime>
+		<MsgType><![CDATA[event]]></MsgType>
+		<Event><![CDATA[subscribe]]></Event>
+		<EventKey><![CDATA[qrscene_tmr.shengmu23.123]]></EventKey>
+		<Ticket><![CDATA[TICKET]]></Ticket>
+	  </xml>

+ 36 - 0
go.mod

@@ -0,0 +1,36 @@
+module kpt.notice
+
+go 1.17
+
+require (
+	github.com/BurntSushi/toml v1.1.0
+	github.com/fastwego/offiaccount v1.0.1
+	github.com/gin-gonic/gin v1.7.7
+	github.com/pkg/errors v0.9.1
+	github.com/robfig/cron v1.2.0
+	gorm.io/driver/mysql v1.3.3
+	gorm.io/gorm v1.23.5
+)
+
+require (
+	github.com/faabiosr/cachego v0.15.0 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.13.0 // indirect
+	github.com/go-playground/universal-translator v0.17.0 // indirect
+	github.com/go-playground/validator/v10 v10.4.1 // indirect
+	github.com/go-sql-driver/mysql v1.6.0 // indirect
+	github.com/golang/protobuf v1.4.2 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.4 // indirect
+	github.com/json-iterator/go v1.1.9 // indirect
+	github.com/leodido/go-urn v1.2.0 // indirect
+	github.com/mattn/go-isatty v0.0.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+	github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
+	github.com/stretchr/testify v1.7.0 // indirect
+	github.com/ugorji/go/codec v1.1.7 // indirect
+	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
+	golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
+	google.golang.org/protobuf v1.23.0 // indirect
+	gopkg.in/yaml.v2 v2.3.0 // indirect
+)

+ 140 - 0
go.sum

@@ -0,0 +1,140 @@
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/faabiosr/cachego v0.15.0 h1:IqcDhvzMbL4a1c9Dek88DIWJYQ5HG//L0PKCReneOA4=
+github.com/faabiosr/cachego v0.15.0/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI=
+github.com/fastwego/offiaccount v1.0.1 h1:tmzc29OjteqZoAqFT9WckT+AHINmUDLJhwLHVRx8wm8=
+github.com/fastwego/offiaccount v1.0.1/go.mod h1:8roSt8OhE2CtdkKOqZnmUdjmmEaoJ2k//Bav/+4BrJQ=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
+github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
+github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/iancoleman/strcase v0.1.1/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
+github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+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=
+go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8=
+gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
+gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
+gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

binární
main


+ 4 - 0
middleware/notice/dding.go

@@ -0,0 +1,4 @@
+package notice
+
+type DDing struct {
+}

+ 33 - 0
middleware/notice/notice_test.go

@@ -0,0 +1,33 @@
+package notice
+
+import (
+	"net/url"
+	"testing"
+
+	"github.com/fastwego/offiaccount/apis/user"
+)
+
+func TestInfo(t *testing.T) {
+	// res, _ := GetUserInfo()
+	params := url.Values{}
+	params.Add("openid", "oLd1b56PwpexCa0QK4NCkza9TKyY")
+	res, _ := user.Get(ServerAcc, params)
+	t.Error(string(res))
+}
+
+func TestSendMsg(t *testing.T) {
+	json := `{"data":{"first":"恭喜你购买成功!","keyword1":{"value":"巧克力"},
+	"keyword2":"39.8元","keyword3":"2014年9月22日","remark":"欢迎再次购买!"},
+	"template_id":"BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo",
+	"touser":"oLd1b56PwpexCa0QK4NCkza9TKyY"}`
+	// json2 := `{
+	// 	"touser":"oLd1b56PwpexCa0QK4NCkza9TKyY",
+	// 	"template_id":"BtkN1rWKOJtKP0C64lGxIrPzLRFsYFas-4gupX2-pFo",
+	// 	"data":{
+
+	// 	}
+	// }`
+	if _, err := MsgTemplate([]byte(json)); err != nil {
+		t.Error(err)
+	}
+}

+ 57 - 0
middleware/notice/wxoffice.go

@@ -0,0 +1,57 @@
+package notice
+
+import (
+	"github.com/fastwego/offiaccount"
+	"github.com/fastwego/offiaccount/apis/account"
+	"github.com/fastwego/offiaccount/apis/message/template"
+	"kpt.notice/pkg/log"
+)
+
+type wxMessage struct {
+	MsgType string `json:"msgtype"`
+	Content string `json:"content"`
+}
+
+var ServerAcc *offiaccount.OffiAccount = offiaccount.New(offiaccount.Config{
+	Appid:          "wxe1cc563ba393dd1a",
+	Secret:         "25e56243da9581eab6f4d67a12ef4658",
+	Token:          "123",
+	EncodingAESKey: "6yYJ4sS5y1hJgvIXEqavV2rmCutyXkywndxUQFgX54f",
+})
+
+func CreateQRCode(sceneID string) []byte {
+	// time limit in 3 days
+	json := `{
+		"action_name": "QR_SCENE", 
+		"expire_seconds":259200,
+		"action_info": {
+			"scene": {
+				"scene_id":` + sceneID + `
+			}
+		}`
+
+	resp, err := account.CreateQRCode(ServerAcc, []byte(json))
+	if err != nil {
+		log.Error(err)
+		return nil
+	}
+	return resp
+}
+
+/*
+send tmp message to wx
+*/
+func MsgTemplate(msg []byte) ([]byte, error) {
+	return template.Send(ServerAcc, msg)
+}
+
+// func OfficeInit() {
+// 	if ServerAcc == nil {
+// 		ServerAcc = offiaccount.New(offiaccount.Config{
+// 			Appid:          "wxe1cc563ba393dd1a",
+// 			Secret:         "25e56243da9581eab6f4d67a12ef4658",
+// 			Token:          "123",
+// 			EncodingAESKey: "6yYJ4sS5y1hJgvIXEqavV2rmCutyXkywndxUQFgX54f",
+// 		})
+// 	}
+// }

+ 53 - 0
pkg/http/client.go

@@ -0,0 +1,53 @@
+package http
+
+import (
+	"net/http"
+	"strings"
+	"time"
+
+	"kpt.notice/pkg/log"
+)
+
+type Client struct {
+	client *http.Client
+}
+
+func NewClient(timeout time.Duration) *Client {
+	return &Client{
+		client: &http.Client{
+			Timeout: timeout,
+		},
+	}
+}
+
+func HttpPost(url, contentType, body string) *http.Response {
+	// body := `{"userinfo":"tmr.shengmu23.bbc","openid":"123"}`
+	// url :="http://kpttest.kptyun.com/userwxopenid/binding"
+	// contentType:="application/json"
+	log.Errorf("HttpPost====enter body=======%+v\n", body)
+	resp, err := http.Post(url, contentType, strings.NewReader(body))
+	if err != nil || resp.StatusCode != 200 {
+		log.Errorf("post  err ====%+v===%+v\n", err, resp.StatusCode)
+		return nil
+	}
+	defer resp.Body.Close()
+	log.Errorf("resp=====%+v\n", resp.StatusCode)
+	return resp
+}
+func HttpGet(url string, params map[string]string) *http.Response {
+	// url :="http://kpttest.kptyun.com/userwxopenid/binding"
+	// contentType:="application/json"
+	// params := map[string]string{"userinfo":"tmr.shengmu23.bbc","openid":"123"}
+	s := make([]string, len(params))
+	for k, v := range params {
+		s = append(s, k+"="+v)
+	}
+	url = url + "?" + strings.Join(s, "&")
+	resp, err := http.Get(url)
+	if err != nil || resp.StatusCode != 200 {
+		return nil
+	}
+	defer resp.Body.Close()
+	return resp
+
+}

+ 72 - 0
pkg/log/file.go

@@ -0,0 +1,72 @@
+package log
+
+import (
+	"fmt"
+	stdlog "log"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+const (
+	dailyRolling = "2006-01-02"
+)
+
+type fileHandler struct {
+	l *stdlog.Logger
+
+	f        *os.File
+	basePath string
+	filePath string
+	fileFrag string
+}
+
+func NewFileHandler(basePath string) Handler {
+	if _, file := filepath.Split(basePath); file == "" {
+		panic("invalid base path")
+	}
+	l := stdlog.New(nil, "", stdlog.LstdFlags|stdlog.Lshortfile)
+	f := &fileHandler{l: l, basePath: basePath}
+	if err := f.roll(); err != nil {
+		panic(err)
+	}
+	return f
+}
+
+func (r *fileHandler) Log(level, msg string) {
+	_ = r.roll()
+	_ = r.l.Output(5, fmt.Sprintf("[%s] %s", level, msg))
+}
+
+func (r *fileHandler) Close() error {
+	if r.f != nil {
+		return r.f.Close()
+	}
+	return nil
+}
+
+func (r *fileHandler) roll() error {
+	suffix := time.Now().Format(dailyRolling)
+	if r.f != nil {
+		if suffix == r.fileFrag {
+			return nil
+		}
+		r.f.Close()
+		r.f = nil
+	}
+	r.fileFrag = suffix
+	r.filePath = fmt.Sprintf("%s.%s", r.basePath, r.fileFrag)
+
+	if dir, _ := filepath.Split(r.basePath); dir != "" && dir != "." {
+		if err := os.MkdirAll(dir, 0777); err != nil {
+			return err
+		}
+	}
+	f, err := os.OpenFile(r.filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+	if err != nil {
+		return err
+	}
+	r.f = f
+	r.l.SetOutput(f)
+	return nil
+}

+ 25 - 0
pkg/log/handler.go

@@ -0,0 +1,25 @@
+package log
+
+import "github.com/pkg/errors"
+
+type Handler interface {
+	Log(level, msg string)
+	Close() error
+}
+
+type Handlers []Handler
+
+func (hs Handlers) Log(level, msg string) {
+	for _, h := range hs {
+		h.Log(level, msg)
+	}
+}
+
+func (hs Handlers) Close() (err error) {
+	for _, h := range hs {
+		if err = h.Close(); err != nil {
+			err = errors.WithStack(err)
+		}
+	}
+	return
+}

+ 138 - 0
pkg/log/log.go

@@ -0,0 +1,138 @@
+package log
+
+import (
+	"flag"
+	"fmt"
+)
+
+var (
+	h Handler
+
+	level  string
+	file   string
+	stdout bool
+
+	defaultLevel int
+
+	levelIndex = map[string]int{
+		"debug": debugLevel,
+		"DEBUG": debugLevel,
+		"info":  infoLevel,
+		"INFO":  infoLevel,
+		"warn":  warnLevel,
+		"WARN":  warnLevel,
+		"error": errorLevel,
+		"ERROR": errorLevel,
+	}
+)
+
+const (
+	debugLevel = iota
+	infoLevel
+	warnLevel
+	errorLevel
+)
+
+func init() {
+	flag.StringVar(&level, "level", "", "log level")
+	flag.StringVar(&file, "file", "", "log will print into file {log}")
+	flag.BoolVar(&stdout, "stdout", true, "log will print into stdout")
+}
+
+type Config struct {
+	Level  string `toml:"level"`
+	File   string `toml:"file"`
+	Stdout bool   `toml:"stdout"`
+}
+
+func Init(c *Config) {
+	if c == nil {
+		c = &Config{
+			Level:  "info",
+			Stdout: true,
+		}
+	}
+	if file != "" {
+		c.File = file
+	}
+	if level != "" {
+		c.Level = level
+	}
+	var ok bool
+	defaultLevel, ok = levelIndex[c.Level]
+	if !ok {
+		defaultLevel = infoLevel
+	}
+
+	var handlers []Handler
+	if c.Stdout && stdout {
+		handlers = append(handlers, NewStdHandler())
+	}
+	if c.File != "" {
+		handlers = append(handlers, NewFileHandler(c.File))
+	}
+	h = Handlers(handlers)
+}
+
+func Info(args ...interface{}) {
+	logs("INFO", args...)
+}
+
+func Infof(format string, args ...interface{}) {
+	logf("INFO", format, args...)
+}
+
+func Warn(args ...interface{}) {
+	logs("WARN", args...)
+}
+
+func Warnf(format string, args ...interface{}) {
+	logf("WARN", format, args...)
+}
+
+func Error(args ...interface{}) {
+	logs("ERROR", args...)
+}
+
+func Errorf(format string, args ...interface{}) {
+	logf("ERROR", format, args...)
+}
+
+func Panic(v interface{}) {
+	panic(v)
+}
+
+func Panicf(format string, args ...interface{}) {
+	panic(fmt.Sprintf(format, args...))
+}
+
+func logf(lv, format string, args ...interface{}) {
+	if h == nil {
+		return
+	}
+	if levelIndex[lv] < defaultLevel {
+		return
+	}
+	msg := format
+	if len(args) > 0 {
+		msg = fmt.Sprintf(format, args...)
+	}
+	h.Log(lv, msg)
+}
+
+func logs(lv string, args ...interface{}) {
+	if h == nil {
+		return
+	}
+	if levelIndex[lv] < defaultLevel {
+		return
+	}
+	h.Log(lv, fmt.Sprint(args...))
+}
+
+func Close() (err error) {
+	if h == nil {
+		return
+	}
+	return h.Close()
+}

+ 27 - 0
pkg/log/stdout.go

@@ -0,0 +1,27 @@
+package log
+
+import (
+	"fmt"
+	stdlog "log"
+	"os"
+)
+
+// stdoutHandler stdout log handler
+type stdoutHandler struct {
+	out *stdlog.Logger
+}
+
+// NewStdHandler create a stdout log handler
+func NewStdHandler() Handler {
+	return &stdoutHandler{out: stdlog.New(os.Stdout, "", stdlog.LstdFlags|stdlog.Lshortfile)}
+}
+
+// Log stdout loging
+func (h *stdoutHandler) Log(level, msg string) {
+	_ = h.out.Output(5, fmt.Sprintf("[%s] %s", level, msg))
+}
+
+// Close stdout loging
+func (h *stdoutHandler) Close() (err error) {
+	return
+}

+ 1 - 0
pkg/setting/setting.go

@@ -0,0 +1 @@
+package setting

+ 20 - 0
pkg/util/util.go

@@ -0,0 +1,20 @@
+package util
+
+import (
+	"encoding/json"
+	"log"
+)
+
+func MarshalToString(in interface{}) string {
+	out, _ := json.Marshal(in)
+	return string(out)
+}
+
+func MapToString(in map[string]interface{}) string {
+	out, err := json.Marshal(in)
+	if err != nil {
+		log.Default().Printf("%+v\n", err)
+		return ""
+	}
+	return string(out)
+}