mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-12-26 01:55:58 +08:00
merged v4.0.9 and fixed conflicts
This commit is contained in:
3
api/.gitignore
vendored
3
api/.gitignore
vendored
@@ -17,4 +17,5 @@ bin
|
||||
data
|
||||
config.toml
|
||||
static/upload
|
||||
storage.json
|
||||
storage.json
|
||||
res/certs/wechat/apiclient_key.pem
|
||||
|
||||
@@ -233,6 +233,7 @@ func needLogin(c *gin.Context) bool {
|
||||
c.Request.URL.Path == "/api/payment/alipay/notify" ||
|
||||
c.Request.URL.Path == "/api/payment/hupipay/notify" ||
|
||||
c.Request.URL.Path == "/api/payment/payjs/notify" ||
|
||||
c.Request.URL.Path == "/api/payment/wechat/notify" ||
|
||||
c.Request.URL.Path == "/api/payment/doPay" ||
|
||||
c.Request.URL.Path == "/api/payment/payWays" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
|
||||
@@ -29,11 +29,12 @@ type AppConfig struct {
|
||||
WeChatBot bool // 是否启用微信机器人
|
||||
SdConfigs []StableDiffusionConfig // sd AI draw service pool
|
||||
|
||||
XXLConfig XXLConfig
|
||||
AlipayConfig AlipayConfig
|
||||
HuPiPayConfig HuPiPayConfig
|
||||
SmtpConfig SmtpConfig // 邮件发送配置
|
||||
JPayConfig JPayConfig // payjs 支付配置
|
||||
XXLConfig XXLConfig
|
||||
AlipayConfig AlipayConfig // 支付宝支付渠道配置
|
||||
HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置
|
||||
SmtpConfig SmtpConfig // 邮件发送配置
|
||||
JPayConfig JPayConfig // payjs 支付配置
|
||||
WechatPayConfig WechatPayConfig // 微信支付渠道配置
|
||||
}
|
||||
|
||||
type SmtpConfig struct {
|
||||
@@ -85,6 +86,17 @@ type AlipayConfig struct {
|
||||
ReturnURL string // 支付成功返回地址
|
||||
}
|
||||
|
||||
type WechatPayConfig struct {
|
||||
Enabled bool // 是否启用该支付通道
|
||||
AppId string // 公众号的APPID,如:wxd678efh567hg6787
|
||||
MchId string // 直连商户的商户号,由微信支付生成并下发
|
||||
SerialNo string // 商户证书的证书序列号
|
||||
PrivateKey string // 用户私钥文件路径
|
||||
ApiV3Key string // API V3 秘钥
|
||||
NotifyURL string // 异步通知回调
|
||||
ReturnURL string // 支付成功返回地址
|
||||
}
|
||||
|
||||
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||
Enabled bool // 是否启用该支付通道
|
||||
Name string // 支付名称,如:wechat/alipay
|
||||
@@ -182,8 +194,9 @@ var QWen = Platform{
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
AdminTitle string `json:"admin_title,omitempty"`
|
||||
Title string `json:"title,omitempty"` // 网站标题
|
||||
Slogan string `json:"slogan,omitempty"` // 网站 slogan
|
||||
AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题
|
||||
Logo string `json:"logo,omitempty"`
|
||||
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
|
||||
DailyPower int `json:"daily_power,omitempty"` // 每日赠送算力
|
||||
|
||||
@@ -28,7 +28,6 @@ type MjTask struct {
|
||||
TaskId string `json:"task_id"`
|
||||
ImgArr []string `json:"img_arr"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
SessionId string `json:"session_id"`
|
||||
Type TaskType `json:"type"`
|
||||
UserId int `json:"user_id"`
|
||||
Prompt string `json:"prompt,omitempty"`
|
||||
@@ -42,7 +41,6 @@ type MjTask struct {
|
||||
|
||||
type SdTask struct {
|
||||
Id int `json:"id"` // job 数据库ID
|
||||
SessionId string `json:"session_id"`
|
||||
Type TaskType `json:"type"`
|
||||
UserId int `json:"user_id"`
|
||||
Params SdTaskParams `json:"params"`
|
||||
|
||||
10
api/go.mod
10
api/go.mod
@@ -19,7 +19,6 @@ require (
|
||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
|
||||
github.com/qiniu/go-sdk/v7 v7.17.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/smartwalle/alipay/v3 v3.2.15
|
||||
go.uber.org/zap v1.23.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
@@ -28,6 +27,7 @@ require (
|
||||
require github.com/xxl-job/xxl-job-executor-go v1.2.0
|
||||
|
||||
require (
|
||||
github.com/go-pay/gopay v1.5.101
|
||||
github.com/mojocn/base64Captcha v1.3.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
@@ -37,6 +37,11 @@ require (
|
||||
|
||||
require (
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-pay/crypto v0.0.1 // indirect
|
||||
github.com/go-pay/errgroup v0.0.2 // indirect
|
||||
github.com/go-pay/util v0.0.2 // indirect
|
||||
github.com/go-pay/xlog v0.0.2 // indirect
|
||||
github.com/go-pay/xtime v0.0.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
@@ -79,9 +84,6 @@ require (
|
||||
github.com/refraction-networking/utls v1.3.2 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.2 // indirect
|
||||
github.com/smartwalle/ngx v1.0.6 // indirect
|
||||
github.com/smartwalle/nsign v1.0.8 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.uber.org/dig v1.16.1 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
|
||||
20
api/go.sum
20
api/go.sum
@@ -45,6 +45,18 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY=
|
||||
github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes=
|
||||
github.com/go-pay/errgroup v0.0.2 h1:5mZMdm0TDClDm2S3G0/sm0f8AuQRtz0dOrTHDR9R8Cc=
|
||||
github.com/go-pay/errgroup v0.0.2/go.mod h1:0+4b8mvFMS71MIzsaC+gVvB4x37I93lRb2dqrwuU8x8=
|
||||
github.com/go-pay/gopay v1.5.101 h1:rVb+sfv6hiQtknAlZnTTLvU27NvFJ4p0yglN/vPpGXI=
|
||||
github.com/go-pay/gopay v1.5.101/go.mod h1:AW4Yj8jDZX9BM1/GTLTY1Gy5SHjiq8kQvG5sBTN2sxI=
|
||||
github.com/go-pay/util v0.0.2 h1:goJ4f6kNY5zzdtg1Cj8oWC+Cw7bfg/qq2rJangMAb9U=
|
||||
github.com/go-pay/util v0.0.2/go.mod h1:qM8VbyF1n7YAPZBSJONSPMPsPedhUTktewUAdf1AjPg=
|
||||
github.com/go-pay/xlog v0.0.2 h1:kUg5X8/5VZAPDg1J5eGjA3MG0/H5kK6Ew0dW/Bycsws=
|
||||
github.com/go-pay/xlog v0.0.2/go.mod h1:DbjMADPK4+Sjxj28ekK9goqn4zmyY4hql/zRiab+S9E=
|
||||
github.com/go-pay/xtime v0.0.2 h1:7YR4/iuELsEHpJ6LUO0SVK80hQxDO9MLCfuVYIiTCRM=
|
||||
github.com/go-pay/xtime v0.0.2/go.mod h1:W1yRbJaSt4CSBcdAtLBQ8xajiN/Pl5hquGczUcUE9xE=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -186,14 +198,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/smartwalle/alipay/v3 v3.2.15 h1:3fvFJnINKKAOXHR/Iv20k1Z7KJ+nOh3oK214lELPqG8=
|
||||
github.com/smartwalle/alipay/v3 v3.2.15/go.mod h1:niTNB609KyUYuAx9Bex/MawEjv2yPx4XOjxSAkqmGjE=
|
||||
github.com/smartwalle/ncrypto v1.0.2 h1:pTAhCqtPCMhpOwFXX+EcMdR6PNzruBNoGQrN2S1GbGI=
|
||||
github.com/smartwalle/ncrypto v1.0.2/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
|
||||
github.com/smartwalle/ngx v1.0.6 h1:JPNqNOIj+2nxxFtrSkJO+vKJfeNUSEQueck/Wworjps=
|
||||
github.com/smartwalle/ngx v1.0.6/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.8 h1:78KWtwKPrdt4Xsn+tNEBVxaTLIJBX9YRX0ZSrMUeuHo=
|
||||
github.com/smartwalle/nsign v1.0.8/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
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=
|
||||
|
||||
@@ -330,7 +330,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
Content: prompt,
|
||||
})
|
||||
req.Input["messages"] = reqMgs
|
||||
} else if session.Model.Platform == types.OpenAI.Value { // extract image for gpt-vision model
|
||||
} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
|
||||
imgURLs := utils.ExtractImgURL(prompt)
|
||||
logger.Debugf("detected IMG: %+v", imgURLs)
|
||||
var content interface{}
|
||||
@@ -651,26 +651,25 @@ func (h *ChatHandler) saveChatHistory(
|
||||
logger.Error("failed to save reply history message: ", res.Error)
|
||||
}
|
||||
|
||||
// 更新用户算力
|
||||
if session.Model.Power > 0 {
|
||||
// 更新用户算力
|
||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||
|
||||
// 保存当前会话
|
||||
var chatItem model.ChatItem
|
||||
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||
if res.Error != nil {
|
||||
chatItem.ChatId = session.ChatId
|
||||
chatItem.UserId = session.UserId
|
||||
chatItem.RoleId = role.Id
|
||||
chatItem.ModelId = session.Model.Id
|
||||
if utf8.RuneCountInString(prompt) > 30 {
|
||||
chatItem.Title = string([]rune(prompt)[:30]) + "..."
|
||||
} else {
|
||||
chatItem.Title = prompt
|
||||
}
|
||||
chatItem.Model = req.Model
|
||||
h.DB.Create(&chatItem)
|
||||
}
|
||||
// 保存当前会话
|
||||
var chatItem model.ChatItem
|
||||
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||
if res.Error != nil {
|
||||
chatItem.ChatId = session.ChatId
|
||||
chatItem.UserId = session.UserId
|
||||
chatItem.RoleId = role.Id
|
||||
chatItem.ModelId = session.Model.Id
|
||||
if utf8.RuneCountInString(prompt) > 30 {
|
||||
chatItem.Title = string([]rune(prompt)[:30]) + "..."
|
||||
} else {
|
||||
chatItem.Title = prompt
|
||||
}
|
||||
chatItem.Model = req.Model
|
||||
h.DB.Create(&chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ func (h *ChatHandler) sendOpenAiMessage(
|
||||
}
|
||||
|
||||
if toolCall { // 调用函数完成任务
|
||||
var params map[string]interface{}
|
||||
params := make(map[string]interface{})
|
||||
_ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
||||
logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
|
||||
params["user_id"] = userVo.Id
|
||||
|
||||
@@ -183,45 +183,29 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode
|
||||
utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
|
||||
|
||||
} else {
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
var res types.ApiError
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
// OpenAI API 调用异常处理
|
||||
if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") {
|
||||
// remove key
|
||||
h.DB.Where("value = ?", apiKey).Delete(&model.ApiKey{})
|
||||
return errors.New("请求 OpenAI API 失败:API KEY 所关联的账户被禁用。")
|
||||
} else if strings.Contains(res.Error.Message, "You exceeded your current quota") {
|
||||
return errors.New("请求 OpenAI API 失败:API KEY 触发并发限制,请稍后再试。")
|
||||
} else {
|
||||
return fmt.Errorf("请求 OpenAI API 失败:%v", res.Error.Message)
|
||||
}
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("请求 OpenAI API 失败:%s", string(body))
|
||||
}
|
||||
|
||||
// 扣减算力
|
||||
res = h.DB.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", chatModel.Power))
|
||||
if res.Error == nil {
|
||||
// 记录算力消费日志
|
||||
var u model.User
|
||||
h.DB.Where("id", userId).First(&u)
|
||||
h.DB.Create(&model.PowerLog{
|
||||
UserId: u.Id,
|
||||
Username: u.Username,
|
||||
Type: types.PowerConsume,
|
||||
Amount: chatModel.Power,
|
||||
Mark: types.PowerSub,
|
||||
Balance: u.Power,
|
||||
Model: chatModel.Value,
|
||||
Remark: fmt.Sprintf("AI绘制思维导图,模型名称:%s, ", chatModel.Value),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
if chatModel.Power > 0 {
|
||||
res = h.DB.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", chatModel.Power))
|
||||
if res.Error == nil {
|
||||
// 记录算力消费日志
|
||||
var u model.User
|
||||
h.DB.Where("id", userId).First(&u)
|
||||
h.DB.Create(&model.PowerLog{
|
||||
UserId: u.Id,
|
||||
Username: u.Username,
|
||||
Type: types.PowerConsume,
|
||||
Amount: chatModel.Power,
|
||||
Mark: types.PowerSub,
|
||||
Balance: u.Power,
|
||||
Model: chatModel.Value,
|
||||
Remark: fmt.Sprintf("AI绘制思维导图,模型名称:%s, ", chatModel.Value),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -235,7 +219,7 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
|
||||
}
|
||||
// use the last unused key
|
||||
if apiKey.Id == 0 {
|
||||
res = h.DB.Where("platform", types.OpenAI).
|
||||
res = h.DB.Where("platform", types.OpenAI.Value).
|
||||
Where("type", "chat").
|
||||
Where("enabled", true).Order("last_used_at ASC").First(apiKey)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,6 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: job.Id,
|
||||
TaskId: taskId,
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskType(data.TaskType),
|
||||
Prompt: data.Prompt,
|
||||
NegPrompt: data.NegPrompt,
|
||||
@@ -283,7 +282,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: job.Id,
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskUpscale,
|
||||
Prompt: data.Prompt,
|
||||
UserId: userId,
|
||||
@@ -350,7 +348,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: job.Id,
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskVariation,
|
||||
Prompt: data.Prompt,
|
||||
UserId: userId,
|
||||
|
||||
@@ -29,39 +29,48 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
PayWayAlipay = "支付宝"
|
||||
PayWayXunHu = "虎皮椒"
|
||||
PayWayJs = "PayJS"
|
||||
type PayWay struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
var (
|
||||
PayWayAlipay = PayWay{Name: "支付宝", Value: "alipay"}
|
||||
PayWayXunHu = PayWay{Name: "虎皮椒", Value: "hupi"}
|
||||
PayWayJs = PayWay{Name: "PayJS", Value: "payjs"}
|
||||
PayWayWechat = PayWay{Name: "微信支付", Value: "wechat"}
|
||||
)
|
||||
|
||||
// PaymentHandler 支付服务回调 handler
|
||||
type PaymentHandler struct {
|
||||
BaseHandler
|
||||
alipayService *payment.AlipayService
|
||||
huPiPayService *payment.HuPiPayService
|
||||
js *payment.PayJS
|
||||
snowflake *service.Snowflake
|
||||
fs embed.FS
|
||||
lock sync.Mutex
|
||||
signKey string // 用来签名的随机秘钥
|
||||
alipayService *payment.AlipayService
|
||||
huPiPayService *payment.HuPiPayService
|
||||
jsPayService *payment.JPayService
|
||||
wechatPayService *payment.WechatPayService
|
||||
snowflake *service.Snowflake
|
||||
fs embed.FS
|
||||
lock sync.Mutex
|
||||
signKey string // 用来签名的随机秘钥
|
||||
}
|
||||
|
||||
func NewPaymentHandler(
|
||||
server *core.AppServer,
|
||||
alipayService *payment.AlipayService,
|
||||
huPiPayService *payment.HuPiPayService,
|
||||
js *payment.PayJS,
|
||||
jsPayService *payment.JPayService,
|
||||
wechatPayService *payment.WechatPayService,
|
||||
db *gorm.DB,
|
||||
snowflake *service.Snowflake,
|
||||
fs embed.FS) *PaymentHandler {
|
||||
return &PaymentHandler{
|
||||
alipayService: alipayService,
|
||||
huPiPayService: huPiPayService,
|
||||
js: js,
|
||||
snowflake: snowflake,
|
||||
fs: fs,
|
||||
lock: sync.Mutex{},
|
||||
alipayService: alipayService,
|
||||
huPiPayService: huPiPayService,
|
||||
jsPayService: jsPayService,
|
||||
wechatPayService: wechatPayService,
|
||||
snowflake: snowflake,
|
||||
fs: fs,
|
||||
lock: sync.Mutex{},
|
||||
BaseHandler: BaseHandler{
|
||||
App: server,
|
||||
DB: db,
|
||||
@@ -108,13 +117,10 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||
|
||||
// 更新扫码状态
|
||||
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned)
|
||||
if payWay == "alipay" { // 支付宝
|
||||
// 生成支付链接
|
||||
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
||||
returnURL := "" // 关闭同步回跳
|
||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
||||
|
||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
|
||||
if payWay == "alipay" { // 支付宝
|
||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||
return
|
||||
@@ -214,14 +220,21 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
var notifyURL string
|
||||
switch data.PayWay {
|
||||
case "hupi":
|
||||
payWay = PayWayXunHu
|
||||
payWay = PayWayXunHu.Value
|
||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
||||
break
|
||||
case "payjs":
|
||||
payWay = PayWayJs
|
||||
payWay = PayWayJs.Value
|
||||
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
||||
default:
|
||||
payWay = PayWayAlipay
|
||||
break
|
||||
case "alipay":
|
||||
payWay = PayWayAlipay.Value
|
||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
||||
break
|
||||
default:
|
||||
payWay = PayWayWechat.Value
|
||||
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
|
||||
|
||||
}
|
||||
// 创建订单
|
||||
remark := types.OrderRemark{
|
||||
@@ -257,7 +270,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
OutTradeNo: order.OrderNo,
|
||||
Subject: product.Name,
|
||||
}
|
||||
r := h.js.Pay(params)
|
||||
r := h.jsPayService.Pay(params)
|
||||
if r.IsOK() {
|
||||
resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode})
|
||||
return
|
||||
@@ -276,6 +289,8 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
} else {
|
||||
logo = "res/img/alipay.jpg"
|
||||
}
|
||||
} else if data.PayWay == "wechat" {
|
||||
logo = "res/img/wechat-pay.jpg"
|
||||
}
|
||||
|
||||
file, err := h.fs.Open(logo)
|
||||
@@ -292,7 +307,18 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
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)
|
||||
var imageURL string
|
||||
if data.PayWay == "wechat" {
|
||||
payUrl, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(math.Floor(order.Amount*100)), product.Name)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generating wechat payment qrcode: "+err.Error())
|
||||
return
|
||||
} else {
|
||||
imageURL = payUrl
|
||||
}
|
||||
} else {
|
||||
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())
|
||||
@@ -339,7 +365,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
var payURL string
|
||||
switch data.PayWay {
|
||||
case "hupi":
|
||||
payWay = PayWayXunHu
|
||||
payWay = PayWayXunHu.Name
|
||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
||||
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
|
||||
parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL)
|
||||
@@ -358,13 +384,14 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
}
|
||||
r, err := h.huPiPayService.Pay(params)
|
||||
if err != nil {
|
||||
logger.Error("error with generating Pay URL: ", err.Error())
|
||||
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
|
||||
errMsg := "error with generating Pay Hupi URL: " + err.Error()
|
||||
logger.Error(errMsg)
|
||||
resp.ERROR(c, errMsg)
|
||||
return
|
||||
}
|
||||
payURL = r.URL
|
||||
case "payjs":
|
||||
payWay = PayWayJs
|
||||
payWay = PayWayJs.Name
|
||||
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
||||
returnURL = h.App.Config.JPayConfig.ReturnURL
|
||||
totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
|
||||
@@ -374,14 +401,22 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
params.Add("body", product.Name)
|
||||
params.Add("notify_url", notifyURL)
|
||||
params.Add("auto", "0")
|
||||
payURL = h.js.PayH5(params)
|
||||
payURL = h.jsPayService.PayH5(params)
|
||||
case "alipay":
|
||||
payWay = PayWayAlipay
|
||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
||||
returnURL = h.App.Config.AlipayConfig.ReturnURL
|
||||
payURL, err = h.alipayService.PayUrlMobile(orderNo, notifyURL, returnURL, fmt.Sprintf("%.2f", amount), product.Name)
|
||||
payWay = PayWayAlipay.Name
|
||||
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
|
||||
errMsg := "error with generating Alipay URL: " + err.Error()
|
||||
resp.ERROR(c, errMsg)
|
||||
return
|
||||
}
|
||||
case "wechat":
|
||||
payWay = PayWayWechat.Name
|
||||
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
|
||||
if err != nil {
|
||||
errMsg := "error with generating Wechat URL: " + err.Error()
|
||||
logger.Error(errMsg)
|
||||
resp.ERROR(c, errMsg)
|
||||
return
|
||||
}
|
||||
default:
|
||||
@@ -493,7 +528,7 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
|
||||
h.DB.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1))
|
||||
|
||||
// 记录算力充值日志
|
||||
if opt != "" {
|
||||
if power > 0 {
|
||||
h.DB.Create(&model.PowerLog{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
@@ -522,6 +557,9 @@ func (h *PaymentHandler) GetPayWays(c *gin.Context) {
|
||||
if h.App.Config.JPayConfig.Enabled {
|
||||
data["payjs"] = gin.H{"name": h.App.Config.JPayConfig.Name}
|
||||
}
|
||||
if h.App.Config.WechatPayConfig.Enabled {
|
||||
data["wechat"] = gin.H{"name": "wechat"}
|
||||
}
|
||||
resp.SUCCESS(c, data)
|
||||
}
|
||||
|
||||
@@ -560,7 +598,7 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||
}
|
||||
|
||||
// TODO:验证交易签名
|
||||
res := h.alipayService.TradeVerify(c.Request.Form)
|
||||
res := h.alipayService.TradeVerify(c.Request)
|
||||
logger.Infof("验证支付结果:%+v", res)
|
||||
if !res.Success() {
|
||||
logger.Error("订单校验失败:", res.Message)
|
||||
@@ -588,7 +626,7 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
|
||||
|
||||
orderNo := c.Request.Form.Get("out_trade_no")
|
||||
returnCode := c.Request.Form.Get("return_code")
|
||||
logger.Infof("收到订单支付回调,订单 NO:%s,支付结果代码:%v", orderNo, returnCode)
|
||||
logger.Infof("收到PayJs订单支付回调,订单 NO:%s,支付结果代码:%v", orderNo, returnCode)
|
||||
// 支付失败
|
||||
if returnCode != "1" {
|
||||
return
|
||||
@@ -596,7 +634,7 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
|
||||
|
||||
// 校验订单支付状态
|
||||
tradeNo := c.Request.Form.Get("payjs_order_id")
|
||||
err = h.js.Check(tradeNo)
|
||||
err = h.jsPayService.TradeVerify(tradeNo)
|
||||
if err != nil {
|
||||
logger.Error("订单校验失败:", err)
|
||||
c.String(http.StatusOK, "fail")
|
||||
@@ -611,3 +649,30 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
|
||||
|
||||
c.String(http.StatusOK, "success")
|
||||
}
|
||||
|
||||
// WechatPayNotify 微信商户支付异步回调
|
||||
func (h *PaymentHandler) WechatPayNotify(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
}
|
||||
|
||||
result := h.wechatPayService.TradeVerify(c.Request)
|
||||
if !result.Success() {
|
||||
logger.Error("订单校验失败:", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "FAIL",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.notify(result.OutTradeNo, result.TradeId)
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
}
|
||||
|
||||
c.String(http.StatusOK, "success")
|
||||
}
|
||||
|
||||
@@ -168,11 +168,10 @@ func (h *SdJobHandler) Image(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.pool.PushTask(types.SdTask{
|
||||
Id: int(job.Id),
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskImage,
|
||||
Params: params,
|
||||
UserId: userId,
|
||||
Id: int(job.Id),
|
||||
Type: types.TaskImage,
|
||||
Params: params,
|
||||
UserId: userId,
|
||||
})
|
||||
|
||||
client := h.pool.Clients.Get(uint(job.UserId))
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
type TestHandler struct {
|
||||
db *gorm.DB
|
||||
snowflake *service.Snowflake
|
||||
js *payment.PayJS
|
||||
js *payment.JPayService
|
||||
}
|
||||
|
||||
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.PayJS) *TestHandler {
|
||||
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.JPayService) *TestHandler {
|
||||
return &TestHandler{db: db, snowflake: snowflake, js: js}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
user := model.User{
|
||||
Username: data.Username,
|
||||
Password: utils.GenPassword(data.Password, salt),
|
||||
Nickname: fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)),
|
||||
Avatar: "/images/avatar/user.png",
|
||||
Salt: salt,
|
||||
Status: true,
|
||||
@@ -118,6 +117,16 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
Power: h.App.SysConfig.InitPower,
|
||||
}
|
||||
|
||||
// 被邀请人也获得赠送算力
|
||||
if data.InviteCode != "" {
|
||||
user.Power += h.App.SysConfig.InvitePower
|
||||
}
|
||||
if h.licenseService.GetLicense().Configs.DeCopy {
|
||||
user.Username = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
|
||||
} else {
|
||||
user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
|
||||
}
|
||||
|
||||
res = h.DB.Create(&user)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "保存数据失败")
|
||||
|
||||
@@ -211,7 +211,8 @@ func main() {
|
||||
|
||||
fx.Provide(payment.NewAlipayService),
|
||||
fx.Provide(payment.NewHuPiPay),
|
||||
fx.Provide(payment.NewPayJS),
|
||||
fx.Provide(payment.NewJPayService),
|
||||
fx.Provide(payment.NewWechatService),
|
||||
fx.Provide(service.NewSnowflake),
|
||||
fx.Provide(service.NewXXLJobExecutor),
|
||||
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
||||
@@ -373,6 +374,7 @@ func main() {
|
||||
group.POST("alipay/notify", h.AlipayNotify)
|
||||
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||
group.POST("payjs/notify", h.PayJsNotify)
|
||||
group.POST("wechat/notify", h.WechatPayNotify)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
||||
group := s.Engine.Group("/api/admin/product/")
|
||||
|
||||
@@ -109,13 +109,13 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
|
||||
logger.Debugf("绘画参数:%+v", task)
|
||||
prompt := task.Prompt
|
||||
// translate prompt
|
||||
if utils.HasChinese(task.Prompt) {
|
||||
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, task.Prompt))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with translate prompt: %v", err)
|
||||
if utils.HasChinese(prompt) {
|
||||
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, prompt))
|
||||
if err == nil {
|
||||
prompt = content
|
||||
logger.Debugf("重写后提示词:%s", prompt)
|
||||
}
|
||||
prompt = content
|
||||
logger.Debugf("重写后提示词:%s", prompt)
|
||||
|
||||
}
|
||||
|
||||
var user model.User
|
||||
@@ -124,10 +124,28 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
|
||||
return "", errors.New("insufficient of power")
|
||||
}
|
||||
|
||||
// 更新用户算力
|
||||
tx := s.db.Model(&model.User{}).Where("id", user.Id).UpdateColumn("power", gorm.Expr("power - ?", task.Power))
|
||||
// 记录算力变化日志
|
||||
if tx.Error == nil && tx.RowsAffected > 0 {
|
||||
var u model.User
|
||||
s.db.Where("id", user.Id).First(&u)
|
||||
s.db.Create(&model.PowerLog{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
Type: types.PowerConsume,
|
||||
Amount: task.Power,
|
||||
Balance: u.Power,
|
||||
Mark: types.PowerSub,
|
||||
Model: "dall-e-3",
|
||||
Remark: fmt.Sprintf("绘画提示词:%s", utils.CutWords(task.Prompt, 10)),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
// get image generation API KEY
|
||||
var apiKey model.ApiKey
|
||||
tx := s.db.Where("platform", types.OpenAI.Value).
|
||||
Where("type", "img").
|
||||
tx = s.db.Where("type", "img").
|
||||
Where("enabled", true).
|
||||
Order("last_used_at ASC").First(&apiKey)
|
||||
if tx.Error != nil {
|
||||
@@ -139,25 +157,28 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
|
||||
if len(apiKey.ProxyURL) > 5 {
|
||||
s.httpClient.SetProxyURL(apiKey.ProxyURL).R()
|
||||
}
|
||||
logger.Infof("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, apiKey.ProxyURL)
|
||||
r, err := s.httpClient.R().SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||
SetBody(imgReq{
|
||||
Model: "dall-e-3",
|
||||
Prompt: prompt,
|
||||
N: 1,
|
||||
Size: task.Size,
|
||||
Style: task.Style,
|
||||
Quality: task.Quality,
|
||||
}).
|
||||
SetErrorResult(&errRes).
|
||||
SetSuccessResult(&res).Post(apiKey.ApiURL)
|
||||
reqBody := imgReq{
|
||||
Model: "dall-e-3",
|
||||
Prompt: prompt,
|
||||
N: 1,
|
||||
Size: task.Size,
|
||||
Style: task.Style,
|
||||
Quality: task.Quality,
|
||||
}
|
||||
logger.Infof("Sending %s request, ApiURL:%s, API KEY:%s, BODY: %+v", apiKey.Platform, apiKey.ApiURL, apiKey.Value, reqBody)
|
||||
request := s.httpClient.R().SetHeader("Content-Type", "application/json")
|
||||
if apiKey.Platform == types.Azure.Value {
|
||||
request = request.SetHeader("api-key", apiKey.Value)
|
||||
} else {
|
||||
request = request.SetHeader("Authorization", "Bearer "+apiKey.Value)
|
||||
}
|
||||
r, err := request.SetBody(reqBody).SetErrorResult(&errRes).SetSuccessResult(&res).Post(apiKey.ApiURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with send request: %v", err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return "", fmt.Errorf("error with send request: %v", errRes.Error)
|
||||
return "", fmt.Errorf("error with send request, status: %s, %+v", r.Status, errRes.Error)
|
||||
}
|
||||
// update the api key last use time
|
||||
s.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
|
||||
@@ -181,25 +202,6 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
|
||||
content = fmt.Sprintf("```\n%s\n```\n下面是我为你创作的图片:\n\n\n", prompt, imgURL)
|
||||
}
|
||||
|
||||
// 更新用户算力
|
||||
tx = s.db.Model(&model.User{}).Where("id", user.Id).UpdateColumn("power", gorm.Expr("power - ?", task.Power))
|
||||
// 记录算力变化日志
|
||||
if tx.Error == nil && tx.RowsAffected > 0 {
|
||||
var u model.User
|
||||
s.db.Where("id", user.Id).First(&u)
|
||||
s.db.Create(&model.PowerLog{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
Type: types.PowerConsume,
|
||||
Amount: task.Power,
|
||||
Balance: u.Power,
|
||||
Mark: types.PowerSub,
|
||||
Model: "dall-e-3",
|
||||
Remark: fmt.Sprintf("绘画提示词:%s", utils.CutWords(task.Prompt, 10)),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ func (s *LicenseService) SyncLicense() {
|
||||
if err != nil {
|
||||
retryCounter++
|
||||
if retryCounter < 5 {
|
||||
logger.Error(err)
|
||||
logger.Warn(err)
|
||||
}
|
||||
s.license.IsActive = false
|
||||
} else {
|
||||
|
||||
@@ -179,14 +179,14 @@ func (p *ServicePool) HasAvailableService() bool {
|
||||
// SyncTaskProgress 异步拉取任务
|
||||
func (p *ServicePool) SyncTaskProgress() {
|
||||
go func() {
|
||||
var items []model.MidJourneyJob
|
||||
var jobs []model.MidJourneyJob
|
||||
for {
|
||||
res := p.db.Where("progress < ?", 100).Find(&items)
|
||||
res := p.db.Where("progress < ?", 100).Find(&jobs)
|
||||
if res.Error != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, job := range items {
|
||||
for _, job := range jobs {
|
||||
// 失败或者 30 分钟还没完成的任务删除并退回算力
|
||||
if time.Now().Sub(job.CreatedAt) > time.Minute*30 || job.Progress == -1 {
|
||||
p.db.Delete(&job)
|
||||
|
||||
@@ -8,12 +8,13 @@ package payment
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"geekai/core/types"
|
||||
logger2 "geekai/logger"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"log"
|
||||
"net/url"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -35,93 +36,90 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
|
||||
return nil, fmt.Errorf("error with read App Private key: %v", err)
|
||||
}
|
||||
|
||||
xClient, err := alipay.New(config.AppId, priKey, !config.SandBox)
|
||||
client, err := alipay.NewClient(config.AppId, priKey, !config.SandBox)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error with initialize alipay service: %v", err)
|
||||
}
|
||||
|
||||
if err = xClient.LoadAppCertPublicKeyFromFile(config.PublicKey); err != nil {
|
||||
return nil, fmt.Errorf("error with loading App PublicKey: %v", err)
|
||||
}
|
||||
if err = xClient.LoadAliPayRootCertFromFile(config.RootCert); err != nil {
|
||||
return nil, fmt.Errorf("error with loading alipay RootCert: %v", err)
|
||||
}
|
||||
if err = xClient.LoadAlipayCertPublicKeyFromFile(config.AlipayPublicKey); err != nil {
|
||||
return nil, fmt.Errorf("error with loading Alipay PublicKey: %v", err)
|
||||
//client.DebugSwitch = gopay.DebugOn // 开启调试模式
|
||||
client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
|
||||
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
|
||||
SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2
|
||||
SetReturnUrl(config.ReturnURL). // 设置返回URL
|
||||
SetNotifyUrl(config.NotifyURL)
|
||||
|
||||
if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil {
|
||||
return nil, fmt.Errorf("error with load payment public key: %v", err)
|
||||
}
|
||||
|
||||
return &AlipayService{config: &config, client: xClient}, nil
|
||||
return &AlipayService{config: &config, client: client}, nil
|
||||
}
|
||||
|
||||
func (s *AlipayService) PayUrlMobile(outTradeNo string, notifyURL string, returnURL string, Amount string, subject string) (string, error) {
|
||||
var p = alipay.TradeWapPay{}
|
||||
p.NotifyURL = notifyURL
|
||||
p.ReturnURL = returnURL
|
||||
p.Subject = subject
|
||||
p.OutTradeNo = outTradeNo
|
||||
p.TotalAmount = Amount
|
||||
p.ProductCode = "QUICK_WAP_WAY"
|
||||
res, err := s.client.TradeWapPay(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.String(), err
|
||||
func (s *AlipayService) PayUrlMobile(outTradeNo string, amount string, subject string) (string, error) {
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("subject", subject)
|
||||
bm.Set("out_trade_no", outTradeNo)
|
||||
bm.Set("quit_url", s.config.ReturnURL)
|
||||
bm.Set("total_amount", amount)
|
||||
bm.Set("product_code", "QUICK_WAP_WAY")
|
||||
return s.client.TradeWapPay(context.Background(), bm)
|
||||
}
|
||||
|
||||
func (s *AlipayService) PayUrlPc(outTradeNo string, notifyURL string, returnURL string, amount string, subject string) (string, error) {
|
||||
var p = alipay.TradePagePay{}
|
||||
p.NotifyURL = notifyURL
|
||||
p.ReturnURL = returnURL
|
||||
p.Subject = subject
|
||||
p.OutTradeNo = outTradeNo
|
||||
p.TotalAmount = amount
|
||||
p.ProductCode = "FAST_INSTANT_TRADE_PAY"
|
||||
res, err := s.client.TradePagePay(p)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return res.String(), err
|
||||
func (s *AlipayService) PayUrlPc(outTradeNo string, amount string, subject string) (string, error) {
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("subject", subject)
|
||||
bm.Set("out_trade_no", outTradeNo)
|
||||
bm.Set("total_amount", amount)
|
||||
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
|
||||
return s.client.TradePagePay(context.Background(), bm)
|
||||
}
|
||||
|
||||
// TradeVerify 交易验证
|
||||
func (s *AlipayService) TradeVerify(reqForm url.Values) NotifyVo {
|
||||
err := s.client.VerifySign(reqForm)
|
||||
func (s *AlipayService) TradeVerify(request *http.Request) NotifyVo {
|
||||
notifyReq, err := alipay.ParseNotifyToBodyMap(request) // c.Request 是 gin 框架的写法
|
||||
if err != nil {
|
||||
log.Println("异步通知验证签名发生错误", err)
|
||||
return NotifyVo{
|
||||
Status: 0,
|
||||
Message: "异步通知验证签名发生错误",
|
||||
Status: Failure,
|
||||
Message: "error with parse notify request: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return s.TradeQuery(reqForm.Get("out_trade_no"))
|
||||
_, err = alipay.VerifySignWithCert(s.config.AlipayPublicKey, notifyReq)
|
||||
if err != nil {
|
||||
return NotifyVo{
|
||||
Status: Failure,
|
||||
Message: "error with verify sign: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return s.TradeQuery(request.Form.Get("out_trade_no"))
|
||||
}
|
||||
|
||||
func (s *AlipayService) TradeQuery(outTradeNo string) NotifyVo {
|
||||
var p = alipay.TradeQuery{}
|
||||
p.OutTradeNo = outTradeNo
|
||||
rsp, err := s.client.TradeQuery(p)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("out_trade_no", outTradeNo)
|
||||
|
||||
//查询订单
|
||||
rsp, err := s.client.TradeQuery(context.Background(), bm)
|
||||
if err != nil {
|
||||
return NotifyVo{
|
||||
Status: 0,
|
||||
Status: Failure,
|
||||
Message: "异步查询验证订单信息发生错误" + outTradeNo + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
if rsp.IsSuccess() == true && rsp.TradeStatus == "TRADE_SUCCESS" {
|
||||
if rsp.Response.TradeStatus == "TRADE_SUCCESS" {
|
||||
return NotifyVo{
|
||||
Status: 1,
|
||||
OutTradeNo: rsp.OutTradeNo,
|
||||
TradeNo: rsp.TradeNo,
|
||||
Amount: rsp.TotalAmount,
|
||||
Subject: rsp.Subject,
|
||||
Status: Success,
|
||||
OutTradeNo: rsp.Response.OutTradeNo,
|
||||
TradeId: rsp.Response.TradeNo,
|
||||
Amount: rsp.Response.TotalAmount,
|
||||
Subject: rsp.Response.Subject,
|
||||
Message: "OK",
|
||||
}
|
||||
} else {
|
||||
return NotifyVo{
|
||||
Status: 0,
|
||||
Status: Failure,
|
||||
Message: "异步查询验证订单信息发生错误" + outTradeNo,
|
||||
}
|
||||
}
|
||||
@@ -134,16 +132,3 @@ func readKey(filename string) (string, error) {
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
type NotifyVo struct {
|
||||
Status int
|
||||
OutTradeNo string
|
||||
TradeNo string
|
||||
Amount string
|
||||
Message string
|
||||
Subject string
|
||||
}
|
||||
|
||||
func (v NotifyVo) Success() bool {
|
||||
return v.Status == 1
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PayJS struct {
|
||||
type JPayService struct {
|
||||
config *types.JPayConfig
|
||||
}
|
||||
|
||||
func NewPayJS(appConfig *types.AppConfig) *PayJS {
|
||||
return &PayJS{
|
||||
func NewJPayService(appConfig *types.AppConfig) *JPayService {
|
||||
return &JPayService{
|
||||
config: &appConfig.JPayConfig,
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (r JPayReps) IsOK() bool {
|
||||
return r.ReturnMsg == "SUCCESS"
|
||||
}
|
||||
|
||||
func (js *PayJS) Pay(param JPayReq) JPayReps {
|
||||
func (js *JPayService) Pay(param JPayReq) JPayReps {
|
||||
param.NotifyURL = js.config.NotifyURL
|
||||
var p = url.Values{}
|
||||
encode := utils.JsonEncode(param)
|
||||
@@ -86,13 +86,13 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
|
||||
return data
|
||||
}
|
||||
|
||||
func (js *PayJS) PayH5(p url.Values) string {
|
||||
func (js *JPayService) PayH5(p url.Values) string {
|
||||
p.Add("mchid", js.config.AppId)
|
||||
p.Add("sign", js.sign(p))
|
||||
return fmt.Sprintf("%s/api/cashier?%s", js.config.ApiURL, p.Encode())
|
||||
}
|
||||
|
||||
func (js *PayJS) sign(params url.Values) string {
|
||||
func (js *JPayService) sign(params url.Values) string {
|
||||
params.Del(`sign`)
|
||||
var keys = make([]string, 0, 0)
|
||||
for key := range params {
|
||||
@@ -117,20 +117,18 @@ func (js *PayJS) sign(params url.Values) string {
|
||||
return strings.ToUpper(md5res)
|
||||
}
|
||||
|
||||
// Check 查询订单支付状态
|
||||
// TradeVerify 查询订单支付状态
|
||||
// @param tradeNo 支付平台交易 ID
|
||||
func (js *PayJS) Check(tradeNo string) error {
|
||||
func (js *JPayService) TradeVerify(tradeNo string) error {
|
||||
apiURL := fmt.Sprintf("%s/api/check", js.config.ApiURL)
|
||||
params := url.Values{}
|
||||
params.Add("payjs_order_id", tradeNo)
|
||||
params.Add("sign", js.sign(params))
|
||||
data := strings.NewReader(params.Encode())
|
||||
resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", data)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with http reqeust: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
||||
19
api/service/payment/types.go
Normal file
19
api/service/payment/types.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package payment
|
||||
|
||||
type NotifyVo struct {
|
||||
Status int
|
||||
OutTradeNo string // 商户订单号
|
||||
TradeId string // 交易ID
|
||||
Amount string // 交易金额
|
||||
Message string
|
||||
Subject string
|
||||
}
|
||||
|
||||
func (v NotifyVo) Success() bool {
|
||||
return v.Status == Success
|
||||
}
|
||||
|
||||
const (
|
||||
Success = 0
|
||||
Failure = 1
|
||||
)
|
||||
135
api/service/payment/wepay_service.go
Normal file
135
api/service/payment/wepay_service.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package payment
|
||||
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"geekai/core/types"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/wechat/v3"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WechatPayService struct {
|
||||
config *types.WechatPayConfig
|
||||
client *wechat.ClientV3
|
||||
}
|
||||
|
||||
func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) {
|
||||
config := appConfig.WechatPayConfig
|
||||
if !config.Enabled {
|
||||
logger.Info("Disabled WechatPay service")
|
||||
return nil, nil
|
||||
}
|
||||
priKey, err := readKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error with read App Private key: %v", err)
|
||||
}
|
||||
|
||||
client, err := wechat.NewClientV3(config.MchId, config.SerialNo, config.ApiV3Key, priKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error with initialize WechatPay service: %v", err)
|
||||
}
|
||||
err = client.AutoVerifySign()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error with autoVerifySign: %v", err)
|
||||
}
|
||||
//client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
return &WechatPayService{config: &config, client: client}, nil
|
||||
}
|
||||
|
||||
func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject string) (string, error) {
|
||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||
// 初始化 BodyMap
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", s.config.AppId).
|
||||
Set("mchid", s.config.MchId).
|
||||
Set("description", subject).
|
||||
Set("out_trade_no", outTradeNo).
|
||||
Set("time_expire", expire).
|
||||
Set("notify_url", s.config.NotifyURL).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", amount).
|
||||
Set("currency", "CNY")
|
||||
})
|
||||
|
||||
wxRsp, err := s.client.V3TransactionNative(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with client v3 transaction Native: %v", err)
|
||||
}
|
||||
if wxRsp.Code != wechat.Success {
|
||||
return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
|
||||
}
|
||||
return wxRsp.Response.CodeUrl, nil
|
||||
}
|
||||
|
||||
func (s *WechatPayService) PayUrlH5(outTradeNo string, amount int, subject string, ip string) (string, error) {
|
||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||
// 初始化 BodyMap
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", s.config.AppId).
|
||||
Set("mchid", s.config.MchId).
|
||||
Set("description", subject).
|
||||
Set("out_trade_no", outTradeNo).
|
||||
Set("time_expire", expire).
|
||||
Set("notify_url", s.config.NotifyURL).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", amount).
|
||||
Set("currency", "CNY")
|
||||
}).
|
||||
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
|
||||
bm.Set("payer_client_ip", ip).
|
||||
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
|
||||
bm.Set("type", "Wap")
|
||||
})
|
||||
})
|
||||
|
||||
wxRsp, err := s.client.V3TransactionH5(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with client v3 transaction H5: %v", err)
|
||||
}
|
||||
if wxRsp.Code != wechat.Success {
|
||||
return "", fmt.Errorf("error with generating pay url: %v", wxRsp.Error)
|
||||
}
|
||||
return wxRsp.Response.H5Url, nil
|
||||
}
|
||||
|
||||
type NotifyResponse struct {
|
||||
Code string `json:"code"`
|
||||
Message string `xml:"message"`
|
||||
}
|
||||
|
||||
// TradeVerify 交易验证
|
||||
func (s *WechatPayService) TradeVerify(request *http.Request) NotifyVo {
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
if err != nil {
|
||||
return NotifyVo{Status: 1, Message: fmt.Sprintf("error with client v3 parse notify: %v", err)}
|
||||
}
|
||||
|
||||
// TODO: 这里验签程序有 Bug,一直报错:crypto/rsa: verification error,先暂时取消验签
|
||||
//err = notifyReq.VerifySignByPK(s.client.WxPublicKey())
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("error with client v3 verify sign: %v", err)
|
||||
//}
|
||||
|
||||
// 解密支付密文,验证订单信息
|
||||
result, err := notifyReq.DecryptPayCipherText(s.config.ApiV3Key)
|
||||
if err != nil {
|
||||
return NotifyVo{Status: Failure, Message: fmt.Sprintf("error with client v3 decrypt: %v", err)}
|
||||
}
|
||||
|
||||
return NotifyVo{
|
||||
Status: Success,
|
||||
OutTradeNo: result.OutTradeNo,
|
||||
TradeId: result.TransactionId,
|
||||
Amount: fmt.Sprintf("%.2f", float64(result.Amount.Total)/100),
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func (p *ServicePool) CheckTaskStatus() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 10)
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
|
||||
return
|
||||
}
|
||||
task.Params.Seed = int64(utils.IntValue(utils.InterfaceToString(info["seed"]), -1))
|
||||
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(model.SdJob{ImgURL: imgURL, Params: utils.JsonEncode(task.Params)})
|
||||
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(model.SdJob{ImgURL: imgURL, Params: utils.JsonEncode(task.Params), Prompt: task.Params.Prompt})
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ func NewSmtpService(appConfig *types.AppConfig) *SmtpService {
|
||||
}
|
||||
|
||||
func (s *SmtpService) SendVerifyCode(to string, code int) error {
|
||||
subject := "Geek-AI 注册验证码"
|
||||
body := fmt.Sprintf("您正在注册 Geek-AI 助手账户,注册验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", code)
|
||||
subject := fmt.Sprintf("%s 注册验证码", s.config.AppName)
|
||||
body := fmt.Sprintf("您正在注册 %s 账户,注册验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", s.config.AppName, code)
|
||||
|
||||
auth := smtp.PlainAuth("", s.config.From, s.config.Password, s.config.Host)
|
||||
if s.config.UseTls {
|
||||
|
||||
@@ -106,23 +106,26 @@ func (e *XXLJobExecutor) ResetVipPower(cxt context.Context, param *xxl.RunReq) (
|
||||
e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("vip", false)
|
||||
continue
|
||||
}
|
||||
// update user
|
||||
tx := e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("power", gorm.Expr("power + ?", config.VipMonthPower))
|
||||
// 记录算力变动日志
|
||||
if tx.Error == nil {
|
||||
var user model.User
|
||||
e.db.Where("id", u.Id).First(&user)
|
||||
e.db.Create(&model.PowerLog{
|
||||
UserId: u.Id,
|
||||
Username: u.Username,
|
||||
Type: types.PowerRecharge,
|
||||
Amount: config.VipMonthPower,
|
||||
Mark: types.PowerAdd,
|
||||
Balance: user.Power,
|
||||
Model: "系统盘点",
|
||||
Remark: fmt.Sprintf("VIP会员每月算力派发,:%d", config.VipMonthPower),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
if u.Power < config.VipMonthPower {
|
||||
power := config.VipMonthPower - u.Power
|
||||
// update user
|
||||
tx := e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("power", gorm.Expr("power + ?", power))
|
||||
// 记录算力变动日志
|
||||
if tx.Error == nil {
|
||||
var user model.User
|
||||
e.db.Where("id", u.Id).First(&user)
|
||||
e.db.Create(&model.PowerLog{
|
||||
UserId: u.Id,
|
||||
Username: u.Username,
|
||||
Type: types.PowerRecharge,
|
||||
Amount: power,
|
||||
Mark: types.PowerAdd,
|
||||
Balance: user.Power,
|
||||
Model: "系统盘点",
|
||||
Remark: fmt.Sprintf("VIP会员每月算力派发,:%d", config.VipMonthPower),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info("月底盘点完成!")
|
||||
|
||||
@@ -2,11 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func main() {
|
||||
text := "https://nk.img.r9it.com/chatgpt-plus/1712709360012445.png"
|
||||
parse, _ := url.Parse(text)
|
||||
fmt.Println(fmt.Sprintf("%s://%s", parse.Scheme, parse.Host))
|
||||
fmt.Println(fmt.Sprintf("%v", float64(90)/100))
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type apiErrRes struct {
|
||||
|
||||
func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
|
||||
var apiKey model.ApiKey
|
||||
res := db.Where("platform = ?", types.OpenAI.Value).Where("type", "chat").Where("enabled = ?", true).First(&apiKey)
|
||||
res := db.Where("platform", types.OpenAI.Value).Where("type", "chat").Where("enabled", true).First(&apiKey)
|
||||
if res.Error != nil {
|
||||
return "", fmt.Errorf("error with fetch OpenAI API KEY:%v", res.Error)
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
|
||||
r, err := client.R().SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||
SetBody(types.ApiRequest{
|
||||
Model: "gpt-3.5-turbo-0125",
|
||||
Model: "gpt-3.5-turbo",
|
||||
Temperature: 0.9,
|
||||
MaxTokens: 1024,
|
||||
Stream: false,
|
||||
|
||||
Reference in New Issue
Block a user