mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 01:06:39 +08:00
feat: add sign check for PC QR code payment
This commit is contained in:
parent
962de0183c
commit
b8e0d7760b
@ -3,6 +3,7 @@
|
|||||||
* 功能优化:当数据库更新失败的时候记录错误日志
|
* 功能优化:当数据库更新失败的时候记录错误日志
|
||||||
* 功能优化:聊天输入框会随着输入内容的增多自动调整高度
|
* 功能优化:聊天输入框会随着输入内容的增多自动调整高度
|
||||||
* Bug修复:修复移动端聊天页面模型切换不生效的Bug
|
* Bug修复:修复移动端聊天页面模型切换不生效的Bug
|
||||||
|
* 功能优化:给PC端扫码支付增加签名验证和有效期验证
|
||||||
|
|
||||||
## v4.0.7
|
## v4.0.7
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
|
|||||||
func (h *ApiKeyHandler) List(c *gin.Context) {
|
func (h *ApiKeyHandler) List(c *gin.Context) {
|
||||||
status := h.GetBool(c, "status")
|
status := h.GetBool(c, "status")
|
||||||
t := h.GetTrim(c, "type")
|
t := h.GetTrim(c, "type")
|
||||||
|
platform := h.GetTrim(c, "platform")
|
||||||
|
|
||||||
session := h.DB.Session(&gorm.Session{})
|
session := h.DB.Session(&gorm.Session{})
|
||||||
if status {
|
if status {
|
||||||
@ -84,6 +85,9 @@ func (h *ApiKeyHandler) List(c *gin.Context) {
|
|||||||
if t != "" {
|
if t != "" {
|
||||||
session = session.Where("type", t)
|
session = session.Where("type", t)
|
||||||
}
|
}
|
||||||
|
if platform != "" {
|
||||||
|
session = session.Where("platform", platform)
|
||||||
|
}
|
||||||
|
|
||||||
var items []model.ApiKey
|
var items []model.ApiKey
|
||||||
var keys = make([]vo.ApiKey, 0)
|
var keys = make([]vo.ApiKey, 0)
|
||||||
|
@ -89,9 +89,13 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
|
|||||||
func (h *ChatModelHandler) List(c *gin.Context) {
|
func (h *ChatModelHandler) List(c *gin.Context) {
|
||||||
session := h.DB.Session(&gorm.Session{})
|
session := h.DB.Session(&gorm.Session{})
|
||||||
enable := h.GetBool(c, "enable")
|
enable := h.GetBool(c, "enable")
|
||||||
|
platform := h.GetTrim(c, "platform")
|
||||||
if enable {
|
if enable {
|
||||||
session = session.Where("enabled", enable)
|
session = session.Where("enabled", enable)
|
||||||
}
|
}
|
||||||
|
if platform != "" {
|
||||||
|
session = session.Where("platform", platform)
|
||||||
|
}
|
||||||
var items []model.ChatModel
|
var items []model.ChatModel
|
||||||
var cms = make([]vo.ChatModel, 0)
|
var cms = make([]vo.ChatModel, 0)
|
||||||
res := session.Order("sort_num ASC").Find(&items)
|
res := session.Order("sort_num ASC").Find(&items)
|
||||||
|
@ -8,6 +8,9 @@ package handler
|
|||||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"geekai/core"
|
"geekai/core"
|
||||||
"geekai/core/types"
|
"geekai/core/types"
|
||||||
"geekai/service"
|
"geekai/service"
|
||||||
@ -15,9 +18,6 @@ import (
|
|||||||
"geekai/store/model"
|
"geekai/store/model"
|
||||||
"geekai/utils"
|
"geekai/utils"
|
||||||
"geekai/utils/resp"
|
"geekai/utils/resp"
|
||||||
"embed"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -44,6 +44,7 @@ type PaymentHandler struct {
|
|||||||
snowflake *service.Snowflake
|
snowflake *service.Snowflake
|
||||||
fs embed.FS
|
fs embed.FS
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
|
signKey string // 用来签名的随机秘钥
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentHandler(
|
func NewPaymentHandler(
|
||||||
@ -65,12 +66,27 @@ func NewPaymentHandler(
|
|||||||
App: server,
|
App: server,
|
||||||
DB: db,
|
DB: db,
|
||||||
},
|
},
|
||||||
|
signKey: utils.RandString(32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||||
orderNo := h.GetTrim(c, "order_no")
|
orderNo := h.GetTrim(c, "order_no")
|
||||||
payWay := h.GetTrim(c, "pay_way")
|
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 == "" {
|
if orderNo == "" {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@ -273,8 +289,10 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
|||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay)
|
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)
|
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
@ -333,6 +351,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
ReturnURL: returnURL,
|
ReturnURL: returnURL,
|
||||||
CallbackURL: returnURL,
|
CallbackURL: returnURL,
|
||||||
WapName: "极客学长",
|
WapName: "极客学长",
|
||||||
|
Type: "WAP",
|
||||||
}
|
}
|
||||||
r, err := h.huPiPayService.Pay(params)
|
r, err := h.huPiPayService.Pay(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -49,6 +49,8 @@ type HuPiPayReq struct {
|
|||||||
CallbackURL string `json:"callback_url"`
|
CallbackURL string `json:"callback_url"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
NonceStr string `json:"nonce_str"`
|
NonceStr string `json:"nonce_str"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
WapUrl string `json:"wap_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HuPiResp struct {
|
type HuPiResp struct {
|
||||||
|
@ -2,6 +2,24 @@
|
|||||||
<div class="container list" v-loading="loading">
|
<div class="container list" v-loading="loading">
|
||||||
|
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
|
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
|
||||||
|
<el-option
|
||||||
|
v-for="item in platforms"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="query.type" placeholder="类型" class="handle-input">
|
||||||
|
<el-option
|
||||||
|
v-for="item in types"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-button :icon="Search" @click="fetchData">搜索</el-button>
|
||||||
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
||||||
<a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
|
<a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
|
||||||
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
|
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
|
||||||
@ -127,11 +145,12 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
|
|||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
|
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";
|
import ClipboardJS from "clipboard";
|
||||||
|
|
||||||
// 变量定义
|
// 变量定义
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
|
const query = ref({type: '',platform:''})
|
||||||
const item = ref({})
|
const item = ref({})
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
@ -166,6 +185,8 @@ onMounted(() => {
|
|||||||
}).catch(e =>{
|
}).catch(e =>{
|
||||||
ElMessage.error("获取配置失败:"+e.message)
|
ElMessage.error("获取配置失败:"+e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fetchData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -173,19 +194,22 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
httpGet('/api/admin/apikey/list').then((res) => {
|
|
||||||
if (res.data) {
|
const fetchData = () => {
|
||||||
// 初始化数据
|
httpGet('/api/admin/apikey/list', query.value).then((res) => {
|
||||||
const arr = res.data;
|
if (res.data) {
|
||||||
for (let i = 0; i < arr.length; i++) {
|
// 初始化数据
|
||||||
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
|
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(() => {
|
||||||
loading.value = false
|
ElMessage.error("获取数据失败");
|
||||||
}).catch(() => {
|
})
|
||||||
ElMessage.error("获取数据失败");
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const add = function () {
|
const add = function () {
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
@ -264,6 +288,10 @@ const changeType = (value) => {
|
|||||||
.list {
|
.list {
|
||||||
.handle-box {
|
.handle-box {
|
||||||
margin-bottom 20px
|
margin-bottom 20px
|
||||||
|
.handle-input {
|
||||||
|
max-width 150px;
|
||||||
|
margin-right 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-key {
|
.copy-key {
|
||||||
|
@ -2,6 +2,16 @@
|
|||||||
<div class="container model-list" v-loading="loading">
|
<div class="container model-list" v-loading="loading">
|
||||||
|
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
|
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
|
||||||
|
<el-option
|
||||||
|
v-for="item in platforms"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-button :icon="Search" @click="fetchData">搜索</el-button>
|
||||||
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -198,12 +208,13 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
|
|||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
|
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 {Sortable} from "sortablejs";
|
||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
|
|
||||||
// 变量定义
|
// 变量定义
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
|
const query = ref({platform:''})
|
||||||
const item = ref({})
|
const item = ref({})
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const title = ref("")
|
const title = ref("")
|
||||||
@ -226,7 +237,7 @@ httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
|
|||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
httpGet('/api/admin/model/list').then((res) => {
|
httpGet('/api/admin/model/list', query.value).then((res) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
const arr = res.data;
|
const arr = res.data;
|
||||||
@ -350,6 +361,10 @@ const remove = function (row) {
|
|||||||
|
|
||||||
.handle-box {
|
.handle-box {
|
||||||
margin-bottom 20px
|
margin-bottom 20px
|
||||||
|
.handle-input {
|
||||||
|
max-width 150px;
|
||||||
|
margin-right 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
|
@ -354,6 +354,8 @@ const doResetPass = () => {
|
|||||||
.pagination {
|
.pagination {
|
||||||
padding 20px 0
|
padding 20px 0
|
||||||
display flex
|
display flex
|
||||||
|
justify-content right
|
||||||
|
width 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select {
|
.el-select {
|
||||||
|
Loading…
Reference in New Issue
Block a user