2020-10-24 15:34:43 +08:00
|
|
|
|
import _ from 'lodash';
|
|
|
|
|
import { pathToRegexp } from 'path-to-regexp';
|
2020-11-23 16:49:46 +08:00
|
|
|
|
import qs from 'qs';
|
2020-10-24 15:34:43 +08:00
|
|
|
|
import { ResourceType } from './resource';
|
|
|
|
|
|
|
|
|
|
export interface ParseRequest {
|
|
|
|
|
path: string;
|
|
|
|
|
method: string;
|
|
|
|
|
// 资源类型
|
|
|
|
|
type?: ResourceType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ParseOptions {
|
|
|
|
|
prefix?: string;
|
|
|
|
|
accessors?: {
|
|
|
|
|
list?: string;
|
|
|
|
|
create?: string;
|
|
|
|
|
get?: string;
|
|
|
|
|
update?: string;
|
|
|
|
|
delete?: string;
|
|
|
|
|
set?: string;
|
|
|
|
|
add?: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 15:23:39 +08:00
|
|
|
|
export interface ParsedParams {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
actionName?: string;
|
|
|
|
|
resourceName?: string;
|
2021-12-04 16:28:52 +08:00
|
|
|
|
resourceIndex?: string;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
associatedName?: string;
|
2021-12-04 16:28:52 +08:00
|
|
|
|
associatedIndex?: string;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
// table: string;
|
|
|
|
|
// tableKey?: string | number;
|
|
|
|
|
// relatedTable?: string;
|
|
|
|
|
// relatedKey?: string | number;
|
|
|
|
|
// action?: ActionName,
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 15:23:39 +08:00
|
|
|
|
export function getNameByParams(params: ParsedParams): string {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
const { resourceName, associatedName } = params;
|
|
|
|
|
return associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 15:23:39 +08:00
|
|
|
|
export function parseRequest(request: ParseRequest, options: ParseOptions = {}): ParsedParams | false {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
const accessors = {
|
|
|
|
|
// 常规 actions
|
|
|
|
|
list: 'list',
|
|
|
|
|
create: 'create',
|
|
|
|
|
get: 'get',
|
|
|
|
|
update: 'update',
|
|
|
|
|
delete: 'destroy',
|
|
|
|
|
// associate 操作
|
|
|
|
|
add: 'add',
|
|
|
|
|
set: 'set',
|
|
|
|
|
remove: 'remove',
|
|
|
|
|
...(options.accessors || {}),
|
|
|
|
|
};
|
|
|
|
|
const keys = [];
|
|
|
|
|
const regexp = pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}', keys);
|
|
|
|
|
const matches = regexp.exec(request.path);
|
|
|
|
|
if (matches) {
|
|
|
|
|
const params = {};
|
|
|
|
|
keys.forEach((obj, index) => {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
if (matches[index + 1] === undefined) {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-03-28 13:34:51 +08:00
|
|
|
|
params[obj.name] = matches[index + 1];
|
2020-10-24 15:34:43 +08:00
|
|
|
|
});
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
const defaults = {
|
|
|
|
|
single: {
|
|
|
|
|
'/:resourceName': {
|
|
|
|
|
get: accessors.list,
|
|
|
|
|
post: accessors.create,
|
2021-12-06 21:23:34 +08:00
|
|
|
|
delete: accessors.delete,
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:resourceName/:resourceIndex': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
put: accessors.update,
|
|
|
|
|
patch: accessors.update,
|
|
|
|
|
delete: accessors.delete,
|
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.list,
|
|
|
|
|
post: accessors.create,
|
2020-12-08 21:20:30 +08:00
|
|
|
|
delete: accessors.delete,
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName/:resourceIndex': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
post: accessors.create,
|
|
|
|
|
put: accessors.update,
|
|
|
|
|
patch: accessors.update,
|
|
|
|
|
delete: accessors.delete,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
hasOne: {
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
post: accessors.update,
|
|
|
|
|
put: accessors.update,
|
|
|
|
|
patch: accessors.update,
|
|
|
|
|
delete: accessors.delete,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
hasMany: {
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.list,
|
|
|
|
|
post: accessors.create,
|
2020-12-08 21:20:30 +08:00
|
|
|
|
delete: accessors.delete,
|
2020-10-24 15:34:43 +08:00
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName/:resourceIndex': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
post: accessors.create,
|
|
|
|
|
put: accessors.update,
|
|
|
|
|
patch: accessors.update,
|
|
|
|
|
delete: accessors.delete,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
belongsTo: {
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
delete: accessors.remove,
|
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName/:resourceIndex': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
post: accessors.set,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
belongsToMany: {
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.list,
|
|
|
|
|
post: accessors.set,
|
|
|
|
|
},
|
2021-12-04 16:28:52 +08:00
|
|
|
|
'/:associatedName/:associatedIndex/:resourceName/:resourceIndex': {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
get: accessors.get,
|
|
|
|
|
post: accessors.add,
|
|
|
|
|
put: accessors.update, // Many to Many 的 update 是针对 through
|
|
|
|
|
patch: accessors.update, // Many to Many 的 update 是针对 through
|
|
|
|
|
delete: accessors.remove,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2020-11-11 15:23:39 +08:00
|
|
|
|
const params: ParsedParams = {};
|
2020-10-24 15:34:43 +08:00
|
|
|
|
|
2021-12-03 07:31:22 +08:00
|
|
|
|
let prefix = (options.prefix || '').trim().replace(/\/$/, '');
|
2020-10-24 15:34:43 +08:00
|
|
|
|
|
|
|
|
|
if (prefix && !prefix.startsWith('/')) {
|
|
|
|
|
prefix = `/${prefix}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { type = 'single' } = request;
|
|
|
|
|
|
|
|
|
|
for (const path in defaults[type]) {
|
|
|
|
|
const keys = [];
|
|
|
|
|
const regexp = pathToRegexp(`${prefix}${path}`, keys, {});
|
|
|
|
|
const matches = regexp.exec(request.path);
|
|
|
|
|
if (!matches) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
keys.forEach((obj, index) => {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
if (matches[index + 1] === undefined) {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-03-28 13:34:51 +08:00
|
|
|
|
params[obj.name] = matches[index + 1];
|
2020-10-24 15:34:43 +08:00
|
|
|
|
});
|
|
|
|
|
params.actionName = _.get(defaults, [type, path, request.method.toLowerCase()]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Object.keys(params).length === 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (params.resourceName) {
|
|
|
|
|
const [resourceName, actionName] = params.resourceName.split(':');
|
|
|
|
|
if (actionName) {
|
|
|
|
|
params.resourceName = resourceName;
|
|
|
|
|
params.actionName = actionName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function requireModule(module: any) {
|
|
|
|
|
if (typeof module === 'string') {
|
|
|
|
|
module = require(module);
|
|
|
|
|
}
|
|
|
|
|
if (typeof module !== 'object') {
|
|
|
|
|
return module;
|
|
|
|
|
}
|
|
|
|
|
return module.__esModule ? module.default : module;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 16:49:46 +08:00
|
|
|
|
export function parseQuery(input: string): any {
|
|
|
|
|
// 自带 query 处理的不太给力,需要用 qs 转一下
|
|
|
|
|
const query = qs.parse(input, {
|
|
|
|
|
// 原始 query string 中如果一个键连等号“=”都没有可以被认为是 null 类型
|
2020-12-28 21:08:13 +08:00
|
|
|
|
strictNullHandling: true,
|
|
|
|
|
// 逗号分隔转换为数组
|
2022-02-25 23:41:33 +08:00
|
|
|
|
// comma: true,
|
2020-11-23 16:49:46 +08:00
|
|
|
|
});
|
|
|
|
|
// filter 支持 json string
|
|
|
|
|
if (typeof query.filter === 'string') {
|
|
|
|
|
query.filter = JSON.parse(query.filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 15:23:39 +08:00
|
|
|
|
export function parseFields(fields: any) {
|
2020-10-24 15:34:43 +08:00
|
|
|
|
if (!fields) {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
return {};
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (typeof fields === 'string') {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields = fields.split(',').map((field) => field.trim());
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(fields)) {
|
2020-11-23 16:49:46 +08:00
|
|
|
|
const onlyFields = [];
|
|
|
|
|
const output: any = {};
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.forEach((item) => {
|
2020-11-23 16:49:46 +08:00
|
|
|
|
if (typeof item === 'string') {
|
|
|
|
|
onlyFields.push(item);
|
|
|
|
|
} else if (typeof item === 'object') {
|
|
|
|
|
if (item.only) {
|
|
|
|
|
onlyFields.push(...item.only.toString().split(','));
|
|
|
|
|
}
|
|
|
|
|
Object.assign(output, parseFields(item));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (onlyFields.length) {
|
|
|
|
|
output.only = onlyFields;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
2020-11-23 16:49:46 +08:00
|
|
|
|
return output;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (fields.only && typeof fields.only === 'string') {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.only = fields.only.split(',').map((field) => field.trim());
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (fields.except && typeof fields.except === 'string') {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.except = fields.except.split(',').map((field) => field.trim());
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (fields.appends && typeof fields.appends === 'string') {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.appends = fields.appends.split(',').map((field) => field.trim());
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
return fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mergeFields(defaults: any, inputs: any) {
|
|
|
|
|
let fields: any = {};
|
2020-11-11 15:23:39 +08:00
|
|
|
|
defaults = parseFields(defaults);
|
|
|
|
|
inputs = parseFields(inputs);
|
2020-10-24 15:34:43 +08:00
|
|
|
|
if (inputs.only) {
|
|
|
|
|
// 前端提供 only,后端提供 only
|
|
|
|
|
if (defaults.only) {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.only = defaults.only.filter((field) => inputs.only.includes(field));
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
// 前端提供 only,后端提供 except,输出 only 排除 except
|
|
|
|
|
else if (defaults.except) {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.only = inputs.only.filter((field) => !defaults.except.includes(field));
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
// 前端提供 only,后端没有提供 only 或 except
|
|
|
|
|
else {
|
|
|
|
|
fields.only = inputs.only;
|
|
|
|
|
}
|
|
|
|
|
} else if (inputs.except) {
|
|
|
|
|
// 前端提供 except,后端提供 only,只输出 only 里排除 except 的字段
|
|
|
|
|
if (defaults.only) {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields.only = defaults.only.filter((field) => !inputs.except.includes(field));
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
// 前端提供 except,后端提供 except 或不提供,合并 except
|
|
|
|
|
else {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
fields.except = _.uniq([...inputs.except, ...(defaults.except || [])]);
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 前端没提供 only 或 except
|
|
|
|
|
else {
|
|
|
|
|
fields = defaults;
|
|
|
|
|
}
|
|
|
|
|
// 如果前端提供了 appends
|
|
|
|
|
if (!_.isEmpty(inputs.appends)) {
|
2021-03-28 13:34:51 +08:00
|
|
|
|
fields.appends = _.uniq([...inputs.appends, ...(defaults.appends || [])]);
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
if (!fields.appends) {
|
|
|
|
|
fields.appends = [];
|
|
|
|
|
}
|
|
|
|
|
return fields;
|
|
|
|
|
}
|