import {actionCreators} from '../Container/action'; import {BasicConfig, ContainerProps, getRuntimeContext} from '../Container/types'; import {AjaxDataProvider} from './providers/ajax'; import {LocalStorageDataProvider} from './providers/localStorage'; import {providerLoaderInstance} from './loader'; import {InitDataProvider} from './providers/init'; import {clone, get, has, isEqual, isPlainObject} from 'lodash'; import {compileExpressionString, isExpression, parseExpressionString} from '../../util/vm'; import {store} from '../../index'; import {parseExpressString} from '../../parser/index'; providerLoaderInstance.registerProvider('ajax', new AjaxDataProvider(), true); providerLoaderInstance.registerProvider('init', new InitDataProvider(), false); providerLoaderInstance.registerProvider('localStorage', new LocalStorageDataProvider(), false); /** * Provider 对象数据源配置 */ export interface ProviderSourceConfig { /** * provider模式 */ mode: string; /** * Provider配置 */ config?: any; /** * 请求发起所依赖的参数 */ requiredParams?: string[]; /** * 不仅判断参数的key,同样如果每个参数的value转义之后都是true */ strictRequired?: boolean; /** * Provider命名空间 */ namespace?: string; /** * Provider返回值映射[弃用] */ retMapping?: Object; /** * Provider返回值映射 */ responseRewrite?: Object; /** * 返回值检查Expression String */ retCheckPattern?: string; /** * 错误弹出的错误提示 */ retErrMsg?: string; /** * 调试默认 */ debug?: boolean; __previousConfig?: ProviderSourceConfig | null; } /** * Provider 对象全局配置 */ export interface ProviderGlobalOptions { /** * 代理服务配置 */ proxy?: string; } export interface ProviderActions { /** * 同步批量写入数据 */ setDataList: typeof actionCreators.setDataList; /** * 异步加载中 */ asyncLoadDataProgress: typeof actionCreators.asyncLoadDataProgress; /** * 异步加载成功 */ asyncLoadDataSuccess: typeof actionCreators.asyncLoadDataSuccess; /** * 异步加载失败 */ asyncLoadDataFail: typeof actionCreators.asyncLoadDataFail; /** * 同步加载成功 */ syncLoadDataSuccess: typeof actionCreators.syncLoadDataSuccess; /** * 同步加载失败 */ syncLoadDataFail: typeof actionCreators.syncLoadDataFail; } export interface BasicSyncProviderInterface { configCheck(provider: ProviderSourceConfig): boolean; retCheck(ret: Object, provider: ProviderSourceConfig, props: ContainerProps, context: any): boolean; retParse(ret: Object, provider: ProviderSourceConfig, props: ContainerProps, context: any): Object; parse(provider: ProviderSourceConfig, config: ContainerProps, context: any): ProviderSourceConfig; run(provider: ProviderSourceConfig, options?: ProviderGlobalOptions): any; onError(errmsg: string, e?: Error): void; } export interface BasicAsyncProviderInterface { configCheck(provider: ProviderSourceConfig): boolean; retCheck(ret: Object, provider: ProviderSourceConfig, props: ContainerProps, context: any): boolean; retParse(ret: Object, provider: ProviderSourceConfig, props: ContainerProps, context: any): Object; parse(provider: ProviderSourceConfig, config: ContainerProps, context: any): ProviderSourceConfig; run(provider: ProviderSourceConfig, options?: ProviderGlobalOptions): Promise; onError(errmsg: string, e?: Error): void; } /** * DataProvider 是一个数据源控制器 * 它通过为Container组件提供一个单一, 简单的API调用. 来对各种各样的数据获取操作进行封装 * 它支持控制同步的数据和异步的数据操作. 获取到目标数据之后, * DataProvider会触发action来写入数据到redux store * * 流程图可参考 * src/doc/graphic/dataFlow.png * * 同步的操作会触发一个action, 并进行同步写入 * 异步的操作会有3个运行状态: * * 1. before(异步运行前) * 2. progress(异步运行中) * 3.1 success(异步运行成功) * 3.2 fail(异步运行失败) */ export class DataProvider { private getRequestData(providerConfig: ProviderSourceConfig, props: ContainerProps, context: any) { let mode = providerConfig.mode; let providerInfo = providerLoaderInstance.getTargetProvider(mode); if (!providerInfo) { throw new Error('can not find exact data provider of mode:' + mode); } let provider = providerInfo.provider; let previousConfig = providerConfig.__previousConfig; let checkStatus = provider.configCheck(providerConfig); if (!checkStatus) { return false; } let parsedConfig = provider.parse(clone(providerConfig), props, context); let runTime = getRuntimeContext(props, context); let $data = runTime.$data; let isShouldUpdate = $data && $data['$update']; if (isShouldUpdate) { store.dispatch(actionCreators.setData({ name: '$update', value: false }, props.info.model)); } // 如果$data中存在字段没有满足要求,则return false if (providerConfig.requiredParams instanceof Array && $data) { let flag = providerConfig.requiredParams.every(param => { if (providerConfig.strictRequired) { return get($data, param); } return has($data, param); }); if (!flag) { return false; } } // 如果provider数据配置和上次相同, 就必须阻止以后的操作. // 不然就会死循环 // 参考流程图: src/doc/graphic/dataFlow.png if (previousConfig && !isShouldUpdate && isEqual(previousConfig.mode, parsedConfig.mode) && isEqual(previousConfig.config, parsedConfig.config)) { return false; } providerConfig.__previousConfig = parsedConfig; return parsedConfig; } private async updateData(providerConfig: ProviderSourceConfig[], actions: ProviderActions, info: BasicConfig, props: ContainerProps, context: any) { let async: ProviderSourceConfig[] = []; let sync: ProviderSourceConfig[] = []; providerConfig.forEach(provider => { let instance = providerLoaderInstance.getTargetProvider(provider.mode); if (instance.async) { async.push(provider); } else { sync.push(provider); } }); let runTime = getRuntimeContext(props, context); if (async.length > 0) { actions.asyncLoadDataProgress({ model: info.model! }); let isFailed = false; let responseArr = await Promise.all(async.map(async (provider: ProviderSourceConfig) => { if (isFailed) { return false; } let instance = providerLoaderInstance.getTargetProvider(provider.mode); let ret; try { ret = await instance.provider.run(provider, { proxy: context.$global.proxy }); } catch (e) { instance.provider.onError(e.message); actions.syncLoadDataFail({ model: info.model!, error: e.message }); isFailed = true; return false; } let isRetValid = instance.provider.retCheck(ret, provider, props, context); ret = instance.provider.retParse(ret, provider, props, context); let response: any; if (!isRetValid) { let errmsg = provider.retErrMsg; if (typeof errmsg === 'string' && isExpression(errmsg)) { errmsg = parseExpressString(errmsg, { ...runTime, $output: ret }); } if (!errmsg) { errmsg = `model: ${info.model} mode: ${provider.mode} data is not valid`; } instance.provider.onError(errmsg); actions.asyncLoadDataFail({ model: info.model!, error: errmsg }); isFailed = true; return false; } else if (provider.responseRewrite) { if (isPlainObject(provider.responseRewrite)) { response = compileExpressionString(provider.responseRewrite, { ...runTime, $output: ret }); } if (typeof provider.responseRewrite === 'string' && isExpression(provider.responseRewrite)) { response = parseExpressionString(provider.responseRewrite, { ...runTime, $output: ret }); } } else { if (typeof provider.namespace === 'string') { response = { [provider.namespace]: ret }; } else { response = ret; } } if (provider.debug) { console.log('mode: ' + provider.mode + '; context: ', { $output: response, ...runTime }); } return response; })); let output = {}; responseArr.forEach(response => { if (typeof response !== 'boolean') { Object.assign(output, response); } }); actions.asyncLoadDataSuccess({ model: info.model!, data: output }); } else if (sync.length > 0) { let isFailed = false; let responseArr = sync.map((provider: ProviderSourceConfig) => { if (isFailed) { return false; } let instance = providerLoaderInstance.getTargetProvider(provider.mode); let ret; try { ret = instance.provider.run(provider); } catch (e) { console.error('SyncDataProvider exec failed', e); actions.syncLoadDataFail({ model: info.model!, error: e.message }); isFailed = true; return null; } let isRetValid = instance.provider.retCheck(ret, provider, props, context); ret = instance.provider.retParse(ret, provider, props, context); let response; if (!isRetValid) { actions.syncLoadDataFail({ model: info.model!, error: `model: ${info.model} mode: ${provider.mode} data is not valid` }); isFailed = true; return null; } else if (provider.responseRewrite) { if (isPlainObject(provider.responseRewrite)) { response = compileExpressionString(provider.responseRewrite, { ...runTime, $output: ret }); } if (typeof provider.responseRewrite === 'string' && isExpression(provider.responseRewrite)) { response = parseExpressionString(provider.responseRewrite, { ...runTime, $output: ret }); } } else { if (typeof provider.namespace === 'string') { response = { [provider.namespace]: ret }; } else { response = ret; } } return response; }); let output = {}; responseArr.forEach(response => { if (typeof response !== 'boolean') { Object.assign(output, response); } }); actions.syncLoadDataSuccess({ model: info.model!, data: output }); } } public requestForData(providerConfig: ProviderSourceConfig[], actions: ProviderActions, info: BasicConfig, props: ContainerProps, context: any): void { let parsedData: ProviderSourceConfig[] = []; for (let provider of providerConfig) { let ret = this.getRequestData(provider, props, context); if (typeof ret !== 'boolean') { parsedData.push(ret); } } this.updateData(parsedData, actions, info, props, context); } }