Skip to main content

抽象服务(AbstractService)

AbstractService 是前端框架中最核心的能力之一:它在保持 request 方法签名简单 的同时,内置了:

  • HTTP 基础能力封装(基于 Url 枚举与 ResultVO)
  • ECC 密钥交换与报文加解密
  • 防重复提交(时间窗口去重)
  • 设备标识采集(设备 ID + 硬件指纹签名)
  • 请求头自动管理与重试机制

1. 设计目标​

传统方式中,一个普通 Service 往往长成这样:

// 传统写法(示例)
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}

listUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users', {
headers: {
Authorization: localStorage.getItem('token') ?? '',
},
});
}
}

问题在于:

  • 每个 Service 都要关心 baseUrl、路径、请求头、错误处理
  • 难以统一做 设备指纹、防重复提交、加解密 等横切逻辑

AbstractService 的目标是:把这些基础能力全部下沉,让业务 Service 只关心「调用哪个 Url、传什么参数」。

2. 基本用法​

import {Injectable} from '@angular/core';
import {AbstractService, ResultVO, Url} from '@rydeen/angular-framework';
import {AppUrl} from '../../core/constants/app.url';
import {User} from '../types/users.types';

@Injectable({ providedIn: 'root' })
export class UserService extends AbstractService {
async list(): Promise<ResultVO<User[]>> {
return await this.request<User[]>(AppUrl.Users.List);
}

async save(user: User): Promise<ResultVO<void>> {
return await this.request<void>(AppUrl.Users.Save, user);
}
}

AppUrl.Users.List / Save 是一个 Url 枚举实例,内部已经包含了 HTTP 方法与路径。

3. 请求流程(Mermaid)​

你只在顶层看到一个 request<T>(url, body),但框架在内部完成了一整套安全与稳定性逻辑。

4. 防重复提交与重试​

DuplicateSubmitModule 使用「时间窗口 + 请求内容哈希」的方式生成请求 ID:

  • 在同一时间窗口内,相同 URL + 方法 + 请求体 + 用户 的请求仅允许通过一次
  • 可以有效避免按钮连点导致的重复扣款 / 重复创建订单

如果需要开启重试机制,可以在创建 AbstractService 子类时覆盖配置(示意):

// 示例:在构造函数或工厂中传入 HttpClientConfig(具体实现见源码)
const config: HttpClientConfig = {
baseUrl: environment.apiBase,
maxRetries: 2,
retryInterval: 500,
duplicateSubmitTimeWindow: 3000,
};

5. 设备标识(设备 ID 与硬件指纹)​

通过 AbstractService 发起的请求,会在统一的 Header 构建阶段自动附带两类设备标识字段:

  • X-Device-Id:稳定的浏览器设备 ID

    • 由 getOrCreateDeviceId() 基于浏览器指纹生成一次,并持久化在 localStorage / sessionStorage;
    • 用于在较长时间维度上标识同一物理终端(同一浏览器实例)。
  • X-Hardware-Fingerprint:带安全字段的硬件指纹签名

    • 由 generateSecureHardwareFingerprint() + signHardwareFingerprint() 动态生成,包含时间戳与随机数;
    • 后端可用来做「可信设备」「异常登录检测」「Token 绑定设备」等安全策略。

这些字段的几个关键点:

  • 对前端业务是透明的:业务代码无需关心如何生成/保存,只要正常调用 request() 即可;
  • 主要供后端使用:用于做客户端身份标识、风控与审计;
  • 前端只需要注意:不要在自定义拦截器 / Mock 层中无意删除或篡改 X-Device-Id、X-Hardware-Fingerprint 等 Header。

6. 在应用中扩展:AbstractAppService​

在 cross-platform-app 中,并没有直接让业务 Service 继承 AbstractService,而是通过一层 应用级抽象服务 来约束所有 Service 的行为:

// src/app/shared/abstracts/abstract.app.service.ts
import {
AbstractService,
EventManager,
RequestHeader,
ResultVO,
Url,
} from '@rydeen/angular-framework';
import { inject } from '@angular/core';
import { AppEvent } from '../../core/constants/app.event';

export abstract class AbstractAppService extends AbstractService {
private readonly eventManager = inject(EventManager);

override async request<T>(url: Url, body?: any, header?: RequestHeader): Promise<ResultVO<T>> {
this.eventManager.publish(AppEvent.SHOW_GLOBAL_LOADING, true);
return await super.request<T>(url, body, header, async () => {
this.eventManager.publish(AppEvent.SHOW_GLOBAL_LOADING, false);
});
}
}

所有具体 Service(如 UserService、MenuService 等)统一继承 AbstractAppService,自然获得:

  • 全局 Loading 遮罩(开始请求时触发显示,结束时自动关闭)
  • 统一的错误处理与日志策略(可继续在此处扩展)

7. 小结:对比传统写法的优势​

  • 统一的 HTTP 调用入口:所有接口请求都经由 AbstractService.request,方便加限流、埋点、追踪。
  • 内置安全与稳定能力:ECC 加密、防重复提交、重试等开箱即用。
  • 设备标识自动附带:设备 ID + 硬件指纹签名对前端透明、对后端可见,便于做安全与风控策略。
  • 与后端 ResultVO / Page 完全对齐:公共类型在前后端共享,接口语义更清晰。
  • 易于二次封装:应用可以像 AbstractAppService 一样,根据自身需求增加 Loading / Toast / 埋点等横切逻辑。