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 { 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; } } }