feat: migrate workbench services
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const (
|
||||
memberStatusActive = 1
|
||||
memberCategoryReal = 1
|
||||
)
|
||||
|
||||
type AccountRegistration struct {
|
||||
TenantID int64
|
||||
MemberNo string
|
||||
Username string
|
||||
MobileCipher string
|
||||
MobileHash string
|
||||
MobileMask string
|
||||
PasswordHash string
|
||||
ReferralCode string
|
||||
InviteCode string
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
TenantID int64 `db:"tenant_id"`
|
||||
MemberID int64 `db:"member_id"`
|
||||
MemberNo string `db:"member_no"`
|
||||
Username string `db:"username"`
|
||||
MobileMask string `db:"mobile_mask"`
|
||||
MobileHash string `db:"mobile_hash"`
|
||||
PasswordHash string `db:"password_hash"`
|
||||
InviteCode string `db:"invite_code"`
|
||||
MemberStatus int `db:"member_status"`
|
||||
MemberCategory int `db:"member_category"`
|
||||
}
|
||||
|
||||
func (s *Store) RegisterAccount(ctx context.Context, input AccountRegistration) (*Account, error) {
|
||||
if s == nil || s.db == nil {
|
||||
return nil, errors.New("store is unavailable")
|
||||
}
|
||||
tx, err := s.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
parentID, teamLevel, tree, err := referralParent(ctx, tx, input.TenantID, input.ReferralCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
levelID, err := defaultMemberLevel(ctx, tx, input.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := tx.ExecContext(ctx, `INSERT INTO hg_app_member
|
||||
(tenant_id,member_no,username,mobile_cipher,mobile_hash,mobile_mask,password_hash,
|
||||
level_id,parent_id,level,tree,invite_code,member_status,risk_status,auto_dispatch,
|
||||
credit_score,is_test_account,created_by,updated_by)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,1,1,2,100,2,0,0)`,
|
||||
input.TenantID, input.MemberNo, input.Username, input.MobileCipher, input.MobileHash,
|
||||
input.MobileMask, input.PasswordHash, levelID, parentID, teamLevel, tree, input.InviteCode)
|
||||
if err != nil {
|
||||
if isDuplicate(err) {
|
||||
return nil, ErrAccountExists
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
memberID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, `INSERT INTO hg_app_member_profile
|
||||
(tenant_id,member_id,created_by,updated_by) VALUES(?,?,0,0)`, input.TenantID, memberID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, `INSERT INTO hg_wallet_account
|
||||
(tenant_id,member_id,currency,available_amount,frozen_amount,total_income,total_expense,account_status,version)
|
||||
VALUES(?,?,'CNY',0,0,0,0,1,0)`, input.TenantID, memberID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Account{TenantID: input.TenantID, MemberID: memberID, MemberNo: input.MemberNo,
|
||||
Username: input.Username, MobileMask: input.MobileMask, MobileHash: input.MobileHash,
|
||||
InviteCode: input.InviteCode, MemberStatus: memberStatusActive, MemberCategory: memberCategoryReal}, nil
|
||||
}
|
||||
|
||||
func (s *Store) AccountByMobileHash(ctx context.Context, tenantID int64, mobileHash string) (*Account, error) {
|
||||
if s == nil || s.db == nil {
|
||||
return nil, errors.New("store is unavailable")
|
||||
}
|
||||
return accountByMobileHash(ctx, s.db, tenantID, mobileHash)
|
||||
}
|
||||
|
||||
func (s *Store) RecordLogin(ctx context.Context, tenantID, memberID int64) error {
|
||||
_, err := s.db.ExecContext(ctx, `UPDATE hg_app_member SET last_login_at=?,updated_at=?
|
||||
WHERE tenant_id=? AND id=? AND deleted_at IS NULL`, time.Now().UTC(), time.Now().UTC(), tenantID, memberID)
|
||||
return err
|
||||
}
|
||||
|
||||
type queryer interface {
|
||||
GetContext(context.Context, any, string, ...any) error
|
||||
}
|
||||
|
||||
func accountByMobileHash(ctx context.Context, db queryer, tenantID int64, mobileHash string) (*Account, error) {
|
||||
var row Account
|
||||
err := db.GetContext(ctx, &row, `SELECT tenant_id, id AS member_id, member_no, username, mobile_mask,
|
||||
mobile_hash, password_hash, invite_code, member_status, risk_status AS member_category
|
||||
FROM hg_app_member WHERE tenant_id=? AND mobile_hash=? AND deleted_at IS NULL LIMIT 1`, tenantID, mobileHash)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if row.MemberStatus != memberStatusActive {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func referralParent(ctx context.Context, tx *sqlx.Tx, tenantID int64, code string) (int64, int, string, error) {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return 0, 1, "", nil
|
||||
}
|
||||
var parent struct {
|
||||
ID int64 `db:"id"`
|
||||
Level int `db:"level"`
|
||||
Tree string `db:"tree"`
|
||||
}
|
||||
err := tx.GetContext(ctx, &parent, `SELECT id,level,tree FROM hg_app_member
|
||||
WHERE tenant_id=? AND invite_code=? AND member_status=1
|
||||
AND deleted_at IS NULL LIMIT 1 FOR UPDATE`, tenantID, code)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, 0, "", ErrReferralInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
tree := strings.Trim(strings.TrimSpace(parent.Tree)+","+formatInt64(parent.ID), ",")
|
||||
if len(tree) > 512 {
|
||||
return 0, 0, "", ErrReferralInvalid
|
||||
}
|
||||
return parent.ID, parent.Level + 1, tree, nil
|
||||
}
|
||||
|
||||
func defaultMemberLevel(ctx context.Context, tx *sqlx.Tx, tenantID int64) (int64, error) {
|
||||
var id int64
|
||||
err := tx.GetContext(ctx, &id, `SELECT id FROM hg_app_member_level
|
||||
WHERE tenant_id=? AND status=1 AND deleted_at IS NULL ORDER BY sort ASC,id ASC LIMIT 1`, tenantID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, nil
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func isDuplicate(err error) bool {
|
||||
var mysqlErr *mysql.MySQLError
|
||||
return errors.As(err, &mysqlErr) && mysqlErr.Number == 1062
|
||||
}
|
||||
|
||||
func formatInt64(value int64) string {
|
||||
return strconv.FormatInt(value, 10)
|
||||
}
|
||||
Reference in New Issue
Block a user