diff --git a/README.md b/README.md
index bbdbc515..8b85243d 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,11 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI
绘画函数插件。
-## 关于部署镜像申明
+## 最新版本一键部署脚本
-由于目前部署人数越来越多,本人的阿里云镜像仓库流量不够支撑大家使用了。所以从 v3.2.0 版本开始,一键部署脚本和部署镜像将只提供给 **[付费技术交流群]** 内用户使用。
-代码依旧是全部开源的,大家可自行编译打包镜像。
+```shell
+bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.3-8b588904ef.sh)"
+```
## 功能截图
diff --git a/api/core/types/config.go b/api/core/types/config.go
index a1827ba3..026a7954 100644
--- a/api/core/types/config.go
+++ b/api/core/types/config.go
@@ -159,6 +159,8 @@ type SystemConfig struct {
EnabledMsg bool `json:"enabled_msg"` // 是否启用短信验证码服务
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
+ ChatCallPrice float64 `json:"chat_call_price"` // 对话单次调用费用
+ ImgCallPrice float64 `json:"img_call_price"` // 绘图单次调用费用
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
@@ -166,4 +168,5 @@ type SystemConfig struct {
InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数
InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数
ForceInvite bool `json:"force_invite"` // 是否强制必须使用邀请码才能注册
+
}
diff --git a/api/handler/admin/reward_handler.go b/api/handler/admin/reward_handler.go
index b0470c37..61f65696 100644
--- a/api/handler/admin/reward_handler.go
+++ b/api/handler/admin/reward_handler.go
@@ -55,3 +55,16 @@ func (h *RewardHandler) List(c *gin.Context) {
resp.SUCCESS(c, rewards)
}
+
+func (h *RewardHandler) Remove(c *gin.Context) {
+ id := h.GetInt(c, "id", 0)
+
+ if id > 0 {
+ res := h.db.Where("id = ?", id).Delete(&model.Reward{})
+ if res.Error != nil {
+ resp.ERROR(c, "更新数据库失败!")
+ return
+ }
+ }
+ resp.SUCCESS(c)
+}
diff --git a/api/handler/reward_handler.go b/api/handler/reward_handler.go
index ed603d30..8fc36905 100644
--- a/api/handler/reward_handler.go
+++ b/api/handler/reward_handler.go
@@ -4,20 +4,24 @@ import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store/model"
+ "chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
+ "math"
"strings"
+ "sync"
)
type RewardHandler struct {
BaseHandler
- db *gorm.DB
+ db *gorm.DB
+ lock sync.Mutex
}
func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
- h := RewardHandler{db: db}
+ h := RewardHandler{db: db, lock: sync.Mutex{}}
h.App = server
return &h
}
@@ -26,15 +30,25 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
func (h *RewardHandler) Verify(c *gin.Context) {
var data struct {
TxId string `json:"tx_id"`
+ Type string `json:"type"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
+ user, err := utils.GetLoginUser(c, h.db)
+ if err != nil {
+ resp.HACKER(c)
+ return
+ }
+
// 移除转账单号中间的空格,防止有人复制的时候多复制了空格
data.TxId = strings.ReplaceAll(data.TxId, " ", "")
+ h.lock.Lock()
+ defer h.lock.Unlock()
+
var item model.Reward
res := h.db.Where("tx_id = ?", data.TxId).First(&item)
if res.Error != nil {
@@ -47,15 +61,17 @@ func (h *RewardHandler) Verify(c *gin.Context) {
return
}
- user, err := utils.GetLoginUser(c, h.db)
- if err != nil {
- resp.HACKER(c)
- return
- }
-
tx := h.db.Begin()
- calls := (item.Amount + 0.1) * 10
- res = h.db.Model(&user).UpdateColumn("calls", gorm.Expr("calls + ?", calls))
+ exchange := vo.RewardExchange{}
+ if data.Type == "chat" {
+ calls := math.Ceil(item.Amount / h.App.SysConfig.ChatCallPrice)
+ exchange.Calls = int(calls)
+ res = h.db.Model(&user).UpdateColumn("calls", gorm.Expr("calls + ?", calls))
+ } else if data.Type == "img" {
+ calls := math.Ceil(item.Amount / h.App.SysConfig.ImgCallPrice)
+ exchange.ImgCalls = int(calls)
+ res = h.db.Model(&user).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", calls))
+ }
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return
@@ -64,6 +80,7 @@ func (h *RewardHandler) Verify(c *gin.Context) {
// 更新核销状态
item.Status = true
item.UserId = user.Id
+ item.Exchange = utils.JsonEncode(exchange)
res = h.db.Updates(&item)
if res.Error != nil {
tx.Rollback()
diff --git a/api/main.go b/api/main.go
index 94d9040a..f1713bc7 100644
--- a/api/main.go
+++ b/api/main.go
@@ -57,13 +57,7 @@ func main() {
if configFile == "" {
configFile = "config.toml"
}
- var debug bool
- debugEnv := os.Getenv("DEBUG")
- if debugEnv == "" {
- debug = true
- } else {
- debug, _ = strconv.ParseBool(os.Getenv("DEBUG"))
- }
+ debug, _ := strconv.ParseBool(os.Getenv("APP_DEBUG"))
logger.Info("Loading config file: ", configFile)
defer func() {
if err := recover(); err != nil {
@@ -282,6 +276,7 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *admin.RewardHandler) {
group := s.Engine.Group("/api/admin/reward/")
group.GET("list", h.List)
+ group.GET("remove", h.Remove)
}),
fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) {
group := s.Engine.Group("/api/admin/dashboard/")
diff --git a/api/service/wx/bot.go b/api/service/wx/bot.go
index a5564999..0feb097c 100644
--- a/api/service/wx/bot.go
+++ b/api/service/wx/bot.go
@@ -6,6 +6,8 @@ import (
"github.com/eatmoreapple/openwechat"
"github.com/skip2/go-qrcode"
"gorm.io/gorm"
+ "os"
+ "strconv"
)
// 微信收款机器人
@@ -34,8 +36,13 @@ func (b *Bot) Run() error {
}
// scan code login callback
b.bot.UUIDCallback = b.qrCodeCallBack
-
- err := b.bot.Login()
+ debug, err := strconv.ParseBool(os.Getenv("APP_DEBUG"))
+ if debug {
+ reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
+ err = b.bot.HotLogin(reloadStorage, true)
+ } else {
+ err = b.bot.Login()
+ }
if err != nil {
return err
}
@@ -56,8 +63,8 @@ func (b *Bot) messageHandler(msg *openwechat.Message) {
msg.MsgType == openwechat.MsgTypeApp ||
msg.AppMsgType == openwechat.AppMsgTypeUrl {
// 解析支付金额
- message, err := parseTransactionMessage(msg.Content)
- if err == nil {
+ message := parseTransactionMessage(msg.Content)
+ if message.Url != "" {
transaction := extractTransaction(message)
logger.Infof("解析到收款信息:%+v", transaction)
var item model.Reward
diff --git a/api/service/wx/tranaction.go b/api/service/wx/tranaction.go
index ee06a9e3..0b1bca4d 100644
--- a/api/service/wx/tranaction.go
+++ b/api/service/wx/tranaction.go
@@ -2,17 +2,15 @@ package wx
import (
"encoding/xml"
+ "net/url"
"strconv"
"strings"
)
// Message 转账消息
type Message struct {
- XMLName xml.Name `xml:"msg"`
- AppMsg struct {
- Des string `xml:"des"`
- Url string `xml:"url"`
- } `xml:"appmsg"`
+ Des string
+ Url string
}
// Transaction 解析后的交易信息
@@ -23,20 +21,40 @@ type Transaction struct {
}
// 解析微信转账消息
-func parseTransactionMessage(xmlData string) (*Message, error) {
- var msg Message
- if err := xml.Unmarshal([]byte(xmlData), &msg); err != nil {
- return nil, err
- }
+func parseTransactionMessage(xmlData string) *Message {
+ decoder := xml.NewDecoder(strings.NewReader(xmlData))
+ message := Message{}
+ for {
+ token, err := decoder.Token()
+ if err != nil {
+ break
+ }
- return &msg, nil
+ switch se := token.(type) {
+ case xml.StartElement:
+ var value string
+ if se.Name.Local == "des" && message.Des == "" {
+ if err := decoder.DecodeElement(&value, &se); err == nil {
+ message.Des = strings.TrimSpace(value)
+ }
+ break
+ }
+ if se.Name.Local == "weapp_path" && !strings.Contains(message.Url, "customerDetails.html") {
+ if err := decoder.DecodeElement(&value, &se); err == nil {
+ message.Url = strings.TrimSpace(value)
+ }
+ break
+ }
+ }
+ }
+ return &message
}
// 导出交易信息
func extractTransaction(message *Message) Transaction {
var tx = Transaction{}
// 导出交易金额和备注
- lines := strings.Split(message.AppMsg.Des, "\n")
+ lines := strings.Split(message.Des, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 {
@@ -59,10 +77,9 @@ func extractTransaction(message *Message) Transaction {
}
// 解析交易 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])
+ parse, err := url.Parse(message.Url)
+ if err == nil {
+ tx.TransId = parse.Query().Get("id")
}
return tx
}
diff --git a/api/store/model/reward.go b/api/store/model/reward.go
index a035d4d1..43b9c8ce 100644
--- a/api/store/model/reward.go
+++ b/api/store/model/reward.go
@@ -4,9 +4,10 @@ package model
type Reward struct {
BaseModel
- UserId uint // 用户 ID
- TxId string // 交易ID
- Amount float64 // 打赏金额
- Remark string // 打赏备注
- Status bool // 核销状态
+ UserId uint // 用户 ID
+ TxId string // 交易ID
+ Amount float64 // 打赏金额
+ Remark string // 打赏备注
+ Status bool // 核销状态
+ Exchange string // 众筹兑换详情,JSON
}
diff --git a/api/store/vo/reward.go b/api/store/vo/reward.go
index 1aa06cd0..cb6490e1 100644
--- a/api/store/vo/reward.go
+++ b/api/store/vo/reward.go
@@ -2,10 +2,16 @@ package vo
type Reward struct {
BaseVo
- UserId uint `json:"user_id"` // 用户 ID
- Username string `json:"username"`
- TxId string `json:"tx_id"` // 交易ID
- Amount float64 `json:"amount"` // 打赏金额
- Remark string `json:"remark"` // 打赏备注
- Status bool `json:"status"` // 核销状态
+ UserId uint `json:"user_id"` // 用户 ID
+ Username string `json:"username"`
+ TxId string `json:"tx_id"` // 交易ID
+ Amount float64 `json:"amount"` // 打赏金额
+ Remark string `json:"remark"` // 打赏备注
+ Status bool `json:"status"` // 核销状态
+ Exchange RewardExchange `json:"exchange"`
+}
+
+type RewardExchange struct {
+ Calls int `json:"calls"`
+ ImgCalls int `json:"img_calls"`
}
diff --git a/api/test/test.go b/api/test/test.go
index 79058077..fe7f767d 100644
--- a/api/test/test.go
+++ b/api/test/test.go
@@ -1,5 +1,5 @@
package main
func main() {
-
+
}
diff --git a/database/update-v3.2.4.sql b/database/update-v3.2.4.sql
index 32f38023..87642d1a 100644
--- a/database/update-v3.2.4.sql
+++ b/database/update-v3.2.4.sql
@@ -1 +1,2 @@
ALTER TABLE `chatgpt_users` ADD `nickname` VARCHAR(30) NOT NULL COMMENT '昵称' AFTER `mobile`;
+ALTER TABLE `chatgpt_rewards` ADD `exchange` VARCHAR(255) NOT NULL COMMENT '兑换详情(json)' AFTER `status`;
diff --git a/web/src/components/RewardVerify.vue b/web/src/components/RewardVerify.vue
index 17e583f9..141e8c06 100644
--- a/web/src/components/RewardVerify.vue
+++ b/web/src/components/RewardVerify.vue
@@ -13,10 +13,19 @@
-
+
+
+
+
+
+ 对话聊天
+ AI绘图
+
+
+
@@ -46,6 +55,7 @@ const showDialog = computed(() => {
const title = ref('众筹码核销')
const form = ref({
tx_id: '',
+ type: 'chat'
})
const emits = defineEmits(['hide']);
diff --git a/web/src/router.js b/web/src/router.js
index 8dcb3211..c513579c 100644
--- a/web/src/router.js
+++ b/web/src/router.js
@@ -142,7 +142,7 @@ const routes = [
path: '/admin/reward',
name: 'admin-reward',
meta: {title: '众筹管理'},
- component: () => import('@/views/admin/RewardList.vue'),
+ component: () => import('@/views/admin/Reward.vue'),
},
{
path: '/admin/loginLog',
diff --git a/web/src/views/admin/RewardList.vue b/web/src/views/admin/Reward.vue
similarity index 58%
rename from web/src/views/admin/RewardList.vue
rename to web/src/views/admin/Reward.vue
index c966bd84..4de49448 100644
--- a/web/src/views/admin/RewardList.vue
+++ b/web/src/views/admin/Reward.vue
@@ -21,6 +21,25 @@
+
+
+ 聊天{{ scope.row['exchange']['calls'] }}次
+
+ 绘图{{ scope.row['exchange']['img_calls'] }}次
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
@@ -31,7 +50,7 @@
import {ref} from "vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
-import {dateFormat} from "@/utils/libs";
+import {dateFormat, removeArrayItem} from "@/utils/libs";
// 变量定义
const items = ref([])
@@ -52,6 +71,16 @@ httpGet('/api/admin/reward/list').then((res) => {
ElMessage.error("获取数据失败");
})
+const remove = function (row) {
+ httpGet('/api/admin/reward/remove?id=' + row.id).then(() => {
+ ElMessage.success("删除成功!")
+ items.value = removeArrayItem(items.value, row, (v1, v2) => {
+ return v1.id === v2.id
+ })
+ }).catch((e) => {
+ ElMessage.error("删除失败:" + e.message)
+ })
+}