Browse Source

Merge pull request #523 from colinin/enhance-saas

Enhance saas
pull/533/head 5.1.3
yx lin 4 years ago
committed by GitHub
parent
commit
6ed8cb8cb8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      apps/vue/src/api/saas/editions.ts
  2. 23
      apps/vue/src/api/saas/model/editionsModel.ts
  3. 13
      apps/vue/src/api/saas/model/tenantModel.ts
  4. 16
      apps/vue/src/api/saas/tenant.ts
  5. 70
      apps/vue/src/views/saas/editions/components/EditionModal.vue
  6. 108
      apps/vue/src/views/saas/editions/components/EditionTable.vue
  7. 44
      apps/vue/src/views/saas/editions/datas/ModalData.ts
  8. 22
      apps/vue/src/views/saas/editions/datas/TableData.ts
  9. 14
      apps/vue/src/views/saas/editions/hooks/useFeatureModal.ts
  10. 13
      apps/vue/src/views/saas/editions/index.vue
  11. 6
      apps/vue/src/views/saas/tenant/components/TenantConnectionModal.vue
  12. 6
      apps/vue/src/views/saas/tenant/components/TenantModal.vue
  13. 12
      apps/vue/src/views/saas/tenant/components/TenantTable.vue
  14. 58
      apps/vue/src/views/saas/tenant/datas/ModalData.ts
  15. 9
      apps/vue/src/views/saas/tenant/datas/TableData.ts
  16. 2
      apps/vue/src/views/saas/tenant/hooks/useTenantTable.ts
  17. 8
      aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs
  18. 4
      aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Editions/Dto/EditionDto.cs
  19. 6
      aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Tenants/Dto/TenantDto.cs
  20. 12
      aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs
  21. 1
      aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json
  22. 1
      aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json
  23. 1
      aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN.Abp.TenantManagement.Application.csproj
  24. 4
      aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi/LINGYUN.Abp.TenantManagement.HttpApi.csproj
  25. 12
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.Configure.cs
  26. 1
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs
  27. 4
      gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.Development.json
  28. 34
      gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.backendadmin.json

50
apps/vue/src/api/saas/editions.ts

@ -0,0 +1,50 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
Edition,
EditionCreate,
EditionUpdate,
EditionGetListInput,
} from './model/editionsModel';
import { format } from '/@/utils/strings';
import { PagedResultDto } from '../model/baseModel';
enum Api {
Create = '/api/saas/editions',
DeleteById = '/api/saas/editions/{id}',
GetById = '/api/saas/editions/{id}',
GetList = '/api/saas/editions',
Update = '/api/saas/editions/{id}',
}
export const getById = (id: string) => {
return defAbpHttp.get<Edition>({
url: format(Api.GetById, { id: id }),
});
};
export const getList = (input: EditionGetListInput) => {
return defAbpHttp.get<PagedResultDto<Edition>>({
url: Api.GetList,
params: input,
});
};
export const create = (input: EditionCreate) => {
return defAbpHttp.post<Edition>({
url: Api.Create,
data: input,
});
};
export const deleteById = (id: string) => {
return defAbpHttp.delete<void>({
url: format(Api.GetById, { id: id }),
});
};
export const update = (id: string, input: EditionUpdate) => {
return defAbpHttp.put<Edition>({
url: format(Api.Update, { id: id }),
data: input,
});
};

23
apps/vue/src/api/saas/model/editionsModel.ts

@ -0,0 +1,23 @@
import {
IHasConcurrencyStamp,
AuditedEntityDto,
PagedAndSortedResultRequestDto,
} from '../../model/baseModel';
export interface Edition extends AuditedEntityDto {
id: string;
displayName: string;
}
interface EditionCreateOrUpdate {
displayName: string;
}
export type EditionCreate = EditionCreateOrUpdate;
export interface EditionUpdate extends EditionCreateOrUpdate, IHasConcurrencyStamp {
}
export interface EditionGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}

13
apps/vue/src/api/saas/model/tenantModel.ts

@ -9,6 +9,11 @@ import {
export interface Tenant extends AuditedEntityDto { export interface Tenant extends AuditedEntityDto {
id: string; id: string;
name: string; name: string;
editionId?: string;
editionName?: string;
isActive: boolean;
enableTime?: Date;
disableTime?: Date;
} }
export interface TenantConnectionString { export interface TenantConnectionString {
@ -20,10 +25,18 @@ export interface CreateTenant {
name: string; name: string;
adminEmailAddress: string; adminEmailAddress: string;
adminPassword: string; adminPassword: string;
editionId?: string;
isActive: boolean;
enableTime?: Date;
disableTime?: Date;
} }
export interface UpdateTenant { export interface UpdateTenant {
name: string; name: string;
editionId?: string;
isActive: boolean;
enableTime?: Date;
disableTime?: Date;
} }
export interface GetTenantPagedRequest extends PagedAndSortedResultRequestDto { export interface GetTenantPagedRequest extends PagedAndSortedResultRequestDto {

16
apps/vue/src/api/saas/tenant.ts

@ -12,14 +12,14 @@ import { format } from '/@/utils/strings';
/** 与 multi-tenancy中不同,此为管理tenant api */ /** 与 multi-tenancy中不同,此为管理tenant api */
enum Api { enum Api {
Create = '/api/tenant-management/tenants', Create = '/api/saas/tenants',
DeleteById = '/api/tenant-management/tenants/{id}', DeleteById = '/api/saas/tenants/{id}',
GetById = '/api/tenant-management/tenants/{id}', GetById = '/api/saas/tenants/{id}',
GetList = '/api/tenant-management/tenants', GetList = '/api/saas/tenants',
Update = '/api/tenant-management/tenants/{id}', Update = '/api/saas/tenants/{id}',
GetConnectionStrings = '/api/tenant-management/tenants/{id}/connection-string', GetConnectionStrings = '/api/saas/tenants/{id}/connection-string',
SetConnectionString = '/api/tenant-management/tenants/{id}/connection-string', SetConnectionString = '/api/saas/tenants/{id}/connection-string',
DeleteConnectionString = '/api/tenant-management/tenants/{id}/connection-string/{name}', DeleteConnectionString = '/api/saas/tenants/{id}/connection-string/{name}',
} }
export const getById = (id: string) => { export const getById = (id: string) => {

70
apps/vue/src/views/saas/editions/components/EditionModal.vue

@ -0,0 +1,70 @@
<template>
<BasicModal
v-bind="$attrs"
:title="modalTitle"
:loading="loading"
:showOkBtn="!loading"
:showCancelBtn="!loading"
:maskClosable="!loading"
:closable="!loading"
:width="500"
:height="300"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, unref, nextTick } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicForm, useForm } from '/@/components/Form';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getModalFormSchemas } from '../datas//ModalData';
import { getById, create, update } from '/@/api/saas/editions';
const emits = defineEmits(['change', 'register']);
const { L } = useLocalization('AbpSaas');
const loading = ref(false);
const editionIdRef = ref('');
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((data) => {
editionIdRef.value = data.id;
fetchEdition();
});
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
schemas: getModalFormSchemas(),
showActionButtonGroup: false,
});
const modalTitle = computed(() => {
return unref(editionIdRef) ? L('Edit') : L('NewEdition');
});
function fetchEdition() {
const editionId = unref(editionIdRef);
if (!editionId) {
nextTick(() => {
resetFields();
});
return;
}
getById(editionId).then((edition) => {
nextTick(() => {
setFieldsValue(edition);
});
});
}
function handleSubmit() {
validate().then((input) => {
changeOkLoading(true);
const api = input.id ? update(input.id, input) : create(input);
api.then((edition) => {
emits('change', edition);
closeModal();
}).finally(() => {
changeOkLoading(false);
});
});
}
</script>

108
apps/vue/src/views/saas/editions/components/EditionTable.vue

@ -0,0 +1,108 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button
v-if="hasPermission('AbpSaas.Editions.Create')"
type="primary"
@click="handleAddNew"
>{{ L('NewEdition') }}</a-button
>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
auth: 'AbpSaas.Editions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'AbpSaas.Editions.Delete',
color: 'error',
label: L('Delete'),
icon: 'ant-design:delete-outlined',
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
auth: 'AbpSaas.Editions.ManageFeatures',
label: L('ManageFeatures'),
onClick: handleManageFeature.bind(null, record),
},
]"
/>
</template>
</BasicTable>
<EditionModal @register="registerModal" @change="reload" />
<FeatureModal @register="registerFeatureModal" />
</div>
</template>
<script lang="ts" setup>
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { usePermission } from '/@/hooks/web/usePermission';
import { useMessage } from '/@/hooks/web/useMessage';
import { useModal } from '/@/components/Modal';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { useFeatureModal } from '../hooks/useFeatureModal';
import { FeatureModal } from '../../../feature';
import { deleteById, getList } from '../../../../api/saas/editions';
import { getDataColumns } from '../datas/TableData';
import { getSearchFormSchemas } from '../datas//ModalData';
import { formatPagedRequest } from '/@/utils/http/abp/helper';
import EditionModal from './EditionModal.vue';
const { L } = useLocalization('AbpSaas', 'AbpFeatureManagement');
const { createConfirm } = useMessage();
const { hasPermission } = usePermission();
const [registerModal, { openModal }] = useModal();
const { registerModal: registerFeatureModal, handleManageFeature } = useFeatureModal();
const [registerTable, { reload }] = useTable({
rowKey: 'id',
title: L('Editions'),
columns: getDataColumns(),
api: getList,
beforeFetch: formatPagedRequest,
pagination: true,
striped: false,
useSearchForm: true,
showTableSetting: true,
bordered: true,
showIndexColumn: false,
canResize: true,
immediate: true,
canColDrag: true,
formConfig: getSearchFormSchemas(),
actionColumn: {
width: 200,
title: L('Actions'),
dataIndex: 'action',
slots: { customRender: 'action' },
},
});
function handleAddNew() {
openModal(true, {});
}
function handleEdit(record) {
openModal(true, record);
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeletedMessageWithFormat', record.displayName),
okCancel: true,
onOk: () => {
deleteById(record.id).then(() => {
reload();
});
},
});
}
</script>

44
apps/vue/src/views/saas/editions/datas/ModalData.ts

@ -0,0 +1,44 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormProps, FormSchema } from '/@/components/Form';
const { L } = useLocalization('AbpSaas');
export function getSearchFormSchemas(): Partial<FormProps> {
return {
labelWidth: 120,
schemas: [
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 24 },
},
],
};
}
export function getModalFormSchemas(): FormSchema[] {
return [
{
field: 'id',
component: 'Input',
label: 'id',
show: false,
dynamicDisabled: true,
},
{
field: 'concurrencyStamp',
component: 'Input',
label: 'concurrencyStamp',
show: false,
dynamicDisabled: true,
},
{
field: 'displayName',
component: 'Input',
label: L('DisplayName:EditionName'),
colProps: { span: 24 },
required: true,
},
];
}

22
apps/vue/src/views/saas/editions/datas/TableData.ts

@ -0,0 +1,22 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
const { L } = useLocalization('AbpSaas');
export function getDataColumns(): BasicColumn[] {
return [
{
title: 'id',
dataIndex: 'id',
width: 1,
ifShow: false,
},
{
title: L('DisplayName:EditionName'),
dataIndex: 'displayName',
align: 'left',
width: 'auto',
sorter: true,
},
];
}

14
apps/vue/src/views/saas/editions/hooks/useFeatureModal.ts

@ -0,0 +1,14 @@
import { useModal } from '/@/components/Modal';
export function useFeatureModal() {
const [registerModal, { openModal }] = useModal();
function handleManageFeature(record) {
openModal(true, { providerName: 'E', providerKey: record.id });
}
return {
registerModal,
handleManageFeature,
};
}

13
apps/vue/src/views/saas/editions/index.vue

@ -0,0 +1,13 @@
<template>
<EditionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import EditionTable from './components/EditionTable.vue';
export default defineComponent({
name: 'Editions',
components: { EditionTable },
setup() {},
});
</script>

6
apps/vue/src/views/saas/tenant/components/TenantConnectionModal.vue

@ -10,7 +10,7 @@
<BasicTable @register="registerTable"> <BasicTable @register="registerTable">
<template #toolbar> <template #toolbar>
<a-button <a-button
v-if="hasPermission('AbpTenantManagement.Tenants.Create')" v-if="hasPermission('AbpSaas.Tenants.ManageConnectionStrings')"
type="primary" type="primary"
@click="handleAddNew" @click="handleAddNew"
>{{ L('ConnectionStrings:AddNew') }}</a-button >{{ L('ConnectionStrings:AddNew') }}</a-button
@ -20,7 +20,7 @@
<TableAction <TableAction
:actions="[ :actions="[
{ {
auth: 'AbpTenantManagement.Tenants.ManageConnectionStrings', auth: 'AbpSaas.Tenants.ManageConnectionStrings',
color: 'error', color: 'error',
label: L('Delete'), label: L('Delete'),
icon: 'ant-design:delete-outlined', icon: 'ant-design:delete-outlined',
@ -55,7 +55,7 @@
export default defineComponent({ export default defineComponent({
components: { BasicForm, BasicModal, BasicTable, TableAction }, components: { BasicForm, BasicModal, BasicTable, TableAction },
setup() { setup() {
const { L } = useLocalization('AbpTenantManagement'); const { L } = useLocalization('AbpSaas');
const tenantIdRef = ref(''); const tenantIdRef = ref('');
const connectionsRef = ref<any[]>([]); const connectionsRef = ref<any[]>([]);
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();

6
apps/vue/src/views/saas/tenant/components/TenantModal.vue

@ -27,7 +27,7 @@
components: { BasicForm, BasicModal }, components: { BasicForm, BasicModal },
emits: ['change', 'register'], emits: ['change', 'register'],
setup(_props, { emit }) { setup(_props, { emit }) {
const { L } = useLocalization('AbpTenantManagement'); const { L } = useLocalization('AbpSaas');
const loading = ref(false); const loading = ref(false);
const tenantIdRef = ref(''); const tenantIdRef = ref('');
const [registerModal, { closeModal }] = useModalInner((data) => { const [registerModal, { closeModal }] = useModalInner((data) => {
@ -46,9 +46,7 @@
loading.value = true; loading.value = true;
const api = input.id const api = input.id
? update(input.id, { ? update(input.id, input)
name: input.name,
})
: create(input); : create(input);
api api

12
apps/vue/src/views/saas/tenant/components/TenantTable.vue

@ -3,7 +3,7 @@
<BasicTable ref="tableElRef" @register="registerTable"> <BasicTable ref="tableElRef" @register="registerTable">
<template #toolbar> <template #toolbar>
<a-button <a-button
v-if="hasPermission('AbpTenantManagement.Tenants.Create')" v-if="hasPermission('AbpSaas.Tenants.Create')"
type="primary" type="primary"
@click="handleAddNew" @click="handleAddNew"
>{{ L('NewTenant') }}</a-button >{{ L('NewTenant') }}</a-button
@ -19,13 +19,13 @@
<TableAction <TableAction
:actions="[ :actions="[
{ {
auth: 'AbpTenantManagement.Tenants.Update', auth: 'AbpSaas.Tenants.Update',
label: L('Edit'), label: L('Edit'),
icon: 'ant-design:edit-outlined', icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record), onClick: handleEdit.bind(null, record),
}, },
{ {
auth: 'AbpTenantManagement.Tenants.Delete', auth: 'AbpSaas.Tenants.Delete',
color: 'error', color: 'error',
label: L('Delete'), label: L('Delete'),
icon: 'ant-design:delete-outlined', icon: 'ant-design:delete-outlined',
@ -34,12 +34,12 @@
]" ]"
:dropDownActions="[ :dropDownActions="[
{ {
auth: 'FeatureManagement.ManageHostFeatures', auth: 'AbpSaas.Tenants.ManageFeatures',
label: L('ManageFeatures'), label: L('ManageFeatures'),
onClick: handleManageTenantFeature.bind(null, record), onClick: handleManageTenantFeature.bind(null, record),
}, },
{ {
auth: 'AbpTenantManagement.Tenants.ManageConnectionStrings', auth: 'AbpSaas.Tenants.ManageConnectionStrings',
label: L('ConnectionStrings'), label: L('ConnectionStrings'),
onClick: openConnectModal.bind(null, true, record), onClick: openConnectModal.bind(null, true, record),
}, },
@ -70,7 +70,7 @@
name: 'TenantTable', name: 'TenantTable',
components: { BasicTable, FeatureModal, TableAction, TenantModal, TenantConnectionModal }, components: { BasicTable, FeatureModal, TableAction, TenantModal, TenantConnectionModal },
setup() { setup() {
const { L } = useLocalization('AbpTenantManagement', 'AbpFeatureManagement'); const { L } = useLocalization('AbpSaas', 'AbpFeatureManagement');
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const tableElRef = ref<Nullable<TableActionType>>(null); const tableElRef = ref<Nullable<TableActionType>>(null);
const [registerConnectModal, { openModal: openConnectModal }] = useModal(); const [registerConnectModal, { openModal: openConnectModal }] = useModal();

58
apps/vue/src/views/saas/tenant/datas/ModalData.ts

@ -1,7 +1,8 @@
import { useLocalization } from '/@/hooks/abp/useLocalization'; import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormProps, FormSchema } from '/@/components/Form'; import { FormProps, FormSchema } from '/@/components/Form';
import { getList as getEditions } from '/@/api/saas/editions';
const { L } = useLocalization('AbpTenantManagement'); const { L } = useLocalization('AbpSaas');
export function getSearchFormSchemas(): Partial<FormProps> { export function getSearchFormSchemas(): Partial<FormProps> {
return { return {
@ -24,6 +25,22 @@ export function getModalFormSchemas(): FormSchema[] {
component: 'Input', component: 'Input',
label: 'id', label: 'id',
show: false, show: false,
dynamicDisabled: true,
},
{
field: 'concurrencyStamp',
component: 'Input',
label: 'concurrencyStamp',
show: false,
dynamicDisabled: true,
},
{
field: 'isActive',
component: 'Checkbox',
label: L('DisplayName:IsActive'),
labelWidth: 50,
colProps: { span: 24 },
defaultValue: true,
}, },
{ {
field: 'name', field: 'name',
@ -32,6 +49,45 @@ export function getModalFormSchemas(): FormSchema[] {
colProps: { span: 24 }, colProps: { span: 24 },
required: true, required: true,
}, },
{
field: 'editionId',
component: 'ApiSelect',
label: L('DisplayName:EditionName'),
colProps: { span: 24 },
componentProps: {
api: getEditions,
params: {
skipCount: 0,
maxResultCount: 100,
},
resultField: 'items',
labelField: 'displayName',
valueField: 'id',
},
},
{
field: 'enableTime',
component: 'DatePicker',
label: L('DisplayName:EnableTime'),
colProps: { span: 24 },
defaultValue: new Date(),
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'disableTime',
component: 'DatePicker',
label: L('DisplayName:DisableTime'),
colProps: { span: 24 },
componentProps: {
style: {
width: '100%',
},
},
},
{ {
field: 'adminEmailAddress', field: 'adminEmailAddress',
component: 'Input', component: 'Input',

9
apps/vue/src/views/saas/tenant/datas/TableData.ts

@ -1,7 +1,7 @@
import { useLocalization } from '/@/hooks/abp/useLocalization'; import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table'; import { BasicColumn } from '/@/components/Table';
const { L } = useLocalization('AbpTenantManagement'); const { L } = useLocalization('AbpSaas');
export function getDataColumns(): BasicColumn[] { export function getDataColumns(): BasicColumn[] {
return [ return [
@ -15,6 +15,13 @@ export function getDataColumns(): BasicColumn[] {
title: L('DisplayName:TenantName'), title: L('DisplayName:TenantName'),
dataIndex: 'name', dataIndex: 'name',
align: 'left', align: 'left',
width: 200,
sorter: true,
},
{
title: L('DisplayName:EditionName'),
dataIndex: 'editionName',
align: 'left',
width: 'auto', width: 'auto',
sorter: true, sorter: true,
}, },

2
apps/vue/src/views/saas/tenant/hooks/useTenantTable.ts

@ -14,7 +14,7 @@ interface UseTenantTable {
} }
export function useTenantTable({ tableElRef }: UseTenantTable) { export function useTenantTable({ tableElRef }: UseTenantTable) {
const { L } = useLocalization('AbpTenantManagement'); const { L } = useLocalization('AbpSaas');
const [registerTable, {}] = useTable({ const [registerTable, {}] = useTable({
rowKey: 'id', rowKey: 'id',
title: L('Tenants'), title: L('Tenants'),

8
aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs

@ -212,6 +212,14 @@ namespace LINGYUN.Abp.UI.Navigation.VueVbenAdmin
component: "/saas/tenant/index", component: "/saas/tenant/index",
description: "租户管理", description: "租户管理",
multiTenancySides: MultiTenancySides.Host)); multiTenancySides: MultiTenancySides.Host));
saas.AddItem(
new ApplicationMenu(
name: "Editions",
displayName: "版本管理",
url: "/saas/editions",
component: "/saas/editions/index",
description: "版本管理",
multiTenancySides: MultiTenancySides.Host));
return new NavigationDefinition(saas); return new NavigationDefinition(saas);
} }

4
aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Editions/Dto/EditionDto.cs

@ -1,9 +1,11 @@
using System; using System;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
namespace LINGYUN.Abp.Saas.Editions; namespace LINGYUN.Abp.Saas.Editions;
public class EditionDto : ExtensibleFullAuditedEntityDto<Guid> public class EditionDto : ExtensibleAuditedEntityDto<Guid>, IHasConcurrencyStamp
{ {
public string DisplayName { get; set; } public string DisplayName { get; set; }
public string ConcurrencyStamp { get; set; }
} }

6
aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application.Contracts/LINGYUN/Abp/Saas/Tenants/Dto/TenantDto.cs

@ -1,9 +1,10 @@
using System; using System;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
namespace LINGYUN.Abp.Saas.Tenants; namespace LINGYUN.Abp.Saas.Tenants;
public class TenantDto : ExtensibleFullAuditedEntityDto<Guid> public class TenantDto : ExtensibleAuditedEntityDto<Guid>, IHasConcurrencyStamp
{ {
public string Name { get; set; } public string Name { get; set; }
@ -16,4 +17,5 @@ public class TenantDto : ExtensibleFullAuditedEntityDto<Guid>
public DateTime? EnableTime { get; set; } public DateTime? EnableTime { get; set; }
public DateTime? DisableTime { get; set; } public DateTime? DisableTime { get; set; }
public string ConcurrencyStamp { get; set; }
} }

12
aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs

@ -99,6 +99,8 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService
await EventBus.PublishAsync(createEventData); await EventBus.PublishAsync(createEventData);
}); });
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<Tenant, TenantDto>(tenant); return ObjectMapper.Map<Tenant, TenantDto>(tenant);
} }
@ -120,6 +122,8 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService
input.MapExtraPropertiesTo(tenant); input.MapExtraPropertiesTo(tenant);
await TenantRepository.UpdateAsync(tenant); await TenantRepository.UpdateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<Tenant, TenantDto>(tenant); return ObjectMapper.Map<Tenant, TenantDto>(tenant);
} }
@ -132,6 +136,8 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService
return; return;
} }
await TenantRepository.DeleteAsync(tenant); await TenantRepository.DeleteAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync();
} }
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] [Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)]
@ -177,6 +183,10 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService
} }
tenant.SetConnectionString(input.Name, input.Value); tenant.SetConnectionString(input.Name, input.Value);
await TenantRepository.UpdateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync();
return new TenantConnectionStringDto return new TenantConnectionStringDto
{ {
Name = input.Name, Name = input.Name,
@ -204,5 +214,7 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService
}); });
await TenantRepository.UpdateAsync(tenant); await TenantRepository.UpdateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync();
} }
} }

1
aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json

@ -19,6 +19,7 @@
"DisplayName:TenantName": "Tenant name", "DisplayName:TenantName": "Tenant name",
"TenantDeletionConfirmationMessage": "Tenant '{0}' will be deleted. Do you confirm that?", "TenantDeletionConfirmationMessage": "Tenant '{0}' will be deleted. Do you confirm that?",
"ConnectionStrings": "Connection Strings", "ConnectionStrings": "Connection Strings",
"ConnectionStrings:AddNew": "New Connection",
"DisplayName:DefaultConnectionString": "Default Connection String", "DisplayName:DefaultConnectionString": "Default Connection String",
"DisplayName:UseSharedDatabase": "Use the Shared Database", "DisplayName:UseSharedDatabase": "Use the Shared Database",
"DisplayName:Name": "Name", "DisplayName:Name": "Name",

1
aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json

@ -19,6 +19,7 @@
"DisplayName:TenantName": "租户名称", "DisplayName:TenantName": "租户名称",
"TenantDeletionConfirmationMessage": "租户 '{0}' 将被删除. 你确定吗?", "TenantDeletionConfirmationMessage": "租户 '{0}' 将被删除. 你确定吗?",
"ConnectionStrings": "连接字符串", "ConnectionStrings": "连接字符串",
"ConnectionStrings:AddNew": "添加新连接",
"DisplayName:DefaultConnectionString": "默认连接字符串", "DisplayName:DefaultConnectionString": "默认连接字符串",
"DisplayName:UseSharedDatabase": "使用共享数据库", "DisplayName:UseSharedDatabase": "使用共享数据库",
"DisplayName:Name": "名称", "DisplayName:Name": "名称",

1
aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN.Abp.TenantManagement.Application.csproj

@ -14,7 +14,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.MultiTenancy\LINGYUN.Abp.MultiTenancy.csproj" /> <ProjectReference Include="..\LINGYUN.Abp.MultiTenancy\LINGYUN.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TenantManagement.Application.Contracts\LINGYUN.Abp.TenantManagement.Application.Contracts.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi/LINGYUN.Abp.TenantManagement.HttpApi.csproj

@ -12,8 +12,4 @@
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.TenantManagement.Application.Contracts\LINGYUN.Abp.TenantManagement.Application.Contracts.csproj" />
</ItemGroup>
</Project> </Project>

12
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.Configure.cs

@ -2,6 +2,7 @@
using LINGYUN.Abp.ExceptionHandling; using LINGYUN.Abp.ExceptionHandling;
using LINGYUN.Abp.ExceptionHandling.Emailing; using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.Saas;
using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
@ -22,6 +23,8 @@ using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures; using Volo.Abp.GlobalFeatures;
using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Localization;
using Volo.Abp.Json; using Volo.Abp.Json;
@ -87,6 +90,15 @@ public partial class BackendAdminHttpApiHostModule
}); });
} }
private void ConfigureFeatureManagement()
{
Configure<FeatureManagementOptions>(options =>
{
options.ProviderPolicies[EditionFeatureValueProvider.ProviderName] = AbpSaasPermissions.Editions.ManageFeatures;
options.ProviderPolicies[TenantFeatureValueProvider.ProviderName] = AbpSaasPermissions.Tenants.ManageFeatures;
});
}
private void ConfigureJsonSerializer() private void ConfigureJsonSerializer()
{ {
// 统一时间日期格式 // 统一时间日期格式

1
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs

@ -99,6 +99,7 @@ public partial class BackendAdminHttpApiHostModule : AbpModule
ConfigureJsonSerializer(); ConfigureJsonSerializer();
ConfigureExceptionHandling(); ConfigureExceptionHandling();
ConfigureVirtualFileSystem(); ConfigureVirtualFileSystem();
ConfigureFeatureManagement();
ConfigurePermissionManagement(); ConfigurePermissionManagement();
ConfigureCaching(configuration); ConfigureCaching(configuration);
ConfigureAuditing(configuration); ConfigureAuditing(configuration);

4
gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.Development.json

@ -103,7 +103,7 @@
} }
}, },
{ {
"DownstreamPathTemplate": "/api/tenant-management/{everything}", "DownstreamPathTemplate": "/api/saas/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
@ -111,7 +111,7 @@
"Port": 30010 "Port": 30010
} }
], ],
"UpstreamPathTemplate": "/api/tenant-management/{everything}", "UpstreamPathTemplate": "/api/saas/{everything}",
"UpstreamHttpMethod": [ "UpstreamHttpMethod": [
"GET", "GET",
"POST", "POST",

34
gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.backendadmin.json

@ -1,6 +1,6 @@
{ {
"Routes": [ "Routes": [
// // ܶ˵
{ {
"DownstreamPathTemplate": "/api/abp/application-configuration", "DownstreamPathTemplate": "/api/abp/application-configuration",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -26,7 +26,7 @@
}, },
"Key": "backend-admin-configuration" "Key": "backend-admin-configuration"
}, },
// API // ̬ܶAPI˵
{ {
"DownstreamPathTemplate": "/api/abp/api-definition", "DownstreamPathTemplate": "/api/abp/api-definition",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -52,7 +52,7 @@
}, },
"Key": "backend-admin-api-definition" "Key": "backend-admin-api-definition"
}, },
// //
{ {
"DownstreamPathTemplate": "/api/abp/multi-tenancy/{everything}", "DownstreamPathTemplate": "/api/abp/multi-tenancy/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -77,9 +77,9 @@
"UseTracing": true "UseTracing": true
} }
}, },
// //
{ {
"DownstreamPathTemplate": "/api/tenant-management/{everything}", "DownstreamPathTemplate": "/api/saas/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
@ -87,7 +87,7 @@
"Port": 30010 "Port": 30010
} }
], ],
"UpstreamPathTemplate": "/api/tenant-management/{everything}", "UpstreamPathTemplate": "/api/saas/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ], "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],
"LoadBalancerOptions": { "LoadBalancerOptions": {
"Type": "RoundRobin" "Type": "RoundRobin"
@ -108,7 +108,7 @@
"UseTracing": true "UseTracing": true
} }
}, },
// // Ȩ޹
{ {
"DownstreamPathTemplate": "/api/permission-management/{everything}", "DownstreamPathTemplate": "/api/permission-management/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -139,7 +139,7 @@
"UseTracing": true "UseTracing": true
} }
}, },
// //
{ {
"DownstreamPathTemplate": "/api/setting-management/settings/by-current-tenant", "DownstreamPathTemplate": "/api/setting-management/settings/by-current-tenant",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -171,7 +171,7 @@
}, },
"Key": "setting-current-tenant" "Key": "setting-current-tenant"
}, },
// // ȫ
{ {
"DownstreamPathTemplate": "/api/setting-management/settings/by-global", "DownstreamPathTemplate": "/api/setting-management/settings/by-global",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -203,7 +203,7 @@
}, },
"Key": "setting-global" "Key": "setting-global"
}, },
// // ΢
{ {
"DownstreamPathTemplate": "/api/setting-management/wechat/by-current-tenant", "DownstreamPathTemplate": "/api/setting-management/wechat/by-current-tenant",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -235,7 +235,7 @@
}, },
"Key": "wechat-setting-current-tenant" "Key": "wechat-setting-current-tenant"
}, },
// // ΢ȫ
{ {
"DownstreamPathTemplate": "/api/setting-management/wechat/by-global", "DownstreamPathTemplate": "/api/setting-management/wechat/by-global",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -267,7 +267,7 @@
}, },
"Key": "wechat-setting-global" "Key": "wechat-setting-global"
}, },
// //
{ {
"DownstreamPathTemplate": "/api/setting-management/aliyun/by-current-tenant", "DownstreamPathTemplate": "/api/setting-management/aliyun/by-current-tenant",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -299,7 +299,7 @@
}, },
"Key": "aliyun-setting-current-tenant" "Key": "aliyun-setting-current-tenant"
}, },
// // ȫ
{ {
"DownstreamPathTemplate": "/api/setting-management/aliyun/by-global", "DownstreamPathTemplate": "/api/setting-management/aliyun/by-global",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -331,7 +331,7 @@
}, },
"Key": "aliyun-setting-global" "Key": "aliyun-setting-global"
}, },
// // ù
{ {
"DownstreamPathTemplate": "/api/setting-management/{everything}", "DownstreamPathTemplate": "/api/setting-management/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -362,7 +362,7 @@
"UseTracing": true "UseTracing": true
} }
}, },
// // Թ
{ {
"DownstreamPathTemplate": "/api/feature-management/{everything}", "DownstreamPathTemplate": "/api/feature-management/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -393,7 +393,7 @@
"UseTracing": true "UseTracing": true
} }
}, },
// // ־
{ {
"DownstreamPathTemplate": "/api/auditing/{everything}", "DownstreamPathTemplate": "/api/auditing/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -424,7 +424,7 @@
"UseTracing": true "UseTracing": true
} }
}, },
// API // API ĵ
{ {
"DownstreamPathTemplate": "/swagger/v1/swagger.json", "DownstreamPathTemplate": "/swagger/v1/swagger.json",
"DownstreamScheme": "http", "DownstreamScheme": "http",

Loading…
Cancel
Save