推广码后端页面优化

This commit is contained in:
2026-07-23 00:43:49 +08:00
parent cd6b82abd8
commit e93e4a7721
11 changed files with 456 additions and 260 deletions
@@ -26,7 +26,12 @@ export class PromoController {
touch(@CurrentUser() user: AuthUser | undefined, @Body() dto: PromoTouchDto) {
const userId = user?.actorType === ActorType.USER ? user.actorId : undefined;
return this.promoCodeService.touch(
{ promoCode: dto.promoCode, qrcodeId: dto.qrcodeId, promoId: dto.promoId },
{
promoCode: dto.promoCode,
qrcodeId: dto.qrcodeId,
promoId: dto.promoId,
countScan: dto.countScan,
},
userId,
);
}
@@ -1,4 +1,5 @@
import { IsOptional, IsString } from 'class-validator';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { Transform } from 'class-transformer';
export class PromoTouchDto {
@IsOptional()
@@ -13,4 +14,17 @@ export class PromoTouchDto {
@IsOptional()
@IsString()
promoId?: string;
/**
* 是否累加扫码次数。扫码进入为 true;登录后归因可传 false,避免重复计数。
* 默认 true。
*/
@IsOptional()
@Transform(({ value }) => {
if (value === false || value === 'false' || value === 0 || value === '0') return false;
if (value === true || value === 'true' || value === 1 || value === '1') return true;
return undefined;
})
@IsBoolean()
countScan?: boolean;
}
@@ -55,6 +55,12 @@ export class CreatePromoCodeDto {
@IsString()
@MaxLength(256)
remark?: string;
/** 小程序码落地页路径,如 pages/home/index;留空用环境变量 WX_MINI_PROMO_PAGE */
@IsOptional()
@IsString()
@MaxLength(128)
page?: string;
}
export class UpdatePromoCodeDto {
@@ -235,14 +235,19 @@ export class PromoCodeService {
/**
* 调用微信 getwxacodeunlimitscene=推广活动 IDPNG 上传 OSS uploads/qrcode/
*/
private async createQrcodeResource(promoId: bigint, code: string) {
private async createQrcodeResource(promoId: bigint, code: string, pagePath?: string) {
const scene = promoId.toString();
if (scene.length > 32) {
throw new BadRequestException('推广活动 ID 过长,无法写入小程序码 scene');
}
const page = (
pagePath?.trim() ||
process.env.WX_MINI_PROMO_PAGE ||
'pages/home/index'
).replace(/^\//, '');
const pngBuffer = await this.wechat.getWxaCodeUnlimited({
scene,
page: process.env.WX_MINI_PROMO_PAGE || 'pages/home/index',
page,
width: 430,
checkPath: false,
});
@@ -290,7 +295,7 @@ export class PromoCodeService {
});
try {
const resource = await this.createQrcodeResource(row.id, code);
const resource = await this.createQrcodeResource(row.id, code, dto.page);
const updated = await this.prisma.commonPromoCode.update({
where: { id: row.id },
data: { qrcodeResourceId: resource.id },
@@ -380,7 +385,13 @@ export class PromoCodeService {
/** C 端扫码/带参进入:累加 scan_count、归因、标记用户来源 */
async touch(
input: { promoCode?: string; qrcodeId?: string; promoId?: string },
input: {
promoCode?: string;
qrcodeId?: string;
promoId?: string;
/** 默认 true;登录后归因传 false 避免重复计扫码 */
countScan?: boolean;
},
userId?: bigint,
) {
const promoCode = input.promoCode?.trim().toUpperCase();
@@ -395,10 +406,13 @@ export class PromoCodeService {
throw new NotFoundException('推广码无效或已停用');
}
await this.prisma.commonPromoCode.update({
where: { id: promo.id },
data: { scanCount: { increment: 1 } },
});
const shouldCountScan = input.countScan !== false;
if (shouldCountScan) {
await this.prisma.commonPromoCode.update({
where: { id: promo.id },
data: { scanCount: { increment: 1 } },
});
}
let attributed = false;
let sourceApplied = false;
@@ -428,6 +442,7 @@ export class PromoCodeService {
channelName: promo.name,
attributed,
sourceApplied,
scanCounted: shouldCountScan,
});
}
@@ -472,7 +487,14 @@ export class PromoCodeService {
},
}),
]);
return { scanCount, orderCount, conversionRate, attributionCount, sourceMarkedCount };
return {
scanCount,
orderCount,
conversionRate,
attributionCount,
sourceMarkedCount,
registerCount: sourceMarkedCount,
};
}
/** 推广码关联用户:归因记录或用户来源指向本码 */