huangjiajun 1 år sedan
förälder
incheckning
2299e8f77e
11 ändrade filer med 322 tillägg och 23 borttagningar
  1. +34
    -0
      app/db/model/sys_module.go
  2. +36
    -0
      app/hdl/hdl_img_upload.go
  3. +19
    -0
      app/hdl/hdl_wx.go
  4. +49
    -13
      app/md/md_wechat_menu.go
  5. +4
    -4
      app/router/router.go
  6. +29
    -4
      app/svc/svc_wechat.go
  7. +66
    -0
      app/svc/svc_wechat_reply.go
  8. +0
    -1
      app/utils/cache/redis.go
  9. +84
    -1
      app/utils/wx.go
  10. +1
    -0
      go.mod
  11. Binär
     

+ 34
- 0
app/db/model/sys_module.go Visa fil

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

import (
"time"
)

type SysModule struct {
ModId int `json:"mod_id" xorm:"not null pk autoincr INT(10)"`
ModPid int `json:"mod_pid" xorm:"not null default 0 comment('父级模块ID') INT(10)"`
TemplateId int `json:"template_id" xorm:"not null default 0 comment('模板ID') INT(11)"`
ModName string `json:"mod_name" xorm:"not null default '' comment('模块名称') VARCHAR(250)"`
Position string `json:"position" xorm:"not null default '' comment('位置') VARCHAR(250)"`
SkipIdentifier string `json:"skip_identifier" xorm:"not null default '' comment('跳转标识') VARCHAR(250)"`
Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(128)"`
Subtitle string `json:"subtitle" xorm:"not null default '' comment('副标题') VARCHAR(255)"`
Url string `json:"url" xorm:"not null default '' comment('跳转链接') VARCHAR(512)"`
Margin string `json:"margin" xorm:"not null default '0,0,0,0' comment('边距,上右下左') VARCHAR(64)"`
AspectRatio string `json:"aspect_ratio" xorm:"not null default 0.00 comment('宽高比,宽/高保留两位小数') DECIMAL(4,2)"`
Icon string `json:"icon" xorm:"not null default '' comment('图标') VARCHAR(512)"`
Img string `json:"img" xorm:"not null default '' comment('图片') VARCHAR(512)"`
FontColor string `json:"font_color" xorm:"not null default '' comment('文字颜色') VARCHAR(128)"`
BgImg string `json:"bg_img" xorm:"not null default '' comment('背景图片') VARCHAR(512)"`
BgColor string `json:"bg_color" xorm:"not null default '' comment('背景颜色') VARCHAR(512)"`
BgColorT string `json:"bg_color_t" xorm:"not null default '' comment('背景颜色过度') VARCHAR(255)"`
Badge string `json:"badge" xorm:"not null default '' comment('badge图片') VARCHAR(512)"`
Path string `json:"path" xorm:"not null default '' comment('跳转路径') VARCHAR(255)"`
Data string `json:"data" xorm:"comment('内容') TEXT"`
Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"`
State int `json:"state" xorm:"not null default 1 comment('0不显示,1显示') TINYINT(1)"`
IsGlobal int `json:"is_global" xorm:"not null default 0 comment('是否全局显示') TINYINT(1)"`
Platform int `json:"platform" xorm:"not null default 1 comment('平台;1:全平台;2:App应用(ios和android);3:H5(wap);4:微信小程序;5:抖音小程序;6:百度小程序') TINYINT(1)"`
CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"`
UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"`
}

+ 36
- 0
app/hdl/hdl_img_upload.go Visa fil

@@ -0,0 +1,36 @@
package hdl

import (
"applet/app/e"
"applet/app/svc"
"applet/app/utils"
"fmt"
"github.com/gin-gonic/gin"
"github.com/tidwall/gjson"
)

func ImgUpload(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
e.OutErr(c, 400, e.NewErr(400, "上传图片失败"))
return
}
fileStr := "./public/img/" + file.Filename
c.SaveUploadedFile(file, fileStr)
res := map[string]string{
"fileName": "http://ywym.jiaxiandingding.top/public/img/" + file.Filename,
"saveName": "public/img/" + file.Filename,
}
token, err := svc.GetWechatToken()
if err != nil {
e.OutErr(c, 400, err.Error())
return
}
uri := "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token" + token + "&type=image"
postFile, err := utils.PostFile("media", res["fileName"], uri)
fmt.Println(postFile)
fmt.Println(err)
res["media_id"] = gjson.Get(string(postFile), "media_id").String()
e.OutSuc(c, res, nil)
return
}

+ 19
- 0
app/hdl/hdl_wx.go Visa fil

@@ -5,13 +5,16 @@ import (
"applet/app/db/model"
"applet/app/enum"
"applet/app/md"
"applet/app/svc"
"applet/app/utils"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"github.com/tidwall/gjson"
"log"
"net/http"
"strings"
"time"

"github.com/gin-gonic/gin"
@@ -110,6 +113,22 @@ func WXMsgReceive(c *gin.Context) {
WXMsgReply(c, eventMsg.ToUserName, eventMsg.FromUserName, err.Error())
}
}
if eventMsg.Event == "click" { //公众号点击
if strings.Contains(strings.ToLower(eventMsg.EventKey), "official_account_custom_reply") {
id := strings.ReplaceAll(strings.ToLower(eventMsg.EventKey), "official_account_custom_reply_", "")
var mod model.SysModule
db.Db.Where("mod_id=?", id).Get(&mod)
if mod.Data == "" {
return
}
if gjson.Get(mod.Data, "msgType").String() == "text" {
svc.WXMsgTextReply(c, msg.ToUserName, msg.FromUserName, gjson.Get(mod.Data, "text.content").String())
}
if gjson.Get(mod.Data, "msgType").String() == "image" {
svc.WXMsgImageReply(c, msg.ToUserName, msg.FromUserName, gjson.Get(mod.Data, "image.mediaId").String())
}
}
}
}
if msg.MsgType == "text" {
//文本类型消息


+ 49
- 13
app/md/md_wechat_menu.go Visa fil

@@ -1,21 +1,37 @@
package md

type WechatButton struct {
Name string `json:"name"`
Type string `json:"type"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key,omitempty"`
SubButton []WechatSubButton `json:"sub_button,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key,omitempty"`
SubButton []WechatSubButton `json:"sub_button,omitempty"`
ReplyContent []ReplyContent `json:"replyContent"`
}
type WechatSubButton struct {
Type string `json:"type"`
Name string `json:"name"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key,omitempty"`
Type string `json:"type"`
Name string `json:"name"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key"`
ReplyContent []ReplyContent `json:"replyContent"`
Value string `json:"value"`
}
type ReplyContent struct {
Text ReplyContentText `json:"text"`
MsgType string `json:"msgType"`
Image ReplyContentImg `json:"image"`
}
type ReplyContentText struct {
Content string `json:"content"`
}
type ReplyContentImg struct {
MediaId string `json:"mediaId"`
MediaUrl string `json:"mediaUrl"`
Media string `json:"media"`
}
type OffcialWechatButton struct {
Type string `json:"type,omitempty"`
@@ -33,3 +49,23 @@ type SubButtonMap struct {
type WechatReq struct {
Button []WechatButton `json:"button"`
}
type WechatParam struct {
Button []WechatButtonParam `json:"button"`
}
type WechatButtonParam struct {
Name string `json:"name"`
Type string `json:"type"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key,omitempty"`
SubButton []WechatSubButtonParam `json:"sub_button,omitempty"`
}
type WechatSubButtonParam struct {
Type string `json:"type"`
Name string `json:"name"`
Url string `json:"url"`
Appid string `json:"appid,omitempty"`
Pagepath string `json:"pagepath"`
Key string `json:"key"`
}

+ 4
- 4
app/router/router.go Visa fil

@@ -44,7 +44,7 @@ func Init() *gin.Engine {
func route(r *gin.RouterGroup) {
r.Any("/demo", hdl.Demo)
r.POST("/login", hdl.Login)
r.POST("/img_upload", hdl.ImgUpload)
r.Group("/wx")
{
r.Use(mw.DB)
@@ -61,12 +61,12 @@ func route(r *gin.RouterGroup) {
r.Use(mw.Checker) // 以下接口需要检查Header: platform
{
}

r.GET("/wechat_menu/get", hdl.GetMenu)
r.POST("/wechat_menu/set", hdl.SetMenu)
r.GET("/qrcodeBatchDownload", hdl.QrcodeBatchDownload) //二维码批次-下载
r.Use(mw.Auth) // 以下接口需要JWT验证
{
r.GET("/wechat_menu/get", hdl.GetMenu)
r.POST("/wechat_menu/set", hdl.SetMenu)

r.GET("/userInfo", hdl.UserInfo) //用户信息
r.GET("/sysCfg", hdl.GetSysCfg) //基础配置-获取
r.POST("/sysCfg", hdl.SetSysCfg) //基础配置-设置


+ 29
- 4
app/svc/svc_wechat.go Visa fil

@@ -2,6 +2,7 @@ package svc

import (
"applet/app/db"
"applet/app/db/model"
"applet/app/e"
"applet/app/enum"
"applet/app/md"
@@ -11,7 +12,9 @@ import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/tidwall/gjson"
"time"
)

func GetMenu(c *gin.Context) {
@@ -63,12 +66,10 @@ func GetMenu(c *gin.Context) {
}
sysCfgDb.SysCfgUpdate("wechat_menu", utils.SerializeStr(menuList))
}

replyContentSet := []map[string]string{
{"msgType": "text", "name": "文本"},
{"msgType": "image", "name": "图片"},
{"msgType": "video", "name": "视频"},
{"msgType": "voice", "name": "语音"},
{"msgType": "article", "name": "文章"},
}
res := map[string]interface{}{
"button": menuList,
@@ -76,6 +77,7 @@ func GetMenu(c *gin.Context) {
}
e.OutSuc(c, res, nil)
}

func SetMenu(c *gin.Context) {
var args md.WechatReq
if err := c.ShouldBindJSON(&args); err != nil {
@@ -90,7 +92,19 @@ func SetMenu(c *gin.Context) {
e.OutErr(c, 400, err.Error())
return
}
menu, err := utils.SetWechatSelfMenu(token, args)
var param md.WechatParam
copier.Copy(&param, &args)
for k, v := range param.Button {
if v.Type == "click" {
param.Button[k].Key = commSetModule(utils.SerializeStr(args.Button[k].ReplyContent))
}
for k1, v1 := range v.SubButton {
if v1.Type == "click" {
param.Button[k].SubButton[k1].Key = commSetModule(utils.SerializeStr(args.Button[k].SubButton[k1].ReplyContent))
}
}
}
menu, err := utils.SetWechatSelfMenu(token, param)
if err != nil {
e.OutErr(c, 400, err.Error())
return
@@ -102,6 +116,17 @@ func SetMenu(c *gin.Context) {
e.OutSuc(c, "success", nil)
return
}
func commSetModule(modData string) string {
mod := &model.SysModule{
ModName: "official_account_custom_reply",
SkipIdentifier: "pub.flutter.official_account_custom_reply",
Title: "公众号菜单点击事件回复消息",
Data: modData,
CreateAt: time.Now(),
}
db.Db.Insert(mod)
return "official_account_custom_reply" + "_" + utils.IntToStr(mod.ModId)
}

func GetWechatToken() (string, error) {
sysCfgDb := db.SysCfgDb{}


+ 66
- 0
app/svc/svc_wechat_reply.go Visa fil

@@ -0,0 +1,66 @@
package svc

import (
"encoding/xml"
"github.com/gin-gonic/gin"
"log"
"time"
)

// WXRepTextMsg 微信回复文本消息结构体
type WXRepTextMsg struct {
ToUserName string
FromUserName string
CreateTime int64
MsgType string
Content string
// 若不标记XMLName, 则解析后的xml名为该结构体的名称
XMLName xml.Name `xml:"xml"`
}

// WXRepImageMsg 微信回复图片消息结构体
type WXRepImageMsg struct {
ToUserName string
FromUserName string
CreateTime int64
MsgType string
Image struct {
MediaId string
}
// 若不标记XMLName, 则解析后的xml名为该结构体的名称
XMLName xml.Name `xml:"xml"`
}

// WXMsgTextReply 微信消息回复
func WXMsgTextReply(c *gin.Context, fromUser, toUser, content string) {
repTextMsg := WXRepTextMsg{
ToUserName: toUser,
FromUserName: fromUser,
CreateTime: time.Now().Unix(),
MsgType: "text",
Content: content,
}

msg, err := xml.Marshal(&repTextMsg)
if err != nil {
log.Printf("[消息回复] - 将对象进行XML编码出错: %v\n", err)
return
}
_, _ = c.Writer.Write(msg)
}
func WXMsgImageReply(c *gin.Context, fromUser, toUser, content string) {
repTextMsg := WXRepImageMsg{
ToUserName: toUser,
FromUserName: fromUser,
CreateTime: time.Now().Unix(),
MsgType: "image",
Image: struct{ MediaId string }{MediaId: content},
}

msg, err := xml.Marshal(&repTextMsg)
if err != nil {
log.Printf("[消息回复] - 将对象进行XML编码出错: %v\n", err)
return
}
_, _ = c.Writer.Write(msg)
}

+ 0
- 1
app/utils/cache/redis.go Visa fil

@@ -48,7 +48,6 @@ func NewRedis(addr string) {
Wait: true,
Dial: func() (redigo.Conn, error) {
c, err := redigo.Dial("tcp", addr,
redigo.DialPassword(redisPassword),
redigo.DialConnectTimeout(redisDialTTL),
redigo.DialReadTimeout(redisReadTTL),
redigo.DialWriteTimeout(redisWriteTTL),


+ 84
- 1
app/utils/wx.go Visa fil

@@ -2,9 +2,15 @@ package utils

import (
"applet/app/md"
"bytes"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"sort"
"strings"
)
@@ -42,10 +48,87 @@ func GetWechatSelfMenu(token string) (string, error) {
return string(get), err
}

func SetWechatSelfMenu(token string, args md.WechatReq) (string, error) {
func SetWechatSelfMenu(token string, args md.WechatParam) (string, error) {
str := SerializeStr(args)
str = strings.ReplaceAll(str, "\\u0026", "&")
fmt.Println(str)
get, err := CurlPost("https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+token, str, nil)
return string(get), err
}
func UploadWxImg(token string, args md.WechatParam) (string, error) {
str := SerializeStr(args)
str = strings.ReplaceAll(str, "\\u0026", "&")
fmt.Println(str)
get, err := CurlPost("https://api.weixin.qq.com/cgi-bin/material/add_material?access_token"+token+"&type=image", str, nil)
return string(get), err
}
func PostFile(fieldname, filename, uri string) ([]byte, error) {
fields := []MultipartFormField{
{
IsFile: true,
Fieldname: fieldname,
Filename: filename,
},
}
return PostMultipartForm(fields, uri)
}

//MultipartFormField 保存文件或其他字段信息
type MultipartFormField struct {
IsFile bool
Fieldname string
Value []byte
Filename string
}

//PostMultipartForm 上传文件或其他多个字段
func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) {
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)

for _, field := range fields {
if field.IsFile {
fileWriter, e := bodyWriter.CreateFormFile(field.Fieldname, field.Filename)
if e != nil {
err = fmt.Errorf("error writing to buffer , err=%v", e)
return
}

fh, e := os.Open(field.Filename)
if e != nil {
err = fmt.Errorf("error opening file , err=%v", e)
return
}
defer fh.Close()

if _, err = io.Copy(fileWriter, fh); err != nil {
return
}
} else {
partWriter, e := bodyWriter.CreateFormField(field.Fieldname)
if e != nil {
err = e
return
}
valueReader := bytes.NewReader(field.Value)
if _, err = io.Copy(partWriter, valueReader); err != nil {
return
}
}
}

contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()

resp, e := http.Post(uri, contentType, bodyBuf)
if e != nil {
err = e
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, err
}
respBody, err = ioutil.ReadAll(resp.Body)
return
}

+ 1
- 0
go.mod Visa fil

@@ -20,6 +20,7 @@ require (
github.com/golang/snappy v0.0.3 // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/sessions v1.2.1 // indirect
github.com/jinzhu/copier v0.3.5
github.com/json-iterator/go v1.1.10 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect


Binär
Visa fil


Laddar…
Avbryt
Spara