diff --git a/apps/fushouxian-server-liangliangit/.processManager b/apps/fushouxian-server-liangliangit/.processManager new file mode 100644 index 0000000..10e5afb --- /dev/null +++ b/apps/fushouxian-server-liangliangit/.processManager @@ -0,0 +1 @@ +{"pid":36728} \ No newline at end of file diff --git a/apps/fushouxian-server-liangliangit/build.sh b/apps/fushouxian-server-liangliangit/build.sh new file mode 100644 index 0000000..e06668a --- /dev/null +++ b/apps/fushouxian-server-liangliangit/build.sh @@ -0,0 +1,51 @@ +case $3 in + "") + goarch="amd64" + releasePath="x86_64" + break + ;; + "arm") + goarch="arm" + releasePath="arm" + break + ;; + "arm64") + goarch="arm64" + releasePath="arm64" + break + ;; + "loong64") + goarch="loong64" + releasePath="loong64" + break + ;; + *) + echo '目标 ' $3 ' 无法编译' + exit + ;; +esac + +buildfile=$1 + + +case $2 in + "linux") echo '交叉编译目标为linux+'$goarch + echo "CGO_ENABLED=0 GOOS=linux GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=linux GOARCH=$goarch go build -o ./"${buildfile%%.*}-"$releasePath $1 + break + ;; + "windows") echo '交叉编译目标为windows+'$goarch + echo "CGO_ENABLED=0 GOOS=windows GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=windows GOARCH=$goarch go build -o ././"${buildfile%%.*}-"$releasePath".exe" $1 + break + ;; + "darwin") echo '交叉编译目标为maxos+'$goarch + echo "CGO_ENABLED=0 GOOS=darwin GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=darwin GOARCH=$goarch go build -o ./"${buildfile%%.*}-"$releasePath $1 + break + ;; + *) echo '参数不正确:'$goarch + break + ;; +esac + echo '脚本执行结束...' diff --git a/apps/fushouxian-server-liangliangit/config/config.json b/apps/fushouxian-server-liangliangit/config/config.json new file mode 100644 index 0000000..62ea81d --- /dev/null +++ b/apps/fushouxian-server-liangliangit/config/config.json @@ -0,0 +1,3 @@ +{ + "serverport":"9001" +} diff --git a/apps/fushouxian-server-liangliangit/config/dbconfig copy.json b/apps/fushouxian-server-liangliangit/config/dbconfig copy.json new file mode 100644 index 0000000..459825a --- /dev/null +++ b/apps/fushouxian-server-liangliangit/config/dbconfig copy.json @@ -0,0 +1,10 @@ +[ + { + "DbType":"mysql", + "IsMaster":true, + "Dsn":"root:Password1!@tcp(mysql-a.fanhaninfo.test:3306)/dev_digital?charset=utf8mb4", + "sqlMaxIdleConns":100, + "sqlMaxOpenConns":100, + "sqlLogLevel":2 + } +] \ No newline at end of file diff --git a/apps/fushouxian-server-liangliangit/config/dbconfig.json b/apps/fushouxian-server-liangliangit/config/dbconfig.json new file mode 100644 index 0000000..07eee43 --- /dev/null +++ b/apps/fushouxian-server-liangliangit/config/dbconfig.json @@ -0,0 +1,10 @@ +[ + { + "DbType":"mysql", + "IsMaster":true, + "Dsn":"root:12345678@tcp(127.0.0.1:3306)/fushouxian?charset=utf8mb4", + "sqlMaxIdleConns":1, + "sqlMaxOpenConns":1, + "sqlLogLevel":2 + } +] diff --git a/apps/fushouxian-server-liangliangit/config/rpaconfig.json b/apps/fushouxian-server-liangliangit/config/rpaconfig.json new file mode 100644 index 0000000..171003c --- /dev/null +++ b/apps/fushouxian-server-liangliangit/config/rpaconfig.json @@ -0,0 +1,5 @@ +{ + "rpa_url": "http://172.0.0.19:19005", + "username": "admin", + "password": "123" +} diff --git a/apps/fushouxian-server-liangliangit/config/togocdn.client.config.json b/apps/fushouxian-server-liangliangit/config/togocdn.client.config.json new file mode 100644 index 0000000..bfc19c2 --- /dev/null +++ b/apps/fushouxian-server-liangliangit/config/togocdn.client.config.json @@ -0,0 +1,7 @@ +{ + "Priority":100, + "ModuleName":"go", + "ServerUrls":[ + "wss://shop.ruixininfo.com/websocket/jsonrpc" + ] +} diff --git a/apps/fushouxian-server-liangliangit/fushouxian-server.go b/apps/fushouxian-server-liangliangit/fushouxian-server.go new file mode 100644 index 0000000..1944c69 --- /dev/null +++ b/apps/fushouxian-server-liangliangit/fushouxian-server.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + _ "src/init" + + "github.com/towgo/towgo/dao/basedboperat" + "github.com/towgo/towgo/dao/ormDriver/xormDriver" + "github.com/towgo/towgo/lib/processmanager" + "github.com/towgo/towgo/lib/system" + "github.com/towgo/towgo/towgo" +) + +var appName string = "fushouxian-server" +var appVersion string = "1.0.0" + +var basePath = system.GetPathOfProgram() + +func init() { + //初始化xorm数据库驱动 + var dbconfig []xormDriver.DsnConfig + system.ScanConfigJson(basePath+"/config/dbconfig.json", &dbconfig) + xormDriver.New(dbconfig) + + //设定默认orm引擎 + err := basedboperat.SetOrmEngine("xorm") + if err != nil { + log.Print(err.Error()) + } + +} +func main() { + pm := processmanager.GetManager() + for k, v := range os.Args { + switch v { + case "start": + if k == 1 { + if pm.Start() { + log.Print("启动成功") + start() + return + } else { + log.Print("启动失败:" + pm.Error.Error()) + return + } + } + case "restart": + if k == 1 { + if pm.ReStart() { + log.Print("重启成功") + start() + return + } else { + log.Print("重启失败:" + pm.Error.Error()) + } + return + } + case "stop": + if k == 1 { + if pm.Stop() { + log.Print("程序停止成功") + } else { + log.Print("程序停止失败:程序没有运行") + } + return + } + case "version": + if k == 1 { + fmt.Print(appName + ":" + appVersion + "\n") + os.Exit(0) + } + return + + } + } + log.Print("参数传递错误,有效参数如下:\n" + os.Args[0] + " start | stop | reload | stop") + +} + +func start() { + + moduleClientInit() + + conf := struct { + Serverport string `json:"serverport"` + }{} + system.ScanConfigJson(basePath+"/config/config.json", &conf) + + http.HandleFunc("/jsonrpc", towgo.HttpHandller) + + log.Print("http服务运行中:0.0.0.0:" + conf.Serverport + "\n") + http.ListenAndServe("0.0.0.0:"+conf.Serverport, nil) +} + +func moduleClientInit() { + var node towgo.EdgeServerNodeConfig + system.ScanConfigJson(basePath+"config/togocdn.client.config.json", &node) + node.Methods = towgo.GetMethods() + node.ModuleName = appName + for _, v := range node.ServerUrls { + node.ServerUrl = v + client := towgo.NewEdgeServerNode(node) + client.Connect() + } +} diff --git a/apps/fushouxian-server/.processManager b/apps/fushouxian-server/.processManager index fd460de..10e5afb 100644 --- a/apps/fushouxian-server/.processManager +++ b/apps/fushouxian-server/.processManager @@ -1 +1 @@ -{"pid":42691} \ No newline at end of file +{"pid":36728} \ No newline at end of file diff --git a/apps/fushouxian-server/config/dbconfig.json b/apps/fushouxian-server/config/dbconfig.json index fcc97a9..07eee43 100644 --- a/apps/fushouxian-server/config/dbconfig.json +++ b/apps/fushouxian-server/config/dbconfig.json @@ -2,7 +2,7 @@ { "DbType":"mysql", "IsMaster":true, - "Dsn":"server=127.0.0.1;user id=root;password=root;database=fushouxian", + "Dsn":"root:12345678@tcp(127.0.0.1:3306)/fushouxian?charset=utf8mb4", "sqlMaxIdleConns":1, "sqlMaxOpenConns":1, "sqlLogLevel":2 diff --git a/apps/fushouxian-server/config/togocdn.client.config.json b/apps/fushouxian-server/config/togocdn.client.config.json index 41a1e8f..bfc19c2 100644 --- a/apps/fushouxian-server/config/togocdn.client.config.json +++ b/apps/fushouxian-server/config/togocdn.client.config.json @@ -1,7 +1,7 @@ { "Priority":100, - "ModuleName":"digital-employee-server-go", + "ModuleName":"go", "ServerUrls":[ - "ws://172.0.0.19:19000/websocket/jsonrpc" + "wss://shop.ruixininfo.com/websocket/jsonrpc" ] } diff --git a/go.mod b/go.mod index 439e91f..21d9f28 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,11 @@ module src go 1.21.5 -require github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298 +require ( + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.825 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.825 + github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298 +) require ( github.com/go-sql-driver/mysql v1.7.1 // indirect diff --git a/go.sum b/go.sum index 996c99c..e33536b 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,10 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.825 h1:ijG2TLksawrFMdBpfFa5N/HjBZLGRPwmCgg0JFfFl6E= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.825/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.825 h1:F6NQyLl7IscbIV1NcXshJeQVt8nMcS/Ock77aBhYI+k= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.825/go.mod h1:TtDf9a/E/BsheaNyTdYUD3ttvTUIYODO/4bYv6ZW9tY= github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298 h1:oYz9djtL9h+k2OBxsk7i8Emu233eYJJ5RpAGFua5fHA= github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298/go.mod h1:mNKhCpd9uCgi1yKphteeXR0S2ZyUJ8UDVPwJ4LKos3s= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/init/Category.go b/init/Category.go index 4bf5381..62656c1 100644 --- a/init/Category.go +++ b/init/Category.go @@ -1,3 +1,3 @@ package init -import _ "src/module/category" +//import _ "src/module/category" diff --git a/init/usercenter.go b/init/usercenter.go new file mode 100644 index 0000000..202bebb --- /dev/null +++ b/init/usercenter.go @@ -0,0 +1,7 @@ +package init + +import "src/module/usercenter" + +func init() { + usercenter.InitManageApi() +} diff --git a/module/tencent/sms.go b/module/tencent/sms.go new file mode 100644 index 0000000..f66d699 --- /dev/null +++ b/module/tencent/sms.go @@ -0,0 +1,133 @@ +package tencent + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms +) + +type SMSResponse struct { + SendStatusSet []SendStatus `json:"SendStatusSet"` // 发送状态集 + RequestId string `json:"RequestId"` // 请求ID +} + +type SendStatus struct { + SerialNo string `json:"SerialNo"` // 序列号 + PhoneNumber string `json:"PhoneNumber"` // 手机号码 + Fee int `json:"Fee"` // 费用 + SessionContext string `json:"SessionContext"` // 会话上下文 + Code string `json:"Code"` // 状态码 + Message string `json:"Message"` // 消息 + IsoCode string `json:"IsoCode"` // 国际标准化组织代码 +} + +func SendSMSVerificationCode(templateID, signName, mobile, verificationCode string) error { + /* 必要步骤: + * 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 + * 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。 + * 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人, + * 以免泄露密钥对危及您的财产安全。 + * SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */ + credential := common.NewCredential( + // os.Getenv("TENCENTCLOUD_SECRET_ID"), + // os.Getenv("TENCENTCLOUD_SECRET_KEY"), + "AKID1EXk8c8b0U5lPh2TrEa1QnCeBphR9wka", + "sjbOE4sidUlx5EIORRRL6jGkAZDmGb1T", + ) + /* 非必要步骤: + * 实例化一个客户端配置对象,可以指定超时时间等配置 */ + cpf := profile.NewClientProfile() + + /* SDK默认使用POST方法。 + * 如果您一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */ + cpf.HttpProfile.ReqMethod = "POST" + + /* SDK有默认的超时时间,非必要请不要进行调整 + * 如有需要请在代码中查阅以获取最新的默认值 */ + // cpf.HttpProfile.ReqTimeout = 5 + + /* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */ + cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com" + + /* SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要修改这个字段 */ + cpf.SignMethod = "HmacSHA1" + + /* 实例化要请求产品(以sms为例)的client对象 + * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */ + client, _ := sms.NewClient(credential, "ap-guangzhou", cpf) + + /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 + * 您可以直接查询SDK源码确定接口有哪些属性可以设置 + * 属性可能是基本类型,也可能引用了另一个数据结构 + * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */ + request := sms.NewSendSmsRequest() + + /* 基本类型的设置: + * SDK采用的是指针风格指定参数,即使对于基本类型您也需要用指针来对参数赋值。 + * SDK提供对基本类型的指针引用封装函数 + * 帮助链接: + * 短信控制台: https://console.cloud.tencent.com/smsv2 + * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ + + /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ + // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 + request.SmsSdkAppId = common.StringPtr("1400878793") + + /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ + // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 + request.SignName = common.StringPtr(signName) + + /* 模板 ID: 必须填写已审核通过的模板 ID */ + // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 + request.TemplateId = common.StringPtr(templateID) + + /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/ + request.TemplateParamSet = common.StringPtrs([]string{verificationCode}) + + /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] + * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/ + request.PhoneNumberSet = common.StringPtrs([]string{"+86" + mobile}) + + /* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */ + request.SessionContext = common.StringPtr("") + + /* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */ + request.ExtendCode = common.StringPtr("") + + /* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId,无需填写该字段。注:月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */ + request.SenderId = common.StringPtr("") + + // 通过client对象调用想要访问的接口,需要传入请求对象 + response, err := client.SendSms(request) + // 处理异常 + if _, ok := err.(*errors.TencentCloudSDKError); ok { + fmt.Printf("An API error has returned: %s", err) + return err + } + // 非SDK异常,直接失败。实际代码中可以加入其他的处理。 + if err != nil { + return err + } + + var resp SMSResponse + + b, _ := json.Marshal(response.Response) + json.Unmarshal(b, &resp) + // 打印返回的json字符串 + + log.Print(resp) + + /* 当出现以下错误码时,快速解决方案参考 + * [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms) + */ + return nil +} diff --git a/module/usercenter/SMSVerificationCode.go b/module/usercenter/SMSVerificationCode.go new file mode 100644 index 0000000..d2a6618 --- /dev/null +++ b/module/usercenter/SMSVerificationCode.go @@ -0,0 +1,51 @@ +package usercenter + +import ( + "bytes" + "crypto/rand" + "math/big" + "sync" + "time" +) + +var loginOrRegSMSVerificationCode sync.Map + +// 登记注册用验证码 +func StoreLoginOrloginOrRegSMSVerificationCode(mobile string) (string, error) { + codeInterface, ok := loginOrRegSMSVerificationCode.Load(mobile) + if ok { + return codeInterface.(string), nil + } + verificationCode := randCharNumber(6) + loginOrRegSMSVerificationCode.Store(mobile, verificationCode) + go func(mobile string) { + time.Sleep(time.Second * 5 * 60) + loginOrRegSMSVerificationCode.Delete(mobile) + }(mobile) + return verificationCode, nil +} + +// 验证注册验证码 +func LoginOrRegSMSVerification(mobile, verificationCode string) bool { + codeInterface, ok := loginOrRegSMSVerificationCode.LoadAndDelete(mobile) + if !ok { + return false + } + if codeInterface.(string) == verificationCode { + return true + } else { + return false + } +} + +// 随机验证码 +func randCharNumber(size int) string { + char := "0123456789" + len64 := int64(len(char)) + var s bytes.Buffer + for i := 0; i < size; i++ { + in, _ := rand.Int(rand.Reader, big.NewInt(len64)) + s.WriteByte(char[in.Int64()]) + } + return s.String() +} diff --git a/module/usercenter/api.user.go b/module/usercenter/api.user.go new file mode 100644 index 0000000..ae6e449 --- /dev/null +++ b/module/usercenter/api.user.go @@ -0,0 +1,363 @@ +package usercenter + +import ( + "encoding/json" + "log" + "regexp" + "src/module/tencent" + + "github.com/towgo/towgo/dao/basedboperat" + "github.com/towgo/towgo/towgo" +) + +func InitManageApi() { + + //初始化API加载器 + initLoader() + + //注册JSON-RPC服务处理器method路由 + //账户登录 F + + towgo.SetFunc(_methodHead+"/user/loginOrRegByMobile", userLoginOrRegByMobile) + + //获取自己的账户信息 + towgo.SetFunc(_methodHead+"/user/myinfo", userMyinfo) + + //账户注销 F + towgo.SetFunc(_methodHead+"/user/logoff", userLogoff) + + //账户注册移动用户注册 + towgo.SetFunc(_methodHead+"/user/regByMobile", userRegByMobile) + + //获取注册短信验证码 + towgo.SetFunc(_methodHead+"/user/getLoginOrRegSMSVerificationCode", getLoginOrRegSMSVerificationCode) + + //修改密码 F + towgo.SetFunc(_methodHead+"/user/changepassword", userChangepassword) + +} + +func isPhoneNumber(input string) bool { + // 中国手机号码正则表达式 + // 13[0-9], 14[5,7,9], 15[0-3,5-9], 16[6], 17[0-8], 18[0-9], 19[1,8,9] + phoneNumberPattern := `^1([38][0-9]|14[579]|5[^4]|6[6]|7[0-8]|9[189])\d{8}$` + reg := regexp.MustCompile(phoneNumberPattern) + return reg.MatchString(input) +} + +func getLoginOrRegSMSVerificationCode(rpcConn towgo.JsonRpcConnection) { + var params struct { + Mobile string `json:"mobile"` + } + rpcConn.ReadParams(¶ms) + + if !isPhoneNumber(params.Mobile) { + rpcConn.WriteError(500, "手机号码非法") + return + } + + code, err := StoreLoginOrloginOrRegSMSVerificationCode(params.Mobile) + if err != nil { + rpcConn.WriteError(500, err.Error()) + return + } + + err = tencent.SendSMSVerificationCode("2030693", "蕊鑫信息科技", params.Mobile, code) + if err != nil { + rpcConn.WriteError(500, err.Error()) + return + } + + rpcConn.WriteResult("验证码已经发送,请查收") + +} + +// 注册用户 +func userRegByMobile(rpcConn towgo.JsonRpcConnection) { + result := map[string]interface{}{} //初始化结果参数 + + var params struct { + Mobile string `json:"mobile"` + VerificationCode string `json:"verification_code"` + } + + rpcConn.ReadParams(¶ms) + + if params.Mobile == "" { + rpcConn.WriteError(500, "手机号码不能为空") + return + } + if params.VerificationCode == "" { + rpcConn.WriteError(500, "验证码不能为空") + return + } + + if !LoginOrRegSMSVerification(params.Mobile, params.VerificationCode) { + rpcConn.WriteError(500, "验证码错误") + return + } + + var user User + user.Username = params.Mobile + user.Password = randCharNumber(8) + + /* + user := user{} + user.Nickname = jsonObj.Params.Nickname + user.Email = jsonObj.Params.Email + */ + Err := user.Reg(user.Username, user.Password) + if Err != nil { + rpcConn.WriteError(500, Err.Error()) + return + } + + //拼装结果返回 + result["id"] = user.ID + result["username"] = user.Username + rpcConn.WriteResult(result) +} + +// 用户登陆 +func userLogin(rpcConn towgo.JsonRpcConnection) { + result := map[string]interface{}{} //初始化结果参数 + var err error + + rpcResponse := rpcConn.GetRpcResponse() + jsonObj := struct { + Params struct { + Username string `json:"username"` + Password string `json:"password"` + } `json:"params"` + }{} + err = json.Unmarshal([]byte(rpcConn.Read()), &jsonObj) + if err != nil { + rpcResponse.Error.Set(1, err.Error()) + rpcConn.Write() + return + } + if jsonObj.Params.Username == "" { + rpcResponse.Error.Set(1001, "") + rpcConn.Write() + return + } + if jsonObj.Params.Password == "" { + rpcResponse.Error.Set(1002, "") + rpcConn.Write() + return + } + + user := User{} + loginErr := user.Login(jsonObj.Params.Username, jsonObj.Params.Password) + + if loginErr != nil { //模型层登陆成功 + //dblog.Write("user:info", fmt.Sprintf("%s@%s 登录失败! 错误信息:%s", user.Username, rpcConn.GetRemoteAddr(), loginErr.Error())) + rpcResponse.Error.Set(1, "用户名或密码错误") + rpcConn.Write() + return + } + + result["id"] = user.ID + result["username"] = user.Username + + result["token"] = user.UserToken.TokenKey + + //dblog.Write("user:info", fmt.Sprintf("%s@%s 登录成功!", user.Username, rpcConn.GetRemoteAddr())) + rpcConn.WriteResult(result) + +} + +// 用户手机验证码登陆 +func userLoginOrRegByMobile(rpcConn towgo.JsonRpcConnection) { + + var params struct { + Mobile string `json:"mobile"` + VerificationCode string `json:"verification_code"` + } + + rpcConn.ReadParams(¶ms) + + if params.Mobile == "" { + rpcConn.WriteError(500, "手机号码不能为空") + return + } + if params.VerificationCode == "" { + rpcConn.WriteError(500, "验证码不能为空") + return + } + + if !LoginOrRegSMSVerification(params.Mobile, params.VerificationCode) { + rpcConn.WriteError(500, "验证码错误") + return + } + + //验证通过 + + user := User{} + + basedboperat.Get(&user, nil, "username = ?", params.Mobile) + + if user.ID > 0 { + loginErr := user.LoginNoAuth(params.Mobile) + + if loginErr != nil { //模型层登陆成功 + log.Print(loginErr.Error()) + //dblog.Write("user:info", fmt.Sprintf("%s@%s 登录失败! 错误信息:%s", user.Username, rpcConn.GetRemoteAddr(), loginErr.Error())) + rpcConn.WriteError(500, "用户名或密码错误") + return + } + + user.Password = "" + user.Salt = "" + + //dblog.Write("user:info", fmt.Sprintf("%s@%s 登录成功!", user.Username, rpcConn.GetRemoteAddr())) + rpcConn.WriteResult(user) + return + } + + user.Username = params.Mobile + user.Password = randCharNumber(8) + + Err := user.Reg(user.Username, user.Password) + if Err != nil { + rpcConn.WriteError(500, Err.Error()) + return + } + + user.Password = "" + user.Salt = "" + rpcConn.WriteResult(user) + +} + +// token check +func userTokenCheck(rpcConn towgo.JsonRpcConnection) { + result := map[string]interface{}{} //初始化结果参数 + var err error + + rpcResponse := rpcConn.GetRpcResponse() + jsonObj := struct { + Session string `json:"session"` + Params struct { + Username string `json:"username"` + Userid int `json:"userid"` + Token string `json:"token"` + } `json:"params"` + }{} + err = json.Unmarshal([]byte(rpcConn.Read()), &jsonObj) + if err != nil { + rpcResponse.Error.Set(1, err.Error()) + rpcConn.Write() + return + } + + var user *User + user, err = user.LoginByToken(jsonObj.Params.Token) + if err != nil { + result["valid"] = false + rpcConn.WriteResult(result) + return + } + if user.ID == 0 { + result["valid"] = false + rpcConn.WriteResult(result) + return + } + + if !user.UserToken.Valid() { + result["valid"] = false + rpcConn.WriteResult(result) + return + } + + result["valid"] = true + rpcConn.WriteResult(result) +} + +// 用户注销 +func userLogoff(rpcConn towgo.JsonRpcConnection) { + //result := map[string]interface{}{} //初始化结果参数 + var err error + + rpcResponse := rpcConn.GetRpcResponse() + jsonObj := struct { + Session string `json:"session"` + }{} + err = json.Unmarshal([]byte(rpcConn.Read()), &jsonObj) + if err != nil { + rpcResponse.Error.Set(1, err.Error()) + rpcConn.Write() + return + } + + var user *User + user, err = user.LoginByToken(jsonObj.Session) + if err != nil { + rpcConn.WriteResult(map[string]string{"success": "ok"}) + return + } + if user.ID > 0 { + user.Logoff() + } + + rpcConn.WriteResult(map[string]string{"success": "ok"}) + +} + +func userMyinfo(rpcConn towgo.JsonRpcConnection) { + userSession, err := LoginByToken(rpcConn.GetRpcRequest().Session) + userSession.Token = rpcConn.GetRpcRequest().Session + if err != nil { + rpcConn.GetRpcResponse().Error.Set(401, err.Error()) + rpcConn.Write() + return + } + rpcConn.WriteResult(userSession) +} + +func userChangepassword(rpcConn towgo.JsonRpcConnection) { + result := map[string]interface{}{} //初始化结果参数 + var err error + + rpcResponse := rpcConn.GetRpcResponse() + jsonObj := struct { + Session string `json:"session"` + Params struct { + Oldpassword string `json:"oldpassword"` + Newpassword string `json:"newpassword"` + } `json:"params"` + }{} + err = json.Unmarshal([]byte(rpcConn.Read()), &jsonObj) + + if err != nil { + rpcResponse.Error.Set(1, err.Error()) + rpcConn.WriteResult(result) + return + } + + var user *User + user, err = user.LoginByToken(jsonObj.Session) + if err != nil { + rpcResponse.Error.Set(401, err.Error()) + rpcConn.WriteResult(result) + return + } + + if user.ID == 0 { + rpcResponse.Error.Set(1003, "") + rpcConn.WriteResult(result) + return + } + + err = user.Changepassword(jsonObj.Params.Oldpassword, jsonObj.Params.Newpassword) + if err != nil { + rpcResponse.Error.Set(1, err.Error()) + rpcConn.WriteResult(result) + return + } + + rpcConn.WriteResult(struct { + Success string `json:"success"` + }{Success: "ok"}) + +} diff --git a/module/usercenter/init.loader.go b/module/usercenter/init.loader.go new file mode 100644 index 0000000..ba3ac7b --- /dev/null +++ b/module/usercenter/init.loader.go @@ -0,0 +1,25 @@ +package usercenter + +import ( + "github.com/towgo/towgo/dao/ormDriver/xormDriver" +) + +var _methodHead string +var _tableHead string + +func SetMethodHead(methodHead string) { + _methodHead = methodHead +} + +func SetTableHead(tableHead string) { + _tableHead = tableHead +} + +func initLoader() { + + //token任务 + InitTokenTask() + + xormDriver.Sync2(new(User), new(UserToken)) + +} diff --git a/module/usercenter/model.user.go b/module/usercenter/model.user.go new file mode 100644 index 0000000..35df947 --- /dev/null +++ b/module/usercenter/model.user.go @@ -0,0 +1,331 @@ +package usercenter + +import ( + "errors" + + "github.com/towgo/towgo/dao/basedboperat" + "github.com/towgo/towgo/lib/system" +) + +func (User) TableName() string { + return _tableHead + "users" +} + +func (*User) CacheExpire() int64 { + return 5000 +} + +// 账户对象 关联账户信息 +type User struct { + ID int64 `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Password string `json:"password"` + Salt string `json:"-"` //密码加盐 + Email string `json:"email"` + Mobile string `json:"mobile"` + CanDelete bool `json:"-"` + AccessToken string `json:"-"` + Token string `json:"token" gorm:"-" xorm:"-"` + UserToken *UserToken `json:"-" gorm:"-" xorm:"-"` + CreatedAt int64 `json:"created_at"` //创建时间 + UpdatedAt int64 `json:"updated_at"` //更新时间 + GroupID int64 `json:"group_id"` // 用户组ID + Avatar string `json:"avatar"` // 用户头像 + Birthday string `json:"birthday"` // 用户生日 + Money string `json:"money"` // 用户资金 + Score int64 `json:"score"` // 用户积分 + //Verification Verification `json:"verification"` // 用户验证信息 + ChildUserCount int64 `json:"child_user_count"` // 子用户数量 + ChildUserCount1 int64 `json:"child_user_count_1"` // 第一层子用户数量 + ChildUserCount2 int64 `json:"child_user_count_2"` // 第二层子用户数量 + TotalConsume string `json:"total_consume"` // 总消费金额 + UserID int64 `json:"user_id" xorm:"-"` // 用户ID + Createtime int64 `json:"createtime"` // 创建时间 + Expiretime int64 `json:"expiretime"` // 过期时间 + ExpiresIn int64 `json:"expires_in"` // 过期时长 + //Group Group `json:"group"` // 用户组信息 +} + +type Verification struct { + Email int `json:"email"` // 邮箱验证状态 + Mobile int `json:"mobile"` // 手机验证状态 +} + +type Group struct { + Name string `json:"name"` // 用户组名 + Image string `json:"image"` // 用户组图片 +} + +// 注册 +func (a *User) Reg(username, password string) error { + + e := a.CheckForInput(username, password) + if e != nil { + return e + } + + //数据库查询出用户信息 + + finduser := User{} + basedboperat.Get(&finduser, nil, "username = ?", username) + + //检查用户名是否存在 + if username == finduser.Username { + return errors.New("账户已经存在") + } + + //生成密码 + a.NewPassword(password) + + a.Username = username + a.Mobile = username + a.CanDelete = true + + _, err := basedboperat.Create(a) // 通过数据的指针来创建 + if err != nil { + return err + } + _, err = a.CreateRelation() + return err +} + +// 检查输入参数 +func (a *User) CheckForInput(username, password string) error { + + if username == "" { + return errors.New("用户名不能为空") + } + + if password == "" { + return errors.New("密码不能为空") + } + + //防sql注入 + if system.FilteredSQLInject(username) { + return errors.New("用户名存在系统保留或非法的字符") + } + return nil +} + +func (a *User) NewPassword(newpassword string) { + if newpassword == "" { + return + } + //加密密码 + + password := system.MD5(newpassword) + + //生成salt + salt := system.RandCharCrypto(6) + + //密码加盐 + password = password + salt + + //混合加密 + password = system.MD5(password) + + a.Password = password + a.Salt = salt +} + +// 用户登陆 +func (a *User) Login(username, password string) error { + + erro := a.CheckForInput(username, password) + if erro != nil { + return erro + } + + //通过用户名查询用户数据 + err := basedboperat.Get(a, nil, "username = ?", username) + + if err != nil { + return err + } + //检查用户名是否存在 + + //判断用户是否存在 + if a.Username == "" { + return errors.New("用户名不存在") + } + + //加密密码 + upassword := system.MD5(password) + + //撒盐 + upassword = upassword + a.Salt + + //混合加密 + upassword = system.MD5(upassword) + + //判断密码是否一致 + if a.Password != upassword { + //不一致:返回错误 + return errors.New("密码错误") + } + + //验证通过 + + //生成用户信息 + a.UserToken = NewToken(a) + + return nil +} + +// 用户登陆 +func (a *User) LoginNoAuth(username string) error { + + //通过用户名查询用户数据 + err := basedboperat.Get(a, nil, "username = ?", username) + + if err != nil { + return err + } + //检查用户名是否存在 + + //判断用户是否存在 + if a.Username == "" { + return errors.New("用户名不存在") + } + + //验证通过 + + //生成用户信息 + a.UserToken = NewToken(a) + a.Token = a.UserToken.TokenKey + + return nil +} + +// 用户注销 +func (a *User) Logoff() { + DeleteToken(a.UserToken.TokenKey) +} + +func LoginByToken(tokenKey string) (*User, error) { + userToken, err := GetToken(tokenKey) + if err != nil { + return nil, err + } + sessionuser := userToken.Payload.(*User) + sessionuser.UserToken = userToken + return sessionuser, nil +} + +func (a *User) LoginByToken(tokenKey string) (*User, error) { + userToken, err := GetToken(tokenKey) + if err != nil { + return nil, err + } + sessionuser := userToken.Payload.(*User) + sessionuser.UserToken = userToken + return sessionuser, nil +} + +func (a *User) CheckToken(s string) bool { + //判断token是否正确 + if s != a.UserToken.TokenKey { + return false + } + //再判断token是否过期 + return a.UserToken.Valid() +} +func (a *User) Get() error { + + if a.ID > 0 { + return basedboperat.Get(a, nil, "id = ?", a.ID) + } + if a.Username != "" { + return basedboperat.Get(a, nil, "username = ?", a.Username) + } + return errors.New("id或username不能为空") +} + +// 修改密码 +func (a *User) Changepassword(oldpassword, newpassword string) error { + + //通过用户名查询用户数据 + + err := basedboperat.Get(a, nil, "id = ?", a.ID) + if err != nil { + return err + } + //检查用户名是否存在 + + //判断用户是否存在 + if a.Username == "" { + return errors.New("用户名不存在") + } + + //加密密码 + upassword := system.MD5(oldpassword) + + //撒盐 + upassword = upassword + a.Salt + + //混合加密 + upassword = system.MD5(upassword) + + //判断密码是否一致 + if a.Password != upassword { + //不一致:返回错误 + return errors.New("原始密码错误") + } + + a.NewPassword(newpassword) + + basedboperat.Update(a, []string{"password", "salt"}, "id = ?", a.ID) + + return nil +} + +func (a *User) Update() error { + var findModel User + basedboperat.Get(&findModel, nil, "id = ?", a.ID) + if findModel.ID <= 0 { + return errors.New("记录不存在") + } + a.DeleteRelation() + _, err := a.CreateRelation() + if err != nil { + return err + } + basedboperat.Update(a, []string{"nickname", "email"}, "id = ?", a.ID) + return nil +} + +func (a *User) Delete() (int64, error) { + var findModel User + basedboperat.Get(&findModel, nil, "id = ?", a.ID) + if !findModel.CanDelete { + return 0, errors.New("无法删除系统用户") + } + a.DeleteRelation() + return basedboperat.Delete(a, a.ID, nil) +} + +// 删除关联数据 +func (a *User) DeleteRelation() { + if a.ID == 0 { + return + } + +} + +// 创建关联数据 +func (a *User) CreateRelation() (int64, error) { + if a.ID == 0 { + return 0, nil + } + var rowsAffected int64 + + return rowsAffected, nil +} + +func (a *User) AfterQuery() { + if a.ID == 0 { + return + } + a.UserID = a.ID +} diff --git a/module/usercenter/model.user.token.go b/module/usercenter/model.user.token.go new file mode 100644 index 0000000..46cbcff --- /dev/null +++ b/module/usercenter/model.user.token.go @@ -0,0 +1,227 @@ +package usercenter + +import ( + "context" + "errors" + "log" + "sync" + "time" + + "github.com/towgo/towgo/dao/basedboperat" + "github.com/towgo/towgo/lib/system" +) + +// 缓存有效期 +var memCacheTimer int64 = 60 * 10 + +// token有效期 秒单位计算 +var expirationLimit int64 = 86400 * 20 + +var autoClearLimit int64 = 60 * 10 //10分钟清理一次过期的token + +// var expirationLimit int64 = 60 +var memCache *MemCache + +// UserToken结构体 +func (UserToken) TableName() string { + return _tableHead + "users_token" +} + +type UserToken struct { + TokenKey string + Uid int64 + Payload any `gorm:"-" xorm:"-"` + Expiration int64 + UpdatedAt int64 + CreatedAt int64 +} + +func InitTokenTask() { + memCache = &MemCache{} + autoTimeToClear() +} + +type MemCache struct { + timers sync.Map + cacheObject sync.Map + cancels sync.Map +} + +func (mc *MemCache) CreateTimerToDelete(tokenKey string) { + timer := time.NewTimer(time.Second * time.Duration(memCacheTimer)) + mc.timers.Store(tokenKey, timer) + ctx, cancel := context.WithCancel(context.Background()) + + mc.cancels.Store(tokenKey, cancel) + go mc.DeleteWhenTimeOut(ctx, tokenKey, timer) +} + +func (mc *MemCache) DeleteWhenTimeOut(ctx context.Context, tokenKey string, timer *time.Timer) { + select { + case <-timer.C: + mc.timers.Delete(tokenKey) + mc.cacheObject.Delete(tokenKey) + mc.cancels.Delete(tokenKey) + return + case <-ctx.Done(): + return + } +} + +func (mc *MemCache) ResetTimer(tokenKey string) { + timerInterface, ok := mc.timers.Load(tokenKey) + if ok { + timer := timerInterface.(*time.Timer) + timer.Reset(time.Second * time.Duration(memCacheTimer)) + } +} + +func (mc *MemCache) Add(tokenKey string, value any) { + mc.cacheObject.Store(tokenKey, value) + mc.CreateTimerToDelete(tokenKey) +} + +func (mc *MemCache) Del(tokenKey string) { + timerInterface, ok := mc.timers.Load(tokenKey) + if ok { + timer := timerInterface.(*time.Timer) + timer.Stop() //关闭定时器 + cancel_any, isLoaded := mc.cancels.LoadAndDelete(tokenKey) //关闭定时器线程 + if isLoaded { + cancel := cancel_any.(context.CancelFunc) + if cancel != nil { + cancel() + } + } + mc.timers.Delete(tokenKey) //清除定时器委托 + } + mc.cacheObject.Delete(tokenKey) //清除内存 +} + +func (mc *MemCache) Get(tokenKey string) (any, bool) { + return mc.cacheObject.Load(tokenKey) +} + +// 自动清理过期token定时器 +func autoTimeToClear() { + go func() { + defer func() { + err := recover() + if err != nil { + log.Print(err) + } + autoTimeToClear() + }() + var userToken UserToken + for { + time.Sleep(time.Second * time.Duration(autoClearLimit)) + basedboperat.SqlExec("delete from "+userToken.TableName()+" where expiration < ?", time.Now().Unix()) + } + }() +} + +func (t *UserToken) NewTokenGUID(salt string) { + guid := system.GetGUID().Hex() + saltEncode := system.MD5(salt) + tokenCode := system.MD5(guid + saltEncode) + t.TokenKey = tokenCode +} + +// 返回一个唯一标识的token令牌 +func (t *UserToken) String() string { + return t.TokenKey +} + +// token是否有效 检查有效期 +// 有效返回true +// 无效返回false +func (t *UserToken) Valid() bool { + return time.Now().Unix() < t.Expiration +} + +func (t *UserToken) Check(tokenKey string) bool { + return tokenKey == t.TokenKey +} + +// 更新token有效期 +func (t *UserToken) Update(expiration int64) { + if expiration > 0 { + t.Expiration = time.Now().Unix() + expiration + } else { + t.Expiration = time.Now().Unix() + expirationLimit + } +} + +// 新建一个token +func NewToken(user *User) *UserToken { + timenow := time.Now().Unix() + token := &UserToken{ + CreatedAt: timenow, + Uid: user.ID, + Payload: user, + Expiration: timenow + expirationLimit, + } + user.Token = token.TokenKey + token.NewTokenGUID(user.Salt) + basedboperat.Create(token) + memCache.Add(token.TokenKey, token) + return token +} + +func GetToken(tokenKey string) (*UserToken, error) { + var userToken *UserToken = &UserToken{} + + //缓存查询 + userTokenInterface, ok := memCache.Get(tokenKey) + if ok { + //缓存命中 + userToken = userTokenInterface.(*UserToken) + //缓存过期 清理 + if !userToken.Valid() { + memCache.Del(tokenKey) + return nil, errors.New("token过期(登录失效,请重新登录)") + } + } else { + //数据库查询 + + //查询持久化数据 + err := basedboperat.Get(userToken, nil, "token_key = ?", tokenKey) + if err != nil { + return nil, err + } + if userToken.Uid == 0 { + return nil, errors.New("token不存在(登录失效,请重新登录)") //数据不存在 + } + + //token过期 + if !userToken.Valid() { + return nil, errors.New("token过期(登录失效,请重新登录)") + } + + //查询token关联的用户 + var user *User = &User{} + basedboperat.Get(user, nil, "id = ?", userToken.Uid) + if user.ID == 0 { + return nil, errors.New("token关联用户不存在(登录失效,请重新登录)") //用户不存在 + } + + //写入缓存 + userToken.Payload = user + memCache.Add(userToken.TokenKey, userToken) + } + + return userToken, nil +} + +func DeleteToken(tokenKey string) { + memCache.Del(tokenKey) + var userToken *UserToken = &UserToken{} + userToken.TokenKey = tokenKey + + basedboperat.Delete(userToken, nil, "token_key = ?", tokenKey) + +} + +func SetExpiration(hour int64) { + expirationLimit = 3600 * hour +}