mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: remove wechat bot, replace with api callback for receive wechat payment transactions
This commit is contained in:
		@@ -186,6 +186,7 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc {
 | 
			
		||||
		if c.Request.URL.Path == "/api/user/login" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/admin/login" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/user/register" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/reward/push" ||
 | 
			
		||||
			strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
 | 
			
		||||
			strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
 | 
			
		||||
			strings.HasPrefix(c.Request.URL.Path, "/static/") ||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ func NewDefaultConfig() *types.AppConfig {
 | 
			
		||||
			SameSite:  http.SameSiteLaxMode,
 | 
			
		||||
		},
 | 
			
		||||
		ApiConfig:           types.ChatPlusApiConfig{},
 | 
			
		||||
		StartWechatBot: false,
 | 
			
		||||
		ChatPlusExtApiToken: utils.RandString(32),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ type AppConfig struct {
 | 
			
		||||
	ApiConfig           ChatPlusApiConfig // ChatPlus API authorization configs
 | 
			
		||||
	AesEncryptKey       string
 | 
			
		||||
	SmsConfig           AliYunSmsConfig // AliYun send message service config
 | 
			
		||||
	StartWechatBot bool            // 是否启动微信机器人
 | 
			
		||||
	ChatPlusExtApiToken string          // chatgpt-plus-exts callback api token
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChatPlusApiConfig struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,12 @@ go 1.19
 | 
			
		||||
require (
 | 
			
		||||
	github.com/BurntSushi/toml v1.1.0
 | 
			
		||||
	github.com/aliyun/alibaba-cloud-sdk-go v1.62.405
 | 
			
		||||
	github.com/eatmoreapple/openwechat v1.2.1
 | 
			
		||||
	github.com/gin-contrib/sessions v0.0.5
 | 
			
		||||
	github.com/gin-gonic/gin v1.9.1
 | 
			
		||||
	github.com/gorilla/websocket v1.5.0
 | 
			
		||||
	github.com/imroc/req/v3 v3.37.2
 | 
			
		||||
	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0
 | 
			
		||||
	github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
 | 
			
		||||
	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 | 
			
		||||
	github.com/syndtr/goleveldb v1.0.0
 | 
			
		||||
	go.uber.org/zap v1.23.0
 | 
			
		||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,6 @@ 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/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
 | 
			
		||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 | 
			
		||||
github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU=
 | 
			
		||||
github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 | 
			
		||||
@@ -140,8 +138,6 @@ github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62po
 | 
			
		||||
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
 | 
			
		||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
 | 
			
		||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
			
		||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,50 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
 | 
			
		||||
	return &h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *RewardHandler) Push(c *gin.Context) {
 | 
			
		||||
	token := c.GetHeader("X-TOKEN")
 | 
			
		||||
	if token != h.App.Config.ChatPlusExtApiToken {
 | 
			
		||||
		resp.NotAuth(c)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		TransId string  `json:"trans_id"` // 微信转账交易 ID
 | 
			
		||||
		Amount  float64 `json:"amount"`   // 微信转账交易金额
 | 
			
		||||
		Remark  string  `json:"remark"`   // 转账备注
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if data.Amount <= 0 {
 | 
			
		||||
		resp.ERROR(c, "Amount should not be 0")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Infof("收到众筹收款信息: %+v", data)
 | 
			
		||||
	var item model.Reward
 | 
			
		||||
	res := h.db.Where("tx_id = ?", data.TransId).First(&item)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		resp.ERROR(c, "当前交易 ID 己经存在!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = h.db.Create(&model.Reward{
 | 
			
		||||
		TxId:   data.TransId,
 | 
			
		||||
		Amount: data.Amount,
 | 
			
		||||
		Remark: data.Remark,
 | 
			
		||||
		Status: false,
 | 
			
		||||
	})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		logger.Errorf("交易保存失败: %v", res.Error)
 | 
			
		||||
		resp.ERROR(c, "交易保存失败")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify 打赏码核销
 | 
			
		||||
func (h *RewardHandler) Verify(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								api/main.go
									
									
									
									
									
								
							@@ -6,7 +6,6 @@ import (
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/handler/admin"
 | 
			
		||||
	logger2 "chatplus/logger"
 | 
			
		||||
	"chatplus/modules/wexin"
 | 
			
		||||
	"chatplus/service"
 | 
			
		||||
	"chatplus/service/function"
 | 
			
		||||
	"chatplus/store"
 | 
			
		||||
@@ -104,17 +103,6 @@ func main() {
 | 
			
		||||
			return xdb.NewWithBuffer(cBuff)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		// 创建微信机器人
 | 
			
		||||
		fx.Provide(wexin.NewWeChatBot),
 | 
			
		||||
		fx.Invoke(func(bot *wexin.WeChatBot) {
 | 
			
		||||
			go func() {
 | 
			
		||||
				err := bot.Login()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		// 创建函数
 | 
			
		||||
		fx.Provide(func(config *types.AppConfig) (function.FuncZaoBao, error) {
 | 
			
		||||
			return function.NewZaoBao(config.ApiConfig), nil
 | 
			
		||||
@@ -192,6 +180,7 @@ func main() {
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.RewardHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/reward/")
 | 
			
		||||
			group.POST("push", h.Push)
 | 
			
		||||
			group.POST("verify", h.Verify)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
package wexin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"github.com/eatmoreapple/openwechat"
 | 
			
		||||
	"github.com/skip2/go-qrcode"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MessageHandler 消息处理
 | 
			
		||||
func MessageHandler(msg *openwechat.Message, db *gorm.DB) {
 | 
			
		||||
	sender, err := msg.Sender()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// 只处理微信支付的推送消息
 | 
			
		||||
	if sender.NickName == "微信支付" ||
 | 
			
		||||
		msg.MsgType == openwechat.MsgTypeApp ||
 | 
			
		||||
		msg.AppMsgType == openwechat.AppMsgTypeUrl {
 | 
			
		||||
		// 解析支付金额
 | 
			
		||||
		message, err := parseTransactionMessage(msg.Content)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			transaction := extractTransaction(message)
 | 
			
		||||
			logger.Infof("解析到收款信息:%+v", transaction)
 | 
			
		||||
			if transaction.Amount <= 0 {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			var item model.Reward
 | 
			
		||||
			res := db.Where("tx_id = ?", transaction.TransId).First(&item)
 | 
			
		||||
			if res.Error == nil {
 | 
			
		||||
				logger.Infof("当前交易 ID %s 己经存在!", transaction.TransId)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			res = db.Create(&model.Reward{
 | 
			
		||||
				TxId:   transaction.TransId,
 | 
			
		||||
				Amount: transaction.Amount,
 | 
			
		||||
				Remark: transaction.Remark,
 | 
			
		||||
				Status: false,
 | 
			
		||||
			})
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Errorf("交易保存失败,ID: %s", transaction.TransId)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// QrCodeCallBack 登录扫码回调,
 | 
			
		||||
func QrCodeCallBack(uuid string) {
 | 
			
		||||
	logger.Info("请使用微信扫描下面二维码登录")
 | 
			
		||||
	q, _ := qrcode.New("https://login.weixin.qq.com/l/"+uuid, qrcode.Medium)
 | 
			
		||||
	logger.Info(q.ToString(true))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
package wexin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Message 转账消息
 | 
			
		||||
type Message struct {
 | 
			
		||||
	XMLName xml.Name `xml:"msg"`
 | 
			
		||||
	AppMsg  struct {
 | 
			
		||||
		Des string `xml:"des"`
 | 
			
		||||
		Url string `xml:"url"`
 | 
			
		||||
	} `xml:"appmsg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Transaction 解析后的交易信息
 | 
			
		||||
type Transaction struct {
 | 
			
		||||
	TransId string  `json:"trans_id"` // 微信转账交易 ID
 | 
			
		||||
	Amount  float64 `json:"amount"`   // 微信转账交易金额
 | 
			
		||||
	Remark  string  `json:"remark"`   // 转账备注
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析微信转账消息
 | 
			
		||||
func parseTransactionMessage(xmlData string) (*Message, error) {
 | 
			
		||||
	var msg Message
 | 
			
		||||
	if err := xml.Unmarshal([]byte(xmlData), &msg); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &msg, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 导出交易信息
 | 
			
		||||
func extractTransaction(message *Message) Transaction {
 | 
			
		||||
	var tx = Transaction{}
 | 
			
		||||
	// 导出交易金额和备注
 | 
			
		||||
	lines := strings.Split(message.AppMsg.Des, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		line = strings.TrimSpace(line)
 | 
			
		||||
		if len(line) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// 解析收款金额
 | 
			
		||||
		prefix := "收款金额¥"
 | 
			
		||||
		if strings.HasPrefix(line, prefix) {
 | 
			
		||||
			if value, err := strconv.ParseFloat(line[len(prefix):], 64); err == nil {
 | 
			
		||||
				tx.Amount = value
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// 解析收款备注
 | 
			
		||||
		prefix = "付款方备注"
 | 
			
		||||
		if strings.HasPrefix(line, prefix) {
 | 
			
		||||
			tx.Remark = line[len(prefix):]
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 解析交易 ID
 | 
			
		||||
	index := strings.Index(message.AppMsg.Url, "trans_id=")
 | 
			
		||||
	if index != -1 {
 | 
			
		||||
		end := strings.LastIndex(message.AppMsg.Url, "&")
 | 
			
		||||
		tx.TransId = strings.TrimSpace(message.AppMsg.Url[index+9 : end])
 | 
			
		||||
	}
 | 
			
		||||
	return tx
 | 
			
		||||
}
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
package wexin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
	logger2 "chatplus/logger"
 | 
			
		||||
	"github.com/eatmoreapple/openwechat"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 微信收款机器人服务
 | 
			
		||||
var logger = logger2.GetLogger()
 | 
			
		||||
 | 
			
		||||
type WeChatBot struct {
 | 
			
		||||
	bot       *openwechat.Bot
 | 
			
		||||
	db        *gorm.DB
 | 
			
		||||
	appConfig *types.AppConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWeChatBot(db *gorm.DB, config *types.AppConfig) *WeChatBot {
 | 
			
		||||
	bot := openwechat.DefaultBot(openwechat.Desktop)
 | 
			
		||||
	// 注册消息处理函数
 | 
			
		||||
	bot.MessageHandler = func(msg *openwechat.Message) {
 | 
			
		||||
		MessageHandler(msg, db)
 | 
			
		||||
	}
 | 
			
		||||
	// 注册登陆二维码回调
 | 
			
		||||
	bot.UUIDCallback = QrCodeCallBack
 | 
			
		||||
	return &WeChatBot{
 | 
			
		||||
		bot:       bot,
 | 
			
		||||
		db:        db,
 | 
			
		||||
		appConfig: config,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *WeChatBot) Login() error {
 | 
			
		||||
	if !b.appConfig.StartWechatBot {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建热存储容器对象
 | 
			
		||||
	reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
 | 
			
		||||
	// 执行热登录
 | 
			
		||||
	err := b.bot.HotLogin(reloadStorage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("login error: %v", err)
 | 
			
		||||
		return b.bot.Login()
 | 
			
		||||
	}
 | 
			
		||||
	logger.Info("微信登录成功!")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +20,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	fmt.Println(utils.RandString(32))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Http client 取消操作
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,11 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/aes"
 | 
			
		||||
	"crypto/cipher"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AesEncrypt 加密
 | 
			
		||||
@@ -68,3 +71,14 @@ func pkcs7UnPadding(data []byte) ([]byte, error) {
 | 
			
		||||
	unPadding := int(data[length-1])
 | 
			
		||||
	return data[:(length - unPadding)], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Sha256(data string) string {
 | 
			
		||||
	hash := sha256.New()
 | 
			
		||||
	_, err := io.WriteString(hash, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hashValue := hash.Sum(nil)
 | 
			
		||||
	return fmt.Sprintf("%x", hashValue)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user