feat: remove wechat bot, replace with api callback for receive wechat payment transactions

This commit is contained in:
RockYang 2023-08-10 17:24:23 +08:00
parent 2107c13b3d
commit b0e02b43fc
12 changed files with 76 additions and 204 deletions

View File

@ -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/") ||

View File

@ -33,8 +33,8 @@ func NewDefaultConfig() *types.AppConfig {
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
},
ApiConfig: types.ChatPlusApiConfig{},
StartWechatBot: false,
ApiConfig: types.ChatPlusApiConfig{},
ChatPlusExtApiToken: utils.RandString(32),
}
}

View File

@ -6,19 +6,19 @@ import (
)
type AppConfig struct {
Path string `toml:"-"`
Listen string
Session Session
ProxyURL string
MysqlDns string // mysql 连接地址
Manager Manager // 后台管理员账户信息
StaticDir string // 静态资源目录
StaticUrl string // 静态资源 URL
Redis RedisConfig // redis 连接信息
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
AesEncryptKey string
SmsConfig AliYunSmsConfig // AliYun send message service config
StartWechatBot bool // 是否启动微信机器人
Path string `toml:"-"`
Listen string
Session Session
ProxyURL string
MysqlDns string // mysql 连接地址
Manager Manager // 后台管理员账户信息
StaticDir string // 静态资源目录
StaticUrl string // 静态资源 URL
Redis RedisConfig // redis 连接信息
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
AesEncryptKey string
SmsConfig AliYunSmsConfig // AliYun send message service config
ChatPlusExtApiToken string // chatgpt-plus-exts callback api token
}
type ChatPlusApiConfig struct {

View File

@ -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

View File

@ -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=

View File

@ -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 {

View File

@ -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)
}),

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -20,6 +20,7 @@ import (
)
func main() {
fmt.Println(utils.RandString(32))
}
// Http client 取消操作

View File

@ -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)
}