Browse Source

[feature] using xray api and more

Improve DB performance
[api] backward compatibility: add client by update

Co-Authored-By: Alireza Ahmadi <[email protected]>
MHSanaei 1 year ago
parent
commit
70f250dfe1
8 changed files with 531 additions and 176 deletions
  1. 8 1
      go.mod
  2. 12 6
      go.sum
  3. 20 9
      web/controller/inbound.go
  4. 8 6
      web/job/check_inbound_job.go
  5. 291 60
      web/service/inbound.go
  6. 10 3
      web/service/xray.go
  7. 182 0
      xray/api.go
  8. 0 91
      xray/process.go

+ 8 - 1
go.mod

@@ -27,13 +27,15 @@ require (
 	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/bytedance/sonic v1.9.1 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
 	github.com/fasthttp/router v1.4.19 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gaukas/godicttls v0.0.3 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/go-ole/go-ole v1.2.6 // 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.14.0 // indirect
+	github.com/go-playground/validator/v10 v10.14.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/gorilla/context v1.1.1 // indirect
 	github.com/gorilla/securecookie v1.1.1 // indirect
@@ -51,14 +53,19 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pires/go-proxyproto v0.7.0 // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
+	github.com/refraction-networking/utls v1.3.2 // indirect
+	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
+	github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
 	github.com/shoenig/go-m1cpu v0.1.6 // indirect
 	github.com/tklauser/go-sysconf v0.3.11 // indirect
 	github.com/tklauser/numcpus v0.6.1 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
+	github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fasthttp v1.47.0 // indirect
+	github.com/xtls/reality v0.0.0-20230331223127-176a94313eda // indirect
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	golang.org/x/arch v0.3.0 // indirect
 	golang.org/x/crypto v0.9.0 // indirect

+ 12 - 6
go.sum

@@ -18,13 +18,16 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
 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/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
+github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
 github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
 github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
+github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
 github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
 github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
@@ -47,8 +50,8 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 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.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
-github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
+github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -86,8 +89,6 @@ github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nV
 github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
 github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
-github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
 github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -134,7 +135,9 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8
 github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
 github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
 github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
+github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
+github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
@@ -143,6 +146,7 @@ github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aW
 github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
 github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
+github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
 github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
 github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@@ -154,6 +158,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 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=
@@ -164,7 +169,6 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
 github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
 github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
 github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
-github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
 github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
 github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
 github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
@@ -176,11 +180,13 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
 github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
+github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
 github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
 github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
+github.com/xtls/reality v0.0.0-20230331223127-176a94313eda/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
 github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
 github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -223,7 +229,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -264,6 +269,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/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/driver/sqlite v1.5.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4=

+ 20 - 9
web/controller/inbound.go

@@ -79,7 +79,6 @@ func (a *InboundController) getInbound(c *gin.Context) {
 	}
 	jsonObj(c, inbound, nil)
 }
-
 func (a *InboundController) getClientTraffics(c *gin.Context) {
 	email := c.Param("email")
 	clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
@@ -178,13 +177,15 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
 		return
 	}
 
-	err = a.inboundService.AddInboundClient(data)
+	needRestart := false
+
+	needRestart, err = a.inboundService.AddInboundClient(data)
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
 	}
 	jsonMsg(c, "Client(s) added", nil)
-	if err == nil {
+	if err == nil && needRestart {
 		a.xrayService.SetToNeedRestart()
 	}
 }
@@ -197,13 +198,15 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
 	}
 	clientId := c.Param("clientId")
 
-	err = a.inboundService.DelInboundClient(id, clientId)
+	needRestart := false
+
+	needRestart, err = a.inboundService.DelInboundClient(id, clientId)
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
 	}
 	jsonMsg(c, "Client deleted", nil)
-	if err == nil {
+	if err == nil && needRestart {
 		a.xrayService.SetToNeedRestart()
 	}
 }
@@ -218,13 +221,15 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
 		return
 	}
 
-	err = a.inboundService.UpdateInboundClient(inbound, clientId)
+	needRestart := false
+
+	needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
 	}
 	jsonMsg(c, "Client updated", nil)
-	if err == nil {
+	if err == nil && needRestart {
 		a.xrayService.SetToNeedRestart()
 	}
 }
@@ -237,13 +242,15 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
 	}
 	email := c.Param("email")
 
-	err = a.inboundService.ResetClientTraffic(id, email)
+	needRestart := false
+
+	needRestart, err = a.inboundService.ResetClientTraffic(id, email)
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
 	}
 	jsonMsg(c, "traffic reseted", nil)
-	if err == nil {
+	if err == nil && needRestart {
 		a.xrayService.SetToNeedRestart()
 	}
 }
@@ -253,6 +260,8 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
+	} else {
+		a.xrayService.SetToNeedRestart()
 	}
 	jsonMsg(c, "All traffics reseted", nil)
 }
@@ -268,6 +277,8 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
 	if err != nil {
 		jsonMsg(c, "Something went wrong!", err)
 		return
+	} else {
+		a.xrayService.SetToNeedRestart()
 	}
 	jsonMsg(c, "All traffics of client reseted", nil)
 }

+ 8 - 6
web/job/check_inbound_job.go

@@ -15,19 +15,21 @@ func NewCheckInboundJob() *CheckInboundJob {
 }
 
 func (j *CheckInboundJob) Run() {
-	count, err := j.inboundService.DisableInvalidClients()
+	needRestart, count, err := j.inboundService.DisableInvalidClients()
 	if err != nil {
-		logger.Warning("disable invalid Client err:", err)
+		logger.Warning("Error in disabling invalid clients:", err)
 	} else if count > 0 {
-		logger.Debugf("disabled %v Client", count)
-		j.xrayService.SetToNeedRestart()
+		logger.Debugf("%v clients disabled", count)
+		if needRestart {
+			j.xrayService.SetToNeedRestart()
+		}
 	}
 
 	count, err = j.inboundService.DisableInvalidInbounds()
 	if err != nil {
-		logger.Warning("disable invalid inbounds err:", err)
+		logger.Warning("Error in disabling invalid inbounds:", err)
 	} else if count > 0 {
-		logger.Debugf("disabled %v inbounds", count)
+		logger.Debugf("%v inbounds disabled", count)
 		j.xrayService.SetToNeedRestart()
 	}
 }

+ 291 - 60
web/service/inbound.go

@@ -15,6 +15,7 @@ import (
 )
 
 type InboundService struct {
+	xrayApi xray.XrayAPI
 }
 
 func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
@@ -156,11 +157,19 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, err
 	}
 
 	db := database.GetDB()
+	tx := db.Begin()
+	defer func() {
+		if err == nil {
+			tx.Commit()
+		} else {
+			tx.Rollback()
+		}
+	}()
 
-	err = db.Save(inbound).Error
+	err = tx.Save(inbound).Error
 	if err == nil {
 		for _, client := range clients {
-			s.AddClientStat(inbound.Id, &client)
+			s.AddClientStat(tx, inbound.Id, &client)
 		}
 	}
 	return inbound, err
@@ -244,6 +253,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
 	if err != nil {
 		return inbound, err
 	}
+
+	err = s.updateClientTraffics(oldInbound, inbound)
+	if err != nil {
+		return inbound, err
+	}
+
 	oldInbound.Up = inbound.Up
 	oldInbound.Down = inbound.Down
 	oldInbound.Total = inbound.Total
@@ -262,36 +277,92 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
 	return inbound, db.Save(oldInbound).Error
 }
 
-func (s *InboundService) AddInboundClient(data *model.Inbound) error {
-	clients, err := s.GetClients(data)
+func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error {
+	oldClients, err := s.GetClients(oldInbound)
+	if err != nil {
+		return err
+	}
+	newClients, err := s.GetClients(newInbound)
 	if err != nil {
 		return err
 	}
 
+	db := database.GetDB()
+	tx := db.Begin()
+
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	var emailExists bool
+
+	for _, oldClient := range oldClients {
+		emailExists = false
+		for _, newClient := range newClients {
+			if oldClient.Email == newClient.Email {
+				emailExists = true
+				break
+			}
+		}
+		if !emailExists {
+			err = s.DelClientStat(tx, oldClient.Email)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	for _, newClient := range newClients {
+		emailExists = false
+		for _, oldClient := range oldClients {
+			if newClient.Email == oldClient.Email {
+				emailExists = true
+				break
+			}
+		}
+		if !emailExists {
+			err = s.AddClientStat(tx, oldInbound.Id, &newClient)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
+	clients, err := s.GetClients(data)
+	if err != nil {
+		return false, err
+	}
+
 	var settings map[string]interface{}
 	err = json.Unmarshal([]byte(data.Settings), &settings)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	interfaceClients := settings["clients"].([]interface{})
 	existEmail, err := s.checkEmailsExistForClients(clients)
 	if err != nil {
-		return err
+		return false, err
 	}
 	if existEmail != "" {
-		return common.NewError("Duplicate email:", existEmail)
+		return false, common.NewError("Duplicate email:", existEmail)
 	}
 
 	oldInbound, err := s.GetInbound(data.Id)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	var oldSettings map[string]interface{}
 	err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldClients := oldSettings["clients"].([]interface{})
@@ -301,30 +372,58 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) error {
 
 	newSettings, err := json.MarshalIndent(oldSettings, "", "  ")
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldInbound.Settings = string(newSettings)
 
+	db := database.GetDB()
+	tx := db.Begin()
+
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	needRestart := false
+	s.xrayApi.Init(p.GetAPIPort())
 	for _, client := range clients {
 		if len(client.Email) > 0 {
-			s.AddClientStat(data.Id, &client)
+			s.AddClientStat(tx, data.Id, &client)
+			err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
+				"email":    client.Email,
+				"id":       client.ID,
+				"alterId":  client.AlterIds,
+				"flow":     client.Flow,
+				"password": client.Password,
+			})
+			if err1 == nil {
+				logger.Debug("Client added by api:", client.Email)
+			} else {
+				needRestart = true
+			}
+		} else {
+			needRestart = true
 		}
 	}
-	db := database.GetDB()
-	return db.Save(oldInbound).Error
+	s.xrayApi.Close()
+
+	return needRestart, tx.Save(oldInbound).Error
 }
 
-func (s *InboundService) DelInboundClient(inboundId int, clientId string) error {
+func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, error) {
 	oldInbound, err := s.GetInbound(inboundId)
 	if err != nil {
 		logger.Error("Load Old Data Error")
-		return err
+		return false, err
 	}
 	var settings map[string]interface{}
 	err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	email := ""
@@ -351,7 +450,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
 	settings["clients"] = newClients
 	newSettings, err := json.MarshalIndent(settings, "", "  ")
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldInbound.Settings = string(newSettings)
@@ -360,39 +459,49 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
 	err = s.DelClientStat(db, email)
 	if err != nil {
 		logger.Error("Delete stats Data Error")
-		return err
+		return false, err
 	}
 
 	err = s.DelClientIPs(db, email)
 	if err != nil {
 		logger.Error("Error in delete client IPs")
-		return err
+		return false, err
+	}
+	needRestart := true
+	s.xrayApi.Init(p.GetAPIPort())
+	if len(email) > 0 {
+		err = s.xrayApi.RemoveUser(oldInbound.Tag, email)
+		if err == nil {
+			logger.Debug("Client deleted by api:", email)
+			needRestart = false
+		}
 	}
-	return db.Save(oldInbound).Error
+	s.xrayApi.Close()
+	return needRestart, db.Save(oldInbound).Error
 }
 
-func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
+func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
 	clients, err := s.GetClients(data)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	var settings map[string]interface{}
 	err = json.Unmarshal([]byte(data.Settings), &settings)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	inerfaceClients := settings["clients"].([]interface{})
 
 	oldInbound, err := s.GetInbound(data.Id)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldClients, err := s.GetClients(oldInbound)
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldEmail := ""
@@ -416,17 +525,17 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
 	if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
 		existEmail, err := s.checkEmailsExistForClients(clients)
 		if err != nil {
-			return err
+			return false, err
 		}
 		if existEmail != "" {
-			return common.NewError("Duplicate email:", existEmail)
+			return false, common.NewError("Duplicate email:", existEmail)
 		}
 	}
 
 	var oldSettings map[string]interface{}
 	err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
 	if err != nil {
-		return err
+		return false, err
 	}
 	settingsClients := oldSettings["clients"].([]interface{})
 	settingsClients[clientIndex] = inerfaceClients[0]
@@ -434,36 +543,67 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
 
 	newSettings, err := json.MarshalIndent(oldSettings, "", "  ")
 	if err != nil {
-		return err
+		return false, err
 	}
 
 	oldInbound.Settings = string(newSettings)
 	db := database.GetDB()
+	tx := db.Begin()
+
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
 
 	if len(clients[0].Email) > 0 {
 		if len(oldEmail) > 0 {
 			err = s.UpdateClientStat(oldEmail, &clients[0])
 			if err != nil {
-				return err
+				return false, err
 			}
 			err = s.UpdateClientIPs(db, oldEmail, clients[0].Email)
 			if err != nil {
-				return err
+				return false, err
 			}
 		} else {
-			s.AddClientStat(data.Id, &clients[0])
+			s.AddClientStat(tx, data.Id, &clients[0])
 		}
 	} else {
-		err = s.DelClientStat(db, oldEmail)
+		err = s.DelClientStat(tx, oldEmail)
 		if err != nil {
-			return err
+			return false, err
 		}
 		err = s.DelClientIPs(db, oldEmail)
 		if err != nil {
-			return err
+			return false, err
 		}
 	}
-	return db.Save(oldInbound).Error
+	needRestart := true
+	s.xrayApi.Init(p.GetAPIPort())
+	if len(oldEmail) > 0 {
+		s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
+		if clients[0].Enable {
+			err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
+				"email":    clients[0].Email,
+				"id":       clients[0].ID,
+				"alterId":  clients[0].AlterIds,
+				"flow":     clients[0].Flow,
+				"password": clients[0].Password,
+			})
+			if err1 == nil {
+				logger.Debug("Client edited by api:", clients[0].Email)
+				needRestart = false
+			}
+		} else {
+			logger.Debug("Client disabled by api:", clients[0].Email)
+			needRestart = false
+		}
+	}
+	s.xrayApi.Close()
+	return needRestart, tx.Save(oldInbound).Error
 }
 
 func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
@@ -489,6 +629,7 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
 
 	return err
 }
+
 func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
 	if len(traffics) == 0 {
 		return nil
@@ -601,15 +742,42 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
 	return count, err
 }
 
-func (s *InboundService) DisableInvalidClients() (int64, error) {
+func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
 	db := database.GetDB()
 	now := time.Now().Unix() * 1000
+	needRestart := false
+
+	if p != nil {
+		var results []struct {
+			Tag   string
+			Email string
+		}
+
+		err := db.Table("inbounds").
+			Select("inbounds.tag, client_traffics.email").
+			Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
+			Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
+			Scan(&results).Error
+		if err != nil {
+			return false, 0, err
+		}
+		s.xrayApi.Init(p.GetAPIPort())
+		for _, result := range results {
+			err = s.xrayApi.RemoveUser(result.Tag, result.Email)
+			if err == nil {
+				logger.Debug("Client deleted by api:", result.Email)
+			} else {
+				needRestart = true
+			}
+		}
+		s.xrayApi.Close()
+	}
 	result := db.Model(xray.ClientTraffic{}).
 		Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
 		Update("enable", false)
 	err := result.Error
 	count := result.RowsAffected
-	return count, err
+	return needRestart, count, err
 }
 
 func (s *InboundService) MigrationRemoveOrphanedTraffics() {
@@ -624,9 +792,7 @@ func (s *InboundService) MigrationRemoveOrphanedTraffics() {
 	`)
 }
 
-func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
-	db := database.GetDB()
-
+func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error {
 	clientTraffic := xray.ClientTraffic{}
 	clientTraffic.InboundId = inboundId
 	clientTraffic.Email = client.Email
@@ -635,7 +801,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro
 	clientTraffic.Enable = true
 	clientTraffic.Up = 0
 	clientTraffic.Down = 0
-	result := db.Create(&clientTraffic)
+	result := tx.Create(&clientTraffic)
 	err := result.Error
 	if err != nil {
 		return err
@@ -779,7 +945,11 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
 		return err
 	}
 	inbound.Settings = string(modifiedSettings)
-	return s.UpdateInboundClient(inbound, clientId)
+	_, err = s.UpdateInboundClient(inbound, clientId)
+	if err != nil {
+		return err
+	}
+	return nil
 }
 
 func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
@@ -835,7 +1005,13 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, er
 		return false, err
 	}
 	inbound.Settings = string(modifiedSettings)
-	return !clientOldEnabled, s.UpdateInboundClient(inbound, clientId)
+
+	_, err = s.UpdateInboundClient(inbound, clientId)
+	if err != nil {
+		return false, err
+	}
+
+	return !clientOldEnabled, nil
 }
 
 func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) error {
@@ -889,9 +1065,13 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
 		return err
 	}
 	inbound.Settings = string(modifiedSettings)
-	return s.UpdateInboundClient(inbound, clientId)
-}
+	_, err = s.UpdateInboundClient(inbound, clientId)
+	if err != nil {
+		return err
+	}
+	return nil
 
+}
 func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
 	_, inbound, err := s.GetClientInboundByEmail(clientEmail)
 	if err != nil {
@@ -943,7 +1123,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
 		return err
 	}
 	inbound.Settings = string(modifiedSettings)
-	return s.UpdateInboundClient(inbound, clientId)
+	_, err = s.UpdateInboundClient(inbound, clientId)
+	if err != nil {
+		return err
+	}
+	return nil
+
 }
 
 func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
@@ -961,19 +1146,55 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
 	return nil
 }
 
-func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
-	db := database.GetDB()
+func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, error) {
+	needRestart := false
 
-	result := db.Model(xray.ClientTraffic{}).
-		Where("inbound_id = ? and email = ?", id, clientEmail).
-		Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
+	traffic, err := s.GetClientTrafficByEmail(clientEmail)
+	if err != nil {
+		return false, err
+	}
 
-	err := result.Error
+	if !traffic.Enable {
+		inbound, err := s.GetInbound(id)
+		if err != nil {
+			return false, err
+		}
+		clients, err := s.GetClients(inbound)
+		if err != nil {
+			return false, err
+		}
+		for _, client := range clients {
+			if client.Email == clientEmail {
+				s.xrayApi.Init(p.GetAPIPort())
+				err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
+					"email":    client.Email,
+					"id":       client.ID,
+					"alterId":  client.AlterIds,
+					"flow":     client.Flow,
+					"password": client.Password,
+				})
+				if err1 == nil {
+					logger.Debug("Client enabled due to reset traffic:", clientEmail)
+				} else {
+					needRestart = true
+				}
+				s.xrayApi.Close()
+				break
+			}
+		}
+	}
+
+	traffic.Up = 0
+	traffic.Down = 0
+	traffic.Enable = true
 
+	db := database.GetDB()
+	err = db.Save(traffic).Error
 	if err != nil {
-		return err
+		return false, err
 	}
-	return nil
+
+	return needRestart, nil
 }
 
 func (s *InboundService) ResetAllClientTraffics(id int) error {
@@ -1212,10 +1433,19 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
 
 func (s *InboundService) MigrationRequirements() {
 	db := database.GetDB()
+	tx := db.Begin()
+	var err error
+	defer func() {
+		if err == nil {
+			tx.Commit()
+		} else {
+			tx.Rollback()
+		}
+	}()
 
 	// Fix inbounds based problems
 	var inbounds []*model.Inbound
-	err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
+	err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
 	if err != nil && err != gorm.ErrRecordNotFound {
 		return
 	}
@@ -1250,6 +1480,7 @@ func (s *InboundService) MigrationRequirements() {
 
 			inbounds[inbound_index].Settings = string(modifiedSettings)
 		}
+
 		// Add client traffic row for all clients which has email
 		modelClients, err := s.GetClients(inbounds[inbound_index])
 		if err != nil {
@@ -1258,17 +1489,17 @@ func (s *InboundService) MigrationRequirements() {
 		for _, modelClient := range modelClients {
 			if len(modelClient.Email) > 0 {
 				var count int64
-				db.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
+				tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
 				if count == 0 {
-					s.AddClientStat(inbounds[inbound_index].Id, &modelClient)
+					s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient)
 				}
 			}
 		}
 	}
-	db.Save(inbounds)
+	tx.Save(inbounds)
 
 	// Remove orphaned traffics
-	db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
+	tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
 }
 
 func (s *InboundService) MigrateDB() {

+ 10 - 3
web/service/xray.go

@@ -18,6 +18,7 @@ var result string
 type XrayService struct {
 	inboundService InboundService
 	settingService SettingService
+	xrayAPI        xray.XrayAPI
 }
 
 func (s *XrayService) IsXrayRunning() bool {
@@ -143,7 +144,9 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
 	if !s.IsXrayRunning() {
 		return nil, nil, errors.New("xray is not running")
 	}
-	return p.GetTraffic(true)
+	s.xrayAPI.Init(p.GetAPIPort())
+	defer s.xrayAPI.Close()
+	return s.xrayAPI.GetTraffic(true)
 }
 
 func (s *XrayService) RestartXray(isForce bool) error {
@@ -158,7 +161,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
 
 	if p != nil && p.IsRunning() {
 		if !isForce && p.GetConfig().Equals(xrayConfig) {
-			logger.Debug("not need to restart xray")
+			logger.Debug("It does not need to restart xray")
 			return nil
 		}
 		p.Stop()
@@ -166,7 +169,11 @@ func (s *XrayService) RestartXray(isForce bool) error {
 
 	p = xray.NewProcess(xrayConfig)
 	result = ""
-	return p.Start()
+	err = p.Start()
+	if err != nil {
+		return err
+	}
+	return nil
 }
 
 func (s *XrayService) StopXray() error {

+ 182 - 0
xray/api.go

@@ -0,0 +1,182 @@
+package xray
+
+import (
+	"context"
+	"fmt"
+	"regexp"
+	"time"
+	"x-ui/util/common"
+
+	"github.com/xtls/xray-core/app/proxyman/command"
+	statsService "github.com/xtls/xray-core/app/stats/command"
+	"github.com/xtls/xray-core/common/protocol"
+	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/proxy/shadowsocks"
+	"github.com/xtls/xray-core/proxy/trojan"
+	"github.com/xtls/xray-core/proxy/vless"
+	"github.com/xtls/xray-core/proxy/vmess"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+)
+
+type XrayAPI struct {
+	HandlerServiceClient *command.HandlerServiceClient
+	StatsServiceClient   *statsService.StatsServiceClient
+	grpcClient           *grpc.ClientConn
+	isConnected          bool
+}
+
+func (x *XrayAPI) Init(apiPort int) (err error) {
+	if apiPort == 0 {
+		return common.NewError("xray api port wrong:", apiPort)
+	}
+	x.grpcClient, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		return err
+	}
+	x.isConnected = true
+
+	hsClient := command.NewHandlerServiceClient(x.grpcClient)
+	ssClient := statsService.NewStatsServiceClient(x.grpcClient)
+
+	x.HandlerServiceClient = &hsClient
+	x.StatsServiceClient = &ssClient
+
+	return
+}
+
+func (x *XrayAPI) Close() {
+	x.grpcClient.Close()
+	x.HandlerServiceClient = nil
+	x.StatsServiceClient = nil
+	x.isConnected = false
+}
+
+func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]interface{}) error {
+	var account *serial.TypedMessage
+	switch Protocol {
+	case "vmess":
+		account = serial.ToTypedMessage(&vmess.Account{
+			Id:      user["id"].(string),
+			AlterId: uint32(user["alterId"].(uint16)),
+		})
+	case "vless":
+		account = serial.ToTypedMessage(&vless.Account{
+			Id:   user["id"].(string),
+			Flow: user["flow"].(string),
+		})
+	case "trojan":
+		account = serial.ToTypedMessage(&trojan.Account{
+			Password: user["password"].(string),
+		})
+	case "shadowsocks":
+		account = serial.ToTypedMessage(&shadowsocks.Account{
+			Password: user["password"].(string),
+		})
+	default:
+		return nil
+	}
+
+	client := *x.HandlerServiceClient
+
+	_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
+		Tag: inboundTag,
+		Operation: serial.ToTypedMessage(&command.AddUserOperation{
+			User: &protocol.User{
+				Email:   user["email"].(string),
+				Account: account,
+			},
+		}),
+	})
+	return err
+}
+
+func (x *XrayAPI) RemoveUser(inboundTag string, email string) error {
+	client := *x.HandlerServiceClient
+	_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
+		Tag: inboundTag,
+		Operation: serial.ToTypedMessage(&command.RemoveUserOperation{
+			Email: email,
+		}),
+	})
+	return err
+}
+
+func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
+	if x.grpcClient == nil {
+		return nil, nil, common.NewError("xray api is not initialized")
+	}
+	var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
+	var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
+
+	client := *x.StatsServiceClient
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+	defer cancel()
+	request := &statsService.QueryStatsRequest{
+		Reset_: reset,
+	}
+	resp, err := client.QueryStats(ctx, request)
+	if err != nil {
+		return nil, nil, err
+	}
+	tagTrafficMap := map[string]*Traffic{}
+	emailTrafficMap := map[string]*ClientTraffic{}
+
+	clientTraffics := make([]*ClientTraffic, 0)
+	traffics := make([]*Traffic, 0)
+	for _, stat := range resp.GetStat() {
+		matchs := trafficRegex.FindStringSubmatch(stat.Name)
+		if len(matchs) < 3 {
+
+			matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
+			if len(matchs) < 3 {
+				continue
+			} else {
+
+				isUser := matchs[1] == "user"
+				email := matchs[2]
+				isDown := matchs[3] == "downlink"
+				if !isUser {
+					continue
+				}
+				traffic, ok := emailTrafficMap[email]
+				if !ok {
+					traffic = &ClientTraffic{
+						Email: email,
+					}
+					emailTrafficMap[email] = traffic
+					clientTraffics = append(clientTraffics, traffic)
+				}
+				if isDown {
+					traffic.Down = stat.Value
+				} else {
+					traffic.Up = stat.Value
+				}
+
+			}
+			continue
+		}
+		isInbound := matchs[1] == "inbound"
+		tag := matchs[2]
+		isDown := matchs[3] == "downlink"
+		if tag == "api" {
+			continue
+		}
+		traffic, ok := tagTrafficMap[tag]
+		if !ok {
+			traffic = &Traffic{
+				IsInbound: isInbound,
+				Tag:       tag,
+			}
+			tagTrafficMap[tag] = traffic
+			traffics = append(traffics, traffic)
+		}
+		if isDown {
+			traffic.Down = stat.Value
+		} else {
+			traffic.Up = stat.Value
+		}
+	}
+
+	return traffics, clientTraffics, nil
+}

+ 0 - 91
xray/process.go

@@ -3,30 +3,21 @@ package xray
 import (
 	"bufio"
 	"bytes"
-	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"io/fs"
 	"os"
 	"os/exec"
-	"regexp"
 	"runtime"
 	"strings"
 	"sync"
-	"time"
 	"x-ui/config"
 	"x-ui/util/common"
 
 	"github.com/Workiva/go-datastructures/queue"
-	statsservice "github.com/xtls/xray-core/app/stats/command"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials/insecure"
 )
 
-var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
-var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
-
 func GetBinaryName() string {
 	return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH)
 }
@@ -238,85 +229,3 @@ func (p *process) Stop() error {
 	}
 	return p.cmd.Process.Kill()
 }
-
-func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
-	if p.apiPort == 0 {
-		return nil, nil, common.NewError("xray api port wrong:", p.apiPort)
-	}
-	conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", p.apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
-	if err != nil {
-		return nil, nil, err
-	}
-	defer conn.Close()
-
-	client := statsservice.NewStatsServiceClient(conn)
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
-	defer cancel()
-	request := &statsservice.QueryStatsRequest{
-		Reset_: reset,
-	}
-	resp, err := client.QueryStats(ctx, request)
-	if err != nil {
-		return nil, nil, err
-	}
-	tagTrafficMap := map[string]*Traffic{}
-	emailTrafficMap := map[string]*ClientTraffic{}
-
-	clientTraffics := make([]*ClientTraffic, 0)
-	traffics := make([]*Traffic, 0)
-	for _, stat := range resp.GetStat() {
-		matchs := trafficRegex.FindStringSubmatch(stat.Name)
-		if len(matchs) < 3 {
-
-			matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
-			if len(matchs) < 3 {
-				continue
-			} else {
-
-				isUser := matchs[1] == "user"
-				email := matchs[2]
-				isDown := matchs[3] == "downlink"
-				if !isUser {
-					continue
-				}
-				traffic, ok := emailTrafficMap[email]
-				if !ok {
-					traffic = &ClientTraffic{
-						Email: email,
-					}
-					emailTrafficMap[email] = traffic
-					clientTraffics = append(clientTraffics, traffic)
-				}
-				if isDown {
-					traffic.Down = stat.Value
-				} else {
-					traffic.Up = stat.Value
-				}
-
-			}
-			continue
-		}
-		isInbound := matchs[1] == "inbound"
-		tag := matchs[2]
-		isDown := matchs[3] == "downlink"
-		if tag == "api" {
-			continue
-		}
-		traffic, ok := tagTrafficMap[tag]
-		if !ok {
-			traffic = &Traffic{
-				IsInbound: isInbound,
-				Tag:       tag,
-			}
-			tagTrafficMap[tag] = traffic
-			traffics = append(traffics, traffic)
-		}
-		if isDown {
-			traffic.Down = stat.Value
-		} else {
-			traffic.Up = stat.Value
-		}
-	}
-
-	return traffics, clientTraffics, nil
-}