feat: 迁移商务登录品牌和三步找回密码
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
)
|
||||
|
||||
func Migrations() []migration.Step {
|
||||
return []migration.Step{{ID: "202611030004_business_binding_locks", Migrate: func(db *gorm.DB) error {
|
||||
return []migration.Step{{ID: "202611030005_business_password_reset", Migrate: func(db *gorm.DB) error {
|
||||
return migration.CreateMissingTables(db, &resetCodePO{}, &resetTokenPO{})
|
||||
}}, {ID: "202611030004_business_binding_locks", Migrate: func(db *gorm.DB) error {
|
||||
return migration.CreateMissingTables(db, &bindingLockPO{})
|
||||
}}, {ID: "202611030003_business_bindings", Migrate: func(db *gorm.DB) error {
|
||||
return migration.CreateMissingTables(db, &bindingPO{})
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"kra/internal/biz/business"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resetCodePO struct {
|
||||
UserID uint `gorm:"primaryKey;autoIncrement:false"`
|
||||
CodeHash string `gorm:"size:64"`
|
||||
ExpiresAt, SendAfter *time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (resetCodePO) TableName() string { return "sys_business_password_codes" }
|
||||
|
||||
type resetTokenPO struct {
|
||||
Hash string `gorm:"column:token_hash;primaryKey;size:64"`
|
||||
UserID uint `gorm:"index"`
|
||||
ExpiresAt time.Time `gorm:"index"`
|
||||
CreatedAt time.Time
|
||||
Failures int
|
||||
}
|
||||
|
||||
func (resetTokenPO) TableName() string { return "sys_business_password_resets" }
|
||||
|
||||
type passwordResetRepo struct{ data Provider }
|
||||
|
||||
func NewPasswordResetRepo(data Provider) business.PasswordResetRepo {
|
||||
return &passwordResetRepo{data: data}
|
||||
}
|
||||
|
||||
type resetTxKey struct{}
|
||||
|
||||
func (r *passwordResetRepo) db(ctx context.Context) *gorm.DB {
|
||||
if tx, ok := ctx.Value(resetTxKey{}).(*gorm.DB); ok {
|
||||
return tx.WithContext(ctx)
|
||||
}
|
||||
return r.data.DB().WithContext(ctx)
|
||||
}
|
||||
func resetUser(po accountPO) *business.ResetUser {
|
||||
return &business.ResetUser{ID: po.ID, Username: po.Username, Phone: po.Phone, Enabled: po.Enable == 1, Status: po.BusinessStatus}
|
||||
}
|
||||
func (r *passwordResetRepo) UserByName(ctx context.Context, name string) (*business.ResetUser, error) {
|
||||
var po accountPO
|
||||
err := r.db(ctx).Select("id,username,phone,enable,business_status").Where("username = ?", name).First(&po).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
return resetUser(po), nil
|
||||
}
|
||||
func (r *passwordResetRepo) UserByID(ctx context.Context, id uint) (*business.ResetUser, error) {
|
||||
var po accountPO
|
||||
err := r.db(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("id,username,phone,enable,business_status").First(&po, id).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
return resetUser(po), nil
|
||||
}
|
||||
func (r *passwordResetRepo) WithCodeLock(ctx context.Context, id uint, fn func(context.Context) error) error {
|
||||
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := resetCodePO{UserID: id}
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&po, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(context.WithValue(ctx, resetTxKey{}, tx))
|
||||
})
|
||||
return storageError(err)
|
||||
}
|
||||
func (r *passwordResetRepo) Code(ctx context.Context, id uint) (*business.ResetCode, error) {
|
||||
var po resetCodePO
|
||||
if err := r.db(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, storageError(err)
|
||||
}
|
||||
return &business.ResetCode{UserID: po.UserID, CodeHash: po.CodeHash, ExpiresAt: po.ExpiresAt, SendAfter: po.SendAfter}, nil
|
||||
}
|
||||
func (r *passwordResetRepo) SaveCode(ctx context.Context, c *business.ResetCode) error {
|
||||
return storageError(r.db(ctx).Model(&resetCodePO{}).Where("user_id = ?", c.UserID).Updates(map[string]any{"code_hash": c.CodeHash, "expires_at": c.ExpiresAt, "send_after": c.SendAfter}).Error)
|
||||
}
|
||||
func (r *passwordResetRepo) CreateToken(ctx context.Context, t *business.ResetToken) error {
|
||||
if err := r.db(ctx).Where("expires_at < ?", t.CreatedAt).Delete(&resetTokenPO{}).Error; err != nil {
|
||||
return storageError(err)
|
||||
}
|
||||
return storageError(r.db(ctx).Create(&resetTokenPO{Hash: t.Hash, UserID: t.UserID, ExpiresAt: t.ExpiresAt, CreatedAt: t.CreatedAt, Failures: t.Failures}).Error)
|
||||
}
|
||||
func (r *passwordResetRepo) WithTokenLock(ctx context.Context, key string, fn func(context.Context) error) error {
|
||||
return storageError(r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { return fn(context.WithValue(ctx, resetTxKey{}, tx)) }))
|
||||
}
|
||||
func (r *passwordResetRepo) Token(ctx context.Context, key string) (*business.ResetToken, error) {
|
||||
var po resetTokenPO
|
||||
err := r.db(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&po, "token_hash = ?", key).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, storageError(err)
|
||||
}
|
||||
return &business.ResetToken{Hash: po.Hash, UserID: po.UserID, ExpiresAt: po.ExpiresAt, CreatedAt: po.CreatedAt, Failures: po.Failures}, nil
|
||||
}
|
||||
func (r *passwordResetRepo) SaveToken(ctx context.Context, t *business.ResetToken) error {
|
||||
return storageError(r.db(ctx).Model(&resetTokenPO{}).Where("token_hash = ?", t.Hash).Update("failures", t.Failures).Error)
|
||||
}
|
||||
func (r *passwordResetRepo) DeleteToken(ctx context.Context, key string) error {
|
||||
return storageError(r.db(ctx).Where("token_hash = ?", key).Delete(&resetTokenPO{}).Error)
|
||||
}
|
||||
func (r *passwordResetRepo) UpdatePassword(ctx context.Context, id uint, hash string) error {
|
||||
result := r.db(ctx).Model(&accountPO{}).Where("id = ?", id).Updates(map[string]any{"password": hash, "password_updated_at": time.Now(), "must_change_password": false})
|
||||
if result.Error != nil {
|
||||
return storageError(result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return business.ErrResetToken
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user