API 代理
生成的服务为业务 API 提供类型化方法和 DTO。它们使用 RestService,共享框架认证、租户与语言处理。
应用服务
在 vue/ 中对已启动后端运行:
pnpm abpv proxy add --module app
pnpm abpv proxy refresh生成器读取 /api/abp/api-definition,应用配置提供额外的对象扩展与策略元数据。开发时可用 --insecure 接受不受信任的本地证书。--source、--config-source 支持离线输入。
默认输出到 src/proxy,包含服务、DTO、受支持的验证器、策略常量与 generate-proxy.json。导入实际生成的名称与路径,将产物提交以便审阅 API 变化。
内置模块代理
内置模块服务已由包的 /proxy 入口提供:
import { inject } from '@lsw-abpvue/core';
import { IdentityUserService } from '@lsw-abpvue/identity/proxy';
const users = inject(IdentityUserService);
const page = await users.getList({ maxResultCount: 10, skipCount: 0 });片段在 setup 或提供者工厂中运行,异步回调前先获取服务。只有明确要单独维护时才生成内置代理的本地副本。
验证器与策略
验证器映射反映受支持的 DTO 注解,不包含所有业务规则。需要时组合继承 DTO 的映射。策略常量使用后端名称,认证配置可以补齐 API 元数据缺少的策略名。前端检查不代替服务端授权。
生成服务返回 Promise,接受请求配置。为过时操作传取消信号。手写请求与错误选项见 HTTP。abpv doctor 对照当前后端定义检查文件,proxy refresh 重新生成。
API 修改后重新生成
- 先修改、启动后端,确认 api-definition 中模块、控制器、动词、路由参数与 DTO 属性正确。
- 在 vue/ 使用记录的配置运行
pnpm abpv proxy refresh;首次注册新模块用proxy add --module app。 - 审阅生成差异,编译错误标识变化的消费者,不要修改生成方法来伪装旧契约。
- 调整页面与验证器,执行类型检查并调用真实端点。
- 将审阅后的代理元数据和产物与兼容后端的应用修改一起提交。
代理产物是业务页面可重现的输入。自定义服务辅助函数放在产物外。方法类型描述传输结构,不执行运行时业务验证。
找到正确导入
查看命名空间目录与桶导出,不要猜服务名。移除根命名空间会改变路径,名称冲突可能导致 DTO 重命名。generate 命令重放代理选项来解析名称;自行移动代理文件可能破坏关系。
通过生成方法最后一个可选 RestConfig 传 signal 或具名 API 覆盖。setup 中获取服务,点击处理器保留引用。服务工厂本身不应因注入而发请求。
离线与受保护定义
保存授权后的 api-definition 响应,条件允许时保存同一会话的 application-configuration。按proxy使用 --source、--config-source。缺少认证策略元数据时,一些权限名称无法补齐,doctor 会报告限制,不会把空集合当作匹配。
不要在代理元数据或命令例子中提交访问令牌。生成代码引用框架服务,不引用凭据。模块位于其他宿主时,apiName 必须匹配配置中的具名 apis。
查看生成目录
BookStore API 使用 BookStore.Books 命名空间时,移除 BookStore 根命名空间会产生:
pnpm abpv proxy add --module app --root-namespace BookStore --target src/proxysrc/proxy/
├── books/
│ ├── book.service.ts
│ ├── models.ts
│ ├── book-type.enum.ts
│ ├── validators.ts
│ └── index.ts
├── policy-names.ts
├── index.ts
└── generate-proxy.json使用你实际的根命名空间(例如 Acme.BookStore)、模块与输出目录。不同后端生成不同目录与 DTO 成员。根设置缩短目录,不改变端点 URL。服务仍使用模块 apiName,独立宿主需要配置具名 API。
使用枚举与验证器映射
以下完整消费者使用 create DTO 含 name、type、publishDate、price 的 Book API,生成文件取自仓库运行的 BookStore 测试后端。可选 Authors/Books 方案还需要 authorId,应保留自己 DTO 声明的所有字段。
登录用户应拥有应用服务需要的 Books 查询/新增权限;仓库后端使用 BookStore.Books、BookStore.Books.Create。
创建 src/pages/BookForm.vue,生成该命名空间后,将 ./generated/books 导入改为 ../proxy/books:
<template>
<form @submit.prevent="save">
<AbpFormField v-slot="{ id }" label="Name" :errors="errors('name')" required>
<AbpInput :id="id" v-model="form.controls.name.value" :disabled="saving" />
</AbpFormField>
<AbpFormField v-slot="{ id }" label="Type" :errors="errors('type')" required>
<AbpSelect :id="id" v-model="form.controls.type.value" :options="types" :disabled="saving" />
</AbpFormField>
<AbpFormField v-slot="{ id }" label="Publication date" :errors="errors('publishDate')" required>
<AbpDatePicker :id="id" v-model="form.controls.publishDate.value" :disabled="saving" />
</AbpFormField>
<AbpFormField v-slot="{ id }" label="Price" :errors="errors('price')" required>
<AbpInput :id="id" v-model="form.controls.price.value" type="number" :disabled="saving" />
</AbpFormField>
<p v-for="message in form.unmatchedServerErrors" :key="message" role="alert">{{ message }}</p>
<AbpButton type="submit" :loading="saving">Save</AbpButton>
<output v-if="createdId">Created book: {{ createdId }}</output>
</form>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from 'vue';
import { inject, useLocalization } from '@lsw-abpvue/core';
import {
AbpButton,
AbpDatePicker,
AbpFormField,
AbpInput,
AbpSelect,
useAbpForm,
useServerValidation,
useValidationMessages,
Validators,
} from '@lsw-abpvue/theme-shared';
import {
BookService,
BookType,
bookTypeOptions,
createUpdateBookDtoValidators,
type CreateUpdateBookDto,
} from './generated/books';
const books = inject(BookService);
const localization = useLocalization();
const messages = useValidationMessages();
const form = useAbpForm({
name: { value: '', validators: [...createUpdateBookDtoValidators.name] },
type: {
value: BookType.Adventure as number | null,
validators: [...createUpdateBookDtoValidators.type],
},
publishDate: {
value: null as string | null,
validators: [...createUpdateBookDtoValidators.publishDate],
},
price: {
value: 0 as number | null,
validators: [...createUpdateBookDtoValidators.price, Validators.max(500)],
},
});
useServerValidation(form);
const types = computed(() =>
bookTypeOptions.map(option => ({
value: option.value,
label: localization.t({
key: `BookStore::Enum:BookType.${option.value}`,
defaultValue: option.key,
}),
})),
);
const errors = (key: keyof typeof form.controls) =>
form.controls[key].touched ? messages(form.controls[key].errors) : [];
const saving = ref(false);
const createdId = ref<string>();
const request = new AbortController();
onBeforeUnmount(() => request.abort());
async function save(): Promise<void> {
if (saving.value || !form.validate()) return;
const { name, type, publishDate, price } = form.value;
if (type === null || publishDate === null || price === null) return;
const input: CreateUpdateBookDto = { name, type, publishDate, price, extraProperties: {} };
saving.value = true;
try {
const created = await books.create(input, { signal: request.signal });
if (request.signal.aborted) return;
createdId.value = created.id;
form.reset();
} catch {
/* Keep the input and server validation messages. */
} finally {
saving.value = false;
}
}
</script>向 API 发送枚举数值,标签来自 bookTypeOptions 与本地化枚举 key。映射由元数据生成,不是手动复制。为资源添加枚举文本,否则显示回退枚举名。
添加应用规则前展开各生成验证器数组。示例保留后端必填/范围规则,增加更严格的价格上限 500。重新生成更新共享映射,不覆盖页面自定义规则。继承 DTO 须显式组合声明类型的映射。
日期控件返回日期字符串,此后端以 DateTime 接受出版日期,表示日历日期,不是任意用户时区的瞬间。价格与枚举保持数字,构造类型化 DTO 前收窄可为空的必填控件。日期语义解释预约或事件时间的其他建模方式。
使用 setup 中取得的服务调用生成方法,将请求配置放在最后一个参数。不要 await 后注入。失败保留表单,将服务端验证分配到控件;真实请求还会检查注解之外的规则。
刷新时保留应用代码
修改 DTO 或枚举后运行 proxy refresh,审阅差异。命名空间/方法未变时页面继续导入相同公开索引;类型错误标识需要修改的消费者。不要修改生成 DTO 来掩盖 API 不匹配。例如移除枚举成员还需更新存储值和 UI 选项。
业务辅助函数、字段标签、自定义验证器与页面放在 src/proxy 外。将生成记录与产物一起审阅提交。dry run 或 doctor 可发现漂移,但不能证明 API 正确保存数据。