0.引言

该系列博文主要在【官方文档】及【tkbSimplest】ABP框架理论研究系列博文的基础上进行总结的,或许大家会质问,别人都已经翻译过了,这不是多此一举吗?原因如下:

1.【tkbSimplest】的相关博文由于撰写得比较早的,在参照官方文档学习的过程中,发现部分知识未能及时同步(当前V4.0.2版本),如【EntityHistory】、【Multi-Lingual Engities】章节未涉及、【Caching】章节没有Entity Caching等内容。

2.进一步深入学习ABP的理论知识。

3.借此机会提高英文文档的阅读能力,故根据官方当前最新的版本,并在前人的基础上,自己也感受一下英文帮助文档的魅力。

好了,下面开始进入正题。

1.APB是什么?

ABP是ASP.NET Boilerplate的简称,从英文字面上理解它是一个关于ASP.NET的模板,在github上已经有5.7k的star(截止2018年11月21日)。官方的解释:ABP是一个开源且文档友好的应用程序框架。ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型。

ABP与最新的ASP.NET COREEF CORE版本保持同步,同样也支持ASP.NET MVC 5.x和EF6.x。

2.一个快速事例

让我们研究一个简单的类,看看ABP具有哪些优点:

public class TaskAppService : ApplicationService, ITaskAppService
{
private readonly IRepository<Task> _taskRepository; public TaskAppService(IRepository<Task> taskRepository)
{
_taskRepository = taskRepository;
} [AbpAuthorize(MyPermissions.UpdateTasks)]
public async Task UpdateTask(UpdateTaskInput input)
{
Logger.Info("Updating a task for input: " + input); var task = await _taskRepository.FirstOrDefaultAsync(input.TaskId);
if (task == null)
{
throw new UserFriendlyException(L("CouldNotFindTheTaskMessage"));
} input.MapTo(task);
}
}

这里我们看到一个Application Service(应用服务)方法。在DDD中,应用服务直接用于表现层(UI)执行应用程序的用例。那么在UI层中就可以通过javascript ajax的方式调用UpdateTask方法。

var _taskService = abp.services.app.task;
_taskService.updateTask(...);

3.ABP的优点

通过上述事例,让我们来看看ABP的一些优点:

依赖注入(Dependency Injection):ABP使用并提供了传统的DI基础设施。上述TaskAppService类是一个应用服务(继承自ApplicationService),所以它按照惯例以短暂(每次请求创建一次)的形式自动注册到DI容器中。同样的,也可以简单地注入其他依赖(如事例中的IRepository<Task>)。

部分源码分析:TaskAppService类继承自ApplicationService,IApplicaitonServcie又继承自ITransientDependency接口,在ABP框架中已经将ITransientDependency接口注入到DI容器中,所有继承自ITransientDependency接口的类或接口都会默认注入。

 //空接口
public interface ITransientDependency
{ } //应用服务接口
public interface IApplicationService : ITransientDependency
{ } //仓储接口
public interface IRepository : ITransientDependency
{ }
 public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
{
public void RegisterAssembly(IConventionalRegistrationContext context)
{
//注入到IOC,所有继承自ITransientDependency的类、接口等都会默认注入
context.IocManager.IocContainer.Register(
Classes.FromAssembly(context.Assembly)
.IncludeNonPublicTypes()
.BasedOn<ITransientDependency>()
.If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
.WithService.Self()
.WithService.DefaultInterfaces()
.LifestyleTransient()
); //Singleton
context.IocManager.IocContainer.Register(
Classes.FromAssembly(context.Assembly)
.IncludeNonPublicTypes()
.BasedOn<ISingletonDependency>()
.If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
.WithService.Self()
.WithService.DefaultInterfaces()
.LifestyleSingleton()
); //Windsor Interceptors
context.IocManager.IocContainer.Register(
Classes.FromAssembly(context.Assembly)
.IncludeNonPublicTypes()
.BasedOn<IInterceptor>()
.If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
.WithService.Self()
.LifestyleTransient()
);
}

仓储(Repository):ABP可以为每一个实体创建一个默认的仓储(如事例中的IRepository<Task>)。默认的仓储提供了很多有用的方法,如事例中的FirstOrDefault方法。当然,也可以根据需求扩展默认的仓储。仓储抽象了DBMS和ORMs,并简化了数据访问逻辑。

授权(Authorization):ABP可以通过声明的方式检查权限。如果当前用户没有【update task】的权限或没有登录,则会阻止访问UpdateTask方法。ABP不仅提供了声明属性的方式授权,而且还可以通过其它的方式。

部分源码分析:AbpAuthorizeAttribute类实现了Attribute,可在类或方法上通过【AbpAuthorize】声明。

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AbpAuthorizeAttribute : Attribute, IAbpAuthorizeAttribute
{
/// <summary>
/// A list of permissions to authorize.
/// </summary>
public string[] Permissions { get; } /// <summary>
/// If this property is set to true, all of the <see cref="Permissions"/> must be
granted.
/// If it's false, at least one of the <see cref="Permissions"/> must be granted.
/// Default: false.
/// </summary>
public bool RequireAllPermissions { get; set; } /// <summary>
/// Creates a new instance of <see cref="AbpAuthorizeAttribute"/> class.
/// </summary>
/// <param name="permissions">A list of permissions to authorize</param>
public AbpAuthorizeAttribute(params string[] permissions)
{
Permissions = permissions;
}
}

通过AuthorizationProvider类中的SetPermissions方法进行自定义授权。

 public abstract class AuthorizationProvider : ITransientDependency
{
/// <summary>
/// This method is called once on application startup to allow to define
permissions.
/// </summary>
/// <param name="context">Permission definition context</param>
public abstract void SetPermissions(IPermissionDefinitionContext context);
}

验证(Validation):ABP自动检查输入是否为null。它也基于标准数据注释特性和自定义验证规则验证所有的输入属性。如果请求无效,它会在客户端抛出适合的验证异常。

部分源码分析:ABP框架中主要通过拦截器ValidationInterceptor(AOP实现方式之一,)实现验证,该拦截器在ValidationInterceptorRegistrar的Initialize方法中调用。

internal static class ValidationInterceptorRegistrar
{
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
} private static void Kernel_ComponentRegistered(string key, IHandler handler)
{
if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(handler.ComponentModel.Implementation))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(ValidationInterceptor)));
}
}
}
 public class ValidationInterceptor : IInterceptor
{
private readonly IIocResolver _iocResolver; public ValidationInterceptor(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
} public void Intercept(IInvocation invocation)
{
if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Validation))
{
invocation.Proceed();
return;
} using (var validator = _iocResolver.ResolveAsDisposable<MethodInvocationValidator>())
{
validator.Object.Initialize(invocation.MethodInvocationTarget, invocation.Arguments);
validator.Object.Validate();
} invocation.Proceed();
}
}

自定义Customvalidator类

 public class CustomValidator : IMethodParameterValidator
{
private readonly IIocResolver _iocResolver; public CustomValidator(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
} public IReadOnlyList<ValidationResult> Validate(object validatingObject)
{
var validationErrors = new List<ValidationResult>(); if (validatingObject is ICustomValidate customValidateObject)
{
var context = new CustomValidationContext(validationErrors, _iocResolver);
customValidateObject.AddValidationErrors(context);
} return validationErrors;
}
}

审计日志(Audit Logging):基于约定和配置,用户、浏览器、IP地址、调用服务、方法、参数、调用时间、执行时长以及其它信息会为每一个请求自动保存。

部分源码分析:ABP框架中主要通过拦截器AuditingInterceptor(AOP实现方式之一,)实现审计日志,该拦截器在AuditingInterceptorRegistrar的Initialize方法中调用。

internal static class AuditingInterceptorRegistrar
{
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
if (!iocManager.IsRegistered<IAuditingConfiguration>())
{
return;
} var auditingConfiguration = iocManager.Resolve<IAuditingConfiguration>(); if (ShouldIntercept(auditingConfiguration, handler.ComponentModel.Implementation))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AuditingInterceptor)));
}
};
}
        private static bool ShouldIntercept(IAuditingConfiguration auditingConfiguration, Type type)
{
if (auditingConfiguration.Selectors.Any(selector => selector.Predicate(type)))
{
return true;
} if (type.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true))
{
return true;
} if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true)))
{
return true;
} return false;
}
}
 internal class AuditingInterceptor : IInterceptor
{
private readonly IAuditingHelper _auditingHelper; public AuditingInterceptor(IAuditingHelper auditingHelper)
{
_auditingHelper = auditingHelper;
} public void Intercept(IInvocation invocation)
{
if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget,
AbpCrossCuttingConcerns.Auditing))
{
invocation.Proceed();
return;
} if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget))
{
invocation.Proceed();
return;
} var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetType,
invocation.MethodInvocationTarget, invocation.Arguments); if (invocation.Method.IsAsync())
{
PerformAsyncAuditing(invocation, auditInfo);
}
else
{
PerformSyncAuditing(invocation, auditInfo);
}
} private void PerformSyncAuditing(IInvocation invocation, AuditInfo auditInfo)
{
var stopwatch = Stopwatch.StartNew(); try
{
invocation.Proceed();
}
catch (Exception ex)
{
auditInfo.Exception = ex;
throw;
}
finally
{
stopwatch.Stop();
auditInfo.ExecutionDuration =
Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
_auditingHelper.Save(auditInfo);
}
} private void PerformAsyncAuditing(IInvocation invocation, AuditInfo auditInfo)
{
var stopwatch = Stopwatch.StartNew(); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally(
(Task) invocation.ReturnValue,
exception => SaveAuditInfo(auditInfo, stopwatch, exception)
);
}
else //Task<TResult>
{
invocation.ReturnValue =
InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[],
invocation.ReturnValue,
exception => SaveAuditInfo(auditInfo, stopwatch, exception)
);
}
} private void SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception
exception)
{
stopwatch.Stop();
auditInfo.Exception = exception;
auditInfo.ExecutionDuration =
Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); _auditingHelper.Save(auditInfo);
}
}

工作单元(Unit Of Work):在ABP中,应用服务方法默认视为一个工作单元。它会自动创建一个连接并在方法的开始位置开启事务。如果方法成功完成并没有异常,事务会提交并释放连接。即使这个方法使用不同的仓储或方法,它们都是原子的(事务的)。当事务提交时,实体的所有改变都会自动保存。如上述事例所示,甚至不需要调用_repository.Update(task)方法。

部分源码分析:ABP框架中主要通过拦截器UnitOfWorkInterceptor(AOP实现方式之一,)实现工作单元,该拦截器在UnitOfWorkRegistrar的Initialize方法中调用。

internal class UnitOfWorkInterceptor : IInterceptor
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IUnitOfWorkDefaultOptions _unitOfWorkOptions; public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkDefaultOptions unitOfWorkOptions)
{
_unitOfWorkManager = unitOfWorkManager;
_unitOfWorkOptions = unitOfWorkOptions;
} /// <summary>
/// Intercepts a method.
/// </summary>
/// <param name="invocation">Method invocation arguments</param>
public void Intercept(IInvocation invocation)
{
MethodInfo method;
try
{
method = invocation.MethodInvocationTarget;
}
catch
{
method = invocation.GetConcreteMethod();
} var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{
//No need to a uow
invocation.Proceed();
return;
} //No current uow, run a new one
PerformUow(invocation, unitOfWorkAttr.CreateOptions());
} private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
{
if (invocation.Method.IsAsync())
{
PerformAsyncUow(invocation, options);
}
else
{
PerformSyncUow(invocation, options);
}
} private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
using (var uow = _unitOfWorkManager.Begin(options))
{
invocation.Proceed();
uow.Complete();
}
} private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
var uow = _unitOfWorkManager.Begin(options); try
{
invocation.Proceed();
}
catch
{
uow.Dispose();
throw;
} if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task) invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
else //Task<TResult>
{
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[],
invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
}
}

异常处理(Exception):在使用了ABP框架的Web应用程序中,我们几乎不用手动处理异常。默认情况下,所有的异常都会自动处理。如果发生异常,ABP会自动记录并给客户端返回合适的结果。例如:对于一个ajax请求,返回一个json对象给客户端,表明发生了错误。但会对客户端隐藏实际的异常,除非像上述事例那样使用UserFriendlyException方法抛出。它也理解和处理客户端的错误,并向客户端显示合适的信息。

部分源码分析:UserFriendlyException抛出异常方法。

 [Serializable]
public class UserFriendlyException : AbpException, IHasLogSeverity, IHasErrorCode
{
/// <summary>
/// Additional information about the exception.
/// </summary>
public string Details { get; private set; } /// <summary>
/// An arbitrary error code.
/// </summary>
public int Code { get; set; } /// <summary>
/// Severity of the exception.
/// Default: Warn.
/// </summary>
public LogSeverity Severity { get; set; } /// <summary>
/// Constructor.
/// </summary>
public UserFriendlyException()
{
Severity = LogSeverity.Warn;
} /// <summary>
/// Constructor for serializing.
/// </summary>
public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{ } /// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message</param>
public UserFriendlyException(string message)
: base(message)
{
Severity = LogSeverity.Warn;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="severity">Exception severity</param>
public UserFriendlyException(string message, LogSeverity severity)
: base(message)
{
Severity = severity;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="code">Error code</param>
/// <param name="message">Exception message</param>
public UserFriendlyException(int code, string message)
: this(message)
{
Code = code;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="details">Additional information about the exception</param>
public UserFriendlyException(string message, string details)
: this(message)
{
Details = details;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="code">Error code</param>
/// <param name="message">Exception message</param>
/// <param name="details">Additional information about the exception</param>
public UserFriendlyException(int code, string message, string details)
: this(message, details)
{
Code = code;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public UserFriendlyException(string message, Exception innerException)
: base(message, innerException)
{
Severity = LogSeverity.Warn;
} /// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="details">Additional information about the exception</param>
/// <param name="innerException">Inner exception</param>
public UserFriendlyException(string message, string details, Exception innerException)
: this(message, innerException)
{
Details = details;
}
}

日志(Logging):由上述事例可见,可以通过在基类定义的Logger对象来写日志。ABP默认使用了Log4Net,但它是可更改和可配置的。

部分源码分析:Log4NetLoggerFactory类。

 public class Log4NetLoggerFactory : AbstractLoggerFactory
{
internal const string DefaultConfigFileName = "log4net.config";
private readonly ILoggerRepository _loggerRepository; public Log4NetLoggerFactory()
: this(DefaultConfigFileName)
{
} public Log4NetLoggerFactory(string configFileName)
{
_loggerRepository = LogManager.CreateRepository(
typeof(Log4NetLoggerFactory).GetAssembly(),
typeof(log4net.Repository.Hierarchy.Hierarchy)
); var log4NetConfig = new XmlDocument();
log4NetConfig.Load(File.OpenRead(configFileName));
XmlConfigurator.Configure(_loggerRepository, log4NetConfig["log4net"]);
} public override ILogger Create(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
} return new Log4NetLogger(LogManager.GetLogger(_loggerRepository.Name, name), this);
} public override ILogger Create(string name, LoggerLevel level)
{
throw new NotSupportedException("Logger levels cannot be set at runtime. Please review your configuration file.");
}
}

本地化(Localization):注意,在上述事例中使用了L("XXX")方法处理抛出的异常。因此,它会基于当前用户的文化自动实现本地化。详细见后续本地化章节。

部分源码分析:......

自动映射(Auto Mapping):在上述事例最后一行代码,使用了ABP的MapTo扩展方法将输入对象的属性映射到实体属性。ABP使用AutoMapper第三方库执行映射。根据命名惯例可以很容易的将属性从一个对象映射到另一个对象。

部分源码分析:AutoMapExtensions类中的MapTo()方法。

 public static class AutoMapExtensions
{ public static TDestination MapTo<TDestination>(this object source)
{
return Mapper.Map<TDestination>(source);
} public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
{
return Mapper.Map(source, destination);
} ......
}

动态API层(Dynamic API Layer):在上述事例中,TaskAppService实际上是一个简单的类。通常必须编写一个Web API Controller包装器给js客户端暴露方法,而ABP会在运行时自动完成。通过这种方式,可以在客户端直接使用应用服务方法。

部分源码分析:......

动态javascript ajax代理(Dynamic JavaScript AJAX Proxy):ABP创建动态代理方法,从而使得调用应用服务方法就像调用客户端的js方法一样简单。

部分源码分析:......

4.本章小节

通过上述简单的类可以看到ABP的优点。完成所有这些任务通常需要花费大量的时间,但是ABP框架会自动处理。

除了这个上述简单的事例外,ABP还提供了一个健壮的基础设施和开发模型,如模块化、多租户、缓存、后台工作、数据过滤、设置管理、领域事件、单元&集成测试等等,那么你可以专注于业务代码,而不需要重复做这些工作(DRY)

【ABP框架系列学习】介绍篇(1)的更多相关文章

  1. ABP框架实践基础篇之开发UI层

    返回总目录<一步一步使用ABP框架搭建正式项目系列教程> 说明 其实最开始写的,就是这个ABP框架实践基础篇.在写这篇博客之前,又回头复习了一下ABP框架的理论,如果你还没学习,请查看AB ...

  2. C#高级知识点&(ABP框架理论学习高级篇)——白金版

    前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...

  3. ABP 框架学习-01篇

    从来没有自己写过太多的技术性文章,博客里面的文章都是拷贝别人的东西,做一个笔记功能给自己用的.最近觉得应该写点自己的学习博客 https://aspnetboilerplate.com/ ABP框架, ...

  4. 【ABP框架系列学习】启动配置(5)

    ABP提供了在启动时配置模块的基础设施和模型. 1.配置ABP 配置ABP是在模块的PreInitialize方法中完成的,例如: public class SimpleTaskSystemModul ...

  5. 【ABP框架系列学习】模块系统(4)之插件示例开发

    0.引言 上一篇博文主要介绍了ABP模块及插件的相关知识,本章节主要开发一个插件示例来学习如何创建一个插件,并在应用程序中使用.这个命名为FirstABPPlugin的插件主要在指定的时间段内删除审计 ...

  6. 【ABP框架系列学习】模块系统(4)

    0.引言 ABP提供了构建模块和通过组合模块以创建应用程序的基础设施.一个模块可以依赖于另外一个模块.通常,程序集可以认为是模块.如果创建多个程序集的应用程序,建议为每个程序集创建模块定义. 当前,模 ...

  7. 【ABP框架系列学习】N层架构(3)

    目录 0.引言 1.DDD分层 2.ABP应用构架模型 客户端应用程序(Client Applications) 表现层(Presentation Layer) 分布式服务层(Distributed ...

  8. 180719-Quick-Task 动态脚本支持框架之使用介绍篇

    文章链接:https://liuyueyi.github.io/hexblog/2018/07/19/180719-Quick-Task-动态脚本支持框架之使用介绍篇/ Quick-Task 动态脚本 ...

  9. 2019 年起如何开始学习 ABP 框架系列文章-开篇有益

    2019 年起如何开始学习 ABP 框架系列文章-开篇有益 [[TOC]] 本系列文章推荐阅读地址为:52ABP 开发文档 https://www.52abp.com/Wiki/52abp/lates ...

随机推荐

  1. Maximum Width Ramp LT962

    Given an array A of integers, a ramp is a tuple (i, j) for which i < j and A[i] <= A[j].  The ...

  2. JavaScript RegExp(正则)

    第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp('正则表达式')创建一个RegExp对象. 两种写法是一样的: var re1 = /ABC\-001/; var re ...

  3. CUDA C

    一.CUDA结构 硬件:GPU(Graphics Processing Unit)   SM(Streaming Multiprocessor)     SP(Streaming Processor) ...

  4. day33 锁和队列

    队列 #put 和  get #__author : 'liuyang' #date : 2019/4/16 0016 上午 11:32 # 多进程之间的数据是隔离的 # 进程之间的数据交互 # 是可 ...

  5. 用python turtle实现汉诺塔的移动

    1.汉诺塔 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小 ...

  6. SpringBoot_定制banner

    SpringBoot项目在启动时会打印一个banner 这个banner 是可以定制的, 在resources 目录下创建一个banner.txt 文件,在这个文件中写入的文本将在项目启动时打印出来. ...

  7. 2019.03.25 bzoj4567: [Scoi2016]背单词(trie+贪心)

    传送门 题意: 给你n个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串s的位置为x): 1.排在s后面的字符串有s的后缀,则代价为n^2: 2.排在s前面的字符串有s的后缀,且没有排在s ...

  8. 2019.03.25 bzoj2329: [HNOI2011]括号修复(fhq_treap)

    传送门 题意简述: 给一个括号序列,要求支持: 区间覆盖 区间取负 区间翻转 查询把一个区间改成合法括号序列最少改几位 思路: 先考虑静态的时候如何维护答案. 显然把所有合法的都删掉之后序列长这样: ...

  9. Qt打包发布exe

    1.首先以 release 方式编译源代码,然后将生成的a. exe 程序放到一个单独的文件夹中. 2.再从开始菜单打开 Qt 命令行工具. 3.在命令行中,进入到第一步中a. exe 程序所在的文件 ...

  10. python turtle库

    turtle库初步 先看 https://www.cnblogs.com/chy8/p/9448606.html 一 turtle库介绍 turtle乌龟 import turtle from tur ...