增加邮件服务功能

This commit is contained in:
dushixiang
2021-03-08 20:00:51 +08:00
parent 60fbb507f5
commit b48f650f7e
11 changed files with 198 additions and 17 deletions

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/antonfisher/nested-logrus-formatter v1.3.0 github.com/antonfisher/nested-logrus-formatter v1.3.0
github.com/gofrs/uuid v3.3.0+incompatible github.com/gofrs/uuid v3.3.0+incompatible
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/labstack/echo/v4 v4.1.17 github.com/labstack/echo/v4 v4.1.17
github.com/labstack/gommon v0.3.0 github.com/labstack/gommon v0.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible

12
go.sum
View File

@ -107,6 +107,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@ -151,9 +153,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@ -182,16 +182,12 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 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/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -215,7 +211,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -300,7 +295,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -308,7 +302,6 @@ golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirS
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -354,7 +347,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -14,10 +14,11 @@ func UserCreateEndpoint(c echo.Context) error {
if err := c.Bind(&item); err != nil { if err := c.Bind(&item); err != nil {
return err return err
} }
password := item.Password
var pass []byte var pass []byte
var err error var err error
if pass, err = utils.Encoder.Encode([]byte(item.Password)); err != nil { if pass, err = utils.Encoder.Encode([]byte(password)); err != nil {
return err return err
} }
item.Password = string(pass) item.Password = string(pass)
@ -28,6 +29,10 @@ func UserCreateEndpoint(c echo.Context) error {
if err := model.CreateNewUser(&item); err != nil { if err := model.CreateNewUser(&item); err != nil {
return err return err
} }
if item.Mail != "" {
go model.SendMail(item.Mail, "[Next Terminal] 注册通知", "你好,"+item.Nickname+"。管理员为你注册了账号:"+item.Username+" 密码:"+password)
}
return Success(c, item) return Success(c, item)
} }
@ -36,11 +41,12 @@ func UserPagingEndpoint(c echo.Context) error {
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username") username := c.QueryParam("username")
nickname := c.QueryParam("nickname") nickname := c.QueryParam("nickname")
mail := c.QueryParam("mail")
order := c.QueryParam("order") order := c.QueryParam("order")
field := c.QueryParam("field") field := c.QueryParam("field")
items, total, err := model.FindPageUser(pageIndex, pageSize, username, nickname, order, field) items, total, err := model.FindPageUser(pageIndex, pageSize, username, nickname, mail, order, field)
if err != nil { if err != nil {
return err return err
} }
@ -109,6 +115,11 @@ func UserChangePasswordEndpoint(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
password := c.QueryParam("password") password := c.QueryParam("password")
user, err := model.FindUserById(id)
if err != nil {
return err
}
passwd, err := utils.Encoder.Encode([]byte(password)) passwd, err := utils.Encoder.Encode([]byte(password))
if err != nil { if err != nil {
return err return err
@ -117,6 +128,11 @@ func UserChangePasswordEndpoint(c echo.Context) error {
Password: string(passwd), Password: string(passwd),
} }
model.UpdateUserById(u, id) model.UpdateUserById(u, id)
if user.Mail != "" {
go model.SendMail(user.Mail, "[Next Terminal] 密码修改通知", "你好,"+user.Nickname+"。管理员已将你的密码修改为:"+password)
}
return Success(c, "") return Success(c, "")
} }

View File

@ -1,12 +1,19 @@
package model package model
import ( import (
"github.com/jordan-wright/email"
"github.com/sirupsen/logrus"
"net/smtp"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
) )
const ( const (
SshMode = "ssh-mode" SshMode = "ssh-mode"
MailHost = "mail-host"
MailPort = "mail-port"
MailUsername = "mail-username"
MailPassword = "mail-password"
) )
type Property struct { type Property struct {
@ -64,3 +71,26 @@ func GetRecordingPath() (string, error) {
} }
return property.Value, nil return property.Value, nil
} }
func SendMail(to, subject, text string) {
propertiesMap := FindAllPropertiesMap()
host := propertiesMap[MailHost]
port := propertiesMap[MailPort]
username := propertiesMap[MailUsername]
password := propertiesMap[MailPassword]
if host == "" || port == "" || username == "" || password == "" {
logrus.Debugf("邮箱信息不完整,跳过发送邮件。")
return
}
e := email.NewEmail()
e.From = "Next Terminal <" + username + ">"
e.To = []string{to}
e.Subject = subject
e.Text = []byte(text)
err := e.Send(host+":"+port, smtp.PlainAuth("", username, password, host))
if err != nil {
logrus.Errorf("邮件发送失败: %v", err.Error())
}
}

View File

@ -28,6 +28,8 @@ type UserVo struct {
ID string `json:"id"` ID string `json:"id"`
Username string `json:"username"` Username string `json:"username"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
TOTPSecret string `json:"totpSecret"`
Mail string `json:"mail"`
Online bool `json:"online"` Online bool `json:"online"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Created utils.JsonTime `json:"created"` Created utils.JsonTime `json:"created"`
@ -50,8 +52,8 @@ func FindAllUser() (o []User) {
return return
} }
func FindPageUser(pageIndex, pageSize int, username, nickname, order, field string) (o []UserVo, total int64, err error) { func FindPageUser(pageIndex, pageSize int, username, nickname, mail, order, field string) (o []UserVo, total int64, err error) {
db := global.DB.Table("users").Select("users.id,users.username,users.nickname,users.online,users.enabled,users.created,users.type, count(resource_sharers.user_id) as sharer_asset_count").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id") db := global.DB.Table("users").Select("users.id,users.username,users.nickname,users.mail,users.online,users.enabled,users.created,users.type, count(resource_sharers.user_id) as sharer_asset_count, users.totp_secret").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id")
dbCounter := global.DB.Table("users") dbCounter := global.DB.Table("users")
if len(username) > 0 { if len(username) > 0 {
db = db.Where("users.username like ?", "%"+username+"%") db = db.Where("users.username like ?", "%"+username+"%")
@ -63,6 +65,11 @@ func FindPageUser(pageIndex, pageSize int, username, nickname, order, field stri
dbCounter = dbCounter.Where("nickname like ?", "%"+nickname+"%") dbCounter = dbCounter.Where("nickname like ?", "%"+nickname+"%")
} }
if len(mail) > 0 {
db = db.Where("users.mail like ?", "%"+mail+"%")
dbCounter = dbCounter.Where("mail like ?", "%"+mail+"%")
}
err = dbCounter.Count(&total).Error err = dbCounter.Count(&total).Error
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -86,6 +93,14 @@ func FindPageUser(pageIndex, pageSize int, username, nickname, order, field stri
if o == nil { if o == nil {
o = make([]UserVo, 0) o = make([]UserVo, 0)
} }
for i := 0; i < len(o); i++ {
if o[i].TOTPSecret == "" || o[i].TOTPSecret == "-" {
o[i].TOTPSecret = "0"
} else {
o[i].TOTPSecret = "1"
}
}
return return
} }

View File

@ -318,6 +318,7 @@ class LoginLog extends Component {
onSearch={this.handleSearchByNickname} onSearch={this.handleSearchByNickname}
onChange={this.handleChangeByUserId} onChange={this.handleChangeByUserId}
filterOption={false} filterOption={false}
allowClear
> >
{userOptions} {userOptions}
</Select> </Select>

View File

@ -379,6 +379,7 @@ class OfflineSession extends Component {
onSearch={this.handleSearchByNickname} onSearch={this.handleSearchByNickname}
onChange={this.handleChangeByUserId} onChange={this.handleChangeByUserId}
filterOption={false} filterOption={false}
allowClear
> >
{userOptions} {userOptions}
</Select> </Select>

View File

@ -374,6 +374,7 @@ class OnlineSession extends Component {
onSearch={this.handleSearchByNickname} onSearch={this.handleSearchByNickname}
onChange={this.handleChangeByUserId} onChange={this.handleChangeByUserId}
filterOption={false} filterOption={false}
allowClear
> >
{userOptions} {userOptions}
</Select> </Select>

View File

@ -42,6 +42,7 @@ class Setting extends Component {
sshSettingFormRef = React.createRef(); sshSettingFormRef = React.createRef();
vncSettingFormRef = React.createRef(); vncSettingFormRef = React.createRef();
otherSettingFormRef = React.createRef(); otherSettingFormRef = React.createRef();
mailSettingFormRef = React.createRef();
componentDidMount() { componentDidMount() {
this.getProperties(); this.getProperties();
@ -98,6 +99,10 @@ class Setting extends Component {
if (this.otherSettingFormRef.current) { if (this.otherSettingFormRef.current) {
this.otherSettingFormRef.current.setFieldsValue(properties) this.otherSettingFormRef.current.setFieldsValue(properties)
} }
if (this.mailSettingFormRef.current) {
this.mailSettingFormRef.current.setFieldsValue(properties)
}
} else { } else {
message.error(result['message']); message.error(result['message']);
} }
@ -561,6 +566,77 @@ class Setting extends Component {
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item {...formTailLayout}>
<Button type="primary" htmlType="submit">
更新
</Button>
</Form.Item>
</Form>
</TabPane>
<TabPane tab="邮箱配置" key="mail">
<Title level={3}>Guacd 服务配置</Title>
<Form ref={this.mailSettingFormRef} name="password" onFinish={this.changeProperties}
layout="vertical">
<Form.Item
{...formItemLayout}
name="mail-host"
label="邮件服务器地址"
rules={[
{
required: false,
message: '邮件服务器地址',
},
]}
>
<Input type='text' placeholder="请输入邮件服务器地址"/>
</Form.Item>
<Form.Item
{...formItemLayout}
name="mail-port"
label="邮件服务器端口"
rules={[
{
required: false,
message: '邮件服务器地址',
min: 1,
max: 65535
},
]}
>
<Input type='number' placeholder="请输入邮件服务器地址"/>
</Form.Item>
<Form.Item
{...formItemLayout}
name="mail-username"
label="邮箱账号"
rules={[
{
required: false,
type: "email",
message: '请输入正确的邮箱账号',
},
]}
>
<Input type='email' placeholder="请输入邮箱账号"/>
</Form.Item>
<Form.Item
{...formItemLayout}
name="mail-password"
label="邮箱密码"
rules={[
{
required: false,
message: '邮箱密码',
},
]}
>
<Input type='password' placeholder="请输入邮箱密码"/>
</Form.Item>
<Form.Item {...formTailLayout}> <Form.Item {...formTailLayout}>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
更新 更新

View File

@ -25,9 +25,10 @@ import UserModal from "./UserModal";
import request from "../../common/request"; import request from "../../common/request";
import {message} from "antd/es"; import {message} from "antd/es";
import { import {
CheckCircleOutlined,
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
ExclamationCircleOutlined, ExclamationCircleOutlined, InsuranceOutlined,
LockOutlined, LockOutlined,
PlusOutlined, PlusOutlined,
SyncOutlined, SyncOutlined,
@ -58,6 +59,7 @@ class User extends Component {
inputRefOfNickname = React.createRef(); inputRefOfNickname = React.createRef();
inputRefOfUsername = React.createRef(); inputRefOfUsername = React.createRef();
inputRefOfMail = React.createRef();
changePasswordFormRef = React.createRef() changePasswordFormRef = React.createRef()
state = { state = {
@ -234,6 +236,17 @@ class User extends Component {
this.loadTableData(query); this.loadTableData(query);
}; };
handleSearchByMail = mail => {
let query = {
...this.state.queryParams,
'pageIndex': 1,
'pageSize': this.state.queryParams.pageSize,
'mail': mail,
}
this.loadTableData(query);
};
batchDelete = async () => { batchDelete = async () => {
this.setState({ this.setState({
delBtnLoading: true delBtnLoading: true
@ -327,7 +340,22 @@ class User extends Component {
} else { } else {
return text; return text;
} }
}
}, {
title: '邮箱',
dataIndex: 'mail',
key: 'mail',
}, {
title: '二次认证',
dataIndex: 'totpSecret',
key: 'totpSecret',
render: (text, record) => {
if (text === '1') {
return <Tag icon={<InsuranceOutlined />} color="success">开启</Tag>;
}else {
return <Tag icon={<ExclamationCircleOutlined />} color="warning">关闭</Tag>;
}
} }
}, { }, {
title: '在线状态', title: '在线状态',
@ -394,6 +422,7 @@ class User extends Component {
let result = await request.post(`/users/${record['id']}/reset-totp`); let result = await request.post(`/users/${record['id']}/reset-totp`);
if (result['code'] === 1) { if (result['code'] === 1) {
message.success('操作成功', 3); message.success('操作成功', 3);
this.loadTableData();
} else { } else {
message.error(result['message'], 10); message.error(result['message'], 10);
} }
@ -414,7 +443,14 @@ class User extends Component {
return ( return (
<div> <div>
<Button type="link" size='small' <Button type="link" size='small'
onClick={() => this.showModal('更新用户', record)}>编辑</Button> onClick={async () => {
let result = await request.get(`/users/${record['id']}`);
if (result['code'] !== 1) {
message.error(result['message']);
return;
}
this.showModal('更新用户', result['data']);
}}>编辑</Button>
<Dropdown overlay={menu}> <Dropdown overlay={menu}>
<Button type="link" size='small'> <Button type="link" size='small'>
更多 <DownOutlined/> 更多 <DownOutlined/>
@ -474,11 +510,19 @@ class User extends Component {
onSearch={this.handleSearchByUsername} onSearch={this.handleSearchByUsername}
/> />
<Search
ref={this.inputRefOfMail}
placeholder="邮箱"
allowClear
onSearch={this.handleSearchByMail}
/>
<Tooltip title='重置查询'> <Tooltip title='重置查询'>
<Button icon={<UndoOutlined/>} onClick={() => { <Button icon={<UndoOutlined/>} onClick={() => {
this.inputRefOfUsername.current.setValue(''); this.inputRefOfUsername.current.setValue('');
this.inputRefOfNickname.current.setValue(''); this.inputRefOfNickname.current.setValue('');
this.inputRefOfMail.current.setValue('');
this.loadTableData({pageIndex: 1, pageSize: 10}) this.loadTableData({pageIndex: 1, pageSize: 10})
}}> }}>

View File

@ -52,6 +52,10 @@ const UserModal = ({title, visible, handleOk, handleCancel, confirmLoading, mode
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label="邮箱账号" name="mail" rules={[{required: false, type: "email", message: '请输入正确的邮箱账号',},]}>
<Input type='email' placeholder="请输入邮箱账号"/>
</Form.Item>
{ {
title.indexOf('新增') > -1 ? title.indexOf('新增') > -1 ?
(<Form.Item label="登录密码" name='password' rules={[{required: true, message: '请输入登录密码'}]}> (<Form.Item label="登录密码" name='password' rules={[{required: true, message: '请输入登录密码'}]}>