446 lines
11 KiB
Go
446 lines
11 KiB
Go
package handlers
|
||
|
||
import (
|
||
"net/netip"
|
||
"platform/pkg/u"
|
||
"platform/web/auth"
|
||
"platform/web/core"
|
||
g "platform/web/globals"
|
||
"platform/web/globals/orm"
|
||
m "platform/web/models"
|
||
q "platform/web/queries"
|
||
s "platform/web/services"
|
||
"time"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
)
|
||
|
||
// ListChannel 分页查询当前用户通道
|
||
func ListChannel(c *fiber.Ctx) error {
|
||
// 检查权限
|
||
authContext, err := auth.GetAuthCtx(c).PermitUser()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 解析请求参数
|
||
req := new(ListChannelsReq)
|
||
if err := c.BodyParser(req); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 构造查询条件
|
||
cond := q.Channel.
|
||
Where(q.Channel.UserID.Eq(authContext.User.ID))
|
||
switch req.AuthType {
|
||
case s.ChannelAuthTypeIp:
|
||
cond.Where(q.Channel.Whitelists.IsNotNull())
|
||
case s.ChannelAuthTypePass:
|
||
cond.Where(q.Channel.Username.IsNotNull(), q.Channel.Password.IsNotNull())
|
||
default:
|
||
break
|
||
}
|
||
|
||
if req.ExpireAfter != nil {
|
||
cond = cond.Where(q.Channel.ExpiredAt.Gte(req.ExpireAfter.UTC()))
|
||
}
|
||
if req.ExpireBefore != nil {
|
||
cond = cond.Where(q.Channel.ExpiredAt.Lte(req.ExpireBefore.UTC()))
|
||
}
|
||
|
||
// 查询数据
|
||
channels, err := q.Channel.
|
||
Where(cond).
|
||
Order(q.Channel.CreatedAt.Desc()).
|
||
Offset(req.GetOffset()).
|
||
Limit(req.GetLimit()).
|
||
Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 查询总量
|
||
var total int64
|
||
if len(channels) < req.GetLimit() {
|
||
total = int64(len(channels) + req.GetOffset())
|
||
} else {
|
||
total, err = q.Channel.Where(cond).Count()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 返回结果
|
||
return c.JSON(core.PageResp{
|
||
Total: int(total),
|
||
Page: req.GetPage(),
|
||
Size: req.GetSize(),
|
||
List: channels,
|
||
})
|
||
}
|
||
|
||
type ListChannelsReq struct {
|
||
core.PageReq
|
||
AuthType s.ChannelAuthType `json:"auth_type"`
|
||
ExpireAfter *time.Time `json:"expire_after"`
|
||
ExpireBefore *time.Time `json:"expire_before"`
|
||
}
|
||
|
||
// CreateChannel 创建新通道
|
||
func CreateChannel(c *fiber.Ctx) error {
|
||
// 不检查权限,允许 api 调用
|
||
|
||
// 解析参数
|
||
req := new(CreateChannelReq)
|
||
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)
|
||
}
|
||
|
||
// 创建通道
|
||
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, no,
|
||
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 CreateChannelReq struct {
|
||
ResourceId int32 `json:"resource_id" 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"`
|
||
}
|
||
|
||
// 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"`
|
||
IP string `json:"ip"`
|
||
Port uint16 `json:"port"`
|
||
Username *string `json:"username,omitempty"`
|
||
Password *string `json:"password,omitempty"`
|
||
}
|
||
|
||
// RemoveChannels 删除通道
|
||
func RemoveChannels(c *fiber.Ctx) error {
|
||
// 检查权限
|
||
_, err := auth.GetAuthCtx(c).PermitOfficialClient()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 解析请求参数
|
||
req := new(RemoveChannelsReq)
|
||
if err := c.BodyParser(req); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 删除通道
|
||
err = s.Channel.RemoveChannels(req.Batch)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return c.SendStatus(fiber.StatusOK)
|
||
}
|
||
|
||
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 {
|
||
// 检查权限
|
||
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelReadOfUser)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 解析请求参数
|
||
var req PageChannelOfUserByAdminReq
|
||
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 构建查询条件
|
||
do := q.Channel.Where(q.Channel.UserID.Eq(req.UserID))
|
||
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.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()))
|
||
}
|
||
|
||
// 查询通道列表
|
||
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 PageChannelOfUserByAdminReq struct {
|
||
core.PageReq
|
||
UserID int32 `json:"user_id" validate:"required"`
|
||
ResourceNo *string `json:"resource_no"`
|
||
BatchNo *string `json:"batch_no"`
|
||
ProxyHost *string `json:"proxy_host"`
|
||
ProxyPort *uint16 `json:"proxy_port"`
|
||
ExpiredAtStart *time.Time `json:"expired_at_start"`
|
||
ExpiredAtEnd *time.Time `json:"expired_at_end"`
|
||
}
|
||
|
||
// SyncChannelClearExpiredByAdmin 清理过期通道
|
||
func SyncChannelClearExpiredByAdmin(c *fiber.Ctx) error {
|
||
if _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelWriteClearExpired); err != nil {
|
||
return err
|
||
}
|
||
|
||
var req SyncChannelClearExpiredByAdminReq
|
||
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||
return err
|
||
}
|
||
|
||
count, err := s.Channel.ClearExpiredChannels(req.ProxyID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return c.JSON(SyncChannelClearExpiredByAdminResp{
|
||
Count: count,
|
||
})
|
||
}
|
||
|
||
type SyncChannelClearExpiredByAdminReq struct {
|
||
ProxyID int32 `json:"proxy_id" validate:"required"`
|
||
}
|
||
|
||
type SyncChannelClearExpiredByAdminResp struct {
|
||
Count int `json:"count"`
|
||
}
|