Подготовить HY2XS к production-сборке

This commit is contained in:
2026-04-25 23:13:12 +05:00
commit 84a4e94567
277 changed files with 26513 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
package service
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
"hy2xs-admin/dao"
"hy2xs-admin/model/bo"
"hy2xs-admin/model/constant"
"hy2xs-admin/proxy"
"net/url"
"strings"
"time"
)
func Hysteria2Auth(conPass string) (int64, string, error) {
if !Hysteria2IsRunning() {
return 0, "", errors.New("hysteria2 is not running")
}
now := time.Now().UnixMilli()
account, err := dao.GetAccount("con_pass = ? and deleted = 0 and (quota < 0 or quota > download + upload) and ? < expire_time and ? > kick_util_time", conPass, now, now)
if err != nil {
return 0, "", err
}
// 限制设备数
onlineUsers, err := Hysteria2Online()
if err != nil {
return 0, "", err
}
device, exist := onlineUsers[*account.Username]
if exist && *account.DeviceNo <= device {
return 0, "", errors.New("device limited")
}
return *account.Id, *account.Username, nil
}
func Hysteria2Online() (map[string]int64, error) {
if !Hysteria2IsRunning() {
return map[string]int64{}, nil
}
apiPort, err := GetHysteria2ApiPort()
if err != nil {
return nil, errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
if err != nil {
return nil, err
}
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value)
if err != nil {
return nil, err
}
return onlineUsers, nil
}
func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
if !Hysteria2IsRunning() {
return errors.New("hysteria2 is not running")
}
if err := dao.UpdateAccount(ids, map[string]interface{}{"kick_util_time": kickUtilTime}); err != nil {
return err
}
accounts, err := dao.ListAccount("id in ?", ids)
if err != nil {
return err
}
var keys []string
for _, item := range accounts {
keys = append(keys, *item.Username)
}
apiPort, err := GetHysteria2ApiPort()
if err != nil {
return errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
if err != nil {
return err
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil {
return err
}
return nil
}
func Hysteria2SubscribeUrl(accountId int64, protocol string, host string) (string, error) {
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
return "", err
}
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
if err != nil {
return "", err
}
webContext := ""
if config.Value != nil && *config.Value != "/" && strings.HasPrefix(*config.Value, "/") {
webContext = *config.Value
}
return fmt.Sprintf("%s//%s%s/hui/%s", protocol, host, webContext, url.QueryEscape(*account.ConPass)), nil
}
func Hysteria2Subscribe(conPass string, clientType string, host string) (string, string, error) {
hysteria2Config, err := GetHysteria2Config()
if err != nil {
return "", "", err
}
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", "", errors.New("hysteria2 config is empty")
}
account, err := dao.GetAccount("con_pass = ?", conPass)
if err != nil {
return "", "", err
}
hysteria2Name := "hysteria2"
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", "", err
}
if *hysteria2ConfigRemark.Value != "" {
hysteria2Name = *hysteria2ConfigRemark.Value
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
if err != nil {
return "", "", err
}
userInfo := ""
configStr := ""
if clientType == constant.Shadowrocket || clientType == constant.Clash {
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
*account.Upload,
*account.Download,
*account.Quota,
*account.ExpireTime/1000)
hysteria2 := bo.Hysteria2{
Name: hysteria2Name,
Type: "hysteria2",
Server: strings.Split(host, ":")[0],
Port: strings.Split(*hysteria2Config.Listen, ":")[1],
Ports: *hysteria2ConfigPortHopping.Value,
Password: conPass,
}
if hysteria2Config.Bandwidth != nil {
if hysteria2Config.Bandwidth.Up != nil &&
*hysteria2Config.Bandwidth.Up != "" {
hysteria2.Up = *hysteria2Config.Bandwidth.Up
}
if hysteria2Config.Bandwidth.Down != nil &&
*hysteria2Config.Bandwidth.Down != "" {
hysteria2.Down = *hysteria2Config.Bandwidth.Down
}
}
if hysteria2Config.Obfs != nil &&
hysteria2Config.Obfs.Type != nil &&
*hysteria2Config.Obfs.Type == "salamander" &&
hysteria2Config.Obfs.Salamander != nil &&
hysteria2Config.Obfs.Salamander.Password != nil &&
*hysteria2Config.Obfs.Salamander.Password != "" {
if clientType == constant.Shadowrocket {
hysteria2.Obfs = *hysteria2Config.Obfs.Salamander.Password
} else {
hysteria2.Obfs = "salamander"
hysteria2.ObfsPassword = *hysteria2Config.Obfs.Salamander.Password
}
}
if hysteria2Config.ACME != nil &&
hysteria2Config.ACME.Domains != nil &&
len(hysteria2Config.ACME.Domains) > 0 {
hysteria2.Sni = hysteria2Config.ACME.Domains[0]
}
hysteria2.SkipCertVerify = false
proxyGroup := bo.ProxyGroup{
Name: "PROXY",
Type: "select",
Proxies: []string{hysteria2Name},
}
clashConfig := bo.ClashConfig{
ProxyGroups: []bo.ProxyGroup{
proxyGroup,
},
Proxies: []interface{}{hysteria2},
}
clashConfigYaml, err := yaml.Marshal(&clashConfig)
if err != nil {
return "", "", err
}
configStr = string(clashConfigYaml)
if clientType == constant.Clash {
clashExtension, err := GetConfig(constant.ClashExtension)
if err != nil {
return "", "", err
}
if clashExtension.Value != nil && *clashExtension.Value != "" {
configStr = fmt.Sprintf("%s%s", configStr, *clashExtension.Value)
}
}
} else if clientType == constant.V2rayN {
hysteria2Url, err := Hysteria2Url(*account.Id, strings.Split(host, ":")[0])
if err != nil {
return "", "", err
}
configStr = hysteria2Url
}
return userInfo, configStr, nil
}
func Hysteria2Url(accountId int64, hostname string) (string, error) {
hysteria2Config, err := GetHysteria2Config()
if err != nil {
return "", err
}
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", errors.New("hysteria2 config is empty")
}
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
return "", err
}
urlConfig := ""
if hysteria2Config.Obfs != nil &&
hysteria2Config.Obfs.Type != nil &&
*hysteria2Config.Obfs.Type == "salamander" &&
hysteria2Config.Obfs.Salamander != nil &&
hysteria2Config.Obfs.Salamander.Password != nil &&
*hysteria2Config.Obfs.Salamander.Password != "" {
urlConfig += fmt.Sprintf("&obfs=salamander&obfs-password=%s", *hysteria2Config.Obfs.Salamander.Password)
}
if hysteria2Config.ACME != nil &&
hysteria2Config.ACME.Domains != nil &&
len(hysteria2Config.ACME.Domains) > 0 {
urlConfig += fmt.Sprintf("&sni=%s", hysteria2Config.ACME.Domains[0])
// shadowrocket
urlConfig += fmt.Sprintf("&peer=%s", hysteria2Config.ACME.Domains[0])
}
urlConfig += "&insecure=0"
if hysteria2Config.Bandwidth != nil &&
hysteria2Config.Bandwidth.Down != nil &&
*hysteria2Config.Bandwidth.Down != "" {
// shadowrocket
urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down))
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
if err != nil {
return "", err
}
if *hysteria2ConfigPortHopping.Value != "" {
// shadowrocket
urlConfig += fmt.Sprintf("&mport=%s", *hysteria2ConfigPortHopping.Value)
}
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", err
}
if *hysteria2ConfigRemark.Value != "" {
urlConfig += fmt.Sprintf("#%s", *hysteria2ConfigRemark.Value)
}
if urlConfig != "" {
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
}
return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil
}