cloud-functions.service.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { HttpClient, HttpErrorResponse } from '@angular/common/http';
  2. import { inject, Injectable } from '@angular/core';
  3. import { firstValueFrom, Subject } from 'rxjs';
  4. import { CLOUD_FUNCTION_BASE_URL } from './admin.constants';
  5. import { AdminSessionService } from './admin-session.service';
  6. import { CloudEnvelope, LegacyCloudEnvelope } from './admin.models';
  7. export class CloudFunctionError extends Error {
  8. constructor(message: string, readonly status = 0, readonly payload?: unknown) {
  9. super(message);
  10. }
  11. }
  12. @Injectable({ providedIn: 'root' })
  13. export class CloudFunctionsService {
  14. readonly dataChanges$ = new Subject<'local' | 'broadcast'>();
  15. private readonly http = inject(HttpClient);
  16. private readonly sessions = inject(AdminSessionService);
  17. private readonly dataChannel = this.createDataChannel();
  18. async admin<T>(operation: string, params: Record<string, unknown> = {}): Promise<T> {
  19. const token = this.sessions.session()?.sessionToken;
  20. if (!token) throw new CloudFunctionError('管理员会话已失效', 401);
  21. try {
  22. const envelope = await firstValueFrom(this.http.post<CloudEnvelope<T>>(
  23. `${CLOUD_FUNCTION_BASE_URL}/xiaoshu/admin/gateway`,
  24. { token, params: { operation, ...params } },
  25. ));
  26. if (!envelope.success) { await this.handleAuthenticationFailure(token, 0, envelope); throw new CloudFunctionError(envelope.message || envelope.error || '云函数执行失败'); }
  27. if (this.isMutation(operation)) this.announceDataChange();
  28. return envelope.data;
  29. } catch (error) {
  30. if (error instanceof CloudFunctionError) throw error;
  31. if (error instanceof HttpErrorResponse) {
  32. const payload = error.error as { error?: string; message?: string } | null;
  33. await this.handleAuthenticationFailure(token, error.status, payload);
  34. throw new CloudFunctionError(payload?.message || payload?.error || error.message, error.status, payload);
  35. }
  36. throw new CloudFunctionError(error instanceof Error ? error.message : '未知云函数错误');
  37. }
  38. }
  39. private async handleAuthenticationFailure(token: string, status: number, payload: unknown): Promise<void> {
  40. const body = payload as { code?: number; error?: string; message?: string; retmsg?: string } | null;
  41. const message = String(body?.message || body?.retmsg || body?.error || '');
  42. const invalid = status === 401 || Number(body?.code) === 209 ||
  43. /^(登录状态已失效|管理员会话已失效|账号已停用|账号已删除|invalid session token)[::。.!]?$/i.test(message.trim());
  44. if (/^(账号已停用|账号已删除)[::。.!]?$/.test(message.trim())) this.sessions.invalidateSession(token);
  45. else if (invalid) await this.sessions.verifySessionAfterFailure(token);
  46. }
  47. private isMutation(operation: string): boolean {
  48. return /\/(?:create|save|update|delete|recycle|restore|complete|adjust|post|commit|confirm|cancel|review|mark-paid|deactivate|run|retry|refresh|add-adjustment|exclude-line|import-commit)$/.test(operation);
  49. }
  50. private createDataChannel(): BroadcastChannel | null {
  51. if (typeof globalThis.BroadcastChannel !== 'function') return null;
  52. const channel = new globalThis.BroadcastChannel('xiaoshu-live-data-v1');
  53. channel.addEventListener('message', () => this.dataChanges$.next('broadcast'));
  54. return channel;
  55. }
  56. private announceDataChange(): void {
  57. this.dataChanges$.next('local');
  58. this.dataChannel?.postMessage({ changedAt: new Date().toISOString() });
  59. }
  60. async operations<T>(operation: string, params: Record<string, unknown> = {}): Promise<T> {
  61. const token = this.sessions.session()?.sessionToken;
  62. if (!token) throw new CloudFunctionError('管理员会话已失效', 401);
  63. try {
  64. const envelope = await firstValueFrom(this.http.post<CloudEnvelope<T>>(
  65. `${CLOUD_FUNCTION_BASE_URL}/xiaoshu/ops/gateway-v3`,
  66. { token, params: { operation, ...params } },
  67. ));
  68. if (!envelope.success) { await this.handleAuthenticationFailure(token, 0, envelope); throw new CloudFunctionError(envelope.message || envelope.error || '运营云函数执行失败'); }
  69. if (this.isMutation(operation)) this.announceDataChange();
  70. return envelope.data;
  71. } catch (error) {
  72. if (error instanceof CloudFunctionError) throw error;
  73. if (error instanceof HttpErrorResponse) {
  74. const payload = error.error as { error?: string; message?: string } | null;
  75. await this.handleAuthenticationFailure(token, error.status, payload);
  76. throw new CloudFunctionError(payload?.message || payload?.error || error.message, error.status, payload);
  77. }
  78. throw new CloudFunctionError(error instanceof Error ? error.message : '未知运营云函数错误');
  79. }
  80. }
  81. async app<T>(action: string, params: Record<string, unknown> = {}): Promise<T> {
  82. const token = this.sessions.session()?.sessionToken;
  83. if (!token) throw new CloudFunctionError('管理员会话已失效', 401);
  84. try {
  85. const envelope = await firstValueFrom(this.http.post<LegacyCloudEnvelope<T>>(
  86. `${CLOUD_FUNCTION_BASE_URL}/xiaoshu/app/gateway`,
  87. { token, params: { action, ...params } },
  88. ));
  89. if (envelope.retcode === -1) { await this.handleAuthenticationFailure(token, 0, envelope); throw new CloudFunctionError(envelope.retmsg || '云函数执行失败'); }
  90. return envelope.result;
  91. } catch (error) {
  92. if (error instanceof CloudFunctionError) throw error;
  93. if (error instanceof HttpErrorResponse) {
  94. const payload = error.error as { error?: string; message?: string; retmsg?: string } | null;
  95. await this.handleAuthenticationFailure(token, error.status, payload);
  96. throw new CloudFunctionError(payload?.retmsg || payload?.message || payload?.error || error.message, error.status, payload);
  97. }
  98. throw new CloudFunctionError(error instanceof Error ? error.message : '未知云函数错误');
  99. }
  100. }
  101. }