Browse Source

sopport abp dynamic api proxy

pull/165/head
cKey 5 years ago
parent
commit
94c4eae7a0
  1. 1
      vueJs/.env.development
  2. 2
      vueJs/src/App.vue
  3. 287
      vueJs/src/api/dynamic-api.ts
  4. 1
      vueJs/src/api/identity-server4.ts
  5. 5
      vueJs/src/api/types.ts
  6. 71
      vueJs/src/mixins/HttpProxyMiXin.ts
  7. 2
      vueJs/src/store/index.ts
  8. 46
      vueJs/src/store/modules/http-proxy.ts
  9. 13
      vueJs/src/views/admin/identityServer/client/index.vue

1
vueJs/.env.development

@ -2,6 +2,7 @@
#VUE_APP_BASE_API = '/dev-api' #VUE_APP_BASE_API = '/dev-api'
VUE_APP_BASE_MOCK_API = '/dev-api' VUE_APP_BASE_MOCK_API = '/dev-api'
REMOTE_SERVICE_BASE_URL = ''
VUE_APP_BASE_API = '/api' VUE_APP_BASE_API = '/api'
#Signalr #Signalr
VUE_APP_SIGNALR_SERVER = '/signalr-hubs' VUE_APP_SIGNALR_SERVER = '/signalr-hubs'

2
vueJs/src/App.vue

@ -7,6 +7,7 @@
<script lang="ts"> <script lang="ts">
import { AbpModule } from '@/store/modules/abp' import { AbpModule } from '@/store/modules/abp'
import { HttpProxyModule } from '@/store/modules/http-proxy'
import { Component, Vue } from 'vue-property-decorator' import { Component, Vue } from 'vue-property-decorator'
import ServiceWorkerUpdatePopup from '@/pwa/components/ServiceWorkerUpdatePopup.vue' import ServiceWorkerUpdatePopup from '@/pwa/components/ServiceWorkerUpdatePopup.vue'
@ -22,6 +23,7 @@ export default class extends Vue {
} }
private async initializeAbpConfiguration() { private async initializeAbpConfiguration() {
await HttpProxyModule.Initialize()
await AbpModule.LoadAbpConfiguration() await AbpModule.LoadAbpConfiguration()
} }
} }

287
vueJs/src/api/dynamic-api.ts

@ -0,0 +1,287 @@
/*
Reference:
*/
import ApiService from './serviceBase'
import { Method } from 'axios'
const sourceUrl = '/api/abp/api-definition'
const serviceUrl = process.env.VUE_APP_BASE_API
export default class DynamicApiService {
/** api
* @param includeTypes
*/
public static get(includeTypes = false) {
const _url = sourceUrl + '?includeTypes=' + includeTypes
return ApiService.Get<ApplicationApiDescriptionModel>(_url, serviceUrl)
}
}
export class UrlBuilder {
public static generateUrlWithParameters(action: ActionApiDescriptionModel, methodArguments: any, apiVersion: ApiVersionInfo) {
let urlBuilder = action.url
urlBuilder = this.replacePathVariables(urlBuilder, action.parameters, methodArguments, apiVersion)
urlBuilder = this.addQueryStringParameters(urlBuilder, action.parameters, methodArguments, apiVersion)
return urlBuilder
}
private static replacePathVariables(
urlBuilder: string,
actionParameters: ParameterApiDescriptionModel[],
methodArguments: any,
apiVersion: ApiVersionInfo) {
const pathParameters = actionParameters.filter(x => x.bindingSourceId === ParameterBindingSources.path)
if (pathParameters.length === 0) {
return urlBuilder
}
if (pathParameters.some(x => x.name === 'apiVersion')) {
urlBuilder = urlBuilder.replace("{apiVersion}", apiVersion.version)
}
pathParameters
.filter(x => x.name !== 'apiVersion')
.forEach(pathParameter => {
const value = HttpActionParameterHelper.findParameterValue(methodArguments, pathParameter)
if (!value) {
if (pathParameter.isOptional) {
urlBuilder = urlBuilder.replace(`{{${pathParameter.name}}}`, '')
} else if (pathParameter.defaultValue) {
urlBuilder = urlBuilder.replace(`{{${pathParameter.name}}}`, String(pathParameter.defaultValue))
} else {
throw new Error(`Missing path parameter value for ${pathParameter.name} (${pathParameter.nameOnMethod})`)
}
} else {
urlBuilder = urlBuilder.replace(`{{${pathParameter.name}}}`, String(value))
}
})
return urlBuilder
}
private static addQueryStringParameters(
urlBuilder: string,
actionParameters: ParameterApiDescriptionModel[],
methodArguments: any,
apiVersion: ApiVersionInfo
) {
const bindSources = [ParameterBindingSources.modelBinding, ParameterBindingSources.query]
const queryStringParameters = actionParameters.filter(x => bindSources.some(b => b === x.bindingSourceId))
let isFirstParam = true
queryStringParameters
.forEach(queryStringParameter => {
const value = HttpActionParameterHelper.findParameterValue(methodArguments, queryStringParameter)
if (!value) {
return
}
urlBuilder = this.addQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.name, value)
isFirstParam = false
})
if (apiVersion.shouldSendInQueryString()) {
urlBuilder = this.addQueryStringParameter(urlBuilder, isFirstParam, 'api-version', apiVersion.version)
}
return urlBuilder
}
private static addQueryStringParameter(
urlBuilder: string,
isFirstParam: boolean,
name: string,
value: any
) {
urlBuilder += isFirstParam ? '?' : '&'
if (Array.isArray(value)) {
let index = 0
value.forEach(val => {
urlBuilder += `${name}[${index++}]=` + encodeURI(val)
})
urlBuilder.substring(0, urlBuilder.length - 1)
} else {
urlBuilder += `${name}=` + encodeURI(value)
}
return urlBuilder
}
}
export class HttpActionParameterHelper {
public static findParameterValue(methodArguments: any, apiParameter: ParameterApiDescriptionModel) {
const methodArgKeys = Object.keys(methodArguments)
const keyIndex = apiParameter.name === apiParameter.nameOnMethod ?
methodArgKeys.findIndex(key => apiParameter.name.toLowerCase() === key.toLowerCase()) :
methodArgKeys.findIndex(key => apiParameter.nameOnMethod.toLowerCase() === key.toLowerCase())
let value = methodArguments[methodArgKeys[keyIndex]]
if (!value) {
return null
}
if (apiParameter.name === apiParameter.nameOnMethod) {
return value
}
const inputKeys = Object.keys(value)
const inputKeyIndex = inputKeys.findIndex(key => key.toLowerCase() === apiParameter.name.toLowerCase())
value = inputKeyIndex < 0 ? null : value[inputKeys[inputKeyIndex]]
return String(value)
}
}
export class ApiVersionInfo {
bindingSource?: string
version!: string
constructor (
version: string,
bindingSource?: string
) {
this.version = version
this.bindingSource = bindingSource
}
public shouldSendInQueryString() {
return ['Path'].some(x => x === this.bindingSource)
}
}
export enum ParameterBindingSources {
modelBinding = 'ModelBinding',
query = 'Query',
body = 'Body',
path = 'Path',
form = 'Form',
header = 'Header',
custom = 'Custom',
services = 'Services'
}
export class ControllerInterfaceApiDescriptionModel {
type!: string
}
export class ReturnValueApiDescriptionModel {
type!: string
typeSimple!: string
}
export class MethodParameterApiDescriptionModel {
name!: string
typeAsString!: string
type!: string
typeSimple!: string
isOptional!: boolean
defaultValue?: any
}
export class ParameterApiDescriptionModel {
nameOnMethod!: string
name!: string
type!: string
typeSimple!: string
isOptional!: boolean
defaultValue?: any
constraintTypes = new Array<string>()
bindingSourceId?: string
descriptorName?: string
}
export class PropertyApiDescriptionModel {
name!: string
type!: string
typeSimple!: string
isRequired!: boolean
}
export class ActionApiDescriptionModel {
uniqueName!: string
name!: string
httpMethod!: Method
url!: string
supportedVersions = new Array<string>()
parametersOnMethod = new Array<MethodParameterApiDescriptionModel>()
parameters = new Array<ParameterApiDescriptionModel>()
returnValue = new ReturnValueApiDescriptionModel()
}
export class ControllerApiDescriptionModel {
controllerName!: string
type!: string
interfaces = new Array<ControllerInterfaceApiDescriptionModel>()
actions: {[key: string]: ActionApiDescriptionModel} = {}
public static getAction(
actionName: string,
actions: {[key: string]: ActionApiDescriptionModel}) {
const actionKeys = Object.keys(actions)
const index = actionKeys.findIndex(key => {
const a = actions[key]
if (a.name.toLowerCase() === actionName.toLowerCase()) {
return a
}
})
if (index < 0) {
throw new Error(`没有找到名为 ${actionName} 的方法定义!`)
}
return actions[actionKeys[index]]
}
}
export class ModuleApiDescriptionModel {
rootPath = 'app'
remoteServiceName = 'Default'
controllers: {[key: string]: ControllerApiDescriptionModel} = {}
public static getController(
controllerName: string,
controllers: {[key: string]: ControllerApiDescriptionModel}) {
const controllerKeys = Object.keys(controllers)
const index = controllerKeys.findIndex(key => {
const c = controllers[key]
if (c.controllerName.toLowerCase() === controllerName.toLowerCase()) {
return c
}
})
if (index < 0) {
throw new Error(`没有找到名为 ${controllerName} 的接口定义!`)
}
return controllers[controllerKeys[index]]
}
}
export class TypeApiDescriptionModel {
baseType!: string
isEnum!: boolean
enumNames = new Array<string>()
enumValues = new Array<any>()
genericArguments = new Array<string>()
properties = new Array<PropertyApiDescriptionModel>()
}
export class ApplicationApiDescriptionModel {
modules: {[key: string]: ModuleApiDescriptionModel} = {}
types: {[key: string]: TypeApiDescriptionModel} = {}
public static getAction(
remoteService: string,
controllerName: string,
actionName: string,
modules: {[key: string]: ModuleApiDescriptionModel}) {
const module = this.getModule(remoteService, modules)
const controller = ModuleApiDescriptionModel.getController(controllerName, module.controllers)
return ControllerApiDescriptionModel.getAction(actionName, controller.actions)
}
private static getModule(
remoteService: string,
modules: {[key: string]: ModuleApiDescriptionModel}) {
const moduleKeys = Object.keys(modules)
const index = moduleKeys.findIndex(key => {
const m = modules[key]
if (m.remoteServiceName.toLowerCase() === remoteService.toLowerCase()) {
return m
}
})
if (index < 0) {
throw new Error(`没有找到名为 ${remoteService} 的服务定义!`)
}
return modules[moduleKeys[index]]
}
}

1
vueJs/src/api/identity-server4.ts

@ -1,5 +1,4 @@
import ApiService from './serviceBase' import ApiService from './serviceBase'
import { getOrDefault } from '@/utils/localStorage'
const openIdConfigurationUrl = '/.well-known/openid-configuration' const openIdConfigurationUrl = '/.well-known/openid-configuration'

5
vueJs/src/api/types.ts

@ -286,3 +286,8 @@ export class Claim implements IClaim {
this.value = value this.value = value
} }
} }
export class KeyValue<TKey, TValue> {
key!: TKey
value?: TValue
}

71
vueJs/src/mixins/HttpProxyMiXin.ts

@ -0,0 +1,71 @@
import ApiService from '@/api/serviceBase'
import { PagedResultDto } from '@/api/types'
import { Component, Vue } from 'vue-property-decorator'
import { HttpProxyModule } from '@/store/modules/http-proxy'
import {
ActionApiDescriptionModel,
ApiVersionInfo,
ParameterBindingSources,
UrlBuilder,
ApplicationApiDescriptionModel
} from '@/api/dynamic-api'
/**
* Http代理组件
*/
@Component({
name: 'HttpProxyMiXin'
})
export default class HttpProxyMiXin extends Vue {
protected pagedRequest<TResult>(options: {
service: string,
controller: string,
action: string,
data?: any,
params?: any
}) {
return this.request<PagedResultDto<TResult>>(options)
}
protected request<TResult>(options: {
service: string,
controller: string,
action: string,
data?: any
}) {
const action = ApplicationApiDescriptionModel
.getAction(
options.service, options.controller, options.action,
HttpProxyModule.applicationApiDescriptionModel.modules)
const apiVersion = this.getApiVersionInfo(action)
let url = process.env.REMOTE_SERVICE_BASE_URL || ''
url = this.ensureEndsWith(url, '/') + UrlBuilder.generateUrlWithParameters(action, options.data, apiVersion)
return ApiService.HttpRequest<TResult>({
url: url,
method: action?.httpMethod,
data: options.data
})
}
private getApiVersionInfo(action: ActionApiDescriptionModel) {
const apiVersion = this.findBestApiVersion(action)
const versionParam =
action.parameters.find(p => p.name === 'apiVersion' || p.bindingSourceId === ParameterBindingSources.path) ??
action.parameters.find(p => p.name === 'api-version' || p.bindingSourceId === ParameterBindingSources.query)
return new ApiVersionInfo(apiVersion, versionParam?.bindingSourceId)
}
private findBestApiVersion(action: ActionApiDescriptionModel) {
if (action.supportedVersions.length === 0) {
return '1.0'
}
return action.supportedVersions[action.supportedVersions.length - 1]
}
private ensureEndsWith(str: string, c: string) {
if (str.lastIndexOf(c) === str.length - c.length) {
return str
}
return str + c
}
}

2
vueJs/src/store/index.ts

@ -8,6 +8,7 @@ import { IPermissionState } from './modules/permission'
import { ISettingsState } from './modules/settings' import { ISettingsState } from './modules/settings'
import { IRoleState } from './modules/role' import { IRoleState } from './modules/role'
import { IAbpState } from './modules/abp' import { IAbpState } from './modules/abp'
import { IHttpProxy } from './modules/http-proxy'
Vue.use(Vuex) Vue.use(Vuex)
@ -20,6 +21,7 @@ export interface IRootState {
permission: IPermissionState permission: IPermissionState
settings: ISettingsState settings: ISettingsState
role: IRoleState role: IRoleState
httpProxy: IHttpProxy
} }
// Declare empty store first, dynamically register all modules later. // Declare empty store first, dynamically register all modules later.

46
vueJs/src/store/modules/http-proxy.ts

@ -0,0 +1,46 @@
import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from '@/store'
import { getItem, setItem } from '@/utils/localStorage'
import DynamicApiService, { ApplicationApiDescriptionModel } from '@/api/dynamic-api'
export interface IHttpProxy {
applicationApiDescriptionModel: ApplicationApiDescriptionModel
}
const dynamicApiKey = 'abp-api-definition'
@Module({ dynamic: true, store, name: 'http-proxy' })
class HttpProxy extends VuexModule implements IHttpProxy {
public applicationApiDescriptionModel = new ApplicationApiDescriptionModel()
@Mutation
private SET_API_DESCRIPTOR(apiDescriptor: ApplicationApiDescriptionModel) {
this.applicationApiDescriptionModel = apiDescriptor
}
@Action({ rawError: true })
public async Initialize() {
const dynamicApiJson = getItem(dynamicApiKey)
if (dynamicApiJson) {
this.SET_API_DESCRIPTOR(JSON.parse(dynamicApiJson))
return
}
const dynamicApi = await DynamicApiService.get()
this.SET_API_DESCRIPTOR(dynamicApi)
setItem(dynamicApiKey, JSON.stringify(dynamicApi))
}
@Action({ rawError: true })
public findAction(service: {
name: string,
controller: string,
action: string
}) {
return ApplicationApiDescriptionModel
.getAction(
service.name, service.controller, service.action,
this.applicationApiDescriptionModel.modules)
}
}
export const HttpProxyModule = getModule(HttpProxy)

13
vueJs/src/views/admin/identityServer/client/index.vue

@ -263,6 +263,7 @@ import { abpPagerFormat } from '@/utils/index'
import { checkPermission } from '@/utils/permission' import { checkPermission } from '@/utils/permission'
import DataListMiXin from '@/mixins/DataListMiXin' import DataListMiXin from '@/mixins/DataListMiXin'
import HttpProxyMiXin from '@/mixins/HttpProxyMiXin'
import Component, { mixins } from 'vue-class-component' import Component, { mixins } from 'vue-class-component'
import Pagination from '@/components/Pagination/index.vue' import Pagination from '@/components/Pagination/index.vue'
import ClientCloneForm from './components/ClientCloneForm.vue' import ClientCloneForm from './components/ClientCloneForm.vue'
@ -297,7 +298,7 @@ import ClientService, { Client, ClientGetByPaged } from '@/api/clients'
} }
} }
}) })
export default class extends mixins(DataListMiXin) { export default class extends mixins(DataListMiXin, HttpProxyMiXin) {
private editClient = new Client() private editClient = new Client()
private editClientTitle = '' private editClientTitle = ''
@ -319,7 +320,15 @@ export default class extends mixins(DataListMiXin) {
} }
protected getPagedList(filter: any) { protected getPagedList(filter: any) {
return ClientService.getList(filter) return this.pagedRequest<Client>({
service: 'IdentityServer',
controller: 'Client',
action: 'GetListAsync',
data: {
input: filter
}
})
// return ClientService.getList(filter)
} }
private handleGetOpenIdConfiguration() { private handleGetOpenIdConfiguration() {

Loading…
Cancel
Save