| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
- import { inject, Injectable } from '@angular/core';
- import { catchError, map, Observable, throwError } from 'rxjs';
- import { API_CONFIG } from './app.constants';
- import { requiresCloudSession, usesCloudGateway } from './cloud-action-migration';
- import { ApiEnvelope } from './models';
- import { SessionService } from './session.service';
- export class ApiError extends Error {
- constructor(message: string, readonly status = 0, readonly payload?: unknown) {
- super(message);
- }
- }
- @Injectable({ providedIn: 'root' })
- export class ApiService {
- private readonly http = inject(HttpClient);
- private readonly sessions = inject(SessionService);
- get<T>(action: string, params: Record<string, unknown> = {}): Observable<ApiEnvelope<T>> {
- const token = this.parseSessionToken();
- if (usesCloudGateway(action, params, Boolean(token))) return this.callCloud<T>(action, params, requiresCloudSession(action, params) ? token : '');
- return this.http
- .get<unknown>(this.actionUrl(action), { params: this.toHttpParams(params) })
- .pipe(map((response) => this.normalize<T>(response)), catchError((error) => this.handleError(error)));
- }
- post<T>(action: string, body: Record<string, unknown> = {}): Observable<ApiEnvelope<T>> {
- const token = this.parseSessionToken();
- if (action === 'user_login_passwd' && usesCloudGateway(action, body, Boolean(token))) {
- return this.callCloud<T>(action, body).pipe(
- catchError((error: ApiError) => [401, 403].includes(error.status)
- ? this.postLegacy<T>(action, body)
- : throwError(() => error)),
- );
- }
- if (usesCloudGateway(action, body, Boolean(token))) return this.callCloud<T>(action, body, requiresCloudSession(action, body) ? token : '');
- return this.postLegacy<T>(action, body);
- }
- private postLegacy<T>(action: string, body: Record<string, unknown>): Observable<ApiEnvelope<T>> {
- const encoded = this.toHttpParams(body);
- return this.http
- .post<unknown>(this.actionUrl(action), encoded.toString(), {
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
- })
- .pipe(map((response) => this.normalize<T>(response)), catchError((error) => this.handleError(error)));
- }
- callCloud<T>(action: string, params: Record<string, unknown> = {}, sessionToken = ''): Observable<ApiEnvelope<T>> {
- const body = {
- ...(sessionToken ? { token: sessionToken } : {}),
- params: { action, ...params },
- };
- return this.http
- .post<unknown>(API_CONFIG.cloudFunctionUrl, body, {
- headers: {
- 'Content-Type': 'application/json',
- 'X-Parse-Application-Id': API_CONFIG.parseAppId,
- },
- })
- .pipe(map((response) => this.normalize<T>(response)), catchError((error) => this.handleError(error)));
- }
- upload(file: File): Observable<ApiEnvelope<string>> {
- const formData = new FormData();
- formData.append('file', file, file.name);
- return this.http
- .post<unknown>(this.actionUrl('upload'), formData)
- .pipe(map((response) => this.normalize<string>(response)), catchError((error) => this.handleError(error)));
- }
- actionUrl(action: string): string {
- const query = new URLSearchParams({
- apiId: API_CONFIG.apiId,
- apiKey: API_CONFIG.apiKey,
- action,
- });
- return `${API_CONFIG.baseUrl}${API_CONFIG.endpoint}?${query.toString()}`;
- }
- assetUrl(path: unknown): string {
- const value = String(path ?? '').trim();
- if (!value) return `${API_CONFIG.baseUrl}/UploadFiles/nopic.svg`;
- if (/^(data:image|https?:\/\/)/i.test(value)) return value;
- return `${API_CONFIG.baseUrl}${value.startsWith('/') ? value : `/${value}`}`;
- }
- private normalize<T>(input: unknown): ApiEnvelope<T> {
- const envelope = (input ?? {}) as ApiEnvelope<unknown>;
- let result = envelope.result;
- if (typeof result === 'string') {
- try {
- result = JSON.parse(result) as unknown;
- } catch {
- // Some endpoints intentionally return plain text.
- }
- }
- const normalized = { ...envelope, result } as ApiEnvelope<T>;
- if (normalized.retcode === -1) {
- throw new ApiError(normalized.retmsg || '请求失败', 200, normalized);
- }
- return normalized;
- }
- private toHttpParams(values: Record<string, unknown>): HttpParams {
- let params = new HttpParams();
- for (const [key, raw] of Object.entries(values)) {
- if (raw === undefined || raw === null || raw === '') continue;
- const value = typeof raw === 'object' ? JSON.stringify(raw) : String(raw);
- params = params.set(key, value);
- }
- return params;
- }
- private handleError(error: unknown): Observable<never> {
- if (error instanceof ApiError) return throwError(() => error);
- if (error instanceof HttpErrorResponse) {
- const payload = error.error as { retmsg?: string } | string | null;
- const message = typeof payload === 'object' && payload?.retmsg
- ? payload.retmsg
- : error.message || '网络请求失败';
- return throwError(() => new ApiError(message, error.status, payload));
- }
- return throwError(() => new ApiError(error instanceof Error ? error.message : '未知错误'));
- }
- private parseSessionToken(): string {
- const token = this.sessions.token();
- return token.startsWith('r:') ? token : '';
- }
- }
|