From c556053febf1b0dbeb64e7a5111dd2c2d97e9f71 Mon Sep 17 00:00:00 2001 From: henry Date: Wed, 29 Sep 2021 16:25:56 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AE=8C=E5=96=84=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/controller/account.go | 2 +- app/common/model/common.go | 31 ++++-- app/common/model/sys_user_tenant.go | 12 +- app/common/model/tenant_user.go | 39 ++++++- app/enterprise/api/account.go | 53 +++++++-- app/enterprise/api/config.go | 14 +++ app/enterprise/api/user.go | 3 + app/enterprise/controller/account/basic.go | 41 +++++++ app/enterprise/controller/account/login.go | 104 +++++++++++------- app/enterprise/controller/account/logout.go | 22 ++++ app/enterprise/controller/account/other.go | 15 +++ app/enterprise/controller/account/register.go | 68 ++++++++++++ app/enterprise/controller/config.go | 13 +++ app/enterprise/model/tenant_user.go | 9 +- app/service/session.go | 21 ++++ config/tenant.go | 19 ++++ 16 files changed, 382 insertions(+), 84 deletions(-) create mode 100644 app/enterprise/api/config.go create mode 100644 app/enterprise/api/user.go create mode 100644 app/enterprise/controller/account/basic.go create mode 100644 app/enterprise/controller/account/logout.go create mode 100644 app/enterprise/controller/account/other.go create mode 100644 app/enterprise/controller/account/register.go create mode 100644 app/enterprise/controller/config.go create mode 100644 config/tenant.go diff --git a/app/api/controller/account.go b/app/api/controller/account.go index f9fb006..25ce4b7 100644 --- a/app/api/controller/account.go +++ b/app/api/controller/account.go @@ -54,7 +54,7 @@ func (c *Account) Login(account, password, captchaKey, captchaValue, equipment, return nil, err } else if !isExist { return nil, errors.New("当前不属于任何租户,不可登录") - } else if mSysUserTenant.Status != model2.SysUserTenantStatusForEnable { + } else if mSysUserTenant.Status != model2.AccountStatusForEnable { return nil, errors.New("当前账号已禁用,不可登录,请联系管理员!") } if mSysUserTenant.TenantID <= 0 { diff --git a/app/common/model/common.go b/app/common/model/common.go index 9397103..f85933d 100644 --- a/app/common/model/common.go +++ b/app/common/model/common.go @@ -51,6 +51,26 @@ func (m *Images) AnalysisSlice(domain string) []string { return images } +// AccountStatus 账号状态 +type AccountStatus struct { + Status AccountStatusKind `gorm:"column:status;type:tinyint(1);default:1;comment:状态(1:启用,2:禁用)" json:"-"` +} + +// AccountStatusKind 状态 +type AccountStatusKind int + +const ( + // AccountStatusForEnable 启用 + AccountStatusForEnable AccountStatusKind = iota + 1 + // AccountStatusForDisable 禁用 + AccountStatusForDisable +) + +// Format 格式化 +func (m *AccountStatus) Format() string { + return utils.AnyToJSON(m) +} + // Position 坐标信息 type Position struct { Longitude float64 `json:"longitude"` // 经度 @@ -61,14 +81,3 @@ type Position struct { func (m *Position) Format() string { return utils.AnyToJSON(m) } - -// Tags 标签 -type Tags struct { - Key string `json:"key"` - Value string `json:"value"` -} - -// Format 格式化 -func (m *Tags) Format() string { - return utils.AnyToJSON(m) -} diff --git a/app/common/model/sys_user_tenant.go b/app/common/model/sys_user_tenant.go index c7732da..4cf172e 100644 --- a/app/common/model/sys_user_tenant.go +++ b/app/common/model/sys_user_tenant.go @@ -7,7 +7,7 @@ type SysUserTenant struct { Department string `gorm:"column:department;type:varchar(100);default:null;comment:部门信息" json:"department"` Role string `gorm:"column:role;type:varchar(100);default:null;comment:角色信息" json:"role"` Identity SysUserTenantIdentity `gorm:"column:identity;type:tinyint(1);default:0;comment:用户身份(1:管理员,2:用户)" json:"-"` - Status SysUserTenantStatus `gorm:"column:status;type:tinyint(1);default:1;comment:状态(1:启用,2:禁用)" json:"-"` + AccountStatus ModelDeleted ModelAt } @@ -21,16 +21,6 @@ const ( SysUserTenantIdentityForSystemUser ) -// SysUserTenantStatus 状态 -type SysUserTenantStatus int - -const ( - // SysUserTenantStatusForEnable 启用 - SysUserTenantStatusForEnable SysUserTenantStatus = iota + 1 - // SysUserTenantStatusForDisable 禁用 - SysUserTenantStatusForDisable -) - func (m *SysUserTenant) TableName() string { return m.NewTableName("sys_user_tenant") } diff --git a/app/common/model/tenant_user.go b/app/common/model/tenant_user.go index 891bb63..57f8e63 100644 --- a/app/common/model/tenant_user.go +++ b/app/common/model/tenant_user.go @@ -1,13 +1,26 @@ package model +import ( + "SciencesServer/utils" + "gorm.io/gorm" + "time" +) + type TenantUser struct { Model - ModelTenant - UUID uint64 `gorm:"column:uuid;uniqueIndex:idx_sys_user_uuid;type:int;default:0;comment:用户唯一UUID" json:"-"` - Avatar string `gorm:"column:avatar;type:varchar(255);default:null;comment:头像" json:"avatar"` - Name string `gorm:"column:name;type:varchar(20);default:null;comment:真实姓名" json:"name"` - Mobile string `gorm:"column:mobile;index:idx_sys_user_mobile;type:varchar(15);default:null;comment:联系方式" json:"mobile"` - Email string `gorm:"column:email;type:varchar(50);default:null;comment:邮箱" json:"email"` + UUID uint64 `gorm:"column:uuid;uniqueIndex:idx_tenant_user_uuid;type:int;default:0;comment:用户唯一UUID" json:"-"` + Avatar string `gorm:"column:avatar;type:varchar(255);default:null;comment:头像" json:"avatar"` + Name string `gorm:"column:name;type:varchar(20);default:null;comment:真实姓名" json:"name"` + Mobile string `gorm:"column:mobile;index:idx_tenant_user_mobile;type:varchar(15);default:null;comment:联系方式" json:"mobile"` + Email string `gorm:"column:email;type:varchar(50);default:null;comment:邮箱" json:"email"` + Identity int `gorm:"column:identity;type:int(8);default:0;comment:身份信息" json:"-"` + Password string `gorm:"column:password;type:varchar(100);default:null;comment:密码" json:"-"` + Salt string `gorm:"column:salt;type:varchar(10);default:null;comment:盐值" json:"-"` + Province uint64 `gorm:"column:province;type:int;default:0;comment:所在省" json:"province"` + City uint64 `gorm:"column:city;type:int;default:0;comment:所在市" json:"city"` + District uint64 `gorm:"column:district;type:int;default:0;comment:所在区/县" json:"district"` + Address string `gorm:"column:address;type:varchar(255);default:null;comment:详细地址" json:"address"` + AccountStatus ModelDeleted ModelAt } @@ -16,6 +29,20 @@ func (m *TenantUser) TableName() string { return m.NewTableName("tenant_user") } +func (m *TenantUser) BeforeCreate(db *gorm.DB) error { + m.NewPassword() + snowflake, _ := utils.NewSnowflake(1) + m.UUID = uint64(snowflake.GetID()) + m.Status = AccountStatusForEnable + m.CreatedAt = time.Now() + return nil +} + +func (m *TenantUser) NewPassword() { + m.Salt = utils.GetRandomString(8) + m.Password = utils.HashString([]byte(utils.Md5String(m.Password, m.Salt))) +} + func NewTenantUser() *TenantUser { return &TenantUser{} } diff --git a/app/enterprise/api/account.go b/app/enterprise/api/account.go index 2c3445e..f2ead78 100644 --- a/app/enterprise/api/account.go +++ b/app/enterprise/api/account.go @@ -3,6 +3,7 @@ package api import ( "SciencesServer/app/common/api" "SciencesServer/app/enterprise/controller/account" + "SciencesServer/app/service" "github.com/gin-gonic/gin" ) @@ -10,10 +11,17 @@ type Account struct{} type ( accountLoginForm struct { - Mode int `json:"mode" form:"mode" binding:"required"` + Mode int `json:"mode" form:"mode" binding:"required"` + Mobile string `json:"mobile" form:"mobile"` + Captcha string `json:"captcha" form:"captcha"` + Password string `json:"password" form:"password"` } - accountRegisterForm struct { + Name string `json:"name" form:"name" binding:"required"` + Mobile string `json:"mobile" form:"mobile" binding:"required"` + Captcha string `json:"captcha" form:"captcha" binding:"required"` + Password string `json:"password" form:"password" binding:"required"` + RepeatPass string `json:"repeat_pass" form:"repeat_pass" binding:"required"` } ) @@ -24,22 +32,45 @@ func (a *Account) Login(c *gin.Context) { api.APIFailure(err.(error))(c) return } - data, err := account.NewLogin().Login()(account.LoginMode(form.Mode), nil) + data, err := account.NewLogin()().Launch(account.LoginMode(form.Mode), &account.LoginRequest{ + Captcha: struct { + Mobile string + Captcha string + }{Mobile: form.Mobile, Captcha: form.Captcha}, + Password: struct { + Mobile string + Password string + }{Mobile: form.Mobile, Password: form.Password}, + }) api.APIResponse(err, data) } -func (a *Account) Register() { +func (a *Account) Register(c *gin.Context) { + form := new(accountRegisterForm) + if err := api.Bind(form)(c); err != nil { + api.APIFailure(err.(error))(c) + return + } + data, err := account.NewRegister()().Launch(&account.RegisterRequest{ + Name: form.Name, Mobile: form.Mobile, Captcha: form.Captcha, + Password: form.Password, RepeatPass: form.RepeatPass, + }) + api.APIResponse(err, data) } -func (c *Account) BindName() { - +func (a *Account) BindMobile() { + account.NewOther()().BindMobile() } -func (c *Account) BindMobile() { - -} - -func (a *Account) Logout() { +func (a *Account) Logout(c *gin.Context) { + handle := api.GetSession()(c) + session := new(service.SessionEnterprise) + + if handle != nil { + session = handle.(*service.SessionEnterprise) + } + err := account.NewLogout()(session).Launch() + api.APIResponse(err)(c) } diff --git a/app/enterprise/api/config.go b/app/enterprise/api/config.go new file mode 100644 index 0000000..81a14f1 --- /dev/null +++ b/app/enterprise/api/config.go @@ -0,0 +1,14 @@ +package api + +import ( + "SciencesServer/app/common/api" + "SciencesServer/app/enterprise/controller" + "github.com/gin-gonic/gin" +) + +type Config struct{} + +func (a *Config) Identity(c *gin.Context) { + data := controller.NewConfig().Identity() + api.APISuccess(data) +} diff --git a/app/enterprise/api/user.go b/app/enterprise/api/user.go new file mode 100644 index 0000000..64bd58e --- /dev/null +++ b/app/enterprise/api/user.go @@ -0,0 +1,3 @@ +package api + +type User struct{} diff --git a/app/enterprise/controller/account/basic.go b/app/enterprise/controller/account/basic.go new file mode 100644 index 0000000..8e932c7 --- /dev/null +++ b/app/enterprise/controller/account/basic.go @@ -0,0 +1,41 @@ +package account + +import ( + "SciencesServer/app/enterprise/model" + "SciencesServer/app/service" + "SciencesServer/config" + "SciencesServer/utils" +) + +type Account struct{} + +type LoginCallback func(user *model.TenantUser) *LoginResponse + +type ( + LoginResponse struct { + Token string `json:"token"` + EffectTime int `json:"effect_time"` + } +) + +func (c *Account) Login() LoginCallback { + return func(mTenantUser *model.TenantUser) *LoginResponse { + token := utils.JWTEncrypt(config.SettingInfo.TokenEffectTime, map[string]interface{}{ + config.TokenForUID: mTenantUser.UUID, + }) + session := service.NewSessionEnterprise() + session.Token = token + session.UID = mTenantUser.UUID + session.Name = mTenantUser.Name + session.Mobile = mTenantUser.Mobile + session.Identity = mTenantUser.Identity + + service.Publish(config.EventForRedisHashProduce, config.RedisKeyForAccount, mTenantUser.UUIDToString(), session) + + return &LoginResponse{Token: token, EffectTime: config.SettingInfo.TokenEffectTime} + } +} + +func NewAccount() *Account { + return &Account{} +} diff --git a/app/enterprise/controller/account/login.go b/app/enterprise/controller/account/login.go index 76b22f7..7f6ddc9 100644 --- a/app/enterprise/controller/account/login.go +++ b/app/enterprise/controller/account/login.go @@ -4,7 +4,6 @@ import ( model2 "SciencesServer/app/common/model" "SciencesServer/app/enterprise/model" "SciencesServer/app/handle" - "SciencesServer/config" "SciencesServer/utils" "errors" ) @@ -12,48 +11,46 @@ import ( type Login struct{} type ( - LoginHandle func(LoginMode, *LoginRequest) (*LoginResponse, error) + LoginHandle func() *Login ) type ( LoginRequest struct { Captcha struct { - Mobile string `json:"mobile"` - Captcha string `json:"captcha"` + Mobile string + Captcha string } Password struct { - Mobile string `json:"mobile"` - Password string `json:"password"` + Mobile string + Password string } Platform struct { - OpenID string `json:"open_id"` + OpenID string } } - LoginResponse struct { - Token string `json:"token"` - EffectTime int `json:"effect_time"` - } ) // LoginMode 登陆模式 type LoginMode int const ( - LoginModeForCaptcha LoginMode = iota + 1e2 + 1 // 验证码登陆 - LoginModeForPassword // 密码登陆 - LoginModeForWechat // 微信登陆 - LoginModeForQQ // QQ登陆 + LoginModeForSmsCaptcha LoginMode = iota + 1e2 + 1 // 短信验证码登陆 + LoginModeForPassword // 密码登陆 + LoginModeForWechat // 微信登陆 + LoginModeForQQ // QQ登陆 ) var loginHandle = map[LoginMode]func(*LoginRequest) (*model.TenantUser, error){ - LoginModeForCaptcha: loginForCaptcha, LoginModeForPassword: loginForPassword, + LoginModeForSmsCaptcha: loginForSmsCaptcha, LoginModeForPassword: loginForPassword, } -// loginForCaptcha 验证码登陆 -func loginForCaptcha(req *LoginRequest) (*model.TenantUser, error) { +// loginForSmsCaptcha 短信验证码登陆 +func loginForSmsCaptcha(req *LoginRequest) (*model.TenantUser, error) { + if !utils.ValidateMobile(req.Captcha.Mobile) { + return nil, errors.New("手机号码格式异常") + } pass, err := handle.NewCaptcha().Validate(&handle.CaptchaSms{ - Mobile: req.Captcha.Mobile, - Captcha: req.Captcha.Captcha, + Mobile: req.Captcha.Mobile, Captcha: req.Captcha.Captcha, }) if err != nil { return nil, err @@ -64,20 +61,26 @@ func loginForCaptcha(req *LoginRequest) (*model.TenantUser, error) { mTenantUsr := model.NewTenantUser() - if isExist, err = model2.FirstField(mTenantUsr.TenantUser, []string{"id", "name", "status"}, + if isExist, err = model2.FirstField(mTenantUsr.TenantUser, []string{"id", "uuid", "name", "mobile", "status"}, model2.NewWhere("mobile", req.Captcha.Mobile)); err != nil { return nil, err } else if isExist { - return nil, errors.New("当前手机号码未注册") + return mTenantUsr, nil } + mTenantUsr.Name = req.Captcha.Mobile + mTenantUsr.Password = utils.GetRandomString(12) return mTenantUsr, nil } // loginForPassword 密码登陆 func loginForPassword(req *LoginRequest) (*model.TenantUser, error) { + if !utils.ValidateMobile(req.Password.Mobile) { + return nil, errors.New("手机号码格式异常") + } mTenantUsr := model.NewTenantUser() - isExist, err := model2.FirstField(mTenantUsr.TenantUser, []string{"id", "name", "status"}, + isExist, err := model2.FirstField(mTenantUsr.TenantUser, []string{"id", "uuid", "name", "mobile", + "password", "salt", "status"}, model2.NewWhere("mobile", req.Password.Mobile)) if err != nil { @@ -95,27 +98,46 @@ func loginForPlatform(req *LoginRequest) error { return nil } -func (c *Login) Login() LoginHandle { - return func(mode LoginMode, req *LoginRequest) (*LoginResponse, error) { - handle, has := loginHandle[mode] +func (c *Login) Launch(mode LoginMode, req *LoginRequest) (*LoginResponse, error) { + handle, has := loginHandle[mode] - if !has { - return nil, errors.New("未知的登陆模式") - } - user, err := handle(req) + if !has { + return nil, errors.New("未知的登陆模式") + } + user, err := handle(req) - if err != nil { - return nil, err - } - token := utils.JWTEncrypt(config.SettingInfo.TokenEffectTime, map[string]interface{}{ - config.TokenForUID: user.UUID, - }) - return &LoginResponse{ - Token: token, EffectTime: config.SettingInfo.TokenEffectTime, - }, nil + if err != nil { + return nil, err + } + if user.Status != model2.AccountStatusForEnable { + return nil, errors.New("该账号已禁止登陆,请联系管理员") + } + return NewAccount().Login()(user), err +} + +func (c *Login) BindName(token, name string) { + // 解析token + tokenInfo := utils.JWTDecrypt(token) + + if tokenInfo == nil { + //return errors.New("") } } -func NewLogin() *Login { - return &Login{} +func (c *Login) BindMobile(token, mobile, captcha string) (*LoginResponse, error) { + pass, err := handle.NewCaptcha().Validate(&handle.CaptchaSms{ + Mobile: mobile, Captcha: captcha, + }) + if err != nil { + return nil, err + } else if !pass { + return nil, errors.New("验证码错误或已过期") + } + return nil, err +} + +func NewLogin() LoginHandle { + return func() *Login { + return &Login{} + } } diff --git a/app/enterprise/controller/account/logout.go b/app/enterprise/controller/account/logout.go new file mode 100644 index 0000000..8e9aa6f --- /dev/null +++ b/app/enterprise/controller/account/logout.go @@ -0,0 +1,22 @@ +package account + +import ( + "SciencesServer/app/service" + "SciencesServer/config" + "SciencesServer/utils" +) + +type Logout struct{ *service.SessionEnterprise } + +type LogoutHandle func(*service.SessionEnterprise) *Logout + +func (c *Logout) Launch() error { + service.Publish(config.EventForRedisHashDestroy, config.RedisKeyForAccount, utils.UintToString(c.UID)) + return nil +} + +func NewLogout() LogoutHandle { + return func(enterprise *service.SessionEnterprise) *Logout { + return &Logout{enterprise} + } +} diff --git a/app/enterprise/controller/account/other.go b/app/enterprise/controller/account/other.go new file mode 100644 index 0000000..e7694d6 --- /dev/null +++ b/app/enterprise/controller/account/other.go @@ -0,0 +1,15 @@ +package account + +type Other struct{} + +type OtherHandle func() *Other + +func (c *Other) BindMobile() { + +} + +func NewOther() OtherHandle { + return func() *Other { + return &Other{} + } +} diff --git a/app/enterprise/controller/account/register.go b/app/enterprise/controller/account/register.go new file mode 100644 index 0000000..8496d97 --- /dev/null +++ b/app/enterprise/controller/account/register.go @@ -0,0 +1,68 @@ +package account + +import ( + model2 "SciencesServer/app/common/model" + "SciencesServer/app/enterprise/model" + "SciencesServer/app/handle" + "errors" +) + +type Register struct{} + +type RegisterHandle func() *Register + +type ( + RegisterRequest struct { + Name, Mobile, Captcha, Password, RepeatPass string + } +) + +func (c *RegisterRequest) checkPassword() bool { + return c.Password == c.RepeatPass +} + +func (c *RegisterRequest) checkUserExist(mTenantUser *model.TenantUser) (bool, error) { + var count int64 + + if err := model2.Count(mTenantUser.TenantUser, &count, model2.NewWhere("mobile", c.Mobile)); err != nil { + return false, err + } + return count > 0, nil +} +func (c *RegisterRequest) checkCaptcha() (bool, error) { + return handle.NewCaptcha().Validate(&handle.CaptchaSms{Captcha: c.Captcha, Mobile: c.Mobile}) +} + +func (c *Register) Launch(req *RegisterRequest) (*LoginResponse, error) { + if req.checkPassword() { + return nil, errors.New("两次密码不一致") + } + mTenantUser := model.NewTenantUser() + + pass, err := req.checkUserExist(mTenantUser) + + if err != nil { + return nil, err + } else if pass { + return nil, errors.New("当前手机号码已注册") + } + if pass, err = req.checkCaptcha(); err != nil { + return nil, err + } else if !pass { + return nil, errors.New("验证码错误或已过期") + } + mTenantUser.Name = req.Name + mTenantUser.Mobile = req.Mobile + mTenantUser.Password = req.Password + + if err = model2.Create(mTenantUser.TenantUser); err != nil { + return nil, err + } + return NewAccount().Login()(mTenantUser), err +} + +func NewRegister() RegisterHandle { + return func() *Register { + return &Register{} + } +} diff --git a/app/enterprise/controller/config.go b/app/enterprise/controller/config.go new file mode 100644 index 0000000..55c91cb --- /dev/null +++ b/app/enterprise/controller/config.go @@ -0,0 +1,13 @@ +package controller + +import "SciencesServer/config" + +type Config struct{} + +func (c *Config) Identity() map[uint]string { + return config.TenantUserIdentityData +} + +func NewConfig() *Config { + return &Config{} +} diff --git a/app/enterprise/model/tenant_user.go b/app/enterprise/model/tenant_user.go index d2635d0..08b3507 100644 --- a/app/enterprise/model/tenant_user.go +++ b/app/enterprise/model/tenant_user.go @@ -1,17 +1,20 @@ package model -import "SciencesServer/app/common/model" +import ( + "SciencesServer/app/common/model" + "SciencesServer/utils" +) type TenantUser struct { *model.TenantUser } func (m *TenantUser) UUIDToString() string { - return "" + return utils.UintToString(m.UUID) } func (m *TenantUser) ValidatePassword(password string) bool { - return true + return utils.HashCompare([]byte(m.Password), []byte(utils.Md5String(password, m.Salt))) } func NewTenantUser() *TenantUser { diff --git a/app/service/session.go b/app/service/session.go index 11832c4..9c4c9ba 100644 --- a/app/service/session.go +++ b/app/service/session.go @@ -26,3 +26,24 @@ func (this *Session) UnmarshalBinary(data []byte) error { func NewSession() *Session { return &Session{} } + +// SessionEnterprise 企业用户 +type SessionEnterprise struct { + UID uint64 `json:"uid"` // 唯一标识ID + Token string `json:"token"` // token + Name string `json:"name"` // 名称 + Mobile string `json:"mobile"` // 手机号码 + Identity int `json:"identity"` // 身份信息 +} + +func (this *SessionEnterprise) MarshalBinary() ([]byte, error) { + return json.Marshal(this) +} + +func (this *SessionEnterprise) UnmarshalBinary(data []byte) error { + return utils.FromJSONBytes(data, this) +} + +func NewSessionEnterprise() *SessionEnterprise { + return &SessionEnterprise{} +} diff --git a/config/tenant.go b/config/tenant.go new file mode 100644 index 0000000..bd61458 --- /dev/null +++ b/config/tenant.go @@ -0,0 +1,19 @@ +package config + +// TenantUserIdentity 租户用户身份信息 +type TenantUserIdentity uint + +const ( + TenantUserIdentityForCompany uint = 1 // 1 + TenantUserIdentityForExpert uint = 1 << 1 // 2 + TenantUserIdentityForResearch uint = 1 << 2 // 4 + TenantUserIdentityForLaboratory uint = 1 << 3 // 8 + TenantUserIdentityForAgent uint = 1 << 4 // 16 +) + +// TenantUserIdentityData 用户身份信息 +var TenantUserIdentityData = map[uint]string{ + TenantUserIdentityForCompany: "企业", TenantUserIdentityForExpert: "专家", + TenantUserIdentityForResearch: "研究机构", TenantUserIdentityForLaboratory: "实验室", + TenantUserIdentityForAgent: "即可经纪人", +}