120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { loadAppConfig } from '@dukang/shared-types';
|
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
|
import type { WechatActorRef } from '../wechat/wechat-log.util';
|
|
|
|
export type ReverseGeocodeResult = {
|
|
province: string;
|
|
city: string;
|
|
district: string;
|
|
logId: bigint;
|
|
};
|
|
|
|
function normalizeCityName(name: string) {
|
|
return name.replace(/市$/, '').trim();
|
|
}
|
|
|
|
@Injectable()
|
|
export class TencentLbsProvider {
|
|
private readonly logger = new Logger(TencentLbsProvider.name);
|
|
private readonly config = loadAppConfig();
|
|
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
isEnabled() {
|
|
return !!this.config.tencentLbsKey;
|
|
}
|
|
|
|
async reverseGeocode(
|
|
latitude: number,
|
|
longitude: number,
|
|
actorRef?: WechatActorRef,
|
|
): Promise<ReverseGeocodeResult | null> {
|
|
const baseLog = {
|
|
provider: 'WECHAT_MAP' as const,
|
|
scene: 'REVERSE_GEOCODE',
|
|
refType: actorRef?.refType,
|
|
refId: actorRef?.refId,
|
|
requestUrl: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
requestBody: {
|
|
latitude: Number(latitude.toFixed(6)),
|
|
longitude: Number(longitude.toFixed(6)),
|
|
},
|
|
};
|
|
|
|
if (!this.isEnabled()) {
|
|
const log = await this.prisma.logThirdParty.create({
|
|
data: {
|
|
...baseLog,
|
|
status: 'FAILED',
|
|
errorMessage: 'TENCENT_LBS_KEY 未配置',
|
|
},
|
|
});
|
|
this.logger.warn('Tencent LBS key missing, skip reverse geocode');
|
|
return null;
|
|
}
|
|
|
|
const location = `${latitude},${longitude}`;
|
|
const url = new URL('https://apis.map.qq.com/ws/geocoder/v1/');
|
|
url.searchParams.set('location', location);
|
|
url.searchParams.set('key', this.config.tencentLbsKey);
|
|
url.searchParams.set('get_poi', '0');
|
|
|
|
try {
|
|
const res = await fetch(url.toString());
|
|
const data = (await res.json()) as {
|
|
status?: number;
|
|
message?: string;
|
|
result?: {
|
|
ad_info?: {
|
|
province?: string;
|
|
city?: string;
|
|
district?: string;
|
|
};
|
|
};
|
|
};
|
|
|
|
const ad = data.result?.ad_info;
|
|
const ok = data.status === 0 && !!ad?.city;
|
|
const responseBody = {
|
|
status: data.status,
|
|
message: data.message,
|
|
province: ad?.province,
|
|
city: ad?.city,
|
|
district: ad?.district,
|
|
};
|
|
|
|
const log = await this.prisma.logThirdParty.create({
|
|
data: {
|
|
...baseLog,
|
|
responseBody,
|
|
status: ok ? 'SUCCESS' : 'FAILED',
|
|
errorMessage: ok ? undefined : data.message ?? '逆地理编码失败',
|
|
},
|
|
});
|
|
|
|
if (!ok || !ad?.province || !ad?.city) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
province: ad.province,
|
|
city: normalizeCityName(ad.city),
|
|
district: ad.district ?? '',
|
|
logId: log.id,
|
|
};
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
this.logger.error(`Tencent LBS reverse geocode failed: ${message}`);
|
|
await this.prisma.logThirdParty.create({
|
|
data: {
|
|
...baseLog,
|
|
status: 'FAILED',
|
|
errorMessage: message.slice(0, 512),
|
|
},
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
}
|