|
|
@@ -1,6 +1,6 @@
|
|
|
-import { Injectable } from '@angular/core';
|
|
|
-import { HttpClient } from '@angular/common/http';
|
|
|
-import { Observable, catchError, shareReplay, throwError } from 'rxjs';
|
|
|
+import { Injectable, signal } from '@angular/core';
|
|
|
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
|
|
+import { Observable, catchError, shareReplay, tap, throwError } from 'rxjs';
|
|
|
|
|
|
export interface UpstreamStatus {
|
|
|
provider: string;
|
|
|
@@ -201,21 +201,39 @@ export interface FmodeApiTopups {
|
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
export class ApiService {
|
|
|
- /** 后端为 future-server 的 /api/profit 子路由,纯数据接口 */
|
|
|
- private readonly base = '/api/profit/api';
|
|
|
- private readonly key: string;
|
|
|
+ private readonly base = 'https://server.fmode.cn/api/profit/api';
|
|
|
+ private readonly sessionKey = 'voc-profit-key';
|
|
|
+ private key = '';
|
|
|
private readonly cache = new Map<string, Observable<unknown>>();
|
|
|
+ readonly authenticated = signal(false);
|
|
|
|
|
|
constructor(private http: HttpClient) {
|
|
|
- this.key =
|
|
|
- new URLSearchParams(location.search).get('key') ||
|
|
|
- localStorage.getItem('voc-profit-key') ||
|
|
|
- 'fmode-voc-profit';
|
|
|
- localStorage.setItem('voc-profit-key', this.key);
|
|
|
+ localStorage.removeItem(this.sessionKey);
|
|
|
}
|
|
|
|
|
|
- private q(params: string): string {
|
|
|
- return `${params}&key=${encodeURIComponent(this.key)}`;
|
|
|
+ getSessionKey(): string {
|
|
|
+ return sessionStorage.getItem(this.sessionKey) || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ authenticate(key: string): Observable<{ ok: boolean }> {
|
|
|
+ const nextKey = key.trim();
|
|
|
+ const headers = new HttpHeaders({ Authorization: `Bearer ${nextKey}` });
|
|
|
+
|
|
|
+ return this.http.get<{ ok: boolean }>(`${this.base}/auth`, { headers }).pipe(
|
|
|
+ tap(() => {
|
|
|
+ this.key = nextKey;
|
|
|
+ sessionStorage.setItem(this.sessionKey, nextKey);
|
|
|
+ this.cache.clear();
|
|
|
+ this.authenticated.set(true);
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ clearAccess() {
|
|
|
+ this.key = '';
|
|
|
+ sessionStorage.removeItem(this.sessionKey);
|
|
|
+ this.cache.clear();
|
|
|
+ this.authenticated.set(false);
|
|
|
}
|
|
|
|
|
|
clearCache() {
|
|
|
@@ -226,10 +244,12 @@ export class ApiService {
|
|
|
const cached = this.cache.get(url);
|
|
|
if (cached) return cached as Observable<T>;
|
|
|
|
|
|
- const request = this.http.get<T>(url).pipe(
|
|
|
+ const headers = new HttpHeaders({ Authorization: `Bearer ${this.key}` });
|
|
|
+ const request = this.http.get<T>(url, { headers }).pipe(
|
|
|
shareReplay({ bufferSize: 1, refCount: false }),
|
|
|
catchError((error: unknown) => {
|
|
|
this.cache.delete(url);
|
|
|
+ if (error instanceof HttpErrorResponse && error.status === 401) this.clearAccess();
|
|
|
return throwError(() => error);
|
|
|
}),
|
|
|
);
|
|
|
@@ -243,47 +263,47 @@ export class ApiService {
|
|
|
}
|
|
|
|
|
|
getOverview(days: number, month?: string): Observable<Overview> {
|
|
|
- return this.cachedGet<Overview>(`${this.base}/overview?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<Overview>(`${this.base}/overview?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
/** 上一周期 overview(环比对比用):month=上月,或 start/end 日期范围;lite=1 不落快照 */
|
|
|
getOverviewPrev(prev: { month?: string; start?: string; end?: string }): Observable<Overview> {
|
|
|
const p = prev.month ? `month=${prev.month}` : `start=${prev.start}&end=${prev.end}`;
|
|
|
- return this.cachedGet<Overview>(`${this.base}/overview?${this.q(`${p}&lite=1`)}`);
|
|
|
+ return this.cachedGet<Overview>(`${this.base}/overview?${p}&lite=1`);
|
|
|
}
|
|
|
|
|
|
getFmodeApiRecharge(days: number, month?: string): Observable<FmodeApiRecharge> {
|
|
|
- return this.cachedGet<FmodeApiRecharge>(`${this.base}/fmodeapi/recharge?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<FmodeApiRecharge>(`${this.base}/fmodeapi/recharge?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
getFmodeApiUsers(limit = 20, offset = 0, search = ''): Observable<FmodeApiUsers> {
|
|
|
const s = search ? `&search=${encodeURIComponent(search)}` : '';
|
|
|
- return this.cachedGet<FmodeApiUsers>(`${this.base}/fmodeapi/users?${this.q(`limit=${limit}&offset=${offset}${s}`)}`);
|
|
|
+ return this.cachedGet<FmodeApiUsers>(`${this.base}/fmodeapi/users?limit=${limit}&offset=${offset}${s}`);
|
|
|
}
|
|
|
|
|
|
/** NewAPI 钱包充值逐笔明细 */
|
|
|
getFmodeApiTopups(days: number, month?: string): Observable<FmodeApiTopups> {
|
|
|
- return this.cachedGet<FmodeApiTopups>(`${this.base}/fmodeapi/topups?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<FmodeApiTopups>(`${this.base}/fmodeapi/topups?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
/** 用户消耗统计:排行 + 趋势;timeQ 为 days=N 或 month=YYYY-MM */
|
|
|
getFmodeApiUserStats(timeQ: string, top: number, gran: 'hour' | 'day'): Observable<FmodeApiUserStats> {
|
|
|
- return this.cachedGet<FmodeApiUserStats>(`${this.base}/fmodeapi/user-stats?${this.q(`${timeQ}&top=${top}&gran=${gran}`)}`);
|
|
|
+ return this.cachedGet<FmodeApiUserStats>(`${this.base}/fmodeapi/user-stats?${timeQ}&top=${top}&gran=${gran}`);
|
|
|
}
|
|
|
|
|
|
getApigOrders(days: number, month?: string): Observable<ApigOrderGroups> {
|
|
|
- return this.cachedGet<ApigOrderGroups>(`${this.base}/orders?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<ApigOrderGroups>(`${this.base}/orders?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
getUpstreamCost(days: number, month?: string): Observable<UpstreamCost> {
|
|
|
- return this.cachedGet<UpstreamCost>(`${this.base}/upstream-cost?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<UpstreamCost>(`${this.base}/upstream-cost?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
getModuleForwardingDetails(days: number, month?: string): Observable<ModuleForwardingDetails> {
|
|
|
- return this.cachedGet<ModuleForwardingDetails>(`${this.base}/module-forwarding?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<ModuleForwardingDetails>(`${this.base}/module-forwarding?${this.range(days, month)}`);
|
|
|
}
|
|
|
|
|
|
getFmodeApiNewUsers(days: number, month?: string): Observable<FmodeApiNewUsers> {
|
|
|
- return this.cachedGet<FmodeApiNewUsers>(`${this.base}/fmodeapi/new-users?${this.q(this.range(days, month))}`);
|
|
|
+ return this.cachedGet<FmodeApiNewUsers>(`${this.base}/fmodeapi/new-users?${this.range(days, month)}`);
|
|
|
}
|
|
|
}
|