Skip to main content

HTTP 与 API 访问层

本章节聚焦这一点:前端如何优雅地调用后端 API,同时兼顾:

  • 与 Java 服务端统一的 ResultVO<Page<T>> 协议;
  • 安全性:加解密、防重复提交、设备指纹;
  • 可观测性:统一错误处理、全局 Loading、埋点。

1. 传统写法回顾:直接用 HttpClient/axios 调接口​

先回忆一下最常见的前端 HTTP 写法,这里用 Vue3 + axios 举例:

// api/user.ts
import axios from 'axios';

export async function fetchUsers(params?: any) {
const res = await axios.get('/api/users', { params });
return res.data; // 结构往往是 any
}

// 组件中
const users = ref<User[]>([]);
onMounted(async () => {
const data = await fetchUsers();
users.value = data;
});

问题在于:

  • URL 和 Header 很容易在多个地方复制粘贴;
  • 与后端 ResultVO<T> 协议不一致,错误处理方式千奇百怪;
  • 一旦要加入「防重复提交、设备指纹、加解密」,旧代码几乎得重写。

2. 新架构中的 HTTP 访问层分工​

在本框架中,HTTP 访问被拆成几层:

对应职责:

  • 页面组件:只关心「调用哪个 Service、拿到什么数据」。
  • 业务 Service (AbstractAppService 的子类):只关心「调用哪个 Url」。
  • AbstractService:统一处理所有 HTTP 细节。
  • 拦截器 / Crypto / Duplicate 模块:实现具体的加解密、重试、防重复提交逻辑。

3. Url 枚举:把所有接口放在「一个地方」管理​

在 cross-platform-app 中,所有路由和 API 地址都集中在 app/core/constants/app.url.ts 里,由一个 Url 类型封装:

// 示例:app/core/constants/app.url.ts(简化版)
import {Url, Method} from '@rydeen/angular-framework';

export class AppUrl {
static readonly Users = {
List: new Url('/api/users', Method.GET),
Create: new Url('/api/users', Method.POST),
} as const;

static readonly Menu = {
Sync: new Url('/api/menu/sync', Method.POST),
} as const;
}

这样做的好处:

  • 前端所有 API 地址都在一个文件里,一眼看全;
  • Url 同时带着「路径 + 方法」,防止把 POST 当成 GET 调。

4. Service 层:用 request(url, body) 代替手写 HttpClient​

借助 AbstractService,业务 Service 的代码可以非常精简:

import {Injectable} from '@angular/core';
import {AbstractAppService} from '../../shared/abstracts/abstract.app.service';
import {ResultVO} from '@rydeen/angular-framework';
import {AppUrl} from '../../core/constants/app.url';
import {User} from './types/users.types';

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

async create(payload: User): Promise<ResultVO<void>> {
return await this.request<void>(AppUrl.Users.Create, payload);
}
}

相比「直接注入 HttpClient 自己写 URL」:

  • 你不再需要关心 baseUrl、Header、加解密、防重试;
  • 返回类型清晰:永远是 ResultVO<T> / Page<T>,和 Java 一致。

5. AbstractService 内部做了什么?​

下面是一个简化过的调用流程示意:

你只需要调用 request,内部已经帮你:

  • 如果需要,先与服务端进行 ECC 密钥交换,建立共享密钥;
  • 按约定规则生成防重复提交 ID,并在时间窗口内拦截重复请求;
  • 自动附加设备指纹、请求签名等头信息;
  • 在 AbstractAppService 层触发全局 Loading 事件。

6. 与传统 Vue3 调用方式的对比​

维度传统 Vue3 + axios 写法本架构写法(Angular + 框架)
API 地址管理多个 api/*.ts 文件分散定义统一在 AppUrl 中集中定义
HTTP 调用入口各 Service/组件自行注入 axios/HttpClient所有调用走 AbstractService.request
安全性(加密、防重复提交)需要在每个调用点或拦截器中手写封装在 AbstractService 内部
返回结构any 或简单 DTOResultVO<T> / Page<T>,与 Java 服务完全对齐
全局 Loading每个页面自己维护 isLoading由 AbstractAppService + EventManager 统一控制

可以简单理解为:我们把 HttpClient 的“正确用法”固化成框架,你只需要在业务 Service 中调用一个统一入口。

7. 实战建议​

  1. 先画出 URL 表:在后端接口稳定后,先把所有 API 映射成 AppUrl 中的枚举。
  2. Service 一律继承 AbstractAppService:确保所有请求都经过统一链路,而不是在某个角落直接 new HttpClient。
  3. 组件不直接调 HttpClient:所有数据访问一律通过 Service,让「数据获取」与「展示逻辑」分离。

配合前面章节的「模块划分」与「状态管理」,你会发现前端代码的层次感会清晰许多:
页面负责交互,Service 负责业务,HTTP 细节则完全隐藏在框架内部。