mirror of
https://github.com/cmz0228/hysteria-dev.git
synced 2025-06-07 12:59:53 +00:00
v1
This commit is contained in:
parent
78598bfd1b
commit
74b74deac5
99
.github/workflows/docker.yml
vendored
99
.github/workflows/docker.yml
vendored
@ -1,44 +1,89 @@
|
||||
name: "Build Docker Image"
|
||||
name: Docker
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- app/v*.*.*
|
||||
branches: [ "master" ]
|
||||
# Publish semver tags as releases.
|
||||
tags: [ 'v*.*.*' ]
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# This is used to complete the identity challenge
|
||||
# with sigstore/fulcio when running outside of PRs.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: echo "version=$(git describe --tags --always --match 'app/v*' | sed -n 's|app/\([^/-]*\)\(-.*\)\{0,1\}|\1|p')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
cosign-release: 'v2.2.2'
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.5.1
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:v2,tobyxdd/hysteria:${{ steps.get_version.outputs.version }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
platforms: linux/amd64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ github.repository_owner }}/hysteria
|
||||
${{ env.REGISTRY }}/${{ github.repository_owner }}/hysteria:latest
|
||||
${{ env.REGISTRY }}/${{ github.repository_owner }}/hysteria:${{ steps.get_version.outputs.version }}
|
||||
# Sign the resulting Docker image digest except on PRs.
|
||||
# This will only write to the public Rekor transparency log when the Docker
|
||||
# repository is public to avoid leaking data. If you would like to publish
|
||||
# transparency data even for private images, pass --force to cosign below.
|
||||
# https://github.com/sigstore/cosign
|
||||
- name: Sign the published Docker image
|
||||
env:
|
||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
||||
TAGS: ${{ steps.meta.outputs.tags }}
|
||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
||||
# This step uses the identity token to provision an ephemeral certificate
|
||||
# against the sigstore community Fulcio instance.
|
||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
@ -29,11 +29,12 @@ RUN if [ ! -e /etc/nsswitch.conf ]; then echo 'hosts: files dns' > /etc/nsswitch
|
||||
#
|
||||
# Do not try to add the "--no-cache" option when there are multiple "apk"
|
||||
# commands, this will cause the build process to become very slow.
|
||||
COPY ./entrypoint /usr/local/bin/entrypoint
|
||||
RUN set -ex \
|
||||
&& apk upgrade \
|
||||
&& apk add bash tzdata ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& chmod +x /usr/local/bin/entrypoint
|
||||
|
||||
COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria
|
||||
|
||||
ENTRYPOINT ["hysteria"]
|
||||
CMD ["entrypoint"]
|
95
README.md
95
README.md
@ -1,60 +1,49 @@
|
||||
# 
|
||||
|
||||
[![License][1]][2] [![Release][3]][4] [![Telegram][5]][6] [![Discussions][7]][8]
|
||||
# 支持对接V2board面板的Hysteria2后端
|
||||
|
||||
[1]: https://img.shields.io/badge/license-MIT-blue
|
||||
[2]: LICENSE.md
|
||||
[3]: https://img.shields.io/github/v/release/apernet/hysteria?style=flat-square
|
||||
[4]: https://github.com/apernet/hysteria/releases
|
||||
[5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square
|
||||
[6]: https://t.me/hysteria_github
|
||||
[7]: https://img.shields.io/github/discussions/apernet/hysteria?style=flat-square
|
||||
[8]: https://github.com/apernet/hysteria/discussions
|
||||
### 项目说明
|
||||
本项目基于hysteria官方内核二次开发,添加了从v2b获取节点信息、用户鉴权信息与上报用户流量的功能。
|
||||
性能方面已经由hysteria2内核作者亲自指导优化过了。
|
||||
|
||||
<h2 style="text-align: center;">Hysteria is a powerful, lightning fast and censorship resistant proxy.</h2>
|
||||
### TG交流群
|
||||
欢迎加入交流群 [点击加入](https://t.me/+DcRt8AB2VbI2Yzc1)
|
||||
|
||||
### [Get Started](https://v2.hysteria.network/)
|
||||
|
||||
### [中文文档](https://v2.hysteria.network/zh/)
|
||||
### 示例配置
|
||||
```
|
||||
v2board:
|
||||
apiHost: https://面板地址
|
||||
apiKey: 面板节点密钥
|
||||
nodeID: 节点ID
|
||||
tls:
|
||||
type: tls
|
||||
cert: /etc/hysteria/tls.crt
|
||||
key: /etc/hysteria/tls.key
|
||||
auth:
|
||||
type: v2board
|
||||
trafficStats:
|
||||
listen: 127.0.0.1:7653
|
||||
acl:
|
||||
inline:
|
||||
- reject(10.0.0.0/8)
|
||||
- reject(172.16.0.0/12)
|
||||
- reject(192.168.0.0/16)
|
||||
- reject(127.0.0.0/8)
|
||||
- reject(fc00::/7)
|
||||
```
|
||||
> 其他配置完全与hysteria文档的一致,可以查看hysteria2官方文档 [点击查看](https://hysteria.network/zh/docs/getting-started/Installation/)
|
||||
|
||||
### [Hysteria 1.x (legacy)](https://v1.hysteria.network/)
|
||||
|
||||
---
|
||||
|
||||
<div class="feature-grid">
|
||||
<div>
|
||||
<h3>🛠️ Jack of all trades</h3>
|
||||
<p>Wide range of modes including SOCKS5, HTTP Proxy, TCP/UDP Forwarding, Linux TProxy, TUN - with more features being added constantly.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>⚡ Blazing fast</h3>
|
||||
<p>Powered by a customized QUIC protocol, Hysteria is designed to deliver unparalleled performance over unreliable and lossy networks.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>✊ Censorship resistant</h3>
|
||||
<p>The protocol masquerades as standard HTTP/3 traffic, making it very difficult for censors to detect and block without widespread collateral damage.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>💻 Cross-platform</h3>
|
||||
<p>We have builds for every major platform and architecture. Deploy anywhere & use everywhere. Not to mention the long list of 3rd party apps.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>🔗 Easy integration</h3>
|
||||
<p>With built-in support for custom authentication, traffic statistics & access control, Hysteria is easy to integrate into your infrastructure.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>🤗 Chill and supportive</h3>
|
||||
<p>We have well-documented specifications and code for developers to contribute and/or build their own apps. And a helpful community, too.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
**If you find Hysteria useful, consider giving it a ⭐️!**
|
||||
|
||||
[](https://star-history.com/#apernet/hysteria&Date)
|
||||
### 快速启动
|
||||
```
|
||||
docker run -itd --restart=always --network=host \
|
||||
-e apiHost=https://example.com \
|
||||
-e apiKey=xxxxxxxxxxxxxxxxxxxxx \
|
||||
-e domain=hy2.example.com \
|
||||
-e nodeID=1 \
|
||||
ghcr.io/cedar2025/hysteria:latest
|
||||
```
|
||||
### docker 仓库
|
||||
```
|
||||
docker pull ghcr.io/cedar2025/hysteria:latest
|
||||
```
|
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@ -54,6 +55,7 @@ func init() {
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
V2board *v2boardConfig `mapstructure:"v2board"`
|
||||
Listen string `mapstructure:"listen"`
|
||||
Obfs serverConfigObfs `mapstructure:"obfs"`
|
||||
TLS *serverConfigTLS `mapstructure:"tls"`
|
||||
@ -73,6 +75,14 @@ type serverConfig struct {
|
||||
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
||||
}
|
||||
|
||||
type v2boardConfig struct {
|
||||
ApiHost string `mapstructure:"apiHost"`
|
||||
ApiKey string `mapstructure:"apiKey"`
|
||||
NodeID uint `mapstructure:"nodeID"`
|
||||
PullInterval time.Duration `mapstructure:"pullInterval"`
|
||||
PushInterval time.Duration `mapstructure:"pushInterval"`
|
||||
}
|
||||
|
||||
type serverConfigObfsSalamander struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
@ -764,6 +774,14 @@ func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
|
||||
return configError{Field: "auth.command", Err: errors.New("empty auth command")}
|
||||
}
|
||||
hyConfig.Authenticator = &auth.CommandAuthenticator{Cmd: c.Auth.Command}
|
||||
return nil
|
||||
case "v2board":
|
||||
v2boardConfig := c.V2board
|
||||
if v2boardConfig.ApiHost == "" || v2boardConfig.ApiKey == "" || v2boardConfig.NodeID == 0 {
|
||||
return configError{Field: "auth.v2board", Err: errors.New("v2board config error")}
|
||||
}
|
||||
hyConfig.Authenticator = &auth.V2boardApiProvider{URL: fmt.Sprintf("%s?token=%s&node_id=%d&node_type=hysteria", c.V2board.ApiHost+"/api/v1/server/UniProxy/user", c.V2board.ApiKey, c.V2board.NodeID)}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
||||
@ -776,11 +794,28 @@ func (c *serverConfig) fillEventLogger(hyConfig *server.Config) error {
|
||||
}
|
||||
|
||||
func (c *serverConfig) fillTrafficLogger(hyConfig *server.Config) error {
|
||||
pullInterval := time.Second * 5
|
||||
if c.V2board.PullInterval > 0 {
|
||||
pullInterval = time.Duration(c.V2board.PullInterval) * time.Second
|
||||
}
|
||||
pushInterval := time.Second * 60
|
||||
if c.V2board.PushInterval > 0 {
|
||||
pushInterval = time.Duration(c.V2board.PushInterval) * time.Second
|
||||
}
|
||||
userURL := fmt.Sprintf("%s?token=%s&node_id=%d&node_type=hysteria", c.V2board.ApiHost+"/api/v1/server/UniProxy/user", c.V2board.ApiKey, c.V2board.NodeID)
|
||||
pushURL := fmt.Sprintf("%s?token=%s&node_id=%d&node_type=hysteria", c.V2board.ApiHost+"/api/v1/server/UniProxy/push", c.V2board.ApiKey, c.V2board.NodeID)
|
||||
if c.TrafficStats.Listen != "" {
|
||||
tss := trafficlogger.NewTrafficStatsServer(c.TrafficStats.Secret)
|
||||
hyConfig.TrafficLogger = tss
|
||||
if c.V2board != nil && c.V2board.ApiHost != "" {
|
||||
go auth.UpdateUsers(userURL, pullInterval, hyConfig.TrafficLogger)
|
||||
go hyConfig.TrafficLogger.PushTrafficToV2boardInterval(pushURL, pushInterval)
|
||||
}
|
||||
go runTrafficStatsServer(c.TrafficStats.Listen, tss)
|
||||
} else {
|
||||
go auth.UpdateUsers(userURL, pullInterval, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -894,6 +929,19 @@ func (c *serverConfig) Config() (*server.Config, error) {
|
||||
return hyConfig, nil
|
||||
}
|
||||
|
||||
type ResponseNodeInfo struct {
|
||||
Host string `json:"host"`
|
||||
ServerPort uint `json:"server_port"`
|
||||
ServerName string `json:"server_name"`
|
||||
UpMbps uint `json:"down_mbps"`
|
||||
DownMbps uint `json:"up_mbps"`
|
||||
Obfs string `json:"obfs"`
|
||||
BaseConfig struct {
|
||||
PushInterval int `json:"push_interval"`
|
||||
PullInterval int `json:"pull_interval"`
|
||||
} `json:"base_config"`
|
||||
}
|
||||
|
||||
func runServer(cmd *cobra.Command, args []string) {
|
||||
logger.Info("server mode")
|
||||
|
||||
@ -904,6 +952,48 @@ func runServer(cmd *cobra.Command, args []string) {
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
logger.Fatal("failed to parse server config", zap.Error(err))
|
||||
}
|
||||
// 如果配置了v2board 则自动获取监听端口、obfs
|
||||
if config.V2board != nil && config.V2board.ApiHost != "" {
|
||||
// 创建一个url.Values来存储查询参数
|
||||
queryParams := url.Values{
|
||||
"token": {config.V2board.ApiKey},
|
||||
"node_id": {strconv.Itoa(int(config.V2board.NodeID))},
|
||||
"node_type": {"hysteria"},
|
||||
}
|
||||
nodeInfoUrl := config.V2board.ApiHost + "/api/v1/server/UniProxy/config?" + queryParams.Encode()
|
||||
resp, err := http.Get(nodeInfoUrl)
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
fmt.Println("HTTP GET 请求出错:", err)
|
||||
logger.Fatal("failed to client v2board api to get nodeInfo", zap.Error(err))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 读取响应数据
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to read v2board reaponse", zap.Error(err))
|
||||
}
|
||||
// 解析JSON数据
|
||||
var responseNodeInfo ResponseNodeInfo
|
||||
err = json.Unmarshal(body, &responseNodeInfo)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to unmarshal v2board reaponse", zap.Error(err))
|
||||
}
|
||||
// 给 hy的端口、obfs、上行下行进行赋值
|
||||
if responseNodeInfo.ServerPort != 0 {
|
||||
config.Listen = ":" + strconv.Itoa(int(responseNodeInfo.ServerPort))
|
||||
}
|
||||
if responseNodeInfo.DownMbps != 0 {
|
||||
config.Bandwidth.Down = strconv.Itoa(int(responseNodeInfo.DownMbps)) + "Mbps"
|
||||
}
|
||||
if responseNodeInfo.UpMbps != 0 {
|
||||
config.Bandwidth.Up = strconv.Itoa(int(responseNodeInfo.UpMbps)) + "Mbps"
|
||||
}
|
||||
if responseNodeInfo.Obfs != "" {
|
||||
config.Obfs.Type = "salamander"
|
||||
config.Obfs.Salamander.Password = responseNodeInfo.Obfs
|
||||
}
|
||||
}
|
||||
hyConfig, err := config.Config()
|
||||
if err != nil {
|
||||
logger.Fatal("failed to load server config", zap.Error(err))
|
||||
|
@ -211,5 +211,7 @@ type EventLogger interface {
|
||||
// The implementation of this interface must be thread-safe.
|
||||
type TrafficLogger interface {
|
||||
LogTraffic(id string, tx, rx uint64) (ok bool)
|
||||
PushTrafficToV2boardInterval(url string, interval time.Duration)
|
||||
LogOnlineState(id string, online bool)
|
||||
NewKick(id string) (ok bool)
|
||||
}
|
||||
|
50
entrypoint
Normal file
50
entrypoint
Normal file
@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
|
||||
CONFIG_FILE="/etc/hysteria/server.yaml"
|
||||
|
||||
# 判断配置文件是否存存在,如果不存在走不存在的逻辑
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Creating configuration file $CONFIG_FILE"
|
||||
mkdir -p /etc/hysteria
|
||||
cat <<EOF >"$CONFIG_FILE"
|
||||
v2board:
|
||||
apiHost: ${apiHost}
|
||||
apiKey: ${apiKey}
|
||||
nodeID: ${nodeID}
|
||||
acme:
|
||||
domains:
|
||||
- ${domain}
|
||||
email: your@email.com
|
||||
auth:
|
||||
type: v2board
|
||||
trafficStats:
|
||||
listen: 127.0.0.1:7653
|
||||
acl:
|
||||
inline:
|
||||
- reject(10.0.0.0/8)
|
||||
- reject(172.16.0.0/12)
|
||||
- reject(192.168.0.0/16)
|
||||
- reject(127.0.0.0/8)
|
||||
- reject(fc00::/7)
|
||||
EOF
|
||||
fi
|
||||
|
||||
hysteria server -c $CONFIG_FILE 2>&1 | tee &
|
||||
|
||||
# 获取HYSTERIA server命令的进程组ID(Process Group ID)
|
||||
HYSTERIA_PID=$!
|
||||
|
||||
# 定义一个函数来处理Ctrl+C信号
|
||||
cleanup() {
|
||||
echo "接收到Ctrl+C信号,正在停止HYSTERIA..."
|
||||
# 向HYSTERIA进程组发送终止信号
|
||||
kill -SIGINT $HYSTERIA_PID
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 捕获Ctrl+C信号并调用cleanup函数
|
||||
trap cleanup INT
|
||||
trap cleanup SIGTERM
|
||||
|
||||
# 等待HYSTERIA进程结束
|
||||
wait
|
110
extras/auth/v2board.go
Normal file
110
extras/auth/v2board.go
Normal file
@ -0,0 +1,110 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &V2boardApiProvider{}
|
||||
|
||||
type V2boardApiProvider struct {
|
||||
Client *http.Client
|
||||
URL string
|
||||
}
|
||||
|
||||
// 用户列表
|
||||
var (
|
||||
usersMap map[string]User
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
SpeedLimit *uint32 `json:"speed_limit"`
|
||||
}
|
||||
type ResponseData struct {
|
||||
Users []User `json:"users"`
|
||||
}
|
||||
|
||||
func getUserList(url string) ([]User, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var responseData ResponseData
|
||||
err = json.NewDecoder(resp.Body).Decode(&responseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responseData.Users, nil
|
||||
}
|
||||
|
||||
func UpdateUsers(url string, interval time.Duration, trafficlogger server.TrafficLogger) {
|
||||
fmt.Println("用户列表自动更新服务已激活,更新周期为", interval)
|
||||
|
||||
// 先立即执行一次更新
|
||||
userList, err := getUserList(url)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return // 如果第一次获取失败,退出函数
|
||||
}
|
||||
processUserList(userList, trafficlogger)
|
||||
|
||||
// 设置定时器
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
userList, err := getUserList(url)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
continue
|
||||
}
|
||||
processUserList(userList, trafficlogger)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理用户列表的逻辑
|
||||
func processUserList(userList []User, trafficlogger server.TrafficLogger) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
newUsersMap := make(map[string]User)
|
||||
for _, user := range userList {
|
||||
newUsersMap[user.UUID] = user
|
||||
}
|
||||
if trafficlogger != nil {
|
||||
for uuid := range usersMap {
|
||||
if _, exists := newUsersMap[uuid]; !exists {
|
||||
fmt.Println(usersMap[uuid].ID)
|
||||
trafficlogger.NewKick(strconv.Itoa(usersMap[uuid].ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usersMap = newUsersMap
|
||||
}
|
||||
|
||||
// 验证代码
|
||||
func (v *V2boardApiProvider) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
|
||||
|
||||
// 获取判断连接用户是否在用户列表内
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if user, exists := usersMap[auth]; exists {
|
||||
return true, strconv.Itoa(user.ID)
|
||||
}
|
||||
return false, ""
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package trafficlogger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
)
|
||||
@ -29,6 +33,57 @@ func NewTrafficStatsServer(secret string) TrafficStatsServer {
|
||||
}
|
||||
}
|
||||
|
||||
type TrafficPushRequest struct {
|
||||
Data map[string][2]int64
|
||||
}
|
||||
|
||||
// 定时提交用户流量情况
|
||||
func (s *trafficStatsServerImpl) PushTrafficToV2boardInterval(url string, interval time.Duration) {
|
||||
fmt.Println("用户流量情况监控已启动,提交周期为:", interval)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
if err := s.PushTrafficToV2board(url); err != nil {
|
||||
fmt.Println("用户流量信息提交失败:", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 向v2board 提交用户流量使用情况
|
||||
func (s *trafficStatsServerImpl) PushTrafficToV2board(url string) error {
|
||||
s.Mutex.Lock() // 写锁,阻止其他操作 StatsMap 的并发访问
|
||||
defer s.Mutex.Unlock() // 确保在函数退出时释放写锁
|
||||
|
||||
request := TrafficPushRequest{
|
||||
Data: make(map[string][2]int64),
|
||||
}
|
||||
for id, stats := range s.StatsMap {
|
||||
request.Data[id] = [2]int64{int64(stats.Tx), int64(stats.Rx)}
|
||||
}
|
||||
if len(request.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
jsonData, err := json.Marshal(request.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
fmt.Println(resp)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("HTTP request failed with status code: " + resp.Status)
|
||||
}
|
||||
s.StatsMap = make(map[string]*trafficStatsEntry)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type trafficStatsServerImpl struct {
|
||||
Mutex sync.RWMutex
|
||||
StatsMap map[string]*trafficStatsEntry
|
||||
@ -152,3 +207,11 @@ func (s *trafficStatsServerImpl) kick(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// 踢出用户名单
|
||||
func (s *trafficStatsServerImpl) NewKick(id string) bool {
|
||||
s.Mutex.Lock()
|
||||
s.KickMap[id] = struct{}{}
|
||||
s.Mutex.Unlock()
|
||||
return true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user