口径
This commit is contained in:
parent
75b867ca16
commit
f9e011b9cb
@ -333,7 +333,7 @@ func loadUserDims(ctx context.Context, db *sql.DB, state *rebuildState, app stri
|
||||
|
||||
func rebuildRegistrations(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64, loc *time.Location) error {
|
||||
// 注册国家以 users.country 映射出的真实 country_id 为准;region_id 只作为独立维度写入,不能再兜底成国家。
|
||||
// source=game_robot 是后台全站机器人池账号,不能进入新增用户、注册 cohort 或留存口径。
|
||||
// source=game_robot 是后台全站机器人池账号,source=quick_account 是后台快捷建号;两者都不是 App 自然注册,不能进入新增用户、注册 cohort 或留存口径。
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT u.user_id, COALESCE(c.country_id, 0), COALESCE(u.region_id, 0), u.created_at_ms
|
||||
FROM users u
|
||||
@ -341,7 +341,7 @@ func rebuildRegistrations(ctx context.Context, db *sql.DB, state *rebuildState,
|
||||
WHERE u.app_code = ?
|
||||
AND u.created_at_ms >= ?
|
||||
AND u.created_at_ms < ?
|
||||
AND LOWER(COALESCE(u.source, '')) <> 'game_robot'`,
|
||||
AND LOWER(COALESCE(u.source, '')) NOT IN ('game_robot', 'quick_account')`,
|
||||
app, startMS, endMS)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -127,6 +127,25 @@ func newAuthService(repository *mysqltest.Repository, now *time.Time, ids []int6
|
||||
}, options...)
|
||||
}
|
||||
|
||||
func assertUserRegisteredOutboxCount(t *testing.T, repository *mysqltest.Repository, userID int64, want int) {
|
||||
t.Helper()
|
||||
var count int
|
||||
var status string
|
||||
if err := repository.RawDB().QueryRowContext(context.Background(), `
|
||||
SELECT COUNT(*), COALESCE(MAX(status), '')
|
||||
FROM user_outbox
|
||||
WHERE app_code = 'lalu' AND event_type = 'UserRegistered' AND aggregate_id = ?
|
||||
`, userID).Scan(&count, &status); err != nil {
|
||||
t.Fatalf("query UserRegistered outbox failed: %v", err)
|
||||
}
|
||||
if count != want {
|
||||
t.Fatalf("UserRegistered outbox count for user %d = %d, want %d", userID, count, want)
|
||||
}
|
||||
if want > 0 && status != "pending" {
|
||||
t.Fatalf("UserRegistered outbox status for user %d = %s, want pending", userID, status)
|
||||
}
|
||||
}
|
||||
|
||||
func thirdPartyRegistration(platform string) authdomain.ThirdPartyRegistration {
|
||||
// 部分测试需要覆盖注册快照字段,因此默认提供完整资料;完成态仍必须由 onboarding 流程推进。
|
||||
return authdomain.ThirdPartyRegistration{
|
||||
@ -197,6 +216,7 @@ func TestThirdPartyUserSetsPasswordThenLogsIn(t *testing.T) {
|
||||
if thirdToken.ProfileCompleted || thirdToken.OnboardingStatus != string(userdomain.OnboardingStatusProfileRequired) {
|
||||
t.Fatalf("new third-party user must require onboarding: %+v", thirdToken)
|
||||
}
|
||||
assertUserRegisteredOutboxCount(t, repository, thirdToken.UserID, 1)
|
||||
|
||||
if _, err := svc.LoginPassword(ctx, "163000", "secret-pass", "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.AuthFailed) {
|
||||
// 未设置密码前不能通过短号密码登录,且错误必须统一 AUTH_FAILED。
|
||||
@ -285,6 +305,7 @@ func TestQuickCreateAccountCreatesCompletedPasswordLogin(t *testing.T) {
|
||||
if imImporter.userID != 900011 || imImporter.nickname != "Lingxian Test" || imImporter.faceURL != "https://cdn.example/avatar.png" {
|
||||
t.Fatalf("quick account must import IM account: %+v", imImporter)
|
||||
}
|
||||
assertUserRegisteredOutboxCount(t, repository, token.UserID, 0)
|
||||
|
||||
loginToken, err := svc.LoginPassword(ctx, token.DisplayUserID, "secret-pass", "quick-dev", authservice.Meta{RequestID: "req-quick-login", AppCode: "lalu"})
|
||||
if err != nil {
|
||||
@ -343,6 +364,7 @@ func TestQuickCreateGameRobotCannotLoginOrCreateSession(t *testing.T) {
|
||||
if sessionCount != 0 {
|
||||
t.Fatalf("robot account must not create auth session, got %d", sessionCount)
|
||||
}
|
||||
assertUserRegisteredOutboxCount(t, repository, token.UserID, 0)
|
||||
if _, err := svc.LoginPassword(ctx, token.DisplayUserID, "secret-pass", "robot-dev", authservice.Meta{RequestID: "req-robot-login", AppCode: "lalu"}); !xerr.IsCode(err, xerr.UserDisabled) {
|
||||
t.Fatalf("robot password login must be blocked, got %v", err)
|
||||
}
|
||||
|
||||
@ -84,6 +84,10 @@ func (r *Repository) CreateThirdPartyUser(ctx context.Context, user userdomain.U
|
||||
// session 插入失败会回滚用户和三方绑定。
|
||||
return err
|
||||
}
|
||||
if err := userstorage.InsertUserRegisteredOutbox(ctx, tx, user); err != nil {
|
||||
// UserRegistered 是统计新增用户、留存 cohort 和注册后异步读模型的唯一事实;它必须跟首次三方注册同事务提交,避免用户已经创建但统计链路永远收不到事件。
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
@ -146,14 +146,15 @@ func (r *Repository) CreateUserWithIdentity(ctx context.Context, user userdomain
|
||||
if _, err := invitestorage.EnsurePrimaryCodeForUser(ctx, tx, user.AppCode, user.UserID, user.CreatedAtMs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertUserRegisteredOutbox(ctx, tx, user); err != nil {
|
||||
if err := InsertUserRegisteredOutbox(ctx, tx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func insertUserRegisteredOutbox(ctx context.Context, tx *sql.Tx, user userdomain.User) error {
|
||||
// InsertUserRegisteredOutbox writes the durable UserRegistered fact in the same transaction that creates the user.
|
||||
func InsertUserRegisteredOutbox(ctx context.Context, tx *sql.Tx, user userdomain.User) error {
|
||||
if userdomain.IsGameRobotRegisterSource(user.RegisterSource) {
|
||||
// 全站机器人只复用用户资料、头像和短号参与游戏展示,不是真实自然注册用户;
|
||||
// 这里不写 UserRegistered 事实,避免实时统计、留存 cohort 和注册奖励把批量机器人当新增用户。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user