今天这篇文章主要来总结一下ABP中的多语言是怎么实现的,在后面我们将结合ABP中的源码和相关的实例来一步步进行说明,在介绍这个之前我们先来看看ABP的官方文档,通过这个文档我们就知道怎样在我们的系统中使用ABP自带的本地化处理方式了,当前文章将分为三个部分,1  怎样在应用层和领域层添加本地化支持。2  ABP本地化中的源码分析。3  怎么实现Dto中本地化。下面就每一个部分来进行深入的分析。

  一  怎样在应用层和领域层添加本地化支持

  按照ABP官方文档中介绍,存储本地化资源推荐使用XML File 或者是Json  File 这里主要是介绍怎么使用Json File来存储需要实现本地化的一些重要信息。

  1 在PreInitialize方法中添加支持的语言。

  在我们的项目中我们将这个方法的实现放在了每一个Domain层的Module的 PreInitialize()方法里面,这里我们将当前的方法写成了一个静态方法,然后再在PreInitialize()里面调用,这里我们来看看具体的代码实现。

public static class DcsLocalizationConfigurer {

        public static void Configure(ILocalizationConfiguration localizationConfiguration) {
localizationConfiguration.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flags england"));
localizationConfiguration.Languages.Add(new LanguageInfo("zh-Hans", "简体中文", "famfamfam-flags cn", isDefault: true)); localizationConfiguration.Sources.Add(
new DictionaryBasedLocalizationSource(DcsConsts.LocalizationSourceName,
new JsonEmbeddedFileLocalizationDictionaryProvider(
typeof(DcsLocalizationConfigurer).GetAssembly(),
"XXX.Localization.SourceFiles"
)
)
);
}
}

  注意这里的XXX就是当前Domain对应的命名空间了,Localization、SourceFiles都是我们放置本地化信息文件具体路径,具体目录结构如下图所示,这里我们的源文件中实现了中文和英文两种本地化配置文件,通过文件名称中间的en或者zh-Hans我们就能够加以区分。

图一 本地化文件路径

  这里我们在看看具体Json文件该怎么进行定义?

{
"culture": "zh-Hans",
"texts": {
"HelloWorld": "欢迎",
"RegionNotFound": "省市区信息未找到",
"DutyUnitNotFound": "责任单位信息未找到",
"ExistsDutyUnitCode": "已存在责任单位编码为",
"FaultModelNotFound": "故障模式未找到",
"CompanyNotFound": "当前用户所在企业信息未找到",
"PartNotFound": "配件信息未找到",
"ProductNotFound": "产品信息未找到",
"BranchNotFound": "营销分公司信息未找到"
}
}

  这里介绍了中文一些本地化信息的配置,我们可以看到定义是按照Key、Value的形式进行存储的,culture代表当前的语言类型。通过这一步我们就能够将定义的本地化文件添加到ABP中了。

  2 在应用层或者是领域层中使用本地化

  由于在ABP中应用层和领域层中都是继承自抽象类AbpServiceBase,所以我们便可以直接在代码中进行使用L方法,具体使用如下面的代码所示。

private DutyUnit GetDutyUnitById(Guid id) {
var dutyUnit = _dutyUnitRepository.GetAll().FirstOrDefault(p => p.Id == id && p.Status == DutyUnitStatus.生效 && p.Type == DutyUnitType.配件供应商);
if (dutyUnit == null) {
throw new PreconditionFailedException(L("DutyUnitNotFound"));
}
return dutyUnit;
}  

  上面的代码是一个定义在领域层的方法,该方法就是当数据库中到不到当前Id对应的DutyUnit的时候,就会抛出一个异常,这个异常信息就用L("DutyUnitNotFound"),这个L方法就会根据当前配置的Culture到对应的Json文件中找到匹配项,就像当前代码我们配置当前的Culture为中文的时候,那么最终抛出的异常信息将会是:“责任单位信息未找到”,通过这种方式我们就会发现通过配置不同语言的Json文件我们就能够实现多语言的报错提示信息了。

  谈到这里我们就应该明白了到底如何配置和使用L方法了,但是我们还有一个疑问就是到底该如何去配置当前的Culture呢?因为最终到底会到哪个本地化文件中查询多语言的信息是取决于当前项目中的Culture,这里我们需要在我们的项目中配置好我们所需要的本地化SourceName,当然在实际的项目中这个可以根据前端按钮的选择从而向项目中传递不同的Culture,最终实现界面、抛错提示信息等等的动态切换,当然我们这里举例是在代码中默认选定一种LocalizationSourceName,而不是进行动态切换,实际项目需要根据实际情况进行考虑。

    public abstract class DcsDomainServiceBase : DomainService {

        protected DcsDomainServiceBase() {
LocalizationSourceName = DcsConsts.LocalizationSourceName;
}
}

  这里我们在所有的Domain服务的基类中指定LocalizationSourceName = DcsConsts.LocalizationSourceName(注: DcsConsts.LocalizationSourceName值为"zh-Hans"),这样就为当前项目中指定Cluture啦,这样我们领域层所有引用L方法的地方均能正确找到本地化信息了。

  二  ABP本地化中的源码分析

  上面的代码只是告诉了我们如何在自己的项目中集成ABP自带的本地化方式,那么这些背后实现的原理到底是怎么样的呢?通过分析源码相信我们能够一步步找到问题的答案。

  通过第一部分的介绍我们知道本地化的核心实现在于AbpServiceBase类中的L方法,那么我们首先来看看ABP中的这个方法吧。

using System.Globalization;
using Abp.Configuration;
using Abp.Domain.Uow;
using Abp.Localization;
using Abp.Localization.Sources;
using Abp.ObjectMapping;
using Castle.Core.Logging; namespace Abp
{
/// <summary>
/// This class can be used as a base class for services.
/// It has some useful objects property-injected and has some basic methods
/// most of services may need to.
/// </summary>
public abstract class AbpServiceBase
{
/// <summary>
/// Reference to the setting manager.
/// </summary>
public ISettingManager SettingManager { get; set; } /// <summary>
/// Reference to <see cref="IUnitOfWorkManager"/>.
/// </summary>
public IUnitOfWorkManager UnitOfWorkManager
{
get
{
if (_unitOfWorkManager == null)
{
throw new AbpException("Must set UnitOfWorkManager before use it.");
} return _unitOfWorkManager;
}
set { _unitOfWorkManager = value; }
}
private IUnitOfWorkManager _unitOfWorkManager; /// <summary>
/// Gets current unit of work.
/// </summary>
protected IActiveUnitOfWork CurrentUnitOfWork { get { return UnitOfWorkManager.Current; } } /// <summary>
/// Reference to the localization manager.
/// </summary>
public ILocalizationManager LocalizationManager { get; set; } /// <summary>
/// Gets/sets name of the localization source that is used in this application service.
/// It must be set in order to use <see cref="L(string)"/> and <see cref="L(string,CultureInfo)"/> methods.
/// </summary>
protected string LocalizationSourceName { get; set; } /// <summary>
/// Gets localization source.
/// It's valid if <see cref="LocalizationSourceName"/> is set.
/// </summary>
protected ILocalizationSource LocalizationSource
{
get
{
if (LocalizationSourceName == null)
{
throw new AbpException("Must set LocalizationSourceName before, in order to get LocalizationSource");
} if (_localizationSource == null || _localizationSource.Name != LocalizationSourceName)
{
_localizationSource = LocalizationManager.GetSource(LocalizationSourceName);
} return _localizationSource;
}
}
private ILocalizationSource _localizationSource; /// <summary>
/// Reference to the logger to write logs.
/// </summary>
public ILogger Logger { protected get; set; } /// <summary>
/// Reference to the object to object mapper.
/// </summary>
public IObjectMapper ObjectMapper { get; set; } /// <summary>
/// Constructor.
/// </summary>
protected AbpServiceBase()
{
Logger = NullLogger.Instance;
ObjectMapper = NullObjectMapper.Instance;
LocalizationManager = NullLocalizationManager.Instance;
} /// <summary>
/// Gets localized string for given key name and current language.
/// </summary>
/// <param name="name">Key name</param>
/// <returns>Localized string</returns>
protected virtual string L(string name)
{
return LocalizationSource.GetString(name);
} /// <summary>
/// Gets localized string for given key name and current language with formatting strings.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="args">Format arguments</param>
/// <returns>Localized string</returns>
protected string L(string name, params object[] args)
{
return LocalizationSource.GetString(name, args);
} /// <summary>
/// Gets localized string for given key name and specified culture information.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="culture">culture information</param>
/// <returns>Localized string</returns>
protected virtual string L(string name, CultureInfo culture)
{
return LocalizationSource.GetString(name, culture);
} /// <summary>
/// Gets localized string for given key name and current language with formatting strings.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="culture">culture information</param>
/// <param name="args">Format arguments</param>
/// <returns>Localized string</returns>
protected string L(string name, CultureInfo culture, params object[] args)
{
return LocalizationSource.GetString(name, culture, args);
}
}
}

  我们可以看到这其中的L方法有四种实现,通过添加不同的参数我们能够实现不同功能。这里我们就先从最简单的带一个name参数的方法说起吧。这里面又调用了一个ILocalizationSource接口中的GetString方法,这里我们先来看看LocalizationSource这个定义的属性来开始说吧。  

/// <summary>
/// Gets localization source.
/// It's valid if <see cref="LocalizationSourceName"/> is set.
/// </summary>
protected ILocalizationSource LocalizationSource
{
get
{
if (LocalizationSourceName == null)
{
throw new AbpException("Must set LocalizationSourceName before, in order to get LocalizationSource");
} if (_localizationSource == null || _localizationSource.Name != LocalizationSourceName)
{
_localizationSource = LocalizationManager.GetSource(LocalizationSourceName);
} return _localizationSource;
}

  在这个方法中第一次进入的时候会创建私有的_localizationSource对象,在这个创建的时候调用LocalizationManager.GetSource(LocalizationSourceName)这个方法,这其中LocalizationManager是通过属性注入的对象,LocalizationSourceName这个参数通过上面的分析你应该知道为什么需要在DcsDomainServiceBase 中首先定义这个属性了吧。到了这里我们来看看LocalizationManager中定义的GetSource方法。

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Localization.Dictionaries;
using Abp.Localization.Sources;
using Castle.Core.Logging; namespace Abp.Localization
{
internal class LocalizationManager : ILocalizationManager
{
public ILogger Logger { get; set; } private readonly ILanguageManager _languageManager;
private readonly ILocalizationConfiguration _configuration;
private readonly IIocResolver _iocResolver;
private readonly IDictionary<string, ILocalizationSource> _sources; /// <summary>
/// Constructor.
/// </summary>
public LocalizationManager(
ILanguageManager languageManager,
ILocalizationConfiguration configuration,
IIocResolver iocResolver)
{
Logger = NullLogger.Instance;
_languageManager = languageManager;
_configuration = configuration;
_iocResolver = iocResolver;
_sources = new Dictionary<string, ILocalizationSource>();
} public void Initialize()
{
InitializeSources();
} private void InitializeSources()
{
if (!_configuration.IsEnabled)
{
Logger.Debug("Localization disabled.");
return;
} Logger.Debug(string.Format("Initializing {0} localization sources.", _configuration.Sources.Count));
foreach (var source in _configuration.Sources)
{
if (_sources.ContainsKey(source.Name))
{
throw new AbpException("There are more than one source with name: " + source.Name + "! Source name must be unique!");
} _sources[source.Name] = source;
source.Initialize(_configuration, _iocResolver); //Extending dictionaries
if (source is IDictionaryBasedLocalizationSource)
{
var dictionaryBasedSource = source as IDictionaryBasedLocalizationSource;
var extensions = _configuration.Sources.Extensions.Where(e => e.SourceName == source.Name).ToList();
foreach (var extension in extensions)
{
extension.DictionaryProvider.Initialize(source.Name);
foreach (var extensionDictionary in extension.DictionaryProvider.Dictionaries.Values)
{
dictionaryBasedSource.Extend(extensionDictionary);
}
}
} Logger.Debug("Initialized localization source: " + source.Name);
}
} /// <summary>
/// Gets a localization source with name.
/// </summary>
/// <param name="name">Unique name of the localization source</param>
/// <returns>The localization source</returns>
public ILocalizationSource GetSource(string name)
{
if (!_configuration.IsEnabled)
{
return NullLocalizationSource.Instance;
} if (name == null)
{
throw new ArgumentNullException("name");
} ILocalizationSource source;
if (!_sources.TryGetValue(name, out source))
{
throw new AbpException("Can not find a source with name: " + name);
} return source;
} /// <summary>
/// Gets all registered localization sources.
/// </summary>
/// <returns>List of sources</returns>
public IReadOnlyList<ILocalizationSource> GetAllSources()
{
return _sources.Values.ToImmutableList();
}
}
}

  在这个GetSource方法中,会到类型为IDictionary<string, ILocalizationSource>的私有变量_sources中找到当前名称为LocalizationSourceName对应的ILocalizationSource,那么我们在DomainModule中通过PreInitialize()方法配置ILocalizationConfiguration中的Source集合,这个集合中我们默认添加了一个Key为DcsConsts.LocalizationSourceName,value为JsonEmbeddedFileLocalizationDictionaryProvider的对象,那么这个ILocalizationConfiguration中定义的Source和我们在LocalizationManager中定义的_sources是怎么关联到一起的呢?答案是通过LocalizationManager中的Initialize()来实现的,通过代码分析我们发现LocalizationManager中的Initialize()是在AbpKernelModule中的PostInitialize()方法中的调用的。

  public override void PostInitialize()
{
RegisterMissingComponents(); IocManager.Resolve<SettingDefinitionManager>().Initialize();
IocManager.Resolve<FeatureManager>().Initialize();
IocManager.Resolve<PermissionManager>().Initialize();
IocManager.Resolve<LocalizationManager>().Initialize();
IocManager.Resolve<NotificationDefinitionManager>().Initialize();
IocManager.Resolve<NavigationManager>().Initialize(); if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
var workerManager = IocManager.Resolve<IBackgroundWorkerManager>();
workerManager.Start();
workerManager.Add(IocManager.Resolve<IBackgroundJobManager>());
}
}

  如果熟悉ABP模块加载顺序的应该知道,如果对ABP中的模块化还不太熟悉,请点击上篇下篇了解相关内容,AbpKernelModule是整个ABP Module集合中永远处在第一个位置的模块,并且所有的模块会按照拓扑排序进行存放的,并且在初始化整个ABP系统的过程中会依次调用每一个模块PreInitialize()、Initialize()、PostInitialize()方法来进行模块的初始化,当我们的业务DomainModule通过PreInitialize()方法添加到ILocalizationConfiguration中的Source中的对象会被后面的AbpKernelModule执行PostInitialize()(在这其中调用LocalizationManager的Initialize())的时候将我们在具体业务Module中配置的Source添加到LocalizationManager私有的_sources对象只能够,后面我们再执行LocalizationManager中的GetSource方法的时候就能够从私有的_sources集合中获取到最终的ILocalizationSource对象了,这个就是整个实现过程,这里也贴出LocalizationManager中的Initialize方法中关键的代码。

private void InitializeSources()
{
if (!_configuration.IsEnabled)
{
Logger.Debug("Localization disabled.");
return;
} Logger.Debug(string.Format("Initializing {0} localization sources.", _configuration.Sources.Count));
foreach (var source in _configuration.Sources)
{
if (_sources.ContainsKey(source.Name))
{
throw new AbpException("There are more than one source with name: " + source.Name + "! Source name must be unique!");
} _sources[source.Name] = source;
source.Initialize(_configuration, _iocResolver); //Extending dictionaries
if (source is IDictionaryBasedLocalizationSource)
{
var dictionaryBasedSource = source as IDictionaryBasedLocalizationSource;
var extensions = _configuration.Sources.Extensions.Where(e => e.SourceName == source.Name).ToList();
foreach (var extension in extensions)
{
extension.DictionaryProvider.Initialize(source.Name);
foreach (var extensionDictionary in extension.DictionaryProvider.Dictionaries.Values)
{
dictionaryBasedSource.Extend(extensionDictionary);
}
}
} Logger.Debug("Initialized localization source: " + source.Name);
}
}

  到了这里结合具体的代码你应该明白整个过程了,这一部分具体过程请参考下篇

 三  怎么实现Dto中本地化

  最后在这里说一下,Dto中怎么去为验证信息添加本地化支持,我们知道在ABP中会默认提供一个ICustomValidate的接口,这个里面提供了一个AddValidationErrors的方法,用于对Dto进行各种验证操作,例如下面的代码。

 public class AddOrUpdateWarrantyPolicyBase : ICustomValidate {
public AddOrUpdateWarrantyPolicyBase() {
Items = Array.Empty<AddOrUpdatePolicyItemInput>();
} //名称
[Required]
public string Name { get; set; } //启用日期
public DateTime StartTime { get; set; } //备注
public string Remark { get; set; } public AddOrUpdatePolicyItemInput[] Items { get; set; }
public void AddValidationErrors(CustomValidationContext context) {
if (StartTime <= DateTime.Now) {
context.Results.Add(new ValidationResult("启用日期必须大于服务器当前日期"));
} if (Items.Length == 0) {
context.Results.Add(new ValidationResult("整车保修政策清单列表不允许为空"));
}
}
}

  由于在Dto中无法使用L方法,那么像这样的类型该进行怎样的处理呢?也许我们可以获取到LocalizationManager对象并调用其中的GetSource方法来实现,对,我们发现AddValidationErrors(CustomValidationContext context)这个方法的参数context中有公共的IocResolver,我们也可以先看看这个CustomValidationContext 的定义。  

public class CustomValidationContext
{
/// <summary>
/// List of validation results (errors). Add validation errors to this list.
/// </summary>
public List<ValidationResult> Results { get; } /// <summary>
/// Can be used to resolve dependencies on validation.
/// </summary>
public IIocResolver IocResolver { get; } public CustomValidationContext(List<ValidationResult> results, IIocResolver iocResolver)
{
Results = results;
IocResolver = iocResolver;
}
}  

  有了这个就好办了,我们可以获取ABP中的各种实例了,当然也包括ILocalizationManager对象了,这里我们来看看我们通过一个拓展方法的实现。

 /// <summary>
/// 主要是用作对Dto的验证信息提供本地化支持
/// </summary>
public static class CustomValidationContextExtension {
/// <summary>
/// Gets localized string for given key name and current language.
/// </summary>
/// <param name="context">CustomValidation Context</param>
/// <param name="keyName">key name</param>
/// <param name="localizationSourceName">the default source name is chinese</param>
/// <returns></returns>
public static string LocalizeString(this CustomValidationContext context, string keyName, string localizationSourceName = DcsConsts.LocalizationSourceName) {
var localizationManager = context.IocResolver.Resolve<ILocalizationManager>();
if (null == localizationManager) {
throw new AbpValidationException("Can not resolve instance of ILocalizationManager");
} var localizationSource = localizationManager.GetSource(localizationSourceName);
return localizationSource.GetString(keyName);
} /// <summary>
/// Gets localized string for given key name and current language.
/// </summary>
/// <param name="context">CustomValidation Context</param>
/// <param name="keyName">key name</param>
/// <param name="culture">culture information</param>
/// <param name="localizationSourceName">the default source name is chinese</param>
/// <returns></returns>
public static string LocalizeString(this CustomValidationContext context, string keyName, CultureInfo culture, string localizationSourceName = DcsConsts.LocalizationSourceName) {
var localizationManager = context.IocResolver.Resolve<ILocalizationManager>();
if (null == localizationManager) {
throw new AbpValidationException("Can not resolve instance of ILocalizationManager");
} var localizationSource = localizationManager.GetSource(localizationSourceName);
return localizationSource.GetString(keyName, culture);
} /// <summary>
/// Gets localized string for given key name and current language.
/// </summary>
/// <param name="context">CustomValidation Context</param>
/// <param name="keyName">key name</param>
/// <param name="culture">culture information</param>
/// <param name="localizationSourceName">the default source name is chinese</param>
/// <param name="args">Format arguments</param>
/// <returns></returns>
public static string LocalizeString(this CustomValidationContext context, string keyName, CultureInfo culture, string localizationSourceName = DcsConsts.LocalizationSourceName, params object[] args) {
var localizationManager = context.IocResolver.Resolve<ILocalizationManager>();
if (null == localizationManager) {
throw new AbpValidationException("Can not resolve instance of ILocalizationManager");
} var localizationSource = localizationManager.GetSource(localizationSourceName);
return localizationSource.GetString(keyName, culture, args);
}
}  

  通过这个我们就直接通过LocalizeString等相关的重载方法来完成我们所需要的各种本地化操作了,本篇文章就介绍到这里。

  最后,点击这里返回整个ABP系列的主目录。

ABP中的本地化处理(上)的更多相关文章

  1. ABP中的本地化处理(下)

    在上篇文章中我们的重点是讲述怎样通过在Domain层通过PreInitialize()配置ILocalizationConfiguration中的Sources(IList<ILocalizat ...

  2. ABP中的Filter(上)

    这个部分我打算用上下两个部分来将整个结构来讲完,在我们读ABP中的代码之后我们一直有一个疑问?在ABP中为什么要定义Interceptor和Filter,甚至这两者之间我们都能找到一些对应关系,比如: ...

  3. ABP中的拦截器之ValidationInterceptor(上)

    从今天这一节起就要深入到ABP中的每一个重要的知识点来一步步进行分析,在进行介绍ABP中的拦截器之前我们先要有个概念,到底什么是拦截器,在介绍这些之前,我们必须要了解AOP编程思想,这个一般翻译是面向 ...

  4. (新年快乐)ABP理论学习之本地化(2016第一篇)

    返回总目录 本篇目录 应用语言 本地化资源 获取本地化文本 扩展本地化资源 最佳实践 应用语言 一个应用至少有一种UI语言,许多应用不止有一种语言.ABP为应用提供了一个灵活的本地化系统. 第一件事情 ...

  5. ABP中模块初始化过程(二)

    在上一篇介绍在StartUp类中的ConfigureService()中的AddAbp方法后我们再来重点说一说在Configure()方法中的UserAbp()方法,还是和前面的一样我们来通过代码来进 ...

  6. ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  7. Laravel中的日志与上传

    PHP中的框架众多,我自己就接触了好几个.大学那会啥也不懂啥也不会,拿了一个ThinkPHP学了.也许有好多人吐槽TP,但是个人感觉不能说哪个框架好,哪个框架不好,再不好的框架你能把源码读上一遍,框架 ...

  8. UnitOfWork以及其在ABP中的应用

    Unit Of Work(UoW)模式在企业应用架构中被广泛使用,它能够将Domain Model中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据 ...

  9. ABP中使用Redis Cache(2)

    上一篇讲解了如何在ABP中使用Redis Cache,虽然能够正常的访问Redis,但是Redis里的信息无法同步更新.本文将讲解如何实现Redis Cache与实体同步更新.要实现数据的同步更新,我 ...

随机推荐

  1. [WEB安全]phpMyadmin后台任意文件包含漏洞分析(CVE-2018-12613)

    0x00 简介 影响版本:4.8.0--4.8.1 本次实验采用版本:4.8.1 0x01 效果展示 payload: http://your-ip:8080/index.php?target=db_ ...

  2. python 3 安装

    如果本机安装了python2,尽量不要管他,使用python3运行python脚本就好,因为可能有程序依赖目前的python2环境, 比如yum!!!!! 不要动现有的python2环境! 一.安装p ...

  3. Java8函数式编程的宏观总结

    1.java8优势通过将行为进行抽象,java8提供了批量处理数据的并行类库,使得代码可以在多核CPU上高效运行. 2.函数式编程的核心使用不可变值和函数,函数对一个值进行处理,映射成另一个值. 3. ...

  4. scanf和fgets比较

    scanf 长度限制 #include<stdio.h> int main() { char food[5]; printf("Enter food"); scanf( ...

  5. Colab使用教程

    目录 有关链接 使用GPU 切换文件夹 参考 有关链接 Google Colabratory Google Drive 使用GPU 以下两种方式都可以: "修改"->&quo ...

  6. Mininet系列实验(五):Mininet设置带宽之简单性能测试

    1.实验目的 该实验通过Mininet学习python自定义拓扑实现,可在python脚本文件中设计任意想要的拓扑,简单方便,并通过设置交换机和主机之间链路的带宽.延迟及丢包率,测试主机之间的性能.在 ...

  7. Vue学习手记03-路由跳转与路由嵌套

    1.路由跳转 添加一个LearnVue.vue文件, 在router->index.js中 引入import Learn from '@/components/LearnVue' 在touter ...

  8. Linux 一条命令杀死占用端口的所有进程

    Linux 一条命令杀死占用端口的所有进程 2018年05月28日 19:43:05 gq97 阅读数 7655更多 分类专栏: Linux   版权声明:本文为博主原创文章,遵循CC 4.0 BY- ...

  9. ArcGIS中国工具3.2新功能

    ArcGIS中国工具3.2新功能 1.       增加属性格式刷, 2.       编辑自动保存,每5分钟保存一次

  10. mac解压7z格式文件

    brew直接安装解压工具 $ brew search 7z p7zip $ brew install p7zip ==> Downloading https://downloads.source ...