update for new user

zhangzeliang
liangliangit 2 years ago
parent be6eca5128
commit 814258931b

@ -1 +1 @@
{"pid":27289} {"pid":28155}

@ -2,7 +2,11 @@ module src
go 1.21.5 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 ( require (
github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect

@ -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/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 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 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 h1:oYz9djtL9h+k2OBxsk7i8Emu233eYJJ5RpAGFua5fHA=
github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298/go.mod h1:mNKhCpd9uCgi1yKphteeXR0S2ZyUJ8UDVPwJ4LKos3s= github.com/towgo/towgo v0.0.0-20231224080502-c72f73646298/go.mod h1:mNKhCpd9uCgi1yKphteeXR0S2ZyUJ8UDVPwJ4LKos3s=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

@ -1,3 +1,3 @@
package init package init
import _ "src/module/category" //import _ "src/module/category"

@ -0,0 +1,7 @@
package init
import "src/module/usercenter"
func init() {
usercenter.InitManageApi()
}

@ -0,0 +1,112 @@
package tencent
import (
"encoding/json"
"fmt"
"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
)
func SendSMSVerificationCode(templateID, signName, mobile, verificationCode string) error {
/*
* secretIdsecretKey
*
*
*
* SecretIdSecretKey : 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
* 使GETGET */
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 + 8613711112222200*/
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
}
b, _ := json.Marshal(response.Response)
// 打印返回的json字符串
fmt.Printf("%s", b)
/*
* [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
}

@ -0,0 +1,51 @@
package usercenter
import (
"bytes"
"crypto/rand"
"math/big"
"sync"
"time"
)
var regSMSVerificationCode sync.Map
// 登记注册用验证码
func StoreRegSMSVerificationCode(mobile string) (string, error) {
codeInterface, ok := regSMSVerificationCode.Load(mobile)
if ok {
return codeInterface.(string), nil
}
verificationCode := randCharNumber(6)
regSMSVerificationCode.Store(mobile, verificationCode)
go func(mobile string) {
time.Sleep(time.Second * 5 * 60)
regSMSVerificationCode.Delete(mobile)
}(mobile)
return verificationCode, nil
}
// 验证注册验证码
func RegSMSVerification(mobile, verificationCode string) bool {
codeInterface, ok := regSMSVerificationCode.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()
}

@ -0,0 +1,293 @@
package usercenter
import (
"encoding/json"
"regexp"
"src/module/tencent"
"github.com/towgo/towgo/towgo"
)
func InitManageApi() {
//初始化API加载器
initLoader()
//注册JSON-RPC服务处理器method路由
//账户登录 F
towgo.SetFunc(_methodHead+"/user/login", userLogin)
//获取自己的账户信息
towgo.SetFunc(_methodHead+"/user/myinfo", userMyinfo)
//账户注销 F
towgo.SetFunc(_methodHead+"/user/logoff", userLogoff)
//账户注册移动用户注册
towgo.SetFunc(_methodHead+"/user/regByMobile", userRegByMobile)
//获取注册短信验证码
towgo.SetFunc(_methodHead+"/user/getRegSMSVerificationCode", getRegSMSVerificationCode)
//修改密码 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 getRegSMSVerificationCode(rpcConn towgo.JsonRpcConnection) {
var params struct {
Mobile string `json:"mobile"`
}
rpcConn.ReadParams(&params)
if !isPhoneNumber(params.Mobile) {
rpcConn.WriteError(500, "手机号码非法")
return
}
code, err := StoreRegSMSVerificationCode(params.Mobile)
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(&params)
if params.Mobile == "" {
rpcConn.WriteError(500, "手机号码不能为空")
return
}
if params.VerificationCode == "" {
rpcConn.WriteError(500, "验证码不能为空")
return
}
if !RegSMSVerification(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)
}
// 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"})
}

@ -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))
}

@ -0,0 +1,280 @@
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"` //更新时间
}
// 注册
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.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) 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
}
}

@ -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
}
Loading…
Cancel
Save