diff --git a/src/content-moderation/adapters/douyin-content.adapter.ts b/src/content-moderation/adapters/douyin-content.adapter.ts index b2d2538..a67ba25 100644 --- a/src/content-moderation/adapters/douyin-content.adapter.ts +++ b/src/content-moderation/adapters/douyin-content.adapter.ts @@ -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, ) { @@ -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 { @@ -170,96 +157,37 @@ export class DouyinContentAdapter extends BaseContentAdapter { * 获取访问令牌 */ private async getAccessToken(): Promise { - // 检查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 { - 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 { // 确保有有效的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的响应格式不同,需要根据实际返回结果进行适配