perf: 优化邀请注册流程

This commit is contained in:
xiaojunnuo
2026-06-04 18:22:21 +08:00
parent fdb1d1e6dd
commit 7a71e45799
11 changed files with 115 additions and 30 deletions
@@ -0,0 +1,78 @@
import assert from "node:assert/strict";
import { LoginService } from "./login-service.js";
function createLoginService() {
const service = new LoginService();
const calls: any = {
register: [],
bindInvitee: [],
withTx: [],
};
const txManager = { tx: true };
const newUser = { id: 1001, username: "new_user" };
service.userService = {
async register(type: string, user: any, withTx?: any) {
calls.register.push({ type, user, withTx });
user.id = newUser.id;
if (withTx) {
await withTx(txManager);
}
return newUser;
},
} as any;
service.inviteService = {
async bindInvitee(ctx: any, req: any) {
calls.bindInvitee.push({ ctx, req });
return { id: 1 };
},
} as any;
return { service, calls, newUser, txManager };
}
describe("LoginService.register", () => {
it("registers user and binds invite code after user creation", async () => {
const { service, calls, newUser } = createLoginService();
const registerUser = { username: "alice" } as any;
const result = await service.register("username", registerUser, "ABC123");
assert.equal(result, newUser);
assert.equal(calls.register.length, 1);
assert.equal(calls.register[0].type, "username");
assert.equal(calls.register[0].user, registerUser);
assert.equal(calls.bindInvitee.length, 1);
assert.deepEqual(calls.bindInvitee[0].ctx, {});
assert.deepEqual(calls.bindInvitee[0].req, {
inviteeUserId: newUser.id,
inviteCode: "ABC123",
});
});
it("keeps registration successful when invite binding fails", async () => {
const { service, calls, newUser } = createLoginService();
service.inviteService = {
async bindInvitee(ctx: any, req: any) {
calls.bindInvitee.push({ ctx, req });
throw new Error("invalid invite code");
},
} as any;
const result = await service.register("username", { username: "alice" } as any, "BADCODE");
assert.equal(result, newUser);
assert.equal(calls.register.length, 1);
assert.equal(calls.bindInvitee.length, 1);
});
it("keeps original registration transaction callback", async () => {
const { service, calls, txManager } = createLoginService();
await service.register("username", { username: "alice" } as any, "", async tx => {
calls.withTx.push(tx);
});
assert.equal(calls.withTx.length, 1);
assert.equal(calls.withTx[0], txManager);
assert.equal(calls.bindInvitee.length, 0);
});
});
@@ -4,7 +4,7 @@ import jwt from "jsonwebtoken";
import { AuthException, CommonException, Need2FAException, SysPrivateSettings, SysSettingsService } from "@certd/lib-server";
import { RoleService } from "../../sys/authority/service/role-service.js";
import { UserEntity } from "../../sys/authority/entity/user.js";
import { cache, utils } from "@certd/basic";
import { cache, logger, utils } from "@certd/basic";
import { LoginErrorException } from "@certd/lib-server";
import { CodeService } from "../../basic/service/code-service.js";
import { TwoFactorService } from "../../mine/service/two-factor-service.js";
@@ -14,6 +14,7 @@ import { AddonService } from "@certd/lib-server";
import { OauthBoundService } from "./oauth-bound-service.js";
import { PasskeyService } from "./passkey-service.js";
import { InviteService } from "@certd/commercial-core";
import { EntityManager } from "typeorm";
/**
*/
@@ -108,6 +109,19 @@ export class LoginService {
throw new LoginErrorException(errorMessage, leftTimes);
}
async register(type: string, user: UserEntity, inviteCode?: string, withTx?: (tx: EntityManager) => Promise<void>) {
const newUser = await this.userService.register(type, user, withTx);
if (!inviteCode) {
return newUser;
}
try {
await this.inviteService.bindInvitee({}, { inviteeUserId: newUser.id, inviteCode });
} catch (e) {
logger.error("绑定邀请关系失败,不影响用户注册", e);
}
return newUser;
}
async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; inviteCode?: string }) {
this.checkIsBlocked(req.mobile);
@@ -130,9 +144,7 @@ export class LoginService {
mobile,
password: "",
} as any;
info = await this.userService.register("mobile", registerUser, async txManager => {
await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: req.inviteCode });
});
info = await this.register("mobile", registerUser, req.inviteCode);
}
this.clearCacheOnSuccess(mobile);
return this.onLoginSuccess(info);