157 lines
4.9 KiB
TypeScript
157 lines
4.9 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { ClientApp } from '@dukang/shared-types';
|
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
|
import { TencentLbsProvider } from '../../integrations/map/tencent-lbs.provider';
|
|
import { logWechatAuth, type WechatActorRef } from '../../integrations/wechat/wechat-log.util';
|
|
import { AnalyticsService } from '../analytics/analytics.service';
|
|
|
|
export type ReportWechatLocationInput = {
|
|
latitude?: number;
|
|
longitude?: number;
|
|
sdk: 'jssdk' | 'geolocation';
|
|
status: 'success' | 'fail';
|
|
errMsg?: string;
|
|
clientApp?: string;
|
|
userId?: bigint;
|
|
};
|
|
|
|
export type ReportWechatLocationResult = {
|
|
province?: string;
|
|
city?: string;
|
|
district?: string;
|
|
cityCode?: string;
|
|
cityName?: string;
|
|
openCity: boolean;
|
|
thirdPartyLogIds: {
|
|
location?: string;
|
|
geocode?: string;
|
|
};
|
|
};
|
|
|
|
function normalizeCityName(name: string) {
|
|
return name.replace(/市$/, '').trim();
|
|
}
|
|
|
|
function matchOpenCity(
|
|
cities: Array<{ code: string; name: string; province: string }>,
|
|
province: string,
|
|
city: string,
|
|
) {
|
|
const cityNorm = normalizeCityName(city);
|
|
return cities.find((c) => {
|
|
const nameNorm = normalizeCityName(c.name);
|
|
if (nameNorm !== cityNorm && c.name !== city && c.name !== `${cityNorm}市`) return false;
|
|
if (c.province && province && c.province !== province) return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
@Injectable()
|
|
export class WechatLocationService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly tencentLbs: TencentLbsProvider,
|
|
private readonly analyticsService: AnalyticsService,
|
|
) {}
|
|
|
|
async reportLocation(input: ReportWechatLocationInput): Promise<ReportWechatLocationResult> {
|
|
const actorRef: WechatActorRef | undefined = input.userId
|
|
? { refType: 'USER', refId: input.userId }
|
|
: undefined;
|
|
const clientApp = (input.clientApp as ClientApp) || ClientApp.USER_H5;
|
|
const thirdPartyLogIds: ReportWechatLocationResult['thirdPartyLogIds'] = {};
|
|
|
|
const locationLogId = await logWechatAuth(this.prisma, {
|
|
scene: 'GET_LOCATION',
|
|
requestBody: {
|
|
sdk: input.sdk,
|
|
status: input.status,
|
|
...(input.latitude != null && input.longitude != null
|
|
? {
|
|
latitude: Number(input.latitude.toFixed(3)),
|
|
longitude: Number(input.longitude.toFixed(3)),
|
|
}
|
|
: {}),
|
|
...(input.errMsg ? { errMsg: input.errMsg.slice(0, 200) } : {}),
|
|
},
|
|
responseBody: { reported: true },
|
|
status: input.status === 'success' ? 'SUCCESS' : 'FAILED',
|
|
errorMessage: input.status === 'fail' ? input.errMsg?.slice(0, 512) : undefined,
|
|
actorRef,
|
|
});
|
|
thirdPartyLogIds.location = locationLogId.toString();
|
|
|
|
if (input.status !== 'success' || input.latitude == null || input.longitude == null) {
|
|
return { openCity: false, thirdPartyLogIds };
|
|
}
|
|
|
|
const geo = await this.tencentLbs.reverseGeocode(input.latitude, input.longitude, actorRef);
|
|
if (geo) {
|
|
thirdPartyLogIds.geocode = geo.logId.toString();
|
|
}
|
|
if (!geo) {
|
|
return { openCity: false, thirdPartyLogIds };
|
|
}
|
|
|
|
const openCities = await this.prisma.commonCity.findMany({
|
|
where: { status: 'ACTIVE' },
|
|
select: { code: true, name: true, province: true },
|
|
});
|
|
const matched = matchOpenCity(openCities, geo.province, geo.city);
|
|
|
|
const result: ReportWechatLocationResult = {
|
|
province: geo.province,
|
|
city: geo.city,
|
|
district: geo.district,
|
|
cityCode: matched?.code,
|
|
cityName: matched?.name ?? `${geo.city}市`,
|
|
openCity: !!matched,
|
|
thirdPartyLogIds,
|
|
};
|
|
|
|
if (input.userId) {
|
|
const mapLogId = geo.logId;
|
|
this.analyticsService.trackOneSafe(input.userId, clientApp, {
|
|
eventName: 'wechat_location',
|
|
refType: 'THIRD_PARTY_LOG',
|
|
refId: mapLogId,
|
|
extraJson: {
|
|
sdk: input.sdk,
|
|
province: geo.province,
|
|
city: geo.city,
|
|
district: geo.district,
|
|
openCity: !!matched,
|
|
cityCode: matched?.code,
|
|
},
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async reportChooseImage(input: {
|
|
status: 'success' | 'fail';
|
|
errMsg?: string;
|
|
sourceType?: string;
|
|
stage?: string;
|
|
pageUrl?: string;
|
|
actorRef?: WechatActorRef;
|
|
}) {
|
|
const logId = await logWechatAuth(this.prisma, {
|
|
scene: 'CHOOSE_IMAGE',
|
|
requestUrl: input.pageUrl?.split('#')[0]?.slice(0, 512),
|
|
requestBody: {
|
|
status: input.status,
|
|
...(input.sourceType ? { sourceType: input.sourceType } : {}),
|
|
...(input.stage ? { stage: input.stage } : {}),
|
|
...(input.errMsg ? { errMsg: input.errMsg.slice(0, 200) } : {}),
|
|
},
|
|
responseBody: { reported: true },
|
|
status: input.status === 'success' ? 'SUCCESS' : 'FAILED',
|
|
errorMessage: input.status === 'fail' ? input.errMsg?.slice(0, 512) : undefined,
|
|
actorRef: input.actorRef,
|
|
});
|
|
return { ok: true, logId: logId.toString() };
|
|
}
|
|
}
|