init commit
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
PORT=8080
|
||||||
|
SECRET=testing123
|
||||||
|
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_DATABASE=app
|
||||||
|
DB_USERNAME=proxy
|
||||||
|
DB_PASSWORD=proxy
|
||||||
|
DB_TIMEZONE=Asia/Shanghai
|
||||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
### Go template
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
bin/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
### editor
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./bin/proxy_linux_amd64 /app/proxy
|
||||||
|
RUN chmod +x /app/proxy
|
||||||
|
|
||||||
|
EXPOSE $PORT
|
||||||
|
|
||||||
|
CMD ["/app/proxy"]
|
||||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## todo
|
||||||
|
|
||||||
|
docker 脚本和环境变量文件都放在 scripts 目录下
|
||||||
|
|
||||||
|
client 配置环境变量(或者命令行参数)
|
||||||
|
|
||||||
|
测试流程完成后合并项目
|
||||||
|
|
||||||
|
考虑是否需要将 devices 表改名成 nodes
|
||||||
|
|
||||||
|
### 长期
|
||||||
|
|
||||||
|
考虑一下连接安全性
|
||||||
|
|
||||||
|
内部接口 http2 或者 protobuf
|
||||||
|
|
||||||
|
考虑重构服务,集成 socks 解析,鉴权,转发
|
||||||
|
|
||||||
|
## 开发相关
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
新增环境变量时,需要确保两个位置正确传递了变量
|
||||||
|
|
||||||
|
1. 系统环境或本地 `.env` 文件正确配置了变量值
|
||||||
|
2. `docker-compose.yml` 文件中给服务容器正确传递了变量
|
||||||
|
|
||||||
|
### 更新测试环境
|
||||||
|
|
||||||
|
1. 构建项目
|
||||||
|
2. 使用测试配置远程启动 docker
|
||||||
|
|
||||||
|
## 转发服务
|
||||||
31
gen.go
Normal file
31
gen.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gen"
|
||||||
|
"proxy-server/server/orm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Host = "localhost"
|
||||||
|
Port = "5432"
|
||||||
|
Username = "gorm"
|
||||||
|
Password = "gorm"
|
||||||
|
Database = "gorm"
|
||||||
|
Timezone = "Asia/Shanghai"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := gen.NewGenerator(gen.Config{
|
||||||
|
OutPath: "../../temp-out",
|
||||||
|
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
|
||||||
|
})
|
||||||
|
|
||||||
|
orm.Init()
|
||||||
|
g.UseDB(orm.DB)
|
||||||
|
|
||||||
|
g.ApplyBasic(
|
||||||
|
g.GenerateAllTable()...,
|
||||||
|
)
|
||||||
|
|
||||||
|
g.Execute()
|
||||||
|
}
|
||||||
59
go.mod
Normal file
59
go.mod
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
module proxy-server
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/google/gopacket v1.1.19
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lmittmann/tint v1.0.7
|
||||||
|
github.com/mattn/go-colorable v0.1.14
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
gorm.io/driver/postgres v1.5.11
|
||||||
|
gorm.io/gen v0.3.26
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.12.8 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
|
||||||
|
gorm.io/driver/mysql v1.4.4 // indirect
|
||||||
|
gorm.io/hints v1.1.0 // indirect
|
||||||
|
gorm.io/plugin/dbresolver v1.5.0 // indirect
|
||||||
|
)
|
||||||
170
go.sum
Normal file
170
go.sum
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||||
|
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
|
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||||
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||||
|
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
|
||||||
|
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
|
||||||
|
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||||
|
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c h1:jWdr7cHgl8c/ua5vYbR2WhSp+NQmzhsj0xoY3foTzW8=
|
||||||
|
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c/go.mod h1:SH2K9R+2RMjuX1CkCONrPwoe9JzVv2hkQvEu4bXGojE=
|
||||||
|
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||||
|
gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ=
|
||||||
|
gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM=
|
||||||
|
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||||
|
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
|
gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
|
||||||
|
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||||
|
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||||
|
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
|
||||||
|
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
|
||||||
|
gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY=
|
||||||
|
gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE=
|
||||||
|
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
|
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
|
gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw=
|
||||||
|
gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y=
|
||||||
|
gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY=
|
||||||
|
gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
9
main.go
Normal file
9
main.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"proxy-server/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server.Start()
|
||||||
|
}
|
||||||
21
pkg/resp/resp.go
Normal file
21
pkg/resp/resp.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Error bool
|
||||||
|
Cause string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Done(data interface{}) *Data {
|
||||||
|
return &Data{
|
||||||
|
Error: false,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fail(cause string) *Data {
|
||||||
|
return &Data{
|
||||||
|
Error: true,
|
||||||
|
Cause: cause,
|
||||||
|
}
|
||||||
|
}
|
||||||
42
scripts/build.ps1
Normal file
42
scripts/build.ps1
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
Remove-Job *
|
||||||
|
|
||||||
|
$tasks = 0
|
||||||
|
$start = Get-Date
|
||||||
|
|
||||||
|
$tasks++
|
||||||
|
Write-Output "building proxy for windows amd64..."
|
||||||
|
Start-Job -ScriptBlock {
|
||||||
|
$env:GOOS = "windows"; $env:GOARCH = "amd64"; go build -ldflags '-w -s' -o bin/proxy_win_amd64.exe main.go
|
||||||
|
} | Out-Null
|
||||||
|
|
||||||
|
$tasks++
|
||||||
|
Write-Output "building proxy for linux amd64..."
|
||||||
|
Start-Job -ScriptBlock {
|
||||||
|
$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -ldflags '-w -s' -o bin/proxy_linux_amd64 main.go
|
||||||
|
} | Out-Null
|
||||||
|
|
||||||
|
$tasks++
|
||||||
|
Write-Output "building proxy for linux arm64..."
|
||||||
|
Start-Job -ScriptBlock {
|
||||||
|
$env:GOOS = "linux"; $env:GOARCH = "arm64"; go build -ldflags '-w -s' -o bin/proxy_linux_arm64 main.go
|
||||||
|
} | Out-Null
|
||||||
|
|
||||||
|
# Wait for all jobs to complete
|
||||||
|
while ($tasks -gt 0)
|
||||||
|
{
|
||||||
|
foreach ($job in Get-Job)
|
||||||
|
{
|
||||||
|
if ($job.State -eq "Completed")
|
||||||
|
{
|
||||||
|
$tasks--
|
||||||
|
$job | Receive-Job
|
||||||
|
$job | Remove-Job
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$end = Get-Date
|
||||||
|
|
||||||
|
Write-Output "build completed"
|
||||||
|
Write-Output "time taken: $( ($end - $start).TotalSeconds ) seconds"
|
||||||
|
Write-Output "output files are in ./bin/"
|
||||||
38
scripts/dev/docker-compose.yaml
Normal file
38
scripts/dev/docker-compose.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: proxy-server
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
frp:
|
||||||
|
container_name: proxy-server-dev-frp
|
||||||
|
build:
|
||||||
|
context: ./frp
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "18080:18080"
|
||||||
|
- "20000-20100:20000-20100"
|
||||||
|
networks:
|
||||||
|
- proxy-server-dev
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
container_name: proxy-server-dev-postgres
|
||||||
|
image: postgres:17
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: $DB_DATABASE
|
||||||
|
POSTGRES_USER: $DB_USERNAME
|
||||||
|
POSTGRES_PASSWORD: $DB_PASSWORD
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- proxy-server-dev
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy-server-dev:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
name: proxy-server-dev-postgres-data
|
||||||
10
scripts/dev/frp/Dockerfile
Normal file
10
scripts/dev/frp/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY frps frps
|
||||||
|
COPY frps.toml frps.toml
|
||||||
|
|
||||||
|
EXPOSE 18080
|
||||||
|
|
||||||
|
CMD ["./frps", "-c", "frps.toml"]
|
||||||
BIN
scripts/dev/frp/frps
Normal file
BIN
scripts/dev/frp/frps
Normal file
Binary file not shown.
14
scripts/dev/frp/frps.toml
Normal file
14
scripts/dev/frp/frps.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
bindPort = 18080
|
||||||
|
transport.tcpMux = true
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "chan-req"
|
||||||
|
addr = "host.docker.internal:8080"
|
||||||
|
path = "/chan/request"
|
||||||
|
ops = ["NewUserConn"]
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "chan-test"
|
||||||
|
addr = "host.docker.internal:8080"
|
||||||
|
path = "/chan/test"
|
||||||
|
ops = ["NewProxy", "NewWorkConn"]
|
||||||
73
scripts/sql/init.sql
Normal file
73
scripts/sql/init.sql
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
-- nodes
|
||||||
|
DROP TABLE IF EXISTS nodes;
|
||||||
|
CREATE TABLE nodes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
provider VARCHAR(255) NOT NULL,
|
||||||
|
location VARCHAR(255) NOT NULL,
|
||||||
|
ip_address VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX devices_provider_index ON nodes (provider);
|
||||||
|
CREATE INDEX devices_location_index ON nodes (location);
|
||||||
|
|
||||||
|
-- users
|
||||||
|
DROP TABLE IF EXISTS users;
|
||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
phone VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- user_ips
|
||||||
|
DROP TABLE IF EXISTS user_ips;
|
||||||
|
CREATE TABLE user_ips (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id int NOT NULL REFERENCES users (id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
ip_address VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX user_ips_user_id_index ON user_ips (user_id);
|
||||||
|
CREATE INDEX user_ips_ip_address_index ON user_ips (ip_address);
|
||||||
|
|
||||||
|
-- channel
|
||||||
|
DROP TABLE IF EXISTS channels;
|
||||||
|
CREATE TABLE channels (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id int NOT NULL REFERENCES users (id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
node_id int NOT NULL REFERENCES nodes (id) --
|
||||||
|
ON UPDATE CASCADE --
|
||||||
|
ON DELETE SET NULL, -- 节点删除后,用户侧需要保留提取记录
|
||||||
|
forward_snapshot VARCHAR(255),
|
||||||
|
protocol VARCHAR(255),
|
||||||
|
auth_ip bool,
|
||||||
|
auth_pass bool,
|
||||||
|
username VARCHAR(255),
|
||||||
|
password VARCHAR(255),
|
||||||
|
expiration TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX channel_user_id_index ON channels (user_id);
|
||||||
|
CREATE INDEX channel_node_id_index ON channels (node_id);
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- 填充数据
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
|
||||||
4
scripts/sql/临时迁移记录.txt
Normal file
4
scripts/sql/临时迁移记录.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
====================
|
||||||
|
删除 channels.target_snapshot 不需要目标地址快照
|
||||||
|
添加 channels.auth_ip 是否验证白名单
|
||||||
|
添加 channels.auth_pass 是否验证密码
|
||||||
59
scripts/test/docker-compose.yaml
Normal file
59
scripts/test/docker-compose.yaml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: proxy-server
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
frp:
|
||||||
|
container_name: proxy-server-dev-frp
|
||||||
|
build:
|
||||||
|
context: ./frp
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "18080:18080"
|
||||||
|
- "20000-20100:20000-20100"
|
||||||
|
networks:
|
||||||
|
- proxy-server-test
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
container_name: proxy-server-dev-postgres
|
||||||
|
image: postgres:17
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: $DB_DATABASE
|
||||||
|
POSTGRES_USER: $DB_USERNAME
|
||||||
|
POSTGRES_PASSWORD: $DB_PASSWORD
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- proxy-server-test
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
service:
|
||||||
|
container_name: proxy-server-dev-service
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
PORT: $PORT
|
||||||
|
DB_HOST: $DB_HOST
|
||||||
|
DB_PORT: $DB_PORT
|
||||||
|
DB_DATABASE: $DB_DATABASE
|
||||||
|
DB_USERNAME: $DB_USERNAME
|
||||||
|
DB_PASSWORD: $DB_PASSWORD
|
||||||
|
DB_TIMEZONE: $DB_TIMEZONE
|
||||||
|
ports:
|
||||||
|
- "${PORT}:${PORT}"
|
||||||
|
networks:
|
||||||
|
- proxy-server-test
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy-server-test:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
name: proxy-server-test-postgres-data
|
||||||
10
scripts/test/frp/Dockerfile
Normal file
10
scripts/test/frp/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY frps frps
|
||||||
|
COPY frps.toml frps.toml
|
||||||
|
|
||||||
|
EXPOSE 18080
|
||||||
|
|
||||||
|
CMD ["./frps", "-c", "frps.toml"]
|
||||||
BIN
scripts/test/frp/frps
Normal file
BIN
scripts/test/frp/frps
Normal file
Binary file not shown.
8
scripts/test/frp/frps.toml
Normal file
8
scripts/test/frp/frps.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
bindPort = 18080
|
||||||
|
transport.tcpMux = true
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "chan-req"
|
||||||
|
addr = "service:8080"
|
||||||
|
path = "/chan/request"
|
||||||
|
ops = ["NewUserConn"]
|
||||||
61
server/monitor/monitor.go
Normal file
61
server/monitor/monitor.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(ctx context.Context, errCh chan error) {
|
||||||
|
|
||||||
|
// 打开一个网络接口
|
||||||
|
device, err := pcap.OpenLive("WLAN", 1600, true, pcap.BlockForever)
|
||||||
|
if err != nil {
|
||||||
|
b, er := hex.DecodeString("\\xbb")
|
||||||
|
slog.Debug("b", b, er)
|
||||||
|
errCh <- errors.Wrap(err, "打开网络接口失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer device.Close()
|
||||||
|
|
||||||
|
err = device.SetBPFFilter("tcp")
|
||||||
|
if err != nil {
|
||||||
|
errCh <- errors.Wrap(err, "设置 BPF 过滤器失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = device.SetDirection(pcap.DirectionIn)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- errors.Wrap(err, "设置捕获方向失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个数据包源
|
||||||
|
source := gopacket.NewPacketSource(device, device.LinkType())
|
||||||
|
source.NoCopy = true
|
||||||
|
source.Lazy = true
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
slog.Debug("monitor 被动结束")
|
||||||
|
errCh <- nil
|
||||||
|
return
|
||||||
|
|
||||||
|
case packet := <-source.Packets():
|
||||||
|
handle(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(packet gopacket.Packet) {
|
||||||
|
slog.Debug("Packet: ", packet)
|
||||||
|
slog.Debug("Layers: ", packet.Layers())
|
||||||
|
slog.Debug("Application: ", packet.ApplicationLayer())
|
||||||
|
slog.Debug("Transport: ", packet.TransportLayer())
|
||||||
|
slog.Debug("Network: ", packet.NetworkLayer())
|
||||||
|
slog.Debug("Link: ", packet.LinkLayer())
|
||||||
|
}
|
||||||
34
server/orm/orm.go
Normal file
34
server/orm/orm.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
Host := os.Getenv("DB_HOST")
|
||||||
|
Port := os.Getenv("DB_PORT")
|
||||||
|
Database := os.Getenv("DB_DATABASE")
|
||||||
|
Username := os.Getenv("DB_USERNAME")
|
||||||
|
Password := os.Getenv("DB_PASSWORD")
|
||||||
|
Timezone := os.Getenv("DB_TIMEZONE")
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable TimeZone=%s",
|
||||||
|
Host, Port, Username, Password, Database, Timezone,
|
||||||
|
)
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
|
Logger: logger.Default,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
DB = db
|
||||||
|
}
|
||||||
71
server/service.go
Normal file
71
server/service.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/lmittmann/tint"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"proxy-server/server/orm"
|
||||||
|
"proxy-server/server/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("服务由于意外的 panic 导致退出", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 初始化环境变量
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("没有本地环境变量文件")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置日志
|
||||||
|
writer := colorable.NewColorable(os.Stdout)
|
||||||
|
logger := slog.New(tint.NewHandler(writer, &tint.Options{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
|
||||||
|
err, ok := attr.Value.Any().(error)
|
||||||
|
if !ok {
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
return tint.Err(err)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
// 初始化公共组件
|
||||||
|
orm.Init()
|
||||||
|
|
||||||
|
// 启动子服务
|
||||||
|
goCount := 1
|
||||||
|
errChan := make(chan error, goCount)
|
||||||
|
ctxC, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go web.Start(ctxC, errChan)
|
||||||
|
//go monitor.Start(ctxC, errChan)
|
||||||
|
slog.Info("服务启动成功")
|
||||||
|
|
||||||
|
// 监听异常
|
||||||
|
well := true
|
||||||
|
for i := 0; i < goCount; i++ {
|
||||||
|
err := <-errChan
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("服务异常退出", err)
|
||||||
|
if well { // 第一次出错时取消其他服务
|
||||||
|
well = false
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(errChan)
|
||||||
|
slog.Info("服务已全部退出")
|
||||||
|
}
|
||||||
166
server/web/app/handlers/channel.go
Normal file
166
server/web/app/handlers/channel.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"log/slog"
|
||||||
|
"proxy-server/pkg/resp"
|
||||||
|
"proxy-server/server/orm"
|
||||||
|
"proxy-server/server/web/app/models"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// region frp 接口
|
||||||
|
|
||||||
|
type FrpData struct {
|
||||||
|
Reject bool
|
||||||
|
RejectReason string
|
||||||
|
Unchange bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChanRequest(c *gin.Context) {
|
||||||
|
type Body struct {
|
||||||
|
Content struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
ProxyType string `json:"proxy_type"`
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
User interface{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
op := c.Query("op")
|
||||||
|
if op != "NewUserConn" {
|
||||||
|
_ = c.Error(errors.New("不支持的操作"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.GetHeader("X-Frp-Reqid")
|
||||||
|
if id == "" {
|
||||||
|
_ = c.Error(errors.New("请求头中缺少 X-Frp-Reqid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body Body
|
||||||
|
err := c.ShouldBindJSON(&body)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "解析请求正文失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := body.Content
|
||||||
|
|
||||||
|
// 检查此 ip 是否有权限访问目标 node
|
||||||
|
clientIp := strings.Split(content.RemoteAddr, ":")[0]
|
||||||
|
targetNode := content.ProxyName
|
||||||
|
slog.Debug(id + " 客户端 " + clientIp + " 请求连接到 " + targetNode)
|
||||||
|
|
||||||
|
var channels []models.Channel
|
||||||
|
err = orm.DB.
|
||||||
|
Joins("INNER JOIN public.nodes n ON channels.node_id = n.id AND n.name = ?", targetNode).
|
||||||
|
Joins("INNER JOIN public.users u ON channels.user_id = u.id").
|
||||||
|
Joins("INNER JOIN public.user_ips ip ON u.id = ip.user_id AND ip.ip_address = ?", clientIp).
|
||||||
|
Find(&channels).Error
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "查询用户权限失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回响应
|
||||||
|
rsCount := len(channels)
|
||||||
|
if rsCount > 1 {
|
||||||
|
slog.Warn(clientIp + " + " + targetNode + "的组合有多个权限结果,这是不应当存在的")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsCount == 0 {
|
||||||
|
slog.Debug(id + " 没有权限")
|
||||||
|
reject(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel := channels[0]
|
||||||
|
if channel.Expiration.Before(time.Now()) {
|
||||||
|
slog.Debug(id + " 权限已过期")
|
||||||
|
reject(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug(id + " 通过验证")
|
||||||
|
confirm(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChanTest(c *gin.Context) {
|
||||||
|
var body map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&body)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("解析请求正文失败", err)
|
||||||
|
}
|
||||||
|
for k, v := range body {
|
||||||
|
slog.Debug("map", "key: ", k, " value: ", v)
|
||||||
|
}
|
||||||
|
confirm(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirm(c *gin.Context) {
|
||||||
|
c.JSON(200, FrpData{
|
||||||
|
Reject: false,
|
||||||
|
Unchange: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func reject(c *gin.Context) {
|
||||||
|
c.JSON(401, FrpData{
|
||||||
|
Reject: true,
|
||||||
|
RejectReason: "客户端没有权限访问该节点",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
func ChanAuth(c *gin.Context) {
|
||||||
|
type Body struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
type Data struct {
|
||||||
|
Timeout uint64 `json:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var body Body
|
||||||
|
err := c.ShouldBindJSON(&body)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.JSON(400, resp.Fail("请求参数错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找通道
|
||||||
|
var result *models.Channel
|
||||||
|
orm.DB.
|
||||||
|
Model(&models.Channel{}).
|
||||||
|
Where(&models.Channel{
|
||||||
|
Username: body.Username,
|
||||||
|
Password: body.Password,
|
||||||
|
}).
|
||||||
|
First(&result)
|
||||||
|
if result == nil {
|
||||||
|
_ = c.Error(errors.New("用户信息不存在"))
|
||||||
|
c.JSON(401, resp.Fail("账号密码错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证账号密码 todo 哈希密码验证
|
||||||
|
if result.Username != body.Username || result.Password != body.Password {
|
||||||
|
_ = c.Error(errors.New("账号密码错误"))
|
||||||
|
c.JSON(401, resp.Fail("账号密码错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算到期时间
|
||||||
|
timeout := result.Expiration.Sub(time.Now())
|
||||||
|
|
||||||
|
// todo 保存会话 对于大量短连接的情况,考虑如何保存连接会话信息
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
c.JSON(200, resp.Done(Data{
|
||||||
|
Timeout: uint64(timeout.Seconds()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
115
server/web/app/handlers/node.go
Normal file
115
server/web/app/handlers/node.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"os"
|
||||||
|
"proxy-server/server/orm"
|
||||||
|
"proxy-server/server/web/app/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodeRegisterReq struct {
|
||||||
|
Name string
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NodeRegister(c *gin.Context) {
|
||||||
|
|
||||||
|
// 请求参数
|
||||||
|
var req NodeRegisterReq
|
||||||
|
err := c.ShouldBind(&req)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "参数解析错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 secret
|
||||||
|
secret := os.Getenv("SECRET")
|
||||||
|
if req.Secret != secret {
|
||||||
|
_ = c.Error(errors.New("拒绝连接"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册节点
|
||||||
|
// todo 查询运营商和地区
|
||||||
|
err = orm.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
// 查询节点是否已存在
|
||||||
|
var count int64
|
||||||
|
err := orm.DB.Where(&models.Node{
|
||||||
|
Name: req.Name,
|
||||||
|
}).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不存在则注册
|
||||||
|
if count == 0 {
|
||||||
|
ipAddress := c.ClientIP()
|
||||||
|
node := models.Node{
|
||||||
|
Name: req.Name,
|
||||||
|
Provider: "",
|
||||||
|
Location: "",
|
||||||
|
IPAddress: ipAddress,
|
||||||
|
}
|
||||||
|
err = orm.DB.Create(&node).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "注册节点失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeReportReq struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NodeReport(c *gin.Context) {
|
||||||
|
|
||||||
|
// 请求参数
|
||||||
|
var req NodeReportReq
|
||||||
|
err := c.ShouldBind(&req)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "参数解析错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报节点信息
|
||||||
|
err = orm.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
// 查询节点
|
||||||
|
var node models.Node
|
||||||
|
err = orm.DB.Where(&models.Node{
|
||||||
|
Name: req.Name,
|
||||||
|
}).First(&node).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新节点信息
|
||||||
|
ipAddress := c.ClientIP()
|
||||||
|
if ipAddress != node.IPAddress {
|
||||||
|
err = orm.DB.Model(&node).Update("ip_address", ipAddress).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Wrap(err, "上报节点信息失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(200)
|
||||||
|
}
|
||||||
1
server/web/app/handlers/user.go
Normal file
1
server/web/app/handlers/user.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package handlers
|
||||||
19
server/web/app/models/channel.go
Normal file
19
server/web/app/models/channel.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Channel 连接认证模型
|
||||||
|
type Channel struct {
|
||||||
|
gorm.Model
|
||||||
|
UserId uint
|
||||||
|
NodeId uint
|
||||||
|
Protocol string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
AuthIp bool
|
||||||
|
AuthPass bool
|
||||||
|
Expiration time.Time
|
||||||
|
}
|
||||||
14
server/web/app/models/node.go
Normal file
14
server/web/app/models/node.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
// Node 客户端模型
|
||||||
|
type Node struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
Location string
|
||||||
|
IPAddress string
|
||||||
|
|
||||||
|
Channels []Channel `gorm:"foreignKey:NodeId"`
|
||||||
|
}
|
||||||
14
server/web/app/models/user.go
Normal file
14
server/web/app/models/user.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Password string
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Channels []Channel `gorm:"foreignKey:UserId"`
|
||||||
|
}
|
||||||
10
server/web/auth/auth.go
Normal file
10
server/web/auth/auth.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Apply(r *gin.Engine, config *Config) {
|
||||||
|
r.Use(middleware)
|
||||||
|
}
|
||||||
41
server/web/auth/context.go
Normal file
41
server/web/auth/context.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
Permissions() map[string]struct{}
|
||||||
|
PermitAll(permissions ...string) bool
|
||||||
|
PermitAny(permissions ...string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// region DeviceContext
|
||||||
|
|
||||||
|
type DeviceContext struct {
|
||||||
|
ID uint
|
||||||
|
IpAddress string
|
||||||
|
Permissions map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DeviceContext) PermitAny(permissions ...string) bool {
|
||||||
|
if _, exist := c.Permissions["*"]; exist {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if _, ok := c.Permissions[permission]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DeviceContext) PermitAll(permissions ...string) bool {
|
||||||
|
if _, exist := c.Permissions["*"]; exist {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if _, ok := c.Permissions[permission]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
97
server/web/auth/middleware.go
Normal file
97
server/web/auth/middleware.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"proxy-server/pkg/resp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func middleware(c *gin.Context) {
|
||||||
|
|
||||||
|
auth := check(c)
|
||||||
|
if auth {
|
||||||
|
secret, err := getSecret(c)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("认证失败", err)
|
||||||
|
fail400(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = authenticate(c, secret)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("认证失败", err)
|
||||||
|
fail401(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
securedPaths = []string{
|
||||||
|
"/connect",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func check(c *gin.Context) bool {
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
if slices.Contains(securedPaths, path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecret(c *gin.Context) (string, error) {
|
||||||
|
|
||||||
|
// 获取认证信息
|
||||||
|
header := strings.Split(c.GetHeader("Authorization"), " ")
|
||||||
|
if len(header) != 2 {
|
||||||
|
return "", errors.New("无认证信息")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查认证类型
|
||||||
|
schema := header[0]
|
||||||
|
if schema != "Secret" {
|
||||||
|
return "", errors.New("不支持的认证类型 " + schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码密钥
|
||||||
|
parameters := header[1]
|
||||||
|
result, err := base64.URLEncoding.DecodeString(parameters)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "密钥解析失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(_ *gin.Context, secret string) error {
|
||||||
|
if secret != os.Getenv("SECRET") {
|
||||||
|
return errors.New("认证失败")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail400(c *gin.Context, err error) {
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.Abort()
|
||||||
|
c.JSON(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
resp.Fail(err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail401(c *gin.Context, err error) {
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.Abort()
|
||||||
|
c.JSON(
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
resp.Fail(err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
16
server/web/router/router.go
Normal file
16
server/web/router/router.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"proxy-server/server/web/app/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Apply(r *gin.Engine) {
|
||||||
|
|
||||||
|
r.POST("/node/register", handlers.NodeRegister)
|
||||||
|
r.POST("/node/report", handlers.NodeReport)
|
||||||
|
|
||||||
|
r.POST("/chan/request", handlers.ChanRequest)
|
||||||
|
r.POST("/chan/auth", handlers.ChanAuth)
|
||||||
|
r.POST("/chan/test", handlers.ChanTest)
|
||||||
|
}
|
||||||
44
server/web/web.go
Normal file
44
server/web/web.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"proxy-server/server/web/auth"
|
||||||
|
"proxy-server/server/web/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
var server *http.Server
|
||||||
|
|
||||||
|
func Start(ctx context.Context, errCh chan error) {
|
||||||
|
address := ":" + os.Getenv("PORT")
|
||||||
|
engine := gin.Default()
|
||||||
|
server = &http.Server{Addr: address, Handler: engine}
|
||||||
|
|
||||||
|
// 监听关闭信号
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
slog.Info("web 服务被动关闭")
|
||||||
|
err := server.Shutdown(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("web 服务关闭失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 配置中间件和路由
|
||||||
|
auth.Apply(engine, nil)
|
||||||
|
router.Apply(engine)
|
||||||
|
|
||||||
|
// 启动服务
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("web 服务主动结束")
|
||||||
|
errCh <- nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user