From 9088d22a66e18b8fc9a47b4481f1a2f10f115456 Mon Sep 17 00:00:00 2001 From: RockYang Date: Sun, 2 Jul 2023 20:51:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B3=A8=E5=86=8C=E7=9F=AD=E4=BF=A1?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B7=B2=E7=BB=8F=E5=BC=80=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/go/core/app_server.go | 15 ++-- api/go/core/config.go | 14 ++-- api/go/core/types/config.go | 10 +++ api/go/go.mod | 8 +- api/go/go.sum | 27 +++++- api/go/handler/admin/admin_handler.go | 2 +- api/go/handler/chat_handler.go | 2 +- api/go/handler/upload_handler.go | 4 +- api/go/handler/user_handler.go | 20 ++++- api/go/handler/verify_handler.go | 116 ++++++++++++++++++++++++++ api/go/main.go | 10 +++ api/go/service/aliyun_sms_service.go | 54 ++++++++++++ api/go/service/sms_service.go | 5 ++ api/go/store/leveldb.go | 10 ++- api/go/store/model/user.go | 1 + api/go/store/vo/user.go | 1 + api/go/test/test.go | 30 +++++-- api/go/utils/crypto.go | 70 ++++++++++++++++ api/go/utils/strings.go | 19 +++-- database/sms.sql | 1 + web/src/views/Register.vue | 91 +++++++++++++++++--- 21 files changed, 457 insertions(+), 53 deletions(-) create mode 100644 api/go/handler/verify_handler.go create mode 100644 api/go/service/aliyun_sms_service.go create mode 100644 api/go/service/sms_service.go create mode 100644 api/go/utils/crypto.go create mode 100644 database/sms.sql diff --git a/api/go/core/app_server.go b/api/go/core/app_server.go index 2f8e44bf..32f92477 100644 --- a/api/go/core/app_server.go +++ b/api/go/core/app_server.go @@ -20,7 +20,7 @@ import ( type AppServer struct { Debug bool - AppConfig *types.AppConfig + Config *types.AppConfig Engine *gin.Engine ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message ChatConfig *types.ChatConfig // 聊天配置 @@ -37,7 +37,7 @@ func NewServer(appConfig *types.AppConfig) *AppServer { gin.DefaultWriter = io.Discard return &AppServer{ Debug: false, - AppConfig: appConfig, + Config: appConfig, Engine: gin.Default(), ChatContexts: types.NewLMap[string, []types.Message](), ChatSession: types.NewLMap[string, types.ChatSession](), @@ -53,11 +53,11 @@ func (s *AppServer) Init(debug bool) { s.Engine.Use(corsMiddleware()) } - s.Engine.Use(sessionMiddleware(s.AppConfig)) + s.Engine.Use(sessionMiddleware(s.Config)) s.Engine.Use(authorizeMiddleware(s)) s.Engine.Use(errorHandler) // 添加静态资源访问 - s.Engine.Static("/static", s.AppConfig.StaticDir) + s.Engine.Static("/static", s.Config.StaticDir) } func (s *AppServer) Run(db *gorm.DB) error { @@ -71,15 +71,15 @@ func (s *AppServer) Run(db *gorm.DB) error { if err != nil { return err } - logger.Infof("http://%s", s.AppConfig.Listen) - return s.Engine.Run(s.AppConfig.Listen) + logger.Infof("http://%s", s.Config.Listen) + return s.Engine.Run(s.Config.Listen) } // 全局异常处理 func errorHandler(c *gin.Context) { defer func() { if r := recover(); r != nil { - logger.Error("Handler Panic: %v\n", r) + logger.Errorf("Handler Panic: %v", r) debug.PrintStack() c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: types.ErrorMsg}) c.Abort() @@ -164,6 +164,7 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc { if c.Request.URL.Path == "/api/user/login" || c.Request.URL.Path == "/api/admin/login" || c.Request.URL.Path == "/api/user/register" || + strings.HasPrefix(c.Request.URL.Path, "/api/verify/") || strings.HasPrefix(c.Request.URL.Path, "/static/") || c.Request.URL.Path == "/api/admin/config/get" { c.Next() diff --git a/api/go/core/config.go b/api/go/core/config.go index d8f61985..b2f6bf34 100644 --- a/api/go/core/config.go +++ b/api/go/core/config.go @@ -15,13 +15,13 @@ var logger = logger2.GetLogger() func NewDefaultConfig() *types.AppConfig { return &types.AppConfig{ - Listen: "0.0.0.0:5678", - ProxyURL: "", - Manager: types.Manager{Username: "admin", Password: "admin123"}, - StaticDir: "./static", - StaticUrl: "http://localhost/5678/static", - Redis: types.RedisConfig{Host: "localhost", Port: 6379, Password: ""}, - + Listen: "0.0.0.0:5678", + ProxyURL: "", + Manager: types.Manager{Username: "admin", Password: "admin123"}, + StaticDir: "./static", + StaticUrl: "http://localhost/5678/static", + Redis: types.RedisConfig{Host: "localhost", Port: 6379, Password: ""}, + AesEncryptKey: utils.RandString(24), Session: types.Session{ Driver: types.SessionDriverCookie, SecretKey: utils.RandString(64), diff --git a/api/go/core/types/config.go b/api/go/core/types/config.go index 7d3718d1..0ccc223d 100644 --- a/api/go/core/types/config.go +++ b/api/go/core/types/config.go @@ -15,6 +15,16 @@ type AppConfig struct { StaticDir string // 静态资源目录 StaticUrl string // 静态资源 URL Redis RedisConfig // redis 连接信息 + + AesEncryptKey string + SmsConfig AliYunSmsConfig // 短信发送配置 +} + +type AliYunSmsConfig struct { + AccessKey string + AccessSecret string + Product string + Domain string } type RedisConfig struct { diff --git a/api/go/go.mod b/api/go/go.mod index d0ab0d0d..8a341c75 100644 --- a/api/go/go.mod +++ b/api/go/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/BurntSushi/toml v1.1.0 + github.com/aliyun/alibaba-cloud-sdk-go v1.62.405 github.com/gin-contrib/sessions v0.0.5 github.com/gin-gonic/gin v1.9.0 github.com/gorilla/websocket v1.5.0 @@ -24,7 +25,9 @@ require ( github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -33,6 +36,7 @@ require ( golang.org/x/net v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -48,10 +52,10 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ugorji/go/codec v1.2.9 // indirect - go.uber.org/atomic v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/fx v1.19.3 go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.6.0 diff --git a/api/go/go.sum b/api/go/go.sum index f7a87865..2f19b477 100644 --- a/api/go/go.sum +++ b/api/go/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.62.405 h1:cKNFQmeCQFN0WNfjScKoVrGi7vXxTVbkCvCqSrOf+P4= +github.com/aliyun/alibaba-cloud-sdk-go v1.62.405/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= @@ -32,6 +34,7 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -58,11 +61,17 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr 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/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -70,8 +79,10 @@ github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259 github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -79,9 +90,12 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480 h1:IFhPCcB0/HtnEN+ZoUGDT55YgFCymbFJ15kXqs3nv5w= github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480/go.mod h1:BijIqAP84FMYC4XbdJgjyMpiSjusU8x0Y0W9K2t0QtU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -103,10 +117,15 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 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/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= @@ -137,14 +156,18 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y= diff --git a/api/go/handler/admin/admin_handler.go b/api/go/handler/admin/admin_handler.go index cbb099c8..8777ff80 100644 --- a/api/go/handler/admin/admin_handler.go +++ b/api/go/handler/admin/admin_handler.go @@ -36,7 +36,7 @@ func (h *ManagerHandler) Login(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - manager := h.App.AppConfig.Manager + manager := h.App.Config.Manager if data.Username == manager.Username && data.Password == manager.Password { err := utils.SetLoginAdmin(c, manager) if err != nil { diff --git a/api/go/handler/chat_handler.go b/api/go/handler/chat_handler.go index 3707d80c..e18bdefa 100644 --- a/api/go/handler/chat_handler.go +++ b/api/go/handler/chat_handler.go @@ -394,7 +394,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, user vo.User, apiKey *strin request = request.WithContext(ctx) request.Header.Add("Content-Type", "application/json") - proxyURL := h.App.AppConfig.ProxyURL + proxyURL := h.App.Config.ProxyURL if proxyURL == "" { client = &http.Client{} } else { // 使用代理 diff --git a/api/go/handler/upload_handler.go b/api/go/handler/upload_handler.go index ca3248af..0398e7a0 100644 --- a/api/go/handler/upload_handler.go +++ b/api/go/handler/upload_handler.go @@ -47,7 +47,7 @@ func (h *UploadHandler) Upload(c *gin.Context) { // 生成上传文件路径 func (h *UploadHandler) genFilePath(filename string) (string, error) { now := time.Now() - dir := fmt.Sprintf("%s/upload/%d/%d", h.App.AppConfig.StaticDir, now.Year(), now.Month()) + dir := fmt.Sprintf("%s/upload/%d/%d", h.App.Config.StaticDir, now.Year(), now.Month()) _, err := os.Stat(dir) if err != nil { err = os.MkdirAll(dir, 0755) @@ -63,5 +63,5 @@ func (h *UploadHandler) genFilePath(filename string) (string, error) { func (h *UploadHandler) genFileUrl(filePath string) string { now := time.Now() filename := filepath.Base(filePath) - return fmt.Sprintf("%s/upload/%d/%d/%s", h.App.AppConfig.StaticUrl, now.Year(), now.Month(), filename) + return fmt.Sprintf("%s/upload/%d/%d/%s", h.App.Config.StaticUrl, now.Year(), now.Month(), filename) } diff --git a/api/go/handler/user_handler.go b/api/go/handler/user_handler.go index ee106878..1bb9e9ef 100644 --- a/api/go/handler/user_handler.go +++ b/api/go/handler/user_handler.go @@ -3,6 +3,7 @@ package handler import ( "chatplus/core" "chatplus/core/types" + "chatplus/store" "chatplus/store/model" "chatplus/store/vo" "chatplus/utils" @@ -21,10 +22,11 @@ type UserHandler struct { BaseHandler db *gorm.DB searcher *xdb.Searcher + levelDB *store.LevelDB } -func NewUserHandler(app *core.AppServer, db *gorm.DB, searcher *xdb.Searcher) *UserHandler { - handler := &UserHandler{db: db, searcher: searcher} +func NewUserHandler(app *core.AppServer, db *gorm.DB, searcher *xdb.Searcher, levelDB *store.LevelDB) *UserHandler { + handler := &UserHandler{db: db, searcher: searcher, levelDB: levelDB} handler.App = app return handler } @@ -35,6 +37,8 @@ func (h *UserHandler) Register(c *gin.Context) { var data struct { Username string `json:"username"` Password string `json:"password"` + Mobile string `json:"mobile"` + Code int `json:"code"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -52,6 +56,16 @@ func (h *UserHandler) Register(c *gin.Context) { return } + // 检查验证码 + key := CodeStorePrefix + data.Mobile + code, err := h.levelDB.Get(key) + if err != nil || int(code.(float64)) != data.Code { + resp.ERROR(c, "短信验证码错误") + return + } else { + _ = h.levelDB.Delete(key) // 删除短信验证码 + } + // check if the username is exists var item model.User tx := h.db.Where("username = ?", data.Username).First(&item) @@ -89,7 +103,7 @@ func (h *UserHandler) Register(c *gin.Context) { var cfg model.Config h.db.Where("marker = ?", "system").First(&cfg) var config types.SystemConfig - err := utils.JsonDecode(cfg.Config, &config) + err = utils.JsonDecode(cfg.Config, &config) if err != nil || config.UserInitCalls <= 0 { user.Calls = types.UserInitCalls } else { diff --git a/api/go/handler/verify_handler.go b/api/go/handler/verify_handler.go new file mode 100644 index 00000000..a28c36cd --- /dev/null +++ b/api/go/handler/verify_handler.go @@ -0,0 +1,116 @@ +package handler + +import ( + "chatplus/core" + "chatplus/core/types" + "chatplus/service" + "chatplus/store" + "chatplus/utils" + "chatplus/utils/resp" + "time" + + "github.com/gin-gonic/gin" +) + +// 生成验证的控制器 + +type VerifyHandler struct { + BaseHandler + sms *service.AliYunSmsService + db *store.LevelDB +} + +const TokenStorePrefix = "/tokens/" +const CodeStorePrefix = "/codes/" + +func NewVerifyHandler(app *core.AppServer, sms *service.AliYunSmsService, db *store.LevelDB) *VerifyHandler { + handler := &VerifyHandler{sms: sms, db: db} + handler.App = app + return handler +} + +type VerifyToken struct { + Token string + Timestamp int64 +} + +// Token 生成自验证 token +func (h *VerifyHandler) Token(c *gin.Context) { + // 确保是通过浏览器访问 + if c.GetHeader("Sec-Fetch-Mode") != "cors" { + resp.HACKER(c) + return + } + + token := VerifyToken{ + Token: utils.RandString(32), + Timestamp: time.Now().Unix(), + } + json := utils.JsonEncode(token) + encrypt, err := utils.AesEncrypt(h.App.Config.AesEncryptKey, []byte(json)) + if err != nil { + resp.ERROR(c, "Token 加密出错") + return + } + err = h.db.Put(TokenStorePrefix+token.Token, token) + if err != nil { + resp.ERROR(c, "Token 存储失败") + return + } + + resp.SUCCESS(c, encrypt) +} + +// SendMsg 发送验证码短信 +func (h *VerifyHandler) SendMsg(c *gin.Context) { + var data struct { + Mobile string `json:"mobile"` + Token string `json:"token"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + decrypt, err := utils.AesDecrypt(h.App.Config.AesEncryptKey, data.Token) + if err != nil { + resp.ERROR(c, "Token 解密失败") + return + } + + var token VerifyToken + err = utils.JsonDecode(string(decrypt), &token) + if err != nil { + resp.ERROR(c, "Token 解码失败") + return + } + + _, err = h.db.Get(TokenStorePrefix + token.Token) + if err != nil { + resp.HACKER(c) + return + } + + if time.Now().Unix()-token.Timestamp > 30 { + resp.ERROR(c, "Token 已过期,请刷新页面重试") + return + } + + code := utils.RandomNumber(6) + err = h.sms.SendVerifyCode(data.Mobile, code) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 每个 token 用完一次立即失效 + _ = h.db.Delete(TokenStorePrefix + token.Token) + // 存储验证码,等待后面注册验证 + err = h.db.Put(CodeStorePrefix+data.Mobile, code) + if err != nil { + resp.ERROR(c, "验证码保存失败") + return + } + + resp.SUCCESS(c) +} diff --git a/api/go/main.go b/api/go/main.go index c2945e1a..af4041b7 100644 --- a/api/go/main.go +++ b/api/go/main.go @@ -6,6 +6,7 @@ import ( "chatplus/handler" "chatplus/handler/admin" logger2 "chatplus/logger" + "chatplus/service" "chatplus/store" "context" "embed" @@ -103,6 +104,7 @@ func main() { fx.Provide(handler.NewUserHandler), fx.Provide(handler.NewChatHandler), fx.Provide(handler.NewUploadHandler), + fx.Provide(handler.NewVerifyHandler), fx.Provide(admin.NewConfigHandler), fx.Provide(admin.NewAdminHandler), @@ -110,6 +112,9 @@ func main() { fx.Provide(admin.NewUserHandler), fx.Provide(admin.NewChatRoleHandler), + // 创建服务 + fx.Provide(service.NewAliYunSmsService), + // 注册路由 fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) { group := s.Engine.Group("/api/role/") @@ -139,6 +144,11 @@ func main() { fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) { s.Engine.POST("/api/upload", h.Upload) }), + fx.Invoke(func(s *core.AppServer, h *handler.VerifyHandler) { + group := s.Engine.Group("/api/verify/") + group.GET("token", h.Token) + group.POST("sms", h.SendMsg) + }), // 管理后台控制器 fx.Invoke(func(s *core.AppServer, h *admin.ConfigHandler) { diff --git a/api/go/service/aliyun_sms_service.go b/api/go/service/aliyun_sms_service.go new file mode 100644 index 00000000..4e4c57e3 --- /dev/null +++ b/api/go/service/aliyun_sms_service.go @@ -0,0 +1,54 @@ +package service + +import ( + "chatplus/core/types" + "chatplus/store" + "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" +) + +type AliYunSmsService struct { + config *types.AppConfig + db *store.LevelDB + client *dysmsapi.Client +} + +func NewAliYunSmsService(config *types.AppConfig, db *store.LevelDB) (*AliYunSmsService, error) { + // 创建阿里云短信客户端 + client, err := dysmsapi.NewClientWithAccessKey( + "cn-hangzhou", + config.SmsConfig.AccessKey, + config.SmsConfig.AccessSecret) + if err != nil { + return nil, fmt.Errorf("failed to create client: %v", err) + } + + return &AliYunSmsService{ + config: config, + db: db, + client: client, + }, nil +} + +func (s *AliYunSmsService) SendVerifyCode(mobile string, code int) error { + // 创建短信请求并设置参数 + request := dysmsapi.CreateSendSmsRequest() + request.Scheme = "https" + request.Domain = s.config.SmsConfig.Domain + request.PhoneNumbers = mobile + request.SignName = "飞行的蜗牛" + request.TemplateCode = "SMS_281460317" + request.TemplateParam = fmt.Sprintf("{\"code\":\"%d\"}", code) // 短信模板中的参数 + + // 发送短信 + response, err := s.client.SendSms(request) + if err != nil { + return fmt.Errorf("failed to send SMS:%v", err) + } + + if response.Code != "OK" { + return fmt.Errorf("failed to send SMS:%v", response.Message) + } + + return nil +} diff --git a/api/go/service/sms_service.go b/api/go/service/sms_service.go new file mode 100644 index 00000000..371ea20e --- /dev/null +++ b/api/go/service/sms_service.go @@ -0,0 +1,5 @@ +package service + +type SmsService interface { + SendVerifyCode(mobile string, code int) error +} diff --git a/api/go/store/leveldb.go b/api/go/store/leveldb.go index 525bf3f6..18bf9d5a 100644 --- a/api/go/store/leveldb.go +++ b/api/go/store/leveldb.go @@ -30,13 +30,19 @@ func (db *LevelDB) Put(key string, value interface{}) error { return db.driver.Put([]byte(key), bytes, nil) } -func (db *LevelDB) Get(key string) ([]byte, error) { +func (db *LevelDB) Get(key string) (interface{}, error) { bytes, err := db.driver.Get([]byte(key), nil) if err != nil { return nil, err } - return bytes, nil + var value interface{} + err = json.Unmarshal(bytes, &value) + if err != nil { + return nil, err + } + + return value, nil } func (db *LevelDB) Search(prefix string) []string { diff --git a/api/go/store/model/user.go b/api/go/store/model/user.go index 01abc6a8..2e2e9684 100644 --- a/api/go/store/model/user.go +++ b/api/go/store/model/user.go @@ -3,6 +3,7 @@ package model type User struct { BaseModel Username string `gorm:"index:username,unique"` + Mobile string Password string Nickname string Avatar string diff --git a/api/go/store/vo/user.go b/api/go/store/vo/user.go index 95b6a5ba..df66132e 100644 --- a/api/go/store/vo/user.go +++ b/api/go/store/vo/user.go @@ -5,6 +5,7 @@ import "chatplus/core/types" type User struct { BaseVo Username string `json:"username"` + Mobile string `json:"mobile"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` Salt string `json:"salt"` // 密码盐 diff --git a/api/go/test/test.go b/api/go/test/test.go index be9c81c7..dd096565 100644 --- a/api/go/test/test.go +++ b/api/go/test/test.go @@ -1,27 +1,24 @@ package main import ( - "chatplus/core/types" "chatplus/store/model" "chatplus/store/vo" "chatplus/utils" "context" "fmt" - "github.com/lionsoul2014/ip2region/binding/golang/xdb" - "github.com/pkoukk/tiktoken-go" "io" "log" "net/http" "strings" "time" + + "github.com/lionsoul2014/ip2region/binding/golang/xdb" + "github.com/pkoukk/tiktoken-go" ) func main() { - lMap := types.NewLMap[string, types.ChatSession]() - lMap.Put("name", types.ChatSession{SessionId: utils.RandString(32)}) - - item := lMap.Get("abc") - fmt.Println(item) + //testAesEncrypt() + fmt.Println(utils.RandomNumber(6)) } // Http client 取消操作 @@ -143,3 +140,20 @@ func calTokens() { fmt.Println(len(token)) } + +func testAesEncrypt() { + // 加密 + text := []byte("this is a secret text") + key := utils.RandString(24) + encrypt, err := utils.AesEncrypt(key, text) + if err != nil { + panic(err) + } + fmt.Println("加密密文:", encrypt) + // 解密 + decrypt, err := utils.AesDecrypt(key, encrypt) + if err != nil { + panic(err) + } + fmt.Println("解密明文:", string(decrypt)) +} diff --git a/api/go/utils/crypto.go b/api/go/utils/crypto.go new file mode 100644 index 00000000..fbebbf07 --- /dev/null +++ b/api/go/utils/crypto.go @@ -0,0 +1,70 @@ +package utils + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "errors" +) + +// AesEncrypt 加密 +func AesEncrypt(keyStr string, data []byte) (string, error) { + //创建加密实例 + key := []byte(keyStr) + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + blockSize := block.BlockSize() + encryptBytes := pkcs7Padding(data, blockSize) + result := make([]byte, len(encryptBytes)) + //使用cbc加密模式 + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + //执行加密 + blockMode.CryptBlocks(result, encryptBytes) + return base64.StdEncoding.EncodeToString(result), nil +} + +// AesDecrypt 解密 +func AesDecrypt(keyStr string, dataStr string) ([]byte, error) { + //创建实例 + key := []byte(keyStr) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + data, err := base64.StdEncoding.DecodeString(dataStr) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + result := make([]byte, len(data)) + //执行解密 + blockMode.CryptBlocks(result, data) + //去除填充 + result, err = pkcs7UnPadding(result) + if err != nil { + return nil, err + } + return result, nil +} + +func pkcs7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padText...) +} + +func pkcs7UnPadding(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, errors.New("empty encrypt data") + } + unPadding := int(data[length-1]) + return data[:(length - unPadding)], nil +} diff --git a/api/go/utils/strings.go b/api/go/utils/strings.go index 5a796d30..ad400d61 100644 --- a/api/go/utils/strings.go +++ b/api/go/utils/strings.go @@ -22,17 +22,20 @@ func RandString(length int) string { } func RandomNumber(bit int) int { - rand.Seed(time.Now().UnixNano()) - min := 1 // min value - max := 1 //max value - for i := 0; i < bit; i++ { - min = min * 10 - max = max * 10 - } - max = max * 10 + min := intPow(10, bit-1) + max := intPow(10, bit) - 1 + return rand.Intn(max-min+1) + min } +func intPow(x, y int) int { + result := 1 + for i := 0; i < y; i++ { + result *= x + } + return result +} + func ContainsStr(slice []string, item string) bool { for _, e := range slice { if e == item { diff --git a/database/sms.sql b/database/sms.sql new file mode 100644 index 00000000..2f2cec62 --- /dev/null +++ b/database/sms.sql @@ -0,0 +1 @@ +ALTER TABLE `chatgpt_users` ADD `mobile` CHAR(11) NOT NULL COMMENT '手机号码' AFTER `username`; \ No newline at end of file diff --git a/web/src/views/Register.vue b/web/src/views/Register.vue index be134dc2..4c05316e 100644 --- a/web/src/views/Register.vue +++ b/web/src/views/Register.vue @@ -11,7 +11,7 @@
- @@ -48,6 +48,42 @@
+
+ + + +
+ +
+ + + + + + + + {{ + btnText + }} + + + +
+ @@ -70,17 +106,20 @@