feat: use six-digit numeric member invite codes
This commit is contained in:
@@ -210,7 +210,11 @@ func (s *sSysAppMember) Edit(ctx context.Context, in *sysin.AppMemberEditInp) er
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data[mc.MemberNo] = "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
data[mc.MemberNo] = "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
||||||
data[mc.InviteCode] = strings.ToUpper(grand.S(8))
|
inviteCode, e := newAppMemberInviteCode(ctx, s.memberModel(ctx))
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
data[mc.InviteCode] = inviteCode
|
||||||
data[mc.MemberStatus] = consts.AppMemberStatusNormal
|
data[mc.MemberStatus] = consts.AppMemberStatusNormal
|
||||||
data[mc.CreatedBy] = userId
|
data[mc.CreatedBy] = userId
|
||||||
result, e := s.memberModel(ctx).Fields(mc.MemberNo, mc.Username, mc.Mobile, mc.PasswordHash, mc.TradePasswordHash, mc.LevelId, mc.ParentId, mc.Level, mc.Tree, mc.InviteCode, mc.MemberStatus, mc.RiskStatus, mc.AutoDispatch, mc.CreditScore, mc.IsTestAccount, mc.CreatedBy, mc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
result, e := s.memberModel(ctx).Fields(mc.MemberNo, mc.Username, mc.Mobile, mc.PasswordHash, mc.TradePasswordHash, mc.LevelId, mc.ParentId, mc.Level, mc.Tree, mc.InviteCode, mc.MemberStatus, mc.RiskStatus, mc.AutoDispatch, mc.CreditScore, mc.IsTestAccount, mc.CreatedBy, mc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package sys
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hotgo/internal/consts"
|
"hotgo/internal/consts"
|
||||||
"hotgo/internal/dao"
|
"hotgo/internal/dao"
|
||||||
@@ -54,19 +53,15 @@ func (s *sSysAppMemberAuth) Register(ctx context.Context, in *sysin.AppMemberReg
|
|||||||
return gerror.New("手机号已存在")
|
return gerror.New("手机号已存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
parentId, level, tree := uint64(0), 1, ""
|
|
||||||
if in.InviteCode != "" {
|
|
||||||
var parent *entity.AppMember
|
var parent *entity.AppMember
|
||||||
if e = s.memberModel(ctx).Where(mc.InviteCode, in.InviteCode).Scan(&parent); e != nil {
|
if e = s.memberModel(ctx).Where(mc.InviteCode, in.InviteCode).Where(mc.MemberStatus, consts.AppMemberStatusNormal).Scan(&parent); e != nil {
|
||||||
return gerror.Wrap(e, "查询邀请人失败")
|
return gerror.Wrap(e, "查询邀请人失败")
|
||||||
}
|
}
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return gerror.New("邀请码不存在")
|
return gerror.New("邀请码不存在")
|
||||||
}
|
}
|
||||||
parentId = parent.Id
|
parentId, level := parent.Id, parent.Level+1
|
||||||
level = parent.Level + 1
|
tree := parent.Tree + "tr_" + fmt.Sprint(parent.Id) + " "
|
||||||
tree = parent.Tree + "tr_" + fmt.Sprint(parent.Id) + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
levelId := uint64(0)
|
levelId := uint64(0)
|
||||||
var firstLevel *entity.AppMemberLevel
|
var firstLevel *entity.AppMemberLevel
|
||||||
@@ -81,11 +76,15 @@ func (s *sSysAppMemberAuth) Register(ctx context.Context, in *sysin.AppMemberReg
|
|||||||
return gerror.Wrap(e, "生成密码摘要失败")
|
return gerror.Wrap(e, "生成密码摘要失败")
|
||||||
}
|
}
|
||||||
memberNo := "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
memberNo := "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
||||||
|
inviteCode, e := newAppMemberInviteCode(ctx, s.memberModel(ctx))
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
memberData := g.Map{
|
memberData := g.Map{
|
||||||
mc.TenantId: 0,
|
mc.TenantId: 0,
|
||||||
mc.MemberNo: memberNo, mc.Username: in.Username, mc.Mobile: in.Mobile,
|
mc.MemberNo: memberNo, mc.Username: in.Username, mc.Mobile: in.Mobile,
|
||||||
mc.PasswordHash: string(hashed), mc.LevelId: levelId, mc.ParentId: parentId,
|
mc.PasswordHash: string(hashed), mc.LevelId: levelId, mc.ParentId: parentId,
|
||||||
mc.Level: level, mc.Tree: tree, mc.InviteCode: strings.ToUpper(grand.S(8)),
|
mc.Level: level, mc.Tree: tree, mc.InviteCode: inviteCode,
|
||||||
mc.MemberStatus: consts.AppMemberStatusNormal, mc.RiskStatus: consts.AppMemberRiskNormal,
|
mc.MemberStatus: consts.AppMemberStatusNormal, mc.RiskStatus: consts.AppMemberRiskNormal,
|
||||||
mc.AutoDispatch: consts.AppMemberDispatchManual, mc.CreditScore: 100, mc.IsTestAccount: 2,
|
mc.AutoDispatch: consts.AppMemberDispatchManual, mc.CreditScore: 100, mc.IsTestAccount: 2,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"hotgo/internal/dao"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAppMemberInviteCode(ctx context.Context, members *gdb.Model) (string, error) {
|
||||||
|
for attempt := 0; attempt < 20; attempt++ {
|
||||||
|
number, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||||
|
if err != nil {
|
||||||
|
return "", gerror.Wrap(err, "生成邀请码失败")
|
||||||
|
}
|
||||||
|
code := fmt.Sprintf("%06d", number.Int64())
|
||||||
|
count, err := members.Unscoped().Where(dao.AppMember.Columns().InviteCode, code).Count()
|
||||||
|
if err != nil {
|
||||||
|
return "", gerror.Wrap(err, "检查邀请码失败")
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", gerror.New("邀请码暂时无法生成,请稍后重试")
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package sysin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
@@ -11,7 +12,7 @@ type AppMemberRegisterInp struct {
|
|||||||
Username string `json:"username" v:"required|length:3,64#用户名不能为空|用户名长度为3到64个字符"`
|
Username string `json:"username" v:"required|length:3,64#用户名不能为空|用户名长度为3到64个字符"`
|
||||||
Mobile string `json:"mobile" v:"required|phone-loose#手机号不能为空|手机号格式不正确"`
|
Mobile string `json:"mobile" v:"required|phone-loose#手机号不能为空|手机号格式不正确"`
|
||||||
Password string `json:"password" v:"required|length:8,64#密码不能为空|密码长度为8到64个字符"`
|
Password string `json:"password" v:"required|length:8,64#密码不能为空|密码长度为8到64个字符"`
|
||||||
InviteCode string `json:"inviteCode" v:"max-length:32#邀请码不能超过32个字符"`
|
InviteCode string `json:"inviteCode" v:"required#请输入邀请码"`
|
||||||
RegisterOrigin string `json:"registerOrigin" v:"max-length:512#注册来源不能超过512个字符"`
|
RegisterOrigin string `json:"registerOrigin" v:"max-length:512#注册来源不能超过512个字符"`
|
||||||
RegisterHost string `json:"registerHost" v:"max-length:255#注册域名不能超过255个字符"`
|
RegisterHost string `json:"registerHost" v:"max-length:255#注册域名不能超过255个字符"`
|
||||||
}
|
}
|
||||||
@@ -20,6 +21,9 @@ func (in *AppMemberRegisterInp) Filter(ctx context.Context) error {
|
|||||||
in.Username = strings.TrimSpace(in.Username)
|
in.Username = strings.TrimSpace(in.Username)
|
||||||
in.Mobile = strings.TrimSpace(in.Mobile)
|
in.Mobile = strings.TrimSpace(in.Mobile)
|
||||||
in.InviteCode = strings.TrimSpace(in.InviteCode)
|
in.InviteCode = strings.TrimSpace(in.InviteCode)
|
||||||
|
if !regexp.MustCompile(`^[0-9]{6}$`).MatchString(in.InviteCode) {
|
||||||
|
return gerror.New("邀请码必须为6位数字")
|
||||||
|
}
|
||||||
if !containsLetterAndNumber(in.Password) {
|
if !containsLetterAndNumber(in.Password) {
|
||||||
return gerror.New("密码至少8位且必须同时包含字母和数字")
|
return gerror.New("密码至少8位且必须同时包含字母和数字")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- 将现有会员邀请码迁移为唯一的六位数字,保留原码映射供核查和回退。
|
||||||
|
-- 执行前确认每个租户会员数不超过 900000,且没有占用 100000 起始区间的六位数字码。
|
||||||
|
-- 本迁移只调整邀请码;团队关系由 parent_id 和 tree 保存,不受影响。
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `hg_app_member_invite_code_backup_20261015` (
|
||||||
|
`tenant_id` bigint unsigned NOT NULL,
|
||||||
|
`member_id` bigint unsigned NOT NULL,
|
||||||
|
`old_code` varchar(32) NOT NULL,
|
||||||
|
`new_code` char(6) NOT NULL,
|
||||||
|
`migrated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (`tenant_id`, `member_id`),
|
||||||
|
UNIQUE KEY `uk_invite_code_backup_new` (`tenant_id`, `new_code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员邀请码六位数字迁移备份';
|
||||||
|
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
INSERT INTO `hg_app_member_invite_code_backup_20261015`
|
||||||
|
(`tenant_id`, `member_id`, `old_code`, `new_code`)
|
||||||
|
SELECT `tenant_id`, `id`, `invite_code`,
|
||||||
|
LPAD(100000 + ROW_NUMBER() OVER (PARTITION BY `tenant_id` ORDER BY `id`) - 1, 6, '0')
|
||||||
|
FROM `hg_app_member`;
|
||||||
|
|
||||||
|
UPDATE `hg_app_member` AS m
|
||||||
|
JOIN `hg_app_member_invite_code_backup_20261015` AS b
|
||||||
|
ON b.`tenant_id` = m.`tenant_id` AND b.`member_id` = m.`id`
|
||||||
|
SET m.`invite_code` = b.`new_code`;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Reference in New Issue
Block a user