| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- import { Injectable } from '@angular/core';
- import { HttpClient, HttpHeaders } from '@angular/common/http';
- import { Observable, throwError } from 'rxjs';
- import { catchError, retry } from 'rxjs/operators';
- import { environment } from '../../environments/environment';
- // 抖音API接口定义
- interface DouyinSearchParams {
- keyword: string;
- cursor?: number | string; // 支持数字或字符串
- sort_type?: string;
- publish_time?: string;
- filter_duration?: string;
- content_type?: string;
- search_id?: string;
- }
- interface DouyinVideoDetailParams {
- aweme_id: string;
- }
- interface DouyinCommentsParams {
- aweme_id: string;
- cursor?: number | string;
- count?: number | string;
- }
- @Injectable({
- providedIn: 'root'
- })
- export class DouyinApiService {
- private readonly BASE_URL = environment.douyinApi.baseUrl;
- private readonly TIMEOUT = environment.douyinApi.timeout;
- private readonly MAX_RETRIES = environment.douyinApi.maxRetries;
- constructor(private http: HttpClient) {}
- /**
- * 抖音综合搜索
- * @param params 搜索参数
- * @returns 搜索结果
- */
- searchVideos(params: DouyinSearchParams): Observable<any> {
- const url = `${this.BASE_URL}/api/v1/douyin/search/fetch_challenge_search_v2`;
- const defaultParams = {
- cursor: 0,
- sort_type: '0',
- publish_time: '0',
- filter_duration: '0',
- content_type: '0',
- search_id: ''
- };
- const requestParams = {
- ...defaultParams,
- ...params,
- cursor: params.cursor || 0 // 保持cursor为数字,根据API文档
- };
- return this.http.post(url, requestParams, {
- headers: this.getHeaders(),
- timeout: this.TIMEOUT
- }).pipe(
- retry(this.MAX_RETRIES),
- catchError(this.handleError)
- );
- }
- /**
- * 获取单个视频详情
- * @param aweme_id 视频ID
- * @returns 视频详情
- */
- getVideoDetail(aweme_id: string): Observable<any> {
- const url = `${this.BASE_URL}/api/v1/douyin/app/v3/fetch_one_video_v3`;
- return this.http.get(url, {
- params: { aweme_id },
- headers: this.getHeaders(),
- timeout: this.TIMEOUT
- }).pipe(
- retry(this.MAX_RETRIES),
- catchError(this.handleError)
- );
- }
- /**
- * 获取视频评论
- * @param params 评论参数
- * @returns 评论列表
- */
- getVideoComments(params: DouyinCommentsParams): Observable<any> {
- const url = `${this.BASE_URL}/app/v3/fetch_video_comments`;
- const defaultParams = {
- cursor: 0,
- count: 20
- };
- const requestParams = {
- ...defaultParams,
- ...params,
- cursor: params.cursor || 0,
- count: params.count || 20
- };
- return this.http.get(url, {
- params: requestParams,
- headers: this.getHeaders(),
- timeout: this.TIMEOUT
- }).pipe(
- retry(this.MAX_RETRIES),
- catchError(this.handleError)
- );
- }
- /**
- * 获取评论回复
- * @param item_id 视频ID
- * @param comment_id 评论ID
- * @param cursor 游标
- * @returns 回复列表
- */
- getCommentReplies(item_id: string, comment_id: string, cursor: number = 0): Observable<any> {
- const url = `${this.BASE_URL}/comment/reply/list`;
- return this.http.post(url, {
- item_id,
- comment_id,
- cursor,
- count: 20
- }, {
- headers: this.getHeaders(),
- timeout: this.TIMEOUT
- }).pipe(
- retry(this.MAX_RETRIES),
- catchError(this.handleError)
- );
- }
- /**
- * 获取用户信息
- * @param sec_uid 用户sec_uid
- * @returns 用户信息
- */
- getUserProfile(sec_uid: string): Observable<any> {
- const url = `${this.BASE_URL}/app/v3/handler_user_profile`;
- return this.http.get(url, {
- params: { sec_user_id: sec_uid },
- headers: this.getHeaders(),
- timeout: this.TIMEOUT
- }).pipe(
- retry(this.MAX_RETRIES),
- catchError(this.handleError)
- );
- }
- /**
- * 获取请求头(使用环境配置)
- */
- private getHeaders(): HttpHeaders {
- return new HttpHeaders({
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- 'Authorization': `Bearer ${environment.douyinApi.token}`
- });
- }
- /**
- * 错误处理
- */
- private handleError(error: any): Observable<never> {
- let errorMessage = '';
- if (error.error instanceof ErrorEvent) {
- // 客户端错误
- errorMessage = `网络错误: ${error.error.message}`;
- } else {
- // 服务端错误
- switch (error.status) {
- case 401:
- errorMessage = '认证失败 (401): Token无效或已过期';
- break;
- case 403:
- errorMessage = '访问被拒绝 (403): 权限不足';
- break;
- case 429:
- errorMessage = '请求过于频繁 (429): 请稍后重试';
- break;
- case 500:
- errorMessage = '服务器内部错误 (500): 请稍后重试';
- break;
- case 400:
- errorMessage = '请求参数错误 (400): 请检查请求参数格式';
- break;
- case 0:
- errorMessage = '网络连接失败: 请检查网络设置';
- break;
- default:
- errorMessage = `请求失败 (${error.status}): ${error.message || '未知错误'}`;
- }
- // 检查API返回的具体错误信息
- if (error.error && error.error.msg) {
- errorMessage += ` | API错误: ${error.error.msg}`;
- }
- }
- console.error('Douyin API Error:', errorMessage, error);
- return throwError(() => new Error(errorMessage));
- }
- }
|