Files
waf-platform/EdgeUser/internal/web/actions/default/lb/create.go
2026-02-04 20:27:13 +08:00

548 lines
14 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 lb
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
"strconv"
)
type CreateAction struct {
actionutils.ParentAction
}
func (this *CreateAction) Init() {
this.Nav("", "", "create")
}
func (this *CreateAction) RunGet(params struct{}) {
var supportTCP = this.ValidateFeature(userconfigs.UserFeatureCodeServerTCP, 0)
var supportUDP = this.ValidateFeature(userconfigs.UserFeatureCodeServerUDP, 0)
if !supportTCP && !supportUDP {
return
}
this.Data["supportTCP"] = supportTCP
this.Data["supportUDP"] = supportUDP
// 服务类型
var serverTypes = []maps.Map{}
if supportTCP {
serverTypes = append(serverTypes, maps.Map{
"name": "TCP负载均衡",
"code": serverconfigs.ServerTypeTCPProxy,
})
}
if supportUDP {
serverTypes = append(serverTypes, maps.Map{
"name": "UDP负载均衡",
"code": serverconfigs.ServerTypeUDPProxy,
})
}
this.Data["serverTypes"] = serverTypes
this.Data["canSpecifyTCPPort"] = this.ValidateFeature(userconfigs.UserFeatureCodeServerTCPPort, 0)
this.Data["canSpecifyUDPPort"] = this.ValidateFeature(userconfigs.UserFeatureCodeServerUDPPort, 0)
this.Show()
}
func (this *CreateAction) RunPost(params struct {
Name string
ServerType string
Protocols []string
CertIdsJSON []byte
OriginsJSON []byte
TcpPorts []int
TlsPorts []int
UdpPorts []int
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 检查ServerType
var serverType = params.ServerType
if !lists.ContainsString([]string{serverconfigs.ServerTypeTCPProxy, serverconfigs.ServerTypeUDPProxy}, serverType) {
this.Fail("请选择正确的服务类型")
}
// 检查用户所在集群
clusterIdResp, err := this.RPC().UserRPC().FindUserNodeClusterId(this.UserContext(), &pb.FindUserNodeClusterIdRequest{UserId: this.UserId()})
if err != nil {
this.ErrorPage(err)
return
}
clusterId := clusterIdResp.NodeClusterId
if clusterId == 0 {
this.Fail("当前用户没有指定集群,不能使用此服务")
}
// 检查是否有TCP权限
if lists.ContainsString(params.Protocols, "tcp") && !this.ValidateFeature(userconfigs.UserFeatureCodeServerTCP, 0) {
this.Fail("你没有权限使用TCP负载均衡功能")
}
// 检查是否有UDP权限
if lists.ContainsString(params.Protocols, "udp") && !this.ValidateFeature(userconfigs.UserFeatureCodeServerUDP, 0) {
this.Fail("你没有权限使用UDP负载均衡功能")
}
params.Must.
Field("name", params.Name).
Require("请输入服务名称")
// 协议
if len(params.Protocols) == 0 {
this.Fail("请选择至少一个协议")
}
// TCP
canSpecifyTCPPort := this.ValidateFeature(userconfigs.UserFeatureCodeServerTCPPort, 0)
if serverType == serverconfigs.ServerTypeTCPProxy {
// 检查TCP端口
if canSpecifyTCPPort {
if lists.Contains(params.Protocols, "tcp") {
if len(params.TcpPorts) == 0 {
this.Fail("需要至少指定一个TCP监听端口")
}
for _, port := range params.TcpPorts {
if port < 1024 || port > 65534 {
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
}
// 检查是否被使用
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
Port: types.Int32(port),
NodeClusterId: clusterId,
ProtocolFamily: "tcp",
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsUsing {
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
}
}
}
if lists.Contains(params.Protocols, "tls") {
if len(params.TlsPorts) == 0 {
this.Fail("需要至少指定一个TLS监听端口")
}
for _, port := range params.TlsPorts {
if port < 1024 || port > 65534 {
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
}
if lists.ContainsInt(params.TcpPorts, port) {
this.Fail("TLS端口 '" + strconv.Itoa(port) + "' 已经被TCP端口使用不能重复使用")
}
// 检查是否被使用
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
Port: types.Int32(port),
NodeClusterId: clusterId,
ProtocolFamily: "tcp",
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsUsing {
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
}
}
}
}
}
// UDP
canSpecifyUDPPort := this.ValidateFeature(userconfigs.UserFeatureCodeServerUDPPort, 0)
if serverType == serverconfigs.ServerTypeUDPProxy {
// 检查UDP端口
if canSpecifyUDPPort {
if lists.Contains(params.Protocols, "udp") {
if len(params.UdpPorts) == 0 {
this.Fail("需要至少指定一个UDP监听端口")
}
for _, port := range params.UdpPorts {
if port < 1024 || port > 65534 {
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
}
// 检查是否被使用
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
Port: types.Int32(port),
NodeClusterId: clusterId,
ProtocolFamily: "udp",
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsUsing {
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
}
}
}
}
}
// 先加锁
lockerKey := "create_tcp_server"
lockResp, err := this.RPC().SysLockerRPC().SysLockerLock(this.UserContext(), &pb.SysLockerLockRequest{
Key: lockerKey,
TimeoutSeconds: 30,
})
if err != nil {
this.ErrorPage(err)
return
}
if !lockResp.Ok {
this.Fail("操作繁忙,请稍后再试")
}
defer func() {
_, err := this.RPC().SysLockerRPC().SysLockerUnlock(this.UserContext(), &pb.SysLockerUnlockRequest{Key: lockerKey})
if err != nil {
this.ErrorPage(err)
return
}
}()
tcpConfig := &serverconfigs.TCPProtocolConfig{}
tlsConfig := &serverconfigs.TLSProtocolConfig{}
udpConfig := &serverconfigs.UDPProtocolConfig{}
if serverType == serverconfigs.ServerTypeTCPProxy {
// TCP
ports := []int{}
if lists.ContainsString(params.Protocols, "tcp") {
tcpConfig.IsOn = true
if canSpecifyTCPPort {
for _, port := range params.TcpPorts {
tcpConfig.Listen = append(tcpConfig.Listen, &serverconfigs.NetworkAddressConfig{
Protocol: serverconfigs.ProtocolTCP,
Host: "",
PortRange: strconv.Itoa(port),
})
}
} else {
// 获取随机端口
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
NodeClusterId: clusterId,
ProtocolFamily: "tcp",
})
if err != nil {
this.ErrorPage(err)
return
}
port := int(portResp.Port)
ports = append(ports, port)
tcpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolTCP,
Host: "",
PortRange: strconv.Itoa(port),
},
}
}
}
// TLS
if lists.ContainsString(params.Protocols, "tls") {
tlsConfig.IsOn = true
if canSpecifyTCPPort {
for _, port := range params.TlsPorts {
tlsConfig.Listen = append(tlsConfig.Listen, &serverconfigs.NetworkAddressConfig{
Protocol: serverconfigs.ProtocolTLS,
Host: "",
PortRange: strconv.Itoa(port),
})
}
} else {
var port int
// 尝试N次
for i := 0; i < 5; i++ {
// 获取随机端口
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
NodeClusterId: clusterId,
ProtocolFamily: "tcp",
})
if err != nil {
this.ErrorPage(err)
return
}
p := int(portResp.Port)
if !lists.ContainsInt(ports, p) {
port = p
break
}
}
if port == 0 {
this.Fail("无法找到可用的端口,请稍后重试")
}
tlsConfig.Listen = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolTLS,
Host: "",
PortRange: strconv.Itoa(port),
},
}
}
if len(params.CertIdsJSON) == 0 {
this.Fail("请选择或者上传TLS证书")
}
certIds := []int64{}
err := json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
if len(certIds) == 0 {
this.Fail("请选择或者上传TLS证书")
}
certRefs := []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
})
}
certRefsJSON, err := json.Marshal(certRefs)
if err != nil {
this.ErrorPage(err)
return
}
// 创建策略
sslPolicyIdResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.UserContext(), &pb.CreateSSLPolicyRequest{
Http2Enabled: false,
Http3Enabled: false,
MinVersion: "TLS 1.1",
SslCertsJSON: certRefsJSON,
HstsJSON: nil,
ClientAuthType: 0,
ClientCACertsJSON: nil,
CipherSuites: nil,
CipherSuitesIsOn: false,
})
if err != nil {
this.ErrorPage(err)
return
}
tlsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyIdResp.SslPolicyId,
}
}
}
// UDP
if serverType == serverconfigs.ServerTypeUDPProxy {
if lists.ContainsString(params.Protocols, "udp") {
udpConfig.IsOn = true
if canSpecifyUDPPort {
for _, port := range params.UdpPorts {
udpConfig.Listen = append(udpConfig.Listen, &serverconfigs.NetworkAddressConfig{
Protocol: serverconfigs.ProtocolUDP,
Host: "",
PortRange: strconv.Itoa(port),
})
}
} else {
// 获取随机端口
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
NodeClusterId: clusterId,
ProtocolFamily: "udp",
})
if err != nil {
this.ErrorPage(err)
return
}
port := int(portResp.Port)
udpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolUDP,
Host: "",
PortRange: strconv.Itoa(port),
},
}
}
}
}
// 源站信息
originMaps := []maps.Map{}
if len(params.OriginsJSON) == 0 {
this.Fail("请输入源站信息")
}
err = json.Unmarshal(params.OriginsJSON, &originMaps)
if err != nil {
this.ErrorPage(err)
return
}
if len(originMaps) == 0 {
this.Fail("请输入源站信息")
}
primaryOriginRefs := []*serverconfigs.OriginRef{}
backupOriginRefs := []*serverconfigs.OriginRef{}
for _, originMap := range originMaps {
host := originMap.GetString("host")
isPrimary := originMap.GetBool("isPrimary")
scheme := originMap.GetString("scheme")
if len(host) == 0 {
this.Fail("源站地址不能为空")
}
addrHost, addrPort, err := net.SplitHostPort(host)
if err != nil {
this.Fail("源站地址'" + host + "'格式错误")
}
if (serverType == serverconfigs.ServerTypeTCPProxy && scheme != "tcp" && scheme != "tls") ||
(serverType == serverconfigs.ServerTypeUDPProxy && scheme != "udp") {
this.Fail("错误的源站协议")
}
originIdResp, err := this.RPC().OriginRPC().CreateOrigin(this.UserContext(), &pb.CreateOriginRequest{
Name: "",
Addr: &pb.NetworkAddress{
Protocol: scheme,
Host: addrHost,
PortRange: addrPort,
},
Description: "",
Weight: 10,
IsOn: true,
})
if err != nil {
this.ErrorPage(err)
return
}
if isPrimary {
primaryOriginRefs = append(primaryOriginRefs, &serverconfigs.OriginRef{
IsOn: true,
OriginId: originIdResp.OriginId,
})
} else {
backupOriginRefs = append(backupOriginRefs, &serverconfigs.OriginRef{
IsOn: true,
OriginId: originIdResp.OriginId,
})
}
}
primaryOriginsJSON, err := json.Marshal(primaryOriginRefs)
if err != nil {
this.ErrorPage(err)
return
}
backupOriginsJSON, err := json.Marshal(backupOriginRefs)
if err != nil {
this.ErrorPage(err)
return
}
scheduling := &serverconfigs.SchedulingConfig{
Code: "random",
Options: nil,
}
schedulingJSON, err := json.Marshal(scheduling)
if err != nil {
this.ErrorPage(err)
return
}
// 反向代理
reverseProxyResp, err := this.RPC().ReverseProxyRPC().CreateReverseProxy(this.UserContext(), &pb.CreateReverseProxyRequest{
SchedulingJSON: schedulingJSON,
PrimaryOriginsJSON: primaryOriginsJSON,
BackupOriginsJSON: backupOriginsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
reverseProxyId := reverseProxyResp.ReverseProxyId
reverseProxyRef := &serverconfigs.ReverseProxyRef{
IsPrior: false,
IsOn: true,
ReverseProxyId: reverseProxyId,
}
reverseProxyRefJSON, err := json.Marshal(reverseProxyRef)
if err != nil {
this.ErrorPage(err)
return
}
// 开始保存
var tcpJSON []byte
var tlsJSON []byte
var udpJSON []byte
if tcpConfig.IsOn {
tcpJSON, err = tcpConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
}
if tlsConfig.IsOn {
tlsJSON, err = tlsConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
}
if udpConfig.IsOn {
udpJSON, err = udpConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
}
createResp, err := this.RPC().ServerRPC().CreateServer(this.UserContext(), &pb.CreateServerRequest{
UserId: this.UserId(),
AdminId: 0,
Type: serverType,
Name: params.Name,
Description: "",
ServerNamesJSON: []byte("[]"),
HttpJSON: nil,
HttpsJSON: nil,
TcpJSON: tcpJSON,
TlsJSON: tlsJSON,
UdpJSON: udpJSON,
WebId: 0,
ReverseProxyJSON: reverseProxyRefJSON,
ServerGroupIds: nil,
NodeClusterId: clusterId,
IncludeNodesJSON: nil,
ExcludeNodesJSON: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
serverId := createResp.ServerId
defer this.CreateLogInfo(codes.Server_LogCreateServer, serverId)
this.Success()
}