From b8e0d7760bb95f67e02120bb53dbdea73ef1055c Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 22 May 2024 17:47:53 +0800 Subject: [PATCH] feat: add sign check for PC QR code payment --- CHANGELOG.md | 1 + api/handler/admin/api_key_handler.go | 4 ++ api/handler/admin/chat_model_handler.go | 4 ++ api/handler/payment_handler.go | 29 ++++++++++--- api/service/payment/hupipay_serive.go | 2 + web/src/views/admin/ApiKey.vue | 54 +++++++++++++++++++------ web/src/views/admin/ChatModel.vue | 19 ++++++++- web/src/views/admin/Users.vue | 2 + 8 files changed, 95 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fae673a..c12664bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * 功能优化:当数据库更新失败的时候记录错误日志 * 功能优化:聊天输入框会随着输入内容的增多自动调整高度 * Bug修复:修复移动端聊天页面模型切换不生效的Bug +* 功能优化:给PC端扫码支付增加签名验证和有效期验证 ## v4.0.7 diff --git a/api/handler/admin/api_key_handler.go b/api/handler/admin/api_key_handler.go index 58ea37f3..f412c037 100644 --- a/api/handler/admin/api_key_handler.go +++ b/api/handler/admin/api_key_handler.go @@ -76,6 +76,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) { func (h *ApiKeyHandler) List(c *gin.Context) { status := h.GetBool(c, "status") t := h.GetTrim(c, "type") + platform := h.GetTrim(c, "platform") session := h.DB.Session(&gorm.Session{}) if status { @@ -84,6 +85,9 @@ func (h *ApiKeyHandler) List(c *gin.Context) { if t != "" { session = session.Where("type", t) } + if platform != "" { + session = session.Where("platform", platform) + } var items []model.ApiKey var keys = make([]vo.ApiKey, 0) diff --git a/api/handler/admin/chat_model_handler.go b/api/handler/admin/chat_model_handler.go index dd594b23..74f187cc 100644 --- a/api/handler/admin/chat_model_handler.go +++ b/api/handler/admin/chat_model_handler.go @@ -89,9 +89,13 @@ func (h *ChatModelHandler) Save(c *gin.Context) { func (h *ChatModelHandler) List(c *gin.Context) { session := h.DB.Session(&gorm.Session{}) enable := h.GetBool(c, "enable") + platform := h.GetTrim(c, "platform") if enable { session = session.Where("enabled", enable) } + if platform != "" { + session = session.Where("platform", platform) + } var items []model.ChatModel var cms = make([]vo.ChatModel, 0) res := session.Order("sort_num ASC").Find(&items) diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index b6162784..c3f5cd61 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -8,6 +8,9 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "embed" + "encoding/base64" + "fmt" "geekai/core" "geekai/core/types" "geekai/service" @@ -15,9 +18,6 @@ import ( "geekai/store/model" "geekai/utils" "geekai/utils/resp" - "embed" - "encoding/base64" - "fmt" "github.com/shopspring/decimal" "math" "net/http" @@ -44,6 +44,7 @@ type PaymentHandler struct { snowflake *service.Snowflake fs embed.FS lock sync.Mutex + signKey string // 用来签名的随机秘钥 } func NewPaymentHandler( @@ -65,12 +66,27 @@ func NewPaymentHandler( App: server, DB: db, }, + signKey: utils.RandString(32), } } func (h *PaymentHandler) DoPay(c *gin.Context) { orderNo := h.GetTrim(c, "order_no") payWay := h.GetTrim(c, "pay_way") + t := h.GetInt(c, "t", 0) + sign := h.GetTrim(c, "sign") + signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, payWay, t, h.signKey) + newSign := utils.Sha256(signStr) + if newSign != sign { + resp.ERROR(c, "订单签名错误!") + return + } + + // 检查二维码是否过期 + if time.Now().Unix()-int64(t) > 30 { + resp.ERROR(c, "支付二维码已过期,请重新生成!") + return + } if orderNo == "" { resp.ERROR(c, types.InvalidArgs) @@ -273,8 +289,10 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { resp.ERROR(c, err.Error()) return } - - imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay) + timestamp := time.Now().Unix() + signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey) + sign := utils.Sha256(signStr) + imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, timestamp, sign) imgData, err := utils.GenQrcode(imageURL, 400, file) if err != nil { resp.ERROR(c, err.Error()) @@ -333,6 +351,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) { ReturnURL: returnURL, CallbackURL: returnURL, WapName: "极客学长", + Type: "WAP", } r, err := h.huPiPayService.Pay(params) if err != nil { diff --git a/api/service/payment/hupipay_serive.go b/api/service/payment/hupipay_serive.go index d0d0809a..69a2e211 100644 --- a/api/service/payment/hupipay_serive.go +++ b/api/service/payment/hupipay_serive.go @@ -49,6 +49,8 @@ type HuPiPayReq struct { CallbackURL string `json:"callback_url"` Time string `json:"time"` NonceStr string `json:"nonce_str"` + Type string `json:"type"` + WapUrl string `json:"wap_url"` } type HuPiResp struct { diff --git a/web/src/views/admin/ApiKey.vue b/web/src/views/admin/ApiKey.vue index aacc05a2..11e81a2f 100644 --- a/web/src/views/admin/ApiKey.vue +++ b/web/src/views/admin/ApiKey.vue @@ -2,6 +2,24 @@
+ + + + + + + + 搜索 新增 购买API-KEY @@ -127,11 +145,12 @@ import {onMounted, onUnmounted, reactive, ref} from "vue"; import {httpGet, httpPost} from "@/utils/http"; import {ElMessage} from "element-plus"; import {dateFormat, removeArrayItem, substr} from "@/utils/libs"; -import {DocumentCopy, Plus, ShoppingCart, InfoFilled} from "@element-plus/icons-vue"; +import {DocumentCopy, Plus, ShoppingCart, Search} from "@element-plus/icons-vue"; import ClipboardJS from "clipboard"; // 变量定义 const items = ref([]) +const query = ref({type: '',platform:''}) const item = ref({}) const showDialog = ref(false) const rules = reactive({ @@ -166,6 +185,8 @@ onMounted(() => { }).catch(e =>{ ElMessage.error("获取配置失败:"+e.message) }) + + fetchData() }) onUnmounted(() => { @@ -173,19 +194,22 @@ onUnmounted(() => { }) // 获取数据 -httpGet('/api/admin/apikey/list').then((res) => { - if (res.data) { - // 初始化数据 - const arr = res.data; - for (let i = 0; i < arr.length; i++) { - arr[i].last_used_at = dateFormat(arr[i].last_used_at) + +const fetchData = () => { + httpGet('/api/admin/apikey/list', query.value).then((res) => { + if (res.data) { + // 初始化数据 + const arr = res.data; + for (let i = 0; i < arr.length; i++) { + arr[i].last_used_at = dateFormat(arr[i].last_used_at) + } + items.value = arr } - items.value = arr - } - loading.value = false -}).catch(() => { - ElMessage.error("获取数据失败"); -}) + loading.value = false + }).catch(() => { + ElMessage.error("获取数据失败"); + }) +} const add = function () { showDialog.value = true @@ -264,6 +288,10 @@ const changeType = (value) => { .list { .handle-box { margin-bottom 20px + .handle-input { + max-width 150px; + margin-right 10px; + } } .copy-key { diff --git a/web/src/views/admin/ChatModel.vue b/web/src/views/admin/ChatModel.vue index 5daba7a2..c06bd3bb 100644 --- a/web/src/views/admin/ChatModel.vue +++ b/web/src/views/admin/ChatModel.vue @@ -2,6 +2,16 @@
+ + + + + 搜索 新增
@@ -198,12 +208,13 @@ import {onMounted, onUnmounted, reactive, ref} from "vue"; import {httpGet, httpPost} from "@/utils/http"; import {ElMessage} from "element-plus"; import {dateFormat, removeArrayItem, substr} from "@/utils/libs"; -import {DocumentCopy, InfoFilled, Plus} from "@element-plus/icons-vue"; +import {DocumentCopy, InfoFilled, Plus,Search} from "@element-plus/icons-vue"; import {Sortable} from "sortablejs"; import ClipboardJS from "clipboard"; // 变量定义 const items = ref([]) +const query = ref({platform:''}) const item = ref({}) const showDialog = ref(false) const title = ref("") @@ -226,7 +237,7 @@ httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => { // 获取数据 const fetchData = () => { - httpGet('/api/admin/model/list').then((res) => { + httpGet('/api/admin/model/list', query.value).then((res) => { if (res.data) { // 初始化数据 const arr = res.data; @@ -350,6 +361,10 @@ const remove = function (row) { .handle-box { margin-bottom 20px + .handle-input { + max-width 150px; + margin-right 10px; + } } .cell { diff --git a/web/src/views/admin/Users.vue b/web/src/views/admin/Users.vue index e4eaddfd..83fc1595 100644 --- a/web/src/views/admin/Users.vue +++ b/web/src/views/admin/Users.vue @@ -354,6 +354,8 @@ const doResetPass = () => { .pagination { padding 20px 0 display flex + justify-content right + width 100% } .el-select {