在项目中,我们要进行一些参数配置,通常是使用aspnetcore的配置系统,通过appsettings.json来存储配置参数;Abp框架也包含了一套完善的设备管理模块,使用它可以很方便的从不同维度获取和设置应用程序的设置。
Abp设置管理模块预置的5个设置管理提供程序,分别是
DefaultValueSettingManagementProvider
: 从设置定义的默认值中获取值,由于默认值是硬编码在设置定义上的,所以无法更改默认值.ConfigurationSettingManagementProvider
:从 IConfiguration 服务中获取值. 由于无法在运行时更改配置值,所以无法更改配置值.GlobalSettingManagementProvider
: 获取或设定设置的全局 (系统范围)值.TenantSettingManagementProvider
: 获取或设定租户的设置值.UserSettingManagementProvider
: 获取或设定用户的设置值
下面用图形展示在5种设置提供间设置和获取值的模型
你也可以扩展自己的设置提供程序,比如组织结构的设置管理提供程序,它应该放在租户和用户之间。
下面以上一章的源码为示例,介绍如何为文件管理模块添加设置管理。
1、添加设置定义
所谓DDD领域驱动,也就是从领域层开始设计,不管是前面章节文件管理模块,还是此处的设置管理,都是从领域开始,一步步延申出去,直到前端页面
找到文件管理的Domain项目,在Settings目录下FileManagementSettings.cs中添加设置Key的常量
public static class FileManagementSettings
{
public const string GroupName = "FileManagement";
// 允许上传的文件大小
public const string MaxUploadFileSize = GroupName + ".MaxUploadFileSize";
// 用户存储空间
public const string UserStorageSize = GroupName + ".UserStorageSize";
}
在FileManagementSettingDefinitionProvider.cs文件中添加设置定义
using MyCompany.FileManagement.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
namespace MyCompany.FileManagement.Settings
{
public class FileManagementSettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition(
FileManagementSettings.MaxUploadFileSize,
"1048576", // 默认值
L("MaxUploadFileSize:Display"), // 显示值
L("MaxUploadFileSize:Desc"), // 描述
true)); // 是否对客户端可见
context.Add(
new SettingDefinition(
FileManagementSettings.UserStorageSize,
"1073741824",
L("UserStorageSize:Display"),
L("UserStorageSize:Desc"),
true));
}
// 语言本地化
private static LocalizableString L(string name)
{
return LocalizableString.Create<FileManagementResource>(name);
}
}
}
这样做了后设置已经生效了,启动HttpApi.Host,在浏览器输入https://localhost:44358/api/abp/application-configuration,查看应用程序配置,在下面找到Settings配置节,可以发现刚才定义的设置:
2、ConfigurationSetting配置设置
打开启动项目的appsettings.json文件,添加文件管理配置如下:
"Settings": {
"FileManagement.MaxUploadFileSize": 1000000,
"FileManagement.UserStorageSize": 200000000
}
重新启动HttpApi.Host项目,再次查看应用程序配置,发现设置的值已更改
对于其他设置提供程序的设置,就需要界面交互操作来设置了,接下来就应该做应用服务层的设计了
3、应用服务层设计
对于设置管理功能模块,它的基础设施已经在Abp框架中实现了,并且在启动项目中添加了依赖;所以这里就不用去管如何设计数据库实体和存储数据了,直接可以注入它提供的ISettingManager 接口来实现应用服务层的功能
首先在Application.Contracts项目中添加数据传输对象DTO和服务接口:
FileManagementSettingsDto.cs
namespace MyCompany.FileManagement.Settings
{
public class FileManagementSettingsDto
{
public long MaxUploadFileSize { get; set; }
public long UserStorageSize { get; set; }
}
}
IFileManagementSettingsService.cs
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace MyCompany.FileManagement.Settings
{
public interface IFileManagementSettingsService : IApplicationService
{
Task<FileManagementSettingsDto> GetAsync();
Task UpdateAsync(FileManagementSettingsDto input);
}
}
然后实现设置管理应用服务
在Application项目中添加Volo.Abp.SettingManagement.Application包的引用
添加FileManagementSettingsService.cs
using Microsoft.AspNetCore.Authorization;
using MyCompany.FileManagement.Permissions;
using MyCompany.FileManagement.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.SettingManagement;
namespace MyCompany.FileManagement.Services
{
[Authorize(FileManagementPermissions.Settings.Default)]
public class FileManagementSettingsService : FileManagementAppService, IFileManagementSettingsService
{
private readonly ISettingManager _settingManager;
public FileManagementSettingsService(ISettingManager settingManager)
{
_settingManager = settingManager;
}
public virtual async Task<FileManagementSettingsDto> GetAsync()
{
return new FileManagementSettingsDto
{
// 用户设置
MaxUploadFileSize = Convert.ToInt64(await _settingManager.GetOrNullForCurrentUserAsync(FileManagementSettings.MaxUploadFileSize)),
// 全局设置
UserStorageSize = Convert.ToInt64(await _settingManager.GetOrNullGlobalAsync(FileManagementSettings.UserStorageSize)),
};
}
public virtual async Task UpdateAsync(FileManagementSettingsDto input)
{
await _settingManager.SetForCurrentUserAsync(FileManagementSettings.MaxUploadFileSize, input.MaxUploadFileSize.ToString());
await _settingManager.SetGlobalAsync(FileManagementSettings.UserStorageSize, input.UserStorageSize.ToString());
}
}
}
最后添加权限定义,注意这里我们把文件管理模块的设置菜单项放到了框架的设置管理的权限菜单下面,因此,需要在Application.Contracts项目中添加Volo.Abp.SettingManagement.Application.Contracts包的引用
在FileManagementPermissions.cs中添加常量
...
using Volo.Abp.SettingManagement;
namespace MyCompany.FileManagement.Permissions
{
public class FileManagementPermissions
{
...
...
public static class Settings
{
public const string Default = SettingManagementPermissions.GroupName + ".FileManagement";
}
}
}
在FileManagementPermissionDefinitionProvider中添加权限定义
...
using Volo.Abp.SettingManagement;
namespace MyCompany.FileManagement.Permissions
{
public class FileManagementPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
...
// 获取框架的设置管理权限组
var settingGroup = context.GetGroup(SettingManagementPermissions.GroupName);
// 在设置管理权限组中添加文件管理权限
settingGroup.AddPermission(FileManagementPermissions.Settings.Default, L("Menu:FileManagement"));
}
...
}
}
启动前后端项目,登录后进入角色管理页面,查看admin角色权限,如图:
可以看到设置管理里有文件管理的权限了,勾选它,然后保存
4、添加WebApi控制器
Abp框架支持从Application服务自动生成webapi控制器,这里我们还是跟文件管理一样,自己创建api控制器,在HttpApi中添加FileManagementSettingsController.cs
using Microsoft.AspNetCore.Mvc;
using MyCompany.FileManagement.Settings;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
namespace MyCompany.FileManagement
{
[RemoteService(Name = FileManagementRemoteServiceConsts.RemoteServiceName)]
[Area(FileManagementRemoteServiceConsts.ModuleName)]
[Route("api/file-management/settings")]
public class FileManagementSettingsController : AbpController, IFileManagementSettingsService
{
protected IFileManagementSettingsService _fileManagementSettingsService { get; }
public FileManagementSettingsController(IFileManagementSettingsService fileManagementSettingsService)
{
_fileManagementSettingsService = fileManagementSettingsService;
}
[HttpGet]
public Task<FileManagementSettingsDto> GetAsync()
{
return _fileManagementSettingsService.GetAsync();
}
[HttpPut]
public Task UpdateAsync(FileManagementSettingsDto input)
{
return _fileManagementSettingsService.UpdateAsync(input);
}
}
}
5、配置angular设置菜单和组件
angular设置组件是通过SettingTabsService服务收集各个模块的设置方法,然后通过设置菜单和Tab页方式展示出来。
首先在file-management的config子模块中添加文件管理设置界面组件,因为config子模块是手动添加的,在使用angular-cli添加组件前,需要在angular.json中配置config子模块:
"file-management": {
...
...
},
"file-management-config": {
"projectType": "library",
"root": "projects/file-management/config",
"sourceRoot": "projects/file-management/config/src",
"prefix": ""
}
子模块的项目名称定义为file-management-config,因此可以在终端执行语句添加组件:
ng generate component file-management-settings-group --skip-tests --project file-management-config
接着在providers目录中添加setting-tab.provider.ts,内容如下:
import { SettingTabsService } from '@abp/ng.setting-management/config';
import { APP_INITIALIZER } from '@angular/core';
import { FileManagementSettingsGroupComponent } from '../lib/file-management-settings-group/file-management-settings-group.component';
export const FILE_MANAGE_SETTING_TAB_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: configureSettingTabs,
deps: [SettingTabsService],
multi: true,
},
];
export function configureSettingTabs(settingTabs: SettingTabsService) {
return () => {
settingTabs.add([
{
name: 'FileManagement::Menu:FileManagement',
order: 50,
requiredPolicy: 'SettingManagement.FileManagement',
component: FileManagementSettingsGroupComponent,
},
]);
};
}
其中APP_INITIALIZER是一个InjectionToken类型的可注入令牌,它可接收的值是一个方法组,在这些方法中可对程序进行配置,代码中FILE_MANAGE_SETTING_TAB_PROVIDERS将settingTabs.add(...)方法封装为configureSettingTabs方法提供给APP_INITIALIZER,其他模块也可能会有这个一个setting-tab.provider,他们都会通过模块的forRoot()方法提供给根模块进行初始化,在程序中任何地方注入APP_INITIALIZER就可以获取到所有地方提供的配置方法了
修改file-management-config.module.ts文件,在forRoot()中添加FILE_MANAGE_SETTING_TAB_PROVIDERS
执行yarn build:files编译文件模块,然后npm start 启动
可以看到设置页已生效了
6、生成angular服务代理
可以直接运行abp generate-proxy -t ng -m fileManagement -a FileManagement --target file-management-config生成客户端代理代码到config子模块,但是这样做文件管理主模块和config子模块都有一份相同的客户端代理代码,而config又不能引用主模块中的方法;
有两种方法解决这个问题,第一种是在文件模块中再创建一个proxy子模块,这样主模块和config子模块都可以引用proxy子模块了;第二种方法是修改后端webapi控制器的Area属性修饰,生成代理指令 -m 也换成Area属性修饰的值
这里我们用第二种方法,修改FileManagementSettingsController的Area属性修饰值为fileManagementConfig
[Area("fileManagementConfig")]
...
public class FileManagementSettingsController : AbpController, IFileManagementSettingsService
重新启动后端Host项目
再次生成客户端代理(注意最新版升级后abp-cli生成代理的方法有些不同了):
abp generate-proxy -t ng -m fileManagementConfig -a FileManagement --target file-management-config
7、编写文件设置组件代码
修改file-management-settings-group.component.html,内容如下:
<h2>{{ 'FileManagement::Menu:FileManagement' | abpLocalization }}</h2>
<hr class="my-3" />
<form *ngIf="form" [formGroup]="form" (ngSubmit)="submit()" validateOnSubmit>
<div class="mb-3">
<label class="form-label" for="max-upload-size">{{ 'FileManagement::MaxUploadFileSize:Display' | abpLocalization }}</label>
<input type="number" id="max-upload-size" class="form-control" formControlName="maxUploadFileSize" />
</div>
<div class="mb-3">
<label class="form-label" for="user-storage-size">{{ 'FileManagement::UserStorageSize:Display' | abpLocalization
}}</label>
<input type="number" id="user-storage-size" class="form-control" formControlName="userStorageSize" />
</div>
<hr />
<button type="submit" [disabled]="saving" class="btn btn-primary">
<i class="ms-1" [ngClass]="{'fa fa-save': !saving, 'fa fa-spinner fa-spin': saving}"></i>
{{ 'FileManagement::Files:Save' | abpLocalization }}
</button>
</form>
修改file-management-settings-group.component.ts,内容如下:
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { FileManagementSettingsService } from '../proxy';
import { FileManagementSettingsDto } from '../proxy/settings';
@Component({
selector: 'lib-file-management-settings-group',
templateUrl: './file-management-settings-group.component.html',
styleUrls: ['./file-management-settings-group.component.css']
})
export class FileManagementSettingsGroupComponent implements OnInit {
form: FormGroup;
selected: FileManagementSettingsDto;
saving = false;
constructor(
private filesSettingsService: FileManagementSettingsService,
private toasterService: ToasterService,
protected fb: FormBuilder,
) { }
ngOnInit(): void {
this.getData();
}
private getData() {
this.filesSettingsService.get().subscribe(res => {
this.buildForm(res);
});
}
private buildForm(filesSettings: FileManagementSettingsDto) {
this.selected = filesSettings;
this.form = this.fb.group({
maxUploadFileSize: [filesSettings.maxUploadFileSize, [Validators.required]],
userStorageSize: [filesSettings.userStorageSize, [Validators.required]],
});
}
submit() {
if (this.saving || this.form.invalid) { return; }
this.saving = true;
this.filesSettingsService
.update(this.form.value)
.pipe(finalize(() => (this.saving = false)))
.subscribe(() => {
this.toasterService.success('AbpSettingManagement::SuccessfullySaved');
this.getData();
});
}
}
8、添加本地化语言资源
在后台Domain.Shared中添加多语言资源
"MaxUploadFileSize:Display": "上传文件大小限制",
"MaxUploadFileSize:Desc": "允许上传的文件大小的最大值(单位KB)。",
"UserStorageSize:Display": "用户存储空间",
"UserStorageSize:Desc": "每个用户拥有的文件存储空间大小。"
重新启动后台Host项目,
angular中执行yarn build:files编译文件模块,然后npm start 启动,界面如下:
修改设置值,点击保存
9、在angular前端获取设置值
前面我们已经可以通过设置修改文件上传大小限制和用户存储空间了,那么我要怎么获取设置值呢,比如在文件提交到后台之前,判断上传文件的大小限制
打开文件管理模块中的UploadFilesComponent组件,注入ConfigStateService服务,修改内容如下:
constructor(
...
private toastService: ToasterService,
private config: ConfigStateService,
...) {
// 获取上传大小限制的设置
const maxFileSize = this.config.getOne('setting').values['FileManagement.MaxUploadFileSize'];
this.options = {
concurrency: 1,
maxUploads: 3,
maxFileSize: maxFileSize // 上传文件大小限制
};
...
}
重新执行yarn build:files编译文件模块,然后npm start 启动,发现上传超过1M就被拦截了
10、在后端获取设置值
打开文件模块Domain项目的FileManagementManager文件,注入ISettingProvider服务来获取设置值,修改内容如下
using MyCompany.FileManagement.Settings;
...
namespace MyCompany.FileManagement
{
public class FileManagementManager: DomainService
{
...
// 注入设置程序接口
private readonly ISettingProvider _settingProvider;
public FileManagementManager(...
ISettingProvider settingProvider)
{
...
_settingProvider = settingProvider;
}
...
public async Task<string> CreateAsync(...)
{
// 获取用户存储空间限制的设置值
var userStorageLimit = await _settingProvider.GetAsync<int>(FileManagementSettings.UserStorageSize);
// 获取当前已用用户空间大小
var userUsedSize = await _fileRepository.GetStorageSizeAsync(userId);
var userTotalSize = bytes.Length + userUsedSize;
if (userTotalSize > userStorageLimit)
{
throw new UserFriendlyException("剩余空间不足!");
}
11、验证用户的配置
之前在Application应用服务中保存设置时,用户存储空间存储为全局设置,而上传文件大小限制存储为用户设置,打开文件管理设置页,修改设置值,然后添加一个新用户,设置好权限,切换到新用户登录,查看文件管理设置页,发现用户设置上传文件大小限制变为了appsettings.json配置中设置的值1000000,而全局设置用户存储空间则跟admin用户中的值一样
本文源码:Abp5.0.0文件管理模块