Files
platform/web/handlers/channel.go

446 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"`
}