mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-12-25 17:46:00 +08:00
@@ -1,3 +1,8 @@
|
||||
// Package cmd
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -13,11 +18,11 @@ var (
|
||||
Brief: "系统授权,当为第三方客户开发应用项目不想将源码和可执行文件让其随意使用时,可以通过授权的方式约束使用方。",
|
||||
Description: `目前已实现,一对一、一对多、有效期授权,具体使用可以参考现有逻辑结合实际场景进行改造`,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
service.TCPAuth().Start(ctx)
|
||||
service.AuthClient().Start(ctx)
|
||||
|
||||
// 退出信号监听
|
||||
signalListen(ctx, func(sig os.Signal) {
|
||||
service.TCPAuth().Stop(ctx)
|
||||
service.AuthClient().Stop(ctx)
|
||||
})
|
||||
|
||||
// 信号监听
|
||||
@@ -26,7 +31,6 @@ var (
|
||||
case <-serverCloseSignal:
|
||||
// ...
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -32,6 +31,7 @@ var (
|
||||
>> 所有服务 [go run main.go] 热编译 [gf run main.go]
|
||||
>> HTTP服务 [go run main.go http]
|
||||
>> 消息队列 [go run main.go queue]
|
||||
>> 定时任务 [go run main.go cron]
|
||||
>> 查看帮助 [go run main.go help]
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
@@ -59,6 +59,12 @@ var (
|
||||
}
|
||||
})
|
||||
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
if err := Cron.Func(ctx, parser); err != nil {
|
||||
g.Log().Fatal(ctx, "cron start fail:", err)
|
||||
}
|
||||
})
|
||||
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
if err := Http.Func(ctx, parser); err != nil {
|
||||
g.Log().Fatal(ctx, "http server start fail:", err)
|
||||
@@ -73,14 +79,14 @@ var (
|
||||
// ...
|
||||
}
|
||||
|
||||
g.Log().Info(ctx, "service successfully closed ..")
|
||||
g.Log().Debug(ctx, "service successfully closed ..")
|
||||
return
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Main.AddCommand(Http, Queue, Tools, Auth, All, Help); err != nil {
|
||||
if err := Main.AddCommand(All, Http, Queue, Cron, Auth, Tools, Help); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
serverCloseSignal = make(chan struct{}, 1)
|
||||
|
||||
42
server/internal/cmd/cron.go
Normal file
42
server/internal/cmd/cron.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package cmd
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"hotgo/internal/crons"
|
||||
"hotgo/internal/service"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
Cron = &gcmd.Command{
|
||||
Name: "cron",
|
||||
Brief: "定时任务,用来部署一些可独立运行的定时任务,通过tcp方式和后台保持长连接通讯,动态调整任务属性。",
|
||||
Description: ``,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
// 启动定时任务
|
||||
service.SysCron().StartCron(ctx)
|
||||
|
||||
// tcp客户端
|
||||
service.CronClient().Start(ctx)
|
||||
|
||||
// 退出信号监听
|
||||
signalListen(ctx, func(sig os.Signal) {
|
||||
service.CronClient().Stop(ctx)
|
||||
crons.StopALL()
|
||||
serverCloseSignal <- struct{}{}
|
||||
})
|
||||
|
||||
select {
|
||||
case <-serverCloseSignal:
|
||||
// ...
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -38,7 +38,10 @@ var (
|
||||
r.Response.Writeln("403 - 网站拒绝显示此网页")
|
||||
})
|
||||
|
||||
// 请求结束事件回调
|
||||
// 初始化请求前回调
|
||||
s.BindHookHandler("/*any", ghttp.HookBeforeServe, service.Hook().BeforeServe)
|
||||
|
||||
// 请求响应结束后回调
|
||||
s.BindHookHandler("/*any", ghttp.HookAfterOutput, service.Hook().AfterOutput)
|
||||
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
@@ -68,11 +71,8 @@ var (
|
||||
addons.RegisterModulesRouter(ctx, group)
|
||||
})
|
||||
|
||||
// 启动定时任务
|
||||
service.SysCron().StartCron(ctx)
|
||||
|
||||
//// 启动TCP服务
|
||||
//service.TCPServer().Start(ctx)
|
||||
// 启动tcp服务
|
||||
service.TCPServer().Start(ctx)
|
||||
|
||||
// https
|
||||
setSSL(ctx, s)
|
||||
@@ -82,7 +82,7 @@ var (
|
||||
s.Shutdown()
|
||||
crons.StopALL()
|
||||
websocket.Stop()
|
||||
//service.TCPServer().Stop(ctx)
|
||||
service.TCPServer().Stop(ctx)
|
||||
})
|
||||
|
||||
// Just run the server.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"hotgo/internal/library/queue"
|
||||
"hotgo/utility/simple"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,9 +19,21 @@ var (
|
||||
Brief: "消息队列",
|
||||
Description: ``,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
g.Log().Debug(ctx, "start queue consumer..")
|
||||
queue.StartConsumersListener(ctx)
|
||||
g.Log().Debug(ctx, "start queue consumer success..")
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
g.Log().Debug(ctx, "start queue consumer..")
|
||||
queue.StartConsumersListener(ctx)
|
||||
g.Log().Debug(ctx, "start queue consumer success..")
|
||||
})
|
||||
|
||||
// 信号监听
|
||||
signalListen(ctx, signalHandlerForOverall)
|
||||
|
||||
select {
|
||||
case <-serverCloseSignal:
|
||||
// ...
|
||||
}
|
||||
|
||||
g.Log().Debug(ctx, "queue successfully closed ..")
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
var (
|
||||
Tools = &gcmd.Command{
|
||||
Name: "tools",
|
||||
Brief: "工具",
|
||||
Brief: "常用工具",
|
||||
Description: ``,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
flags := parser.GetOptAll()
|
||||
@@ -39,12 +38,12 @@ var (
|
||||
g.Log().Fatal(ctx, "casbin参数不能为空")
|
||||
return
|
||||
}
|
||||
casbin.InitEnforcer(ctx)
|
||||
if a1 == "clear" {
|
||||
if err := casbin.Clear(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if a1 == "refresh" {
|
||||
casbin.InitEnforcer(ctx)
|
||||
if err := casbin.Refresh(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
const (
|
||||
|
||||
13
server/internal/consts/cash.go
Normal file
13
server/internal/consts/cash.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
// 提现状态
|
||||
const (
|
||||
CashStatusWait = 1 // 处理中
|
||||
CashStatusOk = 2 // 提现成功
|
||||
CashStatusFail = 3 // 提现异常
|
||||
)
|
||||
@@ -3,11 +3,11 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package consts
|
||||
|
||||
// ContextKey 上下文
|
||||
const (
|
||||
ContextKey = "HotGoContext" // http上下文变量名称
|
||||
ContextKeyCronArgs = "args" // 定时任务参数上下文变量名称
|
||||
ContextKey = "HotGoContext" // http上下文变量名称
|
||||
ContextKeyCronArgs = "args" // 定时任务参数上下文变量名称
|
||||
ContextTCPKey = "HotGoTCPContext" // tcp上下文变量名称
|
||||
)
|
||||
|
||||
85
server/internal/consts/credit_log.go
Normal file
85
server/internal/consts/credit_log.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
const (
|
||||
CreditTypeBalance = "balance" // 余额
|
||||
CreditTypeIntegral = "integral" // 积分
|
||||
)
|
||||
|
||||
const (
|
||||
CreditGroupDecr = "decr" // 扣款
|
||||
CreditGroupIncr = "incr" // 加款
|
||||
CreditGroupOpDecr = "op_decr" // 操作扣款
|
||||
CreditGroupOpIncr = "op_incr" // 操作加款
|
||||
CreditGroupBalanceRecharge = "balance_recharge" // 余额充值
|
||||
CreditGroupBalanceRefund = "balance_refund" // 余额退款
|
||||
CreditGroupApplyCash = "apply_cash" // 申请提现
|
||||
)
|
||||
|
||||
// CreditTypeOptions 变动类型
|
||||
var CreditTypeOptions = []g.Map{
|
||||
{
|
||||
"key": CreditTypeBalance,
|
||||
"value": CreditTypeBalance,
|
||||
"label": "余额",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": CreditTypeIntegral,
|
||||
"value": CreditTypeIntegral,
|
||||
"label": "积分",
|
||||
"listClass": "info",
|
||||
},
|
||||
}
|
||||
|
||||
// CreditGroupOptions 变动分组
|
||||
var CreditGroupOptions = []g.Map{
|
||||
{
|
||||
"key": CreditGroupDecr,
|
||||
"value": CreditGroupDecr,
|
||||
"label": "扣款",
|
||||
"listClass": "warning",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupIncr,
|
||||
"value": CreditGroupIncr,
|
||||
"label": "加款",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupOpDecr,
|
||||
"value": CreditGroupOpDecr,
|
||||
"label": "操作扣款",
|
||||
"listClass": "warning",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupOpIncr,
|
||||
"value": CreditGroupOpIncr,
|
||||
"label": "操作加款",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupBalanceRefund,
|
||||
"value": CreditGroupBalanceRefund,
|
||||
"label": "余额退款",
|
||||
"listClass": "warning",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupBalanceRecharge,
|
||||
"value": CreditGroupBalanceRecharge,
|
||||
"label": "余额充值",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": CreditGroupApplyCash,
|
||||
"value": CreditGroupApplyCash,
|
||||
"label": "申请提现",
|
||||
"listClass": "info",
|
||||
},
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
// 公告类型
|
||||
|
||||
129
server/internal/consts/order.go
Normal file
129
server/internal/consts/order.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// 订单分组
|
||||
// 为不同的业务订单设置不同的分组,分组可以设置不同的业务回调方法
|
||||
|
||||
const (
|
||||
OrderGroupDefault = "order" // 普通订单
|
||||
OrderGroupAdminOrder = "admin_order" // 后台充值订单
|
||||
// 还可以设置其他,方便后期扩展..
|
||||
)
|
||||
|
||||
// 订单类型
|
||||
|
||||
const (
|
||||
OrderTypeBalance = "balance" // 余额充值
|
||||
OrderTypeProduct = "product" // 购买产品
|
||||
)
|
||||
|
||||
const (
|
||||
OrderStatusALL int64 = -1 // 全部状态
|
||||
OrderStatusNotPay = 1 // 待付款
|
||||
OrderStatusPay = 2 // 已付款
|
||||
OrderStatusShipments = 3 // 已发货
|
||||
OrderStatusDone = 4 // 已完成
|
||||
OrderStatusClose = 5 // 已关闭
|
||||
OrderStatusReturnRequest = 6 // 申请退款
|
||||
OrderStatusReturning = 7 // 退款中
|
||||
OrderStatusReturned = 8 // 已退款
|
||||
OrderStatusReturnReject = 9 // 拒绝退款
|
||||
)
|
||||
|
||||
var OrderStatusSlice = []int64{
|
||||
OrderStatusALL,
|
||||
OrderStatusNotPay, OrderStatusPay, OrderStatusShipments, OrderStatusDone, OrderStatusClose,
|
||||
OrderStatusReturnRequest, OrderStatusReturning, OrderStatusReturned, OrderStatusReturnReject,
|
||||
}
|
||||
|
||||
// OrderStatusOptions 订单状态选项
|
||||
var OrderStatusOptions = []g.Map{
|
||||
{
|
||||
"key": OrderStatusALL,
|
||||
"value": OrderStatusALL,
|
||||
"label": "全部",
|
||||
"listClass": "info",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusNotPay,
|
||||
"value": OrderStatusNotPay,
|
||||
"label": "待付款",
|
||||
"listClass": "info",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusPay,
|
||||
"value": OrderStatusPay,
|
||||
"label": "已付款",
|
||||
"listClass": "info",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusShipments,
|
||||
"value": OrderStatusShipments,
|
||||
"label": "已发货",
|
||||
"listClass": "info",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusDone,
|
||||
"value": OrderStatusDone,
|
||||
"label": "已完成",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusClose,
|
||||
"value": OrderStatusClose,
|
||||
"label": "已关闭",
|
||||
"listClass": "default",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturnRequest,
|
||||
"value": OrderStatusReturnRequest,
|
||||
"label": "申请退款",
|
||||
"listClass": "warning",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturning,
|
||||
"value": OrderStatusReturning,
|
||||
"label": "退款中",
|
||||
"listClass": "default",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturned,
|
||||
"value": OrderStatusReturned,
|
||||
"label": "已退款",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturnReject,
|
||||
"value": OrderStatusReturnReject,
|
||||
"label": "拒绝退款",
|
||||
"listClass": "error",
|
||||
},
|
||||
}
|
||||
|
||||
// OrderAcceptRefundOptions 订单退款受理状态
|
||||
var OrderAcceptRefundOptions = []g.Map{
|
||||
{
|
||||
"key": OrderStatusReturnRequest,
|
||||
"value": OrderStatusReturnRequest,
|
||||
"label": "申请退款",
|
||||
"listClass": "warning",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturned,
|
||||
"value": OrderStatusReturned,
|
||||
"label": "已退款",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": OrderStatusReturnReject,
|
||||
"value": OrderStatusReturnReject,
|
||||
"label": "拒绝退款",
|
||||
"listClass": "error",
|
||||
},
|
||||
}
|
||||
97
server/internal/consts/pay.go
Normal file
97
server/internal/consts/pay.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// 支付方式
|
||||
|
||||
const (
|
||||
PayTypeALL = "" // 全部
|
||||
PayTypeWxPay = "wxpay" // 微信支付
|
||||
PayTypeAliPay = "alipay" // 支付宝
|
||||
PayTypeQQPay = "qqpay" // QQ支付
|
||||
)
|
||||
|
||||
var (
|
||||
PayTypeSlice = []string{
|
||||
PayTypeWxPay, PayTypeAliPay, PayTypeQQPay,
|
||||
}
|
||||
|
||||
PayTypeNameMap = map[string]string{
|
||||
PayTypeALL: "全部",
|
||||
PayTypeWxPay: "微信支付",
|
||||
PayTypeAliPay: "支付宝",
|
||||
PayTypeQQPay: "QQ支付",
|
||||
}
|
||||
)
|
||||
|
||||
// 交易方式
|
||||
|
||||
const (
|
||||
// 微信
|
||||
TradeTypeWxMP = "mp" // 公众号
|
||||
TradeTypeWxMini = "mini" // 小程序
|
||||
TradeTypeWxApp = "app" // APP
|
||||
TradeTypeWxScan = "scan" // 二维码扫码
|
||||
TradeTypeWxPos = "pos" // 二维码收款
|
||||
TradeTypeWxH5 = "h5" // H5
|
||||
|
||||
// 支付宝
|
||||
TradeTypeAliWeb = "web" // 电脑网页
|
||||
TradeTypeAliApp = "app" // APP
|
||||
TradeTypeAliScan = "scan" // 二维码扫码
|
||||
TradeTypeAliWap = "wap" // 手机网页
|
||||
TradeTypeAliPos = "pos" // 二维码收款
|
||||
|
||||
// QQ
|
||||
TradeTypeQQWeb = "qqweb" // PC网页
|
||||
TradeTypeQQWap = "qqwap" // 移动端
|
||||
)
|
||||
|
||||
var (
|
||||
TradeTypeWxSlice = []string{TradeTypeWxMP, TradeTypeWxMini, TradeTypeWxApp, TradeTypeWxScan, TradeTypeWxPos, TradeTypeWxH5}
|
||||
TradeTypeAliSlice = []string{TradeTypeAliWeb, TradeTypeAliApp, TradeTypeAliScan, TradeTypeAliWap, TradeTypeAliPos}
|
||||
TradeTypeQQSlice = []string{TradeTypeQQWeb, TradeTypeQQWap}
|
||||
)
|
||||
|
||||
// 支付状态
|
||||
|
||||
const (
|
||||
PayStatusWait = 1 // 待支付
|
||||
PayStatusOk = 2 // 已支付
|
||||
)
|
||||
|
||||
// 退款状态
|
||||
|
||||
const (
|
||||
RefundStatusNo = 1 // 未退款
|
||||
RefundStatusApply = 2 // 申请退款
|
||||
RefundStatusReject = 3 // 拒绝退款
|
||||
RefundStatusAgree = 4 // 同意退款,已退款
|
||||
)
|
||||
|
||||
// PayTypeOptions 支付方式选项
|
||||
var PayTypeOptions = []g.Map{
|
||||
{
|
||||
"key": PayTypeWxPay,
|
||||
"value": PayTypeWxPay,
|
||||
"label": "微信支付",
|
||||
"listClass": "success",
|
||||
},
|
||||
{
|
||||
"key": PayTypeAliPay,
|
||||
"value": PayTypeAliPay,
|
||||
"label": "支付宝",
|
||||
"listClass": "info",
|
||||
},
|
||||
{
|
||||
"key": PayTypeQQPay,
|
||||
"value": PayTypeQQPay,
|
||||
"label": "QQ支付",
|
||||
"listClass": "default",
|
||||
},
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
// Package consts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package consts
|
||||
|
||||
// 短信驱动
|
||||
|
||||
25
server/internal/consts/tcp.go
Normal file
25
server/internal/consts/tcp.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package consts
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TCPMsgCodeSuccess = 2000 // 成功的状态码
|
||||
)
|
||||
|
||||
// 定时任务
|
||||
const (
|
||||
TCPCronHeartbeatVerify = "tcpHeartbeatVerify"
|
||||
TCPCronHeartbeat = "tcpHeartbeat"
|
||||
TCPCronAuthVerify = "tcpAuthVerify"
|
||||
)
|
||||
|
||||
// 认证分组
|
||||
const (
|
||||
TCPClientGroupCron = "cron" // 定时任务
|
||||
TCPClientGroupQueue = "queue" // 消息队列
|
||||
TCPClientGroupAuth = "auth" // 服务授权
|
||||
)
|
||||
|
||||
const (
|
||||
TCPRpcTimeout = time.Second * 10 // rpc通讯超时时间, 默认10s
|
||||
)
|
||||
@@ -7,5 +7,5 @@ package consts
|
||||
|
||||
// VersionApp HotGo版本
|
||||
const (
|
||||
VersionApp = "2.5.3"
|
||||
VersionApp = "2.6.7"
|
||||
)
|
||||
|
||||
13
server/internal/consts/wechat.go
Normal file
13
server/internal/consts/wechat.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package consts
|
||||
|
||||
// 授权类型
|
||||
const (
|
||||
WechatAuthorizeOpenId = "openId" // 设置openid
|
||||
WechatAuthorizeBindLogin = "bindLogin" // 绑定微信登录
|
||||
)
|
||||
|
||||
// 应用授权作用域
|
||||
const (
|
||||
WechatScopeBase = "snsapi_base" // 只获取openid,无需用户授权
|
||||
WechatScopeUserinfo = "snsapi_userinfo" // 获取用户信息,需要授权
|
||||
)
|
||||
74
server/internal/controller/admin/admin/cash.go
Normal file
74
server/internal/controller/admin/admin/cash.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package admin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/api/admin/cash"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
Cash = cCash{}
|
||||
)
|
||||
|
||||
type cCash struct{}
|
||||
|
||||
// View 获取指定信息
|
||||
func (c *cCash) View(ctx context.Context, req *cash.ViewReq) (*cash.ViewRes, error) {
|
||||
data, err := service.AdminCash().View(ctx, adminin.CashViewInp{Id: req.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res cash.ViewRes
|
||||
res.CashViewModel = data
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// List 查看列表
|
||||
func (c *cCash) List(ctx context.Context, req *cash.ListReq) (res *cash.ListRes, err error) {
|
||||
var in adminin.CashListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list, totalCount, err := service.AdminCash().List(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = new(cash.ListRes)
|
||||
res.List = list
|
||||
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
|
||||
res.Page = req.Page
|
||||
res.PerPage = req.PerPage
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Apply 申请提现
|
||||
func (c *cCash) Apply(ctx context.Context, req *cash.ApplyReq) (res *cash.ApplyRes, err error) {
|
||||
err = service.AdminCash().Apply(ctx, adminin.CashApplyInp{
|
||||
Money: req.Money,
|
||||
MemberId: contexts.GetUserId(ctx),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Payment 提现打款处理
|
||||
func (c *cCash) Payment(ctx context.Context, req *cash.PaymentReq) (res *cash.PaymentRes, err error) {
|
||||
err = service.AdminCash().Payment(ctx, adminin.CashPaymentInp{
|
||||
Id: req.Id,
|
||||
Status: req.Status,
|
||||
Msg: req.Msg,
|
||||
})
|
||||
return
|
||||
}
|
||||
74
server/internal/controller/admin/admin/credits_log.go
Normal file
74
server/internal/controller/admin/admin/credits_log.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package admin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.5.3
|
||||
// @AutoGenerate Date 2023-04-15 15:59:58
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/creditslog"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/validate"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var (
|
||||
CreditsLog = cCreditsLog{}
|
||||
)
|
||||
|
||||
type cCreditsLog struct{}
|
||||
|
||||
// List 查看资产变动列表
|
||||
func (c *cCreditsLog) List(ctx context.Context, req *creditslog.ListReq) (res *creditslog.ListRes, err error) {
|
||||
var in adminin.CreditsLogListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, totalCount, err := service.AdminCreditsLog().List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(creditslog.ListRes)
|
||||
res.List = list
|
||||
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
|
||||
res.Page = req.Page
|
||||
res.PerPage = req.PerPage
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出资产变动列表
|
||||
func (c *cCreditsLog) Export(ctx context.Context, req *creditslog.ExportReq) (res *creditslog.ExportRes, err error) {
|
||||
var in adminin.CreditsLogListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminCreditsLog().Export(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Option 获取变动状态选项
|
||||
func (c *cCreditsLog) Option(ctx context.Context, req *creditslog.OptionReq) (res *creditslog.OptionRes, err error) {
|
||||
res = &creditslog.OptionRes{
|
||||
CreditType: consts.CreditTypeOptions,
|
||||
CreditGroup: consts.CreditGroupOptions,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package admin
|
||||
|
||||
import (
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/api/admin/dept"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
@@ -105,3 +105,23 @@ func (c *cDept) Status(ctx context.Context, req *dept.StatusReq) (res *dept.Stat
|
||||
err = service.AdminDept().Status(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Option 获取部门选项树
|
||||
func (c *cDept) Option(ctx context.Context, req *dept.OptionReq) (res *dept.OptionRes, err error) {
|
||||
var in adminin.DeptOptionInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, totalCount, err := service.AdminDept().Option(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(dept.OptionRes)
|
||||
res.DeptOptionModel = list
|
||||
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
|
||||
res.Page = req.Page
|
||||
res.PerPage = req.PerPage
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package admin
|
||||
|
||||
import (
|
||||
@@ -175,19 +174,6 @@ func (c *cMember) Edit(ctx context.Context, req *member.EditReq) (res *member.Ed
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 最大排序
|
||||
func (c *cMember) MaxSort(ctx context.Context, req *member.MaxSortReq) (res *member.MaxSortRes, err error) {
|
||||
var in = adminin.MemberMaxSortInp{Id: req.Id}
|
||||
data, err := service.AdminMember().MaxSort(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(member.MaxSortRes)
|
||||
res.Sort = data.Sort
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定信息
|
||||
func (c *cMember) View(ctx context.Context, req *member.ViewReq) (res *member.ViewRes, err error) {
|
||||
data, err := service.AdminMember().View(ctx, adminin.MemberViewInp{Id: req.Id})
|
||||
@@ -253,3 +239,33 @@ func (c *cMember) Select(ctx context.Context, req *member.SelectReq) (res *membe
|
||||
res = (*member.SelectRes)(&data)
|
||||
return
|
||||
}
|
||||
|
||||
// AddBalance 增加余额
|
||||
func (c *cMember) AddBalance(ctx context.Context, req *member.AddBalanceReq) (res *member.AddBalanceRes, err error) {
|
||||
var in adminin.MemberAddBalanceInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminMember().AddBalance(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// AddIntegral 增加积分
|
||||
func (c *cMember) AddIntegral(ctx context.Context, req *member.AddIntegralReq) (res *member.AddIntegralRes, err error) {
|
||||
var in adminin.MemberAddIntegralInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminMember().AddIntegral(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/api/admin/menu"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
@@ -21,30 +18,6 @@ var (
|
||||
|
||||
type cMenu struct{}
|
||||
|
||||
// RoleList 查询角色菜单列表
|
||||
func (c *cMenu) RoleList(ctx context.Context, req *menu.RoleListReq) (res *menu.RoleListRes, err error) {
|
||||
var in adminin.MenuRoleListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := service.AdminMenu().RoleList(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(menu.RoleListRes)
|
||||
res.CheckedKeys = data.CheckedKeys
|
||||
res.Menus = data.Menus
|
||||
return
|
||||
}
|
||||
|
||||
// SearchList 查询菜单列表
|
||||
func (c *cMenu) SearchList(ctx context.Context, req *menu.SearchListReq) (res *menu.SearchListRes, err error) {
|
||||
res, err = service.AdminMenu().SearchList(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 最大排序
|
||||
func (c *cMenu) MaxSort(ctx context.Context, req *menu.MaxSortReq) (res *menu.MaxSortRes, err error) {
|
||||
res, err = service.AdminMenu().MaxSort(ctx, req)
|
||||
|
||||
190
server/internal/controller/admin/admin/order.go
Normal file
190
server/internal/controller/admin/admin/order.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Package admin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/order"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/adminin"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/validate"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var (
|
||||
Order = cOrder{}
|
||||
)
|
||||
|
||||
type cOrder struct{}
|
||||
|
||||
// AcceptRefund 受理申请退款
|
||||
func (c *cOrder) AcceptRefund(ctx context.Context, req *order.AcceptRefundReq) (res *order.AcceptRefundRes, err error) {
|
||||
var in adminin.OrderAcceptRefundInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().AcceptRefund(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplyRefund 申请退款
|
||||
func (c *cOrder) ApplyRefund(ctx context.Context, req *order.ApplyRefundReq) (res *order.ApplyRefundRes, err error) {
|
||||
var in adminin.OrderApplyRefundInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().ApplyRefund(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Option 获取订单状态选项
|
||||
func (c *cOrder) Option(ctx context.Context, req *order.OptionReq) (res *order.OptionRes, err error) {
|
||||
res = &order.OptionRes{
|
||||
Status: consts.OrderStatusOptions,
|
||||
AcceptRefundStatus: consts.OrderAcceptRefundOptions,
|
||||
PayType: consts.PayTypeOptions,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create 创建充值订单
|
||||
func (c *cOrder) Create(ctx context.Context, req *order.CreateReq) (res *order.CreateRes, err error) {
|
||||
var in adminin.OrderCreateInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := service.AdminOrder().Create(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(order.CreateRes)
|
||||
res.OrderCreateModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// List 查看充值订单列表
|
||||
func (c *cOrder) List(ctx context.Context, req *order.ListReq) (res *order.ListRes, err error) {
|
||||
var in adminin.OrderListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, totalCount, err := service.AdminOrder().List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(order.ListRes)
|
||||
res.List = list
|
||||
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
|
||||
res.Page = req.Page
|
||||
res.PerPage = req.PerPage
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出充值订单列表
|
||||
func (c *cOrder) Export(ctx context.Context, req *order.ExportReq) (res *order.ExportRes, err error) {
|
||||
var in adminin.OrderListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().Export(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新充值订单
|
||||
func (c *cOrder) Edit(ctx context.Context, req *order.EditReq) (res *order.EditRes, err error) {
|
||||
var in adminin.OrderEditInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().Edit(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取指定充值订单信息
|
||||
func (c *cOrder) View(ctx context.Context, req *order.ViewReq) (res *order.ViewRes, err error) {
|
||||
var in adminin.OrderViewInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := service.AdminOrder().View(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(order.ViewRes)
|
||||
res.OrderViewModel = data
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除充值订单
|
||||
func (c *cOrder) Delete(ctx context.Context, req *order.DeleteReq) (res *order.DeleteRes, err error) {
|
||||
var in adminin.OrderDeleteInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().Delete(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新充值订单状态
|
||||
func (c *cOrder) Status(ctx context.Context, req *order.StatusReq) (res *order.StatusRes, err error) {
|
||||
var in adminin.OrderStatusInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.AdminOrder().Status(ctx, in)
|
||||
return
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package admin
|
||||
|
||||
import (
|
||||
|
||||
49
server/internal/controller/admin/common/wechat.go
Normal file
49
server/internal/controller/admin/common/wechat.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Package common
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/api/admin/common"
|
||||
"hotgo/internal/model/input/commonin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/validate"
|
||||
)
|
||||
|
||||
var (
|
||||
Wechat = cWechat{}
|
||||
)
|
||||
|
||||
type cWechat struct{}
|
||||
|
||||
func (c *cWechat) Authorize(ctx context.Context, req *common.WechatAuthorizeReq) (res *common.WechatAuthorizeRes, err error) {
|
||||
var in commonin.WechatAuthorizeInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = service.CommonWechat().Authorize(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cWechat) AuthorizeCall(ctx context.Context, req *common.WechatAuthorizeCallReq) (res *common.WechatAuthorizeCallRes, err error) {
|
||||
var in commonin.WechatAuthorizeCallInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = service.CommonWechat().AuthorizeCall(ctx, in)
|
||||
return
|
||||
}
|
||||
64
server/internal/controller/admin/pay/refund.go
Normal file
64
server/internal/controller/admin/pay/refund.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Package pay
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.5.3
|
||||
// @AutoGenerate Date 2023-04-15 15:59:58
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/admin/pay"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/payin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/validate"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var (
|
||||
Refund = cRefund{}
|
||||
)
|
||||
|
||||
type cRefund struct{}
|
||||
|
||||
// List 查看交易退款列表
|
||||
func (c *cRefund) List(ctx context.Context, req *pay.RefundListReq) (res *pay.RefundListRes, err error) {
|
||||
var in payin.PayRefundListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, totalCount, err := service.PayRefund().List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(pay.RefundListRes)
|
||||
res.List = list
|
||||
res.PageCount = form.CalPageCount(totalCount, req.PerPage)
|
||||
res.Page = req.Page
|
||||
res.PerPage = req.PerPage
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出交易退款列表
|
||||
func (c *cRefund) Export(ctx context.Context, req *pay.RefundExportReq) (res *pay.RefundExportRes, err error) {
|
||||
var in payin.PayRefundListInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.PreFilter(ctx, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.PayRefund().Export(ctx, in)
|
||||
return
|
||||
}
|
||||
@@ -56,3 +56,10 @@ func (c *cConfig) TypeSelect(ctx context.Context, req *config.TypeSelectReq) (re
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCash 获取指定分组的配置
|
||||
func (c *cConfig) GetCash(ctx context.Context, req *config.GetCashReq) (res *config.GetCashRes, err error) {
|
||||
res = new(config.GetCashRes)
|
||||
res.GetConfigModel, err = service.SysConfig().GetConfigByGroup(ctx, sysin.GetConfigInp{Group: "cash"})
|
||||
return
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package sys
|
||||
|
||||
import (
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/api/admin/cron"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
@@ -24,23 +24,23 @@ type cCron struct{}
|
||||
|
||||
// Delete 删除
|
||||
func (c *cCron) Delete(ctx context.Context, req *cron.DeleteReq) (res *cron.DeleteRes, err error) {
|
||||
var in sysin.CronDeleteInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
var in = new(msgin.CronDelete)
|
||||
if err = gconv.Scan(req, &in.CronDeleteInp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.SysCron().Delete(ctx, in)
|
||||
err = service.TCPServer().CronDelete(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 更新
|
||||
func (c *cCron) Edit(ctx context.Context, req *cron.EditReq) (res *cron.EditRes, err error) {
|
||||
var in sysin.CronEditInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
var in = new(msgin.CronEdit)
|
||||
if err = gconv.Scan(req, &in.CronEditInp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = service.SysCron().Edit(ctx, in)
|
||||
err = service.TCPServer().CronEdit(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -105,10 +105,11 @@ func (c *cCron) OnlineExec(ctx context.Context, req *cron.OnlineExecReq) (res *c
|
||||
return nil, gerror.New("定时任务ID不能为空")
|
||||
}
|
||||
|
||||
var in sysin.OnlineExecInp
|
||||
if err = gconv.Scan(req, &in); err != nil {
|
||||
var in = new(msgin.CronOnlineExec)
|
||||
if err = gconv.Scan(req, &in.OnlineExecInp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return res, service.SysCron().OnlineExec(ctx, in)
|
||||
err = service.TCPServer().CronOnlineExec(ctx, in)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
// @AutoGenerate Version 2.1.4
|
||||
// @AutoGenerate Date 2023-02-20 16:41:58
|
||||
//
|
||||
// @AutoGenerate Version 2.5.3
|
||||
// @AutoGenerate Date 2023-04-28 15:28:40
|
||||
package sys
|
||||
|
||||
import (
|
||||
|
||||
53
server/internal/controller/api/pay/notify.go
Normal file
53
server/internal/controller/api/pay/notify.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Package pay
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/api/api/pay"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/payin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
Notify = cNotify{}
|
||||
)
|
||||
|
||||
type cNotify struct{}
|
||||
|
||||
// AliPay 支付宝回调
|
||||
func (c *cNotify) AliPay(ctx context.Context, req *pay.NotifyAliPayReq) (res *pay.NotifyAliPayRes, err error) {
|
||||
_, err = service.Pay().Notify(ctx, payin.PayNotifyInp{PayType: consts.PayTypeAliPay})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &pay.NotifyAliPayRes{PayType: consts.PayTypeAliPay, Message: "success"}
|
||||
return
|
||||
}
|
||||
|
||||
// WxPay 微信支付回调
|
||||
func (c *cNotify) WxPay(ctx context.Context, req *pay.NotifyWxPayReq) (res *pay.NotifyWxPayRes, err error) {
|
||||
_, err = service.Pay().Notify(ctx, payin.PayNotifyInp{PayType: consts.PayTypeWxPay})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &pay.NotifyWxPayRes{PayType: consts.PayTypeWxPay, Code: "SUCCESS", Message: "收单成功"}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QQPay QQ支付回调
|
||||
func (c *cNotify) QQPay(ctx context.Context, req *pay.NotifyQQPayReq) (res *pay.NotifyQQPayRes, err error) {
|
||||
_, err = service.Pay().Notify(ctx, payin.PayNotifyInp{PayType: consts.PayTypeQQPay})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &pay.NotifyQQPayRes{PayType: consts.PayTypeQQPay, Message: "SUCCESS"}
|
||||
return
|
||||
}
|
||||
43
server/internal/crons/close_order.go
Normal file
43
server/internal/crons/close_order.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Package crons
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package crons
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cronList = append(cronList, CloseOrder)
|
||||
}
|
||||
|
||||
// CloseOrder 取消过期订单
|
||||
var CloseOrder = &cCloseOrder{name: "close_order"}
|
||||
|
||||
type cCloseOrder struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (c *cCloseOrder) GetName() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// Execute 执行任务
|
||||
func (c *cCloseOrder) Execute(ctx context.Context) {
|
||||
_, err := service.AdminOrder().Model(ctx).
|
||||
Where(dao.AdminOrder.Columns().Status, consts.OrderStatusNotPay).
|
||||
WhereLTE(dao.AdminOrder.Columns().CreatedAt, gtime.Now().AddDate(0, 0, -1)).
|
||||
Data(g.Map{
|
||||
dao.AdminOrder.Columns().Status: consts.OrderStatusClose,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
g.Log().Warning(ctx, "cron CloseOrder Execute err:%+v", err)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package crons
|
||||
|
||||
import (
|
||||
@@ -177,12 +176,13 @@ func Delete(sysCron *entity.SysCron) (err error) {
|
||||
if sysCron == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range gcron.Entries() {
|
||||
if v.Name == sysCron.Name {
|
||||
gcron.Remove(v.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
27
server/internal/dao/admin_cash.go
Normal file
27
server/internal/dao/admin_cash.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalAdminCashDao is internal type for wrapping internal DAO implements.
|
||||
type internalAdminCashDao = *internal.AdminCashDao
|
||||
|
||||
// adminCashDao is the data access object for table hg_admin_cash.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type adminCashDao struct {
|
||||
internalAdminCashDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminCash is globally public accessible object for table hg_admin_cash operations.
|
||||
AdminCash = adminCashDao{
|
||||
internal.NewAdminCashDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
27
server/internal/dao/admin_credits_log.go
Normal file
27
server/internal/dao/admin_credits_log.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalAdminCreditsLogDao is internal type for wrapping internal DAO implements.
|
||||
type internalAdminCreditsLogDao = *internal.AdminCreditsLogDao
|
||||
|
||||
// adminCreditsLogDao is the data access object for table hg_admin_credits_log.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type adminCreditsLogDao struct {
|
||||
internalAdminCreditsLogDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminCreditsLog is globally public accessible object for table hg_admin_credits_log operations.
|
||||
AdminCreditsLog = adminCreditsLogDao{
|
||||
internal.NewAdminCreditsLogDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
@@ -48,18 +48,3 @@ func (dao *adminDeptDao) IsUniqueName(ctx context.Context, id int64, name string
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// TopPid 获取最上级pid
|
||||
func (dao *adminDeptDao) TopPid(ctx context.Context, data *entity.AdminDept) (int64, error) {
|
||||
var pidData *entity.AdminDept
|
||||
if data.Pid == 0 {
|
||||
return data.Id, nil
|
||||
}
|
||||
err := dao.Ctx(ctx).Where("id", data.Pid).Scan(&pidData)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return dao.TopPid(ctx, pidData)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao/internal"
|
||||
"hotgo/internal/model/entity"
|
||||
)
|
||||
@@ -54,22 +53,3 @@ func (dao *adminMemberPostDao) UpdatePostIds(ctx context.Context, memberId int64
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMemberByIds 获取指定关联员的岗位ids
|
||||
func (dao *adminMemberPostDao) GetMemberByIds(ctx context.Context, memberId int64) (postIds []int64, err error) {
|
||||
var list []*entity.AdminMemberPost
|
||||
err = dao.Ctx(ctx).
|
||||
Fields("post_id").
|
||||
Where("member_id", memberId).
|
||||
Scan(&list)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return postIds, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
postIds = append(postIds, list[i].PostId)
|
||||
}
|
||||
|
||||
return postIds, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao/internal"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/entity"
|
||||
)
|
||||
|
||||
@@ -70,68 +69,3 @@ func (dao *adminMenuDao) IsUniqueName(ctx context.Context, id int64, name string
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (dao *adminMenuDao) GenLabelTreeList(ctx context.Context, pid int64) ([]*model.LabelTreeMenu, error) {
|
||||
|
||||
var (
|
||||
newLst []*model.LabelTreeMenu
|
||||
)
|
||||
if err := dao.Ctx(ctx).Where("pid", pid).Order("sort asc,id desc").Scan(&newLst); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(newLst); i++ {
|
||||
newLst[i].Key = newLst[i].Id
|
||||
newLst[i].Label = newLst[i].Name
|
||||
err := dao.Ctx(ctx).Where("pid", newLst[i].Id).Order("sort asc,id desc").Scan(&newLst[i].Children)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i2 := 0; i2 < len(newLst[i].Children); i2++ {
|
||||
newLst[i].Children[i2].Key = newLst[i].Children[i2].Id
|
||||
newLst[i].Children[i2].Label = newLst[i].Children[i2].Name
|
||||
}
|
||||
}
|
||||
|
||||
return newLst, nil
|
||||
}
|
||||
|
||||
// GenTreeList 生成树列表
|
||||
func (dao *adminMenuDao) GenTreeList(ctx context.Context, pid int64, ids []int64) ([]*model.TreeMenu, error) {
|
||||
|
||||
var (
|
||||
newLst []*model.TreeMenu
|
||||
)
|
||||
if err := dao.Ctx(ctx).Where("id", ids).Where("pid", pid).Order("sort asc,id desc").Scan(&newLst); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(newLst); i++ {
|
||||
err := dao.Ctx(ctx).Where("pid", newLst[i].Id).Order("sort asc,id desc").Scan(&newLst[i].Children)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newLst, nil
|
||||
}
|
||||
|
||||
// TopPid 获取最上级pid
|
||||
func (dao *adminMenuDao) TopPid(ctx context.Context, data *entity.AdminMenu) (int64, error) {
|
||||
var pidData *entity.AdminMenu
|
||||
if data.Pid == 0 {
|
||||
return data.Id, nil
|
||||
}
|
||||
err := dao.Ctx(ctx).Where("id", data.Pid).Scan(&pidData)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return dao.TopPid(ctx, pidData)
|
||||
}
|
||||
|
||||
27
server/internal/dao/admin_oauth.go
Normal file
27
server/internal/dao/admin_oauth.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalAdminOauthDao is internal type for wrapping internal DAO implements.
|
||||
type internalAdminOauthDao = *internal.AdminOauthDao
|
||||
|
||||
// adminOauthDao is the data access object for table hg_admin_oauth.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type adminOauthDao struct {
|
||||
internalAdminOauthDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminOauth is globally public accessible object for table hg_admin_oauth operations.
|
||||
AdminOauth = adminOauthDao{
|
||||
internal.NewAdminOauthDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
27
server/internal/dao/admin_order.go
Normal file
27
server/internal/dao/admin_order.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalAdminOrderDao is internal type for wrapping internal DAO implements.
|
||||
type internalAdminOrderDao = *internal.AdminOrderDao
|
||||
|
||||
// adminOrderDao is the data access object for table hg_admin_order.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type adminOrderDao struct {
|
||||
internalAdminOrderDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AdminOrder is globally public accessible object for table hg_admin_order operations.
|
||||
AdminOrder = adminOrderDao{
|
||||
internal.NewAdminOrderDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
91
server/internal/dao/internal/admin_cash.go
Normal file
91
server/internal/dao/internal/admin_cash.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AdminCashDao is the data access object for table hg_admin_cash.
|
||||
type AdminCashDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns AdminCashColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// AdminCashColumns defines and stores column names for table hg_admin_cash.
|
||||
type AdminCashColumns struct {
|
||||
Id string // ID
|
||||
MemberId string // 管理员ID
|
||||
Money string // 提现金额
|
||||
Fee string // 手续费
|
||||
LastMoney string // 最终到账金额
|
||||
Ip string // 申请人IP
|
||||
Status string // 状态码
|
||||
Msg string // 处理结果
|
||||
HandleAt string // 处理时间
|
||||
CreatedAt string // 申请时间
|
||||
}
|
||||
|
||||
// adminCashColumns holds the columns for table hg_admin_cash.
|
||||
var adminCashColumns = AdminCashColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
Money: "money",
|
||||
Fee: "fee",
|
||||
LastMoney: "last_money",
|
||||
Ip: "ip",
|
||||
Status: "status",
|
||||
Msg: "msg",
|
||||
HandleAt: "handle_at",
|
||||
CreatedAt: "created_at",
|
||||
}
|
||||
|
||||
// NewAdminCashDao creates and returns a new DAO object for table data access.
|
||||
func NewAdminCashDao() *AdminCashDao {
|
||||
return &AdminCashDao{
|
||||
group: "default",
|
||||
table: "hg_admin_cash",
|
||||
columns: adminCashColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *AdminCashDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *AdminCashDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *AdminCashDao) Columns() AdminCashColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *AdminCashDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *AdminCashDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *AdminCashDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
101
server/internal/dao/internal/admin_credits_log.go
Normal file
101
server/internal/dao/internal/admin_credits_log.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AdminCreditsLogDao is the data access object for table hg_admin_credits_log.
|
||||
type AdminCreditsLogDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns AdminCreditsLogColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// AdminCreditsLogColumns defines and stores column names for table hg_admin_credits_log.
|
||||
type AdminCreditsLogColumns struct {
|
||||
Id string // 变动ID
|
||||
MemberId string // 管理员ID
|
||||
AppId string // 应用id
|
||||
AddonsName string // 插件名称
|
||||
CreditType string // 变动类型
|
||||
CreditGroup string // 变动组别
|
||||
BeforeNum string // 变动前
|
||||
Num string // 变动数据
|
||||
AfterNum string // 变动后
|
||||
Remark string // 备注
|
||||
Ip string // 操作人IP
|
||||
MapId string // 关联ID
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// adminCreditsLogColumns holds the columns for table hg_admin_credits_log.
|
||||
var adminCreditsLogColumns = AdminCreditsLogColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
AppId: "app_id",
|
||||
AddonsName: "addons_name",
|
||||
CreditType: "credit_type",
|
||||
CreditGroup: "credit_group",
|
||||
BeforeNum: "before_num",
|
||||
Num: "num",
|
||||
AfterNum: "after_num",
|
||||
Remark: "remark",
|
||||
Ip: "ip",
|
||||
MapId: "map_id",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewAdminCreditsLogDao creates and returns a new DAO object for table data access.
|
||||
func NewAdminCreditsLogDao() *AdminCreditsLogDao {
|
||||
return &AdminCreditsLogDao{
|
||||
group: "default",
|
||||
table: "hg_admin_credits_log",
|
||||
columns: adminCreditsLogColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *AdminCreditsLogDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *AdminCreditsLogDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *AdminCreditsLogDao) Columns() AdminCreditsLogColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *AdminCreditsLogDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *AdminCreditsLogDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *AdminCreditsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -29,6 +29,7 @@ type AdminMemberColumns struct {
|
||||
Salt string // 密码盐
|
||||
AuthKey string // 授权令牌
|
||||
PasswordResetToken string // 密码重置令牌
|
||||
Integral string // 积分
|
||||
Balance string // 余额
|
||||
Avatar string // 头像
|
||||
Sex string // 性别
|
||||
@@ -60,6 +61,7 @@ var adminMemberColumns = AdminMemberColumns{
|
||||
Salt: "salt",
|
||||
AuthKey: "auth_key",
|
||||
PasswordResetToken: "password_reset_token",
|
||||
Integral: "integral",
|
||||
Balance: "balance",
|
||||
Avatar: "avatar",
|
||||
Sex: "sex",
|
||||
|
||||
101
server/internal/dao/internal/admin_oauth.go
Normal file
101
server/internal/dao/internal/admin_oauth.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AdminOauthDao is the data access object for table hg_admin_oauth.
|
||||
type AdminOauthDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns AdminOauthColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// AdminOauthColumns defines and stores column names for table hg_admin_oauth.
|
||||
type AdminOauthColumns struct {
|
||||
Id string // 主键
|
||||
MemberId string // 用户ID
|
||||
Unionid string // 唯一ID
|
||||
OauthClient string // 授权组别
|
||||
OauthOpenid string // 授权开放ID
|
||||
Sex string // 性别
|
||||
Nickname string // 昵称
|
||||
HeadPortrait string // 头像
|
||||
Birthday string // 生日
|
||||
Country string // 国家
|
||||
Province string // 省
|
||||
City string // 市
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// adminOauthColumns holds the columns for table hg_admin_oauth.
|
||||
var adminOauthColumns = AdminOauthColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
Unionid: "unionid",
|
||||
OauthClient: "oauth_client",
|
||||
OauthOpenid: "oauth_openid",
|
||||
Sex: "sex",
|
||||
Nickname: "nickname",
|
||||
HeadPortrait: "head_portrait",
|
||||
Birthday: "birthday",
|
||||
Country: "country",
|
||||
Province: "province",
|
||||
City: "city",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewAdminOauthDao creates and returns a new DAO object for table data access.
|
||||
func NewAdminOauthDao() *AdminOauthDao {
|
||||
return &AdminOauthDao{
|
||||
group: "default",
|
||||
table: "hg_admin_oauth",
|
||||
columns: adminOauthColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *AdminOauthDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *AdminOauthDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *AdminOauthDao) Columns() AdminOauthColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *AdminOauthDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *AdminOauthDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *AdminOauthDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
95
server/internal/dao/internal/admin_order.go
Normal file
95
server/internal/dao/internal/admin_order.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AdminOrderDao is the data access object for table hg_admin_order.
|
||||
type AdminOrderDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns AdminOrderColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// AdminOrderColumns defines and stores column names for table hg_admin_order.
|
||||
type AdminOrderColumns struct {
|
||||
Id string // 主键
|
||||
MemberId string // 管理员id
|
||||
OrderType string // 订单类型
|
||||
ProductId string // 产品id
|
||||
OrderSn string // 关联订单号
|
||||
Money string // 充值金额
|
||||
Remark string // 备注
|
||||
RefundReason string // 退款原因
|
||||
RejectRefundReason string // 拒绝退款原因
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// adminOrderColumns holds the columns for table hg_admin_order.
|
||||
var adminOrderColumns = AdminOrderColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
OrderType: "order_type",
|
||||
ProductId: "product_id",
|
||||
OrderSn: "order_sn",
|
||||
Money: "money",
|
||||
Remark: "remark",
|
||||
RefundReason: "refund_reason",
|
||||
RejectRefundReason: "reject_refund_reason",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewAdminOrderDao creates and returns a new DAO object for table data access.
|
||||
func NewAdminOrderDao() *AdminOrderDao {
|
||||
return &AdminOrderDao{
|
||||
group: "default",
|
||||
table: "hg_admin_order",
|
||||
columns: adminOrderColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *AdminOrderDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *AdminOrderDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *AdminOrderDao) Columns() AdminOrderColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *AdminOrderDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *AdminOrderDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *AdminOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
131
server/internal/dao/internal/pay_log.go
Normal file
131
server/internal/dao/internal/pay_log.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// PayLogDao is the data access object for table hg_pay_log.
|
||||
type PayLogDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns PayLogColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// PayLogColumns defines and stores column names for table hg_pay_log.
|
||||
type PayLogColumns struct {
|
||||
Id string // 主键
|
||||
MemberId string // 会员ID
|
||||
AppId string // 应用ID
|
||||
AddonsName string // 插件名称
|
||||
OrderSn string // 关联订单号
|
||||
OrderGroup string // 组别[默认统一支付类型]
|
||||
Openid string // openid
|
||||
MchId string // 商户支付账户
|
||||
Subject string // 订单标题
|
||||
Detail string // 支付商品详情
|
||||
AuthCode string // 刷卡码
|
||||
OutTradeNo string // 商户订单号
|
||||
TransactionId string // 交易号
|
||||
PayType string // 支付类型
|
||||
PayAmount string // 支付金额
|
||||
ActualAmount string // 实付金额
|
||||
PayStatus string // 支付状态
|
||||
PayAt string // 支付时间
|
||||
TradeType string // 交易类型
|
||||
RefundSn string // 退款单号
|
||||
IsRefund string // 是否退款
|
||||
Custom string // 自定义参数
|
||||
CreateIp string // 创建者IP
|
||||
PayIp string // 支付者IP
|
||||
NotifyUrl string // 支付通知回调地址
|
||||
ReturnUrl string // 买家付款成功跳转地址
|
||||
TraceIds string // 链路ID集合
|
||||
Status string // 状态
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 修改时间
|
||||
}
|
||||
|
||||
// payLogColumns holds the columns for table hg_pay_log.
|
||||
var payLogColumns = PayLogColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
AppId: "app_id",
|
||||
AddonsName: "addons_name",
|
||||
OrderSn: "order_sn",
|
||||
OrderGroup: "order_group",
|
||||
Openid: "openid",
|
||||
MchId: "mch_id",
|
||||
Subject: "subject",
|
||||
Detail: "detail",
|
||||
AuthCode: "auth_code",
|
||||
OutTradeNo: "out_trade_no",
|
||||
TransactionId: "transaction_id",
|
||||
PayType: "pay_type",
|
||||
PayAmount: "pay_amount",
|
||||
ActualAmount: "actual_amount",
|
||||
PayStatus: "pay_status",
|
||||
PayAt: "pay_at",
|
||||
TradeType: "trade_type",
|
||||
RefundSn: "refund_sn",
|
||||
IsRefund: "is_refund",
|
||||
Custom: "custom",
|
||||
CreateIp: "create_ip",
|
||||
PayIp: "pay_ip",
|
||||
NotifyUrl: "notify_url",
|
||||
ReturnUrl: "return_url",
|
||||
TraceIds: "trace_ids",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewPayLogDao creates and returns a new DAO object for table data access.
|
||||
func NewPayLogDao() *PayLogDao {
|
||||
return &PayLogDao{
|
||||
group: "default",
|
||||
table: "hg_pay_log",
|
||||
columns: payLogColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *PayLogDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *PayLogDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *PayLogDao) Columns() PayLogColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *PayLogDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *PayLogDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *PayLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
97
server/internal/dao/internal/pay_refund.go
Normal file
97
server/internal/dao/internal/pay_refund.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// PayRefundDao is the data access object for table hg_pay_refund.
|
||||
type PayRefundDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of current DAO.
|
||||
columns PayRefundColumns // columns contains all the column names of Table for convenient usage.
|
||||
}
|
||||
|
||||
// PayRefundColumns defines and stores column names for table hg_pay_refund.
|
||||
type PayRefundColumns struct {
|
||||
Id string // 主键ID
|
||||
MemberId string // 会员ID
|
||||
AppId string // 应用ID
|
||||
OrderSn string // 业务订单号
|
||||
RefundTradeNo string // 退款交易号
|
||||
RefundMoney string // 退款金额
|
||||
RefundWay string // 退款方式
|
||||
Ip string // 申请者IP
|
||||
Reason string // 申请退款原因
|
||||
Remark string // 退款备注
|
||||
Status string // 退款状态
|
||||
CreatedAt string // 申请时间
|
||||
UpdatedAt string // 更新时间
|
||||
}
|
||||
|
||||
// payRefundColumns holds the columns for table hg_pay_refund.
|
||||
var payRefundColumns = PayRefundColumns{
|
||||
Id: "id",
|
||||
MemberId: "member_id",
|
||||
AppId: "app_id",
|
||||
OrderSn: "order_sn",
|
||||
RefundTradeNo: "refund_trade_no",
|
||||
RefundMoney: "refund_money",
|
||||
RefundWay: "refund_way",
|
||||
Ip: "ip",
|
||||
Reason: "reason",
|
||||
Remark: "remark",
|
||||
Status: "status",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewPayRefundDao creates and returns a new DAO object for table data access.
|
||||
func NewPayRefundDao() *PayRefundDao {
|
||||
return &PayRefundDao{
|
||||
group: "default",
|
||||
table: "hg_pay_refund",
|
||||
columns: payRefundColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of current DAO.
|
||||
func (dao *PayRefundDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of current dao.
|
||||
func (dao *PayRefundDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of current dao.
|
||||
func (dao *PayRefundDao) Columns() PayRefundColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the configuration group name of database of current dao.
|
||||
func (dao *PayRefundDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
|
||||
func (dao *PayRefundDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note that, you should not Commit or Rollback the transaction in function f
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *PayRefundDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
27
server/internal/dao/pay_log.go
Normal file
27
server/internal/dao/pay_log.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalPayLogDao is internal type for wrapping internal DAO implements.
|
||||
type internalPayLogDao = *internal.PayLogDao
|
||||
|
||||
// payLogDao is the data access object for table hg_pay_log.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type payLogDao struct {
|
||||
internalPayLogDao
|
||||
}
|
||||
|
||||
var (
|
||||
// PayLog is globally public accessible object for table hg_pay_log operations.
|
||||
PayLog = payLogDao{
|
||||
internal.NewPayLogDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
27
server/internal/dao/pay_refund.go
Normal file
27
server/internal/dao/pay_refund.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalPayRefundDao is internal type for wrapping internal DAO implements.
|
||||
type internalPayRefundDao = *internal.PayRefundDao
|
||||
|
||||
// payRefundDao is the data access object for table hg_pay_refund.
|
||||
// You can define custom methods on it to extend its functionality as you wish.
|
||||
type payRefundDao struct {
|
||||
internalPayRefundDao
|
||||
}
|
||||
|
||||
var (
|
||||
// PayRefund is globally public accessible object for table hg_pay_refund operations.
|
||||
PayRefund = payRefundDao{
|
||||
internal.NewPayRefundDao(),
|
||||
}
|
||||
)
|
||||
|
||||
// Fill with you ideas below.
|
||||
@@ -35,7 +35,6 @@ func Init(ctx context.Context) {
|
||||
g.Log().Fatal(ctx, "配置读取异常:", err, "\r\n你确定 config/config.yaml 文件存在且格式正确吗?\r\n")
|
||||
return
|
||||
}
|
||||
//g.SetDebug(debug.Bool())
|
||||
|
||||
// 默认上海时区
|
||||
if err = gtime.SetTimeZone("Asia/Shanghai"); err != nil {
|
||||
@@ -55,6 +54,12 @@ func Init(ctx context.Context) {
|
||||
// 加载ip访问黑名单
|
||||
service.SysBlacklist().Load(ctx)
|
||||
|
||||
// 初始化功能库配置
|
||||
service.SysConfig().InitConfig(ctx)
|
||||
|
||||
// 注册支付成功回调方法
|
||||
payNotifyCall()
|
||||
|
||||
// 初始化生成代码配置
|
||||
hggen.InIt(ctx)
|
||||
}
|
||||
|
||||
13
server/internal/global/pay.go
Normal file
13
server/internal/global/pay.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/payment"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
// 注册支付成功回调方法
|
||||
func payNotifyCall() {
|
||||
payment.RegisterNotifyCall(consts.OrderGroupAdminOrder, service.AdminOrder().PayNotify) // 后台充值订单
|
||||
// ...
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func ScanInstall(m Module) (record *InstallRecord, err error) {
|
||||
func IsInstall(m Module) bool {
|
||||
record, err := ScanInstall(m)
|
||||
if err != nil {
|
||||
g.Log().Debug(m.Ctx(), err.Error())
|
||||
g.Log().Debugf(m.Ctx(), "addons.IsInstall err:%+v", err)
|
||||
return false
|
||||
}
|
||||
if record == nil {
|
||||
|
||||
2
server/internal/library/cache/cache.go
vendored
2
server/internal/library/cache/cache.go
vendored
@@ -51,7 +51,7 @@ func SetAdapter(ctx context.Context) {
|
||||
|
||||
if !gfile.Exists(conf.FileDir) {
|
||||
if err := gfile.Mkdir(conf.FileDir); err != nil {
|
||||
g.Log().Fatalf(ctx, "Failed to create the cache directory. Procedure, err:%+v", err)
|
||||
g.Log().Fatalf(ctx, "failed to create the cache directory. procedure, err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
16
server/internal/library/cache/file/file.go
vendored
16
server/internal/library/cache/file/file.go
vendored
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -181,7 +181,12 @@ func (c *AdapterFile) createName(key string) string {
|
||||
}
|
||||
|
||||
func (c *AdapterFile) read(key string) (*fileContent, error) {
|
||||
value, err := ioutil.ReadFile(c.createName(key))
|
||||
rp := gfile.RealPath(c.createName(key))
|
||||
if rp == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
value, err := os.ReadFile(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,6 +241,10 @@ func (c *AdapterFile) Fetch(key string) (interface{}, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if content == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return content.Data, nil
|
||||
}
|
||||
|
||||
@@ -286,5 +295,6 @@ func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) err
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(c.createName(key), data, perm)
|
||||
err = os.WriteFile(c.createName(key), data, perm)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package casbin
|
||||
|
||||
import (
|
||||
@@ -31,7 +30,7 @@ CREATE TABLE IF NOT EXISTS %s (
|
||||
v4 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v5 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (id) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'casbin权限表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '管理员_casbin权限表' ROW_FORMAT = Dynamic;
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package casbin
|
||||
|
||||
import (
|
||||
|
||||
@@ -34,7 +34,7 @@ func Get(ctx context.Context) *model.Context {
|
||||
func SetUser(ctx context.Context, user *model.Identity) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetUser, c == nil ")
|
||||
g.Log().Warning(ctx, "contexts.SetUser, c == nil ")
|
||||
return
|
||||
}
|
||||
c.User = user
|
||||
@@ -44,7 +44,7 @@ func SetUser(ctx context.Context, user *model.Identity) {
|
||||
func SetResponse(ctx context.Context, response *model.Response) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetResponse, c == nil ")
|
||||
g.Log().Warning(ctx, "contexts.SetResponse, c == nil ")
|
||||
return
|
||||
}
|
||||
c.Response = response
|
||||
@@ -54,22 +54,12 @@ func SetResponse(ctx context.Context, response *model.Response) {
|
||||
func SetModule(ctx context.Context, module string) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetModule, c == nil ")
|
||||
g.Log().Warning(ctx, "contexts.SetModule, c == nil ")
|
||||
return
|
||||
}
|
||||
c.Module = module
|
||||
}
|
||||
|
||||
// SetTakeUpTime 设置请求耗时
|
||||
func SetTakeUpTime(ctx context.Context, takeUpTime int64) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetTakeUpTime, c == nil ")
|
||||
return
|
||||
}
|
||||
c.TakeUpTime = takeUpTime
|
||||
}
|
||||
|
||||
// GetUser 获取用户信息
|
||||
func GetUser(ctx context.Context) *model.Identity {
|
||||
c := Get(ctx)
|
||||
@@ -120,7 +110,7 @@ func GetModule(ctx context.Context) string {
|
||||
func SetAddonName(ctx context.Context, name string) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetAddonName, c == nil ")
|
||||
g.Log().Warning(ctx, "contexts.SetAddonName, c == nil ")
|
||||
return
|
||||
}
|
||||
Get(ctx).AddonName = name
|
||||
@@ -143,3 +133,35 @@ func GetAddonName(ctx context.Context) string {
|
||||
}
|
||||
return Get(ctx).AddonName
|
||||
}
|
||||
|
||||
// SetData 设置额外数据
|
||||
func SetData(ctx context.Context, k string, v interface{}) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetData, c == nil ")
|
||||
return
|
||||
}
|
||||
Get(ctx).Data[k] = v
|
||||
}
|
||||
|
||||
// SetDataMap 设置额外数据
|
||||
func SetDataMap(ctx context.Context, vs g.Map) {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
g.Log().Warning(ctx, "contexts.SetData, c == nil ")
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range vs {
|
||||
Get(ctx).Data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// GetData 获取额外数据
|
||||
func GetData(ctx context.Context) g.Map {
|
||||
c := Get(ctx)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Data
|
||||
}
|
||||
|
||||
30
server/internal/library/contexts/detached.go
Normal file
30
server/internal/library/contexts/detached.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package contexts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type detached struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (detached) Deadline() (time.Time, bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
func (detached) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (detached) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d detached) Value(key interface{}) interface{} {
|
||||
return d.ctx.Value(key)
|
||||
}
|
||||
|
||||
func Detach(ctx context.Context) context.Context {
|
||||
return detached{ctx: ctx}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ buildDone:
|
||||
return
|
||||
}
|
||||
|
||||
// getBuildInVarMapJson retrieves and returns the custom build-in variables in configuration
|
||||
// getBuildInVarStr retrieves and returns the custom build-in variables in configuration
|
||||
// file as json.
|
||||
func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
|
||||
buildInVarMap := in.VarMap
|
||||
|
||||
@@ -12,6 +12,7 @@ var (
|
||||
type cGen struct {
|
||||
g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
|
||||
cGenDao
|
||||
cGenEnums
|
||||
cGenPb
|
||||
cGenPbEntity
|
||||
cGenService
|
||||
|
||||
15
server/internal/library/hggen/internal/cmd/cmd_gen_enums.go
Normal file
15
server/internal/library/hggen/internal/cmd/cmd_gen_enums.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/hggen/internal/cmd/genenums"
|
||||
)
|
||||
|
||||
type (
|
||||
cGenEnums = genenums.CGenEnums
|
||||
)
|
||||
@@ -3,6 +3,8 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -37,6 +39,10 @@ gf init my-mono-repo -m
|
||||
name for the project. It will create a folder with NAME in current directory.
|
||||
The NAME will also be the module name for the project.
|
||||
`
|
||||
// cInitGitDir the git directory
|
||||
cInitGitDir = ".git"
|
||||
// cInitGitignore the gitignore file
|
||||
cInitGitignore = ".gitignore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -57,17 +63,22 @@ type cInitInput struct {
|
||||
type cInitOutput struct{}
|
||||
|
||||
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
var (
|
||||
overwrote = false
|
||||
)
|
||||
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
|
||||
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
|
||||
if strings.EqualFold(s, "n") {
|
||||
return
|
||||
}
|
||||
overwrote = true
|
||||
}
|
||||
mlog.Print("initializing...")
|
||||
|
||||
// Create project folder and files.
|
||||
var (
|
||||
templateRepoName string
|
||||
gitignoreFile = in.Name + "/" + cInitGitignore
|
||||
)
|
||||
if in.Mono {
|
||||
templateRepoName = cInitMonoRepo
|
||||
@@ -81,14 +92,35 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
return
|
||||
}
|
||||
|
||||
// build ignoreFiles from the .gitignore file
|
||||
ignoreFiles := make([]string, 0, 10)
|
||||
ignoreFiles = append(ignoreFiles, cInitGitDir)
|
||||
if overwrote {
|
||||
err = gfile.ReadLines(gitignoreFile, func(line string) error {
|
||||
// Add only hidden files or directories
|
||||
// If other directories are added, it may cause the entire directory to be ignored
|
||||
// such as 'main' in the .gitignore file, but the path is 'D:\main\my-project'
|
||||
if line != "" && strings.HasPrefix(line, ".") {
|
||||
ignoreFiles = append(ignoreFiles, line)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// if not found the .gitignore file will skip os.ErrNotExist error
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Replace template name to project name.
|
||||
err = gfile.ReplaceDir(
|
||||
cInitRepoPrefix+templateRepoName,
|
||||
gfile.Basename(gfile.RealPath(in.Name)),
|
||||
in.Name,
|
||||
"*",
|
||||
true,
|
||||
)
|
||||
err = gfile.ReplaceDirFunc(func(path, content string) string {
|
||||
for _, ignoreFile := range ignoreFiles {
|
||||
if strings.Contains(path, ignoreFile) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, gfile.Basename(gfile.RealPath(in.Name)))
|
||||
}, in.Name, "*", true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ like json/xml/yaml/toml/ini.
|
||||
cTplParseEg = `
|
||||
gf tpl parse -p ./template -v values.json -r
|
||||
gf tpl parse -p ./template -v values.json -n *.tpl -r
|
||||
gf tpl parse -p ./template -v values.json -d '${,}}' -r
|
||||
gf tpl parse -p ./template -v values.json -d '${{,}}' -r
|
||||
gf tpl parse -p ./template -v values.json -o ./template.parsed
|
||||
`
|
||||
cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini`
|
||||
@@ -63,7 +63,7 @@ func init() {
|
||||
}
|
||||
|
||||
func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) {
|
||||
if in.Output == "" && in.Replace == false {
|
||||
if in.Output == "" && !in.Replace {
|
||||
return nil, gerror.New(`parameter output and replace should not be both empty`)
|
||||
}
|
||||
delimiters := gstr.SplitAndTrim(in.Delimiters, ",")
|
||||
|
||||
@@ -50,7 +50,7 @@ func generateStructDefinition(ctx context.Context, in generateStructDefinitionIn
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
||||
// generateStructFieldDefinition generates and returns the attribute definition for specified field.
|
||||
func generateStructFieldDefinition(
|
||||
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
|
||||
) []string {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package genenums
|
||||
|
||||
import (
|
||||
"context"
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
"hotgo/internal/library/hggen/internal/consts"
|
||||
"hotgo/internal/library/hggen/internal/utility/mlog"
|
||||
)
|
||||
|
||||
type (
|
||||
CGenEnums struct{}
|
||||
CGenEnumsInput struct {
|
||||
g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"`
|
||||
Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"."`
|
||||
Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/boot/boot_enums.go"`
|
||||
Prefix string `name:"prefix" short:"x" dc:"only exports packages that starts with specified prefix"`
|
||||
}
|
||||
CGenEnumsOutput struct{}
|
||||
)
|
||||
|
||||
const (
|
||||
CGenEnumsConfig = `gfcli.gen.enums`
|
||||
CGenEnumsBrief = `parse go files in current project and generate enums go file`
|
||||
CGenEnumsEg = `
|
||||
gf gen enums
|
||||
gf gen enums -p internal/boot/boot_enums.go
|
||||
gf gen enums -p internal/boot/boot_enums.go -s .
|
||||
gf gen enums -x github.com/gogf
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`CGenEnumsEg`: CGenEnumsEg,
|
||||
`CGenEnumsBrief`: CGenEnumsBrief,
|
||||
`CGenEnumsConfig`: CGenEnumsConfig,
|
||||
})
|
||||
}
|
||||
|
||||
func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnumsOutput, err error) {
|
||||
realPath := gfile.RealPath(in.Src)
|
||||
if realPath == "" {
|
||||
mlog.Fatalf(`source folder path "%s" does not exist`, in.Src)
|
||||
}
|
||||
err = gfile.Chdir(realPath)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
mlog.Printf(`scanning: %s`, realPath)
|
||||
cfg := &packages.Config{
|
||||
Dir: realPath,
|
||||
Mode: pkgLoadMode,
|
||||
Tests: false,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
p := NewEnumsParser(in.Prefix)
|
||||
p.ParsePackages(pkgs)
|
||||
var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{
|
||||
"{PackageName}": gfile.Basename(gfile.Dir(in.Path)),
|
||||
"{EnumsJson}": "`" + p.Export() + "`",
|
||||
})
|
||||
enumsContent = gstr.Trim(enumsContent)
|
||||
if err = gfile.PutContents(in.Path, enumsContent); err != nil {
|
||||
return
|
||||
}
|
||||
mlog.Printf(`generated: %s`, in.Path)
|
||||
mlog.Print("done!")
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package genenums
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"go/types"
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const pkgLoadMode = 0xffffff
|
||||
|
||||
type EnumsParser struct {
|
||||
enums []EnumItem
|
||||
parsedPkg map[string]struct{}
|
||||
prefix string
|
||||
}
|
||||
|
||||
type EnumItem struct {
|
||||
Name string
|
||||
Value string
|
||||
Kind constant.Kind // String/Int/Bool/Float/Complex/Unknown
|
||||
Type string // Pkg.ID + TypeName
|
||||
}
|
||||
|
||||
var standardPackages = make(map[string]struct{})
|
||||
|
||||
//func init() {
|
||||
// stdPackages, err := packages.Load(nil, "std")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// for _, p := range stdPackages {
|
||||
// standardPackages[p.ID] = struct{}{}
|
||||
// }
|
||||
//}
|
||||
|
||||
func NewEnumsParser(prefix string) *EnumsParser {
|
||||
return &EnumsParser{
|
||||
enums: make([]EnumItem, 0),
|
||||
parsedPkg: make(map[string]struct{}),
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *EnumsParser) ParsePackages(pkgs []*packages.Package) {
|
||||
for _, pkg := range pkgs {
|
||||
p.ParsePackage(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *EnumsParser) ParsePackage(pkg *packages.Package) {
|
||||
// Ignore std packages.
|
||||
if _, ok := standardPackages[pkg.ID]; ok {
|
||||
return
|
||||
}
|
||||
// Ignore pared packages.
|
||||
if _, ok := p.parsedPkg[pkg.ID]; ok {
|
||||
return
|
||||
}
|
||||
p.parsedPkg[pkg.ID] = struct{}{}
|
||||
// Only parse specified prefix.
|
||||
if p.prefix != "" {
|
||||
if !gstr.HasPrefix(pkg.ID, p.prefix) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
scope = pkg.Types.Scope()
|
||||
names = scope.Names()
|
||||
)
|
||||
for _, name := range names {
|
||||
con, ok := scope.Lookup(name).(*types.Const)
|
||||
if !ok {
|
||||
// Only constants can be enums.
|
||||
continue
|
||||
}
|
||||
if !con.Exported() {
|
||||
// Ignore unexported values.
|
||||
continue
|
||||
}
|
||||
|
||||
var enumType = con.Type().String()
|
||||
if !gstr.Contains(enumType, "/") {
|
||||
// Ignore std types.
|
||||
continue
|
||||
}
|
||||
var (
|
||||
enumName = con.Name()
|
||||
enumValue = con.Val().ExactString()
|
||||
enumKind = con.Val().Kind()
|
||||
)
|
||||
if con.Val().Kind() == constant.String {
|
||||
enumValue = constant.StringVal(con.Val())
|
||||
}
|
||||
p.enums = append(p.enums, EnumItem{
|
||||
Name: enumName,
|
||||
Value: enumValue,
|
||||
Type: enumType,
|
||||
Kind: enumKind,
|
||||
})
|
||||
|
||||
}
|
||||
for _, im := range pkg.Imports {
|
||||
p.ParsePackage(im)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *EnumsParser) Export() string {
|
||||
var typeEnumMap = make(map[string][]interface{})
|
||||
for _, enum := range p.enums {
|
||||
if typeEnumMap[enum.Type] == nil {
|
||||
typeEnumMap[enum.Type] = make([]interface{}, 0)
|
||||
}
|
||||
var value interface{}
|
||||
switch enum.Kind {
|
||||
case constant.Int:
|
||||
value = gconv.Int64(enum.Value)
|
||||
case constant.String:
|
||||
value = enum.Value
|
||||
case constant.Float:
|
||||
value = gconv.Float64(enum.Value)
|
||||
case constant.Bool:
|
||||
value = gconv.Bool(enum.Value)
|
||||
default:
|
||||
value = enum.Value
|
||||
}
|
||||
typeEnumMap[enum.Type] = append(typeEnumMap[enum.Type], value)
|
||||
}
|
||||
return gjson.MustEncodeString(typeEnumMap)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package consts
|
||||
|
||||
const TemplateGenEnums = `
|
||||
// ================================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ================================================================================
|
||||
|
||||
package {PackageName}
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtag.SetGlobalEnums({EnumsJson})
|
||||
}
|
||||
`
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package views
|
||||
|
||||
import (
|
||||
@@ -21,7 +20,7 @@ const (
|
||||
LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!"
|
||||
LogicListSimpleSelect = "\tfields, err := hgorm.GenSelect(ctx, sysin.%sListModel{}, dao.%s)\n\tif err != nil {\n\t\treturn\n\t}"
|
||||
LogicListJoinSelect = "\t//关联表select\n\tfields, err := hgorm.GenJoinSelect(ctx, %sin.%sListModel{}, dao.%s, []*hgorm.Join{\n%v\t})"
|
||||
LogicListJoinOnRelation = "\t// 关联表%s\n\tmod = mod.%s(hgorm.GenJoinOnRelation(\n\t\tdao.%s.Table(), dao.%s.Columns().%s, // 主表表名,关联条件\n\t\tdao.%s.Table(), \"%s\", dao.%s.Columns().%s, // 关联表表名,别名,关联条件\n\t)...)\n\n"
|
||||
LogicListJoinOnRelation = "\t// 关联表%s\n\tmod = mod.%s(hgorm.GenJoinOnRelation(\n\t\tdao.%s.Table(), dao.%s.Columns().%s, // 主表表名,关联字段\n\t\tdao.%s.Table(), \"%s\", dao.%s.Columns().%s, // 关联表表名,别名,关联字段\n\t)...)\n\n"
|
||||
LogicEditUpdate = "\t\t_, err = s.Model(ctx).\n\t\t\tFieldsEx(\n%s\t\t\t).\n\t\t\tWhere(dao.%s.Columns().%s, in.%s).Data(in).Update()\n\t\treturn "
|
||||
LogicEditInsert = "\t_, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFieldsEx(\n%s\t\t).\n\t\tData(in).Insert()"
|
||||
LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}"
|
||||
@@ -136,10 +135,11 @@ func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput)
|
||||
selectBuffer.WriteString(fmt.Sprintf(LogicListJoinSelect, in.options.TemplateGroup, in.In.VarName, in.In.DaoName, joinSelectRows))
|
||||
|
||||
data["select"] = selectBuffer.String()
|
||||
data["fields"] = "fields"
|
||||
data["link"] = linkBuffer.String()
|
||||
|
||||
} else {
|
||||
data["select"] = fmt.Sprintf(LogicListSimpleSelect, in.In.VarName, in.In.DaoName)
|
||||
data["fields"] = fmt.Sprintf("%sin.%sListModel{}", in.options.TemplateGroup, in.In.VarName)
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/utility/convert"
|
||||
@@ -24,7 +25,9 @@ func (l *gCurd) webModelTplData(ctx context.Context, in *CurdPreviewInput) (data
|
||||
data["defaultState"] = l.generateWebModelDefaultState(ctx, in)
|
||||
data["rules"] = l.generateWebModelRules(ctx, in)
|
||||
data["formSchema"] = l.generateWebModelFormSchema(ctx, in)
|
||||
data["columns"] = l.generateWebModelColumns(ctx, in)
|
||||
if data["columns"], err = l.generateWebModelColumns(ctx, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -214,12 +217,14 @@ func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*s
|
||||
}
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInput) string {
|
||||
func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInput) (string, error) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString("export const columns = [\n")
|
||||
|
||||
// 主表
|
||||
l.generateWebModelColumnsEach(buffer, in, in.masterFields)
|
||||
if err := l.generateWebModelColumnsEach(buffer, in, in.masterFields); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 关联表
|
||||
if len(in.options.Join) > 0 {
|
||||
@@ -227,15 +232,17 @@ func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInpu
|
||||
if !isEffectiveJoin(v) {
|
||||
continue
|
||||
}
|
||||
l.generateWebModelColumnsEach(buffer, in, v.Columns)
|
||||
if err := l.generateWebModelColumnsEach(buffer, in, v.Columns); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString("];\n")
|
||||
return buffer.String()
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel) {
|
||||
func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel) (err error) {
|
||||
for _, field := range fields {
|
||||
if !field.IsList {
|
||||
continue
|
||||
@@ -251,9 +258,17 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.TsName)
|
||||
|
||||
case FormModeSelect:
|
||||
if g.IsEmpty(in.options.dictMap[field.TsName]) {
|
||||
err = gerror.Newf("设置单选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name)
|
||||
return
|
||||
}
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, row.%s),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
|
||||
case FormModeSelectMultiple:
|
||||
if g.IsEmpty(in.options.dictMap[field.TsName]) {
|
||||
err = gerror.Newf("设置多选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name)
|
||||
return
|
||||
}
|
||||
component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s) || !isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, tagKey),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, tagKey),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeUploadImage:
|
||||
@@ -280,4 +295,6 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie
|
||||
|
||||
buffer.WriteString(component)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package views
|
||||
|
||||
import (
|
||||
@@ -45,7 +44,10 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
|
||||
case FormModeTime:
|
||||
component = defaultComponent
|
||||
|
||||
case FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple:
|
||||
case FormModeRadio, FormModeSelect:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-tag\n :type=\"getOptionTag(options.%s, formValue?.%s)\"\n size=\"small\"\n class=\"min-left-space\"\n >{{ getOptionLabel(options.%s, formValue?.%s) }}</n-tag\n >\n </n-descriptions-item>", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
|
||||
|
||||
case FormModeCheckbox, FormModeSelectMultiple:
|
||||
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <template v-for=\"(item, key) in formValue?.%s\" :key=\"key\">\n <n-tag\n :type=\"getOptionTag(options.%s, item)\"\n size=\"small\"\n class=\"min-left-space\"\n >{{ getOptionLabel(options.%s, item) }}</n-tag\n >\n </template>\n </n-descriptions-item>", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
|
||||
|
||||
case FormModeUploadImage:
|
||||
|
||||
@@ -107,3 +107,7 @@ func GetAuthorization(r *ghttp.Request) string {
|
||||
|
||||
return gstr.Replace(authorization, "Bearer ", "")
|
||||
}
|
||||
|
||||
func GenAuthKey(token string) string {
|
||||
return gmd5.MustEncryptString(token)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/kayon/iploc"
|
||||
"hotgo/utility/validate"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -169,7 +169,7 @@ func GetPublicIP(ctx context.Context) (ip string, err error) {
|
||||
var data *WhoisRegionData
|
||||
err = g.Client().Timeout(10*time.Second).GetVar(ctx, whoisApi).Scan(&data)
|
||||
if err != nil {
|
||||
g.Log().Infof(ctx, "GetPublicIP alternatives are being tried err:%+v", err)
|
||||
g.Log().Info(ctx, "GetPublicIP fail, alternatives are being tried.")
|
||||
return GetPublicIP2()
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func GetPublicIP2() (ip string, err error) {
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -219,6 +219,9 @@ func GetLocalIP() (ip string, err error) {
|
||||
|
||||
// GetClientIp 获取客户端IP
|
||||
func GetClientIp(r *ghttp.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = r.GetClientIp()
|
||||
|
||||
@@ -3,7 +3,6 @@ package tcp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
@@ -36,6 +35,7 @@ type Client struct {
|
||||
IsLogin bool // 是否已登录
|
||||
addr string
|
||||
auth *AuthMeta
|
||||
rpc *Rpc
|
||||
timeout time.Duration
|
||||
connectInterval time.Duration
|
||||
maxConnectCount uint
|
||||
@@ -103,6 +103,7 @@ func NewClient(config *ClientConfig) (client *Client, err error) {
|
||||
client.timeout = config.Timeout
|
||||
}
|
||||
|
||||
client.rpc = NewRpc(client.Ctx)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -248,7 +249,31 @@ func (client *Client) read() {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg invalid message: %+v", msg)
|
||||
continue
|
||||
}
|
||||
f(msg.Data, client.conn)
|
||||
|
||||
switch msg.Router {
|
||||
case "ResponseServerLogin", "ResponseServerHeartbeat": // 服务登录、心跳无需验证签名
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{})
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
default: // 通用路由消息处理
|
||||
in, err := VerifySign(msg.Data, client.auth.AppId, client.auth.SecretKey)
|
||||
if err != nil {
|
||||
client.Logger.Warningf(client.Ctx, "client read VerifySign err:%+v message: %+v", err, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: client.conn,
|
||||
Auth: client.auth,
|
||||
TraceID: in.TraceID,
|
||||
})
|
||||
|
||||
// 响应rpc消息
|
||||
if client.rpc.HandleMsg(ctx, cancel, msg.Data) {
|
||||
return
|
||||
}
|
||||
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -307,16 +332,45 @@ func (client *Client) Write(data interface{}) error {
|
||||
return gerror.New("client Write message is nil")
|
||||
}
|
||||
|
||||
// 签名
|
||||
SetSign(data, gctx.CtxId(client.Ctx), client.auth.AppId, client.auth.SecretKey)
|
||||
|
||||
msgType := reflect.TypeOf(data)
|
||||
if msgType == nil || msgType.Kind() != reflect.Ptr {
|
||||
return gerror.Newf("client json message pointer required: %+v", data)
|
||||
}
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
client.Logger.Debugf(client.Ctx, "client Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(client.conn, msg)
|
||||
}
|
||||
|
||||
// Send 发送消息
|
||||
func (client *Client) Send(ctx context.Context, data interface{}) error {
|
||||
MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||||
return client.Write(data)
|
||||
}
|
||||
|
||||
// Reply 回复消息
|
||||
func (client *Client) Reply(ctx context.Context, data interface{}) (err error) {
|
||||
user := GetCtx(ctx)
|
||||
if user == nil {
|
||||
err = gerror.New("获取回复用户信息失败")
|
||||
return
|
||||
}
|
||||
MsgPkg(data, client.auth, user.TraceID)
|
||||
return client.Write(data)
|
||||
}
|
||||
|
||||
// RpcRequest 发送消息并等待响应结果
|
||||
func (client *Client) RpcRequest(ctx context.Context, data interface{}) (res interface{}, err error) {
|
||||
var (
|
||||
traceID = MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||||
key = client.rpc.GetCallId(client.conn, traceID)
|
||||
)
|
||||
|
||||
if traceID == "" {
|
||||
err = gerror.New("traceID is required")
|
||||
return
|
||||
}
|
||||
|
||||
return client.rpc.Request(key, func() {
|
||||
client.Write(data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
func (client *Client) getCronKey(s string) string {
|
||||
@@ -19,19 +20,19 @@ func (client *Client) stopCron() {
|
||||
|
||||
func (client *Client) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(client.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
if gcron.Search(client.getCronKey(consts.TCPCronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 600s", func(ctx context.Context) {
|
||||
if client.heartbeat < gtime.Timestamp()-600 {
|
||||
client.Logger.Debugf(client.Ctx, "client heartbeat timeout, about to reconnect..")
|
||||
client.Logger.Debugf(client.Ctx, "client heartbeat timeout, about to reconnect..")
|
||||
client.Destroy()
|
||||
}
|
||||
}, client.getCronKey(cronHeartbeatVerify))
|
||||
}, client.getCronKey(consts.TCPCronHeartbeatVerify))
|
||||
}
|
||||
|
||||
// 心跳
|
||||
if gcron.Search(client.getCronKey(cronHeartbeat)) == nil {
|
||||
if gcron.Search(client.getCronKey(consts.TCPCronHeartbeat)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 120s", func(ctx context.Context) {
|
||||
client.serverHeartbeat()
|
||||
}, client.getCronKey(cronHeartbeat))
|
||||
}, client.getCronKey(consts.TCPCronHeartbeat))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
)
|
||||
|
||||
// serverLogin 心跳
|
||||
func (client *Client) serverHeartbeat() {
|
||||
if err := client.Write(&msgin.ServerHeartbeat{}); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerHeartbeat err:%+v", err)
|
||||
ctx := gctx.New()
|
||||
if err := client.Send(ctx, &msgin.ServerHeartbeat{}); err != nil {
|
||||
client.Logger.Debugf(ctx, "client WriteMsg ServerHeartbeat err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -22,40 +25,45 @@ func (client *Client) serverLogin() {
|
||||
Name: client.auth.Name,
|
||||
}
|
||||
|
||||
if err := client.Write(data); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerLogin err:%+v", err)
|
||||
ctx := gctx.New()
|
||||
if err := client.Send(ctx, data); err != nil {
|
||||
client.Logger.Debugf(ctx, "client WriteMsg ServerLogin err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerLogin(ctx context.Context, args ...interface{}) {
|
||||
var in *msgin.ResponseServerLogin
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(ctx, "onResponseServerLogin message Scan failed:%+v, args:%+v", err, args[0])
|
||||
return
|
||||
}
|
||||
|
||||
if in.Code != consts.TCPMsgCodeSuccess {
|
||||
client.IsLogin = false
|
||||
client.Logger.Warningf(ctx, "onResponseServerLogin quit err:%v", in.Message)
|
||||
client.Destroy()
|
||||
return
|
||||
}
|
||||
|
||||
client.IsLogin = true
|
||||
|
||||
if client.loginEvent != nil {
|
||||
client.loginEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerLogin(args ...interface{}) {
|
||||
var in *msgin.ResponseServerLogin
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin message Scan failed:%+v, args:%+v", err, args[0])
|
||||
return
|
||||
}
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin in:%+v", *in)
|
||||
|
||||
if in.Code != gcode.CodeOK.Code() {
|
||||
client.IsLogin = false
|
||||
client.Logger.Warningf(client.Ctx, "onResponseServerLogin quit err:%v", in.Message)
|
||||
client.Destroy()
|
||||
return
|
||||
}
|
||||
client.IsLogin = true
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerHeartbeat(args ...interface{}) {
|
||||
func (client *Client) onResponseServerHeartbeat(ctx context.Context, args ...interface{}) {
|
||||
var in *msgin.ResponseServerHeartbeat
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
client.Logger.Infof(ctx, "onResponseServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
|
||||
if in.Code != consts.TCPMsgCodeSuccess {
|
||||
client.Logger.Warningf(ctx, "onResponseServerHeartbeat err:%v", in.Message)
|
||||
return
|
||||
}
|
||||
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat in:%+v", *in)
|
||||
}
|
||||
|
||||
57
server/internal/library/network/tcp/context.go
Normal file
57
server/internal/library/network/tcp/context.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
// initCtx 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
|
||||
func initCtx(ctx context.Context, model *Context) (newCtx context.Context, cancel context.CancelFunc) {
|
||||
if model.TraceID != "" {
|
||||
newCtx, _ = gtrace.WithTraceID(ctx, model.TraceID)
|
||||
} else {
|
||||
newCtx = ctx
|
||||
}
|
||||
newCtx = context.WithValue(newCtx, consts.ContextTCPKey, model)
|
||||
newCtx, cancel = context.WithCancel(newCtx)
|
||||
return
|
||||
}
|
||||
|
||||
// SetCtx 设置上下文变量
|
||||
func SetCtx(ctx context.Context, model *Context) {
|
||||
context.WithValue(ctx, consts.ContextTCPKey, model)
|
||||
}
|
||||
|
||||
// GetCtx 获得上下文变量,如果没有设置,那么返回nil
|
||||
func GetCtx(ctx context.Context) *Context {
|
||||
value := ctx.Value(consts.ContextTCPKey)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if localCtx, ok := value.(*Context); ok {
|
||||
return localCtx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCtxConn .
|
||||
func GetCtxConn(ctx context.Context) *gtcp.Conn {
|
||||
c := GetCtx(ctx)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
// GetCtxAuth 认证元数据
|
||||
func GetCtxAuth(ctx context.Context) *AuthMeta {
|
||||
c := GetCtx(ctx)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.Auth
|
||||
}
|
||||
@@ -1,19 +1,8 @@
|
||||
package tcp
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
// 定时任务
|
||||
const (
|
||||
cronHeartbeatVerify = "tcpHeartbeatVerify"
|
||||
cronHeartbeat = "tcpHeartbeat"
|
||||
cronAuthVerify = "tcpAuthVerify"
|
||||
)
|
||||
|
||||
// 认证分组
|
||||
const (
|
||||
ClientGroupCron = "cron" // 定时任务
|
||||
ClientGroupQueue = "queue" // 消息队列
|
||||
ClientGroupAuth = "auth" // 服务授权
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// AuthMeta 认证元数据
|
||||
@@ -25,5 +14,11 @@ type AuthMeta struct {
|
||||
EndAt *gtime.Time `json:"-"`
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Conn *gtcp.Conn `json:"conn"`
|
||||
Auth *AuthMeta `json:"auth"` // 认证元数据
|
||||
TraceID string `json:"traceID"` // 链路ID
|
||||
}
|
||||
|
||||
// CallbackEvent 回调事件
|
||||
type CallbackEvent func()
|
||||
|
||||
22
server/internal/library/network/tcp/response.go
Normal file
22
server/internal/library/network/tcp/response.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tcp
|
||||
|
||||
type Response interface {
|
||||
PkgResponse()
|
||||
GetError() (err error)
|
||||
}
|
||||
|
||||
// PkgResponse 打包响应消息
|
||||
func PkgResponse(data interface{}) {
|
||||
if c, ok := data.(Response); ok {
|
||||
c.PkgResponse()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetResponseError 解析响应消息中的错误
|
||||
func GetResponseError(data interface{}) (err error) {
|
||||
if c, ok := data.(Response); ok {
|
||||
return c.GetError()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/grpool"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type RouterHandler func(args ...interface{})
|
||||
var GoPool = grpool.New(100)
|
||||
|
||||
type RouterHandler func(ctx context.Context, args ...interface{})
|
||||
|
||||
// Message 路由消息
|
||||
type Message struct {
|
||||
@@ -37,3 +41,26 @@ func RecvPkg(conn *gtcp.Conn) (*Message, error) {
|
||||
return msg, err
|
||||
}
|
||||
}
|
||||
|
||||
// MsgPkg 打包消息
|
||||
func MsgPkg(data interface{}, auth *AuthMeta, traceID string) string {
|
||||
// 打包签名
|
||||
msg := PkgSign(data, auth.AppId, auth.SecretKey, traceID)
|
||||
|
||||
// 打包响应消息
|
||||
PkgResponse(data)
|
||||
|
||||
if msg == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return msg.TraceID
|
||||
}
|
||||
|
||||
// doHandleRouterMsg 处理路由消息
|
||||
func doHandleRouterMsg(fun RouterHandler, ctx context.Context, cancel context.CancelFunc, args ...interface{}) {
|
||||
GoPool.Add(ctx, func(ctx context.Context) {
|
||||
fun(ctx, args...)
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
103
server/internal/library/network/tcp/rpc.go
Normal file
103
server/internal/library/network/tcp/rpc.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/utility/simple"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Rpc struct {
|
||||
ctx context.Context
|
||||
mutex sync.Mutex
|
||||
callbacks map[string]RpcRespFunc
|
||||
}
|
||||
|
||||
type RpcResp struct {
|
||||
res interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
type RpcRespFunc func(resp interface{}, err error)
|
||||
|
||||
func NewRpc(ctx context.Context) *Rpc {
|
||||
return &Rpc{
|
||||
ctx: ctx,
|
||||
callbacks: make(map[string]RpcRespFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// GetCallId 获取回调id
|
||||
func (r *Rpc) GetCallId(client *gtcp.Conn, traceID string) string {
|
||||
return fmt.Sprintf("%v.%v", client.LocalAddr().String(), traceID)
|
||||
}
|
||||
|
||||
// HandleMsg 处理rpc消息
|
||||
func (r *Rpc) HandleMsg(ctx context.Context, cancel context.CancelFunc, data interface{}) bool {
|
||||
user := GetCtx(ctx)
|
||||
callId := r.GetCallId(user.Conn, user.TraceID)
|
||||
|
||||
if call, ok := r.callbacks[callId]; ok {
|
||||
r.mutex.Lock()
|
||||
delete(r.callbacks, callId)
|
||||
r.mutex.Unlock()
|
||||
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
call(data, nil)
|
||||
cancel()
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Request 发起rpc请求
|
||||
func (r *Rpc) Request(callId string, send func()) (res interface{}, err error) {
|
||||
var (
|
||||
waitCh = make(chan struct{})
|
||||
resCh = make(chan RpcResp, 1)
|
||||
isClose = false
|
||||
)
|
||||
|
||||
defer func() {
|
||||
isClose = true
|
||||
close(resCh)
|
||||
|
||||
// 移除消息
|
||||
if _, ok := r.callbacks[callId]; ok {
|
||||
r.mutex.Lock()
|
||||
delete(r.callbacks, callId)
|
||||
r.mutex.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
simple.SafeGo(r.ctx, func(ctx context.Context) {
|
||||
close(waitCh)
|
||||
|
||||
// 加入回调
|
||||
r.mutex.Lock()
|
||||
r.callbacks[callId] = func(res interface{}, err error) {
|
||||
if !isClose {
|
||||
resCh <- RpcResp{res: res, err: err}
|
||||
}
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
|
||||
// 发送消息
|
||||
send()
|
||||
})
|
||||
|
||||
<-waitCh
|
||||
select {
|
||||
case <-time.After(consts.TCPRpcTimeout):
|
||||
err = gerror.New("rpc response timeout")
|
||||
return
|
||||
case got := <-resCh:
|
||||
return got.res, got.err
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package tcp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"hotgo/internal/consts"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -31,6 +31,7 @@ type Server struct {
|
||||
Logger *glog.Logger
|
||||
addr string
|
||||
name string
|
||||
rpc *Rpc
|
||||
ln *gtcp.Server
|
||||
wgLn sync.WaitGroup
|
||||
mutex sync.Mutex
|
||||
@@ -72,6 +73,7 @@ func NewServer(config *ServerConfig) (server *Server, err error) {
|
||||
return
|
||||
}
|
||||
server.Logger = logger
|
||||
server.rpc = NewRpc(server.Ctx)
|
||||
|
||||
server.startCron()
|
||||
|
||||
@@ -100,13 +102,19 @@ func (server *Server) accept(conn *gtcp.Conn) {
|
||||
|
||||
switch msg.Router {
|
||||
case "ServerLogin": // 服务登录
|
||||
server.onServerLogin(msg.Data, conn)
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: conn,
|
||||
})
|
||||
doHandleRouterMsg(server.onServerLogin, ctx, cancel, msg.Data)
|
||||
case "ServerHeartbeat": // 心跳
|
||||
if client == nil {
|
||||
server.Logger.Infof(server.Ctx, "conn not connected, ignore the heartbeat, msg:%+v", msg)
|
||||
continue
|
||||
}
|
||||
server.onServerHeartbeat(msg, client)
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{})
|
||||
doHandleRouterMsg(server.onServerHeartbeat, ctx, cancel, msg.Data, client)
|
||||
default: // 通用路由消息处理
|
||||
if client == nil {
|
||||
server.Logger.Warningf(server.Ctx, "conn is not logged in but sends a routing message. actively conn disconnect, msg:%+v", msg)
|
||||
@@ -121,14 +129,25 @@ func (server *Server) accept(conn *gtcp.Conn) {
|
||||
|
||||
// handleRouterMsg 处理路由消息
|
||||
func (server *Server) handleRouterMsg(msg *Message, client *ClientConn) {
|
||||
|
||||
// 验证签名
|
||||
err := VerifySign(msg.Data, client.Auth.AppId, client.Auth.SecretKey)
|
||||
in, err := VerifySign(msg.Data, client.Auth.AppId, client.Auth.SecretKey)
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "handleRouterMsg VerifySign err:%+v message: %+v", err, msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: client.Conn,
|
||||
Auth: client.Auth,
|
||||
TraceID: in.TraceID,
|
||||
})
|
||||
|
||||
// 响应rpc消息
|
||||
if server.rpc.HandleMsg(ctx, cancel, msg.Data) {
|
||||
return
|
||||
}
|
||||
|
||||
handle := func(routers map[string]RouterHandler, group string) {
|
||||
if routers == nil {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg route is not initialized %v message: %+v", group, msg)
|
||||
@@ -139,15 +158,16 @@ func (server *Server) handleRouterMsg(msg *Message, client *ClientConn) {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg invalid %v message: %+v", group, msg)
|
||||
return
|
||||
}
|
||||
f(msg.Data, client)
|
||||
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
}
|
||||
|
||||
switch client.Auth.Group {
|
||||
case ClientGroupCron:
|
||||
case consts.TCPClientGroupCron:
|
||||
handle(server.cronRouters, client.Auth.Group)
|
||||
case ClientGroupQueue:
|
||||
case consts.TCPClientGroupQueue:
|
||||
handle(server.queueRouters, client.Auth.Group)
|
||||
case ClientGroupAuth:
|
||||
case consts.TCPClientGroupAuth:
|
||||
handle(server.authRouters, client.Auth.Group)
|
||||
default:
|
||||
server.Logger.Warningf(server.Ctx, "group is not registered: %+v", client.Auth.Group)
|
||||
@@ -173,6 +193,16 @@ func (server *Server) getAppIdClients(appid string) (list []*ClientConn) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetGroupClients 获取指定分组的所有连接
|
||||
func (server *Server) GetGroupClients(group string) (list []*ClientConn) {
|
||||
for _, v := range server.clients {
|
||||
if v.Auth.Group == group {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterAuthRouter 注册授权路由
|
||||
func (server *Server) RegisterAuthRouter(routers map[string]RouterHandler) {
|
||||
server.mutex.Lock()
|
||||
@@ -272,7 +302,39 @@ func (server *Server) Write(conn *gtcp.Conn, data interface{}) (err error) {
|
||||
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
server.Logger.Debugf(server.Ctx, "server Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(conn, msg)
|
||||
}
|
||||
|
||||
// Send 发送消息
|
||||
func (server *Server) Send(ctx context.Context, client *ClientConn, data interface{}) (err error) {
|
||||
MsgPkg(data, client.Auth, gctx.CtxId(ctx))
|
||||
return server.Write(client.Conn, data)
|
||||
}
|
||||
|
||||
// Reply 回复消息
|
||||
func (server *Server) Reply(ctx context.Context, data interface{}) (err error) {
|
||||
user := GetCtx(ctx)
|
||||
if user == nil {
|
||||
err = gerror.New("获取回复用户信息失败")
|
||||
return
|
||||
}
|
||||
MsgPkg(data, user.Auth, user.TraceID)
|
||||
return server.Write(user.Conn, data)
|
||||
}
|
||||
|
||||
// RpcRequest 向指定客户端发送消息并等待响应结果
|
||||
func (server *Server) RpcRequest(ctx context.Context, client *ClientConn, data interface{}) (res interface{}, err error) {
|
||||
var (
|
||||
traceID = MsgPkg(data, client.Auth, gctx.CtxId(ctx))
|
||||
key = server.rpc.GetCallId(client.Conn, traceID)
|
||||
)
|
||||
|
||||
if traceID == "" {
|
||||
err = gerror.New("traceID is required")
|
||||
return
|
||||
}
|
||||
|
||||
return server.rpc.Request(key, func() {
|
||||
server.Write(client.Conn, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
func (server *Server) getCronKey(s string) string {
|
||||
@@ -19,7 +20,7 @@ func (server *Server) stopCron() {
|
||||
|
||||
func (server *Server) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(server.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
if gcron.Search(server.getCronKey(consts.TCPCronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(server.Ctx, "@every 300s", func(ctx context.Context) {
|
||||
if server.clients == nil {
|
||||
return
|
||||
@@ -30,11 +31,11 @@ func (server *Server) startCron() {
|
||||
server.Logger.Debugf(server.Ctx, "client heartbeat timeout, close conn. auth:%+v", client.Auth)
|
||||
}
|
||||
}
|
||||
}, server.getCronKey(cronHeartbeatVerify))
|
||||
}, server.getCronKey(consts.TCPCronHeartbeatVerify))
|
||||
}
|
||||
|
||||
// 认证检查
|
||||
if gcron.Search(server.getCronKey(cronAuthVerify)) == nil {
|
||||
if gcron.Search(server.getCronKey(consts.TCPCronAuthVerify)) == nil {
|
||||
gcron.AddSingleton(server.Ctx, "@every 300s", func(ctx context.Context) {
|
||||
if server.clients == nil {
|
||||
return
|
||||
@@ -45,6 +46,6 @@ func (server *Server) startCron() {
|
||||
server.Logger.Debugf(server.Ctx, "client auth expired, close conn. auth:%+v", client.Auth)
|
||||
}
|
||||
}
|
||||
}, server.getCronKey(cronAuthVerify))
|
||||
}, server.getCronKey(consts.TCPCronAuthVerify))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -12,75 +12,73 @@ import (
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
func (server *Server) onServerLogin(args ...interface{}) {
|
||||
func (server *Server) onServerLogin(ctx context.Context, args ...interface{}) {
|
||||
var (
|
||||
in = new(msgin.ServerLogin)
|
||||
conn = args[1].(*gtcp.Conn)
|
||||
user = GetCtx(ctx)
|
||||
res = new(msgin.ResponseServerLogin)
|
||||
models *entity.SysServeLicense
|
||||
)
|
||||
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin message Scan failed:%+v, args:%+v", err, args)
|
||||
server.Logger.Warningf(ctx, "onServerLogin message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin in:%+v", *in)
|
||||
|
||||
err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
err := g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("appid = ?", in.AppId).
|
||||
Scan(&models)
|
||||
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Message = err.Error()
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
res.Code = 2
|
||||
res.Message = "授权信息不存在"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if err = VerifySign(in, models.Appid, models.SecretKey); err != nil {
|
||||
if _, err = VerifySign(in, models.Appid, models.SecretKey); err != nil {
|
||||
res.Code = 3
|
||||
res.Message = "签名错误,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Status != consts.StatusEnabled {
|
||||
res.Code = 4
|
||||
res.Message = "授权已禁用,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Group != in.Group {
|
||||
res.Code = 5
|
||||
res.Message = "你登录的授权分组未得到授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.EndAt.Before(gtime.Now()) {
|
||||
res.Code = 6
|
||||
res.Message = "授权已过期,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
allowedIps := convert.IpFilterStrategy(models.AllowedIps)
|
||||
if _, ok := allowedIps["*"]; !ok {
|
||||
ip := gstr.StrTillEx(conn.RemoteAddr().String(), ":")
|
||||
ip := gstr.StrTillEx(user.Conn.RemoteAddr().String(), ":")
|
||||
if _, ok2 := allowedIps[ip]; !ok2 {
|
||||
res.Code = 7
|
||||
res.Message = "IP(" + ip + ")未授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -99,14 +97,14 @@ func (server *Server) onServerLogin(args ...interface{}) {
|
||||
}
|
||||
|
||||
// 当前连接也踢掉
|
||||
server.Write(conn, res2)
|
||||
conn.Close()
|
||||
server.Write(user.Conn, res2)
|
||||
user.Conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
server.mutexConns.Lock()
|
||||
server.clients[conn.RemoteAddr().String()] = &ClientConn{
|
||||
Conn: conn,
|
||||
server.clients[user.Conn.RemoteAddr().String()] = &ClientConn{
|
||||
Conn: user.Conn,
|
||||
Auth: &AuthMeta{
|
||||
Group: in.Group,
|
||||
Name: in.Name,
|
||||
@@ -118,39 +116,46 @@ func (server *Server) onServerLogin(args ...interface{}) {
|
||||
}
|
||||
server.mutexConns.Unlock()
|
||||
|
||||
server.Write(conn, res)
|
||||
|
||||
_, err = g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
_, err = g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("id = ?", models.Id).Data(g.Map{
|
||||
"online": online,
|
||||
"login_times": models.LoginTimes + 1,
|
||||
"last_login_at": gtime.Now(),
|
||||
"last_active_at": gtime.Now(),
|
||||
"remote_addr": conn.RemoteAddr().String(),
|
||||
"remote_addr": user.Conn.RemoteAddr().String(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerLogin Update err:%+v", err)
|
||||
server.Logger.Warningf(ctx, "onServerLogin Update err:%+v", err)
|
||||
}
|
||||
|
||||
res.AppId = in.AppId
|
||||
res.Code = consts.TCPMsgCodeSuccess
|
||||
server.Write(user.Conn, res)
|
||||
}
|
||||
|
||||
func (server *Server) onServerHeartbeat(args ...interface{}) {
|
||||
var in *msgin.ServerHeartbeat
|
||||
if err := gconv.Scan(args, &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
func (server *Server) onServerHeartbeat(ctx context.Context, args ...interface{}) {
|
||||
var (
|
||||
in *msgin.ServerHeartbeat
|
||||
res = new(msgin.ResponseServerHeartbeat)
|
||||
)
|
||||
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
server.Logger.Warningf(ctx, "onServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
|
||||
client := args[1].(*ClientConn)
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
|
||||
server.Write(client.Conn, &msgin.ResponseServerHeartbeat{})
|
||||
|
||||
_, err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
_, err := g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("appid = ?", client.Auth.AppId).Data(g.Map{
|
||||
"last_active_at": gtime.Now(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerHeartbeat Update err:%+v", err)
|
||||
server.Logger.Warningf(ctx, "onServerHeartbeat Update err:%+v", err)
|
||||
}
|
||||
|
||||
res.Code = consts.TCPMsgCodeSuccess
|
||||
server.Write(client.Conn, res)
|
||||
|
||||
}
|
||||
|
||||
@@ -7,35 +7,38 @@ import (
|
||||
)
|
||||
|
||||
type Sign interface {
|
||||
SetSign(traceID, appId, secretKey string)
|
||||
SetSign(appId, secretKey string) *msgin.RpcMsg
|
||||
SetTraceID(traceID string)
|
||||
}
|
||||
|
||||
// SetSign 设置签名
|
||||
func SetSign(data interface{}, traceID, appId, secretKey string) {
|
||||
// PkgSign 打包签名
|
||||
func PkgSign(data interface{}, appId, secretKey, traceID string) *msgin.RpcMsg {
|
||||
if c, ok := data.(Sign); ok {
|
||||
c.SetSign(traceID, appId, secretKey)
|
||||
return
|
||||
c.SetTraceID(traceID)
|
||||
return c.SetSign(appId, secretKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySign 验证签名
|
||||
func VerifySign(data interface{}, appId, secretKey string) (err error) {
|
||||
func VerifySign(data interface{}, appId, secretKey string) (in *msgin.RpcMsg, err error) {
|
||||
// 无密钥,无需签名
|
||||
if secretKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var in *msgin.Request
|
||||
if err = gconv.Scan(data, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if appId != in.AppId {
|
||||
return gerror.New("appId invalid")
|
||||
err = gerror.New("appId invalid")
|
||||
return
|
||||
}
|
||||
|
||||
if in.Sign != in.GetSign(secretKey) {
|
||||
return gerror.New("sign invalid")
|
||||
err = gerror.New("sign invalid")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ package feishu
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"hotgo/internal/library/notify/feishu/internal/security"
|
||||
)
|
||||
|
||||
@@ -59,23 +60,18 @@ func (d *Client) Send(message Message) (string, *Response, error) {
|
||||
if err != nil {
|
||||
return "", res, err
|
||||
}
|
||||
reqString := string(reqBytes)
|
||||
|
||||
client := resty.New()
|
||||
URL := fmt.Sprintf("%v%v", feishuAPI, d.AccessToken)
|
||||
resp, err := client.SetRetryCount(3).R().
|
||||
SetBody(body).
|
||||
var (
|
||||
result *Response
|
||||
URL = fmt.Sprintf("%v%v", feishuAPI, d.AccessToken)
|
||||
reqString = string(reqBytes)
|
||||
)
|
||||
|
||||
g.Client().
|
||||
Retry(3, time.Second).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Post(URL)
|
||||
|
||||
if err != nil {
|
||||
return reqString, nil, err
|
||||
}
|
||||
|
||||
result := resp.Result().(*Response)
|
||||
PostVar(gctx.New(), URL, &result)
|
||||
if result.Code != 0 {
|
||||
return reqString, result, fmt.Errorf("send message to feishu error = %s", result.Msg)
|
||||
}
|
||||
|
||||
197
server/internal/library/payment/alipay/handle.go
Normal file
197
server/internal/library/payment/alipay/handle.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package alipay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/payin"
|
||||
)
|
||||
|
||||
func New(config *model.PayConfig) *aliPay {
|
||||
return &aliPay{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
type aliPay struct {
|
||||
config *model.PayConfig
|
||||
}
|
||||
|
||||
// Refund 订单退款
|
||||
func (h *aliPay) Refund(ctx context.Context, in payin.RefundInp) (res *payin.RefundModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("refund_amount", in.RefundMoney).
|
||||
Set("out_request_no", in.RefundSn).
|
||||
Set("refund_reason", in.Remark)
|
||||
|
||||
refund, err := client.TradeRefund(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if refund.Response.FundChange != "Y" {
|
||||
err = gerror.New("支付宝本次退款未发生资金变化!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Notify 异步通知
|
||||
func (h *aliPay) Notify(ctx context.Context, in payin.NotifyInp) (res *payin.NotifyModel, err error) {
|
||||
notifyReq, err := alipay.ParseNotifyToBodyMap(ghttp.RequestFromCtx(ctx).Request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 支付宝异步通知验签(公钥证书模式)
|
||||
ok, err := alipay.VerifySignWithCert(h.config.AliPayCertPublicKeyRSA2, notifyReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err = gerror.New("支付宝验签不通过!")
|
||||
return
|
||||
}
|
||||
|
||||
var notify *NotifyRequest
|
||||
if err = gconv.Scan(notifyReq, ¬ify); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if notify == nil {
|
||||
err = gerror.New("解析订单参数失败!")
|
||||
return
|
||||
}
|
||||
|
||||
if notify.TradeStatus != "TRADE_SUCCESS" {
|
||||
err = gerror.New("非交易支付成功状态,无需处理!")
|
||||
// 这里如果相对非交易支付成功状态进行处理,可自行调整此处逻辑
|
||||
// ...
|
||||
return
|
||||
}
|
||||
|
||||
if notify.OutTradeNo == "" {
|
||||
err = gerror.New("订单中没有找到商户单号!")
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.NotifyModel)
|
||||
res.TransactionId = notify.TradeNo
|
||||
res.OutTradeNo = notify.OutTradeNo
|
||||
res.PayAt = notify.GmtPayment
|
||||
res.ActualAmount = gconv.Float64(notify.ReceiptAmount)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateOrder 创建订单
|
||||
func (h *aliPay) CreateOrder(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置回调地址
|
||||
client.SetReturnUrl(in.Pay.ReturnUrl).SetNotifyUrl(in.Pay.NotifyUrl)
|
||||
|
||||
switch in.Pay.TradeType {
|
||||
case consts.TradeTypeAliScan, consts.TradeTypeAliWeb:
|
||||
return h.scan(ctx, in)
|
||||
case consts.TradeTypeAliWap:
|
||||
return h.wap(ctx, in)
|
||||
default:
|
||||
err = gerror.Newf("暂未支持的交易方式:%v", in.Pay.TradeType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetClient(config *model.PayConfig) (client *alipay.Client, err error) {
|
||||
client, err = alipay.NewClient(config.AliPayAppId, gfile.GetContents(config.AliPayPrivateKey), true)
|
||||
if err != nil {
|
||||
err = gerror.Newf("创建支付宝客户端失败:%+v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 打开Debug开关,输出日志,默认关闭
|
||||
if config.Debug {
|
||||
client.DebugSwitch = gopay.DebugOn
|
||||
}
|
||||
|
||||
client.SetLocation(alipay.LocationShanghai) // 设置时区,不设置或出错均为默认服务器时间
|
||||
|
||||
// 证书路径
|
||||
err = client.SetCertSnByPath(config.AliPayAppCertPublicKey, config.AliPayRootCert, config.AliPayCertPublicKeyRSA2)
|
||||
return
|
||||
}
|
||||
|
||||
// scan 扫码支付
|
||||
func (h *aliPay) scan(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置回调地址
|
||||
client.SetReturnUrl(in.Pay.ReturnUrl).SetNotifyUrl(in.Pay.NotifyUrl)
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("subject", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("total_amount", in.Pay.PayAmount)
|
||||
|
||||
payUrl, err := client.TradePagePay(ctx, bm)
|
||||
if err != nil {
|
||||
if bizErr, ok := alipay.IsBizError(err); ok {
|
||||
return nil, bizErr
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.PayURL = payUrl
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
return
|
||||
}
|
||||
|
||||
func (h *aliPay) wap(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置回调地址
|
||||
client.SetReturnUrl(in.Pay.ReturnUrl).SetNotifyUrl(in.Pay.NotifyUrl)
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("subject", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("total_amount", in.Pay.PayAmount).
|
||||
Set("product_code", "QUICK_WAP_WAY")
|
||||
|
||||
// 手机网站支付请求
|
||||
payUrl, err := client.TradeWapPay(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.PayURL = payUrl
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
return
|
||||
}
|
||||
61
server/internal/library/payment/alipay/model.go
Normal file
61
server/internal/library/payment/alipay/model.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package alipay
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
// NotifyRequest 支付宝异步通知参数
|
||||
// 文档:https://opendocs.alipay.com/open/203/105286
|
||||
type NotifyRequest struct {
|
||||
NotifyTime string `json:"notify_time,omitempty"`
|
||||
NotifyType string `json:"notify_type,omitempty"`
|
||||
NotifyId string `json:"notify_id,omitempty"`
|
||||
AppId string `json:"app_id,omitempty"`
|
||||
Charset string `json:"charset,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
SignType string `json:"sign_type,omitempty"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
AuthAppId string `json:"auth_app_id,omitempty"`
|
||||
TradeNo string `json:"trade_no,omitempty"`
|
||||
OutTradeNo string `json:"out_trade_no,omitempty"`
|
||||
OutBizNo string `json:"out_biz_no,omitempty"`
|
||||
BuyerId string `json:"buyer_id,omitempty"`
|
||||
BuyerLogonId string `json:"buyer_logon_id,omitempty"`
|
||||
SellerId string `json:"seller_id,omitempty"`
|
||||
SellerEmail string `json:"seller_email,omitempty"`
|
||||
TradeStatus string `json:"trade_status,omitempty"`
|
||||
TotalAmount string `json:"total_amount,omitempty"`
|
||||
ReceiptAmount string `json:"receipt_amount,omitempty"`
|
||||
InvoiceAmount string `json:"invoice_amount,omitempty"`
|
||||
BuyerPayAmount string `json:"buyer_pay_amount,omitempty"`
|
||||
PointAmount string `json:"point_amount,omitempty"`
|
||||
RefundFee string `json:"refund_fee,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
GmtCreate string `json:"gmt_create,omitempty"`
|
||||
GmtPayment *gtime.Time `json:"gmt_payment,omitempty"`
|
||||
GmtRefund string `json:"gmt_refund,omitempty"`
|
||||
GmtClose string `json:"gmt_close,omitempty"`
|
||||
FundBillList []*FundBillListInfo `json:"fund_bill_list,omitempty"`
|
||||
PassbackParams string `json:"passback_params,omitempty"`
|
||||
VoucherDetailList []*VoucherDetail `json:"voucher_detail_list,omitempty"`
|
||||
Method string `json:"method,omitempty"` // 电脑网站支付 支付宝请求 return_url 同步返回参数
|
||||
Timestamp string `json:"timestamp,omitempty"` // 电脑网站支付 支付宝请求 return_url 同步返回参数
|
||||
}
|
||||
|
||||
type FundBillListInfo struct {
|
||||
Amount string `json:"amount,omitempty"`
|
||||
FundChannel string `json:"fundChannel,omitempty"` // 异步通知里是 fundChannel
|
||||
}
|
||||
|
||||
type VoucherDetail struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Amount string `json:"amount,omitempty"`
|
||||
MerchantContribute string `json:"merchant_contribute,omitempty"`
|
||||
OtherContribute string `json:"other_contribute,omitempty"`
|
||||
Memo string `json:"memo,omitempty"`
|
||||
TemplateId string `json:"template_id,omitempty"`
|
||||
PurchaseBuyerContribute string `json:"purchase_buyer_contribute,omitempty"`
|
||||
PurchaseMerchantContribute string `json:"purchase_merchant_contribute,omitempty"`
|
||||
PurchaseAntContribute string `json:"purchase_ant_contribute,omitempty"`
|
||||
}
|
||||
13
server/internal/library/payment/config.go
Normal file
13
server/internal/library/payment/config.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package payment
|
||||
|
||||
import "hotgo/internal/model"
|
||||
|
||||
var config *model.PayConfig
|
||||
|
||||
func SetConfig(c *model.PayConfig) {
|
||||
config = c
|
||||
}
|
||||
|
||||
func GetConfig() *model.PayConfig {
|
||||
return config
|
||||
}
|
||||
44
server/internal/library/payment/notifycall.go
Normal file
44
server/internal/library/payment/notifycall.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/input/payin"
|
||||
"hotgo/utility/simple"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 异步回调
|
||||
|
||||
type NotifyCallFunc func(ctx context.Context, pay payin.NotifyCallFuncInp) (err error)
|
||||
|
||||
var (
|
||||
notifyCall = make(map[string]NotifyCallFunc)
|
||||
ncLock sync.Mutex
|
||||
)
|
||||
|
||||
// RegisterNotifyCall 注册支付成功回调方法
|
||||
func RegisterNotifyCall(group string, f NotifyCallFunc) {
|
||||
ncLock.Lock()
|
||||
defer ncLock.Unlock()
|
||||
if _, ok := notifyCall[group]; ok {
|
||||
panic("notifyCall repeat registration, group:" + group)
|
||||
}
|
||||
notifyCall[group] = f
|
||||
}
|
||||
|
||||
// NotifyCall 执行订单分组的异步回调
|
||||
func NotifyCall(ctx context.Context, in payin.NotifyCallFuncInp) {
|
||||
f, ok := notifyCall[in.Pay.OrderGroup]
|
||||
if ok {
|
||||
ctx = contexts.Detach(ctx)
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
if err := f(ctx, in); err != nil {
|
||||
g.Log().Warningf(ctx, "payment.NotifyCall in:%+v exec err:%+v", gjson.New(in.Pay).String(), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
121
server/internal/library/payment/payment.go
Normal file
121
server/internal/library/payment/payment.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/payment/alipay"
|
||||
"hotgo/internal/library/payment/qqpay"
|
||||
"hotgo/internal/library/payment/wxpay"
|
||||
"hotgo/internal/model/input/payin"
|
||||
"hotgo/utility/validate"
|
||||
)
|
||||
|
||||
// PayClient 支付客户端
|
||||
type PayClient interface {
|
||||
// CreateOrder 创建订单
|
||||
CreateOrder(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error)
|
||||
// Notify 异步通知
|
||||
Notify(ctx context.Context, in payin.NotifyInp) (res *payin.NotifyModel, err error)
|
||||
// Refund 订单退款
|
||||
Refund(ctx context.Context, in payin.RefundInp) (res *payin.RefundModel, err error)
|
||||
}
|
||||
|
||||
func New(name ...string) PayClient {
|
||||
var (
|
||||
payType = consts.PayTypeWxPay
|
||||
client PayClient
|
||||
)
|
||||
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
payType = name[0]
|
||||
}
|
||||
|
||||
switch payType {
|
||||
case consts.PayTypeAliPay:
|
||||
client = alipay.New(config)
|
||||
case consts.PayTypeWxPay:
|
||||
client = wxpay.New(config)
|
||||
case consts.PayTypeQQPay:
|
||||
client = qqpay.New(config)
|
||||
default:
|
||||
panic(fmt.Sprintf("暂不支持的支付方式:%v", payType))
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// GenOrderSn 生成业务订单号
|
||||
func GenOrderSn() string {
|
||||
orderSn := fmt.Sprintf("HG@%v%v", gtime.Now().Format("YmdHis"), grand.S(4))
|
||||
count, err := g.Model("pay_log").Where("order_sn", orderSn).Count()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("payment.GenOrderSn err:%+v", err))
|
||||
}
|
||||
if count > 0 {
|
||||
return GenOrderSn()
|
||||
}
|
||||
return orderSn
|
||||
}
|
||||
|
||||
// GenOutTradeNo 生成商户订单号
|
||||
func GenOutTradeNo() string {
|
||||
outTradeNo := fmt.Sprintf("%v%v", gtime.Now().Format("YmdHis"), grand.N(10000000, 99999999))
|
||||
count, err := g.Model("pay_log").Where("out_trade_no", outTradeNo).Count()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("payment.GenOutTradeNo err:%+v", err))
|
||||
}
|
||||
if count > 0 {
|
||||
return GenOutTradeNo()
|
||||
}
|
||||
return outTradeNo
|
||||
}
|
||||
|
||||
// GenRefundSn 生成退款订单号
|
||||
func GenRefundSn() string {
|
||||
outTradeNo := fmt.Sprintf("%v%v", gtime.Now().Format("YmdHis"), grand.N(10000, 99999))
|
||||
count, err := g.Model("pay_refund").Where("refund_trade_no", outTradeNo).Count()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("payment.GenRefundSn err:%+v", err))
|
||||
}
|
||||
if count > 0 {
|
||||
return GenRefundSn()
|
||||
}
|
||||
return outTradeNo
|
||||
}
|
||||
|
||||
// AutoTradeType 根据userAgent自动识别交易方式,在实际支付场景中你可以手动调整识别规则
|
||||
func AutoTradeType(payType, userAgent string) (tradeType string) {
|
||||
isMobile := validate.IsMobileVisit(userAgent)
|
||||
switch payType {
|
||||
case consts.PayTypeAliPay:
|
||||
if isMobile {
|
||||
return consts.TradeTypeAliWap
|
||||
}
|
||||
return consts.TradeTypeAliWeb
|
||||
case consts.PayTypeWxPay:
|
||||
if isMobile {
|
||||
if validate.IsWxBrowserVisit(userAgent) {
|
||||
return consts.TradeTypeWxMP
|
||||
}
|
||||
|
||||
if validate.IsWxMiniProgramVisit(userAgent) {
|
||||
return consts.TradeTypeWxMini
|
||||
}
|
||||
|
||||
return consts.TradeTypeWxH5
|
||||
}
|
||||
return consts.TradeTypeWxScan
|
||||
case consts.PayTypeQQPay:
|
||||
if isMobile {
|
||||
return consts.TradeTypeQQWap
|
||||
}
|
||||
return consts.TradeTypeQQWeb
|
||||
default:
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
134
server/internal/library/payment/qqpay/handle.go
Normal file
134
server/internal/library/payment/qqpay/handle.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package qqpay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/qq"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/payin"
|
||||
)
|
||||
|
||||
func New(config *model.PayConfig) *qqPay {
|
||||
return &qqPay{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
type qqPay struct {
|
||||
config *model.PayConfig
|
||||
}
|
||||
|
||||
// Refund 订单退款
|
||||
func (h *qqPay) Refund(ctx context.Context, in payin.RefundInp) (res *payin.RefundModel, err error) {
|
||||
err = gerror.New("暂不支持QQ支付申请退款,如有疑问请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
// Notify 异步通知
|
||||
func (h *qqPay) Notify(ctx context.Context, in payin.NotifyInp) (res *payin.NotifyModel, err error) {
|
||||
notifyReq, err := qq.ParseNotifyToBodyMap(ghttp.RequestFromCtx(ctx).Request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 验签操作
|
||||
ok, err := qq.VerifySign(h.config.QQPayApiKey, qq.SignType_MD5, notifyReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err = gerror.New("QQ支付验签不通过!")
|
||||
return
|
||||
}
|
||||
|
||||
var notify *NotifyRequest
|
||||
if err = gconv.Scan(notifyReq, ¬ify); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if notify == nil {
|
||||
err = gerror.New("解析订单参数失败!")
|
||||
return
|
||||
}
|
||||
|
||||
if notify.TradeState != "SUCCESS" {
|
||||
err = gerror.New("非交易支付成功状态,无需处理!")
|
||||
// 这里如果相对非交易支付成功状态进行处理,可自行调整此处逻辑
|
||||
// ...
|
||||
return
|
||||
}
|
||||
|
||||
if notify.OutTradeNo == "" {
|
||||
err = gerror.New("订单中没有找到商户单号!")
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.NotifyModel)
|
||||
res.TransactionId = notify.TransactionId
|
||||
res.OutTradeNo = notify.OutTradeNo
|
||||
res.PayAt = gtime.New(notify.TimeEnd)
|
||||
res.ActualAmount = gconv.Float64(notify.CouponFee) / 100 // 用户本次交易中,实际支付的金额 转为元,和系统内保持一至
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateOrder 创建订单
|
||||
func (h *qqPay) CreateOrder(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client := GetClient(h.config)
|
||||
|
||||
switch in.Pay.TradeType {
|
||||
case consts.TradeTypeQQWeb, consts.TradeTypeQQWap:
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.
|
||||
Set("mch_id", h.config.QQPayMchId).
|
||||
Set("body", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("notify_url", in.Pay.NotifyUrl).
|
||||
Set("nonce_str", grand.Letters(32)).
|
||||
Set("spbill_create_ip", in.Pay.CreateIp).
|
||||
Set("trade_type", "NATIVE"). // MICROPAY、APP、JSAPI、NATIVE
|
||||
Set("total_fee", int64(in.Pay.PayAmount*100))
|
||||
|
||||
qqRsp, err := client.UnifiedOrder(ctx, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if qqRsp.ReturnCode != "SUCCESS" {
|
||||
err = gerror.New(qqRsp.ReturnMsg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if qqRsp.ResultCode != "SUCCESS" {
|
||||
err = gerror.New(qqRsp.ErrCodeDes)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.PayURL = qqRsp.CodeUrl
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
|
||||
default:
|
||||
err = gerror.Newf("暂未支持的交易方式:%v", in.Pay.TradeType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetClient(config *model.PayConfig) (client *qq.Client) {
|
||||
client = qq.NewClient(config.QQPayMchId, config.QQPayApiKey)
|
||||
|
||||
// 打开Debug开关,输出日志,默认关闭
|
||||
if config.Debug {
|
||||
client.DebugSwitch = gopay.DebugOn
|
||||
}
|
||||
return
|
||||
}
|
||||
23
server/internal/library/payment/qqpay/model.go
Normal file
23
server/internal/library/payment/qqpay/model.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package qqpay
|
||||
|
||||
// NotifyRequest QQ支付异步通知参数
|
||||
// 文档:https://mp.qpay.tenpay.cn/buss/wiki/38/1204
|
||||
type NotifyRequest struct {
|
||||
Appid string `xml:"appid,omitempty" json:"appid,omitempty"`
|
||||
MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"`
|
||||
NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str,omitempty"`
|
||||
Sign string `xml:"sign,omitempty" json:"sign,omitempty"`
|
||||
DeviceInfo string `xml:"device_info,omitempty" json:"device_info,omitempty"`
|
||||
TradeType string `xml:"trade_type,omitempty" json:"trade_type,omitempty"`
|
||||
TradeState string `xml:"trade_state,omitempty" json:"trade_state,omitempty"`
|
||||
BankType string `xml:"bank_type,omitempty" json:"bank_type,omitempty"`
|
||||
FeeType string `xml:"fee_type,omitempty" json:"fee_type,omitempty"`
|
||||
TotalFee string `xml:"total_fee,omitempty" json:"total_fee,omitempty"`
|
||||
CashFee string `xml:"cash_fee,omitempty" json:"cash_fee,omitempty"`
|
||||
CouponFee string `xml:"coupon_fee,omitempty" json:"coupon_fee,omitempty"`
|
||||
TransactionId string `xml:"transaction_id,omitempty" json:"transaction_id,omitempty"`
|
||||
OutTradeNo string `xml:"out_trade_no,omitempty" json:"out_trade_no,omitempty"`
|
||||
Attach string `xml:"attach,omitempty" json:"attach,omitempty"`
|
||||
TimeEnd string `xml:"time_end,omitempty" json:"time_end,omitempty"`
|
||||
Openid string `xml:"openid,omitempty" json:"openid,omitempty"`
|
||||
}
|
||||
311
server/internal/library/payment/wxpay/handle.go
Normal file
311
server/internal/library/payment/wxpay/handle.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package wxpay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/xpem"
|
||||
"github.com/go-pay/gopay/wechat/v3"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
weOpen "hotgo/internal/library/wechat"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/input/payin"
|
||||
"time"
|
||||
)
|
||||
|
||||
func New(config *model.PayConfig) *wxPay {
|
||||
return &wxPay{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
type wxPay struct {
|
||||
config *model.PayConfig
|
||||
}
|
||||
|
||||
// Refund 订单退款
|
||||
func (h *wxPay) Refund(ctx context.Context, in payin.RefundInp) (res *payin.RefundModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("out_refund_no", in.RefundSn).
|
||||
Set("reason", in.Remark).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", int64(in.Pay.PayAmount*100)).
|
||||
Set("currency", "CNY").
|
||||
Set("refund", int64(in.RefundMoney*100))
|
||||
})
|
||||
|
||||
refund, err := client.V3Refund(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if refund.Error != "" {
|
||||
err = gerror.Newf("微信支付发起退款失败,原因:%v", refund.Response.Status)
|
||||
return
|
||||
}
|
||||
|
||||
if refund.Response.Status != "SUCCESS" && refund.Response.Status != "PROCESSING" {
|
||||
err = gerror.Newf("微信支付发起退款失败,状态码:%v", refund.Response.Status)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Notify 异步通知
|
||||
func (h *wxPay) Notify(ctx context.Context, in payin.NotifyInp) (res *payin.NotifyModel, err error) {
|
||||
notifyReq, err := wechat.V3ParseNotify(ghttp.RequestFromCtx(ctx).Request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取微信平台证书
|
||||
certMap, err := getPublicKeyMap(client)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证异步通知的签名
|
||||
if err = notifyReq.VerifySignByPKMap(certMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
notify, err := notifyReq.DecryptCipherText(h.config.WxPayAPIv3Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if notify.TradeState != "SUCCESS" {
|
||||
err = gerror.New("非交易支付成功状态,无需处理!")
|
||||
// 这里如果相对非交易支付成功状态进行处理,可自行调整此处逻辑
|
||||
// ...
|
||||
return
|
||||
}
|
||||
|
||||
if notify.OutTradeNo == "" {
|
||||
err = gerror.New("订单中没有找到商户单号!")
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.NotifyModel)
|
||||
res.TransactionId = notify.TransactionId
|
||||
res.OutTradeNo = notify.OutTradeNo
|
||||
res.PayAt = gtime.New(notify.SuccessTime)
|
||||
res.ActualAmount = float64(notify.Amount.PayerTotal / 100) // 转为元,和系统内保持一至
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateOrder 创建订单
|
||||
func (h *wxPay) CreateOrder(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
switch in.Pay.TradeType {
|
||||
case consts.TradeTypeWxScan:
|
||||
return h.scan(ctx, in)
|
||||
case consts.TradeTypeWxMP, consts.TradeTypeWxMini:
|
||||
return h.jsapi(ctx, in)
|
||||
case consts.TradeTypeWxH5:
|
||||
return h.h5(ctx, in)
|
||||
default:
|
||||
err = gerror.Newf("暂未支持的交易方式:%v", in.Pay.TradeType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetClient(config *model.PayConfig) (client *wechat.ClientV3, err error) {
|
||||
client, err = wechat.NewClientV3(config.WxPayMchId, config.WxPaySerialNo, config.WxPayAPIv3Key, config.WxPayPrivateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, _, err = client.GetAndSelectNewestCertALL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serialNo, snCertMap, err := client.GetAndSelectNewestCert()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
snPkMap := make(map[string]*rsa.PublicKey)
|
||||
for sn, cert := range snCertMap {
|
||||
pubKey, err := xpem.DecodePublicKey([]byte(cert))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snPkMap[sn] = pubKey
|
||||
}
|
||||
|
||||
client.SnCertMap = snPkMap
|
||||
client.WxSerialNo = serialNo
|
||||
|
||||
// 打开Debug开关,输出日志,默认关闭
|
||||
if config.Debug {
|
||||
client.DebugSwitch = gopay.DebugOn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPublicKeyMap(client *wechat.ClientV3) (wxPublicKeyMap map[string]*rsa.PublicKey, err error) {
|
||||
serialNo, snCertMap, err := client.GetAndSelectNewestCert()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
snPkMap := make(map[string]*rsa.PublicKey)
|
||||
for sn, cert := range snCertMap {
|
||||
pubKey, err := xpem.DecodePublicKey([]byte(cert))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snPkMap[sn] = pubKey
|
||||
}
|
||||
client.SnCertMap = snPkMap
|
||||
client.WxSerialNo = serialNo
|
||||
|
||||
wxPublicKeyMap = client.WxPublicKeyMap()
|
||||
return
|
||||
}
|
||||
|
||||
// scan 创建扫码支付订单
|
||||
func (h *wxPay) scan(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", h.config.WxPayAppId).
|
||||
Set("mchid", h.config.WxPayMchId).
|
||||
Set("description", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("time_expire", time.Now().Add(2*time.Hour).Format(time.RFC3339)).
|
||||
Set("notify_url", in.Pay.NotifyUrl).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", int64(in.Pay.PayAmount*100)).
|
||||
Set("currency", "CNY")
|
||||
})
|
||||
|
||||
wxRsp, err := client.V3TransactionNative(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wxRsp.Code != 0 {
|
||||
err = gerror.New(wxRsp.Error)
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.PayURL = wxRsp.Response.CodeUrl
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
return
|
||||
}
|
||||
|
||||
// h5 创建H5支付订单
|
||||
func (h *wxPay) h5(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化参数Map
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", h.config.WxPayAppId).
|
||||
Set("mchid", h.config.WxPayMchId).
|
||||
Set("description", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("time_expire", time.Now().Add(2*time.Hour).Format(time.RFC3339)).
|
||||
Set("notify_url", in.Pay.NotifyUrl).
|
||||
SetBodyMap("amount", func(b gopay.BodyMap) {
|
||||
b.Set("total", int64(in.Pay.PayAmount*100)).
|
||||
Set("currency", "CNY")
|
||||
}).
|
||||
SetBodyMap("scene_info", func(b gopay.BodyMap) {
|
||||
b.Set("payer_client_ip", in.Pay.CreateIp).
|
||||
SetBodyMap("h5_info", func(b gopay.BodyMap) {
|
||||
b.Set("type", "Wap")
|
||||
})
|
||||
})
|
||||
|
||||
// 请求支付下单,成功后得到结果
|
||||
wxRsp, err := client.V3TransactionH5(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wxRsp.Code != 0 {
|
||||
err = gerror.New(wxRsp.Error)
|
||||
return
|
||||
}
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.PayURL = wxRsp.Response.H5Url
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
return
|
||||
}
|
||||
|
||||
// jsapi 创建jsapi支付订单
|
||||
func (h *wxPay) jsapi(ctx context.Context, in payin.CreateOrderInp) (res *payin.CreateOrderModel, err error) {
|
||||
jsApi := new(payin.JSAPI)
|
||||
jsApi.Config, err = weOpen.GetJsConfig(ctx, in.Pay.ReturnUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := GetClient(h.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", h.config.WxPayAppId).
|
||||
Set("mchid", h.config.WxPayMchId).
|
||||
Set("description", in.Pay.Subject).
|
||||
Set("out_trade_no", in.Pay.OutTradeNo).
|
||||
Set("time_expire", time.Now().Add(2*time.Hour).Format(time.RFC3339)).
|
||||
Set("notify_url", in.Pay.NotifyUrl).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", int64(in.Pay.PayAmount*100)).
|
||||
Set("currency", "CNY")
|
||||
}).
|
||||
SetBodyMap("payer", func(bm gopay.BodyMap) {
|
||||
bm.Set("openid", in.Pay.Openid)
|
||||
})
|
||||
|
||||
wxRsp, err := client.V3TransactionJsapi(ctx, bm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wxRsp.Code != 0 {
|
||||
err = gerror.New(wxRsp.Error)
|
||||
return
|
||||
}
|
||||
|
||||
js, err := client.PaySignOfJSAPI(h.config.WxPayAppId, wxRsp.Response.PrepayId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
jsApi.Params = js
|
||||
|
||||
res = new(payin.CreateOrderModel)
|
||||
res.TradeType = in.Pay.TradeType
|
||||
res.OutTradeNo = in.Pay.OutTradeNo
|
||||
res.JsApi = jsApi
|
||||
return
|
||||
}
|
||||
1
server/internal/library/payment/wxpay/model.go
Normal file
1
server/internal/library/payment/wxpay/model.go
Normal file
@@ -0,0 +1 @@
|
||||
package wxpay
|
||||
@@ -51,7 +51,7 @@ func (q *DiskConsumerMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg M
|
||||
if mqMsg.MsgId != "" {
|
||||
receiveDo(mqMsg)
|
||||
queue.Commit(index, offset)
|
||||
sleep = time.Millisecond * 1
|
||||
sleep = time.Millisecond * 10
|
||||
}
|
||||
} else {
|
||||
sleep = time.Second
|
||||
@@ -102,6 +102,11 @@ func (d *DiskProducerMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, er
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DiskProducerMq) SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error) {
|
||||
err = gerror.New("implement me")
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DiskProducerMq) getProducer(topic string) *disk.Queue {
|
||||
queue, ok := d.producers[topic]
|
||||
if ok {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
@@ -76,6 +75,11 @@ func (r *KafkaMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error
|
||||
return mqMsg, nil
|
||||
}
|
||||
|
||||
func (r *KafkaMq) SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error) {
|
||||
err = gerror.New("implement me")
|
||||
return
|
||||
}
|
||||
|
||||
// ListenReceiveMsgDo 消费数据
|
||||
func (r *KafkaMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
|
||||
if r.consumerIns == nil {
|
||||
|
||||
@@ -3,35 +3,28 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/utility/charset"
|
||||
)
|
||||
|
||||
const (
|
||||
ConsumerLogErrFormat = "消费 [%s] 失败, mqMsgId:%+v, mqMsgData:%+v, err:%+v, stack:%+v"
|
||||
ProducerLogErrFormat = "生产 [%s] 失败, data:%+v, err:%+v, stack:%+v"
|
||||
ConsumerLogErrFormat = "消费 [%s] 失败, body:%+v, err:%+v"
|
||||
ProducerLogErrFormat = "生产 [%s] 失败, body:%+v, err:%+v"
|
||||
)
|
||||
|
||||
// ConsumerLog 消费日志
|
||||
func ConsumerLog(ctx context.Context, topic string, mqMsg MqMsg, err error) {
|
||||
if err != nil {
|
||||
g.Log().Printf(ctx, ConsumerLogErrFormat, topic, mqMsg.MsgId, mqMsg.BodyString(), err, charset.ParseErrStack(err))
|
||||
} else {
|
||||
g.Log().Print(ctx, "消费 ["+topic+"] 成功", mqMsg.MsgId)
|
||||
g.Log().Errorf(ctx, ConsumerLogErrFormat, topic, string(mqMsg.Body), err)
|
||||
}
|
||||
}
|
||||
|
||||
// ProducerLog 生产日志
|
||||
func ProducerLog(ctx context.Context, topic string, data interface{}, err error) {
|
||||
func ProducerLog(ctx context.Context, topic string, mqMsg MqMsg, err error) {
|
||||
if err != nil {
|
||||
g.Log().Printf(ctx, ProducerLogErrFormat, topic, gconv.String(data), err, charset.ParseErrStack(err))
|
||||
} else {
|
||||
g.Log().Print(ctx, "生产 ["+topic+"] 成功", gconv.String(data))
|
||||
g.Log().Errorf(ctx, ProducerLogErrFormat, topic, string(mqMsg.Body), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@ func Push(topic string, data interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
mqMsg, err := q.SendMsg(topic, gconv.String(data))
|
||||
ProducerLog(ctx, topic, mqMsg.MsgId, err)
|
||||
return err
|
||||
ProducerLog(ctx, topic, mqMsg, err)
|
||||
return
|
||||
}
|
||||
|
||||
// DelayPush 推送延迟队列
|
||||
func DelayPush(topic string, data interface{}, second int64) (err error) {
|
||||
q, err := InstanceProducer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mqMsg, err := q.SendDelayMsg(topic, gconv.String(data), second)
|
||||
ProducerLog(ctx, topic, mqMsg, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
type MqProducer interface {
|
||||
SendMsg(topic string, body string) (mqMsg MqMsg, err error)
|
||||
SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error)
|
||||
SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error)
|
||||
}
|
||||
|
||||
type MqConsumer interface {
|
||||
@@ -42,10 +43,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
type RedisConf struct {
|
||||
Address string `json:"address"`
|
||||
Db int `json:"db"`
|
||||
Pass string `json:"pass"`
|
||||
IdleTimeout int `json:"idleTimeout"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
type RocketmqConf struct {
|
||||
Address []string `json:"address"`
|
||||
@@ -124,19 +122,12 @@ func NewProducer(groupName string) (mqClient MqProducer, err error) {
|
||||
Version: config.Kafka.Version,
|
||||
})
|
||||
case "redis":
|
||||
address := g.Cfg().MustGet(ctx, "queue.redis.address", nil).String()
|
||||
if len(address) == 0 {
|
||||
err = gerror.New("queue redis address is not support")
|
||||
return
|
||||
if _, err = g.Redis().Do(ctx, "ping"); err == nil {
|
||||
mqClient = RegisterRedisMqProducer(RedisOption{
|
||||
Timeout: config.Redis.Timeout,
|
||||
}, groupName)
|
||||
}
|
||||
mqClient, err = RegisterRedisMqProducer(RedisOption{
|
||||
Addr: config.Redis.Address,
|
||||
Passwd: config.Redis.Pass,
|
||||
DBnum: config.Redis.Db,
|
||||
Timeout: config.Redis.IdleTimeout,
|
||||
}, PoolOption{
|
||||
5, 50, 5,
|
||||
}, groupName, config.Retry)
|
||||
|
||||
case "disk":
|
||||
config.Disk.GroupName = groupName
|
||||
mqClient, err = RegisterDiskMqProducer(config.Disk)
|
||||
@@ -197,19 +188,11 @@ func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
|
||||
ClientId: clientId,
|
||||
})
|
||||
case "redis":
|
||||
if len(config.Redis.Address) == 0 {
|
||||
err = gerror.New("queue redis address is not support")
|
||||
return
|
||||
if _, err = g.Redis().Do(ctx, "ping"); err == nil {
|
||||
mqClient = RegisterRedisMqConsumer(RedisOption{
|
||||
Timeout: config.Redis.Timeout,
|
||||
}, groupName)
|
||||
}
|
||||
|
||||
mqClient, err = RegisterRedisMqConsumer(RedisOption{
|
||||
Addr: config.Redis.Address,
|
||||
Passwd: config.Redis.Pass,
|
||||
DBnum: config.Redis.Db,
|
||||
Timeout: config.Redis.IdleTimeout,
|
||||
}, PoolOption{
|
||||
5, 50, 5,
|
||||
}, groupName)
|
||||
case "disk":
|
||||
config.Disk.GroupName = groupName
|
||||
mqClient, err = RegisterDiskMqConsumer(config.Disk)
|
||||
@@ -1,42 +1,26 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/bufanyun/pool"
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"hotgo/utility/encrypt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RedisMq struct {
|
||||
poolName string
|
||||
groupName string
|
||||
retry int
|
||||
timeout int
|
||||
}
|
||||
|
||||
type PoolOption struct {
|
||||
InitCap int
|
||||
MaxCap int
|
||||
IdleTimeout int
|
||||
timeout int64
|
||||
}
|
||||
|
||||
type RedisOption struct {
|
||||
Addr string
|
||||
Passwd string
|
||||
DBnum int
|
||||
Timeout int
|
||||
}
|
||||
|
||||
var redisPoolMap map[string]pool.Pool
|
||||
|
||||
func init() {
|
||||
redisPoolMap = make(map[string]pool.Pool)
|
||||
|
||||
Timeout int64
|
||||
}
|
||||
|
||||
// SendMsg 按字符串类型生产数据
|
||||
@@ -49,43 +33,88 @@ func (r *RedisMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error
|
||||
if r.poolName == "" {
|
||||
return mqMsg, gerror.New("RedisMq producer not register")
|
||||
}
|
||||
|
||||
if topic == "" {
|
||||
return mqMsg, gerror.New("RedisMq topic is empty")
|
||||
}
|
||||
|
||||
msgId := getRandMsgId()
|
||||
rdx, put, err := getRedis(r.poolName, r.retry)
|
||||
defer put()
|
||||
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者获取redis实例失败:", err))
|
||||
}
|
||||
|
||||
mqMsg = MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: topic,
|
||||
MsgId: msgId,
|
||||
MsgId: getRandMsgId(),
|
||||
Body: body,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
mqMsgJson, err := json.Marshal(mqMsg)
|
||||
|
||||
data, err := json.Marshal(mqMsg)
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者解析json消息失败:", err))
|
||||
return
|
||||
}
|
||||
|
||||
queueName := r.genQueueName(r.groupName, topic)
|
||||
|
||||
_, err = redis.Int64(rdx.Do("LPUSH", queueName, mqMsgJson))
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者添加消息失败:", err))
|
||||
key := r.genKey(r.groupName, topic)
|
||||
if _, err = g.Redis().Do(ctx, "LPUSH", key, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.timeout > 0 {
|
||||
_, err = rdx.Do("EXPIRE", queueName, r.timeout)
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者设置过期时间失败:", err))
|
||||
if _, err = g.Redis().Do(ctx, "EXPIRE", key, r.timeout); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RedisMq) SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error) {
|
||||
if delaySecond < 1 {
|
||||
return r.SendMsg(topic, body)
|
||||
}
|
||||
|
||||
if r.poolName == "" {
|
||||
err = gerror.New("SendDelayMsg RedisMq not register")
|
||||
return
|
||||
}
|
||||
|
||||
if topic == "" {
|
||||
err = gerror.New("SendDelayMsg RedisMq topic is empty")
|
||||
return
|
||||
}
|
||||
|
||||
mqMsg = MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: topic,
|
||||
MsgId: getRandMsgId(),
|
||||
Body: []byte(body),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(mqMsg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
conn = g.Redis()
|
||||
key = r.genKey(r.groupName, "delay:"+topic)
|
||||
expireSecond = time.Now().Unix() + delaySecond
|
||||
timePiece = fmt.Sprintf("%s:%d", key, expireSecond)
|
||||
z = gredis.ZAddMember{Score: float64(expireSecond), Member: timePiece}
|
||||
)
|
||||
|
||||
if _, err = conn.ZAdd(ctx, key, &gredis.ZAddOption{}, z); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.RPush(ctx, timePiece, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// consumer will also delete the item
|
||||
if r.timeout > 0 {
|
||||
_, _ = conn.Expire(ctx, timePiece, r.timeout+delaySecond)
|
||||
_, _ = conn.Expire(ctx, key, r.timeout)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,190 +127,148 @@ func (r *RedisMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg))
|
||||
return gerror.New("RedisMq topic is empty")
|
||||
}
|
||||
|
||||
queueName := r.genQueueName(r.groupName, topic)
|
||||
var (
|
||||
key = r.genKey(r.groupName, topic)
|
||||
key2 = r.genKey(r.groupName, "delay:"+topic)
|
||||
)
|
||||
|
||||
go func() {
|
||||
for range time.Tick(500 * time.Millisecond) {
|
||||
mqMsgList := r.loopReadQueue(queueName)
|
||||
for range time.Tick(300 * time.Millisecond) {
|
||||
mqMsgList := r.loopReadQueue(key)
|
||||
for _, mqMsg := range mqMsgList {
|
||||
receiveDo(mqMsg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
mqMsgCh, errCh := r.loopReadDelayQueue(key2)
|
||||
for mqMsg := range mqMsgCh {
|
||||
receiveDo(mqMsg)
|
||||
}
|
||||
for err = range errCh {
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
g.Log().Infof(ctx, "ListenReceiveMsgDo Delay topic:%v, err:%+v", topic, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
// 生成队列名称
|
||||
func (r *RedisMq) genQueueName(groupName string, topic string) string {
|
||||
// 生成队列key
|
||||
func (r *RedisMq) genKey(groupName string, topic string) string {
|
||||
return fmt.Sprintf("queue:%s_%s", groupName, topic)
|
||||
}
|
||||
|
||||
func (r *RedisMq) loopReadQueue(queueName string) (mqMsgList []MqMsg) {
|
||||
rdx, put, err := getRedis(r.poolName, r.retry)
|
||||
defer put()
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue getRedis err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RedisMq) loopReadQueue(key string) (mqMsgList []MqMsg) {
|
||||
conn := g.Redis()
|
||||
for {
|
||||
infoByte, err := redis.Bytes(rdx.Do("RPOP", queueName))
|
||||
if redis.ErrNil == err || len(infoByte) == 0 {
|
||||
break
|
||||
}
|
||||
data, err := conn.Do(ctx, "RPOP", key)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue redis RPOP err:%+v", err)
|
||||
break
|
||||
}
|
||||
|
||||
var mqMsg MqMsg
|
||||
if err = json.Unmarshal(infoByte, &mqMsg); err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue Unmarshal err:%+v", err)
|
||||
if data.IsEmpty() {
|
||||
break
|
||||
}
|
||||
|
||||
var mqMsg MqMsg
|
||||
if err = data.Scan(&mqMsg); err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue Scan err:%+v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if mqMsg.MsgId != "" {
|
||||
mqMsgList = append(mqMsgList, mqMsg)
|
||||
}
|
||||
|
||||
}
|
||||
return mqMsgList
|
||||
}
|
||||
|
||||
func RegisterRedisMqProducer(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (client MqProducer, err error) {
|
||||
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, retry)
|
||||
if err != nil {
|
||||
err = gerror.Newf("RegisterRedisMqProducer err:%+v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
func RegisterRedisMqProducer(connOpt RedisOption, groupName string) (client MqProducer) {
|
||||
return RegisterRedisMq(connOpt, groupName)
|
||||
}
|
||||
|
||||
// RegisterRedisMqConsumer 注册消费者
|
||||
func RegisterRedisMqConsumer(connOpt RedisOption, poolOpt PoolOption, groupName string) (client MqConsumer, err error) {
|
||||
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, 0)
|
||||
if err != nil {
|
||||
err = gerror.Newf("RegisterRedisMqConsumer err:%+v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
func RegisterRedisMqConsumer(connOpt RedisOption, groupName string) (client MqConsumer) {
|
||||
return RegisterRedisMq(connOpt, groupName)
|
||||
}
|
||||
|
||||
// RegisterRedisMq 注册redis实例
|
||||
func RegisterRedisMq(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (mqIns *RedisMq, err error) {
|
||||
poolName, err := registerRedis(connOpt.Addr, connOpt.Passwd, connOpt.DBnum, poolOpt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if retry <= 0 {
|
||||
retry = 0
|
||||
}
|
||||
|
||||
mqIns = &RedisMq{
|
||||
poolName: poolName,
|
||||
func RegisterRedisMq(connOpt RedisOption, groupName string) *RedisMq {
|
||||
return &RedisMq{
|
||||
poolName: encrypt.Md5ToString(fmt.Sprintf("%s-%d", groupName, time.Now().UnixNano())),
|
||||
groupName: groupName,
|
||||
retry: retry,
|
||||
timeout: connOpt.Timeout,
|
||||
}
|
||||
return mqIns, nil
|
||||
}
|
||||
|
||||
// RegisterRedis 注册一个redis配置
|
||||
func registerRedis(host, pass string, dbNum int, opt PoolOption) (poolName string, err error) {
|
||||
poolName = encrypt.Md5ToString(fmt.Sprintf("%s-%s-%d", host, pass, dbNum))
|
||||
if _, ok := redisPoolMap[poolName]; ok {
|
||||
return poolName, nil
|
||||
}
|
||||
|
||||
connRedis := func() (interface{}, error) {
|
||||
conn, err := redis.Dial("tcp", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pass != "" {
|
||||
if _, err = conn.Do("AUTH", pass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if dbNum > 0 {
|
||||
if _, err = conn.Do("SELECT", dbNum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// closeRedis 关闭连接
|
||||
closeRedis := func(v interface{}) error {
|
||||
return v.(redis.Conn).Close()
|
||||
}
|
||||
|
||||
// pingRedis 检测连接连通性
|
||||
pingRedis := func(v interface{}) error {
|
||||
conn := v.(redis.Conn)
|
||||
val, err := redis.String(conn.Do("PING"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val != "PONG" {
|
||||
return gerror.New("queue redis ping is error ping => " + val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := pool.NewChannelPool(&pool.Config{
|
||||
InitialCap: opt.InitCap,
|
||||
MaxCap: opt.MaxCap,
|
||||
Factory: connRedis,
|
||||
Close: closeRedis,
|
||||
Ping: pingRedis,
|
||||
IdleTimeout: time.Duration(opt.IdleTimeout) * time.Second,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return poolName, err
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
redisPoolMap[poolName] = p
|
||||
return poolName, nil
|
||||
}
|
||||
|
||||
// getRedis 获取一个redis db连接
|
||||
func getRedis(poolName string, retry int) (db redis.Conn, put func(), err error) {
|
||||
put = func() {}
|
||||
if _, ok := redisPoolMap[poolName]; ok == false {
|
||||
return nil, put, gerror.New("db connect is nil")
|
||||
}
|
||||
redisPool := redisPoolMap[poolName]
|
||||
|
||||
conn, err := redisPool.Get()
|
||||
for i := 0; i < retry; i++ {
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
conn, err = redisPool.Get()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, put, err
|
||||
}
|
||||
|
||||
put = func() {
|
||||
if err = redisPool.Put(conn); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
db = conn.(redis.Conn)
|
||||
return db, put, nil
|
||||
}
|
||||
|
||||
func getRandMsgId() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
rand.NewSource(time.Now().UnixNano())
|
||||
radium := rand.Intn(999) + 1
|
||||
timeCode := time.Now().UnixNano()
|
||||
return fmt.Sprintf("%d%.4d", timeCode, radium)
|
||||
}
|
||||
|
||||
func (r *RedisMq) loopReadDelayQueue(key string) (resCh chan MqMsg, errCh chan error) {
|
||||
resCh = make(chan MqMsg, 0)
|
||||
errCh = make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resCh)
|
||||
defer close(errCh)
|
||||
|
||||
conn := g.Redis()
|
||||
for {
|
||||
now := time.Now().Unix()
|
||||
do, err := conn.Do(ctx, "zrangebyscore", key, "0", strconv.FormatInt(now, 10), "limit", 0, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
val := do.Strings()
|
||||
if len(val) == 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errCh <- ctx.Err()
|
||||
return
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, listK := range val {
|
||||
for {
|
||||
pop, err := conn.LPop(ctx, listK)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
} else if pop.IsEmpty() {
|
||||
conn.ZRem(ctx, key, listK)
|
||||
conn.Del(ctx, listK)
|
||||
break
|
||||
} else {
|
||||
var mqMsg MqMsg
|
||||
if err = pop.Scan(&mqMsg); err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadDelayQueue Scan err:%+v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if mqMsg.MsgId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case resCh <- mqMsg:
|
||||
case <-ctx.Done():
|
||||
errCh <- ctx.Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return resCh, errCh
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user