城市合伙人端的修改(后台)

This commit is contained in:
2026-07-12 10:19:39 +08:00
parent d0f0fa09af
commit 7f031cc4c2
74 changed files with 5430 additions and 975 deletions
@@ -168,15 +168,15 @@ export class AuthService {
}
private trackPartnerEvent(
partnerAccountId: bigint | undefined,
partnerId: bigint,
actorAccountId: bigint | undefined,
primaryAccountId: bigint,
clientApp: ClientApp | string,
eventName: string,
extraJson?: Record<string, unknown>,
ref?: { refType?: string; refId?: bigint },
) {
this.analyticsService.trackPartnerOneSafe(partnerAccountId, clientApp, {
partnerId,
this.analyticsService.trackPartnerOneSafe(actorAccountId, clientApp, {
partnerAccountId: primaryAccountId,
eventName,
refType: ref?.refType,
refId: ref?.refId,
@@ -184,10 +184,42 @@ export class AuthService {
});
}
private async resolvePrimaryAccount(accountId: bigint) {
const account = await this.prisma.partnerAccount.findUnique({ where: { id: accountId } });
if (!account) throw new NotFoundException('合伙人账号不存在');
if (account.isPrimary === 1) return account;
if (!account.parentAccountId) {
throw new BadRequestException('子账号缺少主账号');
}
return this.prisma.partnerAccount.findUniqueOrThrow({ where: { id: account.parentAccountId } });
}
private partnerTokenPayload(
account: {
id: bigint;
name: string;
phone: string;
isPrimary: number;
staffRole: string | null;
permissions?: unknown;
},
primary: { id: bigint; companyName: string | null },
) {
return {
id: account.id.toString(),
primaryAccountId: primary.id.toString(),
name: account.name,
phone: account.phone,
isPrimary: account.isPrimary === 1,
staffRole: account.staffRole ?? undefined,
companyName: primary.companyName ?? undefined,
permissions: Array.isArray(account.permissions) ? account.permissions : undefined,
};
}
private async assertPartnerAccountByPhone(phone: string) {
const account = await this.prisma.partnerAccount.findUnique({
where: { phone },
include: { partner: true },
});
if (!account) throw new BadRequestException('未找到合伙人账号');
if (account.status !== 'ACTIVE') throw new BadRequestException('合伙人账号已停用');
@@ -197,11 +229,12 @@ export class AuthService {
async checkPartnerPhone(phone: string) {
const normalizedPhone = this.assertMobilePhone(phone);
const account = await this.assertPartnerAccountByPhone(normalizedPhone);
const primary = await this.resolvePrimaryAccount(account.id);
return {
ok: true,
maskedPhone: this.maskPhone(normalizedPhone),
name: account.name,
companyName: account.partner.companyName,
companyName: primary.companyName,
};
}
@@ -306,12 +339,13 @@ export class AuthService {
) {
const partnerAccount = await this.prisma.partnerAccount.findUnique({
where: { id: actorRef.refId },
select: { id: true, partnerId: true },
select: { id: true, isPrimary: true, parentAccountId: true },
});
if (partnerAccount) {
const primary = await this.resolvePrimaryAccount(partnerAccount.id);
this.trackPartnerEvent(
partnerAccount.id,
partnerAccount.partnerId,
primary.id,
clientApp,
'partner_sms_send',
{
@@ -415,20 +449,12 @@ export class AuthService {
private async buildPartnerSessionResponse(accountId: bigint, clientApp: ClientApp) {
const account = await this.prisma.partnerAccount.findUnique({
where: { id: accountId },
include: { partner: true },
});
if (!account || account.status !== 'ACTIVE') {
throw new UnauthorizedException('Invalid refresh token');
}
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, {
id: account.id.toString(),
partnerId: account.partnerId.toString(),
name: account.name,
phone: account.phone,
isPrimary: account.isPrimary === 1,
staffRole: account.staffRole ?? undefined,
companyName: account.partner.companyName,
});
const primary = await this.resolvePrimaryAccount(account.id);
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, this.partnerTokenPayload(account, primary));
}
async loginUser(phone: string, code: string, clientApp: ClientApp, guestId?: bigint) {
@@ -601,7 +627,8 @@ export class AuthService {
} catch (err) {
const account = await this.prisma.partnerAccount.findUnique({ where: { phone: normalizedPhone } });
if (account) {
this.trackPartnerEvent(account.id, account.partnerId, clientApp, 'partner_sms_verify_fail', {
const primary = await this.resolvePrimaryAccount(account.id);
this.trackPartnerEvent(account.id, primary.id, clientApp, 'partner_sms_verify_fail', {
phone: this.maskPhone(normalizedPhone),
reason: err instanceof BadRequestException ? err.message : '验证码错误',
});
@@ -610,29 +637,21 @@ export class AuthService {
}
const account = await this.prisma.partnerAccount.findUnique({
where: { phone: normalizedPhone },
include: { partner: true },
});
if (!account) throw new BadRequestException('未找到合伙人账号');
if (account.status !== 'ACTIVE') throw new BadRequestException('合伙人账号已停用');
const primary = await this.resolvePrimaryAccount(account.id);
await this.prisma.partnerAccount.update({
where: { id: account.id },
data: { lastLoginAt: new Date() },
});
this.trackPartnerEvent(account.id, account.partnerId, clientApp, 'partner_sms_login', {
this.trackPartnerEvent(account.id, primary.id, clientApp, 'partner_sms_login', {
phone: this.maskPhone(normalizedPhone),
});
this.trackPartnerEvent(account.id, account.partnerId, clientApp, 'partner_login_success', {
this.trackPartnerEvent(account.id, primary.id, clientApp, 'partner_login_success', {
method: 'sms',
});
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, {
id: account.id.toString(),
partnerId: account.partnerId.toString(),
name: account.name,
phone: account.phone,
isPrimary: account.isPrimary === 1,
staffRole: account.staffRole ?? undefined,
companyName: account.partner.companyName,
});
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, this.partnerTokenPayload(account, primary));
}
async loginHq(phone: string, code: string, clientApp: ClientApp) {
@@ -734,9 +753,14 @@ export class AuthService {
if (actorType === 'PARTNER') {
const account = await this.prisma.partnerAccount.findUnique({
where: { id: actorId },
include: { partner: true },
});
return serializeBigInt(account);
if (!account) return null;
const primary = await this.resolvePrimaryAccount(account.id);
return serializeBigInt({
...account,
primaryAccountId: primary.id,
companyName: primary.companyName,
});
}
if (actorType === 'HQ') {
const account = await this.prisma.hqAccount.findUnique({ where: { id: actorId } });
@@ -1102,7 +1126,6 @@ export class AuthService {
const account = await this.prisma.partnerAccount.findUnique({
where: { id: partnerAccountId },
include: { partner: true },
});
if (!account) throw new BadRequestException('合伙人账号不存在');
@@ -1120,19 +1143,12 @@ export class AuthService {
wxUnionId: session.unionId ?? account.wxUnionId,
lastLoginAt: new Date(),
},
include: { partner: true },
});
const primary = await this.resolvePrimaryAccount(updated.id);
this.trackPartnerEvent(updated.id, updated.partnerId, clientApp, 'partner_wechat_bind', { platform });
this.trackPartnerEvent(updated.id, primary.id, clientApp, 'partner_wechat_bind', { platform });
return this.issueToken('PARTNER', updated.id, clientApp, false, undefined, undefined, {
id: updated.id.toString(),
partnerId: updated.partnerId.toString(),
name: updated.name,
phone: updated.phone,
isPrimary: updated.isPrimary === 1,
companyName: updated.partner.companyName,
});
return this.issueToken('PARTNER', updated.id, clientApp, false, undefined, undefined, this.partnerTokenPayload(updated, primary));
}
async loginPartnerWechat(code: string, clientApp: ClientApp, platform: 'h5' | 'mini' = 'h5') {
@@ -1144,7 +1160,6 @@ export class AuthService {
let account = await this.prisma.partnerAccount.findFirst({
where: { wxOpenId: session.openId },
include: { partner: true },
});
if (!account && this.wechatProvider.isMock()) {
@@ -1152,7 +1167,6 @@ export class AuthService {
account = await this.prisma.partnerAccount.findFirst({
where: { status: 'ACTIVE' },
orderBy: [{ isPrimary: 'desc' }, { id: 'asc' }],
include: { partner: true },
});
}
@@ -1167,23 +1181,15 @@ export class AuthService {
wxUnionId: session.unionId ?? account.wxUnionId,
lastLoginAt: new Date(),
},
include: { partner: true },
});
const primary = await this.resolvePrimaryAccount(account.id);
this.trackPartnerEvent(account.id, account.partnerId, clientApp, 'partner_wechat_login', { platform });
this.trackPartnerEvent(account.id, account.partnerId, clientApp, 'partner_login_success', {
this.trackPartnerEvent(account.id, primary.id, clientApp, 'partner_wechat_login', { platform });
this.trackPartnerEvent(account.id, primary.id, clientApp, 'partner_login_success', {
method: 'wechat',
});
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, {
id: account.id.toString(),
partnerId: account.partnerId.toString(),
name: account.name,
phone: account.phone,
isPrimary: account.isPrimary === 1,
staffRole: account.staffRole ?? undefined,
companyName: account.partner.companyName,
});
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, this.partnerTokenPayload(account, primary));
}
private async mergeUsers(guestId: bigint, primaryId: bigint): Promise<UserRow> {