fix: 修复抖音内容审核适配器HTTP请求Observable问题
- 导入firstValueFrom从rxjs处理HttpService返回的Observable - 使用await firstValueFrom包装httpService.post调用 - 修复响应数据访问,使用response.data获取实际数据
This commit is contained in:
parent
6cd2e75d15
commit
39f2ecc4f5
|
|
@ -3,12 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import Client, {
|
import { firstValueFrom } from 'rxjs';
|
||||||
CensorImageRequest,
|
|
||||||
CensorImageResponse,
|
|
||||||
OauthClientTokenRequest,
|
|
||||||
} from '@open-dy/open_api_sdk';
|
|
||||||
import CredentialClient from '@open-dy/open_api_credential';
|
|
||||||
import { BaseContentAdapter } from './base-content.adapter';
|
import { BaseContentAdapter } from './base-content.adapter';
|
||||||
import { ContentAuditLogEntity } from '../entities/content-audit-log.entity';
|
import { ContentAuditLogEntity } from '../entities/content-audit-log.entity';
|
||||||
import { PlatformType } from '../../entities/platform-user.entity';
|
import { PlatformType } from '../../entities/platform-user.entity';
|
||||||
|
|
@ -20,7 +15,7 @@ import {
|
||||||
RiskLevel,
|
RiskLevel,
|
||||||
AuditSuggestion,
|
AuditSuggestion,
|
||||||
} from '../interfaces/content-moderation.interface';
|
} from '../interfaces/content-moderation.interface';
|
||||||
|
import { DouyinAuthClient } from './DouYinClient';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DouyinContentAdapter extends BaseContentAdapter {
|
export class DouyinContentAdapter extends BaseContentAdapter {
|
||||||
platform = PlatformType.BYTEDANCE;
|
platform = PlatformType.BYTEDANCE;
|
||||||
|
|
@ -32,13 +27,10 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
||||||
grantType: string;
|
grantType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
private douyinClient: Client;
|
|
||||||
private accessToken: string | null = null;
|
|
||||||
private tokenExpiresAt: Date | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly httpService: HttpService,
|
protected readonly httpService: HttpService,
|
||||||
protected readonly configService: ConfigService,
|
protected readonly configService: ConfigService,
|
||||||
|
protected readonly douyinAuthClient: DouyinAuthClient,
|
||||||
@InjectRepository(ContentAuditLogEntity)
|
@InjectRepository(ContentAuditLogEntity)
|
||||||
protected readonly auditLogRepository: Repository<ContentAuditLogEntity>,
|
protected readonly auditLogRepository: Repository<ContentAuditLogEntity>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -51,11 +43,6 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
||||||
appId: this.configService.get('BYTEDANCE_APP_ID') || '',
|
appId: this.configService.get('BYTEDANCE_APP_ID') || '',
|
||||||
grantType: this.configService.get('BYTEDANCE_GRANT_TYPE') || 'client_credential',
|
grantType: this.configService.get('BYTEDANCE_GRANT_TYPE') || 'client_credential',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.douyinClient = new Client({
|
|
||||||
clientKey: this.douyinConfig.clientKey,
|
|
||||||
clientSecret: this.douyinConfig.clientSecret,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async auditImage(auditData: ImageAuditRequest): Promise<ContentAuditResult> {
|
async auditImage(auditData: ImageAuditRequest): Promise<ContentAuditResult> {
|
||||||
|
|
@ -170,96 +157,37 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
||||||
* 获取访问令牌
|
* 获取访问令牌
|
||||||
*/
|
*/
|
||||||
private async getAccessToken(): Promise<string> {
|
private async getAccessToken(): Promise<string> {
|
||||||
// 检查token是否有效
|
return this.douyinAuthClient.getAccessToken().then(res => res.accessToken)
|
||||||
if (this.accessToken && this.tokenExpiresAt && new Date() < this.tokenExpiresAt) {
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取新的token
|
|
||||||
const params = new OauthClientTokenRequest({
|
|
||||||
clientKey: this.douyinConfig.clientKey,
|
|
||||||
clientSecret: this.douyinConfig.clientSecret,
|
|
||||||
grantType: this.douyinConfig.grantType,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.douyinClient.oauthClientToken(params);
|
|
||||||
|
|
||||||
if (response.errNo && response.errNo !== 0) {
|
|
||||||
throw new Error(`获取访问令牌失败: ${response.errMsg || '未知错误'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储token和过期时间
|
|
||||||
this.accessToken = response.accessToken || '';
|
|
||||||
if (response.expiresIn) {
|
|
||||||
// 提前5分钟刷新token
|
|
||||||
const expiresIn = parseInt(response.expiresIn.toString()) - 300;
|
|
||||||
this.tokenExpiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.accessToken) {
|
|
||||||
throw new Error('获取访问令牌失败: 返回的accessToken为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查并刷新token
|
|
||||||
*/
|
|
||||||
private async ensureValidToken(): Promise<string> {
|
|
||||||
if (!this.accessToken || !this.tokenExpiresAt || new Date() >= this.tokenExpiresAt) {
|
|
||||||
return this.getAccessToken();
|
|
||||||
}
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试token获取功能 - 公共方法用于测试
|
|
||||||
*/
|
|
||||||
async testTokenAcquisition(): Promise<{
|
|
||||||
success: boolean;
|
|
||||||
accessToken?: string;
|
|
||||||
expiresAt?: Date;
|
|
||||||
error?: string;
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const token = await this.getAccessToken();
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
accessToken: token.substring(0, 10) + '...', // 只显示前10位
|
|
||||||
expiresAt: this.tokenExpiresAt || undefined,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : '未知错误',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用抖音审核API (使用SDK v3)
|
* 调用抖音审核API (使用SDK v3)
|
||||||
|
* {
|
||||||
|
"app_id": "ttxxxxxxxxxxxxxxxx",
|
||||||
|
"access_token": "0d495e15563015e3f599c742384f546cac4ce63911464106af8094a0581bae7386dcff77b1b9b6fc4c16b69c9048ba2a2846c7ae8d8f07aa8b84a52bcb4d560a5b8724d99f8816600b5xxxxxxxxxx",
|
||||||
|
"image_data": "MGQ0OTVlMTU1NjMwMTVlM2Y1OTljNzQyMzg0ZjU0NmNhYzRjZTYzOTExNDY0MTA2YWY4MDk0YTA1ODFiYWU3Mzg2ZGNmZjc3YjFiOWI2ZmM0YzE2YjY5YzkwNDhiYTJhMjg0NmM3YWU4ZDhmMDdhYThiODRhNTJiY2I0ZDU2MGE1Yjg3MjRkOTlmODgxNjYwMGI1eHh4eHh4eHh4eA=="
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
private async callDouyinAuditAPI(
|
private async callDouyinAuditAPI(
|
||||||
auditData: ImageAuditRequest,
|
auditData: ImageAuditRequest,
|
||||||
): Promise<ContentAuditResult> {
|
): Promise<ContentAuditResult> {
|
||||||
// 确保有有效的access token
|
// 确保有有效的access token
|
||||||
const accessToken = await this.ensureValidToken();
|
const accessToken = await this.getAccessToken();
|
||||||
|
const url = `https://open.douyin.com/api/apps/v1/censor/image/`
|
||||||
const params = new CensorImageRequest({
|
const response = await firstValueFrom(
|
||||||
accessToken: accessToken,
|
this.httpService.post(url, {
|
||||||
appId: this.douyinConfig.appId,
|
app_id: this.douyinConfig.appId,
|
||||||
image: auditData.imageUrl,
|
image: auditData.imageUrl
|
||||||
imageData: auditData.imageBase64, // base64编码的图片数据(可选)
|
}, {
|
||||||
});
|
headers: {
|
||||||
|
[`access_token`]: accessToken,
|
||||||
const response = await this.douyinClient.censorImage(params);
|
[`content-type`]: `application/json`
|
||||||
|
}
|
||||||
if (response.errNo && response.errNo !== 0) {
|
})
|
||||||
throw new Error(`抖音审核API错误: ${response.errMsg || '未知错误'}`);
|
);
|
||||||
}
|
console.log({ response: response.data });
|
||||||
|
return this.parseDouyinSDKResponse(response.data, auditData.taskId || '');
|
||||||
return this.parseDouyinSDKResponse(response, auditData.taskId || '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -285,7 +213,7 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
||||||
* 解析抖音SDK响应
|
* 解析抖音SDK响应
|
||||||
*/
|
*/
|
||||||
private parseDouyinSDKResponse(
|
private parseDouyinSDKResponse(
|
||||||
responseData: CensorImageResponse,
|
responseData: any,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
): ContentAuditResult {
|
): ContentAuditResult {
|
||||||
// SDK v3的响应格式不同,需要根据实际返回结果进行适配
|
// SDK v3的响应格式不同,需要根据实际返回结果进行适配
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue