147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import { BadRequestException, Body, Controller, Get, Inject, Post, Query, Req, UseGuards } from '@nestjs/common';
|
|
import { IsIn, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
|
|
import { ClientApp } from '@dukang/shared-types';
|
|
import type { Request } from 'express';
|
|
import { WECHAT_PROVIDER } from '../../integrations/integrations.constants';
|
|
import type { IWechatProvider } from '../../integrations/wechat/wechat.interface';
|
|
import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard';
|
|
import type { AuthUser } from '../../common/guards/jwt-auth.guard';
|
|
import { wechatActorRefFromAuth } from '../../integrations/wechat/wechat-log.util';
|
|
import { WechatLocationService } from './wechat-location.service';
|
|
|
|
class PhoneNumberDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
code: string;
|
|
|
|
@IsString()
|
|
@IsIn(['mini', 'h5'])
|
|
@IsOptional()
|
|
platform?: 'mini' | 'h5';
|
|
}
|
|
|
|
class WechatLocationDto {
|
|
@IsNumber()
|
|
@IsOptional()
|
|
latitude?: number;
|
|
|
|
@IsNumber()
|
|
@IsOptional()
|
|
longitude?: number;
|
|
|
|
@IsString()
|
|
@IsIn(['jssdk', 'geolocation'])
|
|
sdk: 'jssdk' | 'geolocation';
|
|
|
|
@IsString()
|
|
@IsIn(['success', 'fail'])
|
|
status: 'success' | 'fail';
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
errMsg?: string;
|
|
}
|
|
|
|
class WechatChooseImageDto {
|
|
@IsString()
|
|
@IsIn(['success', 'fail'])
|
|
status: 'success' | 'fail';
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
errMsg?: string;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
sourceType?: string;
|
|
|
|
@IsString()
|
|
@IsIn(['jssdk', 'choose', 'read', 'empty'])
|
|
@IsOptional()
|
|
stage?: 'jssdk' | 'choose' | 'read' | 'empty';
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
pageUrl?: string;
|
|
}
|
|
|
|
@Controller('common/wechat')
|
|
export class WechatController {
|
|
constructor(
|
|
@Inject(WECHAT_PROVIDER) private readonly wechat: IWechatProvider,
|
|
private readonly locationService: WechatLocationService,
|
|
) {}
|
|
|
|
@Get('jssdk-config')
|
|
async jssdkConfig(@Query('url') url: string, @Req() req: Request) {
|
|
if (!url) throw new BadRequestException('url 参数必填');
|
|
const decoded = decodeURIComponent(url).split('#')[0];
|
|
const pageUrl = this.normalizeJssdkUrl(decoded);
|
|
const user = (req as Request & { user?: AuthUser }).user;
|
|
const actorRef =
|
|
user?.actorType === 'USER' ? { refType: 'USER', refId: user.actorId } : undefined;
|
|
return this.wechat.createJssdkConfig(pageUrl, actorRef);
|
|
}
|
|
|
|
private normalizeJssdkUrl(rawUrl: string): string {
|
|
try {
|
|
const parsed = new URL(rawUrl);
|
|
parsed.hash = '';
|
|
parsed.searchParams.delete('code');
|
|
parsed.searchParams.delete('state');
|
|
const query = parsed.searchParams.toString();
|
|
return `${parsed.origin}${parsed.pathname}${query ? `?${query}` : ''}`;
|
|
} catch {
|
|
return rawUrl;
|
|
}
|
|
}
|
|
|
|
@Get('oauth-url')
|
|
oauthUrl(
|
|
@Query('redirectUri') redirectUri: string,
|
|
@Query('state') state: string,
|
|
@Query('scope') scope?: string,
|
|
) {
|
|
if (!redirectUri || !state) throw new BadRequestException('redirectUri 与 state 必填');
|
|
return { url: this.wechat.buildOAuthUrl(redirectUri, state, scope) };
|
|
}
|
|
|
|
@Post('phone-number')
|
|
phoneNumber(@Body() dto: PhoneNumberDto) {
|
|
return this.wechat
|
|
.getPhoneNumberByCode(dto.code, dto.platform ?? 'mini')
|
|
.then((phone) => ({ phone }));
|
|
}
|
|
|
|
@Post('location')
|
|
@UseGuards(OptionalJwtAuthGuard)
|
|
reportLocation(@Req() req: Request, @Body() dto: WechatLocationDto) {
|
|
const user = (req as Request & { user?: AuthUser }).user;
|
|
const userId = user?.actorType === 'USER' ? user.actorId : undefined;
|
|
const clientApp = (req.headers['x-client-app'] as string) || ClientApp.USER_H5;
|
|
return this.locationService.reportLocation({
|
|
latitude: dto.latitude,
|
|
longitude: dto.longitude,
|
|
sdk: dto.sdk,
|
|
status: dto.status,
|
|
errMsg: dto.errMsg,
|
|
clientApp,
|
|
userId,
|
|
});
|
|
}
|
|
|
|
@Post('choose-image')
|
|
@UseGuards(OptionalJwtAuthGuard)
|
|
reportChooseImage(@Req() req: Request, @Body() dto: WechatChooseImageDto) {
|
|
const user = (req as Request & { user?: AuthUser }).user;
|
|
return this.locationService.reportChooseImage({
|
|
status: dto.status,
|
|
errMsg: dto.errMsg,
|
|
sourceType: dto.sourceType,
|
|
stage: dto.stage,
|
|
pageUrl: dto.pageUrl,
|
|
actorRef: wechatActorRefFromAuth(user?.actorType, user?.actorId),
|
|
});
|
|
}
|
|
}
|