Compare commits
8 Commits
80f04c92ec
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 32e56b1a0f | |||
| b436a6cade | |||
| dd08655e2c | |||
| 9fe6cb4bf5 | |||
| cf4bc4932a | |||
| dbc909c736 | |||
| 71554da541 | |||
| 8f89503c88 |
21
README.md
21
README.md
@@ -1,29 +1,16 @@
|
||||
## TODO
|
||||
|
||||
提取代理:
|
||||
- 网关修改问题
|
||||
- 在提取流程中如果网关被修改,有可能导致数据不一致
|
||||
- 提供读写锁,在提取流程中获取读锁,在修改网关时获取写锁
|
||||
- 端口悬空问题
|
||||
- 在提取流程中如果发生异常,可能导致端口被占用但通道信息没有被写入数据库,配置以及端口将无法解除
|
||||
- 提交删除任务时,带上配置信息,无需再查数据库且接口总是会被正确清理
|
||||
- 异常情况下,将只清理网关端口,而无法解除节点连接,这个问题需要额外处理
|
||||
|
||||
---
|
||||
|
||||
- otel 没有正确记录接口失败信息
|
||||
|
||||
筛选和关联展示功能扩展
|
||||
用反射实现环境变量解析,以简化函数签名
|
||||
|
||||
错误提示增强,展示整链路信息
|
||||
|
||||
ip 提取频率限制,在 ensure 函数加逻辑,通过 redis 或者 pg 计算分钟内提取次数,只允许每分钟提取 30 次
|
||||
|
||||
proxy 的删除和更新,锁粒度应该有问题
|
||||
|
||||
交易信息持久化
|
||||
|
||||
用反射实现环境变量解析,以简化函数签名
|
||||
订单关闭问题,在前端关闭窗口后直接调用了全部订单接口,应改成先确认再关闭
|
||||
- 取消订单接口改成只允许管理员调用
|
||||
- 新增关闭订单接口,关闭订单的逻辑是先尝试完成,如果订单未支付则取消订单
|
||||
|
||||
---
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -26,6 +26,7 @@ require (
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/sync v0.20.0
|
||||
gorm.io/datatypes v1.2.7
|
||||
@@ -88,7 +89,6 @@ require (
|
||||
go.opentelemetry.io/contrib v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
|
||||
21
pkg/u/u.go
21
pkg/u/u.go
@@ -81,24 +81,15 @@ func Map[T any, R any](src []T, convert func(T) R) []R {
|
||||
// 时间
|
||||
// ====================
|
||||
|
||||
func DateHead(date time.Time) time.Time {
|
||||
var y, m, d = date.Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, date.Location())
|
||||
}
|
||||
|
||||
func DateTail(date time.Time) time.Time {
|
||||
var y, m, d = date.Date()
|
||||
return time.Date(y, m, d, 23, 59, 59, 999999999, date.Location())
|
||||
func IsSameDate(date1, date2 time.Time) bool {
|
||||
var y1, m1, d1 = date1.Local().Date()
|
||||
var y2, m2, d2 = date2.Local().Date()
|
||||
return y1 == y2 && m1 == m2 && d1 == d2
|
||||
}
|
||||
|
||||
func Today() time.Time {
|
||||
return DateHead(time.Now())
|
||||
}
|
||||
|
||||
func IsSameDate(date1, date2 time.Time) bool {
|
||||
var y1, m1, d1 = date1.Date()
|
||||
var y2, m2, d2 = date2.Date()
|
||||
return y1 == y2 && m1 == m2 && d1 == d2
|
||||
var y, m, d = time.Now().Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, time.Local)
|
||||
}
|
||||
|
||||
func IsToday(date time.Time) bool {
|
||||
|
||||
@@ -16,7 +16,7 @@ func FindSession(accessToken string, now time.Time) (*m.Session, error) {
|
||||
Preload(field.Associations).
|
||||
Where(
|
||||
q.Session.AccessToken.Eq(accessToken),
|
||||
q.Session.AccessTokenExpires.Gt(now),
|
||||
q.Session.AccessTokenExpires.Gt(now.UTC()),
|
||||
).First()
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func FindSessionByRefresh(refreshToken string, now time.Time) (*m.Session, error
|
||||
Preload(field.Associations).
|
||||
Where(
|
||||
q.Session.RefreshToken.Eq(refreshToken),
|
||||
q.Session.RefreshTokenExpires.Gt(now),
|
||||
q.Session.RefreshTokenExpires.Gt(now.UTC()),
|
||||
).First()
|
||||
}
|
||||
|
||||
|
||||
9
web/events/edges.go
Normal file
9
web/events/edges.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package events
|
||||
|
||||
import "github.com/hibiken/asynq"
|
||||
|
||||
const RefreshEdge = "edge:refresh"
|
||||
|
||||
func NewRefreshEdge() *asynq.Task {
|
||||
return asynq.NewTask(RefreshEdge, nil)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
@@ -31,12 +30,10 @@ func PageBalanceActivity(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Bill.As("Bill").BillNo.Eq(*req.BillNo))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询余额变动列表
|
||||
@@ -93,16 +90,14 @@ func PageBalanceActivityByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Bill.As("Bill").BillNo.Eq(*req.BillNo))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询余额变动列表
|
||||
list, total, err := q.BalanceActivity.Debug().
|
||||
list, total, err := q.BalanceActivity.
|
||||
Joins(q.BalanceActivity.User, q.BalanceActivity.Admin, q.BalanceActivity.Bill).
|
||||
Select(
|
||||
q.BalanceActivity.ALL,
|
||||
@@ -155,12 +150,10 @@ func PageBalanceActivityOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Bill.As("Bill").BillNo.Eq(*req.BillNo))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(t))
|
||||
do = do.Where(q.BalanceActivity.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询余额变动列表
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
c "platform/web/core"
|
||||
@@ -29,13 +28,18 @@ func PageBatch(ctx *fiber.Ctx) error {
|
||||
// 查询批次
|
||||
conds := q.LogsUserUsage.Where(q.LogsUserUsage.UserID.Eq(authCtx.User.ID))
|
||||
if req.TimeStart != nil {
|
||||
conds.Where(q.LogsUserUsage.Time.Gte(*req.TimeStart))
|
||||
conds.Where(q.LogsUserUsage.Time.Gte(req.TimeStart.UTC()))
|
||||
}
|
||||
if req.TimeEnd != nil {
|
||||
conds.Where(q.LogsUserUsage.Time.Lte(*req.TimeEnd))
|
||||
conds.Where(q.LogsUserUsage.Time.Lte(req.TimeEnd.UTC()))
|
||||
}
|
||||
if req.ResourceNo != nil {
|
||||
conds.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
|
||||
}
|
||||
|
||||
list, total, err := q.LogsUserUsage.Where(conds).
|
||||
list, total, err := q.LogsUserUsage.
|
||||
Joins(q.LogsUserUsage.Resource).
|
||||
Where(conds).
|
||||
Order(q.LogsUserUsage.Time.Desc()).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
@@ -53,6 +57,7 @@ func PageBatch(ctx *fiber.Ctx) error {
|
||||
|
||||
type PageResourceBatchReq struct {
|
||||
c.PageReq
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
TimeStart *time.Time `json:"time_start"`
|
||||
TimeEnd *time.Time `json:"time_end"`
|
||||
}
|
||||
@@ -89,12 +94,10 @@ func PageBatchByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.LogsUserUsage.ISP.Eq(*req.Isp))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.LogsUserUsage.Time.Gte(time))
|
||||
do = do.Where(q.LogsUserUsage.Time.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.LogsUserUsage.Time.Lte(time))
|
||||
do = do.Where(q.LogsUserUsage.Time.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
list, total, err := q.LogsUserUsage.
|
||||
@@ -104,6 +107,7 @@ func PageBatchByAdmin(c *fiber.Ctx) error {
|
||||
q.User.As("User").Phone.As("User__phone"),
|
||||
q.User.As("User").Name.As("User__name"),
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.LogsUserUsage.Time.Desc()).
|
||||
@@ -158,12 +162,10 @@ func PageBatchOfUserByAdmin(ctx *fiber.Ctx) error {
|
||||
do = do.Where(q.LogsUserUsage.ISP.Eq(*req.Isp))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.LogsUserUsage.Time.Gte(t))
|
||||
do = do.Where(q.LogsUserUsage.Time.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.LogsUserUsage.Time.Lte(t))
|
||||
do = do.Where(q.LogsUserUsage.Time.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
list, total, err := q.LogsUserUsage.
|
||||
@@ -173,6 +175,7 @@ func PageBatchOfUserByAdmin(ctx *fiber.Ctx) error {
|
||||
q.User.As("User").Phone.As("User__phone"),
|
||||
q.User.As("User").Name.As("User__name"),
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.LogsUserUsage.Time.Desc()).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
@@ -40,12 +39,10 @@ func PageBillByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Bill.BillNo.Eq(*req.BillNo))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Bill.CreatedAt.Gte(time))
|
||||
do = do.Where(q.Bill.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateHead(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Bill.CreatedAt.Lte(time))
|
||||
do = do.Where(q.Bill.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
if req.ProductCode != nil {
|
||||
do = do.Where(q.Resource.As("Resource").Code.Eq(*req.ProductCode))
|
||||
@@ -72,6 +69,7 @@ func PageBillByAdmin(c *fiber.Ctx) error {
|
||||
q.Trade.As("Trade").InnerNo.As("Trade__inner_no"),
|
||||
q.Trade.As("Trade").Acquirer.As("Trade__acquirer"),
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.Bill.CreatedAt.Desc()).
|
||||
@@ -127,12 +125,10 @@ func PageBillOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Bill.BillNo.Eq(*req.BillNo))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Bill.CreatedAt.Gte(time))
|
||||
do = do.Where(q.Bill.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateHead(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Bill.CreatedAt.Lte(time))
|
||||
do = do.Where(q.Bill.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
if req.ProductCode != nil {
|
||||
do = do.Where(q.Resource.As("Resource").Code.Eq(*req.ProductCode))
|
||||
@@ -156,6 +152,7 @@ func PageBillOfUserByAdmin(c *fiber.Ctx) error {
|
||||
q.Trade.As("Trade").InnerNo.As("Trade__inner_no"),
|
||||
q.Trade.As("Trade").Acquirer.As("Trade__acquirer"),
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.Bill.CreatedAt.Desc()).
|
||||
@@ -207,10 +204,10 @@ func ListBill(c *fiber.Ctx) error {
|
||||
do.Where(q.Bill.Type.Eq(int(*req.Type)))
|
||||
}
|
||||
if req.CreateAfter != nil {
|
||||
do.Where(q.Bill.CreatedAt.Gte(*req.CreateAfter))
|
||||
do = do.Where(q.Bill.CreatedAt.Gte(req.CreateAfter.UTC()))
|
||||
}
|
||||
if req.CreateBefore != nil {
|
||||
do.Where(q.Bill.CreatedAt.Lte(*req.CreateBefore))
|
||||
do = do.Where(q.Bill.CreatedAt.Lte(req.CreateBefore.UTC()))
|
||||
}
|
||||
if req.BillNo != nil && *req.BillNo != "" {
|
||||
do.Where(q.Bill.BillNo.Eq(*req.BillNo))
|
||||
|
||||
@@ -15,90 +15,6 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// PageChannelByAdmin 分页查询所有通道
|
||||
func PageChannelByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelRead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
var req PageChannelsByAdminReq
|
||||
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
do := q.Channel.Where()
|
||||
if req.UserPhone != nil {
|
||||
do = do.Where(q.User.As("User").Phone.Eq(*req.UserPhone))
|
||||
}
|
||||
if req.ResourceNo != nil {
|
||||
do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
|
||||
}
|
||||
if req.BatchNo != nil {
|
||||
do = do.Where(q.Channel.BatchNo.Eq(*req.BatchNo))
|
||||
}
|
||||
if req.ProxyHost != nil {
|
||||
do = do.Where(q.Channel.Host.Eq(*req.ProxyHost))
|
||||
}
|
||||
if req.ProxyPort != nil {
|
||||
do = do.Where(q.Channel.Port.Eq(*req.ProxyPort))
|
||||
}
|
||||
if req.NodeIP != nil {
|
||||
ip, err := orm.ParseInet(*req.NodeIP)
|
||||
if err != nil {
|
||||
return core.NewBizErr("查询参数 ip 格式不正确")
|
||||
}
|
||||
do = do.Where(q.Channel.IP.Eq(ip))
|
||||
}
|
||||
if req.ExpiredAtStart != nil {
|
||||
time := u.DateHead(*req.ExpiredAtStart)
|
||||
do = do.Where(q.Channel.ExpiredAt.Gte(time))
|
||||
}
|
||||
if req.ExpiredAtEnd != nil {
|
||||
time := u.DateHead(*req.ExpiredAtEnd)
|
||||
do = do.Where(q.Channel.ExpiredAt.Lte(time))
|
||||
}
|
||||
|
||||
// 查询通道列表
|
||||
list, total, err := q.Channel.
|
||||
Joins(q.Channel.User, q.Channel.Resource).
|
||||
Select(
|
||||
q.Channel.ALL,
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.User.As("User").Phone.As("User__phone"),
|
||||
q.User.As("User").Name.As("User__name"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.Channel.CreatedAt.Desc()).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
type PageChannelsByAdminReq struct {
|
||||
core.PageReq
|
||||
UserPhone *string `json:"user_phone"`
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
BatchNo *string `json:"batch_no"`
|
||||
ProxyHost *string `json:"proxy_host"`
|
||||
ProxyPort *uint16 `json:"proxy_port"`
|
||||
NodeIP *string `json:"node_ip" validator:"omitempty,ip"`
|
||||
ExpiredAtStart *time.Time `json:"expired_at_start"`
|
||||
ExpiredAtEnd *time.Time `json:"expired_at_end"`
|
||||
}
|
||||
|
||||
// ListChannel 分页查询当前用户通道
|
||||
func ListChannel(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
@@ -126,10 +42,10 @@ func ListChannel(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if req.ExpireAfter != nil {
|
||||
cond.Where(q.Channel.ExpiredAt.Gte(*req.ExpireAfter))
|
||||
cond = cond.Where(q.Channel.ExpiredAt.Gte(req.ExpireAfter.UTC()))
|
||||
}
|
||||
if req.ExpireBefore != nil {
|
||||
cond.Where(q.Channel.ExpiredAt.Lte(*req.ExpireBefore))
|
||||
cond = cond.Where(q.Channel.ExpiredAt.Lte(req.ExpireBefore.UTC()))
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
@@ -186,13 +102,17 @@ func CreateChannel(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 创建通道
|
||||
no, err := s.FindResourceNoById(req.ResourceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isp *m.EdgeISP
|
||||
if req.Isp != nil {
|
||||
isp = u.X(m.ToEdgeISP(*req.Isp))
|
||||
}
|
||||
result, err := s.Channel.CreateChannels(
|
||||
ip,
|
||||
req.ResourceId,
|
||||
ip, no,
|
||||
req.AuthType == s.ChannelAuthTypeIp,
|
||||
req.AuthType == s.ChannelAuthTypePass,
|
||||
req.Count,
|
||||
@@ -233,6 +153,69 @@ type CreateChannelReq struct {
|
||||
Isp *int `json:"isp"`
|
||||
}
|
||||
|
||||
// CreateChannelV2 创建新通道 v2,使用 resource_no 替代 resource_id
|
||||
func CreateChannelV2(c *fiber.Ctx) error {
|
||||
// 不检查权限,允许 api 调用
|
||||
|
||||
// 解析参数
|
||||
req := new(CreateChannelReqV2)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return core.NewBizErr("解析参数失败", err)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(c.IP())
|
||||
if err != nil {
|
||||
return core.NewBizErr("获取客户端地址失败", err)
|
||||
}
|
||||
|
||||
// 创建通道
|
||||
var isp *m.EdgeISP
|
||||
if req.Isp != nil {
|
||||
isp = u.X(m.ToEdgeISP(*req.Isp))
|
||||
}
|
||||
result, err := s.Channel.CreateChannels(
|
||||
ip,
|
||||
req.ResourceNo,
|
||||
req.AuthType == s.ChannelAuthTypeIp,
|
||||
req.AuthType == s.ChannelAuthTypePass,
|
||||
req.Count,
|
||||
&s.EdgeFilter{
|
||||
Isp: isp,
|
||||
Prov: req.Prov,
|
||||
City: req.City,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
var resp = make([]*CreateChannelRespItem, len(result))
|
||||
for i, channel := range result {
|
||||
resp[i] = &CreateChannelRespItem{
|
||||
Proto: req.Protocol,
|
||||
Host: channel.Host,
|
||||
IP: channel.Proxy.IP.String(),
|
||||
Port: channel.Port,
|
||||
}
|
||||
if req.AuthType == s.ChannelAuthTypePass {
|
||||
resp[i].Username = channel.Username
|
||||
resp[i].Password = channel.Password
|
||||
}
|
||||
}
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
type CreateChannelReqV2 struct {
|
||||
ResourceNo string `json:"resource_no" validate:"required"`
|
||||
AuthType s.ChannelAuthType `json:"auth_type" validate:"required"`
|
||||
Protocol int `json:"protocol" validate:"required"`
|
||||
Count int `json:"count" validate:"required"`
|
||||
Prov *string `json:"prov"`
|
||||
City *string `json:"city"`
|
||||
Isp *int `json:"isp"`
|
||||
}
|
||||
|
||||
type CreateChannelRespItem struct {
|
||||
Proto int `json:"-"`
|
||||
Host string `json:"host"`
|
||||
@@ -269,6 +252,97 @@ type RemoveChannelsReq struct {
|
||||
Batch string `json:"batch" validate:"required"`
|
||||
}
|
||||
|
||||
// PageChannelByAdmin 分页查询所有通道
|
||||
func PageChannelByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelRead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
var req PageChannelsByAdminReq
|
||||
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
do := q.Channel.Where()
|
||||
if req.UserPhone != nil {
|
||||
do = do.Where(q.User.As("User").Phone.Eq(*req.UserPhone))
|
||||
}
|
||||
if req.ResourceNo != nil {
|
||||
do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
|
||||
}
|
||||
if req.BatchNo != nil {
|
||||
do = do.Where(q.Channel.BatchNo.Eq(*req.BatchNo))
|
||||
}
|
||||
if req.ProxyHost != nil {
|
||||
do = do.Where(q.Channel.Host.Eq(*req.ProxyHost))
|
||||
}
|
||||
if req.ProxyPort != nil {
|
||||
do = do.Where(q.Channel.Port.Eq(*req.ProxyPort))
|
||||
}
|
||||
if req.NodeIP != nil {
|
||||
ip, err := orm.ParseInet(*req.NodeIP)
|
||||
if err != nil {
|
||||
return core.NewBizErr("查询参数 ip 格式不正确")
|
||||
}
|
||||
do = do.Where(q.Channel.IP.Eq(ip))
|
||||
}
|
||||
if req.ExpiredAtStart != nil {
|
||||
do = do.Where(q.Channel.ExpiredAt.Gte(req.ExpiredAtStart.UTC()))
|
||||
}
|
||||
if req.ExpiredAtEnd != nil {
|
||||
do = do.Where(q.Channel.ExpiredAt.Lte(req.ExpiredAtEnd.UTC()))
|
||||
}
|
||||
if req.Expired != nil {
|
||||
if *req.Expired {
|
||||
do = do.Where(q.Channel.ExpiredAt.Lte(time.Now().UTC()))
|
||||
} else {
|
||||
do = do.Where(q.Channel.ExpiredAt.Gt(time.Now().UTC()))
|
||||
}
|
||||
}
|
||||
|
||||
// 查询通道列表
|
||||
list, total, err := q.Channel.
|
||||
Joins(q.Channel.User, q.Channel.Resource).
|
||||
Select(
|
||||
q.Channel.ALL,
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
q.User.As("User").Phone.As("User__phone"),
|
||||
q.User.As("User").Name.As("User__name"),
|
||||
).
|
||||
Where(do).
|
||||
Order(q.Channel.CreatedAt.Desc()).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
type PageChannelsByAdminReq struct {
|
||||
core.PageReq
|
||||
UserPhone *string `json:"user_phone"`
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
BatchNo *string `json:"batch_no"`
|
||||
ProxyHost *string `json:"proxy_host"`
|
||||
ProxyPort *uint16 `json:"proxy_port"`
|
||||
NodeIP *string `json:"node_ip" validator:"omitempty,ip"`
|
||||
ExpiredAtStart *time.Time `json:"expired_at_start"`
|
||||
ExpiredAtEnd *time.Time `json:"expired_at_end"`
|
||||
Expired *bool `json:"expired"`
|
||||
}
|
||||
|
||||
// PageChannelOfUserByAdmin 分页查询指定用户的通道
|
||||
func PageChannelOfUserByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
@@ -298,12 +372,10 @@ func PageChannelOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Channel.Port.Eq(*req.ProxyPort))
|
||||
}
|
||||
if req.ExpiredAtStart != nil {
|
||||
t := u.DateHead(*req.ExpiredAtStart)
|
||||
do = do.Where(q.Channel.ExpiredAt.Gte(t))
|
||||
do = do.Where(q.Channel.ExpiredAt.Gte(req.ExpiredAtStart.UTC()))
|
||||
}
|
||||
if req.ExpiredAtEnd != nil {
|
||||
t := u.DateHead(*req.ExpiredAtEnd)
|
||||
do = do.Where(q.Channel.ExpiredAt.Lte(t))
|
||||
do = do.Where(q.Channel.ExpiredAt.Lte(req.ExpiredAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询通道列表
|
||||
@@ -312,6 +384,7 @@ func PageChannelOfUserByAdmin(c *fiber.Ctx) error {
|
||||
Select(
|
||||
q.Channel.ALL,
|
||||
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
|
||||
q.Resource.As("Resource").Type.As("Resource__type"),
|
||||
q.User.As("User").Phone.As("User__phone"),
|
||||
q.User.As("User").Name.As("User__name"),
|
||||
).
|
||||
|
||||
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
@@ -276,28 +275,28 @@ func couponUserPageConditions(req CouponUserPageFilter) []gen.Condition {
|
||||
}
|
||||
if req.Expired != nil {
|
||||
if *req.Expired {
|
||||
conds = append(conds, q.CouponUser.ExpireAt.IsNotNull(), q.CouponUser.ExpireAt.Lte(time.Now()))
|
||||
conds = append(conds, q.CouponUser.ExpireAt.IsNotNull(), q.CouponUser.ExpireAt.Lte(time.Now().UTC()))
|
||||
} else {
|
||||
conds = append(conds, q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now())))
|
||||
conds = append(conds, q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now().UTC())))
|
||||
}
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
conds = append(conds, q.CouponUser.CreatedAt.Gte(u.DateHead(*req.CreatedAtStart)))
|
||||
conds = append(conds, q.CouponUser.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
conds = append(conds, q.CouponUser.CreatedAt.Lte(u.DateTail(*req.CreatedAtEnd)))
|
||||
conds = append(conds, q.CouponUser.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
if req.ExpireAtStart != nil {
|
||||
conds = append(conds, q.CouponUser.ExpireAt.Gte(u.DateHead(*req.ExpireAtStart)))
|
||||
conds = append(conds, q.CouponUser.ExpireAt.Gte(req.ExpireAtStart.UTC()))
|
||||
}
|
||||
if req.ExpireAtEnd != nil {
|
||||
conds = append(conds, q.CouponUser.ExpireAt.Lte(u.DateTail(*req.ExpireAtEnd)))
|
||||
conds = append(conds, q.CouponUser.ExpireAt.Lte(req.ExpireAtEnd.UTC()))
|
||||
}
|
||||
if req.UsedAtStart != nil {
|
||||
conds = append(conds, q.CouponUser.UsedAt.Gte(u.DateHead(*req.UsedAtStart)))
|
||||
conds = append(conds, q.CouponUser.UsedAt.Gte(req.UsedAtStart.UTC()))
|
||||
}
|
||||
if req.UsedAtEnd != nil {
|
||||
conds = append(conds, q.CouponUser.UsedAt.Lte(u.DateTail(*req.UsedAtEnd)))
|
||||
conds = append(conds, q.CouponUser.UsedAt.Lte(req.UsedAtEnd.UTC()))
|
||||
}
|
||||
return conds
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func IdentifyCallbackNew(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 更新用户实名认证状态
|
||||
_, err = q.User.
|
||||
r, err := q.User.
|
||||
Where(q.User.ID.Eq(info.Uid)).
|
||||
UpdateSimple(
|
||||
q.User.IDType.Value(info.Type),
|
||||
@@ -153,6 +153,9 @@ func IdentifyCallbackNew(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return renderIdenResult(c, false, "保存实名认证信息失败,请联系客服处理")
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return renderIdenResult(c, false, "用户状态已失效")
|
||||
}
|
||||
|
||||
// 返回结果页面
|
||||
return renderIdenResult(c, true, "实名认证成功,请在扫码页面点击按钮完成认证")
|
||||
@@ -172,7 +175,7 @@ func DebugIdentifyClear(c *fiber.Ctx) error {
|
||||
return core.NewServErr("需要提供手机号")
|
||||
}
|
||||
|
||||
_, err := q.User.
|
||||
r, err := q.User.
|
||||
Where(
|
||||
q.User.Phone.Eq(phone),
|
||||
).
|
||||
@@ -184,6 +187,9 @@ func DebugIdentifyClear(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return core.NewServErr("清除实名认证失败")
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("用户状态已失效")
|
||||
}
|
||||
|
||||
return c.SendString("实名信息已清除")
|
||||
}
|
||||
|
||||
@@ -44,26 +44,26 @@ func PageResourceShort(c *fiber.Ctx) error {
|
||||
do.Where(q.ResourceShort.As(q.Resource.Short.Name()).Type.Eq(*req.Type))
|
||||
}
|
||||
if req.CreateAfter != nil {
|
||||
do.Where(q.Resource.CreatedAt.Gte(*req.CreateAfter))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreateAfter.UTC()))
|
||||
}
|
||||
if req.CreateBefore != nil {
|
||||
do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreateBefore.UTC()))
|
||||
}
|
||||
if req.ExpireAfter != nil {
|
||||
do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Gte(*req.ExpireAfter))
|
||||
do = do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Gte(req.ExpireAfter.UTC()))
|
||||
}
|
||||
if req.ExpireBefore != nil {
|
||||
do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Lte(*req.ExpireBefore))
|
||||
do = do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Lte(req.ExpireBefore.UTC()))
|
||||
}
|
||||
if req.Status != nil {
|
||||
var short = q.ResourceShort.As(q.Resource.Short.Name())
|
||||
switch *req.Status {
|
||||
case 1:
|
||||
var timeCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeTime)), short.ExpireAt.Gte(time.Now()))
|
||||
var timeCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeTime)), short.ExpireAt.Gte(time.Now().UTC()))
|
||||
var quotaCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeQuota)), short.Quota.GtCol(short.Used))
|
||||
do.Where(q.Resource.Where(timeCond).Or(quotaCond))
|
||||
case 2:
|
||||
var timeCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeTime)), short.ExpireAt.Lte(time.Now()))
|
||||
var timeCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeTime)), short.ExpireAt.Lte(time.Now().UTC()))
|
||||
var quotaCond = q.Resource.Where(short.Type.Eq(int(m.ResourceModeQuota)), short.Quota.LteCol(short.Used))
|
||||
do.Where(q.Resource.Where(timeCond).Or(quotaCond))
|
||||
}
|
||||
@@ -141,26 +141,26 @@ func PageResourceLong(c *fiber.Ctx) error {
|
||||
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Type.Eq(int(*req.Type)))
|
||||
}
|
||||
if req.CreateAfter != nil {
|
||||
do.Where(q.Resource.CreatedAt.Gte(*req.CreateAfter))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreateAfter.UTC()))
|
||||
}
|
||||
if req.CreateBefore != nil {
|
||||
do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreateBefore.UTC()))
|
||||
}
|
||||
if req.ExpireAfter != nil {
|
||||
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Gte(*req.ExpireAfter))
|
||||
do = do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Gte(req.ExpireAfter.UTC()))
|
||||
}
|
||||
if req.ExpireBefore != nil {
|
||||
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Lte(*req.ExpireBefore))
|
||||
do = do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Lte(req.ExpireBefore.UTC()))
|
||||
}
|
||||
if req.Status != nil {
|
||||
var long = q.ResourceLong.As(q.Resource.Long.Name())
|
||||
switch *req.Status {
|
||||
case 1:
|
||||
var timeCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeTime)), long.ExpireAt.Gte(time.Now()))
|
||||
var timeCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeTime)), long.ExpireAt.Gte(time.Now().UTC()))
|
||||
var quotaCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeQuota)), long.Quota.GtCol(long.Used))
|
||||
do.Where(q.Resource.Where(timeCond).Or(quotaCond))
|
||||
case 2:
|
||||
var timeCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeTime)), long.ExpireAt.Lte(time.Now()))
|
||||
var timeCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeTime)), long.ExpireAt.Lte(time.Now().UTC()))
|
||||
var quotaCond = q.Resource.Where(long.Type.Eq(int(m.ResourceModeQuota)), long.Quota.LteCol(long.Used))
|
||||
do.Where(q.Resource.Where(timeCond).Or(quotaCond))
|
||||
}
|
||||
@@ -235,18 +235,16 @@ func PageResourceShortByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.ResourceShort.As("Short").Type.Eq(int(*req.Mode)))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(time))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(time))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
if req.Expired != nil {
|
||||
if *req.Expired {
|
||||
do = do.Where(q.Resource.Where(
|
||||
q.ResourceShort.As("Short").Type.Eq(int(m.ResourceModeTime)),
|
||||
q.ResourceShort.As("Short").ExpireAt.Lte(time.Now()),
|
||||
q.ResourceShort.As("Short").ExpireAt.Lte(time.Now().UTC()),
|
||||
).Or(
|
||||
q.ResourceShort.As("Short").Type.Eq(int(m.ResourceModeQuota)),
|
||||
q.ResourceShort.As("Short").Quota.LteCol(q.ResourceShort.As("Short").Used),
|
||||
@@ -254,7 +252,7 @@ func PageResourceShortByAdmin(c *fiber.Ctx) error {
|
||||
} else {
|
||||
do = do.Where(q.Resource.Where(
|
||||
q.ResourceShort.As("Short").Type.Eq(int(m.ResourceModeTime)),
|
||||
q.ResourceShort.As("Short").ExpireAt.Gt(time.Now()),
|
||||
q.ResourceShort.As("Short").ExpireAt.Gt(time.Now().UTC()),
|
||||
).Or(
|
||||
q.ResourceShort.As("Short").Type.Eq(int(m.ResourceModeQuota)),
|
||||
q.ResourceShort.As("Short").Quota.GtCol(q.ResourceShort.As("Short").Used),
|
||||
@@ -329,16 +327,16 @@ func PageResourceLongByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.ResourceLong.As("Long").Type.Eq(*req.Mode))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(*req.CreatedAtStart))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(*req.CreatedAtEnd))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
if req.Expired != nil {
|
||||
if *req.Expired {
|
||||
do = do.Where(q.Resource.Where(
|
||||
q.ResourceLong.As("Long").Type.Eq(int(m.ResourceModeTime)),
|
||||
q.ResourceLong.As("Long").ExpireAt.Lte(time.Now()),
|
||||
q.ResourceLong.As("Long").ExpireAt.Lte(time.Now().UTC()),
|
||||
).Or(
|
||||
q.ResourceLong.As("Long").Type.Eq(int(m.ResourceModeQuota)),
|
||||
q.ResourceLong.As("Long").Quota.LteCol(q.ResourceLong.As("Long").Used),
|
||||
@@ -346,7 +344,7 @@ func PageResourceLongByAdmin(c *fiber.Ctx) error {
|
||||
} else {
|
||||
do = do.Where(q.Resource.Where(
|
||||
q.ResourceLong.As("Long").Type.Eq(int(m.ResourceModeTime)),
|
||||
q.ResourceLong.As("Long").ExpireAt.Gt(time.Now()),
|
||||
q.ResourceLong.As("Long").ExpireAt.Gt(time.Now().UTC()),
|
||||
).Or(
|
||||
q.ResourceLong.As("Long").Type.Eq(int(m.ResourceModeQuota)),
|
||||
q.ResourceLong.As("Long").Quota.GtCol(q.ResourceLong.As("Long").Used),
|
||||
@@ -418,15 +416,13 @@ func PageResourceShortOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.ResourceShort.As("Short").Type.Eq(int(*req.Mode)))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(t))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(t))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
list, total, err := q.Resource.
|
||||
list, total, err := q.Resource.Debug().
|
||||
Joins(q.Resource.User, q.Resource.Short, q.Resource.Short.Sku).
|
||||
Select(
|
||||
q.Resource.ALL,
|
||||
@@ -489,12 +485,10 @@ func PageResourceLongOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.ResourceLong.As("Long").Type.Eq(*req.Mode))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
t := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(t))
|
||||
do = do.Where(q.Resource.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
t := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(t))
|
||||
do = do.Where(q.Resource.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
list, total, err := q.Resource.
|
||||
@@ -554,6 +548,8 @@ func AllActiveResource(c *fiber.Ctx) error {
|
||||
Joins(
|
||||
q.Resource.Short,
|
||||
q.Resource.Long,
|
||||
q.Resource.Short.Sku,
|
||||
q.Resource.Long.Sku,
|
||||
).
|
||||
Where(
|
||||
q.Resource.UserID.Eq(authCtx.User.ID),
|
||||
@@ -562,9 +558,9 @@ func AllActiveResource(c *fiber.Ctx) error {
|
||||
q.Resource.Type.Eq(int(m.ResourceTypeShort)),
|
||||
q.ResourceShort.As(q.Resource.Short.Name()).Where(
|
||||
short.Type.Eq(int(m.ResourceModeTime)),
|
||||
short.ExpireAt.Gte(now),
|
||||
short.ExpireAt.Gte(now.UTC()),
|
||||
q.ResourceShort.As(q.Resource.Short.Name()).
|
||||
Where(short.LastAt.Lt(u.Today())).
|
||||
Where(short.LastAt.Lt(u.Today().UTC())).
|
||||
Or(short.Quota.GtCol(short.Daily)),
|
||||
).Or(
|
||||
short.Type.Eq(int(m.ResourceModeQuota)),
|
||||
@@ -574,9 +570,9 @@ func AllActiveResource(c *fiber.Ctx) error {
|
||||
q.Resource.Type.Eq(int(m.ResourceTypeLong)),
|
||||
q.ResourceLong.As(q.Resource.Long.Name()).Where(
|
||||
long.Type.Eq(int(m.ResourceModeTime)),
|
||||
long.ExpireAt.Gte(now),
|
||||
long.ExpireAt.Gte(now.UTC()),
|
||||
q.ResourceLong.As(q.Resource.Long.Name()).
|
||||
Where(long.LastAt.Lt(u.Today())).
|
||||
Where(long.LastAt.Lt(u.Today().UTC())).
|
||||
Or(long.Quota.GtCol(long.Daily)),
|
||||
).Or(
|
||||
long.Type.Eq(int(m.ResourceModeQuota)),
|
||||
@@ -590,6 +586,15 @@ func AllActiveResource(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
switch resource.Type {
|
||||
case m.ResourceTypeShort:
|
||||
resource.Short.Sku = &m.ProductSku{Name: resource.Short.Sku.Name}
|
||||
case m.ResourceTypeLong:
|
||||
resource.Long.Sku = &m.ProductSku{Name: resource.Long.Sku.Name}
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(resources)
|
||||
}
|
||||
|
||||
@@ -755,10 +760,10 @@ func StatisticResourceUsage(c *fiber.Ctx) error {
|
||||
)
|
||||
|
||||
if req.TimeAfter != nil {
|
||||
do.Where(q.LogsUserUsage.Time.Gte(*req.TimeAfter))
|
||||
do = do.Where(q.LogsUserUsage.Time.Gte(req.TimeAfter.UTC()))
|
||||
}
|
||||
if req.TimeBefore != nil {
|
||||
do.Where(q.LogsUserUsage.Time.Lte(*req.TimeBefore))
|
||||
do = do.Where(q.LogsUserUsage.Time.Lte(req.TimeBefore.UTC()))
|
||||
}
|
||||
|
||||
var data = new(StatisticResourceUsageResp)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
@@ -53,12 +52,10 @@ func PageTradeByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Trade.Status.Eq(*req.Status))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Trade.CreatedAt.Gte(time))
|
||||
do = do.Where(q.Trade.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Trade.CreatedAt.Lte(time))
|
||||
do = do.Where(q.Trade.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
@@ -129,12 +126,10 @@ func PageTradeOfUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.Trade.Status.Eq(*req.Status))
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
time := u.DateHead(*req.CreatedAtStart)
|
||||
do = do.Where(q.Trade.CreatedAt.Gte(time))
|
||||
do = do.Where(q.Trade.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
time := u.DateTail(*req.CreatedAtEnd)
|
||||
do = do.Where(q.Trade.CreatedAt.Lte(time))
|
||||
do = do.Where(q.Trade.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询订单列表
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -65,6 +66,12 @@ func PageUserByAdmin(c *fiber.Ctx) error {
|
||||
do = do.Where(q.User.AdminID.IsNull())
|
||||
}
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
do = do.Where(q.User.CreatedAt.Gte(req.CreatedAtStart.UTC()))
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
do = do.Where(q.User.CreatedAt.Lte(req.CreatedAtEnd.UTC()))
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
users, total, err := q.User.
|
||||
@@ -107,6 +114,8 @@ type PageUserByAdminReq struct {
|
||||
Identified *bool `json:"identified,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Assigned *bool `json:"assigned,omitempty"`
|
||||
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
|
||||
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
|
||||
}
|
||||
|
||||
// 管理员获取单个用户
|
||||
@@ -274,7 +283,7 @@ func BindAdmin(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
result, err := q.User.Where(
|
||||
r, err := q.User.Where(
|
||||
q.User.ID.Eq(int32(req.UserID)),
|
||||
q.User.AdminID.IsNull(),
|
||||
).UpdateColumnSimple(
|
||||
@@ -283,7 +292,7 @@ func BindAdmin(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户已绑定管理员")
|
||||
}
|
||||
|
||||
@@ -323,7 +332,7 @@ func UpdateUser(c *fiber.Ctx) error {
|
||||
if req.ContactWechat != nil {
|
||||
do = append(do, q.User.ContactWechat.Value(*req.ContactWechat))
|
||||
}
|
||||
_, err = q.User.
|
||||
r, err := q.User.
|
||||
Where(q.User.ID.Eq(authCtx.User.ID)).
|
||||
UpdateSimple(do...)
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
@@ -332,6 +341,9 @@ func UpdateUser(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户状态已过期")
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
@@ -359,7 +371,7 @@ func UpdateAccount(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
_, err = q.User.
|
||||
r, err := q.User.
|
||||
Where(q.User.ID.Eq(authCtx.User.ID)).
|
||||
Updates(m.User{
|
||||
Username: &req.Username,
|
||||
@@ -368,6 +380,9 @@ func UpdateAccount(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户状态已过期")
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
@@ -410,12 +425,15 @@ func UpdatePassword(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.User.
|
||||
r, err := q.User.
|
||||
Where(q.User.ID.Eq(authCtx.User.ID)).
|
||||
UpdateColumn(q.User.Password, newHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户状态已过期")
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
|
||||
@@ -156,7 +156,7 @@ func UpdateWhitelist(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 更新白名单
|
||||
_, err = q.Whitelist.
|
||||
r, err := q.Whitelist.
|
||||
Where(
|
||||
q.Whitelist.ID.Eq(req.ID),
|
||||
q.Whitelist.UserID.Eq(authCtx.User.ID),
|
||||
@@ -168,6 +168,9 @@ func UpdateWhitelist(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("白名单状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -201,7 +204,7 @@ func RemoveWhitelist(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 删除白名单
|
||||
_, err = q.Whitelist.
|
||||
r, err := q.Whitelist.
|
||||
Where(
|
||||
q.Whitelist.ID.In(ids...),
|
||||
q.Whitelist.UserID.Eq(authCtx.User.ID),
|
||||
@@ -212,6 +215,9 @@ func RemoveWhitelist(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("白名单状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"platform/pkg/env"
|
||||
auth2 "platform/web/auth"
|
||||
"platform/web/core"
|
||||
"platform/web/globals"
|
||||
"platform/web/handlers"
|
||||
"time"
|
||||
|
||||
@@ -28,7 +29,7 @@ func ApplyRouters(app *fiber.App) {
|
||||
debug.Get("/sms/:phone", handlers.DebugGetSmsCode)
|
||||
debug.Get("/iden/clear/:phone", handlers.DebugIdentifyClear)
|
||||
debug.Get("/session/now", func(ctx *fiber.Ctx) error {
|
||||
rs, err := q.Session.Where(q.Session.AccessTokenExpires.Gt(time.Now())).Find()
|
||||
rs, err := q.Session.Where(q.Session.AccessTokenExpires.Gt(time.Now().UTC())).Find()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -37,6 +38,17 @@ func ApplyRouters(app *fiber.App) {
|
||||
debug.Get("/test/err", func(ctx *fiber.Ctx) error {
|
||||
return core.NewBizErr("测试错误")
|
||||
})
|
||||
|
||||
debug.Get("/trade/status/:trade_no", func(ctx *fiber.Ctx) error {
|
||||
tradeNo := ctx.Params("trade_no")
|
||||
resp, err := globals.SFTPay.QueryTrade(&globals.QueryTradeReq{
|
||||
MchOrderNo: &tradeNo,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.JSON(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +95,7 @@ func userRouter(api fiber.Router) {
|
||||
channel := api.Group("/channel")
|
||||
channel.Post("/list", handlers.ListChannel)
|
||||
channel.Post("/create", handlers.CreateChannel)
|
||||
channel.Post("/create/v2", handlers.CreateChannelV2)
|
||||
|
||||
// 交易
|
||||
trade := api.Group("/trade")
|
||||
|
||||
@@ -110,7 +110,7 @@ func (s *adminService) Update(update *UpdateAdmin) error {
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
// 更新管理员基本信息
|
||||
if len(simples) > 0 {
|
||||
_, err := q.Admin.
|
||||
r, err := q.Admin.
|
||||
Where(
|
||||
q.Admin.ID.Eq(update.Id),
|
||||
q.Admin.Lock.Is(false),
|
||||
@@ -119,6 +119,9 @@ func (s *adminService) Update(update *UpdateAdmin) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("管理员状态已过期")
|
||||
}
|
||||
}
|
||||
|
||||
// 更新角色关联
|
||||
@@ -157,11 +160,17 @@ type UpdateAdmin struct {
|
||||
}
|
||||
|
||||
func (s *adminService) Remove(id int32) error {
|
||||
_, err := q.Admin.
|
||||
r, err := q.Admin.
|
||||
Where(
|
||||
q.Admin.ID.Eq(id),
|
||||
q.Admin.Lock.Is(false),
|
||||
).
|
||||
UpdateColumn(q.Admin.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("管理员状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,8 +137,14 @@ type UpdateAdminRole struct {
|
||||
}
|
||||
|
||||
func (r *adminRoleService) RemoveAdminRole(id int32) error {
|
||||
_, err := q.AdminRole.Where(q.AdminRole.ID.Eq(id)).UpdateColumn(q.AdminRole.DeletedAt, time.Now())
|
||||
rs, err := q.AdminRole.Where(q.AdminRole.ID.Eq(id)).UpdateColumn(q.AdminRole.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.RowsAffected == 0 {
|
||||
return core.NewBizErr("管理员角色状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var AdminRoleModifyLock = "platform:admin_role_permissions:modify"
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"net/netip"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
@@ -24,7 +25,7 @@ var Channel = &channelServer{
|
||||
}
|
||||
|
||||
type ChannelServiceProvider interface {
|
||||
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
|
||||
CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
|
||||
RemoveChannels(batch string) error
|
||||
ClearExpiredChannels(proxyId int32) (int, error)
|
||||
}
|
||||
@@ -33,8 +34,8 @@ type channelServer struct {
|
||||
provider ChannelServiceProvider
|
||||
}
|
||||
|
||||
func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
|
||||
return s.provider.CreateChannels(source, resourceId, authWhitelist, authPassword, count, edgeFilter)
|
||||
func (s *channelServer) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
|
||||
return s.provider.CreateChannels(source, resourceNo, authWhitelist, authPassword, count, edgeFilter)
|
||||
}
|
||||
|
||||
func (s *channelServer) RemoveChannels(batch string) error {
|
||||
@@ -45,6 +46,48 @@ func (s *channelServer) ClearExpiredChannels(proxyId int32) (int, error) {
|
||||
return s.provider.ClearExpiredChannels(proxyId)
|
||||
}
|
||||
|
||||
func (s *channelServer) RefreshEdges() error {
|
||||
if env.RunMode != env.RunModeProd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 找到所有网关
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询网关失败: %w", err)
|
||||
}
|
||||
|
||||
for _, proxy := range proxies {
|
||||
gateway, err := proxyGateway(proxy)
|
||||
if err != nil {
|
||||
return core.NewServErr("创建代理网关失败", err)
|
||||
}
|
||||
|
||||
// 选取随机节点
|
||||
edges, err := gateway.GatewayEdge(&g.GatewayEdgeReq{
|
||||
Assigned: u.P(false),
|
||||
Limit: u.P(1000),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取边缘节点失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交断开配置
|
||||
edgeIds := make([]string, 0, len(edges))
|
||||
for id, _ := range edges {
|
||||
edgeIds = append(edgeIds, id)
|
||||
}
|
||||
g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
|
||||
Uuid: proxy.Mac,
|
||||
Edge: &edgeIds,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 授权方式
|
||||
type ChannelAuthType int
|
||||
|
||||
@@ -72,12 +115,23 @@ func genPassPair() (string, string) {
|
||||
return string(username), string(password)
|
||||
}
|
||||
|
||||
func FindResourceNoById(resourceId int32) (string, error) {
|
||||
resource, err := q.Resource.
|
||||
Select(q.Resource.ResourceNo).
|
||||
Where(q.Resource.ID.Eq(resourceId)).
|
||||
Take()
|
||||
if err != nil {
|
||||
return "", ErrResourceNotExist
|
||||
}
|
||||
return u.Z(resource.ResourceNo), nil
|
||||
}
|
||||
|
||||
// 查找资源
|
||||
func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
|
||||
func findResourceViewByNo(resourceNo string, now time.Time) (*ResourceView, error) {
|
||||
resource, err := q.Resource.
|
||||
Preload(field.Associations).
|
||||
Where(
|
||||
q.Resource.ID.Eq(resourceId),
|
||||
q.Resource.ResourceNo.Eq(resourceNo),
|
||||
q.Resource.Active.Is(true),
|
||||
).
|
||||
Take()
|
||||
@@ -88,7 +142,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
|
||||
return nil, ErrResourceNotExist
|
||||
}
|
||||
var info = &ResourceView{
|
||||
Id: resource.ID,
|
||||
ID: resource.ID,
|
||||
User: *resource.User,
|
||||
Active: resource.Active,
|
||||
Type: resource.Type,
|
||||
@@ -114,7 +168,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
|
||||
var sub = resource.Long
|
||||
info.LongId = &sub.ID
|
||||
info.ExpireAt = sub.ExpireAt
|
||||
info.Live = time.Duration(sub.Live) * time.Hour
|
||||
info.Live = time.Duration(sub.Live) * time.Minute
|
||||
info.Mode = sub.Type
|
||||
info.Quota = sub.Quota
|
||||
info.Used = sub.Used
|
||||
@@ -134,7 +188,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
|
||||
|
||||
// ResourceView 套餐数据的简化视图,便于直接获取主要数据
|
||||
type ResourceView struct {
|
||||
Id int32
|
||||
ID int32
|
||||
User m.User
|
||||
Active bool
|
||||
Type m.ResourceType
|
||||
@@ -152,13 +206,13 @@ type ResourceView struct {
|
||||
}
|
||||
|
||||
// 检查用户是否可提取
|
||||
func ensure(now time.Time, source netip.Addr, resourceId int32, authWhitelist bool, count int) (*ResourceView, []string, error) {
|
||||
func ensure(now time.Time, source netip.Addr, resourceNo string, authWhitelist bool, count int) (*ResourceView, []string, error) {
|
||||
if count > 400 {
|
||||
return nil, nil, core.NewBizErr("单次最多提取 400 个")
|
||||
}
|
||||
|
||||
// 获取用户套餐
|
||||
resource, err := findResource(resourceId, now)
|
||||
resource, err := findResourceViewByNo(resourceNo, now)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -24,18 +24,21 @@ import (
|
||||
|
||||
type channelBaiyinProvider struct{}
|
||||
|
||||
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
|
||||
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
|
||||
if filter == nil {
|
||||
return nil, core.NewBizErr("缺少节点过滤条件")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
batchNo := ID.GenReadable("bat")
|
||||
channels := make([]*m.Channel, count)
|
||||
|
||||
// 资源锁,防止并发扣减失败导致的端口悬空问题
|
||||
err := g.Redsync.WithLock(lockChannelCreateKey(resourceNo), func() error {
|
||||
// 检查并获取套餐与白名单
|
||||
resource, whitelists, err := ensure(now, source, resourceId, authWhitelist, count)
|
||||
resource, whitelists, err := ensure(now, source, resourceNo, authWhitelist, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
user := resource.User
|
||||
@@ -44,20 +47,11 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
// 选择代理
|
||||
proxy, gateway, err := selectProxy(count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
// 取用端口
|
||||
chans, err := selectPorts(proxy.ID, batchNo, count, expire)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 节点查询到提交,需要锁定防止并发取用
|
||||
channels := make([]*m.Channel, count)
|
||||
err = g.Redsync.WithLock(lockChannelCreateKey(), func() error {
|
||||
// 取用节点
|
||||
edges, err := selectEdges(gateway, filter, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -67,17 +61,15 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
edgeConfigs := make([]string, 0, count)
|
||||
for i := range count {
|
||||
ch := chans[i]
|
||||
edge := edges[i]
|
||||
|
||||
// 通道数据
|
||||
channels[i] = &m.Channel{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
ResourceID: resource.ID,
|
||||
BatchNo: batchNo,
|
||||
ProxyID: proxy.ID,
|
||||
Host: u.Else(proxy.Host, proxy.IP.String()),
|
||||
Port: ch.Port(),
|
||||
EdgeRef: u.P(edge.EdgeID),
|
||||
FilterISP: filter.Isp,
|
||||
FilterProv: filter.Prov,
|
||||
FilterCity: filter.City,
|
||||
@@ -89,7 +81,12 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
chanConfigs[i] = &g.PortConfigsReq{
|
||||
Port: int(ch.Port()),
|
||||
Status: true,
|
||||
Edge: &[]string{edge.EdgeID},
|
||||
AutoEdgeConfig: &g.AutoEdgeConfig{
|
||||
Province: u.Z(filter.Prov),
|
||||
City: u.Z(filter.City),
|
||||
Isp: filter.Isp.String(),
|
||||
Count: u.P(1),
|
||||
},
|
||||
}
|
||||
|
||||
// 白名单模式
|
||||
@@ -105,27 +102,25 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
channels[i].Password = &password
|
||||
chanConfigs[i].Userpass = u.P(username + ":" + password)
|
||||
}
|
||||
|
||||
// 连接配置数据
|
||||
if edge.Type == EdgeInfoCloud {
|
||||
edgeConfigs = append(edgeConfigs, edge.EdgeID)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
slog.Debug("提交代理端口配置", "proxy", proxy.IP.String(), "total_count", len(chanConfigs), "remote_count", len(edgeConfigs))
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 连接节点到网关
|
||||
if err := g.Cloud.CloudConnect(&g.CloudConnectReq{Uuid: proxy.Mac, Edge: &edgeConfigs}); err != nil {
|
||||
return core.NewServErr("连接云平台失败", err)
|
||||
// 从云端补足节点
|
||||
err := ensureEdges(proxy, gateway, filter, count)
|
||||
if err != nil {
|
||||
slog.Warn("ensureEdges 失败", "err", err) // 不阻止通道创建,继续走后续流程
|
||||
}
|
||||
|
||||
// 启用网关代理通道
|
||||
if len(chanConfigs) > 0 {
|
||||
if err := gateway.GatewayPortConfigs(chanConfigs); err != nil {
|
||||
slog.Warn("提交代理端口配置失败", "error", err.Error())
|
||||
return core.NewServErr(fmt.Sprintf("配置代理 %s 端口失败", proxy.IP.String()), err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, item := range chanConfigs {
|
||||
str, _ := json.Marshal(item)
|
||||
@@ -166,13 +161,13 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
)
|
||||
|
||||
default:
|
||||
return core.NewBizErr("套餐类型不正确,无法更新", nil)
|
||||
return core.NewBizErr("套餐类型不正确,无法更新")
|
||||
}
|
||||
if err != nil {
|
||||
return core.NewServErr("更新套餐使用记录失败", err)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return core.NewBizErr("提取太频繁,请稍后再试", nil)
|
||||
return core.NewBizErr("套餐状态已过期")
|
||||
}
|
||||
|
||||
// 保存通道
|
||||
@@ -186,7 +181,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
// 保存提取记录
|
||||
err = q.LogsUserUsage.Create(&m.LogsUserUsage{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
ResourceID: resource.ID,
|
||||
BatchNo: batchNo,
|
||||
Count: int32(count),
|
||||
ISP: u.X(filter.Isp.String()),
|
||||
@@ -214,75 +209,61 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
|
||||
return g.Redsync.WithLock(lockChannelRemoveKey(batch), func() error {
|
||||
func (s *channelBaiyinProvider) RemoveChannels(batchNo string) error {
|
||||
return g.Redsync.WithLock(lockChannelRemoveKey(batchNo), func() error {
|
||||
start := time.Now()
|
||||
|
||||
// 获取连接数据
|
||||
channels, err := q.Channel.Where(q.Channel.BatchNo.Eq(batch)).Find()
|
||||
channels, err := q.Channel.Where(q.Channel.BatchNo.Eq(batchNo)).Find()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取通道数据失败,batch:%s", batch), err)
|
||||
return core.NewServErr(fmt.Sprintf("获取通道数据失败,batch:%s", batchNo), err)
|
||||
}
|
||||
if len(channels) == 0 {
|
||||
slog.Warn(fmt.Sprintf("未找到通道数据,batch:%s", batch))
|
||||
slog.Warn(fmt.Sprintf("未找到通道数据,batch:%s", batchNo))
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(channels[0].ProxyID)).Take()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取代理数据失败,batch:%s", batch), err)
|
||||
return core.NewServErr(fmt.Sprintf("获取代理数据失败,batch:%s", batchNo), err)
|
||||
}
|
||||
|
||||
// 检查通道是否存在
|
||||
exist, err := g.Redis.Exists(context.Background(), usedChansKey(proxy.ID, batch)).Result()
|
||||
chans, err := g.Redis.LRange(context.Background(), usedChansKey(proxy.ID, batchNo), 0, -1).Result()
|
||||
if err != nil {
|
||||
return core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
if exist == 0 {
|
||||
if len(chans) == 0 {
|
||||
slog.Debug("通道为空,跳过清理", "key", usedChansKey(proxy.ID, batchNo))
|
||||
return nil // 没有使用中通道,已经被清理过了
|
||||
}
|
||||
|
||||
// 准备配置数据
|
||||
edgeConfigs := make([]string, len(channels))
|
||||
configs := make([]*g.PortConfigsReq, len(channels))
|
||||
for i, channel := range channels {
|
||||
if channel.EdgeRef != nil {
|
||||
edgeConfigs[i] = *channel.EdgeRef
|
||||
} else {
|
||||
slog.Warn(fmt.Sprintf("通道 %d 没有保存节点引用", channel.ID))
|
||||
configs := make([]*g.PortConfigsReq, len(chans))
|
||||
for i, ch := range chans {
|
||||
ap, err := netip.ParseAddrPort(ch)
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("解析通道数据失败: %s", ch), err)
|
||||
}
|
||||
|
||||
configs[i] = &g.PortConfigsReq{
|
||||
Status: false,
|
||||
Port: int(channel.Port),
|
||||
Port: int(ap.Port()),
|
||||
Edge: &[]string{},
|
||||
AutoEdgeConfig: &g.AutoEdgeConfig{Count: u.P(0)},
|
||||
Status: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 清空通道配置
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
err := gateway.GatewayPortConfigs(configs)
|
||||
gateway, err := proxyGateway(proxy)
|
||||
if err != nil {
|
||||
return core.NewServErr("创建代理网关失败", err)
|
||||
}
|
||||
|
||||
if err = gateway.GatewayPortConfigs(configs); err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("清空代理 %s 端口配置失败", proxy.IP.String()), err)
|
||||
}
|
||||
|
||||
// 断开节点连接
|
||||
_, err = g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
|
||||
Uuid: proxy.Mac,
|
||||
Edge: &edgeConfigs,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("断开云平台连接失败", "error", err.Error())
|
||||
return core.NewServErr("断开云平台连接失败", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
for _, item := range configs {
|
||||
str, _ := json.Marshal(item)
|
||||
@@ -291,12 +272,12 @@ func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
|
||||
}
|
||||
|
||||
// 释放端口
|
||||
err = freeChans(proxy.ID, batch)
|
||||
err = freeChans(proxy.ID, batchNo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("清除代理端口配置", "proxy", proxy.ID, "batch", batch, "duration", time.Since(start).String())
|
||||
slog.Debug("清除代理端口配置", "proxy", proxy.ID, "batch", batchNo, "duration", time.Since(start).String())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -331,7 +312,7 @@ func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error)
|
||||
Select(q.Channel.BatchNo).
|
||||
Where(
|
||||
q.Channel.BatchNo.In(batchList...),
|
||||
q.Channel.ExpiredAt.Gte(now),
|
||||
q.Channel.ExpiredAt.Gte(now.UTC()),
|
||||
).
|
||||
Group(q.Channel.BatchNo).
|
||||
Scan(&batchQueried)
|
||||
@@ -354,8 +335,8 @@ func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error)
|
||||
return len(batchSet), nil
|
||||
}
|
||||
|
||||
func lockChannelCreateKey() string {
|
||||
return "platform:channel:create"
|
||||
func lockChannelCreateKey(resourceNo string) string {
|
||||
return fmt.Sprintf("platform:channel:create:%s", resourceNo)
|
||||
}
|
||||
|
||||
func lockChannelRemoveKey(bid string) string {
|
||||
@@ -399,13 +380,12 @@ func selectProxy(count int) (*m.Proxy, g.GatewayClient, error) {
|
||||
if maxCount < count {
|
||||
return nil, nil, core.NewBizErr("无可用代理")
|
||||
}
|
||||
proxy := proxyMap[maxId]
|
||||
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return nil, nil, core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
proxy := proxyMap[maxId]
|
||||
gateway, err := proxyGateway(proxy)
|
||||
if err != nil {
|
||||
return nil, nil, core.NewServErr("创建代理网关失败", err)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
|
||||
return proxy, gateway, nil
|
||||
}
|
||||
@@ -427,14 +407,16 @@ func selectPorts(proxyId int32, batchNo string, count int, expire time.Time) ([]
|
||||
return chans, nil
|
||||
}
|
||||
|
||||
// selectEdges 选择节点,优先本地节点,失败重试,直到达到重试次数限制
|
||||
// ensureEdges 检查本地节点是否足够,如果不足从云端连入
|
||||
// 本地节点通过 Assigned = false 排除已分配节点
|
||||
// 云端节点通过 NoRepeat = true 排除已分配节点
|
||||
func selectEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) ([]EdgeInfo, error) {
|
||||
edges := make([]EdgeInfo, 0, count)
|
||||
func ensureEdges(proxy *m.Proxy, gateway g.GatewayClient, filter *EdgeFilter, count int) error {
|
||||
if filter.IsEmpty() {
|
||||
return nil // 没有过滤条件,直接返回空,避免无意义的查询
|
||||
}
|
||||
|
||||
// 先查本地
|
||||
localEdgesResp, err := gateway.GatewayEdge(&g.GatewayEdgeReq{
|
||||
localEdges, err := gateway.GatewayEdge(&g.GatewayEdgeReq{
|
||||
Province: filter.Prov,
|
||||
City: filter.City,
|
||||
Isp: u.X(filter.Isp.String()),
|
||||
@@ -442,22 +424,15 @@ func selectEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) ([]Edge
|
||||
Assigned: u.P(false),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("获取可用节点失败[1]", err)
|
||||
return core.NewBizErr("检查可用节点失败[1]", err)
|
||||
}
|
||||
|
||||
for id, _ := range localEdgesResp {
|
||||
edges = append(edges, EdgeInfo{
|
||||
Type: EdgeInfoLocal,
|
||||
EdgeID: id,
|
||||
})
|
||||
}
|
||||
if len(edges) >= count {
|
||||
return edges, nil
|
||||
if len(localEdges) >= count {
|
||||
return nil // 本地节点足够,直接返回空,后续逻辑会优先使用本地节点
|
||||
}
|
||||
|
||||
// 再查云端
|
||||
remaining := count - len(edges)
|
||||
cloudEdgesResp, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
|
||||
remaining := count - len(localEdges)
|
||||
cloudEdges, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
|
||||
Province: filter.Prov,
|
||||
City: filter.City,
|
||||
Isp: u.X(filter.Isp.String()),
|
||||
@@ -467,20 +442,23 @@ func selectEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) ([]Edge
|
||||
IpUnchangedTime: u.P(3600),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("获取可用节点失败[2]", err)
|
||||
return core.NewBizErr("检查可用节点失败[2]", err)
|
||||
}
|
||||
if len(cloudEdges.Edges) < remaining {
|
||||
return core.NewBizErr("地区可用节点数量不足")
|
||||
}
|
||||
|
||||
for _, edge := range cloudEdgesResp.Edges {
|
||||
edges = append(edges, EdgeInfo{
|
||||
Type: EdgeInfoCloud,
|
||||
EdgeID: edge.EdgeID,
|
||||
})
|
||||
}
|
||||
if len(edges) < count {
|
||||
return nil, core.NewBizErr("地区可用节点数量不足")
|
||||
// 连入云端节点
|
||||
edges := make([]string, remaining)
|
||||
for i, edge := range cloudEdges.Edges {
|
||||
edges[i] = edge.EdgeID
|
||||
}
|
||||
|
||||
return edges, nil
|
||||
if err := g.Cloud.CloudConnect(&g.CloudConnectReq{Uuid: proxy.Mac, Edge: &edges}); err != nil {
|
||||
return core.NewServErr("连接云平台失败", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type EdgeInfo struct {
|
||||
|
||||
@@ -87,9 +87,15 @@ func (s *couponService) Update(data UpdateCouponData) error {
|
||||
do = append(do, q.Coupon.ExpireType.Value(int(*data.ExpireType)))
|
||||
}
|
||||
|
||||
_, err := q.Coupon.Where(q.Coupon.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
r, err := q.Coupon.Where(q.Coupon.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("优惠券状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateCouponData struct {
|
||||
ID int32 `json:"id" validate:"required"`
|
||||
@@ -104,9 +110,15 @@ type UpdateCouponData struct {
|
||||
}
|
||||
|
||||
func (s *couponService) Delete(id int32) error {
|
||||
_, err := q.Coupon.Where(q.Coupon.ID.Eq(id)).UpdateColumn(q.Coupon.DeletedAt, time.Now())
|
||||
r, err := q.Coupon.Where(q.Coupon.ID.Eq(id)).UpdateColumn(q.Coupon.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("优惠券状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *couponService) Assign(couponID int32, userID int32) error {
|
||||
return CouponUser.Create(CreateCouponUserData{
|
||||
@@ -122,7 +134,7 @@ func (s *couponService) GetUserCoupon(uid int32, cuid int32, amount decimal.Deci
|
||||
q.CouponUser.ID.Eq(cuid),
|
||||
q.CouponUser.UserID.Eq(uid),
|
||||
q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
|
||||
q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now())),
|
||||
q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now().UTC())),
|
||||
).Take()
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, core.NewBizErr("优惠券不存在或已失效")
|
||||
@@ -140,7 +152,7 @@ func (s *couponService) GetUserCoupon(uid int32, cuid int32, amount decimal.Deci
|
||||
}
|
||||
|
||||
func (s *couponService) UseCoupon(q *q.Query, cuid int32) error {
|
||||
_, err := q.CouponUser.
|
||||
r, err := q.CouponUser.
|
||||
Where(
|
||||
q.CouponUser.ID.Eq(cuid),
|
||||
q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
|
||||
@@ -149,5 +161,11 @@ func (s *couponService) UseCoupon(q *q.Query, cuid int32) error {
|
||||
q.CouponUser.Status.Value(int(m.CouponUserStatusUsed)),
|
||||
q.CouponUser.UsedAt.Value(time.Now()),
|
||||
)
|
||||
return err
|
||||
if err != nil {
|
||||
return core.NewBizErr("使用优惠券失败", err)
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("优惠券状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
)
|
||||
@@ -37,3 +38,15 @@ type EdgeFilter struct {
|
||||
Prov *string `json:"prov"`
|
||||
City *string `json:"city"`
|
||||
}
|
||||
|
||||
func (f *EdgeFilter) IsEmpty() bool {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if f.Isp.String() == "" || u.Z(f.Prov) != "" || u.Z(f.City) != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -117,9 +117,15 @@ func (s *productService) UpdateProduct(update *UpdateProductData) error {
|
||||
if update.Status != nil {
|
||||
do = append(do, q.Product.Status.Value(*update.Status))
|
||||
}
|
||||
_, err := q.Product.Where(q.Product.ID.Eq(update.Id)).UpdateSimple(do...)
|
||||
r, err := q.Product.Where(q.Product.ID.Eq(update.Id)).UpdateSimple(do...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("产品状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateProductData struct {
|
||||
Id int32 `json:"id"`
|
||||
@@ -132,6 +138,12 @@ type UpdateProductData struct {
|
||||
|
||||
// 删除产品
|
||||
func (s *productService) DeleteProduct(id int32) error {
|
||||
_, err := q.Product.Where(q.Product.ID.Eq(id)).UpdateColumn(q.Product.DeletedAt, time.Now())
|
||||
r, err := q.Product.Where(q.Product.ID.Eq(id)).UpdateColumn(q.Product.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("产品状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,9 +43,15 @@ func (s *productDiscountService) Update(data UpdateProductDiscountData) (err err
|
||||
do = append(do, q.ProductDiscount.Discount.Value(*data.Discount))
|
||||
}
|
||||
|
||||
_, err = q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
r, err := q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("产品折扣状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateProductDiscountData struct {
|
||||
ID int32 `json:"id"`
|
||||
@@ -54,6 +60,12 @@ type UpdateProductDiscountData struct {
|
||||
}
|
||||
|
||||
func (s *productDiscountService) Delete(id int32) (err error) {
|
||||
_, err = q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(id)).UpdateColumn(q.ProductDiscount.DeletedAt, time.Now())
|
||||
return
|
||||
r, err := q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(id)).UpdateColumn(q.ProductDiscount.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("产品折扣状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -111,9 +111,15 @@ func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
|
||||
do = append(do, q.ProductSku.CountMin.Value(*update.CountMin))
|
||||
}
|
||||
|
||||
_, err = q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...)
|
||||
r, err := q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("产品套餐状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateProductSkuData struct {
|
||||
ID int32 `json:"id"`
|
||||
@@ -128,15 +134,27 @@ type UpdateProductSkuData struct {
|
||||
}
|
||||
|
||||
func (s *productSkuService) Delete(id int32) (err error) {
|
||||
_, err = q.ProductSku.Where(q.ProductSku.ID.Eq(id)).UpdateColumn(q.ProductSku.DeletedAt, time.Now())
|
||||
return
|
||||
r, err := q.ProductSku.Where(q.ProductSku.ID.Eq(id)).UpdateColumn(q.ProductSku.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("产品套餐状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *productSkuService) BatchUpdateDiscount(data BatchUpdateSkuDiscountData) (err error) {
|
||||
_, err = q.ProductSku.Where(q.ProductSku.ProductID.Eq(data.ProductID)).UpdateSimple(
|
||||
r, err := q.ProductSku.Where(q.ProductSku.ProductID.Eq(data.ProductID)).UpdateSimple(
|
||||
q.ProductSku.DiscountId.Value(data.DiscountID),
|
||||
)
|
||||
return
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewServErr("产品套餐状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BatchUpdateSkuDiscountData struct {
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"platform/web/globals/orm"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gen/field"
|
||||
@@ -208,3 +210,14 @@ func (s *proxyService) UpdateStatus(update *UpdateProxyStatus) error {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func proxyGateway(proxy *m.Proxy) (g.GatewayClient, error) {
|
||||
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return nil, core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
|
||||
return gateway, nil
|
||||
}
|
||||
|
||||
@@ -130,12 +130,15 @@ func (s *resourceService) Update(data *UpdateResourceData) error {
|
||||
do = append(do, q.Resource.CheckIP.Value(*data.CheckIP))
|
||||
}
|
||||
|
||||
_, err := q.Resource.
|
||||
r, err := q.Resource.
|
||||
Where(q.Resource.ID.Eq(data.Id)).
|
||||
UpdateSimple(do...)
|
||||
if err != nil {
|
||||
return core.NewServErr("更新套餐失败", err)
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("套餐状态已过期")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
|
||||
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
// 更新交易信息
|
||||
_, err := q.Trade.
|
||||
r, err := q.Trade.
|
||||
Where(
|
||||
q.Trade.InnerNo.Eq(interNo),
|
||||
q.Trade.Status.Eq(int(m.TradeStatusPending)),
|
||||
@@ -299,6 +299,9 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
|
||||
if err != nil {
|
||||
return core.NewServErr("更新交易信息失败", err)
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("交易状态已过期")
|
||||
}
|
||||
|
||||
switch trade.Type {
|
||||
case m.TradeTypeRecharge:
|
||||
@@ -406,7 +409,7 @@ func (s *tradeService) CancelTrade(ref *TradeRef) error {
|
||||
return nil
|
||||
}
|
||||
func (s *tradeService) OnCancelTrade(tradeNo string, now time.Time) error {
|
||||
_, err := q.Trade.
|
||||
r, err := q.Trade.
|
||||
Where(
|
||||
q.Trade.InnerNo.Eq(tradeNo),
|
||||
q.Trade.Status.Eq(int(m.TradeStatusPending)),
|
||||
@@ -418,6 +421,9 @@ func (s *tradeService) OnCancelTrade(tradeNo string, now time.Time) error {
|
||||
if err != nil {
|
||||
return core.NewServErr("更新交易状态失败", err)
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("交易状态已过期")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Dec
|
||||
}
|
||||
|
||||
// 更新余额
|
||||
_, err := q.User.
|
||||
r, err := q.User.
|
||||
Where(
|
||||
q.User.ID.Eq(user.ID),
|
||||
q.User.Balance.Eq(user.Balance),
|
||||
@@ -61,6 +61,9 @@ func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Dec
|
||||
if err != nil {
|
||||
return core.NewServErr("更新用户余额失败", err)
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("余额状态已过期")
|
||||
}
|
||||
|
||||
// 新增动账记录
|
||||
err = q.BalanceActivity.Create(&m.BalanceActivity{
|
||||
@@ -204,13 +207,19 @@ func (s *userService) UpdateByAdmin(data UpdateUserByAdminData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := q.User.Where(q.User.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
r, err := q.User.Where(q.User.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return core.NewBizErr("账号已存在,请检查手机号/用户名/邮箱是否重复")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户状态已过期")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateUserByAdminData struct {
|
||||
ID int32 `json:"id" validate:"required"`
|
||||
@@ -231,6 +240,12 @@ type UpdateUserByAdminData struct {
|
||||
}
|
||||
|
||||
func (s *userService) RemoveByAdmin(id int32) error {
|
||||
_, err := q.User.Where(q.User.ID.Eq(id)).UpdateColumn(q.User.DeletedAt, time.Now())
|
||||
r, err := q.User.Where(q.User.ID.Eq(id)).UpdateColumn(q.User.DeletedAt, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户状态已过期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,3 +52,13 @@ func HandleRemoveChannel(_ context.Context, task *asynq.Task) (err error) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleRefreshEdges(_ context.Context, task *asynq.Task) (err error) {
|
||||
slog.Info("[event]刷新边缘节点")
|
||||
|
||||
err = s.Channel.RefreshEdges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("刷新边缘节点失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
30
web/web.go
30
web/web.go
@@ -42,6 +42,10 @@ func RunApp(pCtx context.Context) error {
|
||||
return RunTask(ctx)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return RunCron(ctx)
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
@@ -80,7 +84,7 @@ func RunWeb(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func RunTask(ctx context.Context) error {
|
||||
var server = asynq.NewServerFromRedisClient(deps.Redis, asynq.Config{
|
||||
server := asynq.NewServerFromRedisClient(deps.Redis, asynq.Config{
|
||||
ShutdownTimeout: 5 * time.Second,
|
||||
ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
|
||||
slog.Error("任务执行失败", "task", task.Type(), "error", err)
|
||||
@@ -91,6 +95,7 @@ func RunTask(ctx context.Context) error {
|
||||
var mux = asynq.NewServeMux()
|
||||
mux.HandleFunc(events.RemoveChannel, tasks.HandleRemoveChannel)
|
||||
mux.HandleFunc(events.CloseTrade, tasks.HandleCompleteTrade)
|
||||
mux.HandleFunc(events.RefreshEdge, tasks.HandleRefreshEdges)
|
||||
|
||||
// 停止服务
|
||||
go func() {
|
||||
@@ -107,6 +112,29 @@ func RunTask(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunCron(ctx context.Context) error {
|
||||
cron := asynq.NewSchedulerFromRedisClient(deps.Redis, &asynq.SchedulerOpts{
|
||||
Logger: &AppAsynqLogger{},
|
||||
Location: time.Local,
|
||||
})
|
||||
|
||||
cron.Register("0/10 * * * *", events.NewRefreshEdge())
|
||||
|
||||
// 停止服务
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
cron.Shutdown()
|
||||
}()
|
||||
|
||||
// 启动服务
|
||||
err := cron.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("定时任务服务运行失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppAsynqLogger struct{}
|
||||
|
||||
func (l *AppAsynqLogger) Debug(args ...any) {
|
||||
|
||||
Reference in New Issue
Block a user