上一篇我们介绍了AOP的基本概览,并使用动态代理的方式添加了服务日志;本章我们将介绍过滤与搜索、分页与排序并添加对应的功能


注:本章内容大多是基于solenovex的使用 ASP.NET Core 3.x 构建 RESTful Web API视频内容,若想进一步了解相关知识,请查看原视频

一、过滤与搜索

1、定义

1、什么是过滤?意思就是把某个字段的名字及希望匹配的值传递给系统,系统根据条件限定返回的集合内容;

按点外卖的例子来说,食物类别、店铺评分、距离远近等过滤条件提供给你,您自个儿根据需求筛选,系统返回过滤后的内容给你;

2、什么是搜索?意思就是把需要搜索的值传递给系统,系统按照其内部逻辑查找符合条件的数据,完成后将数据添加到集合中返回;

还是按点外卖的例子来说,一哥们张三特别喜欢吃烧烤,他在搜索栏中搜索烧烤,会出现什么?食物类别是烧烤的,店铺名称是烧烤的,甚至会有商品名称包含烧烤的,当然具体出现什么还要看系统的内部逻辑;

3、相同点及差异

  • 相同点:过滤和搜索的参数并不是资源的一部分,而是使用者根据实际需求自行添加的;

  • 差异:过滤一般是一个完整的集合,根据条件把匹配或不匹配的数据移除;

    ​ 搜索一般是一个空集合,根据条件把匹配或不匹配的数据往里面添加

2、实际应用

1、在前面的章节我们有提到过数据模型的概览,即用户看到的和存储在数据库的可能不是一个字段,所以在实际进行过滤或搜索操作时,用户只能针对他看到的资源的字段进行过滤或搜索操作,所以内部逻辑要考虑到这一点;

2、在实际开发中,会有添加字段的情况,那就意味着过滤/搜索的条件是会变化的,为了适应这种不确定性,我们可以针对过滤/搜索条件建立对应的类,在类的内部添加过滤/搜索条件。

3、实际应用时过滤和搜索经常会配合使用

3、基于项目的添加

3.1、添加参数类

我们在Model层添加一个Parameters文件夹,这里计划以文章作为演示,所以我们添加一个ArticleParameters类,添加过滤字段和搜索字段,如下:

using System;

namespace BlogSystem.Model.Parameters
{
public class ArticleParameters
{
//过滤条件——距离时间
public DistanceTime DistanceTime { get; set; } //搜索条件
public string SearchStr { get; set; }
} public enum DistanceTime
{
Week = 1,
Month = 2,
Year = 3,
}
}

3.2、添加接口

在IBLL层的IArticleService中添加对应的过滤搜索方法,其返回值是文章集合,如下:

        /// <summary>
/// 文章过滤及搜索
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
Task<List<ArticleListViewModel>> GetArticles(ArticleParameters parameters);

3.3、方法实现

在BLL层的ArticleService中实现上一步新增的接口方法,如下:

        /// <summary>
/// 文章过滤及搜索
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public async Task<List<ArticleListViewModel>> GetArticles(ArticleParameters parameters)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters)); var resultList = _articleRepository.GetAll(); var dateTime = DateTime.Now; //过滤条件,判断枚举是否引用
if (Enum.IsDefined(typeof(DistanceTime), parameters.DistanceTime))
{
switch (parameters.DistanceTime)
{
case DistanceTime.Week:
dateTime = dateTime.AddDays(-7);
break;
case DistanceTime.Month:
dateTime = dateTime.AddMonths(-1);
break;
case DistanceTime.Year:
dateTime = dateTime.AddYears(-1);
break;
}
resultList = resultList.Where(m => m.CreateTime > dateTime);
} //搜索条件,暂时添加标题和内容
if (!string.IsNullOrWhiteSpace(parameters.SearchStr))
{
parameters.SearchStr = parameters.SearchStr.Trim();
resultList = resultList.Where(m =>
m.Title.Contains(parameters.SearchStr) || m.Content.Contains(parameters.SearchStr));
} //返回最终结果
return await resultList.Select(m => new ArticleListViewModel
{
ArticleId = m.Id,
Title = m.Title,
Content = m.Content,
CreateTime = m.CreateTime,
Account = m.User.Account,
ProfilePhoto = m.User.ProfilePhoto
}).ToListAsync();
}

3.4、控制层调用

在BlogSystem.Core项目的ArticleController中添加筛选/搜索方法,如下:

        /// <summary>
/// 通过过滤/搜索查询符合条件的文章
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetArticles(ArticleParameters parameters)
{
var list = await _articleService.GetArticles(parameters);
return Ok(list);
}

3.5、问题与功能实现

运行后选择对应的筛选条件,输入对应的查询字段,查询发现出现如下错误:TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method c,还记得上一节提到的对象绑定吗?跳转查看,这里我们需要手动指定查询参数的来源为[FromQuery],修改并编译后重新运行,输入过滤和搜索条件,成功执行

二、分页

1、分页说明

  • 通常在集合资源比较大的情况下,会进行翻页查询,来避免可能出现的性能问题;
  • 系统默认情况下就应该进行分页,且操作对象应该是底层的数据;
  • 一般情况下查询参数分为每页的个数PageSize和页码PageNumber,且会通过QueryString传递;

2、实际应用

  • 我们应该对每页的个数PageSize进行控制,防止用户录入一个比较大的数字;
  • 我们应该设定一个默认值,用户不指定页码和数量的情况下则按默认数值进行查询
  • 分页应该在过滤和搜索之后进行,否则结果会不准确

3、一般实现

3.1、添加默认参数

在添加过滤和搜索功能时,我们添加了一个ArticleParameters类用来放置条件参数,同样我们可以把分页相关的参数放置在这个类里面,如下:

3.2、逻辑方法调整

我们选择过滤与搜索时添加的ArticleService类中的GetArticles方法,在最终tolist前进行分页操作,如下:

4、进阶实现

4.1、说明

除了数据集合外,我们可以将前后页的链接,当前页码,当前页面的数量,总记录数,总页数等信息一并返回

返回的信息放在哪里也是一个问题,部分开发者习惯将上述信息放置在Http响应的Body中,虽然使用上没有任何问题,但是翻页信息不是资源表述的一部分,所以从RESTful风格看它破坏了自我描述性信息约束,API的消费者不知到如何使用application/json这个媒体类型来解释响应内容,而针对这类问题我们一般将此类信息放在Http响应Header的X-Pagination中

4.2、实现

1、首先我们在BlogSystem.Model项目中新建一个Helpers文件夹,并在其中新建一个PageList类,先将需要返回的翻页信息声明为属性,并在构造函数中初始化这些信息,信息对应的数据则由一个异步的静态方法提供, 具体实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; namespace BlogSystem.Model.Helpers
{
public class PageList<T> : List<T>
{
//当前页码
public int CurrentPage { get; }
//总页码数
public int TotalPages { get; }
//每页数量
public int PageSize { get; }
//结果数量
public int TotalCount { get; }
//是否有前一页
public bool HasPrevious => CurrentPage > 1;
//是否有后一页
public bool HasNext => CurrentPage < TotalPages; //初始化翻页信息
public PageList(List<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
AddRange(items);
} //创建分页信息
public static async Task<PageList<T>> CreatePageMsgAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PageList<T>(items, count, pageNumber, pageSize);
}
}
}

2、我们将IArticleService中的GetArticles方法的返回值,以及ArticelService中的GetArticle方法修改如下:

Task<PageList<ArticleListViewModel>> GetArticles(ArticleParameters parameters);

3、当前页的前一页和后一页的链接信息如何获得?我们可以借助Url类的link方法,前提是对应的方法有它自身的名字,将ArticleController中的GetArticles方法命个名,并添加名字为CreateArticleUrl的方法来生成link,具体实现如下;其中UriType是一个枚举类型,我们将它放在了Model层的Helpers文件夹下

namespace BlogSystem.Model.Helpers
{
public enum UrlType
{
PreviousPage,
NextPage
}
}
        //返回前一页面,后一页,以及当前页的url信息
private string CreateArticleUrl(ArticleParameters parameters, UrlType type)
{
var isDefined = Enum.IsDefined(typeof(DistanceTime), parameters.DistanceTime); switch (type)
{
case UrlType.PreviousPage:
return Url.Link(nameof(GetArticles), new
{
pageNumber = parameters.PageNumber - 1,
pageSize = parameters.PageSize,
distanceTime = isDefined ? parameters.DistanceTime.ToString() : null,
searchStr = parameters.SearchStr
});
case UrlType.NextPage:
return Url.Link(nameof(GetArticles), new
{
pageNumber = parameters.PageNumber + 1,
pageSize = parameters.PageSize,
distanceTime = isDefined ? parameters.DistanceTime.ToString() : null,
searchStr = parameters.SearchStr
});
default:
return Url.Link(nameof(GetArticles), new
{
pageNumber = parameters.PageNumber,
pageSize = parameters.PageSize,
distanceTime = isDefined ? parameters.DistanceTime.ToString() : null,
searchStr = parameters.SearchStr
});
}
}

对应的Controller方法修改如下:

        /// <summary>
/// 过滤/搜索文章信息并返回list和分页信息
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
[HttpGet("search", Name = nameof(GetArticles))]
public async Task<IActionResult> GetArticles([FromQuery]ArticleParameters parameters)
{
var list = await _articleService.GetArticles(parameters); var previousPageLink = list.HasPrevious ? CreateArticleUrl(parameters, UrlType.PreviousPage) : null; var nextPageLink = list.HasNext ? CreateArticleUrl(parameters, UrlType.NextPage) : null; var paginationX = new
{
totalCount = list.TotalCount,
pageSize = list.PageSize,
currentPage = list.CurrentPage,
totalPages = list.TotalPages,
previousPageLink,
nextPageLink
}; Response.Headers.Add("Pagination-X", JsonSerializer.Serialize(paginationX)); return Ok(list);
}

4.3、实现效果

如下图,可以看到Header中多了一行名为Pagination-X的key,且对应value中存在下一页的url,但是系统默认进行了转义&符号无法正常显示,所以这里我们在传入Header时做如下处理

Response.Headers.Add("Pagination-X", JsonSerializer.Serialize(paginationX, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}));

三、排序

1、排序说明

通常情况下我们会使用QueryString针对多个字段为集合资源进行排序,字段默认为正序,也可以添加desc改变为倒序

2、实际应用

  • 实际应用中字段对应的通常是Dto或ViewModel的字段而非数据库字段,所以可能会存在数据映射的问题;
  • 目前我们只能使用属性名所对应的字符串进行排序,而不是使用lambda表达式;这里我们可以借助Linq的扩展库来解决这个问题,只要Dto/VieModel中存在这个字段,就进行排序,避免我们手动去匹配字符串的对应关系
  • 此外我们需要考虑复用性的问题,可以针对IQueryable新增一个排序的扩展方法

3、一般实现

不考虑上述说明,进行最简单的排序方法

  1. 这里我们还是使用ArticleController进行演示,先在Model层Parameters文件夹的ArticleParameters文件中添加排序属性,这里我们添加一项为创建时间,如下:public string Orderby { get; set; } = "CreateTime";
  2. 在BLL层的ArticleService的GetArticles方法中添加实如下逻辑,即可完成一般排序

4、进阶实现

一般方法只能实现最简单的一种排序且无法复用,不灵活,下面我们自定义方法实现第一二点中的功能

1、先来看一下具体的实现思路,左边为层级关系,右边为需要实现的类;如果有点晕可以先敲完再完再回头看

2、由于逻辑相对复杂,所以在BlogSystem.Common层的Helpers文件夹中再建立一个SortHelper文件夹;

3、在SortHelper文件夹下建立PropertyMapping类,用来定义属性之间的映射关系,如下:

using System;
using System.Collections.Generic; namespace BlogSystem.Common.Helpers.SortHelper
{
//定义属性之间的映射关系
public class PropertyMapping
{
//针对可能出现的一对多的情况——如name对应的是firstName+lastName
public IEnumerable<string> DestinationProperties { get; set; } //针对出生日期靠前但是对应年龄大的情况
public bool Revert { get; set; } public PropertyMapping(IEnumerable<string> destinationProperties, bool revert = false)
{
DestinationProperties = destinationProperties ?? throw new ArgumentNullException(nameof(destinationProperties));
Revert = revert;
}
}
}

4、在SortHelper文件夹下建立ModelMapping类,用来定义两个类之间的映射关系,如下:

using System;
using System.Collections.Generic; namespace BlogSystem.Common.Helpers.SortHelper
{
//定义模型对象之间的映射关系,如xxx对应xxxDto
public class ModelMapping<TSource, TDestination>
{
public Dictionary<string, PropertyMapping> MappingDictionary { get; private set; } public ModelMapping(Dictionary<string, PropertyMapping> mappingDictionary)
{
MappingDictionary = mappingDictionary ?? throw new ArgumentNullException(nameof(mappingDictionary));
}
}
}

5、在SortHelper文件夹下建立PropertyMappingService类,里面是针对属性映射情况的逻辑处理;但在使用ModelMapping时发现无法解析泛型类型,所以我们需要使用一个空的接口来为其打上标签。 如下,新增空接口,添加ModelMapping继承此接口

namespace BlogSystem.Common.Helpers.SortHelper
{
//标记接口,只用来给对象打上标签
public interface IModelMapping
{
}
}

PropertyMappingService类的实现如下:

using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq; namespace BlogSystem.Common.Helpers.SortHelper
{
//属性映射处理
public class PropertyMappingService
{
//一个只读属性的字典,里面是Dto和数据库表字段的映射关系
private readonly Dictionary<string, PropertyMapping> _articlePropertyMapping
= new Dictionary<string, PropertyMapping>(StringComparer.OrdinalIgnoreCase) //忽略大小写
{
{"Id",new PropertyMapping(new List<string>{"Id"}) },
{"Title",new PropertyMapping(new List<string>{"Title"}) },
{"Content",new PropertyMapping(new List<string>{"Content"}) },
{"CreateTime",new PropertyMapping(new List<string>{"CreateTime"}) }
}; //需要解决ModelMapping泛型关系无法建立问题,可新增的一个空的标志接口
private readonly IList<IModelMapping> _propertyMappings = new List<IModelMapping>(); //构造函数——内部添加的是类和类的映射关系以及属性和属性的映射关系
public PropertyMappingService()
{
_propertyMappings.Add(new ModelMapping<ArticleListViewModel, Article>(_articlePropertyMapping));
} //通过两个类的类型获取映射关系
public Dictionary<string, PropertyMapping> GetPropertyMapping<TSource, TDestination>()
{
var matchingMapping = _propertyMappings.OfType<ModelMapping<TSource, TDestination>>();
var propertyMappings = matchingMapping.ToList();
if (propertyMappings.Count == 1)
{
return propertyMappings.First().MappingDictionary;
}
throw new Exception($"无法找到唯一的映射关系:{typeof(TSource)},{typeof(TDestination)}");
}
}
}

6、最后由于需要通过依赖注入的方式进行使用,所以需要新增一个接口,添加PropertyMappingService继承此接口

using System.Collections.Generic;

namespace BlogSystem.Common.Helpers.SortHelper
{
//实现依赖注入新建的接口——对应的是属性映射服务
public interface IPropertyMappingService
{
Dictionary<string, PropertyMapping> GetPropertyMapping<TSource, TDestination>();
}
}

7、针对IQueryable新增一个排序的扩展方法IQueryableExtensions,放在Common层的SortHelper文件夹中,最后orderby时需要使用NuGet包安装System.Linq.Dynamic.Core,并引入命名空间,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core; namespace BlogSystem.Common.Helpers.SortHelper
{
//排序扩展方法
public static class IQueryableExtensions
{
public static IQueryable<T> ApplySort<T>(this IQueryable<T> source, string orderBy, Dictionary<string, PropertyMapping> mappingDictionary)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} if (mappingDictionary == null)
{
throw new ArgumentNullException(nameof(mappingDictionary));
} if (string.IsNullOrWhiteSpace(orderBy))
{
return source;
} //分隔orderby字段
var orderByAfterSplit = orderBy.Split(",");
foreach (var orderByClause in orderByAfterSplit.Reverse())
{
var trimmedOrderByClause = orderByClause.Trim();
//判断是否以倒序desc结尾
var orderDescending = trimmedOrderByClause.EndsWith(" desc");
//获取空格的索引
var indexOfFirstSpace = trimmedOrderByClause.IndexOf(" ", StringComparison.Ordinal);
//根据有无空格获取属性
var propertyName = indexOfFirstSpace ==
-1 ? trimmedOrderByClause : trimmedOrderByClause.Remove(indexOfFirstSpace);
//不含映射则抛出错误
if (!mappingDictionary.ContainsKey(propertyName))
{
throw new ArgumentNullException($"没有找到Key为{propertyName}的映射");
}
//否则取出属性映射关系
var propertyMappingValue = mappingDictionary[propertyName];
if (propertyMappingValue == null)
{
throw new ArgumentNullException(nameof(propertyMappingValue));
} //一次取出属性值进行排序
foreach (var destinationProperty in propertyMappingValue.DestinationProperties.Reverse())
{
if (propertyMappingValue.Revert)
{
orderDescending = !orderDescending;
}
//orderby需要安装System.Linq.Dynamic.Core库
source = source.OrderBy(destinationProperty + (orderDescending ? " descending" : " ascending"));
}
} return source;
}
}
}

8、在BlogSystem.BLL中的ArticleService类构造函数中注入IPropertyMappingService接口,如下:

9、在ArticleService中使用新增的IQueryable扩展方法实现排序逻辑,如下:

10、在BlogSystem.Core的StartUp类的ConfigureServices方法进行注册,这里我添加在的位置是方法内部的最后位置:

//自定义判断属性隐射关系
services.AddTransient<IPropertyMappingService, PropertyMappingService>();

11、运行后可以通过QueryString的形式,比如:?orderby=createtime或者?orderby=createtime desc或者orderby=createtime desc,title之类的形式进行排序查询(实际上createtime和title的组合无意义,可根据实际情况使用),如下:

5、进阶问题解决

1、在进行分页操作时我们有添加前后页的信息,但是在排序后,前后页面信息是不包括排序信息的,所以我们需要解决这一问题,在ArticleController中的CreateArticleUrl创建的3个Url中添加 orderBy = parameters.Orderby即可;

2、此外我们发现,输入一个不存在的排序字段时虽然弹出了我们预先添加的错误提示,错误代码却是500,但是这一错误并不是服务端引起的,在Common层的PropertyMappingService类中添加判断字段是否存在的逻辑。此外需要在PropertyMappingService对应的接口IPropertyMappingService中添加这一方法,方法逻辑如下:

 		//判断字符串是否存在
public bool PropertyMappingExist<TSource, TDestination>(string fields)
{
var propertyMapping = GetPropertyMapping<TSource, TDestination>();
if (string.IsNullOrWhiteSpace(fields))
{
return true;
} //查询字符串逗号分隔
var fieldAfterSplit = fields.Split(",");
foreach (var field in fieldAfterSplit)
{
var trimmedFields = field.Trim();//字段去空
var indexOfFirstSpace = trimmedFields.IndexOf(" ", StringComparison.Ordinal);//获取字段中第一个空格的索引
//空格不存在,则属性名为其本身,否则移除空格
var propertyName = indexOfFirstSpace == -1 ? trimmedFields : trimmedFields.Remove(indexOfFirstSpace);
//只要有一个字段对应不上就返回fasle
if (!propertyMapping.ContainsKey(propertyName))
{
return false;
}
} return true;
}

3、完成上述操作后,在ArticleController的构造函数中注入该服务,并在GetArticles方法中添加判断

4、再次运行,可以发现前后页面信息中已经包括了排序信息,且遇到不存在的字段时也是正常返回客户端异常

本章完~


本人知识点有限,若文中有错误的地方请及时指正,方便大家更好的学习和交流。

本文部分内容参考了网络上的视频内容和文章,仅为学习和交流,视频地址如下:

solenovex,ASP.NET Core 3.x 入门视频

solenovex,使用 ASP.NET Core 3.x 构建 RESTful Web API

声明

学习ASP.NET Core(08)-过滤搜索与分页排序的更多相关文章

  1. 学习ASP.NET Core Razor 编程系列十六——排序

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  2. 学习ASP.NET Core Razor 编程系列十九——分页

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  3. 学习ASP.NET Core Razor 编程系列十八——并发解决方案

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  4. 学习ASP.NET Core Razor 编程系列十七——分组

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  5. 学习ASP.NET Core Razor 编程系列九——增加查询功能

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  6. 从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目

    原文:从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.ne ...

  7. 022年9月12日 学习ASP.NET Core Blazor编程系列三——实体

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  8. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  9. asp.net core MVC 过滤器之ActionFilter过滤器(二)

    本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core ...

随机推荐

  1. shell基础知识DAY2

    1.管道符(|):把一个命令的输出,把输出的内容传递给管道符后面命令的输入.如:ls -l | grep "^[^d]".2.jobs作业控制,后台运行bg PID,前台运行fg ...

  2. extend()和append()区别

    extend()和append()都可以用来添加. 例: a = [1,2,3,4,5,6] b = [7,8,9,10] c = {'aa':123,'bb':456,'cc':789} 1.    ...

  3. Codeforce-CodeCraft-20 (Div. 2)-A. Grade Allocation

    n students are taking an exam. The highest possible score at this exam is m. Let ai be the score of ...

  4. 数据结构--栈(附上STL栈)

    定义: 栈是一种只能在某一端插入和删除数据的特殊线性表.他按照先进先出的原则存储数据,先进的数据被压入栈底,最后进入的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后被压入栈的,最先弹出).因此栈 ...

  5. andorid jar/库源码解析之Butterknife

    目录:andorid jar/库源码解析 Butterknife: 作用: 用于初始化界面控件,控件方法,通过注释进行绑定控件和控件方法 栗子: public class MainActivity e ...

  6. Spring AOP概述

    一.AOP的基本概念: 首先先给出一段比较专业的术语: 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的 ...

  7. Codeforces Round #575 (Div. 3) 昨天的div3 补题

    Codeforces Round #575 (Div. 3) 这个div3打的太差了,心态都崩了. B. Odd Sum Segments B 题我就想了很久,这个题目我是找的奇数的个数,因为奇数想分 ...

  8. 2019国防科大校赛 B Escape LouvreⅡ

    https://ac.nowcoder.com/acm/contest/878/B 这个题目是一个网络流,但是建图却没有那么好建,首先我们都会把每一个人与源点相连,每一个洞口和汇点相连. 然后人和洞口 ...

  9. 【Swift】获取UILabel中点击的某个功能标签文字并作出响应动作

    1.需求 首先.针对UILabel中显示的多个功能标签,作出颜色标记提示. 其次.对关键字作出点击响应动作. 如图所示: 解决: 1.使用正则匹配到关键字 public static var hash ...

  10. DHCP报文(1)

    DHCP报文 1.地址申请类型(4步工作原理,常考) (1)此题是典型的四步工作原理,在其配置过程中由于没有分配IP地址,用的是广播形式,所以其4中报文类型的目的IP地址均为255.255.255.2 ...