支持自定义的endpoint

This commit is contained in:
Pan Qiancheng 2025-04-15 09:28:14 +08:00
parent b5739a4128
commit 26b02e4cca
3 changed files with 88 additions and 42 deletions

5
lib/AppLoader.d.ts vendored
View File

@ -3,8 +3,9 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord, FreeTimer, OperationResult } from "oak-domain/lib/types";
import { DbStore } from "./DbStore";
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
import { IncomingHttpHeaders, IncomingMessage } from 'http';
import { IncomingHttpHeaders } from 'http';
import { Namespace } from 'socket.io';
import Koa from "koa";
import DataSubscriber from './cluster/DataSubscriber';
import Synchronizer from './Synchronizer';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
@ -37,7 +38,7 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
}>;
initialize(): Promise<void>;
getStore(): DbStore<ED, Cxt>;
getEndpoints(prefix: string): [string, "post" | "get" | "put" | "delete", string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<any>][];
getEndpoints(prefix: string): ["rest" | "custom" | undefined, string, "post" | "get" | "put" | "delete", string, (koaCtx: Koa.ParameterizedContext) => Promise<any>][];
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt, singleton?: true): Promise<OperationResult<ED>>;
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, forUpdate?: true, singleton?: true): Promise<Partial<ED[T]["Schema"]>[]>;
protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<OperationResult<ED> | undefined>;

View File

@ -264,7 +264,7 @@ class AppLoader extends types_1.AppLoader {
const endPointRouters = [];
const endPointMap = {};
const transformEndpointItem = (key, item) => {
const { name, method, fn, params: itemParams } = item;
const { name, method, fn, params: itemParams, type } = item;
const k = `${key}-${name}-${method}`;
const makeEndpoint = async () => {
endPointMap[k] = true;
@ -274,19 +274,36 @@ class AppLoader extends types_1.AppLoader {
url += `/:${p}`;
}
}
endPointRouters.push([name, method, url, async (params, headers, req, body) => {
const context = await this.makeContext(undefined, headers);
try {
const result = await fn(context, params, headers, req, body);
await context.commit();
return result;
}
catch (err) {
await context.rollback();
console.error(`endpoint「${key}」方法「${method}」出错`, err);
throw err;
}
}]);
if (type == "custom") {
endPointRouters.push([type, name, method, url, async (koaCtx) => {
const { request } = koaCtx;
const { headers } = request;
try {
await fn(() => this.makeContext(undefined, headers), koaCtx);
}
catch (err) {
console.error(`endpoint「${key}」方法「${method}」出错`, err);
throw err;
}
}]);
}
else {
endPointRouters.push([type, name, method, url, async (koaCtx) => {
const { request, params } = koaCtx;
const { body, headers, files, req } = request;
const context = await this.makeContext(undefined, headers);
try {
const result = await fn(context, params, headers, req, files ? Object.assign({}, body, files) : body);
await context.commit();
return result;
}
catch (err) {
await context.rollback();
console.error(`endpoint「${key}」方法「${method}」出错`, err);
throw err;
}
}]);
}
};
if (endPointMap[k]) {
if (process.env.NODE_ENV === 'development') {

View File

@ -15,6 +15,7 @@ import assert from 'assert';
import { IncomingHttpHeaders, IncomingMessage } from 'http';
import { Namespace } from 'socket.io';
import { analyzeDepedency } from 'oak-domain/lib/compiler/dependencyBuilder';
import Koa from "koa"
import DataSubscriber from './cluster/DataSubscriber';
import { getClusterInfo } from './cluster/env';
@ -114,7 +115,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
async commit() {
const { eventOperationMap, opRecords } = this;
await super.commit();
// 注入在提交后向dataSubscribe发送订阅的事件
if (loaderThis.dataSubscriber) {
Object.keys(eventOperationMap).forEach(
@ -179,7 +180,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
const socketFilePath = join(this.path, 'lib/socket/index');
if (existsSync(socketFilePath)) {
const { registerSocketEntry } = require(socketFilePath);
assert(typeof registerSocketEntry === 'function');
assert(typeof registerSocketEntry === 'function');
}
}
@ -260,7 +261,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
const data = this.requireSth('lib/data/index')!;
// oak-domain中只有i18n
assert(data.i18n);
data.i18n.push(...domainI18nData);
data.i18n.push(...domainI18nData);
const context = this.contextBuilder(this.dbStore);
context.openRootMode();
@ -281,7 +282,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
await context.commit();
console.log(`data in ${entity} omitted, ${rows.length} rows passed`);
continue;
}
}
}
// 再插入所有的行
try {
@ -308,7 +309,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
catch (err) {
await context.rollback();
console.error(`data on ${entity} initilization failed!`);
throw err;
throw err;
}
}
}
@ -321,11 +322,17 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
getEndpoints(prefix: string) {
const endpoints: Record<string, Endpoint<ED, Cxt>> = this.requireSth('lib/endpoints/index');
const endPointRouters: Array<[EndpointItem<ED, Cxt>['name'], EndpointItem<ED, Cxt>['method'], string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<any>]> = [];
const endPointRouters: Array<[
EndpointItem<ED, Cxt>['type'],
EndpointItem<ED, Cxt>['name'],
EndpointItem<ED, Cxt>['method'],
string,
(koaCtx: Koa.ParameterizedContext) => Promise<any>
]> = [];
const endPointMap: Record<string, true> = {};
const transformEndpointItem = (key: string, item: EndpointItem<ED, Cxt>) => {
const { name, method, fn, params: itemParams } = item;
const { name, method, fn, params: itemParams, type } = item;
const k = `${key}-${name}-${method}`;
const makeEndpoint = async () => {
endPointMap[k] = true;
@ -335,22 +342,43 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
url += `/:${p}`;
}
}
endPointRouters.push(
[name, method, url, async (params, headers, req, body) => {
const context = await this.makeContext(undefined, headers);
if (type == "custom") {
endPointRouters.push(
[type, name, method, url, async (koaCtx) => {
const { request, res } = koaCtx
const { headers } = request;
koaCtx.respond = false
try {
await fn(() => this.makeContext(undefined, headers), koaCtx);
res.statusCode = 200
}
catch (err) {
console.error(`endpoint「${key}」方法「${method}」出错`, err);
res.statusCode = 500
throw err;
}
}]
);
} else {
endPointRouters.push(
[type, name, method, url, async (koaCtx) => {
const { request, params } = koaCtx;
const { body, headers, files, req } = request;
const context = await this.makeContext(undefined, headers);
try {
const result = await fn(context, params, headers, req, body);
await context.commit();
return result;
}
catch (err) {
await context.rollback();
console.error(`endpoint「${key}」方法「${method}」出错`, err);
throw err;
}
}]
);
try {
const result = await fn(context, params, headers, req, files ? Object.assign({}, body, files) : body);
await context.commit();
return result;
}
catch (err) {
await context.rollback();
console.error(`endpoint「${key}」方法「${method}」出错`, err);
throw err;
}
}]
);
}
}
if (endPointMap[k]) {
if (process.env.NODE_ENV === 'development') {
@ -401,18 +429,18 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
let result: OperationResult<ED> | undefined;
try {
if (watcher.hasOwnProperty('actionData')) {
const { entity, action, filter, actionData, singleton } = <BBWatcher<ED, keyof ED>>watcher;
const { entity, action, filter, actionData, singleton } = <BBWatcher<ED, keyof ED>>watcher;
const filter2 = typeof filter === 'function' ? await filter() : cloneDeep(filter);
const data = typeof actionData === 'function' ? await (actionData)() : cloneDeep(actionData);
result = await this.operateInWatcher(entity, {
id: await generateNewIdAsync(),
action: action as string,
data,
filter: filter2,
filter: filter2,
}, context, singleton);
}
else {
const { entity, projection, fn, filter, singleton,forUpdate } = <WBWatcher<ED, keyof ED, Cxt>>watcher;
const { entity, projection, fn, filter, singleton, forUpdate } = <WBWatcher<ED, keyof ED, Cxt>>watcher;
const filter2 = typeof filter === 'function' ? await filter() : cloneDeep(filter);
const projection2 = typeof projection === 'function' ? await projection() : cloneDeep(projection);
const rows = await this.selectInWatcher(entity, {
@ -503,7 +531,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
const job = scheduleJob(name, cron, async (date) => {
const start = Date.now();
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}`);
if (timer.hasOwnProperty('entity')) {
try {
const result = await this.execWatcher(timer as Watcher<ED, keyof ED, Cxt>);