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 { ConfigService } from '@nestjs/config';
|
||||
import { Repository } from 'typeorm';
|
||||
import Client, {
|
||||
CensorImageRequest,
|
||||
CensorImageResponse,
|
||||
OauthClientTokenRequest,
|
||||
} from '@open-dy/open_api_sdk';
|
||||
import CredentialClient from '@open-dy/open_api_credential';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { BaseContentAdapter } from './base-content.adapter';
|
||||
import { ContentAuditLogEntity } from '../entities/content-audit-log.entity';
|
||||
import { PlatformType } from '../../entities/platform-user.entity';
|
||||
|
|
@ -20,7 +15,7 @@ import {
|
|||
RiskLevel,
|
||||
AuditSuggestion,
|
||||
} from '../interfaces/content-moderation.interface';
|
||||
|
||||
import { DouyinAuthClient } from './DouYinClient';
|
||||
@Injectable()
|
||||
export class DouyinContentAdapter extends BaseContentAdapter {
|
||||
platform = PlatformType.BYTEDANCE;
|
||||
|
|
@ -32,13 +27,10 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
|||
grantType: string;
|
||||
};
|
||||
|
||||
private douyinClient: Client;
|
||||
private accessToken: string | null = null;
|
||||
private tokenExpiresAt: Date | null = null;
|
||||
|
||||
constructor(
|
||||
protected readonly httpService: HttpService,
|
||||
protected readonly configService: ConfigService,
|
||||
protected readonly douyinAuthClient: DouyinAuthClient,
|
||||
@InjectRepository(ContentAuditLogEntity)
|
||||
protected readonly auditLogRepository: Repository<ContentAuditLogEntity>,
|
||||
) {
|
||||
|
|
@ -51,11 +43,6 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
|||
appId: this.configService.get('BYTEDANCE_APP_ID') || '',
|
||||
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> {
|
||||
|
|
@ -170,96 +157,37 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
|||
* 获取访问令牌
|
||||
*/
|
||||
private async getAccessToken(): Promise<string> {
|
||||
// 检查token是否有效
|
||||
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;
|
||||
return this.douyinAuthClient.getAccessToken().then(res => res.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)
|
||||
* {
|
||||
"app_id": "ttxxxxxxxxxxxxxxxx",
|
||||
"access_token": "0d495e15563015e3f599c742384f546cac4ce63911464106af8094a0581bae7386dcff77b1b9b6fc4c16b69c9048ba2a2846c7ae8d8f07aa8b84a52bcb4d560a5b8724d99f8816600b5xxxxxxxxxx",
|
||||
"image_data": "MGQ0OTVlMTU1NjMwMTVlM2Y1OTljNzQyMzg0ZjU0NmNhYzRjZTYzOTExNDY0MTA2YWY4MDk0YTA1ODFiYWU3Mzg2ZGNmZjc3YjFiOWI2ZmM0YzE2YjY5YzkwNDhiYTJhMjg0NmM3YWU4ZDhmMDdhYThiODRhNTJiY2I0ZDU2MGE1Yjg3MjRkOTlmODgxNjYwMGI1eHh4eHh4eHh4eA=="
|
||||
}
|
||||
*/
|
||||
private async callDouyinAuditAPI(
|
||||
auditData: ImageAuditRequest,
|
||||
): Promise<ContentAuditResult> {
|
||||
// 确保有有效的access token
|
||||
const accessToken = await this.ensureValidToken();
|
||||
|
||||
const params = new CensorImageRequest({
|
||||
accessToken: accessToken,
|
||||
appId: this.douyinConfig.appId,
|
||||
image: auditData.imageUrl,
|
||||
imageData: auditData.imageBase64, // base64编码的图片数据(可选)
|
||||
});
|
||||
|
||||
const response = await this.douyinClient.censorImage(params);
|
||||
|
||||
if (response.errNo && response.errNo !== 0) {
|
||||
throw new Error(`抖音审核API错误: ${response.errMsg || '未知错误'}`);
|
||||
}
|
||||
|
||||
return this.parseDouyinSDKResponse(response, auditData.taskId || '');
|
||||
const accessToken = await this.getAccessToken();
|
||||
const url = `https://open.douyin.com/api/apps/v1/censor/image/`
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(url, {
|
||||
app_id: this.douyinConfig.appId,
|
||||
image: auditData.imageUrl
|
||||
}, {
|
||||
headers: {
|
||||
[`access_token`]: accessToken,
|
||||
[`content-type`]: `application/json`
|
||||
}
|
||||
})
|
||||
);
|
||||
console.log({ response: response.data });
|
||||
return this.parseDouyinSDKResponse(response.data, auditData.taskId || '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -285,7 +213,7 @@ export class DouyinContentAdapter extends BaseContentAdapter {
|
|||
* 解析抖音SDK响应
|
||||
*/
|
||||
private parseDouyinSDKResponse(
|
||||
responseData: CensorImageResponse,
|
||||
responseData: any,
|
||||
taskId: string,
|
||||
): ContentAuditResult {
|
||||
// SDK v3的响应格式不同,需要根据实际返回结果进行适配
|
||||
|
|
|
|||
Loading…
Reference in New Issue